From 58e5e10f28230fc9a9815b73948a6678e886550a Mon Sep 17 00:00:00 2001 From: Wojciech Bieganski Date: Tue, 4 Mar 2014 16:05:13 +0100 Subject: OV7670 and TVP5150 drivers added, updated configs --- arch/arm/configs/apalis_t30_defconfig | 2 + arch/arm/mach-tegra/board-apalis_t30.c | 48 ++ arch/arm/mach-tegra/board-colibri_t20.c | 47 ++ arch/arm/mach-tegra/board-colibri_t30.c | 48 ++ drivers/media/video/Kconfig | 12 + drivers/media/video/Makefile | 2 + drivers/media/video/ov7670soc.c | 746 ++++++++++++++++++++++++++++++++ drivers/media/video/tvp5150_reg.h | 3 + drivers/media/video/tvp5150soc.c | 538 +++++++++++++++++++++++ 9 files changed, 1446 insertions(+) create mode 100644 drivers/media/video/ov7670soc.c create mode 100644 drivers/media/video/tvp5150soc.c diff --git a/arch/arm/configs/apalis_t30_defconfig b/arch/arm/configs/apalis_t30_defconfig index d1c2e57384a9..56a68d00cd95 100644 --- a/arch/arm/configs/apalis_t30_defconfig +++ b/arch/arm/configs/apalis_t30_defconfig @@ -275,6 +275,8 @@ CONFIG_TEGRA_NVAVP=y CONFIG_VIDEO_ADV7180=m CONFIG_SOC_CAMERA=y CONFIG_SOC_CAMERA_MAX9526=m +CONFIG_SOC_CAMERA_OV7670SOC=m +CONFIG_SOC_CAMERA_TVP5150=m CONFIG_VIDEO_TEGRA=m CONFIG_USB_VIDEO_CLASS=y # CONFIG_RADIO_ADAPTERS is not set diff --git a/arch/arm/mach-tegra/board-apalis_t30.c b/arch/arm/mach-tegra/board-apalis_t30.c index f7340b91eb8d..6b2a39e7bcce 100644 --- a/arch/arm/mach-tegra/board-apalis_t30.c +++ b/arch/arm/mach-tegra/board-apalis_t30.c @@ -158,6 +158,48 @@ static struct platform_device soc_camera_adv7180 = { }, }; #endif /* CONFIG_VIDEO_ADV7180 | CONFIG_VIDEO_ADV7180_MODULE */ + +#if defined(CONFIG_SOC_CAMERA_TVP5150) || defined(CONFIG_SOC_CAMERA_TVP5150_MODULE) +static struct i2c_board_info camera_i2c_tvp5150soc = { + I2C_BOARD_INFO("tvp5150soc", 0x5d), +}; + +static struct soc_camera_link iclink_tvp5150soc = { + .board_info = &camera_i2c_tvp5150soc, + .bus_id = -1, /* This must match the .id of tegra_vi01_device */ + .i2c_adapter_id = 2, +}; + +static struct platform_device soc_camera_tvp5150soc = { + .name = "soc-camera-pdrv", + .id = 2, + .dev = { + .platform_data = &iclink_tvp5150soc, + }, +}; +#endif /* CONFIG_SOC_CAMERA_TVP5150 | CONFIG_SOC_CAMERA_TVP5150_MODULE */ + +#if defined(CONFIG_SOC_CAMERA_OV7670SOC) || defined(CONFIG_SOC_CAMERA_OV7670SOC_MODULE) +static struct i2c_board_info camera_i2c_ov7670soc = { + I2C_BOARD_INFO("ov7670soc", 0x21), +}; + +static struct soc_camera_link iclink_ov7670soc = { + .board_info = &camera_i2c_ov7670soc, + .bus_id = -1, /* This must match the .id of tegra_vi01_device */ + .i2c_adapter_id = 2, +}; + +static struct platform_device soc_camera_ov7670soc = { + .name = "soc-camera-pdrv", + .id = 3, + .dev = { + .platform_data = &iclink_ov7670soc, + }, +}; + +#endif /* CONFIG_SOC_CAMERA_OV7670SOC | CONFIG_SOC_CAMERA_OV7670SOC_MODULE */ + #endif /* CONFIG_VIDEO_TEGRA | CONFIG_VIDEO_TEGRA_MODULE */ /* CAN */ @@ -1392,6 +1434,12 @@ static void __init apalis_t30_init(void) #if defined(CONFIG_VIDEO_ADV7180) || defined(CONFIG_VIDEO_ADV7180_MODULE) platform_device_register(&soc_camera_adv7180); #endif +#if defined(CONFIG_SOC_CAMERA_TVP5150) || defined(CONFIG_SOC_CAMERA_TVP5150_MODULE) + platform_device_register(&soc_camera_tvp5150soc); +#endif +#if defined(CONFIG_SOC_CAMERA_OV7670SOC) || defined(CONFIG_SOC_CAMERA_OV7670SOC_MODULE) + platform_device_register(&soc_camera_ov7670soc); +#endif #endif /* CONFIG_VIDEO_TEGRA | CONFIG_VIDEO_TEGRA_MODULE */ tegra_release_bootloader_fb(); diff --git a/arch/arm/mach-tegra/board-colibri_t20.c b/arch/arm/mach-tegra/board-colibri_t20.c index 470cc004165a..e8bb48e27865 100644 --- a/arch/arm/mach-tegra/board-colibri_t20.c +++ b/arch/arm/mach-tegra/board-colibri_t20.c @@ -162,6 +162,47 @@ static struct platform_device soc_camera_adv7180 = { }, }; #endif /* CONFIG_VIDEO_ADV7180 | CONFIG_VIDEO_ADV7180_MODULE */ + +#if defined(CONFIG_SOC_CAMERA_TVP5150) || defined(CONFIG_SOC_CAMERA_TVP5150_MODULE) +static struct i2c_board_info camera_i2c_tvp5150soc = { + I2C_BOARD_INFO("tvp5150soc", 0x5d), +}; + +static struct soc_camera_link iclink_tvp5150soc = { + .board_info = &camera_i2c_tvp5150soc, + .bus_id = -1, /* This must match the .id of tegra_vi01_device */ + .i2c_adapter_id = 0, +}; + +static struct platform_device soc_camera_tvp5150soc = { + .name = "soc-camera-pdrv", + .id = 2, + .dev = { + .platform_data = &iclink_tvp5150soc, + }, +}; +#endif /* CONFIG_SOC_CAMERA_TVP5150 | CONFIG_SOC_CAMERA_TVP5150_MODULE */ + +#if defined(CONFIG_SOC_CAMERA_OV7670SOC) || defined(CONFIG_SOC_CAMERA_OV7670SOC_MODULE) +static struct i2c_board_info camera_i2c_ov7670soc = { + I2C_BOARD_INFO("ov7670soc", 0x21), +}; + +static struct soc_camera_link iclink_ov7670soc = { + .board_info = &camera_i2c_ov7670soc, + .bus_id = -1, /* This must match the .id of tegra_vi01_device */ + .i2c_adapter_id = 0, +}; + +static struct platform_device soc_camera_ov7670soc = { + .name = "soc-camera-pdrv", + .id = 3, + .dev = { + .platform_data = &iclink_ov7670soc, + }, +}; +#endif /* CONFIG_SOC_CAMERA_OV7670SOC | CONFIG_SOC_CAMERA_OV7670SOC_MODULE */ + #endif /* CONFIG_VIDEO_TEGRA | CONFIG_VIDEO_TEGRA_MODULE */ /* CAN */ @@ -1531,6 +1572,12 @@ static void __init colibri_t20_init(void) #if defined(CONFIG_VIDEO_ADV7180) || defined(CONFIG_VIDEO_ADV7180_MODULE) platform_device_register(&soc_camera_adv7180); #endif +#if defined(CONFIG_SOC_CAMERA_TVP5150) || defined(CONFIG_SOC_CAMERA_TVP5150_MODULE) + platform_device_register(&soc_camera_tvp5150soc); +#endif +#if defined(CONFIG_SOC_CAMERA_OV7670SOC) || defined(CONFIG_SOC_CAMERA_OV7670SOC_MODULE) + platform_device_register(&soc_camera_ov7670soc); +#endif #endif /* CONFIG_VIDEO_TEGRA | CONFIG_VIDEO_TEGRA_MODULE */ diff --git a/arch/arm/mach-tegra/board-colibri_t30.c b/arch/arm/mach-tegra/board-colibri_t30.c index db3ab08cbc3d..c35f2737f9df 100644 --- a/arch/arm/mach-tegra/board-colibri_t30.c +++ b/arch/arm/mach-tegra/board-colibri_t30.c @@ -158,6 +158,47 @@ static struct platform_device soc_camera_adv7180 = { }, }; #endif /* CONFIG_VIDEO_ADV7180 | CONFIG_VIDEO_ADV7180_MODULE */ + +#if defined(CONFIG_SOC_CAMERA_TVP5150) || defined(CONFIG_SOC_CAMERA_TVP5150_MODULE) +static struct i2c_board_info camera_i2c_tvp5150soc = { + I2C_BOARD_INFO("tvp5150soc", 0x5d), +}; + +static struct soc_camera_link iclink_tvp5150soc = { + .board_info = &camera_i2c_tvp5150soc, + .bus_id = -1, /* This must match the .id of tegra_vi01_device */ + .i2c_adapter_id = 0, +}; + +static struct platform_device soc_camera_tvp5150soc = { + .name = "soc-camera-pdrv", + .id = 2, + .dev = { + .platform_data = &iclink_tvp5150soc, + }, +}; +#endif /* CONFIG_SOC_CAMERA_TVP5150 | CONFIG_SOC_CAMERA_TVP5150_MODULE */ + +#if defined(CONFIG_SOC_CAMERA_OV7670SOC) || defined(CONFIG_SOC_CAMERA_OV7670SOC_MODULE) +static struct i2c_board_info camera_i2c_ov7670soc = { + I2C_BOARD_INFO("ov7670soc", 0x21), +}; + +static struct soc_camera_link iclink_ov7670soc = { + .board_info = &camera_i2c_ov7670soc, + .bus_id = -1, /* This must match the .id of tegra_vi01_device */ + .i2c_adapter_id = 0, +}; + +static struct platform_device soc_camera_ov7670soc = { + .name = "soc-camera-pdrv", + .id = 3, + .dev = { + .platform_data = &iclink_ov7670soc, + }, +}; +#endif /* CONFIG_SOC_CAMERA_OV7670SOC | CONFIG_SOC_CAMERA_OV7670SOC_MODULE */ + #endif /* CONFIG_VIDEO_TEGRA | CONFIG_VIDEO_TEGRA_MODULE */ /* CAN */ @@ -281,6 +322,7 @@ static struct tegra_clk_init_table colibri_t30_clk_init_table[] __initdata = { {"spdif_out", "pll_a_out0", 0, false}, {"vi", "pll_p", 0, false}, {"vi_sensor", "pll_p", 150000000, false}, + {"clk_out_2", "extern2", 24000000, false}, {NULL, NULL, 0, 0}, }; @@ -1511,6 +1553,12 @@ static void __init colibri_t30_init(void) #if defined(CONFIG_VIDEO_ADV7180) || defined(CONFIG_VIDEO_ADV7180_MODULE) platform_device_register(&soc_camera_adv7180); #endif +#if defined(CONFIG_SOC_CAMERA_TVP5150) || defined(CONFIG_SOC_CAMERA_TVP5150_MODULE) + platform_device_register(&soc_camera_tvp5150soc); +#endif +#if defined(CONFIG_SOC_CAMERA_OV7670SOC) || defined(CONFIG_SOC_CAMERA_OV7670SOC_MODULE) + platform_device_register(&soc_camera_ov7670soc); +#endif #endif /* CONFIG_VIDEO_TEGRA | CONFIG_VIDEO_TEGRA_MODULE */ tegra_release_bootloader_fb(); diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 0fe628f5912d..9f8b400886e4 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -884,6 +884,12 @@ config SOC_CAMERA_OV6650 ---help--- This is a V4L2 SoC camera driver for the OmniVision OV6650 sensor +config SOC_CAMERA_OV7670SOC + tristate "ov7670soc sensor support (NEW)" + depends on SOC_CAMERA && I2C + ---help--- + This is a V4L2 SoC camera driver for the OmniVision OV7670 sensor + config SOC_CAMERA_OV772X tristate "ov772x camera support" depends on SOC_CAMERA && I2C @@ -902,6 +908,12 @@ config SOC_CAMERA_OV9740 help This is a ov9740 camera driver +config SOC_CAMERA_TVP5150 + tristate "tvp5150soc support (NEW)" + depends on SOC_CAMERA && I2C + help + This is a V4L2 SoC camera driver for the Texas Instruments TVP5150 chip + config MX1_VIDEO bool diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 28362059e105..fecfe0fd1075 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -84,10 +84,12 @@ obj-$(CONFIG_SOC_CAMERA_OV5640) += ov5640.o obj-$(CONFIG_SOC_CAMERA_OV5642) += ov5642.o obj-$(CONFIG_SOC_CAMERA_OV5650) += ov5650.o obj-$(CONFIG_SOC_CAMERA_OV6650) += ov6650.o +obj-$(CONFIG_SOC_CAMERA_OV7670SOC) += ov7670soc.o obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o obj-$(CONFIG_SOC_CAMERA_OV9640) += ov9640.o obj-$(CONFIG_SOC_CAMERA_OV9740) += ov9740.o obj-$(CONFIG_SOC_CAMERA_RJ54N1) += rj54n1cb0c.o +obj-$(CONFIG_SOC_CAMERA_TVP5150) += tvp5150soc.o obj-$(CONFIG_SOC_CAMERA_TW9910) += tw9910.o # And now the v4l2 drivers: diff --git a/drivers/media/video/ov7670soc.c b/drivers/media/video/ov7670soc.c new file mode 100644 index 000000000000..5e88d2ea3995 --- /dev/null +++ b/drivers/media/video/ov7670soc.c @@ -0,0 +1,746 @@ +/* + * drivers/media/video/ov7670soc.c + * + * OmniVision OV7670 cameras driver + * + * Copyright (c) 2011 Ming-Yao Chen + * (based on tvp514x.c) + * + * Copyright (c) 2013 Ant Micro + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* MODULE NAME*/ +#define OV7670SOC_MODULE_NAME "ov7670soc" + +/* Private macros for OV7670 */ +#define I2C_RETRY_COUNT (5) +#define VGA_WIDTH (640) +#define VGA_HEIGHT (480) + +/* Registers */ +#define REG_GAIN 0x00 /* Gain lower 8 bits (rest in vref) */ +#define REG_BLUE 0x01 /* blue gain */ +#define REG_RED 0x02 /* red gain */ +#define REG_VREF 0x03 /* Pieces of GAIN, VSTART, VSTOP */ +#define REG_COM1 0x04 /* Control 1 */ +#define COM1_CCIR656 0x40 /* CCIR656 enable */ +#define REG_BAVE 0x05 /* U/B Average level */ +#define REG_GbAVE 0x06 /* Y/Gb Average level */ +#define REG_AECHH 0x07 /* AEC MS 5 bits */ +#define REG_RAVE 0x08 /* V/R Average level */ +#define REG_COM2 0x09 /* Control 2 */ +#define COM2_SSLEEP 0x10 /* Soft sleep mode */ +#define REG_PID 0x0a /* Product ID MSB */ +#define REG_VER 0x0b /* Product ID LSB */ +#define REG_COM3 0x0c /* Control 3 */ +#define COM3_SWAP 0x40 /* Byte swap */ +#define COM3_SCALEEN 0x08 /* Enable scaling */ +#define COM3_DCWEN 0x04 /* Enable downsamp/crop/window */ +#define REG_COM4 0x0d /* Control 4 */ +#define REG_COM5 0x0e /* All "reserved" */ +#define REG_COM6 0x0f /* Control 6 */ +#define REG_AECH 0x10 /* More bits of AEC value */ +#define REG_CLKRC 0x11 /* Clocl control */ +#define CLK_EXT 0x40 /* Use external clock directly */ +#define CLK_SCALE 0x3f /* Mask for internal clock scale */ +#define REG_COM7 0x12 /* Control 7 */ +#define COM7_RESET 0x80 /* Register reset */ +#define COM7_FMT_MASK 0x38 +#define COM7_FMT_VGA 0x00 +#define COM7_FMT_CIF 0x20 /* CIF format */ +#define COM7_FMT_QVGA 0x10 /* QVGA format */ +#define COM7_FMT_QCIF 0x08 /* QCIF format */ +#define COM7_RGB 0x04 /* bits 0 and 2 - RGB format */ +#define COM7_YUV 0x00 /* YUV */ +#define COM7_BAYER 0x01 /* Bayer format */ +#define COM7_PBAYER 0x05 /* "Processed bayer" */ +#define REG_COM8 0x13 /* Control 8 */ +#define COM8_FASTAEC 0x80 /* Enable fast AGC/AEC */ +#define COM8_AECSTEP 0x40 /* Unlimited AEC step size */ +#define COM8_BFILT 0x20 /* Band filter enable */ +#define COM8_AGC 0x04 /* Auto gain enable */ +#define COM8_AWB 0x02 /* White balance enable */ +#define COM8_AEC 0x01 /* Auto exposure enable */ +#define REG_COM9 0x14 /* Control 9 - gain ceiling */ +#define REG_COM10 0x15 /* Control 10 */ +#define COM10_HSYNC 0x40 /* HSYNC instead of HREF */ +#define COM10_PCLK_HB 0x20 /* Suppress PCLK on horiz blank */ +#define COM10_HREF_REV 0x08 /* Reverse HREF */ +#define COM10_VS_LEAD 0x04 /* VSYNC on clock leading edge */ +#define COM10_VS_NEG 0x02 /* VSYNC negative */ +#define COM10_HS_NEG 0x01 /* HSYNC negative */ +#define REG_HSTART 0x17 /* Horiz start high bits */ +#define REG_HSTOP 0x18 /* Horiz stop high bits */ +#define REG_VSTART 0x19 /* Vert start high bits */ +#define REG_VSTOP 0x1a /* Vert stop high bits */ +#define REG_PSHFT 0x1b /* Pixel delay after HREF */ +#define REG_MIDH 0x1c /* Manuf. ID high */ +#define REG_MIDL 0x1d /* Manuf. ID low */ +#define REG_MVFP 0x1e /* Mirror / vflip */ +#define MVFP_MIRROR 0x20 /* Mirror image */ +#define MVFP_FLIP 0x10 /* Vertical flip */ +#define REG_AEW 0x24 /* AGC upper limit */ +#define REG_AEB 0x25 /* AGC lower limit */ +#define REG_VPT 0x26 /* AGC/AEC fast mode op region */ +#define REG_HSYST 0x30 /* HSYNC rising edge delay */ +#define REG_HSYEN 0x31 /* HSYNC falling edge delay */ +#define REG_HREF 0x32 /* HREF pieces */ +#define REG_TSLB 0x3a /* lots of stuff */ +#define TSLB_YLAST 0x08 /* UYVY or VYUY - see com13 */ +#define REG_COM11 0x3b /* Control 11 */ +#define COM11_NIGHT 0x80 /* NIght mode enable */ +#define COM11_NMFR 0x60 /* Two bit NM frame rate */ +#define COM11_HZAUTO 0x10 /* Auto detect 50/60 Hz */ +#define COM11_50HZ 0x08 /* Manual 50Hz select */ +#define COM11_EXP 0x02 +#define REG_COM12 0x3c /* Control 12 */ +#define COM12_HREF 0x80 /* HREF always */ +#define REG_COM13 0x3d /* Control 13 */ +#define COM13_GAMMA 0x80 /* Gamma enable */ +#define COM13_UVSAT 0x40 /* UV saturation auto adjustment */ +#define COM13_UVSWAP 0x01 /* V before U - w/TSLB */ +#define REG_COM14 0x3e /* Control 14 */ +#define COM14_DCWEN 0x10 /* DCW/PCLK-scale enable */ +#define REG_EDGE 0x3f /* Edge enhancement factor */ +#define REG_COM15 0x40 /* Control 15 */ +#define COM15_R10F0 0x00 /* Data range 10 to F0 */ +#define COM15_R01FE 0x80 /* 01 to FE */ +#define COM15_R00FF 0xc0 /* 00 to FF */ +#define COM15_RGB565 0x10 /* RGB565 output */ +#define COM15_RGB555 0x30 /* RGB555 output */ +#define REG_COM16 0x41 /* Control 16 */ +#define COM16_AWBGAIN 0x08 /* AWB gain enable */ +#define REG_COM17 0x42 /* Control 17 */ +#define COM17_AECWIN 0xc0 /* AEC window - must match COM4 */ +#define COM17_CBAR 0x08 /* DSP Color bar */ +/* + * This matrix defines how the colors are generated, must be + * tweaked to adjust hue and saturation. + * + * Order: v-red, v-green, v-blue, u-red, u-green, u-blue + * + * They are nine-bit signed quantities, with the sign bit + * stored in 0x58. Sign for v-red is bit 0, and up from there. + */ +#define REG_CMATRIX_BASE 0x4f +#define CMATRIX_LEN 6 +#define REG_MTX1 0x4f +#define REG_MTX2 0x50 +#define REG_MTX3 0x51 +#define REG_MTX4 0x52 +#define REG_MTX5 0x53 +#define REG_MTX6 0x54 +#define REG_BRIGHTNESS 0x55 /* Brightness */ +#define REG_CONTRAST 0x56 /* Contrast control */ +#define REG_CMATRIX_SIGN 0x58 +#define REG_GFIX 0x69 /* Fix gain control */ +#define REG_DBLV 0x6b /* PLL control an debugging */ +#define DBLV_BYPASS 0x00 /* Bypass PLL */ +#define DBLV_X4 0x01 /* clock x4 */ +#define DBLV_X6 0x10 /* clock x6 */ +#define DBLV_X8 0x11 /* clock x8 */ +#define REG_SCALING_X 0x70 +#define REG_SCALING_Y 0x71 +#define REG_REG76 0x76 /* OV's name */ +#define R76_BLKPCOR 0x80 /* Black pixel correction enable */ +#define R76_WHTPCOR 0x40 /* White pixel correction enable */ +#define REG_RGB444 0x8c /* RGB 444 control */ +#define R444_ENABLE 0x02 /* Turn on RGB444, overrides 5x5 */ +#define R444_RGBX 0x01 /* Empty nibble at end */ +#define REG_HAECC1 0x9f /* Hist AEC/AGC control 1 */ +#define REG_HAECC2 0xa0 /* Hist AEC/AGC control 2 */ +#define REG_BD50MAX 0xa5 /* 50hz banding step limit */ +#define REG_HAECC3 0xa6 /* Hist AEC/AGC control 3 */ +#define REG_HAECC4 0xa7 /* Hist AEC/AGC control 4 */ +#define REG_HAECC5 0xa8 /* Hist AEC/AGC control 5 */ +#define REG_HAECC6 0xa9 /* Hist AEC/AGC control 6 */ +#define REG_HAECC7 0xaa /* Hist AEC/AGC control 7 */ +#define REG_BD60MAX 0xab /* 60hz banding step limit */ + +/* + * Store information about the video data format. The color matrix + * is deeply tied into the format, so keep the relevant values here. + * The magic matrix numbers come from OmniVision. + */ +static struct ov7670soc_format_struct { + enum v4l2_mbus_pixelcode mbus_code; + enum v4l2_colorspace colorspace; + struct regval_list *regs; + int cmatrix[CMATRIX_LEN]; +} ov7670soc_formats[] = { + { + /* TODO: registers are set for UYUV, but we are present as YUYV, otherwise image is + * invalid + */ + .mbus_code = V4L2_MBUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + /*TODO: list of regs with preset values for specified format + .regs = ov7670soc_fmt_yuv422,*/ + .cmatrix = { -64, -52, -12, -23, -41, 132 }, + }, +}; +#define N_OV7670_FMTS ARRAY_SIZE(ov7670soc_formats) + +/** + * struct ov7670soc_decoder - OV7670 decoder object + * sd: Subdevice Slave handle + * TODO: ov7670soc_regs: copy of hw's regs with preset values. + * pdata: Board specific + * fmt_list: Format list + * num_fmts: Number of formats + * fmt: Current format + * brightness: only for queries + * contrast: only for queries + * saturation: only for queries + * hue: only for queries + */ +struct ov7670soc_decoder { + struct v4l2_subdev sd; + /* + TODO: list of def reg values for read queries and RMW operations. + Local cache due to impossibility of registers read + struct ov7670soc_reg ov7670soc_regs[ARRAY_SIZE(ov7670soc_reg_list_default)];*/ + const struct ov7670soc_platform_data *pdata; + const struct ov7670soc_format_struct *fmt_list; + int num_fmts; + struct ov7670soc_format_struct *fmt; /* Current format */ + int brightness; + int contrast; + int saturation; + int hue; +}; + +static const struct v4l2_queryctrl ov7670soc_controls[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + }, + { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "contrast", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 64, + }, + { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "saturation", + .minimum = 0, + .maximum = 256, + .step = 1, + .default_value = 128, + }, + { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "hue", + .minimum = -180, + .maximum = 180, + .step = 5, + .default_value = 0, + }, +}; + +static inline struct ov7670soc_decoder *to_decoder(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ov7670soc_decoder, sd); +} + +/* + * Read register prohibited on Tegra T-20 due to + * arbitration lost after sending device read addres + */ +#ifndef CONFIG_ARCH_TEGRA_2x_SOC +static int ov7670soc_read_reg(struct i2c_client *client, unsigned char reg, unsigned char *val) +{ + int err, retry = 0; + +read_again: + err = i2c_smbus_read_byte_data(client, reg); + if (err < 0) { + if (retry <= I2C_RETRY_COUNT) { + retry++; + msleep_interruptible(10); + goto read_again; + } + dev_err(&client->dev, "Failed to read register 0x%02X!\n", reg); + } + *val = (unsigned char)err; + return err; +} +#endif + +static int ov7670soc_write_reg(struct i2c_client *client, unsigned char reg, unsigned char val) +{ + int err, retry = 0; + +write_again: + err = i2c_smbus_write_byte_data(client, reg, val); + if (err) { + if (retry <= I2C_RETRY_COUNT) { + retry++; + msleep_interruptible(10); + goto write_again; + } + dev_err(&client->dev, "Failed to write 0x%02X to register 0x%02X!\n", val, reg); + } + + return err; +} + +/* + * Reset all camera registers to default values + */ +static int ov7670soc_reset(struct i2c_client *client) +{ + int ret; + + ret = ov7670soc_write_reg(client, REG_COM7, COM7_RESET); + if (ret) + dev_err(&client->dev, "An error occurred while entering soft reset!\n"); + + return ret; +} + +/* + * ov7670soc_setup - initializes a list of OV7670 registers + */ +static int ov7670soc_setup(struct v4l2_subdev *sd, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + ov7670soc_reset(client); + + /* Configure HSYNC & VSYNC */ + ov7670soc_write_reg(client, REG_COM10, COM10_HSYNC | COM10_VS_NEG); + /* not sure what this does, but improves colors quality */ + ov7670soc_write_reg(client, 0xb0, 0x84); + + /* Config HSYNC offset */ + ov7670soc_write_reg(client, REG_HSTART, 0x00); + ov7670soc_write_reg(client, REG_HSTOP, 0x00); + ov7670soc_write_reg(client, REG_HSYST, 0x00); + ov7670soc_write_reg(client, REG_HSYEN, 0x05); + /* default MTX1..MTX5 and MTX6 = 0x84 makes better image */ + ov7670soc_write_reg(client, REG_MTX6, 0x84); + + //TODO: output format, move to mbus_s_fmt + ov7670soc_write_reg(client, REG_COM7, COM7_YUV); /* Selects YUV mode */ + /* U before V */ + ov7670soc_write_reg(client, REG_COM13, COM13_GAMMA | COM13_UVSAT); + + return 0; +} + +/* + * Hue also requires messing with the color matrix. It also requires + * trig functions, which tend not to be well supported in the kernel. + * So here is a simple table of sine values, 0-90 degrees, in steps + * of five degrees. Values are multiplied by 1000. + * + * The following naive approximate trig functions require an argument + * carefully limited to -180 <= theta <= 180. + */ +#define SIN_STEP 5 +static const int ov7670soc_sin_table[] = { + 0, 87, 173, 258, 342, 422, + 499, 573, 642, 707, 766, 819, + 866, 906, 939, 965, 984, 996, + 1000 +}; + +static int ov7670soc_sine(int theta) +{ + int chs = 1; + int sine; + + if (theta < 0) { + theta = -theta; + chs = -1; + } + if (theta <= 90) + sine = ov7670soc_sin_table[theta/SIN_STEP]; + else { + theta -= 90; + sine = 1000 - ov7670soc_sin_table[theta/SIN_STEP]; + } + return sine*chs; +} + +static int ov7670soc_cosine(int theta) +{ + theta = 90 - theta; + if (theta > 180) + theta -= 360; + else if (theta < -180) + theta += 360; + return ov7670soc_sine(theta); +} + +static void ov7670soc_calc_cmatrix(struct ov7670soc_decoder *decoder, int matrix[CMATRIX_LEN], int sat, int hue) +{ + int i; + /* + * Apply the current saturation setting first. + */ + for (i = 0; i < CMATRIX_LEN; i++) + matrix[i] = (decoder->fmt->cmatrix[i] * sat) >> 7; + /* + * Then, if need be, rotate the hue value. + */ + if (hue != 0) { + int sinth, costh; + sinth = ov7670soc_sine(hue); + costh = ov7670soc_cosine(hue); + + matrix[0] = (matrix[3]*sinth + matrix[0]*costh)/1000; + matrix[1] = (matrix[4]*sinth + matrix[1]*costh)/1000; + matrix[2] = (matrix[5]*sinth + matrix[2]*costh)/1000; + matrix[3] = (matrix[3]*costh - matrix[0]*sinth)/1000; + matrix[4] = (matrix[4]*costh - matrix[1]*sinth)/1000; + matrix[5] = (matrix[5]*costh - matrix[2]*sinth)/1000; + } +} + +static int ov7670soc_store_cmatrix(struct v4l2_subdev *sd, int matrix[CMATRIX_LEN]) +{ + int i, ret; + unsigned char signbits = 0; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* + * Weird crap seems to exist in the upper part of + * the sign bits register, so let's preserve it. + */ +/*#ifndef CONFIG_ARCH_TEGRA_2x_SOC + /* TODO: read forbidden on T-20 + ret = ov7670soc_read_reg(client, REG_CMATRIX_SIGN, &signbits); + signbits &= 0xc0; +#endif*/ + for (i = 0; i < CMATRIX_LEN; i++) { + unsigned char raw; + + if (matrix[i] < 0) { + signbits |= (1 << i); + if (matrix[i] < -255) + raw = 0xff; + else + raw = (-1 * matrix[i]) & 0xff; + } + else { + if (matrix[i] > 255) + raw = 0xff; + else + raw = matrix[i] & 0xff; + } + ret += ov7670soc_write_reg(client, REG_CMATRIX_BASE + i, raw); + } + ret += ov7670soc_write_reg(client, REG_CMATRIX_SIGN, signbits); + return ret; +} + +static unsigned char ov7670soc_abs_to_sm(unsigned char v) +{ + if (v > 127) + return v & 0x7f; + return (128 - v) | 0x80; +} + +static int ov7670soc_s_brightness(struct v4l2_subdev *sd, int value) +{ + unsigned char v;//, com8 = 0; + int ret; + struct i2c_client *client = v4l2_get_subdevdata(sd); + + /* TODO: read forbidden on T-20 */ +/*#ifndef CONFIG_ARCH_TEGRA_2x_SOC + unsigned char com8 = 0; + ret = ov7670soc_read_reg(client, REG_COM8, &com8); + com8 &= ~COM8_AEC; +#endif*/ + + ov7670soc_write_reg(client, REG_COM8, 0x8e); //defaul val is 0x8f, 0x8e->disable AEC + v = ov7670soc_abs_to_sm(value); + ret = ov7670soc_write_reg(client, REG_BRIGHTNESS, v); + return ret; +} + +static int ov7670soc_s_contrast(struct v4l2_subdev *sd, int value) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return ov7670soc_write_reg(client, REG_CONTRAST, (unsigned char) value); +} + +static int ov7670soc_s_sat_hue(struct v4l2_subdev *sd, int sat, int hue) +{ + struct ov7670soc_decoder *decoder = to_decoder(sd); + int matrix[CMATRIX_LEN]; + int ret; + + ov7670soc_calc_cmatrix(decoder, matrix, sat, hue); + ret = ov7670soc_store_cmatrix(sd, matrix); + return ret; +} + +static int ov7670soc_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov7670soc_decoder *decoder = to_decoder(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + return ov7670soc_s_brightness(sd, ctrl->value); + case V4L2_CID_CONTRAST: + return ov7670soc_s_contrast(sd, ctrl->value); + case V4L2_CID_SATURATION: + return ov7670soc_s_sat_hue(sd, ctrl->value, decoder->hue); + case V4L2_CID_HUE: + return ov7670soc_s_sat_hue(sd, decoder->saturation, ctrl->value); + } + return -EINVAL; +} + +static int ov7670soc_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + struct ov7670soc_decoder *decoder = to_decoder(sd); + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = decoder->brightness; + break; + case V4L2_CID_CONTRAST: + ctrl->value = decoder->contrast; + break; + case V4L2_CID_SATURATION: + ctrl->value = decoder->saturation; + break; + case V4L2_CID_HUE: + ctrl->value = decoder->hue; + break; + } + return 0; +} + +/* Functions required by soc_camera_ops ***************************************/ +/* Alter bus settings on camera side */ +static int ov7670soc_set_bus_param(struct soc_camera_device *icd, unsigned long flags) +{ + return 0; +} + +static unsigned long ov7670soc_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); +} +/******************************************************************************/ + +/* Functions required by v4l2_subdev_video_ops ********************************/ +static int ov7670soc_s_stream(struct v4l2_subdev *sd, int enable) +{ + /* + The OV7670 camera dose not have ability to start/stop streaming + */ + return 0; +} + +static int ov7670soc_s_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + /* + * Set the image format. Currently we support only one format with + * fixed resolution, so we can set the format as it is on camera startup. + */ + ov7670soc_setup(sd, 0); + + return 0; +} + +static int ov7670soc_try_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + struct ov7670soc_decoder *decoder = to_decoder(sd); + int index; + + /* Check if we support queried image format. */ + for (index = 0; index < N_OV7670_FMTS; index++) + if (ov7670soc_formats[index].mbus_code == mf->code) + break; + /* If not, set the only one which we support */ + if (index >= N_OV7670_FMTS) { + /* default to first format */ + index = 0; + mf->code = ov7670soc_formats[0].mbus_code; + } + + /* Store the current format */ + decoder->fmt = &ov7670soc_formats[index]; + + /* Fixed value, move to ov7670_formats */ + mf->field = V4L2_FIELD_NONE; + /* TODO: support for other resolutions (CIF/QCIF etc).*/ + mf->width = VGA_WIDTH; + mf->height = VGA_HEIGHT; + mf->colorspace = decoder->fmt->colorspace; + + return 0; +} + +static int ov7670soc_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index, enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(ov7670soc_formats)) + return -EINVAL; + + *code = ov7670soc_formats[index].mbus_code; + + return 0; +} +/******************************************************************************/ + +static struct soc_camera_ops ov7670soc_soc_camera_ops = { + .set_bus_param = ov7670soc_set_bus_param, + .query_bus_param = ov7670soc_query_bus_param, + .controls = ov7670soc_controls, + .num_controls = ARRAY_SIZE(ov7670soc_controls), +}; + +static const struct v4l2_subdev_core_ops ov7670soc_core_ops = { + .g_ctrl = ov7670soc_g_ctrl, + .s_ctrl = ov7670soc_s_ctrl, +}; + +static const struct v4l2_subdev_video_ops ov7670soc_video_ops = { + .s_stream = ov7670soc_s_stream, + .s_mbus_fmt = ov7670soc_s_mbus_fmt, + .try_mbus_fmt = ov7670soc_try_mbus_fmt, + .enum_mbus_fmt = ov7670soc_enum_mbus_fmt, +}; + +static const struct v4l2_subdev_ops ov7670soc_ops = { + .core = &ov7670soc_core_ops, + .video = &ov7670soc_video_ops, +}; + +static int ov7670soc_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct ov7670soc_decoder *decoder; + struct soc_camera_device *icd = client->dev.platform_data; + struct soc_camera_link *icl; + struct v4l2_subdev *sd; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + icl = to_soc_camera_link(icd); + if (!icl) { + dev_err(&client->dev, "No platform data!!\n"); + return -ENODEV; + } + + decoder = kzalloc(sizeof(struct ov7670soc_decoder), GFP_KERNEL); + if (!decoder) + { + dev_err(&client->dev, "Failed to allocate memory for private data!\n"); + return -ENOMEM; + } + sd = &decoder->sd; + + /* Initialize the ov7670soc_decoder with default configuration */ + decoder->fmt_list = ov7670soc_formats; + decoder->num_fmts = ARRAY_SIZE(ov7670soc_formats); + decoder->fmt = &ov7670soc_formats[0]; + decoder->pdata = client->dev.platform_data; + decoder->brightness = 128; + decoder->contrast = 64; + decoder->saturation = 128; + decoder->hue = 0; + /* Register with V4L2 layer as slave device */ + v4l2_i2c_subdev_init(sd, client, &ov7670soc_ops); + + icd->ops = &ov7670soc_soc_camera_ops; + + return 0; +} + +static int ov7670soc_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ov7670soc_decoder *decoder = to_decoder(sd); + + v4l2_device_unregister_subdev(sd); + kfree(decoder); + + return 0; +} + +static const struct i2c_device_id ov7670soc_id[] = +{ + {OV7670SOC_MODULE_NAME, 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov7670soc_id); + +static struct i2c_driver ov7670soc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = OV7670SOC_MODULE_NAME, + }, + .probe = ov7670soc_probe, + .remove = ov7670soc_remove, + .id_table = ov7670soc_id, +}; + +static int __init ov7670soc_init(void) +{ + return i2c_add_driver(&ov7670soc_driver); +} + +static void __exit ov7670soc_exit(void) +{ + i2c_del_driver(&ov7670soc_driver); +} + +module_init(ov7670soc_init); +module_exit(ov7670soc_exit); + +MODULE_AUTHOR("Antmicro Ltd."); +MODULE_DESCRIPTION("OV7670 linux decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/tvp5150_reg.h b/drivers/media/video/tvp5150_reg.h index 4240043c0b2a..c4cdbadeead9 100644 --- a/drivers/media/video/tvp5150_reg.h +++ b/drivers/media/video/tvp5150_reg.h @@ -6,6 +6,9 @@ */ #define TVP5150_VD_IN_SRC_SEL_1 0x00 /* Video input source selection #1 */ +#define TVP5150_VIDEO_INPUT_SELECT_AIP1A 0x00 +#define TVP5150_VIDEO_INPUT_SELECT_AIP1B 0x02 +#define TVP5150_VIDEO_INPUT_SELECT_SVIDEO 0x01 #define TVP5150_ANAL_CHL_CTL 0x01 /* Analog channel controls */ #define TVP5150_OP_MODE_CTL 0x02 /* Operation mode controls */ #define TVP5150_MISC_CTL 0x03 /* Miscellaneous controls */ diff --git a/drivers/media/video/tvp5150soc.c b/drivers/media/video/tvp5150soc.c new file mode 100644 index 000000000000..3cb7cd6b4dc8 --- /dev/null +++ b/drivers/media/video/tvp5150soc.c @@ -0,0 +1,538 @@ +/* + * tvp5150 - Texas Instruments TVP5150A/AM1 video decoder driver + * + * Copyright (c) 2005,2006 Mauro Carvalho Chehab (mchehab@infradead.org) + * This code is placed under the terms of the GNU General Public License v2 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "tvp5150_reg.h" + +#define I2C_RETRY_COUNT 3 +#define LINE_PIXELS 576 +#define FRAME_LINES 520 + +#define MODULE_NAME "tvp5150soc" + +static unsigned int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level"); + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +static struct tvp5150soc_format_struct { + enum v4l2_mbus_pixelcode mbus_code; + enum v4l2_colorspace colorspace; +} tvp5150soc_formats[] = { + { + .mbus_code = V4L2_MBUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + }, +}; +#define N_TVP5150_FMTS ARRAY_SIZE(tvp5150soc_formats) + +struct tvp5150soc_decoder { + struct v4l2_subdev sd; + const struct tvp5150soc_format_struct *fmt_list; + struct tvp5150soc_format_struct *fmt; + int num_fmts; + int active_input; +}; + +static const struct v4l2_queryctrl tvp5150soc_controls[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + }, + { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "contrast", + .minimum = 0, + .maximum = 207, + .step = 1, + .default_value = 128, + }, + { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "saturation", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 128, + }, + { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "hue", + .minimum = -127, /*TODO: TVP5150 supports hue in range -180..180, which is equal to -127..127 reg value*/ + .maximum = 127, + .step = 1, + .default_value = 0, + }, +}; + +static inline struct tvp5150soc_decoder *to_decoder(struct v4l2_subdev *sd) +{ + return container_of(sd, struct tvp5150soc_decoder, sd); +} + +static int tvp5150soc_read_reg(struct v4l2_subdev *sd, unsigned char addr, unsigned char *val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err, retry = I2C_RETRY_COUNT; + + int ccc = client->addr; + int aaa = addr; + printk(KERN_ERR "tvp5150soc_read_reg: %04X %04X\n", ccc, aaa); + + while(retry) + { + err = i2c_smbus_read_byte_data(client, addr); + if(err > 0) + break; + retry--; + msleep_interruptible(10); + } + + if(err < 0) + v4l2_dbg(0, debug, sd, "i2c i/o error: %d\n", err); + else + { + *val = (unsigned char)err; + v4l2_dbg(2, debug, sd, "tvp5150: read 0x%02x = 0x%02x\n", addr, *val); + } + + printk(KERN_ERR "tvp5150soc_read_reg result: %d\n", err); + + return err; +} + +static int tvp5150soc_write_reg(struct v4l2_subdev *sd, unsigned char addr, unsigned char val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + int err, retry = I2C_RETRY_COUNT; + + while(retry) + { + err = i2c_smbus_write_byte_data(client, addr, val); + if(err > 0) + break; + retry--; + msleep_interruptible(10); + } + + if(err < 0) + v4l2_dbg(0, debug, sd, "i2c i/o error: %d\n", err); + else + v4l2_dbg(2, debug, sd, "tvp5150: write 0x%02x = 0x%02x\n", addr, val); + + return err; +} + +/* Default values as sugested at TVP5150AM1 datasheet */ +static const struct i2c_reg_value tvp5150soc_init_default[] = { + {TVP5150_VD_IN_SRC_SEL_1,0x00}, /* 0x00 */ + {TVP5150_ANAL_CHL_CTL,0x15}, /* 0x01 */ + {TVP5150_OP_MODE_CTL,0x00}, /* 0x02 */ + {TVP5150_MISC_CTL,0x01}, /* 0x03 */ + {TVP5150_COLOR_KIL_THSH_CTL,0x10}, /* 0x06 */ + {TVP5150_LUMA_PROC_CTL_1,0x60}, /* 0x07 */ + {TVP5150_LUMA_PROC_CTL_2,0x00}, /* 0x08 */ + {TVP5150_BRIGHT_CTL,0x80}, /* 0x09 */ + {TVP5150_SATURATION_CTL,0x80}, /* 0x0a */ + {TVP5150_HUE_CTL,0x00}, /* 0x0b */ + {TVP5150_CONTRAST_CTL,0x80}, /* 0x0c */ + {TVP5150_DATA_RATE_SEL,0x47}, /* 0x0d */ + {TVP5150_LUMA_PROC_CTL_3,0x00}, /* 0x0e */ + {TVP5150_CONF_SHARED_PIN,0x08}, /* 0x0f */ + {TVP5150_ACT_VD_CROP_ST_MSB,0x00}, /* 0x11 */ + {TVP5150_ACT_VD_CROP_ST_LSB,0x00}, /* 0x12 */ + {TVP5150_ACT_VD_CROP_STP_MSB,0x00}, /* 0x13 */ + {TVP5150_ACT_VD_CROP_STP_LSB,0x00}, /* 0x14 */ + {TVP5150_GENLOCK,0x01}, /* 0x15 */ + {TVP5150_HORIZ_SYNC_START,0x80}, /* 0x16 */ + {TVP5150_VERT_BLANKING_START,0x00}, /* 0x18 */ + {TVP5150_VERT_BLANKING_STOP,0x00}, /* 0x19 */ + {TVP5150_CHROMA_PROC_CTL_1,0x0c}, /* 0x1a */ + {TVP5150_CHROMA_PROC_CTL_2,0x14}, /* 0x1b */ + {TVP5150_INT_RESET_REG_B,0x00}, /* 0x1c */ + {TVP5150_INT_ENABLE_REG_B,0x00}, /* 0x1d */ + {TVP5150_INTT_CONFIG_REG_B,0x00}, /* 0x1e */ + {TVP5150_VIDEO_STD,0x00}, /* 0x28 */ + {TVP5150_MACROVISION_ON_CTR,0x0f}, /* 0x2e */ + {TVP5150_MACROVISION_OFF_CTR,0x01}, /* 0x2f */ + {TVP5150_TELETEXT_FIL_ENA,0x00}, /* 0xbb */ + {TVP5150_INT_STATUS_REG_A,0x00}, /* 0xc0 */ + {TVP5150_INT_ENABLE_REG_A,0x00}, /* 0xc1 */ + {TVP5150_INT_CONF,0x04}, /* 0xc2 */ + {TVP5150_FIFO_INT_THRESHOLD,0x80}, /* 0xc8 */ + {TVP5150_FIFO_RESET,0x00}, /* 0xc9 */ + {TVP5150_LINE_NUMBER_INT,0x00}, /* 0xca */ + {TVP5150_PIX_ALIGN_REG_LOW,0x4e}, /* 0xcb */ + {TVP5150_PIX_ALIGN_REG_HIGH,0x00}, /* 0xcc */ + {TVP5150_FIFO_OUT_CTRL,0x01}, /* 0xcd */ + {TVP5150_FULL_FIELD_ENA,0x00}, /* 0xcf */ + {TVP5150_LINE_MODE_INI,0x00}, /* 0xd0 */ + {TVP5150_FULL_FIELD_MODE_REG,0x7f}, /* 0xfc */ + { /* end of data */0xff,0xff} +}; + +/* Default values as sugested at TVP5150AM1 datasheet */ +static const struct i2c_reg_value tvp5150soc_init_enable[] = { + {TVP5150_VD_IN_SRC_SEL_1, 0x02}, + {TVP5150_CONF_SHARED_PIN, 0x02}, + {TVP5150_ANAL_CHL_CTL, 0x15}, /* Automatic offset and AGC enabled */ + {TVP5150_MISC_CTL, 0x6f}, /* Activate YCrCb output 0x9 or 0xd ? */ + {TVP5150_AUTOSW_MSK, 0x0}, /* Activates video std autodetection for all standards */ + {TVP5150_DATA_RATE_SEL, 0x47},/* Default format: 0x47. For 4:2:2: 0x40 */ + {TVP5150_CHROMA_PROC_CTL_1, 0x0c}, + {TVP5150_CHROMA_PROC_CTL_2, 0x54}, + {0x27, 0x20},/* Non documented, but initialized on WinTV USB2 */ + {0xff, 0xff} +}; + +static int tvp5150soc_write_inittab(struct v4l2_subdev *sd, const struct i2c_reg_value *regs) +{ + while (regs->reg != 0xff) { + tvp5150soc_write_reg(sd, regs->reg, regs->value); + regs++; + } + return 0; +} + +static int tvp5150soc_s_input(struct file *file, void *priv, unsigned int i) +{ + struct soc_camera_device *icd = file->private_data; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct tvp5150soc_decoder *decoder = to_decoder(sd); + unsigned char val; + + if (i < 3) + { + decoder->active_input = i; + switch (decoder->active_input) { + case 0: + val = TVP5150_VIDEO_INPUT_SELECT_AIP1A; + break; + case 1: + val = TVP5150_VIDEO_INPUT_SELECT_AIP1B; + break; + case 2: + val = TVP5150_VIDEO_INPUT_SELECT_SVIDEO; + break; + default: + val = TVP5150_VIDEO_INPUT_SELECT_AIP1A; + } + return tvp5150soc_write_reg(sd, TVP5150_VD_IN_SRC_SEL_1, val); + } + + return -EINVAL; +} + +static int tvp5150soc_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct soc_camera_device *icd = file->private_data; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct tvp5150soc_decoder *decoder = to_decoder(sd); + + *i = decoder->active_input; + + return 0; +} + +/********************************************************************************************************************************/ + +static int tvp5150soc_set_bus_param(struct soc_camera_device *icd, unsigned long flags) +{ + return 0; +} + +static unsigned long tvp5150soc_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); +} + +/********************************************************************************************************************************/ + +static int tvp5150soc_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + tvp5150soc_read_reg(sd, TVP5150_BRIGHT_CTL, (unsigned char*)&ctrl->value); + return 0; + case V4L2_CID_CONTRAST: + tvp5150soc_read_reg(sd, TVP5150_CONTRAST_CTL, (unsigned char*)&ctrl->value); + return 0; + case V4L2_CID_SATURATION: + tvp5150soc_read_reg(sd, TVP5150_SATURATION_CTL, (unsigned char*)&ctrl->value); + return 0; + case V4L2_CID_HUE: + tvp5150soc_read_reg(sd, TVP5150_HUE_CTL, (unsigned char*)&ctrl->value); + return 0; + } + return -EINVAL; +} + +static int tvp5150soc_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +{ + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + tvp5150soc_write_reg(sd, TVP5150_BRIGHT_CTL, ctrl->value); + return 0; + case V4L2_CID_CONTRAST: + tvp5150soc_write_reg(sd, TVP5150_CONTRAST_CTL, ctrl->value); + return 0; + case V4L2_CID_SATURATION: + tvp5150soc_write_reg(sd, TVP5150_SATURATION_CTL, ctrl->value); + return 0; + case V4L2_CID_HUE: + tvp5150soc_write_reg(sd, TVP5150_HUE_CTL, ctrl->value); + return 0; + } + return -EINVAL; +} + +static int tvp5150soc_reset(struct v4l2_subdev *sd, u32 val) +{ + /* Initializes TVP5150 to its default values */ + /* TVP5150 has no ability to software reset */ + tvp5150soc_write_inittab(sd, tvp5150soc_init_default); + + /* Initializes TVP5150 to stream enabled values */ + tvp5150soc_write_inittab(sd, tvp5150soc_init_enable); + + return 0; +} + +/********************************************************************************************************************************/ + +static int tvp5150soc_s_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + /* + * Set the image format. Currently we support only one format with + * fixed resolution, so we can set the format as it is on camera startup. + */ + tvp5150soc_reset(sd, 0); + + return 0; +} + +static int tvp5150soc_try_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + struct tvp5150soc_decoder *decoder = to_decoder(sd); + int index; + + printk(KERN_ERR "tvp5150soc_probe try mbus format\n"); + + /* Check if we support queried image format. */ + for (index = 0; index < N_TVP5150_FMTS; index++) + if (tvp5150soc_formats[index].mbus_code == mf->code) + { + printk(KERN_ERR "tvp5150soc_probe try mbus format found format\n"); + break; + } + /* If not, set the only one which we support */ + if (index >= N_TVP5150_FMTS) { + /* default to first format */ + index = 0; + printk(KERN_ERR "tvp5150soc_probe try mbus format default format\n"); + mf->code = tvp5150soc_formats[0].mbus_code; + } + + /* Store the current format */ + decoder->fmt = &tvp5150soc_formats[index]; + + /* Fixed value, move to tvp5150soc_formats */ + mf->field = V4L2_FIELD_INTERLACED_TB; + mf->width = LINE_PIXELS; + mf->height = FRAME_LINES; + mf->colorspace = tvp5150soc_formats[index].colorspace; + + return 0; +} + +static int tvp5150soc_enum_mbus_fmt(struct v4l2_subdev *sd, unsigned int index, enum v4l2_mbus_pixelcode *code) +{ + if (index >= ARRAY_SIZE(tvp5150soc_formats)) + return -EINVAL; + + *code = tvp5150soc_formats[index].mbus_code; + + return 0; +} +/********************************************************************************************************************************/ + +static struct soc_camera_ops tvp5150soc_soc_camera_ops = { + .set_bus_param = tvp5150soc_set_bus_param, + .query_bus_param = tvp5150soc_query_bus_param, + .controls = tvp5150soc_controls, + .num_controls = ARRAY_SIZE(tvp5150soc_controls), +}; + +static const struct v4l2_subdev_core_ops tvp5150soc_core_ops = { + .g_ctrl = tvp5150soc_g_ctrl, + .s_ctrl = tvp5150soc_s_ctrl, + .reset = tvp5150soc_reset, +}; + +static const struct v4l2_subdev_video_ops tvp5150soc_video_ops = { + .s_mbus_fmt = tvp5150soc_s_mbus_fmt, + .try_mbus_fmt = tvp5150soc_try_mbus_fmt, + .enum_mbus_fmt = tvp5150soc_enum_mbus_fmt, +}; + +static const struct v4l2_subdev_ops tvp5150soc_ops = { + .core = &tvp5150soc_core_ops, + .video = &tvp5150soc_video_ops, +}; + + +/**************************************************************************** + I2C Client & Driver + ****************************************************************************/ +static int tvp5150soc_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct soc_camera_device *icd = client->dev.platform_data; + struct soc_camera_link *icl; + struct tvp5150soc_decoder *decoder; + struct v4l2_subdev *sd; + struct v4l2_ioctl_ops *ops; + unsigned char msb_id, lsb_id, msb_rom, lsb_rom; + + printk(KERN_ERR "tvp5150soc_probe start\n"); + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + { + printk(KERN_ERR "tvp5150soc_probe error 1\n"); + return -EIO; + } + + icl = to_soc_camera_link(icd); + if (!icl) + { + dev_err(&client->dev, "No platform data!!\n"); + printk(KERN_ERR "tvp5150soc_probe error 2\n"); + return -ENODEV; + } + + decoder = kzalloc(sizeof(struct tvp5150soc_decoder), GFP_KERNEL); + if (!decoder) + { + dev_err(&client->dev, "Failed to allocate memory for private data!\n"); + printk(KERN_ERR "tvp5150soc_probe error 3\n"); + return -ENOMEM; + } + + /* TODO: init def settings of tvp5150soc_decoder */ + + sd = &decoder->sd; + + /* Register with V4L2 layer as slave device */ + v4l2_i2c_subdev_init(sd, client, &tvp5150soc_ops); + + tvp5150soc_read_reg(sd, TVP5150_MSB_DEV_ID, &msb_id); + tvp5150soc_read_reg(sd, TVP5150_LSB_DEV_ID, &lsb_id); + tvp5150soc_read_reg(sd, TVP5150_ROM_MAJOR_VER, &msb_rom); + tvp5150soc_read_reg(sd, TVP5150_ROM_MINOR_VER, &lsb_rom); + + if (msb_rom == 4 && lsb_rom == 0) { /* Is TVP5150AM1 */ + v4l2_info(sd, "tvp%02x%02xam1 detected.\n", msb_id, lsb_id); + /* ITU-T BT.656.4 timing */ + tvp5150soc_write_reg(sd, TVP5150_REV_SELECT, 0); + } else { + if (msb_rom == 3 || lsb_rom == 0x21) { /* Is TVP5150A */ + v4l2_info(sd, "tvp%02x%02xa detected.\n", msb_id, lsb_id); + } else { + v4l2_info(sd, "*** unknown tvp%02x%02x chip detected.\n", msb_id, lsb_id); + v4l2_info(sd, "*** Rom ver is %d.%d\n", msb_rom, lsb_rom); + } + } + + icd->ops = &tvp5150soc_soc_camera_ops; + + /* + * This is the only way to support more than one input as soc_camera + * assumes in its own vidioc_s(g)_input implementation that only one + * input is present we have to override that with our own handlers. + */ + ops = (struct v4l2_ioctl_ops*)icd->vdev->ioctl_ops; + ops->vidioc_s_input = &tvp5150soc_s_input; + ops->vidioc_g_input = &tvp5150soc_g_input; + + printk(KERN_ERR "tvp5150soc_probe return 0\n"); + return 0; +} + +static int tvp5150soc_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tvp5150soc_decoder *decoder = to_decoder(sd); + + v4l2_device_unregister_subdev(sd); + kfree(decoder); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static const struct i2c_device_id tvp5150soc_id[] = { + { MODULE_NAME, 0 }, + { } +}; + +static struct i2c_driver tvp5150soc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = MODULE_NAME, + }, + .probe = tvp5150soc_probe, + .remove = tvp5150soc_remove, + .id_table = tvp5150soc_id, +}; + +static __init int init_tvp5150soc(void) +{ + return i2c_add_driver(&tvp5150soc_driver); +} + +static __exit void exit_tvp5150soc(void) +{ + i2c_del_driver(&tvp5150soc_driver); +} + +module_init(init_tvp5150soc); +module_exit(exit_tvp5150soc); + +MODULE_DESCRIPTION("Texas Instruments TVP5150A video decoder driver for soc_camera interface"); +MODULE_AUTHOR("Antmicro Ltd."); +MODULE_LICENSE("GPL"); -- cgit v1.2.3