1069 lines
31 KiB
C
1069 lines
31 KiB
C
/*
|
|
* Copyright 2015 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
/*
|
|
* SHI driver for Chrome EC.
|
|
*
|
|
* This uses Input/Output buffer to handle SPI transmission and reception.
|
|
*/
|
|
|
|
#include "chipset.h"
|
|
#include "clock.h"
|
|
#include "console.h"
|
|
#include "gpio.h"
|
|
#include "task.h"
|
|
#include "hooks.h"
|
|
#include "host_command.h"
|
|
#include "registers.h"
|
|
#include "spi.h"
|
|
#include "system.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
|
|
#define CPUTS(outstr) cputs(CC_SPI, outstr)
|
|
#define CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
|
|
#define CPRINTF(format, args...) cprintf(CC_SPI, format, ## args)
|
|
|
|
#if !(DEBUG_SHI)
|
|
#define DEBUG_CPUTS(...)
|
|
#define DEBUG_CPRINTS(...)
|
|
#define DEBUG_CPRINTF(...)
|
|
#else
|
|
#define DEBUG_CPUTS(outstr) cputs(CC_SPI, outstr)
|
|
#define DEBUG_CPRINTS(format, args...) cprints(CC_SPI, format, ## args)
|
|
#define DEBUG_CPRINTF(format, args...) cprintf(CC_SPI, format, ## args)
|
|
#endif
|
|
|
|
/* SHI Bus definition */
|
|
#ifdef NPCX_SHI_V2
|
|
#define SHI_OBUF_FULL_SIZE 128 /* Full output buffer size */
|
|
#define SHI_IBUF_FULL_SIZE 128 /* Full input buffer size */
|
|
/* Configure the IBUFLVL2 = the size of V3 protocol header */
|
|
#define SHI_IBUFLVL2_THRESHOLD (sizeof(struct ec_host_request))
|
|
#else
|
|
#define SHI_OBUF_FULL_SIZE 64 /* Full output buffer size */
|
|
#define SHI_IBUF_FULL_SIZE 64 /* Full input buffer size */
|
|
#endif
|
|
#define SHI_OBUF_HALF_SIZE (SHI_OBUF_FULL_SIZE/2) /* Half output buffer size */
|
|
#define SHI_IBUF_HALF_SIZE (SHI_IBUF_FULL_SIZE/2) /* Half input buffer size */
|
|
|
|
/* Start address of SHI output buffer */
|
|
#define SHI_OBUF_START_ADDR (volatile uint8_t *)(NPCX_SHI_BASE_ADDR + 0x020)
|
|
/* Middle address of SHI output buffer */
|
|
#define SHI_OBUF_HALF_ADDR (SHI_OBUF_START_ADDR + SHI_OBUF_HALF_SIZE)
|
|
/* Top address of SHI output buffer */
|
|
#define SHI_OBUF_FULL_ADDR (SHI_OBUF_START_ADDR + SHI_IBUF_FULL_SIZE)
|
|
/*
|
|
* Valid offset of SHI output buffer to write.
|
|
* When SIMUL bit is set, IBUFPTR can be used instead of OBUFPTR
|
|
*/
|
|
#define SHI_OBUF_VALID_OFFSET ((shi_read_buf_pointer() + \
|
|
SHI_OUT_PREAMBLE_LENGTH) % SHI_OBUF_FULL_SIZE)
|
|
/* Start address of SHI input buffer */
|
|
#define SHI_IBUF_START_ADDR (&NPCX_IBUF(0))
|
|
/* Current address of SHI input buffer */
|
|
#define SHI_IBUF_CUR_ADDR (SHI_IBUF_START_ADDR + shi_read_buf_pointer())
|
|
|
|
/*
|
|
* Timeout to wait for SHI request packet
|
|
*
|
|
* This affects the slowest SPI clock we can support. A delay of 8192 us
|
|
* permits a 512-byte request at 500 KHz, assuming the master starts sending
|
|
* bytes as soon as it asserts chip select. That's as slow as we would
|
|
* practically want to run the SHI interface, since running it slower
|
|
* significantly impacts firmware update times.
|
|
*/
|
|
#define SHI_CMD_RX_TIMEOUT_US 8192
|
|
|
|
/* Timeout for glitch case. Make sure it will exceed 8 SPI clocks */
|
|
#define SHI_GLITCH_TIMEOUT_US 10000
|
|
|
|
/*
|
|
* The AP blindly clocks back bytes over the SPI interface looking for a
|
|
* framing byte. So this preamble must always precede the actual response
|
|
* packet.
|
|
*/
|
|
|
|
#define SHI_OUT_PREAMBLE_LENGTH 2
|
|
/*
|
|
* Space allocation of the past-end status byte (EC_SPI_PAST_END) in the out_msg
|
|
* buffer.
|
|
*/
|
|
#define EC_SPI_PAST_END_LENGTH 1
|
|
/*
|
|
* Space allocation of the frame status byte (EC_SPI_FRAME_START) in the out_msg
|
|
* buffer.
|
|
*/
|
|
#define EC_SPI_FRAME_START_LENGTH 1
|
|
|
|
/*
|
|
* Offset of output parameters needs to account for pad and framing bytes and
|
|
* one last past-end byte at the end so any additional bytes clocked out by
|
|
* the AP will have a known and identifiable value.
|
|
*/
|
|
#define SHI_PROTO3_OVERHEAD (EC_SPI_PAST_END_LENGTH + EC_SPI_FRAME_START_LENGTH)
|
|
|
|
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
/* The boundary which SHI will output invalid data on MISO. */
|
|
#define SHI_BYPASS_BOUNDARY 256
|
|
/* Increase FRAME_START_LENGTH in case shi outputs invalid FRAME_START byte */
|
|
#undef EC_SPI_FRAME_START_LENGTH
|
|
#define EC_SPI_FRAME_START_LENGTH 2
|
|
#endif
|
|
|
|
/*
|
|
* Max data size for a version 3 request/response packet. This is big enough
|
|
* to handle a request/response header, flash write offset/size, and 512 bytes
|
|
* of flash data:
|
|
* sizeof(ec_host_request): 8
|
|
* sizoef(ec_params_flash_write): 8
|
|
* payload 512
|
|
*
|
|
*/
|
|
#define SHI_MAX_REQUEST_SIZE 0x220
|
|
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
/* Make sure SHI_MAX_RESPONSE_SIZE won't exceed 256 bytes */
|
|
#define SHI_MAX_RESPONSE_SIZE (160 + sizeof(struct ec_host_response))
|
|
BUILD_ASSERT(SHI_MAX_RESPONSE_SIZE <= SHI_BYPASS_BOUNDARY);
|
|
#else
|
|
#define SHI_MAX_RESPONSE_SIZE 0x220
|
|
#endif
|
|
|
|
/*
|
|
* Our input and output msg buffers. These must be large enough for our largest
|
|
* message, including protocol overhead. The pointers after the protocol
|
|
* overhead, as passed to the host command handler, must be 32-bit aligned.
|
|
*/
|
|
#define SHI_OUT_START_PAD (4 * (EC_SPI_FRAME_START_LENGTH / 4 + 1))
|
|
#define SHI_OUT_END_PAD (4 * (EC_SPI_PAST_END_LENGTH / 4 + 1))
|
|
static uint8_t out_msg_padded[SHI_OUT_START_PAD +
|
|
SHI_MAX_RESPONSE_SIZE +
|
|
SHI_OUT_END_PAD] __aligned(4);
|
|
static uint8_t * const out_msg =
|
|
out_msg_padded + SHI_OUT_START_PAD - EC_SPI_FRAME_START_LENGTH;
|
|
static uint8_t in_msg[SHI_MAX_REQUEST_SIZE] __aligned(4);
|
|
|
|
/* Parameters used by host protocols */
|
|
static struct host_packet shi_packet;
|
|
|
|
enum shi_state {
|
|
/* SHI not enabled (initial state, and when chipset is off) */
|
|
SHI_STATE_DISABLED = 0,
|
|
/* Ready to receive next request */
|
|
SHI_STATE_READY_TO_RECV,
|
|
/* Receiving request */
|
|
SHI_STATE_RECEIVING,
|
|
/* Processing request */
|
|
SHI_STATE_PROCESSING,
|
|
/* Canceling response since CS deasserted and output NOT_READY byte */
|
|
SHI_STATE_CNL_RESP_NOT_RDY,
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
/* Keep output buffer as PROCESSING byte until reaching 256B boundary */
|
|
SHI_STATE_WAIT_ALIGNMENT,
|
|
#endif
|
|
/* Sending response */
|
|
SHI_STATE_SENDING,
|
|
/* Received data is valid. */
|
|
SHI_STATE_BAD_RECEIVED_DATA,
|
|
};
|
|
|
|
volatile enum shi_state state;
|
|
|
|
/* SHI bus parameters */
|
|
struct shi_bus_parameters {
|
|
uint8_t *rx_msg; /* Entry pointer of msg rx buffer */
|
|
uint8_t *tx_msg; /* Entry pointer of msg tx buffer */
|
|
volatile uint8_t *rx_buf; /* Entry pointer of receive buffer */
|
|
volatile uint8_t *tx_buf; /* Entry pointer of transmit buffer */
|
|
uint16_t sz_received; /* Size of received data in bytes */
|
|
uint16_t sz_sending; /* Size of sending data in bytes */
|
|
uint16_t sz_request; /* request bytes need to receive */
|
|
uint16_t sz_response; /* response bytes need to receive */
|
|
timestamp_t rx_deadline; /* deadline of receiving */
|
|
uint8_t pre_ibufstat; /* Previous IBUFSTAT value */
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
uint16_t bytes_in_256b; /* Sent bytes in 256 bytes boundary */
|
|
#endif
|
|
} shi_params;
|
|
|
|
/* Forward declaration */
|
|
static void shi_reset_prepare(void);
|
|
static void shi_bad_received_data(void);
|
|
static void shi_fill_out_status(uint8_t status);
|
|
static void shi_write_half_outbuf(void);
|
|
static void shi_write_first_pkg_outbuf(uint16_t szbytes);
|
|
static int shi_read_inbuf_wait(uint16_t szbytes);
|
|
static uint8_t shi_read_buf_pointer(void);
|
|
|
|
/*****************************************************************************/
|
|
/* V3 protocol layer functions */
|
|
|
|
/**
|
|
* Called to send a response back to the host.
|
|
*
|
|
* Some commands can continue for a while. This function is called by
|
|
* host_command when it completes.
|
|
*
|
|
*/
|
|
static void shi_send_response_packet(struct host_packet *pkt)
|
|
{
|
|
/*
|
|
* Disable interrupts. This routine is not called from interrupt
|
|
* context and buffer underrun will likely occur if it is
|
|
* preempted after writing its initial reply byte. Also, we must be
|
|
* sure our state doesn't unexpectedly change, in case we're expected
|
|
* to take RESP_NOT_RDY actions.
|
|
*/
|
|
interrupt_disable();
|
|
if (state == SHI_STATE_PROCESSING) {
|
|
/* Append our past-end byte, which we reserved space for. */
|
|
((uint8_t *) pkt->response)[pkt->response_size + 0] =
|
|
EC_SPI_PAST_END;
|
|
|
|
/* Computing sending bytes of response */
|
|
shi_params.sz_response =
|
|
pkt->response_size + SHI_PROTO3_OVERHEAD;
|
|
|
|
/* Start to fill output buffer with msg buffer */
|
|
shi_write_first_pkg_outbuf(shi_params.sz_response);
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
/*
|
|
* If response package is over 256B boundary,
|
|
* keep sending PROCESSING byte
|
|
*/
|
|
if (state != SHI_STATE_WAIT_ALIGNMENT) {
|
|
#endif
|
|
/* Transmit the reply */
|
|
state = SHI_STATE_SENDING;
|
|
DEBUG_CPRINTF("SND-");
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
}
|
|
#endif
|
|
}
|
|
/*
|
|
* If we're not processing, then the AP has already terminated the
|
|
* transaction, and won't be listening for a response.
|
|
* Reset state machine for next transaction.
|
|
*/
|
|
else if (state == SHI_STATE_CNL_RESP_NOT_RDY) {
|
|
shi_reset_prepare();
|
|
DEBUG_CPRINTF("END\n");
|
|
} else
|
|
DEBUG_CPRINTS("Unexpected state %d in response handler", state);
|
|
interrupt_enable();
|
|
}
|
|
|
|
void shi_handle_host_package(void)
|
|
{
|
|
uint16_t sz_inbuf_int = shi_params.sz_request / SHI_IBUF_HALF_SIZE;
|
|
uint16_t cnt_inbuf_int = shi_params.sz_received / SHI_IBUF_HALF_SIZE;
|
|
if (sz_inbuf_int - cnt_inbuf_int)
|
|
/* Need to receive data from buffer */
|
|
return;
|
|
else {
|
|
uint16_t remain_bytes = shi_params.sz_request
|
|
- shi_params.sz_received;
|
|
|
|
/* Read remaining bytes from input buffer directly */
|
|
if (!shi_read_inbuf_wait(remain_bytes))
|
|
return shi_bad_received_data();
|
|
/* Move to processing state immediately */
|
|
state = SHI_STATE_PROCESSING;
|
|
DEBUG_CPRINTF("PRC-");
|
|
}
|
|
/* Fill output buffer to indicate we`re processing request */
|
|
shi_fill_out_status(EC_SPI_PROCESSING);
|
|
|
|
/* Set up parameters for host request */
|
|
shi_packet.send_response = shi_send_response_packet;
|
|
|
|
shi_packet.request = in_msg;
|
|
shi_packet.request_temp = NULL;
|
|
shi_packet.request_max = sizeof(in_msg);
|
|
shi_packet.request_size = shi_params.sz_request;
|
|
|
|
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
/* Move FRAME_START to second byte */
|
|
out_msg[0] = EC_SPI_PROCESSING;
|
|
out_msg[1] = EC_SPI_FRAME_START;
|
|
#else
|
|
/* Put FRAME_START in first byte */
|
|
out_msg[0] = EC_SPI_FRAME_START;
|
|
#endif
|
|
shi_packet.response = out_msg + EC_SPI_FRAME_START_LENGTH;
|
|
|
|
/* Reserve space for frame start and trailing past-end byte */
|
|
shi_packet.response_max = SHI_MAX_RESPONSE_SIZE;
|
|
shi_packet.response_size = 0;
|
|
shi_packet.driver_result = EC_RES_SUCCESS;
|
|
|
|
/* Go to common-layer to handle request */
|
|
host_packet_receive(&shi_packet);
|
|
}
|
|
|
|
/* Parse header for version of spi-protocol */
|
|
static void shi_parse_header(void)
|
|
{
|
|
/* We're now inside a transaction */
|
|
state = SHI_STATE_RECEIVING;
|
|
DEBUG_CPRINTF("RV-");
|
|
|
|
/* Setup deadline time for receiving */
|
|
shi_params.rx_deadline = get_time();
|
|
shi_params.rx_deadline.val += SHI_CMD_RX_TIMEOUT_US;
|
|
|
|
/* Wait for version, command, length bytes */
|
|
if (!shi_read_inbuf_wait(3))
|
|
return shi_bad_received_data();
|
|
|
|
if (in_msg[0] == EC_HOST_REQUEST_VERSION) {
|
|
/* Protocol version 3 */
|
|
struct ec_host_request *r = (struct ec_host_request *) in_msg;
|
|
int pkt_size;
|
|
/*
|
|
* If request is over 32 bytes,
|
|
* we need to modified the algorithm again.
|
|
*/
|
|
ASSERT(sizeof(*r) < SHI_IBUF_HALF_SIZE);
|
|
|
|
/* Wait for the rest of the command header */
|
|
if (!shi_read_inbuf_wait(sizeof(*r) - 3))
|
|
return shi_bad_received_data();
|
|
|
|
/* Check how big the packet should be */
|
|
pkt_size = host_request_expected_size(r);
|
|
if (pkt_size == 0 || pkt_size > sizeof(in_msg))
|
|
return shi_bad_received_data();
|
|
|
|
/* Computing total bytes need to receive */
|
|
shi_params.sz_request = pkt_size;
|
|
|
|
shi_handle_host_package();
|
|
} else {
|
|
/* Invalid version number */
|
|
return shi_bad_received_data();
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* IC specific low-level driver */
|
|
|
|
/* This routine fills out all SHI output buffer with status byte */
|
|
static void shi_fill_out_status(uint8_t status)
|
|
{
|
|
uint8_t offset;
|
|
uint8_t *obuf_ptr;
|
|
uint8_t *obuf_end;
|
|
|
|
/* Disable interrupts in case the interfere by the other interrupts */
|
|
interrupt_disable();
|
|
|
|
offset = SHI_OBUF_VALID_OFFSET;
|
|
|
|
/* Fill out all output buffer with status byte */
|
|
obuf_ptr = (uint8_t *)SHI_OBUF_START_ADDR + offset;
|
|
obuf_end = (uint8_t *)SHI_OBUF_START_ADDR + SHI_OBUF_FULL_SIZE;
|
|
while (obuf_ptr != obuf_end)
|
|
*(obuf_ptr++) = status;
|
|
|
|
obuf_ptr = (uint8_t *)SHI_OBUF_START_ADDR;
|
|
obuf_end = (uint8_t *)SHI_OBUF_START_ADDR + offset;
|
|
while (obuf_ptr != obuf_end)
|
|
*(obuf_ptr++) = status;
|
|
|
|
/* End of critical section */
|
|
interrupt_enable();
|
|
}
|
|
|
|
#ifdef NPCX_SHI_V2
|
|
/*
|
|
* This routine configures at which level the Input Buffer Half Full 2(IBHF2))
|
|
* event triggers an interrupt to core.
|
|
*/
|
|
static void shi_sec_ibf_int_enable(int enable)
|
|
{
|
|
if (enable) {
|
|
/* Setup IBUFLVL2 threshold and enable it */
|
|
SET_BIT(NPCX_SHICFG5, NPCX_SHICFG5_IBUFLVL2DIS);
|
|
SET_FIELD(NPCX_SHICFG5, NPCX_SHICFG5_IBUFLVL2,
|
|
SHI_IBUFLVL2_THRESHOLD);
|
|
CLEAR_BIT(NPCX_SHICFG5, NPCX_SHICFG5_IBUFLVL2DIS);
|
|
/* Enable IBHF2 event */
|
|
SET_BIT(NPCX_EVENABLE2, NPCX_EVENABLE2_IBHF2EN);
|
|
} else {
|
|
/* Disable IBHF2 event first */
|
|
CLEAR_BIT(NPCX_EVENABLE2, NPCX_EVENABLE2_IBHF2EN);
|
|
/* Disable IBUFLVL2 and set threshold back to zero */
|
|
SET_BIT(NPCX_SHICFG5, NPCX_SHICFG5_IBUFLVL2DIS);
|
|
SET_FIELD(NPCX_SHICFG5, NPCX_SHICFG5_IBUFLVL2, 0);
|
|
}
|
|
}
|
|
#else
|
|
/*
|
|
* This routine makes sure it's valid transaction or glitch on CS bus.
|
|
*/
|
|
static int shi_is_cs_glitch(void)
|
|
{
|
|
timestamp_t deadline;
|
|
|
|
deadline.val = get_time().val + SHI_GLITCH_TIMEOUT_US;
|
|
/*
|
|
* If input buffer pointer is no changed after timeout, it will
|
|
* return true
|
|
*/
|
|
while (shi_params.pre_ibufstat == shi_read_buf_pointer())
|
|
if (timestamp_expired(deadline, NULL))
|
|
return 1;
|
|
/* valid package */
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* This routine write SHI next half output buffer from msg buffer
|
|
*/
|
|
static void shi_write_half_outbuf(void)
|
|
{
|
|
const uint8_t size = MIN(SHI_OBUF_HALF_SIZE,
|
|
shi_params.sz_response -
|
|
shi_params.sz_sending);
|
|
uint8_t *obuf_ptr = (uint8_t *)shi_params.tx_buf;
|
|
const uint8_t *obuf_end = obuf_ptr + size;
|
|
uint8_t *msg_ptr = shi_params.tx_msg;
|
|
|
|
/* Fill half output buffer */
|
|
while (obuf_ptr != obuf_end)
|
|
*(obuf_ptr++) = *(msg_ptr++);
|
|
|
|
shi_params.sz_sending += size;
|
|
shi_params.tx_buf = obuf_ptr;
|
|
shi_params.tx_msg = msg_ptr;
|
|
}
|
|
|
|
/*
|
|
* This routine write SHI output buffer from msg buffer over halt of it.
|
|
* It make sure we have enought time to handle next operations.
|
|
*/
|
|
static void shi_write_first_pkg_outbuf(uint16_t szbytes)
|
|
{
|
|
uint8_t size, offset;
|
|
uint8_t *obuf_ptr;
|
|
uint8_t *obuf_end;
|
|
uint8_t *msg_ptr;
|
|
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
/*
|
|
* If response package is across 256 bytes boundary,
|
|
* bypass needs to extend PROCESSING bytes after reaching the boundary.
|
|
*/
|
|
if (shi_params.bytes_in_256b + SHI_OBUF_FULL_SIZE + szbytes
|
|
> SHI_BYPASS_BOUNDARY) {
|
|
state = SHI_STATE_WAIT_ALIGNMENT;
|
|
/* Set pointer of output buffer to the start address */
|
|
shi_params.tx_buf = SHI_OBUF_START_ADDR;
|
|
DEBUG_CPRINTF("WAT-");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Start writing at our current OBUF position */
|
|
offset = SHI_OBUF_VALID_OFFSET;
|
|
obuf_ptr = (uint8_t *)SHI_OBUF_START_ADDR + offset;
|
|
msg_ptr = shi_params.tx_msg;
|
|
|
|
/* Fill up to OBUF mid point, or OBUF end */
|
|
size = MIN(SHI_OBUF_HALF_SIZE - (offset % SHI_OBUF_HALF_SIZE),
|
|
szbytes - shi_params.sz_sending);
|
|
obuf_end = obuf_ptr + size;
|
|
while (obuf_ptr != obuf_end)
|
|
*(obuf_ptr++) = *(msg_ptr++);
|
|
|
|
/* Track bytes sent for later accounting */
|
|
shi_params.sz_sending += size;
|
|
|
|
/* Write data to beginning of OBUF if we've reached the end */
|
|
if (obuf_ptr == SHI_OBUF_FULL_ADDR)
|
|
obuf_ptr = (uint8_t *)SHI_OBUF_START_ADDR;
|
|
|
|
/* Fill next half output buffer */
|
|
size = MIN(SHI_OBUF_HALF_SIZE, szbytes - shi_params.sz_sending);
|
|
obuf_end = obuf_ptr + size;
|
|
while (obuf_ptr != obuf_end)
|
|
*(obuf_ptr++) = *(msg_ptr++);
|
|
|
|
/* Track bytes sent / last OBUF position written for later accounting */
|
|
shi_params.sz_sending += size;
|
|
shi_params.tx_buf = obuf_ptr;
|
|
shi_params.tx_msg = msg_ptr;
|
|
}
|
|
|
|
/* This routine copies SHI half input buffer data to msg buffer */
|
|
static void shi_read_half_inbuf(void)
|
|
{
|
|
/*
|
|
* Copy to read buffer until reaching middle/top address of
|
|
* input buffer or completing receiving data
|
|
*/
|
|
do {
|
|
/* Restore data to msg buffer */
|
|
*(shi_params.rx_msg++) = *(shi_params.rx_buf++);
|
|
shi_params.sz_received++;
|
|
} while (shi_params.sz_received % SHI_IBUF_HALF_SIZE
|
|
&& shi_params.sz_received != shi_params.sz_request);
|
|
}
|
|
|
|
/*
|
|
* This routine read SHI input buffer to msg buffer until
|
|
* we have received a certain number of bytes
|
|
*/
|
|
static int shi_read_inbuf_wait(uint16_t szbytes)
|
|
{
|
|
uint16_t i;
|
|
|
|
/* Copy data to msg buffer from input buffer */
|
|
for (i = 0; i < szbytes; i++, shi_params.sz_received++) {
|
|
/*
|
|
* If input buffer pointer equals pointer which wants to read,
|
|
* it means data is not ready.
|
|
*/
|
|
while (shi_params.rx_buf == SHI_IBUF_CUR_ADDR)
|
|
if (timestamp_expired(shi_params.rx_deadline, NULL))
|
|
return 0;
|
|
/* Restore data to msg buffer */
|
|
*(shi_params.rx_msg++) = *(shi_params.rx_buf++);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* Read pointer of input or output buffer by consecutive reading */
|
|
static uint8_t shi_read_buf_pointer(void)
|
|
{
|
|
uint8_t stat;
|
|
/* Wait for two consecutive equal values are read */
|
|
do {
|
|
stat = NPCX_IBUFSTAT;
|
|
} while (stat != NPCX_IBUFSTAT);
|
|
|
|
return stat;
|
|
}
|
|
|
|
/* This routine handles shi recevied unexcepted data */
|
|
static void shi_bad_received_data(void)
|
|
{
|
|
uint16_t i;
|
|
|
|
/* State machine mismatch, timeout, or protocol we can't handle. */
|
|
shi_fill_out_status(EC_SPI_RX_BAD_DATA);
|
|
state = SHI_STATE_BAD_RECEIVED_DATA;
|
|
|
|
CPRINTF("BAD-");
|
|
CPRINTF("in_msg=[");
|
|
for (i = 0; i < shi_params.sz_received; i++)
|
|
CPRINTF("%02x ", in_msg[i]);
|
|
CPRINTF("]\n");
|
|
|
|
/* Reset shi's state machine for error recovery */
|
|
shi_reset_prepare();
|
|
|
|
DEBUG_CPRINTF("END\n");
|
|
}
|
|
|
|
/*
|
|
* Avoid spamming the console with prints every IBF / IBHF interrupt, if
|
|
* we find ourselves in an unexpected state.
|
|
*/
|
|
static int last_error_state = -1;
|
|
|
|
static void log_unexpected_state(char *isr_name)
|
|
{
|
|
#if !(DEBUG_SHI)
|
|
if (state != last_error_state)
|
|
CPRINTS("Unexpected state %d in %s ISR", state, isr_name);
|
|
#endif
|
|
last_error_state = state;
|
|
}
|
|
|
|
static void shi_handle_cs_assert(void)
|
|
{
|
|
/* If not enabled, ignore glitches on SHI_CS_L */
|
|
if (state == SHI_STATE_DISABLED)
|
|
return;
|
|
|
|
/* SHI V2 module filters cs glitch by hardware automatically */
|
|
#ifndef NPCX_SHI_V2
|
|
/*
|
|
* IBUFSTAT resets on the 7th clock cycle after CS assertion, which
|
|
* may not have happened yet. We use NPCX_IBUFSTAT for calculating
|
|
* buffer fill depth, so make sure it's valid before proceeding.
|
|
*/
|
|
if (shi_is_cs_glitch()) {
|
|
CPRINTS("ERR-GTH");
|
|
shi_reset_prepare();
|
|
DEBUG_CPRINTF("END\n");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* NOT_READY should be sent and there're no spi transaction now. */
|
|
if (state == SHI_STATE_CNL_RESP_NOT_RDY)
|
|
return;
|
|
|
|
/* Chip select is low = asserted */
|
|
if (state != SHI_STATE_READY_TO_RECV) {
|
|
/* State machine should be reset in EVSTAT_EOR ISR */
|
|
CPRINTS("Unexpected state %d in CS ISR", state);
|
|
return;
|
|
}
|
|
|
|
DEBUG_CPRINTF("CSL-");
|
|
|
|
/*
|
|
* Clear possible EOR event from previous transaction since it's
|
|
* irrelevant now that CS is re-asserted.
|
|
*/
|
|
NPCX_EVSTAT = 1 << NPCX_EVSTAT_EOR;
|
|
|
|
/* Do not deep sleep during SHI transaction */
|
|
disable_sleep(SLEEP_MASK_SPI);
|
|
|
|
#ifndef NPCX_SHI_V2
|
|
/*
|
|
* Enable SHI interrupt - we will either succeed to parse our host
|
|
* command or reset on failure from here.
|
|
*/
|
|
task_enable_irq(NPCX_IRQ_SHI);
|
|
|
|
/* Read first three bytes to parse which protocol is receiving */
|
|
shi_parse_header();
|
|
#endif
|
|
}
|
|
|
|
/* This routine handles all interrupts of this module */
|
|
void shi_int_handler(void)
|
|
{
|
|
uint8_t stat_reg;
|
|
#ifdef NPCX_SHI_V2
|
|
uint8_t stat2_reg;
|
|
#endif
|
|
|
|
/* Read status register and clear interrupt status early */
|
|
stat_reg = NPCX_EVSTAT;
|
|
NPCX_EVSTAT = stat_reg;
|
|
#ifdef NPCX_SHI_V2
|
|
stat2_reg = NPCX_EVSTAT2;
|
|
|
|
/* SHI CS pin is asserted in EVSTAT2 */
|
|
if (IS_BIT_SET(stat2_reg, NPCX_EVSTAT2_CSNFE)) {
|
|
/* clear CSNFE bit */
|
|
NPCX_EVSTAT2 = BIT(NPCX_EVSTAT2_CSNFE);
|
|
DEBUG_CPRINTF("CSNFE-");
|
|
/*
|
|
* BUSY bit is set when SHI_CS is asserted. If not, leave it for
|
|
* SHI_CS de-asserted event.
|
|
*/
|
|
if (!IS_BIT_SET(NPCX_SHICFG2, NPCX_SHICFG2_BUSY)) {
|
|
DEBUG_CPRINTF("CSNB-");
|
|
return;
|
|
}
|
|
shi_handle_cs_assert();
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* End of data for read/write transaction. ie SHI_CS is deasserted.
|
|
* Host completed or aborted transaction
|
|
*/
|
|
#ifdef NPCX_SHI_V2
|
|
/*
|
|
* EOR has the limitation that it will not be set even if the SHI_CS is
|
|
* deasserted without SPI clocks. The new SHI module introduce the
|
|
* CSNRE bit which will be set when SHI_CS is deasserted regardless of
|
|
* SPI clocks.
|
|
*/
|
|
if (IS_BIT_SET(stat2_reg, NPCX_EVSTAT2_CSNRE)) {
|
|
/* Clear pending bit of CSNRE */
|
|
NPCX_EVSTAT2 = BIT(NPCX_EVSTAT2_CSNRE);
|
|
#else
|
|
if (IS_BIT_SET(stat_reg, NPCX_EVSTAT_EOR)) {
|
|
#endif
|
|
/*
|
|
* We're not in proper state.
|
|
* Mark not ready to abort next transaction
|
|
*/
|
|
DEBUG_CPRINTF("CSH-");
|
|
/*
|
|
* If the buffer is still used by the host command.
|
|
* Change state machine for response handler.
|
|
*/
|
|
if (state == SHI_STATE_PROCESSING) {
|
|
/*
|
|
* Mark not ready to prevent the other
|
|
* transaction immediately
|
|
*/
|
|
shi_fill_out_status(EC_SPI_NOT_READY);
|
|
|
|
state = SHI_STATE_CNL_RESP_NOT_RDY;
|
|
|
|
/*
|
|
* Disable SHI interrupt, it will remain disabled
|
|
* until shi_send_response_packet() is called and
|
|
* CS is asserted for a new transaction.
|
|
*/
|
|
task_disable_irq(NPCX_IRQ_SHI);
|
|
|
|
DEBUG_CPRINTF("CNL-");
|
|
return;
|
|
/* Next transaction but we're not ready */
|
|
} else if (state == SHI_STATE_CNL_RESP_NOT_RDY)
|
|
return;
|
|
|
|
/* Error state for checking*/
|
|
if (state != SHI_STATE_SENDING)
|
|
#ifdef NPCX_SHI_V2
|
|
log_unexpected_state("CSNRE");
|
|
#else
|
|
log_unexpected_state("IBEOR");
|
|
#endif
|
|
|
|
/* reset SHI and prepare to next transaction again */
|
|
shi_reset_prepare();
|
|
DEBUG_CPRINTF("END\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Indicate input/output buffer pointer reaches the half buffer size.
|
|
* Transaction is processing.
|
|
*/
|
|
if (IS_BIT_SET(stat_reg, NPCX_EVSTAT_IBHF)) {
|
|
if (state == SHI_STATE_RECEIVING) {
|
|
/* Read data from input to msg buffer */
|
|
shi_read_half_inbuf();
|
|
return shi_handle_host_package();
|
|
} else if (state == SHI_STATE_SENDING) {
|
|
/* Write data from msg buffer to output buffer */
|
|
if (shi_params.tx_buf == SHI_OBUF_START_ADDR +
|
|
SHI_OBUF_FULL_SIZE) {
|
|
/* Write data from bottom address again */
|
|
shi_params.tx_buf = SHI_OBUF_START_ADDR;
|
|
return shi_write_half_outbuf();
|
|
} else /* ignore it */
|
|
return;
|
|
} else if (state == SHI_STATE_PROCESSING) {
|
|
/* Wait for host to handle request */
|
|
}
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
else if (state == SHI_STATE_WAIT_ALIGNMENT) {
|
|
/*
|
|
* If pointer of output buffer will reach 256 bytes
|
|
* boundary soon, start to fill response data.
|
|
*/
|
|
if (shi_params.bytes_in_256b == SHI_BYPASS_BOUNDARY -
|
|
SHI_OBUF_FULL_SIZE) {
|
|
state = SHI_STATE_SENDING;
|
|
DEBUG_CPRINTF("SND-");
|
|
return shi_write_half_outbuf();
|
|
}
|
|
}
|
|
#endif
|
|
else
|
|
/* Unexpected status */
|
|
log_unexpected_state("IBHF");
|
|
}
|
|
|
|
#ifdef NPCX_SHI_V2
|
|
/*
|
|
* The size of input buffer reaches the size of
|
|
* protocol V3 header(=8) after CS asserted.
|
|
*/
|
|
if (IS_BIT_SET(stat2_reg, NPCX_EVSTAT2_IBHF2)) {
|
|
/* Clear IBHF2 */
|
|
NPCX_EVSTAT2 = BIT(NPCX_EVSTAT2_IBHF2);
|
|
DEBUG_CPRINTF("HDR-");
|
|
/* Disable second IBF interrupt and start to parse header */
|
|
shi_sec_ibf_int_enable(0);
|
|
shi_parse_header();
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Indicate input/output buffer pointer reaches the full buffer size.
|
|
* Transaction is processing.
|
|
*/
|
|
if (IS_BIT_SET(stat_reg, NPCX_EVSTAT_IBF)) {
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
/* Record the sent bytes within 256B boundary */
|
|
shi_params.bytes_in_256b = (shi_params.bytes_in_256b +
|
|
SHI_OBUF_FULL_SIZE) % SHI_BYPASS_BOUNDARY;
|
|
#endif
|
|
if (state == SHI_STATE_RECEIVING) {
|
|
/* read data from input to msg buffer */
|
|
shi_read_half_inbuf();
|
|
/* Read to bottom address again */
|
|
shi_params.rx_buf = SHI_IBUF_START_ADDR;
|
|
return shi_handle_host_package();
|
|
} else if (state == SHI_STATE_SENDING)
|
|
/* Write data from msg buffer to output buffer */
|
|
if (shi_params.tx_buf == SHI_OBUF_START_ADDR +
|
|
SHI_OBUF_HALF_SIZE)
|
|
return shi_write_half_outbuf();
|
|
else /* ignore it */
|
|
return;
|
|
else if (state == SHI_STATE_PROCESSING
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
|| state == SHI_STATE_WAIT_ALIGNMENT
|
|
#endif
|
|
)
|
|
/* Wait for host handles request */
|
|
return;
|
|
else
|
|
/* Unexpected status */
|
|
log_unexpected_state("IBF");
|
|
}
|
|
}
|
|
DECLARE_IRQ(NPCX_IRQ_SHI, shi_int_handler, 2);
|
|
|
|
/* Handle an CS assert event on the SHI_CS_L pin */
|
|
void shi_cs_event(enum gpio_signal signal)
|
|
{
|
|
#ifdef NPCX_SHI_V2
|
|
/*
|
|
* New SHI module handles the CS low event in the SHI module interrupt
|
|
* handler (checking CSNFE bit) instead of in GPIO(MIWU) interrupt
|
|
* handler. But there is still a need to configure the MIWU to generate
|
|
* event to wake up EC from deep sleep. Immediately return to bypass
|
|
* the CS low interrupt event from MIWU module.
|
|
*/
|
|
return;
|
|
#else
|
|
shi_handle_cs_assert();
|
|
#endif
|
|
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* Hook functions for chipset and initialization */
|
|
|
|
/*
|
|
* Reset SHI bus and prepare next transaction
|
|
* Please make sure it is executed when there're no spi transactions
|
|
*/
|
|
static void shi_reset_prepare(void)
|
|
{
|
|
uint16_t i;
|
|
|
|
/* We no longer care about SHI interrupts, so disable them. */
|
|
task_disable_irq(NPCX_IRQ_SHI);
|
|
|
|
/* Disable SHI unit to clear all status bits */
|
|
CLEAR_BIT(NPCX_SHICFG1, NPCX_SHICFG1_EN);
|
|
|
|
/* Initialize parameters of next transaction */
|
|
shi_params.rx_msg = in_msg;
|
|
shi_params.tx_msg = out_msg;
|
|
shi_params.rx_buf = SHI_IBUF_START_ADDR;
|
|
shi_params.tx_buf = SHI_OBUF_HALF_ADDR;
|
|
shi_params.sz_received = 0;
|
|
shi_params.sz_sending = 0;
|
|
shi_params.sz_request = 0;
|
|
shi_params.sz_response = 0;
|
|
#ifdef NPCX_SHI_BYPASS_OVER_256B
|
|
shi_params.bytes_in_256b = 0;
|
|
#endif
|
|
/* Record last IBUFSTAT for glitch case */
|
|
shi_params.pre_ibufstat = shi_read_buf_pointer();
|
|
|
|
/*
|
|
* Fill output buffer to indicate we`re
|
|
* ready to receive next transaction.
|
|
*/
|
|
for (i = 1; i < SHI_OBUF_FULL_SIZE; i++)
|
|
NPCX_OBUF(i) = EC_SPI_RECEIVING;
|
|
NPCX_OBUF(0) = EC_SPI_OLD_READY;
|
|
|
|
/* Enable SHI & WEN functionality */
|
|
NPCX_SHICFG1 = 0x85;
|
|
|
|
/* Ready to receive */
|
|
state = SHI_STATE_READY_TO_RECV;
|
|
last_error_state = -1;
|
|
|
|
/* Setup second IBF interrupt and enable SHI's interrupt */
|
|
#ifdef NPCX_SHI_V2
|
|
shi_sec_ibf_int_enable(1);
|
|
task_enable_irq(NPCX_IRQ_SHI);
|
|
#endif
|
|
|
|
/* Allow deep sleep at the end of SHI transaction */
|
|
enable_sleep(SLEEP_MASK_SPI);
|
|
|
|
DEBUG_CPRINTF("RDY-");
|
|
}
|
|
|
|
static void shi_enable(void)
|
|
{
|
|
int gpio_flags;
|
|
|
|
shi_reset_prepare();
|
|
|
|
/* Ensure SHI_CS_L interrupt is disabled */
|
|
gpio_disable_interrupt(GPIO_SHI_CS_L);
|
|
|
|
/* Enable PU, if requested */
|
|
gpio_flags = GPIO_INPUT | GPIO_INT_F_FALLING;
|
|
#ifdef NPCX_SHI_CS_PU
|
|
gpio_flags |= GPIO_PULL_UP;
|
|
#endif
|
|
gpio_set_flags(GPIO_SHI_CS_L, gpio_flags);
|
|
|
|
/*
|
|
* Mux SHI related pins
|
|
* SHI_SDI SHI_SDO SHI_CS# SHI_SCLK are selected to device pins
|
|
*/
|
|
SET_BIT(NPCX_DEVALT(ALT_GROUP_C), NPCX_DEVALTC_SHI_SL);
|
|
|
|
task_clear_pending_irq(NPCX_IRQ_SHI);
|
|
|
|
/* Enable SHI_CS_L interrupt */
|
|
gpio_enable_interrupt(GPIO_SHI_CS_L);
|
|
|
|
/*
|
|
* If CS is already asserted prior to enabling our GPIO interrupt then
|
|
* we have missed the falling edge and we need to handle the
|
|
* deassertion interrupt.
|
|
*/
|
|
task_enable_irq(NPCX_IRQ_SHI);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_RESUME, shi_enable, HOOK_PRIO_DEFAULT);
|
|
|
|
static void shi_reenable_on_sysjump(void)
|
|
{
|
|
#if !(DEBUG_SHI)
|
|
if (system_jumped_to_this_image() && chipset_in_state(CHIPSET_STATE_ON))
|
|
#endif
|
|
shi_enable();
|
|
}
|
|
/* Call hook after chipset sets initial power state */
|
|
DECLARE_HOOK(HOOK_INIT,
|
|
shi_reenable_on_sysjump,
|
|
HOOK_PRIO_INIT_CHIPSET + 1);
|
|
|
|
/* Disable SHI bus */
|
|
static void shi_disable(void)
|
|
{
|
|
state = SHI_STATE_DISABLED;
|
|
|
|
task_disable_irq(NPCX_IRQ_SHI);
|
|
|
|
/* Disable SHI_CS_L interrupt */
|
|
gpio_disable_interrupt(GPIO_SHI_CS_L);
|
|
|
|
/* Restore SHI_CS_L back to default state */
|
|
gpio_reset(GPIO_SHI_CS_L);
|
|
|
|
/*
|
|
* Mux SHI related pins
|
|
* SHI_SDI SHI_SDO SHI_CS# SHI_SCLK are selected to GPIO
|
|
*/
|
|
CLEAR_BIT(NPCX_DEVALT(ALT_GROUP_C), NPCX_DEVALTC_SHI_SL);
|
|
|
|
/*
|
|
* Allow deep sleep again in case CS dropped before ec was
|
|
* informed in hook function and turn off SHI's interrupt in time.
|
|
*/
|
|
enable_sleep(SLEEP_MASK_SPI);
|
|
}
|
|
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, shi_disable, HOOK_PRIO_DEFAULT);
|
|
DECLARE_HOOK(HOOK_SYSJUMP, shi_disable, HOOK_PRIO_DEFAULT);
|
|
|
|
static void shi_init(void)
|
|
{
|
|
/* Power on SHI module first */
|
|
CLEAR_BIT(NPCX_PWDWN_CTL(NPCX_PMC_PWDWN_5), NPCX_PWDWN_CTL5_SHI_PD);
|
|
|
|
#ifdef NPCX_SHI_V2
|
|
/* Enable SHI module Version 2 */
|
|
SET_BIT(NPCX_DEVALT(ALT_GROUP_F), NPCX_DEVALTF_SHI_NEW);
|
|
#endif
|
|
|
|
/*
|
|
* SHICFG1 (SHI Configuration 1) setting
|
|
* [7] - IWRAP = 1: Wrap input buffer to the first address
|
|
* [6] - CPOL = 0: Sampling on rising edge and output on falling edge
|
|
* [5] - DAS = 0: return STATUS reg data after Status command
|
|
* [4] - AUTOBE = 0: Automatically update the OBES bit in STATUS reg
|
|
* [3] - AUTIBF = 0: Automatically update the IBFS bit in STATUS reg
|
|
* [2] - WEN = 0: Enable host write to input buffer
|
|
* [1] - Reserved 0
|
|
* [0] - ENABLE = 0: Disable SHI at the beginning
|
|
*/
|
|
NPCX_SHICFG1 = 0x80;
|
|
|
|
/*
|
|
* SHICFG2 (SHI Configuration 2) setting
|
|
* [7] - Reserved 0
|
|
* [6] - REEVEN = 0: Restart events are not used
|
|
* [5] - Reserved 0
|
|
* [4] - REEN = 0: Restart transactions are not used
|
|
* [3] - SLWU = 0: Seem-less wake-up is enabled by default
|
|
* [2] - ONESHOT= 0: WEN is cleared at the end of a write transaction
|
|
* [1] - BUSY = 0: SHI bus is busy 0: idle.
|
|
* [0] - SIMUL = 1: Turn on simultaneous Read/Write
|
|
*/
|
|
NPCX_SHICFG2 = 0x01;
|
|
|
|
/*
|
|
* EVENABLE (Event Enable) setting
|
|
* [7] - IBOREN = 0: Input buffer overrun interrupt enable
|
|
* [6] - STSREN = 0: status read interrupt disable
|
|
* [5] - EOWEN = 0: End-of-Data for Write Transaction Interrupt Enable
|
|
* [4] - EOREN = 1: End-of-Data for Read Transaction Interrupt Enable
|
|
* [3] - IBHFEN = 1: Input Buffer Half Full Interrupt Enable
|
|
* [2] - IBFEN = 1: Input Buffer Full Interrupt Enable
|
|
* [1] - OBHEEN = 0: Output Buffer Half Empty Interrupt Enable
|
|
* [0] - OBEEN = 0: Output Buffer Empty Interrupt Enable
|
|
*/
|
|
NPCX_EVENABLE = 0x1C;
|
|
|
|
#ifdef NPCX_SHI_V2
|
|
/*
|
|
* EVENABLE2 (Event Enable 2) setting
|
|
* [2] - CSNFEEN = 1: SHI_CS Falling Edge Interrupt Enable
|
|
* [1] - CSNREEN = 1: SHI_CS Rising Edge Interrupt Enable
|
|
* [0] - IBHF2EN = 0: Input Buffer Half Full 2 Interrupt Enable
|
|
*/
|
|
NPCX_EVENABLE2 = 0x06;
|
|
#endif
|
|
|
|
/* Clear SHI events status register */
|
|
NPCX_EVSTAT = 0XFF;
|
|
}
|
|
/* Call hook before chipset sets initial power state and calls resume hooks */
|
|
DECLARE_HOOK(HOOK_INIT, shi_init, HOOK_PRIO_INIT_CHIPSET - 1);
|
|
|
|
/**
|
|
* Get protocol information
|
|
*/
|
|
static enum ec_status shi_get_protocol_info(struct host_cmd_handler_args *args)
|
|
{
|
|
struct ec_response_get_protocol_info *r = args->response;
|
|
|
|
memset(r, 0, sizeof(*r));
|
|
r->protocol_versions = BIT(3);
|
|
r->max_request_packet_size = SHI_MAX_REQUEST_SIZE;
|
|
r->max_response_packet_size = SHI_MAX_RESPONSE_SIZE;
|
|
r->flags = EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED;
|
|
|
|
args->response_size = sizeof(*r);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO, shi_get_protocol_info,
|
|
EC_VER_MASK(0));
|