| /* | 
 |  * 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, struct video_frmival *frmival) | 
 | { | 
 | 	struct emul_imager_data *data = dev->data; | 
 | 	struct video_frmival_enum fie = {.format = &data->fmt, .discrete = *frmival}; | 
 |  | 
 | 	video_closest_frmival(dev, &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, struct video_frmival *frmival) | 
 | { | 
 | 	struct emul_imager_data *data = dev->data; | 
 |  | 
 | 	frmival->numerator = 1; | 
 | 	frmival->denominator = data->mode->fps; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int emul_imager_enum_frmival(const struct device *dev, struct video_frmival_enum *fie) | 
 | { | 
 | 	const struct emul_imager_mode *mode; | 
 | 	size_t fmt_id; | 
 | 	int ret; | 
 |  | 
 | 	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; | 
 |  | 
 | 	return mode->fps == 0; | 
 | } | 
 |  | 
 | static int emul_imager_set_fmt(const struct device *const dev, struct video_format *fmt) | 
 | { | 
 | 	struct emul_imager_data *data = dev->data; | 
 | 	size_t fmt_id; | 
 | 	int ret; | 
 |  | 
 | 	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, struct video_format *fmt) | 
 | { | 
 | 	struct emul_imager_data *data = dev->data; | 
 |  | 
 | 	*fmt = data->fmt; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int emul_imager_get_caps(const struct device *dev, struct video_caps *caps) | 
 | { | 
 | 	caps->format_caps = fmts; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int emul_imager_set_stream(const struct device *dev, bool enable, enum video_buf_type type) | 
 | { | 
 | 	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 = { | 
 | 		.pixelformat = fmts[0].pixelformat, | 
 | 		.width = fmts[0].width_min, | 
 | 		.height = fmts[0].height_min, | 
 | 	}; | 
 | 	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; | 
 | 	} | 
 |  | 
 | 	ret = emul_imager_set_fmt(dev, &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) |