Linux Sound subsystem development
 help / color / mirror / Atom feed
From: root <davide.gironi@gmail.com>
To: linux-sound@vger.kernel.org
Cc: perex@perex.cz, tiwai@suse.com, Davide Gironi <davide.gironi@gmail.com>
Subject: [PATCH] ASoC: ice1712: add TerraTec PHASE 88 support
Date: Sat, 24 Oct 2026 13:27:00 +0200	[thread overview]
Message-ID: <20261024112700.3634-1-root@debian.lab.local> (raw)

From: Davide Gironi <davide.gironi@gmail.com>

Add board-specific handling for the TerraTec PHASE 88, which shares an
EWS88MT device ID but uses different external hardware.

Use the write-only expander at 0x42 for AK4524 chip selection, initialize
its second register, and add the measured Line/Mic 7/8 control. Correct the
EEPROM-reported AC'97 codec and MIDI UART configuration, and guard consumer
capture open when AC'97 initialization failed.

Tested on two PHASE 88 racks with kernel 6.18.55; built against Debian
6.12.111. The patch applies to 6.18.58.

Assisted-by: Pi:gpt-6-luna
Signed-off-by: Davide Gironi <davide.gironi@gmail.com>
---
 sound/pci/ice1712/ews.c     | 163 +++++++++++++++++++++++++++++++++++-
 sound/pci/ice1712/ews.h     |  26 ++++++
 sound/pci/ice1712/ice1712.c |  38 ++++++++-
 3 files changed, 224 insertions(+), 3 deletions(-)

diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c
index 1dffcb0..ac36370 100644
--- a/sound/pci/ice1712/ews.c
+++ b/sound/pci/ice1712/ews.c
@@ -32,6 +32,8 @@ enum {
 /* additional i2c devices for EWS boards */
 struct ews_spec {
 	struct snd_i2c_device *i2cdevs[3];
+	unsigned char pcf8575_data;	/* PHASE 88: shadow of the chip select byte */
+	unsigned char pcf8575_input;	/* PHASE 88: shadow of the input select byte */
 };
 
 /*
@@ -128,12 +130,130 @@ static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = {
  * AK4524 access
  */
 
+/*
+ * PHASE 88 AK4524 chip select
+ *
+ * Contrary to the EWS88MT the chip select byte of the PHASE 88 cannot be
+ * read back: the expander is only ever written by the original Windows
+ * driver, which composes the byte from its own state.  Therefore keep a
+ * shadow copy of the flag bits and always write the complete value.
+ */
+/*
+ * Write one register of the PHASE 88 expander.
+ *
+ * The bit-banged bus of the EWS boards takes the GPIO lock on its own, so
+ * the caller must not hold ice->gpio_mutex - it is not a recursive mutex
+ * and the writer would deadlock itself inside ewx_i2c_start().
+ */
+static int snd_ice1712_phase88_write(struct snd_ice1712 *ice,
+				     unsigned char page, unsigned char value)
+{
+	struct ews_spec *spec = ice->spec;
+	unsigned char bytes[2];
+	int err;
+
+	bytes[0] = page;
+	bytes[1] = value;
+	snd_i2c_lock(ice->i2c);
+	err = snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], bytes, 2);
+	snd_i2c_unlock(ice->i2c);
+	if (err != 2) {
+		dev_err(ice->card->dev,
+			"PHASE 88 expander write failed (page 0x%02x, addr 0x%02x, err %d)\n",
+			page, spec->i2cdevs[EWS_I2C_PCF2]->addr, err);
+		return -EIO;
+	}
+	return 0;
+}
+
+static int snd_ice1712_phase88_chip_select(struct snd_ice1712 *ice, int chip_mask)
+{
+	struct ews_spec *spec = ice->spec;
+	int err;
+
+	if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f))
+		return -EINVAL;
+	spec->pcf8575_data = (spec->pcf8575_data & 0xf0) | chip_mask;
+	err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_OUTPUT_PAGE,
+					spec->pcf8575_data);
+	if (err < 0)
+		spec->pcf8575_data ^= chip_mask;
+	return err;
+}
+
+/*
+ * PHASE 88: Line/Mic select of the four analog input pairs
+ *
+ * The Terratec mixers of the original operating systems offer a "Line/Mic
+ * 1/2 ... 7/8" switch for every pair of the AK4524 codecs; the setting is
+ * kept in the second register of the chip select expander.
+ *
+ * With a microphone on input 7 the level of that pair rises by roughly
+ * 30 dB when bit 4 (0x10) of this register is set, while bits 5 ... 7 do
+ * not change anything that could be measured on the other pairs - only the
+ * pair with the microphone attached could be verified.  The register is
+ * write-only, so the bits are assumed to be ordered from the last pair to
+ * the first one: 0x10 selects the microphone of pair 7/8, 0x80 the one of
+ * pair 1/2.
+ */
+static int snd_ice1712_phase88_linemic_info(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int snd_ice1712_phase88_linemic_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;
+
+	ucontrol->value.integer.value[0] =
+		!!(spec->pcf8575_input & (0x80 >> kcontrol->private_value));
+	return 0;
+}
+
+static int snd_ice1712_phase88_linemic_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 bit = 0x80 >> kcontrol->private_value;
+	unsigned char val = ucontrol->value.integer.value[0] ?
+		spec->pcf8575_input | bit : spec->pcf8575_input & ~bit;
+	int err;
+
+	if (val == spec->pcf8575_input)
+		return 0;
+	err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_INPUT_PAGE, val);
+	if (err < 0)
+		return err;
+	spec->pcf8575_input = val;
+	return 1;
+}
+
+static const struct snd_kcontrol_new snd_ice1712_phase88_linemic_ctl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Line/Mic 7/8 Switch",
+	.info = snd_ice1712_phase88_linemic_info,
+	.get = snd_ice1712_phase88_linemic_get,
+	.put = snd_ice1712_phase88_linemic_put,
+	.private_value = 3,		/* bit 0x10: the pair nothing else drives */
+};
+
 /* 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 (ice->eeprom.subvendor == ICE1712_SUBDEVICE_PHASE88)
+		return snd_ice1712_phase88_chip_select(ice, chip_mask);
+
 	if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f))
 		return -EINVAL;
 	snd_i2c_lock(ice->i2c);
@@ -458,9 +578,37 @@ static int snd_ice1712_ews_init(struct snd_ice1712 *ice)
 		}
 		snd_ice1712_6fire_write_pca(ice, PCF9554_REG_CONFIG, 0x80);
 		break;
+	case ICE1712_SUBDEVICE_PHASE88:
+		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, "Output expander",
+					    ICE1712_PHASE88_OUTPUT_ADDR,
+					    &spec->i2cdevs[EWS_I2C_PCF2]);
+		if (err < 0)
+			return err;
+	spec->pcf8575_data = ICE1712_PHASE88_OUTPUT_INIT;
+		/* Check if the chip select expander is present */
+		err = snd_ice1712_ews88mt_chip_select(ice, 0x0f);
+		if (err < 0)
+			return err;
+		/*
+		 * The latch of the second expander register is undefined
+		 * after power on; the Terratec driver clears it while it
+		 * initialises a "type 8" board, before the codecs are set up.
+		 * With the latch in an unknown state all four AK4524 keep
+		 * their transmit drivers disabled and nothing is captured.
+		 */
+		spec->pcf8575_input = ICE1712_PHASE88_INPUT_INIT;
+		err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_INPUT_PAGE,
+						spec->pcf8575_input);
+		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_i2c_device_create(ice->i2c, "CS8404",
@@ -982,9 +1130,20 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice)
 				return err;
 		}
 		break;
+	case ICE1712_SUBDEVICE_PHASE88:
+		/*
+		 * The PHASE 88 has neither the input sensitivity expander on
+		 * 0x23 nor the output expander on 0x24 of the EWS88MT, so the
+		 * corresponding switches must not be created here; instead
+		 * the Line/Mic select of the codec pairs is offered.
+		 */
+		err = snd_ctl_add(ice->card,
+				  snd_ctl_new1(&snd_ice1712_phase88_linemic_ctl, 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)
diff --git a/sound/pci/ice1712/ews.h b/sound/pci/ice1712/ews.h
index aec8dc6..08f9238 100644
--- a/sound/pci/ice1712/ews.h
+++ b/sound/pci/ice1712/ews.h
@@ -58,6 +58,32 @@ extern struct snd_ice1712_card_info snd_ice1712_ews_cards[];
 #define ICE1712_EWS88MT_OUTPUT_SENSE	0x40	/* mask */
 #define ICE1712_EWS88D_PCF_ADDR		(0x40>>1)
 
+/*
+ * TerraTec PHASE 88 configuration definitions
+ *
+ * The PHASE 88 is derived from the EWS88MT but it does not carry the two
+ * PCF8574 expanders at 0x23/0x24.  The AK4524 chip selects are driven by a
+ * 16-bit output expander answering at the 8-bit address 0x42, programmed
+ * with a two byte transfer: an address/page byte followed by the data byte
+ * holding the chip select bits (0-3, active low) and the board flags.
+ * This matches the "type 8" code path of the Terratec Windows driver for
+ * subsystem 0x1151 (PHASE 88) and 0xf151 (PHASE 88 master).
+ */
+#define ICE1712_PHASE88_OUTPUT_ADDR	(0x42 >> 1)
+#define ICE1712_PHASE88_OUTPUT_PAGE	0x01	/* first byte of the transfer */
+#define ICE1712_PHASE88_OUTPUT_INIT	0x20	/* flags kept asserted (bits 4-7) */
+
+/*
+ * The same expander has a second register that the Terratec drivers clear
+ * once while they initialise a "type 8" board ({0x03, 0x00} written to
+ * 8-bit 0x42).  The latch of the expander is undefined after power on and
+ * the AK4524 codecs stay silent when it is left alone, so it has to be
+ * written before the codecs are set up.  Its four upper bits select either
+ * the line input or the microphone input of the four codec pairs.
+ */
+#define ICE1712_PHASE88_INPUT_PAGE	0x03
+#define ICE1712_PHASE88_INPUT_INIT	0x00
+
 /* 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  */
diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c
index 376df1a..86ecf7b 100644
--- a/sound/pci/ice1712/ice1712.c
+++ b/sound/pci/ice1712/ice1712.c
@@ -768,6 +768,9 @@ 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);
 
+	if (!ice->ac97)
+		return -ENODEV;
+
 	ice->capture_con_substream = substream;
 	runtime->hw = snd_ice1712_capture;
 	runtime->hw.rates = ice->ac97->rates[AC97_RATES_ADC];
@@ -2290,12 +2293,45 @@ static int snd_ice1712_chip_init(struct snd_ice1712 *ice)
 	outb(ICE1712_NATIVE, ICEREG(ice, CONTROL));
 	udelay(200);
 	if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE &&
-	    !ice->dxr_enable)
+	    !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;
+	} else if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_PHASE88) {
+		/*
+		 * The EEPROM of the PHASE 88 advertises a consumer AC'97
+		 * codec, but the board does not carry one - the AC'97 link
+		 * does not answer even after a cold reset (every register
+		 * reads back as 0xffff).  All converters of this card are the
+		 * AKM codecs on the multitrack I2S interface, and the original
+		 * Terratec driver does not touch the AC'97 link either.
+		 * Without this quirk the probe wastes two seconds in the
+		 * AC'97 reset and creates consumer PCM devices that have no
+		 * codec behind them.
+		 */
+		ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_NO_CON_AC97;
+		/*
+		 * The rack has two MIDI interfaces - one on the front panel, one
+		 * on the rear - but the EEPROM advertises only one UART, so
+		 * without this the rear connector stays dead.  Terratec's own
+		 * driver exposes both: "[PHASE88.Interface.Midi1]" and
+		 * "[PHASE88.Interface.Midi2]" in ews88wdm.inf, "PHASE88 MIDI Port
+		 * 1/2", the changelog naming the second one "Mic28 Extensions
+		 * (Midi 2)".  Both UARTs are inside the Envy24, so claiming the
+		 * second one is only a matter of setting the bit the EEPROM
+		 * should have carried.
+		 *
+		 * Verified on a PHASE 88 rack: with a loopback cable in the rear
+		 * connector, a SysEx frame written to the second UART came back
+		 * on it byte-exactly, and the first UART (front connector) kept
+		 * working as before.  With the second UART enabled and both ports
+		 * idle the card raised no interrupt on its IRQ line for 10 s,
+		 * so an unconnected input does not generate traffic.
+		 */
+		ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_2xMPU401;
+	}
 	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]);

             reply	other threads:[~2026-10-24 11:27 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24 11:27 root [this message]
2026-10-26  7:59 ` [PATCH] ASoC: ice1712: add TerraTec PHASE 88 support Takashi Iwai
2026-10-28 18:24   ` [PATCH v2 0/2] ALSA: ice1712: support the TerraTec PHASE 88 Davide Gironi
2026-10-28 18:24     ` [PATCH v2 1/2] ALSA: ice1712: add a board-specific EEPROM initialization hook Davide Gironi
2026-10-28 18:24     ` [PATCH v2 2/2] ALSA: ice1712: add TerraTec PHASE 88 support Davide Gironi
2026-11-02  8:55     ` [PATCH v2 0/2] ALSA: ice1712: support the TerraTec PHASE 88 Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261024112700.3634-1-root@debian.lab.local \
    --to=davide.gironi@gmail.com \
    --cc=linux-sound@vger.kernel.org \
    --cc=perex@perex.cz \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox