Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

427 lines
13 KiB

/*
* Copyright (c) 2024 tinyVision.ai Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT zephyr_video_emul_imager
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/drivers/video.h>
#include <zephyr/drivers/video-controls.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/logging/log.h>
#include "video_ctrls.h"
#include "video_device.h"
LOG_MODULE_REGISTER(video_emul_imager, CONFIG_VIDEO_LOG_LEVEL);
#define EMUL_IMAGER_REG_SENSOR_ID 0x0000
#define EMUL_IMAGER_SENSOR_ID 0x99
#define EMUL_IMAGER_REG_CTRL 0x0001
#define EMUL_IMAGER_REG_INIT1 0x0002
#define EMUL_IMAGER_REG_INIT2 0x0003
#define EMUL_IMAGER_REG_TIMING1 0x0004
#define EMUL_IMAGER_REG_TIMING2 0x0005
#define EMUL_IMAGER_REG_TIMING3 0x0006
#define EMUL_IMAGER_REG_CUSTOM 0x0007
#define EMUL_IMAGER_REG_FORMAT 0x000a
#define EMUL_IMAGER_PATTERN_OFF 0x00
#define EMUL_IMAGER_PATTERN_BARS1 0x01
#define EMUL_IMAGER_PATTERN_BARS2 0x02
/* Custom control that is just an I2C write for example and test purpose */
#define EMUL_IMAGER_CID_CUSTOM (VIDEO_CID_PRIVATE_BASE + 0x01)
/* Emulated register bank */
uint8_t emul_imager_fake_regs[10];
enum emul_imager_fmt_id {
RGB565_320x240,
YUYV_320x240,
};
struct emul_imager_reg {
uint16_t addr;
uint8_t value;
};
struct emul_imager_mode {
uint8_t fps;
/* List of registers lists to configure the various properties of the sensor.
* This permits to deduplicate the list of registers in case some lare sections
* are repeated across modes, such as the resolution for different FPS.
*/
const struct emul_imager_reg *regs[3];
/* More fields can be added according to the needs of the sensor driver */
};
struct emul_imager_config {
struct i2c_dt_spec i2c;
};
struct emul_imager_ctrls {
struct video_ctrl custom;
};
struct emul_imager_data {
/* First field is a line buffer for I/O emulation purpose */
uint8_t framebuffer[320 * sizeof(uint16_t)];
/* Other fields are shared with real hardware drivers */
const struct emul_imager_mode *mode;
enum emul_imager_fmt_id fmt_id;
struct video_format fmt;
struct emul_imager_ctrls ctrls;
};
/* All the I2C registers sent on various scenario */
static const struct emul_imager_reg emul_imager_init_regs[] = {
{EMUL_IMAGER_REG_CTRL, 0x00},
{EMUL_IMAGER_REG_INIT1, 0x10},
{EMUL_IMAGER_REG_INIT2, 0x00},
/* Undocumented registers from the vendor */
{0x1200, 0x01},
{0x1204, 0x01},
{0x1205, 0x20},
{0x1209, 0x7f},
{0},
};
static const struct emul_imager_reg emul_imager_rgb565[] = {
{EMUL_IMAGER_REG_FORMAT, 0x01},
{0},
};
static const struct emul_imager_reg emul_imager_yuyv[] = {
{EMUL_IMAGER_REG_FORMAT, 0x02},
{0},
};
static const struct emul_imager_reg emul_imager_320x240[] = {
{EMUL_IMAGER_REG_TIMING1, 0x32},
{EMUL_IMAGER_REG_TIMING2, 0x24},
{0},
};
static const struct emul_imager_reg emul_imager_15fps[] = {
{EMUL_IMAGER_REG_TIMING3, 15},
{0},
};
static const struct emul_imager_reg emul_imager_30fps[] = {
{EMUL_IMAGER_REG_TIMING3, 30},
{0},
};
static const struct emul_imager_reg emul_imager_60fps[] = {
{EMUL_IMAGER_REG_TIMING3, 60},
{0},
};
/* Description of "modes", that pick lists of registesr that will be all sentto the imager */
struct emul_imager_mode emul_imager_rgb565_320x240_modes[] = {
{.fps = 15, .regs = {emul_imager_320x240, emul_imager_rgb565, emul_imager_15fps}},
{.fps = 30, .regs = {emul_imager_320x240, emul_imager_rgb565, emul_imager_30fps}},
{.fps = 60, .regs = {emul_imager_320x240, emul_imager_rgb565, emul_imager_60fps}},
{0},
};
struct emul_imager_mode emul_imager_yuyv_320x240_modes[] = {
{.fps = 15, .regs = {emul_imager_320x240, emul_imager_yuyv, emul_imager_15fps}},
{.fps = 30, .regs = {emul_imager_320x240, emul_imager_yuyv, emul_imager_30fps}},
{0},
};
/* Summary of all the modes of all the frame formats, with indexes matching those of fmts[]. */
static const struct emul_imager_mode *emul_imager_modes[] = {
[RGB565_320x240] = emul_imager_rgb565_320x240_modes,
[YUYV_320x240] = emul_imager_yuyv_320x240_modes,
};
/* Video device capabilities where the supported resolutions and pixel formats are listed.
* The format ID is used as index to fetch the matching mode from the list above.
*/
#define EMUL_IMAGER_VIDEO_FORMAT_CAP(format, width, height) \
{ \
/* For a real imager, the width and height would be macro parameters */ \
.pixelformat = (format), \
.width_min = (width), \
.width_max = (width), \
.width_step = 0, \
.height_min = (height), \
.height_max = (height), \
.height_step = 0, \
}
static const struct video_format_cap fmts[] = {
[RGB565_320x240] = EMUL_IMAGER_VIDEO_FORMAT_CAP(VIDEO_PIX_FMT_RGB565, 320, 240),
[YUYV_320x240] = EMUL_IMAGER_VIDEO_FORMAT_CAP(VIDEO_PIX_FMT_YUYV, 320, 240),
{0},
};
/* Emulated I2C register interface, to replace with actual I2C calls for real hardware */
static int emul_imager_read_reg(const struct device *const dev, uint8_t reg_addr, uint8_t *value)
{
LOG_DBG("Placeholder for I2C read from 0x%02x", reg_addr);
switch (reg_addr) {
case EMUL_IMAGER_REG_SENSOR_ID:
*value = EMUL_IMAGER_SENSOR_ID;
break;
default:
*value = emul_imager_fake_regs[reg_addr];
}
return 0;
}
/* Some sensors will need reg8 or reg16 variants. */
static int emul_imager_write_reg(const struct device *const dev, uint8_t reg_addr, uint8_t value)
{
LOG_DBG("Placeholder for I2C write 0x%08x to 0x%02x", value, reg_addr);
emul_imager_fake_regs[reg_addr] = value;
return 0;
}
static int emul_imager_write_multi(const struct device *const dev,
const struct emul_imager_reg *regs)
{
int ret;
for (int i = 0; regs[i].addr != 0; i++) {
ret = emul_imager_write_reg(dev, regs[i].addr, regs[i].value);
if (ret < 0) {
return ret;
}
}
return 0;
}
static int emul_imager_set_ctrl(const struct device *dev, uint32_t id)
{
struct emul_imager_data *data = dev->data;
return emul_imager_write_reg(dev, EMUL_IMAGER_REG_CUSTOM, data->ctrls.custom.val);
}
/* Customize this function according to your "struct emul_imager_mode". */
static int emul_imager_set_mode(const struct device *dev, const struct emul_imager_mode *mode)
{
struct emul_imager_data *data = dev->data;
int ret;
if (data->mode == mode) {
return 0;
}
LOG_DBG("Applying mode %p at %d FPS", mode, mode->fps);
/* Apply all the configuration registers for that mode */
for (int i = 0; i < 2; i++) {
ret = emul_imager_write_multi(dev, mode->regs[i]);
if (ret < 0) {
goto err;
}
}
data->mode = mode;
return 0;
err:
LOG_ERR("Could not apply mode %p (%u FPS)", mode, mode->fps);
return ret;
}
static int emul_imager_set_frmival(const struct device *dev, enum video_endpoint_id ep,
struct video_frmival *frmival)
{
struct emul_imager_data *data = dev->data;
struct video_frmival_enum fie = {.format = &data->fmt, .discrete = *frmival};
if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
return -EINVAL;
}
video_closest_frmival(dev, ep, &fie);
LOG_DBG("Applying frame interval number %u", fie.index);
return emul_imager_set_mode(dev, &emul_imager_modes[data->fmt_id][fie.index]);
}
static int emul_imager_get_frmival(const struct device *dev, enum video_endpoint_id ep,
struct video_frmival *frmival)
{
struct emul_imager_data *data = dev->data;
if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
return -EINVAL;
}
frmival->numerator = 1;
frmival->denominator = data->mode->fps;
return 0;
}
static int emul_imager_enum_frmival(const struct device *dev, enum video_endpoint_id ep,
struct video_frmival_enum *fie)
{
const struct emul_imager_mode *mode;
size_t fmt_id;
int ret;
if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
return -EINVAL;
}
ret = video_format_caps_index(fmts, fie->format, &fmt_id);
if (ret < 0) {
return ret;
}
mode = &emul_imager_modes[fmt_id][fie->index];
fie->type = VIDEO_FRMIVAL_TYPE_DISCRETE;
fie->discrete.numerator = 1;
fie->discrete.denominator = mode->fps;
fie->index++;
return mode->fps == 0;
}
static int emul_imager_set_fmt(const struct device *const dev, enum video_endpoint_id ep,
struct video_format *fmt)
{
struct emul_imager_data *data = dev->data;
size_t fmt_id;
int ret;
if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
return -EINVAL;
}
if (memcmp(&data->fmt, fmt, sizeof(data->fmt)) == 0) {
return 0;
}
ret = video_format_caps_index(fmts, fmt, &fmt_id);
if (ret < 0) {
LOG_ERR("Format %x %ux%u not found", fmt->pixelformat, fmt->width, fmt->height);
return ret;
}
ret = emul_imager_set_mode(dev, &emul_imager_modes[fmt_id][0]);
if (ret < 0) {
return ret;
}
/* For the purpose of simulation, fill the image line buffer with 50% gray, this data
* will be collected by the video_emul_rx driver.
*/
if (fmt->pixelformat == VIDEO_PIX_FMT_RGB565) {
for (int i = 0; i < fmt->width; i++) {
((uint16_t *)data->framebuffer)[i] = sys_cpu_to_le16(0x7bef);
}
} else {
memset(data->framebuffer, 0x7f, fmt->pitch);
}
data->fmt_id = fmt_id;
data->fmt = *fmt;
return 0;
}
static int emul_imager_get_fmt(const struct device *dev, enum video_endpoint_id ep,
struct video_format *fmt)
{
struct emul_imager_data *data = dev->data;
if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
return -EINVAL;
}
*fmt = data->fmt;
return 0;
}
static int emul_imager_get_caps(const struct device *dev, enum video_endpoint_id ep,
struct video_caps *caps)
{
if (ep != VIDEO_EP_OUT && ep != VIDEO_EP_ALL) {
return -EINVAL;
}
caps->format_caps = fmts;
return 0;
}
static int emul_imager_set_stream(const struct device *dev, bool enable)
{
return emul_imager_write_reg(dev, EMUL_IMAGER_REG_CTRL, enable ? 1 : 0);
}
static DEVICE_API(video, emul_imager_driver_api) = {
.set_ctrl = emul_imager_set_ctrl,
.set_frmival = emul_imager_set_frmival,
.get_frmival = emul_imager_get_frmival,
.enum_frmival = emul_imager_enum_frmival,
.set_format = emul_imager_set_fmt,
.get_format = emul_imager_get_fmt,
.get_caps = emul_imager_get_caps,
.set_stream = emul_imager_set_stream,
};
static int emul_imager_init_controls(const struct device *dev)
{
struct emul_imager_data *drv_data = dev->data;
return video_init_ctrl(
&drv_data->ctrls.custom, dev, EMUL_IMAGER_CID_CUSTOM,
(struct video_ctrl_range){.min = 0, .max = 255, .step = 1, .def = 128});
}
int emul_imager_init(const struct device *dev)
{
struct video_format fmt;
uint8_t sensor_id;
int ret;
if (/* !i2c_is_ready_dt(&cfg->i2c) */ false) {
/* LOG_ERR("Bus %s is not ready", cfg->i2c.bus->name); */
return -ENODEV;
}
ret = emul_imager_read_reg(dev, EMUL_IMAGER_REG_SENSOR_ID, &sensor_id);
if (ret < 0 || sensor_id != EMUL_IMAGER_SENSOR_ID) {
LOG_ERR("Failed to get a correct sensor ID 0x%x", sensor_id);
return ret;
}
ret = emul_imager_write_multi(dev, emul_imager_init_regs);
if (ret < 0) {
LOG_ERR("Could not set initial registers");
return ret;
}
fmt.pixelformat = fmts[0].pixelformat;
fmt.width = fmts[0].width_min;
fmt.height = fmts[0].height_min;
fmt.pitch = fmt.width * 2;
ret = emul_imager_set_fmt(dev, VIDEO_EP_OUT, &fmt);
if (ret < 0) {
LOG_ERR("Failed to set to default format %x %ux%u",
fmt.pixelformat, fmt.width, fmt.height);
}
/* Initialize controls */
return emul_imager_init_controls(dev);
}
#define EMUL_IMAGER_DEFINE(inst) \
static struct emul_imager_data emul_imager_data_##inst; \
\
static const struct emul_imager_config emul_imager_cfg_##inst = { \
.i2c = /* I2C_DT_SPEC_INST_GET(inst) */ {0}, \
}; \
\
DEVICE_DT_INST_DEFINE(inst, &emul_imager_init, NULL, &emul_imager_data_##inst, \
&emul_imager_cfg_##inst, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, \
&emul_imager_driver_api); \
\
VIDEO_DEVICE_DEFINE(emul_imager_##inst, DEVICE_DT_INST_GET(inst), NULL);
DT_INST_FOREACH_STATUS_OKAY(EMUL_IMAGER_DEFINE)