summaryrefslogtreecommitdiffstats
path: root/sound/pci/ice1712
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/ice1712')
-rw-r--r--sound/pci/ice1712/Makefile13
-rw-r--r--sound/pci/ice1712/ak4xxx.c170
-rw-r--r--sound/pci/ice1712/amp.c83
-rw-r--r--sound/pci/ice1712/amp.h34
-rw-r--r--sound/pci/ice1712/aureon.c2264
-rw-r--r--sound/pci/ice1712/aureon.h51
-rw-r--r--sound/pci/ice1712/delta.c922
-rw-r--r--sound/pci/ice1712/delta.h152
-rw-r--r--sound/pci/ice1712/envy24ht.h206
-rw-r--r--sound/pci/ice1712/ews.c1075
-rw-r--r--sound/pci/ice1712/ews.h72
-rw-r--r--sound/pci/ice1712/hoontech.c370
-rw-r--r--sound/pci/ice1712/hoontech.h64
-rw-r--r--sound/pci/ice1712/ice1712.c2750
-rw-r--r--sound/pci/ice1712/ice1712.h525
-rw-r--r--sound/pci/ice1712/ice1724.c2757
-rw-r--r--sound/pci/ice1712/juli.c662
-rw-r--r--sound/pci/ice1712/juli.h11
-rw-r--r--sound/pci/ice1712/maya44.c748
-rw-r--r--sound/pci/ice1712/maya44.h11
-rw-r--r--sound/pci/ice1712/phase.c950
-rw-r--r--sound/pci/ice1712/phase.h39
-rw-r--r--sound/pci/ice1712/pontis.c809
-rw-r--r--sound/pci/ice1712/pontis.h19
-rw-r--r--sound/pci/ice1712/prodigy192.c791
-rw-r--r--sound/pci/ice1712/prodigy192.h20
-rw-r--r--sound/pci/ice1712/prodigy_hifi.c1265
-rw-r--r--sound/pci/ice1712/prodigy_hifi.h24
-rw-r--r--sound/pci/ice1712/psc724.c447
-rw-r--r--sound/pci/ice1712/psc724.h14
-rw-r--r--sound/pci/ice1712/quartet.c1070
-rw-r--r--sound/pci/ice1712/quartet.h11
-rw-r--r--sound/pci/ice1712/revo.c631
-rw-r--r--sound/pci/ice1712/revo.h41
-rw-r--r--sound/pci/ice1712/se.c752
-rw-r--r--sound/pci/ice1712/se.h16
-rw-r--r--sound/pci/ice1712/stac946x.h26
-rw-r--r--sound/pci/ice1712/vt1720_mobo.c124
-rw-r--r--sound/pci/ice1712/vt1720_mobo.h27
-rw-r--r--sound/pci/ice1712/wm8766.c332
-rw-r--r--sound/pci/ice1712/wm8766.h147
-rw-r--r--sound/pci/ice1712/wm8776.c601
-rw-r--r--sound/pci/ice1712/wm8776.h209
-rw-r--r--sound/pci/ice1712/wtm.c632
-rw-r--r--sound/pci/ice1712/wtm.h21
45 files changed, 21958 insertions, 0 deletions
diff --git a/sound/pci/ice1712/Makefile b/sound/pci/ice1712/Makefile
new file mode 100644
index 0000000000..1196f22a9b
--- /dev/null
+++ b/sound/pci/ice1712/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for ALSA
+# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
+#
+
+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 psc724.o wm8766.o wm8776.o
+
+# Toplevel Module Dependency
+obj-$(CONFIG_SND_ICE1712) += snd-ice1712.o snd-ice17xx-ak4xxx.o
+obj-$(CONFIG_SND_ICE1724) += snd-ice1724.o snd-ice17xx-ak4xxx.o
diff --git a/sound/pci/ice1712/ak4xxx.c b/sound/pci/ice1712/ak4xxx.c
new file mode 100644
index 0000000000..cad33a2f26
--- /dev/null
+++ b/sound/pci/ice1712/ak4xxx.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * AK4524 / AK4528 / AK4529 / AK4355 / AK4381 interface
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "ice1712.h"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ICEnsemble ICE17xx <-> AK4xxx AD/DA chip interface");
+MODULE_LICENSE("GPL");
+
+static void snd_ice1712_akm4xxx_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ snd_ice1712_save_gpio_status(ice);
+}
+
+static void snd_ice1712_akm4xxx_unlock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ snd_ice1712_restore_gpio_status(ice);
+}
+
+/*
+ * write AK4xxx register
+ */
+static void snd_ice1712_akm4xxx_write(struct snd_akm4xxx *ak, int chip,
+ unsigned char addr, unsigned char data)
+{
+ unsigned int tmp;
+ int idx;
+ unsigned int addrdata;
+ struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ if (snd_BUG_ON(chip < 0 || chip >= 4))
+ return;
+
+ tmp = snd_ice1712_gpio_read(ice);
+ tmp |= priv->add_flags;
+ tmp &= ~priv->mask_flags;
+ if (priv->cs_mask == priv->cs_addr) {
+ if (priv->cif) {
+ tmp |= priv->cs_mask; /* start without chip select */
+ } else {
+ tmp &= ~priv->cs_mask; /* chip select low */
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+ } else {
+ /* doesn't handle cf=1 yet */
+ tmp &= ~priv->cs_mask;
+ tmp |= priv->cs_addr;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+
+ /* build I2C address + data byte */
+ addrdata = (priv->caddr << 6) | 0x20 | (addr & 0x1f);
+ addrdata = (addrdata << 8) | data;
+ for (idx = 15; idx >= 0; idx--) {
+ /* drop clock */
+ tmp &= ~priv->clk_mask;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ /* set data */
+ if (addrdata & (1 << idx))
+ tmp |= priv->data_mask;
+ else
+ tmp &= ~priv->data_mask;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ /* raise clock */
+ tmp |= priv->clk_mask;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+
+ if (priv->cs_mask == priv->cs_addr) {
+ if (priv->cif) {
+ /* assert a cs pulse to trigger */
+ tmp &= ~priv->cs_mask;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+ tmp |= priv->cs_mask; /* chip select high to trigger */
+ } else {
+ tmp &= ~priv->cs_mask;
+ tmp |= priv->cs_none; /* deselect address */
+ }
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+}
+
+/*
+ * initialize the struct snd_akm4xxx record with the template
+ */
+int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak, const struct snd_akm4xxx *temp,
+ const struct snd_ak4xxx_private *_priv, struct snd_ice1712 *ice)
+{
+ struct snd_ak4xxx_private *priv;
+
+ if (_priv != NULL) {
+ priv = kmalloc(sizeof(*priv), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+ *priv = *_priv;
+ } else {
+ priv = NULL;
+ }
+ *ak = *temp;
+ ak->card = ice->card;
+ ak->private_value[0] = (unsigned long)priv;
+ ak->private_data[0] = ice;
+ if (ak->ops.lock == NULL)
+ ak->ops.lock = snd_ice1712_akm4xxx_lock;
+ if (ak->ops.unlock == NULL)
+ ak->ops.unlock = snd_ice1712_akm4xxx_unlock;
+ if (ak->ops.write == NULL)
+ ak->ops.write = snd_ice1712_akm4xxx_write;
+ snd_akm4xxx_init(ak);
+ return 0;
+}
+
+void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice)
+{
+ unsigned int akidx;
+ if (ice->akm == NULL)
+ return;
+ for (akidx = 0; akidx < ice->akm_codecs; akidx++) {
+ struct snd_akm4xxx *ak = &ice->akm[akidx];
+ kfree((void*)ak->private_value[0]);
+ }
+ kfree(ice->akm);
+}
+
+/*
+ * build AK4xxx controls
+ */
+int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice)
+{
+ unsigned int akidx;
+ int err;
+
+ for (akidx = 0; akidx < ice->akm_codecs; akidx++) {
+ struct snd_akm4xxx *ak = &ice->akm[akidx];
+ err = snd_akm4xxx_build_controls(ak);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(snd_ice1712_akm4xxx_init);
+EXPORT_SYMBOL(snd_ice1712_akm4xxx_free);
+EXPORT_SYMBOL(snd_ice1712_akm4xxx_build_controls);
diff --git a/sound/pci/ice1712/amp.c b/sound/pci/ice1712/amp.c
new file mode 100644
index 0000000000..a7b496de6c
--- /dev/null
+++ b/sound/pci/ice1712/amp.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Advanced Micro Peripherals Ltd AUDIO2000
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "amp.h"
+
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ unsigned short cval;
+ cval = (reg << 9) | val;
+ snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff);
+}
+
+static int snd_vt1724_amp_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm_inits[] = {
+ WM_ATTEN_L, 0x0000, /* 0 db */
+ WM_ATTEN_R, 0x0000, /* 0 db */
+ WM_DAC_CTRL, 0x0008, /* 24bit I2S */
+ WM_INT_CTRL, 0x0001, /* 24bit I2S */
+ };
+
+ unsigned int i;
+
+ /* only use basic functionality for now */
+
+ /* VT1616 6ch codec connected to PSDOUT0 using packed mode */
+ ice->num_total_dacs = 6;
+ ice->num_total_adcs = 2;
+
+ /* Chaintech AV-710 has another WM8728 codec connected to PSDOUT4
+ (shared with the SPDIF output). Mixer control for this codec
+ is not yet supported. */
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AV710) {
+ for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2)
+ wm_put(ice, wm_inits[i], wm_inits[i+1]);
+ }
+
+ return 0;
+}
+
+static int snd_vt1724_amp_add_controls(struct snd_ice1712 *ice)
+{
+ if (ice->ac97)
+ /* we use pins 39 and 41 of the VT1616 for left and right
+ read outputs */
+ snd_ac97_write_cache(ice->ac97, 0x5a,
+ snd_ac97_read(ice->ac97, 0x5a) & ~0x8000);
+ return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_amp_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_AV710,
+ .name = "Chaintech AV-710",
+ .model = "av710",
+ .chip_init = snd_vt1724_amp_init,
+ .build_controls = snd_vt1724_amp_add_controls,
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_AUDIO2000,
+ .name = "AMP Ltd AUDIO2000",
+ .model = "amp2000",
+ .chip_init = snd_vt1724_amp_init,
+ .build_controls = snd_vt1724_amp_add_controls,
+ },
+ { } /* terminator */
+};
+
diff --git a/sound/pci/ice1712/amp.h b/sound/pci/ice1712/amp.h
new file mode 100644
index 0000000000..bd6323fe38
--- /dev/null
+++ b/sound/pci/ice1712/amp.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_AMP_H
+#define __SOUND_AMP_H
+
+/*
+ * ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Advanced Micro Peripherals Ltd AUDIO2000
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#define AMP_AUDIO2000_DEVICE_DESC "{AMP Ltd,AUDIO2000},"\
+ "{Chaintech,AV-710},"
+
+#if 0
+#define VT1724_SUBDEVICE_AUDIO2000 0x12142417 /* Advanced Micro Peripherals Ltd AUDIO2000 */
+#else
+#define VT1724_SUBDEVICE_AUDIO2000 0x00030003 /* a dummy ID for AMP Audio2000 */
+#endif
+#define VT1724_SUBDEVICE_AV710 0x12142417 /* AV710 - the same ID with Audio2000! */
+
+/* WM8728 on I2C for AV710 */
+#define WM_DEV 0x36
+
+#define WM_ATTEN_L 0x00
+#define WM_ATTEN_R 0x01
+#define WM_DAC_CTRL 0x02
+#define WM_INT_CTRL 0x03
+
+extern struct snd_ice1712_card_info snd_vt1724_amp_cards[];
+
+
+#endif /* __SOUND_AMP_H */
diff --git a/sound/pci/ice1712/aureon.c b/sound/pci/ice1712/aureon.c
new file mode 100644
index 0000000000..027849329c
--- /dev/null
+++ b/sound/pci/ice1712/aureon.c
@@ -0,0 +1,2264 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Terratec Aureon cards
+ *
+ * Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
+ *
+ * NOTES:
+ *
+ * - we reuse the struct snd_akm4xxx record for storing the wm8770 codec data.
+ * both wm and akm codecs are pretty similar, so we can integrate
+ * both controls in the future, once if wm codecs are reused in
+ * many boards.
+ *
+ * - DAC digital volumes are not implemented in the mixer.
+ * if they show better response than DAC analog volumes, we can use them
+ * instead.
+ *
+ * Lowlevel functions for AudioTrak Prodigy 7.1 (and possibly 192) cards
+ * Copyright (c) 2003 Dimitromanolakis Apostolos <apostol@cs.utoronto.ca>
+ *
+ * version 0.82: Stable / not all features work yet (no communication with AC97 secondary)
+ * added 64x/128x oversampling switch (should be 64x only for 96khz)
+ * fixed some recording labels (still need to check the rest)
+ * recording is working probably thanks to correct wm8770 initialization
+ *
+ * version 0.5: Initial release:
+ * working: analog output, mixer, headphone amplifier switch
+ * not working: prety much everything else, at least i could verify that
+ * we have no digital output, no capture, pretty bad clicks and poops
+ * on mixer switch and other coll stuff.
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "aureon.h"
+#include <sound/tlv.h>
+
+/* AC97 register cache for Aureon */
+struct aureon_spec {
+ unsigned short stac9744[64];
+ unsigned int cs8415_mux;
+ unsigned short master[2];
+ unsigned short vol[8];
+ unsigned char pca9554_out;
+};
+
+/* WM8770 registers */
+#define WM_DAC_ATTEN 0x00 /* DAC1-8 analog attenuation */
+#define WM_DAC_MASTER_ATTEN 0x08 /* DAC master analog attenuation */
+#define WM_DAC_DIG_ATTEN 0x09 /* DAC1-8 digital attenuation */
+#define WM_DAC_DIG_MASTER_ATTEN 0x11 /* DAC master digital attenuation */
+#define WM_PHASE_SWAP 0x12 /* DAC phase */
+#define WM_DAC_CTRL1 0x13 /* DAC control bits */
+#define WM_MUTE 0x14 /* mute controls */
+#define WM_DAC_CTRL2 0x15 /* de-emphasis and zefo-flag */
+#define WM_INT_CTRL 0x16 /* interface control */
+#define WM_MASTER 0x17 /* master clock and mode */
+#define WM_POWERDOWN 0x18 /* power-down controls */
+#define WM_ADC_GAIN 0x19 /* ADC gain L(19)/R(1a) */
+#define WM_ADC_MUX 0x1b /* input MUX */
+#define WM_OUT_MUX1 0x1c /* output MUX */
+#define WM_OUT_MUX2 0x1e /* output MUX */
+#define WM_RESET 0x1f /* software reset */
+
+/* CS8415A registers */
+#define CS8415_CTRL1 0x01
+#define CS8415_CTRL2 0x02
+#define CS8415_QSUB 0x14
+#define CS8415_RATIO 0x1E
+#define CS8415_C_BUFFER 0x20
+#define CS8415_ID 0x7F
+
+/* PCA9554 registers */
+#define PCA9554_DEV 0x40 /* I2C device address */
+#define PCA9554_IN 0x00 /* input port */
+#define PCA9554_OUT 0x01 /* output port */
+#define PCA9554_INVERT 0x02 /* input invert */
+#define PCA9554_DIR 0x03 /* port directions */
+
+/*
+ * Aureon Universe additional controls using PCA9554
+ */
+
+/*
+ * Send data to pca9554
+ */
+static void aureon_pca9554_write(struct snd_ice1712 *ice, unsigned char reg,
+ unsigned char data)
+{
+ unsigned int tmp;
+ int i, j;
+ unsigned char dev = PCA9554_DEV; /* ID 0100000, write */
+ unsigned char val = 0;
+
+ tmp = snd_ice1712_gpio_read(ice);
+
+ snd_ice1712_gpio_set_mask(ice, ~(AUREON_SPI_MOSI|AUREON_SPI_CLK|
+ AUREON_WM_RW|AUREON_WM_CS|
+ AUREON_CS8415_CS));
+ tmp |= AUREON_WM_RW;
+ tmp |= AUREON_CS8415_CS | AUREON_WM_CS; /* disable SPI devices */
+
+ tmp &= ~AUREON_SPI_MOSI;
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(50);
+
+ /*
+ * send i2c stop condition and start condition
+ * to obtain sane state
+ */
+ tmp |= AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(50);
+ tmp |= AUREON_SPI_MOSI;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(100);
+ tmp &= ~AUREON_SPI_MOSI;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(50);
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(100);
+ /*
+ * send device address, command and value,
+ * skipping ack cycles in between
+ */
+ for (j = 0; j < 3; j++) {
+ switch (j) {
+ case 0:
+ val = dev;
+ break;
+ case 1:
+ val = reg;
+ break;
+ case 2:
+ val = data;
+ break;
+ }
+ for (i = 7; i >= 0; i--) {
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ if (val & (1 << i))
+ tmp |= AUREON_SPI_MOSI;
+ else
+ tmp &= ~AUREON_SPI_MOSI;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ tmp |= AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ }
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ tmp |= AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ }
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ tmp &= ~AUREON_SPI_MOSI;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(40);
+ tmp |= AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(50);
+ tmp |= AUREON_SPI_MOSI;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(100);
+}
+
+static int aureon_universe_inmux_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[3] =
+ {"Internal Aux", "Wavetable", "Rear Line-In"};
+
+ return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int aureon_universe_inmux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ ucontrol->value.enumerated.item[0] = spec->pca9554_out;
+ return 0;
+}
+
+static int aureon_universe_inmux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ unsigned char oval, nval;
+ int change;
+
+ nval = ucontrol->value.enumerated.item[0];
+ if (nval >= 3)
+ return -EINVAL;
+ snd_ice1712_save_gpio_status(ice);
+ oval = spec->pca9554_out;
+ change = (oval != nval);
+ if (change) {
+ aureon_pca9554_write(ice, PCA9554_OUT, nval);
+ spec->pca9554_out = nval;
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+
+static void aureon_ac97_write(struct snd_ice1712 *ice, unsigned short reg,
+ unsigned short val)
+{
+ struct aureon_spec *spec = ice->spec;
+ unsigned int tmp;
+
+ /* Send address to XILINX chip */
+ tmp = (snd_ice1712_gpio_read(ice) & ~0xFF) | (reg & 0x7F);
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+ tmp |= AUREON_AC97_ADDR;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+ tmp &= ~AUREON_AC97_ADDR;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+
+ /* Send low-order byte to XILINX chip */
+ tmp &= ~AUREON_AC97_DATA_MASK;
+ tmp |= val & AUREON_AC97_DATA_MASK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+ tmp |= AUREON_AC97_DATA_LOW;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+ tmp &= ~AUREON_AC97_DATA_LOW;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+
+ /* Send high-order byte to XILINX chip */
+ tmp &= ~AUREON_AC97_DATA_MASK;
+ tmp |= (val >> 8) & AUREON_AC97_DATA_MASK;
+
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+ tmp |= AUREON_AC97_DATA_HIGH;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+ tmp &= ~AUREON_AC97_DATA_HIGH;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+
+ /* Instruct XILINX chip to parse the data to the STAC9744 chip */
+ tmp |= AUREON_AC97_COMMIT;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+ tmp &= ~AUREON_AC97_COMMIT;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(10);
+
+ /* Store the data in out private buffer */
+ spec->stac9744[(reg & 0x7F) >> 1] = val;
+}
+
+static unsigned short aureon_ac97_read(struct snd_ice1712 *ice, unsigned short reg)
+{
+ struct aureon_spec *spec = ice->spec;
+ return spec->stac9744[(reg & 0x7F) >> 1];
+}
+
+/*
+ * Initialize STAC9744 chip
+ */
+static int aureon_ac97_init(struct snd_ice1712 *ice)
+{
+ struct aureon_spec *spec = ice->spec;
+ int i;
+ static const unsigned short ac97_defaults[] = {
+ 0x00, 0x9640,
+ 0x02, 0x8000,
+ 0x04, 0x8000,
+ 0x06, 0x8000,
+ 0x0C, 0x8008,
+ 0x0E, 0x8008,
+ 0x10, 0x8808,
+ 0x12, 0x8808,
+ 0x14, 0x8808,
+ 0x16, 0x8808,
+ 0x18, 0x8808,
+ 0x1C, 0x8000,
+ 0x26, 0x000F,
+ 0x28, 0x0201,
+ 0x2C, 0xBB80,
+ 0x32, 0xBB80,
+ 0x7C, 0x8384,
+ 0x7E, 0x7644,
+ (unsigned short)-1
+ };
+ unsigned int tmp;
+
+ /* Cold reset */
+ tmp = (snd_ice1712_gpio_read(ice) | AUREON_AC97_RESET) & ~AUREON_AC97_DATA_MASK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(3);
+
+ tmp &= ~AUREON_AC97_RESET;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(3);
+
+ tmp |= AUREON_AC97_RESET;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(3);
+
+ memset(&spec->stac9744, 0, sizeof(spec->stac9744));
+ for (i = 0; ac97_defaults[i] != (unsigned short)-1; i += 2)
+ spec->stac9744[(ac97_defaults[i]) >> 1] = ac97_defaults[i+1];
+
+ /* Unmute AC'97 master volume permanently - muting is done by WM8770 */
+ aureon_ac97_write(ice, AC97_MASTER, 0x0000);
+
+ return 0;
+}
+
+#define AUREON_AC97_STEREO 0x80
+
+/*
+ * AC'97 volume controls
+ */
+static int aureon_ac97_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = kcontrol->private_value & AUREON_AC97_STEREO ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 31;
+ return 0;
+}
+
+static int aureon_ac97_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short vol;
+
+ mutex_lock(&ice->gpio_mutex);
+
+ vol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F);
+ ucontrol->value.integer.value[0] = 0x1F - (vol & 0x1F);
+ if (kcontrol->private_value & AUREON_AC97_STEREO)
+ ucontrol->value.integer.value[1] = 0x1F - ((vol >> 8) & 0x1F);
+
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int aureon_ac97_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+
+ ovol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F);
+ nvol = (0x1F - ucontrol->value.integer.value[0]) & 0x001F;
+ if (kcontrol->private_value & AUREON_AC97_STEREO)
+ nvol |= ((0x1F - ucontrol->value.integer.value[1]) << 8) & 0x1F00;
+ nvol |= ovol & ~0x1F1F;
+
+ change = (ovol != nvol);
+ if (change)
+ aureon_ac97_write(ice, kcontrol->private_value & 0x7F, nvol);
+
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * AC'97 mute controls
+ */
+#define aureon_ac97_mute_info snd_ctl_boolean_mono_info
+
+static int aureon_ac97_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+
+ ucontrol->value.integer.value[0] = aureon_ac97_read(ice,
+ kcontrol->private_value & 0x7F) & 0x8000 ? 0 : 1;
+
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int aureon_ac97_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+
+ ovol = aureon_ac97_read(ice, kcontrol->private_value & 0x7F);
+ nvol = (ucontrol->value.integer.value[0] ? 0x0000 : 0x8000) | (ovol & ~0x8000);
+
+ change = (ovol != nvol);
+ if (change)
+ aureon_ac97_write(ice, kcontrol->private_value & 0x7F, nvol);
+
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * AC'97 mute controls
+ */
+#define aureon_ac97_micboost_info snd_ctl_boolean_mono_info
+
+static int aureon_ac97_micboost_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+
+ ucontrol->value.integer.value[0] = aureon_ac97_read(ice, AC97_MIC) & 0x0020 ? 0 : 1;
+
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int aureon_ac97_micboost_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+
+ ovol = aureon_ac97_read(ice, AC97_MIC);
+ nvol = (ucontrol->value.integer.value[0] ? 0x0000 : 0x0020) | (ovol & ~0x0020);
+
+ change = (ovol != nvol);
+ if (change)
+ aureon_ac97_write(ice, AC97_MIC, nvol);
+
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * write data in the SPI mode
+ */
+static void aureon_spi_write(struct snd_ice1712 *ice, unsigned int cs, unsigned int data, int bits)
+{
+ unsigned int tmp;
+ int i;
+ unsigned int mosi, clk;
+
+ tmp = snd_ice1712_gpio_read(ice);
+
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT ||
+ ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT) {
+ snd_ice1712_gpio_set_mask(ice, ~(PRODIGY_SPI_MOSI|PRODIGY_SPI_CLK|PRODIGY_WM_CS));
+ mosi = PRODIGY_SPI_MOSI;
+ clk = PRODIGY_SPI_CLK;
+ } else {
+ snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RW|AUREON_SPI_MOSI|AUREON_SPI_CLK|
+ AUREON_WM_CS|AUREON_CS8415_CS));
+ mosi = AUREON_SPI_MOSI;
+ clk = AUREON_SPI_CLK;
+
+ tmp |= AUREON_WM_RW;
+ }
+
+ tmp &= ~cs;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ for (i = bits - 1; i >= 0; i--) {
+ tmp &= ~clk;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ if (data & (1 << i))
+ tmp |= mosi;
+ else
+ tmp &= ~mosi;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= clk;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+
+ tmp &= ~clk;
+ tmp |= cs;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= clk;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+}
+
+/*
+ * Read data in SPI mode
+ */
+static void aureon_spi_read(struct snd_ice1712 *ice, unsigned int cs,
+ unsigned int data, int bits, unsigned char *buffer, int size)
+{
+ int i, j;
+ unsigned int tmp;
+
+ tmp = (snd_ice1712_gpio_read(ice) & ~AUREON_SPI_CLK) | AUREON_CS8415_CS|AUREON_WM_CS;
+ snd_ice1712_gpio_write(ice, tmp);
+ tmp &= ~cs;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ for (i = bits-1; i >= 0; i--) {
+ if (data & (1 << i))
+ tmp |= AUREON_SPI_MOSI;
+ else
+ tmp &= ~AUREON_SPI_MOSI;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ tmp |= AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+
+ for (j = 0; j < size; j++) {
+ unsigned char outdata = 0;
+ for (i = 7; i >= 0; i--) {
+ tmp = snd_ice1712_gpio_read(ice);
+ outdata <<= 1;
+ outdata |= (tmp & AUREON_SPI_MISO) ? 1 : 0;
+ udelay(1);
+
+ tmp |= AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ tmp &= ~AUREON_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+ buffer[j] = outdata;
+ }
+
+ tmp |= cs;
+ snd_ice1712_gpio_write(ice, tmp);
+}
+
+static unsigned char aureon_cs8415_get(struct snd_ice1712 *ice, int reg)
+{
+ unsigned char val;
+ aureon_spi_write(ice, AUREON_CS8415_CS, 0x2000 | reg, 16);
+ aureon_spi_read(ice, AUREON_CS8415_CS, 0x21, 8, &val, 1);
+ return val;
+}
+
+static void aureon_cs8415_read(struct snd_ice1712 *ice, int reg,
+ unsigned char *buffer, int size)
+{
+ aureon_spi_write(ice, AUREON_CS8415_CS, 0x2000 | reg, 16);
+ aureon_spi_read(ice, AUREON_CS8415_CS, 0x21, 8, buffer, size);
+}
+
+static void aureon_cs8415_put(struct snd_ice1712 *ice, int reg,
+ unsigned char val)
+{
+ aureon_spi_write(ice, AUREON_CS8415_CS, 0x200000 | (reg << 8) | val, 24);
+}
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+ reg <<= 1;
+ return ((unsigned short)ice->akm[0].images[reg] << 8) |
+ ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ aureon_spi_write(ice,
+ ((ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT ||
+ ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT) ?
+ PRODIGY_WM_CS : AUREON_WM_CS),
+ (reg << 9) | (val & 0x1ff), 16);
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ wm_put_nocache(ice, reg, val);
+ reg <<= 1;
+ ice->akm[0].images[reg] = val >> 8;
+ ice->akm[0].images[reg + 1] = val;
+}
+
+/*
+ */
+#define aureon_mono_bool_info snd_ctl_boolean_mono_info
+
+/*
+ * AC'97 master playback mute controls (Mute on WM8770 chip)
+ */
+#define aureon_ac97_mmute_info snd_ctl_boolean_mono_info
+
+static int aureon_ac97_mmute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX1) >> 1) & 0x01;
+
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int aureon_ac97_mmute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+
+ ovol = wm_get(ice, WM_OUT_MUX1);
+ nvol = (ovol & ~0x02) | (ucontrol->value.integer.value[0] ? 0x02 : 0x00);
+ change = (ovol != nvol);
+ if (change)
+ wm_put(ice, WM_OUT_MUX1, nvol);
+
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -10000, 100, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_pcm, -6400, 50, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_adc, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_ac97_master, -4650, 150, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_ac97_gain, -3450, 150, 0);
+
+#define WM_VOL_MAX 100
+#define WM_VOL_CNT 101 /* 0dB .. -100dB */
+#define WM_VOL_MUTE 0x8000
+
+static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index, unsigned short vol, unsigned short master)
+{
+ unsigned char nvol;
+
+ if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE)) {
+ nvol = 0;
+ } else {
+ nvol = ((vol % WM_VOL_CNT) * (master % WM_VOL_CNT)) /
+ WM_VOL_MAX;
+ nvol += 0x1b;
+ }
+
+ wm_put(ice, index, nvol);
+ wm_put_nocache(ice, index, 0x180 | nvol);
+}
+
+/*
+ * DAC mute control
+ */
+#define wm_pcm_mute_info snd_ctl_boolean_mono_info
+
+static int wm_pcm_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ? 0 : 1;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_pcm_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short nval, oval;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+ oval = wm_get(ice, WM_MUTE);
+ nval = (oval & ~0x10) | (ucontrol->value.integer.value[0] ? 0 : 0x10);
+ change = (oval != nval);
+ if (change)
+ wm_put(ice, WM_MUTE, nval);
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * Master volume attenuation mixer control
+ */
+static int wm_master_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = WM_VOL_MAX;
+ return 0;
+}
+
+static int wm_master_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ int i;
+ for (i = 0; i < 2; i++)
+ ucontrol->value.integer.value[i] =
+ spec->master[i] & ~WM_VOL_MUTE;
+ return 0;
+}
+
+static int wm_master_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ int ch, change = 0;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (ch = 0; ch < 2; ch++) {
+ unsigned int vol = ucontrol->value.integer.value[ch];
+ if (vol > WM_VOL_MAX)
+ vol = WM_VOL_MAX;
+ vol |= spec->master[ch] & WM_VOL_MUTE;
+ if (vol != spec->master[ch]) {
+ int dac;
+ spec->master[ch] = vol;
+ for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+ wm_set_vol(ice, WM_DAC_ATTEN + dac + ch,
+ spec->vol[dac + ch],
+ spec->master[ch]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * DAC volume attenuation mixer control
+ */
+static int wm_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ int voices = kcontrol->private_value >> 8;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = voices;
+ uinfo->value.integer.min = 0; /* mute (-101dB) */
+ uinfo->value.integer.max = WM_VOL_MAX; /* 0dB */
+ return 0;
+}
+
+static int wm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ int i, ofs, voices;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xff;
+ for (i = 0; i < voices; i++)
+ ucontrol->value.integer.value[i] =
+ spec->vol[ofs+i] & ~WM_VOL_MUTE;
+ return 0;
+}
+
+static int wm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ int i, idx, ofs, voices;
+ int change = 0;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xff;
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < voices; i++) {
+ unsigned int vol = ucontrol->value.integer.value[i];
+ if (vol > WM_VOL_MAX)
+ vol = WM_VOL_MAX;
+ vol |= spec->vol[ofs+i] & WM_VOL_MUTE;
+ if (vol != spec->vol[ofs+i]) {
+ spec->vol[ofs+i] = vol;
+ idx = WM_DAC_ATTEN + ofs + i;
+ wm_set_vol(ice, idx, spec->vol[ofs + i],
+ spec->master[i]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * WM8770 mute control
+ */
+static int wm_mute_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = kcontrol->private_value >> 8;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int wm_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ int voices, ofs, i;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xFF;
+
+ for (i = 0; i < voices; i++)
+ ucontrol->value.integer.value[i] =
+ (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1;
+ return 0;
+}
+
+static int wm_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ int change = 0, voices, ofs, i;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xFF;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < voices; i++) {
+ int val = (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1;
+ if (ucontrol->value.integer.value[i] != val) {
+ spec->vol[ofs + i] &= ~WM_VOL_MUTE;
+ spec->vol[ofs + i] |=
+ ucontrol->value.integer.value[i] ? 0 : WM_VOL_MUTE;
+ wm_set_vol(ice, ofs + i, spec->vol[ofs + i],
+ spec->master[i]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * WM8770 master mute control
+ */
+#define wm_master_mute_info snd_ctl_boolean_stereo_info
+
+static int wm_master_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+
+ ucontrol->value.integer.value[0] =
+ (spec->master[0] & WM_VOL_MUTE) ? 0 : 1;
+ ucontrol->value.integer.value[1] =
+ (spec->master[1] & WM_VOL_MUTE) ? 0 : 1;
+ return 0;
+}
+
+static int wm_master_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ int change = 0, i;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < 2; i++) {
+ int val = (spec->master[i] & WM_VOL_MUTE) ? 0 : 1;
+ if (ucontrol->value.integer.value[i] != val) {
+ int dac;
+ spec->master[i] &= ~WM_VOL_MUTE;
+ spec->master[i] |=
+ ucontrol->value.integer.value[i] ? 0 : WM_VOL_MUTE;
+ for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+ wm_set_vol(ice, WM_DAC_ATTEN + dac + i,
+ spec->vol[dac + i],
+ spec->master[i]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/* digital master volume */
+#define PCM_0dB 0xff
+#define PCM_RES 128 /* -64dB */
+#define PCM_MIN (PCM_0dB - PCM_RES)
+static int wm_pcm_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0; /* mute (-64dB) */
+ uinfo->value.integer.max = PCM_RES; /* 0dB */
+ return 0;
+}
+
+static int wm_pcm_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ mutex_lock(&ice->gpio_mutex);
+ val = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+ val = val > PCM_MIN ? (val - PCM_MIN) : 0;
+ ucontrol->value.integer.value[0] = val;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_pcm_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int change = 0;
+
+ nvol = ucontrol->value.integer.value[0];
+ if (nvol > PCM_RES)
+ return -EINVAL;
+ snd_ice1712_save_gpio_status(ice);
+ nvol = (nvol ? (nvol + PCM_MIN) : 0) & 0xff;
+ ovol = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+ if (ovol != nvol) {
+ wm_put(ice, WM_DAC_DIG_MASTER_ATTEN, nvol); /* prelatch */
+ wm_put_nocache(ice, WM_DAC_DIG_MASTER_ATTEN, nvol | 0x100); /* update */
+ change = 1;
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * ADC mute control
+ */
+#define wm_adc_mute_info snd_ctl_boolean_stereo_info
+
+static int wm_adc_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+ int i;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ val = wm_get(ice, WM_ADC_GAIN + i);
+ ucontrol->value.integer.value[i] = ~val>>5 & 0x1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short new, old;
+ int i, change = 0;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < 2; i++) {
+ old = wm_get(ice, WM_ADC_GAIN + i);
+ new = (~ucontrol->value.integer.value[i]<<5&0x20) | (old&~0x20);
+ if (new != old) {
+ wm_put(ice, WM_ADC_GAIN + i, new);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * ADC gain mixer control
+ */
+static int wm_adc_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* -12dB */
+ uinfo->value.integer.max = 0x1f; /* 19dB */
+ return 0;
+}
+
+static int wm_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int i, idx;
+ unsigned short vol;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ idx = WM_ADC_GAIN + i;
+ vol = wm_get(ice, idx) & 0x1f;
+ ucontrol->value.integer.value[i] = vol;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int i, idx;
+ unsigned short ovol, nvol;
+ int change = 0;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < 2; i++) {
+ idx = WM_ADC_GAIN + i;
+ nvol = ucontrol->value.integer.value[i] & 0x1f;
+ ovol = wm_get(ice, idx);
+ if ((ovol & 0x1f) != nvol) {
+ wm_put(ice, idx, nvol | (ovol & ~0x1f));
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * ADC input mux mixer control
+ */
+static int wm_adc_mux_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "CD", /* AIN1 */
+ "Aux", /* AIN2 */
+ "Line", /* AIN3 */
+ "Mic", /* AIN4 */
+ "AC97" /* AIN5 */
+ };
+ static const char * const universe_texts[] = {
+ "Aux1", /* AIN1 */
+ "CD", /* AIN2 */
+ "Phono", /* AIN3 */
+ "Line", /* AIN4 */
+ "Aux2", /* AIN5 */
+ "Mic", /* AIN6 */
+ "Aux3", /* AIN7 */
+ "AC97" /* AIN8 */
+ };
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON71_UNIVERSE)
+ return snd_ctl_enum_info(uinfo, 2, 8, universe_texts);
+ else
+ return snd_ctl_enum_info(uinfo, 2, 5, texts);
+}
+
+static int wm_adc_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ mutex_lock(&ice->gpio_mutex);
+ val = wm_get(ice, WM_ADC_MUX);
+ ucontrol->value.enumerated.item[0] = val & 7;
+ ucontrol->value.enumerated.item[1] = (val >> 4) & 7;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short oval, nval;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+ oval = wm_get(ice, WM_ADC_MUX);
+ nval = oval & ~0x77;
+ nval |= ucontrol->value.enumerated.item[0] & 7;
+ nval |= (ucontrol->value.enumerated.item[1] & 7) << 4;
+ change = (oval != nval);
+ if (change)
+ wm_put(ice, WM_ADC_MUX, nval);
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * CS8415 Input mux
+ */
+static int aureon_cs8415_mux_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ static const char * const aureon_texts[] = {
+ "CD", /* RXP0 */
+ "Optical" /* RXP1 */
+ };
+ static const char * const prodigy_texts[] = {
+ "CD",
+ "Coax"
+ };
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71)
+ return snd_ctl_enum_info(uinfo, 1, 2, prodigy_texts);
+ else
+ return snd_ctl_enum_info(uinfo, 1, 2, aureon_texts);
+}
+
+static int aureon_cs8415_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+
+ /* snd_ice1712_save_gpio_status(ice); */
+ /* val = aureon_cs8415_get(ice, CS8415_CTRL2); */
+ ucontrol->value.enumerated.item[0] = spec->cs8415_mux;
+ /* snd_ice1712_restore_gpio_status(ice); */
+ return 0;
+}
+
+static int aureon_cs8415_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct aureon_spec *spec = ice->spec;
+ unsigned short oval, nval;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+ oval = aureon_cs8415_get(ice, CS8415_CTRL2);
+ nval = oval & ~0x07;
+ nval |= ucontrol->value.enumerated.item[0] & 7;
+ change = (oval != nval);
+ if (change)
+ aureon_cs8415_put(ice, CS8415_CTRL2, nval);
+ snd_ice1712_restore_gpio_status(ice);
+ spec->cs8415_mux = ucontrol->value.enumerated.item[0];
+ return change;
+}
+
+static int aureon_cs8415_rate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 192000;
+ return 0;
+}
+
+static int aureon_cs8415_rate_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char ratio;
+ ratio = aureon_cs8415_get(ice, CS8415_RATIO);
+ ucontrol->value.integer.value[0] = (int)((unsigned int)ratio * 750);
+ return 0;
+}
+
+/*
+ * CS8415A Mute
+ */
+#define aureon_cs8415_mute_info snd_ctl_boolean_mono_info
+
+static int aureon_cs8415_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ snd_ice1712_save_gpio_status(ice);
+ ucontrol->value.integer.value[0] = (aureon_cs8415_get(ice, CS8415_CTRL1) & 0x20) ? 0 : 1;
+ snd_ice1712_restore_gpio_status(ice);
+ return 0;
+}
+
+static int aureon_cs8415_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char oval, nval;
+ int change;
+ snd_ice1712_save_gpio_status(ice);
+ oval = aureon_cs8415_get(ice, CS8415_CTRL1);
+ if (ucontrol->value.integer.value[0])
+ nval = oval & ~0x20;
+ else
+ nval = oval | 0x20;
+ change = (oval != nval);
+ if (change)
+ aureon_cs8415_put(ice, CS8415_CTRL1, nval);
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * CS8415A Q-Sub info
+ */
+static int aureon_cs8415_qsub_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = 10;
+ return 0;
+}
+
+static int aureon_cs8415_qsub_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ snd_ice1712_save_gpio_status(ice);
+ aureon_cs8415_read(ice, CS8415_QSUB, ucontrol->value.bytes.data, 10);
+ snd_ice1712_restore_gpio_status(ice);
+
+ return 0;
+}
+
+static int aureon_cs8415_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int aureon_cs8415_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ memset(ucontrol->value.iec958.status, 0xFF, 24);
+ return 0;
+}
+
+static int aureon_cs8415_spdif_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ snd_ice1712_save_gpio_status(ice);
+ aureon_cs8415_read(ice, CS8415_C_BUFFER, ucontrol->value.iec958.status, 24);
+ snd_ice1712_restore_gpio_status(ice);
+ return 0;
+}
+
+/*
+ * Headphone Amplifier
+ */
+static int aureon_set_headphone_amp(struct snd_ice1712 *ice, int enable)
+{
+ unsigned int tmp, tmp2;
+
+ tmp2 = tmp = snd_ice1712_gpio_read(ice);
+ if (enable)
+ if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+ ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT)
+ tmp |= AUREON_HP_SEL;
+ else
+ tmp |= PRODIGY_HP_SEL;
+ else
+ if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+ ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT)
+ tmp &= ~AUREON_HP_SEL;
+ else
+ tmp &= ~PRODIGY_HP_SEL;
+ if (tmp != tmp2) {
+ snd_ice1712_gpio_write(ice, tmp);
+ return 1;
+ }
+ return 0;
+}
+
+static int aureon_get_headphone_amp(struct snd_ice1712 *ice)
+{
+ unsigned int tmp = snd_ice1712_gpio_read(ice);
+
+ return (tmp & AUREON_HP_SEL) != 0;
+}
+
+#define aureon_hpamp_info snd_ctl_boolean_mono_info
+
+static int aureon_hpamp_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = aureon_get_headphone_amp(ice);
+ return 0;
+}
+
+
+static int aureon_hpamp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ return aureon_set_headphone_amp(ice, ucontrol->value.integer.value[0]);
+}
+
+/*
+ * Deemphasis
+ */
+
+#define aureon_deemp_info snd_ctl_boolean_mono_info
+
+static int aureon_deemp_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL2) & 0xf) == 0xf;
+ return 0;
+}
+
+static int aureon_deemp_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int temp, temp2;
+ temp2 = temp = wm_get(ice, WM_DAC_CTRL2);
+ if (ucontrol->value.integer.value[0])
+ temp |= 0xf;
+ else
+ temp &= ~0xf;
+ if (temp != temp2) {
+ wm_put(ice, WM_DAC_CTRL2, temp);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * ADC Oversampling
+ */
+static int aureon_oversampling_info(struct snd_kcontrol *k, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[2] = { "128x", "64x" };
+
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
+}
+
+static int aureon_oversampling_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = (wm_get(ice, WM_MASTER) & 0x8) == 0x8;
+ return 0;
+}
+
+static int aureon_oversampling_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ int temp, temp2;
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ temp2 = temp = wm_get(ice, WM_MASTER);
+
+ if (ucontrol->value.enumerated.item[0])
+ temp |= 0x8;
+ else
+ temp &= ~0x8;
+
+ if (temp != temp2) {
+ wm_put(ice, WM_MASTER, temp);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * mixers
+ */
+
+static const struct snd_kcontrol_new aureon_dac_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Switch",
+ .info = wm_master_mute_info,
+ .get = wm_master_mute_get,
+ .put = wm_master_mute_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Master Playback Volume",
+ .info = wm_master_vol_info,
+ .get = wm_master_vol_get,
+ .put = wm_master_vol_put,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Front Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (2 << 8) | 0
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Front Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (2 << 8) | 0,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Rear Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (2 << 8) | 2
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Rear Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (2 << 8) | 2,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Center Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (1 << 8) | 4
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Center Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (1 << 8) | 4,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "LFE Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (1 << 8) | 5
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "LFE Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (1 << 8) | 5,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Side Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (2 << 8) | 6
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Side Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (2 << 8) | 6,
+ .tlv = { .p = db_scale_wm_dac }
+ }
+};
+
+static const struct snd_kcontrol_new wm_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Switch",
+ .info = wm_pcm_mute_info,
+ .get = wm_pcm_mute_get,
+ .put = wm_pcm_mute_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "PCM Playback Volume",
+ .info = wm_pcm_vol_info,
+ .get = wm_pcm_vol_get,
+ .put = wm_pcm_vol_put,
+ .tlv = { .p = db_scale_wm_pcm }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Switch",
+ .info = wm_adc_mute_info,
+ .get = wm_adc_mute_get,
+ .put = wm_adc_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Capture Volume",
+ .info = wm_adc_vol_info,
+ .get = wm_adc_vol_get,
+ .put = wm_adc_vol_put,
+ .tlv = { .p = db_scale_wm_adc }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = wm_adc_mux_info,
+ .get = wm_adc_mux_get,
+ .put = wm_adc_mux_put,
+ .private_value = 5
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "External Amplifier",
+ .info = aureon_hpamp_info,
+ .get = aureon_hpamp_get,
+ .put = aureon_hpamp_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DAC Deemphasis Switch",
+ .info = aureon_deemp_info,
+ .get = aureon_deemp_get,
+ .put = aureon_deemp_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "ADC Oversampling",
+ .info = aureon_oversampling_info,
+ .get = aureon_oversampling_get,
+ .put = aureon_oversampling_put
+ }
+};
+
+static const struct snd_kcontrol_new ac97_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "AC97 Playback Switch",
+ .info = aureon_ac97_mmute_info,
+ .get = aureon_ac97_mmute_get,
+ .put = aureon_ac97_mmute_put,
+ .private_value = AC97_MASTER
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "AC97 Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_MASTER|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_master }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "CD Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_CD
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "CD Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_CD|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Aux Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_AUX,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Aux Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_AUX|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_LINE
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Line Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_LINE|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_MIC
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Mic Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_MIC,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic Boost (+20dB)",
+ .info = aureon_ac97_micboost_info,
+ .get = aureon_ac97_micboost_get,
+ .put = aureon_ac97_micboost_put
+ }
+};
+
+static const struct snd_kcontrol_new universe_ac97_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "AC97 Playback Switch",
+ .info = aureon_ac97_mmute_info,
+ .get = aureon_ac97_mmute_get,
+ .put = aureon_ac97_mmute_put,
+ .private_value = AC97_MASTER
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "AC97 Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_MASTER|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_master }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "CD Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_AUX
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "CD Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_AUX|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Phono Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_CD
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Phono Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_CD|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_LINE
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Line Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_LINE|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_MIC
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Mic Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_MIC,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Mic Boost (+20dB)",
+ .info = aureon_ac97_micboost_info,
+ .get = aureon_ac97_micboost_get,
+ .put = aureon_ac97_micboost_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Aux Playback Switch",
+ .info = aureon_ac97_mute_info,
+ .get = aureon_ac97_mute_get,
+ .put = aureon_ac97_mute_put,
+ .private_value = AC97_VIDEO,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Aux Playback Volume",
+ .info = aureon_ac97_vol_info,
+ .get = aureon_ac97_vol_get,
+ .put = aureon_ac97_vol_put,
+ .private_value = AC97_VIDEO|AUREON_AC97_STEREO,
+ .tlv = { .p = db_scale_ac97_gain }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Aux Source",
+ .info = aureon_universe_inmux_info,
+ .get = aureon_universe_inmux_get,
+ .put = aureon_universe_inmux_put
+ }
+
+};
+
+static const struct snd_kcontrol_new cs8415_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, SWITCH),
+ .info = aureon_cs8415_mute_info,
+ .get = aureon_cs8415_mute_get,
+ .put = aureon_cs8415_mute_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Source",
+ .info = aureon_cs8415_mux_info,
+ .get = aureon_cs8415_mux_get,
+ .put = aureon_cs8415_mux_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("Q-subcode ", CAPTURE, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = aureon_cs8415_qsub_info,
+ .get = aureon_cs8415_qsub_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = aureon_cs8415_spdif_info,
+ .get = aureon_cs8415_mask_get
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = aureon_cs8415_spdif_info,
+ .get = aureon_cs8415_spdif_get
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) "Rate",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = aureon_cs8415_rate_info,
+ .get = aureon_cs8415_rate_get
+ }
+};
+
+static int aureon_add_controls(struct snd_ice1712 *ice)
+{
+ unsigned int i, counts;
+ int err;
+
+ counts = ARRAY_SIZE(aureon_dac_controls);
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY)
+ counts -= 2; /* no side */
+ for (i = 0; i < counts; i++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&aureon_dac_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm_controls); i++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&wm_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON71_UNIVERSE) {
+ for (i = 0; i < ARRAY_SIZE(universe_ac97_controls); i++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&universe_ac97_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+ } else if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+ ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) {
+ for (i = 0; i < ARRAY_SIZE(ac97_controls); i++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&ac97_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+ }
+
+ if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+ ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) {
+ unsigned char id;
+ snd_ice1712_save_gpio_status(ice);
+ id = aureon_cs8415_get(ice, CS8415_ID);
+ snd_ice1712_restore_gpio_status(ice);
+ if (id != 0x41)
+ dev_info(ice->card->dev,
+ "No CS8415 chip. Skipping CS8415 controls.\n");
+ else {
+ for (i = 0; i < ARRAY_SIZE(cs8415_controls); i++) {
+ struct snd_kcontrol *kctl;
+ kctl = snd_ctl_new1(&cs8415_controls[i], ice);
+ if (i > 1)
+ kctl->id.device = ice->pcm->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * reset the chip
+ */
+static int aureon_reset(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm_inits_aureon[] = {
+ /* These come first to reduce init pop noise */
+ 0x1b, 0x044, /* ADC Mux (AC'97 source) */
+ 0x1c, 0x00B, /* Out Mux1 (VOUT1 = DAC+AUX, VOUT2 = DAC) */
+ 0x1d, 0x009, /* Out Mux2 (VOUT2 = DAC, VOUT3 = DAC) */
+
+ 0x18, 0x000, /* All power-up */
+
+ 0x16, 0x122, /* I2S, normal polarity, 24bit */
+ 0x17, 0x022, /* 256fs, slave mode */
+ 0x00, 0, /* DAC1 analog mute */
+ 0x01, 0, /* DAC2 analog mute */
+ 0x02, 0, /* DAC3 analog mute */
+ 0x03, 0, /* DAC4 analog mute */
+ 0x04, 0, /* DAC5 analog mute */
+ 0x05, 0, /* DAC6 analog mute */
+ 0x06, 0, /* DAC7 analog mute */
+ 0x07, 0, /* DAC8 analog mute */
+ 0x08, 0x100, /* master analog mute */
+ 0x09, 0xff, /* DAC1 digital full */
+ 0x0a, 0xff, /* DAC2 digital full */
+ 0x0b, 0xff, /* DAC3 digital full */
+ 0x0c, 0xff, /* DAC4 digital full */
+ 0x0d, 0xff, /* DAC5 digital full */
+ 0x0e, 0xff, /* DAC6 digital full */
+ 0x0f, 0xff, /* DAC7 digital full */
+ 0x10, 0xff, /* DAC8 digital full */
+ 0x11, 0x1ff, /* master digital full */
+ 0x12, 0x000, /* phase normal */
+ 0x13, 0x090, /* unmute DAC L/R */
+ 0x14, 0x000, /* all unmute */
+ 0x15, 0x000, /* no deemphasis, no ZFLG */
+ 0x19, 0x000, /* -12dB ADC/L */
+ 0x1a, 0x000, /* -12dB ADC/R */
+ (unsigned short)-1
+ };
+ static const unsigned short wm_inits_prodigy[] = {
+
+ /* These come first to reduce init pop noise */
+ 0x1b, 0x000, /* ADC Mux */
+ 0x1c, 0x009, /* Out Mux1 */
+ 0x1d, 0x009, /* Out Mux2 */
+
+ 0x18, 0x000, /* All power-up */
+
+ 0x16, 0x022, /* I2S, normal polarity, 24bit, high-pass on */
+ 0x17, 0x006, /* 128fs, slave mode */
+
+ 0x00, 0, /* DAC1 analog mute */
+ 0x01, 0, /* DAC2 analog mute */
+ 0x02, 0, /* DAC3 analog mute */
+ 0x03, 0, /* DAC4 analog mute */
+ 0x04, 0, /* DAC5 analog mute */
+ 0x05, 0, /* DAC6 analog mute */
+ 0x06, 0, /* DAC7 analog mute */
+ 0x07, 0, /* DAC8 analog mute */
+ 0x08, 0x100, /* master analog mute */
+
+ 0x09, 0x7f, /* DAC1 digital full */
+ 0x0a, 0x7f, /* DAC2 digital full */
+ 0x0b, 0x7f, /* DAC3 digital full */
+ 0x0c, 0x7f, /* DAC4 digital full */
+ 0x0d, 0x7f, /* DAC5 digital full */
+ 0x0e, 0x7f, /* DAC6 digital full */
+ 0x0f, 0x7f, /* DAC7 digital full */
+ 0x10, 0x7f, /* DAC8 digital full */
+ 0x11, 0x1FF, /* master digital full */
+
+ 0x12, 0x000, /* phase normal */
+ 0x13, 0x090, /* unmute DAC L/R */
+ 0x14, 0x000, /* all unmute */
+ 0x15, 0x000, /* no deemphasis, no ZFLG */
+
+ 0x19, 0x000, /* -12dB ADC/L */
+ 0x1a, 0x000, /* -12dB ADC/R */
+ (unsigned short)-1
+
+ };
+ static const unsigned short cs_inits[] = {
+ 0x0441, /* RUN */
+ 0x0180, /* no mute, OMCK output on RMCK pin */
+ 0x0201, /* S/PDIF source on RXP1 */
+ 0x0605, /* slave, 24bit, MSB on second OSCLK, SDOUT for right channel when OLRCK is high */
+ (unsigned short)-1
+ };
+ unsigned int tmp;
+ const unsigned short *p;
+ int err;
+ struct aureon_spec *spec = ice->spec;
+
+ err = aureon_ac97_init(ice);
+ if (err != 0)
+ return err;
+
+ snd_ice1712_gpio_set_dir(ice, 0x5fffff); /* fix this for the time being */
+
+ /* reset the wm codec as the SPI mode */
+ snd_ice1712_save_gpio_status(ice);
+ snd_ice1712_gpio_set_mask(ice, ~(AUREON_WM_RESET|AUREON_WM_CS|AUREON_CS8415_CS|AUREON_HP_SEL));
+
+ tmp = snd_ice1712_gpio_read(ice);
+ tmp &= ~AUREON_WM_RESET;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= AUREON_WM_CS | AUREON_CS8415_CS;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= AUREON_WM_RESET;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ /* initialize WM8770 codec */
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71 ||
+ ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71LT ||
+ ice->eeprom.subvendor == VT1724_SUBDEVICE_PRODIGY71XT)
+ p = wm_inits_prodigy;
+ else
+ p = wm_inits_aureon;
+ for (; *p != (unsigned short)-1; p += 2)
+ wm_put(ice, p[0], p[1]);
+
+ /* initialize CS8415A codec */
+ if (ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71LT &&
+ ice->eeprom.subvendor != VT1724_SUBDEVICE_PRODIGY71XT) {
+ for (p = cs_inits; *p != (unsigned short)-1; p++)
+ aureon_spi_write(ice, AUREON_CS8415_CS, *p | 0x200000, 24);
+ spec->cs8415_mux = 1;
+
+ aureon_set_headphone_amp(ice, 1);
+ }
+
+ snd_ice1712_restore_gpio_status(ice);
+
+ /* initialize PCA9554 pin directions & set default input */
+ aureon_pca9554_write(ice, PCA9554_DIR, 0x00);
+ aureon_pca9554_write(ice, PCA9554_OUT, 0x00); /* internal AUX */
+ return 0;
+}
+
+/*
+ * suspend/resume
+ */
+#ifdef CONFIG_PM_SLEEP
+static int aureon_resume(struct snd_ice1712 *ice)
+{
+ struct aureon_spec *spec = ice->spec;
+ int err, i;
+
+ err = aureon_reset(ice);
+ if (err != 0)
+ return err;
+
+ /* workaround for poking volume with alsamixer after resume:
+ * just set stored volume again */
+ for (i = 0; i < ice->num_total_dacs; i++)
+ wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]);
+ return 0;
+}
+#endif
+
+/*
+ * initialize the chip
+ */
+static int aureon_init(struct snd_ice1712 *ice)
+{
+ struct aureon_spec *spec;
+ int i, err;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_AUREON51_SKY) {
+ ice->num_total_dacs = 6;
+ ice->num_total_adcs = 2;
+ } else {
+ /* aureon 7.1 and prodigy 7.1 */
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 2;
+ }
+
+ /* to remember the register values of CS8415 */
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (!ice->akm)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ err = aureon_reset(ice);
+ if (err != 0)
+ return err;
+
+ spec->master[0] = WM_VOL_MUTE;
+ spec->master[1] = WM_VOL_MUTE;
+ for (i = 0; i < ice->num_total_dacs; i++) {
+ spec->vol[i] = WM_VOL_MUTE;
+ wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]);
+ }
+
+#ifdef CONFIG_PM_SLEEP
+ ice->pm_resume = aureon_resume;
+ ice->pm_suspend_enabled = 1;
+#endif
+
+ return 0;
+}
+
+
+/*
+ * Aureon boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static const unsigned char aureon51_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x0a, /* clock 512, spdif-in/ADC, 3DACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x5f,
+ [ICE_EEP2_GPIO_MASK] = 0x00,
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0x00,
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+static const unsigned char aureon71_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x0b, /* clock 512, spdif-in/ADC, 4DACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x5f,
+ [ICE_EEP2_GPIO_MASK] = 0x00,
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0x00,
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+#define prodigy71_eeprom aureon71_eeprom
+
+static const unsigned char aureon71_universe_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x2b, /* clock 512, mpu401, spdif-in/ADC,
+ * 4DACs
+ */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x5f,
+ [ICE_EEP2_GPIO_MASK] = 0x00,
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0x00,
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+static const unsigned char prodigy71lt_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x4b, /* clock 384, spdif-in/ADC, 4DACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x5f,
+ [ICE_EEP2_GPIO_MASK] = 0x00,
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0x00,
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+#define prodigy71xt_eeprom prodigy71lt_eeprom
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_aureon_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_AUREON51_SKY,
+ .name = "Terratec Aureon 5.1-Sky",
+ .model = "aureon51",
+ .chip_init = aureon_init,
+ .build_controls = aureon_add_controls,
+ .eeprom_size = sizeof(aureon51_eeprom),
+ .eeprom_data = aureon51_eeprom,
+ .driver = "Aureon51",
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_AUREON71_SPACE,
+ .name = "Terratec Aureon 7.1-Space",
+ .model = "aureon71",
+ .chip_init = aureon_init,
+ .build_controls = aureon_add_controls,
+ .eeprom_size = sizeof(aureon71_eeprom),
+ .eeprom_data = aureon71_eeprom,
+ .driver = "Aureon71",
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_AUREON71_UNIVERSE,
+ .name = "Terratec Aureon 7.1-Universe",
+ .model = "universe",
+ .chip_init = aureon_init,
+ .build_controls = aureon_add_controls,
+ .eeprom_size = sizeof(aureon71_universe_eeprom),
+ .eeprom_data = aureon71_universe_eeprom,
+ .driver = "Aureon71Univ", /* keep in 15 letters */
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_PRODIGY71,
+ .name = "Audiotrak Prodigy 7.1",
+ .model = "prodigy71",
+ .chip_init = aureon_init,
+ .build_controls = aureon_add_controls,
+ .eeprom_size = sizeof(prodigy71_eeprom),
+ .eeprom_data = prodigy71_eeprom,
+ .driver = "Prodigy71", /* should be identical with Aureon71 */
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_PRODIGY71LT,
+ .name = "Audiotrak Prodigy 7.1 LT",
+ .model = "prodigy71lt",
+ .chip_init = aureon_init,
+ .build_controls = aureon_add_controls,
+ .eeprom_size = sizeof(prodigy71lt_eeprom),
+ .eeprom_data = prodigy71lt_eeprom,
+ .driver = "Prodigy71LT",
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_PRODIGY71XT,
+ .name = "Audiotrak Prodigy 7.1 XT",
+ .model = "prodigy71xt",
+ .chip_init = aureon_init,
+ .build_controls = aureon_add_controls,
+ .eeprom_size = sizeof(prodigy71xt_eeprom),
+ .eeprom_data = prodigy71xt_eeprom,
+ .driver = "Prodigy71LT",
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/aureon.h b/sound/pci/ice1712/aureon.h
new file mode 100644
index 0000000000..de011495e2
--- /dev/null
+++ b/sound/pci/ice1712/aureon.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_AUREON_H
+#define __SOUND_AUREON_H
+
+/*
+ * ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Terratec Aureon cards
+ *
+ * Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
+ */
+
+#define AUREON_DEVICE_DESC "{Terratec,Aureon 5.1 Sky},"\
+ "{Terratec,Aureon 7.1 Space},"\
+ "{Terratec,Aureon 7.1 Universe}," \
+ "{AudioTrak,Prodigy 7.1}," \
+ "{AudioTrak,Prodigy 7.1 LT},"\
+ "{AudioTrak,Prodigy 7.1 XT},"
+
+#define VT1724_SUBDEVICE_AUREON51_SKY 0x3b154711 /* Aureon 5.1 Sky */
+#define VT1724_SUBDEVICE_AUREON71_SPACE 0x3b154511 /* Aureon 7.1 Space */
+#define VT1724_SUBDEVICE_AUREON71_UNIVERSE 0x3b155311 /* Aureon 7.1 Universe */
+#define VT1724_SUBDEVICE_PRODIGY71 0x33495345 /* PRODIGY 7.1 */
+#define VT1724_SUBDEVICE_PRODIGY71LT 0x32315441 /* PRODIGY 7.1 LT */
+#define VT1724_SUBDEVICE_PRODIGY71XT 0x36315441 /* PRODIGY 7.1 XT*/
+
+extern struct snd_ice1712_card_info snd_vt1724_aureon_cards[];
+
+/* GPIO bits */
+#define AUREON_CS8415_CS (1 << 22)
+#define AUREON_SPI_MISO (1 << 21)
+#define AUREON_WM_RESET (1 << 20)
+#define AUREON_SPI_CLK (1 << 19)
+#define AUREON_SPI_MOSI (1 << 18)
+#define AUREON_WM_RW (1 << 17)
+#define AUREON_AC97_RESET (1 << 16)
+#define AUREON_DIGITAL_SEL1 (1 << 15)
+#define AUREON_HP_SEL (1 << 14)
+#define AUREON_WM_CS (1 << 12)
+#define AUREON_AC97_COMMIT (1 << 11)
+#define AUREON_AC97_ADDR (1 << 10)
+#define AUREON_AC97_DATA_LOW (1 << 9)
+#define AUREON_AC97_DATA_HIGH (1 << 8)
+#define AUREON_AC97_DATA_MASK 0xFF
+
+#define PRODIGY_WM_CS (1 << 8)
+#define PRODIGY_SPI_MOSI (1 << 10)
+#define PRODIGY_SPI_CLK (1 << 9)
+#define PRODIGY_HP_SEL (1 << 5)
+
+#endif /* __SOUND_AUREON_H */
diff --git a/sound/pci/ice1712/delta.c b/sound/pci/ice1712/delta.c
new file mode 100644
index 0000000000..08adf4dd13
--- /dev/null
+++ b/sound/pci/ice1712/delta.c
@@ -0,0 +1,922 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for M-Audio Delta 1010, 1010E, 44, 66, 66E, Dio2496,
+ * Audiophile, Digigram VX442
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "delta.h"
+
+#define SND_CS8403
+#include <sound/cs8403.h>
+
+
+/*
+ * CS8427 via SPI mode (for Audiophile), emulated I2C
+ */
+
+/* send 8 bits */
+static void ap_cs8427_write_byte(struct snd_ice1712 *ice, unsigned char data, unsigned char tmp)
+{
+ int idx;
+
+ for (idx = 7; idx >= 0; idx--) {
+ tmp &= ~(ICE1712_DELTA_AP_DOUT|ICE1712_DELTA_AP_CCLK);
+ if (data & (1 << idx))
+ tmp |= ICE1712_DELTA_AP_DOUT;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(5);
+ tmp |= ICE1712_DELTA_AP_CCLK;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(5);
+ }
+}
+
+/* read 8 bits */
+static unsigned char ap_cs8427_read_byte(struct snd_ice1712 *ice, unsigned char tmp)
+{
+ unsigned char data = 0;
+ int idx;
+
+ for (idx = 7; idx >= 0; idx--) {
+ tmp &= ~ICE1712_DELTA_AP_CCLK;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(5);
+ if (snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_DELTA_AP_DIN)
+ data |= 1 << idx;
+ tmp |= ICE1712_DELTA_AP_CCLK;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(5);
+ }
+ return data;
+}
+
+/* assert chip select */
+static unsigned char ap_cs8427_codec_select(struct snd_ice1712 *ice)
+{
+ unsigned char tmp;
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DELTA1010E:
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ tmp &= ~ICE1712_DELTA_1010LT_CS;
+ tmp |= ICE1712_DELTA_1010LT_CCLK | ICE1712_DELTA_1010LT_CS_CS8427;
+ break;
+ case ICE1712_SUBDEVICE_AUDIOPHILE:
+ case ICE1712_SUBDEVICE_DELTA410:
+ tmp |= ICE1712_DELTA_AP_CCLK | ICE1712_DELTA_AP_CS_CODEC;
+ tmp &= ~ICE1712_DELTA_AP_CS_DIGITAL;
+ break;
+ case ICE1712_SUBDEVICE_DELTA66E:
+ tmp |= ICE1712_DELTA_66E_CCLK | ICE1712_DELTA_66E_CS_CHIP_A |
+ ICE1712_DELTA_66E_CS_CHIP_B;
+ tmp &= ~ICE1712_DELTA_66E_CS_CS8427;
+ break;
+ case ICE1712_SUBDEVICE_VX442:
+ tmp |= ICE1712_VX442_CCLK | ICE1712_VX442_CODEC_CHIP_A | ICE1712_VX442_CODEC_CHIP_B;
+ tmp &= ~ICE1712_VX442_CS_DIGITAL;
+ break;
+ }
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(5);
+ return tmp;
+}
+
+/* deassert chip select */
+static void ap_cs8427_codec_deassert(struct snd_ice1712 *ice, unsigned char tmp)
+{
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DELTA1010E:
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ tmp &= ~ICE1712_DELTA_1010LT_CS;
+ tmp |= ICE1712_DELTA_1010LT_CS_NONE;
+ break;
+ case ICE1712_SUBDEVICE_AUDIOPHILE:
+ case ICE1712_SUBDEVICE_DELTA410:
+ tmp |= ICE1712_DELTA_AP_CS_DIGITAL;
+ break;
+ case ICE1712_SUBDEVICE_DELTA66E:
+ tmp |= ICE1712_DELTA_66E_CS_CS8427;
+ break;
+ case ICE1712_SUBDEVICE_VX442:
+ tmp |= ICE1712_VX442_CS_DIGITAL;
+ break;
+ }
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+}
+
+/* sequential write */
+static int ap_cs8427_sendbytes(struct snd_i2c_device *device, unsigned char *bytes, int count)
+{
+ struct snd_ice1712 *ice = device->bus->private_data;
+ int res = count;
+ unsigned char tmp;
+
+ mutex_lock(&ice->gpio_mutex);
+ tmp = ap_cs8427_codec_select(ice);
+ ap_cs8427_write_byte(ice, (device->addr << 1) | 0, tmp); /* address + write mode */
+ while (count-- > 0)
+ ap_cs8427_write_byte(ice, *bytes++, tmp);
+ ap_cs8427_codec_deassert(ice, tmp);
+ mutex_unlock(&ice->gpio_mutex);
+ return res;
+}
+
+/* sequential read */
+static int ap_cs8427_readbytes(struct snd_i2c_device *device, unsigned char *bytes, int count)
+{
+ struct snd_ice1712 *ice = device->bus->private_data;
+ int res = count;
+ unsigned char tmp;
+
+ mutex_lock(&ice->gpio_mutex);
+ tmp = ap_cs8427_codec_select(ice);
+ ap_cs8427_write_byte(ice, (device->addr << 1) | 1, tmp); /* address + read mode */
+ while (count-- > 0)
+ *bytes++ = ap_cs8427_read_byte(ice, tmp);
+ ap_cs8427_codec_deassert(ice, tmp);
+ mutex_unlock(&ice->gpio_mutex);
+ return res;
+}
+
+static int ap_cs8427_probeaddr(struct snd_i2c_bus *bus, unsigned short addr)
+{
+ if (addr == 0x10)
+ return 1;
+ return -ENOENT;
+}
+
+static const struct snd_i2c_ops ap_cs8427_i2c_ops = {
+ .sendbytes = ap_cs8427_sendbytes,
+ .readbytes = ap_cs8427_readbytes,
+ .probeaddr = ap_cs8427_probeaddr,
+};
+
+/*
+ */
+
+static void snd_ice1712_delta_cs8403_spdif_write(struct snd_ice1712 *ice, unsigned char bits)
+{
+ unsigned char tmp, mask1, mask2;
+ int idx;
+ /* send byte to transmitter */
+ mask1 = ICE1712_DELTA_SPDIF_OUT_STAT_CLOCK;
+ mask2 = ICE1712_DELTA_SPDIF_OUT_STAT_DATA;
+ mutex_lock(&ice->gpio_mutex);
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+ for (idx = 7; idx >= 0; idx--) {
+ tmp &= ~(mask1 | mask2);
+ if (bits & (1 << idx))
+ tmp |= mask2;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(100);
+ tmp |= mask1;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(100);
+ }
+ tmp &= ~mask1;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+
+static void delta_spdif_default_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ snd_cs8403_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_bits);
+}
+
+static int delta_spdif_default_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int val;
+ int change;
+
+ val = snd_cs8403_encode_spdif_bits(&ucontrol->value.iec958);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_bits != val;
+ ice->spdif.cs8403_bits = val;
+ if (change && ice->playback_pro_substream == NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_delta_cs8403_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ return change;
+}
+
+static void delta_spdif_stream_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ snd_cs8403_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_stream_bits);
+}
+
+static int delta_spdif_stream_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int val;
+ int change;
+
+ val = snd_cs8403_encode_spdif_bits(&ucontrol->value.iec958);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_stream_bits != val;
+ ice->spdif.cs8403_stream_bits = val;
+ if (change && ice->playback_pro_substream != NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_delta_cs8403_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ return change;
+}
+
+
+/*
+ * AK4524 on Delta 44 and 66 to choose the chip mask
+ */
+static void delta_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ snd_ice1712_save_gpio_status(ice);
+ priv->cs_mask =
+ priv->cs_addr = chip == 0 ? ICE1712_DELTA_CODEC_CHIP_A :
+ ICE1712_DELTA_CODEC_CHIP_B;
+}
+
+/*
+ * AK4524 on Delta1010LT to choose the chip address
+ */
+static void delta1010lt_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ snd_ice1712_save_gpio_status(ice);
+ priv->cs_mask = ICE1712_DELTA_1010LT_CS;
+ priv->cs_addr = chip << 4;
+}
+
+/*
+ * AK4524 on Delta66 rev E to choose the chip address
+ */
+static void delta66e_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ snd_ice1712_save_gpio_status(ice);
+ priv->cs_mask =
+ priv->cs_addr = chip == 0 ? ICE1712_DELTA_66E_CS_CHIP_A :
+ ICE1712_DELTA_66E_CS_CHIP_B;
+}
+
+/*
+ * AK4528 on VX442 to choose the chip mask
+ */
+static void vx442_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ snd_ice1712_save_gpio_status(ice);
+ priv->cs_mask =
+ priv->cs_addr = chip == 0 ? ICE1712_VX442_CODEC_CHIP_A :
+ ICE1712_VX442_CODEC_CHIP_B;
+}
+
+/*
+ * change the DFS bit according rate for Delta1010
+ */
+static void delta_1010_set_rate_val(struct snd_ice1712 *ice, unsigned int rate)
+{
+ unsigned char tmp, tmp2;
+
+ if (rate == 0) /* no hint - S/PDIF input is master, simply return */
+ return;
+
+ mutex_lock(&ice->gpio_mutex);
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+ tmp2 = tmp & ~ICE1712_DELTA_DFS;
+ if (rate > 48000)
+ tmp2 |= ICE1712_DELTA_DFS;
+ if (tmp != tmp2)
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp2);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+/*
+ * change the rate of AK4524 on Delta 44/66, AP, 1010LT
+ */
+static void delta_ak4524_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+ unsigned char tmp, tmp2;
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ if (rate == 0) /* no hint - S/PDIF input is master, simply return */
+ return;
+
+ /* check before reset ak4524 to avoid unnecessary clicks */
+ mutex_lock(&ice->gpio_mutex);
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+ mutex_unlock(&ice->gpio_mutex);
+ tmp2 = tmp & ~ICE1712_DELTA_DFS;
+ if (rate > 48000)
+ tmp2 |= ICE1712_DELTA_DFS;
+ if (tmp == tmp2)
+ return;
+
+ /* do it again */
+ snd_akm4xxx_reset(ak, 1);
+ mutex_lock(&ice->gpio_mutex);
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ~ICE1712_DELTA_DFS;
+ if (rate > 48000)
+ tmp |= ICE1712_DELTA_DFS;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ mutex_unlock(&ice->gpio_mutex);
+ snd_akm4xxx_reset(ak, 0);
+}
+
+/*
+ * change the rate of AK4524 on VX442
+ */
+static void vx442_ak4524_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+ unsigned char val;
+
+ val = (rate > 48000) ? 0x65 : 0x60;
+ if (snd_akm4xxx_get(ak, 0, 0x02) != val ||
+ snd_akm4xxx_get(ak, 1, 0x02) != val) {
+ snd_akm4xxx_reset(ak, 1);
+ snd_akm4xxx_write(ak, 0, 0x02, val);
+ snd_akm4xxx_write(ak, 1, 0x02, val);
+ snd_akm4xxx_reset(ak, 0);
+ }
+}
+
+
+/*
+ * SPDIF ops for Delta 1010, Dio, 66
+ */
+
+/* open callback */
+static void delta_open_spdif(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+ ice->spdif.cs8403_stream_bits = ice->spdif.cs8403_bits;
+}
+
+/* set up */
+static void delta_setup_spdif(struct snd_ice1712 *ice, int rate)
+{
+ unsigned long flags;
+ unsigned int tmp;
+ int change;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ tmp = ice->spdif.cs8403_stream_bits;
+ if (tmp & 0x01) /* consumer */
+ tmp &= (tmp & 0x01) ? ~0x06 : ~0x18;
+ switch (rate) {
+ case 32000: tmp |= (tmp & 0x01) ? 0x04 : 0x00; break;
+ case 44100: tmp |= (tmp & 0x01) ? 0x00 : 0x10; break;
+ case 48000: tmp |= (tmp & 0x01) ? 0x02 : 0x08; break;
+ default: tmp |= (tmp & 0x01) ? 0x00 : 0x18; break;
+ }
+ change = ice->spdif.cs8403_stream_bits != tmp;
+ ice->spdif.cs8403_stream_bits = tmp;
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ if (change)
+ snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &ice->spdif.stream_ctl->id);
+ snd_ice1712_delta_cs8403_spdif_write(ice, tmp);
+}
+
+#define snd_ice1712_delta1010lt_wordclock_status_info \
+ snd_ctl_boolean_mono_info
+
+static int snd_ice1712_delta1010lt_wordclock_status_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ char reg = 0x10; /* CS8427 receiver error register */
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ if (snd_i2c_sendbytes(ice->cs8427, &reg, 1) != 1)
+ dev_err(ice->card->dev,
+ "unable to send register 0x%x byte to CS8427\n", reg);
+ snd_i2c_readbytes(ice->cs8427, &reg, 1);
+ ucontrol->value.integer.value[0] = (reg & CS8427_UNLOCK) ? 1 : 0;
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_status =
+{
+ .access = (SNDRV_CTL_ELEM_ACCESS_READ),
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Word Clock Status",
+ .info = snd_ice1712_delta1010lt_wordclock_status_info,
+ .get = snd_ice1712_delta1010lt_wordclock_status_get,
+};
+
+/*
+ * initialize the chips on M-Audio cards
+ */
+
+static const struct snd_akm4xxx akm_audiophile = {
+ .type = SND_AK4528,
+ .num_adcs = 2,
+ .num_dacs = 2,
+ .ops = {
+ .set_rate_val = delta_ak4524_set_rate_val
+ }
+};
+
+static const struct snd_ak4xxx_private akm_audiophile_priv = {
+ .caddr = 2,
+ .cif = 0,
+ .data_mask = ICE1712_DELTA_AP_DOUT,
+ .clk_mask = ICE1712_DELTA_AP_CCLK,
+ .cs_mask = ICE1712_DELTA_AP_CS_CODEC,
+ .cs_addr = ICE1712_DELTA_AP_CS_CODEC,
+ .cs_none = 0,
+ .add_flags = ICE1712_DELTA_AP_CS_DIGITAL,
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_delta410 = {
+ .type = SND_AK4529,
+ .num_adcs = 2,
+ .num_dacs = 8,
+ .ops = {
+ .set_rate_val = delta_ak4524_set_rate_val
+ }
+};
+
+static const struct snd_ak4xxx_private akm_delta410_priv = {
+ .caddr = 0,
+ .cif = 0,
+ .data_mask = ICE1712_DELTA_AP_DOUT,
+ .clk_mask = ICE1712_DELTA_AP_CCLK,
+ .cs_mask = ICE1712_DELTA_AP_CS_CODEC,
+ .cs_addr = ICE1712_DELTA_AP_CS_CODEC,
+ .cs_none = 0,
+ .add_flags = ICE1712_DELTA_AP_CS_DIGITAL,
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_delta1010lt = {
+ .type = SND_AK4524,
+ .num_adcs = 8,
+ .num_dacs = 8,
+ .ops = {
+ .lock = delta1010lt_ak4524_lock,
+ .set_rate_val = delta_ak4524_set_rate_val
+ }
+};
+
+static const struct snd_ak4xxx_private akm_delta1010lt_priv = {
+ .caddr = 2,
+ .cif = 0, /* the default level of the CIF pin from AK4524 */
+ .data_mask = ICE1712_DELTA_1010LT_DOUT,
+ .clk_mask = ICE1712_DELTA_1010LT_CCLK,
+ .cs_mask = 0,
+ .cs_addr = 0, /* set later */
+ .cs_none = ICE1712_DELTA_1010LT_CS_NONE,
+ .add_flags = 0,
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_delta66e = {
+ .type = SND_AK4524,
+ .num_adcs = 4,
+ .num_dacs = 4,
+ .ops = {
+ .lock = delta66e_ak4524_lock,
+ .set_rate_val = delta_ak4524_set_rate_val
+ }
+};
+
+static const struct snd_ak4xxx_private akm_delta66e_priv = {
+ .caddr = 2,
+ .cif = 0, /* the default level of the CIF pin from AK4524 */
+ .data_mask = ICE1712_DELTA_66E_DOUT,
+ .clk_mask = ICE1712_DELTA_66E_CCLK,
+ .cs_mask = 0,
+ .cs_addr = 0, /* set later */
+ .cs_none = 0,
+ .add_flags = 0,
+ .mask_flags = 0,
+};
+
+
+static const struct snd_akm4xxx akm_delta44 = {
+ .type = SND_AK4524,
+ .num_adcs = 4,
+ .num_dacs = 4,
+ .ops = {
+ .lock = delta_ak4524_lock,
+ .set_rate_val = delta_ak4524_set_rate_val
+ }
+};
+
+static const struct snd_ak4xxx_private akm_delta44_priv = {
+ .caddr = 2,
+ .cif = 0, /* the default level of the CIF pin from AK4524 */
+ .data_mask = ICE1712_DELTA_CODEC_SERIAL_DATA,
+ .clk_mask = ICE1712_DELTA_CODEC_SERIAL_CLOCK,
+ .cs_mask = 0,
+ .cs_addr = 0, /* set later */
+ .cs_none = 0,
+ .add_flags = 0,
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_vx442 = {
+ .type = SND_AK4524,
+ .num_adcs = 4,
+ .num_dacs = 4,
+ .ops = {
+ .lock = vx442_ak4524_lock,
+ .set_rate_val = vx442_ak4524_set_rate_val
+ }
+};
+
+static const struct snd_ak4xxx_private akm_vx442_priv = {
+ .caddr = 2,
+ .cif = 0,
+ .data_mask = ICE1712_VX442_DOUT,
+ .clk_mask = ICE1712_VX442_CCLK,
+ .cs_mask = 0,
+ .cs_addr = 0, /* set later */
+ .cs_none = 0,
+ .add_flags = 0,
+ .mask_flags = 0,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int snd_ice1712_delta_resume(struct snd_ice1712 *ice)
+{
+ unsigned char akm_img_bak[AK4XXX_IMAGE_SIZE];
+ unsigned char akm_vol_bak[AK4XXX_IMAGE_SIZE];
+
+ /* init spdif */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_AUDIOPHILE:
+ case ICE1712_SUBDEVICE_DELTA410:
+ case ICE1712_SUBDEVICE_DELTA1010E:
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ case ICE1712_SUBDEVICE_VX442:
+ case ICE1712_SUBDEVICE_DELTA66E:
+ snd_cs8427_init(ice->i2c, ice->cs8427);
+ break;
+ case ICE1712_SUBDEVICE_DELTA1010:
+ case ICE1712_SUBDEVICE_MEDIASTATION:
+ /* nothing */
+ break;
+ case ICE1712_SUBDEVICE_DELTADIO2496:
+ case ICE1712_SUBDEVICE_DELTA66:
+ /* Set spdif defaults */
+ snd_ice1712_delta_cs8403_spdif_write(ice, ice->spdif.cs8403_bits);
+ break;
+ }
+
+ /* init codec and restore registers */
+ if (ice->akm_codecs) {
+ memcpy(akm_img_bak, ice->akm->images, sizeof(akm_img_bak));
+ memcpy(akm_vol_bak, ice->akm->volumes, sizeof(akm_vol_bak));
+ snd_akm4xxx_init(ice->akm);
+ memcpy(ice->akm->images, akm_img_bak, sizeof(akm_img_bak));
+ memcpy(ice->akm->volumes, akm_vol_bak, sizeof(akm_vol_bak));
+ snd_akm4xxx_reset(ice->akm, 0);
+ }
+
+ return 0;
+}
+
+static int snd_ice1712_delta_suspend(struct snd_ice1712 *ice)
+{
+ if (ice->akm_codecs) /* reset & mute codec */
+ snd_akm4xxx_reset(ice->akm, 1);
+
+ return 0;
+}
+#endif
+
+static int snd_ice1712_delta_init(struct snd_ice1712 *ice)
+{
+ int err;
+ struct snd_akm4xxx *ak;
+ unsigned char tmp;
+
+ if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA1010 &&
+ ice->eeprom.gpiodir == 0x7b)
+ ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA1010E;
+
+ if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DELTA66 &&
+ ice->eeprom.gpiodir == 0xfb)
+ ice->eeprom.subvendor = ICE1712_SUBDEVICE_DELTA66E;
+
+ /* determine I2C, DACs and ADCs */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_AUDIOPHILE:
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 2;
+ break;
+ case ICE1712_SUBDEVICE_DELTA410:
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 2;
+ break;
+ case ICE1712_SUBDEVICE_DELTA44:
+ case ICE1712_SUBDEVICE_DELTA66:
+ ice->num_total_dacs = ice->omni ? 8 : 4;
+ ice->num_total_adcs = ice->omni ? 8 : 4;
+ break;
+ case ICE1712_SUBDEVICE_DELTA1010:
+ case ICE1712_SUBDEVICE_DELTA1010E:
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ case ICE1712_SUBDEVICE_MEDIASTATION:
+ case ICE1712_SUBDEVICE_EDIROLDA2496:
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 8;
+ break;
+ case ICE1712_SUBDEVICE_DELTADIO2496:
+ ice->num_total_dacs = 4; /* two AK4324 codecs */
+ break;
+ case ICE1712_SUBDEVICE_VX442:
+ case ICE1712_SUBDEVICE_DELTA66E: /* omni not supported yet */
+ ice->num_total_dacs = 4;
+ ice->num_total_adcs = 4;
+ break;
+ }
+#ifdef CONFIG_PM_SLEEP
+ ice->pm_resume = snd_ice1712_delta_resume;
+ ice->pm_suspend = snd_ice1712_delta_suspend;
+ ice->pm_suspend_enabled = 1;
+#endif
+ /* initialize the SPI clock to high */
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+ tmp |= ICE1712_DELTA_AP_CCLK;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(5);
+
+ /* initialize spdif */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_AUDIOPHILE:
+ case ICE1712_SUBDEVICE_DELTA410:
+ case ICE1712_SUBDEVICE_DELTA1010E:
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ case ICE1712_SUBDEVICE_VX442:
+ case ICE1712_SUBDEVICE_DELTA66E:
+ err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c);
+ if (err < 0) {
+ dev_err(ice->card->dev, "unable to create I2C bus\n");
+ return err;
+ }
+ ice->i2c->private_data = ice;
+ ice->i2c->ops = &ap_cs8427_i2c_ops;
+ err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR);
+ if (err < 0)
+ return err;
+ break;
+ case ICE1712_SUBDEVICE_DELTA1010:
+ case ICE1712_SUBDEVICE_MEDIASTATION:
+ ice->gpio.set_pro_rate = delta_1010_set_rate_val;
+ break;
+ case ICE1712_SUBDEVICE_DELTADIO2496:
+ ice->gpio.set_pro_rate = delta_1010_set_rate_val;
+ fallthrough;
+ case ICE1712_SUBDEVICE_DELTA66:
+ ice->spdif.ops.open = delta_open_spdif;
+ ice->spdif.ops.setup_rate = delta_setup_spdif;
+ ice->spdif.ops.default_get = delta_spdif_default_get;
+ ice->spdif.ops.default_put = delta_spdif_default_put;
+ ice->spdif.ops.stream_get = delta_spdif_stream_get;
+ ice->spdif.ops.stream_put = delta_spdif_stream_put;
+ /* Set spdif defaults */
+ snd_ice1712_delta_cs8403_spdif_write(ice, ice->spdif.cs8403_bits);
+ break;
+ }
+
+ /* no analog? */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DELTA1010:
+ case ICE1712_SUBDEVICE_DELTA1010E:
+ case ICE1712_SUBDEVICE_DELTADIO2496:
+ case ICE1712_SUBDEVICE_MEDIASTATION:
+ return 0;
+ }
+
+ /* second stage of initialization, analog parts and others */
+ ak = ice->akm = kmalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ak)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_AUDIOPHILE:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_audiophile, &akm_audiophile_priv, ice);
+ break;
+ case ICE1712_SUBDEVICE_DELTA410:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_delta410, &akm_delta410_priv, ice);
+ break;
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ case ICE1712_SUBDEVICE_EDIROLDA2496:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_delta1010lt, &akm_delta1010lt_priv, ice);
+ break;
+ case ICE1712_SUBDEVICE_DELTA66:
+ case ICE1712_SUBDEVICE_DELTA44:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_delta44, &akm_delta44_priv, ice);
+ break;
+ case ICE1712_SUBDEVICE_VX442:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_vx442, &akm_vx442_priv, ice);
+ break;
+ case ICE1712_SUBDEVICE_DELTA66E:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_delta66e, &akm_delta66e_priv, ice);
+ break;
+ default:
+ snd_BUG();
+ return -EINVAL;
+ }
+
+ return err;
+}
+
+
+/*
+ * additional controls for M-Audio cards
+ */
+
+static const struct snd_kcontrol_new snd_ice1712_delta1010_wordclock_select =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Sync", 0, ICE1712_DELTA_WORD_CLOCK_SELECT, 1, 0);
+static const struct snd_kcontrol_new snd_ice1712_delta1010lt_wordclock_select =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Sync", 0, ICE1712_DELTA_1010LT_WORDCLOCK, 0, 0);
+static const struct snd_kcontrol_new snd_ice1712_delta1010_wordclock_status =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Word Clock Status", 0, ICE1712_DELTA_WORD_CLOCK_STATUS, 1, SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE);
+static const struct snd_kcontrol_new snd_ice1712_deltadio2496_spdif_in_select =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "IEC958 Input Optical", 0, ICE1712_DELTA_SPDIF_INPUT_SELECT, 0, 0);
+static const struct snd_kcontrol_new snd_ice1712_delta_spdif_in_status =
+ICE1712_GPIO(SNDRV_CTL_ELEM_IFACE_MIXER, "Delta IEC958 Input Status", 0, ICE1712_DELTA_SPDIF_IN_STAT, 1, SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE);
+
+
+static int snd_ice1712_delta_add_controls(struct snd_ice1712 *ice)
+{
+ int err;
+
+ /* 1010 and dio specific controls */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DELTA1010:
+ case ICE1712_SUBDEVICE_MEDIASTATION:
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010_wordclock_select, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010_wordclock_status, ice));
+ if (err < 0)
+ return err;
+ break;
+ case ICE1712_SUBDEVICE_DELTADIO2496:
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_deltadio2496_spdif_in_select, ice));
+ if (err < 0)
+ return err;
+ break;
+ case ICE1712_SUBDEVICE_DELTA1010E:
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010lt_wordclock_select, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta1010lt_wordclock_status, ice));
+ if (err < 0)
+ return err;
+ break;
+ }
+
+ /* normal spdif controls */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DELTA1010:
+ case ICE1712_SUBDEVICE_DELTADIO2496:
+ case ICE1712_SUBDEVICE_DELTA66:
+ case ICE1712_SUBDEVICE_MEDIASTATION:
+ err = snd_ice1712_spdif_build_controls(ice);
+ if (err < 0)
+ return err;
+ break;
+ }
+
+ /* spdif status in */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DELTA1010:
+ case ICE1712_SUBDEVICE_DELTADIO2496:
+ case ICE1712_SUBDEVICE_DELTA66:
+ case ICE1712_SUBDEVICE_MEDIASTATION:
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_delta_spdif_in_status, ice));
+ if (err < 0)
+ return err;
+ break;
+ }
+
+ /* ak4524 controls */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DELTA1010LT:
+ case ICE1712_SUBDEVICE_AUDIOPHILE:
+ case ICE1712_SUBDEVICE_DELTA410:
+ case ICE1712_SUBDEVICE_DELTA44:
+ case ICE1712_SUBDEVICE_DELTA66:
+ case ICE1712_SUBDEVICE_VX442:
+ case ICE1712_SUBDEVICE_DELTA66E:
+ case ICE1712_SUBDEVICE_EDIROLDA2496:
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+ break;
+ }
+
+ return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_ice1712_delta_cards[] = {
+ {
+ .subvendor = ICE1712_SUBDEVICE_DELTA1010,
+ .name = "M Audio Delta 1010",
+ .model = "delta1010",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_DELTADIO2496,
+ .name = "M Audio Delta DiO 2496",
+ .model = "dio2496",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ .no_mpu401 = 1,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_DELTA66,
+ .name = "M Audio Delta 66",
+ .model = "delta66",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ .no_mpu401 = 1,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_DELTA44,
+ .name = "M Audio Delta 44",
+ .model = "delta44",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ .no_mpu401 = 1,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_AUDIOPHILE,
+ .name = "M Audio Audiophile 24/96",
+ .model = "audiophile",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_DELTA410,
+ .name = "M Audio Delta 410",
+ .model = "delta410",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_DELTA1010LT,
+ .name = "M Audio Delta 1010LT",
+ .model = "delta1010lt",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_VX442,
+ .name = "Digigram VX442",
+ .model = "vx442",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ .no_mpu401 = 1,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_MEDIASTATION,
+ .name = "Lionstracs Mediastation",
+ .model = "mediastation",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_EDIROLDA2496,
+ .name = "Edirol DA2496",
+ .model = "da2496",
+ .chip_init = snd_ice1712_delta_init,
+ .build_controls = snd_ice1712_delta_add_controls,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/delta.h b/sound/pci/ice1712/delta.h
new file mode 100644
index 0000000000..01fcaf7e85
--- /dev/null
+++ b/sound/pci/ice1712/delta.h
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_DELTA_H
+#define __SOUND_DELTA_H
+
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for M-Audio Delta 1010, 44, 66, Dio2496, Audiophile
+ * Digigram VX442
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#define DELTA_DEVICE_DESC \
+ "{MidiMan M Audio,Delta 1010},"\
+ "{MidiMan M Audio,Delta 1010LT},"\
+ "{MidiMan M Audio,Delta DiO 2496},"\
+ "{MidiMan M Audio,Delta 66},"\
+ "{MidiMan M Audio,Delta 44},"\
+ "{MidiMan M Audio,Delta 410},"\
+ "{MidiMan M Audio,Audiophile 24/96},"\
+ "{Digigram,VX442},"\
+ "{Lionstracs,Mediastation},"\
+ "{Edirol,DA2496},"
+
+#define ICE1712_SUBDEVICE_DELTA1010 0x121430d6
+#define ICE1712_SUBDEVICE_DELTA1010E 0xff1430d6
+#define ICE1712_SUBDEVICE_DELTADIO2496 0x121431d6
+#define ICE1712_SUBDEVICE_DELTA66 0x121432d6
+#define ICE1712_SUBDEVICE_DELTA66E 0xff1432d6
+#define ICE1712_SUBDEVICE_DELTA44 0x121433d6
+#define ICE1712_SUBDEVICE_AUDIOPHILE 0x121434d6
+#define ICE1712_SUBDEVICE_DELTA410 0x121438d6
+#define ICE1712_SUBDEVICE_DELTA1010LT 0x12143bd6
+#define ICE1712_SUBDEVICE_VX442 0x12143cd6
+#define ICE1712_SUBDEVICE_MEDIASTATION 0x694c0100
+#define ICE1712_SUBDEVICE_EDIROLDA2496 0xce164010
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_ice1712_delta_cards[];
+
+
+/*
+ * MidiMan M-Audio Delta GPIO definitions
+ */
+
+/* MidiMan M-Audio Delta shared pins */
+#define ICE1712_DELTA_DFS 0x01 /* fast/slow sample rate mode */
+ /* (>48kHz must be 1) */
+#define ICE1712_DELTA_SPDIF_IN_STAT 0x02
+ /* S/PDIF input status */
+ /* 0 = valid signal is present */
+ /* all except Delta44 */
+ /* look to CS8414 datasheet */
+#define ICE1712_DELTA_SPDIF_OUT_STAT_CLOCK 0x04
+ /* S/PDIF output status clock */
+ /* (writing on rising edge - 0->1) */
+ /* all except Delta44 */
+ /* look to CS8404A datasheet */
+#define ICE1712_DELTA_SPDIF_OUT_STAT_DATA 0x08
+ /* S/PDIF output status data */
+ /* all except Delta44 */
+ /* look to CS8404A datasheet */
+/* MidiMan M-Audio DeltaDiO */
+/* 0x01 = DFS */
+/* 0x02 = SPDIF_IN_STAT */
+/* 0x04 = SPDIF_OUT_STAT_CLOCK */
+/* 0x08 = SPDIF_OUT_STAT_DATA */
+#define ICE1712_DELTA_SPDIF_INPUT_SELECT 0x10
+ /* coaxial (0), optical (1) */
+ /* S/PDIF input select*/
+
+/* MidiMan M-Audio Delta1010 */
+/* 0x01 = DFS */
+/* 0x02 = SPDIF_IN_STAT */
+/* 0x04 = SPDIF_OUT_STAT_CLOCK */
+/* 0x08 = SPDIF_OUT_STAT_DATA */
+#define ICE1712_DELTA_WORD_CLOCK_SELECT 0x10
+ /* 1 - clock are taken from S/PDIF input */
+ /* 0 - clock are taken from Word Clock input */
+ /* affected SPMCLKIN pin of Envy24 */
+#define ICE1712_DELTA_WORD_CLOCK_STATUS 0x20
+ /* 0 = valid word clock signal is present */
+
+/* MidiMan M-Audio Delta66 */
+/* 0x01 = DFS */
+/* 0x02 = SPDIF_IN_STAT */
+/* 0x04 = SPDIF_OUT_STAT_CLOCK */
+/* 0x08 = SPDIF_OUT_STAT_DATA */
+#define ICE1712_DELTA_CODEC_SERIAL_DATA 0x10
+ /* AKM4524 serial data */
+#define ICE1712_DELTA_CODEC_SERIAL_CLOCK 0x20
+ /* AKM4524 serial clock */
+ /* (writing on rising edge - 0->1 */
+#define ICE1712_DELTA_CODEC_CHIP_A 0x40
+#define ICE1712_DELTA_CODEC_CHIP_B 0x80
+ /* 1 - select chip A or B */
+
+/* MidiMan M-Audio Delta44 */
+/* 0x01 = DFS */
+/* 0x10 = CODEC_SERIAL_DATA */
+/* 0x20 = CODEC_SERIAL_CLOCK */
+/* 0x40 = CODEC_CHIP_A */
+/* 0x80 = CODEC_CHIP_B */
+
+/* MidiMan M-Audio Audiophile/Delta410 definitions */
+/* thanks to Kristof Pelckmans <Kristof.Pelckmans@antwerpen.be> for Delta410 info */
+/* 0x01 = DFS */
+#define ICE1712_DELTA_AP_CCLK 0x02 /* SPI clock */
+ /* (clocking on rising edge - 0->1) */
+#define ICE1712_DELTA_AP_DIN 0x04 /* data input */
+#define ICE1712_DELTA_AP_DOUT 0x08 /* data output */
+#define ICE1712_DELTA_AP_CS_DIGITAL 0x10 /* CS8427 chip select */
+ /* low signal = select */
+#define ICE1712_DELTA_AP_CS_CODEC 0x20 /* AK4528 (audiophile), AK4529 (Delta410) chip select */
+ /* low signal = select */
+
+/* MidiMan M-Audio Delta1010LT definitions */
+/* thanks to Anders Johansson <ajh@watri.uwa.edu.au> */
+/* 0x01 = DFS */
+#define ICE1712_DELTA_1010LT_CCLK 0x02 /* SPI clock (AK4524 + CS8427) */
+#define ICE1712_DELTA_1010LT_DIN 0x04 /* data input (CS8427) */
+#define ICE1712_DELTA_1010LT_DOUT 0x08 /* data output (AK4524 + CS8427) */
+#define ICE1712_DELTA_1010LT_CS 0x70 /* mask for CS address */
+#define ICE1712_DELTA_1010LT_CS_CHIP_A 0x00 /* AK4524 #0 */
+#define ICE1712_DELTA_1010LT_CS_CHIP_B 0x10 /* AK4524 #1 */
+#define ICE1712_DELTA_1010LT_CS_CHIP_C 0x20 /* AK4524 #2 */
+#define ICE1712_DELTA_1010LT_CS_CHIP_D 0x30 /* AK4524 #3 */
+#define ICE1712_DELTA_1010LT_CS_CS8427 0x40 /* CS8427 */
+#define ICE1712_DELTA_1010LT_CS_NONE 0x50 /* nothing */
+#define ICE1712_DELTA_1010LT_WORDCLOCK 0x80 /* sample clock source: 0 = Word Clock Input, 1 = S/PDIF Input ??? */
+
+/* M-Audio Delta 66 rev. E definitions.
+ * Newer revisions of Delta 66 have CS8427 over SPI for
+ * S/PDIF transceiver instead of CS8404/CS8414. */
+/* 0x01 = DFS */
+#define ICE1712_DELTA_66E_CCLK 0x02 /* SPI clock */
+#define ICE1712_DELTA_66E_DIN 0x04 /* data input */
+#define ICE1712_DELTA_66E_DOUT 0x08 /* data output */
+#define ICE1712_DELTA_66E_CS_CS8427 0x10 /* chip select, low = CS8427 */
+#define ICE1712_DELTA_66E_CS_CHIP_A 0x20 /* AK4524 #0 */
+#define ICE1712_DELTA_66E_CS_CHIP_B 0x40 /* AK4524 #1 */
+
+/* Digigram VX442 definitions */
+#define ICE1712_VX442_CCLK 0x02 /* SPI clock */
+#define ICE1712_VX442_DIN 0x04 /* data input */
+#define ICE1712_VX442_DOUT 0x08 /* data output */
+#define ICE1712_VX442_CS_DIGITAL 0x10 /* chip select, low = CS8427 */
+#define ICE1712_VX442_CODEC_CHIP_A 0x20 /* select chip A */
+#define ICE1712_VX442_CODEC_CHIP_B 0x40 /* select chip B */
+
+#endif /* __SOUND_DELTA_H */
diff --git a/sound/pci/ice1712/envy24ht.h b/sound/pci/ice1712/envy24ht.h
new file mode 100644
index 0000000000..10e79c82f8
--- /dev/null
+++ b/sound/pci/ice1712/envy24ht.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_VT1724_H
+#define __SOUND_VT1724_H
+
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24)
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/rawmidi.h>
+#include <sound/i2c.h>
+#include <sound/pcm.h>
+
+#include "ice1712.h"
+
+enum {
+ ICE_EEP2_SYSCONF = 0, /* 06 */
+ ICE_EEP2_ACLINK, /* 07 */
+ ICE_EEP2_I2S, /* 08 */
+ ICE_EEP2_SPDIF, /* 09 */
+ ICE_EEP2_GPIO_DIR, /* 0a */
+ ICE_EEP2_GPIO_DIR1, /* 0b */
+ ICE_EEP2_GPIO_DIR2, /* 0c */
+ ICE_EEP2_GPIO_MASK, /* 0d */
+ ICE_EEP2_GPIO_MASK1, /* 0e */
+ ICE_EEP2_GPIO_MASK2, /* 0f */
+ ICE_EEP2_GPIO_STATE, /* 10 */
+ ICE_EEP2_GPIO_STATE1, /* 11 */
+ ICE_EEP2_GPIO_STATE2 /* 12 */
+};
+
+/*
+ * Direct registers
+ */
+
+#define ICEREG1724(ice, x) ((ice)->port + VT1724_REG_##x)
+
+#define VT1724_REG_CONTROL 0x00 /* byte */
+#define VT1724_RESET 0x80 /* reset whole chip */
+#define VT1724_REG_IRQMASK 0x01 /* byte */
+#define VT1724_IRQ_MPU_RX 0x80
+#define VT1724_IRQ_MPU_TX 0x20
+#define VT1724_IRQ_MTPCM 0x10
+#define VT1724_REG_IRQSTAT 0x02 /* byte */
+/* look to VT1724_IRQ_* */
+#define VT1724_REG_SYS_CFG 0x04 /* byte - system configuration PCI60 on Envy24*/
+#define VT1724_CFG_CLOCK 0xc0
+#define VT1724_CFG_CLOCK512 0x00 /* 22.5692Mhz, 44.1kHz*512 */
+#define VT1724_CFG_CLOCK384 0x40 /* 16.9344Mhz, 44.1kHz*384 */
+#define VT1724_CFG_MPU401 0x20 /* MPU401 UARTs */
+#define VT1724_CFG_ADC_MASK 0x0c /* one, two or one and S/PDIF, stereo ADCs */
+#define VT1724_CFG_ADC_NONE 0x0c /* no ADCs */
+#define VT1724_CFG_DAC_MASK 0x03 /* one, two, three, four stereo DACs */
+
+#define VT1724_REG_AC97_CFG 0x05 /* byte */
+#define VT1724_CFG_PRO_I2S 0x80 /* multitrack converter: I2S or AC'97 */
+#define VT1724_CFG_AC97_PACKED 0x01 /* split or packed mode - AC'97 */
+
+#define VT1724_REG_I2S_FEATURES 0x06 /* byte */
+#define VT1724_CFG_I2S_VOLUME 0x80 /* volume/mute capability */
+#define VT1724_CFG_I2S_96KHZ 0x40 /* supports 96kHz sampling */
+#define VT1724_CFG_I2S_RESMASK 0x30 /* resolution mask, 16,18,20,24-bit */
+#define VT1724_CFG_I2S_192KHZ 0x08 /* supports 192kHz sampling */
+#define VT1724_CFG_I2S_OTHER 0x07 /* other I2S IDs */
+
+#define VT1724_REG_SPDIF_CFG 0x07 /* byte */
+#define VT1724_CFG_SPDIF_OUT_EN 0x80 /*Internal S/PDIF output is enabled*/
+#define VT1724_CFG_SPDIF_OUT_INT 0x40 /*Internal S/PDIF output is implemented*/
+#define VT1724_CFG_I2S_CHIPID 0x3c /* I2S chip ID */
+#define VT1724_CFG_SPDIF_IN 0x02 /* S/PDIF input is present */
+#define VT1724_CFG_SPDIF_OUT 0x01 /* External S/PDIF output is present */
+
+/*there is no consumer AC97 codec with the VT1724*/
+//#define VT1724_REG_AC97_INDEX 0x08 /* byte */
+//#define VT1724_REG_AC97_CMD 0x09 /* byte */
+
+#define VT1724_REG_MPU_TXFIFO 0x0a /*byte ro. number of bytes in TX fifo*/
+#define VT1724_REG_MPU_RXFIFO 0x0b /*byte ro. number of bytes in RX fifo*/
+
+#define VT1724_REG_MPU_DATA 0x0c /* byte */
+#define VT1724_REG_MPU_CTRL 0x0d /* byte */
+#define VT1724_MPU_UART 0x01
+#define VT1724_MPU_TX_EMPTY 0x02
+#define VT1724_MPU_TX_FULL 0x04
+#define VT1724_MPU_RX_EMPTY 0x08
+#define VT1724_MPU_RX_FULL 0x10
+
+#define VT1724_REG_MPU_FIFO_WM 0x0e /*byte set the high/low watermarks for RX/TX fifos*/
+#define VT1724_MPU_RX_FIFO 0x20 //1=rx fifo watermark 0=tx fifo watermark
+#define VT1724_MPU_FIFO_MASK 0x1f
+
+#define VT1724_REG_I2C_DEV_ADDR 0x10 /* byte */
+#define VT1724_I2C_WRITE 0x01 /* write direction */
+#define VT1724_REG_I2C_BYTE_ADDR 0x11 /* byte */
+#define VT1724_REG_I2C_DATA 0x12 /* byte */
+#define VT1724_REG_I2C_CTRL 0x13 /* byte */
+#define VT1724_I2C_EEPROM 0x80 /* 1 = EEPROM exists */
+#define VT1724_I2C_BUSY 0x01 /* busy bit */
+
+#define VT1724_REG_GPIO_DATA 0x14 /* word */
+#define VT1724_REG_GPIO_WRITE_MASK 0x16 /* word */
+#define VT1724_REG_GPIO_DIRECTION 0x18 /* dword? (3 bytes) 0=input 1=output.
+ bit3 - during reset used for Eeprom power-on strapping
+ if TESTEN# pin active, bit 2 always input*/
+#define VT1724_REG_POWERDOWN 0x1c
+#define VT1724_REG_GPIO_DATA_22 0x1e /* byte direction for GPIO 16:22 */
+#define VT1724_REG_GPIO_WRITE_MASK_22 0x1f /* byte write mask for GPIO 16:22 */
+
+
+/*
+ * Professional multi-track direct control registers
+ */
+
+#define ICEMT1724(ice, x) ((ice)->profi_port + VT1724_MT_##x)
+
+#define VT1724_MT_IRQ 0x00 /* byte - interrupt mask */
+#define VT1724_MULTI_PDMA4 0x80 /* SPDIF Out / PDMA4 */
+#define VT1724_MULTI_PDMA3 0x40 /* PDMA3 */
+#define VT1724_MULTI_PDMA2 0x20 /* PDMA2 */
+#define VT1724_MULTI_PDMA1 0x10 /* PDMA1 */
+#define VT1724_MULTI_FIFO_ERR 0x08 /* DMA FIFO underrun/overrun. */
+#define VT1724_MULTI_RDMA1 0x04 /* RDMA1 (S/PDIF input) */
+#define VT1724_MULTI_RDMA0 0x02 /* RMDA0 */
+#define VT1724_MULTI_PDMA0 0x01 /* MC Interleave/PDMA0 */
+
+#define VT1724_MT_RATE 0x01 /* byte - sampling rate select */
+#define VT1724_SPDIF_MASTER 0x10 /* S/PDIF input is master clock */
+#define VT1724_MT_I2S_FORMAT 0x02 /* byte - I2S data format */
+#define VT1724_MT_I2S_MCLK_128X 0x08
+#define VT1724_MT_I2S_FORMAT_MASK 0x03
+#define VT1724_MT_I2S_FORMAT_I2S 0x00
+#define VT1724_MT_DMA_INT_MASK 0x03 /* byte -DMA Interrupt Mask */
+/* lool to VT1724_MULTI_* */
+#define VT1724_MT_AC97_INDEX 0x04 /* byte - AC'97 index */
+#define VT1724_MT_AC97_CMD 0x05 /* byte - AC'97 command & status */
+#define VT1724_AC97_COLD 0x80 /* cold reset */
+#define VT1724_AC97_WARM 0x40 /* warm reset */
+#define VT1724_AC97_WRITE 0x20 /* W: write, R: write in progress */
+#define VT1724_AC97_READ 0x10 /* W: read, R: read in progress */
+#define VT1724_AC97_READY 0x08 /* codec ready status bit */
+#define VT1724_AC97_ID_MASK 0x03 /* codec id mask */
+#define VT1724_MT_AC97_DATA 0x06 /* word - AC'97 data */
+#define VT1724_MT_PLAYBACK_ADDR 0x10 /* dword - playback address */
+#define VT1724_MT_PLAYBACK_SIZE 0x14 /* dword - playback size */
+#define VT1724_MT_DMA_CONTROL 0x18 /* byte - control */
+#define VT1724_PDMA4_START 0x80 /* SPDIF out / PDMA4 start */
+#define VT1724_PDMA3_START 0x40 /* PDMA3 start */
+#define VT1724_PDMA2_START 0x20 /* PDMA2 start */
+#define VT1724_PDMA1_START 0x10 /* PDMA1 start */
+#define VT1724_RDMA1_START 0x04 /* RDMA1 start */
+#define VT1724_RDMA0_START 0x02 /* RMDA0 start */
+#define VT1724_PDMA0_START 0x01 /* MC Interleave / PDMA0 start */
+#define VT1724_MT_BURST 0x19 /* Interleaved playback DMA Active streams / PCI burst size */
+#define VT1724_MT_DMA_FIFO_ERR 0x1a /*Global playback and record DMA FIFO Underrun/Overrun */
+#define VT1724_PDMA4_UNDERRUN 0x80
+#define VT1724_PDMA2_UNDERRUN 0x40
+#define VT1724_PDMA3_UNDERRUN 0x20
+#define VT1724_PDMA1_UNDERRUN 0x10
+#define VT1724_RDMA1_UNDERRUN 0x04
+#define VT1724_RDMA0_UNDERRUN 0x02
+#define VT1724_PDMA0_UNDERRUN 0x01
+#define VT1724_MT_DMA_PAUSE 0x1b /*Global playback and record DMA FIFO pause/resume */
+#define VT1724_PDMA4_PAUSE 0x80
+#define VT1724_PDMA3_PAUSE 0x40
+#define VT1724_PDMA2_PAUSE 0x20
+#define VT1724_PDMA1_PAUSE 0x10
+#define VT1724_RDMA1_PAUSE 0x04
+#define VT1724_RDMA0_PAUSE 0x02
+#define VT1724_PDMA0_PAUSE 0x01
+#define VT1724_MT_PLAYBACK_COUNT 0x1c /* word - playback count */
+#define VT1724_MT_CAPTURE_ADDR 0x20 /* dword - capture address */
+#define VT1724_MT_CAPTURE_SIZE 0x24 /* word - capture size */
+#define VT1724_MT_CAPTURE_COUNT 0x26 /* word - capture count */
+
+#define VT1724_MT_ROUTE_PLAYBACK 0x2c /* word */
+
+#define VT1724_MT_RDMA1_ADDR 0x30 /* dword - RDMA1 capture address */
+#define VT1724_MT_RDMA1_SIZE 0x34 /* word - RDMA1 capture size */
+#define VT1724_MT_RDMA1_COUNT 0x36 /* word - RDMA1 capture count */
+
+#define VT1724_MT_SPDIF_CTRL 0x3c /* word */
+#define VT1724_MT_MONITOR_PEAKINDEX 0x3e /* byte */
+#define VT1724_MT_MONITOR_PEAKDATA 0x3f /* byte */
+
+/* concurrent stereo channels */
+#define VT1724_MT_PDMA4_ADDR 0x40 /* dword */
+#define VT1724_MT_PDMA4_SIZE 0x44 /* word */
+#define VT1724_MT_PDMA4_COUNT 0x46 /* word */
+#define VT1724_MT_PDMA3_ADDR 0x50 /* dword */
+#define VT1724_MT_PDMA3_SIZE 0x54 /* word */
+#define VT1724_MT_PDMA3_COUNT 0x56 /* word */
+#define VT1724_MT_PDMA2_ADDR 0x60 /* dword */
+#define VT1724_MT_PDMA2_SIZE 0x64 /* word */
+#define VT1724_MT_PDMA2_COUNT 0x66 /* word */
+#define VT1724_MT_PDMA1_ADDR 0x70 /* dword */
+#define VT1724_MT_PDMA1_SIZE 0x74 /* word */
+#define VT1724_MT_PDMA1_COUNT 0x76 /* word */
+
+
+unsigned char snd_vt1724_read_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr);
+void snd_vt1724_write_i2c(struct snd_ice1712 *ice, unsigned char dev, unsigned char addr, unsigned char data);
+
+#endif /* __SOUND_VT1724_H */
diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c
new file mode 100644
index 0000000000..8bb86b3c89
--- /dev/null
+++ b/sound/pci/ice1712/ews.c
@@ -0,0 +1,1075 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ * 2002 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "ews.h"
+
+#define SND_CS8404
+#include <sound/cs8403.h>
+
+enum {
+ EWS_I2C_CS8404 = 0, EWS_I2C_PCF1, EWS_I2C_PCF2,
+ EWS_I2C_88D = 0,
+ EWS_I2C_6FIRE = 0
+};
+
+
+/* additional i2c devices for EWS boards */
+struct ews_spec {
+ struct snd_i2c_device *i2cdevs[3];
+};
+
+/*
+ * access via i2c mode (for EWX 24/96, EWS 88MT&D)
+ */
+
+/* send SDA and SCL */
+static void ewx_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ unsigned char tmp = 0;
+ if (clk)
+ tmp |= ICE1712_EWX2496_SERIAL_CLOCK;
+ if (data)
+ tmp |= ICE1712_EWX2496_SERIAL_DATA;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, tmp);
+ udelay(5);
+}
+
+static int ewx_i2c_getclock(struct snd_i2c_bus *bus)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_CLOCK ? 1 : 0;
+}
+
+static int ewx_i2c_getdata(struct snd_i2c_bus *bus, int ack)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ int bit;
+ /* set RW pin to low */
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_RW);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, 0);
+ if (ack)
+ udelay(5);
+ bit = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & ICE1712_EWX2496_SERIAL_DATA ? 1 : 0;
+ /* set RW pin to high */
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ICE1712_EWX2496_RW);
+ /* reset write mask */
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~ICE1712_EWX2496_SERIAL_CLOCK);
+ return bit;
+}
+
+static void ewx_i2c_start(struct snd_i2c_bus *bus)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ unsigned char mask;
+
+ snd_ice1712_save_gpio_status(ice);
+ /* set RW high */
+ mask = ICE1712_EWX2496_RW;
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWX2496:
+ mask |= ICE1712_EWX2496_AK4524_CS; /* CS high also */
+ break;
+ case ICE1712_SUBDEVICE_DMX6FIRE:
+ mask |= ICE1712_6FIRE_AK4524_CS_MASK; /* CS high also */
+ break;
+ }
+ snd_ice1712_gpio_write_bits(ice, mask, mask);
+}
+
+static void ewx_i2c_stop(struct snd_i2c_bus *bus)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ snd_ice1712_restore_gpio_status(ice);
+}
+
+static void ewx_i2c_direction(struct snd_i2c_bus *bus, int clock, int data)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ unsigned char mask = 0;
+
+ if (clock)
+ mask |= ICE1712_EWX2496_SERIAL_CLOCK; /* write SCL */
+ if (data)
+ mask |= ICE1712_EWX2496_SERIAL_DATA; /* write SDA */
+ ice->gpio.direction &= ~(ICE1712_EWX2496_SERIAL_CLOCK|ICE1712_EWX2496_SERIAL_DATA);
+ ice->gpio.direction |= mask;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->gpio.direction);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~mask);
+}
+
+static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = {
+ .start = ewx_i2c_start,
+ .stop = ewx_i2c_stop,
+ .direction = ewx_i2c_direction,
+ .setlines = ewx_i2c_setlines,
+ .getclock = ewx_i2c_getclock,
+ .getdata = ewx_i2c_getdata,
+};
+
+
+/*
+ * AK4524 access
+ */
+
+/* AK4524 chip select; address 0x48 bit 0-3 */
+static int snd_ice1712_ews88mt_chip_select(struct snd_ice1712 *ice, int chip_mask)
+{
+ struct ews_spec *spec = ice->spec;
+ unsigned char data, ndata;
+
+ if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f))
+ return -EINVAL;
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1)
+ goto __error;
+ ndata = (data & 0xf0) | chip_mask;
+ if (ndata != data)
+ if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], &ndata, 1)
+ != 1)
+ goto __error;
+ snd_i2c_unlock(ice->i2c);
+ return 0;
+
+ __error:
+ snd_i2c_unlock(ice->i2c);
+ dev_err(ice->card->dev,
+ "AK4524 chip select failed, check cable to the front module\n");
+ return -EIO;
+}
+
+/* start callback for EWS88MT, needs to select a certain chip mask */
+static void ews88mt_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+ unsigned char tmp;
+ /* assert AK4524 CS */
+ if (snd_ice1712_ews88mt_chip_select(ice, ~(1 << chip) & 0x0f) < 0)
+ dev_err(ice->card->dev, "fatal error (ews88mt chip select)\n");
+ snd_ice1712_save_gpio_status(ice);
+ tmp = ICE1712_EWS88_SERIAL_DATA |
+ ICE1712_EWS88_SERIAL_CLOCK |
+ ICE1712_EWS88_RW;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+ ice->gpio.direction | tmp);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+/* stop callback for EWS88MT, needs to deselect chip mask */
+static void ews88mt_ak4524_unlock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+ snd_ice1712_restore_gpio_status(ice);
+ udelay(1);
+ snd_ice1712_ews88mt_chip_select(ice, 0x0f);
+}
+
+/* start callback for EWX24/96 */
+static void ewx2496_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+ unsigned char tmp;
+ snd_ice1712_save_gpio_status(ice);
+ tmp = ICE1712_EWX2496_SERIAL_DATA |
+ ICE1712_EWX2496_SERIAL_CLOCK |
+ ICE1712_EWX2496_AK4524_CS |
+ ICE1712_EWX2496_RW;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+ ice->gpio.direction | tmp);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+/* start callback for DMX 6fire */
+static void dmx6fire_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ak4xxx_private *priv = (void *)ak->private_value[0];
+ struct snd_ice1712 *ice = ak->private_data[0];
+ unsigned char tmp;
+ snd_ice1712_save_gpio_status(ice);
+ tmp = priv->cs_mask = priv->cs_addr = (1 << chip) & ICE1712_6FIRE_AK4524_CS_MASK;
+ tmp |= ICE1712_6FIRE_SERIAL_DATA |
+ ICE1712_6FIRE_SERIAL_CLOCK |
+ ICE1712_6FIRE_RW;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+ ice->gpio.direction | tmp);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+/*
+ * CS8404 interface on EWS88MT/D
+ */
+
+static void snd_ice1712_ews_cs8404_spdif_write(struct snd_ice1712 *ice, unsigned char bits)
+{
+ struct ews_spec *spec = ice->spec;
+ unsigned char bytes[2];
+
+ snd_i2c_lock(ice->i2c);
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWS88MT:
+ case ICE1712_SUBDEVICE_EWS88MT_NEW:
+ case ICE1712_SUBDEVICE_PHASE88:
+ case ICE1712_SUBDEVICE_TS88:
+ if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_CS8404], &bits, 1)
+ != 1)
+ goto _error;
+ break;
+ case ICE1712_SUBDEVICE_EWS88D:
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], bytes, 2)
+ != 2)
+ goto _error;
+ if (bits != bytes[1]) {
+ bytes[1] = bits;
+ if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_88D],
+ bytes, 2) != 2)
+ goto _error;
+ }
+ break;
+ }
+ _error:
+ snd_i2c_unlock(ice->i2c);
+}
+
+/*
+ */
+
+static void ews88_spdif_default_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ snd_cs8404_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_bits);
+}
+
+static int ews88_spdif_default_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int val;
+ int change;
+
+ val = snd_cs8404_encode_spdif_bits(&ucontrol->value.iec958);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_bits != val;
+ ice->spdif.cs8403_bits = val;
+ if (change && ice->playback_pro_substream == NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_ews_cs8404_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ return change;
+}
+
+static void ews88_spdif_stream_get(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ snd_cs8404_decode_spdif_bits(&ucontrol->value.iec958, ice->spdif.cs8403_stream_bits);
+}
+
+static int ews88_spdif_stream_put(struct snd_ice1712 *ice, struct snd_ctl_elem_value *ucontrol)
+{
+ unsigned int val;
+ int change;
+
+ val = snd_cs8404_encode_spdif_bits(&ucontrol->value.iec958);
+ spin_lock_irq(&ice->reg_lock);
+ change = ice->spdif.cs8403_stream_bits != val;
+ ice->spdif.cs8403_stream_bits = val;
+ if (change && ice->playback_pro_substream != NULL) {
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_ews_cs8404_spdif_write(ice, val);
+ } else {
+ spin_unlock_irq(&ice->reg_lock);
+ }
+ return change;
+}
+
+
+/* open callback */
+static void ews88_open_spdif(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+ ice->spdif.cs8403_stream_bits = ice->spdif.cs8403_bits;
+}
+
+/* set up SPDIF for EWS88MT / EWS88D */
+static void ews88_setup_spdif(struct snd_ice1712 *ice, int rate)
+{
+ unsigned long flags;
+ unsigned char tmp;
+ int change;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ tmp = ice->spdif.cs8403_stream_bits;
+ if (tmp & 0x10) /* consumer */
+ tmp &= (tmp & 0x01) ? ~0x06 : ~0x60;
+ switch (rate) {
+ case 32000: tmp |= (tmp & 0x01) ? 0x02 : 0x00; break;
+ case 44100: tmp |= (tmp & 0x01) ? 0x06 : 0x40; break;
+ case 48000: tmp |= (tmp & 0x01) ? 0x04 : 0x20; break;
+ default: tmp |= (tmp & 0x01) ? 0x06 : 0x40; break;
+ }
+ change = ice->spdif.cs8403_stream_bits != tmp;
+ ice->spdif.cs8403_stream_bits = tmp;
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ if (change)
+ snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &ice->spdif.stream_ctl->id);
+ snd_ice1712_ews_cs8404_spdif_write(ice, tmp);
+}
+
+
+/*
+ */
+static const struct snd_akm4xxx akm_ews88mt = {
+ .num_adcs = 8,
+ .num_dacs = 8,
+ .type = SND_AK4524,
+ .ops = {
+ .lock = ews88mt_ak4524_lock,
+ .unlock = ews88mt_ak4524_unlock
+ }
+};
+
+static const struct snd_ak4xxx_private akm_ews88mt_priv = {
+ .caddr = 2,
+ .cif = 1, /* CIF high */
+ .data_mask = ICE1712_EWS88_SERIAL_DATA,
+ .clk_mask = ICE1712_EWS88_SERIAL_CLOCK,
+ .cs_mask = 0,
+ .cs_addr = 0,
+ .cs_none = 0, /* no chip select on gpio */
+ .add_flags = ICE1712_EWS88_RW, /* set rw bit high */
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_ewx2496 = {
+ .num_adcs = 2,
+ .num_dacs = 2,
+ .type = SND_AK4524,
+ .ops = {
+ .lock = ewx2496_ak4524_lock
+ }
+};
+
+static const struct snd_ak4xxx_private akm_ewx2496_priv = {
+ .caddr = 2,
+ .cif = 1, /* CIF high */
+ .data_mask = ICE1712_EWS88_SERIAL_DATA,
+ .clk_mask = ICE1712_EWS88_SERIAL_CLOCK,
+ .cs_mask = ICE1712_EWX2496_AK4524_CS,
+ .cs_addr = ICE1712_EWX2496_AK4524_CS,
+ .cs_none = 0,
+ .add_flags = ICE1712_EWS88_RW, /* set rw bit high */
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_6fire = {
+ .num_adcs = 6,
+ .num_dacs = 6,
+ .type = SND_AK4524,
+ .ops = {
+ .lock = dmx6fire_ak4524_lock
+ }
+};
+
+static const struct snd_ak4xxx_private akm_6fire_priv = {
+ .caddr = 2,
+ .cif = 1, /* CIF high */
+ .data_mask = ICE1712_6FIRE_SERIAL_DATA,
+ .clk_mask = ICE1712_6FIRE_SERIAL_CLOCK,
+ .cs_mask = 0,
+ .cs_addr = 0, /* set later */
+ .cs_none = 0,
+ .add_flags = ICE1712_6FIRE_RW, /* set rw bit high */
+ .mask_flags = 0,
+};
+
+/*
+ * initialize the chip
+ */
+
+/* 6fire specific */
+#define PCF9554_REG_INPUT 0
+#define PCF9554_REG_OUTPUT 1
+#define PCF9554_REG_POLARITY 2
+#define PCF9554_REG_CONFIG 3
+
+static int snd_ice1712_6fire_write_pca(struct snd_ice1712 *ice, unsigned char reg, unsigned char data);
+
+static int snd_ice1712_ews_init(struct snd_ice1712 *ice)
+{
+ int err;
+ struct snd_akm4xxx *ak;
+ struct ews_spec *spec;
+
+ /* set the analog DACs */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWX2496:
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 2;
+ break;
+ case ICE1712_SUBDEVICE_EWS88MT:
+ case ICE1712_SUBDEVICE_EWS88MT_NEW:
+ case ICE1712_SUBDEVICE_PHASE88:
+ case ICE1712_SUBDEVICE_TS88:
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 8;
+ break;
+ case ICE1712_SUBDEVICE_EWS88D:
+ /* Note: not analog but ADAT I/O */
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 8;
+ break;
+ case ICE1712_SUBDEVICE_DMX6FIRE:
+ ice->num_total_dacs = 6;
+ ice->num_total_adcs = 6;
+ break;
+ }
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ /* create i2c */
+ err = snd_i2c_bus_create(ice->card, "ICE1712 GPIO 1", NULL, &ice->i2c);
+ if (err < 0) {
+ dev_err(ice->card->dev, "unable to create I2C bus\n");
+ return err;
+ }
+ ice->i2c->private_data = ice;
+ ice->i2c->hw_ops.bit = &snd_ice1712_ewx_cs8427_bit_ops;
+
+ /* create i2c devices */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_DMX6FIRE:
+ err = snd_i2c_device_create(ice->i2c, "PCF9554",
+ ICE1712_6FIRE_PCF9554_ADDR,
+ &spec->i2cdevs[EWS_I2C_6FIRE]);
+ if (err < 0) {
+ dev_err(ice->card->dev,
+ "PCF9554 initialization failed\n");
+ return err;
+ }
+ snd_ice1712_6fire_write_pca(ice, PCF9554_REG_CONFIG, 0x80);
+ break;
+ case ICE1712_SUBDEVICE_EWS88MT:
+ case ICE1712_SUBDEVICE_EWS88MT_NEW:
+ case ICE1712_SUBDEVICE_PHASE88:
+ case ICE1712_SUBDEVICE_TS88:
+
+ err = snd_i2c_device_create(ice->i2c, "CS8404",
+ ICE1712_EWS88MT_CS8404_ADDR,
+ &spec->i2cdevs[EWS_I2C_CS8404]);
+ if (err < 0)
+ return err;
+ err = snd_i2c_device_create(ice->i2c, "PCF8574 (1st)",
+ ICE1712_EWS88MT_INPUT_ADDR,
+ &spec->i2cdevs[EWS_I2C_PCF1]);
+ if (err < 0)
+ return err;
+ err = snd_i2c_device_create(ice->i2c, "PCF8574 (2nd)",
+ ICE1712_EWS88MT_OUTPUT_ADDR,
+ &spec->i2cdevs[EWS_I2C_PCF2]);
+ if (err < 0)
+ return err;
+ /* Check if the front module is connected */
+ err = snd_ice1712_ews88mt_chip_select(ice, 0x0f);
+ if (err < 0)
+ return err;
+ break;
+ case ICE1712_SUBDEVICE_EWS88D:
+ err = snd_i2c_device_create(ice->i2c, "PCF8575",
+ ICE1712_EWS88D_PCF_ADDR,
+ &spec->i2cdevs[EWS_I2C_88D]);
+ if (err < 0)
+ return err;
+ break;
+ }
+
+ /* set up SPDIF interface */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWX2496:
+ err = snd_ice1712_init_cs8427(ice, CS8427_BASE_ADDR);
+ if (err < 0)
+ return err;
+ snd_cs8427_reg_write(ice->cs8427, CS8427_REG_RECVERRMASK, CS8427_UNLOCK | CS8427_CONF | CS8427_BIP | CS8427_PAR);
+ break;
+ case ICE1712_SUBDEVICE_DMX6FIRE:
+ err = snd_ice1712_init_cs8427(ice, ICE1712_6FIRE_CS8427_ADDR);
+ if (err < 0)
+ return err;
+ snd_cs8427_reg_write(ice->cs8427, CS8427_REG_RECVERRMASK, CS8427_UNLOCK | CS8427_CONF | CS8427_BIP | CS8427_PAR);
+ break;
+ case ICE1712_SUBDEVICE_EWS88MT:
+ case ICE1712_SUBDEVICE_EWS88MT_NEW:
+ case ICE1712_SUBDEVICE_PHASE88:
+ case ICE1712_SUBDEVICE_TS88:
+ case ICE1712_SUBDEVICE_EWS88D:
+ /* set up CS8404 */
+ ice->spdif.ops.open = ews88_open_spdif;
+ ice->spdif.ops.setup_rate = ews88_setup_spdif;
+ ice->spdif.ops.default_get = ews88_spdif_default_get;
+ ice->spdif.ops.default_put = ews88_spdif_default_put;
+ ice->spdif.ops.stream_get = ews88_spdif_stream_get;
+ ice->spdif.ops.stream_put = ews88_spdif_stream_put;
+ /* Set spdif defaults */
+ snd_ice1712_ews_cs8404_spdif_write(ice, ice->spdif.cs8403_bits);
+ break;
+ }
+
+ /* no analog? */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWS88D:
+ return 0;
+ }
+
+ /* analog section */
+ ak = ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ak)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWS88MT:
+ case ICE1712_SUBDEVICE_EWS88MT_NEW:
+ case ICE1712_SUBDEVICE_PHASE88:
+ case ICE1712_SUBDEVICE_TS88:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_ews88mt, &akm_ews88mt_priv, ice);
+ break;
+ case ICE1712_SUBDEVICE_EWX2496:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_ewx2496, &akm_ewx2496_priv, ice);
+ break;
+ case ICE1712_SUBDEVICE_DMX6FIRE:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_6fire, &akm_6fire_priv, ice);
+ break;
+ default:
+ err = 0;
+ }
+
+ return err;
+}
+
+/*
+ * EWX 24/96 specific controls
+ */
+
+/* i/o sensitivity - this callback is shared among other devices, too */
+static int snd_ice1712_ewx_io_sense_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo){
+
+ static const char * const texts[2] = {
+ "+4dBu", "-10dBV",
+ };
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
+}
+
+static int snd_ice1712_ewx_io_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char mask = kcontrol->private_value & 0xff;
+
+ snd_ice1712_save_gpio_status(ice);
+ ucontrol->value.enumerated.item[0] = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA) & mask ? 1 : 0;
+ snd_ice1712_restore_gpio_status(ice);
+ return 0;
+}
+
+static int snd_ice1712_ewx_io_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char mask = kcontrol->private_value & 0xff;
+ int val, nval;
+
+ if (kcontrol->private_value & (1 << 31))
+ return -EPERM;
+ nval = ucontrol->value.enumerated.item[0] ? mask : 0;
+ snd_ice1712_save_gpio_status(ice);
+ val = snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+ nval |= val & ~mask;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, nval);
+ snd_ice1712_restore_gpio_status(ice);
+ return val != nval;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_ewx2496_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Input Sensitivity Switch",
+ .info = snd_ice1712_ewx_io_sense_info,
+ .get = snd_ice1712_ewx_io_sense_get,
+ .put = snd_ice1712_ewx_io_sense_put,
+ .private_value = ICE1712_EWX2496_AIN_SEL,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Output Sensitivity Switch",
+ .info = snd_ice1712_ewx_io_sense_info,
+ .get = snd_ice1712_ewx_io_sense_get,
+ .put = snd_ice1712_ewx_io_sense_put,
+ .private_value = ICE1712_EWX2496_AOUT_SEL,
+ },
+};
+
+
+/*
+ * EWS88MT specific controls
+ */
+/* analog output sensitivity;; address 0x48 bit 6 */
+static int snd_ice1712_ews88mt_output_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct ews_spec *spec = ice->spec;
+ unsigned char data;
+
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ snd_i2c_unlock(ice->i2c);
+ ucontrol->value.enumerated.item[0] = data & ICE1712_EWS88MT_OUTPUT_SENSE ? 1 : 0; /* high = -10dBV, low = +4dBu */
+ return 0;
+}
+
+/* analog output sensitivity;; address 0x48 bit 6 */
+static int snd_ice1712_ews88mt_output_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct ews_spec *spec = ice->spec;
+ unsigned char data, ndata;
+
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF2], &data, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ ndata = (data & ~ICE1712_EWS88MT_OUTPUT_SENSE) | (ucontrol->value.enumerated.item[0] ? ICE1712_EWS88MT_OUTPUT_SENSE : 0);
+ if (ndata != data && snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2],
+ &ndata, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ snd_i2c_unlock(ice->i2c);
+ return ndata != data;
+}
+
+/* analog input sensitivity; address 0x46 */
+static int snd_ice1712_ews88mt_input_sense_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct ews_spec *spec = ice->spec;
+ int channel = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned char data;
+
+ if (snd_BUG_ON(channel < 0 || channel > 7))
+ return 0;
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF1], &data, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ /* reversed; high = +4dBu, low = -10dBV */
+ ucontrol->value.enumerated.item[0] = data & (1 << channel) ? 0 : 1;
+ snd_i2c_unlock(ice->i2c);
+ return 0;
+}
+
+/* analog output sensitivity; address 0x46 */
+static int snd_ice1712_ews88mt_input_sense_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct ews_spec *spec = ice->spec;
+ int channel = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned char data, ndata;
+
+ if (snd_BUG_ON(channel < 0 || channel > 7))
+ return 0;
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_PCF1], &data, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ ndata = (data & ~(1 << channel)) | (ucontrol->value.enumerated.item[0] ? 0 : (1 << channel));
+ if (ndata != data && snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF1],
+ &ndata, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ snd_i2c_unlock(ice->i2c);
+ return ndata != data;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_ews88mt_input_sense = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Input Sensitivity Switch",
+ .info = snd_ice1712_ewx_io_sense_info,
+ .get = snd_ice1712_ews88mt_input_sense_get,
+ .put = snd_ice1712_ews88mt_input_sense_put,
+ .count = 8,
+};
+
+static const struct snd_kcontrol_new snd_ice1712_ews88mt_output_sense = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Output Sensitivity Switch",
+ .info = snd_ice1712_ewx_io_sense_info,
+ .get = snd_ice1712_ews88mt_output_sense_get,
+ .put = snd_ice1712_ews88mt_output_sense_put,
+};
+
+
+/*
+ * EWS88D specific controls
+ */
+
+#define snd_ice1712_ews88d_control_info snd_ctl_boolean_mono_info
+
+static int snd_ice1712_ews88d_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct ews_spec *spec = ice->spec;
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value >> 8) & 1;
+ unsigned char data[2];
+
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ snd_i2c_unlock(ice->i2c);
+ data[0] = (data[shift >> 3] >> (shift & 7)) & 0x01;
+ if (invert)
+ data[0] ^= 0x01;
+ ucontrol->value.integer.value[0] = data[0];
+ return 0;
+}
+
+static int snd_ice1712_ews88d_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct ews_spec *spec = ice->spec;
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value >> 8) & 1;
+ unsigned char data[2], ndata[2];
+ int change;
+
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ ndata[shift >> 3] = data[shift >> 3] & ~(1 << (shift & 7));
+ if (invert) {
+ if (! ucontrol->value.integer.value[0])
+ ndata[shift >> 3] |= (1 << (shift & 7));
+ } else {
+ if (ucontrol->value.integer.value[0])
+ ndata[shift >> 3] |= (1 << (shift & 7));
+ }
+ change = (data[shift >> 3] != ndata[shift >> 3]);
+ if (change &&
+ snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_88D], data, 2) != 2) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ snd_i2c_unlock(ice->i2c);
+ return change;
+}
+
+#define EWS88D_CONTROL(xiface, xname, xshift, xinvert, xaccess) \
+{ .iface = xiface,\
+ .name = xname,\
+ .access = xaccess,\
+ .info = snd_ice1712_ews88d_control_info,\
+ .get = snd_ice1712_ews88d_control_get,\
+ .put = snd_ice1712_ews88d_control_put,\
+ .private_value = xshift | (xinvert << 8),\
+}
+
+static const struct snd_kcontrol_new snd_ice1712_ews88d_controls[] = {
+ EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "IEC958 Input Optical", 0, 1, 0), /* inverted */
+ EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT Output Optical", 1, 0, 0),
+ EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT External Master Clock", 2, 0, 0),
+ EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "Enable ADAT", 3, 0, 0),
+ EWS88D_CONTROL(SNDRV_CTL_ELEM_IFACE_MIXER, "ADAT Through", 4, 1, 0),
+};
+
+
+/*
+ * DMX 6Fire specific controls
+ */
+
+static int snd_ice1712_6fire_read_pca(struct snd_ice1712 *ice, unsigned char reg)
+{
+ unsigned char byte;
+ struct ews_spec *spec = ice->spec;
+
+ snd_i2c_lock(ice->i2c);
+ byte = reg;
+ if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_6FIRE], &byte, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ dev_err(ice->card->dev, "cannot send pca\n");
+ return -EIO;
+ }
+
+ byte = 0;
+ if (snd_i2c_readbytes(spec->i2cdevs[EWS_I2C_6FIRE], &byte, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ dev_err(ice->card->dev, "cannot read pca\n");
+ return -EIO;
+ }
+ snd_i2c_unlock(ice->i2c);
+ return byte;
+}
+
+static int snd_ice1712_6fire_write_pca(struct snd_ice1712 *ice, unsigned char reg, unsigned char data)
+{
+ unsigned char bytes[2];
+ struct ews_spec *spec = ice->spec;
+
+ snd_i2c_lock(ice->i2c);
+ bytes[0] = reg;
+ bytes[1] = data;
+ if (snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_6FIRE], bytes, 2) != 2) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ snd_i2c_unlock(ice->i2c);
+ return 0;
+}
+
+#define snd_ice1712_6fire_control_info snd_ctl_boolean_mono_info
+
+static int snd_ice1712_6fire_control_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value >> 8) & 1;
+ int data;
+
+ data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT);
+ if (data < 0)
+ return data;
+ data = (data >> shift) & 1;
+ if (invert)
+ data ^= 1;
+ ucontrol->value.integer.value[0] = data;
+ return 0;
+}
+
+static int snd_ice1712_6fire_control_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value >> 8) & 1;
+ int data, ndata;
+
+ data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT);
+ if (data < 0)
+ return data;
+ ndata = data & ~(1 << shift);
+ if (ucontrol->value.integer.value[0])
+ ndata |= (1 << shift);
+ if (invert)
+ ndata ^= (1 << shift);
+ if (data != ndata) {
+ snd_ice1712_6fire_write_pca(ice, PCF9554_REG_OUTPUT, (unsigned char)ndata);
+ return 1;
+ }
+ return 0;
+}
+
+static int snd_ice1712_6fire_select_input_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[4] = {
+ "Internal", "Front Input", "Rear Input", "Wave Table"
+ };
+ return snd_ctl_enum_info(uinfo, 1, 4, texts);
+}
+
+static int snd_ice1712_6fire_select_input_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int data;
+
+ data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT);
+ if (data < 0)
+ return data;
+ ucontrol->value.integer.value[0] = data & 3;
+ return 0;
+}
+
+static int snd_ice1712_6fire_select_input_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int data, ndata;
+
+ data = snd_ice1712_6fire_read_pca(ice, PCF9554_REG_OUTPUT);
+ if (data < 0)
+ return data;
+ ndata = data & ~3;
+ ndata |= (ucontrol->value.integer.value[0] & 3);
+ if (data != ndata) {
+ snd_ice1712_6fire_write_pca(ice, PCF9554_REG_OUTPUT, (unsigned char)ndata);
+ return 1;
+ }
+ return 0;
+}
+
+
+#define DMX6FIRE_CONTROL(xname, xshift, xinvert) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+ .name = xname,\
+ .info = snd_ice1712_6fire_control_info,\
+ .get = snd_ice1712_6fire_control_get,\
+ .put = snd_ice1712_6fire_control_put,\
+ .private_value = xshift | (xinvert << 8),\
+}
+
+static const struct snd_kcontrol_new snd_ice1712_6fire_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Input Select",
+ .info = snd_ice1712_6fire_select_input_info,
+ .get = snd_ice1712_6fire_select_input_get,
+ .put = snd_ice1712_6fire_select_input_put,
+ },
+ DMX6FIRE_CONTROL("Front Digital Input Switch", 2, 1),
+ // DMX6FIRE_CONTROL("Master Clock Select", 3, 0),
+ DMX6FIRE_CONTROL("Optical Digital Input Switch", 4, 0),
+ DMX6FIRE_CONTROL("Phono Analog Input Switch", 5, 0),
+ DMX6FIRE_CONTROL("Breakbox LED", 6, 0),
+};
+
+
+static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice)
+{
+ unsigned int idx;
+ int err;
+
+ /* all terratec cards have spdif, but cs8427 module builds it's own controls */
+ if (ice->cs8427 == NULL) {
+ err = snd_ice1712_spdif_build_controls(ice);
+ if (err < 0)
+ return err;
+ }
+
+ /* ak4524 controls */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWX2496:
+ case ICE1712_SUBDEVICE_EWS88MT:
+ case ICE1712_SUBDEVICE_EWS88MT_NEW:
+ case ICE1712_SUBDEVICE_PHASE88:
+ case ICE1712_SUBDEVICE_TS88:
+ case ICE1712_SUBDEVICE_DMX6FIRE:
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+ break;
+ }
+
+ /* card specific controls */
+ switch (ice->eeprom.subvendor) {
+ case ICE1712_SUBDEVICE_EWX2496:
+ for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_ewx2496_controls); idx++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ewx2496_controls[idx], ice));
+ if (err < 0)
+ return err;
+ }
+ break;
+ case ICE1712_SUBDEVICE_EWS88MT:
+ case ICE1712_SUBDEVICE_EWS88MT_NEW:
+ case ICE1712_SUBDEVICE_PHASE88:
+ case ICE1712_SUBDEVICE_TS88:
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_input_sense, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_output_sense, ice));
+ if (err < 0)
+ return err;
+ break;
+ case ICE1712_SUBDEVICE_EWS88D:
+ for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_ews88d_controls); idx++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88d_controls[idx], ice));
+ if (err < 0)
+ return err;
+ }
+ break;
+ case ICE1712_SUBDEVICE_DMX6FIRE:
+ for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_6fire_controls); idx++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_6fire_controls[idx], ice));
+ if (err < 0)
+ return err;
+ }
+ break;
+ }
+ return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_ice1712_ews_cards[] = {
+ {
+ .subvendor = ICE1712_SUBDEVICE_EWX2496,
+ .name = "TerraTec EWX24/96",
+ .model = "ewx2496",
+ .chip_init = snd_ice1712_ews_init,
+ .build_controls = snd_ice1712_ews_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_EWS88MT,
+ .name = "TerraTec EWS88MT",
+ .model = "ews88mt",
+ .chip_init = snd_ice1712_ews_init,
+ .build_controls = snd_ice1712_ews_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_EWS88MT_NEW,
+ .name = "TerraTec EWS88MT",
+ .model = "ews88mt_new",
+ .chip_init = snd_ice1712_ews_init,
+ .build_controls = snd_ice1712_ews_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_PHASE88,
+ .name = "TerraTec Phase88",
+ .model = "phase88",
+ .chip_init = snd_ice1712_ews_init,
+ .build_controls = snd_ice1712_ews_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_TS88,
+ .name = "terrasoniq TS88",
+ .model = "phase88",
+ .chip_init = snd_ice1712_ews_init,
+ .build_controls = snd_ice1712_ews_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_EWS88D,
+ .name = "TerraTec EWS88D",
+ .model = "ews88d",
+ .chip_init = snd_ice1712_ews_init,
+ .build_controls = snd_ice1712_ews_add_controls,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_DMX6FIRE,
+ .name = "TerraTec DMX6Fire",
+ .model = "dmx6fire",
+ .chip_init = snd_ice1712_ews_init,
+ .build_controls = snd_ice1712_ews_add_controls,
+ .mpu401_1_name = "MIDI-Front DMX6fire",
+ .mpu401_2_name = "Wavetable DMX6fire",
+ .mpu401_2_info_flags = MPU401_INFO_OUTPUT,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/ews.h b/sound/pci/ice1712/ews.h
new file mode 100644
index 0000000000..aec8dc69a9
--- /dev/null
+++ b/sound/pci/ice1712/ews.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_EWS_H
+#define __SOUND_EWS_H
+
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for Terratec EWS88MT/D, EWX24/96, DMX 6Fire
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ * 2002 Takashi Iwai <tiwai@suse.de>
+ */
+
+#define EWS_DEVICE_DESC \
+ "{TerraTec,EWX 24/96},"\
+ "{TerraTec,EWS 88MT},"\
+ "{TerraTec,EWS 88D},"\
+ "{TerraTec,DMX 6Fire},"\
+ "{TerraTec,Phase 88}," \
+ "{terrasoniq,TS 88},"
+
+#define ICE1712_SUBDEVICE_EWX2496 0x3b153011
+#define ICE1712_SUBDEVICE_EWS88MT 0x3b151511
+#define ICE1712_SUBDEVICE_EWS88MT_NEW 0x3b152511
+#define ICE1712_SUBDEVICE_EWS88D 0x3b152b11
+#define ICE1712_SUBDEVICE_DMX6FIRE 0x3b153811
+#define ICE1712_SUBDEVICE_PHASE88 0x3b155111
+#define ICE1712_SUBDEVICE_TS88 0x3b157c11
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_ice1712_ews_cards[];
+
+
+/* TerraTec EWX 24/96 configuration definitions */
+
+#define ICE1712_EWX2496_AK4524_CS 0x01 /* AK4524 chip select; low = active */
+#define ICE1712_EWX2496_AIN_SEL 0x02 /* input sensitivity switch; high = louder */
+#define ICE1712_EWX2496_AOUT_SEL 0x04 /* output sensitivity switch; high = louder */
+#define ICE1712_EWX2496_RW 0x08 /* read/write switch for i2c; high = write */
+#define ICE1712_EWX2496_SERIAL_DATA 0x10 /* i2c & ak4524 data */
+#define ICE1712_EWX2496_SERIAL_CLOCK 0x20 /* i2c & ak4524 clock */
+#define ICE1712_EWX2496_TX2 0x40 /* MIDI2 (not used) */
+#define ICE1712_EWX2496_RX2 0x80 /* MIDI2 (not used) */
+
+/* TerraTec EWS 88MT/D configuration definitions */
+/* RW, SDA snd SCLK are identical with EWX24/96 */
+#define ICE1712_EWS88_CS8414_RATE 0x07 /* CS8414 sample rate: gpio 0-2 */
+#define ICE1712_EWS88_RW 0x08 /* read/write switch for i2c; high = write */
+#define ICE1712_EWS88_SERIAL_DATA 0x10 /* i2c & ak4524 data */
+#define ICE1712_EWS88_SERIAL_CLOCK 0x20 /* i2c & ak4524 clock */
+#define ICE1712_EWS88_TX2 0x40 /* MIDI2 (only on 88D) */
+#define ICE1712_EWS88_RX2 0x80 /* MIDI2 (only on 88D) */
+
+/* i2c address */
+#define ICE1712_EWS88MT_CS8404_ADDR (0x40>>1)
+#define ICE1712_EWS88MT_INPUT_ADDR (0x46>>1)
+#define ICE1712_EWS88MT_OUTPUT_ADDR (0x48>>1)
+#define ICE1712_EWS88MT_OUTPUT_SENSE 0x40 /* mask */
+#define ICE1712_EWS88D_PCF_ADDR (0x40>>1)
+
+/* TerraTec DMX 6Fire configuration definitions */
+#define ICE1712_6FIRE_AK4524_CS_MASK 0x07 /* AK4524 chip select #1-#3 */
+#define ICE1712_6FIRE_RW 0x08 /* read/write switch for i2c; high = write */
+#define ICE1712_6FIRE_SERIAL_DATA 0x10 /* i2c & ak4524 data */
+#define ICE1712_6FIRE_SERIAL_CLOCK 0x20 /* i2c & ak4524 clock */
+#define ICE1712_6FIRE_TX2 0x40 /* MIDI2 */
+#define ICE1712_6FIRE_RX2 0x80 /* MIDI2 */
+
+#define ICE1712_6FIRE_PCF9554_ADDR (0x40>>1)
+#define ICE1712_6FIRE_CS8427_ADDR (0x22)
+
+#endif /* __SOUND_EWS_H */
diff --git a/sound/pci/ice1712/hoontech.c b/sound/pci/ice1712/hoontech.c
new file mode 100644
index 0000000000..46daeea8dc
--- /dev/null
+++ b/sound/pci/ice1712/hoontech.c
@@ -0,0 +1,370 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for Hoontech STDSP24
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "hoontech.h"
+
+/* Hoontech-specific setting */
+struct hoontech_spec {
+ unsigned char boxbits[4];
+ unsigned int config;
+ unsigned short boxconfig[4];
+};
+
+static void snd_ice1712_stdsp24_gpio_write(struct snd_ice1712 *ice, unsigned char byte)
+{
+ byte |= ICE1712_STDSP24_CLOCK_BIT;
+ udelay(100);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte);
+ byte &= ~ICE1712_STDSP24_CLOCK_BIT;
+ udelay(100);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte);
+ byte |= ICE1712_STDSP24_CLOCK_BIT;
+ udelay(100);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, byte);
+}
+
+static void snd_ice1712_stdsp24_darear(struct snd_ice1712 *ice, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_0_DAREAR(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_mute(struct snd_ice1712 *ice, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_3_MUTE(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_insel(struct snd_ice1712 *ice, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_3_INSEL(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_box_channel(struct snd_ice1712 *ice, int box, int chn, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+
+ mutex_lock(&ice->gpio_mutex);
+
+ /* select box */
+ ICE1712_STDSP24_0_BOX(spec->boxbits, box);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]);
+
+ /* prepare for write */
+ if (chn == 3)
+ ICE1712_STDSP24_2_CHN4(spec->boxbits, 0);
+ ICE1712_STDSP24_2_MIDI1(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+
+ ICE1712_STDSP24_1_CHN1(spec->boxbits, 1);
+ ICE1712_STDSP24_1_CHN2(spec->boxbits, 1);
+ ICE1712_STDSP24_1_CHN3(spec->boxbits, 1);
+ ICE1712_STDSP24_2_CHN4(spec->boxbits, 1);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+ udelay(100);
+ if (chn == 3) {
+ ICE1712_STDSP24_2_CHN4(spec->boxbits, 0);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+ } else {
+ switch (chn) {
+ case 0: ICE1712_STDSP24_1_CHN1(spec->boxbits, 0); break;
+ case 1: ICE1712_STDSP24_1_CHN2(spec->boxbits, 0); break;
+ case 2: ICE1712_STDSP24_1_CHN3(spec->boxbits, 0); break;
+ }
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]);
+ }
+ udelay(100);
+ ICE1712_STDSP24_1_CHN1(spec->boxbits, 1);
+ ICE1712_STDSP24_1_CHN2(spec->boxbits, 1);
+ ICE1712_STDSP24_1_CHN3(spec->boxbits, 1);
+ ICE1712_STDSP24_2_CHN4(spec->boxbits, 1);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[1]);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+ udelay(100);
+
+ ICE1712_STDSP24_2_MIDI1(spec->boxbits, 0);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_box_midi(struct snd_ice1712 *ice, int box, int master)
+{
+ struct hoontech_spec *spec = ice->spec;
+
+ mutex_lock(&ice->gpio_mutex);
+
+ /* select box */
+ ICE1712_STDSP24_0_BOX(spec->boxbits, box);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[0]);
+
+ ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1);
+ ICE1712_STDSP24_2_MIDI1(spec->boxbits, master);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+
+ udelay(100);
+
+ ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 0);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+
+ mdelay(10);
+
+ ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[2]);
+
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void snd_ice1712_stdsp24_midi2(struct snd_ice1712 *ice, int activate)
+{
+ struct hoontech_spec *spec = ice->spec;
+ mutex_lock(&ice->gpio_mutex);
+ ICE1712_STDSP24_3_MIDI2(spec->boxbits, activate);
+ snd_ice1712_stdsp24_gpio_write(ice, spec->boxbits[3]);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static int hoontech_init(struct snd_ice1712 *ice, bool staudio)
+{
+ struct hoontech_spec *spec;
+ int box, chn;
+
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 8;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ ICE1712_STDSP24_SET_ADDR(spec->boxbits, 0);
+ ICE1712_STDSP24_CLOCK(spec->boxbits, 0, 1);
+ ICE1712_STDSP24_0_BOX(spec->boxbits, 0);
+ ICE1712_STDSP24_0_DAREAR(spec->boxbits, 0);
+
+ ICE1712_STDSP24_SET_ADDR(spec->boxbits, 1);
+ ICE1712_STDSP24_CLOCK(spec->boxbits, 1, 1);
+ ICE1712_STDSP24_1_CHN1(spec->boxbits, 1);
+ ICE1712_STDSP24_1_CHN2(spec->boxbits, 1);
+ ICE1712_STDSP24_1_CHN3(spec->boxbits, 1);
+
+ ICE1712_STDSP24_SET_ADDR(spec->boxbits, 2);
+ ICE1712_STDSP24_CLOCK(spec->boxbits, 2, 1);
+ ICE1712_STDSP24_2_CHN4(spec->boxbits, 1);
+ ICE1712_STDSP24_2_MIDIIN(spec->boxbits, 1);
+ ICE1712_STDSP24_2_MIDI1(spec->boxbits, 0);
+
+ ICE1712_STDSP24_SET_ADDR(spec->boxbits, 3);
+ ICE1712_STDSP24_CLOCK(spec->boxbits, 3, 1);
+ ICE1712_STDSP24_3_MIDI2(spec->boxbits, 0);
+ ICE1712_STDSP24_3_MUTE(spec->boxbits, 1);
+ ICE1712_STDSP24_3_INSEL(spec->boxbits, 0);
+
+ /* let's go - activate only functions in first box */
+ if (staudio)
+ spec->config = ICE1712_STDSP24_MUTE;
+ else
+ spec->config = 0;
+ /* ICE1712_STDSP24_MUTE |
+ ICE1712_STDSP24_INSEL |
+ ICE1712_STDSP24_DAREAR; */
+ /* These boxconfigs have caused problems in the past.
+ * The code is not optimal, but should now enable a working config to
+ * be achieved.
+ * ** MIDI IN can only be configured on one box **
+ * ICE1712_STDSP24_BOX_MIDI1 needs to be set for that box.
+ * Tests on a ADAC2000 box suggest the box config flags do not
+ * work as would be expected, and the inputs are crossed.
+ * Setting ICE1712_STDSP24_BOX_MIDI1 and ICE1712_STDSP24_BOX_MIDI2
+ * on the same box connects MIDI-In to both 401 uarts; both outputs
+ * are then active on all boxes.
+ * The default config here sets up everything on the first box.
+ * Alan Horstmann 5.2.2008
+ */
+ spec->boxconfig[0] = ICE1712_STDSP24_BOX_CHN1 |
+ ICE1712_STDSP24_BOX_CHN2 |
+ ICE1712_STDSP24_BOX_CHN3 |
+ ICE1712_STDSP24_BOX_CHN4 |
+ ICE1712_STDSP24_BOX_MIDI1 |
+ ICE1712_STDSP24_BOX_MIDI2;
+ if (staudio) {
+ spec->boxconfig[1] =
+ spec->boxconfig[2] =
+ spec->boxconfig[3] = spec->boxconfig[0];
+ } else {
+ spec->boxconfig[1] =
+ spec->boxconfig[2] =
+ spec->boxconfig[3] = 0;
+ }
+
+ snd_ice1712_stdsp24_darear(ice,
+ (spec->config & ICE1712_STDSP24_DAREAR) ? 1 : 0);
+ snd_ice1712_stdsp24_mute(ice,
+ (spec->config & ICE1712_STDSP24_MUTE) ? 1 : 0);
+ snd_ice1712_stdsp24_insel(ice,
+ (spec->config & ICE1712_STDSP24_INSEL) ? 1 : 0);
+ for (box = 0; box < 4; box++) {
+ if (spec->boxconfig[box] & ICE1712_STDSP24_BOX_MIDI2)
+ snd_ice1712_stdsp24_midi2(ice, 1);
+ for (chn = 0; chn < 4; chn++)
+ snd_ice1712_stdsp24_box_channel(ice, box, chn,
+ (spec->boxconfig[box] & (1 << chn)) ? 1 : 0);
+ if (spec->boxconfig[box] & ICE1712_STDSP24_BOX_MIDI1)
+ snd_ice1712_stdsp24_box_midi(ice, box, 1);
+ }
+
+ return 0;
+}
+
+static int snd_ice1712_hoontech_init(struct snd_ice1712 *ice)
+{
+ return hoontech_init(ice, false);
+}
+
+static int snd_ice1712_staudio_init(struct snd_ice1712 *ice)
+{
+ return hoontech_init(ice, true);
+}
+
+/*
+ * AK4524 access
+ */
+
+/* start callback for STDSP24 with modified hardware */
+static void stdsp24_ak4524_lock(struct snd_akm4xxx *ak, int chip)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+ unsigned char tmp;
+ snd_ice1712_save_gpio_status(ice);
+ tmp = ICE1712_STDSP24_SERIAL_DATA |
+ ICE1712_STDSP24_SERIAL_CLOCK |
+ ICE1712_STDSP24_AK4524_CS;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+ ice->gpio.direction | tmp);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ~tmp);
+}
+
+static int snd_ice1712_value_init(struct snd_ice1712 *ice)
+{
+ /* Hoontech STDSP24 with modified hardware */
+ static const struct snd_akm4xxx akm_stdsp24_mv = {
+ .num_adcs = 2,
+ .num_dacs = 2,
+ .type = SND_AK4524,
+ .ops = {
+ .lock = stdsp24_ak4524_lock
+ }
+ };
+
+ static const struct snd_ak4xxx_private akm_stdsp24_mv_priv = {
+ .caddr = 2,
+ .cif = 1, /* CIF high */
+ .data_mask = ICE1712_STDSP24_SERIAL_DATA,
+ .clk_mask = ICE1712_STDSP24_SERIAL_CLOCK,
+ .cs_mask = ICE1712_STDSP24_AK4524_CS,
+ .cs_addr = ICE1712_STDSP24_AK4524_CS,
+ .cs_none = 0,
+ .add_flags = 0,
+ };
+
+ int err;
+ struct snd_akm4xxx *ak;
+
+ /* set the analog DACs */
+ ice->num_total_dacs = 2;
+
+ /* set the analog ADCs */
+ ice->num_total_adcs = 2;
+
+ /* analog section */
+ ak = ice->akm = kmalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ak)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ err = snd_ice1712_akm4xxx_init(ak, &akm_stdsp24_mv, &akm_stdsp24_mv_priv, ice);
+ if (err < 0)
+ return err;
+
+ /* ak4524 controls */
+ return snd_ice1712_akm4xxx_build_controls(ice);
+}
+
+static int snd_ice1712_ez8_init(struct snd_ice1712 *ice)
+{
+ ice->gpio.write_mask = ice->eeprom.gpiomask;
+ ice->gpio.direction = ice->eeprom.gpiodir;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, ice->eeprom.gpiomask);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, ice->eeprom.gpiodir);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, ice->eeprom.gpiostate);
+ return 0;
+}
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_ice1712_hoontech_cards[] = {
+ {
+ .subvendor = ICE1712_SUBDEVICE_STDSP24,
+ .name = "Hoontech SoundTrack Audio DSP24",
+ .model = "dsp24",
+ .chip_init = snd_ice1712_hoontech_init,
+ .mpu401_1_name = "MIDI-1 Hoontech/STA DSP24",
+ .mpu401_2_name = "MIDI-2 Hoontech/STA DSP24",
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_STDSP24_VALUE, /* a dummy id */
+ .name = "Hoontech SoundTrack Audio DSP24 Value",
+ .model = "dsp24_value",
+ .chip_init = snd_ice1712_value_init,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_STDSP24_MEDIA7_1,
+ .name = "Hoontech STA DSP24 Media 7.1",
+ .model = "dsp24_71",
+ .chip_init = snd_ice1712_hoontech_init,
+ },
+ {
+ .subvendor = ICE1712_SUBDEVICE_EVENT_EZ8, /* a dummy id */
+ .name = "Event Electronics EZ8",
+ .model = "ez8",
+ .chip_init = snd_ice1712_ez8_init,
+ },
+ {
+ /* STAudio ADCIII has the same SSID as Hoontech StA DSP24,
+ * thus identified only via the explicit model option
+ */
+ .subvendor = ICE1712_SUBDEVICE_STAUDIO_ADCIII, /* a dummy id */
+ .name = "STAudio ADCIII",
+ .model = "staudio",
+ .chip_init = snd_ice1712_staudio_init,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/hoontech.h b/sound/pci/ice1712/hoontech.h
new file mode 100644
index 0000000000..89404ceecb
--- /dev/null
+++ b/sound/pci/ice1712/hoontech.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_HOONTECH_H
+#define __SOUND_HOONTECH_H
+
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for Hoontech STDSP24
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#define HOONTECH_DEVICE_DESC \
+ "{Hoontech,SoundTrack DSP 24}," \
+ "{Hoontech,SoundTrack DSP 24 Value}," \
+ "{Hoontech,SoundTrack DSP 24 Media 7.1}," \
+ "{Event Electronics,EZ8},"
+
+#define ICE1712_SUBDEVICE_STDSP24 0x12141217 /* Hoontech SoundTrack Audio DSP 24 */
+#define ICE1712_SUBDEVICE_STDSP24_VALUE 0x00010010 /* A dummy id for Hoontech SoundTrack Audio DSP 24 Value */
+#define ICE1712_SUBDEVICE_STDSP24_MEDIA7_1 0x16141217 /* Hoontech ST Audio DSP24 Media 7.1 */
+#define ICE1712_SUBDEVICE_EVENT_EZ8 0x00010001 /* A dummy id for EZ8 */
+#define ICE1712_SUBDEVICE_STAUDIO_ADCIII 0x00010002 /* A dummy id for STAudio ADCIII */
+
+extern struct snd_ice1712_card_info snd_ice1712_hoontech_cards[];
+
+
+/* Hoontech SoundTrack Audio DSP 24 GPIO definitions */
+
+#define ICE1712_STDSP24_0_BOX(r, x) r[0] = ((r[0] & ~3) | ((x)&3))
+#define ICE1712_STDSP24_0_DAREAR(r, x) r[0] = ((r[0] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_1_CHN1(r, x) r[1] = ((r[1] & ~1) | ((x)&1))
+#define ICE1712_STDSP24_1_CHN2(r, x) r[1] = ((r[1] & ~2) | (((x)&1)<<1))
+#define ICE1712_STDSP24_1_CHN3(r, x) r[1] = ((r[1] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_2_CHN4(r, x) r[2] = ((r[2] & ~1) | ((x)&1))
+#define ICE1712_STDSP24_2_MIDIIN(r, x) r[2] = ((r[2] & ~2) | (((x)&1)<<1))
+#define ICE1712_STDSP24_2_MIDI1(r, x) r[2] = ((r[2] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_3_MIDI2(r, x) r[3] = ((r[3] & ~1) | ((x)&1))
+#define ICE1712_STDSP24_3_MUTE(r, x) r[3] = ((r[3] & ~2) | (((x)&1)<<1))
+#define ICE1712_STDSP24_3_INSEL(r, x) r[3] = ((r[3] & ~4) | (((x)&1)<<2))
+#define ICE1712_STDSP24_SET_ADDR(r, a) r[a&3] = ((r[a&3] & ~0x18) | (((a)&3)<<3))
+#define ICE1712_STDSP24_CLOCK(r, a, c) r[a&3] = ((r[a&3] & ~0x20) | (((c)&1)<<5))
+#define ICE1712_STDSP24_CLOCK_BIT (1<<5)
+
+/* Hoontech SoundTrack Audio DSP 24 box configuration definitions */
+
+#define ICE1712_STDSP24_DAREAR (1<<0)
+#define ICE1712_STDSP24_MUTE (1<<1)
+#define ICE1712_STDSP24_INSEL (1<<2)
+
+#define ICE1712_STDSP24_BOX_CHN1 (1<<0) /* input channel 1 */
+#define ICE1712_STDSP24_BOX_CHN2 (1<<1) /* input channel 2 */
+#define ICE1712_STDSP24_BOX_CHN3 (1<<2) /* input channel 3 */
+#define ICE1712_STDSP24_BOX_CHN4 (1<<3) /* input channel 4 */
+#define ICE1712_STDSP24_BOX_MIDI1 (1<<8)
+#define ICE1712_STDSP24_BOX_MIDI2 (1<<9)
+
+/* Hoontech SoundTrack Audio DSP 24 Value definitions for modified hardware */
+
+#define ICE1712_STDSP24_AK4524_CS 0x03 /* AK4524 chip select; low = active */
+#define ICE1712_STDSP24_SERIAL_DATA 0x0c /* ak4524 data */
+#define ICE1712_STDSP24_SERIAL_CLOCK 0x30 /* ak4524 clock */
+
+#endif /* __SOUND_HOONTECH_H */
diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c
new file mode 100644
index 0000000000..3b0c3e7098
--- /dev/null
+++ b/sound/pci/ice1712/ice1712.c
@@ -0,0 +1,2750 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+/*
+ NOTES:
+ - spdif nonaudio consumer mode does not work (at least with my
+ Sony STR-DB830)
+*/
+
+/*
+ * Changes:
+ *
+ * 2002.09.09 Takashi Iwai <tiwai@suse.de>
+ * split the code to several files. each low-level routine
+ * is stored in the local file and called from registration
+ * function from card_info struct.
+ *
+ * 2002.11.26 James Stafford <jstafford@ampltd.com>
+ * Added support for VT1724 (Envy24HT)
+ * I have left out support for 176.4 and 192 KHz for the moment.
+ * I also haven't done anything with the internal S/PDIF transmitter or the MPU-401
+ *
+ * 2003.02.20 Taksahi Iwai <tiwai@suse.de>
+ * Split vt1724 part to an independent driver.
+ * The GPIO is accessed through the callback functions now.
+ *
+ * 2004.03.31 Doug McLain <nostar@comcast.net>
+ * Added support for Event Electronics EZ8 card to hoontech.c.
+ */
+
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/cs8427.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+
+/* lowlevel routines */
+#include "delta.h"
+#include "ews.h"
+#include "hoontech.h"
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
+MODULE_DESCRIPTION("ICEnsemble ICE1712 (Envy24)");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
+static char *model[SNDRV_CARDS];
+static bool omni[SNDRV_CARDS]; /* Delta44 & 66 Omni I/O support */
+static int cs8427_timeout[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = 500}; /* CS8427 S/PDIF transceiver reset timeout value in msec */
+static int dxr_enable[SNDRV_CARDS]; /* DXR enable for DMX6FIRE */
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ICE1712 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ICE1712 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ICE1712 soundcard.");
+module_param_array(omni, bool, NULL, 0444);
+MODULE_PARM_DESC(omni, "Enable Midiman M-Audio Delta Omni I/O support.");
+module_param_array(cs8427_timeout, int, NULL, 0444);
+MODULE_PARM_DESC(cs8427_timeout, "Define reset timeout for cs8427 chip in msec resolution.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Use the given board model.");
+module_param_array(dxr_enable, int, NULL, 0444);
+MODULE_PARM_DESC(dxr_enable, "Enable DXR support for Terratec DMX6FIRE.");
+
+
+static const struct pci_device_id snd_ice1712_ids[] = {
+ { PCI_VDEVICE(ICE, PCI_DEVICE_ID_ICE_1712), 0 }, /* ICE1712 */
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_ice1712_ids);
+
+static int snd_ice1712_build_pro_mixer(struct snd_ice1712 *ice);
+static int snd_ice1712_build_controls(struct snd_ice1712 *ice);
+
+static int PRO_RATE_LOCKED;
+static int PRO_RATE_RESET = 1;
+static unsigned int PRO_RATE_DEFAULT = 44100;
+
+/*
+ * Basic I/O
+ */
+
+/* check whether the clock mode is spdif-in */
+static inline int is_spdif_master(struct snd_ice1712 *ice)
+{
+ return (inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER) ? 1 : 0;
+}
+
+static inline int is_pro_rate_locked(struct snd_ice1712 *ice)
+{
+ return is_spdif_master(ice) || PRO_RATE_LOCKED;
+}
+
+static inline void snd_ice1712_ds_write(struct snd_ice1712 *ice, u8 channel, u8 addr, u32 data)
+{
+ outb((channel << 4) | addr, ICEDS(ice, INDEX));
+ outl(data, ICEDS(ice, DATA));
+}
+
+static inline u32 snd_ice1712_ds_read(struct snd_ice1712 *ice, u8 channel, u8 addr)
+{
+ outb((channel << 4) | addr, ICEDS(ice, INDEX));
+ return inl(ICEDS(ice, DATA));
+}
+
+static void snd_ice1712_ac97_write(struct snd_ac97 *ac97,
+ unsigned short reg,
+ unsigned short val)
+{
+ struct snd_ice1712 *ice = ac97->private_data;
+ int tm;
+ unsigned char old_cmd = 0;
+
+ for (tm = 0; tm < 0x10000; tm++) {
+ old_cmd = inb(ICEREG(ice, AC97_CMD));
+ if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+ continue;
+ if (!(old_cmd & ICE1712_AC97_READY))
+ continue;
+ break;
+ }
+ outb(reg, ICEREG(ice, AC97_INDEX));
+ outw(val, ICEREG(ice, AC97_DATA));
+ old_cmd &= ~(ICE1712_AC97_PBK_VSR | ICE1712_AC97_CAP_VSR);
+ outb(old_cmd | ICE1712_AC97_WRITE, ICEREG(ice, AC97_CMD));
+ for (tm = 0; tm < 0x10000; tm++)
+ if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_WRITE) == 0)
+ break;
+}
+
+static unsigned short snd_ice1712_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct snd_ice1712 *ice = ac97->private_data;
+ int tm;
+ unsigned char old_cmd = 0;
+
+ for (tm = 0; tm < 0x10000; tm++) {
+ old_cmd = inb(ICEREG(ice, AC97_CMD));
+ if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+ continue;
+ if (!(old_cmd & ICE1712_AC97_READY))
+ continue;
+ break;
+ }
+ outb(reg, ICEREG(ice, AC97_INDEX));
+ outb(old_cmd | ICE1712_AC97_READ, ICEREG(ice, AC97_CMD));
+ for (tm = 0; tm < 0x10000; tm++)
+ if ((inb(ICEREG(ice, AC97_CMD)) & ICE1712_AC97_READ) == 0)
+ break;
+ if (tm >= 0x10000) /* timeout */
+ return ~0;
+ return inw(ICEREG(ice, AC97_DATA));
+}
+
+/*
+ * pro ac97 section
+ */
+
+static void snd_ice1712_pro_ac97_write(struct snd_ac97 *ac97,
+ unsigned short reg,
+ unsigned short val)
+{
+ struct snd_ice1712 *ice = ac97->private_data;
+ int tm;
+ unsigned char old_cmd = 0;
+
+ for (tm = 0; tm < 0x10000; tm++) {
+ old_cmd = inb(ICEMT(ice, AC97_CMD));
+ if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+ continue;
+ if (!(old_cmd & ICE1712_AC97_READY))
+ continue;
+ break;
+ }
+ outb(reg, ICEMT(ice, AC97_INDEX));
+ outw(val, ICEMT(ice, AC97_DATA));
+ old_cmd &= ~(ICE1712_AC97_PBK_VSR | ICE1712_AC97_CAP_VSR);
+ outb(old_cmd | ICE1712_AC97_WRITE, ICEMT(ice, AC97_CMD));
+ for (tm = 0; tm < 0x10000; tm++)
+ if ((inb(ICEMT(ice, AC97_CMD)) & ICE1712_AC97_WRITE) == 0)
+ break;
+}
+
+
+static unsigned short snd_ice1712_pro_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ struct snd_ice1712 *ice = ac97->private_data;
+ int tm;
+ unsigned char old_cmd = 0;
+
+ for (tm = 0; tm < 0x10000; tm++) {
+ old_cmd = inb(ICEMT(ice, AC97_CMD));
+ if (old_cmd & (ICE1712_AC97_WRITE | ICE1712_AC97_READ))
+ continue;
+ if (!(old_cmd & ICE1712_AC97_READY))
+ continue;
+ break;
+ }
+ outb(reg, ICEMT(ice, AC97_INDEX));
+ outb(old_cmd | ICE1712_AC97_READ, ICEMT(ice, AC97_CMD));
+ for (tm = 0; tm < 0x10000; tm++)
+ if ((inb(ICEMT(ice, AC97_CMD)) & ICE1712_AC97_READ) == 0)
+ break;
+ if (tm >= 0x10000) /* timeout */
+ return ~0;
+ return inw(ICEMT(ice, AC97_DATA));
+}
+
+/*
+ * consumer ac97 digital mix
+ */
+#define snd_ice1712_digmix_route_ac97_info snd_ctl_boolean_mono_info
+
+static int snd_ice1712_digmix_route_ac97_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = inb(ICEMT(ice, MONITOR_ROUTECTRL)) & ICE1712_ROUTE_AC97 ? 1 : 0;
+ return 0;
+}
+
+static int snd_ice1712_digmix_route_ac97_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val, nval;
+
+ spin_lock_irq(&ice->reg_lock);
+ val = inb(ICEMT(ice, MONITOR_ROUTECTRL));
+ nval = val & ~ICE1712_ROUTE_AC97;
+ if (ucontrol->value.integer.value[0])
+ nval |= ICE1712_ROUTE_AC97;
+ outb(nval, ICEMT(ice, MONITOR_ROUTECTRL));
+ spin_unlock_irq(&ice->reg_lock);
+ return val != nval;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_mixer_digmix_route_ac97 = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Digital Mixer To AC97",
+ .info = snd_ice1712_digmix_route_ac97_info,
+ .get = snd_ice1712_digmix_route_ac97_get,
+ .put = snd_ice1712_digmix_route_ac97_put,
+};
+
+
+/*
+ * gpio operations
+ */
+static void snd_ice1712_set_gpio_dir(struct snd_ice1712 *ice, unsigned int data)
+{
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, data);
+ inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_ice1712_get_gpio_dir(struct snd_ice1712 *ice)
+{
+ return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DIRECTION);
+}
+
+static unsigned int snd_ice1712_get_gpio_mask(struct snd_ice1712 *ice)
+{
+ return snd_ice1712_read(ice, ICE1712_IREG_GPIO_WRITE_MASK);
+}
+
+static void snd_ice1712_set_gpio_mask(struct snd_ice1712 *ice, unsigned int data)
+{
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, data);
+ inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_ice1712_get_gpio_data(struct snd_ice1712 *ice)
+{
+ return snd_ice1712_read(ice, ICE1712_IREG_GPIO_DATA);
+}
+
+static void snd_ice1712_set_gpio_data(struct snd_ice1712 *ice, unsigned int val)
+{
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA, val);
+ inb(ICEREG(ice, DATA)); /* dummy read for pci-posting */
+}
+
+/*
+ *
+ * CS8427 interface
+ *
+ */
+
+/*
+ * change the input clock selection
+ * spdif_clock = 1 - IEC958 input, 0 - Envy24
+ */
+static int snd_ice1712_cs8427_set_input_clock(struct snd_ice1712 *ice, int spdif_clock)
+{
+ unsigned char reg[2] = { 0x80 | 4, 0 }; /* CS8427 auto increment | register number 4 + data */
+ unsigned char val, nval;
+ int res = 0;
+
+ snd_i2c_lock(ice->i2c);
+ if (snd_i2c_sendbytes(ice->cs8427, reg, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ if (snd_i2c_readbytes(ice->cs8427, &val, 1) != 1) {
+ snd_i2c_unlock(ice->i2c);
+ return -EIO;
+ }
+ nval = val & 0xf0;
+ if (spdif_clock)
+ nval |= 0x01;
+ else
+ nval |= 0x04;
+ if (val != nval) {
+ reg[1] = nval;
+ if (snd_i2c_sendbytes(ice->cs8427, reg, 2) != 2) {
+ res = -EIO;
+ } else {
+ res++;
+ }
+ }
+ snd_i2c_unlock(ice->i2c);
+ return res;
+}
+
+/*
+ * spdif callbacks
+ */
+static void open_cs8427(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+ snd_cs8427_iec958_active(ice->cs8427, 1);
+}
+
+static void close_cs8427(struct snd_ice1712 *ice, struct snd_pcm_substream *substream)
+{
+ snd_cs8427_iec958_active(ice->cs8427, 0);
+}
+
+static void setup_cs8427(struct snd_ice1712 *ice, int rate)
+{
+ snd_cs8427_iec958_pcm(ice->cs8427, rate);
+}
+
+/*
+ * create and initialize callbacks for cs8427 interface
+ */
+int snd_ice1712_init_cs8427(struct snd_ice1712 *ice, int addr)
+{
+ int err;
+
+ err = snd_cs8427_create(ice->i2c, addr,
+ (ice->cs8427_timeout * HZ) / 1000, &ice->cs8427);
+ if (err < 0) {
+ dev_err(ice->card->dev, "CS8427 initialization failed\n");
+ return err;
+ }
+ ice->spdif.ops.open = open_cs8427;
+ ice->spdif.ops.close = close_cs8427;
+ ice->spdif.ops.setup_rate = setup_cs8427;
+ return 0;
+}
+
+static void snd_ice1712_set_input_clock_source(struct snd_ice1712 *ice, int spdif_is_master)
+{
+ /* change CS8427 clock source too */
+ if (ice->cs8427)
+ snd_ice1712_cs8427_set_input_clock(ice, spdif_is_master);
+ /* notify ak4524 chip as well */
+ if (spdif_is_master) {
+ unsigned int i;
+ for (i = 0; i < ice->akm_codecs; i++) {
+ if (ice->akm[i].ops.set_rate_val)
+ ice->akm[i].ops.set_rate_val(&ice->akm[i], 0);
+ }
+ }
+}
+
+/*
+ * Interrupt handler
+ */
+
+static irqreturn_t snd_ice1712_interrupt(int irq, void *dev_id)
+{
+ struct snd_ice1712 *ice = dev_id;
+ unsigned char status;
+ int handled = 0;
+
+ while (1) {
+ status = inb(ICEREG(ice, IRQSTAT));
+ if (status == 0)
+ break;
+ handled = 1;
+ if (status & ICE1712_IRQ_MPU1) {
+ if (ice->rmidi[0])
+ snd_mpu401_uart_interrupt(irq, ice->rmidi[0]->private_data);
+ outb(ICE1712_IRQ_MPU1, ICEREG(ice, IRQSTAT));
+ status &= ~ICE1712_IRQ_MPU1;
+ }
+ if (status & ICE1712_IRQ_TIMER)
+ outb(ICE1712_IRQ_TIMER, ICEREG(ice, IRQSTAT));
+ if (status & ICE1712_IRQ_MPU2) {
+ if (ice->rmidi[1])
+ snd_mpu401_uart_interrupt(irq, ice->rmidi[1]->private_data);
+ outb(ICE1712_IRQ_MPU2, ICEREG(ice, IRQSTAT));
+ status &= ~ICE1712_IRQ_MPU2;
+ }
+ if (status & ICE1712_IRQ_PROPCM) {
+ unsigned char mtstat = inb(ICEMT(ice, IRQ));
+ if (mtstat & ICE1712_MULTI_PBKSTATUS) {
+ if (ice->playback_pro_substream)
+ snd_pcm_period_elapsed(ice->playback_pro_substream);
+ outb(ICE1712_MULTI_PBKSTATUS, ICEMT(ice, IRQ));
+ }
+ if (mtstat & ICE1712_MULTI_CAPSTATUS) {
+ if (ice->capture_pro_substream)
+ snd_pcm_period_elapsed(ice->capture_pro_substream);
+ outb(ICE1712_MULTI_CAPSTATUS, ICEMT(ice, IRQ));
+ }
+ }
+ if (status & ICE1712_IRQ_FM)
+ outb(ICE1712_IRQ_FM, ICEREG(ice, IRQSTAT));
+ if (status & ICE1712_IRQ_PBKDS) {
+ u32 idx;
+ u16 pbkstatus;
+ struct snd_pcm_substream *substream;
+ pbkstatus = inw(ICEDS(ice, INTSTAT));
+ /* dev_dbg(ice->card->dev, "pbkstatus = 0x%x\n", pbkstatus); */
+ for (idx = 0; idx < 6; idx++) {
+ if ((pbkstatus & (3 << (idx * 2))) == 0)
+ continue;
+ substream = ice->playback_con_substream_ds[idx];
+ if (substream != NULL)
+ snd_pcm_period_elapsed(substream);
+ outw(3 << (idx * 2), ICEDS(ice, INTSTAT));
+ }
+ outb(ICE1712_IRQ_PBKDS, ICEREG(ice, IRQSTAT));
+ }
+ if (status & ICE1712_IRQ_CONCAP) {
+ if (ice->capture_con_substream)
+ snd_pcm_period_elapsed(ice->capture_con_substream);
+ outb(ICE1712_IRQ_CONCAP, ICEREG(ice, IRQSTAT));
+ }
+ if (status & ICE1712_IRQ_CONPBK) {
+ if (ice->playback_con_substream)
+ snd_pcm_period_elapsed(ice->playback_con_substream);
+ outb(ICE1712_IRQ_CONPBK, ICEREG(ice, IRQSTAT));
+ }
+ }
+ return IRQ_RETVAL(handled);
+}
+
+
+/*
+ * PCM part - consumer I/O
+ */
+
+static int snd_ice1712_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ int result = 0;
+ u32 tmp;
+
+ spin_lock(&ice->reg_lock);
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_PBK_CTRL);
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ tmp |= 1;
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ tmp &= ~1;
+ } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
+ tmp |= 2;
+ } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) {
+ tmp &= ~2;
+ } else {
+ result = -EINVAL;
+ }
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_CTRL, tmp);
+ spin_unlock(&ice->reg_lock);
+ return result;
+}
+
+static int snd_ice1712_playback_ds_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ int result = 0;
+ u32 tmp;
+
+ spin_lock(&ice->reg_lock);
+ tmp = snd_ice1712_ds_read(ice, substream->number * 2, ICE1712_DSC_CONTROL);
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ tmp |= 1;
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ tmp &= ~1;
+ } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
+ tmp |= 2;
+ } else if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) {
+ tmp &= ~2;
+ } else {
+ result = -EINVAL;
+ }
+ snd_ice1712_ds_write(ice, substream->number * 2, ICE1712_DSC_CONTROL, tmp);
+ spin_unlock(&ice->reg_lock);
+ return result;
+}
+
+static int snd_ice1712_capture_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ int result = 0;
+ u8 tmp;
+
+ spin_lock(&ice->reg_lock);
+ tmp = snd_ice1712_read(ice, ICE1712_IREG_CAP_CTRL);
+ if (cmd == SNDRV_PCM_TRIGGER_START) {
+ tmp |= 1;
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ tmp &= ~1;
+ } else {
+ result = -EINVAL;
+ }
+ snd_ice1712_write(ice, ICE1712_IREG_CAP_CTRL, tmp);
+ spin_unlock(&ice->reg_lock);
+ return result;
+}
+
+static int snd_ice1712_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u32 period_size, buf_size, rate, tmp;
+
+ period_size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1;
+ buf_size = snd_pcm_lib_buffer_bytes(substream) - 1;
+ tmp = 0x0000;
+ if (snd_pcm_format_width(runtime->format) == 16)
+ tmp |= 0x10;
+ if (runtime->channels == 2)
+ tmp |= 0x08;
+ rate = (runtime->rate * 8192) / 375;
+ if (rate > 0x000fffff)
+ rate = 0x000fffff;
+ spin_lock_irq(&ice->reg_lock);
+ outb(0, ice->ddma_port + 15);
+ outb(ICE1712_DMA_MODE_WRITE | ICE1712_DMA_AUTOINIT, ice->ddma_port + 0x0b);
+ outl(runtime->dma_addr, ice->ddma_port + 0);
+ outw(buf_size, ice->ddma_port + 4);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_LO, rate & 0xff);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_MID, (rate >> 8) & 0xff);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_RATE_HI, (rate >> 16) & 0xff);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_CTRL, tmp);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_COUNT_LO, period_size & 0xff);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_COUNT_HI, period_size >> 8);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_LEFT, 0);
+ snd_ice1712_write(ice, ICE1712_IREG_PBK_RIGHT, 0);
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int snd_ice1712_playback_ds_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u32 period_size, rate, tmp, chn;
+
+ period_size = snd_pcm_lib_period_bytes(substream) - 1;
+ tmp = 0x0064;
+ if (snd_pcm_format_width(runtime->format) == 16)
+ tmp &= ~0x04;
+ if (runtime->channels == 2)
+ tmp |= 0x08;
+ rate = (runtime->rate * 8192) / 375;
+ if (rate > 0x000fffff)
+ rate = 0x000fffff;
+ ice->playback_con_active_buf[substream->number] = 0;
+ ice->playback_con_virt_addr[substream->number] = runtime->dma_addr;
+ chn = substream->number * 2;
+ spin_lock_irq(&ice->reg_lock);
+ snd_ice1712_ds_write(ice, chn, ICE1712_DSC_ADDR0, runtime->dma_addr);
+ snd_ice1712_ds_write(ice, chn, ICE1712_DSC_COUNT0, period_size);
+ snd_ice1712_ds_write(ice, chn, ICE1712_DSC_ADDR1, runtime->dma_addr + (runtime->periods > 1 ? period_size + 1 : 0));
+ snd_ice1712_ds_write(ice, chn, ICE1712_DSC_COUNT1, period_size);
+ snd_ice1712_ds_write(ice, chn, ICE1712_DSC_RATE, rate);
+ snd_ice1712_ds_write(ice, chn, ICE1712_DSC_VOLUME, 0);
+ snd_ice1712_ds_write(ice, chn, ICE1712_DSC_CONTROL, tmp);
+ if (runtime->channels == 2) {
+ snd_ice1712_ds_write(ice, chn + 1, ICE1712_DSC_RATE, rate);
+ snd_ice1712_ds_write(ice, chn + 1, ICE1712_DSC_VOLUME, 0);
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int snd_ice1712_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ u32 period_size, buf_size;
+ u8 tmp;
+
+ period_size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1;
+ buf_size = snd_pcm_lib_buffer_bytes(substream) - 1;
+ tmp = 0x06;
+ if (snd_pcm_format_width(runtime->format) == 16)
+ tmp &= ~0x04;
+ if (runtime->channels == 2)
+ tmp &= ~0x02;
+ spin_lock_irq(&ice->reg_lock);
+ outl(ice->capture_con_virt_addr = runtime->dma_addr, ICEREG(ice, CONCAP_ADDR));
+ outw(buf_size, ICEREG(ice, CONCAP_COUNT));
+ snd_ice1712_write(ice, ICE1712_IREG_CAP_COUNT_HI, period_size >> 8);
+ snd_ice1712_write(ice, ICE1712_IREG_CAP_COUNT_LO, period_size & 0xff);
+ snd_ice1712_write(ice, ICE1712_IREG_CAP_CTRL, tmp);
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ac97_set_rate(ice->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_ice1712_playback_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ size_t ptr;
+
+ if (!(snd_ice1712_read(ice, ICE1712_IREG_PBK_CTRL) & 1))
+ return 0;
+ ptr = runtime->buffer_size - inw(ice->ddma_port + 4);
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (ptr == runtime->buffer_size)
+ ptr = 0;
+ return ptr;
+}
+
+static snd_pcm_uframes_t snd_ice1712_playback_ds_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ u8 addr;
+ size_t ptr;
+
+ if (!(snd_ice1712_ds_read(ice, substream->number * 2, ICE1712_DSC_CONTROL) & 1))
+ return 0;
+ if (ice->playback_con_active_buf[substream->number])
+ addr = ICE1712_DSC_ADDR1;
+ else
+ addr = ICE1712_DSC_ADDR0;
+ ptr = snd_ice1712_ds_read(ice, substream->number * 2, addr) -
+ ice->playback_con_virt_addr[substream->number];
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (ptr == substream->runtime->buffer_size)
+ ptr = 0;
+ return ptr;
+}
+
+static snd_pcm_uframes_t snd_ice1712_capture_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (!(snd_ice1712_read(ice, ICE1712_IREG_CAP_CTRL) & 1))
+ return 0;
+ ptr = inl(ICEREG(ice, CONCAP_ADDR)) - ice->capture_con_virt_addr;
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (ptr == substream->runtime->buffer_size)
+ ptr = 0;
+ return ptr;
+}
+
+static const struct snd_pcm_hardware snd_ice1712_playback = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (64*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (64*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_ice1712_playback_ds = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 2,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_ice1712_capture = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
+ .rate_min = 4000,
+ .rate_max = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (64*1024),
+ .period_bytes_min = 64,
+ .period_bytes_max = (64*1024),
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static int snd_ice1712_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ ice->playback_con_substream = substream;
+ runtime->hw = snd_ice1712_playback;
+ return 0;
+}
+
+static int snd_ice1712_playback_ds_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ u32 tmp;
+
+ ice->playback_con_substream_ds[substream->number] = substream;
+ runtime->hw = snd_ice1712_playback_ds;
+ spin_lock_irq(&ice->reg_lock);
+ tmp = inw(ICEDS(ice, INTMASK)) & ~(1 << (substream->number * 2));
+ outw(tmp, ICEDS(ice, INTMASK));
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int snd_ice1712_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ ice->capture_con_substream = substream;
+ runtime->hw = snd_ice1712_capture;
+ runtime->hw.rates = ice->ac97->rates[AC97_RATES_ADC];
+ if (!(runtime->hw.rates & SNDRV_PCM_RATE_8000))
+ runtime->hw.rate_min = 48000;
+ return 0;
+}
+
+static int snd_ice1712_playback_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ ice->playback_con_substream = NULL;
+ return 0;
+}
+
+static int snd_ice1712_playback_ds_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ u32 tmp;
+
+ spin_lock_irq(&ice->reg_lock);
+ tmp = inw(ICEDS(ice, INTMASK)) | (3 << (substream->number * 2));
+ outw(tmp, ICEDS(ice, INTMASK));
+ spin_unlock_irq(&ice->reg_lock);
+ ice->playback_con_substream_ds[substream->number] = NULL;
+ return 0;
+}
+
+static int snd_ice1712_capture_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ ice->capture_con_substream = NULL;
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_ice1712_playback_ops = {
+ .open = snd_ice1712_playback_open,
+ .close = snd_ice1712_playback_close,
+ .prepare = snd_ice1712_playback_prepare,
+ .trigger = snd_ice1712_playback_trigger,
+ .pointer = snd_ice1712_playback_pointer,
+};
+
+static const struct snd_pcm_ops snd_ice1712_playback_ds_ops = {
+ .open = snd_ice1712_playback_ds_open,
+ .close = snd_ice1712_playback_ds_close,
+ .prepare = snd_ice1712_playback_ds_prepare,
+ .trigger = snd_ice1712_playback_ds_trigger,
+ .pointer = snd_ice1712_playback_ds_pointer,
+};
+
+static const struct snd_pcm_ops snd_ice1712_capture_ops = {
+ .open = snd_ice1712_capture_open,
+ .close = snd_ice1712_capture_close,
+ .prepare = snd_ice1712_capture_prepare,
+ .trigger = snd_ice1712_capture_trigger,
+ .pointer = snd_ice1712_capture_pointer,
+};
+
+static int snd_ice1712_pcm(struct snd_ice1712 *ice, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(ice->card, "ICE1712 consumer", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ice1712_capture_ops);
+
+ pcm->private_data = ice;
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "ICE1712 consumer");
+ ice->pcm = pcm;
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ &ice->pci->dev, 64*1024, 64*1024);
+
+ dev_warn(ice->card->dev,
+ "Consumer PCM code does not work well at the moment --jk\n");
+
+ return 0;
+}
+
+static int snd_ice1712_pcm_ds(struct snd_ice1712 *ice, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(ice->card, "ICE1712 consumer (DS)", device, 6, 0, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_ds_ops);
+
+ pcm->private_data = ice;
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "ICE1712 consumer (DS)");
+ ice->pcm_ds = pcm;
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ &ice->pci->dev, 64*1024, 128*1024);
+
+ return 0;
+}
+
+/*
+ * PCM code - professional part (multitrack)
+ */
+
+static const unsigned int rates[] = { 8000, 9600, 11025, 12000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 64000, 88200, 96000 };
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+static int snd_ice1712_pro_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ {
+ unsigned int what;
+ unsigned int old;
+ if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)
+ return -EINVAL;
+ what = ICE1712_PLAYBACK_PAUSE;
+ snd_pcm_trigger_done(substream, substream);
+ spin_lock(&ice->reg_lock);
+ old = inl(ICEMT(ice, PLAYBACK_CONTROL));
+ if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+ old |= what;
+ else
+ old &= ~what;
+ outl(old, ICEMT(ice, PLAYBACK_CONTROL));
+ spin_unlock(&ice->reg_lock);
+ break;
+ }
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_STOP:
+ {
+ unsigned int what = 0;
+ unsigned int old;
+ struct snd_pcm_substream *s;
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s == ice->playback_pro_substream) {
+ what |= ICE1712_PLAYBACK_START;
+ snd_pcm_trigger_done(s, substream);
+ } else if (s == ice->capture_pro_substream) {
+ what |= ICE1712_CAPTURE_START_SHADOW;
+ snd_pcm_trigger_done(s, substream);
+ }
+ }
+ spin_lock(&ice->reg_lock);
+ old = inl(ICEMT(ice, PLAYBACK_CONTROL));
+ if (cmd == SNDRV_PCM_TRIGGER_START)
+ old |= what;
+ else
+ old &= ~what;
+ outl(old, ICEMT(ice, PLAYBACK_CONTROL));
+ spin_unlock(&ice->reg_lock);
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ */
+static void snd_ice1712_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate, int force)
+{
+ unsigned long flags;
+ unsigned char val, old;
+ unsigned int i;
+
+ switch (rate) {
+ case 8000: val = 6; break;
+ case 9600: val = 3; break;
+ case 11025: val = 10; break;
+ case 12000: val = 2; break;
+ case 16000: val = 5; break;
+ case 22050: val = 9; break;
+ case 24000: val = 1; break;
+ case 32000: val = 4; break;
+ case 44100: val = 8; break;
+ case 48000: val = 0; break;
+ case 64000: val = 15; break;
+ case 88200: val = 11; break;
+ case 96000: val = 7; break;
+ default:
+ snd_BUG();
+ val = 0;
+ rate = 48000;
+ break;
+ }
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if (inb(ICEMT(ice, PLAYBACK_CONTROL)) & (ICE1712_CAPTURE_START_SHADOW|
+ ICE1712_PLAYBACK_PAUSE|
+ ICE1712_PLAYBACK_START)) {
+__out:
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ return;
+ }
+ if (!force && is_pro_rate_locked(ice))
+ goto __out;
+
+ old = inb(ICEMT(ice, RATE));
+ if (!force && old == val)
+ goto __out;
+
+ ice->cur_rate = rate;
+ outb(val, ICEMT(ice, RATE));
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+
+ if (ice->gpio.set_pro_rate)
+ ice->gpio.set_pro_rate(ice, rate);
+ for (i = 0; i < ice->akm_codecs; i++) {
+ if (ice->akm[i].ops.set_rate_val)
+ ice->akm[i].ops.set_rate_val(&ice->akm[i], rate);
+ }
+ if (ice->spdif.ops.setup_rate)
+ ice->spdif.ops.setup_rate(ice, rate);
+}
+
+static int snd_ice1712_playback_pro_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ ice->playback_pro_size = snd_pcm_lib_buffer_bytes(substream);
+ spin_lock_irq(&ice->reg_lock);
+ outl(substream->runtime->dma_addr, ICEMT(ice, PLAYBACK_ADDR));
+ outw((ice->playback_pro_size >> 2) - 1, ICEMT(ice, PLAYBACK_SIZE));
+ outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1, ICEMT(ice, PLAYBACK_COUNT));
+ spin_unlock_irq(&ice->reg_lock);
+
+ return 0;
+}
+
+static int snd_ice1712_playback_pro_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ snd_ice1712_set_pro_rate(ice, params_rate(hw_params), 0);
+ return 0;
+}
+
+static int snd_ice1712_capture_pro_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ ice->capture_pro_size = snd_pcm_lib_buffer_bytes(substream);
+ spin_lock_irq(&ice->reg_lock);
+ outl(substream->runtime->dma_addr, ICEMT(ice, CAPTURE_ADDR));
+ outw((ice->capture_pro_size >> 2) - 1, ICEMT(ice, CAPTURE_SIZE));
+ outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1, ICEMT(ice, CAPTURE_COUNT));
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int snd_ice1712_capture_pro_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ snd_ice1712_set_pro_rate(ice, params_rate(hw_params), 0);
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_ice1712_playback_pro_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (!(inl(ICEMT(ice, PLAYBACK_CONTROL)) & ICE1712_PLAYBACK_START))
+ return 0;
+ ptr = ice->playback_pro_size - (inw(ICEMT(ice, PLAYBACK_SIZE)) << 2);
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (ptr == substream->runtime->buffer_size)
+ ptr = 0;
+ return ptr;
+}
+
+static snd_pcm_uframes_t snd_ice1712_capture_pro_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (!(inl(ICEMT(ice, PLAYBACK_CONTROL)) & ICE1712_CAPTURE_START_SHADOW))
+ return 0;
+ ptr = ice->capture_pro_size - (inw(ICEMT(ice, CAPTURE_SIZE)) << 2);
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (ptr == substream->runtime->buffer_size)
+ ptr = 0;
+ return ptr;
+}
+
+static const struct snd_pcm_hardware snd_ice1712_playback_pro = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_96000,
+ .rate_min = 4000,
+ .rate_max = 96000,
+ .channels_min = 10,
+ .channels_max = 10,
+ .buffer_bytes_max = (256*1024),
+ .period_bytes_min = 10 * 4 * 2,
+ .period_bytes_max = 131040,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static const struct snd_pcm_hardware snd_ice1712_capture_pro = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_96000,
+ .rate_min = 4000,
+ .rate_max = 96000,
+ .channels_min = 12,
+ .channels_max = 12,
+ .buffer_bytes_max = (256*1024),
+ .period_bytes_min = 12 * 4 * 2,
+ .period_bytes_max = 131040,
+ .periods_min = 1,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static int snd_ice1712_playback_pro_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ ice->playback_pro_substream = substream;
+ runtime->hw = snd_ice1712_playback_pro;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+ if (is_pro_rate_locked(ice)) {
+ runtime->hw.rate_min = PRO_RATE_DEFAULT;
+ runtime->hw.rate_max = PRO_RATE_DEFAULT;
+ }
+
+ if (ice->spdif.ops.open)
+ ice->spdif.ops.open(ice, substream);
+
+ return 0;
+}
+
+static int snd_ice1712_capture_pro_open(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ ice->capture_pro_substream = substream;
+ runtime->hw = snd_ice1712_capture_pro;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates);
+ if (is_pro_rate_locked(ice)) {
+ runtime->hw.rate_min = PRO_RATE_DEFAULT;
+ runtime->hw.rate_max = PRO_RATE_DEFAULT;
+ }
+
+ return 0;
+}
+
+static int snd_ice1712_playback_pro_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ if (PRO_RATE_RESET)
+ snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 0);
+ ice->playback_pro_substream = NULL;
+ if (ice->spdif.ops.close)
+ ice->spdif.ops.close(ice, substream);
+
+ return 0;
+}
+
+static int snd_ice1712_capture_pro_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ if (PRO_RATE_RESET)
+ snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 0);
+ ice->capture_pro_substream = NULL;
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_ice1712_playback_pro_ops = {
+ .open = snd_ice1712_playback_pro_open,
+ .close = snd_ice1712_playback_pro_close,
+ .hw_params = snd_ice1712_playback_pro_hw_params,
+ .prepare = snd_ice1712_playback_pro_prepare,
+ .trigger = snd_ice1712_pro_trigger,
+ .pointer = snd_ice1712_playback_pro_pointer,
+};
+
+static const struct snd_pcm_ops snd_ice1712_capture_pro_ops = {
+ .open = snd_ice1712_capture_pro_open,
+ .close = snd_ice1712_capture_pro_close,
+ .hw_params = snd_ice1712_capture_pro_hw_params,
+ .prepare = snd_ice1712_capture_pro_prepare,
+ .trigger = snd_ice1712_pro_trigger,
+ .pointer = snd_ice1712_capture_pro_pointer,
+};
+
+static int snd_ice1712_pcm_profi(struct snd_ice1712 *ice, int device)
+{
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(ice->card, "ICE1712 multi", device, 1, 1, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ice1712_playback_pro_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ice1712_capture_pro_ops);
+
+ pcm->private_data = ice;
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "ICE1712 multi");
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ &ice->pci->dev, 256*1024, 256*1024);
+
+ ice->pcm_pro = pcm;
+
+ if (ice->cs8427) {
+ /* assign channels to iec958 */
+ err = snd_cs8427_iec958_build(ice->cs8427,
+ pcm->streams[0].substream,
+ pcm->streams[1].substream);
+ if (err < 0)
+ return err;
+ }
+
+ return snd_ice1712_build_pro_mixer(ice);
+}
+
+/*
+ * Mixer section
+ */
+
+static void snd_ice1712_update_volume(struct snd_ice1712 *ice, int index)
+{
+ unsigned int vol = ice->pro_volumes[index];
+ unsigned short val = 0;
+
+ val |= (vol & 0x8000) == 0 ? (96 - (vol & 0x7f)) : 0x7f;
+ val |= ((vol & 0x80000000) == 0 ? (96 - ((vol >> 16) & 0x7f)) : 0x7f) << 8;
+ outb(index, ICEMT(ice, MONITOR_INDEX));
+ outw(val, ICEMT(ice, MONITOR_VOLUME));
+}
+
+#define snd_ice1712_pro_mixer_switch_info snd_ctl_boolean_stereo_info
+
+static int snd_ice1712_pro_mixer_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+ kcontrol->private_value;
+
+ spin_lock_irq(&ice->reg_lock);
+ ucontrol->value.integer.value[0] =
+ !((ice->pro_volumes[priv_idx] >> 15) & 1);
+ ucontrol->value.integer.value[1] =
+ !((ice->pro_volumes[priv_idx] >> 31) & 1);
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int snd_ice1712_pro_mixer_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+ kcontrol->private_value;
+ unsigned int nval, change;
+
+ nval = (ucontrol->value.integer.value[0] ? 0 : 0x00008000) |
+ (ucontrol->value.integer.value[1] ? 0 : 0x80000000);
+ spin_lock_irq(&ice->reg_lock);
+ nval |= ice->pro_volumes[priv_idx] & ~0x80008000;
+ change = nval != ice->pro_volumes[priv_idx];
+ ice->pro_volumes[priv_idx] = nval;
+ snd_ice1712_update_volume(ice, priv_idx);
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static int snd_ice1712_pro_mixer_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 96;
+ return 0;
+}
+
+static int snd_ice1712_pro_mixer_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+ kcontrol->private_value;
+
+ spin_lock_irq(&ice->reg_lock);
+ ucontrol->value.integer.value[0] =
+ (ice->pro_volumes[priv_idx] >> 0) & 127;
+ ucontrol->value.integer.value[1] =
+ (ice->pro_volumes[priv_idx] >> 16) & 127;
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int snd_ice1712_pro_mixer_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int priv_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) +
+ kcontrol->private_value;
+ unsigned int nval, change;
+
+ nval = (ucontrol->value.integer.value[0] & 127) |
+ ((ucontrol->value.integer.value[1] & 127) << 16);
+ spin_lock_irq(&ice->reg_lock);
+ nval |= ice->pro_volumes[priv_idx] & ~0x007f007f;
+ change = nval != ice->pro_volumes[priv_idx];
+ ice->pro_volumes[priv_idx] = nval;
+ snd_ice1712_update_volume(ice, priv_idx);
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_playback, -14400, 150, 0);
+
+static const struct snd_kcontrol_new snd_ice1712_multi_playback_ctrls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Playback Switch",
+ .info = snd_ice1712_pro_mixer_switch_info,
+ .get = snd_ice1712_pro_mixer_switch_get,
+ .put = snd_ice1712_pro_mixer_switch_put,
+ .private_value = 0,
+ .count = 10,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Multi Playback Volume",
+ .info = snd_ice1712_pro_mixer_volume_info,
+ .get = snd_ice1712_pro_mixer_volume_get,
+ .put = snd_ice1712_pro_mixer_volume_put,
+ .private_value = 0,
+ .count = 10,
+ .tlv = { .p = db_scale_playback }
+ },
+};
+
+static const struct snd_kcontrol_new snd_ice1712_multi_capture_analog_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "H/W Multi Capture Switch",
+ .info = snd_ice1712_pro_mixer_switch_info,
+ .get = snd_ice1712_pro_mixer_switch_get,
+ .put = snd_ice1712_pro_mixer_switch_put,
+ .private_value = 10,
+};
+
+static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_switch = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, SWITCH),
+ .info = snd_ice1712_pro_mixer_switch_info,
+ .get = snd_ice1712_pro_mixer_switch_get,
+ .put = snd_ice1712_pro_mixer_switch_put,
+ .private_value = 18,
+ .count = 2,
+};
+
+static const struct snd_kcontrol_new snd_ice1712_multi_capture_analog_volume = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "H/W Multi Capture Volume",
+ .info = snd_ice1712_pro_mixer_volume_info,
+ .get = snd_ice1712_pro_mixer_volume_get,
+ .put = snd_ice1712_pro_mixer_volume_put,
+ .private_value = 10,
+ .tlv = { .p = db_scale_playback }
+};
+
+static const struct snd_kcontrol_new snd_ice1712_multi_capture_spdif_volume = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("Multi ", CAPTURE, VOLUME),
+ .info = snd_ice1712_pro_mixer_volume_info,
+ .get = snd_ice1712_pro_mixer_volume_get,
+ .put = snd_ice1712_pro_mixer_volume_put,
+ .private_value = 18,
+ .count = 2,
+};
+
+static int snd_ice1712_build_pro_mixer(struct snd_ice1712 *ice)
+{
+ struct snd_card *card = ice->card;
+ unsigned int idx;
+ int err;
+
+ /* multi-channel mixer */
+ for (idx = 0; idx < ARRAY_SIZE(snd_ice1712_multi_playback_ctrls); idx++) {
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_playback_ctrls[idx], ice));
+ if (err < 0)
+ return err;
+ }
+
+ if (ice->num_total_adcs > 0) {
+ struct snd_kcontrol_new tmp = snd_ice1712_multi_capture_analog_switch;
+ tmp.count = ice->num_total_adcs;
+ err = snd_ctl_add(card, snd_ctl_new1(&tmp, ice));
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_capture_spdif_switch, ice));
+ if (err < 0)
+ return err;
+
+ if (ice->num_total_adcs > 0) {
+ struct snd_kcontrol_new tmp = snd_ice1712_multi_capture_analog_volume;
+ tmp.count = ice->num_total_adcs;
+ err = snd_ctl_add(card, snd_ctl_new1(&tmp, ice));
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_ctl_add(card, snd_ctl_new1(&snd_ice1712_multi_capture_spdif_volume, ice));
+ if (err < 0)
+ return err;
+
+ /* initialize volumes */
+ for (idx = 0; idx < 10; idx++) {
+ ice->pro_volumes[idx] = 0x80008000; /* mute */
+ snd_ice1712_update_volume(ice, idx);
+ }
+ for (idx = 10; idx < 10 + ice->num_total_adcs; idx++) {
+ ice->pro_volumes[idx] = 0x80008000; /* mute */
+ snd_ice1712_update_volume(ice, idx);
+ }
+ for (idx = 18; idx < 20; idx++) {
+ ice->pro_volumes[idx] = 0x80008000; /* mute */
+ snd_ice1712_update_volume(ice, idx);
+ }
+ return 0;
+}
+
+static void snd_ice1712_mixer_free_ac97(struct snd_ac97 *ac97)
+{
+ struct snd_ice1712 *ice = ac97->private_data;
+ ice->ac97 = NULL;
+}
+
+static int snd_ice1712_ac97_mixer(struct snd_ice1712 *ice)
+{
+ int err, bus_num = 0;
+ struct snd_ac97_template ac97;
+ struct snd_ac97_bus *pbus;
+ static const struct snd_ac97_bus_ops con_ops = {
+ .write = snd_ice1712_ac97_write,
+ .read = snd_ice1712_ac97_read,
+ };
+ static const struct snd_ac97_bus_ops pro_ops = {
+ .write = snd_ice1712_pro_ac97_write,
+ .read = snd_ice1712_pro_ac97_read,
+ };
+
+ if (ice_has_con_ac97(ice)) {
+ err = snd_ac97_bus(ice->card, bus_num++, &con_ops, NULL, &pbus);
+ if (err < 0)
+ return err;
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = ice;
+ ac97.private_free = snd_ice1712_mixer_free_ac97;
+ err = snd_ac97_mixer(pbus, &ac97, &ice->ac97);
+ if (err < 0)
+ dev_warn(ice->card->dev,
+ "cannot initialize ac97 for consumer, skipped\n");
+ else {
+ return snd_ctl_add(ice->card,
+ snd_ctl_new1(&snd_ice1712_mixer_digmix_route_ac97,
+ ice));
+ }
+ }
+
+ if (!(ice->eeprom.data[ICE_EEP1_ACLINK] & ICE1712_CFG_PRO_I2S)) {
+ err = snd_ac97_bus(ice->card, bus_num, &pro_ops, NULL, &pbus);
+ if (err < 0)
+ return err;
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = ice;
+ ac97.private_free = snd_ice1712_mixer_free_ac97;
+ err = snd_ac97_mixer(pbus, &ac97, &ice->ac97);
+ if (err < 0)
+ dev_warn(ice->card->dev,
+ "cannot initialize pro ac97, skipped\n");
+ else
+ return 0;
+ }
+ /* I2S mixer only */
+ strcat(ice->card->mixername, "ICE1712 - multitrack");
+ return 0;
+}
+
+/*
+ *
+ */
+
+static inline unsigned int eeprom_double(struct snd_ice1712 *ice, int idx)
+{
+ return (unsigned int)ice->eeprom.data[idx] | ((unsigned int)ice->eeprom.data[idx + 1] << 8);
+}
+
+static void snd_ice1712_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ unsigned int idx;
+
+ snd_iprintf(buffer, "%s\n\n", ice->card->longname);
+ snd_iprintf(buffer, "EEPROM:\n");
+
+ snd_iprintf(buffer, " Subvendor : 0x%x\n", ice->eeprom.subvendor);
+ snd_iprintf(buffer, " Size : %i bytes\n", ice->eeprom.size);
+ snd_iprintf(buffer, " Version : %i\n", ice->eeprom.version);
+ snd_iprintf(buffer, " Codec : 0x%x\n", ice->eeprom.data[ICE_EEP1_CODEC]);
+ snd_iprintf(buffer, " ACLink : 0x%x\n", ice->eeprom.data[ICE_EEP1_ACLINK]);
+ snd_iprintf(buffer, " I2S ID : 0x%x\n", ice->eeprom.data[ICE_EEP1_I2SID]);
+ snd_iprintf(buffer, " S/PDIF : 0x%x\n", ice->eeprom.data[ICE_EEP1_SPDIF]);
+ snd_iprintf(buffer, " GPIO mask : 0x%x\n", ice->eeprom.gpiomask);
+ snd_iprintf(buffer, " GPIO state : 0x%x\n", ice->eeprom.gpiostate);
+ snd_iprintf(buffer, " GPIO direction : 0x%x\n", ice->eeprom.gpiodir);
+ snd_iprintf(buffer, " AC'97 main : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_MAIN_LO));
+ snd_iprintf(buffer, " AC'97 pcm : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_PCM_LO));
+ snd_iprintf(buffer, " AC'97 record : 0x%x\n", eeprom_double(ice, ICE_EEP1_AC97_REC_LO));
+ snd_iprintf(buffer, " AC'97 record src : 0x%x\n", ice->eeprom.data[ICE_EEP1_AC97_RECSRC]);
+ for (idx = 0; idx < 4; idx++)
+ snd_iprintf(buffer, " DAC ID #%i : 0x%x\n", idx, ice->eeprom.data[ICE_EEP1_DAC_ID + idx]);
+ for (idx = 0; idx < 4; idx++)
+ snd_iprintf(buffer, " ADC ID #%i : 0x%x\n", idx, ice->eeprom.data[ICE_EEP1_ADC_ID + idx]);
+ for (idx = 0x1c; idx < ice->eeprom.size; idx++)
+ snd_iprintf(buffer, " Extra #%02i : 0x%x\n", idx, ice->eeprom.data[idx]);
+
+ snd_iprintf(buffer, "\nRegisters:\n");
+ snd_iprintf(buffer, " PSDOUT03 : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_PSDOUT03)));
+ snd_iprintf(buffer, " CAPTURE : 0x%08x\n", inl(ICEMT(ice, ROUTE_CAPTURE)));
+ snd_iprintf(buffer, " SPDOUT : 0x%04x\n", (unsigned)inw(ICEMT(ice, ROUTE_SPDOUT)));
+ snd_iprintf(buffer, " RATE : 0x%02x\n", (unsigned)inb(ICEMT(ice, RATE)));
+ snd_iprintf(buffer, " GPIO_DATA : 0x%02x\n", (unsigned)snd_ice1712_get_gpio_data(ice));
+ snd_iprintf(buffer, " GPIO_WRITE_MASK : 0x%02x\n", (unsigned)snd_ice1712_read(ice, ICE1712_IREG_GPIO_WRITE_MASK));
+ snd_iprintf(buffer, " GPIO_DIRECTION : 0x%02x\n", (unsigned)snd_ice1712_read(ice, ICE1712_IREG_GPIO_DIRECTION));
+}
+
+static void snd_ice1712_proc_init(struct snd_ice1712 *ice)
+{
+ snd_card_ro_proc_new(ice->card, "ice1712", ice, snd_ice1712_proc_read);
+}
+
+/*
+ *
+ */
+
+static int snd_ice1712_eeprom_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = sizeof(struct snd_ice1712_eeprom);
+ return 0;
+}
+
+static int snd_ice1712_eeprom_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ memcpy(ucontrol->value.bytes.data, &ice->eeprom, sizeof(ice->eeprom));
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_eeprom = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "ICE1712 EEPROM",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = snd_ice1712_eeprom_info,
+ .get = snd_ice1712_eeprom_get
+};
+
+/*
+ */
+static int snd_ice1712_spdif_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_ice1712_spdif_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ if (ice->spdif.ops.default_get)
+ ice->spdif.ops.default_get(ice, ucontrol);
+ return 0;
+}
+
+static int snd_ice1712_spdif_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ if (ice->spdif.ops.default_put)
+ return ice->spdif.ops.default_put(ice, ucontrol);
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_spdif_default =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .info = snd_ice1712_spdif_info,
+ .get = snd_ice1712_spdif_default_get,
+ .put = snd_ice1712_spdif_default_put
+};
+
+static int snd_ice1712_spdif_maskc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ if (ice->spdif.ops.default_get) {
+ ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+ IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_CON_NOT_COPYRIGHT |
+ IEC958_AES0_CON_EMPHASIS;
+ ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL |
+ IEC958_AES1_CON_CATEGORY;
+ ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
+ } else {
+ ucontrol->value.iec958.status[0] = 0xff;
+ ucontrol->value.iec958.status[1] = 0xff;
+ ucontrol->value.iec958.status[2] = 0xff;
+ ucontrol->value.iec958.status[3] = 0xff;
+ ucontrol->value.iec958.status[4] = 0xff;
+ }
+ return 0;
+}
+
+static int snd_ice1712_spdif_maskp_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ if (ice->spdif.ops.default_get) {
+ ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+ IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_PRO_FS |
+ IEC958_AES0_PRO_EMPHASIS;
+ ucontrol->value.iec958.status[1] = IEC958_AES1_PRO_MODE;
+ } else {
+ ucontrol->value.iec958.status[0] = 0xff;
+ ucontrol->value.iec958.status[1] = 0xff;
+ ucontrol->value.iec958.status[2] = 0xff;
+ ucontrol->value.iec958.status[3] = 0xff;
+ ucontrol->value.iec958.status[4] = 0xff;
+ }
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_spdif_maskc =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+ .info = snd_ice1712_spdif_info,
+ .get = snd_ice1712_spdif_maskc_get,
+};
+
+static const struct snd_kcontrol_new snd_ice1712_spdif_maskp =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+ .info = snd_ice1712_spdif_info,
+ .get = snd_ice1712_spdif_maskp_get,
+};
+
+static int snd_ice1712_spdif_stream_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ if (ice->spdif.ops.stream_get)
+ ice->spdif.ops.stream_get(ice, ucontrol);
+ return 0;
+}
+
+static int snd_ice1712_spdif_stream_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ if (ice->spdif.ops.stream_put)
+ return ice->spdif.ops.stream_put(ice, ucontrol);
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_spdif_stream =
+{
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_INACTIVE),
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
+ .info = snd_ice1712_spdif_info,
+ .get = snd_ice1712_spdif_stream_get,
+ .put = snd_ice1712_spdif_stream_put
+};
+
+int snd_ice1712_gpio_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char mask = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value & (1<<24)) ? 1 : 0;
+
+ snd_ice1712_save_gpio_status(ice);
+ ucontrol->value.integer.value[0] =
+ (snd_ice1712_gpio_read(ice) & mask ? 1 : 0) ^ invert;
+ snd_ice1712_restore_gpio_status(ice);
+ return 0;
+}
+
+int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char mask = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value & (1<<24)) ? mask : 0;
+ unsigned int val, nval;
+
+ if (kcontrol->private_value & (1 << 31))
+ return -EPERM;
+ nval = (ucontrol->value.integer.value[0] ? mask : 0) ^ invert;
+ snd_ice1712_save_gpio_status(ice);
+ val = snd_ice1712_gpio_read(ice);
+ nval |= val & ~mask;
+ if (val != nval)
+ snd_ice1712_gpio_write(ice, nval);
+ snd_ice1712_restore_gpio_status(ice);
+ return val != nval;
+}
+
+/*
+ * rate
+ */
+static int snd_ice1712_pro_internal_clock_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "8000", /* 0: 6 */
+ "9600", /* 1: 3 */
+ "11025", /* 2: 10 */
+ "12000", /* 3: 2 */
+ "16000", /* 4: 5 */
+ "22050", /* 5: 9 */
+ "24000", /* 6: 1 */
+ "32000", /* 7: 4 */
+ "44100", /* 8: 8 */
+ "48000", /* 9: 0 */
+ "64000", /* 10: 15 */
+ "88200", /* 11: 11 */
+ "96000", /* 12: 7 */
+ "IEC958 Input", /* 13: -- */
+ };
+ return snd_ctl_enum_info(uinfo, 1, 14, texts);
+}
+
+static int snd_ice1712_pro_internal_clock_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ static const unsigned char xlate[16] = {
+ 9, 6, 3, 1, 7, 4, 0, 12, 8, 5, 2, 11, 255, 255, 255, 10
+ };
+ unsigned char val;
+
+ spin_lock_irq(&ice->reg_lock);
+ if (is_spdif_master(ice)) {
+ ucontrol->value.enumerated.item[0] = 13;
+ } else {
+ val = xlate[inb(ICEMT(ice, RATE)) & 15];
+ if (val == 255) {
+ snd_BUG();
+ val = 0;
+ }
+ ucontrol->value.enumerated.item[0] = val;
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int snd_ice1712_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ static const unsigned int xrate[13] = {
+ 8000, 9600, 11025, 12000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 64000, 88200, 96000
+ };
+ unsigned char oval;
+ int change = 0;
+
+ spin_lock_irq(&ice->reg_lock);
+ oval = inb(ICEMT(ice, RATE));
+ if (ucontrol->value.enumerated.item[0] == 13) {
+ outb(oval | ICE1712_SPDIF_MASTER, ICEMT(ice, RATE));
+ } else {
+ PRO_RATE_DEFAULT = xrate[ucontrol->value.integer.value[0] % 13];
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_set_pro_rate(ice, PRO_RATE_DEFAULT, 1);
+ spin_lock_irq(&ice->reg_lock);
+ }
+ change = inb(ICEMT(ice, RATE)) != oval;
+ spin_unlock_irq(&ice->reg_lock);
+
+ if ((oval & ICE1712_SPDIF_MASTER) !=
+ (inb(ICEMT(ice, RATE)) & ICE1712_SPDIF_MASTER))
+ snd_ice1712_set_input_clock_source(ice, is_spdif_master(ice));
+
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Internal Clock",
+ .info = snd_ice1712_pro_internal_clock_info,
+ .get = snd_ice1712_pro_internal_clock_get,
+ .put = snd_ice1712_pro_internal_clock_put
+};
+
+static int snd_ice1712_pro_internal_clock_default_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "8000", /* 0: 6 */
+ "9600", /* 1: 3 */
+ "11025", /* 2: 10 */
+ "12000", /* 3: 2 */
+ "16000", /* 4: 5 */
+ "22050", /* 5: 9 */
+ "24000", /* 6: 1 */
+ "32000", /* 7: 4 */
+ "44100", /* 8: 8 */
+ "48000", /* 9: 0 */
+ "64000", /* 10: 15 */
+ "88200", /* 11: 11 */
+ "96000", /* 12: 7 */
+ /* "IEC958 Input", 13: -- */
+ };
+ return snd_ctl_enum_info(uinfo, 1, 13, texts);
+}
+
+static int snd_ice1712_pro_internal_clock_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int val;
+ static const unsigned int xrate[13] = {
+ 8000, 9600, 11025, 12000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 64000, 88200, 96000
+ };
+
+ for (val = 0; val < 13; val++) {
+ if (xrate[val] == PRO_RATE_DEFAULT)
+ break;
+ }
+
+ ucontrol->value.enumerated.item[0] = val;
+ return 0;
+}
+
+static int snd_ice1712_pro_internal_clock_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ static const unsigned int xrate[13] = {
+ 8000, 9600, 11025, 12000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 64000, 88200, 96000
+ };
+ unsigned char oval;
+ int change = 0;
+
+ oval = PRO_RATE_DEFAULT;
+ PRO_RATE_DEFAULT = xrate[ucontrol->value.integer.value[0] % 13];
+ change = PRO_RATE_DEFAULT != oval;
+
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_pro_internal_clock_default = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Internal Clock Default",
+ .info = snd_ice1712_pro_internal_clock_default_info,
+ .get = snd_ice1712_pro_internal_clock_default_get,
+ .put = snd_ice1712_pro_internal_clock_default_put
+};
+
+#define snd_ice1712_pro_rate_locking_info snd_ctl_boolean_mono_info
+
+static int snd_ice1712_pro_rate_locking_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = PRO_RATE_LOCKED;
+ return 0;
+}
+
+static int snd_ice1712_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int change = 0, nval;
+
+ nval = ucontrol->value.integer.value[0] ? 1 : 0;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_LOCKED != nval;
+ PRO_RATE_LOCKED = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_pro_rate_locking = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Rate Locking",
+ .info = snd_ice1712_pro_rate_locking_info,
+ .get = snd_ice1712_pro_rate_locking_get,
+ .put = snd_ice1712_pro_rate_locking_put
+};
+
+#define snd_ice1712_pro_rate_reset_info snd_ctl_boolean_mono_info
+
+static int snd_ice1712_pro_rate_reset_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = PRO_RATE_RESET;
+ return 0;
+}
+
+static int snd_ice1712_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int change = 0, nval;
+
+ nval = ucontrol->value.integer.value[0] ? 1 : 0;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_RESET != nval;
+ PRO_RATE_RESET = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_pro_rate_reset = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Rate Reset",
+ .info = snd_ice1712_pro_rate_reset_info,
+ .get = snd_ice1712_pro_rate_reset_get,
+ .put = snd_ice1712_pro_rate_reset_put
+};
+
+/*
+ * routing
+ */
+static int snd_ice1712_pro_route_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "PCM Out", /* 0 */
+ "H/W In 0", "H/W In 1", "H/W In 2", "H/W In 3", /* 1-4 */
+ "H/W In 4", "H/W In 5", "H/W In 6", "H/W In 7", /* 5-8 */
+ "IEC958 In L", "IEC958 In R", /* 9-10 */
+ "Digital Mixer", /* 11 - optional */
+ };
+ int num_items = snd_ctl_get_ioffidx(kcontrol, &uinfo->id) < 2 ? 12 : 11;
+ return snd_ctl_enum_info(uinfo, 1, num_items, texts);
+}
+
+static int snd_ice1712_pro_route_analog_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned int val, cval;
+
+ spin_lock_irq(&ice->reg_lock);
+ val = inw(ICEMT(ice, ROUTE_PSDOUT03));
+ cval = inl(ICEMT(ice, ROUTE_CAPTURE));
+ spin_unlock_irq(&ice->reg_lock);
+
+ val >>= ((idx % 2) * 8) + ((idx / 2) * 2);
+ val &= 3;
+ cval >>= ((idx / 2) * 8) + ((idx % 2) * 4);
+ if (val == 1 && idx < 2)
+ ucontrol->value.enumerated.item[0] = 11;
+ else if (val == 2)
+ ucontrol->value.enumerated.item[0] = (cval & 7) + 1;
+ else if (val == 3)
+ ucontrol->value.enumerated.item[0] = ((cval >> 3) & 1) + 9;
+ else
+ ucontrol->value.enumerated.item[0] = 0;
+ return 0;
+}
+
+static int snd_ice1712_pro_route_analog_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int change, shift;
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned int val, old_val, nval;
+
+ /* update PSDOUT */
+ if (ucontrol->value.enumerated.item[0] >= 11)
+ nval = idx < 2 ? 1 : 0; /* dig mixer (or pcm) */
+ else if (ucontrol->value.enumerated.item[0] >= 9)
+ nval = 3; /* spdif in */
+ else if (ucontrol->value.enumerated.item[0] >= 1)
+ nval = 2; /* analog in */
+ else
+ nval = 0; /* pcm */
+ shift = ((idx % 2) * 8) + ((idx / 2) * 2);
+ spin_lock_irq(&ice->reg_lock);
+ val = old_val = inw(ICEMT(ice, ROUTE_PSDOUT03));
+ val &= ~(0x03 << shift);
+ val |= nval << shift;
+ change = val != old_val;
+ if (change)
+ outw(val, ICEMT(ice, ROUTE_PSDOUT03));
+ spin_unlock_irq(&ice->reg_lock);
+ if (nval < 2) /* dig mixer of pcm */
+ return change;
+
+ /* update CAPTURE */
+ spin_lock_irq(&ice->reg_lock);
+ val = old_val = inl(ICEMT(ice, ROUTE_CAPTURE));
+ shift = ((idx / 2) * 8) + ((idx % 2) * 4);
+ if (nval == 2) { /* analog in */
+ nval = ucontrol->value.enumerated.item[0] - 1;
+ val &= ~(0x07 << shift);
+ val |= nval << shift;
+ } else { /* spdif in */
+ nval = (ucontrol->value.enumerated.item[0] - 9) << 3;
+ val &= ~(0x08 << shift);
+ val |= nval << shift;
+ }
+ if (val != old_val) {
+ change = 1;
+ outl(val, ICEMT(ice, ROUTE_CAPTURE));
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static int snd_ice1712_pro_route_spdif_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned int val, cval;
+ val = inw(ICEMT(ice, ROUTE_SPDOUT));
+ cval = (val >> (idx * 4 + 8)) & 0x0f;
+ val = (val >> (idx * 2)) & 0x03;
+ if (val == 1)
+ ucontrol->value.enumerated.item[0] = 11;
+ else if (val == 2)
+ ucontrol->value.enumerated.item[0] = (cval & 7) + 1;
+ else if (val == 3)
+ ucontrol->value.enumerated.item[0] = ((cval >> 3) & 1) + 9;
+ else
+ ucontrol->value.enumerated.item[0] = 0;
+ return 0;
+}
+
+static int snd_ice1712_pro_route_spdif_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int change, shift;
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ unsigned int val, old_val, nval;
+
+ /* update SPDOUT */
+ spin_lock_irq(&ice->reg_lock);
+ val = old_val = inw(ICEMT(ice, ROUTE_SPDOUT));
+ if (ucontrol->value.enumerated.item[0] >= 11)
+ nval = 1;
+ else if (ucontrol->value.enumerated.item[0] >= 9)
+ nval = 3;
+ else if (ucontrol->value.enumerated.item[0] >= 1)
+ nval = 2;
+ else
+ nval = 0;
+ shift = idx * 2;
+ val &= ~(0x03 << shift);
+ val |= nval << shift;
+ shift = idx * 4 + 8;
+ if (nval == 2) {
+ nval = ucontrol->value.enumerated.item[0] - 1;
+ val &= ~(0x07 << shift);
+ val |= nval << shift;
+ } else if (nval == 3) {
+ nval = (ucontrol->value.enumerated.item[0] - 9) << 3;
+ val &= ~(0x08 << shift);
+ val |= nval << shift;
+ }
+ change = val != old_val;
+ if (change)
+ outw(val, ICEMT(ice, ROUTE_SPDOUT));
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_mixer_pro_analog_route = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "H/W Playback Route",
+ .info = snd_ice1712_pro_route_info,
+ .get = snd_ice1712_pro_route_analog_get,
+ .put = snd_ice1712_pro_route_analog_put,
+};
+
+static const struct snd_kcontrol_new snd_ice1712_mixer_pro_spdif_route = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
+ .info = snd_ice1712_pro_route_info,
+ .get = snd_ice1712_pro_route_spdif_get,
+ .put = snd_ice1712_pro_route_spdif_put,
+ .count = 2,
+};
+
+
+static int snd_ice1712_pro_volume_rate_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_ice1712_pro_volume_rate_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = inb(ICEMT(ice, MONITOR_RATE));
+ return 0;
+}
+
+static int snd_ice1712_pro_volume_rate_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int change;
+
+ spin_lock_irq(&ice->reg_lock);
+ change = inb(ICEMT(ice, MONITOR_RATE)) != ucontrol->value.integer.value[0];
+ outb(ucontrol->value.integer.value[0], ICEMT(ice, MONITOR_RATE));
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_mixer_pro_volume_rate = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Volume Rate",
+ .info = snd_ice1712_pro_volume_rate_info,
+ .get = snd_ice1712_pro_volume_rate_get,
+ .put = snd_ice1712_pro_volume_rate_put
+};
+
+static int snd_ice1712_pro_peak_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 22;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_ice1712_pro_peak_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx;
+
+ spin_lock_irq(&ice->reg_lock);
+ for (idx = 0; idx < 22; idx++) {
+ outb(idx, ICEMT(ice, MONITOR_PEAKINDEX));
+ ucontrol->value.integer.value[idx] = inb(ICEMT(ice, MONITOR_PEAKDATA));
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_mixer_pro_peak = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Multi Track Peak",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_ice1712_pro_peak_info,
+ .get = snd_ice1712_pro_peak_get
+};
+
+/*
+ *
+ */
+
+/*
+ * list of available boards
+ */
+static const struct snd_ice1712_card_info *card_tables[] = {
+ snd_ice1712_hoontech_cards,
+ snd_ice1712_delta_cards,
+ snd_ice1712_ews_cards,
+ NULL,
+};
+
+static unsigned char snd_ice1712_read_i2c(struct snd_ice1712 *ice,
+ unsigned char dev,
+ unsigned char addr)
+{
+ long t = 0x10000;
+
+ outb(addr, ICEREG(ice, I2C_BYTE_ADDR));
+ outb(dev & ~ICE1712_I2C_WRITE, ICEREG(ice, I2C_DEV_ADDR));
+ while (t-- > 0 && (inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_BUSY)) ;
+ return inb(ICEREG(ice, I2C_DATA));
+}
+
+static int snd_ice1712_read_eeprom(struct snd_ice1712 *ice,
+ const char *modelname)
+{
+ int dev = ICE_I2C_EEPROM_ADDR; /* I2C EEPROM device address */
+ unsigned int i, size;
+ const struct snd_ice1712_card_info * const *tbl, *c;
+
+ if (!modelname || !*modelname) {
+ ice->eeprom.subvendor = 0;
+ if ((inb(ICEREG(ice, I2C_CTRL)) & ICE1712_I2C_EEPROM) != 0)
+ ice->eeprom.subvendor = (snd_ice1712_read_i2c(ice, dev, 0x00) << 0) |
+ (snd_ice1712_read_i2c(ice, dev, 0x01) << 8) |
+ (snd_ice1712_read_i2c(ice, dev, 0x02) << 16) |
+ (snd_ice1712_read_i2c(ice, dev, 0x03) << 24);
+ if (ice->eeprom.subvendor == 0 ||
+ ice->eeprom.subvendor == (unsigned int)-1) {
+ /* invalid subvendor from EEPROM, try the PCI subststem ID instead */
+ u16 vendor, device;
+ pci_read_config_word(ice->pci, PCI_SUBSYSTEM_VENDOR_ID, &vendor);
+ pci_read_config_word(ice->pci, PCI_SUBSYSTEM_ID, &device);
+ ice->eeprom.subvendor = ((unsigned int)swab16(vendor) << 16) | swab16(device);
+ if (ice->eeprom.subvendor == 0 || ice->eeprom.subvendor == (unsigned int)-1) {
+ dev_err(ice->card->dev,
+ "No valid ID is found\n");
+ return -ENXIO;
+ }
+ }
+ }
+ for (tbl = card_tables; *tbl; tbl++) {
+ for (c = *tbl; c->subvendor; c++) {
+ if (modelname && c->model && !strcmp(modelname, c->model)) {
+ dev_info(ice->card->dev,
+ "Using board model %s\n", c->name);
+ ice->eeprom.subvendor = c->subvendor;
+ } else if (c->subvendor != ice->eeprom.subvendor)
+ continue;
+ if (!c->eeprom_size || !c->eeprom_data)
+ goto found;
+ /* if the EEPROM is given by the driver, use it */
+ dev_dbg(ice->card->dev, "using the defined eeprom..\n");
+ ice->eeprom.version = 1;
+ ice->eeprom.size = c->eeprom_size + 6;
+ memcpy(ice->eeprom.data, c->eeprom_data, c->eeprom_size);
+ goto read_skipped;
+ }
+ }
+ dev_warn(ice->card->dev, "No matching model found for ID 0x%x\n",
+ ice->eeprom.subvendor);
+
+ found:
+ ice->eeprom.size = snd_ice1712_read_i2c(ice, dev, 0x04);
+ if (ice->eeprom.size < 6)
+ ice->eeprom.size = 32; /* FIXME: any cards without the correct size? */
+ else if (ice->eeprom.size > 32) {
+ dev_err(ice->card->dev,
+ "invalid EEPROM (size = %i)\n", ice->eeprom.size);
+ return -EIO;
+ }
+ ice->eeprom.version = snd_ice1712_read_i2c(ice, dev, 0x05);
+ if (ice->eeprom.version != 1) {
+ dev_err(ice->card->dev, "invalid EEPROM version %i\n",
+ ice->eeprom.version);
+ /* return -EIO; */
+ }
+ size = ice->eeprom.size - 6;
+ for (i = 0; i < size; i++)
+ ice->eeprom.data[i] = snd_ice1712_read_i2c(ice, dev, i + 6);
+
+ read_skipped:
+ ice->eeprom.gpiomask = ice->eeprom.data[ICE_EEP1_GPIO_MASK];
+ ice->eeprom.gpiostate = ice->eeprom.data[ICE_EEP1_GPIO_STATE];
+ ice->eeprom.gpiodir = ice->eeprom.data[ICE_EEP1_GPIO_DIR];
+
+ return 0;
+}
+
+
+
+static int snd_ice1712_chip_init(struct snd_ice1712 *ice)
+{
+ outb(ICE1712_RESET | ICE1712_NATIVE, ICEREG(ice, CONTROL));
+ udelay(200);
+ outb(ICE1712_NATIVE, ICEREG(ice, CONTROL));
+ udelay(200);
+ if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE &&
+ !ice->dxr_enable)
+ /* Set eeprom value to limit active ADCs and DACs to 6;
+ * Also disable AC97 as no hardware in standard 6fire card/box
+ * Note: DXR extensions are not currently supported
+ */
+ ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a;
+ pci_write_config_byte(ice->pci, 0x60, ice->eeprom.data[ICE_EEP1_CODEC]);
+ pci_write_config_byte(ice->pci, 0x61, ice->eeprom.data[ICE_EEP1_ACLINK]);
+ pci_write_config_byte(ice->pci, 0x62, ice->eeprom.data[ICE_EEP1_I2SID]);
+ pci_write_config_byte(ice->pci, 0x63, ice->eeprom.data[ICE_EEP1_SPDIF]);
+ if (ice->eeprom.subvendor != ICE1712_SUBDEVICE_STDSP24 &&
+ ice->eeprom.subvendor != ICE1712_SUBDEVICE_STAUDIO_ADCIII) {
+ ice->gpio.write_mask = ice->eeprom.gpiomask;
+ ice->gpio.direction = ice->eeprom.gpiodir;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK,
+ ice->eeprom.gpiomask);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION,
+ ice->eeprom.gpiodir);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA,
+ ice->eeprom.gpiostate);
+ } else {
+ ice->gpio.write_mask = 0xc0;
+ ice->gpio.direction = 0xff;
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_WRITE_MASK, 0xc0);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DIRECTION, 0xff);
+ snd_ice1712_write(ice, ICE1712_IREG_GPIO_DATA,
+ ICE1712_STDSP24_CLOCK_BIT);
+ }
+ snd_ice1712_write(ice, ICE1712_IREG_PRO_POWERDOWN, 0);
+ if (!(ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97)) {
+ outb(ICE1712_AC97_WARM, ICEREG(ice, AC97_CMD));
+ udelay(100);
+ outb(0, ICEREG(ice, AC97_CMD));
+ udelay(200);
+ snd_ice1712_write(ice, ICE1712_IREG_CONSUMER_POWERDOWN, 0);
+ }
+ snd_ice1712_set_pro_rate(ice, 48000, 1);
+ /* unmask used interrupts */
+ outb(((ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_2xMPU401) == 0 ?
+ ICE1712_IRQ_MPU2 : 0) |
+ ((ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97) ?
+ ICE1712_IRQ_PBKDS | ICE1712_IRQ_CONCAP | ICE1712_IRQ_CONPBK : 0),
+ ICEREG(ice, IRQMASK));
+ outb(0x00, ICEMT(ice, IRQ));
+
+ return 0;
+}
+
+int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice)
+{
+ int err;
+ struct snd_kcontrol *kctl;
+
+ if (snd_BUG_ON(!ice->pcm_pro))
+ return -EIO;
+ kctl = snd_ctl_new1(&snd_ice1712_spdif_default, ice);
+ kctl->id.device = ice->pcm_pro->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ kctl = snd_ctl_new1(&snd_ice1712_spdif_maskc, ice);
+ kctl->id.device = ice->pcm_pro->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ kctl = snd_ctl_new1(&snd_ice1712_spdif_maskp, ice);
+ kctl->id.device = ice->pcm_pro->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ kctl = snd_ctl_new1(&snd_ice1712_spdif_stream, ice);
+ kctl->id.device = ice->pcm_pro->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ ice->spdif.stream_ctl = kctl;
+ return 0;
+}
+
+
+static int snd_ice1712_build_controls(struct snd_ice1712 *ice)
+{
+ int err;
+
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_eeprom, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_internal_clock, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_internal_clock_default, ice));
+ if (err < 0)
+ return err;
+
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_rate_locking, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_pro_rate_reset, ice));
+ if (err < 0)
+ return err;
+
+ if (ice->num_total_dacs > 0) {
+ struct snd_kcontrol_new tmp = snd_ice1712_mixer_pro_analog_route;
+ tmp.count = ice->num_total_dacs;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&tmp, ice));
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_pro_spdif_route, ice));
+ if (err < 0)
+ return err;
+
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_mixer_pro_volume_rate, ice));
+ if (err < 0)
+ return err;
+ return snd_ctl_add(ice->card,
+ snd_ctl_new1(&snd_ice1712_mixer_pro_peak, ice));
+}
+
+static void snd_ice1712_free(struct snd_card *card)
+{
+ struct snd_ice1712 *ice = card->private_data;
+
+ if (ice->card_info && ice->card_info->chip_exit)
+ ice->card_info->chip_exit(ice);
+
+ /* mask all interrupts */
+ outb(ICE1712_MULTI_CAPTURE | ICE1712_MULTI_PLAYBACK, ICEMT(ice, IRQ));
+ outb(0xff, ICEREG(ice, IRQMASK));
+
+ snd_ice1712_akm4xxx_free(ice);
+}
+
+static int snd_ice1712_create(struct snd_card *card,
+ struct pci_dev *pci,
+ const char *modelname,
+ int omni,
+ int cs8427_timeout,
+ int dxr_enable)
+{
+ struct snd_ice1712 *ice = card->private_data;
+ int err;
+
+ /* enable PCI device */
+ err = pcim_enable_device(pci);
+ if (err < 0)
+ return err;
+ /* check, if we can restrict PCI DMA transfers to 28 bits */
+ if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(28))) {
+ dev_err(card->dev,
+ "architecture does not support 28bit PCI busmaster DMA\n");
+ return -ENXIO;
+ }
+
+ ice->omni = omni ? 1 : 0;
+ if (cs8427_timeout < 1)
+ cs8427_timeout = 1;
+ else if (cs8427_timeout > 1000)
+ cs8427_timeout = 1000;
+ ice->cs8427_timeout = cs8427_timeout;
+ ice->dxr_enable = dxr_enable;
+ spin_lock_init(&ice->reg_lock);
+ mutex_init(&ice->gpio_mutex);
+ mutex_init(&ice->i2c_mutex);
+ mutex_init(&ice->open_mutex);
+ ice->gpio.set_mask = snd_ice1712_set_gpio_mask;
+ ice->gpio.get_mask = snd_ice1712_get_gpio_mask;
+ ice->gpio.set_dir = snd_ice1712_set_gpio_dir;
+ ice->gpio.get_dir = snd_ice1712_get_gpio_dir;
+ ice->gpio.set_data = snd_ice1712_set_gpio_data;
+ ice->gpio.get_data = snd_ice1712_get_gpio_data;
+
+ ice->spdif.cs8403_bits =
+ ice->spdif.cs8403_stream_bits = (0x01 | /* consumer format */
+ 0x10 | /* no emphasis */
+ 0x20); /* PCM encoder/decoder */
+ ice->card = card;
+ ice->pci = pci;
+ ice->irq = -1;
+ pci_set_master(pci);
+ /* disable legacy emulation */
+ pci_write_config_word(ice->pci, 0x40, 0x807f);
+ pci_write_config_word(ice->pci, 0x42, 0x0006);
+ snd_ice1712_proc_init(ice);
+
+ err = pci_request_regions(pci, "ICE1712");
+ if (err < 0)
+ return err;
+ ice->port = pci_resource_start(pci, 0);
+ ice->ddma_port = pci_resource_start(pci, 1);
+ ice->dmapath_port = pci_resource_start(pci, 2);
+ ice->profi_port = pci_resource_start(pci, 3);
+
+ if (devm_request_irq(&pci->dev, pci->irq, snd_ice1712_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, ice)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ return -EIO;
+ }
+
+ ice->irq = pci->irq;
+ card->sync_irq = ice->irq;
+ card->private_free = snd_ice1712_free;
+
+ if (snd_ice1712_read_eeprom(ice, modelname) < 0)
+ return -EIO;
+ if (snd_ice1712_chip_init(ice) < 0)
+ return -EIO;
+
+ return 0;
+}
+
+
+/*
+ *
+ * Registration
+ *
+ */
+
+static struct snd_ice1712_card_info no_matched;
+
+static int snd_ice1712_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ struct snd_card *card;
+ struct snd_ice1712 *ice;
+ int pcm_dev = 0, err;
+ const struct snd_ice1712_card_info * const *tbl, *c;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(*ice), &card);
+ if (err < 0)
+ return err;
+ ice = card->private_data;
+
+ strcpy(card->driver, "ICE1712");
+ strcpy(card->shortname, "ICEnsemble ICE1712");
+
+ err = snd_ice1712_create(card, pci, model[dev], omni[dev],
+ cs8427_timeout[dev], dxr_enable[dev]);
+ if (err < 0)
+ return err;
+
+ for (tbl = card_tables; *tbl; tbl++) {
+ for (c = *tbl; c->subvendor; c++) {
+ if (c->subvendor == ice->eeprom.subvendor) {
+ strcpy(card->shortname, c->name);
+ if (c->driver) /* specific driver? */
+ strcpy(card->driver, c->driver);
+ if (c->chip_init) {
+ err = c->chip_init(ice);
+ if (err < 0)
+ return err;
+ }
+ ice->card_info = c;
+ goto __found;
+ }
+ }
+ }
+ c = &no_matched;
+ __found:
+
+ err = snd_ice1712_pcm_profi(ice, pcm_dev++);
+ if (err < 0)
+ return err;
+
+ if (ice_has_con_ac97(ice)) {
+ err = snd_ice1712_pcm(ice, pcm_dev++);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_ice1712_ac97_mixer(ice);
+ if (err < 0)
+ return err;
+
+ err = snd_ice1712_build_controls(ice);
+ if (err < 0)
+ return err;
+
+ if (c->build_controls) {
+ err = c->build_controls(ice);
+ if (err < 0)
+ return err;
+ }
+
+ if (ice_has_con_ac97(ice)) {
+ err = snd_ice1712_pcm_ds(ice, pcm_dev++);
+ if (err < 0)
+ return err;
+ }
+
+ if (!c->no_mpu401) {
+ err = snd_mpu401_uart_new(card, 0, MPU401_HW_ICE1712,
+ ICEREG(ice, MPU1_CTRL),
+ c->mpu401_1_info_flags |
+ MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK,
+ -1, &ice->rmidi[0]);
+ if (err < 0)
+ return err;
+ if (c->mpu401_1_name)
+ /* Preferred name available in card_info */
+ snprintf(ice->rmidi[0]->name,
+ sizeof(ice->rmidi[0]->name),
+ "%s %d", c->mpu401_1_name, card->number);
+
+ if (ice->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_2xMPU401) {
+ /* 2nd port used */
+ err = snd_mpu401_uart_new(card, 1, MPU401_HW_ICE1712,
+ ICEREG(ice, MPU2_CTRL),
+ c->mpu401_2_info_flags |
+ MPU401_INFO_INTEGRATED | MPU401_INFO_IRQ_HOOK,
+ -1, &ice->rmidi[1]);
+
+ if (err < 0)
+ return err;
+ if (c->mpu401_2_name)
+ /* Preferred name available in card_info */
+ snprintf(ice->rmidi[1]->name,
+ sizeof(ice->rmidi[1]->name),
+ "%s %d", c->mpu401_2_name,
+ card->number);
+ }
+ }
+
+ snd_ice1712_set_input_clock_source(ice, 0);
+
+ sprintf(card->longname, "%s at 0x%lx, irq %i",
+ card->shortname, ice->port, ice->irq);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int snd_ice1712_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_ice1712 *ice = card->private_data;
+
+ if (!ice->pm_suspend_enabled)
+ return 0;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+ snd_ac97_suspend(ice->ac97);
+
+ spin_lock_irq(&ice->reg_lock);
+ ice->pm_saved_is_spdif_master = is_spdif_master(ice);
+ ice->pm_saved_spdif_ctrl = inw(ICEMT(ice, ROUTE_SPDOUT));
+ ice->pm_saved_route = inw(ICEMT(ice, ROUTE_PSDOUT03));
+ spin_unlock_irq(&ice->reg_lock);
+
+ if (ice->pm_suspend)
+ ice->pm_suspend(ice);
+ return 0;
+}
+
+static int snd_ice1712_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_ice1712 *ice = card->private_data;
+ int rate;
+
+ if (!ice->pm_suspend_enabled)
+ return 0;
+
+ if (ice->cur_rate)
+ rate = ice->cur_rate;
+ else
+ rate = PRO_RATE_DEFAULT;
+
+ if (snd_ice1712_chip_init(ice) < 0) {
+ snd_card_disconnect(card);
+ return -EIO;
+ }
+
+ ice->cur_rate = rate;
+
+ if (ice->pm_resume)
+ ice->pm_resume(ice);
+
+ if (ice->pm_saved_is_spdif_master) {
+ /* switching to external clock via SPDIF */
+ spin_lock_irq(&ice->reg_lock);
+ outb(inb(ICEMT(ice, RATE)) | ICE1712_SPDIF_MASTER,
+ ICEMT(ice, RATE));
+ spin_unlock_irq(&ice->reg_lock);
+ snd_ice1712_set_input_clock_source(ice, 1);
+ } else {
+ /* internal on-card clock */
+ snd_ice1712_set_pro_rate(ice, rate, 1);
+ snd_ice1712_set_input_clock_source(ice, 0);
+ }
+
+ outw(ice->pm_saved_spdif_ctrl, ICEMT(ice, ROUTE_SPDOUT));
+ outw(ice->pm_saved_route, ICEMT(ice, ROUTE_PSDOUT03));
+
+ snd_ac97_resume(ice->ac97);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(snd_ice1712_pm, snd_ice1712_suspend, snd_ice1712_resume);
+#define SND_VT1712_PM_OPS &snd_ice1712_pm
+#else
+#define SND_VT1712_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver ice1712_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_ice1712_ids,
+ .probe = snd_ice1712_probe,
+ .driver = {
+ .pm = SND_VT1712_PM_OPS,
+ },
+};
+
+module_pci_driver(ice1712_driver);
diff --git a/sound/pci/ice1712/ice1712.h b/sound/pci/ice1712/ice1712.h
new file mode 100644
index 0000000000..cd02710d82
--- /dev/null
+++ b/sound/pci/ice1712/ice1712.h
@@ -0,0 +1,525 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_ICE1712_H
+#define __SOUND_ICE1712_H
+
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ */
+
+#include <linux/io.h>
+#include <sound/control.h>
+#include <sound/ac97_codec.h>
+#include <sound/rawmidi.h>
+#include <sound/i2c.h>
+#include <sound/ak4xxx-adda.h>
+#include <sound/ak4114.h>
+#include <sound/pt2258.h>
+#include <sound/pcm.h>
+#include <sound/mpu401.h>
+
+
+/*
+ * Direct registers
+ */
+
+#define ICEREG(ice, x) ((ice)->port + ICE1712_REG_##x)
+
+#define ICE1712_REG_CONTROL 0x00 /* byte */
+#define ICE1712_RESET 0x80 /* soft reset whole chip */
+#define ICE1712_SERR_ASSERT_DS_DMA 0x40 /* disabled SERR# assertion for the DS DMA Ch-C irq otherwise enabled */
+#define ICE1712_DOS_VOL 0x10 /* DOS WT/FM volume control */
+#define ICE1712_SERR_LEVEL 0x08 /* SERR# level otherwise edge */
+#define ICE1712_SERR_ASSERT_SB 0x02 /* disabled SERR# assertion for SB irq otherwise enabled */
+#define ICE1712_NATIVE 0x01 /* native mode otherwise SB */
+#define ICE1712_REG_IRQMASK 0x01 /* byte */
+#define ICE1712_IRQ_MPU1 0x80 /* MIDI irq mask */
+#define ICE1712_IRQ_TIMER 0x40 /* Timer mask */
+#define ICE1712_IRQ_MPU2 0x20 /* Secondary MIDI irq mask */
+#define ICE1712_IRQ_PROPCM 0x10 /* professional multi-track */
+#define ICE1712_IRQ_FM 0x08 /* FM/MIDI - legacy */
+#define ICE1712_IRQ_PBKDS 0x04 /* playback DS channels */
+#define ICE1712_IRQ_CONCAP 0x02 /* consumer capture */
+#define ICE1712_IRQ_CONPBK 0x01 /* consumer playback */
+#define ICE1712_REG_IRQSTAT 0x02 /* byte */
+/* look to ICE1712_IRQ_* */
+#define ICE1712_REG_INDEX 0x03 /* byte - indirect CCIxx regs */
+#define ICE1712_REG_DATA 0x04 /* byte - indirect CCIxx regs */
+#define ICE1712_REG_NMI_STAT1 0x05 /* byte */
+#define ICE1712_REG_NMI_DATA 0x06 /* byte */
+#define ICE1712_REG_NMI_INDEX 0x07 /* byte */
+#define ICE1712_REG_AC97_INDEX 0x08 /* byte */
+#define ICE1712_REG_AC97_CMD 0x09 /* byte */
+#define ICE1712_AC97_COLD 0x80 /* cold reset */
+#define ICE1712_AC97_WARM 0x40 /* warm reset */
+#define ICE1712_AC97_WRITE 0x20 /* W: write, R: write in progress */
+#define ICE1712_AC97_READ 0x10 /* W: read, R: read in progress */
+#define ICE1712_AC97_READY 0x08 /* codec ready status bit */
+#define ICE1712_AC97_PBK_VSR 0x02 /* playback VSR */
+#define ICE1712_AC97_CAP_VSR 0x01 /* capture VSR */
+#define ICE1712_REG_AC97_DATA 0x0a /* word (little endian) */
+#define ICE1712_REG_MPU1_CTRL 0x0c /* byte */
+#define ICE1712_REG_MPU1_DATA 0x0d /* byte */
+#define ICE1712_REG_I2C_DEV_ADDR 0x10 /* byte */
+#define ICE1712_I2C_WRITE 0x01 /* write direction */
+#define ICE1712_REG_I2C_BYTE_ADDR 0x11 /* byte */
+#define ICE1712_REG_I2C_DATA 0x12 /* byte */
+#define ICE1712_REG_I2C_CTRL 0x13 /* byte */
+#define ICE1712_I2C_EEPROM 0x80 /* EEPROM exists */
+#define ICE1712_I2C_BUSY 0x01 /* busy bit */
+#define ICE1712_REG_CONCAP_ADDR 0x14 /* dword - consumer capture */
+#define ICE1712_REG_CONCAP_COUNT 0x18 /* word - current/base count */
+#define ICE1712_REG_SERR_SHADOW 0x1b /* byte */
+#define ICE1712_REG_MPU2_CTRL 0x1c /* byte */
+#define ICE1712_REG_MPU2_DATA 0x1d /* byte */
+#define ICE1712_REG_TIMER 0x1e /* word */
+
+/*
+ * Indirect registers
+ */
+
+#define ICE1712_IREG_PBK_COUNT_LO 0x00
+#define ICE1712_IREG_PBK_COUNT_HI 0x01
+#define ICE1712_IREG_PBK_CTRL 0x02
+#define ICE1712_IREG_PBK_LEFT 0x03 /* left volume */
+#define ICE1712_IREG_PBK_RIGHT 0x04 /* right volume */
+#define ICE1712_IREG_PBK_SOFT 0x05 /* soft volume */
+#define ICE1712_IREG_PBK_RATE_LO 0x06
+#define ICE1712_IREG_PBK_RATE_MID 0x07
+#define ICE1712_IREG_PBK_RATE_HI 0x08
+#define ICE1712_IREG_CAP_COUNT_LO 0x10
+#define ICE1712_IREG_CAP_COUNT_HI 0x11
+#define ICE1712_IREG_CAP_CTRL 0x12
+#define ICE1712_IREG_GPIO_DATA 0x20
+#define ICE1712_IREG_GPIO_WRITE_MASK 0x21
+#define ICE1712_IREG_GPIO_DIRECTION 0x22
+#define ICE1712_IREG_CONSUMER_POWERDOWN 0x30
+#define ICE1712_IREG_PRO_POWERDOWN 0x31
+
+/*
+ * Consumer section direct DMA registers
+ */
+
+#define ICEDS(ice, x) ((ice)->dmapath_port + ICE1712_DS_##x)
+
+#define ICE1712_DS_INTMASK 0x00 /* word - interrupt mask */
+#define ICE1712_DS_INTSTAT 0x02 /* word - interrupt status */
+#define ICE1712_DS_DATA 0x04 /* dword - channel data */
+#define ICE1712_DS_INDEX 0x08 /* dword - channel index */
+
+/*
+ * Consumer section channel registers
+ */
+
+#define ICE1712_DSC_ADDR0 0x00 /* dword - base address 0 */
+#define ICE1712_DSC_COUNT0 0x01 /* word - count 0 */
+#define ICE1712_DSC_ADDR1 0x02 /* dword - base address 1 */
+#define ICE1712_DSC_COUNT1 0x03 /* word - count 1 */
+#define ICE1712_DSC_CONTROL 0x04 /* byte - control & status */
+#define ICE1712_BUFFER1 0x80 /* buffer1 is active */
+#define ICE1712_BUFFER1_AUTO 0x40 /* buffer1 auto init */
+#define ICE1712_BUFFER0_AUTO 0x20 /* buffer0 auto init */
+#define ICE1712_FLUSH 0x10 /* flush FIFO */
+#define ICE1712_STEREO 0x08 /* stereo */
+#define ICE1712_16BIT 0x04 /* 16-bit data */
+#define ICE1712_PAUSE 0x02 /* pause */
+#define ICE1712_START 0x01 /* start */
+#define ICE1712_DSC_RATE 0x05 /* dword - rate */
+#define ICE1712_DSC_VOLUME 0x06 /* word - volume control */
+
+/*
+ * Professional multi-track direct control registers
+ */
+
+#define ICEMT(ice, x) ((ice)->profi_port + ICE1712_MT_##x)
+
+#define ICE1712_MT_IRQ 0x00 /* byte - interrupt mask */
+#define ICE1712_MULTI_CAPTURE 0x80 /* capture IRQ */
+#define ICE1712_MULTI_PLAYBACK 0x40 /* playback IRQ */
+#define ICE1712_MULTI_CAPSTATUS 0x02 /* capture IRQ status */
+#define ICE1712_MULTI_PBKSTATUS 0x01 /* playback IRQ status */
+#define ICE1712_MT_RATE 0x01 /* byte - sampling rate select */
+#define ICE1712_SPDIF_MASTER 0x10 /* S/PDIF input is master clock */
+#define ICE1712_MT_I2S_FORMAT 0x02 /* byte - I2S data format */
+#define ICE1712_MT_AC97_INDEX 0x04 /* byte - AC'97 index */
+#define ICE1712_MT_AC97_CMD 0x05 /* byte - AC'97 command & status */
+/* look to ICE1712_AC97_* */
+#define ICE1712_MT_AC97_DATA 0x06 /* word - AC'97 data */
+#define ICE1712_MT_PLAYBACK_ADDR 0x10 /* dword - playback address */
+#define ICE1712_MT_PLAYBACK_SIZE 0x14 /* word - playback size */
+#define ICE1712_MT_PLAYBACK_COUNT 0x16 /* word - playback count */
+#define ICE1712_MT_PLAYBACK_CONTROL 0x18 /* byte - control */
+#define ICE1712_CAPTURE_START_SHADOW 0x04 /* capture start */
+#define ICE1712_PLAYBACK_PAUSE 0x02 /* playback pause */
+#define ICE1712_PLAYBACK_START 0x01 /* playback start */
+#define ICE1712_MT_CAPTURE_ADDR 0x20 /* dword - capture address */
+#define ICE1712_MT_CAPTURE_SIZE 0x24 /* word - capture size */
+#define ICE1712_MT_CAPTURE_COUNT 0x26 /* word - capture count */
+#define ICE1712_MT_CAPTURE_CONTROL 0x28 /* byte - control */
+#define ICE1712_CAPTURE_START 0x01 /* capture start */
+#define ICE1712_MT_ROUTE_PSDOUT03 0x30 /* word */
+#define ICE1712_MT_ROUTE_SPDOUT 0x32 /* word */
+#define ICE1712_MT_ROUTE_CAPTURE 0x34 /* dword */
+#define ICE1712_MT_MONITOR_VOLUME 0x38 /* word */
+#define ICE1712_MT_MONITOR_INDEX 0x3a /* byte */
+#define ICE1712_MT_MONITOR_RATE 0x3b /* byte */
+#define ICE1712_MT_MONITOR_ROUTECTRL 0x3c /* byte */
+#define ICE1712_ROUTE_AC97 0x01 /* route digital mixer output to AC'97 */
+#define ICE1712_MT_MONITOR_PEAKINDEX 0x3e /* byte */
+#define ICE1712_MT_MONITOR_PEAKDATA 0x3f /* byte */
+
+/*
+ * Codec configuration bits
+ */
+
+/* PCI[60] System Configuration */
+#define ICE1712_CFG_CLOCK 0xc0
+#define ICE1712_CFG_CLOCK512 0x00 /* 22.5692Mhz, 44.1kHz*512 */
+#define ICE1712_CFG_CLOCK384 0x40 /* 16.9344Mhz, 44.1kHz*384 */
+#define ICE1712_CFG_EXT 0x80 /* external clock */
+#define ICE1712_CFG_2xMPU401 0x20 /* two MPU401 UARTs */
+#define ICE1712_CFG_NO_CON_AC97 0x10 /* consumer AC'97 codec is not present */
+#define ICE1712_CFG_ADC_MASK 0x0c /* one, two, three, four stereo ADCs */
+#define ICE1712_CFG_DAC_MASK 0x03 /* one, two, three, four stereo DACs */
+/* PCI[61] AC-Link Configuration */
+#define ICE1712_CFG_PRO_I2S 0x80 /* multitrack converter: I2S or AC'97 */
+#define ICE1712_CFG_AC97_PACKED 0x01 /* split or packed mode - AC'97 */
+/* PCI[62] I2S Features */
+#define ICE1712_CFG_I2S_VOLUME 0x80 /* volume/mute capability */
+#define ICE1712_CFG_I2S_96KHZ 0x40 /* supports 96kHz sampling */
+#define ICE1712_CFG_I2S_RESMASK 0x30 /* resolution mask, 16,18,20,24-bit */
+#define ICE1712_CFG_I2S_OTHER 0x0f /* other I2S IDs */
+/* PCI[63] S/PDIF Configuration */
+#define ICE1712_CFG_I2S_CHIPID 0xfc /* I2S chip ID */
+#define ICE1712_CFG_SPDIF_IN 0x02 /* S/PDIF input is present */
+#define ICE1712_CFG_SPDIF_OUT 0x01 /* S/PDIF output is present */
+
+/*
+ * DMA mode values
+ * identical with DMA_XXX on i386 architecture.
+ */
+#define ICE1712_DMA_MODE_WRITE 0x48
+#define ICE1712_DMA_AUTOINIT 0x10
+
+
+/*
+ * I2C EEPROM Address
+ */
+#define ICE_I2C_EEPROM_ADDR 0xA0
+
+struct snd_ice1712;
+
+struct snd_ice1712_eeprom {
+ unsigned int subvendor; /* PCI[2c-2f] */
+ unsigned char size; /* size of EEPROM image in bytes */
+ unsigned char version; /* must be 1 (or 2 for vt1724) */
+ unsigned char data[32];
+ unsigned int gpiomask;
+ unsigned int gpiostate;
+ unsigned int gpiodir;
+};
+
+enum {
+ ICE_EEP1_CODEC = 0, /* 06 */
+ ICE_EEP1_ACLINK, /* 07 */
+ ICE_EEP1_I2SID, /* 08 */
+ ICE_EEP1_SPDIF, /* 09 */
+ ICE_EEP1_GPIO_MASK, /* 0a */
+ ICE_EEP1_GPIO_STATE, /* 0b */
+ ICE_EEP1_GPIO_DIR, /* 0c */
+ ICE_EEP1_AC97_MAIN_LO, /* 0d */
+ ICE_EEP1_AC97_MAIN_HI, /* 0e */
+ ICE_EEP1_AC97_PCM_LO, /* 0f */
+ ICE_EEP1_AC97_PCM_HI, /* 10 */
+ ICE_EEP1_AC97_REC_LO, /* 11 */
+ ICE_EEP1_AC97_REC_HI, /* 12 */
+ ICE_EEP1_AC97_RECSRC, /* 13 */
+ ICE_EEP1_DAC_ID, /* 14 */
+ ICE_EEP1_DAC_ID1,
+ ICE_EEP1_DAC_ID2,
+ ICE_EEP1_DAC_ID3,
+ ICE_EEP1_ADC_ID, /* 18 */
+ ICE_EEP1_ADC_ID1,
+ ICE_EEP1_ADC_ID2,
+ ICE_EEP1_ADC_ID3
+};
+
+#define ice_has_con_ac97(ice) (!((ice)->eeprom.data[ICE_EEP1_CODEC] & ICE1712_CFG_NO_CON_AC97))
+
+
+struct snd_ak4xxx_private {
+ unsigned int cif:1; /* CIF mode */
+ unsigned char caddr; /* C0 and C1 bits */
+ unsigned int data_mask; /* DATA gpio bit */
+ unsigned int clk_mask; /* CLK gpio bit */
+ unsigned int cs_mask; /* bit mask for select/deselect address */
+ unsigned int cs_addr; /* bits to select address */
+ unsigned int cs_none; /* bits to deselect address */
+ unsigned int add_flags; /* additional bits at init */
+ unsigned int mask_flags; /* total mask bits */
+ struct snd_akm4xxx_ops {
+ void (*set_rate_val)(struct snd_akm4xxx *ak, unsigned int rate);
+ } ops;
+};
+
+struct snd_ice1712_spdif {
+ unsigned char cs8403_bits;
+ unsigned char cs8403_stream_bits;
+ struct snd_kcontrol *stream_ctl;
+
+ struct snd_ice1712_spdif_ops {
+ void (*open)(struct snd_ice1712 *, struct snd_pcm_substream *);
+ void (*setup_rate)(struct snd_ice1712 *, int rate);
+ void (*close)(struct snd_ice1712 *, struct snd_pcm_substream *);
+ void (*default_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+ int (*default_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+ void (*stream_get)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+ int (*stream_put)(struct snd_ice1712 *, struct snd_ctl_elem_value *ucontrol);
+ } ops;
+};
+
+struct snd_ice1712_card_info;
+
+struct snd_ice1712 {
+ unsigned long conp_dma_size;
+ unsigned long conc_dma_size;
+ unsigned long prop_dma_size;
+ unsigned long proc_dma_size;
+ int irq;
+
+ unsigned long port;
+ unsigned long ddma_port;
+ unsigned long dmapath_port;
+ unsigned long profi_port;
+
+ struct pci_dev *pci;
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_pcm *pcm_ds;
+ struct snd_pcm *pcm_pro;
+ struct snd_pcm_substream *playback_con_substream;
+ struct snd_pcm_substream *playback_con_substream_ds[6];
+ struct snd_pcm_substream *capture_con_substream;
+ struct snd_pcm_substream *playback_pro_substream;
+ struct snd_pcm_substream *capture_pro_substream;
+ unsigned int playback_pro_size;
+ unsigned int capture_pro_size;
+ unsigned int playback_con_virt_addr[6];
+ unsigned int playback_con_active_buf[6];
+ unsigned int capture_con_virt_addr;
+ unsigned int ac97_ext_id;
+ struct snd_ac97 *ac97;
+ struct snd_rawmidi *rmidi[2];
+
+ spinlock_t reg_lock;
+ struct snd_info_entry *proc_entry;
+
+ struct snd_ice1712_eeprom eeprom;
+ const struct snd_ice1712_card_info *card_info;
+
+ unsigned int pro_volumes[20];
+ unsigned int omni:1; /* Delta Omni I/O */
+ unsigned int dxr_enable:1; /* Terratec DXR enable for DMX6FIRE */
+ unsigned int vt1724:1;
+ unsigned int vt1720:1;
+ unsigned int has_spdif:1; /* VT1720/4 - has SPDIF I/O */
+ unsigned int force_pdma4:1; /* VT1720/4 - PDMA4 as non-spdif */
+ unsigned int force_rdma1:1; /* VT1720/4 - RDMA1 as non-spdif */
+ unsigned int midi_output:1; /* VT1720/4: MIDI output triggered */
+ unsigned int midi_input:1; /* VT1720/4: MIDI input triggered */
+ unsigned int own_routing:1; /* VT1720/4: use own routing ctls */
+ unsigned int num_total_dacs; /* total DACs */
+ unsigned int num_total_adcs; /* total ADCs */
+ unsigned int cur_rate; /* current rate */
+
+ struct mutex open_mutex;
+ struct snd_pcm_substream *pcm_reserved[4];
+ const struct snd_pcm_hw_constraint_list *hw_rates; /* card-specific rate constraints */
+
+ unsigned int akm_codecs;
+ struct snd_akm4xxx *akm;
+ struct snd_ice1712_spdif spdif;
+
+ struct mutex i2c_mutex; /* I2C mutex for ICE1724 registers */
+ struct snd_i2c_bus *i2c; /* I2C bus */
+ struct snd_i2c_device *cs8427; /* CS8427 I2C device */
+ unsigned int cs8427_timeout; /* CS8427 reset timeout in HZ/100 */
+
+ struct ice1712_gpio {
+ unsigned int direction; /* current direction bits */
+ unsigned int write_mask; /* current mask bits */
+ unsigned int saved[2]; /* for ewx_i2c */
+ /* operators */
+ void (*set_mask)(struct snd_ice1712 *ice, unsigned int data);
+ unsigned int (*get_mask)(struct snd_ice1712 *ice);
+ void (*set_dir)(struct snd_ice1712 *ice, unsigned int data);
+ unsigned int (*get_dir)(struct snd_ice1712 *ice);
+ void (*set_data)(struct snd_ice1712 *ice, unsigned int data);
+ unsigned int (*get_data)(struct snd_ice1712 *ice);
+ /* misc operators - move to another place? */
+ void (*set_pro_rate)(struct snd_ice1712 *ice, unsigned int rate);
+ void (*i2s_mclk_changed)(struct snd_ice1712 *ice);
+ } gpio;
+ struct mutex gpio_mutex;
+
+ /* other board-specific data */
+ void *spec;
+
+ /* VT172x specific */
+ int pro_rate_default;
+ int (*is_spdif_master)(struct snd_ice1712 *ice);
+ unsigned int (*get_rate)(struct snd_ice1712 *ice);
+ void (*set_rate)(struct snd_ice1712 *ice, unsigned int rate);
+ unsigned char (*set_mclk)(struct snd_ice1712 *ice, unsigned int rate);
+ int (*set_spdif_clock)(struct snd_ice1712 *ice, int type);
+ int (*get_spdif_master_type)(struct snd_ice1712 *ice);
+ const char * const *ext_clock_names;
+ int ext_clock_count;
+ void (*pro_open)(struct snd_ice1712 *, struct snd_pcm_substream *);
+#ifdef CONFIG_PM_SLEEP
+ int (*pm_suspend)(struct snd_ice1712 *);
+ int (*pm_resume)(struct snd_ice1712 *);
+ unsigned int pm_suspend_enabled:1;
+ unsigned int pm_saved_is_spdif_master:1;
+ unsigned int pm_saved_spdif_ctrl;
+ unsigned char pm_saved_spdif_cfg;
+ unsigned int pm_saved_route;
+#endif
+};
+
+
+/*
+ * gpio access functions
+ */
+static inline void snd_ice1712_gpio_set_dir(struct snd_ice1712 *ice, unsigned int bits)
+{
+ ice->gpio.set_dir(ice, bits);
+}
+
+static inline unsigned int snd_ice1712_gpio_get_dir(struct snd_ice1712 *ice)
+{
+ return ice->gpio.get_dir(ice);
+}
+
+static inline void snd_ice1712_gpio_set_mask(struct snd_ice1712 *ice, unsigned int bits)
+{
+ ice->gpio.set_mask(ice, bits);
+}
+
+static inline void snd_ice1712_gpio_write(struct snd_ice1712 *ice, unsigned int val)
+{
+ ice->gpio.set_data(ice, val);
+}
+
+static inline unsigned int snd_ice1712_gpio_read(struct snd_ice1712 *ice)
+{
+ return ice->gpio.get_data(ice);
+}
+
+/*
+ * save and restore gpio status
+ * The access to gpio will be protected by mutex, so don't forget to
+ * restore!
+ */
+static inline void snd_ice1712_save_gpio_status(struct snd_ice1712 *ice)
+{
+ mutex_lock(&ice->gpio_mutex);
+ ice->gpio.saved[0] = ice->gpio.direction;
+ ice->gpio.saved[1] = ice->gpio.write_mask;
+}
+
+static inline void snd_ice1712_restore_gpio_status(struct snd_ice1712 *ice)
+{
+ ice->gpio.set_dir(ice, ice->gpio.saved[0]);
+ ice->gpio.set_mask(ice, ice->gpio.saved[1]);
+ ice->gpio.direction = ice->gpio.saved[0];
+ ice->gpio.write_mask = ice->gpio.saved[1];
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+/* for bit controls */
+#define ICE1712_GPIO(xiface, xname, xindex, mask, invert, xaccess) \
+{ .iface = xiface, .name = xname, .access = xaccess, .info = snd_ctl_boolean_mono_info, \
+ .get = snd_ice1712_gpio_get, .put = snd_ice1712_gpio_put, \
+ .private_value = mask | (invert << 24) }
+
+int snd_ice1712_gpio_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+
+/*
+ * set gpio direction, write mask and data
+ */
+static inline void snd_ice1712_gpio_write_bits(struct snd_ice1712 *ice,
+ unsigned int mask, unsigned int bits)
+{
+ unsigned val;
+
+ ice->gpio.direction |= mask;
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+ val = snd_ice1712_gpio_read(ice);
+ val &= ~mask;
+ val |= mask & bits;
+ snd_ice1712_gpio_write(ice, val);
+}
+
+static inline int snd_ice1712_gpio_read_bits(struct snd_ice1712 *ice,
+ unsigned int mask)
+{
+ ice->gpio.direction &= ~mask;
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+ return snd_ice1712_gpio_read(ice) & mask;
+}
+
+/* route access functions */
+int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift);
+int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val,
+ int shift);
+
+int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice);
+
+int snd_ice1712_akm4xxx_init(struct snd_akm4xxx *ak,
+ const struct snd_akm4xxx *template,
+ const struct snd_ak4xxx_private *priv,
+ struct snd_ice1712 *ice);
+void snd_ice1712_akm4xxx_free(struct snd_ice1712 *ice);
+int snd_ice1712_akm4xxx_build_controls(struct snd_ice1712 *ice);
+
+int snd_ice1712_init_cs8427(struct snd_ice1712 *ice, int addr);
+
+static inline void snd_ice1712_write(struct snd_ice1712 *ice, u8 addr, u8 data)
+{
+ outb(addr, ICEREG(ice, INDEX));
+ outb(data, ICEREG(ice, DATA));
+}
+
+static inline u8 snd_ice1712_read(struct snd_ice1712 *ice, u8 addr)
+{
+ outb(addr, ICEREG(ice, INDEX));
+ return inb(ICEREG(ice, DATA));
+}
+
+
+/*
+ * entry pointer
+ */
+
+struct snd_ice1712_card_info {
+ unsigned int subvendor;
+ const char *name;
+ const char *model;
+ const 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;
+ unsigned int mpu401_2_info_flags;
+ const char *mpu401_1_name;
+ const char *mpu401_2_name;
+ const unsigned int eeprom_size;
+ const unsigned char *eeprom_data;
+};
+
+
+#endif /* __SOUND_ICE1712_H */
diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c
new file mode 100644
index 0000000000..1dc776acd6
--- /dev/null
+++ b/sound/pci/ice1712/ice1724.c
@@ -0,0 +1,2757 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for VT1724 ICEnsemble ICE1724 / VIA VT1724 (Envy24HT)
+ * VIA VT1720 (Envy24PT)
+ *
+ * Copyright (c) 2000 Jaroslav Kysela <perex@perex.cz>
+ * 2002 James Stafford <jstafford@ampltd.com>
+ * 2003 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+#include <sound/initval.h>
+
+#include <sound/asoundef.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+
+/* lowlevel routines */
+#include "amp.h"
+#include "revo.h"
+#include "aureon.h"
+#include "vt1720_mobo.h"
+#include "pontis.h"
+#include "prodigy192.h"
+#include "prodigy_hifi.h"
+#include "juli.h"
+#include "maya44.h"
+#include "phase.h"
+#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)");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+static char *model[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for ICE1724 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for ICE1724 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable ICE1724 soundcard.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Use the given board model.");
+
+
+/* Both VT1720 and VT1724 have the same PCI IDs */
+static const struct pci_device_id snd_vt1724_ids[] = {
+ { PCI_VDEVICE(ICE, PCI_DEVICE_ID_VT1724), 0 },
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_vt1724_ids);
+
+
+static int PRO_RATE_LOCKED;
+static int PRO_RATE_RESET = 1;
+static unsigned int PRO_RATE_DEFAULT = 44100;
+
+static const char * const ext_clock_names[1] = { "IEC958 In" };
+
+/*
+ * Basic I/O
+ */
+
+/*
+ * default rates, default clock routines
+ */
+
+/* check whether the clock mode is spdif-in */
+static inline int stdclock_is_spdif_master(struct snd_ice1712 *ice)
+{
+ return (inb(ICEMT1724(ice, RATE)) & VT1724_SPDIF_MASTER) ? 1 : 0;
+}
+
+/*
+ * locking rate makes sense only for internal clock mode
+ */
+static inline int is_pro_rate_locked(struct snd_ice1712 *ice)
+{
+ return (!ice->is_spdif_master(ice)) && PRO_RATE_LOCKED;
+}
+
+/*
+ * ac97 section
+ */
+
+static unsigned char snd_vt1724_ac97_ready(struct snd_ice1712 *ice)
+{
+ unsigned char old_cmd;
+ int tm;
+ for (tm = 0; tm < 0x10000; tm++) {
+ old_cmd = inb(ICEMT1724(ice, AC97_CMD));
+ if (old_cmd & (VT1724_AC97_WRITE | VT1724_AC97_READ))
+ continue;
+ if (!(old_cmd & VT1724_AC97_READY))
+ continue;
+ return old_cmd;
+ }
+ dev_dbg(ice->card->dev, "snd_vt1724_ac97_ready: timeout\n");
+ return old_cmd;
+}
+
+static int snd_vt1724_ac97_wait_bit(struct snd_ice1712 *ice, unsigned char bit)
+{
+ int tm;
+ for (tm = 0; tm < 0x10000; tm++)
+ if ((inb(ICEMT1724(ice, AC97_CMD)) & bit) == 0)
+ return 0;
+ dev_dbg(ice->card->dev, "snd_vt1724_ac97_wait_bit: timeout\n");
+ return -EIO;
+}
+
+static void snd_vt1724_ac97_write(struct snd_ac97 *ac97,
+ unsigned short reg,
+ unsigned short val)
+{
+ struct snd_ice1712 *ice = ac97->private_data;
+ unsigned char old_cmd;
+
+ old_cmd = snd_vt1724_ac97_ready(ice);
+ old_cmd &= ~VT1724_AC97_ID_MASK;
+ old_cmd |= ac97->num;
+ outb(reg, ICEMT1724(ice, AC97_INDEX));
+ outw(val, ICEMT1724(ice, AC97_DATA));
+ outb(old_cmd | VT1724_AC97_WRITE, ICEMT1724(ice, AC97_CMD));
+ snd_vt1724_ac97_wait_bit(ice, VT1724_AC97_WRITE);
+}
+
+static unsigned short snd_vt1724_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
+{
+ struct snd_ice1712 *ice = ac97->private_data;
+ unsigned char old_cmd;
+
+ old_cmd = snd_vt1724_ac97_ready(ice);
+ old_cmd &= ~VT1724_AC97_ID_MASK;
+ old_cmd |= ac97->num;
+ outb(reg, ICEMT1724(ice, AC97_INDEX));
+ outb(old_cmd | VT1724_AC97_READ, ICEMT1724(ice, AC97_CMD));
+ if (snd_vt1724_ac97_wait_bit(ice, VT1724_AC97_READ) < 0)
+ return ~0;
+ return inw(ICEMT1724(ice, AC97_DATA));
+}
+
+
+/*
+ * GPIO operations
+ */
+
+/* set gpio direction 0 = read, 1 = write */
+static void snd_vt1724_set_gpio_dir(struct snd_ice1712 *ice, unsigned int data)
+{
+ outl(data, ICEREG1724(ice, GPIO_DIRECTION));
+ inw(ICEREG1724(ice, GPIO_DIRECTION)); /* dummy read for pci-posting */
+}
+
+/* get gpio direction 0 = read, 1 = write */
+static unsigned int snd_vt1724_get_gpio_dir(struct snd_ice1712 *ice)
+{
+ return inl(ICEREG1724(ice, GPIO_DIRECTION));
+}
+
+/* set the gpio mask (0 = writable) */
+static void snd_vt1724_set_gpio_mask(struct snd_ice1712 *ice, unsigned int data)
+{
+ outw(data, ICEREG1724(ice, GPIO_WRITE_MASK));
+ if (!ice->vt1720) /* VT1720 supports only 16 GPIO bits */
+ outb((data >> 16) & 0xff, ICEREG1724(ice, GPIO_WRITE_MASK_22));
+ inw(ICEREG1724(ice, GPIO_WRITE_MASK)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_vt1724_get_gpio_mask(struct snd_ice1712 *ice)
+{
+ unsigned int mask;
+ if (!ice->vt1720)
+ mask = (unsigned int)inb(ICEREG1724(ice, GPIO_WRITE_MASK_22));
+ else
+ mask = 0;
+ mask = (mask << 16) | inw(ICEREG1724(ice, GPIO_WRITE_MASK));
+ return mask;
+}
+
+static void snd_vt1724_set_gpio_data(struct snd_ice1712 *ice, unsigned int data)
+{
+ outw(data, ICEREG1724(ice, GPIO_DATA));
+ if (!ice->vt1720)
+ outb(data >> 16, ICEREG1724(ice, GPIO_DATA_22));
+ inw(ICEREG1724(ice, GPIO_DATA)); /* dummy read for pci-posting */
+}
+
+static unsigned int snd_vt1724_get_gpio_data(struct snd_ice1712 *ice)
+{
+ unsigned int data;
+ if (!ice->vt1720)
+ data = (unsigned int)inb(ICEREG1724(ice, GPIO_DATA_22));
+ else
+ data = 0;
+ data = (data << 16) | inw(ICEREG1724(ice, GPIO_DATA));
+ return data;
+}
+
+/*
+ * MIDI
+ */
+
+static void vt1724_midi_clear_rx(struct snd_ice1712 *ice)
+{
+ unsigned int count;
+
+ for (count = inb(ICEREG1724(ice, MPU_RXFIFO)); count > 0; --count)
+ inb(ICEREG1724(ice, MPU_DATA));
+}
+
+static inline struct snd_rawmidi_substream *
+get_rawmidi_substream(struct snd_ice1712 *ice, unsigned int stream)
+{
+ return list_first_entry(&ice->rmidi[0]->streams[stream].substreams,
+ struct snd_rawmidi_substream, list);
+}
+
+static void enable_midi_irq(struct snd_ice1712 *ice, u8 flag, int enable);
+
+static void vt1724_midi_write(struct snd_ice1712 *ice)
+{
+ struct snd_rawmidi_substream *s;
+ int count, i;
+ u8 buffer[32];
+
+ s = get_rawmidi_substream(ice, SNDRV_RAWMIDI_STREAM_OUTPUT);
+ count = 31 - inb(ICEREG1724(ice, MPU_TXFIFO));
+ if (count > 0) {
+ count = snd_rawmidi_transmit(s, buffer, count);
+ for (i = 0; i < count; ++i)
+ outb(buffer[i], ICEREG1724(ice, MPU_DATA));
+ }
+ /* mask irq when all bytes have been transmitted.
+ * enabled again in output_trigger when the new data comes in.
+ */
+ enable_midi_irq(ice, VT1724_IRQ_MPU_TX,
+ !snd_rawmidi_transmit_empty(s));
+}
+
+static void vt1724_midi_read(struct snd_ice1712 *ice)
+{
+ struct snd_rawmidi_substream *s;
+ int count, i;
+ u8 buffer[32];
+
+ s = get_rawmidi_substream(ice, SNDRV_RAWMIDI_STREAM_INPUT);
+ count = inb(ICEREG1724(ice, MPU_RXFIFO));
+ if (count > 0) {
+ count = min(count, 32);
+ for (i = 0; i < count; ++i)
+ buffer[i] = inb(ICEREG1724(ice, MPU_DATA));
+ snd_rawmidi_receive(s, buffer, count);
+ }
+}
+
+/* call with ice->reg_lock */
+static void enable_midi_irq(struct snd_ice1712 *ice, u8 flag, int enable)
+{
+ u8 mask = inb(ICEREG1724(ice, IRQMASK));
+ if (enable)
+ mask &= ~flag;
+ else
+ mask |= flag;
+ outb(mask, ICEREG1724(ice, IRQMASK));
+}
+
+static void vt1724_enable_midi_irq(struct snd_rawmidi_substream *substream,
+ u8 flag, int enable)
+{
+ struct snd_ice1712 *ice = substream->rmidi->private_data;
+
+ spin_lock_irq(&ice->reg_lock);
+ enable_midi_irq(ice, flag, enable);
+ spin_unlock_irq(&ice->reg_lock);
+}
+
+static int vt1724_midi_output_open(struct snd_rawmidi_substream *s)
+{
+ return 0;
+}
+
+static int vt1724_midi_output_close(struct snd_rawmidi_substream *s)
+{
+ return 0;
+}
+
+static void vt1724_midi_output_trigger(struct snd_rawmidi_substream *s, int up)
+{
+ struct snd_ice1712 *ice = s->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if (up) {
+ ice->midi_output = 1;
+ vt1724_midi_write(ice);
+ } else {
+ ice->midi_output = 0;
+ enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0);
+ }
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+static void vt1724_midi_output_drain(struct snd_rawmidi_substream *s)
+{
+ struct snd_ice1712 *ice = s->rmidi->private_data;
+ unsigned long timeout;
+
+ vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_TX, 0);
+ /* 32 bytes should be transmitted in less than about 12 ms */
+ timeout = jiffies + msecs_to_jiffies(15);
+ do {
+ if (inb(ICEREG1724(ice, MPU_CTRL)) & VT1724_MPU_TX_EMPTY)
+ break;
+ schedule_timeout_uninterruptible(1);
+ } while (time_after(timeout, jiffies));
+}
+
+static const struct snd_rawmidi_ops vt1724_midi_output_ops = {
+ .open = vt1724_midi_output_open,
+ .close = vt1724_midi_output_close,
+ .trigger = vt1724_midi_output_trigger,
+ .drain = vt1724_midi_output_drain,
+};
+
+static int vt1724_midi_input_open(struct snd_rawmidi_substream *s)
+{
+ vt1724_midi_clear_rx(s->rmidi->private_data);
+ vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_RX, 1);
+ return 0;
+}
+
+static int vt1724_midi_input_close(struct snd_rawmidi_substream *s)
+{
+ vt1724_enable_midi_irq(s, VT1724_IRQ_MPU_RX, 0);
+ return 0;
+}
+
+static void vt1724_midi_input_trigger(struct snd_rawmidi_substream *s, int up)
+{
+ struct snd_ice1712 *ice = s->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if (up) {
+ ice->midi_input = 1;
+ vt1724_midi_read(ice);
+ } else {
+ ice->midi_input = 0;
+ }
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+static const struct snd_rawmidi_ops vt1724_midi_input_ops = {
+ .open = vt1724_midi_input_open,
+ .close = vt1724_midi_input_close,
+ .trigger = vt1724_midi_input_trigger,
+};
+
+
+/*
+ * Interrupt handler
+ */
+
+static irqreturn_t snd_vt1724_interrupt(int irq, void *dev_id)
+{
+ struct snd_ice1712 *ice = dev_id;
+ unsigned char status;
+ unsigned char status_mask =
+ VT1724_IRQ_MPU_RX | VT1724_IRQ_MPU_TX | VT1724_IRQ_MTPCM;
+ int handled = 0;
+ int timeout = 0;
+
+ while (1) {
+ status = inb(ICEREG1724(ice, IRQSTAT));
+ status &= status_mask;
+ if (status == 0)
+ break;
+ spin_lock(&ice->reg_lock);
+ if (++timeout > 10) {
+ status = inb(ICEREG1724(ice, IRQSTAT));
+ dev_err(ice->card->dev,
+ "Too long irq loop, status = 0x%x\n", status);
+ if (status & VT1724_IRQ_MPU_TX) {
+ dev_err(ice->card->dev, "Disabling MPU_TX\n");
+ enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0);
+ }
+ spin_unlock(&ice->reg_lock);
+ break;
+ }
+ handled = 1;
+ if (status & VT1724_IRQ_MPU_TX) {
+ if (ice->midi_output)
+ vt1724_midi_write(ice);
+ else
+ enable_midi_irq(ice, VT1724_IRQ_MPU_TX, 0);
+ /* Due to mysterical reasons, MPU_TX is always
+ * generated (and can't be cleared) when a PCM
+ * playback is going. So let's ignore at the
+ * next loop.
+ */
+ status_mask &= ~VT1724_IRQ_MPU_TX;
+ }
+ if (status & VT1724_IRQ_MPU_RX) {
+ if (ice->midi_input)
+ vt1724_midi_read(ice);
+ else
+ vt1724_midi_clear_rx(ice);
+ }
+ /* ack MPU irq */
+ outb(status, ICEREG1724(ice, IRQSTAT));
+ spin_unlock(&ice->reg_lock);
+ if (status & VT1724_IRQ_MTPCM) {
+ /*
+ * Multi-track PCM
+ * PCM assignment are:
+ * Playback DMA0 (M/C) = playback_pro_substream
+ * Playback DMA1 = playback_con_substream_ds[0]
+ * Playback DMA2 = playback_con_substream_ds[1]
+ * Playback DMA3 = playback_con_substream_ds[2]
+ * Playback DMA4 (SPDIF) = playback_con_substream
+ * Record DMA0 = capture_pro_substream
+ * Record DMA1 = capture_con_substream
+ */
+ unsigned char mtstat = inb(ICEMT1724(ice, IRQ));
+ if (mtstat & VT1724_MULTI_PDMA0) {
+ if (ice->playback_pro_substream)
+ snd_pcm_period_elapsed(ice->playback_pro_substream);
+ }
+ if (mtstat & VT1724_MULTI_RDMA0) {
+ if (ice->capture_pro_substream)
+ snd_pcm_period_elapsed(ice->capture_pro_substream);
+ }
+ if (mtstat & VT1724_MULTI_PDMA1) {
+ if (ice->playback_con_substream_ds[0])
+ snd_pcm_period_elapsed(ice->playback_con_substream_ds[0]);
+ }
+ if (mtstat & VT1724_MULTI_PDMA2) {
+ if (ice->playback_con_substream_ds[1])
+ snd_pcm_period_elapsed(ice->playback_con_substream_ds[1]);
+ }
+ if (mtstat & VT1724_MULTI_PDMA3) {
+ if (ice->playback_con_substream_ds[2])
+ snd_pcm_period_elapsed(ice->playback_con_substream_ds[2]);
+ }
+ if (mtstat & VT1724_MULTI_PDMA4) {
+ if (ice->playback_con_substream)
+ snd_pcm_period_elapsed(ice->playback_con_substream);
+ }
+ if (mtstat & VT1724_MULTI_RDMA1) {
+ if (ice->capture_con_substream)
+ snd_pcm_period_elapsed(ice->capture_con_substream);
+ }
+ /* ack anyway to avoid freeze */
+ outb(mtstat, ICEMT1724(ice, IRQ));
+ /* ought to really handle this properly */
+ if (mtstat & VT1724_MULTI_FIFO_ERR) {
+ unsigned char fstat = inb(ICEMT1724(ice, DMA_FIFO_ERR));
+ outb(fstat, ICEMT1724(ice, DMA_FIFO_ERR));
+ outb(VT1724_MULTI_FIFO_ERR | inb(ICEMT1724(ice, DMA_INT_MASK)), ICEMT1724(ice, DMA_INT_MASK));
+ /* If I don't do this, I get machine lockup due to continual interrupts */
+ }
+
+ }
+ }
+ return IRQ_RETVAL(handled);
+}
+
+/*
+ * PCM code - professional part (multitrack)
+ */
+
+static const unsigned int rates[] = {
+ 8000, 9600, 11025, 12000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 64000, 88200, 96000,
+ 176400, 192000,
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates_96 = {
+ .count = ARRAY_SIZE(rates) - 2, /* up to 96000 */
+ .list = rates,
+ .mask = 0,
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates_48 = {
+ .count = ARRAY_SIZE(rates) - 5, /* up to 48000 */
+ .list = rates,
+ .mask = 0,
+};
+
+static const struct snd_pcm_hw_constraint_list hw_constraints_rates_192 = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0,
+};
+
+struct vt1724_pcm_reg {
+ unsigned int addr; /* ADDR register offset */
+ unsigned int size; /* SIZE register offset */
+ unsigned int count; /* COUNT register offset */
+ unsigned int start; /* start & pause bit */
+};
+
+static int snd_vt1724_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ unsigned char what;
+ unsigned char old;
+ struct snd_pcm_substream *s;
+
+ what = 0;
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (snd_pcm_substream_chip(s) == ice) {
+ const struct vt1724_pcm_reg *reg;
+ reg = s->runtime->private_data;
+ what |= reg->start;
+ snd_pcm_trigger_done(s, substream);
+ }
+ }
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ spin_lock(&ice->reg_lock);
+ old = inb(ICEMT1724(ice, DMA_PAUSE));
+ if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH)
+ old |= what;
+ else
+ old &= ~what;
+ outb(old, ICEMT1724(ice, DMA_PAUSE));
+ spin_unlock(&ice->reg_lock);
+ break;
+
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ spin_lock(&ice->reg_lock);
+ old = inb(ICEMT1724(ice, DMA_CONTROL));
+ if (cmd == SNDRV_PCM_TRIGGER_START)
+ old |= what;
+ else
+ old &= ~what;
+ outb(old, ICEMT1724(ice, DMA_CONTROL));
+ spin_unlock(&ice->reg_lock);
+ break;
+
+ case SNDRV_PCM_TRIGGER_RESUME:
+ /* apps will have to restart stream */
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ */
+
+#define DMA_STARTS (VT1724_RDMA0_START|VT1724_PDMA0_START|VT1724_RDMA1_START|\
+ VT1724_PDMA1_START|VT1724_PDMA2_START|VT1724_PDMA3_START|VT1724_PDMA4_START)
+#define DMA_PAUSES (VT1724_RDMA0_PAUSE|VT1724_PDMA0_PAUSE|VT1724_RDMA1_PAUSE|\
+ VT1724_PDMA1_PAUSE|VT1724_PDMA2_PAUSE|VT1724_PDMA3_PAUSE|VT1724_PDMA4_PAUSE)
+
+static const unsigned int stdclock_rate_list[16] = {
+ 48000, 24000, 12000, 9600, 32000, 16000, 8000, 96000, 44100,
+ 22050, 11025, 88200, 176400, 0, 192000, 64000
+};
+
+static unsigned int stdclock_get_rate(struct snd_ice1712 *ice)
+{
+ return stdclock_rate_list[inb(ICEMT1724(ice, RATE)) & 15];
+}
+
+static void stdclock_set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(stdclock_rate_list); i++) {
+ if (stdclock_rate_list[i] == rate) {
+ outb(i, ICEMT1724(ice, RATE));
+ return;
+ }
+ }
+}
+
+static unsigned char stdclock_set_mclk(struct snd_ice1712 *ice,
+ unsigned int rate)
+{
+ unsigned char val, old;
+ /* check MT02 */
+ if (ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S) {
+ val = old = inb(ICEMT1724(ice, I2S_FORMAT));
+ if (rate > 96000)
+ val |= VT1724_MT_I2S_MCLK_128X; /* 128x MCLK */
+ else
+ val &= ~VT1724_MT_I2S_MCLK_128X; /* 256x MCLK */
+ if (val != old) {
+ outb(val, ICEMT1724(ice, I2S_FORMAT));
+ /* master clock changed */
+ return 1;
+ }
+ }
+ /* no change in master clock */
+ return 0;
+}
+
+static int snd_vt1724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate,
+ int force)
+{
+ unsigned long flags;
+ unsigned char mclk_change;
+ unsigned int i, old_rate;
+ bool call_set_rate = false;
+
+ if (rate > ice->hw_rates->list[ice->hw_rates->count - 1])
+ return -EINVAL;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ if ((inb(ICEMT1724(ice, DMA_CONTROL)) & DMA_STARTS) ||
+ (inb(ICEMT1724(ice, DMA_PAUSE)) & DMA_PAUSES)) {
+ /* running? we cannot change the rate now... */
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ return ((rate == ice->cur_rate) && !force) ? 0 : -EBUSY;
+ }
+ if (!force && is_pro_rate_locked(ice)) {
+ /* comparing required and current rate - makes sense for
+ * internal clock only */
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ return (rate == ice->cur_rate) ? 0 : -EBUSY;
+ }
+
+ if (force || !ice->is_spdif_master(ice)) {
+ /* force means the rate was switched by ucontrol, otherwise
+ * setting clock rate for internal clock mode */
+ old_rate = ice->get_rate(ice);
+ if (force || (old_rate != rate))
+ call_set_rate = true;
+ else if (rate == ice->cur_rate) {
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+ return 0;
+ }
+ }
+
+ ice->cur_rate = rate;
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+
+ if (call_set_rate)
+ ice->set_rate(ice, rate);
+
+ /* setting master clock */
+ mclk_change = ice->set_mclk(ice, rate);
+
+ if (mclk_change && ice->gpio.i2s_mclk_changed)
+ ice->gpio.i2s_mclk_changed(ice);
+ if (ice->gpio.set_pro_rate)
+ ice->gpio.set_pro_rate(ice, rate);
+
+ /* set up codecs */
+ for (i = 0; i < ice->akm_codecs; i++) {
+ if (ice->akm[i].ops.set_rate_val)
+ ice->akm[i].ops.set_rate_val(&ice->akm[i], rate);
+ }
+ if (ice->spdif.ops.setup_rate)
+ ice->spdif.ops.setup_rate(ice, rate);
+
+ return 0;
+}
+
+static int snd_vt1724_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ int i, chs;
+
+ chs = params_channels(hw_params);
+ mutex_lock(&ice->open_mutex);
+ /* mark surround channels */
+ if (substream == ice->playback_pro_substream) {
+ /* PDMA0 can be multi-channel up to 8 */
+ chs = chs / 2 - 1;
+ for (i = 0; i < chs; i++) {
+ if (ice->pcm_reserved[i] &&
+ ice->pcm_reserved[i] != substream) {
+ mutex_unlock(&ice->open_mutex);
+ return -EBUSY;
+ }
+ ice->pcm_reserved[i] = substream;
+ }
+ for (; i < 3; i++) {
+ if (ice->pcm_reserved[i] == substream)
+ ice->pcm_reserved[i] = NULL;
+ }
+ } else {
+ for (i = 0; i < 3; i++) {
+ /* check individual playback stream */
+ if (ice->playback_con_substream_ds[i] == substream) {
+ if (ice->pcm_reserved[i] &&
+ ice->pcm_reserved[i] != substream) {
+ mutex_unlock(&ice->open_mutex);
+ return -EBUSY;
+ }
+ ice->pcm_reserved[i] = substream;
+ break;
+ }
+ }
+ }
+ mutex_unlock(&ice->open_mutex);
+
+ return snd_vt1724_set_pro_rate(ice, params_rate(hw_params), 0);
+}
+
+static int snd_vt1724_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ int i;
+
+ mutex_lock(&ice->open_mutex);
+ /* unmark surround channels */
+ for (i = 0; i < 3; i++)
+ if (ice->pcm_reserved[i] == substream)
+ ice->pcm_reserved[i] = NULL;
+ mutex_unlock(&ice->open_mutex);
+ return 0;
+}
+
+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;
+
+ spin_lock_irq(&ice->reg_lock);
+ val = (8 - substream->runtime->channels) >> 1;
+ outb(val, ICEMT1724(ice, BURST));
+
+ outl(substream->runtime->dma_addr, ICEMT1724(ice, PLAYBACK_ADDR));
+
+ size = (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1;
+ /* outl(size, ICEMT1724(ice, PLAYBACK_SIZE)); */
+ outw(size, ICEMT1724(ice, PLAYBACK_SIZE));
+ outb(size >> 16, ICEMT1724(ice, PLAYBACK_SIZE) + 2);
+ size = (snd_pcm_lib_period_bytes(substream) >> 2) - 1;
+ /* outl(size, ICEMT1724(ice, PLAYBACK_COUNT)); */
+ outw(size, ICEMT1724(ice, PLAYBACK_COUNT));
+ outb(size >> 16, ICEMT1724(ice, PLAYBACK_COUNT) + 2);
+
+ spin_unlock_irq(&ice->reg_lock);
+
+ /*
+ dev_dbg(ice->card->dev, "pro prepare: ch = %d, addr = 0x%x, "
+ "buffer = 0x%x, period = 0x%x\n",
+ substream->runtime->channels,
+ (unsigned int)substream->runtime->dma_addr,
+ snd_pcm_lib_buffer_bytes(substream),
+ snd_pcm_lib_period_bytes(substream));
+ */
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_vt1724_playback_pro_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ size_t ptr;
+
+ if (!(inl(ICEMT1724(ice, DMA_CONTROL)) & VT1724_PDMA0_START))
+ return 0;
+#if 0 /* read PLAYBACK_ADDR */
+ ptr = inl(ICEMT1724(ice, PLAYBACK_ADDR));
+ if (ptr < substream->runtime->dma_addr) {
+ dev_dbg(ice->card->dev, "invalid negative ptr\n");
+ return 0;
+ }
+ ptr -= substream->runtime->dma_addr;
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (ptr >= substream->runtime->buffer_size) {
+ dev_dbg(ice->card->dev, "invalid ptr %d (size=%d)\n",
+ (int)ptr, (int)substream->runtime->period_size);
+ return 0;
+ }
+#else /* read PLAYBACK_SIZE */
+ ptr = inl(ICEMT1724(ice, PLAYBACK_SIZE)) & 0xffffff;
+ ptr = (ptr + 1) << 2;
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (!ptr)
+ ;
+ else if (ptr <= substream->runtime->buffer_size)
+ ptr = substream->runtime->buffer_size - ptr;
+ else {
+ dev_dbg(ice->card->dev, "invalid ptr %d (size=%d)\n",
+ (int)ptr, (int)substream->runtime->buffer_size);
+ ptr = 0;
+ }
+#endif
+ return ptr;
+}
+
+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;
+
+ spin_lock_irq(&ice->reg_lock);
+ outl(substream->runtime->dma_addr, ice->profi_port + reg->addr);
+ outw((snd_pcm_lib_buffer_bytes(substream) >> 2) - 1,
+ ice->profi_port + reg->size);
+ outw((snd_pcm_lib_period_bytes(substream) >> 2) - 1,
+ ice->profi_port + reg->count);
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_vt1724_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ const struct vt1724_pcm_reg *reg = substream->runtime->private_data;
+ size_t ptr;
+
+ if (!(inl(ICEMT1724(ice, DMA_CONTROL)) & reg->start))
+ return 0;
+#if 0 /* use ADDR register */
+ ptr = inl(ice->profi_port + reg->addr);
+ ptr -= substream->runtime->dma_addr;
+ return bytes_to_frames(substream->runtime, ptr);
+#else /* use SIZE register */
+ ptr = inw(ice->profi_port + reg->size);
+ ptr = (ptr + 1) << 2;
+ ptr = bytes_to_frames(substream->runtime, ptr);
+ if (!ptr)
+ ;
+ else if (ptr <= substream->runtime->buffer_size)
+ ptr = substream->runtime->buffer_size - ptr;
+ else {
+ dev_dbg(ice->card->dev, "invalid ptr %d (size=%d)\n",
+ (int)ptr, (int)substream->runtime->buffer_size);
+ ptr = 0;
+ }
+ return ptr;
+#endif
+}
+
+static const struct vt1724_pcm_reg vt1724_pdma0_reg = {
+ .addr = VT1724_MT_PLAYBACK_ADDR,
+ .size = VT1724_MT_PLAYBACK_SIZE,
+ .count = VT1724_MT_PLAYBACK_COUNT,
+ .start = VT1724_PDMA0_START,
+};
+
+static const struct vt1724_pcm_reg vt1724_pdma4_reg = {
+ .addr = VT1724_MT_PDMA4_ADDR,
+ .size = VT1724_MT_PDMA4_SIZE,
+ .count = VT1724_MT_PDMA4_COUNT,
+ .start = VT1724_PDMA4_START,
+};
+
+static const struct vt1724_pcm_reg vt1724_rdma0_reg = {
+ .addr = VT1724_MT_CAPTURE_ADDR,
+ .size = VT1724_MT_CAPTURE_SIZE,
+ .count = VT1724_MT_CAPTURE_COUNT,
+ .start = VT1724_RDMA0_START,
+};
+
+static const struct vt1724_pcm_reg vt1724_rdma1_reg = {
+ .addr = VT1724_MT_RDMA1_ADDR,
+ .size = VT1724_MT_RDMA1_SIZE,
+ .count = VT1724_MT_RDMA1_COUNT,
+ .start = VT1724_RDMA1_START,
+};
+
+#define vt1724_playback_pro_reg vt1724_pdma0_reg
+#define vt1724_playback_spdif_reg vt1724_pdma4_reg
+#define vt1724_capture_pro_reg vt1724_rdma0_reg
+#define vt1724_capture_spdif_reg vt1724_rdma1_reg
+
+static const struct snd_pcm_hardware snd_vt1724_playback_pro = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 2,
+ .channels_max = 8,
+ .buffer_bytes_max = (1UL << 21), /* 19bits dword */
+ .period_bytes_min = 8 * 4 * 2, /* FIXME: constraints needed */
+ .period_bytes_max = (1UL << 21),
+ .periods_min = 2,
+ .periods_max = 1024,
+};
+
+static const struct snd_pcm_hardware snd_vt1724_spdif = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rates = (SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_44100|
+ SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_88200|
+ SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_176400|
+ SNDRV_PCM_RATE_192000),
+ .rate_min = 32000,
+ .rate_max = 192000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (1UL << 18), /* 16bits dword */
+ .period_bytes_min = 2 * 4 * 2,
+ .period_bytes_max = (1UL << 18),
+ .periods_min = 2,
+ .periods_max = 1024,
+};
+
+static const struct snd_pcm_hardware snd_vt1724_2ch_stereo = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S32_LE,
+ .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_192000,
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (1UL << 18), /* 16bits dword */
+ .period_bytes_min = 2 * 4 * 2,
+ .period_bytes_max = (1UL << 18),
+ .periods_min = 2,
+ .periods_max = 1024,
+};
+
+/*
+ * set rate constraints
+ */
+static void set_std_hw_rates(struct snd_ice1712 *ice)
+{
+ if (ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S) {
+ /* I2S */
+ /* VT1720 doesn't support more than 96kHz */
+ if ((ice->eeprom.data[ICE_EEP2_I2S] & 0x08) && !ice->vt1720)
+ ice->hw_rates = &hw_constraints_rates_192;
+ else
+ ice->hw_rates = &hw_constraints_rates_96;
+ } else {
+ /* ACLINK */
+ ice->hw_rates = &hw_constraints_rates_48;
+ }
+}
+
+static int set_rate_constraints(struct snd_ice1712 *ice,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->hw.rate_min = ice->hw_rates->list[0];
+ runtime->hw.rate_max = ice->hw_rates->list[ice->hw_rates->count - 1];
+ runtime->hw.rates = SNDRV_PCM_RATE_KNOT;
+ return snd_pcm_hw_constraint_list(runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ ice->hw_rates);
+}
+
+/* if the card has the internal rate locked (is_pro_locked), limit runtime
+ hw rates to the current internal rate only.
+*/
+static void constrain_rate_if_locked(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int rate;
+ if (is_pro_rate_locked(ice)) {
+ rate = ice->get_rate(ice);
+ if (rate >= runtime->hw.rate_min
+ && rate <= runtime->hw.rate_max) {
+ runtime->hw.rate_min = rate;
+ runtime->hw.rate_max = rate;
+ }
+ }
+}
+
+
+/* multi-channel playback needs alignment 8x32bit regardless of the channels
+ * actually used
+ */
+#define VT1724_BUFFER_ALIGN 0x20
+
+static int snd_vt1724_playback_pro_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ int chs, num_indeps;
+
+ runtime->private_data = (void *)&vt1724_playback_pro_reg;
+ ice->playback_pro_substream = substream;
+ runtime->hw = snd_vt1724_playback_pro;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ set_rate_constraints(ice, substream);
+ mutex_lock(&ice->open_mutex);
+ /* calculate the currently available channels */
+ num_indeps = ice->num_total_dacs / 2 - 1;
+ for (chs = 0; chs < num_indeps; chs++) {
+ if (ice->pcm_reserved[chs])
+ break;
+ }
+ chs = (chs + 1) * 2;
+ runtime->hw.channels_max = chs;
+ if (chs > 2) /* channels must be even */
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+ mutex_unlock(&ice->open_mutex);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ VT1724_BUFFER_ALIGN);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ VT1724_BUFFER_ALIGN);
+ constrain_rate_if_locked(substream);
+ if (ice->pro_open)
+ ice->pro_open(ice, substream);
+ return 0;
+}
+
+static int snd_vt1724_capture_pro_open(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->private_data = (void *)&vt1724_capture_pro_reg;
+ ice->capture_pro_substream = substream;
+ runtime->hw = snd_vt1724_2ch_stereo;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ set_rate_constraints(ice, substream);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ VT1724_BUFFER_ALIGN);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ VT1724_BUFFER_ALIGN);
+ constrain_rate_if_locked(substream);
+ if (ice->pro_open)
+ ice->pro_open(ice, substream);
+ return 0;
+}
+
+static int snd_vt1724_playback_pro_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ if (PRO_RATE_RESET)
+ snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+ ice->playback_pro_substream = NULL;
+
+ return 0;
+}
+
+static int snd_vt1724_capture_pro_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ if (PRO_RATE_RESET)
+ snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+ ice->capture_pro_substream = NULL;
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_vt1724_playback_pro_ops = {
+ .open = snd_vt1724_playback_pro_open,
+ .close = snd_vt1724_playback_pro_close,
+ .hw_params = snd_vt1724_pcm_hw_params,
+ .hw_free = snd_vt1724_pcm_hw_free,
+ .prepare = snd_vt1724_playback_pro_prepare,
+ .trigger = snd_vt1724_pcm_trigger,
+ .pointer = snd_vt1724_playback_pro_pointer,
+};
+
+static const struct snd_pcm_ops snd_vt1724_capture_pro_ops = {
+ .open = snd_vt1724_capture_pro_open,
+ .close = snd_vt1724_capture_pro_close,
+ .hw_params = snd_vt1724_pcm_hw_params,
+ .hw_free = snd_vt1724_pcm_hw_free,
+ .prepare = snd_vt1724_pcm_prepare,
+ .trigger = snd_vt1724_pcm_trigger,
+ .pointer = snd_vt1724_pcm_pointer,
+};
+
+static int snd_vt1724_pcm_profi(struct snd_ice1712 *ice, int device)
+{
+ struct snd_pcm *pcm;
+ int capt, err;
+
+ if ((ice->eeprom.data[ICE_EEP2_SYSCONF] & VT1724_CFG_ADC_MASK) ==
+ VT1724_CFG_ADC_NONE)
+ capt = 0;
+ else
+ capt = 1;
+ err = snd_pcm_new(ice->card, "ICE1724", device, 1, capt, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_vt1724_playback_pro_ops);
+ if (capt)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_vt1724_capture_pro_ops);
+
+ pcm->private_data = ice;
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "ICE1724");
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ &ice->pci->dev, 256*1024, 256*1024);
+
+ ice->pcm_pro = pcm;
+
+ return 0;
+}
+
+
+/*
+ * SPDIF PCM
+ */
+
+/* update spdif control bits; call with reg_lock */
+static void update_spdif_bits(struct snd_ice1712 *ice, unsigned int val)
+{
+ unsigned char cbit, disabled;
+
+ cbit = inb(ICEREG1724(ice, SPDIF_CFG));
+ disabled = cbit & ~VT1724_CFG_SPDIF_OUT_EN;
+ if (cbit != disabled)
+ outb(disabled, ICEREG1724(ice, SPDIF_CFG));
+ outw(val, ICEMT1724(ice, SPDIF_CTRL));
+ if (cbit != disabled)
+ outb(cbit, ICEREG1724(ice, SPDIF_CFG));
+ outw(val, ICEMT1724(ice, SPDIF_CTRL));
+}
+
+/* update SPDIF control bits according to the given rate */
+static void update_spdif_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+ unsigned int val, nval;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ice->reg_lock, flags);
+ nval = val = inw(ICEMT1724(ice, SPDIF_CTRL));
+ nval &= ~(7 << 12);
+ switch (rate) {
+ case 44100: break;
+ case 48000: nval |= 2 << 12; break;
+ case 32000: nval |= 3 << 12; break;
+ case 88200: nval |= 4 << 12; break;
+ case 96000: nval |= 5 << 12; break;
+ case 192000: nval |= 6 << 12; break;
+ case 176400: nval |= 7 << 12; break;
+ }
+ if (val != nval)
+ update_spdif_bits(ice, nval);
+ spin_unlock_irqrestore(&ice->reg_lock, flags);
+}
+
+static int snd_vt1724_playback_spdif_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ if (!ice->force_pdma4)
+ update_spdif_rate(ice, substream->runtime->rate);
+ return snd_vt1724_pcm_prepare(substream);
+}
+
+static int snd_vt1724_playback_spdif_open(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->private_data = (void *)&vt1724_playback_spdif_reg;
+ ice->playback_con_substream = substream;
+ if (ice->force_pdma4) {
+ runtime->hw = snd_vt1724_2ch_stereo;
+ set_rate_constraints(ice, substream);
+ } else
+ runtime->hw = snd_vt1724_spdif;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ VT1724_BUFFER_ALIGN);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ VT1724_BUFFER_ALIGN);
+ constrain_rate_if_locked(substream);
+ if (ice->spdif.ops.open)
+ ice->spdif.ops.open(ice, substream);
+ return 0;
+}
+
+static int snd_vt1724_playback_spdif_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ if (PRO_RATE_RESET)
+ snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+ ice->playback_con_substream = NULL;
+ if (ice->spdif.ops.close)
+ ice->spdif.ops.close(ice, substream);
+
+ return 0;
+}
+
+static int snd_vt1724_capture_spdif_open(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ runtime->private_data = (void *)&vt1724_capture_spdif_reg;
+ ice->capture_con_substream = substream;
+ if (ice->force_rdma1) {
+ runtime->hw = snd_vt1724_2ch_stereo;
+ set_rate_constraints(ice, substream);
+ } else
+ runtime->hw = snd_vt1724_spdif;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+ VT1724_BUFFER_ALIGN);
+ snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ VT1724_BUFFER_ALIGN);
+ constrain_rate_if_locked(substream);
+ if (ice->spdif.ops.open)
+ ice->spdif.ops.open(ice, substream);
+ return 0;
+}
+
+static int snd_vt1724_capture_spdif_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ if (PRO_RATE_RESET)
+ snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+ ice->capture_con_substream = NULL;
+ if (ice->spdif.ops.close)
+ ice->spdif.ops.close(ice, substream);
+
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_vt1724_playback_spdif_ops = {
+ .open = snd_vt1724_playback_spdif_open,
+ .close = snd_vt1724_playback_spdif_close,
+ .hw_params = snd_vt1724_pcm_hw_params,
+ .hw_free = snd_vt1724_pcm_hw_free,
+ .prepare = snd_vt1724_playback_spdif_prepare,
+ .trigger = snd_vt1724_pcm_trigger,
+ .pointer = snd_vt1724_pcm_pointer,
+};
+
+static const struct snd_pcm_ops snd_vt1724_capture_spdif_ops = {
+ .open = snd_vt1724_capture_spdif_open,
+ .close = snd_vt1724_capture_spdif_close,
+ .hw_params = snd_vt1724_pcm_hw_params,
+ .hw_free = snd_vt1724_pcm_hw_free,
+ .prepare = snd_vt1724_pcm_prepare,
+ .trigger = snd_vt1724_pcm_trigger,
+ .pointer = snd_vt1724_pcm_pointer,
+};
+
+
+static int snd_vt1724_pcm_spdif(struct snd_ice1712 *ice, int device)
+{
+ char *name;
+ struct snd_pcm *pcm;
+ int play, capt;
+ int err;
+
+ if (ice->force_pdma4 ||
+ (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_OUT_INT)) {
+ play = 1;
+ ice->has_spdif = 1;
+ } else
+ play = 0;
+ if (ice->force_rdma1 ||
+ (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_IN)) {
+ capt = 1;
+ ice->has_spdif = 1;
+ } else
+ capt = 0;
+ if (!play && !capt)
+ return 0; /* no spdif device */
+
+ if (ice->force_pdma4 || ice->force_rdma1)
+ name = "ICE1724 Secondary";
+ else
+ name = "ICE1724 IEC958";
+ err = snd_pcm_new(ice->card, name, device, play, capt, &pcm);
+ if (err < 0)
+ return err;
+
+ if (play)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_vt1724_playback_spdif_ops);
+ if (capt)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_vt1724_capture_spdif_ops);
+
+ pcm->private_data = ice;
+ pcm->info_flags = 0;
+ strcpy(pcm->name, name);
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ &ice->pci->dev, 256*1024, 256*1024);
+
+ ice->pcm = pcm;
+
+ return 0;
+}
+
+
+/*
+ * independent surround PCMs
+ */
+
+static const struct vt1724_pcm_reg vt1724_playback_dma_regs[3] = {
+ {
+ .addr = VT1724_MT_PDMA1_ADDR,
+ .size = VT1724_MT_PDMA1_SIZE,
+ .count = VT1724_MT_PDMA1_COUNT,
+ .start = VT1724_PDMA1_START,
+ },
+ {
+ .addr = VT1724_MT_PDMA2_ADDR,
+ .size = VT1724_MT_PDMA2_SIZE,
+ .count = VT1724_MT_PDMA2_COUNT,
+ .start = VT1724_PDMA2_START,
+ },
+ {
+ .addr = VT1724_MT_PDMA3_ADDR,
+ .size = VT1724_MT_PDMA3_SIZE,
+ .count = VT1724_MT_PDMA3_COUNT,
+ .start = VT1724_PDMA3_START,
+ },
+};
+
+static int snd_vt1724_playback_indep_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ unsigned char val;
+
+ spin_lock_irq(&ice->reg_lock);
+ val = 3 - substream->number;
+ if (inb(ICEMT1724(ice, BURST)) < val)
+ outb(val, ICEMT1724(ice, BURST));
+ spin_unlock_irq(&ice->reg_lock);
+ return snd_vt1724_pcm_prepare(substream);
+}
+
+static int snd_vt1724_playback_indep_open(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ mutex_lock(&ice->open_mutex);
+ /* already used by PDMA0? */
+ if (ice->pcm_reserved[substream->number]) {
+ mutex_unlock(&ice->open_mutex);
+ return -EBUSY; /* FIXME: should handle blocking mode properly */
+ }
+ mutex_unlock(&ice->open_mutex);
+ runtime->private_data = (void *)&vt1724_playback_dma_regs[substream->number];
+ ice->playback_con_substream_ds[substream->number] = substream;
+ runtime->hw = snd_vt1724_2ch_stereo;
+ snd_pcm_set_sync(substream);
+ snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ set_rate_constraints(ice, substream);
+ return 0;
+}
+
+static int snd_vt1724_playback_indep_close(struct snd_pcm_substream *substream)
+{
+ struct snd_ice1712 *ice = snd_pcm_substream_chip(substream);
+
+ if (PRO_RATE_RESET)
+ snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 0);
+ ice->playback_con_substream_ds[substream->number] = NULL;
+ ice->pcm_reserved[substream->number] = NULL;
+
+ return 0;
+}
+
+static const struct snd_pcm_ops snd_vt1724_playback_indep_ops = {
+ .open = snd_vt1724_playback_indep_open,
+ .close = snd_vt1724_playback_indep_close,
+ .hw_params = snd_vt1724_pcm_hw_params,
+ .hw_free = snd_vt1724_pcm_hw_free,
+ .prepare = snd_vt1724_playback_indep_prepare,
+ .trigger = snd_vt1724_pcm_trigger,
+ .pointer = snd_vt1724_pcm_pointer,
+};
+
+
+static int snd_vt1724_pcm_indep(struct snd_ice1712 *ice, int device)
+{
+ struct snd_pcm *pcm;
+ int play;
+ int err;
+
+ play = ice->num_total_dacs / 2 - 1;
+ if (play <= 0)
+ return 0;
+
+ err = snd_pcm_new(ice->card, "ICE1724 Surrounds", device, play, 0, &pcm);
+ if (err < 0)
+ return err;
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_vt1724_playback_indep_ops);
+
+ pcm->private_data = ice;
+ pcm->info_flags = 0;
+ strcpy(pcm->name, "ICE1724 Surround PCM");
+
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
+ &ice->pci->dev, 256*1024, 256*1024);
+
+ ice->pcm_ds = pcm;
+
+ return 0;
+}
+
+
+/*
+ * Mixer section
+ */
+
+static int snd_vt1724_ac97_mixer(struct snd_ice1712 *ice)
+{
+ int err;
+
+ if (!(ice->eeprom.data[ICE_EEP2_ACLINK] & VT1724_CFG_PRO_I2S)) {
+ struct snd_ac97_bus *pbus;
+ struct snd_ac97_template ac97;
+ static const struct snd_ac97_bus_ops ops = {
+ .write = snd_vt1724_ac97_write,
+ .read = snd_vt1724_ac97_read,
+ };
+
+ /* cold reset */
+ outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
+ mdelay(5); /* FIXME */
+ outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
+
+ err = snd_ac97_bus(ice->card, 0, &ops, NULL, &pbus);
+ if (err < 0)
+ return err;
+ memset(&ac97, 0, sizeof(ac97));
+ ac97.private_data = ice;
+ err = snd_ac97_mixer(pbus, &ac97, &ice->ac97);
+ if (err < 0)
+ dev_warn(ice->card->dev,
+ "cannot initialize pro ac97, skipped\n");
+ else
+ return 0;
+ }
+ /* I2S mixer only */
+ strcat(ice->card->mixername, "ICE1724 - multitrack");
+ return 0;
+}
+
+/*
+ *
+ */
+
+static inline unsigned int eeprom_triple(struct snd_ice1712 *ice, int idx)
+{
+ return (unsigned int)ice->eeprom.data[idx] | \
+ ((unsigned int)ice->eeprom.data[idx + 1] << 8) | \
+ ((unsigned int)ice->eeprom.data[idx + 2] << 16);
+}
+
+static void snd_vt1724_proc_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ unsigned int idx;
+
+ snd_iprintf(buffer, "%s\n\n", ice->card->longname);
+ snd_iprintf(buffer, "EEPROM:\n");
+
+ snd_iprintf(buffer, " Subvendor : 0x%x\n", ice->eeprom.subvendor);
+ snd_iprintf(buffer, " Size : %i bytes\n", ice->eeprom.size);
+ snd_iprintf(buffer, " Version : %i\n", ice->eeprom.version);
+ snd_iprintf(buffer, " System Config : 0x%x\n",
+ ice->eeprom.data[ICE_EEP2_SYSCONF]);
+ snd_iprintf(buffer, " ACLink : 0x%x\n",
+ ice->eeprom.data[ICE_EEP2_ACLINK]);
+ snd_iprintf(buffer, " I2S : 0x%x\n",
+ ice->eeprom.data[ICE_EEP2_I2S]);
+ snd_iprintf(buffer, " S/PDIF : 0x%x\n",
+ ice->eeprom.data[ICE_EEP2_SPDIF]);
+ snd_iprintf(buffer, " GPIO direction : 0x%x\n",
+ ice->eeprom.gpiodir);
+ snd_iprintf(buffer, " GPIO mask : 0x%x\n",
+ ice->eeprom.gpiomask);
+ snd_iprintf(buffer, " GPIO state : 0x%x\n",
+ ice->eeprom.gpiostate);
+ for (idx = 0x12; idx < ice->eeprom.size; idx++)
+ snd_iprintf(buffer, " Extra #%02i : 0x%x\n",
+ idx, ice->eeprom.data[idx]);
+
+ snd_iprintf(buffer, "\nRegisters:\n");
+
+ snd_iprintf(buffer, " PSDOUT03 : 0x%08x\n",
+ (unsigned)inl(ICEMT1724(ice, ROUTE_PLAYBACK)));
+ for (idx = 0x0; idx < 0x20 ; idx++)
+ snd_iprintf(buffer, " CCS%02x : 0x%02x\n",
+ idx, inb(ice->port+idx));
+ for (idx = 0x0; idx < 0x30 ; idx++)
+ snd_iprintf(buffer, " MT%02x : 0x%02x\n",
+ idx, inb(ice->profi_port+idx));
+}
+
+static void snd_vt1724_proc_init(struct snd_ice1712 *ice)
+{
+ snd_card_ro_proc_new(ice->card, "ice1724", ice, snd_vt1724_proc_read);
+}
+
+/*
+ *
+ */
+
+static int snd_vt1724_eeprom_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+ uinfo->count = sizeof(struct snd_ice1712_eeprom);
+ return 0;
+}
+
+static int snd_vt1724_eeprom_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ memcpy(ucontrol->value.bytes.data, &ice->eeprom, sizeof(ice->eeprom));
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_eeprom = {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "ICE1724 EEPROM",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = snd_vt1724_eeprom_info,
+ .get = snd_vt1724_eeprom_get
+};
+
+/*
+ */
+static int snd_vt1724_spdif_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static unsigned int encode_spdif_bits(struct snd_aes_iec958 *diga)
+{
+ unsigned int val, rbits;
+
+ val = diga->status[0] & 0x03; /* professional, non-audio */
+ if (val & 0x01) {
+ /* professional */
+ if ((diga->status[0] & IEC958_AES0_PRO_EMPHASIS) ==
+ IEC958_AES0_PRO_EMPHASIS_5015)
+ val |= 1U << 3;
+ rbits = (diga->status[4] >> 3) & 0x0f;
+ if (rbits) {
+ switch (rbits) {
+ case 2: val |= 5 << 12; break; /* 96k */
+ case 3: val |= 6 << 12; break; /* 192k */
+ case 10: val |= 4 << 12; break; /* 88.2k */
+ case 11: val |= 7 << 12; break; /* 176.4k */
+ }
+ } else {
+ switch (diga->status[0] & IEC958_AES0_PRO_FS) {
+ case IEC958_AES0_PRO_FS_44100:
+ break;
+ case IEC958_AES0_PRO_FS_32000:
+ val |= 3U << 12;
+ break;
+ default:
+ val |= 2U << 12;
+ break;
+ }
+ }
+ } else {
+ /* consumer */
+ val |= diga->status[1] & 0x04; /* copyright */
+ if ((diga->status[0] & IEC958_AES0_CON_EMPHASIS) ==
+ IEC958_AES0_CON_EMPHASIS_5015)
+ val |= 1U << 3;
+ val |= (unsigned int)(diga->status[1] & 0x3f) << 4; /* category */
+ val |= (unsigned int)(diga->status[3] & IEC958_AES3_CON_FS) << 12; /* fs */
+ }
+ return val;
+}
+
+static void decode_spdif_bits(struct snd_aes_iec958 *diga, unsigned int val)
+{
+ memset(diga->status, 0, sizeof(diga->status));
+ diga->status[0] = val & 0x03; /* professional, non-audio */
+ if (val & 0x01) {
+ /* professional */
+ if (val & (1U << 3))
+ diga->status[0] |= IEC958_AES0_PRO_EMPHASIS_5015;
+ switch ((val >> 12) & 0x7) {
+ case 0:
+ break;
+ case 2:
+ diga->status[0] |= IEC958_AES0_PRO_FS_32000;
+ break;
+ default:
+ diga->status[0] |= IEC958_AES0_PRO_FS_48000;
+ break;
+ }
+ } else {
+ /* consumer */
+ diga->status[0] |= val & (1U << 2); /* copyright */
+ if (val & (1U << 3))
+ diga->status[0] |= IEC958_AES0_CON_EMPHASIS_5015;
+ diga->status[1] |= (val >> 4) & 0x3f; /* category */
+ diga->status[3] |= (val >> 12) & 0x07; /* fs */
+ }
+}
+
+static int snd_vt1724_spdif_default_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ val = inw(ICEMT1724(ice, SPDIF_CTRL));
+ decode_spdif_bits(&ucontrol->value.iec958, val);
+ return 0;
+}
+
+static int snd_vt1724_spdif_default_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val, old;
+
+ val = encode_spdif_bits(&ucontrol->value.iec958);
+ spin_lock_irq(&ice->reg_lock);
+ old = inw(ICEMT1724(ice, SPDIF_CTRL));
+ if (val != old)
+ update_spdif_bits(ice, val);
+ spin_unlock_irq(&ice->reg_lock);
+ return val != old;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_spdif_default =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+ .info = snd_vt1724_spdif_info,
+ .get = snd_vt1724_spdif_default_get,
+ .put = snd_vt1724_spdif_default_put
+};
+
+static int snd_vt1724_spdif_maskc_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+ IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_CON_NOT_COPYRIGHT |
+ IEC958_AES0_CON_EMPHASIS;
+ ucontrol->value.iec958.status[1] = IEC958_AES1_CON_ORIGINAL |
+ IEC958_AES1_CON_CATEGORY;
+ ucontrol->value.iec958.status[3] = IEC958_AES3_CON_FS;
+ return 0;
+}
+
+static int snd_vt1724_spdif_maskp_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
+ IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_PRO_FS |
+ IEC958_AES0_PRO_EMPHASIS;
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_spdif_maskc =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
+ .info = snd_vt1724_spdif_info,
+ .get = snd_vt1724_spdif_maskc_get,
+};
+
+static const struct snd_kcontrol_new snd_vt1724_spdif_maskp =
+{
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PRO_MASK),
+ .info = snd_vt1724_spdif_info,
+ .get = snd_vt1724_spdif_maskp_get,
+};
+
+#define snd_vt1724_spdif_sw_info snd_ctl_boolean_mono_info
+
+static int snd_vt1724_spdif_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = inb(ICEREG1724(ice, SPDIF_CFG)) &
+ VT1724_CFG_SPDIF_OUT_EN ? 1 : 0;
+ return 0;
+}
+
+static int snd_vt1724_spdif_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char old, val;
+
+ spin_lock_irq(&ice->reg_lock);
+ old = val = inb(ICEREG1724(ice, SPDIF_CFG));
+ val &= ~VT1724_CFG_SPDIF_OUT_EN;
+ if (ucontrol->value.integer.value[0])
+ val |= VT1724_CFG_SPDIF_OUT_EN;
+ if (old != val)
+ outb(val, ICEREG1724(ice, SPDIF_CFG));
+ spin_unlock_irq(&ice->reg_lock);
+ return old != val;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_spdif_switch =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ /* FIXME: the following conflict with IEC958 Playback Route */
+ /* .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), */
+ .name = SNDRV_CTL_NAME_IEC958("Output ", NONE, SWITCH),
+ .info = snd_vt1724_spdif_sw_info,
+ .get = snd_vt1724_spdif_sw_get,
+ .put = snd_vt1724_spdif_sw_put
+};
+
+
+#if 0 /* NOT USED YET */
+/*
+ * GPIO access from extern
+ */
+
+#define snd_vt1724_gpio_info snd_ctl_boolean_mono_info
+
+int snd_vt1724_gpio_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value & (1<<24)) ? 1 : 0;
+
+ snd_ice1712_save_gpio_status(ice);
+ ucontrol->value.integer.value[0] =
+ (snd_ice1712_gpio_read(ice) & (1 << shift) ? 1 : 0) ^ invert;
+ snd_ice1712_restore_gpio_status(ice);
+ return 0;
+}
+
+int snd_ice1712_gpio_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int shift = kcontrol->private_value & 0xff;
+ int invert = (kcontrol->private_value & (1<<24)) ? mask : 0;
+ unsigned int val, nval;
+
+ if (kcontrol->private_value & (1 << 31))
+ return -EPERM;
+ nval = (ucontrol->value.integer.value[0] ? (1 << shift) : 0) ^ invert;
+ snd_ice1712_save_gpio_status(ice);
+ val = snd_ice1712_gpio_read(ice);
+ nval |= val & ~(1 << shift);
+ if (val != nval)
+ snd_ice1712_gpio_write(ice, nval);
+ snd_ice1712_restore_gpio_status(ice);
+ return val != nval;
+}
+#endif /* NOT USED YET */
+
+/*
+ * rate
+ */
+static int snd_vt1724_pro_internal_clock_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int hw_rates_count = ice->hw_rates->count;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+
+ /* internal clocks */
+ uinfo->value.enumerated.items = hw_rates_count;
+ /* external clocks */
+ if (ice->force_rdma1 ||
+ (ice->eeprom.data[ICE_EEP2_SPDIF] & VT1724_CFG_SPDIF_IN))
+ uinfo->value.enumerated.items += ice->ext_clock_count;
+ /* upper limit - keep at top */
+ if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+ uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+ if (uinfo->value.enumerated.item >= hw_rates_count)
+ /* ext_clock items */
+ strcpy(uinfo->value.enumerated.name,
+ ice->ext_clock_names[
+ uinfo->value.enumerated.item - hw_rates_count]);
+ else
+ /* int clock items */
+ sprintf(uinfo->value.enumerated.name, "%d",
+ ice->hw_rates->list[uinfo->value.enumerated.item]);
+ return 0;
+}
+
+static int snd_vt1724_pro_internal_clock_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int i, rate;
+
+ spin_lock_irq(&ice->reg_lock);
+ if (ice->is_spdif_master(ice)) {
+ ucontrol->value.enumerated.item[0] = ice->hw_rates->count +
+ ice->get_spdif_master_type(ice);
+ } else {
+ rate = ice->get_rate(ice);
+ ucontrol->value.enumerated.item[0] = 0;
+ for (i = 0; i < ice->hw_rates->count; i++) {
+ if (ice->hw_rates->list[i] == rate) {
+ ucontrol->value.enumerated.item[0] = i;
+ break;
+ }
+ }
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static int stdclock_get_spdif_master_type(struct snd_ice1712 *ice)
+{
+ /* standard external clock - only single type - SPDIF IN */
+ return 0;
+}
+
+/* setting clock to external - SPDIF */
+static int stdclock_set_spdif_clock(struct snd_ice1712 *ice, int type)
+{
+ unsigned char oval;
+ unsigned char i2s_oval;
+ oval = inb(ICEMT1724(ice, RATE));
+ outb(oval | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+ /* setting 256fs */
+ i2s_oval = inb(ICEMT1724(ice, I2S_FORMAT));
+ outb(i2s_oval & ~VT1724_MT_I2S_MCLK_128X, ICEMT1724(ice, I2S_FORMAT));
+ return 0;
+}
+
+
+static int snd_vt1724_pro_internal_clock_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int old_rate, new_rate;
+ unsigned int item = ucontrol->value.enumerated.item[0];
+ unsigned int first_ext_clock = ice->hw_rates->count;
+
+ if (item > first_ext_clock + ice->ext_clock_count - 1)
+ return -EINVAL;
+
+ /* if rate = 0 => external clock */
+ spin_lock_irq(&ice->reg_lock);
+ if (ice->is_spdif_master(ice))
+ old_rate = 0;
+ else
+ old_rate = ice->get_rate(ice);
+ if (item >= first_ext_clock) {
+ /* switching to external clock */
+ ice->set_spdif_clock(ice, item - first_ext_clock);
+ new_rate = 0;
+ } else {
+ /* internal on-card clock */
+ new_rate = ice->hw_rates->list[item];
+ ice->pro_rate_default = new_rate;
+ spin_unlock_irq(&ice->reg_lock);
+ snd_vt1724_set_pro_rate(ice, ice->pro_rate_default, 1);
+ spin_lock_irq(&ice->reg_lock);
+ }
+ spin_unlock_irq(&ice->reg_lock);
+
+ /* the first switch to the ext. clock mode? */
+ if (old_rate != new_rate && !new_rate) {
+ /* notify akm chips as well */
+ unsigned int i;
+ if (ice->gpio.set_pro_rate)
+ ice->gpio.set_pro_rate(ice, 0);
+ for (i = 0; i < ice->akm_codecs; i++) {
+ if (ice->akm[i].ops.set_rate_val)
+ ice->akm[i].ops.set_rate_val(&ice->akm[i], 0);
+ }
+ }
+ return old_rate != new_rate;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_pro_internal_clock = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Internal Clock",
+ .info = snd_vt1724_pro_internal_clock_info,
+ .get = snd_vt1724_pro_internal_clock_get,
+ .put = snd_vt1724_pro_internal_clock_put
+};
+
+#define snd_vt1724_pro_rate_locking_info snd_ctl_boolean_mono_info
+
+static int snd_vt1724_pro_rate_locking_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = PRO_RATE_LOCKED;
+ return 0;
+}
+
+static int snd_vt1724_pro_rate_locking_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int change = 0, nval;
+
+ nval = ucontrol->value.integer.value[0] ? 1 : 0;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_LOCKED != nval;
+ PRO_RATE_LOCKED = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_pro_rate_locking = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Rate Locking",
+ .info = snd_vt1724_pro_rate_locking_info,
+ .get = snd_vt1724_pro_rate_locking_get,
+ .put = snd_vt1724_pro_rate_locking_put
+};
+
+#define snd_vt1724_pro_rate_reset_info snd_ctl_boolean_mono_info
+
+static int snd_vt1724_pro_rate_reset_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = PRO_RATE_RESET ? 1 : 0;
+ return 0;
+}
+
+static int snd_vt1724_pro_rate_reset_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int change = 0, nval;
+
+ nval = ucontrol->value.integer.value[0] ? 1 : 0;
+ spin_lock_irq(&ice->reg_lock);
+ change = PRO_RATE_RESET != nval;
+ PRO_RATE_RESET = nval;
+ spin_unlock_irq(&ice->reg_lock);
+ return change;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_pro_rate_reset = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Multi Track Rate Reset",
+ .info = snd_vt1724_pro_rate_reset_info,
+ .get = snd_vt1724_pro_rate_reset_get,
+ .put = snd_vt1724_pro_rate_reset_put
+};
+
+
+/*
+ * routing
+ */
+static int snd_vt1724_pro_route_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "PCM Out", /* 0 */
+ "H/W In 0", "H/W In 1", /* 1-2 */
+ "IEC958 In L", "IEC958 In R", /* 3-4 */
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 5, texts);
+}
+
+static inline int analog_route_shift(int idx)
+{
+ return (idx % 2) * 12 + ((idx / 2) * 3) + 8;
+}
+
+static inline int digital_route_shift(int idx)
+{
+ return idx * 3;
+}
+
+int snd_ice1724_get_route_val(struct snd_ice1712 *ice, int shift)
+{
+ unsigned long val;
+ unsigned char eitem;
+ static const unsigned char xlate[8] = {
+ 0, 255, 1, 2, 255, 255, 3, 4,
+ };
+
+ val = inl(ICEMT1724(ice, ROUTE_PLAYBACK));
+ val >>= shift;
+ val &= 7; /* we now have 3 bits per output */
+ eitem = xlate[val];
+ if (eitem == 255) {
+ snd_BUG();
+ return 0;
+ }
+ return eitem;
+}
+
+int snd_ice1724_put_route_val(struct snd_ice1712 *ice, unsigned int val,
+ int shift)
+{
+ unsigned int old_val, nval;
+ int change;
+ static const unsigned char xroute[8] = {
+ 0, /* PCM */
+ 2, /* PSDIN0 Left */
+ 3, /* PSDIN0 Right */
+ 6, /* SPDIN Left */
+ 7, /* SPDIN Right */
+ };
+
+ nval = xroute[val % 5];
+ val = old_val = inl(ICEMT1724(ice, ROUTE_PLAYBACK));
+ val &= ~(0x07 << shift);
+ val |= nval << shift;
+ change = val != old_val;
+ if (change)
+ outl(val, ICEMT1724(ice, ROUTE_PLAYBACK));
+ return change;
+}
+
+static int snd_vt1724_pro_route_analog_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ ucontrol->value.enumerated.item[0] =
+ snd_ice1724_get_route_val(ice, analog_route_shift(idx));
+ return 0;
+}
+
+static int snd_vt1724_pro_route_analog_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ return snd_ice1724_put_route_val(ice,
+ ucontrol->value.enumerated.item[0],
+ analog_route_shift(idx));
+}
+
+static int snd_vt1724_pro_route_spdif_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ ucontrol->value.enumerated.item[0] =
+ snd_ice1724_get_route_val(ice, digital_route_shift(idx));
+ return 0;
+}
+
+static int snd_vt1724_pro_route_spdif_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ return snd_ice1724_put_route_val(ice,
+ ucontrol->value.enumerated.item[0],
+ digital_route_shift(idx));
+}
+
+static const struct snd_kcontrol_new snd_vt1724_mixer_pro_analog_route =
+{
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "H/W Playback Route",
+ .info = snd_vt1724_pro_route_info,
+ .get = snd_vt1724_pro_route_analog_get,
+ .put = snd_vt1724_pro_route_analog_put,
+};
+
+static const struct snd_kcontrol_new snd_vt1724_mixer_pro_spdif_route = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, NONE) "Route",
+ .info = snd_vt1724_pro_route_info,
+ .get = snd_vt1724_pro_route_spdif_get,
+ .put = snd_vt1724_pro_route_spdif_put,
+ .count = 2,
+};
+
+
+static int snd_vt1724_pro_peak_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 22; /* FIXME: for compatibility with ice1712... */
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 255;
+ return 0;
+}
+
+static int snd_vt1724_pro_peak_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx;
+
+ spin_lock_irq(&ice->reg_lock);
+ for (idx = 0; idx < 22; idx++) {
+ outb(idx, ICEMT1724(ice, MONITOR_PEAKINDEX));
+ ucontrol->value.integer.value[idx] =
+ inb(ICEMT1724(ice, MONITOR_PEAKDATA));
+ }
+ spin_unlock_irq(&ice->reg_lock);
+ return 0;
+}
+
+static const struct snd_kcontrol_new snd_vt1724_mixer_pro_peak = {
+ .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+ .name = "Multi Track Peak",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_vt1724_pro_peak_info,
+ .get = snd_vt1724_pro_peak_get
+};
+
+/*
+ ooAoo cards with no controls
+*/
+static const unsigned char ooaoo_sq210_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x4c, /* 49MHz crystal, no mpu401, no ADC,
+ 1xDACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0x78, /* no volume, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc1, /* out-en, out-int, out-ext */
+ [ICE_EEP2_GPIO_DIR] = 0x00, /* no GPIOs are used */
+ [ICE_EEP2_GPIO_DIR1] = 0x00,
+ [ICE_EEP2_GPIO_DIR2] = 0x00,
+ [ICE_EEP2_GPIO_MASK] = 0xff,
+ [ICE_EEP2_GPIO_MASK1] = 0xff,
+ [ICE_EEP2_GPIO_MASK2] = 0xff,
+
+ [ICE_EEP2_GPIO_STATE] = 0x00, /* inputs */
+ [ICE_EEP2_GPIO_STATE1] = 0x00, /* all 1, but GPIO_CPLD_RW
+ and GPIO15 always zero */
+ [ICE_EEP2_GPIO_STATE2] = 0x00, /* inputs */
+};
+
+
+static const struct snd_ice1712_card_info snd_vt1724_ooaoo_cards[] = {
+ {
+ .name = "ooAoo SQ210a",
+ .model = "sq210a",
+ .eeprom_size = sizeof(ooaoo_sq210_eeprom),
+ .eeprom_data = ooaoo_sq210_eeprom,
+ },
+ { } /* terminator */
+};
+
+static const struct snd_ice1712_card_info *card_tables[] = {
+ snd_vt1724_revo_cards,
+ snd_vt1724_amp_cards,
+ snd_vt1724_aureon_cards,
+ snd_vt1720_mobo_cards,
+ snd_vt1720_pontis_cards,
+ snd_vt1724_prodigy_hifi_cards,
+ snd_vt1724_prodigy192_cards,
+ snd_vt1724_juli_cards,
+ snd_vt1724_maya44_cards,
+ snd_vt1724_phase_cards,
+ snd_vt1724_wtm_cards,
+ snd_vt1724_se_cards,
+ snd_vt1724_qtet_cards,
+ snd_vt1724_ooaoo_cards,
+ snd_vt1724_psc724_cards,
+ NULL,
+};
+
+
+/*
+ */
+
+static void wait_i2c_busy(struct snd_ice1712 *ice)
+{
+ int t = 0x10000;
+ while ((inb(ICEREG1724(ice, I2C_CTRL)) & VT1724_I2C_BUSY) && t--)
+ ;
+ if (t == -1)
+ dev_err(ice->card->dev, "i2c busy timeout\n");
+}
+
+unsigned char snd_vt1724_read_i2c(struct snd_ice1712 *ice,
+ unsigned char dev, unsigned char addr)
+{
+ unsigned char val;
+
+ mutex_lock(&ice->i2c_mutex);
+ wait_i2c_busy(ice);
+ outb(addr, ICEREG1724(ice, I2C_BYTE_ADDR));
+ outb(dev & ~VT1724_I2C_WRITE, ICEREG1724(ice, I2C_DEV_ADDR));
+ wait_i2c_busy(ice);
+ val = inb(ICEREG1724(ice, I2C_DATA));
+ mutex_unlock(&ice->i2c_mutex);
+ /*
+ dev_dbg(ice->card->dev, "i2c_read: [0x%x,0x%x] = 0x%x\n", dev, addr, val);
+ */
+ return val;
+}
+
+void snd_vt1724_write_i2c(struct snd_ice1712 *ice,
+ unsigned char dev, unsigned char addr, unsigned char data)
+{
+ mutex_lock(&ice->i2c_mutex);
+ wait_i2c_busy(ice);
+ /*
+ dev_dbg(ice->card->dev, "i2c_write: [0x%x,0x%x] = 0x%x\n", dev, addr, data);
+ */
+ outb(addr, ICEREG1724(ice, I2C_BYTE_ADDR));
+ outb(data, ICEREG1724(ice, I2C_DATA));
+ outb(dev | VT1724_I2C_WRITE, ICEREG1724(ice, I2C_DEV_ADDR));
+ wait_i2c_busy(ice);
+ mutex_unlock(&ice->i2c_mutex);
+}
+
+static int snd_vt1724_read_eeprom(struct snd_ice1712 *ice,
+ const char *modelname)
+{
+ const int dev = 0xa0; /* EEPROM device address */
+ unsigned int i, size;
+ const struct snd_ice1712_card_info * const *tbl, *c;
+
+ if (!modelname || !*modelname) {
+ ice->eeprom.subvendor = 0;
+ if ((inb(ICEREG1724(ice, I2C_CTRL)) & VT1724_I2C_EEPROM) != 0)
+ ice->eeprom.subvendor =
+ (snd_vt1724_read_i2c(ice, dev, 0x00) << 0) |
+ (snd_vt1724_read_i2c(ice, dev, 0x01) << 8) |
+ (snd_vt1724_read_i2c(ice, dev, 0x02) << 16) |
+ (snd_vt1724_read_i2c(ice, dev, 0x03) << 24);
+ if (ice->eeprom.subvendor == 0 ||
+ ice->eeprom.subvendor == (unsigned int)-1) {
+ /* invalid subvendor from EEPROM, try the PCI
+ * subststem ID instead
+ */
+ u16 vendor, device;
+ pci_read_config_word(ice->pci, PCI_SUBSYSTEM_VENDOR_ID,
+ &vendor);
+ pci_read_config_word(ice->pci, PCI_SUBSYSTEM_ID, &device);
+ ice->eeprom.subvendor =
+ ((unsigned int)swab16(vendor) << 16) | swab16(device);
+ if (ice->eeprom.subvendor == 0 ||
+ ice->eeprom.subvendor == (unsigned int)-1) {
+ dev_err(ice->card->dev,
+ "No valid ID is found\n");
+ return -ENXIO;
+ }
+ }
+ }
+ for (tbl = card_tables; *tbl; tbl++) {
+ for (c = *tbl; c->name; c++) {
+ if (modelname && c->model &&
+ !strcmp(modelname, c->model)) {
+ dev_info(ice->card->dev,
+ "Using board model %s\n",
+ c->name);
+ 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 */
+ dev_dbg(ice->card->dev, "using the defined eeprom..\n");
+ ice->eeprom.version = 2;
+ ice->eeprom.size = c->eeprom_size + 6;
+ memcpy(ice->eeprom.data, c->eeprom_data, c->eeprom_size);
+ goto read_skipped;
+ }
+ }
+ dev_warn(ice->card->dev, "No matching model found for ID 0x%x\n",
+ ice->eeprom.subvendor);
+#ifdef CONFIG_PM_SLEEP
+ /* assume AC97-only card which can suspend without additional code */
+ ice->pm_suspend_enabled = 1;
+#endif
+
+ found:
+ ice->eeprom.size = snd_vt1724_read_i2c(ice, dev, 0x04);
+ if (ice->eeprom.size < 6)
+ ice->eeprom.size = 32;
+ else if (ice->eeprom.size > 32) {
+ dev_err(ice->card->dev, "Invalid EEPROM (size = %i)\n",
+ ice->eeprom.size);
+ return -EIO;
+ }
+ ice->eeprom.version = snd_vt1724_read_i2c(ice, dev, 0x05);
+ if (ice->eeprom.version != 1 && ice->eeprom.version != 2)
+ dev_warn(ice->card->dev, "Invalid EEPROM version %i\n",
+ ice->eeprom.version);
+ size = ice->eeprom.size - 6;
+ for (i = 0; i < size; i++)
+ ice->eeprom.data[i] = snd_vt1724_read_i2c(ice, dev, i + 6);
+
+ read_skipped:
+ ice->eeprom.gpiomask = eeprom_triple(ice, ICE_EEP2_GPIO_MASK);
+ ice->eeprom.gpiostate = eeprom_triple(ice, ICE_EEP2_GPIO_STATE);
+ ice->eeprom.gpiodir = eeprom_triple(ice, ICE_EEP2_GPIO_DIR);
+
+ return 0;
+}
+
+
+
+static void snd_vt1724_chip_reset(struct snd_ice1712 *ice)
+{
+ outb(VT1724_RESET , ICEREG1724(ice, CONTROL));
+ inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */
+ msleep(10);
+ outb(0, ICEREG1724(ice, CONTROL));
+ inb(ICEREG1724(ice, CONTROL)); /* pci posting flush */
+ msleep(10);
+}
+
+static int snd_vt1724_chip_init(struct snd_ice1712 *ice)
+{
+ outb(ice->eeprom.data[ICE_EEP2_SYSCONF], ICEREG1724(ice, SYS_CFG));
+ outb(ice->eeprom.data[ICE_EEP2_ACLINK], ICEREG1724(ice, AC97_CFG));
+ outb(ice->eeprom.data[ICE_EEP2_I2S], ICEREG1724(ice, I2S_FEATURES));
+ outb(ice->eeprom.data[ICE_EEP2_SPDIF], ICEREG1724(ice, SPDIF_CFG));
+
+ ice->gpio.write_mask = ice->eeprom.gpiomask;
+ ice->gpio.direction = ice->eeprom.gpiodir;
+ snd_vt1724_set_gpio_mask(ice, ice->eeprom.gpiomask);
+ snd_vt1724_set_gpio_dir(ice, ice->eeprom.gpiodir);
+ snd_vt1724_set_gpio_data(ice, ice->eeprom.gpiostate);
+
+ outb(0, ICEREG1724(ice, POWERDOWN));
+
+ /* MPU_RX and TX irq masks are cleared later dynamically */
+ outb(VT1724_IRQ_MPU_RX | VT1724_IRQ_MPU_TX , ICEREG1724(ice, IRQMASK));
+
+ /* don't handle FIFO overrun/underruns (just yet),
+ * since they cause machine lockups
+ */
+ outb(VT1724_MULTI_FIFO_ERR, ICEMT1724(ice, DMA_INT_MASK));
+
+ return 0;
+}
+
+static int snd_vt1724_spdif_build_controls(struct snd_ice1712 *ice)
+{
+ int err;
+ struct snd_kcontrol *kctl;
+
+ if (snd_BUG_ON(!ice->pcm))
+ return -EIO;
+
+ if (!ice->own_routing) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&snd_vt1724_mixer_pro_spdif_route, ice));
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_spdif_switch, ice));
+ if (err < 0)
+ return err;
+
+ kctl = snd_ctl_new1(&snd_vt1724_spdif_default, ice);
+ kctl->id.device = ice->pcm->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ kctl = snd_ctl_new1(&snd_vt1724_spdif_maskc, ice);
+ kctl->id.device = ice->pcm->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ kctl = snd_ctl_new1(&snd_vt1724_spdif_maskp, ice);
+ kctl->id.device = ice->pcm->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+#if 0 /* use default only */
+ kctl = snd_ctl_new1(&snd_vt1724_spdif_stream, ice);
+ kctl->id.device = ice->pcm->device;
+ err = snd_ctl_add(ice->card, kctl);
+ if (err < 0)
+ return err;
+ ice->spdif.stream_ctl = kctl;
+#endif
+ return 0;
+}
+
+
+static int snd_vt1724_build_controls(struct snd_ice1712 *ice)
+{
+ int err;
+
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_eeprom, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_internal_clock, ice));
+ if (err < 0)
+ return err;
+
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_rate_locking, ice));
+ if (err < 0)
+ return err;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_vt1724_pro_rate_reset, ice));
+ if (err < 0)
+ return err;
+
+ if (!ice->own_routing && ice->num_total_dacs > 0) {
+ struct snd_kcontrol_new tmp = snd_vt1724_mixer_pro_analog_route;
+ tmp.count = ice->num_total_dacs;
+ if (ice->vt1720 && tmp.count > 2)
+ tmp.count = 2;
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&tmp, ice));
+ if (err < 0)
+ return err;
+ }
+
+ return snd_ctl_add(ice->card,
+ snd_ctl_new1(&snd_vt1724_mixer_pro_peak, ice));
+}
+
+static void snd_vt1724_free(struct snd_card *card)
+{
+ struct snd_ice1712 *ice = card->private_data;
+
+ /* mask all interrupts */
+ outb(0xff, ICEMT1724(ice, DMA_INT_MASK));
+ outb(0xff, ICEREG1724(ice, IRQMASK));
+
+ snd_ice1712_akm4xxx_free(ice);
+}
+
+static int snd_vt1724_create(struct snd_card *card,
+ struct pci_dev *pci,
+ const char *modelname)
+{
+ struct snd_ice1712 *ice = card->private_data;
+ int err;
+
+ /* enable PCI device */
+ err = pcim_enable_device(pci);
+ if (err < 0)
+ return err;
+
+ ice->vt1724 = 1;
+ spin_lock_init(&ice->reg_lock);
+ mutex_init(&ice->gpio_mutex);
+ mutex_init(&ice->open_mutex);
+ mutex_init(&ice->i2c_mutex);
+ ice->gpio.set_mask = snd_vt1724_set_gpio_mask;
+ ice->gpio.get_mask = snd_vt1724_get_gpio_mask;
+ ice->gpio.set_dir = snd_vt1724_set_gpio_dir;
+ ice->gpio.get_dir = snd_vt1724_get_gpio_dir;
+ ice->gpio.set_data = snd_vt1724_set_gpio_data;
+ ice->gpio.get_data = snd_vt1724_get_gpio_data;
+ ice->card = card;
+ ice->pci = pci;
+ ice->irq = -1;
+ pci_set_master(pci);
+ snd_vt1724_proc_init(ice);
+
+ err = pci_request_regions(pci, "ICE1724");
+ if (err < 0)
+ return err;
+ ice->port = pci_resource_start(pci, 0);
+ ice->profi_port = pci_resource_start(pci, 1);
+
+ if (devm_request_irq(&pci->dev, pci->irq, snd_vt1724_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, ice)) {
+ dev_err(card->dev, "unable to grab IRQ %d\n", pci->irq);
+ return -EIO;
+ }
+
+ ice->irq = pci->irq;
+ card->sync_irq = ice->irq;
+ card->private_free = snd_vt1724_free;
+
+ snd_vt1724_chip_reset(ice);
+ if (snd_vt1724_read_eeprom(ice, modelname) < 0)
+ return -EIO;
+ if (snd_vt1724_chip_init(ice) < 0)
+ return -EIO;
+
+ return 0;
+}
+
+
+/*
+ *
+ * Registration
+ *
+ */
+
+static int __snd_vt1724_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ struct snd_card *card;
+ struct snd_ice1712 *ice;
+ int pcm_dev = 0, err;
+ const struct snd_ice1712_card_info *c;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ err = snd_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ sizeof(*ice), &card);
+ if (err < 0)
+ return err;
+ ice = card->private_data;
+
+ strcpy(card->driver, "ICE1724");
+ strcpy(card->shortname, "ICEnsemble ICE1724");
+
+ err = snd_vt1724_create(card, pci, model[dev]);
+ if (err < 0)
+ return err;
+
+ /* field init before calling chip_init */
+ ice->ext_clock_count = 0;
+
+ c = ice->card_info;
+ if (c) {
+ strcpy(card->shortname, c->name);
+ if (c->driver) /* specific driver? */
+ strcpy(card->driver, c->driver);
+ if (c->chip_init) {
+ err = c->chip_init(ice);
+ if (err < 0)
+ return err;
+ }
+ }
+
+ /*
+ * VT1724 has separate DMAs for the analog and the SPDIF streams while
+ * ICE1712 has only one for both (mixed up).
+ *
+ * Confusingly the analog PCM is named "professional" here because it
+ * was called so in ice1712 driver, and vt1724 driver is derived from
+ * ice1712 driver.
+ */
+ ice->pro_rate_default = PRO_RATE_DEFAULT;
+ if (!ice->is_spdif_master)
+ ice->is_spdif_master = stdclock_is_spdif_master;
+ if (!ice->get_rate)
+ ice->get_rate = stdclock_get_rate;
+ if (!ice->set_rate)
+ ice->set_rate = stdclock_set_rate;
+ if (!ice->set_mclk)
+ ice->set_mclk = stdclock_set_mclk;
+ if (!ice->set_spdif_clock)
+ ice->set_spdif_clock = stdclock_set_spdif_clock;
+ if (!ice->get_spdif_master_type)
+ ice->get_spdif_master_type = stdclock_get_spdif_master_type;
+ if (!ice->ext_clock_names)
+ ice->ext_clock_names = ext_clock_names;
+ if (!ice->ext_clock_count)
+ ice->ext_clock_count = ARRAY_SIZE(ext_clock_names);
+
+ if (!ice->hw_rates)
+ set_std_hw_rates(ice);
+
+ err = snd_vt1724_pcm_profi(ice, pcm_dev++);
+ if (err < 0)
+ return err;
+
+ err = snd_vt1724_pcm_spdif(ice, pcm_dev++);
+ if (err < 0)
+ return err;
+
+ err = snd_vt1724_pcm_indep(ice, pcm_dev++);
+ if (err < 0)
+ return err;
+
+ err = snd_vt1724_ac97_mixer(ice);
+ if (err < 0)
+ return err;
+
+ err = snd_vt1724_build_controls(ice);
+ if (err < 0)
+ return err;
+
+ if (ice->pcm && ice->has_spdif) { /* has SPDIF I/O */
+ err = snd_vt1724_spdif_build_controls(ice);
+ if (err < 0)
+ return err;
+ }
+
+ if (c && c->build_controls) {
+ err = c->build_controls(ice);
+ if (err < 0)
+ return err;
+ }
+
+ if (!c || !c->no_mpu401) {
+ if (ice->eeprom.data[ICE_EEP2_SYSCONF] & VT1724_CFG_MPU401) {
+ struct snd_rawmidi *rmidi;
+
+ err = snd_rawmidi_new(card, "MIDI", 0, 1, 1, &rmidi);
+ if (err < 0)
+ return err;
+ ice->rmidi[0] = rmidi;
+ rmidi->private_data = ice;
+ strcpy(rmidi->name, "ICE1724 MIDI");
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &vt1724_midi_output_ops);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &vt1724_midi_input_ops);
+
+ /* set watermarks */
+ outb(VT1724_MPU_RX_FIFO | 0x1,
+ ICEREG1724(ice, MPU_FIFO_WM));
+ outb(0x1, ICEREG1724(ice, MPU_FIFO_WM));
+ /* set UART mode */
+ outb(VT1724_MPU_UART, ICEREG1724(ice, MPU_CTRL));
+ }
+ }
+
+ sprintf(card->longname, "%s at 0x%lx, irq %i",
+ card->shortname, ice->port, ice->irq);
+
+ err = snd_card_register(card);
+ if (err < 0)
+ return err;
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+}
+
+static int snd_vt1724_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ return snd_card_free_on_error(&pci->dev, __snd_vt1724_probe(pci, pci_id));
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int snd_vt1724_suspend(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_ice1712 *ice = card->private_data;
+
+ if (!ice->pm_suspend_enabled)
+ return 0;
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+
+ snd_ac97_suspend(ice->ac97);
+
+ spin_lock_irq(&ice->reg_lock);
+ ice->pm_saved_is_spdif_master = ice->is_spdif_master(ice);
+ ice->pm_saved_spdif_ctrl = inw(ICEMT1724(ice, SPDIF_CTRL));
+ ice->pm_saved_spdif_cfg = inb(ICEREG1724(ice, SPDIF_CFG));
+ ice->pm_saved_route = inl(ICEMT1724(ice, ROUTE_PLAYBACK));
+ spin_unlock_irq(&ice->reg_lock);
+
+ if (ice->pm_suspend)
+ ice->pm_suspend(ice);
+ return 0;
+}
+
+static int snd_vt1724_resume(struct device *dev)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_ice1712 *ice = card->private_data;
+
+ if (!ice->pm_suspend_enabled)
+ return 0;
+
+ snd_vt1724_chip_reset(ice);
+
+ if (snd_vt1724_chip_init(ice) < 0) {
+ snd_card_disconnect(card);
+ return -EIO;
+ }
+
+ if (ice->pm_resume)
+ ice->pm_resume(ice);
+
+ if (ice->pm_saved_is_spdif_master) {
+ /* switching to external clock via SPDIF */
+ ice->set_spdif_clock(ice, 0);
+ } else {
+ /* internal on-card clock */
+ 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);
+
+ outb(ice->pm_saved_spdif_cfg, ICEREG1724(ice, SPDIF_CFG));
+ outl(ice->pm_saved_route, ICEMT1724(ice, ROUTE_PLAYBACK));
+
+ snd_ac97_resume(ice->ac97);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(snd_vt1724_pm, snd_vt1724_suspend, snd_vt1724_resume);
+#define SND_VT1724_PM_OPS &snd_vt1724_pm
+#else
+#define SND_VT1724_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static struct pci_driver vt1724_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_vt1724_ids,
+ .probe = snd_vt1724_probe,
+ .driver = {
+ .pm = SND_VT1724_PM_OPS,
+ },
+};
+
+module_pci_driver(vt1724_driver);
diff --git a/sound/pci/ice1712/juli.c b/sound/pci/ice1712/juli.c
new file mode 100644
index 0000000000..d679842ae1
--- /dev/null
+++ b/sound/pci/ice1712/juli.c
@@ -0,0 +1,662 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for ESI Juli@ cards
+ *
+ * Copyright (c) 2004 Jaroslav Kysela <perex@perex.cz>
+ * 2008 Pavel Hofman <dustin@seznam.cz>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "juli.h"
+
+struct juli_spec {
+ struct ak4114 *ak4114;
+ unsigned int analog:1;
+};
+
+/*
+ * chip addresses on I2C bus
+ */
+#define AK4114_ADDR 0x20 /* S/PDIF receiver */
+#define AK4358_ADDR 0x22 /* DAC */
+
+/*
+ * Juli does not use the standard ICE1724 clock scheme. Juli's ice1724 chip is
+ * supplied by external clock provided by Xilinx array and MK73-1 PLL frequency
+ * multiplier. Actual frequency is set by ice1724 GPIOs hooked to the Xilinx.
+ *
+ * The clock circuitry is supplied by the two ice1724 crystals. This
+ * arrangement allows to generate independent clock signal for AK4114's input
+ * rate detection circuit. As a result, Juli, unlike most other
+ * ice1724+ak4114-based cards, detects spdif input rate correctly.
+ * This fact is applied in the driver, allowing to modify PCM stream rate
+ * parameter according to the actual input rate.
+ *
+ * Juli uses the remaining three stereo-channels of its DAC to optionally
+ * monitor analog input, digital input, and digital output. The corresponding
+ * I2S signals are routed by Xilinx, controlled by GPIOs.
+ *
+ * The master mute is implemented using output muting transistors (GPIO) in
+ * combination with smuting the DAC.
+ *
+ * The card itself has no HW master volume control, implemented using the
+ * vmaster control.
+ *
+ * TODO:
+ * researching and fixing the input monitors
+ */
+
+/*
+ * GPIO pins
+ */
+#define GPIO_FREQ_MASK (3<<0)
+#define GPIO_FREQ_32KHZ (0<<0)
+#define GPIO_FREQ_44KHZ (1<<0)
+#define GPIO_FREQ_48KHZ (2<<0)
+#define GPIO_MULTI_MASK (3<<2)
+#define GPIO_MULTI_4X (0<<2)
+#define GPIO_MULTI_2X (1<<2)
+#define GPIO_MULTI_1X (2<<2) /* also external */
+#define GPIO_MULTI_HALF (3<<2)
+#define GPIO_INTERNAL_CLOCK (1<<4) /* 0 = external, 1 = internal */
+#define GPIO_CLOCK_MASK (1<<4)
+#define GPIO_ANALOG_PRESENT (1<<5) /* RO only: 0 = present */
+#define GPIO_RXMCLK_SEL (1<<7) /* must be 0 */
+#define GPIO_AK5385A_CKS0 (1<<8)
+#define GPIO_AK5385A_DFS1 (1<<9)
+#define GPIO_AK5385A_DFS0 (1<<10)
+#define GPIO_DIGOUT_MONITOR (1<<11) /* 1 = active */
+#define GPIO_DIGIN_MONITOR (1<<12) /* 1 = active */
+#define GPIO_ANAIN_MONITOR (1<<13) /* 1 = active */
+#define GPIO_AK5385A_CKS1 (1<<14) /* must be 0 */
+#define GPIO_MUTE_CONTROL (1<<15) /* output mute, 1 = muted */
+
+#define GPIO_RATE_MASK (GPIO_FREQ_MASK | GPIO_MULTI_MASK | \
+ GPIO_CLOCK_MASK)
+#define GPIO_AK5385A_MASK (GPIO_AK5385A_CKS0 | GPIO_AK5385A_DFS0 | \
+ GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS1)
+
+#define JULI_PCM_RATE (SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | \
+ SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
+
+#define GPIO_RATE_16000 (GPIO_FREQ_32KHZ | GPIO_MULTI_HALF | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_22050 (GPIO_FREQ_44KHZ | GPIO_MULTI_HALF | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_24000 (GPIO_FREQ_48KHZ | GPIO_MULTI_HALF | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_32000 (GPIO_FREQ_32KHZ | GPIO_MULTI_1X | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_44100 (GPIO_FREQ_44KHZ | GPIO_MULTI_1X | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_48000 (GPIO_FREQ_48KHZ | GPIO_MULTI_1X | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_64000 (GPIO_FREQ_32KHZ | GPIO_MULTI_2X | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_88200 (GPIO_FREQ_44KHZ | GPIO_MULTI_2X | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_96000 (GPIO_FREQ_48KHZ | GPIO_MULTI_2X | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_176400 (GPIO_FREQ_44KHZ | GPIO_MULTI_4X | \
+ GPIO_INTERNAL_CLOCK)
+#define GPIO_RATE_192000 (GPIO_FREQ_48KHZ | GPIO_MULTI_4X | \
+ GPIO_INTERNAL_CLOCK)
+
+/*
+ * Initial setup of the conversion array GPIO <-> rate
+ */
+static const unsigned int juli_rates[] = {
+ 16000, 22050, 24000, 32000,
+ 44100, 48000, 64000, 88200,
+ 96000, 176400, 192000,
+};
+
+static const unsigned int gpio_vals[] = {
+ GPIO_RATE_16000, GPIO_RATE_22050, GPIO_RATE_24000, GPIO_RATE_32000,
+ GPIO_RATE_44100, GPIO_RATE_48000, GPIO_RATE_64000, GPIO_RATE_88200,
+ GPIO_RATE_96000, GPIO_RATE_176400, GPIO_RATE_192000,
+};
+
+static const struct snd_pcm_hw_constraint_list juli_rates_info = {
+ .count = ARRAY_SIZE(juli_rates),
+ .list = juli_rates,
+ .mask = 0,
+};
+
+static int get_gpio_val(int rate)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(juli_rates); i++)
+ if (juli_rates[i] == rate)
+ return gpio_vals[i];
+ return 0;
+}
+
+static void juli_ak4114_write(void *private_data, unsigned char reg,
+ unsigned char val)
+{
+ snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4114_ADDR,
+ reg, val);
+}
+
+static unsigned char juli_ak4114_read(void *private_data, unsigned char reg)
+{
+ return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data,
+ AK4114_ADDR, reg);
+}
+
+/*
+ * If SPDIF capture and slaved to SPDIF-IN, setting runtime rate
+ * to the external rate
+ */
+static void juli_spdif_in_open(struct snd_ice1712 *ice,
+ struct snd_pcm_substream *substream)
+{
+ struct juli_spec *spec = ice->spec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int rate;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
+ !ice->is_spdif_master(ice))
+ return;
+ rate = snd_ak4114_external_rate(spec->ak4114);
+ if (rate >= runtime->hw.rate_min && rate <= runtime->hw.rate_max) {
+ runtime->hw.rate_min = rate;
+ runtime->hw.rate_max = rate;
+ }
+}
+
+/*
+ * AK4358 section
+ */
+
+static void juli_akm_lock(struct snd_akm4xxx *ak, int chip)
+{
+}
+
+static void juli_akm_unlock(struct snd_akm4xxx *ak, int chip)
+{
+}
+
+static void juli_akm_write(struct snd_akm4xxx *ak, int chip,
+ unsigned char addr, unsigned char data)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ if (snd_BUG_ON(chip))
+ return;
+ snd_vt1724_write_i2c(ice, AK4358_ADDR, addr, data);
+}
+
+/*
+ * change the rate of envy24HT, AK4358, AK5385
+ */
+static void juli_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+ unsigned char old, tmp, ak4358_dfs;
+ unsigned int ak5385_pins, old_gpio, new_gpio;
+ struct snd_ice1712 *ice = ak->private_data[0];
+ struct juli_spec *spec = ice->spec;
+
+ if (rate == 0) /* no hint - S/PDIF input is master or the new spdif
+ input rate undetected, simply return */
+ return;
+
+ /* adjust DFS on codecs */
+ if (rate > 96000) {
+ ak4358_dfs = 2;
+ ak5385_pins = GPIO_AK5385A_DFS1 | GPIO_AK5385A_CKS0;
+ } else if (rate > 48000) {
+ ak4358_dfs = 1;
+ ak5385_pins = GPIO_AK5385A_DFS0;
+ } else {
+ ak4358_dfs = 0;
+ ak5385_pins = 0;
+ }
+ /* AK5385 first, since it requires cold reset affecting both codecs */
+ old_gpio = ice->gpio.get_data(ice);
+ new_gpio = (old_gpio & ~GPIO_AK5385A_MASK) | ak5385_pins;
+ /* dev_dbg(ice->card->dev, "JULI - ak5385 set_rate_val: new gpio 0x%x\n",
+ new_gpio); */
+ ice->gpio.set_data(ice, new_gpio);
+
+ /* cold reset */
+ old = inb(ICEMT1724(ice, AC97_CMD));
+ outb(old | VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD));
+ udelay(1);
+ outb(old & ~VT1724_AC97_COLD, ICEMT1724(ice, AC97_CMD));
+
+ /* AK4358 */
+ /* set new value, reset DFS */
+ tmp = snd_akm4xxx_get(ak, 0, 2);
+ snd_akm4xxx_reset(ak, 1);
+ tmp = snd_akm4xxx_get(ak, 0, 2);
+ tmp &= ~(0x03 << 4);
+ tmp |= ak4358_dfs << 4;
+ snd_akm4xxx_set(ak, 0, 2, tmp);
+ snd_akm4xxx_reset(ak, 0);
+
+ /* reinit ak4114 */
+ snd_ak4114_reinit(spec->ak4114);
+}
+
+#define AK_DAC(xname, xch) { .name = xname, .num_channels = xch }
+#define PCM_VOLUME "PCM Playback Volume"
+#define MONITOR_AN_IN_VOLUME "Monitor Analog In Volume"
+#define MONITOR_DIG_IN_VOLUME "Monitor Digital In Volume"
+#define MONITOR_DIG_OUT_VOLUME "Monitor Digital Out Volume"
+
+static const struct snd_akm4xxx_dac_channel juli_dac[] = {
+ AK_DAC(PCM_VOLUME, 2),
+ AK_DAC(MONITOR_AN_IN_VOLUME, 2),
+ AK_DAC(MONITOR_DIG_OUT_VOLUME, 2),
+ AK_DAC(MONITOR_DIG_IN_VOLUME, 2),
+};
+
+
+static const struct snd_akm4xxx akm_juli_dac = {
+ .type = SND_AK4358,
+ .num_dacs = 8, /* DAC1 - analog out
+ DAC2 - analog in monitor
+ DAC3 - digital out monitor
+ DAC4 - digital in monitor
+ */
+ .ops = {
+ .lock = juli_akm_lock,
+ .unlock = juli_akm_unlock,
+ .write = juli_akm_write,
+ .set_rate_val = juli_akm_set_rate_val
+ },
+ .dac_info = juli_dac,
+};
+
+#define juli_mute_info snd_ctl_boolean_mono_info
+
+static int juli_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ val = ice->gpio.get_data(ice) & (unsigned int) kcontrol->private_value;
+ if (kcontrol->private_value == GPIO_MUTE_CONTROL)
+ /* val 0 = signal on */
+ ucontrol->value.integer.value[0] = (val) ? 0 : 1;
+ else
+ /* val 1 = signal on */
+ ucontrol->value.integer.value[0] = (val) ? 1 : 0;
+ return 0;
+}
+
+static int juli_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int old_gpio, new_gpio;
+ old_gpio = ice->gpio.get_data(ice);
+ if (ucontrol->value.integer.value[0]) {
+ /* unmute */
+ if (kcontrol->private_value == GPIO_MUTE_CONTROL) {
+ /* 0 = signal on */
+ new_gpio = old_gpio & ~GPIO_MUTE_CONTROL;
+ /* un-smuting DAC */
+ snd_akm4xxx_write(ice->akm, 0, 0x01, 0x01);
+ } else
+ /* 1 = signal on */
+ new_gpio = old_gpio |
+ (unsigned int) kcontrol->private_value;
+ } else {
+ /* mute */
+ if (kcontrol->private_value == GPIO_MUTE_CONTROL) {
+ /* 1 = signal off */
+ new_gpio = old_gpio | GPIO_MUTE_CONTROL;
+ /* smuting DAC */
+ snd_akm4xxx_write(ice->akm, 0, 0x01, 0x03);
+ } else
+ /* 0 = signal off */
+ new_gpio = old_gpio &
+ ~((unsigned int) kcontrol->private_value);
+ }
+ /* dev_dbg(ice->card->dev,
+ "JULI - mute/unmute: control_value: 0x%x, old_gpio: 0x%x, "
+ "new_gpio 0x%x\n",
+ (unsigned int)ucontrol->value.integer.value[0], old_gpio,
+ new_gpio); */
+ if (old_gpio != new_gpio) {
+ ice->gpio.set_data(ice, new_gpio);
+ return 1;
+ }
+ /* no change */
+ return 0;
+}
+
+static const struct snd_kcontrol_new juli_mute_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Switch",
+ .info = juli_mute_info,
+ .get = juli_mute_get,
+ .put = juli_mute_put,
+ .private_value = GPIO_MUTE_CONTROL,
+ },
+ /* Although the following functionality respects the succint NDA'd
+ * documentation from the card manufacturer, and the same way of
+ * operation is coded in OSS Juli driver, only Digital Out monitor
+ * seems to work. Surprisingly, Analog input monitor outputs Digital
+ * output data. The two are independent, as enabling both doubles
+ * volume of the monitor sound.
+ *
+ * Checking traces on the board suggests the functionality described
+ * by the manufacturer is correct - I2S from ADC and AK4114
+ * go to ICE as well as to Xilinx, I2S inputs of DAC2,3,4 (the monitor
+ * inputs) are fed from Xilinx.
+ *
+ * I even checked traces on board and coded a support in driver for
+ * an alternative possibility - the unused I2S ICE output channels
+ * switched to HW-IN/SPDIF-IN and providing the monitoring signal to
+ * the DAC - to no avail. The I2S outputs seem to be unconnected.
+ *
+ * The windows driver supports the monitoring correctly.
+ */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Analog In Switch",
+ .info = juli_mute_info,
+ .get = juli_mute_get,
+ .put = juli_mute_put,
+ .private_value = GPIO_ANAIN_MONITOR,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Digital Out Switch",
+ .info = juli_mute_info,
+ .get = juli_mute_get,
+ .put = juli_mute_put,
+ .private_value = GPIO_DIGOUT_MONITOR,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Monitor Digital In Switch",
+ .info = juli_mute_info,
+ .get = juli_mute_get,
+ .put = juli_mute_put,
+ .private_value = GPIO_DIGIN_MONITOR,
+ },
+};
+
+static const char * const follower_vols[] = {
+ PCM_VOLUME,
+ MONITOR_AN_IN_VOLUME,
+ MONITOR_DIG_IN_VOLUME,
+ MONITOR_DIG_OUT_VOLUME,
+ NULL
+};
+
+static
+DECLARE_TLV_DB_SCALE(juli_master_db_scale, -6350, 50, 1);
+
+static int juli_add_controls(struct snd_ice1712 *ice)
+{
+ struct juli_spec *spec = ice->spec;
+ int err;
+ unsigned int i;
+ struct snd_kcontrol *vmaster;
+
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < ARRAY_SIZE(juli_mute_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&juli_mute_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+ /* Create virtual master control */
+ vmaster = snd_ctl_make_virtual_master("Master Playback Volume",
+ juli_master_db_scale);
+ if (!vmaster)
+ return -ENOMEM;
+ err = snd_ctl_add(ice->card, vmaster);
+ if (err < 0)
+ return err;
+ err = snd_ctl_add_followers(ice->card, vmaster, follower_vols);
+ if (err < 0)
+ return err;
+
+ /* only capture SPDIF over AK4114 */
+ return snd_ak4114_build(spec->ak4114, NULL,
+ ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+}
+
+/*
+ * suspend/resume
+ * */
+
+#ifdef CONFIG_PM_SLEEP
+static int juli_resume(struct snd_ice1712 *ice)
+{
+ struct snd_akm4xxx *ak = ice->akm;
+ struct juli_spec *spec = ice->spec;
+ /* akm4358 un-reset, un-mute */
+ snd_akm4xxx_reset(ak, 0);
+ /* reinit ak4114 */
+ snd_ak4114_resume(spec->ak4114);
+ return 0;
+}
+
+static int juli_suspend(struct snd_ice1712 *ice)
+{
+ struct snd_akm4xxx *ak = ice->akm;
+ struct juli_spec *spec = ice->spec;
+ /* akm4358 reset and soft-mute */
+ snd_akm4xxx_reset(ak, 1);
+ snd_ak4114_suspend(spec->ak4114);
+ return 0;
+}
+#endif
+
+/*
+ * initialize the chip
+ */
+
+static inline int juli_is_spdif_master(struct snd_ice1712 *ice)
+{
+ return (ice->gpio.get_data(ice) & GPIO_INTERNAL_CLOCK) ? 0 : 1;
+}
+
+static unsigned int juli_get_rate(struct snd_ice1712 *ice)
+{
+ int i;
+ unsigned char result;
+
+ result = ice->gpio.get_data(ice) & GPIO_RATE_MASK;
+ for (i = 0; i < ARRAY_SIZE(gpio_vals); i++)
+ if (gpio_vals[i] == result)
+ return juli_rates[i];
+ return 0;
+}
+
+/* setting new rate */
+static void juli_set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+ unsigned int old, new;
+ unsigned char val;
+
+ old = ice->gpio.get_data(ice);
+ new = (old & ~GPIO_RATE_MASK) | get_gpio_val(rate);
+ /* dev_dbg(ice->card->dev, "JULI - set_rate: old %x, new %x\n",
+ old & GPIO_RATE_MASK,
+ new & GPIO_RATE_MASK); */
+
+ ice->gpio.set_data(ice, new);
+ /* switching to external clock - supplied by external circuits */
+ val = inb(ICEMT1724(ice, RATE));
+ outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+}
+
+static inline unsigned char juli_set_mclk(struct snd_ice1712 *ice,
+ unsigned int rate)
+{
+ /* no change in master clock */
+ return 0;
+}
+
+/* setting clock to external - SPDIF */
+static int juli_set_spdif_clock(struct snd_ice1712 *ice, int type)
+{
+ unsigned int old;
+ old = ice->gpio.get_data(ice);
+ /* external clock (= 0), multiply 1x, 48kHz */
+ ice->gpio.set_data(ice, (old & ~GPIO_RATE_MASK) | GPIO_MULTI_1X |
+ GPIO_FREQ_48KHZ);
+ return 0;
+}
+
+/* Called when ak4114 detects change in the input SPDIF stream */
+static void juli_ak4114_change(struct ak4114 *ak4114, unsigned char c0,
+ unsigned char c1)
+{
+ struct snd_ice1712 *ice = ak4114->change_callback_private;
+ int rate;
+ if (ice->is_spdif_master(ice) && c1) {
+ /* only for SPDIF master mode, rate was changed */
+ rate = snd_ak4114_external_rate(ak4114);
+ /* dev_dbg(ice->card->dev, "ak4114 - input rate changed to %d\n",
+ rate); */
+ juli_akm_set_rate_val(ice->akm, rate);
+ }
+}
+
+static int juli_init(struct snd_ice1712 *ice)
+{
+ static const unsigned char ak4114_init_vals[] = {
+ /* AK4117_REG_PWRDN */ AK4114_RST | AK4114_PWN |
+ AK4114_OCKS0 | AK4114_OCKS1,
+ /* AK4114_REQ_FORMAT */ AK4114_DIF_I24I2S,
+ /* AK4114_REG_IO0 */ AK4114_TX1E,
+ /* AK4114_REG_IO1 */ AK4114_EFH_1024 | AK4114_DIT |
+ AK4114_IPS(1),
+ /* AK4114_REG_INT0_MASK */ 0,
+ /* AK4114_REG_INT1_MASK */ 0
+ };
+ static const unsigned char ak4114_init_txcsb[] = {
+ 0x41, 0x02, 0x2c, 0x00, 0x00
+ };
+ int err;
+ struct juli_spec *spec;
+ struct snd_akm4xxx *ak;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ err = snd_ak4114_create(ice->card,
+ juli_ak4114_read,
+ juli_ak4114_write,
+ ak4114_init_vals, ak4114_init_txcsb,
+ ice, &spec->ak4114);
+ if (err < 0)
+ return err;
+ /* callback for codecs rate setting */
+ spec->ak4114->change_callback = juli_ak4114_change;
+ spec->ak4114->change_callback_private = ice;
+ /* AK4114 in Juli can detect external rate correctly */
+ spec->ak4114->check_flags = 0;
+
+#if 0
+/*
+ * it seems that the analog doughter board detection does not work reliably, so
+ * force the analog flag; it should be very rare (if ever) to come at Juli@
+ * used without the analog daughter board
+ */
+ spec->analog = (ice->gpio.get_data(ice) & GPIO_ANALOG_PRESENT) ? 0 : 1;
+#else
+ spec->analog = 1;
+#endif
+
+ if (spec->analog) {
+ dev_info(ice->card->dev, "juli@: analog I/O detected\n");
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 2;
+
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ ak = ice->akm;
+ if (!ak)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+ err = snd_ice1712_akm4xxx_init(ak, &akm_juli_dac, NULL, ice);
+ if (err < 0)
+ return err;
+ }
+
+ /* juli is clocked by Xilinx array */
+ ice->hw_rates = &juli_rates_info;
+ ice->is_spdif_master = juli_is_spdif_master;
+ ice->get_rate = juli_get_rate;
+ ice->set_rate = juli_set_rate;
+ ice->set_mclk = juli_set_mclk;
+ ice->set_spdif_clock = juli_set_spdif_clock;
+
+ ice->spdif.ops.open = juli_spdif_in_open;
+
+#ifdef CONFIG_PM_SLEEP
+ ice->pm_resume = juli_resume;
+ ice->pm_suspend = juli_suspend;
+ ice->pm_suspend_enabled = 1;
+#endif
+
+ return 0;
+}
+
+
+/*
+ * Juli@ boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static const unsigned char juli_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x2b, /* clock 512, mpu401, 1xADC, 1xDACs,
+ SPDIF in */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xf8, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0x9f, /* 5, 6:inputs; 7, 4-0 outputs*/
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x7f,
+ [ICE_EEP2_GPIO_MASK] = 0x60, /* 5, 6: locked; 7, 4-0 writable */
+ [ICE_EEP2_GPIO_MASK1] = 0x00, /* 0-7 writable */
+ [ICE_EEP2_GPIO_MASK2] = 0x7f,
+ [ICE_EEP2_GPIO_STATE] = GPIO_FREQ_48KHZ | GPIO_MULTI_1X |
+ GPIO_INTERNAL_CLOCK, /* internal clock, multiple 1x, 48kHz*/
+ [ICE_EEP2_GPIO_STATE1] = 0x00, /* unmuted */
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_juli_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_JULI,
+ .name = "ESI Juli@",
+ .model = "juli",
+ .chip_init = juli_init,
+ .build_controls = juli_add_controls,
+ .eeprom_size = sizeof(juli_eeprom),
+ .eeprom_data = juli_eeprom,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/juli.h b/sound/pci/ice1712/juli.h
new file mode 100644
index 0000000000..9c22d4e73e
--- /dev/null
+++ b/sound/pci/ice1712/juli.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_JULI_H
+#define __SOUND_JULI_H
+
+#define JULI_DEVICE_DESC "{ESI,Juli@},"
+
+#define VT1724_SUBDEVICE_JULI 0x31305345 /* Juli@ */
+
+extern struct snd_ice1712_card_info snd_vt1724_juli_cards[];
+
+#endif /* __SOUND_JULI_H */
diff --git a/sound/pci/ice1712/maya44.c b/sound/pci/ice1712/maya44.c
new file mode 100644
index 0000000000..b46df18212
--- /dev/null
+++ b/sound/pci/ice1712/maya44.c
@@ -0,0 +1,748 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for ESI Maya44 cards
+ *
+ * Copyright (c) 2009 Takashi Iwai <tiwai@suse.de>
+ * Based on the patches by Rainer Zimmermann <mail@lightshed.de>
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "maya44.h"
+
+/* WM8776 register indexes */
+#define WM8776_REG_HEADPHONE_L 0x00
+#define WM8776_REG_HEADPHONE_R 0x01
+#define WM8776_REG_HEADPHONE_MASTER 0x02
+#define WM8776_REG_DAC_ATTEN_L 0x03
+#define WM8776_REG_DAC_ATTEN_R 0x04
+#define WM8776_REG_DAC_ATTEN_MASTER 0x05
+#define WM8776_REG_DAC_PHASE 0x06
+#define WM8776_REG_DAC_CONTROL 0x07
+#define WM8776_REG_DAC_MUTE 0x08
+#define WM8776_REG_DAC_DEEMPH 0x09
+#define WM8776_REG_DAC_IF_CONTROL 0x0a
+#define WM8776_REG_ADC_IF_CONTROL 0x0b
+#define WM8776_REG_MASTER_MODE_CONTROL 0x0c
+#define WM8776_REG_POWERDOWN 0x0d
+#define WM8776_REG_ADC_ATTEN_L 0x0e
+#define WM8776_REG_ADC_ATTEN_R 0x0f
+#define WM8776_REG_ADC_ALC1 0x10
+#define WM8776_REG_ADC_ALC2 0x11
+#define WM8776_REG_ADC_ALC3 0x12
+#define WM8776_REG_ADC_NOISE_GATE 0x13
+#define WM8776_REG_ADC_LIMITER 0x14
+#define WM8776_REG_ADC_MUX 0x15
+#define WM8776_REG_OUTPUT_MUX 0x16
+#define WM8776_REG_RESET 0x17
+
+#define WM8776_NUM_REGS 0x18
+
+/* clock ratio identifiers for snd_wm8776_set_rate() */
+#define WM8776_CLOCK_RATIO_128FS 0
+#define WM8776_CLOCK_RATIO_192FS 1
+#define WM8776_CLOCK_RATIO_256FS 2
+#define WM8776_CLOCK_RATIO_384FS 3
+#define WM8776_CLOCK_RATIO_512FS 4
+#define WM8776_CLOCK_RATIO_768FS 5
+
+enum { WM_VOL_HP, WM_VOL_DAC, WM_VOL_ADC, WM_NUM_VOLS };
+enum { WM_SW_DAC, WM_SW_BYPASS, WM_NUM_SWITCHES };
+
+struct snd_wm8776 {
+ unsigned char addr;
+ unsigned short regs[WM8776_NUM_REGS];
+ unsigned char volumes[WM_NUM_VOLS][2];
+ unsigned int switch_bits;
+};
+
+struct snd_maya44 {
+ struct snd_ice1712 *ice;
+ struct snd_wm8776 wm[2];
+ struct mutex mutex;
+};
+
+
+/* write the given register and save the data to the cache */
+static void wm8776_write(struct snd_ice1712 *ice, struct snd_wm8776 *wm,
+ unsigned char reg, unsigned short val)
+{
+ /*
+ * WM8776 registers are up to 9 bits wide, bit 8 is placed in the LSB
+ * of the address field
+ */
+ snd_vt1724_write_i2c(ice, wm->addr,
+ (reg << 1) | ((val >> 8) & 1),
+ val & 0xff);
+ wm->regs[reg] = val;
+}
+
+/*
+ * update the given register with and/or mask and save the data to the cache
+ */
+static int wm8776_write_bits(struct snd_ice1712 *ice, struct snd_wm8776 *wm,
+ unsigned char reg,
+ unsigned short mask, unsigned short val)
+{
+ val |= wm->regs[reg] & ~mask;
+ if (val != wm->regs[reg]) {
+ wm8776_write(ice, wm, reg, val);
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * WM8776 volume controls
+ */
+
+struct maya_vol_info {
+ unsigned int maxval; /* volume range: 0..maxval */
+ unsigned char regs[2]; /* left and right registers */
+ unsigned short mask; /* value mask */
+ unsigned short offset; /* zero-value offset */
+ unsigned short mute; /* mute bit */
+ unsigned short update; /* update bits */
+ unsigned char mux_bits[2]; /* extra bits for ADC mute */
+};
+
+static const struct maya_vol_info vol_info[WM_NUM_VOLS] = {
+ [WM_VOL_HP] = {
+ .maxval = 80,
+ .regs = { WM8776_REG_HEADPHONE_L, WM8776_REG_HEADPHONE_R },
+ .mask = 0x7f,
+ .offset = 0x30,
+ .mute = 0x00,
+ .update = 0x180, /* update and zero-cross enable */
+ },
+ [WM_VOL_DAC] = {
+ .maxval = 255,
+ .regs = { WM8776_REG_DAC_ATTEN_L, WM8776_REG_DAC_ATTEN_R },
+ .mask = 0xff,
+ .offset = 0x01,
+ .mute = 0x00,
+ .update = 0x100, /* zero-cross enable */
+ },
+ [WM_VOL_ADC] = {
+ .maxval = 91,
+ .regs = { WM8776_REG_ADC_ATTEN_L, WM8776_REG_ADC_ATTEN_R },
+ .mask = 0xff,
+ .offset = 0xa5,
+ .mute = 0xa5,
+ .update = 0x100, /* update */
+ .mux_bits = { 0x80, 0x40 }, /* ADCMUX bits */
+ },
+};
+
+/*
+ * dB tables
+ */
+/* headphone output: mute, -73..+6db (1db step) */
+static const DECLARE_TLV_DB_SCALE(db_scale_hp, -7400, 100, 1);
+/* DAC output: mute, -127..0db (0.5db step) */
+static const DECLARE_TLV_DB_SCALE(db_scale_dac, -12750, 50, 1);
+/* ADC gain: mute, -21..+24db (0.5db step) */
+static const DECLARE_TLV_DB_SCALE(db_scale_adc, -2100, 50, 1);
+
+static int maya_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ unsigned int idx = kcontrol->private_value;
+ const struct maya_vol_info *vol = &vol_info[idx];
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = vol->maxval;
+ return 0;
+}
+
+static int maya_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ struct snd_wm8776 *wm =
+ &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+ unsigned int idx = kcontrol->private_value;
+
+ mutex_lock(&chip->mutex);
+ ucontrol->value.integer.value[0] = wm->volumes[idx][0];
+ ucontrol->value.integer.value[1] = wm->volumes[idx][1];
+ mutex_unlock(&chip->mutex);
+ return 0;
+}
+
+static int maya_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ struct snd_wm8776 *wm =
+ &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+ unsigned int idx = kcontrol->private_value;
+ const struct maya_vol_info *vol = &vol_info[idx];
+ unsigned int val, data;
+ int ch, changed = 0;
+
+ mutex_lock(&chip->mutex);
+ for (ch = 0; ch < 2; ch++) {
+ val = ucontrol->value.integer.value[ch];
+ if (val > vol->maxval)
+ val = vol->maxval;
+ if (val == wm->volumes[idx][ch])
+ continue;
+ if (!val)
+ data = vol->mute;
+ else
+ data = (val - 1) + vol->offset;
+ data |= vol->update;
+ changed |= wm8776_write_bits(chip->ice, wm, vol->regs[ch],
+ vol->mask | vol->update, data);
+ if (vol->mux_bits[ch])
+ wm8776_write_bits(chip->ice, wm, WM8776_REG_ADC_MUX,
+ vol->mux_bits[ch],
+ val ? 0 : vol->mux_bits[ch]);
+ wm->volumes[idx][ch] = val;
+ }
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+/*
+ * WM8776 switch controls
+ */
+
+#define COMPOSE_SW_VAL(idx, reg, mask) ((idx) | ((reg) << 8) | ((mask) << 16))
+#define GET_SW_VAL_IDX(val) ((val) & 0xff)
+#define GET_SW_VAL_REG(val) (((val) >> 8) & 0xff)
+#define GET_SW_VAL_MASK(val) (((val) >> 16) & 0xff)
+
+#define maya_sw_info snd_ctl_boolean_mono_info
+
+static int maya_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ struct snd_wm8776 *wm =
+ &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+ unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value);
+
+ ucontrol->value.integer.value[0] = (wm->switch_bits >> idx) & 1;
+ return 0;
+}
+
+static int maya_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ struct snd_wm8776 *wm =
+ &chip->wm[snd_ctl_get_ioff(kcontrol, &ucontrol->id)];
+ unsigned int idx = GET_SW_VAL_IDX(kcontrol->private_value);
+ unsigned int mask, val;
+ int changed;
+
+ mutex_lock(&chip->mutex);
+ mask = 1 << idx;
+ wm->switch_bits &= ~mask;
+ val = ucontrol->value.integer.value[0];
+ if (val)
+ wm->switch_bits |= mask;
+ mask = GET_SW_VAL_MASK(kcontrol->private_value);
+ changed = wm8776_write_bits(chip->ice, wm,
+ GET_SW_VAL_REG(kcontrol->private_value),
+ mask, val ? mask : 0);
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+/*
+ * GPIO pins (known ones for maya44)
+ */
+#define GPIO_PHANTOM_OFF 2
+#define GPIO_MIC_RELAY 4
+#define GPIO_SPDIF_IN_INV 5
+#define GPIO_MUST_BE_0 7
+
+/*
+ * GPIO switch controls
+ */
+
+#define COMPOSE_GPIO_VAL(shift, inv) ((shift) | ((inv) << 8))
+#define GET_GPIO_VAL_SHIFT(val) ((val) & 0xff)
+#define GET_GPIO_VAL_INV(val) (((val) >> 8) & 1)
+
+static int maya_set_gpio_bits(struct snd_ice1712 *ice, unsigned int mask,
+ unsigned int bits)
+{
+ unsigned int data;
+ data = snd_ice1712_gpio_read(ice);
+ if ((data & mask) == bits)
+ return 0;
+ snd_ice1712_gpio_write(ice, (data & ~mask) | bits);
+ return 1;
+}
+
+#define maya_gpio_sw_info snd_ctl_boolean_mono_info
+
+static int maya_gpio_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value);
+ unsigned int val;
+
+ val = (snd_ice1712_gpio_read(chip->ice) >> shift) & 1;
+ if (GET_GPIO_VAL_INV(kcontrol->private_value))
+ val = !val;
+ ucontrol->value.integer.value[0] = val;
+ return 0;
+}
+
+static int maya_gpio_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ unsigned int shift = GET_GPIO_VAL_SHIFT(kcontrol->private_value);
+ unsigned int val, mask;
+ int changed;
+
+ mutex_lock(&chip->mutex);
+ mask = 1 << shift;
+ val = ucontrol->value.integer.value[0];
+ if (GET_GPIO_VAL_INV(kcontrol->private_value))
+ val = !val;
+ val = val ? mask : 0;
+ changed = maya_set_gpio_bits(chip->ice, mask, val);
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+/*
+ * capture source selection
+ */
+
+/* known working input slots (0-4) */
+#define MAYA_LINE_IN 1 /* in-2 */
+#define MAYA_MIC_IN 3 /* in-4 */
+
+static void wm8776_select_input(struct snd_maya44 *chip, int idx, int line)
+{
+ wm8776_write_bits(chip->ice, &chip->wm[idx], WM8776_REG_ADC_MUX,
+ 0x1f, 1 << line);
+}
+
+static int maya_rec_src_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = { "Line", "Mic" };
+
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int maya_rec_src_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ int sel;
+
+ if (snd_ice1712_gpio_read(chip->ice) & (1 << GPIO_MIC_RELAY))
+ sel = 1;
+ else
+ sel = 0;
+ ucontrol->value.enumerated.item[0] = sel;
+ return 0;
+}
+
+static int maya_rec_src_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ int sel = ucontrol->value.enumerated.item[0];
+ int changed;
+
+ mutex_lock(&chip->mutex);
+ changed = maya_set_gpio_bits(chip->ice, 1 << GPIO_MIC_RELAY,
+ sel ? (1 << GPIO_MIC_RELAY) : 0);
+ wm8776_select_input(chip, 0, sel ? MAYA_MIC_IN : MAYA_LINE_IN);
+ mutex_unlock(&chip->mutex);
+ return changed;
+}
+
+/*
+ * Maya44 routing switch settings have different meanings than the standard
+ * ice1724 switches as defined in snd_vt1724_pro_route_info (ice1724.c).
+ */
+static int maya_pb_route_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "PCM Out", /* 0 */
+ "Input 1", "Input 2", "Input 3", "Input 4"
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int maya_pb_route_shift(int idx)
+{
+ static const unsigned char shift[10] =
+ { 8, 20, 0, 3, 11, 23, 14, 26, 17, 29 };
+ return shift[idx % 10];
+}
+
+static int maya_pb_route_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ ucontrol->value.enumerated.item[0] =
+ snd_ice1724_get_route_val(chip->ice, maya_pb_route_shift(idx));
+ return 0;
+}
+
+static int maya_pb_route_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_maya44 *chip = snd_kcontrol_chip(kcontrol);
+ int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ return snd_ice1724_put_route_val(chip->ice,
+ ucontrol->value.enumerated.item[0],
+ maya_pb_route_shift(idx));
+}
+
+
+/*
+ * controls to be added
+ */
+
+static const struct snd_kcontrol_new maya_controls[] = {
+ {
+ .name = "Crossmix Playback Volume",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = maya_vol_info,
+ .get = maya_vol_get,
+ .put = maya_vol_put,
+ .tlv = { .p = db_scale_hp },
+ .private_value = WM_VOL_HP,
+ .count = 2,
+ },
+ {
+ .name = "PCM Playback Volume",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = maya_vol_info,
+ .get = maya_vol_get,
+ .put = maya_vol_put,
+ .tlv = { .p = db_scale_dac },
+ .private_value = WM_VOL_DAC,
+ .count = 2,
+ },
+ {
+ .name = "Line Capture Volume",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = maya_vol_info,
+ .get = maya_vol_get,
+ .put = maya_vol_put,
+ .tlv = { .p = db_scale_adc },
+ .private_value = WM_VOL_ADC,
+ .count = 2,
+ },
+ {
+ .name = "PCM Playback Switch",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = maya_sw_info,
+ .get = maya_sw_get,
+ .put = maya_sw_put,
+ .private_value = COMPOSE_SW_VAL(WM_SW_DAC,
+ WM8776_REG_OUTPUT_MUX, 0x01),
+ .count = 2,
+ },
+ {
+ .name = "Bypass Playback Switch",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = maya_sw_info,
+ .get = maya_sw_get,
+ .put = maya_sw_put,
+ .private_value = COMPOSE_SW_VAL(WM_SW_BYPASS,
+ WM8776_REG_OUTPUT_MUX, 0x04),
+ .count = 2,
+ },
+ {
+ .name = "Capture Source",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = maya_rec_src_info,
+ .get = maya_rec_src_get,
+ .put = maya_rec_src_put,
+ },
+ {
+ .name = "Mic Phantom Power Switch",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = maya_gpio_sw_info,
+ .get = maya_gpio_sw_get,
+ .put = maya_gpio_sw_put,
+ .private_value = COMPOSE_GPIO_VAL(GPIO_PHANTOM_OFF, 1),
+ },
+ {
+ .name = "SPDIF Capture Switch",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .info = maya_gpio_sw_info,
+ .get = maya_gpio_sw_get,
+ .put = maya_gpio_sw_put,
+ .private_value = COMPOSE_GPIO_VAL(GPIO_SPDIF_IN_INV, 1),
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "H/W Playback Route",
+ .info = maya_pb_route_info,
+ .get = maya_pb_route_get,
+ .put = maya_pb_route_put,
+ .count = 4, /* FIXME: do controls 5-9 have any meaning? */
+ },
+};
+
+static int maya44_add_controls(struct snd_ice1712 *ice)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(maya_controls); i++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&maya_controls[i],
+ ice->spec));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+
+/*
+ * initialize a wm8776 chip
+ */
+static void wm8776_init(struct snd_ice1712 *ice,
+ struct snd_wm8776 *wm, unsigned int addr)
+{
+ static const unsigned short inits_wm8776[] = {
+ 0x02, 0x100, /* R2: headphone L+R muted + update */
+ 0x05, 0x100, /* R5: DAC output L+R muted + update */
+ 0x06, 0x000, /* R6: DAC output phase normal */
+ 0x07, 0x091, /* R7: DAC enable zero cross detection,
+ normal output */
+ 0x08, 0x000, /* R8: DAC soft mute off */
+ 0x09, 0x000, /* R9: no deemph, DAC zero detect disabled */
+ 0x0a, 0x022, /* R10: DAC I2C mode, std polarities, 24bit */
+ 0x0b, 0x022, /* R11: ADC I2C mode, std polarities, 24bit,
+ highpass filter enabled */
+ 0x0c, 0x042, /* R12: ADC+DAC slave, ADC+DAC 44,1kHz */
+ 0x0d, 0x000, /* R13: all power up */
+ 0x0e, 0x100, /* R14: ADC left muted,
+ enable zero cross detection */
+ 0x0f, 0x100, /* R15: ADC right muted,
+ enable zero cross detection */
+ /* R16: ALC...*/
+ 0x11, 0x000, /* R17: disable ALC */
+ /* R18: ALC...*/
+ /* R19: noise gate...*/
+ 0x15, 0x000, /* R21: ADC input mux init, mute all inputs */
+ 0x16, 0x001, /* R22: output mux, select DAC */
+ 0xff, 0xff
+ };
+
+ const unsigned short *ptr;
+ unsigned char reg;
+ unsigned short data;
+
+ wm->addr = addr;
+ /* enable DAC output; mute bypass, aux & all inputs */
+ wm->switch_bits = (1 << WM_SW_DAC);
+
+ ptr = inits_wm8776;
+ while (*ptr != 0xff) {
+ reg = *ptr++;
+ data = *ptr++;
+ wm8776_write(ice, wm, reg, data);
+ }
+}
+
+
+/*
+ * change the rate on the WM8776 codecs.
+ * this assumes that the VT17xx's rate is changed by the calling function.
+ * NOTE: even though the WM8776's are running in slave mode and rate
+ * selection is automatic, we need to call snd_wm8776_set_rate() here
+ * to make sure some flags are set correctly.
+ */
+static void set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+ struct snd_maya44 *chip = ice->spec;
+ unsigned int ratio, adc_ratio, val;
+ int i;
+
+ switch (rate) {
+ case 192000:
+ ratio = WM8776_CLOCK_RATIO_128FS;
+ break;
+ case 176400:
+ ratio = WM8776_CLOCK_RATIO_128FS;
+ break;
+ case 96000:
+ ratio = WM8776_CLOCK_RATIO_256FS;
+ break;
+ case 88200:
+ ratio = WM8776_CLOCK_RATIO_384FS;
+ break;
+ case 48000:
+ ratio = WM8776_CLOCK_RATIO_512FS;
+ break;
+ case 44100:
+ ratio = WM8776_CLOCK_RATIO_512FS;
+ break;
+ case 32000:
+ ratio = WM8776_CLOCK_RATIO_768FS;
+ break;
+ case 0:
+ /* no hint - S/PDIF input is master, simply return */
+ return;
+ default:
+ snd_BUG();
+ return;
+ }
+
+ /*
+ * this currently sets the same rate for ADC and DAC, but limits
+ * ADC rate to 256X (96kHz). For 256X mode (96kHz), this sets ADC
+ * oversampling to 64x, as recommended by WM8776 datasheet.
+ * Setting the rate is not really necessary in slave mode.
+ */
+ adc_ratio = ratio;
+ if (adc_ratio < WM8776_CLOCK_RATIO_256FS)
+ adc_ratio = WM8776_CLOCK_RATIO_256FS;
+
+ val = adc_ratio;
+ if (adc_ratio == WM8776_CLOCK_RATIO_256FS)
+ val |= 8;
+ val |= ratio << 4;
+
+ mutex_lock(&chip->mutex);
+ for (i = 0; i < 2; i++)
+ wm8776_write_bits(ice, &chip->wm[i],
+ WM8776_REG_MASTER_MODE_CONTROL,
+ 0x180, val);
+ mutex_unlock(&chip->mutex);
+}
+
+/*
+ * supported sample rates (to override the default one)
+ */
+
+static const unsigned int rates[] = {
+ 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000
+};
+
+/* playback rates: 32..192 kHz */
+static const struct snd_pcm_hw_constraint_list dac_rates = {
+ .count = ARRAY_SIZE(rates),
+ .list = rates,
+ .mask = 0
+};
+
+
+/*
+ * chip addresses on I2C bus
+ */
+static const unsigned char wm8776_addr[2] = {
+ 0x34, 0x36, /* codec 0 & 1 */
+};
+
+/*
+ * initialize the chip
+ */
+static int maya44_init(struct snd_ice1712 *ice)
+{
+ int i;
+ struct snd_maya44 *chip;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+ mutex_init(&chip->mutex);
+ chip->ice = ice;
+ ice->spec = chip;
+
+ /* initialise codecs */
+ ice->num_total_dacs = 4;
+ ice->num_total_adcs = 4;
+ ice->akm_codecs = 0;
+
+ for (i = 0; i < 2; i++) {
+ wm8776_init(ice, &chip->wm[i], wm8776_addr[i]);
+ wm8776_select_input(chip, i, MAYA_LINE_IN);
+ }
+
+ /* set card specific rates */
+ ice->hw_rates = &dac_rates;
+
+ /* register change rate notifier */
+ ice->gpio.set_pro_rate = set_rate;
+
+ /* RDMA1 (2nd input channel) is used for ADC by default */
+ ice->force_rdma1 = 1;
+
+ /* have an own routing control */
+ ice->own_routing = 1;
+
+ return 0;
+}
+
+
+/*
+ * Maya44 boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static const unsigned char maya44_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x45,
+ /* clock xin1=49.152MHz, mpu401, 2 stereo ADCs+DACs */
+ [ICE_EEP2_ACLINK] = 0x80,
+ /* I2S */
+ [ICE_EEP2_I2S] = 0xf8,
+ /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3,
+ /* enable spdif out, spdif out supp, spdif-in, ext spdif out */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0xff,
+ [ICE_EEP2_GPIO_MASK] = 0/*0x9f*/,
+ [ICE_EEP2_GPIO_MASK1] = 0/*0xff*/,
+ [ICE_EEP2_GPIO_MASK2] = 0/*0x7f*/,
+ [ICE_EEP2_GPIO_STATE] = (1 << GPIO_PHANTOM_OFF) |
+ (1 << GPIO_SPDIF_IN_INV),
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_maya44_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_MAYA44,
+ .name = "ESI Maya44",
+ .model = "maya44",
+ .chip_init = maya44_init,
+ .build_controls = maya44_add_controls,
+ .eeprom_size = sizeof(maya44_eeprom),
+ .eeprom_data = maya44_eeprom,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/maya44.h b/sound/pci/ice1712/maya44.h
new file mode 100644
index 0000000000..f5a97d987a
--- /dev/null
+++ b/sound/pci/ice1712/maya44.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_MAYA44_H
+#define __SOUND_MAYA44_H
+
+#define MAYA44_DEVICE_DESC "{ESI,Maya44},"
+
+#define VT1724_SUBDEVICE_MAYA44 0x34315441 /* Maya44 */
+
+extern struct snd_ice1712_card_info snd_vt1724_maya44_cards[];
+
+#endif /* __SOUND_MAYA44_H */
diff --git a/sound/pci/ice1712/phase.c b/sound/pci/ice1712/phase.c
new file mode 100644
index 0000000000..1e47e46ab8
--- /dev/null
+++ b/sound/pci/ice1712/phase.c
@@ -0,0 +1,950 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1724 (Envy24)
+ *
+ * Lowlevel functions for Terratec PHASE 22
+ *
+ * Copyright (c) 2005 Misha Zhilin <misha@epiphan.com>
+ */
+
+/* PHASE 22 overview:
+ * Audio controller: VIA Envy24HT-S (slightly trimmed down Envy24HT, 4in/4out)
+ * Analog chip: AK4524 (partially via Philip's 74HCT125)
+ * Digital receiver: CS8414-CS (supported in this release)
+ * PHASE 22 revision 2.0 and Terrasoniq/Musonik TS22PCI have CS8416
+ * (support status unknown, please test and report)
+ *
+ * Envy connects to AK4524
+ * - CS directly from GPIO 10
+ * - CCLK via 74HCT125's gate #4 from GPIO 4
+ * - CDTI via 74HCT125's gate #2 from GPIO 5
+ * CDTI may be completely blocked by 74HCT125's gate #1
+ * controlled by GPIO 3
+ */
+
+/* PHASE 28 overview:
+ * Audio controller: VIA Envy24HT (full untrimmed version, 4in/8out)
+ * Analog chip: WM8770 (8 channel 192k DAC, 2 channel 96k ADC)
+ * Digital receiver: CS8414-CS (supported in this release)
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "phase.h"
+#include <sound/tlv.h>
+
+/* AC97 register cache for Phase28 */
+struct phase28_spec {
+ unsigned short master[2];
+ unsigned short vol[8];
+};
+
+/* WM8770 registers */
+#define WM_DAC_ATTEN 0x00 /* DAC1-8 analog attenuation */
+#define WM_DAC_MASTER_ATTEN 0x08 /* DAC master analog attenuation */
+#define WM_DAC_DIG_ATTEN 0x09 /* DAC1-8 digital attenuation */
+#define WM_DAC_DIG_MASTER_ATTEN 0x11 /* DAC master digital attenuation */
+#define WM_PHASE_SWAP 0x12 /* DAC phase */
+#define WM_DAC_CTRL1 0x13 /* DAC control bits */
+#define WM_MUTE 0x14 /* mute controls */
+#define WM_DAC_CTRL2 0x15 /* de-emphasis and zefo-flag */
+#define WM_INT_CTRL 0x16 /* interface control */
+#define WM_MASTER 0x17 /* master clock and mode */
+#define WM_POWERDOWN 0x18 /* power-down controls */
+#define WM_ADC_GAIN 0x19 /* ADC gain L(19)/R(1a) */
+#define WM_ADC_MUX 0x1b /* input MUX */
+#define WM_OUT_MUX1 0x1c /* output MUX */
+#define WM_OUT_MUX2 0x1e /* output MUX */
+#define WM_RESET 0x1f /* software reset */
+
+
+/*
+ * Logarithmic volume values for WM8770
+ * Computed as 20 * Log10(255 / x)
+ */
+static const unsigned char wm_vol[256] = {
+ 127, 48, 42, 39, 36, 34, 33, 31, 30, 29, 28, 27, 27, 26, 25, 25, 24,
+ 24, 23, 23, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 18,
+ 17, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14,
+ 14, 13, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+#define WM_VOL_MAX (sizeof(wm_vol) - 1)
+#define WM_VOL_MUTE 0x8000
+
+static const struct snd_akm4xxx akm_phase22 = {
+ .type = SND_AK4524,
+ .num_dacs = 2,
+ .num_adcs = 2,
+};
+
+static const struct snd_ak4xxx_private akm_phase22_priv = {
+ .caddr = 2,
+ .cif = 1,
+ .data_mask = 1 << 4,
+ .clk_mask = 1 << 5,
+ .cs_mask = 1 << 10,
+ .cs_addr = 1 << 10,
+ .cs_none = 0,
+ .add_flags = 1 << 3,
+ .mask_flags = 0,
+};
+
+static int phase22_init(struct snd_ice1712 *ice)
+{
+ struct snd_akm4xxx *ak;
+ int err;
+
+ /* Configure DAC/ADC description for generic part of ice1724 */
+ switch (ice->eeprom.subvendor) {
+ case VT1724_SUBDEVICE_PHASE22:
+ case VT1724_SUBDEVICE_TS22:
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 2;
+ ice->vt1720 = 1; /* Envy24HT-S have 16 bit wide GPIO */
+ break;
+ default:
+ snd_BUG();
+ return -EINVAL;
+ }
+
+ /* Initialize analog chips */
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ ak = ice->akm;
+ if (!ak)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+ switch (ice->eeprom.subvendor) {
+ case VT1724_SUBDEVICE_PHASE22:
+ case VT1724_SUBDEVICE_TS22:
+ err = snd_ice1712_akm4xxx_init(ak, &akm_phase22,
+ &akm_phase22_priv, ice);
+ if (err < 0)
+ return err;
+ break;
+ }
+
+ return 0;
+}
+
+static int phase22_add_controls(struct snd_ice1712 *ice)
+{
+ int err = 0;
+
+ switch (ice->eeprom.subvendor) {
+ case VT1724_SUBDEVICE_PHASE22:
+ case VT1724_SUBDEVICE_TS22:
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static const unsigned char phase22_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x28, /* clock 512, mpu 401,
+ spdif-in/1xADC, 1xDACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xf0, /* vol, 96k, 24bit */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0xff,
+ [ICE_EEP2_GPIO_MASK] = 0x00,
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0x00,
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+static const unsigned char phase28_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x2b, /* clock 512, mpu401,
+ spdif-in/1xADC, 4xDACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xfc, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x5f,
+ [ICE_EEP2_GPIO_MASK] = 0x00,
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0x00,
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+/*
+ * write data in the SPI mode
+ */
+static void phase28_spi_write(struct snd_ice1712 *ice, unsigned int cs,
+ unsigned int data, int bits)
+{
+ unsigned int tmp;
+ int i;
+
+ tmp = snd_ice1712_gpio_read(ice);
+
+ snd_ice1712_gpio_set_mask(ice, ~(PHASE28_WM_RW|PHASE28_SPI_MOSI|
+ PHASE28_SPI_CLK|PHASE28_WM_CS));
+ tmp |= PHASE28_WM_RW;
+ tmp &= ~cs;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ for (i = bits - 1; i >= 0; i--) {
+ tmp &= ~PHASE28_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ if (data & (1 << i))
+ tmp |= PHASE28_SPI_MOSI;
+ else
+ tmp &= ~PHASE28_SPI_MOSI;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= PHASE28_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ }
+
+ tmp &= ~PHASE28_SPI_CLK;
+ tmp |= cs;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= PHASE28_SPI_CLK;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+}
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+ reg <<= 1;
+ return ((unsigned short)ice->akm[0].images[reg] << 8) |
+ ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ phase28_spi_write(ice, PHASE28_WM_CS, (reg << 9) | (val & 0x1ff), 16);
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ wm_put_nocache(ice, reg, val);
+ reg <<= 1;
+ ice->akm[0].images[reg] = val >> 8;
+ ice->akm[0].images[reg + 1] = val;
+}
+
+static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index,
+ unsigned short vol, unsigned short master)
+{
+ unsigned char nvol;
+
+ if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE))
+ nvol = 0;
+ else
+ nvol = 127 - wm_vol[(((vol & ~WM_VOL_MUTE) *
+ (master & ~WM_VOL_MUTE)) / 127) & WM_VOL_MAX];
+
+ wm_put(ice, index, nvol);
+ wm_put_nocache(ice, index, 0x180 | nvol);
+}
+
+/*
+ * DAC mute control
+ */
+#define wm_pcm_mute_info snd_ctl_boolean_mono_info
+
+static int wm_pcm_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_MUTE) & 0x10) ?
+ 0 : 1;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_pcm_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short nval, oval;
+ int change;
+
+ snd_ice1712_save_gpio_status(ice);
+ oval = wm_get(ice, WM_MUTE);
+ nval = (oval & ~0x10) | (ucontrol->value.integer.value[0] ? 0 : 0x10);
+ change = (nval != oval);
+ if (change)
+ wm_put(ice, WM_MUTE, nval);
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * Master volume attenuation mixer control
+ */
+static int wm_master_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = WM_VOL_MAX;
+ return 0;
+}
+
+static int wm_master_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+ int i;
+ for (i = 0; i < 2; i++)
+ ucontrol->value.integer.value[i] = spec->master[i] &
+ ~WM_VOL_MUTE;
+ return 0;
+}
+
+static int wm_master_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+ int ch, change = 0;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (ch = 0; ch < 2; ch++) {
+ unsigned int vol = ucontrol->value.integer.value[ch];
+ if (vol > WM_VOL_MAX)
+ continue;
+ vol |= spec->master[ch] & WM_VOL_MUTE;
+ if (vol != spec->master[ch]) {
+ int dac;
+ spec->master[ch] = vol;
+ for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+ wm_set_vol(ice, WM_DAC_ATTEN + dac + ch,
+ spec->vol[dac + ch],
+ spec->master[ch]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+static int phase28_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm_inits_phase28[] = {
+ /* These come first to reduce init pop noise */
+ 0x1b, 0x044, /* ADC Mux (AC'97 source) */
+ 0x1c, 0x00B, /* Out Mux1 (VOUT1 = DAC+AUX, VOUT2 = DAC) */
+ 0x1d, 0x009, /* Out Mux2 (VOUT2 = DAC, VOUT3 = DAC) */
+
+ 0x18, 0x000, /* All power-up */
+
+ 0x16, 0x122, /* I2S, normal polarity, 24bit */
+ 0x17, 0x022, /* 256fs, slave mode */
+ 0x00, 0, /* DAC1 analog mute */
+ 0x01, 0, /* DAC2 analog mute */
+ 0x02, 0, /* DAC3 analog mute */
+ 0x03, 0, /* DAC4 analog mute */
+ 0x04, 0, /* DAC5 analog mute */
+ 0x05, 0, /* DAC6 analog mute */
+ 0x06, 0, /* DAC7 analog mute */
+ 0x07, 0, /* DAC8 analog mute */
+ 0x08, 0x100, /* master analog mute */
+ 0x09, 0xff, /* DAC1 digital full */
+ 0x0a, 0xff, /* DAC2 digital full */
+ 0x0b, 0xff, /* DAC3 digital full */
+ 0x0c, 0xff, /* DAC4 digital full */
+ 0x0d, 0xff, /* DAC5 digital full */
+ 0x0e, 0xff, /* DAC6 digital full */
+ 0x0f, 0xff, /* DAC7 digital full */
+ 0x10, 0xff, /* DAC8 digital full */
+ 0x11, 0x1ff, /* master digital full */
+ 0x12, 0x000, /* phase normal */
+ 0x13, 0x090, /* unmute DAC L/R */
+ 0x14, 0x000, /* all unmute */
+ 0x15, 0x000, /* no deemphasis, no ZFLG */
+ 0x19, 0x000, /* -12dB ADC/L */
+ 0x1a, 0x000, /* -12dB ADC/R */
+ (unsigned short)-1
+ };
+
+ unsigned int tmp;
+ struct snd_akm4xxx *ak;
+ struct phase28_spec *spec;
+ const unsigned short *p;
+ int i;
+
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 2;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ /* Initialize analog chips */
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ ak = ice->akm;
+ if (!ak)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ snd_ice1712_gpio_set_dir(ice, 0x5fffff); /* fix this for time being */
+
+ /* reset the wm codec as the SPI mode */
+ snd_ice1712_save_gpio_status(ice);
+ snd_ice1712_gpio_set_mask(ice, ~(PHASE28_WM_RESET|PHASE28_WM_CS|
+ PHASE28_HP_SEL));
+
+ tmp = snd_ice1712_gpio_read(ice);
+ tmp &= ~PHASE28_WM_RESET;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= PHASE28_WM_CS;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ tmp |= PHASE28_WM_RESET;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+
+ p = wm_inits_phase28;
+ for (; *p != (unsigned short)-1; p += 2)
+ wm_put(ice, p[0], p[1]);
+
+ snd_ice1712_restore_gpio_status(ice);
+
+ spec->master[0] = WM_VOL_MUTE;
+ spec->master[1] = WM_VOL_MUTE;
+ for (i = 0; i < ice->num_total_dacs; i++) {
+ spec->vol[i] = WM_VOL_MUTE;
+ wm_set_vol(ice, i, spec->vol[i], spec->master[i % 2]);
+ }
+
+ return 0;
+}
+
+/*
+ * DAC volume attenuation mixer control
+ */
+static int wm_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int voices = kcontrol->private_value >> 8;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = voices;
+ uinfo->value.integer.min = 0; /* mute (-101dB) */
+ uinfo->value.integer.max = 0x7F; /* 0dB */
+ return 0;
+}
+
+static int wm_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+ int i, ofs, voices;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xff;
+ for (i = 0; i < voices; i++)
+ ucontrol->value.integer.value[i] =
+ spec->vol[ofs+i] & ~WM_VOL_MUTE;
+ return 0;
+}
+
+static int wm_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+ int i, idx, ofs, voices;
+ int change = 0;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xff;
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < voices; i++) {
+ unsigned int vol;
+ vol = ucontrol->value.integer.value[i];
+ if (vol > 0x7f)
+ continue;
+ vol |= spec->vol[ofs+i] & WM_VOL_MUTE;
+ if (vol != spec->vol[ofs+i]) {
+ spec->vol[ofs+i] = vol;
+ idx = WM_DAC_ATTEN + ofs + i;
+ wm_set_vol(ice, idx, spec->vol[ofs+i],
+ spec->master[i]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * WM8770 mute control
+ */
+static int wm_mute_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo) {
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = kcontrol->private_value >> 8;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int wm_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+ int voices, ofs, i;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xFF;
+
+ for (i = 0; i < voices; i++)
+ ucontrol->value.integer.value[i] =
+ (spec->vol[ofs+i] & WM_VOL_MUTE) ? 0 : 1;
+ return 0;
+}
+
+static int wm_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+ int change = 0, voices, ofs, i;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xFF;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < voices; i++) {
+ int val = (spec->vol[ofs + i] & WM_VOL_MUTE) ? 0 : 1;
+ if (ucontrol->value.integer.value[i] != val) {
+ spec->vol[ofs + i] &= ~WM_VOL_MUTE;
+ spec->vol[ofs + i] |=
+ ucontrol->value.integer.value[i] ? 0 :
+ WM_VOL_MUTE;
+ wm_set_vol(ice, ofs + i, spec->vol[ofs + i],
+ spec->master[i]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/*
+ * WM8770 master mute control
+ */
+#define wm_master_mute_info snd_ctl_boolean_stereo_info
+
+static int wm_master_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+
+ ucontrol->value.integer.value[0] =
+ (spec->master[0] & WM_VOL_MUTE) ? 0 : 1;
+ ucontrol->value.integer.value[1] =
+ (spec->master[1] & WM_VOL_MUTE) ? 0 : 1;
+ return 0;
+}
+
+static int wm_master_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct phase28_spec *spec = ice->spec;
+ int change = 0, i;
+
+ snd_ice1712_save_gpio_status(ice);
+ for (i = 0; i < 2; i++) {
+ int val = (spec->master[i] & WM_VOL_MUTE) ? 0 : 1;
+ if (ucontrol->value.integer.value[i] != val) {
+ int dac;
+ spec->master[i] &= ~WM_VOL_MUTE;
+ spec->master[i] |=
+ ucontrol->value.integer.value[i] ? 0 :
+ WM_VOL_MUTE;
+ for (dac = 0; dac < ice->num_total_dacs; dac += 2)
+ wm_set_vol(ice, WM_DAC_ATTEN + dac + i,
+ spec->vol[dac + i],
+ spec->master[i]);
+ change = 1;
+ }
+ }
+ snd_ice1712_restore_gpio_status(ice);
+
+ return change;
+}
+
+/* digital master volume */
+#define PCM_0dB 0xff
+#define PCM_RES 128 /* -64dB */
+#define PCM_MIN (PCM_0dB - PCM_RES)
+static int wm_pcm_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0; /* mute (-64dB) */
+ uinfo->value.integer.max = PCM_RES; /* 0dB */
+ return 0;
+}
+
+static int wm_pcm_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+
+ mutex_lock(&ice->gpio_mutex);
+ val = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+ val = val > PCM_MIN ? (val - PCM_MIN) : 0;
+ ucontrol->value.integer.value[0] = val;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_pcm_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int change = 0;
+
+ nvol = ucontrol->value.integer.value[0];
+ if (nvol > PCM_RES)
+ return -EINVAL;
+ snd_ice1712_save_gpio_status(ice);
+ nvol = (nvol ? (nvol + PCM_MIN) : 0) & 0xff;
+ ovol = wm_get(ice, WM_DAC_DIG_MASTER_ATTEN) & 0xff;
+ if (ovol != nvol) {
+ wm_put(ice, WM_DAC_DIG_MASTER_ATTEN, nvol); /* prelatch */
+ /* update */
+ wm_put_nocache(ice, WM_DAC_DIG_MASTER_ATTEN, nvol | 0x100);
+ change = 1;
+ }
+ snd_ice1712_restore_gpio_status(ice);
+ return change;
+}
+
+/*
+ * Deemphasis
+ */
+#define phase28_deemp_info snd_ctl_boolean_mono_info
+
+static int phase28_deemp_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL2) & 0xf) ==
+ 0xf;
+ return 0;
+}
+
+static int phase28_deemp_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int temp, temp2;
+ temp = wm_get(ice, WM_DAC_CTRL2);
+ temp2 = temp;
+ if (ucontrol->value.integer.value[0])
+ temp |= 0xf;
+ else
+ temp &= ~0xf;
+ if (temp != temp2) {
+ wm_put(ice, WM_DAC_CTRL2, temp);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * ADC Oversampling
+ */
+static int phase28_oversampling_info(struct snd_kcontrol *k,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[2] = { "128x", "64x" };
+
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
+}
+
+static int phase28_oversampling_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.enumerated.item[0] = (wm_get(ice, WM_MASTER) & 0x8) ==
+ 0x8;
+ return 0;
+}
+
+static int phase28_oversampling_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int temp, temp2;
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ temp = wm_get(ice, WM_MASTER);
+ temp2 = temp;
+
+ if (ucontrol->value.enumerated.item[0])
+ temp |= 0x8;
+ else
+ temp &= ~0x8;
+
+ if (temp != temp2) {
+ wm_put(ice, WM_MASTER, temp);
+ return 1;
+ }
+ return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -12700, 100, 1);
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_pcm, -6400, 50, 1);
+
+static const struct snd_kcontrol_new phase28_dac_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Switch",
+ .info = wm_master_mute_info,
+ .get = wm_master_mute_get,
+ .put = wm_master_mute_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Master Playback Volume",
+ .info = wm_master_vol_info,
+ .get = wm_master_vol_get,
+ .put = wm_master_vol_put,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Front Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (2 << 8) | 0
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Front Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (2 << 8) | 0,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Rear Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (2 << 8) | 2
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Rear Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (2 << 8) | 2,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Center Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (1 << 8) | 4
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Center Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (1 << 8) | 4,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "LFE Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (1 << 8) | 5
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "LFE Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (1 << 8) | 5,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Side Playback Switch",
+ .info = wm_mute_info,
+ .get = wm_mute_get,
+ .put = wm_mute_put,
+ .private_value = (2 << 8) | 6
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Side Playback Volume",
+ .info = wm_vol_info,
+ .get = wm_vol_get,
+ .put = wm_vol_put,
+ .private_value = (2 << 8) | 6,
+ .tlv = { .p = db_scale_wm_dac }
+ }
+};
+
+static const struct snd_kcontrol_new wm_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Switch",
+ .info = wm_pcm_mute_info,
+ .get = wm_pcm_mute_get,
+ .put = wm_pcm_mute_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "PCM Playback Volume",
+ .info = wm_pcm_vol_info,
+ .get = wm_pcm_vol_get,
+ .put = wm_pcm_vol_put,
+ .tlv = { .p = db_scale_wm_pcm }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DAC Deemphasis Switch",
+ .info = phase28_deemp_info,
+ .get = phase28_deemp_get,
+ .put = phase28_deemp_put
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "ADC Oversampling",
+ .info = phase28_oversampling_info,
+ .get = phase28_oversampling_get,
+ .put = phase28_oversampling_put
+ }
+};
+
+static int phase28_add_controls(struct snd_ice1712 *ice)
+{
+ unsigned int i, counts;
+ int err;
+
+ counts = ARRAY_SIZE(phase28_dac_controls);
+ for (i = 0; i < counts; i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&phase28_dac_controls[i],
+ ice));
+ if (err < 0)
+ return err;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&wm_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+struct snd_ice1712_card_info snd_vt1724_phase_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_PHASE22,
+ .name = "Terratec PHASE 22",
+ .model = "phase22",
+ .chip_init = phase22_init,
+ .build_controls = phase22_add_controls,
+ .eeprom_size = sizeof(phase22_eeprom),
+ .eeprom_data = phase22_eeprom,
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_PHASE28,
+ .name = "Terratec PHASE 28",
+ .model = "phase28",
+ .chip_init = phase28_init,
+ .build_controls = phase28_add_controls,
+ .eeprom_size = sizeof(phase28_eeprom),
+ .eeprom_data = phase28_eeprom,
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_TS22,
+ .name = "Terrasoniq TS22 PCI",
+ .model = "TS22",
+ .chip_init = phase22_init,
+ .build_controls = phase22_add_controls,
+ .eeprom_size = sizeof(phase22_eeprom),
+ .eeprom_data = phase22_eeprom,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/phase.h b/sound/pci/ice1712/phase.h
new file mode 100644
index 0000000000..c019018e2e
--- /dev/null
+++ b/sound/pci/ice1712/phase.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_PHASE_H
+#define __SOUND_PHASE_H
+
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for Terratec PHASE 22
+ *
+ * Copyright (c) 2005 Misha Zhilin <misha@epiphan.com>
+ */
+
+#define PHASE_DEVICE_DESC "{Terratec,Phase 22},"\
+ "{Terratec,Phase 28},"\
+ "{Terrasoniq,TS22},"
+
+#define VT1724_SUBDEVICE_PHASE22 0x3b155011
+#define VT1724_SUBDEVICE_PHASE28 0x3b154911
+#define VT1724_SUBDEVICE_TS22 0x3b157b11
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_vt1724_phase_cards[];
+
+/* PHASE28 GPIO bits */
+#define PHASE28_SPI_MISO (1 << 21)
+#define PHASE28_WM_RESET (1 << 20)
+#define PHASE28_SPI_CLK (1 << 19)
+#define PHASE28_SPI_MOSI (1 << 18)
+#define PHASE28_WM_RW (1 << 17)
+#define PHASE28_AC97_RESET (1 << 16)
+#define PHASE28_DIGITAL_SEL1 (1 << 15)
+#define PHASE28_HP_SEL (1 << 14)
+#define PHASE28_WM_CS (1 << 12)
+#define PHASE28_AC97_COMMIT (1 << 11)
+#define PHASE28_AC97_ADDR (1 << 10)
+#define PHASE28_AC97_DATA_LOW (1 << 9)
+#define PHASE28_AC97_DATA_HIGH (1 << 8)
+#define PHASE28_AC97_DATA_MASK 0xFF
+#endif /* __SOUND_PHASE */
diff --git a/sound/pci/ice1712/pontis.c b/sound/pci/ice1712/pontis.c
new file mode 100644
index 0000000000..683909ca14
--- /dev/null
+++ b/sound/pci/ice1712/pontis.c
@@ -0,0 +1,809 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Pontis MS300
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "pontis.h"
+
+/* I2C addresses */
+#define WM_DEV 0x34
+#define CS_DEV 0x20
+
+/* WM8776 registers */
+#define WM_HP_ATTEN_L 0x00 /* headphone left attenuation */
+#define WM_HP_ATTEN_R 0x01 /* headphone left attenuation */
+#define WM_HP_MASTER 0x02 /* headphone master (both channels) */
+ /* override LLR */
+#define WM_DAC_ATTEN_L 0x03 /* digital left attenuation */
+#define WM_DAC_ATTEN_R 0x04
+#define WM_DAC_MASTER 0x05
+#define WM_PHASE_SWAP 0x06 /* DAC phase swap */
+#define WM_DAC_CTRL1 0x07
+#define WM_DAC_MUTE 0x08
+#define WM_DAC_CTRL2 0x09
+#define WM_DAC_INT 0x0a
+#define WM_ADC_INT 0x0b
+#define WM_MASTER_CTRL 0x0c
+#define WM_POWERDOWN 0x0d
+#define WM_ADC_ATTEN_L 0x0e
+#define WM_ADC_ATTEN_R 0x0f
+#define WM_ALC_CTRL1 0x10
+#define WM_ALC_CTRL2 0x11
+#define WM_ALC_CTRL3 0x12
+#define WM_NOISE_GATE 0x13
+#define WM_LIMITER 0x14
+#define WM_ADC_MUX 0x15
+#define WM_OUT_MUX 0x16
+#define WM_RESET 0x17
+
+/*
+ * GPIO
+ */
+#define PONTIS_CS_CS (1<<4) /* CS */
+#define PONTIS_CS_CLK (1<<5) /* CLK */
+#define PONTIS_CS_RDATA (1<<6) /* CS8416 -> VT1720 */
+#define PONTIS_CS_WDATA (1<<7) /* VT1720 -> CS8416 */
+
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+ reg <<= 1;
+ return ((unsigned short)ice->akm[0].images[reg] << 8) |
+ ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ unsigned short cval;
+ cval = (reg << 9) | val;
+ snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff);
+}
+
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ wm_put_nocache(ice, reg, val);
+ reg <<= 1;
+ ice->akm[0].images[reg] = val >> 8;
+ ice->akm[0].images[reg + 1] = val;
+}
+
+/*
+ * DAC volume attenuation mixer control (-64dB to 0dB)
+ */
+
+#define DAC_0dB 0xff
+#define DAC_RES 128
+#define DAC_MIN (DAC_0dB - DAC_RES)
+
+static int wm_dac_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* mute */
+ uinfo->value.integer.max = DAC_RES; /* 0dB, 0.5dB step */
+ return 0;
+}
+
+static int wm_dac_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+ int i;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ val = wm_get(ice, WM_DAC_ATTEN_L + i) & 0xff;
+ val = val > DAC_MIN ? (val - DAC_MIN) : 0;
+ ucontrol->value.integer.value[i] = val;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short oval, nval;
+ int i, idx, change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ nval = ucontrol->value.integer.value[i];
+ nval = (nval ? (nval + DAC_MIN) : 0) & 0xff;
+ idx = WM_DAC_ATTEN_L + i;
+ oval = wm_get(ice, idx) & 0xff;
+ if (oval != nval) {
+ wm_put(ice, idx, nval);
+ wm_put_nocache(ice, idx, nval | 0x100);
+ change = 1;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * ADC gain mixer control (-64dB to 0dB)
+ */
+
+#define ADC_0dB 0xcf
+#define ADC_RES 128
+#define ADC_MIN (ADC_0dB - ADC_RES)
+
+static int wm_adc_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* mute (-64dB) */
+ uinfo->value.integer.max = ADC_RES; /* 0dB, 0.5dB step */
+ return 0;
+}
+
+static int wm_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+ int i;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ val = wm_get(ice, WM_ADC_ATTEN_L + i) & 0xff;
+ val = val > ADC_MIN ? (val - ADC_MIN) : 0;
+ ucontrol->value.integer.value[i] = val;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int i, idx, change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ nvol = ucontrol->value.integer.value[i];
+ nvol = nvol ? (nvol + ADC_MIN) : 0;
+ idx = WM_ADC_ATTEN_L + i;
+ ovol = wm_get(ice, idx) & 0xff;
+ if (ovol != nvol) {
+ wm_put(ice, idx, nvol);
+ change = 1;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * ADC input mux mixer control
+ */
+#define wm_adc_mux_info snd_ctl_boolean_mono_info
+
+static int wm_adc_mux_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int bit = kcontrol->private_value;
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_mux_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int bit = kcontrol->private_value;
+ unsigned short oval, nval;
+ int change;
+
+ mutex_lock(&ice->gpio_mutex);
+ nval = oval = wm_get(ice, WM_ADC_MUX);
+ if (ucontrol->value.integer.value[0])
+ nval |= (1 << bit);
+ else
+ nval &= ~(1 << bit);
+ change = nval != oval;
+ if (change) {
+ wm_put(ice, WM_ADC_MUX, nval);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * Analog bypass (In -> Out)
+ */
+#define wm_bypass_info snd_ctl_boolean_mono_info
+
+static int wm_bypass_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_bypass_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val, oval;
+ int change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ val = oval = wm_get(ice, WM_OUT_MUX);
+ if (ucontrol->value.integer.value[0])
+ val |= 0x04;
+ else
+ val &= ~0x04;
+ if (val != oval) {
+ wm_put(ice, WM_OUT_MUX, val);
+ change = 1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * Left/Right swap
+ */
+#define wm_chswap_info snd_ctl_boolean_mono_info
+
+static int wm_chswap_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] = (wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_chswap_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val, oval;
+ int change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ oval = wm_get(ice, WM_DAC_CTRL1);
+ val = oval & 0x0f;
+ if (ucontrol->value.integer.value[0])
+ val |= 0x60;
+ else
+ val |= 0x90;
+ if (val != oval) {
+ wm_put(ice, WM_DAC_CTRL1, val);
+ wm_put_nocache(ice, WM_DAC_CTRL1, val);
+ change = 1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * write data in the SPI mode
+ */
+static void set_gpio_bit(struct snd_ice1712 *ice, unsigned int bit, int val)
+{
+ unsigned int tmp = snd_ice1712_gpio_read(ice);
+ if (val)
+ tmp |= bit;
+ else
+ tmp &= ~bit;
+ snd_ice1712_gpio_write(ice, tmp);
+}
+
+static void spi_send_byte(struct snd_ice1712 *ice, unsigned char data)
+{
+ int i;
+ for (i = 0; i < 8; i++) {
+ set_gpio_bit(ice, PONTIS_CS_CLK, 0);
+ udelay(1);
+ set_gpio_bit(ice, PONTIS_CS_WDATA, data & 0x80);
+ udelay(1);
+ set_gpio_bit(ice, PONTIS_CS_CLK, 1);
+ udelay(1);
+ data <<= 1;
+ }
+}
+
+static unsigned int spi_read_byte(struct snd_ice1712 *ice)
+{
+ int i;
+ unsigned int val = 0;
+
+ for (i = 0; i < 8; i++) {
+ val <<= 1;
+ set_gpio_bit(ice, PONTIS_CS_CLK, 0);
+ udelay(1);
+ if (snd_ice1712_gpio_read(ice) & PONTIS_CS_RDATA)
+ val |= 1;
+ udelay(1);
+ set_gpio_bit(ice, PONTIS_CS_CLK, 1);
+ udelay(1);
+ }
+ return val;
+}
+
+
+static void spi_write(struct snd_ice1712 *ice, unsigned int dev, unsigned int reg, unsigned int data)
+{
+ snd_ice1712_gpio_set_dir(ice, PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK);
+ snd_ice1712_gpio_set_mask(ice, ~(PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK));
+ set_gpio_bit(ice, PONTIS_CS_CS, 0);
+ spi_send_byte(ice, dev & ~1); /* WRITE */
+ spi_send_byte(ice, reg); /* MAP */
+ spi_send_byte(ice, data); /* DATA */
+ /* trigger */
+ set_gpio_bit(ice, PONTIS_CS_CS, 1);
+ udelay(1);
+ /* restore */
+ snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+}
+
+static unsigned int spi_read(struct snd_ice1712 *ice, unsigned int dev, unsigned int reg)
+{
+ unsigned int val;
+ snd_ice1712_gpio_set_dir(ice, PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK);
+ snd_ice1712_gpio_set_mask(ice, ~(PONTIS_CS_CS|PONTIS_CS_WDATA|PONTIS_CS_CLK));
+ set_gpio_bit(ice, PONTIS_CS_CS, 0);
+ spi_send_byte(ice, dev & ~1); /* WRITE */
+ spi_send_byte(ice, reg); /* MAP */
+ /* trigger */
+ set_gpio_bit(ice, PONTIS_CS_CS, 1);
+ udelay(1);
+ set_gpio_bit(ice, PONTIS_CS_CS, 0);
+ spi_send_byte(ice, dev | 1); /* READ */
+ val = spi_read_byte(ice);
+ /* trigger */
+ set_gpio_bit(ice, PONTIS_CS_CS, 1);
+ udelay(1);
+ /* restore */
+ snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+ return val;
+}
+
+
+/*
+ * SPDIF input source
+ */
+static int cs_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[] = {
+ "Coax", /* RXP0 */
+ "Optical", /* RXP1 */
+ "CD", /* RXP2 */
+ };
+ return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int cs_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.enumerated.item[0] = ice->gpio.saved[0];
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int cs_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+ int change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ if (ucontrol->value.enumerated.item[0] != ice->gpio.saved[0]) {
+ ice->gpio.saved[0] = ucontrol->value.enumerated.item[0] & 3;
+ val = 0x80 | (ice->gpio.saved[0] << 3);
+ spi_write(ice, CS_DEV, 0x04, val);
+ change = 1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+
+/*
+ * GPIO controls
+ */
+static int pontis_gpio_mask_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 0xffff; /* 16bit */
+ return 0;
+}
+
+static int pontis_gpio_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ mutex_lock(&ice->gpio_mutex);
+ /* 4-7 reserved */
+ ucontrol->value.integer.value[0] = (~ice->gpio.write_mask & 0xffff) | 0x00f0;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int pontis_gpio_mask_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ int changed;
+ mutex_lock(&ice->gpio_mutex);
+ /* 4-7 reserved */
+ val = (~ucontrol->value.integer.value[0] & 0xffff) | 0x00f0;
+ changed = val != ice->gpio.write_mask;
+ ice->gpio.write_mask = val;
+ mutex_unlock(&ice->gpio_mutex);
+ return changed;
+}
+
+static int pontis_gpio_dir_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ mutex_lock(&ice->gpio_mutex);
+ /* 4-7 reserved */
+ ucontrol->value.integer.value[0] = ice->gpio.direction & 0xff0f;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int pontis_gpio_dir_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ int changed;
+ mutex_lock(&ice->gpio_mutex);
+ /* 4-7 reserved */
+ val = ucontrol->value.integer.value[0] & 0xff0f;
+ changed = (val != ice->gpio.direction);
+ ice->gpio.direction = val;
+ mutex_unlock(&ice->gpio_mutex);
+ return changed;
+}
+
+static int pontis_gpio_data_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ mutex_lock(&ice->gpio_mutex);
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+ snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+ ucontrol->value.integer.value[0] = snd_ice1712_gpio_read(ice) & 0xffff;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int pontis_gpio_data_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val, nval;
+ int changed = 0;
+ mutex_lock(&ice->gpio_mutex);
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+ snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+ val = snd_ice1712_gpio_read(ice) & 0xffff;
+ nval = ucontrol->value.integer.value[0] & 0xffff;
+ if (val != nval) {
+ snd_ice1712_gpio_write(ice, nval);
+ changed = 1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return changed;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_volume, -6400, 50, 1);
+
+/*
+ * mixers
+ */
+
+static const struct snd_kcontrol_new pontis_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "PCM Playback Volume",
+ .info = wm_dac_vol_info,
+ .get = wm_dac_vol_get,
+ .put = wm_dac_vol_put,
+ .tlv = { .p = db_scale_volume },
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Capture Volume",
+ .info = wm_adc_vol_info,
+ .get = wm_adc_vol_get,
+ .put = wm_adc_vol_put,
+ .tlv = { .p = db_scale_volume },
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "CD Capture Switch",
+ .info = wm_adc_mux_info,
+ .get = wm_adc_mux_get,
+ .put = wm_adc_mux_put,
+ .private_value = 0,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line Capture Switch",
+ .info = wm_adc_mux_info,
+ .get = wm_adc_mux_get,
+ .put = wm_adc_mux_put,
+ .private_value = 1,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Bypass Switch",
+ .info = wm_bypass_info,
+ .get = wm_bypass_get,
+ .put = wm_bypass_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Swap Output Channels",
+ .info = wm_chswap_info,
+ .get = wm_chswap_get,
+ .put = wm_chswap_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "IEC958 Input Source",
+ .info = cs_source_info,
+ .get = cs_source_get,
+ .put = cs_source_put,
+ },
+ /* FIXME: which interface? */
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "GPIO Mask",
+ .info = pontis_gpio_mask_info,
+ .get = pontis_gpio_mask_get,
+ .put = pontis_gpio_mask_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "GPIO Direction",
+ .info = pontis_gpio_mask_info,
+ .get = pontis_gpio_dir_get,
+ .put = pontis_gpio_dir_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_CARD,
+ .name = "GPIO Data",
+ .info = pontis_gpio_mask_info,
+ .get = pontis_gpio_data_get,
+ .put = pontis_gpio_data_put,
+ },
+};
+
+
+/*
+ * WM codec registers
+ */
+static void wm_proc_regs_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ char line[64];
+ unsigned int reg, val;
+ mutex_lock(&ice->gpio_mutex);
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ if (reg <= 0x17 && val <= 0xffff)
+ wm_put(ice, reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_regs_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ int reg, val;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (reg = 0; reg <= 0x17; reg++) {
+ val = wm_get(ice, reg);
+ snd_iprintf(buffer, "%02x = %04x\n", reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_init(struct snd_ice1712 *ice)
+{
+ snd_card_rw_proc_new(ice->card, "wm_codec", ice, wm_proc_regs_read,
+ wm_proc_regs_write);
+}
+
+static void cs_proc_regs_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ int reg, val;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (reg = 0; reg <= 0x26; reg++) {
+ val = spi_read(ice, CS_DEV, reg);
+ snd_iprintf(buffer, "%02x = %02x\n", reg, val);
+ }
+ val = spi_read(ice, CS_DEV, 0x7f);
+ snd_iprintf(buffer, "%02x = %02x\n", 0x7f, val);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void cs_proc_init(struct snd_ice1712 *ice)
+{
+ snd_card_ro_proc_new(ice->card, "cs_codec", ice, cs_proc_regs_read);
+}
+
+
+static int pontis_add_controls(struct snd_ice1712 *ice)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(pontis_controls); i++) {
+ err = snd_ctl_add(ice->card, snd_ctl_new1(&pontis_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+
+ wm_proc_init(ice);
+ cs_proc_init(ice);
+
+ return 0;
+}
+
+
+/*
+ * initialize the chip
+ */
+static int pontis_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm_inits[] = {
+ /* These come first to reduce init pop noise */
+ WM_ADC_MUX, 0x00c0, /* ADC mute */
+ WM_DAC_MUTE, 0x0001, /* DAC softmute */
+ WM_DAC_CTRL1, 0x0000, /* DAC mute */
+
+ WM_POWERDOWN, 0x0008, /* All power-up except HP */
+ WM_RESET, 0x0000, /* reset */
+ };
+ static const unsigned short wm_inits2[] = {
+ WM_MASTER_CTRL, 0x0022, /* 256fs, slave mode */
+ WM_DAC_INT, 0x0022, /* I2S, normal polarity, 24bit */
+ WM_ADC_INT, 0x0022, /* I2S, normal polarity, 24bit */
+ WM_DAC_CTRL1, 0x0090, /* DAC L/R */
+ WM_OUT_MUX, 0x0001, /* OUT DAC */
+ WM_HP_ATTEN_L, 0x0179, /* HP 0dB */
+ WM_HP_ATTEN_R, 0x0179, /* HP 0dB */
+ WM_DAC_ATTEN_L, 0x0000, /* DAC 0dB */
+ WM_DAC_ATTEN_L, 0x0100, /* DAC 0dB */
+ WM_DAC_ATTEN_R, 0x0000, /* DAC 0dB */
+ WM_DAC_ATTEN_R, 0x0100, /* DAC 0dB */
+ /* WM_DAC_MASTER, 0x0100, */ /* DAC master muted */
+ WM_PHASE_SWAP, 0x0000, /* phase normal */
+ WM_DAC_CTRL2, 0x0000, /* no deemphasis, no ZFLG */
+ WM_ADC_ATTEN_L, 0x0000, /* ADC muted */
+ WM_ADC_ATTEN_R, 0x0000, /* ADC muted */
+#if 0
+ WM_ALC_CTRL1, 0x007b, /* */
+ WM_ALC_CTRL2, 0x0000, /* */
+ WM_ALC_CTRL3, 0x0000, /* */
+ WM_NOISE_GATE, 0x0000, /* */
+#endif
+ WM_DAC_MUTE, 0x0000, /* DAC unmute */
+ WM_ADC_MUX, 0x0003, /* ADC unmute, both CD/Line On */
+ };
+ static const unsigned char cs_inits[] = {
+ 0x04, 0x80, /* RUN, RXP0 */
+ 0x05, 0x05, /* slave, 24bit */
+ 0x01, 0x00,
+ 0x02, 0x00,
+ 0x03, 0x00,
+ };
+ unsigned int i;
+
+ ice->vt1720 = 1;
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 2;
+
+ /* to remember the register values */
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ice->akm)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ /* HACK - use this as the SPDIF source.
+ * don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten
+ */
+ ice->gpio.saved[0] = 0;
+
+ /* initialize WM8776 codec */
+ for (i = 0; i < ARRAY_SIZE(wm_inits); i += 2)
+ wm_put(ice, wm_inits[i], wm_inits[i+1]);
+ schedule_timeout_uninterruptible(1);
+ for (i = 0; i < ARRAY_SIZE(wm_inits2); i += 2)
+ wm_put(ice, wm_inits2[i], wm_inits2[i+1]);
+
+ /* initialize CS8416 codec */
+ /* assert PRST#; MT05 bit 7 */
+ outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
+ mdelay(5);
+ /* deassert PRST# */
+ outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
+
+ for (i = 0; i < ARRAY_SIZE(cs_inits); i += 2)
+ spi_write(ice, CS_DEV, cs_inits[i], cs_inits[i+1]);
+
+ return 0;
+}
+
+
+/*
+ * Pontis boards don't provide the EEPROM data at all.
+ * hence the driver needs to sets up it properly.
+ */
+
+static const unsigned char pontis_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x08, /* clock 256, mpu401, spdif-in/ADC, 1DAC */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xf8, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0x07,
+ [ICE_EEP2_GPIO_DIR1] = 0x00,
+ [ICE_EEP2_GPIO_DIR2] = 0x00, /* ignored */
+ [ICE_EEP2_GPIO_MASK] = 0x0f, /* 4-7 reserved for CS8416 */
+ [ICE_EEP2_GPIO_MASK1] = 0xff,
+ [ICE_EEP2_GPIO_MASK2] = 0x00, /* ignored */
+ [ICE_EEP2_GPIO_STATE] = 0x06, /* 0-low, 1-high, 2-high */
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00, /* ignored */
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1720_pontis_cards[] = {
+ {
+ .subvendor = VT1720_SUBDEVICE_PONTIS_MS300,
+ .name = "Pontis MS300",
+ .model = "ms300",
+ .chip_init = pontis_init,
+ .build_controls = pontis_add_controls,
+ .eeprom_size = sizeof(pontis_eeprom),
+ .eeprom_data = pontis_eeprom,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/pontis.h b/sound/pci/ice1712/pontis.h
new file mode 100644
index 0000000000..bba01aeac7
--- /dev/null
+++ b/sound/pci/ice1712/pontis.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_PONTIS_H
+#define __SOUND_PONTIS_H
+
+/*
+ * ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Pontis MS300 boards
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#define PONTIS_DEVICE_DESC "{Pontis,MS300},"
+
+#define VT1720_SUBDEVICE_PONTIS_MS300 0x00020002 /* a dummy id for MS300 */
+
+extern struct snd_ice1712_card_info snd_vt1720_pontis_cards[];
+
+#endif /* __SOUND_PONTIS_H */
diff --git a/sound/pci/ice1712/prodigy192.c b/sound/pci/ice1712/prodigy192.c
new file mode 100644
index 0000000000..096ec76f53
--- /dev/null
+++ b/sound/pci/ice1712/prodigy192.c
@@ -0,0 +1,791 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for AudioTrak Prodigy 192 cards
+ * Supported IEC958 input from optional MI/ODI/O add-on card.
+ *
+ * Specifics (SW, HW):
+ * -------------------
+ * * 49.5MHz crystal
+ * * SPDIF-OUT on the card:
+ * - coax (through isolation transformer)/toslink supplied by
+ * 74HC04 gates - 3 in parallel
+ * - output switched between on-board CD drive dig-out connector
+ * and ice1724 SPDTX pin, using 74HC02 NOR gates, controlled
+ * by GPIO20 (0 = CD dig-out, 1 = SPDTX)
+ * * SPDTX goes straight to MI/ODI/O card's SPDIF-OUT coax
+ *
+ * * MI/ODI/O card: AK4114 based, used for iec958 input only
+ * - toslink input -> RX0
+ * - coax input -> RX1
+ * - 4wire protocol:
+ * AK4114 ICE1724
+ * ------------------------------
+ * CDTO (pin 32) -- GPIO11 pin 86
+ * CDTI (pin 33) -- GPIO10 pin 77
+ * CCLK (pin 34) -- GPIO9 pin 76
+ * CSN (pin 35) -- GPIO8 pin 75
+ * - output data Mode 7 (24bit, I2S, slave)
+ * - both MCKO1 and MCKO2 of ak4114 are fed to FPGA, which
+ * outputs master clock to SPMCLKIN of ice1724.
+ * Experimentally I found out that only a combination of
+ * OCKS0=1, OCKS1=1 (128fs, 64fs output) and ice1724 -
+ * VT1724_MT_I2S_MCLK_128X=0 (256fs input) yields correct
+ * sampling rate. That means that the FPGA doubles the
+ * MCK01 rate.
+ *
+ * Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
+ * Copyright (c) 2003 Dimitromanolakis Apostolos <apostol@cs.utoronto.ca>
+ * Copyright (c) 2004 Kouichi ONO <co2b@ceres.dti.ne.jp>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "prodigy192.h"
+#include "stac946x.h"
+#include <sound/tlv.h>
+
+struct prodigy192_spec {
+ struct ak4114 *ak4114;
+ /* rate change needs atomic mute/unmute of all dacs*/
+ struct mutex mute_mutex;
+};
+
+static inline void stac9460_put(struct snd_ice1712 *ice, int reg, unsigned char val)
+{
+ snd_vt1724_write_i2c(ice, PRODIGY192_STAC9460_ADDR, reg, val);
+}
+
+static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg)
+{
+ return snd_vt1724_read_i2c(ice, PRODIGY192_STAC9460_ADDR, reg);
+}
+
+/*
+ * DAC mute control
+ */
+
+/*
+ * idx = STAC9460 volume register number, mute: 0 = mute, 1 = unmute
+ */
+static int stac9460_dac_mute(struct snd_ice1712 *ice, int idx,
+ unsigned char mute)
+{
+ unsigned char new, old;
+ int change;
+ old = stac9460_get(ice, idx);
+ new = (~mute << 7 & 0x80) | (old & ~0x80);
+ change = (new != old);
+ if (change)
+ /* dev_dbg(ice->card->dev, "Volume register 0x%02x: 0x%02x\n", idx, new);*/
+ stac9460_put(ice, idx, new);
+ return change;
+}
+
+#define stac9460_dac_mute_info snd_ctl_boolean_mono_info
+
+static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+ int idx;
+
+ if (kcontrol->private_value)
+ idx = STAC946X_MASTER_VOLUME;
+ else
+ idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+ val = stac9460_get(ice, idx);
+ ucontrol->value.integer.value[0] = (~val >> 7) & 0x1;
+ return 0;
+}
+
+static int stac9460_dac_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy192_spec *spec = ice->spec;
+ int idx, change;
+
+ if (kcontrol->private_value)
+ idx = STAC946X_MASTER_VOLUME;
+ else
+ idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+ /* due to possible conflicts with stac9460_set_rate_val, mutexing */
+ mutex_lock(&spec->mute_mutex);
+ /*
+ dev_dbg(ice->card->dev, "Mute put: reg 0x%02x, ctrl value: 0x%02x\n", idx,
+ ucontrol->value.integer.value[0]);
+ */
+ change = stac9460_dac_mute(ice, idx, ucontrol->value.integer.value[0]);
+ mutex_unlock(&spec->mute_mutex);
+ return change;
+}
+
+/*
+ * DAC volume attenuation mixer control
+ */
+static int stac9460_dac_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0; /* mute */
+ uinfo->value.integer.max = 0x7f; /* 0dB */
+ return 0;
+}
+
+static int stac9460_dac_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx;
+ unsigned char vol;
+
+ if (kcontrol->private_value)
+ idx = STAC946X_MASTER_VOLUME;
+ else
+ idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+ vol = stac9460_get(ice, idx) & 0x7f;
+ ucontrol->value.integer.value[0] = 0x7f - vol;
+
+ return 0;
+}
+
+static int stac9460_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx;
+ unsigned char tmp, ovol, nvol;
+ int change;
+
+ if (kcontrol->private_value)
+ idx = STAC946X_MASTER_VOLUME;
+ else
+ idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + STAC946X_LF_VOLUME;
+ nvol = ucontrol->value.integer.value[0];
+ tmp = stac9460_get(ice, idx);
+ ovol = 0x7f - (tmp & 0x7f);
+ change = (ovol != nvol);
+ if (change) {
+ ovol = (0x7f - nvol) | (tmp & 0x80);
+ /*
+ dev_dbg(ice->card->dev, "DAC Volume: reg 0x%02x: 0x%02x\n",
+ idx, ovol);
+ */
+ stac9460_put(ice, idx, (0x7f - nvol) | (tmp & 0x80));
+ }
+ return change;
+}
+
+/*
+ * ADC mute control
+ */
+#define stac9460_adc_mute_info snd_ctl_boolean_stereo_info
+
+static int stac9460_adc_mute_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+ int i;
+
+ for (i = 0; i < 2; ++i) {
+ val = stac9460_get(ice, STAC946X_MIC_L_VOLUME + i);
+ ucontrol->value.integer.value[i] = ~val>>7 & 0x1;
+ }
+
+ return 0;
+}
+
+static int stac9460_adc_mute_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char new, old;
+ int i, reg;
+ int change;
+
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ old = stac9460_get(ice, reg);
+ new = (~ucontrol->value.integer.value[i]<<7&0x80) | (old&~0x80);
+ change = (new != old);
+ if (change)
+ stac9460_put(ice, reg, new);
+ }
+
+ return change;
+}
+
+/*
+ * ADC gain mixer control
+ */
+static int stac9460_adc_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* 0dB */
+ uinfo->value.integer.max = 0x0f; /* 22.5dB */
+ return 0;
+}
+
+static int stac9460_adc_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int i, reg;
+ unsigned char vol;
+
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ vol = stac9460_get(ice, reg) & 0x0f;
+ ucontrol->value.integer.value[i] = 0x0f - vol;
+ }
+
+ return 0;
+}
+
+static int stac9460_adc_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int i, reg;
+ unsigned char ovol, nvol;
+ int change;
+
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ nvol = ucontrol->value.integer.value[i] & 0x0f;
+ ovol = 0x0f - stac9460_get(ice, reg);
+ change = ((ovol & 0x0f) != nvol);
+ if (change)
+ stac9460_put(ice, reg, (0x0f - nvol) | (ovol & ~0x0f));
+ }
+
+ return change;
+}
+
+static int stac9460_mic_sw_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[2] = { "Line In", "Mic" };
+
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
+}
+
+
+static int stac9460_mic_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+
+ val = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+ ucontrol->value.enumerated.item[0] = (val >> 7) & 0x1;
+ return 0;
+}
+
+static int stac9460_mic_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char new, old;
+ int change;
+ old = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+ new = (ucontrol->value.enumerated.item[0] << 7 & 0x80) | (old & ~0x80);
+ change = (new != old);
+ if (change)
+ stac9460_put(ice, STAC946X_GENERAL_PURPOSE, new);
+ return change;
+}
+/*
+ * Handler for setting correct codec rate - called when rate change is detected
+ */
+static void stac9460_set_rate_val(struct snd_ice1712 *ice, unsigned int rate)
+{
+ unsigned char old, new;
+ int idx;
+ unsigned char changed[7];
+ struct prodigy192_spec *spec = ice->spec;
+
+ if (rate == 0) /* no hint - S/PDIF input is master, simply return */
+ return;
+ else if (rate <= 48000)
+ new = 0x08; /* 256x, base rate mode */
+ else if (rate <= 96000)
+ new = 0x11; /* 256x, mid rate mode */
+ else
+ new = 0x12; /* 128x, high rate mode */
+ old = stac9460_get(ice, STAC946X_MASTER_CLOCKING);
+ if (old == new)
+ return;
+ /* change detected, setting master clock, muting first */
+ /* due to possible conflicts with mute controls - mutexing */
+ mutex_lock(&spec->mute_mutex);
+ /* we have to remember current mute status for each DAC */
+ for (idx = 0; idx < 7 ; ++idx)
+ changed[idx] = stac9460_dac_mute(ice,
+ STAC946X_MASTER_VOLUME + idx, 0);
+ /*dev_dbg(ice->card->dev, "Rate change: %d, new MC: 0x%02x\n", rate, new);*/
+ stac9460_put(ice, STAC946X_MASTER_CLOCKING, new);
+ udelay(10);
+ /* unmuting - only originally unmuted dacs -
+ * i.e. those changed when muting */
+ for (idx = 0; idx < 7 ; ++idx) {
+ if (changed[idx])
+ stac9460_dac_mute(ice, STAC946X_MASTER_VOLUME + idx, 1);
+ }
+ mutex_unlock(&spec->mute_mutex);
+}
+
+
+static const DECLARE_TLV_DB_SCALE(db_scale_dac, -19125, 75, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_adc, 0, 150, 0);
+
+/*
+ * mixers
+ */
+
+static const struct snd_kcontrol_new stac_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Switch",
+ .info = stac9460_dac_mute_info,
+ .get = stac9460_dac_mute_get,
+ .put = stac9460_dac_mute_put,
+ .private_value = 1,
+ .tlv = { .p = db_scale_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Master Playback Volume",
+ .info = stac9460_dac_vol_info,
+ .get = stac9460_dac_vol_get,
+ .put = stac9460_dac_vol_put,
+ .private_value = 1,
+ .tlv = { .p = db_scale_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DAC Switch",
+ .count = 6,
+ .info = stac9460_dac_mute_info,
+ .get = stac9460_dac_mute_get,
+ .put = stac9460_dac_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "DAC Volume",
+ .count = 6,
+ .info = stac9460_dac_vol_info,
+ .get = stac9460_dac_vol_get,
+ .put = stac9460_dac_vol_put,
+ .tlv = { .p = db_scale_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "ADC Capture Switch",
+ .count = 1,
+ .info = stac9460_adc_mute_info,
+ .get = stac9460_adc_mute_get,
+ .put = stac9460_adc_mute_put,
+
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "ADC Capture Volume",
+ .count = 1,
+ .info = stac9460_adc_vol_info,
+ .get = stac9460_adc_vol_get,
+ .put = stac9460_adc_vol_put,
+ .tlv = { .p = db_scale_adc }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Capture Input",
+ .info = stac9460_mic_sw_info,
+ .get = stac9460_mic_sw_get,
+ .put = stac9460_mic_sw_put,
+
+ },
+};
+
+/* AK4114 - ICE1724 connections on Prodigy192 + MI/ODI/O */
+/* CDTO (pin 32) -- GPIO11 pin 86
+ * CDTI (pin 33) -- GPIO10 pin 77
+ * CCLK (pin 34) -- GPIO9 pin 76
+ * CSN (pin 35) -- GPIO8 pin 75
+ */
+#define AK4114_ADDR 0x00 /* C1-C0: Chip Address
+ * (According to datasheet fixed to “00”)
+ */
+
+/*
+ * 4wire ak4114 protocol - writing data
+ */
+static void write_data(struct snd_ice1712 *ice, unsigned int gpio,
+ unsigned int data, int idx)
+{
+ for (; idx >= 0; idx--) {
+ /* drop clock */
+ gpio &= ~VT1724_PRODIGY192_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ /* set data */
+ if (data & (1 << idx))
+ gpio |= VT1724_PRODIGY192_CDOUT;
+ else
+ gpio &= ~VT1724_PRODIGY192_CDOUT;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ /* raise clock */
+ gpio |= VT1724_PRODIGY192_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ }
+}
+
+/*
+ * 4wire ak4114 protocol - reading data
+ */
+static unsigned char read_data(struct snd_ice1712 *ice, unsigned int gpio,
+ int idx)
+{
+ unsigned char data = 0;
+
+ for (; idx >= 0; idx--) {
+ /* drop clock */
+ gpio &= ~VT1724_PRODIGY192_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ /* read data */
+ if (snd_ice1712_gpio_read(ice) & VT1724_PRODIGY192_CDIN)
+ data |= (1 << idx);
+ udelay(1);
+ /* raise clock */
+ gpio |= VT1724_PRODIGY192_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ }
+ return data;
+}
+/*
+ * 4wire ak4114 protocol - starting sequence
+ */
+static unsigned int prodigy192_4wire_start(struct snd_ice1712 *ice)
+{
+ unsigned int tmp;
+
+ snd_ice1712_save_gpio_status(ice);
+ tmp = snd_ice1712_gpio_read(ice);
+
+ tmp |= VT1724_PRODIGY192_CCLK; /* high at init */
+ tmp &= ~VT1724_PRODIGY192_CS; /* drop chip select */
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ return tmp;
+}
+
+/*
+ * 4wire ak4114 protocol - final sequence
+ */
+static void prodigy192_4wire_finish(struct snd_ice1712 *ice, unsigned int tmp)
+{
+ tmp |= VT1724_PRODIGY192_CS; /* raise chip select */
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ snd_ice1712_restore_gpio_status(ice);
+}
+
+/*
+ * Write data to addr register of ak4114
+ */
+static void prodigy192_ak4114_write(void *private_data, unsigned char addr,
+ unsigned char data)
+{
+ struct snd_ice1712 *ice = private_data;
+ unsigned int tmp, addrdata;
+ tmp = prodigy192_4wire_start(ice);
+ addrdata = (AK4114_ADDR << 6) | 0x20 | (addr & 0x1f);
+ addrdata = (addrdata << 8) | data;
+ write_data(ice, tmp, addrdata, 15);
+ prodigy192_4wire_finish(ice, tmp);
+}
+
+/*
+ * Read data from addr register of ak4114
+ */
+static unsigned char prodigy192_ak4114_read(void *private_data,
+ unsigned char addr)
+{
+ struct snd_ice1712 *ice = private_data;
+ unsigned int tmp;
+ unsigned char data;
+
+ tmp = prodigy192_4wire_start(ice);
+ write_data(ice, tmp, (AK4114_ADDR << 6) | (addr & 0x1f), 7);
+ data = read_data(ice, tmp, 7);
+ prodigy192_4wire_finish(ice, tmp);
+ return data;
+}
+
+
+static int ak4114_input_sw_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[2] = { "Toslink", "Coax" };
+
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
+}
+
+
+static int ak4114_input_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+
+ val = prodigy192_ak4114_read(ice, AK4114_REG_IO1);
+ /* AK4114_IPS0 bit = 0 -> RX0 = Toslink
+ * AK4114_IPS0 bit = 1 -> RX1 = Coax
+ */
+ ucontrol->value.enumerated.item[0] = (val & AK4114_IPS0) ? 1 : 0;
+ return 0;
+}
+
+static int ak4114_input_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char new, old, itemvalue;
+ int change;
+
+ old = prodigy192_ak4114_read(ice, AK4114_REG_IO1);
+ /* AK4114_IPS0 could be any bit */
+ itemvalue = (ucontrol->value.enumerated.item[0]) ? 0xff : 0x00;
+
+ new = (itemvalue & AK4114_IPS0) | (old & ~AK4114_IPS0);
+ change = (new != old);
+ if (change)
+ prodigy192_ak4114_write(ice, AK4114_REG_IO1, new);
+ return change;
+}
+
+
+static const struct snd_kcontrol_new ak4114_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "MIODIO IEC958 Capture Input",
+ .info = ak4114_input_sw_info,
+ .get = ak4114_input_sw_get,
+ .put = ak4114_input_sw_put,
+
+ }
+};
+
+
+static int prodigy192_ak4114_init(struct snd_ice1712 *ice)
+{
+ static const unsigned char ak4114_init_vals[] = {
+ AK4114_RST | AK4114_PWN | AK4114_OCKS0 | AK4114_OCKS1,
+ /* ice1724 expects I2S and provides clock,
+ * DEM0 disables the deemphasis filter
+ */
+ AK4114_DIF_I24I2S | AK4114_DEM0 ,
+ AK4114_TX1E,
+ AK4114_EFH_1024 | AK4114_DIT, /* default input RX0 */
+ 0,
+ 0
+ };
+ static const unsigned char ak4114_init_txcsb[] = {
+ 0x41, 0x02, 0x2c, 0x00, 0x00
+ };
+ struct prodigy192_spec *spec = ice->spec;
+ int err;
+
+ err = snd_ak4114_create(ice->card,
+ prodigy192_ak4114_read,
+ prodigy192_ak4114_write,
+ ak4114_init_vals, ak4114_init_txcsb,
+ ice, &spec->ak4114);
+ if (err < 0)
+ return err;
+ /* AK4114 in Prodigy192 cannot detect external rate correctly.
+ * No reason to stop capture stream due to incorrect checks */
+ spec->ak4114->check_flags = AK4114_CHECK_NO_RATE;
+ return 0;
+}
+
+static void stac9460_proc_regs_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ int reg, val;
+ /* registers 0x0 - 0x14 */
+ for (reg = 0; reg <= 0x15; reg++) {
+ val = stac9460_get(ice, reg);
+ snd_iprintf(buffer, "0x%02x = 0x%02x\n", reg, val);
+ }
+}
+
+
+static void stac9460_proc_init(struct snd_ice1712 *ice)
+{
+ snd_card_ro_proc_new(ice->card, "stac9460_codec", ice,
+ stac9460_proc_regs_read);
+}
+
+
+static int prodigy192_add_controls(struct snd_ice1712 *ice)
+{
+ struct prodigy192_spec *spec = ice->spec;
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(stac_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&stac_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+ if (spec->ak4114) {
+ /* ak4114 is connected */
+ for (i = 0; i < ARRAY_SIZE(ak4114_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&ak4114_controls[i],
+ ice));
+ if (err < 0)
+ return err;
+ }
+ err = snd_ak4114_build(spec->ak4114,
+ NULL, /* ak4114 in MIO/DI/O handles no IEC958 output */
+ ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+ if (err < 0)
+ return err;
+ }
+ stac9460_proc_init(ice);
+ return 0;
+}
+
+/*
+ * check for presence of MI/ODI/O add-on card with digital inputs
+ */
+static int prodigy192_miodio_exists(struct snd_ice1712 *ice)
+{
+
+ unsigned char orig_value;
+ const unsigned char test_data = 0xd1; /* random value */
+ unsigned char addr = AK4114_REG_INT0_MASK; /* random SAFE address */
+ int exists = 0;
+
+ orig_value = prodigy192_ak4114_read(ice, addr);
+ prodigy192_ak4114_write(ice, addr, test_data);
+ if (prodigy192_ak4114_read(ice, addr) == test_data) {
+ /* ak4114 seems to communicate, apparently exists */
+ /* writing back original value */
+ prodigy192_ak4114_write(ice, addr, orig_value);
+ exists = 1;
+ }
+ return exists;
+}
+
+/*
+ * initialize the chip
+ */
+static int prodigy192_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short stac_inits_prodigy[] = {
+ STAC946X_RESET, 0,
+ STAC946X_MASTER_CLOCKING, 0x11,
+/* STAC946X_MASTER_VOLUME, 0,
+ STAC946X_LF_VOLUME, 0,
+ STAC946X_RF_VOLUME, 0,
+ STAC946X_LR_VOLUME, 0,
+ STAC946X_RR_VOLUME, 0,
+ STAC946X_CENTER_VOLUME, 0,
+ STAC946X_LFE_VOLUME, 0,*/
+ (unsigned short)-1
+ };
+ const unsigned short *p;
+ int err = 0;
+ struct prodigy192_spec *spec;
+
+ /* prodigy 192 */
+ ice->num_total_dacs = 6;
+ ice->num_total_adcs = 2;
+ ice->vt1720 = 0; /* ice1724, e.g. 23 GPIOs */
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+ mutex_init(&spec->mute_mutex);
+
+ /* initialize codec */
+ p = stac_inits_prodigy;
+ for (; *p != (unsigned short)-1; p += 2)
+ stac9460_put(ice, p[0], p[1]);
+ ice->gpio.set_pro_rate = stac9460_set_rate_val;
+
+ /* MI/ODI/O add on card with AK4114 */
+ if (prodigy192_miodio_exists(ice)) {
+ err = prodigy192_ak4114_init(ice);
+ /* from this moment if err = 0 then
+ * spec->ak4114 should not be null
+ */
+ dev_dbg(ice->card->dev,
+ "AK4114 initialized with status %d\n", err);
+ } else
+ dev_dbg(ice->card->dev, "AK4114 not found\n");
+
+ return err;
+}
+
+
+/*
+ * Aureon boards don't provide the EEPROM data except for the vendor IDs.
+ * hence the driver needs to sets up it properly.
+ */
+
+static const unsigned char prodigy71_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x6a, /* 49MHz crystal, mpu401,
+ * spdif-in+ 1 stereo ADC,
+ * 3 stereo DACs
+ */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0xf8, /* vol, 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = ~(VT1724_PRODIGY192_CDIN >> 8) ,
+ [ICE_EEP2_GPIO_DIR2] = 0xbf,
+ [ICE_EEP2_GPIO_MASK] = 0x00,
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0x00,
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x10, /* GPIO20: 0 = CD drive dig. input
+ * passthrough,
+ * 1 = SPDIF-OUT from ice1724
+ */
+};
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_prodigy192_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_PRODIGY192VE,
+ .name = "Audiotrak Prodigy 192",
+ .model = "prodigy192",
+ .chip_init = prodigy192_init,
+ .build_controls = prodigy192_add_controls,
+ .eeprom_size = sizeof(prodigy71_eeprom),
+ .eeprom_data = prodigy71_eeprom,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/prodigy192.h b/sound/pci/ice1712/prodigy192.h
new file mode 100644
index 0000000000..7bfd769ba9
--- /dev/null
+++ b/sound/pci/ice1712/prodigy192.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_PRODIGY192_H
+#define __SOUND_PRODIGY192_H
+
+#define PRODIGY192_DEVICE_DESC "{AudioTrak,Prodigy 192},"
+#define PRODIGY192_STAC9460_ADDR 0x54
+
+#define VT1724_SUBDEVICE_PRODIGY192VE 0x34495345 /* PRODIGY 192 VE */
+/*
+ * AudioTrak Prodigy192 GPIO definitions for MI/ODI/O card with
+ * AK4114 (SPDIF-IN)
+ */
+#define VT1724_PRODIGY192_CS (1 << 8) /* GPIO8, pin 75 */
+#define VT1724_PRODIGY192_CCLK (1 << 9) /* GPIO9, pin 76 */
+#define VT1724_PRODIGY192_CDOUT (1 << 10) /* GPIO10, pin 77 */
+#define VT1724_PRODIGY192_CDIN (1 << 11) /* GPIO11, pin 86 */
+
+extern struct snd_ice1712_card_info snd_vt1724_prodigy192_cards[];
+
+#endif /* __SOUND_PRODIGY192_H */
diff --git a/sound/pci/ice1712/prodigy_hifi.c b/sound/pci/ice1712/prodigy_hifi.c
new file mode 100644
index 0000000000..9aa12a67d3
--- /dev/null
+++ b/sound/pci/ice1712/prodigy_hifi.c
@@ -0,0 +1,1265 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Audiotrak Prodigy 7.1 Hifi
+ * based on pontis.c
+ *
+ * Copyright (c) 2007 Julian Scheel <julian@jusst.de>
+ * Copyright (c) 2007 allank
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/tlv.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "prodigy_hifi.h"
+
+struct prodigy_hifi_spec {
+ unsigned short master[2];
+ unsigned short vol[8];
+};
+
+/* I2C addresses */
+#define WM_DEV 0x34
+
+/* WM8776 registers */
+#define WM_HP_ATTEN_L 0x00 /* headphone left attenuation */
+#define WM_HP_ATTEN_R 0x01 /* headphone left attenuation */
+#define WM_HP_MASTER 0x02 /* headphone master (both channels),
+ override LLR */
+#define WM_DAC_ATTEN_L 0x03 /* digital left attenuation */
+#define WM_DAC_ATTEN_R 0x04
+#define WM_DAC_MASTER 0x05
+#define WM_PHASE_SWAP 0x06 /* DAC phase swap */
+#define WM_DAC_CTRL1 0x07
+#define WM_DAC_MUTE 0x08
+#define WM_DAC_CTRL2 0x09
+#define WM_DAC_INT 0x0a
+#define WM_ADC_INT 0x0b
+#define WM_MASTER_CTRL 0x0c
+#define WM_POWERDOWN 0x0d
+#define WM_ADC_ATTEN_L 0x0e
+#define WM_ADC_ATTEN_R 0x0f
+#define WM_ALC_CTRL1 0x10
+#define WM_ALC_CTRL2 0x11
+#define WM_ALC_CTRL3 0x12
+#define WM_NOISE_GATE 0x13
+#define WM_LIMITER 0x14
+#define WM_ADC_MUX 0x15
+#define WM_OUT_MUX 0x16
+#define WM_RESET 0x17
+
+/* Analog Recording Source :- Mic, LineIn, CD/Video, */
+
+/* implement capture source select control for WM8776 */
+
+#define WM_AIN1 "AIN1"
+#define WM_AIN2 "AIN2"
+#define WM_AIN3 "AIN3"
+#define WM_AIN4 "AIN4"
+#define WM_AIN5 "AIN5"
+
+/* GPIO pins of envy24ht connected to wm8766 */
+#define WM8766_SPI_CLK (1<<17) /* CLK, Pin97 on ICE1724 */
+#define WM8766_SPI_MD (1<<16) /* DATA VT1724 -> WM8766, Pin96 */
+#define WM8766_SPI_ML (1<<18) /* Latch, Pin98 */
+
+/* WM8766 registers */
+#define WM8766_DAC_CTRL 0x02 /* DAC Control */
+#define WM8766_INT_CTRL 0x03 /* Interface Control */
+#define WM8766_DAC_CTRL2 0x09
+#define WM8766_DAC_CTRL3 0x0a
+#define WM8766_RESET 0x1f
+#define WM8766_LDA1 0x00
+#define WM8766_LDA2 0x04
+#define WM8766_LDA3 0x06
+#define WM8766_RDA1 0x01
+#define WM8766_RDA2 0x05
+#define WM8766_RDA3 0x07
+#define WM8766_MUTE1 0x0C
+#define WM8766_MUTE2 0x0F
+
+
+/*
+ * Prodigy HD2
+ */
+#define AK4396_ADDR 0x00
+#define AK4396_CSN (1 << 8) /* CSN->GPIO8, pin 75 */
+#define AK4396_CCLK (1 << 9) /* CCLK->GPIO9, pin 76 */
+#define AK4396_CDTI (1 << 10) /* CDTI->GPIO10, pin 77 */
+
+/* ak4396 registers */
+#define AK4396_CTRL1 0x00
+#define AK4396_CTRL2 0x01
+#define AK4396_CTRL3 0x02
+#define AK4396_LCH_ATT 0x03
+#define AK4396_RCH_ATT 0x04
+
+
+/*
+ * get the current register value of WM codec
+ */
+static unsigned short wm_get(struct snd_ice1712 *ice, int reg)
+{
+ reg <<= 1;
+ return ((unsigned short)ice->akm[0].images[reg] << 8) |
+ ice->akm[0].images[reg + 1];
+}
+
+/*
+ * set the register value of WM codec and remember it
+ */
+static void wm_put_nocache(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ unsigned short cval;
+ cval = (reg << 9) | val;
+ snd_vt1724_write_i2c(ice, WM_DEV, cval >> 8, cval & 0xff);
+}
+
+static void wm_put(struct snd_ice1712 *ice, int reg, unsigned short val)
+{
+ wm_put_nocache(ice, reg, val);
+ reg <<= 1;
+ ice->akm[0].images[reg] = val >> 8;
+ ice->akm[0].images[reg + 1] = val;
+}
+
+/*
+ * write data in the SPI mode
+ */
+
+static void set_gpio_bit(struct snd_ice1712 *ice, unsigned int bit, int val)
+{
+ unsigned int tmp = snd_ice1712_gpio_read(ice);
+ if (val)
+ tmp |= bit;
+ else
+ tmp &= ~bit;
+ snd_ice1712_gpio_write(ice, tmp);
+}
+
+/*
+ * SPI implementation for WM8766 codec - only writing supported, no readback
+ */
+
+static void wm8766_spi_send_word(struct snd_ice1712 *ice, unsigned int data)
+{
+ int i;
+ for (i = 0; i < 16; i++) {
+ set_gpio_bit(ice, WM8766_SPI_CLK, 0);
+ udelay(1);
+ set_gpio_bit(ice, WM8766_SPI_MD, data & 0x8000);
+ udelay(1);
+ set_gpio_bit(ice, WM8766_SPI_CLK, 1);
+ udelay(1);
+ data <<= 1;
+ }
+}
+
+static void wm8766_spi_write(struct snd_ice1712 *ice, unsigned int reg,
+ unsigned int data)
+{
+ unsigned int block;
+
+ snd_ice1712_gpio_set_dir(ice, WM8766_SPI_MD|
+ WM8766_SPI_CLK|WM8766_SPI_ML);
+ snd_ice1712_gpio_set_mask(ice, ~(WM8766_SPI_MD|
+ WM8766_SPI_CLK|WM8766_SPI_ML));
+ /* latch must be low when writing */
+ set_gpio_bit(ice, WM8766_SPI_ML, 0);
+ block = (reg << 9) | (data & 0x1ff);
+ wm8766_spi_send_word(ice, block); /* REGISTER ADDRESS */
+ /* release latch */
+ set_gpio_bit(ice, WM8766_SPI_ML, 1);
+ udelay(1);
+ /* restore */
+ snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+}
+
+
+/*
+ * serial interface for ak4396 - only writing supported, no readback
+ */
+
+static void ak4396_send_word(struct snd_ice1712 *ice, unsigned int data)
+{
+ int i;
+ for (i = 0; i < 16; i++) {
+ set_gpio_bit(ice, AK4396_CCLK, 0);
+ udelay(1);
+ set_gpio_bit(ice, AK4396_CDTI, data & 0x8000);
+ udelay(1);
+ set_gpio_bit(ice, AK4396_CCLK, 1);
+ udelay(1);
+ data <<= 1;
+ }
+}
+
+static void ak4396_write(struct snd_ice1712 *ice, unsigned int reg,
+ unsigned int data)
+{
+ unsigned int block;
+
+ snd_ice1712_gpio_set_dir(ice, AK4396_CSN|AK4396_CCLK|AK4396_CDTI);
+ snd_ice1712_gpio_set_mask(ice, ~(AK4396_CSN|AK4396_CCLK|AK4396_CDTI));
+ /* latch must be low when writing */
+ set_gpio_bit(ice, AK4396_CSN, 0);
+ block = ((AK4396_ADDR & 0x03) << 14) | (1 << 13) |
+ ((reg & 0x1f) << 8) | (data & 0xff);
+ ak4396_send_word(ice, block); /* REGISTER ADDRESS */
+ /* release latch */
+ set_gpio_bit(ice, AK4396_CSN, 1);
+ udelay(1);
+ /* restore */
+ snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask);
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+}
+
+
+/*
+ * ak4396 mixers
+ */
+
+
+
+/*
+ * DAC volume attenuation mixer control (-64dB to 0dB)
+ */
+
+static int ak4396_dac_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* mute */
+ uinfo->value.integer.max = 0xFF; /* linear */
+ return 0;
+}
+
+static int ak4396_dac_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i;
+
+ for (i = 0; i < 2; i++)
+ ucontrol->value.integer.value[i] = spec->vol[i];
+
+ return 0;
+}
+
+static int ak4396_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i;
+ int change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ if (ucontrol->value.integer.value[i] != spec->vol[i]) {
+ spec->vol[i] = ucontrol->value.integer.value[i];
+ ak4396_write(ice, AK4396_LCH_ATT + i,
+ spec->vol[i] & 0xff);
+ change = 1;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+static const DECLARE_TLV_DB_SCALE(db_scale_wm_dac, -12700, 100, 1);
+static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0);
+
+static const struct snd_kcontrol_new prodigy_hd2_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Front Playback Volume",
+ .info = ak4396_dac_vol_info,
+ .get = ak4396_dac_vol_get,
+ .put = ak4396_dac_vol_put,
+ .tlv = { .p = ak4396_db_scale },
+ },
+};
+
+
+/* --------------- */
+
+#define WM_VOL_MAX 255
+#define WM_VOL_MUTE 0x8000
+
+
+#define DAC_0dB 0xff
+#define DAC_RES 128
+#define DAC_MIN (DAC_0dB - DAC_RES)
+
+
+static void wm_set_vol(struct snd_ice1712 *ice, unsigned int index,
+ unsigned short vol, unsigned short master)
+{
+ unsigned char nvol;
+
+ if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE))
+ nvol = 0;
+ else {
+ nvol = (((vol & ~WM_VOL_MUTE) * (master & ~WM_VOL_MUTE)) / 128)
+ & WM_VOL_MAX;
+ nvol = (nvol ? (nvol + DAC_MIN) : 0) & 0xff;
+ }
+
+ wm_put(ice, index, nvol);
+ wm_put_nocache(ice, index, 0x100 | nvol);
+}
+
+static void wm8766_set_vol(struct snd_ice1712 *ice, unsigned int index,
+ unsigned short vol, unsigned short master)
+{
+ unsigned char nvol;
+
+ if ((master & WM_VOL_MUTE) || (vol & WM_VOL_MUTE))
+ nvol = 0;
+ else {
+ nvol = (((vol & ~WM_VOL_MUTE) * (master & ~WM_VOL_MUTE)) / 128)
+ & WM_VOL_MAX;
+ nvol = (nvol ? (nvol + DAC_MIN) : 0) & 0xff;
+ }
+
+ wm8766_spi_write(ice, index, (0x0100 | nvol));
+}
+
+
+/*
+ * DAC volume attenuation mixer control (-64dB to 0dB)
+ */
+
+static int wm_dac_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* mute */
+ uinfo->value.integer.max = DAC_RES; /* 0dB, 0.5dB step */
+ return 0;
+}
+
+static int wm_dac_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i;
+
+ for (i = 0; i < 2; i++)
+ ucontrol->value.integer.value[i] =
+ spec->vol[2 + i] & ~WM_VOL_MUTE;
+ return 0;
+}
+
+static int wm_dac_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i, idx, change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ if (ucontrol->value.integer.value[i] != spec->vol[2 + i]) {
+ idx = WM_DAC_ATTEN_L + i;
+ spec->vol[2 + i] &= WM_VOL_MUTE;
+ spec->vol[2 + i] |= ucontrol->value.integer.value[i];
+ wm_set_vol(ice, idx, spec->vol[2 + i], spec->master[i]);
+ change = 1;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+
+/*
+ * WM8766 DAC volume attenuation mixer control
+ */
+static int wm8766_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int voices = kcontrol->private_value >> 8;
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = voices;
+ uinfo->value.integer.min = 0; /* mute */
+ uinfo->value.integer.max = DAC_RES; /* 0dB */
+ return 0;
+}
+
+static int wm8766_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i, ofs, voices;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xff;
+ for (i = 0; i < voices; i++)
+ ucontrol->value.integer.value[i] = spec->vol[ofs + i];
+ return 0;
+}
+
+static int wm8766_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i, idx, ofs, voices;
+ int change = 0;
+
+ voices = kcontrol->private_value >> 8;
+ ofs = kcontrol->private_value & 0xff;
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < voices; i++) {
+ if (ucontrol->value.integer.value[i] != spec->vol[ofs + i]) {
+ idx = WM8766_LDA1 + ofs + i;
+ spec->vol[ofs + i] &= WM_VOL_MUTE;
+ spec->vol[ofs + i] |= ucontrol->value.integer.value[i];
+ wm8766_set_vol(ice, idx,
+ spec->vol[ofs + i], spec->master[i]);
+ change = 1;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * Master volume attenuation mixer control / applied to WM8776+WM8766
+ */
+static int wm_master_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = DAC_RES;
+ return 0;
+}
+
+static int wm_master_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i;
+ for (i = 0; i < 2; i++)
+ ucontrol->value.integer.value[i] = spec->master[i];
+ return 0;
+}
+
+static int wm_master_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int ch, change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (ch = 0; ch < 2; ch++) {
+ if (ucontrol->value.integer.value[ch] != spec->master[ch]) {
+ spec->master[ch] = ucontrol->value.integer.value[ch];
+
+ /* Apply to front DAC */
+ wm_set_vol(ice, WM_DAC_ATTEN_L + ch,
+ spec->vol[2 + ch], spec->master[ch]);
+
+ wm8766_set_vol(ice, WM8766_LDA1 + ch,
+ spec->vol[0 + ch], spec->master[ch]);
+
+ wm8766_set_vol(ice, WM8766_LDA2 + ch,
+ spec->vol[4 + ch], spec->master[ch]);
+
+ wm8766_set_vol(ice, WM8766_LDA3 + ch,
+ spec->vol[6 + ch], spec->master[ch]);
+ change = 1;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+
+/* KONSTI */
+
+static int wm_adc_mux_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[32] = {
+ "NULL", WM_AIN1, WM_AIN2, WM_AIN1 "+" WM_AIN2,
+ WM_AIN3, WM_AIN1 "+" WM_AIN3, WM_AIN2 "+" WM_AIN3,
+ WM_AIN1 "+" WM_AIN2 "+" WM_AIN3,
+ WM_AIN4, WM_AIN1 "+" WM_AIN4, WM_AIN2 "+" WM_AIN4,
+ WM_AIN1 "+" WM_AIN2 "+" WM_AIN4,
+ WM_AIN3 "+" WM_AIN4, WM_AIN1 "+" WM_AIN3 "+" WM_AIN4,
+ WM_AIN2 "+" WM_AIN3 "+" WM_AIN4,
+ WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN4,
+ WM_AIN5, WM_AIN1 "+" WM_AIN5, WM_AIN2 "+" WM_AIN5,
+ WM_AIN1 "+" WM_AIN2 "+" WM_AIN5,
+ WM_AIN3 "+" WM_AIN5, WM_AIN1 "+" WM_AIN3 "+" WM_AIN5,
+ WM_AIN2 "+" WM_AIN3 "+" WM_AIN5,
+ WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN5,
+ WM_AIN4 "+" WM_AIN5, WM_AIN1 "+" WM_AIN4 "+" WM_AIN5,
+ WM_AIN2 "+" WM_AIN4 "+" WM_AIN5,
+ WM_AIN1 "+" WM_AIN2 "+" WM_AIN4 "+" WM_AIN5,
+ WM_AIN3 "+" WM_AIN4 "+" WM_AIN5,
+ WM_AIN1 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5,
+ WM_AIN2 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5,
+ WM_AIN1 "+" WM_AIN2 "+" WM_AIN3 "+" WM_AIN4 "+" WM_AIN5
+ };
+
+ return snd_ctl_enum_info(uinfo, 1, 32, texts);
+}
+
+static int wm_adc_mux_enum_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.enumerated.item[0] = wm_get(ice, WM_ADC_MUX) & 0x1f;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_mux_enum_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short oval, nval;
+ int change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ oval = wm_get(ice, WM_ADC_MUX);
+ nval = (oval & 0xe0) | ucontrol->value.enumerated.item[0];
+ if (nval != oval) {
+ wm_put(ice, WM_ADC_MUX, nval);
+ change = 1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/* KONSTI */
+
+/*
+ * ADC gain mixer control (-64dB to 0dB)
+ */
+
+#define ADC_0dB 0xcf
+#define ADC_RES 128
+#define ADC_MIN (ADC_0dB - ADC_RES)
+
+static int wm_adc_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* mute (-64dB) */
+ uinfo->value.integer.max = ADC_RES; /* 0dB, 0.5dB step */
+ return 0;
+}
+
+static int wm_adc_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val;
+ int i;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ val = wm_get(ice, WM_ADC_ATTEN_L + i) & 0xff;
+ val = val > ADC_MIN ? (val - ADC_MIN) : 0;
+ ucontrol->value.integer.value[i] = val;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short ovol, nvol;
+ int i, idx, change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (i = 0; i < 2; i++) {
+ nvol = ucontrol->value.integer.value[i];
+ nvol = nvol ? (nvol + ADC_MIN) : 0;
+ idx = WM_ADC_ATTEN_L + i;
+ ovol = wm_get(ice, idx) & 0xff;
+ if (ovol != nvol) {
+ wm_put(ice, idx, nvol);
+ change = 1;
+ }
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * ADC input mux mixer control
+ */
+#define wm_adc_mux_info snd_ctl_boolean_mono_info
+
+static int wm_adc_mux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int bit = kcontrol->private_value;
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] =
+ (wm_get(ice, WM_ADC_MUX) & (1 << bit)) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_adc_mux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int bit = kcontrol->private_value;
+ unsigned short oval, nval;
+ int change;
+
+ mutex_lock(&ice->gpio_mutex);
+ nval = oval = wm_get(ice, WM_ADC_MUX);
+ if (ucontrol->value.integer.value[0])
+ nval |= (1 << bit);
+ else
+ nval &= ~(1 << bit);
+ change = nval != oval;
+ if (change) {
+ wm_put(ice, WM_ADC_MUX, nval);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+/*
+ * Analog bypass (In -> Out)
+ */
+#define wm_bypass_info snd_ctl_boolean_mono_info
+
+static int wm_bypass_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] =
+ (wm_get(ice, WM_OUT_MUX) & 0x04) ? 1 : 0;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_bypass_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val, oval;
+ int change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ val = oval = wm_get(ice, WM_OUT_MUX);
+ if (ucontrol->value.integer.value[0])
+ val |= 0x04;
+ else
+ val &= ~0x04;
+ if (val != oval) {
+ wm_put(ice, WM_OUT_MUX, val);
+ change = 1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+/*
+ * Left/Right swap
+ */
+#define wm_chswap_info snd_ctl_boolean_mono_info
+
+static int wm_chswap_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+
+ mutex_lock(&ice->gpio_mutex);
+ ucontrol->value.integer.value[0] =
+ (wm_get(ice, WM_DAC_CTRL1) & 0xf0) != 0x90;
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+
+static int wm_chswap_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned short val, oval;
+ int change = 0;
+
+ mutex_lock(&ice->gpio_mutex);
+ oval = wm_get(ice, WM_DAC_CTRL1);
+ val = oval & 0x0f;
+ if (ucontrol->value.integer.value[0])
+ val |= 0x60;
+ else
+ val |= 0x90;
+ if (val != oval) {
+ wm_put(ice, WM_DAC_CTRL1, val);
+ wm_put_nocache(ice, WM_DAC_CTRL1, val);
+ change = 1;
+ }
+ mutex_unlock(&ice->gpio_mutex);
+ return change;
+}
+
+
+/*
+ * mixers
+ */
+
+static const struct snd_kcontrol_new prodigy_hifi_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Master Playback Volume",
+ .info = wm_master_vol_info,
+ .get = wm_master_vol_get,
+ .put = wm_master_vol_put,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Front Playback Volume",
+ .info = wm_dac_vol_info,
+ .get = wm_dac_vol_get,
+ .put = wm_dac_vol_put,
+ .tlv = { .p = db_scale_wm_dac },
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Rear Playback Volume",
+ .info = wm8766_vol_info,
+ .get = wm8766_vol_get,
+ .put = wm8766_vol_put,
+ .private_value = (2 << 8) | 0,
+ .tlv = { .p = db_scale_wm_dac },
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Center Playback Volume",
+ .info = wm8766_vol_info,
+ .get = wm8766_vol_get,
+ .put = wm8766_vol_put,
+ .private_value = (1 << 8) | 4,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "LFE Playback Volume",
+ .info = wm8766_vol_info,
+ .get = wm8766_vol_get,
+ .put = wm8766_vol_put,
+ .private_value = (1 << 8) | 5,
+ .tlv = { .p = db_scale_wm_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Side Playback Volume",
+ .info = wm8766_vol_info,
+ .get = wm8766_vol_get,
+ .put = wm8766_vol_put,
+ .private_value = (2 << 8) | 6,
+ .tlv = { .p = db_scale_wm_dac },
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Capture Volume",
+ .info = wm_adc_vol_info,
+ .get = wm_adc_vol_get,
+ .put = wm_adc_vol_put,
+ .tlv = { .p = db_scale_wm_dac },
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "CD Capture Switch",
+ .info = wm_adc_mux_info,
+ .get = wm_adc_mux_get,
+ .put = wm_adc_mux_put,
+ .private_value = 0,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Line Capture Switch",
+ .info = wm_adc_mux_info,
+ .get = wm_adc_mux_get,
+ .put = wm_adc_mux_put,
+ .private_value = 1,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Bypass Switch",
+ .info = wm_bypass_info,
+ .get = wm_bypass_get,
+ .put = wm_bypass_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Swap Output Channels",
+ .info = wm_chswap_info,
+ .get = wm_chswap_get,
+ .put = wm_chswap_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Capture Source",
+ .info = wm_adc_mux_enum_info,
+ .get = wm_adc_mux_enum_get,
+ .put = wm_adc_mux_enum_put,
+ },
+};
+
+/*
+ * WM codec registers
+ */
+static void wm_proc_regs_write(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ char line[64];
+ unsigned int reg, val;
+ mutex_lock(&ice->gpio_mutex);
+ while (!snd_info_get_line(buffer, line, sizeof(line))) {
+ if (sscanf(line, "%x %x", &reg, &val) != 2)
+ continue;
+ if (reg <= 0x17 && val <= 0xffff)
+ wm_put(ice, reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_regs_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ int reg, val;
+
+ mutex_lock(&ice->gpio_mutex);
+ for (reg = 0; reg <= 0x17; reg++) {
+ val = wm_get(ice, reg);
+ snd_iprintf(buffer, "%02x = %04x\n", reg, val);
+ }
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static void wm_proc_init(struct snd_ice1712 *ice)
+{
+ snd_card_rw_proc_new(ice->card, "wm_codec", ice, wm_proc_regs_read,
+ wm_proc_regs_write);
+}
+
+static int prodigy_hifi_add_controls(struct snd_ice1712 *ice)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(prodigy_hifi_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&prodigy_hifi_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+
+ wm_proc_init(ice);
+
+ return 0;
+}
+
+static int prodigy_hd2_add_controls(struct snd_ice1712 *ice)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(prodigy_hd2_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&prodigy_hd2_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+
+ wm_proc_init(ice);
+
+ return 0;
+}
+
+static void wm8766_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm8766_inits[] = {
+ WM8766_RESET, 0x0000,
+ WM8766_DAC_CTRL, 0x0120,
+ WM8766_INT_CTRL, 0x0022, /* I2S Normal Mode, 24 bit */
+ WM8766_DAC_CTRL2, 0x0001,
+ WM8766_DAC_CTRL3, 0x0080,
+ WM8766_LDA1, 0x0100,
+ WM8766_LDA2, 0x0100,
+ WM8766_LDA3, 0x0100,
+ WM8766_RDA1, 0x0100,
+ WM8766_RDA2, 0x0100,
+ WM8766_RDA3, 0x0100,
+ WM8766_MUTE1, 0x0000,
+ WM8766_MUTE2, 0x0000,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8766_inits); i += 2)
+ wm8766_spi_write(ice, wm8766_inits[i], wm8766_inits[i + 1]);
+}
+
+static void wm8776_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm8776_inits[] = {
+ /* These come first to reduce init pop noise */
+ WM_ADC_MUX, 0x0003, /* ADC mute */
+ /* 0x00c0 replaced by 0x0003 */
+
+ WM_DAC_MUTE, 0x0001, /* DAC softmute */
+ WM_DAC_CTRL1, 0x0000, /* DAC mute */
+
+ WM_POWERDOWN, 0x0008, /* All power-up except HP */
+ WM_RESET, 0x0000, /* reset */
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8776_inits); i += 2)
+ wm_put(ice, wm8776_inits[i], wm8776_inits[i + 1]);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int prodigy_hifi_resume(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm8776_reinit_registers[] = {
+ WM_MASTER_CTRL,
+ WM_DAC_INT,
+ WM_ADC_INT,
+ WM_OUT_MUX,
+ WM_HP_ATTEN_L,
+ WM_HP_ATTEN_R,
+ WM_PHASE_SWAP,
+ WM_DAC_CTRL2,
+ WM_ADC_ATTEN_L,
+ WM_ADC_ATTEN_R,
+ WM_ALC_CTRL1,
+ WM_ALC_CTRL2,
+ WM_ALC_CTRL3,
+ WM_NOISE_GATE,
+ WM_ADC_MUX,
+ /* no DAC attenuation here */
+ };
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i, ch;
+
+ mutex_lock(&ice->gpio_mutex);
+
+ /* reinitialize WM8776 and re-apply old register values */
+ wm8776_init(ice);
+ schedule_timeout_uninterruptible(1);
+ for (i = 0; i < ARRAY_SIZE(wm8776_reinit_registers); i++)
+ wm_put(ice, wm8776_reinit_registers[i],
+ wm_get(ice, wm8776_reinit_registers[i]));
+
+ /* reinitialize WM8766 and re-apply volumes for all DACs */
+ wm8766_init(ice);
+ for (ch = 0; ch < 2; ch++) {
+ wm_set_vol(ice, WM_DAC_ATTEN_L + ch,
+ spec->vol[2 + ch], spec->master[ch]);
+
+ wm8766_set_vol(ice, WM8766_LDA1 + ch,
+ spec->vol[0 + ch], spec->master[ch]);
+
+ wm8766_set_vol(ice, WM8766_LDA2 + ch,
+ spec->vol[4 + ch], spec->master[ch]);
+
+ wm8766_set_vol(ice, WM8766_LDA3 + ch,
+ spec->vol[6 + ch], spec->master[ch]);
+ }
+
+ /* unmute WM8776 DAC */
+ wm_put(ice, WM_DAC_MUTE, 0x00);
+ wm_put(ice, WM_DAC_CTRL1, 0x90);
+
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+#endif
+
+/*
+ * initialize the chip
+ */
+static int prodigy_hifi_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short wm8776_defaults[] = {
+ WM_MASTER_CTRL, 0x0022, /* 256fs, slave mode */
+ WM_DAC_INT, 0x0022, /* I2S, normal polarity, 24bit */
+ WM_ADC_INT, 0x0022, /* I2S, normal polarity, 24bit */
+ WM_DAC_CTRL1, 0x0090, /* DAC L/R */
+ WM_OUT_MUX, 0x0001, /* OUT DAC */
+ WM_HP_ATTEN_L, 0x0179, /* HP 0dB */
+ WM_HP_ATTEN_R, 0x0179, /* HP 0dB */
+ WM_DAC_ATTEN_L, 0x0000, /* DAC 0dB */
+ WM_DAC_ATTEN_L, 0x0100, /* DAC 0dB */
+ WM_DAC_ATTEN_R, 0x0000, /* DAC 0dB */
+ WM_DAC_ATTEN_R, 0x0100, /* DAC 0dB */
+ WM_PHASE_SWAP, 0x0000, /* phase normal */
+#if 0
+ WM_DAC_MASTER, 0x0100, /* DAC master muted */
+#endif
+ WM_DAC_CTRL2, 0x0000, /* no deemphasis, no ZFLG */
+ WM_ADC_ATTEN_L, 0x0000, /* ADC muted */
+ WM_ADC_ATTEN_R, 0x0000, /* ADC muted */
+#if 1
+ WM_ALC_CTRL1, 0x007b, /* */
+ WM_ALC_CTRL2, 0x0000, /* */
+ WM_ALC_CTRL3, 0x0000, /* */
+ WM_NOISE_GATE, 0x0000, /* */
+#endif
+ WM_DAC_MUTE, 0x0000, /* DAC unmute */
+ WM_ADC_MUX, 0x0003, /* ADC unmute, both CD/Line On */
+ };
+ struct prodigy_hifi_spec *spec;
+ unsigned int i;
+
+ ice->vt1720 = 0;
+ ice->vt1724 = 1;
+
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 1;
+
+ /* HACK - use this as the SPDIF source.
+ * don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten
+ */
+ ice->gpio.saved[0] = 0;
+ /* to remember the register values */
+
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ice->akm)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ /* initialize WM8776 codec */
+ wm8776_init(ice);
+ schedule_timeout_uninterruptible(1);
+ for (i = 0; i < ARRAY_SIZE(wm8776_defaults); i += 2)
+ wm_put(ice, wm8776_defaults[i], wm8776_defaults[i + 1]);
+
+ wm8766_init(ice);
+
+#ifdef CONFIG_PM_SLEEP
+ ice->pm_resume = &prodigy_hifi_resume;
+ ice->pm_suspend_enabled = 1;
+#endif
+
+ return 0;
+}
+
+
+/*
+ * initialize the chip
+ */
+static void ak4396_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short ak4396_inits[] = {
+ AK4396_CTRL1, 0x87, /* I2S Normal Mode, 24 bit */
+ AK4396_CTRL2, 0x02,
+ AK4396_CTRL3, 0x00,
+ AK4396_LCH_ATT, 0x00,
+ AK4396_RCH_ATT, 0x00,
+ };
+
+ unsigned int i;
+
+ /* initialize ak4396 codec */
+ /* reset codec */
+ ak4396_write(ice, AK4396_CTRL1, 0x86);
+ msleep(100);
+ ak4396_write(ice, AK4396_CTRL1, 0x87);
+
+ for (i = 0; i < ARRAY_SIZE(ak4396_inits); i += 2)
+ ak4396_write(ice, ak4396_inits[i], ak4396_inits[i+1]);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int prodigy_hd2_resume(struct snd_ice1712 *ice)
+{
+ /* initialize ak4396 codec and restore previous mixer volumes */
+ struct prodigy_hifi_spec *spec = ice->spec;
+ int i;
+ mutex_lock(&ice->gpio_mutex);
+ ak4396_init(ice);
+ for (i = 0; i < 2; i++)
+ ak4396_write(ice, AK4396_LCH_ATT + i, spec->vol[i] & 0xff);
+ mutex_unlock(&ice->gpio_mutex);
+ return 0;
+}
+#endif
+
+static int prodigy_hd2_init(struct snd_ice1712 *ice)
+{
+ struct prodigy_hifi_spec *spec;
+
+ ice->vt1720 = 0;
+ ice->vt1724 = 1;
+
+ ice->num_total_dacs = 1;
+ ice->num_total_adcs = 1;
+
+ /* HACK - use this as the SPDIF source.
+ * don't call snd_ice1712_gpio_get/put(), otherwise it's overwritten
+ */
+ ice->gpio.saved[0] = 0;
+ /* to remember the register values */
+
+ ice->akm = kzalloc(sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ice->akm)
+ return -ENOMEM;
+ ice->akm_codecs = 1;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+#ifdef CONFIG_PM_SLEEP
+ ice->pm_resume = &prodigy_hd2_resume;
+ ice->pm_suspend_enabled = 1;
+#endif
+
+ ak4396_init(ice);
+
+ return 0;
+}
+
+
+static const unsigned char prodigy71hifi_eeprom[] = {
+ 0x4b, /* SYSCONF: clock 512, spdif-in/ADC, 4DACs */
+ 0x80, /* ACLINK: I2S */
+ 0xfc, /* I2S: vol, 96k, 24bit, 192k */
+ 0xc3, /* SPDIF: out-en, out-int, spdif-in */
+ 0xff, /* GPIO_DIR */
+ 0xff, /* GPIO_DIR1 */
+ 0x5f, /* GPIO_DIR2 */
+ 0x00, /* GPIO_MASK */
+ 0x00, /* GPIO_MASK1 */
+ 0x00, /* GPIO_MASK2 */
+ 0x00, /* GPIO_STATE */
+ 0x00, /* GPIO_STATE1 */
+ 0x00, /* GPIO_STATE2 */
+};
+
+static const unsigned char prodigyhd2_eeprom[] = {
+ 0x4b, /* SYSCONF: clock 512, spdif-in/ADC, 4DACs */
+ 0x80, /* ACLINK: I2S */
+ 0xfc, /* I2S: vol, 96k, 24bit, 192k */
+ 0xc3, /* SPDIF: out-en, out-int, spdif-in */
+ 0xff, /* GPIO_DIR */
+ 0xff, /* GPIO_DIR1 */
+ 0x5f, /* GPIO_DIR2 */
+ 0x00, /* GPIO_MASK */
+ 0x00, /* GPIO_MASK1 */
+ 0x00, /* GPIO_MASK2 */
+ 0x00, /* GPIO_STATE */
+ 0x00, /* GPIO_STATE1 */
+ 0x00, /* GPIO_STATE2 */
+};
+
+static const unsigned char fortissimo4_eeprom[] = {
+ 0x43, /* SYSCONF: clock 512, ADC, 4DACs */
+ 0x80, /* ACLINK: I2S */
+ 0xfc, /* I2S: vol, 96k, 24bit, 192k */
+ 0xc1, /* SPDIF: out-en, out-int */
+ 0xff, /* GPIO_DIR */
+ 0xff, /* GPIO_DIR1 */
+ 0x5f, /* GPIO_DIR2 */
+ 0x00, /* GPIO_MASK */
+ 0x00, /* GPIO_MASK1 */
+ 0x00, /* GPIO_MASK2 */
+ 0x00, /* GPIO_STATE */
+ 0x00, /* GPIO_STATE1 */
+ 0x00, /* GPIO_STATE2 */
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_prodigy_hifi_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_PRODIGY_HIFI,
+ .name = "Audiotrak Prodigy 7.1 HiFi",
+ .model = "prodigy71hifi",
+ .chip_init = prodigy_hifi_init,
+ .build_controls = prodigy_hifi_add_controls,
+ .eeprom_size = sizeof(prodigy71hifi_eeprom),
+ .eeprom_data = prodigy71hifi_eeprom,
+ .driver = "Prodigy71HIFI",
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_PRODIGY_HD2,
+ .name = "Audiotrak Prodigy HD2",
+ .model = "prodigyhd2",
+ .chip_init = prodigy_hd2_init,
+ .build_controls = prodigy_hd2_add_controls,
+ .eeprom_size = sizeof(prodigyhd2_eeprom),
+ .eeprom_data = prodigyhd2_eeprom,
+ .driver = "Prodigy71HD2",
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_FORTISSIMO4,
+ .name = "Hercules Fortissimo IV",
+ .model = "fortissimo4",
+ .chip_init = prodigy_hifi_init,
+ .build_controls = prodigy_hifi_add_controls,
+ .eeprom_size = sizeof(fortissimo4_eeprom),
+ .eeprom_data = fortissimo4_eeprom,
+ .driver = "Fortissimo4",
+ },
+ { } /* terminator */
+};
+
diff --git a/sound/pci/ice1712/prodigy_hifi.h b/sound/pci/ice1712/prodigy_hifi.h
new file mode 100644
index 0000000000..f0e88396de
--- /dev/null
+++ b/sound/pci/ice1712/prodigy_hifi.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_PRODIGY_HIFI_H
+#define __SOUND_PRODIGY_HIFI_H
+
+/*
+ * ALSA driver for VIA VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Audiotrak Prodigy Hifi
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#define PRODIGY_HIFI_DEVICE_DESC "{Audiotrak,Prodigy 7.1 HIFI},"\
+ "{Audiotrak Prodigy HD2},"\
+ "{Hercules Fortissimo IV},"
+
+#define VT1724_SUBDEVICE_PRODIGY_HIFI 0x38315441 /* PRODIGY 7.1 HIFI */
+#define VT1724_SUBDEVICE_PRODIGY_HD2 0x37315441 /* PRODIGY HD2 */
+#define VT1724_SUBDEVICE_FORTISSIMO4 0x81160100 /* Fortissimo IV */
+
+
+extern struct snd_ice1712_card_info snd_vt1724_prodigy_hifi_cards[];
+
+#endif /* __SOUND_PRODIGY_HIFI_H */
diff --git a/sound/pci/ice1712/psc724.c b/sound/pci/ice1712/psc724.c
new file mode 100644
index 0000000000..0818e42c94
--- /dev/null
+++ b/sound/pci/ice1712/psc724.c
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Philips PSC724 Ultimate Edge
+ *
+ * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
+ */
+
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "psc724.h"
+#include "wm8766.h"
+#include "wm8776.h"
+
+struct psc724_spec {
+ struct snd_wm8766 wm8766;
+ struct snd_wm8776 wm8776;
+ bool mute_all, jack_detect;
+ 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 (bug!)
+ * 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 **
+ *
+ * 5.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
+ */
+
+/* codec access low-level functions */
+
+#define GPIO_HP_JACK (1 << 14)
+#define GPIO_MUTE_SUR (1 << 20)
+#define GPIO_MUTE_ALL (1 << 22)
+
+#define JACK_INTERVAL 1000
+
+#define PSC724_SPI_DELAY 1
+
+#define PSC724_SPI_DATA (1 << 16)
+#define PSC724_SPI_CLK (1 << 17)
+#define PSC724_SPI_LOAD (1 << 18)
+#define PSC724_SPI_MASK (PSC724_SPI_DATA | PSC724_SPI_CLK | PSC724_SPI_LOAD)
+
+static void psc724_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data)
+{
+ struct psc724_spec *spec = container_of(wm, struct psc724_spec, wm8766);
+ struct snd_ice1712 *ice = spec->ice;
+ u32 st, bits;
+ int i;
+
+ snd_ice1712_save_gpio_status(ice);
+
+ st = ((addr & 0x7f) << 9) | (data & 0x1ff);
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction | PSC724_SPI_MASK);
+ snd_ice1712_gpio_set_mask(ice, ice->gpio.write_mask & ~PSC724_SPI_MASK);
+ bits = snd_ice1712_gpio_read(ice) & ~PSC724_SPI_MASK;
+ snd_ice1712_gpio_write(ice, bits);
+
+ for (i = 0; i < 16; i++) {
+ udelay(PSC724_SPI_DELAY);
+ bits &= ~PSC724_SPI_CLK;
+ /* MSB first */
+ st <<= 1;
+ if (st & 0x10000)
+ bits |= PSC724_SPI_DATA;
+ else
+ bits &= ~PSC724_SPI_DATA;
+ snd_ice1712_gpio_write(ice, bits);
+ /* CLOCK high */
+ udelay(PSC724_SPI_DELAY);
+ bits |= PSC724_SPI_CLK;
+ snd_ice1712_gpio_write(ice, bits);
+ }
+ /* LOAD high */
+ udelay(PSC724_SPI_DELAY);
+ bits |= PSC724_SPI_LOAD;
+ snd_ice1712_gpio_write(ice, bits);
+ /* LOAD low, DATA and CLOCK high */
+ udelay(PSC724_SPI_DELAY);
+ bits |= (PSC724_SPI_DATA | PSC724_SPI_CLK);
+ snd_ice1712_gpio_write(ice, bits);
+
+ snd_ice1712_restore_gpio_status(ice);
+}
+
+static void psc724_wm8776_write(struct snd_wm8776 *wm, u8 addr, u8 data)
+{
+ struct psc724_spec *spec = container_of(wm, struct psc724_spec, wm8776);
+
+ snd_vt1724_write_i2c(spec->ice, 0x34, addr, data);
+}
+
+/* mute all */
+
+static void psc724_set_master_switch(struct snd_ice1712 *ice, bool on)
+{
+ unsigned int bits = snd_ice1712_gpio_read(ice);
+ struct psc724_spec *spec = ice->spec;
+
+ spec->mute_all = !on;
+ if (on)
+ bits &= ~(GPIO_MUTE_ALL | GPIO_MUTE_SUR);
+ else
+ bits |= GPIO_MUTE_ALL | GPIO_MUTE_SUR;
+ snd_ice1712_gpio_write(ice, bits);
+}
+
+static bool psc724_get_master_switch(struct snd_ice1712 *ice)
+{
+ struct psc724_spec *spec = ice->spec;
+
+ return !spec->mute_all;
+}
+
+/* jack detection */
+
+static void psc724_set_jack_state(struct snd_ice1712 *ice, bool hp_connected)
+{
+ struct psc724_spec *spec = ice->spec;
+ struct snd_kcontrol *kctl;
+ u16 power = spec->wm8776.regs[WM8776_REG_PWRDOWN] & ~WM8776_PWR_HPPD;
+
+ psc724_set_master_switch(ice, !hp_connected);
+ if (!hp_connected)
+ power |= WM8776_PWR_HPPD;
+ snd_wm8776_set_power(&spec->wm8776, power);
+ spec->hp_connected = hp_connected;
+ /* notify about master speaker mute change */
+ kctl = snd_ctl_find_id_mixer(ice->card,
+ "Master Speakers Playback Switch");
+ if (kctl)
+ snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+ /* and headphone mute change */
+ kctl = snd_ctl_find_id_mixer(ice->card,
+ spec->wm8776.ctl[WM8776_CTL_HP_SW].name);
+ if (kctl)
+ snd_ctl_notify(ice->card, SNDRV_CTL_EVENT_MASK_VALUE, &kctl->id);
+}
+
+static void psc724_update_hp_jack_state(struct work_struct *work)
+{
+ struct psc724_spec *spec = container_of(work, struct psc724_spec,
+ hp_work.work);
+ struct snd_ice1712 *ice = spec->ice;
+ bool hp_connected = snd_ice1712_gpio_read(ice) & GPIO_HP_JACK;
+
+ schedule_delayed_work(&spec->hp_work, msecs_to_jiffies(JACK_INTERVAL));
+ if (hp_connected == spec->hp_connected)
+ return;
+ psc724_set_jack_state(ice, hp_connected);
+}
+
+static void psc724_set_jack_detection(struct snd_ice1712 *ice, bool on)
+{
+ struct psc724_spec *spec = ice->spec;
+
+ if (spec->jack_detect == on)
+ return;
+
+ spec->jack_detect = on;
+ if (on) {
+ bool hp_connected = snd_ice1712_gpio_read(ice) & GPIO_HP_JACK;
+ psc724_set_jack_state(ice, hp_connected);
+ schedule_delayed_work(&spec->hp_work,
+ msecs_to_jiffies(JACK_INTERVAL));
+ } else
+ cancel_delayed_work_sync(&spec->hp_work);
+}
+
+static bool psc724_get_jack_detection(struct snd_ice1712 *ice)
+{
+ struct psc724_spec *spec = ice->spec;
+
+ return spec->jack_detect;
+}
+
+/* mixer controls */
+
+struct psc724_control {
+ const char *name;
+ void (*set)(struct snd_ice1712 *ice, bool on);
+ bool (*get)(struct snd_ice1712 *ice);
+};
+
+static const struct psc724_control psc724_cont[] = {
+ {
+ .name = "Master Speakers Playback Switch",
+ .set = psc724_set_master_switch,
+ .get = psc724_get_master_switch,
+ },
+ {
+ .name = "Headphone Jack Detection Playback Switch",
+ .set = psc724_set_jack_detection,
+ .get = psc724_get_jack_detection,
+ },
+};
+
+static int psc724_ctl_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int n = kcontrol->private_value;
+
+ ucontrol->value.integer.value[0] = psc724_cont[n].get(ice);
+
+ return 0;
+}
+
+static int psc724_ctl_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int n = kcontrol->private_value;
+
+ psc724_cont[n].set(ice, ucontrol->value.integer.value[0]);
+
+ return 0;
+}
+
+static const char *front_volume = "Front Playback Volume";
+static const char *front_switch = "Front Playback Switch";
+static const char *front_zc = "Front Zero Cross Detect Playback Switch";
+static const char *front_izd = "Front Infinite Zero Detect Playback Switch";
+static const char *front_phase = "Front Phase Invert Playback Switch";
+static const char *front_deemph = "Front Deemphasis Playback Switch";
+static const char *ain1_switch = "Line Capture Switch";
+static const char *ain2_switch = "CD Capture Switch";
+static const char *ain3_switch = "AUX Capture Switch";
+static const char *ain4_switch = "Front Mic Capture Switch";
+static const char *ain5_switch = "Rear Mic Capture Switch";
+static const char *rear_volume = "Surround Playback Volume";
+static const char *clfe_volume = "CLFE Playback Volume";
+static const char *rear_switch = "Surround Playback Switch";
+static const char *clfe_switch = "CLFE Playback Switch";
+static const char *rear_phase = "Surround Phase Invert Playback Switch";
+static const char *clfe_phase = "CLFE Phase Invert Playback Switch";
+static const char *rear_deemph = "Surround Deemphasis Playback Switch";
+static const char *clfe_deemph = "CLFE Deemphasis Playback Switch";
+static const char *rear_clfe_izd = "Rear Infinite Zero Detect Playback Switch";
+static const char *rear_clfe_zc = "Rear Zero Cross Detect Playback Switch";
+
+static int psc724_add_controls(struct snd_ice1712 *ice)
+{
+ struct snd_kcontrol_new cont;
+ struct snd_kcontrol *ctl;
+ int err, i;
+ struct psc724_spec *spec = ice->spec;
+
+ spec->wm8776.ctl[WM8776_CTL_DAC_VOL].name = front_volume;
+ spec->wm8776.ctl[WM8776_CTL_DAC_SW].name = front_switch;
+ spec->wm8776.ctl[WM8776_CTL_DAC_ZC_SW].name = front_zc;
+ spec->wm8776.ctl[WM8776_CTL_AUX_SW].name = NULL;
+ spec->wm8776.ctl[WM8776_CTL_DAC_IZD_SW].name = front_izd;
+ spec->wm8776.ctl[WM8776_CTL_PHASE_SW].name = front_phase;
+ spec->wm8776.ctl[WM8776_CTL_DEEMPH_SW].name = front_deemph;
+ spec->wm8776.ctl[WM8776_CTL_INPUT1_SW].name = ain1_switch;
+ spec->wm8776.ctl[WM8776_CTL_INPUT2_SW].name = ain2_switch;
+ spec->wm8776.ctl[WM8776_CTL_INPUT3_SW].name = ain3_switch;
+ spec->wm8776.ctl[WM8776_CTL_INPUT4_SW].name = ain4_switch;
+ spec->wm8776.ctl[WM8776_CTL_INPUT5_SW].name = ain5_switch;
+ snd_wm8776_build_controls(&spec->wm8776);
+ spec->wm8766.ctl[WM8766_CTL_CH1_VOL].name = rear_volume;
+ spec->wm8766.ctl[WM8766_CTL_CH2_VOL].name = clfe_volume;
+ spec->wm8766.ctl[WM8766_CTL_CH3_VOL].name = NULL;
+ spec->wm8766.ctl[WM8766_CTL_CH1_SW].name = rear_switch;
+ spec->wm8766.ctl[WM8766_CTL_CH2_SW].name = clfe_switch;
+ spec->wm8766.ctl[WM8766_CTL_CH3_SW].name = NULL;
+ spec->wm8766.ctl[WM8766_CTL_PHASE1_SW].name = rear_phase;
+ spec->wm8766.ctl[WM8766_CTL_PHASE2_SW].name = clfe_phase;
+ spec->wm8766.ctl[WM8766_CTL_PHASE3_SW].name = NULL;
+ spec->wm8766.ctl[WM8766_CTL_DEEMPH1_SW].name = rear_deemph;
+ spec->wm8766.ctl[WM8766_CTL_DEEMPH2_SW].name = clfe_deemph;
+ spec->wm8766.ctl[WM8766_CTL_DEEMPH3_SW].name = NULL;
+ spec->wm8766.ctl[WM8766_CTL_IZD_SW].name = rear_clfe_izd;
+ spec->wm8766.ctl[WM8766_CTL_ZC_SW].name = rear_clfe_zc;
+ snd_wm8766_build_controls(&spec->wm8766);
+
+ 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.info = snd_ctl_boolean_mono_info;
+ cont.get = psc724_ctl_get;
+ cont.put = psc724_ctl_put;
+ ctl = snd_ctl_new1(&cont, ice);
+ if (!ctl)
+ return -ENOMEM;
+ err = snd_ctl_add(ice->card, ctl);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static void psc724_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+ struct psc724_spec *spec = ice->spec;
+ /* restore codec volume settings after rate change (PMCLK stop) */
+ snd_wm8776_volume_restore(&spec->wm8776);
+ snd_wm8766_volume_restore(&spec->wm8766);
+}
+
+/* power management */
+
+#ifdef CONFIG_PM_SLEEP
+static int psc724_resume(struct snd_ice1712 *ice)
+{
+ struct psc724_spec *spec = ice->spec;
+
+ snd_wm8776_resume(&spec->wm8776);
+ snd_wm8766_resume(&spec->wm8766);
+
+ return 0;
+}
+#endif
+
+/* init */
+
+static int psc724_init(struct snd_ice1712 *ice)
+{
+ struct psc724_spec *spec;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+ spec->ice = ice;
+
+ ice->num_total_dacs = 6;
+ ice->num_total_adcs = 2;
+ spec->wm8776.ops.write = psc724_wm8776_write;
+ spec->wm8776.card = ice->card;
+ snd_wm8776_init(&spec->wm8776);
+ spec->wm8766.ops.write = psc724_wm8766_write;
+ spec->wm8766.card = ice->card;
+#ifdef CONFIG_PM_SLEEP
+ ice->pm_resume = psc724_resume;
+ ice->pm_suspend_enabled = 1;
+#endif
+ snd_wm8766_init(&spec->wm8766);
+ snd_wm8766_set_if(&spec->wm8766,
+ WM8766_IF_FMT_I2S | WM8766_IF_IWL_24BIT);
+ ice->gpio.set_pro_rate = psc724_set_pro_rate;
+ INIT_DELAYED_WORK(&spec->hp_work, psc724_update_hp_jack_state);
+ psc724_set_jack_detection(ice, true);
+ return 0;
+}
+
+static void psc724_exit(struct snd_ice1712 *ice)
+{
+ struct psc724_spec *spec = ice->spec;
+
+ cancel_delayed_work_sync(&spec->hp_work);
+}
+
+/* PSC724 has buggy EEPROM (no 96&192kHz, all FFh GPIOs), so override it here */
+static const unsigned char psc724_eeprom[] = {
+ [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 */
+ /* GPIO outputs */
+ [ICE_EEP2_GPIO_DIR2] = 0x5f, /* MUTE_ALL,WM8766 MUTE/MODE/ML/MC/MD */
+ /* GPIO write enable */
+ [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 */
+ /* GPIO initial state */
+ [ICE_EEP2_GPIO_STATE2] = 0x20, /* unmuted, all WM8766 pins low */
+};
+
+struct snd_ice1712_card_info snd_vt1724_psc724_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_PSC724,
+ .name = "Philips PSC724 Ultimate Edge",
+ .model = "psc724",
+ .chip_init = psc724_init,
+ .chip_exit = psc724_exit,
+ .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 0000000000..e6ce335ae8
--- /dev/null
+++ b/sound/pci/ice1712/psc724.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#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 */
diff --git a/sound/pci/ice1712/quartet.c b/sound/pci/ice1712/quartet.c
new file mode 100644
index 0000000000..f61ee9f5c7
--- /dev/null
+++ b/sound/pci/ice1712/quartet.c
@@ -0,0 +1,1070 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Infrasonic Quartet
+ *
+ * Copyright (c) 2009 Pavel Hofman <pavel.hofman@ivitera.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <sound/info.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include <sound/ak4113.h>
+#include "quartet.h"
+
+struct qtet_spec {
+ struct ak4113 *ak4113;
+ unsigned int scr; /* system control register */
+ unsigned int mcr; /* monitoring control register */
+ unsigned int cpld; /* cpld register */
+};
+
+struct qtet_kcontrol_private {
+ unsigned int bit;
+ void (*set_register)(struct snd_ice1712 *ice, unsigned int val);
+ unsigned int (*get_register)(struct snd_ice1712 *ice);
+ const char * const texts[2];
+};
+
+enum {
+ IN12_SEL = 0,
+ IN34_SEL,
+ AIN34_SEL,
+ COAX_OUT,
+ IN12_MON12,
+ IN12_MON34,
+ IN34_MON12,
+ IN34_MON34,
+ OUT12_MON34,
+ OUT34_MON12,
+};
+
+static const char * const ext_clock_names[3] = {"IEC958 In", "Word Clock 1xFS",
+ "Word Clock 256xFS"};
+
+/* chip address on I2C bus */
+#define AK4113_ADDR 0x26 /* S/PDIF receiver */
+
+/* chip address on SPI bus */
+#define AK4620_ADDR 0x02 /* ADC/DAC */
+
+
+/*
+ * GPIO pins
+ */
+
+/* GPIO0 - O - DATA0, def. 0 */
+#define GPIO_D0 (1<<0)
+/* GPIO1 - I/O - DATA1, Jack Detect Input0 (0:present, 1:missing), def. 1 */
+#define GPIO_D1_JACKDTC0 (1<<1)
+/* GPIO2 - I/O - DATA2, Jack Detect Input1 (0:present, 1:missing), def. 1 */
+#define GPIO_D2_JACKDTC1 (1<<2)
+/* GPIO3 - I/O - DATA3, def. 1 */
+#define GPIO_D3 (1<<3)
+/* GPIO4 - I/O - DATA4, SPI CDTO, def. 1 */
+#define GPIO_D4_SPI_CDTO (1<<4)
+/* GPIO5 - I/O - DATA5, SPI CCLK, def. 1 */
+#define GPIO_D5_SPI_CCLK (1<<5)
+/* GPIO6 - I/O - DATA6, Cable Detect Input (0:detected, 1:not detected */
+#define GPIO_D6_CD (1<<6)
+/* GPIO7 - I/O - DATA7, Device Detect Input (0:detected, 1:not detected */
+#define GPIO_D7_DD (1<<7)
+/* GPIO8 - O - CPLD Chip Select, def. 1 */
+#define GPIO_CPLD_CSN (1<<8)
+/* GPIO9 - O - CPLD register read/write (0:write, 1:read), def. 0 */
+#define GPIO_CPLD_RW (1<<9)
+/* GPIO10 - O - SPI Chip Select for CODEC#0, def. 1 */
+#define GPIO_SPI_CSN0 (1<<10)
+/* GPIO11 - O - SPI Chip Select for CODEC#1, def. 1 */
+#define GPIO_SPI_CSN1 (1<<11)
+/* GPIO12 - O - Ex. Register Output Enable (0:enable, 1:disable), def. 1,
+ * init 0 */
+#define GPIO_EX_GPIOE (1<<12)
+/* GPIO13 - O - Ex. Register0 Chip Select for System Control Register,
+ * def. 1 */
+#define GPIO_SCR (1<<13)
+/* GPIO14 - O - Ex. Register1 Chip Select for Monitor Control Register,
+ * def. 1 */
+#define GPIO_MCR (1<<14)
+
+#define GPIO_SPI_ALL (GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK |\
+ GPIO_SPI_CSN0 | GPIO_SPI_CSN1)
+
+#define GPIO_DATA_MASK (GPIO_D0 | GPIO_D1_JACKDTC0 | \
+ GPIO_D2_JACKDTC1 | GPIO_D3 | \
+ GPIO_D4_SPI_CDTO | GPIO_D5_SPI_CCLK | \
+ GPIO_D6_CD | GPIO_D7_DD)
+
+/* System Control Register GPIO_SCR data bits */
+/* Mic/Line select relay (0:line, 1:mic) */
+#define SCR_RELAY GPIO_D0
+/* Phantom power drive control (0:5V, 1:48V) */
+#define SCR_PHP_V GPIO_D1_JACKDTC0
+/* H/W mute control (0:Normal, 1:Mute) */
+#define SCR_MUTE GPIO_D2_JACKDTC1
+/* Phantom power control (0:Phantom on, 1:off) */
+#define SCR_PHP GPIO_D3
+/* Analog input 1/2 Source Select */
+#define SCR_AIN12_SEL0 GPIO_D4_SPI_CDTO
+#define SCR_AIN12_SEL1 GPIO_D5_SPI_CCLK
+/* Analog input 3/4 Source Select (0:line, 1:hi-z) */
+#define SCR_AIN34_SEL GPIO_D6_CD
+/* Codec Power Down (0:power down, 1:normal) */
+#define SCR_CODEC_PDN GPIO_D7_DD
+
+#define SCR_AIN12_LINE (0)
+#define SCR_AIN12_MIC (SCR_AIN12_SEL0)
+#define SCR_AIN12_LOWCUT (SCR_AIN12_SEL1 | SCR_AIN12_SEL0)
+
+/* Monitor Control Register GPIO_MCR data bits */
+/* Input 1/2 to Monitor 1/2 (0:off, 1:on) */
+#define MCR_IN12_MON12 GPIO_D0
+/* Input 1/2 to Monitor 3/4 (0:off, 1:on) */
+#define MCR_IN12_MON34 GPIO_D1_JACKDTC0
+/* Input 3/4 to Monitor 1/2 (0:off, 1:on) */
+#define MCR_IN34_MON12 GPIO_D2_JACKDTC1
+/* Input 3/4 to Monitor 3/4 (0:off, 1:on) */
+#define MCR_IN34_MON34 GPIO_D3
+/* Output to Monitor 1/2 (0:off, 1:on) */
+#define MCR_OUT34_MON12 GPIO_D4_SPI_CDTO
+/* Output to Monitor 3/4 (0:off, 1:on) */
+#define MCR_OUT12_MON34 GPIO_D5_SPI_CCLK
+
+/* CPLD Register DATA bits */
+/* Clock Rate Select */
+#define CPLD_CKS0 GPIO_D0
+#define CPLD_CKS1 GPIO_D1_JACKDTC0
+#define CPLD_CKS2 GPIO_D2_JACKDTC1
+/* Sync Source Select (0:Internal, 1:External) */
+#define CPLD_SYNC_SEL GPIO_D3
+/* Word Clock FS Select (0:FS, 1:256FS) */
+#define CPLD_WORD_SEL GPIO_D4_SPI_CDTO
+/* Coaxial Output Source (IS-Link) (0:SPDIF, 1:I2S) */
+#define CPLD_COAX_OUT GPIO_D5_SPI_CCLK
+/* Input 1/2 Source Select (0:Analog12, 1:An34) */
+#define CPLD_IN12_SEL GPIO_D6_CD
+/* Input 3/4 Source Select (0:Analog34, 1:Digital In) */
+#define CPLD_IN34_SEL GPIO_D7_DD
+
+/* internal clock (CPLD_SYNC_SEL = 0) options */
+#define CPLD_CKS_44100HZ (0)
+#define CPLD_CKS_48000HZ (CPLD_CKS0)
+#define CPLD_CKS_88200HZ (CPLD_CKS1)
+#define CPLD_CKS_96000HZ (CPLD_CKS1 | CPLD_CKS0)
+#define CPLD_CKS_176400HZ (CPLD_CKS2)
+#define CPLD_CKS_192000HZ (CPLD_CKS2 | CPLD_CKS0)
+
+#define CPLD_CKS_MASK (CPLD_CKS0 | CPLD_CKS1 | CPLD_CKS2)
+
+/* external clock (CPLD_SYNC_SEL = 1) options */
+/* external clock - SPDIF */
+#define CPLD_EXT_SPDIF (0 | CPLD_SYNC_SEL)
+/* external clock - WordClock 1xfs */
+#define CPLD_EXT_WORDCLOCK_1FS (CPLD_CKS1 | CPLD_SYNC_SEL)
+/* external clock - WordClock 256xfs */
+#define CPLD_EXT_WORDCLOCK_256FS (CPLD_CKS1 | CPLD_WORD_SEL |\
+ CPLD_SYNC_SEL)
+
+#define EXT_SPDIF_TYPE 0
+#define EXT_WORDCLOCK_1FS_TYPE 1
+#define EXT_WORDCLOCK_256FS_TYPE 2
+
+#define AK4620_DFS0 (1<<0)
+#define AK4620_DFS1 (1<<1)
+#define AK4620_CKS0 (1<<2)
+#define AK4620_CKS1 (1<<3)
+/* Clock and Format Control register */
+#define AK4620_DFS_REG 0x02
+
+/* Deem and Volume Control register */
+#define AK4620_DEEMVOL_REG 0x03
+#define AK4620_SMUTE (1<<7)
+
+/*
+ * Conversion from int value to its binary form. Used for debugging.
+ * The output buffer must be allocated prior to calling the function.
+ */
+static char *get_binary(char *buffer, int value)
+{
+ int i, j, pos;
+ pos = 0;
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < 8; ++j) {
+ if (value & (1 << (31-(i*8 + j))))
+ buffer[pos] = '1';
+ else
+ buffer[pos] = '0';
+ pos++;
+ }
+ if (i < 3) {
+ buffer[pos] = ' ';
+ pos++;
+ }
+ }
+ buffer[pos] = '\0';
+ return buffer;
+}
+
+/*
+ * Initial setup of the conversion array GPIO <-> rate
+ */
+static const unsigned int qtet_rates[] = {
+ 44100, 48000, 88200,
+ 96000, 176400, 192000,
+};
+
+static const unsigned int cks_vals[] = {
+ CPLD_CKS_44100HZ, CPLD_CKS_48000HZ, CPLD_CKS_88200HZ,
+ CPLD_CKS_96000HZ, CPLD_CKS_176400HZ, CPLD_CKS_192000HZ,
+};
+
+static const struct snd_pcm_hw_constraint_list qtet_rates_info = {
+ .count = ARRAY_SIZE(qtet_rates),
+ .list = qtet_rates,
+ .mask = 0,
+};
+
+static void qtet_ak4113_write(void *private_data, unsigned char reg,
+ unsigned char val)
+{
+ snd_vt1724_write_i2c((struct snd_ice1712 *)private_data, AK4113_ADDR,
+ reg, val);
+}
+
+static unsigned char qtet_ak4113_read(void *private_data, unsigned char reg)
+{
+ return snd_vt1724_read_i2c((struct snd_ice1712 *)private_data,
+ AK4113_ADDR, reg);
+}
+
+
+/*
+ * AK4620 section
+ */
+
+/*
+ * Write data to addr register of ak4620
+ */
+static void qtet_akm_write(struct snd_akm4xxx *ak, int chip,
+ unsigned char addr, unsigned char data)
+{
+ unsigned int tmp, orig_dir;
+ int idx;
+ unsigned int addrdata;
+ struct snd_ice1712 *ice = ak->private_data[0];
+
+ if (snd_BUG_ON(chip < 0 || chip >= 4))
+ return;
+ /*dev_dbg(ice->card->dev, "Writing to AK4620: chip=%d, addr=0x%x,
+ data=0x%x\n", chip, addr, data);*/
+ orig_dir = ice->gpio.get_dir(ice);
+ ice->gpio.set_dir(ice, orig_dir | GPIO_SPI_ALL);
+ /* set mask - only SPI bits */
+ ice->gpio.set_mask(ice, ~GPIO_SPI_ALL);
+
+ tmp = ice->gpio.get_data(ice);
+ /* high all */
+ tmp |= GPIO_SPI_ALL;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+ /* drop chip select */
+ if (chip)
+ /* CODEC 1 */
+ tmp &= ~GPIO_SPI_CSN1;
+ else
+ tmp &= ~GPIO_SPI_CSN0;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+
+ /* build I2C address + data byte */
+ addrdata = (AK4620_ADDR << 6) | 0x20 | (addr & 0x1f);
+ addrdata = (addrdata << 8) | data;
+ for (idx = 15; idx >= 0; idx--) {
+ /* drop clock */
+ tmp &= ~GPIO_D5_SPI_CCLK;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+ /* set data */
+ if (addrdata & (1 << idx))
+ tmp |= GPIO_D4_SPI_CDTO;
+ else
+ tmp &= ~GPIO_D4_SPI_CDTO;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+ /* raise clock */
+ tmp |= GPIO_D5_SPI_CCLK;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+ }
+ /* all back to 1 */
+ tmp |= GPIO_SPI_ALL;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+
+ /* return all gpios to non-writable */
+ ice->gpio.set_mask(ice, 0xffffff);
+ /* restore GPIOs direction */
+ ice->gpio.set_dir(ice, orig_dir);
+}
+
+static void qtet_akm_set_regs(struct snd_akm4xxx *ak, unsigned char addr,
+ unsigned char mask, unsigned char value)
+{
+ unsigned char tmp;
+ int chip;
+ for (chip = 0; chip < ak->num_chips; chip++) {
+ tmp = snd_akm4xxx_get(ak, chip, addr);
+ /* clear the bits */
+ tmp &= ~mask;
+ /* set the new bits */
+ tmp |= value;
+ snd_akm4xxx_write(ak, chip, addr, tmp);
+ }
+}
+
+/*
+ * change the rate of AK4620
+ */
+static void qtet_akm_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+ unsigned char ak4620_dfs;
+
+ if (rate == 0) /* no hint - S/PDIF input is master or the new spdif
+ input rate undetected, simply return */
+ return;
+
+ /* adjust DFS on codecs - see datasheet */
+ if (rate > 108000)
+ ak4620_dfs = AK4620_DFS1 | AK4620_CKS1;
+ else if (rate > 54000)
+ ak4620_dfs = AK4620_DFS0 | AK4620_CKS0;
+ else
+ ak4620_dfs = 0;
+
+ /* set new value */
+ qtet_akm_set_regs(ak, AK4620_DFS_REG, AK4620_DFS0 | AK4620_DFS1 |
+ AK4620_CKS0 | AK4620_CKS1, ak4620_dfs);
+}
+
+#define AK_CONTROL(xname, xch) { .name = xname, .num_channels = xch }
+
+#define PCM_12_PLAYBACK_VOLUME "PCM 1/2 Playback Volume"
+#define PCM_34_PLAYBACK_VOLUME "PCM 3/4 Playback Volume"
+#define PCM_12_CAPTURE_VOLUME "PCM 1/2 Capture Volume"
+#define PCM_34_CAPTURE_VOLUME "PCM 3/4 Capture Volume"
+
+static const struct snd_akm4xxx_dac_channel qtet_dac[] = {
+ AK_CONTROL(PCM_12_PLAYBACK_VOLUME, 2),
+ AK_CONTROL(PCM_34_PLAYBACK_VOLUME, 2),
+};
+
+static const struct snd_akm4xxx_adc_channel qtet_adc[] = {
+ AK_CONTROL(PCM_12_CAPTURE_VOLUME, 2),
+ AK_CONTROL(PCM_34_CAPTURE_VOLUME, 2),
+};
+
+static const struct snd_akm4xxx akm_qtet_dac = {
+ .type = SND_AK4620,
+ .num_dacs = 4, /* DAC1 - Output 12
+ */
+ .num_adcs = 4, /* ADC1 - Input 12
+ */
+ .ops = {
+ .write = qtet_akm_write,
+ .set_rate_val = qtet_akm_set_rate_val,
+ },
+ .dac_info = qtet_dac,
+ .adc_info = qtet_adc,
+};
+
+/* Communication routines with the CPLD */
+
+
+/* Writes data to external register reg, both reg and data are
+ * GPIO representations */
+static void reg_write(struct snd_ice1712 *ice, unsigned int reg,
+ unsigned int data)
+{
+ unsigned int tmp;
+
+ mutex_lock(&ice->gpio_mutex);
+ /* set direction of used GPIOs*/
+ /* all outputs */
+ tmp = 0x00ffff;
+ ice->gpio.set_dir(ice, tmp);
+ /* mask - writable bits */
+ ice->gpio.set_mask(ice, ~(tmp));
+ /* write the data */
+ tmp = ice->gpio.get_data(ice);
+ tmp &= ~GPIO_DATA_MASK;
+ tmp |= data;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+ /* drop output enable */
+ tmp &= ~GPIO_EX_GPIOE;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+ /* drop the register gpio */
+ tmp &= ~reg;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+ /* raise the register GPIO */
+ tmp |= reg;
+ ice->gpio.set_data(ice, tmp);
+ udelay(100);
+
+ /* raise all data gpios */
+ tmp |= GPIO_DATA_MASK;
+ ice->gpio.set_data(ice, tmp);
+ /* mask - immutable bits */
+ ice->gpio.set_mask(ice, 0xffffff);
+ /* outputs only 8-15 */
+ ice->gpio.set_dir(ice, 0x00ff00);
+ mutex_unlock(&ice->gpio_mutex);
+}
+
+static unsigned int get_scr(struct snd_ice1712 *ice)
+{
+ struct qtet_spec *spec = ice->spec;
+ return spec->scr;
+}
+
+static unsigned int get_mcr(struct snd_ice1712 *ice)
+{
+ struct qtet_spec *spec = ice->spec;
+ return spec->mcr;
+}
+
+static unsigned int get_cpld(struct snd_ice1712 *ice)
+{
+ struct qtet_spec *spec = ice->spec;
+ return spec->cpld;
+}
+
+static void set_scr(struct snd_ice1712 *ice, unsigned int val)
+{
+ struct qtet_spec *spec = ice->spec;
+ reg_write(ice, GPIO_SCR, val);
+ spec->scr = val;
+}
+
+static void set_mcr(struct snd_ice1712 *ice, unsigned int val)
+{
+ struct qtet_spec *spec = ice->spec;
+ reg_write(ice, GPIO_MCR, val);
+ spec->mcr = val;
+}
+
+static void set_cpld(struct snd_ice1712 *ice, unsigned int val)
+{
+ struct qtet_spec *spec = ice->spec;
+ reg_write(ice, GPIO_CPLD_CSN, val);
+ spec->cpld = val;
+}
+
+static void proc_regs_read(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_ice1712 *ice = entry->private_data;
+ char bin_buffer[36];
+
+ snd_iprintf(buffer, "SCR: %s\n", get_binary(bin_buffer,
+ get_scr(ice)));
+ snd_iprintf(buffer, "MCR: %s\n", get_binary(bin_buffer,
+ get_mcr(ice)));
+ snd_iprintf(buffer, "CPLD: %s\n", get_binary(bin_buffer,
+ get_cpld(ice)));
+}
+
+static void proc_init(struct snd_ice1712 *ice)
+{
+ snd_card_ro_proc_new(ice->card, "quartet", ice, proc_regs_read);
+}
+
+static int qtet_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ val = get_scr(ice) & SCR_MUTE;
+ ucontrol->value.integer.value[0] = (val) ? 0 : 1;
+ return 0;
+}
+
+static int qtet_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int old, new, smute;
+ old = get_scr(ice) & SCR_MUTE;
+ if (ucontrol->value.integer.value[0]) {
+ /* unmute */
+ new = 0;
+ /* un-smuting DAC */
+ smute = 0;
+ } else {
+ /* mute */
+ new = SCR_MUTE;
+ /* smuting DAC */
+ smute = AK4620_SMUTE;
+ }
+ if (old != new) {
+ struct snd_akm4xxx *ak = ice->akm;
+ set_scr(ice, (get_scr(ice) & ~SCR_MUTE) | new);
+ /* set smute */
+ qtet_akm_set_regs(ak, AK4620_DEEMVOL_REG, AK4620_SMUTE, smute);
+ return 1;
+ }
+ /* no change */
+ return 0;
+}
+
+static int qtet_ain12_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[3] =
+ {"Line In 1/2", "Mic", "Mic + Low-cut"};
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts);
+}
+
+static int qtet_ain12_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val, result;
+ val = get_scr(ice) & (SCR_AIN12_SEL1 | SCR_AIN12_SEL0);
+ switch (val) {
+ case SCR_AIN12_LINE:
+ result = 0;
+ break;
+ case SCR_AIN12_MIC:
+ result = 1;
+ break;
+ case SCR_AIN12_LOWCUT:
+ result = 2;
+ break;
+ default:
+ /* BUG - no other combinations allowed */
+ snd_BUG();
+ result = 0;
+ }
+ ucontrol->value.integer.value[0] = result;
+ return 0;
+}
+
+static int qtet_ain12_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int old, new, tmp, masked_old;
+ old = get_scr(ice);
+ masked_old = old & (SCR_AIN12_SEL1 | SCR_AIN12_SEL0);
+ tmp = ucontrol->value.integer.value[0];
+ if (tmp == 2)
+ tmp = 3; /* binary 10 is not supported */
+ tmp <<= 4; /* shifting to SCR_AIN12_SEL0 */
+ if (tmp != masked_old) {
+ /* change requested */
+ switch (tmp) {
+ case SCR_AIN12_LINE:
+ new = old & ~(SCR_AIN12_SEL1 | SCR_AIN12_SEL0);
+ set_scr(ice, new);
+ /* turn off relay */
+ new &= ~SCR_RELAY;
+ set_scr(ice, new);
+ break;
+ case SCR_AIN12_MIC:
+ /* turn on relay */
+ new = old | SCR_RELAY;
+ set_scr(ice, new);
+ new = (new & ~SCR_AIN12_SEL1) | SCR_AIN12_SEL0;
+ set_scr(ice, new);
+ break;
+ case SCR_AIN12_LOWCUT:
+ /* turn on relay */
+ new = old | SCR_RELAY;
+ set_scr(ice, new);
+ new |= SCR_AIN12_SEL1 | SCR_AIN12_SEL0;
+ set_scr(ice, new);
+ break;
+ default:
+ snd_BUG();
+ }
+ return 1;
+ }
+ /* no change */
+ return 0;
+}
+
+static int qtet_php_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int val;
+ /* if phantom voltage =48V, phantom on */
+ val = get_scr(ice) & SCR_PHP_V;
+ ucontrol->value.integer.value[0] = val ? 1 : 0;
+ return 0;
+}
+
+static int qtet_php_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int old, new;
+ old = new = get_scr(ice);
+ if (ucontrol->value.integer.value[0] /* phantom on requested */
+ && (~old & SCR_PHP_V)) /* 0 = voltage 5V */ {
+ /* is off, turn on */
+ /* turn voltage on first, = 1 */
+ new = old | SCR_PHP_V;
+ set_scr(ice, new);
+ /* turn phantom on, = 0 */
+ new &= ~SCR_PHP;
+ set_scr(ice, new);
+ } else if (!ucontrol->value.integer.value[0] && (old & SCR_PHP_V)) {
+ /* phantom off requested and 1 = voltage 48V */
+ /* is on, turn off */
+ /* turn voltage off first, = 0 */
+ new = old & ~SCR_PHP_V;
+ set_scr(ice, new);
+ /* turn phantom off, = 1 */
+ new |= SCR_PHP;
+ set_scr(ice, new);
+ }
+ if (old != new)
+ return 1;
+ /* no change */
+ return 0;
+}
+
+#define PRIV_SW(xid, xbit, xreg) [xid] = {.bit = xbit,\
+ .set_register = set_##xreg,\
+ .get_register = get_##xreg, }
+
+
+#define PRIV_ENUM2(xid, xbit, xreg, xtext1, xtext2) [xid] = {.bit = xbit,\
+ .set_register = set_##xreg,\
+ .get_register = get_##xreg,\
+ .texts = {xtext1, xtext2} }
+
+static const struct qtet_kcontrol_private qtet_privates[] = {
+ PRIV_ENUM2(IN12_SEL, CPLD_IN12_SEL, cpld, "An In 1/2", "An In 3/4"),
+ PRIV_ENUM2(IN34_SEL, CPLD_IN34_SEL, cpld, "An In 3/4", "IEC958 In"),
+ PRIV_ENUM2(AIN34_SEL, SCR_AIN34_SEL, scr, "Line In 3/4", "Hi-Z"),
+ PRIV_ENUM2(COAX_OUT, CPLD_COAX_OUT, cpld, "IEC958", "I2S"),
+ PRIV_SW(IN12_MON12, MCR_IN12_MON12, mcr),
+ PRIV_SW(IN12_MON34, MCR_IN12_MON34, mcr),
+ PRIV_SW(IN34_MON12, MCR_IN34_MON12, mcr),
+ PRIV_SW(IN34_MON34, MCR_IN34_MON34, mcr),
+ PRIV_SW(OUT12_MON34, MCR_OUT12_MON34, mcr),
+ PRIV_SW(OUT34_MON12, MCR_OUT34_MON12, mcr),
+};
+
+static int qtet_enum_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct qtet_kcontrol_private private =
+ qtet_privates[kcontrol->private_value];
+ return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(private.texts),
+ private.texts);
+}
+
+static int qtet_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct qtet_kcontrol_private private =
+ qtet_privates[kcontrol->private_value];
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ ucontrol->value.integer.value[0] =
+ (private.get_register(ice) & private.bit) ? 1 : 0;
+ return 0;
+}
+
+static int qtet_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct qtet_kcontrol_private private =
+ qtet_privates[kcontrol->private_value];
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned int old, new;
+ old = private.get_register(ice);
+ if (ucontrol->value.integer.value[0])
+ new = old | private.bit;
+ else
+ new = old & ~private.bit;
+ if (old != new) {
+ private.set_register(ice, new);
+ return 1;
+ }
+ /* no change */
+ return 0;
+}
+
+#define qtet_sw_info snd_ctl_boolean_mono_info
+
+#define QTET_CONTROL(xname, xtype, xpriv) \
+ {.iface = SNDRV_CTL_ELEM_IFACE_MIXER,\
+ .name = xname,\
+ .info = qtet_##xtype##_info,\
+ .get = qtet_sw_get,\
+ .put = qtet_sw_put,\
+ .private_value = xpriv }
+
+static const struct snd_kcontrol_new qtet_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Switch",
+ .info = qtet_sw_info,
+ .get = qtet_mute_get,
+ .put = qtet_mute_put,
+ .private_value = 0
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Phantom Power",
+ .info = qtet_sw_info,
+ .get = qtet_php_get,
+ .put = qtet_php_put,
+ .private_value = 0
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog In 1/2 Capture Switch",
+ .info = qtet_ain12_enum_info,
+ .get = qtet_ain12_sw_get,
+ .put = qtet_ain12_sw_put,
+ .private_value = 0
+ },
+ QTET_CONTROL("Analog In 3/4 Capture Switch", enum, AIN34_SEL),
+ QTET_CONTROL("PCM In 1/2 Capture Switch", enum, IN12_SEL),
+ QTET_CONTROL("PCM In 3/4 Capture Switch", enum, IN34_SEL),
+ QTET_CONTROL("Coax Output Source", enum, COAX_OUT),
+ QTET_CONTROL("Analog In 1/2 to Monitor 1/2", sw, IN12_MON12),
+ QTET_CONTROL("Analog In 1/2 to Monitor 3/4", sw, IN12_MON34),
+ QTET_CONTROL("Analog In 3/4 to Monitor 1/2", sw, IN34_MON12),
+ QTET_CONTROL("Analog In 3/4 to Monitor 3/4", sw, IN34_MON34),
+ QTET_CONTROL("Output 1/2 to Monitor 3/4", sw, OUT12_MON34),
+ QTET_CONTROL("Output 3/4 to Monitor 1/2", sw, OUT34_MON12),
+};
+
+static const char * const follower_vols[] = {
+ PCM_12_PLAYBACK_VOLUME,
+ PCM_34_PLAYBACK_VOLUME,
+ NULL
+};
+
+static
+DECLARE_TLV_DB_SCALE(qtet_master_db_scale, -6350, 50, 1);
+
+static int qtet_add_controls(struct snd_ice1712 *ice)
+{
+ struct qtet_spec *spec = ice->spec;
+ int err, i;
+ struct snd_kcontrol *vmaster;
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+ for (i = 0; i < ARRAY_SIZE(qtet_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&qtet_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+
+ /* Create virtual master control */
+ vmaster = snd_ctl_make_virtual_master("Master Playback Volume",
+ qtet_master_db_scale);
+ if (!vmaster)
+ return -ENOMEM;
+ err = snd_ctl_add(ice->card, vmaster);
+ if (err < 0)
+ return err;
+ err = snd_ctl_add_followers(ice->card, vmaster, follower_vols);
+ if (err < 0)
+ return err;
+ /* only capture SPDIF over AK4113 */
+ return snd_ak4113_build(spec->ak4113,
+ ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+}
+
+static inline int qtet_is_spdif_master(struct snd_ice1712 *ice)
+{
+ /* CPLD_SYNC_SEL: 0 = internal, 1 = external (i.e. spdif master) */
+ return (get_cpld(ice) & CPLD_SYNC_SEL) ? 1 : 0;
+}
+
+static unsigned int qtet_get_rate(struct snd_ice1712 *ice)
+{
+ int i;
+ unsigned char result;
+
+ result = get_cpld(ice) & CPLD_CKS_MASK;
+ for (i = 0; i < ARRAY_SIZE(cks_vals); i++)
+ if (cks_vals[i] == result)
+ return qtet_rates[i];
+ return 0;
+}
+
+static int get_cks_val(int rate)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(qtet_rates); i++)
+ if (qtet_rates[i] == rate)
+ return cks_vals[i];
+ return 0;
+}
+
+/* setting new rate */
+static void qtet_set_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+ unsigned int new;
+ unsigned char val;
+ /* switching ice1724 to external clock - supplied by ext. circuits */
+ val = inb(ICEMT1724(ice, RATE));
+ outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+
+ new = (get_cpld(ice) & ~CPLD_CKS_MASK) | get_cks_val(rate);
+ /* switch to internal clock, drop CPLD_SYNC_SEL */
+ new &= ~CPLD_SYNC_SEL;
+ /* dev_dbg(ice->card->dev, "QT - set_rate: old %x, new %x\n",
+ get_cpld(ice), new); */
+ set_cpld(ice, new);
+}
+
+static inline unsigned char qtet_set_mclk(struct snd_ice1712 *ice,
+ unsigned int rate)
+{
+ /* no change in master clock */
+ return 0;
+}
+
+/* setting clock to external - SPDIF */
+static int qtet_set_spdif_clock(struct snd_ice1712 *ice, int type)
+{
+ unsigned int old, new;
+
+ old = new = get_cpld(ice);
+ new &= ~(CPLD_CKS_MASK | CPLD_WORD_SEL);
+ switch (type) {
+ case EXT_SPDIF_TYPE:
+ new |= CPLD_EXT_SPDIF;
+ break;
+ case EXT_WORDCLOCK_1FS_TYPE:
+ new |= CPLD_EXT_WORDCLOCK_1FS;
+ break;
+ case EXT_WORDCLOCK_256FS_TYPE:
+ new |= CPLD_EXT_WORDCLOCK_256FS;
+ break;
+ default:
+ snd_BUG();
+ }
+ if (old != new) {
+ set_cpld(ice, new);
+ /* changed */
+ return 1;
+ }
+ return 0;
+}
+
+static int qtet_get_spdif_master_type(struct snd_ice1712 *ice)
+{
+ unsigned int val;
+ int result;
+ val = get_cpld(ice);
+ /* checking only rate/clock-related bits */
+ val &= (CPLD_CKS_MASK | CPLD_WORD_SEL | CPLD_SYNC_SEL);
+ if (!(val & CPLD_SYNC_SEL)) {
+ /* switched to internal clock, is not any external type */
+ result = -1;
+ } else {
+ switch (val) {
+ case (CPLD_EXT_SPDIF):
+ result = EXT_SPDIF_TYPE;
+ break;
+ case (CPLD_EXT_WORDCLOCK_1FS):
+ result = EXT_WORDCLOCK_1FS_TYPE;
+ break;
+ case (CPLD_EXT_WORDCLOCK_256FS):
+ result = EXT_WORDCLOCK_256FS_TYPE;
+ break;
+ default:
+ /* undefined combination of external clock setup */
+ snd_BUG();
+ result = 0;
+ }
+ }
+ return result;
+}
+
+/* Called when ak4113 detects change in the input SPDIF stream */
+static void qtet_ak4113_change(struct ak4113 *ak4113, unsigned char c0,
+ unsigned char c1)
+{
+ struct snd_ice1712 *ice = ak4113->change_callback_private;
+ int rate;
+ if ((qtet_get_spdif_master_type(ice) == EXT_SPDIF_TYPE) &&
+ c1) {
+ /* only for SPDIF master mode, rate was changed */
+ rate = snd_ak4113_external_rate(ak4113);
+ /* dev_dbg(ice->card->dev, "ak4113 - input rate changed to %d\n",
+ rate); */
+ qtet_akm_set_rate_val(ice->akm, rate);
+ }
+}
+
+/*
+ * If clock slaved to SPDIF-IN, setting runtime rate
+ * to the detected external rate
+ */
+static void qtet_spdif_in_open(struct snd_ice1712 *ice,
+ struct snd_pcm_substream *substream)
+{
+ struct qtet_spec *spec = ice->spec;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int rate;
+
+ if (qtet_get_spdif_master_type(ice) != EXT_SPDIF_TYPE)
+ /* not external SPDIF, no rate limitation */
+ return;
+ /* only external SPDIF can detect incoming sample rate */
+ rate = snd_ak4113_external_rate(spec->ak4113);
+ if (rate >= runtime->hw.rate_min && rate <= runtime->hw.rate_max) {
+ runtime->hw.rate_min = rate;
+ runtime->hw.rate_max = rate;
+ }
+}
+
+/*
+ * initialize the chip
+ */
+static int qtet_init(struct snd_ice1712 *ice)
+{
+ static const unsigned char ak4113_init_vals[] = {
+ /* AK4113_REG_PWRDN */ AK4113_RST | AK4113_PWN |
+ AK4113_OCKS0 | AK4113_OCKS1,
+ /* AK4113_REQ_FORMAT */ AK4113_DIF_I24I2S | AK4113_VTX |
+ AK4113_DEM_OFF | AK4113_DEAU,
+ /* AK4113_REG_IO0 */ AK4113_OPS2 | AK4113_TXE |
+ AK4113_XTL_24_576M,
+ /* AK4113_REG_IO1 */ AK4113_EFH_1024LRCLK | AK4113_IPS(0),
+ /* AK4113_REG_INT0_MASK */ 0,
+ /* AK4113_REG_INT1_MASK */ 0,
+ /* AK4113_REG_DATDTS */ 0,
+ };
+ int err;
+ struct qtet_spec *spec;
+ struct snd_akm4xxx *ak;
+ unsigned char val;
+
+ /* switching ice1724 to external clock - supplied by ext. circuits */
+ val = inb(ICEMT1724(ice, RATE));
+ outb(val | VT1724_SPDIF_MASTER, ICEMT1724(ice, RATE));
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ /* qtet is clocked by Xilinx array */
+ ice->hw_rates = &qtet_rates_info;
+ ice->is_spdif_master = qtet_is_spdif_master;
+ ice->get_rate = qtet_get_rate;
+ ice->set_rate = qtet_set_rate;
+ ice->set_mclk = qtet_set_mclk;
+ ice->set_spdif_clock = qtet_set_spdif_clock;
+ ice->get_spdif_master_type = qtet_get_spdif_master_type;
+ ice->ext_clock_names = ext_clock_names;
+ ice->ext_clock_count = ARRAY_SIZE(ext_clock_names);
+ /* since Qtet can detect correct SPDIF-in rate, all streams can be
+ * limited to this specific rate */
+ ice->spdif.ops.open = ice->pro_open = qtet_spdif_in_open;
+ ice->spec = spec;
+
+ /* Mute Off */
+ /* SCR Initialize*/
+ /* keep codec power down first */
+ set_scr(ice, SCR_PHP);
+ udelay(1);
+ /* codec power up */
+ set_scr(ice, SCR_PHP | SCR_CODEC_PDN);
+
+ /* MCR Initialize */
+ set_mcr(ice, 0);
+
+ /* CPLD Initialize */
+ set_cpld(ice, 0);
+
+
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 2;
+
+ ice->akm = kcalloc(2, sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ ak = ice->akm;
+ if (!ak)
+ return -ENOMEM;
+ /* only one codec with two chips */
+ ice->akm_codecs = 1;
+ err = snd_ice1712_akm4xxx_init(ak, &akm_qtet_dac, NULL, ice);
+ if (err < 0)
+ return err;
+ err = snd_ak4113_create(ice->card,
+ qtet_ak4113_read,
+ qtet_ak4113_write,
+ ak4113_init_vals,
+ ice, &spec->ak4113);
+ if (err < 0)
+ return err;
+ /* callback for codecs rate setting */
+ spec->ak4113->change_callback = qtet_ak4113_change;
+ spec->ak4113->change_callback_private = ice;
+ /* AK41143 in Quartet can detect external rate correctly
+ * (i.e. check_flags = 0) */
+ spec->ak4113->check_flags = 0;
+
+ proc_init(ice);
+
+ qtet_set_rate(ice, 44100);
+ return 0;
+}
+
+static const unsigned char qtet_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x28, /* clock 256(24MHz), mpu401, 1xADC,
+ 1xDACs, SPDIF in */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0x78, /* 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, in, out-ext */
+ [ICE_EEP2_GPIO_DIR] = 0x00, /* 0-7 inputs, switched to output
+ only during output operations */
+ [ICE_EEP2_GPIO_DIR1] = 0xff, /* 8-15 outputs */
+ [ICE_EEP2_GPIO_DIR2] = 0x00,
+ [ICE_EEP2_GPIO_MASK] = 0xff, /* changed only for OUT operations */
+ [ICE_EEP2_GPIO_MASK1] = 0x00,
+ [ICE_EEP2_GPIO_MASK2] = 0xff,
+
+ [ICE_EEP2_GPIO_STATE] = 0x00, /* inputs */
+ [ICE_EEP2_GPIO_STATE1] = 0x7d, /* all 1, but GPIO_CPLD_RW
+ and GPIO15 always zero */
+ [ICE_EEP2_GPIO_STATE2] = 0x00, /* inputs */
+};
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_qtet_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_QTET,
+ .name = "Infrasonic Quartet",
+ .model = "quartet",
+ .chip_init = qtet_init,
+ .build_controls = qtet_add_controls,
+ .eeprom_size = sizeof(qtet_eeprom),
+ .eeprom_data = qtet_eeprom,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/quartet.h b/sound/pci/ice1712/quartet.h
new file mode 100644
index 0000000000..a1c2fe2718
--- /dev/null
+++ b/sound/pci/ice1712/quartet.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_QTET_H
+#define __SOUND_QTET_H
+
+#define QTET_DEVICE_DESC "{Infrasonic,Quartet},"
+
+#define VT1724_SUBDEVICE_QTET 0x30305349 /* Infrasonic Quartet */
+
+extern struct snd_ice1712_card_info snd_vt1724_qtet_cards[];
+
+#endif /* __SOUND_QTET_H */
diff --git a/sound/pci/ice1712/revo.c b/sound/pci/ice1712/revo.c
new file mode 100644
index 0000000000..bcf114152d
--- /dev/null
+++ b/sound/pci/ice1712/revo.c
@@ -0,0 +1,631 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for M-Audio Audiophile 192, Revolution 7.1 and 5.1
+ *
+ * Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "revo.h"
+
+/* a non-standard I2C device for revo51 */
+struct revo51_spec {
+ struct snd_i2c_device *dev;
+ struct snd_pt2258 *pt2258;
+ struct ak4114 *ak4114;
+};
+
+static void revo_i2s_mclk_changed(struct snd_ice1712 *ice)
+{
+ /* assert PRST# to converters; MT05 bit 7 */
+ outb(inb(ICEMT1724(ice, AC97_CMD)) | 0x80, ICEMT1724(ice, AC97_CMD));
+ mdelay(5);
+ /* deassert PRST# */
+ outb(inb(ICEMT1724(ice, AC97_CMD)) & ~0x80, ICEMT1724(ice, AC97_CMD));
+}
+
+/*
+ * change the rate of Envy24HT, AK4355 and AK4381
+ */
+static void revo_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+ unsigned char old, tmp, dfs;
+ int reg, shift;
+
+ if (rate == 0) /* no hint - S/PDIF input is master, simply return */
+ return;
+
+ /* adjust DFS on codecs */
+ if (rate > 96000)
+ dfs = 2;
+ else if (rate > 48000)
+ dfs = 1;
+ else
+ dfs = 0;
+
+ if (ak->type == SND_AK4355 || ak->type == SND_AK4358) {
+ reg = 2;
+ shift = 4;
+ } else {
+ reg = 1;
+ shift = 3;
+ }
+ tmp = snd_akm4xxx_get(ak, 0, reg);
+ old = (tmp >> shift) & 0x03;
+ if (old == dfs)
+ return;
+
+ /* reset DFS */
+ snd_akm4xxx_reset(ak, 1);
+ tmp = snd_akm4xxx_get(ak, 0, reg);
+ tmp &= ~(0x03 << shift);
+ tmp |= dfs << shift;
+ /* snd_akm4xxx_write(ak, 0, reg, tmp); */
+ snd_akm4xxx_set(ak, 0, reg, tmp); /* value is written in reset(0) */
+ snd_akm4xxx_reset(ak, 0);
+}
+
+/*
+ * I2C access to the PT2258 volume controller on GPIO 6/7 (Revolution 5.1)
+ */
+
+static void revo_i2c_start(struct snd_i2c_bus *bus)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ snd_ice1712_save_gpio_status(ice);
+}
+
+static void revo_i2c_stop(struct snd_i2c_bus *bus)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ snd_ice1712_restore_gpio_status(ice);
+}
+
+static void revo_i2c_direction(struct snd_i2c_bus *bus, int clock, int data)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ unsigned int mask, val;
+
+ val = 0;
+ if (clock)
+ val |= VT1724_REVO_I2C_CLOCK; /* write SCL */
+ if (data)
+ val |= VT1724_REVO_I2C_DATA; /* write SDA */
+ mask = VT1724_REVO_I2C_CLOCK | VT1724_REVO_I2C_DATA;
+ ice->gpio.direction &= ~mask;
+ ice->gpio.direction |= val;
+ snd_ice1712_gpio_set_dir(ice, ice->gpio.direction);
+ snd_ice1712_gpio_set_mask(ice, ~mask);
+}
+
+static void revo_i2c_setlines(struct snd_i2c_bus *bus, int clk, int data)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ unsigned int val = 0;
+
+ if (clk)
+ val |= VT1724_REVO_I2C_CLOCK;
+ if (data)
+ val |= VT1724_REVO_I2C_DATA;
+ snd_ice1712_gpio_write_bits(ice,
+ VT1724_REVO_I2C_DATA |
+ VT1724_REVO_I2C_CLOCK, val);
+ udelay(5);
+}
+
+static int revo_i2c_getdata(struct snd_i2c_bus *bus, int ack)
+{
+ struct snd_ice1712 *ice = bus->private_data;
+ int bit;
+
+ if (ack)
+ udelay(5);
+ bit = snd_ice1712_gpio_read_bits(ice, VT1724_REVO_I2C_DATA) ? 1 : 0;
+ return bit;
+}
+
+static struct snd_i2c_bit_ops revo51_bit_ops = {
+ .start = revo_i2c_start,
+ .stop = revo_i2c_stop,
+ .direction = revo_i2c_direction,
+ .setlines = revo_i2c_setlines,
+ .getdata = revo_i2c_getdata,
+};
+
+static int revo51_i2c_init(struct snd_ice1712 *ice,
+ struct snd_pt2258 *pt)
+{
+ struct revo51_spec *spec;
+ int err;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ /* create the I2C bus */
+ err = snd_i2c_bus_create(ice->card, "ICE1724 GPIO6", NULL, &ice->i2c);
+ if (err < 0)
+ return err;
+
+ ice->i2c->private_data = ice;
+ ice->i2c->hw_ops.bit = &revo51_bit_ops;
+
+ /* create the I2C device */
+ err = snd_i2c_device_create(ice->i2c, "PT2258", 0x40, &spec->dev);
+ if (err < 0)
+ return err;
+
+ pt->card = ice->card;
+ pt->i2c_bus = ice->i2c;
+ pt->i2c_dev = spec->dev;
+ spec->pt2258 = pt;
+
+ snd_pt2258_reset(pt);
+
+ return 0;
+}
+
+/*
+ * initialize the chips on M-Audio Revolution cards
+ */
+
+#define AK_DAC(xname,xch) { .name = xname, .num_channels = xch }
+
+static const struct snd_akm4xxx_dac_channel revo71_front[] = {
+ {
+ .name = "PCM Playback Volume",
+ .num_channels = 2,
+ /* front channels DAC supports muting */
+ .switch_name = "PCM Playback Switch",
+ },
+};
+
+static const struct snd_akm4xxx_dac_channel revo71_surround[] = {
+ AK_DAC("PCM Center Playback Volume", 1),
+ AK_DAC("PCM LFE Playback Volume", 1),
+ AK_DAC("PCM Side Playback Volume", 2),
+ AK_DAC("PCM Rear Playback Volume", 2),
+};
+
+static const struct snd_akm4xxx_dac_channel revo51_dac[] = {
+ AK_DAC("PCM Playback Volume", 2),
+ AK_DAC("PCM Center Playback Volume", 1),
+ AK_DAC("PCM LFE Playback Volume", 1),
+ AK_DAC("PCM Rear Playback Volume", 2),
+ AK_DAC("PCM Headphone Volume", 2),
+};
+
+static const char *revo51_adc_input_names[] = {
+ "Mic",
+ "Line",
+ "CD",
+ NULL
+};
+
+static const struct snd_akm4xxx_adc_channel revo51_adc[] = {
+ {
+ .name = "PCM Capture Volume",
+ .switch_name = "PCM Capture Switch",
+ .num_channels = 2,
+ .input_names = revo51_adc_input_names
+ },
+};
+
+static const struct snd_akm4xxx akm_revo_front = {
+ .type = SND_AK4381,
+ .num_dacs = 2,
+ .ops = {
+ .set_rate_val = revo_set_rate_val
+ },
+ .dac_info = revo71_front,
+};
+
+static const struct snd_ak4xxx_private akm_revo_front_priv = {
+ .caddr = 1,
+ .cif = 0,
+ .data_mask = VT1724_REVO_CDOUT,
+ .clk_mask = VT1724_REVO_CCLK,
+ .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+ .cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS2,
+ .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+ .add_flags = VT1724_REVO_CCLK, /* high at init */
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_revo_surround = {
+ .type = SND_AK4355,
+ .idx_offset = 1,
+ .num_dacs = 6,
+ .ops = {
+ .set_rate_val = revo_set_rate_val
+ },
+ .dac_info = revo71_surround,
+};
+
+static const struct snd_ak4xxx_private akm_revo_surround_priv = {
+ .caddr = 3,
+ .cif = 0,
+ .data_mask = VT1724_REVO_CDOUT,
+ .clk_mask = VT1724_REVO_CCLK,
+ .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+ .cs_addr = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+ .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1 | VT1724_REVO_CS2,
+ .add_flags = VT1724_REVO_CCLK, /* high at init */
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_revo51 = {
+ .type = SND_AK4358,
+ .num_dacs = 8,
+ .ops = {
+ .set_rate_val = revo_set_rate_val
+ },
+ .dac_info = revo51_dac,
+};
+
+static const struct snd_ak4xxx_private akm_revo51_priv = {
+ .caddr = 2,
+ .cif = 0,
+ .data_mask = VT1724_REVO_CDOUT,
+ .clk_mask = VT1724_REVO_CCLK,
+ .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+ .cs_addr = VT1724_REVO_CS1,
+ .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+ .add_flags = VT1724_REVO_CCLK, /* high at init */
+ .mask_flags = 0,
+};
+
+static const struct snd_akm4xxx akm_revo51_adc = {
+ .type = SND_AK5365,
+ .num_adcs = 2,
+ .adc_info = revo51_adc,
+};
+
+static const struct snd_ak4xxx_private akm_revo51_adc_priv = {
+ .caddr = 2,
+ .cif = 0,
+ .data_mask = VT1724_REVO_CDOUT,
+ .clk_mask = VT1724_REVO_CCLK,
+ .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+ .cs_addr = VT1724_REVO_CS0,
+ .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS1,
+ .add_flags = VT1724_REVO_CCLK, /* high at init */
+ .mask_flags = 0,
+};
+
+static struct snd_pt2258 ptc_revo51_volume;
+
+/* AK4358 for AP192 DAC, AK5385A for ADC */
+static void ap192_set_rate_val(struct snd_akm4xxx *ak, unsigned int rate)
+{
+ struct snd_ice1712 *ice = ak->private_data[0];
+ int dfs;
+
+ revo_set_rate_val(ak, rate);
+
+ /* reset CKS */
+ snd_ice1712_gpio_write_bits(ice, 1 << 8, rate > 96000 ? 1 << 8 : 0);
+ /* reset DFS pins of AK5385A for ADC, too */
+ if (rate > 96000)
+ dfs = 2;
+ else if (rate > 48000)
+ dfs = 1;
+ else
+ dfs = 0;
+ snd_ice1712_gpio_write_bits(ice, 3 << 9, dfs << 9);
+ /* reset ADC */
+ snd_ice1712_gpio_write_bits(ice, 1 << 11, 0);
+ snd_ice1712_gpio_write_bits(ice, 1 << 11, 1 << 11);
+}
+
+static const struct snd_akm4xxx_dac_channel ap192_dac[] = {
+ AK_DAC("PCM Playback Volume", 2)
+};
+
+static const struct snd_akm4xxx akm_ap192 = {
+ .type = SND_AK4358,
+ .num_dacs = 2,
+ .ops = {
+ .set_rate_val = ap192_set_rate_val
+ },
+ .dac_info = ap192_dac,
+};
+
+static const struct snd_ak4xxx_private akm_ap192_priv = {
+ .caddr = 2,
+ .cif = 0,
+ .data_mask = VT1724_REVO_CDOUT,
+ .clk_mask = VT1724_REVO_CCLK,
+ .cs_mask = VT1724_REVO_CS0 | VT1724_REVO_CS3,
+ .cs_addr = VT1724_REVO_CS3,
+ .cs_none = VT1724_REVO_CS0 | VT1724_REVO_CS3,
+ .add_flags = VT1724_REVO_CCLK, /* high at init */
+ .mask_flags = 0,
+};
+
+/* AK4114 support on Audiophile 192 */
+/* CDTO (pin 32) -- GPIO2 pin 52
+ * CDTI (pin 33) -- GPIO3 pin 53 (shared with AK4358)
+ * CCLK (pin 34) -- GPIO1 pin 51 (shared with AK4358)
+ * CSN (pin 35) -- GPIO7 pin 59
+ */
+#define AK4114_ADDR 0x00
+
+static void write_data(struct snd_ice1712 *ice, unsigned int gpio,
+ unsigned int data, int idx)
+{
+ for (; idx >= 0; idx--) {
+ /* drop clock */
+ gpio &= ~VT1724_REVO_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ /* set data */
+ if (data & (1 << idx))
+ gpio |= VT1724_REVO_CDOUT;
+ else
+ gpio &= ~VT1724_REVO_CDOUT;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ /* raise clock */
+ gpio |= VT1724_REVO_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ }
+}
+
+static unsigned char read_data(struct snd_ice1712 *ice, unsigned int gpio,
+ int idx)
+{
+ unsigned char data = 0;
+
+ for (; idx >= 0; idx--) {
+ /* drop clock */
+ gpio &= ~VT1724_REVO_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ /* read data */
+ if (snd_ice1712_gpio_read(ice) & VT1724_REVO_CDIN)
+ data |= (1 << idx);
+ udelay(1);
+ /* raise clock */
+ gpio |= VT1724_REVO_CCLK;
+ snd_ice1712_gpio_write(ice, gpio);
+ udelay(1);
+ }
+ return data;
+}
+
+static unsigned int ap192_4wire_start(struct snd_ice1712 *ice)
+{
+ unsigned int tmp;
+
+ snd_ice1712_save_gpio_status(ice);
+ tmp = snd_ice1712_gpio_read(ice);
+ tmp |= VT1724_REVO_CCLK; /* high at init */
+ tmp |= VT1724_REVO_CS0;
+ tmp &= ~VT1724_REVO_CS3;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ return tmp;
+}
+
+static void ap192_4wire_finish(struct snd_ice1712 *ice, unsigned int tmp)
+{
+ tmp |= VT1724_REVO_CS3;
+ tmp |= VT1724_REVO_CS0;
+ snd_ice1712_gpio_write(ice, tmp);
+ udelay(1);
+ snd_ice1712_restore_gpio_status(ice);
+}
+
+static void ap192_ak4114_write(void *private_data, unsigned char addr,
+ unsigned char data)
+{
+ struct snd_ice1712 *ice = private_data;
+ unsigned int tmp, addrdata;
+
+ tmp = ap192_4wire_start(ice);
+ addrdata = (AK4114_ADDR << 6) | 0x20 | (addr & 0x1f);
+ addrdata = (addrdata << 8) | data;
+ write_data(ice, tmp, addrdata, 15);
+ ap192_4wire_finish(ice, tmp);
+}
+
+static unsigned char ap192_ak4114_read(void *private_data, unsigned char addr)
+{
+ struct snd_ice1712 *ice = private_data;
+ unsigned int tmp;
+ unsigned char data;
+
+ tmp = ap192_4wire_start(ice);
+ write_data(ice, tmp, (AK4114_ADDR << 6) | (addr & 0x1f), 7);
+ data = read_data(ice, tmp, 7);
+ ap192_4wire_finish(ice, tmp);
+ return data;
+}
+
+static int ap192_ak4114_init(struct snd_ice1712 *ice)
+{
+ static const unsigned char ak4114_init_vals[] = {
+ AK4114_RST | AK4114_PWN | AK4114_OCKS0,
+ AK4114_DIF_I24I2S,
+ AK4114_TX1E,
+ AK4114_EFH_1024 | AK4114_DIT | AK4114_IPS(0),
+ 0,
+ 0
+ };
+ static const unsigned char ak4114_init_txcsb[] = {
+ 0x41, 0x02, 0x2c, 0x00, 0x00
+ };
+ int err;
+
+ struct revo51_spec *spec;
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ err = snd_ak4114_create(ice->card,
+ ap192_ak4114_read,
+ ap192_ak4114_write,
+ ak4114_init_vals, ak4114_init_txcsb,
+ ice, &spec->ak4114);
+ if (err < 0)
+ return err;
+ /* AK4114 in Revo cannot detect external rate correctly.
+ * No reason to stop capture stream due to incorrect checks */
+ spec->ak4114->check_flags = AK4114_CHECK_NO_RATE;
+
+ return 0;
+}
+
+static int revo_init(struct snd_ice1712 *ice)
+{
+ struct snd_akm4xxx *ak;
+ int err;
+
+ /* determine I2C, DACs and ADCs */
+ switch (ice->eeprom.subvendor) {
+ case VT1724_SUBDEVICE_REVOLUTION71:
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 2;
+ ice->gpio.i2s_mclk_changed = revo_i2s_mclk_changed;
+ break;
+ case VT1724_SUBDEVICE_REVOLUTION51:
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 2;
+ break;
+ case VT1724_SUBDEVICE_AUDIOPHILE192:
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 2;
+ break;
+ default:
+ snd_BUG();
+ return -EINVAL;
+ }
+
+ /* second stage of initialization, analog parts and others */
+ ak = ice->akm = kcalloc(2, sizeof(struct snd_akm4xxx), GFP_KERNEL);
+ if (! ak)
+ return -ENOMEM;
+ switch (ice->eeprom.subvendor) {
+ case VT1724_SUBDEVICE_REVOLUTION71:
+ ice->akm_codecs = 2;
+ err = snd_ice1712_akm4xxx_init(ak, &akm_revo_front,
+ &akm_revo_front_priv, ice);
+ if (err < 0)
+ return err;
+ err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo_surround,
+ &akm_revo_surround_priv, ice);
+ if (err < 0)
+ return err;
+ /* unmute all codecs */
+ snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
+ VT1724_REVO_MUTE);
+ break;
+ case VT1724_SUBDEVICE_REVOLUTION51:
+ ice->akm_codecs = 2;
+ err = snd_ice1712_akm4xxx_init(ak, &akm_revo51,
+ &akm_revo51_priv, ice);
+ if (err < 0)
+ return err;
+ err = snd_ice1712_akm4xxx_init(ak+1, &akm_revo51_adc,
+ &akm_revo51_adc_priv, ice);
+ if (err < 0)
+ return err;
+ err = revo51_i2c_init(ice, &ptc_revo51_volume);
+ if (err < 0)
+ return err;
+ /* unmute all codecs */
+ snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
+ VT1724_REVO_MUTE);
+ break;
+ case VT1724_SUBDEVICE_AUDIOPHILE192:
+ ice->akm_codecs = 1;
+ err = snd_ice1712_akm4xxx_init(ak, &akm_ap192, &akm_ap192_priv,
+ ice);
+ if (err < 0)
+ return err;
+ err = ap192_ak4114_init(ice);
+ if (err < 0)
+ return err;
+
+ /* unmute all codecs */
+ snd_ice1712_gpio_write_bits(ice, VT1724_REVO_MUTE,
+ VT1724_REVO_MUTE);
+ break;
+ }
+
+ return 0;
+}
+
+
+static int revo_add_controls(struct snd_ice1712 *ice)
+{
+ struct revo51_spec *spec = ice->spec;
+ int err;
+
+ switch (ice->eeprom.subvendor) {
+ case VT1724_SUBDEVICE_REVOLUTION71:
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+ break;
+ case VT1724_SUBDEVICE_REVOLUTION51:
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+ spec = ice->spec;
+ err = snd_pt2258_build_controls(spec->pt2258);
+ if (err < 0)
+ return err;
+ break;
+ case VT1724_SUBDEVICE_AUDIOPHILE192:
+ err = snd_ice1712_akm4xxx_build_controls(ice);
+ if (err < 0)
+ return err;
+ /* only capture SPDIF over AK4114 */
+ err = snd_ak4114_build(spec->ak4114, NULL,
+ ice->pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream);
+ if (err < 0)
+ return err;
+ break;
+ }
+ return 0;
+}
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1724_revo_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_REVOLUTION71,
+ .name = "M Audio Revolution-7.1",
+ .model = "revo71",
+ .chip_init = revo_init,
+ .build_controls = revo_add_controls,
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_REVOLUTION51,
+ .name = "M Audio Revolution-5.1",
+ .model = "revo51",
+ .chip_init = revo_init,
+ .build_controls = revo_add_controls,
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_AUDIOPHILE192,
+ .name = "M Audio Audiophile192",
+ .model = "ap192",
+ .chip_init = revo_init,
+ .build_controls = revo_add_controls,
+ },
+ { } /* terminator */
+};
diff --git a/sound/pci/ice1712/revo.h b/sound/pci/ice1712/revo.h
new file mode 100644
index 0000000000..573ae25067
--- /dev/null
+++ b/sound/pci/ice1712/revo.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_REVO_H
+#define __SOUND_REVO_H
+
+/*
+ * ALSA driver for ICEnsemble ICE1712 (Envy24)
+ *
+ * Lowlevel functions for M-Audio Revolution 7.1
+ *
+ * Copyright (c) 2003 Takashi Iwai <tiwai@suse.de>
+ */
+
+#define REVO_DEVICE_DESC \
+ "{MidiMan M Audio,Revolution 7.1},"\
+ "{MidiMan M Audio,Revolution 5.1},"\
+ "{MidiMan M Audio,Audiophile 192},"
+
+#define VT1724_SUBDEVICE_REVOLUTION71 0x12143036
+#define VT1724_SUBDEVICE_REVOLUTION51 0x12143136
+#define VT1724_SUBDEVICE_AUDIOPHILE192 0x12143236
+
+/* entry point */
+extern struct snd_ice1712_card_info snd_vt1724_revo_cards[];
+
+
+/*
+ * MidiMan M-Audio Revolution GPIO definitions
+ */
+
+#define VT1724_REVO_CCLK 0x02
+#define VT1724_REVO_CDIN 0x04 /* not used */
+#define VT1724_REVO_CDOUT 0x08
+#define VT1724_REVO_CS0 0x10 /* AK5365 chipselect for (revo51) */
+#define VT1724_REVO_CS1 0x20 /* front AKM4381 chipselect */
+#define VT1724_REVO_CS2 0x40 /* surround AKM4355 CS (revo71) */
+#define VT1724_REVO_I2C_DATA 0x40 /* I2C: PT 2258 SDA (on revo51) */
+#define VT1724_REVO_I2C_CLOCK 0x80 /* I2C: PT 2258 SCL (on revo51) */
+#define VT1724_REVO_CS3 0x80 /* AK4114 for AP192 */
+#define VT1724_REVO_MUTE (1<<22) /* 0 = all mute, 1 = normal operation */
+
+#endif /* __SOUND_REVO_H */
diff --git a/sound/pci/ice1712/se.c b/sound/pci/ice1712/se.c
new file mode 100644
index 0000000000..ffa9d8860a
--- /dev/null
+++ b/sound/pci/ice1712/se.c
@@ -0,0 +1,752 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for ONKYO WAVIO SE-90PCI and SE-200PCI
+ *
+ * Copyright (c) 2007 Shin-ya Okada sh_okada(at)d4.dion.ne.jp
+ * (at) -> @
+ */
+
+#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 "se.h"
+
+struct se_spec {
+ struct {
+ unsigned char ch1, ch2;
+ } vol[8];
+};
+
+/****************************************************************************/
+/* ONKYO WAVIO SE-200PCI */
+/****************************************************************************/
+/*
+ * system configuration ICE_EEP2_SYSCONF=0x4b
+ * XIN1 49.152MHz
+ * not have UART
+ * one stereo ADC and a S/PDIF receiver connected
+ * four stereo DACs connected
+ *
+ * AC-Link configuration ICE_EEP2_ACLINK=0x80
+ * use I2C, not use AC97
+ *
+ * I2S converters feature ICE_EEP2_I2S=0x78
+ * I2S codec has no volume/mute control feature
+ * I2S codec supports 96KHz and 192KHz
+ * I2S codec 24bits
+ *
+ * S/PDIF configuration ICE_EEP2_SPDIF=0xc3
+ * Enable integrated S/PDIF transmitter
+ * internal S/PDIF out implemented
+ * S/PDIF is stereo
+ * External S/PDIF out implemented
+ *
+ *
+ * ** connected chips **
+ *
+ * WM8740
+ * A 2ch-DAC of main outputs.
+ * It setuped as I2S mode by wire, so no way to setup from software.
+ * The sample-rate are automatically changed.
+ * ML/I2S (28pin) --------+
+ * MC/DM1 (27pin) -- 5V |
+ * MD/DM0 (26pin) -- GND |
+ * MUTEB (25pin) -- NC |
+ * MODE (24pin) -- GND |
+ * CSBIW (23pin) --------+
+ * |
+ * RSTB (22pin) --R(1K)-+
+ * Probably it reduce the noise from the control line.
+ *
+ * WM8766
+ * A 6ch-DAC for surrounds.
+ * It's control wire was connected to GPIOxx (3-wire serial interface)
+ * ML/I2S (11pin) -- GPIO18
+ * MC/IWL (12pin) -- GPIO17
+ * MD/DM (13pin) -- GPIO16
+ * MUTE (14pin) -- GPIO01
+ *
+ * WM8776
+ * A 2ch-ADC(with 10ch-selector) plus 2ch-DAC.
+ * It's control wire was connected to SDA/SCLK (2-wire serial interface)
+ * MODE (16pin) -- R(1K) -- GND
+ * CE (17pin) -- R(1K) -- GND 2-wire mode (address=0x34)
+ * DI (18pin) -- SDA
+ * CL (19pin) -- SCLK
+ *
+ *
+ * ** output pins and device names **
+ *
+ * 7.1ch name -- output connector color -- device (-D option)
+ *
+ * FRONT 2ch -- green -- plughw:0,0
+ * CENTER(Lch) SUBWOOFER(Rch) -- black -- plughw:0,2,0
+ * SURROUND 2ch -- orange -- plughw:0,2,1
+ * SURROUND BACK 2ch -- white -- plughw:0,2,2
+ *
+ */
+
+
+/****************************************************************************/
+/* WM8740 interface */
+/****************************************************************************/
+
+static void se200pci_WM8740_init(struct snd_ice1712 *ice)
+{
+ /* nothing to do */
+}
+
+
+static void se200pci_WM8740_set_pro_rate(struct snd_ice1712 *ice,
+ unsigned int rate)
+{
+ /* nothing to do */
+}
+
+
+/****************************************************************************/
+/* WM8766 interface */
+/****************************************************************************/
+
+static void se200pci_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 se200pci_WM8766_set_volume(struct snd_ice1712 *ice, int ch,
+ unsigned int vol1, unsigned int vol2)
+{
+ switch (ch) {
+ case 0:
+ se200pci_WM8766_write(ice, 0x000, vol1);
+ se200pci_WM8766_write(ice, 0x001, vol2 | 0x100);
+ break;
+ case 1:
+ se200pci_WM8766_write(ice, 0x004, vol1);
+ se200pci_WM8766_write(ice, 0x005, vol2 | 0x100);
+ break;
+ case 2:
+ se200pci_WM8766_write(ice, 0x006, vol1);
+ se200pci_WM8766_write(ice, 0x007, vol2 | 0x100);
+ break;
+ }
+}
+
+static void se200pci_WM8766_init(struct snd_ice1712 *ice)
+{
+ se200pci_WM8766_write(ice, 0x1f, 0x000); /* RESET ALL */
+ udelay(10);
+
+ se200pci_WM8766_set_volume(ice, 0, 0, 0); /* volume L=0 R=0 */
+ se200pci_WM8766_set_volume(ice, 1, 0, 0); /* volume L=0 R=0 */
+ se200pci_WM8766_set_volume(ice, 2, 0, 0); /* volume L=0 R=0 */
+
+ se200pci_WM8766_write(ice, 0x03, 0x022); /* serial mode I2S-24bits */
+ se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */
+ se200pci_WM8766_write(ice, 0x12, 0x000); /* MDP=0 */
+ se200pci_WM8766_write(ice, 0x15, 0x000); /* MDP=0 */
+ se200pci_WM8766_write(ice, 0x09, 0x000); /* demp=off mute=off */
+
+ se200pci_WM8766_write(ice, 0x02, 0x124); /* ch-assign L=L R=R RESET */
+ se200pci_WM8766_write(ice, 0x02, 0x120); /* ch-assign L=L R=R */
+}
+
+static void se200pci_WM8766_set_pro_rate(struct snd_ice1712 *ice,
+ unsigned int rate)
+{
+ if (rate > 96000)
+ se200pci_WM8766_write(ice, 0x0a, 0x000); /* MCLK=128fs */
+ else
+ se200pci_WM8766_write(ice, 0x0a, 0x080); /* MCLK=256fs */
+}
+
+
+/****************************************************************************/
+/* WM8776 interface */
+/****************************************************************************/
+
+static void se200pci_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 se200pci_WM8776_set_output_volume(struct snd_ice1712 *ice,
+ unsigned int vol1, unsigned int vol2)
+{
+ se200pci_WM8776_write(ice, 0x03, vol1);
+ se200pci_WM8776_write(ice, 0x04, vol2 | 0x100);
+}
+
+static void se200pci_WM8776_set_input_volume(struct snd_ice1712 *ice,
+ unsigned int vol1, unsigned int vol2)
+{
+ se200pci_WM8776_write(ice, 0x0e, vol1);
+ se200pci_WM8776_write(ice, 0x0f, vol2 | 0x100);
+}
+
+static const char * const se200pci_sel[] = {
+ "LINE-IN", "CD-IN", "MIC-IN", "ALL-MIX", NULL
+};
+
+static void se200pci_WM8776_set_input_selector(struct snd_ice1712 *ice,
+ unsigned int sel)
+{
+ static const unsigned char vals[] = {
+ /* LINE, CD, MIC, ALL, GND */
+ 0x10, 0x04, 0x08, 0x1c, 0x03
+ };
+ if (sel > 4)
+ sel = 4;
+ se200pci_WM8776_write(ice, 0x15, vals[sel]);
+}
+
+static void se200pci_WM8776_set_afl(struct snd_ice1712 *ice, unsigned int afl)
+{
+ /* AFL -- After Fader Listening */
+ if (afl)
+ se200pci_WM8776_write(ice, 0x16, 0x005);
+ else
+ se200pci_WM8776_write(ice, 0x16, 0x001);
+}
+
+static const char * const se200pci_agc[] = {
+ "Off", "LimiterMode", "ALCMode", NULL
+};
+
+static void se200pci_WM8776_set_agc(struct snd_ice1712 *ice, unsigned int agc)
+{
+ /* AGC -- Auto Gain Control of the input */
+ switch (agc) {
+ case 0:
+ se200pci_WM8776_write(ice, 0x11, 0x000); /* Off */
+ break;
+ case 1:
+ se200pci_WM8776_write(ice, 0x10, 0x07b);
+ se200pci_WM8776_write(ice, 0x11, 0x100); /* LimiterMode */
+ break;
+ case 2:
+ se200pci_WM8776_write(ice, 0x10, 0x1fb);
+ se200pci_WM8776_write(ice, 0x11, 0x100); /* ALCMode */
+ break;
+ }
+}
+
+static void se200pci_WM8776_init(struct snd_ice1712 *ice)
+{
+ int i;
+ static const unsigned short 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
+ };
+
+ se200pci_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++)
+ se200pci_WM8776_write(ice, i, default_values[i]);
+
+ se200pci_WM8776_set_input_selector(ice, 0);
+ se200pci_WM8776_set_afl(ice, 0);
+ se200pci_WM8776_set_agc(ice, 0);
+ se200pci_WM8776_set_input_volume(ice, 0, 0);
+ se200pci_WM8776_set_output_volume(ice, 0, 0);
+
+ /* head phone mute and power down */
+ se200pci_WM8776_write(ice, 0x00, 0);
+ se200pci_WM8776_write(ice, 0x01, 0);
+ se200pci_WM8776_write(ice, 0x02, 0x100);
+ se200pci_WM8776_write(ice, 0x0d, 0x080);
+}
+
+static void se200pci_WM8776_set_pro_rate(struct snd_ice1712 *ice,
+ unsigned int rate)
+{
+ /* nothing to do */
+}
+
+
+/****************************************************************************/
+/* runtime interface */
+/****************************************************************************/
+
+static void se200pci_set_pro_rate(struct snd_ice1712 *ice, unsigned int rate)
+{
+ se200pci_WM8740_set_pro_rate(ice, rate);
+ se200pci_WM8766_set_pro_rate(ice, rate);
+ se200pci_WM8776_set_pro_rate(ice, rate);
+}
+
+struct se200pci_control {
+ const char *name;
+ enum {
+ WM8766,
+ WM8776in,
+ WM8776out,
+ WM8776sel,
+ WM8776agc,
+ WM8776afl
+ } target;
+ enum { VOLUME1, VOLUME2, BOOLEAN, ENUM } type;
+ int ch;
+ const char * const *member;
+ const char *comment;
+};
+
+static const struct se200pci_control se200pci_cont[] = {
+ {
+ .name = "Front Playback Volume",
+ .target = WM8776out,
+ .type = VOLUME1,
+ .comment = "Front(green)"
+ },
+ {
+ .name = "Side Playback Volume",
+ .target = WM8766,
+ .type = VOLUME1,
+ .ch = 1,
+ .comment = "Surround(orange)"
+ },
+ {
+ .name = "Surround Playback Volume",
+ .target = WM8766,
+ .type = VOLUME1,
+ .ch = 2,
+ .comment = "SurroundBack(white)"
+ },
+ {
+ .name = "CLFE Playback Volume",
+ .target = WM8766,
+ .type = VOLUME1,
+ .ch = 0,
+ .comment = "Center(Lch)&SubWoofer(Rch)(black)"
+ },
+ {
+ .name = "Capture Volume",
+ .target = WM8776in,
+ .type = VOLUME2
+ },
+ {
+ .name = "Capture Select",
+ .target = WM8776sel,
+ .type = ENUM,
+ .member = se200pci_sel
+ },
+ {
+ .name = "AGC Capture Mode",
+ .target = WM8776agc,
+ .type = ENUM,
+ .member = se200pci_agc
+ },
+ {
+ .name = "AFL Bypass Playback Switch",
+ .target = WM8776afl,
+ .type = BOOLEAN
+ }
+};
+
+static int se200pci_get_enum_count(int n)
+{
+ const char * const *member;
+ int c;
+
+ member = se200pci_cont[n].member;
+ if (!member)
+ return 0;
+ for (c = 0; member[c]; c++)
+ ;
+ return c;
+}
+
+static int se200pci_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 se200pci_cont_boolean_info snd_ctl_boolean_mono_info
+
+static int se200pci_cont_enum_info(struct snd_kcontrol *kc,
+ struct snd_ctl_elem_info *uinfo)
+{
+ int n, c;
+
+ n = kc->private_value;
+ c = se200pci_get_enum_count(n);
+ if (!c)
+ return -EINVAL;
+ return snd_ctl_enum_info(uinfo, 1, c, se200pci_cont[n].member);
+}
+
+static int se200pci_cont_volume_get(struct snd_kcontrol *kc,
+ struct snd_ctl_elem_value *uc)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+ struct se_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 se200pci_cont_boolean_get(struct snd_kcontrol *kc,
+ struct snd_ctl_elem_value *uc)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+ struct se_spec *spec = ice->spec;
+ int n = kc->private_value;
+ uc->value.integer.value[0] = spec->vol[n].ch1;
+ return 0;
+}
+
+static int se200pci_cont_enum_get(struct snd_kcontrol *kc,
+ struct snd_ctl_elem_value *uc)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+ struct se_spec *spec = ice->spec;
+ int n = kc->private_value;
+ uc->value.enumerated.item[0] = spec->vol[n].ch1;
+ return 0;
+}
+
+static void se200pci_cont_update(struct snd_ice1712 *ice, int n)
+{
+ struct se_spec *spec = ice->spec;
+ switch (se200pci_cont[n].target) {
+ case WM8766:
+ se200pci_WM8766_set_volume(ice,
+ se200pci_cont[n].ch,
+ spec->vol[n].ch1,
+ spec->vol[n].ch2);
+ break;
+
+ case WM8776in:
+ se200pci_WM8776_set_input_volume(ice,
+ spec->vol[n].ch1,
+ spec->vol[n].ch2);
+ break;
+
+ case WM8776out:
+ se200pci_WM8776_set_output_volume(ice,
+ spec->vol[n].ch1,
+ spec->vol[n].ch2);
+ break;
+
+ case WM8776sel:
+ se200pci_WM8776_set_input_selector(ice,
+ spec->vol[n].ch1);
+ break;
+
+ case WM8776agc:
+ se200pci_WM8776_set_agc(ice, spec->vol[n].ch1);
+ break;
+
+ case WM8776afl:
+ se200pci_WM8776_set_afl(ice, spec->vol[n].ch1);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int se200pci_cont_volume_put(struct snd_kcontrol *kc,
+ struct snd_ctl_elem_value *uc)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+ struct se_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)
+ se200pci_cont_update(ice, n);
+
+ return changed;
+}
+
+static int se200pci_cont_boolean_put(struct snd_kcontrol *kc,
+ struct snd_ctl_elem_value *uc)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+ struct se_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;
+ se200pci_cont_update(ice, n);
+ return 1;
+ }
+ return 0;
+}
+
+static int se200pci_cont_enum_put(struct snd_kcontrol *kc,
+ struct snd_ctl_elem_value *uc)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kc);
+ struct se_spec *spec = ice->spec;
+ int n = kc->private_value;
+ unsigned int vol1;
+
+ vol1 = uc->value.enumerated.item[0];
+ if (vol1 >= se200pci_get_enum_count(n))
+ return -EINVAL;
+ if (spec->vol[n].ch1 != vol1) {
+ spec->vol[n].ch1 = vol1;
+ se200pci_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 se200pci_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(se200pci_cont); i++) {
+ cont.private_value = i;
+ cont.name = se200pci_cont[i].name;
+ cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+ cont.tlv.p = NULL;
+ switch (se200pci_cont[i].type) {
+ case VOLUME1:
+ case VOLUME2:
+ cont.info = se200pci_cont_volume_info;
+ cont.get = se200pci_cont_volume_get;
+ cont.put = se200pci_cont_volume_put;
+ cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
+ if (se200pci_cont[i].type == VOLUME1)
+ cont.tlv.p = db_scale_gain1;
+ else
+ cont.tlv.p = db_scale_gain2;
+ break;
+ case BOOLEAN:
+ cont.info = se200pci_cont_boolean_info;
+ cont.get = se200pci_cont_boolean_get;
+ cont.put = se200pci_cont_boolean_put;
+ break;
+ case ENUM:
+ cont.info = se200pci_cont_enum_info;
+ cont.get = se200pci_cont_enum_get;
+ cont.put = se200pci_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;
+}
+
+
+/****************************************************************************/
+/* ONKYO WAVIO SE-90PCI */
+/****************************************************************************/
+/*
+ * system configuration ICE_EEP2_SYSCONF=0x4b
+ * AC-Link configuration ICE_EEP2_ACLINK=0x80
+ * I2S converters feature ICE_EEP2_I2S=0x78
+ * S/PDIF configuration ICE_EEP2_SPDIF=0xc3
+ *
+ * ** connected chip **
+ *
+ * WM8716
+ * A 2ch-DAC of main outputs.
+ * It setuped as I2S mode by wire, so no way to setup from software.
+ * ML/I2S (28pin) -- +5V
+ * MC/DM1 (27pin) -- GND
+ * MC/DM0 (26pin) -- GND
+ * MUTEB (25pin) -- open (internal pull-up)
+ * MODE (24pin) -- GND
+ * CSBIWO (23pin) -- +5V
+ *
+ */
+
+ /* Nothing to do for this chip. */
+
+
+/****************************************************************************/
+/* probe/initialize/setup */
+/****************************************************************************/
+
+static int se_init(struct snd_ice1712 *ice)
+{
+ struct se_spec *spec;
+
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE90PCI) {
+ ice->num_total_dacs = 2;
+ ice->num_total_adcs = 0;
+ ice->vt1720 = 1;
+ return 0;
+
+ } else if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE200PCI) {
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 2;
+ se200pci_WM8740_init(ice);
+ se200pci_WM8766_init(ice);
+ se200pci_WM8776_init(ice);
+ ice->gpio.set_pro_rate = se200pci_set_pro_rate;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int se_add_controls(struct snd_ice1712 *ice)
+{
+ int err;
+
+ err = 0;
+ /* nothing to do for VT1724_SUBDEVICE_SE90PCI */
+ if (ice->eeprom.subvendor == VT1724_SUBDEVICE_SE200PCI)
+ err = se200pci_add_controls(ice);
+
+ return err;
+}
+
+
+/****************************************************************************/
+/* entry point */
+/****************************************************************************/
+
+static const unsigned char se200pci_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x4b, /* 49.152Hz, spdif-in/ADC, 4DACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0x78, /* 96k-ok, 24bit, 192k-ok */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+
+ [ICE_EEP2_GPIO_DIR] = 0x02, /* WM8766 mute 1=output */
+ [ICE_EEP2_GPIO_DIR1] = 0x00, /* not used */
+ [ICE_EEP2_GPIO_DIR2] = 0x07, /* WM8766 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, /* WM8766 mute=0 */
+ [ICE_EEP2_GPIO_STATE1] = 0x00, /* not used */
+ [ICE_EEP2_GPIO_STATE2] = 0x07, /* WM8766 ML/MC/MD */
+};
+
+static const unsigned char se90pci_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x4b, /* 49.152Hz, spdif-in/ADC, 4DACs */
+ [ICE_EEP2_ACLINK] = 0x80, /* I2S */
+ [ICE_EEP2_I2S] = 0x78, /* 96k-ok, 24bit, 192k-ok */
+ [ICE_EEP2_SPDIF] = 0xc3, /* out-en, out-int, spdif-in */
+
+ /* ALL GPIO bits are in input mode */
+};
+
+struct snd_ice1712_card_info snd_vt1724_se_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_SE200PCI,
+ .name = "ONKYO SE200PCI",
+ .model = "se200pci",
+ .chip_init = se_init,
+ .build_controls = se_add_controls,
+ .eeprom_size = sizeof(se200pci_eeprom),
+ .eeprom_data = se200pci_eeprom,
+ },
+ {
+ .subvendor = VT1724_SUBDEVICE_SE90PCI,
+ .name = "ONKYO SE90PCI",
+ .model = "se90pci",
+ .chip_init = se_init,
+ .build_controls = se_add_controls,
+ .eeprom_size = sizeof(se90pci_eeprom),
+ .eeprom_data = se90pci_eeprom,
+ },
+ {} /*terminator*/
+};
diff --git a/sound/pci/ice1712/se.h b/sound/pci/ice1712/se.h
new file mode 100644
index 0000000000..61348ecef1
--- /dev/null
+++ b/sound/pci/ice1712/se.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_SE_H
+#define __SOUND_SE_H
+
+/* ID */
+#define SE_DEVICE_DESC \
+ "{ONKYO INC,SE-90PCI},"\
+ "{ONKYO INC,SE-200PCI},"
+
+#define VT1724_SUBDEVICE_SE90PCI 0xb161000
+#define VT1724_SUBDEVICE_SE200PCI 0xb160100
+
+/* entry struct */
+extern struct snd_ice1712_card_info snd_vt1724_se_cards[];
+
+#endif /* __SOUND_SE_H */
diff --git a/sound/pci/ice1712/stac946x.h b/sound/pci/ice1712/stac946x.h
new file mode 100644
index 0000000000..58f9f17a25
--- /dev/null
+++ b/sound/pci/ice1712/stac946x.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_STAC946X_H
+#define __SOUND_STAC946X_H
+
+#define STAC946X_RESET 0x00
+#define STAC946X_STATUS 0x01
+#define STAC946X_MASTER_VOLUME 0x02
+#define STAC946X_LF_VOLUME 0x03
+#define STAC946X_RF_VOLUME 0x04
+#define STAC946X_LR_VOLUME 0x05
+#define STAC946X_RR_VOLUME 0x06
+#define STAC946X_CENTER_VOLUME 0x07
+#define STAC946X_LFE_VOLUME 0x08
+#define STAC946X_MIC_L_VOLUME 0x09
+#define STAC946X_MIC_R_VOLUME 0x0a
+#define STAC946X_DEEMPHASIS 0x0c
+#define STAC946X_GENERAL_PURPOSE 0x0d
+#define STAC946X_AUDIO_PORT_CONTROL 0x0e
+#define STAC946X_MASTER_CLOCKING 0x0f
+#define STAC946X_POWERDOWN_CTRL1 0x10
+#define STAC946X_POWERDOWN_CTRL2 0x11
+#define STAC946X_REVISION_CODE 0x12
+#define STAC946X_ADDRESS_CONTROL 0x13
+#define STAC946X_ADDRESS 0x14
+
+#endif /* __SOUND_STAC946X_H */
diff --git a/sound/pci/ice1712/vt1720_mobo.c b/sound/pci/ice1712/vt1720_mobo.c
new file mode 100644
index 0000000000..47470b0719
--- /dev/null
+++ b/sound/pci/ice1712/vt1720_mobo.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for VT1720/VT1724 (Envy24PT/Envy24HT)
+ *
+ * Lowlevel functions for VT1720-based motherboards
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "vt1720_mobo.h"
+
+
+static int k8x800_init(struct snd_ice1712 *ice)
+{
+ ice->vt1720 = 1;
+
+ /* VT1616 codec */
+ ice->num_total_dacs = 6;
+ ice->num_total_adcs = 2;
+
+ /* WM8728 codec */
+ /* FIXME: TODO */
+
+ return 0;
+}
+
+static int k8x800_add_controls(struct snd_ice1712 *ice)
+{
+ /* FIXME: needs some quirks for VT1616? */
+ return 0;
+}
+
+/* EEPROM image */
+
+static const unsigned char k8x800_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x01, /* clock 256, 1ADC, 2DACs */
+ [ICE_EEP2_ACLINK] = 0x02, /* ACLINK, packed */
+ [ICE_EEP2_I2S] = 0x00, /* - */
+ [ICE_EEP2_SPDIF] = 0x00, /* - */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x00, /* - */
+ [ICE_EEP2_GPIO_MASK] = 0xff,
+ [ICE_EEP2_GPIO_MASK1] = 0xff,
+ [ICE_EEP2_GPIO_MASK2] = 0x00, /* - */
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00, /* - */
+};
+
+static const unsigned char sn25p_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x01, /* clock 256, 1ADC, 2DACs */
+ [ICE_EEP2_ACLINK] = 0x02, /* ACLINK, packed */
+ [ICE_EEP2_I2S] = 0x00, /* - */
+ [ICE_EEP2_SPDIF] = 0x41, /* - */
+ [ICE_EEP2_GPIO_DIR] = 0xff,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x00, /* - */
+ [ICE_EEP2_GPIO_MASK] = 0xff,
+ [ICE_EEP2_GPIO_MASK1] = 0xff,
+ [ICE_EEP2_GPIO_MASK2] = 0x00, /* - */
+ [ICE_EEP2_GPIO_STATE] = 0x00,
+ [ICE_EEP2_GPIO_STATE1] = 0x00,
+ [ICE_EEP2_GPIO_STATE2] = 0x00, /* - */
+};
+
+
+/* entry point */
+struct snd_ice1712_card_info snd_vt1720_mobo_cards[] = {
+ {
+ .subvendor = VT1720_SUBDEVICE_K8X800,
+ .name = "Albatron K8X800 Pro II",
+ .model = "k8x800",
+ .chip_init = k8x800_init,
+ .build_controls = k8x800_add_controls,
+ .eeprom_size = sizeof(k8x800_eeprom),
+ .eeprom_data = k8x800_eeprom,
+ },
+ {
+ .subvendor = VT1720_SUBDEVICE_ZNF3_150,
+ .name = "Chaintech ZNF3-150",
+ /* identical with k8x800 */
+ .chip_init = k8x800_init,
+ .build_controls = k8x800_add_controls,
+ .eeprom_size = sizeof(k8x800_eeprom),
+ .eeprom_data = k8x800_eeprom,
+ },
+ {
+ .subvendor = VT1720_SUBDEVICE_ZNF3_250,
+ .name = "Chaintech ZNF3-250",
+ /* identical with k8x800 */
+ .chip_init = k8x800_init,
+ .build_controls = k8x800_add_controls,
+ .eeprom_size = sizeof(k8x800_eeprom),
+ .eeprom_data = k8x800_eeprom,
+ },
+ {
+ .subvendor = VT1720_SUBDEVICE_9CJS,
+ .name = "Chaintech 9CJS",
+ /* identical with k8x800 */
+ .chip_init = k8x800_init,
+ .build_controls = k8x800_add_controls,
+ .eeprom_size = sizeof(k8x800_eeprom),
+ .eeprom_data = k8x800_eeprom,
+ },
+ {
+ .subvendor = VT1720_SUBDEVICE_SN25P,
+ .name = "Shuttle SN25P",
+ .model = "sn25p",
+ .chip_init = k8x800_init,
+ .build_controls = k8x800_add_controls,
+ .eeprom_size = sizeof(k8x800_eeprom),
+ .eeprom_data = sn25p_eeprom,
+ },
+ { } /* terminator */
+};
+
diff --git a/sound/pci/ice1712/vt1720_mobo.h b/sound/pci/ice1712/vt1720_mobo.h
new file mode 100644
index 0000000000..90c77d8270
--- /dev/null
+++ b/sound/pci/ice1712/vt1720_mobo.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_VT1720_MOBO_H
+#define __SOUND_VT1720_MOBO_H
+
+/*
+ * ALSA driver for VT1720/VT1724 (Envy24PT/Envy24HT)
+ *
+ * Lowlevel functions for VT1720-based motherboards
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ */
+
+#define VT1720_MOBO_DEVICE_DESC "{Albatron,K8X800 Pro II},"\
+ "{Chaintech,ZNF3-150},"\
+ "{Chaintech,ZNF3-250},"\
+ "{Chaintech,9CJS},"\
+ "{Shuttle,SN25P},"
+
+#define VT1720_SUBDEVICE_K8X800 0xf217052c
+#define VT1720_SUBDEVICE_ZNF3_150 0x0f2741f6
+#define VT1720_SUBDEVICE_ZNF3_250 0x0f2745f6
+#define VT1720_SUBDEVICE_9CJS 0x0f272327
+#define VT1720_SUBDEVICE_SN25P 0x97123650
+
+extern struct snd_ice1712_card_info snd_vt1720_mobo_cards[];
+
+#endif /* __SOUND_VT1720_MOBO_H */
diff --git a/sound/pci/ice1712/wm8766.c b/sound/pci/ice1712/wm8766.c
new file mode 100644
index 0000000000..fe3e243b38
--- /dev/null
+++ b/sound/pci/ice1712/wm8766.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT17xx
+ *
+ * Lowlevel functions for WM8766 codec
+ *
+ * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
+ */
+
+#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_COUNT)
+ wm->regs[addr] = data;
+ wm->ops.write(wm, addr, data);
+}
+
+/* mixer controls */
+
+static const DECLARE_TLV_DB_SCALE(wm8766_tlv, -12750, 50, 1);
+
+static const 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_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);
+ if (wm->ctl[n].flags & WM8766_FLAG_STEREO)
+ 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 a 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;
+}
diff --git a/sound/pci/ice1712/wm8766.h b/sound/pci/ice1712/wm8766.h
new file mode 100644
index 0000000000..44ab7c72a3
--- /dev/null
+++ b/sound/pci/ice1712/wm8766.h
@@ -0,0 +1,147 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_WM8766_H
+#define __SOUND_WM8766_H
+
+/*
+ * ALSA driver for ICEnsemble VT17xx
+ *
+ * Lowlevel functions for WM8766 codec
+ *
+ * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
+ */
+
+#define WM8766_REG_DACL1 0x00
+#define WM8766_REG_DACR1 0x01
+#define WM8766_VOL_MASK 0x1ff /* incl. update bit */
+#define WM8766_VOL_UPDATE (1 << 8) /* update volume */
+#define WM8766_REG_DACCTRL1 0x02
+#define WM8766_DAC_MUTEALL (1 << 0)
+#define WM8766_DAC_DEEMPALL (1 << 1)
+#define WM8766_DAC_PDWN (1 << 2)
+#define WM8766_DAC_ATC (1 << 3)
+#define WM8766_DAC_IZD (1 << 4)
+#define WM8766_DAC_PL_MASK 0x1e0
+#define WM8766_DAC_PL_LL (1 << 5) /* L chan: L signal */
+#define WM8766_DAC_PL_LR (2 << 5) /* L chan: R signal */
+#define WM8766_DAC_PL_LB (3 << 5) /* L chan: both */
+#define WM8766_DAC_PL_RL (1 << 7) /* R chan: L signal */
+#define WM8766_DAC_PL_RR (2 << 7) /* R chan: R signal */
+#define WM8766_DAC_PL_RB (3 << 7) /* R chan: both */
+#define WM8766_REG_IFCTRL 0x03
+#define WM8766_IF_FMT_RIGHTJ (0 << 0)
+#define WM8766_IF_FMT_LEFTJ (1 << 0)
+#define WM8766_IF_FMT_I2S (2 << 0)
+#define WM8766_IF_FMT_DSP (3 << 0)
+#define WM8766_IF_DSP_LATE (1 << 2) /* in DSP mode */
+#define WM8766_IF_LRC_INVERTED (1 << 2) /* in other modes */
+#define WM8766_IF_BCLK_INVERTED (1 << 3)
+#define WM8766_IF_IWL_16BIT (0 << 4)
+#define WM8766_IF_IWL_20BIT (1 << 4)
+#define WM8766_IF_IWL_24BIT (2 << 4)
+#define WM8766_IF_IWL_32BIT (3 << 4)
+#define WM8766_IF_MASK 0x3f
+#define WM8766_PHASE_INVERT1 (1 << 6)
+#define WM8766_PHASE_INVERT2 (1 << 7)
+#define WM8766_PHASE_INVERT3 (1 << 8)
+#define WM8766_REG_DACL2 0x04
+#define WM8766_REG_DACR2 0x05
+#define WM8766_REG_DACL3 0x06
+#define WM8766_REG_DACR3 0x07
+#define WM8766_REG_MASTDA 0x08
+#define WM8766_REG_DACCTRL2 0x09
+#define WM8766_DAC2_ZCD (1 << 0)
+#define WM8766_DAC2_ZFLAG_ALL (0 << 1)
+#define WM8766_DAC2_ZFLAG_1 (1 << 1)
+#define WM8766_DAC2_ZFLAG_2 (2 << 1)
+#define WM8766_DAC2_ZFLAG_3 (3 << 1)
+#define WM8766_DAC2_MUTE1 (1 << 3)
+#define WM8766_DAC2_MUTE2 (1 << 4)
+#define WM8766_DAC2_MUTE3 (1 << 5)
+#define WM8766_DAC2_DEEMP1 (1 << 6)
+#define WM8766_DAC2_DEEMP2 (1 << 7)
+#define WM8766_DAC2_DEEMP3 (1 << 8)
+#define WM8766_REG_DACCTRL3 0x0a
+#define WM8766_DAC3_DACPD1 (1 << 1)
+#define WM8766_DAC3_DACPD2 (1 << 2)
+#define WM8766_DAC3_DACPD3 (1 << 3)
+#define WM8766_DAC3_PWRDNALL (1 << 4)
+#define WM8766_DAC3_POWER_MASK 0x1e
+#define WM8766_DAC3_MASTER (1 << 5)
+#define WM8766_DAC3_DAC128FS (0 << 6)
+#define WM8766_DAC3_DAC192FS (1 << 6)
+#define WM8766_DAC3_DAC256FS (2 << 6)
+#define WM8766_DAC3_DAC384FS (3 << 6)
+#define WM8766_DAC3_DAC512FS (4 << 6)
+#define WM8766_DAC3_DAC768FS (5 << 6)
+#define WM8766_DAC3_MSTR_MASK 0x1e0
+#define WM8766_REG_MUTE1 0x0c
+#define WM8766_MUTE1_MPD (1 << 6)
+#define WM8766_REG_MUTE2 0x0f
+#define WM8766_MUTE2_MPD (1 << 5)
+#define WM8766_REG_RESET 0x1f
+
+#define WM8766_REG_COUNT 0x10 /* don't cache the RESET register */
+
+struct snd_wm8766;
+
+struct snd_wm8766_ops {
+ void (*write)(struct snd_wm8766 *wm, u16 addr, u16 data);
+};
+
+enum snd_wm8766_ctl_id {
+ WM8766_CTL_CH1_VOL,
+ WM8766_CTL_CH2_VOL,
+ WM8766_CTL_CH3_VOL,
+ WM8766_CTL_CH1_SW,
+ WM8766_CTL_CH2_SW,
+ WM8766_CTL_CH3_SW,
+ WM8766_CTL_PHASE1_SW,
+ WM8766_CTL_PHASE2_SW,
+ WM8766_CTL_PHASE3_SW,
+ WM8766_CTL_DEEMPH1_SW,
+ WM8766_CTL_DEEMPH2_SW,
+ WM8766_CTL_DEEMPH3_SW,
+ WM8766_CTL_IZD_SW,
+ WM8766_CTL_ZC_SW,
+
+ WM8766_CTL_COUNT,
+};
+
+#define WM8766_ENUM_MAX 16
+
+#define WM8766_FLAG_STEREO (1 << 0)
+#define WM8766_FLAG_VOL_UPDATE (1 << 1)
+#define WM8766_FLAG_INVERT (1 << 2)
+#define WM8766_FLAG_LIM (1 << 3)
+#define WM8766_FLAG_ALC (1 << 4)
+
+struct snd_wm8766_ctl {
+ struct snd_kcontrol *kctl;
+ const char *name;
+ snd_ctl_elem_type_t type;
+ const char *const enum_names[WM8766_ENUM_MAX];
+ const unsigned int *tlv;
+ u16 reg1, reg2, mask1, mask2, min, max, flags;
+ void (*set)(struct snd_wm8766 *wm, u16 ch1, u16 ch2);
+ void (*get)(struct snd_wm8766 *wm, u16 *ch1, u16 *ch2);
+};
+
+enum snd_wm8766_agc_mode { WM8766_AGC_OFF, WM8766_AGC_LIM, WM8766_AGC_ALC };
+
+struct snd_wm8766 {
+ struct snd_card *card;
+ struct snd_wm8766_ctl ctl[WM8766_CTL_COUNT];
+ enum snd_wm8766_agc_mode agc_mode;
+ struct snd_wm8766_ops ops;
+ u16 regs[WM8766_REG_COUNT]; /* 9-bit registers */
+};
+
+
+
+void snd_wm8766_init(struct snd_wm8766 *wm);
+void snd_wm8766_resume(struct snd_wm8766 *wm);
+void snd_wm8766_set_if(struct snd_wm8766 *wm, u16 dac);
+void snd_wm8766_volume_restore(struct snd_wm8766 *wm);
+int snd_wm8766_build_controls(struct snd_wm8766 *wm);
+
+#endif /* __SOUND_WM8766_H */
diff --git a/sound/pci/ice1712/wm8776.c b/sound/pci/ice1712/wm8776.c
new file mode 100644
index 0000000000..493425697b
--- /dev/null
+++ b/sound/pci/ice1712/wm8776.c
@@ -0,0 +1,601 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT17xx
+ *
+ * Lowlevel functions for WM8776 codec
+ *
+ * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
+ */
+
+#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,
+ const char *ctl_name,
+ bool active)
+{
+ struct snd_card *card = wm->card;
+ struct snd_kcontrol *kctl;
+ struct snd_kcontrol_volatile *vd;
+ unsigned int index_offset;
+
+ kctl = snd_ctl_find_id_mixer(card, ctl_name);
+ 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 const 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_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);
+ if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
+ 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 a 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;
+}
diff --git a/sound/pci/ice1712/wm8776.h b/sound/pci/ice1712/wm8776.h
new file mode 100644
index 0000000000..0d49d0c748
--- /dev/null
+++ b/sound/pci/ice1712/wm8776.h
@@ -0,0 +1,209 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef __SOUND_WM8776_H
+#define __SOUND_WM8776_H
+
+/*
+ * ALSA driver for ICEnsemble VT17xx
+ *
+ * Lowlevel functions for WM8776 codec
+ *
+ * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
+ */
+
+#define WM8776_REG_HPLVOL 0x00
+#define WM8776_REG_HPRVOL 0x01
+#define WM8776_REG_HPMASTER 0x02
+#define WM8776_HPVOL_MASK 0x17f /* incl. update bit */
+#define WM8776_VOL_HPZCEN (1 << 7) /* zero cross detect */
+#define WM8776_VOL_UPDATE (1 << 8) /* update volume */
+#define WM8776_REG_DACLVOL 0x03
+#define WM8776_REG_DACRVOL 0x04
+#define WM8776_REG_DACMASTER 0x05
+#define WM8776_DACVOL_MASK 0x1ff /* incl. update bit */
+#define WM8776_REG_PHASESWAP 0x06
+#define WM8776_PHASE_INVERTL (1 << 0)
+#define WM8776_PHASE_INVERTR (1 << 1)
+#define WM8776_REG_DACCTRL1 0x07
+#define WM8776_DAC_DZCEN (1 << 0)
+#define WM8776_DAC_ATC (1 << 1)
+#define WM8776_DAC_IZD (1 << 2)
+#define WM8776_DAC_TOD (1 << 3)
+#define WM8776_DAC_PL_MASK 0xf0
+#define WM8776_DAC_PL_LL (1 << 4) /* L chan: L signal */
+#define WM8776_DAC_PL_LR (2 << 4) /* L chan: R signal */
+#define WM8776_DAC_PL_LB (3 << 4) /* L chan: both */
+#define WM8776_DAC_PL_RL (1 << 6) /* R chan: L signal */
+#define WM8776_DAC_PL_RR (2 << 6) /* R chan: R signal */
+#define WM8776_DAC_PL_RB (3 << 6) /* R chan: both */
+#define WM8776_REG_DACMUTE 0x08
+#define WM8776_DACMUTE (1 << 0)
+#define WM8776_REG_DACCTRL2 0x09
+#define WM8776_DAC2_DEEMPH (1 << 0)
+#define WM8776_DAC2_ZFLAG_DISABLE (0 << 1)
+#define WM8776_DAC2_ZFLAG_OWN (1 << 1)
+#define WM8776_DAC2_ZFLAG_BOTH (2 << 1)
+#define WM8776_DAC2_ZFLAG_EITHER (3 << 1)
+#define WM8776_REG_DACIFCTRL 0x0a
+#define WM8776_FMT_RIGHTJ (0 << 0)
+#define WM8776_FMT_LEFTJ (1 << 0)
+#define WM8776_FMT_I2S (2 << 0)
+#define WM8776_FMT_DSP (3 << 0)
+#define WM8776_FMT_DSP_LATE (1 << 2) /* in DSP mode */
+#define WM8776_FMT_LRC_INVERTED (1 << 2) /* in other modes */
+#define WM8776_FMT_BCLK_INVERTED (1 << 3)
+#define WM8776_FMT_16BIT (0 << 4)
+#define WM8776_FMT_20BIT (1 << 4)
+#define WM8776_FMT_24BIT (2 << 4)
+#define WM8776_FMT_32BIT (3 << 4)
+#define WM8776_REG_ADCIFCTRL 0x0b
+#define WM8776_FMT_ADCMCLK_INVERTED (1 << 6)
+#define WM8776_FMT_ADCHPD (1 << 8)
+#define WM8776_REG_MSTRCTRL 0x0c
+#define WM8776_IF_ADC256FS (2 << 0)
+#define WM8776_IF_ADC384FS (3 << 0)
+#define WM8776_IF_ADC512FS (4 << 0)
+#define WM8776_IF_ADC768FS (5 << 0)
+#define WM8776_IF_OVERSAMP64 (1 << 3)
+#define WM8776_IF_DAC128FS (0 << 4)
+#define WM8776_IF_DAC192FS (1 << 4)
+#define WM8776_IF_DAC256FS (2 << 4)
+#define WM8776_IF_DAC384FS (3 << 4)
+#define WM8776_IF_DAC512FS (4 << 4)
+#define WM8776_IF_DAC768FS (5 << 4)
+#define WM8776_IF_DAC_MASTER (1 << 7)
+#define WM8776_IF_ADC_MASTER (1 << 8)
+#define WM8776_REG_PWRDOWN 0x0d
+#define WM8776_PWR_PDWN (1 << 0)
+#define WM8776_PWR_ADCPD (1 << 1)
+#define WM8776_PWR_DACPD (1 << 2)
+#define WM8776_PWR_HPPD (1 << 3)
+#define WM8776_PWR_AINPD (1 << 6)
+#define WM8776_REG_ADCLVOL 0x0e
+#define WM8776_REG_ADCRVOL 0x0f
+#define WM8776_ADC_GAIN_MASK 0xff
+#define WM8776_ADC_ZCEN (1 << 8)
+#define WM8776_REG_ALCCTRL1 0x10
+#define WM8776_ALC1_LCT_MASK 0x0f /* 0=-16dB, 1=-15dB..15=-1dB */
+#define WM8776_ALC1_MAXGAIN_MASK 0x70 /* 0,1=0dB, 2=+4dB...7=+24dB */
+#define WM8776_ALC1_LCSEL_MASK 0x180
+#define WM8776_ALC1_LCSEL_LIMITER (0 << 7)
+#define WM8776_ALC1_LCSEL_ALCR (1 << 7)
+#define WM8776_ALC1_LCSEL_ALCL (2 << 7)
+#define WM8776_ALC1_LCSEL_ALCSTEREO (3 << 7)
+#define WM8776_REG_ALCCTRL2 0x11
+#define WM8776_ALC2_HOLD_MASK 0x0f /*0=0ms, 1=2.67ms, 2=5.33ms.. */
+#define WM8776_ALC2_ZCEN (1 << 7)
+#define WM8776_ALC2_LCEN (1 << 8)
+#define WM8776_REG_ALCCTRL3 0x12
+#define WM8776_ALC3_ATK_MASK 0x0f
+#define WM8776_ALC3_DCY_MASK 0xf0
+#define WM8776_ALC3_FDECAY (1 << 8)
+#define WM8776_REG_NOISEGATE 0x13
+#define WM8776_NGAT_ENABLE (1 << 0)
+#define WM8776_NGAT_THR_MASK 0x1c /*0=-78dB, 1=-72dB...7=-36dB */
+#define WM8776_REG_LIMITER 0x14
+#define WM8776_LIM_MAXATTEN_MASK 0x0f
+#define WM8776_LIM_TRANWIN_MASK 0x70 /*0=0us, 1=62.5us, 2=125us.. */
+#define WM8776_REG_ADCMUX 0x15
+#define WM8776_ADC_MUX_AIN1 (1 << 0)
+#define WM8776_ADC_MUX_AIN2 (1 << 1)
+#define WM8776_ADC_MUX_AIN3 (1 << 2)
+#define WM8776_ADC_MUX_AIN4 (1 << 3)
+#define WM8776_ADC_MUX_AIN5 (1 << 4)
+#define WM8776_ADC_MUTER (1 << 6)
+#define WM8776_ADC_MUTEL (1 << 7)
+#define WM8776_ADC_LRBOTH (1 << 8)
+#define WM8776_REG_OUTMUX 0x16
+#define WM8776_OUTMUX_DAC (1 << 0)
+#define WM8776_OUTMUX_AUX (1 << 1)
+#define WM8776_OUTMUX_BYPASS (1 << 2)
+#define WM8776_REG_RESET 0x17
+
+#define WM8776_REG_COUNT 0x17 /* don't cache the RESET register */
+
+struct snd_wm8776;
+
+struct snd_wm8776_ops {
+ void (*write)(struct snd_wm8776 *wm, u8 addr, u8 data);
+};
+
+enum snd_wm8776_ctl_id {
+ WM8776_CTL_DAC_VOL,
+ WM8776_CTL_DAC_SW,
+ WM8776_CTL_DAC_ZC_SW,
+ WM8776_CTL_HP_VOL,
+ WM8776_CTL_HP_SW,
+ WM8776_CTL_HP_ZC_SW,
+ WM8776_CTL_AUX_SW,
+ WM8776_CTL_BYPASS_SW,
+ WM8776_CTL_DAC_IZD_SW,
+ WM8776_CTL_PHASE_SW,
+ WM8776_CTL_DEEMPH_SW,
+ WM8776_CTL_ADC_VOL,
+ WM8776_CTL_ADC_SW,
+ WM8776_CTL_INPUT1_SW,
+ WM8776_CTL_INPUT2_SW,
+ WM8776_CTL_INPUT3_SW,
+ WM8776_CTL_INPUT4_SW,
+ WM8776_CTL_INPUT5_SW,
+ WM8776_CTL_AGC_SEL,
+ WM8776_CTL_LIM_THR,
+ WM8776_CTL_LIM_ATK,
+ WM8776_CTL_LIM_DCY,
+ WM8776_CTL_LIM_TRANWIN,
+ WM8776_CTL_LIM_MAXATTN,
+ WM8776_CTL_ALC_TGT,
+ WM8776_CTL_ALC_ATK,
+ WM8776_CTL_ALC_DCY,
+ WM8776_CTL_ALC_MAXGAIN,
+ WM8776_CTL_ALC_MAXATTN,
+ WM8776_CTL_ALC_HLD,
+ WM8776_CTL_NGT_SW,
+ WM8776_CTL_NGT_THR,
+
+ WM8776_CTL_COUNT,
+};
+
+#define WM8776_ENUM_MAX 16
+
+#define WM8776_FLAG_STEREO (1 << 0)
+#define WM8776_FLAG_VOL_UPDATE (1 << 1)
+#define WM8776_FLAG_INVERT (1 << 2)
+#define WM8776_FLAG_LIM (1 << 3)
+#define WM8776_FLAG_ALC (1 << 4)
+
+struct snd_wm8776_ctl {
+ const char *name;
+ snd_ctl_elem_type_t type;
+ const char *const enum_names[WM8776_ENUM_MAX];
+ const unsigned int *tlv;
+ u16 reg1, reg2, mask1, mask2, min, max, flags;
+ void (*set)(struct snd_wm8776 *wm, u16 ch1, u16 ch2);
+ void (*get)(struct snd_wm8776 *wm, u16 *ch1, u16 *ch2);
+};
+
+enum snd_wm8776_agc_mode {
+ WM8776_AGC_OFF,
+ WM8776_AGC_LIM,
+ WM8776_AGC_ALC_R,
+ WM8776_AGC_ALC_L,
+ WM8776_AGC_ALC_STEREO
+};
+
+struct snd_wm8776 {
+ struct snd_card *card;
+ struct snd_wm8776_ctl ctl[WM8776_CTL_COUNT];
+ enum snd_wm8776_agc_mode agc_mode;
+ struct snd_wm8776_ops ops;
+ u16 regs[WM8776_REG_COUNT]; /* 9-bit registers */
+};
+
+
+
+void snd_wm8776_init(struct snd_wm8776 *wm);
+void snd_wm8776_resume(struct snd_wm8776 *wm);
+void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power);
+void snd_wm8776_volume_restore(struct snd_wm8776 *wm);
+int snd_wm8776_build_controls(struct snd_wm8776 *wm);
+
+#endif /* __SOUND_WM8776_H */
diff --git a/sound/pci/ice1712/wtm.c b/sound/pci/ice1712/wtm.c
new file mode 100644
index 0000000000..f613f0067d
--- /dev/null
+++ b/sound/pci/ice1712/wtm.c
@@ -0,0 +1,632 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA driver for ICEnsemble VT1724 (Envy24HT)
+ *
+ * Lowlevel functions for Ego Sys Waveterminal 192M
+ *
+ * Copyright (c) 2006 Guedez Clement <klem.dev@gmail.com>
+ * Some functions are taken from the Prodigy192 driver
+ * source
+ */
+
+
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/tlv.h>
+#include <linux/slab.h>
+
+#include "ice1712.h"
+#include "envy24ht.h"
+#include "wtm.h"
+#include "stac946x.h"
+
+struct wtm_spec {
+ /* rate change needs atomic mute/unmute of all dacs*/
+ struct mutex mute_mutex;
+};
+
+
+/*
+ * 2*ADC 6*DAC no1 ringbuffer r/w on i2c bus
+ */
+static inline void stac9460_put(struct snd_ice1712 *ice, int reg,
+ unsigned char val)
+{
+ snd_vt1724_write_i2c(ice, STAC9460_I2C_ADDR, reg, val);
+}
+
+static inline unsigned char stac9460_get(struct snd_ice1712 *ice, int reg)
+{
+ return snd_vt1724_read_i2c(ice, STAC9460_I2C_ADDR, reg);
+}
+
+/*
+ * 2*ADC 2*DAC no2 ringbuffer r/w on i2c bus
+ */
+static inline void stac9460_2_put(struct snd_ice1712 *ice, int reg,
+ unsigned char val)
+{
+ snd_vt1724_write_i2c(ice, STAC9460_2_I2C_ADDR, reg, val);
+}
+
+static inline unsigned char stac9460_2_get(struct snd_ice1712 *ice, int reg)
+{
+ return snd_vt1724_read_i2c(ice, STAC9460_2_I2C_ADDR, reg);
+}
+
+
+/*
+ * DAC mute control
+ */
+static void stac9460_dac_mute_all(struct snd_ice1712 *ice, unsigned char mute,
+ unsigned short int *change_mask)
+{
+ unsigned char new, old;
+ int id, idx, change;
+
+ /*stac9460 1*/
+ for (id = 0; id < 7; id++) {
+ if (*change_mask & (0x01 << id)) {
+ if (id == 0)
+ idx = STAC946X_MASTER_VOLUME;
+ else
+ idx = STAC946X_LF_VOLUME - 1 + id;
+ old = stac9460_get(ice, idx);
+ new = (~mute << 7 & 0x80) | (old & ~0x80);
+ change = (new != old);
+ if (change) {
+ stac9460_put(ice, idx, new);
+ *change_mask = *change_mask | (0x01 << id);
+ } else {
+ *change_mask = *change_mask & ~(0x01 << id);
+ }
+ }
+ }
+
+ /*stac9460 2*/
+ for (id = 0; id < 3; id++) {
+ if (*change_mask & (0x01 << (id + 7))) {
+ if (id == 0)
+ idx = STAC946X_MASTER_VOLUME;
+ else
+ idx = STAC946X_LF_VOLUME - 1 + id;
+ old = stac9460_2_get(ice, idx);
+ new = (~mute << 7 & 0x80) | (old & ~0x80);
+ change = (new != old);
+ if (change) {
+ stac9460_2_put(ice, idx, new);
+ *change_mask = *change_mask | (0x01 << id);
+ } else {
+ *change_mask = *change_mask & ~(0x01 << id);
+ }
+ }
+ }
+}
+
+
+
+#define stac9460_dac_mute_info snd_ctl_boolean_mono_info
+
+static int stac9460_dac_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ struct wtm_spec *spec = ice->spec;
+ unsigned char val;
+ int idx, id;
+
+ mutex_lock(&spec->mute_mutex);
+
+ if (kcontrol->private_value) {
+ idx = STAC946X_MASTER_VOLUME;
+ id = 0;
+ } else {
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ idx = id + STAC946X_LF_VOLUME;
+ }
+ if (id < 6)
+ val = stac9460_get(ice, idx);
+ else
+ val = stac9460_2_get(ice, idx - 6);
+ ucontrol->value.integer.value[0] = (~val >> 7) & 0x1;
+
+ mutex_unlock(&spec->mute_mutex);
+ return 0;
+}
+
+static int stac9460_dac_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char new, old;
+ int id, idx;
+ int change;
+
+ if (kcontrol->private_value) {
+ idx = STAC946X_MASTER_VOLUME;
+ old = stac9460_get(ice, idx);
+ new = (~ucontrol->value.integer.value[0] << 7 & 0x80) |
+ (old & ~0x80);
+ change = (new != old);
+ if (change) {
+ stac9460_put(ice, idx, new);
+ stac9460_2_put(ice, idx, new);
+ }
+ } else {
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ idx = id + STAC946X_LF_VOLUME;
+ if (id < 6)
+ old = stac9460_get(ice, idx);
+ else
+ old = stac9460_2_get(ice, idx - 6);
+ new = (~ucontrol->value.integer.value[0] << 7 & 0x80) |
+ (old & ~0x80);
+ change = (new != old);
+ if (change) {
+ if (id < 6)
+ stac9460_put(ice, idx, new);
+ else
+ stac9460_2_put(ice, idx - 6, new);
+ }
+ }
+ return change;
+}
+
+/*
+ * DAC volume attenuation mixer control
+ */
+static int stac9460_dac_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0; /* mute */
+ uinfo->value.integer.max = 0x7f; /* 0dB */
+ return 0;
+}
+
+static int stac9460_dac_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx, id;
+ unsigned char vol;
+
+ if (kcontrol->private_value) {
+ idx = STAC946X_MASTER_VOLUME;
+ id = 0;
+ } else {
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ idx = id + STAC946X_LF_VOLUME;
+ }
+ if (id < 6)
+ vol = stac9460_get(ice, idx) & 0x7f;
+ else
+ vol = stac9460_2_get(ice, idx - 6) & 0x7f;
+ ucontrol->value.integer.value[0] = 0x7f - vol;
+ return 0;
+}
+
+static int stac9460_dac_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int idx, id;
+ unsigned char tmp, ovol, nvol;
+ int change;
+
+ if (kcontrol->private_value) {
+ idx = STAC946X_MASTER_VOLUME;
+ nvol = ucontrol->value.integer.value[0] & 0x7f;
+ tmp = stac9460_get(ice, idx);
+ ovol = 0x7f - (tmp & 0x7f);
+ change = (ovol != nvol);
+ if (change) {
+ stac9460_put(ice, idx, (0x7f - nvol) | (tmp & 0x80));
+ stac9460_2_put(ice, idx, (0x7f - nvol) | (tmp & 0x80));
+ }
+ } else {
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ idx = id + STAC946X_LF_VOLUME;
+ nvol = ucontrol->value.integer.value[0] & 0x7f;
+ if (id < 6)
+ tmp = stac9460_get(ice, idx);
+ else
+ tmp = stac9460_2_get(ice, idx - 6);
+ ovol = 0x7f - (tmp & 0x7f);
+ change = (ovol != nvol);
+ if (change) {
+ if (id < 6)
+ stac9460_put(ice, idx, (0x7f - nvol) |
+ (tmp & 0x80));
+ else
+ stac9460_2_put(ice, idx-6, (0x7f - nvol) |
+ (tmp & 0x80));
+ }
+ }
+ return change;
+}
+
+/*
+ * ADC mute control
+ */
+#define stac9460_adc_mute_info snd_ctl_boolean_stereo_info
+
+static int stac9460_adc_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+ int i, id;
+
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ if (id == 0) {
+ for (i = 0; i < 2; ++i) {
+ val = stac9460_get(ice, STAC946X_MIC_L_VOLUME + i);
+ ucontrol->value.integer.value[i] = ~val>>7 & 0x1;
+ }
+ } else {
+ for (i = 0; i < 2; ++i) {
+ val = stac9460_2_get(ice, STAC946X_MIC_L_VOLUME + i);
+ ucontrol->value.integer.value[i] = ~val>>7 & 0x1;
+ }
+ }
+ return 0;
+}
+
+static int stac9460_adc_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char new, old;
+ int i, reg, id;
+ int change;
+
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ if (id == 0) {
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ old = stac9460_get(ice, reg);
+ new = (~ucontrol->value.integer.value[i]<<7&0x80) |
+ (old&~0x80);
+ change = (new != old);
+ if (change)
+ stac9460_put(ice, reg, new);
+ }
+ } else {
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ old = stac9460_2_get(ice, reg);
+ new = (~ucontrol->value.integer.value[i]<<7&0x80) |
+ (old&~0x80);
+ change = (new != old);
+ if (change)
+ stac9460_2_put(ice, reg, new);
+ }
+ }
+ return change;
+}
+
+/*
+ *ADC gain mixer control
+ */
+static int stac9460_adc_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0; /* 0dB */
+ uinfo->value.integer.max = 0x0f; /* 22.5dB */
+ return 0;
+}
+
+static int stac9460_adc_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int i, reg, id;
+ unsigned char vol;
+
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ if (id == 0) {
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ vol = stac9460_get(ice, reg) & 0x0f;
+ ucontrol->value.integer.value[i] = 0x0f - vol;
+ }
+ } else {
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ vol = stac9460_2_get(ice, reg) & 0x0f;
+ ucontrol->value.integer.value[i] = 0x0f - vol;
+ }
+ }
+ return 0;
+}
+
+static int stac9460_adc_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ int i, reg, id;
+ unsigned char ovol, nvol;
+ int change;
+
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ if (id == 0) {
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ nvol = ucontrol->value.integer.value[i] & 0x0f;
+ ovol = 0x0f - stac9460_get(ice, reg);
+ change = ((ovol & 0x0f) != nvol);
+ if (change)
+ stac9460_put(ice, reg, (0x0f - nvol) |
+ (ovol & ~0x0f));
+ }
+ } else {
+ for (i = 0; i < 2; ++i) {
+ reg = STAC946X_MIC_L_VOLUME + i;
+ nvol = ucontrol->value.integer.value[i] & 0x0f;
+ ovol = 0x0f - stac9460_2_get(ice, reg);
+ change = ((ovol & 0x0f) != nvol);
+ if (change)
+ stac9460_2_put(ice, reg, (0x0f - nvol) |
+ (ovol & ~0x0f));
+ }
+ }
+ return change;
+}
+
+/*
+ * MIC / LINE switch fonction
+ */
+static int stac9460_mic_sw_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[2] = { "Line In", "Mic" };
+
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
+}
+
+
+static int stac9460_mic_sw_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char val;
+ int id;
+
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ if (id == 0)
+ val = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+ else
+ val = stac9460_2_get(ice, STAC946X_GENERAL_PURPOSE);
+ ucontrol->value.enumerated.item[0] = (val >> 7) & 0x1;
+ return 0;
+}
+
+static int stac9460_mic_sw_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol);
+ unsigned char new, old;
+ int change, id;
+
+ id = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ if (id == 0)
+ old = stac9460_get(ice, STAC946X_GENERAL_PURPOSE);
+ else
+ old = stac9460_2_get(ice, STAC946X_GENERAL_PURPOSE);
+ new = (ucontrol->value.enumerated.item[0] << 7 & 0x80) | (old & ~0x80);
+ change = (new != old);
+ if (change) {
+ if (id == 0)
+ stac9460_put(ice, STAC946X_GENERAL_PURPOSE, new);
+ else
+ stac9460_2_put(ice, STAC946X_GENERAL_PURPOSE, new);
+ }
+ return change;
+}
+
+
+/*
+ * Handler for setting correct codec rate - called when rate change is detected
+ */
+static void stac9460_set_rate_val(struct snd_ice1712 *ice, unsigned int rate)
+{
+ unsigned char old, new;
+ unsigned short int changed;
+ struct wtm_spec *spec = ice->spec;
+
+ if (rate == 0) /* no hint - S/PDIF input is master, simply return */
+ return;
+ else if (rate <= 48000)
+ new = 0x08; /* 256x, base rate mode */
+ else if (rate <= 96000)
+ new = 0x11; /* 256x, mid rate mode */
+ else
+ new = 0x12; /* 128x, high rate mode */
+
+ old = stac9460_get(ice, STAC946X_MASTER_CLOCKING);
+ if (old == new)
+ return;
+ /* change detected, setting master clock, muting first */
+ /* due to possible conflicts with mute controls - mutexing */
+ mutex_lock(&spec->mute_mutex);
+ /* we have to remember current mute status for each DAC */
+ changed = 0xFFFF;
+ stac9460_dac_mute_all(ice, 0, &changed);
+ /*printk(KERN_DEBUG "Rate change: %d, new MC: 0x%02x\n", rate, new);*/
+ stac9460_put(ice, STAC946X_MASTER_CLOCKING, new);
+ stac9460_2_put(ice, STAC946X_MASTER_CLOCKING, new);
+ udelay(10);
+ /* unmuting - only originally unmuted dacs -
+ * i.e. those changed when muting */
+ stac9460_dac_mute_all(ice, 1, &changed);
+ mutex_unlock(&spec->mute_mutex);
+}
+
+
+/*Limits value in dB for fader*/
+static const DECLARE_TLV_DB_SCALE(db_scale_dac, -19125, 75, 0);
+static const DECLARE_TLV_DB_SCALE(db_scale_adc, 0, 150, 0);
+
+/*
+ * Control tabs
+ */
+static const struct snd_kcontrol_new stac9640_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+ .name = "Master Playback Switch",
+ .info = stac9460_dac_mute_info,
+ .get = stac9460_dac_mute_get,
+ .put = stac9460_dac_mute_put,
+ .private_value = 1,
+ .tlv = { .p = db_scale_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Master Playback Volume",
+ .info = stac9460_dac_vol_info,
+ .get = stac9460_dac_vol_get,
+ .put = stac9460_dac_vol_put,
+ .private_value = 1,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "MIC/Line Input Enum",
+ .count = 2,
+ .info = stac9460_mic_sw_info,
+ .get = stac9460_mic_sw_get,
+ .put = stac9460_mic_sw_put,
+
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "DAC Switch",
+ .count = 8,
+ .info = stac9460_dac_mute_info,
+ .get = stac9460_dac_mute_get,
+ .put = stac9460_dac_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+
+ .name = "DAC Volume",
+ .count = 8,
+ .info = stac9460_dac_vol_info,
+ .get = stac9460_dac_vol_get,
+ .put = stac9460_dac_vol_put,
+ .tlv = { .p = db_scale_dac }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "ADC Switch",
+ .count = 2,
+ .info = stac9460_adc_mute_info,
+ .get = stac9460_adc_mute_get,
+ .put = stac9460_adc_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+
+ .name = "ADC Volume",
+ .count = 2,
+ .info = stac9460_adc_vol_info,
+ .get = stac9460_adc_vol_get,
+ .put = stac9460_adc_vol_put,
+ .tlv = { .p = db_scale_adc }
+ }
+};
+
+
+
+/*INIT*/
+static int wtm_add_controls(struct snd_ice1712 *ice)
+{
+ unsigned int i;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(stac9640_controls); i++) {
+ err = snd_ctl_add(ice->card,
+ snd_ctl_new1(&stac9640_controls[i], ice));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int wtm_init(struct snd_ice1712 *ice)
+{
+ static const unsigned short stac_inits_wtm[] = {
+ STAC946X_RESET, 0,
+ STAC946X_MASTER_CLOCKING, 0x11,
+ (unsigned short)-1
+ };
+ const unsigned short *p;
+ struct wtm_spec *spec;
+
+ /*WTM 192M*/
+ ice->num_total_dacs = 8;
+ ice->num_total_adcs = 4;
+ ice->force_rdma1 = 1;
+
+ /*init mutex for dac mute conflict*/
+ spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+ if (!spec)
+ return -ENOMEM;
+ ice->spec = spec;
+ mutex_init(&spec->mute_mutex);
+
+
+ /*initialize codec*/
+ p = stac_inits_wtm;
+ for (; *p != (unsigned short)-1; p += 2) {
+ stac9460_put(ice, p[0], p[1]);
+ stac9460_2_put(ice, p[0], p[1]);
+ }
+ ice->gpio.set_pro_rate = stac9460_set_rate_val;
+ return 0;
+}
+
+
+static const unsigned char wtm_eeprom[] = {
+ [ICE_EEP2_SYSCONF] = 0x67, /*SYSCONF: clock 192KHz, mpu401,
+ 4ADC, 8DAC */
+ [ICE_EEP2_ACLINK] = 0x80, /* ACLINK : I2S */
+ [ICE_EEP2_I2S] = 0xf8, /* I2S: vol; 96k, 24bit, 192k */
+ [ICE_EEP2_SPDIF] = 0xc1, /*SPDIF: out-en, spidf ext out*/
+ [ICE_EEP2_GPIO_DIR] = 0x9f,
+ [ICE_EEP2_GPIO_DIR1] = 0xff,
+ [ICE_EEP2_GPIO_DIR2] = 0x7f,
+ [ICE_EEP2_GPIO_MASK] = 0x9f,
+ [ICE_EEP2_GPIO_MASK1] = 0xff,
+ [ICE_EEP2_GPIO_MASK2] = 0x7f,
+ [ICE_EEP2_GPIO_STATE] = 0x16,
+ [ICE_EEP2_GPIO_STATE1] = 0x80,
+ [ICE_EEP2_GPIO_STATE2] = 0x00,
+};
+
+
+/*entry point*/
+struct snd_ice1712_card_info snd_vt1724_wtm_cards[] = {
+ {
+ .subvendor = VT1724_SUBDEVICE_WTM,
+ .name = "ESI Waveterminal 192M",
+ .model = "WT192M",
+ .chip_init = wtm_init,
+ .build_controls = wtm_add_controls,
+ .eeprom_size = sizeof(wtm_eeprom),
+ .eeprom_data = wtm_eeprom,
+ },
+ {} /*terminator*/
+};
diff --git a/sound/pci/ice1712/wtm.h b/sound/pci/ice1712/wtm.h
new file mode 100644
index 0000000000..1cfcbde15f
--- /dev/null
+++ b/sound/pci/ice1712/wtm.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SOUND_WTM_H
+#define __SOUND_WTM_H
+
+/* ID */
+#define WTM_DEVICE_DESC "{EGO SYS INC,WaveTerminal 192M},"
+#define VT1724_SUBDEVICE_WTM 0x36495345 /* WT192M ver1.0 */
+
+/*
+ *chip addresses on I2C bus
+ */
+
+#define AK4114_ADDR 0x20 /*S/PDIF receiver*/
+#define STAC9460_I2C_ADDR 0x54 /* ADC*2 | DAC*6 */
+#define STAC9460_2_I2C_ADDR 0x56 /* ADC|DAC *2 */
+
+
+extern struct snd_ice1712_card_info snd_vt1724_wtm_cards[];
+
+#endif /* __SOUND_WTM_H */
+