| /* |
| * Copyright (c) 2023 TOKITA Hiroshi <tokita.hiroshi@fujitsu.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/drivers/reset.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/dt-bindings/dma/rpi_pico_dma.h> |
| |
| #include <hardware/dma.h> |
| |
| #define DT_DRV_COMPAT raspberrypi_pico_dma |
| |
| #define DMA_INT_ERROR_FLAGS \ |
| (DMA_CH0_CTRL_TRIG_AHB_ERROR_BITS | DMA_CH0_CTRL_TRIG_READ_ERROR_BITS | \ |
| DMA_CH0_CTRL_TRIG_WRITE_ERROR_BITS) |
| |
| LOG_MODULE_REGISTER(dma_rpi_pico, CONFIG_DMA_LOG_LEVEL); |
| |
| struct dma_rpi_pico_config { |
| uint32_t reg; |
| uint32_t channels; |
| struct reset_dt_spec reset; |
| void (*irq_configure)(void); |
| uint32_t *irq0_channels; |
| size_t irq0_channels_size; |
| }; |
| |
| struct dma_rpi_pico_channel { |
| dma_callback_t callback; |
| void *user_data; |
| uint32_t direction; |
| dma_channel_config config; |
| void *source_address; |
| void *dest_address; |
| size_t block_size; |
| }; |
| |
| struct dma_rpi_pico_data { |
| struct dma_context ctx; |
| struct dma_rpi_pico_channel *channels; |
| }; |
| |
| /* |
| * Register access functions |
| */ |
| |
| static inline void rpi_pico_dma_channel_clear_error_flags(const struct device *dev, |
| uint32_t channel) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| |
| ((dma_hw_t *)cfg->reg)->ch[channel].al1_ctrl &= ~DMA_INT_ERROR_FLAGS; |
| } |
| |
| static inline uint32_t rpi_pico_dma_channel_get_error_flags(const struct device *dev, |
| uint32_t channel) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| |
| return ((dma_hw_t *)cfg->reg)->ch[channel].al1_ctrl & DMA_INT_ERROR_FLAGS; |
| } |
| |
| static inline void rpi_pico_dma_channel_abort(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| |
| ((dma_hw_t *)cfg->reg)->abort = BIT(channel); |
| } |
| |
| /* |
| * Utility functions |
| */ |
| |
| static inline uint32_t dma_rpi_pico_transfer_size(uint32_t width) |
| { |
| switch (width) { |
| case 4: |
| return DMA_SIZE_32; |
| case 2: |
| return DMA_SIZE_16; |
| default: |
| return DMA_SIZE_8; |
| } |
| } |
| |
| static inline uint32_t dma_rpi_pico_channel_irq(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| |
| for (size_t i = 0; i < cfg->irq0_channels_size; i++) { |
| if (cfg->irq0_channels[i] == channel) { |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * API functions |
| */ |
| |
| static int dma_rpi_pico_config(const struct device *dev, uint32_t channel, |
| struct dma_config *dma_cfg) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| struct dma_rpi_pico_data *data = dev->data; |
| |
| if (channel >= cfg->channels) { |
| LOG_ERR("channel must be < %" PRIu32 " (%" PRIu32 ")", cfg->channels, channel); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->block_count != 1) { |
| LOG_ERR("chained block transfer not supported."); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->channel_priority > 3) { |
| LOG_ERR("channel_priority must be < 4 (%" PRIu32 ")", dma_cfg->channel_priority); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->head_block->source_addr_adj == DMA_ADDR_ADJ_DECREMENT) { |
| LOG_ERR("source_addr_adj not supported DMA_ADDR_ADJ_DECREMENT"); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->head_block->dest_addr_adj == DMA_ADDR_ADJ_DECREMENT) { |
| LOG_ERR("dest_addr_adj not supported DMA_ADDR_ADJ_DECREMENT"); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->head_block->source_addr_adj != DMA_ADDR_ADJ_INCREMENT && |
| dma_cfg->head_block->source_addr_adj != DMA_ADDR_ADJ_NO_CHANGE) { |
| LOG_ERR("invalid source_addr_adj %" PRIu16, dma_cfg->head_block->source_addr_adj); |
| return -ENOTSUP; |
| } |
| if (dma_cfg->head_block->dest_addr_adj != DMA_ADDR_ADJ_INCREMENT && |
| dma_cfg->head_block->dest_addr_adj != DMA_ADDR_ADJ_NO_CHANGE) { |
| LOG_ERR("invalid dest_addr_adj %" PRIu16, dma_cfg->head_block->dest_addr_adj); |
| return -ENOTSUP; |
| } |
| |
| if (dma_cfg->source_data_size != 1 && dma_cfg->source_data_size != 2 && |
| dma_cfg->source_data_size != 4) { |
| LOG_ERR("source_data_size must be 1, 2, or 4 (%" PRIu32 ")", |
| dma_cfg->source_data_size); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->source_data_size != dma_cfg->dest_data_size) { |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->dest_data_size != 1 && dma_cfg->dest_data_size != 2 && |
| dma_cfg->dest_data_size != 4) { |
| LOG_ERR("dest_data_size must be 1, 2, or 4 (%" PRIu32 ")", dma_cfg->dest_data_size); |
| return -EINVAL; |
| } |
| |
| if (dma_cfg->channel_direction > PERIPHERAL_TO_MEMORY) { |
| LOG_ERR("channel_direction must be MEMORY_TO_MEMORY, " |
| "MEMORY_TO_PERIPHERAL or PERIPHERAL_TO_MEMORY (%" PRIu32 ")", |
| dma_cfg->channel_direction); |
| return -ENOTSUP; |
| } |
| |
| data->channels[channel].config = dma_channel_get_default_config(channel); |
| |
| data->channels[channel].source_address = (void *)dma_cfg->head_block->source_address; |
| data->channels[channel].dest_address = (void *)dma_cfg->head_block->dest_address; |
| data->channels[channel].block_size = dma_cfg->head_block->block_size; |
| channel_config_set_read_increment(&data->channels[channel].config, |
| dma_cfg->head_block->source_addr_adj == |
| DMA_ADDR_ADJ_INCREMENT); |
| channel_config_set_write_increment(&data->channels[channel].config, |
| dma_cfg->head_block->dest_addr_adj == |
| DMA_ADDR_ADJ_INCREMENT); |
| channel_config_set_transfer_data_size( |
| &data->channels[channel].config, |
| dma_rpi_pico_transfer_size(dma_cfg->source_data_size)); |
| channel_config_set_dreq(&data->channels[channel].config, |
| RPI_PICO_DMA_SLOT_TO_DREQ(dma_cfg->dma_slot)); |
| channel_config_set_high_priority(&data->channels[channel].config, |
| !!(dma_cfg->channel_priority)); |
| |
| data->channels[channel].callback = dma_cfg->dma_callback; |
| data->channels[channel].user_data = dma_cfg->user_data; |
| data->channels[channel].direction = dma_cfg->channel_direction; |
| |
| return 0; |
| } |
| |
| static int dma_rpi_pico_reload(const struct device *dev, uint32_t ch, uint32_t src, uint32_t dst, |
| size_t size) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| struct dma_rpi_pico_data *data = dev->data; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("reload channel must be < %" PRIu32 " (%" PRIu32 ")", cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| if (dma_channel_is_busy(ch)) { |
| return -EBUSY; |
| } |
| |
| data->channels[ch].source_address = (void *)src; |
| data->channels[ch].dest_address = (void *)dst; |
| data->channels[ch].block_size = size; |
| dma_channel_configure(ch, &data->channels[ch].config, data->channels[ch].dest_address, |
| data->channels[ch].source_address, data->channels[ch].block_size, |
| true); |
| |
| return 0; |
| } |
| |
| static int dma_rpi_pico_start(const struct device *dev, uint32_t ch) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| struct dma_rpi_pico_data *data = dev->data; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("start channel must be < %" PRIu32 " (%" PRIu32 ")", cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| dma_irqn_acknowledge_channel(dma_rpi_pico_channel_irq(dev, ch), ch); |
| dma_irqn_set_channel_enabled(dma_rpi_pico_channel_irq(dev, ch), ch, true); |
| |
| dma_channel_configure(ch, &data->channels[ch].config, data->channels[ch].dest_address, |
| data->channels[ch].source_address, data->channels[ch].block_size, |
| true); |
| |
| return 0; |
| } |
| |
| static int dma_rpi_pico_stop(const struct device *dev, uint32_t ch) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("stop channel must be < %" PRIu32 " (%" PRIu32 ")", cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| dma_irqn_set_channel_enabled(dma_rpi_pico_channel_irq(dev, ch), ch, false); |
| rpi_pico_dma_channel_clear_error_flags(dev, ch); |
| |
| /* |
| * Considering the possibility of being called in an interrupt context, |
| * it does not wait until the abort bit becomes clear. |
| * Ensure the busy status is canceled with dma_get_status |
| * before the next transfer starts. |
| */ |
| rpi_pico_dma_channel_abort(dev, ch); |
| |
| return 0; |
| } |
| |
| static int dma_rpi_pico_get_status(const struct device *dev, uint32_t ch, struct dma_status *stat) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| struct dma_rpi_pico_data *data = dev->data; |
| |
| if (ch >= cfg->channels) { |
| LOG_ERR("channel must be < %" PRIu32 " (%" PRIu32 ")", cfg->channels, ch); |
| return -EINVAL; |
| } |
| |
| stat->pending_length = 0; |
| stat->dir = data->channels[ch].direction; |
| stat->busy = dma_channel_is_busy(ch); |
| |
| return 0; |
| } |
| |
| static bool dma_rpi_pico_api_chan_filter(const struct device *dev, int ch, void *filter_param) |
| { |
| uint32_t filter; |
| |
| if (!filter_param) { |
| LOG_ERR("filter_param must not be NULL"); |
| return false; |
| } |
| |
| filter = *((uint32_t *)filter_param); |
| |
| return (filter & BIT(ch)); |
| } |
| |
| static int dma_rpi_pico_init(const struct device *dev) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| |
| (void)reset_line_toggle_dt(&cfg->reset); |
| |
| cfg->irq_configure(); |
| |
| return 0; |
| } |
| |
| static void dma_rpi_pico_isr(const struct device *dev) |
| { |
| const struct dma_rpi_pico_config *cfg = dev->config; |
| struct dma_rpi_pico_data *data = dev->data; |
| int err = 0; |
| |
| for (uint32_t i = 0; i < cfg->channels; i++) { |
| if (!dma_irqn_get_channel_status(dma_rpi_pico_channel_irq(dev, i), i)) { |
| continue; |
| } |
| |
| if (rpi_pico_dma_channel_get_error_flags(dev, i)) { |
| err = -EIO; |
| } |
| |
| dma_irqn_acknowledge_channel(dma_rpi_pico_channel_irq(dev, i), i); |
| dma_irqn_set_channel_enabled(dma_rpi_pico_channel_irq(dev, i), i, false); |
| rpi_pico_dma_channel_clear_error_flags(dev, i); |
| |
| if (data->channels[i].callback) { |
| data->channels[i].callback(dev, data->channels[i].user_data, i, err); |
| } |
| } |
| } |
| |
| static const struct dma_driver_api dma_rpi_pico_driver_api = { |
| .config = dma_rpi_pico_config, |
| .reload = dma_rpi_pico_reload, |
| .start = dma_rpi_pico_start, |
| .stop = dma_rpi_pico_stop, |
| .get_status = dma_rpi_pico_get_status, |
| .chan_filter = dma_rpi_pico_api_chan_filter, |
| }; |
| |
| #define IRQ_CONFIGURE(n, inst) \ |
| IRQ_CONNECT(DT_INST_IRQ_BY_IDX(inst, n, irq), DT_INST_IRQ_BY_IDX(inst, n, priority), \ |
| dma_rpi_pico_isr, DEVICE_DT_INST_GET(inst), 0); \ |
| irq_enable(DT_INST_IRQ_BY_IDX(inst, n, irq)); |
| |
| #define CONFIGURE_ALL_IRQS(inst, n) LISTIFY(n, IRQ_CONFIGURE, (), inst) |
| |
| #define RPI_PICO_DMA_INIT(inst) \ |
| static void dma_rpi_pico##inst##_irq_configure(void) \ |
| { \ |
| CONFIGURE_ALL_IRQS(inst, DT_NUM_IRQS(DT_DRV_INST(inst))); \ |
| } \ |
| static uint32_t dma_rpi_pico##inst##_irq0_channels[] = \ |
| DT_INST_PROP_OR(inst, irq0_channels, {0}); \ |
| static const struct dma_rpi_pico_config dma_rpi_pico##inst##_config = { \ |
| .reg = DT_INST_REG_ADDR(inst), \ |
| .channels = DT_INST_PROP(inst, dma_channels), \ |
| .reset = RESET_DT_SPEC_INST_GET(inst), \ |
| .irq_configure = dma_rpi_pico##inst##_irq_configure, \ |
| .irq0_channels = dma_rpi_pico##inst##_irq0_channels, \ |
| .irq0_channels_size = ARRAY_SIZE(dma_rpi_pico##inst##_irq0_channels), \ |
| }; \ |
| static struct dma_rpi_pico_channel \ |
| dma_rpi_pico##inst##_channels[DT_INST_PROP(inst, dma_channels)]; \ |
| ATOMIC_DEFINE(dma_rpi_pico_atomic##inst, DT_INST_PROP(inst, dma_channels)); \ |
| static struct dma_rpi_pico_data dma_rpi_pico##inst##_data = { \ |
| .ctx = \ |
| { \ |
| .magic = DMA_MAGIC, \ |
| .atomic = dma_rpi_pico_atomic##inst, \ |
| .dma_channels = DT_INST_PROP(inst, dma_channels), \ |
| }, \ |
| .channels = dma_rpi_pico##inst##_channels, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, &dma_rpi_pico_init, NULL, &dma_rpi_pico##inst##_data, \ |
| &dma_rpi_pico##inst##_config, POST_KERNEL, CONFIG_DMA_INIT_PRIORITY, \ |
| &dma_rpi_pico_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(RPI_PICO_DMA_INIT) |