496 lines
14 KiB
C
496 lines
14 KiB
C
/*
|
|
* Copyright (c) 2020 Henrik Brix Andersen <henrik@brixandersen.dk>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT xlnx_xps_spi_2_00_a
|
|
|
|
#include <device.h>
|
|
#include <drivers/spi.h>
|
|
#include <sys/sys_io.h>
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(xlnx_quadspi, CONFIG_SPI_LOG_LEVEL);
|
|
|
|
#include "spi_context.h"
|
|
|
|
/* AXI Quad SPI v3.2 register offsets (See Xilinx PG153 for details) */
|
|
#define SRR_OFFSET 0x40
|
|
#define SPICR_OFFSET 0x60
|
|
#define SPISR_OFFSET 0x64
|
|
#define SPI_DTR_OFFSET 0x68
|
|
#define SPI_DRR_OFFSET 0x6c
|
|
#define SPISSR_OFFSET 0x70
|
|
#define SPI_TX_FIFO_OCR_OFFSET 0x74
|
|
#define SPI_RX_FIFO_OCR_OFFSET 0x78
|
|
#define DGIER_OFFSET 0x1c
|
|
#define IPISR_OFFSET 0x20
|
|
#define IPIER_OFFSET 0x28
|
|
|
|
/* SRR bit definitions */
|
|
#define SRR_SOFTRESET_MAGIC 0xa
|
|
|
|
/* SPICR bit definitions */
|
|
#define SPICR_LOOP BIT(0)
|
|
#define SPICR_SPE BIT(1)
|
|
#define SPICR_MASTER BIT(2)
|
|
#define SPICR_CPOL BIT(3)
|
|
#define SPICR_CPHA BIT(4)
|
|
#define SPICR_TX_FIFO_RESET BIT(5)
|
|
#define SPICR_RX_FIFO_RESET BIT(6)
|
|
#define SPICR_MANUAL_SS BIT(7)
|
|
#define SPICR_MASTER_XFER_INH BIT(8)
|
|
#define SPICR_LSB_FIRST BIT(9)
|
|
|
|
/* SPISR bit definitions */
|
|
#define SPISR_RX_EMPTY BIT(0)
|
|
#define SPISR_RX_FULL BIT(1)
|
|
#define SPISR_TX_EMPTY BIT(2)
|
|
#define SPISR_TX_FULL BIT(3)
|
|
#define SPISR_MODF BIT(4)
|
|
#define SPISR_SLAVE_MODE_SELECT BIT(5)
|
|
#define SPISR_CPOL_CPHA_ERROR BIT(6)
|
|
#define SPISR_SLAVE_MODE_ERROR BIT(7)
|
|
#define SPISR_MSB_ERROR BIT(8)
|
|
#define SPISR_LOOPBACK_ERROR BIT(9)
|
|
#define SPISR_COMMAND_ERROR BIT(10)
|
|
|
|
#define SPISR_ERROR_MASK (SPISR_COMMAND_ERROR | \
|
|
SPISR_LOOPBACK_ERROR | \
|
|
SPISR_MSB_ERROR | \
|
|
SPISR_SLAVE_MODE_ERROR | \
|
|
SPISR_CPOL_CPHA_ERROR)
|
|
|
|
/* DGIER bit definitions */
|
|
#define DGIER_GIE BIT(31)
|
|
|
|
/* IPISR and IPIER bit definitions */
|
|
#define IPIXR_MODF BIT(0)
|
|
#define IPIXR_SLAVE_MODF BIT(1)
|
|
#define IPIXR_DTR_EMPTY BIT(2)
|
|
#define IPIXR_DTR_UNDERRUN BIT(3)
|
|
#define IPIXR_DRR_FULL BIT(4)
|
|
#define IPIXR_DRR_OVERRUN BIT(5)
|
|
#define IPIXR_TX_FIFO_HALF_EMPTY BIT(6)
|
|
#define IPIXR_SLAVE_MODE_SELECT BIT(7)
|
|
#define IPIXR_DDR_NOT_EMPTY BIT(8)
|
|
#define IPIXR_CPOL_CPHA_ERROR BIT(9)
|
|
#define IPIXR_SLAVE_MODE_ERROR BIT(10)
|
|
#define IPIXR_MSB_ERROR BIT(11)
|
|
#define IPIXR_LOOPBACK_ERROR BIT(12)
|
|
#define IPIXR_COMMAND_ERROR BIT(13)
|
|
|
|
struct xlnx_quadspi_config {
|
|
mm_reg_t base;
|
|
void (*irq_config_func)(const struct device *dev);
|
|
uint8_t num_ss_bits;
|
|
uint8_t num_xfer_bytes;
|
|
};
|
|
|
|
struct xlnx_quadspi_data {
|
|
struct spi_context ctx;
|
|
};
|
|
|
|
static inline uint32_t xlnx_quadspi_read32(const struct device *dev,
|
|
mm_reg_t offset)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
|
|
return sys_read32(config->base + offset);
|
|
}
|
|
|
|
static inline void xlnx_quadspi_write32(const struct device *dev,
|
|
uint32_t value,
|
|
mm_reg_t offset)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
|
|
sys_write32(value, config->base + offset);
|
|
}
|
|
|
|
static void xlnx_quadspi_cs_control(const struct device *dev, bool on)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
struct xlnx_quadspi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
uint32_t spissr = BIT_MASK(config->num_ss_bits);
|
|
|
|
if (IS_ENABLED(CONFIG_SPI_SLAVE) && spi_context_is_slave(ctx)) {
|
|
/* Skip slave select assert/de-assert in slave mode */
|
|
return;
|
|
}
|
|
|
|
if (on) {
|
|
/* SPISSR is one-hot, active-low */
|
|
spissr &= ~BIT(ctx->config->slave);
|
|
} else if (ctx->config->operation & SPI_HOLD_ON_CS) {
|
|
/* Skip slave select de-assert */
|
|
return;
|
|
}
|
|
|
|
xlnx_quadspi_write32(dev, spissr, SPISSR_OFFSET);
|
|
spi_context_cs_control(ctx, on);
|
|
}
|
|
|
|
static int xlnx_quadspi_configure(const struct device *dev,
|
|
const struct spi_config *spi_cfg)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
struct xlnx_quadspi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
uint32_t word_size;
|
|
uint32_t spicr;
|
|
uint32_t spisr;
|
|
|
|
if (spi_context_configured(ctx, spi_cfg)) {
|
|
/* Configuration already active, just enable SPI IOs */
|
|
spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET);
|
|
spicr |= SPICR_SPE;
|
|
xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET);
|
|
return 0;
|
|
}
|
|
|
|
if (spi_cfg->slave >= config->num_ss_bits) {
|
|
LOG_ERR("unsupported slave %d, num_ss_bits %d",
|
|
spi_cfg->slave, config->num_ss_bits);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (spi_cfg->operation & SPI_CS_ACTIVE_HIGH) {
|
|
LOG_ERR("unsupported CS polarity active high");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_SPI_SLAVE) && \
|
|
(spi_cfg->operation & SPI_OP_MODE_SLAVE)) {
|
|
LOG_ERR("slave mode support not enabled");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
word_size = SPI_WORD_SIZE_GET(spi_cfg->operation);
|
|
if (word_size != (config->num_xfer_bytes * 8)) {
|
|
LOG_ERR("unsupported word size %d bits, num_xfer_bytes %d",
|
|
word_size, config->num_xfer_bytes);
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Reset FIFOs, SPI IOs enabled */
|
|
spicr = SPICR_TX_FIFO_RESET | SPICR_RX_FIFO_RESET | SPICR_SPE;
|
|
|
|
/* Master mode, inhibit master transmit, manual slave select */
|
|
if (!IS_ENABLED(CONFIG_SPI_SLAVE) ||
|
|
(spi_cfg->operation & SPI_OP_MODE_SLAVE) == 0U) {
|
|
spicr |= SPICR_MASTER | SPICR_MASTER_XFER_INH | SPICR_MANUAL_SS;
|
|
}
|
|
|
|
if (spi_cfg->operation & SPI_MODE_CPOL) {
|
|
spicr |= SPICR_CPOL;
|
|
}
|
|
|
|
if (spi_cfg->operation & SPI_MODE_CPHA) {
|
|
spicr |= SPICR_CPHA;
|
|
}
|
|
|
|
if (spi_cfg->operation & SPI_MODE_LOOP) {
|
|
spicr |= SPICR_LOOP;
|
|
}
|
|
|
|
if (spi_cfg->operation & SPI_TRANSFER_LSB) {
|
|
spicr |= SPICR_LSB_FIRST;
|
|
}
|
|
|
|
/*
|
|
* Write configuration and verify it is compliant with the IP core
|
|
* configuration. Tri-state SPI IOs on error.
|
|
*/
|
|
xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET);
|
|
spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET);
|
|
if (spisr & SPISR_ERROR_MASK) {
|
|
LOG_ERR("unsupported configuration, spisr = 0x%08x", spisr);
|
|
xlnx_quadspi_write32(dev, SPICR_MASTER_XFER_INH, SPICR_OFFSET);
|
|
ctx->config = NULL;
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
ctx->config = spi_cfg;
|
|
|
|
if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) {
|
|
spi_context_cs_configure(ctx);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xlnx_quadspi_start_tx(const struct device *dev)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
struct xlnx_quadspi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
size_t xfer_len;
|
|
uint32_t spicr = 0U;
|
|
uint32_t spisr;
|
|
uint32_t dtr = 0U;
|
|
|
|
if (!spi_context_tx_on(ctx) && !spi_context_rx_on(ctx)) {
|
|
/* All done, de-assert slave select */
|
|
xlnx_quadspi_cs_control(dev, false);
|
|
|
|
if ((ctx->config->operation & SPI_HOLD_ON_CS) == 0U) {
|
|
/* Tri-state SPI IOs */
|
|
spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET);
|
|
spicr &= ~(SPICR_SPE);
|
|
xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET);
|
|
}
|
|
|
|
spi_context_complete(ctx, 0);
|
|
return;
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) {
|
|
/* Inhibit master transaction while writing TX data */
|
|
spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET);
|
|
spicr |= SPICR_MASTER_XFER_INH;
|
|
xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET);
|
|
}
|
|
|
|
/* We can only see as far as the current rx buffer */
|
|
xfer_len = spi_context_longest_current_buf(ctx);
|
|
|
|
/* Write TX data */
|
|
while (xfer_len--) {
|
|
if (spi_context_tx_buf_on(ctx)) {
|
|
switch (config->num_xfer_bytes) {
|
|
case 1:
|
|
dtr = UNALIGNED_GET((uint8_t *)(ctx->tx_buf));
|
|
break;
|
|
case 2:
|
|
dtr = UNALIGNED_GET((uint16_t *)(ctx->tx_buf));
|
|
break;
|
|
case 4:
|
|
dtr = UNALIGNED_GET((uint32_t *)(ctx->tx_buf));
|
|
break;
|
|
default:
|
|
__ASSERT(0, "unsupported num_xfer_bytes");
|
|
}
|
|
} else {
|
|
/* No TX buffer. Use dummy TX data */
|
|
dtr = 0U;
|
|
}
|
|
|
|
xlnx_quadspi_write32(dev, dtr, SPI_DTR_OFFSET);
|
|
spi_context_update_tx(ctx, config->num_xfer_bytes, 1);
|
|
|
|
spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET);
|
|
if (spisr & SPISR_TX_FULL) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET);
|
|
if (spisr & SPISR_COMMAND_ERROR) {
|
|
/* Command not supported by memory type configured in IP core */
|
|
LOG_ERR("unsupported command");
|
|
xlnx_quadspi_cs_control(dev, false);
|
|
|
|
spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET);
|
|
if ((ctx->config->operation & SPI_HOLD_ON_CS) == 0U) {
|
|
/* Tri-state SPI IOs */
|
|
spicr &= ~(SPICR_SPE);
|
|
}
|
|
xlnx_quadspi_write32(dev, spicr | SPICR_TX_FIFO_RESET,
|
|
SPICR_OFFSET);
|
|
|
|
spi_context_complete(ctx, -ENOTSUP);
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) {
|
|
/* Uninhibit master transaction */
|
|
spicr &= ~(SPICR_MASTER_XFER_INH);
|
|
xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET);
|
|
}
|
|
}
|
|
|
|
static int xlnx_quadspi_transceive(const struct device *dev,
|
|
const struct spi_config *spi_cfg,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs,
|
|
bool async, struct k_poll_signal *signal)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
struct xlnx_quadspi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
int ret;
|
|
|
|
spi_context_lock(ctx, async, signal, spi_cfg);
|
|
|
|
ret = xlnx_quadspi_configure(dev, spi_cfg);
|
|
if (ret) {
|
|
goto out;
|
|
}
|
|
|
|
spi_context_buffers_setup(ctx, tx_bufs, rx_bufs,
|
|
config->num_xfer_bytes);
|
|
|
|
xlnx_quadspi_cs_control(dev, true);
|
|
|
|
xlnx_quadspi_start_tx(dev);
|
|
|
|
ret = spi_context_wait_for_completion(ctx);
|
|
out:
|
|
spi_context_release(ctx, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int xlnx_quadspi_transceive_blocking(const struct device *dev,
|
|
const struct spi_config *spi_cfg,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
return xlnx_quadspi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, false,
|
|
NULL);
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
static int xlnx_quadspi_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,
|
|
struct k_poll_signal *signal)
|
|
{
|
|
return xlnx_quadspi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, true,
|
|
signal);
|
|
}
|
|
#endif /* CONFIG_SPI_ASYNC */
|
|
|
|
static int xlnx_quadspi_release(const struct device *dev,
|
|
const struct spi_config *spi_cfg)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
struct xlnx_quadspi_data *data = dev->data;
|
|
uint32_t spicr;
|
|
|
|
/* Force slave select de-assert */
|
|
xlnx_quadspi_write32(dev, BIT_MASK(config->num_ss_bits), SPISSR_OFFSET);
|
|
|
|
/* Tri-state SPI IOs */
|
|
spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET);
|
|
spicr &= ~(SPICR_SPE);
|
|
xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET);
|
|
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xlnx_quadspi_isr(const struct device *dev)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
struct xlnx_quadspi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
uint32_t temp;
|
|
uint32_t drr;
|
|
|
|
/* Acknowledge interrupt */
|
|
temp = xlnx_quadspi_read32(dev, IPISR_OFFSET);
|
|
xlnx_quadspi_write32(dev, temp, IPISR_OFFSET);
|
|
|
|
if (temp & IPIXR_DTR_EMPTY) {
|
|
temp = xlnx_quadspi_read32(dev, SPISR_OFFSET);
|
|
|
|
/* Read RX data */
|
|
while (!(temp & SPISR_RX_EMPTY)) {
|
|
drr = xlnx_quadspi_read32(dev, SPI_DRR_OFFSET);
|
|
|
|
if (spi_context_rx_buf_on(ctx)) {
|
|
switch (config->num_xfer_bytes) {
|
|
case 1:
|
|
UNALIGNED_PUT(drr,
|
|
(uint8_t *)ctx->rx_buf);
|
|
break;
|
|
case 2:
|
|
UNALIGNED_PUT(drr,
|
|
(uint16_t *)ctx->rx_buf);
|
|
break;
|
|
case 4:
|
|
UNALIGNED_PUT(drr,
|
|
(uint32_t *)ctx->rx_buf);
|
|
break;
|
|
default:
|
|
__ASSERT(0,
|
|
"unsupported num_xfer_bytes");
|
|
}
|
|
}
|
|
|
|
spi_context_update_rx(ctx, config->num_xfer_bytes, 1);
|
|
|
|
temp = xlnx_quadspi_read32(dev, SPISR_OFFSET);
|
|
}
|
|
|
|
/* Start next TX */
|
|
xlnx_quadspi_start_tx(dev);
|
|
} else {
|
|
LOG_WRN("unhandled interrupt, ipisr = 0x%08x", temp);
|
|
}
|
|
}
|
|
|
|
static int xlnx_quadspi_init(const struct device *dev)
|
|
{
|
|
const struct xlnx_quadspi_config *config = dev->config;
|
|
struct xlnx_quadspi_data *data = dev->data;
|
|
|
|
/* Reset controller */
|
|
xlnx_quadspi_write32(dev, SRR_SOFTRESET_MAGIC, SRR_OFFSET);
|
|
|
|
config->irq_config_func(dev);
|
|
|
|
/* Enable DTR Empty interrupt */
|
|
xlnx_quadspi_write32(dev, IPIXR_DTR_EMPTY, IPIER_OFFSET);
|
|
xlnx_quadspi_write32(dev, DGIER_GIE, DGIER_OFFSET);
|
|
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct spi_driver_api xlnx_quadspi_driver_api = {
|
|
.transceive = xlnx_quadspi_transceive_blocking,
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
.transceive_async = xlnx_quadspi_transceive_async,
|
|
#endif /* CONFIG_SPI_ASYNC */
|
|
.release = xlnx_quadspi_release,
|
|
};
|
|
|
|
#define XLNX_QUADSPI_INIT(n) \
|
|
static void xlnx_quadspi_config_func_##n(const struct device *dev); \
|
|
\
|
|
static const struct xlnx_quadspi_config xlnx_quadspi_config_##n = { \
|
|
.base = DT_INST_REG_ADDR(n), \
|
|
.irq_config_func = xlnx_quadspi_config_func_##n, \
|
|
.num_ss_bits = DT_INST_PROP(n, xlnx_num_ss_bits), \
|
|
.num_xfer_bytes = \
|
|
DT_INST_PROP(n, xlnx_num_transfer_bits) / 8, \
|
|
}; \
|
|
\
|
|
static struct xlnx_quadspi_data xlnx_quadspi_data_##n = { \
|
|
SPI_CONTEXT_INIT_LOCK(xlnx_quadspi_data_##n, ctx), \
|
|
SPI_CONTEXT_INIT_SYNC(xlnx_quadspi_data_##n, ctx), \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, &xlnx_quadspi_init, \
|
|
NULL, \
|
|
&xlnx_quadspi_data_##n, \
|
|
&xlnx_quadspi_config_##n, POST_KERNEL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
|
&xlnx_quadspi_driver_api); \
|
|
\
|
|
static void xlnx_quadspi_config_func_##n(const struct device *dev) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
|
|
xlnx_quadspi_isr, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
irq_enable(DT_INST_IRQN(n)); \
|
|
}
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(XLNX_QUADSPI_INIT)
|