/* * 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)