485 lines
15 KiB
C
485 lines
15 KiB
C
/*
|
|
* Copyright (c) 2022 Microchip Technology Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT microchip_mpfs_spi
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/spi.h>
|
|
#include <zephyr/drivers/spi/rtio.h>
|
|
#include <zephyr/sys/sys_io.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
|
|
LOG_MODULE_REGISTER(mss_spi, CONFIG_SPI_LOG_LEVEL);
|
|
|
|
#include "spi_context.h"
|
|
|
|
/* MSS SPI Register offsets */
|
|
#define MSS_SPI_REG_CONTROL (0x00)
|
|
#define MSS_SPI_REG_TXRXDF_SIZE (0x04)
|
|
#define MSS_SPI_REG_STATUS (0x08)
|
|
#define MSS_SPI_REG_INT_CLEAR (0x0c)
|
|
#define MSS_SPI_REG_RX_DATA (0x10)
|
|
#define MSS_SPI_REG_TX_DATA (0x14)
|
|
#define MSS_SPI_REG_CLK_GEN (0x18)
|
|
#define MSS_SPI_REG_SS (0x1c)
|
|
#define MSS_SPI_REG_MIS (0x20)
|
|
#define MSS_SPI_REG_RIS (0x24)
|
|
#define MSS_SPI_REG_CONTROL2 (0x28)
|
|
#define MSS_SPI_REG_COMMAND (0x2c)
|
|
#define MSS_SPI_REG_PKTSIZE (0x30)
|
|
#define MSS_SPI_REG_CMD_SIZE (0x34)
|
|
#define MSS_SPI_REG_HWSTATUS (0x38)
|
|
#define MSS_SPI_REG_FRAMESUP (0x50)
|
|
|
|
/* SPICR bit definitions */
|
|
#define MSS_SPI_CONTROL_ENABLE BIT(0)
|
|
#define MSS_SPI_CONTROL_MASTER BIT(1)
|
|
#define MSS_SPI_CONTROL_PROTO_MSK BIT(2)
|
|
#define MSS_SPI_CONTROL_PROTO_MOTO (0 << 2)
|
|
#define MSS_SPI_CONTROL_RX_DATA_INT BIT(4)
|
|
#define MSS_SPI_CONTROL_TX_DATA_INT BIT(5)
|
|
#define MSS_SPI_CONTROL_RX_OVER_INT BIT(6)
|
|
#define MSS_SPI_CONTROL_TX_UNDER_INT BIT(7)
|
|
#define MSS_SPI_CONTROL_CNT_MSK (0xffff << 8)
|
|
#define MSS_SPI_CONTROL_CNT_SHF (8)
|
|
#define MSS_SPI_CONTROL_SPO BIT(24)
|
|
#define MSS_SPI_CONTROL_SPH BIT(25)
|
|
#define MSS_SPI_CONTROL_SPS BIT(26)
|
|
#define MSS_SPI_CONTROL_FRAMEURUN BIT(27)
|
|
#define MSS_SPI_CONTROL_CLKMODE BIT(28)
|
|
#define MSS_SPI_CONTROL_BIGFIFO BIT(29)
|
|
#define MSS_SPI_CONTROL_OENOFF BIT(30)
|
|
#define MSS_SPI_CONTROL_RESET BIT(31)
|
|
|
|
/* SPIFRAMESIZE bit definitions */
|
|
#define MSS_SPI_FRAMESIZE_DEFAULT (8)
|
|
|
|
/* SPISS bit definitions */
|
|
#define MSS_SPI_SSEL_MASK (0xff)
|
|
#define MSS_SPI_DIRECT (0x100)
|
|
#define MSS_SPI_SSELOUT (0x200)
|
|
#define MSS_SPI_MIN_SLAVE (0)
|
|
#define MSS_SPI_MAX_SLAVE (7)
|
|
|
|
/* SPIST bit definitions */
|
|
#define MSS_SPI_STATUS_ACTIVE BIT(14)
|
|
#define MSS_SPI_STATUS_SSEL BIT(13)
|
|
#define MSS_SPI_STATUS_FRAMESTART BIT(12)
|
|
#define MSS_SPI_STATUS_TXFIFO_EMPTY_NEXT_READ BIT(11)
|
|
#define MSS_SPI_STATUS_TXFIFO_EMPTY BIT(10)
|
|
#define MSS_SPI_STATUS_TXFIFO_FULL_NEXT_WRITE BIT(9)
|
|
#define MSS_SPI_STATUS_TXFIFO_FULL BIT(8)
|
|
#define MSS_SPI_STATUS_RXFIFO_EMPTY_NEXT_READ BIT(7)
|
|
#define MSS_SPI_STATUS_RXFIFO_EMPTY BIT(6)
|
|
#define MSS_SPI_STATUS_RXFIFO_FULL_NEXT_WRITE BIT(5)
|
|
#define MSS_SPI_STATUS_RXFIFO_FULL BIT(4)
|
|
#define MSS_SPI_STATUS_TX_UNDERRUN BIT(3)
|
|
#define MSS_SPI_STATUS_RX_OVERFLOW BIT(2)
|
|
#define MSS_SPI_STATUS_RXDAT_RCED BIT(1)
|
|
#define MSS_SPI_STATUS_TXDAT_SENT BIT(0)
|
|
|
|
/* SPIINT register defines */
|
|
#define MSS_SPI_INT_TXDONE BIT(0)
|
|
#define MSS_SPI_INT_RXRDY BIT(1)
|
|
#define MSS_SPI_INT_RX_CH_OVRFLW BIT(2)
|
|
#define MSS_SPI_INT_TX_CH_UNDRUN BIT(3)
|
|
#define MSS_SPI_INT_CMD BIT(4)
|
|
#define MSS_SPI_INT_SSEND BIT(5)
|
|
|
|
/* SPICOMMAND bit definitions */
|
|
#define MSS_SPI_COMMAND_FIFO_MASK (0xC)
|
|
|
|
/* SPIFRAMESUP bit definitions */
|
|
#define MSS_SPI_FRAMESUP_UP_BYTES_MSK (0xFFFF << 16)
|
|
#define MSS_SPI_FRAMESUP_LO_BYTES_MSK (0xFFFF << 0)
|
|
|
|
struct mss_spi_config {
|
|
mm_reg_t base;
|
|
uint8_t clk_gen;
|
|
int clock_freq;
|
|
};
|
|
|
|
struct mss_spi_transfer {
|
|
uint32_t rx_len;
|
|
uint32_t control;
|
|
};
|
|
|
|
struct mss_spi_data {
|
|
struct spi_context ctx;
|
|
struct mss_spi_transfer xfer;
|
|
};
|
|
|
|
static inline uint32_t mss_spi_read(const struct mss_spi_config *cfg, mm_reg_t offset)
|
|
{
|
|
return sys_read32(cfg->base + offset);
|
|
}
|
|
|
|
static inline void mss_spi_write(const struct mss_spi_config *cfg, mm_reg_t offset, uint32_t val)
|
|
{
|
|
sys_write32(val, cfg->base + offset);
|
|
}
|
|
|
|
static inline void mss_spi_hw_tfsz_set(const struct mss_spi_config *cfg, int len)
|
|
{
|
|
uint32_t control;
|
|
|
|
mss_spi_write(cfg, MSS_SPI_REG_FRAMESUP, (len & MSS_SPI_FRAMESUP_UP_BYTES_MSK));
|
|
control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
control &= ~MSS_SPI_CONTROL_CNT_MSK;
|
|
control |= ((len & MSS_SPI_FRAMESUP_LO_BYTES_MSK) << MSS_SPI_CONTROL_CNT_SHF);
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
|
|
}
|
|
|
|
static inline void mss_spi_enable_controller(const struct mss_spi_config *cfg)
|
|
{
|
|
uint32_t control;
|
|
|
|
control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
control |= MSS_SPI_CONTROL_ENABLE;
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
|
|
}
|
|
|
|
static inline void mss_spi_disable_controller(const struct mss_spi_config *cfg)
|
|
{
|
|
uint32_t control;
|
|
|
|
control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
control &= ~MSS_SPI_CONTROL_ENABLE;
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
|
|
}
|
|
|
|
static void mss_spi_enable_ints(const struct mss_spi_config *cfg)
|
|
{
|
|
uint32_t control;
|
|
uint32_t mask = MSS_SPI_CONTROL_RX_DATA_INT | MSS_SPI_CONTROL_TX_DATA_INT |
|
|
MSS_SPI_CONTROL_RX_OVER_INT | MSS_SPI_CONTROL_TX_UNDER_INT;
|
|
|
|
control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
control |= mask;
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
|
|
}
|
|
|
|
static void mss_spi_disable_ints(const struct mss_spi_config *cfg)
|
|
{
|
|
uint32_t control;
|
|
uint32_t mask = MSS_SPI_CONTROL_RX_DATA_INT | MSS_SPI_CONTROL_TX_DATA_INT |
|
|
MSS_SPI_CONTROL_RX_OVER_INT | MSS_SPI_CONTROL_TX_UNDER_INT;
|
|
|
|
mask = ~mask;
|
|
control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
control &= ~mask;
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
|
|
}
|
|
|
|
static inline void mss_spi_readwr_fifo(const struct device *dev)
|
|
{
|
|
const struct mss_spi_config *cfg = dev->config;
|
|
struct mss_spi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
struct mss_spi_transfer *xfer = &data->xfer;
|
|
uint32_t rx_raw = 0, rd_byte_size, tr_len;
|
|
uint32_t data8, transfer_idx = 0;
|
|
int count;
|
|
|
|
tr_len = spi_context_longest_current_buf(ctx);
|
|
count = spi_context_total_tx_len(ctx);
|
|
if (ctx->rx_buf) {
|
|
rd_byte_size = count - tr_len;
|
|
} else {
|
|
rd_byte_size = 0;
|
|
}
|
|
mss_spi_hw_tfsz_set(cfg, count);
|
|
|
|
mss_spi_enable_ints(cfg);
|
|
spi_context_update_rx(ctx, 1, xfer->rx_len);
|
|
while (transfer_idx < count) {
|
|
if (!(mss_spi_read(cfg, MSS_SPI_REG_STATUS) & MSS_SPI_STATUS_RXFIFO_EMPTY)) {
|
|
rx_raw = mss_spi_read(cfg, MSS_SPI_REG_RX_DATA);
|
|
if (transfer_idx >= tr_len) {
|
|
if (spi_context_rx_buf_on(ctx)) {
|
|
UNALIGNED_PUT(rx_raw, (uint8_t *)ctx->rx_buf);
|
|
spi_context_update_rx(ctx, 1, 1);
|
|
}
|
|
}
|
|
++transfer_idx;
|
|
}
|
|
|
|
if (!(mss_spi_read(cfg, MSS_SPI_REG_STATUS) & MSS_SPI_STATUS_TXFIFO_FULL)) {
|
|
if (spi_context_tx_buf_on(ctx)) {
|
|
data8 = ctx->tx_buf[0];
|
|
mss_spi_write(cfg, MSS_SPI_REG_TX_DATA, data8);
|
|
spi_context_update_tx(ctx, 1, 1);
|
|
} else {
|
|
mss_spi_write(cfg, MSS_SPI_REG_TX_DATA, 0x0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline int mss_spi_select_slave(const struct mss_spi_config *cfg, int cs)
|
|
{
|
|
uint32_t slave;
|
|
uint32_t reg = mss_spi_read(cfg, MSS_SPI_REG_SS);
|
|
|
|
slave = (cs >= MSS_SPI_MIN_SLAVE && cs <= MSS_SPI_MAX_SLAVE) ? (1 << cs) : 0;
|
|
reg &= ~MSS_SPI_SSEL_MASK;
|
|
reg |= slave;
|
|
|
|
mss_spi_write(cfg, MSS_SPI_REG_SS, reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void mss_spi_activate_cs(struct mss_spi_config *cfg)
|
|
{
|
|
uint32_t reg = mss_spi_read(cfg, MSS_SPI_REG_SS);
|
|
|
|
reg |= MSS_SPI_SSELOUT;
|
|
mss_spi_write(cfg, MSS_SPI_REG_SS, reg);
|
|
}
|
|
|
|
static inline void mss_spi_deactivate_cs(const struct mss_spi_config *cfg)
|
|
{
|
|
uint32_t reg = mss_spi_read(cfg, MSS_SPI_REG_SS);
|
|
|
|
reg &= ~MSS_SPI_SSELOUT;
|
|
mss_spi_write(cfg, MSS_SPI_REG_SS, reg);
|
|
}
|
|
|
|
static inline int mss_spi_clk_gen_set(const struct mss_spi_config *cfg,
|
|
const struct spi_config *spi_cfg)
|
|
{
|
|
uint32_t idx, clkrate, val = 0, speed;
|
|
|
|
if (spi_cfg->frequency > cfg->clock_freq) {
|
|
speed = cfg->clock_freq / 2;
|
|
}
|
|
|
|
for (idx = 1; idx < 16; idx++) {
|
|
clkrate = cfg->clock_freq / (2 * idx);
|
|
if (clkrate <= spi_cfg->frequency) {
|
|
val = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mss_spi_write(cfg, MSS_SPI_REG_CLK_GEN, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int mss_spi_hw_mode_set(const struct mss_spi_config *cfg, unsigned int mode)
|
|
{
|
|
uint32_t control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
|
|
/* set the mode */
|
|
if (mode & SPI_MODE_CPHA) {
|
|
control |= MSS_SPI_CONTROL_SPH;
|
|
} else {
|
|
control &= ~MSS_SPI_CONTROL_SPH;
|
|
}
|
|
|
|
if (mode & SPI_MODE_CPOL) {
|
|
control |= MSS_SPI_CONTROL_SPO;
|
|
} else {
|
|
control &= ~MSS_SPI_CONTROL_SPO;
|
|
}
|
|
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mss_spi_interrupt(const struct device *dev)
|
|
{
|
|
const struct mss_spi_config *cfg = dev->config;
|
|
struct mss_spi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
int intfield = mss_spi_read(cfg, MSS_SPI_REG_MIS) & 0xf;
|
|
|
|
if (intfield == 0) {
|
|
return;
|
|
}
|
|
|
|
mss_spi_write(cfg, MSS_SPI_REG_INT_CLEAR, intfield);
|
|
spi_context_complete(ctx, dev, 0);
|
|
}
|
|
|
|
static int mss_spi_release(const struct device *dev, const struct spi_config *config)
|
|
{
|
|
const struct mss_spi_config *cfg = dev->config;
|
|
struct mss_spi_data *data = dev->data;
|
|
|
|
mss_spi_disable_ints(cfg);
|
|
|
|
/* release kernel resources */
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
mss_spi_disable_controller(cfg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mss_spi_configure(const struct device *dev, const struct spi_config *spi_cfg)
|
|
{
|
|
const struct mss_spi_config *cfg = dev->config;
|
|
struct mss_spi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
struct mss_spi_transfer *xfer = &data->xfer;
|
|
uint32_t control;
|
|
|
|
if (spi_cfg->operation & (SPI_TRANSFER_LSB | SPI_OP_MODE_SLAVE | SPI_MODE_LOOP)) {
|
|
LOG_WRN("not supported operation\n\r");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (SPI_WORD_SIZE_GET(spi_cfg->operation) != MSS_SPI_FRAMESIZE_DEFAULT) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
ctx->config = spi_cfg;
|
|
mss_spi_select_slave(cfg, spi_cfg->slave);
|
|
control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
|
|
/*
|
|
* Fill up the default values
|
|
* Slave select behaviour set
|
|
* Fifo depth greater than 4 frames
|
|
* Methodology to calculate SPI Clock:
|
|
* 0: SPICLK = 1 / (2 CLK_GEN + 1) , CLK_GEN is from 0 to 15
|
|
* 1: SPICLK = 1 / (2 * (CLK_GEN + 1)) , CLK_GEN is from 0 to 255
|
|
*/
|
|
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, xfer->control);
|
|
|
|
if (mss_spi_clk_gen_set(cfg, spi_cfg)) {
|
|
LOG_ERR("can't set clk divider\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mss_spi_hw_mode_set(cfg, spi_cfg->operation);
|
|
mss_spi_write(cfg, MSS_SPI_REG_TXRXDF_SIZE, MSS_SPI_FRAMESIZE_DEFAULT);
|
|
mss_spi_enable_controller(cfg);
|
|
mss_spi_write(cfg, MSS_SPI_REG_COMMAND, MSS_SPI_COMMAND_FIFO_MASK);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mss_spi_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, spi_callback_t cb, void *userdata)
|
|
{
|
|
|
|
const struct mss_spi_config *config = dev->config;
|
|
struct mss_spi_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
struct mss_spi_transfer *xfer = &data->xfer;
|
|
int ret = 0;
|
|
|
|
spi_context_lock(ctx, async, cb, userdata, spi_cfg);
|
|
|
|
ret = mss_spi_configure(dev, spi_cfg);
|
|
if (ret) {
|
|
LOG_ERR("Fail to configure\n\r");
|
|
goto out;
|
|
}
|
|
|
|
spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1);
|
|
xfer->rx_len = ctx->rx_len;
|
|
mss_spi_readwr_fifo(dev);
|
|
ret = spi_context_wait_for_completion(ctx);
|
|
out:
|
|
spi_context_release(ctx, ret);
|
|
mss_spi_disable_ints(config);
|
|
mss_spi_disable_controller(config);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mss_spi_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 mss_spi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL);
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
static int mss_spi_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 mss_spi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata);
|
|
}
|
|
#endif /* CONFIG_SPI_ASYNC */
|
|
|
|
static int mss_spi_init(const struct device *dev)
|
|
{
|
|
const struct mss_spi_config *cfg = dev->config;
|
|
struct mss_spi_data *data = dev->data;
|
|
struct mss_spi_transfer *xfer = &data->xfer;
|
|
int ret = 0;
|
|
uint32_t control = 0;
|
|
|
|
/* Remove SPI from Reset */
|
|
control = mss_spi_read(cfg, MSS_SPI_REG_CONTROL);
|
|
control &= ~MSS_SPI_CONTROL_RESET;
|
|
mss_spi_write(cfg, MSS_SPI_REG_CONTROL, control);
|
|
|
|
/* Set master mode */
|
|
mss_spi_disable_controller(cfg);
|
|
xfer->control = (MSS_SPI_CONTROL_SPS | MSS_SPI_CONTROL_BIGFIFO | MSS_SPI_CONTROL_MASTER |
|
|
MSS_SPI_CONTROL_CLKMODE);
|
|
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define MICROCHIP_SPI_PM_OPS (NULL)
|
|
|
|
static const struct spi_driver_api mss_spi_driver_api = {
|
|
.transceive = mss_spi_transceive_blocking,
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
.transceive_async = mss_spi_transceive_async,
|
|
#endif /* CONFIG_SPI_ASYNC */
|
|
#ifdef CONFIG_SPI_RTIO
|
|
.iodev_submit = spi_rtio_iodev_default_submit,
|
|
#endif
|
|
.release = mss_spi_release,
|
|
};
|
|
|
|
#define MSS_SPI_INIT(n) \
|
|
static int mss_spi_init_##n(const struct device *dev) \
|
|
{ \
|
|
mss_spi_init(dev); \
|
|
\
|
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), mss_spi_interrupt, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
\
|
|
irq_enable(DT_INST_IRQN(n)); \
|
|
\
|
|
return 0; \
|
|
} \
|
|
\
|
|
static const struct mss_spi_config mss_spi_config_##n = { \
|
|
.base = DT_INST_REG_ADDR(n), \
|
|
.clock_freq = DT_INST_PROP(n, clock_frequency), \
|
|
}; \
|
|
\
|
|
static struct mss_spi_data mss_spi_data_##n = { \
|
|
SPI_CONTEXT_INIT_LOCK(mss_spi_data_##n, ctx), \
|
|
SPI_CONTEXT_INIT_SYNC(mss_spi_data_##n, ctx), \
|
|
}; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, mss_spi_init_##n, NULL, &mss_spi_data_##n, &mss_spi_config_##n, \
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
|
&mss_spi_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(MSS_SPI_INIT)
|