diff --git a/drivers/video/CMakeLists.txt b/drivers/video/CMakeLists.txt index 19ad0e2bc797ee..2e52d1f66ea889 100644 --- a/drivers/video/CMakeLists.txt +++ b/drivers/video/CMakeLists.txt @@ -12,3 +12,4 @@ zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7725 ov7725.c) 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) diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 787311622e0cc9..d6a6bd3ccce669 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -47,4 +47,6 @@ source "drivers/video/Kconfig.stm32_dcmi" source "drivers/video/Kconfig.ov5640" +source "drivers/video/Kconfig.ov7670" + endif # VIDEO diff --git a/drivers/video/Kconfig.ov7670 b/drivers/video/Kconfig.ov7670 new file mode 100644 index 00000000000000..9a2a1d70f3d90c --- /dev/null +++ b/drivers/video/Kconfig.ov7670 @@ -0,0 +1,10 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +config VIDEO_OV7670 + bool "OV7670 CMOS digital image sensor" + select I2C + depends on DT_HAS_OVTI_OV7670_ENABLED + default y + help + Enable driver for OV7670 CMOS digital image sensor device. diff --git a/drivers/video/ov7670.c b/drivers/video/ov7670.c new file mode 100644 index 00000000000000..96cb3f860db1f1 --- /dev/null +++ b/drivers/video/ov7670.c @@ -0,0 +1,488 @@ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ovti_ov7670 + +#include +#include +#include + +#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include +LOG_MODULE_REGISTER(ov7670); + +/* Initialization register structure */ +struct ov7670_reg { + uint8_t reg; + uint8_t cmd; +}; + +struct ov7670_config { + struct i2c_dt_spec bus; +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) + struct gpio_dt_spec reset; +#endif +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios) + struct gpio_dt_spec pwdn; +#endif +}; + +struct ov7670_data { + struct video_format fmt; +}; + +/* OV7670 registers */ +#define OV7670_PID 0x0A +#define OV7670_COM7 0x12 +#define OV7670_MVFP 0x1E +#define OV7670_COM10 0x15 +#define OV7670_COM12 0x3C +#define OV7670_BRIGHT 0x55 +#define OV7670_CLKRC 0x11 +#define OV7670_SCALING_PCLK_DIV 0x73 +#define OV7670_COM14 0x3E +#define OV7670_DBLV 0x6B +#define OV7670_SCALING_XSC 0x70 +#define OV7670_SCALING_YSC 0x71 +#define OV7670_COM2 0x09 +#define OV7670_SCALING_PCLK_DELAY 0xA2 +#define OV7670_BD50MAX 0xA5 +#define OV7670_BD60MAX 0xAB +#define OV7670_HAECC7 0xAA +#define OV7670_COM3 0x0C +#define OV7670_COM4 0x0D +#define OV7670_COM6 0x0F +#define OV7670_COM11 0x3B +#define OV7670_EDGE 0x3F +#define OV7670_DNSTH 0x4C +#define OV7670_DM_LNL 0x92 +#define OV7670_DM_LNH 0x93 +#define OV7670_COM15 0x40 +#define OV7670_TSLB 0x3A +#define OV7670_COM13 0x3D +#define OV7670_MANU 0x67 +#define OV7670_MANV 0x68 +#define OV7670_HSTART 0x17 +#define OV7670_HSTOP 0x18 +#define OV7670_VSTRT 0x19 +#define OV7670_VSTOP 0x1A +#define OV7670_HREF 0x32 +#define OV7670_VREF 0x03 +#define OV7670_SCALING_DCWCTR 0x72 +#define OV7670_GAIN 0x00 +#define OV7670_AECHH 0x07 +#define OV7670_AECH 0x10 +#define OV7670_COM8 0x13 +#define OV7670_COM9 0x14 +#define OV7670_AEW 0x24 +#define OV7670_AEB 0x25 +#define OV7670_VPT 0x26 +#define OV7670_AWBC1 0x43 +#define OV7670_AWBC2 0x44 +#define OV7670_AWBC3 0x45 +#define OV7670_AWBC4 0x46 +#define OV7670_AWBC5 0x47 +#define OV7670_AWBC6 0x48 +#define OV7670_MTX1 0x4F +#define OV7670_MTX2 0x50 +#define OV7670_MTX3 0x51 +#define OV7670_MTX4 0x52 +#define OV7670_MTX5 0x53 +#define OV7670_MTX6 0x54 +#define OV7670_LCC1 0x62 +#define OV7670_LCC2 0x63 +#define OV7670_LCC3 0x64 +#define OV7670_LCC4 0x65 +#define OV7670_LCC5 0x66 +#define OV7670_LCC6 0x94 +#define OV7670_LCC7 0x95 +#define OV7670_SLOP 0x7A +#define OV7670_GAM1 0x7B +#define OV7670_GAM2 0x7C +#define OV7670_GAM3 0x7D +#define OV7670_GAM4 0x7E +#define OV7670_GAM5 0x7F +#define OV7670_GAM6 0x80 +#define OV7670_GAM7 0x81 +#define OV7670_GAM8 0x82 +#define OV7670_GAM9 0x83 +#define OV7670_GAM10 0x84 +#define OV7670_GAM11 0x85 +#define OV7670_GAM12 0x86 +#define OV7670_GAM13 0x87 +#define OV7670_GAM14 0x88 +#define OV7670_GAM15 0x89 +#define OV7670_HAECC1 0x9F +#define OV7670_HAECC2 0xA0 +#define OV7670_HSYEN 0x31 +#define OV7670_HAECC3 0xA6 +#define OV7670_HAECC4 0xA7 +#define OV7670_HAECC5 0xA8 +#define OV7670_HAECC6 0xA9 + +/* OV7670 definitions */ +#define OV7670_PROD_ID 0x76 + +#define OV7670_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[] = { + OV7670_VIDEO_FORMAT_CAP(176, 144, VIDEO_PIX_FMT_RGB565), /* QCIF */ + OV7670_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_RGB565), /* QVGA */ + OV7670_VIDEO_FORMAT_CAP(352, 288, VIDEO_PIX_FMT_RGB565), /* CIF */ + OV7670_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_RGB565), /* VGA */ + OV7670_VIDEO_FORMAT_CAP(176, 144, VIDEO_PIX_FMT_YUYV), /* QCIF */ + OV7670_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_YUYV), /* QVGA */ + OV7670_VIDEO_FORMAT_CAP(352, 288, VIDEO_PIX_FMT_YUYV), /* CIF */ + OV7670_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_YUYV), /* VGA */ + {0}}; + +/* This initialization table is based on the MCUX SDK driver for the OV7670 */ +static const struct ov7670_reg ov7670_init_regtbl[] = { + {OV7670_MVFP, 0x20}, /* MVFP: Mirror/VFlip,Normal image */ + + /* configure the output timing */ + /* PCLK does not toggle during horizontal blank, one PCLK, one pixel */ + {OV7670_COM10, 0x20}, /* COM10 */ + {OV7670_COM12, 0x00}, /* COM12,No HREF when VSYNC is low */ + /* Brightness Control, with signal -128 to +128, 0x00 is middle value */ + {OV7670_BRIGHT, 0x2f}, + + /* Internal clock pre-scalar,F(internal clock) = F(input clock)/(Bit[5:0]+1) */ + {OV7670_CLKRC, 0x81}, /* Clock Div, Input/(n+1), bit6 set to 1 to disable divider */ + + /* SCALING_PCLK_DIV, */ + {OV7670_SCALING_PCLK_DIV, 0x00}, /* 0: Enable clock divider,010: Divided by 4 */ + /* Common Control 14,Bit[4]: DCW and scaling PCLK enable,Bit[3]: Manual scaling */ + {OV7670_COM14, 0x00}, + + /* DBLV,Bit[7:6]: PLL control */ + /* 0:Bypass PLL.,40: Input clock x4 , 80: Input clock x6 ,C0: Input clock x8 */ + {OV7670_DBLV, 0x40}, + + /* test pattern, useful in some case */ + {OV7670_SCALING_XSC, 0x0}, + {OV7670_SCALING_YSC, 0}, + + /* Output Drive Capability */ + {OV7670_COM2, 0x00}, /* Common Control 2, Output Drive Capability: 1x */ + {OV7670_SCALING_PCLK_DELAY, 0x02}, + {OV7670_BD50MAX, 0x05}, + {OV7670_BD60MAX, 0x07}, + {OV7670_HAECC7, 0x94}, + + {OV7670_COM3, 0x00}, + {OV7670_COM4, 0x00}, + {OV7670_COM6, 0x4b}, + {OV7670_COM11, 0x9F}, /* Night mode */ + {OV7670_EDGE, 0x04}, /* Edge Enhancement Adjustment */ + {OV7670_DNSTH, 0x00}, /* De-noise Strength */ + + {OV7670_DM_LNL, 0x00}, + {OV7670_DM_LNH, 0x00}, + + /* reserved */ + {0x16, 0x02}, + {0x21, 0x02}, + {0x22, 0x91}, + {0x29, 0x07}, + {0x35, 0x0b}, + {0x33, 0x0b}, + {0x37, 0x1d}, + {0x38, 0x71}, + {0x39, 0x2a}, + {0x0e, 0x61}, + {0x56, 0x40}, + {0x57, 0x80}, + {0x69, 0x00}, + {0x74, 0x19}, + + /* display , need retain */ + {OV7670_COM15, 0xD0}, /* Common Control 15 */ + {OV7670_TSLB, 0x0C}, /* Line Buffer Test Option */ + {OV7670_COM13, 0x80}, /* Common Control 13 */ + {OV7670_MANU, 0x11}, /* Manual U Value */ + {OV7670_MANV, 0xFF}, /* Manual V Value */ + /* config the output window data, this can be configed later */ + {OV7670_HSTART, 0x16}, /* HSTART */ + {OV7670_HSTOP, 0x04}, /* HSTOP */ + {OV7670_VSTRT, 0x02}, /* VSTRT */ + {OV7670_VSTOP, 0x7a}, /* VSTOP */ + {OV7670_HREF, 0x80}, /* HREF */ + {OV7670_VREF, 0x0a}, /* VREF */ + + /* DCW Control, */ + {OV7670_SCALING_DCWCTR, 0x11}, + + /* AGC/AEC - Automatic Gain Control/Automatic exposure Control */ + {OV7670_GAIN, 0x00}, /* AGC */ + {OV7670_AECHH, 0x3F}, /* Exposure Value */ + {OV7670_AECH, 0xFF}, + {OV7670_COM8, 0x66}, + {OV7670_COM9, 0x21}, /* limit the max gain */ + {OV7670_AEW, 0x75}, + {OV7670_AEB, 0x63}, + {OV7670_VPT, 0xA5}, + /* Automatic white balance control */ + {OV7670_AWBC1, 0x14}, + {OV7670_AWBC2, 0xf0}, + {OV7670_AWBC3, 0x34}, + {OV7670_AWBC4, 0x58}, + {OV7670_AWBC5, 0x28}, + {OV7670_AWBC6, 0x3a}, + /* Matrix Coefficient */ + {OV7670_MTX1, 0x80}, + {OV7670_MTX2, 0x80}, + {OV7670_MTX3, 0x00}, + {OV7670_MTX4, 0x22}, + {OV7670_MTX5, 0x5e}, + {OV7670_MTX6, 0x80}, + /* AWB Control */ + {0x59, 0x88}, + {0x5a, 0x88}, + {0x5b, 0x44}, + {0x5c, 0x67}, + {0x5d, 0x49}, + {0x5e, 0x0e}, + {0x6c, 0x0a}, + {0x6d, 0x55}, + {0x6e, 0x11}, + {0x6f, 0x9f}, + /* Lens Correction Option */ + {OV7670_LCC1, 0x00}, + {OV7670_LCC2, 0x00}, + {OV7670_LCC3, 0x04}, + {OV7670_LCC4, 0x20}, + {OV7670_LCC5, 0x05}, + {OV7670_LCC6, 0x04}, /* effective only when LCC5[2] is high */ + {OV7670_LCC7, 0x08}, /* effective only when LCC5[2] is high */ + /* Gamma Curve, needn't config */ + {OV7670_SLOP, 0x20}, + {OV7670_GAM1, 0x1c}, + {OV7670_GAM2, 0x28}, + {OV7670_GAM3, 0x3c}, + {OV7670_GAM4, 0x55}, + {OV7670_GAM5, 0x68}, + {OV7670_GAM6, 0x76}, + {OV7670_GAM7, 0x80}, + {OV7670_GAM8, 0x88}, + {OV7670_GAM9, 0x8f}, + {OV7670_GAM10, 0x96}, + {OV7670_GAM11, 0xa3}, + {OV7670_GAM12, 0xaf}, + {OV7670_GAM13, 0xc4}, + {OV7670_GAM14, 0xd7}, + {OV7670_GAM15, 0xe8}, + /* Histogram-based AEC/AGC Control */ + {OV7670_HAECC1, 0x78}, + {OV7670_HAECC2, 0x68}, + {OV7670_HSYEN, 0xff}, + {0xa1, 0x03}, + {OV7670_HAECC3, 0xdf}, + {OV7670_HAECC4, 0xdf}, + {OV7670_HAECC5, 0xf0}, + {OV7670_HAECC6, 0x90}, + /* Automatic black Level Compensation */ + {0xb0, 0x84}, + {0xb1, 0x0c}, + {0xb2, 0x0e}, + {0xb3, 0x82}, + {0xb8, 0x0a}, +}; + +static int ov7670_get_caps(const struct device *dev, enum video_endpoint_id ep, + struct video_caps *caps) +{ + caps->format_caps = fmts; + return 0; +} + +static int ov7670_set_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + const struct ov7670_config *config = dev->config; + struct ov7670_data *data = dev->data; + uint8_t com7 = 0U; + uint8_t i = 0U; + + if (fmt->pixelformat != VIDEO_PIX_FMT_RGB565 && fmt->pixelformat != VIDEO_PIX_FMT_YUYV) { + LOG_ERR("Only RGB565 and YUYV supported!"); + return -ENOTSUP; + } + + if (!memcmp(&data->fmt, fmt, sizeof(data->fmt))) { + /* nothing to do */ + return 0; + } + + memcpy(&data->fmt, fmt, sizeof(data->fmt)); + + if (fmt->pixelformat == VIDEO_PIX_FMT_RGB565) { + com7 |= 0x4; + } + + /* Set output resolution */ + while (fmts[i].pixelformat) { + if (fmts[i].width_min == fmt->width && fmts[i].height_min == fmt->height && + fmts[i].pixelformat == fmt->pixelformat) { + /* Set output format */ + switch (fmts[i].width_min) { + case 176: /* QCIF */ + com7 |= BIT(3); + break; + case 320: /* QVGA */ + com7 |= BIT(4); + break; + case 352: /* CIF */ + com7 |= BIT(5); + break; + default: /* VGA */ + break; + } + /* Program COM7 to set format */ + return i2c_reg_write_byte_dt(&config->bus, OV7670_COM7, com7); + } + i++; + } + LOG_ERR("Unsupported format"); + return -ENOTSUP; +} + +static int ov7670_get_fmt(const struct device *dev, enum video_endpoint_id ep, + struct video_format *fmt) +{ + struct ov7670_data *data = dev->data; + + if (fmt == NULL) { + return -EINVAL; + } + memcpy(fmt, &data->fmt, sizeof(data->fmt)); + return 0; +} + +static int ov7670_init(const struct device *dev) +{ + const struct ov7670_config *config = dev->config; + int ret, i; + uint8_t pid; + struct video_format fmt; + const struct ov7670_reg *reg; + + if (!i2c_is_ready_dt(&config->bus)) { + /* I2C device is not ready, return */ + return -ENODEV; + } + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios) + /* Power up camera module */ + if (config->pwdn.port != NULL) { + if (!gpio_is_ready_dt(&config->pwdn)) { + return -ENODEV; + } + ret = gpio_pin_configure_dt(&config->pwdn, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Could not clear power down pin: %d", ret); + return ret; + } + } +#endif +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) + /* Reset camera module */ + if (config->reset.port != NULL) { + if (!gpio_is_ready_dt(&config->reset)) { + return -ENODEV; + } + ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT); + if (ret < 0) { + LOG_ERR("Could not set reset pin: %d", ret); + return ret; + } + /* Reset is active low, has 1ms settling time*/ + gpio_pin_set_dt(&config->reset, 0); + k_msleep(1); + gpio_pin_set_dt(&config->reset, 1); + k_msleep(1); + } +#endif + + /* + * Read product ID from camera. This camera implements the SCCB, + * spec- which *should* be I2C compatible, but in practice does + * not seem to respond when I2C repeated start commands are used. + * To work around this, use a write then a read to interface with + * registers. + */ + uint8_t cmd = OV7670_PID; + + ret = i2c_write_dt(&config->bus, &cmd, sizeof(cmd)); + if (ret < 0) { + LOG_ERR("Could not request product ID: %d", ret); + return ret; + } + ret = i2c_read_dt(&config->bus, &pid, sizeof(pid)); + if (ret < 0) { + LOG_ERR("Could not read product ID: %d", ret); + return ret; + } + + if (pid != OV7670_PROD_ID) { + LOG_ERR("Incorrect product ID: 0x%02X", pid); + return -ENODEV; + } + + /* Set default camera format (QVGA, YUYV) */ + fmt.pixelformat = VIDEO_PIX_FMT_YUYV; + fmt.width = 640; + fmt.height = 480; + fmt.pitch = fmt.width * 2; + ret = ov7670_set_fmt(dev, VIDEO_EP_OUT, &fmt); + if (ret < 0) { + return ret; + } + + /* Write initialization values to OV7670 */ + for (i = 0; i < ARRAY_SIZE(ov7670_init_regtbl); i++) { + reg = &ov7670_init_regtbl[i]; + ret = i2c_reg_write_byte_dt(&config->bus, reg->reg, reg->cmd); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +static const struct video_driver_api ov7670_api = { + .set_format = ov7670_set_fmt, + .get_format = ov7670_get_fmt, + .get_caps = ov7670_get_caps, +}; + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) +#define OV7670_RESET_GPIO(inst) .reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {}), +#else +#define OV7670_RESET_GPIO(inst) +#endif + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios) +#define OV7670_PWDN_GPIO(inst) .pwdn = GPIO_DT_SPEC_INST_GET_OR(inst, pwdn_gpios, {}), +#else +#define OV7670_PWDN_GPIO(inst) +#endif + +#define OV7670_INIT(inst) \ + const struct ov7670_config ov7670_config_##inst = {.bus = I2C_DT_SPEC_INST_GET(inst), \ + OV7670_RESET_GPIO(inst) \ + OV7670_PWDN_GPIO(inst)}; \ + struct ov7670_data ov7670_data_##inst; \ + \ + DEVICE_DT_INST_DEFINE(inst, ov7670_init, NULL, &ov7670_data_##inst, &ov7670_config_##inst, \ + POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &ov7670_api); + +DT_INST_FOREACH_STATUS_OKAY(OV7670_INIT) diff --git a/dts/bindings/video/ovti,ov7670.yaml b/dts/bindings/video/ovti,ov7670.yaml new file mode 100644 index 00000000000000..4b8fa88f1170b8 --- /dev/null +++ b/dts/bindings/video/ovti,ov7670.yaml @@ -0,0 +1,20 @@ +# Copyright 2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: OV7670 CMOS video sensor + +compatible: "ovti,ov7670" + +properties: + reset-gpios: + type: phandle-array + description: | + The RESETn pin is asserted to disable the sensor causing a hard + reset. The sensor receives this as an active-low signal. + pwdn-gpios: + type: phandle-array + description: | + The PWDN pin is asserted to power down the sensor. The sensor + receives this as an active high signal + +include: i2c-device.yaml