18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * A V4L2 driver for OmniVision OV7670 cameras. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2006 One Laptop Per Child Association, Inc. Written 68c2ecf20Sopenharmony_ci * by Jonathan Corbet with substantial inspiration from Mark 78c2ecf20Sopenharmony_ci * McClelland's ovcamchip code. 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci#include <linux/clk.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/i2c.h> 168c2ecf20Sopenharmony_ci#include <linux/delay.h> 178c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 188c2ecf20Sopenharmony_ci#include <linux/gpio.h> 198c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 208c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 218c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 228c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h> 238c2ecf20Sopenharmony_ci#include <media/v4l2-fwnode.h> 248c2ecf20Sopenharmony_ci#include <media/v4l2-mediabus.h> 258c2ecf20Sopenharmony_ci#include <media/v4l2-image-sizes.h> 268c2ecf20Sopenharmony_ci#include <media/i2c/ov7670.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jonathan Corbet <corbet@lwn.net>"); 298c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("A low-level driver for OmniVision ov7670 sensors"); 308c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic bool debug; 338c2ecf20Sopenharmony_cimodule_param(debug, bool, 0644); 348c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Debug level (0-1)"); 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci/* 378c2ecf20Sopenharmony_ci * The 7670 sits on i2c with ID 0x42 388c2ecf20Sopenharmony_ci */ 398c2ecf20Sopenharmony_ci#define OV7670_I2C_ADDR 0x42 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#define PLL_FACTOR 4 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci/* Registers */ 448c2ecf20Sopenharmony_ci#define REG_GAIN 0x00 /* Gain lower 8 bits (rest in vref) */ 458c2ecf20Sopenharmony_ci#define REG_BLUE 0x01 /* blue gain */ 468c2ecf20Sopenharmony_ci#define REG_RED 0x02 /* red gain */ 478c2ecf20Sopenharmony_ci#define REG_VREF 0x03 /* Pieces of GAIN, VSTART, VSTOP */ 488c2ecf20Sopenharmony_ci#define REG_COM1 0x04 /* Control 1 */ 498c2ecf20Sopenharmony_ci#define COM1_CCIR656 0x40 /* CCIR656 enable */ 508c2ecf20Sopenharmony_ci#define REG_BAVE 0x05 /* U/B Average level */ 518c2ecf20Sopenharmony_ci#define REG_GbAVE 0x06 /* Y/Gb Average level */ 528c2ecf20Sopenharmony_ci#define REG_AECHH 0x07 /* AEC MS 5 bits */ 538c2ecf20Sopenharmony_ci#define REG_RAVE 0x08 /* V/R Average level */ 548c2ecf20Sopenharmony_ci#define REG_COM2 0x09 /* Control 2 */ 558c2ecf20Sopenharmony_ci#define COM2_SSLEEP 0x10 /* Soft sleep mode */ 568c2ecf20Sopenharmony_ci#define REG_PID 0x0a /* Product ID MSB */ 578c2ecf20Sopenharmony_ci#define REG_VER 0x0b /* Product ID LSB */ 588c2ecf20Sopenharmony_ci#define REG_COM3 0x0c /* Control 3 */ 598c2ecf20Sopenharmony_ci#define COM3_SWAP 0x40 /* Byte swap */ 608c2ecf20Sopenharmony_ci#define COM3_SCALEEN 0x08 /* Enable scaling */ 618c2ecf20Sopenharmony_ci#define COM3_DCWEN 0x04 /* Enable downsamp/crop/window */ 628c2ecf20Sopenharmony_ci#define REG_COM4 0x0d /* Control 4 */ 638c2ecf20Sopenharmony_ci#define REG_COM5 0x0e /* All "reserved" */ 648c2ecf20Sopenharmony_ci#define REG_COM6 0x0f /* Control 6 */ 658c2ecf20Sopenharmony_ci#define REG_AECH 0x10 /* More bits of AEC value */ 668c2ecf20Sopenharmony_ci#define REG_CLKRC 0x11 /* Clocl control */ 678c2ecf20Sopenharmony_ci#define CLK_EXT 0x40 /* Use external clock directly */ 688c2ecf20Sopenharmony_ci#define CLK_SCALE 0x3f /* Mask for internal clock scale */ 698c2ecf20Sopenharmony_ci#define REG_COM7 0x12 /* Control 7 */ 708c2ecf20Sopenharmony_ci#define COM7_RESET 0x80 /* Register reset */ 718c2ecf20Sopenharmony_ci#define COM7_FMT_MASK 0x38 728c2ecf20Sopenharmony_ci#define COM7_FMT_VGA 0x00 738c2ecf20Sopenharmony_ci#define COM7_FMT_CIF 0x20 /* CIF format */ 748c2ecf20Sopenharmony_ci#define COM7_FMT_QVGA 0x10 /* QVGA format */ 758c2ecf20Sopenharmony_ci#define COM7_FMT_QCIF 0x08 /* QCIF format */ 768c2ecf20Sopenharmony_ci#define COM7_RGB 0x04 /* bits 0 and 2 - RGB format */ 778c2ecf20Sopenharmony_ci#define COM7_YUV 0x00 /* YUV */ 788c2ecf20Sopenharmony_ci#define COM7_BAYER 0x01 /* Bayer format */ 798c2ecf20Sopenharmony_ci#define COM7_PBAYER 0x05 /* "Processed bayer" */ 808c2ecf20Sopenharmony_ci#define REG_COM8 0x13 /* Control 8 */ 818c2ecf20Sopenharmony_ci#define COM8_FASTAEC 0x80 /* Enable fast AGC/AEC */ 828c2ecf20Sopenharmony_ci#define COM8_AECSTEP 0x40 /* Unlimited AEC step size */ 838c2ecf20Sopenharmony_ci#define COM8_BFILT 0x20 /* Band filter enable */ 848c2ecf20Sopenharmony_ci#define COM8_AGC 0x04 /* Auto gain enable */ 858c2ecf20Sopenharmony_ci#define COM8_AWB 0x02 /* White balance enable */ 868c2ecf20Sopenharmony_ci#define COM8_AEC 0x01 /* Auto exposure enable */ 878c2ecf20Sopenharmony_ci#define REG_COM9 0x14 /* Control 9 - gain ceiling */ 888c2ecf20Sopenharmony_ci#define REG_COM10 0x15 /* Control 10 */ 898c2ecf20Sopenharmony_ci#define COM10_HSYNC 0x40 /* HSYNC instead of HREF */ 908c2ecf20Sopenharmony_ci#define COM10_PCLK_HB 0x20 /* Suppress PCLK on horiz blank */ 918c2ecf20Sopenharmony_ci#define COM10_HREF_REV 0x08 /* Reverse HREF */ 928c2ecf20Sopenharmony_ci#define COM10_VS_LEAD 0x04 /* VSYNC on clock leading edge */ 938c2ecf20Sopenharmony_ci#define COM10_VS_NEG 0x02 /* VSYNC negative */ 948c2ecf20Sopenharmony_ci#define COM10_HS_NEG 0x01 /* HSYNC negative */ 958c2ecf20Sopenharmony_ci#define REG_HSTART 0x17 /* Horiz start high bits */ 968c2ecf20Sopenharmony_ci#define REG_HSTOP 0x18 /* Horiz stop high bits */ 978c2ecf20Sopenharmony_ci#define REG_VSTART 0x19 /* Vert start high bits */ 988c2ecf20Sopenharmony_ci#define REG_VSTOP 0x1a /* Vert stop high bits */ 998c2ecf20Sopenharmony_ci#define REG_PSHFT 0x1b /* Pixel delay after HREF */ 1008c2ecf20Sopenharmony_ci#define REG_MIDH 0x1c /* Manuf. ID high */ 1018c2ecf20Sopenharmony_ci#define REG_MIDL 0x1d /* Manuf. ID low */ 1028c2ecf20Sopenharmony_ci#define REG_MVFP 0x1e /* Mirror / vflip */ 1038c2ecf20Sopenharmony_ci#define MVFP_MIRROR 0x20 /* Mirror image */ 1048c2ecf20Sopenharmony_ci#define MVFP_FLIP 0x10 /* Vertical flip */ 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci#define REG_AEW 0x24 /* AGC upper limit */ 1078c2ecf20Sopenharmony_ci#define REG_AEB 0x25 /* AGC lower limit */ 1088c2ecf20Sopenharmony_ci#define REG_VPT 0x26 /* AGC/AEC fast mode op region */ 1098c2ecf20Sopenharmony_ci#define REG_HSYST 0x30 /* HSYNC rising edge delay */ 1108c2ecf20Sopenharmony_ci#define REG_HSYEN 0x31 /* HSYNC falling edge delay */ 1118c2ecf20Sopenharmony_ci#define REG_HREF 0x32 /* HREF pieces */ 1128c2ecf20Sopenharmony_ci#define REG_TSLB 0x3a /* lots of stuff */ 1138c2ecf20Sopenharmony_ci#define TSLB_YLAST 0x04 /* UYVY or VYUY - see com13 */ 1148c2ecf20Sopenharmony_ci#define REG_COM11 0x3b /* Control 11 */ 1158c2ecf20Sopenharmony_ci#define COM11_NIGHT 0x80 /* NIght mode enable */ 1168c2ecf20Sopenharmony_ci#define COM11_NMFR 0x60 /* Two bit NM frame rate */ 1178c2ecf20Sopenharmony_ci#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */ 1188c2ecf20Sopenharmony_ci#define COM11_50HZ 0x08 /* Manual 50Hz select */ 1198c2ecf20Sopenharmony_ci#define COM11_EXP 0x02 1208c2ecf20Sopenharmony_ci#define REG_COM12 0x3c /* Control 12 */ 1218c2ecf20Sopenharmony_ci#define COM12_HREF 0x80 /* HREF always */ 1228c2ecf20Sopenharmony_ci#define REG_COM13 0x3d /* Control 13 */ 1238c2ecf20Sopenharmony_ci#define COM13_GAMMA 0x80 /* Gamma enable */ 1248c2ecf20Sopenharmony_ci#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ 1258c2ecf20Sopenharmony_ci#define COM13_UVSWAP 0x01 /* V before U - w/TSLB */ 1268c2ecf20Sopenharmony_ci#define REG_COM14 0x3e /* Control 14 */ 1278c2ecf20Sopenharmony_ci#define COM14_DCWEN 0x10 /* DCW/PCLK-scale enable */ 1288c2ecf20Sopenharmony_ci#define REG_EDGE 0x3f /* Edge enhancement factor */ 1298c2ecf20Sopenharmony_ci#define REG_COM15 0x40 /* Control 15 */ 1308c2ecf20Sopenharmony_ci#define COM15_R10F0 0x00 /* Data range 10 to F0 */ 1318c2ecf20Sopenharmony_ci#define COM15_R01FE 0x80 /* 01 to FE */ 1328c2ecf20Sopenharmony_ci#define COM15_R00FF 0xc0 /* 00 to FF */ 1338c2ecf20Sopenharmony_ci#define COM15_RGB565 0x10 /* RGB565 output */ 1348c2ecf20Sopenharmony_ci#define COM15_RGB555 0x30 /* RGB555 output */ 1358c2ecf20Sopenharmony_ci#define REG_COM16 0x41 /* Control 16 */ 1368c2ecf20Sopenharmony_ci#define COM16_AWBGAIN 0x08 /* AWB gain enable */ 1378c2ecf20Sopenharmony_ci#define REG_COM17 0x42 /* Control 17 */ 1388c2ecf20Sopenharmony_ci#define COM17_AECWIN 0xc0 /* AEC window - must match COM4 */ 1398c2ecf20Sopenharmony_ci#define COM17_CBAR 0x08 /* DSP Color bar */ 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci/* 1428c2ecf20Sopenharmony_ci * This matrix defines how the colors are generated, must be 1438c2ecf20Sopenharmony_ci * tweaked to adjust hue and saturation. 1448c2ecf20Sopenharmony_ci * 1458c2ecf20Sopenharmony_ci * Order: v-red, v-green, v-blue, u-red, u-green, u-blue 1468c2ecf20Sopenharmony_ci * 1478c2ecf20Sopenharmony_ci * They are nine-bit signed quantities, with the sign bit 1488c2ecf20Sopenharmony_ci * stored in 0x58. Sign for v-red is bit 0, and up from there. 1498c2ecf20Sopenharmony_ci */ 1508c2ecf20Sopenharmony_ci#define REG_CMATRIX_BASE 0x4f 1518c2ecf20Sopenharmony_ci#define CMATRIX_LEN 6 1528c2ecf20Sopenharmony_ci#define REG_CMATRIX_SIGN 0x58 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci#define REG_BRIGHT 0x55 /* Brightness */ 1568c2ecf20Sopenharmony_ci#define REG_CONTRAS 0x56 /* Contrast control */ 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci#define REG_GFIX 0x69 /* Fix gain control */ 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci#define REG_DBLV 0x6b /* PLL control an debugging */ 1618c2ecf20Sopenharmony_ci#define DBLV_BYPASS 0x0a /* Bypass PLL */ 1628c2ecf20Sopenharmony_ci#define DBLV_X4 0x4a /* clock x4 */ 1638c2ecf20Sopenharmony_ci#define DBLV_X6 0x8a /* clock x6 */ 1648c2ecf20Sopenharmony_ci#define DBLV_X8 0xca /* clock x8 */ 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci#define REG_SCALING_XSC 0x70 /* Test pattern and horizontal scale factor */ 1678c2ecf20Sopenharmony_ci#define TEST_PATTTERN_0 0x80 1688c2ecf20Sopenharmony_ci#define REG_SCALING_YSC 0x71 /* Test pattern and vertical scale factor */ 1698c2ecf20Sopenharmony_ci#define TEST_PATTTERN_1 0x80 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci#define REG_REG76 0x76 /* OV's name */ 1728c2ecf20Sopenharmony_ci#define R76_BLKPCOR 0x80 /* Black pixel correction enable */ 1738c2ecf20Sopenharmony_ci#define R76_WHTPCOR 0x40 /* White pixel correction enable */ 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci#define REG_RGB444 0x8c /* RGB 444 control */ 1768c2ecf20Sopenharmony_ci#define R444_ENABLE 0x02 /* Turn on RGB444, overrides 5x5 */ 1778c2ecf20Sopenharmony_ci#define R444_RGBX 0x01 /* Empty nibble at end */ 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci#define REG_HAECC1 0x9f /* Hist AEC/AGC control 1 */ 1808c2ecf20Sopenharmony_ci#define REG_HAECC2 0xa0 /* Hist AEC/AGC control 2 */ 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci#define REG_BD50MAX 0xa5 /* 50hz banding step limit */ 1838c2ecf20Sopenharmony_ci#define REG_HAECC3 0xa6 /* Hist AEC/AGC control 3 */ 1848c2ecf20Sopenharmony_ci#define REG_HAECC4 0xa7 /* Hist AEC/AGC control 4 */ 1858c2ecf20Sopenharmony_ci#define REG_HAECC5 0xa8 /* Hist AEC/AGC control 5 */ 1868c2ecf20Sopenharmony_ci#define REG_HAECC6 0xa9 /* Hist AEC/AGC control 6 */ 1878c2ecf20Sopenharmony_ci#define REG_HAECC7 0xaa /* Hist AEC/AGC control 7 */ 1888c2ecf20Sopenharmony_ci#define REG_BD60MAX 0xab /* 60hz banding step limit */ 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cienum ov7670_model { 1918c2ecf20Sopenharmony_ci MODEL_OV7670 = 0, 1928c2ecf20Sopenharmony_ci MODEL_OV7675, 1938c2ecf20Sopenharmony_ci}; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistruct ov7670_win_size { 1968c2ecf20Sopenharmony_ci int width; 1978c2ecf20Sopenharmony_ci int height; 1988c2ecf20Sopenharmony_ci unsigned char com7_bit; 1998c2ecf20Sopenharmony_ci int hstart; /* Start/stop values for the camera. Note */ 2008c2ecf20Sopenharmony_ci int hstop; /* that they do not always make complete */ 2018c2ecf20Sopenharmony_ci int vstart; /* sense to humans, but evidently the sensor */ 2028c2ecf20Sopenharmony_ci int vstop; /* will do the right thing... */ 2038c2ecf20Sopenharmony_ci struct regval_list *regs; /* Regs to tweak */ 2048c2ecf20Sopenharmony_ci}; 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_cistruct ov7670_devtype { 2078c2ecf20Sopenharmony_ci /* formats supported for each model */ 2088c2ecf20Sopenharmony_ci struct ov7670_win_size *win_sizes; 2098c2ecf20Sopenharmony_ci unsigned int n_win_sizes; 2108c2ecf20Sopenharmony_ci /* callbacks for frame rate control */ 2118c2ecf20Sopenharmony_ci int (*set_framerate)(struct v4l2_subdev *, struct v4l2_fract *); 2128c2ecf20Sopenharmony_ci void (*get_framerate)(struct v4l2_subdev *, struct v4l2_fract *); 2138c2ecf20Sopenharmony_ci}; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci/* 2168c2ecf20Sopenharmony_ci * Information we maintain about a known sensor. 2178c2ecf20Sopenharmony_ci */ 2188c2ecf20Sopenharmony_cistruct ov7670_format_struct; /* coming later */ 2198c2ecf20Sopenharmony_cistruct ov7670_info { 2208c2ecf20Sopenharmony_ci struct v4l2_subdev sd; 2218c2ecf20Sopenharmony_ci#if defined(CONFIG_MEDIA_CONTROLLER) 2228c2ecf20Sopenharmony_ci struct media_pad pad; 2238c2ecf20Sopenharmony_ci#endif 2248c2ecf20Sopenharmony_ci struct v4l2_ctrl_handler hdl; 2258c2ecf20Sopenharmony_ci struct { 2268c2ecf20Sopenharmony_ci /* gain cluster */ 2278c2ecf20Sopenharmony_ci struct v4l2_ctrl *auto_gain; 2288c2ecf20Sopenharmony_ci struct v4l2_ctrl *gain; 2298c2ecf20Sopenharmony_ci }; 2308c2ecf20Sopenharmony_ci struct { 2318c2ecf20Sopenharmony_ci /* exposure cluster */ 2328c2ecf20Sopenharmony_ci struct v4l2_ctrl *auto_exposure; 2338c2ecf20Sopenharmony_ci struct v4l2_ctrl *exposure; 2348c2ecf20Sopenharmony_ci }; 2358c2ecf20Sopenharmony_ci struct { 2368c2ecf20Sopenharmony_ci /* saturation/hue cluster */ 2378c2ecf20Sopenharmony_ci struct v4l2_ctrl *saturation; 2388c2ecf20Sopenharmony_ci struct v4l2_ctrl *hue; 2398c2ecf20Sopenharmony_ci }; 2408c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt format; 2418c2ecf20Sopenharmony_ci struct ov7670_format_struct *fmt; /* Current format */ 2428c2ecf20Sopenharmony_ci struct ov7670_win_size *wsize; 2438c2ecf20Sopenharmony_ci struct clk *clk; 2448c2ecf20Sopenharmony_ci int on; 2458c2ecf20Sopenharmony_ci struct gpio_desc *resetb_gpio; 2468c2ecf20Sopenharmony_ci struct gpio_desc *pwdn_gpio; 2478c2ecf20Sopenharmony_ci unsigned int mbus_config; /* Media bus configuration flags */ 2488c2ecf20Sopenharmony_ci int min_width; /* Filter out smaller sizes */ 2498c2ecf20Sopenharmony_ci int min_height; /* Filter out smaller sizes */ 2508c2ecf20Sopenharmony_ci int clock_speed; /* External clock speed (MHz) */ 2518c2ecf20Sopenharmony_ci u8 clkrc; /* Clock divider value */ 2528c2ecf20Sopenharmony_ci bool use_smbus; /* Use smbus I/O instead of I2C */ 2538c2ecf20Sopenharmony_ci bool pll_bypass; 2548c2ecf20Sopenharmony_ci bool pclk_hb_disable; 2558c2ecf20Sopenharmony_ci const struct ov7670_devtype *devtype; /* Device specifics */ 2568c2ecf20Sopenharmony_ci}; 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_cistatic inline struct ov7670_info *to_state(struct v4l2_subdev *sd) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci return container_of(sd, struct ov7670_info, sd); 2618c2ecf20Sopenharmony_ci} 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistatic inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) 2648c2ecf20Sopenharmony_ci{ 2658c2ecf20Sopenharmony_ci return &container_of(ctrl->handler, struct ov7670_info, hdl)->sd; 2668c2ecf20Sopenharmony_ci} 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci/* 2718c2ecf20Sopenharmony_ci * The default register settings, as obtained from OmniVision. There 2728c2ecf20Sopenharmony_ci * is really no making sense of most of these - lots of "reserved" values 2738c2ecf20Sopenharmony_ci * and such. 2748c2ecf20Sopenharmony_ci * 2758c2ecf20Sopenharmony_ci * These settings give VGA YUYV. 2768c2ecf20Sopenharmony_ci */ 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_cistruct regval_list { 2798c2ecf20Sopenharmony_ci unsigned char reg_num; 2808c2ecf20Sopenharmony_ci unsigned char value; 2818c2ecf20Sopenharmony_ci}; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic struct regval_list ov7670_default_regs[] = { 2848c2ecf20Sopenharmony_ci { REG_COM7, COM7_RESET }, 2858c2ecf20Sopenharmony_ci/* 2868c2ecf20Sopenharmony_ci * Clock scale: 3 = 15fps 2878c2ecf20Sopenharmony_ci * 2 = 20fps 2888c2ecf20Sopenharmony_ci * 1 = 30fps 2898c2ecf20Sopenharmony_ci */ 2908c2ecf20Sopenharmony_ci { REG_CLKRC, 0x1 }, /* OV: clock scale (30 fps) */ 2918c2ecf20Sopenharmony_ci { REG_TSLB, 0x04 }, /* OV */ 2928c2ecf20Sopenharmony_ci { REG_COM7, 0 }, /* VGA */ 2938c2ecf20Sopenharmony_ci /* 2948c2ecf20Sopenharmony_ci * Set the hardware window. These values from OV don't entirely 2958c2ecf20Sopenharmony_ci * make sense - hstop is less than hstart. But they work... 2968c2ecf20Sopenharmony_ci */ 2978c2ecf20Sopenharmony_ci { REG_HSTART, 0x13 }, { REG_HSTOP, 0x01 }, 2988c2ecf20Sopenharmony_ci { REG_HREF, 0xb6 }, { REG_VSTART, 0x02 }, 2998c2ecf20Sopenharmony_ci { REG_VSTOP, 0x7a }, { REG_VREF, 0x0a }, 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci { REG_COM3, 0 }, { REG_COM14, 0 }, 3028c2ecf20Sopenharmony_ci /* Mystery scaling numbers */ 3038c2ecf20Sopenharmony_ci { REG_SCALING_XSC, 0x3a }, 3048c2ecf20Sopenharmony_ci { REG_SCALING_YSC, 0x35 }, 3058c2ecf20Sopenharmony_ci { 0x72, 0x11 }, { 0x73, 0xf0 }, 3068c2ecf20Sopenharmony_ci { 0xa2, 0x02 }, { REG_COM10, 0x0 }, 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci /* Gamma curve values */ 3098c2ecf20Sopenharmony_ci { 0x7a, 0x20 }, { 0x7b, 0x10 }, 3108c2ecf20Sopenharmony_ci { 0x7c, 0x1e }, { 0x7d, 0x35 }, 3118c2ecf20Sopenharmony_ci { 0x7e, 0x5a }, { 0x7f, 0x69 }, 3128c2ecf20Sopenharmony_ci { 0x80, 0x76 }, { 0x81, 0x80 }, 3138c2ecf20Sopenharmony_ci { 0x82, 0x88 }, { 0x83, 0x8f }, 3148c2ecf20Sopenharmony_ci { 0x84, 0x96 }, { 0x85, 0xa3 }, 3158c2ecf20Sopenharmony_ci { 0x86, 0xaf }, { 0x87, 0xc4 }, 3168c2ecf20Sopenharmony_ci { 0x88, 0xd7 }, { 0x89, 0xe8 }, 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci /* AGC and AEC parameters. Note we start by disabling those features, 3198c2ecf20Sopenharmony_ci then turn them only after tweaking the values. */ 3208c2ecf20Sopenharmony_ci { REG_COM8, COM8_FASTAEC | COM8_AECSTEP | COM8_BFILT }, 3218c2ecf20Sopenharmony_ci { REG_GAIN, 0 }, { REG_AECH, 0 }, 3228c2ecf20Sopenharmony_ci { REG_COM4, 0x40 }, /* magic reserved bit */ 3238c2ecf20Sopenharmony_ci { REG_COM9, 0x18 }, /* 4x gain + magic rsvd bit */ 3248c2ecf20Sopenharmony_ci { REG_BD50MAX, 0x05 }, { REG_BD60MAX, 0x07 }, 3258c2ecf20Sopenharmony_ci { REG_AEW, 0x95 }, { REG_AEB, 0x33 }, 3268c2ecf20Sopenharmony_ci { REG_VPT, 0xe3 }, { REG_HAECC1, 0x78 }, 3278c2ecf20Sopenharmony_ci { REG_HAECC2, 0x68 }, { 0xa1, 0x03 }, /* magic */ 3288c2ecf20Sopenharmony_ci { REG_HAECC3, 0xd8 }, { REG_HAECC4, 0xd8 }, 3298c2ecf20Sopenharmony_ci { REG_HAECC5, 0xf0 }, { REG_HAECC6, 0x90 }, 3308c2ecf20Sopenharmony_ci { REG_HAECC7, 0x94 }, 3318c2ecf20Sopenharmony_ci { REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC }, 3328c2ecf20Sopenharmony_ci 3338c2ecf20Sopenharmony_ci /* Almost all of these are magic "reserved" values. */ 3348c2ecf20Sopenharmony_ci { REG_COM5, 0x61 }, { REG_COM6, 0x4b }, 3358c2ecf20Sopenharmony_ci { 0x16, 0x02 }, { REG_MVFP, 0x07 }, 3368c2ecf20Sopenharmony_ci { 0x21, 0x02 }, { 0x22, 0x91 }, 3378c2ecf20Sopenharmony_ci { 0x29, 0x07 }, { 0x33, 0x0b }, 3388c2ecf20Sopenharmony_ci { 0x35, 0x0b }, { 0x37, 0x1d }, 3398c2ecf20Sopenharmony_ci { 0x38, 0x71 }, { 0x39, 0x2a }, 3408c2ecf20Sopenharmony_ci { REG_COM12, 0x78 }, { 0x4d, 0x40 }, 3418c2ecf20Sopenharmony_ci { 0x4e, 0x20 }, { REG_GFIX, 0 }, 3428c2ecf20Sopenharmony_ci { 0x6b, 0x4a }, { 0x74, 0x10 }, 3438c2ecf20Sopenharmony_ci { 0x8d, 0x4f }, { 0x8e, 0 }, 3448c2ecf20Sopenharmony_ci { 0x8f, 0 }, { 0x90, 0 }, 3458c2ecf20Sopenharmony_ci { 0x91, 0 }, { 0x96, 0 }, 3468c2ecf20Sopenharmony_ci { 0x9a, 0 }, { 0xb0, 0x84 }, 3478c2ecf20Sopenharmony_ci { 0xb1, 0x0c }, { 0xb2, 0x0e }, 3488c2ecf20Sopenharmony_ci { 0xb3, 0x82 }, { 0xb8, 0x0a }, 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci /* More reserved magic, some of which tweaks white balance */ 3518c2ecf20Sopenharmony_ci { 0x43, 0x0a }, { 0x44, 0xf0 }, 3528c2ecf20Sopenharmony_ci { 0x45, 0x34 }, { 0x46, 0x58 }, 3538c2ecf20Sopenharmony_ci { 0x47, 0x28 }, { 0x48, 0x3a }, 3548c2ecf20Sopenharmony_ci { 0x59, 0x88 }, { 0x5a, 0x88 }, 3558c2ecf20Sopenharmony_ci { 0x5b, 0x44 }, { 0x5c, 0x67 }, 3568c2ecf20Sopenharmony_ci { 0x5d, 0x49 }, { 0x5e, 0x0e }, 3578c2ecf20Sopenharmony_ci { 0x6c, 0x0a }, { 0x6d, 0x55 }, 3588c2ecf20Sopenharmony_ci { 0x6e, 0x11 }, { 0x6f, 0x9f }, /* "9e for advance AWB" */ 3598c2ecf20Sopenharmony_ci { 0x6a, 0x40 }, { REG_BLUE, 0x40 }, 3608c2ecf20Sopenharmony_ci { REG_RED, 0x60 }, 3618c2ecf20Sopenharmony_ci { REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC|COM8_AWB }, 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_ci /* Matrix coefficients */ 3648c2ecf20Sopenharmony_ci { 0x4f, 0x80 }, { 0x50, 0x80 }, 3658c2ecf20Sopenharmony_ci { 0x51, 0 }, { 0x52, 0x22 }, 3668c2ecf20Sopenharmony_ci { 0x53, 0x5e }, { 0x54, 0x80 }, 3678c2ecf20Sopenharmony_ci { 0x58, 0x9e }, 3688c2ecf20Sopenharmony_ci 3698c2ecf20Sopenharmony_ci { REG_COM16, COM16_AWBGAIN }, { REG_EDGE, 0 }, 3708c2ecf20Sopenharmony_ci { 0x75, 0x05 }, { 0x76, 0xe1 }, 3718c2ecf20Sopenharmony_ci { 0x4c, 0 }, { 0x77, 0x01 }, 3728c2ecf20Sopenharmony_ci { REG_COM13, 0xc3 }, { 0x4b, 0x09 }, 3738c2ecf20Sopenharmony_ci { 0xc9, 0x60 }, { REG_COM16, 0x38 }, 3748c2ecf20Sopenharmony_ci { 0x56, 0x40 }, 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci { 0x34, 0x11 }, { REG_COM11, COM11_EXP|COM11_HZAUTO }, 3778c2ecf20Sopenharmony_ci { 0xa4, 0x88 }, { 0x96, 0 }, 3788c2ecf20Sopenharmony_ci { 0x97, 0x30 }, { 0x98, 0x20 }, 3798c2ecf20Sopenharmony_ci { 0x99, 0x30 }, { 0x9a, 0x84 }, 3808c2ecf20Sopenharmony_ci { 0x9b, 0x29 }, { 0x9c, 0x03 }, 3818c2ecf20Sopenharmony_ci { 0x9d, 0x4c }, { 0x9e, 0x3f }, 3828c2ecf20Sopenharmony_ci { 0x78, 0x04 }, 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci /* Extra-weird stuff. Some sort of multiplexor register */ 3858c2ecf20Sopenharmony_ci { 0x79, 0x01 }, { 0xc8, 0xf0 }, 3868c2ecf20Sopenharmony_ci { 0x79, 0x0f }, { 0xc8, 0x00 }, 3878c2ecf20Sopenharmony_ci { 0x79, 0x10 }, { 0xc8, 0x7e }, 3888c2ecf20Sopenharmony_ci { 0x79, 0x0a }, { 0xc8, 0x80 }, 3898c2ecf20Sopenharmony_ci { 0x79, 0x0b }, { 0xc8, 0x01 }, 3908c2ecf20Sopenharmony_ci { 0x79, 0x0c }, { 0xc8, 0x0f }, 3918c2ecf20Sopenharmony_ci { 0x79, 0x0d }, { 0xc8, 0x20 }, 3928c2ecf20Sopenharmony_ci { 0x79, 0x09 }, { 0xc8, 0x80 }, 3938c2ecf20Sopenharmony_ci { 0x79, 0x02 }, { 0xc8, 0xc0 }, 3948c2ecf20Sopenharmony_ci { 0x79, 0x03 }, { 0xc8, 0x40 }, 3958c2ecf20Sopenharmony_ci { 0x79, 0x05 }, { 0xc8, 0x30 }, 3968c2ecf20Sopenharmony_ci { 0x79, 0x26 }, 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci { 0xff, 0xff }, /* END MARKER */ 3998c2ecf20Sopenharmony_ci}; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci/* 4038c2ecf20Sopenharmony_ci * Here we'll try to encapsulate the changes for just the output 4048c2ecf20Sopenharmony_ci * video format. 4058c2ecf20Sopenharmony_ci * 4068c2ecf20Sopenharmony_ci * RGB656 and YUV422 come from OV; RGB444 is homebrewed. 4078c2ecf20Sopenharmony_ci * 4088c2ecf20Sopenharmony_ci * IMPORTANT RULE: the first entry must be for COM7, see ov7670_s_fmt for why. 4098c2ecf20Sopenharmony_ci */ 4108c2ecf20Sopenharmony_ci 4118c2ecf20Sopenharmony_ci 4128c2ecf20Sopenharmony_cistatic struct regval_list ov7670_fmt_yuv422[] = { 4138c2ecf20Sopenharmony_ci { REG_COM7, 0x0 }, /* Selects YUV mode */ 4148c2ecf20Sopenharmony_ci { REG_RGB444, 0 }, /* No RGB444 please */ 4158c2ecf20Sopenharmony_ci { REG_COM1, 0 }, /* CCIR601 */ 4168c2ecf20Sopenharmony_ci { REG_COM15, COM15_R00FF }, 4178c2ecf20Sopenharmony_ci { REG_COM9, 0x48 }, /* 32x gain ceiling; 0x8 is reserved bit */ 4188c2ecf20Sopenharmony_ci { 0x4f, 0x80 }, /* "matrix coefficient 1" */ 4198c2ecf20Sopenharmony_ci { 0x50, 0x80 }, /* "matrix coefficient 2" */ 4208c2ecf20Sopenharmony_ci { 0x51, 0 }, /* vb */ 4218c2ecf20Sopenharmony_ci { 0x52, 0x22 }, /* "matrix coefficient 4" */ 4228c2ecf20Sopenharmony_ci { 0x53, 0x5e }, /* "matrix coefficient 5" */ 4238c2ecf20Sopenharmony_ci { 0x54, 0x80 }, /* "matrix coefficient 6" */ 4248c2ecf20Sopenharmony_ci { REG_COM13, COM13_GAMMA|COM13_UVSAT }, 4258c2ecf20Sopenharmony_ci { 0xff, 0xff }, 4268c2ecf20Sopenharmony_ci}; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_cistatic struct regval_list ov7670_fmt_rgb565[] = { 4298c2ecf20Sopenharmony_ci { REG_COM7, COM7_RGB }, /* Selects RGB mode */ 4308c2ecf20Sopenharmony_ci { REG_RGB444, 0 }, /* No RGB444 please */ 4318c2ecf20Sopenharmony_ci { REG_COM1, 0x0 }, /* CCIR601 */ 4328c2ecf20Sopenharmony_ci { REG_COM15, COM15_RGB565 }, 4338c2ecf20Sopenharmony_ci { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */ 4348c2ecf20Sopenharmony_ci { 0x4f, 0xb3 }, /* "matrix coefficient 1" */ 4358c2ecf20Sopenharmony_ci { 0x50, 0xb3 }, /* "matrix coefficient 2" */ 4368c2ecf20Sopenharmony_ci { 0x51, 0 }, /* vb */ 4378c2ecf20Sopenharmony_ci { 0x52, 0x3d }, /* "matrix coefficient 4" */ 4388c2ecf20Sopenharmony_ci { 0x53, 0xa7 }, /* "matrix coefficient 5" */ 4398c2ecf20Sopenharmony_ci { 0x54, 0xe4 }, /* "matrix coefficient 6" */ 4408c2ecf20Sopenharmony_ci { REG_COM13, COM13_GAMMA|COM13_UVSAT }, 4418c2ecf20Sopenharmony_ci { 0xff, 0xff }, 4428c2ecf20Sopenharmony_ci}; 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_cistatic struct regval_list ov7670_fmt_rgb444[] = { 4458c2ecf20Sopenharmony_ci { REG_COM7, COM7_RGB }, /* Selects RGB mode */ 4468c2ecf20Sopenharmony_ci { REG_RGB444, R444_ENABLE }, /* Enable xxxxrrrr ggggbbbb */ 4478c2ecf20Sopenharmony_ci { REG_COM1, 0x0 }, /* CCIR601 */ 4488c2ecf20Sopenharmony_ci { REG_COM15, COM15_R01FE|COM15_RGB565 }, /* Data range needed? */ 4498c2ecf20Sopenharmony_ci { REG_COM9, 0x38 }, /* 16x gain ceiling; 0x8 is reserved bit */ 4508c2ecf20Sopenharmony_ci { 0x4f, 0xb3 }, /* "matrix coefficient 1" */ 4518c2ecf20Sopenharmony_ci { 0x50, 0xb3 }, /* "matrix coefficient 2" */ 4528c2ecf20Sopenharmony_ci { 0x51, 0 }, /* vb */ 4538c2ecf20Sopenharmony_ci { 0x52, 0x3d }, /* "matrix coefficient 4" */ 4548c2ecf20Sopenharmony_ci { 0x53, 0xa7 }, /* "matrix coefficient 5" */ 4558c2ecf20Sopenharmony_ci { 0x54, 0xe4 }, /* "matrix coefficient 6" */ 4568c2ecf20Sopenharmony_ci { REG_COM13, COM13_GAMMA|COM13_UVSAT|0x2 }, /* Magic rsvd bit */ 4578c2ecf20Sopenharmony_ci { 0xff, 0xff }, 4588c2ecf20Sopenharmony_ci}; 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_cistatic struct regval_list ov7670_fmt_raw[] = { 4618c2ecf20Sopenharmony_ci { REG_COM7, COM7_BAYER }, 4628c2ecf20Sopenharmony_ci { REG_COM13, 0x08 }, /* No gamma, magic rsvd bit */ 4638c2ecf20Sopenharmony_ci { REG_COM16, 0x3d }, /* Edge enhancement, denoise */ 4648c2ecf20Sopenharmony_ci { REG_REG76, 0xe1 }, /* Pix correction, magic rsvd */ 4658c2ecf20Sopenharmony_ci { 0xff, 0xff }, 4668c2ecf20Sopenharmony_ci}; 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_ci 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci/* 4718c2ecf20Sopenharmony_ci * Low-level register I/O. 4728c2ecf20Sopenharmony_ci * 4738c2ecf20Sopenharmony_ci * Note that there are two versions of these. On the XO 1, the 4748c2ecf20Sopenharmony_ci * i2c controller only does SMBUS, so that's what we use. The 4758c2ecf20Sopenharmony_ci * ov7670 is not really an SMBUS device, though, so the communication 4768c2ecf20Sopenharmony_ci * is not always entirely reliable. 4778c2ecf20Sopenharmony_ci */ 4788c2ecf20Sopenharmony_cistatic int ov7670_read_smbus(struct v4l2_subdev *sd, unsigned char reg, 4798c2ecf20Sopenharmony_ci unsigned char *value) 4808c2ecf20Sopenharmony_ci{ 4818c2ecf20Sopenharmony_ci struct i2c_client *client = v4l2_get_subdevdata(sd); 4828c2ecf20Sopenharmony_ci int ret; 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci ret = i2c_smbus_read_byte_data(client, reg); 4858c2ecf20Sopenharmony_ci if (ret >= 0) { 4868c2ecf20Sopenharmony_ci *value = (unsigned char)ret; 4878c2ecf20Sopenharmony_ci ret = 0; 4888c2ecf20Sopenharmony_ci } 4898c2ecf20Sopenharmony_ci return ret; 4908c2ecf20Sopenharmony_ci} 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_cistatic int ov7670_write_smbus(struct v4l2_subdev *sd, unsigned char reg, 4948c2ecf20Sopenharmony_ci unsigned char value) 4958c2ecf20Sopenharmony_ci{ 4968c2ecf20Sopenharmony_ci struct i2c_client *client = v4l2_get_subdevdata(sd); 4978c2ecf20Sopenharmony_ci int ret = i2c_smbus_write_byte_data(client, reg, value); 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci if (reg == REG_COM7 && (value & COM7_RESET)) 5008c2ecf20Sopenharmony_ci msleep(5); /* Wait for reset to run */ 5018c2ecf20Sopenharmony_ci return ret; 5028c2ecf20Sopenharmony_ci} 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci/* 5058c2ecf20Sopenharmony_ci * On most platforms, we'd rather do straight i2c I/O. 5068c2ecf20Sopenharmony_ci */ 5078c2ecf20Sopenharmony_cistatic int ov7670_read_i2c(struct v4l2_subdev *sd, unsigned char reg, 5088c2ecf20Sopenharmony_ci unsigned char *value) 5098c2ecf20Sopenharmony_ci{ 5108c2ecf20Sopenharmony_ci struct i2c_client *client = v4l2_get_subdevdata(sd); 5118c2ecf20Sopenharmony_ci u8 data = reg; 5128c2ecf20Sopenharmony_ci struct i2c_msg msg; 5138c2ecf20Sopenharmony_ci int ret; 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci /* 5168c2ecf20Sopenharmony_ci * Send out the register address... 5178c2ecf20Sopenharmony_ci */ 5188c2ecf20Sopenharmony_ci msg.addr = client->addr; 5198c2ecf20Sopenharmony_ci msg.flags = 0; 5208c2ecf20Sopenharmony_ci msg.len = 1; 5218c2ecf20Sopenharmony_ci msg.buf = &data; 5228c2ecf20Sopenharmony_ci ret = i2c_transfer(client->adapter, &msg, 1); 5238c2ecf20Sopenharmony_ci if (ret < 0) { 5248c2ecf20Sopenharmony_ci printk(KERN_ERR "Error %d on register write\n", ret); 5258c2ecf20Sopenharmony_ci return ret; 5268c2ecf20Sopenharmony_ci } 5278c2ecf20Sopenharmony_ci /* 5288c2ecf20Sopenharmony_ci * ...then read back the result. 5298c2ecf20Sopenharmony_ci */ 5308c2ecf20Sopenharmony_ci msg.flags = I2C_M_RD; 5318c2ecf20Sopenharmony_ci ret = i2c_transfer(client->adapter, &msg, 1); 5328c2ecf20Sopenharmony_ci if (ret >= 0) { 5338c2ecf20Sopenharmony_ci *value = data; 5348c2ecf20Sopenharmony_ci ret = 0; 5358c2ecf20Sopenharmony_ci } 5368c2ecf20Sopenharmony_ci return ret; 5378c2ecf20Sopenharmony_ci} 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci 5408c2ecf20Sopenharmony_cistatic int ov7670_write_i2c(struct v4l2_subdev *sd, unsigned char reg, 5418c2ecf20Sopenharmony_ci unsigned char value) 5428c2ecf20Sopenharmony_ci{ 5438c2ecf20Sopenharmony_ci struct i2c_client *client = v4l2_get_subdevdata(sd); 5448c2ecf20Sopenharmony_ci struct i2c_msg msg; 5458c2ecf20Sopenharmony_ci unsigned char data[2] = { reg, value }; 5468c2ecf20Sopenharmony_ci int ret; 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci msg.addr = client->addr; 5498c2ecf20Sopenharmony_ci msg.flags = 0; 5508c2ecf20Sopenharmony_ci msg.len = 2; 5518c2ecf20Sopenharmony_ci msg.buf = data; 5528c2ecf20Sopenharmony_ci ret = i2c_transfer(client->adapter, &msg, 1); 5538c2ecf20Sopenharmony_ci if (ret > 0) 5548c2ecf20Sopenharmony_ci ret = 0; 5558c2ecf20Sopenharmony_ci if (reg == REG_COM7 && (value & COM7_RESET)) 5568c2ecf20Sopenharmony_ci msleep(5); /* Wait for reset to run */ 5578c2ecf20Sopenharmony_ci return ret; 5588c2ecf20Sopenharmony_ci} 5598c2ecf20Sopenharmony_ci 5608c2ecf20Sopenharmony_cistatic int ov7670_read(struct v4l2_subdev *sd, unsigned char reg, 5618c2ecf20Sopenharmony_ci unsigned char *value) 5628c2ecf20Sopenharmony_ci{ 5638c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 5648c2ecf20Sopenharmony_ci if (info->use_smbus) 5658c2ecf20Sopenharmony_ci return ov7670_read_smbus(sd, reg, value); 5668c2ecf20Sopenharmony_ci else 5678c2ecf20Sopenharmony_ci return ov7670_read_i2c(sd, reg, value); 5688c2ecf20Sopenharmony_ci} 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_cistatic int ov7670_write(struct v4l2_subdev *sd, unsigned char reg, 5718c2ecf20Sopenharmony_ci unsigned char value) 5728c2ecf20Sopenharmony_ci{ 5738c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 5748c2ecf20Sopenharmony_ci if (info->use_smbus) 5758c2ecf20Sopenharmony_ci return ov7670_write_smbus(sd, reg, value); 5768c2ecf20Sopenharmony_ci else 5778c2ecf20Sopenharmony_ci return ov7670_write_i2c(sd, reg, value); 5788c2ecf20Sopenharmony_ci} 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_cistatic int ov7670_update_bits(struct v4l2_subdev *sd, unsigned char reg, 5818c2ecf20Sopenharmony_ci unsigned char mask, unsigned char value) 5828c2ecf20Sopenharmony_ci{ 5838c2ecf20Sopenharmony_ci unsigned char orig; 5848c2ecf20Sopenharmony_ci int ret; 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci ret = ov7670_read(sd, reg, &orig); 5878c2ecf20Sopenharmony_ci if (ret) 5888c2ecf20Sopenharmony_ci return ret; 5898c2ecf20Sopenharmony_ci 5908c2ecf20Sopenharmony_ci return ov7670_write(sd, reg, (orig & ~mask) | (value & mask)); 5918c2ecf20Sopenharmony_ci} 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci/* 5948c2ecf20Sopenharmony_ci * Write a list of register settings; ff/ff stops the process. 5958c2ecf20Sopenharmony_ci */ 5968c2ecf20Sopenharmony_cistatic int ov7670_write_array(struct v4l2_subdev *sd, struct regval_list *vals) 5978c2ecf20Sopenharmony_ci{ 5988c2ecf20Sopenharmony_ci while (vals->reg_num != 0xff || vals->value != 0xff) { 5998c2ecf20Sopenharmony_ci int ret = ov7670_write(sd, vals->reg_num, vals->value); 6008c2ecf20Sopenharmony_ci if (ret < 0) 6018c2ecf20Sopenharmony_ci return ret; 6028c2ecf20Sopenharmony_ci vals++; 6038c2ecf20Sopenharmony_ci } 6048c2ecf20Sopenharmony_ci return 0; 6058c2ecf20Sopenharmony_ci} 6068c2ecf20Sopenharmony_ci 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_ci/* 6098c2ecf20Sopenharmony_ci * Stuff that knows about the sensor. 6108c2ecf20Sopenharmony_ci */ 6118c2ecf20Sopenharmony_cistatic int ov7670_reset(struct v4l2_subdev *sd, u32 val) 6128c2ecf20Sopenharmony_ci{ 6138c2ecf20Sopenharmony_ci ov7670_write(sd, REG_COM7, COM7_RESET); 6148c2ecf20Sopenharmony_ci msleep(1); 6158c2ecf20Sopenharmony_ci return 0; 6168c2ecf20Sopenharmony_ci} 6178c2ecf20Sopenharmony_ci 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_cistatic int ov7670_init(struct v4l2_subdev *sd, u32 val) 6208c2ecf20Sopenharmony_ci{ 6218c2ecf20Sopenharmony_ci return ov7670_write_array(sd, ov7670_default_regs); 6228c2ecf20Sopenharmony_ci} 6238c2ecf20Sopenharmony_ci 6248c2ecf20Sopenharmony_cistatic int ov7670_detect(struct v4l2_subdev *sd) 6258c2ecf20Sopenharmony_ci{ 6268c2ecf20Sopenharmony_ci unsigned char v; 6278c2ecf20Sopenharmony_ci int ret; 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci ret = ov7670_init(sd, 0); 6308c2ecf20Sopenharmony_ci if (ret < 0) 6318c2ecf20Sopenharmony_ci return ret; 6328c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_MIDH, &v); 6338c2ecf20Sopenharmony_ci if (ret < 0) 6348c2ecf20Sopenharmony_ci return ret; 6358c2ecf20Sopenharmony_ci if (v != 0x7f) /* OV manuf. id. */ 6368c2ecf20Sopenharmony_ci return -ENODEV; 6378c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_MIDL, &v); 6388c2ecf20Sopenharmony_ci if (ret < 0) 6398c2ecf20Sopenharmony_ci return ret; 6408c2ecf20Sopenharmony_ci if (v != 0xa2) 6418c2ecf20Sopenharmony_ci return -ENODEV; 6428c2ecf20Sopenharmony_ci /* 6438c2ecf20Sopenharmony_ci * OK, we know we have an OmniVision chip...but which one? 6448c2ecf20Sopenharmony_ci */ 6458c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_PID, &v); 6468c2ecf20Sopenharmony_ci if (ret < 0) 6478c2ecf20Sopenharmony_ci return ret; 6488c2ecf20Sopenharmony_ci if (v != 0x76) /* PID + VER = 0x76 / 0x73 */ 6498c2ecf20Sopenharmony_ci return -ENODEV; 6508c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_VER, &v); 6518c2ecf20Sopenharmony_ci if (ret < 0) 6528c2ecf20Sopenharmony_ci return ret; 6538c2ecf20Sopenharmony_ci if (v != 0x73) /* PID + VER = 0x76 / 0x73 */ 6548c2ecf20Sopenharmony_ci return -ENODEV; 6558c2ecf20Sopenharmony_ci return 0; 6568c2ecf20Sopenharmony_ci} 6578c2ecf20Sopenharmony_ci 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci/* 6608c2ecf20Sopenharmony_ci * Store information about the video data format. The color matrix 6618c2ecf20Sopenharmony_ci * is deeply tied into the format, so keep the relevant values here. 6628c2ecf20Sopenharmony_ci * The magic matrix numbers come from OmniVision. 6638c2ecf20Sopenharmony_ci */ 6648c2ecf20Sopenharmony_cistatic struct ov7670_format_struct { 6658c2ecf20Sopenharmony_ci u32 mbus_code; 6668c2ecf20Sopenharmony_ci enum v4l2_colorspace colorspace; 6678c2ecf20Sopenharmony_ci struct regval_list *regs; 6688c2ecf20Sopenharmony_ci int cmatrix[CMATRIX_LEN]; 6698c2ecf20Sopenharmony_ci} ov7670_formats[] = { 6708c2ecf20Sopenharmony_ci { 6718c2ecf20Sopenharmony_ci .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, 6728c2ecf20Sopenharmony_ci .colorspace = V4L2_COLORSPACE_SRGB, 6738c2ecf20Sopenharmony_ci .regs = ov7670_fmt_yuv422, 6748c2ecf20Sopenharmony_ci .cmatrix = { 128, -128, 0, -34, -94, 128 }, 6758c2ecf20Sopenharmony_ci }, 6768c2ecf20Sopenharmony_ci { 6778c2ecf20Sopenharmony_ci .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, 6788c2ecf20Sopenharmony_ci .colorspace = V4L2_COLORSPACE_SRGB, 6798c2ecf20Sopenharmony_ci .regs = ov7670_fmt_rgb444, 6808c2ecf20Sopenharmony_ci .cmatrix = { 179, -179, 0, -61, -176, 228 }, 6818c2ecf20Sopenharmony_ci }, 6828c2ecf20Sopenharmony_ci { 6838c2ecf20Sopenharmony_ci .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, 6848c2ecf20Sopenharmony_ci .colorspace = V4L2_COLORSPACE_SRGB, 6858c2ecf20Sopenharmony_ci .regs = ov7670_fmt_rgb565, 6868c2ecf20Sopenharmony_ci .cmatrix = { 179, -179, 0, -61, -176, 228 }, 6878c2ecf20Sopenharmony_ci }, 6888c2ecf20Sopenharmony_ci { 6898c2ecf20Sopenharmony_ci .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, 6908c2ecf20Sopenharmony_ci .colorspace = V4L2_COLORSPACE_SRGB, 6918c2ecf20Sopenharmony_ci .regs = ov7670_fmt_raw, 6928c2ecf20Sopenharmony_ci .cmatrix = { 0, 0, 0, 0, 0, 0 }, 6938c2ecf20Sopenharmony_ci }, 6948c2ecf20Sopenharmony_ci}; 6958c2ecf20Sopenharmony_ci#define N_OV7670_FMTS ARRAY_SIZE(ov7670_formats) 6968c2ecf20Sopenharmony_ci 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci/* 6998c2ecf20Sopenharmony_ci * Then there is the issue of window sizes. Try to capture the info here. 7008c2ecf20Sopenharmony_ci */ 7018c2ecf20Sopenharmony_ci 7028c2ecf20Sopenharmony_ci/* 7038c2ecf20Sopenharmony_ci * QCIF mode is done (by OV) in a very strange way - it actually looks like 7048c2ecf20Sopenharmony_ci * VGA with weird scaling options - they do *not* use the canned QCIF mode 7058c2ecf20Sopenharmony_ci * which is allegedly provided by the sensor. So here's the weird register 7068c2ecf20Sopenharmony_ci * settings. 7078c2ecf20Sopenharmony_ci */ 7088c2ecf20Sopenharmony_cistatic struct regval_list ov7670_qcif_regs[] = { 7098c2ecf20Sopenharmony_ci { REG_COM3, COM3_SCALEEN|COM3_DCWEN }, 7108c2ecf20Sopenharmony_ci { REG_COM3, COM3_DCWEN }, 7118c2ecf20Sopenharmony_ci { REG_COM14, COM14_DCWEN | 0x01}, 7128c2ecf20Sopenharmony_ci { 0x73, 0xf1 }, 7138c2ecf20Sopenharmony_ci { 0xa2, 0x52 }, 7148c2ecf20Sopenharmony_ci { 0x7b, 0x1c }, 7158c2ecf20Sopenharmony_ci { 0x7c, 0x28 }, 7168c2ecf20Sopenharmony_ci { 0x7d, 0x3c }, 7178c2ecf20Sopenharmony_ci { 0x7f, 0x69 }, 7188c2ecf20Sopenharmony_ci { REG_COM9, 0x38 }, 7198c2ecf20Sopenharmony_ci { 0xa1, 0x0b }, 7208c2ecf20Sopenharmony_ci { 0x74, 0x19 }, 7218c2ecf20Sopenharmony_ci { 0x9a, 0x80 }, 7228c2ecf20Sopenharmony_ci { 0x43, 0x14 }, 7238c2ecf20Sopenharmony_ci { REG_COM13, 0xc0 }, 7248c2ecf20Sopenharmony_ci { 0xff, 0xff }, 7258c2ecf20Sopenharmony_ci}; 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_cistatic struct ov7670_win_size ov7670_win_sizes[] = { 7288c2ecf20Sopenharmony_ci /* VGA */ 7298c2ecf20Sopenharmony_ci { 7308c2ecf20Sopenharmony_ci .width = VGA_WIDTH, 7318c2ecf20Sopenharmony_ci .height = VGA_HEIGHT, 7328c2ecf20Sopenharmony_ci .com7_bit = COM7_FMT_VGA, 7338c2ecf20Sopenharmony_ci .hstart = 158, /* These values from */ 7348c2ecf20Sopenharmony_ci .hstop = 14, /* Omnivision */ 7358c2ecf20Sopenharmony_ci .vstart = 10, 7368c2ecf20Sopenharmony_ci .vstop = 490, 7378c2ecf20Sopenharmony_ci .regs = NULL, 7388c2ecf20Sopenharmony_ci }, 7398c2ecf20Sopenharmony_ci /* CIF */ 7408c2ecf20Sopenharmony_ci { 7418c2ecf20Sopenharmony_ci .width = CIF_WIDTH, 7428c2ecf20Sopenharmony_ci .height = CIF_HEIGHT, 7438c2ecf20Sopenharmony_ci .com7_bit = COM7_FMT_CIF, 7448c2ecf20Sopenharmony_ci .hstart = 170, /* Empirically determined */ 7458c2ecf20Sopenharmony_ci .hstop = 90, 7468c2ecf20Sopenharmony_ci .vstart = 14, 7478c2ecf20Sopenharmony_ci .vstop = 494, 7488c2ecf20Sopenharmony_ci .regs = NULL, 7498c2ecf20Sopenharmony_ci }, 7508c2ecf20Sopenharmony_ci /* QVGA */ 7518c2ecf20Sopenharmony_ci { 7528c2ecf20Sopenharmony_ci .width = QVGA_WIDTH, 7538c2ecf20Sopenharmony_ci .height = QVGA_HEIGHT, 7548c2ecf20Sopenharmony_ci .com7_bit = COM7_FMT_QVGA, 7558c2ecf20Sopenharmony_ci .hstart = 168, /* Empirically determined */ 7568c2ecf20Sopenharmony_ci .hstop = 24, 7578c2ecf20Sopenharmony_ci .vstart = 12, 7588c2ecf20Sopenharmony_ci .vstop = 492, 7598c2ecf20Sopenharmony_ci .regs = NULL, 7608c2ecf20Sopenharmony_ci }, 7618c2ecf20Sopenharmony_ci /* QCIF */ 7628c2ecf20Sopenharmony_ci { 7638c2ecf20Sopenharmony_ci .width = QCIF_WIDTH, 7648c2ecf20Sopenharmony_ci .height = QCIF_HEIGHT, 7658c2ecf20Sopenharmony_ci .com7_bit = COM7_FMT_VGA, /* see comment above */ 7668c2ecf20Sopenharmony_ci .hstart = 456, /* Empirically determined */ 7678c2ecf20Sopenharmony_ci .hstop = 24, 7688c2ecf20Sopenharmony_ci .vstart = 14, 7698c2ecf20Sopenharmony_ci .vstop = 494, 7708c2ecf20Sopenharmony_ci .regs = ov7670_qcif_regs, 7718c2ecf20Sopenharmony_ci } 7728c2ecf20Sopenharmony_ci}; 7738c2ecf20Sopenharmony_ci 7748c2ecf20Sopenharmony_cistatic struct ov7670_win_size ov7675_win_sizes[] = { 7758c2ecf20Sopenharmony_ci /* 7768c2ecf20Sopenharmony_ci * Currently, only VGA is supported. Theoretically it could be possible 7778c2ecf20Sopenharmony_ci * to support CIF, QVGA and QCIF too. Taking values for ov7670 as a 7788c2ecf20Sopenharmony_ci * base and tweak them empirically could be required. 7798c2ecf20Sopenharmony_ci */ 7808c2ecf20Sopenharmony_ci { 7818c2ecf20Sopenharmony_ci .width = VGA_WIDTH, 7828c2ecf20Sopenharmony_ci .height = VGA_HEIGHT, 7838c2ecf20Sopenharmony_ci .com7_bit = COM7_FMT_VGA, 7848c2ecf20Sopenharmony_ci .hstart = 158, /* These values from */ 7858c2ecf20Sopenharmony_ci .hstop = 14, /* Omnivision */ 7868c2ecf20Sopenharmony_ci .vstart = 14, /* Empirically determined */ 7878c2ecf20Sopenharmony_ci .vstop = 494, 7888c2ecf20Sopenharmony_ci .regs = NULL, 7898c2ecf20Sopenharmony_ci } 7908c2ecf20Sopenharmony_ci}; 7918c2ecf20Sopenharmony_ci 7928c2ecf20Sopenharmony_cistatic void ov7675_get_framerate(struct v4l2_subdev *sd, 7938c2ecf20Sopenharmony_ci struct v4l2_fract *tpf) 7948c2ecf20Sopenharmony_ci{ 7958c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 7968c2ecf20Sopenharmony_ci u32 clkrc = info->clkrc; 7978c2ecf20Sopenharmony_ci int pll_factor; 7988c2ecf20Sopenharmony_ci 7998c2ecf20Sopenharmony_ci if (info->pll_bypass) 8008c2ecf20Sopenharmony_ci pll_factor = 1; 8018c2ecf20Sopenharmony_ci else 8028c2ecf20Sopenharmony_ci pll_factor = PLL_FACTOR; 8038c2ecf20Sopenharmony_ci 8048c2ecf20Sopenharmony_ci clkrc++; 8058c2ecf20Sopenharmony_ci if (info->fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8) 8068c2ecf20Sopenharmony_ci clkrc = (clkrc >> 1); 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_ci tpf->numerator = 1; 8098c2ecf20Sopenharmony_ci tpf->denominator = (5 * pll_factor * info->clock_speed) / 8108c2ecf20Sopenharmony_ci (4 * clkrc); 8118c2ecf20Sopenharmony_ci} 8128c2ecf20Sopenharmony_ci 8138c2ecf20Sopenharmony_cistatic int ov7675_apply_framerate(struct v4l2_subdev *sd) 8148c2ecf20Sopenharmony_ci{ 8158c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 8168c2ecf20Sopenharmony_ci int ret; 8178c2ecf20Sopenharmony_ci 8188c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_CLKRC, info->clkrc); 8198c2ecf20Sopenharmony_ci if (ret < 0) 8208c2ecf20Sopenharmony_ci return ret; 8218c2ecf20Sopenharmony_ci 8228c2ecf20Sopenharmony_ci return ov7670_write(sd, REG_DBLV, 8238c2ecf20Sopenharmony_ci info->pll_bypass ? DBLV_BYPASS : DBLV_X4); 8248c2ecf20Sopenharmony_ci} 8258c2ecf20Sopenharmony_ci 8268c2ecf20Sopenharmony_cistatic int ov7675_set_framerate(struct v4l2_subdev *sd, 8278c2ecf20Sopenharmony_ci struct v4l2_fract *tpf) 8288c2ecf20Sopenharmony_ci{ 8298c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 8308c2ecf20Sopenharmony_ci u32 clkrc; 8318c2ecf20Sopenharmony_ci int pll_factor; 8328c2ecf20Sopenharmony_ci 8338c2ecf20Sopenharmony_ci /* 8348c2ecf20Sopenharmony_ci * The formula is fps = 5/4*pixclk for YUV/RGB and 8358c2ecf20Sopenharmony_ci * fps = 5/2*pixclk for RAW. 8368c2ecf20Sopenharmony_ci * 8378c2ecf20Sopenharmony_ci * pixclk = clock_speed / (clkrc + 1) * PLLfactor 8388c2ecf20Sopenharmony_ci * 8398c2ecf20Sopenharmony_ci */ 8408c2ecf20Sopenharmony_ci if (tpf->numerator == 0 || tpf->denominator == 0) { 8418c2ecf20Sopenharmony_ci clkrc = 0; 8428c2ecf20Sopenharmony_ci } else { 8438c2ecf20Sopenharmony_ci pll_factor = info->pll_bypass ? 1 : PLL_FACTOR; 8448c2ecf20Sopenharmony_ci clkrc = (5 * pll_factor * info->clock_speed * tpf->numerator) / 8458c2ecf20Sopenharmony_ci (4 * tpf->denominator); 8468c2ecf20Sopenharmony_ci if (info->fmt->mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8) 8478c2ecf20Sopenharmony_ci clkrc = (clkrc << 1); 8488c2ecf20Sopenharmony_ci clkrc--; 8498c2ecf20Sopenharmony_ci } 8508c2ecf20Sopenharmony_ci 8518c2ecf20Sopenharmony_ci /* 8528c2ecf20Sopenharmony_ci * The datasheet claims that clkrc = 0 will divide the input clock by 1 8538c2ecf20Sopenharmony_ci * but we've checked with an oscilloscope that it divides by 2 instead. 8548c2ecf20Sopenharmony_ci * So, if clkrc = 0 just bypass the divider. 8558c2ecf20Sopenharmony_ci */ 8568c2ecf20Sopenharmony_ci if (clkrc <= 0) 8578c2ecf20Sopenharmony_ci clkrc = CLK_EXT; 8588c2ecf20Sopenharmony_ci else if (clkrc > CLK_SCALE) 8598c2ecf20Sopenharmony_ci clkrc = CLK_SCALE; 8608c2ecf20Sopenharmony_ci info->clkrc = clkrc; 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_ci /* Recalculate frame rate */ 8638c2ecf20Sopenharmony_ci ov7675_get_framerate(sd, tpf); 8648c2ecf20Sopenharmony_ci 8658c2ecf20Sopenharmony_ci /* 8668c2ecf20Sopenharmony_ci * If the device is not powered up by the host driver do 8678c2ecf20Sopenharmony_ci * not apply any changes to H/W at this time. Instead 8688c2ecf20Sopenharmony_ci * the framerate will be restored right after power-up. 8698c2ecf20Sopenharmony_ci */ 8708c2ecf20Sopenharmony_ci if (info->on) 8718c2ecf20Sopenharmony_ci return ov7675_apply_framerate(sd); 8728c2ecf20Sopenharmony_ci 8738c2ecf20Sopenharmony_ci return 0; 8748c2ecf20Sopenharmony_ci} 8758c2ecf20Sopenharmony_ci 8768c2ecf20Sopenharmony_cistatic void ov7670_get_framerate_legacy(struct v4l2_subdev *sd, 8778c2ecf20Sopenharmony_ci struct v4l2_fract *tpf) 8788c2ecf20Sopenharmony_ci{ 8798c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 8808c2ecf20Sopenharmony_ci 8818c2ecf20Sopenharmony_ci tpf->numerator = 1; 8828c2ecf20Sopenharmony_ci tpf->denominator = info->clock_speed; 8838c2ecf20Sopenharmony_ci if ((info->clkrc & CLK_EXT) == 0 && (info->clkrc & CLK_SCALE) > 1) 8848c2ecf20Sopenharmony_ci tpf->denominator /= (info->clkrc & CLK_SCALE); 8858c2ecf20Sopenharmony_ci} 8868c2ecf20Sopenharmony_ci 8878c2ecf20Sopenharmony_cistatic int ov7670_set_framerate_legacy(struct v4l2_subdev *sd, 8888c2ecf20Sopenharmony_ci struct v4l2_fract *tpf) 8898c2ecf20Sopenharmony_ci{ 8908c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 8918c2ecf20Sopenharmony_ci int div; 8928c2ecf20Sopenharmony_ci 8938c2ecf20Sopenharmony_ci if (tpf->numerator == 0 || tpf->denominator == 0) 8948c2ecf20Sopenharmony_ci div = 1; /* Reset to full rate */ 8958c2ecf20Sopenharmony_ci else 8968c2ecf20Sopenharmony_ci div = (tpf->numerator * info->clock_speed) / tpf->denominator; 8978c2ecf20Sopenharmony_ci if (div == 0) 8988c2ecf20Sopenharmony_ci div = 1; 8998c2ecf20Sopenharmony_ci else if (div > CLK_SCALE) 9008c2ecf20Sopenharmony_ci div = CLK_SCALE; 9018c2ecf20Sopenharmony_ci info->clkrc = (info->clkrc & 0x80) | div; 9028c2ecf20Sopenharmony_ci tpf->numerator = 1; 9038c2ecf20Sopenharmony_ci tpf->denominator = info->clock_speed / div; 9048c2ecf20Sopenharmony_ci 9058c2ecf20Sopenharmony_ci /* 9068c2ecf20Sopenharmony_ci * If the device is not powered up by the host driver do 9078c2ecf20Sopenharmony_ci * not apply any changes to H/W at this time. Instead 9088c2ecf20Sopenharmony_ci * the framerate will be restored right after power-up. 9098c2ecf20Sopenharmony_ci */ 9108c2ecf20Sopenharmony_ci if (info->on) 9118c2ecf20Sopenharmony_ci return ov7670_write(sd, REG_CLKRC, info->clkrc); 9128c2ecf20Sopenharmony_ci 9138c2ecf20Sopenharmony_ci return 0; 9148c2ecf20Sopenharmony_ci} 9158c2ecf20Sopenharmony_ci 9168c2ecf20Sopenharmony_ci/* 9178c2ecf20Sopenharmony_ci * Store a set of start/stop values into the camera. 9188c2ecf20Sopenharmony_ci */ 9198c2ecf20Sopenharmony_cistatic int ov7670_set_hw(struct v4l2_subdev *sd, int hstart, int hstop, 9208c2ecf20Sopenharmony_ci int vstart, int vstop) 9218c2ecf20Sopenharmony_ci{ 9228c2ecf20Sopenharmony_ci int ret; 9238c2ecf20Sopenharmony_ci unsigned char v; 9248c2ecf20Sopenharmony_ci/* 9258c2ecf20Sopenharmony_ci * Horizontal: 11 bits, top 8 live in hstart and hstop. Bottom 3 of 9268c2ecf20Sopenharmony_ci * hstart are in href[2:0], bottom 3 of hstop in href[5:3]. There is 9278c2ecf20Sopenharmony_ci * a mystery "edge offset" value in the top two bits of href. 9288c2ecf20Sopenharmony_ci */ 9298c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_HSTART, (hstart >> 3) & 0xff); 9308c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_HSTOP, (hstop >> 3) & 0xff); 9318c2ecf20Sopenharmony_ci ret += ov7670_read(sd, REG_HREF, &v); 9328c2ecf20Sopenharmony_ci v = (v & 0xc0) | ((hstop & 0x7) << 3) | (hstart & 0x7); 9338c2ecf20Sopenharmony_ci msleep(10); 9348c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_HREF, v); 9358c2ecf20Sopenharmony_ci/* 9368c2ecf20Sopenharmony_ci * Vertical: similar arrangement, but only 10 bits. 9378c2ecf20Sopenharmony_ci */ 9388c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_VSTART, (vstart >> 2) & 0xff); 9398c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_VSTOP, (vstop >> 2) & 0xff); 9408c2ecf20Sopenharmony_ci ret += ov7670_read(sd, REG_VREF, &v); 9418c2ecf20Sopenharmony_ci v = (v & 0xf0) | ((vstop & 0x3) << 2) | (vstart & 0x3); 9428c2ecf20Sopenharmony_ci msleep(10); 9438c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_VREF, v); 9448c2ecf20Sopenharmony_ci return ret; 9458c2ecf20Sopenharmony_ci} 9468c2ecf20Sopenharmony_ci 9478c2ecf20Sopenharmony_ci 9488c2ecf20Sopenharmony_cistatic int ov7670_enum_mbus_code(struct v4l2_subdev *sd, 9498c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 9508c2ecf20Sopenharmony_ci struct v4l2_subdev_mbus_code_enum *code) 9518c2ecf20Sopenharmony_ci{ 9528c2ecf20Sopenharmony_ci if (code->pad || code->index >= N_OV7670_FMTS) 9538c2ecf20Sopenharmony_ci return -EINVAL; 9548c2ecf20Sopenharmony_ci 9558c2ecf20Sopenharmony_ci code->code = ov7670_formats[code->index].mbus_code; 9568c2ecf20Sopenharmony_ci return 0; 9578c2ecf20Sopenharmony_ci} 9588c2ecf20Sopenharmony_ci 9598c2ecf20Sopenharmony_cistatic int ov7670_try_fmt_internal(struct v4l2_subdev *sd, 9608c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *fmt, 9618c2ecf20Sopenharmony_ci struct ov7670_format_struct **ret_fmt, 9628c2ecf20Sopenharmony_ci struct ov7670_win_size **ret_wsize) 9638c2ecf20Sopenharmony_ci{ 9648c2ecf20Sopenharmony_ci int index, i; 9658c2ecf20Sopenharmony_ci struct ov7670_win_size *wsize; 9668c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 9678c2ecf20Sopenharmony_ci unsigned int n_win_sizes = info->devtype->n_win_sizes; 9688c2ecf20Sopenharmony_ci unsigned int win_sizes_limit = n_win_sizes; 9698c2ecf20Sopenharmony_ci 9708c2ecf20Sopenharmony_ci for (index = 0; index < N_OV7670_FMTS; index++) 9718c2ecf20Sopenharmony_ci if (ov7670_formats[index].mbus_code == fmt->code) 9728c2ecf20Sopenharmony_ci break; 9738c2ecf20Sopenharmony_ci if (index >= N_OV7670_FMTS) { 9748c2ecf20Sopenharmony_ci /* default to first format */ 9758c2ecf20Sopenharmony_ci index = 0; 9768c2ecf20Sopenharmony_ci fmt->code = ov7670_formats[0].mbus_code; 9778c2ecf20Sopenharmony_ci } 9788c2ecf20Sopenharmony_ci if (ret_fmt != NULL) 9798c2ecf20Sopenharmony_ci *ret_fmt = ov7670_formats + index; 9808c2ecf20Sopenharmony_ci /* 9818c2ecf20Sopenharmony_ci * Fields: the OV devices claim to be progressive. 9828c2ecf20Sopenharmony_ci */ 9838c2ecf20Sopenharmony_ci fmt->field = V4L2_FIELD_NONE; 9848c2ecf20Sopenharmony_ci 9858c2ecf20Sopenharmony_ci /* 9868c2ecf20Sopenharmony_ci * Don't consider values that don't match min_height and min_width 9878c2ecf20Sopenharmony_ci * constraints. 9888c2ecf20Sopenharmony_ci */ 9898c2ecf20Sopenharmony_ci if (info->min_width || info->min_height) 9908c2ecf20Sopenharmony_ci for (i = 0; i < n_win_sizes; i++) { 9918c2ecf20Sopenharmony_ci wsize = info->devtype->win_sizes + i; 9928c2ecf20Sopenharmony_ci 9938c2ecf20Sopenharmony_ci if (wsize->width < info->min_width || 9948c2ecf20Sopenharmony_ci wsize->height < info->min_height) { 9958c2ecf20Sopenharmony_ci win_sizes_limit = i; 9968c2ecf20Sopenharmony_ci break; 9978c2ecf20Sopenharmony_ci } 9988c2ecf20Sopenharmony_ci } 9998c2ecf20Sopenharmony_ci /* 10008c2ecf20Sopenharmony_ci * Round requested image size down to the nearest 10018c2ecf20Sopenharmony_ci * we support, but not below the smallest. 10028c2ecf20Sopenharmony_ci */ 10038c2ecf20Sopenharmony_ci for (wsize = info->devtype->win_sizes; 10048c2ecf20Sopenharmony_ci wsize < info->devtype->win_sizes + win_sizes_limit; wsize++) 10058c2ecf20Sopenharmony_ci if (fmt->width >= wsize->width && fmt->height >= wsize->height) 10068c2ecf20Sopenharmony_ci break; 10078c2ecf20Sopenharmony_ci if (wsize >= info->devtype->win_sizes + win_sizes_limit) 10088c2ecf20Sopenharmony_ci wsize--; /* Take the smallest one */ 10098c2ecf20Sopenharmony_ci if (ret_wsize != NULL) 10108c2ecf20Sopenharmony_ci *ret_wsize = wsize; 10118c2ecf20Sopenharmony_ci /* 10128c2ecf20Sopenharmony_ci * Note the size we'll actually handle. 10138c2ecf20Sopenharmony_ci */ 10148c2ecf20Sopenharmony_ci fmt->width = wsize->width; 10158c2ecf20Sopenharmony_ci fmt->height = wsize->height; 10168c2ecf20Sopenharmony_ci fmt->colorspace = ov7670_formats[index].colorspace; 10178c2ecf20Sopenharmony_ci 10188c2ecf20Sopenharmony_ci info->format = *fmt; 10198c2ecf20Sopenharmony_ci 10208c2ecf20Sopenharmony_ci return 0; 10218c2ecf20Sopenharmony_ci} 10228c2ecf20Sopenharmony_ci 10238c2ecf20Sopenharmony_cistatic int ov7670_apply_fmt(struct v4l2_subdev *sd) 10248c2ecf20Sopenharmony_ci{ 10258c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 10268c2ecf20Sopenharmony_ci struct ov7670_win_size *wsize = info->wsize; 10278c2ecf20Sopenharmony_ci unsigned char com7, com10 = 0; 10288c2ecf20Sopenharmony_ci int ret; 10298c2ecf20Sopenharmony_ci 10308c2ecf20Sopenharmony_ci /* 10318c2ecf20Sopenharmony_ci * COM7 is a pain in the ass, it doesn't like to be read then 10328c2ecf20Sopenharmony_ci * quickly written afterward. But we have everything we need 10338c2ecf20Sopenharmony_ci * to set it absolutely here, as long as the format-specific 10348c2ecf20Sopenharmony_ci * register sets list it first. 10358c2ecf20Sopenharmony_ci */ 10368c2ecf20Sopenharmony_ci com7 = info->fmt->regs[0].value; 10378c2ecf20Sopenharmony_ci com7 |= wsize->com7_bit; 10388c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_COM7, com7); 10398c2ecf20Sopenharmony_ci if (ret) 10408c2ecf20Sopenharmony_ci return ret; 10418c2ecf20Sopenharmony_ci 10428c2ecf20Sopenharmony_ci /* 10438c2ecf20Sopenharmony_ci * Configure the media bus through COM10 register 10448c2ecf20Sopenharmony_ci */ 10458c2ecf20Sopenharmony_ci if (info->mbus_config & V4L2_MBUS_VSYNC_ACTIVE_LOW) 10468c2ecf20Sopenharmony_ci com10 |= COM10_VS_NEG; 10478c2ecf20Sopenharmony_ci if (info->mbus_config & V4L2_MBUS_HSYNC_ACTIVE_LOW) 10488c2ecf20Sopenharmony_ci com10 |= COM10_HREF_REV; 10498c2ecf20Sopenharmony_ci if (info->pclk_hb_disable) 10508c2ecf20Sopenharmony_ci com10 |= COM10_PCLK_HB; 10518c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_COM10, com10); 10528c2ecf20Sopenharmony_ci if (ret) 10538c2ecf20Sopenharmony_ci return ret; 10548c2ecf20Sopenharmony_ci 10558c2ecf20Sopenharmony_ci /* 10568c2ecf20Sopenharmony_ci * Now write the rest of the array. Also store start/stops 10578c2ecf20Sopenharmony_ci */ 10588c2ecf20Sopenharmony_ci ret = ov7670_write_array(sd, info->fmt->regs + 1); 10598c2ecf20Sopenharmony_ci if (ret) 10608c2ecf20Sopenharmony_ci return ret; 10618c2ecf20Sopenharmony_ci 10628c2ecf20Sopenharmony_ci ret = ov7670_set_hw(sd, wsize->hstart, wsize->hstop, wsize->vstart, 10638c2ecf20Sopenharmony_ci wsize->vstop); 10648c2ecf20Sopenharmony_ci if (ret) 10658c2ecf20Sopenharmony_ci return ret; 10668c2ecf20Sopenharmony_ci 10678c2ecf20Sopenharmony_ci if (wsize->regs) { 10688c2ecf20Sopenharmony_ci ret = ov7670_write_array(sd, wsize->regs); 10698c2ecf20Sopenharmony_ci if (ret) 10708c2ecf20Sopenharmony_ci return ret; 10718c2ecf20Sopenharmony_ci } 10728c2ecf20Sopenharmony_ci 10738c2ecf20Sopenharmony_ci /* 10748c2ecf20Sopenharmony_ci * If we're running RGB565, we must rewrite clkrc after setting 10758c2ecf20Sopenharmony_ci * the other parameters or the image looks poor. If we're *not* 10768c2ecf20Sopenharmony_ci * doing RGB565, we must not rewrite clkrc or the image looks 10778c2ecf20Sopenharmony_ci * *really* poor. 10788c2ecf20Sopenharmony_ci * 10798c2ecf20Sopenharmony_ci * (Update) Now that we retain clkrc state, we should be able 10808c2ecf20Sopenharmony_ci * to write it unconditionally, and that will make the frame 10818c2ecf20Sopenharmony_ci * rate persistent too. 10828c2ecf20Sopenharmony_ci */ 10838c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_CLKRC, info->clkrc); 10848c2ecf20Sopenharmony_ci if (ret) 10858c2ecf20Sopenharmony_ci return ret; 10868c2ecf20Sopenharmony_ci 10878c2ecf20Sopenharmony_ci return 0; 10888c2ecf20Sopenharmony_ci} 10898c2ecf20Sopenharmony_ci 10908c2ecf20Sopenharmony_ci/* 10918c2ecf20Sopenharmony_ci * Set a format. 10928c2ecf20Sopenharmony_ci */ 10938c2ecf20Sopenharmony_cistatic int ov7670_set_fmt(struct v4l2_subdev *sd, 10948c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 10958c2ecf20Sopenharmony_ci struct v4l2_subdev_format *format) 10968c2ecf20Sopenharmony_ci{ 10978c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 10988c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 10998c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *mbus_fmt; 11008c2ecf20Sopenharmony_ci#endif 11018c2ecf20Sopenharmony_ci int ret; 11028c2ecf20Sopenharmony_ci 11038c2ecf20Sopenharmony_ci if (format->pad) 11048c2ecf20Sopenharmony_ci return -EINVAL; 11058c2ecf20Sopenharmony_ci 11068c2ecf20Sopenharmony_ci if (format->which == V4L2_SUBDEV_FORMAT_TRY) { 11078c2ecf20Sopenharmony_ci ret = ov7670_try_fmt_internal(sd, &format->format, NULL, NULL); 11088c2ecf20Sopenharmony_ci if (ret) 11098c2ecf20Sopenharmony_ci return ret; 11108c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 11118c2ecf20Sopenharmony_ci mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad); 11128c2ecf20Sopenharmony_ci *mbus_fmt = format->format; 11138c2ecf20Sopenharmony_ci#endif 11148c2ecf20Sopenharmony_ci return 0; 11158c2ecf20Sopenharmony_ci } 11168c2ecf20Sopenharmony_ci 11178c2ecf20Sopenharmony_ci ret = ov7670_try_fmt_internal(sd, &format->format, &info->fmt, &info->wsize); 11188c2ecf20Sopenharmony_ci if (ret) 11198c2ecf20Sopenharmony_ci return ret; 11208c2ecf20Sopenharmony_ci 11218c2ecf20Sopenharmony_ci /* 11228c2ecf20Sopenharmony_ci * If the device is not powered up by the host driver do 11238c2ecf20Sopenharmony_ci * not apply any changes to H/W at this time. Instead 11248c2ecf20Sopenharmony_ci * the frame format will be restored right after power-up. 11258c2ecf20Sopenharmony_ci */ 11268c2ecf20Sopenharmony_ci if (info->on) 11278c2ecf20Sopenharmony_ci return ov7670_apply_fmt(sd); 11288c2ecf20Sopenharmony_ci 11298c2ecf20Sopenharmony_ci return 0; 11308c2ecf20Sopenharmony_ci} 11318c2ecf20Sopenharmony_ci 11328c2ecf20Sopenharmony_cistatic int ov7670_get_fmt(struct v4l2_subdev *sd, 11338c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 11348c2ecf20Sopenharmony_ci struct v4l2_subdev_format *format) 11358c2ecf20Sopenharmony_ci{ 11368c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 11378c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 11388c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *mbus_fmt; 11398c2ecf20Sopenharmony_ci#endif 11408c2ecf20Sopenharmony_ci 11418c2ecf20Sopenharmony_ci if (format->which == V4L2_SUBDEV_FORMAT_TRY) { 11428c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 11438c2ecf20Sopenharmony_ci mbus_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); 11448c2ecf20Sopenharmony_ci format->format = *mbus_fmt; 11458c2ecf20Sopenharmony_ci return 0; 11468c2ecf20Sopenharmony_ci#else 11478c2ecf20Sopenharmony_ci return -EINVAL; 11488c2ecf20Sopenharmony_ci#endif 11498c2ecf20Sopenharmony_ci } else { 11508c2ecf20Sopenharmony_ci format->format = info->format; 11518c2ecf20Sopenharmony_ci } 11528c2ecf20Sopenharmony_ci 11538c2ecf20Sopenharmony_ci return 0; 11548c2ecf20Sopenharmony_ci} 11558c2ecf20Sopenharmony_ci 11568c2ecf20Sopenharmony_ci/* 11578c2ecf20Sopenharmony_ci * Implement G/S_PARM. There is a "high quality" mode we could try 11588c2ecf20Sopenharmony_ci * to do someday; for now, we just do the frame rate tweak. 11598c2ecf20Sopenharmony_ci */ 11608c2ecf20Sopenharmony_cistatic int ov7670_g_frame_interval(struct v4l2_subdev *sd, 11618c2ecf20Sopenharmony_ci struct v4l2_subdev_frame_interval *ival) 11628c2ecf20Sopenharmony_ci{ 11638c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 11648c2ecf20Sopenharmony_ci 11658c2ecf20Sopenharmony_ci 11668c2ecf20Sopenharmony_ci info->devtype->get_framerate(sd, &ival->interval); 11678c2ecf20Sopenharmony_ci 11688c2ecf20Sopenharmony_ci return 0; 11698c2ecf20Sopenharmony_ci} 11708c2ecf20Sopenharmony_ci 11718c2ecf20Sopenharmony_cistatic int ov7670_s_frame_interval(struct v4l2_subdev *sd, 11728c2ecf20Sopenharmony_ci struct v4l2_subdev_frame_interval *ival) 11738c2ecf20Sopenharmony_ci{ 11748c2ecf20Sopenharmony_ci struct v4l2_fract *tpf = &ival->interval; 11758c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 11768c2ecf20Sopenharmony_ci 11778c2ecf20Sopenharmony_ci 11788c2ecf20Sopenharmony_ci return info->devtype->set_framerate(sd, tpf); 11798c2ecf20Sopenharmony_ci} 11808c2ecf20Sopenharmony_ci 11818c2ecf20Sopenharmony_ci 11828c2ecf20Sopenharmony_ci/* 11838c2ecf20Sopenharmony_ci * Frame intervals. Since frame rates are controlled with the clock 11848c2ecf20Sopenharmony_ci * divider, we can only do 30/n for integer n values. So no continuous 11858c2ecf20Sopenharmony_ci * or stepwise options. Here we just pick a handful of logical values. 11868c2ecf20Sopenharmony_ci */ 11878c2ecf20Sopenharmony_ci 11888c2ecf20Sopenharmony_cistatic int ov7670_frame_rates[] = { 30, 15, 10, 5, 1 }; 11898c2ecf20Sopenharmony_ci 11908c2ecf20Sopenharmony_cistatic int ov7670_enum_frame_interval(struct v4l2_subdev *sd, 11918c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 11928c2ecf20Sopenharmony_ci struct v4l2_subdev_frame_interval_enum *fie) 11938c2ecf20Sopenharmony_ci{ 11948c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 11958c2ecf20Sopenharmony_ci unsigned int n_win_sizes = info->devtype->n_win_sizes; 11968c2ecf20Sopenharmony_ci int i; 11978c2ecf20Sopenharmony_ci 11988c2ecf20Sopenharmony_ci if (fie->pad) 11998c2ecf20Sopenharmony_ci return -EINVAL; 12008c2ecf20Sopenharmony_ci if (fie->index >= ARRAY_SIZE(ov7670_frame_rates)) 12018c2ecf20Sopenharmony_ci return -EINVAL; 12028c2ecf20Sopenharmony_ci 12038c2ecf20Sopenharmony_ci /* 12048c2ecf20Sopenharmony_ci * Check if the width/height is valid. 12058c2ecf20Sopenharmony_ci * 12068c2ecf20Sopenharmony_ci * If a minimum width/height was requested, filter out the capture 12078c2ecf20Sopenharmony_ci * windows that fall outside that. 12088c2ecf20Sopenharmony_ci */ 12098c2ecf20Sopenharmony_ci for (i = 0; i < n_win_sizes; i++) { 12108c2ecf20Sopenharmony_ci struct ov7670_win_size *win = &info->devtype->win_sizes[i]; 12118c2ecf20Sopenharmony_ci 12128c2ecf20Sopenharmony_ci if (info->min_width && win->width < info->min_width) 12138c2ecf20Sopenharmony_ci continue; 12148c2ecf20Sopenharmony_ci if (info->min_height && win->height < info->min_height) 12158c2ecf20Sopenharmony_ci continue; 12168c2ecf20Sopenharmony_ci if (fie->width == win->width && fie->height == win->height) 12178c2ecf20Sopenharmony_ci break; 12188c2ecf20Sopenharmony_ci } 12198c2ecf20Sopenharmony_ci if (i == n_win_sizes) 12208c2ecf20Sopenharmony_ci return -EINVAL; 12218c2ecf20Sopenharmony_ci fie->interval.numerator = 1; 12228c2ecf20Sopenharmony_ci fie->interval.denominator = ov7670_frame_rates[fie->index]; 12238c2ecf20Sopenharmony_ci return 0; 12248c2ecf20Sopenharmony_ci} 12258c2ecf20Sopenharmony_ci 12268c2ecf20Sopenharmony_ci/* 12278c2ecf20Sopenharmony_ci * Frame size enumeration 12288c2ecf20Sopenharmony_ci */ 12298c2ecf20Sopenharmony_cistatic int ov7670_enum_frame_size(struct v4l2_subdev *sd, 12308c2ecf20Sopenharmony_ci struct v4l2_subdev_pad_config *cfg, 12318c2ecf20Sopenharmony_ci struct v4l2_subdev_frame_size_enum *fse) 12328c2ecf20Sopenharmony_ci{ 12338c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 12348c2ecf20Sopenharmony_ci int i; 12358c2ecf20Sopenharmony_ci int num_valid = -1; 12368c2ecf20Sopenharmony_ci __u32 index = fse->index; 12378c2ecf20Sopenharmony_ci unsigned int n_win_sizes = info->devtype->n_win_sizes; 12388c2ecf20Sopenharmony_ci 12398c2ecf20Sopenharmony_ci if (fse->pad) 12408c2ecf20Sopenharmony_ci return -EINVAL; 12418c2ecf20Sopenharmony_ci 12428c2ecf20Sopenharmony_ci /* 12438c2ecf20Sopenharmony_ci * If a minimum width/height was requested, filter out the capture 12448c2ecf20Sopenharmony_ci * windows that fall outside that. 12458c2ecf20Sopenharmony_ci */ 12468c2ecf20Sopenharmony_ci for (i = 0; i < n_win_sizes; i++) { 12478c2ecf20Sopenharmony_ci struct ov7670_win_size *win = &info->devtype->win_sizes[i]; 12488c2ecf20Sopenharmony_ci if (info->min_width && win->width < info->min_width) 12498c2ecf20Sopenharmony_ci continue; 12508c2ecf20Sopenharmony_ci if (info->min_height && win->height < info->min_height) 12518c2ecf20Sopenharmony_ci continue; 12528c2ecf20Sopenharmony_ci if (index == ++num_valid) { 12538c2ecf20Sopenharmony_ci fse->min_width = fse->max_width = win->width; 12548c2ecf20Sopenharmony_ci fse->min_height = fse->max_height = win->height; 12558c2ecf20Sopenharmony_ci return 0; 12568c2ecf20Sopenharmony_ci } 12578c2ecf20Sopenharmony_ci } 12588c2ecf20Sopenharmony_ci 12598c2ecf20Sopenharmony_ci return -EINVAL; 12608c2ecf20Sopenharmony_ci} 12618c2ecf20Sopenharmony_ci 12628c2ecf20Sopenharmony_ci/* 12638c2ecf20Sopenharmony_ci * Code for dealing with controls. 12648c2ecf20Sopenharmony_ci */ 12658c2ecf20Sopenharmony_ci 12668c2ecf20Sopenharmony_cistatic int ov7670_store_cmatrix(struct v4l2_subdev *sd, 12678c2ecf20Sopenharmony_ci int matrix[CMATRIX_LEN]) 12688c2ecf20Sopenharmony_ci{ 12698c2ecf20Sopenharmony_ci int i, ret; 12708c2ecf20Sopenharmony_ci unsigned char signbits = 0; 12718c2ecf20Sopenharmony_ci 12728c2ecf20Sopenharmony_ci /* 12738c2ecf20Sopenharmony_ci * Weird crap seems to exist in the upper part of 12748c2ecf20Sopenharmony_ci * the sign bits register, so let's preserve it. 12758c2ecf20Sopenharmony_ci */ 12768c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_CMATRIX_SIGN, &signbits); 12778c2ecf20Sopenharmony_ci signbits &= 0xc0; 12788c2ecf20Sopenharmony_ci 12798c2ecf20Sopenharmony_ci for (i = 0; i < CMATRIX_LEN; i++) { 12808c2ecf20Sopenharmony_ci unsigned char raw; 12818c2ecf20Sopenharmony_ci 12828c2ecf20Sopenharmony_ci if (matrix[i] < 0) { 12838c2ecf20Sopenharmony_ci signbits |= (1 << i); 12848c2ecf20Sopenharmony_ci if (matrix[i] < -255) 12858c2ecf20Sopenharmony_ci raw = 0xff; 12868c2ecf20Sopenharmony_ci else 12878c2ecf20Sopenharmony_ci raw = (-1 * matrix[i]) & 0xff; 12888c2ecf20Sopenharmony_ci } 12898c2ecf20Sopenharmony_ci else { 12908c2ecf20Sopenharmony_ci if (matrix[i] > 255) 12918c2ecf20Sopenharmony_ci raw = 0xff; 12928c2ecf20Sopenharmony_ci else 12938c2ecf20Sopenharmony_ci raw = matrix[i] & 0xff; 12948c2ecf20Sopenharmony_ci } 12958c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_CMATRIX_BASE + i, raw); 12968c2ecf20Sopenharmony_ci } 12978c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_CMATRIX_SIGN, signbits); 12988c2ecf20Sopenharmony_ci return ret; 12998c2ecf20Sopenharmony_ci} 13008c2ecf20Sopenharmony_ci 13018c2ecf20Sopenharmony_ci 13028c2ecf20Sopenharmony_ci/* 13038c2ecf20Sopenharmony_ci * Hue also requires messing with the color matrix. It also requires 13048c2ecf20Sopenharmony_ci * trig functions, which tend not to be well supported in the kernel. 13058c2ecf20Sopenharmony_ci * So here is a simple table of sine values, 0-90 degrees, in steps 13068c2ecf20Sopenharmony_ci * of five degrees. Values are multiplied by 1000. 13078c2ecf20Sopenharmony_ci * 13088c2ecf20Sopenharmony_ci * The following naive approximate trig functions require an argument 13098c2ecf20Sopenharmony_ci * carefully limited to -180 <= theta <= 180. 13108c2ecf20Sopenharmony_ci */ 13118c2ecf20Sopenharmony_ci#define SIN_STEP 5 13128c2ecf20Sopenharmony_cistatic const int ov7670_sin_table[] = { 13138c2ecf20Sopenharmony_ci 0, 87, 173, 258, 342, 422, 13148c2ecf20Sopenharmony_ci 499, 573, 642, 707, 766, 819, 13158c2ecf20Sopenharmony_ci 866, 906, 939, 965, 984, 996, 13168c2ecf20Sopenharmony_ci 1000 13178c2ecf20Sopenharmony_ci}; 13188c2ecf20Sopenharmony_ci 13198c2ecf20Sopenharmony_cistatic int ov7670_sine(int theta) 13208c2ecf20Sopenharmony_ci{ 13218c2ecf20Sopenharmony_ci int chs = 1; 13228c2ecf20Sopenharmony_ci int sine; 13238c2ecf20Sopenharmony_ci 13248c2ecf20Sopenharmony_ci if (theta < 0) { 13258c2ecf20Sopenharmony_ci theta = -theta; 13268c2ecf20Sopenharmony_ci chs = -1; 13278c2ecf20Sopenharmony_ci } 13288c2ecf20Sopenharmony_ci if (theta <= 90) 13298c2ecf20Sopenharmony_ci sine = ov7670_sin_table[theta/SIN_STEP]; 13308c2ecf20Sopenharmony_ci else { 13318c2ecf20Sopenharmony_ci theta -= 90; 13328c2ecf20Sopenharmony_ci sine = 1000 - ov7670_sin_table[theta/SIN_STEP]; 13338c2ecf20Sopenharmony_ci } 13348c2ecf20Sopenharmony_ci return sine*chs; 13358c2ecf20Sopenharmony_ci} 13368c2ecf20Sopenharmony_ci 13378c2ecf20Sopenharmony_cistatic int ov7670_cosine(int theta) 13388c2ecf20Sopenharmony_ci{ 13398c2ecf20Sopenharmony_ci theta = 90 - theta; 13408c2ecf20Sopenharmony_ci if (theta > 180) 13418c2ecf20Sopenharmony_ci theta -= 360; 13428c2ecf20Sopenharmony_ci else if (theta < -180) 13438c2ecf20Sopenharmony_ci theta += 360; 13448c2ecf20Sopenharmony_ci return ov7670_sine(theta); 13458c2ecf20Sopenharmony_ci} 13468c2ecf20Sopenharmony_ci 13478c2ecf20Sopenharmony_ci 13488c2ecf20Sopenharmony_ci 13498c2ecf20Sopenharmony_ci 13508c2ecf20Sopenharmony_cistatic void ov7670_calc_cmatrix(struct ov7670_info *info, 13518c2ecf20Sopenharmony_ci int matrix[CMATRIX_LEN], int sat, int hue) 13528c2ecf20Sopenharmony_ci{ 13538c2ecf20Sopenharmony_ci int i; 13548c2ecf20Sopenharmony_ci /* 13558c2ecf20Sopenharmony_ci * Apply the current saturation setting first. 13568c2ecf20Sopenharmony_ci */ 13578c2ecf20Sopenharmony_ci for (i = 0; i < CMATRIX_LEN; i++) 13588c2ecf20Sopenharmony_ci matrix[i] = (info->fmt->cmatrix[i] * sat) >> 7; 13598c2ecf20Sopenharmony_ci /* 13608c2ecf20Sopenharmony_ci * Then, if need be, rotate the hue value. 13618c2ecf20Sopenharmony_ci */ 13628c2ecf20Sopenharmony_ci if (hue != 0) { 13638c2ecf20Sopenharmony_ci int sinth, costh, tmpmatrix[CMATRIX_LEN]; 13648c2ecf20Sopenharmony_ci 13658c2ecf20Sopenharmony_ci memcpy(tmpmatrix, matrix, CMATRIX_LEN*sizeof(int)); 13668c2ecf20Sopenharmony_ci sinth = ov7670_sine(hue); 13678c2ecf20Sopenharmony_ci costh = ov7670_cosine(hue); 13688c2ecf20Sopenharmony_ci 13698c2ecf20Sopenharmony_ci matrix[0] = (matrix[3]*sinth + matrix[0]*costh)/1000; 13708c2ecf20Sopenharmony_ci matrix[1] = (matrix[4]*sinth + matrix[1]*costh)/1000; 13718c2ecf20Sopenharmony_ci matrix[2] = (matrix[5]*sinth + matrix[2]*costh)/1000; 13728c2ecf20Sopenharmony_ci matrix[3] = (matrix[3]*costh - matrix[0]*sinth)/1000; 13738c2ecf20Sopenharmony_ci matrix[4] = (matrix[4]*costh - matrix[1]*sinth)/1000; 13748c2ecf20Sopenharmony_ci matrix[5] = (matrix[5]*costh - matrix[2]*sinth)/1000; 13758c2ecf20Sopenharmony_ci } 13768c2ecf20Sopenharmony_ci} 13778c2ecf20Sopenharmony_ci 13788c2ecf20Sopenharmony_ci 13798c2ecf20Sopenharmony_ci 13808c2ecf20Sopenharmony_cistatic int ov7670_s_sat_hue(struct v4l2_subdev *sd, int sat, int hue) 13818c2ecf20Sopenharmony_ci{ 13828c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 13838c2ecf20Sopenharmony_ci int matrix[CMATRIX_LEN]; 13848c2ecf20Sopenharmony_ci int ret; 13858c2ecf20Sopenharmony_ci 13868c2ecf20Sopenharmony_ci ov7670_calc_cmatrix(info, matrix, sat, hue); 13878c2ecf20Sopenharmony_ci ret = ov7670_store_cmatrix(sd, matrix); 13888c2ecf20Sopenharmony_ci return ret; 13898c2ecf20Sopenharmony_ci} 13908c2ecf20Sopenharmony_ci 13918c2ecf20Sopenharmony_ci 13928c2ecf20Sopenharmony_ci/* 13938c2ecf20Sopenharmony_ci * Some weird registers seem to store values in a sign/magnitude format! 13948c2ecf20Sopenharmony_ci */ 13958c2ecf20Sopenharmony_ci 13968c2ecf20Sopenharmony_cistatic unsigned char ov7670_abs_to_sm(unsigned char v) 13978c2ecf20Sopenharmony_ci{ 13988c2ecf20Sopenharmony_ci if (v > 127) 13998c2ecf20Sopenharmony_ci return v & 0x7f; 14008c2ecf20Sopenharmony_ci return (128 - v) | 0x80; 14018c2ecf20Sopenharmony_ci} 14028c2ecf20Sopenharmony_ci 14038c2ecf20Sopenharmony_cistatic int ov7670_s_brightness(struct v4l2_subdev *sd, int value) 14048c2ecf20Sopenharmony_ci{ 14058c2ecf20Sopenharmony_ci unsigned char com8 = 0, v; 14068c2ecf20Sopenharmony_ci int ret; 14078c2ecf20Sopenharmony_ci 14088c2ecf20Sopenharmony_ci ov7670_read(sd, REG_COM8, &com8); 14098c2ecf20Sopenharmony_ci com8 &= ~COM8_AEC; 14108c2ecf20Sopenharmony_ci ov7670_write(sd, REG_COM8, com8); 14118c2ecf20Sopenharmony_ci v = ov7670_abs_to_sm(value); 14128c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_BRIGHT, v); 14138c2ecf20Sopenharmony_ci return ret; 14148c2ecf20Sopenharmony_ci} 14158c2ecf20Sopenharmony_ci 14168c2ecf20Sopenharmony_cistatic int ov7670_s_contrast(struct v4l2_subdev *sd, int value) 14178c2ecf20Sopenharmony_ci{ 14188c2ecf20Sopenharmony_ci return ov7670_write(sd, REG_CONTRAS, (unsigned char) value); 14198c2ecf20Sopenharmony_ci} 14208c2ecf20Sopenharmony_ci 14218c2ecf20Sopenharmony_cistatic int ov7670_s_hflip(struct v4l2_subdev *sd, int value) 14228c2ecf20Sopenharmony_ci{ 14238c2ecf20Sopenharmony_ci unsigned char v = 0; 14248c2ecf20Sopenharmony_ci int ret; 14258c2ecf20Sopenharmony_ci 14268c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_MVFP, &v); 14278c2ecf20Sopenharmony_ci if (value) 14288c2ecf20Sopenharmony_ci v |= MVFP_MIRROR; 14298c2ecf20Sopenharmony_ci else 14308c2ecf20Sopenharmony_ci v &= ~MVFP_MIRROR; 14318c2ecf20Sopenharmony_ci msleep(10); /* FIXME */ 14328c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_MVFP, v); 14338c2ecf20Sopenharmony_ci return ret; 14348c2ecf20Sopenharmony_ci} 14358c2ecf20Sopenharmony_ci 14368c2ecf20Sopenharmony_cistatic int ov7670_s_vflip(struct v4l2_subdev *sd, int value) 14378c2ecf20Sopenharmony_ci{ 14388c2ecf20Sopenharmony_ci unsigned char v = 0; 14398c2ecf20Sopenharmony_ci int ret; 14408c2ecf20Sopenharmony_ci 14418c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_MVFP, &v); 14428c2ecf20Sopenharmony_ci if (value) 14438c2ecf20Sopenharmony_ci v |= MVFP_FLIP; 14448c2ecf20Sopenharmony_ci else 14458c2ecf20Sopenharmony_ci v &= ~MVFP_FLIP; 14468c2ecf20Sopenharmony_ci msleep(10); /* FIXME */ 14478c2ecf20Sopenharmony_ci ret += ov7670_write(sd, REG_MVFP, v); 14488c2ecf20Sopenharmony_ci return ret; 14498c2ecf20Sopenharmony_ci} 14508c2ecf20Sopenharmony_ci 14518c2ecf20Sopenharmony_ci/* 14528c2ecf20Sopenharmony_ci * GAIN is split between REG_GAIN and REG_VREF[7:6]. If one believes 14538c2ecf20Sopenharmony_ci * the data sheet, the VREF parts should be the most significant, but 14548c2ecf20Sopenharmony_ci * experience shows otherwise. There seems to be little value in 14558c2ecf20Sopenharmony_ci * messing with the VREF bits, so we leave them alone. 14568c2ecf20Sopenharmony_ci */ 14578c2ecf20Sopenharmony_cistatic int ov7670_g_gain(struct v4l2_subdev *sd, __s32 *value) 14588c2ecf20Sopenharmony_ci{ 14598c2ecf20Sopenharmony_ci int ret; 14608c2ecf20Sopenharmony_ci unsigned char gain; 14618c2ecf20Sopenharmony_ci 14628c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_GAIN, &gain); 14638c2ecf20Sopenharmony_ci *value = gain; 14648c2ecf20Sopenharmony_ci return ret; 14658c2ecf20Sopenharmony_ci} 14668c2ecf20Sopenharmony_ci 14678c2ecf20Sopenharmony_cistatic int ov7670_s_gain(struct v4l2_subdev *sd, int value) 14688c2ecf20Sopenharmony_ci{ 14698c2ecf20Sopenharmony_ci int ret; 14708c2ecf20Sopenharmony_ci unsigned char com8; 14718c2ecf20Sopenharmony_ci 14728c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_GAIN, value & 0xff); 14738c2ecf20Sopenharmony_ci /* Have to turn off AGC as well */ 14748c2ecf20Sopenharmony_ci if (ret == 0) { 14758c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_COM8, &com8); 14768c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_COM8, com8 & ~COM8_AGC); 14778c2ecf20Sopenharmony_ci } 14788c2ecf20Sopenharmony_ci return ret; 14798c2ecf20Sopenharmony_ci} 14808c2ecf20Sopenharmony_ci 14818c2ecf20Sopenharmony_ci/* 14828c2ecf20Sopenharmony_ci * Tweak autogain. 14838c2ecf20Sopenharmony_ci */ 14848c2ecf20Sopenharmony_cistatic int ov7670_s_autogain(struct v4l2_subdev *sd, int value) 14858c2ecf20Sopenharmony_ci{ 14868c2ecf20Sopenharmony_ci int ret; 14878c2ecf20Sopenharmony_ci unsigned char com8; 14888c2ecf20Sopenharmony_ci 14898c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_COM8, &com8); 14908c2ecf20Sopenharmony_ci if (ret == 0) { 14918c2ecf20Sopenharmony_ci if (value) 14928c2ecf20Sopenharmony_ci com8 |= COM8_AGC; 14938c2ecf20Sopenharmony_ci else 14948c2ecf20Sopenharmony_ci com8 &= ~COM8_AGC; 14958c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_COM8, com8); 14968c2ecf20Sopenharmony_ci } 14978c2ecf20Sopenharmony_ci return ret; 14988c2ecf20Sopenharmony_ci} 14998c2ecf20Sopenharmony_ci 15008c2ecf20Sopenharmony_cistatic int ov7670_s_exp(struct v4l2_subdev *sd, int value) 15018c2ecf20Sopenharmony_ci{ 15028c2ecf20Sopenharmony_ci int ret; 15038c2ecf20Sopenharmony_ci unsigned char com1, com8, aech, aechh; 15048c2ecf20Sopenharmony_ci 15058c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_COM1, &com1) + 15068c2ecf20Sopenharmony_ci ov7670_read(sd, REG_COM8, &com8) + 15078c2ecf20Sopenharmony_ci ov7670_read(sd, REG_AECHH, &aechh); 15088c2ecf20Sopenharmony_ci if (ret) 15098c2ecf20Sopenharmony_ci return ret; 15108c2ecf20Sopenharmony_ci 15118c2ecf20Sopenharmony_ci com1 = (com1 & 0xfc) | (value & 0x03); 15128c2ecf20Sopenharmony_ci aech = (value >> 2) & 0xff; 15138c2ecf20Sopenharmony_ci aechh = (aechh & 0xc0) | ((value >> 10) & 0x3f); 15148c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_COM1, com1) + 15158c2ecf20Sopenharmony_ci ov7670_write(sd, REG_AECH, aech) + 15168c2ecf20Sopenharmony_ci ov7670_write(sd, REG_AECHH, aechh); 15178c2ecf20Sopenharmony_ci /* Have to turn off AEC as well */ 15188c2ecf20Sopenharmony_ci if (ret == 0) 15198c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_COM8, com8 & ~COM8_AEC); 15208c2ecf20Sopenharmony_ci return ret; 15218c2ecf20Sopenharmony_ci} 15228c2ecf20Sopenharmony_ci 15238c2ecf20Sopenharmony_ci/* 15248c2ecf20Sopenharmony_ci * Tweak autoexposure. 15258c2ecf20Sopenharmony_ci */ 15268c2ecf20Sopenharmony_cistatic int ov7670_s_autoexp(struct v4l2_subdev *sd, 15278c2ecf20Sopenharmony_ci enum v4l2_exposure_auto_type value) 15288c2ecf20Sopenharmony_ci{ 15298c2ecf20Sopenharmony_ci int ret; 15308c2ecf20Sopenharmony_ci unsigned char com8; 15318c2ecf20Sopenharmony_ci 15328c2ecf20Sopenharmony_ci ret = ov7670_read(sd, REG_COM8, &com8); 15338c2ecf20Sopenharmony_ci if (ret == 0) { 15348c2ecf20Sopenharmony_ci if (value == V4L2_EXPOSURE_AUTO) 15358c2ecf20Sopenharmony_ci com8 |= COM8_AEC; 15368c2ecf20Sopenharmony_ci else 15378c2ecf20Sopenharmony_ci com8 &= ~COM8_AEC; 15388c2ecf20Sopenharmony_ci ret = ov7670_write(sd, REG_COM8, com8); 15398c2ecf20Sopenharmony_ci } 15408c2ecf20Sopenharmony_ci return ret; 15418c2ecf20Sopenharmony_ci} 15428c2ecf20Sopenharmony_ci 15438c2ecf20Sopenharmony_cistatic const char * const ov7670_test_pattern_menu[] = { 15448c2ecf20Sopenharmony_ci "No test output", 15458c2ecf20Sopenharmony_ci "Shifting \"1\"", 15468c2ecf20Sopenharmony_ci "8-bar color bar", 15478c2ecf20Sopenharmony_ci "Fade to gray color bar", 15488c2ecf20Sopenharmony_ci}; 15498c2ecf20Sopenharmony_ci 15508c2ecf20Sopenharmony_cistatic int ov7670_s_test_pattern(struct v4l2_subdev *sd, int value) 15518c2ecf20Sopenharmony_ci{ 15528c2ecf20Sopenharmony_ci int ret; 15538c2ecf20Sopenharmony_ci 15548c2ecf20Sopenharmony_ci ret = ov7670_update_bits(sd, REG_SCALING_XSC, TEST_PATTTERN_0, 15558c2ecf20Sopenharmony_ci value & BIT(0) ? TEST_PATTTERN_0 : 0); 15568c2ecf20Sopenharmony_ci if (ret) 15578c2ecf20Sopenharmony_ci return ret; 15588c2ecf20Sopenharmony_ci 15598c2ecf20Sopenharmony_ci return ov7670_update_bits(sd, REG_SCALING_YSC, TEST_PATTTERN_1, 15608c2ecf20Sopenharmony_ci value & BIT(1) ? TEST_PATTTERN_1 : 0); 15618c2ecf20Sopenharmony_ci} 15628c2ecf20Sopenharmony_ci 15638c2ecf20Sopenharmony_cistatic int ov7670_g_volatile_ctrl(struct v4l2_ctrl *ctrl) 15648c2ecf20Sopenharmony_ci{ 15658c2ecf20Sopenharmony_ci struct v4l2_subdev *sd = to_sd(ctrl); 15668c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 15678c2ecf20Sopenharmony_ci 15688c2ecf20Sopenharmony_ci switch (ctrl->id) { 15698c2ecf20Sopenharmony_ci case V4L2_CID_AUTOGAIN: 15708c2ecf20Sopenharmony_ci return ov7670_g_gain(sd, &info->gain->val); 15718c2ecf20Sopenharmony_ci } 15728c2ecf20Sopenharmony_ci return -EINVAL; 15738c2ecf20Sopenharmony_ci} 15748c2ecf20Sopenharmony_ci 15758c2ecf20Sopenharmony_cistatic int ov7670_s_ctrl(struct v4l2_ctrl *ctrl) 15768c2ecf20Sopenharmony_ci{ 15778c2ecf20Sopenharmony_ci struct v4l2_subdev *sd = to_sd(ctrl); 15788c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 15798c2ecf20Sopenharmony_ci 15808c2ecf20Sopenharmony_ci switch (ctrl->id) { 15818c2ecf20Sopenharmony_ci case V4L2_CID_BRIGHTNESS: 15828c2ecf20Sopenharmony_ci return ov7670_s_brightness(sd, ctrl->val); 15838c2ecf20Sopenharmony_ci case V4L2_CID_CONTRAST: 15848c2ecf20Sopenharmony_ci return ov7670_s_contrast(sd, ctrl->val); 15858c2ecf20Sopenharmony_ci case V4L2_CID_SATURATION: 15868c2ecf20Sopenharmony_ci return ov7670_s_sat_hue(sd, 15878c2ecf20Sopenharmony_ci info->saturation->val, info->hue->val); 15888c2ecf20Sopenharmony_ci case V4L2_CID_VFLIP: 15898c2ecf20Sopenharmony_ci return ov7670_s_vflip(sd, ctrl->val); 15908c2ecf20Sopenharmony_ci case V4L2_CID_HFLIP: 15918c2ecf20Sopenharmony_ci return ov7670_s_hflip(sd, ctrl->val); 15928c2ecf20Sopenharmony_ci case V4L2_CID_AUTOGAIN: 15938c2ecf20Sopenharmony_ci /* Only set manual gain if auto gain is not explicitly 15948c2ecf20Sopenharmony_ci turned on. */ 15958c2ecf20Sopenharmony_ci if (!ctrl->val) { 15968c2ecf20Sopenharmony_ci /* ov7670_s_gain turns off auto gain */ 15978c2ecf20Sopenharmony_ci return ov7670_s_gain(sd, info->gain->val); 15988c2ecf20Sopenharmony_ci } 15998c2ecf20Sopenharmony_ci return ov7670_s_autogain(sd, ctrl->val); 16008c2ecf20Sopenharmony_ci case V4L2_CID_EXPOSURE_AUTO: 16018c2ecf20Sopenharmony_ci /* Only set manual exposure if auto exposure is not explicitly 16028c2ecf20Sopenharmony_ci turned on. */ 16038c2ecf20Sopenharmony_ci if (ctrl->val == V4L2_EXPOSURE_MANUAL) { 16048c2ecf20Sopenharmony_ci /* ov7670_s_exp turns off auto exposure */ 16058c2ecf20Sopenharmony_ci return ov7670_s_exp(sd, info->exposure->val); 16068c2ecf20Sopenharmony_ci } 16078c2ecf20Sopenharmony_ci return ov7670_s_autoexp(sd, ctrl->val); 16088c2ecf20Sopenharmony_ci case V4L2_CID_TEST_PATTERN: 16098c2ecf20Sopenharmony_ci return ov7670_s_test_pattern(sd, ctrl->val); 16108c2ecf20Sopenharmony_ci } 16118c2ecf20Sopenharmony_ci return -EINVAL; 16128c2ecf20Sopenharmony_ci} 16138c2ecf20Sopenharmony_ci 16148c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops ov7670_ctrl_ops = { 16158c2ecf20Sopenharmony_ci .s_ctrl = ov7670_s_ctrl, 16168c2ecf20Sopenharmony_ci .g_volatile_ctrl = ov7670_g_volatile_ctrl, 16178c2ecf20Sopenharmony_ci}; 16188c2ecf20Sopenharmony_ci 16198c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG 16208c2ecf20Sopenharmony_cistatic int ov7670_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg) 16218c2ecf20Sopenharmony_ci{ 16228c2ecf20Sopenharmony_ci unsigned char val = 0; 16238c2ecf20Sopenharmony_ci int ret; 16248c2ecf20Sopenharmony_ci 16258c2ecf20Sopenharmony_ci ret = ov7670_read(sd, reg->reg & 0xff, &val); 16268c2ecf20Sopenharmony_ci reg->val = val; 16278c2ecf20Sopenharmony_ci reg->size = 1; 16288c2ecf20Sopenharmony_ci return ret; 16298c2ecf20Sopenharmony_ci} 16308c2ecf20Sopenharmony_ci 16318c2ecf20Sopenharmony_cistatic int ov7670_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg) 16328c2ecf20Sopenharmony_ci{ 16338c2ecf20Sopenharmony_ci ov7670_write(sd, reg->reg & 0xff, reg->val & 0xff); 16348c2ecf20Sopenharmony_ci return 0; 16358c2ecf20Sopenharmony_ci} 16368c2ecf20Sopenharmony_ci#endif 16378c2ecf20Sopenharmony_ci 16388c2ecf20Sopenharmony_cistatic void ov7670_power_on(struct v4l2_subdev *sd) 16398c2ecf20Sopenharmony_ci{ 16408c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 16418c2ecf20Sopenharmony_ci 16428c2ecf20Sopenharmony_ci if (info->on) 16438c2ecf20Sopenharmony_ci return; 16448c2ecf20Sopenharmony_ci 16458c2ecf20Sopenharmony_ci clk_prepare_enable(info->clk); 16468c2ecf20Sopenharmony_ci 16478c2ecf20Sopenharmony_ci if (info->pwdn_gpio) 16488c2ecf20Sopenharmony_ci gpiod_set_value(info->pwdn_gpio, 0); 16498c2ecf20Sopenharmony_ci if (info->resetb_gpio) { 16508c2ecf20Sopenharmony_ci gpiod_set_value(info->resetb_gpio, 1); 16518c2ecf20Sopenharmony_ci usleep_range(500, 1000); 16528c2ecf20Sopenharmony_ci gpiod_set_value(info->resetb_gpio, 0); 16538c2ecf20Sopenharmony_ci } 16548c2ecf20Sopenharmony_ci if (info->pwdn_gpio || info->resetb_gpio || info->clk) 16558c2ecf20Sopenharmony_ci usleep_range(3000, 5000); 16568c2ecf20Sopenharmony_ci 16578c2ecf20Sopenharmony_ci info->on = true; 16588c2ecf20Sopenharmony_ci} 16598c2ecf20Sopenharmony_ci 16608c2ecf20Sopenharmony_cistatic void ov7670_power_off(struct v4l2_subdev *sd) 16618c2ecf20Sopenharmony_ci{ 16628c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 16638c2ecf20Sopenharmony_ci 16648c2ecf20Sopenharmony_ci if (!info->on) 16658c2ecf20Sopenharmony_ci return; 16668c2ecf20Sopenharmony_ci 16678c2ecf20Sopenharmony_ci clk_disable_unprepare(info->clk); 16688c2ecf20Sopenharmony_ci 16698c2ecf20Sopenharmony_ci if (info->pwdn_gpio) 16708c2ecf20Sopenharmony_ci gpiod_set_value(info->pwdn_gpio, 1); 16718c2ecf20Sopenharmony_ci 16728c2ecf20Sopenharmony_ci info->on = false; 16738c2ecf20Sopenharmony_ci} 16748c2ecf20Sopenharmony_ci 16758c2ecf20Sopenharmony_cistatic int ov7670_s_power(struct v4l2_subdev *sd, int on) 16768c2ecf20Sopenharmony_ci{ 16778c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 16788c2ecf20Sopenharmony_ci 16798c2ecf20Sopenharmony_ci if (info->on == on) 16808c2ecf20Sopenharmony_ci return 0; 16818c2ecf20Sopenharmony_ci 16828c2ecf20Sopenharmony_ci if (on) { 16838c2ecf20Sopenharmony_ci ov7670_power_on (sd); 16848c2ecf20Sopenharmony_ci ov7670_init(sd, 0); 16858c2ecf20Sopenharmony_ci ov7670_apply_fmt(sd); 16868c2ecf20Sopenharmony_ci ov7675_apply_framerate(sd); 16878c2ecf20Sopenharmony_ci v4l2_ctrl_handler_setup(&info->hdl); 16888c2ecf20Sopenharmony_ci } else { 16898c2ecf20Sopenharmony_ci ov7670_power_off (sd); 16908c2ecf20Sopenharmony_ci } 16918c2ecf20Sopenharmony_ci 16928c2ecf20Sopenharmony_ci return 0; 16938c2ecf20Sopenharmony_ci} 16948c2ecf20Sopenharmony_ci 16958c2ecf20Sopenharmony_cistatic void ov7670_get_default_format(struct v4l2_subdev *sd, 16968c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *format) 16978c2ecf20Sopenharmony_ci{ 16988c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 16998c2ecf20Sopenharmony_ci 17008c2ecf20Sopenharmony_ci format->width = info->devtype->win_sizes[0].width; 17018c2ecf20Sopenharmony_ci format->height = info->devtype->win_sizes[0].height; 17028c2ecf20Sopenharmony_ci format->colorspace = info->fmt->colorspace; 17038c2ecf20Sopenharmony_ci format->code = info->fmt->mbus_code; 17048c2ecf20Sopenharmony_ci format->field = V4L2_FIELD_NONE; 17058c2ecf20Sopenharmony_ci} 17068c2ecf20Sopenharmony_ci 17078c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 17088c2ecf20Sopenharmony_cistatic int ov7670_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) 17098c2ecf20Sopenharmony_ci{ 17108c2ecf20Sopenharmony_ci struct v4l2_mbus_framefmt *format = 17118c2ecf20Sopenharmony_ci v4l2_subdev_get_try_format(sd, fh->pad, 0); 17128c2ecf20Sopenharmony_ci 17138c2ecf20Sopenharmony_ci ov7670_get_default_format(sd, format); 17148c2ecf20Sopenharmony_ci 17158c2ecf20Sopenharmony_ci return 0; 17168c2ecf20Sopenharmony_ci} 17178c2ecf20Sopenharmony_ci#endif 17188c2ecf20Sopenharmony_ci 17198c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */ 17208c2ecf20Sopenharmony_ci 17218c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_core_ops ov7670_core_ops = { 17228c2ecf20Sopenharmony_ci .reset = ov7670_reset, 17238c2ecf20Sopenharmony_ci .init = ov7670_init, 17248c2ecf20Sopenharmony_ci .s_power = ov7670_s_power, 17258c2ecf20Sopenharmony_ci .log_status = v4l2_ctrl_subdev_log_status, 17268c2ecf20Sopenharmony_ci .subscribe_event = v4l2_ctrl_subdev_subscribe_event, 17278c2ecf20Sopenharmony_ci .unsubscribe_event = v4l2_event_subdev_unsubscribe, 17288c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG 17298c2ecf20Sopenharmony_ci .g_register = ov7670_g_register, 17308c2ecf20Sopenharmony_ci .s_register = ov7670_s_register, 17318c2ecf20Sopenharmony_ci#endif 17328c2ecf20Sopenharmony_ci}; 17338c2ecf20Sopenharmony_ci 17348c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_video_ops ov7670_video_ops = { 17358c2ecf20Sopenharmony_ci .s_frame_interval = ov7670_s_frame_interval, 17368c2ecf20Sopenharmony_ci .g_frame_interval = ov7670_g_frame_interval, 17378c2ecf20Sopenharmony_ci}; 17388c2ecf20Sopenharmony_ci 17398c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_pad_ops ov7670_pad_ops = { 17408c2ecf20Sopenharmony_ci .enum_frame_interval = ov7670_enum_frame_interval, 17418c2ecf20Sopenharmony_ci .enum_frame_size = ov7670_enum_frame_size, 17428c2ecf20Sopenharmony_ci .enum_mbus_code = ov7670_enum_mbus_code, 17438c2ecf20Sopenharmony_ci .get_fmt = ov7670_get_fmt, 17448c2ecf20Sopenharmony_ci .set_fmt = ov7670_set_fmt, 17458c2ecf20Sopenharmony_ci}; 17468c2ecf20Sopenharmony_ci 17478c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_ops ov7670_ops = { 17488c2ecf20Sopenharmony_ci .core = &ov7670_core_ops, 17498c2ecf20Sopenharmony_ci .video = &ov7670_video_ops, 17508c2ecf20Sopenharmony_ci .pad = &ov7670_pad_ops, 17518c2ecf20Sopenharmony_ci}; 17528c2ecf20Sopenharmony_ci 17538c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 17548c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_internal_ops ov7670_subdev_internal_ops = { 17558c2ecf20Sopenharmony_ci .open = ov7670_open, 17568c2ecf20Sopenharmony_ci}; 17578c2ecf20Sopenharmony_ci#endif 17588c2ecf20Sopenharmony_ci 17598c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */ 17608c2ecf20Sopenharmony_ci 17618c2ecf20Sopenharmony_cistatic const struct ov7670_devtype ov7670_devdata[] = { 17628c2ecf20Sopenharmony_ci [MODEL_OV7670] = { 17638c2ecf20Sopenharmony_ci .win_sizes = ov7670_win_sizes, 17648c2ecf20Sopenharmony_ci .n_win_sizes = ARRAY_SIZE(ov7670_win_sizes), 17658c2ecf20Sopenharmony_ci .set_framerate = ov7670_set_framerate_legacy, 17668c2ecf20Sopenharmony_ci .get_framerate = ov7670_get_framerate_legacy, 17678c2ecf20Sopenharmony_ci }, 17688c2ecf20Sopenharmony_ci [MODEL_OV7675] = { 17698c2ecf20Sopenharmony_ci .win_sizes = ov7675_win_sizes, 17708c2ecf20Sopenharmony_ci .n_win_sizes = ARRAY_SIZE(ov7675_win_sizes), 17718c2ecf20Sopenharmony_ci .set_framerate = ov7675_set_framerate, 17728c2ecf20Sopenharmony_ci .get_framerate = ov7675_get_framerate, 17738c2ecf20Sopenharmony_ci }, 17748c2ecf20Sopenharmony_ci}; 17758c2ecf20Sopenharmony_ci 17768c2ecf20Sopenharmony_cistatic int ov7670_init_gpio(struct i2c_client *client, struct ov7670_info *info) 17778c2ecf20Sopenharmony_ci{ 17788c2ecf20Sopenharmony_ci info->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", 17798c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 17808c2ecf20Sopenharmony_ci if (IS_ERR(info->pwdn_gpio)) { 17818c2ecf20Sopenharmony_ci dev_info(&client->dev, "can't get %s GPIO\n", "powerdown"); 17828c2ecf20Sopenharmony_ci return PTR_ERR(info->pwdn_gpio); 17838c2ecf20Sopenharmony_ci } 17848c2ecf20Sopenharmony_ci 17858c2ecf20Sopenharmony_ci info->resetb_gpio = devm_gpiod_get_optional(&client->dev, "reset", 17868c2ecf20Sopenharmony_ci GPIOD_OUT_LOW); 17878c2ecf20Sopenharmony_ci if (IS_ERR(info->resetb_gpio)) { 17888c2ecf20Sopenharmony_ci dev_info(&client->dev, "can't get %s GPIO\n", "reset"); 17898c2ecf20Sopenharmony_ci return PTR_ERR(info->resetb_gpio); 17908c2ecf20Sopenharmony_ci } 17918c2ecf20Sopenharmony_ci 17928c2ecf20Sopenharmony_ci usleep_range(3000, 5000); 17938c2ecf20Sopenharmony_ci 17948c2ecf20Sopenharmony_ci return 0; 17958c2ecf20Sopenharmony_ci} 17968c2ecf20Sopenharmony_ci 17978c2ecf20Sopenharmony_ci/* 17988c2ecf20Sopenharmony_ci * ov7670_parse_dt() - Parse device tree to collect mbus configuration 17998c2ecf20Sopenharmony_ci * properties 18008c2ecf20Sopenharmony_ci */ 18018c2ecf20Sopenharmony_cistatic int ov7670_parse_dt(struct device *dev, 18028c2ecf20Sopenharmony_ci struct ov7670_info *info) 18038c2ecf20Sopenharmony_ci{ 18048c2ecf20Sopenharmony_ci struct fwnode_handle *fwnode = dev_fwnode(dev); 18058c2ecf20Sopenharmony_ci struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; 18068c2ecf20Sopenharmony_ci struct fwnode_handle *ep; 18078c2ecf20Sopenharmony_ci int ret; 18088c2ecf20Sopenharmony_ci 18098c2ecf20Sopenharmony_ci if (!fwnode) 18108c2ecf20Sopenharmony_ci return -EINVAL; 18118c2ecf20Sopenharmony_ci 18128c2ecf20Sopenharmony_ci info->pclk_hb_disable = false; 18138c2ecf20Sopenharmony_ci if (fwnode_property_present(fwnode, "ov7670,pclk-hb-disable")) 18148c2ecf20Sopenharmony_ci info->pclk_hb_disable = true; 18158c2ecf20Sopenharmony_ci 18168c2ecf20Sopenharmony_ci ep = fwnode_graph_get_next_endpoint(fwnode, NULL); 18178c2ecf20Sopenharmony_ci if (!ep) 18188c2ecf20Sopenharmony_ci return -EINVAL; 18198c2ecf20Sopenharmony_ci 18208c2ecf20Sopenharmony_ci ret = v4l2_fwnode_endpoint_parse(ep, &bus_cfg); 18218c2ecf20Sopenharmony_ci fwnode_handle_put(ep); 18228c2ecf20Sopenharmony_ci if (ret) 18238c2ecf20Sopenharmony_ci return ret; 18248c2ecf20Sopenharmony_ci 18258c2ecf20Sopenharmony_ci if (bus_cfg.bus_type != V4L2_MBUS_PARALLEL) { 18268c2ecf20Sopenharmony_ci dev_err(dev, "Unsupported media bus type\n"); 18278c2ecf20Sopenharmony_ci return -EINVAL; 18288c2ecf20Sopenharmony_ci } 18298c2ecf20Sopenharmony_ci info->mbus_config = bus_cfg.bus.parallel.flags; 18308c2ecf20Sopenharmony_ci 18318c2ecf20Sopenharmony_ci return 0; 18328c2ecf20Sopenharmony_ci} 18338c2ecf20Sopenharmony_ci 18348c2ecf20Sopenharmony_cistatic int ov7670_probe(struct i2c_client *client, 18358c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 18368c2ecf20Sopenharmony_ci{ 18378c2ecf20Sopenharmony_ci struct v4l2_fract tpf; 18388c2ecf20Sopenharmony_ci struct v4l2_subdev *sd; 18398c2ecf20Sopenharmony_ci struct ov7670_info *info; 18408c2ecf20Sopenharmony_ci int ret; 18418c2ecf20Sopenharmony_ci 18428c2ecf20Sopenharmony_ci info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL); 18438c2ecf20Sopenharmony_ci if (info == NULL) 18448c2ecf20Sopenharmony_ci return -ENOMEM; 18458c2ecf20Sopenharmony_ci sd = &info->sd; 18468c2ecf20Sopenharmony_ci v4l2_i2c_subdev_init(sd, client, &ov7670_ops); 18478c2ecf20Sopenharmony_ci 18488c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API 18498c2ecf20Sopenharmony_ci sd->internal_ops = &ov7670_subdev_internal_ops; 18508c2ecf20Sopenharmony_ci sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; 18518c2ecf20Sopenharmony_ci#endif 18528c2ecf20Sopenharmony_ci 18538c2ecf20Sopenharmony_ci info->clock_speed = 30; /* default: a guess */ 18548c2ecf20Sopenharmony_ci 18558c2ecf20Sopenharmony_ci if (dev_fwnode(&client->dev)) { 18568c2ecf20Sopenharmony_ci ret = ov7670_parse_dt(&client->dev, info); 18578c2ecf20Sopenharmony_ci if (ret) 18588c2ecf20Sopenharmony_ci return ret; 18598c2ecf20Sopenharmony_ci 18608c2ecf20Sopenharmony_ci } else if (client->dev.platform_data) { 18618c2ecf20Sopenharmony_ci struct ov7670_config *config = client->dev.platform_data; 18628c2ecf20Sopenharmony_ci 18638c2ecf20Sopenharmony_ci /* 18648c2ecf20Sopenharmony_ci * Must apply configuration before initializing device, because it 18658c2ecf20Sopenharmony_ci * selects I/O method. 18668c2ecf20Sopenharmony_ci */ 18678c2ecf20Sopenharmony_ci info->min_width = config->min_width; 18688c2ecf20Sopenharmony_ci info->min_height = config->min_height; 18698c2ecf20Sopenharmony_ci info->use_smbus = config->use_smbus; 18708c2ecf20Sopenharmony_ci 18718c2ecf20Sopenharmony_ci if (config->clock_speed) 18728c2ecf20Sopenharmony_ci info->clock_speed = config->clock_speed; 18738c2ecf20Sopenharmony_ci 18748c2ecf20Sopenharmony_ci if (config->pll_bypass) 18758c2ecf20Sopenharmony_ci info->pll_bypass = true; 18768c2ecf20Sopenharmony_ci 18778c2ecf20Sopenharmony_ci if (config->pclk_hb_disable) 18788c2ecf20Sopenharmony_ci info->pclk_hb_disable = true; 18798c2ecf20Sopenharmony_ci } 18808c2ecf20Sopenharmony_ci 18818c2ecf20Sopenharmony_ci info->clk = devm_clk_get(&client->dev, "xclk"); /* optional */ 18828c2ecf20Sopenharmony_ci if (IS_ERR(info->clk)) { 18838c2ecf20Sopenharmony_ci ret = PTR_ERR(info->clk); 18848c2ecf20Sopenharmony_ci if (ret == -ENOENT) 18858c2ecf20Sopenharmony_ci info->clk = NULL; 18868c2ecf20Sopenharmony_ci else 18878c2ecf20Sopenharmony_ci return ret; 18888c2ecf20Sopenharmony_ci } 18898c2ecf20Sopenharmony_ci 18908c2ecf20Sopenharmony_ci ret = ov7670_init_gpio(client, info); 18918c2ecf20Sopenharmony_ci if (ret) 18928c2ecf20Sopenharmony_ci return ret; 18938c2ecf20Sopenharmony_ci 18948c2ecf20Sopenharmony_ci ov7670_power_on(sd); 18958c2ecf20Sopenharmony_ci 18968c2ecf20Sopenharmony_ci if (info->clk) { 18978c2ecf20Sopenharmony_ci info->clock_speed = clk_get_rate(info->clk) / 1000000; 18988c2ecf20Sopenharmony_ci if (info->clock_speed < 10 || info->clock_speed > 48) { 18998c2ecf20Sopenharmony_ci ret = -EINVAL; 19008c2ecf20Sopenharmony_ci goto power_off; 19018c2ecf20Sopenharmony_ci } 19028c2ecf20Sopenharmony_ci } 19038c2ecf20Sopenharmony_ci 19048c2ecf20Sopenharmony_ci /* Make sure it's an ov7670 */ 19058c2ecf20Sopenharmony_ci ret = ov7670_detect(sd); 19068c2ecf20Sopenharmony_ci if (ret) { 19078c2ecf20Sopenharmony_ci v4l_dbg(1, debug, client, 19088c2ecf20Sopenharmony_ci "chip found @ 0x%x (%s) is not an ov7670 chip.\n", 19098c2ecf20Sopenharmony_ci client->addr << 1, client->adapter->name); 19108c2ecf20Sopenharmony_ci goto power_off; 19118c2ecf20Sopenharmony_ci } 19128c2ecf20Sopenharmony_ci v4l_info(client, "chip found @ 0x%02x (%s)\n", 19138c2ecf20Sopenharmony_ci client->addr << 1, client->adapter->name); 19148c2ecf20Sopenharmony_ci 19158c2ecf20Sopenharmony_ci info->devtype = &ov7670_devdata[id->driver_data]; 19168c2ecf20Sopenharmony_ci info->fmt = &ov7670_formats[0]; 19178c2ecf20Sopenharmony_ci info->wsize = &info->devtype->win_sizes[0]; 19188c2ecf20Sopenharmony_ci 19198c2ecf20Sopenharmony_ci ov7670_get_default_format(sd, &info->format); 19208c2ecf20Sopenharmony_ci 19218c2ecf20Sopenharmony_ci info->clkrc = 0; 19228c2ecf20Sopenharmony_ci 19238c2ecf20Sopenharmony_ci /* Set default frame rate to 30 fps */ 19248c2ecf20Sopenharmony_ci tpf.numerator = 1; 19258c2ecf20Sopenharmony_ci tpf.denominator = 30; 19268c2ecf20Sopenharmony_ci info->devtype->set_framerate(sd, &tpf); 19278c2ecf20Sopenharmony_ci 19288c2ecf20Sopenharmony_ci v4l2_ctrl_handler_init(&info->hdl, 10); 19298c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19308c2ecf20Sopenharmony_ci V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); 19318c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19328c2ecf20Sopenharmony_ci V4L2_CID_CONTRAST, 0, 127, 1, 64); 19338c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19348c2ecf20Sopenharmony_ci V4L2_CID_VFLIP, 0, 1, 1, 0); 19358c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19368c2ecf20Sopenharmony_ci V4L2_CID_HFLIP, 0, 1, 1, 0); 19378c2ecf20Sopenharmony_ci info->saturation = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19388c2ecf20Sopenharmony_ci V4L2_CID_SATURATION, 0, 256, 1, 128); 19398c2ecf20Sopenharmony_ci info->hue = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19408c2ecf20Sopenharmony_ci V4L2_CID_HUE, -180, 180, 5, 0); 19418c2ecf20Sopenharmony_ci info->gain = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19428c2ecf20Sopenharmony_ci V4L2_CID_GAIN, 0, 255, 1, 128); 19438c2ecf20Sopenharmony_ci info->auto_gain = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19448c2ecf20Sopenharmony_ci V4L2_CID_AUTOGAIN, 0, 1, 1, 1); 19458c2ecf20Sopenharmony_ci info->exposure = v4l2_ctrl_new_std(&info->hdl, &ov7670_ctrl_ops, 19468c2ecf20Sopenharmony_ci V4L2_CID_EXPOSURE, 0, 65535, 1, 500); 19478c2ecf20Sopenharmony_ci info->auto_exposure = v4l2_ctrl_new_std_menu(&info->hdl, &ov7670_ctrl_ops, 19488c2ecf20Sopenharmony_ci V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL, 0, 19498c2ecf20Sopenharmony_ci V4L2_EXPOSURE_AUTO); 19508c2ecf20Sopenharmony_ci v4l2_ctrl_new_std_menu_items(&info->hdl, &ov7670_ctrl_ops, 19518c2ecf20Sopenharmony_ci V4L2_CID_TEST_PATTERN, 19528c2ecf20Sopenharmony_ci ARRAY_SIZE(ov7670_test_pattern_menu) - 1, 0, 0, 19538c2ecf20Sopenharmony_ci ov7670_test_pattern_menu); 19548c2ecf20Sopenharmony_ci sd->ctrl_handler = &info->hdl; 19558c2ecf20Sopenharmony_ci if (info->hdl.error) { 19568c2ecf20Sopenharmony_ci ret = info->hdl.error; 19578c2ecf20Sopenharmony_ci 19588c2ecf20Sopenharmony_ci goto hdl_free; 19598c2ecf20Sopenharmony_ci } 19608c2ecf20Sopenharmony_ci /* 19618c2ecf20Sopenharmony_ci * We have checked empirically that hw allows to read back the gain 19628c2ecf20Sopenharmony_ci * value chosen by auto gain but that's not the case for auto exposure. 19638c2ecf20Sopenharmony_ci */ 19648c2ecf20Sopenharmony_ci v4l2_ctrl_auto_cluster(2, &info->auto_gain, 0, true); 19658c2ecf20Sopenharmony_ci v4l2_ctrl_auto_cluster(2, &info->auto_exposure, 19668c2ecf20Sopenharmony_ci V4L2_EXPOSURE_MANUAL, false); 19678c2ecf20Sopenharmony_ci v4l2_ctrl_cluster(2, &info->saturation); 19688c2ecf20Sopenharmony_ci 19698c2ecf20Sopenharmony_ci#if defined(CONFIG_MEDIA_CONTROLLER) 19708c2ecf20Sopenharmony_ci info->pad.flags = MEDIA_PAD_FL_SOURCE; 19718c2ecf20Sopenharmony_ci info->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; 19728c2ecf20Sopenharmony_ci ret = media_entity_pads_init(&info->sd.entity, 1, &info->pad); 19738c2ecf20Sopenharmony_ci if (ret < 0) 19748c2ecf20Sopenharmony_ci goto hdl_free; 19758c2ecf20Sopenharmony_ci#endif 19768c2ecf20Sopenharmony_ci 19778c2ecf20Sopenharmony_ci v4l2_ctrl_handler_setup(&info->hdl); 19788c2ecf20Sopenharmony_ci 19798c2ecf20Sopenharmony_ci ret = v4l2_async_register_subdev(&info->sd); 19808c2ecf20Sopenharmony_ci if (ret < 0) 19818c2ecf20Sopenharmony_ci goto entity_cleanup; 19828c2ecf20Sopenharmony_ci 19838c2ecf20Sopenharmony_ci ov7670_power_off(sd); 19848c2ecf20Sopenharmony_ci return 0; 19858c2ecf20Sopenharmony_ci 19868c2ecf20Sopenharmony_cientity_cleanup: 19878c2ecf20Sopenharmony_ci media_entity_cleanup(&info->sd.entity); 19888c2ecf20Sopenharmony_cihdl_free: 19898c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&info->hdl); 19908c2ecf20Sopenharmony_cipower_off: 19918c2ecf20Sopenharmony_ci ov7670_power_off(sd); 19928c2ecf20Sopenharmony_ci return ret; 19938c2ecf20Sopenharmony_ci} 19948c2ecf20Sopenharmony_ci 19958c2ecf20Sopenharmony_cistatic int ov7670_remove(struct i2c_client *client) 19968c2ecf20Sopenharmony_ci{ 19978c2ecf20Sopenharmony_ci struct v4l2_subdev *sd = i2c_get_clientdata(client); 19988c2ecf20Sopenharmony_ci struct ov7670_info *info = to_state(sd); 19998c2ecf20Sopenharmony_ci 20008c2ecf20Sopenharmony_ci v4l2_async_unregister_subdev(sd); 20018c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&info->hdl); 20028c2ecf20Sopenharmony_ci media_entity_cleanup(&info->sd.entity); 20038c2ecf20Sopenharmony_ci return 0; 20048c2ecf20Sopenharmony_ci} 20058c2ecf20Sopenharmony_ci 20068c2ecf20Sopenharmony_cistatic const struct i2c_device_id ov7670_id[] = { 20078c2ecf20Sopenharmony_ci { "ov7670", MODEL_OV7670 }, 20088c2ecf20Sopenharmony_ci { "ov7675", MODEL_OV7675 }, 20098c2ecf20Sopenharmony_ci { } 20108c2ecf20Sopenharmony_ci}; 20118c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ov7670_id); 20128c2ecf20Sopenharmony_ci 20138c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_OF) 20148c2ecf20Sopenharmony_cistatic const struct of_device_id ov7670_of_match[] = { 20158c2ecf20Sopenharmony_ci { .compatible = "ovti,ov7670", }, 20168c2ecf20Sopenharmony_ci { /* sentinel */ }, 20178c2ecf20Sopenharmony_ci}; 20188c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ov7670_of_match); 20198c2ecf20Sopenharmony_ci#endif 20208c2ecf20Sopenharmony_ci 20218c2ecf20Sopenharmony_cistatic struct i2c_driver ov7670_driver = { 20228c2ecf20Sopenharmony_ci .driver = { 20238c2ecf20Sopenharmony_ci .name = "ov7670", 20248c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(ov7670_of_match), 20258c2ecf20Sopenharmony_ci }, 20268c2ecf20Sopenharmony_ci .probe = ov7670_probe, 20278c2ecf20Sopenharmony_ci .remove = ov7670_remove, 20288c2ecf20Sopenharmony_ci .id_table = ov7670_id, 20298c2ecf20Sopenharmony_ci}; 20308c2ecf20Sopenharmony_ci 20318c2ecf20Sopenharmony_cimodule_i2c_driver(ov7670_driver); 2032