Skip to content

Commit 71d8a7f

Browse files
committed
drivers: video: common: introduce CCI utilities
Add a library for the Camera Common Interface, part of the MIPI CSI protocol standard defining methods to configure a camera device over I2C, such as which size for the register address/data. Signed-off-by: Josuah Demangeon <me@josuah.net>
1 parent 05c7fad commit 71d8a7f

File tree

3 files changed

+442
-2
lines changed

3 files changed

+442
-2
lines changed

drivers/video/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ config VIDEO_BUFFER_SMH_ATTRIBUTE
5050
1: SMH_REG_ATTR_NON_CACHEABLE
5151
2: SMH_REG_ATTR_EXTERNAL
5252

53+
config VIDEO_I2C_RETRY_NUM
54+
int "Number of retries after a failed I2C communication"
55+
default 0
56+
help
57+
If set to 0, only a single write attempt will be done with no retry.
58+
The default is to not retry. Board configuration files or user project can then
59+
use the number of retries that matches their situation.
60+
5361
source "drivers/video/Kconfig.esp32_dvp"
5462

5563
source "drivers/video/Kconfig.mcux_csi"

drivers/video/video_common.c

Lines changed: 201 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
/*
22
* Copyright (c) 2019, Linaro Limited
3-
* Copyright (c) 2024, tinyVision.ai Inc.
3+
* Copyright (c) 2024-2025, tinyVision.ai Inc.
44
*
55
* SPDX-License-Identifier: Apache-2.0
66
*/
77

88
#include <string.h>
99

10-
#include <zephyr/kernel.h>
10+
#include <zephyr/device.h>
11+
#include <zephyr/drivers/i2c.h>
1112
#include <zephyr/drivers/video.h>
13+
#include <zephyr/kernel.h>
14+
#include <zephyr/logging/log.h>
15+
#include <zephyr/sys/byteorder.h>
16+
#include <zephyr/sys/util.h>
17+
18+
#include "video_common.h"
19+
20+
LOG_MODULE_REGISTER(video_common, CONFIG_VIDEO_LOG_LEVEL);
1221

1322
#if defined(CONFIG_VIDEO_BUFFER_USE_SHARED_MULTI_HEAP)
1423
#include <zephyr/multi_heap/shared_multi_heap.h>
@@ -166,3 +175,193 @@ void video_closest_frmival(const struct device *dev, struct video_frmival_enum *
166175
}
167176
}
168177
}
178+
179+
static int video_read_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size_w,
180+
uint8_t *buf_r, size_t size_r)
181+
{
182+
int ret;
183+
184+
for (int i = 0;; i++) {
185+
ret = i2c_write_read_dt(i2c, buf_w, size_w, buf_r, size_r);
186+
if (ret == 0) {
187+
break;
188+
}
189+
if (i == CONFIG_VIDEO_I2C_RETRY_NUM) {
190+
LOG_HEXDUMP_ERR(buf_w, size_w, "failed to write-read to I2C register");
191+
return ret;
192+
}
193+
194+
k_sleep(K_MSEC(1));
195+
}
196+
197+
return 0;
198+
}
199+
200+
int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *reg_data)
201+
{
202+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr);
203+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr);
204+
bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr);
205+
uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr);
206+
uint8_t buf_w[sizeof(uint16_t)] = {0};
207+
uint8_t *data_ptr;
208+
int ret;
209+
210+
__ASSERT(addr_size > 0, "The address must have a address size flag");
211+
__ASSERT(data_size > 0, "The address must have a data size flag");
212+
213+
*reg_data = 0;
214+
215+
if (big_endian) {
216+
/* Casting between data sizes in big-endian requires re-aligning */
217+
data_ptr = (uint8_t *)reg_data + sizeof(*reg_data) - data_size;
218+
} else {
219+
/* Casting between data sizes in little-endian is a no-op */
220+
data_ptr = (uint8_t *)reg_data;
221+
}
222+
223+
for (int i = 0; i < data_size; i++) {
224+
if (addr_size == 1) {
225+
buf_w[0] = addr + i;
226+
} else {
227+
sys_put_be16(addr + i, &buf_w[0]);
228+
}
229+
230+
ret = video_read_reg_retry(i2c, buf_w, addr_size, &data_ptr[i], 1);
231+
if (ret < 0) {
232+
LOG_ERR("Failed to read from register 0x%x", addr + i);
233+
return ret;
234+
}
235+
236+
LOG_HEXDUMP_DBG(buf_w, addr_size, "Data written to the I2C device...");
237+
LOG_HEXDUMP_DBG(&data_ptr[i], 1, "... data read back from the I2C device");
238+
}
239+
240+
*reg_data = big_endian ? sys_be32_to_cpu(*reg_data) : sys_le32_to_cpu(*reg_data);
241+
242+
return 0;
243+
}
244+
245+
static int video_write_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size)
246+
{
247+
int ret;
248+
249+
for (int i = 0;; i++) {
250+
ret = i2c_write_dt(i2c, buf_w, size);
251+
if (ret == 0) {
252+
break;
253+
}
254+
if (i == CONFIG_VIDEO_I2C_RETRY_NUM) {
255+
LOG_HEXDUMP_ERR(buf_w, size, "failed to write to I2C register");
256+
return ret;
257+
}
258+
259+
k_sleep(K_MSEC(1));
260+
}
261+
262+
return 0;
263+
}
264+
265+
int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t reg_data)
266+
{
267+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr);
268+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr);
269+
bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr);
270+
uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr);
271+
uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0};
272+
uint8_t *data_ptr;
273+
int ret;
274+
275+
__ASSERT(addr_size > 0, "The address must have a address size flag");
276+
__ASSERT(data_size > 0, "The address must have a data size flag");
277+
278+
if (big_endian) {
279+
/* Casting between data sizes in big-endian requires re-aligning */
280+
reg_data = sys_cpu_to_be32(reg_data);
281+
data_ptr = (uint8_t *)&reg_data + sizeof(reg_data) - data_size;
282+
} else {
283+
/* Casting between data sizes in little-endian is a no-op */
284+
reg_data = sys_cpu_to_le32(reg_data);
285+
data_ptr = (uint8_t *)&reg_data;
286+
}
287+
288+
for (int i = 0; i < data_size; i++) {
289+
/* The address is always big-endian as per CCI standard */
290+
if (addr_size == 1) {
291+
buf_w[0] = addr + i;
292+
} else {
293+
sys_put_be16(addr + i, &buf_w[0]);
294+
}
295+
296+
buf_w[addr_size] = data_ptr[i];
297+
298+
LOG_HEXDUMP_DBG(buf_w, addr_size + 1, "Data written to the I2C device");
299+
300+
ret = video_write_reg_retry(i2c, buf_w, addr_size + 1);
301+
if (ret < 0) {
302+
LOG_ERR("Failed to write to register 0x%x", addr + i);
303+
return ret;
304+
}
305+
}
306+
307+
return 0;
308+
}
309+
310+
int video_modify_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask,
311+
uint32_t field_value)
312+
{
313+
uint32_t reg;
314+
int ret;
315+
316+
ret = video_read_cci_reg(i2c, reg_addr, &reg);
317+
if (ret < 0) {
318+
return ret;
319+
}
320+
321+
return video_write_cci_reg(i2c, reg_addr, (reg & ~field_mask) | field_value);
322+
}
323+
324+
int video_write_cci_multiregs(const struct i2c_dt_spec *i2c, const struct video_reg *regs,
325+
size_t num_regs)
326+
{
327+
int ret;
328+
329+
for (int i = 0; i < num_regs; i++) {
330+
ret = video_write_cci_reg(i2c, regs[i].addr, regs[i].data);
331+
if (ret < 0) {
332+
return ret;
333+
}
334+
}
335+
336+
return 0;
337+
}
338+
339+
int video_write_cci_multiregs8(const struct i2c_dt_spec *i2c, const struct video_reg8 *regs,
340+
size_t num_regs)
341+
{
342+
int ret;
343+
344+
for (int i = 0; i < num_regs; i++) {
345+
ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR8_DATA8, regs[i].data);
346+
if (ret < 0) {
347+
return ret;
348+
}
349+
}
350+
351+
return 0;
352+
}
353+
354+
int video_write_cci_multiregs16(const struct i2c_dt_spec *i2c, const struct video_reg16 *regs,
355+
size_t num_regs)
356+
{
357+
int ret;
358+
359+
for (int i = 0; i < num_regs; i++) {
360+
ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR16_DATA8, regs[i].data);
361+
if (ret < 0) {
362+
return ret;
363+
}
364+
}
365+
366+
return 0;
367+
}

0 commit comments

Comments
 (0)