Skip to content

Commit d3e10fd

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 5fb47cd commit d3e10fd

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

0 commit comments

Comments
 (0)