From d1f81da2361804d62f7ade2703eda68dda6cdb48 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 22 Oct 2008 00:04:45 -0300 Subject: V4L/DVB (9484): v4l: rename compat_ioctl32.c to v4l2-compat-ioctl32.c All core v4l sources should start with 'v4l2-' This file was the last one for which this was not the case. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/media/video/Makefile') diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 16962f3aa15..52f2e958db7 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -10,7 +10,7 @@ stkwebcam-objs := stk-webcam.o stk-sensor.o videodev-objs := v4l2-dev.o v4l2-ioctl.o -obj-$(CONFIG_VIDEO_DEV) += videodev.o compat_ioctl32.o v4l2-int-device.o +obj-$(CONFIG_VIDEO_DEV) += videodev.o v4l2-compat-ioctl32.o v4l2-int-device.o obj-$(CONFIG_VIDEO_V4L2_COMMON) += v4l2-common.o -- cgit v1.2.3-70-g09d2 From 08a66aea55154b50f9e9e2e89cc85d8b75121568 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Fri, 24 Oct 2008 15:43:07 -0300 Subject: V4L/DVB (9488): Add ov772x driver This patch adds ov772x driver that use soc_camera framework. It was tested on SH Migo-r board. Signed-off-by: Kuninori Morimoto Signed-off-by: Guennadi Liakhovetski Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Kconfig | 6 + drivers/media/video/Makefile | 1 + drivers/media/video/ov772x.c | 966 ++++++++++++++++++++++++++++++++++++++++ include/media/ov772x.h | 21 + include/media/v4l2-chip-ident.h | 1 + 5 files changed, 995 insertions(+) create mode 100644 drivers/media/video/ov772x.c create mode 100644 include/media/ov772x.h (limited to 'drivers/media/video/Makefile') diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 057fd7e160c..af951bdc111 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -750,6 +750,12 @@ config SOC_CAMERA_PLATFORM help This is a generic SoC camera platform driver, useful for testing +config SOC_CAMERA_OV772X + tristate "ov772x camera support" + depends on SOC_CAMERA && I2C + help + This is a ov772x camera driver + config VIDEO_PXA27x tristate "PXA27x Quick Capture Interface driver" depends on VIDEO_DEV && PXA27x && SOC_CAMERA diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 52f2e958db7..e5b80137900 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -133,6 +133,7 @@ obj-$(CONFIG_SOC_CAMERA) += soc_camera.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o +obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o obj-$(CONFIG_SOC_CAMERA_PLATFORM) += soc_camera_platform.o obj-$(CONFIG_VIDEO_AU0828) += au0828/ diff --git a/drivers/media/video/ov772x.c b/drivers/media/video/ov772x.c new file mode 100644 index 00000000000..6206dff8d02 --- /dev/null +++ b/drivers/media/video/ov772x.c @@ -0,0 +1,966 @@ +/* + * ov772x Camera Driver + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov7670 and soc_camera_platform driver, + * + * Copyright 2006-7 Jonathan Corbet + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * register offset + */ +#define GAIN 0x00 /* AGC - Gain control gain setting */ +#define BLUE 0x01 /* AWB - Blue channel gain setting */ +#define RED 0x02 /* AWB - Red channel gain setting */ +#define GREEN 0x03 /* AWB - Green channel gain setting */ +#define COM1 0x04 /* Common control 1 */ +#define BAVG 0x05 /* U/B Average Level */ +#define GAVG 0x06 /* Y/Gb Average Level */ +#define RAVG 0x07 /* V/R Average Level */ +#define AECH 0x08 /* Exposure Value - AEC MSBs */ +#define COM2 0x09 /* Common control 2 */ +#define PID 0x0A /* Product ID Number MSB */ +#define VER 0x0B /* Product ID Number LSB */ +#define COM3 0x0C /* Common control 3 */ +#define COM4 0x0D /* Common control 4 */ +#define COM5 0x0E /* Common control 5 */ +#define COM6 0x0F /* Common control 6 */ +#define AEC 0x10 /* Exposure Value */ +#define CLKRC 0x11 /* Internal clock */ +#define COM7 0x12 /* Common control 7 */ +#define COM8 0x13 /* Common control 8 */ +#define COM9 0x14 /* Common control 9 */ +#define COM10 0x15 /* Common control 10 */ +#define HSTART 0x17 /* Horizontal sensor size */ +#define HSIZE 0x18 /* Horizontal frame (HREF column) end high 8-bit */ +#define VSTART 0x19 /* Vertical frame (row) start high 8-bit */ +#define VSIZE 0x1A /* Vertical sensor size */ +#define PSHFT 0x1B /* Data format - pixel delay select */ +#define MIDH 0x1C /* Manufacturer ID byte - high */ +#define MIDL 0x1D /* Manufacturer ID byte - low */ +#define LAEC 0x1F /* Fine AEC value */ +#define COM11 0x20 /* Common control 11 */ +#define BDBASE 0x22 /* Banding filter Minimum AEC value */ +#define DBSTEP 0x23 /* Banding filter Maximum Setp */ +#define AEW 0x24 /* AGC/AEC - Stable operating region (upper limit) */ +#define AEB 0x25 /* AGC/AEC - Stable operating region (lower limit) */ +#define VPT 0x26 /* AGC/AEC Fast mode operating region */ +#define HOUTSIZE 0x29 /* Horizontal data output size MSBs */ +#define EXHCH 0x2A /* Dummy pixel insert MSB */ +#define EXHCL 0x2B /* Dummy pixel insert LSB */ +#define VOUTSIZE 0x2C /* Vertical data output size MSBs */ +#define ADVFL 0x2D /* LSB of insert dummy lines in Vertical direction */ +#define ADVFH 0x2E /* MSG of insert dummy lines in Vertical direction */ +#define YAVE 0x2F /* Y/G Channel Average value */ +#define LUMHTH 0x30 /* Histogram AEC/AGC Luminance high level threshold */ +#define LUMLTH 0x31 /* Histogram AEC/AGC Luminance low level threshold */ +#define HREF 0x32 /* Image start and size control */ +#define DM_LNL 0x33 /* Dummy line low 8 bits */ +#define DM_LNH 0x34 /* Dummy line high 8 bits */ +#define ADOFF_B 0x35 /* AD offset compensation value for B channel */ +#define ADOFF_R 0x36 /* AD offset compensation value for R channel */ +#define ADOFF_GB 0x37 /* AD offset compensation value for Gb channel */ +#define ADOFF_GR 0x38 /* AD offset compensation value for Gr channel */ +#define OFF_B 0x39 /* Analog process B channel offset value */ +#define OFF_R 0x3A /* Analog process R channel offset value */ +#define OFF_GB 0x3B /* Analog process Gb channel offset value */ +#define OFF_GR 0x3C /* Analog process Gr channel offset value */ +#define COM12 0x3D /* Common control 12 */ +#define COM13 0x3E /* Common control 13 */ +#define COM14 0x3F /* Common control 14 */ +#define COM15 0x40 /* Common control 15*/ +#define COM16 0x41 /* Common control 16 */ +#define TGT_B 0x42 /* BLC blue channel target value */ +#define TGT_R 0x43 /* BLC red channel target value */ +#define TGT_GB 0x44 /* BLC Gb channel target value */ +#define TGT_GR 0x45 /* BLC Gr channel target value */ +#define LCC0 0x46 /* Lens correction control 0 */ +#define LCC1 0x47 /* Lens correction option 1 - X coordinate */ +#define LCC2 0x48 /* Lens correction option 2 - Y coordinate */ +#define LCC3 0x49 /* Lens correction option 3 */ +#define LCC4 0x4A /* Lens correction option 4 - radius of the circular */ +#define LCC5 0x4B /* Lens correction option 5 */ +#define LCC6 0x4C /* Lens correction option 6 */ +#define FIXGAIN 0x4D /* Analog fix gain amplifer */ +#define AREF0 0x4E /* Sensor reference control */ +#define AREF1 0x4F /* Sensor reference current control */ +#define AREF2 0x50 /* Analog reference control */ +#define AREF3 0x51 /* ADC reference control */ +#define AREF4 0x52 /* ADC reference control */ +#define AREF5 0x53 /* ADC reference control */ +#define AREF6 0x54 /* Analog reference control */ +#define AREF7 0x55 /* Analog reference control */ +#define UFIX 0x60 /* U channel fixed value output */ +#define VFIX 0x61 /* V channel fixed value output */ +#define AW_BB_BLK 0x62 /* AWB option for advanced AWB */ +#define AW_B_CTRL0 0x63 /* AWB control byte 0 */ +#define DSP_CTRL1 0x64 /* DSP control byte 1 */ +#define DSP_CTRL2 0x65 /* DSP control byte 2 */ +#define DSP_CTRL3 0x66 /* DSP control byte 3 */ +#define DSP_CTRL4 0x67 /* DSP control byte 4 */ +#define AW_B_BIAS 0x68 /* AWB BLC level clip */ +#define AW_BCTRL1 0x69 /* AWB control 1 */ +#define AW_BCTRL2 0x6A /* AWB control 2 */ +#define AW_BCTRL3 0x6B /* AWB control 3 */ +#define AW_BCTRL4 0x6C /* AWB control 4 */ +#define AW_BCTRL5 0x6D /* AWB control 5 */ +#define AW_BCTRL6 0x6E /* AWB control 6 */ +#define AW_BCTRL7 0x6F /* AWB control 7 */ +#define AW_BCTRL8 0x70 /* AWB control 8 */ +#define AW_BCTRL9 0x71 /* AWB control 9 */ +#define AW_BCTRL10 0x72 /* AWB control 10 */ +#define AW_BCTRL11 0x73 /* AWB control 11 */ +#define AW_BCTRL12 0x74 /* AWB control 12 */ +#define AW_BCTRL13 0x75 /* AWB control 13 */ +#define AW_BCTRL14 0x76 /* AWB control 14 */ +#define AW_BCTRL15 0x77 /* AWB control 15 */ +#define AW_BCTRL16 0x78 /* AWB control 16 */ +#define AW_BCTRL17 0x79 /* AWB control 17 */ +#define AW_BCTRL18 0x7A /* AWB control 18 */ +#define AW_BCTRL19 0x7B /* AWB control 19 */ +#define AW_BCTRL20 0x7C /* AWB control 20 */ +#define AW_BCTRL21 0x7D /* AWB control 21 */ +#define GAM1 0x7E /* Gamma Curve 1st segment input end point */ +#define GAM2 0x7F /* Gamma Curve 2nd segment input end point */ +#define GAM3 0x80 /* Gamma Curve 3rd segment input end point */ +#define GAM4 0x81 /* Gamma Curve 4th segment input end point */ +#define GAM5 0x82 /* Gamma Curve 5th segment input end point */ +#define GAM6 0x83 /* Gamma Curve 6th segment input end point */ +#define GAM7 0x84 /* Gamma Curve 7th segment input end point */ +#define GAM8 0x85 /* Gamma Curve 8th segment input end point */ +#define GAM9 0x86 /* Gamma Curve 9th segment input end point */ +#define GAM10 0x87 /* Gamma Curve 10th segment input end point */ +#define GAM11 0x88 /* Gamma Curve 11th segment input end point */ +#define GAM12 0x89 /* Gamma Curve 12th segment input end point */ +#define GAM13 0x8A /* Gamma Curve 13th segment input end point */ +#define GAM14 0x8B /* Gamma Curve 14th segment input end point */ +#define GAM15 0x8C /* Gamma Curve 15th segment input end point */ +#define SLOP 0x8D /* Gamma curve highest segment slope */ +#define DNSTH 0x8E /* De-noise threshold */ +#define EDGE0 0x8F /* Edge enhancement control 0 */ +#define EDGE1 0x90 /* Edge enhancement control 1 */ +#define DNSOFF 0x91 /* Auto De-noise threshold control */ +#define EDGE2 0x92 /* Edge enhancement strength low point control */ +#define EDGE3 0x93 /* Edge enhancement strength high point control */ +#define MTX1 0x94 /* Matrix coefficient 1 */ +#define MTX2 0x95 /* Matrix coefficient 2 */ +#define MTX3 0x96 /* Matrix coefficient 3 */ +#define MTX4 0x97 /* Matrix coefficient 4 */ +#define MTX5 0x98 /* Matrix coefficient 5 */ +#define MTX6 0x99 /* Matrix coefficient 6 */ +#define MTX_CTRL 0x9A /* Matrix control */ +#define BRIGHT 0x9B /* Brightness control */ +#define CNTRST 0x9C /* Contrast contrast */ +#define CNTRST_CTRL 0x9D /* Contrast contrast center */ +#define UVAD_J0 0x9E /* Auto UV adjust contrast 0 */ +#define UVAD_J1 0x9F /* Auto UV adjust contrast 1 */ +#define SCAL0 0xA0 /* Scaling control 0 */ +#define SCAL1 0xA1 /* Scaling control 1 */ +#define SCAL2 0xA2 /* Scaling control 2 */ +#define FIFODLYM 0xA3 /* FIFO manual mode delay control */ +#define FIFODLYA 0xA4 /* FIFO auto mode delay control */ +#define SDE 0xA6 /* Special digital effect control */ +#define USAT 0xA7 /* U component saturation control */ +#define VSAT 0xA8 /* V component saturation control */ +#define HUE0 0xA9 /* Hue control 0 */ +#define HUE1 0xAA /* Hue control 1 */ +#define SIGN 0xAB /* Sign bit for Hue and contrast */ +#define DSPAUTO 0xAC /* DSP auto function ON/OFF control */ + +/* + * register detail + */ + +/* COM2 */ +#define SOFT_SLEEP_MODE 0x10 /* Soft sleep mode */ + /* Output drive capability */ +#define OCAP_1x 0x00 /* 1x */ +#define OCAP_2x 0x01 /* 2x */ +#define OCAP_3x 0x02 /* 3x */ +#define OCAP_4x 0x03 /* 4x */ + +/* COM3 */ +#define SWAP_MASK 0x38 + +#define VFIMG_ON_OFF 0x80 /* Vertical flip image ON/OFF selection */ +#define HMIMG_ON_OFF 0x40 /* Horizontal mirror image ON/OFF selection */ +#define SWAP_RGB 0x20 /* Swap B/R output sequence in RGB mode */ +#define SWAP_YUV 0x10 /* Swap Y/UV output sequence in YUV mode */ +#define SWAP_ML 0x08 /* Swap output MSB/LSB */ + /* Tri-state option for output clock */ +#define NOTRI_CLOCK 0x04 /* 0: Tri-state at this period */ + /* 1: No tri-state at this period */ + /* Tri-state option for output data */ +#define NOTRI_DATA 0x02 /* 0: Tri-state at this period */ + /* 1: No tri-state at this period */ +#define SCOLOR_TEST 0x01 /* Sensor color bar test pattern */ + +/* COM4 */ + /* PLL frequency control */ +#define PLL_BYPASS 0x00 /* 00: Bypass PLL */ +#define PLL_4x 0x40 /* 01: PLL 4x */ +#define PLL_6x 0x80 /* 10: PLL 6x */ +#define PLL_8x 0xc0 /* 11: PLL 8x */ + /* AEC evaluate window */ +#define AEC_FULL 0x00 /* 00: Full window */ +#define AEC_1p2 0x10 /* 01: 1/2 window */ +#define AEC_1p4 0x20 /* 10: 1/4 window */ +#define AEC_2p3 0x30 /* 11: Low 2/3 window */ + +/* COM5 */ +#define AFR_ON_OFF 0x80 /* Auto frame rate control ON/OFF selection */ +#define AFR_SPPED 0x40 /* Auto frame rate control speed slection */ + /* Auto frame rate max rate control */ +#define AFR_NO_RATE 0x00 /* No reduction of frame rate */ +#define AFR_1p2 0x10 /* Max reduction to 1/2 frame rate */ +#define AFR_1p4 0x20 /* Max reduction to 1/4 frame rate */ +#define AFR_1p8 0x30 /* Max reduction to 1/8 frame rate */ + /* Auto frame rate active point control */ +#define AF_2x 0x00 /* Add frame when AGC reaches 2x gain */ +#define AF_4x 0x04 /* Add frame when AGC reaches 4x gain */ +#define AF_8x 0x08 /* Add frame when AGC reaches 8x gain */ +#define AF_16x 0x0c /* Add frame when AGC reaches 16x gain */ + /* AEC max step control */ +#define AEC_NO_LIMIT 0x01 /* 0 : AEC incease step has limit */ + /* 1 : No limit to AEC increase step */ + +/* COM7 */ + /* SCCB Register Reset */ +#define SCCB_RESET 0x80 /* 0 : No change */ + /* 1 : Resets all registers to default */ + /* Resolution selection */ +#define SLCT_MASK 0x40 /* Mask of VGA or QVGA */ +#define SLCT_VGA 0x00 /* 0 : VGA */ +#define SLCT_QVGA 0x40 /* 1 : QVGA */ +#define ITU656_ON_OFF 0x20 /* ITU656 protocol ON/OFF selection */ + /* RGB output format control */ +#define FMT_GBR422 0x00 /* 00 : GBR 4:2:2 */ +#define FMT_RGB565 0x04 /* 01 : RGB 565 */ +#define FMT_RGB555 0x08 /* 10 : RGB 555 */ +#define FMT_RGB444 0x0c /* 11 : RGB 444 */ + /* Output format control */ +#define OFMT_YUV 0x00 /* 00 : YUV */ +#define OFMT_P_BRAW 0x01 /* 01 : Processed Bayer RAW */ +#define OFMT_RGB 0x02 /* 10 : RGB */ +#define OFMT_BRAW 0x03 /* 11 : Bayer RAW */ + +/* COM8 */ +#define FAST_ALGO 0x80 /* Enable fast AGC/AEC algorithm */ + /* AEC Setp size limit */ +#define UNLMT_STEP 0x40 /* 0 : Step size is limited */ + /* 1 : Unlimited step size */ +#define BNDF_ON_OFF 0x20 /* Banding filter ON/OFF */ +#define AEC_BND 0x10 /* Enable AEC below banding value */ +#define AEC_ON_OFF 0x08 /* Fine AEC ON/OFF control */ +#define AGC_ON 0x04 /* AGC Enable */ +#define AWB_ON 0x02 /* AWB Enable */ +#define AEC_ON 0x01 /* AEC Enable */ + +/* COM9 */ +#define BASE_AECAGC 0x80 /* Histogram or average based AEC/AGC */ + /* Automatic gain ceiling - maximum AGC value */ +#define GAIN_2x 0x00 /* 000 : 2x */ +#define GAIN_4x 0x10 /* 001 : 4x */ +#define GAIN_8x 0x20 /* 010 : 8x */ +#define GAIN_16x 0x30 /* 011 : 16x */ +#define GAIN_32x 0x40 /* 100 : 32x */ +#define GAIN_64x 0x50 /* 101 : 64x */ +#define GAIN_128x 0x60 /* 110 : 128x */ +#define DROP_VSYNC 0x04 /* Drop VSYNC output of corrupt frame */ +#define DROP_HREF 0x02 /* Drop HREF output of corrupt frame */ + +/* COM11 */ +#define SGLF_ON_OFF 0x02 /* Single frame ON/OFF selection */ +#define SGLF_TRIG 0x01 /* Single frame transfer trigger */ + +/* EXHCH */ +#define VSIZE_LSB 0x04 /* Vertical data output size LSB */ + +/* DSP_CTRL1 */ +#define FIFO_ON 0x80 /* FIFO enable/disable selection */ +#define UV_ON_OFF 0x40 /* UV adjust function ON/OFF selection */ +#define YUV444_2_422 0x20 /* YUV444 to 422 UV channel option selection */ +#define CLR_MTRX_ON_OFF 0x10 /* Color matrix ON/OFF selection */ +#define INTPLT_ON_OFF 0x08 /* Interpolation ON/OFF selection */ +#define GMM_ON_OFF 0x04 /* Gamma function ON/OFF selection */ +#define AUTO_BLK_ON_OFF 0x02 /* Black defect auto correction ON/OFF */ +#define AUTO_WHT_ON_OFF 0x01 /* White define auto correction ON/OFF */ + +/* DSP_CTRL3 */ +#define UV_MASK 0x80 /* UV output sequence option */ +#define UV_ON 0x80 /* ON */ +#define UV_OFF 0x00 /* OFF */ +#define CBAR_MASK 0x20 /* DSP Color bar mask */ +#define CBAR_ON 0x20 /* ON */ +#define CBAR_OFF 0x00 /* OFF */ + +/* HSTART */ +#define HST_VGA 0x23 +#define HST_QVGA 0x3F + +/* HSIZE */ +#define HSZ_VGA 0xA0 +#define HSZ_QVGA 0x50 + +/* VSTART */ +#define VST_VGA 0x07 +#define VST_QVGA 0x03 + +/* VSIZE */ +#define VSZ_VGA 0xF0 +#define VSZ_QVGA 0x78 + +/* HOUTSIZE */ +#define HOSZ_VGA 0xA0 +#define HOSZ_QVGA 0x50 + +/* VOUTSIZE */ +#define VOSZ_VGA 0xF0 +#define VOSZ_QVGA 0x78 + +/* + * bit configure (32 bit) + * this is used in struct ov772x_color_format :: option + */ +#define OP_UV 0x00000001 +#define OP_SWAP_RGB 0x00000002 + +/* + * struct + */ +struct regval_list { + unsigned char reg_num; + unsigned char value; +}; + +struct ov772x_color_format { + char *name; + __u32 fourcc; + const struct regval_list *regs; + unsigned int option; +}; + +struct ov772x_win_size { + char *name; + __u32 width; + __u32 height; + unsigned char com7_bit; + const struct regval_list *regs; +}; + +struct ov772x_priv { + struct ov772x_camera_info *info; + struct i2c_client *client; + struct soc_camera_device icd; + const struct ov772x_color_format *fmt; + const struct ov772x_win_size *win; +}; + +#define ENDMARKER { 0xff, 0xff } + +static const struct regval_list ov772x_default_regs[] = +{ + { COM3, 0x00 }, + { COM4, PLL_4x | 0x01 }, + { 0x16, 0x00 }, /* Mystery */ + { COM11, 0x10 }, /* Mystery */ + { 0x28, 0x00 }, /* Mystery */ + { HREF, 0x00 }, + { COM13, 0xe2 }, /* Mystery */ + { AREF0, 0xef }, + { AREF2, 0x60 }, + { AREF6, 0x7a }, + ENDMARKER, +}; + +/* + * register setting for color format + */ +static const struct regval_list ov772x_RGB555_regs[] = { + { COM7, FMT_RGB555 | OFMT_RGB }, + ENDMARKER, +}; + +static const struct regval_list ov772x_RGB565_regs[] = { + { COM7, FMT_RGB565 | OFMT_RGB }, + ENDMARKER, +}; + +static const struct regval_list ov772x_YYUV_regs[] = { + { COM3, SWAP_YUV }, + { COM7, OFMT_YUV }, + ENDMARKER, +}; + +static const struct regval_list ov772x_UVYY_regs[] = { + { COM7, OFMT_YUV }, + ENDMARKER, +}; + + +/* + * register setting for window size + */ +static const struct regval_list ov772x_qvga_regs[] = { + { HSTART, HST_QVGA }, + { HSIZE, HSZ_QVGA }, + { VSTART, VST_QVGA }, + { VSIZE, VSZ_QVGA }, + { HOUTSIZE, HOSZ_QVGA }, + { VOUTSIZE, VOSZ_QVGA }, + ENDMARKER, +}; + +static const struct regval_list ov772x_vga_regs[] = { + { HSTART, HST_VGA }, + { HSIZE, HSZ_VGA }, + { VSTART, VST_VGA }, + { VSIZE, VSZ_VGA }, + { HOUTSIZE, HOSZ_VGA }, + { VOUTSIZE, VOSZ_VGA }, + ENDMARKER, +}; + +/* + * supported format list + */ + +#define SETFOURCC(type) .name = (#type), .fourcc = (V4L2_PIX_FMT_ ## type) +static const struct soc_camera_data_format ov772x_fmt_lists[] = { + { + SETFOURCC(YUYV), + .depth = 16, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + SETFOURCC(YVYU), + .depth = 16, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + SETFOURCC(UYVY), + .depth = 16, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + { + SETFOURCC(RGB555), + .depth = 16, + .colorspace = V4L2_COLORSPACE_SRGB, + }, + { + SETFOURCC(RGB555X), + .depth = 16, + .colorspace = V4L2_COLORSPACE_SRGB, + }, + { + SETFOURCC(RGB565), + .depth = 16, + .colorspace = V4L2_COLORSPACE_SRGB, + }, + { + SETFOURCC(RGB565X), + .depth = 16, + .colorspace = V4L2_COLORSPACE_SRGB, + }, +}; + +/* + * color format list + */ +#define T_YUYV 0 +static const struct ov772x_color_format ov772x_cfmts[] = { + [T_YUYV] = { + SETFOURCC(YUYV), + .regs = ov772x_YYUV_regs, + }, + { + SETFOURCC(YVYU), + .regs = ov772x_YYUV_regs, + .option = OP_UV, + }, + { + SETFOURCC(UYVY), + .regs = ov772x_UVYY_regs, + }, + { + SETFOURCC(RGB555), + .regs = ov772x_RGB555_regs, + .option = OP_SWAP_RGB, + }, + { + SETFOURCC(RGB555X), + .regs = ov772x_RGB555_regs, + }, + { + SETFOURCC(RGB565), + .regs = ov772x_RGB565_regs, + .option = OP_SWAP_RGB, + }, + { + SETFOURCC(RGB565X), + .regs = ov772x_RGB565_regs, + }, +}; + + +/* + * window size list + */ +#define VGA_WIDTH 640 +#define VGA_HEIGHT 480 +#define QVGA_WIDTH 320 +#define QVGA_HEIGHT 240 +#define MAX_WIDTH VGA_WIDTH +#define MAX_HEIGHT VGA_HEIGHT + +static const struct ov772x_win_size ov772x_win_vga = { + .name = "VGA", + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .com7_bit = SLCT_VGA, + .regs = ov772x_vga_regs, +}; + +static const struct ov772x_win_size ov772x_win_qvga = { + .name = "QVGA", + .width = QVGA_WIDTH, + .height = QVGA_HEIGHT, + .com7_bit = SLCT_QVGA, + .regs = ov772x_qvga_regs, +}; + + +/* + * general function + */ + +static int ov772x_write_array(struct i2c_client *client, + const struct regval_list *vals) +{ + while (vals->reg_num != 0xff) { + int ret = i2c_smbus_write_byte_data(client, + vals->reg_num, + vals->value); + if (ret < 0) + return ret; + vals++; + } + return 0; +} + +static int ov772x_mask_set(struct i2c_client *client, + u8 command, + u8 mask, + u8 set) +{ + s32 val = i2c_smbus_read_byte_data(client, command); + val &= ~mask; + val |= set; + + return i2c_smbus_write_byte_data(client, command, val); +} + +static int ov772x_reset(struct i2c_client *client) +{ + int ret = i2c_smbus_write_byte_data(client, COM7, SCCB_RESET); + msleep(1); + return ret; +} + +/* + * soc_camera_ops function + */ + +static int ov772x_init(struct soc_camera_device *icd) +{ + return 0; +} + +static int ov772x_release(struct soc_camera_device *icd) +{ + return 0; +} + +static int ov772x_start_capture(struct soc_camera_device *icd) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + int ret; + + + if (!priv->win) + priv->win = &ov772x_win_vga; + if (!priv->fmt) + priv->fmt = &ov772x_cfmts[T_YUYV]; + + /* + * reset hardware + */ + ov772x_reset(priv->client); + ret = ov772x_write_array(priv->client, ov772x_default_regs); + if (ret < 0) + goto start_end; + + /* + * set color format + */ + ret = ov772x_write_array(priv->client, priv->fmt->regs); + if (ret < 0) + goto start_end; + + /* + * set size format + */ + ret = ov772x_write_array(priv->client, priv->win->regs); + if (ret < 0) + goto start_end; + + /* + * set COM7 bit ( QVGA or VGA ) + */ + ret = ov772x_mask_set(priv->client, + COM7, SLCT_MASK, priv->win->com7_bit); + if (ret < 0) + goto start_end; + + /* + * set UV setting + */ + if (priv->fmt->option & OP_UV) { + ret = ov772x_mask_set(priv->client, + DSP_CTRL3, UV_MASK, UV_ON); + if (ret < 0) + goto start_end; + } + + /* + * set SWAP setting + */ + if (priv->fmt->option & OP_SWAP_RGB) { + ret = ov772x_mask_set(priv->client, + COM3, SWAP_MASK, SWAP_RGB); + if (ret < 0) + goto start_end; + } + + dev_info(&icd->dev, + "format %s, win %s\n", priv->fmt->name, priv->win->name); + +start_end: + priv->fmt = NULL; + priv->win = NULL; + + return ret; +} + +static int ov772x_stop_capture(struct soc_camera_device *icd) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + ov772x_reset(priv->client); + return 0; +} + +static int ov772x_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + return 0; +} + +static unsigned long ov772x_query_bus_param(struct soc_camera_device *icd) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + + return SOCAM_PCLK_SAMPLE_RISING | + SOCAM_HSYNC_ACTIVE_HIGH | + SOCAM_VSYNC_ACTIVE_HIGH | + SOCAM_MASTER | + priv->info->buswidth; +} + +static int ov772x_get_chip_id(struct soc_camera_device *icd, + struct v4l2_chip_ident *id) +{ + id->ident = V4L2_IDENT_OV772X; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int ov772x_get_register(struct soc_camera_device *icd, + struct v4l2_register *reg) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + int ret; + + if (reg->reg > 0xff) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(priv->client, reg->reg); + if (ret < 0) + return ret; + + reg->val = (__u64)ret; + + return 0; +} + +static int ov772x_set_register(struct soc_camera_device *icd, + struct v4l2_register *reg) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(priv->client, reg->reg, reg->val); +} +#endif + +static int ov772x_set_fmt_cap(struct soc_camera_device *icd, + __u32 pixfmt, + struct v4l2_rect *rect) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + int ret = -EINVAL; + int i; + + /* + * select format + */ + priv->fmt = NULL; + for (i = 0; i < ARRAY_SIZE(ov772x_cfmts); i++) { + if (pixfmt == ov772x_cfmts[i].fourcc) { + priv->fmt = ov772x_cfmts + i; + ret = 0; + break; + } + } + + return ret; +} + +static int ov772x_try_fmt_cap(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pix = &f->fmt.pix; + struct ov772x_priv *priv; + + priv = container_of(icd, struct ov772x_priv, icd); + + /* QVGA */ + if (pix->width <= ov772x_win_qvga.width || + pix->height <= ov772x_win_qvga.height) { + priv->win = &ov772x_win_qvga; + pix->width = ov772x_win_qvga.width; + pix->height = ov772x_win_qvga.height; + } + + /* VGA */ + else if (pix->width <= ov772x_win_vga.width || + pix->height <= ov772x_win_vga.height) { + priv->win = &ov772x_win_vga; + pix->width = ov772x_win_vga.width; + pix->height = ov772x_win_vga.height; + } + + pix->field = V4L2_FIELD_NONE; + + return 0; +} + +static int ov772x_video_probe(struct soc_camera_device *icd) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + u8 pid, ver; + + /* + * We must have a parent by now. And it cannot be a wrong one. + * So this entire test is completely redundant. + */ + if (!icd->dev.parent || + to_soc_camera_host(icd->dev.parent)->nr != icd->iface) + return -ENODEV; + + /* + * ov772x only use 8 or 10 bit bus width + */ + if (SOCAM_DATAWIDTH_10 != priv->info->buswidth && + SOCAM_DATAWIDTH_8 != priv->info->buswidth) { + dev_err(&icd->dev, "bus width error\n"); + return -ENODEV; + } + + icd->formats = ov772x_fmt_lists; + icd->num_formats = ARRAY_SIZE(ov772x_fmt_lists); + + if (priv->info->link.power) + priv->info->link.power(&priv->client->dev, 1); + + /* + * check and show product ID and manufacturer ID + */ + pid = i2c_smbus_read_byte_data(priv->client, PID); + ver = i2c_smbus_read_byte_data(priv->client, VER); + if (pid != 0x77 || + ver != 0x21) { + if (priv->info->link.power) + priv->info->link.power(&priv->client->dev, 0); + return -ENODEV; + } + + dev_info(&icd->dev, + "ov772x Product ID %0x:%0x Manufacturer ID %x:%x\n", + pid, + ver, + i2c_smbus_read_byte_data(priv->client, MIDH), + i2c_smbus_read_byte_data(priv->client, MIDL)); + + + return soc_camera_video_start(icd); +} + +static void ov772x_video_remove(struct soc_camera_device *icd) +{ + struct ov772x_priv *priv = container_of(icd, struct ov772x_priv, icd); + + soc_camera_video_stop(icd); + + if (priv->info->link.power) + priv->info->link.power(&priv->client->dev, 0); + +} + +static struct soc_camera_ops ov772x_ops = { + .owner = THIS_MODULE, + .probe = ov772x_video_probe, + .remove = ov772x_video_remove, + .init = ov772x_init, + .release = ov772x_release, + .start_capture = ov772x_start_capture, + .stop_capture = ov772x_stop_capture, + .set_fmt_cap = ov772x_set_fmt_cap, + .try_fmt_cap = ov772x_try_fmt_cap, + .set_bus_param = ov772x_set_bus_param, + .query_bus_param = ov772x_query_bus_param, + .get_chip_id = ov772x_get_chip_id, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .get_register = ov772x_get_register, + .set_register = ov772x_set_register, +#endif +}; + +/* + * i2c_driver function + */ + +static int ov772x_probe(struct i2c_client *client, + const struct i2c_device_id *did) + +{ + struct ov772x_priv *priv; + struct ov772x_camera_info *info; + struct soc_camera_device *icd; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + int ret; + + info = client->dev.platform_data; + if (!info) + return -EINVAL; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&adapter->dev, + "I2C-Adapter doesn't support " + "I2C_FUNC_SMBUS_BYTE_DATA\n"); + return -EIO; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = info; + priv->client = client; + i2c_set_clientdata(client, priv); + + icd = &priv->icd; + icd->ops = &ov772x_ops; + icd->control = &client->dev; + icd->width_max = MAX_WIDTH; + icd->height_max = MAX_HEIGHT; + icd->iface = priv->info->link.bus_id; + + ret = soc_camera_device_register(icd); + + if (ret) + kfree(priv); + + return ret; +} + +static int ov772x_remove(struct i2c_client *client) +{ + struct ov772x_priv *priv = i2c_get_clientdata(client); + + soc_camera_device_unregister(&priv->icd); + kfree(priv); + return 0; +} + +static const struct i2c_device_id ov772x_id[] = { + {"ov772x", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ov772x_id); + + +static struct i2c_driver ov772x_i2c_driver = { + .driver = { + .name = "ov772x", + }, + .probe = ov772x_probe, + .remove = ov772x_remove, + .id_table = ov772x_id, +}; + +/* + * module function + */ + +static int __init ov772x_module_init(void) +{ + printk(KERN_INFO "ov772x driver\n"); + return i2c_add_driver(&ov772x_i2c_driver); +} + +static void __exit ov772x_module_exit(void) +{ + i2c_del_driver(&ov772x_i2c_driver); +} + +module_init(ov772x_module_init); +module_exit(ov772x_module_exit); + +MODULE_DESCRIPTION("SoC Camera driver for ov772x"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/include/media/ov772x.h b/include/media/ov772x.h new file mode 100644 index 00000000000..e391d55edb9 --- /dev/null +++ b/include/media/ov772x.h @@ -0,0 +1,21 @@ +/* ov772x Camera + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * This program 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. + */ + +#ifndef __OV772X_H__ +#define __OV772X_H__ + +#include + +struct ov772x_camera_info { + unsigned long buswidth; + struct soc_camera_link link; +}; + +#endif /* __OV772X_H__ */ diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h index d73a8e9028a..bfe5142e667 100644 --- a/include/media/v4l2-chip-ident.h +++ b/include/media/v4l2-chip-ident.h @@ -60,6 +60,7 @@ enum { /* OmniVision sensors: reserved range 250-299 */ V4L2_IDENT_OV7670 = 250, + V4L2_IDENT_OV772X = 251, /* Conexant MPEG encoder/decoders: reserved range 410-420 */ V4L2_IDENT_CX23415 = 415, -- cgit v1.2.3-70-g09d2 From 4b29631db33292d416dc395c56122ea865e7635c Mon Sep 17 00:00:00 2001 From: "Igor M. Liplianin" Date: Sun, 9 Nov 2008 15:25:31 -0300 Subject: V4L/DVB (9533): cx88: Add support for TurboSight TBS8910 DVB-S PCI card The card based on stv0299 or stv0288 demodulators. Signed-off-by: Igor M. Liplianin Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/CARDLIST.cx88 | 2 ++ drivers/media/video/Makefile | 1 + drivers/media/video/cx88/cx88-cards.c | 34 +++++++++++++++++++++++++++++++++ drivers/media/video/cx88/cx88-dvb.c | 2 ++ drivers/media/video/cx88/cx88.h | 2 ++ firmware/Makefile | 16 ++-------------- sound/i2c/other/tea575x-tuner.c | 22 ++++++++++----------- 7 files changed, 54 insertions(+), 25 deletions(-) (limited to 'drivers/media/video/Makefile') diff --git a/Documentation/video4linux/CARDLIST.cx88 b/Documentation/video4linux/CARDLIST.cx88 index 12e600d7424..dc8da94061a 100644 --- a/Documentation/video4linux/CARDLIST.cx88 +++ b/Documentation/video4linux/CARDLIST.cx88 @@ -74,3 +74,5 @@ 73 -> TeVii S420 DVB-S [d420:9022] 74 -> Prolink Pixelview Global Extreme [1554:4976] 75 -> PROF 7300 DVB-S/S2 [B033:3033] + 76 -> SATTRADE ST4200 DVB-S/S2 [b200:4200] + 77 -> TBS 8910 DVB-S [8910:8888] diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index e5b80137900..53d43f9e017 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o obj-$(CONFIG_VIDEO_TVAUDIO) += tvaudio.o obj-$(CONFIG_VIDEO_TDA7432) += tda7432.o obj-$(CONFIG_VIDEO_TDA9875) += tda9875.o +obj-$(CONFIG_SOUND_TVMIXER) += tvmixer.o obj-$(CONFIG_VIDEO_SAA6588) += saa6588.o obj-$(CONFIG_VIDEO_SAA5246A) += saa5246a.o diff --git a/drivers/media/video/cx88/cx88-cards.c b/drivers/media/video/cx88/cx88-cards.c index 3ead9498c4d..bbe5b3343ac 100644 --- a/drivers/media/video/cx88/cx88-cards.c +++ b/drivers/media/video/cx88/cx88-cards.c @@ -1847,6 +1847,18 @@ static const struct cx88_board cx88_boards[] = { } }, .mpeg = CX88_MPEG_DVB, }, + [CX88_BOARD_TBS_8910] = { + .name = "TBS 8910 DVB-S", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, [CX88_BOARD_TBS_8920] = { .name = "TBS 8920 DVB-S/S2", .tuner_type = TUNER_ABSENT, @@ -1871,6 +1883,18 @@ static const struct cx88_board cx88_boards[] = { } }, .mpeg = CX88_MPEG_DVB, }, + [CX88_BOARD_SATTRADE_ST4200] = { + .name = "SATTRADE ST4200 DVB-S/S2", + .tuner_type = UNSET, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .input = {{ + .type = CX88_VMUX_DVB, + .vmux = 0, + } }, + .mpeg = CX88_MPEG_DVB, + }, }; /* ------------------------------------------------------------------ */ @@ -2260,6 +2284,10 @@ static const struct cx88_subid cx88_subids[] = { .subvendor = 0xA044, .subdevice = 0x2011, .card = CX88_BOARD_OMICOM_SS4_PCI, + }, { + .subvendor = 0x8910, + .subdevice = 0x8888, + .card = CX88_BOARD_TBS_8910, }, { .subvendor = 0x8920, .subdevice = 0x8888, @@ -2268,6 +2296,10 @@ static const struct cx88_subid cx88_subids[] = { .subvendor = 0xB033, .subdevice = 0x3033, .card = CX88_BOARD_PROF_7300, + }, { + .subvendor = 0xb200, + .subdevice = 0x4200, + .card = CX88_BOARD_SATTRADE_ST4200, }, }; @@ -2878,8 +2910,10 @@ static void cx88_card_setup(struct cx88_core *core) case CX88_BOARD_TEVII_S420: case CX88_BOARD_TEVII_S460: case CX88_BOARD_OMICOM_SS4_PCI: + case CX88_BOARD_TBS_8910: case CX88_BOARD_TBS_8920: case CX88_BOARD_PROF_7300: + case CX88_BOARD_SATTRADE_ST4200: cx_write(MO_SRST_IO, 0); msleep(100); cx_write(MO_SRST_IO, 1); diff --git a/drivers/media/video/cx88/cx88-dvb.c b/drivers/media/video/cx88/cx88-dvb.c index 8378d46dc80..bab28112e40 100644 --- a/drivers/media/video/cx88/cx88-dvb.c +++ b/drivers/media/video/cx88/cx88-dvb.c @@ -1042,6 +1042,7 @@ static int dvb_register(struct cx8802_dev *dev) 0x08, ISL6421_DCL, 0x00); } break; + case CX88_BOARD_TBS_8910: case CX88_BOARD_TEVII_S420: fe0->dvb.frontend = dvb_attach(stv0299_attach, &tevii_tuner_sharp_config, @@ -1078,6 +1079,7 @@ static int dvb_register(struct cx8802_dev *dev) case CX88_BOARD_OMICOM_SS4_PCI: case CX88_BOARD_TBS_8920: case CX88_BOARD_PROF_7300: + case CX88_BOARD_SATTRADE_ST4200: fe0->dvb.frontend = dvb_attach(cx24116_attach, &hauppauge_hvr4000_config, &core->i2c_adap); diff --git a/drivers/media/video/cx88/cx88.h b/drivers/media/video/cx88/cx88.h index a11599fa1d8..ae1b89a88bc 100644 --- a/drivers/media/video/cx88/cx88.h +++ b/drivers/media/video/cx88/cx88.h @@ -228,6 +228,8 @@ extern struct sram_channel cx88_sram_channels[]; #define CX88_BOARD_TEVII_S420 73 #define CX88_BOARD_PROLINK_PV_GLOBAL_XTREME 74 #define CX88_BOARD_PROF_7300 75 +#define CX88_BOARD_SATTRADE_ST4200 76 +#define CX88_BOARD_TBS_8910 77 enum cx88_itype { CX88_VMUX_COMPOSITE1 = 1, diff --git a/firmware/Makefile b/firmware/Makefile index 4993a4b3d8a..6968388818b 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -150,27 +150,15 @@ $(patsubst %,$(obj)/%.gen.o, $(fw-external-y)): $(obj)/%.gen.o: $(fwdir)/% $(obj)/%: $(obj)/%.ihex | $(objtree)/$(obj)/$$(dir %) $(call cmd,ihex) -# Don't depend on ihex2fw if we're installing and it already exists. -# Putting it after | in the dependencies doesn't seem sufficient when -# we're installing after a cross-compile, because ihex2fw has dependencies -# on stuff like /usr/lib/gcc/ppc64-redhat-linux/4.3.0/include/stddef.h and -# thus wants to be rebuilt. Which it can't be, if the prebuilt kernel tree -# is exported read-only for someone to run 'make install'. -ifeq ($(INSTALL):$(wildcard $(obj)/ihex2fw),install:$(obj)/ihex2fw) -ihex2fw_dep := -else -ihex2fw_dep := $(obj)/ihex2fw -endif - # .HEX is also Intel HEX, but where the offset and length in each record # is actually meaningful, because the firmware has to be loaded in a certain # order rather than as a single binary blob. Thus, we convert them into our # more compact binary representation of ihex records () -$(obj)/%.fw: $(obj)/%.HEX $(ihex2fw_dep) | $(objtree)/$(obj)/$$(dir %) +$(obj)/%.fw: $(obj)/%.HEX $(obj)/ihex2fw | $(objtree)/$(obj)/$$(dir %) $(call cmd,ihex2fw) # .H16 is our own modified form of Intel HEX, with 16-bit length for records. -$(obj)/%.fw: $(obj)/%.H16 $(ihex2fw_dep) | $(objtree)/$(obj)/$$(dir %) +$(obj)/%.fw: $(obj)/%.H16 $(obj)/ihex2fw | $(objtree)/$(obj)/$$(dir %) $(call cmd,h16tofw) $(firmware-dirs): diff --git a/sound/i2c/other/tea575x-tuner.c b/sound/i2c/other/tea575x-tuner.c index c13a178383b..549b4eba149 100644 --- a/sound/i2c/other/tea575x-tuner.c +++ b/sound/i2c/other/tea575x-tuner.c @@ -18,7 +18,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * - */ + */ #include #include @@ -89,7 +89,7 @@ static int snd_tea575x_ioctl(struct inode *inode, struct file *file, { struct snd_tea575x *tea = video_drvdata(file); void __user *arg = (void __user *)data; - + switch(cmd) { case VIDIOCGCAP: { @@ -110,9 +110,9 @@ static int snd_tea575x_ioctl(struct inode *inode, struct file *file, case VIDIOCGTUNER: { struct video_tuner v; - if (copy_from_user(&v, arg,sizeof(v))!=0) + if (copy_from_user(&v, arg,sizeof(v))!=0) return -EFAULT; - if (v.tuner) /* Only 1 tuner */ + if (v.tuner) /* Only 1 tuner */ return -EINVAL; v.rangelow = (87*16000); v.rangehigh = (108*16000); @@ -144,24 +144,24 @@ static int snd_tea575x_ioctl(struct inode *inode, struct file *file, snd_tea575x_set_freq(tea); return 0; case VIDIOCGAUDIO: - { + { struct video_audio v; memset(&v, 0, sizeof(v)); strcpy(v.name, "Radio"); if(copy_to_user(arg,&v, sizeof(v))) return -EFAULT; - return 0; + return 0; } case VIDIOCSAUDIO: { struct video_audio v; - if(copy_from_user(&v, arg, sizeof(v))) - return -EFAULT; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; if (tea->ops->mute) tea->ops->mute(tea, (v.flags & VIDEO_AUDIO_MUTE) ? 1 : 0); - if(v.audio) + if(v.audio) return -EINVAL; return 0; } @@ -240,11 +240,11 @@ static int __init alsa_tea575x_module_init(void) { return 0; } - + static void __exit alsa_tea575x_module_exit(void) { } - + module_init(alsa_tea575x_module_init) module_exit(alsa_tea575x_module_exit) -- cgit v1.2.3-70-g09d2 From 39aee69a166b775a38ed0053596cdb8e717ae315 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 1 Dec 2008 15:40:09 -0300 Subject: V4L/DVB (9815): omap2: add OMAP2 camera driver. Add a driver for the OMAP2 camera block. OMAP2 is used in e.g. Nokia N800/N810 internet tablet. This driver uses the V4L2 internal ioctl interface. Signed-off-by: Sakari Ailus Signed-off-by: Trilok Soni Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Kconfig | 7 + drivers/media/video/Makefile | 3 + drivers/media/video/omap24xxcam-dma.c | 601 +++++++++++ drivers/media/video/omap24xxcam.c | 1908 +++++++++++++++++++++++++++++++++ drivers/media/video/omap24xxcam.h | 593 ++++++++++ 5 files changed, 3112 insertions(+) create mode 100644 drivers/media/video/omap24xxcam-dma.c create mode 100644 drivers/media/video/omap24xxcam.c create mode 100644 drivers/media/video/omap24xxcam.h (limited to 'drivers/media/video/Makefile') diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 8e4b798a7e2..c487d2a1285 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -770,6 +770,13 @@ config VIDEO_SH_MOBILE_CEU ---help--- This is a v4l2 driver for the SuperH Mobile CEU Interface +config VIDEO_OMAP2 + tristate "OMAP2 Camera Capture Interface driver" + depends on VIDEO_DEV && ARCH_OMAP2 + select VIDEOBUF_DMA_SG + ---help--- + This is a v4l2 driver for the TI OMAP2 camera capture interface + # # USB Multimedia device configuration # diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 53d43f9e017..ac147b1aea3 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -8,6 +8,8 @@ msp3400-objs := msp3400-driver.o msp3400-kthreads.o stkwebcam-objs := stk-webcam.o stk-sensor.o +omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o + videodev-objs := v4l2-dev.o v4l2-ioctl.o obj-$(CONFIG_VIDEO_DEV) += videodev.o v4l2-compat-ioctl32.o v4l2-int-device.o @@ -130,6 +132,7 @@ obj-$(CONFIG_VIDEO_CX23885) += cx23885/ obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o +obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o obj-$(CONFIG_SOC_CAMERA) += soc_camera.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o diff --git a/drivers/media/video/omap24xxcam-dma.c b/drivers/media/video/omap24xxcam-dma.c new file mode 100644 index 00000000000..1d54b86c936 --- /dev/null +++ b/drivers/media/video/omap24xxcam-dma.c @@ -0,0 +1,601 @@ +/* + * drivers/media/video/omap24xxcam-dma.c + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus + * + * Based on code from Andy Lowe and + * David Cohen . + * + * This program 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include + +#include "omap24xxcam.h" + +/* + * + * DMA hardware. + * + */ + +/* Ack all interrupt on CSR and IRQSTATUS_L0 */ +static void omap24xxcam_dmahw_ack_all(unsigned long base) +{ + u32 csr; + int i; + + for (i = 0; i < NUM_CAMDMA_CHANNELS; ++i) { + csr = omap24xxcam_reg_in(base, CAMDMA_CSR(i)); + /* ack interrupt in CSR */ + omap24xxcam_reg_out(base, CAMDMA_CSR(i), csr); + } + omap24xxcam_reg_out(base, CAMDMA_IRQSTATUS_L0, 0xf); +} + +/* Ack dmach on CSR and IRQSTATUS_L0 */ +static u32 omap24xxcam_dmahw_ack_ch(unsigned long base, int dmach) +{ + u32 csr; + + csr = omap24xxcam_reg_in(base, CAMDMA_CSR(dmach)); + /* ack interrupt in CSR */ + omap24xxcam_reg_out(base, CAMDMA_CSR(dmach), csr); + /* ack interrupt in IRQSTATUS */ + omap24xxcam_reg_out(base, CAMDMA_IRQSTATUS_L0, (1 << dmach)); + + return csr; +} + +static int omap24xxcam_dmahw_running(unsigned long base, int dmach) +{ + return omap24xxcam_reg_in(base, CAMDMA_CCR(dmach)) & CAMDMA_CCR_ENABLE; +} + +static void omap24xxcam_dmahw_transfer_setup(unsigned long base, int dmach, + dma_addr_t start, u32 len) +{ + omap24xxcam_reg_out(base, CAMDMA_CCR(dmach), + CAMDMA_CCR_SEL_SRC_DST_SYNC + | CAMDMA_CCR_BS + | CAMDMA_CCR_DST_AMODE_POST_INC + | CAMDMA_CCR_SRC_AMODE_POST_INC + | CAMDMA_CCR_FS + | CAMDMA_CCR_WR_ACTIVE + | CAMDMA_CCR_RD_ACTIVE + | CAMDMA_CCR_SYNCHRO_CAMERA); + omap24xxcam_reg_out(base, CAMDMA_CLNK_CTRL(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CEN(dmach), len); + omap24xxcam_reg_out(base, CAMDMA_CFN(dmach), 1); + omap24xxcam_reg_out(base, CAMDMA_CSDP(dmach), + CAMDMA_CSDP_WRITE_MODE_POSTED + | CAMDMA_CSDP_DST_BURST_EN_32 + | CAMDMA_CSDP_DST_PACKED + | CAMDMA_CSDP_SRC_BURST_EN_32 + | CAMDMA_CSDP_SRC_PACKED + | CAMDMA_CSDP_DATA_TYPE_8BITS); + omap24xxcam_reg_out(base, CAMDMA_CSSA(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CDSA(dmach), start); + omap24xxcam_reg_out(base, CAMDMA_CSEI(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CSFI(dmach), DMA_THRESHOLD); + omap24xxcam_reg_out(base, CAMDMA_CDEI(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CDFI(dmach), 0); + omap24xxcam_reg_out(base, CAMDMA_CSR(dmach), + CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR + | CAMDMA_CSR_BLOCK + | CAMDMA_CSR_DROP); + omap24xxcam_reg_out(base, CAMDMA_CICR(dmach), + CAMDMA_CICR_MISALIGNED_ERR_IE + | CAMDMA_CICR_SECURE_ERR_IE + | CAMDMA_CICR_TRANS_ERR_IE + | CAMDMA_CICR_BLOCK_IE + | CAMDMA_CICR_DROP_IE); +} + +static void omap24xxcam_dmahw_transfer_start(unsigned long base, int dmach) +{ + omap24xxcam_reg_out(base, CAMDMA_CCR(dmach), + CAMDMA_CCR_SEL_SRC_DST_SYNC + | CAMDMA_CCR_BS + | CAMDMA_CCR_DST_AMODE_POST_INC + | CAMDMA_CCR_SRC_AMODE_POST_INC + | CAMDMA_CCR_ENABLE + | CAMDMA_CCR_FS + | CAMDMA_CCR_SYNCHRO_CAMERA); +} + +static void omap24xxcam_dmahw_transfer_chain(unsigned long base, int dmach, + int free_dmach) +{ + int prev_dmach, ch; + + if (dmach == 0) + prev_dmach = NUM_CAMDMA_CHANNELS - 1; + else + prev_dmach = dmach - 1; + omap24xxcam_reg_out(base, CAMDMA_CLNK_CTRL(prev_dmach), + CAMDMA_CLNK_CTRL_ENABLE_LNK | dmach); + /* Did we chain the DMA transfer before the previous one + * finished? + */ + ch = (dmach + free_dmach) % NUM_CAMDMA_CHANNELS; + while (!(omap24xxcam_reg_in(base, CAMDMA_CCR(ch)) + & CAMDMA_CCR_ENABLE)) { + if (ch == dmach) { + /* The previous transfer has ended and this one + * hasn't started, so we must not have chained + * to the previous one in time. We'll have to + * start it now. + */ + omap24xxcam_dmahw_transfer_start(base, dmach); + break; + } else + ch = (ch + 1) % NUM_CAMDMA_CHANNELS; + } +} + +/* Abort all chained DMA transfers. After all transfers have been + * aborted and the DMA controller is idle, the completion routines for + * any aborted transfers will be called in sequence. The DMA + * controller may not be idle after this routine completes, because + * the completion routines might start new transfers. + */ +static void omap24xxcam_dmahw_abort_ch(unsigned long base, int dmach) +{ + /* mask all interrupts from this channel */ + omap24xxcam_reg_out(base, CAMDMA_CICR(dmach), 0); + /* unlink this channel */ + omap24xxcam_reg_merge(base, CAMDMA_CLNK_CTRL(dmach), 0, + CAMDMA_CLNK_CTRL_ENABLE_LNK); + /* disable this channel */ + omap24xxcam_reg_merge(base, CAMDMA_CCR(dmach), 0, CAMDMA_CCR_ENABLE); +} + +static void omap24xxcam_dmahw_init(unsigned long base) +{ + omap24xxcam_reg_out(base, CAMDMA_OCP_SYSCONFIG, + CAMDMA_OCP_SYSCONFIG_MIDLEMODE_FSTANDBY + | CAMDMA_OCP_SYSCONFIG_SIDLEMODE_FIDLE + | CAMDMA_OCP_SYSCONFIG_AUTOIDLE); + + omap24xxcam_reg_merge(base, CAMDMA_GCR, 0x10, + CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH); + + omap24xxcam_reg_out(base, CAMDMA_IRQENABLE_L0, 0xf); +} + +/* + * + * Individual DMA channel handling. + * + */ + +/* Start a DMA transfer from the camera to memory. + * Returns zero if the transfer was successfully started, or non-zero if all + * DMA channels are already in use or starting is currently inhibited. + */ +static int omap24xxcam_dma_start(struct omap24xxcam_dma *dma, dma_addr_t start, + u32 len, dma_callback_t callback, void *arg) +{ + unsigned long flags; + int dmach; + + spin_lock_irqsave(&dma->lock, flags); + + if (!dma->free_dmach || atomic_read(&dma->dma_stop)) { + spin_unlock_irqrestore(&dma->lock, flags); + return -EBUSY; + } + + dmach = dma->next_dmach; + + dma->ch_state[dmach].callback = callback; + dma->ch_state[dmach].arg = arg; + + omap24xxcam_dmahw_transfer_setup(dma->base, dmach, start, len); + + /* We're ready to start the DMA transfer. */ + + if (dma->free_dmach < NUM_CAMDMA_CHANNELS) { + /* A transfer is already in progress, so try to chain to it. */ + omap24xxcam_dmahw_transfer_chain(dma->base, dmach, + dma->free_dmach); + } else { + /* No transfer is in progress, so we'll just start this one + * now. + */ + omap24xxcam_dmahw_transfer_start(dma->base, dmach); + } + + dma->next_dmach = (dma->next_dmach + 1) % NUM_CAMDMA_CHANNELS; + dma->free_dmach--; + + spin_unlock_irqrestore(&dma->lock, flags); + + return 0; +} + +/* Abort all chained DMA transfers. After all transfers have been + * aborted and the DMA controller is idle, the completion routines for + * any aborted transfers will be called in sequence. The DMA + * controller may not be idle after this routine completes, because + * the completion routines might start new transfers. + */ +static void omap24xxcam_dma_abort(struct omap24xxcam_dma *dma, u32 csr) +{ + unsigned long flags; + int dmach, i, free_dmach; + dma_callback_t callback; + void *arg; + + spin_lock_irqsave(&dma->lock, flags); + + /* stop any DMA transfers in progress */ + dmach = (dma->next_dmach + dma->free_dmach) % NUM_CAMDMA_CHANNELS; + for (i = 0; i < NUM_CAMDMA_CHANNELS; i++) { + omap24xxcam_dmahw_abort_ch(dma->base, dmach); + dmach = (dmach + 1) % NUM_CAMDMA_CHANNELS; + } + + /* We have to be careful here because the callback routine + * might start a new DMA transfer, and we only want to abort + * transfers that were started before this routine was called. + */ + free_dmach = dma->free_dmach; + while ((dma->free_dmach < NUM_CAMDMA_CHANNELS) && + (free_dmach < NUM_CAMDMA_CHANNELS)) { + dmach = (dma->next_dmach + dma->free_dmach) + % NUM_CAMDMA_CHANNELS; + callback = dma->ch_state[dmach].callback; + arg = dma->ch_state[dmach].arg; + dma->free_dmach++; + free_dmach++; + if (callback) { + /* leave interrupts disabled during callback */ + spin_unlock(&dma->lock); + (*callback) (dma, csr, arg); + spin_lock(&dma->lock); + } + } + + spin_unlock_irqrestore(&dma->lock, flags); +} + +/* Abort all chained DMA transfers. After all transfers have been + * aborted and the DMA controller is idle, the completion routines for + * any aborted transfers will be called in sequence. If the completion + * routines attempt to start a new DMA transfer it will fail, so the + * DMA controller will be idle after this routine completes. + */ +static void omap24xxcam_dma_stop(struct omap24xxcam_dma *dma, u32 csr) +{ + atomic_inc(&dma->dma_stop); + omap24xxcam_dma_abort(dma, csr); + atomic_dec(&dma->dma_stop); +} + +/* Camera DMA interrupt service routine. */ +void omap24xxcam_dma_isr(struct omap24xxcam_dma *dma) +{ + int dmach; + dma_callback_t callback; + void *arg; + u32 csr; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + + spin_lock(&dma->lock); + + if (dma->free_dmach == NUM_CAMDMA_CHANNELS) { + /* A camera DMA interrupt occurred while all channels + * are idle, so we'll acknowledge the interrupt in the + * IRQSTATUS register and exit. + */ + omap24xxcam_dmahw_ack_all(dma->base); + spin_unlock(&dma->lock); + return; + } + + while (dma->free_dmach < NUM_CAMDMA_CHANNELS) { + dmach = (dma->next_dmach + dma->free_dmach) + % NUM_CAMDMA_CHANNELS; + if (omap24xxcam_dmahw_running(dma->base, dmach)) { + /* This buffer hasn't finished yet, so we're done. */ + break; + } + csr = omap24xxcam_dmahw_ack_ch(dma->base, dmach); + if (csr & csr_error) { + /* A DMA error occurred, so stop all DMA + * transfers in progress. + */ + spin_unlock(&dma->lock); + omap24xxcam_dma_stop(dma, csr); + return; + } else { + callback = dma->ch_state[dmach].callback; + arg = dma->ch_state[dmach].arg; + dma->free_dmach++; + if (callback) { + spin_unlock(&dma->lock); + (*callback) (dma, csr, arg); + spin_lock(&dma->lock); + } + } + } + + spin_unlock(&dma->lock); + + omap24xxcam_sgdma_process( + container_of(dma, struct omap24xxcam_sgdma, dma)); +} + +void omap24xxcam_dma_hwinit(struct omap24xxcam_dma *dma) +{ + unsigned long flags; + + spin_lock_irqsave(&dma->lock, flags); + + omap24xxcam_dmahw_init(dma->base); + + spin_unlock_irqrestore(&dma->lock, flags); +} + +static void omap24xxcam_dma_init(struct omap24xxcam_dma *dma, + unsigned long base) +{ + int ch; + + /* group all channels on DMA IRQ0 and unmask irq */ + spin_lock_init(&dma->lock); + dma->base = base; + dma->free_dmach = NUM_CAMDMA_CHANNELS; + dma->next_dmach = 0; + for (ch = 0; ch < NUM_CAMDMA_CHANNELS; ch++) { + dma->ch_state[ch].callback = NULL; + dma->ch_state[ch].arg = NULL; + } +} + +/* + * + * Scatter-gather DMA. + * + * High-level DMA construct for transferring whole picture frames to + * memory that is discontinuous. + * + */ + +/* DMA completion routine for the scatter-gather DMA fragments. */ +static void omap24xxcam_sgdma_callback(struct omap24xxcam_dma *dma, u32 csr, + void *arg) +{ + struct omap24xxcam_sgdma *sgdma = + container_of(dma, struct omap24xxcam_sgdma, dma); + int sgslot = (int)arg; + struct sgdma_state *sg_state; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + + spin_lock(&sgdma->lock); + + /* We got an interrupt, we can remove the timer */ + del_timer(&sgdma->reset_timer); + + sg_state = sgdma->sg_state + sgslot; + if (!sg_state->queued_sglist) { + spin_unlock(&sgdma->lock); + printk(KERN_ERR "%s: sgdma completed when none queued!\n", + __func__); + return; + } + + sg_state->csr |= csr; + if (!--sg_state->queued_sglist) { + /* Queue for this sglist is empty, so check to see if we're + * done. + */ + if ((sg_state->next_sglist == sg_state->sglen) + || (sg_state->csr & csr_error)) { + sgdma_callback_t callback = sg_state->callback; + void *arg = sg_state->arg; + u32 sg_csr = sg_state->csr; + /* All done with this sglist */ + sgdma->free_sgdma++; + if (callback) { + spin_unlock(&sgdma->lock); + (*callback) (sgdma, sg_csr, arg); + return; + } + } + } + + spin_unlock(&sgdma->lock); +} + +/* Start queued scatter-gather DMA transfers. */ +void omap24xxcam_sgdma_process(struct omap24xxcam_sgdma *sgdma) +{ + unsigned long flags; + int queued_sgdma, sgslot; + struct sgdma_state *sg_state; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + + spin_lock_irqsave(&sgdma->lock, flags); + + queued_sgdma = NUM_SG_DMA - sgdma->free_sgdma; + sgslot = (sgdma->next_sgdma + sgdma->free_sgdma) % NUM_SG_DMA; + while (queued_sgdma > 0) { + sg_state = sgdma->sg_state + sgslot; + while ((sg_state->next_sglist < sg_state->sglen) && + !(sg_state->csr & csr_error)) { + const struct scatterlist *sglist; + unsigned int len; + + sglist = sg_state->sglist + sg_state->next_sglist; + /* try to start the next DMA transfer */ + if (sg_state->next_sglist + 1 == sg_state->sglen) { + /* + * On the last sg, we handle the case where + * cam->img.pix.sizeimage % PAGE_ALIGN != 0 + */ + len = sg_state->len - sg_state->bytes_read; + } else { + len = sg_dma_len(sglist); + } + + if (omap24xxcam_dma_start(&sgdma->dma, + sg_dma_address(sglist), + len, + omap24xxcam_sgdma_callback, + (void *)sgslot)) { + /* DMA start failed */ + spin_unlock_irqrestore(&sgdma->lock, flags); + return; + } else { + unsigned long expires; + /* DMA start was successful */ + sg_state->next_sglist++; + sg_state->bytes_read += len; + sg_state->queued_sglist++; + + /* We start the reset timer */ + expires = jiffies + HZ; + mod_timer(&sgdma->reset_timer, expires); + } + } + queued_sgdma--; + sgslot = (sgslot + 1) % NUM_SG_DMA; + } + + spin_unlock_irqrestore(&sgdma->lock, flags); +} + +/* + * Queue a scatter-gather DMA transfer from the camera to memory. + * Returns zero if the transfer was successfully queued, or non-zero + * if all of the scatter-gather slots are already in use. + */ +int omap24xxcam_sgdma_queue(struct omap24xxcam_sgdma *sgdma, + const struct scatterlist *sglist, int sglen, + int len, sgdma_callback_t callback, void *arg) +{ + unsigned long flags; + struct sgdma_state *sg_state; + + if ((sglen < 0) || ((sglen > 0) & !sglist)) + return -EINVAL; + + spin_lock_irqsave(&sgdma->lock, flags); + + if (!sgdma->free_sgdma) { + spin_unlock_irqrestore(&sgdma->lock, flags); + return -EBUSY; + } + + sg_state = sgdma->sg_state + sgdma->next_sgdma; + + sg_state->sglist = sglist; + sg_state->sglen = sglen; + sg_state->next_sglist = 0; + sg_state->bytes_read = 0; + sg_state->len = len; + sg_state->queued_sglist = 0; + sg_state->csr = 0; + sg_state->callback = callback; + sg_state->arg = arg; + + sgdma->next_sgdma = (sgdma->next_sgdma + 1) % NUM_SG_DMA; + sgdma->free_sgdma--; + + spin_unlock_irqrestore(&sgdma->lock, flags); + + omap24xxcam_sgdma_process(sgdma); + + return 0; +} + +/* Sync scatter-gather DMA by aborting any DMA transfers currently in progress. + * Any queued scatter-gather DMA transactions that have not yet been started + * will remain queued. The DMA controller will be idle after this routine + * completes. When the scatter-gather queue is restarted, the next + * scatter-gather DMA transfer will begin at the start of a new transaction. + */ +void omap24xxcam_sgdma_sync(struct omap24xxcam_sgdma *sgdma) +{ + unsigned long flags; + int sgslot; + struct sgdma_state *sg_state; + u32 csr = CAMDMA_CSR_TRANS_ERR; + + /* stop any DMA transfers in progress */ + omap24xxcam_dma_stop(&sgdma->dma, csr); + + spin_lock_irqsave(&sgdma->lock, flags); + + if (sgdma->free_sgdma < NUM_SG_DMA) { + sgslot = (sgdma->next_sgdma + sgdma->free_sgdma) % NUM_SG_DMA; + sg_state = sgdma->sg_state + sgslot; + if (sg_state->next_sglist != 0) { + /* This DMA transfer was in progress, so abort it. */ + sgdma_callback_t callback = sg_state->callback; + void *arg = sg_state->arg; + sgdma->free_sgdma++; + if (callback) { + /* leave interrupts masked */ + spin_unlock(&sgdma->lock); + (*callback) (sgdma, csr, arg); + spin_lock(&sgdma->lock); + } + } + } + + spin_unlock_irqrestore(&sgdma->lock, flags); +} + +void omap24xxcam_sgdma_init(struct omap24xxcam_sgdma *sgdma, + unsigned long base, + void (*reset_callback)(unsigned long data), + unsigned long reset_callback_data) +{ + int sg; + + spin_lock_init(&sgdma->lock); + sgdma->free_sgdma = NUM_SG_DMA; + sgdma->next_sgdma = 0; + for (sg = 0; sg < NUM_SG_DMA; sg++) { + sgdma->sg_state[sg].sglen = 0; + sgdma->sg_state[sg].next_sglist = 0; + sgdma->sg_state[sg].bytes_read = 0; + sgdma->sg_state[sg].queued_sglist = 0; + sgdma->sg_state[sg].csr = 0; + sgdma->sg_state[sg].callback = NULL; + sgdma->sg_state[sg].arg = NULL; + } + + omap24xxcam_dma_init(&sgdma->dma, base); + setup_timer(&sgdma->reset_timer, reset_callback, reset_callback_data); +} diff --git a/drivers/media/video/omap24xxcam.c b/drivers/media/video/omap24xxcam.c new file mode 100644 index 00000000000..85c3c7c92af --- /dev/null +++ b/drivers/media/video/omap24xxcam.c @@ -0,0 +1,1908 @@ +/* + * drivers/media/video/omap24xxcam.c + * + * OMAP 2 camera block driver. + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * Copyright (C) 2007-2008 Nokia Corporation. + * + * Contact: Sakari Ailus + * + * Based on code from Andy Lowe + * + * This program 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include /* needed for videobufs */ +#include +#include +#include +#include + +#include +#include + +#include "omap24xxcam.h" + +#define OMAP24XXCAM_VERSION KERNEL_VERSION(0, 0, 0) + +#define RESET_TIMEOUT_NS 10000 + +static void omap24xxcam_reset(struct omap24xxcam_device *cam); +static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam); +static void omap24xxcam_device_unregister(struct v4l2_int_device *s); +static int omap24xxcam_remove(struct platform_device *pdev); + +/* module parameters */ +static int video_nr = -1; /* video device minor (-1 ==> auto assign) */ +/* + * Maximum amount of memory to use for capture buffers. + * Default is 4800KB, enough to double-buffer SXGA. + */ +static int capture_mem = 1280 * 960 * 2 * 2; + +static struct v4l2_int_device omap24xxcam; + +/* + * + * Clocks. + * + */ + +static void omap24xxcam_clock_put(struct omap24xxcam_device *cam) +{ + if (cam->ick != NULL && !IS_ERR(cam->ick)) + clk_put(cam->ick); + if (cam->fck != NULL && !IS_ERR(cam->fck)) + clk_put(cam->fck); + + cam->ick = cam->fck = NULL; +} + +static int omap24xxcam_clock_get(struct omap24xxcam_device *cam) +{ + int rval = 0; + + cam->fck = clk_get(cam->dev, "cam_fck"); + if (IS_ERR(cam->fck)) { + dev_err(cam->dev, "can't get cam_fck"); + rval = PTR_ERR(cam->fck); + omap24xxcam_clock_put(cam); + return rval; + } + + cam->ick = clk_get(cam->dev, "cam_ick"); + if (IS_ERR(cam->ick)) { + dev_err(cam->dev, "can't get cam_ick"); + rval = PTR_ERR(cam->ick); + omap24xxcam_clock_put(cam); + } + + return rval; +} + +static void omap24xxcam_clock_on(struct omap24xxcam_device *cam) +{ + clk_enable(cam->fck); + clk_enable(cam->ick); +} + +static void omap24xxcam_clock_off(struct omap24xxcam_device *cam) +{ + clk_disable(cam->fck); + clk_disable(cam->ick); +} + +/* + * + * Camera core + * + */ + +/* + * Set xclk. + * + * To disable xclk, use value zero. + */ +static void omap24xxcam_core_xclk_set(const struct omap24xxcam_device *cam, + u32 xclk) +{ + if (xclk) { + u32 divisor = CAM_MCLK / xclk; + + if (divisor == 1) + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, + CC_CTRL_XCLK_DIV_BYPASS); + else + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, divisor); + } else + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, + CC_CTRL_XCLK, CC_CTRL_XCLK_DIV_STABLE_LOW); +} + +static void omap24xxcam_core_hwinit(const struct omap24xxcam_device *cam) +{ + /* + * Setting the camera core AUTOIDLE bit causes problems with frame + * synchronization, so we will clear the AUTOIDLE bit instead. + */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_SYSCONFIG, + CC_SYSCONFIG_AUTOIDLE); + + /* program the camera interface DMA packet size */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL_DMA, + CC_CTRL_DMA_EN | (DMA_THRESHOLD / 4 - 1)); + + /* enable camera core error interrupts */ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQENABLE, + CC_IRQENABLE_FW_ERR_IRQ + | CC_IRQENABLE_FSC_ERR_IRQ + | CC_IRQENABLE_SSC_ERR_IRQ + | CC_IRQENABLE_FIFO_OF_IRQ); +} + +/* + * Enable the camera core. + * + * Data transfer to the camera DMA starts from next starting frame. + */ +static void omap24xxcam_core_enable(const struct omap24xxcam_device *cam) +{ + + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL, + cam->cc_ctrl); +} + +/* + * Disable camera core. + * + * The data transfer will be stopped immediately (CC_CTRL_CC_RST). The + * core internal state machines will be reset. Use + * CC_CTRL_CC_FRAME_TRIG instead if you want to transfer the current + * frame completely. + */ +static void omap24xxcam_core_disable(const struct omap24xxcam_device *cam) +{ + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_CTRL, + CC_CTRL_CC_RST); +} + +/* Interrupt service routine for camera core interrupts. */ +static void omap24xxcam_core_isr(struct omap24xxcam_device *cam) +{ + u32 cc_irqstatus; + const u32 cc_irqstatus_err = + CC_IRQSTATUS_FW_ERR_IRQ + | CC_IRQSTATUS_FSC_ERR_IRQ + | CC_IRQSTATUS_SSC_ERR_IRQ + | CC_IRQSTATUS_FIFO_UF_IRQ + | CC_IRQSTATUS_FIFO_OF_IRQ; + + cc_irqstatus = omap24xxcam_reg_in(cam->mmio_base + CC_REG_OFFSET, + CC_IRQSTATUS); + omap24xxcam_reg_out(cam->mmio_base + CC_REG_OFFSET, CC_IRQSTATUS, + cc_irqstatus); + + if (cc_irqstatus & cc_irqstatus_err + && !atomic_read(&cam->in_reset)) { + dev_dbg(cam->dev, "resetting camera, cc_irqstatus 0x%x\n", + cc_irqstatus); + omap24xxcam_reset(cam); + } +} + +/* + * + * videobuf_buffer handling. + * + * Memory for mmapped videobuf_buffers is not allocated + * conventionally, but by several kmalloc allocations and then + * creating the scatterlist on our own. User-space buffers are handled + * normally. + * + */ + +/* + * Free the memory-mapped buffer memory allocated for a + * videobuf_buffer and the associated scatterlist. + */ +static void omap24xxcam_vbq_free_mmap_buffer(struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + size_t alloc_size; + struct page *page; + int i; + + if (dma->sglist == NULL) + return; + + i = dma->sglen; + while (i) { + i--; + alloc_size = sg_dma_len(&dma->sglist[i]); + page = sg_page(&dma->sglist[i]); + do { + ClearPageReserved(page++); + } while (alloc_size -= PAGE_SIZE); + __free_pages(sg_page(&dma->sglist[i]), + get_order(sg_dma_len(&dma->sglist[i]))); + } + + kfree(dma->sglist); + dma->sglist = NULL; +} + +/* Release all memory related to the videobuf_queue. */ +static void omap24xxcam_vbq_free_mmap_buffers(struct videobuf_queue *vbq) +{ + int i; + + mutex_lock(&vbq->vb_lock); + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == vbq->bufs[i]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[i]->memory) + continue; + vbq->ops->buf_release(vbq, vbq->bufs[i]); + omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]); + kfree(vbq->bufs[i]); + vbq->bufs[i] = NULL; + } + + mutex_unlock(&vbq->vb_lock); + + videobuf_mmap_free(vbq); +} + +/* + * Allocate physically as contiguous as possible buffer for video + * frame and allocate and build DMA scatter-gather list for it. + */ +static int omap24xxcam_vbq_alloc_mmap_buffer(struct videobuf_buffer *vb) +{ + unsigned int order; + size_t alloc_size, size = vb->bsize; /* vb->bsize is page aligned */ + struct page *page; + int max_pages, err = 0, i = 0; + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + /* + * allocate maximum size scatter-gather list. Note this is + * overhead. We may not use as many entries as we allocate + */ + max_pages = vb->bsize >> PAGE_SHIFT; + dma->sglist = kcalloc(max_pages, sizeof(*dma->sglist), GFP_KERNEL); + if (dma->sglist == NULL) { + err = -ENOMEM; + goto out; + } + + while (size) { + order = get_order(size); + /* + * do not over-allocate even if we would get larger + * contiguous chunk that way + */ + if ((PAGE_SIZE << order) > size) + order--; + + /* try to allocate as many contiguous pages as possible */ + page = alloc_pages(GFP_KERNEL | GFP_DMA, order); + /* if allocation fails, try to allocate smaller amount */ + while (page == NULL) { + order--; + page = alloc_pages(GFP_KERNEL | GFP_DMA, order); + if (page == NULL && !order) { + err = -ENOMEM; + goto out; + } + } + size -= (PAGE_SIZE << order); + + /* append allocated chunk of pages into scatter-gather list */ + sg_set_page(&dma->sglist[i], page, PAGE_SIZE << order, 0); + dma->sglen++; + i++; + + alloc_size = (PAGE_SIZE << order); + + /* clear pages before giving them to user space */ + memset(page_address(page), 0, alloc_size); + + /* mark allocated pages reserved */ + do { + SetPageReserved(page++); + } while (alloc_size -= PAGE_SIZE); + } + /* + * REVISIT: not fully correct to assign nr_pages == sglen but + * video-buf is passing nr_pages for e.g. unmap_sg calls + */ + dma->nr_pages = dma->sglen; + dma->direction = PCI_DMA_FROMDEVICE; + + return 0; + +out: + omap24xxcam_vbq_free_mmap_buffer(vb); + return err; +} + +static int omap24xxcam_vbq_alloc_mmap_buffers(struct videobuf_queue *vbq, + unsigned int count) +{ + int i, err = 0; + struct omap24xxcam_fh *fh = + container_of(vbq, struct omap24xxcam_fh, vbq); + + mutex_lock(&vbq->vb_lock); + + for (i = 0; i < count; i++) { + err = omap24xxcam_vbq_alloc_mmap_buffer(vbq->bufs[i]); + if (err) + goto out; + dev_dbg(fh->cam->dev, "sglen is %d for buffer %d\n", + videobuf_to_dma(vbq->bufs[i])->sglen, i); + } + + mutex_unlock(&vbq->vb_lock); + + return 0; +out: + while (i) { + i--; + omap24xxcam_vbq_free_mmap_buffer(vbq->bufs[i]); + } + + mutex_unlock(&vbq->vb_lock); + + return err; +} + +/* + * This routine is called from interrupt context when a scatter-gather DMA + * transfer of a videobuf_buffer completes. + */ +static void omap24xxcam_vbq_complete(struct omap24xxcam_sgdma *sgdma, + u32 csr, void *arg) +{ + struct omap24xxcam_device *cam = + container_of(sgdma, struct omap24xxcam_device, sgdma); + struct omap24xxcam_fh *fh = cam->streaming->private_data; + struct videobuf_buffer *vb = (struct videobuf_buffer *)arg; + const u32 csr_error = CAMDMA_CSR_MISALIGNED_ERR + | CAMDMA_CSR_SUPERVISOR_ERR | CAMDMA_CSR_SECURE_ERR + | CAMDMA_CSR_TRANS_ERR | CAMDMA_CSR_DROP; + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + if (--cam->sgdma_in_queue == 0) + omap24xxcam_core_disable(cam); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + + do_gettimeofday(&vb->ts); + vb->field_count = atomic_add_return(2, &fh->field_count); + if (csr & csr_error) { + vb->state = VIDEOBUF_ERROR; + if (!atomic_read(&fh->cam->in_reset)) { + dev_dbg(cam->dev, "resetting camera, csr 0x%x\n", csr); + omap24xxcam_reset(cam); + } + } else + vb->state = VIDEOBUF_DONE; + wake_up(&vb->done); +} + +static void omap24xxcam_vbq_release(struct videobuf_queue *vbq, + struct videobuf_buffer *vb) +{ + struct videobuf_dmabuf *dma = videobuf_to_dma(vb); + + /* wait for buffer, especially to get out of the sgdma queue */ + videobuf_waiton(vb, 0, 0); + if (vb->memory == V4L2_MEMORY_MMAP) { + dma_unmap_sg(vbq->dev, dma->sglist, dma->sglen, + dma->direction); + dma->direction = DMA_NONE; + } else { + videobuf_dma_unmap(vbq, videobuf_to_dma(vb)); + videobuf_dma_free(videobuf_to_dma(vb)); + } + + vb->state = VIDEOBUF_NEEDS_INIT; +} + +/* + * Limit the number of available kernel image capture buffers based on the + * number requested, the currently selected image size, and the maximum + * amount of memory permitted for kernel capture buffers. + */ +static int omap24xxcam_vbq_setup(struct videobuf_queue *vbq, unsigned int *cnt, + unsigned int *size) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + + if (*cnt <= 0) + *cnt = VIDEO_MAX_FRAME; /* supply a default number of buffers */ + + if (*cnt > VIDEO_MAX_FRAME) + *cnt = VIDEO_MAX_FRAME; + + *size = fh->pix.sizeimage; + + /* accessing fh->cam->capture_mem is ok, it's constant */ + while (*size * *cnt > fh->cam->capture_mem) + (*cnt)--; + + return 0; +} + +static int omap24xxcam_dma_iolock(struct videobuf_queue *vbq, + struct videobuf_dmabuf *dma) +{ + int err = 0; + + dma->direction = PCI_DMA_FROMDEVICE; + if (!dma_map_sg(vbq->dev, dma->sglist, dma->sglen, dma->direction)) { + kfree(dma->sglist); + dma->sglist = NULL; + dma->sglen = 0; + err = -EIO; + } + + return err; +} + +static int omap24xxcam_vbq_prepare(struct videobuf_queue *vbq, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + int err = 0; + + /* + * Accessing pix here is okay since it's constant while + * streaming is on (and we only get called then). + */ + if (vb->baddr) { + /* This is a userspace buffer. */ + if (fh->pix.sizeimage > vb->bsize) { + /* The buffer isn't big enough. */ + err = -EINVAL; + } else + vb->size = fh->pix.sizeimage; + } else { + if (vb->state != VIDEOBUF_NEEDS_INIT) { + /* + * We have a kernel bounce buffer that has + * already been allocated. + */ + if (fh->pix.sizeimage > vb->size) { + /* + * The image size has been changed to + * a larger size since this buffer was + * allocated, so we need to free and + * reallocate it. + */ + omap24xxcam_vbq_release(vbq, vb); + vb->size = fh->pix.sizeimage; + } + } else { + /* We need to allocate a new kernel bounce buffer. */ + vb->size = fh->pix.sizeimage; + } + } + + if (err) + return err; + + vb->width = fh->pix.width; + vb->height = fh->pix.height; + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + if (vb->memory == V4L2_MEMORY_MMAP) + /* + * we have built the scatter-gather list by ourself so + * do the scatter-gather mapping as well + */ + err = omap24xxcam_dma_iolock(vbq, videobuf_to_dma(vb)); + else + err = videobuf_iolock(vbq, vb, NULL); + } + + if (!err) + vb->state = VIDEOBUF_PREPARED; + else + omap24xxcam_vbq_release(vbq, vb); + + return err; +} + +static void omap24xxcam_vbq_queue(struct videobuf_queue *vbq, + struct videobuf_buffer *vb) +{ + struct omap24xxcam_fh *fh = vbq->priv_data; + struct omap24xxcam_device *cam = fh->cam; + enum videobuf_state state = vb->state; + unsigned long flags; + int err; + + /* + * FIXME: We're marking the buffer active since we have no + * pretty way of marking it active exactly when the + * scatter-gather transfer starts. + */ + vb->state = VIDEOBUF_ACTIVE; + + err = omap24xxcam_sgdma_queue(&fh->cam->sgdma, + videobuf_to_dma(vb)->sglist, + videobuf_to_dma(vb)->sglen, vb->size, + omap24xxcam_vbq_complete, vb); + + if (!err) { + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + if (++cam->sgdma_in_queue == 1 + && !atomic_read(&cam->in_reset)) + omap24xxcam_core_enable(cam); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + } else { + /* + * Oops. We're not supposed to get any errors here. + * The only way we could get an error is if we ran out + * of scatter-gather DMA slots, but we are supposed to + * have at least as many scatter-gather DMA slots as + * video buffers so that can't happen. + */ + dev_err(cam->dev, "failed to queue a video buffer for dma!\n"); + dev_err(cam->dev, "likely a bug in the driver!\n"); + vb->state = state; + } +} + +static struct videobuf_queue_ops omap24xxcam_vbq_ops = { + .buf_setup = omap24xxcam_vbq_setup, + .buf_prepare = omap24xxcam_vbq_prepare, + .buf_queue = omap24xxcam_vbq_queue, + .buf_release = omap24xxcam_vbq_release, +}; + +/* + * + * OMAP main camera system + * + */ + +/* + * Reset camera block to power-on state. + */ +static void omap24xxcam_poweron_reset(struct omap24xxcam_device *cam) +{ + int max_loop = RESET_TIMEOUT_NS; + + /* Reset whole camera subsystem */ + omap24xxcam_reg_out(cam->mmio_base, + CAM_SYSCONFIG, + CAM_SYSCONFIG_SOFTRESET); + + /* Wait till it's finished */ + while (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS) + & CAM_SYSSTATUS_RESETDONE) + && --max_loop) { + ndelay(1); + } + + if (!(omap24xxcam_reg_in(cam->mmio_base, CAM_SYSSTATUS) + & CAM_SYSSTATUS_RESETDONE)) + dev_err(cam->dev, "camera soft reset timeout\n"); +} + +/* + * (Re)initialise the camera block. + */ +static void omap24xxcam_hwinit(struct omap24xxcam_device *cam) +{ + omap24xxcam_poweron_reset(cam); + + /* set the camera subsystem autoidle bit */ + omap24xxcam_reg_out(cam->mmio_base, CAM_SYSCONFIG, + CAM_SYSCONFIG_AUTOIDLE); + + /* set the camera MMU autoidle bit */ + omap24xxcam_reg_out(cam->mmio_base, + CAMMMU_REG_OFFSET + CAMMMU_SYSCONFIG, + CAMMMU_SYSCONFIG_AUTOIDLE); + + omap24xxcam_core_hwinit(cam); + + omap24xxcam_dma_hwinit(&cam->sgdma.dma); +} + +/* + * Callback for dma transfer stalling. + */ +static void omap24xxcam_stalled_dma_reset(unsigned long data) +{ + struct omap24xxcam_device *cam = (struct omap24xxcam_device *)data; + + if (!atomic_read(&cam->in_reset)) { + dev_dbg(cam->dev, "dma stalled, resetting camera\n"); + omap24xxcam_reset(cam); + } +} + +/* + * Stop capture. Mark we're doing a reset, stop DMA transfers and + * core. (No new scatter-gather transfers will be queued whilst + * in_reset is non-zero.) + * + * If omap24xxcam_capture_stop is called from several places at + * once, only the first call will have an effect. Similarly, the last + * call omap24xxcam_streaming_cont will have effect. + * + * Serialisation is ensured by using cam->core_enable_disable_lock. + */ +static void omap24xxcam_capture_stop(struct omap24xxcam_device *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + + if (atomic_inc_return(&cam->in_reset) != 1) { + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + return; + } + + omap24xxcam_core_disable(cam); + + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); + + omap24xxcam_sgdma_sync(&cam->sgdma); +} + +/* + * Reset and continue streaming. + * + * Note: Resetting the camera FIFO via the CC_RST bit in the CC_CTRL + * register is supposed to be sufficient to recover from a camera + * interface error, but it doesn't seem to be enough. If we only do + * that then subsequent image captures are out of sync by either one + * or two times DMA_THRESHOLD bytes. Resetting and re-initializing the + * entire camera subsystem prevents the problem with frame + * synchronization. + */ +static void omap24xxcam_capture_cont(struct omap24xxcam_device *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->core_enable_disable_lock, flags); + + if (atomic_read(&cam->in_reset) != 1) + goto out; + + omap24xxcam_hwinit(cam); + + omap24xxcam_sensor_if_enable(cam); + + omap24xxcam_sgdma_process(&cam->sgdma); + + if (cam->sgdma_in_queue) + omap24xxcam_core_enable(cam); + +out: + atomic_dec(&cam->in_reset); + spin_unlock_irqrestore(&cam->core_enable_disable_lock, flags); +} + +static ssize_t +omap24xxcam_streaming_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct omap24xxcam_device *cam = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", cam->streaming ? "active" : "inactive"); +} +static DEVICE_ATTR(streaming, S_IRUGO, omap24xxcam_streaming_show, NULL); + +/* + * Stop capture and restart it. I.e. reset the camera during use. + */ +static void omap24xxcam_reset(struct omap24xxcam_device *cam) +{ + omap24xxcam_capture_stop(cam); + omap24xxcam_capture_cont(cam); +} + +/* + * The main interrupt handler. + */ +static irqreturn_t omap24xxcam_isr(int irq, void *arg) +{ + struct omap24xxcam_device *cam = (struct omap24xxcam_device *)arg; + u32 irqstatus; + unsigned int irqhandled = 0; + + irqstatus = omap24xxcam_reg_in(cam->mmio_base, CAM_IRQSTATUS); + + if (irqstatus & + (CAM_IRQSTATUS_DMA_IRQ2 | CAM_IRQSTATUS_DMA_IRQ1 + | CAM_IRQSTATUS_DMA_IRQ0)) { + omap24xxcam_dma_isr(&cam->sgdma.dma); + irqhandled = 1; + } + if (irqstatus & CAM_IRQSTATUS_CC_IRQ) { + omap24xxcam_core_isr(cam); + irqhandled = 1; + } + if (irqstatus & CAM_IRQSTATUS_MMU_IRQ) + dev_err(cam->dev, "unhandled camera MMU interrupt!\n"); + + return IRQ_RETVAL(irqhandled); +} + +/* + * + * Sensor handling. + * + */ + +/* + * Enable the external sensor interface. Try to negotiate interface + * parameters with the sensor and start using the new ones. The calls + * to sensor_if_enable and sensor_if_disable need not to be balanced. + */ +static int omap24xxcam_sensor_if_enable(struct omap24xxcam_device *cam) +{ + int rval; + struct v4l2_ifparm p; + + rval = vidioc_int_g_ifparm(cam->sdev, &p); + if (rval) { + dev_err(cam->dev, "vidioc_int_g_ifparm failed with %d\n", rval); + return rval; + } + + cam->if_type = p.if_type; + + cam->cc_ctrl = CC_CTRL_CC_EN; + + switch (p.if_type) { + case V4L2_IF_TYPE_BT656: + if (p.u.bt656.frame_start_on_rising_vs) + cam->cc_ctrl |= CC_CTRL_NOBT_SYNCHRO; + if (p.u.bt656.bt_sync_correct) + cam->cc_ctrl |= CC_CTRL_BT_CORRECT; + if (p.u.bt656.swap) + cam->cc_ctrl |= CC_CTRL_PAR_ORDERCAM; + if (p.u.bt656.latch_clk_inv) + cam->cc_ctrl |= CC_CTRL_PAR_CLK_POL; + if (p.u.bt656.nobt_hs_inv) + cam->cc_ctrl |= CC_CTRL_NOBT_HS_POL; + if (p.u.bt656.nobt_vs_inv) + cam->cc_ctrl |= CC_CTRL_NOBT_VS_POL; + + switch (p.u.bt656.mode) { + case V4L2_IF_TYPE_BT656_MODE_NOBT_8BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT8; + break; + case V4L2_IF_TYPE_BT656_MODE_NOBT_10BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT10; + break; + case V4L2_IF_TYPE_BT656_MODE_NOBT_12BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_NOBT12; + break; + case V4L2_IF_TYPE_BT656_MODE_BT_8BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT8; + break; + case V4L2_IF_TYPE_BT656_MODE_BT_10BIT: + cam->cc_ctrl |= CC_CTRL_PAR_MODE_BT10; + break; + default: + dev_err(cam->dev, + "bt656 interface mode %d not supported\n", + p.u.bt656.mode); + return -EINVAL; + } + /* + * The clock rate that the sensor wants has changed. + * We have to adjust the xclk from OMAP 2 side to + * match the sensor's wish as closely as possible. + */ + if (p.u.bt656.clock_curr != cam->if_u.bt656.xclk) { + u32 xclk = p.u.bt656.clock_curr; + u32 divisor; + + if (xclk == 0) + return -EINVAL; + + if (xclk > CAM_MCLK) + xclk = CAM_MCLK; + + divisor = CAM_MCLK / xclk; + if (divisor * xclk < CAM_MCLK) + divisor++; + if (CAM_MCLK / divisor < p.u.bt656.clock_min + && divisor > 1) + divisor--; + if (divisor > 30) + divisor = 30; + + xclk = CAM_MCLK / divisor; + + if (xclk < p.u.bt656.clock_min + || xclk > p.u.bt656.clock_max) + return -EINVAL; + + cam->if_u.bt656.xclk = xclk; + } + omap24xxcam_core_xclk_set(cam, cam->if_u.bt656.xclk); + break; + default: + /* FIXME: how about other interfaces? */ + dev_err(cam->dev, "interface type %d not supported\n", + p.if_type); + return -EINVAL; + } + + return 0; +} + +static void omap24xxcam_sensor_if_disable(const struct omap24xxcam_device *cam) +{ + switch (cam->if_type) { + case V4L2_IF_TYPE_BT656: + omap24xxcam_core_xclk_set(cam, 0); + break; + } +} + +/* + * Initialise the sensor hardware. + */ +static int omap24xxcam_sensor_init(struct omap24xxcam_device *cam) +{ + int err = 0; + struct v4l2_int_device *sdev = cam->sdev; + + omap24xxcam_clock_on(cam); + err = omap24xxcam_sensor_if_enable(cam); + if (err) { + dev_err(cam->dev, "sensor interface could not be enabled at " + "initialisation, %d\n", err); + cam->sdev = NULL; + goto out; + } + + /* power up sensor during sensor initialization */ + vidioc_int_s_power(sdev, 1); + + err = vidioc_int_dev_init(sdev); + if (err) { + dev_err(cam->dev, "cannot initialize sensor, error %d\n", err); + /* Sensor init failed --- it's nonexistent to us! */ + cam->sdev = NULL; + goto out; + } + + dev_info(cam->dev, "sensor is %s\n", sdev->name); + +out: + omap24xxcam_sensor_if_disable(cam); + omap24xxcam_clock_off(cam); + + vidioc_int_s_power(sdev, 0); + + return err; +} + +static void omap24xxcam_sensor_exit(struct omap24xxcam_device *cam) +{ + if (cam->sdev) + vidioc_int_dev_exit(cam->sdev); +} + +static void omap24xxcam_sensor_disable(struct omap24xxcam_device *cam) +{ + omap24xxcam_sensor_if_disable(cam); + omap24xxcam_clock_off(cam); + vidioc_int_s_power(cam->sdev, 0); +} + +/* + * Power-up and configure camera sensor. It's ready for capturing now. + */ +static int omap24xxcam_sensor_enable(struct omap24xxcam_device *cam) +{ + int rval; + + omap24xxcam_clock_on(cam); + + omap24xxcam_sensor_if_enable(cam); + + rval = vidioc_int_s_power(cam->sdev, 1); + if (rval) + goto out; + + rval = vidioc_int_init(cam->sdev); + if (rval) + goto out; + + return 0; + +out: + omap24xxcam_sensor_disable(cam); + + return rval; +} + +static void omap24xxcam_sensor_reset_work(struct work_struct *work) +{ + struct omap24xxcam_device *cam = + container_of(work, struct omap24xxcam_device, + sensor_reset_work); + + if (atomic_read(&cam->reset_disable)) + return; + + omap24xxcam_capture_stop(cam); + + if (vidioc_int_reset(cam->sdev) == 0) { + vidioc_int_init(cam->sdev); + } else { + /* Can't reset it by vidioc_int_reset. */ + omap24xxcam_sensor_disable(cam); + omap24xxcam_sensor_enable(cam); + } + + omap24xxcam_capture_cont(cam); +} + +/* + * + * IOCTL interface. + * + */ + +static int vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + + strlcpy(cap->driver, CAM_NAME, sizeof(cap->driver)); + strlcpy(cap->card, cam->vfd->name, sizeof(cap->card)); + cap->version = OMAP24XXCAM_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + rval = vidioc_int_enum_fmt_cap(cam->sdev, f); + + return rval; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_fmt_cap(cam->sdev, f); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + rval = vidioc_int_s_fmt_cap(cam->sdev, f); + +out: + mutex_unlock(&cam->mutex); + + if (!rval) { + mutex_lock(&ofh->vbq.vb_lock); + ofh->pix = f->fmt.pix; + mutex_unlock(&ofh->vbq.vb_lock); + } + + memset(f, 0, sizeof(*f)); + vidioc_g_fmt_vid_cap(file, fh, f); + + return rval; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_try_fmt_cap(cam->sdev, f); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *b) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + mutex_unlock(&cam->mutex); + return -EBUSY; + } + + omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq); + mutex_unlock(&cam->mutex); + + rval = videobuf_reqbufs(&ofh->vbq, b); + + /* + * Either videobuf_reqbufs failed or the buffers are not + * memory-mapped (which would need special attention). + */ + if (rval < 0 || b->memory != V4L2_MEMORY_MMAP) + goto out; + + rval = omap24xxcam_vbq_alloc_mmap_buffers(&ofh->vbq, rval); + if (rval) + omap24xxcam_vbq_free_mmap_buffers(&ofh->vbq); + +out: + return rval; +} + +static int vidioc_querybuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + + return videobuf_querybuf(&ofh->vbq, b); +} + +static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + + return videobuf_qbuf(&ofh->vbq, b); +} + +static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct videobuf_buffer *vb; + int rval; + +videobuf_dqbuf_again: + rval = videobuf_dqbuf(&ofh->vbq, b, file->f_flags & O_NONBLOCK); + if (rval) + goto out; + + vb = ofh->vbq.bufs[b->index]; + + mutex_lock(&cam->mutex); + /* _needs_reset returns -EIO if reset is required. */ + rval = vidioc_int_g_needs_reset(cam->sdev, (void *)vb->baddr); + mutex_unlock(&cam->mutex); + if (rval == -EIO) + schedule_work(&cam->sensor_reset_work); + else + rval = 0; + +out: + /* + * This is a hack. We don't want to show -EIO to the user + * space. Requeue the buffer and try again if we're not doing + * this in non-blocking mode. + */ + if (rval == -EIO) { + videobuf_qbuf(&ofh->vbq, b); + if (!(file->f_flags & O_NONBLOCK)) + goto videobuf_dqbuf_again; + /* + * We don't have a videobuf_buffer now --- maybe next + * time... + */ + rval = -EAGAIN; + } + + return rval; +} + +static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + rval = omap24xxcam_sensor_if_enable(cam); + if (rval) { + dev_dbg(cam->dev, "vidioc_int_g_ifparm failed\n"); + goto out; + } + + rval = videobuf_streamon(&ofh->vbq); + if (!rval) { + cam->streaming = file; + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } + +out: + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct videobuf_queue *q = &ofh->vbq; + int rval; + + atomic_inc(&cam->reset_disable); + + flush_scheduled_work(); + + rval = videobuf_streamoff(q); + if (!rval) { + mutex_lock(&cam->mutex); + cam->streaming = NULL; + mutex_unlock(&cam->mutex); + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } + + atomic_dec(&cam->reset_disable); + + return rval; +} + +static int vidioc_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + if (inp->index > 0) + return -EINVAL; + + strlcpy(inp->name, "camera", sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int vidioc_s_input(struct file *file, void *fh, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *fh, + struct v4l2_queryctrl *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + rval = vidioc_int_queryctrl(cam->sdev, a); + + return rval; +} + +static int vidioc_g_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_ctrl(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_ctrl(struct file *file, void *fh, + struct v4l2_control *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + mutex_lock(&cam->mutex); + rval = vidioc_int_s_ctrl(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) { + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + int rval; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + mutex_lock(&cam->mutex); + rval = vidioc_int_g_parm(cam->sdev, a); + mutex_unlock(&cam->mutex); + + return rval; +} + +static int vidioc_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct omap24xxcam_fh *ofh = fh; + struct omap24xxcam_device *cam = ofh->cam; + struct v4l2_streamparm old_streamparm; + int rval; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + rval = -EBUSY; + goto out; + } + + old_streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + rval = vidioc_int_g_parm(cam->sdev, &old_streamparm); + if (rval) + goto out; + + rval = vidioc_int_s_parm(cam->sdev, a); + if (rval) + goto out; + + rval = omap24xxcam_sensor_if_enable(cam); + /* + * Revert to old streaming parameters if enabling sensor + * interface with the new ones failed. + */ + if (rval) + vidioc_int_s_parm(cam->sdev, &old_streamparm); + +out: + mutex_unlock(&cam->mutex); + + return rval; +} + +/* + * + * File operations. + * + */ + +static unsigned int omap24xxcam_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + struct videobuf_buffer *vb; + + mutex_lock(&cam->mutex); + if (cam->streaming != file) { + mutex_unlock(&cam->mutex); + return POLLERR; + } + mutex_unlock(&cam->mutex); + + mutex_lock(&fh->vbq.vb_lock); + if (list_empty(&fh->vbq.stream)) { + mutex_unlock(&fh->vbq.vb_lock); + return POLLERR; + } + vb = list_entry(fh->vbq.stream.next, struct videobuf_buffer, stream); + mutex_unlock(&fh->vbq.vb_lock); + + poll_wait(file, &vb->done, wait); + + if (vb->state == VIDEOBUF_DONE || vb->state == VIDEOBUF_ERROR) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int omap24xxcam_mmap_buffers(struct file *file, + struct vm_area_struct *vma) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + struct videobuf_queue *vbq = &fh->vbq; + unsigned int first, last, size, i, j; + int err = 0; + + mutex_lock(&cam->mutex); + if (cam->streaming) { + mutex_unlock(&cam->mutex); + return -EBUSY; + } + mutex_unlock(&cam->mutex); + mutex_lock(&vbq->vb_lock); + + /* look for first buffer to map */ + for (first = 0; first < VIDEO_MAX_FRAME; first++) { + if (NULL == vbq->bufs[first]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[first]->memory) + continue; + if (vbq->bufs[first]->boff == (vma->vm_pgoff << PAGE_SHIFT)) + break; + } + + /* look for last buffer to map */ + for (size = 0, last = first; last < VIDEO_MAX_FRAME; last++) { + if (NULL == vbq->bufs[last]) + continue; + if (V4L2_MEMORY_MMAP != vbq->bufs[last]->memory) + continue; + size += vbq->bufs[last]->bsize; + if (size == (vma->vm_end - vma->vm_start)) + break; + } + + size = 0; + for (i = first; i <= last; i++) { + struct videobuf_dmabuf *dma = videobuf_to_dma(vbq->bufs[i]); + + for (j = 0; j < dma->sglen; j++) { + err = remap_pfn_range( + vma, vma->vm_start + size, + page_to_pfn(sg_page(&dma->sglist[j])), + sg_dma_len(&dma->sglist[j]), vma->vm_page_prot); + if (err) + goto out; + size += sg_dma_len(&dma->sglist[j]); + } + } + +out: + mutex_unlock(&vbq->vb_lock); + + return err; +} + +static int omap24xxcam_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct omap24xxcam_fh *fh = file->private_data; + int rval; + + /* let the video-buf mapper check arguments and set-up structures */ + rval = videobuf_mmap_mapper(&fh->vbq, vma); + if (rval) + return rval; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* do mapping to our allocated buffers */ + rval = omap24xxcam_mmap_buffers(file, vma); + /* + * In case of error, free vma->vm_private_data allocated by + * videobuf_mmap_mapper. + */ + if (rval) + kfree(vma->vm_private_data); + + return rval; +} + +static int omap24xxcam_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct omap24xxcam_device *cam = omap24xxcam.priv; + struct omap24xxcam_fh *fh; + struct v4l2_format format; + + if (!cam || !cam->vfd || (cam->vfd->minor != minor)) + return -ENODEV; + + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (fh == NULL) + return -ENOMEM; + + mutex_lock(&cam->mutex); + if (cam->sdev == NULL || !try_module_get(cam->sdev->module)) { + mutex_unlock(&cam->mutex); + goto out_try_module_get; + } + + if (atomic_inc_return(&cam->users) == 1) { + omap24xxcam_hwinit(cam); + if (omap24xxcam_sensor_enable(cam)) { + mutex_unlock(&cam->mutex); + goto out_omap24xxcam_sensor_enable; + } + } + mutex_unlock(&cam->mutex); + + fh->cam = cam; + mutex_lock(&cam->mutex); + vidioc_int_g_fmt_cap(cam->sdev, &format); + mutex_unlock(&cam->mutex); + /* FIXME: how about fh->pix when there are more users? */ + fh->pix = format.fmt.pix; + + file->private_data = fh; + + spin_lock_init(&fh->vbq_lock); + + videobuf_queue_sg_init(&fh->vbq, &omap24xxcam_vbq_ops, NULL, + &fh->vbq_lock, V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_NONE, + sizeof(struct videobuf_buffer), fh); + + return 0; + +out_omap24xxcam_sensor_enable: + omap24xxcam_poweron_reset(cam); + module_put(cam->sdev->module); + +out_try_module_get: + kfree(fh); + + return -ENODEV; +} + +static int omap24xxcam_release(struct inode *inode, struct file *file) +{ + struct omap24xxcam_fh *fh = file->private_data; + struct omap24xxcam_device *cam = fh->cam; + + atomic_inc(&cam->reset_disable); + + flush_scheduled_work(); + + /* stop streaming capture */ + videobuf_streamoff(&fh->vbq); + + mutex_lock(&cam->mutex); + if (cam->streaming == file) { + cam->streaming = NULL; + mutex_unlock(&cam->mutex); + sysfs_notify(&cam->dev->kobj, NULL, "streaming"); + } else { + mutex_unlock(&cam->mutex); + } + + atomic_dec(&cam->reset_disable); + + omap24xxcam_vbq_free_mmap_buffers(&fh->vbq); + + /* + * Make sure the reset work we might have scheduled is not + * pending! It may be run *only* if we have users. (And it may + * not be scheduled anymore since streaming is already + * disabled.) + */ + flush_scheduled_work(); + + mutex_lock(&cam->mutex); + if (atomic_dec_return(&cam->users) == 0) { + omap24xxcam_sensor_disable(cam); + omap24xxcam_poweron_reset(cam); + } + mutex_unlock(&cam->mutex); + + file->private_data = NULL; + + module_put(cam->sdev->module); + kfree(fh); + + return 0; +} + +static struct file_operations omap24xxcam_fops = { + .llseek = no_llseek, + .ioctl = video_ioctl2, + .poll = omap24xxcam_poll, + .mmap = omap24xxcam_mmap, + .open = omap24xxcam_open, + .release = omap24xxcam_release, +}; + +/* + * + * Power management. + * + */ + +#ifdef CONFIG_PM +static int omap24xxcam_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (atomic_read(&cam->users) == 0) + return 0; + + if (!atomic_read(&cam->reset_disable)) + omap24xxcam_capture_stop(cam); + + omap24xxcam_sensor_disable(cam); + omap24xxcam_poweron_reset(cam); + + return 0; +} + +static int omap24xxcam_resume(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (atomic_read(&cam->users) == 0) + return 0; + + omap24xxcam_hwinit(cam); + omap24xxcam_sensor_enable(cam); + + if (!atomic_read(&cam->reset_disable)) + omap24xxcam_capture_cont(cam); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct v4l2_ioctl_ops omap24xxcam_ioctl_fops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_s_parm = vidioc_s_parm, +}; + +/* + * + * Camera device (i.e. /dev/video). + * + */ + +static int omap24xxcam_device_register(struct v4l2_int_device *s) +{ + struct omap24xxcam_device *cam = s->u.slave->master->priv; + struct video_device *vfd; + int rval; + + /* We already have a slave. */ + if (cam->sdev) + return -EBUSY; + + cam->sdev = s; + + if (device_create_file(cam->dev, &dev_attr_streaming) != 0) { + dev_err(cam->dev, "could not register sysfs entry\n"); + rval = -EBUSY; + goto err; + } + + /* initialize the video_device struct */ + vfd = cam->vfd = video_device_alloc(); + if (!vfd) { + dev_err(cam->dev, "could not allocate video device struct\n"); + rval = -ENOMEM; + goto err; + } + vfd->release = video_device_release; + + vfd->parent = cam->dev; + + strlcpy(vfd->name, CAM_NAME, sizeof(vfd->name)); + vfd->vfl_type = VID_TYPE_CAPTURE | VID_TYPE_CHROMAKEY; + vfd->fops = &omap24xxcam_fops; + vfd->minor = -1; + vfd->ioctl_ops = &omap24xxcam_ioctl_fops; + + omap24xxcam_hwinit(cam); + + rval = omap24xxcam_sensor_init(cam); + if (rval) + goto err; + + if (video_register_device(vfd, VFL_TYPE_GRABBER, video_nr) < 0) { + dev_err(cam->dev, "could not register V4L device\n"); + vfd->minor = -1; + rval = -EBUSY; + goto err; + } + + omap24xxcam_poweron_reset(cam); + + dev_info(cam->dev, "registered device video%d\n", vfd->minor); + + return 0; + +err: + omap24xxcam_device_unregister(s); + + return rval; +} + +static void omap24xxcam_device_unregister(struct v4l2_int_device *s) +{ + struct omap24xxcam_device *cam = s->u.slave->master->priv; + + omap24xxcam_sensor_exit(cam); + + if (cam->vfd) { + if (cam->vfd->minor == -1) { + /* + * The device was never registered, so release the + * video_device struct directly. + */ + video_device_release(cam->vfd); + } else { + /* + * The unregister function will release the + * video_device struct as well as + * unregistering it. + */ + video_unregister_device(cam->vfd); + } + cam->vfd = NULL; + } + + device_remove_file(cam->dev, &dev_attr_streaming); + + cam->sdev = NULL; +} + +static struct v4l2_int_master omap24xxcam_master = { + .attach = omap24xxcam_device_register, + .detach = omap24xxcam_device_unregister, +}; + +static struct v4l2_int_device omap24xxcam = { + .module = THIS_MODULE, + .name = CAM_NAME, + .type = v4l2_int_type_master, + .u = { + .master = &omap24xxcam_master + }, +}; + +/* + * + * Driver initialisation and deinitialisation. + * + */ + +static int __init omap24xxcam_probe(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam; + struct resource *mem; + int irq; + + cam = kzalloc(sizeof(*cam), GFP_KERNEL); + if (!cam) { + dev_err(&pdev->dev, "could not allocate memory\n"); + goto err; + } + + platform_set_drvdata(pdev, cam); + + cam->dev = &pdev->dev; + + /* + * Impose a lower limit on the amount of memory allocated for + * capture. We require at least enough memory to double-buffer + * QVGA (300KB). + */ + if (capture_mem < 320 * 240 * 2 * 2) + capture_mem = 320 * 240 * 2 * 2; + cam->capture_mem = capture_mem; + + /* request the mem region for the camera registers */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(cam->dev, "no mem resource?\n"); + goto err; + } + if (!request_mem_region(mem->start, (mem->end - mem->start) + 1, + pdev->name)) { + dev_err(cam->dev, + "cannot reserve camera register I/O region\n"); + goto err; + } + cam->mmio_base_phys = mem->start; + cam->mmio_size = (mem->end - mem->start) + 1; + + /* map the region */ + cam->mmio_base = (unsigned long) + ioremap_nocache(cam->mmio_base_phys, cam->mmio_size); + if (!cam->mmio_base) { + dev_err(cam->dev, "cannot map camera register I/O region\n"); + goto err; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_err(cam->dev, "no irq for camera?\n"); + goto err; + } + + /* install the interrupt service routine */ + if (request_irq(irq, omap24xxcam_isr, 0, CAM_NAME, cam)) { + dev_err(cam->dev, + "could not install interrupt service routine\n"); + goto err; + } + cam->irq = irq; + + if (omap24xxcam_clock_get(cam)) + goto err; + + INIT_WORK(&cam->sensor_reset_work, omap24xxcam_sensor_reset_work); + + mutex_init(&cam->mutex); + spin_lock_init(&cam->core_enable_disable_lock); + + omap24xxcam_sgdma_init(&cam->sgdma, + cam->mmio_base + CAMDMA_REG_OFFSET, + omap24xxcam_stalled_dma_reset, + (unsigned long)cam); + + omap24xxcam.priv = cam; + + if (v4l2_int_device_register(&omap24xxcam)) + goto err; + + return 0; + +err: + omap24xxcam_remove(pdev); + return -ENODEV; +} + +static int omap24xxcam_remove(struct platform_device *pdev) +{ + struct omap24xxcam_device *cam = platform_get_drvdata(pdev); + + if (!cam) + return 0; + + if (omap24xxcam.priv != NULL) + v4l2_int_device_unregister(&omap24xxcam); + omap24xxcam.priv = NULL; + + omap24xxcam_clock_put(cam); + + if (cam->irq) { + free_irq(cam->irq, cam); + cam->irq = 0; + } + + if (cam->mmio_base) { + iounmap((void *)cam->mmio_base); + cam->mmio_base = 0; + } + + if (cam->mmio_base_phys) { + release_mem_region(cam->mmio_base_phys, cam->mmio_size); + cam->mmio_base_phys = 0; + } + + kfree(cam); + + return 0; +} + +static struct platform_driver omap24xxcam_driver = { + .probe = omap24xxcam_probe, + .remove = omap24xxcam_remove, +#ifdef CONFIG_PM + .suspend = omap24xxcam_suspend, + .resume = omap24xxcam_resume, +#endif + .driver = { + .name = CAM_NAME, + .owner = THIS_MODULE, + }, +}; + +/* + * + * Module initialisation and deinitialisation + * + */ + +static int __init omap24xxcam_init(void) +{ + return platform_driver_register(&omap24xxcam_driver); +} + +static void __exit omap24xxcam_cleanup(void) +{ + platform_driver_unregister(&omap24xxcam_driver); +} + +MODULE_AUTHOR("Sakari Ailus "); +MODULE_DESCRIPTION("OMAP24xx Video for Linux camera driver"); +MODULE_LICENSE("GPL"); +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr, + "Minor number for video device (-1 ==> auto assign)"); +module_param(capture_mem, int, 0); +MODULE_PARM_DESC(capture_mem, "Maximum amount of memory for capture " + "buffers (default 4800kiB)"); + +module_init(omap24xxcam_init); +module_exit(omap24xxcam_cleanup); diff --git a/drivers/media/video/omap24xxcam.h b/drivers/media/video/omap24xxcam.h new file mode 100644 index 00000000000..2ce67f5a48d --- /dev/null +++ b/drivers/media/video/omap24xxcam.h @@ -0,0 +1,593 @@ +/* + * drivers/media/video/omap24xxcam.h + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2004 Texas Instruments. + * Copyright (C) 2007 Nokia Corporation. + * + * Contact: Sakari Ailus + * + * Based on code from Andy Lowe . + * + * This program 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef OMAP24XXCAM_H +#define OMAP24XXCAM_H + +#include +#include + +/* + * + * General driver related definitions. + * + */ + +#define CAM_NAME "omap24xxcam" + +#define CAM_MCLK 96000000 + +/* number of bytes transferred per DMA request */ +#define DMA_THRESHOLD 32 + +/* + * NUM_CAMDMA_CHANNELS is the number of logical channels provided by + * the camera DMA controller. + */ +#define NUM_CAMDMA_CHANNELS 4 + +/* + * NUM_SG_DMA is the number of scatter-gather DMA transfers that can + * be queued. (We don't have any overlay sglists now.) + */ +#define NUM_SG_DMA (VIDEO_MAX_FRAME) + +/* + * + * Register definitions. + * + */ + +/* subsystem register block offsets */ +#define CC_REG_OFFSET 0x00000400 +#define CAMDMA_REG_OFFSET 0x00000800 +#define CAMMMU_REG_OFFSET 0x00000C00 + +/* define camera subsystem register offsets */ +#define CAM_REVISION 0x000 +#define CAM_SYSCONFIG 0x010 +#define CAM_SYSSTATUS 0x014 +#define CAM_IRQSTATUS 0x018 +#define CAM_GPO 0x040 +#define CAM_GPI 0x050 + +/* define camera core register offsets */ +#define CC_REVISION 0x000 +#define CC_SYSCONFIG 0x010 +#define CC_SYSSTATUS 0x014 +#define CC_IRQSTATUS 0x018 +#define CC_IRQENABLE 0x01C +#define CC_CTRL 0x040 +#define CC_CTRL_DMA 0x044 +#define CC_CTRL_XCLK 0x048 +#define CC_FIFODATA 0x04C +#define CC_TEST 0x050 +#define CC_GENPAR 0x054 +#define CC_CCPFSCR 0x058 +#define CC_CCPFECR 0x05C +#define CC_CCPLSCR 0x060 +#define CC_CCPLECR 0x064 +#define CC_CCPDFR 0x068 + +/* define camera dma register offsets */ +#define CAMDMA_REVISION 0x000 +#define CAMDMA_IRQSTATUS_L0 0x008 +#define CAMDMA_IRQSTATUS_L1 0x00C +#define CAMDMA_IRQSTATUS_L2 0x010 +#define CAMDMA_IRQSTATUS_L3 0x014 +#define CAMDMA_IRQENABLE_L0 0x018 +#define CAMDMA_IRQENABLE_L1 0x01C +#define CAMDMA_IRQENABLE_L2 0x020 +#define CAMDMA_IRQENABLE_L3 0x024 +#define CAMDMA_SYSSTATUS 0x028 +#define CAMDMA_OCP_SYSCONFIG 0x02C +#define CAMDMA_CAPS_0 0x064 +#define CAMDMA_CAPS_2 0x06C +#define CAMDMA_CAPS_3 0x070 +#define CAMDMA_CAPS_4 0x074 +#define CAMDMA_GCR 0x078 +#define CAMDMA_CCR(n) (0x080 + (n)*0x60) +#define CAMDMA_CLNK_CTRL(n) (0x084 + (n)*0x60) +#define CAMDMA_CICR(n) (0x088 + (n)*0x60) +#define CAMDMA_CSR(n) (0x08C + (n)*0x60) +#define CAMDMA_CSDP(n) (0x090 + (n)*0x60) +#define CAMDMA_CEN(n) (0x094 + (n)*0x60) +#define CAMDMA_CFN(n) (0x098 + (n)*0x60) +#define CAMDMA_CSSA(n) (0x09C + (n)*0x60) +#define CAMDMA_CDSA(n) (0x0A0 + (n)*0x60) +#define CAMDMA_CSEI(n) (0x0A4 + (n)*0x60) +#define CAMDMA_CSFI(n) (0x0A8 + (n)*0x60) +#define CAMDMA_CDEI(n) (0x0AC + (n)*0x60) +#define CAMDMA_CDFI(n) (0x0B0 + (n)*0x60) +#define CAMDMA_CSAC(n) (0x0B4 + (n)*0x60) +#define CAMDMA_CDAC(n) (0x0B8 + (n)*0x60) +#define CAMDMA_CCEN(n) (0x0BC + (n)*0x60) +#define CAMDMA_CCFN(n) (0x0C0 + (n)*0x60) +#define CAMDMA_COLOR(n) (0x0C4 + (n)*0x60) + +/* define camera mmu register offsets */ +#define CAMMMU_REVISION 0x000 +#define CAMMMU_SYSCONFIG 0x010 +#define CAMMMU_SYSSTATUS 0x014 +#define CAMMMU_IRQSTATUS 0x018 +#define CAMMMU_IRQENABLE 0x01C +#define CAMMMU_WALKING_ST 0x040 +#define CAMMMU_CNTL 0x044 +#define CAMMMU_FAULT_AD 0x048 +#define CAMMMU_TTB 0x04C +#define CAMMMU_LOCK 0x050 +#define CAMMMU_LD_TLB 0x054 +#define CAMMMU_CAM 0x058 +#define CAMMMU_RAM 0x05C +#define CAMMMU_GFLUSH 0x060 +#define CAMMMU_FLUSH_ENTRY 0x064 +#define CAMMMU_READ_CAM 0x068 +#define CAMMMU_READ_RAM 0x06C +#define CAMMMU_EMU_FAULT_AD 0x070 + +/* Define bit fields within selected registers */ +#define CAM_REVISION_MAJOR (15 << 4) +#define CAM_REVISION_MAJOR_SHIFT 4 +#define CAM_REVISION_MINOR (15 << 0) +#define CAM_REVISION_MINOR_SHIFT 0 + +#define CAM_SYSCONFIG_SOFTRESET (1 << 1) +#define CAM_SYSCONFIG_AUTOIDLE (1 << 0) + +#define CAM_SYSSTATUS_RESETDONE (1 << 0) + +#define CAM_IRQSTATUS_CC_IRQ (1 << 4) +#define CAM_IRQSTATUS_MMU_IRQ (1 << 3) +#define CAM_IRQSTATUS_DMA_IRQ2 (1 << 2) +#define CAM_IRQSTATUS_DMA_IRQ1 (1 << 1) +#define CAM_IRQSTATUS_DMA_IRQ0 (1 << 0) + +#define CAM_GPO_CAM_S_P_EN (1 << 1) +#define CAM_GPO_CAM_CCP_MODE (1 << 0) + +#define CAM_GPI_CC_DMA_REQ1 (1 << 24) +#define CAP_GPI_CC_DMA_REQ0 (1 << 23) +#define CAP_GPI_CAM_MSTANDBY (1 << 21) +#define CAP_GPI_CAM_WAIT (1 << 20) +#define CAP_GPI_CAM_S_DATA (1 << 17) +#define CAP_GPI_CAM_S_CLK (1 << 16) +#define CAP_GPI_CAM_P_DATA (0xFFF << 3) +#define CAP_GPI_CAM_P_DATA_SHIFT 3 +#define CAP_GPI_CAM_P_VS (1 << 2) +#define CAP_GPI_CAM_P_HS (1 << 1) +#define CAP_GPI_CAM_P_CLK (1 << 0) + +#define CC_REVISION_MAJOR (15 << 4) +#define CC_REVISION_MAJOR_SHIFT 4 +#define CC_REVISION_MINOR (15 << 0) +#define CC_REVISION_MINOR_SHIFT 0 + +#define CC_SYSCONFIG_SIDLEMODE (3 << 3) +#define CC_SYSCONFIG_SIDLEMODE_FIDLE (0 << 3) +#define CC_SYSCONFIG_SIDLEMODE_NIDLE (1 << 3) +#define CC_SYSCONFIG_SOFTRESET (1 << 1) +#define CC_SYSCONFIG_AUTOIDLE (1 << 0) + +#define CC_SYSSTATUS_RESETDONE (1 << 0) + +#define CC_IRQSTATUS_FS_IRQ (1 << 19) +#define CC_IRQSTATUS_LE_IRQ (1 << 18) +#define CC_IRQSTATUS_LS_IRQ (1 << 17) +#define CC_IRQSTATUS_FE_IRQ (1 << 16) +#define CC_IRQSTATUS_FW_ERR_IRQ (1 << 10) +#define CC_IRQSTATUS_FSC_ERR_IRQ (1 << 9) +#define CC_IRQSTATUS_SSC_ERR_IRQ (1 << 8) +#define CC_IRQSTATUS_FIFO_NOEMPTY_IRQ (1 << 4) +#define CC_IRQSTATUS_FIFO_FULL_IRQ (1 << 3) +#define CC_IRQSTATUS_FIFO_THR_IRQ (1 << 2) +#define CC_IRQSTATUS_FIFO_OF_IRQ (1 << 1) +#define CC_IRQSTATUS_FIFO_UF_IRQ (1 << 0) + +#define CC_IRQENABLE_FS_IRQ (1 << 19) +#define CC_IRQENABLE_LE_IRQ (1 << 18) +#define CC_IRQENABLE_LS_IRQ (1 << 17) +#define CC_IRQENABLE_FE_IRQ (1 << 16) +#define CC_IRQENABLE_FW_ERR_IRQ (1 << 10) +#define CC_IRQENABLE_FSC_ERR_IRQ (1 << 9) +#define CC_IRQENABLE_SSC_ERR_IRQ (1 << 8) +#define CC_IRQENABLE_FIFO_NOEMPTY_IRQ (1 << 4) +#define CC_IRQENABLE_FIFO_FULL_IRQ (1 << 3) +#define CC_IRQENABLE_FIFO_THR_IRQ (1 << 2) +#define CC_IRQENABLE_FIFO_OF_IRQ (1 << 1) +#define CC_IRQENABLE_FIFO_UF_IRQ (1 << 0) + +#define CC_CTRL_CC_ONE_SHOT (1 << 20) +#define CC_CTRL_CC_IF_SYNCHRO (1 << 19) +#define CC_CTRL_CC_RST (1 << 18) +#define CC_CTRL_CC_FRAME_TRIG (1 << 17) +#define CC_CTRL_CC_EN (1 << 16) +#define CC_CTRL_NOBT_SYNCHRO (1 << 13) +#define CC_CTRL_BT_CORRECT (1 << 12) +#define CC_CTRL_PAR_ORDERCAM (1 << 11) +#define CC_CTRL_PAR_CLK_POL (1 << 10) +#define CC_CTRL_NOBT_HS_POL (1 << 9) +#define CC_CTRL_NOBT_VS_POL (1 << 8) +#define CC_CTRL_PAR_MODE (7 << 1) +#define CC_CTRL_PAR_MODE_SHIFT 1 +#define CC_CTRL_PAR_MODE_NOBT8 (0 << 1) +#define CC_CTRL_PAR_MODE_NOBT10 (1 << 1) +#define CC_CTRL_PAR_MODE_NOBT12 (2 << 1) +#define CC_CTRL_PAR_MODE_BT8 (4 << 1) +#define CC_CTRL_PAR_MODE_BT10 (5 << 1) +#define CC_CTRL_PAR_MODE_FIFOTEST (7 << 1) +#define CC_CTRL_CCP_MODE (1 << 0) + +#define CC_CTRL_DMA_EN (1 << 8) +#define CC_CTRL_DMA_FIFO_THRESHOLD (0x7F << 0) +#define CC_CTRL_DMA_FIFO_THRESHOLD_SHIFT 0 + +#define CC_CTRL_XCLK_DIV (0x1F << 0) +#define CC_CTRL_XCLK_DIV_SHIFT 0 +#define CC_CTRL_XCLK_DIV_STABLE_LOW (0 << 0) +#define CC_CTRL_XCLK_DIV_STABLE_HIGH (1 << 0) +#define CC_CTRL_XCLK_DIV_BYPASS (31 << 0) + +#define CC_TEST_FIFO_RD_POINTER (0xFF << 24) +#define CC_TEST_FIFO_RD_POINTER_SHIFT 24 +#define CC_TEST_FIFO_WR_POINTER (0xFF << 16) +#define CC_TEST_FIFO_WR_POINTER_SHIFT 16 +#define CC_TEST_FIFO_LEVEL (0xFF << 8) +#define CC_TEST_FIFO_LEVEL_SHIFT 8 +#define CC_TEST_FIFO_LEVEL_PEAK (0xFF << 0) +#define CC_TEST_FIFO_LEVEL_PEAK_SHIFT 0 + +#define CC_GENPAR_FIFO_DEPTH (7 << 0) +#define CC_GENPAR_FIFO_DEPTH_SHIFT 0 + +#define CC_CCPDFR_ALPHA (0xFF << 8) +#define CC_CCPDFR_ALPHA_SHIFT 8 +#define CC_CCPDFR_DATAFORMAT (15 << 0) +#define CC_CCPDFR_DATAFORMAT_SHIFT 0 +#define CC_CCPDFR_DATAFORMAT_YUV422BE (0 << 0) +#define CC_CCPDFR_DATAFORMAT_YUV422 (1 << 0) +#define CC_CCPDFR_DATAFORMAT_YUV420 (2 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB444 (4 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB565 (5 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB888NDE (6 << 0) +#define CC_CCPDFR_DATAFORMAT_RGB888 (7 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW8NDE (8 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW8 (9 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW10NDE (10 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW10 (11 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW12NDE (12 << 0) +#define CC_CCPDFR_DATAFORMAT_RAW12 (13 << 0) +#define CC_CCPDFR_DATAFORMAT_JPEG8 (15 << 0) + +#define CAMDMA_REVISION_MAJOR (15 << 4) +#define CAMDMA_REVISION_MAJOR_SHIFT 4 +#define CAMDMA_REVISION_MINOR (15 << 0) +#define CAMDMA_REVISION_MINOR_SHIFT 0 + +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE (3 << 12) +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_FSTANDBY (0 << 12) +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_NSTANDBY (1 << 12) +#define CAMDMA_OCP_SYSCONFIG_MIDLEMODE_SSTANDBY (2 << 12) +#define CAMDMA_OCP_SYSCONFIG_FUNC_CLOCK (1 << 9) +#define CAMDMA_OCP_SYSCONFIG_OCP_CLOCK (1 << 8) +#define CAMDMA_OCP_SYSCONFIG_EMUFREE (1 << 5) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE (3 << 3) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_FIDLE (0 << 3) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_NIDLE (1 << 3) +#define CAMDMA_OCP_SYSCONFIG_SIDLEMODE_SIDLE (2 << 3) +#define CAMDMA_OCP_SYSCONFIG_SOFTRESET (1 << 1) +#define CAMDMA_OCP_SYSCONFIG_AUTOIDLE (1 << 0) + +#define CAMDMA_SYSSTATUS_RESETDONE (1 << 0) + +#define CAMDMA_GCR_ARBITRATION_RATE (0xFF << 16) +#define CAMDMA_GCR_ARBITRATION_RATE_SHIFT 16 +#define CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH (0xFF << 0) +#define CAMDMA_GCR_MAX_CHANNEL_FIFO_DEPTH_SHIFT 0 + +#define CAMDMA_CCR_SEL_SRC_DST_SYNC (1 << 24) +#define CAMDMA_CCR_PREFETCH (1 << 23) +#define CAMDMA_CCR_SUPERVISOR (1 << 22) +#define CAMDMA_CCR_SECURE (1 << 21) +#define CAMDMA_CCR_BS (1 << 18) +#define CAMDMA_CCR_TRANSPARENT_COPY_ENABLE (1 << 17) +#define CAMDMA_CCR_CONSTANT_FILL_ENABLE (1 << 16) +#define CAMDMA_CCR_DST_AMODE (3 << 14) +#define CAMDMA_CCR_DST_AMODE_CONST_ADDR (0 << 14) +#define CAMDMA_CCR_DST_AMODE_POST_INC (1 << 14) +#define CAMDMA_CCR_DST_AMODE_SGL_IDX (2 << 14) +#define CAMDMA_CCR_DST_AMODE_DBL_IDX (3 << 14) +#define CAMDMA_CCR_SRC_AMODE (3 << 12) +#define CAMDMA_CCR_SRC_AMODE_CONST_ADDR (0 << 12) +#define CAMDMA_CCR_SRC_AMODE_POST_INC (1 << 12) +#define CAMDMA_CCR_SRC_AMODE_SGL_IDX (2 << 12) +#define CAMDMA_CCR_SRC_AMODE_DBL_IDX (3 << 12) +#define CAMDMA_CCR_WR_ACTIVE (1 << 10) +#define CAMDMA_CCR_RD_ACTIVE (1 << 9) +#define CAMDMA_CCR_SUSPEND_SENSITIVE (1 << 8) +#define CAMDMA_CCR_ENABLE (1 << 7) +#define CAMDMA_CCR_PRIO (1 << 6) +#define CAMDMA_CCR_FS (1 << 5) +#define CAMDMA_CCR_SYNCHRO ((3 << 19) | (31 << 0)) +#define CAMDMA_CCR_SYNCHRO_CAMERA 0x01 + +#define CAMDMA_CLNK_CTRL_ENABLE_LNK (1 << 15) +#define CAMDMA_CLNK_CTRL_NEXTLCH_ID (0x1F << 0) +#define CAMDMA_CLNK_CTRL_NEXTLCH_ID_SHIFT 0 + +#define CAMDMA_CICR_MISALIGNED_ERR_IE (1 << 11) +#define CAMDMA_CICR_SUPERVISOR_ERR_IE (1 << 10) +#define CAMDMA_CICR_SECURE_ERR_IE (1 << 9) +#define CAMDMA_CICR_TRANS_ERR_IE (1 << 8) +#define CAMDMA_CICR_PACKET_IE (1 << 7) +#define CAMDMA_CICR_BLOCK_IE (1 << 5) +#define CAMDMA_CICR_LAST_IE (1 << 4) +#define CAMDMA_CICR_FRAME_IE (1 << 3) +#define CAMDMA_CICR_HALF_IE (1 << 2) +#define CAMDMA_CICR_DROP_IE (1 << 1) + +#define CAMDMA_CSR_MISALIGNED_ERR (1 << 11) +#define CAMDMA_CSR_SUPERVISOR_ERR (1 << 10) +#define CAMDMA_CSR_SECURE_ERR (1 << 9) +#define CAMDMA_CSR_TRANS_ERR (1 << 8) +#define CAMDMA_CSR_PACKET (1 << 7) +#define CAMDMA_CSR_SYNC (1 << 6) +#define CAMDMA_CSR_BLOCK (1 << 5) +#define CAMDMA_CSR_LAST (1 << 4) +#define CAMDMA_CSR_FRAME (1 << 3) +#define CAMDMA_CSR_HALF (1 << 2) +#define CAMDMA_CSR_DROP (1 << 1) + +#define CAMDMA_CSDP_SRC_ENDIANNESS (1 << 21) +#define CAMDMA_CSDP_SRC_ENDIANNESS_LOCK (1 << 20) +#define CAMDMA_CSDP_DST_ENDIANNESS (1 << 19) +#define CAMDMA_CSDP_DST_ENDIANNESS_LOCK (1 << 18) +#define CAMDMA_CSDP_WRITE_MODE (3 << 16) +#define CAMDMA_CSDP_WRITE_MODE_WRNP (0 << 16) +#define CAMDMA_CSDP_WRITE_MODE_POSTED (1 << 16) +#define CAMDMA_CSDP_WRITE_MODE_POSTED_LAST_WRNP (2 << 16) +#define CAMDMA_CSDP_DST_BURST_EN (3 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_1 (0 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_16 (1 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_32 (2 << 14) +#define CAMDMA_CSDP_DST_BURST_EN_64 (3 << 14) +#define CAMDMA_CSDP_DST_PACKED (1 << 13) +#define CAMDMA_CSDP_WR_ADD_TRSLT (15 << 9) +#define CAMDMA_CSDP_WR_ADD_TRSLT_ENABLE_MREQADD (3 << 9) +#define CAMDMA_CSDP_SRC_BURST_EN (3 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_1 (0 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_16 (1 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_32 (2 << 7) +#define CAMDMA_CSDP_SRC_BURST_EN_64 (3 << 7) +#define CAMDMA_CSDP_SRC_PACKED (1 << 6) +#define CAMDMA_CSDP_RD_ADD_TRSLT (15 << 2) +#define CAMDMA_CSDP_RD_ADD_TRSLT_ENABLE_MREQADD (3 << 2) +#define CAMDMA_CSDP_DATA_TYPE (3 << 0) +#define CAMDMA_CSDP_DATA_TYPE_8BITS (0 << 0) +#define CAMDMA_CSDP_DATA_TYPE_16BITS (1 << 0) +#define CAMDMA_CSDP_DATA_TYPE_32BITS (2 << 0) + +#define CAMMMU_SYSCONFIG_AUTOIDLE (1 << 0) + +/* + * + * Declarations. + * + */ + +/* forward declarations */ +struct omap24xxcam_sgdma; +struct omap24xxcam_dma; + +typedef void (*sgdma_callback_t)(struct omap24xxcam_sgdma *cam, + u32 status, void *arg); +typedef void (*dma_callback_t)(struct omap24xxcam_dma *cam, + u32 status, void *arg); + +struct channel_state { + dma_callback_t callback; + void *arg; +}; + +/* sgdma state for each of the possible videobuf_buffers + 2 overlays */ +struct sgdma_state { + const struct scatterlist *sglist; + int sglen; /* number of sglist entries */ + int next_sglist; /* index of next sglist entry to process */ + unsigned int bytes_read; /* number of bytes read */ + unsigned int len; /* total length of sglist (excluding + * bytes due to page alignment) */ + int queued_sglist; /* number of sglist entries queued for DMA */ + u32 csr; /* DMA return code */ + sgdma_callback_t callback; + void *arg; +}; + +/* physical DMA channel management */ +struct omap24xxcam_dma { + spinlock_t lock; /* Lock for the whole structure. */ + + unsigned long base; /* base address for dma controller */ + + /* While dma_stop!=0, an attempt to start a new DMA transfer will + * fail. + */ + atomic_t dma_stop; + int free_dmach; /* number of dma channels free */ + int next_dmach; /* index of next dma channel to use */ + struct channel_state ch_state[NUM_CAMDMA_CHANNELS]; +}; + +/* scatter-gather DMA (scatterlist stuff) management */ +struct omap24xxcam_sgdma { + struct omap24xxcam_dma dma; + + spinlock_t lock; /* Lock for the fields below. */ + int free_sgdma; /* number of free sg dma slots */ + int next_sgdma; /* index of next sg dma slot to use */ + struct sgdma_state sg_state[NUM_SG_DMA]; + + /* Reset timer data */ + struct timer_list reset_timer; +}; + +/* per-device data structure */ +struct omap24xxcam_device { + /*** mutex ***/ + /* + * mutex serialises access to this structure. Also camera + * opening and releasing is synchronised by this. + */ + struct mutex mutex; + + /*** general driver state information ***/ + atomic_t users; + /* + * Lock to serialise core enabling and disabling and access to + * sgdma_in_queue. + */ + spinlock_t core_enable_disable_lock; + /* + * Number or sgdma requests in scatter-gather queue, protected + * by the lock above. + */ + int sgdma_in_queue; + /* + * Sensor interface parameters: interface type, CC_CTRL + * register value and interface specific data. + */ + int if_type; + union { + struct parallel { + u32 xclk; + } bt656; + } if_u; + u32 cc_ctrl; + + /*** subsystem structures ***/ + struct omap24xxcam_sgdma sgdma; + + /*** hardware resources ***/ + unsigned int irq; + unsigned long mmio_base; + unsigned long mmio_base_phys; + unsigned long mmio_size; + + /*** interfaces and device ***/ + struct v4l2_int_device *sdev; + struct device *dev; + struct video_device *vfd; + + /*** camera and sensor reset related stuff ***/ + struct work_struct sensor_reset_work; + /* + * We're in the middle of a reset. Don't enable core if this + * is non-zero! This exists to help decisionmaking in a case + * where videobuf_qbuf is called while we are in the middle of + * a reset. + */ + atomic_t in_reset; + /* + * Non-zero if we don't want any resets for now. Used to + * prevent reset work to run when we're about to stop + * streaming. + */ + atomic_t reset_disable; + + /*** video device parameters ***/ + int capture_mem; + + /*** camera module clocks ***/ + struct clk *fck; + struct clk *ick; + + /*** capture data ***/ + /* file handle, if streaming is on */ + struct file *streaming; +}; + +/* Per-file handle data. */ +struct omap24xxcam_fh { + spinlock_t vbq_lock; /* spinlock for the videobuf queue */ + struct videobuf_queue vbq; + struct v4l2_pix_format pix; /* serialise pix by vbq->lock */ + atomic_t field_count; /* field counter for videobuf_buffer */ + /* accessing cam here doesn't need serialisation: it's constant */ + struct omap24xxcam_device *cam; +}; + +/* + * + * Register I/O functions. + * + */ + +static inline u32 omap24xxcam_reg_in(unsigned long base, u32 offset) +{ + return readl(base + offset); +} + +static inline u32 omap24xxcam_reg_out(unsigned long base, u32 offset, + u32 val) +{ + writel(val, base + offset); + return val; +} + +static inline u32 omap24xxcam_reg_merge(unsigned long base, u32 offset, + u32 val, u32 mask) +{ + u32 addr = base + offset; + u32 new_val = (readl(addr) & ~mask) | (val & mask); + + writel(new_val, addr); + return new_val; +} + +/* + * + * Function prototypes. + * + */ + +/* dma prototypes */ + +void omap24xxcam_dma_hwinit(struct omap24xxcam_dma *dma); +void omap24xxcam_dma_isr(struct omap24xxcam_dma *dma); + +/* sgdma prototypes */ + +void omap24xxcam_sgdma_process(struct omap24xxcam_sgdma *sgdma); +int omap24xxcam_sgdma_queue(struct omap24xxcam_sgdma *sgdma, + const struct scatterlist *sglist, int sglen, + int len, sgdma_callback_t callback, void *arg); +void omap24xxcam_sgdma_sync(struct omap24xxcam_sgdma *sgdma); +void omap24xxcam_sgdma_init(struct omap24xxcam_sgdma *sgdma, + unsigned long base, + void (*reset_callback)(unsigned long data), + unsigned long reset_callback_data); +void omap24xxcam_sgdma_exit(struct omap24xxcam_sgdma *sgdma); + +#endif -- cgit v1.2.3-70-g09d2 From 07b1747c8d0bb463311f9dd05d4c013765abe2eb Mon Sep 17 00:00:00 2001 From: Vaibhav Hiremath Date: Fri, 5 Dec 2008 10:19:36 -0300 Subject: V4L/DVB (9817): v4l: add new tvp514x I2C video decoder driver Signed-off-by: Brijesh Jadav Signed-off-by: Hardik Shah Signed-off-by: Manjunath Hadli Signed-off-by: R Sivaraj Signed-off-by: Vaibhav Hiremath Signed-off-by: Karicheri Muralidharan Signed-off-by: Hans Verkuil Reviewed-by: Hans Verkuil Reviewed-by: David Brownell Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Kconfig | 11 + drivers/media/video/Makefile | 1 + drivers/media/video/tvp514x.c | 1569 ++++++++++++++++++++++++++++++++++++ drivers/media/video/tvp514x_regs.h | 297 +++++++ include/media/tvp514x.h | 118 +++ 5 files changed, 1996 insertions(+) create mode 100644 drivers/media/video/tvp514x.c create mode 100644 drivers/media/video/tvp514x_regs.h create mode 100644 include/media/tvp514x.h (limited to 'drivers/media/video/Makefile') diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index c487d2a1285..eb9ff7e107d 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -361,6 +361,17 @@ config VIDEO_SAA7191 To compile this driver as a module, choose M here: the module will be called saa7191. +config VIDEO_TVP514X + tristate "Texas Instruments TVP514x video decoder" + depends on VIDEO_V4L2 && I2C + ---help--- + This is a Video4Linux2 sensor-level driver for the TI TVP5146/47 + decoder. It is currently working with the TI OMAP3 camera + controller. + + To compile this driver as a module, choose M here: the + module will be called tvp514x. + config VIDEO_TVP5150 tristate "Texas Instruments TVP5150 video decoder" depends on VIDEO_V4L2 && I2C diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index ac147b1aea3..492ab3dce71 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_VIDEO_CX88) += cx88/ obj-$(CONFIG_VIDEO_EM28XX) += em28xx/ obj-$(CONFIG_VIDEO_USBVISION) += usbvision/ obj-$(CONFIG_VIDEO_TVP5150) += tvp5150.o +obj-$(CONFIG_VIDEO_TVP514X) += tvp514x.o obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/ obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o obj-$(CONFIG_VIDEO_CS5345) += cs5345.o diff --git a/drivers/media/video/tvp514x.c b/drivers/media/video/tvp514x.c new file mode 100644 index 00000000000..ac9aa40d09f --- /dev/null +++ b/drivers/media/video/tvp514x.c @@ -0,0 +1,1569 @@ +/* + * drivers/media/video/tvp514x.c + * + * TI TVP5146/47 decoder driver + * + * Copyright (C) 2008 Texas Instruments Inc + * Author: Vaibhav Hiremath + * + * Contributors: + * Sivaraj R + * Brijesh R Jadav + * Hardik Shah + * Manjunath Hadli + * Karicheri Muralidharan + * + * 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 "tvp514x_regs.h" + +/* Module Name */ +#define TVP514X_MODULE_NAME "tvp514x" + +/* Private macros for TVP */ +#define I2C_RETRY_COUNT (5) +#define LOCK_RETRY_COUNT (5) +#define LOCK_RETRY_DELAY (200) + +/* Debug functions */ +static int debug; +module_param(debug, bool, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-1)"); + +#define dump_reg(client, reg, val) \ + do { \ + val = tvp514x_read_reg(client, reg); \ + v4l_info(client, "Reg(0x%.2X): 0x%.2X\n", reg, val); \ + } while (0) + +/** + * enum tvp514x_std - enum for supported standards + */ +enum tvp514x_std { + STD_NTSC_MJ = 0, + STD_PAL_BDGHIN, + STD_INVALID +}; + +/** + * enum tvp514x_state - enum for different decoder states + */ +enum tvp514x_state { + STATE_NOT_DETECTED, + STATE_DETECTED +}; + +/** + * struct tvp514x_std_info - Structure to store standard informations + * @width: Line width in pixels + * @height:Number of active lines + * @video_std: Value to write in REG_VIDEO_STD register + * @standard: v4l2 standard structure information + */ +struct tvp514x_std_info { + unsigned long width; + unsigned long height; + u8 video_std; + struct v4l2_standard standard; +}; + +/** + * struct tvp514x_decoded - TVP5146/47 decoder object + * @v4l2_int_device: Slave handle + * @pdata: Board specific + * @client: I2C client data + * @id: Entry from I2C table + * @ver: Chip version + * @state: TVP5146/47 decoder state - detected or not-detected + * @pix: Current pixel format + * @num_fmts: Number of formats + * @fmt_list: Format list + * @current_std: Current standard + * @num_stds: Number of standards + * @std_list: Standards list + * @route: input and output routing at chip level + */ +struct tvp514x_decoder { + struct v4l2_int_device *v4l2_int_device; + const struct tvp514x_platform_data *pdata; + struct i2c_client *client; + + struct i2c_device_id *id; + + int ver; + enum tvp514x_state state; + + struct v4l2_pix_format pix; + int num_fmts; + const struct v4l2_fmtdesc *fmt_list; + + enum tvp514x_std current_std; + int num_stds; + struct tvp514x_std_info *std_list; + + struct v4l2_routing route; +}; + +/* TVP514x default register values */ +static struct tvp514x_reg tvp514x_reg_list[] = { + {TOK_WRITE, REG_INPUT_SEL, 0x05}, /* Composite selected */ + {TOK_WRITE, REG_AFE_GAIN_CTRL, 0x0F}, + {TOK_WRITE, REG_VIDEO_STD, 0x00}, /* Auto mode */ + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, + {TOK_SKIP, REG_AUTOSWITCH_MASK, 0x3F}, + {TOK_WRITE, REG_COLOR_KILLER, 0x10}, + {TOK_WRITE, REG_LUMA_CONTROL1, 0x00}, + {TOK_WRITE, REG_LUMA_CONTROL2, 0x00}, + {TOK_WRITE, REG_LUMA_CONTROL3, 0x02}, + {TOK_WRITE, REG_BRIGHTNESS, 0x80}, + {TOK_WRITE, REG_CONTRAST, 0x80}, + {TOK_WRITE, REG_SATURATION, 0x80}, + {TOK_WRITE, REG_HUE, 0x00}, + {TOK_WRITE, REG_CHROMA_CONTROL1, 0x00}, + {TOK_WRITE, REG_CHROMA_CONTROL2, 0x0E}, + {TOK_SKIP, 0x0F, 0x00}, /* Reserved */ + {TOK_WRITE, REG_COMP_PR_SATURATION, 0x80}, + {TOK_WRITE, REG_COMP_Y_CONTRAST, 0x80}, + {TOK_WRITE, REG_COMP_PB_SATURATION, 0x80}, + {TOK_SKIP, 0x13, 0x00}, /* Reserved */ + {TOK_WRITE, REG_COMP_Y_BRIGHTNESS, 0x80}, + {TOK_SKIP, 0x15, 0x00}, /* Reserved */ + {TOK_SKIP, REG_AVID_START_PIXEL_LSB, 0x55}, /* NTSC timing */ + {TOK_SKIP, REG_AVID_START_PIXEL_MSB, 0x00}, + {TOK_SKIP, REG_AVID_STOP_PIXEL_LSB, 0x25}, + {TOK_SKIP, REG_AVID_STOP_PIXEL_MSB, 0x03}, + {TOK_SKIP, REG_HSYNC_START_PIXEL_LSB, 0x00}, /* NTSC timing */ + {TOK_SKIP, REG_HSYNC_START_PIXEL_MSB, 0x00}, + {TOK_SKIP, REG_HSYNC_STOP_PIXEL_LSB, 0x40}, + {TOK_SKIP, REG_HSYNC_STOP_PIXEL_MSB, 0x00}, + {TOK_SKIP, REG_VSYNC_START_LINE_LSB, 0x04}, /* NTSC timing */ + {TOK_SKIP, REG_VSYNC_START_LINE_MSB, 0x00}, + {TOK_SKIP, REG_VSYNC_STOP_LINE_LSB, 0x07}, + {TOK_SKIP, REG_VSYNC_STOP_LINE_MSB, 0x00}, + {TOK_SKIP, REG_VBLK_START_LINE_LSB, 0x01}, /* NTSC timing */ + {TOK_SKIP, REG_VBLK_START_LINE_MSB, 0x00}, + {TOK_SKIP, REG_VBLK_STOP_LINE_LSB, 0x15}, + {TOK_SKIP, REG_VBLK_STOP_LINE_MSB, 0x00}, + {TOK_SKIP, 0x26, 0x00}, /* Reserved */ + {TOK_SKIP, 0x27, 0x00}, /* Reserved */ + {TOK_SKIP, REG_FAST_SWTICH_CONTROL, 0xCC}, + {TOK_SKIP, 0x29, 0x00}, /* Reserved */ + {TOK_SKIP, REG_FAST_SWTICH_SCART_DELAY, 0x00}, + {TOK_SKIP, 0x2B, 0x00}, /* Reserved */ + {TOK_SKIP, REG_SCART_DELAY, 0x00}, + {TOK_SKIP, REG_CTI_DELAY, 0x00}, + {TOK_SKIP, REG_CTI_CONTROL, 0x00}, + {TOK_SKIP, 0x2F, 0x00}, /* Reserved */ + {TOK_SKIP, 0x30, 0x00}, /* Reserved */ + {TOK_SKIP, 0x31, 0x00}, /* Reserved */ + {TOK_WRITE, REG_SYNC_CONTROL, 0x00}, /* HS, VS active high */ + {TOK_WRITE, REG_OUTPUT_FORMATTER1, 0x00}, /* 10-bit BT.656 */ + {TOK_WRITE, REG_OUTPUT_FORMATTER2, 0x11}, /* Enable clk & data */ + {TOK_WRITE, REG_OUTPUT_FORMATTER3, 0xEE}, /* Enable AVID & FLD */ + {TOK_WRITE, REG_OUTPUT_FORMATTER4, 0xAF}, /* Enable VS & HS */ + {TOK_WRITE, REG_OUTPUT_FORMATTER5, 0xFF}, + {TOK_WRITE, REG_OUTPUT_FORMATTER6, 0xFF}, + {TOK_WRITE, REG_CLEAR_LOST_LOCK, 0x01}, /* Clear status */ + {TOK_TERM, 0, 0}, +}; + +/* List of image formats supported by TVP5146/47 decoder + * Currently we are using 8 bit mode only, but can be + * extended to 10/20 bit mode. + */ +static const struct v4l2_fmtdesc tvp514x_fmt_list[] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = 0, + .description = "8-bit UYVY 4:2:2 Format", + .pixelformat = V4L2_PIX_FMT_UYVY, + }, +}; + +/* + * Supported standards - + * + * Currently supports two standards only, need to add support for rest of the + * modes, like SECAM, etc... + */ +static struct tvp514x_std_info tvp514x_std_list[] = { + /* Standard: STD_NTSC_MJ */ + [STD_NTSC_MJ] = { + .width = NTSC_NUM_ACTIVE_PIXELS, + .height = NTSC_NUM_ACTIVE_LINES, + .video_std = VIDEO_STD_NTSC_MJ_BIT, + .standard = { + .index = 0, + .id = V4L2_STD_NTSC, + .name = "NTSC", + .frameperiod = {1001, 30000}, + .framelines = 525 + }, + /* Standard: STD_PAL_BDGHIN */ + }, + [STD_PAL_BDGHIN] = { + .width = PAL_NUM_ACTIVE_PIXELS, + .height = PAL_NUM_ACTIVE_LINES, + .video_std = VIDEO_STD_PAL_BDGHIN_BIT, + .standard = { + .index = 1, + .id = V4L2_STD_PAL, + .name = "PAL", + .frameperiod = {1, 25}, + .framelines = 625 + }, + }, + /* Standard: need to add for additional standard */ +}; +/* + * Control structure for Auto Gain + * This is temporary data, will get replaced once + * v4l2_ctrl_query_fill supports it. + */ +static const struct v4l2_queryctrl tvp514x_autogain_ctrl = { + .id = V4L2_CID_AUTOGAIN, + .name = "Gain, Automatic", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, +}; + +/* + * Read a value from a register in an TVP5146/47 decoder device. + * Returns value read if successful, or non-zero (-1) otherwise. + */ +static int tvp514x_read_reg(struct i2c_client *client, u8 reg) +{ + int err; + int retry = 0; +read_again: + + err = i2c_smbus_read_byte_data(client, reg); + if (err == -1) { + if (retry <= I2C_RETRY_COUNT) { + v4l_warn(client, "Read: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto read_again; + } + } + + return err; +} + +/* + * Write a value to a register in an TVP5146/47 decoder device. + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_write_reg(struct i2c_client *client, u8 reg, u8 val) +{ + int err; + int retry = 0; +write_again: + + err = i2c_smbus_write_byte_data(client, reg, val); + if (err) { + if (retry <= I2C_RETRY_COUNT) { + v4l_warn(client, "Write: retry ... %d\n", retry); + retry++; + msleep_interruptible(10); + goto write_again; + } + } + + return err; +} + +/* + * tvp514x_write_regs : Initializes a list of TVP5146/47 registers + * if token is TOK_TERM, then entire write operation terminates + * if token is TOK_DELAY, then a delay of 'val' msec is introduced + * if token is TOK_SKIP, then the register write is skipped + * if token is TOK_WRITE, then the register write is performed + * + * reglist - list of registers to be written + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_write_regs(struct i2c_client *client, + const struct tvp514x_reg reglist[]) +{ + int err; + const struct tvp514x_reg *next = reglist; + + for (; next->token != TOK_TERM; next++) { + if (next->token == TOK_DELAY) { + msleep(next->val); + continue; + } + + if (next->token == TOK_SKIP) + continue; + + err = tvp514x_write_reg(client, next->reg, (u8) next->val); + if (err) { + v4l_err(client, "Write failed. Err[%d]\n", err); + return err; + } + } + return 0; +} + +/* + * tvp514x_get_current_std: + * Returns the current standard detected by TVP5146/47 + */ +static enum tvp514x_std tvp514x_get_current_std(struct tvp514x_decoder + *decoder) +{ + u8 std, std_status; + + std = tvp514x_read_reg(decoder->client, REG_VIDEO_STD); + if ((std & VIDEO_STD_MASK) == VIDEO_STD_AUTO_SWITCH_BIT) { + /* use the standard status register */ + std_status = tvp514x_read_reg(decoder->client, + REG_VIDEO_STD_STATUS); + } else + std_status = std; /* use the standard register itself */ + + switch (std_status & VIDEO_STD_MASK) { + case VIDEO_STD_NTSC_MJ_BIT: + return STD_NTSC_MJ; + + case VIDEO_STD_PAL_BDGHIN_BIT: + return STD_PAL_BDGHIN; + + default: + return STD_INVALID; + } + + return STD_INVALID; +} + +/* + * TVP5146/47 register dump function + */ +static void tvp514x_reg_dump(struct tvp514x_decoder *decoder) +{ + u8 value; + + dump_reg(decoder->client, REG_INPUT_SEL, value); + dump_reg(decoder->client, REG_AFE_GAIN_CTRL, value); + dump_reg(decoder->client, REG_VIDEO_STD, value); + dump_reg(decoder->client, REG_OPERATION_MODE, value); + dump_reg(decoder->client, REG_COLOR_KILLER, value); + dump_reg(decoder->client, REG_LUMA_CONTROL1, value); + dump_reg(decoder->client, REG_LUMA_CONTROL2, value); + dump_reg(decoder->client, REG_LUMA_CONTROL3, value); + dump_reg(decoder->client, REG_BRIGHTNESS, value); + dump_reg(decoder->client, REG_CONTRAST, value); + dump_reg(decoder->client, REG_SATURATION, value); + dump_reg(decoder->client, REG_HUE, value); + dump_reg(decoder->client, REG_CHROMA_CONTROL1, value); + dump_reg(decoder->client, REG_CHROMA_CONTROL2, value); + dump_reg(decoder->client, REG_COMP_PR_SATURATION, value); + dump_reg(decoder->client, REG_COMP_Y_CONTRAST, value); + dump_reg(decoder->client, REG_COMP_PB_SATURATION, value); + dump_reg(decoder->client, REG_COMP_Y_BRIGHTNESS, value); + dump_reg(decoder->client, REG_AVID_START_PIXEL_LSB, value); + dump_reg(decoder->client, REG_AVID_START_PIXEL_MSB, value); + dump_reg(decoder->client, REG_AVID_STOP_PIXEL_LSB, value); + dump_reg(decoder->client, REG_AVID_STOP_PIXEL_MSB, value); + dump_reg(decoder->client, REG_HSYNC_START_PIXEL_LSB, value); + dump_reg(decoder->client, REG_HSYNC_START_PIXEL_MSB, value); + dump_reg(decoder->client, REG_HSYNC_STOP_PIXEL_LSB, value); + dump_reg(decoder->client, REG_HSYNC_STOP_PIXEL_MSB, value); + dump_reg(decoder->client, REG_VSYNC_START_LINE_LSB, value); + dump_reg(decoder->client, REG_VSYNC_START_LINE_MSB, value); + dump_reg(decoder->client, REG_VSYNC_STOP_LINE_LSB, value); + dump_reg(decoder->client, REG_VSYNC_STOP_LINE_MSB, value); + dump_reg(decoder->client, REG_VBLK_START_LINE_LSB, value); + dump_reg(decoder->client, REG_VBLK_START_LINE_MSB, value); + dump_reg(decoder->client, REG_VBLK_STOP_LINE_LSB, value); + dump_reg(decoder->client, REG_VBLK_STOP_LINE_MSB, value); + dump_reg(decoder->client, REG_SYNC_CONTROL, value); + dump_reg(decoder->client, REG_OUTPUT_FORMATTER1, value); + dump_reg(decoder->client, REG_OUTPUT_FORMATTER2, value); + dump_reg(decoder->client, REG_OUTPUT_FORMATTER3, value); + dump_reg(decoder->client, REG_OUTPUT_FORMATTER4, value); + dump_reg(decoder->client, REG_OUTPUT_FORMATTER5, value); + dump_reg(decoder->client, REG_OUTPUT_FORMATTER6, value); + dump_reg(decoder->client, REG_CLEAR_LOST_LOCK, value); +} + +/* + * Configure the TVP5146/47 with the current register settings + * Returns zero if successful, or non-zero otherwise. + */ +static int tvp514x_configure(struct tvp514x_decoder *decoder) +{ + int err; + + /* common register initialization */ + err = + tvp514x_write_regs(decoder->client, tvp514x_reg_list); + if (err) + return err; + + if (debug) + tvp514x_reg_dump(decoder); + + return 0; +} + +/* + * Detect if an tvp514x is present, and if so which revision. + * A device is considered to be detected if the chip ID (LSB and MSB) + * registers match the expected values. + * Any value of the rom version register is accepted. + * Returns ENODEV error number if no device is detected, or zero + * if a device is detected. + */ +static int tvp514x_detect(struct tvp514x_decoder *decoder) +{ + u8 chip_id_msb, chip_id_lsb, rom_ver; + + chip_id_msb = tvp514x_read_reg(decoder->client, REG_CHIP_ID_MSB); + chip_id_lsb = tvp514x_read_reg(decoder->client, REG_CHIP_ID_LSB); + rom_ver = tvp514x_read_reg(decoder->client, REG_ROM_VERSION); + + v4l_dbg(1, debug, decoder->client, + "chip id detected msb:0x%x lsb:0x%x rom version:0x%x\n", + chip_id_msb, chip_id_lsb, rom_ver); + if ((chip_id_msb != TVP514X_CHIP_ID_MSB) + || ((chip_id_lsb != TVP5146_CHIP_ID_LSB) + && (chip_id_lsb != TVP5147_CHIP_ID_LSB))) { + /* We didn't read the values we expected, so this must not be + * an TVP5146/47. + */ + v4l_err(decoder->client, + "chip id mismatch msb:0x%x lsb:0x%x\n", + chip_id_msb, chip_id_lsb); + return -ENODEV; + } + + decoder->ver = rom_ver; + decoder->state = STATE_DETECTED; + + v4l_info(decoder->client, + "%s found at 0x%x (%s)\n", decoder->client->name, + decoder->client->addr << 1, + decoder->client->adapter->name); + return 0; +} + +/* + * Following are decoder interface functions implemented by + * TVP5146/47 decoder driver. + */ + +/** + * ioctl_querystd - V4L2 decoder interface handler for VIDIOC_QUERYSTD ioctl + * @s: pointer to standard V4L2 device structure + * @std_id: standard V4L2 std_id ioctl enum + * + * Returns the current standard detected by TVP5146/47. If no active input is + * detected, returns -EINVAL + */ +static int ioctl_querystd(struct v4l2_int_device *s, v4l2_std_id *std_id) +{ + struct tvp514x_decoder *decoder = s->priv; + enum tvp514x_std current_std; + enum tvp514x_input input_sel; + u8 sync_lock_status, lock_mask; + + if (std_id == NULL) + return -EINVAL; + + /* get the current standard */ + current_std = tvp514x_get_current_std(decoder); + if (current_std == STD_INVALID) + return -EINVAL; + + input_sel = decoder->route.input; + + switch (input_sel) { + case INPUT_CVBS_VI1A: + case INPUT_CVBS_VI1B: + case INPUT_CVBS_VI1C: + case INPUT_CVBS_VI2A: + case INPUT_CVBS_VI2B: + case INPUT_CVBS_VI2C: + case INPUT_CVBS_VI3A: + case INPUT_CVBS_VI3B: + case INPUT_CVBS_VI3C: + case INPUT_CVBS_VI4A: + lock_mask = STATUS_CLR_SUBCAR_LOCK_BIT | + STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + + case INPUT_SVIDEO_VI2A_VI1A: + case INPUT_SVIDEO_VI2B_VI1B: + case INPUT_SVIDEO_VI2C_VI1C: + case INPUT_SVIDEO_VI2A_VI3A: + case INPUT_SVIDEO_VI2B_VI3B: + case INPUT_SVIDEO_VI2C_VI3C: + case INPUT_SVIDEO_VI4A_VI1A: + case INPUT_SVIDEO_VI4A_VI1B: + case INPUT_SVIDEO_VI4A_VI1C: + case INPUT_SVIDEO_VI4A_VI3A: + case INPUT_SVIDEO_VI4A_VI3B: + case INPUT_SVIDEO_VI4A_VI3C: + lock_mask = STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + /*Need to add other interfaces*/ + default: + return -EINVAL; + } + /* check whether signal is locked */ + sync_lock_status = tvp514x_read_reg(decoder->client, REG_STATUS1); + if (lock_mask != (sync_lock_status & lock_mask)) + return -EINVAL; /* No input detected */ + + decoder->current_std = current_std; + *std_id = decoder->std_list[current_std].standard.id; + + v4l_dbg(1, debug, decoder->client, "Current STD: %s", + decoder->std_list[current_std].standard.name); + return 0; +} + +/** + * ioctl_s_std - V4L2 decoder interface handler for VIDIOC_S_STD ioctl + * @s: pointer to standard V4L2 device structure + * @std_id: standard V4L2 v4l2_std_id ioctl enum + * + * If std_id is supported, sets the requested standard. Otherwise, returns + * -EINVAL + */ +static int ioctl_s_std(struct v4l2_int_device *s, v4l2_std_id *std_id) +{ + struct tvp514x_decoder *decoder = s->priv; + int err, i; + + if (std_id == NULL) + return -EINVAL; + + for (i = 0; i < decoder->num_stds; i++) + if (*std_id & decoder->std_list[i].standard.id) + break; + + if ((i == decoder->num_stds) || (i == STD_INVALID)) + return -EINVAL; + + err = tvp514x_write_reg(decoder->client, REG_VIDEO_STD, + decoder->std_list[i].video_std); + if (err) + return err; + + decoder->current_std = i; + tvp514x_reg_list[REG_VIDEO_STD].val = decoder->std_list[i].video_std; + + v4l_dbg(1, debug, decoder->client, "Standard set to: %s", + decoder->std_list[i].standard.name); + return 0; +} + +/** + * ioctl_s_routing - V4L2 decoder interface handler for VIDIOC_S_INPUT ioctl + * @s: pointer to standard V4L2 device structure + * @index: number of the input + * + * If index is valid, selects the requested input. Otherwise, returns -EINVAL if + * the input is not supported or there is no active signal present in the + * selected input. + */ +static int ioctl_s_routing(struct v4l2_int_device *s, + struct v4l2_routing *route) +{ + struct tvp514x_decoder *decoder = s->priv; + int err; + enum tvp514x_input input_sel; + enum tvp514x_output output_sel; + enum tvp514x_std current_std = STD_INVALID; + u8 sync_lock_status, lock_mask; + int try_count = LOCK_RETRY_COUNT; + + if ((!route) || (route->input >= INPUT_INVALID) || + (route->output >= OUTPUT_INVALID)) + return -EINVAL; /* Index out of bound */ + + input_sel = route->input; + output_sel = route->output; + + err = tvp514x_write_reg(decoder->client, REG_INPUT_SEL, input_sel); + if (err) + return err; + + output_sel |= tvp514x_read_reg(decoder->client, + REG_OUTPUT_FORMATTER1) & 0x7; + err = tvp514x_write_reg(decoder->client, REG_OUTPUT_FORMATTER1, + output_sel); + if (err) + return err; + + tvp514x_reg_list[REG_INPUT_SEL].val = input_sel; + tvp514x_reg_list[REG_OUTPUT_FORMATTER1].val = output_sel; + + /* Clear status */ + msleep(LOCK_RETRY_DELAY); + err = + tvp514x_write_reg(decoder->client, REG_CLEAR_LOST_LOCK, 0x01); + if (err) + return err; + + switch (input_sel) { + case INPUT_CVBS_VI1A: + case INPUT_CVBS_VI1B: + case INPUT_CVBS_VI1C: + case INPUT_CVBS_VI2A: + case INPUT_CVBS_VI2B: + case INPUT_CVBS_VI2C: + case INPUT_CVBS_VI3A: + case INPUT_CVBS_VI3B: + case INPUT_CVBS_VI3C: + case INPUT_CVBS_VI4A: + lock_mask = STATUS_CLR_SUBCAR_LOCK_BIT | + STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + + case INPUT_SVIDEO_VI2A_VI1A: + case INPUT_SVIDEO_VI2B_VI1B: + case INPUT_SVIDEO_VI2C_VI1C: + case INPUT_SVIDEO_VI2A_VI3A: + case INPUT_SVIDEO_VI2B_VI3B: + case INPUT_SVIDEO_VI2C_VI3C: + case INPUT_SVIDEO_VI4A_VI1A: + case INPUT_SVIDEO_VI4A_VI1B: + case INPUT_SVIDEO_VI4A_VI1C: + case INPUT_SVIDEO_VI4A_VI3A: + case INPUT_SVIDEO_VI4A_VI3B: + case INPUT_SVIDEO_VI4A_VI3C: + lock_mask = STATUS_HORZ_SYNC_LOCK_BIT | + STATUS_VIRT_SYNC_LOCK_BIT; + break; + /*Need to add other interfaces*/ + default: + return -EINVAL; + } + + while (try_count-- > 0) { + /* Allow decoder to sync up with new input */ + msleep(LOCK_RETRY_DELAY); + + /* get the current standard for future reference */ + current_std = tvp514x_get_current_std(decoder); + if (current_std == STD_INVALID) + continue; + + sync_lock_status = tvp514x_read_reg(decoder->client, + REG_STATUS1); + if (lock_mask == (sync_lock_status & lock_mask)) + break; /* Input detected */ + } + + if ((current_std == STD_INVALID) || (try_count < 0)) + return -EINVAL; + + decoder->current_std = current_std; + decoder->route.input = route->input; + decoder->route.output = route->output; + + v4l_dbg(1, debug, decoder->client, + "Input set to: %d, std : %d", + input_sel, current_std); + + return 0; +} + +/** + * ioctl_queryctrl - V4L2 decoder interface handler for VIDIOC_QUERYCTRL ioctl + * @s: pointer to standard V4L2 device structure + * @qctrl: standard V4L2 v4l2_queryctrl structure + * + * If the requested control is supported, returns the control information. + * Otherwise, returns -EINVAL if the control is not supported. + */ +static int +ioctl_queryctrl(struct v4l2_int_device *s, struct v4l2_queryctrl *qctrl) +{ + struct tvp514x_decoder *decoder = s->priv; + int err = -EINVAL; + + if (qctrl == NULL) + return err; + + switch (qctrl->id) { + case V4L2_CID_BRIGHTNESS: + /* Brightness supported is same as standard one (0-255), + * so make use of standard API provided. + */ + err = v4l2_ctrl_query_fill_std(qctrl); + break; + case V4L2_CID_CONTRAST: + case V4L2_CID_SATURATION: + /* Saturation and Contrast supported is - + * Contrast: 0 - 255 (Default - 128) + * Saturation: 0 - 255 (Default - 128) + */ + err = v4l2_ctrl_query_fill(qctrl, 0, 255, 1, 128); + break; + case V4L2_CID_HUE: + /* Hue Supported is - + * Hue - -180 - +180 (Default - 0, Step - +180) + */ + err = v4l2_ctrl_query_fill(qctrl, -180, 180, 180, 0); + break; + case V4L2_CID_AUTOGAIN: + /* Autogain is either 0 or 1*/ + memcpy(qctrl, &tvp514x_autogain_ctrl, + sizeof(struct v4l2_queryctrl)); + err = 0; + break; + default: + v4l_err(decoder->client, + "invalid control id %d\n", qctrl->id); + return err; + } + + v4l_dbg(1, debug, decoder->client, + "Query Control: %s : Min - %d, Max - %d, Def - %d", + qctrl->name, + qctrl->minimum, + qctrl->maximum, + qctrl->default_value); + + return err; +} + +/** + * ioctl_g_ctrl - V4L2 decoder interface handler for VIDIOC_G_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @ctrl: pointer to v4l2_control structure + * + * If the requested control is supported, returns the control's current + * value from the decoder. Otherwise, returns -EINVAL if the control is not + * supported. + */ +static int +ioctl_g_ctrl(struct v4l2_int_device *s, struct v4l2_control *ctrl) +{ + struct tvp514x_decoder *decoder = s->priv; + + if (ctrl == NULL) + return -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrl->value = tvp514x_reg_list[REG_BRIGHTNESS].val; + break; + case V4L2_CID_CONTRAST: + ctrl->value = tvp514x_reg_list[REG_CONTRAST].val; + break; + case V4L2_CID_SATURATION: + ctrl->value = tvp514x_reg_list[REG_SATURATION].val; + break; + case V4L2_CID_HUE: + ctrl->value = tvp514x_reg_list[REG_HUE].val; + if (ctrl->value == 0x7F) + ctrl->value = 180; + else if (ctrl->value == 0x80) + ctrl->value = -180; + else + ctrl->value = 0; + + break; + case V4L2_CID_AUTOGAIN: + ctrl->value = tvp514x_reg_list[REG_AFE_GAIN_CTRL].val; + if ((ctrl->value & 0x3) == 3) + ctrl->value = 1; + else + ctrl->value = 0; + + break; + default: + v4l_err(decoder->client, + "invalid control id %d\n", ctrl->id); + return -EINVAL; + } + + v4l_dbg(1, debug, decoder->client, + "Get Control: ID - %d - %d", + ctrl->id, ctrl->value); + return 0; +} + +/** + * ioctl_s_ctrl - V4L2 decoder interface handler for VIDIOC_S_CTRL ioctl + * @s: pointer to standard V4L2 device structure + * @ctrl: pointer to v4l2_control structure + * + * If the requested control is supported, sets the control's current + * value in HW. Otherwise, returns -EINVAL if the control is not supported. + */ +static int +ioctl_s_ctrl(struct v4l2_int_device *s, struct v4l2_control *ctrl) +{ + struct tvp514x_decoder *decoder = s->priv; + int err = -EINVAL, value; + + if (ctrl == NULL) + return err; + + value = (__s32) ctrl->value; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + if (ctrl->value < 0 || ctrl->value > 255) { + v4l_err(decoder->client, + "invalid brightness setting %d\n", + ctrl->value); + return -ERANGE; + } + err = tvp514x_write_reg(decoder->client, REG_BRIGHTNESS, + value); + if (err) + return err; + tvp514x_reg_list[REG_BRIGHTNESS].val = value; + break; + case V4L2_CID_CONTRAST: + if (ctrl->value < 0 || ctrl->value > 255) { + v4l_err(decoder->client, + "invalid contrast setting %d\n", + ctrl->value); + return -ERANGE; + } + err = tvp514x_write_reg(decoder->client, REG_CONTRAST, + value); + if (err) + return err; + tvp514x_reg_list[REG_CONTRAST].val = value; + break; + case V4L2_CID_SATURATION: + if (ctrl->value < 0 || ctrl->value > 255) { + v4l_err(decoder->client, + "invalid saturation setting %d\n", + ctrl->value); + return -ERANGE; + } + err = tvp514x_write_reg(decoder->client, REG_SATURATION, + value); + if (err) + return err; + tvp514x_reg_list[REG_SATURATION].val = value; + break; + case V4L2_CID_HUE: + if (value == 180) + value = 0x7F; + else if (value == -180) + value = 0x80; + else if (value == 0) + value = 0; + else { + v4l_err(decoder->client, + "invalid hue setting %d\n", + ctrl->value); + return -ERANGE; + } + err = tvp514x_write_reg(decoder->client, REG_HUE, + value); + if (err) + return err; + tvp514x_reg_list[REG_HUE].val = value; + break; + case V4L2_CID_AUTOGAIN: + if (value == 1) + value = 0x0F; + else if (value == 0) + value = 0x0C; + else { + v4l_err(decoder->client, + "invalid auto gain setting %d\n", + ctrl->value); + return -ERANGE; + } + err = tvp514x_write_reg(decoder->client, REG_AFE_GAIN_CTRL, + value); + if (err) + return err; + tvp514x_reg_list[REG_AFE_GAIN_CTRL].val = value; + break; + default: + v4l_err(decoder->client, + "invalid control id %d\n", ctrl->id); + return err; + } + + v4l_dbg(1, debug, decoder->client, + "Set Control: ID - %d - %d", + ctrl->id, ctrl->value); + + return err; +} + +/** + * ioctl_enum_fmt_cap - Implement the CAPTURE buffer VIDIOC_ENUM_FMT ioctl + * @s: pointer to standard V4L2 device structure + * @fmt: standard V4L2 VIDIOC_ENUM_FMT ioctl structure + * + * Implement the VIDIOC_ENUM_FMT ioctl to enumerate supported formats + */ +static int +ioctl_enum_fmt_cap(struct v4l2_int_device *s, struct v4l2_fmtdesc *fmt) +{ + struct tvp514x_decoder *decoder = s->priv; + int index; + + if (fmt == NULL) + return -EINVAL; + + index = fmt->index; + if ((index >= decoder->num_fmts) || (index < 0)) + return -EINVAL; /* Index out of bound */ + + if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; /* only capture is supported */ + + memcpy(fmt, &decoder->fmt_list[index], + sizeof(struct v4l2_fmtdesc)); + + v4l_dbg(1, debug, decoder->client, + "Current FMT: index - %d (%s)", + decoder->fmt_list[index].index, + decoder->fmt_list[index].description); + return 0; +} + +/** + * ioctl_try_fmt_cap - Implement the CAPTURE buffer VIDIOC_TRY_FMT ioctl + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 VIDIOC_TRY_FMT ioctl structure + * + * Implement the VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type. This + * ioctl is used to negotiate the image capture size and pixel format + * without actually making it take effect. + */ +static int +ioctl_try_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct tvp514x_decoder *decoder = s->priv; + int ifmt; + struct v4l2_pix_format *pix; + enum tvp514x_std current_std; + + if (f == NULL) + return -EINVAL; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + pix = &f->fmt.pix; + + /* Calculate height and width based on current standard */ + current_std = tvp514x_get_current_std(decoder); + if (current_std == STD_INVALID) + return -EINVAL; + + decoder->current_std = current_std; + pix->width = decoder->std_list[current_std].width; + pix->height = decoder->std_list[current_std].height; + + for (ifmt = 0; ifmt < decoder->num_fmts; ifmt++) { + if (pix->pixelformat == + decoder->fmt_list[ifmt].pixelformat) + break; + } + if (ifmt == decoder->num_fmts) + ifmt = 0; /* None of the format matched, select default */ + pix->pixelformat = decoder->fmt_list[ifmt].pixelformat; + + pix->field = V4L2_FIELD_INTERLACED; + pix->bytesperline = pix->width * 2; + pix->sizeimage = pix->bytesperline * pix->height; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->priv = 0; + + v4l_dbg(1, debug, decoder->client, + "Try FMT: pixelformat - %s, bytesperline - %d" + "Width - %d, Height - %d", + decoder->fmt_list[ifmt].description, pix->bytesperline, + pix->width, pix->height); + return 0; +} + +/** + * ioctl_s_fmt_cap - V4L2 decoder interface handler for VIDIOC_S_FMT ioctl + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 VIDIOC_S_FMT ioctl structure + * + * If the requested format is supported, configures the HW to use that + * format, returns error code if format not supported or HW can't be + * correctly configured. + */ +static int +ioctl_s_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct tvp514x_decoder *decoder = s->priv; + struct v4l2_pix_format *pix; + int rval; + + if (f == NULL) + return -EINVAL; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; /* only capture is supported */ + + pix = &f->fmt.pix; + rval = ioctl_try_fmt_cap(s, f); + if (rval) + return rval; + + decoder->pix = *pix; + + return rval; +} + +/** + * ioctl_g_fmt_cap - V4L2 decoder interface handler for ioctl_g_fmt_cap + * @s: pointer to standard V4L2 device structure + * @f: pointer to standard V4L2 v4l2_format structure + * + * Returns the decoder's current pixel format in the v4l2_format + * parameter. + */ +static int +ioctl_g_fmt_cap(struct v4l2_int_device *s, struct v4l2_format *f) +{ + struct tvp514x_decoder *decoder = s->priv; + + if (f == NULL) + return -EINVAL; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; /* only capture is supported */ + + f->fmt.pix = decoder->pix; + + v4l_dbg(1, debug, decoder->client, + "Current FMT: bytesperline - %d" + "Width - %d, Height - %d", + decoder->pix.bytesperline, + decoder->pix.width, decoder->pix.height); + return 0; +} + +/** + * ioctl_g_parm - V4L2 decoder interface handler for VIDIOC_G_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure + * + * Returns the decoder's video CAPTURE parameters. + */ +static int +ioctl_g_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct tvp514x_decoder *decoder = s->priv; + struct v4l2_captureparm *cparm; + enum tvp514x_std current_std; + + if (a == NULL) + return -EINVAL; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; /* only capture is supported */ + + memset(a, 0, sizeof(*a)); + a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* get the current standard */ + current_std = tvp514x_get_current_std(decoder); + if (current_std == STD_INVALID) + return -EINVAL; + + decoder->current_std = current_std; + + cparm = &a->parm.capture; + cparm->capability = V4L2_CAP_TIMEPERFRAME; + cparm->timeperframe = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + +/** + * ioctl_s_parm - V4L2 decoder interface handler for VIDIOC_S_PARM ioctl + * @s: pointer to standard V4L2 device structure + * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure + * + * Configures the decoder to use the input parameters, if possible. If + * not possible, returns the appropriate error code. + */ +static int +ioctl_s_parm(struct v4l2_int_device *s, struct v4l2_streamparm *a) +{ + struct tvp514x_decoder *decoder = s->priv; + struct v4l2_fract *timeperframe; + enum tvp514x_std current_std; + + if (a == NULL) + return -EINVAL; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; /* only capture is supported */ + + timeperframe = &a->parm.capture.timeperframe; + + /* get the current standard */ + current_std = tvp514x_get_current_std(decoder); + if (current_std == STD_INVALID) + return -EINVAL; + + decoder->current_std = current_std; + + *timeperframe = + decoder->std_list[current_std].standard.frameperiod; + + return 0; +} + +/** + * ioctl_g_ifparm - V4L2 decoder interface handler for vidioc_int_g_ifparm_num + * @s: pointer to standard V4L2 device structure + * @p: pointer to standard V4L2 vidioc_int_g_ifparm_num ioctl structure + * + * Gets slave interface parameters. + * Calculates the required xclk value to support the requested + * clock parameters in p. This value is returned in the p + * parameter. + */ +static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p) +{ + struct tvp514x_decoder *decoder = s->priv; + int rval; + + if (p == NULL) + return -EINVAL; + + if (NULL == decoder->pdata->ifparm) + return -EINVAL; + + rval = decoder->pdata->ifparm(p); + if (rval) { + v4l_err(decoder->client, "g_ifparm.Err[%d]\n", rval); + return rval; + } + + p->u.bt656.clock_curr = TVP514X_XCLK_BT656; + + return 0; +} + +/** + * ioctl_g_priv - V4L2 decoder interface handler for vidioc_int_g_priv_num + * @s: pointer to standard V4L2 device structure + * @p: void pointer to hold decoder's private data address + * + * Returns device's (decoder's) private data area address in p parameter + */ +static int ioctl_g_priv(struct v4l2_int_device *s, void *p) +{ + struct tvp514x_decoder *decoder = s->priv; + + if (NULL == decoder->pdata->priv_data_set) + return -EINVAL; + + return decoder->pdata->priv_data_set(p); +} + +/** + * ioctl_s_power - V4L2 decoder interface handler for vidioc_int_s_power_num + * @s: pointer to standard V4L2 device structure + * @on: power state to which device is to be set + * + * Sets devices power state to requrested state, if possible. + */ +static int ioctl_s_power(struct v4l2_int_device *s, enum v4l2_power on) +{ + struct tvp514x_decoder *decoder = s->priv; + int err = 0; + + switch (on) { + case V4L2_POWER_OFF: + /* Power Down Sequence */ + err = + tvp514x_write_reg(decoder->client, REG_OPERATION_MODE, + 0x01); + /* Disable mux for TVP5146/47 decoder data path */ + if (decoder->pdata->power_set) + err |= decoder->pdata->power_set(on); + decoder->state = STATE_NOT_DETECTED; + break; + + case V4L2_POWER_STANDBY: + if (decoder->pdata->power_set) + err = decoder->pdata->power_set(on); + break; + + case V4L2_POWER_ON: + /* Enable mux for TVP5146/47 decoder data path */ + if ((decoder->pdata->power_set) && + (decoder->state == STATE_NOT_DETECTED)) { + int i; + struct tvp514x_init_seq *int_seq = + (struct tvp514x_init_seq *) + decoder->id->driver_data; + + err = decoder->pdata->power_set(on); + + /* Power Up Sequence */ + for (i = 0; i < int_seq->no_regs; i++) { + err |= tvp514x_write_reg(decoder->client, + int_seq->init_reg_seq[i].reg, + int_seq->init_reg_seq[i].val); + } + /* Detect the sensor is not already detected */ + err |= tvp514x_detect(decoder); + if (err) { + v4l_err(decoder->client, + "Unable to detect decoder\n"); + return err; + } + } + err |= tvp514x_configure(decoder); + break; + + default: + err = -ENODEV; + break; + } + + return err; +} + +/** + * ioctl_init - V4L2 decoder interface handler for VIDIOC_INT_INIT + * @s: pointer to standard V4L2 device structure + * + * Initialize the decoder device (calls tvp514x_configure()) + */ +static int ioctl_init(struct v4l2_int_device *s) +{ + struct tvp514x_decoder *decoder = s->priv; + + /* Set default standard to auto */ + tvp514x_reg_list[REG_VIDEO_STD].val = + VIDEO_STD_AUTO_SWITCH_BIT; + + return tvp514x_configure(decoder); +} + +/** + * ioctl_dev_exit - V4L2 decoder interface handler for vidioc_int_dev_exit_num + * @s: pointer to standard V4L2 device structure + * + * Delinitialise the dev. at slave detach. The complement of ioctl_dev_init. + */ +static int ioctl_dev_exit(struct v4l2_int_device *s) +{ + return 0; +} + +/** + * ioctl_dev_init - V4L2 decoder interface handler for vidioc_int_dev_init_num + * @s: pointer to standard V4L2 device structure + * + * Initialise the device when slave attaches to the master. Returns 0 if + * TVP5146/47 device could be found, otherwise returns appropriate error. + */ +static int ioctl_dev_init(struct v4l2_int_device *s) +{ + struct tvp514x_decoder *decoder = s->priv; + int err; + + err = tvp514x_detect(decoder); + if (err < 0) { + v4l_err(decoder->client, + "Unable to detect decoder\n"); + return err; + } + + v4l_info(decoder->client, + "chip version 0x%.2x detected\n", decoder->ver); + + return 0; +} + +static struct v4l2_int_ioctl_desc tvp514x_ioctl_desc[] = { + {vidioc_int_dev_init_num, (v4l2_int_ioctl_func*) ioctl_dev_init}, + {vidioc_int_dev_exit_num, (v4l2_int_ioctl_func*) ioctl_dev_exit}, + {vidioc_int_s_power_num, (v4l2_int_ioctl_func*) ioctl_s_power}, + {vidioc_int_g_priv_num, (v4l2_int_ioctl_func*) ioctl_g_priv}, + {vidioc_int_g_ifparm_num, (v4l2_int_ioctl_func*) ioctl_g_ifparm}, + {vidioc_int_init_num, (v4l2_int_ioctl_func*) ioctl_init}, + {vidioc_int_enum_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_enum_fmt_cap}, + {vidioc_int_try_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_try_fmt_cap}, + {vidioc_int_g_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_g_fmt_cap}, + {vidioc_int_s_fmt_cap_num, + (v4l2_int_ioctl_func *) ioctl_s_fmt_cap}, + {vidioc_int_g_parm_num, (v4l2_int_ioctl_func *) ioctl_g_parm}, + {vidioc_int_s_parm_num, (v4l2_int_ioctl_func *) ioctl_s_parm}, + {vidioc_int_queryctrl_num, + (v4l2_int_ioctl_func *) ioctl_queryctrl}, + {vidioc_int_g_ctrl_num, (v4l2_int_ioctl_func *) ioctl_g_ctrl}, + {vidioc_int_s_ctrl_num, (v4l2_int_ioctl_func *) ioctl_s_ctrl}, + {vidioc_int_querystd_num, (v4l2_int_ioctl_func *) ioctl_querystd}, + {vidioc_int_s_std_num, (v4l2_int_ioctl_func *) ioctl_s_std}, + {vidioc_int_s_video_routing_num, + (v4l2_int_ioctl_func *) ioctl_s_routing}, +}; + +static struct v4l2_int_slave tvp514x_slave = { + .ioctls = tvp514x_ioctl_desc, + .num_ioctls = ARRAY_SIZE(tvp514x_ioctl_desc), +}; + +static struct tvp514x_decoder tvp514x_dev = { + .state = STATE_NOT_DETECTED, + + .fmt_list = tvp514x_fmt_list, + .num_fmts = ARRAY_SIZE(tvp514x_fmt_list), + + .pix = { /* Default to NTSC 8-bit YUV 422 */ + .width = NTSC_NUM_ACTIVE_PIXELS, + .height = NTSC_NUM_ACTIVE_LINES, + .pixelformat = V4L2_PIX_FMT_UYVY, + .field = V4L2_FIELD_INTERLACED, + .bytesperline = NTSC_NUM_ACTIVE_PIXELS * 2, + .sizeimage = + NTSC_NUM_ACTIVE_PIXELS * 2 * NTSC_NUM_ACTIVE_LINES, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + }, + + .current_std = STD_NTSC_MJ, + .std_list = tvp514x_std_list, + .num_stds = ARRAY_SIZE(tvp514x_std_list), + +}; + +static struct v4l2_int_device tvp514x_int_device = { + .module = THIS_MODULE, + .name = TVP514X_MODULE_NAME, + .priv = &tvp514x_dev, + .type = v4l2_int_type_slave, + .u = { + .slave = &tvp514x_slave, + }, +}; + +/** + * tvp514x_probe - decoder driver i2c probe handler + * @client: i2c driver client device structure + * + * Register decoder as an i2c client device and V4L2 + * device. + */ +static int +tvp514x_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct tvp514x_decoder *decoder = &tvp514x_dev; + int err; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + decoder->pdata = client->dev.platform_data; + if (!decoder->pdata) { + v4l_err(client, "No platform data\n!!"); + return -ENODEV; + } + /* + * Fetch platform specific data, and configure the + * tvp514x_reg_list[] accordingly. Since this is one + * time configuration, no need to preserve. + */ + tvp514x_reg_list[REG_OUTPUT_FORMATTER2].val |= + (decoder->pdata->clk_polarity << 1); + tvp514x_reg_list[REG_SYNC_CONTROL].val |= + ((decoder->pdata->hs_polarity << 2) | + (decoder->pdata->vs_polarity << 3)); + /* + * Save the id data, required for power up sequence + */ + decoder->id = (struct i2c_device_id *)id; + /* Attach to Master */ + strcpy(tvp514x_int_device.u.slave->attach_to, decoder->pdata->master); + decoder->v4l2_int_device = &tvp514x_int_device; + decoder->client = client; + i2c_set_clientdata(client, decoder); + + /* Register with V4L2 layer as slave device */ + err = v4l2_int_device_register(decoder->v4l2_int_device); + if (err) { + i2c_set_clientdata(client, NULL); + v4l_err(client, + "Unable to register to v4l2. Err[%d]\n", err); + + } else + v4l_info(client, "Registered to v4l2 master %s!!\n", + decoder->pdata->master); + + return 0; +} + +/** + * tvp514x_remove - decoder driver i2c remove handler + * @client: i2c driver client device structure + * + * Unregister decoder as an i2c client device and V4L2 + * device. Complement of tvp514x_probe(). + */ +static int __exit tvp514x_remove(struct i2c_client *client) +{ + struct tvp514x_decoder *decoder = i2c_get_clientdata(client); + + if (!client->adapter) + return -ENODEV; /* our client isn't attached */ + + v4l2_int_device_unregister(decoder->v4l2_int_device); + i2c_set_clientdata(client, NULL); + + return 0; +} +/* + * TVP5146 Init/Power on Sequence + */ +static const struct tvp514x_reg tvp5146_init_reg_seq[] = { + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x02}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0x80}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, +}; +static const struct tvp514x_init_seq tvp5146_init = { + .no_regs = ARRAY_SIZE(tvp5146_init_reg_seq), + .init_reg_seq = tvp5146_init_reg_seq, +}; +/* + * TVP5147 Init/Power on Sequence + */ +static const struct tvp514x_reg tvp5147_init_reg_seq[] = { + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x02}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0x80}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x01}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x16}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xA0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x16}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS1, 0x60}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS2, 0x00}, + {TOK_WRITE, REG_VBUS_ADDRESS_ACCESS3, 0xB0}, + {TOK_WRITE, REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR, 0x00}, + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, +}; +static const struct tvp514x_init_seq tvp5147_init = { + .no_regs = ARRAY_SIZE(tvp5147_init_reg_seq), + .init_reg_seq = tvp5147_init_reg_seq, +}; +/* + * TVP5146M2/TVP5147M1 Init/Power on Sequence + */ +static const struct tvp514x_reg tvp514xm_init_reg_seq[] = { + {TOK_WRITE, REG_OPERATION_MODE, 0x01}, + {TOK_WRITE, REG_OPERATION_MODE, 0x00}, +}; +static const struct tvp514x_init_seq tvp514xm_init = { + .no_regs = ARRAY_SIZE(tvp514xm_init_reg_seq), + .init_reg_seq = tvp514xm_init_reg_seq, +}; +/* + * I2C Device Table - + * + * name - Name of the actual device/chip. + * driver_data - Driver data + */ +static const struct i2c_device_id tvp514x_id[] = { + {"tvp5146", (unsigned long)&tvp5146_init}, + {"tvp5146m2", (unsigned long)&tvp514xm_init}, + {"tvp5147", (unsigned long)&tvp5147_init}, + {"tvp5147m1", (unsigned long)&tvp514xm_init}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, tvp514x_id); + +static struct i2c_driver tvp514x_i2c_driver = { + .driver = { + .name = TVP514X_MODULE_NAME, + .owner = THIS_MODULE, + }, + .probe = tvp514x_probe, + .remove = __exit_p(tvp514x_remove), + .id_table = tvp514x_id, +}; + +/** + * tvp514x_init + * + * Module init function + */ +static int __init tvp514x_init(void) +{ + return i2c_add_driver(&tvp514x_i2c_driver); +} + +/** + * tvp514x_cleanup + * + * Module exit function + */ +static void __exit tvp514x_cleanup(void) +{ + i2c_del_driver(&tvp514x_i2c_driver); +} + +module_init(tvp514x_init); +module_exit(tvp514x_cleanup); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TVP514X linux decoder driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/tvp514x_regs.h b/drivers/media/video/tvp514x_regs.h new file mode 100644 index 00000000000..351620aeecc --- /dev/null +++ b/drivers/media/video/tvp514x_regs.h @@ -0,0 +1,297 @@ +/* + * drivers/media/video/tvp514x_regs.h + * + * Copyright (C) 2008 Texas Instruments Inc + * Author: Vaibhav Hiremath + * + * Contributors: + * Sivaraj R + * Brijesh R Jadav + * Hardik Shah + * Manjunath Hadli + * Karicheri Muralidharan + * + * 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. + * + */ + +#ifndef _TVP514X_REGS_H +#define _TVP514X_REGS_H + +/* + * TVP5146/47 registers + */ +#define REG_INPUT_SEL (0x00) +#define REG_AFE_GAIN_CTRL (0x01) +#define REG_VIDEO_STD (0x02) +#define REG_OPERATION_MODE (0x03) +#define REG_AUTOSWITCH_MASK (0x04) + +#define REG_COLOR_KILLER (0x05) +#define REG_LUMA_CONTROL1 (0x06) +#define REG_LUMA_CONTROL2 (0x07) +#define REG_LUMA_CONTROL3 (0x08) + +#define REG_BRIGHTNESS (0x09) +#define REG_CONTRAST (0x0A) +#define REG_SATURATION (0x0B) +#define REG_HUE (0x0C) + +#define REG_CHROMA_CONTROL1 (0x0D) +#define REG_CHROMA_CONTROL2 (0x0E) + +/* 0x0F Reserved */ + +#define REG_COMP_PR_SATURATION (0x10) +#define REG_COMP_Y_CONTRAST (0x11) +#define REG_COMP_PB_SATURATION (0x12) + +/* 0x13 Reserved */ + +#define REG_COMP_Y_BRIGHTNESS (0x14) + +/* 0x15 Reserved */ + +#define REG_AVID_START_PIXEL_LSB (0x16) +#define REG_AVID_START_PIXEL_MSB (0x17) +#define REG_AVID_STOP_PIXEL_LSB (0x18) +#define REG_AVID_STOP_PIXEL_MSB (0x19) + +#define REG_HSYNC_START_PIXEL_LSB (0x1A) +#define REG_HSYNC_START_PIXEL_MSB (0x1B) +#define REG_HSYNC_STOP_PIXEL_LSB (0x1C) +#define REG_HSYNC_STOP_PIXEL_MSB (0x1D) + +#define REG_VSYNC_START_LINE_LSB (0x1E) +#define REG_VSYNC_START_LINE_MSB (0x1F) +#define REG_VSYNC_STOP_LINE_LSB (0x20) +#define REG_VSYNC_STOP_LINE_MSB (0x21) + +#define REG_VBLK_START_LINE_LSB (0x22) +#define REG_VBLK_START_LINE_MSB (0x23) +#define REG_VBLK_STOP_LINE_LSB (0x24) +#define REG_VBLK_STOP_LINE_MSB (0x25) + +/* 0x26 - 0x27 Reserved */ + +#define REG_FAST_SWTICH_CONTROL (0x28) + +/* 0x29 Reserved */ + +#define REG_FAST_SWTICH_SCART_DELAY (0x2A) + +/* 0x2B Reserved */ + +#define REG_SCART_DELAY (0x2C) +#define REG_CTI_DELAY (0x2D) +#define REG_CTI_CONTROL (0x2E) + +/* 0x2F - 0x31 Reserved */ + +#define REG_SYNC_CONTROL (0x32) +#define REG_OUTPUT_FORMATTER1 (0x33) +#define REG_OUTPUT_FORMATTER2 (0x34) +#define REG_OUTPUT_FORMATTER3 (0x35) +#define REG_OUTPUT_FORMATTER4 (0x36) +#define REG_OUTPUT_FORMATTER5 (0x37) +#define REG_OUTPUT_FORMATTER6 (0x38) +#define REG_CLEAR_LOST_LOCK (0x39) + +#define REG_STATUS1 (0x3A) +#define REG_STATUS2 (0x3B) + +#define REG_AGC_GAIN_STATUS_LSB (0x3C) +#define REG_AGC_GAIN_STATUS_MSB (0x3D) + +/* 0x3E Reserved */ + +#define REG_VIDEO_STD_STATUS (0x3F) +#define REG_GPIO_INPUT1 (0x40) +#define REG_GPIO_INPUT2 (0x41) + +/* 0x42 - 0x45 Reserved */ + +#define REG_AFE_COARSE_GAIN_CH1 (0x46) +#define REG_AFE_COARSE_GAIN_CH2 (0x47) +#define REG_AFE_COARSE_GAIN_CH3 (0x48) +#define REG_AFE_COARSE_GAIN_CH4 (0x49) + +#define REG_AFE_FINE_GAIN_PB_B_LSB (0x4A) +#define REG_AFE_FINE_GAIN_PB_B_MSB (0x4B) +#define REG_AFE_FINE_GAIN_Y_G_CHROMA_LSB (0x4C) +#define REG_AFE_FINE_GAIN_Y_G_CHROMA_MSB (0x4D) +#define REG_AFE_FINE_GAIN_PR_R_LSB (0x4E) +#define REG_AFE_FINE_GAIN_PR_R_MSB (0x4F) +#define REG_AFE_FINE_GAIN_CVBS_LUMA_LSB (0x50) +#define REG_AFE_FINE_GAIN_CVBS_LUMA_MSB (0x51) + +/* 0x52 - 0x68 Reserved */ + +#define REG_FBIT_VBIT_CONTROL1 (0x69) + +/* 0x6A - 0x6B Reserved */ + +#define REG_BACKEND_AGC_CONTROL (0x6C) + +/* 0x6D - 0x6E Reserved */ + +#define REG_AGC_DECREMENT_SPEED_CONTROL (0x6F) +#define REG_ROM_VERSION (0x70) + +/* 0x71 - 0x73 Reserved */ + +#define REG_AGC_WHITE_PEAK_PROCESSING (0x74) +#define REG_FBIT_VBIT_CONTROL2 (0x75) +#define REG_VCR_TRICK_MODE_CONTROL (0x76) +#define REG_HORIZONTAL_SHAKE_INCREMENT (0x77) +#define REG_AGC_INCREMENT_SPEED (0x78) +#define REG_AGC_INCREMENT_DELAY (0x79) + +/* 0x7A - 0x7F Reserved */ + +#define REG_CHIP_ID_MSB (0x80) +#define REG_CHIP_ID_LSB (0x81) + +/* 0x82 Reserved */ + +#define REG_CPLL_SPEED_CONTROL (0x83) + +/* 0x84 - 0x96 Reserved */ + +#define REG_STATUS_REQUEST (0x97) + +/* 0x98 - 0x99 Reserved */ + +#define REG_VERTICAL_LINE_COUNT_LSB (0x9A) +#define REG_VERTICAL_LINE_COUNT_MSB (0x9B) + +/* 0x9C - 0x9D Reserved */ + +#define REG_AGC_DECREMENT_DELAY (0x9E) + +/* 0x9F - 0xB0 Reserved */ + +#define REG_VDP_TTX_FILTER_1_MASK1 (0xB1) +#define REG_VDP_TTX_FILTER_1_MASK2 (0xB2) +#define REG_VDP_TTX_FILTER_1_MASK3 (0xB3) +#define REG_VDP_TTX_FILTER_1_MASK4 (0xB4) +#define REG_VDP_TTX_FILTER_1_MASK5 (0xB5) +#define REG_VDP_TTX_FILTER_2_MASK1 (0xB6) +#define REG_VDP_TTX_FILTER_2_MASK2 (0xB7) +#define REG_VDP_TTX_FILTER_2_MASK3 (0xB8) +#define REG_VDP_TTX_FILTER_2_MASK4 (0xB9) +#define REG_VDP_TTX_FILTER_2_MASK5 (0xBA) +#define REG_VDP_TTX_FILTER_CONTROL (0xBB) +#define REG_VDP_FIFO_WORD_COUNT (0xBC) +#define REG_VDP_FIFO_INTERRUPT_THRLD (0xBD) + +/* 0xBE Reserved */ + +#define REG_VDP_FIFO_RESET (0xBF) +#define REG_VDP_FIFO_OUTPUT_CONTROL (0xC0) +#define REG_VDP_LINE_NUMBER_INTERRUPT (0xC1) +#define REG_VDP_PIXEL_ALIGNMENT_LSB (0xC2) +#define REG_VDP_PIXEL_ALIGNMENT_MSB (0xC3) + +/* 0xC4 - 0xD5 Reserved */ + +#define REG_VDP_LINE_START (0xD6) +#define REG_VDP_LINE_STOP (0xD7) +#define REG_VDP_GLOBAL_LINE_MODE (0xD8) +#define REG_VDP_FULL_FIELD_ENABLE (0xD9) +#define REG_VDP_FULL_FIELD_MODE (0xDA) + +/* 0xDB - 0xDF Reserved */ + +#define REG_VBUS_DATA_ACCESS_NO_VBUS_ADDR_INCR (0xE0) +#define REG_VBUS_DATA_ACCESS_VBUS_ADDR_INCR (0xE1) +#define REG_FIFO_READ_DATA (0xE2) + +/* 0xE3 - 0xE7 Reserved */ + +#define REG_VBUS_ADDRESS_ACCESS1 (0xE8) +#define REG_VBUS_ADDRESS_ACCESS2 (0xE9) +#define REG_VBUS_ADDRESS_ACCESS3 (0xEA) + +/* 0xEB - 0xEF Reserved */ + +#define REG_INTERRUPT_RAW_STATUS0 (0xF0) +#define REG_INTERRUPT_RAW_STATUS1 (0xF1) +#define REG_INTERRUPT_STATUS0 (0xF2) +#define REG_INTERRUPT_STATUS1 (0xF3) +#define REG_INTERRUPT_MASK0 (0xF4) +#define REG_INTERRUPT_MASK1 (0xF5) +#define REG_INTERRUPT_CLEAR0 (0xF6) +#define REG_INTERRUPT_CLEAR1 (0xF7) + +/* 0xF8 - 0xFF Reserved */ + +/* + * Mask and bit definitions of TVP5146/47 registers + */ +/* The ID values we are looking for */ +#define TVP514X_CHIP_ID_MSB (0x51) +#define TVP5146_CHIP_ID_LSB (0x46) +#define TVP5147_CHIP_ID_LSB (0x47) + +#define VIDEO_STD_MASK (0x07) +#define VIDEO_STD_AUTO_SWITCH_BIT (0x00) +#define VIDEO_STD_NTSC_MJ_BIT (0x01) +#define VIDEO_STD_PAL_BDGHIN_BIT (0x02) +#define VIDEO_STD_PAL_M_BIT (0x03) +#define VIDEO_STD_PAL_COMBINATION_N_BIT (0x04) +#define VIDEO_STD_NTSC_4_43_BIT (0x05) +#define VIDEO_STD_SECAM_BIT (0x06) +#define VIDEO_STD_PAL_60_BIT (0x07) + +/* + * Status bit + */ +#define STATUS_TV_VCR_BIT (1<<0) +#define STATUS_HORZ_SYNC_LOCK_BIT (1<<1) +#define STATUS_VIRT_SYNC_LOCK_BIT (1<<2) +#define STATUS_CLR_SUBCAR_LOCK_BIT (1<<3) +#define STATUS_LOST_LOCK_DETECT_BIT (1<<4) +#define STATUS_FEILD_RATE_BIT (1<<5) +#define STATUS_LINE_ALTERNATING_BIT (1<<6) +#define STATUS_PEAK_WHITE_DETECT_BIT (1<<7) + +/* Tokens for register write */ +#define TOK_WRITE (0) /* token for write operation */ +#define TOK_TERM (1) /* terminating token */ +#define TOK_DELAY (2) /* delay token for reg list */ +#define TOK_SKIP (3) /* token to skip a register */ +/** + * struct tvp514x_reg - Structure for TVP5146/47 register initialization values + * @token - Token: TOK_WRITE, TOK_TERM etc.. + * @reg - Register offset + * @val - Register Value for TOK_WRITE or delay in ms for TOK_DELAY + */ +struct tvp514x_reg { + u8 token; + u8 reg; + u32 val; +}; + +/** + * struct tvp514x_init_seq - Structure for TVP5146/47/46M2/47M1 power up + * Sequence. + * @ no_regs - Number of registers to write for power up sequence. + * @ init_reg_seq - Array of registers and respective value to write. + */ +struct tvp514x_init_seq { + unsigned int no_regs; + const struct tvp514x_reg *init_reg_seq; +}; +#endif /* ifndef _TVP514X_REGS_H */ diff --git a/include/media/tvp514x.h b/include/media/tvp514x.h new file mode 100644 index 00000000000..5e7ee968c6d --- /dev/null +++ b/include/media/tvp514x.h @@ -0,0 +1,118 @@ +/* + * drivers/media/video/tvp514x.h + * + * Copyright (C) 2008 Texas Instruments Inc + * Author: Vaibhav Hiremath + * + * Contributors: + * Sivaraj R + * Brijesh R Jadav + * Hardik Shah + * Manjunath Hadli + * Karicheri Muralidharan + * + * 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. + * + */ + +#ifndef _TVP514X_H +#define _TVP514X_H + +/* + * Other macros + */ +#define TVP514X_MODULE_NAME "tvp514x" + +#define TVP514X_XCLK_BT656 (27000000) + +/* Number of pixels and number of lines per frame for different standards */ +#define NTSC_NUM_ACTIVE_PIXELS (720) +#define NTSC_NUM_ACTIVE_LINES (480) +#define PAL_NUM_ACTIVE_PIXELS (720) +#define PAL_NUM_ACTIVE_LINES (576) + +/** + * enum tvp514x_input - enum for different decoder input pin + * configuration. + */ +enum tvp514x_input { + /* + * CVBS input selection + */ + INPUT_CVBS_VI1A = 0x0, + INPUT_CVBS_VI1B, + INPUT_CVBS_VI1C, + INPUT_CVBS_VI2A = 0x04, + INPUT_CVBS_VI2B, + INPUT_CVBS_VI2C, + INPUT_CVBS_VI3A = 0x08, + INPUT_CVBS_VI3B, + INPUT_CVBS_VI3C, + INPUT_CVBS_VI4A = 0x0C, + /* + * S-Video input selection + */ + INPUT_SVIDEO_VI2A_VI1A = 0x44, + INPUT_SVIDEO_VI2B_VI1B, + INPUT_SVIDEO_VI2C_VI1C, + INPUT_SVIDEO_VI2A_VI3A = 0x54, + INPUT_SVIDEO_VI2B_VI3B, + INPUT_SVIDEO_VI2C_VI3C, + INPUT_SVIDEO_VI4A_VI1A = 0x4C, + INPUT_SVIDEO_VI4A_VI1B, + INPUT_SVIDEO_VI4A_VI1C, + INPUT_SVIDEO_VI4A_VI3A = 0x5C, + INPUT_SVIDEO_VI4A_VI3B, + INPUT_SVIDEO_VI4A_VI3C, + + /* Need to add entries for + * RGB, YPbPr and SCART. + */ + INPUT_INVALID +}; + +/** + * enum tvp514x_output - enum for output format + * supported. + * + */ +enum tvp514x_output { + OUTPUT_10BIT_422_EMBEDDED_SYNC = 0, + OUTPUT_20BIT_422_SEPERATE_SYNC, + OUTPUT_10BIT_422_SEPERATE_SYNC = 3, + OUTPUT_INVALID +}; + +/** + * struct tvp514x_platform_data - Platform data values and access functions. + * @power_set: Power state access function, zero is off, non-zero is on. + * @ifparm: Interface parameters access function. + * @priv_data_set: Device private data (pointer) access function. + * @clk_polarity: Clock polarity of the current interface. + * @ hs_polarity: HSYNC Polarity configuration for current interface. + * @ vs_polarity: VSYNC Polarity configuration for current interface. + */ +struct tvp514x_platform_data { + char *master; + int (*power_set) (enum v4l2_power on); + int (*ifparm) (struct v4l2_ifparm *p); + int (*priv_data_set) (void *); + /* Interface control params */ + bool clk_polarity; + bool hs_polarity; + bool vs_polarity; +}; + + +#endif /* ifndef _TVP514X_H */ -- cgit v1.2.3-70-g09d2 From 2a1fcdf08230522bd5024f91da24aaa6e8d81f59 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sat, 29 Nov 2008 21:36:58 -0300 Subject: V4L/DVB (9820): v4l2: add v4l2_device and v4l2_subdev structs to the v4l2 framework. Start implementing a proper v4l2 framework as discussed during the Linux Plumbers Conference 2008. Introduces v4l2_device (for device instances) and v4l2_subdev (representing sub-device instances). Signed-off-by: Hans Verkuil Reviewed-by: Laurent Pinchart Reviewed-by: Guennadi Liakhovetski Reviewed-by: Andy Walls Reviewed-by: David Brownell Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/v4l2-framework.txt | 362 +++++++++++++++++++++++++++ drivers/media/video/Makefile | 2 +- drivers/media/video/v4l2-device.c | 86 +++++++ drivers/media/video/v4l2-subdev.c | 108 ++++++++ include/media/v4l2-device.h | 109 ++++++++ include/media/v4l2-subdev.h | 188 ++++++++++++++ 6 files changed, 854 insertions(+), 1 deletion(-) create mode 100644 Documentation/video4linux/v4l2-framework.txt create mode 100644 drivers/media/video/v4l2-device.c create mode 100644 drivers/media/video/v4l2-subdev.c create mode 100644 include/media/v4l2-device.h create mode 100644 include/media/v4l2-subdev.h (limited to 'drivers/media/video/Makefile') diff --git a/Documentation/video4linux/v4l2-framework.txt b/Documentation/video4linux/v4l2-framework.txt new file mode 100644 index 00000000000..60eaf54e7ef --- /dev/null +++ b/Documentation/video4linux/v4l2-framework.txt @@ -0,0 +1,362 @@ +Overview of the V4L2 driver framework +===================================== + +This text documents the various structures provided by the V4L2 framework and +their relationships. + + +Introduction +------------ + +The V4L2 drivers tend to be very complex due to the complexity of the +hardware: most devices have multiple ICs, export multiple device nodes in +/dev, and create also non-V4L2 devices such as DVB, ALSA, FB, I2C and input +(IR) devices. + +Especially the fact that V4L2 drivers have to setup supporting ICs to +do audio/video muxing/encoding/decoding makes it more complex than most. +Usually these ICs are connected to the main bridge driver through one or +more I2C busses, but other busses can also be used. Such devices are +called 'sub-devices'. + +For a long time the framework was limited to the video_device struct for +creating V4L device nodes and video_buf for handling the video buffers +(note that this document does not discuss the video_buf framework). + +This meant that all drivers had to do the setup of device instances and +connecting to sub-devices themselves. Some of this is quite complicated +to do right and many drivers never did do it correctly. + +There is also a lot of common code that could never be refactored due to +the lack of a framework. + +So this framework sets up the basic building blocks that all drivers +need and this same framework should make it much easier to refactor +common code into utility functions shared by all drivers. + + +Structure of a driver +--------------------- + +All drivers have the following structure: + +1) A struct for each device instance containing the device state. + +2) A way of initializing and commanding sub-devices (if any). + +3) Creating V4L2 device nodes (/dev/videoX, /dev/vbiX, /dev/radioX and + /dev/vtxX) and keeping track of device-node specific data. + +4) Filehandle-specific structs containing per-filehandle data. + +This is a rough schematic of how it all relates: + + device instances + | + +-sub-device instances + | + \-V4L2 device nodes + | + \-filehandle instances + + +Structure of the framework +-------------------------- + +The framework closely resembles the driver structure: it has a v4l2_device +struct for the device instance data, a v4l2_subdev struct to refer to +sub-device instances, the video_device struct stores V4L2 device node data +and in the future a v4l2_fh struct will keep track of filehandle instances +(this is not yet implemented). + + +struct v4l2_device +------------------ + +Each device instance is represented by a struct v4l2_device (v4l2-device.h). +Very simple devices can just allocate this struct, but most of the time you +would embed this struct inside a larger struct. + +You must register the device instance: + + v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); + +Registration will initialize the v4l2_device struct and link dev->driver_data +to v4l2_dev. Registration will also set v4l2_dev->name to a value derived from +dev (driver name followed by the bus_id, to be precise). You may change the +name after registration if you want. + +You unregister with: + + v4l2_device_unregister(struct v4l2_device *v4l2_dev); + +Unregistering will also automatically unregister all subdevs from the device. + +Sometimes you need to iterate over all devices registered by a specific +driver. This is usually the case if multiple device drivers use the same +hardware. E.g. the ivtvfb driver is a framebuffer driver that uses the ivtv +hardware. The same is true for alsa drivers for example. + +You can iterate over all registered devices as follows: + +static int callback(struct device *dev, void *p) +{ + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + + /* test if this device was inited */ + if (v4l2_dev == NULL) + return 0; + ... + return 0; +} + +int iterate(void *p) +{ + struct device_driver *drv; + int err; + + /* Find driver 'ivtv' on the PCI bus. + pci_bus_type is a global. For USB busses use usb_bus_type. */ + drv = driver_find("ivtv", &pci_bus_type); + /* iterate over all ivtv device instances */ + err = driver_for_each_device(drv, NULL, p, callback); + put_driver(drv); + return err; +} + +Sometimes you need to keep a running counter of the device instance. This is +commonly used to map a device instance to an index of a module option array. + +The recommended approach is as follows: + +static atomic_t drv_instance = ATOMIC_INIT(0); + +static int __devinit drv_probe(struct pci_dev *dev, + const struct pci_device_id *pci_id) +{ + ... + state->instance = atomic_inc_return(&drv_instance) - 1; +} + + +struct v4l2_subdev +------------------ + +Many drivers need to communicate with sub-devices. These devices can do all +sort of tasks, but most commonly they handle audio and/or video muxing, +encoding or decoding. For webcams common sub-devices are sensors and camera +controllers. + +Usually these are I2C devices, but not necessarily. In order to provide the +driver with a consistent interface to these sub-devices the v4l2_subdev struct +(v4l2-subdev.h) was created. + +Each sub-device driver must have a v4l2_subdev struct. This struct can be +stand-alone for simple sub-devices or it might be embedded in a larger struct +if more state information needs to be stored. Usually there is a low-level +device struct (e.g. i2c_client) that contains the device data as setup +by the kernel. It is recommended to store that pointer in the private +data of v4l2_subdev using v4l2_set_subdevdata(). That makes it easy to go +from a v4l2_subdev to the actual low-level bus-specific device data. + +You also need a way to go from the low-level struct to v4l2_subdev. For the +common i2c_client struct the i2c_set_clientdata() call is used to store a +v4l2_subdev pointer, for other busses you may have to use other methods. + +From the bridge driver perspective you load the sub-device module and somehow +obtain the v4l2_subdev pointer. For i2c devices this is easy: you call +i2c_get_clientdata(). For other busses something similar needs to be done. +Helper functions exists for sub-devices on an I2C bus that do most of this +tricky work for you. + +Each v4l2_subdev contains function pointers that sub-device drivers can +implement (or leave NULL if it is not applicable). Since sub-devices can do +so many different things and you do not want to end up with a huge ops struct +of which only a handful of ops are commonly implemented, the function pointers +are sorted according to category and each category has its own ops struct. + +The top-level ops struct contains pointers to the category ops structs, which +may be NULL if the subdev driver does not support anything from that category. + +It looks like this: + +struct v4l2_subdev_core_ops { + int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_chip_ident *chip); + int (*log_status)(struct v4l2_subdev *sd); + int (*init)(struct v4l2_subdev *sd, u32 val); + ... +}; + +struct v4l2_subdev_tuner_ops { + ... +}; + +struct v4l2_subdev_audio_ops { + ... +}; + +struct v4l2_subdev_video_ops { + ... +}; + +struct v4l2_subdev_ops { + const struct v4l2_subdev_core_ops *core; + const struct v4l2_subdev_tuner_ops *tuner; + const struct v4l2_subdev_audio_ops *audio; + const struct v4l2_subdev_video_ops *video; +}; + +The core ops are common to all subdevs, the other categories are implemented +depending on the sub-device. E.g. a video device is unlikely to support the +audio ops and vice versa. + +This setup limits the number of function pointers while still making it easy +to add new ops and categories. + +A sub-device driver initializes the v4l2_subdev struct using: + + v4l2_subdev_init(subdev, &ops); + +Afterwards you need to initialize subdev->name with a unique name and set the +module owner. This is done for you if you use the i2c helper functions. + +A device (bridge) driver needs to register the v4l2_subdev with the +v4l2_device: + + int err = v4l2_device_register_subdev(device, subdev); + +This can fail if the subdev module disappeared before it could be registered. +After this function was called successfully the subdev->dev field points to +the v4l2_device. + +You can unregister a sub-device using: + + v4l2_device_unregister_subdev(subdev); + +Afterwards the subdev module can be unloaded and subdev->dev == NULL. + +You can call an ops function either directly: + + err = subdev->ops->core->g_chip_ident(subdev, &chip); + +but it is better and easier to use this macro: + + err = v4l2_subdev_call(subdev, core, g_chip_ident, &chip); + +The macro will to the right NULL pointer checks and returns -ENODEV if subdev +is NULL, -ENOIOCTLCMD if either subdev->core or subdev->core->g_chip_ident is +NULL, or the actual result of the subdev->ops->core->g_chip_ident ops. + +It is also possible to call all or a subset of the sub-devices: + + v4l2_device_call_all(dev, 0, core, g_chip_ident, &chip); + +Any subdev that does not support this ops is skipped and error results are +ignored. If you want to check for errors use this: + + err = v4l2_device_call_until_err(dev, 0, core, g_chip_ident, &chip); + +Any error except -ENOIOCTLCMD will exit the loop with that error. If no +errors (except -ENOIOCTLCMD) occured, then 0 is returned. + +The second argument to both calls is a group ID. If 0, then all subdevs are +called. If non-zero, then only those whose group ID match that value will +be called. Before a bridge driver registers a subdev it can set subdev->grp_id +to whatever value it wants (it's 0 by default). This value is owned by the +bridge driver and the sub-device driver will never modify or use it. + +The group ID gives the bridge driver more control how callbacks are called. +For example, there may be multiple audio chips on a board, each capable of +changing the volume. But usually only one will actually be used when the +user want to change the volume. You can set the group ID for that subdev to +e.g. AUDIO_CONTROLLER and specify that as the group ID value when calling +v4l2_device_call_all(). That ensures that it will only go to the subdev +that needs it. + +The advantage of using v4l2_subdev is that it is a generic struct and does +not contain any knowledge about the underlying hardware. So a driver might +contain several subdevs that use an I2C bus, but also a subdev that is +controlled through GPIO pins. This distinction is only relevant when setting +up the device, but once the subdev is registered it is completely transparent. + + +I2C sub-device drivers +---------------------- + +Since these drivers are so common, special helper functions are available to +ease the use of these drivers (v4l2-common.h). + +The recommended method of adding v4l2_subdev support to an I2C driver is to +embed the v4l2_subdev struct into the state struct that is created for each +I2C device instance. Very simple devices have no state struct and in that case +you can just create a v4l2_subdev directly. + +A typical state struct would look like this (where 'chipname' is replaced by +the name of the chip): + +struct chipname_state { + struct v4l2_subdev sd; + ... /* additional state fields */ +}; + +Initialize the v4l2_subdev struct as follows: + + v4l2_i2c_subdev_init(&state->sd, client, subdev_ops); + +This function will fill in all the fields of v4l2_subdev and ensure that the +v4l2_subdev and i2c_client both point to one another. + +You should also add a helper inline function to go from a v4l2_subdev pointer +to a chipname_state struct: + +static inline struct chipname_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct chipname_state, sd); +} + +Use this to go from the v4l2_subdev struct to the i2c_client struct: + + struct i2c_client *client = v4l2_get_subdevdata(sd); + +And this to go from an i2c_client to a v4l2_subdev struct: + + struct v4l2_subdev *sd = i2c_get_clientdata(client); + +Finally you need to make a command function to make driver->command() +call the right subdev_ops functions: + +static int subdev_command(struct i2c_client *client, unsigned cmd, void *arg) +{ + return v4l2_subdev_command(i2c_get_clientdata(client), cmd, arg); +} + +If driver->command is never used then you can leave this out. Eventually the +driver->command usage should be removed from v4l. + +Make sure to call v4l2_device_unregister_subdev(sd) when the remove() callback +is called. This will unregister the sub-device from the bridge driver. It is +safe to call this even if the sub-device was never registered. + + +The bridge driver also has some helper functions it can use: + +struct v4l2_subdev *sd = v4l2_i2c_new_subdev(adapter, "module_foo", "chipid", 0x36); + +This loads the given module (can be NULL if no module needs to be loaded) and +calls i2c_new_device() with the given i2c_adapter and chip/address arguments. +If all goes well, then it registers the subdev with the v4l2_device. It gets +the v4l2_device by calling i2c_get_adapdata(adapter), so you should make sure +that adapdata is set to v4l2_device when you setup the i2c_adapter in your +driver. + +You can also use v4l2_i2c_new_probed_subdev() which is very similar to +v4l2_i2c_new_subdev(), except that it has an array of possible I2C addresses +that it should probe. Internally it calls i2c_new_probed_device(). + +Both functions return NULL if something went wrong. + + +struct video_device +------------------- + +Not yet documented. diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 492ab3dce71..84a2be0cbbe 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -10,7 +10,7 @@ stkwebcam-objs := stk-webcam.o stk-sensor.o omap2cam-objs := omap24xxcam.o omap24xxcam-dma.o -videodev-objs := v4l2-dev.o v4l2-ioctl.o +videodev-objs := v4l2-dev.o v4l2-ioctl.o v4l2-device.o v4l2-subdev.o obj-$(CONFIG_VIDEO_DEV) += videodev.o v4l2-compat-ioctl32.o v4l2-int-device.o diff --git a/drivers/media/video/v4l2-device.c b/drivers/media/video/v4l2-device.c new file mode 100644 index 00000000000..9eefde03159 --- /dev/null +++ b/drivers/media/video/v4l2-device.c @@ -0,0 +1,86 @@ +/* + V4L2 device support. + + Copyright (C) 2008 Hans Verkuil + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev) +{ + if (dev == NULL || v4l2_dev == NULL) + return -EINVAL; + /* Warn if we apparently re-register a device */ + WARN_ON(dev_get_drvdata(dev)); + INIT_LIST_HEAD(&v4l2_dev->subdevs); + spin_lock_init(&v4l2_dev->lock); + v4l2_dev->dev = dev; + snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s", + dev->driver->name, dev->bus_id); + dev_set_drvdata(dev, v4l2_dev); + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_device_register); + +void v4l2_device_unregister(struct v4l2_device *v4l2_dev) +{ + struct v4l2_subdev *sd, *next; + + if (v4l2_dev == NULL || v4l2_dev->dev == NULL) + return; + dev_set_drvdata(v4l2_dev->dev, NULL); + /* unregister subdevs */ + list_for_each_entry_safe(sd, next, &v4l2_dev->subdevs, list) + v4l2_device_unregister_subdev(sd); + + v4l2_dev->dev = NULL; +} +EXPORT_SYMBOL_GPL(v4l2_device_unregister); + +int v4l2_device_register_subdev(struct v4l2_device *dev, struct v4l2_subdev *sd) +{ + /* Check for valid input */ + if (dev == NULL || sd == NULL || !sd->name[0]) + return -EINVAL; + /* Warn if we apparently re-register a subdev */ + WARN_ON(sd->dev); + if (!try_module_get(sd->owner)) + return -ENODEV; + sd->dev = dev; + spin_lock(&dev->lock); + list_add_tail(&sd->list, &dev->subdevs); + spin_unlock(&dev->lock); + return 0; +} +EXPORT_SYMBOL_GPL(v4l2_device_register_subdev); + +void v4l2_device_unregister_subdev(struct v4l2_subdev *sd) +{ + /* return if it isn't registered */ + if (sd == NULL || sd->dev == NULL) + return; + spin_lock(&sd->dev->lock); + list_del(&sd->list); + spin_unlock(&sd->dev->lock); + sd->dev = NULL; + module_put(sd->owner); +} +EXPORT_SYMBOL_GPL(v4l2_device_unregister_subdev); diff --git a/drivers/media/video/v4l2-subdev.c b/drivers/media/video/v4l2-subdev.c new file mode 100644 index 00000000000..fe1f01c970a --- /dev/null +++ b/drivers/media/video/v4l2-subdev.c @@ -0,0 +1,108 @@ +/* + V4L2 sub-device support. + + Copyright (C) 2008 Hans Verkuil + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +int v4l2_subdev_command(struct v4l2_subdev *sd, unsigned cmd, void *arg) +{ + switch (cmd) { + case VIDIOC_QUERYCTRL: + return v4l2_subdev_call(sd, core, querymenu, arg); + case VIDIOC_G_CTRL: + return v4l2_subdev_call(sd, core, g_ctrl, arg); + case VIDIOC_S_CTRL: + return v4l2_subdev_call(sd, core, s_ctrl, arg); + case VIDIOC_QUERYMENU: + return v4l2_subdev_call(sd, core, queryctrl, arg); + case VIDIOC_LOG_STATUS: + return v4l2_subdev_call(sd, core, log_status); + case VIDIOC_G_CHIP_IDENT: + return v4l2_subdev_call(sd, core, g_chip_ident, arg); + case VIDIOC_INT_S_STANDBY: + return v4l2_subdev_call(sd, core, s_standby, *(u32 *)arg); + case VIDIOC_INT_RESET: + return v4l2_subdev_call(sd, core, reset, *(u32 *)arg); + case VIDIOC_INT_S_GPIO: + return v4l2_subdev_call(sd, core, s_gpio, *(u32 *)arg); + case VIDIOC_INT_INIT: + return v4l2_subdev_call(sd, core, init, *(u32 *)arg); +#ifdef CONFIG_VIDEO_ADV_DEBUG + case VIDIOC_DBG_G_REGISTER: + return v4l2_subdev_call(sd, core, g_register, arg); + case VIDIOC_DBG_S_REGISTER: + return v4l2_subdev_call(sd, core, s_register, arg); +#endif + + case VIDIOC_INT_S_TUNER_MODE: + return v4l2_subdev_call(sd, tuner, s_mode, *(enum v4l2_tuner_type *)arg); + case AUDC_SET_RADIO: + return v4l2_subdev_call(sd, tuner, s_radio); + case VIDIOC_S_TUNER: + return v4l2_subdev_call(sd, tuner, s_tuner, arg); + case VIDIOC_G_TUNER: + return v4l2_subdev_call(sd, tuner, g_tuner, arg); + case VIDIOC_S_STD: + return v4l2_subdev_call(sd, tuner, s_std, *(v4l2_std_id *)arg); + case VIDIOC_S_FREQUENCY: + return v4l2_subdev_call(sd, tuner, s_frequency, arg); + case VIDIOC_G_FREQUENCY: + return v4l2_subdev_call(sd, tuner, g_frequency, arg); + case TUNER_SET_TYPE_ADDR: + return v4l2_subdev_call(sd, tuner, s_type_addr, arg); + case TUNER_SET_CONFIG: + return v4l2_subdev_call(sd, tuner, s_config, arg); + + case VIDIOC_INT_AUDIO_CLOCK_FREQ: + return v4l2_subdev_call(sd, audio, s_clock_freq, *(u32 *)arg); + case VIDIOC_INT_S_AUDIO_ROUTING: + return v4l2_subdev_call(sd, audio, s_routing, arg); + case VIDIOC_INT_I2S_CLOCK_FREQ: + return v4l2_subdev_call(sd, audio, s_i2s_clock_freq, *(u32 *)arg); + + case VIDIOC_INT_S_VIDEO_ROUTING: + return v4l2_subdev_call(sd, video, s_routing, arg); + case VIDIOC_INT_S_CRYSTAL_FREQ: + return v4l2_subdev_call(sd, video, s_crystal_freq, arg); + case VIDIOC_INT_DECODE_VBI_LINE: + return v4l2_subdev_call(sd, video, decode_vbi_line, arg); + case VIDIOC_INT_S_VBI_DATA: + return v4l2_subdev_call(sd, video, s_vbi_data, arg); + case VIDIOC_INT_G_VBI_DATA: + return v4l2_subdev_call(sd, video, g_vbi_data, arg); + case VIDIOC_S_FMT: + return v4l2_subdev_call(sd, video, s_fmt, arg); + case VIDIOC_G_FMT: + return v4l2_subdev_call(sd, video, g_fmt, arg); + case VIDIOC_INT_S_STD_OUTPUT: + return v4l2_subdev_call(sd, video, s_std_output, *(v4l2_std_id *)arg); + case VIDIOC_STREAMON: + return v4l2_subdev_call(sd, video, s_stream, 1); + case VIDIOC_STREAMOFF: + return v4l2_subdev_call(sd, video, s_stream, 0); + + default: + return v4l2_subdev_call(sd, core, ioctl, cmd, arg); + } +} +EXPORT_SYMBOL_GPL(v4l2_subdev_command); diff --git a/include/media/v4l2-device.h b/include/media/v4l2-device.h new file mode 100644 index 00000000000..97b283a0428 --- /dev/null +++ b/include/media/v4l2-device.h @@ -0,0 +1,109 @@ +/* + V4L2 device support header. + + Copyright (C) 2008 Hans Verkuil + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _V4L2_DEVICE_H +#define _V4L2_DEVICE_H + +#include + +/* Each instance of a V4L2 device should create the v4l2_device struct, + either stand-alone or embedded in a larger struct. + + It allows easy access to sub-devices (see v4l2-subdev.h) and provides + basic V4L2 device-level support. + */ + +#define V4L2_DEVICE_NAME_SIZE (BUS_ID_SIZE + 16) + +struct v4l2_device { + /* dev->driver_data points to this struct */ + struct device *dev; + /* used to keep track of the registered subdevs */ + struct list_head subdevs; + /* lock this struct; can be used by the driver as well if this + struct is embedded into a larger struct. */ + spinlock_t lock; + /* unique device name, by default the driver name + bus ID */ + char name[V4L2_DEVICE_NAME_SIZE]; +}; + +/* Initialize v4l2_dev and make dev->driver_data point to v4l2_dev */ +int __must_check v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev); +/* Set v4l2_dev->dev->driver_data to NULL and unregister all sub-devices */ +void v4l2_device_unregister(struct v4l2_device *v4l2_dev); + +/* Register a subdev with a v4l2 device. While registered the subdev module + is marked as in-use. An error is returned if the module is no longer + loaded when you attempt to register it. */ +int __must_check v4l2_device_register_subdev(struct v4l2_device *dev, struct v4l2_subdev *sd); +/* Unregister a subdev with a v4l2 device. Can also be called if the subdev + wasn't registered. In that case it will do nothing. */ +void v4l2_device_unregister_subdev(struct v4l2_subdev *sd); + +/* Iterate over all subdevs. */ +#define v4l2_device_for_each_subdev(sd, dev) \ + list_for_each_entry(sd, &(dev)->subdevs, list) + +/* Call the specified callback for all subdevs matching the condition. + Ignore any errors. Note that you cannot add or delete a subdev + while walking the subdevs list. */ +#define __v4l2_device_call_subdevs(dev, cond, o, f, args...) \ + do { \ + struct v4l2_subdev *sd; \ + \ + list_for_each_entry(sd, &(dev)->subdevs, list) \ + if ((cond) && sd->ops->o && sd->ops->o->f) \ + sd->ops->o->f(sd , ##args); \ + } while (0) + +/* Call the specified callback for all subdevs matching the condition. + If the callback returns an error other than 0 or -ENOIOCTLCMD, then + return with that error code. Note that you cannot add or delete a + subdev while walking the subdevs list. */ +#define __v4l2_device_call_subdevs_until_err(dev, cond, o, f, args...) \ +({ \ + struct v4l2_subdev *sd; \ + int err = 0; \ + \ + list_for_each_entry(sd, &(dev)->subdevs, list) { \ + if ((cond) && sd->ops->o && sd->ops->o->f) \ + err = sd->ops->o->f(sd , ##args); \ + if (err && err != -ENOIOCTLCMD) \ + break; \ + } \ + (err == -ENOIOCTLCMD) ? 0 : err; \ +}) + +/* Call the specified callback for all subdevs matching grp_id (if 0, then + match them all). Ignore any errors. Note that you cannot add or delete + a subdev while walking the subdevs list. */ +#define v4l2_device_call_all(dev, grp_id, o, f, args...) \ + __v4l2_device_call_subdevs(dev, \ + !(grp_id) || sd->grp_id == (grp_id), o, f , ##args) + +/* Call the specified callback for all subdevs matching grp_id (if 0, then + match them all). If the callback returns an error other than 0 or + -ENOIOCTLCMD, then return with that error code. Note that you cannot + add or delete a subdev while walking the subdevs list. */ +#define v4l2_device_call_until_err(dev, grp_id, o, f, args...) \ + __v4l2_device_call_subdevs_until_err(dev, \ + !(grp_id) || sd->grp_id == (grp_id), o, f , ##args) + +#endif diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h new file mode 100644 index 00000000000..bc9e0fbf282 --- /dev/null +++ b/include/media/v4l2-subdev.h @@ -0,0 +1,188 @@ +/* + V4L2 sub-device support header. + + Copyright (C) 2008 Hans Verkuil + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _V4L2_SUBDEV_H +#define _V4L2_SUBDEV_H + +#include + +struct v4l2_device; +struct v4l2_subdev; +struct tuner_setup; + +/* Sub-devices are devices that are connected somehow to the main bridge + device. These devices are usually audio/video muxers/encoders/decoders or + sensors and webcam controllers. + + Usually these devices are controlled through an i2c bus, but other busses + may also be used. + + The v4l2_subdev struct provides a way of accessing these devices in a + generic manner. Most operations that these sub-devices support fall in + a few categories: core ops, audio ops, video ops and tuner ops. + + More categories can be added if needed, although this should remain a + limited set (no more than approx. 8 categories). + + Each category has its own set of ops that subdev drivers can implement. + + A subdev driver can leave the pointer to the category ops NULL if + it does not implement them (e.g. an audio subdev will generally not + implement the video category ops). The exception is the core category: + this must always be present. + + These ops are all used internally so it is no problem to change, remove + or add ops or move ops from one to another category. Currently these + ops are based on the original ioctls, but since ops are not limited to + one argument there is room for improvement here once all i2c subdev + drivers are converted to use these ops. + */ + +/* Core ops: it is highly recommended to implement at least these ops: + + g_chip_ident + log_status + g_register + s_register + + This provides basic debugging support. + + The ioctl ops is meant for generic ioctl-like commands. Depending on + the use-case it might be better to use subdev-specific ops (currently + not yet implemented) since ops provide proper type-checking. + */ +struct v4l2_subdev_core_ops { + int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_chip_ident *chip); + int (*log_status)(struct v4l2_subdev *sd); + int (*init)(struct v4l2_subdev *sd, u32 val); + int (*s_standby)(struct v4l2_subdev *sd, u32 standby); + int (*reset)(struct v4l2_subdev *sd, u32 val); + int (*s_gpio)(struct v4l2_subdev *sd, u32 val); + int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc); + int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl); + int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl); + int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm); + int (*ioctl)(struct v4l2_subdev *sd, int cmd, void *arg); +#ifdef CONFIG_VIDEO_ADV_DEBUG + int (*g_register)(struct v4l2_subdev *sd, struct v4l2_register *reg); + int (*s_register)(struct v4l2_subdev *sd, struct v4l2_register *reg); +#endif +}; + +struct v4l2_subdev_tuner_ops { + int (*s_mode)(struct v4l2_subdev *sd, enum v4l2_tuner_type); + int (*s_radio)(struct v4l2_subdev *sd); + int (*s_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq); + int (*g_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq); + int (*g_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt); + int (*s_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt); + int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm); + int (*s_type_addr)(struct v4l2_subdev *sd, struct tuner_setup *type); + int (*s_config)(struct v4l2_subdev *sd, const struct v4l2_priv_tun_config *config); +}; + +struct v4l2_subdev_audio_ops { + int (*s_clock_freq)(struct v4l2_subdev *sd, u32 freq); + int (*s_i2s_clock_freq)(struct v4l2_subdev *sd, u32 freq); + int (*s_routing)(struct v4l2_subdev *sd, const struct v4l2_routing *route); +}; + +struct v4l2_subdev_video_ops { + int (*s_routing)(struct v4l2_subdev *sd, const struct v4l2_routing *route); + int (*s_crystal_freq)(struct v4l2_subdev *sd, struct v4l2_crystal_freq *freq); + int (*decode_vbi_line)(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi_line); + int (*s_vbi_data)(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *vbi_data); + int (*g_vbi_data)(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_data *vbi_data); + int (*s_std_output)(struct v4l2_subdev *sd, v4l2_std_id std); + int (*s_stream)(struct v4l2_subdev *sd, int enable); + int (*s_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt); + int (*g_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt); +}; + +struct v4l2_subdev_ops { + const struct v4l2_subdev_core_ops *core; + const struct v4l2_subdev_tuner_ops *tuner; + const struct v4l2_subdev_audio_ops *audio; + const struct v4l2_subdev_video_ops *video; +}; + +#define V4L2_SUBDEV_NAME_SIZE 32 + +/* Each instance of a subdev driver should create this struct, either + stand-alone or embedded in a larger struct. + */ +struct v4l2_subdev { + struct list_head list; + struct module *owner; + struct v4l2_device *dev; + const struct v4l2_subdev_ops *ops; + /* name must be unique */ + char name[V4L2_SUBDEV_NAME_SIZE]; + /* can be used to group similar subdevs, value is driver-specific */ + u32 grp_id; + /* pointer to private data */ + void *priv; +}; + +static inline void v4l2_set_subdevdata(struct v4l2_subdev *sd, void *p) +{ + sd->priv = p; +} + +static inline void *v4l2_get_subdevdata(const struct v4l2_subdev *sd) +{ + return sd->priv; +} + +/* Convert an ioctl-type command to the proper v4l2_subdev_ops function call. + This is used by subdev modules that can be called by both old-style ioctl + commands and through the v4l2_subdev_ops. + + The ioctl API of the subdev driver can call this function to call the + right ops based on the ioctl cmd and arg. + + Once all subdev drivers have been converted and all drivers no longer + use the ioctl interface, then this function can be removed. + */ +int v4l2_subdev_command(struct v4l2_subdev *sd, unsigned cmd, void *arg); + +static inline void v4l2_subdev_init(struct v4l2_subdev *sd, + const struct v4l2_subdev_ops *ops) +{ + INIT_LIST_HEAD(&sd->list); + /* ops->core MUST be set */ + BUG_ON(!ops || !ops->core); + sd->ops = ops; + sd->dev = NULL; + sd->name[0] = '\0'; + sd->grp_id = 0; + sd->priv = NULL; +} + +/* Call an ops of a v4l2_subdev, doing the right checks against + NULL pointers. + + Example: err = v4l2_subdev_call(sd, core, g_chip_ident, &chip); + */ +#define v4l2_subdev_call(sd, o, f, args...) \ + (!(sd) ? -ENODEV : (((sd) && (sd)->ops->o && (sd)->ops->o->f) ? \ + (sd)->ops->o->f((sd) , ##args) : -ENOIOCTLCMD)) + +#endif -- cgit v1.2.3-70-g09d2 From ed922a892e535c14035210b5be328af1f49561c8 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Mon, 29 Dec 2008 06:04:06 -0300 Subject: V4L/DVB (10094): Add tw9910 driver This patch adds tw9910 driver that use soc_camera framework. It was tested on SH Migo-r board and mplayer. create mode 100644 drivers/media/video/tw9910.c create mode 100644 include/media/tw9910.h Signed-off-by: Kuninori Morimoto Signed-off-by: Guennadi Liakhovetski Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Kconfig | 6 + drivers/media/video/Makefile | 1 + drivers/media/video/tw9910.c | 941 ++++++++++++++++++++++++++++++++++++++++ include/media/tw9910.h | 39 ++ include/media/v4l2-chip-ident.h | 3 + 5 files changed, 990 insertions(+) create mode 100644 drivers/media/video/tw9910.c create mode 100644 include/media/tw9910.h (limited to 'drivers/media/video/Makefile') diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index da538d9ca30..9785eeb1ed4 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -755,6 +755,12 @@ config MT9V022_PCA9536_SWITCH Select this if your MT9V022 camera uses a PCA9536 I2C GPIO extender to switch between 8 and 10 bit datawidth modes +config SOC_CAMERA_TW9910 + tristate "tw9910 support" + depends on SOC_CAMERA && I2C + help + This is a tw9910 video driver + config SOC_CAMERA_PLATFORM tristate "platform camera support" depends on SOC_CAMERA diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 84a2be0cbbe..40c69afcde7 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -140,6 +140,7 @@ obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o obj-$(CONFIG_SOC_CAMERA_PLATFORM) += soc_camera_platform.o +obj-$(CONFIG_SOC_CAMERA_TW9910) += tw9910.o obj-$(CONFIG_VIDEO_AU0828) += au0828/ diff --git a/drivers/media/video/tw9910.c b/drivers/media/video/tw9910.c new file mode 100644 index 00000000000..75cfde02d02 --- /dev/null +++ b/drivers/media/video/tw9910.c @@ -0,0 +1,941 @@ +/* + * tw9910 Video Driver + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov772x driver, + * + * Copyright (C) 2008 Kuninori Morimoto + * Copyright 2006-7 Jonathan Corbet + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski + * + * This program 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GET_ID(val) ((val & 0xF8) >> 3) +#define GET_ReV(val) (val & 0x07) + +/* + * register offset + */ +#define ID 0x00 /* Product ID Code Register */ +#define STATUS1 0x01 /* Chip Status Register I */ +#define INFORM 0x02 /* Input Format */ +#define OPFORM 0x03 /* Output Format Control Register */ +#define DLYCTR 0x04 /* Hysteresis and HSYNC Delay Control */ +#define OUTCTR1 0x05 /* Output Control I */ +#define ACNTL1 0x06 /* Analog Control Register 1 */ +#define CROP_HI 0x07 /* Cropping Register, High */ +#define VDELAY_LO 0x08 /* Vertical Delay Register, Low */ +#define VACTIVE_LO 0x09 /* Vertical Active Register, Low */ +#define HDELAY_LO 0x0A /* Horizontal Delay Register, Low */ +#define HACTIVE_LO 0x0B /* Horizontal Active Register, Low */ +#define CNTRL1 0x0C /* Control Register I */ +#define VSCALE_LO 0x0D /* Vertical Scaling Register, Low */ +#define SCALE_HI 0x0E /* Scaling Register, High */ +#define HSCALE_LO 0x0F /* Horizontal Scaling Register, Low */ +#define BRIGHT 0x10 /* BRIGHTNESS Control Register */ +#define CONTRAST 0x11 /* CONTRAST Control Register */ +#define SHARPNESS 0x12 /* SHARPNESS Control Register I */ +#define SAT_U 0x13 /* Chroma (U) Gain Register */ +#define SAT_V 0x14 /* Chroma (V) Gain Register */ +#define HUE 0x15 /* Hue Control Register */ +#define CORING1 0x17 +#define CORING2 0x18 /* Coring and IF compensation */ +#define VBICNTL 0x19 /* VBI Control Register */ +#define ACNTL2 0x1A /* Analog Control 2 */ +#define OUTCTR2 0x1B /* Output Control 2 */ +#define SDT 0x1C /* Standard Selection */ +#define SDTR 0x1D /* Standard Recognition */ +#define TEST 0x1F /* Test Control Register */ +#define CLMPG 0x20 /* Clamping Gain */ +#define IAGC 0x21 /* Individual AGC Gain */ +#define AGCGAIN 0x22 /* AGC Gain */ +#define PEAKWT 0x23 /* White Peak Threshold */ +#define CLMPL 0x24 /* Clamp level */ +#define SYNCT 0x25 /* Sync Amplitude */ +#define MISSCNT 0x26 /* Sync Miss Count Register */ +#define PCLAMP 0x27 /* Clamp Position Register */ +#define VCNTL1 0x28 /* Vertical Control I */ +#define VCNTL2 0x29 /* Vertical Control II */ +#define CKILL 0x2A /* Color Killer Level Control */ +#define COMB 0x2B /* Comb Filter Control */ +#define LDLY 0x2C /* Luma Delay and H Filter Control */ +#define MISC1 0x2D /* Miscellaneous Control I */ +#define LOOP 0x2E /* LOOP Control Register */ +#define MISC2 0x2F /* Miscellaneous Control II */ +#define MVSN 0x30 /* Macrovision Detection */ +#define STATUS2 0x31 /* Chip STATUS II */ +#define HFREF 0x32 /* H monitor */ +#define CLMD 0x33 /* CLAMP MODE */ +#define IDCNTL 0x34 /* ID Detection Control */ +#define CLCNTL1 0x35 /* Clamp Control I */ +#define ANAPLLCTL 0x4C +#define VBIMIN 0x4D +#define HSLOWCTL 0x4E +#define WSS3 0x4F +#define FILLDATA 0x50 +#define SDID 0x51 +#define DID 0x52 +#define WSS1 0x53 +#define WSS2 0x54 +#define VVBI 0x55 +#define LCTL6 0x56 +#define LCTL7 0x57 +#define LCTL8 0x58 +#define LCTL9 0x59 +#define LCTL10 0x5A +#define LCTL11 0x5B +#define LCTL12 0x5C +#define LCTL13 0x5D +#define LCTL14 0x5E +#define LCTL15 0x5F +#define LCTL16 0x60 +#define LCTL17 0x61 +#define LCTL18 0x62 +#define LCTL19 0x63 +#define LCTL20 0x64 +#define LCTL21 0x65 +#define LCTL22 0x66 +#define LCTL23 0x67 +#define LCTL24 0x68 +#define LCTL25 0x69 +#define LCTL26 0x6A +#define HSGEGIN 0x6B +#define HSEND 0x6C +#define OVSDLY 0x6D +#define OVSEND 0x6E +#define VBIDELAY 0x6F + +/* + * register detail + */ + +/* INFORM */ +#define FC27_ON 0x40 /* 1 : Input crystal clock frequency is 27MHz */ +#define FC27_FF 0x00 /* 0 : Square pixel mode. */ + /* Must use 24.54MHz for 60Hz field rate */ + /* source or 29.5MHz for 50Hz field rate */ +#define IFSEL_S 0x10 /* 01 : S-video decoding */ +#define IFSEL_C 0x00 /* 00 : Composite video decoding */ + /* Y input video selection */ +#define YSEL_M0 0x00 /* 00 : Mux0 selected */ +#define YSEL_M1 0x04 /* 01 : Mux1 selected */ +#define YSEL_M2 0x08 /* 10 : Mux2 selected */ +#define YSEL_M3 0x10 /* 11 : Mux3 selected */ + +/* OPFORM */ +#define MODE 0x80 /* 0 : CCIR601 compatible YCrCb 4:2:2 format */ + /* 1 : ITU-R-656 compatible data sequence format */ +#define LEN 0x40 /* 0 : 8-bit YCrCb 4:2:2 output format */ + /* 1 : 16-bit YCrCb 4:2:2 output format.*/ +#define LLCMODE 0x20 /* 1 : LLC output mode. */ + /* 0 : free-run output mode */ +#define AINC 0x10 /* Serial interface auto-indexing control */ + /* 0 : auto-increment */ + /* 1 : non-auto */ +#define VSCTL 0x08 /* 1 : Vertical out ctrl by DVALID */ + /* 0 : Vertical out ctrl by HACTIVE and DVALID */ +#define OEN 0x04 /* Output Enable together with TRI_SEL. */ + +/* OUTCTR1 */ +#define VSP_LO 0x00 /* 0 : VS pin output polarity is active low */ +#define VSP_HI 0x80 /* 1 : VS pin output polarity is active high. */ + /* VS pin output control */ +#define VSSL_VSYNC 0x00 /* 0 : VSYNC */ +#define VSSL_VACT 0x10 /* 1 : VACT */ +#define VSSL_FIELD 0x20 /* 2 : FIELD */ +#define VSSL_VVALID 0x30 /* 3 : VVALID */ +#define VSSL_ZERO 0x70 /* 7 : 0 */ +#define HSP_LOW 0x00 /* 0 : HS pin output polarity is active low */ +#define HSP_HI 0x08 /* 1 : HS pin output polarity is active high.*/ + /* HS pin output control */ +#define HSSL_HACT 0x00 /* 0 : HACT */ +#define HSSL_HSYNC 0x01 /* 1 : HSYNC */ +#define HSSL_DVALID 0x02 /* 2 : DVALID */ +#define HSSL_HLOCK 0x03 /* 3 : HLOCK */ +#define HSSL_ASYNCW 0x04 /* 4 : ASYNCW */ +#define HSSL_ZERO 0x07 /* 7 : 0 */ + +/* ACNTL1 */ +#define SRESET 0x80 /* resets the device to its default state + * but all register content remain unchanged. + * This bit is self-resetting. + */ + +/* VBICNTL */ +/* RTSEL : control the real time signal +* output from the MPOUT pin +*/ +#define RTSEL_MASK 0x07 +#define RTSEL_VLOSS 0x00 /* 0000 = Video loss */ +#define RTSEL_HLOCK 0x01 /* 0001 = H-lock */ +#define RTSEL_SLOCK 0x02 /* 0010 = S-lock */ +#define RTSEL_VLOCK 0x03 /* 0011 = V-lock */ +#define RTSEL_MONO 0x04 /* 0100 = MONO */ +#define RTSEL_DET50 0x05 /* 0101 = DET50 */ +#define RTSEL_FIELD 0x06 /* 0110 = FIELD */ +#define RTSEL_RTCO 0x07 /* 0111 = RTCO ( Real Time Control ) */ + +/* + * structure + */ + +struct regval_list { + unsigned char reg_num; + unsigned char value; +}; + +struct tw9910_scale_ctrl { + char *name; + unsigned short width; + unsigned short height; + u16 hscale; + u16 vscale; +}; + +struct tw9910_cropping_ctrl { + u16 vdelay; + u16 vactive; + u16 hdelay; + u16 hactive; +}; + +struct tw9910_hsync_ctrl { + u16 start; + u16 end; +}; + +struct tw9910_priv { + struct tw9910_video_info *info; + struct i2c_client *client; + struct soc_camera_device icd; + const struct tw9910_scale_ctrl *scale; +}; + +/* + * register settings + */ + +#define ENDMARKER { 0xff, 0xff } + +static const struct regval_list tw9910_default_regs[] = +{ + { OPFORM, 0x00 }, + { OUTCTR1, VSP_LO | VSSL_VVALID | HSP_HI | HSSL_HSYNC }, + ENDMARKER, +}; + +static const struct soc_camera_data_format tw9910_color_fmt[] = { + { + .name = "VYUY", + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + } +}; + +static const struct tw9910_scale_ctrl tw9910_ntsc_scales[] = { + { + .name = "NTSC SQ", + .width = 640, + .height = 480, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "NTSC CCIR601", + .width = 720, + .height = 480, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "NTSC SQ (CIF)", + .width = 320, + .height = 240, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "NTSC CCIR601 (CIF)", + .width = 360, + .height = 240, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "NTSC SQ (QCIF)", + .width = 160, + .height = 120, + .hscale = 0x0400, + .vscale = 0x0400, + }, + { + .name = "NTSC CCIR601 (QCIF)", + .width = 180, + .height = 120, + .hscale = 0x0400, + .vscale = 0x0400, + }, +}; + +static const struct tw9910_scale_ctrl tw9910_pal_scales[] = { + { + .name = "PAL SQ", + .width = 768, + .height = 576, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "PAL CCIR601", + .width = 720, + .height = 576, + .hscale = 0x0100, + .vscale = 0x0100, + }, + { + .name = "PAL SQ (CIF)", + .width = 384, + .height = 288, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "PAL CCIR601 (CIF)", + .width = 360, + .height = 288, + .hscale = 0x0200, + .vscale = 0x0200, + }, + { + .name = "PAL SQ (QCIF)", + .width = 192, + .height = 144, + .hscale = 0x0400, + .vscale = 0x0400, + }, + { + .name = "PAL CCIR601 (QCIF)", + .width = 180, + .height = 144, + .hscale = 0x0400, + .vscale = 0x0400, + }, +}; + +static const struct tw9910_cropping_ctrl tw9910_cropping_ctrl = { + .vdelay = 0x0012, + .vactive = 0x00F0, + .hdelay = 0x0010, + .hactive = 0x02D0, +}; + +static const struct tw9910_hsync_ctrl tw9910_hsync_ctrl = { + .start = 0x0260, + .end = 0x0300, +}; + +/* + * general function + */ +static int tw9910_set_scale(struct i2c_client *client, + const struct tw9910_scale_ctrl *scale) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, SCALE_HI, + (scale->vscale & 0x0F00) >> 4 | + (scale->hscale & 0x0F00) >> 8); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, HSCALE_LO, + scale->hscale & 0x00FF); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, VSCALE_LO, + scale->vscale & 0x00FF); + + return ret; +} + +static int tw9910_set_cropping(struct i2c_client *client, + const struct tw9910_cropping_ctrl *cropping) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, CROP_HI, + (cropping->vdelay & 0x0300) >> 2 | + (cropping->vactive & 0x0300) >> 4 | + (cropping->hdelay & 0x0300) >> 6 | + (cropping->hactive & 0x0300) >> 8); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, VDELAY_LO, + cropping->vdelay & 0x00FF); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, VACTIVE_LO, + cropping->vactive & 0x00FF); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, HDELAY_LO, + cropping->hdelay & 0x00FF); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, HACTIVE_LO, + cropping->hactive & 0x00FF); + + return ret; +} + +static int tw9910_set_hsync(struct i2c_client *client, + const struct tw9910_hsync_ctrl *hsync) +{ + int ret; + + /* bit 10 - 3 */ + ret = i2c_smbus_write_byte_data(client, HSGEGIN, + (hsync->start & 0x07F8) >> 3); + if (ret < 0) + return ret; + + /* bit 10 - 3 */ + ret = i2c_smbus_write_byte_data(client, HSEND, + (hsync->end & 0x07F8) >> 3); + if (ret < 0) + return ret; + + /* bit 2 - 0 */ + ret = i2c_smbus_read_byte_data(client, HSLOWCTL); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(client, HSLOWCTL, + (ret & 0x88) | + (hsync->start & 0x0007) << 4 | + (hsync->end & 0x0007)); + + return ret; +} + +static int tw9910_write_array(struct i2c_client *client, + const struct regval_list *vals) +{ + while (vals->reg_num != 0xff) { + int ret = i2c_smbus_write_byte_data(client, + vals->reg_num, + vals->value); + if (ret < 0) + return ret; + vals++; + } + return 0; +} + +static int tw9910_mask_set(struct i2c_client *client, u8 command, + u8 mask, u8 set) +{ + s32 val = i2c_smbus_read_byte_data(client, command); + + val &= ~mask; + val |= set; + + return i2c_smbus_write_byte_data(client, command, val); +} + +static void tw9910_reset(struct i2c_client *client) +{ + i2c_smbus_write_byte_data(client, ACNTL1, SRESET); + msleep(1); +} + +static const struct tw9910_scale_ctrl* +tw9910_select_norm(struct soc_camera_device *icd, u32 width, u32 height) +{ + const struct tw9910_scale_ctrl *scale; + const struct tw9910_scale_ctrl *ret = NULL; + v4l2_std_id norm = icd->vdev->current_norm; + __u32 diff = 0xffffffff, tmp; + int size, i; + + if (norm & V4L2_STD_NTSC) { + scale = tw9910_ntsc_scales; + size = ARRAY_SIZE(tw9910_ntsc_scales); + } else if (norm & V4L2_STD_PAL) { + scale = tw9910_pal_scales; + size = ARRAY_SIZE(tw9910_pal_scales); + } else { + return NULL; + } + + for (i = 0; i < size; i++) { + tmp = abs(width - scale[i].width) + + abs(height - scale[i].height); + if (tmp < diff) { + diff = tmp; + ret = scale + i; + } + } + + return ret; +} + +/* + * soc_camera_ops function + */ +static int tw9910_init(struct soc_camera_device *icd) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + int ret = 0; + + if (priv->info->link.power) { + ret = priv->info->link.power(&priv->client->dev, 1); + if (ret < 0) + return ret; + } + + if (priv->info->link.reset) + ret = priv->info->link.reset(&priv->client->dev); + + return ret; +} + +static int tw9910_release(struct soc_camera_device *icd) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + int ret = 0; + + if (priv->info->link.power) + ret = priv->info->link.power(&priv->client->dev, 0); + + return ret; +} + +static int tw9910_start_capture(struct soc_camera_device *icd) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + + if (!priv->scale) { + dev_err(&icd->dev, "norm select error\n"); + return -EPERM; + } + + dev_dbg(&icd->dev, "%s %dx%d\n", + priv->scale->name, + priv->scale->width, + priv->scale->height); + + return 0; +} + +static int tw9910_stop_capture(struct soc_camera_device *icd) +{ + return 0; +} + +static int tw9910_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + return 0; +} + +static unsigned long tw9910_query_bus_param(struct soc_camera_device *icd) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + struct soc_camera_link *icl = priv->client->dev.platform_data; + unsigned long flags = SOCAM_PCLK_SAMPLE_RISING | SOCAM_MASTER | + SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_HSYNC_ACTIVE_HIGH | + SOCAM_DATA_ACTIVE_HIGH | priv->info->buswidth; + + return soc_camera_apply_sensor_flags(icl, flags); +} + +static int tw9910_get_chip_id(struct soc_camera_device *icd, + struct v4l2_chip_ident *id) +{ + id->ident = V4L2_IDENT_TW9910; + id->revision = 0; + + return 0; +} + +static int tw9910_set_std(struct soc_camera_device *icd, + v4l2_std_id *a) +{ + int ret = -EINVAL; + + if (*a & (V4L2_STD_NTSC | V4L2_STD_PAL)) + ret = 0; + + return ret; +} + +static int tw9910_enum_input(struct soc_camera_device *icd, + struct v4l2_input *inp) +{ + inp->type = V4L2_INPUT_TYPE_TUNER; + inp->std = V4L2_STD_UNKNOWN; + strcpy(inp->name, "Video"); + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int tw9910_get_register(struct soc_camera_device *icd, + struct v4l2_register *reg) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + int ret; + + if (reg->reg > 0xff) + return -EINVAL; + + ret = i2c_smbus_read_byte_data(priv->client, reg->reg); + if (ret < 0) + return ret; + + /* ret = int + * reg->val = __u64 + */ + reg->val = (__u64)ret; + + return 0; +} + +static int tw9910_set_register(struct soc_camera_device *icd, + struct v4l2_register *reg) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + + if (reg->reg > 0xff || + reg->val > 0xff) + return -EINVAL; + + return i2c_smbus_write_byte_data(priv->client, reg->reg, reg->val); +} +#endif + +static int tw9910_set_fmt(struct soc_camera_device *icd, __u32 pixfmt, + struct v4l2_rect *rect) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + int ret = -EINVAL; + u8 val; + + /* + * select suitable norm + */ + priv->scale = tw9910_select_norm(icd, rect->width, rect->height); + if (!priv->scale) + return ret; + + /* + * reset hardware + */ + tw9910_reset(priv->client); + ret = tw9910_write_array(priv->client, tw9910_default_regs); + if (ret < 0) + return ret; + /* + * set bus width + */ + val = 0x00; + if (SOCAM_DATAWIDTH_16 == priv->info->buswidth) + val = LEN; + + ret = tw9910_mask_set(priv->client, OPFORM, LEN, val); + if (ret < 0) + return ret; + + /* + * select MPOUT behavior + */ + switch (priv->info->mpout) { + case TW9910_MPO_VLOSS: + val = RTSEL_VLOSS; break; + case TW9910_MPO_HLOCK: + val = RTSEL_HLOCK; break; + case TW9910_MPO_SLOCK: + val = RTSEL_SLOCK; break; + case TW9910_MPO_VLOCK: + val = RTSEL_VLOCK; break; + case TW9910_MPO_MONO: + val = RTSEL_MONO; break; + case TW9910_MPO_DET50: + val = RTSEL_DET50; break; + case TW9910_MPO_FIELD: + val = RTSEL_FIELD; break; + case TW9910_MPO_RTCO: + val = RTSEL_RTCO; break; + default: + val = 0; + } + + ret = tw9910_mask_set(priv->client, VBICNTL, RTSEL_MASK, val); + if (ret < 0) + return ret; + + /* + * set scale + */ + ret = tw9910_set_scale(priv->client, priv->scale); + if (ret < 0) + return ret; + + /* + * set cropping + */ + ret = tw9910_set_cropping(priv->client, &tw9910_cropping_ctrl); + if (ret < 0) + return ret; + + /* + * set hsync + */ + ret = tw9910_set_hsync(priv->client, &tw9910_hsync_ctrl); + + return ret; +} + +static int tw9910_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pix = &f->fmt.pix; + const struct tw9910_scale_ctrl *scale; + + if (V4L2_FIELD_ANY == pix->field) { + pix->field = V4L2_FIELD_INTERLACED; + } else if (V4L2_FIELD_INTERLACED != pix->field) { + dev_err(&icd->dev, "Field type invalid.\n"); + return -EINVAL; + } + + /* + * select suitable norm + */ + scale = tw9910_select_norm(icd, pix->width, pix->height); + if (!scale) + return -EINVAL; + + pix->width = scale->width; + pix->height = scale->height; + + return 0; +} + +static int tw9910_video_probe(struct soc_camera_device *icd) +{ + struct tw9910_priv *priv = container_of(icd, struct tw9910_priv, icd); + s32 val; + int ret; + + /* + * We must have a parent by now. And it cannot be a wrong one. + * So this entire test is completely redundant. + */ + if (!icd->dev.parent || + to_soc_camera_host(icd->dev.parent)->nr != icd->iface) + return -ENODEV; + + /* + * tw9910 only use 8 or 16 bit bus width + */ + if (SOCAM_DATAWIDTH_16 != priv->info->buswidth && + SOCAM_DATAWIDTH_8 != priv->info->buswidth) { + dev_err(&icd->dev, "bus width error\n"); + return -ENODEV; + } + + icd->formats = tw9910_color_fmt; + icd->num_formats = ARRAY_SIZE(tw9910_color_fmt); + + /* + * check and show Product ID + */ + val = i2c_smbus_read_byte_data(priv->client, ID); + if (0x0B != GET_ID(val) || + 0x00 != GET_ReV(val)) { + dev_err(&icd->dev, + "Product ID error %x:%x\n", GET_ID(val), GET_ReV(val)); + return -ENODEV; + } + + dev_info(&icd->dev, + "tw9910 Product ID %0x:%0x\n", GET_ID(val), GET_ReV(val)); + + ret = soc_camera_video_start(icd); + if (ret < 0) + return ret; + + icd->vdev->tvnorms = V4L2_STD_NTSC | V4L2_STD_PAL; + icd->vdev->current_norm = V4L2_STD_NTSC; + + return ret; +} + +static void tw9910_video_remove(struct soc_camera_device *icd) +{ + soc_camera_video_stop(icd); +} + +static struct soc_camera_ops tw9910_ops = { + .owner = THIS_MODULE, + .probe = tw9910_video_probe, + .remove = tw9910_video_remove, + .init = tw9910_init, + .release = tw9910_release, + .start_capture = tw9910_start_capture, + .stop_capture = tw9910_stop_capture, + .set_fmt = tw9910_set_fmt, + .try_fmt = tw9910_try_fmt, + .set_bus_param = tw9910_set_bus_param, + .query_bus_param = tw9910_query_bus_param, + .get_chip_id = tw9910_get_chip_id, + .set_std = tw9910_set_std, + .enum_input = tw9910_enum_input, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .get_register = tw9910_get_register, + .set_register = tw9910_set_register, +#endif +}; + +/* + * i2c_driver function + */ + +static int tw9910_probe(struct i2c_client *client, + const struct i2c_device_id *did) + +{ + struct tw9910_priv *priv; + struct tw9910_video_info *info; + struct soc_camera_device *icd; + const struct tw9910_scale_ctrl *scale; + int i, ret; + + info = client->dev.platform_data; + if (!info) + return -EINVAL; + + if (!i2c_check_functionality(to_i2c_adapter(client->dev.parent), + I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, + "I2C-Adapter doesn't support " + "I2C_FUNC_SMBUS_BYTE_DATA\n"); + return -EIO; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = info; + priv->client = client; + i2c_set_clientdata(client, priv); + + icd = &priv->icd; + icd->ops = &tw9910_ops; + icd->control = &client->dev; + icd->iface = info->link.bus_id; + + /* + * set width and height + */ + icd->width_max = tw9910_ntsc_scales[0].width; /* set default */ + icd->width_min = tw9910_ntsc_scales[0].width; + icd->height_max = tw9910_ntsc_scales[0].height; + icd->height_min = tw9910_ntsc_scales[0].height; + + scale = tw9910_ntsc_scales; + for (i = 0; i < ARRAY_SIZE(tw9910_ntsc_scales); i++) { + icd->width_max = max(scale[i].width, icd->width_max); + icd->width_min = min(scale[i].width, icd->width_min); + icd->height_max = max(scale[i].height, icd->height_max); + icd->height_min = min(scale[i].height, icd->height_min); + } + scale = tw9910_pal_scales; + for (i = 0; i < ARRAY_SIZE(tw9910_pal_scales); i++) { + icd->width_max = max(scale[i].width, icd->width_max); + icd->width_min = min(scale[i].width, icd->width_min); + icd->height_max = max(scale[i].height, icd->height_max); + icd->height_min = min(scale[i].height, icd->height_min); + } + + ret = soc_camera_device_register(icd); + + if (ret) { + i2c_set_clientdata(client, NULL); + kfree(priv); + } + + return ret; +} + +static int tw9910_remove(struct i2c_client *client) +{ + struct tw9910_priv *priv = i2c_get_clientdata(client); + + soc_camera_device_unregister(&priv->icd); + i2c_set_clientdata(client, NULL); + kfree(priv); + return 0; +} + +static const struct i2c_device_id tw9910_id[] = { + { "tw9910", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tw9910_id); + +static struct i2c_driver tw9910_i2c_driver = { + .driver = { + .name = "tw9910", + }, + .probe = tw9910_probe, + .remove = tw9910_remove, + .id_table = tw9910_id, +}; + +/* + * module function + */ +static int __init tw9910_module_init(void) +{ + return i2c_add_driver(&tw9910_i2c_driver); +} + +static void __exit tw9910_module_exit(void) +{ + i2c_del_driver(&tw9910_i2c_driver); +} + +module_init(tw9910_module_init); +module_exit(tw9910_module_exit); + +MODULE_DESCRIPTION("SoC Camera driver for tw9910"); +MODULE_AUTHOR("Kuninori Morimoto"); +MODULE_LICENSE("GPL v2"); diff --git a/include/media/tw9910.h b/include/media/tw9910.h new file mode 100644 index 00000000000..73231e7880d --- /dev/null +++ b/include/media/tw9910.h @@ -0,0 +1,39 @@ +/* + * tw9910 Driver header + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto + * + * Based on ov772x.h + * + * Copyright (C) Kuninori Morimoto + * + * This program 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. + */ + +#ifndef __TW9910_H__ +#define __TW9910_H__ + +#include + +enum tw9910_mpout_pin { + TW9910_MPO_VLOSS, + TW9910_MPO_HLOCK, + TW9910_MPO_SLOCK, + TW9910_MPO_VLOCK, + TW9910_MPO_MONO, + TW9910_MPO_DET50, + TW9910_MPO_FIELD, + TW9910_MPO_RTCO, +}; + +struct tw9910_video_info { + unsigned long buswidth; + enum tw9910_mpout_pin mpout; + struct soc_camera_link link; +}; + + +#endif /* __TW9910_H__ */ diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h index 30448cd5175..f9d42ad99b4 100644 --- a/include/media/v4l2-chip-ident.h +++ b/include/media/v4l2-chip-ident.h @@ -87,6 +87,9 @@ enum { /* module wm8775: just ident 8775 */ V4L2_IDENT_WM8775 = 8775, + /* module tw9910: just ident 9910 */ + V4L2_IDENT_TW9910 = 9910, + /* module cs53132a: just ident 53132 */ V4L2_IDENT_CS53l32A = 53132, -- cgit v1.2.3-70-g09d2 From 4e96fd088cf6fb95ba4b212e5e72bac1e6d34e79 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Mon, 29 Dec 2008 06:04:59 -0300 Subject: V4L/DVB (10099): soc-camera: add support for MT9T031 CMOS camera sensor from Micron This camera is rather similar to MT9M001, but also has a couple of enhanced features, like pixel binning. create mode 100644 drivers/media/video/mt9t031.c Signed-off-by: Guennadi Liakhovetski Acked-by: Jean Delvare Signed-off-by: Mauro Carvalho Chehab --- drivers/media/video/Kconfig | 6 + drivers/media/video/Makefile | 1 + drivers/media/video/mt9t031.c | 736 ++++++++++++++++++++++++++++++++++++++++ include/media/v4l2-chip-ident.h | 1 + 4 files changed, 744 insertions(+) create mode 100644 drivers/media/video/mt9t031.c (limited to 'drivers/media/video/Makefile') diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 9785eeb1ed4..19cf3b8f67c 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -741,6 +741,12 @@ config SOC_CAMERA_MT9M111 help This driver supports MT9M111 and MT9M112 cameras from Micron +config SOC_CAMERA_MT9T031 + tristate "mt9t031 support" + depends on SOC_CAMERA && I2C + help + This driver supports MT9T031 cameras from Micron. + config SOC_CAMERA_MT9V022 tristate "mt9v022 support" depends on SOC_CAMERA && I2C diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 40c69afcde7..1611c33b1ae 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -137,6 +137,7 @@ obj-$(CONFIG_VIDEO_OMAP2) += omap2cam.o obj-$(CONFIG_SOC_CAMERA) += soc_camera.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o obj-$(CONFIG_SOC_CAMERA_MT9M111) += mt9m111.o +obj-$(CONFIG_SOC_CAMERA_MT9T031) += mt9t031.o obj-$(CONFIG_SOC_CAMERA_MT9V022) += mt9v022.o obj-$(CONFIG_SOC_CAMERA_OV772X) += ov772x.o obj-$(CONFIG_SOC_CAMERA_PLATFORM) += soc_camera_platform.o diff --git a/drivers/media/video/mt9t031.c b/drivers/media/video/mt9t031.c new file mode 100644 index 00000000000..1a9d53966d0 --- /dev/null +++ b/drivers/media/video/mt9t031.c @@ -0,0 +1,736 @@ +/* + * Driver for MT9T031 CMOS Image Sensor from Micron + * + * Copyright (C) 2008, Guennadi Liakhovetski, DENX Software Engineering + * + * This program 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. + */ + +#include +#include +#include +#include + +#include +#include +#include + +/* mt9t031 i2c address 0x5d + * The platform has to define i2c_board_info + * and call i2c_register_board_info() */ + +/* mt9t031 selected register addresses */ +#define MT9T031_CHIP_VERSION 0x00 +#define MT9T031_ROW_START 0x01 +#define MT9T031_COLUMN_START 0x02 +#define MT9T031_WINDOW_HEIGHT 0x03 +#define MT9T031_WINDOW_WIDTH 0x04 +#define MT9T031_HORIZONTAL_BLANKING 0x05 +#define MT9T031_VERTICAL_BLANKING 0x06 +#define MT9T031_OUTPUT_CONTROL 0x07 +#define MT9T031_SHUTTER_WIDTH_UPPER 0x08 +#define MT9T031_SHUTTER_WIDTH 0x09 +#define MT9T031_PIXEL_CLOCK_CONTROL 0x0a +#define MT9T031_FRAME_RESTART 0x0b +#define MT9T031_SHUTTER_DELAY 0x0c +#define MT9T031_RESET 0x0d +#define MT9T031_READ_MODE_1 0x1e +#define MT9T031_READ_MODE_2 0x20 +#define MT9T031_READ_MODE_3 0x21 +#define MT9T031_ROW_ADDRESS_MODE 0x22 +#define MT9T031_COLUMN_ADDRESS_MODE 0x23 +#define MT9T031_GLOBAL_GAIN 0x35 +#define MT9T031_CHIP_ENABLE 0xF8 + +#define MT9T031_MAX_HEIGHT 1536 +#define MT9T031_MAX_WIDTH 2048 +#define MT9T031_MIN_HEIGHT 2 +#define MT9T031_MIN_WIDTH 2 +#define MT9T031_HORIZONTAL_BLANK 142 +#define MT9T031_VERTICAL_BLANK 25 +#define MT9T031_COLUMN_SKIP 32 +#define MT9T031_ROW_SKIP 20 + +#define MT9T031_BUS_PARAM (SOCAM_PCLK_SAMPLE_RISING | \ + SOCAM_PCLK_SAMPLE_FALLING | SOCAM_HSYNC_ACTIVE_HIGH | \ + SOCAM_VSYNC_ACTIVE_HIGH | SOCAM_DATA_ACTIVE_HIGH | \ + SOCAM_MASTER | SOCAM_DATAWIDTH_10) + +static const struct soc_camera_data_format mt9t031_colour_formats[] = { + { + .name = "Bayer (sRGB) 10 bit", + .depth = 10, + .fourcc = V4L2_PIX_FMT_SGRBG10, + .colorspace = V4L2_COLORSPACE_SRGB, + } +}; + +struct mt9t031 { + struct i2c_client *client; + struct soc_camera_device icd; + int model; /* V4L2_IDENT_MT9T031* codes from v4l2-chip-ident.h */ + unsigned char autoexposure; + u16 xskip; + u16 yskip; +}; + +static int reg_read(struct soc_camera_device *icd, const u8 reg) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + struct i2c_client *client = mt9t031->client; + s32 data = i2c_smbus_read_word_data(client, reg); + return data < 0 ? data : swab16(data); +} + +static int reg_write(struct soc_camera_device *icd, const u8 reg, + const u16 data) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + return i2c_smbus_write_word_data(mt9t031->client, reg, swab16(data)); +} + +static int reg_set(struct soc_camera_device *icd, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(icd, reg); + if (ret < 0) + return ret; + return reg_write(icd, reg, ret | data); +} + +static int reg_clear(struct soc_camera_device *icd, const u8 reg, + const u16 data) +{ + int ret; + + ret = reg_read(icd, reg); + if (ret < 0) + return ret; + return reg_write(icd, reg, ret & ~data); +} + +static int set_shutter(struct soc_camera_device *icd, const u32 data) +{ + int ret; + + ret = reg_write(icd, MT9T031_SHUTTER_WIDTH_UPPER, data >> 16); + + if (ret >= 0) + ret = reg_write(icd, MT9T031_SHUTTER_WIDTH, data & 0xffff); + + return ret; +} + +static int get_shutter(struct soc_camera_device *icd, u32 *data) +{ + int ret; + + ret = reg_read(icd, MT9T031_SHUTTER_WIDTH_UPPER); + *data = ret << 16; + + if (ret >= 0) + ret = reg_read(icd, MT9T031_SHUTTER_WIDTH); + *data |= ret & 0xffff; + + return ret < 0 ? ret : 0; +} + +static int mt9t031_init(struct soc_camera_device *icd) +{ + int ret; + + /* Disable chip output, synchronous option update */ + dev_dbg(icd->vdev->parent, "%s\n", __func__); + + ret = reg_write(icd, MT9T031_RESET, 1); + if (ret >= 0) + ret = reg_write(icd, MT9T031_RESET, 0); + if (ret >= 0) + ret = reg_clear(icd, MT9T031_OUTPUT_CONTROL, 3); + + return ret >= 0 ? 0 : -EIO; +} + +static int mt9t031_release(struct soc_camera_device *icd) +{ + /* Disable the chip */ + reg_clear(icd, MT9T031_OUTPUT_CONTROL, 3); + return 0; +} + +static int mt9t031_start_capture(struct soc_camera_device *icd) +{ + /* Switch to master "normal" mode */ + if (reg_set(icd, MT9T031_OUTPUT_CONTROL, 3) < 0) + return -EIO; + return 0; +} + +static int mt9t031_stop_capture(struct soc_camera_device *icd) +{ + /* Stop sensor readout */ + if (reg_clear(icd, MT9T031_OUTPUT_CONTROL, 3) < 0) + return -EIO; + return 0; +} + +static int mt9t031_set_bus_param(struct soc_camera_device *icd, + unsigned long flags) +{ + /* The caller should have queried our parameters, check anyway */ + if (flags & ~MT9T031_BUS_PARAM) + return -EINVAL; + + if (flags & SOCAM_PCLK_SAMPLE_FALLING) + reg_set(icd, MT9T031_PIXEL_CLOCK_CONTROL, 0x8000); + else + reg_clear(icd, MT9T031_PIXEL_CLOCK_CONTROL, 0x8000); + + return 0; +} + +static unsigned long mt9t031_query_bus_param(struct soc_camera_device *icd) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + struct soc_camera_link *icl = mt9t031->client->dev.platform_data; + + return soc_camera_apply_sensor_flags(icl, MT9T031_BUS_PARAM); +} + +static int mt9t031_set_fmt(struct soc_camera_device *icd, + __u32 pixfmt, struct v4l2_rect *rect) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + int ret; + const u16 hblank = MT9T031_HORIZONTAL_BLANK, + vblank = MT9T031_VERTICAL_BLANK; + u16 xbin, xskip = mt9t031->xskip, ybin, yskip = mt9t031->yskip, + width = rect->width * xskip, height = rect->height * yskip; + + if (pixfmt) { + /* S_FMT - use binning and skipping for scaling, recalculate */ + /* Is this more optimal than just a division? */ + for (xskip = 8; xskip > 1; xskip--) + if (rect->width * xskip <= icd->width_max) + break; + + for (yskip = 8; yskip > 1; yskip--) + if (rect->height * yskip <= icd->height_max) + break; + + width = rect->width * xskip; + height = rect->height * yskip; + + dev_dbg(&icd->dev, "xskip %u, width %u, yskip %u, height %u\n", + xskip, width, yskip, height); + } + + xbin = min(xskip, (u16)3); + ybin = min(yskip, (u16)3); + + /* Make sure we don't exceed frame limits */ + if (rect->left + width > icd->width_max) + rect->left = (icd->width_max - width) / 2; + + if (rect->top + height > icd->height_max) + rect->top = (icd->height_max - height) / 2; + + /* Could just do roundup(rect->left, [xy]bin); but this is cheaper */ + switch (xbin) { + case 2: + rect->left = (rect->left + 1) & ~1; + break; + case 3: + rect->left = roundup(rect->left, 3); + } + + switch (ybin) { + case 2: + rect->top = (rect->top + 1) & ~1; + break; + case 3: + rect->top = roundup(rect->top, 3); + } + + /* Blanking and start values - default... */ + ret = reg_write(icd, MT9T031_HORIZONTAL_BLANKING, hblank); + if (ret >= 0) + ret = reg_write(icd, MT9T031_VERTICAL_BLANKING, vblank); + + if (pixfmt) { + /* Binning, skipping */ + if (ret >= 0) + ret = reg_write(icd, MT9T031_COLUMN_ADDRESS_MODE, + ((xbin - 1) << 4) | (xskip - 1)); + if (ret >= 0) + ret = reg_write(icd, MT9T031_ROW_ADDRESS_MODE, + ((ybin - 1) << 4) | (yskip - 1)); + } + dev_dbg(&icd->dev, "new left %u, top %u\n", rect->left, rect->top); + + /* The caller provides a supported format, as guaranteed by + * icd->try_fmt_cap(), soc_camera_s_crop() and soc_camera_cropcap() */ + if (ret >= 0) + ret = reg_write(icd, MT9T031_COLUMN_START, rect->left); + if (ret >= 0) + ret = reg_write(icd, MT9T031_ROW_START, rect->top); + if (ret >= 0) + ret = reg_write(icd, MT9T031_WINDOW_WIDTH, width - 1); + if (ret >= 0) + ret = reg_write(icd, MT9T031_WINDOW_HEIGHT, + height + icd->y_skip_top - 1); + if (ret >= 0 && mt9t031->autoexposure) { + ret = set_shutter(icd, height + icd->y_skip_top + vblank); + if (ret >= 0) { + const u32 shutter_max = MT9T031_MAX_HEIGHT + vblank; + const struct v4l2_queryctrl *qctrl = + soc_camera_find_qctrl(icd->ops, + V4L2_CID_EXPOSURE); + icd->exposure = (shutter_max / 2 + (height + + icd->y_skip_top + vblank - 1) * + (qctrl->maximum - qctrl->minimum)) / + shutter_max + qctrl->minimum; + } + } + + if (!ret && pixfmt) { + mt9t031->xskip = xskip; + mt9t031->yskip = yskip; + } + + return ret < 0 ? ret : 0; +} + +static int mt9t031_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pix = &f->fmt.pix; + + if (pix->height < icd->height_min) + pix->height = icd->height_min; + if (pix->height > icd->height_max) + pix->height = icd->height_max; + if (pix->width < icd->width_min) + pix->width = icd->width_min; + if (pix->width > icd->width_max) + pix->width = icd->width_max; + + pix->width &= ~0x01; /* has to be even */ + pix->height &= ~0x01; /* has to be even */ + + return 0; +} + +static int mt9t031_get_chip_id(struct soc_camera_device *icd, + struct v4l2_chip_ident *id) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + + if (id->match_type != V4L2_CHIP_MATCH_I2C_ADDR) + return -EINVAL; + + if (id->match_chip != mt9t031->client->addr) + return -ENODEV; + + id->ident = mt9t031->model; + id->revision = 0; + + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mt9t031_get_register(struct soc_camera_device *icd, + struct v4l2_register *reg) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + + if (reg->match_type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match_chip != mt9t031->client->addr) + return -ENODEV; + + reg->val = reg_read(icd, reg->reg); + + if (reg->val > 0xffff) + return -EIO; + + return 0; +} + +static int mt9t031_set_register(struct soc_camera_device *icd, + struct v4l2_register *reg) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + + if (reg->match_type != V4L2_CHIP_MATCH_I2C_ADDR || reg->reg > 0xff) + return -EINVAL; + + if (reg->match_chip != mt9t031->client->addr) + return -ENODEV; + + if (reg_write(icd, reg->reg, reg->val) < 0) + return -EIO; + + return 0; +} +#endif + +static const struct v4l2_queryctrl mt9t031_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_GAIN, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Gain", + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 64, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure", + .minimum = 1, + .maximum = 255, + .step = 1, + .default_value = 255, + .flags = V4L2_CTRL_FLAG_SLIDER, + }, { + .id = V4L2_CID_EXPOSURE_AUTO, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Automatic Exposure", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + } +}; + +static int mt9t031_video_probe(struct soc_camera_device *); +static void mt9t031_video_remove(struct soc_camera_device *); +static int mt9t031_get_control(struct soc_camera_device *, struct v4l2_control *); +static int mt9t031_set_control(struct soc_camera_device *, struct v4l2_control *); + +static struct soc_camera_ops mt9t031_ops = { + .owner = THIS_MODULE, + .probe = mt9t031_video_probe, + .remove = mt9t031_video_remove, + .init = mt9t031_init, + .release = mt9t031_release, + .start_capture = mt9t031_start_capture, + .stop_capture = mt9t031_stop_capture, + .set_fmt = mt9t031_set_fmt, + .try_fmt = mt9t031_try_fmt, + .set_bus_param = mt9t031_set_bus_param, + .query_bus_param = mt9t031_query_bus_param, + .controls = mt9t031_controls, + .num_controls = ARRAY_SIZE(mt9t031_controls), + .get_control = mt9t031_get_control, + .set_control = mt9t031_set_control, + .get_chip_id = mt9t031_get_chip_id, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .get_register = mt9t031_get_register, + .set_register = mt9t031_set_register, +#endif +}; + +static int mt9t031_get_control(struct soc_camera_device *icd, struct v4l2_control *ctrl) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + int data; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + data = reg_read(icd, MT9T031_READ_MODE_2); + if (data < 0) + return -EIO; + ctrl->value = !!(data & 0x8000); + break; + case V4L2_CID_HFLIP: + data = reg_read(icd, MT9T031_READ_MODE_2); + if (data < 0) + return -EIO; + ctrl->value = !!(data & 0x4000); + break; + case V4L2_CID_EXPOSURE_AUTO: + ctrl->value = mt9t031->autoexposure; + break; + } + return 0; +} + +static int mt9t031_set_control(struct soc_camera_device *icd, struct v4l2_control *ctrl) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + const struct v4l2_queryctrl *qctrl; + int data; + + qctrl = soc_camera_find_qctrl(&mt9t031_ops, ctrl->id); + + if (!qctrl) + return -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_VFLIP: + if (ctrl->value) + data = reg_set(icd, MT9T031_READ_MODE_2, 0x8000); + else + data = reg_clear(icd, MT9T031_READ_MODE_2, 0x8000); + if (data < 0) + return -EIO; + break; + case V4L2_CID_HFLIP: + if (ctrl->value) + data = reg_set(icd, MT9T031_READ_MODE_2, 0x4000); + else + data = reg_clear(icd, MT9T031_READ_MODE_2, 0x4000); + if (data < 0) + return -EIO; + break; + case V4L2_CID_GAIN: + if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum) + return -EINVAL; + /* See Datasheet Table 7, Gain settings. */ + if (ctrl->value <= qctrl->default_value) { + /* Pack it into 0..1 step 0.125, register values 0..8 */ + unsigned long range = qctrl->default_value - qctrl->minimum; + data = ((ctrl->value - qctrl->minimum) * 8 + range / 2) / range; + + dev_dbg(&icd->dev, "Setting gain %d\n", data); + data = reg_write(icd, MT9T031_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } else { + /* Pack it into 1.125..15 variable step, register values 9..67 */ + /* We assume qctrl->maximum - qctrl->default_value - 1 > 0 */ + unsigned long range = qctrl->maximum - qctrl->default_value - 1; + unsigned long gain = ((ctrl->value - qctrl->default_value - 1) * + 111 + range / 2) / range + 9; + + if (gain <= 32) + data = gain; + else if (gain <= 64) + data = ((gain - 32) * 16 + 16) / 32 + 80; + else + data = ((gain - 64) * 7 + 28) / 56 + 96; + + dev_dbg(&icd->dev, "Setting gain from %d to %d\n", + reg_read(icd, MT9T031_GLOBAL_GAIN), data); + data = reg_write(icd, MT9T031_GLOBAL_GAIN, data); + if (data < 0) + return -EIO; + } + + /* Success */ + icd->gain = ctrl->value; + break; + case V4L2_CID_EXPOSURE: + /* mt9t031 has maximum == default */ + if (ctrl->value > qctrl->maximum || ctrl->value < qctrl->minimum) + return -EINVAL; + else { + const unsigned long range = qctrl->maximum - qctrl->minimum; + const u32 shutter = ((ctrl->value - qctrl->minimum) * 1048 + + range / 2) / range + 1; + u32 old; + + get_shutter(icd, &old); + dev_dbg(&icd->dev, "Setting shutter width from %u to %u\n", + old, shutter); + if (set_shutter(icd, shutter) < 0) + return -EIO; + icd->exposure = ctrl->value; + mt9t031->autoexposure = 0; + } + break; + case V4L2_CID_EXPOSURE_AUTO: + if (ctrl->value) { + const u16 vblank = MT9T031_VERTICAL_BLANK; + const u32 shutter_max = MT9T031_MAX_HEIGHT + vblank; + if (set_shutter(icd, icd->height + + icd->y_skip_top + vblank) < 0) + return -EIO; + qctrl = soc_camera_find_qctrl(icd->ops, V4L2_CID_EXPOSURE); + icd->exposure = (shutter_max / 2 + (icd->height + + icd->y_skip_top + vblank - 1) * + (qctrl->maximum - qctrl->minimum)) / + shutter_max + qctrl->minimum; + mt9t031->autoexposure = 1; + } else + mt9t031->autoexposure = 0; + break; + } + return 0; +} + +/* Interface active, can use i2c. If it fails, it can indeed mean, that + * this wasn't our capture interface, so, we wait for the right one */ +static int mt9t031_video_probe(struct soc_camera_device *icd) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + s32 data; + int ret; + + /* We must have a parent by now. And it cannot be a wrong one. + * So this entire test is completely redundant. */ + if (!icd->dev.parent || + to_soc_camera_host(icd->dev.parent)->nr != icd->iface) + return -ENODEV; + + /* Enable the chip */ + data = reg_write(icd, MT9T031_CHIP_ENABLE, 1); + dev_dbg(&icd->dev, "write: %d\n", data); + + /* Read out the chip version register */ + data = reg_read(icd, MT9T031_CHIP_VERSION); + + switch (data) { + case 0x1621: + mt9t031->model = V4L2_IDENT_MT9T031; + icd->formats = mt9t031_colour_formats; + icd->num_formats = ARRAY_SIZE(mt9t031_colour_formats); + break; + default: + ret = -ENODEV; + dev_err(&icd->dev, + "No MT9T031 chip detected, register read %x\n", data); + goto ei2c; + } + + dev_info(&icd->dev, "Detected a MT9T031 chip ID %x\n", data); + + /* Now that we know the model, we can start video */ + ret = soc_camera_video_start(icd); + if (ret) + goto evstart; + + return 0; + +evstart: +ei2c: + return ret; +} + +static void mt9t031_video_remove(struct soc_camera_device *icd) +{ + struct mt9t031 *mt9t031 = container_of(icd, struct mt9t031, icd); + + dev_dbg(&icd->dev, "Video %x removed: %p, %p\n", mt9t031->client->addr, + icd->dev.parent, icd->vdev); + soc_camera_video_stop(icd); +} + +static int mt9t031_probe(struct i2c_client *client, + const struct i2c_device_id *did) +{ + struct mt9t031 *mt9t031; + struct soc_camera_device *icd; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct soc_camera_link *icl = client->dev.platform_data; + int ret; + + if (!icl) { + dev_err(&client->dev, "MT9T031 driver needs platform data\n"); + return -EINVAL; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) { + dev_warn(&adapter->dev, + "I2C-Adapter doesn't support I2C_FUNC_SMBUS_WORD\n"); + return -EIO; + } + + mt9t031 = kzalloc(sizeof(struct mt9t031), GFP_KERNEL); + if (!mt9t031) + return -ENOMEM; + + mt9t031->client = client; + i2c_set_clientdata(client, mt9t031); + + /* Second stage probe - when a capture adapter is there */ + icd = &mt9t031->icd; + icd->ops = &mt9t031_ops; + icd->control = &client->dev; + icd->x_min = MT9T031_COLUMN_SKIP; + icd->y_min = MT9T031_ROW_SKIP; + icd->x_current = icd->x_min; + icd->y_current = icd->y_min; + icd->width_min = MT9T031_MIN_WIDTH; + icd->width_max = MT9T031_MAX_WIDTH; + icd->height_min = MT9T031_MIN_HEIGHT; + icd->height_max = MT9T031_MAX_HEIGHT; + icd->y_skip_top = 0; + icd->iface = icl->bus_id; + /* Simulated autoexposure. If enabled, we calculate shutter width + * ourselves in the driver based on vertical blanking and frame width */ + mt9t031->autoexposure = 1; + + mt9t031->xskip = 1; + mt9t031->yskip = 1; + + ret = soc_camera_device_register(icd); + if (ret) + goto eisdr; + + return 0; + +eisdr: + i2c_set_clientdata(client, NULL); + kfree(mt9t031); + return ret; +} + +static int mt9t031_remove(struct i2c_client *client) +{ + struct mt9t031 *mt9t031 = i2c_get_clientdata(client); + + soc_camera_device_unregister(&mt9t031->icd); + i2c_set_clientdata(client, NULL); + kfree(mt9t031); + + return 0; +} + +static const struct i2c_device_id mt9t031_id[] = { + { "mt9t031", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mt9t031_id); + +static struct i2c_driver mt9t031_i2c_driver = { + .driver = { + .name = "mt9t031", + }, + .probe = mt9t031_probe, + .remove = mt9t031_remove, + .id_table = mt9t031_id, +}; + +static int __init mt9t031_mod_init(void) +{ + return i2c_add_driver(&mt9t031_i2c_driver); +} + +static void __exit mt9t031_mod_exit(void) +{ + i2c_del_driver(&mt9t031_i2c_driver); +} + +module_init(mt9t031_mod_init); +module_exit(mt9t031_mod_exit); + +MODULE_DESCRIPTION("Micron MT9T031 Camera driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL v2"); diff --git a/include/media/v4l2-chip-ident.h b/include/media/v4l2-chip-ident.h index f9d42ad99b4..43dbb659f1f 100644 --- a/include/media/v4l2-chip-ident.h +++ b/include/media/v4l2-chip-ident.h @@ -177,6 +177,7 @@ enum { V4L2_IDENT_MT9M112 = 45008, V4L2_IDENT_MT9V022IX7ATC = 45010, /* No way to detect "normal" I77ATx */ V4L2_IDENT_MT9V022IX7ATM = 45015, /* and "lead free" IA7ATx chips */ + V4L2_IDENT_MT9T031 = 45020, }; #endif -- cgit v1.2.3-70-g09d2