From 8d2f4633f2f1333dc305a290434033b8c2c23476 Mon Sep 17 00:00:00 2001 From: Daniel DeGrasse Date: Wed, 5 Apr 2023 17:09:10 +0000 Subject: [PATCH] drivers: dma: introduce SMARTDMA dma driver Introduce SMARTDMA dma driver. The SMARTDMA is a peripheral present on some NXP SOCs, which implements a programmable DMA engine. The DMA engine does not use channels, but rather provides a series of API functions implemented by the firmware provided with MCUX SDK. These API functions can be selected by the dma_config slot parameter. A custom API is also provided to allow the user to install an alternate firmware into the SMARTDMA. Signed-off-by: Daniel DeGrasse --- drivers/dma/CMakeLists.txt | 1 + drivers/dma/Kconfig | 3 + drivers/dma/Kconfig.mcux_smartdma | 9 + drivers/dma/dma_mcux_smartdma.c | 252 ++++++++++++++++++ dts/bindings/dma/nxp,smartdma.yaml | 28 ++ .../zephyr/drivers/dma/dma_mcux_smartdma.h | 53 ++++ 6 files changed, 346 insertions(+) create mode 100644 drivers/dma/Kconfig.mcux_smartdma create mode 100644 drivers/dma/dma_mcux_smartdma.c create mode 100644 dts/bindings/dma/nxp,smartdma.yaml create mode 100644 include/zephyr/drivers/dma/dma_mcux_smartdma.h diff --git a/drivers/dma/CMakeLists.txt b/drivers/dma/CMakeLists.txt index 3cb622ae9e8..bd96efbf0a5 100644 --- a/drivers/dma/CMakeLists.txt +++ b/drivers/dma/CMakeLists.txt @@ -32,3 +32,4 @@ zephyr_library_sources_ifdef(CONFIG_DMA_MCHP_XEC dma_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_DMA_XMC4XXX dma_xmc4xxx.c) zephyr_library_sources_ifdef(CONFIG_DMA_RPI_PICO dma_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_MCUX_PXP dma_mcux_pxp.c) +zephyr_library_sources_ifdef(CONFIG_DMA_MCUX_SMARTDMA dma_mcux_smartdma.c) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 5023d9af9dd..37cd83383d8 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -61,4 +61,7 @@ source "drivers/dma/Kconfig.rpi_pico" source "drivers/dma/Kconfig.intel_lpss" source "drivers/dma/Kconfig.mcux_pxp" + +source "drivers/dma/Kconfig.mcux_smartdma" + endif # DMA diff --git a/drivers/dma/Kconfig.mcux_smartdma b/drivers/dma/Kconfig.mcux_smartdma new file mode 100644 index 00000000000..e4258038ee5 --- /dev/null +++ b/drivers/dma/Kconfig.mcux_smartdma @@ -0,0 +1,9 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +config DMA_MCUX_SMARTDMA + bool "MCUX SmartDMA Driver" + default y + depends on DT_HAS_NXP_SMARTDMA_ENABLED + help + MCUX SmartDMA driver. diff --git a/drivers/dma/dma_mcux_smartdma.c b/drivers/dma/dma_mcux_smartdma.c new file mode 100644 index 00000000000..b1870b3959c --- /dev/null +++ b/drivers/dma/dma_mcux_smartdma.c @@ -0,0 +1,252 @@ +/* + * 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; + +#ifdef CONFIG_PM + /* Block PM transition until DMA completes */ + pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); +#endif + /* 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(); +#ifdef CONFIG_PM + /* Release PM lock */ + pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); +#endif + 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); + } +#ifdef CONFIG_PM + /* Release PM lock */ + pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES); +#endif +} + +/** + * @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) diff --git a/dts/bindings/dma/nxp,smartdma.yaml b/dts/bindings/dma/nxp,smartdma.yaml new file mode 100644 index 00000000000..0724616b5c1 --- /dev/null +++ b/dts/bindings/dma/nxp,smartdma.yaml @@ -0,0 +1,28 @@ +# Copyright 2023 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: NXP SmartDMA controller + +compatible: "nxp,smartdma" + +include: dma-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + program-mem: + type: int + required: true + description: | + Program memory to load SMARTDMA routines into. Must be set to a RAM + region that the SMARTDMA can access on the chip. + + # SmartDMA does not support channels, so no DMA cells should be provided. + # the driver or application using SMARTDMA can set the DMA program to run + # using the `dma_slot` parameter. + "#dma-cells": + const: 0 diff --git a/include/zephyr/drivers/dma/dma_mcux_smartdma.h b/include/zephyr/drivers/dma/dma_mcux_smartdma.h new file mode 100644 index 00000000000..0e09d348d8f --- /dev/null +++ b/include/zephyr/drivers/dma/dma_mcux_smartdma.h @@ -0,0 +1,53 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_DMA_MCUX_SMARTDMA_H_ +#define ZEPHYR_INCLUDE_DRIVERS_DMA_MCUX_SMARTDMA_H_ + +/* Write RGB565 data to MIPI DSI via DMA. */ +#define DMA_SMARTDMA_MIPI_RGB565_DMA 0 +/* Write RGB888 data to MIPI DSI via DMA */ +#define DMA_SMARTDMA_MIPI_RGB888_DMA 1 +/* Write RGB565 data to MIPI DSI via DMA. Rotate output data by 180 degrees */ +#define DMA_SMARTDMA_MIPI_RGB565_180 2 +/* Write RGB888 data to MIPI DSI via DMA. Rotate output data by 180 degrees */ +#define DMA_SMARTDMA_MIPI_RGB888_180 3 + +/* Write RGB565 data to MIPI DSI via DMA. Swap data endianness, so that + * little endian RGB565 data will be written big endian style. + */ +#define DMA_SMARTDMA_MIPI_RGB565_DMA_SWAP 4 +/* Write RGB888 data to MIPI DSI via DMA. Swap data endianness, so that + * little endian RGB888 data will be written big endian style. + */ +#define DMA_SMARTDMA_MIPI_RGB888_DMA_SWAP 5 +/* Write RGB565 data to MIPI DSI via DMA. Rotate output data by 180 degrees, + * and swap data endianness + */ +#define DMA_SMARTDMA_MIPI_RGB565_180_SWAP 6 +/* Write RGB888 data to MIPI DSI via DMA. Rotate output data by 180 degrees, + * and swap data endianness + */ +#define DMA_SMARTDMA_MIPI_RGB888_180_SWAP 7 + + + +/** + * @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); + +#define GD32_DMA_FEATURES_FIFO_THRESHOLD(threshold) (threshold & 0x3) + +#endif /* ZEPHYR_INCLUDE_DRIVERS_DMA_MCUX_SMARTDMA_H_ */