From 084697f7773c3b3cfc2436316bcb4d8d9e4683a3 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Mon, 7 Apr 2003 19:40:04 -0700 Subject: [PATCH] [PATCH] bring core media/video up to date with dvb changes --- drivers/media/video/Kconfig | 27 + drivers/media/video/Makefile | 10 +- drivers/media/video/dpc7146.c | 391 ++++++++++++ drivers/media/video/mxb.c | 1075 ++++++++++++++++++++++++++++++++ drivers/media/video/mxb.h | 42 ++ drivers/media/video/saa7111.c | 79 ++- drivers/media/video/tda9840.c | 289 +++++++++ drivers/media/video/tda9840.h | 35 ++ drivers/media/video/tea6415c.c | 238 +++++++ drivers/media/video/tea6415c.h | 39 ++ drivers/media/video/tea6420.c | 217 +++++++ drivers/media/video/tea6420.h | 17 + 12 files changed, 2426 insertions(+), 33 deletions(-) create mode 100644 drivers/media/video/dpc7146.c create mode 100644 drivers/media/video/mxb.c create mode 100644 drivers/media/video/mxb.h create mode 100644 drivers/media/video/tda9840.c create mode 100644 drivers/media/video/tda9840.h create mode 100644 drivers/media/video/tea6415c.c create mode 100644 drivers/media/video/tea6415c.h create mode 100644 drivers/media/video/tea6420.c create mode 100644 drivers/media/video/tea6420.h diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 8e5e36e797ad0..be8ebca243ddd 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -230,5 +230,32 @@ config VIDEO_SAA7134 whenever you want). If you want to compile it as a module, say M here and read . +config VIDEO_MXB + tristate "Siemens-Nixdorf 'Multimedia eXtension Board'" + depends on VIDEO_DEV && PCI && I2C + ---help--- + This is a video4linux driver for the 'Multimedia eXtension Board' + TV card by Siemens-Nixdorf. + + This driver is available as a module called mxb ( = code + which can be inserted in and removed from the running kernel + whenever you want). If you want to compile it as a module, say M + here and read . + +config VIDEO_DPC + tristate "Philips-Semiconductors 'dpc7146 demonstration board'" + depends on VIDEO_DEV && PCI && I2C + ---help--- + This is a video4linux driver for the 'dpc7146 demonstration + board' by Philips-Semiconductors. It's the reference design + for SAA7146 bases boards, so if you have some unsupported + saa7146 based, analog video card, chances are good that it + will work with this skeleton driver. + + This driver is available as a module called dpc7146 ( = code + which can be inserted in and removed from the running kernel + whenever you want). If you want to compile it as a module, say M + here and read . + endmenu diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 5bb0b014f8e4d..cbc7e09aaf8b7 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -9,7 +9,7 @@ zoran-objs := zr36120.o zr36120_i2c.o zr36120_mem.o obj-$(CONFIG_VIDEO_DEV) += videodev.o v4l2-common.o v4l1-compat.o obj-$(CONFIG_VIDEO_BT848) += bttv.o msp3400.o tvaudio.o \ - tda7432.o tda9875.o tuner.o video-buf.o tda9887.o + tda7432.o tda9875.o obj-$(CONFIG_SOUND_TVMIXER) += tvmixer.o obj-$(CONFIG_VIDEO_ZR36120) += zoran.o @@ -29,5 +29,11 @@ obj-$(CONFIG_VIDEO_CPIA) += cpia.o obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o obj-$(CONFIG_VIDEO_MEYE) += meye.o -obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ tuner.o tda9887.o video-buf.o +obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ +obj-$(CONFIG_VIDEO_MXB) += saa7111.o tuner.o tda9840.o tea6415c.o tea6420.o mxb.o +obj-$(CONFIG_VIDEO_DPC) += saa7111.o dpc7146.o obj-$(CONFIG_TUNER_3036) += tuner-3036.o + +obj-$(CONFIG_VIDEO_TUNER) += tuner.o tda9887.o +obj-$(CONFIG_VIDEO_BUF) += video-buf.o + diff --git a/drivers/media/video/dpc7146.c b/drivers/media/video/dpc7146.c new file mode 100644 index 0000000000000..ee2eb280f6bc1 --- /dev/null +++ b/drivers/media/video/dpc7146.c @@ -0,0 +1,391 @@ +/* + dpc7146.c - v4l2 driver for the dpc7146 demonstration board + + Copyright (C) 2000-2003 Michael Hunold + + 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 +#include /* for saa7111a */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME dpc7146 +#endif + +#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_PARM(debug,"i"); +MODULE_PARM_DESC(debug, "debug verbosity"); + +/* global variables */ +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 = 0; + int i = 0; + + 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)); + + 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 */ + for(i = 0; i < I2C_CLIENT_MAX; i++) { + if( NULL == dpc->i2c_adapter.clients[i] ) { + continue; + } + if( I2C_SAA7111A == dpc->i2c_adapter.clients[i]->addr ) + dpc->saa7111a = dpc->i2c_adapter.clients[i]; + } + + /* check if all devices are present */ + if( 0 == dpc->saa7111a ) { + DEB_D(("dpc_v4l2.o: dpc_attach failed for this device.\n")); + 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 */ + (struct dpc*)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; +} + +/* 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); + 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_dev *dev, unsigned int cmd, void *arg) +{ + 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[] = { + { "PAL-BG", V4L2_STD_PAL_BG, SAA7146_PAL_VALUES }, + { "PAL-I", V4L2_STD_PAL_I, SAA7146_PAL_VALUES }, + { "NTSC", V4L2_STD_NTSC, SAA7146_NTSC_VALUES }, + { "SECAM", V4L2_STD_SECAM, SAA7146_SECAM_VALUES }, +}; + +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, + } +}; + +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, + .ext_vv_data = &vv_data, + + .probe = dpc_probe, + .attach = dpc_attach, + .detach = dpc_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +int __init dpc_init_module(void) +{ + if( 0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +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 "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxb.c b/drivers/media/video/mxb.c new file mode 100644 index 0000000000000..c5a5e6921c13f --- /dev/null +++ b/drivers/media/video/mxb.c @@ -0,0 +1,1075 @@ +/* + mxb.c - v4l2 driver for the Multimedia eXtension Board + + Copyright (C) 1998-2003 Michael Hunold + + 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 +#include /* for saa7111a */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME mxb +#endif + +#include "mxb.h" +#include "tea6415c.h" +#include "tea6420.h" +#include "tda9840.h" +#include + +#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 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_PARM(freq,"i"); +MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); + +static int debug = 0; +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug, "debug verbosity"); + +#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, 1, 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_AUDIOS] = + { 0, 1, 2, 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_freq; /* current frequency the tuner is tuned to */ + int cur_mute; /* current mute status */ +}; + +static int mxb_vbi_bypass(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + s32 byte = 0x0; + int result = 0; + + DEB_EE(("dev:%p\n",dev)); + + /* switch bypass in saa7111a, this should be done in the + saa7111a driver of course... */ + if ( -1 == (result = i2c_smbus_read_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_3))) { + DEB_D(("could not read from saa7111a.\n")); + return -EFAULT; + } + byte = result; + byte &= 0xf0; + byte |= 0x0a; + + if ( 0 != (result = i2c_smbus_write_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_3, byte))) { + DEB_D(("could not write to saa7111a.\n")); + return -EFAULT; + } + return 0; +} + +static int mxb_probe(struct saa7146_dev* dev) +{ + struct mxb* mxb = 0; + int i = 0; + + request_module("tuner"); + request_module("tea6420"); + request_module("tea6415c"); + request_module("tda9840"); + request_module("saa7111"); + + 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)); + + /* 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)); + + 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 */ + for(i = 0; i < I2C_CLIENT_MAX; i++) { + if( NULL == mxb->i2c_adapter.clients[i] ) { + continue; + } + if( I2C_TEA6420_1 == mxb->i2c_adapter.clients[i]->addr ) + mxb->tea6420_1 = mxb->i2c_adapter.clients[i]; + if( I2C_TEA6420_2 == mxb->i2c_adapter.clients[i]->addr ) + mxb->tea6420_2 = mxb->i2c_adapter.clients[i]; + if( I2C_TEA6415C_2 == mxb->i2c_adapter.clients[i]->addr ) + mxb->tea6415c = mxb->i2c_adapter.clients[i]; + if( I2C_TDA9840 == mxb->i2c_adapter.clients[i]->addr ) + mxb->tda9840 = mxb->i2c_adapter.clients[i]; + if( I2C_SAA7111A == mxb->i2c_adapter.clients[i]->addr ) + mxb->saa7111a = mxb->i2c_adapter.clients[i]; + if( 0x60 == mxb->i2c_adapter.clients[i]->addr ) + mxb->tuner = mxb->i2c_adapter.clients[i]; + } + + /* 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. are you sure you\n"); + printk("mxb: insmod'ed tea6420, tea6415c, saa7111, tea6415c and tuner?\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 */ + (struct mxb*)dev->ext_priv = mxb; + + 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 mxb_init_done(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + + struct { + int length; + char data[9]; + } 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} } + }; + + unsigned char init[25] = { + 0x00, + + 0x00, /* 00 - ID byte */ + 0x00, /* 01 - reserved */ + + /*front end */ + 0xd8, /* 02 - FUSE=x, GUDL=x, MODE=x */ + 0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x00, /* 04 - GAI1=256 */ + 0x00, /* 05 - GAI2=256 */ + + /* decoder */ + 0xf0, /* 06 - HSB at xx(50Hz) / xx(60Hz) pixels after end of last line */ + 0x30, /* 07 - HSS at xx(50Hz) / xx(60Hz) pixels after end of last line */ + 0xa8, /* 08 - AUFD=x, FSEL=x, EXFIL=x, VTRC=x, HPLL=x, VNOI=x */ + 0x02, /* 09 - BYPS=x, PREF=x, BPSS=x, VBLB=x, UPTCV=x, APER=x */ + 0x80, /* 0a - BRIG=128 */ + 0x47, /* 0b - CONT=1.109 */ + 0x40, /* 0c - SATN=1.0 */ + 0x00, /* 0d - HUE=0 */ + 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */ + 0x00, /* 0f - reserved */ + 0xd0, /* 10 - OFTS=x, HDEL=x, VRLN=x, YDEL=x */ + 0x8c, /* 11 - GPSW=x, CM99=x, FECO=x, COMPO=x, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + 0x80, /* 12 - xx output control 2 */ + 0x30, /* 13 - xx output control 3 */ + 0x00, /* 14 - reserved */ + 0x15, /* 15 - VBI */ + 0x04, /* 16 - VBI */ + 0x00, /* 17 - VBI */ + }; + + struct i2c_msg msg; + + int i = 0, err = 0; + struct tea6415c_multiplex vm; + + /* write configuration to saa7111a */ + i = i2c_master_send(mxb->saa7111a, init, sizeof(init)); + if (i < 0) { + printk("failed to initialize saa7111a. this should never happen.\n"); + } + + /* select tuner-output on saa7111a */ + i = 0; + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_INPUT, &i); +// i = VIDEO_MODE_PAL; +// mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_NORM, &i); + + mxb_vbi_bypass(dev); + + /* select a tuner type */ + i = 5; + mxb->tuner->driver->command(mxb->tuner,TUNER_SET_TYPE, &i); + + /* 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); + + /* tune in some frequency on tuner */ + mxb->tuner->driver->command(mxb->tuner, VIDIOCSFREQ, &freq); + + /* the rest for mxb */ + mxb->cur_input = 0; + mxb->cur_freq = freq; + 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 = saa7740_init[0].length; + msg.buf = &saa7740_init[0].data[0]; + + if( 1 == (err = i2c_transfer(&mxb->i2c_adapter, &msg, 1))) { + for(i = 1;;i++) { + msg.len = saa7740_init[i].length; + if( -1 == msg.len ) { + break; + } + msg.buf = &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; +} +*/ + +/* 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); + 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; +} + +/* hack: this should go into saa711x */ +static int saa7111_set_gpio(struct saa7146_dev *dev, int bl) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + s32 byte = 0x0; + int result = 0; + + DEB_EE(("dev:%p\n",dev)); + + /* get the old register contents */ + if ( -1 == (byte = i2c_smbus_read_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_1))) { + DEB_D(("could not read from saa711x\n")); + return -EFAULT; + } + + if( 0 == bl ) { + byte &= 0x7f; + } else { + byte |= 0x80; + } + + /* write register contents back */ + if ( 0 != (result = i2c_smbus_write_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_1, byte))) { + DEB_D(("could not write to saa711x\n")); + return -EFAULT; + } + + return 0; +} + +static int mxb_ioctl(struct saa7146_dev *dev, unsigned int cmd, void *arg) +{ + 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; + + byte = mxb->tda9840->driver->command(mxb->tda9840,TDA9840_DETECT, NULL); + 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; + } + + memset(f,0,sizeof(*f)); + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = mxb->cur_freq; + + DEB_EE(("VIDIOC_G_FREQ: freq:0x%08x.\n", mxb->cur_freq)); + return 0; + } + case VIDIOC_S_FREQUENCY: + { + struct v4l2_frequency *f = arg; + int t_locked = 0; + int v_byte = 0; + + 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; + } + + DEB_EE(("VIDIOC_S_FREQUENCY: freq:0x%08x.\n",f->frequency)); + + mxb->cur_freq = f->frequency; + + /* tune in desired frequency */ + mxb->tuner->driver->command(mxb->tuner, VIDIOCSFREQ, &mxb->cur_freq); + + /* check if pll of tuner & saa7111a is locked */ +// mxb->tuner->driver->command(mxb->tuner,TUNER_IS_LOCKED, &t_locked); + mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_GET_STATUS, &v_byte); + + /* not locked -- anything to do here ? */ + if( 0 == t_locked || 0 == (v_byte & DECODER_STATUS_GOOD)) { + } + + /* 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) +{ + 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 */ + saa7111_set_gpio(dev,0); + } 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 */ + saa7111_set_gpio(dev,1); + } + return 0; +} + +static struct saa7146_standard standard[] = { + { "PAL-BG", V4L2_STD_PAL_BG, SAA7146_PAL_VALUES }, + { "PAL-I", V4L2_STD_PAL_I, SAA7146_PAL_VALUES }, + { "NTSC", V4L2_STD_NTSC, SAA7146_NTSC_VALUES }, + { "SECAM", V4L2_STD_SECAM, SAA7146_SECAM_VALUES }, +}; + +static +struct saa7146_extension extension; + +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, + } +}; + +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, + .ext_vv_data = &vv_data, + + .probe = mxb_probe, + .attach = mxb_attach, + .detach = mxb_detach, + + .irq_mask = 0, + .irq_func = NULL, +}; + +int __init mxb_init_module(void) +{ + if( 0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + return 0; +} + +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 "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/mxb.h b/drivers/media/video/mxb.h new file mode 100644 index 0000000000000..755e7927b348d --- /dev/null +++ b/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 */ +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/drivers/media/video/saa7111.c b/drivers/media/video/saa7111.c index 3ac54bdcfb43a..9427c6dfea626 100644 --- a/drivers/media/video/saa7111.c +++ b/drivers/media/video/saa7111.c @@ -57,15 +57,13 @@ struct saa7111 { int sat; }; -#define I2C_SAA7111 0x48 - -#define I2C_DELAY 10 - static unsigned short normal_i2c[] = { 34>>1, I2C_CLIENT_END }; static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; I2C_CLIENT_INSMOD; +static struct i2c_client client_template; + /* ----------------------------------------------------------------------- */ static int saa7111_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind) @@ -73,40 +71,45 @@ static int saa7111_attach(struct i2c_adapter *adap, int addr, unsigned short fla int i; struct saa7111 *decoder; struct i2c_client *client; + + /* who wrote this? init[] is used for i2c_master_send() which expects an array that + will be used for the 'buf' part of an i2c message unchanged. so, the first byte + needs to be the subaddress to start with, then follow the data bytes... */ static const unsigned char init[] = { - 0x00, 0x00, /* 00 - ID byte */ - 0x01, 0x00, /* 01 - reserved */ + 0x00, /* start address */ + + 0x00, /* 00 - ID byte */ + 0x00, /* 01 - reserved */ /*front end */ - 0x02, 0xd0, /* 02 - FUSE=3, GUDL=2, MODE=0 */ - 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 */ + 0xd0, /* 02 - FUSE=3, GUDL=2, MODE=0 */ + 0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ + 0x00, /* 04 - GAI1=256 */ + 0x00, /* 05 - GAI2=256 */ /* decoder */ - 0x06, 0xf3, /* 06 - HSB at 13(50Hz) / 17(60Hz) pixels after end of last line */ - 0x07, 0x13, /* 07 - HSS at 113(50Hz) / 117(60Hz) pixels after end of last line */ - 0x08, 0xc8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, VTRC=1, HPLL=0, VNOI=0 */ - 0x09, 0x01, /* 09 - BYPS=0, PREF=0, BPSS=0, VBLB=0, UPTCV=0, APER=1 */ - 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, 0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */ - 0x11, 0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ - 0x12, 0x00, /* 12 - output control 2 */ - 0x13, 0x00, /* 13 - output control 3 */ - 0x14, 0x00, /* 14 - reserved */ - 0x15, 0x00, /* 15 - VBI */ - 0x16, 0x00, /* 16 - VBI */ - 0x17, 0x00, /* 17 - VBI */ + 0xf3, /* 06 - HSB at 13(50Hz) / 17(60Hz) pixels after end of last line */ + 0x13, /* 07 - HSS at 113(50Hz) / 117(60Hz) pixels after end of last line */ + 0xc8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, VTRC=1, HPLL=0, VNOI=0 */ + 0x01, /* 09 - BYPS=0, PREF=0, BPSS=0, VBLB=0, UPTCV=0, APER=1 */ + 0x80, /* 0a - BRIG=128 */ + 0x47, /* 0b - CONT=1.109 */ + 0x40, /* 0c - SATN=1.0 */ + 0x00, /* 0d - HUE=0 */ + 0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */ + 0x00, /* 0f - reserved */ + 0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */ + 0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ + 0x00, /* 12 - output control 2 */ + 0x00, /* 13 - output control 3 */ + 0x00, /* 14 - reserved */ + 0x00, /* 15 - VBI */ + 0x00, /* 16 - VBI */ + 0x00, /* 17 - VBI */ }; client = kmalloc(sizeof(*client), GFP_KERNEL); if(client == NULL) return -ENOMEM; - memset(client, 0, sizeof(*client)); client_template.adapter = adap; client_template.addr = addr; memcpy(client, &client_template, sizeof(*client)); @@ -136,9 +139,10 @@ static int saa7111_attach(struct i2c_adapter *adap, int addr, unsigned short fla printk(KERN_ERR "%s_attach: init status %d\n", client->dev.name, i); } else { - printk(KERN_INFO "%s_attach: chip version %x\n", - client->dev.name, i2c_smbus_read_byte_data(client, 0x00) >> 4); + printk(KERN_INFO "%s_attach: chip version %x @ 0x%08x\n", + client->dev.name, i2c_smbus_read_byte_data(client, 0x00) >> 4,addr); } + init_MUTEX(&decoder->lock); i2c_attach_client(client); MOD_INC_USE_COUNT; @@ -146,6 +150,16 @@ static int saa7111_attach(struct i2c_adapter *adap, int addr, unsigned short fla } static int saa7111_probe(struct i2c_adapter *adap) { + /* probing unknown devices on any Matrox i2c-bus takes ages due to the + slow bit banging algorithm used. because of the fact a saa7111(a) + is *never* present on a Matrox gfx card, we can skip such adapters + here */ + if( 0 != (adap->id & I2C_HW_B_G400)) { + return -ENODEV; + } + + printk("saa7111: probing %s i2c adapter [id=0x%x]\n", + adap->dev.name,adap->id); return i2c_probe(adap, &addr_data, saa7111_attach); } @@ -385,6 +399,9 @@ static int saa7111_command(struct i2c_client *client, unsigned int cmd, /* ----------------------------------------------------------------------- */ static struct i2c_driver i2c_driver_saa7111 = { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif .name = "saa7111", /* name */ .id = I2C_DRIVERID_SAA7111A, /* ID */ .flags = I2C_DF_NOTIFY, diff --git a/drivers/media/video/tda9840.c b/drivers/media/video/tda9840.c new file mode 100644 index 0000000000000..39d88201ec18e --- /dev/null +++ b/drivers/media/video/tda9840.c @@ -0,0 +1,289 @@ + /* + tda9840.h - i2c-driver for the tda9840 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold + + 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 +#include +#include +#include +#include +#include +#include + +#include "tda9840.h" + +static int debug = 0; /* insmod parameter */ +MODULE_PARM(debug,"i"); +#define dprintk if (debug) printk + +#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}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +/* unique ID allocation */ +static int tda9840_id = 0; + +static struct i2c_driver driver; + +static int tda9840_command(struct i2c_client *client, unsigned int cmd, void* arg) +{ + int result = 0; + + switch (cmd) { + case TDA9840_SWITCH: + { + int byte = *(int*)arg; + + dprintk("tda9840.o: 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; + } + + if ( 0 != (result = i2c_smbus_write_byte_data(client, SWITCH, byte))) { + printk("tda9840.o: TDA9840_SWITCH error.\n"); + return -EFAULT; + } + + return 0; + } + + case TDA9840_LEVEL_ADJUST: + { + int byte = *(int*)arg; + + dprintk("tda9840.o: 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; + + if ( 0 != (result = i2c_smbus_write_byte_data(client, LEVEL_ADJUST, byte))) { + printk("tda9840.o: TDA9840_LEVEL_ADJUST error.\n"); + return -EFAULT; + } + + return 0; + } + + case TDA9840_STEREO_ADJUST: + { + int byte = *(int*)arg; + + dprintk("tda9840.o: 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; + + if ( 0 != (result = i2c_smbus_write_byte_data(client, STEREO_ADJUST, byte))) { + printk("tda9840.o: TDA9840_STEREO_ADJUST error.\n"); + return -EFAULT; + } + + return 0; + } + + case TDA9840_DETECT: + { + int byte = 0x0; + + if ( -1 == (byte = i2c_smbus_read_byte_data(client, STEREO_ADJUST))) { + printk("tda9840.o: TDA9840_DETECT error while reading.\n"); + return -EFAULT; + } + + if( 0 != (byte & 0x80)) { + dprintk("tda9840.o: TDA9840_DETECT, register contents invalid.\n"); + return -EFAULT; + } + + dprintk("tda9840.o: TDA9840_DETECT, result: 0x%02x (original byte)\n",byte); + + return ((byte & 0x60) >> 5); + } + + case TDA9840_TEST: + { + int byte = *(int*)arg; + + dprintk("tda9840.o: TDA9840_TEST: 0x%02x\n",byte); + + /* mask out irrelevant bits */ + byte &= 0x3; + + if ( 0 != (result = i2c_smbus_write_byte_data(client, TEST, byte))) { + printk("tda9840.o: TDA9840_TEST error.\n"); + return -EFAULT; + } + + return 0; + } + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static int tda9840_detect(struct i2c_adapter *adapter, int address, unsigned short flags, 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("tda9840.o: not enough kernel memory.\n"); + return -ENOMEM; + } + + /* fill client structure */ + sprintf(client->dev.name,"tda9840 (0x%02x)", address); + client->id = tda9840_id++; + client->flags = 0; + client->addr = address; + client->adapter = adapter; + client->driver = &driver; + i2c_set_clientdata(client, NULL); + + /* 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; + if ( 0 != (result = tda9840_command(client, TDA9840_LEVEL_ADJUST, &byte))) { + printk("tda9840.o: could not initialize ic #1. continuing anyway. (result:%d)\n",result); + } + + if ( 0 != (result = tda9840_command(client, TDA9840_STEREO_ADJUST, &byte))) { + printk("tda9840.o: could not initialize ic #2. continuing anyway. (result:%d)\n",result); + } + + byte = TDA9840_SET_MONO; + if ( 0 != (result = tda9840_command(client, TDA9840_SWITCH, &byte))) { + printk("tda9840.o: could not initialize ic #3. continuing anyway. (result:%d)\n",result); + } + + printk("tda9840.o: detected @ 0x%02x on adapter %s\n",2*address,&client->adapter->dev.name[0]); + + return 0; +} + +static int tda9840_attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if( adapter->id != I2C_ALGO_SAA7146 ) { + dprintk("tda9840.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->dev.name,adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter,&addr_data,&tda9840_detect); +} + +static int tda9840_detach(struct i2c_client *client) +{ + int err = 0; + + if ( 0 != (err = i2c_detach_client(client))) { + printk("tda9840.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client); + + return 0; +} + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "tda9840 driver", + .id = I2C_DRIVERID_TDA9840, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda9840_attach, + .detach_client = tda9840_detach, + .command = tda9840_command, +}; + +static int tda9840_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void tda9840_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(tda9840_init_module); +module_exit(tda9840_cleanup_module); + +MODULE_AUTHOR("Michael Hunold "); +MODULE_DESCRIPTION("tda9840 driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/media/video/tda9840.h b/drivers/media/video/tda9840.h new file mode 100644 index 0000000000000..28021053bd097 --- /dev/null +++ b/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/drivers/media/video/tea6415c.c b/drivers/media/video/tea6415c.c new file mode 100644 index 0000000000000..71ce11a07f973 --- /dev/null +++ b/drivers/media/video/tea6415c.c @@ -0,0 +1,238 @@ + /* + tea6415c.h - i2c-driver for the tea6415c by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold + + 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 +#include +#include +#include +#include +#include +#include +#include "tea6415c.h" + +static int debug = 0; /* insmod parameter */ +MODULE_PARM(debug,"i"); +#define dprintk if (debug) printk + +#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}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; + +/* unique ID allocation */ +static int tea6415c_id = 0; + +/* this function is called by i2c_probe */ +static int tea6415c_detect(struct i2c_adapter *adapter, int address, unsigned short flags, int kind) +{ + struct i2c_client *client = 0; + 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 */ + sprintf(client->dev.name,"tea6415c (0x%02x)", address); + client->id = tea6415c_id++; + client->flags = 0; + client->addr = address; + client->adapter = adapter; + client->driver = &driver; + + /* tell the i2c layer a new client has arrived */ + if (0 != (err = i2c_attach_client(client))) { + kfree(client); + return err; + } + + printk("tea6415c.o: detected @ 0x%02x on adapter %s\n",2*address,&client->adapter->dev.name[0]); + + return 0; +} + +static int tea6415c_attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if( adapter->id != I2C_ALGO_SAA7146 ) { + dprintk("tea6415c.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->dev.name,adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter,&addr_data,&tea6415c_detect); +} + +static int tea6415c_detach(struct i2c_client *client) +{ + int err = 0; + + if ( 0 != (err = i2c_detach_client(client))) { + printk("tea6415c.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client); + + return 0; +} + +/* makes a connection between the input-pin 'i' and the output-pin 'o' + for the tea6415c-client 'client' */ +static int tea6415c_switch(struct i2c_client *client, int i, int o) +{ + u8 byte = 0; + + dprintk("tea6415c.o: tea6415c_switch: 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; + }; + + if ( 0 != i2c_smbus_write_byte(client,byte)) { + dprintk("tea6415c.o: tea6415c_switch: could not write to tea6415c\n"); + return -1; + } + + return 0; +} + +static int tea6415c_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 = tea6415c_switch(client,v->in,v->out); + break; + } + default: { + return -ENOIOCTLCMD; + } + } + + if ( 0 != result ) + return result; + + return 0; +} + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "tea6415c driver", + .id = I2C_DRIVERID_TEA6415C, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tea6415c_attach, + .detach_client = tea6415c_detach, + .command = tea6415c_command, +}; + +static int tea6415c_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void tea6415c_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(tea6415c_init_module); +module_exit(tea6415c_cleanup_module); + +MODULE_AUTHOR("Michael Hunold "); +MODULE_DESCRIPTION("tea6415c driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/media/video/tea6415c.h b/drivers/media/video/tea6415c.h new file mode 100644 index 0000000000000..f84ed80050b34 --- /dev/null +++ b/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/drivers/media/video/tea6420.c b/drivers/media/video/tea6420.c new file mode 100644 index 0000000000000..b55e8b26c21d5 --- /dev/null +++ b/drivers/media/video/tea6420.c @@ -0,0 +1,217 @@ + /* + tea6420.o - i2c-driver for the tea6420 by SGS Thomson + + Copyright (C) 1998-2003 Michael Hunold + + 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 +#include +#include +#include +#include +#include +#include + +#include "tea6420.h" + +static int debug = 0; /* insmod parameter */ +MODULE_PARM(debug,"i"); +#define dprintk if (debug) printk + +/* 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}; +static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; + +/* magic definition of all other variables and things */ +I2C_CLIENT_INSMOD; + +static struct i2c_driver driver; + +/* unique ID allocation */ +static int tea6420_id = 0; + +/* 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 result = 0; + + dprintk("tea6420.o: tea6420_switch: 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; + } + + /* fixme?: 1 != ... => 0 != */ + if ( 0 != (result = i2c_smbus_write_byte(client,byte))) { + printk("tea6402:%d\n",result); + dprintk(KERN_ERR "tea6420.o: could not switch, result:%d\n",result); + return -EFAULT; + } + + return 0; +} + +/* this function is called by i2c_probe */ +static int tea6420_detect(struct i2c_adapter *adapter, int address, unsigned short flags, 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; + } + + /* fill client structure */ + sprintf(client->dev.name,"tea6420 (0x%02x)", address); + client->id = tea6420_id++; + client->flags = 0; + client->addr = address; + client->adapter = adapter; + client->driver = &driver; + i2c_set_clientdata(client, NULL); + + /* 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( 0 != err) { + printk("tea6420.o: could not initialize chipset. continuing anyway.\n"); + } + + printk("tea6420.o: detected @ 0x%02x on adapter %s\n",2*address,&client->adapter->dev.name[0]); + + return 0; +} + +static int tea6420_attach(struct i2c_adapter *adapter) +{ + /* let's see whether this is a know adapter we can attach to */ + if( adapter->id != I2C_ALGO_SAA7146 ) { + dprintk("tea6420.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->dev.name,adapter->id); + return -ENODEV; + } + + return i2c_probe(adapter,&addr_data,&tea6420_detect); +} + +static int tea6420_detach(struct i2c_client *client) +{ + int err = 0; + + if ( 0 != (err = i2c_detach_client(client))) { + printk("tea6420.o: Client deregistration failed, client not detached.\n"); + return err; + } + + kfree(client); + + return 0; +} + +static int tea6420_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; + } + } + + if ( 0 != result ) + return result; + + return 0; +} + +static struct i2c_driver driver = { +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) + .owner = THIS_MODULE, +#endif + .name = "tea6420 driver", + .id = I2C_DRIVERID_TEA6420, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tea6420_attach, + .detach_client = tea6420_detach, + .command = tea6420_command, +}; + +static int tea6420_init_module(void) +{ + i2c_add_driver(&driver); + return 0; +} + +static void tea6420_cleanup_module(void) +{ + i2c_del_driver(&driver); +} + +module_init(tea6420_init_module); +module_exit(tea6420_cleanup_module); + +MODULE_AUTHOR("Michael Hunold "); +MODULE_DESCRIPTION("tea6420 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/tea6420.h b/drivers/media/video/tea6420.h new file mode 100644 index 0000000000000..ea664df15ad4f --- /dev/null +++ b/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 -- 2.53.0