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 <shawn@rivosinc.com>
This commit is contained in:
parent
b4c4e56b65
commit
5002ea4c42
|
@ -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)
|
||||
|
|
|
@ -121,4 +121,6 @@ source "drivers/spi/Kconfig.pw"
|
|||
|
||||
source "drivers/spi/Kconfig.smartbond"
|
||||
|
||||
source "drivers/spi/Kconfig.opentitan"
|
||||
|
||||
endif # SPI
|
||||
|
|
9
drivers/spi/Kconfig.opentitan
Normal file
9
drivers/spi/Kconfig.opentitan
Normal file
|
@ -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
|
331
drivers/spi/spi_opentitan.c
Normal file
331
drivers/spi/spi_opentitan.c
Normal file
|
@ -0,0 +1,331 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Rivos Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(spi_opentitan);
|
||||
|
||||
#include "spi_context.h"
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/spi.h>
|
||||
#include <soc.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* 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)
|
Loading…
Reference in a new issue