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