diff options
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/video/saa7127.c | 852 |
1 files changed, 852 insertions, 0 deletions
diff --git a/drivers/media/video/saa7127.c b/drivers/media/video/saa7127.c new file mode 100644 index 000000000000..2e8b6f2c6e34 --- /dev/null +++ b/drivers/media/video/saa7127.c @@ -0,0 +1,852 @@ +/* + * saa7127 - Philips SAA7127/SAA7129 video encoder driver + * + * Copyright (C) 2003 Roy Bulter <rbulter@hetnet.nl> + * + * Based on SAA7126 video encoder driver by Gillem & Andreas Oberritter + * + * Copyright (C) 2000-2001 Gillem <htoa@gmx.net> + * Copyright (C) 2002 Andreas Oberritter <obi@saftware.de> + * + * Based on Stadis 4:2:2 MPEG-2 Decoder Driver by Nathan Laredo + * + * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org> + * + * This driver is designed for the Hauppauge 250/350 Linux driver + * from the ivtv Project + * + * Copyright (C) 2003 Kevin Thayer <nufan_wfk@yahoo.com> + * + * Dual output support: + * Copyright (C) 2004 Eric Varsanyi + * + * NTSC Tuning and 7.5 IRE Setup + * Copyright (C) 2004 Chris Kennedy <c@groovy.org> + * + * VBI additions & cleanup: + * Copyright (C) 2004, 2005 Hans Verkuil <hverkuil@xs4all.nl> + * + * Note: the saa7126 is identical to the saa7127, and the saa7128 is + * identical to the saa7129, except that the saa7126 and saa7128 have + * macrovision anti-taping support. This driver will almost certainly + * work find for those chips, except of course for the missing anti-taping + * support. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/videodev2.h> +#include <media/i2c-compat.h> +#include <media/v4l2-common.h> + +static int debug = 0; +static int test_image = 0; + +MODULE_DESCRIPTION("Philips SAA7127/9 video encoder driver"); +MODULE_AUTHOR("Kevin Thayer <nufan_wfk@yahoo.com>"); +MODULE_AUTHOR("Chris Kennedy <c@groovy.org>"); +MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>"); +MODULE_LICENSE("GPL"); +module_param(debug, int, 0644); +module_param(test_image, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); +MODULE_PARM_DESC(test_image, "test_image (0-1)"); + +#define saa7127_dbg(fmt, arg...) \ + do { \ + if (debug >= 1) \ + printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); \ + } while (0) + +/* High volume debug. Use with care. */ +#define saa7127_dbg_highvol(fmt, arg...) \ + do { \ + if (debug == 2) \ + printk(KERN_INFO "%s debug %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); \ + } while (0) + +#define saa7127_err(fmt, arg...) do { \ + printk(KERN_ERR "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) +#define saa7127_info(fmt, arg...) do { \ + printk(KERN_INFO "%s %d-%04x: " fmt, client->driver->name, \ + i2c_adapter_id(client->adapter), client->addr , ## arg); } while (0) + +static unsigned short normal_i2c[] = { 0x88 >> 1, I2C_CLIENT_END }; + + +I2C_CLIENT_INSMOD; + +/* + * SAA7127 registers + */ + +#define SAA7127_REG_STATUS 0x00 +#define SAA7127_REG_WIDESCREEN_CONFIG 0x26 +#define SAA7127_REG_WIDESCREEN_ENABLE 0x27 +#define SAA7127_REG_BURST_START 0x28 +#define SAA7127_REG_BURST_END 0x29 +#define SAA7127_REG_COPYGEN_0 0x2a +#define SAA7127_REG_COPYGEN_1 0x2b +#define SAA7127_REG_COPYGEN_2 0x2c +#define SAA7127_REG_OUTPUT_PORT_CONTROL 0x2d +#define SAA7127_REG_GAIN_LUMINANCE_RGB 0x38 +#define SAA7127_REG_GAIN_COLORDIFF_RGB 0x39 +#define SAA7127_REG_INPUT_PORT_CONTROL_1 0x3a +#define SAA7129_REG_FADE_KEY_COL2 0x4f +#define SAA7127_REG_CHROMA_PHASE 0x5a +#define SAA7127_REG_GAINU 0x5b +#define SAA7127_REG_GAINV 0x5c +#define SAA7127_REG_BLACK_LEVEL 0x5d +#define SAA7127_REG_BLANKING_LEVEL 0x5e +#define SAA7127_REG_VBI_BLANKING 0x5f +#define SAA7127_REG_DAC_CONTROL 0x61 +#define SAA7127_REG_BURST_AMP 0x62 +#define SAA7127_REG_SUBC3 0x63 +#define SAA7127_REG_SUBC2 0x64 +#define SAA7127_REG_SUBC1 0x65 +#define SAA7127_REG_SUBC0 0x66 +#define SAA7127_REG_LINE_21_ODD_0 0x67 +#define SAA7127_REG_LINE_21_ODD_1 0x68 +#define SAA7127_REG_LINE_21_EVEN_0 0x69 +#define SAA7127_REG_LINE_21_EVEN_1 0x6a +#define SAA7127_REG_RCV_PORT_CONTROL 0x6b +#define SAA7127_REG_VTRIG 0x6c +#define SAA7127_REG_HTRIG_HI 0x6d +#define SAA7127_REG_MULTI 0x6e +#define SAA7127_REG_CLOSED_CAPTION 0x6f +#define SAA7127_REG_RCV2_OUTPUT_START 0x70 +#define SAA7127_REG_RCV2_OUTPUT_END 0x71 +#define SAA7127_REG_RCV2_OUTPUT_MSBS 0x72 +#define SAA7127_REG_TTX_REQUEST_H_START 0x73 +#define SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH 0x74 +#define SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT 0x75 +#define SAA7127_REG_TTX_ODD_REQ_VERT_START 0x76 +#define SAA7127_REG_TTX_ODD_REQ_VERT_END 0x77 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_START 0x78 +#define SAA7127_REG_TTX_EVEN_REQ_VERT_END 0x79 +#define SAA7127_REG_FIRST_ACTIVE 0x7a +#define SAA7127_REG_LAST_ACTIVE 0x7b +#define SAA7127_REG_MSB_VERTICAL 0x7c +#define SAA7127_REG_DISABLE_TTX_LINE_LO_0 0x7e +#define SAA7127_REG_DISABLE_TTX_LINE_LO_1 0x7f + +/* + ********************************************************************** + * + * Arrays with configuration parameters for the SAA7127 + * + ********************************************************************** + */ + +struct i2c_reg_value { + unsigned char reg; + unsigned char value; +}; + +static const struct i2c_reg_value saa7129_init_config_extra[] = { + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0x38 }, + { SAA7127_REG_VTRIG, 0xfa }, +}; + +static const struct i2c_reg_value saa7127_init_config_common[] = { + { SAA7127_REG_WIDESCREEN_CONFIG, 0x0d }, + { SAA7127_REG_WIDESCREEN_ENABLE, 0x00 }, + { SAA7127_REG_COPYGEN_0, 0x77 }, + { SAA7127_REG_COPYGEN_1, 0x41 }, + { SAA7127_REG_COPYGEN_2, 0x00 }, /* Macrovision enable/disable */ + { SAA7127_REG_OUTPUT_PORT_CONTROL, 0x9e }, + { SAA7127_REG_GAIN_LUMINANCE_RGB, 0x00 }, + { SAA7127_REG_GAIN_COLORDIFF_RGB, 0x00 }, + { SAA7127_REG_INPUT_PORT_CONTROL_1, 0x80 }, /* for color bars */ + { SAA7127_REG_LINE_21_ODD_0, 0x77 }, + { SAA7127_REG_LINE_21_ODD_1, 0x41 }, + { SAA7127_REG_LINE_21_EVEN_0, 0x88 }, + { SAA7127_REG_LINE_21_EVEN_1, 0x41 }, + { SAA7127_REG_RCV_PORT_CONTROL, 0x12 }, + { SAA7127_REG_VTRIG, 0xf9 }, + { SAA7127_REG_HTRIG_HI, 0x00 }, + { SAA7127_REG_RCV2_OUTPUT_START, 0x41 }, + { SAA7127_REG_RCV2_OUTPUT_END, 0xc3 }, + { SAA7127_REG_RCV2_OUTPUT_MSBS, 0x00 }, + { SAA7127_REG_TTX_REQUEST_H_START, 0x3e }, + { SAA7127_REG_TTX_REQUEST_H_DELAY_LENGTH, 0xb8 }, + { SAA7127_REG_CSYNC_ADVANCE_VSYNC_SHIFT, 0x03 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_ODD_REQ_VERT_END, 0x16 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_START, 0x15 }, + { SAA7127_REG_TTX_EVEN_REQ_VERT_END, 0x16 }, + { SAA7127_REG_FIRST_ACTIVE, 0x1a }, + { SAA7127_REG_LAST_ACTIVE, 0x01 }, + { SAA7127_REG_MSB_VERTICAL, 0xc0 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_0, 0x00 }, + { SAA7127_REG_DISABLE_TTX_LINE_LO_1, 0x00 }, + { 0, 0 } +}; + +#define SAA7127_60HZ_DAC_CONTROL 0x15 +static const struct i2c_reg_value saa7127_init_config_60hz[] = { + { SAA7127_REG_BURST_START, 0x19 }, + /* BURST_END is also used as a chip ID in saa7127_detect_client */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0xa3 }, + { SAA7127_REG_GAINU, 0x98 }, + { SAA7127_REG_GAINV, 0xd3 }, + { SAA7127_REG_BLACK_LEVEL, 0x39 }, + { SAA7127_REG_BLANKING_LEVEL, 0x2e }, + { SAA7127_REG_VBI_BLANKING, 0x2e }, + { SAA7127_REG_DAC_CONTROL, 0x15 }, + { SAA7127_REG_BURST_AMP, 0x4d }, + { SAA7127_REG_SUBC3, 0x1f }, + { SAA7127_REG_SUBC2, 0x7c }, + { SAA7127_REG_SUBC1, 0xf0 }, + { SAA7127_REG_SUBC0, 0x21 }, + { SAA7127_REG_MULTI, 0x90 }, + { SAA7127_REG_CLOSED_CAPTION, 0x11 }, + { 0, 0 } +}; + +#define SAA7127_50HZ_DAC_CONTROL 0x02 +struct i2c_reg_value saa7127_init_config_50hz[] = { + { SAA7127_REG_BURST_START, 0x21 }, + /* BURST_END is also used as a chip ID in saa7127_detect_client */ + { SAA7127_REG_BURST_END, 0x1d }, + { SAA7127_REG_CHROMA_PHASE, 0x3f }, + { SAA7127_REG_GAINU, 0x7d }, + { SAA7127_REG_GAINV, 0xaf }, + { SAA7127_REG_BLACK_LEVEL, 0x33 }, + { SAA7127_REG_BLANKING_LEVEL, 0x35 }, + { SAA7127_REG_VBI_BLANKING, 0x35 }, + { SAA7127_REG_DAC_CONTROL, 0x02 }, + { SAA7127_REG_BURST_AMP, 0x2f }, + { SAA7127_REG_SUBC3, 0xcb }, + { SAA7127_REG_SUBC2, 0x8a }, + { SAA7127_REG_SUBC1, 0x09 }, + { SAA7127_REG_SUBC0, 0x2a }, + { SAA7127_REG_MULTI, 0xa0 }, + { SAA7127_REG_CLOSED_CAPTION, 0x00 }, + { 0, 0 } +}; + +/* Enumeration for the Supported input types */ +enum saa7127_input_type { + SAA7127_INPUT_TYPE_NORMAL, + SAA7127_INPUT_TYPE_TEST_IMAGE +}; + +/* Enumeration for the Supported Output signal types */ +enum saa7127_output_type { + SAA7127_OUTPUT_TYPE_BOTH, + SAA7127_OUTPUT_TYPE_COMPOSITE, + SAA7127_OUTPUT_TYPE_SVIDEO, + SAA7127_OUTPUT_TYPE_RGB, + SAA7127_OUTPUT_TYPE_YUV_C, + SAA7127_OUTPUT_TYPE_YUV_V +}; + +/* + ********************************************************************** + * + * Encoder Struct, holds the configuration state of the encoder + * + ********************************************************************** + */ + +struct saa7127_state { + v4l2_std_id std; + enum v4l2_chip_ident ident; + enum saa7127_input_type input_type; + enum saa7127_output_type output_type; + int video_enable; + int wss_enable; + u16 wss_mode; + int cc_enable; + u16 cc_data; + int xds_enable; + u16 xds_data; + int vps_enable; + u8 vps_data[5]; + u8 reg_2d; + u8 reg_3a; + u8 reg_3a_cb; /* colorbar bit */ + u8 reg_61; +}; + +static const char * const output_strs[] = +{ + "S-Video + Composite", + "Composite", + "S-Video", + "RGB", + "YUV C", + "YUV V" +}; + +static const char * const wss_strs[] = { + "invalid", + "letterbox 14:9 center", + "letterbox 14:9 top", + "invalid", + "letterbox 16:9 top", + "invalid", + "invalid", + "16:9 full format anamorphic" + "4:3 full format", + "invalid", + "invalid", + "letterbox 16:9 center", + "invalid", + "letterbox >16:9 center", + "14:9 full format center", + "invalid", +}; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_read(struct i2c_client *client, u8 reg) +{ + return i2c_smbus_read_byte_data(client, reg); +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write(struct i2c_client *client, u8 reg, u8 val) +{ + int i; + + for (i = 0; i < 3; i++) { + if (i2c_smbus_write_byte_data(client, reg, val) == 0) + return 0; + } + saa7127_err("I2C Write Problem\n"); + return -1; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_write_inittab(struct i2c_client *client, + const struct i2c_reg_value *regs) +{ + while (regs->reg != 0) { + saa7127_write(client, regs->reg, regs->value); + regs++; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_vps(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 16)) + return -EINVAL; + if (state->vps_enable != enable) { + saa7127_dbg("Turn VPS Signal %s\n", enable ? "on" : "off"); + saa7127_write(client, 0x54, enable << 7); + state->vps_enable = enable; + } + if (!enable) + return 0; + + state->vps_data[0] = data->data[4]; + state->vps_data[1] = data->data[10]; + state->vps_data[2] = data->data[11]; + state->vps_data[3] = data->data[12]; + state->vps_data[4] = data->data[13]; + saa7127_dbg("Set VPS data %02x %02x %02x %02x %02x\n", + state->vps_data[0], state->vps_data[1], + state->vps_data[2], state->vps_data[3], + state->vps_data[4]); + saa7127_write(client, 0x55, state->vps_data[0]); + saa7127_write(client, 0x56, state->vps_data[1]); + saa7127_write(client, 0x57, state->vps_data[2]); + saa7127_write(client, 0x58, state->vps_data[3]); + saa7127_write(client, 0x59, state->vps_data[4]); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_cc(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + u16 cc = data->data[0] << 8 | data->data[1]; + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 21)) + return -EINVAL; + if (state->cc_enable != enable) { + saa7127_dbg("Turn CC %s\n", enable ? "on" : "off"); + saa7127_write(client, SAA7127_REG_CLOSED_CAPTION, + (enable << 6) | 0x11); + state->cc_enable = enable; + } + if (!enable) + return 0; + + saa7127_dbg_highvol("CC data: %04x\n", cc); + saa7127_write(client, SAA7127_REG_LINE_21_ODD_0, cc & 0xff); + saa7127_write(client, SAA7127_REG_LINE_21_ODD_1, cc >> 8); + state->cc_data = cc; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_xds(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + u16 xds = data->data[1] << 8 | data->data[0]; + int enable = (data->line != 0); + + if (enable && (data->field != 1 || data->line != 21)) + return -EINVAL; + if (state->xds_enable != enable) { + saa7127_dbg("Turn XDS %s\n", enable ? "on" : "off"); + saa7127_write(client, SAA7127_REG_CLOSED_CAPTION, + (enable << 7) | 0x11); + state->xds_enable = enable; + } + if (!enable) + return 0; + + saa7127_dbg_highvol("XDS data: %04x\n", xds); + saa7127_write(client, SAA7127_REG_LINE_21_EVEN_0, xds & 0xff); + saa7127_write(client, SAA7127_REG_LINE_21_EVEN_1, xds >> 8); + state->xds_data = xds; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_wss(struct i2c_client *client, struct v4l2_sliced_vbi_data *data) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + int enable = (data->line != 0); + + if (enable && (data->field != 0 || data->line != 23)) + return -EINVAL; + if (state->wss_enable != enable) { + saa7127_dbg("Turn WSS %s\n", enable ? "on" : "off"); + saa7127_write(client, 0x27, enable << 7); + state->wss_enable = enable; + } + if (!enable) + return 0; + + saa7127_write(client, 0x26, data->data[0]); + saa7127_write(client, 0x27, 0x80 | (data->data[1] & 0x3f)); + saa7127_dbg("WSS mode: %s\n", wss_strs[data->data[0] & 0xf]); + state->wss_mode = (data->data[1] & 0x3f) << 8 | data->data[0]; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_video_enable(struct i2c_client *client, int enable) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + + if (enable) { + saa7127_dbg("Enable Video Output\n"); + saa7127_write(client, 0x2d, state->reg_2d); + saa7127_write(client, 0x61, state->reg_61); + } else { + saa7127_dbg("Disable Video Output\n"); + saa7127_write(client, 0x2d, (state->reg_2d & 0xf0)); + saa7127_write(client, 0x61, (state->reg_61 | 0xc0)); + } + state->video_enable = enable; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_std(struct i2c_client *client, v4l2_std_id std) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + const struct i2c_reg_value *inittab; + + if (std & V4L2_STD_525_60) { + saa7127_dbg("Selecting 60 Hz video Standard\n"); + inittab = saa7127_init_config_60hz; + state->reg_61 = SAA7127_60HZ_DAC_CONTROL; + } else { + saa7127_dbg("Selecting 50 Hz video Standard\n"); + inittab = saa7127_init_config_50hz; + state->reg_61 = SAA7127_50HZ_DAC_CONTROL; + } + + /* Write Table */ + saa7127_write_inittab(client, inittab); + state->std = std; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_output_type(struct i2c_client *client, int output) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + + switch (output) { + case SAA7127_OUTPUT_TYPE_RGB: + state->reg_2d = 0x0f; /* RGB + CVBS (for sync) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_COMPOSITE: + state->reg_2d = 0x08; /* 00001000 CVBS only, RGB DAC's off (high impedance mode) */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_SVIDEO: + state->reg_2d = 0xff; /* 11111111 croma -> R, luma -> CVBS + G + B */ + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_V: + state->reg_2d = 0x4f; /* reg 2D = 01001111, all DAC's on, RGB + VBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_YUV_C: + state->reg_2d = 0x0f; /* reg 2D = 00001111, all DAC's on, RGB + CVBS */ + state->reg_3a = 0x0b; /* reg 3A = 00001011, bypass RGB-matrix */ + break; + + case SAA7127_OUTPUT_TYPE_BOTH: + state->reg_2d = 0xbf; + state->reg_3a = 0x13; /* by default switch YUV to RGB-matrix on */ + break; + + default: + return -EINVAL; + } + saa7127_dbg("Selecting %s output type\n", output_strs[output]); + + /* Configure Encoder */ + saa7127_write(client, 0x2d, state->reg_2d); + saa7127_write(client, 0x3a, state->reg_3a | state->reg_3a_cb); + state->output_type = output; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_set_input_type(struct i2c_client *client, int input) +{ + struct saa7127_state *state = (struct saa7127_state *)i2c_get_clientdata(client); + + switch (input) { + case SAA7127_INPUT_TYPE_NORMAL: /* avia */ + saa7127_dbg("Selecting Normal Encoder Input\n"); + state->reg_3a_cb = 0; + break; + + case SAA7127_INPUT_TYPE_TEST_IMAGE: /* color bar */ + saa7127_dbg("Selecting Color Bar generator\n"); + state->reg_3a_cb = 0x80; + break; + + default: + return -EINVAL; + } + saa7127_write(client, 0x3a, state->reg_3a | state->reg_3a_cb); + state->input_type = input; + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_command(struct i2c_client *client, + unsigned int cmd, void *arg) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + struct v4l2_format *fmt = arg; + int *iarg = arg; + + switch (cmd) { + case VIDIOC_S_STD: + if (state->std == *(v4l2_std_id *)arg) + break; + return saa7127_set_std(client, *(v4l2_std_id *)arg); + + case VIDIOC_G_STD: + *(v4l2_std_id *)arg = state->std; + break; + + case VIDIOC_S_INPUT: + if (state->input_type == *iarg) + break; + return saa7127_set_input_type(client, *iarg); + + case VIDIOC_S_OUTPUT: + if (state->output_type == *iarg) + break; + return saa7127_set_output_type(client, *iarg); + + case VIDIOC_STREAMON: + case VIDIOC_STREAMOFF: + if (state->video_enable == (cmd == VIDIOC_STREAMON)) + break; + return saa7127_set_video_enable(client, cmd == VIDIOC_STREAMON); + + case VIDIOC_G_FMT: + if (fmt->type != V4L2_BUF_TYPE_SLICED_VBI_CAPTURE) + return -EINVAL; + + memset(&fmt->fmt.sliced, 0, sizeof(fmt->fmt.sliced)); + if (state->vps_enable) + fmt->fmt.sliced.service_lines[0][16] = V4L2_SLICED_VPS; + if (state->wss_enable) + fmt->fmt.sliced.service_lines[0][23] = V4L2_SLICED_WSS_625; + if (state->cc_enable) { + fmt->fmt.sliced.service_lines[0][21] = V4L2_SLICED_CAPTION_525; + fmt->fmt.sliced.service_lines[1][21] = V4L2_SLICED_CAPTION_525; + } + fmt->fmt.sliced.service_set = + (state->vps_enable ? V4L2_SLICED_VPS : 0) | + (state->wss_enable ? V4L2_SLICED_WSS_625 : 0) | + (state->cc_enable ? V4L2_SLICED_CAPTION_525 : 0); + break; + + case VIDIOC_LOG_STATUS: + saa7127_info("Standard: %s\n", (state->std & V4L2_STD_525_60) ? "60 Hz" : "50 Hz"); + saa7127_info("Input: %s\n", state->input_type ? "color bars" : "normal"); + saa7127_info("Output: %s\n", state->video_enable ? + output_strs[state->output_type] : "disabled"); + saa7127_info("WSS: %s\n", state->wss_enable ? + wss_strs[state->wss_mode] : "disabled"); + saa7127_info("VPS: %s\n", state->vps_enable ? "enabled" : "disabled"); + saa7127_info("CC: %s\n", state->cc_enable ? "enabled" : "disabled"); + break; + +#ifdef CONFIG_VIDEO_ADV_DEBUG + case VIDIOC_INT_G_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_SAA7127) + return -EINVAL; + reg->val = saa7127_read(client, reg->reg & 0xff); + break; + } + + case VIDIOC_INT_S_REGISTER: + { + struct v4l2_register *reg = arg; + + if (reg->i2c_id != I2C_DRIVERID_SAA7127) + return -EINVAL; + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + saa7127_write(client, reg->reg & 0xff, reg->val & 0xff); + break; + } +#endif + + case VIDIOC_INT_S_VBI_DATA: + { + struct v4l2_sliced_vbi_data *data = arg; + + switch (data->id) { + case V4L2_SLICED_WSS_625: + return saa7127_set_wss(client, data); + case V4L2_SLICED_VPS: + return saa7127_set_vps(client, data); + case V4L2_SLICED_CAPTION_525: + if (data->field == 0) + return saa7127_set_cc(client, data); + return saa7127_set_xds(client, data); + default: + return -EINVAL; + } + break; + } + + case VIDIOC_INT_G_CHIP_IDENT: + *(enum v4l2_chip_ident *)arg = state->ident; + break; + + default: + return -EINVAL; + } + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_saa7127; + +/* ----------------------------------------------------------------------- */ + +static int saa7127_attach(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client; + struct saa7127_state *state; + struct v4l2_sliced_vbi_data vbi = { 0, 0, 0, 0 }; /* set to disabled */ + int read_result = 0; + + /* Check if the adapter supports the needed features */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return 0; + + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (client == 0) + return -ENOMEM; + + memset(client, 0, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + client->driver = &i2c_driver_saa7127; + client->flags = I2C_CLIENT_ALLOW_USE; + snprintf(client->name, sizeof(client->name) - 1, "saa7127"); + + saa7127_dbg("detecting saa7127 client on address 0x%x\n", address << 1); + + /* First test register 0: Bits 5-7 are a version ID (should be 0), + and bit 2 should also be 0. + This is rather general, so the second test is more specific and + looks at the 'ending point of burst in clock cycles' which is + 0x1d after a reset and not expected to ever change. */ + if ((saa7127_read(client, 0) & 0xe4) != 0 || + (saa7127_read(client, 0x29) & 0x3f) != 0x1d) { + saa7127_dbg("saa7127 not found\n"); + kfree(client); + return 0; + } + state = kmalloc(sizeof(struct saa7127_state), GFP_KERNEL); + + if (state == NULL) { + kfree(client); + return (-ENOMEM); + } + + i2c_set_clientdata(client, state); + memset(state, 0, sizeof(struct saa7127_state)); + + /* Configure Encoder */ + + saa7127_dbg("Configuring encoder\n"); + saa7127_write_inittab(client, saa7127_init_config_common); + saa7127_set_std(client, V4L2_STD_NTSC); + saa7127_set_output_type(client, SAA7127_OUTPUT_TYPE_BOTH); + saa7127_set_vps(client, &vbi); + saa7127_set_wss(client, &vbi); + saa7127_set_cc(client, &vbi); + saa7127_set_xds(client, &vbi); + if (test_image == 1) { + /* The Encoder has an internal Colorbar generator */ + /* This can be used for debugging */ + saa7127_set_input_type(client, SAA7127_INPUT_TYPE_TEST_IMAGE); + } else { + saa7127_set_input_type(client, SAA7127_INPUT_TYPE_NORMAL); + } + saa7127_set_video_enable(client, 1); + + /* Detect if it's an saa7129 */ + read_result = saa7127_read(client, SAA7129_REG_FADE_KEY_COL2); + saa7127_write(client, SAA7129_REG_FADE_KEY_COL2, 0xaa); + if (saa7127_read(client, SAA7129_REG_FADE_KEY_COL2) == 0xaa) { + saa7127_info("saa7129 found @ 0x%x (%s)\n", address << 1, adapter->name); + saa7127_write(client, SAA7129_REG_FADE_KEY_COL2, read_result); + saa7127_write_inittab(client, saa7129_init_config_extra); + state->ident = V4L2_IDENT_SAA7129; + } else { + saa7127_info("saa7127 found @ 0x%x (%s)\n", address << 1, adapter->name); + state->ident = V4L2_IDENT_SAA7127; + } + + i2c_attach_client(client); + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_probe(struct i2c_adapter *adapter) +{ +#ifdef I2C_CLASS_TV_ANALOG + if (adapter->class & I2C_CLASS_TV_ANALOG) +#else + if (adapter->id == I2C_HW_B_BT848) +#endif + return i2c_probe(adapter, &addr_data, saa7127_attach); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static int saa7127_detach(struct i2c_client *client) +{ + struct saa7127_state *state = i2c_get_clientdata(client); + int err; + + /* Turn off TV output */ + saa7127_set_video_enable(client, 0); + + err = i2c_detach_client(client); + + if (err) { + return err; + } + + kfree(state); + kfree(client); + return 0; +} + +/* ----------------------------------------------------------------------- */ + +struct i2c_driver i2c_driver_saa7127 = { + .name = "saa7127", + .id = I2C_DRIVERID_SAA7127, + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa7127_probe, + .detach_client = saa7127_detach, + .command = saa7127_command, + .owner = THIS_MODULE, +}; + + +/* ----------------------------------------------------------------------- */ + +static int __init saa7127_init_module(void) +{ + return i2c_add_driver(&i2c_driver_saa7127); +} + +/* ----------------------------------------------------------------------- */ + +static void __exit saa7127_cleanup_module(void) +{ + i2c_del_driver(&i2c_driver_saa7127); +} + +/* ----------------------------------------------------------------------- */ + +module_init(saa7127_init_module); +module_exit(saa7127_cleanup_module); |