snd-ice1712: Add Philips PSC724 Ultimate Edge
cover snd-ice1712: Add Philips PSC724 Ultimate Edge
Hello,
this patch series adds full support for Philips PSC724 Ultimate Edge sound card to snd-ice1712 driver. Unlike other snd-ice1712 subdrivers, the codec code is splitted into separate files, reusable by other ice1712 subdrivers.
Working: all analog outputs (front, rear, center+lfe, headphone) and inputs (front mic, rear mic, line, cd, aux), mixer, headphone jack detection, suspend/resume
Untested: SPDIF
Signature · 1 line
Ondrej Zary
PATCH 1/5 snd-ice1712: add chip_exit callback
Add chip_exit callback to allow card subdrivers to do cleanup work on module removal.
Needed by Philips PSC724 subdriver to cancel delayed work.
--- sound/pci/ice1712/ice1712.c | 8 +++++++- sound/pci/ice1712/ice1712.h | 3 +++ sound/pci/ice1712/ice1724.c | 8 +++++++- 3 files changed, 17 insertions(+), 2 deletions(-)
sound/pci/ice1712/ice1712.c +7 -1
@@ -2686,6 +2686,7 @@ static int __devinit snd_ice1712_probe(struct pci_dev *pci,for (tbl = card_tables; *tbl; tbl++) {for (c = *tbl; c->subvendor; c++) {if (c->subvendor == ice->eeprom.subvendor) { + ice->card_info = c;strcpy (card->shortname, c->name);if (c->driver)/* specific driver? */ strcpy (card->driver, c->driver);
@@ -2799,7 +2800,12 @@ static int __devinit snd_ice1712_probe(struct pci_dev *pci, static void __devexit snd_ice1712_remove(struct pci_dev *pci) { -snd_card_free (pci_get_drvdata (pci)); +struct snd_card *card =pci_get_drvdata (pci); +struct snd_ice1712 *ice = card->private_data; + +if (ice->card_info && ice->card_info->chip_exit) + ice->card_info->chip_exit (ice); +snd_card_free (card);pci_set_drvdata (pci,NULL ); }
sound/pci/ice1712/ice1712.h +3 -0
@@ -288,6 +288,7 @@ struct snd_ice1712_spdif { } ops; }; +struct snd_ice1712_card_info ;struct snd_ice1712 {unsigned long conp_dma_size;
@@ -324,6 +325,7 @@ struct snd_ice1712 {struct snd_info_entry *proc_entry;struct snd_ice1712_eeprom eeprom; +struct snd_ice1712_card_info *card_info;unsigned int pro_volumes[20 ]; unsigned int omni:1; /* Delta Omni I/O */
@@ -517,6 +519,7 @@ struct snd_ice1712_card_info {char *model;char *driver;int (*chip_init)(struct snd_ice1712 *); +void (*chip_exit)(struct snd_ice1712 *);int (*build_controls)(struct snd_ice1712 *); unsigned int no_mpu401:1;unsigned int mpu401_1_info_flags;
sound/pci/ice1712/ice1724.c +7 -1
@@ -2348,6 +2348,7 @@ static int __devinit snd_vt1724_read_eeprom(struct snd_ice1712 *ice, ice->eeprom.subvendor = c->subvendor; }else if (c->subvendor != ice->eeprom.subvendor)continue ; + ice->card_info = c;if (!c->eeprom_size || !c->eeprom_data) goto found;/* if the EEPROM is given by the driver, use it */
@@ -2788,7 +2789,12 @@ __found: static void __devexit snd_vt1724_remove(struct pci_dev *pci) { -snd_card_free (pci_get_drvdata (pci)); +struct snd_card *card =pci_get_drvdata (pci); +struct snd_ice1712 *ice = card->private_data; + +if (ice->card_info && ice->card_info->chip_exit) + ice->card_info->chip_exit (ice); +snd_card_free (card);pci_set_drvdata (pci,NULL ); }
Signature · 1 line
Ondrej Zary
PATCH 2/5 snd-ice1712: Add Wolfson Microelectronics WM8766 codec support
In reply to Ondrej ZaryNeeded by Philips PSC724 subdriver. The code does not contain any card-specific bits so other ice17xx cards using this codec could be converted to use this generic code.
--- sound/pci/ice1712/wm8766.c | 361 ++++++++++++++++++++++++++++++++++++++++++++ sound/pci/ice1712/wm8766.h | 163 ++++++++++++++++++++ 2 files changed, 524 insertions(+), 0 deletions(-) create mode 100644 sound/pci/ice1712/wm8766.c create mode 100644 sound/pci/ice1712/wm8766.h
sound/pci/ice1712/wm8766.c new file +361 -0
@@ -0,0 +1,361 @@ +/* +* ALSA driver for ICEnsemble VT17xx +* +* Lowlevel functions for WM8766 codec +* +* Copyright (c) 2012 Ondrej Zary <[email protected]> +* +* 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 <linux/delay.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include "wm8766.h" + +/* low-level access */ + +static void snd_wm8766_write (struct snd_wm8766 *wm,u16 addr,u16 data) +{ +if (addr <WM8766_REG_RESET ) + wm->regs[addr] = data; + wm->ops.write (wm, addr, data); +} + +/* mixer controls */ + +static const DECLARE_TLV_DB_SCALE(wm8766_tlv, -12750, 50, 1); + +static struct snd_wm8766_ctl snd_wm8766_default_ctl[WM8766_CTL_COUNT ] = { + [WM8766_CTL_CH1_VOL ] = { + .name ="Channel 1 Playback Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8766_tlv, + .reg1 =WM8766_REG_DACL1 , + .reg2 =WM8766_REG_DACR1 , + .mask1 =WM8766_VOL_MASK , + .mask2 =WM8766_VOL_MASK , + .max =0xff , + .flags =WM8766_FLAG_STEREO |WM8766_FLAG_VOL_UPDATE , + }, + [WM8766_CTL_CH2_VOL ] = { + .name ="Channel 2 Playback Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8766_tlv, + .reg1 =WM8766_REG_DACL2 , + .reg2 =WM8766_REG_DACR2 , + .mask1 =WM8766_VOL_MASK , + .mask2 =WM8766_VOL_MASK , + .max =0xff , + .flags =WM8766_FLAG_STEREO |WM8766_FLAG_VOL_UPDATE , + }, + [WM8766_CTL_CH3_VOL ] = { + .name ="Channel 3 Playback Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8766_tlv, + .reg1 =WM8766_REG_DACL3 , + .reg2 =WM8766_REG_DACR3 , + .mask1 =WM8766_VOL_MASK , + .mask2 =WM8766_VOL_MASK , + .max =0xff , + .flags =WM8766_FLAG_STEREO |WM8766_FLAG_VOL_UPDATE , + }, + [WM8766_CTL_CH1_SW ] = { + .name ="Channel 1 Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL2 , + .mask1 =WM8766_DAC2_MUTE1 , + .flags =WM8766_FLAG_INVERT , + }, + [WM8766_CTL_CH2_SW ] = { + .name ="Channel 2 Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL2 , + .mask1 =WM8766_DAC2_MUTE2 , + .flags =WM8766_FLAG_INVERT , + }, + [WM8766_CTL_CH3_SW ] = { + .name ="Channel 3 Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL2 , + .mask1 =WM8766_DAC2_MUTE3 , + .flags =WM8766_FLAG_INVERT , + }, + [WM8766_CTL_PHASE1_SW ] = { + .name ="Channel 1 Phase Invert Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_IFCTRL , + .mask1 =WM8766_PHASE_INVERT1 , + }, + [WM8766_CTL_PHASE2_SW ] = { + .name ="Channel 2 Phase Invert Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_IFCTRL , + .mask1 =WM8766_PHASE_INVERT2 , + }, + [WM8766_CTL_PHASE3_SW ] = { + .name ="Channel 3 Phase Invert Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_IFCTRL , + .mask1 =WM8766_PHASE_INVERT3 , + }, + [WM8766_CTL_DEEMPH1_SW ] = { + .name ="Channel 1 Deemphasis Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL2 , + .mask1 =WM8766_DAC2_DEEMP1 , + }, + [WM8766_CTL_DEEMPH2_SW ] = { + .name ="Channel 2 Deemphasis Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL2 , + .mask1 =WM8766_DAC2_DEEMP2 , + }, + [WM8766_CTL_DEEMPH3_SW ] = { + .name ="Channel 3 Deemphasis Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL2 , + .mask1 =WM8766_DAC2_DEEMP3 , + }, + [WM8766_CTL_IZD_SW ] = { + .name ="Infinite Zero Detect Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL1 , + .mask1 =WM8766_DAC_IZD , + }, + [WM8766_CTL_ZC_SW ] = { + .name ="Zero Cross Detect Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8766_REG_DACCTRL2 , + .mask1 =WM8766_DAC2_ZCD , + .flags =WM8766_FLAG_INVERT , + }, +}; + +/* exported functions */ + +void snd_wm8766_init (struct snd_wm8766 *wm) +{ +int i; +static const u16 default_values[] = { +0x000 ,0x100 , +0x120 ,0x000 , +0x000 ,0x100 ,0x000 ,0x100 ,0x000 , +0x000 ,0x080 , + }; + +memcpy (wm->ctl, snd_wm8766_default_ctl,sizeof (wm->ctl)); + +snd_wm8766_write (wm,WM8766_REG_RESET ,0x00 );/* reset */ +udelay (10 ); +/* load defaults */ +for (i =0 ; i <ARRAY_SIZE (default_values); i++) +snd_wm8766_write (wm, i, default_values[i]); +} + +void snd_wm8766_resume (struct snd_wm8766 *wm) +{ +int i; + +for (i =0 ; i <WM8766_REG_COUNT ; i++) +snd_wm8766_write (wm, i, wm->regs[i]); +} + +void snd_wm8766_set_if (struct snd_wm8766 *wm,u16 dac) +{ +u16 val = wm->regs[WM8766_REG_IFCTRL ] & ~WM8766_IF_MASK ; + + dac &=WM8766_IF_MASK ; +snd_wm8766_write (wm,WM8766_REG_IFCTRL , val | dac); +} + +void snd_wm8766_set_master_mode (struct snd_wm8766 *wm,u16 mode) +{ +u16 val = wm->regs[WM8766_REG_DACCTRL3 ] & ~WM8766_DAC3_MSTR_MASK ; + + mode &=WM8766_DAC3_MSTR_MASK ; +snd_wm8766_write (wm,WM8766_REG_DACCTRL3 , val | mode); +} + +void snd_wm8766_set_power (struct snd_wm8766 *wm,u16 power) +{ +u16 val = wm->regs[WM8766_REG_DACCTRL3 ] & ~WM8766_DAC3_POWER_MASK ; + + power &=WM8766_DAC3_POWER_MASK ; +snd_wm8766_write (wm,WM8766_REG_DACCTRL3 , val | power); +} + +void snd_wm8766_volume_restore (struct snd_wm8766 *wm) +{ +u16 val = wm->regs[WM8766_REG_DACR1 ]; +/* restore volume after MCLK stopped */ +snd_wm8766_write (wm,WM8766_REG_DACR1 , val |WM8766_VOL_UPDATE ); +} + +/* mixer callbacks */ + +static int snd_wm8766_volume_info (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_info *uinfo) +{ +struct snd_wm8766 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; + + uinfo->type =SNDRV_CTL_ELEM_TYPE_INTEGER ; + uinfo->count = (wm->ctl[n].flags &WM8766_FLAG_STEREO ) ?2 :1 ; + uinfo->value.integer.min = wm->ctl[n].min; + uinfo->value.integer.max = wm->ctl[n].max; + +return 0 ; +} + +static int snd_wm8766_enum_info (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_info *uinfo) +{ +struct snd_wm8766 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; + +return snd_ctl_enum_info (uinfo,1 , wm->ctl[n].max, + wm->ctl[n].enum_names); +} + +static int snd_wm8766_ctl_get (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_value *ucontrol) +{ +struct snd_wm8766 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; +u16 val1, val2; + +if (wm->ctl[n].get) + wm->ctl[n].get (wm, &val1, &val2); +else { + val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1; + val1 >>=__ffs (wm->ctl[n].mask1); +if (wm->ctl[n].flags &WM8766_FLAG_STEREO ) { + val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2; + val2 >>=__ffs (wm->ctl[n].mask2); +if (wm->ctl[n].flags &WM8766_FLAG_VOL_UPDATE ) + val2 &= ~WM8766_VOL_UPDATE ; + } + } +if (wm->ctl[n].flags &WM8766_FLAG_INVERT ) { + val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min); + val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min); + } + ucontrol->value.integer.value[0 ] = val1; +if (wm->ctl[n].flags &WM8766_FLAG_STEREO ) + ucontrol->value.integer.value[1 ] = val2; + +return 0 ; +} + +static int snd_wm8766_ctl_put (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_value *ucontrol) +{ +struct snd_wm8766 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; +u16 val, regval1, regval2; + +/* this also works for enum because value is an union */ + regval1 = ucontrol->value.integer.value[0 ]; + regval2 = ucontrol->value.integer.value[1 ]; +if (wm->ctl[n].flags &WM8766_FLAG_INVERT ) { + regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min); + regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min); + } +if (wm->ctl[n].set) + wm->ctl[n].set (wm, regval1, regval2); +else { + val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1; + val |= regval1 <<__ffs (wm->ctl[n].mask1); +/* both stereo controls in one register */ +if (wm->ctl[n].flags &WM8766_FLAG_STEREO && + wm->ctl[n].reg1 == wm->ctl[n].reg2) { + val &= ~wm->ctl[n].mask2; + val |= regval2 <<__ffs (wm->ctl[n].mask2); + } +snd_wm8766_write (wm, wm->ctl[n].reg1, val); +/* stereo controls in different registers */ +if (wm->ctl[n].flags &WM8766_FLAG_STEREO && + wm->ctl[n].reg1 != wm->ctl[n].reg2) { + val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2; + val |= regval2 <<__ffs (wm->ctl[n].mask2); +if (wm->ctl[n].flags &WM8766_FLAG_VOL_UPDATE ) + val |=WM8766_VOL_UPDATE ; +snd_wm8766_write (wm, wm->ctl[n].reg2, val); + } + } + +return 0 ; +} + +static int snd_wm8766_add_control (struct snd_wm8766 *wm,int num) +{ +struct snd_kcontrol_new cont; +struct snd_kcontrol *ctl; + +memset (&cont,0 ,sizeof (cont)); + cont.iface =SNDRV_CTL_ELEM_IFACE_MIXER ; + cont.private_value = num; + cont.name = wm->ctl[num].name; + cont.access =SNDRV_CTL_ELEM_ACCESS_READWRITE ; +if (wm->ctl[num].flags &WM8766_FLAG_LIM || + wm->ctl[num].flags &WM8766_FLAG_ALC ) + cont.access |=SNDRV_CTL_ELEM_ACCESS_INACTIVE ; + cont.tlv.p =NULL ; + cont.get = snd_wm8766_ctl_get; + cont.put = snd_wm8766_ctl_put; + +switch (wm->ctl[num].type) { +case SNDRV_CTL_ELEM_TYPE_INTEGER : + cont.info = snd_wm8766_volume_info; + cont.access |=SNDRV_CTL_ELEM_ACCESS_TLV_READ ; + cont.tlv.p = wm->ctl[num].tlv; +break ; +case SNDRV_CTL_ELEM_TYPE_BOOLEAN : + wm->ctl[num].max =1 ; +if (wm->ctl[num].flags &WM8766_FLAG_STEREO ) + cont.info = snd_ctl_boolean_stereo_info; +else + cont.info = snd_ctl_boolean_mono_info; +break ; +case SNDRV_CTL_ELEM_TYPE_ENUMERATED : + cont.info = snd_wm8766_enum_info; +break ; +default : +return -EINVAL ; + } + ctl =snd_ctl_new1 (&cont, wm); +if (!ctl) +return -ENOMEM ; + wm->ctl[num].kctl = ctl; + +return snd_ctl_add (wm->card, ctl); +} + +int snd_wm8766_build_controls (struct snd_wm8766 *wm) +{ +int err, i; + +for (i =0 ; i <WM8766_CTL_COUNT ; i++) +if (wm->ctl[i].name) { + err =snd_wm8766_add_control (wm, i); +if (err <0 ) +return err; + } + +return 0 ; +}
sound/pci/ice1712/wm8766.h new file +163 -0
163 lines not on this page · raw message
Signature · 1 line
Ondrej Zary
PATCH 3/5 snd-ice1712: Add Wolfson Microelectronics WM8776 codec support
In reply to Ondrej ZaryNeeded by Philips PSC724 subdriver. The code does not contain any card-specific bits so other ice17xx cards using this codec could be converted to use this generic code.
--- sound/pci/ice1712/wm8776.c | 632 ++++++++++++++++++++++++++++++++++++++++++++ sound/pci/ice1712/wm8776.h | 226 ++++++++++++++++ 2 files changed, 858 insertions(+), 0 deletions(-) create mode 100644 sound/pci/ice1712/wm8776.c create mode 100644 sound/pci/ice1712/wm8776.h
sound/pci/ice1712/wm8776.c new file +632 -0
@@ -0,0 +1,632 @@ +/* +* ALSA driver for ICEnsemble VT17xx +* +* Lowlevel functions for WM8776 codec +* +* Copyright (c) 2012 Ondrej Zary <[email protected]> +* +* 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 <linux/delay.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include "wm8776.h" + +/* low-level access */ + +static void snd_wm8776_write (struct snd_wm8776 *wm,u16 addr,u16 data) +{ +u8 bus_addr = addr <<1 | data >>8 ;/* addr + 9th data bit */ +u8 bus_data = data &0xff ;/* remaining 8 data bits */ + +if (addr <WM8776_REG_RESET ) + wm->regs[addr] = data; + wm->ops.write (wm, bus_addr, bus_data); +} + +/* register-level functions */ + +static void snd_wm8776_activate_ctl (struct snd_wm8776 *wm,char *ctl_name, +bool active) +{ +struct snd_card *card = wm->card; +struct snd_kcontrol *kctl; +struct snd_kcontrol_volatile *vd; +struct snd_ctl_elem_id elem_id; +unsigned int index_offset; + +memset (&elem_id,0 ,sizeof (elem_id)); +strncpy (elem_id.name, ctl_name,sizeof (elem_id.name)); + elem_id.iface =SNDRV_CTL_ELEM_IFACE_MIXER ; + kctl =snd_ctl_find_id (card, &elem_id); +if (!kctl) +return ; + index_offset =snd_ctl_get_ioff (kctl, &kctl->id); + vd = &kctl->vd[index_offset]; +if (active) + vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE ; +else + vd->access |=SNDRV_CTL_ELEM_ACCESS_INACTIVE ; +snd_ctl_notify (card,SNDRV_CTL_EVENT_MASK_INFO , &kctl->id); +} + +static void snd_wm8776_update_agc_ctl (struct snd_wm8776 *wm) +{ +int i, flags_on =0 , flags_off =0 ; + +switch (wm->agc_mode) { +case WM8776_AGC_OFF : + flags_off =WM8776_FLAG_LIM |WM8776_FLAG_ALC ; +break ; +case WM8776_AGC_LIM : + flags_off =WM8776_FLAG_ALC ; + flags_on =WM8776_FLAG_LIM ; +break ; +case WM8776_AGC_ALC_R : +case WM8776_AGC_ALC_L : +case WM8776_AGC_ALC_STEREO : + flags_off =WM8776_FLAG_LIM ; + flags_on =WM8776_FLAG_ALC ; +break ; + } + +for (i =0 ; i <WM8776_CTL_COUNT ; i++) +if (wm->ctl[i].flags & flags_off) +snd_wm8776_activate_ctl (wm, wm->ctl[i].name, false); +else if (wm->ctl[i].flags & flags_on) +snd_wm8776_activate_ctl (wm, wm->ctl[i].name, true); +} + +static void snd_wm8776_set_agc (struct snd_wm8776 *wm,u16 agc,u16 nothing) +{ +u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1 ] & ~WM8776_ALC1_LCT_MASK ; +u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2 ] & ~WM8776_ALC2_LCEN ; + +switch (agc) { +case 0 :/* Off */ + wm->agc_mode =WM8776_AGC_OFF ; +break ; +case 1 :/* Limiter */ + alc2 |=WM8776_ALC2_LCEN ; + wm->agc_mode =WM8776_AGC_LIM ; +break ; +case 2 :/* ALC Right */ + alc1 |=WM8776_ALC1_LCSEL_ALCR ; + alc2 |=WM8776_ALC2_LCEN ; + wm->agc_mode =WM8776_AGC_ALC_R ; +break ; +case 3 :/* ALC Left */ + alc1 |=WM8776_ALC1_LCSEL_ALCL ; + alc2 |=WM8776_ALC2_LCEN ; + wm->agc_mode =WM8776_AGC_ALC_L ; +break ; +case 4 :/* ALC Stereo */ + alc1 |=WM8776_ALC1_LCSEL_ALCSTEREO ; + alc2 |=WM8776_ALC2_LCEN ; + wm->agc_mode =WM8776_AGC_ALC_STEREO ; +break ; + } +snd_wm8776_write (wm,WM8776_REG_ALCCTRL1 , alc1); +snd_wm8776_write (wm,WM8776_REG_ALCCTRL2 , alc2); +snd_wm8776_update_agc_ctl (wm); +} + +static void snd_wm8776_get_agc (struct snd_wm8776 *wm,u16 *mode,u16 *nothing) +{ + *mode = wm->agc_mode; +} + +/* mixer controls */ + +static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0); + +static struct snd_wm8776_ctl snd_wm8776_default_ctl[WM8776_CTL_COUNT ] = { + [WM8776_CTL_DAC_VOL ] = { + .name ="Master Playback Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_dac_tlv, + .reg1 =WM8776_REG_DACLVOL , + .reg2 =WM8776_REG_DACRVOL , + .mask1 =WM8776_DACVOL_MASK , + .mask2 =WM8776_DACVOL_MASK , + .max =0xff , + .flags =WM8776_FLAG_STEREO |WM8776_FLAG_VOL_UPDATE , + }, + [WM8776_CTL_DAC_SW ] = { + .name ="Master Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_DACCTRL1 , + .reg2 =WM8776_REG_DACCTRL1 , + .mask1 =WM8776_DAC_PL_LL , + .mask2 =WM8776_DAC_PL_RR , + .flags =WM8776_FLAG_STEREO , + }, + [WM8776_CTL_DAC_ZC_SW ] = { + .name ="Master Zero Cross Detect Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_DACCTRL1 , + .mask1 =WM8776_DAC_DZCEN , + }, + [WM8776_CTL_HP_VOL ] = { + .name ="Headphone Playback Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_hp_tlv, + .reg1 =WM8776_REG_HPLVOL , + .reg2 =WM8776_REG_HPRVOL , + .mask1 =WM8776_HPVOL_MASK , + .mask2 =WM8776_HPVOL_MASK , + .min =0x2f , + .max =0x7f , + .flags =WM8776_FLAG_STEREO |WM8776_FLAG_VOL_UPDATE , + }, + [WM8776_CTL_HP_SW ] = { + .name ="Headphone Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_PWRDOWN , + .mask1 =WM8776_PWR_HPPD , + .flags =WM8776_FLAG_INVERT , + }, + [WM8776_CTL_HP_ZC_SW ] = { + .name ="Headphone Zero Cross Detect Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_HPLVOL , + .reg2 =WM8776_REG_HPRVOL , + .mask1 =WM8776_VOL_HPZCEN , + .mask2 =WM8776_VOL_HPZCEN , + .flags =WM8776_FLAG_STEREO , + }, + [WM8776_CTL_AUX_SW ] = { + .name ="AUX Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_OUTMUX , + .mask1 =WM8776_OUTMUX_AUX , + }, + [WM8776_CTL_BYPASS_SW ] = { + .name ="Bypass Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_OUTMUX , + .mask1 =WM8776_OUTMUX_BYPASS , + }, + [WM8776_CTL_DAC_IZD_SW ] = { + .name ="Infinite Zero Detect Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_DACCTRL1 , + .mask1 =WM8776_DAC_IZD , + }, + [WM8776_CTL_PHASE_SW ] = { + .name ="Phase Invert Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_PHASESWAP , + .reg2 =WM8776_REG_PHASESWAP , + .mask1 =WM8776_PHASE_INVERTL , + .mask2 =WM8776_PHASE_INVERTR , + .flags =WM8776_FLAG_STEREO , + }, + [WM8776_CTL_DEEMPH_SW ] = { + .name ="Deemphasis Playback Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_DACCTRL2 , + .mask1 =WM8776_DAC2_DEEMPH , + }, + [WM8776_CTL_ADC_VOL ] = { + .name ="Input Capture Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_adc_tlv, + .reg1 =WM8776_REG_ADCLVOL , + .reg2 =WM8776_REG_ADCRVOL , + .mask1 =WM8776_ADC_GAIN_MASK , + .mask2 =WM8776_ADC_GAIN_MASK , + .max =0xff , + .flags =WM8776_FLAG_STEREO |WM8776_FLAG_VOL_UPDATE , + }, + [WM8776_CTL_ADC_SW ] = { + .name ="Input Capture Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_ADCMUX , + .reg2 =WM8776_REG_ADCMUX , + .mask1 =WM8776_ADC_MUTEL , + .mask2 =WM8776_ADC_MUTER , + .flags =WM8776_FLAG_STEREO |WM8776_FLAG_INVERT , + }, + [WM8776_CTL_INPUT1_SW ] = { + .name ="AIN1 Capture Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_ADCMUX , + .mask1 =WM8776_ADC_MUX_AIN1 , + }, + [WM8776_CTL_INPUT2_SW ] = { + .name ="AIN2 Capture Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_ADCMUX , + .mask1 =WM8776_ADC_MUX_AIN2 , + }, + [WM8776_CTL_INPUT3_SW ] = { + .name ="AIN3 Capture Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_ADCMUX , + .mask1 =WM8776_ADC_MUX_AIN3 , + }, + [WM8776_CTL_INPUT4_SW ] = { + .name ="AIN4 Capture Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_ADCMUX , + .mask1 =WM8776_ADC_MUX_AIN4 , + }, + [WM8776_CTL_INPUT5_SW ] = { + .name ="AIN5 Capture Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_ADCMUX , + .mask1 =WM8776_ADC_MUX_AIN5 , + }, + [WM8776_CTL_AGC_SEL ] = { + .name ="AGC Select Capture Enum" , + .type =SNDRV_CTL_ELEM_TYPE_ENUMERATED , + .enum_names = {"Off" ,"Limiter" ,"ALC Right" ,"ALC Left" , +"ALC Stereo" }, + .max =5 ,/* .enum_names item count */ + .set = snd_wm8776_set_agc, + .get = snd_wm8776_get_agc, + }, + [WM8776_CTL_LIM_THR ] = { + .name ="Limiter Threshold Capture Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_lct_tlv, + .reg1 =WM8776_REG_ALCCTRL1 , + .mask1 =WM8776_ALC1_LCT_MASK , + .max =15 , + .flags =WM8776_FLAG_LIM , + }, + [WM8776_CTL_LIM_ATK ] = { + .name ="Limiter Attack Time Capture Enum" , + .type =SNDRV_CTL_ELEM_TYPE_ENUMERATED , + .enum_names = {"0.25 ms" ,"0.5 ms" ,"1 ms" ,"2 ms" ,"4 ms" , +"8 ms" ,"16 ms" ,"32 ms" ,"64 ms" ,"128 ms" ,"256 ms" }, + .max =11 ,/* .enum_names item count */ + .reg1 =WM8776_REG_ALCCTRL3 , + .mask1 =WM8776_ALC3_ATK_MASK , + .flags =WM8776_FLAG_LIM , + }, + [WM8776_CTL_LIM_DCY ] = { + .name ="Limiter Decay Time Capture Enum" , + .type =SNDRV_CTL_ELEM_TYPE_ENUMERATED , + .enum_names = {"1.2 ms" ,"2.4 ms" ,"4.8 ms" ,"9.6 ms" , +"19.2 ms" ,"38.4 ms" ,"76.8 ms" ,"154 ms" ,"307 ms" , +"614 ms" ,"1.23 s" }, + .max =11 ,/* .enum_names item count */ + .reg1 =WM8776_REG_ALCCTRL3 , + .mask1 =WM8776_ALC3_DCY_MASK , + .flags =WM8776_FLAG_LIM , + }, + [WM8776_CTL_LIM_TRANWIN ] = { + .name ="Limiter Transient Window Capture Enum" , + .type =SNDRV_CTL_ELEM_TYPE_ENUMERATED , + .enum_names = {"0 us" ,"62.5 us" ,"125 us" ,"250 us" ,"500 us" , +"1 ms" ,"2 ms" ,"4 ms" }, + .max =8 ,/* .enum_names item count */ + .reg1 =WM8776_REG_LIMITER , + .mask1 =WM8776_LIM_TRANWIN_MASK , + .flags =WM8776_FLAG_LIM , + }, + [WM8776_CTL_LIM_MAXATTN ] = { + .name ="Limiter Maximum Attenuation Capture Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_maxatten_lim_tlv, + .reg1 =WM8776_REG_LIMITER , + .mask1 =WM8776_LIM_MAXATTEN_MASK , + .min =3 , + .max =12 , + .flags =WM8776_FLAG_LIM |WM8776_FLAG_INVERT , + }, + [WM8776_CTL_ALC_TGT ] = { + .name ="ALC Target Level Capture Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_lct_tlv, + .reg1 =WM8776_REG_ALCCTRL1 , + .mask1 =WM8776_ALC1_LCT_MASK , + .max =15 , + .flags =WM8776_FLAG_ALC , + }, + [WM8776_CTL_ALC_ATK ] = { + .name ="ALC Attack Time Capture Enum" , + .type =SNDRV_CTL_ELEM_TYPE_ENUMERATED , + .enum_names = {"8.40 ms" ,"16.8 ms" ,"33.6 ms" ,"67.2 ms" , +"134 ms" ,"269 ms" ,"538 ms" ,"1.08 s" ,"2.15 s" , +"4.3 s" ,"8.6 s" }, + .max =11 ,/* .enum_names item count */ + .reg1 =WM8776_REG_ALCCTRL3 , + .mask1 =WM8776_ALC3_ATK_MASK , + .flags =WM8776_FLAG_ALC , + }, + [WM8776_CTL_ALC_DCY ] = { + .name ="ALC Decay Time Capture Enum" , + .type =SNDRV_CTL_ELEM_TYPE_ENUMERATED , + .enum_names = {"33.5 ms" ,"67.0 ms" ,"134 ms" ,"268 ms" , +"536 ms" ,"1.07 s" ,"2.14 s" ,"4.29 s" ,"8.58 s" , +"17.2 s" ,"34.3 s" }, + .max =11 ,/* .enum_names item count */ + .reg1 =WM8776_REG_ALCCTRL3 , + .mask1 =WM8776_ALC3_DCY_MASK , + .flags =WM8776_FLAG_ALC , + }, + [WM8776_CTL_ALC_MAXGAIN ] = { + .name ="ALC Maximum Gain Capture Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_maxgain_tlv, + .reg1 =WM8776_REG_ALCCTRL1 , + .mask1 =WM8776_ALC1_MAXGAIN_MASK , + .min =1 , + .max =7 , + .flags =WM8776_FLAG_ALC , + }, + [WM8776_CTL_ALC_MAXATTN ] = { + .name ="ALC Maximum Attenuation Capture Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_maxatten_alc_tlv, + .reg1 =WM8776_REG_LIMITER , + .mask1 =WM8776_LIM_MAXATTEN_MASK , + .min =10 , + .max =15 , + .flags =WM8776_FLAG_ALC |WM8776_FLAG_INVERT , + }, + [WM8776_CTL_ALC_HLD ] = { + .name ="ALC Hold Time Capture Enum" , + .type =SNDRV_CTL_ELEM_TYPE_ENUMERATED , + .enum_names = {"0 ms" ,"2.67 ms" ,"5.33 ms" ,"10.6 ms" , +"21.3 ms" ,"42.7 ms" ,"85.3 ms" ,"171 ms" ,"341 ms" , +"683 ms" ,"1.37 s" ,"2.73 s" ,"5.46 s" ,"10.9 s" , +"21.8 s" ,"43.7 s" }, + .max =16 ,/* .enum_names item count */ + .reg1 =WM8776_REG_ALCCTRL2 , + .mask1 =WM8776_ALC2_HOLD_MASK , + .flags =WM8776_FLAG_ALC , + }, + [WM8776_CTL_NGT_SW ] = { + .name ="Noise Gate Capture Switch" , + .type =SNDRV_CTL_ELEM_TYPE_BOOLEAN , + .reg1 =WM8776_REG_NOISEGATE , + .mask1 =WM8776_NGAT_ENABLE , + .flags =WM8776_FLAG_ALC , + }, + [WM8776_CTL_NGT_THR ] = { + .name ="Noise Gate Threshold Capture Volume" , + .type =SNDRV_CTL_ELEM_TYPE_INTEGER , + .tlv = wm8776_ngth_tlv, + .reg1 =WM8776_REG_NOISEGATE , + .mask1 =WM8776_NGAT_THR_MASK , + .max =7 , + .flags =WM8776_FLAG_ALC , + }, +}; + +/* exported functions */ + +void snd_wm8776_init (struct snd_wm8776 *wm) +{ +int i; +static const u16 default_values[] = { +0x000 ,0x100 ,0x000 , +0x000 ,0x100 ,0x000 , +0x000 ,0x090 ,0x000 ,0x000 , +0x022 ,0x022 ,0x022 , +0x008 ,0x0cf ,0x0cf ,0x07b ,0x000 , +0x032 ,0x000 ,0x0a6 ,0x001 ,0x001 + }; + +memcpy (wm->ctl, snd_wm8776_default_ctl,sizeof (wm->ctl)); + +snd_wm8776_write (wm,WM8776_REG_RESET ,0x00 );/* reset */ +udelay (10 ); +/* load defaults */ +for (i =0 ; i <ARRAY_SIZE (default_values); i++) +snd_wm8776_write (wm, i, default_values[i]); +} + +void snd_wm8776_resume (struct snd_wm8776 *wm) +{ +int i; + +for (i =0 ; i <WM8776_REG_COUNT ; i++) +snd_wm8776_write (wm, i, wm->regs[i]); +} + +void snd_wm8776_set_dac_if (struct snd_wm8776 *wm,u16 dac) +{ +snd_wm8776_write (wm,WM8776_REG_DACIFCTRL , dac); +} + +void snd_wm8776_set_adc_if (struct snd_wm8776 *wm,u16 adc) +{ +snd_wm8776_write (wm,WM8776_REG_ADCIFCTRL , adc); +} + +void snd_wm8776_set_master_mode (struct snd_wm8776 *wm,u16 mode) +{ +snd_wm8776_write (wm,WM8776_REG_MSTRCTRL , mode); +} + +void snd_wm8776_set_power (struct snd_wm8776 *wm,u16 power) +{ +snd_wm8776_write (wm,WM8776_REG_PWRDOWN , power); +} + +void snd_wm8776_volume_restore (struct snd_wm8776 *wm) +{ +u16 val = wm->regs[WM8776_REG_DACRVOL ]; +/* restore volume after MCLK stopped */ +snd_wm8776_write (wm,WM8776_REG_DACRVOL , val |WM8776_VOL_UPDATE ); +} + +/* mixer callbacks */ + +static int snd_wm8776_volume_info (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_info *uinfo) +{ +struct snd_wm8776 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; + + uinfo->type =SNDRV_CTL_ELEM_TYPE_INTEGER ; + uinfo->count = (wm->ctl[n].flags &WM8776_FLAG_STEREO ) ?2 :1 ; + uinfo->value.integer.min = wm->ctl[n].min; + uinfo->value.integer.max = wm->ctl[n].max; + +return 0 ; +} + +static int snd_wm8776_enum_info (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_info *uinfo) +{ +struct snd_wm8776 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; + +return snd_ctl_enum_info (uinfo,1 , wm->ctl[n].max, + wm->ctl[n].enum_names); +} + +static int snd_wm8776_ctl_get (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_value *ucontrol) +{ +struct snd_wm8776 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; +u16 val1, val2; + +if (wm->ctl[n].get) + wm->ctl[n].get (wm, &val1, &val2); +else { + val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1; + val1 >>=__ffs (wm->ctl[n].mask1); +if (wm->ctl[n].flags &WM8776_FLAG_STEREO ) { + val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2; + val2 >>=__ffs (wm->ctl[n].mask2); +if (wm->ctl[n].flags &WM8776_FLAG_VOL_UPDATE ) + val2 &= ~WM8776_VOL_UPDATE ; + } + } +if (wm->ctl[n].flags &WM8776_FLAG_INVERT ) { + val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min); + val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min); + } + ucontrol->value.integer.value[0 ] = val1; +if (wm->ctl[n].flags &WM8776_FLAG_STEREO ) + ucontrol->value.integer.value[1 ] = val2; + +return 0 ; +} + +static int snd_wm8776_ctl_put (struct snd_kcontrol *kcontrol, +struct snd_ctl_elem_value *ucontrol) +{ +struct snd_wm8776 *wm =snd_kcontrol_chip (kcontrol); +int n = kcontrol->private_value; +u16 val, regval1, regval2; + +/* this also works for enum because value is an union */ + regval1 = ucontrol->value.integer.value[0 ]; + regval2 = ucontrol->value.integer.value[1 ]; +if (wm->ctl[n].flags &WM8776_FLAG_INVERT ) { + regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min); + regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min); + } +if (wm->ctl[n].set) + wm->ctl[n].set (wm, regval1, regval2); +else { + val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1; + val |= regval1 <<__ffs (wm->ctl[n].mask1); +/* both stereo controls in one register */ +if (wm->ctl[n].flags &WM8776_FLAG_STEREO && + wm->ctl[n].reg1 == wm->ctl[n].reg2) { + val &= ~wm->ctl[n].mask2; + val |= regval2 <<__ffs (wm->ctl[n].mask2); + } +snd_wm8776_write (wm, wm->ctl[n].reg1, val); +/* stereo controls in different registers */ +if (wm->ctl[n].flags &WM8776_FLAG_STEREO && + wm->ctl[n].reg1 != wm->ctl[n].reg2) { + val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2; + val |= regval2 <<__ffs (wm->ctl[n].mask2); +if (wm->ctl[n].flags &WM8776_FLAG_VOL_UPDATE ) + val |=WM8776_VOL_UPDATE ; +snd_wm8776_write (wm, wm->ctl[n].reg2, val); + } + } + +return 0 ; +} + +static int snd_wm8776_add_control (struct snd_wm8776 *wm,int num) +{ +struct snd_kcontrol_new cont; +struct snd_kcontrol *ctl; + +memset (&cont,0 ,sizeof (cont)); + cont.iface =SNDRV_CTL_ELEM_IFACE_MIXER ; + cont.private_value = num; + cont.name = wm->ctl[num].name; + cont.access =SNDRV_CTL_ELEM_ACCESS_READWRITE ; +if (wm->ctl[num].flags &WM8776_FLAG_LIM || + wm->ctl[num].flags &WM8776_FLAG_ALC ) + cont.access |=SNDRV_CTL_ELEM_ACCESS_INACTIVE ; + cont.tlv.p =NULL ; + cont.get = snd_wm8776_ctl_get; + cont.put = snd_wm8776_ctl_put; + +switch (wm->ctl[num].type) { +case SNDRV_CTL_ELEM_TYPE_INTEGER : + cont.info = snd_wm8776_volume_info; + cont.access |=SNDRV_CTL_ELEM_ACCESS_TLV_READ ; + cont.tlv.p = wm->ctl[num].tlv; +break ; +case SNDRV_CTL_ELEM_TYPE_BOOLEAN : + wm->ctl[num].max =1 ; +if (wm->ctl[num].flags &WM8776_FLAG_STEREO ) + cont.info = snd_ctl_boolean_stereo_info; +else + cont.info = snd_ctl_boolean_mono_info; +break ; +case SNDRV_CTL_ELEM_TYPE_ENUMERATED : + cont.info = snd_wm8776_enum_info; +break ; +default : +return -EINVAL ; + } + ctl =snd_ctl_new1 (&cont, wm); +if (!ctl) +return -ENOMEM ; + +return snd_ctl_add (wm->card, ctl); +} + +int snd_wm8776_build_controls (struct snd_wm8776 *wm) +{ +int err, i; + +for (i =0 ; i <WM8776_CTL_COUNT ; i++) +if (wm->ctl[i].name) { + err =snd_wm8776_add_control (wm, i); +if (err <0 ) +return err; + } + +return 0 ; +}
sound/pci/ice1712/wm8776.h new file +226 -0
226 lines not on this page · raw message
Signature · 1 line
Ondrej Zary
PATCH 4/5 snd-ice1712: Add Philips PSC724 Ultimate Edge
In reply to Ondrej ZaryAdd psc724 subdriver to snd-ice1712 that provides full support for Philips PSC724 Ultimate Edge sound cards.
--- sound/pci/Kconfig | 2 +- sound/pci/ice1712/Makefile | 2 +- sound/pci/ice1712/ice1724.c | 4 +- sound/pci/ice1712/psc724.c | 464 +++++++++++++++++++++++++++++++++++++++++++ sound/pci/ice1712/psc724.h | 13 ++ 5 files changed, 482 insertions(+), 3 deletions(-) create mode 100644 sound/pci/ice1712/psc724.c create mode 100644 sound/pci/ice1712/psc724.h
sound/pci/Kconfig +1 -1
@@ -630,7 +630,7 @@ config SND_ICE1724 AudioTrak Prodigy 192, 7.1 (HIFI/LT/XT), HD2; Hercules Fortissimo IV; ESI Juli@; Pontis MS300; EGO-SYS WaveTerminal 192M; Albatron K8X800 Pro II; Chaintech ZNF3-150/250, 9CJS, - AV-710; Shuttle SN25P. + AV-710; Shuttle SN25P; Philips PSC724 Ultimate Edge. To compile this driver as a module, choose M here: the module will be called snd-ice1724.
sound/pci/ice1712/Makefile +1 -1
@@ -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 wm8766.o wm8776.o # Toplevel Module Dependency obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o
sound/pci/ice1712/ice1724.c +3 -1
@@ -54,6 +54,7 @@#include "wtm.h" #include "se.h" #include "quartet.h" +#include "psc724.h" MODULE_AUTHOR ("Jaroslav Kysela <[email protected]>" );MODULE_DESCRIPTION ("VIA ICEnsemble ICE1724/1720 (Envy24HT/PT)" );
@@ -2257,6 +2258,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, };
@@ -2372,7 +2374,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 ;
sound/pci/ice1712/psc724.c new file +464 -0
464 lines not on this page · raw message
sound/pci/ice1712/psc724.h new file +13 -0
@@ -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 */
Signature · 1 line
Ondrej Zary
PATCH 5/5 snd-ice1712: Fix resume on ice1724
In reply to Ondrej Zaryset_pro_rate() is called from hw_params() but not from prepare(), breaking running PCM on suspend/resume. Call it from prepare() if PCM was suspended to fix the problem.
--- sound/pci/ice1712/ice1724.c | 14 ++++++++++++++ 1 files changed, 14 insertions(+), 0 deletions(-)
sound/pci/ice1712/ice1724.c +14 -0
@@ -783,6 +783,13 @@ static int snd_vt1724_playback_pro_prepare(struct snd_pcm_substream *substream)struct snd_ice1712 *ice =snd_pcm_substream_chip (substream);unsigned char val;unsigned int size; +int err; + +if (substream->runtime->status->state ==SNDRV_PCM_STATE_SUSPENDED ) { + err =snd_vt1724_set_pro_rate (ice, substream->runtime->rate,0 ); +if (err <0 ) +return err; + }spin_lock_irq (&ice->reg_lock); val = (8 - substream->runtime->channels) >>1 ;
@@ -853,6 +860,13 @@ static int snd_vt1724_pcm_prepare(struct snd_pcm_substream *substream) {struct snd_ice1712 *ice =snd_pcm_substream_chip (substream);const struct vt1724_pcm_reg *reg = substream->runtime->private_data; +int err; + +if (substream->runtime->status->state ==SNDRV_PCM_STATE_SUSPENDED ) { + err =snd_vt1724_set_pro_rate (ice, substream->runtime->rate,0 ); +if (err <0 ) +return err; + }spin_lock_irq (&ice->reg_lock);outl (substream->runtime->dma_addr, ice->profi_port + reg->addr);
Signature · 1 line
Ondrej Zary
Re: [alsa-devel] [PATCH 5/5] snd-ice1712: Fix resume on ice1724
At Sun, 14 Oct 2012 21:09:23 +0200,
Quoted 5 lines · Ondrej Zary
set_pro_rate()is called fromhw_params()but not fromprepare(), breaking running PCM on suspend/resume. Call it fromprepare()if PCM was suspended to fix the problem.Signed-off-by Ondrej Zary <[email protected]>
This should be fixed in the resume callback. Could you check the patch below instead?
thanks,
Takashi
---
sound/pci/ice1712/ice1724.c +6 -1
@@ -2867,7 +2867,12 @@ static int snd_vt1724_resume(struct device *dev) ice->set_spdif_clock (ice,0 ); }else {/* internal on-card clock */ -snd_vt1724_set_pro_rate (ice, ice->pro_rate_default,1 ); +int rate; +if (ice->cur_rate) + rate = ice->cur_rate; +else + rate = ice->pro_rate_default; +snd_vt1724_set_pro_rate (ice, rate,1 ); }update_spdif_bits (ice, ice->pm_saved_spdif_ctrl);
Re: [alsa-devel] [PATCH 5/5] snd-ice1712: Fix resume on ice1724
Quoted 11 lines · On Wednesday 17 October 2012 08:57:39 Takashi Iwai wrote:
At Sun, 14 Oct 2012 21:09:23 +0200,
Quoted 5 lines · Ondrej Zary
set_pro_rate()is called fromhw_params()but not fromprepare(), breaking running PCM on suspend/resume. Call it fromprepare()if PCM was suspended to fix the problem.Signed-off-by Ondrej Zary <[email protected]>This should be fixed in the resume callback. Could you check the patch below instead?
Now I'm confused. This patch does not work with mplayer+dmix. And my patch doesn't either...
When mplayer is set to s32le (without dmix), it works fine without this or my patch. Also aplay seems to work without these patches.
I bet that when I tested my patch before, it fixed some real problem. But I'm now unable to reproduce that...
Quoted 28 lines
thanks,
Takashi
---
sound/pci/ice1712/ice1724.c +6
-2@@ -2867,7 +2867,12 @@ static int snd_vt1724_resume(struct device *dev) ice->set_spdif_clock (ice,0 ); }else {/* internal on-card clock */ -snd_vt1724_set_pro_rate (ice, ice->pro_rate_default,1 ); +int rate; +if (ice->cur_rate) + rate = ice->cur_rate; +else + rate = ice->pro_rate_default; +snd_vt1724_set_pro_rate (ice, rate,1 ); }update_spdif_bits (ice, ice->pm_saved_spdif_ctrl); --To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Signature · 1 line
Ondrej Zary
Takashi Iwai
AppliedAt Sun, 14 Oct 2012 21:09:18 +0200,
Quoted 11 lines · Ondrej Zary
Hello,
this patch series adds full support for Philips PSC724 Ultimate Edge sound card to snd-ice1712 driver. Unlike other snd-ice1712 subdrivers, the codec code is splitted into separate files, reusable by other ice1712 subdrivers.Working: all analog outputs (front, rear, center+lfe, headphone) and inputs (front mic, rear mic, line, cd, aux), mixer, headphone jack detection, suspend/resume
Untested: SPDIF
Applied patches 1-4 now, but skipped 5. See the reply to the post for details.
thanks,
Takashi