diff options
author | Jun Lin <CHLin56@nuvoton.com> | 2021-03-26 17:29:16 +0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-03-31 15:22:58 +0000 |
commit | 6f75875da06461dd65f13e2741e16034d27b0289 (patch) | |
tree | b01d31e859c9c04ee828b08e8d98262c9952351b | |
parent | cf62c88b539f0ebc8935a8cf9c375002e7a9e33f (diff) | |
download | chrome-ec-6f75875da06461dd65f13e2741e16034d27b0289.tar.gz |
zephyr: npcx: support SHI driver
Add the support for SHI (Serial Host Interface) driver. This is used to
transfer host commands between EC and ARM-based AP.
BUG=b:182600858
BRANCH=none
TEST=Test host command "version" and "Hello" on npcx7_evb and the host
emulator.
Signed-off-by: Jun Lin <CHLin56@nuvoton.com>
Change-Id: I5f290fe910600162764f5728f094dd0f42d508ef
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2786887
Reviewed-by: Jack Rosenthal <jrosenth@chromium.org>
Commit-Queue: Jack Rosenthal <jrosenth@chromium.org>
Tested-by: Jack Rosenthal <jrosenth@chromium.org>
-rw-r--r-- | zephyr/drivers/CMakeLists.txt | 1 | ||||
-rw-r--r-- | zephyr/drivers/Kconfig | 1 | ||||
-rw-r--r-- | zephyr/drivers/cros_shi/CMakeLists.txt | 5 | ||||
-rw-r--r-- | zephyr/drivers/cros_shi/Kconfig | 37 | ||||
-rw-r--r-- | zephyr/drivers/cros_shi/cros_shi_npcx.c | 905 | ||||
-rw-r--r-- | zephyr/dts/bindings/cros_shi/nuvoton,npcx-cros-shi.yaml | 35 | ||||
-rw-r--r-- | zephyr/include/cros/nuvoton/npcx.dtsi | 13 | ||||
-rw-r--r-- | zephyr/include/drivers/cros_shi.h | 89 | ||||
-rw-r--r-- | zephyr/include/soc/nuvoton_npcx/reg_def_cros.h | 83 |
9 files changed, 1169 insertions, 0 deletions
diff --git a/zephyr/drivers/CMakeLists.txt b/zephyr/drivers/CMakeLists.txt index 6ffa0465e2..5435ee6b84 100644 --- a/zephyr/drivers/CMakeLists.txt +++ b/zephyr/drivers/CMakeLists.txt @@ -6,4 +6,5 @@ add_subdirectory(cros_bbram) add_subdirectory(cros_flash) add_subdirectory(cros_kb_raw) add_subdirectory(cros_rtc) +add_subdirectory(cros_shi) add_subdirectory(cros_system) diff --git a/zephyr/drivers/Kconfig b/zephyr/drivers/Kconfig index 0b579713cb..37cf390a64 100644 --- a/zephyr/drivers/Kconfig +++ b/zephyr/drivers/Kconfig @@ -7,3 +7,4 @@ rsource "cros_flash/Kconfig" rsource "cros_kb_raw/Kconfig" rsource "cros_rtc/Kconfig" rsource "cros_system/Kconfig" +rsource "cros_shi/Kconfig" diff --git a/zephyr/drivers/cros_shi/CMakeLists.txt b/zephyr/drivers/cros_shi/CMakeLists.txt new file mode 100644 index 0000000000..6bc531b185 --- /dev/null +++ b/zephyr/drivers/cros_shi/CMakeLists.txt @@ -0,0 +1,5 @@ +# Copyright 2021 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. + +zephyr_library_sources_ifdef(CONFIG_CROS_SHI_NPCX cros_shi_npcx.c) diff --git a/zephyr/drivers/cros_shi/Kconfig b/zephyr/drivers/cros_shi/Kconfig new file mode 100644 index 0000000000..1b1caf4896 --- /dev/null +++ b/zephyr/drivers/cros_shi/Kconfig @@ -0,0 +1,37 @@ +# Copyright 2021 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. + +menuconfig CROS_SHI_NPCX + bool "Nuvoton NPCX Serial Host Interface driver for the Zephyr shim" + depends on SOC_FAMILY_NPCX + default n + help + This option enables Serial Host Interface driver for the NPCX family + of processors. This is used for host-command communication on the + platform which AP is ARM-based SoC. + +if CROS_SHI_NPCX +config CROS_SHI_MAX_REQUEST + hex "Max data size for the version 3 request packet" + default 0x220 + help + This option indicates maximum data size for a version 3 request + packet. This must be big enough to handle a request header of host + command, flash write offset/size, and 512 bytes of flash data. + +config CROS_SHI_MAX_RESPONSE + hex "Max data size for the version 3 response packet" + default 0x220 + help + This option indicates maximum data size for a version 3 response + packet. This must be big enough to handle a response header of host + command, flash read offset/size, and 512 bytes of flash data. + +config CROS_SHI_NPCX_DEBUG + bool "Enable SHI debug" + default n + help + print the debug messages for SHI module + +endif # CROS_SHI_NPCX diff --git a/zephyr/drivers/cros_shi/cros_shi_npcx.c b/zephyr/drivers/cros_shi/cros_shi_npcx.c new file mode 100644 index 0000000000..27ec8292aa --- /dev/null +++ b/zephyr/drivers/cros_shi/cros_shi_npcx.c @@ -0,0 +1,905 @@ +/* Copyright 2021 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. + */ + +#define DT_DRV_COMPAT nuvoton_npcx_cros_shi + +#include <arch/arm/aarch32/cortex_m/cmsis.h> +#include <assert.h> +#include <dt-bindings/clock/npcx_clock.h> +#include <drivers/clock_control.h> +#include <drivers/cros_shi.h> +#include <drivers/gpio.h> +#include <logging/log.h> +#include <kernel.h> +#include <soc.h> +#include <soc/nuvoton_npcx/reg_def_cros.h> + +#include "host_command.h" +#include "soc_miwu.h" +#include "system.h" + +#ifdef CONFIG_CROS_SHI_NPCX_DEBUG +#define DEBUG_CPRINTS(format, args...) cprints(CC_SPI, format, ##args) +#define DEBUG_CPRINTF(format, args...) cprintf(CC_SPI, format, ##args) +#else +#define DEBUG_CPRINTS(...) +#define DEBUG_CPRINTF(...) +#endif + +LOG_MODULE_REGISTER(cros_shi, LOG_LEVEL_DBG); + +#define SHI_NODE DT_NODELABEL(shi) +#define SHI_VER_CTRL_PH DT_PHANDLE_BY_IDX(SHI_NODE, ver_ctrl, 0) +#define SHI_VER_CTRL_ALT_FILED(f) DT_PHA_BY_IDX(SHI_VER_CTRL_PH, alts, 0, f) + +/* Full output buffer size */ +#define SHI_OBUF_FULL_SIZE 128 +/* Full input buffer size */ +#define SHI_IBUF_FULL_SIZE 128 +/* Configure the IBUFLVL2 = the size of V3 protocol header */ +#define SHI_IBUFLVL2_THRESHOLD (sizeof(struct ec_host_request)) +/* Half output buffer size */ +#define SHI_OBUF_HALF_SIZE (SHI_OBUF_FULL_SIZE / 2) +/* Half input buffer size */ +#define SHI_IBUF_HALF_SIZE (SHI_IBUF_FULL_SIZE / 2) + +/* + * 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 + +/* + * 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) + +/* + * 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 + * sizeof(ec_params_flash_write): 8 + * payload 512 + */ +#define SHI_MAX_REQUEST_SIZE CONFIG_CROS_SHI_MAX_REQUEST +#define SHI_MAX_RESPONSE_SIZE CONFIG_CROS_SHI_MAX_RESPONSE + +/* + * 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 cros_shi_npcx_state { + SHI_STATE_NONE = -1, + /* 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, + /* Sending response */ + SHI_STATE_SENDING, + /* Received data is invalid */ + SHI_STATE_BAD_RECEIVED_DATA, +}; + +static enum cros_shi_npcx_state state; + +/* Device config */ +struct cros_shi_npcx_config { + /* Serial Host Interface (SHI) base address */ + uintptr_t base; + /* clock configuration */ + struct npcx_clk_cfg clk_cfg; + /* pinmux configuration */ + const uint8_t alts_size; + const struct npcx_alt *alts_list; + /* SHI IRQ */ + int irq; + struct npcx_wui shi_cs_wui; +}; + +/* 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 */ + uint64_t rx_deadline; /* deadline of receiving */ +} shi_params; + +static const struct npcx_alt cros_shi_alts[] = NPCX_DT_ALT_ITEMS_LIST(0); + +static const struct cros_shi_npcx_config cros_shi_cfg = { + .base = DT_INST_REG_ADDR(0), + .clk_cfg = NPCX_DT_CLK_CFG_ITEM(0), + .alts_size = ARRAY_SIZE(cros_shi_alts), + .alts_list = cros_shi_alts, + .irq = DT_INST_IRQN(0), + .shi_cs_wui = NPCX_DT_WUI_ITEM_BY_NAME(0, shi_cs_wui), +}; + +struct cros_shi_npcx_data { + struct host_packet shi_packet; + sys_slist_t callbacks; +}; + +/* Driver convenience defines */ +#define DRV_CONFIG(dev) ((const struct cros_shi_npcx_config *)(dev)->config) +#define DRV_DATA(dev) ((struct cros_shi_npcx_data *)(dev)->data) +#define HAL_INSTANCE(dev) (struct shi_reg *)(DRV_CONFIG(dev)->base) + +/* Forward declaration */ +static void cros_shi_npcx_reset_prepare(struct shi_reg *const inst); + +/* Read pointer of input or output buffer by consecutive reading */ +static uint32_t shi_read_buf_pointer(struct shi_reg *const inst) +{ + uint8_t stat; + + /* Wait for two consecutive equal values are read */ + do { + stat = inst->IBUFSTAT; + } while (stat != inst->IBUFSTAT); + + return (uint32_t)stat; +} + +/* + * Valid offset of SHI output buffer to write. + * When SIMUL bit is set, IBUFPTR can be used instead of OBUFPTR + */ +static uint32_t shi_valid_obuf_offset(struct shi_reg *const inst) +{ + return (shi_read_buf_pointer(inst) + SHI_OUT_PREAMBLE_LENGTH) % + SHI_OBUF_FULL_SIZE; +} + +/* + * This routine write SHI next half output buffer from msg buffer + */ +static void shi_write_half_outbuf(void) +{ + const uint32_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 read SHI input buffer to msg buffer until + * we have received a certain number of bytes + */ +static int shi_read_inbuf_wait(struct shi_reg *const inst, uint32_t szbytes) +{ + /* Copy data to msg buffer from input buffer */ + for (uint32_t 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 == + inst->IBUF + shi_read_buf_pointer(inst)) { + if (k_uptime_get() > shi_params.rx_deadline) { + return 0; + } + } + /* Restore data to msg buffer */ + *shi_params.rx_msg++ = *shi_params.rx_buf++; + } + return 1; +} + +/* This routine fills out all SHI output buffer with status byte */ +static void shi_fill_out_status(struct shi_reg *const inst, uint8_t status) +{ + uint8_t start, end; + volatile uint8_t *fill_ptr; + volatile uint8_t *fill_end; + volatile uint8_t *obuf_end; + int key; + + /* Disable interrupts in case the interfere by the other interrupts */ + key = irq_lock(); + + /* + * Fill out output buffer with status byte and leave a gap for PREAMBLE. + * The gap guarantees the synchronization. The critical section should + * be done within this gap. No racing happens. + */ + start = shi_valid_obuf_offset(inst); + end = (start + SHI_OBUF_FULL_SIZE - SHI_OUT_PREAMBLE_LENGTH) % + SHI_OBUF_FULL_SIZE; + + fill_ptr = inst->OBUF + start; + fill_end = inst->OBUF + end; + obuf_end = inst->OBUF + SHI_OBUF_FULL_SIZE; + while (fill_ptr != fill_end) { + *fill_ptr++ = status; + if (fill_ptr == obuf_end) + fill_ptr = inst->OBUF; + } + + /* End of critical section */ + irq_unlock(key); +} + +/* This routine handles shi received unexpected data */ +static void shi_bad_received_data(struct shi_reg *const inst) +{ + /* State machine mismatch, timeout, or protocol we can't handle. */ + shi_fill_out_status(inst, EC_SPI_RX_BAD_DATA); + state = SHI_STATE_BAD_RECEIVED_DATA; + + DEBUG_CPRINTF("BAD-"); + DEBUG_CPRINTF("in_msg=["); + for (uint32_t i = 0; i < shi_params.sz_received; i++) + DEBUG_CPRINTF("%02x ", in_msg[i]); + DEBUG_CPRINTF("]\n"); + + /* Reset shi's state machine for error recovery */ + cros_shi_npcx_reset_prepare(inst); + + DEBUG_CPRINTF("END\n"); +} + +/* + * This routine write SHI output buffer from msg buffer over halt of it. + * It make sure we have enough time to handle next operations. + */ +static void shi_write_first_pkg_outbuf(struct shi_reg *const inst, + uint16_t szbytes) +{ + uint8_t size, offset; + volatile uint8_t *obuf_ptr; + volatile uint8_t *obuf_end; + uint8_t *msg_ptr; + uint32_t half_buf_remain; /* Remains in half buffer are free to write */ + + /* Start writing at our current OBUF position */ + offset = shi_valid_obuf_offset(inst); + obuf_ptr = inst->OBUF + offset; + msg_ptr = shi_params.tx_msg; + + /* Fill up to OBUF mid point, or OBUF end */ + half_buf_remain = SHI_OBUF_HALF_SIZE - (offset % SHI_OBUF_HALF_SIZE); + size = MIN(half_buf_remain, 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 == inst->OBUF + SHI_IBUF_FULL_SIZE) + obuf_ptr = inst->OBUF; + + /* 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; +} + +/** + * Called to send a response back to the host. + * + * Some commands can continue for a while. This function is called by + * host_command task after processing request is completed. It fills up the + * FIFOs with response package and the remaining data is handled in shi's ISR. + */ +static void shi_send_response_packet(struct host_packet *pkt) +{ + struct shi_reg *const inst = (struct shi_reg *)(cros_shi_cfg.base); + int key; + + /* + * 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. + */ + key = irq_lock(); + + if (state == SHI_STATE_PROCESSING) { + /* Append our past-end byte, which we reserved space for. */ + ((uint8_t *)pkt->response)[pkt->response_size] = + 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(inst, shi_params.sz_response); + /* Transmit the reply */ + state = SHI_STATE_SENDING; + DEBUG_CPRINTF("SND-"); + } else if (state == SHI_STATE_CNL_RESP_NOT_RDY) { + /* + * 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. + */ + cros_shi_npcx_reset_prepare(inst); + DEBUG_CPRINTF("END\n"); + } else + DEBUG_CPRINTS("Unexpected state %d in response handler", state); + + irq_unlock(key); +} + +void shi_handle_host_package(struct shi_reg *const inst) +{ + uint32_t sz_inbuf_int = shi_params.sz_request / SHI_IBUF_HALF_SIZE; + uint32_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; + uint32_t remain_bytes = shi_params.sz_request - shi_params.sz_received; + + /* Read remaining bytes from input buffer */ + if (!shi_read_inbuf_wait(inst, remain_bytes)) + return shi_bad_received_data(inst); + + /* Move to processing state */ + state = SHI_STATE_PROCESSING; + DEBUG_CPRINTF("PRC-"); + + /* Fill output buffer to indicate we`re processing request */ + shi_fill_out_status(inst, 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; + + /* Put FRAME_START in first byte */ + out_msg[0] = EC_SPI_FRAME_START; + 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); +} + +static void shi_parse_header(struct shi_reg *const inst) +{ + /* We're now inside a transaction */ + state = SHI_STATE_RECEIVING; + DEBUG_CPRINTF("RV-"); + + /* Setup deadline time for receiving */ + shi_params.rx_deadline = k_uptime_get() + SHI_CMD_RX_TIMEOUT_US; + + /* Wait for version, command, length bytes */ + if (!shi_read_inbuf_wait(inst, 3)) + return shi_bad_received_data(inst); + + 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 half of input buffer, + * we need to modified the algorithm again. + */ + __ASSERT_NO_MSG(sizeof(*r) < SHI_IBUF_HALF_SIZE); + + /* Wait for the rest of the command header */ + if (!shi_read_inbuf_wait(inst, sizeof(*r) - 3)) + return shi_bad_received_data(inst); + + /* 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(inst); + + /* Computing total bytes need to receive */ + shi_params.sz_request = pkt_size; + + shi_handle_host_package(inst); + } else { + /* Invalid version number */ + return shi_bad_received_data(inst); + } +} + +static void shi_sec_ibf_int_enable(struct shi_reg *const inst, int enable) +{ + if (enable) { + /* Setup IBUFLVL2 threshold and enable it */ + inst->SHICFG5 |= BIT(NPCX_SHICFG5_IBUFLVL2DIS); + SET_FIELD(inst->SHICFG5, NPCX_SHICFG5_IBUFLVL2, + SHI_IBUFLVL2_THRESHOLD); + inst->SHICFG5 &= ~BIT(NPCX_SHICFG5_IBUFLVL2DIS); + /* Enable IBHF2 event */ + inst->EVENABLE2 |= BIT(NPCX_EVENABLE2_IBHF2EN); + } else { + /* Disable IBHF2 event first */ + inst->EVENABLE2 &= ~BIT(NPCX_EVENABLE2_IBHF2EN); + /* Disable IBUFLVL2 and set threshold back to zero */ + inst->SHICFG5 |= BIT(NPCX_SHICFG5_IBUFLVL2DIS); + SET_FIELD(inst->SHICFG5, NPCX_SHICFG5_IBUFLVL2, 0); + } +} + +/* 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); +} + +/* + * Avoid spamming the console with prints every IBF / IBHF interrupt, if + * we find ourselves in an unexpected state. + */ +static enum cros_shi_npcx_state last_error_state = SHI_STATE_NONE; + +static void log_unexpected_state(char *isr_name) +{ + if (state != last_error_state) + DEBUG_CPRINTF("Unexpected state %d in %s ISR", state, isr_name); + last_error_state = state; +} + +static void shi_handle_cs_assert(struct shi_reg *const inst) +{ + /* If not enabled, ignore glitches on SHI_CS_L */ + if (state == SHI_STATE_DISABLED) + return; + + /* 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 */ + DEBUG_CPRINTF("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. + */ + inst->EVSTAT = BIT(NPCX_EVSTAT_EOR); + + /* Do not deep sleep during SHI transaction */ + disable_sleep(SLEEP_MASK_SPI); +} + +static void shi_handle_cs_deassert(struct shi_reg *const inst) +{ + /* + * 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(inst, 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. + */ + irq_disable(DT_INST_IRQN(0)); + + 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) { + log_unexpected_state("CSNRE"); + } + /* reset SHI and prepare to next transaction again */ + cros_shi_npcx_reset_prepare(inst); + DEBUG_CPRINTF("END\n"); +} + +static void shi_handle_input_buf_half_full(struct shi_reg *const inst) +{ + if (state == SHI_STATE_RECEIVING) { + /* Read data from input to msg buffer */ + shi_read_half_inbuf(); + return shi_handle_host_package(inst); + } else if (state == SHI_STATE_SENDING) { + /* Write data from msg buffer to output buffer */ + if (shi_params.tx_buf == inst->OBUF + SHI_OBUF_FULL_SIZE) { + /* Write data from bottom address again */ + shi_params.tx_buf = inst->OBUF; + return shi_write_half_outbuf(); + } else /* ignore it */ + return; + } else if (state == SHI_STATE_PROCESSING) { + /* Wait for host to handle request */ + } else { + /* Unexpected status */ + log_unexpected_state("IBHF"); + } +} + +static void shi_handle_input_buf_full(struct shi_reg *const inst) +{ + 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 = inst->IBUF; + return shi_handle_host_package(inst); + } else if (state == SHI_STATE_SENDING) { + /* Write data from msg buffer to output buffer */ + if (shi_params.tx_buf == inst->OBUF + SHI_OBUF_HALF_SIZE) + return shi_write_half_outbuf(); + else /* ignore it */ + return; + } else if (state == SHI_STATE_PROCESSING) { + /* Wait for host to handle request */ + return; + } + /* Unexpected status */ + log_unexpected_state("IBF"); +} + +static void cros_shi_npcx_isr(const struct device *dev) +{ + uint8_t stat; + uint8_t stat2; + struct shi_reg *const inst = HAL_INSTANCE(dev); + + /* Read status register and clear interrupt status early */ + stat = inst->EVSTAT; + inst->EVSTAT = stat; + stat2 = inst->EVSTAT2; + + /* SHI CS pin is asserted in EVSTAT2 */ + if (IS_BIT_SET(stat2, NPCX_EVSTAT2_CSNFE)) { + /* Clear pending bit of CSNFE */ + inst->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(inst->SHICFG2, NPCX_SHICFG2_BUSY)) { + DEBUG_CPRINTF("CSNB-"); + return; + } + shi_handle_cs_assert(inst); + } + + /* + * End of data for read/write transaction. i.e. SHI_CS is deasserted. + * Host completed or aborted transaction + * + * 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, NPCX_EVSTAT2_CSNRE)) { + /* Clear pending bit of CSNRE */ + inst->EVSTAT2 = BIT(NPCX_EVSTAT2_CSNRE); + /* + * We're not in proper state. + * Mark not ready to abort next transaction + */ + DEBUG_CPRINTF("CSH-"); + return shi_handle_cs_deassert(inst); + } + + /* + * The number of bytes received reaches the size of + * protocol V3 header(=8) after CS asserted. + */ + if (IS_BIT_SET(stat2, NPCX_EVSTAT2_IBHF2)) { + /* Clear IBHF2 */ + inst->EVSTAT2 = BIT(NPCX_EVSTAT2_IBHF2); + DEBUG_CPRINTF("HDR-"); + /* Disable second IBF interrupt and start to parse header */ + shi_sec_ibf_int_enable(inst, 0); + shi_parse_header(inst); + } + + /* + * Indicate input/output buffer pointer reaches the half buffer size. + * Transaction is processing. + */ + if (IS_BIT_SET(stat, NPCX_EVSTAT_IBHF)) { + return shi_handle_input_buf_half_full(inst); + } + + /* + * Indicate input/output buffer pointer reaches the full buffer size. + * Transaction is processing. + */ + if (IS_BIT_SET(stat, NPCX_EVSTAT_IBF)) { + return shi_handle_input_buf_full(inst); + } +} + +static void cros_shi_npcx_reset_prepare(struct shi_reg *const inst) +{ + uint32_t i; + + state = SHI_STATE_DISABLED; + + irq_disable(DT_INST_IRQN(0)); + + /* Disable SHI unit to clear all status bits */ + inst->SHICFG1 &= ~BIT(NPCX_SHICFG1_EN); + + /* Initialize parameters of next transaction */ + shi_params.rx_msg = in_msg; + shi_params.tx_msg = out_msg; + shi_params.rx_buf = inst->IBUF; + shi_params.tx_buf = inst->IBUF + SHI_OBUF_HALF_SIZE; + shi_params.sz_received = 0; + shi_params.sz_sending = 0; + shi_params.sz_request = 0; + shi_params.sz_response = 0; + + /* + * Fill output buffer to indicate we`re + * ready to receive next transaction. + */ + for (i = 1; i < SHI_OBUF_FULL_SIZE; i++) + inst->OBUF[i] = EC_SPI_RECEIVING; + inst->OBUF[0] = EC_SPI_OLD_READY; + + /* SHI/Host Write/input buffer wrap-around enable */ + inst->SHICFG1 = BIT(NPCX_SHICFG1_IWRAP) | BIT(NPCX_SHICFG1_WEN) | + BIT(NPCX_SHICFG1_EN); + + state = SHI_STATE_READY_TO_RECV; + last_error_state = SHI_STATE_NONE; + + shi_sec_ibf_int_enable(inst, 1); + irq_enable(DT_INST_IRQN(0)); + + /* Allow deep sleep at the end of SHI transaction */ + enable_sleep(SLEEP_MASK_SPI); + + DEBUG_CPRINTF("RDY-"); +} + +static int cros_shi_npcx_enable(const struct device *dev) +{ + const struct cros_shi_npcx_config *const config = DRV_CONFIG(dev); + struct shi_reg *const inst = HAL_INSTANCE(dev); + + cros_shi_npcx_reset_prepare(inst); + npcx_miwu_irq_disable(&config->shi_cs_wui); + + /* Configure pin-mux from GPIO to SHI. */ + npcx_pinctrl_mux_configure(config->alts_list, config->alts_size, 1); + + NVIC_ClearPendingIRQ(DT_INST_IRQN(0)); + npcx_miwu_irq_enable(&config->shi_cs_wui); + irq_enable(DT_INST_IRQN(0)); + + return 0; +} + +static int cros_shi_npcx_disable(const struct device *dev) +{ + const struct cros_shi_npcx_config *const config = DRV_CONFIG(dev); + + state = SHI_STATE_DISABLED; + + irq_disable(DT_INST_IRQN(0)); + npcx_miwu_irq_disable(&config->shi_cs_wui); + + /* Configure pin-mux from SHI to GPIO. */ + npcx_pinctrl_mux_configure(config->alts_list, config->alts_size, 0); + + return 0; + + /* + * 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); +} + +static int shi_npcx_init(const struct device *dev) +{ + int ret; + const struct cros_shi_npcx_config *const config = DRV_CONFIG(dev); + struct shi_reg *const inst = HAL_INSTANCE(dev); + const struct device *const clk_dev = + device_get_binding(NPCX_CLK_CTRL_NAME); + const struct npcx_alt shi_ver_ctrl[] = { + { .group = SHI_VER_CTRL_ALT_FILED(group), + .bit = SHI_VER_CTRL_ALT_FILED(bit), + .inverted = SHI_VER_CTRL_ALT_FILED(inv) } + }; + + /* Enable the new version of SHI hardware module. */ + npcx_pinctrl_mux_configure(shi_ver_ctrl, 1, 1); + + /* Turn on shi device clock first */ + ret = clock_control_on(clk_dev, + (clock_control_subsys_t *)&config->clk_cfg); + if (ret < 0) { + DEBUG_CPRINTF("Turn on SHI clock fail %d", ret); + return ret; + } + + /* + * TODO: for npcx9, HIF_TYP_SEL in DEVCNT register should be set + * by firmware because the BOOTER no longer touches it. + */ + + /* + * 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 + */ + inst->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 + */ + inst->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 + */ + inst->EVENABLE = 0x1C; + + /* + * 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 + */ + inst->EVENABLE2 = 0x06; + + /* Clear SHI events status register */ + inst->EVSTAT = 0xff; + + npcx_miwu_interrupt_configure(&config->shi_cs_wui, NPCX_MIWU_MODE_EDGE, + NPCX_MIWU_TRIG_LOW); + /* SHI interrupt installation */ + IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), + cros_shi_npcx_isr, DEVICE_DT_INST_GET(0), 0); + + return ret; +} + +static const struct cros_shi_driver_api cros_shi_npcx_driver_api = { + .enable = cros_shi_npcx_enable, + .disable = cros_shi_npcx_disable, +}; + +static struct cros_shi_npcx_data cros_shi_data; +DEVICE_DT_INST_DEFINE(0, shi_npcx_init, device_pm_control_nop, &cros_shi_data, + &cros_shi_cfg, PRE_KERNEL_1, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, + &cros_shi_npcx_driver_api); + +/* KBS register structure check */ +NPCX_REG_SIZE_CHECK(shi_reg, 0x120); +NPCX_REG_OFFSET_CHECK(shi_reg, SHICFG1, 0x001); +NPCX_REG_OFFSET_CHECK(shi_reg, EVENABLE, 0x005); +NPCX_REG_OFFSET_CHECK(shi_reg, IBUFSTAT, 0x00a); +NPCX_REG_OFFSET_CHECK(shi_reg, EVENABLE2, 0x010); +NPCX_REG_OFFSET_CHECK(shi_reg, OBUF, 0x020); +NPCX_REG_OFFSET_CHECK(shi_reg, IBUF, 0x0A0); diff --git a/zephyr/dts/bindings/cros_shi/nuvoton,npcx-cros-shi.yaml b/zephyr/dts/bindings/cros_shi/nuvoton,npcx-cros-shi.yaml new file mode 100644 index 0000000000..cf54cde7f8 --- /dev/null +++ b/zephyr/dts/bindings/cros_shi/nuvoton,npcx-cros-shi.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2021 Google Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton, NPCX Serial Host Interface (SHI) node + +compatible: "nuvoton,npcx-cros-shi" + +include: base.yaml + +properties: + reg: + description: mmio register space + required: true + + clocks: + required: true + description: configurations of device source clock controller + + pinctrl-0: + type: phandles + required: true + description: configurations of pinmux controllers + + ver-ctrl: + type: phandle + required: true + description: configurations of SHI module version + shi-cs-wui: + type: phandle + required: true + description: | + Mapping table between Wake-Up Input (WUI) and SHI_CS signal. + + For example the WUI mapping on NPCX7 would be + shi-cs-wui = <&wui_io53>; diff --git a/zephyr/include/cros/nuvoton/npcx.dtsi b/zephyr/include/cros/nuvoton/npcx.dtsi index 569995d775..9c646423bf 100644 --- a/zephyr/include/cros/nuvoton/npcx.dtsi +++ b/zephyr/include/cros/nuvoton/npcx.dtsi @@ -6,6 +6,8 @@ /dts-v1/; +#include <nuvoton/npcx7m6fb.dtsi> + / { named-bbram-regions { @@ -84,6 +86,17 @@ mtc_alarm = <&wui_mtc>; label = "MTC"; }; + + shi: shi@4000f000 { + compatible = "nuvoton,npcx-cros-shi"; + reg = <0x4000f000 0x120>; + interrupts = <18 2>; + clocks = <&pcc NPCX_CLOCK_BUS_APB3 NPCX_PWDWN_CTL5 1>; + pinctrl-0 = <&altc_shi_sl>; + ver-ctrl = <&altf_shi_new>; + shi-cs-wui =<&wui_io53>; + label = "SHI"; + }; }; }; diff --git a/zephyr/include/drivers/cros_shi.h b/zephyr/include/drivers/cros_shi.h new file mode 100644 index 0000000000..aab3507dd3 --- /dev/null +++ b/zephyr/include/drivers/cros_shi.h @@ -0,0 +1,89 @@ +/* Copyright 2021 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. + */ + +/** + * @file + * @brief Chrome OS-specific API for Serial Host Interface (SHI) + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_CROS_SHI_H_ +#define ZEPHYR_INCLUDE_DRIVERS_CROS_SHI_H_ + +/** + * @brief CROS Serial Host Interface Driver APIs + * @defgroup cros_shi_interface CROS Serial Host Interface Driver APIs + * @ingroup io_interfaces + * @{ + */ + +#include <kernel.h> +#include <device.h> + +/** + * @cond INTERNAL_HIDDEN + * + * cros Serial Host Interface driver API definition and system call entry points + * + * (Internal use only.) + */ +typedef int (*cros_shi_api_enable)(const struct device *dev); + +typedef int (*cros_shi_api_disable)(const struct device *dev); + +/** @brief Driver API structure. */ +__subsystem struct cros_shi_driver_api { + cros_shi_api_enable enable; + cros_shi_api_disable disable; +}; + +/** + * @brief Enable SHI module. + * + * @param dev Pointer to the device structure for the driver instance. + * + * @retval non-negative if successful. + * @retval Negative errno code if failure. + */ +__syscall int cros_shi_enable(const struct device *dev); + +static inline int z_impl_cros_shi_enable(const struct device *dev) +{ + const struct cros_shi_driver_api *api = + (const struct cros_shi_driver_api *)dev->api; + + if (!api->enable) { + return -ENOTSUP; + } + + return api->enable(dev); +} + +/** + * @brief Disable SHI module. + * + * @param dev Pointer to the device structure for the driver instance. + * + * @retval no return if successful. + * @retval Negative errno code if failure. + */ +__syscall int cros_shi_disable(const struct device *dev); + +static inline int z_impl_cros_shi_disable(const struct device *dev) +{ + const struct cros_shi_driver_api *api = + (const struct cros_shi_driver_api *)dev->api; + + if (!api->disable) { + return -ENOTSUP; + } + + return api->disable(dev); +} + +/** + * @} + */ +#include <syscalls/cros_shi.h> +#endif /* ZEPHYR_INCLUDE_DRIVERS_CROS_SHI_H_ */ diff --git a/zephyr/include/soc/nuvoton_npcx/reg_def_cros.h b/zephyr/include/soc/nuvoton_npcx/reg_def_cros.h index 7b577bed32..8702502a13 100644 --- a/zephyr/include/soc/nuvoton_npcx/reg_def_cros.h +++ b/zephyr/include/soc/nuvoton_npcx/reg_def_cros.h @@ -165,4 +165,87 @@ struct mtc_reg { #define NPCX_WTC_PTO 30 #define NPCX_WTC_WIE 31 +/* SHI (Serial Host Interface) registers */ +struct shi_reg { + volatile uint8_t reserved1; + /* 0x001: SHI Configuration 1 */ + volatile uint8_t SHICFG1; + /* 0x002: SHI Configuration 2 */ + volatile uint8_t SHICFG2; + volatile uint8_t reserved2[2]; + /* 0x005: Event Enable */ + volatile uint8_t EVENABLE; + /* 0x006: Event Status */ + volatile uint8_t EVSTAT; + /* 0x007: SHI Capabilities */ + volatile uint8_t CAPABILITY; + /* 0x008: Status */ + volatile uint8_t STATUS; + volatile uint8_t reserved3; + /* 0x00A: Input Buffer Status */ + volatile uint8_t IBUFSTAT; + /* 0x00B: Output Buffer Status */ + volatile uint8_t OBUFSTAT; + /* 0x00C: SHI Configuration 3 */ + volatile uint8_t SHICFG3; + /* 0x00D: SHI Configuration 4 */ + volatile uint8_t SHICFG4; + /* 0x00E: SHI Configuration 5 */ + volatile uint8_t SHICFG5; + /* 0x00F: Event Status 2 */ + volatile uint8_t EVSTAT2; + /* 0x010: Event Enable 2 */ + volatile uint8_t EVENABLE2; + volatile uint8_t reserved4[15]; + /* 0x20~0x9F: Output Buffer */ + volatile uint8_t OBUF[128]; + /* 0xA0~0x11F: Input Buffer */ + volatile uint8_t IBUF[128]; +}; + +/* SHI register fields */ +#define NPCX_SHICFG1_EN 0 +#define NPCX_SHICFG1_MODE 1 +#define NPCX_SHICFG1_WEN 2 +#define NPCX_SHICFG1_AUTIBF 3 +#define NPCX_SHICFG1_AUTOBE 4 +#define NPCX_SHICFG1_DAS 5 +#define NPCX_SHICFG1_CPOL 6 +#define NPCX_SHICFG1_IWRAP 7 +#define NPCX_SHICFG2_SIMUL 0 +#define NPCX_SHICFG2_BUSY 1 +#define NPCX_SHICFG2_ONESHOT 2 +#define NPCX_SHICFG2_SLWU 3 +#define NPCX_SHICFG2_REEN 4 +#define NPCX_SHICFG2_RESTART 5 +#define NPCX_SHICFG2_REEVEN 6 +#define NPCX_EVENABLE_OBEEN 0 +#define NPCX_EVENABLE_OBHEEN 1 +#define NPCX_EVENABLE_IBFEN 2 +#define NPCX_EVENABLE_IBHFEN 3 +#define NPCX_EVENABLE_EOREN 4 +#define NPCX_EVENABLE_EOWEN 5 +#define NPCX_EVENABLE_STSREN 6 +#define NPCX_EVENABLE_IBOREN 7 +#define NPCX_EVSTAT_OBE 0 +#define NPCX_EVSTAT_OBHE 1 +#define NPCX_EVSTAT_IBF 2 +#define NPCX_EVSTAT_IBHF 3 +#define NPCX_EVSTAT_EOR 4 +#define NPCX_EVSTAT_EOW 5 +#define NPCX_EVSTAT_STSR 6 +#define NPCX_EVSTAT_IBOR 7 +#define NPCX_STATUS_OBES 6 +#define NPCX_STATUS_IBFS 7 +#define NPCX_SHICFG3_OBUFLVLDIS 7 +#define NPCX_SHICFG4_IBUFLVLDIS 7 +#define NPCX_SHICFG5_IBUFLVL2 FIELD(0, 6) +#define NPCX_SHICFG5_IBUFLVL2DIS 7 +#define NPCX_EVSTAT2_IBHF2 0 +#define NPCX_EVSTAT2_CSNRE 1 +#define NPCX_EVSTAT2_CSNFE 2 +#define NPCX_EVENABLE2_IBHF2EN 0 +#define NPCX_EVENABLE2_CSNREEN 1 +#define NPCX_EVENABLE2_CSNFEEN 2 + #endif /* _NUVOTON_NPCX_REG_DEF_CROS_H */ |