/* * Copyright 2023 NXP * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #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)