diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 2e52d1f66ea8890..05090fee89bc63c 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -13,3 +13,4 @@ zephyr_library_sources_ifdef(CONFIG_VIDEO_OV2640 ov2640.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_STM32_DCMI video_stm32_dcmi.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV5640 ov5640.c) zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7670 ov7670.c) +zephyr_library_sources_ifdef(CONFIG_VIDEO_IMX219 imx219.c) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index d6a6bd3ccce669e..91c0407e5629ffa 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -49,4 +49,6 @@ source "drivers/video/Kconfig.ov5640" source "drivers/video/Kconfig.ov7670" +source "drivers/video/Kconfig.imx219" + endif # VIDEO diff --git a/drivers/video/Kconfig.imx219 b/drivers/video/Kconfig.imx219 new file mode 100644 index 000000000000000..754b32a46076103 --- /dev/null +++ b/drivers/video/Kconfig.imx219 @@ -0,0 +1,12 @@ +# imx219 + +# Copyright (c) 2024 tinyVision.ai Inc. +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_IMX219 + bool "IMX219 8 Mega-Pixel CMOS image sensor" + select I2C + depends on DT_HAS_SONY_IMX219_ENABLED + default y + help + Enable driver for IMX219 8 Mega-Pixel CMOS image sensor diff --git a/drivers/video/imx219.c b/drivers/video/imx219.c new file mode 100644 index 000000000000000..8f582445615210c --- /dev/null +++ b/drivers/video/imx219.c @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2023 Gaurav Singh www.CircuitValley.com + * Copyright 2024 NXP + * Copyright (c) 2024 tinyVision.ai Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT sony_imx219 + +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include +LOG_MODULE_REGISTER(imx219); + +#include + +/* Chip ID */ +#define CHIP_ID_REG 0x0000 +#define CHIP_ID_VAL 0x0219 + +#define IMX219_REG_SOFTWARE_RESET 0x0103 +#define IMX219_SOFTWARE_RESET 1 + +/* mode_select */ +#define IMX219_REG_MODE_SELECT 0x0100 +#define IMX219_MODE_STANDBY 0x00 +#define IMX219_MODE_STREAMING 0x01 + +#define IMX219_REG_CSI_LANE_MODE 0x0114 +#define IMX219_CSI_2_LANE_MODE 0x01 +#define IMX219_CSI_4_LANE_MODE 0x03 + +#define IMX219_REG_DPHY_CTRL 0x0128 +#define IMX219_DPHY_CTRL_TIMING_AUTO 0 +#define IMX219_DPHY_CTRL_TIMING_MANUAL 1 + +#define IMX219_REG_EXCK_FREQ 0x012a +#define IMX219_REG_EXCK_FREQ_LSB 0x012B + +/* Analog gain control */ +#define IMX219_REG_ANALOG_GAIN 0x0157 +#define IMX219_ANA_GAIN_DEFAULT 0x80 + +/* Digital gain control */ +#define IMX219_REG_DIGITAL_GAIN_MSB 0x0158 +#define IMX219_REG_DIGITAL_GAIN_LSB 0x0159 +#define IMX219_DGTL_GAIN_DEFAULT 0x0100 + +#define IMX219_REG_INTEGRATION_TIME_MSB 0x015A +#define IMX219_REG_INTEGRATION_TIME_LSB 0x015B + +/* V_TIMING internal */ +#define IMX219_REG_FRAME_LEN_MSB 0x0160 +#define IMX219_REG_FRAME_LEN_LSB 0x0161 + +#define IMX219_REG_LINE_LENGTH_A_MSB 0x0162 +#define IMX219_REG_LINE_LENGTH_A_LSB 0x0163 +#define IMX219_REG_X_ADD_STA_A_MSB 0x0164 +#define IMX219_REG_X_ADD_STA_A_LSB 0x0165 +#define IMX219_REG_X_ADD_END_A_MSB 0x0166 +#define IMX219_REG_X_ADD_END_A_LSB 0x0167 +#define IMX219_REG_Y_ADD_STA_A_MSB 0x0168 +#define IMX219_REG_Y_ADD_STA_A_LSB 0x0169 +#define IMX219_REG_Y_ADD_END_A_MSB 0x016a +#define IMX219_REG_Y_ADD_END_A_LSB 0x016b +#define IMX219_REG_X_OUTPUT_SIZE_MSB 0x016c +#define IMX219_REG_X_OUTPUT_SIZE_LSB 0x016d +#define IMX219_REG_Y_OUTPUT_SIZE_MSB 0x016e +#define IMX219_REG_Y_OUTPUT_SIZE_LSB 0x016f +#define IMX219_REG_X_ODD_INC_A 0x0170 +#define IMX219_REG_Y_ODD_INC_A 0x0171 +#define IMX219_REG_ORIENTATION 0x0172 + +/* Binning Mode */ +#define IMX219_REG_BINNING_MODE_H 0x0174 +#define IMX219_REG_BINNING_MODE_V 0x0175 +#define IMX219_BINNING_NONE 0x00 + +#define IMX219_REG_CSI_DATA_FORMAT_A_MSB 0x018c +#define IMX219_REG_CSI_DATA_FORMAT_A_LSB 0x018d + +/* PLL Settings */ +#define IMX219_REG_VTPXCK_DIV 0x0301 +#define IMX219_REG_VTSYCK_DIV 0x0303 +#define IMX219_REG_PREPLLCK_VT_DIV 0x0304 +#define IMX219_REG_PREPLLCK_OP_DIV 0x0305 +#define IMX219_REG_PLL_VT_MPY_MSB 0x0306 +#define IMX219_REG_PLL_VT_MPY_LSB 0x0307 +#define IMX219_REG_OPPXCK_DIV 0x0309 +#define IMX219_REG_OPSYCK_DIV 0x030b +#define IMX219_REG_PLL_OP_MPY_MSB 0x030c +#define IMX219_REG_PLL_OP_MPY_LSB 0x030d + +/* Test Pattern Control */ +#define IMX219_REG_TEST_PATTERN_MSB 0x0600 +#define IMX219_REG_TEST_PATTERN_LSB 0x0601 +#define IMX219_TEST_PATTERN_DISABLE 0 + +#define IMX219_REG_TP_X_OFFSET_MSB 0x0620 +#define IMX219_REG_TP_X_OFFSET_LSB 0x0621 +#define IMX219_REG_TP_Y_OFFSET_MSB 0x0622 +#define IMX219_REG_TP_Y_OFFSET_LSB 0x0623 + +/* Test pattern colour components */ +#define IMX219_REG_TESTP_RED_MSB 0x0602 +#define IMX219_REG_TESTP_RED_LSB 0x0603 +#define IMX219_REG_TESTP_GREENR_MSB 0x0604 +#define IMX219_REG_TESTP_GREENR_LSB 0x0605 +#define IMX219_REG_TESTP_BLUE_MSB 0x0606 +#define IMX219_REG_TESTP_BLUE_LSB 0x0607 + +#define IMX219_REG_TP_WINDOW_WIDTH_MSB 0x0624 +#define IMX219_REG_TP_WINDOW_WIDTH_LSB 0x0625 +#define IMX219_REG_TP_WINDOW_HEIGHT_MSB 0x0626 +#define IMX219_REG_TP_WINDOW_HEIGHT_LSB 0x0627 + +#define IMX219_RESOLUTION_PARAM_NUM 20 + +struct imx219_reg { + uint16_t addr; + uint8_t val; +}; + +struct imx219_resolution_config { + uint16_t width; + uint16_t height; + const struct imx219_reg *res_params; +}; + +static const struct imx219_reg imx219_1080p_res_params[] = { + {IMX219_REG_PLL_VT_MPY_MSB, 0x00}, + {IMX219_REG_PLL_VT_MPY_LSB, 0x20}, + {IMX219_REG_VTPXCK_DIV, 0x4}, + {IMX219_REG_INTEGRATION_TIME_MSB, 0x4}, + {IMX219_REG_INTEGRATION_TIME_LSB, 0xac}, + {IMX219_REG_ANALOG_GAIN, 0x80}, + {IMX219_REG_LINE_LENGTH_A_MSB, 0x0d}, + {IMX219_REG_LINE_LENGTH_A_LSB, 0x78}, + {IMX219_REG_FRAME_LEN_MSB, 0x4}, + {IMX219_REG_FRAME_LEN_LSB, 0xb0}, + {IMX219_REG_X_ADD_STA_A_MSB, 0x02}, + {IMX219_REG_X_ADD_STA_A_LSB, 0xa8}, + {IMX219_REG_Y_ADD_STA_A_MSB, 0x2}, + {IMX219_REG_Y_ADD_STA_A_LSB, 0xb4}, + {IMX219_REG_X_ADD_END_A_MSB, 0x0a}, + {IMX219_REG_X_ADD_END_A_LSB, 0x27}, + {IMX219_REG_Y_ADD_END_A_MSB, 0x6}, + {IMX219_REG_Y_ADD_END_A_LSB, 0xeb}, + {IMX219_REG_X_OUTPUT_SIZE_MSB, 0x7}, + {IMX219_REG_X_OUTPUT_SIZE_LSB, 0x80}, + {IMX219_REG_Y_OUTPUT_SIZE_MSB, 0x4}, + {IMX219_REG_Y_OUTPUT_SIZE_LSB, 0x38}, + {IMX219_REG_TEST_PATTERN_LSB, 0x0}, + {IMX219_REG_BINNING_MODE_H, 0x00}, + {IMX219_REG_BINNING_MODE_V, 0x00}, +}; + +static const struct imx219_resolution_config res_params[] = { + {.width = 1920, .height = 1080, .res_params = imx219_1080p_res_params}, +}; + +#define IMX219_VIDEO_FORMAT_CAP(width, height, format) \ + { \ + .pixelformat = (format), .width_min = (width), .width_max = (width), \ + .height_min = (height), .height_max = (height), .width_step = 1, .height_step = 1 \ + } + +static const struct video_format_cap fmts[] = { + IMX219_VIDEO_FORMAT_CAP(1920, 1080, VIDEO_PIX_FMT_BGGR8), + {0}}; + +static int imx219_read_reg(const struct i2c_dt_spec *spec, const uint16_t addr, void *val, + const uint8_t val_size) +{ + int ret; + struct i2c_msg msg[2]; + uint8_t addr_buf[2]; + + if (val_size > 4) { + return -ENOTSUP; + } + + addr_buf[1] = addr & 0xFF; + addr_buf[0] = addr >> 8; + msg[0].buf = addr_buf; + msg[0].len = 2U; + msg[0].flags = I2C_MSG_WRITE; + + msg[1].buf = (uint8_t *)val; + msg[1].len = val_size; + msg[1].flags = I2C_MSG_READ | I2C_MSG_STOP | I2C_MSG_RESTART; + + ret = i2c_transfer_dt(spec, msg, 2); + if (ret) { + return ret; + } + + switch (val_size) { + case 4: + *(uint32_t *)val = sys_be32_to_cpu(*(uint32_t *)val); + break; + case 2: + *(uint16_t *)val = sys_be16_to_cpu(*(uint16_t *)val); + break; + case 1: + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static int imx219_write_reg(const struct i2c_dt_spec *spec, const uint16_t addr, const uint8_t val) +{ + uint8_t addr_buf[2]; + struct i2c_msg msg[2]; + + addr_buf[1] = addr & 0xFF; + addr_buf[0] = addr >> 8; + msg[0].buf = addr_buf; + msg[0].len = 2U; + msg[0].flags = I2C_MSG_WRITE; + + msg[1].buf = (uint8_t *)&val; + msg[1].len = 1; + msg[1].flags = I2C_MSG_WRITE | I2C_MSG_STOP; + + return i2c_transfer_dt(spec, msg, 2); +} + +static int imx219_write_multi_regs(const struct i2c_dt_spec *spec, const struct imx219_reg *regs, + const uint32_t num_regs) +{ + int ret; + + for (int i = 0; i < num_regs; i++) { + ret = imx219_write_reg(spec, regs[i].addr, regs[i].val); + if (ret) { + return ret; + } + } + + return 0; +} + +struct imx219_config { + struct i2c_dt_spec i2c; +}; + +struct imx219_data { + struct video_format fmt; +}; + +static const struct imx219_reg imx219InitParams[] = { + {IMX219_REG_MODE_SELECT, IMX219_MODE_STANDBY}, + {0x30EB, 0x05}, + {0x30EB, 0x0C}, + {0x300A, 0xFF}, + {0x300B, 0xFF}, + {0x30EB, 0x05}, + {0x30EB, 0x09}, + {IMX219_REG_CSI_LANE_MODE, IMX219_CSI_2_LANE_MODE}, + {IMX219_REG_DPHY_CTRL, IMX219_DPHY_CTRL_TIMING_AUTO}, + {IMX219_REG_EXCK_FREQ, 0x18}, + {IMX219_REG_EXCK_FREQ_LSB, 0x00}, + {IMX219_REG_FRAME_LEN_MSB, 0x06}, + {IMX219_REG_FRAME_LEN_LSB, 0xE3}, + {IMX219_REG_LINE_LENGTH_A_MSB, 0x0d}, + {IMX219_REG_LINE_LENGTH_A_LSB, 0x78}, + {IMX219_REG_X_ADD_STA_A_MSB, 0x02}, + {IMX219_REG_X_ADD_STA_A_LSB, 0xA8}, + {IMX219_REG_X_ADD_END_A_MSB, 0x0A}, + {IMX219_REG_X_ADD_END_A_LSB, 0x27}, + {IMX219_REG_Y_ADD_STA_A_MSB, 0x02}, + {IMX219_REG_Y_ADD_STA_A_LSB, 0xB4}, + {IMX219_REG_Y_ADD_END_A_MSB, 0x06}, + {IMX219_REG_Y_ADD_END_A_LSB, 0xEB}, + {IMX219_REG_X_OUTPUT_SIZE_MSB, 0x07}, + {IMX219_REG_X_OUTPUT_SIZE_LSB, 0x80}, + {IMX219_REG_Y_OUTPUT_SIZE_MSB, 0x04}, + {IMX219_REG_Y_OUTPUT_SIZE_LSB, 0x38}, + {IMX219_REG_X_ODD_INC_A, 0x01}, + {IMX219_REG_Y_ODD_INC_A, 0x01}, + {IMX219_REG_BINNING_MODE_H, 0x00}, + {IMX219_REG_BINNING_MODE_V, 0x00}, + {IMX219_REG_CSI_DATA_FORMAT_A_MSB, 0x0A}, + {IMX219_REG_CSI_DATA_FORMAT_A_LSB, 0x0A}, + {IMX219_REG_VTPXCK_DIV, 0x05}, + {IMX219_REG_VTSYCK_DIV, 0x01}, + {IMX219_REG_PREPLLCK_VT_DIV, 0x03}, + {IMX219_REG_PREPLLCK_OP_DIV, 0x03}, + {IMX219_REG_PLL_VT_MPY_MSB, 0x00}, + {IMX219_REG_PLL_VT_MPY_LSB, 0x52}, + {IMX219_REG_OPPXCK_DIV, 0x0A}, + {IMX219_REG_OPSYCK_DIV, 0x01}, + {IMX219_REG_PLL_OP_MPY_MSB, 0x00}, + {IMX219_REG_PLL_OP_MPY_LSB, 0x32}, + {0x455E, 0x00}, + {0x471E, 0x4B}, + {0x4767, 0x0F}, + {0x4750, 0x14}, + {0x4540, 0x00}, + {0x47B4, 0x14}, + {0x4713, 0x30}, + {0x478B, 0x10}, + {0x478F, 0x10}, + {0x4793, 0x10}, + {0x4797, 0x0E}, + {0x479B, 0x0E}, + {IMX219_REG_TESTP_RED_MSB, 0x00}, + {IMX219_REG_TESTP_RED_LSB, 0x00}, + {IMX219_REG_TESTP_GREENR_MSB, 0x00}, + {IMX219_REG_TESTP_GREENR_LSB, 0x00}, + {IMX219_REG_TESTP_BLUE_MSB, 0x00}, + {IMX219_REG_TESTP_BLUE_LSB, 0x00}, + {IMX219_REG_TEST_PATTERN_MSB, 0x00}, + {IMX219_REG_TEST_PATTERN_LSB, 0x00}, + {IMX219_REG_TP_X_OFFSET_MSB, 0x00}, + {IMX219_REG_TP_X_OFFSET_LSB, 0x00}, + {IMX219_REG_TP_Y_OFFSET_MSB, 0x00}, + {IMX219_REG_TP_Y_OFFSET_LSB, 0x00}, + {IMX219_REG_TP_WINDOW_WIDTH_MSB, 0x05}, + {IMX219_REG_TP_WINDOW_WIDTH_LSB, 0x00}, + {IMX219_REG_TP_WINDOW_HEIGHT_MSB, 0x02}, + {IMX219_REG_TP_WINDOW_HEIGHT_LSB, 0xD0}, + {IMX219_REG_DIGITAL_GAIN_MSB, 0x01}, + {IMX219_REG_DIGITAL_GAIN_LSB, 0x00}, + {IMX219_REG_ANALOG_GAIN, 0x80}, + {IMX219_REG_INTEGRATION_TIME_MSB, 0x03}, + {IMX219_REG_INTEGRATION_TIME_LSB, 0x51}, + {IMX219_REG_ORIENTATION, 0x03}, +}; + +static int imx219_set_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct imx219_data *drv_data = dev->data; + const struct imx219_config *cfg = dev->config; + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(fmts); ++i) { + if (fmt->pixelformat == fmts[i].pixelformat && fmt->width >= fmts[i].width_min && + fmt->width <= fmts[i].width_max && fmt->height >= fmts[i].height_min && + fmt->height <= fmts[i].height_max) { + break; + } + } + + if (i == ARRAY_SIZE(fmts)) { + LOG_ERR("Unsupported pixel format or resolution"); + return -ENOTSUP; + } + + if (!memcmp(&drv_data->fmt, fmt, sizeof(drv_data->fmt))) { + return 0; + } + + drv_data->fmt = *fmt; + + /* Set resolution parameters */ + for (i = 0; i < ARRAY_SIZE(res_params); i++) { + if (fmt->width == res_params[i].width && + fmt->height == res_params[i].height) { + ret = imx219_write_multi_regs(&cfg->i2c, res_params[i].res_params, + IMX219_RESOLUTION_PARAM_NUM); + if (ret) { + LOG_ERR("Unable to set resolution parameters"); + return ret; + } + break; + } + } + + return 0; +} + +static int imx219_stream_start(const struct device *dev) +{ + const struct imx219_config *cfg = dev->config; + + return imx219_write_reg(&cfg->i2c, IMX219_REG_MODE_SELECT, IMX219_MODE_STREAMING); +} + +static int imx219_stream_stop(const struct device *dev) +{ + const struct imx219_config *cfg = dev->config; + + return imx219_write_reg(&cfg->i2c, IMX219_REG_MODE_SELECT, IMX219_MODE_STANDBY); +} + +static int imx219_get_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct imx219_data *drv_data = dev->data; + + *fmt = drv_data->fmt; + + return 0; +} + +static int imx219_get_caps(const struct device *dev, enum video_endpoint_id ep, + struct video_caps *caps) +{ + caps->format_caps = fmts; + return 0; +} + +static const struct video_driver_api imx219_driver_api = { + .set_format = imx219_set_fmt, + .get_format = imx219_get_fmt, + .get_caps = imx219_get_caps, + .stream_start = imx219_stream_start, + .stream_stop = imx219_stream_stop, +}; + +static int imx219_init(const struct device *dev) +{ + const struct imx219_config *cfg = dev->config; + struct video_format fmt; + uint16_t chip_id; + int ret; + + if (!device_is_ready(cfg->i2c.bus)) { + LOG_ERR("Bus device is not ready"); + return -ENODEV; + } + + k_sleep(K_MSEC(10)); + + /* Software reset */ + ret = imx219_write_reg(&cfg->i2c, IMX219_REG_SOFTWARE_RESET, IMX219_SOFTWARE_RESET); + if (ret) { + LOG_ERR("Unable to perform software reset"); + return -EIO; + } + + k_sleep(K_MSEC(5)); + + /* Check sensor chip id */ + ret = imx219_read_reg(&cfg->i2c, CHIP_ID_REG, &chip_id, sizeof(chip_id)); + if (ret) { + LOG_ERR("Unable to read sensor chip ID, ret = %d", ret); + return -ENODEV; + } + + if (chip_id != CHIP_ID_VAL) { + LOG_ERR("Wrong chip ID: %04x (expected %04x)", chip_id, CHIP_ID_VAL); + return -ENODEV; + } + + /* Initialize register values */ + ret = imx219_write_multi_regs(&cfg->i2c, imx219InitParams, ARRAY_SIZE(imx219InitParams)); + if (ret) { + LOG_ERR("Unable to initialize the sensor"); + return -EIO; + } + + /* Set default format to 1080p BGGR8 */ + fmt.pixelformat = VIDEO_PIX_FMT_BGGR8; + fmt.width = 1920; + fmt.height = 1080; + fmt.pitch = fmt.width * 2; + ret = imx219_set_fmt(dev, VIDEO_EP_OUT, &fmt); + if (ret) { + LOG_ERR("Unable to configure default format"); + return -EIO; + } + + return 0; +} + +#define IMX219_INIT(n) \ + static struct imx219_data imx219_data_##n; \ + \ + static const struct imx219_config imx219_cfg_##n = { \ + .i2c = I2C_DT_SPEC_INST_GET(n), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &imx219_init, NULL, &imx219_data_##n, &imx219_cfg_##n, \ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &imx219_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(IMX219_INIT) diff --git a/dts/bindings/video/sony,imx219.yaml b/dts/bindings/video/sony,imx219.yaml new file mode 100644 index 000000000000000..6f362f4d7925faf --- /dev/null +++ b/dts/bindings/video/sony,imx219.yaml @@ -0,0 +1,5 @@ +description: IMX219 8 Mega-Pixel CMOS image sensor + +compatible: "sony,imx219" + +include: i2c-device.yaml