| /* Copyright 2019 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| * |
| * SPI driver for Chrome EC. |
| * |
| * This uses FIFO mode to handle transmission and reception. |
| */ |
| |
| #include "chipset.h" |
| #include "console.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "host_command.h" |
| #include "intc.h" |
| #include "registers.h" |
| #include "spi.h" |
| #include "system.h" |
| #include "task.h" |
| #include "util.h" |
| |
| /* Console output macros */ |
| #define CPRINTS(format, args...) cprints(CC_SPI, format, ##args) |
| #define CPRINTF(format, args...) cprintf(CC_SPI, format, ##args) |
| |
| #define SPI_RX_MAX_FIFO_SIZE 256 |
| #define SPI_TX_MAX_FIFO_SIZE 256 |
| |
| #define EC_SPI_PREAMBLE_LENGTH 4 |
| #define EC_SPI_PAST_END_LENGTH 4 |
| |
| /* Max data size for a version 3 request/response packet. */ |
| #define SPI_MAX_REQUEST_SIZE SPI_RX_MAX_FIFO_SIZE |
| #define SPI_MAX_RESPONSE_SIZE \ |
| (SPI_TX_MAX_FIFO_SIZE - EC_SPI_PREAMBLE_LENGTH - EC_SPI_PAST_END_LENGTH) |
| |
| static const uint8_t out_preamble[EC_SPI_PREAMBLE_LENGTH] = { |
| EC_SPI_PROCESSING, |
| EC_SPI_PROCESSING, |
| EC_SPI_PROCESSING, |
| /* This is the byte which matters */ |
| EC_SPI_FRAME_START, |
| }; |
| |
| /* Store read and write data buffer */ |
| static uint8_t in_msg[SPI_RX_MAX_FIFO_SIZE] __aligned(4); |
| static uint8_t out_msg[SPI_TX_MAX_FIFO_SIZE] __aligned(4); |
| |
| /* Parameters used by host protocols */ |
| static struct host_packet spi_packet; |
| |
| enum spi_peripheral_state_machine { |
| /* Ready to receive next request */ |
| SPI_STATE_READY_TO_RECV, |
| /* Receiving request */ |
| SPI_STATE_RECEIVING, |
| /* Processing request */ |
| SPI_STATE_PROCESSING, |
| /* Received bad data */ |
| SPI_STATE_RX_BAD, |
| |
| SPI_STATE_COUNT, |
| } spi_peripheral_state; |
| |
| static const int spi_response_state[] = { |
| [SPI_STATE_READY_TO_RECV] = EC_SPI_RX_READY, |
| [SPI_STATE_RECEIVING] = EC_SPI_RECEIVING, |
| [SPI_STATE_PROCESSING] = EC_SPI_PROCESSING, |
| [SPI_STATE_RX_BAD] = EC_SPI_RX_BAD_DATA, |
| }; |
| BUILD_ASSERT(ARRAY_SIZE(spi_response_state) == SPI_STATE_COUNT); |
| |
| static void spi_set_state(int state) |
| { |
| /* SPI peripheral state machine */ |
| spi_peripheral_state = state; |
| /* Response spi peripheral state */ |
| IT83XX_SPI_SPISRDR = spi_response_state[state]; |
| } |
| |
| static void 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 spi_bad_received_data(int count) |
| { |
| int i; |
| |
| /* State machine mismatch, timeout, or protocol we can't handle. */ |
| spi_set_state(SPI_STATE_RX_BAD); |
| /* End CPU access Rx FIFO, so it can clock in bytes from AP again. */ |
| IT83XX_SPI_TXRXFAR = 0; |
| |
| CPRINTS("SPI rx bad data"); |
| CPRINTF("in_msg=["); |
| for (i = 0; i < count; i++) |
| CPRINTF("%02x ", in_msg[i]); |
| CPRINTF("]\n"); |
| } |
| |
| static void spi_response_host_data(uint8_t *out_msg_addr, int tx_size) |
| { |
| int i; |
| |
| /* 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 (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. |
| */ |
| 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; |
| } |
| |
| /* |
| * Called to send a response back to the host. |
| * |
| * Some commands can continue for a while. This function is called by |
| * host_command when it completes. |
| * |
| */ |
| static void spi_send_response_packet(struct host_packet *pkt) |
| { |
| int i, tx_size; |
| |
| if (spi_peripheral_state != SPI_STATE_PROCESSING) { |
| CPRINTS("The request data is not processing."); |
| return; |
| } |
| |
| /* Append our past-end byte, which we reserved space for. */ |
| for (i = 0; i < EC_SPI_PAST_END_LENGTH; i++) |
| ((uint8_t *)pkt->response)[pkt->response_size + i] = |
| EC_SPI_PAST_END; |
| |
| tx_size = pkt->response_size + EC_SPI_PREAMBLE_LENGTH + |
| EC_SPI_PAST_END_LENGTH; |
| |
| /* Transmit the reply */ |
| spi_response_host_data(out_msg, tx_size); |
| } |
| |
| /* Store request data from Rx FIFO to in_msg buffer */ |
| static void spi_host_request_data(uint8_t *in_msg_addr, int count) |
| { |
| int i; |
| |
| /* CPU Rx FIFO1 access */ |
| IT83XX_SPI_TXRXFAR = IT83XX_SPI_CPURXF1A; |
| /* |
| * In spi_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 reset_rx_fifo. |
| */ |
| |
| for (i = 0; i < count; i += 4) |
| /* Get data from controller to buffer */ |
| *(uint32_t *)(in_msg_addr + i) = IT83XX_SPI_RXFRDRB0; |
| } |
| |
| /* Parse header for version of spi-protocol */ |
| static void spi_parse_header(void) |
| { |
| struct ec_host_request *r = (struct ec_host_request *)in_msg; |
| |
| /* Store request data from Rx FIFO to in_msg buffer */ |
| spi_host_request_data(in_msg, sizeof(*r)); |
| |
| /* Protocol version 3 */ |
| if (in_msg[0] == EC_HOST_REQUEST_VERSION) { |
| int pkt_size; |
| |
| /* Check how big the packet should be */ |
| pkt_size = host_request_expected_size(r); |
| |
| if (pkt_size == 0 || pkt_size > sizeof(in_msg)) |
| return spi_bad_received_data(pkt_size); |
| |
| /* Store request data from Rx FIFO to in_msg buffer */ |
| spi_host_request_data(in_msg + sizeof(*r), |
| pkt_size - sizeof(*r)); |
| |
| /* Set up parameters for host request */ |
| spi_packet.send_response = spi_send_response_packet; |
| spi_packet.request = in_msg; |
| spi_packet.request_temp = NULL; |
| spi_packet.request_max = sizeof(in_msg); |
| spi_packet.request_size = pkt_size; |
| |
| /* Response must start with the preamble */ |
| memcpy(out_msg, out_preamble, EC_SPI_PREAMBLE_LENGTH); |
| |
| spi_packet.response = out_msg + EC_SPI_PREAMBLE_LENGTH; |
| /* Reserve space for frame start and trailing past-end byte */ |
| spi_packet.response_max = SPI_MAX_RESPONSE_SIZE; |
| spi_packet.response_size = 0; |
| spi_packet.driver_result = EC_RES_SUCCESS; |
| |
| /* Move to processing state */ |
| spi_set_state(SPI_STATE_PROCESSING); |
| |
| /* Go to common-layer to handle request */ |
| host_packet_receive(&spi_packet); |
| } else { |
| /* Invalid version number */ |
| CPRINTS("Invalid version number"); |
| return spi_bad_received_data(1); |
| } |
| } |
| |
| void spi_event(enum gpio_signal signal) |
| { |
| if (chipset_in_state(CHIPSET_STATE_ON)) { |
| /* EC has started receiving the request from the AP */ |
| spi_set_state(SPI_STATE_RECEIVING); |
| /* Disable idle task deep sleep bit of SPI in S0. */ |
| disable_sleep(SLEEP_MASK_SPI); |
| } |
| } |
| |
| void spi_peripheral_int_handler(void) |
| { |
| if (IS_ENABLED(CONFIG_BOOTBLOCK) && |
| (IT83XX_SPI_ISR & IT83XX_SPI_RX_FIFO_FULL) && |
| (IT83XX_SPI_EMMCBMR & IT83XX_SPI_EMMCABM)) { |
| spi_host_request_data(in_msg, 128); |
| /* End CPU access RX FIFO */ |
| IT83XX_SPI_TXRXFAR = 0; |
| /* Write to clear interrupt status */ |
| IT83XX_SPI_ISR = 0xff; |
| /* |
| * Handle eMMC CMD0: |
| * GO_IDLE_STATE, GO_PRE_IDLE_STATE, and BOOT_INITIATION |
| */ |
| spi_emmc_cmd0_isr((uint32_t *)in_msg); |
| 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_SPI_PAST_END from |
| * EC responded data, then AP ended the transaction. |
| */ |
| if (IT83XX_SPI_ISR & IT83XX_SPI_ENDDETECTINT) { |
| /* Ready to receive */ |
| spi_set_state(SPI_STATE_READY_TO_RECV); |
| /* |
| * Once there is no SPI active, enable idle task deep |
| * sleep bit of SPI in S3 or lower. |
| */ |
| enable_sleep(SLEEP_MASK_SPI); |
| /* CS# is deasserted, so write clear all peripheral status */ |
| IT83XX_SPI_ISR = 0xff; |
| } |
| /* |
| * 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 peripheral status */ |
| IT83XX_SPI_RX_VLISR = IT83XX_SPI_RVLI; |
| /* Parse header for version of spi-protocol */ |
| spi_parse_header(); |
| } |
| |
| /* Clear the interrupt status */ |
| task_clear_pending_irq(IT83XX_IRQ_SPI_PERIPHERAL); |
| } |
| |
| static void spi_init(void) |
| { |
| /* Set FIFO data target count */ |
| struct ec_host_request cmd_head; |
| |
| /* |
| * 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; |
| |
| /* Set SPI pins to alternate function */ |
| gpio_config_module(MODULE_SPI, 1); |
| /* |
| * 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 */ |
| reset_rx_fifo(); |
| /* Ready to receive */ |
| spi_set_state(SPI_STATE_READY_TO_RECV); |
| /* Interrupt status register(write one to clear) */ |
| IT83XX_SPI_ISR = 0xff; |
| /* SPI peripheral enable (after settings are ready) */ |
| IT83XX_SPI_SPISGCR = IT83XX_SPI_SPISCEN; |
| /* Enable SPI peripheral interrupt */ |
| task_clear_pending_irq(IT83XX_IRQ_SPI_PERIPHERAL); |
| task_enable_irq(IT83XX_IRQ_SPI_PERIPHERAL); |
| /* Enable SPI chip select pin interrupt */ |
| gpio_clear_pending_interrupt(GPIO_SPI0_CS); |
| gpio_enable_interrupt(GPIO_SPI0_CS); |
| } |
| DECLARE_HOOK(HOOK_INIT, spi_init, HOOK_PRIO_INIT_SPI); |
| |
| /* reset peripheral SPI module */ |
| static void spi_reset(void) |
| { |
| /* |
| * Reset SPI module before sysjump. New FW images (RO/RW) will |
| * re-configure it. |
| */ |
| IT83XX_GCTRL_RSTC5 |= BIT(1); |
| } |
| DECLARE_HOOK(HOOK_SYSJUMP, spi_reset, HOOK_PRIO_DEFAULT); |
| |
| #if defined(SECTION_IS_RO) && defined(CONFIG_BOOTBLOCK) |
| /* AP has booted */ |
| void emmc_ap_jump_to_bl(enum gpio_signal signal) |
| { |
| /* Transmission completed. Set SPI pin mux to AP communication mode */ |
| IT83XX_GCTRL_PIN_MUX0 &= ~BIT(7); |
| /* Reset and re-initialize SPI module to communication mode */ |
| spi_reset(); |
| spi_init(); |
| /* Disable interrupt of detection of AP's BOOTBLOCK_EN_L */ |
| gpio_disable_interrupt(GPIO_BOOTBLOCK_EN_L); |
| enable_sleep(SLEEP_MASK_EMMC); |
| |
| CPRINTS("eMMC emulation disabled. AP Jumped to BL"); |
| } |
| #endif |
| |
| /* Get protocol information */ |
| static enum ec_status _spi_get_protocol_info(struct host_cmd_handler_args *args) |
| { |
| struct ec_response_get_protocol_info *r = args->response; |
| |
| memset(r, 0, sizeof(*r)); |
| r->protocol_versions = BIT(3); |
| r->max_request_packet_size = SPI_MAX_REQUEST_SIZE; |
| r->max_response_packet_size = SPI_MAX_RESPONSE_SIZE; |
| r->flags = EC_PROTOCOL_INFO_IN_PROGRESS_SUPPORTED; |
| |
| args->response_size = sizeof(*r); |
| |
| return EC_RES_SUCCESS; |
| } |
| DECLARE_HOST_COMMAND(EC_CMD_GET_PROTOCOL_INFO, _spi_get_protocol_info, |
| EC_VER_MASK(0)); |
| |
| enum ec_status spi_get_protocol_info(struct host_cmd_handler_args *args) |
| { |
| return _spi_get_protocol_info(args); |
| } |