1 /*
2 * Copyright 2024 NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT nxp_video_smartdma
8
9 #include <fsl_smartdma.h>
10 #include <fsl_inputmux.h>
11
12 #include <zephyr/drivers/dma.h>
13 #include <zephyr/drivers/dma/dma_mcux_smartdma.h>
14 #include <zephyr/drivers/video.h>
15 #include <zephyr/drivers/pinctrl.h>
16
17 #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(nxp_video_sdma);
20
21 struct nxp_video_sdma_config {
22 const struct device *dma_dev;
23 const struct device *sensor_dev;
24 const struct pinctrl_dev_config *pincfg;
25 uint8_t vsync_pin;
26 uint8_t hsync_pin;
27 uint8_t pclk_pin;
28 };
29
30 /* Firmware reads 30 lines of data per video buffer */
31 #define SDMA_LINE_COUNT 30
32 /* Firmware only supports 320x240 */
33 #define SDMA_VBUF_HEIGHT 240
34 #define SDMA_VBUF_WIDTH 320
35
36 struct nxp_video_sdma_data {
37 /* Must be aligned on 4 byte boundary, as lower 2 bits of ARM2SDMA register
38 * are used to enable interrupts
39 */
40 smartdma_camera_param_t params __aligned(4);
41 uint32_t smartdma_stack[64] __aligned(32);
42 struct k_fifo fifo_in;
43 struct k_fifo fifo_out;
44 struct k_sem stream_empty; /* Signals stream has run out of buffers */
45 bool stream_starved;
46 bool buf_reload_flag;
47 struct video_buffer *active_buf;
48 struct video_buffer *queued_buf;
49 const struct nxp_video_sdma_config *config;
50 uint32_t frame_idx;
51 };
52
53 /* Executed in interrupt context */
nxp_video_sdma_callback(const struct device * dev,void * user_data,uint32_t channel,int status)54 static void nxp_video_sdma_callback(const struct device *dev, void *user_data,
55 uint32_t channel, int status)
56 {
57 struct nxp_video_sdma_data *data = user_data;
58
59 if (status < 0) {
60 LOG_ERR("Transfer failed: %d, stopping DMA", status);
61 dma_stop(data->config->dma_dev, 0);
62 return;
63 }
64 /*
65 * SmartDMA engine streams 15 lines of RGB565 data, then interrupts the
66 * system. The engine will reload the framebuffer pointer after sending
67 * the first interrupt, and before sending the second interrupt.
68 *
69 * Based on this, we alternate between reloading the framebuffer
70 * pointer and queueing a completed frame every other interrupt
71 */
72 if (data->buf_reload_flag) {
73 /* Save old framebuffer, we will dequeue it next interrupt */
74 data->active_buf = data->queued_buf;
75 /* Load new framebuffer */
76 data->queued_buf = k_fifo_get(&data->fifo_in, K_NO_WAIT);
77 if (data->queued_buf == NULL) {
78 data->stream_starved = true;
79 } else {
80 data->params.p_buffer_ping_pong = (uint32_t *)data->queued_buf->buffer;
81 }
82 } else {
83 if (data->stream_starved) {
84 /* Signal any waiting threads */
85 k_sem_give(&data->stream_empty);
86 }
87 data->active_buf->line_offset = (data->frame_idx / 2) * SDMA_LINE_COUNT;
88 data->active_buf->timestamp = k_uptime_get_32();
89 k_fifo_put(&data->fifo_out, data->active_buf);
90
91 }
92 /* Toggle buffer reload flag*/
93 data->buf_reload_flag = !data->buf_reload_flag;
94 }
95
nxp_video_sdma_set_stream(const struct device * dev,bool enable)96 static int nxp_video_sdma_set_stream(const struct device *dev, bool enable)
97 {
98 const struct nxp_video_sdma_config *config = dev->config;
99 struct nxp_video_sdma_data *data = dev->data;
100 struct dma_config sdma_config = {0};
101 int ret;
102
103 if (!enable) {
104 return dma_stop(config->dma_dev, 0);
105 }
106
107 /* Setup dma configuration for SmartDMA */
108 sdma_config.dma_slot = kSMARTDMA_CameraDiv16FrameQVGA;
109 sdma_config.dma_callback = nxp_video_sdma_callback;
110 sdma_config.user_data = data;
111 /* Setting bit 1 here enables the SmartDMA to interrupt ARM core
112 * when writing to SMARTDMA2ARM register
113 */
114 sdma_config.head_block = (struct dma_block_config *)(((uint32_t)&data->params) | 0x2);
115
116 /* Setup parameters for SmartDMA engine */
117 data->params.smartdma_stack = data->smartdma_stack;
118 /* SmartDMA continuously streams data once started. If user
119 * has not provided a framebuffer, we can't start DMA.
120 */
121 data->queued_buf = k_fifo_get(&data->fifo_in, K_NO_WAIT);
122 if (data->queued_buf == NULL) {
123 return -EIO;
124 }
125 data->params.p_buffer_ping_pong = (uint32_t *)data->queued_buf->buffer;
126 /* The firmware writes the index of the frame slice
127 * (from 0-15) into this buffer
128 */
129 data->params.p_stripe_index = &data->frame_idx;
130
131 /* Start DMA engine */
132 ret = dma_config(config->dma_dev, 0, &sdma_config);
133 if (ret < 0) {
134 return ret;
135 }
136 /* Reset stream state variables */
137 k_sem_reset(&data->stream_empty);
138 data->buf_reload_flag = true;
139 data->stream_starved = false;
140
141 ret = dma_start(config->dma_dev, 0);
142 if (ret < 0) {
143 return ret;
144 }
145
146 return 0;
147 }
148
nxp_video_sdma_enqueue(const struct device * dev,enum video_endpoint_id ep,struct video_buffer * vbuf)149 static int nxp_video_sdma_enqueue(const struct device *dev,
150 enum video_endpoint_id ep,
151 struct video_buffer *vbuf)
152 {
153 struct nxp_video_sdma_data *data = dev->data;
154
155 if (ep != VIDEO_EP_OUT) {
156 return -EINVAL;
157 }
158
159 /* SmartDMA will read 30 lines of RGB565 video data into framebuffer */
160 vbuf->bytesused = SDMA_VBUF_WIDTH * SDMA_LINE_COUNT * sizeof(uint16_t);
161 if (vbuf->size < vbuf->bytesused) {
162 return -EINVAL;
163 }
164
165 /* Put buffer into FIFO */
166 k_fifo_put(&data->fifo_in, vbuf);
167 if (data->stream_starved) {
168 /* Kick SmartDMA off */
169 nxp_video_sdma_set_stream(dev, true);
170 }
171 return 0;
172 }
173
nxp_video_sdma_dequeue(const struct device * dev,enum video_endpoint_id ep,struct video_buffer ** vbuf,k_timeout_t timeout)174 static int nxp_video_sdma_dequeue(const struct device *dev,
175 enum video_endpoint_id ep,
176 struct video_buffer **vbuf,
177 k_timeout_t timeout)
178 {
179 struct nxp_video_sdma_data *data = dev->data;
180
181 if (ep != VIDEO_EP_OUT) {
182 return -EINVAL;
183 }
184
185 *vbuf = k_fifo_get(&data->fifo_out, timeout);
186 if (*vbuf == NULL) {
187 return -EAGAIN;
188 }
189
190 return 0;
191 }
192
nxp_video_sdma_flush(const struct device * dev,enum video_endpoint_id ep,bool cancel)193 static int nxp_video_sdma_flush(const struct device *dev,
194 enum video_endpoint_id ep,
195 bool cancel)
196 {
197 const struct nxp_video_sdma_config *config = dev->config;
198 struct nxp_video_sdma_data *data = dev->data;
199 struct video_buf *vbuf;
200
201 if (!cancel) {
202 /* Wait for DMA to signal it is empty */
203 k_sem_take(&data->stream_empty, K_FOREVER);
204 } else {
205 /* Stop DMA engine */
206 dma_stop(config->dma_dev, 0);
207 /* Forward all buffers in fifo_in to fifo_out */
208 while ((vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT))) {
209 k_fifo_put(&data->fifo_out, vbuf);
210 }
211 }
212 return 0;
213 }
214
215 /* SDMA only supports 320x240 RGB565 */
216 static const struct video_format_cap fmts[] = {
217 {
218 .pixelformat = VIDEO_PIX_FMT_RGB565,
219 .width_min = SDMA_VBUF_WIDTH,
220 .width_max = SDMA_VBUF_WIDTH,
221 .height_min = SDMA_VBUF_HEIGHT,
222 .height_max = SDMA_VBUF_HEIGHT,
223 .width_step = 0,
224 .height_step = 0,
225 },
226 { 0 },
227 };
228
nxp_video_sdma_set_format(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)229 static int nxp_video_sdma_set_format(const struct device *dev,
230 enum video_endpoint_id ep,
231 struct video_format *fmt)
232 {
233 const struct nxp_video_sdma_config *config = dev->config;
234
235 if (fmt == NULL || ep != VIDEO_EP_OUT) {
236 return -EINVAL;
237 }
238
239 if (!device_is_ready(config->sensor_dev)) {
240 LOG_ERR("Sensor device not ready");
241 return -ENODEV;
242 }
243
244 if ((fmt->pixelformat != fmts[0].pixelformat) ||
245 (fmt->width != fmts[0].width_min) ||
246 (fmt->height != fmts[0].height_min) ||
247 (fmt->pitch != fmts[0].width_min * 2)) {
248 LOG_ERR("Unsupported format");
249 return -ENOTSUP;
250 }
251
252 /* Forward format to sensor device */
253 return video_set_format(config->sensor_dev, ep, fmt);
254 }
255
nxp_video_sdma_get_format(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)256 static int nxp_video_sdma_get_format(const struct device *dev,
257 enum video_endpoint_id ep,
258 struct video_format *fmt)
259 {
260 const struct nxp_video_sdma_config *config = dev->config;
261 int ret;
262
263 if (fmt == NULL || ep != VIDEO_EP_OUT) {
264 return -EINVAL;
265 }
266
267 if (!device_is_ready(config->sensor_dev)) {
268 LOG_ERR("Sensor device not ready");
269 return -ENODEV;
270 }
271
272 /*
273 * Check sensor format. If it is not RGB565 320x240
274 * reconfigure the sensor,
275 * as this is the only format supported.
276 */
277 ret = video_get_format(config->sensor_dev, VIDEO_EP_OUT, fmt);
278 if (ret < 0) {
279 return ret;
280 }
281
282 /* Verify that format is RGB565 */
283 if ((fmt->pixelformat != fmts[0].pixelformat) ||
284 (fmt->width != fmts[0].width_min) ||
285 (fmt->height != fmts[0].height_min) ||
286 (fmt->pitch != fmts[0].width_min * 2)) {
287 /* Update format of sensor */
288 fmt->pixelformat = fmts[0].pixelformat;
289 fmt->width = fmts[0].width_min;
290 fmt->height = fmts[0].height_min;
291 fmt->pitch = fmts[0].width_min * 2;
292 ret = video_set_format(config->sensor_dev, VIDEO_EP_OUT, fmt);
293 if (ret < 0) {
294 LOG_ERR("Sensor device does not support RGB565");
295 return ret;
296 }
297 }
298
299 return 0;
300 }
301
nxp_video_sdma_get_caps(const struct device * dev,enum video_endpoint_id ep,struct video_caps * caps)302 static int nxp_video_sdma_get_caps(const struct device *dev,
303 enum video_endpoint_id ep,
304 struct video_caps *caps)
305 {
306 if (ep != VIDEO_EP_OUT) {
307 return -EINVAL;
308 }
309
310 /* SmartDMA needs at least two buffers allocated before starting */
311 caps->min_vbuf_count = 2;
312 /* Firmware reads 30 lines per queued vbuf */
313 caps->min_line_count = caps->max_line_count = SDMA_LINE_COUNT;
314 caps->format_caps = fmts;
315 return 0;
316 }
317
nxp_video_sdma_init(const struct device * dev)318 static int nxp_video_sdma_init(const struct device *dev)
319 {
320 const struct nxp_video_sdma_config *config = dev->config;
321 struct nxp_video_sdma_data *data = dev->data;
322 int ret;
323
324 if (!device_is_ready(config->dma_dev)) {
325 LOG_ERR("SmartDMA not ready");
326 return -ENODEV;
327 }
328
329 INPUTMUX_Init(INPUTMUX0);
330 /* Attach Camera VSYNC, HSYNC, and PCLK as inputs 0, 1, and 2 of the SmartDMA */
331 INPUTMUX_AttachSignal(INPUTMUX0, 0,
332 config->vsync_pin + (SMARTDMAARCHB_INMUX0 << PMUX_SHIFT));
333 INPUTMUX_AttachSignal(INPUTMUX0, 1,
334 config->hsync_pin + (SMARTDMAARCHB_INMUX0 << PMUX_SHIFT));
335 INPUTMUX_AttachSignal(INPUTMUX0, 2,
336 config->pclk_pin + (SMARTDMAARCHB_INMUX0 << PMUX_SHIFT));
337 /* Turnoff clock to inputmux to save power. Clock is only needed to make changes */
338 INPUTMUX_Deinit(INPUTMUX0);
339
340 k_fifo_init(&data->fifo_in);
341 k_fifo_init(&data->fifo_out);
342 /* Given to when the DMA engine runs out of buffers */
343 k_sem_init(&data->stream_empty, 0, 1);
344
345 /* Install camera firmware used by SmartDMA */
346 dma_smartdma_install_fw(config->dma_dev, (uint8_t *)s_smartdmaCameraFirmware,
347 s_smartdmaCameraFirmwareSize);
348 ret = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
349 if (ret < 0) {
350 return ret;
351 }
352
353 return 0;
354 }
355
356 static DEVICE_API(video, nxp_video_sdma_api) = {
357 .get_format = nxp_video_sdma_get_format,
358 .set_format = nxp_video_sdma_set_format,
359 .get_caps = nxp_video_sdma_get_caps,
360 .set_stream = nxp_video_sdma_set_stream,
361 .enqueue = nxp_video_sdma_enqueue,
362 .dequeue = nxp_video_sdma_dequeue,
363 .flush = nxp_video_sdma_flush
364 };
365
366 #define NXP_VIDEO_SDMA_INIT(inst) \
367 PINCTRL_DT_INST_DEFINE(inst); \
368 const struct nxp_video_sdma_config sdma_config_##inst = { \
369 .dma_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
370 .sensor_dev = DEVICE_DT_GET(DT_INST_PHANDLE(inst, sensor)), \
371 .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
372 .vsync_pin = DT_INST_PROP(inst, vsync_pin), \
373 .hsync_pin = DT_INST_PROP(inst, hsync_pin), \
374 .pclk_pin = DT_INST_PROP(inst, pclk_pin), \
375 }; \
376 struct nxp_video_sdma_data sdma_data_##inst = { \
377 .config = &sdma_config_##inst, \
378 }; \
379 \
380 DEVICE_DT_INST_DEFINE(inst, nxp_video_sdma_init, NULL, \
381 &sdma_data_##inst, &sdma_config_##inst, \
382 POST_KERNEL, \
383 CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
384 &nxp_video_sdma_api);
385
386 DT_INST_FOREACH_STATUS_OKAY(NXP_VIDEO_SDMA_INIT)
387