| /* |
| * Copyright 2023 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/dma.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/pm/policy.h> |
| #include <zephyr/drivers/dma/dma_mcux_smartdma.h> |
| |
| #include <soc.h> |
| #include <fsl_smartdma.h> |
| #include <fsl_inputmux.h> |
| #include <fsl_power.h> |
| |
| #define DT_DRV_COMPAT nxp_smartdma |
| |
| LOG_MODULE_REGISTER(dma_mcux_smartdma, CONFIG_DMA_LOG_LEVEL); |
| |
| /* SMARTDMA peripheral registers, taken from MCUX driver implementation*/ |
| struct smartdma_periph { |
| volatile uint32_t BOOT; |
| volatile uint32_t CTRL; |
| volatile uint32_t PC; |
| volatile uint32_t SP; |
| volatile uint32_t BREAK_ADDR; |
| volatile uint32_t BREAK_VECT; |
| volatile uint32_t EMER_VECT; |
| volatile uint32_t EMER_SEL; |
| volatile uint32_t ARM2SMARTDMA; |
| volatile uint32_t SMARTDMA2ARM; |
| volatile uint32_t PENDTRAP; |
| }; |
| |
| struct dma_mcux_smartdma_config { |
| struct smartdma_periph *base; |
| void (*irq_config_func)(const struct device *dev); |
| |
| void (**smartdma_progs)(void); |
| }; |
| |
| struct dma_mcux_smartdma_data { |
| uint32_t smartdma_stack[32]; /* Stack for SMARTDMA */ |
| /* Installed DMA callback and user data */ |
| dma_callback_t callback; |
| void *user_data; |
| }; |
| |
| /* Seems to be written to smartDMA control register when it is configured */ |
| #define SMARTDMA_MAGIC 0xC0DE0000U |
| /* These bits are set when the SMARTDMA boots, cleared to reset it */ |
| #define SMARTDMA_BOOT 0x11 |
| |
| static inline bool dma_mcux_smartdma_prog_is_mipi(uint32_t prog) |
| { |
| return ((prog == kSMARTDMA_MIPI_RGB565_DMA) || |
| (prog == kSMARTDMA_MIPI_RGB888_DMA) || |
| (prog == kSMARTDMA_MIPI_RGB565_R180_DMA) || |
| (prog == kSMARTDMA_MIPI_RGB888_R180_DMA)); |
| } |
| |
| /* Configure a channel */ |
| static int dma_mcux_smartdma_configure(const struct device *dev, |
| uint32_t channel, struct dma_config *config) |
| { |
| const struct dma_mcux_smartdma_config *dev_config = dev->config; |
| struct dma_mcux_smartdma_data *data = dev->data; |
| uint32_t prog_idx; |
| bool swap_pixels = false; |
| |
| /* SMARTDMA does not have channels */ |
| ARG_UNUSED(channel); |
| |
| data->callback = config->dma_callback; |
| data->user_data = config->user_data; |
| |
| /* Reset smartDMA */ |
| SMARTDMA_Reset(); |
| |
| /* |
| * The dma_slot parameter is used to determine which SMARTDMA program |
| * to run. First, convert the Zephyr define to a HAL enum. |
| */ |
| switch (config->dma_slot) { |
| case DMA_SMARTDMA_MIPI_RGB565_DMA: |
| prog_idx = kSMARTDMA_MIPI_RGB565_DMA; |
| break; |
| case DMA_SMARTDMA_MIPI_RGB888_DMA: |
| prog_idx = kSMARTDMA_MIPI_RGB888_DMA; |
| break; |
| case DMA_SMARTDMA_MIPI_RGB565_180: |
| prog_idx = kSMARTDMA_MIPI_RGB565_R180_DMA; |
| break; |
| case DMA_SMARTDMA_MIPI_RGB888_180: |
| prog_idx = kSMARTDMA_MIPI_RGB888_R180_DMA; |
| break; |
| case DMA_SMARTDMA_MIPI_RGB565_DMA_SWAP: |
| swap_pixels = true; |
| prog_idx = kSMARTDMA_MIPI_RGB565_DMA; |
| break; |
| case DMA_SMARTDMA_MIPI_RGB888_DMA_SWAP: |
| swap_pixels = true; |
| prog_idx = kSMARTDMA_MIPI_RGB888_DMA; |
| break; |
| case DMA_SMARTDMA_MIPI_RGB565_180_SWAP: |
| swap_pixels = true; |
| prog_idx = kSMARTDMA_MIPI_RGB565_R180_DMA; |
| break; |
| case DMA_SMARTDMA_MIPI_RGB888_180_SWAP: |
| swap_pixels = true; |
| prog_idx = kSMARTDMA_MIPI_RGB888_R180_DMA; |
| break; |
| default: |
| prog_idx = config->dma_slot; |
| break; |
| } |
| |
| if (dma_mcux_smartdma_prog_is_mipi(prog_idx)) { |
| smartdma_dsi_param_t param = {.disablePixelByteSwap = (swap_pixels == false)}; |
| |
| if (config->block_count != 1) { |
| return -ENOTSUP; |
| } |
| /* Setup SMARTDMA */ |
| param.p_buffer = (uint8_t *)config->head_block->source_address; |
| param.buffersize = config->head_block->block_size; |
| param.smartdma_stack = data->smartdma_stack; |
| /* Save configuration to SMARTDMA */ |
| dev_config->base->ARM2SMARTDMA = (uint32_t)(¶m); |
| } else { |
| /* For other cases, we simply pass the entire DMA config |
| * struct to the SMARTDMA. The user's application could either |
| * populate this structure with data, or choose to write |
| * different configuration data to the SMARTDMA in their |
| * application |
| */ |
| dev_config->base->ARM2SMARTDMA = ((uint32_t)config); |
| } |
| /* Save program */ |
| dev_config->base->BOOT = (uint32_t)dev_config->smartdma_progs[prog_idx]; |
| LOG_DBG("Boot address set to 0x%X", dev_config->base->BOOT); |
| return 0; |
| } |
| |
| static int dma_mcux_smartdma_start(const struct device *dev, uint32_t channel) |
| { |
| const struct dma_mcux_smartdma_config *config = dev->config; |
| |
| /* Block PM transition until DMA completes */ |
| pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| |
| /* Kick off SMARTDMA */ |
| config->base->CTRL = SMARTDMA_MAGIC | SMARTDMA_BOOT; |
| |
| return 0; |
| } |
| |
| |
| static int dma_mcux_smartdma_stop(const struct device *dev, uint32_t channel) |
| { |
| ARG_UNUSED(dev); |
| ARG_UNUSED(channel); |
| |
| /* Stop DMA */ |
| SMARTDMA_Reset(); |
| |
| /* Release PM lock */ |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| |
| return 0; |
| } |
| |
| static int dma_mcux_smartdma_init(const struct device *dev) |
| { |
| const struct dma_mcux_smartdma_config *config = dev->config; |
| /* |
| * Initialize the SMARTDMA with firmware. The default firmware |
| * from MCUX SDK is a display firmware, which has functions |
| * implemented above in the dma configuration function. The |
| * user can install another firmware using `dma_smartdma_install_fw` |
| */ |
| SMARTDMA_Init((uint32_t)config->smartdma_progs, |
| s_smartdmaDisplayFirmware, |
| SMARTDMA_DISPLAY_FIRMWARE_SIZE); |
| config->irq_config_func(dev); |
| |
| return 0; |
| } |
| |
| static void dma_mcux_smartdma_irq(const struct device *dev) |
| { |
| const struct dma_mcux_smartdma_data *data = dev->data; |
| |
| if (data->callback) { |
| data->callback(dev, data->user_data, 0, 0); |
| } |
| |
| /* Release PM lock */ |
| pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); |
| } |
| |
| /** |
| * @brief install SMARTDMA firmware |
| * |
| * Install a custom firmware for the smartDMA. This function allows the user |
| * to install a custom firmware into the smartDMA, which implements |
| * different API functions than the standard MCUX SDK firmware. |
| * @param dev: smartDMA device |
| * @param firmware: address of buffer containing smartDMA firmware |
| * @param len: length of firmware buffer |
| */ |
| void dma_smartdma_install_fw(const struct device *dev, uint8_t *firmware, |
| uint32_t len) |
| { |
| const struct dma_mcux_smartdma_config *config = dev->config; |
| |
| SMARTDMA_InstallFirmware((uint32_t)config->smartdma_progs, firmware, len); |
| } |
| |
| static const struct dma_driver_api dma_mcux_smartdma_api = { |
| .config = dma_mcux_smartdma_configure, |
| .start = dma_mcux_smartdma_start, |
| .stop = dma_mcux_smartdma_stop, |
| }; |
| |
| |
| #define SMARTDMA_INIT(n) \ |
| static void dma_mcux_smartdma_config_func_##n(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \ |
| dma_mcux_smartdma_irq, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } \ |
| \ |
| static const struct dma_mcux_smartdma_config smartdma_##n##_config = { \ |
| .base = (struct smartdma_periph *)DT_INST_REG_ADDR(n), \ |
| .smartdma_progs = (void (**)(void))DT_INST_PROP(n, program_mem),\ |
| .irq_config_func = dma_mcux_smartdma_config_func_##n, \ |
| }; \ |
| static struct dma_mcux_smartdma_data smartdma_##n##_data; \ |
| \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| &dma_mcux_smartdma_init, \ |
| NULL, \ |
| &smartdma_##n##_data, &smartdma_##n##_config, \ |
| POST_KERNEL, CONFIG_DMA_INIT_PRIORITY, \ |
| &dma_mcux_smartdma_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SMARTDMA_INIT) |