| /* |
| * Copyright (c) 2022 Google LLC |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ite_it8xxx2_shi |
| |
| #include "ec_host_cmd_backend_shi.h" |
| |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/dt-bindings/gpio/ite-it8xxx2-gpio.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/mgmt/ec_host_cmd/backend.h> |
| #include <zephyr/mgmt/ec_host_cmd/ec_host_cmd.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/pm/device_runtime.h> |
| #include <zephyr/pm/policy.h> |
| |
| BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, "Invalid number of ITE SHI peripherals"); |
| |
| LOG_MODULE_REGISTER(host_cmd_shi_ite, CONFIG_EC_HC_LOG_LEVEL); |
| |
| #define EC_SHI_PREAMBLE_LENGTH 4 |
| #define EC_SHI_PAST_END_LENGTH 4 |
| #define SPI_RX_MAX_FIFO_SIZE DT_INST_PROP(0, buffer_rx_size) |
| #define SPI_TX_MAX_FIFO_SIZE DT_INST_PROP(0, buffer_tx_size) |
| |
| #define SHI_MAX_RESPONSE_SIZE \ |
| (SPI_TX_MAX_FIFO_SIZE - EC_SHI_PREAMBLE_LENGTH - EC_SHI_PAST_END_LENGTH) |
| |
| BUILD_ASSERT(CONFIG_EC_HOST_CMD_BACKEND_SHI_MAX_REQUEST <= SPI_RX_MAX_FIFO_SIZE, |
| "SHI max request size is too big"); |
| BUILD_ASSERT(CONFIG_EC_HOST_CMD_BACKEND_SHI_MAX_RESPONSE <= SHI_MAX_RESPONSE_SIZE, |
| "SHI max response size is too big"); |
| |
| /* Parameters used by host protocols */ |
| enum shi_state_machine { |
| /* Interface is disabled */ |
| SHI_STATE_DISABLED, |
| /* Ready to receive next request */ |
| SHI_STATE_READY_TO_RECV, |
| /* Receiving request */ |
| SHI_STATE_RECEIVING, |
| /* Processing request */ |
| SHI_STATE_PROCESSING, |
| /* Received bad data */ |
| SHI_STATE_RX_BAD, |
| |
| SHI_STATE_COUNT, |
| }; |
| |
| /* |
| * Structure shi_it8xxx2_cfg is about the setting of SHI, |
| * this config will be used at initial time |
| */ |
| struct shi_it8xxx2_cfg { |
| /* SHI alternate configuration */ |
| const struct pinctrl_dev_config *pcfg; |
| /* Chip select pin */ |
| const struct gpio_dt_spec cs; |
| }; |
| |
| struct shi_it8xxx2_data { |
| /* Peripheral data */ |
| struct ec_host_cmd_rx_ctx *rx_ctx; |
| struct ec_host_cmd_tx_buf *tx; |
| struct gpio_callback cs_cb; |
| /* Current state */ |
| enum shi_state_machine shi_state; |
| /* Buffers */ |
| uint8_t in_msg[SPI_RX_MAX_FIFO_SIZE] __aligned(4); |
| uint8_t out_msg[SPI_TX_MAX_FIFO_SIZE] __aligned(4); |
| }; |
| |
| struct ec_host_cmd_shi_ite_ctx { |
| /* SHI device instance */ |
| const struct device *dev; |
| }; |
| |
| static const uint8_t out_preamble[EC_SHI_PREAMBLE_LENGTH] = { |
| EC_SHI_PROCESSING, |
| EC_SHI_PROCESSING, |
| EC_SHI_PROCESSING, |
| /* This is the byte which matters */ |
| EC_SHI_FRAME_START, |
| }; |
| |
| static const int shi_ite_response_state[] = { |
| [SHI_STATE_DISABLED] = EC_SHI_NOT_READY, [SHI_STATE_READY_TO_RECV] = EC_SHI_RX_READY, |
| [SHI_STATE_RECEIVING] = EC_SHI_RECEIVING, [SHI_STATE_PROCESSING] = EC_SHI_PROCESSING, |
| [SHI_STATE_RX_BAD] = EC_SHI_RX_BAD_DATA, |
| }; |
| BUILD_ASSERT(ARRAY_SIZE(shi_ite_response_state) == SHI_STATE_COUNT); |
| |
| #define EC_HOST_CMD_SHI_ITE_DEFINE(_name) \ |
| static struct ec_host_cmd_shi_ite_ctx _name##_hc_shi_ite; \ |
| struct ec_host_cmd_backend _name = { \ |
| .api = &ec_host_cmd_api, \ |
| .ctx = (struct ec_host_cmd_shi_ite_ctx *)&_name##_hc_shi_ite, \ |
| } |
| |
| static void shi_ite_set_state(struct shi_it8xxx2_data *data, int state) |
| { |
| /* SPI peripheral state machine */ |
| data->shi_state = state; |
| |
| /* Response spi peripheral state */ |
| IT83XX_SPI_SPISRDR = shi_ite_response_state[state]; |
| } |
| |
| static void shi_ite_reset_rx_fifo(void) |
| { |
| /* End Rx FIFO access */ |
| IT83XX_SPI_TXRXFAR = 0x00; |
| |
| /* Rx FIFO reset and count monitor reset */ |
| IT83XX_SPI_FCR = IT83XX_SPI_RXFR | IT83XX_SPI_RXFCMR; |
| } |
| |
| /* This routine handles spi received unexcepted data */ |
| static void shi_ite_bad_received_data(const struct device *dev, int count) |
| { |
| struct shi_it8xxx2_data *data = dev->data; |
| |
| /* State machine mismatch, timeout, or protocol we can't handle. */ |
| shi_ite_set_state(data, SHI_STATE_RX_BAD); |
| |
| /* End CPU access Rx FIFO, so it can clock in bytes from AP again. */ |
| IT83XX_SPI_TXRXFAR = 0; |
| |
| LOG_ERR("SPI rx bad data"); |
| LOG_HEXDUMP_DBG(data->in_msg, count, "in_msg="); |
| } |
| |
| static void shi_ite_response_host_data(const struct device *dev, uint8_t *out_msg_addr, int tx_size) |
| { |
| struct shi_it8xxx2_data *data = dev->data; |
| |
| /* |
| * Protect sequence of filling response packet for host. |
| * This will ensure CPU access FIFO is disabled at SPI end interrupt no |
| * matter the interrupt is triggered before or after the sequence. |
| */ |
| unsigned int key = irq_lock(); |
| |
| if (data->shi_state == SHI_STATE_PROCESSING) { |
| /* Tx FIFO reset and count monitor reset */ |
| IT83XX_SPI_TXFCR = IT83XX_SPI_TXFR | IT83XX_SPI_TXFCMR; |
| |
| /* CPU Tx FIFO1 and FIFO2 access */ |
| IT83XX_SPI_TXRXFAR = IT83XX_SPI_CPUTFA; |
| |
| for (int i = 0; i < tx_size; i += 4) { |
| /* Write response data from out_msg buffer to Tx FIFO */ |
| IT83XX_SPI_CPUWTFDB0 = *(uint32_t *)(out_msg_addr + i); |
| } |
| |
| /* |
| * After writing data to Tx FIFO is finished, this bit will |
| * be to indicate the SPI peripheral controller. |
| */ |
| IT83XX_SPI_TXFCR = IT83XX_SPI_TXFS; |
| |
| /* End Tx FIFO access */ |
| IT83XX_SPI_TXRXFAR = 0; |
| |
| /* SPI peripheral read Tx FIFO */ |
| IT83XX_SPI_FCR = IT83XX_SPI_SPISRTXF; |
| } |
| |
| irq_unlock(key); |
| } |
| |
| /* |
| * 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 int shi_ite_backend_send(const struct ec_host_cmd_backend *backend) |
| { |
| struct ec_host_cmd_shi_ite_ctx *hc_shi = (struct ec_host_cmd_shi_ite_ctx *)backend->ctx; |
| struct shi_it8xxx2_data *data = hc_shi->dev->data; |
| int tx_size; |
| |
| if (data->shi_state != SHI_STATE_PROCESSING) { |
| LOG_ERR("The request data is not processing (state=%d)", data->shi_state); |
| return -EBUSY; |
| } |
| |
| /* Copy preamble */ |
| memcpy(data->out_msg, out_preamble, sizeof(out_preamble)); |
| |
| /* Data to sent are already at "out_msg + sizeof(out_preamble)" memory address(tx buf |
| * assigned in the init function), prepared by the handler. |
| * Append our past-end byte, which we reserved space for. |
| */ |
| memset(&data->out_msg[sizeof(out_preamble) + data->tx->len], EC_SHI_PAST_END, |
| EC_SHI_PAST_END_LENGTH); |
| |
| tx_size = data->tx->len + EC_SHI_PREAMBLE_LENGTH + EC_SHI_PAST_END_LENGTH; |
| |
| /* Transmit the reply */ |
| shi_ite_response_host_data(hc_shi->dev, data->out_msg, tx_size); |
| |
| return 0; |
| } |
| |
| /* Store request data from Rx FIFO to in_msg buffer */ |
| static void shi_ite_host_request_data(uint8_t *in_msg_addr, int count) |
| { |
| /* CPU Rx FIFO1 access */ |
| IT83XX_SPI_TXRXFAR = IT83XX_SPI_CPURXF1A; |
| |
| /* |
| * In shi_ite_parse_header, the request data will separate to write in_msg buffer so we |
| * cannot set CPU to end accessing Rx FIFO in this function. We will set |
| * IT83XX_SPI_TXRXFAR = 0 in shi_ite_reset_rx_fifo. |
| */ |
| for (int i = 0; i < count; i += 4) { |
| /* Get data from master to buffer */ |
| *(uint32_t *)(in_msg_addr + i) = IT83XX_SPI_RXFRDRB0; |
| } |
| } |
| |
| static int shi_ite_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; |
| } |
| |
| /* Parse header for version of spi-protocol */ |
| static void shi_ite_parse_header(const struct device *dev) |
| { |
| struct shi_it8xxx2_data *data = dev->data; |
| struct ec_host_cmd_request_header *r = (struct ec_host_cmd_request_header *)data->in_msg; |
| |
| /* Store request data from Rx FIFO to in_msg buffer (rx_ctx->buf) */ |
| shi_ite_host_request_data(data->in_msg, sizeof(*r)); |
| |
| /* Protocol version 3 */ |
| if (r->prtcl_ver == EC_HOST_REQUEST_VERSION) { |
| /* Check how big the packet should be */ |
| data->rx_ctx->len = shi_ite_host_request_expected_size(r); |
| |
| if (data->rx_ctx->len == 0 || data->rx_ctx->len > sizeof(data->in_msg)) { |
| shi_ite_bad_received_data(dev, data->rx_ctx->len); |
| return; |
| } |
| |
| /* Store request data from Rx FIFO to in_msg buffer */ |
| shi_ite_host_request_data(data->rx_ctx->buf + sizeof(*r), |
| data->rx_ctx->len - sizeof(*r)); |
| |
| k_sem_give(&data->rx_ctx->handler_owns); |
| } else { |
| /* Invalid version number */ |
| LOG_ERR("Invalid version number"); |
| shi_ite_bad_received_data(dev, 1); |
| } |
| } |
| |
| static void shi_ite_int_handler(const struct device *dev) |
| { |
| struct shi_it8xxx2_data *data = dev->data; |
| |
| if (data->shi_state == SHI_STATE_DISABLED) { |
| return; |
| } |
| |
| /* |
| * The status of SPI end detection interrupt bit is set, it means that host command parse |
| * has been completed and AP has received the last byte which is EC_SHI_PAST_END from |
| * EC responded data, then AP ended the transaction. |
| */ |
| if (IT83XX_SPI_ISR & IT83XX_SPI_ENDDETECTINT) { |
| /* Disable CPU access Rx FIFO to clock in data from AP again */ |
| IT83XX_SPI_TXRXFAR = 0; |
| /* Ready to receive */ |
| shi_ite_set_state(data, SHI_STATE_READY_TO_RECV); |
| /* CS# is deasserted, so write clear all slave status */ |
| IT83XX_SPI_ISR = 0xff; |
| /* Allow the MCU to go into lower power mode */ |
| pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| } |
| |
| /* |
| * The status of Rx valid length interrupt bit is set that indicates reached target count |
| * (IT83XX_SPI_FTCB1R, IT83XX_SPI_FTCB0R) and the length field of the host requested data. |
| */ |
| if (IT83XX_SPI_RX_VLISR & IT83XX_SPI_RVLI) { |
| /* write clear slave status */ |
| IT83XX_SPI_RX_VLISR = IT83XX_SPI_RVLI; |
| /* Move to processing state */ |
| shi_ite_set_state(data, SHI_STATE_PROCESSING); |
| /* Parse header for version of spi-protocol */ |
| shi_ite_parse_header(dev); |
| } |
| } |
| |
| void shi_ite_cs_callback(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins) |
| { |
| struct shi_it8xxx2_data *data = CONTAINER_OF(cb, struct shi_it8xxx2_data, cs_cb); |
| |
| if (data->shi_state == SHI_STATE_DISABLED) { |
| return; |
| } |
| |
| /* Prevent the MCU from sleeping during the transmission */ |
| pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| |
| /* Move to processing state */ |
| shi_ite_set_state(data, SHI_STATE_PROCESSING); |
| } |
| |
| static int shi_ite_init_registers(const struct device *dev) |
| { |
| const struct shi_it8xxx2_cfg *const cfg = dev->config; |
| /* Set FIFO data target count */ |
| struct ec_host_cmd_request_header cmd_head; |
| int status; |
| |
| /* |
| * Target count means the size of host request. |
| * And plus extra 4 bytes because the CPU accesses FIFO base on word. If host requested |
| * data length is one byte, we need to align the data length to 4 bytes. |
| */ |
| int target_count = sizeof(cmd_head) + 4; |
| /* Offset of data_len member of host request. */ |
| int offset = (char *)&cmd_head.data_len - (char *)&cmd_head; |
| |
| IT83XX_SPI_FTCB1R = (target_count >> 8) & 0xff; |
| IT83XX_SPI_FTCB0R = target_count & 0xff; |
| |
| /* |
| * The register setting can capture the length field of host |
| * request. |
| */ |
| IT83XX_SPI_TCCB1 = (offset >> 8) & 0xff; |
| IT83XX_SPI_TCCB0 = offset & 0xff; |
| |
| /* |
| * Memory controller configuration register 3. |
| * bit6 : SPI pin function select (0b:Enable, 1b:Mask) |
| */ |
| IT83XX_GCTRL_MCCR3 |= IT83XX_GCTRL_SPISLVPFE; |
| |
| /* Set unused blocked byte */ |
| IT83XX_SPI_HPR2 = 0x00; |
| |
| /* Rx valid length interrupt enabled */ |
| IT83XX_SPI_RX_VLISMR &= ~IT83XX_SPI_RVLIM; |
| |
| /* |
| * General control register2 |
| * bit4 : Rx FIFO2 will not be overwrited once it's full. |
| * bit3 : Rx FIFO1 will not be overwrited once it's full. |
| * bit0 : Rx FIFO1/FIFO2 will reset after each CS_N goes high. |
| */ |
| IT83XX_SPI_GCR2 = IT83XX_SPI_RXF2OC | IT83XX_SPI_RXF1OC | IT83XX_SPI_RXFAR; |
| |
| /* |
| * Interrupt mask register (0b:Enable, 1b:Mask) |
| * bit5 : Rx byte reach interrupt mask |
| * bit2 : SPI end detection interrupt mask |
| */ |
| IT83XX_SPI_IMR &= ~IT83XX_SPI_EDIM; |
| |
| /* Reset fifo and prepare to for next transaction */ |
| shi_ite_reset_rx_fifo(); |
| |
| /* Ready to receive */ |
| shi_ite_set_state(dev->data, SHI_STATE_READY_TO_RECV); |
| |
| /* Interrupt status register(write one to clear) */ |
| IT83XX_SPI_ISR = 0xff; |
| |
| /* SPI peripheral controller enable (after settings are ready) */ |
| IT83XX_SPI_SPISGCR = IT83XX_SPI_SPISCEN; |
| |
| /* Set the pin to SHI alternate function. */ |
| status = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (status < 0) { |
| LOG_ERR("Failed to configure SHI pins"); |
| return status; |
| } |
| |
| /* Enable SPI peripheral interrupt */ |
| IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), shi_ite_int_handler, |
| DEVICE_DT_INST_GET(0), 0); |
| irq_enable(DT_INST_IRQN(0)); |
| |
| return 0; |
| } |
| |
| static int shi_ite_init(const struct device *dev) |
| { |
| const struct shi_it8xxx2_cfg *cfg = dev->config; |
| struct shi_it8xxx2_data *data = dev->data; |
| int ret; |
| |
| ret = shi_ite_init_registers(dev); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Configure the SPI chip select */ |
| ret = gpio_pin_configure(cfg->cs.port, cfg->cs.pin, GPIO_INPUT | cfg->cs.dt_flags); |
| if (ret < 0) { |
| LOG_ERR("Failed to configure SHI CS pin"); |
| return ret; |
| } |
| |
| /* Enable SPI chip select pin interrupt */ |
| gpio_init_callback(&data->cs_cb, shi_ite_cs_callback, BIT(cfg->cs.pin)); |
| ret = gpio_add_callback(cfg->cs.port, &data->cs_cb); |
| if (ret < 0) { |
| return -EINVAL; |
| } |
| |
| ret = gpio_pin_interrupt_configure(cfg->cs.port, cfg->cs.pin, GPIO_INT_EDGE_FALLING); |
| if (ret < 0) { |
| LOG_ERR("Failed to configure SHI CS interrupt"); |
| return -EINVAL; |
| } |
| |
| pm_device_init_suspended(dev); |
| return pm_device_runtime_enable(dev); |
| } |
| |
| static int shi_ite_backend_init(const struct ec_host_cmd_backend *backend, |
| struct ec_host_cmd_rx_ctx *rx_ctx, struct ec_host_cmd_tx_buf *tx) |
| { |
| struct ec_host_cmd_shi_ite_ctx *hc_shi = (struct ec_host_cmd_shi_ite_ctx *)backend->ctx; |
| struct shi_it8xxx2_data *data; |
| |
| hc_shi->dev = DEVICE_DT_INST_GET(0); |
| if (!device_is_ready(hc_shi->dev)) { |
| return -ENODEV; |
| } |
| |
| data = hc_shi->dev->data; |
| data->rx_ctx = rx_ctx; |
| data->tx = tx; |
| |
| rx_ctx->buf = data->in_msg; |
| tx->buf = data->out_msg + sizeof(out_preamble); |
| data->tx->len_max = sizeof(data->out_msg) - EC_SHI_PREAMBLE_LENGTH - EC_SHI_PAST_END_LENGTH; |
| |
| return 0; |
| } |
| |
| PINCTRL_DT_INST_DEFINE(0); |
| |
| #ifdef CONFIG_PM_DEVICE |
| static int shi_ite_pm_cb(const struct device *dev, enum pm_device_action action) |
| { |
| struct shi_it8xxx2_data *data = dev->data; |
| int ret = 0; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_SUSPEND: |
| shi_ite_set_state(data, SHI_STATE_DISABLED); |
| break; |
| case PM_DEVICE_ACTION_RESUME: |
| shi_ite_set_state(data, SHI_STATE_READY_TO_RECV); |
| break; |
| default: |
| ret = -ENOTSUP; |
| break; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| /* Assume only one peripheral */ |
| PM_DEVICE_DT_INST_DEFINE(0, shi_ite_pm_cb); |
| |
| static const struct ec_host_cmd_backend_api ec_host_cmd_api = { |
| .init = shi_ite_backend_init, |
| .send = shi_ite_backend_send, |
| }; |
| |
| static const struct shi_it8xxx2_cfg shi_cfg = { |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), |
| .cs = GPIO_DT_SPEC_GET_BY_IDX(DT_DRV_INST(0), cs_gpios, 0), |
| }; |
| |
| static struct shi_it8xxx2_data shi_data = { |
| .shi_state = SHI_STATE_DISABLED, |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, shi_ite_init, PM_DEVICE_DT_INST_GET(0), &shi_data, &shi_cfg, POST_KERNEL, |
| CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &ec_host_cmd_api); |
| |
| EC_HOST_CMD_SHI_ITE_DEFINE(ec_host_cmd_shi_ite); |
| |
| struct ec_host_cmd_backend *ec_host_cmd_backend_get_shi_ite(void) |
| { |
| return &ec_host_cmd_shi_ite; |
| } |
| |
| #if DT_NODE_EXISTS(DT_CHOSEN(zephyr_host_cmd_shi_backend)) && \ |
| defined(CONFIG_EC_HOST_CMD_INITIALIZE_AT_BOOT) |
| static int host_cmd_init(void) |
| { |
| ec_host_cmd_init(ec_host_cmd_backend_get_shi_ite()); |
| return 0; |
| } |
| SYS_INIT(host_cmd_init, POST_KERNEL, CONFIG_EC_HOST_CMD_INIT_PRIORITY); |
| #endif |