diff options
Diffstat (limited to 'drivers/media/video/tw9910.c')
-rw-r--r-- | drivers/media/video/tw9910.c | 951 |
1 files changed, 951 insertions, 0 deletions
diff --git a/drivers/media/video/tw9910.c b/drivers/media/video/tw9910.c new file mode 100644 index 00000000000..d5cdc4be1a3 --- /dev/null +++ b/drivers/media/video/tw9910.c @@ -0,0 +1,951 @@ +/* + * tw9910 Video Driver + * + * Copyright (C) 2008 Renesas Solutions Corp. + * Kuninori Morimoto <morimoto.kuninori@renesas.com> + * + * Based on ov772x driver, + * + * Copyright (C) 2008 Kuninori Morimoto <morimoto.kuninori@renesas.com> + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net> + * Copyright (C) 2008 Magnus Damm + * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de> + * + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/videodev2.h> +#include <media/v4l2-chip-ident.h> +#include <media/v4l2-common.h> +#include <media/soc_camera.h> +#include <media/tw9910.h> + +#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) + goto tw9910_set_fmt_error; + + /* + * reset hardware + */ + tw9910_reset(priv->client); + ret = tw9910_write_array(priv->client, tw9910_default_regs); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* + * 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) + goto tw9910_set_fmt_error; + + /* + * 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) + goto tw9910_set_fmt_error; + + /* + * set scale + */ + ret = tw9910_set_scale(priv->client, priv->scale); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* + * set cropping + */ + ret = tw9910_set_cropping(priv->client, &tw9910_cropping_ctrl); + if (ret < 0) + goto tw9910_set_fmt_error; + + /* + * set hsync + */ + ret = tw9910_set_hsync(priv->client, &tw9910_hsync_ctrl); + if (ret < 0) + goto tw9910_set_fmt_error; + + return ret; + +tw9910_set_fmt_error: + + tw9910_reset(priv->client); + priv->scale = NULL; + + 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"); |