From 8410f55a21b22ea2dee38bb46e918edd6e99c167 Mon Sep 17 00:00:00 2001 From: Andrew Chew Date: Wed, 8 Aug 2012 13:35:33 -0700 Subject: media: ov5640: Add Omnivision OV5640 support Based on ov5642 driver. There seem to be some register differences, so forking the driver to make those changes. Change-Id: Idb4a79757cbbcd15d5456ce82a708f433faaff7a Signed-off-by: Andrew Chew Signed-off-by: Bryan Wu Reviewed-on: http://git-master/r/169836 Reviewed-by: Winnie Hsu --- drivers/media/video/Kconfig | 6 + drivers/media/video/Makefile | 1 + drivers/media/video/ov5640.c | 1151 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1158 insertions(+) create mode 100644 drivers/media/video/ov5640.c (limited to 'drivers/media') diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 1d392bf0d521..7968ea979d1f 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -854,6 +854,12 @@ config SOC_CAMERA_OV2640 help This is a ov2640 camera driver +config SOC_CAMERA_OV5640 + tristate "ov5640 camera support" + depends on SOC_CAMERA && I2C + help + This is a V4L2 camera driver for the OmniVision OV5640 sensor + config SOC_CAMERA_OV5642 tristate "ov5642 camera support" depends on SOC_CAMERA && I2C diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index b8663f00366e..f63ad7e88fda 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -79,6 +79,7 @@ obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o obj-$(CONFIG_SOC_CAMERA_MT9T112) += mt9t112.o obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o obj-$(CONFIG_SOC_CAMERA_OV2640) += ov2640.o +obj-$(CONFIG_SOC_CAMERA_OV5640) += ov5640.o obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o diff --git a/drivers/media/video/ov5640.c b/drivers/media/video/ov5640.c new file mode 100644 index 000000000000..8e45fa3de5d2 --- /dev/null +++ b/drivers/media/video/ov5640.c @@ -0,0 +1,1151 @@ +/* + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#define to_ov5640(sd) container_of(sd, struct ov5640_priv, subdev) + +#define OV5640_SYSTEM_CTRL 0x3008 +#define OV5640_CHIP_ID_HI 0x300a +#define OV5640_CHIP_ID_LO 0x300b +#define OV5640_PAD_OUTPUT_ENABLE00 0x3016 +#define OV5640_PAD_OUTPUT_ENABLE01 0x3017 +#define OV5640_PAD_OUTPUT_ENABLE02 0x3018 +#define OV5640_SC_PLL_CTRL0 0x3034 +#define OV5640_SC_PLL_CTRL1 0x3035 +#define OV5640_SC_PLL_CTRL2 0x3036 +#define OV5640_SC_PLL_CTRL3 0x3037 + +/* SCCB Control */ +#define OV5640_SCCB_SYSTEM_CTRL1 0x3103 +#define OV5640_SYSTEM_ROOT_DIVIDER 0x3108 + +/* Timing Control */ +#define OV5640_TIMING_HS_HI 0x3800 +#define OV5640_TIMING_HS_LO 0x3801 +#define OV5640_TIMING_VS_HI 0x3802 +#define OV5640_TIMING_VS_LO 0x3803 +#define OV5640_TIMING_HW_HI 0x3804 +#define OV5640_TIMING_HW_LO 0x3805 +#define OV5640_TIMING_VH_HI 0x3806 +#define OV5640_TIMING_VH_LO 0x3807 +#define OV5640_TIMING_DVPHO_HI 0x3808 +#define OV5640_TIMING_DVPHO_LO 0x3809 +#define OV5640_TIMING_DVPVO_HI 0x380a +#define OV5640_TIMING_DVPVO_LO 0x380b +#define OV5640_TIMING_HTS_HI 0x380c +#define OV5640_TIMING_HTS_LO 0x380d +#define OV5640_TIMING_VTS_HI 0x380e +#define OV5640_TIMING_VTS_LO 0x380f +#define OV5640_TIMING_HOFFSET_HI 0x3810 +#define OV5640_TIMING_HOFFSET_LO 0x3811 +#define OV5640_TIMING_VOFFSET_HI 0x3812 +#define OV5640_TIMING_VOFFSET_LO 0x3813 +#define OV5640_TIMING_X_INC 0x3814 +#define OV5640_TIMING_Y_INC 0x3815 +#define OV5640_TIMING_TC_REG20 0x3820 +#define OV5640_TIMING_TC_REG21 0x3821 + +/* AEC/AGC Power Down Domain Control */ +#define OV5640_AEC_MAX_EXPO_60HZ_HI 0x3a02 +#define OV5640_AEC_MAX_EXPO_60HZ_LO 0x3a03 +#define OV5640_AEC_B50_STEP_HI 0x3a08 +#define OV5640_AEC_B50_STEP_LO 0x3a09 +#define OV5640_AEC_B60_STEP_HI 0x3a0a +#define OV5640_AEC_B60_STEP_LO 0x3a0b +#define OV5640_AEC_CTRL0C 0x3a0c +#define OV5640_AEC_CTRL0D 0x3a0d +#define OV5640_AEC_CTRL0E 0x3a0e +#define OV5640_AEC_CTRL0F 0x3a0f +#define OV5640_AEC_CTRL10 0x3a10 +#define OV5640_AEC_CTRL11 0x3a11 +#define OV5640_AEC_CTRL12 0x3a12 +#define OV5640_AEC_CTRL13 0x3a13 +#define OV5640_AEC_MAX_EXPO_50HZ_HI 0x3a14 +#define OV5640_AEC_MAX_EXPO_50HZ_LO 0x3a15 +#define OV5640_AEC_GAIN_CEILING_HI 0x3a18 +#define OV5640_AEC_GAIN_CEILING_LO 0x3a19 +#define OV5640_AEC_CTRL1B 0x3a1b +#define OV5640_AEC_CTRL1E 0x3a1e +#define OV5640_AEC_CTRL1F 0x3a1f + +/* 50/60Hz Detector Control */ +#define OV5640_5060HZ_CTRL00 0x3c00 +#define OV5640_5060HZ_CTRL01 0x3c01 +#define OV5640_5060HZ_CTRL02 0x3c02 +#define OV5640_5060HZ_CTRL03 0x3c03 +#define OV5640_5060HZ_CTRL04 0x3c04 +#define OV5640_5060HZ_CTRL05 0x3c05 +#define OV5640_LIGHT_METER1_THRESHOLD_HI 0x3c06 +#define OV5640_LIGHT_METER1_THRESHOLD_LO 0x3c07 +#define OV5640_LIGHT_METER2_THRESHOLD_HI 0x3c08 +#define OV5640_LIGHT_METER2_THRESHOLD_LO 0x3c09 +#define OV5640_SAMPLE_NUMBER_HI 0x3c0a +#define OV5640_SAMPLE_NUMBER_LO 0x3c0b + +/* ISP General Controls */ +#define OV5640_ISP_CTRL00 0x5000 +#define OV5640_ISP_CTRL01 0x5001 +#define OV5640_ISP_CTRL37 0x5025 + +/* AWB Control */ +#define OV5640_AWB_CTRL00 0x5180 +#define OV5640_AWB_CTRL01 0x5181 +#define OV5640_AWB_CTRL02 0x5182 +#define OV5640_AWB_CTRL03 0x5183 +#define OV5640_AWB_CTRL04 0x5184 +#define OV5640_AWB_CTRL05 0x5185 +#define OV5640_AWB_CTRL06 0x5186 +#define OV5640_AWB_CTRL07 0x5187 +#define OV5640_AWB_CTRL08 0x5188 +#define OV5640_AWB_CTRL09 0x5189 +#define OV5640_AWB_CTRL10 0x518a +#define OV5640_AWB_CTRL11 0x518b +#define OV5640_AWB_CTRL12 0x518c +#define OV5640_AWB_CTRL13 0x518d +#define OV5640_AWB_CTRL14 0x518e +#define OV5640_AWB_CTRL15 0x518f +#define OV5640_AWB_CTRL16 0x5190 +#define OV5640_AWB_CTRL17 0x5191 +#define OV5640_AWB_CTRL18 0x5192 +#define OV5640_AWB_CTRL19 0x5193 +#define OV5640_AWB_CTRL20 0x5194 +#define OV5640_AWB_CTRL21 0x5195 +#define OV5640_AWB_CTRL22 0x5196 +#define OV5640_AWB_CTRL23 0x5197 +#define OV5640_AWB_CTRL24 0x5198 +#define OV5640_AWB_CTRL25 0x5199 +#define OV5640_AWB_CTRL26 0x519a +#define OV5640_AWB_CTRL27 0x519b +#define OV5640_AWB_CTRL28 0x519c +#define OV5640_AWB_CTRL29 0x519d +#define OV5640_AWB_CTRL30 0x519e + +/* CIP Control */ +#define OV5640_CIP_SHARPENMT_THRESHOLD_1 0x5300 +#define OV5640_CIP_SHARPENMT_THRESHOLD_2 0x5301 +#define OV5640_CIP_SHARPENMT_OFFSET_1 0x5302 +#define OV5640_CIP_SHARPENMT_OFFSET_2 0x5303 +#define OV5640_CIP_DNS_THRESHOLD_1 0x5304 +#define OV5640_CIP_DNS_THRESHOLD_2 0x5305 +#define OV5640_CIP_DNS_OFFSET_1 0x5306 +#define OV5640_CIP_DNS_OFFSET_2 0x5307 +#define OV5640_CIP_CTRL 0x5308 +#define OV5640_CIP_SHARPENTH_THRESHOLD_1 0x5309 +#define OV5640_CIP_SHARPENTH_THRESHOLD_2 0x530a +#define OV5640_CIP_SHARPENTH_OFFSET_1 0x530b +#define OV5640_CIP_SHARPENTH_OFFSET_2 0x530c +#define OV5640_CIP_EDGE_MT_AUTO 0x530d +#define OV5640_CIP_DNS_THRESHOLD_AUTO 0x530e +#define OF5640_CIP_SHARPEN_THRESHOLD_AUTO 0x530f + +/* CMX Control */ +#define OV5640_CMX_CTRL 0x5380 +#define OV5640_CMX1 0x5381 +#define OV5640_CMX2 0x5382 +#define OV5640_CMX3 0x5383 +#define OV5640_CMX4 0x5384 +#define OV5640_CMX5 0x5385 +#define OV5640_CMX6 0x5386 +#define OV5640_CMX7 0x5387 +#define OV5640_CMX8 0x5388 +#define OV5640_CMX9 0x5389 +#define OV5640_CMXSIGN_HI 0x538a +#define OV5640_CMXSIGN_LO 0x538b + +/* Gamma Control */ +#define OV5640_GAMMA_CTRL00 0x5480 +#define OV5640_YST00 0x5481 +#define OV5640_YST01 0x5482 +#define OV5640_YST02 0x5483 +#define OV5640_YST03 0x5484 +#define OV5640_YST04 0x5485 +#define OV5640_YST05 0x5486 +#define OV5640_YST06 0x5487 +#define OV5640_YST07 0x5488 +#define OV5640_YST08 0x5489 +#define OV5640_YST09 0x548a +#define OV5640_YST0A 0x548b +#define OV5640_YST0B 0x548c +#define OV5640_YST0C 0x548d +#define OV5640_YST0D 0x548e +#define OV5640_YST0E 0x548f +#define OV5640_YST0F 0x5490 + +/* SDE Control */ +#define OV5640_SDE_CTRL_0 0x5580 +#define OV5640_SDE_CTRL_1 0x5581 +#define OV5640_SDE_CTRL_2 0x5582 +#define OV5640_SDE_CTRL_3 0x5583 +#define OV5640_SDE_CTRL_4 0x5584 +#define OV5640_SDE_CTRL_5 0x5585 +#define OV5640_SDE_CTRL_6 0x5586 +#define OV5640_SDE_CTRL_7 0x5587 +#define OV5640_SDE_CTRL_8 0x5588 +#define OV5640_SDE_CTRL_9 0x5589 +#define OV5640_SDE_CTRL_10 0x558a +#define OV5640_SDE_CTRL_11 0x558b +#define OV5640_SDE_CTRL_12 0x558c + +/* LENC Control */ +#define OV5640_GMTRX00 0x5800 +#define OV5640_GMTRX01 0x5801 +#define OV5640_GMTRX02 0x5802 +#define OV5640_GMTRX03 0x5803 +#define OV5640_GMTRX04 0x5804 +#define OV5640_GMTRX05 0x5805 +#define OV5640_GMTRX10 0x5806 +#define OV5640_GMTRX11 0x5807 +#define OV5640_GMTRX12 0x5808 +#define OV5640_GMTRX13 0x5809 +#define OV5640_GMTRX14 0x580a +#define OV5640_GMTRX15 0x580b +#define OV5640_GMTRX20 0x580c +#define OV5640_GMTRX21 0x580d +#define OV5640_GMTRX22 0x580e +#define OV5640_GMTRX23 0x580f +#define OV5640_GMTRX24 0x5810 +#define OV5640_GMTRX25 0x5811 +#define OV5640_GMTRX30 0x5812 +#define OV5640_GMTRX31 0x5813 +#define OV5640_GMTRX32 0x5814 +#define OV5640_GMTRX33 0x5815 +#define OV5640_GMTRX34 0x5816 +#define OV5640_GMTRX35 0x5817 +#define OV5640_GMTRX40 0x5818 +#define OV5640_GMTRX41 0x5819 +#define OV5640_GMTRX42 0x581a +#define OV5640_GMTRX43 0x581b +#define OV5640_GMTRX44 0x581c +#define OV5640_GMTRX45 0x581d +#define OV5640_GMTRX50 0x581e +#define OV5640_GMTRX51 0x581f +#define OV5640_GMTRX52 0x5820 +#define OV5640_GMTRX53 0x5821 +#define OV5640_GMTRX54 0x5822 +#define OV5640_GMTRX55 0x5823 +#define OV5640_BRMATRX00 0x5824 +#define OV5640_BRMATRX01 0x5825 +#define OV5640_BRMATRX02 0x5826 +#define OV5640_BRMATRX03 0x5827 +#define OV5640_BRMATRX04 0x5828 +#define OV5640_BRMATRX05 0x5829 +#define OV5640_BRMATRX06 0x582a +#define OV5640_BRMATRX07 0x582b +#define OV5640_BRMATRX08 0x582c +#define OV5640_BRMATRX09 0x582d +#define OV5640_BRMATRX20 0x582e +#define OV5640_BRMATRX21 0x582f +#define OV5640_BRMATRX22 0x5830 +#define OV5640_BRMATRX23 0x5831 +#define OV5640_BRMATRX24 0x5832 +#define OV5640_BRMATRX30 0x5833 +#define OV5640_BRMATRX31 0x5834 +#define OV5640_BRMATRX32 0x5835 +#define OV5640_BRMATRX33 0x5836 +#define OV5640_BRMATRX34 0x5837 +#define OV5640_BRMATRX40 0x5838 +#define OV5640_BRMATRX41 0x5839 +#define OV5640_BRMATRX42 0x583a +#define OV5640_BRMATRX43 0x583b +#define OV5640_BRMATRX44 0x583c +#define OV5640_LENC_BR_OFFSET 0x583d + +#define OV5640_MAX_WIDTH 640 +#define OV5640_MAX_HEIGHT 480 + +/* Misc. structures */ +struct ov5640_reg { + u16 reg; + u8 val; +}; + +struct ov5640_priv { + struct v4l2_subdev subdev; + + int ident; + u16 chip_id; + u8 revision; + u8 manid; + u8 smiaver; + + bool flag_vflip; + bool flag_hflip; + + /* For suspend/resume. */ + struct v4l2_mbus_framefmt current_mf; + bool current_enable; +}; + +static const struct ov5640_reg ov5640_defaults[] = { + { OV5640_SCCB_SYSTEM_CTRL1, 0x11}, + { OV5640_SYSTEM_CTRL, 0x82}, + { OV5640_SYSTEM_CTRL, 0x42}, + { OV5640_SCCB_SYSTEM_CTRL1, 0x03}, + { OV5640_PAD_OUTPUT_ENABLE01, 0x00}, + { OV5640_PAD_OUTPUT_ENABLE02, 0x00}, + { OV5640_SC_PLL_CTRL0, 0x18}, + { OV5640_SC_PLL_CTRL1, 0x14}, + { OV5640_SC_PLL_CTRL2, 0x38}, + { OV5640_SC_PLL_CTRL3, 0x13}, + { 0x4800, 0x24}, /* noncontinuous clock */ + { OV5640_SYSTEM_ROOT_DIVIDER, 0x01}, + { 0x3630, 0x36}, + { 0x3631, 0x0e}, + { 0x3632, 0xe2}, + { 0x3633, 0x12}, + { 0x3621, 0xe0}, + { 0x3704, 0xa0}, + { 0x3703, 0x5a}, + { 0x3715, 0x78}, + { 0x3717, 0x01}, + { 0x370b, 0x60}, + { 0x3705, 0x1a}, + { 0x3905, 0x02}, + { 0x3906, 0x10}, + { 0x3901, 0x0a}, + { 0x3731, 0x12}, + { 0x3600, 0x08}, + { 0x3601, 0x33}, + { 0x302d, 0x60}, + { 0x3620, 0x52}, + { 0x371b, 0x20}, + { 0x471c, 0x50}, + { 0x3a13, 0x43}, + { 0x3a18, 0x00}, + { 0x3a19, 0xf8}, + { 0x3635, 0x13}, + { 0x3636, 0x03}, + { 0x3634, 0x40}, + { 0x3622, 0x01}, + { 0x3c01, 0x34}, + { 0x3c04, 0x28}, + { 0x3c05, 0x98}, + { 0x3c06, 0x00}, + { 0x3c07, 0x08}, + { 0x3c08, 0x00}, + { 0x3c09, 0x1c}, + { 0x3c0a, 0x9c}, + { 0x3c0b, 0x40}, + { OV5640_TIMING_TC_REG20, 0x41}, + { OV5640_TIMING_TC_REG21, 0x01}, + { 0x3814, 0x31}, + { 0x3815, 0x31}, + { 0x3800, 0x00}, + { 0x3801, 0x00}, + { 0x3802, 0x00}, + { 0x3803, 0x04}, + { 0x3804, 0x0a}, + { 0x3805, 0x3f}, + { 0x3806, 0x07}, + { 0x3807, 0x9b}, + { 0x3808, 0x02}, + { 0x3809, 0x80}, + { 0x380a, 0x01}, + { 0x380b, 0xe0}, + { 0x380c, 0x07}, + { 0x380d, 0x68}, + { 0x380e, 0x03}, + { 0x380f, 0xd8}, + { 0x3810, 0x00}, + { 0x3811, 0x10}, + { 0x3812, 0x00}, + { 0x3813, 0x06}, + { 0x3618, 0x00}, + { 0x3612, 0x29}, + { 0x3708, 0x64}, + { 0x3709, 0x52}, + { 0x370c, 0x03}, + + /* AEC/AGC Power Down Domain Control */ + { OV5640_AEC_MAX_EXPO_60HZ_HI, 0x03}, + { OV5640_AEC_MAX_EXPO_60HZ_LO, 0xd8}, + { OV5640_AEC_B50_STEP_HI, 0x01}, + { OV5640_AEC_B50_STEP_LO, 0x27}, + { OV5640_AEC_B60_STEP_HI, 0x00}, + { OV5640_AEC_B60_STEP_LO, 0xf6}, + { OV5640_AEC_CTRL0E, 0x03}, + { OV5640_AEC_CTRL0D, 0x04}, + { OV5640_AEC_MAX_EXPO_50HZ_HI, 0x03}, + { OV5640_AEC_MAX_EXPO_50HZ_LO, 0xd8}, + + { 0x4001, 0x02}, + { 0x4004, 0x02}, + { 0x3000, 0x00}, + { 0x3002, 0x1c}, + { 0x3004, 0xff}, + { 0x3006, 0xc3}, + { 0x300e, 0x45}, + { 0x302e, 0x08}, + /* org:30 bit[3:0] + 0x0:YUYV 0x1:YVYU 0x2:UYVY + 0x3:VYUY 0xF:UYVY 0x4~0xE:Not-allowed + */ + { 0x4300, 0x32}, + { 0x501f, 0x00}, + { 0x4713, 0x03}, + { 0x4407, 0x04}, + { 0x440e, 0x00}, + { 0x460b, 0x35}, + { 0x460c, 0x22}, + { 0x4837, 0x44}, + { 0x3824, 0x02}, + { 0x5000, 0xa7}, + { 0x5001, 0xa3}, + + /* AWB Control */ + { OV5640_AWB_CTRL00, 0xff}, { OV5640_AWB_CTRL01, 0xf2}, + { OV5640_AWB_CTRL02, 0x00}, { OV5640_AWB_CTRL03, 0x14}, + { OV5640_AWB_CTRL04, 0x25}, { OV5640_AWB_CTRL05, 0x24}, + { OV5640_AWB_CTRL06, 0x09}, { OV5640_AWB_CTRL07, 0x09}, + { OV5640_AWB_CTRL08, 0x09}, { OV5640_AWB_CTRL09, 0x75}, + { OV5640_AWB_CTRL10, 0x54}, { OV5640_AWB_CTRL11, 0xe0}, + { OV5640_AWB_CTRL12, 0xb2}, { OV5640_AWB_CTRL13, 0x42}, + { OV5640_AWB_CTRL14, 0x3d}, { OV5640_AWB_CTRL15, 0x56}, + { OV5640_AWB_CTRL16, 0x46}, { OV5640_AWB_CTRL17, 0xf8}, + { OV5640_AWB_CTRL18, 0x04}, { OV5640_AWB_CTRL19, 0x70}, + { OV5640_AWB_CTRL20, 0xf0}, { OV5640_AWB_CTRL21, 0xf0}, + { OV5640_AWB_CTRL22, 0x03}, { OV5640_AWB_CTRL23, 0x01}, + { OV5640_AWB_CTRL24, 0x04}, { OV5640_AWB_CTRL25, 0x12}, + { OV5640_AWB_CTRL26, 0x04}, { OV5640_AWB_CTRL27, 0x00}, + { OV5640_AWB_CTRL28, 0x06}, { OV5640_AWB_CTRL29, 0x82}, + { OV5640_AWB_CTRL30, 0x38}, + + /* CMX Control */ + { OV5640_CMX1, 0x1e}, + { OV5640_CMX2, 0x5b}, + { OV5640_CMX3, 0x08}, + { OV5640_CMX4, 0x0a}, + { OV5640_CMX5, 0x7e}, + { OV5640_CMX6, 0x88}, + { OV5640_CMX7, 0x7c}, + { OV5640_CMX8, 0x6c}, + { OV5640_CMX9, 0x10}, + { OV5640_CMXSIGN_HI, 0x01}, + { OV5640_CMXSIGN_LO, 0x98}, + + /* CIP Control */ + { OV5640_CIP_SHARPENMT_THRESHOLD_1, 0x08}, + { OV5640_CIP_SHARPENMT_THRESHOLD_2, 0x30}, + { OV5640_CIP_SHARPENMT_OFFSET_1, 0x10}, + { OV5640_CIP_SHARPENMT_OFFSET_2, 0x00}, + { OV5640_CIP_DNS_THRESHOLD_1, 0x08}, + { OV5640_CIP_DNS_THRESHOLD_2, 0x30}, + { OV5640_CIP_DNS_OFFSET_1, 0x08}, + { OV5640_CIP_DNS_OFFSET_2, 0x16}, + { OV5640_CIP_SHARPENTH_THRESHOLD_1, 0x08}, + { OV5640_CIP_SHARPENTH_THRESHOLD_2, 0x30}, + { OV5640_CIP_SHARPENTH_OFFSET_1, 0x04}, + { OV5640_CIP_SHARPENTH_OFFSET_2, 0x06}, + + /* Gamma Control */ + { OV5640_GAMMA_CTRL00, 0x01}, + { OV5640_YST00, 0x08}, { OV5640_YST01, 0x14}, + { OV5640_YST02, 0x28}, { OV5640_YST03, 0x51}, + { OV5640_YST04, 0x65}, { OV5640_YST05, 0x71}, + { OV5640_YST06, 0x7d}, { OV5640_YST07, 0x87}, + { OV5640_YST08, 0x91}, { OV5640_YST09, 0x9a}, + { OV5640_YST0A, 0xaa}, { OV5640_YST0B, 0xb8}, + { OV5640_YST0C, 0xcd}, { OV5640_YST0D, 0xdd}, + { OV5640_YST0E, 0xea}, { OV5640_YST0F, 0x1d}, + + /* SDE Control */ + { OV5640_SDE_CTRL_0, 0x02}, + { OV5640_SDE_CTRL_3, 0x40}, + { OV5640_SDE_CTRL_4, 0x10}, + { OV5640_SDE_CTRL_9, 0x10}, + { OV5640_SDE_CTRL_10, 0x00}, + { OV5640_SDE_CTRL_11, 0xf8}, + + /* LENC Control */ + { OV5640_GMTRX00, 0x23}, { OV5640_GMTRX01, 0x14}, + { OV5640_GMTRX02, 0x0f}, { OV5640_GMTRX03, 0x0f}, + { OV5640_GMTRX04, 0x12}, { OV5640_GMTRX05, 0x26}, + { OV5640_GMTRX10, 0x0c}, { OV5640_GMTRX11, 0x08}, + { OV5640_GMTRX12, 0x05}, { OV5640_GMTRX13, 0x05}, + { OV5640_GMTRX14, 0x08}, { OV5640_GMTRX15, 0x0d}, + { OV5640_GMTRX20, 0x08}, { OV5640_GMTRX21, 0x03}, + { OV5640_GMTRX22, 0x00}, { OV5640_GMTRX23, 0x00}, + { OV5640_GMTRX24, 0x03}, { OV5640_GMTRX25, 0x09}, + { OV5640_GMTRX30, 0x07}, { OV5640_GMTRX31, 0x03}, + { OV5640_GMTRX32, 0x00}, { OV5640_GMTRX33, 0x01}, + { OV5640_GMTRX34, 0x03}, { OV5640_GMTRX35, 0x08}, + { OV5640_GMTRX40, 0x0d}, { OV5640_GMTRX41, 0x08}, + { OV5640_GMTRX42, 0x05}, { OV5640_GMTRX43, 0x06}, + { OV5640_GMTRX44, 0x08}, { OV5640_GMTRX45, 0x0e}, + { OV5640_GMTRX50, 0x29}, { OV5640_GMTRX51, 0x17}, + { OV5640_GMTRX52, 0x11}, { OV5640_GMTRX53, 0x11}, + { OV5640_GMTRX54, 0x15}, { OV5640_GMTRX55, 0x28}, + { OV5640_BRMATRX00, 0x46}, { OV5640_BRMATRX01, 0x26}, + { OV5640_BRMATRX02, 0x08}, { OV5640_BRMATRX03, 0x26}, + { OV5640_BRMATRX04, 0x64}, { OV5640_BRMATRX05, 0x26}, + { OV5640_BRMATRX06, 0x24}, { OV5640_BRMATRX07, 0x22}, + { OV5640_BRMATRX08, 0x24}, { OV5640_BRMATRX09, 0x24}, + { OV5640_BRMATRX20, 0x06}, { OV5640_BRMATRX21, 0x22}, + { OV5640_BRMATRX22, 0x40}, { OV5640_BRMATRX23, 0x42}, + { OV5640_BRMATRX24, 0x24}, { OV5640_BRMATRX30, 0x26}, + { OV5640_BRMATRX31, 0x24}, { OV5640_BRMATRX32, 0x22}, + { OV5640_BRMATRX33, 0x22}, { OV5640_BRMATRX34, 0x26}, + { OV5640_BRMATRX40, 0x44}, { OV5640_BRMATRX41, 0x24}, + { OV5640_BRMATRX42, 0x26}, { OV5640_BRMATRX43, 0x28}, + { OV5640_BRMATRX44, 0x42}, { OV5640_LENC_BR_OFFSET, 0xce}, + + { OV5640_ISP_CTRL37, 0x00}, + { OV5640_AEC_CTRL0F, 0x30}, + { OV5640_AEC_CTRL10, 0x28}, + { OV5640_AEC_CTRL1B, 0x30}, + { OV5640_AEC_CTRL1E, 0x26}, + { OV5640_AEC_CTRL11, 0x60}, + { OV5640_AEC_CTRL1F, 0x14}, + { OV5640_SYSTEM_CTRL, 0x02}, +}; + +static enum v4l2_mbus_pixelcode ov5640_codes[] = { + V4L2_MBUS_FMT_YUYV8_2X8, +}; + +static const struct v4l2_queryctrl ov5640_controls[] = { + { + .id = V4L2_CID_VFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Flip Vertically", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_HFLIP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Flip Horizontally", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, +}; + +/* read a register */ +static int ov5640_reg_read(struct i2c_client *client, u16 reg, u8 *val) +{ + int ret; + unsigned char data[2] = { reg >> 8, reg & 0xff }; + + ret = i2c_master_send(client, data, 2); + if (ret < 2) { + dev_err(&client->dev, "%s: i2c read error, reg: %x\n", + __func__, reg); + return ret < 0 ? ret : -EIO; + } + + ret = i2c_master_recv(client, val, 1); + if (ret < 1) { + dev_err(&client->dev, "%s: i2c read error, reg: %x\n", + __func__, reg); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + +/* write a register */ +static int ov5640_reg_write(struct i2c_client *client, u16 reg, u8 val) +{ + int ret; + unsigned char data[3] = { reg >> 8, reg & 0xff, val }; + + ret = i2c_master_send(client, data, 3); + if (ret < 3) { + dev_err(&client->dev, "%s: i2c write error, reg: %x\n", + __func__, reg); + return ret < 0 ? ret : -EIO; + } + + return 0; +} + + +/* Read a register, alter its bits, write it back */ +static int ov5640_reg_rmw(struct i2c_client *client, u16 reg, u8 set, u8 unset) +{ + u8 val; + int ret; + + ret = ov5640_reg_read(client, reg, &val); + if (ret < 0) { + dev_err(&client->dev, + "[Read]-Modify-Write of register 0x%04x failed!\n", + reg); + return ret; + } + + val |= set; + val &= ~unset; + + ret = ov5640_reg_write(client, reg, val); + if (ret < 0) { + dev_err(&client->dev, + "Read-Modify-[Write] of register 0x%04x failed!\n", + reg); + return ret; + } + + return 0; +} + +static int ov5640_reg_write_array(struct i2c_client *client, + const struct ov5640_reg *regarray, + int regarraylen) +{ + int i; + int ret; + + for (i = 0; i < regarraylen; i++) { + ret = ov5640_reg_write(client, + regarray[i].reg, regarray[i].val); + if (ret < 0) + return ret; + } + + return 0; +} + +/* Start/Stop streaming from the device */ +static int ov5640_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5640_priv *priv = to_ov5640(sd); + int ret; + + /* Program orientation register. */ + if (priv->flag_vflip) + ret = ov5640_reg_rmw(client, OV5640_TIMING_TC_REG20, 0x2, 0); + else + ret = ov5640_reg_rmw(client, OV5640_TIMING_TC_REG20, 0, 0x2); + if (ret < 0) + return ret; + + if (priv->flag_hflip) + ret = ov5640_reg_rmw(client, OV5640_TIMING_TC_REG21, 0x2, 0); + else + ret = ov5640_reg_rmw(client, OV5640_TIMING_TC_REG21, 0, 0x2); + if (ret < 0) + return ret; + + if (!enable) { + /* Software Reset */ + ret = ov5640_reg_write(client, OV5640_SYSTEM_CTRL, 0x82); + if (!ret) + /* Setting Streaming to Standby */ + ret = ov5640_reg_write(client, OV5640_SYSTEM_CTRL, + 0x42); + } + + priv->current_enable = enable; + + return ret; +} + +/* Alter bus settings on camera side */ +static int ov5640_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + return 0; +} + +/* Request bus settings on camera side */ +static unsigned long ov5640_query_bus_param(struct soc_camera_device *icd) +{ + struct soc_camera_link *icl = to_soc_camera_link(icd); + + unsigned long flags = SOCAM_PCLK_SAMPLE_RISING | SOCAM_MASTER | + SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_HSYNC_ACTIVE_HIGH | + SOCAM_DATA_ACTIVE_HIGH | SOCAM_DATAWIDTH_8; + + return soc_camera_apply_sensor_flags(icl, flags); +} + +/* select nearest higher resolution for capture */ +static void ov5640_res_roundup(u32 *width, u32 *height) +{ + /* Width must be a multiple of 4 pixels. */ + *width = ALIGN(*width, 4); + + /* Max resolution is 1280x720 (720p). */ + if (*width > OV5640_MAX_WIDTH) + *width = OV5640_MAX_WIDTH; + + if (*height > OV5640_MAX_HEIGHT) + *height = OV5640_MAX_HEIGHT; +} + +/* Setup registers according to resolution and color encoding */ +static int ov5640_set_res(struct i2c_client *client, u32 width, u32 height) +{ + /* Note, this stuff is bogus. It's just copied from ov9740.c. */ +#if 0 + u32 x_start; + u32 y_start; + u32 x_end; + u32 y_end; + bool scaling = 0; + u32 scale_input_x; + u32 scale_input_y; + int ret; + + if ((width != OV5640_MAX_WIDTH) || (height != OV5640_MAX_HEIGHT)) + scaling = 1; + + /* + * Try to use as much of the sensor area as possible when supporting + * smaller resolutions. Depending on the aspect ratio of the + * chosen resolution, we can either use the full width of the sensor, + * or the full height of the sensor (or both if the aspect ratio is + * the same as 1280x720. + */ + if ((OV5640_MAX_WIDTH * height) > (OV5640_MAX_HEIGHT * width)) { + scale_input_x = (OV5640_MAX_HEIGHT * width) / height; + scale_input_y = OV5640_MAX_HEIGHT; + } else { + scale_input_x = OV5640_MAX_WIDTH; + scale_input_y = (OV5640_MAX_WIDTH * height) / width; + } + + /* These describe the area of the sensor to use. */ + x_start = (OV5640_MAX_WIDTH - scale_input_x) / 2; + y_start = (OV5640_MAX_HEIGHT - scale_input_y) / 2; + x_end = x_start + scale_input_x - 1; + y_end = y_start + scale_input_y - 1; + + ret = ov5640_reg_write(client, OV5640_X_ADDR_START_HI, x_start >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_X_ADDR_START_LO, x_start & 0xff); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_Y_ADDR_START_HI, y_start >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_Y_ADDR_START_LO, y_start & 0xff); + if (ret) + goto done; + + ret = ov5640_reg_write(client, OV5640_X_ADDR_END_HI, x_end >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_X_ADDR_END_LO, x_end & 0xff); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_Y_ADDR_END_HI, y_end >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_Y_ADDR_END_LO, y_end & 0xff); + if (ret) + goto done; + + ret = ov5640_reg_write(client, OV5640_X_OUTPUT_SIZE_HI, width >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_X_OUTPUT_SIZE_LO, width & 0xff); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_Y_OUTPUT_SIZE_HI, height >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_Y_OUTPUT_SIZE_LO, height & 0xff); + if (ret) + goto done; + + ret = ov5640_reg_write(client, OV5640_ISP_CTRL1E, scale_input_x >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_ISP_CTRL1F, scale_input_x & 0xff); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_ISP_CTRL20, scale_input_y >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_ISP_CTRL21, scale_input_y & 0xff); + if (ret) + goto done; + + ret = ov5640_reg_write(client, OV5640_VFIFO_READ_START_HI, + (scale_input_x - width) >> 8); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_VFIFO_READ_START_LO, + (scale_input_x - width) & 0xff); + if (ret) + goto done; + + ret = ov5640_reg_write(client, OV5640_ISP_CTRL00, 0xff); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_ISP_CTRL01, 0xef | + (scaling << 4)); + if (ret) + goto done; + ret = ov5640_reg_write(client, OV5640_ISP_CTRL03, 0xff); + +done: + return ret; +#endif + return 0; +} + +/* set the format we will capture in */ +static int ov5640_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct ov5640_priv *priv = to_ov5640(sd); + enum v4l2_colorspace cspace; + enum v4l2_mbus_pixelcode code = mf->code; + int ret; + + ov5640_res_roundup(&mf->width, &mf->height); + + switch (code) { + case V4L2_MBUS_FMT_YUYV8_2X8: + cspace = V4L2_COLORSPACE_SRGB; + break; + default: + return -EINVAL; + } + + ret = ov5640_reg_write_array(client, ov5640_defaults, + ARRAY_SIZE(ov5640_defaults)); + if (ret < 0) + return ret; + + ret = ov5640_set_res(client, mf->width, mf->height); + if (ret < 0) + return ret; + + mf->code = code; + mf->colorspace = cspace; + + memcpy(&priv->current_mf, mf, sizeof(struct v4l2_mbus_framefmt)); + + return ret; +} + +static int ov5640_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + ov5640_res_roundup(&mf->width, &mf->height); + + mf->field = V4L2_FIELD_NONE; + mf->code = V4L2_MBUS_FMT_YUYV8_2X8; + mf->colorspace = V4L2_COLORSPACE_SRGB; + + return 0; +} + +static int ov5640_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov5640_codes)) + return -EINVAL; + + *code = ov5640_codes[index]; + + return 0; +} + +static int ov5640_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *a) +{ + a->bounds.left = 0; + a->bounds.top = 0; + a->bounds.width = OV5640_MAX_WIDTH; + a->bounds.height = OV5640_MAX_HEIGHT; + a->defrect = a->bounds; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + a->pixelaspect.numerator = 1; + a->pixelaspect.denominator = 1; + + return 0; +} + +static int ov5640_g_crop(struct v4l2_subdev *sd, struct v4l2_crop *a) +{ + a->c.left = 0; + a->c.top = 0; + a->c.width = OV5640_MAX_WIDTH; + a->c.height = OV5640_MAX_HEIGHT; + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +/* Get status of additional camera capabilities */ +static int ov5640_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov5640_priv *priv = to_ov5640(sd); + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + ctrl->value = priv->flag_vflip; + break; + case V4L2_CID_HFLIP: + ctrl->value = priv->flag_hflip; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Set status of additional camera capabilities */ +static int ov5640_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov5640_priv *priv = to_ov5640(sd); + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + priv->flag_vflip = ctrl->value; + break; + case V4L2_CID_HFLIP: + priv->flag_hflip = ctrl->value; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* Get chip identification */ +static int ov5640_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *id) +{ + struct ov5640_priv *priv = to_ov5640(sd); + + id->ident = priv->ident; + id->revision = priv->revision; + + return 0; +} + +static int ov5640_s_power(struct v4l2_subdev *sd, int on) +{ + struct ov5640_priv *priv = to_ov5640(sd); + + if (!priv->current_enable) + return 0; + + if (on) { + ov5640_s_fmt(sd, &priv->current_mf); + ov5640_s_stream(sd, priv->current_enable); + } else { + ov5640_s_stream(sd, 0); + priv->current_enable = true; + } + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov5640_get_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + u8 val; + + if (reg->reg & ~0xffff) + return -EINVAL; + + reg->size = 2; + + ret = ov5640_reg_read(client, reg->reg, &val); + if (ret) + return ret; + + reg->val = (__u64)val; + + return ret; +} + +static int ov5640_set_register(struct v4l2_subdev *sd, + struct v4l2_dbg_register *reg) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + if (reg->reg & ~0xffff || reg->val & ~0xff) + return -EINVAL; + + return ov5640_reg_write(client, reg->reg, reg->val); +} +#endif + +static int ov5640_video_probe(struct soc_camera_device *icd, + struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov5640_priv *priv = to_ov5640(sd); + u8 chip_id_hi, chip_id_lo; + int ret; + + /* We must have a parent by now. And it cannot be a wrong one. */ + BUG_ON(!icd->parent || + to_soc_camera_host(icd->parent)->nr != icd->iface); + + /* + * check and show product ID and manufacturer ID + */ + ret = ov5640_reg_read(client, OV5640_CHIP_ID_HI, &chip_id_hi); + if (ret < 0) + goto err; + + ret = ov5640_reg_read(client, OV5640_CHIP_ID_LO, &chip_id_lo); + if (ret < 0) + goto err; + + priv->chip_id = (chip_id_hi << 8) | chip_id_lo; + + if (priv->chip_id != 0x5640) { + ret = -ENODEV; + goto err; + } + + priv->ident = V4L2_IDENT_OV5640; + + dev_info(&client->dev, "Chip ID 0x%04x\n", priv->chip_id); + +err: + return ret; +} + +static struct soc_camera_ops ov5640_ops = { + .set_bus_param = ov5640_set_bus_param, + .query_bus_param = ov5640_query_bus_param, + .controls = ov5640_controls, + .num_controls = ARRAY_SIZE(ov5640_controls), +}; + +static struct v4l2_subdev_video_ops ov5640_video_ops = { + .s_stream = ov5640_s_stream, + .s_mbus_fmt = ov5640_s_fmt, + .try_mbus_fmt = ov5640_try_fmt, + .enum_mbus_fmt = ov5640_enum_fmt, + .cropcap = ov5640_cropcap, + .g_crop = ov5640_g_crop, +}; + +static struct v4l2_subdev_core_ops ov5640_core_ops = { + .g_ctrl = ov5640_g_ctrl, + .s_ctrl = ov5640_s_ctrl, + .g_chip_ident = ov5640_g_chip_ident, + .s_power = ov5640_s_power, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .g_register = ov5640_get_register, + .s_register = ov5640_set_register, +#endif +}; + +static struct v4l2_subdev_ops ov5640_subdev_ops = { + .core = &ov5640_core_ops, + .video = &ov5640_video_ops, +}; + +/* + * i2c_driver function + */ +static int ov5640_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct ov5640_priv *priv; + struct soc_camera_device *icd = client->dev.platform_data; + struct soc_camera_link *icl; + int ret; + + if (!icd) { + dev_err(&client->dev, "Missing soc-camera data!\n"); + return -EINVAL; + } + + icl = to_soc_camera_link(icd); + if (!icl) { + dev_err(&client->dev, "Missing platform_data for driver\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(struct ov5640_priv), GFP_KERNEL); + if (!priv) { + dev_err(&client->dev, "Failed to allocate private data!\n"); + return -ENOMEM; + } + + v4l2_i2c_subdev_init(&priv->subdev, client, &ov5640_subdev_ops); + + icd->ops = &ov5640_ops; + + ret = ov5640_video_probe(icd, client); + if (ret < 0) { + icd->ops = NULL; + kfree(priv); + } + + return ret; +} + +static int ov5640_remove(struct i2c_client *client) +{ + struct ov5640_priv *priv = i2c_get_clientdata(client); + + kfree(priv); + + return 0; +} + +static const struct i2c_device_id ov5640_id[] = { + { "ov5640", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov5640_id); + +static struct i2c_driver ov5640_i2c_driver = { + .driver = { + .name = "ov5640", + }, + .probe = ov5640_probe, + .remove = ov5640_remove, + .id_table = ov5640_id, +}; + +static int __init ov5640_module_init(void) +{ + return i2c_add_driver(&ov5640_i2c_driver); +} + +static void __exit ov5640_module_exit(void) +{ + i2c_del_driver(&ov5640_i2c_driver); +} + +module_init(ov5640_module_init); +module_exit(ov5640_module_exit); + +MODULE_DESCRIPTION("SoC Camera driver for OmniVision OV5640"); +MODULE_AUTHOR("Andrew Chew "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3