Files
luban-lite/bsp/peripheral/camera/ov7670/drv_ov7670.c
刘可亮 6e36e8e296 v1.2.0
2025-04-23 17:54:31 +08:00

991 lines
31 KiB
C

/*
* Copyright (c) 2022-2025, ArtInChip Technology Co., Ltd
*
* SPDX-License-Identifier: Apache-2.0
*
* Authors: matteo <duanmt@artinchip.com>
*/
#define LOG_TAG "ov7670"
#include <drivers/i2c.h>
#include <drivers/pin.h>
#include "aic_core.h"
#include "aic_hal_clk.h"
#include "mpp_types.h"
#include "mpp_img_size.h"
#include "mpp_vin.h"
#include "drv_camera.h"
#include "camera_inner.h"
/* Default format configuration of OV7670 */
#define OV7670_DFT_WIN OV7670_WIN_VGA
#define OV7670_DFT_BUS_TYPE MEDIA_BUS_PARALLEL
#define OV7670_DFT_MODEL MODEL_OV7670
#define OV7670_DFT_CODE 0
#define OV7670_DFT_FR 30
/* The clk source is decided by board design */
#define CAMERA_CLK_SRC CLK_OUT1
#define OV7670_I2C_SLAVE_ID 0x21
#define PLL_FACTOR 4
/* Registers */
#define REG_GAIN 0x00 /* Gain lower 8 bits (rest in vref) */
#define REG_BLUE 0x01 /* blue gain */
#define REG_RED 0x02 /* red gain */
#define REG_VREF 0x03 /* Pieces of GAIN, VSTART, VSTOP */
#define REG_COM1 0x04 /* Control 1 */
#define COM1_CCIR656 0x40 /* CCIR656 enable */
#define REG_BAVE 0x05 /* U/B Average level */
#define REG_GbAVE 0x06 /* Y/Gb Average level */
#define REG_AECHH 0x07 /* AEC MS 5 bits */
#define REG_RAVE 0x08 /* V/R Average level */
#define REG_COM2 0x09 /* Control 2 */
#define COM2_SSLEEP 0x10 /* Soft sleep mode */
#define REG_PID 0x0a /* Product ID MSB */
#define REG_VER 0x0b /* Product ID LSB */
#define REG_COM3 0x0c /* Control 3 */
#define COM3_SWAP 0x40 /* Byte swap */
#define COM3_SCALEEN 0x08 /* Enable scaling */
#define COM3_DCWEN 0x04 /* Enable downsamp/crop/window */
#define REG_COM4 0x0d /* Control 4 */
#define REG_COM5 0x0e /* All "reserved" */
#define REG_COM6 0x0f /* Control 6 */
#define REG_AECH 0x10 /* More bits of AEC value */
#define REG_CLKRC 0x11 /* Clocl control */
#define CLK_EXT 0x40 /* Use external clock directly */
#define CLK_SCALE 0x3f /* Mask for internal clock scale */
#define REG_COM7 0x12 /* Control 7 */
#define COM7_RESET 0x80 /* Register reset */
#define COM7_FMT_MASK 0x38
#define COM7_FMT_VGA 0x00
#define COM7_FMT_CIF 0x20 /* CIF format */
#define COM7_FMT_QVGA 0x10 /* QVGA format */
#define COM7_FMT_QCIF 0x08 /* QCIF format */
#define COM7_RGB 0x04 /* bits 0 and 2 - RGB format */
#define COM7_YUV 0x00 /* YUV */
#define COM7_BAYER 0x01 /* Bayer format */
#define COM7_PBAYER 0x05 /* "Processed bayer" */
#define REG_COM8 0x13 /* Control 8 */
#define COM8_FASTAEC 0x80 /* Enable fast AGC/AEC */
#define COM8_AECSTEP 0x40 /* Unlimited AEC step size */
#define COM8_BFILT 0x20 /* Band filter enable */
#define COM8_AGC 0x04 /* Auto gain enable */
#define COM8_AWB 0x02 /* White balance enable */
#define COM8_AEC 0x01 /* Auto exposure enable */
#define REG_COM9 0x14 /* Control 9 - gain ceiling */
#define REG_COM10 0x15 /* Control 10 */
#define COM10_HSYNC 0x40 /* HSYNC instead of HREF */
#define COM10_PCLK_HB 0x20 /* Suppress PCLK on horiz blank */
#define COM10_HREF_REV 0x08 /* Reverse HREF */
#define COM10_VS_LEAD 0x04 /* VSYNC on clock leading edge */
#define COM10_VS_NEG 0x02 /* VSYNC negative */
#define COM10_HS_NEG 0x01 /* HSYNC negative */
#define REG_HSTART 0x17 /* Horiz start high bits */
#define REG_HSTOP 0x18 /* Horiz stop high bits */
#define REG_VSTART 0x19 /* Vert start high bits */
#define REG_VSTOP 0x1a /* Vert stop high bits */
#define REG_PSHFT 0x1b /* Pixel delay after HREF */
#define REG_MIDH 0x1c /* Manuf. ID high */
#define REG_MIDL 0x1d /* Manuf. ID low */
#define REG_MVFP 0x1e /* Mirror / vflip */
#define MVFP_MIRROR 0x20 /* Mirror image */
#define MVFP_FLIP 0x10 /* Vertical flip */
#define REG_AEW 0x24 /* AGC upper limit */
#define REG_AEB 0x25 /* AGC lower limit */
#define REG_VPT 0x26 /* AGC/AEC fast mode op region */
#define REG_HSYST 0x30 /* HSYNC rising edge delay */
#define REG_HSYEN 0x31 /* HSYNC falling edge delay */
#define REG_HREF 0x32 /* HREF pieces */
#define REG_TSLB 0x3a /* lots of stuff */
#define TSLB_YLAST 0x04 /* UYVY or VYUY - see com13 */
#define REG_COM11 0x3b /* Control 11 */
#define COM11_NIGHT 0x80 /* NIght mode enable */
#define COM11_NMFR 0x60 /* Two bit NM frame rate */
#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */
#define COM11_50HZ 0x08 /* Manual 50Hz select */
#define COM11_EXP 0x02
#define REG_COM12 0x3c /* Control 12 */
#define COM12_HREF 0x80 /* HREF always */
#define REG_COM13 0x3d /* Control 13 */
#define COM13_GAMMA 0x80 /* Gamma enable */
#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */
#define COM13_UVSWAP 0x01 /* V before U - w/TSLB */
#define REG_COM14 0x3e /* Control 14 */
#define COM14_DCWEN 0x10 /* DCW/PCLK-scale enable */
#define REG_EDGE 0x3f /* Edge enhancement factor */
#define REG_COM15 0x40 /* Control 15 */
#define COM15_R10F0 0x00 /* Data range 10 to F0 */
#define COM15_R01FE 0x80 /* 01 to FE */
#define COM15_R00FF 0xc0 /* 00 to FF */
#define COM15_RGB565 0x10 /* RGB565 output */
#define COM15_RGB555 0x30 /* RGB555 output */
#define REG_COM16 0x41 /* Control 16 */
#define COM16_AWBGAIN 0x08 /* AWB gain enable */
#define REG_COM17 0x42 /* Control 17 */
#define COM17_AECWIN 0xc0 /* AEC window - must match COM4 */
#define COM17_CBAR 0x08 /* DSP Color bar */
/*
* This matrix defines how the colors are generated, must be
* tweaked to adjust hue and saturation.
*
* Order: v-red, v-green, v-blue, u-red, u-green, u-blue
*
* They are nine-bit signed quantities, with the sign bit
* stored in 0x58. Sign for v-red is bit 0, and up from there.
*/
#define REG_CMATRIX_BASE 0x4f
#define CMATRIX_LEN 6
#define REG_CMATRIX_SIGN 0x58
#define REG_BRIGHT 0x55 /* Brightness */
#define REG_CONTRAS 0x56 /* Contrast control */
#define REG_GFIX 0x69 /* Fix gain control */
#define REG_DBLV 0x6b /* PLL control an debugging */
#define DBLV_BYPASS 0x0a /* Bypass PLL */
#define DBLV_X4 0x4a /* clock x4 */
#define DBLV_X6 0x8a /* clock x6 */
#define DBLV_X8 0xca /* clock x8 */
#define REG_SCALING_XSC 0x70 /* Test pattern and horizontal scale factor */
#define TEST_PATTTERN_0 0x80
#define REG_SCALING_YSC 0x71 /* Test pattern and vertical scale factor */
#define TEST_PATTTERN_1 0x80
#define REG_REG76 0x76 /* OV's name */
#define R76_BLKPCOR 0x80 /* Black pixel correction enable */
#define R76_WHTPCOR 0x40 /* White pixel correction enable */
#define REG_RGB444 0x8c /* RGB 444 control */
#define R444_ENABLE 0x02 /* Turn on RGB444, overrides 5x5 */
#define R444_RGBX 0x01 /* Empty nibble at end */
#define REG_HAECC1 0x9f /* Hist AEC/AGC control 1 */
#define REG_HAECC2 0xa0 /* Hist AEC/AGC control 2 */
#define REG_BD50MAX 0xa5 /* 50hz banding step limit */
#define REG_HAECC3 0xa6 /* Hist AEC/AGC control 3 */
#define REG_HAECC4 0xa7 /* Hist AEC/AGC control 4 */
#define REG_HAECC5 0xa8 /* Hist AEC/AGC control 5 */
#define REG_HAECC6 0xa9 /* Hist AEC/AGC control 6 */
#define REG_HAECC7 0xaa /* Hist AEC/AGC control 7 */
#define REG_BD60MAX 0xab /* 60hz banding step limit */
enum ov7670_win_type {
OV7670_WIN_VGA,
OV7670_WIN_CIF,
OV7670_WIN_QVGA,
OV7670_WIN_QCIF
};
enum ov7670_model {
MODEL_OV7670 = 0,
MODEL_OV7675,
};
struct ov7670_win_size {
int width;
int height;
unsigned char com7_bit;
int hstart; /* Start/stop values for the camera. Note */
int hstop; /* that they do not always make complete */
int vstart; /* sense to humans, but evidently the sensor */
int vstop; /* will do the right thing... */
struct reg8_info *regs; /* Regs to tweak */
};
struct ov7670_format_struct; /* coming later */
struct ov7670_dev {
struct rt_device dev;
struct rt_i2c_bus_device *i2c;
u32 rst_pin;
u32 pwdn_pin;
struct ov7670_format_struct *ov_fmt; /* Current format */
struct mpp_video_fmt fmt;
struct ov7670_win_size *wsize;
struct clk *clk;
int on;
unsigned int mbus_config; /* Media bus configuration flags */
int min_width; /* Filter out smaller sizes */
int min_height; /* Filter out smaller sizes */
int clock_speed; /* External clock speed (MHz) */
u8 clkrc; /* Clock divider value */
bool use_smbus; /* Use smbus I/O instead of I2C */
bool pll_bypass;
bool pclk_hb_disable;
const struct ov7670_devtype *devtype; /* Device specifics */
};
struct ov7670_devtype {
/* formats supported for each model */
struct ov7670_win_size *win_sizes;
unsigned int n_win_sizes;
int (*set_framerate)(struct ov7670_dev *sensor, u32 fr);
};
static struct ov7670_dev g_ov7670_dev = {0};
static struct reg8_info ov7670_default_regs[] = {
{ REG_COM7, COM7_RESET },
/*
* Clock scale: 3 = 15fps
* 2 = 20fps
* 1 = 30fps
*/
{ REG_CLKRC, 0x1 }, /* OV: clock scale (30 fps) */
{ REG_TSLB, 0x04 }, /* OV */
{ REG_COM7, 0 }, /* VGA */
/*
* Set the hardware window. These values from OV don't entirely
* make sense - hstop is less than hstart. But they work...
*/
{ REG_HSTART, 0x13 }, { REG_HSTOP, 0x01 },
{ REG_HREF, 0xb6 }, { REG_VSTART, 0x02 },
{ REG_VSTOP, 0x7a }, { REG_VREF, 0x0a },
{ REG_COM3, 0 }, { REG_COM14, 0 },
/* Mystery scaling numbers */
{ REG_SCALING_XSC, 0x3a },
{ REG_SCALING_YSC, 0x35 },
{ 0x72, 0x11 }, { 0x73, 0xf0 },
{ 0xa2, 0x02 }, { REG_COM10, 0x0 },
/* Gamma curve values */
{ 0x7a, 0x20 }, { 0x7b, 0x10 },
{ 0x7c, 0x1e }, { 0x7d, 0x35 },
{ 0x7e, 0x5a }, { 0x7f, 0x69 },
{ 0x80, 0x76 }, { 0x81, 0x80 },
{ 0x82, 0x88 }, { 0x83, 0x8f },
{ 0x84, 0x96 }, { 0x85, 0xa3 },
{ 0x86, 0xaf }, { 0x87, 0xc4 },
{ 0x88, 0xd7 }, { 0x89, 0xe8 },
/* AGC and AEC parameters. Note we start by disabling those features,
then turn them only after tweaking the values. */
{ REG_COM8, COM8_FASTAEC | COM8_AECSTEP | COM8_BFILT },
{ REG_GAIN, 0 }, { REG_AECH, 0 },
{ REG_COM4, 0x40 }, /* magic reserved bit */
{ REG_COM9, 0x18 }, /* 4x gain + magic rsvd bit */
{ REG_BD50MAX, 0x05 }, { REG_BD60MAX, 0x07 },
{ REG_AEW, 0x95 }, { REG_AEB, 0x33 },
{ REG_VPT, 0xe3 }, { REG_HAECC1, 0x78 },
{ REG_HAECC2, 0x68 }, { 0xa1, 0x03 }, /* magic */
{ REG_HAECC3, 0xd8 }, { REG_HAECC4, 0xd8 },
{ REG_HAECC5, 0xf0 }, { REG_HAECC6, 0x90 },
{ REG_HAECC7, 0x94 },
{ REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC },
/* Almost all of these are magic "reserved" values. */
{ REG_COM5, 0x61 }, { REG_COM6, 0x4b },
{ 0x16, 0x02 }, { REG_MVFP, 0x07 },
{ 0x21, 0x02 }, { 0x22, 0x91 },
{ 0x29, 0x07 }, { 0x33, 0x0b },
{ 0x35, 0x0b }, { 0x37, 0x1d },
{ 0x38, 0x71 }, { 0x39, 0x2a },
{ REG_COM12, 0x78 }, { 0x4d, 0x40 },
{ 0x4e, 0x20 }, { REG_GFIX, 0 },
{ 0x6b, 0x4a }, { 0x74, 0x10 },
{ 0x8d, 0x4f }, { 0x8e, 0 },
{ 0x8f, 0 }, { 0x90, 0 },
{ 0x91, 0 }, { 0x96, 0 },
{ 0x9a, 0 }, { 0xb0, 0x84 },
{ 0xb1, 0x0c }, { 0xb2, 0x0e },
{ 0xb3, 0x82 }, { 0xb8, 0x0a },
/* More reserved magic, some of which tweaks white balance */
{ 0x43, 0x0a }, { 0x44, 0xf0 },
{ 0x45, 0x34 }, { 0x46, 0x58 },
{ 0x47, 0x28 }, { 0x48, 0x3a },
{ 0x59, 0x88 }, { 0x5a, 0x88 },
{ 0x5b, 0x44 }, { 0x5c, 0x67 },
{ 0x5d, 0x49 }, { 0x5e, 0x0e },
{ 0x6c, 0x0a }, { 0x6d, 0x55 },
{ 0x6e, 0x11 }, { 0x6f, 0x9f }, /* "9e for advance AWB" */
{ 0x6a, 0x40 }, { REG_BLUE, 0x40 },
{ REG_RED, 0x60 },
{ REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC|COM8_AWB },
/* Matrix coefficients */
{ 0x4f, 0x80 }, { 0x50, 0x80 },
{ 0x51, 0 }, { 0x52, 0x22 },
{ 0x53, 0x5e }, { 0x54, 0x80 },
{ 0x58, 0x9e },
{ REG_COM16, COM16_AWBGAIN }, { REG_EDGE, 0 },
{ 0x75, 0x05 }, { 0x76, 0xe1 },
{ 0x4c, 0 }, { 0x77, 0x01 },
{ REG_COM13, 0xc3 }, { 0x4b, 0x09 },
{ 0xc9, 0x60 }, { REG_COM16, 0x38 },
{ 0x56, 0x40 },
{ 0x34, 0x11 }, { REG_COM11, COM11_EXP|COM11_HZAUTO },
{ 0xa4, 0x88 }, { 0x96, 0 },
{ 0x97, 0x30 }, { 0x98, 0x20 },
{ 0x99, 0x30 }, { 0x9a, 0x84 },
{ 0x9b, 0x29 }, { 0x9c, 0x03 },
{ 0x9d, 0x4c }, { 0x9e, 0x3f },
{ 0x78, 0x04 },
/* Extra-weird stuff. Some sort of multiplexor register */
{ 0x79, 0x01 }, { 0xc8, 0xf0 },
{ 0x79, 0x0f }, { 0xc8, 0x00 },
{ 0x79, 0x10 }, { 0xc8, 0x7e },
{ 0x79, 0x0a }, { 0xc8, 0x80 },
{ 0x79, 0x0b }, { 0xc8, 0x01 },
{ 0x79, 0x0c }, { 0xc8, 0x0f },
{ 0x79, 0x0d }, { 0xc8, 0x20 },
{ 0x79, 0x09 }, { 0xc8, 0x80 },
{ 0x79, 0x02 }, { 0xc8, 0xc0 },
{ 0x79, 0x03 }, { 0xc8, 0x40 },
{ 0x79, 0x05 }, { 0xc8, 0x30 },
{ 0x79, 0x26 },
{ 0xff, 0xff }, /* END MARKER */
};
/*
* Here we'll try to encapsulate the changes for just the output
* video format.
*
* RGB656 and YUV422 come from OV; RGB444 is homebrewed.
*
* IMPORTANT RULE: the first entry must be for COM7, see ov7670_s_fmt for why.
*/
static struct reg8_info ov7670_fmt_yuv422[] = {
{ REG_COM7, 0x0 }, /* Selects YUV mode */
{ REG_RGB444, 0 }, /* No RGB444 please */
{ REG_COM1, 0 }, /* CCIR601 */
{ REG_COM15, COM15_R00FF },
{ REG_COM9, 0x48 }, /* 32x gain ceiling; 0x8 is reserved bit */
{ 0x4f, 0x80 }, /* "matrix coefficient 1" */
{ 0x50, 0x80 }, /* "matrix coefficient 2" */
{ 0x51, 0 }, /* vb */
{ 0x52, 0x22 }, /* "matrix coefficient 4" */
{ 0x53, 0x5e }, /* "matrix coefficient 5" */
{ 0x54, 0x80 }, /* "matrix coefficient 6" */
{ REG_COM13, COM13_GAMMA|COM13_UVSAT },
{ 0xff, 0xff },
};
static struct reg8_info ov7670_fmt_rgb565[] = {
{ REG_COM7, COM7_RGB }, /* Selects RGB mode */
{ REG_RGB444, 0 }, /* No RGB444 please */
{ REG_COM1, 0x0 }, /* CCIR601 */
{ REG_COM15, COM15_RGB565 },
{ REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */
{ 0x4f, 0xb3 }, /* "matrix coefficient 1" */
{ 0x50, 0xb3 }, /* "matrix coefficient 2" */
{ 0x51, 0 }, /* vb */
{ 0x52, 0x3d }, /* "matrix coefficient 4" */
{ 0x53, 0xa7 }, /* "matrix coefficient 5" */
{ 0x54, 0xe4 }, /* "matrix coefficient 6" */
{ REG_COM13, COM13_GAMMA|COM13_UVSAT },
{ 0xff, 0xff },
};
static struct reg8_info ov7670_fmt_rgb444[] = {
{ REG_COM7, COM7_RGB }, /* Selects RGB mode */
{ REG_RGB444, R444_ENABLE }, /* Enable xxxxrrrr ggggbbbb */
{ REG_COM1, 0x0 }, /* CCIR601 */
{ REG_COM15, COM15_R01FE|COM15_RGB565 }, /* Data range needed? */
{ REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */
{ 0x4f, 0xb3 }, /* "matrix coefficient 1" */
{ 0x50, 0xb3 }, /* "matrix coefficient 2" */
{ 0x51, 0 }, /* vb */
{ 0x52, 0x3d }, /* "matrix coefficient 4" */
{ 0x53, 0xa7 }, /* "matrix coefficient 5" */
{ 0x54, 0xe4 }, /* "matrix coefficient 6" */
{ REG_COM13, COM13_GAMMA|COM13_UVSAT|0x2 }, /* Magic rsvd bit */
{ 0xff, 0xff },
};
static struct reg8_info ov7670_fmt_raw[] = {
{ REG_COM7, COM7_BAYER },
{ REG_COM13, 0x08 }, /* No gamma, magic rsvd bit */
{ REG_COM16, 0x3d }, /* Edge enhancement, denoise */
{ REG_REG76, 0xe1 }, /* Pix correction, magic rsvd */
{ 0xff, 0xff },
};
/*
* Store information about the video data format. The color matrix
* is deeply tied into the format, so keep the relevant values here.
* The magic matrix numbers come from OmniVision.
*/
static struct ov7670_format_struct {
u32 mbus_code;
struct reg8_info *regs;
int cmatrix[CMATRIX_LEN];
} ov7670_formats[] = {
{
.mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
.regs = ov7670_fmt_yuv422,
.cmatrix = { 128, -128, 0, -34, -94, 128 },
},
{
.mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE,
.regs = ov7670_fmt_rgb444,
.cmatrix = { 179, -179, 0, -61, -176, 228 },
},
{
.mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE,
.regs = ov7670_fmt_rgb565,
.cmatrix = { 179, -179, 0, -61, -176, 228 },
},
{
.mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
.regs = ov7670_fmt_raw,
.cmatrix = { 0, 0, 0, 0, 0, 0 },
},
};
#define N_OV7670_FMTS ARRAY_SIZE(ov7670_formats)
/*
* Then there is the issue of window sizes. Try to capture the info here.
*/
/*
* QCIF mode is done (by OV) in a very strange way - it actually looks like
* VGA with weird scaling options - they do *not* use the canned QCIF mode
* which is allegedly provided by the sensor. So here's the weird register
* settings.
*/
static struct reg8_info ov7670_qcif_regs[] = {
{ REG_COM3, COM3_SCALEEN|COM3_DCWEN },
{ REG_COM3, COM3_DCWEN },
{ REG_COM14, COM14_DCWEN | 0x01 },
{ 0x73, 0xf1 },
{ 0xa2, 0x52 },
{ 0x7b, 0x1c },
{ 0x7c, 0x28 },
{ 0x7d, 0x3c },
{ 0x7f, 0x69 },
{ REG_COM9, 0x38 },
{ 0xa1, 0x0b },
{ 0x74, 0x19 },
{ 0x9a, 0x80 },
{ 0x43, 0x14 },
{ REG_COM13, 0xc0 },
{ 0xff, 0xff },
};
static struct ov7670_win_size ov7670_win_sizes[] = {
/* VGA */
{
.width = VGA_WIDTH,
.height = VGA_HEIGHT,
.com7_bit = COM7_FMT_VGA,
.hstart = 158, /* These values from */
.hstop = 14, /* Omnivision */
.vstart = 10,
.vstop = 490,
.regs = NULL,
},
/* CIF */
{
.width = CIF_WIDTH,
.height = CIF_HEIGHT,
.com7_bit = COM7_FMT_CIF,
.hstart = 170, /* Empirically determined */
.hstop = 90,
.vstart = 14,
.vstop = 494,
.regs = NULL,
},
/* QVGA */
{
.width = QVGA_WIDTH,
.height = QVGA_HEIGHT,
.com7_bit = COM7_FMT_QVGA,
.hstart = 168, /* Empirically determined */
.hstop = 24,
.vstart = 12,
.vstop = 492,
.regs = NULL,
},
/* QCIF */
{
.width = QCIF_WIDTH,
.height = QCIF_HEIGHT,
.com7_bit = COM7_FMT_VGA, /* see comment above */
.hstart = 456, /* Empirically determined */
.hstop = 24,
.vstart = 14,
.vstop = 494,
.regs = ov7670_qcif_regs,
}
};
static struct ov7670_win_size ov7675_win_sizes[] = {
/*
* Currently, only VGA is supported. Theoretically it could be possible
* to support CIF, QVGA and QCIF too. Taking values for ov7670 as a
* base and tweak them empirically could be required.
*/
{
.width = VGA_WIDTH,
.height = VGA_HEIGHT,
.com7_bit = COM7_FMT_VGA,
.hstart = 158, /* These values from */
.hstop = 14, /* Omnivision */
.vstart = 14, /* Empirically determined */
.vstop = 494,
.regs = NULL,
}
};
static int ov7670_read_reg(struct rt_i2c_bus_device *i2c, unsigned char reg,
unsigned char *val)
{
if (rt_i2c_read_reg(i2c, OV7670_I2C_SLAVE_ID, reg, val, 1) != 1) {
LOG_E("%s: error: reg = 0x%x, val = 0x%x", __func__, reg, *val);
return -1;
}
return 0;
}
static int ov7670_write_reg(struct rt_i2c_bus_device *i2c, unsigned char reg,
unsigned char val)
{
if (rt_i2c_write_reg(i2c, OV7670_I2C_SLAVE_ID, reg, &val, 1) != 1) {
LOG_E("%s: error: reg = 0x%x, val = 0x%x", __func__, reg, val);
return -1;
}
if (reg == REG_COM7 && (val & COM7_RESET))
aicos_msleep(5); /* Wait for reset to run */
return 0;
}
/* Write a list of register settings; ff/ff stops the process. */
static int ov7670_write_array(struct ov7670_dev *sensor, struct reg8_info *vals)
{
while (vals->reg != 0xff || vals->val != 0xff) {
if (ov7670_write_reg(sensor->i2c, vals->reg, vals->val))
return -1;
vals++;
}
return 0;
}
static int ov7670_base_init(struct ov7670_dev *sensor)
{
return ov7670_write_array(sensor, ov7670_default_regs);
}
static int ov7670_detect(struct ov7670_dev *sensor)
{
unsigned char v = 0;
int ret = 0;
ret = ov7670_base_init(sensor);
if (ret < 0)
return ret;
ret = ov7670_read_reg(sensor->i2c, REG_MIDH, &v);
if (ret < 0)
return ret;
if (v != 0x7f) /* OV manuf. id. */
return -ENODEV;
ret = ov7670_read_reg(sensor->i2c, REG_MIDL, &v);
if (ret < 0)
return ret;
if (v != 0xa2)
return -ENODEV;
/*
* OK, we know we have an OmniVision chip...but which one?
*/
ret = ov7670_read_reg(sensor->i2c, REG_PID, &v);
if (ret < 0)
return ret;
if (v != 0x76) /* PID + VER = 0x76 / 0x73 */
return -ENODEV;
ret = ov7670_read_reg(sensor->i2c, REG_VER, &v);
if (ret < 0)
return ret;
if (v != 0x73) /* PID + VER = 0x76 / 0x73 */
return -ENODEV;
return 0;
}
static int ov7675_apply_framerate(struct ov7670_dev *sensor)
{
int ret = 0;
ret = ov7670_write_reg(sensor->i2c, REG_CLKRC, sensor->clkrc);
if (ret < 0)
return ret;
return ov7670_write_reg(sensor->i2c, REG_DBLV,
sensor->pll_bypass ? DBLV_BYPASS : DBLV_X4);
}
/* Store a set of start/stop values into the camera. */
static int ov7670_set_hw(struct ov7670_dev *sensor, int hstart, int hstop,
int vstart, int vstop)
{
int ret = 0;
unsigned char v = 0;
/*
* Horizontal: 11 bits, top 8 live in hstart and hstop. Bottom 3 of
* hstart are in href[2:0], bottom 3 of hstop in href[5:3]. There is
* a mystery "edge offset" value in the top two bits of href.
*/
ret = ov7670_write_reg(sensor->i2c, REG_HSTART, (hstart >> 3) & 0xff);
ret += ov7670_write_reg(sensor->i2c, REG_HSTOP, (hstop >> 3) & 0xff);
ret += ov7670_read_reg(sensor->i2c, REG_HREF, &v);
v = (v & 0xc0) | ((hstop & 0x7) << 3) | (hstart & 0x7);
aicos_msleep(10);
ret += ov7670_write_reg(sensor->i2c, REG_HREF, v);
/*
* Vertical: similar arrangement, but only 10 bits.
*/
ret += ov7670_write_reg(sensor->i2c, REG_VSTART, (vstart >> 2) & 0xff);
ret += ov7670_write_reg(sensor->i2c, REG_VSTOP, (vstop >> 2) & 0xff);
ret += ov7670_read_reg(sensor->i2c, REG_VREF, &v);
v = (v & 0xf0) | ((vstop & 0x3) << 2) | (vstart & 0x3);
aicos_msleep(10);
ret += ov7670_write_reg(sensor->i2c, REG_VREF, v);
return ret;
}
static int ov7670_apply_fmt(struct ov7670_dev *sensor)
{
struct ov7670_win_size *wsize = sensor->wsize;
unsigned char com7 = 0, com10 = 0;
int ret = 0;
/*
* COM7 is a pain in the ass, it doesn't like to be read then
* quickly written afterward. But we have everything we need
* to set it absolutely here, as long as the format-specific
* register sets list it first.
*/
com7 = sensor->ov_fmt->regs[0].val;
com7 |= wsize->com7_bit;
ret = ov7670_write_reg(sensor->i2c, REG_COM7, com7);
if (ret)
return ret;
/*
* Configure the media bus through COM10 register
*/
if (sensor->mbus_config & MEDIA_SIGNAL_VSYNC_ACTIVE_LOW)
com10 |= COM10_VS_NEG;
if (sensor->mbus_config & MEDIA_SIGNAL_HSYNC_ACTIVE_LOW)
com10 |= COM10_HREF_REV;
if (sensor->pclk_hb_disable)
com10 |= COM10_PCLK_HB;
ret = ov7670_write_reg(sensor->i2c, REG_COM10, com10);
if (ret)
return ret;
/*
* Now write the rest of the array. Also store start/stops
*/
ret = ov7670_write_array(sensor, sensor->ov_fmt->regs + 1);
if (ret)
return ret;
ret = ov7670_set_hw(sensor, wsize->hstart, wsize->hstop, wsize->vstart,
wsize->vstop);
if (ret)
return ret;
if (wsize->regs) {
ret = ov7670_write_array(sensor, wsize->regs);
if (ret)
return ret;
}
/*
* If we're running RGB565, we must rewrite clkrc after setting
* the other parameters or the image looks poor. If we're *not*
* doing RGB565, we must not rewrite clkrc or the image looks
* *really* poor.
*
* (Update) Now that we retain clkrc state, we should be able
* to write it unconditionally, and that will make the frame
* rate persistent too.
*/
ret = ov7670_write_reg(sensor->i2c, REG_CLKRC, sensor->clkrc);
if (ret)
return ret;
return 0;
}
static int ov7670_set_framerate_legacy(struct ov7670_dev *sensor, u32 fr)
{
int div = 1;
sensor->clkrc = (sensor->clkrc & 0x80) | div;
if (sensor->on)
return ov7670_write_reg(sensor->i2c, REG_CLKRC, sensor->clkrc);
return 0;
}
static int ov7675_set_framerate(struct ov7670_dev *sensor, u32 fr)
{
u32 clkrc = 0;
int pll_factor = 0;
/*
* The formula is fps = 5/4*pixclk for YUV/RGB and
* fps = 5/2*pixclk for RAW.
*
* pixclk = clock_speed / (clkrc + 1) * PLLfactor
*
*/
pll_factor = sensor->pll_bypass ? 1 : PLL_FACTOR;
clkrc = (5 * pll_factor * sensor->clock_speed * fr) / 4;
if (sensor->ov_fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8)
clkrc = (clkrc << 1);
clkrc--;
/*
* The datasheet claims that clkrc = 0 will divide the input clock by 1
* but we've checked with an oscilloscope that it divides by 2 instead.
* So, if clkrc = 0 just bypass the divider.
*/
if (clkrc <= 0)
clkrc = CLK_EXT;
else if (clkrc > CLK_SCALE)
clkrc = CLK_SCALE;
sensor->clkrc = clkrc;
/*
* If the device is not powered up by the host driver do
* not apply any changes to H/W at this time. Instead
* the framerate will be restored right after power-up.
*/
if (sensor->on)
return ov7675_apply_framerate(sensor);
return 0;
}
static bool ov7670_is_open(struct ov7670_dev *sensor)
{
return sensor->on;
}
static void ov7670_power_on(struct ov7670_dev *sensor)
{
if (sensor->on)
return;
if (sensor->pwdn_pin)
camera_pin_set_low(sensor->pwdn_pin);
if (sensor->rst_pin) {
camera_pin_set_low(sensor->rst_pin);
aicos_msleep(1);
camera_pin_set_high(sensor->rst_pin);
}
if (sensor->pwdn_pin || sensor->rst_pin || sensor->clk)
aicos_msleep(3);
LOG_I("Power on");
sensor->on = true;
}
static void ov7670_power_off(struct ov7670_dev *sensor)
{
if (!sensor->on)
return;
if (sensor->pwdn_pin)
camera_pin_set_high(sensor->pwdn_pin);
LOG_I("Power off");
sensor->on = false;
}
static const struct ov7670_devtype ov7670_devdata[] = {
[MODEL_OV7670] = {
.win_sizes = ov7670_win_sizes,
.n_win_sizes = ARRAY_SIZE(ov7670_win_sizes),
.set_framerate = ov7670_set_framerate_legacy,
},
[MODEL_OV7675] = {
.win_sizes = ov7675_win_sizes,
.n_win_sizes = ARRAY_SIZE(ov7675_win_sizes),
.set_framerate = ov7675_set_framerate,
},
};
static void ov7670_get_default_format(struct ov7670_dev *sensor)
{
sensor->devtype = &ov7670_devdata[OV7670_DFT_MODEL];
sensor->ov_fmt = &ov7670_formats[OV7670_DFT_CODE];
sensor->wsize = &sensor->devtype->win_sizes[OV7670_DFT_WIN];
sensor->clkrc = 0;
sensor->fmt.code = sensor->ov_fmt->mbus_code;
sensor->fmt.width = sensor->wsize->width;
sensor->fmt.height = sensor->wsize->height;
sensor->fmt.bus_type = OV7670_DFT_BUS_TYPE;
sensor->fmt.flags = MEDIA_SIGNAL_HSYNC_ACTIVE_HIGH |
MEDIA_SIGNAL_VSYNC_ACTIVE_LOW |
MEDIA_SIGNAL_PCLK_SAMPLE_FALLING;
}
static rt_err_t ov7670_init(rt_device_t dev)
{
struct ov7670_dev *sensor = &g_ov7670_dev;
sensor->i2c = camera_i2c_get();
if (!sensor->i2c)
return -RT_EINVAL;
sensor->clock_speed = camera_xclk_rate_get() / 1000000;
if (sensor->clock_speed < 10 || sensor->clock_speed > 48) {
LOG_E("XCLK rate %d is out of range", sensor->clock_speed);
return -EINVAL;
}
ov7670_get_default_format(sensor);
sensor->rst_pin = camera_rst_pin_get();
sensor->pwdn_pin = camera_pwdn_pin_get();
if (!sensor->rst_pin || !sensor->pwdn_pin)
return -RT_EINVAL;
return RT_EOK;
}
static rt_err_t ov7670_open(rt_device_t dev, rt_uint16_t oflag)
{
struct ov7670_dev *sensor = (struct ov7670_dev *)dev;
if (ov7670_is_open(sensor))
return RT_EOK;
ov7670_power_on(sensor);
if (ov7670_detect(sensor)) {
ov7670_power_off(sensor);
LOG_E("Chip found @ 0x%x (i2c%d) is not an ov7670 chip.\n",
OV7670_I2C_SLAVE_ID, AIC_CAMERA_I2C_CHAN);
return -RT_ERROR;
}
sensor->devtype->set_framerate(sensor, OV7670_DFT_FR);
ov7670_base_init(sensor);
ov7670_apply_fmt(sensor);
ov7675_apply_framerate(sensor);
LOG_I("OV7670 inited");
return RT_EOK;
}
static rt_err_t ov7670_close(rt_device_t dev)
{
struct ov7670_dev *sensor = (struct ov7670_dev *)dev;
if (!ov7670_is_open(sensor))
return -RT_ERROR;
ov7670_power_off(sensor);
LOG_D("OV7670 Close");
return RT_EOK;
}
static int ov7670_get_fmt(rt_device_t dev, struct mpp_video_fmt *cfg)
{
struct ov7670_dev *sensor = (struct ov7670_dev *)dev;
cfg->code = sensor->fmt.code;
cfg->width = sensor->fmt.width;
cfg->height = sensor->fmt.height;
cfg->flags = sensor->fmt.flags;
cfg->bus_type = sensor->fmt.bus_type;
return RT_EOK;
}
static int ov7670_start(rt_device_t dev)
{
return 0;
}
static int ov7670_stop(rt_device_t dev)
{
return 0;
}
static int ov7670_pause(rt_device_t dev)
{
struct ov7670_dev *sensor = (struct ov7670_dev *)dev;
return ov7670_write_reg(sensor->i2c, REG_COM2, COM2_SSLEEP);
}
static int ov7670_resume(rt_device_t dev)
{
struct ov7670_dev *sensor = (struct ov7670_dev *)dev;
return ov7670_write_reg(sensor->i2c, REG_COM2, 0x1);
}
static rt_err_t ov7670_control(rt_device_t dev, int cmd, void *args)
{
switch (cmd) {
case CAMERA_CMD_START:
return ov7670_start(dev);
case CAMERA_CMD_STOP:
return ov7670_stop(dev);
case CAMERA_CMD_PAUSE:
return ov7670_pause(dev);
case CAMERA_CMD_RESUME:
return ov7670_resume(dev);
case CAMERA_CMD_GET_FMT:
return ov7670_get_fmt(dev, (struct mpp_video_fmt *)args);
default:
LOG_I("Unsupported cmd: 0x%x", cmd);
return -RT_EINVAL;
}
return RT_EOK;
}
#ifdef RT_USING_DEVICE_OPS
static const struct rt_device_ops ov7670_ops =
{
.init = ov7670_init,
.open = ov7670_open,
.close = ov7670_close,
.control = ov7670_control,
};
#endif
int rt_hw_ov7670_init(void)
{
#ifdef RT_USING_DEVICE_OPS
g_ov7670_dev.dev.ops = &ov7670_ops;
#else
g_ov7670_dev.dev.init = ov7670_init;
g_ov7670_dev.dev.open = ov7670_open;
g_ov7670_dev.dev.close = ov7670_close;
g_ov7670_dev.dev.control = ov7670_control;
#endif
g_ov7670_dev.dev.type = RT_Device_Class_CAMERA;
rt_device_register(&g_ov7670_dev.dev, CAMERA_DEV_NAME, 0);
return 0;
}
INIT_DEVICE_EXPORT(rt_hw_ov7670_init);