aboutsummaryrefslogtreecommitdiff
path: root/sound/pci/ac97/ac97_patch.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/pci/ac97/ac97_patch.c
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'sound/pci/ac97/ac97_patch.c')
-rw-r--r--sound/pci/ac97/ac97_patch.c2309
1 files changed, 2309 insertions, 0 deletions
diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c
new file mode 100644
index 00000000000..13c34a5d820
--- /dev/null
+++ b/sound/pci/ac97/ac97_patch.c
@@ -0,0 +1,2309 @@
+/*
+ * Copyright (c) by Jaroslav Kysela <perex@suse.cz>
+ * Universal interface for Audio Codec '97
+ *
+ * For more details look to AC '97 component specification revision 2.2
+ * by Intel Corporation (http://developer.intel.com) and to datasheets
+ * for specific codecs.
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <sound/driver.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include "ac97_patch.h"
+#include "ac97_id.h"
+#include "ac97_local.h"
+
+/*
+ * Chip specific initialization
+ */
+
+static int patch_build_controls(ac97_t * ac97, const snd_kcontrol_new_t *controls, int count)
+{
+ int idx, err;
+
+ for (idx = 0; idx < count; idx++)
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&controls[idx], ac97))) < 0)
+ return err;
+ return 0;
+}
+
+/* set to the page, update bits and restore the page */
+static int ac97_update_bits_page(ac97_t *ac97, unsigned short reg, unsigned short mask, unsigned short value, unsigned short page)
+{
+ unsigned short page_save;
+ int ret;
+
+ down(&ac97->page_mutex);
+ page_save = snd_ac97_read(ac97, AC97_INT_PAGING) & AC97_PAGE_MASK;
+ snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page);
+ ret = snd_ac97_update_bits(ac97, reg, mask, value);
+ snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, page_save);
+ up(&ac97->page_mutex); /* unlock paging */
+ return ret;
+}
+
+/* The following snd_ac97_ymf753_... items added by David Shust (dshust@shustring.com) */
+
+/* It is possible to indicate to the Yamaha YMF753 the type of speakers being used. */
+static int snd_ac97_ymf753_info_speaker(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ static char *texts[3] = {
+ "Standard", "Small", "Smaller"
+ };
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 3;
+ if (uinfo->value.enumerated.item > 2)
+ uinfo->value.enumerated.item = 2;
+ strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_ac97_ymf753_get_speaker(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ val = ac97->regs[AC97_YMF753_3D_MODE_SEL];
+ val = (val >> 10) & 3;
+ if (val > 0) /* 0 = invalid */
+ val--;
+ ucontrol->value.enumerated.item[0] = val;
+ return 0;
+}
+
+static int snd_ac97_ymf753_put_speaker(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ if (ucontrol->value.enumerated.item[0] > 2)
+ return -EINVAL;
+ val = (ucontrol->value.enumerated.item[0] + 1) << 10;
+ return snd_ac97_update(ac97, AC97_YMF753_3D_MODE_SEL, val);
+}
+
+static const snd_kcontrol_new_t snd_ac97_ymf753_controls_speaker =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "3D Control - Speaker",
+ .info = snd_ac97_ymf753_info_speaker,
+ .get = snd_ac97_ymf753_get_speaker,
+ .put = snd_ac97_ymf753_put_speaker,
+};
+
+/* It is possible to indicate to the Yamaha YMF753 the source to direct to the S/PDIF output. */
+static int snd_ac97_ymf753_spdif_source_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ static char *texts[2] = { "AC-Link", "A/D Converter" };
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 2;
+ if (uinfo->value.enumerated.item > 1)
+ uinfo->value.enumerated.item = 1;
+ strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_ac97_ymf753_spdif_source_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ val = ac97->regs[AC97_YMF753_DIT_CTRL2];
+ ucontrol->value.enumerated.item[0] = (val >> 1) & 1;
+ return 0;
+}
+
+static int snd_ac97_ymf753_spdif_source_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ if (ucontrol->value.enumerated.item[0] > 1)
+ return -EINVAL;
+ val = ucontrol->value.enumerated.item[0] << 1;
+ return snd_ac97_update_bits(ac97, AC97_YMF753_DIT_CTRL2, 0x0002, val);
+}
+
+/* The AC'97 spec states that the S/PDIF signal is to be output at pin 48.
+ The YMF753 will output the S/PDIF signal to pin 43, 47 (EAPD), or 48.
+ By default, no output pin is selected, and the S/PDIF signal is not output.
+ There is also a bit to mute S/PDIF output in a vendor-specific register. */
+static int snd_ac97_ymf753_spdif_output_pin_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo)
+{
+ static char *texts[3] = { "Disabled", "Pin 43", "Pin 48" };
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 3;
+ if (uinfo->value.enumerated.item > 2)
+ uinfo->value.enumerated.item = 2;
+ strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_ac97_ymf753_spdif_output_pin_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ val = ac97->regs[AC97_YMF753_DIT_CTRL2];
+ ucontrol->value.enumerated.item[0] = (val & 0x0008) ? 2 : (val & 0x0020) ? 1 : 0;
+ return 0;
+}
+
+static int snd_ac97_ymf753_spdif_output_pin_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ if (ucontrol->value.enumerated.item[0] > 2)
+ return -EINVAL;
+ val = (ucontrol->value.enumerated.item[0] == 2) ? 0x0008 :
+ (ucontrol->value.enumerated.item[0] == 1) ? 0x0020 : 0;
+ return snd_ac97_update_bits(ac97, AC97_YMF753_DIT_CTRL2, 0x0028, val);
+ /* The following can be used to direct S/PDIF output to pin 47 (EAPD).
+ snd_ac97_write_cache(ac97, 0x62, snd_ac97_read(ac97, 0x62) | 0x0008); */
+}
+
+static const snd_kcontrol_new_t snd_ac97_ymf753_controls_spdif[3] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source",
+ .info = snd_ac97_ymf753_spdif_source_info,
+ .get = snd_ac97_ymf753_spdif_source_get,
+ .put = snd_ac97_ymf753_spdif_source_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Output Pin",
+ .info = snd_ac97_ymf753_spdif_output_pin_info,
+ .get = snd_ac97_ymf753_spdif_output_pin_get,
+ .put = snd_ac97_ymf753_spdif_output_pin_put,
+ },
+ AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",NONE,NONE) "Mute", AC97_YMF753_DIT_CTRL2, 2, 1, 1)
+};
+
+static int patch_yamaha_ymf753_3d(ac97_t * ac97)
+{
+ snd_kcontrol_t *kctl;
+ int err;
+
+ if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ strcpy(kctl->id.name, "3D Control - Wide");
+ kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 9, 7, 0);
+ snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&snd_ac97_ymf753_controls_speaker, ac97))) < 0)
+ return err;
+ snd_ac97_write_cache(ac97, AC97_YMF753_3D_MODE_SEL, 0x0c00);
+ return 0;
+}
+
+static int patch_yamaha_ymf753_post_spdif(ac97_t * ac97)
+{
+ int err;
+
+ if ((err = patch_build_controls(ac97, snd_ac97_ymf753_controls_spdif, ARRAY_SIZE(snd_ac97_ymf753_controls_spdif))) < 0)
+ return err;
+ return 0;
+}
+
+static struct snd_ac97_build_ops patch_yamaha_ymf753_ops = {
+ .build_3d = patch_yamaha_ymf753_3d,
+ .build_post_spdif = patch_yamaha_ymf753_post_spdif
+};
+
+int patch_yamaha_ymf753(ac97_t * ac97)
+{
+ /* Patch for Yamaha YMF753, Copyright (c) by David Shust, dshust@shustring.com.
+ This chip has nonstandard and extended behaviour with regard to its S/PDIF output.
+ The AC'97 spec states that the S/PDIF signal is to be output at pin 48.
+ The YMF753 will ouput the S/PDIF signal to pin 43, 47 (EAPD), or 48.
+ By default, no output pin is selected, and the S/PDIF signal is not output.
+ There is also a bit to mute S/PDIF output in a vendor-specific register.
+ */
+ ac97->build_ops = &patch_yamaha_ymf753_ops;
+ ac97->caps |= AC97_BC_BASS_TREBLE;
+ ac97->caps |= 0x04 << 10; /* Yamaha 3D enhancement */
+ return 0;
+}
+
+/*
+ * May 2, 2003 Liam Girdwood <liam.girdwood@wolfsonmicro.com>
+ * removed broken wolfson00 patch.
+ * added support for WM9705,WM9708,WM9709,WM9710,WM9711,WM9712 and WM9717.
+ */
+
+int patch_wolfson03(ac97_t * ac97)
+{
+ /* This is known to work for the ViewSonic ViewPad 1000
+ Randolph Bentson <bentson@holmsjoen.com> */
+
+ // WM9703/9707/9708/9717
+ snd_ac97_write_cache(ac97, AC97_WM97XX_FMIXER_VOL, 0x0808);
+ snd_ac97_write_cache(ac97, AC97_GENERAL_PURPOSE, 0x8000);
+ return 0;
+}
+
+int patch_wolfson04(ac97_t * ac97)
+{
+ // WM9704M/9704Q
+ // set front and rear mixer volume
+ snd_ac97_write_cache(ac97, AC97_WM97XX_FMIXER_VOL, 0x0808);
+ snd_ac97_write_cache(ac97, AC97_WM9704_RMIXER_VOL, 0x0808);
+
+ // patch for DVD noise
+ snd_ac97_write_cache(ac97, AC97_WM9704_TEST, 0x0200);
+
+ // init vol
+ snd_ac97_write_cache(ac97, AC97_WM9704_RPCM_VOL, 0x0808);
+
+ // set rear surround volume
+ snd_ac97_write_cache(ac97, AC97_SURROUND_MASTER, 0x0000);
+ return 0;
+}
+
+int patch_wolfson05(ac97_t * ac97)
+{
+ // WM9705, WM9710
+ // set front mixer volume
+ snd_ac97_write_cache(ac97, AC97_WM97XX_FMIXER_VOL, 0x0808);
+ return 0;
+}
+
+int patch_wolfson11(ac97_t * ac97)
+{
+ // WM9711, WM9712
+ // set out3 volume
+ snd_ac97_write_cache(ac97, AC97_WM9711_OUT3VOL, 0x0808);
+ return 0;
+}
+
+static const char* wm9713_mic_mixer[] = {"Stereo", "Mic1", "Mic2", "Mute"};
+static const char* wm9713_rec_mux[] = {"Stereo", "Left", "Right", "Mute"};
+static const char* wm9713_rec_src_l[] = {"Mic1", "Mic2", "Line L", "Mono In", "HP Mix L", "Spk Mix", "Mono Mix", "Zh"};
+static const char* wm9713_rec_src_r[] = {"Mic1", "Mic2", "Line R", "Mono In", "HP Mix R", "Spk Mix", "Mono Mix", "Zh"};
+
+static const struct ac97_enum wm9713_enum[] = {
+AC97_ENUM_SINGLE(AC97_LINE, 3, 4, wm9713_mic_mixer),
+AC97_ENUM_SINGLE(AC97_VIDEO, 14, 4, wm9713_rec_mux),
+AC97_ENUM_SINGLE(AC97_VIDEO, 9, 4, wm9713_rec_mux),
+AC97_ENUM_SINGLE(AC97_VIDEO, 3, 8, wm9713_rec_src_l),
+AC97_ENUM_SINGLE(AC97_VIDEO, 0, 8, wm9713_rec_src_r),
+};
+
+static const snd_kcontrol_new_t wm13_snd_ac97_controls_line_in[] = {
+AC97_DOUBLE("Line In Volume", AC97_PC_BEEP, 8, 0, 31, 1),
+AC97_SINGLE("Line In to Headphone Mute", AC97_PC_BEEP, 15, 1, 1),
+AC97_SINGLE("Line In to Speaker Mute", AC97_PC_BEEP, 14, 1, 1),
+AC97_SINGLE("Line In to Mono Mute", AC97_PC_BEEP, 13, 1, 1),
+};
+
+static const snd_kcontrol_new_t wm13_snd_ac97_controls_dac[] = {
+AC97_DOUBLE("DAC Volume", AC97_PHONE, 8, 0, 31, 1),
+AC97_SINGLE("DAC to Headphone Mute", AC97_PHONE, 15, 1, 1),
+AC97_SINGLE("DAC to Speaker Mute", AC97_PHONE, 14, 1, 1),
+AC97_SINGLE("DAC to Mono Mute", AC97_PHONE, 13, 1, 1),
+};
+
+static const snd_kcontrol_new_t wm13_snd_ac97_controls_mic[] = {
+AC97_SINGLE("MICA Volume", AC97_MIC, 8, 31, 1),
+AC97_SINGLE("MICB Volume", AC97_MIC, 0, 31, 1),
+AC97_SINGLE("MICA to Mono Mute", AC97_LINE, 7, 1, 1),
+AC97_SINGLE("MICB to Mono Mute", AC97_LINE, 6, 1, 1),
+AC97_SINGLE("MIC Boost (+20dB)", AC97_LINE, 5, 1, 1),
+AC97_ENUM("MIC Headphone Routing", wm9713_enum[0]),
+AC97_SINGLE("MIC Headphone Mixer Volume", AC97_LINE, 0, 7, 1)
+};
+
+static const snd_kcontrol_new_t wm13_snd_ac97_controls_adc[] = {
+AC97_SINGLE("ADC Mute", AC97_CD, 15, 1, 1),
+AC97_DOUBLE("Gain Step Size (1.5dB/0.75dB)", AC97_CD, 14, 6, 1, 1),
+AC97_DOUBLE("ADC Volume",AC97_CD, 8, 0, 15, 0),
+AC97_SINGLE("ADC Zero Cross", AC97_CD, 7, 1, 1),
+};
+
+static const snd_kcontrol_new_t wm13_snd_ac97_controls_recsel[] = {
+AC97_ENUM("Record to Headphone Path", wm9713_enum[1]),
+AC97_SINGLE("Record to Headphone Volume", AC97_VIDEO, 11, 7, 0),
+AC97_ENUM("Record to Mono Path", wm9713_enum[2]),
+AC97_SINGLE("Record to Mono Boost (+20dB)", AC97_VIDEO, 8, 1, 0),
+AC97_SINGLE("Record ADC Boost (+20dB)", AC97_VIDEO, 6, 1, 0),
+AC97_ENUM("Record Select Left", wm9713_enum[3]),
+AC97_ENUM("Record Select Right", wm9713_enum[4]),
+};
+
+static int patch_wolfson_wm9713_specific(ac97_t * ac97)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_line_in); i++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_line_in[i], ac97))) < 0)
+ return err;
+ }
+ snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x0808);
+
+ for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_dac); i++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_dac[i], ac97))) < 0)
+ return err;
+ }
+ snd_ac97_write_cache(ac97, AC97_PHONE, 0x0808);
+
+ for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_mic); i++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_mic[i], ac97))) < 0)
+ return err;
+ }
+ snd_ac97_write_cache(ac97, AC97_MIC, 0x0808);
+ snd_ac97_write_cache(ac97, AC97_LINE, 0x00da);
+
+ for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_adc); i++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_adc[i], ac97))) < 0)
+ return err;
+ }
+ snd_ac97_write_cache(ac97, AC97_CD, 0x0808);
+
+ for (i = 0; i < ARRAY_SIZE(wm13_snd_ac97_controls_recsel); i++) {
+ if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm13_snd_ac97_controls_recsel[i], ac97))) < 0)
+ return err;
+ }
+ snd_ac97_write_cache(ac97, AC97_VIDEO, 0xd612);
+ snd_ac97_write_cache(ac97, AC97_REC_GAIN, 0x1ba0);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static void patch_wolfson_wm9713_suspend (ac97_t * ac97)
+{
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xfeff);
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0xffff);
+}
+
+static void patch_wolfson_wm9713_resume (ac97_t * ac97)
+{
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00);
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810);
+ snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0);
+}
+#endif
+
+static struct snd_ac97_build_ops patch_wolfson_wm9713_ops = {
+ .build_specific = patch_wolfson_wm9713_specific,
+#ifdef CONFIG_PM
+ .suspend = patch_wolfson_wm9713_suspend,
+ .resume = patch_wolfson_wm9713_resume
+#endif
+};
+
+int patch_wolfson13(ac97_t * ac97)
+{
+ ac97->build_ops = &patch_wolfson_wm9713_ops;
+
+ ac97->flags |= AC97_HAS_NO_REC_GAIN | AC97_STEREO_MUTES | AC97_HAS_NO_PHONE |
+ AC97_HAS_NO_PC_BEEP | AC97_HAS_NO_VIDEO | AC97_HAS_NO_CD;
+
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_MID, 0xda00);
+ snd_ac97_write_cache(ac97, AC97_EXTENDED_MSTATUS, 0x3810);
+ snd_ac97_write_cache(ac97, AC97_POWERDOWN, 0x0);
+
+ return 0;
+}
+
+/*
+ * Tritech codec
+ */
+int patch_tritech_tr28028(ac97_t * ac97)
+{
+ snd_ac97_write_cache(ac97, 0x26, 0x0300);
+ snd_ac97_write_cache(ac97, 0x26, 0x0000);
+ snd_ac97_write_cache(ac97, AC97_SURROUND_MASTER, 0x0000);
+ snd_ac97_write_cache(ac97, AC97_SPDIF, 0x0000);
+ return 0;
+}
+
+/*
+ * Sigmatel STAC97xx codecs
+ */
+static int patch_sigmatel_stac9700_3d(ac97_t * ac97)
+{
+ snd_kcontrol_t *kctl;
+ int err;
+
+ if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ strcpy(kctl->id.name, "3D Control Sigmatel - Depth");
+ kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0);
+ snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
+ return 0;
+}
+
+static int patch_sigmatel_stac9708_3d(ac97_t * ac97)
+{
+ snd_kcontrol_t *kctl;
+ int err;
+
+ if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ strcpy(kctl->id.name, "3D Control Sigmatel - Depth");
+ kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 0, 3, 0);
+ if ((err = snd_ctl_add(ac97->bus->card, kctl = snd_ac97_cnew(&snd_ac97_controls_3d[0], ac97))) < 0)
+ return err;
+ strcpy(kctl->id.name, "3D Control Sigmatel - Rear Depth");
+ kctl->private_value = AC97_SINGLE_VALUE(AC97_3D_CONTROL, 2, 3, 0);
+ snd_ac97_write_cache(ac97, AC97_3D_CONTROL, 0x0000);
+ return 0;
+}
+
+static const snd_kcontrol_new_t snd_ac97_sigmatel_4speaker =
+AC97_SINGLE("Sigmatel 4-Speaker Stereo Playback Switch", AC97_SIGMATEL_DAC2INVERT, 2, 1, 0);
+
+static const snd_kcontrol_new_t snd_ac97_sigmatel_phaseinvert =
+AC97_SINGLE("Sigmatel Surround Phase Inversion Playback Switch", AC97_SIGMATEL_DAC2INVERT, 3, 1, 0);
+
+static const snd_kcontrol_new_t snd_ac97_sigmatel_controls[] = {
+AC97_SINGLE("Sigmatel DAC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 1, 1, 0),
+AC97_SINGLE("Sigmatel ADC 6dB Attenuate", AC97_SIGMATEL_ANALOG, 0, 1, 0)
+};
+
+static int patch_sigmatel_stac97xx_specific(ac97_t * ac97)
+{
+ int err;
+
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_ANALOG, snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) & ~0x0003);
+ if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 1))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[0], 1)) < 0)
+ return err;
+ if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_ANALOG, 0))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_controls[1], 1)) < 0)
+ return err;
+ if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 2))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_4speaker, 1)) < 0)
+ return err;
+ if (snd_ac97_try_bit(ac97, AC97_SIGMATEL_DAC2INVERT, 3))
+ if ((err = patch_build_controls(ac97, &snd_ac97_sigmatel_phaseinvert, 1)) < 0)
+ return err;
+ return 0;
+}
+
+static struct snd_ac97_build_ops patch_sigmatel_stac9700_ops = {
+ .build_3d = patch_sigmatel_stac9700_3d,
+ .build_specific = patch_sigmatel_stac97xx_specific
+};
+
+int patch_sigmatel_stac9700(ac97_t * ac97)
+{
+ ac97->build_ops = &patch_sigmatel_stac9700_ops;
+ return 0;
+}
+
+static int snd_ac97_stac9708_put_bias(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ int err;
+
+ down(&ac97->page_mutex);
+ snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+ err = snd_ac97_update_bits(ac97, AC97_SIGMATEL_BIAS2, 0x0010,
+ (ucontrol->value.integer.value[0] & 1) << 4);
+ snd_ac97_write(ac97, AC97_SIGMATEL_BIAS1, 0);
+ up(&ac97->page_mutex);
+ return err;
+}
+
+static const snd_kcontrol_new_t snd_ac97_stac9708_bias_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Sigmatel Output Bias Switch",
+ .info = snd_ac97_info_volsw,
+ .get = snd_ac97_get_volsw,
+ .put = snd_ac97_stac9708_put_bias,
+ .private_value = AC97_SINGLE_VALUE(AC97_SIGMATEL_BIAS2, 4, 1, 0),
+};
+
+static int patch_sigmatel_stac9708_specific(ac97_t *ac97)
+{
+ int err;
+
+ snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Sigmatel Surround Playback");
+ if ((err = patch_build_controls(ac97, &snd_ac97_stac9708_bias_control, 1)) < 0)
+ return err;
+ return patch_sigmatel_stac97xx_specific(ac97);
+}
+
+static struct snd_ac97_build_ops patch_sigmatel_stac9708_ops = {
+ .build_3d = patch_sigmatel_stac9708_3d,
+ .build_specific = patch_sigmatel_stac9708_specific
+};
+
+int patch_sigmatel_stac9708(ac97_t * ac97)
+{
+ unsigned int codec72, codec6c;
+
+ ac97->build_ops = &patch_sigmatel_stac9708_ops;
+ ac97->caps |= 0x10; /* HP (sigmatel surround) support */
+
+ codec72 = snd_ac97_read(ac97, AC97_SIGMATEL_BIAS2) & 0x8000;
+ codec6c = snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG);
+
+ if ((codec72==0) && (codec6c==0)) {
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1000);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0007);
+ } else if ((codec72==0x8000) && (codec6c==0)) {
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x1001);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_DAC2INVERT, 0x0008);
+ } else if ((codec72==0x8000) && (codec6c==0x0080)) {
+ /* nothing */
+ }
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+ return 0;
+}
+
+int patch_sigmatel_stac9721(ac97_t * ac97)
+{
+ ac97->build_ops = &patch_sigmatel_stac9700_ops;
+ if (snd_ac97_read(ac97, AC97_SIGMATEL_ANALOG) == 0) {
+ // patch for SigmaTel
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x4000);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
+ }
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+ return 0;
+}
+
+int patch_sigmatel_stac9744(ac97_t * ac97)
+{
+ // patch for SigmaTel
+ ac97->build_ops = &patch_sigmatel_stac9700_ops;
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000); /* is this correct? --jk */
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+ return 0;
+}
+
+int patch_sigmatel_stac9756(ac97_t * ac97)
+{
+ // patch for SigmaTel
+ ac97->build_ops = &patch_sigmatel_stac9700_ops;
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_CIC2, 0x0000); /* is this correct? --jk */
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS1, 0xabba);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_BIAS2, 0x0002);
+ snd_ac97_write_cache(ac97, AC97_SIGMATEL_MULTICHN, 0x0000);
+ return 0;
+}
+
+static int snd_ac97_stac9758_output_jack_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ static char *texts[5] = { "Input/Disabled", "Front Output",
+ "Rear Output", "Center/LFE Output", "Mixer Output" };
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 5;
+ if (uinfo->value.enumerated.item > 4)
+ uinfo->value.enumerated.item = 4;
+ strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_ac97_stac9758_output_jack_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t* ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value;
+ unsigned short val;
+
+ val = ac97->regs[AC97_SIGMATEL_OUTSEL] >> shift;
+ if (!(val & 4))
+ ucontrol->value.enumerated.item[0] = 0;
+ else
+ ucontrol->value.enumerated.item[0] = 1 + (val & 3);
+ return 0;
+}
+
+static int snd_ac97_stac9758_output_jack_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value;
+ unsigned short val;
+
+ if (ucontrol->value.enumerated.item[0] > 4)
+ return -EINVAL;
+ if (ucontrol->value.enumerated.item[0] == 0)
+ val = 0;
+ else
+ val = 4 | (ucontrol->value.enumerated.item[0] - 1);
+ return ac97_update_bits_page(ac97, AC97_SIGMATEL_OUTSEL,
+ 7 << shift, val << shift, 0);
+}
+
+static int snd_ac97_stac9758_input_jack_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ static char *texts[7] = { "Mic2 Jack", "Mic1 Jack", "Line In Jack",
+ "Front Jack", "Rear Jack", "Center/LFE Jack", "Mute" };
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 7;
+ if (uinfo->value.enumerated.item > 6)
+ uinfo->value.enumerated.item = 6;
+ strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_ac97_stac9758_input_jack_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t* ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value;
+ unsigned short val;
+
+ val = ac97->regs[AC97_SIGMATEL_INSEL];
+ ucontrol->value.enumerated.item[0] = (val >> shift) & 7;
+ return 0;
+}
+
+static int snd_ac97_stac9758_input_jack_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value;
+
+ return ac97_update_bits_page(ac97, AC97_SIGMATEL_INSEL, 7 << shift,
+ ucontrol->value.enumerated.item[0] << shift, 0);
+}
+
+static int snd_ac97_stac9758_phonesel_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ static char *texts[3] = { "None", "Front Jack", "Rear Jack" };
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 3;
+ if (uinfo->value.enumerated.item > 2)
+ uinfo->value.enumerated.item = 2;
+ strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_ac97_stac9758_phonesel_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t* ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.enumerated.item[0] = ac97->regs[AC97_SIGMATEL_IOMISC] & 3;
+ return 0;
+}
+
+static int snd_ac97_stac9758_phonesel_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ac97_t *ac97 = snd_kcontrol_chip(kcontrol);
+
+ return ac97_update_bits_page(ac97, AC97_SIGMATEL_IOMISC, 3,
+ ucontrol->value.enumerated.item[0], 0);
+}
+
+#define STAC9758_OUTPUT_JACK(xname, shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_ac97_stac9758_output_jack_info, \
+ .get = snd_ac97_stac9758_output_jack_get, \
+ .put = snd_ac97_stac9758_output_jack_put, \
+ .private_value = shift }
+#define STAC9758_INPUT_JACK(xname, shift) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+ .info = snd_ac97_stac9758_input_jack_info, \
+ .get = snd_ac97_stac9758_input_jack_get, \
+ .put = snd_ac97_stac9758_input_jack_put, \
+ .private_value = shift }
+static const snd_kcontrol_new_t snd_ac97_sigmatel_stac9758_controls[] = {
+ STAC9758_OUTPUT_JACK("Mic1 Jack", 1),
+ STAC9758_OUTPUT_JACK("LineIn Jack", 4),
+ STAC9758_OUTPUT_JACK("Front Jack", 7),
+ STAC9758_OUTPUT_JACK("Rear Jack", 10),
+ STAC9758_OUTPUT_JACK("Center/LFE Jack", 13),
+ STAC9758_INPUT_JACK("Mic Input Source", 0),
+ STAC9758_INPUT_JACK("Line Input Source", 8),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Headphone Amp",
+ .info = snd_ac97_stac9758_phonesel_info,
+ .get = snd_ac97_stac9758_phonesel_get,
+ .put = snd_ac97_stac9758_phonesel_put
+ },
+ AC97_SINGLE("Exchange Center/LFE", AC97_SIGMATEL_IOMISC, 4, 1, 0),
+ AC97_SINGLE("Headphone +3dB Boost", AC97_SIGMATEL_IOMISC, 8, 1, 0)
+};
+
+static int patch_sigmatel_stac9758_specific(ac97_t *ac97)
+{
+ int err;
+
+ err = patch_sigmatel_stac97xx_specific(ac97);
+ if (err < 0)
+ return err;
+ err = patch_build_controls(ac97, snd_ac97_sigmatel_stac9758_controls,
+ ARRAY_SIZE(snd_ac97_sigmatel_stac9758_controls));
+ if (err < 0)
+ return err;
+ /* DAC-A direct */
+ snd_ac97_rename_vol_ctl(ac97, "Headphone Playback", "Front Playback");
+ /* DAC-A to Mix = PCM */
+ /* DAC-B direct = Surround */
+ /* DAC-B to Mix */
+ snd_ac97_rename_vol_ctl(ac97, "Video Playback", "Surround Mix Playback");
+ /* DAC-C direct = Center/LFE */
+
+ return 0;
+}
+
+static struct snd_ac97_build_ops patch_sigmatel_stac9758_ops = {
+ .build_3d = patch_sigmatel_stac9700_3d,
+ .build_specific = patch_sigmatel_stac9758_specific
+};
+
+int patch_sigmatel_stac9758(ac97_t * ac97)
+{
+ static unsigned short regs[4] = {
+ AC97_SIGMATEL_OUTSEL,
+ AC97_SIGMATEL_IOMISC,
+ AC97_SIGMATEL_INSEL,
+ AC97_SIGMATEL_VARIOUS
+ };
+ static unsigned short def_regs[4] = {
+ /* OUTSEL */ 0xd794, /* CL:CL, SR:SR, LO:MX, LI:DS, MI:DS */
+ /* IOMISC */ 0x2001,
+ /* INSEL */ 0x0201, /* LI:LI, MI:M1 */
+ /* VARIOUS */ 0x0040
+ };
+ static unsigned short m675_regs[4] = {
+ /* OUTSEL */ 0xfc70, /* CL:MX, SR:MX, LO:DS, LI:MX, MI:DS */
+ /* IOMISC */ 0x2102, /* HP amp on */
+ /* INSEL */ 0x0203, /* LI:LI, MI:FR */
+ /* VARIOUS */ 0x0041 /* stereo mic */
+ };
+ unsigned short *pregs = def_regs;
+ int i;
+
+ /* Gateway M675 notebook */
+ if (ac97->pci &&
+ ac97->subsystem_vendor == 0x107b &&
+ ac97->subsystem_device == 0x0601)
+ pregs = m675_regs;
+
+ // patch for SigmaTel
+ ac97->build_ops = &patch_sigmatel_stac9758_ops;
+ /* FIXME: assume only page 0 for writing cache */
+ snd_ac97_update_bits(ac97, AC97_INT_PAGING, AC97_PAGE_MASK, AC97_PAGE_VENDOR);
+ for (i = 0; i < 4; i++)
+ snd_ac97_write_cache(ac97, regs[i], pregs[i]);
+
+ ac97->flags |= AC97_STEREO_MUTES;
+ return 0;
+}
+
+/*
+ * Cirrus Logic CS42xx codecs
+ */
+static const snd_kcontrol_new_t snd_ac97_cirrus_controls_spdif[2] = {
+ AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CSR_SPDIF, 15, 1, 0),
+ AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "AC97-SPSA", AC97_CSR_ACMODE, 0, 3, 0)
+};
+
+static int patch_cirrus_build_spdif(ac97_t * ac97)
+{
+ int err;
+
+ /* con mask, pro mask, default */
+ if ((err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3)) < 0)
+ return err;
+ /* switch, spsa */
+ if ((err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[0], 1)) < 0)
+ return err;
+ switch (ac97->id & AC97_ID_CS_MASK) {
+ case AC97_ID_CS4205:
+ if ((err = patch_build_controls(ac97, &snd_ac97_cirrus_controls_spdif[1], 1)) < 0)
+ return err;
+ break;
+ }
+ /* set default PCM S/PDIF params */
+ /* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
+ snd_ac97_write_cache(ac97, AC97_CSR_SPDIF, 0x0a20);
+ return 0;
+}
+
+static struct snd_ac97_build_ops patch_cirrus_ops = {
+ .build_spdif = patch_cirrus_build_spdif
+};
+
+int patch_cirrus_spdif(ac97_t * ac97)
+{
+ /* Basically, the cs4201/cs4205/cs4297a has non-standard sp/dif registers.
+ WHY CAN'T ANYONE FOLLOW THE BLOODY SPEC? *sigh*
+ - sp/dif EA ID is not set, but sp/dif is always present.
+ - enable/disable is spdif register bit 15.
+ - sp/dif control register is 0x68. differs from AC97:
+ - valid is bit 14 (vs 15)
+ - no DRS
+ - only 44.1/48k [00 = 48, 01=44,1] (AC97 is 00=44.1, 10=48)
+ - sp/dif ssource select is in 0x5e bits 0,1.
+ */
+
+ ac97->build_ops = &patch_cirrus_ops;
+ ac97->flags |= AC97_CS_SPDIF;
+ ac97->rates[AC97_RATES_SPDIF] &= ~SNDRV_PCM_RATE_32000;
+ ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */
+ snd_ac97_write_cache(ac97, AC97_CSR_ACMODE, 0x0080);
+ return 0;
+}
+
+int patch_cirrus_cs4299(ac97_t * ac97)
+{
+ /* force the detection of PC Beep */
+ ac97->flags |= AC97_HAS_PC_BEEP;
+
+ return patch_cirrus_spdif(ac97);
+}
+
+/*
+ * Conexant codecs
+ */
+static const snd_kcontrol_new_t snd_ac97_conexant_controls_spdif[1] = {
+ AC97_SINGLE(SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH), AC97_CXR_AUDIO_MISC, 3, 1, 0),
+};
+
+static int patch_conexant_build_spdif(ac97_t * ac97)
+{
+ int err;
+
+ /* con mask, pro mask, default */
+ if ((err = patch_build_controls(ac97, &snd_ac97_controls_spdif[0], 3)) < 0)
+ return err;
+ /* switch */
+ if ((err = patch_build_controls(ac97, &snd_ac97_conexant_controls_spdif[0], 1)) < 0)
+ return err;
+ /* set default PCM S/PDIF params */
+ /* consumer,PCM audio,no copyright,no preemphasis,PCM coder,original,48000Hz */
+ snd_ac97_write_cache(ac97, AC97_CXR_AUDIO_MISC,
+ snd_ac97_read(ac97, AC97_CXR_AUDIO_MISC) & ~(AC97_CXR_SPDIFEN|AC97_CXR_COPYRGT|AC97_CXR_SPDIF_MASK));
+ return 0;
+}
+
+static struct snd_ac97_build_ops patch_conexant_ops = {
+ .build_spdif = patch_conexant_build_spdif
+};
+
+int patch_conexant(ac97_t * ac97)
+{
+ ac97->build_ops = &patch_conexant_ops;
+ ac97->flags |= AC97_CX_SPDIF;
+ ac97->ext_id |= AC97_EI_SPDIF; /* force the detection of spdif */
+ ac97->rates[AC97_RATES_SPDIF] = SNDRV_PCM_RATE_48000; /* 48k only */
+ return 0;
+}
+
+/*
+ * Analog Device AD18xx, AD19xx codecs
+ */
+#ifdef CONFIG_PM
+static void ad18xx_resume(ac97_t *ac97)
+{
+ static unsigned short setup_regs[] = {
+ AC97_AD_MISC, AC97_AD_SERIAL_CFG, AC97_AD_JACK_SPDIF,
+ };
+ int i, codec;
+
+ for (i = 0; i < (int)ARRAY_SIZE(setup_regs); i++) {
+ unsigned short reg = setup_regs[i];
+ if (test_bit(reg, ac97->reg_accessed)) {
+ snd_ac97_write(ac97, reg, ac97->regs[reg]);
+ snd_ac97_read(ac97, reg);
+ }
+ }
+
+ if (! (ac97->flags & AC97_AD_MULTI))
+ /* normal restore */
+ snd_ac97_restore_status(ac97);
+ else {
+ /* restore the AD18xx codec configurations */
+ for (codec = 0; codec < 3; codec++) {
+ if (! ac97->spec.ad18xx.id[codec])
+ continue;
+ /* select single codec */
+ snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
+ ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
+ ac97->bus->ops->write(ac97, AC97_AD_CODEC_CFG, ac97->spec.ad18xx.codec_cfg[codec]);
+ }
+ /* select all codecs */
+ snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
+
+ /* restore status */
+ for (i = 2; i < 0x7c ; i += 2) {
+ if (i == AC97_POWERDOWN || i == AC97_EXTENDED_ID)
+ continue;
+ if (test_bit(i, ac97->reg_accessed)) {
+ /* handle multi codecs for AD18xx */
+ if (i == AC97_PCM) {
+ for (codec = 0; codec < 3; codec++) {
+ if (! ac97->spec.ad18xx.id[codec])
+ continue;
+ /* select single codec */
+ snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000,
+ ac97->spec.ad18xx.unchained[codec] | ac97->spec.ad18xx.chained[codec]);
+ /* update PCM bits */
+ ac97->bus->ops->write(ac97, AC97_PCM, ac97->spec.ad18xx.pcmreg[codec]);
+ }
+ /* select all codecs */
+ snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, 0x7000);
+ continue;
+ } else if (i == AC97_AD_TEST ||
+ i == AC97_AD_CODEC_CFG ||
+ i == AC97_AD_SERIAL_CFG)
+ continue; /* ignore */
+ }
+ snd_ac97_write(ac97, i, ac97->regs[i]);
+ snd_ac97_read(ac97, i);
+ }
+ }
+
+ snd_ac97_restore_iec958(ac97);
+}
+#endif
+
+int patch_ad1819(ac97_t * ac97)
+{
+ unsigned short scfg;
+
+ // patch for Analog Devices
+ scfg = snd_ac97_read(ac97, AC97_AD_SERIAL_CFG);
+ snd_ac97_write_cache(ac97, AC97_AD_SERIAL_CFG, scfg | 0x7000); /* select all codecs */
+ return 0;
+}
+
+static unsigned short patch_ad1881_unchained(ac97_t * ac97, int idx, unsigned short mask)
+{
+ unsigned short val;
+
+ // test for unchained codec
+ snd_ac97_update_bits(ac97, AC97_AD_SERIAL_CFG, 0x7000, mask);
+ snd_ac97_write_cache(ac97, AC97_AD_CODEC_CFG, 0x0000); /* ID0C, ID1C, SDIE = off */
+ val = snd_ac97_read(ac97, AC97_VENDOR_ID2);
+ if ((val & 0xff40) != 0x5340)
+ return 0;
+ ac97->spec.ad18xx.unchained[idx] = mask;</