336 lines
9.2 KiB
C
336 lines
9.2 KiB
C
/*
|
|
* 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 <zephyr/drivers/spi/rtio.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
|
|
* segments 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 const struct spi_driver_api spi_opentitan_api = {
|
|
.transceive = spi_opentitan_transceive,
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
.transceive_async = spi_opentitan_transceive_async,
|
|
#endif
|
|
#ifdef CONFIG_SPI_RTIO
|
|
.iodev_submit = spi_rtio_iodev_default_submit,
|
|
#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)
|