ec_host_cmd: add NPCX SHI peripheral for the host commands

This commit adds the support for host commands being transported
by the Serial Host Interface on the NPCX SoC.

Signed-off-by: Michał Barnaś <mb@semihalf.com>
This commit is contained in:
Michał Barnaś 2022-08-01 18:06:33 +02:00 committed by Carles Cufí
commit 3ae105e76b
9 changed files with 1264 additions and 0 deletions

View file

@ -9,3 +9,8 @@ zephyr_library_sources_ifdef(
zephyr_library_sources_ifdef( zephyr_library_sources_ifdef(
CONFIG_EC_HOST_CMD_PERIPH_ESPI CONFIG_EC_HOST_CMD_PERIPH_ESPI
ec_host_cmd_periph_espi.c) ec_host_cmd_periph_espi.c)
zephyr_library_sources_ifdef(
CONFIG_EC_HOST_CMD_PERIPH_SHI_NPCX
ec_host_cmd_periph_shi_npcx.c)

View file

@ -35,6 +35,45 @@ config EC_HOST_CMD_PERIPH_ESPI
Enable support for Embedded Controller host commands using Enable support for Embedded Controller host commands using
the eSPI bus. the eSPI bus.
config EC_HOST_CMD_PERIPH_SHI
bool "Host commands support using SHI"
help
Enable support for Embedded Controller host commands using
the Serial Host Interface.
endchoice endchoice
if EC_HOST_CMD_PERIPH_SHI
choice EC_HOST_CMD_PERIPH_SHI_DRIVER
prompt "SHI driver"
default EC_HOST_CMD_PERIPH_SHI_NPCX if SOC_FAMILY_NPCX
config EC_HOST_CMD_PERIPH_SHI_NPCX
bool "SHI by Nuvoton"
depends on DT_HAS_NUVOTON_NPCX_SHI_ENABLED
help
This option enables the driver for SHI peripheral in the
Nuvoton NPCX chip.
endchoice
config EC_HOST_CMD_PERIPH_SHI_MAX_REQUEST
int "Max data size for the version 3 request packet"
default 544 if EC_HOST_CMD_PERIPH_SHI_NPCX
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 EC_HOST_CMD_PERIPH_SHI_MAX_RESPONSE
int "Max data size for the version 3 response packet"
default 544 if EC_HOST_CMD_PERIPH_SHI_NPCX
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.
endif # EC_HOST_CMD_PERIPH_SHI
endif # EC_HOST_CMD_PERIPH endif # EC_HOST_CMD_PERIPH

View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2022 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_EC_HOST_CMD_PERIPH_SHI_H_
#define ZEPHYR_DRIVERS_EC_HOST_CMD_PERIPH_SHI_H_
#include <zephyr/device.h>
/*
* Byte codes returned by EC over SPI interface.
*
* These can be used by the AP to debug the EC interface, and to determine
* when the EC is not in a state where it will ever get around to responding
* to the AP.
*
* Example of sequence of bytes read from EC for a current good transfer:
* 1. - - AP asserts chip select (CS#)
* 2. EC_SHI_OLD_READY - AP sends first byte(s) of request
* 3. - - EC starts handling CS# interrupt
* 4. EC_SHI_RECEIVING - AP sends remaining byte(s) of request
* 5. EC_SHI_PROCESSING - EC starts processing request; AP is clocking in
* bytes looking for EC_SHI_FRAME_START
* 6. - - EC finishes processing and sets up response
* 7. EC_SHI_FRAME_START - AP reads frame byte
* 8. (response packet) - AP reads response packet
* 9. EC_SHI_PAST_END - Any additional bytes read by AP
* 10 - - AP deasserts chip select
* 11 - - EC processes CS# interrupt and sets up DMA for
* next request
*
* If the AP is waiting for EC_SHI_FRAME_START and sees any value other than
* the following byte values:
* EC_SHI_OLD_READY
* EC_SHI_RX_READY
* EC_SHI_RECEIVING
* EC_SHI_PROCESSING
*
* Then the EC found an error in the request, or was not ready for the request
* and lost data. The AP should give up waiting for EC_SHI_FRAME_START,
* because the EC is unable to tell when the AP is done sending its request.
*/
/*
* Framing byte which precedes a response packet from the EC. After sending a
* request, the AP will clock in bytes until it sees the framing byte, then
* clock in the response packet.
*/
#define EC_SHI_FRAME_START 0xec
/*
* Padding bytes which are clocked out after the end of a response packet.
*/
#define EC_SHI_PAST_END 0xed
/*
* EC is ready to receive, and has ignored the byte sent by the AP. EC expects
* that the AP will send a valid packet header (starting with
* EC_COMMAND_PROTOCOL_3) in the next 32 bytes.
*
* NOTE: Some SPI configurations place the Most Significant Bit on SDO when
* CS goes low. This macro has the Most Significant Bit set to zero,
* so SDO will not be driven high when CS goes low.
*/
#define EC_SHI_RX_READY 0x78
/*
* EC has started receiving the request from the AP, but hasn't started
* processing it yet.
*/
#define EC_SHI_RECEIVING 0xf9
/* EC has received the entire request from the AP and is processing it. */
#define EC_SHI_PROCESSING 0xfa
/*
* EC received bad data from the AP, such as a packet header with an invalid
* length. EC will ignore all data until chip select deasserts.
*/
#define EC_SHI_RX_BAD_DATA 0xfb
/*
* EC received data from the AP before it was ready. That is, the AP asserted
* chip select and started clocking data before the EC was ready to receive it.
* EC will ignore all data until chip select deasserts.
*/
#define EC_SHI_NOT_READY 0xfc
/*
* EC was ready to receive a request from the AP. EC has treated the byte sent
* by the AP as part of a request packet, or (for old-style ECs) is processing
* a fully received packet but is not ready to respond yet.
*/
#define EC_SHI_OLD_READY 0xfd
/* Supported version of host commands protocol. */
#define EC_HOST_REQUEST_VERSION 3
#endif /* ZEPHYR_DRIVERS_EC_HOST_CMD_PERIPH_SHI_H_ */

View file

@ -0,0 +1,976 @@
/*
* Copyright (c) 2022 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nuvoton_npcx_shi
#include "ec_host_cmd_periph_shi.h"
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/ec_host_cmd_periph/ec_host_cmd_periph.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/logging/log.h>
#include <zephyr/mgmt/ec_host_cmd.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <soc_miwu.h>
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, "Invalid number of NPCX SHI peripherals");
LOG_MODULE_REGISTER(host_cmd_shi_npcx, CONFIG_EC_HC_LOG_LEVEL);
/* Driver convenience defines */
#define HAL_INSTANCE(dev) (struct shi_reg *)(((const struct shi_npcx_config *)(dev)->config)->base)
/* Full output buffer size */
#define SHI_OBUF_FULL_SIZE DT_INST_PROP(0, buffer_tx_size)
/* Full input buffer size */
#define SHI_IBUF_FULL_SIZE DT_INST_PROP(0, buffer_rx_size)
/* Configure the IBUFLVL2 = the size of V3 protocol header */
#define SHI_IBUFLVL2_THRESHOLD (sizeof(struct ec_host_cmd_request_header))
/* 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 SPI controller 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 EC_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 EC_SHI_OUT_PREAMBLE_LENGTH 2
/*
* Space allocation of the past-end status byte (EC_SHI_PAST_END) in the out_msg buffer.
*/
#define EC_SHI_PAST_END_LENGTH 1
/*
* Space allocation of the frame status byte (EC_SHI_FRAME_START) in the out_msg buffer.
*/
#define EC_SHI_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 EC_SHI_PROTO3_OVERHEAD (EC_SHI_PAST_END_LENGTH + EC_SHI_FRAME_START_LENGTH)
/*
* 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_SHI_FRAME_START_LENGTH / 4 + 1))
#define SHI_OUT_END_PAD (4 * (EC_SHI_PAST_END_LENGTH / 4 + 1))
enum 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,
};
/* Device config */
struct shi_npcx_config {
/* Serial Host Interface (SHI) base address */
uintptr_t base;
/* Clock configuration */
struct npcx_clk_cfg clk_cfg;
/* Pin control configuration */
const struct pinctrl_dev_config *pcfg;
/* Chip-select interrupts */
int irq;
struct npcx_wui shi_cs_wui;
};
struct shi_npcx_data {
/* Handler mutexes */
struct k_sem handler_owns;
struct k_sem dev_owns;
/* Communication status */
enum shi_npcx_state state;
enum shi_npcx_state last_error_state;
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 */
uint32_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 */
/* Buffers */
uint8_t out_msg_padded[SHI_OUT_START_PAD + CONFIG_EC_HOST_CMD_PERIPH_SHI_MAX_RESPONSE +
SHI_OUT_END_PAD] __aligned(4);
uint8_t *const out_msg;
uint8_t in_msg[CONFIG_EC_HOST_CMD_PERIPH_SHI_MAX_REQUEST] __aligned(4);
};
/* Forward declaration */
static void shi_npcx_reset_prepare(const struct device *dev);
/* Read pointer of input or output buffer by consecutive reading */
static uint32_t shi_npcx_read_buf_pointer(struct shi_reg *const inst)
{
uint8_t stat;
/* Wait for two consecutive equal values 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_npcx_valid_obuf_offset(struct shi_reg *const inst)
{
return (shi_npcx_read_buf_pointer(inst) + EC_SHI_OUT_PREAMBLE_LENGTH) % SHI_OBUF_FULL_SIZE;
}
/*
* This routine write SHI next half output buffer from msg buffer
*/
static void shi_npcx_write_half_outbuf(const struct device *dev)
{
struct shi_npcx_data *data = dev->data;
const uint32_t size = MIN(SHI_OBUF_HALF_SIZE, data->sz_response - data->sz_sending);
uint8_t *obuf_ptr = (uint8_t *)data->tx_buf;
const uint8_t *obuf_end = obuf_ptr + size;
uint8_t *msg_ptr = data->tx_msg;
/* Fill half output buffer */
while (obuf_ptr != obuf_end) {
*obuf_ptr++ = *msg_ptr++;
}
data->sz_sending += size;
data->tx_buf = obuf_ptr;
data->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_npcx_read_inbuf_wait(const struct device *dev, uint32_t szbytes)
{
struct shi_npcx_data *data = dev->data;
struct shi_reg *const inst = HAL_INSTANCE(dev);
/* Copy data to msg buffer from input buffer */
for (uint32_t i = 0; i < szbytes; i++, data->sz_received++) {
/*
* If input buffer pointer equals pointer which wants to read,
* it means data is not ready.
*/
while (data->rx_buf == inst->IBUF + shi_npcx_read_buf_pointer(inst)) {
if (k_cycle_get_64() >= data->rx_deadline) {
return 0;
}
}
/* Copy data to msg buffer */
*data->rx_msg++ = *data->rx_buf++;
}
return 1;
}
/* This routine fills out all SHI output buffer with status byte */
static void shi_npcx_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;
/*
* Disable interrupts in case the interfere by the other interrupts.
* Use __disable_irq/__enable_irq instead of using irq_lock/irq_unlock
* here because irq_lock/irq_unlock leave some system exceptions (like
* SVC, NMI, and faults) still enabled.
*/
__disable_irq();
/*
* 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_npcx_valid_obuf_offset(inst);
end = (start + SHI_OBUF_FULL_SIZE - EC_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 */
__enable_irq();
}
/* This routine handles shi received unexpected data */
static void shi_npcx_bad_received_data(const struct device *dev)
{
struct shi_npcx_data *data = dev->data;
struct shi_reg *const inst = HAL_INSTANCE(dev);
/* State machine mismatch, timeout, or protocol we can't handle. */
shi_npcx_fill_out_status(inst, EC_SHI_RX_BAD_DATA);
data->state = SHI_STATE_BAD_RECEIVED_DATA;
LOG_ERR("SHI bad data recv");
LOG_DBG("BAD-");
LOG_HEXDUMP_DBG(data->in_msg, data->sz_received, "in_msg=");
/* Reset shi's state machine for error recovery */
shi_npcx_reset_prepare(dev);
LOG_DBG("END");
}
/*
* 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_npcx_write_first_pkg_outbuf(const struct device *dev, uint16_t szbytes)
{
struct shi_npcx_data *data = dev->data;
struct shi_reg *const inst = HAL_INSTANCE(dev);
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_npcx_valid_obuf_offset(inst);
obuf_ptr = inst->OBUF + offset;
msg_ptr = data->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 - data->sz_sending);
obuf_end = obuf_ptr + size;
while (obuf_ptr != obuf_end) {
*obuf_ptr++ = *msg_ptr++;
}
/* Track bytes sent for later accounting */
data->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 - data->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 */
data->sz_sending += size;
data->tx_buf = obuf_ptr;
data->tx_msg = msg_ptr;
}
static void shi_npcx_handle_host_package(const struct device *dev)
{
struct shi_npcx_data *data = dev->data;
struct shi_reg *const inst = HAL_INSTANCE(dev);
uint32_t sz_inbuf_int = data->sz_request / SHI_IBUF_HALF_SIZE;
uint32_t cnt_inbuf_int = data->sz_received / SHI_IBUF_HALF_SIZE;
if (sz_inbuf_int - cnt_inbuf_int) {
/* Need to receive data from buffer */
return;
}
uint32_t remain_bytes = data->sz_request - data->sz_received;
/* Read remaining bytes from input buffer */
if (!shi_npcx_read_inbuf_wait(dev, remain_bytes)) {
return shi_npcx_bad_received_data(dev);
}
/* Move to processing state */
data->state = SHI_STATE_PROCESSING;
LOG_DBG("PRC-");
/* Fill output buffer to indicate we`re processing request */
shi_npcx_fill_out_status(inst, EC_SHI_PROCESSING);
data->out_msg[0] = EC_SHI_FRAME_START;
/* Wake-up the HC handler thread */
k_sem_give(&data->handler_owns);
}
static int shi_npcx_host_request_expected_size(const struct ec_host_cmd_request_header *r)
{
/* Check host request version */
if (r->prtcl_ver != EC_HOST_REQUEST_VERSION) {
return 0;
}
/* Reserved byte should be 0 */
if (r->reserved) {
return 0;
}
return sizeof(*r) + r->data_len;
}
static void shi_npcx_parse_header(const struct device *dev)
{
struct shi_npcx_data *data = dev->data;
/* We're now inside a transaction */
data->state = SHI_STATE_RECEIVING;
LOG_DBG("RV-");
/* Setup deadline time for receiving */
data->rx_deadline = k_cycle_get_64() + k_us_to_cyc_near64(EC_SHI_CMD_RX_TIMEOUT_US);
/* Wait for version, command, length bytes */
if (!shi_npcx_read_inbuf_wait(dev, 3)) {
return shi_npcx_bad_received_data(dev);
}
if (data->in_msg[0] == EC_HOST_REQUEST_VERSION) {
/* Protocol version 3 */
struct ec_host_cmd_request_header *r =
(struct ec_host_cmd_request_header *)data->in_msg;
int pkt_size;
/*
* If request is over half of input buffer, we need to modify the algorithm again.
*/
__ASSERT_NO_MSG(sizeof(*r) < SHI_IBUF_HALF_SIZE);
/* Wait for the rest of the command header */
if (!shi_npcx_read_inbuf_wait(dev, sizeof(*r) - 3)) {
return shi_npcx_bad_received_data(dev);
}
/* Check how big the packet should be */
pkt_size = shi_npcx_host_request_expected_size(r);
if (pkt_size == 0 || pkt_size > sizeof(data->in_msg)) {
return shi_npcx_bad_received_data(dev);
}
/* Computing total bytes need to receive */
data->sz_request = pkt_size;
shi_npcx_handle_host_package(dev);
} else {
/* Invalid version number */
return shi_npcx_bad_received_data(dev);
}
}
static void shi_npcx_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_npcx_read_half_inbuf(const struct device *dev)
{
struct shi_npcx_data *data = dev->data;
/*
* Copy to read buffer until reaching middle/top address of
* input buffer or completing receiving data
*/
do {
/* Restore data to msg buffer */
*data->rx_msg++ = *data->rx_buf++;
data->sz_received++;
} while (data->sz_received % SHI_IBUF_HALF_SIZE && data->sz_received != data->sz_request);
}
/*
* Avoid spamming the console with prints every IBF / IBHF interrupt, if
* we find ourselves in an unexpected state.
*/
static void shi_npcx_log_unexpected_state(const struct device *dev, char *isr_name)
{
struct shi_npcx_data *data = dev->data;
if (data->state != data->last_error_state) {
LOG_ERR("Unexpected state %d in %s ISR", data->state, isr_name);
}
data->last_error_state = data->state;
}
static void shi_npcx_handle_cs_assert(const struct device *dev)
{
struct shi_reg *const inst = HAL_INSTANCE(dev);
struct shi_npcx_data *data = dev->data;
/* If not enabled, ignore glitches on SHI_CS_L */
if (data->state == SHI_STATE_DISABLED) {
return;
}
/* NOT_READY should be sent and there're no spi transaction now. */
if (data->state == SHI_STATE_CNL_RESP_NOT_RDY) {
return;
}
/* Chip select is low = asserted */
if (data->state != SHI_STATE_READY_TO_RECV) {
/* State machine should be reset in EVSTAT_EOR ISR */
LOG_ERR("Unexpected state %d in CS ISR", data->state);
return;
}
LOG_DBG("CSL-");
/*
* Clear possible EOR event from previous transaction since it's
* irrelevant now that CS is re-asserted.
*/
inst->EVSTAT = BIT(NPCX_EVSTAT_EOR);
}
static void shi_npcx_handle_cs_deassert(const struct device *dev)
{
struct shi_reg *const inst = HAL_INSTANCE(dev);
struct shi_npcx_data *data = dev->data;
/*
* If the buffer is still used by the host command.
* Change state machine for response handler.
*/
if (data->state == SHI_STATE_PROCESSING) {
/*
* Mark not ready to prevent the other
* transaction immediately
*/
shi_npcx_fill_out_status(inst, EC_SHI_NOT_READY);
data->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));
LOG_DBG("CNL-");
return;
/* Next transaction but we're not ready */
} else if (data->state == SHI_STATE_CNL_RESP_NOT_RDY) {
return;
}
/* Error state for checking*/
if (data->state != SHI_STATE_SENDING) {
shi_npcx_log_unexpected_state(dev, "CSNRE");
}
/* reset SHI and prepare to next transaction again */
shi_npcx_reset_prepare(dev);
LOG_DBG("END\n");
}
static void shi_npcx_handle_input_buf_half_full(const struct device *dev)
{
struct shi_reg *const inst = HAL_INSTANCE(dev);
struct shi_npcx_data *data = dev->data;
if (data->state == SHI_STATE_RECEIVING) {
/* Read data from input to msg buffer */
shi_npcx_read_half_inbuf(dev);
return shi_npcx_handle_host_package(dev);
} else if (data->state == SHI_STATE_SENDING) {
/* Write data from msg buffer to output buffer */
if (data->tx_buf == inst->OBUF + SHI_OBUF_FULL_SIZE) {
/* Write data from bottom address again */
data->tx_buf = inst->OBUF;
shi_npcx_write_half_outbuf(dev);
}
} else if (data->state == SHI_STATE_PROCESSING) {
/* Wait for host to handle request */
} else {
/* Unexpected status */
shi_npcx_log_unexpected_state(dev, "IBHF");
}
}
static void shi_npcx_handle_input_buf_full(const struct device *dev)
{
struct shi_npcx_data *data = dev->data;
struct shi_reg *const inst = HAL_INSTANCE(dev);
if (data->state == SHI_STATE_RECEIVING) {
/* read data from input to msg buffer */
shi_npcx_read_half_inbuf(dev);
/* Read to bottom address again */
data->rx_buf = inst->IBUF;
return shi_npcx_handle_host_package(dev);
} else if (data->state == SHI_STATE_SENDING) {
/* Write data from msg buffer to output buffer */
if (data->tx_buf == inst->OBUF + SHI_OBUF_HALF_SIZE) {
shi_npcx_write_half_outbuf(dev);
}
return;
} else if (data->state == SHI_STATE_PROCESSING) {
/* Wait for host to handle request */
return;
}
/* Unexpected status */
shi_npcx_log_unexpected_state(dev, "IBF");
}
static void shi_npcx_isr(const struct device *dev)
{
struct shi_reg *const inst = HAL_INSTANCE(dev);
uint8_t stat;
uint8_t stat2;
/* 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);
LOG_DBG("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)) {
LOG_DBG("CSNB-");
return;
}
shi_npcx_handle_cs_assert(dev);
}
/*
* 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
*/
LOG_DBG("CSH-");
return shi_npcx_handle_cs_deassert(dev);
}
/*
* 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);
LOG_DBG("HDR-");
/* Disable second IBF interrupt and start to parse header */
shi_npcx_sec_ibf_int_enable(inst, 0);
shi_npcx_parse_header(dev);
}
/*
* Indicate input/output buffer pointer reaches the half buffer size.
* Transaction is processing.
*/
if (IS_BIT_SET(stat, NPCX_EVSTAT_IBHF)) {
return shi_npcx_handle_input_buf_half_full(dev);
}
/*
* Indicate input/output buffer pointer reaches the full buffer size.
* Transaction is processing.
*/
if (IS_BIT_SET(stat, NPCX_EVSTAT_IBF)) {
return shi_npcx_handle_input_buf_full(dev);
}
}
static void shi_npcx_reset_prepare(const struct device *dev)
{
struct shi_reg *const inst = HAL_INSTANCE(dev);
struct shi_npcx_data *data = dev->data;
uint32_t i;
data->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 */
data->rx_msg = data->in_msg;
data->tx_msg = data->out_msg;
data->rx_buf = inst->IBUF;
data->tx_buf = inst->OBUF;
data->sz_received = 0;
data->sz_sending = 0;
data->sz_request = 0;
data->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_SHI_RECEIVING;
}
inst->OBUF[0] = EC_SHI_RX_READY;
/* SHI/Host Write/input buffer wrap-around enable */
inst->SHICFG1 = BIT(NPCX_SHICFG1_IWRAP) | BIT(NPCX_SHICFG1_WEN) | BIT(NPCX_SHICFG1_EN);
data->state = SHI_STATE_READY_TO_RECV;
data->last_error_state = SHI_STATE_NONE;
shi_npcx_sec_ibf_int_enable(inst, 1);
irq_enable(DT_INST_IRQN(0));
LOG_DBG("RDY-");
}
static int shi_npcx_enable(const struct device *dev)
{
const struct device *clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE);
const struct shi_npcx_config *const config = dev->config;
int ret;
ret = clock_control_on(clk_dev, (clock_control_subsys_t *)&config->clk_cfg);
if (ret < 0) {
LOG_ERR("Turn on SHI clock fail %d", ret);
return ret;
}
shi_npcx_reset_prepare(dev);
npcx_miwu_irq_disable(&config->shi_cs_wui);
/* Configure pin control for SHI */
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
if (ret < 0) {
LOG_ERR("shi_npcx pinctrl setup failed (%d)", ret);
return ret;
}
NVIC_ClearPendingIRQ(DT_INST_IRQN(0));
npcx_miwu_irq_enable(&config->shi_cs_wui);
irq_enable(DT_INST_IRQN(0));
return 0;
}
static int shi_npcx_disable(const struct device *dev)
{
const struct device *clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE);
const struct shi_npcx_config *const config = dev->config;
struct shi_npcx_data *data = dev->data;
int ret;
data->state = SHI_STATE_DISABLED;
irq_disable(DT_INST_IRQN(0));
npcx_miwu_irq_disable(&config->shi_cs_wui);
/* Configure pin control back to GPIO */
ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_SLEEP);
if (ret < 0) {
LOG_ERR("KB Raw pinctrl setup failed (%d)", ret);
return ret;
}
ret = clock_control_off(clk_dev, (clock_control_subsys_t *)&config->clk_cfg);
if (ret < 0) {
LOG_ERR("Turn off SHI clock fail %d", ret);
return ret;
}
return 0;
}
static int shi_npcx_init_registers(const struct device *dev)
{
int ret;
const struct shi_npcx_config *const config = dev->config;
struct shi_reg *const inst = HAL_INSTANCE(dev);
const struct device *clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE);
/* Turn on shi device clock first */
ret = clock_control_on(clk_dev, (clock_control_subsys_t *)&config->clk_cfg);
if (ret < 0) {
LOG_ERR("Turn on SHI clock fail %d", ret);
return ret;
}
/* If booter doesn't set the host interface type */
if (!NPCX_BOOTER_IS_HIF_TYPE_SET()) {
npcx_host_interface_sel(NPCX_HIF_TYPE_ESPI_SHI);
}
/*
* 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 = BIT(NPCX_SHICFG1_IWRAP);
/*
* 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 = BIT(NPCX_SHICFG2_SIMUL);
/*
* 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 =
BIT(NPCX_EVENABLE_EOREN) | BIT(NPCX_EVENABLE_IBHFEN) | BIT(NPCX_EVENABLE_IBFEN);
/*
* 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 = BIT(NPCX_EVENABLE2_CSNREEN) | BIT(NPCX_EVENABLE2_CSNFEEN);
/* 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), shi_npcx_isr, DEVICE_DT_INST_GET(0),
0);
shi_npcx_enable(dev);
return ret;
}
static int shi_npcx_init_peripheral(const struct device *dev)
{
struct shi_npcx_data *data = dev->data;
/* Allow writing to rx buff at startup and block on reading. */
k_sem_init(&data->handler_owns, 0, 1);
k_sem_init(&data->dev_owns, 1, 1);
return 0;
}
static int shi_npcx_init(const struct device *dev)
{
int ret;
ret = shi_npcx_init_registers(dev);
if (ret) {
return ret;
}
ret = shi_npcx_init_peripheral(dev);
if (ret) {
return ret;
}
pm_device_init_suspended(dev);
return pm_device_runtime_enable(dev);
}
static int shi_npcx_periph_init_context(const struct device *dev,
struct ec_host_cmd_periph_rx_ctx *rx_ctx)
{
struct shi_npcx_data *data = dev->data;
rx_ctx->dev_owns = &data->dev_owns;
rx_ctx->handler_owns = &data->handler_owns;
rx_ctx->buf = data->in_msg;
rx_ctx->len = &data->sz_received;
return 0;
}
static int shi_npcx_periph_send(const struct device *dev,
const struct ec_host_cmd_periph_tx_buf *in_buf)
{
struct shi_npcx_data *data = dev->data;
uint8_t *out_buf = data->out_msg + EC_SHI_FRAME_START_LENGTH;
/*
* 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.
*/
__disable_irq();
memcpy(out_buf, in_buf->buf, in_buf->len);
if (data->state == SHI_STATE_PROCESSING) {
/* Append our past-end byte, which we reserved space for. */
((uint8_t *)out_buf)[in_buf->len] = EC_SHI_PAST_END;
/* Computing sending bytes of response */
data->sz_response = in_buf->len + EC_SHI_PROTO3_OVERHEAD;
/* Start to fill output buffer with msg buffer */
shi_npcx_write_first_pkg_outbuf(dev, data->sz_response);
/* Transmit the reply */
data->state = SHI_STATE_SENDING;
LOG_DBG("SND-");
} else if (data->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.
*/
shi_npcx_reset_prepare(dev);
LOG_DBG("END\n");
} else {
LOG_ERR("Unexpected state %d in response handler", data->state);
}
__enable_irq();
return 0;
}
static const struct ec_host_cmd_periph_api ec_host_cmd_api = {
.init = shi_npcx_periph_init_context,
.send = shi_npcx_periph_send,
};
#ifdef CONFIG_PM_DEVICE
static int shi_npcx_pm_cb(const struct device *dev, enum pm_device_action action)
{
int ret = 0;
switch (action) {
case PM_DEVICE_ACTION_SUSPEND:
shi_npcx_disable(dev);
break;
case PM_DEVICE_ACTION_RESUME:
shi_npcx_enable(dev);
break;
default:
ret = -ENOTSUP;
break;
}
return ret;
}
#endif
/* Assume only one peripheral */
PM_DEVICE_DT_INST_DEFINE(0, shi_npcx_pm_cb);
PINCTRL_DT_INST_DEFINE(0);
static const struct shi_npcx_config shi_cfg = {
.base = DT_INST_REG_ADDR(0),
.clk_cfg = NPCX_DT_CLK_CFG_ITEM(0),
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
.irq = DT_INST_IRQN(0),
.shi_cs_wui = NPCX_DT_WUI_ITEM_BY_NAME(0, shi_cs_wui),
};
static struct shi_npcx_data shi_data = {
.state = SHI_STATE_DISABLED,
.last_error_state = SHI_STATE_NONE,
.out_msg = shi_data.out_msg_padded + SHI_OUT_START_PAD - EC_SHI_FRAME_START_LENGTH,
};
DEVICE_DT_INST_DEFINE(0, shi_npcx_init, PM_DEVICE_DT_INST_GET(0), &shi_data, &shi_cfg, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &ec_host_cmd_api);

View file

@ -354,6 +354,16 @@
status = "disabled"; status = "disabled";
}; };
shi0: shi@4000f000 {
compatible = "nuvoton,npcx-shi";
reg = <0x4000f000 0x120>;
interrupts = <18 1>;
clocks = <&pcc NPCX_CLOCK_BUS_APB3 NPCX_PWDWN_CTL5 1>;
status = "disabled";
buffer-rx-size = <128>;
buffer-tx-size = <128>;
};
host_sub: lpc@400c1000 { host_sub: lpc@400c1000 {
compatible = "nuvoton,npcx-host-sub"; compatible = "nuvoton,npcx-host-sub";
/* host sub-module register address & size */ /* host sub-module register address & size */

View file

@ -0,0 +1,28 @@
# Copyright (c) 2022 Google LLC
# SPDX-License-Identifier: Apache-2.0
description: Nuvoton, NPCX Serial Host Interface (SHI) node
compatible: "nuvoton,npcx-shi"
include: [pinctrl-device.yaml, shi-device.yaml]
properties:
pinctrl-0:
required: true
pinctrl-names:
required: true
clocks:
required: true
description: configurations of device source clock controller
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>;

View file

@ -0,0 +1,20 @@
# Copyright (c) 2022 Google LLC
# SPDX-License-Identifier: Apache-2.0
include: [base.yaml]
properties:
reg:
required: true
buffer-rx-size:
type: int
required: true
description: |
Size of the internal buffer used for receiving data on the bus.
buffer-tx-size:
type: int
required: true
description: |
Size of the internal buffer used for transmitting data over the bus.

View file

@ -1633,4 +1633,88 @@ struct kbs_reg {
#define KBS_CFG_INDX_RTYTO 2 /* Keyboard Scan Retry Timeout */ #define KBS_CFG_INDX_RTYTO 2 /* Keyboard Scan Retry Timeout */
#define KBS_CFG_INDX_CNUM 3 /* Keyboard Scan Columns Number */ #define KBS_CFG_INDX_CNUM 3 /* Keyboard Scan Columns Number */
#define KBS_CFG_INDX_CDIV 4 /* Keyboard Scan Clock Divisor */ #define KBS_CFG_INDX_CDIV 4 /* Keyboard Scan Clock Divisor */
/* 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_H */ #endif /* _NUVOTON_NPCX_REG_DEF_H */

View file

@ -21,6 +21,7 @@ config EC_HOST_CMD_HANDLER_STACK_SIZE
config EC_HOST_CMD_HANDLER_TX_BUFFER config EC_HOST_CMD_HANDLER_TX_BUFFER
int "Buffer size in bytes for TX buffer shared by all EC host commands" int "Buffer size in bytes for TX buffer shared by all EC host commands"
default EC_HOST_CMD_PERIPH_SHI_MAX_RESPONSE if EC_HOST_CMD_PERIPH_SHI
default 256 default 256
config EC_HOST_CMD_HANDLER_PRIO config EC_HOST_CMD_HANDLER_PRIO