| /* |
| * Copyright 2024 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ovti_ov7670 |
| |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/video.h> |
| |
| #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL |
| #include <zephyr/logging/log.h> |
| 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) |