1 /*
2  * Copyright (c) 2019, Linaro Limited
3  * Copyright (c) 2024-2025, tinyVision.ai Inc.
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <string.h>
9 
10 #include <zephyr/device.h>
11 #include <zephyr/drivers/i2c.h>
12 #include <zephyr/drivers/video.h>
13 #include <zephyr/drivers/video-controls.h>
14 #include <zephyr/kernel.h>
15 #include <zephyr/logging/log.h>
16 #include <zephyr/sys/byteorder.h>
17 #include <zephyr/sys/util.h>
18 
19 #include "video_common.h"
20 
21 LOG_MODULE_REGISTER(video_common, CONFIG_VIDEO_LOG_LEVEL);
22 
23 #if defined(CONFIG_VIDEO_BUFFER_USE_SHARED_MULTI_HEAP)
24 #include <zephyr/multi_heap/shared_multi_heap.h>
25 
26 #define VIDEO_COMMON_HEAP_ALLOC(align, size, timeout)                                              \
27 	shared_multi_heap_aligned_alloc(CONFIG_VIDEO_BUFFER_SMH_ATTRIBUTE, align, size)
28 #define VIDEO_COMMON_FREE(block) shared_multi_heap_free(block)
29 #else
30 
31 #if !defined(CONFIG_VIDEO_BUFFER_POOL_ZEPHYR_REGION)
32 #define VIDEO_BUFFER_POOL_REGION_NAME __noinit_named(kheap_buf_video_buffer_pool)
33 #else
34 #define VIDEO_BUFFER_POOL_REGION_NAME Z_GENERIC_SECTION(CONFIG_VIDEO_BUFFER_POOL_ZEPHYR_REGION_NAME)
35 #endif
36 
37 /*
38  * The k_heap is manually initialized instead of using directly Z_HEAP_DEFINE_IN_SECT
39  * since the section might not be yet accessible from the beginning, making it impossible
40  * to initialize it if done via Z_HEAP_DEFINE_IN_SECT
41  */
42 static char VIDEO_BUFFER_POOL_REGION_NAME __aligned(8)
43 	video_buffer_pool_mem[MAX(CONFIG_VIDEO_BUFFER_POOL_HEAP_SIZE, Z_HEAP_MIN_SIZE)];
44 static struct k_heap video_buffer_pool;
45 static bool video_buffer_pool_initialized;
46 
video_buffer_k_heap_aligned_alloc(size_t align,size_t bytes,k_timeout_t timeout)47 static void *video_buffer_k_heap_aligned_alloc(size_t align, size_t bytes, k_timeout_t timeout)
48 {
49 	if (!video_buffer_pool_initialized) {
50 		k_heap_init(&video_buffer_pool, video_buffer_pool_mem,
51 			    MAX(CONFIG_VIDEO_BUFFER_POOL_HEAP_SIZE, Z_HEAP_MIN_SIZE));
52 		video_buffer_pool_initialized = true;
53 	}
54 
55 	return k_heap_aligned_alloc(&video_buffer_pool, align, bytes, timeout);
56 }
57 
58 #define VIDEO_COMMON_HEAP_ALLOC(align, size, timeout)                                              \
59 	video_buffer_k_heap_aligned_alloc(align, size, timeout)
60 #define VIDEO_COMMON_FREE(block) k_heap_free(&video_buffer_pool, block)
61 #endif
62 
63 static struct video_buffer video_buf[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX];
64 
65 struct mem_block {
66 	void *data;
67 };
68 
69 static struct mem_block video_block[CONFIG_VIDEO_BUFFER_POOL_NUM_MAX];
70 
video_buffer_aligned_alloc(size_t size,size_t align,k_timeout_t timeout)71 struct video_buffer *video_buffer_aligned_alloc(size_t size, size_t align, k_timeout_t timeout)
72 {
73 	struct video_buffer *vbuf = NULL;
74 	struct mem_block *block;
75 	int i;
76 
77 	/* find available video buffer */
78 	for (i = 0; i < ARRAY_SIZE(video_buf); i++) {
79 		if (video_buf[i].buffer == NULL) {
80 			vbuf = &video_buf[i];
81 			block = &video_block[i];
82 			break;
83 		}
84 	}
85 
86 	if (vbuf == NULL) {
87 		return NULL;
88 	}
89 
90 	/* Alloc buffer memory */
91 	block->data = VIDEO_COMMON_HEAP_ALLOC(align, size, timeout);
92 	if (block->data == NULL) {
93 		return NULL;
94 	}
95 
96 	vbuf->buffer = block->data;
97 	vbuf->size = size;
98 	vbuf->bytesused = 0;
99 
100 	return vbuf;
101 }
102 
video_buffer_alloc(size_t size,k_timeout_t timeout)103 struct video_buffer *video_buffer_alloc(size_t size, k_timeout_t timeout)
104 {
105 	return video_buffer_aligned_alloc(size, sizeof(void *), timeout);
106 }
107 
video_buffer_release(struct video_buffer * vbuf)108 void video_buffer_release(struct video_buffer *vbuf)
109 {
110 	struct mem_block *block = NULL;
111 	int i;
112 
113 	__ASSERT_NO_MSG(vbuf != NULL);
114 
115 	/* vbuf to block */
116 	for (i = 0; i < ARRAY_SIZE(video_block); i++) {
117 		if (video_block[i].data == vbuf->buffer) {
118 			block = &video_block[i];
119 			break;
120 		}
121 	}
122 
123 	vbuf->buffer = NULL;
124 	if (block) {
125 		VIDEO_COMMON_FREE(block->data);
126 	}
127 }
128 
video_format_caps_index(const struct video_format_cap * fmts,const struct video_format * fmt,size_t * idx)129 int video_format_caps_index(const struct video_format_cap *fmts, const struct video_format *fmt,
130 			    size_t *idx)
131 {
132 	__ASSERT_NO_MSG(fmts != NULL);
133 	__ASSERT_NO_MSG(fmt != NULL);
134 	__ASSERT_NO_MSG(idx != NULL);
135 
136 	for (int i = 0; fmts[i].pixelformat != 0; i++) {
137 		if (fmts[i].pixelformat == fmt->pixelformat &&
138 		    IN_RANGE(fmt->width, fmts[i].width_min, fmts[i].width_max) &&
139 		    IN_RANGE(fmt->height, fmts[i].height_min, fmts[i].height_max)) {
140 			*idx = i;
141 			return 0;
142 		}
143 	}
144 	return -ENOENT;
145 }
146 
video_closest_frmival_stepwise(const struct video_frmival_stepwise * stepwise,const struct video_frmival * desired,struct video_frmival * match)147 void video_closest_frmival_stepwise(const struct video_frmival_stepwise *stepwise,
148 				    const struct video_frmival *desired,
149 				    struct video_frmival *match)
150 {
151 	__ASSERT_NO_MSG(stepwise != NULL);
152 	__ASSERT_NO_MSG(desired != NULL);
153 	__ASSERT_NO_MSG(match != NULL);
154 
155 	uint64_t min = stepwise->min.numerator;
156 	uint64_t max = stepwise->max.numerator;
157 	uint64_t step = stepwise->step.numerator;
158 	uint64_t goal = desired->numerator;
159 
160 	/* Set a common denominator to all values */
161 	min *= stepwise->max.denominator * stepwise->step.denominator * desired->denominator;
162 	max *= stepwise->min.denominator * stepwise->step.denominator * desired->denominator;
163 	step *= stepwise->min.denominator * stepwise->max.denominator * desired->denominator;
164 	goal *= stepwise->min.denominator * stepwise->max.denominator * stepwise->step.denominator;
165 
166 	__ASSERT_NO_MSG(step != 0U);
167 	/* Prevent division by zero */
168 	if (step == 0U) {
169 		return;
170 	}
171 	/* Saturate the desired value to the min/max supported */
172 	goal = CLAMP(goal, min, max);
173 
174 	/* Compute a numerator and denominator */
175 	match->numerator = min + DIV_ROUND_CLOSEST(goal - min, step) * step;
176 	match->denominator = stepwise->min.denominator * stepwise->max.denominator *
177 			     stepwise->step.denominator * desired->denominator;
178 }
179 
video_closest_frmival(const struct device * dev,struct video_frmival_enum * match)180 void video_closest_frmival(const struct device *dev, struct video_frmival_enum *match)
181 {
182 	__ASSERT_NO_MSG(dev != NULL);
183 	__ASSERT_NO_MSG(match != NULL);
184 
185 	struct video_frmival desired = match->discrete;
186 	struct video_frmival_enum fie = {.format = match->format};
187 	uint64_t best_diff_nsec = INT32_MAX;
188 	uint64_t goal_nsec = video_frmival_nsec(&desired);
189 
190 	__ASSERT(match->type != VIDEO_FRMIVAL_TYPE_STEPWISE,
191 		 "cannot find range matching the range, only a value matching the range");
192 
193 	for (fie.index = 0; video_enum_frmival(dev, &fie) == 0; fie.index++) {
194 		struct video_frmival tmp = {0};
195 		uint64_t diff_nsec = 0;
196 		uint64_t tmp_nsec;
197 
198 		switch (fie.type) {
199 		case VIDEO_FRMIVAL_TYPE_DISCRETE:
200 			tmp = fie.discrete;
201 			break;
202 		case VIDEO_FRMIVAL_TYPE_STEPWISE:
203 			video_closest_frmival_stepwise(&fie.stepwise, &desired, &tmp);
204 			break;
205 		default:
206 			CODE_UNREACHABLE;
207 		}
208 
209 		tmp_nsec = video_frmival_nsec(&tmp);
210 		diff_nsec = tmp_nsec > goal_nsec ? tmp_nsec - goal_nsec : goal_nsec - tmp_nsec;
211 
212 		if (diff_nsec < best_diff_nsec) {
213 			best_diff_nsec = diff_nsec;
214 			match->index = fie.index;
215 			match->discrete = tmp;
216 		}
217 
218 		if (diff_nsec == 0) {
219 			/* Exact match, stop searching a better match */
220 			break;
221 		}
222 	}
223 }
224 
video_read_reg_retry(const struct i2c_dt_spec * i2c,uint8_t * buf_w,size_t size_w,uint8_t * buf_r,size_t size_r)225 static int video_read_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size_w,
226 				uint8_t *buf_r, size_t size_r)
227 {
228 	int ret;
229 
230 	for (int i = 0;; i++) {
231 		ret = i2c_write_read_dt(i2c, buf_w, size_w, buf_r, size_r);
232 		if (ret == 0) {
233 			break;
234 		}
235 		if (i == CONFIG_VIDEO_I2C_RETRY_NUM) {
236 			LOG_HEXDUMP_ERR(buf_w, size_w, "failed to write-read to I2C register");
237 			return ret;
238 		}
239 		if (CONFIG_VIDEO_I2C_RETRY_NUM > 0) {
240 			k_sleep(K_MSEC(1));
241 		}
242 	}
243 
244 	return 0;
245 }
246 
video_read_cci_reg(const struct i2c_dt_spec * i2c,uint32_t reg_addr,uint32_t * reg_data)247 int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *reg_data)
248 {
249 	size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr);
250 	size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr);
251 	bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr);
252 	uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr);
253 	uint8_t buf_w[sizeof(uint16_t)] = {0};
254 	uint8_t *data_ptr;
255 	int ret;
256 
257 	__ASSERT_NO_MSG(i2c != NULL);
258 	__ASSERT_NO_MSG(reg_data != NULL);
259 	__ASSERT(addr_size > 0, "The address must have a address size flag");
260 	__ASSERT(data_size > 0, "The address must have a data size flag");
261 
262 	*reg_data = 0;
263 
264 	if (big_endian) {
265 		/* Casting between data sizes in big-endian requires re-aligning */
266 		data_ptr = (uint8_t *)reg_data + sizeof(*reg_data) - data_size;
267 	} else {
268 		/* Casting between data sizes in little-endian is a no-op */
269 		data_ptr = (uint8_t *)reg_data;
270 	}
271 
272 	for (int i = 0; i < data_size; i++) {
273 		if (addr_size == 1) {
274 			buf_w[0] = addr + i;
275 		} else {
276 			sys_put_be16(addr + i, &buf_w[0]);
277 		}
278 
279 		ret = video_read_reg_retry(i2c, buf_w, addr_size, &data_ptr[i], 1);
280 		if (ret < 0) {
281 			LOG_ERR("Failed to read from register 0x%x", addr + i);
282 			return ret;
283 		}
284 
285 		LOG_HEXDUMP_DBG(buf_w, addr_size, "Data written to the I2C device...");
286 		LOG_HEXDUMP_DBG(&data_ptr[i], 1, "... data read back from the I2C device");
287 	}
288 
289 	*reg_data = big_endian ? sys_be32_to_cpu(*reg_data) : sys_le32_to_cpu(*reg_data);
290 
291 	return 0;
292 }
293 
video_write_reg_retry(const struct i2c_dt_spec * i2c,uint8_t * buf_w,size_t size)294 static int video_write_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size)
295 {
296 	int ret;
297 
298 	__ASSERT_NO_MSG(i2c != NULL);
299 	__ASSERT_NO_MSG(buf_w != NULL);
300 
301 	for (int i = 0;; i++) {
302 		ret = i2c_write_dt(i2c, buf_w, size);
303 		if (ret == 0) {
304 			break;
305 		}
306 		if (i == CONFIG_VIDEO_I2C_RETRY_NUM) {
307 			LOG_HEXDUMP_ERR(buf_w, size, "failed to write to I2C register");
308 			return ret;
309 		}
310 		if (CONFIG_VIDEO_I2C_RETRY_NUM > 0) {
311 			k_sleep(K_MSEC(1));
312 		}
313 	}
314 
315 	return 0;
316 }
317 
video_write_cci_reg(const struct i2c_dt_spec * i2c,uint32_t reg_addr,uint32_t reg_data)318 int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t reg_data)
319 {
320 	size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr);
321 	size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr);
322 	bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr);
323 	uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr);
324 	uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0};
325 	uint8_t *data_ptr;
326 	int ret;
327 
328 	__ASSERT_NO_MSG(i2c != NULL);
329 	__ASSERT(addr_size > 0, "The address must have a address size flag");
330 	__ASSERT(data_size > 0, "The address must have a data size flag");
331 
332 	if (big_endian) {
333 		/* Casting between data sizes in big-endian requires re-aligning */
334 		reg_data = sys_cpu_to_be32(reg_data);
335 		data_ptr = (uint8_t *)&reg_data + sizeof(reg_data) - data_size;
336 	} else {
337 		/* Casting between data sizes in little-endian is a no-op */
338 		reg_data = sys_cpu_to_le32(reg_data);
339 		data_ptr = (uint8_t *)&reg_data;
340 	}
341 
342 	for (int i = 0; i < data_size; i++) {
343 		/* The address is always big-endian as per CCI standard */
344 		if (addr_size == 1) {
345 			buf_w[0] = addr + i;
346 		} else {
347 			sys_put_be16(addr + i, &buf_w[0]);
348 		}
349 
350 		buf_w[addr_size] = data_ptr[i];
351 
352 		LOG_HEXDUMP_DBG(buf_w, addr_size + 1, "Data written to the I2C device");
353 
354 		ret = video_write_reg_retry(i2c, buf_w, addr_size + 1);
355 		if (ret < 0) {
356 			LOG_ERR("Failed to write to register 0x%x", addr + i);
357 			return ret;
358 		}
359 	}
360 
361 	return 0;
362 }
363 
video_modify_cci_reg(const struct i2c_dt_spec * i2c,uint32_t reg_addr,uint32_t field_mask,uint32_t field_value)364 int video_modify_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask,
365 			 uint32_t field_value)
366 {
367 	uint32_t reg;
368 	int ret;
369 
370 	ret = video_read_cci_reg(i2c, reg_addr, &reg);
371 	if (ret < 0) {
372 		return ret;
373 	}
374 
375 	return video_write_cci_reg(i2c, reg_addr, (reg & ~field_mask) | field_value);
376 }
377 
video_write_cci_multiregs(const struct i2c_dt_spec * i2c,const struct video_reg * regs,size_t num_regs)378 int video_write_cci_multiregs(const struct i2c_dt_spec *i2c, const struct video_reg *regs,
379 			      size_t num_regs)
380 {
381 	int ret;
382 
383 	__ASSERT_NO_MSG(regs != NULL);
384 
385 	for (int i = 0; i < num_regs; i++) {
386 		ret = video_write_cci_reg(i2c, regs[i].addr, regs[i].data);
387 		if (ret < 0) {
388 			return ret;
389 		}
390 	}
391 
392 	return 0;
393 }
394 
video_write_cci_multiregs8(const struct i2c_dt_spec * i2c,const struct video_reg8 * regs,size_t num_regs)395 int video_write_cci_multiregs8(const struct i2c_dt_spec *i2c, const struct video_reg8 *regs,
396 			       size_t num_regs)
397 {
398 	int ret;
399 
400 	__ASSERT_NO_MSG(regs != NULL);
401 
402 	for (int i = 0; i < num_regs; i++) {
403 		ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR8_DATA8, regs[i].data);
404 		if (ret < 0) {
405 			return ret;
406 		}
407 	}
408 
409 	return 0;
410 }
411 
video_write_cci_multiregs16(const struct i2c_dt_spec * i2c,const struct video_reg16 * regs,size_t num_regs)412 int video_write_cci_multiregs16(const struct i2c_dt_spec *i2c, const struct video_reg16 *regs,
413 				size_t num_regs)
414 {
415 	int ret;
416 
417 	__ASSERT_NO_MSG(regs != NULL);
418 
419 	for (int i = 0; i < num_regs; i++) {
420 		ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR16_DATA8, regs[i].data);
421 		if (ret < 0) {
422 			return ret;
423 		}
424 	}
425 
426 	return 0;
427 }
428 
video_get_csi_link_freq(const struct device * dev,uint8_t bpp,uint8_t lane_nb)429 int64_t video_get_csi_link_freq(const struct device *dev, uint8_t bpp, uint8_t lane_nb)
430 {
431 	struct video_control ctrl = {
432 		.id = VIDEO_CID_LINK_FREQ,
433 	};
434 	struct video_ctrl_query ctrl_query = {
435 		.dev = dev,
436 		.id = VIDEO_CID_LINK_FREQ,
437 	};
438 	int ret;
439 
440 	/* Try to get the LINK_FREQ value from the source device */
441 	ret = video_get_ctrl(dev, &ctrl);
442 	if (ret < 0) {
443 		goto fallback;
444 	}
445 
446 	ret = video_query_ctrl(&ctrl_query);
447 	if (ret < 0) {
448 		return ret;
449 	}
450 
451 	if (!IN_RANGE(ctrl.val, ctrl_query.range.min, ctrl_query.range.max)) {
452 		return -ERANGE;
453 	}
454 
455 	if (ctrl_query.int_menu == NULL) {
456 		return -EINVAL;
457 	}
458 
459 	return (int64_t)ctrl_query.int_menu[ctrl.val];
460 
461 fallback:
462 	/* If VIDEO_CID_LINK_FREQ is not available, approximate from VIDEO_CID_PIXEL_RATE */
463 	ctrl.id = VIDEO_CID_PIXEL_RATE;
464 	ret = video_get_ctrl(dev, &ctrl);
465 	if (ret < 0) {
466 		return ret;
467 	}
468 
469 	/* CSI D-PHY is using a DDR data bus so bitrate is twice the frequency */
470 	return ctrl.val64 * bpp / (2 * lane_nb);
471 }
472 
video_estimate_fmt_size(struct video_format * fmt)473 int video_estimate_fmt_size(struct video_format *fmt)
474 {
475 	if (fmt == NULL) {
476 		return -EINVAL;
477 	}
478 
479 	switch (fmt->pixelformat) {
480 	case VIDEO_PIX_FMT_JPEG:
481 	case VIDEO_PIX_FMT_H264:
482 		/* Rough estimate for the worst case (quality = 100) */
483 		fmt->pitch = 0;
484 		fmt->size = fmt->width * fmt->height * 2;
485 		break;
486 	default:
487 		/* Uncompressed format */
488 		fmt->pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE;
489 		if (fmt->pitch == 0) {
490 			return -ENOTSUP;
491 		}
492 		fmt->size = fmt->pitch * fmt->height;
493 		break;
494 	}
495 
496 	return 0;
497 }
498 
video_set_compose_format(const struct device * dev,struct video_format * fmt)499 int video_set_compose_format(const struct device *dev, struct video_format *fmt)
500 {
501 	struct video_selection sel = {
502 		.type = fmt->type,
503 		.target = VIDEO_SEL_TGT_COMPOSE,
504 		.rect.left = 0,
505 		.rect.top = 0,
506 		.rect.width = fmt->width,
507 		.rect.height = fmt->height,
508 	};
509 	int ret;
510 
511 	ret = video_set_selection(dev, &sel);
512 	if (ret < 0 && ret != -ENOSYS) {
513 		LOG_ERR("Unable to set selection compose");
514 		return ret;
515 	}
516 
517 	return video_set_format(dev, fmt);
518 }
519 
video_transfer_buffer(const struct device * src,const struct device * sink,enum video_buf_type src_type,enum video_buf_type sink_type,k_timeout_t timeout)520 int video_transfer_buffer(const struct device *src, const struct device *sink,
521 			  enum video_buf_type src_type, enum video_buf_type sink_type,
522 			  k_timeout_t timeout)
523 {
524 	struct video_buffer *buf = &(struct video_buffer){.type = src_type};
525 	int ret;
526 
527 	ret = video_dequeue(src, &buf, timeout);
528 	if (ret < 0) {
529 		return ret;
530 	}
531 
532 	buf->type = sink_type;
533 
534 	return video_enqueue(sink, buf);
535 }
536