diff --git a/drivers/spi/Kconfig.it51xxx b/drivers/spi/Kconfig.it51xxx index b73603decad4..352a3ce3b969 100644 --- a/drivers/spi/Kconfig.it51xxx +++ b/drivers/spi/Kconfig.it51xxx @@ -8,3 +8,31 @@ config SPI_ITE_IT51XXX select PINCTRL help Enable support for the ITE IT51XXX SPI host (SSPI) driver. + +if SPI_ITE_IT51XXX + +config SPI_ITE_IT51XXX_FIFO_MODE + bool "ITE IT51XXX Shared/Group FIFO Mode Support" + select SOC_IT51XXX_CPU_IDLE_GATING + default y + help + Enable ITE IT51XXX shared and group FIFO mode. Due to hardware + limitations, FIFO mode is only supported under the following + conditions: (1) SPI mode 0 (CPOL = 0, CPHA = 0) (2) chip select + 0 is used (3) the number of bytes in both TX and RX transactions + is even and less than SPI_ITE_IT51XXX_FIFO_SIZE (the FIFO size + setting) (4) the clock source is set to the PLL frequency for + group fifo mode. If the transaction doesn't meet these + requirements, the driver automatically switches to PIO mode for + the transfer. + +config SPI_ITE_IT51XXX_FIFO_SIZE + int "ITE IT51XXX Shared/Group FIFO Size" + depends on SPI_ITE_IT51XXX_FIFO_MODE + range 2 2046 + default 128 + help + Set IT51XXX FIFO size. The maximum settable value, as per the + hardware design, is 2046. + +endif # SPI_ITE_IT51XXX diff --git a/drivers/spi/spi_it51xxx.c b/drivers/spi/spi_it51xxx.c index b0ad981704a9..4cf4f2438f7a 100644 --- a/drivers/spi/spi_it51xxx.c +++ b/drivers/spi/spi_it51xxx.c @@ -42,12 +42,67 @@ LOG_MODULE_REGISTER(spi_it51xxx, CONFIG_SPI_LOG_LEVEL); #define TRANSFER_END BIT(1) #define SPI_BUS_BUSY BIT(0) -#define SPI04_CTRL3 0x04 -#define BYTE_DONE_INT_STS BIT(4) +#define SPI04_CTRL3 0x04 +#define SPI_INT_LEVEL_MODE BIT(6) +#define BYTE_DONE_INT_STS BIT(4) #define SPI05_CHAIN_CTRL 0x05 #define PLL_CLOCK_SOURCE_SELECTION BIT(6) +#define SPI06_PAGE_SIZE 0x06 +#define SPI09_FIFO_BASE_ADDR_1 0x09 +#define BIG_ENDIAN_EN BIT(7) +#define FIFO_BASE_ADDR_LB(x) FIELD_GET(GENMASK(14, 8), x) + +#define SPI0B_FIFO_CTRL 0x0B +#define GSCLK_INT_STS BIT(3) +#define FIFO_TX_RX_TERMINATE BIT(1) +#define FIFO_TX_RX_START BIT(0) + +#define SPI0E_CTRL_4 0x0E +#define FIFO_FULL_INT_EN BIT(5) +#define FIFO_FULL_INT_STS BIT(4) + +#define SPI0F_FIFO_BASE_ADDR_2 0x0F +#define FIFO_BASE_ADDR_HB(x) FIELD_GET(GENMASK(17, 15), x) + +#define SPI24_GSCLK_MID_POINT_HOOK_METHOD 0x24 +#define GSCLK_END_POINT_INT_EN BIT(7) +#define GSCLK_END_POINT_HOOK_METHOD_EN FIELD_PREP(GENMASK(6, 4), 4) + +#define SPI82_GROUP_PAGE_SIZE_1 0x82 +#define PAGE_SIZE_LB(x) FIELD_GET(GENMASK(7, 0), x) + +#define SPI83_GROUP_PAGE_SIZE_2 0x83 +#define PAGE_CONTEXT_SIZE_LSB_EN BIT(7) +#define PAGE_SIZE_HB(x) FIELD_GET(GENMASK(10, 8), x) + +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE +/* Based on the hardware design, the shared FIFO mode can be used + * for byte counts divisible by 8, and group FIFO mode for those + * divisible by 2. + */ +#define IS_SHARED_FIFO_MODE(x) ((x >= 8) && (x <= CONFIG_SPI_ITE_IT51XXX_FIFO_SIZE) && (x % 8 == 0)) +#define IS_GROUP_FIFO_MODE(x) ((x >= 2) && (x <= CONFIG_SPI_ITE_IT51XXX_FIFO_SIZE) && (x % 2 == 0)) + +enum spi_it51xxx_xfer_mode { + XFER_TX_ONLY = 0, + XFER_RX_ONLY, + XFER_TX_RX, +}; + +enum spi_it51xxx_ctrl_mode { + PIO_MODE = 0, + SHARED_FIFO_MODE, + GROUP_FIFO_MODE, +}; + +#define GET_MODE_STRING(x) \ + ((x == PIO_MODE) ? "pio mode" \ + : (x == SHARED_FIFO_MODE) ? "share fifo mode" \ + : "group fifo mode") +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + struct spi_it51xxx_config { mm_reg_t base; const struct pinctrl_dev_config *pcfg; @@ -63,6 +118,22 @@ struct spi_it51xxx_config { struct spi_it51xxx_data { struct spi_context ctx; + +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + uint8_t xfer_mode; + + struct { + uint8_t tx; + uint8_t rx; + } ctrl_mode; + + __aligned(256) uint8_t fifo_data[CONFIG_SPI_ITE_IT51XXX_FIFO_SIZE]; + + size_t transfer_len; + size_t receive_len; + + bool direction_turnaround; +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ }; static inline int spi_it51xxx_set_freq(const struct device *dev, const uint32_t frequency) @@ -116,6 +187,77 @@ static inline int spi_it51xxx_set_freq(const struct device *dev, const uint32_t return 0; } +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE +static void spi_it51xxx_ctrl_mode_selection(const struct device *dev) +{ + const struct spi_it51xxx_config *cfg = dev->config; + struct spi_it51xxx_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + size_t total_tx_len = spi_context_total_tx_len(ctx); + size_t total_rx_len = spi_context_total_rx_len(ctx); + + /* rx buffer includes reserved space for tx data pointer and length, + * with the tx pointer set to null + */ + if (!ctx->rx_buf) { + total_rx_len -= ctx->rx_len; + } + + /* spi cs1 only supports pio mode */ + if (ctx->config->slave != 0) { + data->ctrl_mode.tx = PIO_MODE; + data->ctrl_mode.rx = PIO_MODE; + goto out; + } + + /* the shared/group fifo mode is supported only under spi mode 0 */ + if (sys_read8(cfg->base + SPI01_CTRL1) & (CLOCK_POLARTY | CLOCK_PHASE)) { + data->ctrl_mode.tx = PIO_MODE; + data->ctrl_mode.rx = PIO_MODE; + goto out; + } + + if (IS_SHARED_FIFO_MODE(total_rx_len)) { + data->ctrl_mode.rx = SHARED_FIFO_MODE; + } else if (IS_GROUP_FIFO_MODE(total_rx_len)) { + /* group fifo mode only operates with the pll frequency clock source */ + if (sys_read8(cfg->base + SPI05_CHAIN_CTRL) & PLL_CLOCK_SOURCE_SELECTION) { + data->ctrl_mode.rx = GROUP_FIFO_MODE; + } else { + data->ctrl_mode.rx = PIO_MODE; + } + } else { + data->ctrl_mode.rx = PIO_MODE; + } + + if (data->ctrl_mode.rx == PIO_MODE && total_rx_len != 0) { + /* pio mode is used for tx if the rx transaction (tx-then-rx) + * is in pio mode. + */ + data->ctrl_mode.tx = PIO_MODE; + goto out; + } + + if (IS_SHARED_FIFO_MODE(total_tx_len)) { + data->ctrl_mode.tx = SHARED_FIFO_MODE; + } else if (IS_GROUP_FIFO_MODE(total_tx_len)) { + /* group fifo mode only operates with the pll frequency clock source */ + if (sys_read8(cfg->base + SPI05_CHAIN_CTRL) & PLL_CLOCK_SOURCE_SELECTION) { + data->ctrl_mode.tx = GROUP_FIFO_MODE; + } else { + data->ctrl_mode.tx = PIO_MODE; + } + } else { + data->ctrl_mode.tx = PIO_MODE; + } + +out: + LOG_DBG("mode selection: tx/rx: %s/%s", + total_tx_len ? GET_MODE_STRING(data->ctrl_mode.tx) : "-", + total_rx_len ? GET_MODE_STRING(data->ctrl_mode.rx) : "-"); +} +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + static int spi_it51xxx_configure(const struct device *dev, const struct spi_config *spi_cfg) { const struct spi_it51xxx_config *cfg = dev->config; @@ -179,6 +321,115 @@ static int spi_it51xxx_configure(const struct device *dev, const struct spi_conf return 0; } +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE +static inline bool rx_fifo_mode_is_enabled(const struct device *dev) +{ + struct spi_it51xxx_data *data = dev->data; + + return data->ctrl_mode.rx != PIO_MODE; +} + +static inline bool tx_fifo_mode_is_enabled(const struct device *dev) +{ + struct spi_it51xxx_data *data = dev->data; + + return data->ctrl_mode.tx != PIO_MODE; +} + +static inline void spi_it51xxx_set_fifo_len(const struct device *dev, const uint8_t mode, + const size_t length) +{ + const struct spi_it51xxx_config *cfg = dev->config; + + if (mode == GROUP_FIFO_MODE) { + if (!IS_GROUP_FIFO_MODE(length)) { + LOG_WRN("length (%d) is incompatible with group fifo mode", length); + } + sys_write8(PAGE_CONTEXT_SIZE_LSB_EN, cfg->base + SPI83_GROUP_PAGE_SIZE_2); + sys_write8(PAGE_SIZE_LB(length - 1), cfg->base + SPI82_GROUP_PAGE_SIZE_1); + sys_write8(sys_read8(cfg->base + SPI83_GROUP_PAGE_SIZE_2) | + PAGE_SIZE_HB(length - 1), + cfg->base + SPI83_GROUP_PAGE_SIZE_2); + } else { + if (!IS_SHARED_FIFO_MODE(length)) { + LOG_WRN("length (%d) is incompatible with shared fifo mode", length); + } + sys_write8(sys_read8(cfg->base + SPI83_GROUP_PAGE_SIZE_2) & + ~PAGE_CONTEXT_SIZE_LSB_EN, + cfg->base + SPI83_GROUP_PAGE_SIZE_2); + sys_write8(length / 8 - 1, cfg->base + SPI06_PAGE_SIZE); + } +} + +static inline bool direction_turnaround_check_workaround(const struct device *dev) +{ + struct spi_it51xxx_data *data = dev->data; + + /* Hardware limitation: When the direction is switched from tx to rx, the rx + * fifo done(FIFO_FULL_INT_STS) interrupt is triggered unexpectedly. This + * abnormal situation only occurs when using the shared/group fifo mode for + * tx transaction. + */ + return data->xfer_mode == XFER_TX_RX && data->ctrl_mode.tx != PIO_MODE; +} + +static inline void spi_it51xxx_fifo_rx(const struct device *dev) +{ + const struct spi_it51xxx_config *cfg = dev->config; + struct spi_it51xxx_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + + if (direction_turnaround_check_workaround(dev) && !data->direction_turnaround) { + data->direction_turnaround = true; + sys_write8(sys_read8(cfg->base + SPI02_CTRL2) | READ_CYCLE, + cfg->base + SPI02_CTRL2); + return; + } + + sys_write8(sys_read8(cfg->base + SPI02_CTRL2) | READ_CYCLE, cfg->base + SPI02_CTRL2); + + data->direction_turnaround = false; + + if (data->xfer_mode == XFER_RX_ONLY) { + data->receive_len = spi_context_total_rx_len(ctx); + } else { + data->receive_len = ctx->rx_len; + } + spi_it51xxx_set_fifo_len(dev, data->ctrl_mode.rx, data->receive_len); + + sys_write8(sys_read8(cfg->base + SPI0B_FIFO_CTRL) | FIFO_TX_RX_START, + cfg->base + SPI0B_FIFO_CTRL); +} + +static inline void spi_it51xxx_fifo_tx(const struct device *dev) +{ + const struct spi_it51xxx_config *cfg = dev->config; + struct spi_it51xxx_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + + sys_write8(sys_read8(cfg->base + SPI02_CTRL2) & ~READ_CYCLE, cfg->base + SPI02_CTRL2); + sys_write8(sys_read8(cfg->base + SPI09_FIFO_BASE_ADDR_1) | BIG_ENDIAN_EN, + cfg->base + SPI09_FIFO_BASE_ADDR_1); + + if (data->xfer_mode == XFER_TX_ONLY) { + for (int i = 0; i < ctx->tx_count; i++) { + const struct spi_buf spi_buf = ctx->current_tx[i]; + + memcpy(data->fifo_data + data->transfer_len, spi_buf.buf, spi_buf.len); + data->transfer_len += spi_buf.len; + } + } else { + memcpy(data->fifo_data, ctx->tx_buf, ctx->tx_len); + data->transfer_len = ctx->tx_len; + } + spi_it51xxx_set_fifo_len(dev, data->ctrl_mode.tx, data->transfer_len); + + LOG_HEXDUMP_DBG(data->fifo_data, data->transfer_len, "fifo: tx:"); + sys_write8(sys_read8(cfg->base + SPI0B_FIFO_CTRL) | FIFO_TX_RX_START, + cfg->base + SPI0B_FIFO_CTRL); +} +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + static inline void spi_it51xxx_tx(const struct device *dev) { const struct spi_it51xxx_config *cfg = dev->config; @@ -213,14 +464,35 @@ static void spi_it51xxx_next_xfer(const struct device *dev) struct spi_context *ctx = &data->ctx; if (spi_it51xxx_xfer_done(ctx)) { +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + sys_write8(FIFO_TX_RX_TERMINATE, cfg->base + SPI0B_FIFO_CTRL); + spi_context_complete(ctx, dev, 0); +#else sys_write8(SPI_TRANSMISSION_END, cfg->base + SPI03_STATUS); +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ return; } if (!spi_context_tx_on(ctx)) { - spi_it51xxx_rx(dev); +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + if (rx_fifo_mode_is_enabled(dev)) { + spi_it51xxx_fifo_rx(dev); + } else { +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + spi_it51xxx_rx(dev); +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + } +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ } else { - spi_it51xxx_tx(dev); +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + if (tx_fifo_mode_is_enabled(dev)) { + spi_it51xxx_fifo_tx(dev); + } else { +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + spi_it51xxx_tx(dev); +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + } +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ } } @@ -241,6 +513,20 @@ static int transceive(const struct device *dev, const struct spi_config *config, } spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1); + +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + spi_it51xxx_ctrl_mode_selection(dev); + + if (!spi_context_tx_on(ctx)) { + data->xfer_mode = XFER_RX_ONLY; + } else if (!spi_context_rx_on(ctx)) { + data->xfer_mode = XFER_TX_ONLY; + } else { + data->xfer_mode = XFER_TX_RX; + } + + chip_block_idle(); +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); LOG_DBG("tx/rx: %d/%d", spi_context_total_tx_len(ctx), spi_context_total_rx_len(ctx)); @@ -248,6 +534,11 @@ static int transceive(const struct device *dev, const struct spi_config *config, spi_it51xxx_next_xfer(dev); ret = spi_context_wait_for_completion(ctx); +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + chip_permit_idle(); + + data->direction_turnaround = false; +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); out: @@ -283,6 +574,44 @@ static int it51xxx_release(const struct device *dev, const struct spi_config *co return 0; } +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE +static void it51xxx_spi_fifo_done_handle(const struct device *dev, const bool is_tx_done) +{ + struct spi_it51xxx_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + + if (is_tx_done) { + do { + size_t curr_tx_len = ctx->tx_len; + + spi_context_update_tx(ctx, 1, curr_tx_len); + data->transfer_len -= curr_tx_len; + } while (data->transfer_len > 0); + } else { + if (!data->direction_turnaround) { + memcpy(ctx->rx_buf, data->fifo_data, data->receive_len); + LOG_HEXDUMP_DBG(ctx->rx_buf, data->receive_len, "fifo: rx:"); + do { + size_t curr_rx_len = ctx->rx_len; + + spi_context_update_rx(ctx, 1, curr_rx_len); + data->receive_len -= curr_rx_len; + } while (data->receive_len > 0); + } + } + + /* it51xxx spi driver accommodates two scenarios: when spi rx buffer + * either exclude tx data pointer and length, or include them but with + * null pointer. + */ + if (!ctx->rx_buf) { + spi_context_update_rx(ctx, 1, ctx->rx_len); + } + + spi_it51xxx_next_xfer(dev); +} +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + static inline bool is_read_cycle(const struct device *dev) { const struct spi_it51xxx_config *cfg = dev->config; @@ -318,7 +647,6 @@ static void it51xxx_spi_isr(const void *arg) { const struct device *dev = arg; const struct spi_it51xxx_config *cfg = dev->config; - struct spi_it51xxx_data *data = dev->data; uint8_t int_sts; int_sts = sys_read8(cfg->base + SPI03_STATUS); @@ -339,9 +667,33 @@ static void it51xxx_spi_isr(const void *arg) if (int_sts & TRANSFER_END) { LOG_DBG("isr: transaction finished"); sys_write8(TRANSFER_END, cfg->base + SPI03_STATUS); +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + return; +#else + struct spi_it51xxx_data *data = dev->data; + spi_context_complete(&data->ctx, dev, 0); +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ } +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + uint8_t fifo_sts, gsclk_sts; + + fifo_sts = sys_read8(cfg->base + SPI0E_CTRL_4); + if (fifo_sts & FIFO_FULL_INT_STS) { + LOG_DBG("isr: fifo full is asserted"); + sys_write8(fifo_sts, cfg->base + SPI0E_CTRL_4); + it51xxx_spi_fifo_done_handle(dev, false); + } + + gsclk_sts = sys_read8(cfg->base + SPI0B_FIFO_CTRL); + if (gsclk_sts & GSCLK_INT_STS) { + LOG_DBG("isr: gsclk is asserted"); + sys_write8(gsclk_sts, cfg->base + SPI0B_FIFO_CTRL); + it51xxx_spi_fifo_done_handle(dev, true); + } +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + int_sts = sys_read8(cfg->base + SPI04_CTRL3); if (int_sts & BYTE_DONE_INT_STS) { LOG_DBG("isr: byte transfer is done"); @@ -368,15 +720,43 @@ static int spi_it51xxx_init(const struct device *dev) return ret; } +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + /* set fifo base address */ + LOG_INF("fifo base address 0x%x", (uint32_t)&data->fifo_data); + sys_write8(FIFO_BASE_ADDR_LB((uint32_t)&data->fifo_data), + cfg->base + SPI09_FIFO_BASE_ADDR_1); + sys_write8(FIFO_BASE_ADDR_HB((uint32_t)&data->fifo_data), + cfg->base + SPI0F_FIFO_BASE_ADDR_2); + + /* enable gsclk middle point method */ + sys_write8(GSCLK_INT_STS, cfg->base + SPI0B_FIFO_CTRL); + sys_write8(GSCLK_END_POINT_INT_EN | GSCLK_END_POINT_HOOK_METHOD_EN, + cfg->base + SPI24_GSCLK_MID_POINT_HOOK_METHOD); + + /* set fifo full interrupt */ + sys_write8(FIFO_FULL_INT_STS, cfg->base + SPI0E_CTRL_4); + sys_write8(FIFO_FULL_INT_EN, cfg->base + SPI0E_CTRL_4); + + if (cfg->irq_flags != IRQ_TYPE_LEVEL_HIGH) { + LOG_ERR("fifo mode only supports level-high-triggered"); + return -ENOTSUP; + } +#else if (cfg->irq_flags != IRQ_TYPE_EDGE_RISING) { - LOG_ERR("only supports rising-edge-triggered"); + LOG_ERR("pio mode only supports rising-edge-triggered"); return -ENOTSUP; } +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ + ite_intc_irq_polarity_set(cfg->irq_no, cfg->irq_flags); /* write 1 clear interrupt status and enable interrupt */ sys_write8(sys_read8(cfg->base + SPI03_STATUS), cfg->base + SPI03_STATUS); +#ifdef CONFIG_SPI_ITE_IT51XXX_FIFO_MODE + sys_write8(SPI_INT_LEVEL_MODE | BYTE_DONE_INT_STS, cfg->base + SPI04_CTRL3); +#else sys_write8(BYTE_DONE_INT_STS, cfg->base + SPI04_CTRL3); +#endif /* CONFIG_SPI_ITE_IT51XXX_FIFO_MODE */ sys_write8(sys_read8(cfg->base + SPI01_CTRL1) | INTERRUPT_EN, cfg->base + SPI01_CTRL1); cfg->irq_config_func();