| /* |
| * Copyright (c) 2021 Antmicro <www.antmicro.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ovti_ov2640 |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/device.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/drivers/video.h> |
| #include <zephyr/drivers/video-controls.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/gpio.h> |
| |
| LOG_MODULE_REGISTER(video_ov2640, CONFIG_VIDEO_LOG_LEVEL); |
| |
| /* DSP register bank FF=0x00*/ |
| #define QS 0x44 |
| #define HSIZE 0x51 |
| #define VSIZE 0x52 |
| #define XOFFL 0x53 |
| #define YOFFL 0x54 |
| #define VHYX 0x55 |
| #define TEST 0x57 |
| #define ZMOW 0x5A |
| #define ZMOH 0x5B |
| #define ZMHH 0x5C |
| #define BPADDR 0x7C |
| #define BPDATA 0x7D |
| #define SIZEL 0x8C |
| #define HSIZE8 0xC0 |
| #define VSIZE8 0xC1 |
| #define CTRL1 0xC3 |
| |
| #define CTRLI 0x50 |
| #define CTRLI_LP_DP 0x80 |
| |
| #define CTRL0 0xC2 |
| #define CTRL0_YUV422 0x08 |
| #define CTRL0_YUV_EN 0x04 |
| #define CTRL0_RGB_EN 0x02 |
| |
| #define CTRL2 0x86 |
| #define CTRL2_DCW_EN 0x20 |
| #define CTRL2_SDE_EN 0x10 |
| #define CTRL2_UV_ADJ_EN 0x08 |
| #define CTRL2_UV_AVG_EN 0x04 |
| #define CTRL2_CMX_EN 0x01 |
| |
| #define CTRL3 0x87 |
| #define CTRL3_BPC_EN 0x80 |
| #define CTRL3_WPC_EN 0x40 |
| #define R_DVP_SP 0xD3 |
| #define R_DVP_SP_AUTO_MODE 0x80 |
| |
| #define R_BYPASS 0x05 |
| #define R_BYPASS_DSP_EN 0x00 |
| #define R_BYPASS_DSP_BYPAS 0x01 |
| |
| #define IMAGE_MODE 0xDA |
| #define IMAGE_MODE_JPEG_EN 0x10 |
| #define IMAGE_MODE_RGB565 0x08 |
| |
| #define RESET 0xE0 |
| #define RESET_JPEG 0x10 |
| #define RESET_DVP 0x04 |
| |
| #define MC_BIST 0xF9 |
| #define MC_BIST_RESET 0x80 |
| #define MC_BIST_BOOT_ROM_SEL 0x40 |
| |
| #define BANK_SEL 0xFF |
| #define BANK_SEL_DSP 0x00 |
| #define BANK_SEL_SENSOR 0x01 |
| |
| /* Sensor register bank FF=0x01*/ |
| #define COM1 0x03 |
| #define REG_PID 0x0A |
| #define REG_PID_VAL 0x26 |
| #define REG_VER 0x0B |
| #define REG_VER_VAL 0x42 |
| #define AEC 0x10 |
| #define CLKRC 0x11 |
| #define COM10 0x15 |
| #define HSTART 0x17 |
| #define HSTOP 0x18 |
| #define VSTART 0x19 |
| #define VSTOP 0x1A |
| #define AEW 0x24 |
| #define AEB 0x25 |
| #define ARCOM2 0x34 |
| #define FLL 0x46 |
| #define FLH 0x47 |
| #define COM19 0x48 |
| #define ZOOMS 0x49 |
| #define BD50 0x4F |
| #define BD60 0x50 |
| #define REG5D 0x5D |
| #define REG5E 0x5E |
| #define REG5F 0x5F |
| #define REG60 0x60 |
| #define HISTO_LOW 0x61 |
| #define HISTO_HIGH 0x62 |
| |
| #define REG04 0x04 |
| #define REG04_DEFAULT 0x28 |
| #define REG04_HFLIP_IMG 0x80 |
| #define REG04_VFLIP_IMG 0x40 |
| #define REG04_VREF_EN 0x10 |
| #define REG04_HREF_EN 0x08 |
| #define REG04_SET(x) (REG04_DEFAULT | x) |
| |
| #define COM2 0x09 |
| #define COM2_OUT_DRIVE_3x 0x02 |
| |
| #define COM3 0x0C |
| #define COM3_DEFAULT 0x38 |
| #define COM3_BAND_AUTO 0x02 |
| #define COM3_BAND_SET(x) (COM3_DEFAULT | x) |
| |
| #define COM7 0x12 |
| #define COM7_SRST 0x80 |
| #define COM7_RES_UXGA 0x00 /* UXGA */ |
| #define COM7_ZOOM_EN 0x04 /* Enable Zoom */ |
| #define COM7_COLOR_BAR 0x02 /* Enable Color Bar Test */ |
| |
| #define COM8 0x13 |
| #define COM8_DEFAULT 0xC0 |
| #define COM8_BNDF_EN 0x20 /* Enable Banding filter */ |
| #define COM8_AGC_EN 0x04 /* AGC Auto/Manual control selection */ |
| #define COM8_AEC_EN 0x01 /* Auto/Manual Exposure control */ |
| #define COM8_SET(x) (COM8_DEFAULT | x) |
| |
| #define COM9 0x14 /* AGC gain ceiling */ |
| #define COM9_DEFAULT 0x08 |
| #define COM9_AGC_GAIN_8x 0x02 /* AGC: 8x */ |
| #define COM9_AGC_SET(x) (COM9_DEFAULT | (x << 5)) |
| |
| #define COM10 0x15 |
| |
| #define CTRL1_AWB 0x08 /* Enable AWB */ |
| |
| #define VV 0x26 |
| #define VV_AGC_TH_SET(h, l) ((h << 4) | (l & 0x0F)) |
| |
| #define REG32 0x32 |
| #define REG32_UXGA 0x36 |
| |
| /* Configuration arrays */ |
| #define SVGA_HSIZE (800) |
| #define SVGA_VSIZE (600) |
| |
| #define UXGA_HSIZE (1600) |
| #define UXGA_VSIZE (1200) |
| |
| struct ov2640_reg { |
| uint8_t addr; |
| uint8_t value; |
| }; |
| |
| static const struct ov2640_reg default_regs[] = { |
| { BANK_SEL, BANK_SEL_DSP }, |
| { 0x2c, 0xff }, |
| { 0x2e, 0xdf }, |
| { BANK_SEL, BANK_SEL_SENSOR }, |
| { 0x3c, 0x32 }, |
| { CLKRC, 0x80 }, /* Set PCLK divider */ |
| { COM2, COM2_OUT_DRIVE_3x }, /* Output drive x2 */ |
| { REG04, REG04_SET(REG04_HREF_EN)}, |
| { COM8, COM8_SET(COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN) }, |
| { COM9, COM9_AGC_SET(COM9_AGC_GAIN_8x)}, |
| { COM10, 0x00 }, /* Invert VSYNC */ |
| { 0x2c, 0x0c }, |
| { 0x33, 0x78 }, |
| { 0x3a, 0x33 }, |
| { 0x3b, 0xfb }, |
| { 0x3e, 0x00 }, |
| { 0x43, 0x11 }, |
| { 0x16, 0x10 }, |
| { 0x39, 0x02 }, |
| { 0x35, 0x88 }, |
| { 0x22, 0x0a }, |
| { 0x37, 0x40 }, |
| { 0x23, 0x00 }, |
| { ARCOM2, 0xa0 }, |
| { 0x06, 0x02 }, |
| { 0x06, 0x88 }, |
| { 0x07, 0xc0 }, |
| { 0x0d, 0xb7 }, |
| { 0x0e, 0x01 }, |
| { 0x4c, 0x00 }, |
| { 0x4a, 0x81 }, |
| { 0x21, 0x99 }, |
| { AEW, 0x40 }, |
| { AEB, 0x38 }, |
| /* AGC/AEC fast mode operating region */ |
| { VV, VV_AGC_TH_SET(0x08, 0x02) }, |
| { COM19, 0x00 }, /* Zoom control 2 LSBs */ |
| { ZOOMS, 0x00 }, /* Zoom control 8 MSBs */ |
| { 0x5c, 0x00 }, |
| { 0x63, 0x00 }, |
| { FLL, 0x00 }, |
| { FLH, 0x00 }, |
| |
| /* Set banding filter */ |
| { COM3, COM3_BAND_SET(COM3_BAND_AUTO) }, |
| { REG5D, 0x55 }, |
| { REG5E, 0x7d }, |
| { REG5F, 0x7d }, |
| { REG60, 0x55 }, |
| { HISTO_LOW, 0x70 }, |
| { HISTO_HIGH, 0x80 }, |
| { 0x7c, 0x05 }, |
| { 0x20, 0x80 }, |
| { 0x28, 0x30 }, |
| { 0x6c, 0x00 }, |
| { 0x6d, 0x80 }, |
| { 0x6e, 0x00 }, |
| { 0x70, 0x02 }, |
| { 0x71, 0x94 }, |
| { 0x73, 0xc1 }, |
| { 0x3d, 0x34 }, |
| /* { COM7, COM7_RES_UXGA | COM7_ZOOM_EN }, */ |
| { 0x5a, 0x57 }, |
| { BD50, 0xbb }, |
| { BD60, 0x9c }, |
| |
| { BANK_SEL, BANK_SEL_DSP }, |
| { 0xe5, 0x7f }, |
| { MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL }, |
| { 0x41, 0x24 }, |
| { RESET, RESET_JPEG | RESET_DVP }, |
| { 0x76, 0xff }, |
| { 0x33, 0xa0 }, |
| { 0x42, 0x20 }, |
| { 0x43, 0x18 }, |
| { 0x4c, 0x00 }, |
| { CTRL3, CTRL3_BPC_EN | CTRL3_WPC_EN | 0x10 }, |
| { 0x88, 0x3f }, |
| { 0xd7, 0x03 }, |
| { 0xd9, 0x10 }, |
| { R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x2 }, |
| { 0xc8, 0x08 }, |
| { 0xc9, 0x80 }, |
| { BPADDR, 0x00 }, |
| { BPDATA, 0x00 }, |
| { BPADDR, 0x03 }, |
| { BPDATA, 0x48 }, |
| { BPDATA, 0x48 }, |
| { BPADDR, 0x08 }, |
| { BPDATA, 0x20 }, |
| { BPDATA, 0x10 }, |
| { BPDATA, 0x0e }, |
| { 0x90, 0x00 }, |
| { 0x91, 0x0e }, |
| { 0x91, 0x1a }, |
| { 0x91, 0x31 }, |
| { 0x91, 0x5a }, |
| { 0x91, 0x69 }, |
| { 0x91, 0x75 }, |
| { 0x91, 0x7e }, |
| { 0x91, 0x88 }, |
| { 0x91, 0x8f }, |
| { 0x91, 0x96 }, |
| { 0x91, 0xa3 }, |
| { 0x91, 0xaf }, |
| { 0x91, 0xc4 }, |
| { 0x91, 0xd7 }, |
| { 0x91, 0xe8 }, |
| { 0x91, 0x20 }, |
| { 0x92, 0x00 }, |
| { 0x93, 0x06 }, |
| { 0x93, 0xe3 }, |
| { 0x93, 0x03 }, |
| { 0x93, 0x03 }, |
| { 0x93, 0x00 }, |
| { 0x93, 0x02 }, |
| { 0x93, 0x00 }, |
| { 0x93, 0x00 }, |
| { 0x93, 0x00 }, |
| { 0x93, 0x00 }, |
| { 0x93, 0x00 }, |
| { 0x93, 0x00 }, |
| { 0x93, 0x00 }, |
| { 0x96, 0x00 }, |
| { 0x97, 0x08 }, |
| { 0x97, 0x19 }, |
| { 0x97, 0x02 }, |
| { 0x97, 0x0c }, |
| { 0x97, 0x24 }, |
| { 0x97, 0x30 }, |
| { 0x97, 0x28 }, |
| { 0x97, 0x26 }, |
| { 0x97, 0x02 }, |
| { 0x97, 0x98 }, |
| { 0x97, 0x80 }, |
| { 0x97, 0x00 }, |
| { 0x97, 0x00 }, |
| { 0xa4, 0x00 }, |
| { 0xa8, 0x00 }, |
| { 0xc5, 0x11 }, |
| { 0xc6, 0x51 }, |
| { 0xbf, 0x80 }, |
| { 0xc7, 0x10 }, |
| { 0xb6, 0x66 }, |
| { 0xb8, 0xA5 }, |
| { 0xb7, 0x64 }, |
| { 0xb9, 0x7C }, |
| { 0xb3, 0xaf }, |
| { 0xb4, 0x97 }, |
| { 0xb5, 0xFF }, |
| { 0xb0, 0xC5 }, |
| { 0xb1, 0x94 }, |
| { 0xb2, 0x0f }, |
| { 0xc4, 0x5c }, |
| { 0xa6, 0x00 }, |
| { 0xa7, 0x20 }, |
| { 0xa7, 0xd8 }, |
| { 0xa7, 0x1b }, |
| { 0xa7, 0x31 }, |
| { 0xa7, 0x00 }, |
| { 0xa7, 0x18 }, |
| { 0xa7, 0x20 }, |
| { 0xa7, 0xd8 }, |
| { 0xa7, 0x19 }, |
| { 0xa7, 0x31 }, |
| { 0xa7, 0x00 }, |
| { 0xa7, 0x18 }, |
| { 0xa7, 0x20 }, |
| { 0xa7, 0xd8 }, |
| { 0xa7, 0x19 }, |
| { 0xa7, 0x31 }, |
| { 0xa7, 0x00 }, |
| { 0xa7, 0x18 }, |
| { 0x7f, 0x00 }, |
| { 0xe5, 0x1f }, |
| { 0xe1, 0x77 }, |
| { 0xdd, 0x7f }, |
| { CTRL0, CTRL0_YUV422 | CTRL0_YUV_EN | CTRL0_RGB_EN }, |
| { 0x00, 0x00 } |
| }; |
| |
| static const struct ov2640_reg uxga_regs[] = { |
| { BANK_SEL, BANK_SEL_SENSOR }, |
| /* DSP input image resolution and window size control */ |
| { COM7, COM7_RES_UXGA}, |
| { COM1, 0x0F }, /* UXGA=0x0F, SVGA=0x0A, CIF=0x06 */ |
| { REG32, REG32_UXGA }, /* UXGA=0x36, SVGA/CIF=0x09 */ |
| |
| { HSTART, 0x11 }, /* UXGA=0x11, SVGA/CIF=0x11 */ |
| { HSTOP, 0x75 }, /* UXGA=0x75, SVGA/CIF=0x43 */ |
| |
| { VSTART, 0x01 }, /* UXGA=0x01, SVGA/CIF=0x00 */ |
| { VSTOP, 0x97 }, /* UXGA=0x97, SVGA/CIF=0x4b */ |
| { 0x3d, 0x34 }, /* UXGA=0x34, SVGA/CIF=0x38 */ |
| |
| { 0x35, 0x88 }, |
| { 0x22, 0x0a }, |
| { 0x37, 0x40 }, |
| { 0x34, 0xa0 }, |
| { 0x06, 0x02 }, |
| { 0x0d, 0xb7 }, |
| { 0x0e, 0x01 }, |
| { 0x42, 0x83 }, |
| |
| /* |
| * Set DSP input image size and offset. |
| * The sensor output image can be scaled with OUTW/OUTH |
| */ |
| { BANK_SEL, BANK_SEL_DSP }, |
| { R_BYPASS, R_BYPASS_DSP_BYPAS }, |
| |
| { RESET, RESET_DVP }, |
| { HSIZE8, (UXGA_HSIZE>>3)}, /* Image Horizontal Size HSIZE[10:3] */ |
| { VSIZE8, (UXGA_VSIZE>>3)}, /* Image Vertical Size VSIZE[10:3] */ |
| |
| /* {HSIZE[11], HSIZE[2:0], VSIZE[2:0]} */ |
| { SIZEL, ((UXGA_HSIZE>>6)&0x40) | ((UXGA_HSIZE&0x7)<<3) | (UXGA_VSIZE&0x7)}, |
| |
| { XOFFL, 0x00 }, /* OFFSET_X[7:0] */ |
| { YOFFL, 0x00 }, /* OFFSET_Y[7:0] */ |
| { HSIZE, ((UXGA_HSIZE>>2)&0xFF) }, /* H_SIZE[7:0] real/4 */ |
| { VSIZE, ((UXGA_VSIZE>>2)&0xFF) }, /* V_SIZE[7:0] real/4 */ |
| |
| /* V_SIZE[8]/OFFSET_Y[10:8]/H_SIZE[8]/OFFSET_X[10:8] */ |
| { VHYX, ((UXGA_VSIZE>>3)&0x80) | ((UXGA_HSIZE>>7)&0x08) }, |
| { TEST, (UXGA_HSIZE>>4)&0x80}, /* H_SIZE[9] */ |
| |
| { CTRL2, CTRL2_DCW_EN | CTRL2_SDE_EN | |
| CTRL2_UV_AVG_EN | CTRL2_CMX_EN | CTRL2_UV_ADJ_EN }, |
| |
| /* H_DIVIDER/V_DIVIDER */ |
| { CTRLI, CTRLI_LP_DP | 0x00}, |
| /* DVP prescaler */ |
| { R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x04}, |
| |
| { R_BYPASS, R_BYPASS_DSP_EN }, |
| { RESET, 0x00 }, |
| {0, 0}, |
| }; |
| |
| #define NUM_BRIGHTNESS_LEVELS (5) |
| static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS + 1][5] = { |
| { BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, |
| { 0x00, 0x04, 0x09, 0x00, 0x00 }, /* -2 */ |
| { 0x00, 0x04, 0x09, 0x10, 0x00 }, /* -1 */ |
| { 0x00, 0x04, 0x09, 0x20, 0x00 }, /* 0 */ |
| { 0x00, 0x04, 0x09, 0x30, 0x00 }, /* +1 */ |
| { 0x00, 0x04, 0x09, 0x40, 0x00 }, /* +2 */ |
| }; |
| |
| #define NUM_CONTRAST_LEVELS (5) |
| static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS + 1][7] = { |
| { BPADDR, BPDATA, BPADDR, BPDATA, BPDATA, BPDATA, BPDATA }, |
| { 0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06 }, /* -2 */ |
| { 0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06 }, /* -1 */ |
| { 0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06 }, /* 0 */ |
| { 0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06 }, /* +1 */ |
| { 0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06 }, /* +2 */ |
| }; |
| |
| #define NUM_SATURATION_LEVELS (5) |
| static const uint8_t saturation_regs[NUM_SATURATION_LEVELS + 1][5] = { |
| { BPADDR, BPDATA, BPADDR, BPDATA, BPDATA }, |
| { 0x00, 0x02, 0x03, 0x28, 0x28 }, /* -2 */ |
| { 0x00, 0x02, 0x03, 0x38, 0x38 }, /* -1 */ |
| { 0x00, 0x02, 0x03, 0x48, 0x48 }, /* 0 */ |
| { 0x00, 0x02, 0x03, 0x58, 0x58 }, /* +1 */ |
| { 0x00, 0x02, 0x03, 0x58, 0x58 }, /* +2 */ |
| }; |
| |
| struct ov2640_config { |
| struct i2c_dt_spec i2c; |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| struct gpio_dt_spec reset_gpio; |
| #endif |
| uint8_t clock_rate_control; |
| }; |
| |
| struct ov2640_data { |
| struct video_format fmt; |
| }; |
| |
| #define OV2640_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[] = { |
| OV2640_VIDEO_FORMAT_CAP(160, 120, VIDEO_PIX_FMT_RGB565), /* QQVGA */ |
| OV2640_VIDEO_FORMAT_CAP(176, 144, VIDEO_PIX_FMT_RGB565), /* QCIF */ |
| OV2640_VIDEO_FORMAT_CAP(240, 160, VIDEO_PIX_FMT_RGB565), /* HQVGA */ |
| OV2640_VIDEO_FORMAT_CAP(240, 240, VIDEO_PIX_FMT_RGB565), /* 240x240 */ |
| OV2640_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_RGB565), /* QVGA */ |
| OV2640_VIDEO_FORMAT_CAP(352, 288, VIDEO_PIX_FMT_RGB565), /* CIF */ |
| OV2640_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_RGB565), /* VGA */ |
| OV2640_VIDEO_FORMAT_CAP(800, 600, VIDEO_PIX_FMT_RGB565), /* SVGA */ |
| OV2640_VIDEO_FORMAT_CAP(1024, 768, VIDEO_PIX_FMT_RGB565), /* XVGA */ |
| OV2640_VIDEO_FORMAT_CAP(1280, 1024, VIDEO_PIX_FMT_RGB565), /* SXGA */ |
| OV2640_VIDEO_FORMAT_CAP(1600, 1200, VIDEO_PIX_FMT_RGB565), /* UXGA */ |
| OV2640_VIDEO_FORMAT_CAP(160, 120, VIDEO_PIX_FMT_JPEG), /* QQVGA */ |
| OV2640_VIDEO_FORMAT_CAP(176, 144, VIDEO_PIX_FMT_JPEG), /* QCIF */ |
| OV2640_VIDEO_FORMAT_CAP(240, 160, VIDEO_PIX_FMT_JPEG), /* HQVGA */ |
| OV2640_VIDEO_FORMAT_CAP(320, 240, VIDEO_PIX_FMT_JPEG), /* QVGA */ |
| OV2640_VIDEO_FORMAT_CAP(352, 288, VIDEO_PIX_FMT_JPEG), /* CIF */ |
| OV2640_VIDEO_FORMAT_CAP(640, 480, VIDEO_PIX_FMT_JPEG), /* VGA */ |
| OV2640_VIDEO_FORMAT_CAP(800, 600, VIDEO_PIX_FMT_JPEG), /* SVGA */ |
| OV2640_VIDEO_FORMAT_CAP(1024, 768, VIDEO_PIX_FMT_JPEG), /* XVGA */ |
| OV2640_VIDEO_FORMAT_CAP(1280, 1024, VIDEO_PIX_FMT_JPEG), /* SXGA */ |
| OV2640_VIDEO_FORMAT_CAP(1600, 1200, VIDEO_PIX_FMT_JPEG), /* UXGA */ |
| { 0 } |
| }; |
| |
| static int ov2640_write_reg(const struct i2c_dt_spec *spec, uint8_t reg_addr, |
| uint8_t value) |
| { |
| uint8_t tries = 3; |
| |
| /** |
| * It rarely happens that the camera does not respond with ACK signal. |
| * In that case it usually responds on 2nd try but there is a 3rd one |
| * just to be sure that the connection error is not caused by driver |
| * itself. |
| */ |
| while (tries--) { |
| if (!i2c_reg_write_byte_dt(spec, reg_addr, value)) { |
| return 0; |
| } |
| /* If writing failed wait 5ms before next attempt */ |
| k_msleep(5); |
| } |
| LOG_ERR("failed to write 0x%x to 0x%x", value, reg_addr); |
| |
| return -1; |
| } |
| |
| static int ov2640_read_reg(const struct i2c_dt_spec *spec, uint8_t reg_addr) |
| { |
| uint8_t tries = 3; |
| uint8_t value; |
| |
| /** |
| * It rarely happens that the camera does not respond with ACK signal. |
| * In that case it usually responds on 2nd try but there is a 3rd one |
| * just to be sure that the connection error is not caused by driver |
| * itself. |
| */ |
| while (tries--) { |
| if (!i2c_reg_read_byte_dt(spec, reg_addr, &value)) { |
| return value; |
| } |
| /* If reading failed wait 5ms before next attempt */ |
| k_msleep(5); |
| } |
| LOG_ERR("failed to read 0x%x register", reg_addr); |
| |
| return -1; |
| } |
| |
| static int ov2640_write_all(const struct device *dev, |
| const struct ov2640_reg *regs, uint16_t reg_num) |
| { |
| uint16_t i = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| for (i = 0; i < reg_num; i++) { |
| int err; |
| |
| err = ov2640_write_reg(&cfg->i2c, regs[i].addr, regs[i].value); |
| if (err) { |
| return err; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int ov2640_soft_reset(const struct device *dev) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| /* Switch to DSP register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| |
| /* Initiate system reset */ |
| ret |= ov2640_write_reg(&cfg->i2c, COM7, COM7_SRST); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_level(const struct device *dev, int level, |
| int max_level, int cols, const uint8_t regs[][cols]) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| level += (max_level / 2 + 1); |
| if (level < 0 || level > max_level) { |
| return -ENOTSUP; |
| } |
| |
| /* Switch to DSP register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_DSP); |
| |
| for (int i = 0; i < (ARRAY_SIZE(regs[0]) / sizeof(regs[0][0])); i++) { |
| ret |= ov2640_write_reg(&cfg->i2c, regs[0][i], regs[level][i]); |
| } |
| |
| return ret; |
| } |
| |
| static int ov2640_set_brightness(const struct device *dev, int level) |
| { |
| int ret = 0; |
| |
| ret = ov2640_set_level(dev, level, NUM_BRIGHTNESS_LEVELS, |
| ARRAY_SIZE(brightness_regs[0]), brightness_regs); |
| |
| if (ret == -ENOTSUP) { |
| LOG_ERR("Brightness level %d not supported", level); |
| } |
| |
| return ret; |
| } |
| |
| static int ov2640_set_saturation(const struct device *dev, int level) |
| { |
| int ret = 0; |
| |
| ret = ov2640_set_level(dev, level, NUM_SATURATION_LEVELS, |
| ARRAY_SIZE(saturation_regs[0]), saturation_regs); |
| |
| if (ret == -ENOTSUP) { |
| LOG_ERR("Saturation level %d not supported", level); |
| } |
| |
| return ret; |
| } |
| |
| static int ov2640_set_contrast(const struct device *dev, int level) |
| { |
| int ret = 0; |
| |
| ret = ov2640_set_level(dev, level, NUM_CONTRAST_LEVELS, |
| ARRAY_SIZE(contrast_regs[0]), contrast_regs); |
| |
| if (ret == -ENOTSUP) { |
| LOG_ERR("Contrast level %d not supported", level); |
| } |
| |
| return ret; |
| } |
| |
| static int ov2640_set_output_format(const struct device *dev, |
| int output_format) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| /* Switch to DSP register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_DSP); |
| |
| if (output_format == VIDEO_PIX_FMT_JPEG) { |
| /* Enable JPEG compression */ |
| ret |= ov2640_write_reg(&cfg->i2c, IMAGE_MODE, IMAGE_MODE_JPEG_EN); |
| } else if (output_format == VIDEO_PIX_FMT_RGB565) { |
| /* Disable JPEG compression and set output to RGB565 */ |
| ret |= ov2640_write_reg(&cfg->i2c, IMAGE_MODE, IMAGE_MODE_RGB565); |
| } else { |
| LOG_ERR("Image format not supported"); |
| return -ENOTSUP; |
| } |
| k_msleep(30); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_quality(const struct device *dev, int qs) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| /* Switch to DSP register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_DSP); |
| |
| /* Write QS register */ |
| ret |= ov2640_write_reg(&cfg->i2c, QS, qs); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_colorbar(const struct device *dev, uint8_t enable) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint8_t reg; |
| |
| /* Switch to SENSOR register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| |
| /* Update COM7 to enable/disable color bar test pattern */ |
| reg = ov2640_read_reg(&cfg->i2c, COM7); |
| |
| if (enable) { |
| reg |= COM7_COLOR_BAR; |
| } else { |
| reg &= ~COM7_COLOR_BAR; |
| } |
| |
| ret |= ov2640_write_reg(&cfg->i2c, COM7, reg); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_white_bal(const struct device *dev, int enable) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint8_t reg; |
| |
| /* Switch to SENSOR register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| |
| /* Update CTRL1 to enable/disable automatic white balance*/ |
| reg = ov2640_read_reg(&cfg->i2c, CTRL1); |
| |
| if (enable) { |
| reg |= CTRL1_AWB; |
| } else { |
| reg &= ~CTRL1_AWB; |
| } |
| |
| ret |= ov2640_write_reg(&cfg->i2c, CTRL1, reg); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_gain_ctrl(const struct device *dev, int enable) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint8_t reg; |
| |
| /* Switch to SENSOR register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| |
| /* Update COM8 to enable/disable automatic gain control */ |
| reg = ov2640_read_reg(&cfg->i2c, COM8); |
| |
| if (enable) { |
| reg |= COM8_AGC_EN; |
| } else { |
| reg &= ~COM8_AGC_EN; |
| } |
| |
| ret |= ov2640_write_reg(&cfg->i2c, COM8, reg); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_exposure_ctrl(const struct device *dev, int enable) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint8_t reg; |
| |
| /* Switch to SENSOR register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| |
| /* Update COM8 to enable/disable automatic exposure control */ |
| reg = ov2640_read_reg(&cfg->i2c, COM8); |
| |
| if (enable) { |
| reg |= COM8_AEC_EN; |
| } else { |
| reg &= ~COM8_AEC_EN; |
| } |
| |
| ret |= ov2640_write_reg(&cfg->i2c, COM8, reg); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_horizontal_mirror(const struct device *dev, |
| int enable) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint8_t reg; |
| |
| /* Switch to SENSOR register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| |
| /* Update REG04 to enable/disable horizontal mirror */ |
| reg = ov2640_read_reg(&cfg->i2c, REG04); |
| |
| if (enable) { |
| reg |= REG04_HFLIP_IMG; |
| } else { |
| reg &= ~REG04_HFLIP_IMG; |
| } |
| |
| ret |= ov2640_write_reg(&cfg->i2c, REG04, reg); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_vertical_flip(const struct device *dev, int enable) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint8_t reg; |
| |
| /* Switch to SENSOR register bank */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| |
| /* Update REG04 to enable/disable vertical flip */ |
| reg = ov2640_read_reg(&cfg->i2c, REG04); |
| |
| if (enable) { |
| reg |= REG04_VFLIP_IMG | REG04_VREF_EN; |
| } else { |
| reg &= ~(REG04_VFLIP_IMG | REG04_VREF_EN); |
| } |
| |
| ret |= ov2640_write_reg(&cfg->i2c, REG04, reg); |
| |
| return ret; |
| } |
| |
| static int ov2640_set_resolution(const struct device *dev, |
| uint16_t img_width, uint16_t img_height) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint16_t w = img_width; |
| uint16_t h = img_height; |
| |
| /* Disable DSP */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_DSP); |
| ret |= ov2640_write_reg(&cfg->i2c, R_BYPASS, R_BYPASS_DSP_BYPAS); |
| |
| /* Write output width */ |
| ret |= ov2640_write_reg(&cfg->i2c, ZMOW, (w >> 2) & 0xFF); /* OUTW[7:0] (real/4) */ |
| ret |= ov2640_write_reg(&cfg->i2c, ZMOH, (h >> 2) & 0xFF); /* OUTH[7:0] (real/4) */ |
| ret |= ov2640_write_reg(&cfg->i2c, ZMHH, ((h >> 8) & 0x04) | |
| ((w>>10) & 0x03)); /* OUTH[8]/OUTW[9:8] */ |
| |
| /* Set CLKRC */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| ret |= ov2640_write_reg(&cfg->i2c, CLKRC, cfg->clock_rate_control); |
| |
| /* Write DSP input registers */ |
| ov2640_write_all(dev, uxga_regs, ARRAY_SIZE(uxga_regs)); |
| |
| /* Enable DSP */ |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_DSP); |
| ret |= ov2640_write_reg(&cfg->i2c, R_BYPASS, R_BYPASS_DSP_EN); |
| |
| k_msleep(30); |
| |
| return ret; |
| } |
| |
| uint8_t ov2640_check_connection(const struct device *dev) |
| { |
| int ret = 0; |
| const struct ov2640_config *cfg = dev->config; |
| |
| uint8_t reg_pid_val, reg_ver_val; |
| |
| ret |= ov2640_write_reg(&cfg->i2c, BANK_SEL, BANK_SEL_SENSOR); |
| reg_pid_val = ov2640_read_reg(&cfg->i2c, REG_PID); |
| reg_ver_val = ov2640_read_reg(&cfg->i2c, REG_VER); |
| |
| if (REG_PID_VAL != reg_pid_val || REG_VER_VAL != reg_ver_val) { |
| LOG_ERR("OV2640 not detected\n"); |
| return -ENODEV; |
| } |
| |
| return ret; |
| } |
| |
| static int ov2640_set_fmt(const struct device *dev, |
| enum video_endpoint_id ep, struct video_format *fmt) |
| { |
| struct ov2640_data *drv_data = dev->data; |
| uint16_t width, height; |
| int ret = 0; |
| int i = 0; |
| |
| /* We only support RGB565 and JPEG pixel formats */ |
| if (fmt->pixelformat != VIDEO_PIX_FMT_RGB565 && fmt->pixelformat != VIDEO_PIX_FMT_JPEG) { |
| LOG_ERR("ov2640 camera supports only RGB565 and JPG pixelformats!"); |
| return -ENOTSUP; |
| } |
| |
| width = fmt->width; |
| height = fmt->height; |
| |
| if (!memcmp(&drv_data->fmt, fmt, sizeof(drv_data->fmt))) { |
| /* nothing to do */ |
| return 0; |
| } |
| |
| drv_data->fmt = *fmt; |
| |
| /* Set output format */ |
| ret |= ov2640_set_output_format(dev, fmt->pixelformat); |
| |
| /* Check if camera is capable of handling given format */ |
| while (fmts[i].pixelformat) { |
| if (fmts[i].width_min == width && fmts[i].height_min == height && |
| fmts[i].pixelformat == fmt->pixelformat) { |
| /* Set window size */ |
| ret |= ov2640_set_resolution(dev, fmt->width, fmt->height); |
| return ret; |
| } |
| i++; |
| } |
| |
| /* Camera is not capable of handling given format */ |
| LOG_ERR("Image format not supported\n"); |
| return -ENOTSUP; |
| } |
| |
| static int ov2640_get_fmt(const struct device *dev, |
| enum video_endpoint_id ep, struct video_format *fmt) |
| { |
| struct ov2640_data *drv_data = dev->data; |
| |
| *fmt = drv_data->fmt; |
| |
| return 0; |
| } |
| |
| static int ov2640_stream_start(const struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int ov2640_stream_stop(const struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int ov2640_get_caps(const struct device *dev, |
| enum video_endpoint_id ep, |
| struct video_caps *caps) |
| { |
| caps->format_caps = fmts; |
| return 0; |
| } |
| |
| static int ov2640_set_ctrl(const struct device *dev, |
| unsigned int cid, void *value) |
| { |
| int ret = 0; |
| |
| switch (cid) { |
| case VIDEO_CID_HFLIP: |
| ret |= ov2640_set_horizontal_mirror(dev, (int)value); |
| break; |
| case VIDEO_CID_VFLIP: |
| ret |= ov2640_set_vertical_flip(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_EXPOSURE: |
| ret |= ov2640_set_exposure_ctrl(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_GAIN: |
| ret |= ov2640_set_gain_ctrl(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_BRIGHTNESS: |
| ret |= ov2640_set_brightness(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_SATURATION: |
| ret |= ov2640_set_saturation(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_WHITE_BAL: |
| ret |= ov2640_set_white_bal(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_CONTRAST: |
| ret |= ov2640_set_contrast(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_TEST_PATTERN: |
| ret |= ov2640_set_colorbar(dev, (int)value); |
| break; |
| case VIDEO_CID_CAMERA_QUALITY: |
| ret |= ov2640_set_quality(dev, (int)value); |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return ret; |
| } |
| |
| static const struct video_driver_api ov2640_driver_api = { |
| .set_format = ov2640_set_fmt, |
| .get_format = ov2640_get_fmt, |
| .get_caps = ov2640_get_caps, |
| .stream_start = ov2640_stream_start, |
| .stream_stop = ov2640_stream_stop, |
| .set_ctrl = ov2640_set_ctrl, |
| }; |
| |
| static int ov2640_init(const struct device *dev) |
| { |
| struct video_format fmt; |
| int ret = 0; |
| |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| const struct ov2640_config *cfg = dev->config; |
| |
| ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE); |
| if (ret) { |
| return ret; |
| } |
| |
| gpio_pin_set_dt(&cfg->reset_gpio, 0); |
| k_sleep(K_MSEC(1)); |
| gpio_pin_set_dt(&cfg->reset_gpio, 1); |
| k_sleep(K_MSEC(1)); |
| #endif |
| |
| ret = ov2640_check_connection(dev); |
| |
| if (ret) { |
| return ret; |
| } |
| |
| ov2640_soft_reset(dev); |
| k_msleep(300); |
| |
| ov2640_write_all(dev, default_regs, ARRAY_SIZE(default_regs)); |
| |
| /* set default/init format SVGA RGB565 */ |
| fmt.pixelformat = VIDEO_PIX_FMT_RGB565; |
| fmt.width = SVGA_HSIZE; |
| fmt.height = SVGA_VSIZE; |
| fmt.pitch = SVGA_HSIZE * 2; |
| ret = ov2640_set_fmt(dev, VIDEO_EP_OUT, &fmt); |
| if (ret) { |
| LOG_ERR("Unable to configure default format"); |
| return -EIO; |
| } |
| |
| ret |= ov2640_set_exposure_ctrl(dev, 1); |
| ret |= ov2640_set_white_bal(dev, 1); |
| |
| return ret; |
| } |
| |
| /* Unique Instance */ |
| static const struct ov2640_config ov2640_cfg_0 = { |
| .i2c = I2C_DT_SPEC_INST_GET(0), |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| .reset_gpio = GPIO_DT_SPEC_INST_GET(0, reset_gpios), |
| #endif |
| .clock_rate_control = DT_INST_PROP(0, clock_rate_control), |
| }; |
| static struct ov2640_data ov2640_data_0; |
| |
| static int ov2640_init_0(const struct device *dev) |
| { |
| const struct ov2640_config *cfg = dev->config; |
| |
| if (!device_is_ready(cfg->i2c.bus)) { |
| LOG_ERR("Bus device is not ready"); |
| return -ENODEV; |
| } |
| |
| #if DT_INST_NODE_HAS_PROP(0, reset_gpios) |
| if (!gpio_is_ready_dt(&cfg->reset_gpio)) { |
| LOG_ERR("%s: device %s is not ready", dev->name, |
| cfg->reset_gpio.port->name); |
| return -ENODEV; |
| } |
| #endif |
| |
| uint32_t i2c_cfg = I2C_MODE_CONTROLLER | |
| I2C_SPEED_SET(I2C_SPEED_STANDARD); |
| |
| if (i2c_configure(cfg->i2c.bus, i2c_cfg)) { |
| LOG_ERR("Failed to configure ov2640 i2c interface."); |
| } |
| |
| return ov2640_init(dev); |
| } |
| |
| DEVICE_DT_INST_DEFINE(0, &ov2640_init_0, NULL, |
| &ov2640_data_0, &ov2640_cfg_0, |
| POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, |
| &ov2640_driver_api); |