-
Notifications
You must be signed in to change notification settings - Fork 6.1k
/
dma_mcux_smartdma.c
252 lines (215 loc) · 7.16 KB
/
dma_mcux_smartdma.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/*
* 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)