1 /*
2  * Copyright (c) 2019, Linaro Limited
3  * Copyright 2024 NXP
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #define DT_DRV_COMPAT nxp_imx_csi
9 
10 #include <zephyr/kernel.h>
11 #include <zephyr/irq.h>
12 #include <zephyr/drivers/video.h>
13 #include <zephyr/drivers/pinctrl.h>
14 
15 #include <fsl_csi.h>
16 
17 #ifdef CONFIG_HAS_MCUX_CACHE
18 #include <fsl_cache.h>
19 #endif
20 
21 struct video_mcux_csi_config {
22 	CSI_Type *base;
23 	const struct device *source_dev;
24 	const struct pinctrl_dev_config *pincfg;
25 };
26 
27 struct video_mcux_csi_data {
28 	const struct device *dev;
29 	csi_config_t csi_config;
30 	csi_handle_t csi_handle;
31 	struct k_fifo fifo_in;
32 	struct k_fifo fifo_out;
33 	struct k_poll_signal *signal;
34 };
35 
__frame_done_cb(CSI_Type * base,csi_handle_t * handle,status_t status,void * user_data)36 static void __frame_done_cb(CSI_Type *base, csi_handle_t *handle, status_t status, void *user_data)
37 {
38 	struct video_mcux_csi_data *data = user_data;
39 	const struct device *dev = data->dev;
40 	const struct video_mcux_csi_config *config = dev->config;
41 	enum video_signal_result result = VIDEO_BUF_DONE;
42 	struct video_buffer *vbuf, *vbuf_first = NULL;
43 	uint32_t buffer_addr;
44 
45 	/* IRQ context */
46 
47 	if (status != kStatus_CSI_FrameDone) {
48 		return;
49 	}
50 
51 	status = CSI_TransferGetFullBuffer(config->base, &(data->csi_handle), &buffer_addr);
52 	if (status != kStatus_Success) {
53 		result = VIDEO_BUF_ERROR;
54 		goto done;
55 	}
56 
57 	/* Get matching vbuf by addr */
58 	while ((vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT))) {
59 		if ((uint32_t)vbuf->buffer == buffer_addr) {
60 			break;
61 		}
62 
63 		/* should never happen on ordered stream, except on capture
64 		 * start/restart, requeue the frame and continue looking for
65 		 * the right buffer.
66 		 */
67 		k_fifo_put(&data->fifo_in, vbuf);
68 
69 		/* prevent infinite loop */
70 		if (vbuf_first == NULL) {
71 			vbuf_first = vbuf;
72 		} else if (vbuf_first == vbuf) {
73 			vbuf = NULL;
74 			break;
75 		}
76 	}
77 
78 	if (vbuf == NULL) {
79 		result = VIDEO_BUF_ERROR;
80 		goto done;
81 	}
82 
83 	vbuf->timestamp = k_uptime_get_32();
84 
85 #ifdef CONFIG_HAS_MCUX_CACHE
86 	DCACHE_InvalidateByRange(buffer_addr, vbuf->bytesused);
87 #endif
88 
89 	k_fifo_put(&data->fifo_out, vbuf);
90 
91 done:
92 	/* Trigger Event */
93 	if (IS_ENABLED(CONFIG_POLL) && data->signal) {
94 		k_poll_signal_raise(data->signal, result);
95 	}
96 
97 	return;
98 }
99 
100 #if defined(CONFIG_VIDEO_MCUX_MIPI_CSI2RX)
101 K_HEAP_DEFINE(csi_heap, 1000);
102 static struct video_format_cap *fmts;
103 /*
104  * On i.MX RT11xx SoCs which have MIPI CSI-2 Rx, image data from the camera sensor after passing
105  * through the pipeline (MIPI CSI-2 Rx --> Video Mux --> CSI) will be implicitly converted to a
106  * 32-bits pixel format. For example, an input in RGB565 or YUYV (2-bytes format) will become a
107  * XRGB32 or XYUV32 (4-bytes format) respectively, at the output of the CSI.
108  */
video_pix_fmt_convert(struct video_format * fmt,bool isGetFmt)109 static inline void video_pix_fmt_convert(struct video_format *fmt, bool isGetFmt)
110 {
111 	switch (fmt->pixelformat) {
112 	case VIDEO_PIX_FMT_XRGB32:
113 		fmt->pixelformat = isGetFmt ? VIDEO_PIX_FMT_XRGB32 : VIDEO_PIX_FMT_RGB565;
114 		break;
115 	case VIDEO_PIX_FMT_XYUV32:
116 		fmt->pixelformat = isGetFmt ? VIDEO_PIX_FMT_XYUV32 : VIDEO_PIX_FMT_YUYV;
117 		break;
118 	case VIDEO_PIX_FMT_RGB565:
119 		fmt->pixelformat = isGetFmt ? VIDEO_PIX_FMT_XRGB32 : VIDEO_PIX_FMT_RGB565;
120 		break;
121 	case VIDEO_PIX_FMT_YUYV:
122 		fmt->pixelformat = isGetFmt ? VIDEO_PIX_FMT_XYUV32 : VIDEO_PIX_FMT_YUYV;
123 		break;
124 	}
125 
126 	fmt->pitch = fmt->width * video_pix_fmt_bpp(fmt->pixelformat);
127 }
128 #endif
129 
video_mcux_csi_set_fmt(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)130 static int video_mcux_csi_set_fmt(const struct device *dev, enum video_endpoint_id ep,
131 				  struct video_format *fmt)
132 {
133 	const struct video_mcux_csi_config *config = dev->config;
134 	struct video_mcux_csi_data *data = dev->data;
135 	unsigned int bpp = video_pix_fmt_bpp(fmt->pixelformat);
136 	status_t ret;
137 	struct video_format format = *fmt;
138 
139 	if (bpp == 0 || (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL)) {
140 		return -EINVAL;
141 	}
142 
143 	data->csi_config.bytesPerPixel = bpp;
144 	data->csi_config.linePitch_Bytes = fmt->pitch;
145 #if defined(CONFIG_VIDEO_MCUX_MIPI_CSI2RX)
146 	if (fmt->pixelformat != VIDEO_PIX_FMT_XRGB32 && fmt->pixelformat != VIDEO_PIX_FMT_XYUV32) {
147 		return -ENOTSUP;
148 	}
149 	video_pix_fmt_convert(&format, false);
150 	data->csi_config.dataBus = kCSI_DataBus24Bit;
151 #else
152 	data->csi_config.dataBus = kCSI_DataBus8Bit;
153 #endif
154 	data->csi_config.polarityFlags = kCSI_HsyncActiveHigh | kCSI_DataLatchOnRisingEdge;
155 	data->csi_config.workMode = kCSI_GatedClockMode; /* use VSYNC, HSYNC, and PIXCLK */
156 	data->csi_config.useExtVsync = true;
157 	data->csi_config.height = fmt->height;
158 	data->csi_config.width = fmt->width;
159 
160 	ret = CSI_Init(config->base, &data->csi_config);
161 	if (ret != kStatus_Success) {
162 		return -EIO;
163 	}
164 
165 	ret = CSI_TransferCreateHandle(config->base, &data->csi_handle, __frame_done_cb, data);
166 	if (ret != kStatus_Success) {
167 		return -EIO;
168 	}
169 
170 	if (config->source_dev && video_set_format(config->source_dev, ep, &format)) {
171 		return -EIO;
172 	}
173 
174 	return 0;
175 }
176 
video_mcux_csi_get_fmt(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)177 static int video_mcux_csi_get_fmt(const struct device *dev, enum video_endpoint_id ep,
178 				  struct video_format *fmt)
179 {
180 	const struct video_mcux_csi_config *config = dev->config;
181 
182 	if (fmt == NULL || (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL)) {
183 		return -EINVAL;
184 	}
185 
186 	if (config->source_dev && !video_get_format(config->source_dev, ep, fmt)) {
187 #if defined(CONFIG_VIDEO_MCUX_MIPI_CSI2RX)
188 		video_pix_fmt_convert(fmt, true);
189 #endif
190 		/* align CSI with source fmt */
191 		return video_mcux_csi_set_fmt(dev, ep, fmt);
192 	}
193 
194 	return -EIO;
195 }
196 
video_mcux_csi_stream_start(const struct device * dev)197 static int video_mcux_csi_stream_start(const struct device *dev)
198 {
199 	const struct video_mcux_csi_config *config = dev->config;
200 	struct video_mcux_csi_data *data = dev->data;
201 	status_t ret;
202 
203 	ret = CSI_TransferStart(config->base, &data->csi_handle);
204 	if (ret != kStatus_Success) {
205 		return -EIO;
206 	}
207 
208 	if (config->source_dev && video_stream_start(config->source_dev)) {
209 		return -EIO;
210 	}
211 
212 	return 0;
213 }
214 
video_mcux_csi_stream_stop(const struct device * dev)215 static int video_mcux_csi_stream_stop(const struct device *dev)
216 {
217 	const struct video_mcux_csi_config *config = dev->config;
218 	struct video_mcux_csi_data *data = dev->data;
219 	status_t ret;
220 
221 	if (config->source_dev && video_stream_stop(config->source_dev)) {
222 		return -EIO;
223 	}
224 
225 	ret = CSI_TransferStop(config->base, &data->csi_handle);
226 	if (ret != kStatus_Success) {
227 		return -EIO;
228 	}
229 
230 	return 0;
231 }
232 
video_mcux_csi_flush(const struct device * dev,enum video_endpoint_id ep,bool cancel)233 static int video_mcux_csi_flush(const struct device *dev, enum video_endpoint_id ep, bool cancel)
234 {
235 	const struct video_mcux_csi_config *config = dev->config;
236 	struct video_mcux_csi_data *data = dev->data;
237 	struct video_buf *vbuf;
238 	uint32_t buffer_addr;
239 	status_t ret;
240 
241 	if (!cancel) {
242 		/* wait for all buffer to be processed */
243 		do {
244 			k_sleep(K_MSEC(1));
245 		} while (!k_fifo_is_empty(&data->fifo_in));
246 	} else {
247 		/* Flush driver output queue */
248 		do {
249 			ret = CSI_TransferGetFullBuffer(config->base, &(data->csi_handle),
250 							&buffer_addr);
251 		} while (ret == kStatus_Success);
252 
253 		while ((vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT))) {
254 			k_fifo_put(&data->fifo_out, vbuf);
255 			if (IS_ENABLED(CONFIG_POLL) && data->signal) {
256 				k_poll_signal_raise(data->signal, VIDEO_BUF_ABORTED);
257 			}
258 		}
259 	}
260 
261 	return 0;
262 }
263 
video_mcux_csi_enqueue(const struct device * dev,enum video_endpoint_id ep,struct video_buffer * vbuf)264 static int video_mcux_csi_enqueue(const struct device *dev, enum video_endpoint_id ep,
265 				  struct video_buffer *vbuf)
266 {
267 	const struct video_mcux_csi_config *config = dev->config;
268 	struct video_mcux_csi_data *data = dev->data;
269 	unsigned int to_read;
270 	status_t ret;
271 
272 	if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
273 		return -EINVAL;
274 	}
275 
276 	to_read = data->csi_config.linePitch_Bytes * data->csi_config.height;
277 	vbuf->bytesused = to_read;
278 	vbuf->line_offset = 0;
279 
280 	ret = CSI_TransferSubmitEmptyBuffer(config->base, &data->csi_handle,
281 					    (uint32_t)vbuf->buffer);
282 	if (ret != kStatus_Success) {
283 		return -EIO;
284 	}
285 
286 	k_fifo_put(&data->fifo_in, vbuf);
287 
288 	return 0;
289 }
290 
video_mcux_csi_dequeue(const struct device * dev,enum video_endpoint_id ep,struct video_buffer ** vbuf,k_timeout_t timeout)291 static int video_mcux_csi_dequeue(const struct device *dev, enum video_endpoint_id ep,
292 				  struct video_buffer **vbuf, k_timeout_t timeout)
293 {
294 	struct video_mcux_csi_data *data = dev->data;
295 
296 	if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
297 		return -EINVAL;
298 	}
299 
300 	*vbuf = k_fifo_get(&data->fifo_out, timeout);
301 	if (*vbuf == NULL) {
302 		return -EAGAIN;
303 	}
304 
305 	return 0;
306 }
307 
video_mcux_csi_set_ctrl(const struct device * dev,unsigned int cid,void * value)308 static inline int video_mcux_csi_set_ctrl(const struct device *dev, unsigned int cid, void *value)
309 {
310 	const struct video_mcux_csi_config *config = dev->config;
311 	int ret = -ENOTSUP;
312 
313 	/* Forward to source dev if any */
314 	if (config->source_dev) {
315 		ret = video_set_ctrl(config->source_dev, cid, value);
316 	}
317 
318 	return ret;
319 }
320 
video_mcux_csi_get_ctrl(const struct device * dev,unsigned int cid,void * value)321 static inline int video_mcux_csi_get_ctrl(const struct device *dev, unsigned int cid, void *value)
322 {
323 	const struct video_mcux_csi_config *config = dev->config;
324 	int ret = -ENOTSUP;
325 
326 	/* Forward to source dev if any */
327 	if (config->source_dev) {
328 		ret = video_get_ctrl(config->source_dev, cid, value);
329 	}
330 
331 	return ret;
332 }
333 
video_mcux_csi_get_caps(const struct device * dev,enum video_endpoint_id ep,struct video_caps * caps)334 static int video_mcux_csi_get_caps(const struct device *dev, enum video_endpoint_id ep,
335 				   struct video_caps *caps)
336 {
337 	const struct video_mcux_csi_config *config = dev->config;
338 	int err = -ENODEV;
339 
340 	if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
341 		return -EINVAL;
342 	}
343 
344 	/* Just forward to source dev for now */
345 	if (config->source_dev) {
346 		err = video_get_caps(config->source_dev, ep, caps);
347 #if defined(CONFIG_VIDEO_MCUX_MIPI_CSI2RX)
348 		/*
349 		 * On i.MX RT11xx SoCs which have MIPI CSI-2 Rx, image data from the camera sensor
350 		 * after passing through the pipeline (MIPI CSI-2 Rx --> Video Mux --> CSI) will be
351 		 * implicitly converted to a 32-bits pixel format. For example, an input in RGB565
352 		 * or YUYV (2-bytes format) will become an XRGB32 or XYUV32 (4-bytes format)
353 		 * respectively, at the output of the CSI. So, we change the pixel formats of the
354 		 * source caps to reflect this.
355 		 */
356 		int ind = 0;
357 
358 		while (caps->format_caps[ind].pixelformat) {
359 			ind++;
360 		}
361 		k_heap_free(&csi_heap, fmts);
362 		fmts = k_heap_alloc(&csi_heap, (ind + 1) * sizeof(struct video_format_cap),
363 				    K_FOREVER);
364 
365 		for (int i = 0; i <= ind; i++) {
366 			memcpy(&fmts[i], &caps->format_caps[i], sizeof(fmts[i]));
367 			if (fmts[i].pixelformat == VIDEO_PIX_FMT_RGB565) {
368 				fmts[i].pixelformat = VIDEO_PIX_FMT_XRGB32;
369 			} else if (fmts[i].pixelformat == VIDEO_PIX_FMT_YUYV) {
370 				fmts[i].pixelformat = VIDEO_PIX_FMT_XYUV32;
371 			}
372 		}
373 		caps->format_caps = fmts;
374 #endif
375 	}
376 
377 	/* NXP MCUX CSI request at least 2 buffer before starting */
378 	caps->min_vbuf_count = 2;
379 	/* CSI only operates on buffers of full frame size */
380 	caps->min_line_count = caps->max_line_count = LINE_COUNT_HEIGHT;
381 
382 	/* no source dev */
383 	return err;
384 }
385 
386 extern void CSI_DriverIRQHandler(void);
video_mcux_csi_isr(const void * p)387 static void video_mcux_csi_isr(const void *p)
388 {
389 	ARG_UNUSED(p);
390 	CSI_DriverIRQHandler();
391 }
392 
video_mcux_csi_init(const struct device * dev)393 static int video_mcux_csi_init(const struct device *dev)
394 {
395 	const struct video_mcux_csi_config *config = dev->config;
396 	struct video_mcux_csi_data *data = dev->data;
397 	int err;
398 
399 	k_fifo_init(&data->fifo_in);
400 	k_fifo_init(&data->fifo_out);
401 
402 	CSI_GetDefaultConfig(&data->csi_config);
403 
404 	/* check if there is any source device (video ctrl device)
405 	 * the device is not yet initialized so we only check if it exists
406 	 */
407 	if (config->source_dev == NULL) {
408 		return -ENODEV;
409 	}
410 
411 	err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
412 	if (err) {
413 		return err;
414 	}
415 
416 	return 0;
417 }
418 
419 #ifdef CONFIG_POLL
video_mcux_csi_set_signal(const struct device * dev,enum video_endpoint_id ep,struct k_poll_signal * signal)420 static int video_mcux_csi_set_signal(const struct device *dev, enum video_endpoint_id ep,
421 				     struct k_poll_signal *signal)
422 {
423 	struct video_mcux_csi_data *data = dev->data;
424 
425 	if (data->signal && signal != NULL) {
426 		return -EALREADY;
427 	}
428 
429 	data->signal = signal;
430 
431 	return 0;
432 }
433 #endif
434 
video_mcux_csi_set_frmival(const struct device * dev,enum video_endpoint_id ep,struct video_frmival * frmival)435 static int video_mcux_csi_set_frmival(const struct device *dev, enum video_endpoint_id ep,
436 				      struct video_frmival *frmival)
437 {
438 	const struct video_mcux_csi_config *config = dev->config;
439 
440 	return video_set_frmival(config->source_dev, ep, frmival);
441 }
442 
video_mcux_csi_get_frmival(const struct device * dev,enum video_endpoint_id ep,struct video_frmival * frmival)443 static int video_mcux_csi_get_frmival(const struct device *dev, enum video_endpoint_id ep,
444 				      struct video_frmival *frmival)
445 {
446 	const struct video_mcux_csi_config *config = dev->config;
447 
448 	return video_get_frmival(config->source_dev, ep, frmival);
449 }
450 
video_mcux_csi_enum_frmival(const struct device * dev,enum video_endpoint_id ep,struct video_frmival_enum * fie)451 static int video_mcux_csi_enum_frmival(const struct device *dev, enum video_endpoint_id ep,
452 				       struct video_frmival_enum *fie)
453 {
454 	const struct video_mcux_csi_config *config = dev->config;
455 	const struct video_format *fie_fmt = fie->format;
456 	int ret;
457 
458 #if defined(CONFIG_VIDEO_MCUX_MIPI_CSI2RX)
459 	struct video_format converted_fmt = *fie->format;
460 
461 	video_pix_fmt_convert(&converted_fmt, false);
462 	fie->format = &converted_fmt;
463 #endif
464 
465 	ret = video_enum_frmival(config->source_dev, ep, fie);
466 	fie->format = fie_fmt;
467 
468 	return ret;
469 }
470 
471 static DEVICE_API(video, video_mcux_csi_driver_api) = {
472 	.set_format = video_mcux_csi_set_fmt,
473 	.get_format = video_mcux_csi_get_fmt,
474 	.stream_start = video_mcux_csi_stream_start,
475 	.stream_stop = video_mcux_csi_stream_stop,
476 	.flush = video_mcux_csi_flush,
477 	.enqueue = video_mcux_csi_enqueue,
478 	.dequeue = video_mcux_csi_dequeue,
479 	.set_ctrl = video_mcux_csi_set_ctrl,
480 	.get_ctrl = video_mcux_csi_get_ctrl,
481 	.get_caps = video_mcux_csi_get_caps,
482 	.set_frmival = video_mcux_csi_set_frmival,
483 	.get_frmival = video_mcux_csi_get_frmival,
484 	.enum_frmival = video_mcux_csi_enum_frmival,
485 #ifdef CONFIG_POLL
486 	.set_signal = video_mcux_csi_set_signal,
487 #endif
488 };
489 
490 #if 1 /* Unique Instance */
491 PINCTRL_DT_INST_DEFINE(0);
492 
493 static const struct video_mcux_csi_config video_mcux_csi_config_0 = {
494 	.base = (CSI_Type *)DT_INST_REG_ADDR(0),
495 	.source_dev = DEVICE_DT_GET(DT_NODE_REMOTE_DEVICE(DT_INST_ENDPOINT_BY_ID(0, 0, 0))),
496 	.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
497 };
498 
499 static struct video_mcux_csi_data video_mcux_csi_data_0;
500 
video_mcux_csi_init_0(const struct device * dev)501 static int video_mcux_csi_init_0(const struct device *dev)
502 {
503 	struct video_mcux_csi_data *data = dev->data;
504 
505 	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), video_mcux_csi_isr, NULL, 0);
506 
507 	irq_enable(DT_INST_IRQN(0));
508 
509 	data->dev = dev;
510 
511 	return video_mcux_csi_init(dev);
512 }
513 
514 DEVICE_DT_INST_DEFINE(0, &video_mcux_csi_init_0, NULL, &video_mcux_csi_data_0,
515 		      &video_mcux_csi_config_0, POST_KERNEL, CONFIG_VIDEO_MCUX_CSI_INIT_PRIORITY,
516 		      &video_mcux_csi_driver_api);
517 #endif
518