Skip to content

drivers: video: emul-imager: use the emulated I2C bus #87937

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions drivers/video/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ config VIDEO_BUFFER_SMH_ATTRIBUTE
1: SMH_REG_ATTR_NON_CACHEABLE
2: SMH_REG_ATTR_EXTERNAL

config VIDEO_I2C_RETRY_NUM
int "Number of retries after a failed I2C communication"
default 0
help
If set to 0, only a single write attempt will be done with no retry.
The default is to not retry. Board configuration files or user project can then
use the number of retries that matches their situation.

source "drivers/video/Kconfig.esp32_dvp"

source "drivers/video/Kconfig.mcux_csi"
Expand Down
1 change: 1 addition & 0 deletions drivers/video/Kconfig.emul_imager
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ config VIDEO_EMUL_IMAGER
bool "Software implementation of an imager"
depends on DT_HAS_ZEPHYR_VIDEO_EMUL_IMAGER_ENABLED
default y
depends on EMUL
help
Enable driver for the emulated Imager. A line buffer contains
the color pattern within the imager data struct, at the first
Expand Down
7 changes: 7 additions & 0 deletions drivers/video/Kconfig.emul_rx
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ config VIDEO_EMUL_RX
default y
help
Enable driver for the MIPI RX emulated DMA engine.

config VIDEO_EMUL_RX_INIT_PRIORITY
int "Emul Rx init priority"
default 61
depends on VIDEO_EMUL_RX
help
Initialization priority for the EMUL Rx device.
203 changes: 201 additions & 2 deletions drivers/video/video_common.c
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>
Expand Down Expand Up @@ -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) {
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 *)&reg_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 *)&reg_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, &reg);
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)
{
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;
}
Loading