-
Notifications
You must be signed in to change notification settings - Fork 7.6k
drivers: video: common: introduce CCI utilities #87935
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,23 @@ | ||
/* | ||
* Copyright (c) 2019, Linaro Limited | ||
* Copyright (c) 2024, tinyVision.ai Inc. | ||
* Copyright (c) 2024-2025, tinyVision.ai Inc. | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#include <string.h> | ||
|
||
#include <zephyr/kernel.h> | ||
#include <zephyr/device.h> | ||
#include <zephyr/drivers/i2c.h> | ||
#include <zephyr/drivers/video.h> | ||
#include <zephyr/kernel.h> | ||
#include <zephyr/logging/log.h> | ||
#include <zephyr/sys/byteorder.h> | ||
#include <zephyr/sys/util.h> | ||
|
||
#include "video_common.h" | ||
|
||
LOG_MODULE_REGISTER(video_common, CONFIG_VIDEO_LOG_LEVEL); | ||
|
||
#if defined(CONFIG_VIDEO_BUFFER_USE_SHARED_MULTI_HEAP) | ||
#include <zephyr/multi_heap/shared_multi_heap.h> | ||
|
@@ -166,3 +175,193 @@ void video_closest_frmival(const struct device *dev, struct video_frmival_enum * | |
} | ||
} | ||
} | ||
|
||
static int video_read_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size_w, | ||
uint8_t *buf_r, size_t size_r) | ||
{ | ||
int ret; | ||
|
||
for (int i = 0;; i++) { | ||
ret = i2c_write_read_dt(i2c, buf_w, size_w, buf_r, size_r); | ||
if (ret == 0) { | ||
break; | ||
} | ||
if (i == CONFIG_VIDEO_I2C_RETRY_NUM) { | ||
LOG_HEXDUMP_ERR(buf_w, size_w, "failed to write-read to I2C register"); | ||
return ret; | ||
} | ||
|
||
k_sleep(K_MSEC(1)); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int video_read_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t *reg_data) | ||
{ | ||
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr); | ||
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr); | ||
bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr); | ||
uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr); | ||
uint8_t buf_w[sizeof(uint16_t)] = {0}; | ||
uint8_t *data_ptr; | ||
int ret; | ||
|
||
__ASSERT(addr_size > 0, "The address must have a address size flag"); | ||
__ASSERT(data_size > 0, "The address must have a data size flag"); | ||
|
||
*reg_data = 0; | ||
|
||
if (big_endian) { | ||
/* Casting between data sizes in big-endian requires re-aligning */ | ||
data_ptr = (uint8_t *)reg_data + sizeof(*reg_data) - data_size; | ||
} else { | ||
/* Casting between data sizes in little-endian is a no-op */ | ||
data_ptr = (uint8_t *)reg_data; | ||
} | ||
|
||
for (int i = 0; i < data_size; i++) { | ||
if (addr_size == 1) { | ||
buf_w[0] = addr + i; | ||
} else { | ||
sys_put_be16(addr + i, &buf_w[0]); | ||
} | ||
|
||
ret = video_read_reg_retry(i2c, buf_w, addr_size, &data_ptr[i], 1); | ||
if (ret < 0) { | ||
LOG_ERR("Failed to read from register 0x%x", addr + i); | ||
return ret; | ||
} | ||
|
||
LOG_HEXDUMP_DBG(buf_w, addr_size, "Data written to the I2C device..."); | ||
LOG_HEXDUMP_DBG(&data_ptr[i], 1, "... data read back from the I2C device"); | ||
} | ||
|
||
*reg_data = big_endian ? sys_be32_to_cpu(*reg_data) : sys_le32_to_cpu(*reg_data); | ||
|
||
return 0; | ||
} | ||
|
||
static int video_write_reg_retry(const struct i2c_dt_spec *i2c, uint8_t *buf_w, size_t size) | ||
{ | ||
int ret; | ||
|
||
for (int i = 0;; i++) { | ||
ret = i2c_write_dt(i2c, buf_w, size); | ||
if (ret == 0) { | ||
josuah marked this conversation as resolved.
Show resolved
Hide resolved
|
||
break; | ||
} | ||
if (i == CONFIG_VIDEO_I2C_RETRY_NUM) { | ||
LOG_HEXDUMP_ERR(buf_w, size, "failed to write to I2C register"); | ||
return ret; | ||
} | ||
|
||
k_sleep(K_MSEC(1)); | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int video_write_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t reg_data) | ||
{ | ||
size_t addr_size = FIELD_GET(VIDEO_REG_ADDR_SIZE_MASK, reg_addr); | ||
size_t data_size = FIELD_GET(VIDEO_REG_DATA_SIZE_MASK, reg_addr); | ||
bool big_endian = FIELD_GET(VIDEO_REG_ENDIANNESS_MASK, reg_addr); | ||
uint16_t addr = FIELD_GET(VIDEO_REG_ADDR_MASK, reg_addr); | ||
uint8_t buf_w[sizeof(uint16_t) + sizeof(uint32_t)] = {0}; | ||
uint8_t *data_ptr; | ||
int ret; | ||
|
||
__ASSERT(addr_size > 0, "The address must have a address size flag"); | ||
__ASSERT(data_size > 0, "The address must have a data size flag"); | ||
|
||
if (big_endian) { | ||
/* Casting between data sizes in big-endian requires re-aligning */ | ||
reg_data = sys_cpu_to_be32(reg_data); | ||
data_ptr = (uint8_t *)®_data + sizeof(reg_data) - data_size; | ||
} else { | ||
/* Casting between data sizes in little-endian is a no-op */ | ||
reg_data = sys_cpu_to_le32(reg_data); | ||
data_ptr = (uint8_t *)®_data; | ||
} | ||
|
||
for (int i = 0; i < data_size; i++) { | ||
/* The address is always big-endian as per CCI standard */ | ||
if (addr_size == 1) { | ||
buf_w[0] = addr + i; | ||
} else { | ||
sys_put_be16(addr + i, &buf_w[0]); | ||
} | ||
|
||
buf_w[addr_size] = data_ptr[i]; | ||
|
||
LOG_HEXDUMP_DBG(buf_w, addr_size + 1, "Data written to the I2C device"); | ||
|
||
ret = video_write_reg_retry(i2c, buf_w, addr_size + 1); | ||
if (ret < 0) { | ||
LOG_ERR("Failed to write to register 0x%x", addr + i); | ||
return ret; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int video_modify_cci_reg(const struct i2c_dt_spec *i2c, uint32_t reg_addr, uint32_t field_mask, | ||
uint32_t field_value) | ||
{ | ||
uint32_t reg; | ||
int ret; | ||
|
||
ret = video_read_cci_reg(i2c, reg_addr, ®); | ||
if (ret < 0) { | ||
return ret; | ||
} | ||
|
||
return video_write_cci_reg(i2c, reg_addr, (reg & ~field_mask) | field_value); | ||
} | ||
|
||
int video_write_cci_multiregs(const struct i2c_dt_spec *i2c, const struct video_reg *regs, | ||
size_t num_regs) | ||
{ | ||
int ret; | ||
|
||
for (int i = 0; i < num_regs; i++) { | ||
ret = video_write_cci_reg(i2c, regs[i].addr, regs[i].data); | ||
if (ret < 0) { | ||
return ret; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int video_write_cci_multiregs8(const struct i2c_dt_spec *i2c, const struct video_reg8 *regs, | ||
size_t num_regs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the reason for doing differently between the multiregs and multiregs8/16 ? In case of multiregs, this mandate to have a last null entry then iterate until reaching it while in case of multiregs8/16 an ARRAY_SIZE has to be provided. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It is true that this can be confusing and lead to bug if there are different APIs... I will update it so that |
||
{ | ||
int ret; | ||
|
||
for (int i = 0; i < num_regs; i++) { | ||
ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR8_DATA8, regs[i].data); | ||
if (ret < 0) { | ||
return ret; | ||
} | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int video_write_cci_multiregs16(const struct i2c_dt_spec *i2c, const struct video_reg16 *regs, | ||
size_t num_regs) | ||
{ | ||
int ret; | ||
|
||
for (int i = 0; i < num_regs; i++) { | ||
ret = video_write_cci_reg(i2c, regs[i].addr | VIDEO_REG_ADDR16_DATA8, regs[i].data); | ||
if (ret < 0) { | ||
return ret; | ||
} | ||
} | ||
|
||
return 0; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason for not retry as default?
I2C usage on video is synchronous so a retry presence here would add a level of robustness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me this does not matter much, as it can be overridden easily by boards and users.
@avolmat-st I remember you discussing about the RETRY, do you have more insights on that topic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I agree that this is the good place to put the retry feature but so far I have actually seen no case where such retry is necessary. It is really board dependent I think so what would be the reason for introducing such retry by default ? If there is a HW issue or so leading to those transmit failure, better first notice them and add a retry only if necessary no ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense for me now, there is no point to put a retry without undestand the potential source of failure which is hardware dependent.
Feel free to resolve that @josuah :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense for me now, there is no point to put a retry without undestand the potential source of failure which is hardware dependent.
Feel free to resolve that @josuah :)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If putting it the default, then it might be better to put a warning when a retry happens or something to raise awareness of the user that a retry happened (i.e. something else could be going wrong).
It might be possible to start at zero and let the boards decide as they are contributed. If most ends-up wanting a retry, the default better be increased to reduce the amount of config in boards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the strategy of users would be different during development (notice the slightest issue, hardware or software related, with
CONFIG_ASSERT=y
) and production builds (take some margin to cover for unexpected EMI that might happen in the field far from the engineer supervision)... That sounds like a broader topic than this PR though (i.e. I2C-wise?) as if image sensor I2C communication fails, so might other sensors.