[ALSA] Enable the analog loopback of the Revolution 5.1
authorJochen Voss <voss@seehuhn.de>
Wed, 4 Oct 2006 16:08:43 +0000 (18:08 +0200)
committerJaroslav Kysela <perex@suse.cz>
Fri, 9 Feb 2007 08:00:05 +0000 (09:00 +0100)
Enable the analog loopback of the Revolution 5.1 card.
This patch adds support for the PT2258 volume controller and modifies
the Revolution 5.1 driver to make use of this facility.  This allows
to control the analog loopback of the card.

Signed-off-by: Jochen Voss <voss@seehuhn.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>
include/sound/pt2258.h [new file with mode: 0644]
sound/i2c/Makefile
sound/i2c/other/Makefile
sound/i2c/other/pt2258.c [new file with mode: 0644]
sound/pci/ice1712/ice1712.h
sound/pci/ice1712/revo.c
sound/pci/ice1712/revo.h

diff --git a/include/sound/pt2258.h b/include/sound/pt2258.h
new file mode 100644 (file)
index 0000000..160f812
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ *   ALSA Driver for the PT2258 volume controller.
+ *
+ *     Copyright (c) 2006  Jochen Voss <voss@seehuhn.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#ifndef __SOUND_PT2258_H
+#define __SOUND_PT2258_H
+
+struct snd_pt2258 {
+       struct snd_card *card;
+       struct snd_i2c_bus *i2c_bus;
+       struct snd_i2c_device *i2c_dev;
+
+       unsigned char volume[6];
+       int mute;
+};
+
+extern int snd_pt2258_reset(struct snd_pt2258 *pt);
+extern int snd_pt2258_build_controls(struct snd_pt2258 *pt);
+
+#endif /* __SOUND_PT2258_H */
index 816a2e7c88ca88a62940d67fe288b17c678949e3..45902d48c89c78aaee72bb16b6e1b63691617ff3 100644 (file)
@@ -16,3 +16,4 @@ obj-$(CONFIG_SND) += other/
 # Toplevel Module Dependency
 obj-$(CONFIG_SND_INTERWAVE_STB) += snd-tea6330t.o snd-i2c.o
 obj-$(CONFIG_SND_ICE1712) += snd-cs8427.o snd-i2c.o
+obj-$(CONFIG_SND_ICE1724) += snd-i2c.o
index 2fe023ef00a7d643248bd47c97d473fabf0960c9..77a8a7c75dd9b0f21c9b5756a1d3715d7b8bda9b 100644 (file)
@@ -6,11 +6,11 @@
 snd-ak4114-objs := ak4114.o
 snd-ak4117-objs := ak4117.o
 snd-ak4xxx-adda-objs := ak4xxx-adda.o
+snd-pt2258-objs := pt2258.o
 snd-tea575x-tuner-objs := tea575x-tuner.o
 
 # Module Dependency
 obj-$(CONFIG_SND_PDAUDIOCF) += snd-ak4117.o
 obj-$(CONFIG_SND_ICE1712) += snd-ak4xxx-adda.o
-obj-$(CONFIG_SND_ICE1724) += snd-ak4xxx-adda.o
-obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o
+obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o snd-ak4xxx-adda.o snd-pt2258.o
 obj-$(CONFIG_SND_FM801_TEA575X) += snd-tea575x-tuner.o
diff --git a/sound/i2c/other/pt2258.c b/sound/i2c/other/pt2258.c
new file mode 100644 (file)
index 0000000..50df1df
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ *   ALSA Driver for the PT2258 volume controller.
+ *
+ *     Copyright (c) 2006  Jochen Voss <voss@seehuhn.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */      
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/i2c.h>
+#include <sound/pt2258.h>
+
+MODULE_AUTHOR("Jochen Voss <voss@seehuhn.de>");
+MODULE_DESCRIPTION("PT2258 volume controller (Princeton Technology Corp.)");
+MODULE_LICENSE("GPL");
+
+#define PT2258_CMD_RESET 0xc0
+#define PT2258_CMD_UNMUTE 0xf8
+#define PT2258_CMD_MUTE 0xf9
+
+static const unsigned char pt2258_channel_code[12] = {
+       0x80, 0x90,             /* channel 1: -10dB, -1dB */
+       0x40, 0x50,             /* channel 2: -10dB, -1dB */
+       0x00, 0x10,             /* channel 3: -10dB, -1dB */
+       0x20, 0x30,             /* channel 4: -10dB, -1dB */
+       0x60, 0x70,             /* channel 5: -10dB, -1dB */
+       0xa0, 0xb0              /* channel 6: -10dB, -1dB */
+};
+
+int snd_pt2258_reset(struct snd_pt2258 *pt)
+{
+       unsigned char bytes[2];
+       int i;
+
+       /* reset chip */
+       bytes[0] = PT2258_CMD_RESET;
+       snd_i2c_lock(pt->i2c_bus);
+       if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
+               goto __error;
+       snd_i2c_unlock(pt->i2c_bus);
+
+       /* mute all channels */
+       pt->mute = 1;
+       bytes[0] = PT2258_CMD_MUTE;
+       snd_i2c_lock(pt->i2c_bus);
+       if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
+               goto __error;
+       snd_i2c_unlock(pt->i2c_bus);
+
+       /* set all channels to 0dB */
+       for (i = 0; i < 6; ++i)
+               pt->volume[i] = 0;
+       bytes[0] = 0xd0;
+       bytes[1] = 0xe0;
+       snd_i2c_lock(pt->i2c_bus);
+       if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
+               goto __error;
+       snd_i2c_unlock(pt->i2c_bus);
+
+       return 0;
+
+      __error:
+       snd_i2c_unlock(pt->i2c_bus);
+       snd_printk(KERN_ERR "PT2258 reset failed\n");
+       return -EIO;
+}
+
+static int pt2258_stereo_volume_info(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 2;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 79;
+       return 0;
+}
+
+static int pt2258_stereo_volume_get(struct snd_kcontrol *kcontrol,
+                                   struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_pt2258 *pt = kcontrol->private_data;
+       int base = kcontrol->private_value;
+
+       /* chip does not support register reads */
+       ucontrol->value.integer.value[0] = 79 - pt->volume[base];
+       ucontrol->value.integer.value[1] = 79 - pt->volume[base + 1];
+       return 0;
+}
+
+static int pt2258_stereo_volume_put(struct snd_kcontrol *kcontrol,
+                                   struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_pt2258 *pt = kcontrol->private_data;
+       int base = kcontrol->private_value;
+       unsigned char bytes[2];
+       int val0, val1;
+
+       val0 = 79 - ucontrol->value.integer.value[0];
+       val1 = 79 - ucontrol->value.integer.value[1];
+       if (val0 == pt->volume[base] && val1 == pt->volume[base + 1])
+               return 0;
+
+       pt->volume[base] = val0;
+       bytes[0] = pt2258_channel_code[2 * base] | (val0 / 10);
+       bytes[1] = pt2258_channel_code[2 * base + 1] | (val0 % 10);
+       snd_i2c_lock(pt->i2c_bus);
+       if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
+               goto __error;
+       snd_i2c_unlock(pt->i2c_bus);
+
+       pt->volume[base + 1] = val1;
+       bytes[0] = pt2258_channel_code[2 * base + 2] | (val1 / 10);
+       bytes[1] = pt2258_channel_code[2 * base + 3] | (val1 % 10);
+       snd_i2c_lock(pt->i2c_bus);
+       if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 2) != 2)
+               goto __error;
+       snd_i2c_unlock(pt->i2c_bus);
+
+       return 1;
+
+      __error:
+       snd_i2c_unlock(pt->i2c_bus);
+       snd_printk(KERN_ERR "PT2258 access failed\n");
+       return -EIO;
+}
+
+static int pt2258_switch_info(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+       uinfo->count = 1;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = 1;
+       return 0;
+}
+
+static int pt2258_switch_get(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_pt2258 *pt = kcontrol->private_data;
+
+       ucontrol->value.integer.value[0] = !pt->mute;
+       return 0;
+}
+
+static int pt2258_switch_put(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_pt2258 *pt = kcontrol->private_data;
+       unsigned char bytes[2];
+       int val;
+
+       val = !ucontrol->value.integer.value[0];
+       if (pt->mute == val)
+               return 0;
+
+       pt->mute = val;
+       bytes[0] = val ? PT2258_CMD_MUTE : PT2258_CMD_UNMUTE;
+       snd_i2c_lock(pt->i2c_bus);
+       if (snd_i2c_sendbytes(pt->i2c_dev, bytes, 1) != 1)
+               goto __error;
+       snd_i2c_unlock(pt->i2c_bus);
+
+       return 1;
+
+      __error:
+       snd_i2c_unlock(pt->i2c_bus);
+       snd_printk(KERN_ERR "PT2258 access failed 2\n");
+       return -EIO;
+}
+
+static DECLARE_TLV_DB_SCALE(pt2258_db_scale, -7900, 100, 0);
+
+int snd_pt2258_build_controls(struct snd_pt2258 *pt)
+{
+       struct snd_kcontrol_new knew;
+       char *names[3] = {
+               "Mic Loopback Playback Volume",
+               "Line Loopback Playback Volume",
+               "CD Loopback Playback Volume"
+       };
+       int i, err;
+
+       for (i = 0; i < 3; ++i) {
+               memset(&knew, 0, sizeof(knew));
+               knew.name = names[i];
+               knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+               knew.count = 1;
+               knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+                   SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+               knew.private_value = 2 * i;
+               knew.info = pt2258_stereo_volume_info;
+               knew.get = pt2258_stereo_volume_get;
+               knew.put = pt2258_stereo_volume_put;
+               knew.tlv.p = pt2258_db_scale;
+
+               err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt));
+               if (err < 0)
+                       return err;
+       }
+
+       memset(&knew, 0, sizeof(knew));
+       knew.name = "Loopback Switch";
+       knew.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+       knew.info = pt2258_switch_info;
+       knew.get = pt2258_switch_get;
+       knew.put = pt2258_switch_put;
+       knew.access = 0;
+       err = snd_ctl_add(pt->card, snd_ctl_new1(&knew, pt));
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+EXPORT_SYMBOL(snd_pt2258_reset);
+EXPORT_SYMBOL(snd_pt2258_build_controls);
index ce27eac40d4e15d5e8e10a5f3e94b65cda98d90b..064542bf3af89d2881d973ddda298872dcc64bb3 100644 (file)
@@ -28,6 +28,7 @@
 #include <sound/i2c.h>
 #include <sound/ak4xxx-adda.h>
 #include <sound/ak4114.h>
+#include <sound/pt2258.h>
 #include <sound/pcm.h>
 #include <sound/mpu401.h>
 
@@ -381,6 +382,11 @@ struct snd_ice1712 {
                        unsigned short master[2];
                        unsigned short vol[8];
                } phase28;
+               /* a non-standard I2C device for revo51 */
+               struct revo51_spec {
+                       struct snd_i2c_device *dev;
+                       struct snd_pt2258 *pt2258;
+               } revo51;
                /* Hoontech-specific setting */
                struct hoontech_spec {
                        unsigned char boxbits[4];
@@ -462,6 +468,14 @@ static inline void snd_ice1712_gpio_write_bits(struct snd_ice1712 *ice,
        snd_ice1712_gpio_write(ice, mask & bits);
 }
 
+static inline int snd_ice1712_gpio_read_bits(struct snd_ice1712 *ice,
+                                             unsigned int mask)
+{
+       ice->gpio.direction &= ~mask;
+       snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+       return  (snd_ice1712_gpio_read(ice) & mask);
+}
+
 int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice);
 
 int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *template,
index d556de59b9ae5ab2f3bce0a07aece623ce368e71..233e9a5a2e70215dfe64660a2c8c830d01f14c50 100644 (file)
@@ -83,6 +83,102 @@ static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
        snd_akm4xxx_reset(ak, 0);
 }
 
+/*
+ * I2C access to the PT2258 volume controller on GPIO 6/7 (Revolution 5.1)
+ */
+
+static void revo_i2c_start(struct snd_i2c_bus *bus)
+{
+       struct snd_ice1712 *ice = bus->private_data;
+       snd_ice1712_save_gpio_status(ice);
+}
+
+static void revo_i2c_stop(struct snd_i2c_bus *bus)
+{
+       struct snd_ice1712 *ice = bus->private_data;
+       snd_ice1712_restore_gpio_status(ice);
+}
+
+static void revo_i2c_direction(struct snd_i2c_bus *bus, int clock, int data)
+{
+       struct snd_ice1712 *ice = bus->private_data;
+       unsigned int mask, val;
+
+       val = 0;
+       if (clock)
+               val |= VT1724_REVO_I2C_CLOCK;   /* write SCL */
+       if (data)
+               val |= VT1724_REVO_I2C_DATA;    /* write SDA */
+       mask = VT1724_REVO_I2C_CLOCK | VT1724_REVO_I2C_DATA;
+       ice->gpio.direction &= ~mask;
+       ice->gpio.direction |= val;
+       snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+       snd_ice1712_gpio_set_mask(ice, ~mask);
+}
+
+static void revo_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data)
+{
+       struct snd_ice1712 *ice = bus->private_data;
+       unsigned int val = 0;
+
+       if (clk)
+               val |= VT1724_REVO_I2C_CLOCK;
+       if (data)
+               val |= VT1724_REVO_I2C_DATA;
+       snd_ice1712_gpio_write_bits(ice,
+                                   VT1724_REVO_I2C_DATA |
+                                   VT1724_REVO_I2C_CLOCK, val);
+       udelay(5);
+}
+
+static int revo_i2c_getdata(struct snd_i2c_bus *bus, int ack)
+{
+       struct snd_ice1712 *ice = bus->private_data;
+       int bit;
+
+       if (ack)
+               udelay(5);
+       bit = snd_ice1712_gpio_read_bits(ice, VT1724_REVO_I2C_DATA) ? 1 : 0;
+       return bit;
+}
+
+static struct snd_i2c_bit_ops revo51_bit_ops = {
+       .start = revo_i2c_start,
+       .stop = revo_i2c_stop,
+       .direction = revo_i2c_direction,
+       .setlines = revo_i2c_setlines,
+       .getdata = revo_i2c_getdata,
+};
+
+static int revo51_i2c_init(struct snd_ice1712 *ice,
+                          struct snd_pt2258 *pt)
+{
+       int err;
+
+       /* create the I2C bus */
+       err = snd_i2c_bus_create(ice->card, "ICE1724 GPIO6", NULL, &ice->i2c);
+       if (err < 0)
+               return err;
+
+       ice->i2c->private_data = ice;
+       ice->i2c->hw_ops.bit = &revo51_bit_ops;
+
+       /* create the I2C device */
+       err = snd_i2c_device_create(ice->i2c, "PT2258", 0x40,
+                                   &ice->spec.revo51.dev);
+       if (err < 0)
+               return err;
+
+       pt->card = ice->card;
+       pt->i2c_bus = ice->i2c;
+       pt->i2c_dev = ice->spec.revo51.dev;
+       ice->spec.revo51.pt2258 = pt;
+
+       snd_pt2258_reset(pt);
+
+       return 0;
+}
+
 /*
  * initialize the chips on M-Audio Revolution cards
  */
@@ -180,9 +276,9 @@ static struct snd_ak4xxx_private akm_revo51_priv __devinitdata = {
        .cif = 0,
        .data_mask = VT1724_REVO_CDOUT,
        .clk_mask = VT1724_REVO_CCLK,
-       .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
-       .cs_addr = VT1724_REVO_CS1 | VT1724_REVO_CS2,
-       .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+       .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+       .cs_addr = VT1724_REVO_CS1,
+       .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
        .add_flags = VT1724_REVO_CCLK, /* high at init */
        .mask_flags = 0,
 };
@@ -198,13 +294,15 @@ static struct snd_ak4xxx_private akm_revo51_adc_priv __devinitdata = {
        .cif = 0,
        .data_mask = VT1724_REVO_CDOUT,
        .clk_mask = VT1724_REVO_CCLK,
-       .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
-       .cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS2,
-       .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+       .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+       .cs_addr = VT1724_REVO_CS0,
+       .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
        .add_flags = VT1724_REVO_CCLK, /* high at init */
        .mask_flags = 0,
 };
 
+static struct snd_pt2258 ptc_revo51_volume;
+
 static int __devinit revo_init(struct snd_ice1712 *ice)
 {
        struct snd_akm4xxx *ak;
@@ -243,14 +341,20 @@ static int __devinit revo_init(struct snd_ice1712 *ice)
                break;
        case VT1724_SUBDEVICE_REVOLUTION51:
                ice->akm_codecs = 2;
-               if ((err = snd_ice1712_akm4xxx_init(ak, &akm_revo51, &akm_revo51_priv, ice)) < 0)
+               err = snd_ice1712_akm4xxx_init(ak, &akm_revo51,
+                                              &akm_revo51_priv, ice);
+               if (err < 0)
                        return err;
-               err = snd_ice1712_akm4xxx_init(ak + 1, &akm_revo51_adc,
+               err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo51_adc,
                                               &akm_revo51_adc_priv, ice);
                if (err < 0)
                        return err;
-               /* unmute all codecs - needed! */
-               snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE, VT1724_REVO_MUTE);
+               err = revo51_i2c_init(ice, &ptc_revo51_volume);
+               if (err < 0)
+                       return err;
+               /* unmute all codecs */
+               snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
+                                           VT1724_REVO_MUTE);
                break;
        }
 
@@ -264,10 +368,18 @@ static int __devinit revo_add_controls(struct snd_ice1712 *ice)
 
        switch (ice->eeprom.subvendor) {
        case VT1724_SUBDEVICE_REVOLUTION71:
+               err = snd_ice1712_akm4xxx_build_controls(ice);
+               if (err < 0)
+                       return err;
+               break;
        case VT1724_SUBDEVICE_REVOLUTION51:
                err = snd_ice1712_akm4xxx_build_controls(ice);
                if (err < 0)
                        return err;
+               err = snd_pt2258_build_controls(ice->spec.revo51.pt2258);
+               if (err < 0)
+                       return err;
+               break;
        }
        return 0;
 }
index efbb86ec3289ed0286549316635ecd9cc79b225b..c70adaf017c1ccb8569316d70d7c5a2cf0fa0b81 100644 (file)
@@ -42,9 +42,11 @@ extern struct snd_ice1712_card_info snd_vt1724_revo_cards[];
 #define VT1724_REVO_CCLK       0x02
 #define VT1724_REVO_CDIN       0x04    /* not used */
 #define VT1724_REVO_CDOUT      0x08
-#define VT1724_REVO_CS0                0x10    /* AK5365 chipselect for Rev. 5.1 */
+#define VT1724_REVO_CS0                0x10    /* AK5365 chipselect for (revo51) */
 #define VT1724_REVO_CS1                0x20    /* front AKM4381 chipselect */
-#define VT1724_REVO_CS2                0x40    /* surround AKM4355 chipselect */
+#define VT1724_REVO_CS2                0x40    /* surround AKM4355 CS (revo71) */
+#define VT1724_REVO_I2C_DATA    0x40    /* I2C: PT 2258 SDA (on revo51) */
+#define VT1724_REVO_I2C_CLOCK   0x80    /* I2C: PT 2258 SCL (on revo51) */
 #define VT1724_REVO_MUTE       (1<<22) /* 0 = all mute, 1 = normal operation */
 
 #endif /* __SOUND_REVO_H */