/* * Copyright (c) 2024 tinyVision.ai Inc. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT zephyr_video_emul_imager #include #include #include #include #include #include #include #include #include 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_EXPOSURE 0x0007 #define EMUL_IMAGER_REG_GAIN 0x0008 #define EMUL_IMAGER_REG_PATTERN 0x0009 #define EMUL_IMAGER_PATTERN_OFF 0x00 #define EMUL_IMAGER_PATTERN_BARS1 0x01 #define EMUL_IMAGER_PATTERN_BARS2 0x02 /* Emulated register bank */ uint8_t emul_imager_fake_regs[10]; enum emul_imager_fmt_id { RGB565_64x20, YUYV_64x20, }; 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[2]; /* 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_data { /* First field is a framebuffer for I/O emulation purpose */ uint8_t framebuffer[CONFIG_VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE]; /* 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; }; /* Initial parameters of the sensors common to all modes. */ static const struct emul_imager_reg emul_imager_init_regs[] = { {EMUL_IMAGER_REG_CTRL, 0x00}, /* Example comment about REG_INIT1 */ {EMUL_IMAGER_REG_INIT1, 0x10}, {EMUL_IMAGER_REG_INIT2, 0x00}, {0}, }; /* List of registers aggregated together in "modes" that can be applied * to set the timing parameters and other mode-dependent configuration. */ static const struct emul_imager_reg emul_imager_rgb565_64x20[] = { {EMUL_IMAGER_REG_TIMING1, 0x64}, {EMUL_IMAGER_REG_TIMING2, 0x20}, {0}, }; static const struct emul_imager_reg emul_imager_rgb565_64x20_15fps[] = { {EMUL_IMAGER_REG_TIMING3, 15}, {0}, }; static const struct emul_imager_reg emul_imager_rgb565_64x20_30fps[] = { {EMUL_IMAGER_REG_TIMING3, 30}, {0}, }; static const struct emul_imager_reg emul_imager_rgb565_64x20_60fps[] = { {EMUL_IMAGER_REG_TIMING3, 60}, {0}, }; struct emul_imager_mode emul_imager_rgb565_64x20_modes[] = { {.fps = 15, .regs = {emul_imager_rgb565_64x20, emul_imager_rgb565_64x20_15fps}}, {.fps = 30, .regs = {emul_imager_rgb565_64x20, emul_imager_rgb565_64x20_30fps}}, {.fps = 60, .regs = {emul_imager_rgb565_64x20, emul_imager_rgb565_64x20_60fps}}, {0}, }; static const struct emul_imager_reg emul_imager_yuyv_64x20[] = { {EMUL_IMAGER_REG_TIMING1, 0x64}, {EMUL_IMAGER_REG_TIMING2, 0x20}, {0}, }; static const struct emul_imager_reg emul_imager_yuyv_64x20_15fps[] = { {EMUL_IMAGER_REG_TIMING3, 15}, {0}, }; static const struct emul_imager_reg emul_imager_yuyv_64x20_30fps[] = { {EMUL_IMAGER_REG_TIMING3, 30}, {0}, }; struct emul_imager_mode emul_imager_yuyv_64x20_modes[] = { {.fps = 15, .regs = {emul_imager_yuyv_64x20, emul_imager_yuyv_64x20_15fps}}, {.fps = 30, .regs = {emul_imager_yuyv_64x20, emul_imager_yuyv_64x20_30fps}}, {0}, }; /* Summary of all the modes of all the frame formats, with the format ID as * index, matching fmts[]. */ static const struct emul_imager_mode *emul_imager_modes[] = { [RGB565_64x20] = emul_imager_rgb565_64x20_modes, [YUYV_64x20] = emul_imager_yuyv_64x20_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(width, height, format) \ { \ .pixelformat = (format), \ .width_min = (width), \ .width_max = (width), \ .height_min = (height), \ .height_max = (height), \ .width_step = 0, \ .height_step = 0, \ } static const struct video_format_cap fmts[] = { [RGB565_64x20] = EMUL_IMAGER_VIDEO_FORMAT_CAP(64, 20, VIDEO_PIX_FMT_RGB565), [YUYV_64x20] = EMUL_IMAGER_VIDEO_FORMAT_CAP(64, 20, VIDEO_PIX_FMT_YUYV), {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("%s placeholder for I2C read from 0x%02x", dev->name, 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; } /* Helper to read a full integer directly from a register */ static int emul_imager_read_int(const struct device *const dev, uint8_t reg_addr, int *value) { uint8_t val8; int ret; ret = emul_imager_read_reg(dev, reg_addr, &val8); *value = val8; return ret; } /* 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("%s placeholder for I2C write 0x%08x to 0x%02x", dev->name, 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, unsigned int cid, void *value) { switch (cid) { case VIDEO_CID_EXPOSURE: return emul_imager_write_reg(dev, EMUL_IMAGER_REG_EXPOSURE, (int)value); case VIDEO_CID_GAIN: return emul_imager_write_reg(dev, EMUL_IMAGER_REG_GAIN, (int)value); case VIDEO_CID_TEST_PATTERN: return emul_imager_write_reg(dev, EMUL_IMAGER_REG_PATTERN, (int)value); default: return -ENOTSUP; } } static int emul_imager_get_ctrl(const struct device *dev, unsigned int cid, void *value) { struct emul_imager_data *data = dev->data; switch (cid) { case VIDEO_CID_EXPOSURE: return emul_imager_read_int(dev, EMUL_IMAGER_REG_EXPOSURE, value); case VIDEO_CID_GAIN: return emul_imager_read_int(dev, EMUL_IMAGER_REG_GAIN, value); case VIDEO_CID_TEST_PATTERN: return emul_imager_read_int(dev, EMUL_IMAGER_REG_PATTERN, value); case VIDEO_CID_PIXEL_RATE: *(int64_t *)value = (int64_t)data->fmt.width * data->fmt.pitch * data->mode->fps; return 0; default: return -ENOTSUP; } } /* 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 %s mode %p (%u FPS)", dev->name, 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; } /* White, Yellow, Cyan, Green, Magenta, Red, Blue, Black */ static const uint16_t pattern_8bars_yuv[8][3] = { {0xFF, 0x7F, 0x7F}, {0xFF, 0x00, 0xFF}, {0xFF, 0xFF, 0x00}, {0x7F, 0x00, 0x00}, {0x00, 0xFF, 0xFF}, {0x00, 0x00, 0xFF}, {0x00, 0xFF, 0x00}, {0x00, 0x7F, 0x7F}}; static const uint16_t pattern_8bars_rgb[8][3] = { {0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0x00}, {0x00, 0xFF, 0xFF}, {0x00, 0xFF, 0x00}, {0xFF, 0x00, 0xFF}, {0xFF, 0x00, 0x00}, {0x00, 0x00, 0xFF}, {0x00, 0x00, 0x00}}; static void emul_imager_fill_framebuffer(const struct device *const dev, struct video_format *fmt) { struct emul_imager_data *data = dev->data; uint16_t *fb16 = (uint16_t *)data->framebuffer; uint16_t r, g, b, y, uv; /* Fill the first row of the emulated framebuffer */ switch (fmt->pixelformat) { case VIDEO_PIX_FMT_YUYV: for (size_t i = 0; i < fmt->width; i++) { y = pattern_8bars_yuv[i * 8 / fmt->width][0]; uv = pattern_8bars_yuv[i * 8 / fmt->width][1 + i % 2]; fb16[i] = sys_cpu_to_be16(y << 8 | uv << 0); } break; case VIDEO_PIX_FMT_RGB565: for (size_t i = 0; i < fmt->width; i++) { r = pattern_8bars_rgb[i * 8 / fmt->width][0] >> (8 - 5); g = pattern_8bars_rgb[i * 8 / fmt->width][1] >> (8 - 6); b = pattern_8bars_rgb[i * 8 / fmt->width][2] >> (8 - 5); fb16[i] = sys_cpu_to_le16((r << 11) | (g << 6) | (b << 0)); } break; default: LOG_WRN("Unsupported pixel format %x, supported: %x, %x", fmt->pixelformat, VIDEO_PIX_FMT_YUYV, VIDEO_PIX_FMT_RGB565); memset(fb16, 0, fmt->pitch); } /* Duplicate the first row over the whole frame */ for (size_t i = 1; i < fmt->height; i++) { memcpy(data->framebuffer + fmt->pitch * i, data->framebuffer, fmt->pitch); } } 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 (fmt->pitch * fmt->height > CONFIG_VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE) { LOG_ERR("%s has %u bytes of memory, unable to support %x %ux%u (%u bytes)", dev->name, CONFIG_VIDEO_EMUL_IMAGER_FRAMEBUFFER_SIZE, fmt->pixelformat, fmt->width, fmt->height, fmt->pitch * fmt->height); return -ENOMEM; } 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 for %s", fmt->pixelformat, fmt->width, fmt->height, dev->name); return ret; } ret = emul_imager_set_mode(dev, &emul_imager_modes[fmt_id][0]); if (ret < 0) { return ret; } /* Change the image pattern on the framebuffer */ emul_imager_fill_framebuffer(dev, fmt); 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_stream_start(const struct device *dev) { return emul_imager_write_reg(dev, EMUL_IMAGER_REG_CTRL, 1); } static int emul_imager_stream_stop(const struct device *dev) { return emul_imager_write_reg(dev, EMUL_IMAGER_REG_CTRL, 0); } static DEVICE_API(video, emul_imager_driver_api) = { .set_ctrl = emul_imager_set_ctrl, .get_ctrl = emul_imager_get_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, .stream_start = emul_imager_stream_start, .stream_stop = emul_imager_stream_stop, }; 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 %s correct sensor ID (0x%x", dev->name, sensor_id); return ret; } ret = emul_imager_write_multi(dev, emul_imager_init_regs); if (ret < 0) { LOG_ERR("Could not set %s initial registers", dev->name); 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 %s to default format %x %ux%u", dev->name, fmt.pixelformat, fmt.width, fmt.height); } return 0; } #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); DT_INST_FOREACH_STATUS_OKAY(EMUL_IMAGER_DEFINE)