Skip to content

Commit c8c2065

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 c53fb67 commit c8c2065

File tree

3 files changed

+295
-2
lines changed

3 files changed

+295
-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 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: 149 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>
@@ -164,3 +173,141 @@ void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep,
164173
}
165174
}
166175
}
176+
177+
int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *data)
178+
{
179+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr);
180+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr);
181+
bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr);
182+
uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr);
183+
uint8_t buf_w[sizeof(uint16_t)] = {0};
184+
uint8_t *data_ptr;
185+
int ret;
186+
187+
__ASSERT(addr_size > 0, "The address must have a address size flag");
188+
__ASSERT(data_size > 0, "The address must have a data size flag");
189+
190+
if (big_endian) {
191+
/* Casting between data sizes in big-endian requires re-aligning */
192+
*data = 0;
193+
data_ptr = (uint8_t *)data + sizeof(data) - data_size;
194+
} else {
195+
/* Casting between data sizes in little-endian is a no-op */
196+
*data = 0;
197+
data_ptr = (uint8_t *)data;
198+
}
199+
200+
for (int i = 0; i < data_size; i++) {
201+
if (addr_size == 1) {
202+
buf_w[0] = addr + i;
203+
} else {
204+
sys_put_be16(addr + i, &buf_w[0]);
205+
}
206+
207+
ret = i2c_write_read_dt(i2c, buf_w, addr_size, &data_ptr[i], 1);
208+
if (ret != 0) {
209+
LOG_ERR("Failed to read from register 0x%x", addr + i);
210+
return ret;
211+
}
212+
213+
LOG_HEXDUMP_DBG(buf_w, addr_size, "Data written to the I2C device...");
214+
LOG_HEXDUMP_DBG(&data_ptr[i], 1, "... data read back from the I2C device");
215+
}
216+
217+
*data = big_endian ? sys_be32_to_cpu(*data) : sys_le32_to_cpu(*data);
218+
219+
return 0;
220+
}
221+
222+
static int video_write_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size)
223+
{
224+
int ret = 0;
225+
226+
for (int i = 0; i < CONFIG_VIDEO_I2C_RETRY_NUM; i++) {
227+
ret = i2c_write_dt(i2c, buf_w, size);
228+
if (ret == 0) {
229+
return 0;
230+
}
231+
232+
k_sleep(K_MSEC(1));
233+
}
234+
235+
LOG_HEXDUMP_ERR(buf_w, size, "failed to write register configuration over I2C");
236+
237+
return ret;
238+
}
239+
240+
int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t data)
241+
{
242+
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr);
243+
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr);
244+
bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr);
245+
uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr);
246+
uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0};
247+
uint8_t *data_ptr;
248+
int ret;
249+
250+
__ASSERT(addr_size > 0, "The address must have a address size flag");
251+
__ASSERT(data_size > 0, "The address must have a data size flag");
252+
253+
if (big_endian) {
254+
/* Casting between data sizes in big-endian requires re-aligning */
255+
data = sys_cpu_to_be32(data);
256+
data_ptr = (uint8_t *)&data + sizeof(data) - data_size;
257+
} else {
258+
/* Casting between data sizes in little-endian is a no-op */
259+
data = sys_cpu_to_le32(data);
260+
data_ptr = (uint8_t *)&data;
261+
}
262+
263+
for (int i = 0; i < data_size; i++) {
264+
/* The address is always big-endian as per CCI standard */
265+
if (addr_size == 1) {
266+
buf_w[0] = addr + i;
267+
} else {
268+
sys_put_be16(addr + i, &buf_w[0]);
269+
}
270+
271+
buf_w[addr_size] = data_ptr[i];
272+
273+
LOG_HEXDUMP_DBG(buf_w, addr_size + 1, "Data written to the I2C device");
274+
275+
ret = video_write_reg_retry(i2c, buf_w, addr_size + 1);
276+
if (ret != 0) {
277+
LOG_ERR("Failed to write to register 0x%x", addr + i);
278+
return ret;
279+
}
280+
}
281+
282+
return 0;
283+
}
284+
285+
int video_modify_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask,
286+
uint32_t field_value)
287+
{
288+
uint32_t reg;
289+
int ret;
290+
291+
ret = video_read_cci_reg(i2c, reg_addr, &reg);
292+
if (ret != 0) {
293+
return ret;
294+
}
295+
296+
return video_write_cci_reg(i2c, reg_addr, (reg & ~field_mask) | field_value);
297+
}
298+
299+
int video_write_cci_multiregs(const struct i2c_dt_spec *i2c, const struct video_reg *regs)
300+
{
301+
int ret;
302+
303+
for (int i = 0; regs[i].addr != 0; i++) {
304+
ret = video_write_cci_reg(i2c, regs[i].addr, regs[i].data);
305+
if (ret != 0) {
306+
LOG_ERR("Failed to write 0x%04x to register 0x%02x",
307+
regs[i].data, regs[i].addr);
308+
return ret;
309+
}
310+
}
311+
312+
return 0;
313+
}

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/drivers/i2c.h>
14+
#include <zephyr/kernel.h>
15+
#include <zephyr/sys/util.h>
16+
#include <zephyr/types.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, and other flags if used with the @ref video_cci API. */
23+
uint32_t addr;
24+
/** Value to write to 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+
* Endianness 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 (uint32_t)(GENMASK(24, 24))
47+
#define VIDEO_REG_ADDR_SIZE_MASK (uint32_t)(GENMASK(23, 20))
48+
#define VIDEO_REG_DATA_SIZE_MASK (uint32_t)(GENMASK(19, 16))
49+
#define VIDEO_REG_ADDR_MASK (uint32_t)(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_modify_cci_reg(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_multiregs(const struct i2c_dt_spec *i2c, const struct video_reg *regs);
137+
138+
#endif /* ZEPHYR_DRIVERS_VIDEO_COMMON_H_ */

0 commit comments

Comments
 (0)