Skip to content

Commit 1dec42a

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 1dec42a

File tree

3 files changed

+288
-1
lines changed

3 files changed

+288
-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: 142 additions & 1 deletion
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

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

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+
* 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_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)