blob: ad78a424adc528aa2e4e719190658b2f885c4ab8 [file] [log] [blame]
/*
* Copyright (c) 2024 Vogl Electronic GmbH
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT litex_spi_litespi
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(spi_litex_litespi);
#include <zephyr/sys/byteorder.h>
#include "spi_litex_common.h"
#define SPI_LITEX_ANY_HAS_IRQ DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
#define SPI_LITEX_ALL_HAS_IRQ DT_ALL_INST_HAS_PROP_STATUS_OKAY(interrupts)
#define SPI_LITEX_HAS_IRQ UTIL_OR(SPI_LITEX_ALL_HAS_IRQ, dev_config->has_irq)
#define SPIFLASH_MASTER_PHYCONFIG_LEN_OFFSET 0x0
#define SPIFLASH_MASTER_PHYCONFIG_WIDTH_OFFSET 0x1
#define SPIFLASH_MASTER_PHYCONFIG_MASK_OFFSET 0x2
#define SPIFLASH_MASTER_STATUS_TX_READY_OFFSET 0x0
#define SPIFLASH_MASTER_STATUS_RX_READY_OFFSET 0x1
#define SPI_MAX_WORD_SIZE 32
#define SPI_MAX_CS_SIZE 32
#define SPI_LITEX_WIDTH BIT(0)
#define SPI_LITEX_MASK BIT(0)
struct spi_litex_dev_config {
uint32_t master_cs_addr;
uint32_t master_phyconfig_addr;
uint32_t master_rxtx_addr;
uint32_t master_rxtx_size;
uint32_t master_status_addr;
uint32_t phy_clk_divisor_addr;
bool phy_clk_divisor_exists;
#if SPI_LITEX_ANY_HAS_IRQ
#if !SPI_LITEX_ALL_HAS_IRQ
bool has_irq;
#endif /* !SPI_LITEX_ALL_HAS_IRQ */
void (*irq_config_func)(const struct device *dev);
uint32_t master_ev_pending_addr;
uint32_t master_ev_enable_addr;
#endif /* SPI_LITEX_ANY_HAS_IRQ */
};
struct spi_litex_data {
struct spi_context ctx;
uint8_t dfs; /* dfs in bytes: 1,2 or 4 */
uint8_t len; /* length of the last transfer in bytes */
};
static int spi_litex_set_frequency(const struct device *dev, const struct spi_config *config)
{
const struct spi_litex_dev_config *dev_config = dev->config;
if (!dev_config->phy_clk_divisor_exists) {
/* In the LiteX Simulator the phy_clk_divisor doesn't exists, thats why we check. */
LOG_WRN("No phy_clk_divisor found, can't change frequency");
return 0;
}
uint32_t divisor = DIV_ROUND_UP(sys_clock_hw_cycles_per_sec(), (2 * config->frequency)) - 1;
litex_write32(divisor, dev_config->phy_clk_divisor_addr);
return 0;
}
/* Helper Functions */
static int spi_config(const struct device *dev, const struct spi_config *config)
{
struct spi_litex_data *dev_data = dev->data;
if (spi_context_configured(&dev_data->ctx, config)) {
/* Context is already configured */
return 0;
}
if (config->slave >= SPI_MAX_CS_SIZE) {
LOG_ERR("More slaves than supported");
return -ENOTSUP;
}
if (config->operation & SPI_HALF_DUPLEX) {
LOG_ERR("Half-duplex not supported");
return -ENOTSUP;
}
if (SPI_WORD_SIZE_GET(config->operation) > SPI_MAX_WORD_SIZE) {
LOG_ERR("Word size must be <= %d, is %d", SPI_MAX_WORD_SIZE,
SPI_WORD_SIZE_GET(config->operation));
return -ENOTSUP;
}
if (config->operation & SPI_CS_ACTIVE_HIGH) {
LOG_ERR("CS active high not supported");
return -ENOTSUP;
}
if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) &&
(config->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) {
LOG_ERR("Only supports single mode");
return -ENOTSUP;
}
if (config->operation & SPI_TRANSFER_LSB) {
LOG_ERR("LSB first not supported");
return -ENOTSUP;
}
if (config->operation & (SPI_MODE_CPOL | SPI_MODE_CPHA)) {
LOG_ERR("Only supports CPOL=CPHA=0");
return -ENOTSUP;
}
if (config->operation & SPI_OP_MODE_SLAVE) {
LOG_ERR("Slave mode not supported");
return -ENOTSUP;
}
if (config->operation & SPI_MODE_LOOP) {
LOG_ERR("Loopback mode not supported");
return -ENOTSUP;
}
dev_data->dfs = get_dfs_value(config);
spi_litex_set_frequency(dev, config);
dev_data->ctx.config = config;
return 0;
}
static void spiflash_len_mask_width_write(uint32_t len, uint32_t width, uint32_t mask,
uint32_t addr)
{
uint32_t tmp = len & BIT_MASK(8);
uint32_t word = tmp << (SPIFLASH_MASTER_PHYCONFIG_LEN_OFFSET * 8);
tmp = width & BIT_MASK(8);
word |= tmp << (SPIFLASH_MASTER_PHYCONFIG_WIDTH_OFFSET * 8);
tmp = mask & BIT_MASK(8);
word |= tmp << (SPIFLASH_MASTER_PHYCONFIG_MASK_OFFSET * 8);
litex_write32(word, addr);
}
static void spi_litex_spi_do_tx(const struct device *dev)
{
const struct spi_litex_dev_config *dev_config = dev->config;
struct spi_litex_data *data = dev->data;
struct spi_context *ctx = &data->ctx;
uint8_t len;
uint32_t txd = 0U;
len = MIN(spi_context_max_continuous_chunk(ctx), dev_config->master_rxtx_size);
if (len != data->len) {
spiflash_len_mask_width_write(len * 8, SPI_LITEX_WIDTH, SPI_LITEX_MASK,
dev_config->master_phyconfig_addr);
data->len = len;
}
if (spi_context_tx_buf_on(ctx)) {
litex_spi_tx_put(len, &txd, ctx->tx_buf);
}
LOG_DBG("txd: 0x%x", txd);
litex_write32(txd, dev_config->master_rxtx_addr);
spi_context_update_tx(ctx, data->dfs, len / data->dfs);
}
static void spi_litex_spi_do_rx(const struct device *dev)
{
const struct spi_litex_dev_config *dev_config = dev->config;
struct spi_litex_data *data = dev->data;
struct spi_context *ctx = &data->ctx;
uint32_t rxd;
rxd = litex_read32(dev_config->master_rxtx_addr);
LOG_DBG("rxd: 0x%x", rxd);
if (spi_context_rx_buf_on(ctx)) {
litex_spi_rx_put(data->len, &rxd, ctx->rx_buf);
}
spi_context_update_rx(ctx, data->dfs, data->len / data->dfs);
}
static int spi_litex_xfer(const struct device *dev, const struct spi_config *config)
{
const struct spi_litex_dev_config *dev_config = dev->config;
struct spi_litex_data *data = dev->data;
struct spi_context *ctx = &data->ctx;
uint32_t rxd;
litex_write32(BIT(config->slave), dev_config->master_cs_addr);
/* Flush RX buffer */
while ((litex_read8(dev_config->master_status_addr) &
BIT(SPIFLASH_MASTER_STATUS_RX_READY_OFFSET))) {
rxd = litex_read32(dev_config->master_rxtx_addr);
LOG_DBG("flushed rxd: 0x%x", rxd);
}
while (!(litex_read8(dev_config->master_status_addr) &
BIT(SPIFLASH_MASTER_STATUS_TX_READY_OFFSET))) {
(void)litex_read32(dev_config->master_rxtx_addr);
}
#if SPI_LITEX_ANY_HAS_IRQ
if (SPI_LITEX_HAS_IRQ) {
litex_write8(BIT(0), dev_config->master_ev_pending_addr);
litex_write8(BIT(0), dev_config->master_ev_enable_addr);
spi_litex_spi_do_tx(dev);
return spi_context_wait_for_completion(ctx);
}
#endif /* SPI_LITEX_ANY_HAS_IRQ */
do {
spi_litex_spi_do_tx(dev);
while (!(litex_read8(dev_config->master_status_addr) &
BIT(SPIFLASH_MASTER_STATUS_RX_READY_OFFSET))) {
/* Wait for RX data */
}
spi_litex_spi_do_rx(dev);
} while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx));
if ((config->operation & SPI_HOLD_ON_CS) == 0) {
litex_write32(0, dev_config->master_cs_addr);
}
spi_context_complete(ctx, dev, 0);
return 0;
}
static int transceive(const struct device *dev,
const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
bool asynchronous,
spi_callback_t cb,
void *userdata)
{
struct spi_litex_data *data = dev->data;
int ret;
if (!tx_bufs && !rx_bufs) {
return 0;
}
spi_context_lock(&data->ctx, asynchronous, cb, userdata, config);
ret = spi_config(dev, config);
if (ret < 0) {
goto end;
}
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, data->dfs);
ret = spi_litex_xfer(dev, config);
end:
spi_context_release(&data->ctx, ret);
return ret;
}
static int spi_litex_transceive(const struct device *dev,
const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs)
{
return transceive(dev, config, tx_bufs, rx_bufs, false, NULL, NULL);
}
#ifdef CONFIG_SPI_ASYNC
static int spi_litex_transceive_async(const struct device *dev,
const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
spi_callback_t cb,
void *userdata)
{
#if SPI_LITEX_ANY_HAS_IRQ
#if !SPI_LITEX_ALL_HAS_IRQ
const struct spi_litex_dev_config *dev_config = dev->config;
#endif /* !SPI_LITEX_ALL_HAS_IRQ */
if (SPI_LITEX_HAS_IRQ) {
return transceive(dev, config, tx_bufs, rx_bufs, true, cb, userdata);
}
#endif /* SPI_LITEX_ANY_HAS_IRQ */
return -ENOTSUP;
}
#endif /* CONFIG_SPI_ASYNC */
static int spi_litex_release(const struct device *dev, const struct spi_config *config)
{
struct spi_litex_data *data = dev->data;
const struct spi_litex_dev_config *dev_config = dev->config;
if (!spi_context_configured(&data->ctx, config)) {
return -EINVAL;
}
litex_write32(0, dev_config->master_cs_addr);
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
#if SPI_LITEX_ANY_HAS_IRQ
static void spi_litex_irq_handler(const struct device *dev)
{
struct spi_litex_data *data = dev->data;
const struct spi_litex_dev_config *dev_config = dev->config;
struct spi_context *ctx = &data->ctx;
if (litex_read8(dev_config->master_ev_pending_addr) & BIT(0)) {
spi_litex_spi_do_rx(dev);
/* ack reader irq */
litex_write8(BIT(0), dev_config->master_ev_pending_addr);
if (spi_context_tx_on(ctx) || spi_context_rx_on(ctx)) {
spi_litex_spi_do_tx(dev);
} else {
litex_write8(0, dev_config->master_ev_enable_addr);
if ((ctx->config->operation & SPI_HOLD_ON_CS) == 0) {
litex_write32(0, dev_config->master_cs_addr);
}
spi_context_complete(ctx, dev, 0);
}
}
}
#endif /* SPI_LITEX_ANY_HAS_IRQ */
static int spi_litex_init(const struct device *dev)
{
const struct spi_litex_dev_config *dev_config = dev->config;
struct spi_litex_data *data = dev->data;
#if SPI_LITEX_ANY_HAS_IRQ
if (SPI_LITEX_HAS_IRQ) {
dev_config->irq_config_func(dev);
}
#endif /* SPI_LITEX_ANY_HAS_IRQ */
data->len = dev_config->master_rxtx_size;
spiflash_len_mask_width_write(data->len * 8, SPI_LITEX_WIDTH, SPI_LITEX_MASK,
dev_config->master_phyconfig_addr);
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
/* Device Instantiation */
static DEVICE_API(spi, spi_litex_api) = {
.transceive = spi_litex_transceive,
#ifdef CONFIG_SPI_ASYNC
.transceive_async = spi_litex_transceive_async,
#endif /* CONFIG_SPI_ASYNC */
#ifdef CONFIG_SPI_RTIO
.iodev_submit = spi_rtio_iodev_default_submit,
#endif
.release = spi_litex_release,
};
#define SPI_LITEX_IRQ(n) \
BUILD_ASSERT(DT_INST_REG_HAS_NAME(n, master_ev_pending) && \
DT_INST_REG_HAS_NAME(n, master_ev_enable), \
"registers for interrupts missing"); \
\
static void spi_litex_irq_config##n(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), spi_litex_irq_handler, \
DEVICE_DT_INST_GET(n), 0); \
\
irq_enable(DT_INST_IRQN(n)); \
};
#define SPI_LITEX_IRQ_CONFIG(n) \
.irq_config_func = COND_CODE_1(DT_INST_IRQ_HAS_IDX(n, 0), \
(spi_litex_irq_config##n), (NULL)), \
.master_ev_pending_addr = DT_INST_REG_ADDR_BY_NAME_OR(n, master_ev_pending, 0), \
.master_ev_enable_addr = DT_INST_REG_ADDR_BY_NAME_OR(n, master_ev_enable, 0), \
IF_DISABLED(SPI_LITEX_ALL_HAS_IRQ, (.has_irq = DT_INST_IRQ_HAS_IDX(n, 0),))
#define SPI_INIT(n) \
IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, 0), (SPI_LITEX_IRQ(n))) \
\
static struct spi_litex_data spi_litex_data_##n = { \
SPI_CONTEXT_INIT_LOCK(spi_litex_data_##n, ctx), \
SPI_CONTEXT_INIT_SYNC(spi_litex_data_##n, ctx), \
}; \
\
static struct spi_litex_dev_config spi_litex_cfg_##n = { \
.master_cs_addr = DT_INST_REG_ADDR_BY_NAME(n, master_cs), \
.master_phyconfig_addr = DT_INST_REG_ADDR_BY_NAME(n, master_phyconfig), \
.master_rxtx_addr = DT_INST_REG_ADDR_BY_NAME(n, master_rxtx), \
.master_rxtx_size = DT_INST_REG_SIZE_BY_NAME(n, master_rxtx), \
.master_status_addr = DT_INST_REG_ADDR_BY_NAME(n, master_status), \
.phy_clk_divisor_exists = DT_INST_REG_HAS_NAME(n, phy_clk_divisor), \
.phy_clk_divisor_addr = DT_INST_REG_ADDR_BY_NAME_OR(n, phy_clk_divisor, 0), \
IF_ENABLED(SPI_LITEX_ANY_HAS_IRQ, (SPI_LITEX_IRQ_CONFIG(n))) \
}; \
\
SPI_DEVICE_DT_INST_DEFINE(n, spi_litex_init, NULL, &spi_litex_data_##n, \
&spi_litex_cfg_##n, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \
&spi_litex_api);
DT_INST_FOREACH_STATUS_OKAY(SPI_INIT)