All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards
@ 2023-06-13  7:38 Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 1/8] ALSA: emu10k1: introduce alternative E-MU D.A.S. mode Oswald Buddenhagen
                   ` (7 more replies)
  0 siblings, 8 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

This series is what all the work was about: support the "dual-/quad-pumped"
modes of the E-MU cards.

Oswald Buddenhagen (8):
  ALSA: emu10k1: introduce alternative E-MU D.A.S. mode
  ALSA: emu10k1: improve mixer control naming in E-MU D.A.S. mode
  ALSA: emu10k1: set the "no filtering" bits on PCM voices
  ALSA: emu10k1: make playback in E-MU D.A.S. mode 32-bit
  ALSA: add snd_ctl_add_locked()
  ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  ALSA: emu10k1: add high-rate capture in E-MU D.A.S. mode
  ALSA: emu10k1: add high-rate playback in E-MU D.A.S. mode

 include/sound/control.h          |   1 +
 include/sound/emu10k1.h          |  11 +
 sound/core/control.c             |  31 ++
 sound/pci/emu10k1/emu10k1.c      |  29 +-
 sound/pci/emu10k1/emu10k1_main.c |  19 +-
 sound/pci/emu10k1/emufx.c        | 109 +++-
 sound/pci/emu10k1/emumixer.c     | 901 +++++++++++++++++++++++++++----
 sound/pci/emu10k1/emupcm.c       | 422 +++++++++++++--
 sound/pci/emu10k1/emuproc.c      |   5 +
 sound/pci/emu10k1/io.c           |  30 +-
 sound/pci/emu10k1/voice.c        |   6 +
 11 files changed, 1383 insertions(+), 181 deletions(-)

-- 
2.40.0.152.g15d061e6df


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

* [PATCH 1/8] ALSA: emu10k1: introduce alternative E-MU D.A.S. mode
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 2/8] ALSA: emu10k1: improve mixer control naming in " Oswald Buddenhagen
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

As noted in a previous commit, the E-MU Digital Audio System cards
don't try very hard to be Sound Blasters. This commit takes it further
and introduces a module option to switch to a completely separate mode.

In that mode:
- The regular PCM playback/capture devices are removed
- The EFX playback/capture devices get index 0
- Consequently, the regular mixer controls are also completely removed.
  This is no real loss, given the expected use with a sound server.
- The voice send routing+amount & attenuation controls are also removed,
  as they are just another mixer. The routing is redundant with the FPGA
  channel routing, and amounts/att'n can be done in software. The latter
  are also incompatible with 32-bit playback, which we'll add support
  for later.
- A_EXTOUT is now also free for multi-channel capture, so we use that
  instead of A_FXBUS2 - this will later simplify using both at once.
- For the same reason, the FX outputs are not listed in /proc anymore
- The device name is changed, so mixer settings don't get mixed up

This continues the pre-existing design with a single multi-channel
device where the channels are routed by manual mixer controls from/to
the physical ports. De-/multiplexing must be done by the sound server
if independent streams are to be used.

An alternative design would be exposing each physical port as a separate
device and automatically setting up the routes. This would be a somewhat
radical departure from the status quo, and I don't know whether it would
be a net benefit. It certainly would be harder to implement, as it would
require sync start of streams and a channel allocator (the latter would
have the added benefit of properly reporting EMU32 & EDI bus over-
allocation, which the mixer does not). There is only one big multi-
channel capture engine, so streams would have to be de-multiplexed by
the driver in software - which seems a bit counter-productive if the
sound server would re-multiplex them again.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 include/sound/emu10k1.h          |   4 ++
 sound/pci/emu10k1/emu10k1.c      |  29 ++++++---
 sound/pci/emu10k1/emu10k1_main.c |  14 +++-
 sound/pci/emu10k1/emufx.c        |  94 ++++++++++++++++++++++++++-
 sound/pci/emu10k1/emumixer.c     |  93 ++++++++++++++-------------
 sound/pci/emu10k1/emupcm.c       | 107 +++++++++++++++++++++++++++----
 sound/pci/emu10k1/emuproc.c      |   5 ++
 7 files changed, 280 insertions(+), 66 deletions(-)

diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h
index 386a5f3be3e0..cad5faa01c4c 100644
--- a/include/sound/emu10k1.h
+++ b/include/sound/emu10k1.h
@@ -1700,6 +1700,7 @@ struct snd_emu10k1 {
 	unsigned int address_mode;		/* address mode */
 	unsigned long dma_mask;			/* PCI DMA mask */
 	bool iommu_workaround;			/* IOMMU workaround needed */
+	bool das_mode;				/* Alternative E-MU Digital Audio System mode */
 	int max_cache_pages;			/* max memory size / PAGE_SIZE */
 	struct snd_dma_buffer silent_page;	/* silent page */
 	struct snd_dma_buffer ptb_pages;	/* page table pages */
@@ -1729,6 +1730,7 @@ struct snd_emu10k1 {
 	struct snd_pcm *pcm_mic;
 	struct snd_pcm *pcm_efx;
 	struct snd_pcm *pcm_multi;
+	struct snd_pcm *pcm_das;
 	struct snd_pcm *pcm_p16v;
 
 	spinlock_t synth_lock;
@@ -1793,17 +1795,19 @@ struct snd_emu10k1 {
 
 int snd_emu10k1_create(struct snd_card *card,
 		       struct pci_dev *pci,
+		       bool emu_das,
 		       unsigned short extin_mask,
 		       unsigned short extout_mask,
 		       long max_cache_bytes,
 		       int enable_ir,
 		       uint subsystem);
 
 int snd_emu10k1_pcm(struct snd_emu10k1 *emu, int device);
 int snd_emu10k1_pcm_mic(struct snd_emu10k1 *emu, int device);
 int snd_emu10k1_pcm_efx(struct snd_emu10k1 *emu, int device);
 int snd_p16v_pcm(struct snd_emu10k1 *emu, int device);
 int snd_p16v_mixer(struct snd_emu10k1 * emu);
+int snd_emu10k1_pcm_das(struct snd_emu10k1 *emu, int device);
 int snd_emu10k1_pcm_multi(struct snd_emu10k1 *emu, int device);
 int snd_emu10k1_fx8010_pcm(struct snd_emu10k1 *emu, int device);
 int snd_emu10k1_mixer(struct snd_emu10k1 * emu, int pcm_device, int multi_device);
diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c
index 23adace1b969..36beb0254cca 100644
--- a/sound/pci/emu10k1/emu10k1.c
+++ b/sound/pci/emu10k1/emu10k1.c
@@ -27,6 +27,7 @@ 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 bool emu_das[SNDRV_CARDS];
 static int extin[SNDRV_CARDS];
 static int extout[SNDRV_CARDS];
 static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
@@ -41,6 +42,8 @@ module_param_array(id, charp, NULL, 0444);
 MODULE_PARM_DESC(id, "ID string for the EMU10K1 soundcard.");
 module_param_array(enable, bool, NULL, 0444);
 MODULE_PARM_DESC(enable, "Enable the EMU10K1 soundcard.");
+module_param_array(emu_das, bool, NULL, 0444);
+MODULE_PARM_DESC(emu_das, "Use alternative E-MU Digital Audio System mode.");
 module_param_array(extin, int, NULL, 0444);
 MODULE_PARM_DESC(extin, "Available external inputs for FX8010. Zero=default.");
 module_param_array(extout, int, NULL, 0444);
@@ -95,22 +98,27 @@ static int snd_card_emu10k1_probe(struct pci_dev *pci,
 		max_buffer_size[dev] = 32;
 	else if (max_buffer_size[dev] > 1024)
 		max_buffer_size[dev] = 1024;
-	err = snd_emu10k1_create(card, pci, extin[dev], extout[dev],
+	err = snd_emu10k1_create(card, pci, emu_das[dev], extin[dev], extout[dev],
 				 (long)max_buffer_size[dev] * 1024 * 1024,
 				 enable_ir[dev], subsystem[dev]);
 	if (err < 0)
 		return err;
-	err = snd_emu10k1_pcm(emu, 0);
+	if (emu->das_mode)
+		err = snd_emu10k1_pcm_das(emu, 0);
+	else
+		err = snd_emu10k1_pcm(emu, 0);
 	if (err < 0)
 		return err;
 	if (emu->card_capabilities->ac97_chip) {
 		err = snd_emu10k1_pcm_mic(emu, 1);
 		if (err < 0)
 			return err;
 	}
-	err = snd_emu10k1_pcm_efx(emu, 2);
-	if (err < 0)
-		return err;
+	if (!emu->das_mode) {
+		err = snd_emu10k1_pcm_efx(emu, 2);
+		if (err < 0)
+			return err;
+	}
 	/* This stores the periods table. */
 	if (emu->card_capabilities->ca0151_chip) { /* P16V */	
 		emu->p16v_buffer =
@@ -127,9 +135,11 @@ static int snd_card_emu10k1_probe(struct pci_dev *pci,
 	if (err < 0)
 		return err;
 
-	err = snd_emu10k1_pcm_multi(emu, 3);
-	if (err < 0)
-		return err;
+	if (!emu->das_mode) {
+		err = snd_emu10k1_pcm_multi(emu, 3);
+		if (err < 0)
+			return err;
+	}
 	if (emu->card_capabilities->ca0151_chip) { /* P16V */
 		err = snd_p16v_pcm(emu, 4);
 		if (err < 0)
@@ -148,7 +158,8 @@ static int snd_card_emu10k1_probe(struct pci_dev *pci,
 	if (err < 0)
 		return err;
 #ifdef ENABLE_SYNTH
-	if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH,
+	if (emu->das_mode) {
+	} else if (snd_seq_device_new(card, 1, SNDRV_SEQ_DEV_ID_EMU10K1_SYNTH,
 			       sizeof(struct snd_emu10k1_synth_arg), &wave) < 0 ||
 	    wave == NULL) {
 		dev_warn(emu->card->dev,
diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
index 58ed72de6403..aa28a7524a67 100644
--- a/sound/pci/emu10k1/emu10k1_main.c
+++ b/sound/pci/emu10k1/emu10k1_main.c
@@ -1482,6 +1482,7 @@ static void snd_emu10k1_detect_iommu(struct snd_emu10k1 *emu)
 
 int snd_emu10k1_create(struct snd_card *card,
 		       struct pci_dev *pci,
+		       bool emu_das,
 		       unsigned short extin_mask,
 		       unsigned short extout_mask,
 		       long max_cache_bytes,
@@ -1560,8 +1561,19 @@ int snd_emu10k1_create(struct snd_card *card,
 			c->name, pci->vendor, pci->device,
 			emu->serial);
 
-	if (!*card->id && c->id)
+	if (c->emu_model) {
+		if (emu_das)
+			emu->das_mode = 1;
+		else
+			dev_notice(card->dev,
+				"You may want to use emu_das=1 with your %s\n", c->name);
+	}
+
+	if (!*card->id && c->id) {
 		strscpy(card->id, c->id, sizeof(card->id));
+		if (emu->das_mode)
+			strlcat(card->id, "das", sizeof(card->id));
+	}
 
 	is_audigy = emu->audigy = c->emu10k2_chip;
 
diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c
index 9904bcfee106..428ae365eb99 100644
--- a/sound/pci/emu10k1/emufx.c
+++ b/sound/pci/emu10k1/emufx.c
@@ -1282,6 +1282,96 @@ static void snd_emu10k1_audigy_dsp_convert_32_to_2x16(
 
 #define ENUM_GPR(name, size) name, name ## _dummy = name + (size) - 1
 
+/*
+ * initial DSP configuration for E-MU Digital Audio System
+ */
+
+static int _snd_emu10k1_das_init_efx(struct snd_emu10k1 *emu)
+{
+	enum {
+		ENUM_GPR(bit_shifter16, 1),
+		ENUM_GPR(tmp, 1),
+		num_static_gprs
+	};
+	int gpr = num_static_gprs;
+	u32 *gpr_map;
+	u32 ptr = 0;
+
+	int err = -ENOMEM;
+	struct snd_emu10k1_fx8010_code *icode = kzalloc(sizeof(*icode), GFP_KERNEL);
+	if (!icode)
+		return err;
+
+	icode->gpr_map = kcalloc(512 + 256 + 256 + 2 * 1024,
+				 sizeof(u_int32_t), GFP_KERNEL);
+	if (!icode->gpr_map)
+		goto __err_gpr;
+
+	icode->tram_data_map = icode->gpr_map + 512;
+	icode->tram_addr_map = icode->tram_data_map + 256;
+	icode->code = icode->tram_addr_map + 256;
+
+	/* clear free GPRs */
+	memset(icode->gpr_valid, 0xff, sizeof(icode->gpr_valid));
+
+	/* clear TRAM data & address lines */
+	memset(icode->tram_valid, 0xff, sizeof(icode->tram_valid));
+
+	strcpy(icode->name, "E-MU DSP code for ALSA");
+
+	gpr_map = icode->gpr_map;
+	gpr_map[bit_shifter16] = 0x00008000;
+
+	if (emu->card_capabilities->ca0108_chip) {
+		for (int z = 0; z < 16; z++)
+			A_OP(icode, &ptr, iMACINT0, A3_EMU32OUT(z), A_C_00000000, A_FXBUS(z), A_C_00000002);
+
+		snd_emu10k1_audigy_dsp_convert_32_to_2x16(
+			icode, &ptr, tmp, bit_shifter16, A3_EMU32IN(0), A_EXTOUT(0));
+		// A3_EMU32IN(0) is delayed by one sample, so all other A3_EMU32IN channels
+		// need to be delayed as well; we use an auxiliary register for that.
+		for (int z = 1; z < 16; z++) {
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(
+				icode, &ptr, tmp, bit_shifter16, A_GPR(gpr), A_EXTOUT(z * 2));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr), A3_EMU32IN(z), A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+		}
+	} else {
+		for (int z = 0; z < 16; z++)
+			A_OP(icode, &ptr, iMACINT0, A_EMU32OUTL(z), A_C_00000000, A_FXBUS(z), A_C_00000002);
+
+		/* Note that the Alice2 DSPs have 6 I2S inputs which we don't use. */
+		snd_emu10k1_audigy_dsp_convert_32_to_2x16(
+			icode, &ptr, tmp, bit_shifter16, A_P16VIN(0), A_EXTOUT(0));
+		// A_P16VIN(0) is delayed by one sample, so all other A_P16VIN channels
+		// need to be delayed as well; we use an auxiliary register for that.
+		for (int z = 1; z < 16; z++) {
+			snd_emu10k1_audigy_dsp_convert_32_to_2x16(
+				icode, &ptr, tmp, bit_shifter16, A_GPR(gpr), A_EXTOUT(z * 2));
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr), A_P16VIN(z), A_C_00000000, A_C_00000000);
+			gpr_map[gpr++] = 0x00000000;
+		}
+	}
+
+	if (gpr > 512) {
+		snd_BUG();
+		err = -EIO;
+		goto __err;
+	}
+
+	/* clear remaining instruction memory */
+	while (ptr < 0x400)
+		A_OP(icode, &ptr, 0x0f, 0xc0, 0xc0, 0xcf, 0xc0);
+
+	err = snd_emu10k1_icode_poke(emu, icode, true);
+
+__err:
+	kfree(icode->gpr_map);
+__err_gpr:
+	kfree(icode);
+	return err;
+}
+
 /*
  * initial DSP configuration for Audigy
  */
@@ -2376,7 +2466,9 @@ int snd_emu10k1_init_efx(struct snd_emu10k1 *emu)
 {
 	spin_lock_init(&emu->fx8010.irq_lock);
 	INIT_LIST_HEAD(&emu->fx8010.gpr_ctl);
-	if (emu->audigy)
+	if (emu->das_mode)
+		return _snd_emu10k1_das_init_efx(emu);
+	else if (emu->audigy)
 		return _snd_emu10k1_audigy_init_efx(emu);
 	else
 		return _snd_emu10k1_init_efx(emu);
diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c
index f9500cd50a4b..a9358d9c08b9 100644
--- a/sound/pci/emu10k1/emumixer.c
+++ b/sound/pci/emu10k1/emumixer.c
@@ -2230,51 +2230,56 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu,
 		rename_ctl(card, "Aux2 Capture Volume", "Line3 Capture Volume");
 		rename_ctl(card, "Mic Capture Volume", "Unknown1 Capture Volume");
 	}
-	kctl = emu->ctl_send_routing = snd_ctl_new1(&snd_emu10k1_send_routing_control, emu);
-	if (!kctl)
-		return -ENOMEM;
-	kctl->id.device = pcm_device;
-	err = snd_ctl_add(card, kctl);
-	if (err)
-		return err;
-	kctl = emu->ctl_send_volume = snd_ctl_new1(&snd_emu10k1_send_volume_control, emu);
-	if (!kctl)
-		return -ENOMEM;
-	kctl->id.device = pcm_device;
-	err = snd_ctl_add(card, kctl);
-	if (err)
-		return err;
-	kctl = emu->ctl_attn = snd_ctl_new1(&snd_emu10k1_attn_control, emu);
-	if (!kctl)
-		return -ENOMEM;
-	kctl->id.device = pcm_device;
-	err = snd_ctl_add(card, kctl);
-	if (err)
-		return err;
 
-	kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu);
-	if (!kctl)
-		return -ENOMEM;
-	kctl->id.device = multi_device;
-	err = snd_ctl_add(card, kctl);
-	if (err)
-		return err;
-	
-	kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu);
-	if (!kctl)
-		return -ENOMEM;
-	kctl->id.device = multi_device;
-	err = snd_ctl_add(card, kctl);
-	if (err)
-		return err;
-	
-	kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu);
-	if (!kctl)
-		return -ENOMEM;
-	kctl->id.device = multi_device;
-	err = snd_ctl_add(card, kctl);
-	if (err)
-		return err;
+	if (!emu->das_mode) {
+		kctl = emu->ctl_send_routing = snd_ctl_new1(&snd_emu10k1_send_routing_control, emu);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.device = pcm_device;
+		err = snd_ctl_add(card, kctl);
+		if (err)
+			return err;
+
+		kctl = emu->ctl_send_volume = snd_ctl_new1(&snd_emu10k1_send_volume_control, emu);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.device = pcm_device;
+		err = snd_ctl_add(card, kctl);
+		if (err)
+			return err;
+
+		kctl = emu->ctl_attn = snd_ctl_new1(&snd_emu10k1_attn_control, emu);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.device = pcm_device;
+		err = snd_ctl_add(card, kctl);
+		if (err)
+			return err;
+
+		kctl = emu->ctl_efx_send_routing = snd_ctl_new1(&snd_emu10k1_efx_send_routing_control, emu);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.device = multi_device;
+		err = snd_ctl_add(card, kctl);
+		if (err)
+			return err;
+
+		kctl = emu->ctl_efx_send_volume = snd_ctl_new1(&snd_emu10k1_efx_send_volume_control, emu);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.device = multi_device;
+		err = snd_ctl_add(card, kctl);
+		if (err)
+			return err;
+
+		kctl = emu->ctl_efx_attn = snd_ctl_new1(&snd_emu10k1_efx_attn_control, emu);
+		if (!kctl)
+			return -ENOMEM;
+		kctl->id.device = multi_device;
+		err = snd_ctl_add(card, kctl);
+		if (err)
+			return err;
+	}
 
 	if (!emu->card_capabilities->ecard && !emu->card_capabilities->emu_model) {
 		/* sb live! and audigy */
diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
index 387288d623d7..a6fb0647d80a 100644
--- a/sound/pci/emu10k1/emupcm.c
+++ b/sound/pci/emu10k1/emupcm.c
@@ -358,6 +358,22 @@ static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu,
 	spin_unlock_irqrestore(&emu->reg_lock, flags);
 }
 
+static void snd_emu10k1_pcm_init_das_voices(struct snd_emu10k1 *emu,
+					    struct snd_emu10k1_voice *evoice,
+					    unsigned int start_addr,
+					    unsigned int end_addr,
+					    unsigned char channel)
+{
+	static const unsigned char send_amount[8] = { 255, 0, 0, 0, 0, 0, 0, 0 };
+	unsigned char send_routing[8];
+
+	for (int i = 0; i < ARRAY_SIZE(send_routing); i++)
+		send_routing[i] = (channel + i) % NUM_G;
+	snd_emu10k1_pcm_init_voice(emu, evoice, true, false,
+				   start_addr, end_addr,
+				   send_routing, send_amount);
+}
+
 static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu,
 					     struct snd_emu10k1_voice *evoice,
 					     bool w_16,
@@ -477,6 +493,7 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream)
 	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	bool das_mode = emu->das_mode;
 	unsigned int start_addr;
 	unsigned int extra_size, channel_size;
 	unsigned int i;
@@ -492,11 +509,20 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream)
 					 start_addr, start_addr + extra_size);
 
 	epcm->ccca_start_addr = start_addr;
-	for (i = 0; i < runtime->channels; i++) {
-		snd_emu10k1_pcm_init_voices(emu, epcm->voices[i], true, false,
-					    start_addr, start_addr + channel_size,
-					    &emu->efx_pcm_mixer[i]);
-		start_addr += channel_size;
+	if (das_mode) {
+		for (i = 0; i < runtime->channels; i++) {
+			snd_emu10k1_pcm_init_das_voices(emu, epcm->voices[i],
+							start_addr, start_addr + channel_size,
+							i);
+			start_addr += channel_size;
+		}
+	} else {
+		for (i = 0; i < runtime->channels; i++) {
+			snd_emu10k1_pcm_init_voices(emu, epcm->voices[i], true, false,
+						    start_addr, start_addr + channel_size,
+						    &emu->efx_pcm_mixer[i]);
+			start_addr += channel_size;
+		}
 	}
 
 	return 0;
@@ -536,10 +562,16 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream)
 		break;
 	case CAPTURE_EFX:
 		if (emu->card_capabilities->emu_model) {
-			// The upper 32 16-bit capture voices, two for each of the 16 32-bit channels.
-			// The lower voices are occupied by A_EXTOUT_*_CAP*.
-			epcm->capture_cr_val = 0;
-			epcm->capture_cr_val2 = 0xffffffff >> (32 - runtime->channels * 2);
+			unsigned mask = 0xffffffff >> (32 - runtime->channels * 2);
+			if (emu->das_mode) {
+				epcm->capture_cr_val = mask;
+				epcm->capture_cr_val2 = 0;
+			} else {
+				// The upper 32 16-bit capture voices, two for each of the 16 32-bit channels.
+				// The lower voices are occupied by A_EXTOUT_*_CAP*.
+				epcm->capture_cr_val = 0;
+				epcm->capture_cr_val2 = mask;
+			}
 		}
 		if (emu->audigy) {
 			snd_emu10k1_ptr_write_multiple(emu, 0,
@@ -685,6 +717,12 @@ static void snd_emu10k1_playback_unmute_voices(struct snd_emu10k1 *emu,
 		snd_emu10k1_playback_unmute_voice(emu, evoice + 1, true, false, mix);
 }
 
+static void snd_emu10k1_playback_unmute_das_voices(struct snd_emu10k1 *emu,
+						   struct snd_emu10k1_voice *evoice)
+{
+	snd_emu10k1_playback_commit_volume(emu, evoice, 0x8000 << 16);
+}
+
 static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu,
 					    struct snd_emu10k1_voice *evoice)
 {
@@ -928,6 +966,14 @@ static void snd_emu10k1_efx_playback_unmute_voices(struct snd_emu10k1 *emu,
 						  &emu->efx_pcm_mixer[i]);
 }
 
+static void snd_emu10k1_efx_playback_unmute_das_voices(struct snd_emu10k1 *emu,
+						       struct snd_emu10k1_pcm *epcm,
+						       int channels)
+{
+	for (int i = 0; i < channels; i++)
+		snd_emu10k1_playback_unmute_das_voices(emu, epcm->voices[i]);
+}
+
 static void snd_emu10k1_efx_playback_stop_voices(struct snd_emu10k1 *emu,
 						 struct snd_emu10k1_pcm *epcm,
 						 int channels)
@@ -946,6 +992,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	bool das_mode = emu->das_mode;
 	u64 mask;
 	int result = 0;
 
@@ -969,7 +1016,12 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 			// they have been started, to potentially avoid torturing the speakers
 			// if something goes wrong. However, we cannot unmute atomically,
 			// which means that we'd get some mild artifacts in the regular case.
-			snd_emu10k1_efx_playback_unmute_voices(emu, epcm, runtime->channels);
+			if (das_mode)
+				snd_emu10k1_efx_playback_unmute_das_voices(
+						emu, epcm, runtime->channels);
+			else
+				snd_emu10k1_efx_playback_unmute_voices(
+						emu, epcm, runtime->channels);
 
 			snd_emu10k1_playback_set_running(emu, epcm);
 			result = snd_emu10k1_voice_clear_loop_stop_multiple_atomic(emu, mask);
@@ -1135,6 +1187,8 @@ static int snd_emu10k1_efx_playback_close(struct snd_pcm_substream *substream)
 	struct snd_emu10k1_pcm_mixer *mix;
 	int i;
 
+	if (emu->das_mode)
+		return 0;
 	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
 		mix = &emu->efx_pcm_mixer[i];
 		mix->epcm = NULL;
@@ -1185,6 +1239,8 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream)
 		return err;
 	}
 
+	if (emu->das_mode)
+		return 0;
 	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
 		mix = &emu->efx_pcm_mixer[i];
 		for (j = 0; j < 8; j++)
@@ -1869,12 +1925,41 @@ int snd_emu10k1_pcm_efx(struct snd_emu10k1 *emu, int device)
 			return err;
 	} else {
 		// On E-MU cards, the DSP code copies the P16VINs/EMU32INs to
-		// FXBUS2. These are already selected & routed by the FPGA,
+		// EXTOUT/FXBUS2. These are already selected & routed by the FPGA,
 		// so there is no need to apply additional masking.
 	}
 
 	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &emu->pci->dev,
 				       64*1024, 64*1024);
 
 	return 0;
 }
+
+int snd_emu10k1_pcm_das(struct snd_emu10k1 *emu, int device)
+{
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+
+	int err = snd_pcm_new(emu->card, "emu10k1 efx", device, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = emu;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_emu10k1_efx_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_emu10k1_capture_efx_ops);
+
+	strcpy(pcm->name, "Multichannel Playback/Capture");
+	emu->pcm_das = pcm;
+
+	// Playback substream can't use managed buffers due to IOMMU workaround
+	substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+	snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV_SG,
+				      &emu->pci->dev, 64*1024, 64*1024);
+
+	substream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
+	snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV,
+				   &emu->pci->dev, 64*1024, 64*1024);
+
+	return 0;
+}
diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c
index 7e2cc532471f..9415753b3559 100644
--- a/sound/pci/emu10k1/emuproc.c
+++ b/sound/pci/emu10k1/emuproc.c
@@ -85,6 +85,11 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry,
 	snd_iprintf(buffer, "Internal TRAM (words) : 0x%x\n", emu->fx8010.itram_size);
 	snd_iprintf(buffer, "External TRAM (words) : 0x%x\n", (int)emu->fx8010.etram_pages.bytes / 2);
 
+	// The following are internal details in D.A.S. mode,
+	// so there is no use in displaying them to the user.
+	if (emu->das_mode)
+		return;
+
 	snd_iprintf(buffer, "\nEffect Send Routing & Amounts:\n");
 	for (idx = 0; idx < NUM_G; idx++) {
 		ptrx = snd_emu10k1_ptr_read(emu, PTRX, idx);
-- 
2.40.0.152.g15d061e6df


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

* [PATCH 2/8] ALSA: emu10k1: improve mixer control naming in E-MU D.A.S. mode
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 1/8] ALSA: emu10k1: introduce alternative E-MU D.A.S. mode Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 3/8] ALSA: emu10k1: set the "no filtering" bits on PCM voices Oswald Buddenhagen
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

Use the clearer "PbChn <n>" instead of "DSP <n>" for the source names.
In particular, this is much less confusing in the capture source
selection - "DSP 0" having the source "DSP 0" really didn't help.
I didn't use "Playback Channel <n>", because that would be a tad too
long to be sensibly displayed in alsamixer.

The capture enums also get a "DSP" => "CpChn" replacement.

I used zero-padded decimals, so the capture elements are properly sorted
in alsamixer. I found hex too confusing.

Note that unlike in the legacy mixer, we define enum values only for
actually "wired" channels.

I'm leaving the legacy mixer alone, as I don't want to completely
invalidate saved mixer states. This introduces some bloat, but it seems
bearable.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 sound/pci/emu10k1/emumixer.c | 108 ++++++++++++++++++++++++++---------
 1 file changed, 81 insertions(+), 27 deletions(-)

diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c
index a9358d9c08b9..8878b660ba94 100644
--- a/sound/pci/emu10k1/emumixer.c
+++ b/sound/pci/emu10k1/emumixer.c
@@ -106,6 +106,12 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
 	"DSP 16", "DSP 17", "DSP 18", "DSP 19", "DSP 20", "DSP 21", "DSP 22", "DSP 23", \
 	"DSP 24", "DSP 25", "DSP 26", "DSP 27", "DSP 28", "DSP 29", "DSP 30", "DSP 31"
 
+#define PB_TEXTS \
+	"PbChn 00", "PbChn 01", "PbChn 02", "PbChn 03", \
+	"PbChn 04", "PbChn 05", "PbChn 06", "PbChn 07", \
+	"PbChn 08", "PbChn 09", "PbChn 10", "PbChn 11", \
+	"PbChn 12", "PbChn 13", "PbChn 14", "PbChn 15"
+
 #define PAIR_TEXTS(base, one, two) PAIR_PS(base, one, two, "")
 #define LR_TEXTS(base) LR_PS(base, "")
 #define ADAT_TEXTS(pfx) ADAT_PS(pfx, "")
@@ -161,6 +167,11 @@ static const char * const emu1010_src_texts[] = {
 	DSP_TEXTS,
 };
 
+static const char * const emu1010_das_src_texts[] = {
+	EMU1010_COMMON_TEXTS,
+	PB_TEXTS,
+};
+
 static const unsigned short emu1010_src_regs[] = {
 	EMU_SRC_SILENCE,
 	PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B),
@@ -192,6 +203,11 @@ static const char * const emu1010b_src_texts[] = {
 	DSP_TEXTS,
 };
 
+static const char * const emu1010b_das_src_texts[] = {
+	EMU1010b_COMMON_TEXTS,
+	PB_TEXTS,
+};
+
 static const unsigned short emu1010b_src_regs[] = {
 	EMU_SRC_SILENCE,
 	PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B),
@@ -221,6 +237,11 @@ static const char * const emu1616_src_texts[] = {
 	DSP_TEXTS,
 };
 
+static const char * const emu1616_das_src_texts[] = {
+	EMU1616_COMMON_TEXTS,
+	PB_TEXTS,
+};
+
 static const unsigned short emu1616_src_regs[] = {
 	EMU_SRC_SILENCE,
 	PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B),
@@ -244,6 +265,11 @@ static const char * const emu0404_src_texts[] = {
 	DSP_TEXTS,
 };
 
+static const char * const emu0404_das_src_texts[] = {
+	EMU0404_COMMON_TEXTS,
+	PB_TEXTS,
+};
+
 static const unsigned short emu0404_src_regs[] = {
 	EMU_SRC_SILENCE,
 	LR_REGS(EMU_SRC_HAMOA_ADC),
@@ -427,6 +453,25 @@ static const char * const emu1010_input_texts[] = {
 };
 static_assert(ARRAY_SIZE(emu1010_input_texts) <= NUM_INPUT_DESTS);
 
+static const char * const emu1010_das_input_texts[] = {
+	"CpChn 00 Capture Enum",
+	"CpChn 01 Capture Enum",
+	"CpChn 02 Capture Enum",
+	"CpChn 03 Capture Enum",
+	"CpChn 04 Capture Enum",
+	"CpChn 05 Capture Enum",
+	"CpChn 06 Capture Enum",
+	"CpChn 07 Capture Enum",
+	"CpChn 08 Capture Enum",
+	"CpChn 09 Capture Enum",
+	"CpChn 10 Capture Enum",
+	"CpChn 11 Capture Enum",
+	"CpChn 12 Capture Enum",
+	"CpChn 13 Capture Enum",
+	"CpChn 14 Capture Enum",
+	"CpChn 15 Capture Enum",
+};
+
 static const unsigned short emu1010_input_dst[] = {
 	EMU_DST_ALICE2_EMU32_0,
 	EMU_DST_ALICE2_EMU32_1,
@@ -504,78 +549,78 @@ static const unsigned short emu0404_input_dflt[] = {
 };
 
 struct snd_emu1010_routing_info {
-	const char * const *src_texts;
+	const char * const *src_texts[2];
 	const char * const *out_texts;
 	const unsigned short *src_regs;
 	const unsigned short *out_regs;
 	const unsigned short *in_regs;
 	const unsigned short *out_dflts;
 	const unsigned short *in_dflts;
-	unsigned n_srcs;
+	unsigned n_srcs[2];
 	unsigned n_outs;
-	unsigned n_ins;
+	unsigned n_ins[2];
 };
 
 static const struct snd_emu1010_routing_info emu1010_routing_info[] = {
 	{
 		/* rev1 1010 */
 		.src_regs = emu1010_src_regs,
-		.src_texts = emu1010_src_texts,
-		.n_srcs = ARRAY_SIZE(emu1010_src_texts),
+		.src_texts = { emu1010_src_texts, emu1010_das_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu1010_src_texts), ARRAY_SIZE(emu1010_das_src_texts) },
 
 		.out_dflts = emu1010_output_dflt,
 		.out_regs = emu1010_output_dst,
 		.out_texts = emu1010_output_texts,
 		.n_outs = ARRAY_SIZE(emu1010_output_dst),
 
 		.in_dflts = emu1010_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = ARRAY_SIZE(emu1010_input_dst),
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst), 16 },
 	},
 	{
 		/* rev2 1010 */
 		.src_regs = emu1010b_src_regs,
-		.src_texts = emu1010b_src_texts,
-		.n_srcs = ARRAY_SIZE(emu1010b_src_texts),
+		.src_texts = { emu1010b_src_texts, emu1010b_das_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu1010b_src_texts), ARRAY_SIZE(emu1010b_das_src_texts) },
 
 		.out_dflts = emu1010b_output_dflt,
 		.out_regs = emu1010b_output_dst,
 		.out_texts = snd_emu1010b_output_texts,
 		.n_outs = ARRAY_SIZE(emu1010b_output_dst),
 
 		.in_dflts = emu1010_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = ARRAY_SIZE(emu1010_input_dst) - 6,
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
 	},
 	{
 		/* 1616(m) cardbus */
 		.src_regs = emu1616_src_regs,
-		.src_texts = emu1616_src_texts,
-		.n_srcs = ARRAY_SIZE(emu1616_src_texts),
+		.src_texts = { emu1616_src_texts, emu1616_das_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu1616_src_texts), ARRAY_SIZE(emu1616_das_src_texts) },
 
 		.out_dflts = emu1616_output_dflt,
 		.out_regs = emu1616_output_dst,
 		.out_texts = snd_emu1616_output_texts,
 		.n_outs = ARRAY_SIZE(emu1616_output_dst),
 
 		.in_dflts = emu1010_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = ARRAY_SIZE(emu1010_input_dst) - 6,
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
 	},
 	{
 		/* 0404 */
 		.src_regs = emu0404_src_regs,
-		.src_texts = emu0404_src_texts,
-		.n_srcs = ARRAY_SIZE(emu0404_src_texts),
+		.src_texts = { emu0404_src_texts, emu0404_das_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu0404_src_texts), ARRAY_SIZE(emu0404_das_src_texts) },
 
 		.out_dflts = emu0404_output_dflt,
 		.out_regs = emu0404_output_dst,
 		.out_texts = snd_emu0404_output_texts,
 		.n_outs = ARRAY_SIZE(emu0404_output_dflt),
 
 		.in_dflts = emu0404_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = ARRAY_SIZE(emu1010_input_dst) - 6,
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
 	},
 };
 
@@ -608,32 +653,34 @@ static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu)
 {
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned iidx = emu->das_mode;
 
 	for (unsigned i = 0; i < emu_ri->n_outs; i++)
 		snd_emu1010_output_source_apply(
 			emu, i, emu->emu1010.output_source[i]);
-	for (unsigned i = 0; i < emu_ri->n_ins; i++)
+	for (unsigned i = 0; i < emu_ri->n_ins[iidx]; i++)
 		snd_emu1010_input_source_apply(
 			emu, i, emu->emu1010.input_source[i]);
 }
 
 static u8 emu1010_map_source(const struct snd_emu1010_routing_info *emu_ri,
-			     unsigned val)
+			     unsigned das_mode, unsigned val)
 {
-	for (unsigned i = 0; i < emu_ri->n_srcs; i++)
+	for (unsigned i = 0; i < emu_ri->n_srcs[das_mode]; i++)
 		if (val == emu_ri->src_regs[i])
 			return i;
 	return 0;
 }
 
 static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol,
 						struct snd_ctl_elem_info *uinfo)
 {
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned iidx = emu->das_mode;
 
-	return snd_ctl_enum_info(uinfo, 1, emu_ri->n_srcs, emu_ri->src_texts);
+	return snd_ctl_enum_info(uinfo, 1, emu_ri->n_srcs[iidx], emu_ri->src_texts[iidx]);
 }
 
 static int snd_emu1010_output_source_get(struct snd_kcontrol *kcontrol,
@@ -656,11 +703,12 @@ static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol,
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned iidx = emu->das_mode;
 	unsigned val = ucontrol->value.enumerated.item[0];
 	unsigned channel = kcontrol->private_value;
 	int change;
 
-	if (val >= emu_ri->n_srcs)
+	if (val >= emu_ri->n_srcs[iidx])
 		return -EINVAL;
 	if (channel >= emu_ri->n_outs)
 		return -EINVAL;
@@ -686,27 +734,29 @@ static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol,
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned iidx = emu->das_mode;
 	unsigned channel = kcontrol->private_value;
 
-	if (channel >= emu_ri->n_ins)
+	if (channel >= emu_ri->n_ins[iidx])
 		return -EINVAL;
 	ucontrol->value.enumerated.item[0] = emu->emu1010.input_source[channel];
 	return 0;
 }
 
 static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
 {
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned iidx = emu->das_mode;
 	unsigned val = ucontrol->value.enumerated.item[0];
 	unsigned channel = kcontrol->private_value;
 	int change;
 
-	if (val >= emu_ri->n_srcs)
+	if (val >= emu_ri->n_srcs[iidx])
 		return -EINVAL;
-	if (channel >= emu_ri->n_ins)
+	if (channel >= emu_ri->n_ins[iidx])
 		return -EINVAL;
 	change = (emu->emu1010.input_source[channel] != val);
 	if (change) {
@@ -728,14 +778,17 @@ static int add_emu1010_source_mixers(struct snd_emu10k1 *emu)
 {
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned iidx = emu->das_mode;
 	int err;
 
 	err = add_ctls(emu, &emu1010_output_source_ctl,
 		       emu_ri->out_texts, emu_ri->n_outs);
 	if (err < 0)
 		return err;
 	err = add_ctls(emu, &emu1010_input_source_ctl,
-		       emu1010_input_texts, emu_ri->n_ins);
+		       iidx ? emu1010_das_input_texts :
+			      emu1010_input_texts,
+		       emu_ri->n_ins[iidx]);
 	return err;
 }
 
@@ -2338,13 +2391,14 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu,
 		const struct snd_emu1010_routing_info *emu_ri =
 			&emu1010_routing_info[emu_idx];
 		const struct snd_emu1010_pads_info *emu_pi = &emu1010_pads_info[emu_idx];
+		int midx = emu->das_mode;
 
-		for (i = 0; i < emu_ri->n_ins; i++)
+		for (i = 0; i < emu_ri->n_ins[midx]; i++)
 			emu->emu1010.input_source[i] =
-				emu1010_map_source(emu_ri, emu_ri->in_dflts[i]);
+				emu1010_map_source(emu_ri, midx, emu_ri->in_dflts[i]);
 		for (i = 0; i < emu_ri->n_outs; i++)
 			emu->emu1010.output_source[i] =
-				emu1010_map_source(emu_ri, emu_ri->out_dflts[i]);
+				emu1010_map_source(emu_ri, midx, emu_ri->out_dflts[i]);
 		snd_emu1010_apply_sources(emu);
 
 		err = snd_ctl_add(card,
-- 
2.40.0.152.g15d061e6df


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

* [PATCH 3/8] ALSA: emu10k1: set the "no filtering" bits on PCM voices
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 1/8] ALSA: emu10k1: introduce alternative E-MU D.A.S. mode Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 2/8] ALSA: emu10k1: improve mixer control naming in " Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 4/8] ALSA: emu10k1: make playback in E-MU D.A.S. mode 32-bit Oswald Buddenhagen
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

... on Audigy, when we are not pitch-shifting.

The only observable effect is that the Z1/Z2/FXBUS registers don't have
a stray bit set for negative numbers anymore. The bit is below the ones
significant for output, but it would mess with 32-bit sample
recombination, which we are about to add.

kX-project does that, but I had to figure out myself why.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 sound/pci/emu10k1/emupcm.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
index a6fb0647d80a..bd222de7ea9f 100644
--- a/sound/pci/emu10k1/emupcm.c
+++ b/sound/pci/emu10k1/emupcm.c
@@ -318,9 +318,10 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu,
 		REGLIST_END);
 	// Setup routing
 	if (emu->audigy) {
+		u32 unfiltered = (evoice->epcm->pitch_target == PITCH_48000) ? 0x80808080 : 0;
 		snd_emu10k1_ptr_write_multiple(emu, voice,
-			A_FXRT1, snd_emu10k1_compose_audigy_fxrt1(send_routing),
-			A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(send_routing),
+			A_FXRT1, snd_emu10k1_compose_audigy_fxrt1(send_routing) | unfiltered,
+			A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(send_routing) | unfiltered,
 			A_SENDAMOUNTS, snd_emu10k1_compose_audigy_sendamounts(send_amount),
 			REGLIST_END);
 		for (int i = 0; i < 4; i++) {
-- 
2.40.0.152.g15d061e6df


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

* [PATCH 4/8] ALSA: emu10k1: make playback in E-MU D.A.S. mode 32-bit
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
                   ` (2 preceding siblings ...)
  2023-06-13  7:38 ` [PATCH 3/8] ALSA: emu10k1: set the "no filtering" bits on PCM voices Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 5/8] ALSA: add snd_ctl_add_locked() Oswald Buddenhagen
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

Each channel now uses a stereo voice pair. But the FX send amounts and
attenuation are per voice, so we have to use fixed values tuned to the
DSP code to avoid messing up the split samples.

As "regular" mode allows manipulating the amounts and att'n via mixer
controls, we can either:
- "Fake" them with DSP code, which doesn't seem worth the bother
- Ignore them, which doesn't seem very nice

So 32-bit playback simply remains unavailable in non-D.A.S. mode.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 sound/pci/emu10k1/emufx.c  | 18 ++++++++++-----
 sound/pci/emu10k1/emupcm.c | 46 +++++++++++++++++++++++++-------------
 2 files changed, 43 insertions(+), 21 deletions(-)

diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c
index 428ae365eb99..103cb35b39e0 100644
--- a/sound/pci/emu10k1/emufx.c
+++ b/sound/pci/emu10k1/emufx.c
@@ -1290,7 +1290,8 @@ static int _snd_emu10k1_das_init_efx(struct snd_emu10k1 *emu)
 {
 	enum {
 		ENUM_GPR(bit_shifter16, 1),
-		ENUM_GPR(tmp, 1),
+		ENUM_GPR(lowword_mask, 1),
+		ENUM_GPR(tmp, 2),
 		num_static_gprs
 	};
 	int gpr = num_static_gprs;
@@ -1321,10 +1322,14 @@ static int _snd_emu10k1_das_init_efx(struct snd_emu10k1 *emu)
 
 	gpr_map = icode->gpr_map;
 	gpr_map[bit_shifter16] = 0x00008000;
+	gpr_map[lowword_mask] = 0x0000ffff;
 
 	if (emu->card_capabilities->ca0108_chip) {
-		for (int z = 0; z < 16; z++)
-			A_OP(icode, &ptr, iMACINT0, A3_EMU32OUT(z), A_C_00000000, A_FXBUS(z), A_C_00000002);
+		for (int z = 0; z < 16; z++) {
+			A_OP(icode, &ptr, iMAC0, A_GPR(tmp), A_C_00000000, A_FXBUS(z * 2), A_C_00010000); // >> 15
+			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp + 1), A_C_00000000, A_FXBUS(z * 2 + 1), A_C_00000002); // << 1
+			A_OP(icode, &ptr, iANDXOR, A3_EMU32OUT(z), A_GPR(tmp), A_GPR(lowword_mask), A_GPR(tmp + 1));
+		}
 
 		snd_emu10k1_audigy_dsp_convert_32_to_2x16(
 			icode, &ptr, tmp, bit_shifter16, A3_EMU32IN(0), A_EXTOUT(0));
@@ -1337,8 +1342,11 @@ static int _snd_emu10k1_das_init_efx(struct snd_emu10k1 *emu)
 			gpr_map[gpr++] = 0x00000000;
 		}
 	} else {
-		for (int z = 0; z < 16; z++)
-			A_OP(icode, &ptr, iMACINT0, A_EMU32OUTL(z), A_C_00000000, A_FXBUS(z), A_C_00000002);
+		for (int z = 0; z < 16; z++) {
+			A_OP(icode, &ptr, iMAC0, A_GPR(tmp), A_C_00000000, A_FXBUS(z * 2), A_C_00010000); // >> 15
+			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp + 1), A_C_00000000, A_FXBUS(z * 2 + 1), A_C_00000002); // << 1
+			A_OP(icode, &ptr, iANDXOR, A_EMU32OUTL(z), A_GPR(tmp), A_GPR(lowword_mask), A_GPR(tmp + 1));
+		}
 
 		/* Note that the Alice2 DSPs have 6 I2S inputs which we don't use. */
 		snd_emu10k1_audigy_dsp_convert_32_to_2x16(
diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
index bd222de7ea9f..7aed356637ab 100644
--- a/sound/pci/emu10k1/emupcm.c
+++ b/sound/pci/emu10k1/emupcm.c
@@ -366,13 +366,16 @@ static void snd_emu10k1_pcm_init_das_voices(struct snd_emu10k1 *emu,
 					    unsigned char channel)
 {
 	static const unsigned char send_amount[8] = { 255, 0, 0, 0, 0, 0, 0, 0 };
-	unsigned char send_routing[8];
+	unsigned char send_routing[9];
 
 	for (int i = 0; i < ARRAY_SIZE(send_routing); i++)
 		send_routing[i] = (channel + i) % NUM_G;
-	snd_emu10k1_pcm_init_voice(emu, evoice, true, false,
+	snd_emu10k1_pcm_init_voice(emu, evoice, true, true,
 				   start_addr, end_addr,
 				   send_routing, send_amount);
+	snd_emu10k1_pcm_init_voice(emu, evoice + 1, true, true,
+				   start_addr, end_addr,
+				   send_routing + 1, send_amount);
 }
 
 static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu,
@@ -406,7 +409,7 @@ static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream,
 	} else {
 		type = EMU10K1_EFX;
 		channels = params_channels(hw_params);
-		count = 1;
+		count = 1 + emu->das_mode;
 	}
 	err = snd_emu10k1_pcm_channel_alloc(epcm, type, count, channels);
 	if (err < 0)
@@ -509,15 +512,17 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream)
 	snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, true,
 					 start_addr, start_addr + extra_size);
 
-	epcm->ccca_start_addr = start_addr;
 	if (das_mode) {
+		start_addr >>= 1;
+		epcm->ccca_start_addr = start_addr;
 		for (i = 0; i < runtime->channels; i++) {
 			snd_emu10k1_pcm_init_das_voices(emu, epcm->voices[i],
 							start_addr, start_addr + channel_size,
-							i);
+							i * 2);
 			start_addr += channel_size;
 		}
 	} else {
+		epcm->ccca_start_addr = start_addr;
 		for (i = 0; i < runtime->channels; i++) {
 			snd_emu10k1_pcm_init_voices(emu, epcm->voices[i], true, false,
 						    start_addr, start_addr + channel_size,
@@ -722,6 +727,7 @@ static void snd_emu10k1_playback_unmute_das_voices(struct snd_emu10k1 *emu,
 						   struct snd_emu10k1_voice *evoice)
 {
 	snd_emu10k1_playback_commit_volume(emu, evoice, 0x8000 << 16);
+	snd_emu10k1_playback_commit_volume(emu, evoice + 1, 0x8000 << 16);
 }
 
 static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu,
@@ -936,24 +942,29 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream *
 }
 
 static u64 snd_emu10k1_efx_playback_voice_mask(struct snd_emu10k1_pcm *epcm,
-					       int channels)
+					       bool stereo, int channels)
 {
 	u64 mask = 0;
+	u64 mask0 = (1 << (1 << stereo)) - 1;
 
 	for (int i = 0; i < channels; i++) {
 		int voice = epcm->voices[i]->number;
-		mask |= 1ULL << voice;
+		mask |= mask0 << voice;
 	}
 	return mask;
 }
 
 static void snd_emu10k1_efx_playback_freeze_voices(struct snd_emu10k1 *emu,
 						   struct snd_emu10k1_pcm *epcm,
-						   int channels)
+						   bool stereo, int channels)
 {
 	for (int i = 0; i < channels; i++) {
 		int voice = epcm->voices[i]->number;
 		snd_emu10k1_ptr_write(emu, CPF_STOP, voice, 1);
+		if (stereo) {
+			// Weirdly enough, the stereo slave needs to be stopped separately
+			snd_emu10k1_ptr_write(emu, CPF_STOP, voice + 1, 1);
+		}
 		snd_emu10k1_playback_commit_pitch(emu, voice, PITCH_48000 << 16);
 	}
 }
@@ -977,14 +988,14 @@ static void snd_emu10k1_efx_playback_unmute_das_voices(struct snd_emu10k1 *emu,
 
 static void snd_emu10k1_efx_playback_stop_voices(struct snd_emu10k1 *emu,
 						 struct snd_emu10k1_pcm *epcm,
-						 int channels)
+						 bool stereo, int channels)
 {
 	for (int i = 0; i < channels; i++)
 		snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]);
 	snd_emu10k1_playback_set_stopped(emu, epcm);
 
 	for (int i = 0; i < channels; i++)
-		snd_emu10k1_playback_mute_voice(emu, epcm->voices[i]);
+		snd_emu10k1_playback_mute_voices(emu, epcm->voices[i], stereo);
 }
 
 static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
@@ -1003,15 +1014,15 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 	case SNDRV_PCM_TRIGGER_RESUME:
 		mask = snd_emu10k1_efx_playback_voice_mask(
-				epcm, runtime->channels);
+				epcm, das_mode, runtime->channels);
 		for (int i = 0; i < 10; i++) {
 			// Note that the freeze is not interruptible, so we make no
 			// effort to reset the bits outside the error handling here.
 			snd_emu10k1_voice_set_loop_stop_multiple(emu, mask);
 			snd_emu10k1_efx_playback_freeze_voices(
-					emu, epcm, runtime->channels);
+					emu, epcm, das_mode, runtime->channels);
 			snd_emu10k1_playback_prepare_voices(
-					emu, epcm, true, false, runtime->channels);
+					emu, epcm, true, das_mode, runtime->channels);
 
 			// It might seem to make more sense to unmute the voices only after
 			// they have been started, to potentially avoid torturing the speakers
@@ -1033,7 +1044,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 			}
 
 			snd_emu10k1_efx_playback_stop_voices(
-					emu, epcm, runtime->channels);
+					emu, epcm, das_mode, runtime->channels);
 
 			if (result != -EAGAIN)
 				break;
@@ -1046,7 +1057,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 		snd_emu10k1_playback_stop_voice(emu, epcm->extra);
 		snd_emu10k1_efx_playback_stop_voices(
-				emu, epcm, runtime->channels);
+				emu, epcm, das_mode, runtime->channels);
 
 		epcm->resume_pos = snd_emu10k1_playback_pointer(substream);
 		break;
@@ -1232,8 +1243,11 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream)
 	runtime->private_data = epcm;
 	runtime->private_free = snd_emu10k1_pcm_free_substream;
 	runtime->hw = snd_emu10k1_efx_playback;
-	if (emu->card_capabilities->emu_model)
+	if (emu->card_capabilities->emu_model) {
 		snd_emu1010_constrain_efx_rate(emu, runtime);
+		if (emu->das_mode)
+			runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+	}
 	err = snd_emu10k1_playback_set_constraints(runtime);
 	if (err < 0) {
 		kfree(epcm);
-- 
2.40.0.152.g15d061e6df


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

* [PATCH 5/8] ALSA: add snd_ctl_add_locked()
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
                   ` (3 preceding siblings ...)
  2023-06-13  7:38 ` [PATCH 4/8] ALSA: emu10k1: make playback in E-MU D.A.S. mode 32-bit Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode Oswald Buddenhagen
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

This is in fact more symmetrical to snd_ctl_remove() than snd_ctl_add()
is - the former could be named snd_ctl_remove_locked() just as well.

This will be used to dynamically change the available controls from
another control's put() callback, which is already locked.

One might want to add snd_ctl_replace_locked() for completeness, but I
have no use for it now.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 include/sound/control.h |  1 +
 sound/core/control.c    | 31 +++++++++++++++++++++++++++++++
 2 files changed, 32 insertions(+)

diff --git a/include/sound/control.h b/include/sound/control.h
index cc3dcc6cfb0f..d4e210831a38 100644
--- a/include/sound/control.h
+++ b/include/sound/control.h
@@ -134,6 +134,7 @@ void snd_ctl_notify_one(struct snd_card * card, unsigned int mask, struct snd_kc
 struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new * kcontrolnew, void * private_data);
 void snd_ctl_free_one(struct snd_kcontrol * kcontrol);
 int snd_ctl_add(struct snd_card * card, struct snd_kcontrol * kcontrol);
+int snd_ctl_add_locked(struct snd_card * card, struct snd_kcontrol * kcontrol);
 int snd_ctl_remove(struct snd_card * card, struct snd_kcontrol * kcontrol);
 int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol, bool add_on_replace);
 int snd_ctl_remove_id(struct snd_card * card, struct snd_ctl_elem_id *id);
diff --git a/sound/core/control.c b/sound/core/control.c
index 82aa1af1d1d8..ede5ab911add 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -527,6 +527,27 @@ static int snd_ctl_add_replace(struct snd_card *card,
 	return err;
 }
 
+static int snd_ctl_add_replace_locked(struct snd_card *card,
+				      struct snd_kcontrol *kcontrol,
+				      enum snd_ctl_add_mode mode)
+{
+	int err = -EINVAL;
+
+	if (! kcontrol)
+		return err;
+	if (snd_BUG_ON(!card || !kcontrol->info))
+		goto error;
+
+	err = __snd_ctl_add_replace(card, kcontrol, mode);
+	if (err < 0)
+		goto error;
+	return 0;
+
+ error:
+	snd_ctl_free_one(kcontrol);
+	return err;
+}
+
 /**
  * snd_ctl_add - add the control instance to the card
  * @card: the card instance
@@ -547,6 +568,16 @@ int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
 }
 EXPORT_SYMBOL(snd_ctl_add);
 
+/**
+ * snd_ctl_add_locked - same as snd_ctl_add(), but card->controls_rwsem
+ * is expected to be already locked if necessary.
+ */
+int snd_ctl_add_locked(struct snd_card *card, struct snd_kcontrol *kcontrol)
+{
+	return snd_ctl_add_replace_locked(card, kcontrol, CTL_ADD_EXCLUSIVE);
+}
+EXPORT_SYMBOL(snd_ctl_add_locked);
+
 /**
  * snd_ctl_replace - replace the control instance of the card
  * @card: the card instance
-- 
2.40.0.152.g15d061e6df


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

* [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
                   ` (4 preceding siblings ...)
  2023-06-13  7:38 ` [PATCH 5/8] ALSA: add snd_ctl_add_locked() Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-13  9:20   ` Takashi Iwai
  2023-06-13  7:38 ` [PATCH 7/8] ALSA: emu10k1: add high-rate capture " Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 8/8] ALSA: emu10k1: add high-rate playback " Oswald Buddenhagen
  7 siblings, 1 reply; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

This lays the groundwork for supporting 88.2/96/176.4/192 kHz rates
without actually doing so yet - we simply multi-feed the same samples
on playback, and throw away the excess ones on capture. Input-to-output
monitoring does actually use the full sample rate, though.

Notably, add_ctls() now uses snd_ctl_add_locked(), so it doesn't
deadlock when called from snd_emu1010_clock_shift_put(). This also
affects the initial creation of the controls, which is OK, as that is
done before the card is registered, so no concurrent access can occur.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 include/sound/emu10k1.h          |   3 +
 sound/pci/emu10k1/emu10k1_main.c |   2 +-
 sound/pci/emu10k1/emumixer.c     | 648 ++++++++++++++++++++++++++++---
 sound/pci/emu10k1/emupcm.c       |  41 +-
 sound/pci/emu10k1/io.c           |  30 +-
 5 files changed, 663 insertions(+), 61 deletions(-)

diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h
index cad5faa01c4c..1f827290977f 100644
--- a/include/sound/emu10k1.h
+++ b/include/sound/emu10k1.h
@@ -1676,6 +1676,8 @@ struct snd_emu1010 {
 	unsigned int word_clock;  /* Cached effective value */
 	unsigned int clock_source;
 	unsigned int clock_fallback;
+	unsigned int clock_shift;  /* EMU_HANA_WCLOCK_MULT_MASK >> 3 */
+	unsigned int clock_users;
 	unsigned int optical_in; /* 0:SPDIF, 1:ADAT */
 	unsigned int optical_out; /* 0:SPDIF, 1:ADAT */
 	struct delayed_work firmware_work;
@@ -1756,6 +1758,7 @@ struct snd_emu10k1 {
 	struct snd_kcontrol *ctl_efx_send_routing;
 	struct snd_kcontrol *ctl_efx_send_volume;
 	struct snd_kcontrol *ctl_efx_attn;
+	struct snd_kcontrol *ctl_clock_shift;
 
 	void (*hwvol_interrupt)(struct snd_emu10k1 *emu, unsigned int status);
 	void (*capture_interrupt)(struct snd_emu10k1 *emu, unsigned int status);
diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
index aa28a7524a67..13e9200b8fcb 100644
--- a/sound/pci/emu10k1/emu10k1_main.c
+++ b/sound/pci/emu10k1/emu10k1_main.c
@@ -902,12 +902,12 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu)
 
 	emu->emu1010.clock_source = 1;  /* 48000 */
 	emu->emu1010.clock_fallback = 1;  /* 48000 */
+	emu->emu1010.clock_shift = 0;  /* 1x */
 	/* Default WCLK set to 48kHz. */
 	snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K);
 	/* Word Clock source, Internal 48kHz x1 */
 	emu->emu1010.wclock = EMU_HANA_WCLOCK_INT_48K;
 	snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K);
-	/* snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X); */
 	snd_emu1010_update_clock(emu);
 
 	// The routes are all set to EMU_SRC_SILENCE due to the reset,
diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c
index 8878b660ba94..844ccf3b025c 100644
--- a/sound/pci/emu10k1/emumixer.c
+++ b/sound/pci/emu10k1/emumixer.c
@@ -39,7 +39,7 @@ static int add_ctls(struct snd_emu10k1 *emu, const struct snd_kcontrol_new *tpl,
 	for (unsigned i = 0; i < nctls; i++) {
 		kctl.name = ctls[i];
 		kctl.private_value = i;
-		err = snd_ctl_add(emu->card, snd_ctl_new1(&kctl, emu));
+		err = snd_ctl_add_locked(emu->card, snd_ctl_new1(&kctl, emu));
 		if (err < 0)
 			return err;
 	}
@@ -87,15 +87,35 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
 	pfx "ADAT 0" sfx, pfx "ADAT 1" sfx, pfx "ADAT 2" sfx, pfx "ADAT 3" sfx, \
 	pfx "ADAT 4" sfx, pfx "ADAT 5" sfx, pfx "ADAT 6" sfx, pfx "ADAT 7" sfx
 
+#define ADAT_2x_PS(pfx, sfx) \
+	pfx "ADAT 0-1" sfx, pfx "ADAT 2-3" sfx, pfx "ADAT 4-5" sfx, pfx "ADAT 6-7" sfx
+
+#define ADAT_4x_PS(pfx, sfx) \
+	pfx "ADAT 0-3" sfx, pfx "ADAT 4-7" sfx
+
 #define PAIR_REGS(base, one, two) \
 	base ## one ## 1, \
 	base ## two ## 1
+#define PAIR_2x_REGS(base, one, two) \
+	{ base ## one ## 1, base ## one ## 2 }, \
+	{ base ## two ## 1, base ## two ## 2 }
+#define PAIR_4x_REGS(base, one, two) \
+	{ base ## one ## 1, base ## one ## 2, base ## one ## 3, base ## one ## 4 }, \
+	{ base ## two ## 1, base ## two ## 2, base ## two ## 3, base ## two ## 4 }
 
 #define LR_REGS(base) PAIR_REGS(base, _LEFT, _RIGHT)
+#define LR_2x_REGS(base) PAIR_2x_REGS(base, _LEFT, _RIGHT)
+#define LR_4x_REGS(base) PAIR_4x_REGS(base, _LEFT, _RIGHT)
 
 #define ADAT_REGS(base) \
 	base+0, base+1, base+2, base+3, base+4, base+5, base+6, base+7
 
+#define ADAT_2x_REGS(base) \
+	{ base+0, base+1 }, { base+2, base+3 }, { base+4, base+5 }, { base+6, base+7 }
+
+#define ADAT_4x_REGS(base) \
+	{ base+0, base+1, base+2, base+3 }, { base+4, base+5, base+6, base+7 }
+
 /*
  * List of data sources available for each destination
  */
@@ -112,9 +132,16 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
 	"PbChn 08", "PbChn 09", "PbChn 10", "PbChn 11", \
 	"PbChn 12", "PbChn 13", "PbChn 14", "PbChn 15"
 
+#define PB_4x_TEXTS PB_TEXTS  // Only 1x playback for now
+
 #define PAIR_TEXTS(base, one, two) PAIR_PS(base, one, two, "")
 #define LR_TEXTS(base) LR_PS(base, "")
 #define ADAT_TEXTS(pfx) ADAT_PS(pfx, "")
+#define ADAT_2x_TEXTS(pfx) ADAT_2x_PS(pfx, "")
+#define ADAT_4x_TEXTS(pfx) ADAT_4x_PS(pfx, "")
+
+#define SRC_SILENCE_2x { EMU_SRC_SILENCE, EMU_SRC_SILENCE }
+#define SRC_SILENCE_4x { EMU_SRC_SILENCE, EMU_SRC_SILENCE, EMU_SRC_SILENCE, EMU_SRC_SILENCE }
 
 #define EMU32_SRC_REGS \
 	EMU_SRC_ALICE_EMU32A, \
@@ -150,6 +177,27 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
 	EMU_SRC_ALICE_EMU32B+0xe, \
 	EMU_SRC_ALICE_EMU32B+0xf
 
+// Only 1x playback for now
+#define EMU32_2x_SRC_REGS \
+	{ EMU_SRC_ALICE_EMU32A }, \
+	{ EMU_SRC_ALICE_EMU32A+1 }, \
+	{ EMU_SRC_ALICE_EMU32A+2 }, \
+	{ EMU_SRC_ALICE_EMU32A+3 }, \
+	{ EMU_SRC_ALICE_EMU32A+4 }, \
+	{ EMU_SRC_ALICE_EMU32A+5 }, \
+	{ EMU_SRC_ALICE_EMU32A+6 }, \
+	{ EMU_SRC_ALICE_EMU32A+7 }, \
+	{ EMU_SRC_ALICE_EMU32A+8 }, \
+	{ EMU_SRC_ALICE_EMU32A+9 }, \
+	{ EMU_SRC_ALICE_EMU32A+0xa }, \
+	{ EMU_SRC_ALICE_EMU32A+0xb }, \
+	{ EMU_SRC_ALICE_EMU32A+0xc }, \
+	{ EMU_SRC_ALICE_EMU32A+0xd }, \
+	{ EMU_SRC_ALICE_EMU32A+0xe }, \
+	{ EMU_SRC_ALICE_EMU32A+0xf }
+
+#define EMU32_4x_SRC_REGS EMU32_2x_SRC_REGS
+
 /* 1010 rev1 */
 
 #define EMU1010_COMMON_TEXTS \
@@ -185,6 +233,54 @@ static const unsigned short emu1010_src_regs[] = {
 };
 static_assert(ARRAY_SIZE(emu1010_src_regs) == ARRAY_SIZE(emu1010_src_texts));
 
+static const char * const emu1010_2x_src_texts[] = {
+	"Silence",
+	PAIR_TEXTS("Dock Mic", "A", "B"),
+	LR_TEXTS("Dock ADC1"),
+	LR_TEXTS("Dock ADC2"),
+	LR_TEXTS("Dock ADC3"),
+	LR_TEXTS("0202 ADC"),
+	LR_TEXTS("1010 SPDIF"),
+	ADAT_2x_TEXTS("1010 "),
+	PB_TEXTS,
+};
+
+static const unsigned short emu1010_2x_src_regs[][2] = {
+	SRC_SILENCE_2x,
+	PAIR_2x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
+	LR_2x_REGS(EMU_SRC_DOCK_ADC1),
+	LR_2x_REGS(EMU_SRC_DOCK_ADC2),
+	LR_2x_REGS(EMU_SRC_DOCK_ADC3),
+	LR_2x_REGS(EMU_SRC_HAMOA_ADC),
+	LR_2x_REGS(EMU_SRC_HANA_SPDIF),
+	ADAT_2x_REGS(EMU_SRC_HANA_ADAT),
+	EMU32_2x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu1010_2x_src_regs) == ARRAY_SIZE(emu1010_2x_src_texts));
+
+static const char * const emu1010_4x_src_texts[] = {
+	"Silence",
+	PAIR_TEXTS("Dock Mic", "A", "B"),
+	LR_TEXTS("Dock ADC1"),
+	LR_TEXTS("Dock ADC2"),
+	LR_TEXTS("Dock ADC3"),
+	LR_TEXTS("0202 ADC"),
+	ADAT_4x_TEXTS("1010 "),
+	PB_4x_TEXTS,
+};
+
+static const unsigned short emu1010_4x_src_regs[][4] = {
+	SRC_SILENCE_4x,
+	PAIR_4x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
+	LR_4x_REGS(EMU_SRC_DOCK_ADC1),
+	LR_4x_REGS(EMU_SRC_DOCK_ADC2),
+	LR_4x_REGS(EMU_SRC_DOCK_ADC3),
+	LR_4x_REGS(EMU_SRC_HAMOA_ADC),
+	ADAT_4x_REGS(EMU_SRC_HANA_ADAT),
+	EMU32_4x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu1010_4x_src_regs) == ARRAY_SIZE(emu1010_4x_src_texts));
+
 /* 1010 rev2 */
 
 #define EMU1010b_COMMON_TEXTS \
@@ -222,6 +318,58 @@ static const unsigned short emu1010b_src_regs[] = {
 };
 static_assert(ARRAY_SIZE(emu1010b_src_regs) == ARRAY_SIZE(emu1010b_src_texts));
 
+static const char * const emu1010b_2x_src_texts[] = {
+	"Silence",
+	PAIR_TEXTS("Dock Mic", "A", "B"),
+	LR_TEXTS("Dock ADC1"),
+	LR_TEXTS("Dock ADC2"),
+	LR_TEXTS("0202 ADC"),
+	LR_TEXTS("Dock SPDIF"),
+	LR_TEXTS("1010 SPDIF"),
+	ADAT_2x_TEXTS("Dock "),
+	ADAT_2x_TEXTS("1010 "),
+	PB_TEXTS,
+};
+
+static const unsigned short emu1010b_2x_src_regs[][2] = {
+	SRC_SILENCE_2x,
+	PAIR_2x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
+	LR_2x_REGS(EMU_SRC_DOCK_ADC1),
+	LR_2x_REGS(EMU_SRC_DOCK_ADC2),
+	LR_2x_REGS(EMU_SRC_HAMOA_ADC),
+	LR_2x_REGS(EMU_SRC_MDOCK_SPDIF),
+	LR_2x_REGS(EMU_SRC_HANA_SPDIF),
+	ADAT_2x_REGS(EMU_SRC_MDOCK_ADAT),
+	ADAT_2x_REGS(EMU_SRC_HANA_ADAT),
+	EMU32_2x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu1010b_2x_src_regs) == ARRAY_SIZE(emu1010b_2x_src_texts));
+
+static const char * const emu1010b_4x_src_texts[] = {
+	"Silence",
+	PAIR_TEXTS("Dock Mic", "A", "B"),
+	LR_TEXTS("Dock ADC1"),
+	LR_TEXTS("Dock ADC2"),
+	LR_TEXTS("0202 ADC"),
+	LR_TEXTS("1010 SPDIF"),
+	ADAT_4x_TEXTS("Dock "),
+	ADAT_4x_TEXTS("1010 "),
+	PB_4x_TEXTS,
+};
+
+static const unsigned short emu1010b_4x_src_regs[][4] = {
+	SRC_SILENCE_4x,
+	PAIR_4x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
+	LR_4x_REGS(EMU_SRC_DOCK_ADC1),
+	LR_4x_REGS(EMU_SRC_DOCK_ADC2),
+	LR_4x_REGS(EMU_SRC_HAMOA_ADC),
+	LR_4x_REGS(EMU_SRC_HANA_SPDIF),
+	ADAT_4x_REGS(EMU_SRC_MDOCK_ADAT),
+	ADAT_4x_REGS(EMU_SRC_HANA_ADAT),
+	EMU32_4x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu1010b_4x_src_regs) == ARRAY_SIZE(emu1010b_4x_src_texts));
+
 /* 1616(m) cardbus */
 
 #define EMU1616_COMMON_TEXTS \
@@ -253,6 +401,46 @@ static const unsigned short emu1616_src_regs[] = {
 };
 static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts));
 
+static const char * const emu1616_2x_src_texts[] = {
+	"Silence",
+	PAIR_TEXTS("Mic", "A", "B"),
+	LR_TEXTS("ADC1"),
+	LR_TEXTS("ADC2"),
+	LR_TEXTS("SPDIF"),
+	ADAT_2x_TEXTS(""),
+	PB_TEXTS,
+};
+
+static const unsigned short emu1616_2x_src_regs[][2] = {
+	SRC_SILENCE_2x,
+	PAIR_2x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
+	LR_2x_REGS(EMU_SRC_DOCK_ADC1),
+	LR_2x_REGS(EMU_SRC_DOCK_ADC2),
+	LR_2x_REGS(EMU_SRC_MDOCK_SPDIF),
+	ADAT_2x_REGS(EMU_SRC_MDOCK_ADAT),
+	EMU32_2x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu1616_2x_src_regs) == ARRAY_SIZE(emu1616_2x_src_texts));
+
+static const char * const emu1616_4x_src_texts[] = {
+	"Silence",
+	PAIR_TEXTS("Mic", "A", "B"),
+	LR_TEXTS("ADC1"),
+	LR_TEXTS("ADC2"),
+	ADAT_4x_TEXTS(""),
+	PB_4x_TEXTS,
+};
+
+static const unsigned short emu1616_4x_src_regs[][4] = {
+	SRC_SILENCE_4x,
+	PAIR_4x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
+	LR_4x_REGS(EMU_SRC_DOCK_ADC1),
+	LR_4x_REGS(EMU_SRC_DOCK_ADC2),
+	ADAT_4x_REGS(EMU_SRC_MDOCK_ADAT),
+	EMU32_4x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu1616_4x_src_regs) == ARRAY_SIZE(emu1616_4x_src_texts));
+
 /* 0404 rev1 & rev2 */
 
 #define EMU0404_COMMON_TEXTS \
@@ -278,13 +466,36 @@ static const unsigned short emu0404_src_regs[] = {
 };
 static_assert(ARRAY_SIZE(emu0404_src_regs) == ARRAY_SIZE(emu0404_src_texts));
 
+static const unsigned short emu0404_2x_src_regs[][2] = {
+	SRC_SILENCE_2x,
+	LR_2x_REGS(EMU_SRC_HAMOA_ADC),
+	LR_2x_REGS(EMU_SRC_HANA_SPDIF),
+	EMU32_2x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu0404_2x_src_regs) == ARRAY_SIZE(emu0404_das_src_texts));
+
+static const char * const emu0404_4x_src_texts[] = {
+	"Silence",
+	LR_TEXTS("ADC"),
+	PB_4x_TEXTS,
+};
+
+static const unsigned short emu0404_4x_src_regs[][4] = {
+	SRC_SILENCE_4x,
+	LR_4x_REGS(EMU_SRC_HAMOA_ADC),
+	EMU32_4x_SRC_REGS,
+};
+static_assert(ARRAY_SIZE(emu0404_4x_src_regs) == ARRAY_SIZE(emu0404_4x_src_texts));
+
 /*
  * Data destinations - physical EMU outputs.
  * Each destination has an enum mixer control to choose a data source
  */
 
 #define LR_CTLS(base) LR_PS(base, " Playback Enum")
 #define ADAT_CTLS(pfx) ADAT_PS(pfx, " Playback Enum")
+#define ADAT_2x_CTLS(pfx) ADAT_2x_PS(pfx, " Playback Enum")
+#define ADAT_4x_CTLS(pfx) ADAT_4x_PS(pfx, " Playback Enum")
 
 /* 1010 rev1 */
 
@@ -328,6 +539,52 @@ static const unsigned short emu1010_output_dflt[] = {
 };
 static_assert(ARRAY_SIZE(emu1010_output_dflt) == ARRAY_SIZE(emu1010_output_dst));
 
+static const char * const emu1010_2x_output_texts[] = {
+	LR_CTLS("Dock DAC1"),
+	LR_CTLS("Dock DAC2"),
+	LR_CTLS("Dock DAC3"),
+	LR_CTLS("Dock DAC4"),
+	LR_CTLS("Dock Phones"),
+	LR_CTLS("Dock SPDIF"),
+	LR_CTLS("0202 DAC"),
+	LR_CTLS("1010 SPDIF"),
+	ADAT_2x_CTLS("1010 "),
+};
+static_assert(ARRAY_SIZE(emu1010_2x_output_texts) <= NUM_OUTPUT_DESTS);
+
+static const unsigned short emu1010_2x_output_dst[][2] = {
+	LR_2x_REGS(EMU_DST_DOCK_DAC1),
+	LR_2x_REGS(EMU_DST_DOCK_DAC2),
+	LR_2x_REGS(EMU_DST_DOCK_DAC3),
+	LR_2x_REGS(EMU_DST_DOCK_DAC4),
+	LR_2x_REGS(EMU_DST_DOCK_PHONES),
+	LR_2x_REGS(EMU_DST_DOCK_SPDIF),
+	LR_2x_REGS(EMU_DST_HAMOA_DAC),
+	LR_2x_REGS(EMU_DST_HANA_SPDIF),
+	ADAT_2x_REGS(EMU_DST_HANA_ADAT),
+};
+static_assert(ARRAY_SIZE(emu1010_2x_output_dst) == ARRAY_SIZE(emu1010_2x_output_texts));
+
+static const char * const emu1010_4x_output_texts[] = {
+	LR_CTLS("Dock DAC1"),
+	LR_CTLS("Dock DAC2"),
+	LR_CTLS("Dock DAC3"),
+	LR_CTLS("Dock DAC4"),
+	LR_CTLS("0202 DAC"),
+	ADAT_4x_CTLS("1010 "),
+};
+static_assert(ARRAY_SIZE(emu1010_4x_output_texts) <= NUM_OUTPUT_DESTS);
+
+static const unsigned short emu1010_4x_output_dst[][4] = {
+	LR_4x_REGS(EMU_DST_DOCK_DAC1),
+	LR_4x_REGS(EMU_DST_DOCK_DAC2),
+	LR_4x_REGS(EMU_DST_DOCK_DAC3),
+	LR_4x_REGS(EMU_DST_DOCK_DAC4),
+	LR_4x_REGS(EMU_DST_HAMOA_DAC),
+	ADAT_4x_REGS(EMU_DST_HANA_ADAT),
+};
+static_assert(ARRAY_SIZE(emu1010_4x_output_dst) == ARRAY_SIZE(emu1010_4x_output_texts));
+
 /* 1010 rev2 */
 
 static const char * const snd_emu1010b_output_texts[] = {
@@ -367,6 +624,52 @@ static const unsigned short emu1010b_output_dflt[] = {
 	EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7,
 };
 
+static const char * const snd_emu1010b_2x_output_texts[] = {
+	LR_CTLS("Dock DAC1"),
+	LR_CTLS("Dock DAC2"),
+	LR_CTLS("Dock DAC3"),
+	LR_CTLS("Dock SPDIF"),
+	ADAT_2x_CTLS("Dock "),
+	LR_CTLS("0202 DAC"),
+	LR_CTLS("1010 SPDIF"),
+	ADAT_2x_CTLS("1010 "),
+};
+static_assert(ARRAY_SIZE(snd_emu1010b_2x_output_texts) <= NUM_OUTPUT_DESTS);
+
+static const unsigned short emu1010b_2x_output_dst[][2] = {
+	LR_2x_REGS(EMU_DST_DOCK_DAC1),
+	LR_2x_REGS(EMU_DST_DOCK_DAC2),
+	LR_2x_REGS(EMU_DST_DOCK_DAC3),
+	LR_2x_REGS(EMU_DST_MDOCK_SPDIF),
+	ADAT_2x_REGS(EMU_DST_MDOCK_ADAT),
+	LR_2x_REGS(EMU_DST_HAMOA_DAC),
+	LR_2x_REGS(EMU_DST_HANA_SPDIF),
+	ADAT_2x_REGS(EMU_DST_HANA_ADAT),
+};
+static_assert(ARRAY_SIZE(emu1010b_2x_output_dst) == ARRAY_SIZE(snd_emu1010b_2x_output_texts));
+
+static const char * const snd_emu1010b_4x_output_texts[] = {
+	LR_CTLS("Dock DAC1"),
+	LR_CTLS("Dock DAC2"),
+	LR_CTLS("Dock DAC3"),
+	ADAT_4x_CTLS("Dock "),
+	LR_CTLS("0202 DAC"),
+	LR_CTLS("1010 SPDIF"),
+	ADAT_4x_CTLS("1010 "),
+};
+static_assert(ARRAY_SIZE(snd_emu1010b_4x_output_texts) <= NUM_OUTPUT_DESTS);
+
+static const unsigned short emu1010b_4x_output_dst[][4] = {
+	LR_4x_REGS(EMU_DST_DOCK_DAC1),
+	LR_4x_REGS(EMU_DST_DOCK_DAC2),
+	LR_4x_REGS(EMU_DST_DOCK_DAC3),
+	ADAT_4x_REGS(EMU_DST_MDOCK_ADAT),
+	LR_4x_REGS(EMU_DST_HAMOA_DAC),
+	LR_4x_REGS(EMU_DST_HANA_SPDIF),
+	ADAT_4x_REGS(EMU_DST_HANA_ADAT),
+};
+static_assert(ARRAY_SIZE(emu1010b_4x_output_dst) == ARRAY_SIZE(snd_emu1010b_4x_output_texts));
+
 /* 1616(m) cardbus */
 
 static const char * const snd_emu1616_output_texts[] = {
@@ -400,6 +703,40 @@ static const unsigned short emu1616_output_dflt[] = {
 };
 static_assert(ARRAY_SIZE(emu1616_output_dflt) == ARRAY_SIZE(emu1616_output_dst));
 
+static const char * const snd_emu1616_2x_output_texts[] = {
+	LR_CTLS("Dock DAC1"),
+	LR_CTLS("Dock DAC2"),
+	LR_CTLS("Dock DAC3"),
+	LR_CTLS("Dock SPDIF"),
+	ADAT_2x_CTLS("Dock "),
+};
+static_assert(ARRAY_SIZE(snd_emu1616_2x_output_texts) <= NUM_OUTPUT_DESTS);
+
+static const unsigned short emu1616_2x_output_dst[][2] = {
+	LR_2x_REGS(EMU_DST_DOCK_DAC1),
+	LR_2x_REGS(EMU_DST_DOCK_DAC2),
+	LR_2x_REGS(EMU_DST_DOCK_DAC3),
+	LR_2x_REGS(EMU_DST_MDOCK_SPDIF),
+	ADAT_2x_REGS(EMU_DST_MDOCK_ADAT),
+};
+static_assert(ARRAY_SIZE(emu1616_2x_output_dst) == ARRAY_SIZE(snd_emu1616_2x_output_texts));
+
+static const char * const snd_emu1616_4x_output_texts[] = {
+	LR_CTLS("Dock DAC1"),
+	LR_CTLS("Dock DAC2"),
+	LR_CTLS("Dock DAC3"),
+	ADAT_4x_CTLS("Dock "),
+};
+static_assert(ARRAY_SIZE(snd_emu1616_4x_output_texts) <= NUM_OUTPUT_DESTS);
+
+static const unsigned short emu1616_4x_output_dst[][4] = {
+	LR_4x_REGS(EMU_DST_DOCK_DAC1),
+	LR_4x_REGS(EMU_DST_DOCK_DAC2),
+	LR_4x_REGS(EMU_DST_DOCK_DAC3),
+	ADAT_4x_REGS(EMU_DST_MDOCK_ADAT),
+};
+static_assert(ARRAY_SIZE(emu1616_4x_output_dst) == ARRAY_SIZE(snd_emu1616_4x_output_texts));
+
 /* 0404 rev1 & rev2 */
 
 static const char * const snd_emu0404_output_texts[] = {
@@ -420,6 +757,22 @@ static const unsigned short emu0404_output_dflt[] = {
 };
 static_assert(ARRAY_SIZE(emu0404_output_dflt) == ARRAY_SIZE(emu0404_output_dst));
 
+static const unsigned short emu0404_2x_output_dst[][2] = {
+	LR_2x_REGS(EMU_DST_HAMOA_DAC),
+	LR_2x_REGS(EMU_DST_HANA_SPDIF),
+};
+static_assert(ARRAY_SIZE(emu0404_2x_output_dst) == ARRAY_SIZE(snd_emu0404_output_texts));
+
+static const char * const snd_emu0404_4x_output_texts[] = {
+	LR_CTLS("DAC"),
+};
+static_assert(ARRAY_SIZE(snd_emu0404_4x_output_texts) <= NUM_OUTPUT_DESTS);
+
+static const unsigned short emu0404_4x_output_dst[][4] = {
+	LR_4x_REGS(EMU_DST_HAMOA_DAC),
+};
+static_assert(ARRAY_SIZE(emu0404_4x_output_dst) == ARRAY_SIZE(snd_emu0404_4x_output_texts));
+
 /*
  * Data destinations - FPGA outputs going to Alice2 (Audigy) for
  *   capture (EMU32 + I2S links)
@@ -549,168 +902,267 @@ static const unsigned short emu0404_input_dflt[] = {
 };
 
 struct snd_emu1010_routing_info {
-	const char * const *src_texts[2];
-	const char * const *out_texts;
-	const unsigned short *src_regs;
-	const unsigned short *out_regs;
+	const char * const *src_texts[4];
+	const char * const *out_texts[3];
+	const unsigned short *src_regs[3];
+	const unsigned short *out_regs[3];
 	const unsigned short *in_regs;
 	const unsigned short *out_dflts;
 	const unsigned short *in_dflts;
-	unsigned n_srcs[2];
-	unsigned n_outs;
-	unsigned n_ins[2];
+	unsigned n_srcs[4];
+	unsigned n_outs[3];
+	unsigned n_ins[4];
 };
 
 static const struct snd_emu1010_routing_info emu1010_routing_info[] = {
 	{
 		/* rev1 1010 */
-		.src_regs = emu1010_src_regs,
-		.src_texts = { emu1010_src_texts, emu1010_das_src_texts },
-		.n_srcs = { ARRAY_SIZE(emu1010_src_texts), ARRAY_SIZE(emu1010_das_src_texts) },
+		.src_regs = { emu1010_src_regs, emu1010_2x_src_regs[0], emu1010_4x_src_regs[0] },
+		.src_texts = { emu1010_src_texts, emu1010_das_src_texts,
+			       emu1010_2x_src_texts, emu1010_4x_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu1010_src_texts), ARRAY_SIZE(emu1010_das_src_texts),
+			    ARRAY_SIZE(emu1010_2x_src_texts), ARRAY_SIZE(emu1010_4x_src_texts) },
 
 		.out_dflts = emu1010_output_dflt,
-		.out_regs = emu1010_output_dst,
-		.out_texts = emu1010_output_texts,
-		.n_outs = ARRAY_SIZE(emu1010_output_dst),
+		.out_regs = { emu1010_output_dst, emu1010_2x_output_dst[0], emu1010_4x_output_dst[0] },
+		.out_texts = { emu1010_output_texts,
+			       emu1010_2x_output_texts, emu1010_4x_output_texts },
+		.n_outs = { ARRAY_SIZE(emu1010_output_texts),
+			    ARRAY_SIZE(emu1010_2x_output_texts), ARRAY_SIZE(emu1010_4x_output_texts) },
 
 		.in_dflts = emu1010_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst), 16 },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst), 16, 16, 16 },
 	},
 	{
 		/* rev2 1010 */
-		.src_regs = emu1010b_src_regs,
-		.src_texts = { emu1010b_src_texts, emu1010b_das_src_texts },
-		.n_srcs = { ARRAY_SIZE(emu1010b_src_texts), ARRAY_SIZE(emu1010b_das_src_texts) },
+		.src_regs = { emu1010b_src_regs, emu1010b_2x_src_regs[0], emu1010b_4x_src_regs[0] },
+		.src_texts = { emu1010b_src_texts, emu1010b_das_src_texts,
+			       emu1010b_2x_src_texts, emu1010b_4x_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu1010b_src_texts), ARRAY_SIZE(emu1010b_das_src_texts),
+			    ARRAY_SIZE(emu1010b_2x_src_texts), ARRAY_SIZE(emu1010b_4x_src_texts) },
 
 		.out_dflts = emu1010b_output_dflt,
-		.out_regs = emu1010b_output_dst,
-		.out_texts = snd_emu1010b_output_texts,
-		.n_outs = ARRAY_SIZE(emu1010b_output_dst),
+		.out_regs = { emu1010b_output_dst, emu1010b_2x_output_dst[0], emu1010b_4x_output_dst[0] },
+		.out_texts = { snd_emu1010b_output_texts,
+			       snd_emu1010b_2x_output_texts, snd_emu1010b_4x_output_texts },
+		.n_outs = { ARRAY_SIZE(snd_emu1010b_output_texts),
+			    ARRAY_SIZE(snd_emu1010b_2x_output_texts), ARRAY_SIZE(snd_emu1010b_4x_output_texts) },
 
 		.in_dflts = emu1010_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
 	},
 	{
 		/* 1616(m) cardbus */
-		.src_regs = emu1616_src_regs,
-		.src_texts = { emu1616_src_texts, emu1616_das_src_texts },
-		.n_srcs = { ARRAY_SIZE(emu1616_src_texts), ARRAY_SIZE(emu1616_das_src_texts) },
+		.src_regs = { emu1616_src_regs, emu1616_2x_src_regs[0], emu1616_4x_src_regs[0] },
+		.src_texts = { emu1616_src_texts, emu1616_das_src_texts,
+			       emu1616_2x_src_texts, emu1616_4x_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu1616_src_texts), ARRAY_SIZE(emu1616_das_src_texts),
+			    ARRAY_SIZE(emu1616_2x_src_texts), ARRAY_SIZE(emu1616_4x_src_texts) },
 
 		.out_dflts = emu1616_output_dflt,
-		.out_regs = emu1616_output_dst,
-		.out_texts = snd_emu1616_output_texts,
-		.n_outs = ARRAY_SIZE(emu1616_output_dst),
+		.out_regs = { emu1616_output_dst, emu1616_2x_output_dst[0], emu1616_4x_output_dst[0] },
+		.out_texts = { snd_emu1616_output_texts,
+			       snd_emu1616_2x_output_texts, snd_emu1616_4x_output_texts },
+		.n_outs = { ARRAY_SIZE(snd_emu1616_output_texts),
+			    ARRAY_SIZE(snd_emu1616_2x_output_texts), ARRAY_SIZE(snd_emu1616_4x_output_texts) },
 
 		.in_dflts = emu1010_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
 	},
 	{
 		/* 0404 */
-		.src_regs = emu0404_src_regs,
-		.src_texts = { emu0404_src_texts, emu0404_das_src_texts },
-		.n_srcs = { ARRAY_SIZE(emu0404_src_texts), ARRAY_SIZE(emu0404_das_src_texts) },
+		.src_regs = { emu0404_src_regs, emu0404_2x_src_regs[0], emu0404_4x_src_regs[0] },
+		.src_texts = { emu0404_src_texts, emu0404_das_src_texts,
+			       emu0404_das_src_texts, emu0404_4x_src_texts },
+		.n_srcs = { ARRAY_SIZE(emu0404_src_texts), ARRAY_SIZE(emu0404_das_src_texts),
+			    ARRAY_SIZE(emu0404_das_src_texts), ARRAY_SIZE(emu0404_4x_src_texts) },
 
 		.out_dflts = emu0404_output_dflt,
-		.out_regs = emu0404_output_dst,
-		.out_texts = snd_emu0404_output_texts,
-		.n_outs = ARRAY_SIZE(emu0404_output_dflt),
+		.out_regs = { emu0404_output_dst, emu0404_2x_output_dst[0], emu0404_4x_output_dst[0] },
+		.out_texts = { snd_emu0404_output_texts,
+			       snd_emu0404_output_texts, snd_emu0404_4x_output_texts },
+		.n_outs = { ARRAY_SIZE(snd_emu0404_output_texts),
+			    ARRAY_SIZE(snd_emu0404_output_texts), ARRAY_SIZE(snd_emu0404_4x_output_texts) },
 
 		.in_dflts = emu0404_input_dflt,
 		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
 	},
 };
 
 static unsigned emu1010_idx(struct snd_emu10k1 *emu)
 {
 	return emu->card_capabilities->emu_model - 1;
 }
 
+static void snd_emu1010_source_apply(struct snd_emu10k1 *emu, unsigned shift,
+				     const unsigned short *regs,
+				     const unsigned short *vals)
+{
+	unsigned short avals[4];
+
+	if ((vals[0] & 0x700) == 0x300) {  // EMU32x
+		// Only 1x playback for now
+		avals[0] = avals[1] = avals[2] = avals[3] = vals[0];
+		vals = avals;
+	}
+	switch (shift) {
+	case 2:
+		snd_emu1010_fpga_link_dst_src_write(emu, regs[3], vals[3]);
+		snd_emu1010_fpga_link_dst_src_write(emu, regs[2], vals[2]);
+		fallthrough;
+	case 1:
+		snd_emu1010_fpga_link_dst_src_write(emu, regs[1], vals[1]);
+		fallthrough;
+	default:
+		snd_emu1010_fpga_link_dst_src_write(emu, regs[0], vals[0]);
+		break;
+	}
+}
+
 static void snd_emu1010_output_source_apply(struct snd_emu10k1 *emu,
 					    int channel, int src)
 {
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned shift = emu->emu1010.clock_shift;
+	const unsigned short *regs = &emu_ri->out_regs[shift][channel << shift];
+	const unsigned short *vals = &emu_ri->src_regs[shift][src << shift];
 
-	snd_emu1010_fpga_link_dst_src_write(emu,
-		emu_ri->out_regs[channel], emu_ri->src_regs[src]);
+	snd_emu1010_source_apply(emu, shift, regs, vals);
 }
 
 static void snd_emu1010_input_source_apply(struct snd_emu10k1 *emu,
 					   int channel, int src)
 {
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned shift = emu->emu1010.clock_shift;
+	const unsigned short *regs = &emu_ri->in_regs[channel];
+	const unsigned short *vals = &emu_ri->src_regs[shift][src << shift];
 
-	snd_emu1010_fpga_link_dst_src_write(emu,
-		emu_ri->in_regs[channel], emu_ri->src_regs[src]);
+	// Only 1x capture for now
+	snd_emu1010_fpga_link_dst_src_write(emu, regs[0], vals[0]);
 }
 
-static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu)
+static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu, int active)
 {
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
-	unsigned iidx = emu->das_mode;
+	unsigned oidx = emu->emu1010.clock_shift;
+	unsigned iidx = emu->das_mode + oidx;
 
-	for (unsigned i = 0; i < emu_ri->n_outs; i++)
+	for (unsigned i = 0; i < emu_ri->n_outs[oidx]; i++)
 		snd_emu1010_output_source_apply(
-			emu, i, emu->emu1010.output_source[i]);
+			emu, i, active ? emu->emu1010.output_source[i] : 0);
 	for (unsigned i = 0; i < emu_ri->n_ins[iidx]; i++)
 		snd_emu1010_input_source_apply(
-			emu, i, emu->emu1010.input_source[i]);
+			emu, i, active ? emu->emu1010.input_source[i] : 0);
 }
 
 static u8 emu1010_map_source(const struct snd_emu1010_routing_info *emu_ri,
 			     unsigned das_mode, unsigned val)
 {
 	for (unsigned i = 0; i < emu_ri->n_srcs[das_mode]; i++)
-		if (val == emu_ri->src_regs[i])
+		if (val == emu_ri->src_regs[0][i])
 			return i;
 	return 0;
 }
 
+static const unsigned internal_sources[3] = { 16, 16, 8 };
+
+static unsigned emu1010_remap_source(const struct snd_emu1010_routing_info *emu_ri,
+				     unsigned oshift, unsigned nshift, unsigned src)
+{
+	unsigned ibase = emu_ri->n_srcs[oshift + 1] - internal_sources[oshift];
+	if (src >= ibase) {
+		int raw_src = src - ibase - internal_sources[nshift];
+		if (raw_src < 0)
+			return raw_src + emu_ri->n_srcs[nshift + 1];
+	} else {
+		unsigned reg = emu_ri->src_regs[oshift][src << oshift];
+		for (unsigned i = 0; i < emu_ri->n_srcs[nshift + 1]; i++)
+			if (reg == emu_ri->src_regs[nshift][i << nshift])
+				return i;
+	}
+	return 0;
+}
+
+static void snd_emu1010_remap_sources(struct snd_emu10k1 *emu, int oshift, int nshift)
+{
+	const struct snd_emu1010_routing_info *emu_ri =
+		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned char srcs[NUM_OUTPUT_DESTS];
+	unsigned o, n, n_dsts_o, n_dsts_n;
+
+	n_dsts_o = emu_ri->n_outs[oshift];
+	n_dsts_n = emu_ri->n_outs[nshift];
+	for (n = 0; n < n_dsts_n; n++) {
+		unsigned reg = emu_ri->out_regs[nshift][n << nshift];
+		unsigned src = 0;
+		for (o = 0; o < n_dsts_o; o++) {
+			if (emu_ri->out_regs[oshift][o << oshift] == reg) {
+				src = emu1010_remap_source(emu_ri, oshift, nshift,
+							   emu->emu1010.output_source[o]);
+				break;
+			}
+		}
+		srcs[n] = src;
+	}
+	memcpy(emu->emu1010.output_source, srcs, n_dsts_n);
+
+	n_dsts_o = emu_ri->n_ins[oshift + 1];
+	n_dsts_n = emu_ri->n_ins[nshift + 1];
+	for (n = 0; n < n_dsts_n; n++)
+		emu->emu1010.input_source[n] = (n >= n_dsts_o) ? 0 :
+			emu1010_remap_source(emu_ri, oshift, nshift,
+					     emu->emu1010.input_source[n]);
+}
+
 static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol,
 						struct snd_ctl_elem_info *uinfo)
 {
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
-	unsigned iidx = emu->das_mode;
+	unsigned iidx = emu->das_mode + emu->emu1010.clock_shift;
 
 	return snd_ctl_enum_info(uinfo, 1, emu_ri->n_srcs[iidx], emu_ri->src_texts[iidx]);
 }
 
 static int snd_emu1010_output_source_get(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
 {
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
+	unsigned oidx = emu->emu1010.clock_shift;
 	unsigned channel = kcontrol->private_value;
 
-	if (channel >= emu_ri->n_outs)
+	if (channel >= emu_ri->n_outs[oidx])
 		return -EINVAL;
 	ucontrol->value.enumerated.item[0] = emu->emu1010.output_source[channel];
 	return 0;
 }
 
 static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
 {
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
-	unsigned iidx = emu->das_mode;
+	unsigned oidx = emu->emu1010.clock_shift;
+	unsigned iidx = emu->das_mode + oidx;
 	unsigned val = ucontrol->value.enumerated.item[0];
 	unsigned channel = kcontrol->private_value;
 	int change;
 
 	if (val >= emu_ri->n_srcs[iidx])
 		return -EINVAL;
-	if (channel >= emu_ri->n_outs)
+	if (channel >= emu_ri->n_outs[oidx])
 		return -EINVAL;
 	change = (emu->emu1010.output_source[channel] != val);
 	if (change) {
@@ -734,7 +1186,7 @@ static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol,
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
-	unsigned iidx = emu->das_mode;
+	unsigned iidx = emu->das_mode + emu->emu1010.clock_shift;
 	unsigned channel = kcontrol->private_value;
 
 	if (channel >= emu_ri->n_ins[iidx])
@@ -749,7 +1201,7 @@ static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol,
 	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
-	unsigned iidx = emu->das_mode;
+	unsigned iidx = emu->das_mode + emu->emu1010.clock_shift;
 	unsigned val = ucontrol->value.enumerated.item[0];
 	unsigned channel = kcontrol->private_value;
 	int change;
@@ -778,20 +1230,32 @@ static int add_emu1010_source_mixers(struct snd_emu10k1 *emu)
 {
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
-	unsigned iidx = emu->das_mode;
+	unsigned oidx = emu->emu1010.clock_shift;
+	unsigned iidx = emu->das_mode + oidx;
 	int err;
 
 	err = add_ctls(emu, &emu1010_output_source_ctl,
-		       emu_ri->out_texts, emu_ri->n_outs);
+		       emu_ri->out_texts[oidx], emu_ri->n_outs[oidx]);
 	if (err < 0)
 		return err;
 	err = add_ctls(emu, &emu1010_input_source_ctl,
 		       iidx ? emu1010_das_input_texts :
 			      emu1010_input_texts,
 		       emu_ri->n_ins[iidx]);
 	return err;
 }
 
+static void remove_emu1010_source_mixers(struct snd_emu10k1 *emu)
+{
+	struct snd_kcontrol *kctl, *next;
+
+	list_for_each_entry_safe(kctl, next, &emu->card->controls, list) {
+		size_t nlen = strlen(kctl->id.name);
+		if (nlen > 5 && !memcmp(kctl->id.name + nlen - 5, " Enum", 5))
+			snd_ctl_remove(emu->card, kctl);
+	}
+}
+
 
 static const char * const snd_emu1010_adc_pads[] = {
 	"ADC1 14dB PAD 0202 Capture Switch",
@@ -1039,7 +1503,8 @@ static int snd_emu1010_clock_source_put(struct snd_kcontrol *kcontrol,
 	change = (emu->emu1010.clock_source != val);
 	if (change) {
 		emu->emu1010.clock_source = val;
-		emu->emu1010.wclock = emu_ci->vals[val];
+		emu->emu1010.wclock = (emu->emu1010.wclock & ~EMU_HANA_WCLOCK_SRC_MASK) |
+					emu_ci->vals[val];
 
 		snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE);
 		snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, emu->emu1010.wclock);
@@ -1109,6 +1574,68 @@ static const struct snd_kcontrol_new snd_emu1010_clock_fallback =
 	.put = snd_emu1010_clock_fallback_put
 };
 
+static int snd_emu1010_clock_shift_info(struct snd_kcontrol *kcontrol,
+					     struct snd_ctl_elem_info *uinfo)
+{
+	static const char * const texts[3] = {
+		"x1", "x2", "x4"
+	};
+
+	return snd_ctl_enum_info(uinfo, 1, 3, texts);
+}
+
+static int snd_emu1010_clock_shift_get(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+
+	ucontrol->value.enumerated.item[0] = emu->emu1010.clock_shift;
+	return 0;
+}
+
+static int snd_emu1010_clock_shift_put(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
+	unsigned int val = ucontrol->value.enumerated.item[0];
+	int change;
+
+	if (val >= 3)
+		return -EINVAL;
+	change = (emu->emu1010.clock_shift != val);
+	if (change) {
+		snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE);
+		snd_emu1010_apply_sources(emu, 0);
+
+		remove_emu1010_source_mixers(emu);
+		snd_emu1010_remap_sources(emu, emu->emu1010.clock_shift, val);
+		emu->emu1010.clock_shift = val;
+		add_emu1010_source_mixers(emu);
+
+		emu->emu1010.wclock = (emu->emu1010.wclock & ~EMU_HANA_WCLOCK_MULT_MASK) |
+					(val << 3);
+		snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, emu->emu1010.wclock);
+		msleep(10);  // Allow DLL to settle
+
+		snd_emu1010_apply_sources(emu, 1);
+		snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
+
+		snd_emu1010_update_clock(emu);
+	}
+	return change;
+}
+
+static const struct snd_kcontrol_new snd_emu1010_clock_shift =
+{
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name = "Clock Multiplier",
+	.count = 1,
+	.info = snd_emu1010_clock_shift_info,
+	.get = snd_emu1010_clock_shift_get,
+	.put = snd_emu1010_clock_shift_put
+};
+
 static int snd_emu1010_optical_out_info(struct snd_kcontrol *kcontrol,
 					  struct snd_ctl_elem_info *uinfo)
 {
@@ -2396,11 +2923,18 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu,
 		for (i = 0; i < emu_ri->n_ins[midx]; i++)
 			emu->emu1010.input_source[i] =
 				emu1010_map_source(emu_ri, midx, emu_ri->in_dflts[i]);
-		for (i = 0; i < emu_ri->n_outs; i++)
+		for (i = 0; i < emu_ri->n_outs[0]; i++)
 			emu->emu1010.output_source[i] =
 				emu1010_map_source(emu_ri, midx, emu_ri->out_dflts[i]);
-		snd_emu1010_apply_sources(emu);
+		snd_emu1010_apply_sources(emu, 1);
 
+		if (emu->das_mode) {
+			kctl = emu->ctl_clock_shift =
+					snd_ctl_new1(&snd_emu1010_clock_shift, emu);
+			err = snd_ctl_add(card, kctl);
+			if (err < 0)
+				return err;
+		}
 		err = snd_ctl_add(card,
 			snd_ctl_new1(&snd_emu1010_clock_source, emu));
 		if (err < 0)
diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
index 7aed356637ab..69552d5c9e45 100644
--- a/sound/pci/emu10k1/emupcm.c
+++ b/sound/pci/emu10k1/emupcm.c
@@ -1188,19 +1188,42 @@ static void snd_emu10k1_pcm_efx_mixer_notify(struct snd_emu10k1 *emu, int idx, i
 	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_attn, idx, activate);
 }
 
+static void snd_emu10k1_pcm_clock_mutiplier_notify(struct snd_emu10k1 *emu)
+{
+	struct snd_kcontrol *kctl = emu->ctl_clock_shift;
+	struct snd_ctl_elem_id id;
+
+	// Modifying the clock multiplier during playback/capture
+	// would make a mess, so we lock it.
+	if (emu->emu1010.clock_users) {
+		if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE))
+			return;
+		kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE;
+	} else {
+		if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE)
+			return;
+		kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
+	}
+	snd_ctl_build_ioff(&id, kctl, 0);
+	snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_INFO, &id);
+}
+
 static void snd_emu10k1_pcm_free_substream(struct snd_pcm_runtime *runtime)
 {
 	kfree(runtime->private_data);
 }
 
 static int snd_emu10k1_efx_playback_close(struct snd_pcm_substream *substream)
 {
 	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
 	struct snd_emu10k1_pcm_mixer *mix;
 	int i;
 
-	if (emu->das_mode)
+	if (emu->das_mode) {
+		emu->emu1010.clock_users--;
+		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
 		return 0;
+	}
 	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
 		mix = &emu->efx_pcm_mixer[i];
 		mix->epcm = NULL;
@@ -1254,8 +1277,11 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream)
 		return err;
 	}
 
-	if (emu->das_mode)
+	if (emu->das_mode) {
+		emu->emu1010.clock_users++;
+		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
 		return 0;
+	}
 	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
 		mix = &emu->efx_pcm_mixer[i];
 		for (j = 0; j < 8; j++)
@@ -1464,13 +1490,24 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream)
 				   &hw_constraints_capture_buffer_sizes);
 	emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt;
 	emu->pcm_capture_efx_substream = substream;
+
+	if (emu->das_mode) {
+		emu->emu1010.clock_users++;
+		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
+	}
+
 	return 0;
 }
 
 static int snd_emu10k1_capture_efx_close(struct snd_pcm_substream *substream)
 {
 	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
 
+	if (emu->das_mode) {
+		emu->emu1010.clock_users--;
+		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
+	}
+
 	emu->capture_efx_interrupt = NULL;
 	emu->pcm_capture_efx_substream = NULL;
 	return 0;
diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c
index a0d66ce3ee83..dc9c7a59e03a 100644
--- a/sound/pci/emu10k1/io.c
+++ b/sound/pci/emu10k1/io.c
@@ -404,19 +404,47 @@ void snd_emu1010_update_clock(struct snd_emu10k1 *emu)
 		clock = 48000;
 		leds = EMU_HANA_DOCK_LEDS_2_48K;
 		break;
+	case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_2X:
+		clock = 44100;
+		leds = 0;
+		break;
+	case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_2X:
+		clock = 48000;
+		leds = EMU_HANA_DOCK_LEDS_2_96K;
+		break;
+	case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_4X:
+		clock = 44100;
+		leds = 0;
+		break;
+	case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X:
+		clock = 48000;
+		leds = EMU_HANA_DOCK_LEDS_2_192K;
+		break;
 	default:
 		clock = snd_emu1010_get_raw_rate(
 				emu, emu->emu1010.wclock & EMU_HANA_WCLOCK_SRC_MASK);
 		// The raw rate reading is rather coarse (it cannot accurately
 		// represent 44.1 kHz) and fluctuates slightly. Luckily, the
 		// clock comes from digital inputs, which use standardized rates.
 		// So we round to the closest standard rate and ignore discrepancies.
 		if (clock < 46000) {
 			clock = 44100;
 			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_44K;
-		} else {
+		} else if (clock < 75000) {
 			clock = 48000;
 			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_48K;
+		} else if (clock < 92000) {
+			clock = 44100;
+			leds = EMU_HANA_DOCK_LEDS_2_EXT;
+		} else if (clock < 150000) {
+			clock = 48000;
+			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_96K;
+		} else if (clock < 184000) {
+			clock = 44100;
+			leds = EMU_HANA_DOCK_LEDS_2_EXT;
+		} else {
+			clock = 48000;
+			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_192K;
 		}
 		break;
 	}
-- 
2.40.0.152.g15d061e6df


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

* [PATCH 7/8] ALSA: emu10k1: add high-rate capture in E-MU D.A.S. mode
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
                   ` (5 preceding siblings ...)
  2023-06-13  7:38 ` [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-13  7:38 ` [PATCH 8/8] ALSA: emu10k1: add high-rate playback " Oswald Buddenhagen
  7 siblings, 0 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

This is tested only with a 0404b card, so it is unclear whether the
EMU_DST_TINA_EMU32B (1010b) & EMU_DST_TINA2_EMU32B (1616m CardBus)
register definitions (derived from comments in the same file) are
correct, and whether they actually lack the one-sample delay
relative to EMU_DST_ALICE2_EMU32_0.

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 include/sound/emu10k1.h          |   4 ++
 sound/pci/emu10k1/emu10k1_main.c |   3 +
 sound/pci/emu10k1/emufx.c        |   4 +-
 sound/pci/emu10k1/emumixer.c     | 107 +++++++++++++++++++++++++++----
 sound/pci/emu10k1/emupcm.c       |  40 +++++++++---
 5 files changed, 135 insertions(+), 23 deletions(-)

diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h
index 1f827290977f..443710e31021 100644
--- a/include/sound/emu10k1.h
+++ b/include/sound/emu10k1.h
@@ -1286,6 +1286,9 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00)  /* This sets the capture PCM
 #define EMU_DST_HAMOA_DAC_RIGHT4	0x0307	/* Hamoa DAC Right, 4th or 192kHz */
 // In S/MUX mode, the samples of one channel are adjacent.
 #define EMU_DST_HANA_ADAT	0x0400	/* Hana ADAT 8 channel out +0 to +7 */
+/* FIXME: It is not clear whether these are actually enumerated like that. */
+#define EMU_DST_TINA2_EMU32B	0x0400	/* 16 EMU32 channels to Tina2 +0 to +0xf */
+#define EMU_DST_TINA_EMU32B	0x0500	/* 16 EMU32 channels to Tina +0 to +0xf */
 #define EMU_DST_ALICE_I2S0_LEFT		0x0500	/* Alice2 I2S0 Left */
 #define EMU_DST_ALICE_I2S0_RIGHT	0x0501	/* Alice2 I2S0 Right */
 #define EMU_DST_ALICE_I2S1_LEFT		0x0600	/* Alice2 I2S1 Left */
@@ -1648,6 +1651,7 @@ struct snd_emu_chip_details {
 	unsigned int ca0108_chip:1;	/* Audigy 2 Value */
 	unsigned int ca_cardbus_chip:1;	/* Audigy 2 ZS Notebook */
 	unsigned int ca0151_chip:1;	/* P16V */
+	unsigned int emu_in_32:1;	/* EMU32 input has 32 (connected) channels */
 	unsigned int spk20:1;		/* Stereo only */
 	unsigned int spk71:1;		/* Has 7.1 speakers */
 	unsigned int no_adat:1;		/* Has no ADAT, only SPDIF */
diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
index 13e9200b8fcb..78f71df96312 100644
--- a/sound/pci/emu10k1/emu10k1_main.c
+++ b/sound/pci/emu10k1/emu10k1_main.c
@@ -1093,6 +1093,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = {
 	 .emu10k2_chip = 1,
 	 .ca0108_chip = 1,
 	 .ca_cardbus_chip = 1,
+	 .emu_in_32 = 1,
 	 .spk71 = 1 ,
 	 .emu_model = EMU_MODEL_EMU1616},
 	/* Tested by James@superbug.co.uk 4th Nov 2007. */
@@ -1105,6 +1106,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = {
 	 .id = "EMU1010",
 	 .emu10k2_chip = 1,
 	 .ca0108_chip = 1,
+	 .emu_in_32 = 1,
 	 .spk71 = 1,
 	 .emu_model = EMU_MODEL_EMU1010B}, /* EMU 1010 new revision */
 	/* Tested by Maxim Kachur <mcdebugger@duganet.ru> 17th Oct 2012. */
@@ -1119,6 +1121,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = {
 	 .id = "EMU1010",
 	 .emu10k2_chip = 1,
 	 .ca0108_chip = 1,
+	 .emu_in_32 = 1,
 	 .spk71 = 1,
 	 .emu_model = EMU_MODEL_EMU1010B}, /* EMU 1010 PCIe */
 	/* Tested by James@superbug.co.uk 8th July 2005. */
diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c
index 103cb35b39e0..62fcc1d27848 100644
--- a/sound/pci/emu10k1/emufx.c
+++ b/sound/pci/emu10k1/emufx.c
@@ -1325,17 +1325,19 @@ static int _snd_emu10k1_das_init_efx(struct snd_emu10k1 *emu)
 	gpr_map[lowword_mask] = 0x0000ffff;
 
 	if (emu->card_capabilities->ca0108_chip) {
+		int num_cap = emu->card_capabilities->emu_in_32 ? 32 : 16;
+
 		for (int z = 0; z < 16; z++) {
 			A_OP(icode, &ptr, iMAC0, A_GPR(tmp), A_C_00000000, A_FXBUS(z * 2), A_C_00010000); // >> 15
 			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp + 1), A_C_00000000, A_FXBUS(z * 2 + 1), A_C_00000002); // << 1
 			A_OP(icode, &ptr, iANDXOR, A3_EMU32OUT(z), A_GPR(tmp), A_GPR(lowword_mask), A_GPR(tmp + 1));
 		}
 
 		snd_emu10k1_audigy_dsp_convert_32_to_2x16(
 			icode, &ptr, tmp, bit_shifter16, A3_EMU32IN(0), A_EXTOUT(0));
 		// A3_EMU32IN(0) is delayed by one sample, so all other A3_EMU32IN channels
 		// need to be delayed as well; we use an auxiliary register for that.
-		for (int z = 1; z < 16; z++) {
+		for (int z = 1; z < num_cap; z++) {
 			snd_emu10k1_audigy_dsp_convert_32_to_2x16(
 				icode, &ptr, tmp, bit_shifter16, A_GPR(gpr), A_EXTOUT(z * 2));
 			A_OP(icode, &ptr, iACC3, A_GPR(gpr), A3_EMU32IN(z), A_C_00000000, A_C_00000000);
diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c
index 844ccf3b025c..387a0ad895c3 100644
--- a/sound/pci/emu10k1/emumixer.c
+++ b/sound/pci/emu10k1/emumixer.c
@@ -882,6 +882,90 @@ static const unsigned short emu1010_input_dflt[] = {
 };
 static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst));
 
+static const unsigned short emu1010_2x_input_dst[][2] = {
+	{ EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_8 },
+	{ EMU_DST_ALICE2_EMU32_1, EMU_DST_ALICE2_EMU32_9 },
+	{ EMU_DST_ALICE2_EMU32_2, EMU_DST_ALICE2_EMU32_A },
+	{ EMU_DST_ALICE2_EMU32_3, EMU_DST_ALICE2_EMU32_B },
+	{ EMU_DST_ALICE2_EMU32_4, EMU_DST_ALICE2_EMU32_C },
+	{ EMU_DST_ALICE2_EMU32_5, EMU_DST_ALICE2_EMU32_D },
+	{ EMU_DST_ALICE2_EMU32_6, EMU_DST_ALICE2_EMU32_E },
+	{ EMU_DST_ALICE2_EMU32_7, EMU_DST_ALICE2_EMU32_F },
+};
+static_assert(ARRAY_SIZE(emu1010_2x_input_dst) <= NUM_INPUT_DESTS);
+
+static const unsigned short emu1010_4x_input_dst[][4] = {
+	{ EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_4, EMU_DST_ALICE2_EMU32_8, EMU_DST_ALICE2_EMU32_C },
+	{ EMU_DST_ALICE2_EMU32_1, EMU_DST_ALICE2_EMU32_5, EMU_DST_ALICE2_EMU32_9, EMU_DST_ALICE2_EMU32_D },
+	{ EMU_DST_ALICE2_EMU32_2, EMU_DST_ALICE2_EMU32_6, EMU_DST_ALICE2_EMU32_A, EMU_DST_ALICE2_EMU32_E },
+	{ EMU_DST_ALICE2_EMU32_3, EMU_DST_ALICE2_EMU32_7, EMU_DST_ALICE2_EMU32_B, EMU_DST_ALICE2_EMU32_F },
+};
+static_assert(ARRAY_SIZE(emu1010_4x_input_dst) <= NUM_INPUT_DESTS);
+
+static const unsigned short emu1010b_2x_input_dst[][2] = {
+	{ EMU_DST_ALICE2_EMU32_0, EMU_DST_TINA_EMU32B+0x0 },
+	{ EMU_DST_ALICE2_EMU32_1, EMU_DST_TINA_EMU32B+0x1 },
+	{ EMU_DST_ALICE2_EMU32_2, EMU_DST_TINA_EMU32B+0x2 },
+	{ EMU_DST_ALICE2_EMU32_3, EMU_DST_TINA_EMU32B+0x3 },
+	{ EMU_DST_ALICE2_EMU32_4, EMU_DST_TINA_EMU32B+0x4 },
+	{ EMU_DST_ALICE2_EMU32_5, EMU_DST_TINA_EMU32B+0x5 },
+	{ EMU_DST_ALICE2_EMU32_6, EMU_DST_TINA_EMU32B+0x6 },
+	{ EMU_DST_ALICE2_EMU32_7, EMU_DST_TINA_EMU32B+0x7 },
+	{ EMU_DST_ALICE2_EMU32_8, EMU_DST_TINA_EMU32B+0x8 },
+	{ EMU_DST_ALICE2_EMU32_9, EMU_DST_TINA_EMU32B+0x9 },
+	{ EMU_DST_ALICE2_EMU32_A, EMU_DST_TINA_EMU32B+0xa },
+	{ EMU_DST_ALICE2_EMU32_B, EMU_DST_TINA_EMU32B+0xb },
+	{ EMU_DST_ALICE2_EMU32_C, EMU_DST_TINA_EMU32B+0xc },
+	{ EMU_DST_ALICE2_EMU32_D, EMU_DST_TINA_EMU32B+0xd },
+	{ EMU_DST_ALICE2_EMU32_E, EMU_DST_TINA_EMU32B+0xe },
+	{ EMU_DST_ALICE2_EMU32_F, EMU_DST_TINA_EMU32B+0xf },
+};
+static_assert(ARRAY_SIZE(emu1010b_2x_input_dst) <= NUM_INPUT_DESTS);
+
+static const unsigned short emu1010b_4x_input_dst[][4] = {
+	{ EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_8, EMU_DST_TINA_EMU32B+0x0, EMU_DST_TINA_EMU32B+0x8 },
+	{ EMU_DST_ALICE2_EMU32_1, EMU_DST_ALICE2_EMU32_9, EMU_DST_TINA_EMU32B+0x1, EMU_DST_TINA_EMU32B+0x9 },
+	{ EMU_DST_ALICE2_EMU32_2, EMU_DST_ALICE2_EMU32_A, EMU_DST_TINA_EMU32B+0x2, EMU_DST_TINA_EMU32B+0xa },
+	{ EMU_DST_ALICE2_EMU32_3, EMU_DST_ALICE2_EMU32_B, EMU_DST_TINA_EMU32B+0x3, EMU_DST_TINA_EMU32B+0xb },
+	{ EMU_DST_ALICE2_EMU32_4, EMU_DST_ALICE2_EMU32_C, EMU_DST_TINA_EMU32B+0x4, EMU_DST_TINA_EMU32B+0xc },
+	{ EMU_DST_ALICE2_EMU32_5, EMU_DST_ALICE2_EMU32_D, EMU_DST_TINA_EMU32B+0x5, EMU_DST_TINA_EMU32B+0xd },
+	{ EMU_DST_ALICE2_EMU32_6, EMU_DST_ALICE2_EMU32_E, EMU_DST_TINA_EMU32B+0x6, EMU_DST_TINA_EMU32B+0xe },
+	{ EMU_DST_ALICE2_EMU32_7, EMU_DST_ALICE2_EMU32_F, EMU_DST_TINA_EMU32B+0x7, EMU_DST_TINA_EMU32B+0xf },
+};
+static_assert(ARRAY_SIZE(emu1010b_4x_input_dst) <= NUM_INPUT_DESTS);
+
+static const unsigned short emu1616_2x_input_dst[][2] = {
+	{ EMU_DST_ALICE2_EMU32_0, EMU_DST_TINA2_EMU32B+0x0 },
+	{ EMU_DST_ALICE2_EMU32_1, EMU_DST_TINA2_EMU32B+0x1 },
+	{ EMU_DST_ALICE2_EMU32_2, EMU_DST_TINA2_EMU32B+0x2 },
+	{ EMU_DST_ALICE2_EMU32_3, EMU_DST_TINA2_EMU32B+0x3 },
+	{ EMU_DST_ALICE2_EMU32_4, EMU_DST_TINA2_EMU32B+0x4 },
+	{ EMU_DST_ALICE2_EMU32_5, EMU_DST_TINA2_EMU32B+0x5 },
+	{ EMU_DST_ALICE2_EMU32_6, EMU_DST_TINA2_EMU32B+0x6 },
+	{ EMU_DST_ALICE2_EMU32_7, EMU_DST_TINA2_EMU32B+0x7 },
+	{ EMU_DST_ALICE2_EMU32_8, EMU_DST_TINA2_EMU32B+0x8 },
+	{ EMU_DST_ALICE2_EMU32_9, EMU_DST_TINA2_EMU32B+0x9 },
+	{ EMU_DST_ALICE2_EMU32_A, EMU_DST_TINA2_EMU32B+0xa },
+	{ EMU_DST_ALICE2_EMU32_B, EMU_DST_TINA2_EMU32B+0xb },
+	{ EMU_DST_ALICE2_EMU32_C, EMU_DST_TINA2_EMU32B+0xc },
+	{ EMU_DST_ALICE2_EMU32_D, EMU_DST_TINA2_EMU32B+0xd },
+	{ EMU_DST_ALICE2_EMU32_E, EMU_DST_TINA2_EMU32B+0xe },
+	{ EMU_DST_ALICE2_EMU32_F, EMU_DST_TINA2_EMU32B+0xf },
+};
+static_assert(ARRAY_SIZE(emu1616_2x_input_dst) <= NUM_INPUT_DESTS);
+
+static const unsigned short emu1616_4x_input_dst[][4] = {
+	{ EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_8, EMU_DST_TINA2_EMU32B+0x0, EMU_DST_TINA2_EMU32B+0x8 },
+	{ EMU_DST_ALICE2_EMU32_1, EMU_DST_ALICE2_EMU32_9, EMU_DST_TINA2_EMU32B+0x1, EMU_DST_TINA2_EMU32B+0x9 },
+	{ EMU_DST_ALICE2_EMU32_2, EMU_DST_ALICE2_EMU32_A, EMU_DST_TINA2_EMU32B+0x2, EMU_DST_TINA2_EMU32B+0xa },
+	{ EMU_DST_ALICE2_EMU32_3, EMU_DST_ALICE2_EMU32_B, EMU_DST_TINA2_EMU32B+0x3, EMU_DST_TINA2_EMU32B+0xb },
+	{ EMU_DST_ALICE2_EMU32_4, EMU_DST_ALICE2_EMU32_C, EMU_DST_TINA2_EMU32B+0x4, EMU_DST_TINA2_EMU32B+0xc },
+	{ EMU_DST_ALICE2_EMU32_5, EMU_DST_ALICE2_EMU32_D, EMU_DST_TINA2_EMU32B+0x5, EMU_DST_TINA2_EMU32B+0xd },
+	{ EMU_DST_ALICE2_EMU32_6, EMU_DST_ALICE2_EMU32_E, EMU_DST_TINA2_EMU32B+0x6, EMU_DST_TINA2_EMU32B+0xe },
+	{ EMU_DST_ALICE2_EMU32_7, EMU_DST_ALICE2_EMU32_F, EMU_DST_TINA2_EMU32B+0x7, EMU_DST_TINA2_EMU32B+0xf },
+};
+static_assert(ARRAY_SIZE(emu1616_4x_input_dst) <= NUM_INPUT_DESTS);
+
 static const unsigned short emu0404_input_dflt[] = {
 	EMU_SRC_HAMOA_ADC_LEFT1,
 	EMU_SRC_HAMOA_ADC_RIGHT1,
@@ -906,7 +990,7 @@ struct snd_emu1010_routing_info {
 	const char * const *out_texts[3];
 	const unsigned short *src_regs[3];
 	const unsigned short *out_regs[3];
-	const unsigned short *in_regs;
+	const unsigned short *in_regs[3];
 	const unsigned short *out_dflts;
 	const unsigned short *in_dflts;
 	unsigned n_srcs[4];
@@ -931,8 +1015,8 @@ static const struct snd_emu1010_routing_info emu1010_routing_info[] = {
 			    ARRAY_SIZE(emu1010_2x_output_texts), ARRAY_SIZE(emu1010_4x_output_texts) },
 
 		.in_dflts = emu1010_input_dflt,
-		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst), 16, 16, 16 },
+		.in_regs = { emu1010_input_dst, emu1010_2x_input_dst[0], emu1010_4x_input_dst[0] },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst), 16, 8, 4 },
 	},
 	{
 		/* rev2 1010 */
@@ -950,8 +1034,8 @@ static const struct snd_emu1010_routing_info emu1010_routing_info[] = {
 			    ARRAY_SIZE(snd_emu1010b_2x_output_texts), ARRAY_SIZE(snd_emu1010b_4x_output_texts) },
 
 		.in_dflts = emu1010_input_dflt,
-		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
+		.in_regs = { emu1010_input_dst, emu1010b_2x_input_dst[0], emu1010b_4x_input_dst[0] },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 8 },
 	},
 	{
 		/* 1616(m) cardbus */
@@ -969,8 +1053,8 @@ static const struct snd_emu1010_routing_info emu1010_routing_info[] = {
 			    ARRAY_SIZE(snd_emu1616_2x_output_texts), ARRAY_SIZE(snd_emu1616_4x_output_texts) },
 
 		.in_dflts = emu1010_input_dflt,
-		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
+		.in_regs = { emu1010_input_dst, emu1616_2x_input_dst[0], emu1616_4x_input_dst[0] },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 8 },
 	},
 	{
 		/* 0404 */
@@ -988,8 +1072,8 @@ static const struct snd_emu1010_routing_info emu1010_routing_info[] = {
 			    ARRAY_SIZE(snd_emu0404_output_texts), ARRAY_SIZE(snd_emu0404_4x_output_texts) },
 
 		.in_dflts = emu0404_input_dflt,
-		.in_regs = emu1010_input_dst,
-		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
+		.in_regs = { emu1010_input_dst, emu1010_2x_input_dst[0], emu1010_4x_input_dst[0] },
+		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 8, 4 },
 	},
 };
 
@@ -1041,11 +1125,10 @@ static void snd_emu1010_input_source_apply(struct snd_emu10k1 *emu,
 	const struct snd_emu1010_routing_info *emu_ri =
 		&emu1010_routing_info[emu1010_idx(emu)];
 	unsigned shift = emu->emu1010.clock_shift;
-	const unsigned short *regs = &emu_ri->in_regs[channel];
+	const unsigned short *regs = &emu_ri->in_regs[shift][channel << shift];
 	const unsigned short *vals = &emu_ri->src_regs[shift][src << shift];
 
-	// Only 1x capture for now
-	snd_emu1010_fpga_link_dst_src_write(emu, regs[0], vals[0]);
+	snd_emu1010_source_apply(emu, shift, regs, vals);
 }
 
 static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu, int active)
diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
index 69552d5c9e45..037ca92dee98 100644
--- a/sound/pci/emu10k1/emupcm.c
+++ b/sound/pci/emu10k1/emupcm.c
@@ -233,6 +233,16 @@ static void snd_emu1010_constrain_efx_rate(struct snd_emu10k1 *emu,
 	runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
 }
 
+static void snd_emu1010_constrain_efx_capture_rate(struct snd_emu10k1 *emu,
+						   struct snd_pcm_runtime *runtime)
+{
+	int rate;
+
+	rate = emu->emu1010.word_clock << emu->emu1010.clock_shift;
+	runtime->hw.rate_min = runtime->hw.rate_max = rate;
+	runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
+}
+
 static unsigned int emu10k1_calc_pitch_target(unsigned int rate)
 {
 	unsigned int pitch_target;
@@ -570,8 +580,22 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream)
 		if (emu->card_capabilities->emu_model) {
 			unsigned mask = 0xffffffff >> (32 - runtime->channels * 2);
 			if (emu->das_mode) {
+				unsigned shift = emu->emu1010.clock_shift;
+				if (shift) {
+					if (emu->card_capabilities->emu_in_32) {
+						if (shift == 2)
+							mask |= mask << 16;
+						epcm->capture_cr_val2 = mask;
+					} else {
+						if (shift == 2)
+							mask |= mask << 8;
+						mask |= mask << 16;
+						epcm->capture_cr_val2 = 0;
+					}
+				} else {
+					epcm->capture_cr_val2 = 0;
+				}
 				epcm->capture_cr_val = mask;
-				epcm->capture_cr_val2 = 0;
 			} else {
 				// The upper 32 16-bit capture voices, two for each of the 16 32-bit channels.
 				// The lower voices are occupied by A_EXTOUT_*_CAP*.
@@ -1446,26 +1470,22 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream)
 	substream->runtime->private_free = snd_emu10k1_pcm_free_substream;
 	runtime->hw = snd_emu10k1_capture_efx;
 	if (emu->card_capabilities->emu_model) {
-		snd_emu1010_constrain_efx_rate(emu, runtime);
+		snd_emu1010_constrain_efx_capture_rate(emu, runtime);
 		/*
 		 * There are 32 mono channels of 16bits each.
 		 * 24bit Audio uses 2x channels over 16bit,
 		 * 96kHz uses 2x channels over 48kHz,
 		 * 192kHz uses 4x channels over 48kHz.
 		 * So, for 48kHz 24bit, one has 16 channels,
 		 * for 96kHz 24bit, one has 8 channels,
 		 * for 192kHz 24bit, one has 4 channels.
 		 * 1010rev2 and 1616(m) cards have double that,
 		 * but we don't exceed 16 channels anyway.
 		 */
-#if 0
-		/* For 96kHz */
-		runtime->hw.channels_min = runtime->hw.channels_max = 4;
-#endif
-#if 0
-		/* For 192kHz */
-		runtime->hw.channels_min = runtime->hw.channels_max = 2;
-#endif
+		if (emu->das_mode)
+			runtime->hw.channels_max =
+				min(16, 32 >> (emu->emu1010.clock_shift +
+					       !emu->card_capabilities->emu_in_32));
 		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
 	} else {
 		spin_lock_irq(&emu->reg_lock);
-- 
2.40.0.152.g15d061e6df


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

* [PATCH 8/8] ALSA: emu10k1: add high-rate playback in E-MU D.A.S. mode
  2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
                   ` (6 preceding siblings ...)
  2023-06-13  7:38 ` [PATCH 7/8] ALSA: emu10k1: add high-rate capture " Oswald Buddenhagen
@ 2023-06-13  7:38 ` Oswald Buddenhagen
  2023-06-22  7:05   ` kernel test robot
  7 siblings, 1 reply; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13  7:38 UTC (permalink / raw)
  To: alsa-devel; +Cc: Takashi Iwai, Jaroslav Kysela

This mode does not offer mmapped I/O, as we need to copy the buffer
anyway to reshuffle it.

A limitation is that we'll refuse writes which aren't a multiple of 2/4
frames, but that's unlikely to be significant. But if one really wanted
to make it work, this could be done either locally, or by reviving (and
fixing) snd_pcm_sw_params.xfer_align, which was obsoleted in commit
d948035a92 ("Remove PCM xfer_align sw params").

Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
---
 sound/pci/emu10k1/emufx.c    |   7 +-
 sound/pci/emu10k1/emumixer.c |  59 ++++----
 sound/pci/emu10k1/emupcm.c   | 257 ++++++++++++++++++++++++++++-------
 sound/pci/emu10k1/voice.c    |   6 +
 4 files changed, 247 insertions(+), 82 deletions(-)

diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c
index 62fcc1d27848..bc669f35f643 100644
--- a/sound/pci/emu10k1/emufx.c
+++ b/sound/pci/emu10k1/emufx.c
@@ -1327,7 +1327,7 @@ static int _snd_emu10k1_das_init_efx(struct snd_emu10k1 *emu)
 	if (emu->card_capabilities->ca0108_chip) {
 		int num_cap = emu->card_capabilities->emu_in_32 ? 32 : 16;
 
-		for (int z = 0; z < 16; z++) {
+		for (int z = 0; z < 32; z++) {
 			A_OP(icode, &ptr, iMAC0, A_GPR(tmp), A_C_00000000, A_FXBUS(z * 2), A_C_00010000); // >> 15
 			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp + 1), A_C_00000000, A_FXBUS(z * 2 + 1), A_C_00000002); // << 1
 			A_OP(icode, &ptr, iANDXOR, A3_EMU32OUT(z), A_GPR(tmp), A_GPR(lowword_mask), A_GPR(tmp + 1));
@@ -1349,6 +1349,11 @@ static int _snd_emu10k1_das_init_efx(struct snd_emu10k1 *emu)
 			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp + 1), A_C_00000000, A_FXBUS(z * 2 + 1), A_C_00000002); // << 1
 			A_OP(icode, &ptr, iANDXOR, A_EMU32OUTL(z), A_GPR(tmp), A_GPR(lowword_mask), A_GPR(tmp + 1));
 		}
+		for (int z = 0; z < 16; z++) {
+			A_OP(icode, &ptr, iMAC0, A_GPR(tmp), A_C_00000000, A_FXBUS(z * 2 + 32), A_C_00010000); // >> 15
+			A_OP(icode, &ptr, iMACINT0, A_GPR(tmp + 1), A_C_00000000, A_FXBUS(z * 2 + 33), A_C_00000002); // << 1
+			A_OP(icode, &ptr, iANDXOR, A_EMU32OUTH(z), A_GPR(tmp), A_GPR(lowword_mask), A_GPR(tmp + 1));
+		}
 
 		/* Note that the Alice2 DSPs have 6 I2S inputs which we don't use. */
 		snd_emu10k1_audigy_dsp_convert_32_to_2x16(
diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c
index 387a0ad895c3..63f7f702c8a2 100644
--- a/sound/pci/emu10k1/emumixer.c
+++ b/sound/pci/emu10k1/emumixer.c
@@ -126,14 +126,15 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
 	"DSP 16", "DSP 17", "DSP 18", "DSP 19", "DSP 20", "DSP 21", "DSP 22", "DSP 23", \
 	"DSP 24", "DSP 25", "DSP 26", "DSP 27", "DSP 28", "DSP 29", "DSP 30", "DSP 31"
 
-#define PB_TEXTS \
+#define PB_4x_TEXTS \
 	"PbChn 00", "PbChn 01", "PbChn 02", "PbChn 03", \
-	"PbChn 04", "PbChn 05", "PbChn 06", "PbChn 07", \
+	"PbChn 04", "PbChn 05", "PbChn 06", "PbChn 07"
+
+#define PB_TEXTS \
+	PB_4x_TEXTS, \
 	"PbChn 08", "PbChn 09", "PbChn 10", "PbChn 11", \
 	"PbChn 12", "PbChn 13", "PbChn 14", "PbChn 15"
 
-#define PB_4x_TEXTS PB_TEXTS  // Only 1x playback for now
-
 #define PAIR_TEXTS(base, one, two) PAIR_PS(base, one, two, "")
 #define LR_TEXTS(base) LR_PS(base, "")
 #define ADAT_TEXTS(pfx) ADAT_PS(pfx, "")
@@ -177,26 +178,33 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
 	EMU_SRC_ALICE_EMU32B+0xe, \
 	EMU_SRC_ALICE_EMU32B+0xf
 
-// Only 1x playback for now
 #define EMU32_2x_SRC_REGS \
-	{ EMU_SRC_ALICE_EMU32A }, \
-	{ EMU_SRC_ALICE_EMU32A+1 }, \
-	{ EMU_SRC_ALICE_EMU32A+2 }, \
-	{ EMU_SRC_ALICE_EMU32A+3 }, \
-	{ EMU_SRC_ALICE_EMU32A+4 }, \
-	{ EMU_SRC_ALICE_EMU32A+5 }, \
-	{ EMU_SRC_ALICE_EMU32A+6 }, \
-	{ EMU_SRC_ALICE_EMU32A+7 }, \
-	{ EMU_SRC_ALICE_EMU32A+8 }, \
-	{ EMU_SRC_ALICE_EMU32A+9 }, \
-	{ EMU_SRC_ALICE_EMU32A+0xa }, \
-	{ EMU_SRC_ALICE_EMU32A+0xb }, \
-	{ EMU_SRC_ALICE_EMU32A+0xc }, \
-	{ EMU_SRC_ALICE_EMU32A+0xd }, \
-	{ EMU_SRC_ALICE_EMU32A+0xe }, \
-	{ EMU_SRC_ALICE_EMU32A+0xf }
+	{ EMU_SRC_ALICE_EMU32A+0x0, EMU_SRC_ALICE_EMU32A+0x1 }, \
+	{ EMU_SRC_ALICE_EMU32A+0x2, EMU_SRC_ALICE_EMU32A+0x3 }, \
+	{ EMU_SRC_ALICE_EMU32A+0x4, EMU_SRC_ALICE_EMU32A+0x5 }, \
+	{ EMU_SRC_ALICE_EMU32A+0x6, EMU_SRC_ALICE_EMU32A+0x7 }, \
+	{ EMU_SRC_ALICE_EMU32A+0x8, EMU_SRC_ALICE_EMU32A+0x9 }, \
+	{ EMU_SRC_ALICE_EMU32A+0xa, EMU_SRC_ALICE_EMU32A+0xb }, \
+	{ EMU_SRC_ALICE_EMU32A+0xc, EMU_SRC_ALICE_EMU32A+0xd }, \
+	{ EMU_SRC_ALICE_EMU32A+0xe, EMU_SRC_ALICE_EMU32A+0xf }, \
+	{ EMU_SRC_ALICE_EMU32B+0x0, EMU_SRC_ALICE_EMU32B+0x1 }, \
+	{ EMU_SRC_ALICE_EMU32B+0x2, EMU_SRC_ALICE_EMU32B+0x3 }, \
+	{ EMU_SRC_ALICE_EMU32B+0x4, EMU_SRC_ALICE_EMU32B+0x5 }, \
+	{ EMU_SRC_ALICE_EMU32B+0x6, EMU_SRC_ALICE_EMU32B+0x7 }, \
+	{ EMU_SRC_ALICE_EMU32B+0x8, EMU_SRC_ALICE_EMU32B+0x9 }, \
+	{ EMU_SRC_ALICE_EMU32B+0xa, EMU_SRC_ALICE_EMU32B+0xb }, \
+	{ EMU_SRC_ALICE_EMU32B+0xc, EMU_SRC_ALICE_EMU32B+0xd }, \
+	{ EMU_SRC_ALICE_EMU32B+0xe, EMU_SRC_ALICE_EMU32B+0xf }
 
-#define EMU32_4x_SRC_REGS EMU32_2x_SRC_REGS
+#define EMU32_4x_SRC_REGS \
+	{ EMU_SRC_ALICE_EMU32A+0x0, EMU_SRC_ALICE_EMU32A+0x1, EMU_SRC_ALICE_EMU32A+0x2, EMU_SRC_ALICE_EMU32A+0x3 }, \
+	{ EMU_SRC_ALICE_EMU32A+0x4, EMU_SRC_ALICE_EMU32A+0x5, EMU_SRC_ALICE_EMU32A+0x6, EMU_SRC_ALICE_EMU32A+0x7 }, \
+	{ EMU_SRC_ALICE_EMU32A+0x8, EMU_SRC_ALICE_EMU32A+0x9, EMU_SRC_ALICE_EMU32A+0xa, EMU_SRC_ALICE_EMU32A+0xb }, \
+	{ EMU_SRC_ALICE_EMU32A+0xc, EMU_SRC_ALICE_EMU32A+0xd, EMU_SRC_ALICE_EMU32A+0xe, EMU_SRC_ALICE_EMU32A+0xf }, \
+	{ EMU_SRC_ALICE_EMU32B+0x0, EMU_SRC_ALICE_EMU32B+0x1, EMU_SRC_ALICE_EMU32B+0x2, EMU_SRC_ALICE_EMU32B+0x3 }, \
+	{ EMU_SRC_ALICE_EMU32B+0x4, EMU_SRC_ALICE_EMU32B+0x5, EMU_SRC_ALICE_EMU32B+0x6, EMU_SRC_ALICE_EMU32B+0x7 }, \
+	{ EMU_SRC_ALICE_EMU32B+0x8, EMU_SRC_ALICE_EMU32B+0x9, EMU_SRC_ALICE_EMU32B+0xa, EMU_SRC_ALICE_EMU32B+0xb }, \
+	{ EMU_SRC_ALICE_EMU32B+0xc, EMU_SRC_ALICE_EMU32B+0xd, EMU_SRC_ALICE_EMU32B+0xe, EMU_SRC_ALICE_EMU32B+0xf }
 
 /* 1010 rev1 */
 
@@ -1086,13 +1094,6 @@ static void snd_emu1010_source_apply(struct snd_emu10k1 *emu, unsigned shift,
 				     const unsigned short *regs,
 				     const unsigned short *vals)
 {
-	unsigned short avals[4];
-
-	if ((vals[0] & 0x700) == 0x300) {  // EMU32x
-		// Only 1x playback for now
-		avals[0] = avals[1] = avals[2] = avals[3] = vals[0];
-		vals = avals;
-	}
 	switch (shift) {
 	case 2:
 		snd_emu1010_fpga_link_dst_src_write(emu, regs[3], vals[3]);
diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
index 037ca92dee98..90dad6f4fd70 100644
--- a/sound/pci/emu10k1/emupcm.c
+++ b/sound/pci/emu10k1/emupcm.c
@@ -228,16 +228,6 @@ static void snd_emu1010_constrain_efx_rate(struct snd_emu10k1 *emu,
 {
 	int rate;
 
-	rate = emu->emu1010.word_clock;
-	runtime->hw.rate_min = runtime->hw.rate_max = rate;
-	runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
-}
-
-static void snd_emu1010_constrain_efx_capture_rate(struct snd_emu10k1 *emu,
-						   struct snd_pcm_runtime *runtime)
-{
-	int rate;
-
 	rate = emu->emu1010.word_clock << emu->emu1010.clock_shift;
 	runtime->hw.rate_min = runtime->hw.rate_max = rate;
 	runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate);
@@ -419,7 +409,7 @@ static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream,
 	} else {
 		type = EMU10K1_EFX;
 		channels = params_channels(hw_params);
-		count = 1 + emu->das_mode;
+		count = (1 + emu->das_mode) << emu->emu1010.clock_shift;
 	}
 	err = snd_emu10k1_pcm_channel_alloc(epcm, type, count, channels);
 	if (err < 0)
@@ -508,28 +498,32 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream)
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct snd_emu10k1_pcm *epcm = runtime->private_data;
 	bool das_mode = emu->das_mode;
+	unsigned int shift = emu->emu1010.clock_shift;
 	unsigned int start_addr;
 	unsigned int extra_size, channel_size;
-	unsigned int i;
+	unsigned int i, j;
 
 	epcm->pitch_target = PITCH_48000;
 
 	start_addr = epcm->start_addr >> 1;  // 16-bit voices
 
-	extra_size = runtime->period_size;
-	channel_size = runtime->buffer_size;
+	extra_size = runtime->period_size >> shift;
+	channel_size = runtime->buffer_size >> shift;
 
 	snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, true,
 					 start_addr, start_addr + extra_size);
 
 	if (das_mode) {
+		unsigned count = 1 << shift;
 		start_addr >>= 1;
 		epcm->ccca_start_addr = start_addr;
 		for (i = 0; i < runtime->channels; i++) {
-			snd_emu10k1_pcm_init_das_voices(emu, epcm->voices[i],
-							start_addr, start_addr + channel_size,
-							i * 2);
-			start_addr += channel_size;
+			for (j = 0; j < count; j++) {
+				snd_emu10k1_pcm_init_das_voices(emu, epcm->voices[i] + j * 2,
+								start_addr, start_addr + channel_size,
+								(i * count + j) * 2);
+				start_addr += channel_size;
+			}
 		}
 	} else {
 		epcm->ccca_start_addr = start_addr;
@@ -669,26 +663,29 @@ static void snd_emu10k1_playback_fill_cache(struct snd_emu10k1 *emu,
 static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu,
 						struct snd_emu10k1_pcm *epcm,
 						bool w_16, bool stereo,
-						int channels)
+						int shift, int channels)
 {
 	struct snd_pcm_substream *substream = epcm->substream;
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	unsigned eloop_start = epcm->start_addr >> w_16;
 	unsigned loop_start = eloop_start >> stereo;
-	unsigned eloop_size = runtime->period_size;
-	unsigned loop_size = runtime->buffer_size;
+	unsigned eloop_size = runtime->period_size >> shift;
+	unsigned loop_size = runtime->buffer_size >> shift;
 	u32 sample = w_16 ? 0 : 0x80808080;
+	int count = 1 << shift;
 
 	// To make the playback actually start at the 1st frame,
 	// we need to compensate for two circumstances:
 	// - The actual position is delayed by the cache size (64 frames)
 	// - The interpolator is centered around the 4th frame
 	loop_start += (epcm->resume_pos + 64 - 3) % loop_size;
 	for (int i = 0; i < channels; i++) {
 		unsigned voice = epcm->voices[i]->number;
-		snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, voice, loop_start);
-		loop_start += loop_size;
-		snd_emu10k1_playback_fill_cache(emu, voice, sample, stereo);
+		for (int j = 0; j < count; j++, voice += 2) {
+			snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, voice, loop_start);
+			loop_start += loop_size;
+			snd_emu10k1_playback_fill_cache(emu, voice, sample, stereo);
+		}
 	}
 
 	// The interrupt is triggered when CCCA_CURRADDR (CA) wraps around,
@@ -831,7 +828,7 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream,
 	spin_lock(&emu->reg_lock);
 	switch (cmd) {
 	case SNDRV_PCM_TRIGGER_START:
-		snd_emu10k1_playback_prepare_voices(emu, epcm, w_16, stereo, 1);
+		snd_emu10k1_playback_prepare_voices(emu, epcm, w_16, stereo, 0, 1);
 		fallthrough;
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 	case SNDRV_PCM_TRIGGER_RESUME:
@@ -935,6 +932,7 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream *
 	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	int shift = emu->emu1010.clock_shift;
 	int ptr;
 
 	if (!epcm->running)
@@ -954,42 +952,45 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream *
 	//
 	ptr -= 64 - 3;
 	if (ptr < 0)
-		ptr += runtime->buffer_size;
+		ptr += runtime->buffer_size >> shift;
 
 	/*
 	dev_dbg(emu->card->dev,
 	       "ptr = 0x%lx, buffer_size = 0x%lx, period_size = 0x%lx\n",
 	       (long)ptr, (long)runtime->buffer_size,
 	       (long)runtime->period_size);
 	*/
-	return ptr;
+
+	return ptr << shift;
 }
 
 static u64 snd_emu10k1_efx_playback_voice_mask(struct snd_emu10k1_pcm *epcm,
-					       bool stereo, int channels)
+					       bool stereo, int count, int channels)
 {
 	u64 mask = 0;
-	u64 mask0 = (1 << (1 << stereo)) - 1;
+	u64 mask0 = (1 << (count << stereo)) - 1;
 
 	for (int i = 0; i < channels; i++) {
 		int voice = epcm->voices[i]->number;
 		mask |= mask0 << voice;
 	}
 	return mask;
 }
 
 static void snd_emu10k1_efx_playback_freeze_voices(struct snd_emu10k1 *emu,
 						   struct snd_emu10k1_pcm *epcm,
-						   bool stereo, int channels)
+						   bool stereo, int count, int channels)
 {
 	for (int i = 0; i < channels; i++) {
 		int voice = epcm->voices[i]->number;
-		snd_emu10k1_ptr_write(emu, CPF_STOP, voice, 1);
-		if (stereo) {
-			// Weirdly enough, the stereo slave needs to be stopped separately
-			snd_emu10k1_ptr_write(emu, CPF_STOP, voice + 1, 1);
+		for (int j = 0; j < count; j++, voice += 2) {
+			snd_emu10k1_ptr_write(emu, CPF_STOP, voice, 1);
+			if (stereo) {
+				// Weirdly enough, the stereo slave needs to be stopped separately
+				snd_emu10k1_ptr_write(emu, CPF_STOP, voice + 1, 1);
+			}
+			snd_emu10k1_playback_commit_pitch(emu, voice, PITCH_48000 << 16);
 		}
-		snd_emu10k1_playback_commit_pitch(emu, voice, PITCH_48000 << 16);
 	}
 }
 
@@ -1004,57 +1005,63 @@ static void snd_emu10k1_efx_playback_unmute_voices(struct snd_emu10k1 *emu,
 
 static void snd_emu10k1_efx_playback_unmute_das_voices(struct snd_emu10k1 *emu,
 						       struct snd_emu10k1_pcm *epcm,
-						       int channels)
+						       int count, int channels)
 {
 	for (int i = 0; i < channels; i++)
-		snd_emu10k1_playback_unmute_das_voices(emu, epcm->voices[i]);
+		for (int j = 0; j < count; j++)
+			snd_emu10k1_playback_unmute_das_voices(emu, epcm->voices[i] + j * 2);
 }
 
 static void snd_emu10k1_efx_playback_stop_voices(struct snd_emu10k1 *emu,
 						 struct snd_emu10k1_pcm *epcm,
-						 bool stereo, int channels)
+						 bool stereo, int count, int channels)
 {
 	for (int i = 0; i < channels; i++)
-		snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]);
+		for (int j = 0; j < count; j++)
+			snd_emu10k1_playback_stop_voice(emu, epcm->voices[i] + j * 2);
 	snd_emu10k1_playback_set_stopped(emu, epcm);
 
 	for (int i = 0; i < channels; i++)
-		snd_emu10k1_playback_mute_voices(emu, epcm->voices[i], stereo);
+		for (int j = 0; j < count; j++)
+			snd_emu10k1_playback_mute_voices(
+						emu, epcm->voices[i] + j * 2, stereo);
 }
 
 static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 				        int cmd)
 {
 	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct snd_emu10k1_pcm *epcm = runtime->private_data;
+	unsigned shift = emu->emu1010.clock_shift;
+	unsigned count = 1U << shift;
 	bool das_mode = emu->das_mode;
 	u64 mask;
 	int result = 0;
 
 	spin_lock(&emu->reg_lock);
 	switch (cmd) {
 	case SNDRV_PCM_TRIGGER_START:
 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
 	case SNDRV_PCM_TRIGGER_RESUME:
 		mask = snd_emu10k1_efx_playback_voice_mask(
-				epcm, das_mode, runtime->channels);
+				epcm, das_mode, count, runtime->channels);
 		for (int i = 0; i < 10; i++) {
 			// Note that the freeze is not interruptible, so we make no
 			// effort to reset the bits outside the error handling here.
 			snd_emu10k1_voice_set_loop_stop_multiple(emu, mask);
 			snd_emu10k1_efx_playback_freeze_voices(
-					emu, epcm, das_mode, runtime->channels);
+					emu, epcm, das_mode, count, runtime->channels);
 			snd_emu10k1_playback_prepare_voices(
-					emu, epcm, true, das_mode, runtime->channels);
+					emu, epcm, true, das_mode, shift, runtime->channels);
 
 			// It might seem to make more sense to unmute the voices only after
 			// they have been started, to potentially avoid torturing the speakers
 			// if something goes wrong. However, we cannot unmute atomically,
 			// which means that we'd get some mild artifacts in the regular case.
 			if (das_mode)
 				snd_emu10k1_efx_playback_unmute_das_voices(
-						emu, epcm, runtime->channels);
+						emu, epcm, count, runtime->channels);
 			else
 				snd_emu10k1_efx_playback_unmute_voices(
 						emu, epcm, runtime->channels);
@@ -1068,7 +1075,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 			}
 
 			snd_emu10k1_efx_playback_stop_voices(
-					emu, epcm, das_mode, runtime->channels);
+					emu, epcm, das_mode, count, runtime->channels);
 
 			if (result != -EAGAIN)
 				break;
@@ -1081,7 +1088,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 		snd_emu10k1_playback_stop_voice(emu, epcm->extra);
 		snd_emu10k1_efx_playback_stop_voices(
-				emu, epcm, das_mode, runtime->channels);
+				emu, epcm, das_mode, count, runtime->channels);
 
 		epcm->resume_pos = snd_emu10k1_playback_pointer(substream);
 		break;
@@ -1094,6 +1101,129 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream,
 	return result;
 }
 
+static void *get_dma_ptr(struct snd_pcm_runtime *runtime,
+			 int channel, unsigned long hwoff)
+{
+	return runtime->dma_area + hwoff +
+		channel * (runtime->dma_bytes / runtime->channels);
+}
+
+static void *get_dma_ptr_x(struct snd_pcm_runtime *runtime,
+			   int shift, int channel, int subch, unsigned long hwoff)
+{
+	return runtime->dma_area + hwoff +
+		((channel << shift) + subch) *
+			(runtime->dma_bytes / (runtime->channels << shift));
+}
+
+static int snd_emu10k1_efx_playback_silence(struct snd_pcm_substream *substream,
+					    int channel, unsigned long hwoff,
+					    unsigned long bytes)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned shift = emu->emu1010.clock_shift;
+	unsigned i, j, channels, subchans, voices;
+
+	if (!shift) {
+		// Non-interleaved buffer is assumed
+		memset(get_dma_ptr(runtime, channel, hwoff), 0, bytes);
+	} else {
+		// Interleaved buffer is assumed, which isn't actually the case
+		channels = runtime->channels;
+		subchans = 1 << shift;
+		voices = channels << shift;
+		hwoff /= voices;
+		if (bytes % (voices << 2))  // See *_copy_user() below.
+			return -EIO;
+		bytes /= voices;
+		for (i = 0; i < channels; i++)
+			for (j = 0; j < subchans; j++)
+				memset(get_dma_ptr_x(runtime, shift, i, j, hwoff), 0, bytes);
+	}
+	return 0;
+}
+
+static int snd_emu10k1_efx_playback_copy_user(struct snd_pcm_substream *substream,
+					      int channel, unsigned long hwoff,
+					      void __user *buf, unsigned long bytes)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned shift = emu->emu1010.clock_shift;
+	unsigned i, j, k, channels, subchans, voices, frame_size, frames;
+
+	if (!shift) {
+		// Non-interleaved source
+		if (copy_from_user(get_dma_ptr(runtime, channel, hwoff), buf, bytes))
+			return -EFAULT;
+	} else {
+		// Interleaved source
+		channels = runtime->channels;
+		subchans = 1 << shift;
+		voices = channels << shift;
+		frame_size = voices << 2;
+		// It is recommended that writes are period-sized, and it appears
+		// unlikely that someone would actually use a period size which
+		// is not divisible by four, so don't bother making it work.
+		// This check should also prevent that hwoff becomes unaligned.
+		// Ideally, snd_pcm_sw_params.xfer_align would handle this ...
+		if (bytes % frame_size)
+			return -EIO;
+		frames = bytes / frame_size;
+		hwoff /= voices;
+		if (!user_access_begin(buf, bytes))
+			return -EFAULT;
+		for (i = 0; i < channels; i++) {
+			for (j = 0; j < subchans; j++) {
+				u32 *dst = get_dma_ptr_x(runtime, shift, i, j, hwoff);
+				u32 *src = (u32 *)buf + j * channels + i;
+				for (k = 0; k < frames; k++, dst++, src += voices)
+					unsafe_get_user(*dst, src, faulted);
+			}
+		}
+		user_access_end();
+	}
+	return 0;
+
+faulted:
+	user_access_end();
+	return -EFAULT;
+}
+
+static int snd_emu10k1_efx_playback_copy_kernel(struct snd_pcm_substream *substream,
+						int channel, unsigned long hwoff,
+						void *buf, unsigned long bytes)
+{
+	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned shift = emu->emu1010.clock_shift;
+	unsigned i, j, k, channels, subchans, voices, frame_size, frames;
+
+	if (!shift) {
+		// Non-interleaved source
+		memcpy(get_dma_ptr(runtime, channel, hwoff), buf, bytes);
+	} else {
+		// Interleaved source
+		channels = runtime->channels;
+		subchans = 1 << shift;
+		voices = channels << shift;
+		frame_size = voices << 2;
+		if (bytes % frame_size)  // See *_copy_user() above.
+			return -EIO;
+		frames = bytes / frame_size;
+		hwoff /= voices;
+		for (i = 0; i < channels; i++) {
+			for (j = 0; j < subchans; j++) {
+				u32 *dst = get_dma_ptr_x(runtime, shift, i, j, hwoff);
+				u32 *src = (u32 *)buf + j * channels + i;
+				for (k = 0; k < frames; k++, dst++, src += voices)
+					*dst = *src;
+			}
+		}
+	}
+	return 0;
+}
 
 static snd_pcm_uframes_t snd_emu10k1_capture_pointer(struct snd_pcm_substream *substream)
 {
@@ -1256,19 +1386,21 @@ static int snd_emu10k1_efx_playback_close(struct snd_pcm_substream *substream)
 	return 0;
 }
 
-static int snd_emu10k1_playback_set_constraints(struct snd_pcm_runtime *runtime)
+static int snd_emu10k1_playback_set_constraints(struct snd_emu10k1 *emu,
+						struct snd_pcm_runtime *runtime)
 {
 	int err;
 
 	// The buffer size must be a multiple of the period size, to avoid a
 	// mismatch between the extra voice and the regular voices.
 	err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 	if (err < 0)
 		return err;
 	// The hardware is typically the cache's size of 64 frames ahead.
 	// Leave enough time for actually filling up the buffer.
 	err = snd_pcm_hw_constraint_minmax(
-			runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 128, UINT_MAX);
+			runtime, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+			128 << emu->emu1010.clock_shift, UINT_MAX);
 	return err;
 }
 
@@ -1292,10 +1424,28 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream)
 	runtime->hw = snd_emu10k1_efx_playback;
 	if (emu->card_capabilities->emu_model) {
 		snd_emu1010_constrain_efx_rate(emu, runtime);
-		if (emu->das_mode)
+		if (emu->das_mode) {
+			unsigned shift = emu->emu1010.clock_shift;
+			if (shift) {
+				runtime->hw.info =
+					// No SNDRV_PCM_INFO_MMAP; doable without SNDRV_PCM_INFO_MMAP_VALID
+					SNDRV_PCM_INFO_INTERLEAVED |  // Unlike in 1x mode
+					SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					SNDRV_PCM_INFO_RESUME |
+					SNDRV_PCM_INFO_PAUSE;
+				if (shift == 2)
+					runtime->hw.channels_max = 7;  // FIXME: should be 8, but extra voice ...
+				err = snd_pcm_hw_constraint_step(
+						runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 1 << shift);
+				if (err < 0) {
+					kfree(epcm);
+					return err;
+				}
+			}
 			runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+		}
 	}
-	err = snd_emu10k1_playback_set_constraints(runtime);
+	err = snd_emu10k1_playback_set_constraints(emu, runtime);
 	if (err < 0) {
 		kfree(epcm);
 		return err;
@@ -1336,7 +1486,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream)
 	runtime->private_data = epcm;
 	runtime->private_free = snd_emu10k1_pcm_free_substream;
 	runtime->hw = snd_emu10k1_playback;
-	err = snd_emu10k1_playback_set_constraints(runtime);
+	err = snd_emu10k1_playback_set_constraints(emu, runtime);
 	if (err < 0) {
 		kfree(epcm);
 		return err;
@@ -1470,7 +1620,7 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream)
 	substream->runtime->private_free = snd_emu10k1_pcm_free_substream;
 	runtime->hw = snd_emu10k1_capture_efx;
 	if (emu->card_capabilities->emu_model) {
-		snd_emu1010_constrain_efx_capture_rate(emu, runtime);
+		snd_emu1010_constrain_efx_rate(emu, runtime);
 		/*
 		 * There are 32 mono channels of 16bits each.
 		 * 24bit Audio uses 2x channels over 16bit,
@@ -1560,6 +1710,9 @@ static const struct snd_pcm_ops snd_emu10k1_efx_playback_ops = {
 	.prepare =		snd_emu10k1_efx_playback_prepare,
 	.trigger =		snd_emu10k1_efx_playback_trigger,
 	.pointer =		snd_emu10k1_playback_pointer,
+	.copy_user =		snd_emu10k1_efx_playback_copy_user,
+	.copy_kernel =		snd_emu10k1_efx_playback_copy_kernel,
+	.fill_silence =		snd_emu10k1_efx_playback_silence,
 };
 
 int snd_emu10k1_pcm(struct snd_emu10k1 *emu, int device)
diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c
index 6939498e26f0..7545915a033f 100644
--- a/sound/pci/emu10k1/voice.c
+++ b/sound/pci/emu10k1/voice.c
@@ -45,6 +45,12 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number,
 			continue;
 		}
 
+		// The voices must be consecutive without wrap-around
+		if (i + number > NUM_G) {
+			skip = NUM_G - i;
+			continue;
+		}
+
 		for (k = 0; k < number; k++) {
 			voice = &emu->voices[i + k];
 			if (voice->use) {
-- 
2.40.0.152.g15d061e6df


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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13  7:38 ` [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode Oswald Buddenhagen
@ 2023-06-13  9:20   ` Takashi Iwai
  2023-06-13 10:52     ` Oswald Buddenhagen
  0 siblings, 1 reply; 22+ messages in thread
From: Takashi Iwai @ 2023-06-13  9:20 UTC (permalink / raw)
  To: Oswald Buddenhagen; +Cc: alsa-devel, Jaroslav Kysela

On Tue, 13 Jun 2023 09:38:20 +0200,
Oswald Buddenhagen wrote:
> 
> This lays the groundwork for supporting 88.2/96/176.4/192 kHz rates
> without actually doing so yet - we simply multi-feed the same samples
> on playback, and throw away the excess ones on capture. Input-to-output
> monitoring does actually use the full sample rate, though.
> 
> Notably, add_ctls() now uses snd_ctl_add_locked(), so it doesn't
> deadlock when called from snd_emu1010_clock_shift_put(). This also
> affects the initial creation of the controls, which is OK, as that is
> done before the card is registered, so no concurrent access can occur.

Creating and removing the controls from kctl put callback is no good
idea.  In general, dynamic control creation/deletion already confuses
user-space.  On top of that, if it's done by a control element, it can
be even triggered endlessly by user.

A safer approach would be to create controls statically, and set
active flag dynamically, I suppose.

And, if we really have to create / delete a kctl element from some
kctl action, don't do it in the callback but process in another work.


Takashi


> Signed-off-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
> ---
>  include/sound/emu10k1.h          |   3 +
>  sound/pci/emu10k1/emu10k1_main.c |   2 +-
>  sound/pci/emu10k1/emumixer.c     | 648 ++++++++++++++++++++++++++++---
>  sound/pci/emu10k1/emupcm.c       |  41 +-
>  sound/pci/emu10k1/io.c           |  30 +-
>  5 files changed, 663 insertions(+), 61 deletions(-)
> 
> diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h
> index cad5faa01c4c..1f827290977f 100644
> --- a/include/sound/emu10k1.h
> +++ b/include/sound/emu10k1.h
> @@ -1676,6 +1676,8 @@ struct snd_emu1010 {
>  	unsigned int word_clock;  /* Cached effective value */
>  	unsigned int clock_source;
>  	unsigned int clock_fallback;
> +	unsigned int clock_shift;  /* EMU_HANA_WCLOCK_MULT_MASK >> 3 */
> +	unsigned int clock_users;
>  	unsigned int optical_in; /* 0:SPDIF, 1:ADAT */
>  	unsigned int optical_out; /* 0:SPDIF, 1:ADAT */
>  	struct delayed_work firmware_work;
> @@ -1756,6 +1758,7 @@ struct snd_emu10k1 {
>  	struct snd_kcontrol *ctl_efx_send_routing;
>  	struct snd_kcontrol *ctl_efx_send_volume;
>  	struct snd_kcontrol *ctl_efx_attn;
> +	struct snd_kcontrol *ctl_clock_shift;
>  
>  	void (*hwvol_interrupt)(struct snd_emu10k1 *emu, unsigned int status);
>  	void (*capture_interrupt)(struct snd_emu10k1 *emu, unsigned int status);
> diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c
> index aa28a7524a67..13e9200b8fcb 100644
> --- a/sound/pci/emu10k1/emu10k1_main.c
> +++ b/sound/pci/emu10k1/emu10k1_main.c
> @@ -902,12 +902,12 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu)
>  
>  	emu->emu1010.clock_source = 1;  /* 48000 */
>  	emu->emu1010.clock_fallback = 1;  /* 48000 */
> +	emu->emu1010.clock_shift = 0;  /* 1x */
>  	/* Default WCLK set to 48kHz. */
>  	snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K);
>  	/* Word Clock source, Internal 48kHz x1 */
>  	emu->emu1010.wclock = EMU_HANA_WCLOCK_INT_48K;
>  	snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K);
> -	/* snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X); */
>  	snd_emu1010_update_clock(emu);
>  
>  	// The routes are all set to EMU_SRC_SILENCE due to the reset,
> diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c
> index 8878b660ba94..844ccf3b025c 100644
> --- a/sound/pci/emu10k1/emumixer.c
> +++ b/sound/pci/emu10k1/emumixer.c
> @@ -39,7 +39,7 @@ static int add_ctls(struct snd_emu10k1 *emu, const struct snd_kcontrol_new *tpl,
>  	for (unsigned i = 0; i < nctls; i++) {
>  		kctl.name = ctls[i];
>  		kctl.private_value = i;
> -		err = snd_ctl_add(emu->card, snd_ctl_new1(&kctl, emu));
> +		err = snd_ctl_add_locked(emu->card, snd_ctl_new1(&kctl, emu));
>  		if (err < 0)
>  			return err;
>  	}
> @@ -87,15 +87,35 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
>  	pfx "ADAT 0" sfx, pfx "ADAT 1" sfx, pfx "ADAT 2" sfx, pfx "ADAT 3" sfx, \
>  	pfx "ADAT 4" sfx, pfx "ADAT 5" sfx, pfx "ADAT 6" sfx, pfx "ADAT 7" sfx
>  
> +#define ADAT_2x_PS(pfx, sfx) \
> +	pfx "ADAT 0-1" sfx, pfx "ADAT 2-3" sfx, pfx "ADAT 4-5" sfx, pfx "ADAT 6-7" sfx
> +
> +#define ADAT_4x_PS(pfx, sfx) \
> +	pfx "ADAT 0-3" sfx, pfx "ADAT 4-7" sfx
> +
>  #define PAIR_REGS(base, one, two) \
>  	base ## one ## 1, \
>  	base ## two ## 1
> +#define PAIR_2x_REGS(base, one, two) \
> +	{ base ## one ## 1, base ## one ## 2 }, \
> +	{ base ## two ## 1, base ## two ## 2 }
> +#define PAIR_4x_REGS(base, one, two) \
> +	{ base ## one ## 1, base ## one ## 2, base ## one ## 3, base ## one ## 4 }, \
> +	{ base ## two ## 1, base ## two ## 2, base ## two ## 3, base ## two ## 4 }
>  
>  #define LR_REGS(base) PAIR_REGS(base, _LEFT, _RIGHT)
> +#define LR_2x_REGS(base) PAIR_2x_REGS(base, _LEFT, _RIGHT)
> +#define LR_4x_REGS(base) PAIR_4x_REGS(base, _LEFT, _RIGHT)
>  
>  #define ADAT_REGS(base) \
>  	base+0, base+1, base+2, base+3, base+4, base+5, base+6, base+7
>  
> +#define ADAT_2x_REGS(base) \
> +	{ base+0, base+1 }, { base+2, base+3 }, { base+4, base+5 }, { base+6, base+7 }
> +
> +#define ADAT_4x_REGS(base) \
> +	{ base+0, base+1, base+2, base+3 }, { base+4, base+5, base+6, base+7 }
> +
>  /*
>   * List of data sources available for each destination
>   */
> @@ -112,9 +132,16 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
>  	"PbChn 08", "PbChn 09", "PbChn 10", "PbChn 11", \
>  	"PbChn 12", "PbChn 13", "PbChn 14", "PbChn 15"
>  
> +#define PB_4x_TEXTS PB_TEXTS  // Only 1x playback for now
> +
>  #define PAIR_TEXTS(base, one, two) PAIR_PS(base, one, two, "")
>  #define LR_TEXTS(base) LR_PS(base, "")
>  #define ADAT_TEXTS(pfx) ADAT_PS(pfx, "")
> +#define ADAT_2x_TEXTS(pfx) ADAT_2x_PS(pfx, "")
> +#define ADAT_4x_TEXTS(pfx) ADAT_4x_PS(pfx, "")
> +
> +#define SRC_SILENCE_2x { EMU_SRC_SILENCE, EMU_SRC_SILENCE }
> +#define SRC_SILENCE_4x { EMU_SRC_SILENCE, EMU_SRC_SILENCE, EMU_SRC_SILENCE, EMU_SRC_SILENCE }
>  
>  #define EMU32_SRC_REGS \
>  	EMU_SRC_ALICE_EMU32A, \
> @@ -150,6 +177,27 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol,
>  	EMU_SRC_ALICE_EMU32B+0xe, \
>  	EMU_SRC_ALICE_EMU32B+0xf
>  
> +// Only 1x playback for now
> +#define EMU32_2x_SRC_REGS \
> +	{ EMU_SRC_ALICE_EMU32A }, \
> +	{ EMU_SRC_ALICE_EMU32A+1 }, \
> +	{ EMU_SRC_ALICE_EMU32A+2 }, \
> +	{ EMU_SRC_ALICE_EMU32A+3 }, \
> +	{ EMU_SRC_ALICE_EMU32A+4 }, \
> +	{ EMU_SRC_ALICE_EMU32A+5 }, \
> +	{ EMU_SRC_ALICE_EMU32A+6 }, \
> +	{ EMU_SRC_ALICE_EMU32A+7 }, \
> +	{ EMU_SRC_ALICE_EMU32A+8 }, \
> +	{ EMU_SRC_ALICE_EMU32A+9 }, \
> +	{ EMU_SRC_ALICE_EMU32A+0xa }, \
> +	{ EMU_SRC_ALICE_EMU32A+0xb }, \
> +	{ EMU_SRC_ALICE_EMU32A+0xc }, \
> +	{ EMU_SRC_ALICE_EMU32A+0xd }, \
> +	{ EMU_SRC_ALICE_EMU32A+0xe }, \
> +	{ EMU_SRC_ALICE_EMU32A+0xf }
> +
> +#define EMU32_4x_SRC_REGS EMU32_2x_SRC_REGS
> +
>  /* 1010 rev1 */
>  
>  #define EMU1010_COMMON_TEXTS \
> @@ -185,6 +233,54 @@ static const unsigned short emu1010_src_regs[] = {
>  };
>  static_assert(ARRAY_SIZE(emu1010_src_regs) == ARRAY_SIZE(emu1010_src_texts));
>  
> +static const char * const emu1010_2x_src_texts[] = {
> +	"Silence",
> +	PAIR_TEXTS("Dock Mic", "A", "B"),
> +	LR_TEXTS("Dock ADC1"),
> +	LR_TEXTS("Dock ADC2"),
> +	LR_TEXTS("Dock ADC3"),
> +	LR_TEXTS("0202 ADC"),
> +	LR_TEXTS("1010 SPDIF"),
> +	ADAT_2x_TEXTS("1010 "),
> +	PB_TEXTS,
> +};
> +
> +static const unsigned short emu1010_2x_src_regs[][2] = {
> +	SRC_SILENCE_2x,
> +	PAIR_2x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
> +	LR_2x_REGS(EMU_SRC_DOCK_ADC1),
> +	LR_2x_REGS(EMU_SRC_DOCK_ADC2),
> +	LR_2x_REGS(EMU_SRC_DOCK_ADC3),
> +	LR_2x_REGS(EMU_SRC_HAMOA_ADC),
> +	LR_2x_REGS(EMU_SRC_HANA_SPDIF),
> +	ADAT_2x_REGS(EMU_SRC_HANA_ADAT),
> +	EMU32_2x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu1010_2x_src_regs) == ARRAY_SIZE(emu1010_2x_src_texts));
> +
> +static const char * const emu1010_4x_src_texts[] = {
> +	"Silence",
> +	PAIR_TEXTS("Dock Mic", "A", "B"),
> +	LR_TEXTS("Dock ADC1"),
> +	LR_TEXTS("Dock ADC2"),
> +	LR_TEXTS("Dock ADC3"),
> +	LR_TEXTS("0202 ADC"),
> +	ADAT_4x_TEXTS("1010 "),
> +	PB_4x_TEXTS,
> +};
> +
> +static const unsigned short emu1010_4x_src_regs[][4] = {
> +	SRC_SILENCE_4x,
> +	PAIR_4x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
> +	LR_4x_REGS(EMU_SRC_DOCK_ADC1),
> +	LR_4x_REGS(EMU_SRC_DOCK_ADC2),
> +	LR_4x_REGS(EMU_SRC_DOCK_ADC3),
> +	LR_4x_REGS(EMU_SRC_HAMOA_ADC),
> +	ADAT_4x_REGS(EMU_SRC_HANA_ADAT),
> +	EMU32_4x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu1010_4x_src_regs) == ARRAY_SIZE(emu1010_4x_src_texts));
> +
>  /* 1010 rev2 */
>  
>  #define EMU1010b_COMMON_TEXTS \
> @@ -222,6 +318,58 @@ static const unsigned short emu1010b_src_regs[] = {
>  };
>  static_assert(ARRAY_SIZE(emu1010b_src_regs) == ARRAY_SIZE(emu1010b_src_texts));
>  
> +static const char * const emu1010b_2x_src_texts[] = {
> +	"Silence",
> +	PAIR_TEXTS("Dock Mic", "A", "B"),
> +	LR_TEXTS("Dock ADC1"),
> +	LR_TEXTS("Dock ADC2"),
> +	LR_TEXTS("0202 ADC"),
> +	LR_TEXTS("Dock SPDIF"),
> +	LR_TEXTS("1010 SPDIF"),
> +	ADAT_2x_TEXTS("Dock "),
> +	ADAT_2x_TEXTS("1010 "),
> +	PB_TEXTS,
> +};
> +
> +static const unsigned short emu1010b_2x_src_regs[][2] = {
> +	SRC_SILENCE_2x,
> +	PAIR_2x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
> +	LR_2x_REGS(EMU_SRC_DOCK_ADC1),
> +	LR_2x_REGS(EMU_SRC_DOCK_ADC2),
> +	LR_2x_REGS(EMU_SRC_HAMOA_ADC),
> +	LR_2x_REGS(EMU_SRC_MDOCK_SPDIF),
> +	LR_2x_REGS(EMU_SRC_HANA_SPDIF),
> +	ADAT_2x_REGS(EMU_SRC_MDOCK_ADAT),
> +	ADAT_2x_REGS(EMU_SRC_HANA_ADAT),
> +	EMU32_2x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu1010b_2x_src_regs) == ARRAY_SIZE(emu1010b_2x_src_texts));
> +
> +static const char * const emu1010b_4x_src_texts[] = {
> +	"Silence",
> +	PAIR_TEXTS("Dock Mic", "A", "B"),
> +	LR_TEXTS("Dock ADC1"),
> +	LR_TEXTS("Dock ADC2"),
> +	LR_TEXTS("0202 ADC"),
> +	LR_TEXTS("1010 SPDIF"),
> +	ADAT_4x_TEXTS("Dock "),
> +	ADAT_4x_TEXTS("1010 "),
> +	PB_4x_TEXTS,
> +};
> +
> +static const unsigned short emu1010b_4x_src_regs[][4] = {
> +	SRC_SILENCE_4x,
> +	PAIR_4x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
> +	LR_4x_REGS(EMU_SRC_DOCK_ADC1),
> +	LR_4x_REGS(EMU_SRC_DOCK_ADC2),
> +	LR_4x_REGS(EMU_SRC_HAMOA_ADC),
> +	LR_4x_REGS(EMU_SRC_HANA_SPDIF),
> +	ADAT_4x_REGS(EMU_SRC_MDOCK_ADAT),
> +	ADAT_4x_REGS(EMU_SRC_HANA_ADAT),
> +	EMU32_4x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu1010b_4x_src_regs) == ARRAY_SIZE(emu1010b_4x_src_texts));
> +
>  /* 1616(m) cardbus */
>  
>  #define EMU1616_COMMON_TEXTS \
> @@ -253,6 +401,46 @@ static const unsigned short emu1616_src_regs[] = {
>  };
>  static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts));
>  
> +static const char * const emu1616_2x_src_texts[] = {
> +	"Silence",
> +	PAIR_TEXTS("Mic", "A", "B"),
> +	LR_TEXTS("ADC1"),
> +	LR_TEXTS("ADC2"),
> +	LR_TEXTS("SPDIF"),
> +	ADAT_2x_TEXTS(""),
> +	PB_TEXTS,
> +};
> +
> +static const unsigned short emu1616_2x_src_regs[][2] = {
> +	SRC_SILENCE_2x,
> +	PAIR_2x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
> +	LR_2x_REGS(EMU_SRC_DOCK_ADC1),
> +	LR_2x_REGS(EMU_SRC_DOCK_ADC2),
> +	LR_2x_REGS(EMU_SRC_MDOCK_SPDIF),
> +	ADAT_2x_REGS(EMU_SRC_MDOCK_ADAT),
> +	EMU32_2x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu1616_2x_src_regs) == ARRAY_SIZE(emu1616_2x_src_texts));
> +
> +static const char * const emu1616_4x_src_texts[] = {
> +	"Silence",
> +	PAIR_TEXTS("Mic", "A", "B"),
> +	LR_TEXTS("ADC1"),
> +	LR_TEXTS("ADC2"),
> +	ADAT_4x_TEXTS(""),
> +	PB_4x_TEXTS,
> +};
> +
> +static const unsigned short emu1616_4x_src_regs[][4] = {
> +	SRC_SILENCE_4x,
> +	PAIR_4x_REGS(EMU_SRC_DOCK_MIC, _A, _B),
> +	LR_4x_REGS(EMU_SRC_DOCK_ADC1),
> +	LR_4x_REGS(EMU_SRC_DOCK_ADC2),
> +	ADAT_4x_REGS(EMU_SRC_MDOCK_ADAT),
> +	EMU32_4x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu1616_4x_src_regs) == ARRAY_SIZE(emu1616_4x_src_texts));
> +
>  /* 0404 rev1 & rev2 */
>  
>  #define EMU0404_COMMON_TEXTS \
> @@ -278,13 +466,36 @@ static const unsigned short emu0404_src_regs[] = {
>  };
>  static_assert(ARRAY_SIZE(emu0404_src_regs) == ARRAY_SIZE(emu0404_src_texts));
>  
> +static const unsigned short emu0404_2x_src_regs[][2] = {
> +	SRC_SILENCE_2x,
> +	LR_2x_REGS(EMU_SRC_HAMOA_ADC),
> +	LR_2x_REGS(EMU_SRC_HANA_SPDIF),
> +	EMU32_2x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu0404_2x_src_regs) == ARRAY_SIZE(emu0404_das_src_texts));
> +
> +static const char * const emu0404_4x_src_texts[] = {
> +	"Silence",
> +	LR_TEXTS("ADC"),
> +	PB_4x_TEXTS,
> +};
> +
> +static const unsigned short emu0404_4x_src_regs[][4] = {
> +	SRC_SILENCE_4x,
> +	LR_4x_REGS(EMU_SRC_HAMOA_ADC),
> +	EMU32_4x_SRC_REGS,
> +};
> +static_assert(ARRAY_SIZE(emu0404_4x_src_regs) == ARRAY_SIZE(emu0404_4x_src_texts));
> +
>  /*
>   * Data destinations - physical EMU outputs.
>   * Each destination has an enum mixer control to choose a data source
>   */
>  
>  #define LR_CTLS(base) LR_PS(base, " Playback Enum")
>  #define ADAT_CTLS(pfx) ADAT_PS(pfx, " Playback Enum")
> +#define ADAT_2x_CTLS(pfx) ADAT_2x_PS(pfx, " Playback Enum")
> +#define ADAT_4x_CTLS(pfx) ADAT_4x_PS(pfx, " Playback Enum")
>  
>  /* 1010 rev1 */
>  
> @@ -328,6 +539,52 @@ static const unsigned short emu1010_output_dflt[] = {
>  };
>  static_assert(ARRAY_SIZE(emu1010_output_dflt) == ARRAY_SIZE(emu1010_output_dst));
>  
> +static const char * const emu1010_2x_output_texts[] = {
> +	LR_CTLS("Dock DAC1"),
> +	LR_CTLS("Dock DAC2"),
> +	LR_CTLS("Dock DAC3"),
> +	LR_CTLS("Dock DAC4"),
> +	LR_CTLS("Dock Phones"),
> +	LR_CTLS("Dock SPDIF"),
> +	LR_CTLS("0202 DAC"),
> +	LR_CTLS("1010 SPDIF"),
> +	ADAT_2x_CTLS("1010 "),
> +};
> +static_assert(ARRAY_SIZE(emu1010_2x_output_texts) <= NUM_OUTPUT_DESTS);
> +
> +static const unsigned short emu1010_2x_output_dst[][2] = {
> +	LR_2x_REGS(EMU_DST_DOCK_DAC1),
> +	LR_2x_REGS(EMU_DST_DOCK_DAC2),
> +	LR_2x_REGS(EMU_DST_DOCK_DAC3),
> +	LR_2x_REGS(EMU_DST_DOCK_DAC4),
> +	LR_2x_REGS(EMU_DST_DOCK_PHONES),
> +	LR_2x_REGS(EMU_DST_DOCK_SPDIF),
> +	LR_2x_REGS(EMU_DST_HAMOA_DAC),
> +	LR_2x_REGS(EMU_DST_HANA_SPDIF),
> +	ADAT_2x_REGS(EMU_DST_HANA_ADAT),
> +};
> +static_assert(ARRAY_SIZE(emu1010_2x_output_dst) == ARRAY_SIZE(emu1010_2x_output_texts));
> +
> +static const char * const emu1010_4x_output_texts[] = {
> +	LR_CTLS("Dock DAC1"),
> +	LR_CTLS("Dock DAC2"),
> +	LR_CTLS("Dock DAC3"),
> +	LR_CTLS("Dock DAC4"),
> +	LR_CTLS("0202 DAC"),
> +	ADAT_4x_CTLS("1010 "),
> +};
> +static_assert(ARRAY_SIZE(emu1010_4x_output_texts) <= NUM_OUTPUT_DESTS);
> +
> +static const unsigned short emu1010_4x_output_dst[][4] = {
> +	LR_4x_REGS(EMU_DST_DOCK_DAC1),
> +	LR_4x_REGS(EMU_DST_DOCK_DAC2),
> +	LR_4x_REGS(EMU_DST_DOCK_DAC3),
> +	LR_4x_REGS(EMU_DST_DOCK_DAC4),
> +	LR_4x_REGS(EMU_DST_HAMOA_DAC),
> +	ADAT_4x_REGS(EMU_DST_HANA_ADAT),
> +};
> +static_assert(ARRAY_SIZE(emu1010_4x_output_dst) == ARRAY_SIZE(emu1010_4x_output_texts));
> +
>  /* 1010 rev2 */
>  
>  static const char * const snd_emu1010b_output_texts[] = {
> @@ -367,6 +624,52 @@ static const unsigned short emu1010b_output_dflt[] = {
>  	EMU_SRC_ALICE_EMU32A+4, EMU_SRC_ALICE_EMU32A+5, EMU_SRC_ALICE_EMU32A+6, EMU_SRC_ALICE_EMU32A+7,
>  };
>  
> +static const char * const snd_emu1010b_2x_output_texts[] = {
> +	LR_CTLS("Dock DAC1"),
> +	LR_CTLS("Dock DAC2"),
> +	LR_CTLS("Dock DAC3"),
> +	LR_CTLS("Dock SPDIF"),
> +	ADAT_2x_CTLS("Dock "),
> +	LR_CTLS("0202 DAC"),
> +	LR_CTLS("1010 SPDIF"),
> +	ADAT_2x_CTLS("1010 "),
> +};
> +static_assert(ARRAY_SIZE(snd_emu1010b_2x_output_texts) <= NUM_OUTPUT_DESTS);
> +
> +static const unsigned short emu1010b_2x_output_dst[][2] = {
> +	LR_2x_REGS(EMU_DST_DOCK_DAC1),
> +	LR_2x_REGS(EMU_DST_DOCK_DAC2),
> +	LR_2x_REGS(EMU_DST_DOCK_DAC3),
> +	LR_2x_REGS(EMU_DST_MDOCK_SPDIF),
> +	ADAT_2x_REGS(EMU_DST_MDOCK_ADAT),
> +	LR_2x_REGS(EMU_DST_HAMOA_DAC),
> +	LR_2x_REGS(EMU_DST_HANA_SPDIF),
> +	ADAT_2x_REGS(EMU_DST_HANA_ADAT),
> +};
> +static_assert(ARRAY_SIZE(emu1010b_2x_output_dst) == ARRAY_SIZE(snd_emu1010b_2x_output_texts));
> +
> +static const char * const snd_emu1010b_4x_output_texts[] = {
> +	LR_CTLS("Dock DAC1"),
> +	LR_CTLS("Dock DAC2"),
> +	LR_CTLS("Dock DAC3"),
> +	ADAT_4x_CTLS("Dock "),
> +	LR_CTLS("0202 DAC"),
> +	LR_CTLS("1010 SPDIF"),
> +	ADAT_4x_CTLS("1010 "),
> +};
> +static_assert(ARRAY_SIZE(snd_emu1010b_4x_output_texts) <= NUM_OUTPUT_DESTS);
> +
> +static const unsigned short emu1010b_4x_output_dst[][4] = {
> +	LR_4x_REGS(EMU_DST_DOCK_DAC1),
> +	LR_4x_REGS(EMU_DST_DOCK_DAC2),
> +	LR_4x_REGS(EMU_DST_DOCK_DAC3),
> +	ADAT_4x_REGS(EMU_DST_MDOCK_ADAT),
> +	LR_4x_REGS(EMU_DST_HAMOA_DAC),
> +	LR_4x_REGS(EMU_DST_HANA_SPDIF),
> +	ADAT_4x_REGS(EMU_DST_HANA_ADAT),
> +};
> +static_assert(ARRAY_SIZE(emu1010b_4x_output_dst) == ARRAY_SIZE(snd_emu1010b_4x_output_texts));
> +
>  /* 1616(m) cardbus */
>  
>  static const char * const snd_emu1616_output_texts[] = {
> @@ -400,6 +703,40 @@ static const unsigned short emu1616_output_dflt[] = {
>  };
>  static_assert(ARRAY_SIZE(emu1616_output_dflt) == ARRAY_SIZE(emu1616_output_dst));
>  
> +static const char * const snd_emu1616_2x_output_texts[] = {
> +	LR_CTLS("Dock DAC1"),
> +	LR_CTLS("Dock DAC2"),
> +	LR_CTLS("Dock DAC3"),
> +	LR_CTLS("Dock SPDIF"),
> +	ADAT_2x_CTLS("Dock "),
> +};
> +static_assert(ARRAY_SIZE(snd_emu1616_2x_output_texts) <= NUM_OUTPUT_DESTS);
> +
> +static const unsigned short emu1616_2x_output_dst[][2] = {
> +	LR_2x_REGS(EMU_DST_DOCK_DAC1),
> +	LR_2x_REGS(EMU_DST_DOCK_DAC2),
> +	LR_2x_REGS(EMU_DST_DOCK_DAC3),
> +	LR_2x_REGS(EMU_DST_MDOCK_SPDIF),
> +	ADAT_2x_REGS(EMU_DST_MDOCK_ADAT),
> +};
> +static_assert(ARRAY_SIZE(emu1616_2x_output_dst) == ARRAY_SIZE(snd_emu1616_2x_output_texts));
> +
> +static const char * const snd_emu1616_4x_output_texts[] = {
> +	LR_CTLS("Dock DAC1"),
> +	LR_CTLS("Dock DAC2"),
> +	LR_CTLS("Dock DAC3"),
> +	ADAT_4x_CTLS("Dock "),
> +};
> +static_assert(ARRAY_SIZE(snd_emu1616_4x_output_texts) <= NUM_OUTPUT_DESTS);
> +
> +static const unsigned short emu1616_4x_output_dst[][4] = {
> +	LR_4x_REGS(EMU_DST_DOCK_DAC1),
> +	LR_4x_REGS(EMU_DST_DOCK_DAC2),
> +	LR_4x_REGS(EMU_DST_DOCK_DAC3),
> +	ADAT_4x_REGS(EMU_DST_MDOCK_ADAT),
> +};
> +static_assert(ARRAY_SIZE(emu1616_4x_output_dst) == ARRAY_SIZE(snd_emu1616_4x_output_texts));
> +
>  /* 0404 rev1 & rev2 */
>  
>  static const char * const snd_emu0404_output_texts[] = {
> @@ -420,6 +757,22 @@ static const unsigned short emu0404_output_dflt[] = {
>  };
>  static_assert(ARRAY_SIZE(emu0404_output_dflt) == ARRAY_SIZE(emu0404_output_dst));
>  
> +static const unsigned short emu0404_2x_output_dst[][2] = {
> +	LR_2x_REGS(EMU_DST_HAMOA_DAC),
> +	LR_2x_REGS(EMU_DST_HANA_SPDIF),
> +};
> +static_assert(ARRAY_SIZE(emu0404_2x_output_dst) == ARRAY_SIZE(snd_emu0404_output_texts));
> +
> +static const char * const snd_emu0404_4x_output_texts[] = {
> +	LR_CTLS("DAC"),
> +};
> +static_assert(ARRAY_SIZE(snd_emu0404_4x_output_texts) <= NUM_OUTPUT_DESTS);
> +
> +static const unsigned short emu0404_4x_output_dst[][4] = {
> +	LR_4x_REGS(EMU_DST_HAMOA_DAC),
> +};
> +static_assert(ARRAY_SIZE(emu0404_4x_output_dst) == ARRAY_SIZE(snd_emu0404_4x_output_texts));
> +
>  /*
>   * Data destinations - FPGA outputs going to Alice2 (Audigy) for
>   *   capture (EMU32 + I2S links)
> @@ -549,168 +902,267 @@ static const unsigned short emu0404_input_dflt[] = {
>  };
>  
>  struct snd_emu1010_routing_info {
> -	const char * const *src_texts[2];
> -	const char * const *out_texts;
> -	const unsigned short *src_regs;
> -	const unsigned short *out_regs;
> +	const char * const *src_texts[4];
> +	const char * const *out_texts[3];
> +	const unsigned short *src_regs[3];
> +	const unsigned short *out_regs[3];
>  	const unsigned short *in_regs;
>  	const unsigned short *out_dflts;
>  	const unsigned short *in_dflts;
> -	unsigned n_srcs[2];
> -	unsigned n_outs;
> -	unsigned n_ins[2];
> +	unsigned n_srcs[4];
> +	unsigned n_outs[3];
> +	unsigned n_ins[4];
>  };
>  
>  static const struct snd_emu1010_routing_info emu1010_routing_info[] = {
>  	{
>  		/* rev1 1010 */
> -		.src_regs = emu1010_src_regs,
> -		.src_texts = { emu1010_src_texts, emu1010_das_src_texts },
> -		.n_srcs = { ARRAY_SIZE(emu1010_src_texts), ARRAY_SIZE(emu1010_das_src_texts) },
> +		.src_regs = { emu1010_src_regs, emu1010_2x_src_regs[0], emu1010_4x_src_regs[0] },
> +		.src_texts = { emu1010_src_texts, emu1010_das_src_texts,
> +			       emu1010_2x_src_texts, emu1010_4x_src_texts },
> +		.n_srcs = { ARRAY_SIZE(emu1010_src_texts), ARRAY_SIZE(emu1010_das_src_texts),
> +			    ARRAY_SIZE(emu1010_2x_src_texts), ARRAY_SIZE(emu1010_4x_src_texts) },
>  
>  		.out_dflts = emu1010_output_dflt,
> -		.out_regs = emu1010_output_dst,
> -		.out_texts = emu1010_output_texts,
> -		.n_outs = ARRAY_SIZE(emu1010_output_dst),
> +		.out_regs = { emu1010_output_dst, emu1010_2x_output_dst[0], emu1010_4x_output_dst[0] },
> +		.out_texts = { emu1010_output_texts,
> +			       emu1010_2x_output_texts, emu1010_4x_output_texts },
> +		.n_outs = { ARRAY_SIZE(emu1010_output_texts),
> +			    ARRAY_SIZE(emu1010_2x_output_texts), ARRAY_SIZE(emu1010_4x_output_texts) },
>  
>  		.in_dflts = emu1010_input_dflt,
>  		.in_regs = emu1010_input_dst,
> -		.n_ins = { ARRAY_SIZE(emu1010_input_dst), 16 },
> +		.n_ins = { ARRAY_SIZE(emu1010_input_dst), 16, 16, 16 },
>  	},
>  	{
>  		/* rev2 1010 */
> -		.src_regs = emu1010b_src_regs,
> -		.src_texts = { emu1010b_src_texts, emu1010b_das_src_texts },
> -		.n_srcs = { ARRAY_SIZE(emu1010b_src_texts), ARRAY_SIZE(emu1010b_das_src_texts) },
> +		.src_regs = { emu1010b_src_regs, emu1010b_2x_src_regs[0], emu1010b_4x_src_regs[0] },
> +		.src_texts = { emu1010b_src_texts, emu1010b_das_src_texts,
> +			       emu1010b_2x_src_texts, emu1010b_4x_src_texts },
> +		.n_srcs = { ARRAY_SIZE(emu1010b_src_texts), ARRAY_SIZE(emu1010b_das_src_texts),
> +			    ARRAY_SIZE(emu1010b_2x_src_texts), ARRAY_SIZE(emu1010b_4x_src_texts) },
>  
>  		.out_dflts = emu1010b_output_dflt,
> -		.out_regs = emu1010b_output_dst,
> -		.out_texts = snd_emu1010b_output_texts,
> -		.n_outs = ARRAY_SIZE(emu1010b_output_dst),
> +		.out_regs = { emu1010b_output_dst, emu1010b_2x_output_dst[0], emu1010b_4x_output_dst[0] },
> +		.out_texts = { snd_emu1010b_output_texts,
> +			       snd_emu1010b_2x_output_texts, snd_emu1010b_4x_output_texts },
> +		.n_outs = { ARRAY_SIZE(snd_emu1010b_output_texts),
> +			    ARRAY_SIZE(snd_emu1010b_2x_output_texts), ARRAY_SIZE(snd_emu1010b_4x_output_texts) },
>  
>  		.in_dflts = emu1010_input_dflt,
>  		.in_regs = emu1010_input_dst,
> -		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
> +		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
>  	},
>  	{
>  		/* 1616(m) cardbus */
> -		.src_regs = emu1616_src_regs,
> -		.src_texts = { emu1616_src_texts, emu1616_das_src_texts },
> -		.n_srcs = { ARRAY_SIZE(emu1616_src_texts), ARRAY_SIZE(emu1616_das_src_texts) },
> +		.src_regs = { emu1616_src_regs, emu1616_2x_src_regs[0], emu1616_4x_src_regs[0] },
> +		.src_texts = { emu1616_src_texts, emu1616_das_src_texts,
> +			       emu1616_2x_src_texts, emu1616_4x_src_texts },
> +		.n_srcs = { ARRAY_SIZE(emu1616_src_texts), ARRAY_SIZE(emu1616_das_src_texts),
> +			    ARRAY_SIZE(emu1616_2x_src_texts), ARRAY_SIZE(emu1616_4x_src_texts) },
>  
>  		.out_dflts = emu1616_output_dflt,
> -		.out_regs = emu1616_output_dst,
> -		.out_texts = snd_emu1616_output_texts,
> -		.n_outs = ARRAY_SIZE(emu1616_output_dst),
> +		.out_regs = { emu1616_output_dst, emu1616_2x_output_dst[0], emu1616_4x_output_dst[0] },
> +		.out_texts = { snd_emu1616_output_texts,
> +			       snd_emu1616_2x_output_texts, snd_emu1616_4x_output_texts },
> +		.n_outs = { ARRAY_SIZE(snd_emu1616_output_texts),
> +			    ARRAY_SIZE(snd_emu1616_2x_output_texts), ARRAY_SIZE(snd_emu1616_4x_output_texts) },
>  
>  		.in_dflts = emu1010_input_dflt,
>  		.in_regs = emu1010_input_dst,
> -		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
> +		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
>  	},
>  	{
>  		/* 0404 */
> -		.src_regs = emu0404_src_regs,
> -		.src_texts = { emu0404_src_texts, emu0404_das_src_texts },
> -		.n_srcs = { ARRAY_SIZE(emu0404_src_texts), ARRAY_SIZE(emu0404_das_src_texts) },
> +		.src_regs = { emu0404_src_regs, emu0404_2x_src_regs[0], emu0404_4x_src_regs[0] },
> +		.src_texts = { emu0404_src_texts, emu0404_das_src_texts,
> +			       emu0404_das_src_texts, emu0404_4x_src_texts },
> +		.n_srcs = { ARRAY_SIZE(emu0404_src_texts), ARRAY_SIZE(emu0404_das_src_texts),
> +			    ARRAY_SIZE(emu0404_das_src_texts), ARRAY_SIZE(emu0404_4x_src_texts) },
>  
>  		.out_dflts = emu0404_output_dflt,
> -		.out_regs = emu0404_output_dst,
> -		.out_texts = snd_emu0404_output_texts,
> -		.n_outs = ARRAY_SIZE(emu0404_output_dflt),
> +		.out_regs = { emu0404_output_dst, emu0404_2x_output_dst[0], emu0404_4x_output_dst[0] },
> +		.out_texts = { snd_emu0404_output_texts,
> +			       snd_emu0404_output_texts, snd_emu0404_4x_output_texts },
> +		.n_outs = { ARRAY_SIZE(snd_emu0404_output_texts),
> +			    ARRAY_SIZE(snd_emu0404_output_texts), ARRAY_SIZE(snd_emu0404_4x_output_texts) },
>  
>  		.in_dflts = emu0404_input_dflt,
>  		.in_regs = emu1010_input_dst,
> -		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16 },
> +		.n_ins = { ARRAY_SIZE(emu1010_input_dst) - 6, 16, 16, 16 },
>  	},
>  };
>  
>  static unsigned emu1010_idx(struct snd_emu10k1 *emu)
>  {
>  	return emu->card_capabilities->emu_model - 1;
>  }
>  
> +static void snd_emu1010_source_apply(struct snd_emu10k1 *emu, unsigned shift,
> +				     const unsigned short *regs,
> +				     const unsigned short *vals)
> +{
> +	unsigned short avals[4];
> +
> +	if ((vals[0] & 0x700) == 0x300) {  // EMU32x
> +		// Only 1x playback for now
> +		avals[0] = avals[1] = avals[2] = avals[3] = vals[0];
> +		vals = avals;
> +	}
> +	switch (shift) {
> +	case 2:
> +		snd_emu1010_fpga_link_dst_src_write(emu, regs[3], vals[3]);
> +		snd_emu1010_fpga_link_dst_src_write(emu, regs[2], vals[2]);
> +		fallthrough;
> +	case 1:
> +		snd_emu1010_fpga_link_dst_src_write(emu, regs[1], vals[1]);
> +		fallthrough;
> +	default:
> +		snd_emu1010_fpga_link_dst_src_write(emu, regs[0], vals[0]);
> +		break;
> +	}
> +}
> +
>  static void snd_emu1010_output_source_apply(struct snd_emu10k1 *emu,
>  					    int channel, int src)
>  {
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> +	unsigned shift = emu->emu1010.clock_shift;
> +	const unsigned short *regs = &emu_ri->out_regs[shift][channel << shift];
> +	const unsigned short *vals = &emu_ri->src_regs[shift][src << shift];
>  
> -	snd_emu1010_fpga_link_dst_src_write(emu,
> -		emu_ri->out_regs[channel], emu_ri->src_regs[src]);
> +	snd_emu1010_source_apply(emu, shift, regs, vals);
>  }
>  
>  static void snd_emu1010_input_source_apply(struct snd_emu10k1 *emu,
>  					   int channel, int src)
>  {
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> +	unsigned shift = emu->emu1010.clock_shift;
> +	const unsigned short *regs = &emu_ri->in_regs[channel];
> +	const unsigned short *vals = &emu_ri->src_regs[shift][src << shift];
>  
> -	snd_emu1010_fpga_link_dst_src_write(emu,
> -		emu_ri->in_regs[channel], emu_ri->src_regs[src]);
> +	// Only 1x capture for now
> +	snd_emu1010_fpga_link_dst_src_write(emu, regs[0], vals[0]);
>  }
>  
> -static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu)
> +static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu, int active)
>  {
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> -	unsigned iidx = emu->das_mode;
> +	unsigned oidx = emu->emu1010.clock_shift;
> +	unsigned iidx = emu->das_mode + oidx;
>  
> -	for (unsigned i = 0; i < emu_ri->n_outs; i++)
> +	for (unsigned i = 0; i < emu_ri->n_outs[oidx]; i++)
>  		snd_emu1010_output_source_apply(
> -			emu, i, emu->emu1010.output_source[i]);
> +			emu, i, active ? emu->emu1010.output_source[i] : 0);
>  	for (unsigned i = 0; i < emu_ri->n_ins[iidx]; i++)
>  		snd_emu1010_input_source_apply(
> -			emu, i, emu->emu1010.input_source[i]);
> +			emu, i, active ? emu->emu1010.input_source[i] : 0);
>  }
>  
>  static u8 emu1010_map_source(const struct snd_emu1010_routing_info *emu_ri,
>  			     unsigned das_mode, unsigned val)
>  {
>  	for (unsigned i = 0; i < emu_ri->n_srcs[das_mode]; i++)
> -		if (val == emu_ri->src_regs[i])
> +		if (val == emu_ri->src_regs[0][i])
>  			return i;
>  	return 0;
>  }
>  
> +static const unsigned internal_sources[3] = { 16, 16, 8 };
> +
> +static unsigned emu1010_remap_source(const struct snd_emu1010_routing_info *emu_ri,
> +				     unsigned oshift, unsigned nshift, unsigned src)
> +{
> +	unsigned ibase = emu_ri->n_srcs[oshift + 1] - internal_sources[oshift];
> +	if (src >= ibase) {
> +		int raw_src = src - ibase - internal_sources[nshift];
> +		if (raw_src < 0)
> +			return raw_src + emu_ri->n_srcs[nshift + 1];
> +	} else {
> +		unsigned reg = emu_ri->src_regs[oshift][src << oshift];
> +		for (unsigned i = 0; i < emu_ri->n_srcs[nshift + 1]; i++)
> +			if (reg == emu_ri->src_regs[nshift][i << nshift])
> +				return i;
> +	}
> +	return 0;
> +}
> +
> +static void snd_emu1010_remap_sources(struct snd_emu10k1 *emu, int oshift, int nshift)
> +{
> +	const struct snd_emu1010_routing_info *emu_ri =
> +		&emu1010_routing_info[emu1010_idx(emu)];
> +	unsigned char srcs[NUM_OUTPUT_DESTS];
> +	unsigned o, n, n_dsts_o, n_dsts_n;
> +
> +	n_dsts_o = emu_ri->n_outs[oshift];
> +	n_dsts_n = emu_ri->n_outs[nshift];
> +	for (n = 0; n < n_dsts_n; n++) {
> +		unsigned reg = emu_ri->out_regs[nshift][n << nshift];
> +		unsigned src = 0;
> +		for (o = 0; o < n_dsts_o; o++) {
> +			if (emu_ri->out_regs[oshift][o << oshift] == reg) {
> +				src = emu1010_remap_source(emu_ri, oshift, nshift,
> +							   emu->emu1010.output_source[o]);
> +				break;
> +			}
> +		}
> +		srcs[n] = src;
> +	}
> +	memcpy(emu->emu1010.output_source, srcs, n_dsts_n);
> +
> +	n_dsts_o = emu_ri->n_ins[oshift + 1];
> +	n_dsts_n = emu_ri->n_ins[nshift + 1];
> +	for (n = 0; n < n_dsts_n; n++)
> +		emu->emu1010.input_source[n] = (n >= n_dsts_o) ? 0 :
> +			emu1010_remap_source(emu_ri, oshift, nshift,
> +					     emu->emu1010.input_source[n]);
> +}
> +
>  static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol,
>  						struct snd_ctl_elem_info *uinfo)
>  {
>  	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> -	unsigned iidx = emu->das_mode;
> +	unsigned iidx = emu->das_mode + emu->emu1010.clock_shift;
>  
>  	return snd_ctl_enum_info(uinfo, 1, emu_ri->n_srcs[iidx], emu_ri->src_texts[iidx]);
>  }
>  
>  static int snd_emu1010_output_source_get(struct snd_kcontrol *kcontrol,
>                                   struct snd_ctl_elem_value *ucontrol)
>  {
>  	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> +	unsigned oidx = emu->emu1010.clock_shift;
>  	unsigned channel = kcontrol->private_value;
>  
> -	if (channel >= emu_ri->n_outs)
> +	if (channel >= emu_ri->n_outs[oidx])
>  		return -EINVAL;
>  	ucontrol->value.enumerated.item[0] = emu->emu1010.output_source[channel];
>  	return 0;
>  }
>  
>  static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol,
>                                   struct snd_ctl_elem_value *ucontrol)
>  {
>  	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> -	unsigned iidx = emu->das_mode;
> +	unsigned oidx = emu->emu1010.clock_shift;
> +	unsigned iidx = emu->das_mode + oidx;
>  	unsigned val = ucontrol->value.enumerated.item[0];
>  	unsigned channel = kcontrol->private_value;
>  	int change;
>  
>  	if (val >= emu_ri->n_srcs[iidx])
>  		return -EINVAL;
> -	if (channel >= emu_ri->n_outs)
> +	if (channel >= emu_ri->n_outs[oidx])
>  		return -EINVAL;
>  	change = (emu->emu1010.output_source[channel] != val);
>  	if (change) {
> @@ -734,7 +1186,7 @@ static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol,
>  	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> -	unsigned iidx = emu->das_mode;
> +	unsigned iidx = emu->das_mode + emu->emu1010.clock_shift;
>  	unsigned channel = kcontrol->private_value;
>  
>  	if (channel >= emu_ri->n_ins[iidx])
> @@ -749,7 +1201,7 @@ static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol,
>  	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> -	unsigned iidx = emu->das_mode;
> +	unsigned iidx = emu->das_mode + emu->emu1010.clock_shift;
>  	unsigned val = ucontrol->value.enumerated.item[0];
>  	unsigned channel = kcontrol->private_value;
>  	int change;
> @@ -778,20 +1230,32 @@ static int add_emu1010_source_mixers(struct snd_emu10k1 *emu)
>  {
>  	const struct snd_emu1010_routing_info *emu_ri =
>  		&emu1010_routing_info[emu1010_idx(emu)];
> -	unsigned iidx = emu->das_mode;
> +	unsigned oidx = emu->emu1010.clock_shift;
> +	unsigned iidx = emu->das_mode + oidx;
>  	int err;
>  
>  	err = add_ctls(emu, &emu1010_output_source_ctl,
> -		       emu_ri->out_texts, emu_ri->n_outs);
> +		       emu_ri->out_texts[oidx], emu_ri->n_outs[oidx]);
>  	if (err < 0)
>  		return err;
>  	err = add_ctls(emu, &emu1010_input_source_ctl,
>  		       iidx ? emu1010_das_input_texts :
>  			      emu1010_input_texts,
>  		       emu_ri->n_ins[iidx]);
>  	return err;
>  }
>  
> +static void remove_emu1010_source_mixers(struct snd_emu10k1 *emu)
> +{
> +	struct snd_kcontrol *kctl, *next;
> +
> +	list_for_each_entry_safe(kctl, next, &emu->card->controls, list) {
> +		size_t nlen = strlen(kctl->id.name);
> +		if (nlen > 5 && !memcmp(kctl->id.name + nlen - 5, " Enum", 5))
> +			snd_ctl_remove(emu->card, kctl);
> +	}
> +}
> +
>  
>  static const char * const snd_emu1010_adc_pads[] = {
>  	"ADC1 14dB PAD 0202 Capture Switch",
> @@ -1039,7 +1503,8 @@ static int snd_emu1010_clock_source_put(struct snd_kcontrol *kcontrol,
>  	change = (emu->emu1010.clock_source != val);
>  	if (change) {
>  		emu->emu1010.clock_source = val;
> -		emu->emu1010.wclock = emu_ci->vals[val];
> +		emu->emu1010.wclock = (emu->emu1010.wclock & ~EMU_HANA_WCLOCK_SRC_MASK) |
> +					emu_ci->vals[val];
>  
>  		snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE);
>  		snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, emu->emu1010.wclock);
> @@ -1109,6 +1574,68 @@ static const struct snd_kcontrol_new snd_emu1010_clock_fallback =
>  	.put = snd_emu1010_clock_fallback_put
>  };
>  
> +static int snd_emu1010_clock_shift_info(struct snd_kcontrol *kcontrol,
> +					     struct snd_ctl_elem_info *uinfo)
> +{
> +	static const char * const texts[3] = {
> +		"x1", "x2", "x4"
> +	};
> +
> +	return snd_ctl_enum_info(uinfo, 1, 3, texts);
> +}
> +
> +static int snd_emu1010_clock_shift_get(struct snd_kcontrol *kcontrol,
> +					    struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
> +
> +	ucontrol->value.enumerated.item[0] = emu->emu1010.clock_shift;
> +	return 0;
> +}
> +
> +static int snd_emu1010_clock_shift_put(struct snd_kcontrol *kcontrol,
> +					    struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol);
> +	unsigned int val = ucontrol->value.enumerated.item[0];
> +	int change;
> +
> +	if (val >= 3)
> +		return -EINVAL;
> +	change = (emu->emu1010.clock_shift != val);
> +	if (change) {
> +		snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE);
> +		snd_emu1010_apply_sources(emu, 0);
> +
> +		remove_emu1010_source_mixers(emu);
> +		snd_emu1010_remap_sources(emu, emu->emu1010.clock_shift, val);
> +		emu->emu1010.clock_shift = val;
> +		add_emu1010_source_mixers(emu);
> +
> +		emu->emu1010.wclock = (emu->emu1010.wclock & ~EMU_HANA_WCLOCK_MULT_MASK) |
> +					(val << 3);
> +		snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, emu->emu1010.wclock);
> +		msleep(10);  // Allow DLL to settle
> +
> +		snd_emu1010_apply_sources(emu, 1);
> +		snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE);
> +
> +		snd_emu1010_update_clock(emu);
> +	}
> +	return change;
> +}
> +
> +static const struct snd_kcontrol_new snd_emu1010_clock_shift =
> +{
> +	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
> +	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
> +	.name = "Clock Multiplier",
> +	.count = 1,
> +	.info = snd_emu1010_clock_shift_info,
> +	.get = snd_emu1010_clock_shift_get,
> +	.put = snd_emu1010_clock_shift_put
> +};
> +
>  static int snd_emu1010_optical_out_info(struct snd_kcontrol *kcontrol,
>  					  struct snd_ctl_elem_info *uinfo)
>  {
> @@ -2396,11 +2923,18 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu,
>  		for (i = 0; i < emu_ri->n_ins[midx]; i++)
>  			emu->emu1010.input_source[i] =
>  				emu1010_map_source(emu_ri, midx, emu_ri->in_dflts[i]);
> -		for (i = 0; i < emu_ri->n_outs; i++)
> +		for (i = 0; i < emu_ri->n_outs[0]; i++)
>  			emu->emu1010.output_source[i] =
>  				emu1010_map_source(emu_ri, midx, emu_ri->out_dflts[i]);
> -		snd_emu1010_apply_sources(emu);
> +		snd_emu1010_apply_sources(emu, 1);
>  
> +		if (emu->das_mode) {
> +			kctl = emu->ctl_clock_shift =
> +					snd_ctl_new1(&snd_emu1010_clock_shift, emu);
> +			err = snd_ctl_add(card, kctl);
> +			if (err < 0)
> +				return err;
> +		}
>  		err = snd_ctl_add(card,
>  			snd_ctl_new1(&snd_emu1010_clock_source, emu));
>  		if (err < 0)
> diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c
> index 7aed356637ab..69552d5c9e45 100644
> --- a/sound/pci/emu10k1/emupcm.c
> +++ b/sound/pci/emu10k1/emupcm.c
> @@ -1188,19 +1188,42 @@ static void snd_emu10k1_pcm_efx_mixer_notify(struct snd_emu10k1 *emu, int idx, i
>  	snd_emu10k1_pcm_mixer_notify1(emu, emu->ctl_efx_attn, idx, activate);
>  }
>  
> +static void snd_emu10k1_pcm_clock_mutiplier_notify(struct snd_emu10k1 *emu)
> +{
> +	struct snd_kcontrol *kctl = emu->ctl_clock_shift;
> +	struct snd_ctl_elem_id id;
> +
> +	// Modifying the clock multiplier during playback/capture
> +	// would make a mess, so we lock it.
> +	if (emu->emu1010.clock_users) {
> +		if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE))
> +			return;
> +		kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_WRITE;
> +	} else {
> +		if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_WRITE)
> +			return;
> +		kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_WRITE;
> +	}
> +	snd_ctl_build_ioff(&id, kctl, 0);
> +	snd_ctl_notify(emu->card, SNDRV_CTL_EVENT_MASK_INFO, &id);
> +}
> +
>  static void snd_emu10k1_pcm_free_substream(struct snd_pcm_runtime *runtime)
>  {
>  	kfree(runtime->private_data);
>  }
>  
>  static int snd_emu10k1_efx_playback_close(struct snd_pcm_substream *substream)
>  {
>  	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
>  	struct snd_emu10k1_pcm_mixer *mix;
>  	int i;
>  
> -	if (emu->das_mode)
> +	if (emu->das_mode) {
> +		emu->emu1010.clock_users--;
> +		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
>  		return 0;
> +	}
>  	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
>  		mix = &emu->efx_pcm_mixer[i];
>  		mix->epcm = NULL;
> @@ -1254,8 +1277,11 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream)
>  		return err;
>  	}
>  
> -	if (emu->das_mode)
> +	if (emu->das_mode) {
> +		emu->emu1010.clock_users++;
> +		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
>  		return 0;
> +	}
>  	for (i = 0; i < NUM_EFX_PLAYBACK; i++) {
>  		mix = &emu->efx_pcm_mixer[i];
>  		for (j = 0; j < 8; j++)
> @@ -1464,13 +1490,24 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream)
>  				   &hw_constraints_capture_buffer_sizes);
>  	emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt;
>  	emu->pcm_capture_efx_substream = substream;
> +
> +	if (emu->das_mode) {
> +		emu->emu1010.clock_users++;
> +		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
> +	}
> +
>  	return 0;
>  }
>  
>  static int snd_emu10k1_capture_efx_close(struct snd_pcm_substream *substream)
>  {
>  	struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
>  
> +	if (emu->das_mode) {
> +		emu->emu1010.clock_users--;
> +		snd_emu10k1_pcm_clock_mutiplier_notify(emu);
> +	}
> +
>  	emu->capture_efx_interrupt = NULL;
>  	emu->pcm_capture_efx_substream = NULL;
>  	return 0;
> diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c
> index a0d66ce3ee83..dc9c7a59e03a 100644
> --- a/sound/pci/emu10k1/io.c
> +++ b/sound/pci/emu10k1/io.c
> @@ -404,19 +404,47 @@ void snd_emu1010_update_clock(struct snd_emu10k1 *emu)
>  		clock = 48000;
>  		leds = EMU_HANA_DOCK_LEDS_2_48K;
>  		break;
> +	case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_2X:
> +		clock = 44100;
> +		leds = 0;
> +		break;
> +	case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_2X:
> +		clock = 48000;
> +		leds = EMU_HANA_DOCK_LEDS_2_96K;
> +		break;
> +	case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_4X:
> +		clock = 44100;
> +		leds = 0;
> +		break;
> +	case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_4X:
> +		clock = 48000;
> +		leds = EMU_HANA_DOCK_LEDS_2_192K;
> +		break;
>  	default:
>  		clock = snd_emu1010_get_raw_rate(
>  				emu, emu->emu1010.wclock & EMU_HANA_WCLOCK_SRC_MASK);
>  		// The raw rate reading is rather coarse (it cannot accurately
>  		// represent 44.1 kHz) and fluctuates slightly. Luckily, the
>  		// clock comes from digital inputs, which use standardized rates.
>  		// So we round to the closest standard rate and ignore discrepancies.
>  		if (clock < 46000) {
>  			clock = 44100;
>  			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_44K;
> -		} else {
> +		} else if (clock < 75000) {
>  			clock = 48000;
>  			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_48K;
> +		} else if (clock < 92000) {
> +			clock = 44100;
> +			leds = EMU_HANA_DOCK_LEDS_2_EXT;
> +		} else if (clock < 150000) {
> +			clock = 48000;
> +			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_96K;
> +		} else if (clock < 184000) {
> +			clock = 44100;
> +			leds = EMU_HANA_DOCK_LEDS_2_EXT;
> +		} else {
> +			clock = 48000;
> +			leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_192K;
>  		}
>  		break;
>  	}
> -- 
> 2.40.0.152.g15d061e6df
> 

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13  9:20   ` Takashi Iwai
@ 2023-06-13 10:52     ` Oswald Buddenhagen
  2023-06-13 11:08       ` Takashi Iwai
  0 siblings, 1 reply; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13 10:52 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, Jaroslav Kysela

On Tue, Jun 13, 2023 at 11:20:23AM +0200, Takashi Iwai wrote:
>On Tue, 13 Jun 2023 09:38:20 +0200,
>Oswald Buddenhagen wrote:
>> 
>> Notably, add_ctls() now uses snd_ctl_add_locked(), so it doesn't
>> deadlock when called from snd_emu1010_clock_shift_put(). This also
>> affects the initial creation of the controls, which is OK, as that is
>> done before the card is registered, so no concurrent access can occur.
>
>Creating and removing the controls from kctl put callback is no good
>idea.  In general, dynamic control creation/deletion already confuses
>user-space.
>
i kind of expected that, but what i've tried so far worked remarkably 
well (ok, it was mostly alsamixer).

> On top of that, if it's done by a control element, it can
>be even triggered endlessly by user.
>
it shouldn't, because there is no circularity between the controls. 
even if the app sets all controls as a response to new ones appearing, 
the second round will be a no-op for the multiplier control, and 
therefore causes no new creattion/deletion notifications, and thus 
terminates the recursion.

but suppose a sufficiently broken application exists. then causing it to 
fail still seems quite acceptable: this is effectively new hardware (the 
new mode needs to be enabled manually), so it would be simply 
unsupported by the application until that gets fixed.

>A safer approach would be to create controls statically, and set
>active flag dynamically, I suppose.
>
i wanted to do that, but the problem is that not only the number of 
controls changes, but also the number of enum values in each control, as 
there is no way to make particular enum values inactive.
and i didn't want to keep three whole sets of controls around at all 
times, as that seems a bit wasteful.

also, i don't think that disabling would be fundamentally different from 
deleting: the particular code paths taken are somewhat different, but 
the high-level view is essentially the same. so we can't really make 
predictions which one would work better.

>And, if we really have to create / delete a kctl element from some
>kctl action, don't do it in the callback but process in another work.
>
would that really improve anything? for the notification to be received 
before the ioctl returns, it would have to be watched by a different 
thread. but if the app thought that there is a race, it would have to 
take the lock before issuing the ioctl anyway. so i think for user space 
it doesn't matter when exactly the notifications are emitted.

otoh, making the mixer reorganization async would introduce rather 
significant complexity to the driver due to having to deal with ioctls 
that come in while the inconsistent state persists (which seems likely 
during a state restoration).

so i would _really_ prefer to keep things as they are, and think about 
changing them only once we have hard evidence that the approach is too 
problematic.

regards,
ossi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13 10:52     ` Oswald Buddenhagen
@ 2023-06-13 11:08       ` Takashi Iwai
  2023-06-13 14:00         ` Oswald Buddenhagen
  0 siblings, 1 reply; 22+ messages in thread
From: Takashi Iwai @ 2023-06-13 11:08 UTC (permalink / raw)
  To: Oswald Buddenhagen; +Cc: alsa-devel, Jaroslav Kysela

On Tue, 13 Jun 2023 12:52:43 +0200,
Oswald Buddenhagen wrote:
> 
> On Tue, Jun 13, 2023 at 11:20:23AM +0200, Takashi Iwai wrote:
> > On Tue, 13 Jun 2023 09:38:20 +0200,
> > Oswald Buddenhagen wrote:
> >> 
> >> Notably, add_ctls() now uses snd_ctl_add_locked(), so it doesn't
> >> deadlock when called from snd_emu1010_clock_shift_put(). This also
> >> affects the initial creation of the controls, which is OK, as that is
> >> done before the card is registered, so no concurrent access can occur.
> > 
> > Creating and removing the controls from kctl put callback is no good
> > idea.  In general, dynamic control creation/deletion already confuses
> > user-space.
> > 
> i kind of expected that, but what i've tried so far worked remarkably
> well (ok, it was mostly alsamixer).
> 
> > On top of that, if it's done by a control element, it can
> > be even triggered endlessly by user.
> > 
> it shouldn't, because there is no circularity between the
> controls. even if the app sets all controls as a response to new ones
> appearing, the second round will be a no-op for the multiplier
> control, and therefore causes no new creattion/deletion notifications,
> and thus terminates the recursion.

Hmm I don't get it; if an application just toggles the kctl value
between two values in an infinite loop, it'll delete and recreate
kctls endlessly as well with your patch, no?

> but suppose a sufficiently broken application exists. then causing it
> to fail still seems quite acceptable: this is effectively new hardware
> (the new mode needs to be enabled manually), so it would be simply
> unsupported by the application until that gets fixed.
>
> > A safer approach would be to create controls statically, and set
> > active flag dynamically, I suppose.
> > 
> i wanted to do that, but the problem is that not only the number of
> controls changes, but also the number of enum values in each control,
> as there is no way to make particular enum values inactive.
> and i didn't want to keep three whole sets of controls around at all
> times, as that seems a bit wasteful.
> 
> also, i don't think that disabling would be fundamentally different
> from deleting: the particular code paths taken are somewhat different,
> but the high-level view is essentially the same. so we can't really
> make predictions which one would work better.

Creating and deleting needs a lot of different works and much heavier
tasks.  And, above all, many user-space programs will be borked if an
element goes away, simply crashing.  Some (rather rare) nice ones will
still survive, though.  I've learned this from the past.

> > And, if we really have to create / delete a kctl element from some
> > kctl action, don't do it in the callback but process in another work.
> > 
> would that really improve anything?

As a primary reason, I don't want to expose such a stuff.  If you need
such an unlocked version, you're already doing something very exotic,
and in 99% cases, it's something that needs more care.


Takashi

> for the notification to be
> received before the ioctl returns, it would have to be watched by a
> different thread. but if the app thought that there is a race, it
> would have to take the lock before issuing the ioctl anyway. so i
> think for user space it doesn't matter when exactly the notifications
> are emitted.
> 
> otoh, making the mixer reorganization async would introduce rather
> significant complexity to the driver due to having to deal with ioctls
> that come in while the inconsistent state persists (which seems likely
> during a state restoration).
> 
> so i would _really_ prefer to keep things as they are, and think about
> changing them only once we have hard evidence that the approach is too
> problematic.
> 
> regards,
> ossi
> 

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13 11:08       ` Takashi Iwai
@ 2023-06-13 14:00         ` Oswald Buddenhagen
  2023-06-13 14:13           ` Takashi Iwai
  0 siblings, 1 reply; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13 14:00 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, Jaroslav Kysela

On Tue, Jun 13, 2023 at 01:08:55PM +0200, Takashi Iwai wrote:
>On Tue, 13 Jun 2023 12:52:43 +0200,
>Oswald Buddenhagen wrote:
>> 
>> On Tue, Jun 13, 2023 at 11:20:23AM +0200, Takashi Iwai wrote:
>> > Creating and removing the controls from kctl put callback is no 
>> > good
>> > idea.  In general, dynamic control creation/deletion already confuses
>> > user-space.
>> > 
>> i kind of expected that, but what i've tried so far worked remarkably
>> well (ok, it was mostly alsamixer).
>> 
>> > On top of that, if it's done by a control element, it can
>> > be even triggered endlessly by user.
>> > 
>> it shouldn't, because there is no circularity between the
>> controls. even if the app sets all controls as a response to new ones
>> appearing, the second round will be a no-op for the multiplier
>> control, and therefore causes no new creation/deletion notifications,
>> and thus terminates the recursion.
>
>Hmm I don't get it; if an application just toggles the kctl value
>between two values in an infinite loop, it'll delete and recreate
>kctls endlessly as well with your patch, no?
>
yeah, but why should it toggle just so? it's not reasonable to do that.  
and if we assume it's being unreasonable, then there is no reason to 
think that controls appearing and disappearing would be special.

>> also, i don't think that disabling would be fundamentally different
>> from deleting: the particular code paths taken are somewhat different,
>> but the high-level view is essentially the same. so we can't really
>> make predictions which one would work better.
>
>Creating and deleting needs a lot of different works and much heavier
>tasks.
>
it's entirely plausible that an application would tear down structures 
in response to controls being disabled, too.

>And, above all, many user-space programs will be borked if an
>element goes away, simply crashing.  Some (rather rare) nice ones will
>still survive, though.  I've learned this from the past.
>
yeah, but why should we care? it's not a regression when something new 
doesn't work with some crappy pre-existing code.

>> > And, if we really have to create / delete a kctl element from some
>> > kctl action, don't do it in the callback but process in another work.
>> > 
>> would that really improve anything?
>
>As a primary reason, I don't want to expose such a stuff.  If you need
>such an unlocked version, you're already doing something very exotic,
>and in 99% cases, it's something that needs more care.
>
i don't see being "exotic" as something to avoid per se. and before 
putting in "more care" i want to see some evidence that there is 
actually a problem that needs to be addressed, in this place. esp. when 
the proposed much more complex alternative hasn't been shown to be 
actually better in relevant ways, even theoretically.

regards,
ossi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13 14:00         ` Oswald Buddenhagen
@ 2023-06-13 14:13           ` Takashi Iwai
  2023-06-13 15:23             ` Oswald Buddenhagen
  0 siblings, 1 reply; 22+ messages in thread
From: Takashi Iwai @ 2023-06-13 14:13 UTC (permalink / raw)
  To: Oswald Buddenhagen; +Cc: alsa-devel, Jaroslav Kysela

On Tue, 13 Jun 2023 16:00:34 +0200,
Oswald Buddenhagen wrote:
> 
> On Tue, Jun 13, 2023 at 01:08:55PM +0200, Takashi Iwai wrote:
> > On Tue, 13 Jun 2023 12:52:43 +0200,
> > Oswald Buddenhagen wrote:
> >> 
> >> On Tue, Jun 13, 2023 at 11:20:23AM +0200, Takashi Iwai wrote:
> >> > Creating and removing the controls from kctl put callback is no >
> >> good
> >> > idea.  In general, dynamic control creation/deletion already confuses
> >> > user-space.
> >> > i kind of expected that, but what i've tried so far worked
> >> remarkably
> >> well (ok, it was mostly alsamixer).
> >> 
> >> > On top of that, if it's done by a control element, it can
> >> > be even triggered endlessly by user.
> >> > it shouldn't, because there is no circularity between the
> >> controls. even if the app sets all controls as a response to new ones
> >> appearing, the second round will be a no-op for the multiplier
> >> control, and therefore causes no new creation/deletion notifications,
> >> and thus terminates the recursion.
> > 
> > Hmm I don't get it; if an application just toggles the kctl value
> > between two values in an infinite loop, it'll delete and recreate
> > kctls endlessly as well with your patch, no?
> > 
> yeah, but why should it toggle just so? it's not reasonable to do
> that. 

I'm arguing about a malicious or buggy applications.  Don't ask logics
or conscience behind it.

> >> also, i don't think that disabling would be fundamentally different
> >> from deleting: the particular code paths taken are somewhat different,
> >> but the high-level view is essentially the same. so we can't really
> >> make predictions which one would work better.
> > 
> > Creating and deleting needs a lot of different works and much heavier
> > tasks.
> > 
> it's entirely plausible that an application would tear down structures
> in response to controls being disabled, too.

But it's less dangerous.

> > And, above all, many user-space programs will be borked if an
> > element goes away, simply crashing.  Some (rather rare) nice ones will
> > still survive, though.  I've learned this from the past.
> > 
> yeah, but why should we care? it's not a regression when something new
> doesn't work with some crappy pre-existing code.

We can't break user-space.  That's a rule set in stone.

> >> > And, if we really have to create / delete a kctl element from some
> >> > kctl action, don't do it in the callback but process in another work.
> >> > would that really improve anything?
> > 
> > As a primary reason, I don't want to expose such a stuff.  If you need
> > such an unlocked version, you're already doing something very exotic,
> > and in 99% cases, it's something that needs more care.
> > 
> i don't see being "exotic" as something to avoid per se. and before
> putting in "more care" i want to see some evidence that there is
> actually a problem that needs to be addressed, in this
> place. esp. when the proposed much more complex alternative hasn't
> been shown to be actually better in relevant ways, even theoretically.

Well, then another, maybe foremost reason: you can't create / delete
kctls from the callback, simply because the callbacks are called in
the read lock.  Adding / deleting an element may crash the another
concurrent task that traverses the list.


Takashi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13 14:13           ` Takashi Iwai
@ 2023-06-13 15:23             ` Oswald Buddenhagen
  2023-06-13 15:43               ` Takashi Iwai
  0 siblings, 1 reply; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13 15:23 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, Jaroslav Kysela

On Tue, Jun 13, 2023 at 04:13:57PM +0200, Takashi Iwai wrote:
>On Tue, 13 Jun 2023 16:00:34 +0200,
>Oswald Buddenhagen wrote:
>> 
>> On Tue, Jun 13, 2023 at 01:08:55PM +0200, Takashi Iwai wrote:
>> > Hmm I don't get it; if an application just toggles the kctl value
>> > between two values in an infinite loop, it'll delete and recreate
>> > kctls endlessly as well with your patch, no?
>> > 
>> yeah, but why should it toggle just so? it's not reasonable to do
>> that. 
>
>I'm arguing about a malicious or buggy applications.  Don't ask logics
>or conscience behind it.
>
yes, that was exactly the point of the sentence you cut away. it can be 
broken in any number of "creative" ways. there is absolutely no point in 
trying to prevent that.

the notion of "malicious" is meaningless in this context. a valid attack 
vector would allow the application to do something that i cannot do 
otherwise. hogging a cpu thread while flooding the system with 
meaningless ioctls is something an app can do regardless, so whatever.

>> >> also, i don't think that disabling would be fundamentally different
>> >> from deleting: the particular code paths taken are somewhat different,
>> >> but the high-level view is essentially the same. so we can't really
>> >> make predictions which one would work better.
>> > 
>> > Creating and deleting needs a lot of different works and much heavier
>> > tasks.
>> > 
>> it's entirely plausible that an application would tear down structures
>> in response to controls being disabled, too.
>
>But it's less dangerous.
>
if the app does mostly the same in both cases, then obviously neither 
one is any less dangerous than the other one.

there is also the opposite angle to this, which makes it an own goal for 
you: if the app did in fact respond to the elements being disabled by 
merely disabling them in the user interface, then having the currently 
inactive (but superficially identical) controls at all times would 
contribute to a rather horrible user experience. so for this reason 
alone it's better to actually delete the inapplicable set of controls.

>> > And, above all, many user-space programs will be borked if an
>> > element goes away, simply crashing.  Some (rather rare) nice ones will
>> > still survive, though.  I've learned this from the past.
>> > 
>> yeah, but why should we care? it's not a regression when something new
>> doesn't work with some crappy pre-existing code.
>
>We can't break user-space.  That's a rule set in stone.
>
that rule means that we may not cause regressions, which we would not.

>Well, then another, maybe foremost reason: you can't create / delete
>kctls from the callback, simply because the callbacks are called in
>the read lock.  Adding / deleting an element may crash the another
>concurrent task that traverses the list.
>
that would indeed be a problem, but fortunately the put() callback is 
nowadays invoked with a write lock (see also commit 06405d8ee).

also, please go back to the first paragraph of the commit message of 
patch 5 in the series.

regards,
ossi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13 15:23             ` Oswald Buddenhagen
@ 2023-06-13 15:43               ` Takashi Iwai
  2023-06-13 17:14                 ` Oswald Buddenhagen
  0 siblings, 1 reply; 22+ messages in thread
From: Takashi Iwai @ 2023-06-13 15:43 UTC (permalink / raw)
  To: Oswald Buddenhagen; +Cc: alsa-devel, Jaroslav Kysela

On Tue, 13 Jun 2023 17:23:35 +0200,
Oswald Buddenhagen wrote:
> 
> On Tue, Jun 13, 2023 at 04:13:57PM +0200, Takashi Iwai wrote:
> > On Tue, 13 Jun 2023 16:00:34 +0200,
> > Oswald Buddenhagen wrote:
> >> 
> >> On Tue, Jun 13, 2023 at 01:08:55PM +0200, Takashi Iwai wrote:
> >> > Hmm I don't get it; if an application just toggles the kctl value
> >> > between two values in an infinite loop, it'll delete and recreate
> >> > kctls endlessly as well with your patch, no?
> >> > yeah, but why should it toggle just so? it's not reasonable to do
> >> that. 
> > 
> > I'm arguing about a malicious or buggy applications.  Don't ask logics
> > or conscience behind it.
> > 
> yes, that was exactly the point of the sentence you cut away. it can
> be broken in any number of "creative" ways. there is absolutely no
> point in trying to prevent that.

We need to give our best to protect from malicious behavior.

> the notion of "malicious" is meaningless in this context. a valid
> attack vector would allow the application to do something that i
> cannot do otherwise. hogging a cpu thread while flooding the system
> with meaningless ioctls is something an app can do regardless, so
> whatever.

Adding/deleting kctl increases the numid.  It grows and grows.

> >> >> also, i don't think that disabling would be fundamentally different
> >> >> from deleting: the particular code paths taken are somewhat different,
> >> >> but the high-level view is essentially the same. so we can't really
> >> >> make predictions which one would work better.
> >> > > Creating and deleting needs a lot of different works and much
> >> heavier
> >> > tasks.
> >> > it's entirely plausible that an application would tear down
> >> structures
> >> in response to controls being disabled, too.
> > 
> > But it's less dangerous.
> > 
> if the app does mostly the same in both cases, then obviously neither
> one is any less dangerous than the other one.
> 
> there is also the opposite angle to this, which makes it an own goal
> for you: if the app did in fact respond to the elements being disabled
> by merely disabling them in the user interface, then having the
> currently inactive (but superficially identical) controls at all times
> would contribute to a rather horrible user experience. so for this
> reason alone it's better to actually delete the inapplicable set of
> controls.

Crashing an existing application is the worst-case scenario.

> >> > And, above all, many user-space programs will be borked if an
> >> > element goes away, simply crashing.  Some (rather rare) nice ones will
> >> > still survive, though.  I've learned this from the past.
> >> > yeah, but why should we care? it's not a regression when
> >> something new
> >> doesn't work with some crappy pre-existing code.
> > 
> > We can't break user-space.  That's a rule set in stone.
> > 
> that rule means that we may not cause regressions, which we would not.
> 
> > Well, then another, maybe foremost reason: you can't create / delete
> > kctls from the callback, simply because the callbacks are called in
> > the read lock.  Adding / deleting an element may crash the another
> > concurrent task that traverses the list.
> > 
> that would indeed be a problem, but fortunately the put() callback is
> nowadays invoked with a write lock (see also commit 06405d8ee).

Oh well, that's really not a change to be advertised for creating /
deleting kctls from the put callback at all.

Sorry, but my answer is same: NO.  I see no reason why kctl deletion
and creation _must_ be implemented _inevitably_ in that way.

We need a different implementation, some middle ground one.

> also, please go back to the first paragraph of the commit message of
> patch 5 in the series.

Actually, snd_ctl_remove() should be changed back to a version that
takes the lock by itself instead.  There is no reason to have a helper
without the lock called from leaf drivers.

IOW, ideally, the drivers shouldn't need to mimic with card rwsem.


Takashi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13 15:43               ` Takashi Iwai
@ 2023-06-13 17:14                 ` Oswald Buddenhagen
  2023-06-14  6:36                   ` Takashi Iwai
  0 siblings, 1 reply; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-13 17:14 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, Jaroslav Kysela

On Tue, Jun 13, 2023 at 05:43:58PM +0200, Takashi Iwai wrote:
>> the notion of "malicious" is meaningless in this context. a valid
>> attack vector would allow the application to do something that i
>> cannot do otherwise. hogging a cpu thread while flooding the system
>> with meaningless ioctls is something an app can do regardless, so
>> whatever.
>
>Adding/deleting kctl increases the numid.  It grows and grows.
>
as the code handles numid wraparound just fine, that would be a rather 
pointless attack.

>Crashing an existing application is the worst-case scenario.
>
a new driver (which this effectively is) crashing a broken application 
is perfectly legitimate, as it doesn't affect any existing users.

>> that would indeed be a problem, but fortunately the put() callback is
>> nowadays invoked with a write lock (see also commit 06405d8ee).
>
>Oh well, that's really not a change to be advertised for creating /
>deleting kctls from the put callback at all.
>
and? it's done, and it's basically impossible to revert. so we may reap 
its full benefits just as well, as i did in that previous commit.

>Sorry, but my answer is same: NO.  I see no reason why kctl deletion
>and creation _must_ be implemented _inevitably_ in that way.
>
being the most straight-forward way to implement it certainly qualifies 
as a good reason for doing it that way.
and i still see no convincing reason why it shouldn't.

>Actually, snd_ctl_remove() should be changed back to a version that
>takes the lock by itself instead.  There is no reason to have a helper
>without the lock called from leaf drivers.
>
well, except that this driver shows that there _is_ a reason. one may 
choose to throw stones in one's own way, but that's rarely a wise 
decision ...

regards,
ossi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-13 17:14                 ` Oswald Buddenhagen
@ 2023-06-14  6:36                   ` Takashi Iwai
  2023-06-14  8:52                     ` Oswald Buddenhagen
  0 siblings, 1 reply; 22+ messages in thread
From: Takashi Iwai @ 2023-06-14  6:36 UTC (permalink / raw)
  To: Oswald Buddenhagen; +Cc: alsa-devel, Jaroslav Kysela

On Tue, 13 Jun 2023 19:14:18 +0200,
Oswald Buddenhagen wrote:
> 
> On Tue, Jun 13, 2023 at 05:43:58PM +0200, Takashi Iwai wrote:
> >> the notion of "malicious" is meaningless in this context. a valid
> >> attack vector would allow the application to do something that i
> >> cannot do otherwise. hogging a cpu thread while flooding the system
> >> with meaningless ioctls is something an app can do regardless, so
> >> whatever.
> > 
> > Adding/deleting kctl increases the numid.  It grows and grows.
> > 
> as the code handles numid wraparound just fine, that would be a rather
> pointless attack.
> 
> > Crashing an existing application is the worst-case scenario.
> > 
> a new driver (which this effectively is) crashing a broken application
> is perfectly legitimate, as it doesn't affect any existing users.

No, you can't ignore it.

> >> that would indeed be a problem, but fortunately the put() callback is
> >> nowadays invoked with a write lock (see also commit 06405d8ee).
> > 
> > Oh well, that's really not a change to be advertised for creating /
> > deleting kctls from the put callback at all.
> > 
> and? it's done, and it's basically impossible to revert. so we may
> reap its full benefits just as well, as i did in that previous commit.

Well, I can revert your commit, too...
Basically the content protection shouldn't be covered by this rwsem.
It's rather a misuse.

> > Sorry, but my answer is same: NO.  I see no reason why kctl deletion
> > and creation _must_ be implemented _inevitably_ in that way.
> > 
> being the most straight-forward way to implement it certainly
> qualifies as a good reason for doing it that way.
> and i still see no convincing reason why it shouldn't.

I still see no convincing reason why it must be done so, either.
The way you're trying to implement is an anti-pattern, not seen in
other drivers that have been developed over decades.

> > Actually, snd_ctl_remove() should be changed back to a version that
> > takes the lock by itself instead.  There is no reason to have a helper
> > without the lock called from leaf drivers.
> > 
> well, except that this driver shows that there _is_ a reason. one may
> choose to throw stones in one's own way, but that's rarely a wise
> decision ...

The fact that it has to take a rwsem from the caller side itself is a
very bad design, and it should be corrected at best.  The rwsem there
is rather an internal stuff and shouldn't be taken explicitly.  Most
of its use outside control.c is an abuse.


Takashi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-14  6:36                   ` Takashi Iwai
@ 2023-06-14  8:52                     ` Oswald Buddenhagen
  2023-06-14  9:16                       ` Takashi Iwai
  0 siblings, 1 reply; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-14  8:52 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, Jaroslav Kysela

On Wed, Jun 14, 2023 at 08:36:47AM +0200, Takashi Iwai wrote:
>On Tue, 13 Jun 2023 19:14:18 +0200,
>Oswald Buddenhagen wrote:
>> On Tue, Jun 13, 2023 at 05:43:58PM +0200, Takashi Iwai wrote:
>> > Crashing an existing application is the worst-case scenario.
>> > 
>> a new driver (which this effectively is) crashing a broken application
>> is perfectly legitimate, as it doesn't affect any existing users.
>
>No, you can't ignore it.
>
you're allowing _hypothetical_ crappy 3rd party code to dictate what you 
can and cannot do. that's a completely unreasonable and 
counterproductive attitude, akin to letting hostage-takers set the 
rules.

>> > Oh well, that's really not a change to be advertised for creating /
>> > deleting kctls from the put callback at all.
>> > 
>> and? it's done, and it's basically impossible to revert. so we may
>> reap its full benefits just as well, as i did in that previous commit.
>
>Well, I can revert your commit, too...
>
sure, but my point was that there are likely many more such commits, 
some of them not explicitly marked as such. it would be a very costly 
and risky exercise to actually do that revert at this point.

>Basically the content protection shouldn't be covered by this rwsem.
>It's rather a misuse.
>
yes, sort of.
otoh, the commit message is rather convincing, and you clearly saw it 
that way as well.

>> > Sorry, but my answer is same: NO.  I see no reason why kctl 
>> > deletion
>> > and creation _must_ be implemented _inevitably_ in that way.
>> > 
>> being the most straight-forward way to implement it certainly
>> qualifies as a good reason for doing it that way.
>> and i still see no convincing reason why it shouldn't.
>
>I still see no convincing reason why it must be done so, either.
>
the very convincing reason is that it was already done that way, and 
you'd have to bring forward a very convincing justification for further 
investment into a much more complex solution, esp. considering what 
driver we're talking about.

>The way you're trying to implement is an anti-pattern,
>
that's something you keep repeating in various ways, but i see no 
evidence that there is an _actual_ problem.

>not seen in other drivers that have been developed over decades.
>
that alone doesn't mean anything.

what are SNDRV_CTL_EVENT_MASK_{ADD,REMOVE} for, if not exactly that? if 
you're afraid to use it because it makes some poorly written apps crash, 
then you may delete the API just as well.

and as i've argued in a previous mail, doing it synchronously from the 
put() callback would cause an additional risk only for poorly coded 
multi-threaded apps, another purely hypothetical problem. it certainly 
does not appear to create problems in the kernel, given current locking 
realities (though it would not hurt to have a few more eyes confirm 
that).

regards,
ossi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-14  8:52                     ` Oswald Buddenhagen
@ 2023-06-14  9:16                       ` Takashi Iwai
  2023-06-14 10:53                         ` Oswald Buddenhagen
  0 siblings, 1 reply; 22+ messages in thread
From: Takashi Iwai @ 2023-06-14  9:16 UTC (permalink / raw)
  To: Oswald Buddenhagen; +Cc: alsa-devel, Jaroslav Kysela

On Wed, 14 Jun 2023 10:52:54 +0200,
Oswald Buddenhagen wrote:
> 
> On Wed, Jun 14, 2023 at 08:36:47AM +0200, Takashi Iwai wrote:
> > On Tue, 13 Jun 2023 19:14:18 +0200,
> > Oswald Buddenhagen wrote:
> >> On Tue, Jun 13, 2023 at 05:43:58PM +0200, Takashi Iwai wrote:
> >> > Crashing an existing application is the worst-case scenario.
> >> > a new driver (which this effectively is) crashing a broken
> >> application
> >> is perfectly legitimate, as it doesn't affect any existing users.
> > 
> > No, you can't ignore it.
> > 
> you're allowing _hypothetical_ crappy 3rd party code to dictate what
> you can and cannot do. that's a completely unreasonable and
> counterproductive attitude, akin to letting hostage-takers set the
> rules.

Oswald, it's no hypothetical, I have seen lots of applications that
did crash with such mixer element changes in the past.
It's no dictation by 3rd party.  We simply must not crash things by an
update (unless it's a must, something like a security fix).

> >> > Oh well, that's really not a change to be advertised for creating /
> >> > deleting kctls from the put callback at all.
> >> > and? it's done, and it's basically impossible to revert. so we
> >> may
> >> reap its full benefits just as well, as i did in that previous commit.
> > 
> > Well, I can revert your commit, too...
> > 
> sure, but my point was that there are likely many more such commits,
> some of them not explicitly marked as such. it would be a very costly
> and risky exercise to actually do that revert at this point.

Sure, I didn't mean to do it immediately, it's no easy task.

> > Basically the content protection shouldn't be covered by this rwsem.
> > It's rather a misuse.
> > 
> yes, sort of.
> otoh, the commit message is rather convincing, and you clearly saw it
> that way as well.

I wasn't really convinced at that time, too, but the commit was the
easiest workaround, so we agreed on taking it.  Basically it's still a
bad idea to use cards_rwsem lock for the content protection of each
kctl.  This should be revised, but it'll be a much wider work than a
single revert or such, and certainly a lower priority task.

> >> > Sorry, but my answer is same: NO.  I see no reason why kctl >
> >> deletion
> >> > and creation _must_ be implemented _inevitably_ in that way.
> >> > being the most straight-forward way to implement it certainly
> >> qualifies as a good reason for doing it that way.
> >> and i still see no convincing reason why it shouldn't.
> > 
> > I still see no convincing reason why it must be done so, either.
> > 
> the very convincing reason is that it was already done that way, and
> you'd have to bring forward a very convincing justification for
> further investment into a much more complex solution, esp. considering
> what driver we're talking about.

Nah, as I repeatedly wrote, the whole idea is too risky, may crash
things easily, so should be avoided as much as possible.
A correct use of API doesn't mean that everything keeps working as is,
unfortunately.

> > The way you're trying to implement is an anti-pattern,
> > 
> that's something you keep repeating in various ways, but i see no
> evidence that there is an _actual_ problem.

There were actual problems, and we had to address them.

The API is there and it should be usable in the ideal world, but we
know that it breaks far more than expected.  We don't prohibit that
API, but the actual use should be limited for very special use cases.

If it were triggered in only certain (rare and race-free) situations,
it'd be acceptable.  But your patch allows every user to trigger it by
the normal kctl value adjustment, which is simply no-go.


Takashi

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

* Re: [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode
  2023-06-14  9:16                       ` Takashi Iwai
@ 2023-06-14 10:53                         ` Oswald Buddenhagen
  0 siblings, 0 replies; 22+ messages in thread
From: Oswald Buddenhagen @ 2023-06-14 10:53 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, Jaroslav Kysela

On Wed, Jun 14, 2023 at 11:16:19AM +0200, Takashi Iwai wrote:
>On Wed, 14 Jun 2023 10:52:54 +0200,
>Oswald Buddenhagen wrote:
>> 
>> you're allowing _hypothetical_ crappy 3rd party code to dictate what
>> you can and cannot do. that's a completely unreasonable and
>> counterproductive attitude, akin to letting hostage-takers set the
>> rules.
>
>Oswald, it's no hypothetical, I have seen lots of applications that
>did crash with such mixer element changes in the past.
>
these apps have been meanwhile fixed or become obsolete, which makes it 
a hypothetical again.

>It's no dictation by 3rd party.
>
it IS. that's exactly what letting downstream limit your possibilities 
is. especially if it's BROKEN downstream.

>We simply must not crash things by an
>update (unless it's a must, something like a security fix).
>
and i'm telling you that this is an unreasonable reading of the rule.  
_every_ new feature in something already existing has the potential to 
blow up some crappy downstream code.

>> >> > Oh well, that's really not a change to be advertised for creating /
>> >> > deleting kctls from the put callback at all.
>> >> > and? it's done, and it's basically impossible to revert. so we
>> >> may
>> >> reap its full benefits just as well, as i did in that previous commit.
>> > 
>> > Well, I can revert your commit, too...
>> > 
>> sure, but my point was that there are likely many more such commits,
>> some of them not explicitly marked as such. it would be a very costly
>> and risky exercise to actually do that revert at this point.
>
>Sure, I didn't mean to do it immediately, it's no easy task.
>
great! then you can adjust this driver at that later point as well, when 
you actually want to go forward with that project. ;-)

>> > The way you're trying to implement is an anti-pattern,
>> > 
>> that's something you keep repeating in various ways, but i see no
>> evidence that there is an _actual_ problem.
>
>There were actual problems, and we had to address them.
>
what exactly where those problems?
do the circumstances under which they occurred still apply?

>The API is there and it should be usable in the ideal world, but we
>know that it breaks far more than expected.  We don't prohibit that
>API, but the actual use should be limited for very special use cases.
>
that's exactly the wrong way to go about it. the way to make sure that 
fewer apps crash is to hammer them as much as possible. if you wanted to 
make sure that they all *really* work, you'd randomly create and destroy 
fake controls from time to time.

>If it were triggered in only certain (rare and race-free) situations,
>it'd be acceptable.  But your patch allows every user to trigger it by
>the normal kctl value adjustment, which is simply no-go.
>
you are describing a completely contrieved attack scenario. it would 
have to be a multi-user system with an e-mu card where one user 
intentionally messes with the mixer to crash a broken application 
another user is using at the same time. think through the probabilities, 
motivations, alternative attack vectors, and how the whole affair would 
play out IRL for the attacker.

regards,
ossi

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

* Re: [PATCH 8/8] ALSA: emu10k1: add high-rate playback in E-MU D.A.S. mode
  2023-06-13  7:38 ` [PATCH 8/8] ALSA: emu10k1: add high-rate playback " Oswald Buddenhagen
@ 2023-06-22  7:05   ` kernel test robot
  0 siblings, 0 replies; 22+ messages in thread
From: kernel test robot @ 2023-06-22  7:05 UTC (permalink / raw)
  To: Oswald Buddenhagen, alsa-devel
  Cc: oe-kbuild-all, Takashi Iwai, Jaroslav Kysela

Hi Oswald,

kernel test robot noticed the following build warnings:

[auto build test WARNING on tiwai-sound/for-next]
[also build test WARNING on next-20230621]
[cannot apply to tiwai-sound/for-linus linus/master v6.4-rc7]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Oswald-Buddenhagen/ALSA-emu10k1-introduce-alternative-E-MU-D-A-S-mode/20230613-154242
base:   https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git for-next
patch link:    https://lore.kernel.org/r/20230613073822.1343234-9-oswald.buddenhagen%40gmx.de
patch subject: [PATCH 8/8] ALSA: emu10k1: add high-rate playback in E-MU D.A.S. mode
config: i386-randconfig-s002-20230621 (https://download.01.org/0day-ci/archive/20230622/202306221430.7QukSHhG-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce: (https://download.01.org/0day-ci/archive/20230622/202306221430.7QukSHhG-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202306221430.7QukSHhG-lkp@intel.com/

sparse warnings: (new ones prefixed by >>)
>> sound/pci/emu10k1/emupcm.c:1180:45: sparse: sparse: cast removes address space '__user' of expression
>> sound/pci/emu10k1/emupcm.c:1182:41: sparse: sparse: incorrect type in argument 1 (different address spaces) @@     expected void const volatile [noderef] __user *ptr @@     got unsigned int [usertype] *src @@
   sound/pci/emu10k1/emupcm.c:1182:41: sparse:     expected void const volatile [noderef] __user *ptr
   sound/pci/emu10k1/emupcm.c:1182:41: sparse:     got unsigned int [usertype] *src

vim +/__user +1180 sound/pci/emu10k1/emupcm.c

  1146	
  1147	static int snd_emu10k1_efx_playback_copy_user(struct snd_pcm_substream *substream,
  1148						      int channel, unsigned long hwoff,
  1149						      void __user *buf, unsigned long bytes)
  1150	{
  1151		struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream);
  1152		struct snd_pcm_runtime *runtime = substream->runtime;
  1153		unsigned shift = emu->emu1010.clock_shift;
  1154		unsigned i, j, k, channels, subchans, voices, frame_size, frames;
  1155	
  1156		if (!shift) {
  1157			// Non-interleaved source
  1158			if (copy_from_user(get_dma_ptr(runtime, channel, hwoff), buf, bytes))
  1159				return -EFAULT;
  1160		} else {
  1161			// Interleaved source
  1162			channels = runtime->channels;
  1163			subchans = 1 << shift;
  1164			voices = channels << shift;
  1165			frame_size = voices << 2;
  1166			// It is recommended that writes are period-sized, and it appears
  1167			// unlikely that someone would actually use a period size which
  1168			// is not divisible by four, so don't bother making it work.
  1169			// This check should also prevent that hwoff becomes unaligned.
  1170			// Ideally, snd_pcm_sw_params.xfer_align would handle this ...
  1171			if (bytes % frame_size)
  1172				return -EIO;
  1173			frames = bytes / frame_size;
  1174			hwoff /= voices;
  1175			if (!user_access_begin(buf, bytes))
  1176				return -EFAULT;
  1177			for (i = 0; i < channels; i++) {
  1178				for (j = 0; j < subchans; j++) {
  1179					u32 *dst = get_dma_ptr_x(runtime, shift, i, j, hwoff);
> 1180					u32 *src = (u32 *)buf + j * channels + i;
  1181					for (k = 0; k < frames; k++, dst++, src += voices)
> 1182						unsafe_get_user(*dst, src, faulted);
  1183				}
  1184			}
  1185			user_access_end();
  1186		}
  1187		return 0;
  1188	
  1189	faulted:
  1190		user_access_end();
  1191		return -EFAULT;
  1192	}
  1193	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

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

end of thread, other threads:[~2023-06-22  7:06 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-13  7:38 [PATCH 0/8] ALSA: emu10k1: add support for high-bitrate modes of E-MU cards Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 1/8] ALSA: emu10k1: introduce alternative E-MU D.A.S. mode Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 2/8] ALSA: emu10k1: improve mixer control naming in " Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 3/8] ALSA: emu10k1: set the "no filtering" bits on PCM voices Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 4/8] ALSA: emu10k1: make playback in E-MU D.A.S. mode 32-bit Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 5/8] ALSA: add snd_ctl_add_locked() Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 6/8] ALSA: emu10k1: add support for 2x/4x word clocks in E-MU D.A.S. mode Oswald Buddenhagen
2023-06-13  9:20   ` Takashi Iwai
2023-06-13 10:52     ` Oswald Buddenhagen
2023-06-13 11:08       ` Takashi Iwai
2023-06-13 14:00         ` Oswald Buddenhagen
2023-06-13 14:13           ` Takashi Iwai
2023-06-13 15:23             ` Oswald Buddenhagen
2023-06-13 15:43               ` Takashi Iwai
2023-06-13 17:14                 ` Oswald Buddenhagen
2023-06-14  6:36                   ` Takashi Iwai
2023-06-14  8:52                     ` Oswald Buddenhagen
2023-06-14  9:16                       ` Takashi Iwai
2023-06-14 10:53                         ` Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 7/8] ALSA: emu10k1: add high-rate capture " Oswald Buddenhagen
2023-06-13  7:38 ` [PATCH 8/8] ALSA: emu10k1: add high-rate playback " Oswald Buddenhagen
2023-06-22  7:05   ` kernel test robot

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.