From 5002ea4c420a0afbf30282c2fd9727691a62b38b Mon Sep 17 00:00:00 2001 From: Shawn Nematbakhsh Date: Wed, 8 Feb 2023 15:10:44 -0800 Subject: [PATCH] drivers: spi: Add OpenTitan SPI driver Add driver for OpenTitan SPI host. Tested and verified single SPI full duplex on opentitan_earlgrey in Verilator. Signed-off-by: Shawn Nematbakhsh --- drivers/spi/CMakeLists.txt | 1 + drivers/spi/Kconfig | 2 + drivers/spi/Kconfig.opentitan | 9 + drivers/spi/spi_opentitan.c | 331 ++++++++++++++++++++++++++++++++++ 4 files changed, 343 insertions(+) create mode 100644 drivers/spi/Kconfig.opentitan create mode 100644 drivers/spi/spi_opentitan.c diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index ff67d15547e..0f7d5bcb16b 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -36,6 +36,7 @@ zephyr_library_sources_ifdef(CONFIG_NXP_S32_SPI spi_nxp_s32.c) zephyr_library_sources_ifdef(CONFIG_SPI_XMC4XXX spi_xmc4xxx.c) zephyr_library_sources_ifdef(CONFIG_SPI_PW spi_pw.c) zephyr_library_sources_ifdef(CONFIG_SPI_SMARTBOND spi_smartbond.c) +zephyr_library_sources_ifdef(CONFIG_SPI_OPENTITAN spi_opentitan.c) zephyr_library_sources_ifdef(CONFIG_SPI_RTIO spi_rtio.c) zephyr_library_sources_ifdef(CONFIG_SPI_ASYNC spi_signal.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 749b286a991..c4e457b0790 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -121,4 +121,6 @@ source "drivers/spi/Kconfig.pw" source "drivers/spi/Kconfig.smartbond" +source "drivers/spi/Kconfig.opentitan" + endif # SPI diff --git a/drivers/spi/Kconfig.opentitan b/drivers/spi/Kconfig.opentitan new file mode 100644 index 00000000000..156af5a97fb --- /dev/null +++ b/drivers/spi/Kconfig.opentitan @@ -0,0 +1,9 @@ +# Copyright (c) 2023 Rivos Inc. +# SPDX-License-Identifier: Apache-2.0 + +config SPI_OPENTITAN + bool "OpenTitan SPI controller driver" + default y + depends on DT_HAS_LOWRISC_OPENTITAN_SPI_ENABLED + help + Enable the SPI peripherals on OpenTitan diff --git a/drivers/spi/spi_opentitan.c b/drivers/spi/spi_opentitan.c new file mode 100644 index 00000000000..3116315d929 --- /dev/null +++ b/drivers/spi/spi_opentitan.c @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2023 Rivos Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(spi_opentitan); + +#include "spi_context.h" + +#include +#include +#include +#include + +/* Register offsets within the SPI host register space. */ +#define SPI_HOST_INTR_STATE_REG_OFFSET 0x00 +#define SPI_HOST_INTR_ENABLE_REG_OFFSET 0x04 +#define SPI_HOST_INTR_TEST_REG_OFFSET 0x08 +#define SPI_HOST_ALERT_TEST_REG_OFFSET 0x0c +#define SPI_HOST_CONTROL_REG_OFFSET 0x10 +#define SPI_HOST_STATUS_REG_OFFSET 0x14 +#define SPI_HOST_CONFIGOPTS_REG_OFFSET 0x18 +#define SPI_HOST_CSID_REG_OFFSET 0x1c +#define SPI_HOST_COMMAND_REG_OFFSET 0x20 +#define SPI_HOST_RXDATA_REG_OFFSET 0x24 +#define SPI_HOST_TXDATA_REG_OFFSET 0x28 +#define SPI_HOST_ERROR_ENABLE_REG_OFFSET 0x2c +#define SPI_HOST_ERROR_STATUS_REG_OFFSET 0x30 +#define SPI_HOST_EVENT_ENABLE_REG_OFFSET 0x34 + +/* Control register fields. */ +#define SPI_HOST_CONTROL_OUTPUT_EN_BIT BIT(29) +#define SPI_HOST_CONTROL_SW_RST_BIT BIT(30) +#define SPI_HOST_CONTROL_SPIEN_BIT BIT(31) + +/* Status register fields. */ +#define SPI_HOST_STATUS_TXQD_MASK GENMASK(7, 0) +#define SPI_HOST_STATUS_RXQD_MASK GENMASK(15, 8) +#define SPI_HOST_STATUS_BYTEORDER_BIT BIT(22) +#define SPI_HOST_STATUS_RXEMPTY_BIT BIT(24) +#define SPI_HOST_STATUS_ACTIVE_BIT BIT(30) +#define SPI_HOST_STATUS_READY_BIT BIT(31) + +/* Command register fields. */ +#define SPI_HOST_COMMAND_LEN_MASK GENMASK(8, 0) +/* "Chip select active after transaction" */ +#define SPI_HOST_COMMAND_CSAAT_BIT BIT(9) +#define SPI_HOST_COMMAND_SPEED_MASK GENMASK(11, 10) +#define SPI_HOST_COMMAND_SPEED_STANDARD (0 << 10) +#define SPI_HOST_COMMAND_SPEED_DUAL (1 << 10) +#define SPI_HOST_COMMAND_SPEED_QUAD (2 << 10) +#define SPI_HOST_COMMAND_DIRECTION_MASK GENMASK(13, 12) +#define SPI_HOST_COMMAND_DIRECTION_RX (0x1 << 12) +#define SPI_HOST_COMMAND_DIRECTION_TX (0x2 << 12) +#define SPI_HOST_COMMAND_DIRECTION_BOTH (0x3 << 12) + +/* Configopts register fields. */ +#define SPI_HOST_CONFIGOPTS_CPHA0_BIT BIT(30) +#define SPI_HOST_CONFIGOPTS_CPOL0_BIT BIT(31) + +#define DT_DRV_COMPAT lowrisc_opentitan_spi + +struct spi_opentitan_data { + struct spi_context ctx; +}; + +struct spi_opentitan_cfg { + uint32_t base; + uint32_t f_input; +}; + +static int spi_config(const struct device *dev, uint32_t frequency, + uint16_t operation) +{ + const struct spi_opentitan_cfg *cfg = dev->config; + + uint32_t reg; + + if (operation & SPI_HALF_DUPLEX) { + return -ENOTSUP; + } + + if (SPI_OP_MODE_GET(operation) != SPI_OP_MODE_MASTER) { + return -ENOTSUP; + } + + if (operation & SPI_MODE_LOOP) { + return -ENOTSUP; + } + + if (SPI_WORD_SIZE_GET(operation) != 8) { + return -ENOTSUP; + } + + if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) && + (operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) { + return -ENOTSUP; + } + + /* Most significant bit always transferred first. */ + if (operation & SPI_TRANSFER_LSB) { + return -ENOTSUP; + } + + /* Set the SPI frequency, polarity, and clock phase in CONFIGOPTS register. + * Applied divider (divides f_in / 2) is CLKDIV register (16 bit) + 1. + */ + reg = cfg->f_input / 2 / frequency; + if (reg > 0xffffu) { + reg = 0xffffu; + } else if (reg > 0) { + reg--; + } + /* Setup phase */ + if (operation & SPI_MODE_CPHA) { + reg |= SPI_HOST_CONFIGOPTS_CPHA0_BIT; + } + /* Setup polarity. */ + if (operation & SPI_MODE_CPOL) { + reg |= SPI_HOST_CONFIGOPTS_CPOL0_BIT; + } + sys_write32(reg, cfg->base + SPI_HOST_CONFIGOPTS_REG_OFFSET); + + return 0; +} + +static bool spi_opentitan_rx_available(const struct spi_opentitan_cfg *cfg) +{ + /* Rx bytes are available if Tx FIFO is non-empty. */ + return !(sys_read32(cfg->base + SPI_HOST_STATUS_REG_OFFSET) & SPI_HOST_STATUS_RXEMPTY_BIT); +} + +static void spi_opentitan_xfer(const struct device *dev, const bool gpio_cs_control) +{ + const struct spi_opentitan_cfg *cfg = dev->config; + struct spi_opentitan_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + + while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx)) { + const size_t segment_len = MAX(ctx->tx_len, ctx->rx_len); + uint32_t host_command_reg; + + /* Setup transaction duplex. */ + if (!spi_context_tx_on(ctx)) { + host_command_reg = SPI_HOST_COMMAND_DIRECTION_RX; + } else if (!spi_context_rx_on(ctx)) { + host_command_reg = SPI_HOST_COMMAND_DIRECTION_TX; + } else { + host_command_reg = SPI_HOST_COMMAND_DIRECTION_BOTH; + } + + size_t tx_bytes_to_queue = spi_context_tx_buf_on(ctx) ? ctx->tx_len : 0; + + /* First place Tx bytes in FIFO, packed four to a word. */ + while (tx_bytes_to_queue > 0) { + uint32_t fifo_word = 0; + + for (int byte = 0; byte < 4; ++byte) { + if (tx_bytes_to_queue == 0) { + break; + } + fifo_word |= *ctx->tx_buf << (8 * byte); + spi_context_update_tx(ctx, 1, 1); + tx_bytes_to_queue--; + } + sys_write32(fifo_word, cfg->base + SPI_HOST_TXDATA_REG_OFFSET); + } + + /* Keep CS asserted if another Tx segment remains or if two more Rx + * segements remain (because we will handle one Rx segment after the + * forthcoming transaction). + */ + if (ctx->tx_count > 0 || ctx->rx_count > 1) { + host_command_reg |= SPI_HOST_COMMAND_CSAAT_BIT; + } + /* Segment length field holds COMMAND.LEN + 1. */ + host_command_reg |= segment_len - 1; + + /* Issue transaction. */ + sys_write32(host_command_reg, cfg->base + SPI_HOST_COMMAND_REG_OFFSET); + + size_t rx_bytes_to_read = spi_context_rx_buf_on(ctx) ? ctx->rx_len : 0; + + /* Read from Rx FIFO as required. */ + while (rx_bytes_to_read > 0) { + while (!spi_opentitan_rx_available(cfg)) { + ; + } + uint32_t rx_word = sys_read32(cfg->base + + SPI_HOST_RXDATA_REG_OFFSET); + for (int byte = 0; byte < 4; ++byte) { + if (rx_bytes_to_read == 0) { + break; + } + *ctx->rx_buf = (rx_word >> (8 * byte)) & 0xff; + spi_context_update_rx(ctx, 1, 1); + rx_bytes_to_read--; + } + } + } + + /* Deassert the CS line if required. */ + if (gpio_cs_control) { + spi_context_cs_control(ctx, false); + } + + spi_context_complete(ctx, dev, 0); +} + +static int spi_opentitan_init(const struct device *dev) +{ + const struct spi_opentitan_cfg *cfg = dev->config; + struct spi_opentitan_data *data = dev->data; + int err; + + /* Place SPI host peripheral in reset and wait for reset to complete. */ + sys_write32(SPI_HOST_CONTROL_SW_RST_BIT, + cfg->base + SPI_HOST_CONTROL_REG_OFFSET); + while (sys_read32(cfg->base + SPI_HOST_STATUS_REG_OFFSET) + & (SPI_HOST_STATUS_ACTIVE_BIT | SPI_HOST_STATUS_TXQD_MASK | + SPI_HOST_STATUS_RXQD_MASK)) { + ; + } + /* Clear reset and enable SPI host peripheral. */ + sys_write32(SPI_HOST_CONTROL_OUTPUT_EN_BIT | SPI_HOST_CONTROL_SPIEN_BIT, + cfg->base + SPI_HOST_CONTROL_REG_OFFSET); + + err = spi_context_cs_configure_all(&data->ctx); + if (err < 0) { + return err; + } + + /* Make sure the context is unlocked */ + spi_context_unlock_unconditionally(&data->ctx); + return 0; +} + +static int spi_opentitan_transceive(const struct device *dev, + const struct spi_config *config, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs) +{ + int rc = 0; + bool gpio_cs_control = false; + struct spi_opentitan_data *data = dev->data; + + /* Lock the SPI Context */ + spi_context_lock(&data->ctx, false, NULL, NULL, config); + + /* Configure the SPI bus */ + data->ctx.config = config; + rc = spi_config(dev, config->frequency, config->operation); + if (rc < 0) { + spi_context_release(&data->ctx, rc); + return rc; + } + + spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1); + + /* Assert the CS line. HW will always assert the CS pin identified by CSID + * (default CSID: 0), so GPIO CS control will work in addition to HW + * asserted (and presumably ignored) CS. + */ + if (config->cs) { + gpio_cs_control = true; + spi_context_cs_control(&data->ctx, true); + } + + /* Perform transfer */ + spi_opentitan_xfer(dev, gpio_cs_control); + + rc = spi_context_wait_for_completion(&data->ctx); + + spi_context_release(&data->ctx, rc); + + return rc; +} + +#ifdef CONFIG_SPI_ASYNC +static int spi_opentitan_transceive_async(const struct device *dev, + const struct spi_config *spi_cfg, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs, + spi_callback_t cb, + void *userdata) +{ + return -ENOTSUP; +} +#endif + +static int spi_opentitan_release(const struct device *dev, + const struct spi_config *config) +{ + struct spi_opentitan_data *data = dev->data; + + spi_context_unlock_unconditionally(&data->ctx); + return 0; +} + +/* Device Instantiation */ + +static struct spi_driver_api spi_opentitan_api = { + .transceive = spi_opentitan_transceive, +#ifdef CONFIG_SPI_ASYNC + .transceive_async = spi_opentitan_transceive_async, +#endif + .release = spi_opentitan_release, +}; + +#define SPI_INIT(n) \ + static struct spi_opentitan_data spi_opentitan_data_##n = { \ + SPI_CONTEXT_INIT_LOCK(spi_opentitan_data_##n, ctx), \ + SPI_CONTEXT_INIT_SYNC(spi_opentitan_data_##n, ctx), \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx) \ + }; \ + static struct spi_opentitan_cfg spi_opentitan_cfg_##n = { \ + .base = DT_INST_REG_ADDR(n), \ + .f_input = DT_INST_PROP(n, clock_frequency), \ + }; \ + DEVICE_DT_INST_DEFINE(n, \ + spi_opentitan_init, \ + NULL, \ + &spi_opentitan_data_##n, \ + &spi_opentitan_cfg_##n, \ + POST_KERNEL, \ + CONFIG_SPI_INIT_PRIORITY, \ + &spi_opentitan_api); + +DT_INST_FOREACH_STATUS_OKAY(SPI_INIT)