1 /*
2 * Copyright 2024 NXP
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT ovti_ov7670
8
9 #include <zephyr/drivers/gpio.h>
10 #include <zephyr/drivers/i2c.h>
11 #include <zephyr/drivers/video.h>
12 #include <zephyr/logging/log.h>
13
14 LOG_MODULE_REGISTER(video_ov7670, CONFIG_VIDEO_LOG_LEVEL);
15
16 /* Initialization register structure */
17 struct ov7670_reg {
18 uint8_t reg;
19 uint8_t cmd;
20 };
21
22 struct ov7670_config {
23 struct i2c_dt_spec bus;
24 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios)
25 struct gpio_dt_spec reset;
26 #endif
27 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios)
28 struct gpio_dt_spec pwdn;
29 #endif
30 };
31
32 struct ov7670_data {
33 struct video_format fmt;
34 };
35
36 struct ov7670_resolution_cfg {
37 uint8_t com7;
38 uint8_t com3;
39 uint8_t com14;
40 uint8_t scaling_xsc;
41 uint8_t scaling_ysc;
42 uint8_t dcwctr;
43 uint8_t pclk_div;
44 uint8_t pclk_delay;
45 };
46
47 /* Resolution settings for camera, based on those present in MCUX SDK */
48 const struct ov7670_resolution_cfg OV7670_RESOLUTION_QCIF = {
49 .com7 = 0x2c,
50 .com3 = 0x00,
51 .com14 = 0x11,
52 .scaling_xsc = 0x3a,
53 .scaling_ysc = 0x35,
54 .dcwctr = 0x11,
55 .pclk_div = 0xf1,
56 .pclk_delay = 0x52
57 };
58
59 const struct ov7670_resolution_cfg OV7670_RESOLUTION_QVGA = {
60 .com7 = 0x14,
61 .com3 = 0x04,
62 .com14 = 0x19,
63 .scaling_xsc = 0x3a,
64 .scaling_ysc = 0x35,
65 .dcwctr = 0x11,
66 .pclk_div = 0xf1,
67 .pclk_delay = 0x02
68 };
69
70 const struct ov7670_resolution_cfg OV7670_RESOLUTION_CIF = {
71 .com7 = 0x24,
72 .com3 = 0x08,
73 .com14 = 0x11,
74 .scaling_xsc = 0x3a,
75 .scaling_ysc = 0x35,
76 .dcwctr = 0x11,
77 .pclk_div = 0xf1,
78 .pclk_delay = 0x02
79 };
80
81 const struct ov7670_resolution_cfg OV7670_RESOLUTION_VGA = {
82 .com7 = 0x04,
83 .com3 = 0x00,
84 .com14 = 0x00,
85 .scaling_xsc = 0x3a,
86 .scaling_ysc = 0x35,
87 .dcwctr = 0x11,
88 .pclk_div = 0xf0,
89 .pclk_delay = 0x02
90 };
91
92
93 /* OV7670 registers */
94 #define OV7670_PID 0x0A
95 #define OV7670_COM7 0x12
96 #define OV7670_MVFP 0x1E
97 #define OV7670_COM10 0x15
98 #define OV7670_COM12 0x3C
99 #define OV7670_BRIGHT 0x55
100 #define OV7670_CLKRC 0x11
101 #define OV7670_SCALING_PCLK_DIV 0x73
102 #define OV7670_COM14 0x3E
103 #define OV7670_DBLV 0x6B
104 #define OV7670_SCALING_XSC 0x70
105 #define OV7670_SCALING_YSC 0x71
106 #define OV7670_COM2 0x09
107 #define OV7670_SCALING_PCLK_DELAY 0xA2
108 #define OV7670_BD50MAX 0xA5
109 #define OV7670_BD60MAX 0xAB
110 #define OV7670_HAECC7 0xAA
111 #define OV7670_COM3 0x0C
112 #define OV7670_COM4 0x0D
113 #define OV7670_COM6 0x0F
114 #define OV7670_COM11 0x3B
115 #define OV7670_EDGE 0x3F
116 #define OV7670_DNSTH 0x4C
117 #define OV7670_DM_LNL 0x92
118 #define OV7670_DM_LNH 0x93
119 #define OV7670_COM15 0x40
120 #define OV7670_TSLB 0x3A
121 #define OV7670_COM13 0x3D
122 #define OV7670_MANU 0x67
123 #define OV7670_MANV 0x68
124 #define OV7670_HSTART 0x17
125 #define OV7670_HSTOP 0x18
126 #define OV7670_VSTRT 0x19
127 #define OV7670_VSTOP 0x1A
128 #define OV7670_HREF 0x32
129 #define OV7670_VREF 0x03
130 #define OV7670_SCALING_DCWCTR 0x72
131 #define OV7670_GAIN 0x00
132 #define OV7670_AECHH 0x07
133 #define OV7670_AECH 0x10
134 #define OV7670_COM8 0x13
135 #define OV7670_COM9 0x14
136 #define OV7670_AEW 0x24
137 #define OV7670_AEB 0x25
138 #define OV7670_VPT 0x26
139 #define OV7670_AWBC1 0x43
140 #define OV7670_AWBC2 0x44
141 #define OV7670_AWBC3 0x45
142 #define OV7670_AWBC4 0x46
143 #define OV7670_AWBC5 0x47
144 #define OV7670_AWBC6 0x48
145 #define OV7670_MTX1 0x4F
146 #define OV7670_MTX2 0x50
147 #define OV7670_MTX3 0x51
148 #define OV7670_MTX4 0x52
149 #define OV7670_MTX5 0x53
150 #define OV7670_MTX6 0x54
151 #define OV7670_LCC1 0x62
152 #define OV7670_LCC2 0x63
153 #define OV7670_LCC3 0x64
154 #define OV7670_LCC4 0x65
155 #define OV7670_LCC5 0x66
156 #define OV7670_LCC6 0x94
157 #define OV7670_LCC7 0x95
158 #define OV7670_SLOP 0x7A
159 #define OV7670_GAM1 0x7B
160 #define OV7670_GAM2 0x7C
161 #define OV7670_GAM3 0x7D
162 #define OV7670_GAM4 0x7E
163 #define OV7670_GAM5 0x7F
164 #define OV7670_GAM6 0x80
165 #define OV7670_GAM7 0x81
166 #define OV7670_GAM8 0x82
167 #define OV7670_GAM9 0x83
168 #define OV7670_GAM10 0x84
169 #define OV7670_GAM11 0x85
170 #define OV7670_GAM12 0x86
171 #define OV7670_GAM13 0x87
172 #define OV7670_GAM14 0x88
173 #define OV7670_GAM15 0x89
174 #define OV7670_HAECC1 0x9F
175 #define OV7670_HAECC2 0xA0
176 #define OV7670_HSYEN 0x31
177 #define OV7670_HAECC3 0xA6
178 #define OV7670_HAECC4 0xA7
179 #define OV7670_HAECC5 0xA8
180 #define OV7670_HAECC6 0xA9
181
182 /* OV7670 definitions */
183 #define OV7670_PROD_ID 0x76
184
185 #define OV7670_VIDEO_FORMAT_CAP(width, height, format) \
186 { \
187 .pixelformat = (format), .width_min = (width), .width_max = (width), \
188 .height_min = (height), .height_max = (height), .width_step = 0, .height_step = 0 \
189 }
190
191 static const struct video_format_cap fmts[] = {
192 OV7670_VIDEO_FORMAT_CAP(176, 144, VIDEO_PIX_FMT_RGB565), /* QCIF */
193 OV7670_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_RGB565), /* QVGA */
194 OV7670_VIDEO_FORMAT_CAP(352, 288, VIDEO_PIX_FMT_RGB565), /* CIF */
195 OV7670_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_RGB565), /* VGA */
196 OV7670_VIDEO_FORMAT_CAP(176, 144, VIDEO_PIX_FMT_YUYV), /* QCIF */
197 OV7670_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_YUYV), /* QVGA */
198 OV7670_VIDEO_FORMAT_CAP(352, 288, VIDEO_PIX_FMT_YUYV), /* CIF */
199 OV7670_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_YUYV), /* VGA */
200 {0}};
201
202 /*
203 * This initialization table is based on the MCUX SDK driver for the OV7670.
204 * Note that this table assumes the camera is fed a 6MHz XCLK signal
205 */
206 static const struct ov7670_reg ov7670_init_regtbl[] = {
207 {OV7670_MVFP, 0x20}, /* MVFP: Mirror/VFlip,Normal image */
208
209 /* configure the output timing */
210 /* PCLK does not toggle during horizontal blank, one PCLK, one pixel */
211 {OV7670_COM10, 0x20}, /* COM10 */
212 {OV7670_COM12, 0x00}, /* COM12,No HREF when VSYNC is low */
213 /* Brightness Control, with signal -128 to +128, 0x00 is middle value */
214 {OV7670_BRIGHT, 0x2f},
215
216 /* Internal clock pre-scalar,F(internal clock) = F(input clock)/(Bit[5:0]+1) */
217 {OV7670_CLKRC, 0x80}, /* Clock Div, Input/(n+1), bit6 set to 1 to disable divider */
218
219 /* DBLV,Bit[7:6]: PLL control */
220 /* 0:Bypass PLL, 40: Input clock x4 , 80: Input clock x6 ,C0: Input clock x8 */
221 {OV7670_DBLV, 0x00},
222
223 /* Output Drive Capability */
224 {OV7670_COM2, 0x00}, /* Common Control 2, Output Drive Capability: 1x */
225 {OV7670_BD50MAX, 0x05},
226 {OV7670_BD60MAX, 0x07},
227 {OV7670_HAECC7, 0x94},
228
229 {OV7670_COM4, 0x00},
230 {OV7670_COM6, 0x4b},
231 {OV7670_COM11, 0x9F}, /* Night mode */
232 {OV7670_EDGE, 0x04}, /* Edge Enhancement Adjustment */
233 {OV7670_DNSTH, 0x00}, /* De-noise Strength */
234
235 {OV7670_DM_LNL, 0x00},
236 {OV7670_DM_LNH, 0x00},
237
238 /* reserved */
239 {0x16, 0x02},
240 {0x21, 0x02},
241 {0x22, 0x91},
242 {0x29, 0x07},
243 {0x35, 0x0b},
244 {0x33, 0x0b},
245 {0x37, 0x1d},
246 {0x38, 0x71},
247 {0x39, 0x2a},
248 {0x0e, 0x61},
249 {0x56, 0x40},
250 {0x57, 0x80},
251 {0x69, 0x00},
252 {0x74, 0x19},
253
254 /* display , need retain */
255 {OV7670_COM15, 0xD0}, /* Common Control 15 */
256 {OV7670_TSLB, 0x0C}, /* Line Buffer Test Option */
257 {OV7670_COM13, 0x80}, /* Common Control 13 */
258 {OV7670_MANU, 0x11}, /* Manual U Value */
259 {OV7670_MANV, 0xFF}, /* Manual V Value */
260 /* config the output window data, this can be configed later */
261 {OV7670_HSTART, 0x16}, /* HSTART */
262 {OV7670_HSTOP, 0x04}, /* HSTOP */
263 {OV7670_VSTRT, 0x02}, /* VSTRT */
264 {OV7670_VSTOP, 0x7a}, /* VSTOP */
265 {OV7670_HREF, 0x80}, /* HREF */
266 {OV7670_VREF, 0x0a}, /* VREF */
267
268 /* AGC/AEC - Automatic Gain Control/Automatic exposure Control */
269 {OV7670_GAIN, 0x00}, /* AGC */
270 {OV7670_AECHH, 0x3F}, /* Exposure Value */
271 {OV7670_AECH, 0xFF},
272 {OV7670_COM8, 0x66},
273 {OV7670_COM9, 0x21}, /* limit the max gain */
274 {OV7670_AEW, 0x75},
275 {OV7670_AEB, 0x63},
276 {OV7670_VPT, 0xA5},
277 /* Automatic white balance control */
278 {OV7670_AWBC1, 0x14},
279 {OV7670_AWBC2, 0xf0},
280 {OV7670_AWBC3, 0x34},
281 {OV7670_AWBC4, 0x58},
282 {OV7670_AWBC5, 0x28},
283 {OV7670_AWBC6, 0x3a},
284 /* Matrix Coefficient */
285 {OV7670_MTX1, 0x80},
286 {OV7670_MTX2, 0x80},
287 {OV7670_MTX3, 0x00},
288 {OV7670_MTX4, 0x22},
289 {OV7670_MTX5, 0x5e},
290 {OV7670_MTX6, 0x80},
291 /* AWB Control */
292 {0x59, 0x88},
293 {0x5a, 0x88},
294 {0x5b, 0x44},
295 {0x5c, 0x67},
296 {0x5d, 0x49},
297 {0x5e, 0x0e},
298 {0x6c, 0x0a},
299 {0x6d, 0x55},
300 {0x6e, 0x11},
301 {0x6f, 0x9f},
302 /* Lens Correction Option */
303 {OV7670_LCC1, 0x00},
304 {OV7670_LCC2, 0x00},
305 {OV7670_LCC3, 0x04},
306 {OV7670_LCC4, 0x20},
307 {OV7670_LCC5, 0x05},
308 {OV7670_LCC6, 0x04}, /* effective only when LCC5[2] is high */
309 {OV7670_LCC7, 0x08}, /* effective only when LCC5[2] is high */
310 /* Gamma Curve, needn't config */
311 {OV7670_SLOP, 0x20},
312 {OV7670_GAM1, 0x1c},
313 {OV7670_GAM2, 0x28},
314 {OV7670_GAM3, 0x3c},
315 {OV7670_GAM4, 0x55},
316 {OV7670_GAM5, 0x68},
317 {OV7670_GAM6, 0x76},
318 {OV7670_GAM7, 0x80},
319 {OV7670_GAM8, 0x88},
320 {OV7670_GAM9, 0x8f},
321 {OV7670_GAM10, 0x96},
322 {OV7670_GAM11, 0xa3},
323 {OV7670_GAM12, 0xaf},
324 {OV7670_GAM13, 0xc4},
325 {OV7670_GAM14, 0xd7},
326 {OV7670_GAM15, 0xe8},
327 /* Histogram-based AEC/AGC Control */
328 {OV7670_HAECC1, 0x78},
329 {OV7670_HAECC2, 0x68},
330 {OV7670_HSYEN, 0xff},
331 {0xa1, 0x03},
332 {OV7670_HAECC3, 0xdf},
333 {OV7670_HAECC4, 0xdf},
334 {OV7670_HAECC5, 0xf0},
335 {OV7670_HAECC6, 0x90},
336 /* Automatic black Level Compensation */
337 {0xb0, 0x84},
338 {0xb1, 0x0c},
339 {0xb2, 0x0e},
340 {0xb3, 0x82},
341 {0xb8, 0x0a},
342 };
343
ov7670_get_caps(const struct device * dev,enum video_endpoint_id ep,struct video_caps * caps)344 static int ov7670_get_caps(const struct device *dev, enum video_endpoint_id ep,
345 struct video_caps *caps)
346 {
347 caps->format_caps = fmts;
348 return 0;
349 }
350
ov7670_set_fmt(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)351 static int ov7670_set_fmt(const struct device *dev, enum video_endpoint_id ep,
352 struct video_format *fmt)
353 {
354 const struct ov7670_config *config = dev->config;
355 struct ov7670_data *data = dev->data;
356 const struct ov7670_resolution_cfg *resolution;
357 int ret;
358 uint8_t i = 0U;
359
360 if (fmt->pixelformat != VIDEO_PIX_FMT_RGB565 && fmt->pixelformat != VIDEO_PIX_FMT_YUYV) {
361 LOG_ERR("Only RGB565 and YUYV supported!");
362 return -ENOTSUP;
363 }
364
365 if (!memcmp(&data->fmt, fmt, sizeof(data->fmt))) {
366 /* nothing to do */
367 return 0;
368 }
369
370 memcpy(&data->fmt, fmt, sizeof(data->fmt));
371
372 /* Set output resolution */
373 while (fmts[i].pixelformat) {
374 if (fmts[i].width_min == fmt->width && fmts[i].height_min == fmt->height &&
375 fmts[i].pixelformat == fmt->pixelformat) {
376 /* Set output format */
377 switch (fmts[i].width_min) {
378 case 176: /* QCIF */
379 resolution = &OV7670_RESOLUTION_QCIF;
380 break;
381 case 320: /* QVGA */
382 resolution = &OV7670_RESOLUTION_QVGA;
383 break;
384 case 352: /* CIF */
385 resolution = &OV7670_RESOLUTION_CIF;
386 break;
387 default: /* VGA */
388 resolution = &OV7670_RESOLUTION_VGA;
389 break;
390 }
391 /* Program resolution bytes settings */
392 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_COM7,
393 resolution->com7);
394 if (ret < 0) {
395 return ret;
396 }
397 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_COM3,
398 resolution->com3);
399 if (ret < 0) {
400 return ret;
401 }
402 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_COM14,
403 resolution->com14);
404 if (ret < 0) {
405 return ret;
406 }
407 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_SCALING_XSC,
408 resolution->scaling_xsc);
409 if (ret < 0) {
410 return ret;
411 }
412 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_SCALING_YSC,
413 resolution->scaling_ysc);
414 if (ret < 0) {
415 return ret;
416 }
417 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_SCALING_DCWCTR,
418 resolution->dcwctr);
419 if (ret < 0) {
420 return ret;
421 }
422 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_SCALING_PCLK_DIV,
423 resolution->pclk_div);
424 if (ret < 0) {
425 return ret;
426 }
427 return i2c_reg_write_byte_dt(&config->bus, OV7670_SCALING_PCLK_DELAY,
428 resolution->pclk_delay);
429 }
430 i++;
431 }
432
433 LOG_ERR("Unsupported format");
434 return -ENOTSUP;
435 }
436
ov7670_get_fmt(const struct device * dev,enum video_endpoint_id ep,struct video_format * fmt)437 static int ov7670_get_fmt(const struct device *dev, enum video_endpoint_id ep,
438 struct video_format *fmt)
439 {
440 struct ov7670_data *data = dev->data;
441
442 if (fmt == NULL) {
443 return -EINVAL;
444 }
445 memcpy(fmt, &data->fmt, sizeof(data->fmt));
446 return 0;
447 }
448
ov7670_init(const struct device * dev)449 static int ov7670_init(const struct device *dev)
450 {
451 const struct ov7670_config *config = dev->config;
452 int ret, i;
453 uint8_t pid;
454 struct video_format fmt;
455 const struct ov7670_reg *reg;
456
457 if (!i2c_is_ready_dt(&config->bus)) {
458 /* I2C device is not ready, return */
459 return -ENODEV;
460 }
461
462 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios)
463 /* Power up camera module */
464 if (config->pwdn.port != NULL) {
465 if (!gpio_is_ready_dt(&config->pwdn)) {
466 return -ENODEV;
467 }
468 ret = gpio_pin_configure_dt(&config->pwdn, GPIO_OUTPUT_INACTIVE);
469 if (ret < 0) {
470 LOG_ERR("Could not clear power down pin: %d", ret);
471 return ret;
472 }
473 }
474 #endif
475 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios)
476 /* Reset camera module */
477 if (config->reset.port != NULL) {
478 if (!gpio_is_ready_dt(&config->reset)) {
479 return -ENODEV;
480 }
481 ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT);
482 if (ret < 0) {
483 LOG_ERR("Could not set reset pin: %d", ret);
484 return ret;
485 }
486 /* Reset is active low, has 1ms settling time*/
487 gpio_pin_set_dt(&config->reset, 0);
488 k_msleep(1);
489 gpio_pin_set_dt(&config->reset, 1);
490 k_msleep(1);
491 }
492 #endif
493
494 /*
495 * Read product ID from camera. This camera implements the SCCB,
496 * spec- which *should* be I2C compatible, but in practice does
497 * not seem to respond when I2C repeated start commands are used.
498 * To work around this, use a write then a read to interface with
499 * registers.
500 */
501 uint8_t cmd = OV7670_PID;
502
503 ret = i2c_write_dt(&config->bus, &cmd, sizeof(cmd));
504 if (ret < 0) {
505 LOG_ERR("Could not request product ID: %d", ret);
506 return ret;
507 }
508 ret = i2c_read_dt(&config->bus, &pid, sizeof(pid));
509 if (ret < 0) {
510 LOG_ERR("Could not read product ID: %d", ret);
511 return ret;
512 }
513
514 if (pid != OV7670_PROD_ID) {
515 LOG_ERR("Incorrect product ID: 0x%02X", pid);
516 return -ENODEV;
517 }
518
519 /* Reset camera registers */
520 ret = i2c_reg_write_byte_dt(&config->bus, OV7670_COM7, 0x80);
521 if (ret < 0) {
522 LOG_ERR("Could not reset camera: %d", ret);
523 return ret;
524 }
525 /* Delay after reset */
526 k_msleep(5);
527
528 /* Set default camera format (QVGA, YUYV) */
529 fmt.pixelformat = VIDEO_PIX_FMT_YUYV;
530 fmt.width = 640;
531 fmt.height = 480;
532 fmt.pitch = fmt.width * 2;
533 ret = ov7670_set_fmt(dev, VIDEO_EP_OUT, &fmt);
534 if (ret < 0) {
535 return ret;
536 }
537
538 /* Write initialization values to OV7670 */
539 for (i = 0; i < ARRAY_SIZE(ov7670_init_regtbl); i++) {
540 reg = &ov7670_init_regtbl[i];
541 ret = i2c_reg_write_byte_dt(&config->bus, reg->reg, reg->cmd);
542 if (ret < 0) {
543 return ret;
544 }
545 }
546
547 return 0;
548 }
549
550 static DEVICE_API(video, ov7670_api) = {
551 .set_format = ov7670_set_fmt,
552 .get_format = ov7670_get_fmt,
553 .get_caps = ov7670_get_caps,
554 };
555
556 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios)
557 #define OV7670_RESET_GPIO(inst) .reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {}),
558 #else
559 #define OV7670_RESET_GPIO(inst)
560 #endif
561
562 #if DT_ANY_INST_HAS_PROP_STATUS_OKAY(pwdn_gpios)
563 #define OV7670_PWDN_GPIO(inst) .pwdn = GPIO_DT_SPEC_INST_GET_OR(inst, pwdn_gpios, {}),
564 #else
565 #define OV7670_PWDN_GPIO(inst)
566 #endif
567
568 #define OV7670_INIT(inst) \
569 const struct ov7670_config ov7670_config_##inst = {.bus = I2C_DT_SPEC_INST_GET(inst), \
570 OV7670_RESET_GPIO(inst) \
571 OV7670_PWDN_GPIO(inst)}; \
572 struct ov7670_data ov7670_data_##inst; \
573 \
574 DEVICE_DT_INST_DEFINE(inst, ov7670_init, NULL, &ov7670_data_##inst, &ov7670_config_##inst, \
575 POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &ov7670_api);
576
577 DT_INST_FOREACH_STATUS_OKAY(OV7670_INIT)
578