All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] ASoC: ice1712: add TerraTec PHASE 88 support
@ 2026-10-24 11:27 root
  2026-10-26  7:59 ` Takashi Iwai
  0 siblings, 1 reply; 5+ messages in thread
From: root @ 2026-10-24 11:27 UTC (permalink / raw)
  To: linux-sound; +Cc: perex, tiwai, Davide Gironi

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]);

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] ASoC: ice1712: add TerraTec PHASE 88 support
  2026-10-24 11:27 [PATCH] ASoC: ice1712: add TerraTec PHASE 88 support root
@ 2026-10-26  7:59 ` Takashi Iwai
  2026-10-28 18:24   ` [PATCH v2 0/2] ALSA: ice1712: support the TerraTec PHASE 88 Davide Gironi
  0 siblings, 1 reply; 5+ messages in thread
From: Takashi Iwai @ 2026-10-26  7:59 UTC (permalink / raw)
  To: root; +Cc: linux-sound, perex, tiwai

On Sat, 24 Oct 2026 13:27:00 +0200,
root wrote:
> 
> 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>

The Subject prefix should be "ALSA:" instead of "ASoC:" :)
About the code changes: mostly it looks OK, just some nitpicking.

> +/*
> + * 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;
> +}

This can be replaced with snd_ctl_boolean_mono_info; i.e. pass
snd_ctl_boolean_mono_info to ".info = ..." below.


> @@ -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;

The wrong indent level?

> 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;

It's suboptimal to do it.
The problem is likely because your model doesn't override the EEPROM
content before creating the PCM streams where the consumer PCM streams
are skipped when AC97 is not present via ice_has_con_ac7() check.

That is...

>  	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;
> +	}

... we should add eeprom_init() to snd_ice1712_card_info() and move
the model-specific code to each file, instead, then EEPROM setup could
be done initially at snd_ice1712_create().  This move should be done
as a preliminary patch, though.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 0/2] ALSA: ice1712: support the TerraTec PHASE 88
  2026-10-26  7:59 ` Takashi Iwai
@ 2026-10-28 18:24   ` 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
  0 siblings, 2 replies; 5+ messages in thread
From: Davide Gironi @ 2026-10-28 18:24 UTC (permalink / raw)
  To: linux-sound; +Cc: perex, tiwai

The TerraTec PHASE 88 uses different front-module hardware from the
EWS88MT even though the ICE1712 driver identifies it as an EWS88MT variant.
The PHASE 88's write-only expander does not respond at the EWS88MT
addresses, and its EEPROM advertises a consumer AC'97 codec and one MIDI
UART that are not representative of the board.

Changes since v1, following Takashi Iwai's review:

  - Add an early, card-specific EEPROM initialization callback and move the
    existing DMX6Fire fixup out of generic chip initialization.
  - Move the PHASE 88 AC'97 and second-MIDI UART EEPROM fixups into the EWS
    driver callback, which runs before generic hardware setup.
  - Drop the consumer-capture NULL guard: the early EEPROM fixup prevents the
    nonexistent consumer PCM from being created in the first place.
  - Use snd_ctl_boolean_mono_info for the Line/Mic boolean control and fix
    the PHASE 88 initialization indentation.
  - Give TS88 its own model string, and update the PHASE 88 expander shadow
    only after a successful write so an I2C error cannot corrupt it.
  - Updated comments style to fit better the other on the module.

The series has two patches: the callback/refactoring patch first, then the
PHASE 88-specific changes.

Testing on the machine with two PHASE 88 racks.

The patch series applies
to the Linux 6.18.58 source. It was built and hardware-tested against the
running Debian 6.12.113 kernel.

Thanks,
Davide Gironi


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 1/2] ALSA: ice1712: add a board-specific EEPROM initialization hook
  2026-10-28 18:24   ` [PATCH v2 0/2] ALSA: ice1712: support the TerraTec PHASE 88 Davide Gironi
@ 2026-10-28 18:24     ` Davide Gironi
  2026-10-28 18:24     ` [PATCH v2 2/2] ALSA: ice1712: add TerraTec PHASE 88 support Davide Gironi
  1 sibling, 0 replies; 5+ messages in thread
From: Davide Gironi @ 2026-10-28 18:24 UTC (permalink / raw)
  To: linux-sound; +Cc: perex, tiwai, Davide Gironi

Add an eeprom_init callback to card info and call it after reading the
EEPROM but before generic chip setup. Move the existing DMX6Fire fixup
into the EWS driver.

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

diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c
index 1dffcb0..62574c3 100644
--- a/sound/pci/ice1712/ews.c
+++ b/sound/pci/ice1712/ews.c
@@ -1012,6 +1012,19 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice)
 }
 
 
+static int snd_ice1712_ews_eeprom_init(struct snd_ice1712 *ice)
+{
+	if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE &&
+	    !ice->dxr_enable) {
+		/* Set EEPROM value to limit active ADCs and DACs to 6;
+		 * also disable AC'97 as no hardware in standard 6fire card/box.
+		 * DXR extensions are not currently supported.
+		 */
+		ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a;
+	}
+	return 0;
+}
+
 /* entry point */
 struct snd_ice1712_card_info snd_ice1712_ews_cards[] = {
 	{
@@ -1060,6 +1073,7 @@ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = {
 		.subvendor = ICE1712_SUBDEVICE_DMX6FIRE,
 		.name = "TerraTec DMX6Fire",
 		.model = "dmx6fire",
+		.eeprom_init = snd_ice1712_ews_eeprom_init,
 		.chip_init = snd_ice1712_ews_init,
 		.build_controls = snd_ice1712_ews_add_controls,
 		.mpu401_1_name = "MIDI-Front DMX6fire",
diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c
index 376df1a..6cc38ee 100644
--- a/sound/pci/ice1712/ice1712.c
+++ b/sound/pci/ice1712/ice1712.c
@@ -2283,19 +2283,28 @@ static int snd_ice1712_read_eeprom(struct snd_ice1712 *ice,
 
 
 
+static int snd_ice1712_init_eeprom(struct snd_ice1712 *ice)
+{
+	const struct snd_ice1712_card_info * const *tbl, *c;
+
+	for (tbl = card_tables; *tbl; tbl++) {
+		for (c = *tbl; c->subvendor; c++) {
+			if (c->subvendor != ice->eeprom.subvendor)
+				continue;
+			if (c->eeprom_init)
+				return c->eeprom_init(ice);
+			return 0;
+		}
+	}
+	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]);
@@ -2502,8 +2511,12 @@ static int snd_ice1712_create(struct snd_card *card,
 	card->sync_irq = ice->irq;
 	card->private_free = snd_ice1712_free;
 
-	if (snd_ice1712_read_eeprom(ice, modelname) < 0)
-		return -EIO;
+	err = snd_ice1712_read_eeprom(ice, modelname);
+	if (err < 0)
+		return err;
+	err = snd_ice1712_init_eeprom(ice);
+	if (err < 0)
+		return err;
 	if (snd_ice1712_chip_init(ice) < 0)
 		return -EIO;
 
diff --git a/sound/pci/ice1712/ice1712.h b/sound/pci/ice1712/ice1712.h
index cd02710..6e9c13b 100644
--- a/sound/pci/ice1712/ice1712.h
+++ b/sound/pci/ice1712/ice1712.h
@@ -509,6 +509,7 @@ struct snd_ice1712_card_info {
 	const char *name;
 	const char *model;
 	const char *driver;
+	int (*eeprom_init)(struct snd_ice1712 *ice);
 	int (*chip_init)(struct snd_ice1712 *);
 	void (*chip_exit)(struct snd_ice1712 *);
 	int (*build_controls)(struct snd_ice1712 *);

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 2/2] ALSA: ice1712: add TerraTec PHASE 88 support
  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     ` Davide Gironi
  1 sibling, 0 replies; 5+ messages in thread
From: Davide Gironi @ 2026-10-28 18:24 UTC (permalink / raw)
  To: linux-sound; +Cc: perex, tiwai, Davide Gironi

Handle PHASE 88-specific expander, EEPROM, control, and MIDI behavior
in the EWS driver. Use a distinct TS88 model name and preserve the
expander state when a write fails.

Assisted-by: Pi:gpt-6-luna
Signed-off-by: Davide Gironi <davide.gironi@gmail.com>
---
 sound/pci/ice1712/ews.c | 141 +++++++++++++++++++++++++++++++++++++---
 sound/pci/ice1712/ews.h |   7 ++
 2 files changed, 138 insertions(+), 10 deletions(-)

diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c
index 62574c3..5a44bfa 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,94 @@ static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = {
  * AK4524 access
  */
 
+/* The PHASE 88 chip-select expander is write-only; keep a shadow value. */
+/* The bit-banged bus takes gpio_mutex internally; don't hold it here. */
+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;
+	unsigned char data;
+	int err;
+
+	if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f))
+		return -EINVAL;
+	data = (spec->pcf8575_data & 0xf0) | chip_mask;
+	err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_OUTPUT_PAGE,
+					data);
+	if (err >= 0)
+		spec->pcf8575_data = data;
+	return err;
+}
+
+/* PHASE 88 Line/Mic bits are ordered 7/8 to 1/2 (0x10 to 0x80). */
+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_ctl_boolean_mono_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 +542,31 @@ 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;
+		/* Clear the input register before codec setup. */
+		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 +1088,15 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice)
 				return err;
 		}
 		break;
+	case ICE1712_SUBDEVICE_PHASE88:
+		/* The PHASE 88 has no EWS88MT sensitivity controls. */
+		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)
@@ -1014,13 +1126,21 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice)
 
 static int snd_ice1712_ews_eeprom_init(struct snd_ice1712 *ice)
 {
-	if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE &&
-	    !ice->dxr_enable) {
-		/* Set EEPROM value to limit active ADCs and DACs to 6;
-		 * also disable AC'97 as no hardware in standard 6fire card/box.
-		 * DXR extensions are not currently supported.
-		 */
-		ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a;
+	switch (ice->eeprom.subvendor) {
+	case ICE1712_SUBDEVICE_DMX6FIRE:
+		if (!ice->dxr_enable) {
+			/* Limit active ADCs and DACs to 6, and disable AC'97
+			 * as the standard 6fire card/box has no hardware for it.
+			 */
+			ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a;
+		}
+		break;
+	case ICE1712_SUBDEVICE_PHASE88:
+		/* No consumer AC'97 codec on PHASE 88. */
+		ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_NO_CON_AC97;
+		/* Enable the rear MIDI UART. */
+		ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_2xMPU401;
+		break;
 	}
 	return 0;
 }
@@ -1052,13 +1172,14 @@ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = {
 		.subvendor = ICE1712_SUBDEVICE_PHASE88,
 		.name = "TerraTec Phase88",
 		.model = "phase88",
+		.eeprom_init = snd_ice1712_ews_eeprom_init,
 		.chip_init = snd_ice1712_ews_init,
 		.build_controls = snd_ice1712_ews_add_controls,
 	},
 	{
 		.subvendor = ICE1712_SUBDEVICE_TS88,
 		.name = "terrasoniq TS88",
-		.model = "phase88",
+		.model = "ts88",
 		.chip_init = snd_ice1712_ews_init,
 		.build_controls = snd_ice1712_ews_add_controls,
 	},
diff --git a/sound/pci/ice1712/ews.h b/sound/pci/ice1712/ews.h
index aec8dc6..064675a 100644
--- a/sound/pci/ice1712/ews.h
+++ b/sound/pci/ice1712/ews.h
@@ -58,6 +58,13 @@ 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 */
+#define ICE1712_PHASE88_OUTPUT_ADDR	(0x42>>1)
+#define ICE1712_PHASE88_OUTPUT_PAGE	0x01	/* output register */
+#define ICE1712_PHASE88_OUTPUT_INIT	0x20	/* initial output state */
+#define ICE1712_PHASE88_INPUT_PAGE	0x03	/* input register */
+#define ICE1712_PHASE88_INPUT_INIT	0x00	/* line inputs */
+
 /* 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  */

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-10-28 18:33 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-24 11:27 [PATCH] ASoC: ice1712: add TerraTec PHASE 88 support root
2026-10-26  7:59 ` 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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.