Skip to content

Commit 91c0397

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 e72f35b commit 91c0397

File tree

3 files changed

+330
-1
lines changed

3 files changed

+330
-1
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 attempts for retrying I2C communication if it failed"
55+
default 3
56+
help
57+
The default is there to reduce the chance of failure (i.e. occasional EMI) without
58+
flooding the I2C bus upon error with too many retries. There is a 1ms wait time between
59+
every retry.
60+
5361
source "drivers/video/Kconfig.esp32_dvp"
5462

5563
source "drivers/video/Kconfig.mcux_csi"

drivers/video/video_common.c

Lines changed: 184 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
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

1010
#include <zephyr/kernel.h>
11+
#include <zephyr/sys/byteorder.h>
1112
#include <zephyr/drivers/video.h>
13+
#include <zephyr/drivers/i2c.h>
14+
#include <zephyr/logging/log.h>
15+
16+
#include "video_common.h"
17+
18+
LOG_MODULE_REGISTER(video_common, CONFIG_VIDEO_LOG_LEVEL);
1219

1320
#if defined(CONFIG_VIDEO_BUFFER_USE_SHARED_MULTI_HEAP)
1421
#include <zephyr/multi_heap/shared_multi_heap.h>
@@ -164,3 +171,179 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep,
164171
}
165172
}
166173
}
174+
175+
int video_cci_read_reg(const struct i2c_dt_spec *i2c, uint32_t addr, uint32_t *data)
176+
{
177+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, addr);
178+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, addr);
179+
uint8_t *data_ptr = (uint8_t *)data + sizeof(uint32_t) - data_size;
180+
uint8_t buf_w[sizeof(uint16_t)] = {0};
181+
int ret;
182+
183+
*data = 0;
184+
185+
for (int i = 0; i < data_size; i++, addr += 1) {
186+
if (addr_size == 1) {
187+
buf_w[0] = addr;
188+
} else {
189+
sys_put_be16(addr, &buf_w[0]);
190+
}
191+
192+
ret = i2c_write_read_dt(i2c, buf_w, addr_size, &data_ptr[i], 1);
193+
if (ret != 0) {
194+
return ret;
195+
}
196+
}
197+
198+
*data = sys_be32_to_cpu(*data);
199+
200+
return 0;
201+
}
202+
203+
static int video_write_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size)
204+
{
205+
int ret = 0;
206+
207+
for (int i = 0; i < CONFIG_VIDEO_I2C_RETRY_NUM; i++) {
208+
ret = i2c_write_dt(i2c, buf_w, size);
209+
if (ret == 0) {
210+
return 0;
211+
}
212+
213+
k_sleep(K_MSEC(1));
214+
}
215+
216+
LOG_HEXDUMP_ERR(buf_w, size, "failed to write register configuration over I2C");
217+
218+
return ret;
219+
}
220+
221+
int video_cci_write_reg(const struct i2c_dt_spec *i2c, uint32_t addr, uint32_t data)
222+
{
223+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, addr);
224+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, addr);
225+
uint8_t *data_ptr = (uint8_t *)&data + sizeof(uint32_t) - data_size;
226+
uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0};
227+
int ret;
228+
229+
data = sys_cpu_to_be32(data);
230+
231+
for (int i = 0; i < data_size; i++, addr += 1) {
232+
if (addr_size == 1) {
233+
buf_w[0] = addr;
234+
} else {
235+
sys_put_be16(addr, &buf_w[0]);
236+
}
237+
238+
buf_w[addr_size] = data_ptr[i];
239+
240+
ret = video_write_reg_retry(i2c, buf_w, addr_size + 1);
241+
if (ret != 0) {
242+
return ret;
243+
}
244+
}
245+
246+
return 0;
247+
}
248+
249+
int video_cci_write_multi(const struct i2c_dt_spec *i2c, const struct video_reg *regs)
250+
{
251+
int ret;
252+
253+
for (int i = 0; regs[i].addr != 0; i++) {
254+
ret = video_cci_write_reg(i2c, regs[i].addr, regs[i].data);
255+
if (ret != 0) {
256+
LOG_ERR("Failed to write 0x%04x to register 0x%02x",
257+
regs[i].data, regs[i].addr);
258+
return ret;
259+
}
260+
}
261+
262+
return 0;
263+
}
264+
265+
int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t addr, uint32_t *data)
266+
{
267+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, addr);
268+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, addr);
269+
uint8_t *data_ptr = (uint8_t *)data + sizeof(uint32_t) - data_size;
270+
uint8_t buf_w[sizeof(uint16_t)] = {0};
271+
int ret;
272+
273+
*data = 0;
274+
275+
for (int i = 0; i < data_size; i++, addr += 1) {
276+
if (addr_size == 1) {
277+
buf_w[0] = addr;
278+
} else {
279+
sys_put_be16(addr, &buf_w[0]);
280+
}
281+
282+
ret = i2c_write_read_dt(i2c, buf_w, addr_size, &data_ptr[i], 1);
283+
if (ret != 0) {
284+
return ret;
285+
}
286+
}
287+
288+
*data = sys_be32_to_cpu(*data);
289+
290+
return 0;
291+
}
292+
293+
int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t addr, uint32_t data)
294+
{
295+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, addr);
296+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, addr);
297+
uint8_t *data_ptr = (uint8_t *)&data + sizeof(uint32_t) - data_size;
298+
uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0};
299+
int ret;
300+
301+
data = sys_cpu_to_be32(data);
302+
303+
for (int i = 0; i < data_size; i++, addr += 1) {
304+
if (addr_size == 1) {
305+
buf_w[0] = addr;
306+
} else {
307+
sys_put_be16(addr, &buf_w[0]);
308+
}
309+
310+
buf_w[addr_size] = data_ptr[i];
311+
312+
ret = i2c_write_dt(i2c, buf_w, addr_size + 1);
313+
if (ret != 0) {
314+
return ret;
315+
}
316+
}
317+
318+
return 0;
319+
}
320+
321+
int video_write_cci_field(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask,
322+
uint32_t field_value)
323+
{
324+
uint32_t reg;
325+
int ret;
326+
327+
ret = video_read_cci_reg(i2c, reg_addr, &reg);
328+
if (ret != 0) {
329+
return ret;
330+
}
331+
332+
return video_write_cci_reg(i2c, reg_addr, (reg & ~field_mask) | field_value);
333+
}
334+
335+
int video_write_cci_multi(const struct i2c_dt_spec *i2c, const struct video_reg *regs)
336+
{
337+
int ret;
338+
339+
for (int i = 0; regs[i].addr != 0; i++) {
340+
ret = video_write_cci_reg(i2c, regs[i].addr, regs[i].data);
341+
if (ret != 0) {
342+
LOG_ERR("Failed to write 0x%04x to register 0x%02x",
343+
regs[i].data, regs[i].addr);
344+
return ret;
345+
}
346+
}
347+
348+
return 0;
349+
}

drivers/video/video_common.h

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (c) 2025 tinyVision.ai Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_DRIVERS_VIDEO_COMMON_H_
8+
#define ZEPHYR_DRIVERS_VIDEO_COMMON_H_
9+
10+
#include <stddef.h>
11+
12+
#include <zephyr/device.h>
13+
#include <zephyr/kernel.h>
14+
#include <zephyr/types.h>
15+
#include <zephyr/sys/util.h>
16+
#include <zephyr/drivers/i2c.h>
17+
18+
/**
19+
* Type used by register tables that have either the address or value 16-bit wide.
20+
*/
21+
struct video_reg {
22+
/** Address of the register to write to as well as */
23+
uint32_t addr;
24+
/** Value to write in this address */
25+
uint32_t data;
26+
};
27+
28+
/**
29+
* @defgroup video_cci Video Camera Control Interface (CCI) handling
30+
*
31+
* The Camera Control Interface (CCI) is an I2C communication scheme part of MIPI-CSI.
32+
* It defines how register addresses and register values are packed into I2C messages.
33+
*
34+
* After the I2C device address, I2C messages payload contain:
35+
*
36+
* 1. THe 8-bit or 16-bit of the address in big-endian, written to the device.
37+
* 3. The 8-bit of the register data either read or written.
38+
*
39+
* To write to registers larger than 8-bit, multiple read/writes messages are issued.
40+
* Endianess and segmentation of larger registers are defined on a per-sensor basis.
41+
*
42+
* @{
43+
*/
44+
45+
/** @cond INTERNAL_HIDDEN */
46+
#define VIDEO_REG_ENDIANNESS_MASK GENMASK(24, 24)
47+
#define VIDEO_REG_ADDR_SIZE_MASK GENMASK(23, 20)
48+
#define VIDEO_REG_DATA_SIZE_MASK GENMASK(19, 16)
49+
#define VIDEO_REG_ADDR_MASK GENMASK(15, 0)
50+
51+
#define VIDEO_REG(addr_size, data_size, endianness) \
52+
(FIELD_PREP(VIDEO_REG_ADDR_SIZE_MASK, (addr_size)) | \
53+
FIELD_PREP(VIDEO_REG_DATA_SIZE_MASK, (data_size)) | \
54+
FIELD_PREP(VIDEO_REG_ENDIANNESS_MASK, (endianness)))
55+
/** @endcond */
56+
57+
/** Flag a register as 8-bit address size, 8-bit data size */
58+
#define VIDEO_REG_ADDR8_DATA8 VIDEO_REG(1, 1, false)
59+
/** Flag a register as 8-bit address size, 16-bit data size, little-endian */
60+
#define VIDEO_REG_ADDR8_DATA16_LE VIDEO_REG(1, 2, false)
61+
/** Flag a register as 8-bit address size, 16-bit data size, big-endian */
62+
#define VIDEO_REG_ADDR8_DATA16_BE VIDEO_REG(1, 2, true)
63+
/** Flag a register as 8-bit address size, 24-bit data size, little-endian */
64+
#define VIDEO_REG_ADDR8_DATA24_LE VIDEO_REG(1, 3, false)
65+
/** Flag a register as 8-bit address size, 24-bit data size, big-endian */
66+
#define VIDEO_REG_ADDR8_DATA24_BE VIDEO_REG(1, 3, true)
67+
/** Flag a register as 8-bit address size, 32-bit data size, little-endian */
68+
#define VIDEO_REG_ADDR8_DATA32_LE VIDEO_REG(1, 4, false)
69+
/** Flag a register as 8-bit address size, 32-bit data size, big-endian */
70+
#define VIDEO_REG_ADDR8_DATA32_BE VIDEO_REG(1, 4, true)
71+
/** Flag a register as 16-bit address size, 8-bit data size */
72+
#define VIDEO_REG_ADDR16_DATA8 VIDEO_REG(2, 1, false)
73+
/** Flag a register as 16-bit address size, 16-bit data size, little-endian */
74+
#define VIDEO_REG_ADDR16_DATA16_LE VIDEO_REG(2, 2, false)
75+
/** Flag a register as 16-bit address size, 16-bit data size, big-endian */
76+
#define VIDEO_REG_ADDR16_DATA16_BE VIDEO_REG(2, 2, true)
77+
/** Flag a register as 16-bit address size, 24-bit data size, little-endian */
78+
#define VIDEO_REG_ADDR16_DATA24_LE VIDEO_REG(2, 3, false)
79+
/** Flag a register as 16-bit address size, 24-bit data size, big-endian */
80+
#define VIDEO_REG_ADDR16_DATA24_BE VIDEO_REG(2, 3, true)
81+
/** Flag a register as 16-bit address size, 32-bit data size, little-endian */
82+
#define VIDEO_REG_ADDR16_DATA32_LE VIDEO_REG(2, 4, false)
83+
/** Flag a register as 16-bit address size, 32-bit data size, big-endian */
84+
#define VIDEO_REG_ADDR16_DATA32_BE VIDEO_REG(2, 4, true)
85+
86+
/**
87+
* @brief Write a Camera Control Interface register value with the specified address and size.
88+
*
89+
* The size of the register address and register data passed as flags in the high bits of
90+
* @p reg_addr in the unused bits of the address.
91+
*
92+
* @brief i2c Reference to the video device on an I2C bus.
93+
* @brief reg_addr Address of the register to fill with @reg_value along with size information.
94+
* @brief reg_value Value to write at this address, the size to write is encoded in the address.
95+
*/
96+
int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t reg_value);
97+
98+
99+
/**
100+
* @brief Perform a read-modify-write operation on a register given an address, mask and value.
101+
*
102+
* The size of the register address and register data passed as flags in the high bits of
103+
* @p reg_addr in the unused bits of the address.
104+
*
105+
* @brief i2c Reference to the video device on an I2C bus.
106+
* @brief reg_addr Address of the register to fill with @reg_value along with size information.
107+
* @brief field_mask Mask of the field to insert into the existing value.
108+
* @brief field_value Value to write at this address, the size to write is encoded in the address.
109+
*/
110+
int video_write_cci_field(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask,
111+
uint32_t field_value);
112+
113+
/**
114+
* @brief Read a Camera Control Interace register value from the specified address and size.
115+
*
116+
* The size of the register address and register data passed as flags in the high bits of
117+
* @p reg_addr in the unused bits of the address.
118+
*
119+
* @brief i2c Reference to the video device on an I2C bus.
120+
* @brief reg_addr Address of the register to fill with @reg_value along with size information.
121+
* @brief reg_value Value to write at this address, the size to write is encoded in the address.
122+
* This is a 32-bit integer pointer even when reading 8-bit or 16 bits value.
123+
*/
124+
int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *reg_value);
125+
126+
/**
127+
* @brief Write a complete table of registers to a device one by one.
128+
*
129+
* The address present in the registers need to be encoding the size information using the macros
130+
* such as @ref VIDEO_ADDR16_REG8(). The last element must be empty (@c {0}) to mark the end of the
131+
* table.
132+
*
133+
* @brief i2c Reference to the video device on an I2C bus.
134+
* @brief regs Array of address/value pairs to write to the device sequentially.
135+
*/
136+
int video_write_cci_multi(const struct i2c_dt_spec *i2c, const struct video_reg *regs);
137+
138+
#endif /* ZEPHYR_DRIVERS_VIDEO_COMMON_H_ */

0 commit comments

Comments
 (0)