From d4feadc3296a74f286da153937727cffd6095153 Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Sat, 28 Jan 2023 13:00:36 +0900 Subject: [PATCH] drivers: spi: pl022: Add support DMA transfer Add supporting DMA-based transfer for PL022 SPI. Signed-off-by: TOKITA Hiroshi --- drivers/spi/Kconfig.pl022 | 6 + drivers/spi/spi_pl022.c | 405 ++++++++++++++++++++++++++++++++------ 2 files changed, 352 insertions(+), 59 deletions(-) diff --git a/drivers/spi/Kconfig.pl022 b/drivers/spi/Kconfig.pl022 index c01485fe005..fa4cd4b0fd7 100644 --- a/drivers/spi/Kconfig.pl022 +++ b/drivers/spi/Kconfig.pl022 @@ -13,4 +13,10 @@ config SPI_PL022_INTERRUPT help Enables interrupt support for PL022 SPI driver. +config SPI_PL022_DMA + bool "PL022 DMA mode" + select DMA + help + Enables DMA support for PL022 SPI driver. + endif diff --git a/drivers/spi/spi_pl022.c b/drivers/spi/spi_pl022.c index 3408ca07c84..2b3e28ec4dd 100644 --- a/drivers/spi/spi_pl022.c +++ b/drivers/spi/spi_pl022.c @@ -10,10 +10,14 @@ #include #include #include +#include #include #if defined(CONFIG_PINCTRL) #include #endif +#if defined(CONFIG_SPI_PL022_DMA) +#include +#endif #define LOG_LEVEL CONFIG_SPI_LOG_LEVEL #include @@ -227,6 +231,7 @@ LOG_MODULE_REGISTER(spi_pl022); */ #define SSP_READ_REG(reg) (*((volatile uint32_t *)reg)) #define SSP_WRITE_REG(reg, val) (*((volatile uint32_t *)reg) = val) +#define SSP_CLEAR_REG(reg, val) (*((volatile uint32_t *)reg) &= ~(val)) /* * Status check macros @@ -236,6 +241,28 @@ LOG_MODULE_REGISTER(spi_pl022); #define SSP_TX_FIFO_EMPTY(reg) (SSP_READ_REG(SSP_SR(reg)) & SSP_SR_MASK_TFE) #define SSP_TX_FIFO_NOT_FULL(reg) (SSP_READ_REG(SSP_SR(reg)) & SSP_SR_MASK_TNF) +#if defined(CONFIG_SPI_PL022_DMA) +enum spi_pl022_dma_direction { + TX = 0, + RX, + NUM_OF_DIRECTION +}; + +struct spi_pl022_dma_config { + const struct device *dev; + uint32_t channel; + uint32_t channel_config; + uint32_t slot; +}; + +struct spi_pl022_dma_data { + struct dma_config config; + struct dma_block_config block; + uint32_t count; + bool callbacked; +}; +#endif + /* * Max frequency */ @@ -245,20 +272,33 @@ LOG_MODULE_REGISTER(spi_pl022); struct spi_pl022_cfg { const uint32_t reg; const uint32_t pclk; + const bool dma_enabled; #if defined(CONFIG_PINCTRL) const struct pinctrl_dev_config *pincfg; #endif #if defined(CONFIG_SPI_PL022_INTERRUPT) void (*irq_config)(const struct device *port); #endif +#if defined(CONFIG_SPI_PL022_DMA) + const struct spi_pl022_dma_config dma[NUM_OF_DIRECTION]; +#endif }; struct spi_pl022_data { struct spi_context ctx; uint32_t tx_count; uint32_t rx_count; + struct k_spinlock lock; +#if defined(CONFIG_SPI_PL022_DMA) + struct spi_pl022_dma_data dma[NUM_OF_DIRECTION]; +#endif }; +#if defined(CONFIG_SPI_PL022_DMA) +static uint32_t dummy_tx; +static uint32_t dummy_rx; +#endif + /* Helper Functions */ static inline uint32_t spi_pl022_calc_prescale(const uint32_t pclk, const uint32_t baud) @@ -351,8 +391,10 @@ static int spi_pl022_configure(const struct device *dev, SSP_WRITE_REG(SSP_CR1(cfg->reg), cr1); #if defined(CONFIG_SPI_PL022_INTERRUPT) - SSP_WRITE_REG(SSP_IMSC(cfg->reg), - SSP_IMSC_MASK_RORIM | SSP_IMSC_MASK_RTIM | SSP_IMSC_MASK_RXIM); + if (!cfg->dma_enabled) { + SSP_WRITE_REG(SSP_IMSC(cfg->reg), + SSP_IMSC_MASK_RORIM | SSP_IMSC_MASK_RTIM | SSP_IMSC_MASK_RXIM); + } #endif data->ctx.config = spicfg; @@ -365,6 +407,209 @@ static inline bool spi_pl022_transfer_ongoing(struct spi_pl022_data *data) return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx); } +#if defined(CONFIG_SPI_PL022_DMA) +static void spi_pl022_dma_callback(const struct device *dma_dev, void *arg, uint32_t channel, + int status); + +static size_t spi_pl022_dma_enabled_num(const struct device *dev) +{ + const struct spi_pl022_cfg *cfg = dev->config; + + return cfg->dma_enabled ? 2 : 0; +} + +static uint32_t spi_pl022_dma_setup(const struct device *dev, const uint32_t dir) +{ + const struct spi_pl022_cfg *cfg = dev->config; + struct spi_pl022_data *data = dev->data; + struct dma_config *dma_cfg = &data->dma[dir].config; + struct dma_block_config *block_cfg = &data->dma[dir].block; + const struct spi_pl022_dma_config *dma = &cfg->dma[dir]; + int ret; + + memset(dma_cfg, 0, sizeof(struct dma_config)); + memset(block_cfg, 0, sizeof(struct dma_block_config)); + + dma_cfg->source_burst_length = 1; + dma_cfg->dest_burst_length = 1; + dma_cfg->user_data = (void *)dev; + dma_cfg->block_count = 1U; + dma_cfg->head_block = block_cfg; + dma_cfg->dma_slot = cfg->dma[dir].slot; + dma_cfg->channel_direction = dir == TX ? MEMORY_TO_PERIPHERAL : PERIPHERAL_TO_MEMORY; + + if (SPI_WORD_SIZE_GET(data->ctx.config->operation) == 8) { + dma_cfg->source_data_size = 1; + dma_cfg->dest_data_size = 1; + } else { + dma_cfg->source_data_size = 2; + dma_cfg->dest_data_size = 2; + } + + block_cfg->block_size = spi_context_max_continuous_chunk(&data->ctx); + + if (dir == TX) { + dma_cfg->dma_callback = spi_pl022_dma_callback; + block_cfg->dest_address = SSP_DR(cfg->reg); + block_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; + if (spi_context_tx_buf_on(&data->ctx)) { + block_cfg->source_address = (uint32_t)data->ctx.tx_buf; + block_cfg->source_addr_adj = DMA_ADDR_ADJ_INCREMENT; + } else { + block_cfg->source_address = (uint32_t)&dummy_tx; + block_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; + } + } + + if (dir == RX) { + dma_cfg->dma_callback = spi_pl022_dma_callback; + block_cfg->source_address = SSP_DR(cfg->reg); + block_cfg->source_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; + + if (spi_context_rx_buf_on(&data->ctx)) { + block_cfg->dest_address = (uint32_t)data->ctx.rx_buf; + block_cfg->dest_addr_adj = DMA_ADDR_ADJ_INCREMENT; + } else { + block_cfg->dest_address = (uint32_t)&dummy_rx; + block_cfg->dest_addr_adj = DMA_ADDR_ADJ_NO_CHANGE; + } + } + + ret = dma_config(dma->dev, dma->channel, dma_cfg); + if (ret < 0) { + LOG_ERR("dma_config %p failed %d\n", dma->dev, ret); + return ret; + } + + data->dma[dir].callbacked = false; + + ret = dma_start(dma->dev, dma->channel); + if (ret < 0) { + LOG_ERR("dma_start %p failed %d\n", dma->dev, ret); + return ret; + } + + return 0; +} + +static int spi_pl022_start_dma_transceive(const struct device *dev) +{ + const struct spi_pl022_cfg *cfg = dev->config; + int ret = 0; + + SSP_CLEAR_REG(SSP_DMACR(cfg->reg), SSP_DMACR_MASK_RXDMAE | SSP_DMACR_MASK_TXDMAE); + + for (size_t i = 0; i < spi_pl022_dma_enabled_num(dev); i++) { + ret = spi_pl022_dma_setup(dev, i); + if (ret < 0) { + goto on_error; + } + } + + SSP_WRITE_REG(SSP_DMACR(cfg->reg), SSP_DMACR_MASK_RXDMAE | SSP_DMACR_MASK_TXDMAE); + +on_error: + if (ret < 0) { + for (size_t i = 0; i < spi_pl022_dma_enabled_num(dev); i++) { + dma_stop(cfg->dma[i].dev, cfg->dma[i].channel); + } + } + return ret; +} + +static bool spi_pl022_chunk_transfer_finished(const struct device *dev) +{ + struct spi_pl022_data *data = dev->data; + struct spi_pl022_dma_data *dma = data->dma; + const size_t chunk_len = spi_context_max_continuous_chunk(&data->ctx); + + return (MIN(dma[TX].count, dma[RX].count) >= chunk_len); +} + +static void spi_pl022_complete(const struct device *dev, int status) +{ + struct spi_pl022_data *data = dev->data; + const struct spi_pl022_cfg *cfg = dev->config; + + for (size_t i = 0; i < spi_pl022_dma_enabled_num(dev); i++) { + dma_stop(cfg->dma[i].dev, cfg->dma[i].channel); + } + + spi_context_complete(&data->ctx, dev, status); +} + +static void spi_pl022_dma_callback(const struct device *dma_dev, void *arg, uint32_t channel, + int status) +{ + const struct device *dev = (const struct device *)arg; + const struct spi_pl022_cfg *cfg = dev->config; + struct spi_pl022_data *data = dev->data; + bool complete = false; + k_spinlock_key_t key; + size_t chunk_len; + int err = 0; + + if (status < 0) { + key = k_spin_lock(&data->lock); + + LOG_ERR("dma:%p ch:%d callback gets error: %d", dma_dev, channel, status); + spi_pl022_complete(dev, status); + + k_spin_unlock(&data->lock, key); + return; + } + + key = k_spin_lock(&data->lock); + + chunk_len = spi_context_max_continuous_chunk(&data->ctx); + for (size_t i = 0; i < ARRAY_SIZE(cfg->dma); i++) { + if (dma_dev == cfg->dma[i].dev && channel == cfg->dma[i].channel) { + data->dma[i].count += chunk_len; + data->dma[i].callbacked = true; + } + } + /* Check transfer finished. + * The transmission of this chunk is complete if both the dma[TX].count + * and the dma[RX].count reach greater than or equal to the chunk_len. + * chunk_len is zero here means the transfer is already complete. + */ + if (spi_pl022_chunk_transfer_finished(dev)) { + if (SPI_WORD_SIZE_GET(data->ctx.config->operation) == 8) { + spi_context_update_tx(&data->ctx, 1, chunk_len); + spi_context_update_rx(&data->ctx, 1, chunk_len); + } else { + spi_context_update_tx(&data->ctx, 2, chunk_len); + spi_context_update_rx(&data->ctx, 2, chunk_len); + } + + if (spi_pl022_transfer_ongoing(data)) { + /* Next chunk is available, reset the count and + * continue processing + */ + data->dma[TX].count = 0; + data->dma[RX].count = 0; + } else { + /* All data is processed, complete the process */ + complete = true; + } + } + + if (!complete && data->dma[TX].callbacked && data->dma[RX].callbacked) { + err = spi_pl022_start_dma_transceive(dev); + if (err) { + complete = true; + } + } + + if (complete) { + spi_pl022_complete(dev, err); + } + + k_spin_unlock(&data->lock, key); +} + +#endif /* DMA */ + #if defined(CONFIG_SPI_PL022_INTERRUPT) static void spi_pl022_async_xfer(const struct device *dev) @@ -522,6 +767,7 @@ static int spi_pl022_transceive_impl(const struct device *dev, spi_callback_t cb, void *userdata) { + const struct spi_pl022_cfg *cfg = dev->config; struct spi_pl022_data *data = dev->data; struct spi_context *ctx = &data->ctx; int ret; @@ -537,19 +783,46 @@ static int spi_pl022_transceive_impl(const struct device *dev, spi_context_cs_control(ctx, true); -#if defined(CONFIG_SPI_PL022_INTERRUPT) - spi_pl022_start_async_xfer(dev); - ret = spi_context_wait_for_completion(ctx); -#else - do { - spi_pl022_xfer(dev); - spi_context_update_tx(ctx, 1, data->tx_count); - spi_context_update_rx(ctx, 1, data->rx_count); - } while (spi_pl022_transfer_ongoing(data)); + if (cfg->dma_enabled) { +#if defined(CONFIG_SPI_PL022_DMA) + for (size_t i = 0; i < ARRAY_SIZE(data->dma); i++) { + const struct spi_pl022_cfg *cfg = dev->config; + struct dma_status stat = {.busy = true}; -#ifdef CONFIG_SPI_ASYNC - spi_context_complete(&data->ctx, dev, ret); + dma_stop(cfg->dma[i].dev, cfg->dma[i].channel); + + while (stat.busy) { + dma_get_status(cfg->dma[i].dev, cfg->dma[i].channel, &stat); + } + + data->dma[i].count = 0; + } + + ret = spi_pl022_start_dma_transceive(dev); + if (ret < 0) { + spi_context_cs_control(ctx, false); + goto error; + } + ret = spi_context_wait_for_completion(ctx); #endif + } else +#if defined(CONFIG_SPI_PL022_INTERRUPT) + { + spi_pl022_start_async_xfer(dev); + ret = spi_context_wait_for_completion(ctx); + } +#else + { + do { + spi_pl022_xfer(dev); + spi_context_update_tx(ctx, 1, data->tx_count); + spi_context_update_rx(ctx, 1, data->rx_count); + } while (spi_pl022_transfer_ongoing(data)); + +#if defined(CONFIG_SPI_ASYNC) + spi_context_complete(&data->ctx, dev, ret); +#endif + } #endif spi_context_cs_control(ctx, false); @@ -610,12 +883,11 @@ static int spi_pl022_init(const struct device *dev) .operation = SPI_WORD_SET(8), .slave = 0, }; + const struct spi_pl022_cfg *cfg = dev->config; struct spi_pl022_data *data = dev->data; int ret; #if defined(CONFIG_PINCTRL) - const struct spi_pl022_cfg *cfg = dev->config; - ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT); if (ret) { LOG_ERR("Failed to apply pinctrl state"); @@ -623,9 +895,28 @@ static int spi_pl022_init(const struct device *dev) } #endif -#if defined(CONFIG_SPI_PL022_INTERRUPT) - cfg->irq_config(dev); + if (cfg->dma_enabled) { +#if defined(CONFIG_SPI_PL022_DMA) + for (size_t i = 0; i < spi_pl022_dma_enabled_num(dev); i++) { + uint32_t ch_filter = BIT(cfg->dma[i].channel); + + if (!device_is_ready(cfg->dma[i].dev)) { + LOG_ERR("DMA %s not ready", cfg->dma[i].dev->name); + return -ENODEV; + } + + ret = dma_request_channel(cfg->dma[i].dev, &ch_filter); + if (ret < 0) { + LOG_ERR("dma_request_channel failed %d", ret); + return ret; + } + } #endif + } else { +#if defined(CONFIG_SPI_PL022_INTERRUPT) + cfg->irq_config(dev); +#endif + } ret = spi_pl022_configure(dev, &spicfg); if (ret < 0) { @@ -645,50 +936,46 @@ static int spi_pl022_init(const struct device *dev) return 0; } -#if defined(CONFIG_PINCTRL) -#define PINCTRL_DEFINE(n) PINCTRL_DT_INST_DEFINE(n); -#define PINCTRL_INIT(n) .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), -#else -#define PINCTRL_DEFINE(n) -#define PINCTRL_INIT(n) -#endif /* CONFIG_PINCTRL */ - -#if defined(CONFIG_SPI_PL022_INTERRUPT) -#define DECLARE_IRQ_CONFIGURE(idx) \ - static void spi_pl022_irq_config_##idx(const struct device *dev) \ - { \ - IRQ_CONNECT(DT_INST_IRQN(idx), \ - DT_INST_IRQ(idx, priority), \ - spi_pl022_isr, DEVICE_DT_INST_GET(idx), 0); \ - irq_enable(DT_INST_IRQN(idx)); \ +#define DMA_INITIALIZER(idx, dir) \ + { \ + .dev = DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(idx, dir)), \ + .channel = DT_INST_DMAS_CELL_BY_NAME(idx, dir, channel), \ + .slot = DT_INST_DMAS_CELL_BY_NAME(idx, dir, slot), \ + .channel_config = DT_INST_DMAS_CELL_BY_NAME(idx, dir, channel_config), \ } -#define IRQ_HANDLER(idx) .irq_config = spi_pl022_irq_config_##idx, -#else -#define DECLARE_IRQ_CONFIGURE(idx) -#define IRQ_HANDLER(idx) -#endif -#define SPI_PL022_INIT(idx) \ - PINCTRL_DEFINE(idx) \ - DECLARE_IRQ_CONFIGURE(idx) \ - static struct spi_pl022_data spi_pl022_data_##idx = { \ - SPI_CONTEXT_INIT_LOCK(spi_pl022_data_##idx, ctx), \ - SPI_CONTEXT_INIT_SYNC(spi_pl022_data_##idx, ctx), \ - SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(idx), ctx) \ - }; \ - static struct spi_pl022_cfg spi_pl022_cfg_##idx = { \ - .reg = DT_INST_REG_ADDR(idx), \ - .pclk = DT_INST_PROP_BY_PHANDLE(idx, clocks, clock_frequency), \ - IRQ_HANDLER(idx) \ - PINCTRL_INIT(idx) \ - }; \ - DEVICE_DT_INST_DEFINE(idx, \ - spi_pl022_init, \ - NULL, \ - &spi_pl022_data_##idx, \ - &spi_pl022_cfg_##idx, \ - POST_KERNEL, \ - CONFIG_SPI_INIT_PRIORITY, \ +#define DMAS_DECL(idx) \ + { \ + COND_CODE_1(DT_INST_DMAS_HAS_NAME(idx, tx), (DMA_INITIALIZER(idx, tx)), ({0})), \ + COND_CODE_1(DT_INST_DMAS_HAS_NAME(idx, rx), (DMA_INITIALIZER(idx, rx)), ({0})), \ + } + +#define DMAS_ENABLED(idx) (DT_INST_DMAS_HAS_NAME(idx, tx) && DT_INST_DMAS_HAS_NAME(idx, rx)) + +#define SPI_PL022_INIT(idx) \ + IF_ENABLED(CONFIG_PINCTRL, (PINCTRL_DT_INST_DEFINE(idx);)) \ + IF_ENABLED(CONFIG_SPI_PL022_INTERRUPT, \ + (static void spi_pl022_irq_config_##idx(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(idx), DT_INST_IRQ(idx, priority), \ + spi_pl022_isr, DEVICE_DT_INST_GET(idx), 0); \ + irq_enable(DT_INST_IRQN(idx)); \ + })) \ + static struct spi_pl022_data spi_pl022_data_##idx = { \ + SPI_CONTEXT_INIT_LOCK(spi_pl022_data_##idx, ctx), \ + SPI_CONTEXT_INIT_SYNC(spi_pl022_data_##idx, ctx), \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(idx), ctx)}; \ + static struct spi_pl022_cfg spi_pl022_cfg_##idx = { \ + .reg = DT_INST_REG_ADDR(idx), \ + .pclk = DT_INST_PROP_BY_PHANDLE(idx, clocks, clock_frequency), \ + IF_ENABLED(CONFIG_PINCTRL, (.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(idx),)) \ + IF_ENABLED(CONFIG_SPI_PL022_DMA, (.dma = DMAS_DECL(idx),)) COND_CODE_1( \ + CONFIG_SPI_PL022_DMA, (.dma_enabled = DMAS_ENABLED(idx),), \ + (.dma_enabled = false,)) \ + IF_ENABLED(CONFIG_SPI_PL022_INTERRUPT, \ + (.irq_config = spi_pl022_irq_config_##idx,))}; \ + DEVICE_DT_INST_DEFINE(idx, spi_pl022_init, NULL, &spi_pl022_data_##idx, \ + &spi_pl022_cfg_##idx, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ &spi_pl022_api); DT_INST_FOREACH_STATUS_OKAY(SPI_PL022_INIT)