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