diff options
author | Michael Hunold <hunold@linuxtv.org> | 2006-03-08 22:28:04 +0100 |
---|---|---|
committer | Michael Hunold <hunold@linuxtv.org> | 2006-03-08 22:28:04 +0100 |
commit | fb1c40c7fb9da899745ca1eab318fe9009f886d1 (patch) | |
tree | 6a1e9252ae81b6dd8a5d831d95d6108124eb92c9 /linux/drivers/media/video | |
parent | f780713336a2db00609e7bb63648fbd96c5a856d (diff) | |
download | mediapointer-dvb-s2-fb1c40c7fb9da899745ca1eab318fe9009f886d1.tar.gz mediapointer-dvb-s2-fb1c40c7fb9da899745ca1eab318fe9009f886d1.tar.bz2 |
Add mxb, dpc7146, hexium_gemini and hexium_orion from 2.6.15
From: Michael Hunold <hunold@linuxtv.org>
Signed-off-by: Michael Hunold <hunold@linuxtv.org>
Diffstat (limited to 'linux/drivers/media/video')
-rw-r--r-- | linux/drivers/media/video/dpc7146.c | 401 | ||||
-rw-r--r-- | linux/drivers/media/video/hexium_gemini.c | 556 | ||||
-rw-r--r-- | linux/drivers/media/video/hexium_orion.c | 522 | ||||
-rw-r--r-- | linux/drivers/media/video/mxb.c | 1028 | ||||
-rw-r--r-- | linux/drivers/media/video/mxb.h | 42 | ||||
-rw-r--r-- | linux/drivers/media/video/tda9840.c | 253 | ||||
-rw-r--r-- | linux/drivers/media/video/tda9840.h | 35 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6415c.c | 222 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6415c.h | 39 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6420.c | 199 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6420.h | 17 |
11 files changed, 3314 insertions, 0 deletions
diff --git a/linux/drivers/media/video/dpc7146.c b/linux/drivers/media/video/dpc7146.c new file mode 100644 index 000000000..a14b51f0e --- /dev/null +++ b/linux/drivers/media/video/dpc7146.c @@ -0,0 +1,401 @@ +/* + dpc7146.c - v4l2 driver for the dpc7146 demonstration board + + Copyright (C) 2000-2003 Michael Hunold <michael@mihu.de> + + 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. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> +#include <linux/video_decoder.h> /* for saa7111a */ + +#define I2C_SAA7111A 0x24 + +/* All unused bytes are reserverd. */ +#define SAA711X_CHIP_VERSION 0x00 +#define SAA711X_ANALOG_INPUT_CONTROL_1 0x02 +#define SAA711X_ANALOG_INPUT_CONTROL_2 0x03 +#define SAA711X_ANALOG_INPUT_CONTROL_3 0x04 +#define SAA711X_ANALOG_INPUT_CONTROL_4 0x05 +#define SAA711X_HORIZONTAL_SYNC_START 0x06 +#define SAA711X_HORIZONTAL_SYNC_STOP 0x07 +#define SAA711X_SYNC_CONTROL 0x08 +#define SAA711X_LUMINANCE_CONTROL 0x09 +#define SAA711X_LUMINANCE_BRIGHTNESS 0x0A +#define SAA711X_LUMINANCE_CONTRAST 0x0B +#define SAA711X_CHROMA_SATURATION 0x0C +#define SAA711X_CHROMA_HUE_CONTROL 0x0D +#define SAA711X_CHROMA_CONTROL 0x0E +#define SAA711X_FORMAT_DELAY_CONTROL 0x10 +#define SAA711X_OUTPUT_CONTROL_1 0x11 +#define SAA711X_OUTPUT_CONTROL_2 0x12 +#define SAA711X_OUTPUT_CONTROL_3 0x13 +#define SAA711X_V_GATE_1_START 0x15 +#define SAA711X_V_GATE_1_STOP 0x16 +#define SAA711X_V_GATE_1_MSB 0x17 +#define SAA711X_TEXT_SLICER_STATUS 0x1A +#define SAA711X_DECODED_BYTES_OF_TS_1 0x1B +#define SAA711X_DECODED_BYTES_OF_TS_2 0x1C +#define SAA711X_STATUS_BYTE 0x1F + +#define DPC_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +static int dpc_num = 0; + +#define DPC_INPUTS 2 +static struct v4l2_input dpc_inputs[DPC_INPUTS] = { + { 0, "Port A", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 1, "Port B", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +#define DPC_AUDIOS 0 + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_STD, SAA7146_AFTER }, + { 0, 0 } +}; + +struct dpc +{ + struct video_device *video_dev; + struct video_device *vbi_dev; + + struct i2c_adapter i2c_adapter; + struct i2c_client *saa7111a; + + int cur_input; /* current input */ +}; + +/* fixme: add vbi stuff here */ +static int dpc_probe(struct saa7146_dev* dev) +{ + struct dpc* dpc = NULL; + struct i2c_client *client; + struct list_head *item; + + dpc = (struct dpc*)kmalloc(sizeof(struct dpc), GFP_KERNEL); + if( NULL == dpc ) { + printk("dpc_v4l2.o: dpc_probe: not enough kernel memory.\n"); + return -ENOMEM; + } + memset(dpc, 0x0, sizeof(struct dpc)); + + /* FIXME: enable i2c-port pins, video-port-pins + video port pins should be enabled here ?! */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + dpc->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "dpc7146", + }; + saa7146_i2c_adapter_prepare(dev, &dpc->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if(i2c_add_adapter(&dpc->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(dpc); + return -EFAULT; + } + + /* loop through all i2c-devices on the bus and look who is there */ + list_for_each(item,&dpc->i2c_adapter.clients) { + client = list_entry(item, struct i2c_client, list); + if( I2C_SAA7111A == client->addr ) + dpc->saa7111a = client; + } + + /* check if all devices are present */ + if( 0 == dpc->saa7111a ) { + DEB_D(("dpc_v4l2.o: dpc_attach failed for this device.\n")); + i2c_del_adapter(&dpc->i2c_adapter); + kfree(dpc); + return -ENODEV; + } + + /* all devices are present, probe was successful */ + DEB_D(("dpc_v4l2.o: dpc_probe succeeded for this device.\n")); + + /* we store the pointer in our private data field */ + dev->ext_priv = dpc; + + return 0; +} + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int dpc_init_done(struct saa7146_dev* dev) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + DEB_D(("dpc_v4l2.o: dpc_init_done called.\n")); + + /* initialize the helper ics to useful values */ + i2c_smbus_write_byte_data(dpc->saa7111a, 0x00, 0x11); + + i2c_smbus_write_byte_data(dpc->saa7111a, 0x02, 0xc0); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x03, 0x30); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x04, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x05, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x06, 0xde); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x07, 0xad); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x08, 0xa8); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x09, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0a, 0x80); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0b, 0x47); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0c, 0x40); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0d, 0x00); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x0e, 0x03); + + i2c_smbus_write_byte_data(dpc->saa7111a, 0x10, 0xd0); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x11, 0x1c); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x12, 0xc1); + i2c_smbus_write_byte_data(dpc->saa7111a, 0x13, 0x30); + + i2c_smbus_write_byte_data(dpc->saa7111a, 0x1f, 0x81); + + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int dpc_attach(struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + DEB_D(("dpc_v4l2.o: dpc_attach called.\n")); + + /* checking for i2c-devices can be omitted here, because we + already did this in "dpc_vl42_probe" */ + + saa7146_vv_init(dev,&vv_data); + if( 0 != saa7146_register_device(&dpc->video_dev, dev, "dpc", VFL_TYPE_GRABBER)) { + ERR(("cannot register capture v4l2 device. skipping.\n")); + return -1; + } + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if( 0 != DPC_BOARD_CAN_DO_VBI(dev)) { + if( 0 != saa7146_register_device(&dpc->vbi_dev, dev, "dpc", VFL_TYPE_VBI)) { + ERR(("cannot register vbi v4l2 device. skipping.\n")); + } + } + + i2c_use_client(dpc->saa7111a); + + printk("dpc: found 'dpc7146 demonstration board'-%d.\n",dpc_num); + dpc_num++; + + /* the rest */ + dpc->cur_input = 0; + dpc_init_done(dev); + + return 0; +} + +static int dpc_detach(struct saa7146_dev* dev) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + DEB_EE(("dev:%p\n",dev)); + + i2c_release_client(dpc->saa7111a); + + saa7146_unregister_device(&dpc->video_dev,dev); + if( 0 != DPC_BOARD_CAN_DO_VBI(dev)) { + saa7146_unregister_device(&dpc->vbi_dev,dev); + } + saa7146_vv_release(dev); + + dpc_num--; + + i2c_del_adapter(&dpc->i2c_adapter); + kfree(dpc); + return 0; +} + +#ifdef axa +int dpc_vbi_bypass(struct saa7146_dev* dev) +{ + struct dpc* dpc = (struct dpc*)dev->ext_priv; + + int i = 1; + + /* switch bypass in saa7111a */ + if ( 0 != dpc->saa7111a->driver->command(dpc->saa7111a,SAA711X_VBI_BYPASS, &i)) { + printk("dpc_v4l2.o: VBI_BYPASS: could not address saa7111a.\n"); + return -1; + } + + return 0; +} +#endif + +static int dpc_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct dpc* dpc = (struct dpc*)dev->ext_priv; +/* + struct saa7146_vv *vv = dev->vv_data; +*/ + switch(cmd) + { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + DEB_EE(("VIDIOC_ENUMINPUT %d.\n",i->index)); + + if( i->index < 0 || i->index >= DPC_INPUTS) { + return -EINVAL; + } + + memcpy(i, &dpc_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D(("dpc_v4l2.o: v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n",i->index)); + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *)arg; + *input = dpc->cur_input; + + DEB_D(("dpc_v4l2.o: VIDIOC_G_INPUT: %d\n",*input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *)arg; + + if (input < 0 || input >= DPC_INPUTS) { + return -EINVAL; + } + + dpc->cur_input = input; + + /* fixme: switch input here, switch audio, too! */ +// saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, input_port_selection[input].hps_sync); + printk("dpc_v4l2.o: VIDIOC_S_INPUT: fixme switch input.\n"); + + return 0; + } + default: +/* + DEB_D(("dpc_v4l2.o: v4l2_ioctl does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) +{ + return 0; +} + +static struct saa7146_standard standard[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x14, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +static struct saa7146_extension extension; + +static struct saa7146_pci_extension_data dpc = { + .ext_priv = "Multimedia eXtension Board", + .ext = &extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long)&dpc, + }, { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = DPC_INPUTS, + .capabilities = V4L2_CAP_VBI_CAPTURE, + .stds = &standard[0], + .num_stds = sizeof(standard)/sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = dpc_ioctl, +}; + +static struct saa7146_extension extension = { + .name = "dpc7146 demonstration board", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = dpc_probe, + .attach = dpc_attach, + .detach = dpc_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init dpc_init_module(void) +{ + if( 0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit dpc_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(dpc_init_module); +module_exit(dpc_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for the 'dpc7146 demonstration board'"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/hexium_gemini.c b/linux/drivers/media/video/hexium_gemini.c new file mode 100644 index 000000000..34598e608 --- /dev/null +++ b/linux/drivers/media/video/hexium_gemini.c @@ -0,0 +1,556 @@ +/* + hexium_gemini.c - v4l2 driver for Hexium Gemini frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold <michael@mihu.de> + + 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. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num = 0; + +#define HEXIUM_GEMINI 4 +#define HEXIUM_GEMINI_DUAL 5 + +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_QUERYCTRL, SAA7146_BEFORE }, + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_STD, SAA7146_AFTER }, + { VIDIOC_G_CTRL, SAA7146_BEFORE }, + { VIDIOC_S_CTRL, SAA7146_BEFORE }, + { 0, 0 } +}; + +#define HEXIUM_CONTROLS 1 +static struct v4l2_queryctrl hexium_controls[] = { + { V4L2_CID_PRIVATE_BASE, V4L2_CTRL_TYPE_BOOLEAN, "B/W", 0, 1, 1, 0, 0 }, +}; + +#define HEXIUM_GEMINI_V_1_0 1 +#define HEXIUM_GEMINI_DUAL_V_1_0 2 + +struct hexium +{ + int type; + + struct video_device *video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ + v4l2_std_id cur_std; /* current standard */ + int cur_bw; /* current black/white status */ +}; + +/* Samsung KS0127B decoder default registers */ +static u8 hexium_ks0127b[0x100]={ +/*00*/ 0x00,0x52,0x30,0x40,0x01,0x0C,0x2A,0x10, +/*08*/ 0x00,0x00,0x00,0x60,0x00,0x00,0x0F,0x06, +/*10*/ 0x00,0x00,0xE4,0xC0,0x00,0x00,0x00,0x00, +/*18*/ 0x14,0x9B,0xFE,0xFF,0xFC,0xFF,0x03,0x22, +/*20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*28*/ 0x00,0x00,0x00,0x00,0x00,0x2C,0x9B,0x00, +/*30*/ 0x00,0x00,0x10,0x80,0x80,0x10,0x80,0x80, +/*38*/ 0x01,0x04,0x00,0x00,0x00,0x29,0xC0,0x00, +/*40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*48*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*50*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*58*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*68*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*70*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*78*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*88*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*90*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*98*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*A8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*B8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*C8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*D8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*E8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, +/*F8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +static struct hexium_data hexium_pal[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_pal_bw[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_ntsc[] = { + { 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_ntsc_bw[] = { + { 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_secam[] = { + { 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } +}; + +static struct hexium_data hexium_input_select[] = { + { 0x02, 0x60 }, + { 0x02, 0x64 }, + { 0x02, 0x61 }, + { 0x02, 0x65 }, + { 0x02, 0x62 }, + { 0x02, 0x66 }, + { 0x02, 0x68 }, + { 0x02, 0x69 }, + { 0x02, 0x6A }, +}; + +/* fixme: h_offset = 0 for Hexium Gemini *Dual*, which + are currently *not* supported*/ +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 28, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 28, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D(("hexium_init_done called.\n")); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_ks0127b); i++) { + data.byte = hexium_ks0127b[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + printk("hexium_gemini: hexium_init_done() failed for address 0x%02x\n", i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + + DEB_D((".\n")); + + data.byte = hexium_input_select[input].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, hexium_input_select[input].adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + + return 0; +} + +static int hexium_set_standard(struct hexium *hexium, struct hexium_data *vdec) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D((".\n")); + + while (vdec[i].adr != -1) { + data.byte = vdec[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, vdec[i].adr, I2C_SMBUS_BYTE_DATA, &data)) { + printk("hexium_init_done: hexium_set_standard() failed for address 0x%02x\n", i); + return -1; + } + i++; + } + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE((".\n")); + + hexium = (struct hexium *) kmalloc(sizeof(struct hexium), GFP_KERNEL); + if (NULL == hexium) { + printk("hexium_gemini: not enough kernel memory in hexium_attach().\n"); + return -ENOMEM; + } + memset(hexium, 0x0, sizeof(struct hexium)); + dev->ext_priv = hexium; + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + hexium->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "hexium gemini", + }; + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(hexium); + return -EFAULT; + } + + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + saa7146_write(dev, DD1_INIT, 0x07000700); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + + hexium_set_input(hexium, 0); + hexium->cur_input = 0; + + saa7146_vv_init(dev, &vv_data); + if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium gemini", VFL_TYPE_GRABBER)) { + printk("hexium_gemini: cannot register capture v4l2 device. skipping.\n"); + return -1; + } + + printk("hexium_gemini: found 'hexium gemini' frame grabber-%d.\n", hexium_num); + hexium_num++; + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE(("dev:%p\n", dev)); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int hexium_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; +/* + struct saa7146_vv *vv = dev->vv_data; +*/ + switch (cmd) { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + DEB_EE(("VIDIOC_ENUMINPUT %d.\n", i->index)); + + if (i->index < 0 || i->index >= HEXIUM_INPUTS) { + return -EINVAL; + } + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n", i->index)); + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *) arg; + *input = hexium->cur_input; + + DEB_D(("VIDIOC_G_INPUT: %d\n", *input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *) arg; + + DEB_EE(("VIDIOC_S_INPUT %d.\n", input)); + + if (input < 0 || input >= HEXIUM_INPUTS) { + return -EINVAL; + } + + hexium->cur_input = input; + hexium_set_input(hexium, input); + + return 0; + } + /* the saa7146 provides some controls (brightness, contrast, saturation) + which gets registered *after* this function. because of this we have + to return with a value != 0 even if the function succeded.. */ + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *qc = arg; + int i; + + for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { + if (hexium_controls[i].id == qc->id) { + *qc = hexium_controls[i]; + DEB_D(("VIDIOC_QUERYCTRL %d.\n", qc->id)); + return 0; + } + } + return -EAGAIN; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *vc = arg; + int i; + + for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { + if (hexium_controls[i].id == vc->id) { + break; + } + } + + if (i < 0) { + return -EAGAIN; + } + + switch (vc->id) { + case V4L2_CID_PRIVATE_BASE:{ + vc->value = hexium->cur_bw; + DEB_D(("VIDIOC_G_CTRL BW:%d.\n", vc->value)); + return 0; + } + } + return -EINVAL; + } + + case VIDIOC_S_CTRL: + { + struct v4l2_control *vc = arg; + int i = 0; + + for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { + if (hexium_controls[i].id == vc->id) { + break; + } + } + + if (i < 0) { + return -EAGAIN; + } + + switch (vc->id) { + case V4L2_CID_PRIVATE_BASE:{ + hexium->cur_bw = vc->value; + break; + } + } + + DEB_D(("VIDIOC_S_CTRL BW:%d.\n", hexium->cur_bw)); + + if (0 == hexium->cur_bw && V4L2_STD_PAL == hexium->cur_std) { + hexium_set_standard(hexium, hexium_pal); + return 0; + } + if (0 == hexium->cur_bw && V4L2_STD_NTSC == hexium->cur_std) { + hexium_set_standard(hexium, hexium_ntsc); + return 0; + } + if (0 == hexium->cur_bw && V4L2_STD_SECAM == hexium->cur_std) { + hexium_set_standard(hexium, hexium_secam); + return 0; + } + if (1 == hexium->cur_bw && V4L2_STD_PAL == hexium->cur_std) { + hexium_set_standard(hexium, hexium_pal_bw); + return 0; + } + if (1 == hexium->cur_bw && V4L2_STD_NTSC == hexium->cur_std) { + hexium_set_standard(hexium, hexium_ntsc_bw); + return 0; + } + if (1 == hexium->cur_bw && V4L2_STD_SECAM == hexium->cur_std) { + /* fixme: is there no bw secam mode? */ + return -EINVAL; + } + + return -EINVAL; + } + default: +/* + DEB_D(("hexium_ioctl() does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + if (V4L2_STD_PAL == std->id) { + hexium_set_standard(hexium, hexium_pal); + hexium->cur_std = V4L2_STD_PAL; + return 0; + } else if (V4L2_STD_NTSC == std->id) { + hexium_set_standard(hexium, hexium_ntsc); + hexium->cur_std = V4L2_STD_NTSC; + return 0; + } else if (V4L2_STD_SECAM == std->id) { + hexium_set_standard(hexium, hexium_secam); + hexium->cur_std = V4L2_STD_SECAM; + return 0; + } + + return -1; +} + +static struct saa7146_extension hexium_extension; + +static struct saa7146_pci_extension_data hexium_gemini_4bnc = { + .ext_priv = "Hexium Gemini (4 BNC)", + .ext = &hexium_extension, +}; + +static struct saa7146_pci_extension_data hexium_gemini_dual_4bnc = { + .ext_priv = "Hexium Gemini Dual (4 BNC)", + .ext = &hexium_extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2401, + .driver_data = (unsigned long) &hexium_gemini_4bnc, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2402, + .driver_data = (unsigned long) &hexium_gemini_dual_4bnc, + }, + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = sizeof(hexium_standards) / sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = hexium_ioctl, +}; + +static struct saa7146_extension hexium_extension = { + .name = "hexium gemini", + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&hexium_extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&hexium_extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Gemini frame grabber cards"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/hexium_orion.c b/linux/drivers/media/video/hexium_orion.c new file mode 100644 index 000000000..8c9bdcf72 --- /dev/null +++ b/linux/drivers/media/video/hexium_orion.c @@ -0,0 +1,522 @@ +/* + hexium_orion.c - v4l2 driver for the Hexium Orion frame grabber cards + + Visit http://www.mihu.de/linux/saa7146/ and follow the link + to "hexium" for further details about this card. + + Copyright (C) 2003 Michael Hunold <michael@mihu.de> + + 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. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> + +static int debug = 0; +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +static int hexium_num = 0; + +#define HEXIUM_HV_PCI6_ORION 1 +#define HEXIUM_ORION_1SVHS_3BNC 2 +#define HEXIUM_ORION_4BNC 3 + +#define HEXIUM_INPUTS 9 +static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { + { 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +#define HEXIUM_AUDIOS 0 + +struct hexium_data +{ + s8 adr; + u8 byte; +}; + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_STD, SAA7146_AFTER }, + { 0, 0 } +}; + +struct hexium +{ + int type; + struct video_device *video_dev; + struct i2c_adapter i2c_adapter; + + int cur_input; /* current input */ +}; + +/* Philips SAA7110 decoder default registers */ +static u8 hexium_saa7110[53]={ +/*00*/ 0x4C,0x3C,0x0D,0xEF,0xBD,0xF0,0x00,0x00, +/*08*/ 0xF8,0xF8,0x60,0x60,0x40,0x86,0x18,0x90, +/*10*/ 0x00,0x2C,0x40,0x46,0x42,0x1A,0xFF,0xDA, +/*18*/ 0xF0,0x8B,0x00,0x00,0x00,0x00,0x00,0x00, +/*20*/ 0xD9,0x17,0x40,0x41,0x80,0x41,0x80,0x4F, +/*28*/ 0xFE,0x01,0x0F,0x0F,0x03,0x01,0x81,0x03, +/*30*/ 0x44,0x75,0x01,0x8C,0x03 +}; + +static struct { + struct hexium_data data[8]; +} hexium_input_select[] = { +{ + { /* cvbs 1 */ + { 0x06, 0x00 }, + { 0x20, 0xD9 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 2 */ + { 0x06, 0x00 }, + { 0x20, 0x78 }, + { 0x21, 0x07 }, // 0x03, + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ? + { 0x21, 0x03 }, + } +}, { + { /* cvbs 3 */ + { 0x06, 0x00 }, + { 0x20, 0xBA }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 4 */ + { 0x06, 0x00 }, + { 0x20, 0xD8 }, + { 0x21, 0x17 }, // 0x16, + { 0x22, 0x40 }, + { 0x2C, 0x03 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, // ?? + { 0x21, 0x16 }, // 0x03, + } +}, { + { /* cvbs 5 */ + { 0x06, 0x00 }, + { 0x20, 0xB8 }, + { 0x21, 0x07 }, // 0x05, + { 0x22, 0x91 }, + { 0x2C, 0x03 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x05 }, // 0x03, + } +}, { + { /* cvbs 6 */ + { 0x06, 0x00 }, + { 0x20, 0x7C }, + { 0x21, 0x07 }, // 0x03 + { 0x22, 0xD2 }, + { 0x2C, 0x83 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, // ?? + { 0x21, 0x03 }, + } +}, { + { /* y/c 1 */ + { 0x06, 0x80 }, + { 0x20, 0x59 }, + { 0x21, 0x17 }, + { 0x22, 0x42 }, + { 0x2C, 0xA3 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x12 }, + } +}, { + { /* y/c 2 */ + { 0x06, 0x80 }, + { 0x20, 0x9A }, + { 0x21, 0x17 }, + { 0x22, 0xB1 }, + { 0x2C, 0x13 }, + { 0x30, 0x60 }, + { 0x31, 0xB5 }, + { 0x21, 0x14 }, + } +}, { + { /* y/c 3 */ + { 0x06, 0x80 }, + { 0x20, 0x3C }, + { 0x21, 0x27 }, + { 0x22, 0xC1 }, + { 0x2C, 0x23 }, + { 0x30, 0x44 }, + { 0x31, 0x75 }, + { 0x21, 0x21 }, + } +} +}; + +static struct saa7146_standard hexium_standards[] = { + { + .name = "PAL", .id = V4L2_STD_PAL, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 16, .v_field = 240, + .h_offset = 1, .h_pixels = 640, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 16, .v_field = 288, + .h_offset = 1, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +/* this is only called for old HV-PCI6/Orion cards + without eeprom */ +static int hexium_probe(struct saa7146_dev *dev) +{ + struct hexium *hexium = NULL; + union i2c_smbus_data data; + int err = 0; + + DEB_EE((".\n")); + + /* there are no hexium orion cards with revision 0 saa7146s */ + if (0 == dev->revision) { + return -EFAULT; + } + + hexium = (struct hexium *) kmalloc(sizeof(struct hexium), GFP_KERNEL); + if (NULL == hexium) { + printk("hexium_orion: hexium_probe: not enough kernel memory.\n"); + return -ENOMEM; + } + memset(hexium, 0x0, sizeof(struct hexium)); + + /* enable i2c-port pins */ + saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); + + saa7146_write(dev, DD1_INIT, 0x01000100); + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + hexium->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "hexium orion", + }; + saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(hexium); + return -EFAULT; + } + + /* set SAA7110 control GPIO 0 */ + saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTHI); + /* set HWControl GPIO number 2 */ + saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); + + mdelay(10); + + /* detect newer Hexium Orion cards by subsystem ids */ + if (0x17c8 == dev->pci->subsystem_vendor && 0x0101 == dev->pci->subsystem_device) { + printk("hexium_orion: device is a Hexium Orion w/ 1 SVHS + 3 BNC inputs.\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_1SVHS_3BNC; + return 0; + } + + if (0x17c8 == dev->pci->subsystem_vendor && 0x2101 == dev->pci->subsystem_device) { + printk("hexium_orion: device is a Hexium Orion w/ 4 BNC inputs.\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_ORION_4BNC; + return 0; + } + + /* check if this is an old hexium Orion card by looking at + a saa7110 at address 0x4e */ + if (0 == (err = i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_READ, 0x00, I2C_SMBUS_BYTE_DATA, &data))) { + printk("hexium_orion: device is a Hexium HV-PCI6/Orion (old).\n"); + /* we store the pointer in our private data field */ + dev->ext_priv = hexium; + hexium->type = HEXIUM_HV_PCI6_ORION; + return 0; + } + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return -EFAULT; +} + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int hexium_init_done(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + union i2c_smbus_data data; + int i = 0; + + DEB_D(("hexium_init_done called.\n")); + + /* initialize the helper ics to useful values */ + for (i = 0; i < sizeof(hexium_saa7110); i++) { + data.byte = hexium_saa7110[i]; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { + printk("hexium_orion: failed for address 0x%02x\n", i); + } + } + + return 0; +} + +static int hexium_set_input(struct hexium *hexium, int input) +{ + union i2c_smbus_data data; + int i = 0; + + DEB_D((".\n")); + + for (i = 0; i < 8; i++) { + int adr = hexium_input_select[input].data[i].adr; + data.byte = hexium_input_select[input].data[i].byte; + if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, adr, I2C_SMBUS_BYTE_DATA, &data)) { + return -1; + } + printk("%d: 0x%02x => 0x%02x\n",input, adr,data.byte); + } + + return 0; +} + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE((".\n")); + + saa7146_vv_init(dev, &vv_data); + if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium orion", VFL_TYPE_GRABBER)) { + printk("hexium_orion: cannot register capture v4l2 device. skipping.\n"); + return -1; + } + + printk("hexium_orion: found 'hexium orion' frame grabber-%d.\n", hexium_num); + hexium_num++; + + /* the rest */ + hexium->cur_input = 0; + hexium_init_done(dev); + + return 0; +} + +static int hexium_detach(struct saa7146_dev *dev) +{ + struct hexium *hexium = (struct hexium *) dev->ext_priv; + + DEB_EE(("dev:%p\n", dev)); + + saa7146_unregister_device(&hexium->video_dev, dev); + saa7146_vv_release(dev); + + hexium_num--; + + i2c_del_adapter(&hexium->i2c_adapter); + kfree(hexium); + return 0; +} + +static int hexium_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct hexium *hexium = (struct hexium *) dev->ext_priv; +/* + struct saa7146_vv *vv = dev->vv_data; +*/ + switch (cmd) { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + DEB_EE(("VIDIOC_ENUMINPUT %d.\n", i->index)); + + if (i->index < 0 || i->index >= HEXIUM_INPUTS) { + return -EINVAL; + } + + memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); + + DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n", i->index)); + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *) arg; + *input = hexium->cur_input; + + DEB_D(("VIDIOC_G_INPUT: %d\n", *input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *) arg; + + if (input < 0 || input >= HEXIUM_INPUTS) { + return -EINVAL; + } + + hexium->cur_input = input; + hexium_set_input(hexium, input); + + return 0; + } + default: +/* + DEB_D(("hexium_ioctl() does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) +{ + return 0; +} + +static struct saa7146_extension extension; + +static struct saa7146_pci_extension_data hexium_hv_pci6 = { + .ext_priv = "Hexium HV-PCI6 / Orion", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_1svhs_3bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (1 SVHS/3 BNC)", + .ext = &extension, +}; + +static struct saa7146_pci_extension_data hexium_orion_4bnc = { + .ext_priv = "Hexium HV-PCI6 / Orion (4 BNC)", + .ext = &extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long) &hexium_hv_pci6, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x0101, + .driver_data = (unsigned long) &hexium_orion_1svhs_3bnc, + }, + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x17c8, + .subdevice = 0x2101, + .driver_data = (unsigned long) &hexium_orion_4bnc, + }, + { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = HEXIUM_INPUTS, + .capabilities = 0, + .stds = &hexium_standards[0], + .num_stds = sizeof(hexium_standards) / sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = hexium_ioctl, +}; + +static struct saa7146_extension extension = { + .name = "hexium HV-PCI6/Orion", + .flags = 0, // SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = hexium_probe, + .attach = hexium_attach, + .detach = hexium_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init hexium_init_module(void) +{ + if (0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit hexium_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(hexium_init_module); +module_exit(hexium_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for Hexium Orion frame grabber cards"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/mxb.c b/linux/drivers/media/video/mxb.c new file mode 100644 index 000000000..155967988 --- /dev/null +++ b/linux/drivers/media/video/mxb.c @@ -0,0 +1,1028 @@ +/* + mxb - v4l2 driver for the Multimedia eXtension Board + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + Visit http://www.mihu.de/linux/saa7146/mxb/ + for further details about this card. + + 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. +*/ + +#define DEBUG_VARIABLE debug + +#include <media/saa7146_vv.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> +#include <linux/video_decoder.h> + +#include "mxb.h" +#include "tea6415c.h" +#include "tea6420.h" +#include "tda9840.h" + +#define I2C_SAA7111 0x24 + +#define MXB_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) + +/* global variable */ +static int mxb_num = 0; + +/* initial frequence the tuner will be tuned to. + in verden (lower saxony, germany) 4148 is a + channel called "phoenix" */ +static int freq = 4148; +module_param(freq, int, 0644); +MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); + +static int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); + +#define MXB_INPUTS 4 +enum { TUNER, AUX1, AUX3, AUX3_YC }; + +static struct v4l2_input mxb_inputs[MXB_INPUTS] = { + { TUNER, "Tuner", V4L2_INPUT_TYPE_TUNER, 1, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { AUX1, "AUX1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { AUX3, "AUX3 Composite", V4L2_INPUT_TYPE_CAMERA, 4, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, + { AUX3_YC, "AUX3 S-Video", V4L2_INPUT_TYPE_CAMERA, 4, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, +}; + +/* this array holds the information, which port of the saa7146 each + input actually uses. the mxb uses port 0 for every input */ +static struct { + int hps_source; + int hps_sync; +} input_port_selection[MXB_INPUTS] = { + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, + { SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, +}; + +/* this array holds the information of the audio source (mxb_audios), + which has to be switched corresponding to the video source (mxb_channels) */ +static int video_audio_connect[MXB_INPUTS] = + { 0, 1, 3, 3 }; + +/* these are the necessary input-output-pins for bringing one audio source +(see above) to the CD-output */ +static struct tea6420_multiplex TEA6420_cd[MXB_AUDIOS+1][2] = + { + {{1,1,0},{1,1,0}}, /* Tuner */ + {{5,1,0},{6,1,0}}, /* AUX 1 */ + {{4,1,0},{6,1,0}}, /* AUX 2 */ + {{3,1,0},{6,1,0}}, /* AUX 3 */ + {{1,1,0},{3,1,0}}, /* Radio */ + {{1,1,0},{2,1,0}}, /* CD-Rom */ + {{6,1,0},{6,1,0}} /* Mute */ + }; + +/* these are the necessary input-output-pins for bringing one audio source +(see above) to the line-output */ +static struct tea6420_multiplex TEA6420_line[MXB_AUDIOS+1][2] = + { + {{2,3,0},{1,2,0}}, + {{5,3,0},{6,2,0}}, + {{4,3,0},{6,2,0}}, + {{3,3,0},{6,2,0}}, + {{2,3,0},{3,2,0}}, + {{2,3,0},{2,2,0}}, + {{6,3,0},{6,2,0}} /* Mute */ + }; + +#define MAXCONTROLS 1 +static struct v4l2_queryctrl mxb_controls[] = { + { V4L2_CID_AUDIO_MUTE, V4L2_CTRL_TYPE_BOOLEAN, "Mute", 0, 1, 1, 0, 0 }, +}; + +static struct saa7146_extension_ioctls ioctls[] = { + { VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, + { VIDIOC_QUERYCTRL, SAA7146_BEFORE }, + { VIDIOC_G_CTRL, SAA7146_BEFORE }, + { VIDIOC_S_CTRL, SAA7146_BEFORE }, + { VIDIOC_G_TUNER, SAA7146_EXCLUSIVE }, + { VIDIOC_S_TUNER, SAA7146_EXCLUSIVE }, + { VIDIOC_G_FREQUENCY, SAA7146_EXCLUSIVE }, + { VIDIOC_S_FREQUENCY, SAA7146_EXCLUSIVE }, + { VIDIOC_G_AUDIO, SAA7146_EXCLUSIVE }, + { VIDIOC_S_AUDIO, SAA7146_EXCLUSIVE }, + { MXB_S_AUDIO_CD, SAA7146_EXCLUSIVE }, /* custom control */ + { MXB_S_AUDIO_LINE, SAA7146_EXCLUSIVE }, /* custom control */ + { 0, 0 } +}; + +struct mxb +{ + struct video_device *video_dev; + struct video_device *vbi_dev; + + struct i2c_adapter i2c_adapter; + + struct i2c_client* saa7111a; + struct i2c_client* tda9840; + struct i2c_client* tea6415c; + struct i2c_client* tuner; + struct i2c_client* tea6420_1; + struct i2c_client* tea6420_2; + + int cur_mode; /* current audio mode (mono, stereo, ...) */ + int cur_input; /* current input */ + int cur_mute; /* current mute status */ + struct v4l2_frequency cur_freq; /* current frequency the tuner is tuned to */ +}; + +static struct saa7146_extension extension; + +static int mxb_probe(struct saa7146_dev* dev) +{ + struct mxb* mxb = NULL; + struct i2c_client *client; + struct list_head *item; + int result; + + if ((result = request_module("saa7111")) < 0) { + printk("mxb: saa7111 i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tuner")) < 0) { + printk("mxb: tuner i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tea6420")) < 0) { + printk("mxb: tea6420 i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tea6415c")) < 0) { + printk("mxb: tea6415c i2c module not available.\n"); + return -ENODEV; + } + if ((result = request_module("tda9840")) < 0) { + printk("mxb: tda9840 i2c module not available.\n"); + return -ENODEV; + } + + mxb = (struct mxb*)kmalloc(sizeof(struct mxb), GFP_KERNEL); + if( NULL == mxb ) { + DEB_D(("not enough kernel memory.\n")); + return -ENOMEM; + } + memset(mxb, 0x0, sizeof(struct mxb)); + + mxb->i2c_adapter = (struct i2c_adapter) { + .class = I2C_CLASS_TV_ANALOG, + .name = "mxb", + }; + + saa7146_i2c_adapter_prepare(dev, &mxb->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); + if(i2c_add_adapter(&mxb->i2c_adapter) < 0) { + DEB_S(("cannot register i2c-device. skipping.\n")); + kfree(mxb); + return -EFAULT; + } + + /* loop through all i2c-devices on the bus and look who is there */ + list_for_each(item,&mxb->i2c_adapter.clients) { + client = list_entry(item, struct i2c_client, list); + if( I2C_TEA6420_1 == client->addr ) + mxb->tea6420_1 = client; + if( I2C_TEA6420_2 == client->addr ) + mxb->tea6420_2 = client; + if( I2C_TEA6415C_2 == client->addr ) + mxb->tea6415c = client; + if( I2C_TDA9840 == client->addr ) + mxb->tda9840 = client; + if( I2C_SAA7111 == client->addr ) + mxb->saa7111a = client; + if( 0x60 == client->addr ) + mxb->tuner = client; + } + + /* check if all devices are present */ + if( 0 == mxb->tea6420_1 || 0 == mxb->tea6420_2 || 0 == mxb->tea6415c + || 0 == mxb->tda9840 || 0 == mxb->saa7111a || 0 == mxb->tuner ) { + + printk("mxb: did not find all i2c devices. aborting\n"); + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + return -ENODEV; + } + + /* all devices are present, probe was successful */ + + /* we store the pointer in our private data field */ + dev->ext_priv = mxb; + + return 0; +} + +/* some init data for the saa7740, the so-called 'sound arena module'. + there are no specs available, so we simply use some init values */ +static struct { + int length; + char data[9]; +} mxb_saa7740_init[] = { + { 3, { 0x80, 0x00, 0x00 } },{ 3, { 0x80, 0x89, 0x00 } }, + { 3, { 0x80, 0xb0, 0x0a } },{ 3, { 0x00, 0x00, 0x00 } }, + { 3, { 0x49, 0x00, 0x00 } },{ 3, { 0x4a, 0x00, 0x00 } }, + { 3, { 0x4b, 0x00, 0x00 } },{ 3, { 0x4c, 0x00, 0x00 } }, + { 3, { 0x4d, 0x00, 0x00 } },{ 3, { 0x4e, 0x00, 0x00 } }, + { 3, { 0x4f, 0x00, 0x00 } },{ 3, { 0x50, 0x00, 0x00 } }, + { 3, { 0x51, 0x00, 0x00 } },{ 3, { 0x52, 0x00, 0x00 } }, + { 3, { 0x53, 0x00, 0x00 } },{ 3, { 0x54, 0x00, 0x00 } }, + { 3, { 0x55, 0x00, 0x00 } },{ 3, { 0x56, 0x00, 0x00 } }, + { 3, { 0x57, 0x00, 0x00 } },{ 3, { 0x58, 0x00, 0x00 } }, + { 3, { 0x59, 0x00, 0x00 } },{ 3, { 0x5a, 0x00, 0x00 } }, + { 3, { 0x5b, 0x00, 0x00 } },{ 3, { 0x5c, 0x00, 0x00 } }, + { 3, { 0x5d, 0x00, 0x00 } },{ 3, { 0x5e, 0x00, 0x00 } }, + { 3, { 0x5f, 0x00, 0x00 } },{ 3, { 0x60, 0x00, 0x00 } }, + { 3, { 0x61, 0x00, 0x00 } },{ 3, { 0x62, 0x00, 0x00 } }, + { 3, { 0x63, 0x00, 0x00 } },{ 3, { 0x64, 0x00, 0x00 } }, + { 3, { 0x65, 0x00, 0x00 } },{ 3, { 0x66, 0x00, 0x00 } }, + { 3, { 0x67, 0x00, 0x00 } },{ 3, { 0x68, 0x00, 0x00 } }, + { 3, { 0x69, 0x00, 0x00 } },{ 3, { 0x6a, 0x00, 0x00 } }, + { 3, { 0x6b, 0x00, 0x00 } },{ 3, { 0x6c, 0x00, 0x00 } }, + { 3, { 0x6d, 0x00, 0x00 } },{ 3, { 0x6e, 0x00, 0x00 } }, + { 3, { 0x6f, 0x00, 0x00 } },{ 3, { 0x70, 0x00, 0x00 } }, + { 3, { 0x71, 0x00, 0x00 } },{ 3, { 0x72, 0x00, 0x00 } }, + { 3, { 0x73, 0x00, 0x00 } },{ 3, { 0x74, 0x00, 0x00 } }, + { 3, { 0x75, 0x00, 0x00 } },{ 3, { 0x76, 0x00, 0x00 } }, + { 3, { 0x77, 0x00, 0x00 } },{ 3, { 0x41, 0x00, 0x42 } }, + { 3, { 0x42, 0x10, 0x42 } },{ 3, { 0x43, 0x20, 0x42 } }, + { 3, { 0x44, 0x30, 0x42 } },{ 3, { 0x45, 0x00, 0x01 } }, + { 3, { 0x46, 0x00, 0x01 } },{ 3, { 0x47, 0x00, 0x01 } }, + { 3, { 0x48, 0x00, 0x01 } }, + { 9, { 0x01, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x21, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, + { 9, { 0x09, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x29, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, + { 9, { 0x11, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x31, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, + { 9, { 0x19, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x39, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, + { 9, { 0x05, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x25, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, + { 9, { 0x0d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x2d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, + { 9, { 0x15, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x35, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, + { 9, { 0x1d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 9, { 0x3d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, + { 3, { 0x80, 0xb3, 0x0a } }, + {-1, { 0} } +}; + +static const unsigned char mxb_saa7111_init[] = { + 0x00, 0x00, /* 00 - ID byte */ + 0x01, 0x00, /* 01 - reserved */ + + /*front end */ + 0x02, 0xd8, /* 02 - FUSE=x, GUDL=x, MODE=x */ + 0x03, 0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x04, 0x00, /* 04 - GAI1=256 */ + 0x05, 0x00, /* 05 - GAI2=256 */ + + /* decoder */ + 0x06, 0xf0, /* 06 - HSB at xx(50Hz) / xx(60Hz) pixels after end of last line */ + 0x07, 0x30, /* 07 - HSS at xx(50Hz) / xx(60Hz) pixels after end of last line */ + 0x08, 0xa8, /* 08 - AUFD=x, FSEL=x, EXFIL=x, VTRC=x, HPLL=x, VNOI=x */ + 0x09, 0x02, /* 09 - BYPS=x, PREF=x, BPSS=x, VBLB=x, UPTCV=x, APER=x */ + 0x0a, 0x80, /* 0a - BRIG=128 */ + 0x0b, 0x47, /* 0b - CONT=1.109 */ + 0x0c, 0x40, /* 0c - SATN=1.0 */ + 0x0d, 0x00, /* 0d - HUE=0 */ + 0x0e, 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */ + 0x0f, 0x00, /* 0f - reserved */ + 0x10, 0xd0, /* 10 - OFTS=x, HDEL=x, VRLN=x, YDEL=x */ + 0x11, 0x8c, /* 11 - GPSW=x, CM99=x, FECO=x, COMPO=x, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + 0x12, 0x80, /* 12 - xx output control 2 */ + 0x13, 0x30, /* 13 - xx output control 3 */ + 0x14, 0x00, /* 14 - reserved */ + 0x15, 0x15, /* 15 - VBI */ + 0x16, 0x04, /* 16 - VBI */ + 0x17, 0x00, /* 17 - VBI */ +}; + +/* bring hardware to a sane state. this has to be done, just in case someone + wants to capture from this device before it has been properly initialized. + the capture engine would badly fail, because no valid signal arrives on the + saa7146, thus leading to timeouts and stuff. */ +static int mxb_init_done(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + struct video_decoder_init init; + struct i2c_msg msg; + struct tuner_setup tun_setup; + + int i = 0, err = 0; + struct tea6415c_multiplex vm; + + /* select video mode in saa7111a */ + i = VIDEO_MODE_PAL; + /* fixme: currently pointless: gets overwritten by configuration below */ + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_NORM, &i); + + /* write configuration to saa7111a */ + init.data = mxb_saa7111_init; + init.len = sizeof(mxb_saa7111_init); + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_INIT, &init); + + /* select tuner-output on saa7111a */ + i = 0; + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_INPUT, &i); + + /* enable vbi bypass */ + i = 1; + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_VBI_BYPASS, &i); + + /* select a tuner type */ + tun_setup.mode_mask = T_ANALOG_TV; + tun_setup.addr = ADDR_UNSET; + tun_setup.type = TUNER_PHILIPS_PAL; + mxb->tuner->driver->command(mxb->tuner,TUNER_SET_TYPE_ADDR, &tun_setup); + /* tune in some frequency on tuner */ + mxb->cur_freq.tuner = 0; + mxb->cur_freq.type = V4L2_TUNER_ANALOG_TV; + mxb->cur_freq.frequency = freq; + mxb->tuner->driver->command(mxb->tuner, VIDIOC_S_FREQUENCY, + &mxb->cur_freq); + + /* mute audio on tea6420s */ + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[6][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[6][1]); + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_cd[6][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_cd[6][1]); + + /* switch to tuner-channel on tea6415c*/ + vm.out = 17; + vm.in = 3; + mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm); + + /* select tuner-output on multicable on tea6415c*/ + vm.in = 3; + vm.out = 13; + mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm); + + /* the rest for mxb */ + mxb->cur_input = 0; + mxb->cur_mute = 1; + + mxb->cur_mode = V4L2_TUNER_MODE_STEREO; + mxb->tda9840->driver->command(mxb->tda9840, TDA9840_SWITCH, &mxb->cur_mode); + + /* check if the saa7740 (aka 'sound arena module') is present + on the mxb. if so, we must initialize it. due to lack of + informations about the saa7740, the values were reverse + engineered. */ + msg.addr = 0x1b; + msg.flags = 0; + msg.len = mxb_saa7740_init[0].length; + msg.buf = &mxb_saa7740_init[0].data[0]; + + if( 1 == (err = i2c_transfer(&mxb->i2c_adapter, &msg, 1))) { + /* the sound arena module is a pos, that's probably the reason + philips refuses to hand out a datasheet for the saa7740... + it seems to screw up the i2c bus, so we disable fast irq + based i2c transactions here and rely on the slow and safe + polling method ... */ + extension.flags &= ~SAA7146_USE_I2C_IRQ; + for(i = 1;;i++) { + if( -1 == mxb_saa7740_init[i].length ) { + break; + } + + msg.len = mxb_saa7740_init[i].length; + msg.buf = &mxb_saa7740_init[i].data[0]; + if( 1 != (err = i2c_transfer(&mxb->i2c_adapter, &msg, 1))) { + DEB_D(("failed to initialize 'sound arena module'.\n")); + goto err; + } + } + INFO(("'sound arena module' detected.\n")); + } +err: + /* the rest for saa7146: you should definitely set some basic values + for the input-port handling of the saa7146. */ + + /* ext->saa has been filled by the core driver */ + + /* some stuff is done via variables */ + saa7146_set_hps_source_and_sync(dev, input_port_selection[mxb->cur_input].hps_source, input_port_selection[mxb->cur_input].hps_sync); + + /* some stuff is done via direct write to the registers */ + + /* this is ugly, but because of the fact that this is completely + hardware dependend, it should be done directly... */ + saa7146_write(dev, DD1_STREAM_B, 0x00000000); + saa7146_write(dev, DD1_INIT, 0x02000200); + saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); + + return 0; +} + +/* interrupt-handler. this gets called when irq_mask is != 0. + it must clear the interrupt-bits in irq_mask it has handled */ +/* +void mxb_irq_bh(struct saa7146_dev* dev, u32* irq_mask) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; +} +*/ + +static struct saa7146_ext_vv vv_data; + +/* this function only gets called when the probing was successful */ +static int mxb_attach(struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + + DEB_EE(("dev:%p\n",dev)); + + /* checking for i2c-devices can be omitted here, because we + already did this in "mxb_vl42_probe" */ + + saa7146_vv_init(dev,&vv_data); + if( 0 != saa7146_register_device(&mxb->video_dev, dev, "mxb", VFL_TYPE_GRABBER)) { + ERR(("cannot register capture v4l2 device. skipping.\n")); + return -1; + } + + /* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ + if( 0 != MXB_BOARD_CAN_DO_VBI(dev)) { + if( 0 != saa7146_register_device(&mxb->vbi_dev, dev, "mxb", VFL_TYPE_VBI)) { + ERR(("cannot register vbi v4l2 device. skipping.\n")); + } + } + + i2c_use_client(mxb->tea6420_1); + i2c_use_client(mxb->tea6420_2); + i2c_use_client(mxb->tea6415c); + i2c_use_client(mxb->tda9840); + i2c_use_client(mxb->saa7111a); + i2c_use_client(mxb->tuner); + + printk("mxb: found 'Multimedia eXtension Board'-%d.\n",mxb_num); + + mxb_num++; + mxb_init_done(dev); + return 0; +} + +static int mxb_detach(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + + DEB_EE(("dev:%p\n",dev)); + + i2c_release_client(mxb->tea6420_1); + i2c_release_client(mxb->tea6420_2); + i2c_release_client(mxb->tea6415c); + i2c_release_client(mxb->tda9840); + i2c_release_client(mxb->saa7111a); + i2c_release_client(mxb->tuner); + + saa7146_unregister_device(&mxb->video_dev,dev); + if( 0 != MXB_BOARD_CAN_DO_VBI(dev)) { + saa7146_unregister_device(&mxb->vbi_dev,dev); + } + saa7146_vv_release(dev); + + mxb_num--; + + i2c_del_adapter(&mxb->i2c_adapter); + kfree(mxb); + + return 0; +} + +static int mxb_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) +{ + struct saa7146_dev *dev = fh->dev; + struct mxb* mxb = (struct mxb*)dev->ext_priv; + struct saa7146_vv *vv = dev->vv_data; + + switch(cmd) { + case VIDIOC_ENUMINPUT: + { + struct v4l2_input *i = arg; + + DEB_EE(("VIDIOC_ENUMINPUT %d.\n",i->index)); + if( i->index < 0 || i->index >= MXB_INPUTS) { + return -EINVAL; + } + memcpy(i, &mxb_inputs[i->index], sizeof(struct v4l2_input)); + + return 0; + } + /* the saa7146 provides some controls (brightness, contrast, saturation) + which gets registered *after* this function. because of this we have + to return with a value != 0 even if the function succeded.. */ + case VIDIOC_QUERYCTRL: + { + struct v4l2_queryctrl *qc = arg; + int i; + + for (i = MAXCONTROLS - 1; i >= 0; i--) { + if (mxb_controls[i].id == qc->id) { + *qc = mxb_controls[i]; + DEB_D(("VIDIOC_QUERYCTRL %d.\n",qc->id)); + return 0; + } + } + return -EAGAIN; + } + case VIDIOC_G_CTRL: + { + struct v4l2_control *vc = arg; + int i; + + for (i = MAXCONTROLS - 1; i >= 0; i--) { + if (mxb_controls[i].id == vc->id) { + break; + } + } + + if( i < 0 ) { + return -EAGAIN; + } + + switch (vc->id ) { + case V4L2_CID_AUDIO_MUTE: { + vc->value = mxb->cur_mute; + DEB_D(("VIDIOC_G_CTRL V4L2_CID_AUDIO_MUTE:%d.\n",vc->value)); + return 0; + } + } + + DEB_EE(("VIDIOC_G_CTRL V4L2_CID_AUDIO_MUTE:%d.\n",vc->value)); + return 0; + } + + case VIDIOC_S_CTRL: + { + struct v4l2_control *vc = arg; + int i = 0; + + for (i = MAXCONTROLS - 1; i >= 0; i--) { + if (mxb_controls[i].id == vc->id) { + break; + } + } + + if( i < 0 ) { + return -EAGAIN; + } + + switch (vc->id ) { + case V4L2_CID_AUDIO_MUTE: { + mxb->cur_mute = vc->value; + if( 0 == vc->value ) { + /* switch the audio-source */ + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[mxb->cur_input]][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[mxb->cur_input]][1]); + } else { + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[6][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[6][1]); + } + DEB_EE(("VIDIOC_S_CTRL, V4L2_CID_AUDIO_MUTE: %d.\n",vc->value)); + break; + } + } + return 0; + } + case VIDIOC_G_INPUT: + { + int *input = (int *)arg; + *input = mxb->cur_input; + + DEB_EE(("VIDIOC_G_INPUT %d.\n",*input)); + return 0; + } + case VIDIOC_S_INPUT: + { + int input = *(int *)arg; + struct tea6415c_multiplex vm; + int i = 0; + + DEB_EE(("VIDIOC_S_INPUT %d.\n",input)); + + if (input < 0 || input >= MXB_INPUTS) { + return -EINVAL; + } + + /* fixme: locke das setzen des inputs mit hilfe des mutexes + down(&dev->lock); + video_mux(dev,*i); + up(&dev->lock); + */ + + /* fixme: check if streaming capture + if ( 0 != dev->streaming ) { + DEB_D(("VIDIOC_S_INPUT illegal while streaming.\n")); + return -EPERM; + } + */ + + mxb->cur_input = input; + + saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, input_port_selection[input].hps_sync); + + /* prepare switching of tea6415c and saa7111a; + have a look at the 'background'-file for further informations */ + switch( input ) { + + case TUNER: + { + i = 0; + vm.in = 3; + vm.out = 17; + + if ( 0 != mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm)) { + printk("VIDIOC_S_INPUT: could not address tea6415c #1\n"); + return -EFAULT; + } + /* connect tuner-output always to multicable */ + vm.in = 3; + vm.out = 13; + break; + } + case AUX3_YC: + { + /* nothing to be done here. aux3_yc is + directly connected to the saa711a */ + i = 5; + break; + } + case AUX3: + { + /* nothing to be done here. aux3 is + directly connected to the saa711a */ + i = 1; + break; + } + case AUX1: + { + i = 0; + vm.in = 1; + vm.out = 17; + break; + } + } + + /* switch video in tea6415c only if necessary */ + switch( input ) { + case TUNER: + case AUX1: + { + if ( 0 != mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm)) { + printk("VIDIOC_S_INPUT: could not address tea6415c #3\n"); + return -EFAULT; + } + break; + } + default: + { + break; + } + } + + /* switch video in saa7111a */ + if ( 0 != mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_INPUT, &i)) { + printk("VIDIOC_S_INPUT: could not address saa7111a #1.\n"); + } + + /* switch the audio-source only if necessary */ + if( 0 == mxb->cur_mute ) { + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[input]][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[input]][1]); + } + + return 0; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *t = arg; + int byte = 0; + + if( 0 != t->index ) { + DEB_D(("VIDIOC_G_TUNER: channel %d does not have a tuner attached.\n", t->index)); + return -EINVAL; + } + + DEB_EE(("VIDIOC_G_TUNER: %d\n", t->index)); + + memset(t,0,sizeof(*t)); + strcpy(t->name, "Television"); + + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; + t->rangelow = 772; /* 48.25 MHZ / 62.5 kHz = 772, see fi1216mk2-specs, page 2 */ + t->rangehigh = 13684; /* 855.25 MHz / 62.5 kHz = 13684 */ + /* FIXME: add the real signal strength here */ + t->signal = 0xffff; + t->afc = 0; + + mxb->tda9840->driver->command(mxb->tda9840,TDA9840_DETECT, &byte); + t->audmode = mxb->cur_mode; + + if( byte < 0 ) { + t->rxsubchans = V4L2_TUNER_SUB_MONO; + } else { + switch(byte) { + case TDA9840_MONO_DETECT: { + t->rxsubchans = V4L2_TUNER_SUB_MONO; + DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_MONO.\n")); + break; + } + case TDA9840_DUAL_DETECT: { + t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; + DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_LANG1.\n")); + break; + } + case TDA9840_STEREO_DETECT: { + t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; + DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_STEREO.\n")); + break; + } + default: { /* TDA9840_INCORRECT_DETECT */ + t->rxsubchans = V4L2_TUNER_MODE_MONO; + DEB_D(("VIDIOC_G_TUNER: TDA9840_INCORRECT_DETECT => V4L2_TUNER_MODE_MONO\n")); + break; + } + } + } + + return 0; + } + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *t = arg; + int result = 0; + int byte = 0; + + if( 0 != t->index ) { + DEB_D(("VIDIOC_S_TUNER: channel %d does not have a tuner attached.\n",t->index)); + return -EINVAL; + } + + switch(t->audmode) { + case V4L2_TUNER_MODE_STEREO: { + mxb->cur_mode = V4L2_TUNER_MODE_STEREO; + byte = TDA9840_SET_STEREO; + DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_STEREO\n")); + break; + } + case V4L2_TUNER_MODE_LANG1: { + mxb->cur_mode = V4L2_TUNER_MODE_LANG1; + byte = TDA9840_SET_LANG1; + DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1\n")); + break; + } + case V4L2_TUNER_MODE_LANG2: { + mxb->cur_mode = V4L2_TUNER_MODE_LANG2; + byte = TDA9840_SET_LANG2; + DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG2\n")); + break; + } + default: { /* case V4L2_TUNER_MODE_MONO: {*/ + mxb->cur_mode = V4L2_TUNER_MODE_MONO; + byte = TDA9840_SET_MONO; + DEB_D(("VIDIOC_S_TUNER: TDA9840_SET_MONO\n")); + break; + } + } + + if( 0 != (result = mxb->tda9840->driver->command(mxb->tda9840, TDA9840_SWITCH, &byte))) { + printk("VIDIOC_S_TUNER error. result:%d, byte:%d\n",result,byte); + } + + return 0; + } + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if(0 != mxb->cur_input) { + DEB_D(("VIDIOC_G_FREQ: channel %d does not have a tuner!\n",mxb->cur_input)); + return -EINVAL; + } + + *f = mxb->cur_freq; + + DEB_EE(("VIDIOC_G_FREQ: freq:0x%08x.\n", mxb->cur_freq.frequency)); + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + + if (0 != f->tuner) + return -EINVAL; + + if (V4L2_TUNER_ANALOG_TV != f->type) + return -EINVAL; + + if(0 != mxb->cur_input) { + DEB_D(("VIDIOC_S_FREQ: channel %d does not have a tuner!\n",mxb->cur_input)); + return -EINVAL; + } + + mxb->cur_freq = *f; + DEB_EE(("VIDIOC_S_FREQUENCY: freq:0x%08x.\n", mxb->cur_freq.frequency)); + + /* tune in desired frequency */ + mxb->tuner->driver->command(mxb->tuner, VIDIOC_S_FREQUENCY, &mxb->cur_freq); + + /* hack: changing the frequency should invalidate the vbi-counter (=> alevt) */ + spin_lock(&dev->slock); + vv->vbi_fieldcount = 0; + spin_unlock(&dev->slock); + + return 0; + } + case MXB_S_AUDIO_CD: + { + int i = *(int*)arg; + + if( i < 0 || i >= MXB_AUDIOS ) { + DEB_D(("illegal argument to MXB_S_AUDIO_CD: i:%d.\n",i)); + return -EINVAL; + } + + DEB_EE(("MXB_S_AUDIO_CD: i:%d.\n",i)); + + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_cd[i][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_cd[i][1]); + + return 0; + } + case MXB_S_AUDIO_LINE: + { + int i = *(int*)arg; + + if( i < 0 || i >= MXB_AUDIOS ) { + DEB_D(("illegal argument to MXB_S_AUDIO_LINE: i:%d.\n",i)); + return -EINVAL; + } + + DEB_EE(("MXB_S_AUDIO_LINE: i:%d.\n",i)); + mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[i][0]); + mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[i][1]); + + return 0; + } + case VIDIOC_G_AUDIO: + { + struct v4l2_audio *a = arg; + + if( a->index < 0 || a->index > MXB_INPUTS ) { + DEB_D(("VIDIOC_G_AUDIO %d out of range.\n",a->index)); + return -EINVAL; + } + + DEB_EE(("VIDIOC_G_AUDIO %d.\n",a->index)); + memcpy(a, &mxb_audios[video_audio_connect[mxb->cur_input]], sizeof(struct v4l2_audio)); + + return 0; + } + case VIDIOC_S_AUDIO: + { + struct v4l2_audio *a = arg; + DEB_D(("VIDIOC_S_AUDIO %d.\n",a->index)); + return 0; + } + default: +/* + DEB2(printk("does not handle this ioctl.\n")); +*/ + return -ENOIOCTLCMD; + } + return 0; +} + +static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + int zero = 0; + int one = 1; + + if(V4L2_STD_PAL_I == std->id ) { + DEB_D(("VIDIOC_S_STD: setting mxb for PAL_I.\n")); + /* set the 7146 gpio register -- I don't know what this does exactly */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + /* unset the 7111 gpio register -- I don't know what this does exactly */ + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_GPIO, &zero); + } else { + DEB_D(("VIDIOC_S_STD: setting mxb for PAL/NTSC/SECAM.\n")); + /* set the 7146 gpio register -- I don't know what this does exactly */ + saa7146_write(dev, GPIO_CTRL, 0x00404050); + /* set the 7111 gpio register -- I don't know what this does exactly */ + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_GPIO, &one); + } + return 0; +} + +static struct saa7146_standard standard[] = { + { + .name = "PAL-BG", .id = V4L2_STD_PAL_BG, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "PAL-I", .id = V4L2_STD_PAL_I, + .v_offset = 0x17, .v_field = 288, + .h_offset = 0x14, .h_pixels = 680, + .v_max_out = 576, .h_max_out = 768, + }, { + .name = "NTSC", .id = V4L2_STD_NTSC, + .v_offset = 0x16, .v_field = 240, + .h_offset = 0x06, .h_pixels = 708, + .v_max_out = 480, .h_max_out = 640, + }, { + .name = "SECAM", .id = V4L2_STD_SECAM, + .v_offset = 0x14, .v_field = 288, + .h_offset = 0x14, .h_pixels = 720, + .v_max_out = 576, .h_max_out = 768, + } +}; + +static struct saa7146_pci_extension_data mxb = { + .ext_priv = "Multimedia eXtension Board", + .ext = &extension, +}; + +static struct pci_device_id pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7146, + .subvendor = 0x0000, + .subdevice = 0x0000, + .driver_data = (unsigned long)&mxb, + }, { + .vendor = 0, + } +}; + +MODULE_DEVICE_TABLE(pci, pci_tbl); + +static struct saa7146_ext_vv vv_data = { + .inputs = MXB_INPUTS, + .capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE, + .stds = &standard[0], + .num_stds = sizeof(standard)/sizeof(struct saa7146_standard), + .std_callback = &std_callback, + .ioctls = &ioctls[0], + .ioctl = mxb_ioctl, +}; + +static struct saa7146_extension extension = { + .name = MXB_IDENTIFIER, + .flags = SAA7146_USE_I2C_IRQ, + + .pci_tbl = &pci_tbl[0], + .module = THIS_MODULE, + + .probe = mxb_probe, + .attach = mxb_attach, + .detach = mxb_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +static int __init mxb_init_module(void) +{ + if( 0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +static void __exit mxb_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(mxb_init_module); +module_exit(mxb_cleanup_module); + +MODULE_DESCRIPTION("video4linux-2 driver for the Siemens-Nixdorf 'Multimedia eXtension board'"); +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/mxb.h b/linux/drivers/media/video/mxb.h new file mode 100644 index 000000000..400a57ba6 --- /dev/null +++ b/linux/drivers/media/video/mxb.h @@ -0,0 +1,42 @@ +#ifndef __MXB__ +#define __MXB__ + +#define BASE_VIDIOC_MXB 10 + +#define MXB_S_AUDIO_CD _IOW ('V', BASE_VIDIOC_PRIVATE+BASE_VIDIOC_MXB+0, int) +#define MXB_S_AUDIO_LINE _IOW ('V', BASE_VIDIOC_PRIVATE+BASE_VIDIOC_MXB+1, int) + +#define MXB_IDENTIFIER "Multimedia eXtension Board" + +#define MXB_AUDIOS 6 + +/* these are the available audio sources, which can switched + to the line- and cd-output individually */ +static struct v4l2_audio mxb_audios[MXB_AUDIOS] = { + { + .index = 0, + .name = "Tuner", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 1, + .name = "AUX1", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 2, + .name = "AUX2", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 3, + .name = "AUX3", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 4, + .name = "Radio (X9)", + .capability = V4L2_AUDCAP_STEREO, + } , { + .index = 5, + .name = "CD-ROM (X10)", + .capability = V4L2_AUDCAP_STEREO, + } +}; +#endif diff --git a/linux/drivers/media/video/tda9840.c b/linux/drivers/media/video/tda9840.c new file mode 100644 index 000000000..f39e4f54c --- /dev/null +++ b/linux/drivers/media/video/tda9840.c @@ -0,0 +1,253 @@ + /* + tda9840 - i2c-driver for the tda9840 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + The tda9840 is a stereo/dual sound processor with digital + identification. It can be found at address 0x84 on the i2c-bus. + + For detailed informations download the specifications directly + from SGS Thomson at http://www.st.com + + 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/module.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> + +#include "tda9840.h" + +static int debug = 0; /* insmod parameter */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); +#define dprintk(args...) \ + do { if (debug) { printk("%s: %s()[%d]: ",__stringify(KBUILD_MODNAME), __FUNCTION__, __LINE__); printk(args); } } while (0) + +#define SWITCH 0x00 +#define LEVEL_ADJUST 0x02 +#define STEREO_ADJUST 0x03 +#define TEST 0x04 + +/* addresses to scan, found only at 0x42 (7-Bit) */ +static unsigned short normal_i2c[] = { I2C_TDA9840, I2C_CLIENT_END }; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +static int command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + int result; + int byte = *(int *)arg; + + switch (cmd) { + case TDA9840_SWITCH: + + dprintk("TDA9840_SWITCH: 0x%02x\n", byte); + + if (byte != TDA9840_SET_MONO + && byte != TDA9840_SET_MUTE + && byte != TDA9840_SET_STEREO + && byte != TDA9840_SET_LANG1 + && byte != TDA9840_SET_LANG2 + && byte != TDA9840_SET_BOTH + && byte != TDA9840_SET_BOTH_R + && byte != TDA9840_SET_EXTERNAL) { + return -EINVAL; + } + + result = i2c_smbus_write_byte_data(client, SWITCH, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + + case TDA9840_LEVEL_ADJUST: + + dprintk("TDA9840_LEVEL_ADJUST: %d\n", byte); + + /* check for correct range */ + if (byte > 25 || byte < -20) + return -EINVAL; + + /* calculate actual value to set, see specs, page 18 */ + byte /= 5; + if (0 < byte) + byte += 0x8; + else + byte = -byte; + + result = i2c_smbus_write_byte_data(client, LEVEL_ADJUST, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + + case TDA9840_STEREO_ADJUST: + + dprintk("TDA9840_STEREO_ADJUST: %d\n", byte); + + /* check for correct range */ + if (byte > 25 || byte < -24) + return -EINVAL; + + /* calculate actual value to set */ + byte /= 5; + if (0 < byte) + byte += 0x20; + else + byte = -byte; + + result = i2c_smbus_write_byte_data(client, STEREO_ADJUST, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + + case TDA9840_DETECT: { + int *ret = (int *)arg; + + byte = i2c_smbus_read_byte_data(client, STEREO_ADJUST); + if (byte == -1) { + dprintk("i2c_smbus_read_byte_data() failed\n"); + return -EIO; + } + + if (0 != (byte & 0x80)) { + dprintk("TDA9840_DETECT: register contents invalid\n"); + return -EINVAL; + } + + dprintk("TDA9840_DETECT: byte: 0x%02x\n", byte); + *ret = ((byte & 0x60) >> 5); + result = 0; + break; + } + case TDA9840_TEST: + dprintk("TDA9840_TEST: 0x%02x\n", byte); + + /* mask out irrelevant bits */ + byte &= 0x3; + + result = i2c_smbus_write_byte_data(client, TEST, byte); + if (result) + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", result); + break; + default: + return -ENOIOCTLCMD; + } + + if (result) + return -EIO; + + return 0; +} + +static int detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client; + int result = 0; + + int byte = 0x0; + + /* let's see whether this adapter can support what we need */ + if (0 == i2c_check_functionality(adapter, + I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { + return 0; + } + + /* allocate memory for client structure */ + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (0 == client) { + printk("not enough kernel memory\n"); + return -ENOMEM; + } + + /* fill client structure */ + memcpy(client, &client_template, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + + /* tell the i2c layer a new client has arrived */ + if (0 != (result = i2c_attach_client(client))) { + kfree(client); + return result; + } + + /* set initial values for level & stereo - adjustment, mode */ + byte = 0; + result = command(client, TDA9840_LEVEL_ADJUST, &byte); + result += command(client, TDA9840_STEREO_ADJUST, &byte); + byte = TDA9840_SET_MONO; + result = command(client, TDA9840_SWITCH, &byte); + if (result) { + dprintk("could not initialize tda9840\n"); + return -ENODEV; + } + + printk("tda9840: detected @ 0x%02x on adapter %s\n", address, &client->adapter->name[0]); + return 0; +} + +static int attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if (adapter->id != I2C_HW_SAA7146) { + dprintk("refusing to probe on unknown adapter [name='%s',id=0x%x]\n", adapter->name, adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter, &addr_data, &detect); +} + +static int detach(struct i2c_client *client) +{ + int ret = i2c_detach_client(client); + kfree(client); + return ret; +} + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "tda9840", + .id = I2C_DRIVERID_TDA9840, + .flags = I2C_DF_NOTIFY, + .attach_adapter = attach, + .detach_client = detach, + .command = command, +}; + +static struct i2c_client client_template = { + .name = "tda9840", + .driver = &driver, +}; + +static int __init this_module_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit this_module_exit(void) +{ + i2c_del_driver(&driver); +} + +module_init(this_module_init); +module_exit(this_module_exit); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tda9840 driver"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/tda9840.h b/linux/drivers/media/video/tda9840.h new file mode 100644 index 000000000..28021053b --- /dev/null +++ b/linux/drivers/media/video/tda9840.h @@ -0,0 +1,35 @@ +#ifndef __INCLUDED_TDA9840__ +#define __INCLUDED_TDA9840__ + +#define I2C_TDA9840 0x42 + +#define TDA9840_DETECT _IOR('v',1,int) +/* return values for TDA9840_DETCT */ +#define TDA9840_MONO_DETECT 0x0 +#define TDA9840_DUAL_DETECT 0x1 +#define TDA9840_STEREO_DETECT 0x2 +#define TDA9840_INCORRECT_DETECT 0x3 + +#define TDA9840_SWITCH _IOW('v',2,int) +/* modes than can be set with TDA9840_SWITCH */ +#define TDA9840_SET_MUTE 0x00 +#define TDA9840_SET_MONO 0x10 +#define TDA9840_SET_STEREO 0x2a +#define TDA9840_SET_LANG1 0x12 +#define TDA9840_SET_LANG2 0x1e +#define TDA9840_SET_BOTH 0x1a +#define TDA9840_SET_BOTH_R 0x16 +#define TDA9840_SET_EXTERNAL 0x7a + +/* values may range between +2.5 and -2.0; + the value has to be multiplied with 10 */ +#define TDA9840_LEVEL_ADJUST _IOW('v',3,int) + +/* values may range between +2.5 and -2.4; + the value has to be multiplied with 10 */ +#define TDA9840_STEREO_ADJUST _IOW('v',4,int) + +/* currently not implemented */ +#define TDA9840_TEST _IOW('v',5,int) + +#endif diff --git a/linux/drivers/media/video/tea6415c.c b/linux/drivers/media/video/tea6415c.c new file mode 100644 index 000000000..ad09e4c1c --- /dev/null +++ b/linux/drivers/media/video/tea6415c.c @@ -0,0 +1,222 @@ + /* + tea6415c - i2c-driver for the tea6415c by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + The tea6415c is a bus controlled video-matrix-switch + with 8 inputs and 6 outputs. + It is cascadable, i.e. it can be found at the addresses + 0x86 and 0x06 on the i2c-bus. + + For detailed informations download the specifications directly + from SGS Thomson at http://www.st.com + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License vs 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 Mvss Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> + +#include "tea6415c.h" + +static int debug = 0; /* insmod parameter */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); +#define dprintk(args...) \ + do { if (debug) { printk("%s: %s()[%d]: ",__stringify(KBUILD_MODNAME), __FUNCTION__, __LINE__); printk(args); } } while (0) + +#define TEA6415C_NUM_INPUTS 8 +#define TEA6415C_NUM_OUTPUTS 6 + +/* addresses to scan, found only at 0x03 and/or 0x43 (7-bit) */ +static unsigned short normal_i2c[] = { I2C_TEA6415C_1, I2C_TEA6415C_2, I2C_CLIENT_END }; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* this function is called by i2c_probe */ +static int detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client = NULL; + int err = 0; + + /* let's see whether this adapter can support what we need */ + if (0 == i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) { + return 0; + } + + /* allocate memory for client structure */ + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (0 == client) { + return -ENOMEM; + } + + /* fill client structure */ + memcpy(client, &client_template, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + + /* tell the i2c layer a new client has arrived */ + if (0 != (err = i2c_attach_client(client))) { + kfree(client); + return err; + } + + printk("tea6415c: detected @ 0x%02x on adapter %s\n", address, &client->adapter->name[0]); + + return 0; +} + +static int attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if (adapter->id != I2C_HW_SAA7146) { + dprintk("refusing to probe on unknown adapter [name='%s',id=0x%x]\n", adapter->name, adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter, &addr_data, &detect); +} + +static int detach(struct i2c_client *client) +{ + int ret = i2c_detach_client(client); + kfree(client); + return ret; +} + +/* makes a connection between the input-pin 'i' and the output-pin 'o' + for the tea6415c-client 'client' */ +static int switch_matrix(struct i2c_client *client, int i, int o) +{ + u8 byte = 0; + int ret; + + dprintk("adr:0x%02x, i:%d, o:%d\n", client->addr, i, o); + + /* check if the pins are valid */ + if (0 == ((1 == i || 3 == i || 5 == i || 6 == i || 8 == i || 10 == i || 20 == i || 11 == i) + && (18 == o || 17 == o || 16 == o || 15 == o || 14 == o || 13 == o))) + return -1; + + /* to understand this, have a look at the tea6415c-specs (p.5) */ + switch (o) { + case 18: + byte = 0x00; + break; + case 14: + byte = 0x20; + break; + case 16: + byte = 0x10; + break; + case 17: + byte = 0x08; + break; + case 15: + byte = 0x18; + break; + case 13: + byte = 0x28; + break; + }; + + switch (i) { + case 5: + byte |= 0x00; + break; + case 8: + byte |= 0x04; + break; + case 3: + byte |= 0x02; + break; + case 20: + byte |= 0x06; + break; + case 6: + byte |= 0x01; + break; + case 10: + byte |= 0x05; + break; + case 1: + byte |= 0x03; + break; + case 11: + byte |= 0x07; + break; + }; + + ret = i2c_smbus_write_byte(client, byte); + if (ret) { + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", ret); + return -EIO; + } + + return ret; +} + +static int command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tea6415c_multiplex *v = (struct tea6415c_multiplex *)arg; + int result = 0; + + switch (cmd) { + case TEA6415C_SWITCH: + result = switch_matrix(client, v->in, v->out); + break; + default: + return -ENOIOCTLCMD; + } + + return result; +} + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "tea6415c", + .id = I2C_DRIVERID_TEA6415C, + .flags = I2C_DF_NOTIFY, + .attach_adapter = attach, + .detach_client = detach, + .command = command, +}; + +static struct i2c_client client_template = { + .name = "tea6415c", + .driver = &driver, +}; + +static int __init this_module_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit this_module_exit(void) +{ + i2c_del_driver(&driver); +} + +module_init(this_module_init); +module_exit(this_module_exit); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6415c driver"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/tea6415c.h b/linux/drivers/media/video/tea6415c.h new file mode 100644 index 000000000..f84ed8005 --- /dev/null +++ b/linux/drivers/media/video/tea6415c.h @@ -0,0 +1,39 @@ +#ifndef __INCLUDED_TEA6415C__ +#define __INCLUDED_TEA6415C__ + +/* possible i2c-addresses */ +#define I2C_TEA6415C_1 0x03 +#define I2C_TEA6415C_2 0x43 + +/* the tea6415c's design is quite brain-dead. although there are + 8 inputs and 6 outputs, these aren't enumerated in any way. because + I don't want to say "connect input pin 20 to output pin 17", I define + a "virtual" pin-order. */ + +/* input pins */ +#define TEA6415C_OUTPUT1 18 +#define TEA6415C_OUTPUT2 14 +#define TEA6415C_OUTPUT3 16 +#define TEA6415C_OUTPUT4 17 +#define TEA6415C_OUTPUT5 13 +#define TEA6415C_OUTPUT6 15 + +/* output pins */ +#define TEA6415C_INPUT1 5 +#define TEA6415C_INPUT2 8 +#define TEA6415C_INPUT3 3 +#define TEA6415C_INPUT4 20 +#define TEA6415C_INPUT5 6 +#define TEA6415C_INPUT6 10 +#define TEA6415C_INPUT7 1 +#define TEA6415C_INPUT8 11 + +struct tea6415c_multiplex +{ + int in; /* input-pin */ + int out; /* output-pin */ +}; + +#define TEA6415C_SWITCH _IOW('v',1,struct tea6415c_multiplex) + +#endif diff --git a/linux/drivers/media/video/tea6420.c b/linux/drivers/media/video/tea6420.c new file mode 100644 index 000000000..dd8279ad5 --- /dev/null +++ b/linux/drivers/media/video/tea6420.c @@ -0,0 +1,199 @@ + /* + tea6420 - i2c-driver for the tea6420 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + + The tea6420 is a bus controlled audio-matrix with 5 stereo inputs, + 4 stereo outputs and gain control for each output. + It is cascadable, i.e. it can be found at the adresses 0x98 + and 0x9a on the i2c-bus. + + For detailed informations download the specifications directly + from SGS Thomson at http://www.st.com + + 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/module.h> +#include <linux/ioctl.h> +#include <linux/i2c.h> + +#include "tea6420.h" + +static int debug = 0; /* insmod parameter */ +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Turn on/off device debugging (default:off)."); +#define dprintk(args...) \ + do { if (debug) { printk("%s: %s()[%d]: ",__stringify(KBUILD_MODNAME), __FUNCTION__, __LINE__); printk(args); } } while (0) + +/* addresses to scan, found only at 0x4c and/or 0x4d (7-Bit) */ +static unsigned short normal_i2c[] = { I2C_TEA6420_1, I2C_TEA6420_2, I2C_CLIENT_END }; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; +static struct i2c_client client_template; + +/* make a connection between the input 'i' and the output 'o' + with gain 'g' for the tea6420-client 'client' (note: i = 6 means 'mute') */ +static int tea6420_switch(struct i2c_client *client, int i, int o, int g) +{ + u8 byte = 0; + int ret; + + dprintk("adr:0x%02x, i:%d, o:%d, g:%d\n", client->addr, i, o, g); + + /* check if the paramters are valid */ + if (i < 1 || i > 6 || o < 1 || o > 4 || g < 0 || g > 6 || g % 2 != 0) + return -1; + + byte = ((o - 1) << 5); + byte |= (i - 1); + + /* to understand this, have a look at the tea6420-specs (p.5) */ + switch (g) { + case 0: + byte |= (3 << 3); + break; + case 2: + byte |= (2 << 3); + break; + case 4: + byte |= (1 << 3); + break; + case 6: + break; + } + + ret = i2c_smbus_write_byte(client, byte); + if (ret) { + dprintk("i2c_smbus_write_byte() failed, ret:%d\n", ret); + return -EIO; + } + + return 0; +} + +/* this function is called by i2c_probe */ +static int tea6420_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *client; + int err = 0, i = 0; + + /* let's see whether this adapter can support what we need */ + if (0 == i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) { + return 0; + } + + /* allocate memory for client structure */ + client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); + if (0 == client) { + return -ENOMEM; + } + memset(client, 0x0, sizeof(struct i2c_client)); + + /* fill client structure */ + memcpy(client, &client_template, sizeof(struct i2c_client)); + client->addr = address; + client->adapter = adapter; + + /* tell the i2c layer a new client has arrived */ + if (0 != (err = i2c_attach_client(client))) { + kfree(client); + return err; + } + + /* set initial values: set "mute"-input to all outputs at gain 0 */ + err = 0; + for (i = 1; i < 5; i++) { + err += tea6420_switch(client, 6, i, 0); + } + if (err) { + dprintk("could not initialize tea6420\n"); + kfree(client); + return -ENODEV; + } + + printk("tea6420: detected @ 0x%02x on adapter %s\n", address, &client->adapter->name[0]); + + return 0; +} + +static int attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if (adapter->id != I2C_HW_SAA7146) { + dprintk("refusing to probe on unknown adapter [name='%s',id=0x%x]\n", adapter->name, adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter, &addr_data, &tea6420_detect); +} + +static int detach(struct i2c_client *client) +{ + int ret = i2c_detach_client(client); + kfree(client); + return ret; +} + +static int command(struct i2c_client *client, unsigned int cmd, void *arg) +{ + struct tea6420_multiplex *a = (struct tea6420_multiplex *)arg; + int result = 0; + + switch (cmd) { + case TEA6420_SWITCH: + result = tea6420_switch(client, a->in, a->out, a->gain); + break; + default: + return -ENOIOCTLCMD; + } + + return result; +} + +static struct i2c_driver driver = { + .owner = THIS_MODULE, + .name = "tea6420", + .id = I2C_DRIVERID_TEA6420, + .flags = I2C_DF_NOTIFY, + .attach_adapter = attach, + .detach_client = detach, + .command = command, +}; + +static struct i2c_client client_template = { + .name = "tea6420", + .driver = &driver, +}; + +static int __init this_module_init(void) +{ + return i2c_add_driver(&driver); +} + +static void __exit this_module_exit(void) +{ + i2c_del_driver(&driver); +} + +module_init(this_module_init); +module_exit(this_module_exit); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6420 driver"); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/tea6420.h b/linux/drivers/media/video/tea6420.h new file mode 100644 index 000000000..ea664df15 --- /dev/null +++ b/linux/drivers/media/video/tea6420.h @@ -0,0 +1,17 @@ +#ifndef __INCLUDED_TEA6420__ +#define __INCLUDED_TEA6420__ + +/* possible addresses */ +#define I2C_TEA6420_1 0x4c +#define I2C_TEA6420_2 0x4d + +struct tea6420_multiplex +{ + int in; /* input of audio switch */ + int out; /* output of audio switch */ + int gain; /* gain of connection */ +}; + +#define TEA6420_SWITCH _IOW('v',1,struct tea6420_multiplex) + +#endif |