diff options
-rw-r--r-- | linux/drivers/media/video/mxb.c | 1009 | ||||
-rw-r--r-- | linux/drivers/media/video/mxb.h | 22 | ||||
-rw-r--r-- | linux/drivers/media/video/saa7111.c | 433 | ||||
-rw-r--r-- | linux/drivers/media/video/tda9840.c | 274 | ||||
-rw-r--r-- | linux/drivers/media/video/tda9840.h | 69 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6415c.c | 223 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6415c.h | 68 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6420.c | 202 | ||||
-rw-r--r-- | linux/drivers/media/video/tea6420.h | 46 |
9 files changed, 2346 insertions, 0 deletions
diff --git a/linux/drivers/media/video/mxb.c b/linux/drivers/media/video/mxb.c new file mode 100644 index 000000000..094cbbf9a --- /dev/null +++ b/linux/drivers/media/video/mxb.c @@ -0,0 +1,1009 @@ +/* + mxb.c - v4l2 driver for the Multimedia eXtension Board + + Copyright (C) 1998-2002 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. +*/ + +#include <saa7146.h> + +#include "mxb.h" +#include "tea6415c.h" +#include "tea6420.h" +#include "tda9840.h" +#include "tuner.h" +#include <linux/video_decoder.h> /* for saa7111a */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + #define KBUILD_MODNAME "mxb" +#endif + +#define I2C_SAA7111A 0x24 + +/* 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; + +/* debug verbosity */ +/* fixme */ +static int debug = 247; + +#ifdef MODULE +MODULE_PARM(freq,"i"); +MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); +MODULE_PARM(debug,"i"); +MODULE_PARM_DESC(debug, "debug verbosity"); +#endif + + +#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 audio_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 audio_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 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 */ +}; + +/* this function gets called very early in the registration process of + the extension. it has been reported that some devices need to enable + the i2c-bus explicitly for example -- this can be done here. please + note, that you cannot be sure that the device is really the hardware + you expect, so you should do as little as possible in here, in order + to avoid confusing the hardware. +*/ +static int mxb_preinit(struct saa7146_dev* dev) +{ + return 0; +} + +static int mxb_probe(struct saa7146_dev* dev, unsigned int subvendor, unsigned int subdevice) +{ + struct mxb* mxb = 0; + + int i = 0; + + 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)); + + /* loop through all i2c-devices on the bus and look who is there */ + for(i = 0; i < dev->i2c_adapter->client_count; i++) { + if( I2C_TEA6420_1 == dev->i2c_adapter->clients[i]->addr ) + mxb->tea6420_1 = dev->i2c_adapter->clients[i]; + if( I2C_TEA6420_2 == dev->i2c_adapter->clients[i]->addr ) + mxb->tea6420_2 = dev->i2c_adapter->clients[i]; + if( I2C_TEA6415C_2 == dev->i2c_adapter->clients[i]->addr ) + mxb->tea6415c = dev->i2c_adapter->clients[i]; + if( I2C_TDA9840 == dev->i2c_adapter->clients[i]->addr ) + mxb->tda9840 = dev->i2c_adapter->clients[i]; + if( I2C_SAA7111A == dev->i2c_adapter->clients[i]->addr ) + mxb->saa7111a = dev->i2c_adapter->clients[i]; + if( 0x60 == dev->i2c_adapter->clients[i]->addr ) + mxb->tuner = dev->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 ) { + DEB_D(("this saa7146 is not on an mxb.\n")); + 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 video_decoder_init saa7111a_init; + struct i2c_msg msg; + + int i = 0, err = 0; + struct tea6415c_video_multiplex vm; + + memcpy(&saa7111a_init.data, &init, sizeof(init)); + saa7111a_init.count = sizeof(init); + + /* write configuration to saa7111a */ + mxb->saa7111a->driver->command(mxb->saa7111a, DECODER_INIT, &saa7111a_init); + /* 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); + + /* 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(dev->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(dev->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 bh-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 mxb* mxb = (struct mxb*)dev->ext_priv; + + /* checking for i2c-devices can be omitted here, because we + already did this in "mxb_vl42_probe" */ + + mxb_num++; + + i2c_inc_use_client(mxb->tea6420_1); + i2c_inc_use_client(mxb->tea6420_2); + i2c_inc_use_client(mxb->tea6415c); + i2c_inc_use_client(mxb->tda9840); + i2c_inc_use_client(mxb->saa7111a); + i2c_inc_use_client(mxb->tuner); + + return mxb_init_done(dev); +} + +static int mxb_detach(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + + i2c_dec_use_client(mxb->tea6420_1); + i2c_dec_use_client(mxb->tea6420_2); + i2c_dec_use_client(mxb->tea6415c); + i2c_dec_use_client(mxb->tda9840); + i2c_dec_use_client(mxb->saa7111a); + i2c_dec_use_client(mxb->tuner); + + mxb_num--; + kfree(mxb); + + return 0; +} + +static int mxb_vbi_bypass(struct saa7146_dev* dev) +{ + struct mxb* mxb = (struct mxb*)dev->ext_priv; + int i = 1; + + /* switch bypass in saa7111a */ + /* fixme saa + if ( 0 != mxb->saa7111a->driver->command(mxb->saa7111a,SAA711X_VBI_BYPASS, &i)) { + DEB_D(("could not address saa7111a.\n")); + return -1; + } + */ + + 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; + + /* get the old register contents */ + if ( -1 == (byte = i2c_smbus_read_byte_data(mxb->saa7111a, 0x11))) { + 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, 0x11, 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; + + 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_video_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; + /* fixme: _CAP_NORM needed? */ + 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); + dev->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 }, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) +static void mxb_inc_use(struct saa7146_dev *dev) +{ + MOD_INC_USE_COUNT; +} + +static void mxb_dec_use(struct saa7146_dev *dev) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static struct saa7146_sub_info sub_data[] = { + { 0x0000, 0x0000 }, + { 0xffff, 0xffff }, +}; + +static struct saa7146_extension extension = { + MXB_IDENTIFIER, + MXB_INPUTS, + MXB_AUDIOS, + V4L2_CAP_TUNER, + + sub_data, + + THIS_MODULE, + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + mxb_inc_use, + mxb_dec_use, +#endif + + &standard[0], + sizeof(standard)/sizeof(struct saa7146_standard), + &std_callback, + + 1, + mxb_vbi_bypass, + + &ioctls[0], + + mxb_preinit, + mxb_probe, + mxb_attach, + mxb_detach, + + mxb_ioctl, + + 0, + NULL, +}; + +static int mxb_init(void) +{ + if( 0 != saa7146_register_extension(&extension)) { + DEB_S(("failed to register extension.\n")); + return -ENODEV; + } + + /* mxb_num gets increased if the probe function is called successfully */ + + if( 0 == mxb_num ) { + INFO(("no mxb(s) found.\n")); + return -ENODEV; + } + + INFO(("%d mxb(s) found.\n", mxb_num)); + return 0; +} + +#ifdef MODULE +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("video4linux driver for the Siemens-Nixdorf 'Multimedia eXtension board'"); +MODULE_LICENSE("GPL"); + +EXPORT_NO_SYMBOLS; + +static int mxb_init_module(void) +{ + return mxb_init(); +} + +static void mxb_cleanup_module(void) +{ + saa7146_unregister_extension(&extension); +} + +module_init(mxb_init_module); +module_exit(mxb_cleanup_module); +#endif diff --git a/linux/drivers/media/video/mxb.h b/linux/drivers/media/video/mxb.h new file mode 100644 index 000000000..3f73ea7ca --- /dev/null +++ b/linux/drivers/media/video/mxb.h @@ -0,0 +1,22 @@ +#ifndef __MXB__ +#define __MXB__ + +#define BASE_VIDIOC_MXB 10 + +#define MXB_S_AUDIO_CD _IOW ('V', BASE_VIDIOC_PRIVATE+BASE_VIDIOC_MXB+00, int) +#define MXB_S_AUDIO_LINE _IOW ('V', BASE_VIDIOC_PRIVATE+BASE_VIDIOC_MXB+01, 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] = + { { 0, "Tuner", 0}, + { 1, "AUX1", 0}, + { 2, "AUX2", 0}, + { 3, "AUX3", 0}, + { 4, "Radio (X9)", 0}, + { 5, "CD-ROM (X10)", 0} }; +#endif diff --git a/linux/drivers/media/video/saa7111.c b/linux/drivers/media/video/saa7111.c new file mode 100644 index 000000000..ad2efed8b --- /dev/null +++ b/linux/drivers/media/video/saa7111.c @@ -0,0 +1,433 @@ +/* + saa7111 - Philips SAA7111A video decoder driver version 0.0.3 + + Copyright (C) 1998 Dave Perks <dperks@ibm.net> + + Slight changes for video timing and attachment output by + Wolfgang Scherr <scherr@net4you.net> + + 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/init.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include <linux/videodev.h> +#include <linux/version.h> +#include <linux/i2c.h> + +#include <linux/video_decoder.h> + +#define DEBUG(x) /* Debug driver */ + +/* ----------------------------------------------------------------------- */ + +struct saa7111 { + struct i2c_client *client; + int addr; + struct semaphore lock; + unsigned char reg[32]; + + int norm; + int input; + int enable; + int bright; + int contrast; + int hue; + int sat; +}; + +/* hmm, the saa7111(a) specs don't say anything about address 34>>1 (= 17 = 0x11), + only for 0x24 and 0x25 ... */ +static unsigned short normal_i2c[] = { 34>>1, 0x24, 0x25, 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_client client_template; +/* ----------------------------------------------------------------------- */ + +static int saa7111_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind) +{ + 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, /* start address */ + + 0x00, /* 00 - ID byte */ + 0x00, /* 01 - reserved */ + + /*front end */ + 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 */ + 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; + client_template.adapter = adap; + client_template.addr = addr; + memcpy(client, &client_template, sizeof(*client)); + + decoder = kmalloc(sizeof(*decoder), GFP_KERNEL); + if (decoder == NULL) + { + kfree(client); + return -ENOMEM; + } + + memset(decoder, 0, sizeof(*decoder)); + strcpy(client->name, "saa7111"); + decoder->client = client; + client->data = decoder; + decoder->addr = addr; + decoder->norm = VIDEO_MODE_NTSC; + decoder->input = 0; + decoder->enable = 1; + decoder->bright = 32768; + decoder->contrast = 32768; + decoder->hue = 32768; + decoder->sat = 32768; + + i = i2c_master_send(client, init, sizeof(init)); + if (i < 0) { + printk(KERN_ERR "%s_attach: init status %d\n", + client->name, i); + } else { + printk(KERN_INFO "%s_attach: chip version %x @ 0x%08x\n", + client->name, i2c_smbus_read_byte_data(client, 0x00) >> 4,addr); + } + + init_MUTEX(&decoder->lock); + i2c_attach_client(client); + MOD_INC_USE_COUNT; + return 0; +} +static int saa7111_probe(struct i2c_adapter *adap) +{ + printk("saa7111: probing %s i2c adapter [id=0x%x]\n", + adap->name,adap->id); + return i2c_probe(adap, &addr_data, saa7111_attach); +} + +static int saa7111_detach(struct i2c_client *client) +{ + struct saa7111 *decoder = client->data; + i2c_detach_client(client); + kfree(decoder); + kfree(client); + MOD_DEC_USE_COUNT; + return 0; +} + +static int saa7111_command(struct i2c_client *client, unsigned int cmd, + void *arg) +{ + struct saa7111 *decoder = client->data; + switch (cmd) { + +#if defined(DECODER_DUMP) + case DECODER_DUMP: + { + int i; + + for (i = 0; i < 32; i += 16) { + int j; + + printk("KERN_DEBUG %s: %03x", client->name, + i); + for (j = 0; j < 16; ++j) { + printk(" %02x", + i2c_smbus_read_byte_data(client, + i + j)); + } + printk("\n"); + } + } + break; +#endif /* defined(DECODER_DUMP) */ + case DECODER_GET_CAPABILITIES: + { + struct video_decoder_capability *cap = arg; + + cap->flags + = VIDEO_DECODER_PAL + | VIDEO_DECODER_NTSC + | VIDEO_DECODER_AUTO | VIDEO_DECODER_CCIR; + cap->inputs = 8; + cap->outputs = 1; + } + break; + case DECODER_INIT: + { + int i = 0; + struct video_decoder_init *init = arg; + printk("DECODER_INIT: %d\n",init->count); + i = i2c_master_send(client, init->data, init->count); + if (i < 0) { + printk(KERN_ERR "DECODER_INIT failed."); + return -EFAULT; + } else { + printk(KERN_ERR "DECODER_INIT ok."); + } + } + break; + case DECODER_GET_STATUS: + { + int *iarg = arg; + int status; + int res; + + status = i2c_smbus_read_byte_data(client, 0x1f); + res = 0; + if ((status & (1 << 6)) == 0) { + res |= DECODER_STATUS_GOOD; + } + switch (decoder->norm) { + case VIDEO_MODE_NTSC: + res |= DECODER_STATUS_NTSC; + break; + case VIDEO_MODE_PAL: + res |= DECODER_STATUS_PAL; + break; + default: + case VIDEO_MODE_AUTO: + if ((status & (1 << 5)) != 0) { + res |= DECODER_STATUS_NTSC; + } else { + res |= DECODER_STATUS_PAL; + } + break; + } + if ((status & (1 << 0)) != 0) { + res |= DECODER_STATUS_COLOR; + } + *iarg = res; + } + break; + + case DECODER_SET_NORM: + { + int *iarg = arg; + + switch (*iarg) { + + case VIDEO_MODE_NTSC: + i2c_smbus_write_byte_data(client, 0x08, + (decoder-> + reg[0x08] & 0x3f) | 0x40); + break; + + case VIDEO_MODE_PAL: + i2c_smbus_write_byte_data(client, 0x08, + (decoder-> + reg[0x08] & 0x3f) | 0x00); + break; + + case VIDEO_MODE_AUTO: + i2c_smbus_write_byte_data(client, 0x08, + (decoder-> + reg[0x08] & 0x3f) | 0x80); + break; + + default: + return -EINVAL; + + } + decoder->norm = *iarg; + } + break; + + case DECODER_SET_INPUT: + { + int *iarg = arg; + + if (*iarg < 0 || *iarg > 7) { + return -EINVAL; + } + + if (decoder->input != *iarg) { + decoder->input = *iarg; + /* select mode */ + i2c_smbus_write_byte_data(client, 0x02, + (decoder-> + reg[0x02] & 0xf8) | + decoder->input); + /* bypass chrominance trap for modes 4..7 */ + i2c_smbus_write_byte_data(client, 0x09, + (decoder-> + reg[0x09] & 0x7f) | + ((decoder->input > + 3) ? 0x80 : 0)); + } + } + break; + + case DECODER_SET_OUTPUT: + { + int *iarg = arg; + + /* not much choice of outputs */ + if (*iarg != 0) { + return -EINVAL; + } + } + break; + + case DECODER_ENABLE_OUTPUT: + { + int *iarg = arg; + int enable = (*iarg != 0); + + if (decoder->enable != enable) { + decoder->enable = enable; + +// RJ: If output should be disabled (for playing videos), we also need a open PLL. +// The input is set to 0 (where no input source is connected), although this +// is not necessary. +// +// If output should be enabled, we have to reverse the above. + + if (decoder->enable) { + i2c_smbus_write_byte_data(client, 0x02, + (decoder-> + reg[0x02] & 0xf8) | + decoder->input); + i2c_smbus_write_byte_data(client, 0x08, + (decoder-> + reg[0x08] & 0xfb)); + i2c_smbus_write_byte_data(client, 0x11, + (decoder-> + reg[0x11] & 0xf3) | + 0x0c); + } else { + i2c_smbus_write_byte_data(client, 0x02, + (decoder-> + reg[0x02] & 0xf8)); + i2c_smbus_write_byte_data(client, 0x08, + (decoder-> + reg[0x08] & 0xfb) | + 0x04); + i2c_smbus_write_byte_data(client, 0x11, + (decoder-> + reg[0x11] & 0xf3)); + } + } + } + break; + + case DECODER_SET_PICTURE: + { + struct video_picture *pic = arg; + + if (decoder->bright != pic->brightness) { + /* We want 0 to 255 we get 0-65535 */ + decoder->bright = pic->brightness; + i2c_smbus_write_byte_data(client, 0x0a, + decoder->bright >> 8); + } + if (decoder->contrast != pic->contrast) { + /* We want 0 to 127 we get 0-65535 */ + decoder->contrast = pic->contrast; + i2c_smbus_write_byte_data(client, 0x0b, + decoder->contrast >> 9); + } + if (decoder->sat != pic->colour) { + /* We want 0 to 127 we get 0-65535 */ + decoder->sat = pic->colour; + i2c_smbus_write_byte_data(client, 0x0c, + decoder->sat >> 9); + } + if (decoder->hue != pic->hue) { + /* We want -128 to 127 we get 0-65535 */ + decoder->hue = pic->hue; + i2c_smbus_write_byte_data(client, 0x0d, + (decoder->hue - 32768) >> 8); + } + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* ----------------------------------------------------------------------- */ + +static struct i2c_driver i2c_driver_saa7111 = { + .name = "saa7111", /* name */ + .id = I2C_DRIVERID_SAA7111A, /* ID */ + .flags = I2C_DF_NOTIFY, + .attach_adapter = saa7111_probe, + .detach_client = saa7111_detach, + .command = saa7111_command +}; + +static struct i2c_client client_template = { + .name = "saa7111_client", + .id = -1, + .driver = &i2c_driver_saa7111 +}; + +static int saa7111_init(void) +{ + return i2c_add_driver(&i2c_driver_saa7111); +} + +static void saa7111_exit(void) +{ + i2c_del_driver(&i2c_driver_saa7111); +} + +module_init(saa7111_init); +module_exit(saa7111_exit); +MODULE_LICENSE("GPL"); diff --git a/linux/drivers/media/video/tda9840.c b/linux/drivers/media/video/tda9840.c new file mode 100644 index 000000000..116b59f74 --- /dev/null +++ b/linux/drivers/media/video/tda9840.c @@ -0,0 +1,274 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/init.h> + +#include "tda9840.h" + +#ifdef MODULE +MODULE_PARM(debug,"i"); +#endif + +static int debug = 0; /* insmod parameter */ +#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->name,"tda9840 (0x%02x)", address); + client->id = tda9840_id++; + client->flags = 0; + client->addr = address; + client->adapter = adapter; + client->driver = &driver; + client->data = 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->name[0]); + + return 0; +} + +static int tda9840_attach(struct i2c_adapter *adapter) +{ + 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; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) +static void tda9840_inc_use(struct i2c_client *client) +{ + MOD_INC_USE_COUNT; +} + +static void tda9840_dec_use(struct i2c_client *client) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static struct i2c_driver driver = { + .name = "tda9840 driver", + .id = I2C_DRIVERID_TDA9840, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tda9840_attach, + .detach_client = tda9840_detach, + .command = tda9840_command, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + .inc_use = tda9840_inc_use, + .dec_use = tda9840_dec_use, +#endif +}; + +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); + +#ifdef MODULE +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tda9840 driver"); +MODULE_LICENSE("GPL"); +#endif + diff --git a/linux/drivers/media/video/tda9840.h b/linux/drivers/media/video/tda9840.h new file mode 100644 index 000000000..54e4a77e4 --- /dev/null +++ b/linux/drivers/media/video/tda9840.h @@ -0,0 +1,69 @@ + /* + tda9840.h - definitions for the i2c-driver + for the tda9840 by SGS Thomson + + Copyright (C) 1998,1999,2000 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. + */ + +#ifndef __INCLUDED_TDA9840__ +#define __INCLUDED_TDA9840__ + +#define I2C_TDA9840 0x42 + +/* I'm using the currently unused letter 'd' for + identifying. perhaps this will be changed in the + future. + + (see /usr/src/linux/Documentation/ioctl-number.txt + for further details.) */ + +#define TDA9840_DETECT _IOR('d',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('d',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('d',3,int) + +/* values may range between +2.5 and -2.4; + the value has to be multiplied with 10 */ +#define TDA9840_STEREO_ADJUST _IOW('d',4,int) + +/* currently not implemented */ +#define TDA9840_TEST _IOW('d',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..5050fe286 --- /dev/null +++ b/linux/drivers/media/video/tea6415c.c @@ -0,0 +1,223 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include "tea6415c.h" + +#ifdef MODULE +MODULE_PARM(debug,"i"); +#endif + +static int debug = 0; /* insmod parameter */ +#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->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->name[0]); + + return 0; +} + +static int tea6415c_attach(struct i2c_adapter *adapter) +{ + 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_video_multiplex *v = (struct tea6415c_video_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; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) +static void tea6415c_inc_use(struct i2c_client *client) +{ + MOD_INC_USE_COUNT; +} + +static void tea6415c_dec_use(struct i2c_client *client) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static struct i2c_driver driver = { + "tea6415c driver", + I2C_DRIVERID_TEA6415C, + I2C_DF_NOTIFY, + tea6415c_attach, + tea6415c_detach, + tea6415c_command, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + tea6415c_inc_use, + tea6415c_dec_use, +#endif +}; + +EXPORT_NO_SYMBOLS; + +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); + +#ifdef MODULE +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6415c driver"); +MODULE_LICENSE("GPL"); +#endif + diff --git a/linux/drivers/media/video/tea6415c.h b/linux/drivers/media/video/tea6415c.h new file mode 100644 index 000000000..775fb6fe8 --- /dev/null +++ b/linux/drivers/media/video/tea6415c.h @@ -0,0 +1,68 @@ + /* + tea6415c.h - definitions for the i2c-driver + for the tea6415c by SGS Thomson + + Copyright (C) 1998,1999,2000 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. + */ + +#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_video_multiplex +{ + int in; /* input-pin */ + int out; /* output-pin */ +}; + +#define TEA6415C_SWITCH _IOW('t',1,struct tea6415c_video_multiplex) + +#endif diff --git a/linux/drivers/media/video/tea6420.c b/linux/drivers/media/video/tea6420.c new file mode 100644 index 000000000..9955df2cf --- /dev/null +++ b/linux/drivers/media/video/tea6420.c @@ -0,0 +1,202 @@ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/init.h> + +#include "tea6420.h" + +#ifdef MODULE +MODULE_PARM(debug,"i"); +#endif + +static int debug = 0; /* insmod parameter */ +#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->name,"tea6420 (0x%02x)", address); + client->id = tea6420_id++; + client->flags = 0; + client->addr = address; + client->adapter = adapter; + client->driver = &driver; + client->data = 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->name[0]); + + return 0; +} + +static int tea6420_attach(struct i2c_adapter *adapter) +{ + 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 audio_multiplex *a = (struct audio_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; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) +static void tea6420_inc_use(struct i2c_client *client) +{ + MOD_INC_USE_COUNT; +} + +static void tea6420_dec_use(struct i2c_client *client) +{ + MOD_DEC_USE_COUNT; +} +#endif + +static struct i2c_driver driver = { + .name = "tea6420 driver", + .id = I2C_DRIVERID_TEA6420, + .flags = I2C_DF_NOTIFY, + .attach_adapter = tea6420_attach, + .detach_client = tea6420_detach, + .command = tea6420_command, +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) + .inc_use = tea6420_inc_use, + .dec_use = tea6420_dec_use, +#endif +}; + +EXPORT_NO_SYMBOLS; + +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); + +#ifdef MODULE +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("tea6420 driver"); +MODULE_LICENSE("GPL"); +#endif diff --git a/linux/drivers/media/video/tea6420.h b/linux/drivers/media/video/tea6420.h new file mode 100644 index 000000000..5cda0c408 --- /dev/null +++ b/linux/drivers/media/video/tea6420.h @@ -0,0 +1,46 @@ + /* + tea6420.h - definitions for the i2c-driver + for the tea6420 by SGS Thomson + + Copyright (C) 1998,1999,2000 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. + */ + +#ifndef __INCLUDED_TEA6420__ +#define __INCLUDED_TEA6420__ + +/* possible addresses */ +#define I2C_TEA6420_1 0x4c +#define I2C_TEA6420_2 0x4d + +struct audio_multiplex +{ + int in; /* input of audio switch */ + int out; /* output of audio switch */ + int gain; /* gain of connection */ +}; + +#define TEA6420_SWITCH _IOW('t',1,struct audio_multiplex) + +#endif |