/* * 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)