Commit feaa6a74 authored by Jochen Voss's avatar Jochen Voss Committed by Jaroslav Kysela

[ALSA] Enable the analog loopback of the Revolution 5.1

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: default avatarJochen Voss <voss@seehuhn.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@suse.cz>
parent a58e7cb1
/*
* 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 */
...@@ -16,3 +16,4 @@ obj-$(CONFIG_SND) += other/ ...@@ -16,3 +16,4 @@ obj-$(CONFIG_SND) += other/
# Toplevel Module Dependency # Toplevel Module Dependency
obj-$(CONFIG_SND_INTERWAVE_STB) += snd-tea6330t.o snd-i2c.o obj-$(CONFIG_SND_INTERWAVE_STB) += snd-tea6330t.o snd-i2c.o
obj-$(CONFIG_SND_ICE1712) += snd-cs8427.o snd-i2c.o obj-$(CONFIG_SND_ICE1712) += snd-cs8427.o snd-i2c.o
obj-$(CONFIG_SND_ICE1724) += snd-i2c.o
...@@ -6,11 +6,11 @@ ...@@ -6,11 +6,11 @@
snd-ak4114-objs := ak4114.o snd-ak4114-objs := ak4114.o
snd-ak4117-objs := ak4117.o snd-ak4117-objs := ak4117.o
snd-ak4xxx-adda-objs := ak4xxx-adda.o snd-ak4xxx-adda-objs := ak4xxx-adda.o
snd-pt2258-objs := pt2258.o
snd-tea575x-tuner-objs := tea575x-tuner.o snd-tea575x-tuner-objs := tea575x-tuner.o
# Module Dependency # Module Dependency
obj-$(CONFIG_SND_PDAUDIOCF) += snd-ak4117.o obj-$(CONFIG_SND_PDAUDIOCF) += snd-ak4117.o
obj-$(CONFIG_SND_ICE1712) += snd-ak4xxx-adda.o obj-$(CONFIG_SND_ICE1712) += snd-ak4xxx-adda.o
obj-$(CONFIG_SND_ICE1724) += snd-ak4xxx-adda.o obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o snd-ak4xxx-adda.o snd-pt2258.o
obj-$(CONFIG_SND_ICE1724) += snd-ak4114.o
obj-$(CONFIG_SND_FM801_TEA575X) += snd-tea575x-tuner.o obj-$(CONFIG_SND_FM801_TEA575X) += snd-tea575x-tuner.o
/*
* 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);
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <sound/i2c.h> #include <sound/i2c.h>
#include <sound/ak4xxx-adda.h> #include <sound/ak4xxx-adda.h>
#include <sound/ak4114.h> #include <sound/ak4114.h>
#include <sound/pt2258.h>
#include <sound/pcm.h> #include <sound/pcm.h>
#include <sound/mpu401.h> #include <sound/mpu401.h>
...@@ -381,6 +382,11 @@ struct snd_ice1712 { ...@@ -381,6 +382,11 @@ struct snd_ice1712 {
unsigned short master[2]; unsigned short master[2];
unsigned short vol[8]; unsigned short vol[8];
} phase28; } phase28;
/* a non-standard I2C device for revo51 */
struct revo51_spec {
struct snd_i2c_device *dev;
struct snd_pt2258 *pt2258;
} revo51;
/* Hoontech-specific setting */ /* Hoontech-specific setting */
struct hoontech_spec { struct hoontech_spec {
unsigned char boxbits[4]; unsigned char boxbits[4];
...@@ -462,6 +468,14 @@ static inline void snd_ice1712_gpio_write_bits(struct snd_ice1712 *ice, ...@@ -462,6 +468,14 @@ static inline void snd_ice1712_gpio_write_bits(struct snd_ice1712 *ice,
snd_ice1712_gpio_write(ice, mask & bits); 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_spdif_build_controls(struct snd_ice1712 *ice);
int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *template, int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *template,
......
...@@ -83,6 +83,102 @@ static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate) ...@@ -83,6 +83,102 @@ static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
snd_akm4xxx_reset(ak, 0); 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 * initialize the chips on M-Audio Revolution cards
*/ */
...@@ -180,9 +276,9 @@ static struct snd_ak4xxx_private akm_revo51_priv __devinitdata = { ...@@ -180,9 +276,9 @@ static struct snd_ak4xxx_private akm_revo51_priv __devinitdata = {
.cif = 0, .cif = 0,
.data_mask = VT1724_REVO_CDOUT, .data_mask = VT1724_REVO_CDOUT,
.clk_mask = VT1724_REVO_CCLK, .clk_mask = VT1724_REVO_CCLK,
.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
.cs_addr = VT1724_REVO_CS1 | VT1724_REVO_CS2, .cs_addr = VT1724_REVO_CS1,
.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
.add_flags = VT1724_REVO_CCLK, /* high at init */ .add_flags = VT1724_REVO_CCLK, /* high at init */
.mask_flags = 0, .mask_flags = 0,
}; };
...@@ -198,13 +294,15 @@ static struct snd_ak4xxx_private akm_revo51_adc_priv __devinitdata = { ...@@ -198,13 +294,15 @@ static struct snd_ak4xxx_private akm_revo51_adc_priv __devinitdata = {
.cif = 0, .cif = 0,
.data_mask = VT1724_REVO_CDOUT, .data_mask = VT1724_REVO_CDOUT,
.clk_mask = VT1724_REVO_CCLK, .clk_mask = VT1724_REVO_CCLK,
.cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
.cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS2, .cs_addr = VT1724_REVO_CS0,
.cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2, .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
.add_flags = VT1724_REVO_CCLK, /* high at init */ .add_flags = VT1724_REVO_CCLK, /* high at init */
.mask_flags = 0, .mask_flags = 0,
}; };
static struct snd_pt2258 ptc_revo51_volume;
static int __devinit revo_init(struct snd_ice1712 *ice) static int __devinit revo_init(struct snd_ice1712 *ice)
{ {
struct snd_akm4xxx *ak; struct snd_akm4xxx *ak;
...@@ -243,14 +341,20 @@ static int __devinit revo_init(struct snd_ice1712 *ice) ...@@ -243,14 +341,20 @@ static int __devinit revo_init(struct snd_ice1712 *ice)
break; break;
case VT1724_SUBDEVICE_REVOLUTION51: case VT1724_SUBDEVICE_REVOLUTION51:
ice->akm_codecs = 2; 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; 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); &akm_revo51_adc_priv, ice);
if (err < 0) if (err < 0)
return err; return err;
/* unmute all codecs - needed! */ err = revo51_i2c_init(ice, &ptc_revo51_volume);
snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE, VT1724_REVO_MUTE); if (err < 0)
return err;
/* unmute all codecs */
snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
VT1724_REVO_MUTE);
break; break;
} }
...@@ -264,10 +368,18 @@ static int __devinit revo_add_controls(struct snd_ice1712 *ice) ...@@ -264,10 +368,18 @@ static int __devinit revo_add_controls(struct snd_ice1712 *ice)
switch (ice->eeprom.subvendor) { switch (ice->eeprom.subvendor) {
case VT1724_SUBDEVICE_REVOLUTION71: case VT1724_SUBDEVICE_REVOLUTION71:
err = snd_ice1712_akm4xxx_build_controls(ice);
if (err < 0)
return err;
break;
case VT1724_SUBDEVICE_REVOLUTION51: case VT1724_SUBDEVICE_REVOLUTION51:
err = snd_ice1712_akm4xxx_build_controls(ice); err = snd_ice1712_akm4xxx_build_controls(ice);
if (err < 0) if (err < 0)
return err; return err;
err = snd_pt2258_build_controls(ice->spec.revo51.pt2258);
if (err < 0)
return err;
break;
} }
return 0; return 0;
} }
......
...@@ -42,9 +42,11 @@ extern struct snd_ice1712_card_info snd_vt1724_revo_cards[]; ...@@ -42,9 +42,11 @@ extern struct snd_ice1712_card_info snd_vt1724_revo_cards[];
#define VT1724_REVO_CCLK 0x02 #define VT1724_REVO_CCLK 0x02
#define VT1724_REVO_CDIN 0x04 /* not used */ #define VT1724_REVO_CDIN 0x04 /* not used */
#define VT1724_REVO_CDOUT 0x08 #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_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 */ #define VT1724_REVO_MUTE (1<<22) /* 0 = all mute, 1 = normal operation */
#endif /* __SOUND_REVO_H */ #endif /* __SOUND_REVO_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment