summaryrefslogtreecommitdiffstats
path: root/drivers/char/tda8425.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/char/tda8425.c')
-rw-r--r--drivers/char/tda8425.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/drivers/char/tda8425.c b/drivers/char/tda8425.c
new file mode 100644
index 000000000..87d79b00e
--- /dev/null
+++ b/drivers/char/tda8425.c
@@ -0,0 +1,325 @@
+/*
+ * 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 <galexand@acm.org>
+ * 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 <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/malloc.h>
+#include <linux/videodev.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+#include "bttv.h"
+#include "audiochip.h"
+
+/* Addresses to scan */
+#define I2C_TDA8425 0x82
+static unsigned short normal_i2c[] = {
+ I2C_TDA8425 >> 1,
+ I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {I2C_CLIENT_END};
+static unsigned short probe[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static struct i2c_client_address_data addr_data = {
+ normal_i2c, normal_i2c_range,
+ probe, probe_range,
+ ignore, ignore_range,
+ force
+};
+
+MODULE_PARM(debug,"i");
+static int debug = 0; /* insmod parameter */
+#define dprintk if (debug) printk
+
+
+struct tda8425 {
+ int mode; /* set to AUDIO_{OFF,TUNER,RADIO,EXTERN} */
+ int stereo;
+ __u16 left,right;
+ __u16 bass,treble;
+};
+
+static struct i2c_driver driver;
+static struct i2c_client client_template;
+
+
+#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_client *client, int addr, int val)
+{
+ unsigned char buffer[2];
+
+ buffer[0] = addr;
+ buffer[1] = val;
+ if (2 != i2c_master_send(client,buffer,2)) {
+ printk(KERN_WARNING "tda8425: I/O error, trying (write %d 0x%x)\n",
+ addr, val);
+ return -1;
+ }
+ return 0;
+}
+
+static void tda8425_set(struct i2c_client *client)
+{
+ struct tda8425 *tda = client->data;
+
+ /* 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(client, TDA8425_VL, tda->left>>10 |0xC0);
+ tda8425_write(client, TDA8425_VR, tda->right>>10 |0xC0);
+ tda8425_write(client, TDA8425_BA, tda->bass>>12 |0xF0);
+ tda8425_write(client, TDA8425_TR, tda->treble>>12|0xF0);
+}
+
+static void tda8425_init(struct i2c_client *client)
+{
+ struct tda8425 *tda = client->data;
+
+ 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(client, TDA8425_S1, TDA8425_S1_OFF); /* mute */
+ tda8425_set(client);
+}
+
+static void tda8425_audio(struct i2c_client *client, int mode)
+{
+ struct tda8425 *tda = client->data;
+
+ /* 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(client, 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_adapter *adap, int addr,
+ unsigned short flags, int kind)
+{
+ struct tda8425 *tda;
+ struct i2c_client *client;
+
+ client = kmalloc(sizeof *client,GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+ memcpy(client,&client_template,sizeof(struct i2c_client));
+ client->adapter = adap;
+ client->addr = addr;
+
+ client->data = tda = kmalloc(sizeof *tda,GFP_KERNEL);
+ if (!tda)
+ return -ENOMEM;
+ memset(tda,0,sizeof *tda);
+ tda8425_init(client);
+ MOD_INC_USE_COUNT;
+ strcpy(client->name,"TDA8425");
+ printk(KERN_INFO "tda8425: init\n");
+
+ i2c_attach_client(client);
+ return 0;
+}
+
+static int tda8425_probe(struct i2c_adapter *adap)
+{
+ if (adap->id == (I2C_ALGO_BIT | I2C_HW_B_BT848))
+ return i2c_probe(adap, &addr_data, tda8425_attach);
+ return 0;
+}
+
+
+static int tda8425_detach(struct i2c_client *client)
+{
+ struct tda8425 *tda = client->data;
+
+ tda8425_init(client);
+ i2c_detach_client(client);
+
+ kfree(tda);
+ kfree(client);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int tda8425_command(struct i2c_client *client,
+ unsigned int cmd, void *arg)
+{
+ struct tda8425 *tda = client->data;
+ __u16 *sarg = arg;
+
+ switch (cmd) {
+ case AUDC_SET_RADIO:
+ tda8425_audio(client,AUDIO_RADIO);
+ break;
+ case AUDC_SET_INPUT:
+ tda8425_audio(client,*sarg);
+ break;
+
+ /* --- v4l ioctls --- */
+ /* take care: bttv does userspace copying, we'll get a
+ kernel pointer here... */
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ va->flags |= VIDEO_AUDIO_VOLUME |
+ VIDEO_AUDIO_BASS |
+ VIDEO_AUDIO_TREBLE;
+ va->volume=MAX(tda->left,tda->right);
+ va->balance=(32768*MIN(tda->left,tda->right))/
+ (va->volume ? va->volume : 1);
+ va->balance=(tda->left<tda->right)?
+ (65535-va->balance) : va->balance;
+ va->bass = tda->bass;
+ va->treble = tda->treble;
+ break;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio *va = arg;
+
+ tda->left = (MIN(65536 - va->balance,32768) *
+ va->volume) / 32768;
+ tda->right = (MIN(va->balance,32768) *
+ va->volume) / 32768;
+ tda->bass = va->bass;
+ tda->treble = va->treble;
+ tda8425_set(client);
+ break;
+ }
+
+#if 0
+ /* --- old, obsolete interface --- */
+ 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;
+ tda8425_set(client);
+ break;
+ case AUDC_SET_VOLUME_RIGHT:
+ tda->right = *sarg;
+ tda8425_set(client);
+ break;
+
+ case AUDC_GET_BASS:
+ *sarg = tda->bass;
+ break;
+ case AUDC_SET_BASS:
+ tda->bass = *sarg;
+ tda8425_set(client);
+ break;
+
+ case AUDC_GET_TREBLE:
+ *sarg = tda->treble;
+ break;
+ case AUDC_SET_TREBLE:
+ tda->treble = *sarg;
+ tda8425_set(client);
+ 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_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)
+*/
+#endif
+ default:
+ /* nothing */
+ }
+ return 0;
+}
+
+
+static struct i2c_driver driver = {
+ "i2c tda8424 driver",
+ I2C_DRIVERID_TDA8425,
+ I2C_DF_NOTIFY,
+ tda8425_probe,
+ tda8425_detach,
+ tda8425_command,
+};
+
+static struct i2c_client client_template =
+{
+ "(unset)", /* name */
+ -1,
+ 0,
+ 0,
+ NULL,
+ &driver
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+int tda8425_init(void)
+#endif
+{
+ i2c_add_driver(&driver);
+ return 0;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ i2c_del_driver(&driver);
+}
+#endif
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */