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 *sensor_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 	uint32_t pixelformat;
34 	struct k_poll_signal *signal;
35 };
36 
video_pix_fmt_bpp(uint32_t pixelformat)37 static inline unsigned int video_pix_fmt_bpp(uint32_t pixelformat)
38 {
39 	switch (pixelformat) {
40 	case VIDEO_PIX_FMT_BGGR8:
41 	case VIDEO_PIX_FMT_GBRG8:
42 	case VIDEO_PIX_FMT_GRBG8:
43 	case VIDEO_PIX_FMT_RGGB8:
44 		return 1;
45 	case VIDEO_PIX_FMT_RGB565:
46 	case VIDEO_PIX_FMT_YUYV:
47 		return 2;
48 	default:
49 		return 0;
50 	}
51 }
52 
__frame_done_cb(CSI_Type * base,csi_handle_t * handle,status_t status,void * user_data)53 static void __frame_done_cb(CSI_Type *base, csi_handle_t *handle,
54 			    status_t status, void *user_data)
55 {
56 	struct video_mcux_csi_data *data = user_data;
57 	const struct device *dev = data->dev;
58 	const struct video_mcux_csi_config *config = dev->config;
59 	enum video_signal_result result = VIDEO_BUF_DONE;
60 	struct video_buffer *vbuf, *vbuf_first = NULL;
61 	uint32_t buffer_addr;
62 
63 	/* IRQ context */
64 
65 	if (status != kStatus_CSI_FrameDone) {
66 		return;
67 	}
68 
69 	status = CSI_TransferGetFullBuffer(config->base, &(data->csi_handle),
70 					   &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 
video_mcux_csi_set_fmt(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)119 static int video_mcux_csi_set_fmt(const struct device *dev,
120 				  enum video_endpoint_id ep,
121 				  struct video_format *fmt)
122 {
123 	const struct video_mcux_csi_config *config = dev->config;
124 	struct video_mcux_csi_data *data = dev->data;
125 	unsigned int bpp = video_pix_fmt_bpp(fmt->pixelformat);
126 	status_t ret;
127 
128 	if (!bpp || ep != VIDEO_EP_OUT) {
129 		return -EINVAL;
130 	}
131 
132 	data->pixelformat = fmt->pixelformat;
133 	data->csi_config.bytesPerPixel = bpp;
134 	data->csi_config.linePitch_Bytes = fmt->pitch;
135 	data->csi_config.polarityFlags = kCSI_HsyncActiveHigh | kCSI_DataLatchOnRisingEdge;
136 	data->csi_config.workMode = kCSI_GatedClockMode; /* use VSYNC, HSYNC, and PIXCLK */
137 	data->csi_config.dataBus = kCSI_DataBus8Bit;
138 	data->csi_config.useExtVsync = true;
139 	data->csi_config.height = fmt->height;
140 	data->csi_config.width = fmt->width;
141 
142 	ret = CSI_Init(config->base, &data->csi_config);
143 	if (ret != kStatus_Success) {
144 		return -EIO;
145 	}
146 
147 	ret = CSI_TransferCreateHandle(config->base, &data->csi_handle,
148 				       __frame_done_cb, data);
149 	if (ret != kStatus_Success) {
150 		return -EIO;
151 	}
152 
153 	if (config->sensor_dev && video_set_format(config->sensor_dev, ep, fmt)) {
154 		return -EIO;
155 	}
156 
157 	return 0;
158 }
159 
video_mcux_csi_get_fmt(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)160 static int video_mcux_csi_get_fmt(const struct device *dev,
161 				  enum video_endpoint_id ep,
162 				  struct video_format *fmt)
163 {
164 	struct video_mcux_csi_data *data = dev->data;
165 	const struct video_mcux_csi_config *config = dev->config;
166 
167 	if (fmt == NULL || ep != VIDEO_EP_OUT) {
168 		return -EINVAL;
169 	}
170 
171 	if (config->sensor_dev && !video_get_format(config->sensor_dev, ep, fmt)) {
172 		/* align CSI with sensor fmt */
173 		return video_mcux_csi_set_fmt(dev, ep, fmt);
174 	}
175 
176 
177 	fmt->pixelformat = data->pixelformat;
178 	fmt->height = data->csi_config.height;
179 	fmt->width = data->csi_config.width;
180 	fmt->pitch = data->csi_config.linePitch_Bytes;
181 
182 	return 0;
183 }
184 
video_mcux_csi_stream_start(const struct device * dev)185 static int video_mcux_csi_stream_start(const struct device *dev)
186 {
187 	const struct video_mcux_csi_config *config = dev->config;
188 	struct video_mcux_csi_data *data = dev->data;
189 	status_t ret;
190 
191 	ret = CSI_TransferStart(config->base, &data->csi_handle);
192 	if (ret != kStatus_Success) {
193 		return -EIO;
194 	}
195 
196 	if (config->sensor_dev && video_stream_start(config->sensor_dev)) {
197 		return -EIO;
198 	}
199 
200 	return 0;
201 }
202 
video_mcux_csi_stream_stop(const struct device * dev)203 static int video_mcux_csi_stream_stop(const struct device *dev)
204 {
205 	const struct video_mcux_csi_config *config = dev->config;
206 	struct video_mcux_csi_data *data = dev->data;
207 	status_t ret;
208 
209 	if (config->sensor_dev && video_stream_stop(config->sensor_dev)) {
210 		return -EIO;
211 	}
212 
213 	ret = CSI_TransferStop(config->base, &data->csi_handle);
214 	if (ret != kStatus_Success) {
215 		return -EIO;
216 	}
217 
218 	return 0;
219 }
220 
221 
video_mcux_csi_flush(const struct device * dev,enum video_endpoint_id ep,bool cancel)222 static int video_mcux_csi_flush(const struct device *dev,
223 				enum video_endpoint_id ep,
224 				bool cancel)
225 {
226 	const struct video_mcux_csi_config *config = dev->config;
227 	struct video_mcux_csi_data *data = dev->data;
228 	struct video_buf *vbuf;
229 	uint32_t buffer_addr;
230 	status_t ret;
231 
232 	if (!cancel) {
233 		/* wait for all buffer to be processed */
234 		do {
235 			k_sleep(K_MSEC(1));
236 		} while (!k_fifo_is_empty(&data->fifo_in));
237 	} else {
238 		/* Flush driver output queue */
239 		do {
240 			ret = CSI_TransferGetFullBuffer(config->base,
241 							&(data->csi_handle),
242 							&buffer_addr);
243 		} while (ret == kStatus_Success);
244 
245 		while ((vbuf = k_fifo_get(&data->fifo_in, K_NO_WAIT))) {
246 			k_fifo_put(&data->fifo_out, vbuf);
247 			if (IS_ENABLED(CONFIG_POLL) && data->signal) {
248 				k_poll_signal_raise(data->signal,
249 						    VIDEO_BUF_ABORTED);
250 			}
251 		}
252 	}
253 
254 	return 0;
255 }
256 
video_mcux_csi_enqueue(const struct device * dev,enum video_endpoint_id ep,struct video_buffer * vbuf)257 static int video_mcux_csi_enqueue(const struct device *dev,
258 				  enum video_endpoint_id ep,
259 				  struct video_buffer *vbuf)
260 {
261 	const struct video_mcux_csi_config *config = dev->config;
262 	struct video_mcux_csi_data *data = dev->data;
263 	unsigned int to_read;
264 	status_t ret;
265 
266 	if (ep != VIDEO_EP_OUT) {
267 		return -EINVAL;
268 	}
269 
270 	to_read = data->csi_config.linePitch_Bytes * data->csi_config.height;
271 	vbuf->bytesused = to_read;
272 
273 	ret = CSI_TransferSubmitEmptyBuffer(config->base, &data->csi_handle,
274 					    (uint32_t)vbuf->buffer);
275 	if (ret != kStatus_Success) {
276 		return -EIO;
277 	}
278 
279 	k_fifo_put(&data->fifo_in, vbuf);
280 
281 	return 0;
282 }
283 
video_mcux_csi_dequeue(const struct device * dev,enum video_endpoint_id ep,struct video_buffer ** vbuf,k_timeout_t timeout)284 static int video_mcux_csi_dequeue(const struct device *dev,
285 				  enum video_endpoint_id ep,
286 				  struct video_buffer **vbuf,
287 				  k_timeout_t timeout)
288 {
289 	struct video_mcux_csi_data *data = dev->data;
290 
291 	if (ep != VIDEO_EP_OUT) {
292 		return -EINVAL;
293 	}
294 
295 	*vbuf = k_fifo_get(&data->fifo_out, timeout);
296 	if (*vbuf == NULL) {
297 		return -EAGAIN;
298 	}
299 
300 	return 0;
301 }
302 
video_mcux_csi_set_ctrl(const struct device * dev,unsigned int cid,void * value)303 static inline int video_mcux_csi_set_ctrl(const struct device *dev,
304 					  unsigned int cid,
305 					  void *value)
306 {
307 	const struct video_mcux_csi_config *config = dev->config;
308 	int ret = -ENOTSUP;
309 
310 	/* Forward to sensor dev if any */
311 	if (config->sensor_dev) {
312 		ret = video_set_ctrl(config->sensor_dev, cid, value);
313 	}
314 
315 	return ret;
316 }
317 
video_mcux_csi_get_ctrl(const struct device * dev,unsigned int cid,void * value)318 static inline int video_mcux_csi_get_ctrl(const struct device *dev,
319 					  unsigned int cid,
320 					  void *value)
321 {
322 	const struct video_mcux_csi_config *config = dev->config;
323 	int ret = -ENOTSUP;
324 
325 	/* Forward to sensor dev if any */
326 	if (config->sensor_dev) {
327 		ret = video_get_ctrl(config->sensor_dev, cid, value);
328 	}
329 
330 	return ret;
331 }
332 
video_mcux_csi_get_caps(const struct device * dev,enum video_endpoint_id ep,struct video_caps * caps)333 static int video_mcux_csi_get_caps(const struct device *dev,
334 				   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) {
341 		return -EINVAL;
342 	}
343 
344 	/* Just forward to sensor dev for now */
345 	if (config->sensor_dev) {
346 		err = video_get_caps(config->sensor_dev, ep, caps);
347 	}
348 
349 	/* NXP MCUX CSI request at least 2 buffer before starting */
350 	caps->min_vbuf_count = 2;
351 
352 	/* no sensor dev */
353 	return err;
354 }
355 
356 extern void CSI_DriverIRQHandler(void);
video_mcux_csi_isr(const void * p)357 static void video_mcux_csi_isr(const void *p)
358 {
359 	ARG_UNUSED(p);
360 	CSI_DriverIRQHandler();
361 }
362 
video_mcux_csi_init(const struct device * dev)363 static int video_mcux_csi_init(const struct device *dev)
364 {
365 	const struct video_mcux_csi_config *config = dev->config;
366 	struct video_mcux_csi_data *data = dev->data;
367 	int err;
368 
369 	k_fifo_init(&data->fifo_in);
370 	k_fifo_init(&data->fifo_out);
371 
372 	CSI_GetDefaultConfig(&data->csi_config);
373 
374 	/* check if there is any sensor device (video ctrl device)
375 	 * the device is not yet initialized so we only check if it exists
376 	 */
377 	if (config->sensor_dev == NULL) {
378 		return -ENODEV;
379 	}
380 
381 	err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
382 	if (err) {
383 		return err;
384 	}
385 
386 	return 0;
387 }
388 
389 #ifdef CONFIG_POLL
video_mcux_csi_set_signal(const struct device * dev,enum video_endpoint_id ep,struct k_poll_signal * signal)390 static int video_mcux_csi_set_signal(const struct device *dev,
391 				     enum video_endpoint_id ep,
392 				     struct k_poll_signal *signal)
393 {
394 	struct video_mcux_csi_data *data = dev->data;
395 
396 	if (data->signal && signal != NULL) {
397 		return -EALREADY;
398 	}
399 
400 	data->signal = signal;
401 
402 	return 0;
403 }
404 #endif
405 
406 static const struct video_driver_api video_mcux_csi_driver_api = {
407 	.set_format = video_mcux_csi_set_fmt,
408 	.get_format = video_mcux_csi_get_fmt,
409 	.stream_start = video_mcux_csi_stream_start,
410 	.stream_stop = video_mcux_csi_stream_stop,
411 	.flush = video_mcux_csi_flush,
412 	.enqueue = video_mcux_csi_enqueue,
413 	.dequeue = video_mcux_csi_dequeue,
414 	.set_ctrl = video_mcux_csi_set_ctrl,
415 	.get_ctrl = video_mcux_csi_get_ctrl,
416 	.get_caps = video_mcux_csi_get_caps,
417 #ifdef CONFIG_POLL
418 	.set_signal = video_mcux_csi_set_signal,
419 #endif
420 };
421 
422 #if 1 /* Unique Instance */
423 PINCTRL_DT_INST_DEFINE(0);
424 
425 static const struct video_mcux_csi_config video_mcux_csi_config_0 = {
426 	.base = (CSI_Type *)DT_INST_REG_ADDR(0),
427 	.sensor_dev = DEVICE_DT_GET(DT_INST_PHANDLE(0, sensor)),
428 	.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0),
429 };
430 
431 static struct video_mcux_csi_data video_mcux_csi_data_0;
432 
video_mcux_csi_init_0(const struct device * dev)433 static int video_mcux_csi_init_0(const struct device *dev)
434 {
435 	struct video_mcux_csi_data *data = dev->data;
436 
437 	IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority),
438 		    video_mcux_csi_isr, NULL, 0);
439 
440 	irq_enable(DT_INST_IRQN(0));
441 
442 	data->dev = dev;
443 
444 	return video_mcux_csi_init(dev);
445 }
446 
447 /* CONFIG_KERNEL_INIT_PRIORITY_DEVICE is used to make sure the
448  * CSI peripheral is initialized before the camera, which is
449  * necessary since the clock to the camera is provided by the
450  * CSI peripheral.
451  */
452 DEVICE_DT_INST_DEFINE(0, &video_mcux_csi_init_0,
453 		    NULL, &video_mcux_csi_data_0,
454 		    &video_mcux_csi_config_0,
455 		    POST_KERNEL, CONFIG_VIDEO_MCUX_CSI_INIT_PRIORITY,
456 		    &video_mcux_csi_driver_api);
457 #endif
458