| /* |
| * Copyright (c) 2023 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT intel_sedi_dma |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/pm/device.h> |
| #include <string.h> |
| #include <zephyr/init.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/cache.h> |
| #include <soc.h> |
| |
| #include "sedi_driver_dma.h" |
| #include "sedi_driver_core.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(sedi_dma, CONFIG_DMA_LOG_LEVEL); |
| |
| extern void dma_isr(sedi_dma_t dma_device); |
| |
| struct dma_sedi_config_info { |
| sedi_dma_t peripheral_id; /* Controller instance. */ |
| uint8_t chn_num; |
| void (*irq_config)(void); |
| }; |
| |
| struct dma_sedi_driver_data { |
| struct dma_config dma_configs[DMA_CHANNEL_NUM]; |
| }; |
| |
| #define DEV_DATA(dev) ((struct dma_sedi_driver_data *const)(dev)->data) |
| #define DEV_CFG(dev) \ |
| ((const struct dma_sedi_config_info *const)(dev)->config) |
| |
| /* |
| * this function will be called when dma transferring is completed |
| * or error happened |
| */ |
| static void dma_handler(sedi_dma_t dma_device, int channel, int event_id, |
| void *args) |
| { |
| ARG_UNUSED(args); |
| const struct device *dev = (const struct device *)args; |
| struct dma_sedi_driver_data *const data = DEV_DATA(dev); |
| struct dma_config *config = &(data->dma_configs[channel]); |
| |
| /* run user-defined callback */ |
| if (config->dma_callback) { |
| if ((event_id == SEDI_DMA_EVENT_TRANSFER_DONE) && |
| (config->complete_callback_en)) { |
| config->dma_callback(dev, config->user_data, |
| channel, 0); |
| } else if (!config->error_callback_dis) { |
| config->dma_callback(dev, config->user_data, |
| channel, event_id); |
| } |
| } |
| } |
| |
| /* map width to certain macros*/ |
| static int width_index(uint32_t num_bytes, uint32_t *index) |
| { |
| switch (num_bytes) { |
| case 1: |
| *index = DMA_TRANS_WIDTH_8; |
| break; |
| case 2: |
| *index = DMA_TRANS_WIDTH_16; |
| break; |
| case 4: |
| *index = DMA_TRANS_WIDTH_32; |
| break; |
| case 8: |
| *index = DMA_TRANS_WIDTH_64; |
| break; |
| case 16: |
| *index = DMA_TRANS_WIDTH_128; |
| break; |
| case 32: |
| *index = DMA_TRANS_WIDTH_256; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| /* map burst size to certain macros*/ |
| static int burst_index(uint32_t num_units, uint32_t *index) |
| { |
| switch (num_units) { |
| case 1: |
| *index = DMA_BURST_TRANS_LENGTH_1; |
| break; |
| case 4: |
| *index = DMA_BURST_TRANS_LENGTH_4; |
| break; |
| case 8: |
| *index = DMA_BURST_TRANS_LENGTH_8; |
| break; |
| case 16: |
| *index = DMA_BURST_TRANS_LENGTH_16; |
| break; |
| case 32: |
| *index = DMA_BURST_TRANS_LENGTH_32; |
| break; |
| case 64: |
| *index = DMA_BURST_TRANS_LENGTH_64; |
| break; |
| case 128: |
| *index = DMA_BURST_TRANS_LENGTH_128; |
| break; |
| case 256: |
| *index = DMA_BURST_TRANS_LENGTH_256; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static void dma_config_convert(struct dma_config *config, |
| dma_memory_type_t *src_mem, |
| dma_memory_type_t *dst_mem, |
| uint8_t *sedi_dma_dir) |
| { |
| |
| *src_mem = DMA_SRAM_MEM; |
| *dst_mem = DMA_SRAM_MEM; |
| *sedi_dma_dir = MEMORY_TO_MEMORY; |
| switch (config->channel_direction) { |
| case MEMORY_TO_MEMORY: |
| case MEMORY_TO_PERIPHERAL: |
| case PERIPHERAL_TO_MEMORY: |
| case PERIPHERAL_TO_PERIPHERAL: |
| *sedi_dma_dir = config->channel_direction; |
| break; |
| case MEMORY_TO_HOST: |
| *dst_mem = DMA_DRAM_MEM; |
| break; |
| case HOST_TO_MEMORY: |
| *src_mem = DMA_DRAM_MEM; |
| break; |
| #ifdef MEMORY_TO_IMR |
| case MEMORY_TO_IMR: |
| *dst_mem = DMA_UMA_MEM; |
| break; |
| #endif |
| #ifdef IMR_TO_MEMORY |
| case IMR_TO_MEMORY: |
| *src_mem = DMA_UMA_MEM; |
| break; |
| #endif |
| } |
| } |
| |
| /* config basic dma */ |
| static int dma_sedi_apply_common_config(sedi_dma_t dev, uint32_t channel, |
| struct dma_config *config, uint8_t *dir) |
| { |
| uint8_t direction = MEMORY_TO_MEMORY; |
| dma_memory_type_t src_mem = DMA_SRAM_MEM, dst_mem = DMA_SRAM_MEM; |
| |
| dma_config_convert(config, &src_mem, &dst_mem, &direction); |
| |
| if (dir) { |
| *dir = direction; |
| } |
| |
| /* configure dma transferring direction*/ |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_DIRECTION, |
| direction); |
| |
| if (direction == MEMORY_TO_MEMORY) { |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_SR_MEM_TYPE, |
| src_mem); |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_DT_MEM_TYPE, |
| dst_mem); |
| } else if (direction == MEMORY_TO_PERIPHERAL) { |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_DEVICE_ID, |
| config->dma_slot); |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_POLARITY, |
| DMA_HS_POLARITY_HIGH); |
| sedi_dma_control(dev, channel, |
| SEDI_CONFIG_DMA_HS_DEVICE_ID_PER_DIR, |
| DMA_HS_PER_TX); |
| } else if (direction == PERIPHERAL_TO_MEMORY) { |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_DEVICE_ID, |
| config->dma_slot); |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_HS_POLARITY, |
| DMA_HS_POLARITY_HIGH); |
| sedi_dma_control(dev, channel, |
| SEDI_CONFIG_DMA_HS_DEVICE_ID_PER_DIR, |
| DMA_HS_PER_RX); |
| } else { |
| return -1; |
| } |
| return 0; |
| } |
| |
| static int dma_sedi_apply_single_config(sedi_dma_t dev, uint32_t channel, |
| struct dma_config *config) |
| { |
| int ret = 0; |
| uint32_t temp = 0; |
| |
| ret = dma_sedi_apply_common_config(dev, channel, config, NULL); |
| if (ret != 0) { |
| goto INVALID_ARGS; |
| } |
| /* configurate dma width of source data*/ |
| ret = width_index(config->source_data_size, &temp); |
| if (ret != 0) { |
| goto INVALID_ARGS; |
| } |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_SR_TRANS_WIDTH, temp); |
| |
| /* configurate dma width of destination data*/ |
| ret = width_index(config->dest_data_size, &temp); |
| if (ret != 0) { |
| goto INVALID_ARGS; |
| } |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_DT_TRANS_WIDTH, temp); |
| |
| /* configurate dma burst size*/ |
| ret = burst_index(config->source_burst_length, &temp); |
| if (ret != 0) { |
| goto INVALID_ARGS; |
| } |
| sedi_dma_control(dev, channel, SEDI_CONFIG_DMA_BURST_LENGTH, temp); |
| return 0; |
| |
| INVALID_ARGS: |
| return ret; |
| } |
| |
| static int dma_sedi_chan_config(const struct device *dev, uint32_t channel, |
| struct dma_config *config) |
| { |
| if ((dev == NULL) || (channel >= DEV_CFG(dev)->chn_num) |
| || (config == NULL) |
| || (config->block_count != 1)) { |
| goto INVALID_ARGS; |
| } |
| |
| const struct dma_sedi_config_info *const info = DEV_CFG(dev); |
| struct dma_sedi_driver_data *const data = DEV_DATA(dev); |
| |
| memcpy(&(data->dma_configs[channel]), config, sizeof(struct dma_config)); |
| |
| /* initialize the dma controller, following the sedi api*/ |
| sedi_dma_event_cb_t cb = dma_handler; |
| |
| sedi_dma_init(info->peripheral_id, (int)channel, cb, (void *)dev); |
| |
| return 0; |
| |
| INVALID_ARGS: |
| return -1; |
| } |
| |
| static int dma_sedi_reload(const struct device *dev, uint32_t channel, |
| uint64_t src, uint64_t dst, size_t size) |
| { |
| if ((dev == NULL) || (channel >= DEV_CFG(dev)->chn_num)) { |
| LOG_ERR("dma reload failed for invalid args"); |
| return -ENOTSUP; |
| } |
| |
| int ret = 0; |
| struct dma_sedi_driver_data *const data = DEV_DATA(dev); |
| struct dma_config *config = &(data->dma_configs[channel]); |
| struct dma_block_config *block_config; |
| |
| if ((config == NULL) || (config->head_block == NULL)) { |
| LOG_ERR("dma reload failed, no config found"); |
| return -ENOTSUP; |
| } |
| block_config = config->head_block; |
| |
| if ((config->block_count == 1) || (block_config->next_block == NULL)) { |
| block_config->source_address = src; |
| block_config->dest_address = dst; |
| block_config->block_size = size; |
| } else { |
| LOG_ERR("no reload support for multi-linkedlist mode"); |
| return -ENOTSUP; |
| } |
| return ret; |
| } |
| |
| static int dma_sedi_start(const struct device *dev, uint32_t channel) |
| { |
| if ((dev == NULL) || (channel >= DEV_CFG(dev)->chn_num)) { |
| LOG_ERR("dma transferring failed for invalid args"); |
| return -ENOTSUP; |
| } |
| |
| int ret = -1; |
| const struct dma_sedi_config_info *const info = DEV_CFG(dev); |
| struct dma_sedi_driver_data *const data = DEV_DATA(dev); |
| struct dma_config *config = &(data->dma_configs[channel]); |
| struct dma_block_config *block_config = config->head_block; |
| uint64_t src_addr, dst_addr; |
| |
| if (config->block_count == 1) { |
| /* call sedi start function */ |
| ret = dma_sedi_apply_single_config(info->peripheral_id, |
| channel, config); |
| if (ret) { |
| goto ERR; |
| } |
| src_addr = block_config->source_address; |
| dst_addr = block_config->dest_address; |
| |
| ret = sedi_dma_start_transfer(info->peripheral_id, channel, |
| src_addr, dst_addr, block_config->block_size); |
| } else { |
| LOG_ERR("MULTIPLE_BLOCK CONFIG is not set"); |
| goto ERR; |
| } |
| |
| if (ret != SEDI_DRIVER_OK) { |
| goto ERR; |
| } |
| |
| return ret; |
| |
| ERR: |
| LOG_ERR("dma transfer failed"); |
| return ret; |
| } |
| |
| static int dma_sedi_stop(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_sedi_config_info *const info = DEV_CFG(dev); |
| |
| LOG_DBG("stopping dma: %p, %d", dev, channel); |
| sedi_dma_abort_transfer(info->peripheral_id, channel); |
| |
| return 0; |
| } |
| |
| static const struct dma_driver_api dma_funcs = { .config = dma_sedi_chan_config, |
| .start = dma_sedi_start, |
| .stop = dma_sedi_stop, |
| .reload = dma_sedi_reload, |
| .get_status = NULL |
| }; |
| |
| static int dma_sedi_init(const struct device *dev) |
| { |
| const struct dma_sedi_config_info *const config = DEV_CFG(dev); |
| |
| config->irq_config(); |
| |
| return 0; |
| } |
| |
| #define DMA_DEVICE_INIT_SEDI(inst) \ |
| static void dma_sedi_##inst##_irq_config(void); \ |
| \ |
| static struct dma_sedi_driver_data dma_sedi_dev_data_##inst; \ |
| static const struct dma_sedi_config_info dma_sedi_config_data_##inst = { \ |
| .peripheral_id = DT_INST_PROP(inst, peripheral_id), \ |
| .chn_num = DT_INST_PROP(inst, dma_channels), \ |
| .irq_config = dma_sedi_##inst##_irq_config \ |
| }; \ |
| DEVICE_DT_DEFINE(DT_INST(inst, DT_DRV_COMPAT), &dma_sedi_init, \ |
| NULL, &dma_sedi_dev_data_##inst, &dma_sedi_config_data_##inst, PRE_KERNEL_2, \ |
| CONFIG_KERNEL_INIT_PRIORITY_DEVICE, (void *)&dma_funcs); \ |
| \ |
| static void dma_sedi_##inst##_irq_config(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(inst), \ |
| DT_INST_IRQ(inst, priority), dma_isr, \ |
| (void *)DT_INST_PROP(inst, peripheral_id), \ |
| DT_INST_IRQ(inst, sense)); \ |
| irq_enable(DT_INST_IRQN(inst)); \ |
| } |
| |
| DT_INST_FOREACH_STATUS_OKAY(DMA_DEVICE_INIT_SEDI) |