|  | /* | 
|  | * 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_en) { | 
|  | 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); | 
|  | struct dma_config *local_config = &(data->dma_configs[channel]); | 
|  |  | 
|  | local_config->head_block = config->head_block; | 
|  |  | 
|  | /* 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; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_PM_DEVICE | 
|  |  | 
|  | static bool is_dma_busy(sedi_dma_t dev) | 
|  | { | 
|  | sedi_dma_status_t chn_status; | 
|  |  | 
|  | for (int chn = 0; chn < DMA_CHANNEL_NUM; chn++) { | 
|  | sedi_dma_get_status(dev, chn, &chn_status); | 
|  | if (chn_status.busy == 1) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static int dma_change_device_power(const struct device *dev, | 
|  | enum pm_device_action action) | 
|  | { | 
|  | struct dma_sedi_driver_data *const data = DEV_DATA(dev); | 
|  | const struct dma_sedi_config_info *const info = DEV_CFG(dev); | 
|  | sedi_dma_t dma_dev = info->peripheral_id; | 
|  | int ret; | 
|  |  | 
|  | sedi_power_state_t state; | 
|  |  | 
|  | switch (action) { | 
|  | case PM_DEVICE_ACTION_RESUME: | 
|  | state = SEDI_POWER_FULL; | 
|  | break; | 
|  | case PM_DEVICE_ACTION_SUSPEND: | 
|  | if (is_dma_busy(dma_dev)) { | 
|  | return -EBUSY; | 
|  | } | 
|  | state = SEDI_POWER_SUSPEND; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | for (uint8_t chn = 0; chn < DMA_CHANNEL_NUM; chn++) { | 
|  | ret = sedi_dma_set_power(dma_dev, chn, state); | 
|  | if (ret != SEDI_DRIVER_OK) { | 
|  | return -EIO; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | #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) |