/* * for the TDA8425 chip (I don't know which cards have this) * WARNING: THIS DRIVER WILL LOAD WITHOUT COMPLAINTS EVEN IF A DIFFERENT * CHIP IS AT ADDRESS 0x82 (it relies on i2c to make sure that there is a * device acknowledging that address) * * Copyright (c) 1998 Greg Alexander * This code is placed under the terms of the GNU General Public License * Code liberally copied from msp3400.c, which is by Gerd Knorr * * All of this should work, though it would be nice to eventually support * balance (different left,right values). Also, the chip seems (?) to have * two stereo inputs, so if someone has this card, could they tell me if the * second one can be used for anything (i.e., does it have an external input * that you can't hear even if you set input to composite?) */ #include #include #include #include #include #include #include #include #include "i2c.h" #include "bttv.h" #include "videodev.h" #include "audiochip.h" static int debug = 0; /* insmod parameter */ struct tda8425 { struct i2c_bus *bus; int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */ int stereo; __u16 left,right; __u16 bass,treble; }; #define dprintk if (debug) printk /* I don't understand this part (I haven't investigated), it's just there * because that's how MSP3400 did it. */ #if LINUX_VERSION_CODE >= 0x020117 MODULE_PARM(debug,"i"); #endif #define I2C_TDA8425 0x82 /* registers (subaddresses): */ #define TDA8425_VL 0x00 /* volume left */ #define TDA8425_VR 0x01 /* volume right */ #define TDA8425_BA 0x02 /* bass */ #define TDA8425_TR 0x03 /* treble */ #define TDA8425_S1 0x08 /* switch functions */ /* values for those registers: */ #define TDA8425_S1_OFF 0xEE /* audio off (mute on) */ #define TDA8425_S1_ON 0xCE /* audio on (mute off) - "linear stereo" mode */ /* ******************************** * * functions for talking to TDA8425 * * ******************************** */ static int tda8425_write(struct i2c_bus *bus, int addr, int val) { int ret = 0; i2c_start(bus); if (i2c_sendbyte(bus, I2C_TDA8425, 0) || i2c_sendbyte(bus, addr, 0) || i2c_sendbyte(bus, val, 0)) ret = -1; i2c_stop(bus); if (-1 == ret) { printk(KERN_WARNING "tda8425: I/O error, trying (write %d 0x%x)\n", addr, val); } return ret; } static void tda8425_set(struct tda8425 *tda) { /* mode is ignored today */ dprintk(KERN_DEBUG "tda8425_set(%04x,%04x,%04x,%04x)\n",tda->left>>10,tda->right>>10,tda->bass>>12,tda->treble>>12); tda8425_write(tda->bus, TDA8425_VL, tda->left>>10 |0xC0); tda8425_write(tda->bus, TDA8425_VR, tda->right>>10 |0xC0); tda8425_write(tda->bus, TDA8425_BA, tda->bass>>12 |0xF0); tda8425_write(tda->bus, TDA8425_TR, tda->treble>>12|0xF0); } static void tda8425_init(struct tda8425 *tda) { tda->left=tda->right =61440; /* 0dB */ tda->bass=tda->treble=24576; /* 0dB */ tda->mode=AUDIO_OFF; tda->stereo=1; /* left=right=0x27<<10, bass=treble=0x07<<12 */ tda8425_write(tda->bus, TDA8425_S1, TDA8425_S1_OFF); /* mute */ tda8425_set(tda); } static void tda8425_audio(struct tda8425 *tda, int mode) { /* valid for AUDIO_TUNER, RADIO, EXTERN, OFF */ dprintk(KERN_DEBUG "tda8425_audio:%d (T,R,E,I,O)\n",mode); tda->mode=mode; tda8425_write(tda->bus, TDA8425_S1, (mode==AUDIO_OFF)?TDA8425_S1_OFF:TDA8425_S1_ON); /* this is the function we'll need to change if it turns out the * input-selecting capabilities should be used. */ } /* *********************** * * i2c interface functions * * *********************** */ static int tda8425_attach(struct i2c_device *device) { struct tda8425 *tda; LOCK_FLAGS; device->data = tda = kmalloc(sizeof *tda,GFP_KERNEL); if (!tda) return -ENOMEM; memset(tda,0,sizeof *tda); tda->bus = device->bus; LOCK_I2C_BUS(tda->bus); tda8425_init(tda); UNLOCK_I2C_BUS(tda->bus); MOD_INC_USE_COUNT; strcpy(device->name,"TDA8425"); printk(KERN_INFO "tda8425: init\n"); return 0; } static int tda8425_detach(struct i2c_device *device) { struct tda8425 *tda = (struct tda8425 *)device->data; LOCK_FLAGS; LOCK_I2C_BUS(tda->bus); tda8425_init(tda); UNLOCK_I2C_BUS(tda->bus); kfree(tda); MOD_DEC_USE_COUNT; return 0; } static int tda8425_command(struct i2c_device *device, unsigned int cmd, void *arg) { struct tda8425 *tda = (struct tda8425 *)device->data; __u16 *sarg = arg; LOCK_FLAGS; switch (cmd) { case AUDC_SET_RADIO: LOCK_I2C_BUS(tda->bus); tda8425_audio(tda,AUDIO_RADIO); UNLOCK_I2C_BUS(tda->bus); break; case AUDC_GET_VOLUME_LEFT: *sarg = tda->left; break; case AUDC_GET_VOLUME_RIGHT: *sarg = tda->right; break; case AUDC_SET_VOLUME_LEFT: tda->left = *sarg; LOCK_I2C_BUS(tda->bus); tda8425_set(tda); UNLOCK_I2C_BUS(tda->bus); break; case AUDC_SET_VOLUME_RIGHT: tda->right = *sarg; LOCK_I2C_BUS(tda->bus); tda8425_set(tda); UNLOCK_I2C_BUS(tda->bus); break; case AUDC_GET_BASS: *sarg = tda->bass; break; case AUDC_SET_BASS: tda->bass = *sarg; LOCK_I2C_BUS(tda->bus); tda8425_set(tda); UNLOCK_I2C_BUS(tda->bus); break; case AUDC_GET_TREBLE: *sarg = tda->treble; break; case AUDC_SET_TREBLE: tda->treble = *sarg; LOCK_I2C_BUS(tda->bus); tda8425_set(tda); UNLOCK_I2C_BUS(tda->bus); break; case AUDC_GET_STEREO: *sarg = tda->stereo?VIDEO_SOUND_STEREO:VIDEO_SOUND_MONO; break; case AUDC_SET_STEREO: tda->stereo=(*sarg==VIDEO_SOUND_MONO)?0:1; /* TODO: make this write to the TDA9850? */ break; case AUDC_SET_INPUT: LOCK_I2C_BUS(tda->bus); tda8425_audio(tda,*sarg); UNLOCK_I2C_BUS(tda->bus); break; /* case AUDC_SWITCH_MUTE: someday, maybe -- not a lot of point to case AUDC_NEWCHANNEL: it and it would require preserving state case AUDC_GET_DC: huh?? (not used by bttv.c) */ default: return -EINVAL; } return 0; } static struct i2c_driver i2c_driver_tda = { "tda8425", /* name */ I2C_DRIVERID_AUDIOCHIP, /* ID */ I2C_TDA8425, I2C_TDA8425, /* addr range */ tda8425_attach, tda8425_detach, tda8425_command }; #ifdef MODULE int init_module(void) #else int tda8425_init(void) #endif { i2c_register_driver(&i2c_driver_tda); return 0; } #ifdef MODULE void cleanup_module(void) { i2c_unregister_driver(&i2c_driver_tda); } #endif