205 lines
7.4 KiB
C
205 lines
7.4 KiB
C
/*
|
|
* Copyright 2023 NXP
|
|
* All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/dma.h>
|
|
#include <zephyr/drivers/dma/dma_mcux_pxp.h>
|
|
#include <zephyr/devicetree.h>
|
|
|
|
#include <fsl_pxp.h>
|
|
#ifdef CONFIG_HAS_MCUX_CACHE
|
|
#include <fsl_cache.h>
|
|
#endif
|
|
|
|
#define DT_DRV_COMPAT nxp_pxp
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(dma_mcux_pxp, CONFIG_DMA_LOG_LEVEL);
|
|
|
|
struct dma_mcux_pxp_config {
|
|
PXP_Type *base;
|
|
void (*irq_config_func)(const struct device *dev);
|
|
};
|
|
|
|
struct dma_mcux_pxp_data {
|
|
void *user_data;
|
|
dma_callback_t dma_callback;
|
|
uint32_t ps_buf_addr;
|
|
uint32_t ps_buf_size;
|
|
uint32_t out_buf_addr;
|
|
uint32_t out_buf_size;
|
|
};
|
|
|
|
static void dma_mcux_pxp_irq_handler(const struct device *dev)
|
|
{
|
|
const struct dma_mcux_pxp_config *config = dev->config;
|
|
struct dma_mcux_pxp_data *data = dev->data;
|
|
|
|
PXP_ClearStatusFlags(config->base, kPXP_CompleteFlag);
|
|
#ifdef CONFIG_HAS_MCUX_CACHE
|
|
DCACHE_InvalidateByRange((uint32_t)data->out_buf_addr, data->out_buf_size);
|
|
#endif
|
|
if (data->dma_callback) {
|
|
data->dma_callback(dev, data->user_data, 0, 0);
|
|
}
|
|
}
|
|
|
|
/* Configure a channel */
|
|
static int dma_mcux_pxp_configure(const struct device *dev, uint32_t channel,
|
|
struct dma_config *config)
|
|
{
|
|
const struct dma_mcux_pxp_config *dev_config = dev->config;
|
|
struct dma_mcux_pxp_data *dev_data = dev->data;
|
|
pxp_ps_buffer_config_t ps_buffer_cfg;
|
|
pxp_output_buffer_config_t output_buffer_cfg;
|
|
uint8_t bytes_per_pixel;
|
|
pxp_rotate_degree_t rotate;
|
|
|
|
ARG_UNUSED(channel);
|
|
if (config->channel_direction != MEMORY_TO_MEMORY) {
|
|
return -ENOTSUP;
|
|
}
|
|
/*
|
|
* Use the DMA slot value to get the pixel format and rotation
|
|
* settings
|
|
*/
|
|
switch ((config->dma_slot & DMA_MCUX_PXP_CMD_MASK) >> DMA_MCUX_PXP_CMD_SHIFT) {
|
|
case DMA_MCUX_PXP_CMD_ROTATE_0:
|
|
rotate = kPXP_Rotate0;
|
|
break;
|
|
case DMA_MCUX_PXP_CMD_ROTATE_90:
|
|
rotate = kPXP_Rotate90;
|
|
break;
|
|
case DMA_MCUX_PXP_CMD_ROTATE_180:
|
|
rotate = kPXP_Rotate180;
|
|
break;
|
|
case DMA_MCUX_PXP_CMD_ROTATE_270:
|
|
rotate = kPXP_Rotate270;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
switch ((config->dma_slot & DMA_MCUX_PXP_FMT_MASK) >> DMA_MCUX_PXP_FMT_SHIFT) {
|
|
case DMA_MCUX_PXP_FMT_RGB565:
|
|
ps_buffer_cfg.pixelFormat = kPXP_PsPixelFormatRGB565;
|
|
output_buffer_cfg.pixelFormat = kPXP_OutputPixelFormatRGB565;
|
|
bytes_per_pixel = 2;
|
|
break;
|
|
case DMA_MCUX_PXP_FMT_RGB888:
|
|
#if (!(defined(FSL_FEATURE_PXP_HAS_NO_EXTEND_PIXEL_FORMAT) && \
|
|
FSL_FEATURE_PXP_HAS_NO_EXTEND_PIXEL_FORMAT)) && \
|
|
(!(defined(FSL_FEATURE_PXP_V3) && FSL_FEATURE_PXP_V3))
|
|
ps_buffer_cfg.pixelFormat = kPXP_PsPixelFormatARGB8888;
|
|
#else
|
|
ps_buffer_cfg.pixelFormat = kPXP_PsPixelFormatRGB888;
|
|
#endif
|
|
output_buffer_cfg.pixelFormat = kPXP_OutputPixelFormatRGB888;
|
|
bytes_per_pixel = 3;
|
|
break;
|
|
case DMA_MCUX_PXP_FMT_ARGB8888:
|
|
ps_buffer_cfg.pixelFormat = kPXP_PsPixelFormatARGB8888;
|
|
output_buffer_cfg.pixelFormat = kPXP_OutputPixelFormatARGB8888;
|
|
bytes_per_pixel = 4;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
DCACHE_CleanByRange((uint32_t)config->head_block->source_address,
|
|
config->head_block->block_size);
|
|
|
|
/*
|
|
* Some notes on how specific fields of the DMA config are used by
|
|
* the PXP:
|
|
* head block source address: PS buffer source address
|
|
* head block destination address: Output buffer address
|
|
* head block block size: size of destination and source buffer
|
|
* source data size: width of source buffer in bytes (pitch)
|
|
* source burst length: height of source buffer in pixels
|
|
* dest data size: width of destination buffer in bytes (pitch)
|
|
* dest burst length: height of destination buffer in pixels
|
|
*/
|
|
ps_buffer_cfg.swapByte = false;
|
|
ps_buffer_cfg.bufferAddr = config->head_block->source_address;
|
|
ps_buffer_cfg.bufferAddrU = 0U;
|
|
ps_buffer_cfg.bufferAddrV = 0U;
|
|
ps_buffer_cfg.pitchBytes = config->source_data_size;
|
|
PXP_SetProcessSurfaceBufferConfig(dev_config->base, &ps_buffer_cfg);
|
|
|
|
output_buffer_cfg.interlacedMode = kPXP_OutputProgressive;
|
|
output_buffer_cfg.buffer0Addr = config->head_block->dest_address;
|
|
output_buffer_cfg.buffer1Addr = 0U;
|
|
output_buffer_cfg.pitchBytes = config->dest_data_size;
|
|
output_buffer_cfg.width = (config->dest_data_size / bytes_per_pixel);
|
|
output_buffer_cfg.height = config->dest_burst_length;
|
|
PXP_SetOutputBufferConfig(dev_config->base, &output_buffer_cfg);
|
|
/* We only support a process surface that covers the full buffer */
|
|
PXP_SetProcessSurfacePosition(dev_config->base, 0U, 0U, output_buffer_cfg.width,
|
|
output_buffer_cfg.height);
|
|
/* Setup rotation */
|
|
PXP_SetRotateConfig(dev_config->base, kPXP_RotateProcessSurface, rotate, kPXP_FlipDisable);
|
|
|
|
dev_data->ps_buf_addr = config->head_block->source_address;
|
|
dev_data->ps_buf_size = config->head_block->block_size;
|
|
dev_data->out_buf_addr = config->head_block->dest_address;
|
|
dev_data->out_buf_size = config->head_block->block_size;
|
|
dev_data->dma_callback = config->dma_callback;
|
|
dev_data->user_data = config->user_data;
|
|
return 0;
|
|
}
|
|
|
|
static int dma_mcux_pxp_start(const struct device *dev, uint32_t channel)
|
|
{
|
|
const struct dma_mcux_pxp_config *config = dev->config;
|
|
struct dma_mcux_pxp_data *data = dev->data;
|
|
#ifdef CONFIG_HAS_MCUX_CACHE
|
|
DCACHE_CleanByRange((uint32_t)data->ps_buf_addr, data->ps_buf_size);
|
|
#endif
|
|
|
|
ARG_UNUSED(channel);
|
|
PXP_Start(config->base);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dma_driver_api dma_mcux_pxp_api = {
|
|
.config = dma_mcux_pxp_configure,
|
|
.start = dma_mcux_pxp_start,
|
|
};
|
|
|
|
static int dma_mcux_pxp_init(const struct device *dev)
|
|
{
|
|
const struct dma_mcux_pxp_config *config = dev->config;
|
|
|
|
PXP_Init(config->base);
|
|
PXP_SetProcessSurfaceBackGroundColor(config->base, 0U);
|
|
/* Disable alpha surface and CSC1 */
|
|
PXP_SetAlphaSurfacePosition(config->base, 0xFFFFU, 0xFFFFU, 0U, 0U);
|
|
PXP_EnableCsc1(config->base, false);
|
|
PXP_EnableInterrupts(config->base, kPXP_CompleteInterruptEnable);
|
|
config->irq_config_func(dev);
|
|
return 0;
|
|
}
|
|
|
|
#define DMA_INIT(n) \
|
|
static void dma_pxp_config_func##n(const struct device *dev) \
|
|
{ \
|
|
IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, 0), \
|
|
(IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \
|
|
dma_mcux_pxp_irq_handler, DEVICE_DT_INST_GET(n), 0); \
|
|
irq_enable(DT_INST_IRQ(n, irq));)) \
|
|
} \
|
|
\
|
|
static const struct dma_mcux_pxp_config dma_config_##n = { \
|
|
.base = (PXP_Type *)DT_INST_REG_ADDR(n), \
|
|
.irq_config_func = dma_pxp_config_func##n, \
|
|
}; \
|
|
\
|
|
static struct dma_mcux_pxp_data dma_data_##n; \
|
|
\
|
|
DEVICE_DT_INST_DEFINE(n, &dma_mcux_pxp_init, NULL, &dma_data_##n, &dma_config_##n, \
|
|
PRE_KERNEL_1, CONFIG_DMA_INIT_PRIORITY, &dma_mcux_pxp_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(DMA_INIT)
|