[alsa-devel] [BAD PATCH] ice1712: Add support for Philips PSC724 Ultimate Edge
Hello, I'm trying do implement support for Philips PSC724 Ultimate Edge card, which is based on VT1722 + WM8776 + WM8766 chips. I've examined the connections and commented them in the code. The patch is heavily based on se.c.
When trying to play a sound, the player runs but no sound comes from the output, no matter what I do with alsamixer.
The interesting thing is that recording works - I can record something from e.g. line-in and play it on another machine. Recording source selection in alsamixer works too (if line-in is deselected, only silence is recorded). So WM8776 I2C control works.
I'm missing something but don't know what. Any ideas?
diff --git a/sound/pci/ice1712/Makefile b/sound/pci/ice1712/Makefile index f7ce33f..6d6ae06 100644 --- a/sound/pci/ice1712/Makefile +++ b/sound/pci/ice1712/Makefile @@ -5,7 +5,7 @@
snd-ice17xx-ak4xxx-objs := ak4xxx.o snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o -snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o +snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o psc724.o
# Toplevel Module Dependency obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 9236297..d19db09 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -54,6 +54,7 @@ #include "wtm.h" #include "se.h" #include "quartet.h" +#include "psc724.h"
MODULE_AUTHOR("Jaroslav Kysela perex@perex.cz"); MODULE_DESCRIPTION("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)"); @@ -2234,6 +2235,7 @@ static struct snd_ice1712_card_info *card_tables[] __devinitdata = { snd_vt1724_se_cards, snd_vt1724_qtet_cards, snd_vt1724_ooaoo_cards, + snd_vt1724_psc724_cards, NULL, };
@@ -2348,7 +2350,7 @@ static int __devinit snd_vt1724_read_eeprom(struct snd_ice1712 *ice, return -EIO; } ice->eeprom.version = snd_vt1724_read_i2c(ice, dev, 0x05); - if (ice->eeprom.version != 2) + if (ice->eeprom.version != 1 && ice->eeprom.version != 2) printk(KERN_WARNING "ice1724: Invalid EEPROM version %i\n", ice->eeprom.version); size = ice->eeprom.size - 6; diff --git a/sound/pci/ice1712/psc724.c b/sound/pci/ice1712/psc724.c new file mode 100644 index 0000000..ac9511b --- /dev/null +++ b/sound/pci/ice1712/psc724.c @@ -0,0 +1,664 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Philips PSC724 Ultimate Edge + * + * Copyright (c) 2012 Ondrej Zary linux@rainbow-software.org + * + * 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 <asm/io.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/tlv.h> + +#include "ice1712.h" +#include "envy24ht.h" +#include "psc724.h" + +struct psc724_spec { + struct { + unsigned char ch1, ch2; + } vol[6]; +}; + +/****************************************************************************/ +/* PHILIPS PSC724 ULTIMATE EDGE */ +/****************************************************************************/ +/* + * system configuration ICE_EEP2_SYSCONF=0x42 + * XIN1 49.152MHz + * no MPU401 + * one stereo ADC, no S/PDIF receiver + * three stereo DACs (FRONT, REAR, CENTER+LFE) + * + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * use I2S, not AC97 + * + * I2S converters feature ICE_EEP2_I2S=0x30 + * I2S codec has no volume/mute control feature + * I2S codec does not support 96KHz or 192KHz(bug!) + * I2S codec 24bits + * + * S/PDIF configuration ICE_EEP2_SPDIF=0xc1 + * Enable integrated S/PDIF transmitter + * internal S/PDIF out implemented + * No S/PDIF input + * External S/PDIF out implemented + * + * + * ** connected chips ** + * + * WM8776 + * 2-channel DAC used for main output and stereo ADC (with 10-channel MUX) + * AIN1: LINE IN, AIN2: CD/VIDEO, AIN3: AUX, AIN4: Front MIC, AIN5: Rear MIC + * Controlled by I2C using VT1722 I2C interface: + * MODE (pin16) -- GND + * CE (pin17) -- GND I2C mode (address=0x34) + * DI (pin18) -- SDA (VT1722 pin70) + * CL (pin19) -- SCLK (VT1722 pin71) + * + * WM8766 + * 6-channel DAC used for rear & center/LFE outputs (only 4 channels used) + * Controlled by SPI using VT1722 GPIO pins: + * MODE (pin 1) -- GPIO19 (VT1722 pin99) + * ML/I2S (pin11) -- GPIO18 (VT1722 pin98) + * MC/IWL (pin12) -- GPIO17 (VT1722 pin97) + * MD/DM (pin13) -- GPIO16 (VT1722 pin96) + * MUTE (pin14) -- GPIO20 (VT1722 pin101) + * + * ** output pins and device names ** + * + * 7.1ch name -- output connector color -- device (-D option) + * + * FRONT 2ch -- green -- plughw:0,0 + * REAR 2ch -- black -- plughw:0,2,0 + * CENTER(Lch) SUBWOOFER(Rch) -- orange -- plughw:0,2,1 + * + */ + + +/****************************************************************************/ +/* WM8766 interface */ +/****************************************************************************/ + +static void psc724_WM8766_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int st; + unsigned int bits; + int i; + const unsigned int DATA = 0x010000; + const unsigned int CLOCK = 0x020000; + const unsigned int LOAD = 0x040000; + const unsigned int ALL_MASK = (DATA | CLOCK | LOAD); + + snd_ice1712_save_gpio_status(ice); + + st = ((addr & 0x7f) << 9) | (data & 0x1ff); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | ALL_MASK); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~ALL_MASK); + bits = snd_ice1712_gpio_read(ice) & ~ALL_MASK; + + snd_ice1712_gpio_write(ice, bits); + for (i = 0; i < 16; i++) { + udelay(1); + bits &= ~CLOCK; + st = (st << 1); + if (st & 0x10000) + bits |= DATA; + else + bits &= ~DATA; + + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= CLOCK; + snd_ice1712_gpio_write(ice, bits); + } + + udelay(1); + bits |= LOAD; + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= (DATA | CLOCK); + snd_ice1712_gpio_write(ice, bits); + + snd_ice1712_restore_gpio_status(ice); +} + +static void psc724_WM8766_set_volume(struct snd_ice1712 *ice, int ch, + unsigned int vol1, unsigned int vol2) +{ + switch (ch) { + case 0: + psc724_WM8766_write(ice, 0x000, vol1); + psc724_WM8766_write(ice, 0x001, vol2 | 0x100); + break; + case 1: + psc724_WM8766_write(ice, 0x004, vol1); + psc724_WM8766_write(ice, 0x005, vol2 | 0x100); + break; + case 2: + psc724_WM8766_write(ice, 0x006, vol1); + psc724_WM8766_write(ice, 0x007, vol2 | 0x100); + break; + } +} + +static void __devinit psc724_WM8766_init(struct snd_ice1712 *ice) +{ + psc724_WM8766_write(ice, 0x1f, 0x000); /* RESET ALL */ + udelay(10); + + psc724_WM8766_set_volume(ice, 0, 0, 0); /* volume L=0 R=0 */ + psc724_WM8766_set_volume(ice, 1, 0, 0); /* volume L=0 R=0 */ + psc724_WM8766_set_volume(ice, 2, 0, 0); /* volume L=0 R=0 */ + + psc724_WM8766_write(ice, 0x03, 0x022); /* serial mode I2S-24bits */ + psc724_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ + psc724_WM8766_write(ice, 0x12, 0x000); /* MDP=0 */ + psc724_WM8766_write(ice, 0x15, 0x000); /* MDP=0 */ + psc724_WM8766_write(ice, 0x09, 0x000); /* demp=off mute=off */ + + psc724_WM8766_write(ice, 0x02, 0x124); /* ch-assign L=L R=R RESET */ + psc724_WM8766_write(ice, 0x02, 0x120); /* ch-assign L=L R=R */ +} + +static void psc724_WM8766_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + if (rate > 96000) + psc724_WM8766_write(ice, 0x0a, 0x000); /* MCLK=128fs */ + else + psc724_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ +} + + +/****************************************************************************/ +/* WM8776 interface */ +/****************************************************************************/ + +static void psc724_WM8776_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int val; + + val = (addr << 9) | data; + printk("write_i2c 0x%x 0x%x\n", addr, data); + snd_vt1724_write_i2c(ice, 0x34, val >> 8, val & 0xff); +} + + +static void psc724_WM8776_set_output_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + printk("%s %d,%d\n", __FUNCTION__, vol1, vol2); + psc724_WM8776_write(ice, 0x03, vol1); + psc724_WM8776_write(ice, 0x04, vol2 | 0x100); +} + +static void psc724_WM8776_set_input_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + psc724_WM8776_write(ice, 0x0e, vol1); + psc724_WM8776_write(ice, 0x0f, vol2 | 0x100); +} + +static const char *psc724_sel[] = { + "LINE-IN", "CD-IN", "AUX-IN", "FRONT-MIC-IN", "REAR-MIC-IN", "ALL-MIX", NULL +}; + +static void psc724_WM8776_set_input_selector(struct snd_ice1712 *ice, + unsigned int sel) +{ + static unsigned char vals[] = { + /* LINE, CD, AUX, FRONT-MIC, REAR-MIC, ALL, nothing */ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00 + }; + if (sel > 6) + sel = 6; + psc724_WM8776_write(ice, 0x15, vals[sel]); +} + +static void psc724_WM8776_set_afl(struct snd_ice1712 *ice, unsigned int afl) +{ + /* AFL -- After Fader Listening */ + if (afl) + psc724_WM8776_write(ice, 0x16, 0x005); + else + psc724_WM8776_write(ice, 0x16, 0x001); +} + +static const char *psc724_agc[] = { + "Off", "LimiterMode", "ALCMode", NULL +}; + +static void psc724_WM8776_set_agc(struct snd_ice1712 *ice, unsigned int agc) +{ + /* AGC -- Auto Gain Control of the input */ + switch (agc) { + case 0: + psc724_WM8776_write(ice, 0x11, 0x000); /* Off */ + break; + case 1: + psc724_WM8776_write(ice, 0x10, 0x07b); + psc724_WM8776_write(ice, 0x11, 0x100); /* LimiterMode */ + break; + case 2: + psc724_WM8776_write(ice, 0x10, 0x1fb); + psc724_WM8776_write(ice, 0x11, 0x100); /* ALCMode */ + break; + } +} + +static void __devinit psc724_WM8776_init(struct snd_ice1712 *ice) +{ + int i; + static unsigned short __devinitdata default_values[] = { + 0x100, 0x100, 0x100, + 0x100, 0x100, 0x100, + 0x000, 0x090, 0x000, 0x000, + 0x022, 0x022, 0x022, + 0x008, 0x0cf, 0x0cf, 0x07b, 0x000, + 0x032, 0x000, 0x0a6, 0x001, 0x001 + }; + + psc724_WM8776_write(ice, 0x17, 0x000); /* reset all */ + /* ADC and DAC interface is I2S 24bits mode */ + /* The sample-rate are automatically changed */ + udelay(10); + /* BUT my board can not do reset all, so I load all by manually. */ + for (i = 0; i < ARRAY_SIZE(default_values); i++) + psc724_WM8776_write(ice, i, default_values[i]); + + psc724_WM8776_set_input_selector(ice, 0); + psc724_WM8776_set_afl(ice, 0); + psc724_WM8776_set_agc(ice, 0); + psc724_WM8776_set_input_volume(ice, 0, 0); + psc724_WM8776_set_output_volume(ice, 0, 0); + + /* head phone mute and power down */ +// psc724_WM8776_write(ice, 0x00, 0); +// psc724_WM8776_write(ice, 0x01, 0); +// psc724_WM8776_write(ice, 0x02, 0x100); +// psc724_WM8776_write(ice, 0x0d, 0x080); +} + +static void psc724_WM8776_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* nothing to do */ +} + + +/****************************************************************************/ +/* runtime interface */ +/****************************************************************************/ + +static void psc724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + psc724_WM8766_set_pro_rate(ice, rate); + psc724_WM8776_set_pro_rate(ice, rate); +} + +struct psc724_control { + char *name; + enum { + WM8766, + WM8776in, + WM8776out, + WM8776sel, + WM8776agc, + WM8776afl + } target; + enum { VOLUME1, VOLUME2, BOOLEAN, ENUM } type; + int ch; + const char **member; + const char *comment; +}; + +static const struct psc724_control psc724_cont[] = { + { + .name = "Front Playback Volume", + .target = WM8776out, + .type = VOLUME1, + .comment = "Front(green)" + }, + { + .name = "Surround Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 0, + .comment = "SurroundBack(white)" + }, + { + .name = "CLFE Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 1, + .comment = "Center(Lch)&SubWoofer(Rch)(black)" + }, + { + .name = "Capture Volume", + .target = WM8776in, + .type = VOLUME2 + }, + { + .name = "Capture Select", + .target = WM8776sel, + .type = ENUM, + .member = psc724_sel + }, + { + .name = "AGC Capture Mode", + .target = WM8776agc, + .type = ENUM, + .member = psc724_agc + }, + { + .name = "AFL Bypass Playback Switch", + .target = WM8776afl, + .type = BOOLEAN + } +}; + +static int psc724_get_enum_count(int n) +{ + const char **member; + int c; + + member = psc724_cont[n].member; + if (!member) + return 0; + for (c = 0; member[c]; c++) + ; + return c; +} + +static int psc724_cont_volume_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; /* mute */ + uinfo->value.integer.max = 0xff; /* 0dB */ + return 0; +} + +#define psc724_cont_boolean_info snd_ctl_boolean_mono_info + +static int psc724_cont_enum_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + int n, c; + + n = kc->private_value; + c = psc724_get_enum_count(n); + if (!c) + return -EINVAL; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = c; + if (uinfo->value.enumerated.item >= c) + uinfo->value.enumerated.item = c - 1; + strcpy(uinfo->value.enumerated.name, + psc724_cont[n].member[uinfo->value.enumerated.item]); + return 0; +} + +static int psc724_cont_volume_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + uc->value.integer.value[1] = spec->vol[n].ch2; + return 0; +} + +static int psc724_cont_boolean_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + return 0; +} + +static int psc724_cont_enum_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.enumerated.item[0] = spec->vol[n].ch1; + return 0; +} + +static void psc724_cont_update(struct snd_ice1712 *ice, int n) +{ + struct psc724_spec *spec = ice->spec; + switch (psc724_cont[n].target) { + case WM8766: + psc724_WM8766_set_volume(ice, + psc724_cont[n].ch, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776in: + psc724_WM8776_set_input_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776out: + psc724_WM8776_set_output_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776sel: + psc724_WM8776_set_input_selector(ice, + spec->vol[n].ch1); + break; + + case WM8776agc: + psc724_WM8776_set_agc(ice, spec->vol[n].ch1); + break; + + case WM8776afl: + psc724_WM8776_set_afl(ice, spec->vol[n].ch1); + break; + + default: + break; + } +} + +static int psc724_cont_volume_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1, vol2; + int changed; + + changed = 0; + vol1 = uc->value.integer.value[0] & 0xff; + vol2 = uc->value.integer.value[1] & 0xff; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + changed = 1; + } + if (spec->vol[n].ch2 != vol2) { + spec->vol[n].ch2 = vol2; + changed = 1; + } + if (changed) + psc724_cont_update(ice, n); + + return changed; +} + +static int psc724_cont_boolean_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = !!uc->value.integer.value[0]; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + psc724_cont_update(ice, n); + return 1; + } + return 0; +} + +static int psc724_cont_enum_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = uc->value.enumerated.item[0]; + if (vol1 >= psc724_get_enum_count(n)) + return -EINVAL; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + psc724_cont_update(ice, n); + return 1; + } + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_gain1, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_gain2, -10350, 50, 1); + +static int __devinit psc724_add_controls(struct snd_ice1712 *ice) +{ + int i; + struct snd_kcontrol_new cont; + int err; + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + for (i = 0; i < ARRAY_SIZE(psc724_cont); i++) { + cont.private_value = i; + cont.name = psc724_cont[i].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + cont.tlv.p = NULL; + switch (psc724_cont[i].type) { + case VOLUME1: + case VOLUME2: + cont.info = psc724_cont_volume_info; + cont.get = psc724_cont_volume_get; + cont.put = psc724_cont_volume_put; + cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + if (psc724_cont[i].type == VOLUME1) + cont.tlv.p = db_scale_gain1; + else + cont.tlv.p = db_scale_gain2; + break; + case BOOLEAN: + cont.info = psc724_cont_boolean_info; + cont.get = psc724_cont_boolean_get; + cont.put = psc724_cont_boolean_put; + break; + case ENUM: + cont.info = psc724_cont_enum_info; + cont.get = psc724_cont_enum_get; + cont.put = psc724_cont_enum_put; + break; + default: + snd_BUG(); + return -EINVAL; + } + err = snd_ctl_add(ice->card, snd_ctl_new1(&cont, ice)); + if (err < 0) + return err; + } + + return 0; +} + +/****************************************************************************/ +/* probe/initialize/setup */ +/****************************************************************************/ + +static int __devinit psc724_init(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + psc724_WM8766_init(ice); + psc724_WM8776_init(ice); + ice->gpio.set_pro_rate = psc724_set_pro_rate; + return 0; +} + +/* PSC724 has buggy EEPROM, so override it here */ +static unsigned char psc724_eeprom[] __devinitdata = { + [ICE_EEP2_SYSCONF] = 0x42, /* 49.152MHz, ADC, 3DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0x78, /* 96k-ok, 24bit, 192k-ok */ + [ICE_EEP2_SPDIF] = 0xc1, /* out-en, out-int, no spdif-in */ + + [ICE_EEP2_GPIO_DIR] = 0x00, /* not used */ + [ICE_EEP2_GPIO_DIR1] = 0x00, /* not used */ + [ICE_EEP2_GPIO_DIR2] = 0x1f, /* WM8766 MUTE/MODE/ML/MC/MD 1=output */ + + [ICE_EEP2_GPIO_MASK] = 0x00, /* 0=writable */ + [ICE_EEP2_GPIO_MASK1] = 0x00, /* 0=writable */ + [ICE_EEP2_GPIO_MASK2] = 0x00, /* 0=writable */ + + [ICE_EEP2_GPIO_STATE] = 0x00, /* not used */ + [ICE_EEP2_GPIO_STATE1] = 0x00, /* not used */ + [ICE_EEP2_GPIO_STATE2] = 0x07, /* WM8766 ML/MC/MD */ +}; + +struct snd_ice1712_card_info snd_vt1724_psc724_cards[] __devinitdata = { + { + .subvendor = VT1724_SUBDEVICE_PSC724, + .name = "Philips PSC724 Ultimate Edge", + .model = "psc724", + .chip_init = psc724_init, + .build_controls = psc724_add_controls, + .eeprom_size = sizeof(psc724_eeprom), + .eeprom_data = psc724_eeprom, + }, + {} /*terminator*/ +}; diff --git a/sound/pci/ice1712/psc724.h b/sound/pci/ice1712/psc724.h new file mode 100644 index 0000000..858e5fd --- /dev/null +++ b/sound/pci/ice1712/psc724.h @@ -0,0 +1,13 @@ +#ifndef __SOUND_PSC724_H +#define __SOUND_PSC724_H + +/* ID */ +#define PSC724_DEVICE_DESC \ + "{Philips,PSC724 Ultimate Edge}," + +#define VT1724_SUBDEVICE_PSC724 0xab170619 + +/* entry struct */ +extern struct snd_ice1712_card_info snd_vt1724_psc724_cards[]; + +#endif /* __SOUND_PSC724_H */
Dne 13.3.2012 21:59, Ondrej Zary napsal(a):
Hello, I'm trying do implement support for Philips PSC724 Ultimate Edge card, which is based on VT1722 + WM8776 + WM8766 chips. I've examined the connections and commented them in the code. The patch is heavily based on se.c.
When trying to play a sound, the player runs but no sound comes from the output, no matter what I do with alsamixer.
The interesting thing is that recording works - I can record something from e.g. line-in and play it on another machine. Recording source selection in alsamixer works too (if line-in is deselected, only silence is recorded). So WM8776 I2C control works.
I'm missing something but don't know what. Any ideas?
I always found that a simple proc interface listing all register values of the chips onboard greatly helped with troubleshooting. E.g. http://git.alsa-project.org/?p=alsa-kmirror.git;a=blob;f=i2c/other/ak4xxx-ad...
http://git.alsa-project.org/?p=alsa-kmirror.git;a=blob;f=i2c/other/ak4114.c;...
Regards,
Pavel.
On Tue, Mar 13, 2012 at 10:25:54PM +0100, Pavel Hofman wrote:
I always found that a simple proc interface listing all register values of the chips onboard greatly helped with troubleshooting. E.g. http://git.alsa-project.org/?p=alsa-kmirror.git;a=blob;f=i2c/other/ak4xxx-ad...
http://git.alsa-project.org/?p=alsa-kmirror.git;a=blob;f=i2c/other/ak4114.c;...
If you use regmap for your register I/O you get this for free (plus some other infrastructure stuff).
Dne 13.3.2012 22:33, Mark Brown napsal(a):
On Tue, Mar 13, 2012 at 10:25:54PM +0100, Pavel Hofman wrote:
I always found that a simple proc interface listing all register values of the chips onboard greatly helped with troubleshooting. E.g. http://git.alsa-project.org/?p=alsa-kmirror.git;a=blob;f=i2c/other/ak4xxx-ad...
http://git.alsa-project.org/?p=alsa-kmirror.git;a=blob;f=i2c/other/ak4114.c;...
If you use regmap for your register I/O you get this for free (plus some other infrastructure stuff).
Mark, thanks for the information. I searched sound-2.6 tree and found the only reference to linux/regmap.h in soc-io.c. Please could you point me to a place where it is used in alsa source code so that I could take it as an example? I have no problem with using the latest kernel infrastructure helpers but there are just too many of them.
Thanks a lot,
Pavel.
On Wed, Mar 14, 2012 at 09:07:57AM +0100, Pavel Hofman wrote:
Mark, thanks for the information. I searched sound-2.6 tree and found the only reference to linux/regmap.h in soc-io.c. Please could you point me to a place where it is used in alsa source code so that I could take it as an example? I have no problem with using the latest kernel infrastructure helpers but there are just too many of them.
You've already found an example, what problems are you having with that example? There's also other examples in the kernel, especially in more recent kernels.
On Tuesday 13 March 2012 21:59:16 Ondrej Zary wrote:
Hello, I'm trying do implement support for Philips PSC724 Ultimate Edge card, which is based on VT1722 + WM8776 + WM8766 chips. I've examined the connections and commented them in the code. The patch is heavily based on se.c.
When trying to play a sound, the player runs but no sound comes from the output, no matter what I do with alsamixer.
The interesting thing is that recording works - I can record something from e.g. line-in and play it on another machine. Recording source selection in alsamixer works too (if line-in is deselected, only silence is recorded). So WM8776 I2C control works.
I'm missing something but don't know what. Any ideas?
I've missed a mute-all circuit (which grounds all outputs) controlled by GPIO22. Now the driver works - front, rear and center/lfe outputs. TODO: - support headphone output - move WM8776 and WM8766 code out
diff --git a/sound/pci/ice1712/Makefile b/sound/pci/ice1712/Makefile index f7ce33f..6d6ae06 100644 --- a/sound/pci/ice1712/Makefile +++ b/sound/pci/ice1712/Makefile @@ -5,7 +5,7 @@
snd-ice17xx-ak4xxx-objs := ak4xxx.o snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o -snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o +snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o psc724.o
# Toplevel Module Dependency obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 9236297..d19db09 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -54,6 +54,7 @@ #include "wtm.h" #include "se.h" #include "quartet.h" +#include "psc724.h"
MODULE_AUTHOR("Jaroslav Kysela perex@perex.cz"); MODULE_DESCRIPTION("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)"); @@ -2234,6 +2235,7 @@ static struct snd_ice1712_card_info *card_tables[] __devinitdata = { snd_vt1724_se_cards, snd_vt1724_qtet_cards, snd_vt1724_ooaoo_cards, + snd_vt1724_psc724_cards, NULL, };
@@ -2348,7 +2350,7 @@ static int __devinit snd_vt1724_read_eeprom(struct snd_ice1712 *ice, return -EIO; } ice->eeprom.version = snd_vt1724_read_i2c(ice, dev, 0x05); - if (ice->eeprom.version != 2) + if (ice->eeprom.version != 1 && ice->eeprom.version != 2) printk(KERN_WARNING "ice1724: Invalid EEPROM version %i\n", ice->eeprom.version); size = ice->eeprom.size - 6; diff --git a/sound/pci/ice1712/psc724.c b/sound/pci/ice1712/psc724.c new file mode 100644 index 0000000..17376d4 --- /dev/null +++ b/sound/pci/ice1712/psc724.c @@ -0,0 +1,688 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Philips PSC724 Ultimate Edge + * + * Copyright (c) 2012 Ondrej Zary linux@rainbow-software.org + * + * 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 <asm/io.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/tlv.h> + +#include "ice1712.h" +#include "envy24ht.h" +#include "psc724.h" + +struct psc724_spec { + struct { + unsigned char ch1, ch2; + } vol[6]; +}; + +/****************************************************************************/ +/* PHILIPS PSC724 ULTIMATE EDGE */ +/****************************************************************************/ +/* + * VT1722 (Envy24GT) - 6 outputs, 4 inputs (only 2 used), 24-bit/96kHz + * + * system configuration ICE_EEP2_SYSCONF=0x42 + * XIN1 49.152MHz + * no MPU401 + * one stereo ADC, no S/PDIF receiver + * three stereo DACs (FRONT, REAR, CENTER+LFE) + * + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * use I2S, not AC97 + * + * I2S converters feature ICE_EEP2_I2S=0x30 + * I2S codec has no volume/mute control feature + * I2S codec does not support 96KHz or 192KHz(bug!) + * I2S codec 24bits + * + * S/PDIF configuration ICE_EEP2_SPDIF=0xc1 + * Enable integrated S/PDIF transmitter + * internal S/PDIF out implemented + * No S/PDIF input + * External S/PDIF out implemented + * + * + * ** connected chips ** + * + * WM8776 + * 2-channel DAC used for main output and stereo ADC (with 10-channel MUX) + * AIN1: LINE IN, AIN2: CD/VIDEO, AIN3: AUX, AIN4: Front MIC, AIN5: Rear MIC + * Controlled by I2C using VT1722 I2C interface: + * MODE (pin16) -- GND + * CE (pin17) -- GND I2C mode (address=0x34) + * DI (pin18) -- SDA (VT1722 pin70) + * CL (pin19) -- SCLK (VT1722 pin71) + * + * WM8766 + * 6-channel DAC used for rear & center/LFE outputs (only 4 channels used) + * Controlled by SPI using VT1722 GPIO pins: + * MODE (pin 1) -- GPIO19 (VT1722 pin99) + * ML/I2S (pin11) -- GPIO18 (VT1722 pin98) + * MC/IWL (pin12) -- GPIO17 (VT1722 pin97) + * MD/DM (pin13) -- GPIO16 (VT1722 pin96) + * MUTE (pin14) -- GPIO20 (VT1722 pin101) + * + * GPIO22 is used as MUTE ALL output, grounding all 6 channels + * + * ** output pins and device names ** + * + * 7.1ch name -- output connector color -- device (-D option) + * + * FRONT 2ch -- green -- plughw:0,0 + * CENTER(Lch) SUBWOOFER(Rch) -- orange -- plughw:0,2,0 + * REAR 2ch -- black -- plughw:0,2,1 + * + */ + +#define GPIO_MUTE_SUR (1 << 20) +#define GPIO_MUTE_ALL (1 << 22) + +/****************************************************************************/ +/* WM8766 interface */ +/****************************************************************************/ + +static void psc724_WM8766_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int st; + unsigned int bits; + int i; + const unsigned int DATA = 0x010000; + const unsigned int CLOCK = 0x020000; + const unsigned int LOAD = 0x040000; + const unsigned int ALL_MASK = (DATA | CLOCK | LOAD); + + snd_ice1712_save_gpio_status(ice); + + st = ((addr & 0x7f) << 9) | (data & 0x1ff); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | ALL_MASK); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~ALL_MASK); + bits = snd_ice1712_gpio_read(ice) & ~ALL_MASK; + + snd_ice1712_gpio_write(ice, bits); + for (i = 0; i < 16; i++) { + udelay(1); + bits &= ~CLOCK; + st = (st << 1); + if (st & 0x10000) + bits |= DATA; + else + bits &= ~DATA; + + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= CLOCK; + snd_ice1712_gpio_write(ice, bits); + } + + udelay(1); + bits |= LOAD; + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= (DATA | CLOCK); + snd_ice1712_gpio_write(ice, bits); + + snd_ice1712_restore_gpio_status(ice); +} + +static void psc724_WM8766_set_volume(struct snd_ice1712 *ice, int ch, + unsigned int vol1, unsigned int vol2) +{ + switch (ch) { + case 0: + psc724_WM8766_write(ice, 0x000, vol1); + psc724_WM8766_write(ice, 0x001, vol2 | 0x100); + break; + case 1: + psc724_WM8766_write(ice, 0x004, vol1); + psc724_WM8766_write(ice, 0x005, vol2 | 0x100); + break; + case 2: + psc724_WM8766_write(ice, 0x006, vol1); + psc724_WM8766_write(ice, 0x007, vol2 | 0x100); + break; + } +} + +static void __devinit psc724_WM8766_init(struct snd_ice1712 *ice) +{ + psc724_WM8766_write(ice, 0x1f, 0x000); /* RESET ALL */ + udelay(10); + + psc724_WM8766_set_volume(ice, 0, 0, 0); /* volume L=0 R=0 */ + psc724_WM8766_set_volume(ice, 1, 0, 0); /* volume L=0 R=0 */ + psc724_WM8766_set_volume(ice, 2, 0, 0); /* volume L=0 R=0 */ + + psc724_WM8766_write(ice, 0x03, 0x022); /* serial mode I2S-24bits */ + psc724_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ + psc724_WM8766_write(ice, 0x12, 0x000); /* MDP=0 */ + psc724_WM8766_write(ice, 0x15, 0x000); /* MDP=0 */ + psc724_WM8766_write(ice, 0x09, 0x000); /* demp=off mute=off */ + + psc724_WM8766_write(ice, 0x02, 0x124); /* ch-assign L=L R=R RESET */ + psc724_WM8766_write(ice, 0x02, 0x120); /* ch-assign L=L R=R */ +} + +static void psc724_WM8766_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + if (rate > 96000) + psc724_WM8766_write(ice, 0x0a, 0x000); /* MCLK=128fs */ + else + psc724_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ +} + + +/****************************************************************************/ +/* WM8776 interface */ +/****************************************************************************/ + +static void psc724_WM8776_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int val; + + val = (addr << 9) | data; + printk("write_i2c 0x%x 0x%x\n", addr, data); + snd_vt1724_write_i2c(ice, 0x34, val >> 8, val & 0xff); +} + + +static void psc724_WM8776_set_output_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + psc724_WM8776_write(ice, 0x03, vol1); + psc724_WM8776_write(ice, 0x04, vol2 | 0x100); +} + +static void psc724_WM8776_set_input_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + psc724_WM8776_write(ice, 0x0e, vol1); + psc724_WM8776_write(ice, 0x0f, vol2 | 0x100); +} + +static const char *psc724_sel[] = { + "LINE-IN", "CD-IN", "AUX-IN", "FRONT-MIC-IN", "REAR-MIC-IN", "ALL-MIX", NULL +}; + +static void psc724_WM8776_set_input_selector(struct snd_ice1712 *ice, + unsigned int sel) +{ + static unsigned char vals[] = { + /* LINE, CD, AUX, FRONT-MIC, REAR-MIC, ALL, nothing */ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00 + }; + if (sel > 6) + sel = 6; + psc724_WM8776_write(ice, 0x15, vals[sel]); +} + +static void psc724_WM8776_set_afl(struct snd_ice1712 *ice, unsigned int afl) +{ + /* AFL -- After Fader Listening */ + if (afl) + psc724_WM8776_write(ice, 0x16, 0x005); + else + psc724_WM8776_write(ice, 0x16, 0x001); +} + +static const char *psc724_agc[] = { + "Off", "LimiterMode", "ALCMode", NULL +}; + +static void psc724_WM8776_set_agc(struct snd_ice1712 *ice, unsigned int agc) +{ + /* AGC -- Auto Gain Control of the input */ + switch (agc) { + case 0: + psc724_WM8776_write(ice, 0x11, 0x000); /* Off */ + break; + case 1: + psc724_WM8776_write(ice, 0x10, 0x07b); + psc724_WM8776_write(ice, 0x11, 0x100); /* LimiterMode */ + break; + case 2: + psc724_WM8776_write(ice, 0x10, 0x1fb); + psc724_WM8776_write(ice, 0x11, 0x100); /* ALCMode */ + break; + } +} + +static void psc724_set_mute_all(struct snd_ice1712 *ice, unsigned int unmute) +{ + unsigned int bits = snd_ice1712_gpio_read(ice); + printk("%s %d\n", __FUNCTION__, unmute); + if (unmute) + snd_ice1712_gpio_write(ice, bits & ~(GPIO_MUTE_ALL | GPIO_MUTE_SUR)); + else + snd_ice1712_gpio_write(ice, bits | GPIO_MUTE_ALL | GPIO_MUTE_SUR); +} + + +static void __devinit psc724_WM8776_init(struct snd_ice1712 *ice) +{ + int i; + static unsigned short __devinitdata default_values[] = { + 0x100, 0x100, 0x100, + 0x100, 0x100, 0x100, + 0x000, 0x090, 0x000, 0x000, + 0x022, 0x022, 0x022, + 0x008, 0x0cf, 0x0cf, 0x07b, 0x000, + 0x032, 0x000, 0x0a6, 0x001, 0x001 + }; + + psc724_WM8776_write(ice, 0x17, 0x000); /* reset all */ + /* ADC and DAC interface is I2S 24bits mode */ + /* The sample-rate are automatically changed */ + udelay(10); + /* BUT my board can not do reset all, so I load all by manually. */ + for (i = 0; i < ARRAY_SIZE(default_values); i++) + psc724_WM8776_write(ice, i, default_values[i]); + + psc724_WM8776_set_input_selector(ice, 0); + psc724_WM8776_set_afl(ice, 0); + psc724_WM8776_set_agc(ice, 0); + psc724_WM8776_set_input_volume(ice, 0, 0); + psc724_WM8776_set_output_volume(ice, 0, 0); + + /* head phone mute and power down */ + psc724_WM8776_write(ice, 0x00, 0); + psc724_WM8776_write(ice, 0x01, 0); + psc724_WM8776_write(ice, 0x02, 0x100); + psc724_WM8776_write(ice, 0x0d, 0x080); +} + +static void psc724_WM8776_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* nothing to do */ +} + + +/****************************************************************************/ +/* runtime interface */ +/****************************************************************************/ + +static void psc724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + psc724_WM8766_set_pro_rate(ice, rate); + psc724_WM8776_set_pro_rate(ice, rate); +} + +struct psc724_control { + char *name; + enum { + WM8766, + WM8776in, + WM8776out, + WM8776sel, + WM8776agc, + WM8776afl, + mute_all + } target; + enum { VOLUME1, VOLUME2, BOOLEAN, ENUM } type; + int ch; + const char **member; + const char *comment; +}; + +static const struct psc724_control psc724_cont[] = { + { + .name = "Front Playback Volume", + .target = WM8776out, + .type = VOLUME1, + .comment = "Front(green)" + }, + { + .name = "Surround Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 0, + .comment = "Surround(black)" + }, + { + .name = "CLFE Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 1, + .comment = "Center(Lch)&SubWoofer(Rch)(orange)" + }, + { + .name = "Capture Volume", + .target = WM8776in, + .type = VOLUME2 + }, + { + .name = "Capture Select", + .target = WM8776sel, + .type = ENUM, + .member = psc724_sel + }, + { + .name = "AGC Capture Mode", + .target = WM8776agc, + .type = ENUM, + .member = psc724_agc + }, + { + .name = "AFL Bypass Playback Switch", + .target = WM8776afl, + .type = BOOLEAN + }, + { + .name = "Master Playback Switch", + .target = mute_all, + .type = BOOLEAN + } +}; + +static int psc724_get_enum_count(int n) +{ + const char **member; + int c; + + member = psc724_cont[n].member; + if (!member) + return 0; + for (c = 0; member[c]; c++) + ; + return c; +} + +static int psc724_cont_volume_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; /* mute */ + uinfo->value.integer.max = 0xff; /* 0dB */ + return 0; +} + +#define psc724_cont_boolean_info snd_ctl_boolean_mono_info + +static int psc724_cont_enum_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + int n, c; + + n = kc->private_value; + c = psc724_get_enum_count(n); + if (!c) + return -EINVAL; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = c; + if (uinfo->value.enumerated.item >= c) + uinfo->value.enumerated.item = c - 1; + strcpy(uinfo->value.enumerated.name, + psc724_cont[n].member[uinfo->value.enumerated.item]); + return 0; +} + +static int psc724_cont_volume_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + uc->value.integer.value[1] = spec->vol[n].ch2; + return 0; +} + +static int psc724_cont_boolean_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + return 0; +} + +static int psc724_cont_enum_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.enumerated.item[0] = spec->vol[n].ch1; + return 0; +} + +static void psc724_cont_update(struct snd_ice1712 *ice, int n) +{ + struct psc724_spec *spec = ice->spec; + switch (psc724_cont[n].target) { + case WM8766: + psc724_WM8766_set_volume(ice, + psc724_cont[n].ch, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776in: + psc724_WM8776_set_input_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776out: + psc724_WM8776_set_output_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776sel: + psc724_WM8776_set_input_selector(ice, + spec->vol[n].ch1); + break; + + case WM8776agc: + psc724_WM8776_set_agc(ice, spec->vol[n].ch1); + break; + + case WM8776afl: + psc724_WM8776_set_afl(ice, spec->vol[n].ch1); + break; + case mute_all: + psc724_set_mute_all(ice, spec->vol[n].ch1); + break; + + default: + break; + } +} + +static int psc724_cont_volume_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1, vol2; + int changed; + + changed = 0; + vol1 = uc->value.integer.value[0] & 0xff; + vol2 = uc->value.integer.value[1] & 0xff; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + changed = 1; + } + if (spec->vol[n].ch2 != vol2) { + spec->vol[n].ch2 = vol2; + changed = 1; + } + if (changed) + psc724_cont_update(ice, n); + + return changed; +} + +static int psc724_cont_boolean_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = !!uc->value.integer.value[0]; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + psc724_cont_update(ice, n); + return 1; + } + return 0; +} + +static int psc724_cont_enum_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = uc->value.enumerated.item[0]; + if (vol1 >= psc724_get_enum_count(n)) + return -EINVAL; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + psc724_cont_update(ice, n); + return 1; + } + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_gain1, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_gain2, -10350, 50, 1); + +static int __devinit psc724_add_controls(struct snd_ice1712 *ice) +{ + int i; + struct snd_kcontrol_new cont; + int err; + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + for (i = 0; i < ARRAY_SIZE(psc724_cont); i++) { + cont.private_value = i; + cont.name = psc724_cont[i].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + cont.tlv.p = NULL; + switch (psc724_cont[i].type) { + case VOLUME1: + case VOLUME2: + cont.info = psc724_cont_volume_info; + cont.get = psc724_cont_volume_get; + cont.put = psc724_cont_volume_put; + cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + if (psc724_cont[i].type == VOLUME1) + cont.tlv.p = db_scale_gain1; + else + cont.tlv.p = db_scale_gain2; + break; + case BOOLEAN: + cont.info = psc724_cont_boolean_info; + cont.get = psc724_cont_boolean_get; + cont.put = psc724_cont_boolean_put; + break; + case ENUM: + cont.info = psc724_cont_enum_info; + cont.get = psc724_cont_enum_get; + cont.put = psc724_cont_enum_put; + break; + default: + snd_BUG(); + return -EINVAL; + } + err = snd_ctl_add(ice->card, snd_ctl_new1(&cont, ice)); + if (err < 0) + return err; + } + + return 0; +} + +/****************************************************************************/ +/* probe/initialize/setup */ +/****************************************************************************/ + +static int __devinit psc724_init(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + psc724_WM8766_init(ice); + psc724_WM8776_init(ice); + ice->gpio.set_pro_rate = psc724_set_pro_rate; + return 0; +} + +/* PSC724 has buggy EEPROM (no 96&192kHz, all FFh GPIOs), so override it here */ +static unsigned char psc724_eeprom[] __devinitdata = { + [ICE_EEP2_SYSCONF] = 0x42, /* 49.152MHz, 1 ADC, 3 DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xf0, /* I2S volume, 96kHz, 24bit */ + [ICE_EEP2_SPDIF] = 0xc1, /* spdif out-en, out-int, no input */ + + [ICE_EEP2_GPIO_DIR] = 0x00, /* not used */ + [ICE_EEP2_GPIO_DIR1] = 0x00, /* not used */ + [ICE_EEP2_GPIO_DIR2] = 0x5f, /* MUTE_ALL, WM8766 MUTE/MODE/ML/MC/MD outputs */ + + [ICE_EEP2_GPIO_MASK] = 0xff, /* read-only */ + [ICE_EEP2_GPIO_MASK1] = 0xff, /* read-only */ + [ICE_EEP2_GPIO_MASK2] = 0xa0, /* MUTE_ALL, WM8766 MUTE/MODE/ML/MC/MD writable */ + + [ICE_EEP2_GPIO_STATE2] = 0x24, +// [ICE_EEP2_GPIO_STATE2] = 0x5f, /* muted, all WM8766 pins high */ +}; + +struct snd_ice1712_card_info snd_vt1724_psc724_cards[] __devinitdata = { + { + .subvendor = VT1724_SUBDEVICE_PSC724, + .name = "Philips PSC724 Ultimate Edge", + .model = "psc724", + .chip_init = psc724_init, + .build_controls = psc724_add_controls, + .eeprom_size = sizeof(psc724_eeprom), + .eeprom_data = psc724_eeprom, + }, + {} /*terminator*/ +}; diff --git a/sound/pci/ice1712/psc724.h b/sound/pci/ice1712/psc724.h new file mode 100644 index 0000000..858e5fd --- /dev/null +++ b/sound/pci/ice1712/psc724.h @@ -0,0 +1,13 @@ +#ifndef __SOUND_PSC724_H +#define __SOUND_PSC724_H + +/* ID */ +#define PSC724_DEVICE_DESC \ + "{Philips,PSC724 Ultimate Edge}," + +#define VT1724_SUBDEVICE_PSC724 0xab170619 + +/* entry struct */ +extern struct snd_ice1712_card_info snd_vt1724_psc724_cards[]; + +#endif /* __SOUND_PSC724_H */
On Wednesday 21 March 2012 00:03:42 Ondrej Zary wrote:
On Tuesday 13 March 2012 21:59:16 Ondrej Zary wrote:
Hello, I'm trying do implement support for Philips PSC724 Ultimate Edge card, which is based on VT1722 + WM8776 + WM8766 chips. I've examined the connections and commented them in the code. The patch is heavily based on se.c.
When trying to play a sound, the player runs but no sound comes from the output, no matter what I do with alsamixer.
The interesting thing is that recording works - I can record something from e.g. line-in and play it on another machine. Recording source selection in alsamixer works too (if line-in is deselected, only silence is recorded). So WM8776 I2C control works.
I'm missing something but don't know what. Any ideas?
I've missed a mute-all circuit (which grounds all outputs) controlled by GPIO22. Now the driver works - front, rear and center/lfe outputs. TODO:
- support headphone output
- move WM8776 and WM8766 code out
New version again, this time with headphone output support. Including (simple and broken) headphone jack detection (using GPIO14).
diff --git a/sound/pci/ice1712/Makefile b/sound/pci/ice1712/Makefile index f7ce33f..6d6ae06 100644 --- a/sound/pci/ice1712/Makefile +++ b/sound/pci/ice1712/Makefile @@ -5,7 +5,7 @@
snd-ice17xx-ak4xxx-objs := ak4xxx.o snd-ice1712-objs := ice1712.o delta.o hoontech.o ews.o -snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o +snd-ice1724-objs := ice1724.o amp.o revo.o aureon.o vt1720_mobo.o pontis.o prodigy192.o prodigy_hifi.o juli.o phase.o wtm.o se.o maya44.o quartet.o psc724.o
# Toplevel Module Dependency obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 9236297..d19db09 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -54,6 +54,7 @@ #include "wtm.h" #include "se.h" #include "quartet.h" +#include "psc724.h"
MODULE_AUTHOR("Jaroslav Kysela perex@perex.cz"); MODULE_DESCRIPTION("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)"); @@ -2234,6 +2235,7 @@ static struct snd_ice1712_card_info *card_tables[] __devinitdata = { snd_vt1724_se_cards, snd_vt1724_qtet_cards, snd_vt1724_ooaoo_cards, + snd_vt1724_psc724_cards, NULL, };
@@ -2348,7 +2350,7 @@ static int __devinit snd_vt1724_read_eeprom(struct snd_ice1712 *ice, return -EIO; } ice->eeprom.version = snd_vt1724_read_i2c(ice, dev, 0x05); - if (ice->eeprom.version != 2) + if (ice->eeprom.version != 1 && ice->eeprom.version != 2) printk(KERN_WARNING "ice1724: Invalid EEPROM version %i\n", ice->eeprom.version); size = ice->eeprom.size - 6; diff --git a/sound/pci/ice1712/psc724.c b/sound/pci/ice1712/psc724.c new file mode 100644 index 0000000..2903af3 --- /dev/null +++ b/sound/pci/ice1712/psc724.c @@ -0,0 +1,755 @@ +/* + * ALSA driver for ICEnsemble VT1724 (Envy24HT) + * + * Lowlevel functions for Philips PSC724 Ultimate Edge + * + * Copyright (c) 2012 Ondrej Zary linux@rainbow-software.org + * + * 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 <asm/io.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/tlv.h> + +#include "ice1712.h" +#include "envy24ht.h" +#include "psc724.h" + +struct psc724_spec { + struct { + unsigned char ch1, ch2; + } vol[10]; + struct snd_ice1712 *ice; + struct delayed_work hp_work; + bool hp_connected; +}; + +/****************************************************************************/ +/* PHILIPS PSC724 ULTIMATE EDGE */ +/****************************************************************************/ +/* + * VT1722 (Envy24GT) - 6 outputs, 4 inputs (only 2 used), 24-bit/96kHz + * + * system configuration ICE_EEP2_SYSCONF=0x42 + * XIN1 49.152MHz + * no MPU401 + * one stereo ADC, no S/PDIF receiver + * three stereo DACs (FRONT, REAR, CENTER+LFE) + * + * AC-Link configuration ICE_EEP2_ACLINK=0x80 + * use I2S, not AC97 + * + * I2S converters feature ICE_EEP2_I2S=0x30 + * I2S codec has no volume/mute control feature + * I2S codec does not support 96KHz or 192KHz(bug!) + * I2S codec 24bits + * + * S/PDIF configuration ICE_EEP2_SPDIF=0xc1 + * Enable integrated S/PDIF transmitter + * internal S/PDIF out implemented + * No S/PDIF input + * External S/PDIF out implemented + * + * + * ** connected chips ** + * + * WM8776 + * 2-channel DAC used for main output and stereo ADC (with 10-channel MUX) + * AIN1: LINE IN, AIN2: CD/VIDEO, AIN3: AUX, AIN4: Front MIC, AIN5: Rear MIC + * Controlled by I2C using VT1722 I2C interface: + * MODE (pin16) -- GND + * CE (pin17) -- GND I2C mode (address=0x34) + * DI (pin18) -- SDA (VT1722 pin70) + * CL (pin19) -- SCLK (VT1722 pin71) + * + * WM8766 + * 6-channel DAC used for rear & center/LFE outputs (only 4 channels used) + * Controlled by SPI using VT1722 GPIO pins: + * MODE (pin 1) -- GPIO19 (VT1722 pin99) + * ML/I2S (pin11) -- GPIO18 (VT1722 pin98) + * MC/IWL (pin12) -- GPIO17 (VT1722 pin97) + * MD/DM (pin13) -- GPIO16 (VT1722 pin96) + * MUTE (pin14) -- GPIO20 (VT1722 pin101) + * + * GPIO14 is used as input for headphone jack detection (1 = connected) + * GPIO22 is used as MUTE ALL output, grounding all 6 channels + * + * ** output pins and device names ** + * + * 7.1ch name -- output connector color -- device (-D option) + * + * FRONT 2ch -- green -- plughw:0,0 + * CENTER(Lch) SUBWOOFER(Rch) -- orange -- plughw:0,2,0 + * REAR 2ch -- black -- plughw:0,2,1 + * + */ + +#define GPIO_HP_JACK (1 << 14) +#define GPIO_MUTE_SUR (1 << 20) +#define GPIO_MUTE_ALL (1 << 22) + +/****************************************************************************/ +/* WM8766 interface */ +/****************************************************************************/ + +static void psc724_WM8766_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int st; + unsigned int bits; + int i; + const unsigned int DATA = 0x010000; + const unsigned int CLOCK = 0x020000; + const unsigned int LOAD = 0x040000; + const unsigned int ALL_MASK = (DATA | CLOCK | LOAD); + + snd_ice1712_save_gpio_status(ice); + + st = ((addr & 0x7f) << 9) | (data & 0x1ff); + snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | ALL_MASK); + snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~ALL_MASK); + bits = snd_ice1712_gpio_read(ice) & ~ALL_MASK; + + snd_ice1712_gpio_write(ice, bits); + for (i = 0; i < 16; i++) { + udelay(1); + bits &= ~CLOCK; + st = (st << 1); + if (st & 0x10000) + bits |= DATA; + else + bits &= ~DATA; + + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= CLOCK; + snd_ice1712_gpio_write(ice, bits); + } + + udelay(1); + bits |= LOAD; + snd_ice1712_gpio_write(ice, bits); + + udelay(1); + bits |= (DATA | CLOCK); + snd_ice1712_gpio_write(ice, bits); + + snd_ice1712_restore_gpio_status(ice); +} + +static void psc724_WM8766_set_volume(struct snd_ice1712 *ice, int ch, + unsigned int vol1, unsigned int vol2) +{ + switch (ch) { + case 0: + psc724_WM8766_write(ice, 0x000, vol1); + psc724_WM8766_write(ice, 0x001, vol2 | 0x100); + break; + case 1: + psc724_WM8766_write(ice, 0x004, vol1); + psc724_WM8766_write(ice, 0x005, vol2 | 0x100); + break; + case 2: + psc724_WM8766_write(ice, 0x006, vol1); + psc724_WM8766_write(ice, 0x007, vol2 | 0x100); + break; + } +} + +static void __devinit psc724_WM8766_init(struct snd_ice1712 *ice) +{ + psc724_WM8766_write(ice, 0x1f, 0x000); /* RESET ALL */ + udelay(10); + + psc724_WM8766_set_volume(ice, 0, 0, 0); /* volume L=0 R=0 */ + psc724_WM8766_set_volume(ice, 1, 0, 0); /* volume L=0 R=0 */ + psc724_WM8766_set_volume(ice, 2, 0, 0); /* volume L=0 R=0 */ + + psc724_WM8766_write(ice, 0x03, 0x022); /* serial mode I2S-24bits */ + psc724_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ + psc724_WM8766_write(ice, 0x12, 0x000); /* MDP=0 */ + psc724_WM8766_write(ice, 0x15, 0x000); /* MDP=0 */ + psc724_WM8766_write(ice, 0x09, 0x000); /* demp=off mute=off */ + + psc724_WM8766_write(ice, 0x02, 0x124); /* ch-assign L=L R=R RESET */ + psc724_WM8766_write(ice, 0x02, 0x120); /* ch-assign L=L R=R */ +} + +static void psc724_WM8766_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + if (rate > 96000) + psc724_WM8766_write(ice, 0x0a, 0x000); /* MCLK=128fs */ + else + psc724_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */ +} + + +/****************************************************************************/ +/* WM8776 interface */ +/****************************************************************************/ + +static void psc724_WM8776_write(struct snd_ice1712 *ice, + unsigned int addr, unsigned int data) +{ + unsigned int val; + + val = (addr << 9) | data; + snd_vt1724_write_i2c(ice, 0x34, val >> 8, val & 0xff); +} + + +static void psc724_WM8776_set_output_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + psc724_WM8776_write(ice, 0x03, vol1); + psc724_WM8776_write(ice, 0x04, vol2 | 0x100); +} + +static void psc724_WM8776_set_input_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + psc724_WM8776_write(ice, 0x0e, vol1); + psc724_WM8776_write(ice, 0x0f, vol2 | 0x100); +} + +static const char *psc724_sel[] = { + "LINE-IN", "CD-IN", "AUX-IN", "FRONT-MIC-IN", "REAR-MIC-IN", "ALL-MIX", NULL +}; + +static void psc724_WM8776_set_input_selector(struct snd_ice1712 *ice, + unsigned int sel) +{ + static unsigned char vals[] = { + /* LINE, CD, AUX, FRONT-MIC, REAR-MIC, ALL, nothing */ + 0x01, 0x02, 0x04, 0x08, 0x10, 0x1f, 0x00 + }; + if (sel > 6) + sel = 6; + psc724_WM8776_write(ice, 0x15, vals[sel]); +} + +static void psc724_WM8776_set_afl(struct snd_ice1712 *ice, unsigned int afl) +{ + /* AFL -- After Fader Listening */ + if (afl) + psc724_WM8776_write(ice, 0x16, 0x005); + else + psc724_WM8776_write(ice, 0x16, 0x001); +} + +static const char *psc724_agc[] = { + "Off", "LimiterMode", "ALCMode", NULL +}; + +static void psc724_WM8776_set_agc(struct snd_ice1712 *ice, unsigned int agc) +{ + /* AGC -- Auto Gain Control of the input */ + switch (agc) { + case 0: + psc724_WM8776_write(ice, 0x11, 0x000); /* Off */ + break; + case 1: + psc724_WM8776_write(ice, 0x10, 0x07b); + psc724_WM8776_write(ice, 0x11, 0x100); /* LimiterMode */ + break; + case 2: + psc724_WM8776_write(ice, 0x10, 0x1fb); + psc724_WM8776_write(ice, 0x11, 0x100); /* ALCMode */ + break; + } +} + +static void psc724_WM8776_set_headphone_volume(struct snd_ice1712 *ice, + unsigned int vol1, unsigned int vol2) +{ + psc724_WM8776_write(ice, 0x00, vol1); + psc724_WM8776_write(ice, 0x01, vol2 | 0x100); +} + +static void psc724_WM8776_set_headphone_mute(struct snd_ice1712 *ice, + unsigned int unmute) +{ + if (unmute) + psc724_WM8776_write(ice, 0x0d, 0); + else + psc724_WM8776_write(ice, 0x0d, 1 << 3); +} + +static void psc724_set_mute_all(struct snd_ice1712 *ice, unsigned int unmute) +{ + unsigned int bits = snd_ice1712_gpio_read(ice); + if (unmute) + snd_ice1712_gpio_write(ice, bits & ~(GPIO_MUTE_ALL | GPIO_MUTE_SUR)); + else + snd_ice1712_gpio_write(ice, bits | GPIO_MUTE_ALL | GPIO_MUTE_SUR); +} + + +static void __devinit psc724_WM8776_init(struct snd_ice1712 *ice) +{ + int i; + static unsigned short __devinitdata default_values[] = { + 0x100, 0x100, 0x100, + 0x100, 0x100, 0x100, + 0x000, 0x090, 0x000, 0x000, + 0x022, 0x022, 0x022, + 0x008, 0x0cf, 0x0cf, 0x07b, 0x000, + 0x032, 0x000, 0x0a6, 0x001, 0x001 + }; + + psc724_WM8776_write(ice, 0x17, 0x000); /* reset all */ + /* ADC and DAC interface is I2S 24bits mode */ + /* The sample-rate are automatically changed */ + udelay(10); + /* BUT my board can not do reset all, so I load all by manually. */ + for (i = 0; i < ARRAY_SIZE(default_values); i++) + psc724_WM8776_write(ice, i, default_values[i]); + + psc724_WM8776_set_input_selector(ice, 0); + psc724_WM8776_set_afl(ice, 0); + psc724_WM8776_set_agc(ice, 0); + psc724_WM8776_set_input_volume(ice, 0, 0); + psc724_WM8776_set_output_volume(ice, 0, 0); + + /* head phone mute and power down */ + psc724_WM8776_write(ice, 0x00, 0); + psc724_WM8776_write(ice, 0x01, 0); + psc724_WM8776_write(ice, 0x02, 0x100); + psc724_WM8776_write(ice, 0x0d, 0x080); +} + +static void psc724_WM8776_set_pro_rate(struct snd_ice1712 *ice, + unsigned int rate) +{ + /* nothing to do */ +} + + +/****************************************************************************/ +/* runtime interface */ +/****************************************************************************/ + +static void psc724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate) +{ + psc724_WM8766_set_pro_rate(ice, rate); + psc724_WM8776_set_pro_rate(ice, rate); +} + +struct psc724_control { + char *name; + enum { + WM8766, + WM8776in, + WM8776out, + WM8776sel, + WM8776agc, + WM8776afl, + WM8776hp, + WM8776hpm, + mute_all + } target; + enum { VOLUME1, VOLUME2, BOOLEAN, ENUM } type; + int ch; + const char **member; + const char *comment; +}; + +static const struct psc724_control psc724_cont[] = { + { + .name = "Front Playback Volume", + .target = WM8776out, + .type = VOLUME1, + .comment = "Front(green)" + }, + { + .name = "Surround Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 0, + .comment = "Surround(black)" + }, + { + .name = "CLFE Playback Volume", + .target = WM8766, + .type = VOLUME1, + .ch = 1, + .comment = "Center(Lch)&SubWoofer(Rch)(orange)" + }, + { + .name = "Headphone Playback Volume", + .target = WM8776hp, + .type = VOLUME1, + .comment = "Front panel" + }, + { + .name = "Headphone Playback Switch", + .target = WM8776hpm, + .type = BOOLEAN + }, + { + .name = "Capture Volume", + .target = WM8776in, + .type = VOLUME2 + }, + { + .name = "Capture Select", + .target = WM8776sel, + .type = ENUM, + .member = psc724_sel + }, + { + .name = "AGC Capture Mode", + .target = WM8776agc, + .type = ENUM, + .member = psc724_agc + }, + { + .name = "AFL Bypass Playback Switch", + .target = WM8776afl, + .type = BOOLEAN + }, + { + .name = "Master Playback Switch", + .target = mute_all, + .type = BOOLEAN + } +}; + +static int psc724_get_enum_count(int n) +{ + const char **member; + int c; + + member = psc724_cont[n].member; + if (!member) + return 0; + for (c = 0; member[c]; c++) + ; + return c; +} + +static int psc724_cont_volume_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; /* mute */ + uinfo->value.integer.max = 0xff; /* 0dB */ + return 0; +} + +#define psc724_cont_boolean_info snd_ctl_boolean_mono_info + +static int psc724_cont_enum_info(struct snd_kcontrol *kc, + struct snd_ctl_elem_info *uinfo) +{ + int n, c; + + n = kc->private_value; + c = psc724_get_enum_count(n); + if (!c) + return -EINVAL; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = c; + if (uinfo->value.enumerated.item >= c) + uinfo->value.enumerated.item = c - 1; + strcpy(uinfo->value.enumerated.name, + psc724_cont[n].member[uinfo->value.enumerated.item]); + return 0; +} + +static int psc724_cont_volume_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + uc->value.integer.value[1] = spec->vol[n].ch2; + return 0; +} + +static int psc724_cont_boolean_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.integer.value[0] = spec->vol[n].ch1; + return 0; +} + +static int psc724_cont_enum_get(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + uc->value.enumerated.item[0] = spec->vol[n].ch1; + return 0; +} + +static void psc724_cont_update(struct snd_ice1712 *ice, int n) +{ + struct psc724_spec *spec = ice->spec; + switch (psc724_cont[n].target) { + case WM8766: + psc724_WM8766_set_volume(ice, + psc724_cont[n].ch, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776in: + psc724_WM8776_set_input_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776out: + psc724_WM8776_set_output_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776hp: + psc724_WM8776_set_headphone_volume(ice, + spec->vol[n].ch1, + spec->vol[n].ch2); + break; + + case WM8776hpm: + psc724_WM8776_set_headphone_mute(ice, spec->vol[n].ch1); + break; + + case WM8776sel: + psc724_WM8776_set_input_selector(ice, spec->vol[n].ch1); + break; + + case WM8776agc: + psc724_WM8776_set_agc(ice, spec->vol[n].ch1); + break; + + case WM8776afl: + psc724_WM8776_set_afl(ice, spec->vol[n].ch1); + break; + case mute_all: + psc724_set_mute_all(ice, spec->vol[n].ch1); + break; + + default: + break; + } +} + +static int psc724_cont_volume_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1, vol2; + int changed; + + changed = 0; + vol1 = uc->value.integer.value[0] & 0xff; + vol2 = uc->value.integer.value[1] & 0xff; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + changed = 1; + } + if (spec->vol[n].ch2 != vol2) { + spec->vol[n].ch2 = vol2; + changed = 1; + } + if (changed) + psc724_cont_update(ice, n); + + return changed; +} + +static int psc724_cont_boolean_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = !!uc->value.integer.value[0]; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + psc724_cont_update(ice, n); + return 1; + } + return 0; +} + +static int psc724_cont_enum_put(struct snd_kcontrol *kc, + struct snd_ctl_elem_value *uc) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kc); + struct psc724_spec *spec = ice->spec; + int n = kc->private_value; + unsigned int vol1; + + vol1 = uc->value.enumerated.item[0]; + if (vol1 >= psc724_get_enum_count(n)) + return -EINVAL; + if (spec->vol[n].ch1 != vol1) { + spec->vol[n].ch1 = vol1; + psc724_cont_update(ice, n); + return 1; + } + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_gain1, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(db_scale_gain2, -10350, 50, 1); + +static int __devinit psc724_add_controls(struct snd_ice1712 *ice) +{ + int i; + struct snd_kcontrol_new cont; + int err; + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + for (i = 0; i < ARRAY_SIZE(psc724_cont); i++) { + cont.private_value = i; + cont.name = psc724_cont[i].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + cont.tlv.p = NULL; + switch (psc724_cont[i].type) { + case VOLUME1: + case VOLUME2: + cont.info = psc724_cont_volume_info; + cont.get = psc724_cont_volume_get; + cont.put = psc724_cont_volume_put; + cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + if (psc724_cont[i].type == VOLUME1) + cont.tlv.p = db_scale_gain1; + else + cont.tlv.p = db_scale_gain2; + break; + case BOOLEAN: + cont.info = psc724_cont_boolean_info; + cont.get = psc724_cont_boolean_get; + cont.put = psc724_cont_boolean_put; + break; + case ENUM: + cont.info = psc724_cont_enum_info; + cont.get = psc724_cont_enum_get; + cont.put = psc724_cont_enum_put; + break; + default: + snd_BUG(); + return -EINVAL; + } + err = snd_ctl_add(ice->card, snd_ctl_new1(&cont, ice)); + if (err < 0) + return err; + } + + return 0; +} + +static void psc724_update_hp_jack_state(struct work_struct *work) +{ + struct psc724_spec *spec = container_of(work, struct psc724_spec, + hp_work.work); +// printk("spec=%p\n", spec); + struct snd_ice1712 *ice = spec->ice; +// printk("dacs=%d\n", ice->num_total_dacs); + bool hp_connected = snd_ice1712_gpio_read(ice) & GPIO_HP_JACK; +// printk("hp_connected=%d\n", hp_connected); + schedule_delayed_work(&spec->hp_work, msecs_to_jiffies(1000)); + + if (hp_connected == spec->hp_connected) + return; + if (hp_connected) { + psc724_set_mute_all(ice, 0); + psc724_WM8776_set_headphone_mute(ice, 1); + } else { + psc724_set_mute_all(ice, 1); + psc724_WM8776_set_headphone_mute(ice, 0); + } + spec->hp_connected = hp_connected; +} + +/****************************************************************************/ +/* probe/initialize/setup */ +/****************************************************************************/ + +static int __devinit psc724_init(struct snd_ice1712 *ice) +{ + struct psc724_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + ice->spec = spec; + + ice->num_total_dacs = 6; + ice->num_total_adcs = 2; + psc724_WM8766_init(ice); + psc724_WM8776_init(ice); + ice->gpio.set_pro_rate = psc724_set_pro_rate; + spec->ice = ice; + INIT_DELAYED_WORK(&spec->hp_work, psc724_update_hp_jack_state); + schedule_delayed_work(&spec->hp_work, msecs_to_jiffies(1000)); + return 0; +} + +/* PSC724 has buggy EEPROM (no 96&192kHz, all FFh GPIOs), so override it here */ +static unsigned char psc724_eeprom[] __devinitdata = { + [ICE_EEP2_SYSCONF] = 0x42, /* 49.152MHz, 1 ADC, 3 DACs */ + [ICE_EEP2_ACLINK] = 0x80, /* I2S */ + [ICE_EEP2_I2S] = 0xf0, /* I2S volume, 96kHz, 24bit */ + [ICE_EEP2_SPDIF] = 0xc1, /* spdif out-en, out-int, no input */ + + [ICE_EEP2_GPIO_DIR] = 0x00, /* not used */ + [ICE_EEP2_GPIO_DIR1] = 0x00, /* not used */ + [ICE_EEP2_GPIO_DIR2] = 0x5f, /* MUTE_ALL, WM8766 MUTE/MODE/ML/MC/MD outputs */ + + [ICE_EEP2_GPIO_MASK] = 0xff, /* read-only */ + [ICE_EEP2_GPIO_MASK1] = 0xff, /* read-only */ + [ICE_EEP2_GPIO_MASK2] = 0xa0, /* MUTE_ALL, WM8766 MUTE/MODE/ML/MC/MD writable */ + + [ICE_EEP2_GPIO_STATE2] = 0x24, +// [ICE_EEP2_GPIO_STATE2] = 0x5f, /* muted, all WM8766 pins high */ +}; + +struct snd_ice1712_card_info snd_vt1724_psc724_cards[] __devinitdata = { + { + .subvendor = VT1724_SUBDEVICE_PSC724, + .name = "Philips PSC724 Ultimate Edge", + .model = "psc724", + .chip_init = psc724_init, + .build_controls = psc724_add_controls, + .eeprom_size = sizeof(psc724_eeprom), + .eeprom_data = psc724_eeprom, + }, + {} /*terminator*/ +}; diff --git a/sound/pci/ice1712/psc724.h b/sound/pci/ice1712/psc724.h new file mode 100644 index 0000000..858e5fd --- /dev/null +++ b/sound/pci/ice1712/psc724.h @@ -0,0 +1,13 @@ +#ifndef __SOUND_PSC724_H +#define __SOUND_PSC724_H + +/* ID */ +#define PSC724_DEVICE_DESC \ + "{Philips,PSC724 Ultimate Edge}," + +#define VT1724_SUBDEVICE_PSC724 0xab170619 + +/* entry struct */ +extern struct snd_ice1712_card_info snd_vt1724_psc724_cards[]; + +#endif /* __SOUND_PSC724_H */
participants (3)
-
Mark Brown
-
Ondrej Zary
-
Pavel Hofman