blob: 46cc059592f18360080232f722bb45780acfca12 [file] [log] [blame]
/*
* 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)