From 16cafbd97759ba66c79bdede4250074f026839f3 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:36 +0200 Subject: ALSA: emu10k1: remove pointless locks from timer code Contrary to its name, reg_lock locks the emu data structure, not the registers. As the functions access only data which is set once at card initialization, there is no point in locking it. Actually locking the registers would be pointless as well, as snd_emu10k1_intr_{en,dis}able() does its own locking, and TIMER is accessed only in this one place. Locking snd_emu10k1_timer_{start,stop}() against each other also wouldn't buy us anything; the functions interleaving their I/O accesses wouldn't introduce new problems. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/timer.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/timer.c b/sound/pci/emu10k1/timer.c index 2435d3ba68f7..993663fef885 100644 --- a/sound/pci/emu10k1/timer.c +++ b/sound/pci/emu10k1/timer.c @@ -18,29 +18,23 @@ static int snd_emu10k1_timer_start(struct snd_timer *timer) { struct snd_emu10k1 *emu; - unsigned long flags; unsigned int delay; emu = snd_timer_chip(timer); delay = timer->sticks - 1; if (delay < 5 ) /* minimum time is 5 ticks */ delay = 5; - spin_lock_irqsave(&emu->reg_lock, flags); snd_emu10k1_intr_enable(emu, INTE_INTERVALTIMERENB); outw(delay & TIMER_RATE_MASK, emu->port + TIMER); - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } static int snd_emu10k1_timer_stop(struct snd_timer *timer) { struct snd_emu10k1 *emu; - unsigned long flags; emu = snd_timer_chip(timer); - spin_lock_irqsave(&emu->reg_lock, flags); snd_emu10k1_intr_disable(emu, INTE_INTERVALTIMERENB); - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } -- cgit v1.2.3 From 71781147dabdd0b256542bb51122f376db740b0d Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:37 +0200 Subject: ALSA: emu10k1: remove pointless locks from /proc code emu_lock locks the card's registers, but that's necessary only for multi-register access, incl. read-modify-write cycles. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 6 ------ 1 file changed, 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index bec72dc60a41..c92253de881e 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -399,13 +399,10 @@ static void snd_emu_proc_io_reg_read(struct snd_info_entry *entry, { struct snd_emu10k1 *emu = entry->private_data; unsigned long value; - unsigned long flags; int i; snd_iprintf(buffer, "IO Registers:\n\n"); for(i = 0; i < 0x40; i+=4) { - spin_lock_irqsave(&emu->emu_lock, flags); value = inl(emu->port + i); - spin_unlock_irqrestore(&emu->emu_lock, flags); snd_iprintf(buffer, "%02X: %08lX\n", i, value); } } @@ -414,16 +411,13 @@ static void snd_emu_proc_io_reg_write(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct snd_emu10k1 *emu = entry->private_data; - unsigned long flags; char line[64]; u32 reg, val; while (!snd_info_get_line(buffer, line, sizeof(line))) { if (sscanf(line, "%x %x", ®, &val) != 2) continue; if (reg < 0x40 && val <= 0xffffffff) { - spin_lock_irqsave(&emu->emu_lock, flags); outl(val, emu->port + (reg & 0xfffffffc)); - spin_unlock_irqrestore(&emu->emu_lock, flags); } } } -- cgit v1.2.3 From 50164f69f8c71377bfa3d356b8a6bc18b2197a45 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:38 +0200 Subject: ALSA: emu10k1: use the right lock in snd_emu10k1_shared_spdif_put() The function does read-modify-write cycles on the card's registers, and doesn't access mutable members of the emu data structure. I suppose this might have been a mixup due to the lock names being logically swapped. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 3ebc7c36a444..24052f17d81c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1654,7 +1654,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, sw = ucontrol->value.integer.value[0]; if (emu->card_capabilities->invert_shared_spdif) sw = !sw; - spin_lock_irqsave(&emu->reg_lock, flags); + spin_lock_irqsave(&emu->emu_lock, flags); if ( emu->card_capabilities->i2c_adc) { /* Do nothing for Audigy 2 ZS Notebook */ } else if (emu->audigy) { @@ -1675,7 +1675,7 @@ static int snd_emu10k1_shared_spdif_put(struct snd_kcontrol *kcontrol, reg |= val; outl(reg | val, emu->port + HCFG); } - spin_unlock_irqrestore(&emu->reg_lock, flags); + spin_unlock_irqrestore(&emu->emu_lock, flags); return change; } -- cgit v1.2.3 From 35d1d5824ffe1cfc849aa1694488d779dc79a920 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:39 +0200 Subject: ALSA: emu10k1: fix locking in snd_emu1010_fpga_link_dst_src_write() This is a multi-register operation, which must be locked in its entirety. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index cfb96a67aa35..aee84c3f9f37 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -233,16 +233,13 @@ int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu, return err; } -void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) +static void snd_emu1010_fpga_write_locked(struct snd_emu10k1 *emu, u32 reg, u32 value) { - unsigned long flags; - if (snd_BUG_ON(reg > 0x3f)) return; reg += 0x40; /* 0x40 upwards are registers. */ if (snd_BUG_ON(value > 0x3f)) /* 0 to 0x3f are values */ return; - spin_lock_irqsave(&emu->emu_lock, flags); outw(reg, emu->port + A_GPIO); udelay(10); outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ @@ -250,6 +247,14 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) outw(value, emu->port + A_GPIO); udelay(10); outw(value | 0x80 , emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ +} + +void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_write_locked(emu, reg, value); spin_unlock_irqrestore(&emu->emu_lock, flags); } @@ -276,14 +281,18 @@ void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value) */ void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src) { + unsigned long flags; + if (snd_BUG_ON(dst & ~0x71f)) return; if (snd_BUG_ON(src & ~0x71f)) return; - snd_emu1010_fpga_write(emu, EMU_HANA_DESTHI, dst >> 8); - snd_emu1010_fpga_write(emu, EMU_HANA_DESTLO, dst & 0x1f); - snd_emu1010_fpga_write(emu, EMU_HANA_SRCHI, src >> 8); - snd_emu1010_fpga_write(emu, EMU_HANA_SRCLO, src & 0x1f); + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTHI, dst >> 8); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTLO, dst & 0x1f); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_SRCHI, src >> 8); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_SRCLO, src & 0x1f); + spin_unlock_irqrestore(&emu->emu_lock, flags); } void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb) -- cgit v1.2.3 From 06405d8ee8c3bac5b3a4ddda58f6431187a3be48 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:41 +0200 Subject: ALSA: emu10k1: remove now superfluous mixer locking Since commit 5bbb1ab5bd ("control: use counting semaphore as write lock for ELEM_WRITE operation"), mixer values have been fully read-write locked. This means that it is now unnecessary to apply any additional locks to values that are accessed solely by mixer callbacks. Values that are read outside mixer callbacks still need write locking. There are no cases of mixer values being written outside mixer callbacks, so no read locks remain in mixer callbacks. Note that the removed locks refer only to the emu data structure, not the card's registers as the lock's name suggests. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706278-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 7 ------- sound/pci/emu10k1/emumixer.c | 28 ---------------------------- sound/pci/emu10k1/emupcm.c | 2 -- 3 files changed, 37 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 3f64ccab0e63..98785110ef63 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -318,16 +318,12 @@ static int snd_emu10k1_gpr_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ct static int snd_emu10k1_gpr_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_fx8010_ctl *ctl = (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; - unsigned long flags; unsigned int i; - spin_lock_irqsave(&emu->reg_lock, flags); for (i = 0; i < ctl->vcount; i++) ucontrol->value.integer.value[i] = ctl->value[i]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -336,12 +332,10 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_fx8010_ctl *ctl = (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; - unsigned long flags; unsigned int nval, val; unsigned int i, j; int change = 0; - spin_lock_irqsave(&emu->reg_lock, flags); for (i = 0; i < ctl->vcount; i++) { nval = ucontrol->value.integer.value[i]; if (nval < ctl->min) @@ -380,7 +374,6 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl } } __error: - spin_unlock_irqrestore(&emu->reg_lock, flags); return change; } diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 24052f17d81c..ab04f8be25bd 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -41,17 +41,14 @@ static int snd_emu10k1_spdif_get(struct snd_kcontrol *kcontrol, { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); - unsigned long flags; /* Limit: emu->spdif_bits */ if (idx >= 3) return -EINVAL; - spin_lock_irqsave(&emu->reg_lock, flags); ucontrol->value.iec958.status[0] = (emu->spdif_bits[idx] >> 0) & 0xff; ucontrol->value.iec958.status[1] = (emu->spdif_bits[idx] >> 8) & 0xff; ucontrol->value.iec958.status[2] = (emu->spdif_bits[idx] >> 16) & 0xff; ucontrol->value.iec958.status[3] = (emu->spdif_bits[idx] >> 24) & 0xff; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1070,10 +1067,7 @@ static int snd_audigy_spdif_output_rate_get(struct snd_kcontrol *kcontrol, { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int tmp; - unsigned long flags; - - spin_lock_irqsave(&emu->reg_lock, flags); tmp = snd_emu10k1_ptr_read(emu, A_SPDIF_SAMPLERATE, 0); switch (tmp & A_SPDIF_RATE_MASK) { case A_SPDIF_44100: @@ -1088,7 +1082,6 @@ static int snd_audigy_spdif_output_rate_get(struct snd_kcontrol *kcontrol, default: ucontrol->value.enumerated.item[0] = 1; } - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1146,7 +1139,6 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); int change; unsigned int val; - unsigned long flags; /* Limit: emu->spdif_bits */ if (idx >= 3) @@ -1155,13 +1147,11 @@ static int snd_emu10k1_spdif_put(struct snd_kcontrol *kcontrol, (ucontrol->value.iec958.status[1] << 8) | (ucontrol->value.iec958.status[2] << 16) | (ucontrol->value.iec958.status[3] << 24); - spin_lock_irqsave(&emu->reg_lock, flags); change = val != emu->spdif_bits[idx]; if (change) { snd_emu10k1_ptr_write(emu, SPCS0 + idx, 0, val); emu->spdif_bits[idx] = val; } - spin_unlock_irqrestore(&emu->reg_lock, flags); return change; } @@ -1229,7 +1219,6 @@ static int snd_emu10k1_send_routing_info(struct snd_kcontrol *kcontrol, struct s static int snd_emu10k1_send_routing_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; @@ -1237,12 +1226,10 @@ static int snd_emu10k1_send_routing_get(struct snd_kcontrol *kcontrol, int num_efx = emu->audigy ? 8 : 4; int mask = emu->audigy ? 0x3f : 0x0f; - spin_lock_irqsave(&emu->reg_lock, flags); for (voice = 0; voice < 3; voice++) for (idx = 0; idx < num_efx; idx++) ucontrol->value.integer.value[(voice * num_efx) + idx] = mix->send_routing[voice][idx] & mask; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1305,17 +1292,14 @@ static int snd_emu10k1_send_volume_info(struct snd_kcontrol *kcontrol, struct sn static int snd_emu10k1_send_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; int idx; int num_efx = emu->audigy ? 8 : 4; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < 3*num_efx; idx++) ucontrol->value.integer.value[idx] = mix->send_volume[idx/num_efx][idx%num_efx]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1378,13 +1362,10 @@ static int snd_emu10k1_attn_get(struct snd_kcontrol *kcontrol, struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; - unsigned long flags; int idx; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < 3; idx++) ucontrol->value.integer.value[idx] = mix->attn[idx]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1443,7 +1424,6 @@ static int snd_emu10k1_efx_send_routing_info(struct snd_kcontrol *kcontrol, stru static int snd_emu10k1_efx_send_routing_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; @@ -1451,11 +1431,9 @@ static int snd_emu10k1_efx_send_routing_get(struct snd_kcontrol *kcontrol, int num_efx = emu->audigy ? 8 : 4; int mask = emu->audigy ? 0x3f : 0x0f; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < num_efx; idx++) ucontrol->value.integer.value[idx] = mix->send_routing[0][idx] & mask; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1513,17 +1491,14 @@ static int snd_emu10k1_efx_send_volume_info(struct snd_kcontrol *kcontrol, struc static int snd_emu10k1_efx_send_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - unsigned long flags; struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; int idx; int num_efx = emu->audigy ? 8 : 4; - spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < num_efx; idx++) ucontrol->value.integer.value[idx] = mix->send_volume[0][idx]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } @@ -1582,11 +1557,8 @@ static int snd_emu10k1_efx_attn_get(struct snd_kcontrol *kcontrol, struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; - unsigned long flags; - spin_lock_irqsave(&emu->reg_lock, flags); ucontrol->value.integer.value[0] = mix->attn[0]; - spin_unlock_irqrestore(&emu->reg_lock, flags); return 0; } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index e8d2f0f6fbb3..5ed404e8ed39 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1433,10 +1433,8 @@ static int snd_emu10k1_pcm_efx_voices_mask_get(struct snd_kcontrol *kcontrol, st int nefx = emu->audigy ? 64 : 32; int idx; - spin_lock_irq(&emu->reg_lock); for (idx = 0; idx < nefx; idx++) ucontrol->value.integer.value[idx] = (emu->efx_voices_mask[idx / 32] & (1 << (idx % 32))) ? 1 : 0; - spin_unlock_irq(&emu->reg_lock); return 0; } -- cgit v1.2.3 From 946233bb23becc2898db31ad785d94fe80aa15dc Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 28 Apr 2023 11:59:41 +0200 Subject: ALSA: emu10k1: minor E-MU naming fixups - Fix mixer source port names. These will require some users to re-adjust their mixer settings, which seems acceptable: - The S/PDIF port is on the main 1010 card, not the 0202 daughter card - The 1616m CardBus card has all inputs on the dock, so there is no point in specifying it - Conversely, the 1010 card has "dispersed" inputs, so say where the ADAT port is, consistently with the S/PDIF port - The 1616m CardBus card is actually named E-MU 02 (due to the headphone output jack it has) - Fix capitalization of "E-MU" Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230428095941.1706335-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_main.c | 16 ++++++++-------- sound/pci/emu10k1/emumixer.c | 36 ++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 26 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 192208c291d6..068cb6624e36 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -1310,7 +1310,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* Does NOT support sync daughter card (obviously). */ /* Tested by James@superbug.co.uk 4th Nov 2007. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x42011102, - .driver = "Audigy2", .name = "E-mu 1010 Notebook [MAEM8950]", + .driver = "Audigy2", .name = "E-MU 02 CardBus [MAEM8950]", .id = "EMU1010", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1323,7 +1323,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { * MicroDock[M] to make it an E-MU 1616[m]. */ /* Does NOT support sync daughter card. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40041102, - .driver = "Audigy2", .name = "E-mu 1010b PCI [MAEM8960]", + .driver = "Audigy2", .name = "E-MU 1010b PCI [MAEM8960]", .id = "EMU1010", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1337,7 +1337,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { * still work. */ /* Does NOT support sync daughter card. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40071102, - .driver = "Audigy2", .name = "E-mu 1010 PCIe [MAEM8986]", + .driver = "Audigy2", .name = "E-MU 1010 PCIe [MAEM8986]", .id = "EMU1010", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1349,7 +1349,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { * AudioDock[M] to make it an E-MU 1820[m]. */ /* Supports sync daughter card. */ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40011102, - .driver = "Audigy2", .name = "E-mu 1010 [MAEM8810]", + .driver = "Audigy2", .name = "E-MU 1010 [MAEM8810]", .id = "EMU1010", .emu10k2_chip = 1, .ca0102_chip = 1, @@ -1359,7 +1359,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* Supports sync daughter card. */ /* Tested by oswald.buddenhagen@gmx.de Mar 2023. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40021102, - .driver = "Audigy2", .name = "E-mu 0404b PCI [MAEM8852]", + .driver = "Audigy2", .name = "E-MU 0404b PCI [MAEM8852]", .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1369,7 +1369,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* Supports sync daughter card. */ /* Tested by James@superbug.co.uk 20-3-2007. */ {.vendor = 0x1102, .device = 0x0004, .subsystem = 0x40021102, - .driver = "Audigy2", .name = "E-mu 0404 [MAEM8850]", + .driver = "Audigy2", .name = "E-MU 0404 [MAEM8850]", .id = "EMU0404", .emu10k2_chip = 1, .ca0102_chip = 1, @@ -1378,7 +1378,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { /* EMU0404 PCIe */ /* Does NOT support sync daughter card. */ {.vendor = 0x1102, .device = 0x0008, .subsystem = 0x40051102, - .driver = "Audigy2", .name = "E-mu 0404 PCIe [MAEM8984]", + .driver = "Audigy2", .name = "E-MU 0404 PCIe [MAEM8984]", .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, @@ -1645,7 +1645,7 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .ac97_chip = 1, .sblive51 = 1} , {.vendor = 0x1102, .device = 0x0002, .subsystem = 0x40011102, - .driver = "EMU10K1", .name = "E-mu APS [PC545]", + .driver = "EMU10K1", .name = "E-MU APS [PC545]", .id = "APS", .emu10k1_chip = 1, .ecard = 1} , diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index ab04f8be25bd..610700be1e37 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -78,16 +78,16 @@ static const char * const emu1010_src_texts[] = { "Dock ADC3 Right", "0202 ADC Left", "0202 ADC Right", - "0202 SPDIF Left", - "0202 SPDIF Right", - "ADAT 0", - "ADAT 1", - "ADAT 2", - "ADAT 3", - "ADAT 4", - "ADAT 5", - "ADAT 6", - "ADAT 7", + "1010 SPDIF Left", + "1010 SPDIF Right", + "1010 ADAT 0", + "1010 ADAT 1", + "1010 ADAT 2", + "1010 ADAT 3", + "1010 ADAT 4", + "1010 ADAT 5", + "1010 ADAT 6", + "1010 ADAT 7", "DSP 0", "DSP 1", "DSP 2", @@ -126,14 +126,14 @@ static const char * const emu1010_src_texts[] = { static const char * const emu1616_src_texts[] = { "Silence", - "Dock Mic A", - "Dock Mic B", - "Dock ADC1 Left", - "Dock ADC1 Right", - "Dock ADC2 Left", - "Dock ADC2 Right", - "Dock SPDIF Left", - "Dock SPDIF Right", + "Mic A", + "Mic B", + "ADC1 Left", + "ADC1 Right", + "ADC2 Left", + "ADC2 Right", + "SPDIF Left", + "SPDIF Right", "ADAT 0", "ADAT 1", "ADAT 2", -- cgit v1.2.3 From a8661af513040ed522e27d0e5339b3f757c1a351 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:37:22 +0200 Subject: ALSA: emu10k1: don't create regular S/PDIF controls for E-MU cards These ports are unused on these cards. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173722.3072439-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 610700be1e37..48f0d3f8b8e7 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -2055,7 +2055,7 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, mix->attn[0] = 0xffff; } - if (! emu->card_capabilities->ecard) { /* FIXME: APS has these controls? */ + if (!emu->card_capabilities->ecard && !emu->card_capabilities->emu_model) { /* sb live! and audigy */ kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu); if (!kctl) -- cgit v1.2.3 From 2a3fa40aefbe7de6cca3c6d56711ab336dfe34ae Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:04 +0200 Subject: ALSA: emu10k1: make tone control switch mono It controls the whole surround set, so stereo can't work. As a consequence, only the left channel was paid attention to. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 98785110ef63..6442028fe48a 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1543,13 +1543,13 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) #undef TREBLE_GPR for (z = 0; z < 8; z++) { - A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0); - A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0); + A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); + A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr); A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); } - snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); - gpr += 2; + snd_emu10k1_init_mono_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); + gpr++; /* Master volume (will be renamed later) */ A_OP(icode, &ptr, iMAC0, A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS)); @@ -2263,13 +2263,13 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) #undef TREBLE_GPR for (z = 0; z < 6; z++) { - SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr + 0); - SWITCH_NEG(icode, &ptr, tmp + 1, gpr + 0); + SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); + SWITCH_NEG(icode, &ptr, tmp + 1, gpr); SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000); } - snd_emu10k1_init_stereo_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); - gpr += 2; + snd_emu10k1_init_mono_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); + gpr++; /* * Process outputs -- cgit v1.2.3 From 8cabf83c7aa54530e699be56249fb44f9505c4f3 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:05 +0200 Subject: ALSA: emu10k1: roll up loops in DSP setup code for Audigy There is no apparent reason for the massive code duplication. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 112 ++++------------------------------------------ 1 file changed, 9 insertions(+), 103 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 6442028fe48a..eb40159a2eeb 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1552,14 +1552,8 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) gpr++; /* Master volume (will be renamed later) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+0+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+1+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+2+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+3+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+4+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+5+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+6+SND_EMU10K1_PLAYBACK_CHANNELS)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+7+SND_EMU10K1_PLAYBACK_CHANNELS)); + for (z = 0; z < 8; z++) + A_OP(icode, &ptr, iMAC0, A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr += 2; @@ -1646,102 +1640,14 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) dev_dbg(emu->card->dev, "emufx.c: gpr=0x%x, tmp=0x%x\n", gpr, tmp); */ - /* For the EMU1010: How to get 32bit values from the DSP. High 16bits into L, low 16bits into R. */ - /* A_P16VIN(0) is delayed by one sample, - * so all other A_P16VIN channels will need to also be delayed - */ - /* Left ADC in. 1 of 2 */ snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_P16VIN(0x0), A_FXBUS2(0) ); - /* Right ADC in 1 of 2 */ - gpr_map[gpr++] = 0x00000000; - /* Delaying by one sample: instead of copying the input - * value A_P16VIN to output A_FXBUS2 as in the first channel, - * we use an auxiliary register, delaying the value by one - * sample - */ - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(2) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x1), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(4) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x2), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(6) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x3), A_C_00000000, A_C_00000000); - /* For 96kHz mode */ - /* Left ADC in. 2 of 2 */ - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0x8) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x4), A_C_00000000, A_C_00000000); - /* Right ADC in 2 of 2 */ - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xa) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x5), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xc) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x6), A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr - 1), A_FXBUS2(0xe) ); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x7), A_C_00000000, A_C_00000000); - /* Pavel Hofman - we still have voices, A_FXBUS2s, and - * A_P16VINs available - - * let's add 8 more capture channels - total of 16 - */ - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x10)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x8), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x12)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0x9), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x14)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xa), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x16)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xb), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x18)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xc), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x1a)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xd), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x1c)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xe), - A_C_00000000, A_C_00000000); - gpr_map[gpr++] = 0x00000000; - snd_emu10k1_audigy_dsp_convert_32_to_2x16(icode, &ptr, tmp, - bit_shifter16, - A_GPR(gpr - 1), - A_FXBUS2(0x1e)); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 1), A_P16VIN(0xf), - A_C_00000000, A_C_00000000); + /* A_P16VIN(0) is delayed by one sample, so all other A_P16VIN channels + * will need to also be delayed; we use an auxiliary register for that. */ + for (z = 1; z < 0x10; z++) { + snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, A_GPR(gpr), A_FXBUS2(z * 2) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr), A_P16VIN(z), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; + } } #if 0 -- cgit v1.2.3 From 4102ac29759586e86cf129d66fc5ad5406a431a8 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:06 +0200 Subject: ALSA: emu10k1: fix+optimize E-MU stereo capture DSP code Presumably, JDC added the seemingly superfluous indirection over the temporary register because without it he'd get only zero readings. However, switching the X and Y operands (or using EMU32 as the A operand in the temporary load) works just fine. Presumably a DSP bug? The original code was also actually buggy, though: both channels used the left volume control. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index eb40159a2eeb..795b2573fef4 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1324,11 +1324,9 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) /* EMU1010 DSP 0 and DSP 1 Capture */ // The 24 MSB hold the actual value. We implicitly discard the 16 LSB. if (emu->card_capabilities->ca0108_chip) { - /* Note:JCD:No longer bit shift lower 16bits to upper 16bits of 32bit value. */ - A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x0), A_C_00000001); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_GPR(tmp)); - A_OP(icode, &ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A3_EMU32IN(0x1), A_C_00000001); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr), A_GPR(tmp)); + // For unclear reasons, the EMU32IN cannot be the Y operand! + A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); + A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A3_EMU32IN(0x1), A_GPR(gpr+1)); } else { A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0)); A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_P16VIN(0x1)); -- cgit v1.2.3 From 4c7bfbcf7516b0804ba0204e865f885baca604e4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:07 +0200 Subject: ALSA: emu10k1: simplify snd_emu10k1_audigy_dsp_convert_32_to_2x16() Instead of spending lots of instructions on masking and transplanting the sign bit, sidestep the issue by replacing the last bit shift with a wrapping addition to self. Solution stolen from kX-project, after I pondered other ideas first. Also, the function really doesn't need to return a constant int value. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 795b2573fef4..8ba294138dfe 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1184,20 +1184,22 @@ snd_emu10k1_init_stereo_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl * to 2 x 16-bit registers in Audigy - their values are read via DMA. * Conversion is performed by Audigy DSP instructions of FX8010. */ -static int snd_emu10k1_audigy_dsp_convert_32_to_2x16( +static void snd_emu10k1_audigy_dsp_convert_32_to_2x16( struct snd_emu10k1_fx8010_code *icode, u32 *ptr, int tmp, int bit_shifter16, int reg_in, int reg_out) { - A_OP(icode, ptr, iACC3, A_GPR(tmp + 1), reg_in, A_C_00000000, A_C_00000000); - A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp + 1), A_GPR(bit_shifter16 - 1), A_C_00000000); - A_OP(icode, ptr, iTSTNEG, A_GPR(tmp + 2), A_GPR(tmp), A_C_80000000, A_GPR(bit_shifter16 - 2)); - A_OP(icode, ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_C_80000000, A_C_00000000); - A_OP(icode, ptr, iANDXOR, A_GPR(tmp), A_GPR(tmp), A_GPR(bit_shifter16 - 3), A_C_00000000); - A_OP(icode, ptr, iMACINT0, A_GPR(tmp), A_C_00000000, A_GPR(tmp), A_C_00010000); - A_OP(icode, ptr, iANDXOR, reg_out, A_GPR(tmp), A_C_ffffffff, A_GPR(tmp + 2)); - A_OP(icode, ptr, iACC3, reg_out + 1, A_GPR(tmp + 1), A_C_00000000, A_C_00000000); - return 1; + // This leaves the low word in place, which is fine, + // as the low bits are completely ignored subsequently. + // reg_out[1] = reg_in + A_OP(icode, ptr, iACC3, reg_out + 1, reg_in, A_C_00000000, A_C_00000000); + // It is fine to read reg_in multiple times. + // tmp = reg_in << 15 + A_OP(icode, ptr, iMACINT1, A_GPR(tmp), A_C_00000000, reg_in, A_GPR(bit_shifter16)); + // Left-shift once more. This is a separate step, as the + // signed multiplication would clobber the MSB. + // reg_out[0] = tmp + ((tmp << 31) >> 31) + A_OP(icode, ptr, iMAC3, reg_out, A_GPR(tmp), A_GPR(tmp), A_C_80000000); } /* @@ -1247,10 +1249,8 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) ptr = 0; nctl = 0; gpr = stereo_mix + 10; - gpr_map[gpr++] = 0x00007fff; - gpr_map[gpr++] = 0x00008000; - gpr_map[gpr++] = 0x0000ffff; bit_shifter16 = gpr; + gpr_map[gpr++] = 0x00008000; #if 1 /* PCM front Playback Volume (independent from stereo mix) -- cgit v1.2.3 From f549466b8b8519260e49460543dff9b37f280cc9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:08 +0200 Subject: ALSA: emu10k1: apply channel delay hack to all E-MU cards Evidently, the channel delay bug exists in all E-MU cards; it's in the Hana FPGA program, and was never fixed. Note that the implementation is somewhat lazy - to localize the code paths, we actually waste a GPR and a DSP instruction by keeping two delay registers for the same physical source. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emufx.c | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 8fe80dcee71b..7129b9249eb3 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1192,6 +1192,7 @@ * emumixer.c - snd_emu1010_output_enum_ctls[], snd_emu1010_input_enum_ctls[] */ #define EMU_DST_ALICE2_EMU32_0 0x000f /* 16 EMU32 channels to Alice2 +0 to +0xf */ + /* This channel is delayed by one sample. */ #define EMU_DST_ALICE2_EMU32_1 0x0000 /* 16 EMU32 channels to Alice2 +0 to +0xf */ #define EMU_DST_ALICE2_EMU32_2 0x0001 /* 16 EMU32 channels to Alice2 +0 to +0xf */ #define EMU_DST_ALICE2_EMU32_3 0x0002 /* 16 EMU32 channels to Alice2 +0 to +0xf */ diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 8ba294138dfe..2e139ae8b41b 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1326,13 +1326,20 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) if (emu->card_capabilities->ca0108_chip) { // For unclear reasons, the EMU32IN cannot be the Y operand! A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A3_EMU32IN(0x1), A_GPR(gpr+1)); + // 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. + A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A3_EMU32IN(0x1), A_C_00000000, A_C_00000000); } else { A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_P16VIN(0x1)); + // 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. + A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_GPR(gpr+2)); + A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A_P16VIN(0x1), A_C_00000000, A_C_00000000); } snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0); - gpr += 2; + gpr_map[gpr + 2] = 0x00000000; + gpr += 3; } /* AC'97 Playback Volume - used only for mic (renamed later) */ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); @@ -1624,11 +1631,17 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) dev_info(emu->card->dev, "EMU2 inputs on\n"); /* Note that the Tina[2] DSPs have 16 more EMU32 inputs which we don't use. */ - for (z = 0; z < 0x10; z++) { + snd_emu10k1_audigy_dsp_convert_32_to_2x16( + icode, &ptr, tmp, bit_shifter16, A3_EMU32IN(0), A_FXBUS2(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 (z = 1; z < 0x10; z++) { snd_emu10k1_audigy_dsp_convert_32_to_2x16( icode, &ptr, tmp, bit_shifter16, - A3_EMU32IN(z), + A_GPR(gpr), A_FXBUS2(z*2) ); + A_OP(icode, &ptr, iACC3, A_GPR(gpr), A3_EMU32IN(z), A_C_00000000, A_C_00000000); + gpr_map[gpr++] = 0x00000000; } } else { dev_info(emu->card->dev, "EMU inputs on\n"); -- cgit v1.2.3 From 59f038a09c62d77de91387ddcff66e54d99f2ec9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 10 May 2023 19:39:09 +0200 Subject: ALSA: emu10k1: simplify tone control switch DSP code Instead of using lots of instructions to mix wet and dry signals, simply skip over the whole code block if tone control is disabled. This also allows us doing away with the "shadow" playback channels. Tested-by: Jonathan Dowland Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230510173917.3073107-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 123 +++++++++++++++++++--------------------------- 1 file changed, 50 insertions(+), 73 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 2e139ae8b41b..2da1f9f1fb5a 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1211,10 +1211,10 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) int err, z, gpr, nctl; int bit_shifter16; const int playback = 10; - const int capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); /* we reserve 10 voices */ + const int capture = playback + SND_EMU10K1_PLAYBACK_CHANNELS; /* we reserve 10 voices */ const int stereo_mix = capture + 2; const int tmp = 0x88; - u32 ptr; + u32 ptr, ptr_skip; struct snd_emu10k1_fx8010_code *icode = NULL; struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; u32 *gpr_map; @@ -1474,18 +1474,6 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) /* * Process tone control */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), A_GPR(playback + 0), A_C_00000000, A_C_00000000); /* left */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), A_GPR(playback + 1), A_C_00000000, A_C_00000000); /* right */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), A_GPR(playback + 2), A_C_00000000, A_C_00000000); /* rear left */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), A_GPR(playback + 3), A_C_00000000, A_C_00000000); /* rear right */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), A_GPR(playback + 4), A_C_00000000, A_C_00000000); /* center */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), A_GPR(playback + 5), A_C_00000000, A_C_00000000); /* LFE */ - if (emu->card_capabilities->spk71) { - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 6), A_GPR(playback + 6), A_C_00000000, A_C_00000000); /* side left */ - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 7), A_GPR(playback + 7), A_C_00000000, A_C_00000000); /* side right */ - } - - ctl = &controls[nctl + 0]; ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, "Tone Control - Bass"); @@ -1515,12 +1503,19 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) controls[nctl + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j; } } + nctl += 2; + + A_OP(icode, &ptr, iACC3, A_C_00000000, A_GPR(gpr), A_C_00000000, A_C_00000000); + snd_emu10k1_init_mono_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); + gpr++; + A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_CC_REG_ZERO, A_GPR(gpr)); + ptr_skip = ptr; for (z = 0; z < 4; z++) { /* front/rear/center-lfe/side */ int j, k, l, d; for (j = 0; j < 2; j++) { /* left/right */ k = 0xb0 + (z * 8) + (j * 4); l = 0xe0 + (z * 8) + (j * 4); - d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j; + d = playback + z * 2 + j; A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(BASS_GPR + 0 + j)); A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(BASS_GPR + 4 + j)); @@ -1542,36 +1537,27 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) break; } } - nctl += 2; + gpr_map[gpr++] = ptr - ptr_skip; #undef BASS_GPR #undef TREBLE_GPR - for (z = 0; z < 8; z++) { - A_SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); - A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr); - A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); - A_OP(icode, &ptr, iACC3, A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); - } - snd_emu10k1_init_mono_onoff_control(controls + nctl++, "Tone Control - Switch", gpr, 0); - gpr++; - /* Master volume (will be renamed later) */ for (z = 0; z < 8; z++) - A_OP(icode, &ptr, iMAC0, A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS), A_C_00000000, A_GPR(gpr), A_GPR(playback+z+SND_EMU10K1_PLAYBACK_CHANNELS)); + A_OP(icode, &ptr, iMAC0, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr += 2; /* analog speakers */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); + A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5); if (emu->card_capabilities->spk71) - A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6 + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6); /* headphone */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback); /* digital outputs */ /* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */ @@ -1580,9 +1566,9 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) dev_info(emu->card->dev, "EMU outputs on\n"); for (z = 0; z < 8; z++) { if (emu->card_capabilities->ca0108_chip) { - A_OP(icode, &ptr, iACC3, A3_EMU32OUT(z), A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iACC3, A3_EMU32OUT(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); } else { - A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); } } } @@ -1598,7 +1584,7 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000); A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z); A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z); - A_SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1); + A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); if ((z==1) && (emu->card_capabilities->spdif_bug)) { /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */ dev_info(emu->card->dev, @@ -1613,13 +1599,13 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0); gpr += 2; - A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4 + SND_EMU10K1_PLAYBACK_CHANNELS); - A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5 + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5); /* ADC buffer */ #ifdef EMU10K1_CAPTURE_DIGITAL_OUT - A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); + A_PUT_STEREO_OUTPUT(A_EXTOUT_ADC_CAP_L, A_EXTOUT_ADC_CAP_R, playback); #else A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_L, capture); A_PUT_OUTPUT(A_EXTOUT_ADC_CAP_R, capture+1); @@ -1762,7 +1748,7 @@ static void _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) { int err, i, z, gpr, tmp, playback, capture; - u32 ptr; + u32 ptr, ptr_skip; struct snd_emu10k1_fx8010_code *icode; struct snd_emu10k1_fx8010_pcm_rec *ipcm = NULL; struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; @@ -1805,7 +1791,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* we have 12 inputs */ playback = SND_EMU10K1_INPUTS; /* we have 6 playback channels and tone control doubles */ - capture = playback + (SND_EMU10K1_PLAYBACK_CHANNELS * 2); + capture = playback + SND_EMU10K1_PLAYBACK_CHANNELS; gpr = capture + SND_EMU10K1_CAPTURE_CHANNELS; tmp = 0x88; /* we need 4 temporary GPR */ /* from 0x8c to 0xff is the area for tone control */ @@ -2109,13 +2095,6 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* * Process tone control */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 0), GPR(playback + 0), C_00000000, C_00000000); /* left */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 1), GPR(playback + 1), C_00000000, C_00000000); /* right */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 2), GPR(playback + 2), C_00000000, C_00000000); /* rear left */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 3), GPR(playback + 3), C_00000000, C_00000000); /* rear right */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 4), GPR(playback + 4), C_00000000, C_00000000); /* center */ - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + 5), GPR(playback + 5), C_00000000, C_00000000); /* LFE */ - ctl = &controls[i + 0]; ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, "Tone Control - Bass"); @@ -2147,12 +2126,19 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) controls[i + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j; } } + i += 2; + + OP(icode, &ptr, iACC3, C_00000000, GPR(gpr), C_00000000, C_00000000); + snd_emu10k1_init_mono_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); + gpr++; + OP(icode, &ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_ZERO, GPR(gpr)); + ptr_skip = ptr; for (z = 0; z < 3; z++) { /* front/rear/center-lfe */ int j, k, l, d; for (j = 0; j < 2; j++) { /* left/right */ k = 0xa0 + (z * 8) + (j * 4); l = 0xd0 + (z * 8) + (j * 4); - d = playback + SND_EMU10K1_PLAYBACK_CHANNELS + z * 2 + j; + d = playback + z * 2 + j; OP(icode, &ptr, iMAC0, C_00000000, C_00000000, GPR(d), GPR(BASS_GPR + 0 + j)); OP(icode, &ptr, iMACMV, GPR(k+1), GPR(k), GPR(k+1), GPR(BASS_GPR + 4 + j)); @@ -2174,20 +2160,11 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) break; } } - i += 2; + gpr_map[gpr++] = ptr - ptr_skip; #undef BASS_GPR #undef TREBLE_GPR - for (z = 0; z < 6; z++) { - SWITCH(icode, &ptr, tmp + 0, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, gpr); - SWITCH_NEG(icode, &ptr, tmp + 1, gpr); - SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); - OP(icode, &ptr, iACC3, GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), GPR(tmp + 0), GPR(tmp + 1), C_00000000); - } - snd_emu10k1_init_mono_onoff_control(controls + i++, "Tone Control - Switch", gpr, 0); - gpr++; - /* * Process outputs */ @@ -2195,7 +2172,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* AC'97 Playback Volume */ for (z = 0; z < 2; z++) - OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_L + z), GPR(playback + SND_EMU10K1_PLAYBACK_CHANNELS + z), C_00000000, C_00000000); + OP(icode, &ptr, iACC3, EXTOUT(EXTOUT_AC97_L + z), GPR(playback + z), C_00000000, C_00000000); } if (emu->fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & ((1<fx8010.extout_mask & (1<fx8010.extout_mask & (1< Date: Sun, 14 May 2023 19:03:21 +0200 Subject: ALSA: emu10k1: validate parameters of snd_emu10k1_ptr_{read,write}() Rather than applying masks to the provided values, make assertions about them being valid - otherwise we'd just try to paper over bugs. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408798-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index aee84c3f9f37..ced69165d69a 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -18,14 +18,27 @@ #include #include "p17v.h" +static inline bool check_ptr_reg(struct snd_emu10k1 *emu, unsigned int reg) +{ + if (snd_BUG_ON(!emu)) + return false; + if (snd_BUG_ON(reg & (emu->audigy ? (0xffff0000 & ~A_PTR_ADDRESS_MASK) + : (0xffff0000 & ~PTR_ADDRESS_MASK)))) + return false; + if (snd_BUG_ON(reg & 0x0000ffff & ~PTR_CHANNELNUM_MASK)) + return false; + return true; +} + unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn) { unsigned long flags; unsigned int regptr, val; unsigned int mask; - mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK; - regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK); + regptr = (reg << 16) | chn; + if (!check_ptr_reg(emu, regptr)) + return 0; if (reg & 0xff000000) { unsigned char size, offset; @@ -57,18 +70,20 @@ void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned i unsigned long flags; unsigned int mask; - if (snd_BUG_ON(!emu)) + regptr = (reg << 16) | chn; + if (!check_ptr_reg(emu, regptr)) return; - mask = emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK; - regptr = ((reg << 16) & mask) | (chn & PTR_CHANNELNUM_MASK); if (reg & 0xff000000) { unsigned char size, offset; size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; - mask = ((1 << size) - 1) << offset; - data = (data << offset) & mask; + mask = (1 << size) - 1; + if (snd_BUG_ON(data & ~mask)) + return; + mask <<= offset; + data <<= offset; spin_lock_irqsave(&emu->emu_lock, flags); outl(regptr, emu->port + PTR); -- cgit v1.2.3 From 2093dcfc04e1477efd47d3acf0adc4582dc5f4f6 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:22 +0200 Subject: ALSA: emu10k1: merge common paths in snd_emu10k1_ptr_{read,write}() Avoids some code duplication. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408798-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index ced69165d69a..2d6bbb77c961 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -40,6 +40,11 @@ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, un if (!check_ptr_reg(emu, regptr)) return 0; + spin_lock_irqsave(&emu->emu_lock, flags); + outl(regptr, emu->port + PTR); + val = inl(emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); + if (reg & 0xff000000) { unsigned char size, offset; @@ -47,17 +52,8 @@ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, un offset = (reg >> 16) & 0x1f; mask = ((1 << size) - 1) << offset; - spin_lock_irqsave(&emu->emu_lock, flags); - outl(regptr, emu->port + PTR); - val = inl(emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); - return (val & mask) >> offset; } else { - spin_lock_irqsave(&emu->emu_lock, flags); - outl(regptr, emu->port + PTR); - val = inl(emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); return val; } } @@ -88,14 +84,12 @@ void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned i spin_lock_irqsave(&emu->emu_lock, flags); outl(regptr, emu->port + PTR); data |= inl(emu->port + DATA) & ~mask; - outl(data, emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); } else { spin_lock_irqsave(&emu->emu_lock, flags); outl(regptr, emu->port + PTR); - outl(data, emu->port + DATA); - spin_unlock_irqrestore(&emu->emu_lock, flags); } + outl(data, emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); } EXPORT_SYMBOL(snd_emu10k1_ptr_write); -- cgit v1.2.3 From 2e9bd50f117ea3f638802627a196949f7eefcf02 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:23 +0200 Subject: ALSA: emu10k1: optimize mask calculation in snd_emu10k1_ptr_read() Unlike in snd_emu10k1_ptr_write(), we don't need to keep the value's bits in place, so we can save one shift. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408798-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/io.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 2d6bbb77c961..59b0f4d08c6b 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -50,9 +50,9 @@ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, un size = (reg >> 24) & 0x3f; offset = (reg >> 16) & 0x1f; - mask = ((1 << size) - 1) << offset; + mask = (1 << size) - 1; - return (val & mask) >> offset; + return (val >> offset) & mask; } else { return val; } -- cgit v1.2.3 From a746516d75fd734e6af1d9a53bacbdef790e76d1 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:17 +0200 Subject: ALSA: emu10k1: polish audigy GPR allocation - Pull ahead all fixed allocations, so we don't rely on the semi- dynamic ones not crossing the arbitrarily determined limit - Use an enum for the fixed allocations - Stop arbitrarily wasting registers on unexplained "reservations" - Don't reserve two regs for the master volume control - it's mono Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 69 +++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 32 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 2da1f9f1fb5a..43abb6c04a7f 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1202,18 +1202,31 @@ static void snd_emu10k1_audigy_dsp_convert_32_to_2x16( A_OP(icode, ptr, iMAC3, reg_out, A_GPR(tmp), A_GPR(tmp), A_C_80000000); } +#define ENUM_GPR(name, size) name, name ## _dummy = name + (size) - 1 + /* * initial DSP configuration for Audigy */ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) { - int err, z, gpr, nctl; - int bit_shifter16; - const int playback = 10; - const int capture = playback + SND_EMU10K1_PLAYBACK_CHANNELS; /* we reserve 10 voices */ - const int stereo_mix = capture + 2; - const int tmp = 0x88; + int err, z, nctl; + enum { + ENUM_GPR(playback, SND_EMU10K1_PLAYBACK_CHANNELS), + ENUM_GPR(stereo_mix, 2), + ENUM_GPR(capture, 2), + ENUM_GPR(bit_shifter16, 1), + // The fixed allocation of these breaks the pattern, but why not. + // Splitting these into left/right is questionable, as it will break + // down for center/lfe. But it works for stereo/quadro, so whatever. + ENUM_GPR(bass_gpr, 2 * 5), // two sides, five coefficients + ENUM_GPR(treble_gpr, 2 * 5), + ENUM_GPR(bass_tmp, SND_EMU10K1_PLAYBACK_CHANNELS * 4), // four delay stages + ENUM_GPR(treble_tmp, SND_EMU10K1_PLAYBACK_CHANNELS * 4), + ENUM_GPR(tmp, 3), + num_static_gprs + }; + int gpr = num_static_gprs; u32 ptr, ptr_skip; struct snd_emu10k1_fx8010_code *icode = NULL; struct snd_emu10k1_fx8010_control_gpr *controls = NULL, *ctl; @@ -1248,9 +1261,7 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) strcpy(icode->name, "Audigy DSP code for ALSA"); ptr = 0; nctl = 0; - gpr = stereo_mix + 10; - bit_shifter16 = gpr; - gpr_map[gpr++] = 0x00008000; + gpr_map[bit_shifter16] = 0x00008000; #if 1 /* PCM front Playback Volume (independent from stereo mix) @@ -1492,15 +1503,11 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) ctl->max = 40; ctl->value[0] = ctl->value[1] = 20; ctl->translation = EMU10K1_GPR_TRANSLATION_TREBLE; - -#define BASS_GPR 0x8c -#define TREBLE_GPR 0x96 - for (z = 0; z < 5; z++) { int j; for (j = 0; j < 2; j++) { - controls[nctl + 0].gpr[z * 2 + j] = BASS_GPR + z * 2 + j; - controls[nctl + 1].gpr[z * 2 + j] = TREBLE_GPR + z * 2 + j; + controls[nctl + 0].gpr[z * 2 + j] = bass_gpr + z * 2 + j; + controls[nctl + 1].gpr[z * 2 + j] = treble_gpr + z * 2 + j; } } nctl += 2; @@ -1513,22 +1520,22 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) for (z = 0; z < 4; z++) { /* front/rear/center-lfe/side */ int j, k, l, d; for (j = 0; j < 2; j++) { /* left/right */ - k = 0xb0 + (z * 8) + (j * 4); - l = 0xe0 + (z * 8) + (j * 4); + k = bass_tmp + (z * 8) + (j * 4); + l = treble_tmp + (z * 8) + (j * 4); d = playback + z * 2 + j; - A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(BASS_GPR + 0 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(BASS_GPR + 4 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(k), A_GPR(d), A_GPR(k), A_GPR(BASS_GPR + 2 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(k+3), A_GPR(k+2), A_GPR(k+3), A_GPR(BASS_GPR + 8 + j)); - A_OP(icode, &ptr, iMAC0, A_GPR(k+2), A_GPR_ACCU, A_GPR(k+2), A_GPR(BASS_GPR + 6 + j)); + A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(d), A_GPR(bass_gpr + 0 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k+1), A_GPR(k), A_GPR(k+1), A_GPR(bass_gpr + 4 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k), A_GPR(d), A_GPR(k), A_GPR(bass_gpr + 2 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(k+3), A_GPR(k+2), A_GPR(k+3), A_GPR(bass_gpr + 8 + j)); + A_OP(icode, &ptr, iMAC0, A_GPR(k+2), A_GPR_ACCU, A_GPR(k+2), A_GPR(bass_gpr + 6 + j)); A_OP(icode, &ptr, iACC3, A_GPR(k+2), A_GPR(k+2), A_GPR(k+2), A_C_00000000); - A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(k+2), A_GPR(TREBLE_GPR + 0 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(l+1), A_GPR(l), A_GPR(l+1), A_GPR(TREBLE_GPR + 4 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(l), A_GPR(k+2), A_GPR(l), A_GPR(TREBLE_GPR + 2 + j)); - A_OP(icode, &ptr, iMACMV, A_GPR(l+3), A_GPR(l+2), A_GPR(l+3), A_GPR(TREBLE_GPR + 8 + j)); - A_OP(icode, &ptr, iMAC0, A_GPR(l+2), A_GPR_ACCU, A_GPR(l+2), A_GPR(TREBLE_GPR + 6 + j)); + A_OP(icode, &ptr, iMAC0, A_C_00000000, A_C_00000000, A_GPR(k+2), A_GPR(treble_gpr + 0 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l+1), A_GPR(l), A_GPR(l+1), A_GPR(treble_gpr + 4 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l), A_GPR(k+2), A_GPR(l), A_GPR(treble_gpr + 2 + j)); + A_OP(icode, &ptr, iMACMV, A_GPR(l+3), A_GPR(l+2), A_GPR(l+3), A_GPR(treble_gpr + 8 + j)); + A_OP(icode, &ptr, iMAC0, A_GPR(l+2), A_GPR_ACCU, A_GPR(l+2), A_GPR(treble_gpr + 6 + j)); A_OP(icode, &ptr, iMACINT0, A_GPR(l+2), A_C_00000000, A_GPR(l+2), A_C_00000010); A_OP(icode, &ptr, iACC3, A_GPR(d), A_GPR(l+2), A_C_00000000, A_C_00000000); @@ -1539,14 +1546,11 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) } gpr_map[gpr++] = ptr - ptr_skip; -#undef BASS_GPR -#undef TREBLE_GPR - /* Master volume (will be renamed later) */ for (z = 0; z < 8; z++) A_OP(icode, &ptr, iMAC0, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); - gpr += 2; + gpr++; /* analog speakers */ A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); @@ -1668,11 +1672,12 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) * ok, set up done.. */ - if (gpr > tmp) { + 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); -- cgit v1.2.3 From bb5ceb43b7bfa166fd5d739d51ad46c1cfb225e3 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:18 +0200 Subject: ALSA: emu10k1: fix non-zero mixer control defaults in highres mode The default value needs to be scaled. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 43abb6c04a7f..fbc1bfc122fc 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1116,18 +1116,19 @@ snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 1; - ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; if (high_res_gpr_volume) { ctl->min = 0; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; + defval = defval * 0x7fffffffLL / 100; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; } + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; } static void @@ -1137,19 +1138,20 @@ snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 2; - ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; - ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; if (high_res_gpr_volume) { ctl->min = 0; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; + defval = defval * 0x7fffffffLL / 100; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; } + ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; + ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; } static void -- cgit v1.2.3 From 1a38ae579606dae836dced573d5ffa78cce6fc48 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:19 +0200 Subject: ALSA: emu10k1: validate min/max values of translated controls User space could pass arbitrary ranges, which were uncritically accepted. This could lead to table lookups out of range. I don't think that this is a security issue, as it only allowed someone with CAP_SYS_ADMIN to crash the kernel, but still. Setting an invalid translation mode will also be rejected now. That did no harm, but it's still better to detect errors. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index fbc1bfc122fc..796e24b6f01a 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -769,6 +769,32 @@ static int snd_emu10k1_verify_controls(struct snd_emu10k1 *emu, err = -EINVAL; goto __error; } + switch (gctl->translation) { + case EMU10K1_GPR_TRANSLATION_NONE: + break; + case EMU10K1_GPR_TRANSLATION_TABLE100: + if (gctl->min != 0 || gctl->max != 100) { + err = -EINVAL; + goto __error; + } + break; + case EMU10K1_GPR_TRANSLATION_BASS: + case EMU10K1_GPR_TRANSLATION_TREBLE: + if (gctl->min != 0 || gctl->max != 40) { + err = -EINVAL; + goto __error; + } + break; + case EMU10K1_GPR_TRANSLATION_ONOFF: + if (gctl->min != 0 || gctl->max != 1) { + err = -EINVAL; + goto __error; + } + break; + default: + err = -EINVAL; + goto __error; + } } for (i = 0; i < icode->gpr_list_control_count; i++) { /* FIXME: we need to check the WRITE access */ -- cgit v1.2.3 From 6175ccd1a98136203bf88279cebcc6514ec15bdd Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:20 +0200 Subject: ALSA: emu10k1: omit non-applicable mixer controls for E-MU cards The E-MU cards don't try very hard to be Sound Blasters. All sound I/O goes through the Hana FPGA, thus making the regular extin/out controls useless. Still showing them just serves to clutter up the interface and confuse the user. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 233 +++++++++++++++++++++++----------------------- 1 file changed, 116 insertions(+), 117 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 796e24b6f01a..8c171bbaf02c 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1379,87 +1379,88 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0); gpr_map[gpr + 2] = 0x00000000; gpr += 3; - } - /* AC'97 Playback Volume - used only for mic (renamed later) */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); - gpr += 2; - /* AC'97 Capture Volume - used only for mic */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); - gpr += 2; + } else { + /* AC'97 Playback Volume - used only for mic (renamed later) */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); + gpr += 2; + /* AC'97 Capture Volume - used only for mic */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); + gpr += 2; - /* mic capture buffer */ - A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); + /* mic capture buffer */ + A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); - /* Audigy CD Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_SPDIF_CD_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Audigy CD Playback Volume" : "CD Playback Volume", - gpr, 0); - gpr += 2; - /* Audigy CD Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_SPDIF_CD_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_SPDIF_CD_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Audigy CD Capture Volume" : "CD Capture Volume", - gpr, 0); - gpr += 2; + /* Audigy CD Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_SPDIF_CD_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Audigy CD Playback Volume" : "CD Playback Volume", + gpr, 0); + gpr += 2; + /* Audigy CD Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_SPDIF_CD_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_SPDIF_CD_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Audigy CD Capture Volume" : "CD Capture Volume", + gpr, 0); + gpr += 2; - /* Optical SPDIF Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_OPT_SPDIF_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_OPT_SPDIF_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",PLAYBACK,VOLUME), gpr, 0); - gpr += 2; - /* Optical SPDIF Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_OPT_SPDIF_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_OPT_SPDIF_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",CAPTURE,VOLUME), gpr, 0); - gpr += 2; + /* Optical SPDIF Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_OPT_SPDIF_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_OPT_SPDIF_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",PLAYBACK,VOLUME), gpr, 0); + gpr += 2; + /* Optical SPDIF Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_OPT_SPDIF_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_OPT_SPDIF_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], SNDRV_CTL_NAME_IEC958("Optical ",CAPTURE,VOLUME), gpr, 0); + gpr += 2; - /* Line2 Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_LINE2_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_LINE2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Line2 Playback Volume" : "Line Playback Volume", - gpr, 0); - gpr += 2; - /* Line2 Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_LINE2_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_LINE2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Line2 Capture Volume" : "Line Capture Volume", - gpr, 0); - gpr += 2; - - /* Philips ADC Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_ADC_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_ADC_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Playback Volume", gpr, 0); - gpr += 2; - /* Philips ADC Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_ADC_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_ADC_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Capture Volume", gpr, 0); - gpr += 2; + /* Line2 Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_LINE2_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_LINE2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Line2 Playback Volume" : "Line Playback Volume", + gpr, 0); + gpr += 2; + /* Line2 Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_LINE2_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_LINE2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Line2 Capture Volume" : "Line Capture Volume", + gpr, 0); + gpr += 2; - /* Aux2 Playback Volume */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AUX2_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AUX2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Aux2 Playback Volume" : "Aux Playback Volume", - gpr, 0); - gpr += 2; - /* Aux2 Capture Volume */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AUX2_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AUX2_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], - emu->card_capabilities->ac97_chip ? "Aux2 Capture Volume" : "Aux Capture Volume", - gpr, 0); - gpr += 2; + /* Philips ADC Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_ADC_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_ADC_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Playback Volume", gpr, 0); + gpr += 2; + /* Philips ADC Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_ADC_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_ADC_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Analog Mix Capture Volume", gpr, 0); + gpr += 2; + + /* Aux2 Playback Volume */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AUX2_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AUX2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Aux2 Playback Volume" : "Aux Playback Volume", + gpr, 0); + gpr += 2; + /* Aux2 Capture Volume */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AUX2_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AUX2_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], + emu->card_capabilities->ac97_chip ? "Aux2 Capture Volume" : "Aux Capture Volume", + gpr, 0); + gpr += 2; + } /* Stereo Mix Front Playback Volume */ A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix)); @@ -1580,19 +1581,6 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr++; - /* analog speakers */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); - A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2); - A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4); - A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5); - if (emu->card_capabilities->spk71) - A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6); - - /* headphone */ - A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback); - - /* digital outputs */ - /* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */ if (emu->card_capabilities->emu_model) { /* EMU1010 Outputs from PCM Front, Rear, Center, LFE, Side */ dev_info(emu->card->dev, "EMU outputs on\n"); @@ -1603,37 +1591,48 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) A_OP(icode, &ptr, iACC3, A_EMU32OUTL(z), A_GPR(playback + z), A_C_00000000, A_C_00000000); } } - } + } else { + /* analog speakers */ + A_PUT_STEREO_OUTPUT(A_EXTOUT_AFRONT_L, A_EXTOUT_AFRONT_R, playback); + A_PUT_STEREO_OUTPUT(A_EXTOUT_AREAR_L, A_EXTOUT_AREAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_ACENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_ALFE, playback+5); + if (emu->card_capabilities->spk71) + A_PUT_STEREO_OUTPUT(A_EXTOUT_ASIDE_L, A_EXTOUT_ASIDE_R, playback+6); - /* IEC958 Optical Raw Playback Switch */ - gpr_map[gpr++] = 0; - gpr_map[gpr++] = 0x1008; - gpr_map[gpr++] = 0xffff0000; - for (z = 0; z < 2; z++) { - A_OP(icode, &ptr, iMAC0, A_GPR(tmp + 2), A_FXBUS(FXBUS_PT_LEFT + z), A_C_00000000, A_C_00000000); - A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_GPR(gpr - 2), A_C_00000001); - A_OP(icode, &ptr, iACC3, A_GPR(tmp + 2), A_C_00000000, A_C_00010000, A_GPR(tmp + 2)); - A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000); - A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z); - A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z); - A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); - if ((z==1) && (emu->card_capabilities->spdif_bug)) { - /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */ - dev_info(emu->card->dev, - "Installing spdif_bug patch: %s\n", - emu->card_capabilities->name); - A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000); - A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); - } else { - A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + /* headphone */ + A_PUT_STEREO_OUTPUT(A_EXTOUT_HEADPHONE_L, A_EXTOUT_HEADPHONE_R, playback); + + /* IEC958 Optical Raw Playback Switch */ + gpr_map[gpr++] = 0; + gpr_map[gpr++] = 0x1008; + gpr_map[gpr++] = 0xffff0000; + for (z = 0; z < 2; z++) { + A_OP(icode, &ptr, iMAC0, A_GPR(tmp + 2), A_FXBUS(FXBUS_PT_LEFT + z), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iSKIP, A_GPR_COND, A_GPR_COND, A_GPR(gpr - 2), A_C_00000001); + A_OP(icode, &ptr, iACC3, A_GPR(tmp + 2), A_C_00000000, A_C_00010000, A_GPR(tmp + 2)); + A_OP(icode, &ptr, iANDXOR, A_GPR(tmp + 2), A_GPR(tmp + 2), A_GPR(gpr - 1), A_C_00000000); + A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z); + A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z); + A_SWITCH(icode, &ptr, tmp + 1, playback + z, tmp + 1); + if ((z==1) && (emu->card_capabilities->spdif_bug)) { + /* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */ + dev_info(emu->card->dev, + "Installing spdif_bug patch: %s\n", + emu->card_capabilities->name); + A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000); + A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + } else { + A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000); + } } + snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0); + gpr += 2; + + A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2); + A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4); + A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5); } - snd_emu10k1_init_stereo_onoff_control(controls + nctl++, SNDRV_CTL_NAME_IEC958("Optical Raw ",PLAYBACK,SWITCH), gpr, 0); - gpr += 2; - - A_PUT_STEREO_OUTPUT(A_EXTOUT_REAR_L, A_EXTOUT_REAR_R, playback+2); - A_PUT_OUTPUT(A_EXTOUT_CENTER, playback+4); - A_PUT_OUTPUT(A_EXTOUT_LFE, playback+5); /* ADC buffer */ #ifdef EMU10K1_CAPTURE_DIGITAL_OUT -- cgit v1.2.3 From de0dc31070a54d146bb5e7e5a739c9588034165c Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:21 +0200 Subject: ALSA: emu10k1: skip mic capture PCM for cards without AC97 codec The microphone capture device is a feature of the AC97 codec, so its availability should be coupled to the presence of that codec. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1.c | 8 +++++--- sound/pci/emu10k1/emufx.c | 28 +++++++++++++++------------- 2 files changed, 20 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c index b8163f26004a..0c97237af922 100644 --- a/sound/pci/emu10k1/emu10k1.c +++ b/sound/pci/emu10k1/emu10k1.c @@ -107,9 +107,11 @@ static int snd_card_emu10k1_probe(struct pci_dev *pci, err = snd_emu10k1_pcm(emu, 0); if (err < 0) return err; - err = snd_emu10k1_pcm_mic(emu, 1); - 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; diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 8c171bbaf02c..9c9ffba7e591 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1380,19 +1380,21 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) gpr_map[gpr + 2] = 0x00000000; gpr += 3; } else { - /* AC'97 Playback Volume - used only for mic (renamed later) */ - A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); - gpr += 2; - /* AC'97 Capture Volume - used only for mic */ - A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); - A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); - snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); - gpr += 2; - - /* mic capture buffer */ - A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); + if (emu->card_capabilities->ac97_chip) { + /* AC'97 Playback Volume - used only for mic (renamed later) */ + A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(stereo_mix+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "AMic Playback Volume", gpr, 0); + gpr += 2; + /* AC'97 Capture Volume - used only for mic */ + A_ADD_VOLUME_IN(capture, gpr, A_EXTIN_AC97_L); + A_ADD_VOLUME_IN(capture+1, gpr+1, A_EXTIN_AC97_R); + snd_emu10k1_init_stereo_control(&controls[nctl++], "Mic Capture Volume", gpr, 0); + gpr += 2; + + /* mic capture buffer */ + A_OP(icode, &ptr, iINTERP, A_EXTOUT(A_EXTOUT_MIC_CAP), A_EXTIN(A_EXTIN_AC97_L), A_C_40000000, A_EXTIN(A_EXTIN_AC97_R)); + } /* Audigy CD Playback Volume */ A_ADD_VOLUME_IN(stereo_mix, gpr, A_EXTIN_SPDIF_CD_L); -- cgit v1.2.3 From 1298bc978afba0a507cedd0a91e53267ca152804 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:22 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 1: DSP attenuation Fractional multiplication with the maximal value 2^31-1 causes some tiny distortion. Instead, we want to multiply with the full 2^31. The catch is of course that this cannot be represented in the DSP's signed 32 bit registers. One way to deal with this is to encode 1.0 as a negative number and special-case it. As a matter of fact, the SbLive! code path already contained such code, though the controls never actually exercised it. A more efficient approach is to use negative values, which actually extend to -2^31. Accordingly, for all the volume adjustments we now use the MAC1 instruction which negates the X operand. The range of the controls in highres mode is extended downwards, so -1 is the new zero/mute. At maximal excursion, real zero is not mute any more, but I don't think anyone will notice this behavior change. ;-) That also required making the min/max/values in the control structs signed. This technically changes the user space interface, but it seems implausible that someone would notice - the numbers were actually treated as if they were signed anyway (and in the actual mixer iface they _are_). And without this change, the min value didn't even make sense in the first place (and no-one noticed, because it was always 0). Tested-by: Jonathan Dowland Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 6 +-- include/uapi/sound/emu10k1.h | 8 +-- sound/pci/emu10k1/emufx.c | 119 ++++++++++++++++++++----------------------- 3 files changed, 64 insertions(+), 69 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index e9b1729ade60..8e27f7074230 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1508,9 +1508,9 @@ struct snd_emu10k1_fx8010_ctl { unsigned int vcount; unsigned int count; /* count of GPR (1..16) */ unsigned short gpr[32]; /* GPR number(s) */ - unsigned int value[32]; - unsigned int min; /* minimum range */ - unsigned int max; /* maximum range */ + int value[32]; + int min; /* minimum range */ + int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ struct snd_kcontrol *kcontrol; }; diff --git a/include/uapi/sound/emu10k1.h b/include/uapi/sound/emu10k1.h index c8e131d6da00..4c32a116e7ad 100644 --- a/include/uapi/sound/emu10k1.h +++ b/include/uapi/sound/emu10k1.h @@ -308,6 +308,8 @@ struct snd_emu10k1_fx8010_info { #define EMU10K1_GPR_TRANSLATION_BASS 2 #define EMU10K1_GPR_TRANSLATION_TREBLE 3 #define EMU10K1_GPR_TRANSLATION_ONOFF 4 +#define EMU10K1_GPR_TRANSLATION_NEGATE 5 +#define EMU10K1_GPR_TRANSLATION_NEG_TABLE100 6 enum emu10k1_ctl_elem_iface { EMU10K1_CTL_ELEM_IFACE_MIXER = 2, /* virtual mixer device */ @@ -328,9 +330,9 @@ struct snd_emu10k1_fx8010_control_gpr { unsigned int vcount; /* visible count */ unsigned int count; /* count of GPR (1..16) */ unsigned short gpr[32]; /* GPR number(s) */ - unsigned int value[32]; /* initial values */ - unsigned int min; /* minimum range */ - unsigned int max; /* maximum range */ + int value[32]; /* initial values */ + int min; /* minimum range */ + int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ const unsigned int *tlv; }; diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 9c9ffba7e591..4c9d67e72ae5 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -332,7 +332,7 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); struct snd_emu10k1_fx8010_ctl *ctl = (struct snd_emu10k1_fx8010_ctl *) kcontrol->private_value; - unsigned int nval, val; + int nval, val; unsigned int i, j; int change = 0; @@ -349,9 +349,16 @@ static int snd_emu10k1_gpr_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl case EMU10K1_GPR_TRANSLATION_NONE: snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, val); break; + case EMU10K1_GPR_TRANSLATION_NEGATE: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, ~val); + break; case EMU10K1_GPR_TRANSLATION_TABLE100: snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, db_table[val]); break; + case EMU10K1_GPR_TRANSLATION_NEG_TABLE100: + snd_emu10k1_ptr_write(emu, emu->gpr_base + ctl->gpr[i], 0, + val == 100 ? 0x80000000 : -(int)db_table[val]); + break; case EMU10K1_GPR_TRANSLATION_BASS: if ((ctl->count % 5) != 0 || (ctl->count / 5) != ctl->vcount) { change = -EIO; @@ -771,8 +778,10 @@ static int snd_emu10k1_verify_controls(struct snd_emu10k1 *emu, } switch (gctl->translation) { case EMU10K1_GPR_TRANSLATION_NONE: + case EMU10K1_GPR_TRANSLATION_NEGATE: break; case EMU10K1_GPR_TRANSLATION_TABLE100: + case EMU10K1_GPR_TRANSLATION_NEG_TABLE100: if (gctl->min != 0 || gctl->max != 100) { err = -EINVAL; goto __error; @@ -1137,44 +1146,44 @@ static int snd_emu10k1_ipcm_peek(struct snd_emu10k1 *emu, static void snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) + const char *name, int gpr, int defval) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 1; if (high_res_gpr_volume) { - ctl->min = 0; + ctl->min = -1; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; - ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; - defval = defval * 0x7fffffffLL / 100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; + defval = defval * 0x80000000LL / 100 - 1; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; - ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEG_TABLE100; } ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; } static void snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) + const char *name, int gpr, int defval) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); ctl->vcount = ctl->count = 2; if (high_res_gpr_volume) { - ctl->min = 0; + ctl->min = -1; ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; - ctl->translation = EMU10K1_GPR_TRANSLATION_NONE; - defval = defval * 0x7fffffffLL / 100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; + defval = defval * 0x80000000LL / 100 - 1; } else { ctl->min = 0; ctl->max = 100; ctl->tlv = snd_emu10k1_db_scale1; - ctl->translation = EMU10K1_GPR_TRANSLATION_TABLE100; + ctl->translation = EMU10K1_GPR_TRANSLATION_NEG_TABLE100; } ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; @@ -1293,36 +1302,36 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) #if 1 /* PCM front Playback Volume (independent from stereo mix) - * playback = 0 + ( gpr * FXBUS_PCM_LEFT_FRONT >> 31) - * where gpr contains attenuation from corresponding mixer control + * playback = -gpr * FXBUS_PCM_LEFT_FRONT >> 31 + * where gpr contains negated attenuation from corresponding mixer control * (snd_emu10k1_init_stereo_control) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_FRONT)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_FRONT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Front Playback Volume", gpr, 100); gpr += 2; /* PCM Surround Playback (independent from stereo mix) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_REAR)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+2), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_REAR)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+3), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_REAR)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Surround Playback Volume", gpr, 100); gpr += 2; /* PCM Side Playback (independent from stereo mix) */ if (emu->card_capabilities->spk71) { - A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_SIDE)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+6), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT_SIDE)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+7), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT_SIDE)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Side Playback Volume", gpr, 100); gpr += 2; } /* PCM Center Playback (independent from stereo mix) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_CENTER)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+4), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_CENTER)); snd_emu10k1_init_mono_control(&controls[nctl++], "PCM Center Playback Volume", gpr, 100); gpr++; /* PCM LFE Playback (independent from stereo mix) */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LFE)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+5), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LFE)); snd_emu10k1_init_mono_control(&controls[nctl++], "PCM LFE Playback Volume", gpr, 100); gpr++; @@ -1330,26 +1339,26 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) * Stereo Mix */ /* Wave (PCM) Playback Volume (will be renamed later) */ - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Wave Playback Volume", gpr, 100); gpr += 2; /* Synth Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(stereo_mix+1), A_GPR(stereo_mix+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+0), A_GPR(stereo_mix+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(stereo_mix+1), A_GPR(stereo_mix+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Playback Volume", gpr, 100); gpr += 2; /* Wave (PCM) Capture */ - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_C_00000000, A_GPR(gpr), A_FXBUS(FXBUS_PCM_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_C_00000000, A_GPR(gpr+1), A_FXBUS(FXBUS_PCM_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "PCM Capture Volume", gpr, 0); gpr += 2; /* Synth Capture */ - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_FXBUS(FXBUS_MIDI_LEFT)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0); gpr += 2; @@ -1357,23 +1366,23 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) * inputs */ #define A_ADD_VOLUME_IN(var,vol,input) \ -A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) + A_OP(icode, &ptr, iMAC1, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) if (emu->card_capabilities->emu_model) { /* EMU1010 DSP 0 and DSP 1 Capture */ // The 24 MSB hold the actual value. We implicitly discard the 16 LSB. if (emu->card_capabilities->ca0108_chip) { // For unclear reasons, the EMU32IN cannot be the Y operand! - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A3_EMU32IN(0x0), A_GPR(gpr)); // 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. - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A3_EMU32IN(0x1), A_C_00000000, A_C_00000000); } else { - A_OP(icode, &ptr, iMAC0, A_GPR(capture+0), A_GPR(capture+0), A_GPR(gpr), A_P16VIN(0x0)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+0), A_GPR(capture+0), A_P16VIN(0x0), A_GPR(gpr)); // 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. - A_OP(icode, &ptr, iMAC0, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_GPR(gpr+2)); + A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+2), A_GPR(gpr+1)); A_OP(icode, &ptr, iACC3, A_GPR(gpr+2), A_P16VIN(0x1), A_C_00000000, A_C_00000000); } snd_emu10k1_init_stereo_control(&controls[nctl++], "EMU Capture Volume", gpr, 0); @@ -1465,33 +1474,33 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) } /* Stereo Mix Front Playback Volume */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+1), A_GPR(playback+1), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback), A_GPR(playback), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+1), A_GPR(playback+1), A_GPR(gpr+1), A_GPR(stereo_mix+1)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Front Playback Volume", gpr, 100); gpr += 2; /* Stereo Mix Surround Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+3), A_GPR(playback+3), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+2), A_GPR(playback+2), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+3), A_GPR(playback+3), A_GPR(gpr+1), A_GPR(stereo_mix+1)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Surround Playback Volume", gpr, 0); gpr += 2; /* Stereo Mix Center Playback */ /* Center = sub = Left/2 + Right/2 */ A_OP(icode, &ptr, iINTERP, A_GPR(tmp), A_GPR(stereo_mix), A_C_40000000, A_GPR(stereo_mix+1)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+4), A_GPR(playback+4), A_GPR(gpr), A_GPR(tmp)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+4), A_GPR(playback+4), A_GPR(gpr), A_GPR(tmp)); snd_emu10k1_init_mono_control(&controls[nctl++], "Center Playback Volume", gpr, 0); gpr++; /* Stereo Mix LFE Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+5), A_GPR(playback+5), A_GPR(gpr), A_GPR(tmp)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+5), A_GPR(playback+5), A_GPR(gpr), A_GPR(tmp)); snd_emu10k1_init_mono_control(&controls[nctl++], "LFE Playback Volume", gpr, 0); gpr++; if (emu->card_capabilities->spk71) { /* Stereo Mix Side Playback */ - A_OP(icode, &ptr, iMAC0, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix)); - A_OP(icode, &ptr, iMAC0, A_GPR(playback+7), A_GPR(playback+7), A_GPR(gpr+1), A_GPR(stereo_mix+1)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+6), A_GPR(playback+6), A_GPR(gpr), A_GPR(stereo_mix)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+7), A_GPR(playback+7), A_GPR(gpr+1), A_GPR(stereo_mix+1)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Side Playback Volume", gpr, 0); gpr += 2; } @@ -1579,7 +1588,7 @@ A_OP(icode, &ptr, iMAC0, A_GPR(var), A_GPR(var), A_GPR(vol), A_EXTIN(input)) /* Master volume (will be renamed later) */ for (z = 0; z < 8; z++) - A_OP(icode, &ptr, iMAC0, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); + A_OP(icode, &ptr, iMAC1, A_GPR(playback+z), A_C_00000000, A_GPR(gpr), A_GPR(playback+z)); snd_emu10k1_init_mono_control(&controls[nctl++], "Wave Master Playback Volume", gpr, 0); gpr++; @@ -1731,30 +1740,14 @@ __err_gpr: * initial DSP configuration for Emu10k1 */ -/* when volume = max, then copy only to avoid volume modification */ -/* with iMAC0 (negative values) */ +/* Volumes are in the [-2^31, 0] range, zero being mute. */ static void _volume(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) { - OP(icode, ptr, iMAC0, dst, C_00000000, src, vol); - OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff); - OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000001); - OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000); + OP(icode, ptr, iMAC1, dst, C_00000000, src, vol); } static void _volume_add(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) { - OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff); - OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002); - OP(icode, ptr, iMACINT0, dst, dst, src, C_00000001); - OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001); - OP(icode, ptr, iMAC0, dst, dst, src, vol); -} -static void _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst, u32 src, u32 vol) -{ - OP(icode, ptr, iANDXOR, C_00000000, vol, C_ffffffff, C_7fffffff); - OP(icode, ptr, iSKIP, GPR_COND, GPR_COND, CC_REG_NONZERO, C_00000002); - OP(icode, ptr, iACC3, dst, src, C_00000000, C_00000000); - OP(icode, ptr, iSKIP, C_00000000, C_7fffffff, C_7fffffff, C_00000001); - OP(icode, ptr, iMAC0, dst, C_00000000, src, vol); + OP(icode, ptr, iMAC1, dst, dst, src, vol); } #define VOLUME(icode, ptr, dst, src, vol) \ @@ -1766,7 +1759,7 @@ static void _volume_out(struct snd_emu10k1_fx8010_code *icode, u32 *ptr, u32 dst #define VOLUME_ADDIN(icode, ptr, dst, src, vol) \ _volume_add(icode, ptr, GPR(dst), EXTIN(src), GPR(vol)) #define VOLUME_OUT(icode, ptr, dst, src, vol) \ - _volume_out(icode, ptr, EXTOUT(dst), GPR(src), GPR(vol)) + _volume(icode, ptr, EXTOUT(dst), GPR(src), GPR(vol)) #define _SWITCH(icode, ptr, dst, src, sw) \ OP((icode), ptr, iMACINT0, dst, C_00000000, src, sw); #define SWITCH(icode, ptr, dst, src, sw) \ -- cgit v1.2.3 From bcdbd3b7888e1db89b7b2f7c78237c9ed5c2ebb1 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Sun, 14 May 2023 19:03:23 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 2: voice attenuation The voice volume is a raw fractional multiplier that can't actually represent 1.0. To still enable real pass-through, we now set the volume to 0.5 (which results in no loss of precision, as the FX bus provides fractional values) and scale up the samples in DSP code. To maintain backwards compatibility with existing configuration files, we rescale the values in the mixer controls. The range is extended upwards from 0xffff to 0x1fffd, which actually introduces the possibility of specifying an amplification. There is still a minor incompatibility with user space, namely if someone loaded custom DSP code. They'll just get half the volume, so this doesn't seem like a big deal. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230514170323.3408834-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- Documentation/sound/cards/audigy-mixer.rst | 2 +- Documentation/sound/cards/sb-live-mixer.rst | 2 +- include/sound/emu10k1.h | 3 +++ sound/pci/emu10k1/emufx.c | 30 +++++++++++++++++------------ sound/pci/emu10k1/emumixer.c | 15 +++++++++------ sound/pci/emu10k1/emupcm.c | 4 ++-- 6 files changed, 34 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/cards/audigy-mixer.rst b/Documentation/sound/cards/audigy-mixer.rst index aa176451d5b5..e02dd890d089 100644 --- a/Documentation/sound/cards/audigy-mixer.rst +++ b/Documentation/sound/cards/audigy-mixer.rst @@ -227,7 +227,7 @@ PCM stream related controls name='EMU10K1 PCM Volume',index 0-31 ------------------------------------ -Channel volume attenuation in range 0-0xffff. The maximum value (no +Channel volume attenuation in range 0-0x1fffd. The middle value (no attenuation) is default. The channel mapping for three values is as follows: diff --git a/Documentation/sound/cards/sb-live-mixer.rst b/Documentation/sound/cards/sb-live-mixer.rst index 819886634400..4dd9bfe01bd8 100644 --- a/Documentation/sound/cards/sb-live-mixer.rst +++ b/Documentation/sound/cards/sb-live-mixer.rst @@ -258,7 +258,7 @@ PCM stream related controls ``name='EMU10K1 PCM Volume',index 0-31`` ---------------------------------------- -Channel volume attenuation in range 0-0xffff. The maximum value (no +Channel volume attenuation in range 0-0x1fffd. The middle value (no attenuation) is default. The channel mapping for three values is as follows: diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 8e27f7074230..7bcb1a2d779a 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -415,6 +415,7 @@ SUB_REG(PTRX, PITCHTARGET, 0xffff0000) /* Pitch target of specified channel */ SUB_REG(PTRX, FXSENDAMOUNT_A, 0x0000ff00) /* Linear level of channel output sent to FX send bus A */ SUB_REG(PTRX, FXSENDAMOUNT_B, 0x000000ff) /* Linear level of channel output sent to FX send bus B */ +// Note: the volumes are raw multpliers, so real 100% is impossible. #define CVCF 0x02 /* Current volume and filter cutoff register */ SUB_REG(CVCF, CURRENTVOL, 0xffff0000) /* Current linear volume of specified channel */ SUB_REG(CVCF, CURRENTFILTER, 0x0000ffff) /* Current filter cutoff frequency of specified channel */ @@ -1477,6 +1478,8 @@ struct snd_emu10k1_pcm_mixer { /* mono, left, right x 8 sends (4 on emu10k1) */ unsigned char send_routing[3][8]; unsigned char send_volume[3][8]; + // 0x8000 is neutral. The mixer code rescales it to 0xffff to maintain + // backwards compatibility with user space. unsigned short attn[3]; struct snd_emu10k1_pcm *epcm; }; diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index 4c9d67e72ae5..f64b2b4eb348 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1361,7 +1361,13 @@ static int _snd_emu10k1_audigy_init_efx(struct snd_emu10k1 *emu) A_OP(icode, &ptr, iMAC1, A_GPR(capture+1), A_GPR(capture+1), A_GPR(gpr+1), A_FXBUS(FXBUS_MIDI_RIGHT)); snd_emu10k1_init_stereo_control(&controls[nctl++], "Synth Capture Volume", gpr, 0); gpr += 2; - + + // We need to double the volume, as we configure the voices for half volume, + // which is necessary for bit-identical reproduction. + { static_assert(stereo_mix == playback + SND_EMU10K1_PLAYBACK_CHANNELS); } + for (z = 0; z < SND_EMU10K1_PLAYBACK_CHANNELS + 2; z++) + A_OP(icode, &ptr, iACC3, A_GPR(playback + z), A_GPR(playback + z), A_GPR(playback + z), A_C_00000000); + /* * inputs */ @@ -1826,18 +1832,18 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* * Process FX Buses */ - OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000004); + OP(icode, &ptr, iMACINT0, GPR(0), C_00000000, FXBUS(FXBUS_PCM_LEFT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(1), C_00000000, FXBUS(FXBUS_PCM_RIGHT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(2), C_00000000, FXBUS(FXBUS_MIDI_LEFT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(3), C_00000000, FXBUS(FXBUS_MIDI_RIGHT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(4), C_00000000, FXBUS(FXBUS_PCM_LEFT_REAR), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(5), C_00000000, FXBUS(FXBUS_PCM_RIGHT_REAR), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(6), C_00000000, FXBUS(FXBUS_PCM_CENTER), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(7), C_00000000, FXBUS(FXBUS_PCM_LFE), C_00000008); OP(icode, &ptr, iMACINT0, GPR(8), C_00000000, C_00000000, C_00000000); /* S/PDIF left */ OP(icode, &ptr, iMACINT0, GPR(9), C_00000000, C_00000000, C_00000000); /* S/PDIF right */ - OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000004); - OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000004); + OP(icode, &ptr, iMACINT0, GPR(10), C_00000000, FXBUS(FXBUS_PCM_LEFT_FRONT), C_00000008); + OP(icode, &ptr, iMACINT0, GPR(11), C_00000000, FXBUS(FXBUS_PCM_RIGHT_FRONT), C_00000008); /* Raw S/PDIF PCM */ ipcm->substream = 0; @@ -1931,7 +1937,7 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* Wave Center/LFE Playback Volume */ OP(icode, &ptr, iACC3, GPR(tmp + 0), FXBUS(FXBUS_PCM_LEFT), FXBUS(FXBUS_PCM_RIGHT), C_00000000); - OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000002); + OP(icode, &ptr, iMACINT0, GPR(tmp + 0), C_00000000, GPR(tmp + 0), C_00000004); VOLUME(icode, &ptr, playback + 4, tmp + 0, gpr); snd_emu10k1_init_mono_control(controls + i++, "Wave Center Playback Volume", gpr++, 0); VOLUME(icode, &ptr, playback + 5, tmp + 0, gpr); diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 48f0d3f8b8e7..9fa4bc845116 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1352,7 +1352,7 @@ static int snd_emu10k1_attn_info(struct snd_kcontrol *kcontrol, struct snd_ctl_e uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 3; uinfo->value.integer.min = 0; - uinfo->value.integer.max = 0xffff; + uinfo->value.integer.max = 0x1fffd; return 0; } @@ -1365,7 +1365,7 @@ static int snd_emu10k1_attn_get(struct snd_kcontrol *kcontrol, int idx; for (idx = 0; idx < 3; idx++) - ucontrol->value.integer.value[idx] = mix->attn[idx]; + ucontrol->value.integer.value[idx] = mix->attn[idx] * 0xffffU / 0x8000U; return 0; } @@ -1380,7 +1380,8 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, spin_lock_irqsave(&emu->reg_lock, flags); for (idx = 0; idx < 3; idx++) { - val = ucontrol->value.integer.value[idx] & 0xffff; + unsigned uval = ucontrol->value.integer.value[idx] & 0x1ffff; + val = uval * 0x8000U / 0xffffU; if (mix->attn[idx] != val) { mix->attn[idx] = val; change = 1; @@ -1547,7 +1548,7 @@ static int snd_emu10k1_efx_attn_info(struct snd_kcontrol *kcontrol, struct snd_c uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; uinfo->count = 1; uinfo->value.integer.min = 0; - uinfo->value.integer.max = 0xffff; + uinfo->value.integer.max = 0x1fffd; return 0; } @@ -1558,7 +1559,7 @@ static int snd_emu10k1_efx_attn_get(struct snd_kcontrol *kcontrol, struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[snd_ctl_get_ioffidx(kcontrol, &ucontrol->id)]; - ucontrol->value.integer.value[0] = mix->attn[0]; + ucontrol->value.integer.value[0] = mix->attn[0] * 0xffffU / 0x8000U; return 0; } @@ -1570,9 +1571,11 @@ static int snd_emu10k1_efx_attn_put(struct snd_kcontrol *kcontrol, int ch = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); struct snd_emu10k1_pcm_mixer *mix = &emu->efx_pcm_mixer[ch]; int change = 0, val; + unsigned uval; spin_lock_irqsave(&emu->reg_lock, flags); - val = ucontrol->value.integer.value[0] & 0xffff; + uval = ucontrol->value.integer.value[0] & 0x1ffff; + val = uval * 0x8000U / 0xffffU; if (mix->attn[0] != val) { mix->attn[0] = val; change = 1; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 5ed404e8ed39..6e6d3103ed90 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1049,7 +1049,7 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) mix->send_routing[0][0] = i; memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = 255; - mix->attn[0] = 0xffff; + mix->attn[0] = 0x8000; mix->epcm = epcm; snd_emu10k1_pcm_efx_mixer_notify(emu, i, 1); } @@ -1098,7 +1098,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = mix->send_volume[0][1] = mix->send_volume[1][0] = mix->send_volume[2][1] = 255; - mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff; + mix->attn[0] = mix->attn[1] = mix->attn[2] = 0x8000; mix->epcm = epcm; snd_emu10k1_pcm_mixer_notify(emu, substream->number, 1); return 0; -- cgit v1.2.3 From 24cdfcb4ccbb75d85d70460a69f3105fda33d385 Mon Sep 17 00:00:00 2001 From: Min-Hua Chen Date: Wed, 17 May 2023 06:38:05 +0800 Subject: ALSA: compat_ioctl: use correct snd_ctl_elem_type_t type SNDRV_CTL_ELEM_TYPE_* are type of snd_ctl_elem_type_t, we have to __force cast them to int when comparing them with int to fix the following sparse warnings. sound/core/control_compat.c:203:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:205:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:207:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:209:14: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:237:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:238:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:270:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer sound/core/control_compat.c:271:21: sparse: warning: restricted snd_ctl_elem_type_t degrades to integer Signed-off-by: Min-Hua Chen Link: https://lore.kernel.org/r/20230516223806.185683-1-minhuadotchen@gmail.com Signed-off-by: Takashi Iwai --- sound/core/control_compat.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index d8a86d1a99d6..9cae5d74335c 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -197,7 +197,7 @@ static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, return err; } -static int get_elem_size(int type, int count) +static int get_elem_size(snd_ctl_elem_type_t type, int count) { switch (type) { case SNDRV_CTL_ELEM_TYPE_INTEGER64: @@ -234,8 +234,8 @@ static int copy_ctl_value_from_user(struct snd_card *card, if (type < 0) return type; - if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -244,7 +244,7 @@ static int copy_ctl_value_from_user(struct snd_card *card, data->value.integer.value[i] = val; } } else { - size = get_elem_size(type, count); + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); if (size < 0) { dev_err(card->dev, "snd_ioctl32_ctl_elem_value: unknown type %d\n", type); return -EINVAL; @@ -267,8 +267,8 @@ static int copy_ctl_value_to_user(void __user *userdata, struct snd_ctl_elem_value32 __user *data32 = userdata; int i, size; - if (type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || - type == SNDRV_CTL_ELEM_TYPE_INTEGER) { + if (type == (__force int)SNDRV_CTL_ELEM_TYPE_BOOLEAN || + type == (__force int)SNDRV_CTL_ELEM_TYPE_INTEGER) { for (i = 0; i < count; i++) { s32 __user *intp = valuep; int val; @@ -277,7 +277,7 @@ static int copy_ctl_value_to_user(void __user *userdata, return -EFAULT; } } else { - size = get_elem_size(type, count); + size = get_elem_size((__force snd_ctl_elem_type_t)type, count); if (copy_to_user(valuep, data->value.bytes.data, size)) return -EFAULT; } -- cgit v1.2.3 From 155e3d3bf0cdf88430a6e6da629316d9cf766cc7 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:07 +0200 Subject: ALSA: emu10k1: straighten out FX send init The mixer structures were filled in two places: on driver init, and when the devices are opened. The latter made the former pointless, so we remove the former. This implies that mixer dumps may now return all zeroes, which is OK, as restoring them is meaningless as well. Things were even weirder for the (generally unused) secondary sends: Some of the initialization loops were forgotten when support for Audigy was added, thus creating the technically illegal state of multiple sends being routed to the same FX accumulator (though it apparently doesn't matter when the amount is zero). The global multi-channel init used some rather bizarre values for the secondary sends, and the init on open actually forgot to re-initialize them. We now use a not really more useful, but simpler formula. The direct register init was also bogus. This doesn't really matter, as the value is overwritten when a voice comes into use, but still. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- Documentation/sound/cards/audigy-mixer.rst | 36 ++++++++++++------------ sound/pci/emu10k1/emu10k1_main.c | 2 +- sound/pci/emu10k1/emumixer.c | 44 +----------------------------- sound/pci/emu10k1/emupcm.c | 14 ++++------ 4 files changed, 26 insertions(+), 70 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/cards/audigy-mixer.rst b/Documentation/sound/cards/audigy-mixer.rst index e02dd890d089..ea66b50a2b03 100644 --- a/Documentation/sound/cards/audigy-mixer.rst +++ b/Documentation/sound/cards/audigy-mixer.rst @@ -240,30 +240,30 @@ name='EMU10K1 PCM Send Routing',index 0-31 This control specifies the destination - FX-bus accumulators. There are 24 values in this mapping: -* 0 - mono, A destination (FX-bus 0-63), default 0 -* 1 - mono, B destination (FX-bus 0-63), default 1 -* 2 - mono, C destination (FX-bus 0-63), default 2 -* 3 - mono, D destination (FX-bus 0-63), default 3 -* 4 - mono, E destination (FX-bus 0-63), default 0 -* 5 - mono, F destination (FX-bus 0-63), default 0 -* 6 - mono, G destination (FX-bus 0-63), default 0 -* 7 - mono, H destination (FX-bus 0-63), default 0 -* 8 - left, A destination (FX-bus 0-63), default 0 -* 9 - left, B destination (FX-bus 0-63), default 1 +* 0 - mono, A destination (FX-bus 0-63), default 0 +* 1 - mono, B destination (FX-bus 0-63), default 1 +* 2 - mono, C destination (FX-bus 0-63), default 2 +* 3 - mono, D destination (FX-bus 0-63), default 3 +* 4 - mono, E destination (FX-bus 0-63), default 4 +* 5 - mono, F destination (FX-bus 0-63), default 5 +* 6 - mono, G destination (FX-bus 0-63), default 6 +* 7 - mono, H destination (FX-bus 0-63), default 7 +* 8 - left, A destination (FX-bus 0-63), default 0 +* 9 - left, B destination (FX-bus 0-63), default 1 * 10 - left, C destination (FX-bus 0-63), default 2 * 11 - left, D destination (FX-bus 0-63), default 3 -* 12 - left, E destination (FX-bus 0-63), default 0 -* 13 - left, F destination (FX-bus 0-63), default 0 -* 14 - left, G destination (FX-bus 0-63), default 0 -* 15 - left, H destination (FX-bus 0-63), default 0 +* 12 - left, E destination (FX-bus 0-63), default 4 +* 13 - left, F destination (FX-bus 0-63), default 5 +* 14 - left, G destination (FX-bus 0-63), default 6 +* 15 - left, H destination (FX-bus 0-63), default 7 * 16 - right, A destination (FX-bus 0-63), default 0 * 17 - right, B destination (FX-bus 0-63), default 1 * 18 - right, C destination (FX-bus 0-63), default 2 * 19 - right, D destination (FX-bus 0-63), default 3 -* 20 - right, E destination (FX-bus 0-63), default 0 -* 21 - right, F destination (FX-bus 0-63), default 0 -* 22 - right, G destination (FX-bus 0-63), default 0 -* 23 - right, H destination (FX-bus 0-63), default 0 +* 20 - right, E destination (FX-bus 0-63), default 4 +* 21 - right, F destination (FX-bus 0-63), default 5 +* 22 - right, G destination (FX-bus 0-63), default 6 +* 23 - right, H destination (FX-bus 0-63), default 7 Don't forget that it's illegal to assign a channel to the same FX-bus accumulator more than once (it means 0=0 && 1=0 is an invalid combination). diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 068cb6624e36..5c8f38f20fcc 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -95,7 +95,7 @@ void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) snd_emu10k1_ptr_write(emu, A_CSFE, ch, 0); snd_emu10k1_ptr_write(emu, A_CSHG, ch, 0); snd_emu10k1_ptr_write(emu, A_FXRT1, ch, 0x03020100); - snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x3f3f3f3f); + snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x07060504); snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, ch, 0); } } diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 9fa4bc845116..e067a4066cda 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1752,7 +1752,7 @@ static int rename_ctl(struct snd_card *card, const char *src, const char *dst) int snd_emu10k1_mixer(struct snd_emu10k1 *emu, int pcm_device, int multi_device) { - int err, pcm; + int err; struct snd_kcontrol *kctl; struct snd_card *card = emu->card; const char * const *c; @@ -2016,48 +2016,6 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, if (err) return err; - /* initialize the routing and volume table for each pcm playback stream */ - for (pcm = 0; pcm < 32; pcm++) { - struct snd_emu10k1_pcm_mixer *mix; - int v; - - mix = &emu->pcm_mixer[pcm]; - mix->epcm = NULL; - - for (v = 0; v < 4; v++) - mix->send_routing[0][v] = - mix->send_routing[1][v] = - mix->send_routing[2][v] = v; - - memset(&mix->send_volume, 0, sizeof(mix->send_volume)); - mix->send_volume[0][0] = mix->send_volume[0][1] = - mix->send_volume[1][0] = mix->send_volume[2][1] = 255; - - mix->attn[0] = mix->attn[1] = mix->attn[2] = 0xffff; - } - - /* initialize the routing and volume table for the multichannel playback stream */ - for (pcm = 0; pcm < NUM_EFX_PLAYBACK; pcm++) { - struct snd_emu10k1_pcm_mixer *mix; - int v; - - mix = &emu->efx_pcm_mixer[pcm]; - mix->epcm = NULL; - - mix->send_routing[0][0] = pcm; - mix->send_routing[0][1] = (pcm == 0) ? 1 : 0; - for (v = 0; v < 2; v++) - mix->send_routing[0][2+v] = 13+v; - if (emu->audigy) - for (v = 0; v < 4; v++) - mix->send_routing[0][4+v] = 60+v; - - memset(&mix->send_volume, 0, sizeof(mix->send_volume)); - mix->send_volume[0][0] = 255; - - mix->attn[0] = 0xffff; - } - if (!emu->card_capabilities->ecard && !emu->card_capabilities->emu_model) { /* sb live! and audigy */ kctl = snd_ctl_new1(&snd_emu10k1_spdif_mask_control, emu); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 6e6d3103ed90..c5ab0926d04f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -283,11 +283,8 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, /* volume parameters */ if (extra) { - memset(send_routing, 0, sizeof(send_routing)); - send_routing[0] = 0; - send_routing[1] = 1; - send_routing[2] = 2; - send_routing[3] = 3; + for (int i = 0; i < 8; i++) + send_routing[i] = i; memset(send_amount, 0, sizeof(send_amount)); } else { /* mono, left, right (master voice = left) */ @@ -1031,7 +1028,7 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) struct snd_emu10k1_pcm *epcm; struct snd_emu10k1_pcm_mixer *mix; struct snd_pcm_runtime *runtime = substream->runtime; - int i; + int i, j; epcm = kzalloc(sizeof(*epcm), GFP_KERNEL); if (epcm == NULL) @@ -1046,7 +1043,8 @@ static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) for (i = 0; i < NUM_EFX_PLAYBACK; i++) { mix = &emu->efx_pcm_mixer[i]; - mix->send_routing[0][0] = i; + for (j = 0; j < 8; j++) + mix->send_routing[0][j] = i + j; memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = 255; mix->attn[0] = 0x8000; @@ -1093,7 +1091,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) return err; } mix = &emu->pcm_mixer[substream->number]; - for (i = 0; i < 4; i++) + for (i = 0; i < 8; i++) mix->send_routing[0][i] = mix->send_routing[1][i] = mix->send_routing[2][i] = i; memset(&mix->send_volume, 0, sizeof(mix->send_volume)); mix->send_volume[0][0] = mix->send_volume[0][1] = -- cgit v1.2.3 From 94dabafea04e49448cfbb7c2d86ac0db2dbd5df9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:08 +0200 Subject: ALSA: emu10k1: cleanup envelope register init We (rightfully) don't enable the envelope engine for PCM voices, so any related setup is entirely pointless - the EMU8K documentation makes that very clear, and the fact that the various open drivers all use different values to no observable detriment pretty much confirms it. The remaining initializations are regrouped for clarity. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 - sound/pci/emu10k1/emu10k1_main.c | 10 +++---- sound/pci/emu10k1/emupcm.c | 42 +++++++-------------------- sound/pci/emu10k1/io.c | 61 ---------------------------------------- 4 files changed, 14 insertions(+), 100 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 7bcb1a2d779a..36687195c8f7 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1798,7 +1798,6 @@ void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data); -unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate); #ifdef CONFIG_PM_SLEEP void snd_emu10k1_suspend_regs(struct snd_emu10k1 *emu); diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 5c8f38f20fcc..793ae8797172 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -58,7 +58,6 @@ MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME); void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) { snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0); - snd_emu10k1_ptr_write(emu, IP, ch, 0); snd_emu10k1_ptr_write(emu, VTFT, ch, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, ch, CVCF_CURRENTFILTER_MASK); snd_emu10k1_ptr_write(emu, PTRX, ch, 0); @@ -72,19 +71,18 @@ void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) snd_emu10k1_ptr_write(emu, Z2, ch, 0); snd_emu10k1_ptr_write(emu, FXRT, ch, 0x32100000); - snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0); + // The rest is meaningless as long as DCYSUSV_CHANNELENABLE_MASK is zero snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0); + snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0); + snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0); + snd_emu10k1_ptr_write(emu, IP, ch, 0); snd_emu10k1_ptr_write(emu, IFATN, ch, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK); snd_emu10k1_ptr_write(emu, PEFE, ch, 0); snd_emu10k1_ptr_write(emu, FMMOD, ch, 0); snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24); /* 1 Hz */ snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24); /* 1 Hz */ - snd_emu10k1_ptr_write(emu, TEMPENV, ch, 0); - - /*** these are last so OFF prevents writing ***/ snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0); snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0); - snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0); snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0); snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index c5ab0926d04f..d377669a8a94 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -348,24 +348,9 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page); snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page); - /* modulation envelope */ + // Disable filter (in conjunction with CCCA_RESONANCE == 0) snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, ATKHLDM, voice, 0); - snd_emu10k1_ptr_write(emu, DCYSUSM, voice, 0x007f); - snd_emu10k1_ptr_write(emu, LFOVAL1, voice, 0x8000); - snd_emu10k1_ptr_write(emu, LFOVAL2, voice, 0x8000); - snd_emu10k1_ptr_write(emu, FMMOD, voice, 0); - snd_emu10k1_ptr_write(emu, TREMFRQ, voice, 0); - snd_emu10k1_ptr_write(emu, FM2FRQ2, voice, 0); - snd_emu10k1_ptr_write(emu, ENVVAL, voice, 0x8000); - /* volume envelope */ - snd_emu10k1_ptr_write(emu, ATKHLDV, voice, 0x7f7f); - snd_emu10k1_ptr_write(emu, ENVVOL, voice, 0x0000); - /* filter envelope */ - snd_emu10k1_ptr_write(emu, PEFE_FILTERAMOUNT, voice, 0x7f); - /* pitch envelope */ - snd_emu10k1_ptr_write(emu, PEFE_PITCHAMOUNT, voice, 0); spin_unlock_irqrestore(&emu->reg_lock, flags); } @@ -600,12 +585,12 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e } static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, - int master, int extra, + int master, struct snd_emu10k1_pcm_mixer *mix) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; - unsigned int attn, vattn; + unsigned int vattn; unsigned int voice, tmp; if (evoice == NULL) /* skip second voice for mono */ @@ -614,13 +599,10 @@ static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct s runtime = substream->runtime; voice = evoice->number; - attn = extra ? 0 : 0x00ff; tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0; vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0; - snd_emu10k1_ptr_write(emu, IFATN, voice, attn); snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, DCYSUSV, voice, 0x7f7f); snd_emu10k1_voice_clear_loop_stop(emu, voice); } @@ -628,7 +610,7 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; - unsigned int voice, pitch, pitch_target; + unsigned int voice, pitch_target; if (evoice == NULL) /* skip second voice for mono */ return; @@ -636,7 +618,6 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s runtime = substream->runtime; voice = evoice->number; - pitch = snd_emu10k1_rate_to_pitch(runtime->rate) >> 8; if (emu->card_capabilities->emu_model) pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else @@ -644,7 +625,6 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); if (master || evoice->epcm->type == PLAYBACK_EFX) snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); - snd_emu10k1_ptr_write(emu, IP, voice, pitch); if (extra) snd_emu10k1_voice_intr_enable(emu, voice); } @@ -659,10 +639,8 @@ static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, struct snd_ snd_emu10k1_voice_intr_disable(emu, voice); snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); - snd_emu10k1_ptr_write(emu, IFATN, voice, 0xffff); snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, IP, voice, 0); } static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu, @@ -707,9 +685,9 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime); mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, 0, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, 0, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL); + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, mix); + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, mix); + snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0); snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); @@ -853,11 +831,11 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, 1, NULL); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, 0, + snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, &emu->efx_pcm_mixer[0]); for (i = 1; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, 0, + snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, &emu->efx_pcm_mixer[i]); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0); snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 59b0f4d08c6b..f50943913a31 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -514,64 +514,3 @@ void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned outw(data, emu->port + AC97DATA); spin_unlock_irqrestore(&emu->emu_lock, flags); } - -/* - * convert rate to pitch - */ - -unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate) -{ - static const u32 logMagTable[128] = { - 0x00000, 0x02dfc, 0x05b9e, 0x088e6, 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2, - 0x1663f, 0x1918a, 0x1bc84, 0x1e72e, 0x2118b, 0x23b9a, 0x2655d, 0x28ed5, - 0x2b803, 0x2e0e8, 0x30985, 0x331db, 0x359eb, 0x381b6, 0x3a93d, 0x3d081, - 0x3f782, 0x41e42, 0x444c1, 0x46b01, 0x49101, 0x4b6c4, 0x4dc49, 0x50191, - 0x5269e, 0x54b6f, 0x57006, 0x59463, 0x5b888, 0x5dc74, 0x60029, 0x623a7, - 0x646ee, 0x66a00, 0x68cdd, 0x6af86, 0x6d1fa, 0x6f43c, 0x7164b, 0x73829, - 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5, 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e, - 0x86082, 0x88089, 0x8a064, 0x8c014, 0x8df98, 0x8fef1, 0x91e20, 0x93d26, - 0x95c01, 0x97ab4, 0x9993e, 0x9b79f, 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d, - 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537, 0xac241, 0xadf26, 0xafbe7, 0xb1885, - 0xb3500, 0xb5157, 0xb6d8c, 0xb899f, 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899, - 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587, 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c, - 0xceaec, 0xd053f, 0xd1f73, 0xd398a, 0xd5384, 0xd6d60, 0xd8720, 0xda0c3, - 0xdba4a, 0xdd3b4, 0xded03, 0xe0636, 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3, - 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08, 0xee44c, 0xefc78, 0xf148a, 0xf2c83, - 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71, 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df - }; - static const char logSlopeTable[128] = { - 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58, - 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53, - 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, - 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b, - 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47, - 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44, - 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41, - 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e, - 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c, - 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39, - 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37, - 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35, - 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34, - 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32, - 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30, - 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f - }; - int i; - - if (rate == 0) - return 0; /* Bail out if no leading "1" */ - rate *= 11185; /* Scale 48000 to 0x20002380 */ - for (i = 31; i > 0; i--) { - if (rate & 0x80000000) { /* Detect leading "1" */ - return (((unsigned int) (i - 15) << 20) + - logMagTable[0x7f & (rate >> 24)] + - (0x7f & (rate >> 17)) * - logSlopeTable[0x7f & (rate >> 24)]); - } - rate <<= 1; - } - - return 0; /* Should never reach this point */ -} - -- cgit v1.2.3 From a61c695aee87ba9c9f6b2996f98e933e3c33a049 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:09 +0200 Subject: ALSA: emu10k1: remove useless resets of stop-on-loop-end bits We initialize them at card init and don't touch them later, so there is no need to reset them again at voice start. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 ++ sound/pci/emu10k1/emupcm.c | 1 - sound/pci/emu10k1/io.c | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 36687195c8f7..a5e935e16651 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1792,8 +1792,10 @@ void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); +#if 0 void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); +#endif void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index d377669a8a94..2b6f5d2bbb3e 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -603,7 +603,6 @@ static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct s vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0; snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); - snd_emu10k1_voice_clear_loop_stop(emu, voice); } static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, int master, int extra) diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index f50943913a31..36fd6f7a0a2c 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -434,6 +434,7 @@ void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int spin_unlock_irqrestore(&emu->emu_lock, flags); } +#if 0 void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum) { unsigned long flags; @@ -471,6 +472,7 @@ void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voi outl(sol, emu->port + DATA); spin_unlock_irqrestore(&emu->emu_lock, flags); } +#endif void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait) { -- cgit v1.2.3 From 35a60d1edff4dec9a31862a3515676cd0fafe4e4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:10 +0200 Subject: ALSA: emu10k1: rewire {en,dis}abling interrupts for PCM playback We now enable ints even before triggering, and disable them only after stopping - otherwise there is a race condition we may plausibly run into when we pause/resume near the end of the buffer. Updating the epcm->running flag is moved the same way, as it affects the *_pointer() functions, which are called by the interrupt handler. Also, factor these out to own functions, for clarity. For multi-channel, the extra voice is now triggered after all regular voices - we wouldn't want to receive an int before all channels have passed the period boundary. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 2b6f5d2bbb3e..7b0ab4e02cfd 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -605,7 +605,9 @@ static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct s snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); } -static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, int master, int extra) +static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + int master) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; @@ -624,24 +626,36 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct s snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); if (master || evoice->epcm->type == PLAYBACK_EFX) snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); - if (extra) - snd_emu10k1_voice_intr_enable(emu, voice); } -static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) +static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) { unsigned int voice; if (evoice == NULL) return; voice = evoice->number; - snd_emu10k1_voice_intr_disable(emu, voice); snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); } +static void snd_emu10k1_playback_set_running(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm) +{ + epcm->running = 1; + snd_emu10k1_voice_intr_enable(emu, epcm->extra->number); +} + +static void snd_emu10k1_playback_set_stopped(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm) +{ + snd_emu10k1_voice_intr_disable(emu, epcm->extra->number); + epcm->running = 0; +} + static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu, struct snd_emu10k1_pcm *epcm, struct snd_pcm_substream *substream, @@ -687,18 +701,18 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, mix); snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, mix); snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1, 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0, 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); - epcm->running = 1; + snd_emu10k1_playback_set_running(emu, epcm); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: - epcm->running = 0; snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]); snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]); snd_emu10k1_playback_stop_voice(emu, epcm->extra); + snd_emu10k1_playback_set_stopped(emu, epcm); break; default: result = -EINVAL; @@ -836,20 +850,19 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, for (i = 1; i < NUM_EFX_PLAYBACK; i++) snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, &emu->efx_pcm_mixer[i]); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 0, 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1, 1); - for (i = 1; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0, 0); - epcm->running = 1; + snd_emu10k1_playback_set_running(emu, epcm); + for (i = 0; i < NUM_EFX_PLAYBACK; i++) + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - epcm->running = 0; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]); } snd_emu10k1_playback_stop_voice(emu, epcm->extra); + snd_emu10k1_playback_set_stopped(emu, epcm); break; default: result = -EINVAL; -- cgit v1.2.3 From 77e067d0fa0511daec7e4c72ec3f830e5faaee9e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:11 +0200 Subject: ALSA: emu10k1: skip needless setting of some voice registers Many registers are meaningless for stereo slaves and the extra voices. This patch cleans up these unnecessary register writes. snd_emu10k1_playback_{trigger,stop}_voice() is not called for stereo slaves any more. snd_emu10k1_playback_prepare_voice() is renamed to snd_emu10k1_playback_unmute_voice(), as this better reflects its remaining function. It's not called for the extra voices any more. Accordingly, snd_emu10k1_playback_mute_voice() is factored out from snd_emu10k1_playback_stop_voice(), and is called selectively as well. This doesn't add conditionals which would avoid initializing sub-registers, as that wouldn't pull its weight. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 8 +++++ sound/pci/emu10k1/emupcm.c | 89 +++++++++++++++++++++++++--------------------- 2 files changed, 56 insertions(+), 41 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index a5e935e16651..5c1e5b123362 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -404,6 +404,14 @@ SUB_REG(HCFG, LOCKTANKCACHE, 0x00000004) /* 1 = Cancel bustmaster accesses to ta // distortion), the modulation engine sets the target registers, towards // which the current registers "swerve" gradually. +// For the odd channel in a stereo pair, these registers are meaningless: +// CPF_STEREO, CPF_CURRENTPITCH, PTRX_PITCHTARGET, CCR_CACHEINVALIDSIZE, +// PSST_LOOPSTARTADDR, DSL_LOOPENDADDR, CCCA_CURRADDR +// The somewhat non-obviously still meaningful ones are: +// CPF_STOP, CPF_FRACADDRESS, CCR_READADDRESS (!), +// CCCA_INTERPROM, CCCA_8BITSELECT (!) +// (The envelope engine is ignored here, as stereo matters only for verbatim playback.) + #define CPF 0x00 /* Current pitch and fraction register */ SUB_REG(CPF, CURRENTPITCH, 0xffff0000) /* Current pitch (linear, 0x4000 == unity pitch shift) */ #define CPF_STEREO_MASK 0x00008000 /* 1 = Even channel interleave, odd channel locked */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 7b0ab4e02cfd..4ade0ef2cd1b 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -301,12 +301,12 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, start_addr += ccis; end_addr += ccis + emu->delay_pcm_irq; } - if (stereo && !extra) { - snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); - snd_emu10k1_ptr_write(emu, CPF, (voice + 1), CPF_STEREO_MASK); - } else { - snd_emu10k1_ptr_write(emu, CPF, voice, 0); - } + } + if (stereo && !extra) { + // Not really necessary for the slave, but it doesn't hurt + snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); + } else { + snd_emu10k1_ptr_write(emu, CPF, voice, 0); } /* setup routing */ @@ -325,6 +325,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_compose_send_routing(send_routing)); /* Assumption that PT is already 0 so no harm overwriting */ snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]); + // Stereo slaves don't need to have the addresses set, but it doesn't hurt snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24)); snd_emu10k1_ptr_write(emu, PSST, voice, (start_addr + (extra ? emu->delay_pcm_irq : 0)) | @@ -554,8 +555,6 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e struct snd_pcm_runtime *runtime; unsigned int voice, stereo, i, ccis, cra = 64, cs, sample; - if (evoice == NULL) - return; runtime = evoice->epcm->substream->runtime; voice = evoice->number; stereo = (!extra && runtime->channels == 2); @@ -575,6 +574,7 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra); if (stereo) { snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0); + // The engine goes haywire if this one is out of sync snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra); } /* fill cache */ @@ -584,37 +584,49 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int e } } -static void snd_emu10k1_playback_prepare_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, - int master, - struct snd_emu10k1_pcm_mixer *mix) +static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + unsigned int vattn) +{ + snd_emu10k1_ptr_write(emu, VTFT, evoice->number, vattn | VTFT_FILTERTARGET_MASK); + snd_emu10k1_ptr_write(emu, CVCF, evoice->number, vattn | CVCF_CURRENTFILTER_MASK); +} + +static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool master, + struct snd_emu10k1_pcm_mixer *mix) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; unsigned int vattn; - unsigned int voice, tmp; + unsigned int tmp; if (evoice == NULL) /* skip second voice for mono */ return; substream = evoice->epcm->substream; runtime = substream->runtime; - voice = evoice->number; tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0; - vattn = mix != NULL ? (mix->attn[tmp] << 16) : 0; - snd_emu10k1_ptr_write(emu, VTFT, voice, vattn | VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, voice, vattn | CVCF_CURRENTFILTER_MASK); + vattn = mix->attn[tmp] << 16; + snd_emu10k1_playback_commit_volume(emu, evoice, vattn); } +static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) +{ + if (evoice == NULL) + return; + snd_emu10k1_playback_commit_volume(emu, evoice, 0); +} + static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, - struct snd_emu10k1_voice *evoice, - int master) + struct snd_emu10k1_voice *evoice) { struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; unsigned int voice, pitch_target; - if (evoice == NULL) /* skip second voice for mono */ - return; substream = evoice->epcm->substream; runtime = substream->runtime; voice = evoice->number; @@ -624,8 +636,7 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, else pitch_target = emu10k1_calc_pitch_target(runtime->rate); snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); - if (master || evoice->epcm->type == PLAYBACK_EFX) - snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); + snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); } static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, @@ -633,13 +644,9 @@ static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, { unsigned int voice; - if (evoice == NULL) - return; voice = evoice->number; snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); - snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); } static void snd_emu10k1_playback_set_running(struct snd_emu10k1 *emu, @@ -698,21 +705,20 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime); mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 1, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[1], 0, mix); - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], true, mix); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], false, mix); snd_emu10k1_playback_set_running(emu, epcm); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0], 1); - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[1], 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_SUSPEND: snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]); - snd_emu10k1_playback_stop_voice(emu, epcm->voices[1]); snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); + snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]); + snd_emu10k1_playback_mute_voice(emu, epcm->voices[1]); break; default: result = -EINVAL; @@ -844,16 +850,14 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - snd_emu10k1_playback_prepare_voice(emu, epcm->extra, 1, NULL); - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[0], 0, - &emu->efx_pcm_mixer[0]); - for (i = 1; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_prepare_voice(emu, epcm->voices[i], 0, - &emu->efx_pcm_mixer[i]); + for (i = 0; i < NUM_EFX_PLAYBACK; i++) + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, + &emu->efx_pcm_mixer[i]); + snd_emu10k1_playback_set_running(emu, epcm); for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i], 0); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra, 1); + snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i]); + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: @@ -863,6 +867,9 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, } snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); + + for (i = 0; i < NUM_EFX_PLAYBACK; i++) + snd_emu10k1_playback_mute_voice(emu, epcm->voices[i]); break; default: result = -EINVAL; -- cgit v1.2.3 From 51d652f4587f22b619028f4113dd262b80a82489 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:12 +0200 Subject: ALSA: emu10k1: factor out snd_emu10k1_compose_audigy_sendamounts() Saves a bit of code duplication. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536451-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 +++ sound/pci/emu10k1/emumixer.c | 7 ++----- sound/pci/emu10k1/emupcm.c | 5 +---- 3 files changed, 6 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 5c1e5b123362..456af84735a8 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1501,6 +1501,9 @@ struct snd_emu10k1_pcm_mixer { #define snd_emu10k1_compose_audigy_fxrt2(route) \ ((unsigned int)route[4] | ((unsigned int)route[5] << 8) | ((unsigned int)route[6] << 16) | ((unsigned int)route[7] << 24)) +#define snd_emu10k1_compose_audigy_sendamounts(vol) \ +(((unsigned int)vol[4] << 24) | ((unsigned int)vol[5] << 16) | ((unsigned int)vol[6] << 8) | (unsigned int)vol[7]) + struct snd_emu10k1_memblk { struct snd_util_memblk mem; /* private part */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index e067a4066cda..1ebf161d410e 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1196,11 +1196,8 @@ static void update_emu10k1_send_volume(struct snd_emu10k1 *emu, int voice, unsig snd_emu10k1_ptr_write(emu, PSST_FXSENDAMOUNT_C, voice, volume[2]); snd_emu10k1_ptr_write(emu, DSL_FXSENDAMOUNT_D, voice, volume[3]); if (emu->audigy) { - unsigned int val = ((unsigned int)volume[4] << 24) | - ((unsigned int)volume[5] << 16) | - ((unsigned int)volume[6] << 8) | - (unsigned int)volume[7]; - snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, val); + snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, + snd_emu10k1_compose_audigy_sendamounts(volume)); } } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 4ade0ef2cd1b..d669f93d8930 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -316,10 +316,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_ptr_write(emu, A_FXRT2, voice, snd_emu10k1_compose_audigy_fxrt2(send_routing)); snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, - ((unsigned int)send_amount[4] << 24) | - ((unsigned int)send_amount[5] << 16) | - ((unsigned int)send_amount[6] << 8) | - (unsigned int)send_amount[7]); + snd_emu10k1_compose_audigy_sendamounts(send_amount)); } else snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(send_routing)); -- cgit v1.2.3 From 9b00a1e9b1aedd70fd397335f5e41609b6e6109b Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:03 +0200 Subject: ALSA: emu10k1: make some initializer arrays less wasteful - Use bit fields in struct snd_emu_chip_details - Use shorts in the E-MU routing register arrays Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 36 ++++++++++++++++++------------------ sound/pci/emu10k1/emumixer.c | 10 +++++----- 2 files changed, 23 insertions(+), 23 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 456af84735a8..03850fa186fc 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1612,24 +1612,24 @@ struct snd_emu_chip_details { u32 device; u32 subsystem; unsigned char revision; - unsigned char emu10k1_chip; /* Original SB Live. Not SB Live 24bit. */ - /* Redundant with emu10k2_chip being unset. */ - unsigned char emu10k2_chip; /* Audigy 1 or Audigy 2. */ - unsigned char ca0102_chip; /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */ - /* Redundant with ca0108_chip being unset. */ - unsigned char ca0108_chip; /* Audigy 2 Value */ - unsigned char ca_cardbus_chip; /* Audigy 2 ZS Notebook */ - unsigned char ca0151_chip; /* P16V */ - unsigned char spk71; /* Has 7.1 speakers */ - unsigned char sblive51; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ - unsigned char spdif_bug; /* Has Spdif phasing bug */ - unsigned char ac97_chip; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ - unsigned char ecard; /* APS EEPROM */ - unsigned char emu_model; /* EMU model type */ - unsigned char spi_dac; /* SPI interface for DAC; requires ca0108_chip */ - unsigned char i2c_adc; /* I2C interface for ADC; requires ca0108_chip */ - unsigned char adc_1361t; /* Use Philips 1361T ADC */ - unsigned char invert_shared_spdif; /* analog/digital switch inverted */ + unsigned char emu_model; /* EMU model type */ + unsigned int emu10k1_chip:1; /* Original SB Live. Not SB Live 24bit. */ + /* Redundant with emu10k2_chip being unset. */ + unsigned int emu10k2_chip:1; /* Audigy 1 or Audigy 2. */ + unsigned int ca0102_chip:1; /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */ + /* Redundant with ca0108_chip being unset. */ + 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 spk71:1; /* Has 7.1 speakers */ + unsigned int sblive51:1; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ + unsigned int spdif_bug:1; /* Has Spdif phasing bug */ + unsigned int ac97_chip:2; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ + unsigned int ecard:1; /* APS EEPROM */ + unsigned int spi_dac:1; /* SPI interface for DAC; requires ca0108_chip */ + unsigned int i2c_adc:1; /* I2C interface for ADC; requires ca0108_chip */ + unsigned int adc_1361t:1; /* Use Philips 1361T ADC */ + unsigned int invert_shared_spdif:1; /* analog/digital switch inverted */ const char *driver; const char *name; const char *id; /* for backward compatibility - can be NULL if not needed */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 1ebf161d410e..4d28a917aa16 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -180,7 +180,7 @@ static const char * const emu1616_src_texts[] = { /* * List of data sources available for each destination */ -static const unsigned int emu1010_src_regs[] = { +static const unsigned short emu1010_src_regs[] = { EMU_SRC_SILENCE,/* 0 */ EMU_SRC_DOCK_MIC_A1, /* 1 */ EMU_SRC_DOCK_MIC_B1, /* 2 */ @@ -237,7 +237,7 @@ static const unsigned int emu1010_src_regs[] = { }; /* 1616(m) cardbus */ -static const unsigned int emu1616_src_regs[] = { +static const unsigned short emu1616_src_regs[] = { EMU_SRC_SILENCE, EMU_SRC_DOCK_MIC_A1, EMU_SRC_DOCK_MIC_B1, @@ -293,7 +293,7 @@ static const unsigned int emu1616_src_regs[] = { * Data destinations - physical EMU outputs. * Each destination has an enum mixer control to choose a data source */ -static const unsigned int emu1010_output_dst[] = { +static const unsigned short emu1010_output_dst[] = { EMU_DST_DOCK_DAC1_LEFT1, /* 0 */ EMU_DST_DOCK_DAC1_RIGHT1, /* 1 */ EMU_DST_DOCK_DAC2_LEFT1, /* 2 */ @@ -321,7 +321,7 @@ static const unsigned int emu1010_output_dst[] = { }; /* 1616(m) cardbus */ -static const unsigned int emu1616_output_dst[] = { +static const unsigned short emu1616_output_dst[] = { EMU_DST_DOCK_DAC1_LEFT1, EMU_DST_DOCK_DAC1_RIGHT1, EMU_DST_DOCK_DAC2_LEFT1, @@ -347,7 +347,7 @@ static const unsigned int emu1616_output_dst[] = { * capture (EMU32 + I2S links) * Each destination has an enum mixer control to choose a data source */ -static const unsigned int emu1010_input_dst[] = { +static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_1, EMU_DST_ALICE2_EMU32_2, -- cgit v1.2.3 From dc39bb3e4c25b784899cce572e539055898b2c73 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:04 +0200 Subject: ALSA: emu10k1: compactize E-MU routing source arrays Use macros to avoid duplication. Arguably, this is somewhat less legible, but future additions would grow this part of the file to completely unmanageable dimensions. The EMU*_COMMON_TEXTS macros will save duplication in a future commit; I pulled them ahead to reduce churn. While rewriting the tables anyway, rearrange them such that each card's strings and registers are adjacent. Also, add some static asserts to verify that the array sizes match. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 315 ++++++++++++++----------------------------- 1 file changed, 103 insertions(+), 212 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 4d28a917aa16..fd5fcacfe0d5 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -62,232 +62,123 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol, return 0; } -/* - * Items labels in enum mixer controls assigning source data to - * each destination - */ -static const char * const emu1010_src_texts[] = { - "Silence", - "Dock Mic A", - "Dock Mic B", - "Dock ADC1 Left", - "Dock ADC1 Right", - "Dock ADC2 Left", - "Dock ADC2 Right", - "Dock ADC3 Left", - "Dock ADC3 Right", - "0202 ADC Left", - "0202 ADC Right", - "1010 SPDIF Left", - "1010 SPDIF Right", - "1010 ADAT 0", - "1010 ADAT 1", - "1010 ADAT 2", - "1010 ADAT 3", - "1010 ADAT 4", - "1010 ADAT 5", - "1010 ADAT 6", - "1010 ADAT 7", - "DSP 0", - "DSP 1", - "DSP 2", - "DSP 3", - "DSP 4", - "DSP 5", - "DSP 6", - "DSP 7", - "DSP 8", - "DSP 9", - "DSP 10", - "DSP 11", - "DSP 12", - "DSP 13", - "DSP 14", - "DSP 15", - "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 PAIR_PS(base, one, two, sfx) base " " one sfx, base " " two sfx +#define LR_PS(base, sfx) PAIR_PS(base, "Left", "Right", sfx) -/* 1616(m) cardbus */ +#define ADAT_PS(pfx, sfx) \ + 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 -static const char * const emu1616_src_texts[] = { - "Silence", - "Mic A", - "Mic B", - "ADC1 Left", - "ADC1 Right", - "ADC2 Left", - "ADC2 Right", - "SPDIF Left", - "SPDIF Right", - "ADAT 0", - "ADAT 1", - "ADAT 2", - "ADAT 3", - "ADAT 4", - "ADAT 5", - "ADAT 6", - "ADAT 7", - "DSP 0", - "DSP 1", - "DSP 2", - "DSP 3", - "DSP 4", - "DSP 5", - "DSP 6", - "DSP 7", - "DSP 8", - "DSP 9", - "DSP 10", - "DSP 11", - "DSP 12", - "DSP 13", - "DSP 14", - "DSP 15", - "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 PAIR_REGS(base, one, two) \ + base ## one ## 1, \ + base ## two ## 1 + +#define LR_REGS(base) PAIR_REGS(base, _LEFT, _RIGHT) +#define ADAT_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 */ + +#define DSP_TEXTS \ + "DSP 0", "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", \ + "DSP 8", "DSP 9", "DSP 10", "DSP 11", "DSP 12", "DSP 13", "DSP 14", "DSP 15", \ + "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 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 EMU32_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_EMU32B, \ + EMU_SRC_ALICE_EMU32B+1, \ + EMU_SRC_ALICE_EMU32B+2, \ + EMU_SRC_ALICE_EMU32B+3, \ + EMU_SRC_ALICE_EMU32B+4, \ + EMU_SRC_ALICE_EMU32B+5, \ + EMU_SRC_ALICE_EMU32B+6, \ + EMU_SRC_ALICE_EMU32B+7, \ + EMU_SRC_ALICE_EMU32B+8, \ + EMU_SRC_ALICE_EMU32B+9, \ + 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 EMU1010_COMMON_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_TEXTS("1010 ") + +static const char * const emu1010_src_texts[] = { + EMU1010_COMMON_TEXTS, + DSP_TEXTS, +}; + static const unsigned short emu1010_src_regs[] = { - EMU_SRC_SILENCE,/* 0 */ - EMU_SRC_DOCK_MIC_A1, /* 1 */ - EMU_SRC_DOCK_MIC_B1, /* 2 */ - EMU_SRC_DOCK_ADC1_LEFT1, /* 3 */ - EMU_SRC_DOCK_ADC1_RIGHT1, /* 4 */ - EMU_SRC_DOCK_ADC2_LEFT1, /* 5 */ - EMU_SRC_DOCK_ADC2_RIGHT1, /* 6 */ - EMU_SRC_DOCK_ADC3_LEFT1, /* 7 */ - EMU_SRC_DOCK_ADC3_RIGHT1, /* 8 */ - EMU_SRC_HAMOA_ADC_LEFT1, /* 9 */ - EMU_SRC_HAMOA_ADC_RIGHT1, /* 10 */ - EMU_SRC_HANA_SPDIF_LEFT1, /* 11 */ - EMU_SRC_HANA_SPDIF_RIGHT1, /* 12 */ - EMU_SRC_HANA_ADAT, /* 13 */ - EMU_SRC_HANA_ADAT+1, /* 14 */ - EMU_SRC_HANA_ADAT+2, /* 15 */ - EMU_SRC_HANA_ADAT+3, /* 16 */ - EMU_SRC_HANA_ADAT+4, /* 17 */ - EMU_SRC_HANA_ADAT+5, /* 18 */ - EMU_SRC_HANA_ADAT+6, /* 19 */ - EMU_SRC_HANA_ADAT+7, /* 20 */ - EMU_SRC_ALICE_EMU32A, /* 21 */ - EMU_SRC_ALICE_EMU32A+1, /* 22 */ - EMU_SRC_ALICE_EMU32A+2, /* 23 */ - EMU_SRC_ALICE_EMU32A+3, /* 24 */ - EMU_SRC_ALICE_EMU32A+4, /* 25 */ - EMU_SRC_ALICE_EMU32A+5, /* 26 */ - EMU_SRC_ALICE_EMU32A+6, /* 27 */ - EMU_SRC_ALICE_EMU32A+7, /* 28 */ - EMU_SRC_ALICE_EMU32A+8, /* 29 */ - EMU_SRC_ALICE_EMU32A+9, /* 30 */ - EMU_SRC_ALICE_EMU32A+0xa, /* 31 */ - EMU_SRC_ALICE_EMU32A+0xb, /* 32 */ - EMU_SRC_ALICE_EMU32A+0xc, /* 33 */ - EMU_SRC_ALICE_EMU32A+0xd, /* 34 */ - EMU_SRC_ALICE_EMU32A+0xe, /* 35 */ - EMU_SRC_ALICE_EMU32A+0xf, /* 36 */ - EMU_SRC_ALICE_EMU32B, /* 37 */ - EMU_SRC_ALICE_EMU32B+1, /* 38 */ - EMU_SRC_ALICE_EMU32B+2, /* 39 */ - EMU_SRC_ALICE_EMU32B+3, /* 40 */ - EMU_SRC_ALICE_EMU32B+4, /* 41 */ - EMU_SRC_ALICE_EMU32B+5, /* 42 */ - EMU_SRC_ALICE_EMU32B+6, /* 43 */ - EMU_SRC_ALICE_EMU32B+7, /* 44 */ - EMU_SRC_ALICE_EMU32B+8, /* 45 */ - EMU_SRC_ALICE_EMU32B+9, /* 46 */ - EMU_SRC_ALICE_EMU32B+0xa, /* 47 */ - EMU_SRC_ALICE_EMU32B+0xb, /* 48 */ - EMU_SRC_ALICE_EMU32B+0xc, /* 49 */ - EMU_SRC_ALICE_EMU32B+0xd, /* 50 */ - EMU_SRC_ALICE_EMU32B+0xe, /* 51 */ - EMU_SRC_ALICE_EMU32B+0xf, /* 52 */ + EMU_SRC_SILENCE, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_DOCK_ADC3), + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_HANA_SPDIF), + ADAT_REGS(EMU_SRC_HANA_ADAT), + EMU32_SRC_REGS, }; +static_assert(ARRAY_SIZE(emu1010_src_regs) == ARRAY_SIZE(emu1010_src_texts)); /* 1616(m) cardbus */ + +#define EMU1616_COMMON_TEXTS \ + "Silence", \ + PAIR_TEXTS("Mic", "A", "B"), \ + LR_TEXTS("ADC1"), \ + LR_TEXTS("ADC2"), \ + LR_TEXTS("SPDIF"), \ + ADAT_TEXTS("") + +static const char * const emu1616_src_texts[] = { + EMU1616_COMMON_TEXTS, + DSP_TEXTS, +}; + static const unsigned short emu1616_src_regs[] = { EMU_SRC_SILENCE, - EMU_SRC_DOCK_MIC_A1, - EMU_SRC_DOCK_MIC_B1, - EMU_SRC_DOCK_ADC1_LEFT1, - EMU_SRC_DOCK_ADC1_RIGHT1, - EMU_SRC_DOCK_ADC2_LEFT1, - EMU_SRC_DOCK_ADC2_RIGHT1, - EMU_SRC_MDOCK_SPDIF_LEFT1, - EMU_SRC_MDOCK_SPDIF_RIGHT1, - EMU_SRC_MDOCK_ADAT, - EMU_SRC_MDOCK_ADAT+1, - EMU_SRC_MDOCK_ADAT+2, - EMU_SRC_MDOCK_ADAT+3, - EMU_SRC_MDOCK_ADAT+4, - EMU_SRC_MDOCK_ADAT+5, - EMU_SRC_MDOCK_ADAT+6, - EMU_SRC_MDOCK_ADAT+7, - 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_EMU32B, - EMU_SRC_ALICE_EMU32B+1, - EMU_SRC_ALICE_EMU32B+2, - EMU_SRC_ALICE_EMU32B+3, - EMU_SRC_ALICE_EMU32B+4, - EMU_SRC_ALICE_EMU32B+5, - EMU_SRC_ALICE_EMU32B+6, - EMU_SRC_ALICE_EMU32B+7, - EMU_SRC_ALICE_EMU32B+8, - EMU_SRC_ALICE_EMU32B+9, - 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, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_MDOCK_SPDIF), + ADAT_REGS(EMU_SRC_MDOCK_ADAT), + EMU32_SRC_REGS, }; +static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); /* * Data destinations - physical EMU outputs. -- cgit v1.2.3 From 536438f1def68eb56fe611c07d2a6ec73ab4a5b1 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:05 +0200 Subject: ALSA: emu10k1: make mixer control mass creation less wasteful Define arrays of strings instead of snd_kcontrol_new. While at it, move the E-MU source & destination enum defs next to their hardware defs, which is a lot more logical and will come in handy in a followup commit. And add some static asserts to verify that the array sizes match. This also applies the compactization from the previous commit to the destination registers. While reshuffling the arrays anyway, switch the order of the HAMOA_DAC & HANA_SPDIF output destinations for the 1010 card, so they follow a more regular pattern. This should have no functional impact. The code is somewhat de-duplicated by the extraction of add_ctls(). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 +- sound/pci/emu10k1/emumixer.c | 449 +++++++++++++++++++------------------------ 2 files changed, 203 insertions(+), 248 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 03850fa186fc..b263c762c01a 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1195,7 +1195,7 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM * physical outputs of Hana, or outputs going to Alice2/Tina for capture - * 16 x EMU_DST_ALICE2_EMU32_X (2x on rev2 boards). Which data is fed into * a channel depends on the mixer control setting for each destination - see - * emumixer.c - snd_emu1010_output_enum_ctls[], snd_emu1010_input_enum_ctls[] + * the register arrays in emumixer.c. */ #define EMU_DST_ALICE2_EMU32_0 0x000f /* 16 EMU32 channels to Alice2 +0 to +0xf */ /* This channel is delayed by one sample. */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index fd5fcacfe0d5..92545559a36c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -29,6 +29,24 @@ static const DECLARE_TLV_DB_SCALE(snd_audigy_db_scale2, -10350, 50, 1); /* WM8775 gain scale */ + +static int add_ctls(struct snd_emu10k1 *emu, const struct snd_kcontrol_new *tpl, + const char * const *ctls, unsigned nctls) +{ + struct snd_kcontrol_new kctl = *tpl; + int err; + + 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)); + if (err < 0) + return err; + } + return 0; +} + + static int snd_emu10k1_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; @@ -184,60 +202,88 @@ static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_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") + +static const char * const emu1010_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_CTLS("1010 "), +}; + static const unsigned short emu1010_output_dst[] = { - EMU_DST_DOCK_DAC1_LEFT1, /* 0 */ - EMU_DST_DOCK_DAC1_RIGHT1, /* 1 */ - EMU_DST_DOCK_DAC2_LEFT1, /* 2 */ - EMU_DST_DOCK_DAC2_RIGHT1, /* 3 */ - EMU_DST_DOCK_DAC3_LEFT1, /* 4 */ - EMU_DST_DOCK_DAC3_RIGHT1, /* 5 */ - EMU_DST_DOCK_DAC4_LEFT1, /* 6 */ - EMU_DST_DOCK_DAC4_RIGHT1, /* 7 */ - EMU_DST_DOCK_PHONES_LEFT1, /* 8 */ - EMU_DST_DOCK_PHONES_RIGHT1, /* 9 */ - EMU_DST_DOCK_SPDIF_LEFT1, /* 10 */ - EMU_DST_DOCK_SPDIF_RIGHT1, /* 11 */ - EMU_DST_HANA_SPDIF_LEFT1, /* 12 */ - EMU_DST_HANA_SPDIF_RIGHT1, /* 13 */ - EMU_DST_HAMOA_DAC_LEFT1, /* 14 */ - EMU_DST_HAMOA_DAC_RIGHT1, /* 15 */ - EMU_DST_HANA_ADAT, /* 16 */ - EMU_DST_HANA_ADAT+1, /* 17 */ - EMU_DST_HANA_ADAT+2, /* 18 */ - EMU_DST_HANA_ADAT+3, /* 19 */ - EMU_DST_HANA_ADAT+4, /* 20 */ - EMU_DST_HANA_ADAT+5, /* 21 */ - EMU_DST_HANA_ADAT+6, /* 22 */ - EMU_DST_HANA_ADAT+7, /* 23 */ + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_DOCK_DAC4), + LR_REGS(EMU_DST_DOCK_PHONES), + LR_REGS(EMU_DST_DOCK_SPDIF), + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), + ADAT_REGS(EMU_DST_HANA_ADAT), }; +static_assert(ARRAY_SIZE(emu1010_output_dst) == ARRAY_SIZE(emu1010_output_texts)); /* 1616(m) cardbus */ + +static const char * const snd_emu1616_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock SPDIF"), + ADAT_CTLS("Dock "), + LR_CTLS("Mana DAC"), +}; + static const unsigned short emu1616_output_dst[] = { - EMU_DST_DOCK_DAC1_LEFT1, - EMU_DST_DOCK_DAC1_RIGHT1, - EMU_DST_DOCK_DAC2_LEFT1, - EMU_DST_DOCK_DAC2_RIGHT1, - EMU_DST_DOCK_DAC3_LEFT1, - EMU_DST_DOCK_DAC3_RIGHT1, - EMU_DST_MDOCK_SPDIF_LEFT1, - EMU_DST_MDOCK_SPDIF_RIGHT1, - EMU_DST_MDOCK_ADAT, - EMU_DST_MDOCK_ADAT+1, - EMU_DST_MDOCK_ADAT+2, - EMU_DST_MDOCK_ADAT+3, - EMU_DST_MDOCK_ADAT+4, - EMU_DST_MDOCK_ADAT+5, - EMU_DST_MDOCK_ADAT+6, - EMU_DST_MDOCK_ADAT+7, - EMU_DST_MANA_DAC_LEFT, - EMU_DST_MANA_DAC_RIGHT, + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_MDOCK_SPDIF), + ADAT_REGS(EMU_DST_MDOCK_ADAT), + EMU_DST_MANA_DAC_LEFT, EMU_DST_MANA_DAC_RIGHT, }; +static_assert(ARRAY_SIZE(emu1616_output_dst) == ARRAY_SIZE(snd_emu1616_output_texts)); /* * Data destinations - FPGA outputs going to Alice2 (Audigy) for * capture (EMU32 + I2S links) * Each destination has an enum mixer control to choose a data source */ + +static const char * const emu1010_input_texts[] = { + "DSP 0 Capture Enum", + "DSP 1 Capture Enum", + "DSP 2 Capture Enum", + "DSP 3 Capture Enum", + "DSP 4 Capture Enum", + "DSP 5 Capture Enum", + "DSP 6 Capture Enum", + "DSP 7 Capture Enum", + "DSP 8 Capture Enum", + "DSP 9 Capture Enum", + "DSP A Capture Enum", + "DSP B Capture Enum", + "DSP C Capture Enum", + "DSP D Capture Enum", + "DSP E Capture Enum", + "DSP F Capture Enum", + /* These exist only on rev1 EMU1010 cards. */ + "DSP 10 Capture Enum", + "DSP 11 Capture Enum", + "DSP 12 Capture Enum", + "DSP 13 Capture Enum", + "DSP 14 Capture Enum", + "DSP 15 Capture Enum", +}; + static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE2_EMU32_0, EMU_DST_ALICE2_EMU32_1, @@ -263,6 +309,7 @@ static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE_I2S2_LEFT, EMU_DST_ALICE_I2S2_RIGHT, }; +static_assert(ARRAY_SIZE(emu1010_input_dst) == ARRAY_SIZE(emu1010_input_texts)); static int snd_emu1010_input_output_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) @@ -321,6 +368,14 @@ static int snd_emu1010_output_source_put(struct snd_kcontrol *kcontrol, return 1; } +static const struct snd_kcontrol_new emu1010_output_source_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_input_output_source_info, + .get = snd_emu1010_output_source_get, + .put = snd_emu1010_output_source_put +}; + static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -363,110 +418,36 @@ static int snd_emu1010_input_source_put(struct snd_kcontrol *kcontrol, return 1; } -#define EMU1010_SOURCE_OUTPUT(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_input_output_source_info, \ - .get = snd_emu1010_output_source_get, \ - .put = snd_emu1010_output_source_put, \ - .private_value = chid \ -} - -static const struct snd_kcontrol_new snd_emu1010_output_enum_ctls[] = { - EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0), - EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5), - EMU1010_SOURCE_OUTPUT("Dock DAC4 Left Playback Enum", 6), - EMU1010_SOURCE_OUTPUT("Dock DAC4 Right Playback Enum", 7), - EMU1010_SOURCE_OUTPUT("Dock Phones Left Playback Enum", 8), - EMU1010_SOURCE_OUTPUT("Dock Phones Right Playback Enum", 9), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 0xa), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 0xb), - EMU1010_SOURCE_OUTPUT("1010 SPDIF Left Playback Enum", 0xc), - EMU1010_SOURCE_OUTPUT("1010 SPDIF Right Playback Enum", 0xd), - EMU1010_SOURCE_OUTPUT("0202 DAC Left Playback Enum", 0xe), - EMU1010_SOURCE_OUTPUT("0202 DAC Right Playback Enum", 0xf), - EMU1010_SOURCE_OUTPUT("1010 ADAT 0 Playback Enum", 0x10), - EMU1010_SOURCE_OUTPUT("1010 ADAT 1 Playback Enum", 0x11), - EMU1010_SOURCE_OUTPUT("1010 ADAT 2 Playback Enum", 0x12), - EMU1010_SOURCE_OUTPUT("1010 ADAT 3 Playback Enum", 0x13), - EMU1010_SOURCE_OUTPUT("1010 ADAT 4 Playback Enum", 0x14), - EMU1010_SOURCE_OUTPUT("1010 ADAT 5 Playback Enum", 0x15), - EMU1010_SOURCE_OUTPUT("1010 ADAT 6 Playback Enum", 0x16), - EMU1010_SOURCE_OUTPUT("1010 ADAT 7 Playback Enum", 0x17), +static const struct snd_kcontrol_new emu1010_input_source_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_input_output_source_info, + .get = snd_emu1010_input_source_get, + .put = snd_emu1010_input_source_put }; -/* 1616(m) cardbus */ -static const struct snd_kcontrol_new snd_emu1616_output_enum_ctls[] = { - EMU1010_SOURCE_OUTPUT("Dock DAC1 Left Playback Enum", 0), - EMU1010_SOURCE_OUTPUT("Dock DAC1 Right Playback Enum", 1), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Left Playback Enum", 2), - EMU1010_SOURCE_OUTPUT("Dock DAC2 Right Playback Enum", 3), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Left Playback Enum", 4), - EMU1010_SOURCE_OUTPUT("Dock DAC3 Right Playback Enum", 5), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Left Playback Enum", 6), - EMU1010_SOURCE_OUTPUT("Dock SPDIF Right Playback Enum", 7), - EMU1010_SOURCE_OUTPUT("Dock ADAT 0 Playback Enum", 8), - EMU1010_SOURCE_OUTPUT("Dock ADAT 1 Playback Enum", 9), - EMU1010_SOURCE_OUTPUT("Dock ADAT 2 Playback Enum", 0xa), - EMU1010_SOURCE_OUTPUT("Dock ADAT 3 Playback Enum", 0xb), - EMU1010_SOURCE_OUTPUT("Dock ADAT 4 Playback Enum", 0xc), - EMU1010_SOURCE_OUTPUT("Dock ADAT 5 Playback Enum", 0xd), - EMU1010_SOURCE_OUTPUT("Dock ADAT 6 Playback Enum", 0xe), - EMU1010_SOURCE_OUTPUT("Dock ADAT 7 Playback Enum", 0xf), - EMU1010_SOURCE_OUTPUT("Mana DAC Left Playback Enum", 0x10), - EMU1010_SOURCE_OUTPUT("Mana DAC Right Playback Enum", 0x11), +static const char * const snd_emu1010_adc_pads[] = { + "ADC1 14dB PAD Audio Dock Capture Switch", + "ADC2 14dB PAD Audio Dock Capture Switch", + "ADC3 14dB PAD Audio Dock Capture Switch", + "ADC1 14dB PAD 0202 Capture Switch", }; - -#define EMU1010_SOURCE_INPUT(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_input_output_source_info, \ - .get = snd_emu1010_input_source_get, \ - .put = snd_emu1010_input_source_put, \ - .private_value = chid \ -} - -static const struct snd_kcontrol_new snd_emu1010_input_enum_ctls[] = { - EMU1010_SOURCE_INPUT("DSP 0 Capture Enum", 0), - EMU1010_SOURCE_INPUT("DSP 1 Capture Enum", 1), - EMU1010_SOURCE_INPUT("DSP 2 Capture Enum", 2), - EMU1010_SOURCE_INPUT("DSP 3 Capture Enum", 3), - EMU1010_SOURCE_INPUT("DSP 4 Capture Enum", 4), - EMU1010_SOURCE_INPUT("DSP 5 Capture Enum", 5), - EMU1010_SOURCE_INPUT("DSP 6 Capture Enum", 6), - EMU1010_SOURCE_INPUT("DSP 7 Capture Enum", 7), - EMU1010_SOURCE_INPUT("DSP 8 Capture Enum", 8), - EMU1010_SOURCE_INPUT("DSP 9 Capture Enum", 9), - EMU1010_SOURCE_INPUT("DSP A Capture Enum", 0xa), - EMU1010_SOURCE_INPUT("DSP B Capture Enum", 0xb), - EMU1010_SOURCE_INPUT("DSP C Capture Enum", 0xc), - EMU1010_SOURCE_INPUT("DSP D Capture Enum", 0xd), - EMU1010_SOURCE_INPUT("DSP E Capture Enum", 0xe), - EMU1010_SOURCE_INPUT("DSP F Capture Enum", 0xf), - EMU1010_SOURCE_INPUT("DSP 10 Capture Enum", 0x10), - EMU1010_SOURCE_INPUT("DSP 11 Capture Enum", 0x11), - EMU1010_SOURCE_INPUT("DSP 12 Capture Enum", 0x12), - EMU1010_SOURCE_INPUT("DSP 13 Capture Enum", 0x13), - EMU1010_SOURCE_INPUT("DSP 14 Capture Enum", 0x14), - EMU1010_SOURCE_INPUT("DSP 15 Capture Enum", 0x15), +static const unsigned short snd_emu1010_adc_pad_regs[] = { + EMU_HANA_DOCK_ADC_PAD1, + EMU_HANA_DOCK_ADC_PAD2, + EMU_HANA_DOCK_ADC_PAD3, + EMU_HANA_0202_ADC_PAD1, }; - - #define snd_emu1010_adc_pads_info snd_ctl_boolean_mono_info static int snd_emu1010_adc_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_adc_pad_regs[kcontrol->private_value]; + ucontrol->value.integer.value[0] = (emu->emu1010.adc_pads & mask) ? 1 : 0; return 0; } @@ -474,7 +455,7 @@ static int snd_emu1010_adc_pads_get(struct snd_kcontrol *kcontrol, struct snd_ct static int snd_emu1010_adc_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_adc_pad_regs[kcontrol->private_value]; unsigned int val, cache; val = ucontrol->value.integer.value[0]; cache = emu->emu1010.adc_pads; @@ -490,23 +471,29 @@ static int snd_emu1010_adc_pads_put(struct snd_kcontrol *kcontrol, struct snd_ct return 0; } +static const struct snd_kcontrol_new emu1010_adc_pads_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_adc_pads_info, + .get = snd_emu1010_adc_pads_get, + .put = snd_emu1010_adc_pads_put +}; -#define EMU1010_ADC_PADS(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_adc_pads_info, \ - .get = snd_emu1010_adc_pads_get, \ - .put = snd_emu1010_adc_pads_put, \ - .private_value = chid \ -} +static const char * const snd_emu1010_dac_pads[] = { + "DAC1 Audio Dock 14dB PAD Playback Switch", + "DAC2 Audio Dock 14dB PAD Playback Switch", + "DAC3 Audio Dock 14dB PAD Playback Switch", + "DAC4 Audio Dock 14dB PAD Playback Switch", + "DAC1 0202 14dB PAD Playback Switch", +}; -static const struct snd_kcontrol_new snd_emu1010_adc_pads[] = { - EMU1010_ADC_PADS("ADC1 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD1), - EMU1010_ADC_PADS("ADC2 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD2), - EMU1010_ADC_PADS("ADC3 14dB PAD Audio Dock Capture Switch", EMU_HANA_DOCK_ADC_PAD3), - EMU1010_ADC_PADS("ADC1 14dB PAD 0202 Capture Switch", EMU_HANA_0202_ADC_PAD1), +static const unsigned short snd_emu1010_dac_regs[] = { + EMU_HANA_DOCK_DAC_PAD1, + EMU_HANA_DOCK_DAC_PAD2, + EMU_HANA_DOCK_DAC_PAD3, + EMU_HANA_DOCK_DAC_PAD4, + EMU_HANA_0202_DAC_PAD1, }; #define snd_emu1010_dac_pads_info snd_ctl_boolean_mono_info @@ -514,7 +501,8 @@ static const struct snd_kcontrol_new snd_emu1010_adc_pads[] = { static int snd_emu1010_dac_pads_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; + ucontrol->value.integer.value[0] = (emu->emu1010.dac_pads & mask) ? 1 : 0; return 0; } @@ -522,7 +510,7 @@ static int snd_emu1010_dac_pads_get(struct snd_kcontrol *kcontrol, struct snd_ct static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int mask = kcontrol->private_value & 0xff; + unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; unsigned int val, cache; val = ucontrol->value.integer.value[0]; cache = emu->emu1010.dac_pads; @@ -538,24 +526,12 @@ static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ct return 0; } - - -#define EMU1010_DAC_PADS(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ - .info = snd_emu1010_dac_pads_info, \ - .get = snd_emu1010_dac_pads_get, \ - .put = snd_emu1010_dac_pads_put, \ - .private_value = chid \ -} - -static const struct snd_kcontrol_new snd_emu1010_dac_pads[] = { - EMU1010_DAC_PADS("DAC1 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD1), - EMU1010_DAC_PADS("DAC2 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD2), - EMU1010_DAC_PADS("DAC3 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD3), - EMU1010_DAC_PADS("DAC4 Audio Dock 14dB PAD Playback Switch", EMU_HANA_DOCK_DAC_PAD4), - EMU1010_DAC_PADS("DAC1 0202 14dB PAD Playback Switch", EMU_HANA_0202_DAC_PAD1), +static const struct snd_kcontrol_new emu1010_dac_pads_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_emu1010_dac_pads_info, + .get = snd_emu1010_dac_pads_get, + .put = snd_emu1010_dac_pads_put }; @@ -927,22 +903,19 @@ static int snd_audigy_i2c_volume_put(struct snd_kcontrol *kcontrol, return change; } -#define I2C_VOLUME(xname,chid) \ -{ \ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ - SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ - .info = snd_audigy_i2c_volume_info, \ - .get = snd_audigy_i2c_volume_get, \ - .put = snd_audigy_i2c_volume_put, \ - .tlv = { .p = snd_audigy_db_scale2 }, \ - .private_value = chid \ -} - +static const struct snd_kcontrol_new i2c_volume_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = snd_audigy_i2c_volume_info, + .get = snd_audigy_i2c_volume_get, + .put = snd_audigy_i2c_volume_put, + .tlv = { .p = snd_audigy_db_scale2 } +}; -static const struct snd_kcontrol_new snd_audigy_i2c_volume_ctls[] = { - I2C_VOLUME("Mic Capture Volume", 0), - I2C_VOLUME("Line Capture Volume", 0) +static const char * const snd_audigy_i2c_volume_ctls[] = { + "Mic Capture Volume", + "Line Capture Volume", }; #if 0 @@ -1958,34 +1931,26 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { /* 1616(m) cardbus */ - int i; - - for (i = 0; i < ARRAY_SIZE(snd_emu1616_output_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1616_output_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_input_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads) - 2; i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_adc_pads[i], emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads) - 2; i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_dac_pads[i], emu)); - if (err < 0) - return err; - } + err = add_ctls(emu, &emu1010_output_source_ctl, + snd_emu1616_output_texts, + ARRAY_SIZE(snd_emu1616_output_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_input_source_ctl, + emu1010_input_texts, + ARRAY_SIZE(emu1010_input_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_adc_pads_ctl, + snd_emu1010_adc_pads, + ARRAY_SIZE(snd_emu1010_adc_pads) - 2); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_dac_pads_ctl, + snd_emu1010_dac_pads, + ARRAY_SIZE(snd_emu1010_dac_pads) - 2); + if (err < 0) + return err; err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_internal_clock, emu)); if (err < 0) @@ -2001,34 +1966,26 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, } else if (emu->card_capabilities->emu_model) { /* all other e-mu cards for now */ - int i; - - for (i = 0; i < ARRAY_SIZE(snd_emu1010_output_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_output_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_input_enum_ctls); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_input_enum_ctls[i], - emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_adc_pads); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_adc_pads[i], emu)); - if (err < 0) - return err; - } - for (i = 0; i < ARRAY_SIZE(snd_emu1010_dac_pads); i++) { - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_dac_pads[i], emu)); - if (err < 0) - return err; - } + err = add_ctls(emu, &emu1010_output_source_ctl, + emu1010_output_texts, + ARRAY_SIZE(emu1010_output_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_input_source_ctl, + emu1010_input_texts, + ARRAY_SIZE(emu1010_input_texts)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_adc_pads_ctl, + snd_emu1010_adc_pads, + ARRAY_SIZE(snd_emu1010_adc_pads)); + if (err < 0) + return err; + err = add_ctls(emu, &emu1010_dac_pads_ctl, + snd_emu1010_dac_pads, + ARRAY_SIZE(snd_emu1010_dac_pads)); + if (err < 0) + return err; err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_internal_clock, emu)); if (err < 0) @@ -2044,17 +2001,15 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, } if ( emu->card_capabilities->i2c_adc) { - int i; - err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_capture_source, emu)); if (err < 0) return err; - for (i = 0; i < ARRAY_SIZE(snd_audigy_i2c_volume_ctls); i++) { - err = snd_ctl_add(card, snd_ctl_new1(&snd_audigy_i2c_volume_ctls[i], emu)); - if (err < 0) - return err; - } + err = add_ctls(emu, &i2c_volume_ctl, + snd_audigy_i2c_volume_ctls, + ARRAY_SIZE(snd_audigy_i2c_volume_ctls)); + if (err < 0) + return err; } if (emu->card_capabilities->ac97_chip && emu->audigy) { -- cgit v1.2.3 From 511cbe8f59e30cd09a04c1dbafe6337d9111e88a Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:06 +0200 Subject: ALSA: emu10k1: un-hardcode E-MU mixer control callbacks somewhat Instead of hard-coding the card-specific arrays and their sizes in each function, use a more data-driven approach. As a drive-by, also hide the unavailable I2S input destinations on the 1616 cardbus card. Also as a drive-by, use more assignments at variable declaration for brevity. This also removes the pointless masking of kctl.private_value. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 155 ++++++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 54 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 92545559a36c..c5ebec52c8cf 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -311,28 +311,89 @@ static const unsigned short emu1010_input_dst[] = { }; static_assert(ARRAY_SIZE(emu1010_input_dst) == ARRAY_SIZE(emu1010_input_texts)); +struct snd_emu1010_routing_info { + const char * const *src_texts; + const unsigned short *src_regs; + const unsigned short *out_regs; + const unsigned short *in_regs; + unsigned n_srcs; + unsigned n_outs; + unsigned n_ins; +}; + +const struct snd_emu1010_routing_info emu1010_routing_info[] = { + { + .src_regs = emu1010_src_regs, + .src_texts = emu1010_src_texts, + .n_srcs = ARRAY_SIZE(emu1010_src_texts), + + .out_regs = emu1010_output_dst, + .n_outs = ARRAY_SIZE(emu1010_output_dst), + + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst), + }, + { + /* 1616(m) cardbus */ + .src_regs = emu1616_src_regs, + .src_texts = emu1616_src_texts, + .n_srcs = ARRAY_SIZE(emu1616_src_texts), + + .out_regs = emu1616_output_dst, + .n_outs = ARRAY_SIZE(emu1616_output_dst), + + .in_regs = emu1010_input_dst, + .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, + }, +}; + +static unsigned emu1010_idx(struct snd_emu10k1 *emu) +{ + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) + return 1; + else + return 0; +} + +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)]; + + snd_emu1010_fpga_link_dst_src_write(emu, + emu_ri->out_regs[channel], emu_ri->src_regs[src]); +} + +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)]; + + snd_emu1010_fpga_link_dst_src_write(emu, + emu_ri->in_regs[channel], emu_ri->src_regs[src]); +} + 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)]; - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - return snd_ctl_enum_info(uinfo, 1, 49, emu1616_src_texts); - else - return snd_ctl_enum_info(uinfo, 1, 53, emu1010_src_texts); + return snd_ctl_enum_info(uinfo, 1, emu_ri->n_srcs, emu_ri->src_texts); } 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); - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned channel = kcontrol->private_value; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_output_dst, emu->emu1010.output_source */ - if (channel >= 24 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - channel >= 18)) + if (channel >= emu_ri->n_outs) return -EINVAL; ucontrol->value.enumerated.item[0] = emu->emu1010.output_source[channel]; return 0; @@ -342,30 +403,22 @@ 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); - unsigned int val; - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned val = ucontrol->value.enumerated.item[0]; + unsigned channel = kcontrol->private_value; + int change; - val = ucontrol->value.enumerated.item[0]; - if (val >= 53 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - val >= 49)) + if (val >= emu_ri->n_srcs) return -EINVAL; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_output_dst, emu->emu1010.output_source */ - if (channel >= 24 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - channel >= 18)) + if (channel >= emu_ri->n_outs) return -EINVAL; - if (emu->emu1010.output_source[channel] == val) - return 0; - emu->emu1010.output_source[channel] = val; - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - snd_emu1010_fpga_link_dst_src_write(emu, - emu1616_output_dst[channel], emu1616_src_regs[val]); - else - snd_emu1010_fpga_link_dst_src_write(emu, - emu1010_output_dst[channel], emu1010_src_regs[val]); - return 1; + change = (emu->emu1010.output_source[channel] != val); + if (change) { + emu->emu1010.output_source[channel] = val; + snd_emu1010_output_source_apply(emu, channel, val); + } + return change; } static const struct snd_kcontrol_new emu1010_output_source_ctl = { @@ -380,11 +433,11 @@ static int snd_emu1010_input_source_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned channel = kcontrol->private_value; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_input_dst, emu->emu1010.input_source */ - if (channel >= 22) + if (channel >= emu_ri->n_ins) return -EINVAL; ucontrol->value.enumerated.item[0] = emu->emu1010.input_source[channel]; return 0; @@ -394,28 +447,22 @@ 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); - unsigned int val; - unsigned int channel; + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + unsigned val = ucontrol->value.enumerated.item[0]; + unsigned channel = kcontrol->private_value; + int change; - val = ucontrol->value.enumerated.item[0]; - if (val >= 53 || - (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616 && - val >= 49)) + if (val >= emu_ri->n_srcs) return -EINVAL; - channel = (kcontrol->private_value) & 0xff; - /* Limit: emu1010_input_dst, emu->emu1010.input_source */ - if (channel >= 22) + if (channel >= emu_ri->n_ins) return -EINVAL; - if (emu->emu1010.input_source[channel] == val) - return 0; - emu->emu1010.input_source[channel] = val; - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - snd_emu1010_fpga_link_dst_src_write(emu, - emu1010_input_dst[channel], emu1616_src_regs[val]); - else - snd_emu1010_fpga_link_dst_src_write(emu, - emu1010_input_dst[channel], emu1010_src_regs[val]); - return 1; + change = (emu->emu1010.input_source[channel] != val); + if (change) { + emu->emu1010.input_source[channel] = val; + snd_emu1010_input_source_apply(emu, channel, val); + } + return change; } static const struct snd_kcontrol_new emu1010_input_source_ctl = { -- cgit v1.2.3 From cc766807a208bfa06c204be784ec099fb25c87a4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:07 +0200 Subject: ALSA: emu10k1: fix return value of snd_emu1010_dac_pads_put() It returned zero even if the value had changed. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index c5ebec52c8cf..ef05d4f3316c 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -559,18 +559,21 @@ static int snd_emu1010_dac_pads_put(struct snd_kcontrol *kcontrol, struct snd_ct struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int mask = snd_emu1010_dac_regs[kcontrol->private_value]; unsigned int val, cache; + int change; + val = ucontrol->value.integer.value[0]; cache = emu->emu1010.dac_pads; if (val == 1) cache = cache | mask; else cache = cache & ~mask; - if (cache != emu->emu1010.dac_pads) { + change = (cache != emu->emu1010.dac_pads); + if (change) { snd_emu1010_fpga_write(emu, EMU_HANA_DAC_PADS, cache ); emu->emu1010.dac_pads = cache; } - return 0; + return change; } static const struct snd_kcontrol_new emu1010_dac_pads_ctl = { -- cgit v1.2.3 From 1fc710f06aa8f33abab4fdded9463eefdff7d390 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:08 +0200 Subject: ALSA: emu10k1: make E-MU I/O routing init data-driven ... and move it to the mixer init, as it's logically part of it. As a side effect, this fixes the initial values of the input destination mixer controls, which would have previously remained at "Silent" despite different defaults. This didn't really matter, though, as ALSA state restoration would hide that bug beyond first use. Note that this completely does away with clearing the output routing registers, as it was rather pointless - we just programmed the FPGA (resetting it first if necessary), so everything is zeroed anyway (that's documented by Xilinx, and as further evidence, some of the loops terminated too early, and we didn't bother clearing the high channels of the input routes at all, all with no observed adverse effects). As a drive-by, this also fixes some capture channel defaults - any EMU_SRC_*2 isn't a sensible value in 1x clock mode. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_main.c | 248 +-------------------------------------- sound/pci/emu10k1/emumixer.c | 97 +++++++++++++++ 2 files changed, 99 insertions(+), 246 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 793ae8797172..6a3476de74e6 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -796,7 +796,6 @@ static void emu1010_firmware_work(struct work_struct *work) */ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) { - unsigned int i; u32 tmp, tmp2, reg; int err; @@ -887,253 +886,10 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) /* Audio Dock LEDs. */ snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_LOCK | EMU_HANA_DOCK_LEDS_2_48K); -#if 0 - /* For 96kHz */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT2); -#endif -#if 0 - /* For 192kHz */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_0, EMU_SRC_HAMOA_ADC_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_1, EMU_SRC_HAMOA_ADC_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_RIGHT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_HAMOA_ADC_LEFT3); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_HAMOA_ADC_RIGHT3); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_6, EMU_SRC_HAMOA_ADC_LEFT4); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_7, EMU_SRC_HAMOA_ADC_RIGHT4); -#endif -#if 1 - /* For 48kHz */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_0, EMU_SRC_DOCK_MIC_A1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_1, EMU_SRC_DOCK_MIC_B1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_2, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_3, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_DOCK_ADC1_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_DOCK_ADC1_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_6, EMU_SRC_DOCK_ADC2_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_7, EMU_SRC_DOCK_ADC2_RIGHT1); - /* Pavel Hofman - setting defaults for 8 more capture channels - * Defaults only, users will set their own values anyways, let's - * just copy/paste. - */ - - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_8, EMU_SRC_DOCK_MIC_A1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_9, EMU_SRC_DOCK_MIC_B1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_A, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_B, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_ADC1_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_ADC1_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_E, EMU_SRC_DOCK_ADC2_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_F, EMU_SRC_DOCK_ADC2_RIGHT1); -#endif -#if 0 - /* Original */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_4, EMU_SRC_HANA_ADAT); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_5, EMU_SRC_HANA_ADAT + 1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_6, EMU_SRC_HANA_ADAT + 2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_7, EMU_SRC_HANA_ADAT + 3); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_8, EMU_SRC_HANA_ADAT + 4); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_9, EMU_SRC_HANA_ADAT + 5); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_A, EMU_SRC_HANA_ADAT + 6); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_B, EMU_SRC_HANA_ADAT + 7); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_C, EMU_SRC_DOCK_MIC_A1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_D, EMU_SRC_DOCK_MIC_B1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_E, EMU_SRC_HAMOA_ADC_LEFT2); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE2_EMU32_F, EMU_SRC_HAMOA_ADC_LEFT2); -#endif - for (i = 0; i < 0x20; i++) { - /* AudioDock Elink <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, 0x0100 + i, EMU_SRC_SILENCE); - } - for (i = 0; i < 4; i++) { - /* Hana SPDIF Out <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, 0x0200 + i, EMU_SRC_SILENCE); - } - for (i = 0; i < 7; i++) { - /* Hamoa DAC <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, 0x0300 + i, EMU_SRC_SILENCE); - } - for (i = 0; i < 7; i++) { - /* Hana ADAT Out <- Silence */ - snd_emu1010_fpga_link_dst_src_write(emu, EMU_DST_HANA_ADAT + i, EMU_SRC_SILENCE); - } - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S0_LEFT, EMU_SRC_DOCK_ADC1_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S0_RIGHT, EMU_SRC_DOCK_ADC1_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S1_LEFT, EMU_SRC_DOCK_ADC2_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S1_RIGHT, EMU_SRC_DOCK_ADC2_RIGHT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S2_LEFT, EMU_SRC_DOCK_ADC3_LEFT1); - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_ALICE_I2S2_RIGHT, EMU_SRC_DOCK_ADC3_RIGHT1); + // The routes are all set to EMU_SRC_SILENCE due to the reset, + // so it is safe to simply enable the outputs. snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); -#if 0 - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32B + 2); /* ALICE2 bus 0xa2 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32B + 3); /* ALICE2 bus 0xa3 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 2); /* ALICE2 bus 0xb2 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); /* ALICE2 bus 0xb3 */ -#endif - /* Default outputs */ - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { - /* 1616(M) cardbus default outputs */ - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[0] = 17; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[1] = 18; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2); - emu->emu1010.output_source[2] = 19; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); - emu->emu1010.output_source[3] = 20; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4); - emu->emu1010.output_source[4] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5); - emu->emu1010.output_source[5] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_MANA_DAC_LEFT, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[16] = 17; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_MANA_DAC_RIGHT, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[17] = 18; - } else { - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[0] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC1_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[1] = 22; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_LEFT1, EMU_SRC_ALICE_EMU32A + 2); - emu->emu1010.output_source[2] = 23; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC2_RIGHT1, EMU_SRC_ALICE_EMU32A + 3); - emu->emu1010.output_source[3] = 24; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_LEFT1, EMU_SRC_ALICE_EMU32A + 4); - emu->emu1010.output_source[4] = 25; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC3_RIGHT1, EMU_SRC_ALICE_EMU32A + 5); - emu->emu1010.output_source[5] = 26; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC4_LEFT1, EMU_SRC_ALICE_EMU32A + 6); - emu->emu1010.output_source[6] = 27; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_DAC4_RIGHT1, EMU_SRC_ALICE_EMU32A + 7); - emu->emu1010.output_source[7] = 28; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_PHONES_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[8] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_PHONES_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[9] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[10] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_DOCK_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[11] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[12] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_SPDIF_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[13] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_LEFT1, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[14] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HAMOA_DAC_RIGHT1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[15] = 22; - /* ALICE2 bus 0xa0 */ - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT, EMU_SRC_ALICE_EMU32A + 0); - emu->emu1010.output_source[16] = 21; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 1, EMU_SRC_ALICE_EMU32A + 1); - emu->emu1010.output_source[17] = 22; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 2, EMU_SRC_ALICE_EMU32A + 2); - emu->emu1010.output_source[18] = 23; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 3, EMU_SRC_ALICE_EMU32A + 3); - emu->emu1010.output_source[19] = 24; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 4, EMU_SRC_ALICE_EMU32A + 4); - emu->emu1010.output_source[20] = 25; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 5, EMU_SRC_ALICE_EMU32A + 5); - emu->emu1010.output_source[21] = 26; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 6, EMU_SRC_ALICE_EMU32A + 6); - emu->emu1010.output_source[22] = 27; - snd_emu1010_fpga_link_dst_src_write(emu, - EMU_DST_HANA_ADAT + 7, EMU_SRC_ALICE_EMU32A + 7); - emu->emu1010.output_source[23] = 28; - } - return 0; } /* diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index ef05d4f3316c..b3e5a842ac17 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -231,6 +231,20 @@ static const unsigned short emu1010_output_dst[] = { }; static_assert(ARRAY_SIZE(emu1010_output_dst) == ARRAY_SIZE(emu1010_output_texts)); +static const unsigned short emu1010_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, 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+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, 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, +}; +static_assert(ARRAY_SIZE(emu1010_output_dflt) == ARRAY_SIZE(emu1010_output_dst)); + /* 1616(m) cardbus */ static const char * const snd_emu1616_output_texts[] = { @@ -252,6 +266,17 @@ static const unsigned short emu1616_output_dst[] = { }; static_assert(ARRAY_SIZE(emu1616_output_dst) == ARRAY_SIZE(snd_emu1616_output_texts)); +static const unsigned short emu1616_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, 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+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, 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+0, EMU_SRC_ALICE_EMU32A+1, +}; +static_assert(ARRAY_SIZE(emu1616_output_dflt) == ARRAY_SIZE(emu1616_output_dst)); + /* * Data destinations - FPGA outputs going to Alice2 (Audigy) for * capture (EMU32 + I2S links) @@ -311,11 +336,43 @@ static const unsigned short emu1010_input_dst[] = { }; static_assert(ARRAY_SIZE(emu1010_input_dst) == ARRAY_SIZE(emu1010_input_texts)); +static const unsigned short emu1010_input_dflt[] = { + EMU_SRC_DOCK_MIC_A1, + EMU_SRC_DOCK_MIC_B1, + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + /* Pavel Hofman - setting defaults for all capture channels. + * Defaults only, users will set their own values anyways, let's + * just copy/paste. */ + EMU_SRC_DOCK_MIC_A1, + EMU_SRC_DOCK_MIC_B1, + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + + EMU_SRC_DOCK_ADC1_LEFT1, + EMU_SRC_DOCK_ADC1_RIGHT1, + EMU_SRC_DOCK_ADC2_LEFT1, + EMU_SRC_DOCK_ADC2_RIGHT1, + EMU_SRC_DOCK_ADC3_LEFT1, + EMU_SRC_DOCK_ADC3_RIGHT1, +}; +static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst)); + struct snd_emu1010_routing_info { const char * const *src_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_outs; unsigned n_ins; @@ -327,9 +384,11 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .src_texts = emu1010_src_texts, .n_srcs = ARRAY_SIZE(emu1010_src_texts), + .out_dflts = emu1010_output_dflt, .out_regs = emu1010_output_dst, .n_outs = ARRAY_SIZE(emu1010_output_dst), + .in_dflts = emu1010_input_dflt, .in_regs = emu1010_input_dst, .n_ins = ARRAY_SIZE(emu1010_input_dst), }, @@ -339,9 +398,11 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .src_texts = emu1616_src_texts, .n_srcs = ARRAY_SIZE(emu1616_src_texts), + .out_dflts = emu1616_output_dflt, .out_regs = emu1616_output_dst, .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, }, @@ -375,6 +436,28 @@ static void snd_emu1010_input_source_apply(struct snd_emu10k1 *emu, emu_ri->in_regs[channel], emu_ri->src_regs[src]); } +static void snd_emu1010_apply_sources(struct snd_emu10k1 *emu) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + + 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++) + 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) +{ + for (unsigned i = 0; i < emu_ri->n_srcs; 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) { @@ -1979,6 +2062,20 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, return err; } + if (emu->card_capabilities->emu_model) { + unsigned i, emu_idx = emu1010_idx(emu); + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu_idx]; + + for (i = 0; i < emu_ri->n_ins; i++) + emu->emu1010.input_source[i] = + emu1010_map_source(emu_ri, 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]); + snd_emu1010_apply_sources(emu); + } + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { /* 1616(m) cardbus */ err = add_ctls(emu, &emu1010_output_source_ctl, -- cgit v1.2.3 From 97f1582e92c91e77bcf4af3dbf445c6694eb2dff Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:09 +0200 Subject: ALSA: emu10k1: make E-MU mixer control creation more data-driven The more card models are handled separately, the more code duplication this saves. add_emu1010_source_mixers() is factored out the save duplication in a later commit. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 100 +++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 51 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index b3e5a842ac17..4fda6c2829ac 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -368,6 +368,7 @@ static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst)); struct snd_emu1010_routing_info { const char * const *src_texts; + const char * const *out_texts; const unsigned short *src_regs; const unsigned short *out_regs; const unsigned short *in_regs; @@ -386,6 +387,7 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .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, @@ -400,6 +402,7 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .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, @@ -556,6 +559,21 @@ static const struct snd_kcontrol_new emu1010_input_source_ctl = { .put = snd_emu1010_input_source_put }; +static int add_emu1010_source_mixers(struct snd_emu10k1 *emu) +{ + const struct snd_emu1010_routing_info *emu_ri = + &emu1010_routing_info[emu1010_idx(emu)]; + 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); + return err; +} + static const char * const snd_emu1010_adc_pads[] = { "ADC1 14dB PAD Audio Dock Capture Switch", @@ -668,6 +686,29 @@ static const struct snd_kcontrol_new emu1010_dac_pads_ctl = { }; +struct snd_emu1010_pads_info { + const char * const *adc_ctls, * const *dac_ctls; + unsigned n_adc_ctls, n_dac_ctls; +}; + +const struct snd_emu1010_pads_info emu1010_pads_info[] = { + { + /* all other e-mu cards for now */ + .adc_ctls = snd_emu1010_adc_pads, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads), + .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads), + }, + { + /* 1616(m) cardbus */ + .adc_ctls = snd_emu1010_adc_pads, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 2, + .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 2, + }, +}; + + static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -2066,6 +2107,7 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, unsigned i, emu_idx = emu1010_idx(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]; for (i = 0; i < emu_ri->n_ins; i++) emu->emu1010.input_source[i] = @@ -2074,69 +2116,21 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, emu->emu1010.output_source[i] = emu1010_map_source(emu_ri, emu_ri->out_dflts[i]); snd_emu1010_apply_sources(emu); - } - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { - /* 1616(m) cardbus */ - err = add_ctls(emu, &emu1010_output_source_ctl, - snd_emu1616_output_texts, - ARRAY_SIZE(snd_emu1616_output_texts)); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_input_source_ctl, - emu1010_input_texts, - ARRAY_SIZE(emu1010_input_texts)); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_adc_pads_ctl, - snd_emu1010_adc_pads, - ARRAY_SIZE(snd_emu1010_adc_pads) - 2); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_dac_pads_ctl, - snd_emu1010_dac_pads, - ARRAY_SIZE(snd_emu1010_dac_pads) - 2); - if (err < 0) - return err; err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_internal_clock, emu)); if (err < 0) return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_out, emu)); - if (err < 0) - return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_in, emu)); - if (err < 0) - return err; - } else if (emu->card_capabilities->emu_model) { - /* all other e-mu cards for now */ - err = add_ctls(emu, &emu1010_output_source_ctl, - emu1010_output_texts, - ARRAY_SIZE(emu1010_output_texts)); - if (err < 0) - return err; - err = add_ctls(emu, &emu1010_input_source_ctl, - emu1010_input_texts, - ARRAY_SIZE(emu1010_input_texts)); - if (err < 0) - return err; err = add_ctls(emu, &emu1010_adc_pads_ctl, - snd_emu1010_adc_pads, - ARRAY_SIZE(snd_emu1010_adc_pads)); + emu_pi->adc_ctls, emu_pi->n_adc_ctls); if (err < 0) return err; err = add_ctls(emu, &emu1010_dac_pads_ctl, - snd_emu1010_dac_pads, - ARRAY_SIZE(snd_emu1010_dac_pads)); - if (err < 0) - return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_internal_clock, emu)); + emu_pi->dac_ctls, emu_pi->n_dac_ctls); if (err < 0) return err; + err = snd_ctl_add(card, snd_ctl_new1(&snd_emu1010_optical_out, emu)); if (err < 0) @@ -2145,6 +2139,10 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, snd_ctl_new1(&snd_emu1010_optical_in, emu)); if (err < 0) return err; + + err = add_emu1010_source_mixers(emu); + if (err < 0) + return err; } if ( emu->card_capabilities->i2c_adc) { -- cgit v1.2.3 From f69d705d3972fae19b4b00c7643efdd3d2953f25 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:10 +0200 Subject: ALSA: emu10k1: improve mixer controls for E-MU 1010 rev2 card This card has rather different inputs/outputs due to switching from the AudioDock to the MicroDock. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 106 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 4fda6c2829ac..0e3007120fb8 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -172,6 +172,38 @@ static const unsigned short emu1010_src_regs[] = { }; static_assert(ARRAY_SIZE(emu1010_src_regs) == ARRAY_SIZE(emu1010_src_texts)); +/* 1010 rev2 */ + +#define EMU1010b_COMMON_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_TEXTS("Dock "), \ + ADAT_TEXTS("1010 ") + +static const char * const emu1010b_src_texts[] = { + EMU1010b_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu1010b_src_regs[] = { + EMU_SRC_SILENCE, + PAIR_REGS(EMU_SRC_DOCK_MIC, _A, _B), + LR_REGS(EMU_SRC_DOCK_ADC1), + LR_REGS(EMU_SRC_DOCK_ADC2), + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_MDOCK_SPDIF), + LR_REGS(EMU_SRC_HANA_SPDIF), + ADAT_REGS(EMU_SRC_MDOCK_ADAT), + ADAT_REGS(EMU_SRC_HANA_ADAT), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu1010b_src_regs) == ARRAY_SIZE(emu1010b_src_texts)); + /* 1616(m) cardbus */ #define EMU1616_COMMON_TEXTS \ @@ -245,6 +277,44 @@ static const unsigned short emu1010_output_dflt[] = { }; static_assert(ARRAY_SIZE(emu1010_output_dflt) == ARRAY_SIZE(emu1010_output_dst)); +/* 1010 rev2 */ + +static const char * const snd_emu1010b_output_texts[] = { + LR_CTLS("Dock DAC1"), + LR_CTLS("Dock DAC2"), + LR_CTLS("Dock DAC3"), + LR_CTLS("Dock SPDIF"), + ADAT_CTLS("Dock "), + LR_CTLS("0202 DAC"), + LR_CTLS("1010 SPDIF"), + ADAT_CTLS("1010 "), +}; + +static const unsigned short emu1010b_output_dst[] = { + LR_REGS(EMU_DST_DOCK_DAC1), + LR_REGS(EMU_DST_DOCK_DAC2), + LR_REGS(EMU_DST_DOCK_DAC3), + LR_REGS(EMU_DST_MDOCK_SPDIF), + ADAT_REGS(EMU_DST_MDOCK_ADAT), + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), + ADAT_REGS(EMU_DST_HANA_ADAT), +}; +static_assert(ARRAY_SIZE(emu1010b_output_dst) == ARRAY_SIZE(snd_emu1010b_output_texts)); + +static const unsigned short emu1010b_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, 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+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, 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+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, 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, +}; + /* 1616(m) cardbus */ static const char * const snd_emu1616_output_texts[] = { @@ -394,6 +464,21 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .in_regs = emu1010_input_dst, .n_ins = ARRAY_SIZE(emu1010_input_dst), }, + { + /* rev2 1010 */ + .src_regs = emu1010b_src_regs, + .src_texts = emu1010b_src_texts, + .n_srcs = ARRAY_SIZE(emu1010b_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, + }, { /* 1616(m) cardbus */ .src_regs = emu1616_src_regs, @@ -414,6 +499,8 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { static unsigned emu1010_idx(struct snd_emu10k1 *emu) { if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) + return 2; + else if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010B) return 1; else return 0; @@ -576,17 +663,17 @@ static int add_emu1010_source_mixers(struct snd_emu10k1 *emu) static const char * const snd_emu1010_adc_pads[] = { + "ADC1 14dB PAD 0202 Capture Switch", "ADC1 14dB PAD Audio Dock Capture Switch", "ADC2 14dB PAD Audio Dock Capture Switch", "ADC3 14dB PAD Audio Dock Capture Switch", - "ADC1 14dB PAD 0202 Capture Switch", }; static const unsigned short snd_emu1010_adc_pad_regs[] = { + EMU_HANA_0202_ADC_PAD1, EMU_HANA_DOCK_ADC_PAD1, EMU_HANA_DOCK_ADC_PAD2, EMU_HANA_DOCK_ADC_PAD3, - EMU_HANA_0202_ADC_PAD1, }; #define snd_emu1010_adc_pads_info snd_ctl_boolean_mono_info @@ -629,19 +716,19 @@ static const struct snd_kcontrol_new emu1010_adc_pads_ctl = { static const char * const snd_emu1010_dac_pads[] = { + "DAC1 0202 14dB PAD Playback Switch", "DAC1 Audio Dock 14dB PAD Playback Switch", "DAC2 Audio Dock 14dB PAD Playback Switch", "DAC3 Audio Dock 14dB PAD Playback Switch", "DAC4 Audio Dock 14dB PAD Playback Switch", - "DAC1 0202 14dB PAD Playback Switch", }; static const unsigned short snd_emu1010_dac_regs[] = { + EMU_HANA_0202_DAC_PAD1, EMU_HANA_DOCK_DAC_PAD1, EMU_HANA_DOCK_DAC_PAD2, EMU_HANA_DOCK_DAC_PAD3, EMU_HANA_DOCK_DAC_PAD4, - EMU_HANA_0202_DAC_PAD1, }; #define snd_emu1010_dac_pads_info snd_ctl_boolean_mono_info @@ -700,10 +787,17 @@ const struct snd_emu1010_pads_info emu1010_pads_info[] = { .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads), }, { - /* 1616(m) cardbus */ + /* rev2 1010 */ .adc_ctls = snd_emu1010_adc_pads, - .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 2, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 1, .dac_ctls = snd_emu1010_dac_pads, + .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 1, + }, + { + /* 1616(m) cardbus */ + .adc_ctls = snd_emu1010_adc_pads + 1, + .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads) - 2, + .dac_ctls = snd_emu1010_dac_pads + 1, .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 2, }, }; -- cgit v1.2.3 From 6f3609f8a3da1214cd78f8a8a2ee2dab8fcc4505 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:11 +0200 Subject: ALSA: emu10k1: add explicit support for E-MU 0404 Unlike the other models, this is actually a distinct card, rather than an E-MU 1010 with different "dongles". It is stereo only, and supports no ADAT (there is no trace of ADAT in the manual, switching the output mode to ADAT has no effect, and switching the input mode to ADAT just breaks input (presumably ... my only ADAT source is the card's output)). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-10-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 + sound/pci/emu10k1/emu10k1_main.c | 20 ++++--- sound/pci/emu10k1/emumixer.c | 112 +++++++++++++++++++++++++++++++++------ sound/pci/emu10k1/emuproc.c | 18 ++++--- 4 files changed, 123 insertions(+), 29 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index b263c762c01a..aab45a23320e 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1621,7 +1621,9 @@ 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 spk20:1; /* Stereo only */ unsigned int spk71:1; /* Has 7.1 speakers */ + unsigned int no_adat:1; /* Has no ADAT, only SPDIF */ unsigned int sblive51:1; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ unsigned int spdif_bug:1; /* Has Spdif phasing bug */ unsigned int ac97_chip:2; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 6a3476de74e6..da7c988b5c97 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -852,9 +852,14 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) snd_emu1010_fpga_read(emu, EMU_HANA_OPTION_CARDS, ®); dev_info(emu->card->dev, "emu1010: Card options = 0x%x\n", reg); - /* Optical -> ADAT I/O */ - emu->emu1010.optical_in = 1; /* IN_ADAT */ - emu->emu1010.optical_out = 1; /* OUT_ADAT */ + if (emu->card_capabilities->no_adat) { + emu->emu1010.optical_in = 0; /* IN_SPDIF */ + emu->emu1010.optical_out = 0; /* OUT_SPDIF */ + } else { + /* Optical -> ADAT I/O */ + emu->emu1010.optical_in = 1; /* IN_ADAT */ + emu->emu1010.optical_out = 1; /* OUT_ADAT */ + } tmp = (emu->emu1010.optical_in ? EMU_HANA_OPTICAL_IN_ADAT : EMU_HANA_OPTICAL_IN_SPDIF) | (emu->emu1010.optical_out ? EMU_HANA_OPTICAL_OUT_ADAT : EMU_HANA_OPTICAL_OUT_SPDIF); snd_emu1010_fpga_write(emu, EMU_HANA_OPTICAL_TYPE, tmp); @@ -1117,7 +1122,8 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, - .spk71 = 1, + .spk20 = 1, + .no_adat = 1, .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 new revision */ /* This is MAEM8850 "HanaLite" */ /* Supports sync daughter card. */ @@ -1127,7 +1133,8 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .id = "EMU0404", .emu10k2_chip = 1, .ca0102_chip = 1, - .spk71 = 1, + .spk20 = 1, + .no_adat = 1, .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 */ /* EMU0404 PCIe */ /* Does NOT support sync daughter card. */ @@ -1136,7 +1143,8 @@ static const struct snd_emu_chip_details emu_chip_details[] = { .id = "EMU0404", .emu10k2_chip = 1, .ca0108_chip = 1, - .spk71 = 1, + .spk20 = 1, + .no_adat = 1, .emu_model = EMU_MODEL_EMU0404}, /* EMU 0404 PCIe ver_03 */ {.vendor = 0x1102, .device = 0x0008, .driver = "Audigy2", .name = "SB Audigy 2 Value [Unknown]", diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 0e3007120fb8..41a1cf10c6d8 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -144,6 +144,8 @@ static int snd_emu10k1_spdif_get_mask(struct snd_kcontrol *kcontrol, EMU_SRC_ALICE_EMU32B+0xe, \ EMU_SRC_ALICE_EMU32B+0xf +/* 1010 rev1 */ + #define EMU1010_COMMON_TEXTS \ "Silence", \ PAIR_TEXTS("Dock Mic", "A", "B"), \ @@ -230,6 +232,26 @@ static const unsigned short emu1616_src_regs[] = { }; static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); +/* 0404 rev1 & rev2 */ + +#define EMU0404_COMMON_TEXTS \ + "Silence", \ + LR_TEXTS("ADC"), \ + LR_TEXTS("SPDIF") + +static const char * const emu0404_src_texts[] = { + EMU0404_COMMON_TEXTS, + DSP_TEXTS, +}; + +static const unsigned short emu0404_src_regs[] = { + EMU_SRC_SILENCE, + LR_REGS(EMU_SRC_HAMOA_ADC), + LR_REGS(EMU_SRC_HANA_SPDIF), + EMU32_SRC_REGS, +}; +static_assert(ARRAY_SIZE(emu0404_src_regs) == ARRAY_SIZE(emu0404_src_texts)); + /* * Data destinations - physical EMU outputs. * Each destination has an enum mixer control to choose a data source @@ -238,6 +260,8 @@ static_assert(ARRAY_SIZE(emu1616_src_regs) == ARRAY_SIZE(emu1616_src_texts)); #define LR_CTLS(base) LR_PS(base, " Playback Enum") #define ADAT_CTLS(pfx) ADAT_PS(pfx, " Playback Enum") +/* 1010 rev1 */ + static const char * const emu1010_output_texts[] = { LR_CTLS("Dock DAC1"), LR_CTLS("Dock DAC2"), @@ -347,6 +371,25 @@ static const unsigned short emu1616_output_dflt[] = { }; static_assert(ARRAY_SIZE(emu1616_output_dflt) == ARRAY_SIZE(emu1616_output_dst)); +/* 0404 rev1 & rev2 */ + +static const char * const snd_emu0404_output_texts[] = { + LR_CTLS("DAC"), + LR_CTLS("SPDIF"), +}; + +static const unsigned short emu0404_output_dst[] = { + LR_REGS(EMU_DST_HAMOA_DAC), + LR_REGS(EMU_DST_HANA_SPDIF), +}; +static_assert(ARRAY_SIZE(emu0404_output_dst) == ARRAY_SIZE(snd_emu0404_output_texts)); + +static const unsigned short emu0404_output_dflt[] = { + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, + EMU_SRC_ALICE_EMU32A+0, EMU_SRC_ALICE_EMU32A+1, +}; +static_assert(ARRAY_SIZE(emu0404_output_dflt) == ARRAY_SIZE(emu0404_output_dst)); + /* * Data destinations - FPGA outputs going to Alice2 (Audigy) for * capture (EMU32 + I2S links) @@ -436,6 +479,25 @@ static const unsigned short emu1010_input_dflt[] = { }; static_assert(ARRAY_SIZE(emu1010_input_dflt) == ARRAY_SIZE(emu1010_input_dst)); +static const unsigned short emu0404_input_dflt[] = { + EMU_SRC_HAMOA_ADC_LEFT1, + EMU_SRC_HAMOA_ADC_RIGHT1, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_HANA_SPDIF_LEFT1, + EMU_SRC_HANA_SPDIF_RIGHT1, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, + EMU_SRC_SILENCE, +}; + struct snd_emu1010_routing_info { const char * const *src_texts; const char * const *out_texts; @@ -451,6 +513,7 @@ struct snd_emu1010_routing_info { 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), @@ -494,16 +557,26 @@ const struct snd_emu1010_routing_info emu1010_routing_info[] = { .in_regs = emu1010_input_dst, .n_ins = ARRAY_SIZE(emu1010_input_dst) - 6, }, + { + /* 0404 */ + .src_regs = emu0404_src_regs, + .src_texts = emu0404_src_texts, + .n_srcs = ARRAY_SIZE(emu0404_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, + }, }; static unsigned emu1010_idx(struct snd_emu10k1 *emu) { - if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) - return 2; - else if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010B) - return 1; - else - return 0; + return emu->card_capabilities->emu_model - 1; } static void snd_emu1010_output_source_apply(struct snd_emu10k1 *emu, @@ -780,7 +853,7 @@ struct snd_emu1010_pads_info { const struct snd_emu1010_pads_info emu1010_pads_info[] = { { - /* all other e-mu cards for now */ + /* rev1 1010 */ .adc_ctls = snd_emu1010_adc_pads, .n_adc_ctls = ARRAY_SIZE(snd_emu1010_adc_pads), .dac_ctls = snd_emu1010_dac_pads, @@ -800,6 +873,13 @@ const struct snd_emu1010_pads_info emu1010_pads_info[] = { .dac_ctls = snd_emu1010_dac_pads + 1, .n_dac_ctls = ARRAY_SIZE(snd_emu1010_dac_pads) - 2, }, + { + /* 0404 */ + .adc_ctls = NULL, + .n_adc_ctls = 0, + .dac_ctls = NULL, + .n_dac_ctls = 0, + }, }; @@ -2225,14 +2305,16 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, if (err < 0) return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_out, emu)); - if (err < 0) - return err; - err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_optical_in, emu)); - if (err < 0) - return err; + if (!emu->card_capabilities->no_adat) { + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_optical_out, emu)); + if (err < 0) + return err; + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_optical_in, emu)); + if (err < 0) + return err; + } err = add_emu1010_source_mixers(emu); if (err < 0) diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index c92253de881e..708aff6cf09a 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -229,14 +229,16 @@ static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, u32 rate; if (emu->card_capabilities->emu_model) { - snd_emu1010_fpga_read(emu, 0x38, &value); - if ((value & 0x1) == 0) { - snd_emu1010_fpga_read(emu, 0x2a, &value); - snd_emu1010_fpga_read(emu, 0x2b, &value2); - rate = 0x1770000 / (((value << 5) | value2)+1); - snd_iprintf(buffer, "ADAT Locked : %u\n", rate); - } else { - snd_iprintf(buffer, "ADAT Unlocked\n"); + if (!emu->card_capabilities->no_adat) { + snd_emu1010_fpga_read(emu, 0x38, &value); + if ((value & 0x1) == 0) { + snd_emu1010_fpga_read(emu, 0x2a, &value); + snd_emu1010_fpga_read(emu, 0x2b, &value2); + rate = 0x1770000 / (((value << 5) | value2)+1); + snd_iprintf(buffer, "ADAT Locked : %u\n", rate); + } else { + snd_iprintf(buffer, "ADAT Unlocked\n"); + } } snd_emu1010_fpga_read(emu, 0x20, &value); if ((value & 0x4) == 0) { -- cgit v1.2.3 From 216abe45cf4addba4e4c1eb2fae24762ffdefe9e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 16 May 2023 11:36:12 +0200 Subject: ALSA: emu10k1: make struct snd_emu1010 less wasteful Shrink the {in,out}put_source arrays and their data type to what is actually necessary. To be still on the safe side, add some static asserts. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230516093612.3536508-11-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 7 +++++-- sound/pci/emu10k1/emumixer.c | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index aab45a23320e..5ad2144fa530 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1637,9 +1637,12 @@ struct snd_emu_chip_details { const char *id; /* for backward compatibility - can be NULL if not needed */ }; +#define NUM_OUTPUT_DESTS 28 +#define NUM_INPUT_DESTS 22 + struct snd_emu1010 { - unsigned int output_source[64]; - unsigned int input_source[64]; + unsigned char output_source[NUM_OUTPUT_DESTS]; + unsigned char input_source[NUM_INPUT_DESTS]; unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ unsigned int internal_clock; /* 44100 or 48000 */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 41a1cf10c6d8..3a7f25f81504 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -273,6 +273,7 @@ static const char * const emu1010_output_texts[] = { LR_CTLS("1010 SPDIF"), ADAT_CTLS("1010 "), }; +static_assert(ARRAY_SIZE(emu1010_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu1010_output_dst[] = { LR_REGS(EMU_DST_DOCK_DAC1), @@ -313,6 +314,7 @@ static const char * const snd_emu1010b_output_texts[] = { LR_CTLS("1010 SPDIF"), ADAT_CTLS("1010 "), }; +static_assert(ARRAY_SIZE(snd_emu1010b_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu1010b_output_dst[] = { LR_REGS(EMU_DST_DOCK_DAC1), @@ -349,6 +351,7 @@ static const char * const snd_emu1616_output_texts[] = { ADAT_CTLS("Dock "), LR_CTLS("Mana DAC"), }; +static_assert(ARRAY_SIZE(snd_emu1616_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu1616_output_dst[] = { LR_REGS(EMU_DST_DOCK_DAC1), @@ -377,6 +380,7 @@ static const char * const snd_emu0404_output_texts[] = { LR_CTLS("DAC"), LR_CTLS("SPDIF"), }; +static_assert(ARRAY_SIZE(snd_emu0404_output_texts) <= NUM_OUTPUT_DESTS); static const unsigned short emu0404_output_dst[] = { LR_REGS(EMU_DST_HAMOA_DAC), @@ -421,6 +425,7 @@ static const char * const emu1010_input_texts[] = { "DSP 14 Capture Enum", "DSP 15 Capture Enum", }; +static_assert(ARRAY_SIZE(emu1010_input_texts) <= NUM_INPUT_DESTS); static const unsigned short emu1010_input_dst[] = { EMU_DST_ALICE2_EMU32_0, -- cgit v1.2.3 From 9fe0731bc345230e8ce125056b9407c63960f74e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 18:48:00 +0200 Subject: ALSA: emu10k1: remove runtime 64-bit divisions 32-bit platforms don't like these. As we're actually dealing with constants, factor out the calculations and pass them in as additional arguments. To keep the call sites clean, wrap the actual functions in macros which generate the arguments. Fixes: bb5ceb43b7bf ("ALSA: emu10k1: fix non-zero mixer control defaults in highres mode") Fixes: 1298bc978afb ("ALSA: emu10k1: enable bit-exact playback, part 1: DSP attenuation") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202305171622.jKTovBvy-lkp@intel.com/ Reported-by: Linux Kernel Functional Testing Reported-by: Christophe Leroy Closes: https://lore.kernel.org/r/CA+G9fYsShNP=LALHdMd-Btx3PBtO_CjyBNgpStr9fPGXNbRvdg@mail.gmail.com Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517164800.3650699-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emufx.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index f64b2b4eb348..e9855d37fa5c 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -1144,9 +1144,11 @@ static int snd_emu10k1_ipcm_peek(struct snd_emu10k1 *emu, #define SND_EMU10K1_PLAYBACK_CHANNELS 8 #define SND_EMU10K1_CAPTURE_CHANNELS 4 +#define HR_VAL(v) ((v) * 0x80000000LL / 100 - 1) + static void -snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) +snd_emu10k1_init_mono_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); @@ -1156,7 +1158,7 @@ snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; - defval = defval * 0x80000000LL / 100 - 1; + defval = defval_hr; } else { ctl->min = 0; ctl->max = 100; @@ -1165,10 +1167,12 @@ snd_emu10k1_init_mono_control(struct snd_emu10k1_fx8010_control_gpr *ctl, } ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; } +#define snd_emu10k1_init_mono_control(ctl, name, gpr, defval) \ + snd_emu10k1_init_mono_control2(ctl, name, gpr, defval, HR_VAL(defval)) static void -snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, - const char *name, int gpr, int defval) +snd_emu10k1_init_stereo_control2(struct snd_emu10k1_fx8010_control_gpr *ctl, + const char *name, int gpr, int defval, int defval_hr) { ctl->id.iface = (__force int)SNDRV_CTL_ELEM_IFACE_MIXER; strcpy(ctl->id.name, name); @@ -1178,7 +1182,7 @@ snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->max = 0x7fffffff; ctl->tlv = snd_emu10k1_db_linear; ctl->translation = EMU10K1_GPR_TRANSLATION_NEGATE; - defval = defval * 0x80000000LL / 100 - 1; + defval = defval_hr; } else { ctl->min = 0; ctl->max = 100; @@ -1188,6 +1192,8 @@ snd_emu10k1_init_stereo_control(struct snd_emu10k1_fx8010_control_gpr *ctl, ctl->gpr[0] = gpr + 0; ctl->value[0] = defval; ctl->gpr[1] = gpr + 1; ctl->value[1] = defval; } +#define snd_emu10k1_init_stereo_control(ctl, name, gpr, defval) \ + snd_emu10k1_init_stereo_control2(ctl, name, gpr, defval, HR_VAL(defval)) static void snd_emu10k1_init_mono_onoff_control(struct snd_emu10k1_fx8010_control_gpr *ctl, -- cgit v1.2.3 From af7fd0276ed76357974ebb0e5b5968b4b4e84781 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:48 +0200 Subject: ALSA: emu10k1: pass frame instead of byte addresses ... to snd_emu10k1_pcm_init_voice(). This makes the code arguably less convoluted. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index d669f93d8930..9f151a0a7756 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -270,15 +270,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, stereo = runtime->channels == 2; w_16 = snd_pcm_format_width(runtime->format) == 16; - if (!extra && stereo) { - start_addr >>= 1; - end_addr >>= 1; - } - if (w_16) { - start_addr >>= 1; - end_addr >>= 1; - } - spin_lock_irqsave(&emu->reg_lock, flags); /* volume parameters */ @@ -424,19 +415,16 @@ static int snd_emu10k1_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 w_16 = snd_pcm_format_width(runtime->format) == 16; + bool stereo = runtime->channels == 2; unsigned int start_addr, end_addr; - start_addr = epcm->start_addr; - end_addr = snd_pcm_lib_period_bytes(substream); - if (runtime->channels == 2) { - start_addr >>= 1; - end_addr >>= 1; - } - end_addr += start_addr; + start_addr = epcm->start_addr >> w_16; + end_addr = start_addr + runtime->period_size; snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, start_addr, end_addr, NULL); - start_addr = epcm->start_addr; - end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream); + start_addr >>= stereo; + end_addr = start_addr + runtime->buffer_size; snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], start_addr, end_addr, &emu->pcm_mixer[substream->number]); @@ -452,14 +440,13 @@ 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; - unsigned int start_addr, end_addr; + unsigned int start_addr; unsigned int channel_size; int i; - start_addr = epcm->start_addr; - end_addr = epcm->start_addr + snd_pcm_lib_buffer_bytes(substream); + start_addr = epcm->start_addr >> 1; // 16-bit voices - channel_size = ( end_addr - start_addr ) / NUM_EFX_PLAYBACK; + channel_size = runtime->buffer_size; snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, start_addr, start_addr + (channel_size / 2), NULL); -- cgit v1.2.3 From 1e5323bd7725c1e3a5bd65af210ea7d54ccdbd00 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:49 +0200 Subject: Revert "ALSA: emu10k1 - delay the PCM interrupts (add pcm_irq_delay parameter)" This workaround fails to address the underlying problem, which is actually wholly self-made. Subsequent patches will fix it. This reverts commit 56385a12d9bb9e173751f74b6c430742018cafc0. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 - sound/core/pcm_native.c | 4 ---- sound/pci/emu10k1/emu10k1.c | 4 ---- sound/pci/emu10k1/emupcm.c | 25 ++----------------------- sound/pci/emu10k1/memory.c | 4 +--- 5 files changed, 3 insertions(+), 35 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 5ad2144fa530..2d64f07e3883 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1670,7 +1670,6 @@ struct snd_emu10k1 { unsigned int address_mode; /* address mode */ unsigned long dma_mask; /* PCI DMA mask */ bool iommu_workaround; /* IOMMU workaround needed */ - unsigned int delay_pcm_irq; /* in samples */ 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 */ diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 39a65d1415ab..95fc56e403b1 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1605,10 +1605,6 @@ static int snd_pcm_do_pause(struct snd_pcm_substream *substream, { if (substream->runtime->trigger_master != substream) return 0; - /* some drivers might use hw_ptr to recover from the pause - - update the hw_ptr now */ - if (pause_pushed(state)) - snd_pcm_update_hw_ptr(substream); /* The jiffies check in snd_pcm_update_hw_ptr*() is done by * a delta between the current jiffies, this gives a large enough * delta, effectively to skip the check once. diff --git a/sound/pci/emu10k1/emu10k1.c b/sound/pci/emu10k1/emu10k1.c index 0c97237af922..23adace1b969 100644 --- a/sound/pci/emu10k1/emu10k1.c +++ b/sound/pci/emu10k1/emu10k1.c @@ -34,7 +34,6 @@ static int max_synth_voices[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 64}; static int max_buffer_size[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 128}; static bool enable_ir[SNDRV_CARDS]; static uint subsystem[SNDRV_CARDS]; /* Force card subsystem model */ -static uint delay_pcm_irq[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for the EMU10K1 soundcard."); @@ -56,8 +55,6 @@ module_param_array(enable_ir, bool, NULL, 0444); MODULE_PARM_DESC(enable_ir, "Enable IR."); module_param_array(subsystem, uint, NULL, 0444); MODULE_PARM_DESC(subsystem, "Force card subsystem model."); -module_param_array(delay_pcm_irq, uint, NULL, 0444); -MODULE_PARM_DESC(delay_pcm_irq, "Delay PCM interrupt by specified number of samples (default 0)."); /* * Class 0401: 1102:0008 (rev 00) Subsystem: 1102:1001 -> Audigy2 Value Model:SB0400 */ @@ -103,7 +100,6 @@ static int snd_card_emu10k1_probe(struct pci_dev *pci, enable_ir[dev], subsystem[dev]); if (err < 0) return err; - emu->delay_pcm_irq = delay_pcm_irq[dev] & 0x1f; err = snd_emu10k1_pcm(emu, 0); if (err < 0) return err; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 9f151a0a7756..27977d03e323 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -290,7 +290,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, evoice->epcm->ccca_start_addr = start_addr + ccis; if (extra) { start_addr += ccis; - end_addr += ccis + emu->delay_pcm_irq; + end_addr += ccis; } } if (stereo && !extra) { @@ -315,9 +315,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]); // Stereo slaves don't need to have the addresses set, but it doesn't hurt snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24)); - snd_emu10k1_ptr_write(emu, PSST, voice, - (start_addr + (extra ? emu->delay_pcm_irq : 0)) | - (send_amount[2] << 24)); + snd_emu10k1_ptr_write(emu, PSST, voice, start_addr | (send_amount[2] << 24)); if (emu->card_capabilities->emu_model) pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else @@ -647,23 +645,6 @@ static void snd_emu10k1_playback_set_stopped(struct snd_emu10k1 *emu, epcm->running = 0; } -static inline void snd_emu10k1_playback_mangle_extra(struct snd_emu10k1 *emu, - struct snd_emu10k1_pcm *epcm, - struct snd_pcm_substream *substream, - struct snd_pcm_runtime *runtime) -{ - unsigned int ptr, period_pos; - - /* try to sychronize the current position for the interrupt - source voice */ - period_pos = runtime->status->hw_ptr - runtime->hw_ptr_interrupt; - period_pos %= runtime->period_size; - ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->extra->number); - ptr &= ~0x00ffffff; - ptr |= epcm->ccca_start_addr + period_pos; - snd_emu10k1_ptr_write(emu, CCCA, epcm->extra->number, ptr); -} - static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, int cmd) { @@ -686,8 +667,6 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - if (cmd == SNDRV_PCM_TRIGGER_PAUSE_RELEASE) - snd_emu10k1_playback_mangle_extra(emu, epcm, substream, runtime); mix = &emu->pcm_mixer[substream->number]; snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], true, mix); snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], false, mix); diff --git a/sound/pci/emu10k1/memory.c b/sound/pci/emu10k1/memory.c index edb3f1763719..20b07117574b 100644 --- a/sound/pci/emu10k1/memory.c +++ b/sound/pci/emu10k1/memory.c @@ -315,10 +315,8 @@ snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *subst if (snd_BUG_ON(!hdr)) return NULL; - idx = runtime->period_size >= runtime->buffer_size ? - (emu->delay_pcm_irq * 2) : 0; mutex_lock(&hdr->block_mutex); - blk = search_empty(emu, runtime->dma_bytes + idx); + blk = search_empty(emu, runtime->dma_bytes); if (blk == NULL) { mutex_unlock(&hdr->block_mutex); return NULL; -- cgit v1.2.3 From be3b7629e13a5861b6988d46912212ac9f24c369 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:50 +0200 Subject: ALSA: emu10k1: remove pointless displacement of the extra voices The idea is to make the extra voice lag behind the "real" voices, but moving the buffer address around doesn't contribute to that, as the CCCA write below uses the same address. The exact address is unimportant, as the data is discarded anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 27977d03e323..16e7d0ff97a4 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -288,10 +288,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, if (master) { evoice->epcm->ccca_start_addr = start_addr + ccis; - if (extra) { - start_addr += ccis; - end_addr += ccis; - } } if (stereo && !extra) { // Not really necessary for the slave, but it doesn't hurt -- cgit v1.2.3 From cd6dceb197ca5ec70a3ed4c6aec50f9abdf85f8e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:51 +0200 Subject: ALSA: emu10k1: skip pointless cache setup for extra voices Given that the data is going to be ignored anyway, and that the cache does not influence interrupt timing (which is the purpose of the extra voices), it's pointless to pre-fill the cache. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 16e7d0ff97a4..a6c4f1895a08 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -528,14 +528,15 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) return 0; } -static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, int extra, struct snd_emu10k1_voice *evoice) +static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice) { struct snd_pcm_runtime *runtime; unsigned int voice, stereo, i, ccis, cra = 64, cs, sample; runtime = evoice->epcm->substream->runtime; voice = evoice->number; - stereo = (!extra && runtime->channels == 2); + stereo = (runtime->channels == 2); sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080; ccis = emu10k1_ccis(stereo, sample == 0); /* set cs to 2 * number of cache registers beside the invalidated */ @@ -658,8 +659,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_invalidate_cache(emu, 1, epcm->extra); /* do we need this? */ - snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[0]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[0]); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: @@ -803,9 +803,8 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: /* prepare voices */ for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_invalidate_cache(emu, 0, epcm->voices[i]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i]); } - snd_emu10k1_playback_invalidate_cache(emu, 1, epcm->extra); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: -- cgit v1.2.3 From 5b1cd21f0f05757e724e18a599b391689f8565fc Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:52 +0200 Subject: ALSA: emu10k1: fix PCM playback cache and interrupt handling The cache causes a fixed delay regardless of stream parameters. Consequently, all that "cache invalidate size" calculation stuff was garbage (which can be traced right back to Creative's OSS driver). This also removes the definitions of registers CD1..CDF, because they are accessed only relative to CD0 anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 38 +++++++++++++------------- sound/pci/emu10k1/emupcm.c | 67 ++++++++++++++++------------------------------ 2 files changed, 43 insertions(+), 62 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 2d64f07e3883..ee662a1b0dc7 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -116,6 +116,10 @@ #define IPR_MIDITRANSBUFEMPTY 0x00000100 /* MIDI UART transmit buffer empty */ #define IPR_MIDIRECVBUFEMPTY 0x00000080 /* MIDI UART receive buffer empty */ #define IPR_CHANNELLOOP 0x00000040 /* Channel (half) loop interrupt(s) pending */ + /* The interrupt is triggered shortly after */ + /* CCR_READADDRESS has crossed the boundary; */ + /* due to the cache, this runs ahead of the */ + /* actual playback position. */ #define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */ /* highest set channel in CLIPL, CLIPH, HLIPL, */ /* or HLIPH. When IPR is written with CL set, */ @@ -586,24 +590,22 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ /* 0x1f: not used */ -#define CD0 0x20 /* Cache data 0 register */ -#define CD1 0x21 /* Cache data 1 register */ -#define CD2 0x22 /* Cache data 2 register */ -#define CD3 0x23 /* Cache data 3 register */ -#define CD4 0x24 /* Cache data 4 register */ -#define CD5 0x25 /* Cache data 5 register */ -#define CD6 0x26 /* Cache data 6 register */ -#define CD7 0x27 /* Cache data 7 register */ -#define CD8 0x28 /* Cache data 8 register */ -#define CD9 0x29 /* Cache data 9 register */ -#define CDA 0x2a /* Cache data A register */ -#define CDB 0x2b /* Cache data B register */ -#define CDC 0x2c /* Cache data C register */ -#define CDD 0x2d /* Cache data D register */ -#define CDE 0x2e /* Cache data E register */ -#define CDF 0x2f /* Cache data F register */ - -/* 0x30-3f seem to be the same as 0x20-2f */ +// 32 cache registers (== 128 bytes) per channel follow. +// In stereo mode, the two channels' caches are concatenated into one, +// and hold the interleaved frames. +// The cache holds 64 frames, so the upper half is not used in 8-bit mode. +// All registers mentioned below count in frames. +// The cache is a ring buffer; CCR_READADDRESS operates modulo 64. +// The cache is filled from (CCCA_CURRADDR - CCR_CACHEINVALIDSIZE) +// into (CCR_READADDRESS - CCR_CACHEINVALIDSIZE). +// The engine has a fetch threshold of 32 bytes, so it tries to keep +// CCR_CACHEINVALIDSIZE below 8 (16-bit stereo), 16 (16-bit mono, +// 8-bit stereo), or 32 (8-bit mono). The actual transfers are pretty +// unpredictable, especially if several voices are running. +// Frames are consumed at CCR_READADDRESS, which is incremented afterwards, +// along with CCCA_CURRADDR and CCR_CACHEINVALIDSIZE. This implies that the +// actual playback position always lags CCCA_CURRADDR by exactly 64 frames. +#define CD0 0x20 /* Cache data registers 0 .. 0x1f */ #define PTB 0x40 /* Page table base register */ #define PTB_MASK 0xfffff000 /* Physical address of the page table in host memory */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index a6c4f1895a08..feb575922738 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -112,6 +112,10 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic } } if (epcm->extra == NULL) { + // The hardware supports only (half-)loop interrupts, so to support an + // arbitrary number of periods per buffer, we use an extra voice with a + // period-sized loop as the interrupt source. Additionally, the interrupt + // timing of the hardware is "suboptimal" and needs some compensation. err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, 1, @@ -232,23 +236,6 @@ static unsigned int emu10k1_select_interprom(unsigned int pitch_target) return CCCA_INTERPROM_2; } -/* - * calculate cache invalidate size - * - * stereo: channel is stereo - * w_16: using 16bit samples - * - * returns: cache invalidate size in samples - */ -static inline int emu10k1_ccis(int stereo, int w_16) -{ - if (w_16) { - return stereo ? 24 : 26; - } else { - return stereo ? 24*2 : 26*2; - } -} - static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, int master, int extra, struct snd_emu10k1_voice *evoice, @@ -264,7 +251,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, unsigned char send_routing[8]; unsigned long flags; unsigned int pitch_target; - unsigned int ccis; voice = evoice->number; stereo = runtime->channels == 2; @@ -284,10 +270,8 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, memcpy(send_amount, &mix->send_volume[tmp][0], 8); } - ccis = emu10k1_ccis(stereo, w_16); - if (master) { - evoice->epcm->ccca_start_addr = start_addr + ccis; + evoice->epcm->ccca_start_addr = start_addr + 64 - 3; } if (stereo && !extra) { // Not really necessary for the slave, but it doesn't hurt @@ -317,11 +301,11 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, else pitch_target = emu10k1_calc_pitch_target(runtime->rate); if (extra) - snd_emu10k1_ptr_write(emu, CCCA, voice, start_addr | + snd_emu10k1_ptr_write(emu, CCCA, voice, (end_addr - 3) | emu10k1_select_interprom(pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT)); else - snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + ccis) | + snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + 64 - 3) | emu10k1_select_interprom(pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT)); /* Clear filter delay memory */ @@ -532,35 +516,30 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { struct snd_pcm_runtime *runtime; - unsigned int voice, stereo, i, ccis, cra = 64, cs, sample; + unsigned voice, stereo, sample; + u32 ccr; runtime = evoice->epcm->substream->runtime; voice = evoice->number; stereo = (runtime->channels == 2); sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080; - ccis = emu10k1_ccis(stereo, sample == 0); - /* set cs to 2 * number of cache registers beside the invalidated */ - cs = (sample == 0) ? (32-ccis) : (64-ccis+1) >> 1; - if (cs > 16) cs = 16; - for (i = 0; i < cs; i++) { + + // We assume that the cache is resting at this point (i.e., + // CCR_CACHEINVALIDSIZE is very small). + + // Clear leading frames. For simplicitly, this does too much, + // except for 16-bit stereo. And the interpolator will actually + // access them at all only when we're pitch-shifting. + for (int i = 0; i < 3; i++) snd_emu10k1_ptr_write(emu, CD0 + i, voice, sample); - if (stereo) { - snd_emu10k1_ptr_write(emu, CD0 + i, voice + 1, sample); - } - } - /* reset cache */ - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, 0); - snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice, cra); - if (stereo) { - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice + 1, 0); - // The engine goes haywire if this one is out of sync - snd_emu10k1_ptr_write(emu, CCR_READADDRESS, voice + 1, cra); - } - /* fill cache */ - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice, ccis); + + // Fill cache + ccr = (64 - 3) << REG_SHIFT(CCR_CACHEINVALIDSIZE); if (stereo) { - snd_emu10k1_ptr_write(emu, CCR_CACHEINVALIDSIZE, voice+1, ccis); + // The engine goes haywire if CCR_READADDRESS is out of sync + snd_emu10k1_ptr_write(emu, CCR, voice + 1, ccr); } + snd_emu10k1_ptr_write(emu, CCR, voice, ccr); } static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, -- cgit v1.2.3 From 9e72666b9ee1140b7ecc66abc30b1c694ed7a6a0 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:54 +0200 Subject: ALSA: emu10k1: improve API of low-level voice manipulation functions Originally, there was a 1:1 relationship between the PCM streams' and the low-level voices' parameters. The addition of multi-channel playback partially invalidated that, but didn't introduce proper layering, so things kept working only by virtue of the multi-channel device never having two channels (yet). The upcoming addition of 32-bit playback would complete upending the relationships. So this patch detaches the low-level parameters from the high-level ones: we pass pre-calculated bit width and stereo flags to the low-level manipulation functions instead of calculating them in-place from the stream parameters. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 50 ++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index feb575922738..bbe054be2448 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -239,6 +239,7 @@ static unsigned int emu10k1_select_interprom(unsigned int pitch_target) static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, int master, int extra, struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo, unsigned int start_addr, unsigned int end_addr, struct snd_emu10k1_pcm_mixer *mix) @@ -246,15 +247,13 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream = evoice->epcm->substream; struct snd_pcm_runtime *runtime = substream->runtime; unsigned int silent_page, tmp; - int voice, stereo, w_16; + int voice; unsigned char send_amount[8]; unsigned char send_routing[8]; unsigned long flags; unsigned int pitch_target; voice = evoice->number; - stereo = runtime->channels == 2; - w_16 = snd_pcm_format_width(runtime->format) == 16; spin_lock_irqsave(&emu->reg_lock, flags); @@ -273,7 +272,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, if (master) { evoice->epcm->ccca_start_addr = start_addr + 64 - 3; } - if (stereo && !extra) { + if (stereo) { // Not really necessary for the slave, but it doesn't hurt snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); } else { @@ -399,15 +398,15 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) start_addr = epcm->start_addr >> w_16; end_addr = start_addr + runtime->period_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, + snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, w_16, false, start_addr, end_addr, NULL); start_addr >>= stereo; end_addr = start_addr + runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], + snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, start_addr, end_addr, &emu->pcm_mixer[substream->number]); if (epcm->voices[1]) - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], + snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], w_16, true, start_addr, end_addr, &emu->pcm_mixer[substream->number]); return 0; @@ -426,17 +425,17 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) channel_size = runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, + snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, true, false, start_addr, start_addr + (channel_size / 2), NULL); /* only difference with the master voice is we use it for the pointer */ - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], + snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], true, false, start_addr, start_addr + channel_size, &emu->efx_pcm_mixer[0]); start_addr += channel_size; for (i = 1; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], + snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], true, false, start_addr, start_addr + channel_size, &emu->efx_pcm_mixer[i]); start_addr += channel_size; @@ -513,16 +512,14 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) } static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, - struct snd_emu10k1_voice *evoice) + struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo) { - struct snd_pcm_runtime *runtime; - unsigned voice, stereo, sample; + unsigned voice, sample; u32 ccr; - runtime = evoice->epcm->substream->runtime; voice = evoice->number; - stereo = (runtime->channels == 2); - sample = snd_pcm_format_width(runtime->format) == 16 ? 0 : 0x80808080; + sample = w_16 ? 0 : 0x80808080; // We assume that the cache is resting at this point (i.e., // CCR_CACHEINVALIDSIZE is very small). @@ -552,20 +549,15 @@ static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, - bool master, + bool stereo, bool master, struct snd_emu10k1_pcm_mixer *mix) { - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; unsigned int vattn; unsigned int tmp; if (evoice == NULL) /* skip second voice for mono */ return; - substream = evoice->epcm->substream; - runtime = substream->runtime; - - tmp = runtime->channels == 2 ? (master ? 1 : 2) : 0; + tmp = stereo ? (master ? 1 : 2) : 0; vattn = mix->attn[tmp] << 16; snd_emu10k1_playback_commit_volume(emu, evoice, vattn); } @@ -628,6 +620,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; struct snd_emu10k1_pcm_mixer *mix; + bool w_16 = snd_pcm_format_width(runtime->format) == 16; + bool stereo = runtime->channels == 2; int result = 0; /* @@ -638,13 +632,13 @@ 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_invalidate_cache(emu, epcm->voices[0]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[0], w_16, stereo); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], true, mix); - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], false, mix); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix); + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], stereo, false, mix); snd_emu10k1_playback_set_running(emu, epcm); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); snd_emu10k1_playback_trigger_voice(emu, epcm->extra); @@ -782,13 +776,13 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_START: /* prepare voices */ for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i]); + snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i], true, false); } fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, true, &emu->efx_pcm_mixer[i]); snd_emu10k1_playback_set_running(emu, epcm); -- cgit v1.2.3 From 9581128a213461cf2f82dd09558b7066d363360c Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:55 +0200 Subject: ALSA: emu10k1: refactor PCM playback cache filling Rename snd_emu10k1_playback_invalidate_cache() to the more apt snd_emu10k1_playback_fill_cache(), and factor out snd_emu10k1_playback_prepare_voices(), which calls the former for all channels. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index bbe054be2448..063918397a5f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -511,16 +511,12 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) return 0; } -static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, - struct snd_emu10k1_voice *evoice, - bool w_16, bool stereo) +static void snd_emu10k1_playback_fill_cache(struct snd_emu10k1 *emu, + unsigned voice, + u32 sample, bool stereo) { - unsigned voice, sample; u32 ccr; - voice = evoice->number; - sample = w_16 ? 0 : 0x80808080; - // We assume that the cache is resting at this point (i.e., // CCR_CACHEINVALIDSIZE is very small). @@ -539,6 +535,22 @@ static void snd_emu10k1_playback_invalidate_cache(struct snd_emu10k1 *emu, snd_emu10k1_ptr_write(emu, CCR, voice, ccr); } +static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + bool w_16, bool stereo, + int channels) +{ + u32 sample = w_16 ? 0 : 0x80808080; + + for (int i = 0; i < channels; i++) { + unsigned voice = epcm->voices[i]->number; + snd_emu10k1_playback_fill_cache(emu, voice, sample, stereo); + } + + // It takes a moment until the cache fills complete, + // but the unmuting takes long enough for that. +} + static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, unsigned int vattn) @@ -632,7 +644,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_invalidate_cache(emu, epcm->voices[0], w_16, stereo); + snd_emu10k1_playback_prepare_voices(emu, epcm, w_16, stereo, 1); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: @@ -774,10 +786,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, spin_lock(&emu->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - /* prepare voices */ - for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_invalidate_cache(emu, epcm->voices[i], true, false); - } + snd_emu10k1_playback_prepare_voices(emu, epcm, true, false, NUM_EFX_PLAYBACK); fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: -- cgit v1.2.3 From fa75064d92fdec157d75375bca06c77fb30c25df Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Wed, 17 May 2023 19:42:56 +0200 Subject: ALSA: emu10k1: refactor PCM playback address handling Pull the special handling of extra voices out of snd_emu10k1_pcm_init_voice(), simplify snd_emu10k1_playback_pointer(), and make the logic overall clearer. Also, add verbose comments. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230517174256.3657060-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 81 +++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 29 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 063918397a5f..89f7e85034b6 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -269,9 +269,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, memcpy(send_amount, &mix->send_volume[tmp][0], 8); } - if (master) { - evoice->epcm->ccca_start_addr = start_addr + 64 - 3; - } if (stereo) { // Not really necessary for the slave, but it doesn't hurt snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); @@ -299,12 +296,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else pitch_target = emu10k1_calc_pitch_target(runtime->rate); - if (extra) - snd_emu10k1_ptr_write(emu, CCCA, voice, (end_addr - 3) | - emu10k1_select_interprom(pitch_target) | - (w_16 ? 0 : CCCA_8BITSELECT)); - else - snd_emu10k1_ptr_write(emu, CCCA, voice, (start_addr + 64 - 3) | + snd_emu10k1_ptr_write(emu, CCCA, voice, emu10k1_select_interprom(pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT)); /* Clear filter delay memory */ @@ -401,6 +393,7 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, w_16, false, start_addr, end_addr, NULL); start_addr >>= stereo; + epcm->ccca_start_addr = start_addr; end_addr = start_addr + runtime->buffer_size; snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, start_addr, end_addr, @@ -428,13 +421,8 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, true, false, start_addr, start_addr + (channel_size / 2), NULL); - /* only difference with the master voice is we use it for the pointer */ - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], true, false, - start_addr, start_addr + channel_size, - &emu->efx_pcm_mixer[0]); - - start_addr += channel_size; - for (i = 1; i < NUM_EFX_PLAYBACK; i++) { + epcm->ccca_start_addr = start_addr; + for (i = 0; i < NUM_EFX_PLAYBACK; i++) { snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], true, false, start_addr, start_addr + channel_size, &emu->efx_pcm_mixer[i]); @@ -540,13 +528,45 @@ static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, bool w_16, bool stereo, 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; u32 sample = w_16 ? 0 : 0x80808080; + // 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 += 64 - 3; 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); } + // The interrupt is triggered when CCCA_CURRADDR (CA) wraps around, + // which is ahead of the actual playback position, so the interrupt + // source needs to be delayed. + // + // In principle, this wouldn't need to be the cache's entire size - in + // practice, CCR_CACHEINVALIDSIZE (CIS) > `fetch threshold` has never + // been observed, and assuming 40 _bytes_ should be safe. + // + // The cache fills are somewhat random, which makes it impossible to + // align them with the interrupts. This makes a non-delayed interrupt + // source not practical, as the interrupt handler would have to wait + // for (CA - CIS) >= period_boundary for every channel in the stream. + // + // This is why all other (open) drivers for these chips use timer-based + // interrupts. + // + eloop_start += eloop_size - 3; + snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, epcm->extra->number, eloop_start); + // It takes a moment until the cache fills complete, // but the unmuting takes long enough for that. } @@ -746,24 +766,27 @@ 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; - unsigned int ptr; + int ptr; if (!epcm->running) return 0; + ptr = snd_emu10k1_ptr_read(emu, CCCA, epcm->voices[0]->number) & 0x00ffffff; -#if 0 /* Perex's code */ - ptr += runtime->buffer_size; ptr -= epcm->ccca_start_addr; - ptr %= runtime->buffer_size; -#else /* EMU10K1 Open Source code from Creative */ - if (ptr < epcm->ccca_start_addr) - ptr += runtime->buffer_size - epcm->ccca_start_addr; - else { - ptr -= epcm->ccca_start_addr; - if (ptr >= runtime->buffer_size) - ptr -= runtime->buffer_size; - } -#endif + + // This is the size of the whole cache minus the interpolator read-ahead, + // which leads us to the actual playback position. + // + // The cache is constantly kept mostly filled, so in principle we could + // return a more advanced position representing how far the hardware has + // already read the buffer, and set runtime->delay accordingly. However, + // this would be slightly different for every channel (and remarkably slow + // to obtain), so only a fixed worst-case value would be practical. + // + ptr -= 64 - 3; + if (ptr < 0) + ptr += runtime->buffer_size; + /* dev_dbg(emu->card->dev, "ptr = 0x%lx, buffer_size = 0x%lx, period_size = 0x%lx\n", -- cgit v1.2.3 From 0be0a62fd08414e3cd67c8fb898b2bb9e74eb225 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:22:24 +0200 Subject: ALSA: emu10k1: fix PCM playback buffer size constraints The period_bytes_min parameter and the buffer_bytes minimum constraint made no sense at all, as they didn't reflect any hardware limitation. Instead, apply a frame-based period_size minimum constraint, which is derived from the cache size (it would be actually possible to go below that, but it would require special handling, and it would be practically impossible to keep up with the IRQ rate anyway). Sync up the constraints of the EFX playback with those of the regular playback, as there is no reason for them to diverge. N.b., the maximum buffer size is actually arbitrary - the hardware could go waay beyond 128 KiB. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518092224.3696958-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 89f7e85034b6..9045359bb461 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -444,9 +444,8 @@ static const struct snd_pcm_hardware snd_emu10k1_efx_playback = .rate_max = 48000, .channels_min = NUM_EFX_PLAYBACK, .channels_max = NUM_EFX_PLAYBACK, - .buffer_bytes_max = (64*1024), - .period_bytes_min = 64, - .period_bytes_max = (64*1024), + .buffer_bytes_max = (128*1024), + .period_bytes_max = (128*1024), .periods_min = 2, .periods_max = 2, .fifo_size = 0, @@ -877,7 +876,6 @@ static const struct snd_pcm_hardware snd_emu10k1_playback = .channels_min = 1, .channels_max = 2, .buffer_bytes_max = (128*1024), - .period_bytes_min = 64, .period_bytes_max = (128*1024), .periods_min = 1, .periods_max = 1024, @@ -982,13 +980,29 @@ 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) +{ + 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); + return err; +} + static int snd_emu10k1_efx_playback_open(struct snd_pcm_substream *substream) { struct snd_emu10k1 *emu = snd_pcm_substream_chip(substream); struct snd_emu10k1_pcm *epcm; struct snd_emu10k1_pcm_mixer *mix; struct snd_pcm_runtime *runtime = substream->runtime; - int i, j; + int i, j, err; epcm = kzalloc(sizeof(*epcm), GFP_KERNEL); if (epcm == NULL) @@ -1000,7 +1014,12 @@ 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; - + err = snd_emu10k1_playback_set_constraints(runtime); + if (err < 0) { + kfree(epcm); + return err; + } + for (i = 0; i < NUM_EFX_PLAYBACK; i++) { mix = &emu->efx_pcm_mixer[i]; for (j = 0; j < 8; j++) @@ -1031,12 +1050,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_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - if (err < 0) { - kfree(epcm); - return err; - } - err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX); + err = snd_emu10k1_playback_set_constraints(runtime); if (err < 0) { kfree(epcm); return err; -- cgit v1.2.3 From 583307bafb264a1a42a1ffc8cdf6493f9deda414 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:44 +0200 Subject: ALSA: emu10k1: simplify interrupt handler, part 1 IPR_CHANNELNUMBERMASK cannot be non-zero when IPR_CHANNELLOOP is unset, so join marking them as handled. This logically reverts part of commit f453e20d8a0 ("ALSA update 0.9.3a"), which made the inverse change with no explanation. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index dfb44e5e69a7..0cb89bd8c16b 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -79,9 +79,8 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) val >>= 1; pvoice++; } - status &= ~IPR_CHANNELLOOP; + status &= ~(IPR_CHANNELLOOP | IPR_CHANNELNUMBERMASK); } - status &= ~IPR_CHANNELNUMBERMASK; if (status & (IPR_ADCBUFFULL|IPR_ADCBUFHALFFULL)) { if (emu->capture_interrupt) emu->capture_interrupt(emu, status); -- cgit v1.2.3 From 016027741f97457087b81bf304f1cb807bdeffe0 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:45 +0200 Subject: ALSA: emu10k1: simplify interrupt handler, part 2 Remove weird INTE_* clearing code. The bits were a subset of the actually handled interrupts, which kind of contradicted the stated purpose. I suppose it would make sense to complete the set and negate it, but interrupts being enabled out of the blue is neither something that happens a lot, nor should it result in just one error message, IMO. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index 0cb89bd8c16b..312511300053 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -146,26 +146,8 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) } if (status) { - unsigned int bits; dev_err(emu->card->dev, "unhandled interrupt: 0x%08x\n", status); - //make sure any interrupts we don't handle are disabled: - bits = INTE_FXDSPENABLE | - INTE_PCIERRORENABLE | - INTE_VOLINCRENABLE | - INTE_VOLDECRENABLE | - INTE_MUTEENABLE | - INTE_MICBUFENABLE | - INTE_ADCBUFENABLE | - INTE_EFXBUFENABLE | - INTE_GPSPDIFENABLE | - INTE_CDSPDIFENABLE | - INTE_INTERVALTIMERENB | - INTE_MIDITXENABLE | - INTE_MIDIRXENABLE; - if (emu->audigy) - bits |= INTE_A_MIDITXENABLE2 | INTE_A_MIDIRXENABLE2; - snd_emu10k1_intr_disable(emu, bits); } outl(orig_status, emu->port + IPR); /* ack all */ } -- cgit v1.2.3 From 9436f0151d30b80873eda80341304524db9e8149 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:46 +0200 Subject: ALSA: emu10k1: simplify interrupt handler, part 3 Handle the "timeout" (actually the retry counter) such that it's more obvious and causes less cost in the normal case. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index 312511300053..7dc803aaa850 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -22,15 +22,18 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) int handled = 0; int timeout = 0; - while (((status = inl(emu->port + IPR)) != 0) && (timeout < 1000)) { - timeout++; - orig_status = status; + while ((status = inl(emu->port + IPR)) != 0) { handled = 1; if ((status & 0xffffffff) == 0xffffffff) { dev_info(emu->card->dev, "Suspected sound card removal\n"); break; } + if (++timeout == 1000) { + dev_info(emu->card->dev, "emu10k1 irq routine failure\n"); + break; + } + orig_status = status; if (status & IPR_PCIERROR) { dev_err(emu->card->dev, "interrupt: PCI error\n"); snd_emu10k1_intr_disable(emu, INTE_PCIERRORENABLE); @@ -151,8 +154,6 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) } outl(orig_status, emu->port + IPR); /* ack all */ } - if (timeout == 1000) - dev_info(emu->card->dev, "emu10k1 irq routine failure\n"); return IRQ_RETVAL(handled); } -- cgit v1.2.3 From 6797400ef4abb4359c225a207c1f3ca28591f51c Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:30:47 +0200 Subject: ALSA: emu10k1: fix handling of half-loop interrupts We'd try to iterate the voices twice without resetting the pointer. This went unnoticed, because the code isn't actually in use. Amends commit 27ae958cf6 ("emu10k1 driver - add multichannel device hw:x,3 [2-8/8]"). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093047.3697887-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/irq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/irq.c b/sound/pci/emu10k1/irq.c index 7dc803aaa850..a813ef8c2f8d 100644 --- a/sound/pci/emu10k1/irq.c +++ b/sound/pci/emu10k1/irq.c @@ -47,12 +47,13 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) status &= ~(IPR_VOLINCR|IPR_VOLDECR|IPR_MUTE); } if (status & IPR_CHANNELLOOP) { + struct snd_emu10k1_voice *pvoice; int voice; int voice_max = status & IPR_CHANNELNUMBERMASK; u32 val; - struct snd_emu10k1_voice *pvoice = emu->voices; val = snd_emu10k1_ptr_read(emu, CLIPL, 0); + pvoice = emu->voices; for (voice = 0; voice <= voice_max; voice++) { if (voice == 0x20) val = snd_emu10k1_ptr_read(emu, CLIPH, 0); @@ -68,6 +69,7 @@ irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id) pvoice++; } val = snd_emu10k1_ptr_read(emu, HLIPL, 0); + pvoice = emu->voices; for (voice = 0; voice <= voice_max; voice++) { if (voice == 0x20) val = snd_emu10k1_ptr_read(emu, HLIPH, 0); -- cgit v1.2.3 From 46055699e5f81db8c70946609f445c572983eca5 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 11:31:34 +0200 Subject: ALSA: emu10k1: introduce and use snd_emu10k1_ptr_write_multiple() While this nicely denoises the code, the real intent is being able to write many registers pseudo-atomically, which will come in handy later. Idea stolen from kX-project. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518093134.3697955-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 4 + sound/pci/emu10k1/emu10k1_callback.c | 209 +++++++++++++++++------------------ sound/pci/emu10k1/emu10k1_main.c | 168 +++++++++++++++------------- sound/pci/emu10k1/emumixer.c | 8 +- sound/pci/emu10k1/emupcm.c | 108 +++++++++--------- sound/pci/emu10k1/io.c | 31 ++++++ 6 files changed, 293 insertions(+), 235 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index ee662a1b0dc7..9c5de1f45566 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -64,6 +64,9 @@ #define REG_VAL_GET(r, v) ((v & REG_MASK(r)) >> REG_SHIFT(r)) #define REG_VAL_PUT(r, v) ((v) << REG_SHIFT(r)) +// List terminator for snd_emu10k1_ptr_write_multiple() +#define REGLIST_END ~0 + // Audigy specify registers are prefixed with 'A_' /************************************************************************************************/ @@ -1793,6 +1796,7 @@ int snd_emu10k1_done(struct snd_emu10k1 * emu); /* I/O functions */ unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); +void snd_emu10k1_ptr_write_multiple(struct snd_emu10k1 *emu, unsigned int chn, ...); unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); int snd_emu10k1_spi_write(struct snd_emu10k1 * emu, unsigned int data); diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 9455df18f7b2..06440b97b5d7 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -33,9 +33,8 @@ static void release_voice(struct snd_emux_voice *vp); static void update_voice(struct snd_emux_voice *vp, int update); static void terminate_voice(struct snd_emux_voice *vp); static void free_voice(struct snd_emux_voice *vp); -static void set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); -static void set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); -static void set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp); +static u32 make_fmmod(struct snd_emux_voice *vp); +static u32 make_fm2frq2(struct snd_emux_voice *vp); /* * Ensure a value is between two points @@ -116,14 +115,13 @@ snd_emu10k1_synth_get_voice(struct snd_emu10k1 *hw) static void release_voice(struct snd_emux_voice *vp) { - int dcysusv; struct snd_emu10k1 *hw; hw = vp->hw; - dcysusv = (unsigned char)vp->reg.parm.modrelease | DCYSUSM_PHASE1_MASK; - snd_emu10k1_ptr_write(hw, DCYSUSM, vp->ch, dcysusv); - dcysusv = (unsigned char)vp->reg.parm.volrelease | DCYSUSV_PHASE1_MASK | DCYSUSV_CHANNELENABLE_MASK; - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, dcysusv); + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + DCYSUSM, (unsigned char)vp->reg.parm.modrelease | DCYSUSM_PHASE1_MASK, + DCYSUSV, (unsigned char)vp->reg.parm.volrelease | DCYSUSV_PHASE1_MASK | DCYSUSV_CHANNELENABLE_MASK, + REGLIST_END); } @@ -192,13 +190,13 @@ update_voice(struct snd_emux_voice *vp, int update) snd_emu10k1_ptr_write(hw, PTRX_FXSENDAMOUNT_B, vp->ch, vp->aaux); } if (update & SNDRV_EMUX_UPDATE_FMMOD) - set_fmmod(hw, vp); + snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, make_fmmod(vp)); if (update & SNDRV_EMUX_UPDATE_TREMFREQ) snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq); if (update & SNDRV_EMUX_UPDATE_FM2FRQ2) - set_fm2frq2(hw, vp); + snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, make_fm2frq2(vp)); if (update & SNDRV_EMUX_UPDATE_Q) - set_filterQ(hw, vp); + snd_emu10k1_ptr_write(hw, CCCA_RESONANCE, vp->ch, vp->reg.parm.filterQ); } @@ -310,6 +308,7 @@ start_voice(struct snd_emux_voice *vp) { unsigned int temp; int ch; + u32 psst, dsl, map, ccca, vtarget; unsigned int addr, mapped_offset; struct snd_midi_channel *chan; struct snd_emu10k1 *hw; @@ -347,66 +346,93 @@ start_voice(struct snd_emux_voice *vp) snd_emu10k1_ptr_write(hw, FXRT, ch, temp); } - /* channel to be silent and idle */ - snd_emu10k1_ptr_write(hw, DCYSUSV, ch, 0); - snd_emu10k1_ptr_write(hw, VTFT, ch, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(hw, CVCF, ch, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(hw, PTRX, ch, 0); - snd_emu10k1_ptr_write(hw, CPF, ch, 0); - - /* set pitch offset */ - snd_emu10k1_ptr_write(hw, IP, vp->ch, vp->apitch); - - /* set envelope parameters */ - snd_emu10k1_ptr_write(hw, ENVVAL, ch, vp->reg.parm.moddelay); - snd_emu10k1_ptr_write(hw, ATKHLDM, ch, vp->reg.parm.modatkhld); - snd_emu10k1_ptr_write(hw, DCYSUSM, ch, vp->reg.parm.moddcysus); - snd_emu10k1_ptr_write(hw, ENVVOL, ch, vp->reg.parm.voldelay); - snd_emu10k1_ptr_write(hw, ATKHLDV, ch, vp->reg.parm.volatkhld); - /* decay/sustain parameter for volume envelope is used - for triggerg the voice */ - - /* cutoff and volume */ - temp = (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol; - snd_emu10k1_ptr_write(hw, IFATN, vp->ch, temp); - - /* modulation envelope heights */ - snd_emu10k1_ptr_write(hw, PEFE, ch, vp->reg.parm.pefe); - - /* lfo1/2 delay */ - snd_emu10k1_ptr_write(hw, LFOVAL1, ch, vp->reg.parm.lfo1delay); - snd_emu10k1_ptr_write(hw, LFOVAL2, ch, vp->reg.parm.lfo2delay); - - /* lfo1 pitch & cutoff shift */ - set_fmmod(hw, vp); - /* lfo1 volume & freq */ - snd_emu10k1_ptr_write(hw, TREMFRQ, vp->ch, vp->reg.parm.tremfrq); - /* lfo2 pitch & freq */ - set_fm2frq2(hw, vp); - - /* reverb and loop start (reverb 8bit, MSB) */ temp = vp->reg.parm.reverb; temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10; LIMITMAX(temp, 255); addr = vp->reg.loopstart; - snd_emu10k1_ptr_write(hw, PSST, vp->ch, (temp << 24) | addr); + psst = (temp << 24) | addr; - /* chorus & loop end (chorus 8bit, MSB) */ addr = vp->reg.loopend; temp = vp->reg.parm.chorus; temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10; LIMITMAX(temp, 255); - temp = (temp <<24) | addr; - snd_emu10k1_ptr_write(hw, DSL, ch, temp); + dsl = (temp << 24) | addr; - /* clear filter delay memory */ - snd_emu10k1_ptr_write(hw, Z1, ch, 0); - snd_emu10k1_ptr_write(hw, Z2, ch, 0); + map = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - /* invalidate maps */ - temp = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - snd_emu10k1_ptr_write(hw, MAPA, ch, temp); - snd_emu10k1_ptr_write(hw, MAPB, ch, temp); + addr = vp->reg.start; + temp = vp->reg.parm.filterQ; + ccca = (temp << 28) | addr; + if (vp->apitch < 0xe400) + ccca |= CCCA_INTERPROM_0; + else { + unsigned int shift = (vp->apitch - 0xe000) >> 10; + ccca |= shift << 25; + } + if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) + ccca |= CCCA_8BITSELECT; + + vtarget = (unsigned int)vp->vtarget << 16; + + snd_emu10k1_ptr_write_multiple(hw, ch, + /* channel to be silent and idle */ + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + + /* set pitch offset */ + IP, vp->apitch, + + /* set envelope parameters */ + ENVVAL, vp->reg.parm.moddelay, + ATKHLDM, vp->reg.parm.modatkhld, + DCYSUSM, vp->reg.parm.moddcysus, + ENVVOL, vp->reg.parm.voldelay, + ATKHLDV, vp->reg.parm.volatkhld, + /* decay/sustain parameter for volume envelope is used + for triggerg the voice */ + + /* cutoff and volume */ + IFATN, (unsigned int)vp->acutoff << 8 | (unsigned char)vp->avol, + + /* modulation envelope heights */ + PEFE, vp->reg.parm.pefe, + + /* lfo1/2 delay */ + LFOVAL1, vp->reg.parm.lfo1delay, + LFOVAL2, vp->reg.parm.lfo2delay, + + /* lfo1 pitch & cutoff shift */ + FMMOD, make_fmmod(vp), + /* lfo1 volume & freq */ + TREMFRQ, vp->reg.parm.tremfrq, + /* lfo2 pitch & freq */ + FM2FRQ2, make_fm2frq2(vp), + + /* reverb and loop start (reverb 8bit, MSB) */ + PSST, psst, + + /* chorus & loop end (chorus 8bit, MSB) */ + DSL, dsl, + + /* clear filter delay memory */ + Z1, 0, + Z2, 0, + + /* invalidate maps */ + MAPA, map, + MAPB, map, + + /* Q & current address (Q 4bit value, MSB) */ + CCCA, ccca, + + /* reset volume */ + VTFT, vtarget | vp->ftarget, + CVCF, vtarget | CVCF_CURRENTFILTER_MASK, + + REGLIST_END); #if 0 /* cache */ { @@ -437,24 +463,6 @@ start_voice(struct snd_emux_voice *vp) } #endif - /* Q & current address (Q 4bit value, MSB) */ - addr = vp->reg.start; - temp = vp->reg.parm.filterQ; - temp = (temp<<28) | addr; - if (vp->apitch < 0xe400) - temp |= CCCA_INTERPROM_0; - else { - unsigned int shift = (vp->apitch - 0xe000) >> 10; - temp |= shift << 25; - } - if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) - temp |= CCCA_8BITSELECT; - snd_emu10k1_ptr_write(hw, CCCA, ch, temp); - - /* reset volume */ - temp = (unsigned int)vp->vtarget << 16; - snd_emu10k1_ptr_write(hw, VTFT, ch, temp | vp->ftarget); - snd_emu10k1_ptr_write(hw, CVCF, ch, temp | CVCF_CURRENTFILTER_MASK); return 0; } @@ -464,7 +472,7 @@ start_voice(struct snd_emux_voice *vp) static void trigger_voice(struct snd_emux_voice *vp) { - unsigned int temp, ptarget; + unsigned int ptarget; struct snd_emu10k1 *hw; struct snd_emu10k1_memblk *emem; @@ -479,24 +487,25 @@ trigger_voice(struct snd_emux_voice *vp) #else ptarget = IP_TO_CP(vp->apitch); #endif - /* set pitch target and pan (volume) */ - temp = ptarget | (vp->apan << 8) | vp->aaux; - snd_emu10k1_ptr_write(hw, PTRX, vp->ch, temp); + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + /* set pitch target and pan (volume) */ + PTRX, ptarget | (vp->apan << 8) | vp->aaux, + + /* current pitch and fractional address */ + CPF, ptarget, - /* pitch target */ - snd_emu10k1_ptr_write(hw, CPF, vp->ch, ptarget); + /* enable envelope engine */ + DCYSUSV, vp->reg.parm.voldcysus | DCYSUSV_CHANNELENABLE_MASK, - /* trigger voice */ - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, vp->reg.parm.voldcysus|DCYSUSV_CHANNELENABLE_MASK); + REGLIST_END); } #define MOD_SENSE 18 -/* set lfo1 modulation height and cutoff */ -static void -set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) +/* calculate lfo1 modulation height and cutoff register */ +static u32 +make_fmmod(struct snd_emux_voice *vp) { - unsigned short fmmod; short pitch; unsigned char cutoff; int modulation; @@ -506,15 +515,13 @@ set_fmmod(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; pitch += (MOD_SENSE * modulation) / 1200; LIMITVALUE(pitch, -128, 127); - fmmod = ((unsigned char)pitch<<8) | cutoff; - snd_emu10k1_ptr_write(hw, FMMOD, vp->ch, fmmod); + return ((unsigned char)pitch << 8) | cutoff; } -/* set lfo2 pitch & frequency */ -static void -set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) +/* calculate set lfo2 pitch & frequency register */ +static u32 +make_fm2frq2(struct snd_emux_voice *vp) { - unsigned short fm2frq2; short pitch; unsigned char freq; int modulation; @@ -524,13 +531,5 @@ set_fm2frq2(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; pitch += (MOD_SENSE * modulation) / 1200; LIMITVALUE(pitch, -128, 127); - fm2frq2 = ((unsigned char)pitch<<8) | freq; - snd_emu10k1_ptr_write(hw, FM2FRQ2, vp->ch, fm2frq2); -} - -/* set filterQ */ -static void -set_filterQ(struct snd_emu10k1 *hw, struct snd_emux_voice *vp) -{ - snd_emu10k1_ptr_write(hw, CCCA_RESONANCE, vp->ch, vp->reg.parm.filterQ); + return ((unsigned char)pitch << 8) | freq; } diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index da7c988b5c97..65207ef689cb 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -57,44 +57,49 @@ MODULE_FIRMWARE(EMU1010_NOTEBOOK_FILENAME); void snd_emu10k1_voice_init(struct snd_emu10k1 *emu, int ch) { - snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0); - snd_emu10k1_ptr_write(emu, VTFT, ch, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, ch, CVCF_CURRENTFILTER_MASK); - snd_emu10k1_ptr_write(emu, PTRX, ch, 0); - snd_emu10k1_ptr_write(emu, CPF, ch, 0); - snd_emu10k1_ptr_write(emu, CCR, ch, 0); - - snd_emu10k1_ptr_write(emu, PSST, ch, 0); - snd_emu10k1_ptr_write(emu, DSL, ch, 0x10); - snd_emu10k1_ptr_write(emu, CCCA, ch, 0); - snd_emu10k1_ptr_write(emu, Z1, ch, 0); - snd_emu10k1_ptr_write(emu, Z2, ch, 0); - snd_emu10k1_ptr_write(emu, FXRT, ch, 0x32100000); - - // The rest is meaningless as long as DCYSUSV_CHANNELENABLE_MASK is zero - snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0); - snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0); - snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0); - snd_emu10k1_ptr_write(emu, IP, ch, 0); - snd_emu10k1_ptr_write(emu, IFATN, ch, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK); - snd_emu10k1_ptr_write(emu, PEFE, ch, 0); - snd_emu10k1_ptr_write(emu, FMMOD, ch, 0); - snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24); /* 1 Hz */ - snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24); /* 1 Hz */ - snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0); - snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0); - snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0); - snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0); + snd_emu10k1_ptr_write_multiple(emu, ch, + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + CCR, 0, + + PSST, 0, + DSL, 0x10, + CCCA, 0, + Z1, 0, + Z2, 0, + FXRT, 0x32100000, + + // The rest is meaningless as long as DCYSUSV_CHANNELENABLE_MASK is zero + DCYSUSM, 0, + ATKHLDV, 0, + ATKHLDM, 0, + IP, 0, + IFATN, IFATN_FILTERCUTOFF_MASK | IFATN_ATTENUATION_MASK, + PEFE, 0, + FMMOD, 0, + TREMFRQ, 24, /* 1 Hz */ + FM2FRQ2, 24, /* 1 Hz */ + LFOVAL2, 0, + LFOVAL1, 0, + ENVVOL, 0, + ENVVAL, 0, + + REGLIST_END); /* Audigy extra stuffs */ if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_CSBA, ch, 0); - snd_emu10k1_ptr_write(emu, A_CSDC, ch, 0); - snd_emu10k1_ptr_write(emu, A_CSFE, ch, 0); - snd_emu10k1_ptr_write(emu, A_CSHG, ch, 0); - snd_emu10k1_ptr_write(emu, A_FXRT1, ch, 0x03020100); - snd_emu10k1_ptr_write(emu, A_FXRT2, ch, 0x07060504); - snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, ch, 0); + snd_emu10k1_ptr_write_multiple(emu, ch, + A_CSBA, 0, + A_CSDC, 0, + A_CSFE, 0, + A_CSHG, 0, + A_FXRT1, 0x03020100, + A_FXRT2, 0x07060504, + A_SENDAMOUNTS, 0, + REGLIST_END); } } @@ -148,22 +153,26 @@ static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir) outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG); - /* reset recording buffers */ - snd_emu10k1_ptr_write(emu, MICBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, MICBA, 0, 0); - snd_emu10k1_ptr_write(emu, FXBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, FXBA, 0, 0); - snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, ADCBA, 0, 0); - - /* disable channel interrupt */ outl(0, emu->port + INTE); - snd_emu10k1_ptr_write(emu, CLIEL, 0, 0); - snd_emu10k1_ptr_write(emu, CLIEH, 0, 0); - /* disable stop on loop end */ - snd_emu10k1_ptr_write(emu, SOLEL, 0, 0); - snd_emu10k1_ptr_write(emu, SOLEH, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + /* reset recording buffers */ + MICBS, ADCBS_BUFSIZE_NONE, + MICBA, 0, + FXBS, ADCBS_BUFSIZE_NONE, + FXBA, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + + /* disable channel interrupt */ + CLIEL, 0, + CLIEH, 0, + + /* disable stop on loop end */ + SOLEL, 0, + SOLEH, 0, + + REGLIST_END); if (emu->audigy) { /* set SPDIF bypass mode */ @@ -177,9 +186,11 @@ static int snd_emu10k1_init(struct snd_emu10k1 *emu, int enable_ir) for (ch = 0; ch < NUM_G; ch++) snd_emu10k1_voice_init(emu, ch); - snd_emu10k1_ptr_write(emu, SPCS0, 0, emu->spdif_bits[0]); - snd_emu10k1_ptr_write(emu, SPCS1, 0, emu->spdif_bits[1]); - snd_emu10k1_ptr_write(emu, SPCS2, 0, emu->spdif_bits[2]); + snd_emu10k1_ptr_write_multiple(emu, 0, + SPCS0, emu->spdif_bits[0], + SPCS1, emu->spdif_bits[1], + SPCS2, emu->spdif_bits[2], + REGLIST_END); if (emu->card_capabilities->emu_model) { } else if (emu->card_capabilities->ca0151_chip) { /* audigy2 */ @@ -390,41 +401,48 @@ int snd_emu10k1_done(struct snd_emu10k1 *emu) outl(0, emu->port + INTE); /* - * Shutdown the chip + * Shutdown the voices */ - for (ch = 0; ch < NUM_G; ch++) - snd_emu10k1_ptr_write(emu, DCYSUSV, ch, 0); for (ch = 0; ch < NUM_G; ch++) { - snd_emu10k1_ptr_write(emu, VTFT, ch, 0); - snd_emu10k1_ptr_write(emu, CVCF, ch, 0); - snd_emu10k1_ptr_write(emu, PTRX, ch, 0); - snd_emu10k1_ptr_write(emu, CPF, ch, 0); + snd_emu10k1_ptr_write_multiple(emu, ch, + DCYSUSV, 0, + VTFT, 0, + CVCF, 0, + PTRX, 0, + CPF, 0, + REGLIST_END); } - /* reset recording buffers */ - snd_emu10k1_ptr_write(emu, MICBS, 0, 0); - snd_emu10k1_ptr_write(emu, MICBA, 0, 0); - snd_emu10k1_ptr_write(emu, FXBS, 0, 0); - snd_emu10k1_ptr_write(emu, FXBA, 0, 0); - snd_emu10k1_ptr_write(emu, FXWC, 0, 0); - snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE); - snd_emu10k1_ptr_write(emu, ADCBA, 0, 0); - snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_16K); - snd_emu10k1_ptr_write(emu, TCB, 0, 0); + // stop the DSP if (emu->audigy) snd_emu10k1_ptr_write(emu, A_DBG, 0, A_DBG_SINGLE_STEP); else snd_emu10k1_ptr_write(emu, DBG, 0, EMU10K1_DBG_SINGLE_STEP); - /* disable channel interrupt */ - snd_emu10k1_ptr_write(emu, CLIEL, 0, 0); - snd_emu10k1_ptr_write(emu, CLIEH, 0, 0); - snd_emu10k1_ptr_write(emu, SOLEL, 0, 0); - snd_emu10k1_ptr_write(emu, SOLEH, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + /* reset recording buffers */ + MICBS, 0, + MICBA, 0, + FXBS, 0, + FXBA, 0, + FXWC, 0, + ADCBS, ADCBS_BUFSIZE_NONE, + ADCBA, 0, + TCBS, TCBS_BUFFSIZE_16K, + TCB, 0, + + /* disable channel interrupt */ + CLIEL, 0, + CLIEH, 0, + SOLEL, 0, + SOLEH, 0, + + PTB, 0, + + REGLIST_END); /* disable audio and lock cache */ outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK | HCFG_MUTEBUTTONENABLE, emu->port + HCFG); - snd_emu10k1_ptr_write(emu, PTB, 0, 0); return 0; } diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 3a7f25f81504..183051e846f2 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1396,10 +1396,10 @@ static const struct snd_kcontrol_new snd_emu10k1_spdif_control = static void update_emu10k1_fxrt(struct snd_emu10k1 *emu, int voice, unsigned char *route) { if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXRT1, voice, - snd_emu10k1_compose_audigy_fxrt1(route)); - snd_emu10k1_ptr_write(emu, A_FXRT2, voice, - snd_emu10k1_compose_audigy_fxrt2(route)); + snd_emu10k1_ptr_write_multiple(emu, voice, + A_FXRT1, snd_emu10k1_compose_audigy_fxrt1(route), + A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(route), + REGLIST_END); } else { snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(route)); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 9045359bb461..1ca16f0ddbed 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -268,47 +268,43 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, memcpy(send_routing, &mix->send_routing[tmp][0], 8); memcpy(send_amount, &mix->send_volume[tmp][0], 8); } - - if (stereo) { + if (emu->card_capabilities->emu_model) + pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ + else + pitch_target = emu10k1_calc_pitch_target(runtime->rate); + silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | + (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); + snd_emu10k1_ptr_write_multiple(emu, voice, // Not really necessary for the slave, but it doesn't hurt - snd_emu10k1_ptr_write(emu, CPF, voice, CPF_STEREO_MASK); - } else { - snd_emu10k1_ptr_write(emu, CPF, voice, 0); - } - - /* setup routing */ + CPF, stereo ? CPF_STEREO_MASK : 0, + // Assumption that PT is already 0 so no harm overwriting + PTRX, (send_amount[0] << 8) | send_amount[1], + // Stereo slaves don't need to have the addresses set, but it doesn't hurt + DSL, end_addr | (send_amount[3] << 24), + PSST, start_addr | (send_amount[2] << 24), + CCCA, emu10k1_select_interprom(pitch_target) | + (w_16 ? 0 : CCCA_8BITSELECT), + // Clear filter delay memory + Z1, 0, + Z2, 0, + // Invalidate maps + MAPA, silent_page, + MAPB, silent_page, + // Disable filter (in conjunction with CCCA_RESONANCE == 0) + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + REGLIST_END); + // Setup routing if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXRT1, voice, - snd_emu10k1_compose_audigy_fxrt1(send_routing)); - snd_emu10k1_ptr_write(emu, A_FXRT2, voice, - snd_emu10k1_compose_audigy_fxrt2(send_routing)); - snd_emu10k1_ptr_write(emu, A_SENDAMOUNTS, voice, - snd_emu10k1_compose_audigy_sendamounts(send_amount)); - } else + 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_SENDAMOUNTS, snd_emu10k1_compose_audigy_sendamounts(send_amount), + REGLIST_END); + } else { snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(send_routing)); - /* Assumption that PT is already 0 so no harm overwriting */ - snd_emu10k1_ptr_write(emu, PTRX, voice, (send_amount[0] << 8) | send_amount[1]); - // Stereo slaves don't need to have the addresses set, but it doesn't hurt - snd_emu10k1_ptr_write(emu, DSL, voice, end_addr | (send_amount[3] << 24)); - snd_emu10k1_ptr_write(emu, PSST, voice, start_addr | (send_amount[2] << 24)); - if (emu->card_capabilities->emu_model) - pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ - else - pitch_target = emu10k1_calc_pitch_target(runtime->rate); - snd_emu10k1_ptr_write(emu, CCCA, voice, - emu10k1_select_interprom(pitch_target) | - (w_16 ? 0 : CCCA_8BITSELECT)); - /* Clear filter delay memory */ - snd_emu10k1_ptr_write(emu, Z1, voice, 0); - snd_emu10k1_ptr_write(emu, Z2, voice, 0); - /* invalidate maps */ - silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - snd_emu10k1_ptr_write(emu, MAPA, voice, silent_page); - snd_emu10k1_ptr_write(emu, MAPB, voice, silent_page); - // Disable filter (in conjunction with CCCA_RESONANCE == 0) - snd_emu10k1_ptr_write(emu, VTFT, voice, VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, voice, CVCF_CURRENTFILTER_MASK); + } spin_unlock_irqrestore(&emu->reg_lock, flags); } @@ -466,8 +462,10 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) break; case CAPTURE_EFX: if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0); - snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, 0, + A_FXWC2, 0, + REGLIST_END); } else snd_emu10k1_ptr_write(emu, FXWC, 0, 0); break; @@ -574,8 +572,10 @@ static void snd_emu10k1_playback_commit_volume(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice, unsigned int vattn) { - snd_emu10k1_ptr_write(emu, VTFT, evoice->number, vattn | VTFT_FILTERTARGET_MASK); - snd_emu10k1_ptr_write(emu, CVCF, evoice->number, vattn | CVCF_CURRENTFILTER_MASK); + snd_emu10k1_ptr_write_multiple(emu, evoice->number, + VTFT, vattn | VTFT_FILTERTARGET_MASK, + CVCF, vattn | CVCF_CURRENTFILTER_MASK, + REGLIST_END); } static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, @@ -716,8 +716,10 @@ static int snd_emu10k1_capture_trigger(struct snd_pcm_substream *substream, break; case CAPTURE_EFX: if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXWC1, 0, epcm->capture_cr_val); - snd_emu10k1_ptr_write(emu, A_FXWC2, 0, epcm->capture_cr_val2); + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, epcm->capture_cr_val, + A_FXWC2, epcm->capture_cr_val2, + REGLIST_END); dev_dbg(emu->card->dev, "cr_val=0x%x, cr_val2=0x%x\n", epcm->capture_cr_val, @@ -744,8 +746,10 @@ static int snd_emu10k1_capture_trigger(struct snd_pcm_substream *substream, break; case CAPTURE_EFX: if (emu->audigy) { - snd_emu10k1_ptr_write(emu, A_FXWC1, 0, 0); - snd_emu10k1_ptr_write(emu, A_FXWC2, 0, 0); + snd_emu10k1_ptr_write_multiple(emu, 0, + A_FXWC1, 0, + A_FXWC2, 0, + REGLIST_END); } else snd_emu10k1_ptr_write(emu, FXWC, 0, 0); break; @@ -1562,12 +1566,14 @@ static int snd_emu10k1_fx8010_playback_prepare(struct snd_pcm_substream *substre pcm->pcm_rec.sw_buffer_size = snd_pcm_lib_buffer_bytes(substream); pcm->tram_pos = INITIAL_TRAM_POS(pcm->buffer_size); pcm->tram_shift = 0; - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_running, 0, 0); /* reset */ - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_trigger, 0, 0); /* reset */ - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_size, 0, runtime->buffer_size); - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_ptr, 0, 0); /* reset ptr number */ - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_count, 0, runtime->period_size); - snd_emu10k1_ptr_write(emu, emu->gpr_base + pcm->gpr_tmpcount, 0, runtime->period_size); + snd_emu10k1_ptr_write_multiple(emu, 0, + emu->gpr_base + pcm->gpr_running, 0, /* reset */ + emu->gpr_base + pcm->gpr_trigger, 0, /* reset */ + emu->gpr_base + pcm->gpr_size, runtime->buffer_size, + emu->gpr_base + pcm->gpr_ptr, 0, /* reset ptr number */ + emu->gpr_base + pcm->gpr_count, runtime->period_size, + emu->gpr_base + pcm->gpr_tmpcount, runtime->period_size, + REGLIST_END); for (i = 0; i < pcm->channels; i++) snd_emu10k1_ptr_write(emu, TANKMEMADDRREGBASE + 0x80 + pcm->etram[i], 0, (TANKMEMADDRREG_READ|TANKMEMADDRREG_ALIGN) + i * (runtime->buffer_size / pcm->channels)); return 0; diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 36fd6f7a0a2c..6419719c739c 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -94,6 +94,37 @@ void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned i EXPORT_SYMBOL(snd_emu10k1_ptr_write); +void snd_emu10k1_ptr_write_multiple(struct snd_emu10k1 *emu, unsigned int chn, ...) +{ + va_list va; + u32 addr_mask; + unsigned long flags; + + if (snd_BUG_ON(!emu)) + return; + if (snd_BUG_ON(chn & ~PTR_CHANNELNUM_MASK)) + return; + addr_mask = ~((emu->audigy ? A_PTR_ADDRESS_MASK : PTR_ADDRESS_MASK) >> 16); + + va_start(va, chn); + spin_lock_irqsave(&emu->emu_lock, flags); + for (;;) { + u32 data; + u32 reg = va_arg(va, u32); + if (reg == REGLIST_END) + break; + data = va_arg(va, u32); + if (snd_BUG_ON(reg & addr_mask)) // Only raw registers supported here + continue; + outl((reg << 16) | chn, emu->port + PTR); + outl(data, emu->port + DATA); + } + spin_unlock_irqrestore(&emu->emu_lock, flags); + va_end(va); +} + +EXPORT_SYMBOL(snd_emu10k1_ptr_write_multiple); + unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn) -- cgit v1.2.3 From 816967d55f425a647137ef884d8e92f2baf541dc Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Thu, 18 May 2023 08:38:26 -0400 Subject: ALSA: emu10k1: set variables emu1010_routing_info and emu1010_pads_info storage-class-specifier to static smatch reports sound/pci/emu10k1/emumixer.c:519:39: warning: symbol 'emu1010_routing_info' was not declared. Should it be static? sound/pci/emu10k1/emumixer.c:859:36: warning: symbol 'emu1010_pads_info' was not declared. Should it be static? These variables are only used in their defining file, so it should be static Signed-off-by: Tom Rix Reviewed-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518123826.925752-1-trix@redhat.com Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emumixer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 183051e846f2..47d5e6a88a89 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -516,7 +516,7 @@ struct snd_emu1010_routing_info { unsigned n_ins; }; -const struct snd_emu1010_routing_info emu1010_routing_info[] = { +static const struct snd_emu1010_routing_info emu1010_routing_info[] = { { /* rev1 1010 */ .src_regs = emu1010_src_regs, @@ -856,7 +856,7 @@ struct snd_emu1010_pads_info { unsigned n_adc_ctls, n_dac_ctls; }; -const struct snd_emu1010_pads_info emu1010_pads_info[] = { +static const struct snd_emu1010_pads_info emu1010_pads_info[] = { { /* rev1 1010 */ .adc_ctls = snd_emu1010_adc_pads, -- cgit v1.2.3 From df335e9a8bcb58be3b7388cff556f06eeb3d024f Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:38 +0200 Subject: ALSA: emu10k1: fix synthesizer sample playback position and caching Compensate for the cache delay, and actually populate the cache. Without these, the playback would start with garbage (which would be (mostly?) masqueraded by the attack phase). Unlike for the PCM voices, this doesn't try to compensate for the interpolator read-ahead, because it's pointless to be super-exact here. Note that this code is probably still broken for particularly short samples, because we ignore the loop-related parts of CCR. But I'm not going to reverse-engineer that now ... Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722308-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_callback.c | 36 +++++------------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 06440b97b5d7..aab8d64fd708 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -253,7 +253,7 @@ lookup_voices(struct snd_emux *emu, struct snd_emu10k1 *hw, /* check if sample is finished playing (non-looping only) */ if (bp != best + V_OFF && bp != best + V_FREE && (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) { - val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch); + val = snd_emu10k1_ptr_read(hw, CCCA_CURRADDR, vp->ch) - 64; if (val >= vp->reg.loopstart) bp = best + V_OFF; } @@ -360,7 +360,7 @@ start_voice(struct snd_emux_voice *vp) map = (hw->silent_page.addr << hw->address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - addr = vp->reg.start; + addr = vp->reg.start + 64; temp = vp->reg.parm.filterQ; ccca = (temp << 28) | addr; if (vp->apitch < 0xe400) @@ -428,40 +428,14 @@ start_voice(struct snd_emux_voice *vp) /* Q & current address (Q 4bit value, MSB) */ CCCA, ccca, + /* cache */ + CCR, REG_VAL_PUT(CCR_CACHEINVALIDSIZE, 64), + /* reset volume */ VTFT, vtarget | vp->ftarget, CVCF, vtarget | CVCF_CURRENTFILTER_MASK, REGLIST_END); -#if 0 - /* cache */ - { - unsigned int val, sample; - val = 32; - if (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_8BITS) - sample = 0x80808080; - else { - sample = 0; - val *= 2; - } - - /* cache */ - snd_emu10k1_ptr_write(hw, CCR, ch, 0x1c << 16); - snd_emu10k1_ptr_write(hw, CDE, ch, sample); - snd_emu10k1_ptr_write(hw, CDF, ch, sample); - - /* invalidate maps */ - temp = ((unsigned int)hw->silent_page.addr << hw_address_mode) | (hw->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); - snd_emu10k1_ptr_write(hw, MAPA, ch, temp); - snd_emu10k1_ptr_write(hw, MAPB, ch, temp); - - /* fill cache */ - val -= 4; - val <<= 25; - val |= 0x1c << 16; - snd_emu10k1_ptr_write(hw, CCR, ch, val); - } -#endif return 0; } -- cgit v1.2.3 From 5c2664cc09f94ba11c61908d5c7dac1c35d6dee8 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:39 +0200 Subject: ALSA: emu10k1: fix terminating synthesizer voices Make terminate_voice() actually do at all what it's supposed to do: instantly and completely shut down the note. The bogus behavior was mostly harmless, as usually the voice is freed right afterwards, which implicitly terminates it anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722308-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_callback.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index aab8d64fd708..dcd7be2d281b 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -136,8 +136,13 @@ terminate_voice(struct snd_emux_voice *vp) if (snd_BUG_ON(!vp)) return; hw = vp->hw; - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, - DCYSUSV_PHASE1_MASK | DCYSUSV_DECAYTIME_MASK | DCYSUSV_CHANNELENABLE_MASK); + snd_emu10k1_ptr_write_multiple(hw, vp->ch, + DCYSUSV, 0, + VTFT, VTFT_FILTERTARGET_MASK, + CVCF, CVCF_CURRENTFILTER_MASK, + PTRX, 0, + CPF, 0, + REGLIST_END); if (vp->block) { struct snd_emu10k1_memblk *emem; emem = (struct snd_emu10k1_memblk *)vp->block; -- cgit v1.2.3 From 08e55ae996cbdbd76c3eb12fcad6cc79cba7ddbc Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:38 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 3: pitch CPF_CURRENTPITCH starts swerving towards PTRX_PITCHTARGET as soon as that is set. In practice this means that CPF_FRACADDRESS may acquire a non-zero value before we manage to force CPF_CURRENTPITCH to the final value, which would prevent bit-for-bit reproduction. To avoid that this state persists, we now reset CPF_FRACADDRESS when setting CPF_CURRENTPITCH, and to (mostly) avoid that it progresses too far in the first place (possibly even reaching CCCA_CURRADDR), we write PTRX and CPF in one critical section (though NMIs, etc. still make this unreliable). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722279-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 1ca16f0ddbed..0b23ff8d9c3b 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -601,6 +601,17 @@ static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, snd_emu10k1_playback_commit_volume(emu, evoice, 0); } +static void snd_emu10k1_playback_commit_pitch(struct snd_emu10k1 *emu, + u32 voice, u32 pitch_target) +{ + u32 ptrx = snd_emu10k1_ptr_read(emu, PTRX, voice); + u32 cpf = snd_emu10k1_ptr_read(emu, CPF, voice); + snd_emu10k1_ptr_write_multiple(emu, voice, + PTRX, (ptrx & ~PTRX_PITCHTARGET_MASK) | pitch_target, + CPF, (cpf & ~(CPF_CURRENTPITCH_MASK | CPF_FRACADDRESS_MASK)) | pitch_target, + REGLIST_END); +} + static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { @@ -616,8 +627,7 @@ static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else pitch_target = emu10k1_calc_pitch_target(runtime->rate); - snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, pitch_target); - snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, pitch_target); + snd_emu10k1_playback_commit_pitch(emu, voice, pitch_target << 16); } static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, @@ -626,8 +636,7 @@ static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, unsigned int voice; voice = evoice->number; - snd_emu10k1_ptr_write(emu, PTRX_PITCHTARGET, voice, 0); - snd_emu10k1_ptr_write(emu, CPF_CURRENTPITCH, voice, 0); + snd_emu10k1_playback_commit_pitch(emu, voice, 0); } static void snd_emu10k1_playback_set_running(struct snd_emu10k1 *emu, -- cgit v1.2.3 From fccd6f31a450d58109f64eda2dd9294e160fb0aa Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:03:39 +0200 Subject: ALSA: emu10k1: enable bit-exact playback, part 4: send amounts On Audigy, the send amounts are merely targets, presumably to avoid sound distortion due to sudden changes, which the EMU8K docu explicitly warns about. However, that "soft-start" would prevent bit-for-bit reproduction, so we now force the current send amounts to their final values at PCM playback init. One might want to do that for the MIDI synthesizer as well, though it seems mostly pointless due to the attack phase each note has anyway. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140339.3722279-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 ++ sound/pci/emu10k1/emupcm.c | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 9c5de1f45566..583fabef0b99 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -709,6 +709,8 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ #define ADCBS_BUFSIZE_57344 0x0000001e #define ADCBS_BUFSIZE_65536 0x0000001f +// On Audigy, the FX send amounts are not applied instantly, but determine +// targets towards which the following registers swerve gradually. #define A_CSBA 0x4c /* FX send B & A current amounts */ #define A_CSDC 0x4d /* FX send D & C current amounts */ #define A_CSFE 0x4e /* FX send F & E current amounts */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0b23ff8d9c3b..903a68a4d396 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -236,6 +236,18 @@ static unsigned int emu10k1_select_interprom(unsigned int pitch_target) return CCCA_INTERPROM_2; } +static u16 emu10k1_send_target_from_amount(u8 amount) +{ + static const u8 shifts[8] = { 4, 4, 5, 6, 7, 8, 9, 10 }; + static const u16 offsets[8] = { 0, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000 }; + u8 exp; + + if (amount == 0xff) + return 0xffff; + exp = amount >> 5; + return ((amount & 0x1f) << shifts[exp]) + offsets[exp]; +} + static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, int master, int extra, struct snd_emu10k1_voice *evoice, @@ -301,6 +313,11 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, A_FXRT2, snd_emu10k1_compose_audigy_fxrt2(send_routing), A_SENDAMOUNTS, snd_emu10k1_compose_audigy_sendamounts(send_amount), REGLIST_END); + for (int i = 0; i < 4; i++) { + u32 aml = emu10k1_send_target_from_amount(send_amount[2 * i]); + u32 amh = emu10k1_send_target_from_amount(send_amount[2 * i + 1]); + snd_emu10k1_ptr_write(emu, A_CSBA + i, voice, (amh << 16) | aml); + } } else { snd_emu10k1_ptr_write(emu, FXRT, voice, snd_emu10k1_compose_send_routing(send_routing)); -- cgit v1.2.3 From f26a4cf087cbfc9dc71bb3812e8e11ccac0d4d61 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:41 +0200 Subject: ALSA: emu10k1: simplify freeing synth voices snd_emu10k1_voice_free() resets the hardware itself, so doing that in the calling function as well is redundant. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emu10k1_callback.c | 5 ----- 1 file changed, 5 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index dcd7be2d281b..6686ca8ce5fc 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -165,11 +165,6 @@ free_voice(struct snd_emux_voice *vp) /* Problem apparent on plug, unplug then plug */ /* on the Audigy 2 ZS Notebook. */ if (hw && (vp->ch >= 0)) { - snd_emu10k1_ptr_write(hw, IFATN, vp->ch, 0xff00); - snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0x807f | DCYSUSV_CHANNELENABLE_MASK); - // snd_emu10k1_ptr_write(hw, DCYSUSV, vp->ch, 0); - snd_emu10k1_ptr_write(hw, VTFT, vp->ch, 0xffff); - snd_emu10k1_ptr_write(hw, CVCF, vp->ch, 0xffff); snd_emu10k1_voice_free(hw, &hw->voices[vp->ch]); vp->emu->num_voices--; vp->ch = -1; -- cgit v1.2.3 From 3eb5b1d0a11d1daf85243eb06f813cf83752e1d0 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:42 +0200 Subject: ALSA: emu10k1: don't forget to reset reclaimed synth voices The subsequent allocation may still fail after freeing some voices, so we shouldn't leave them in their programmed state. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/voice.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index cbeb8443492c..a602df9117f6 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -98,6 +98,15 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, return 0; } +static void voice_free(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *pvoice) +{ + snd_emu10k1_voice_init(emu, pvoice->number); + pvoice->interrupt = NULL; + pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; + pvoice->epcm = NULL; +} + int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, struct snd_emu10k1_voice **rvoice) { @@ -118,12 +127,8 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, /* free a voice from synth */ if (emu->get_synth_voice) { result = emu->get_synth_voice(emu); - if (result >= 0) { - struct snd_emu10k1_voice *pvoice = &emu->voices[result]; - pvoice->interrupt = NULL; - pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; - pvoice->epcm = NULL; - } + if (result >= 0) + voice_free(emu, &emu->voices[result]); } if (result < 0) break; @@ -143,10 +148,7 @@ int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, if (snd_BUG_ON(!pvoice)) return -EINVAL; spin_lock_irqsave(&emu->voice_lock, flags); - pvoice->interrupt = NULL; - pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; - pvoice->epcm = NULL; - snd_emu10k1_voice_init(emu, pvoice->number); + voice_free(emu, pvoice); spin_unlock_irqrestore(&emu->voice_lock, flags); return 0; } -- cgit v1.2.3 From b840f8d8fcb3df9e65bb6782a9072897b6ea117d Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:43 +0200 Subject: ALSA: emu10k1: improve voice status display in /proc Eliminate the MIDI type, as there is no such thing - the MPU401 port doesn't have anything to do with voices. For clarity, differentiate between regular and extra voices. Don't atomize the enum into bits in the table display. Simplify/optimize the storage. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 13 ++++++------- sound/pci/emu10k1/emupcm.c | 2 +- sound/pci/emu10k1/emuproc.c | 16 ++++++++-------- sound/pci/emu10k1/voice.c | 20 +++----------------- 4 files changed, 18 insertions(+), 33 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 583fabef0b99..1fa7816c07fd 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1439,21 +1439,20 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM /* ------------------- STRUCTURES -------------------- */ enum { + EMU10K1_UNUSED, // This must be zero EMU10K1_EFX, + EMU10K1_EFX_IRQ, EMU10K1_PCM, + EMU10K1_PCM_IRQ, EMU10K1_SYNTH, - EMU10K1_MIDI + EMU10K1_NUM_TYPES }; struct snd_emu10k1; struct snd_emu10k1_voice { - int number; - unsigned int use: 1, - pcm: 1, - efx: 1, - synth: 1, - midi: 1; + unsigned char number; + unsigned char use; void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); struct snd_emu10k1_pcm *epcm; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 903a68a4d396..216b6cde326f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -117,7 +117,7 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic // period-sized loop as the interrupt source. Additionally, the interrupt // timing of the hardware is "suboptimal" and needs some compensation. err = snd_emu10k1_voice_alloc(epcm->emu, - epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, + epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM_IRQ : EMU10K1_EFX_IRQ, 1, &epcm->extra); if (err < 0) { diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 708aff6cf09a..c423a56ebf9e 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -367,17 +367,17 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, struct snd_emu10k1 *emu = entry->private_data; struct snd_emu10k1_voice *voice; int idx; - - snd_iprintf(buffer, "ch\tuse\tpcm\tefx\tsynth\tmidi\n"); + static const char * const types[] = { + "Unused", "EFX", "EFX IRQ", "PCM", "PCM IRQ", "Synth" + }; + static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); + + snd_iprintf(buffer, "ch\tuse\n"); for (idx = 0; idx < NUM_G; idx++) { voice = &emu->voices[idx]; - snd_iprintf(buffer, "%i\t%i\t%i\t%i\t%i\t%i\n", + snd_iprintf(buffer, "%i\t%s\n", idx, - voice->use, - voice->pcm, - voice->efx, - voice->synth, - voice->midi); + types[voice->use]); } } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index a602df9117f6..ac89d09ed9bc 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -78,21 +78,7 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", voice->number, idx-first_voice+1, number); */ - voice->use = 1; - switch (type) { - case EMU10K1_PCM: - voice->pcm = 1; - break; - case EMU10K1_SYNTH: - voice->synth = 1; - break; - case EMU10K1_MIDI: - voice->midi = 1; - break; - case EMU10K1_EFX: - voice->efx = 1; - break; - } + voice->use = type; } *rvoice = &emu->voices[first_voice]; return 0; @@ -103,7 +89,7 @@ static void voice_free(struct snd_emu10k1 *emu, { snd_emu10k1_voice_init(emu, pvoice->number); pvoice->interrupt = NULL; - pvoice->use = pvoice->pcm = pvoice->synth = pvoice->midi = pvoice->efx = 0; + pvoice->use = 0; pvoice->epcm = NULL; } @@ -121,7 +107,7 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, spin_lock_irqsave(&emu->voice_lock, flags); for (;;) { result = voice_alloc(emu, type, number, rvoice); - if (result == 0 || type == EMU10K1_SYNTH || type == EMU10K1_MIDI) + if (result == 0 || type == EMU10K1_SYNTH) break; /* free a voice from synth */ -- cgit v1.2.3 From 82a9fa6e9e3c769f7edc62810c9718997cada53d Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:44 +0200 Subject: ALSA: emu10k1: make freeing untouched playback voices cheap This allows us to drop the code that tries to preserve already allocated voices upon repeated hw_param callback invocations. Getting it right for multi-channel voices would otherwise get a bit hairy. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emu10k1_callback.c | 1 + sound/pci/emu10k1/emupcm.c | 13 ++----------- sound/pci/emu10k1/emuproc.c | 5 +++-- sound/pci/emu10k1/voice.c | 5 +++-- 5 files changed, 10 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 1fa7816c07fd..0ce84beb6441 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1453,6 +1453,7 @@ struct snd_emu10k1; struct snd_emu10k1_voice { unsigned char number; unsigned char use; + unsigned char dirty; void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); struct snd_emu10k1_pcm *epcm; diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 6686ca8ce5fc..6d483bda46df 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -437,6 +437,7 @@ start_voice(struct snd_emux_voice *vp) REGLIST_END); + hw->voices[ch].dirty = 1; return 0; } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 216b6cde326f..324db1141479 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -80,17 +80,6 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic { int err, i; - if (epcm->voices[1] != NULL && voices < 2) { - snd_emu10k1_voice_free(epcm->emu, epcm->voices[1]); - epcm->voices[1] = NULL; - } - for (i = 0; i < voices; i++) { - if (epcm->voices[i] == NULL) - break; - } - if (i == voices) - return 0; /* already allocated */ - for (i = 0; i < ARRAY_SIZE(epcm->voices); i++) { if (epcm->voices[i]) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); @@ -323,6 +312,8 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, snd_emu10k1_compose_send_routing(send_routing)); } + emu->voices[voice].dirty = 1; + spin_unlock_irqrestore(&emu->reg_lock, flags); } diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index c423a56ebf9e..abcec8a01760 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -372,11 +372,12 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, }; static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); - snd_iprintf(buffer, "ch\tuse\n"); + snd_iprintf(buffer, "ch\tdirty\tuse\n"); for (idx = 0; idx < NUM_G; idx++) { voice = &emu->voices[idx]; - snd_iprintf(buffer, "%i\t%s\n", + snd_iprintf(buffer, "%i\t%u\t%s\n", idx, + voice->dirty, types[voice->use]); } } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index ac89d09ed9bc..25e78fc188bf 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -87,9 +87,10 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, static void voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice) { - snd_emu10k1_voice_init(emu, pvoice->number); + if (pvoice->dirty) + snd_emu10k1_voice_init(emu, pvoice->number); pvoice->interrupt = NULL; - pvoice->use = 0; + pvoice->use = pvoice->dirty = 0; pvoice->epcm = NULL; } -- cgit v1.2.3 From bdb3b567b84e321c51786aba2a05ec23bb90bfdf Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 19 May 2023 20:41:22 +0200 Subject: ALSA: emu10k1: centralize freeing PCM voices This saves some code duplication; no functional change. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230519184122.3808185-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 324db1141479..9d6eb58e773f 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -76,16 +76,22 @@ static void snd_emu10k1_pcm_efx_interrupt(struct snd_emu10k1 *emu, snd_pcm_period_elapsed(emu->pcm_capture_efx_substream); } -static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices) +static void snd_emu10k1_pcm_free_voices(struct snd_emu10k1_pcm *epcm) { - int err, i; - - for (i = 0; i < ARRAY_SIZE(epcm->voices); i++) { + for (unsigned i = 0; i < ARRAY_SIZE(epcm->voices); i++) { if (epcm->voices[i]) { snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); epcm->voices[i] = NULL; } } +} + +static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices) +{ + int err, i; + + snd_emu10k1_pcm_free_voices(epcm); + err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, voices, @@ -115,15 +121,13 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic "failed extra: voices=%d, frame=%d\n", voices, frame); */ - for (i = 0; i < voices; i++) { - snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); - epcm->voices[i] = NULL; - } + snd_emu10k1_pcm_free_voices(epcm); return err; } epcm->extra->epcm = epcm; epcm->extra->interrupt = snd_emu10k1_pcm_interrupt; } + return 0; } @@ -359,7 +363,6 @@ static int snd_emu10k1_playback_hw_free(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; - int i; if (runtime->private_data == NULL) return 0; @@ -368,12 +371,7 @@ static int snd_emu10k1_playback_hw_free(struct snd_pcm_substream *substream) snd_emu10k1_voice_free(epcm->emu, epcm->extra); epcm->extra = NULL; } - for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - if (epcm->voices[i]) { - snd_emu10k1_voice_free(epcm->emu, epcm->voices[i]); - epcm->voices[i] = NULL; - } - } + snd_emu10k1_pcm_free_voices(epcm); if (epcm->memblk) { snd_emu10k1_free_pages(emu, epcm->memblk); epcm->memblk = NULL; -- cgit v1.2.3 From b4fea2d3f25b5f3ad6b230f91e61151165f6d023 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:46 +0200 Subject: ALSA: emu10k1: make snd_emu10k1_voice_alloc() assign voices' epcm The voice allocator clearly knows about the field (it resets it), so it's more consistent (and leads to less duplicated code) to have the constructor take it as a parameter. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 ++- sound/pci/emu10k1/emu10k1_callback.c | 2 +- sound/pci/emu10k1/emupcm.c | 7 ++----- sound/pci/emu10k1/voice.c | 7 ++++--- 4 files changed, 9 insertions(+), 10 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 0ce84beb6441..3cd1b7752c2e 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1850,7 +1850,8 @@ int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_me int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk); /* voice allocation */ -int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, struct snd_emu10k1_voice **rvoice); +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice); int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); /* MIDI uart */ diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 6d483bda46df..2fdfed7f07c2 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -287,7 +287,7 @@ get_voice(struct snd_emux *emu, struct snd_emux_port *port) if (vp->ch < 0) { /* allocate a voice */ struct snd_emu10k1_voice *hwvoice; - if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, &hwvoice) < 0 || hwvoice == NULL) + if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, NULL, &hwvoice) < 0 || hwvoice == NULL) continue; vp->ch = hwvoice->number; emu->num_voices++; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 9d6eb58e773f..0651e7795ecf 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -95,15 +95,13 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, voices, - &epcm->voices[0]); + epcm, &epcm->voices[0]); if (err < 0) return err; - epcm->voices[0]->epcm = epcm; if (voices > 1) { for (i = 1; i < voices; i++) { epcm->voices[i] = &epcm->emu->voices[(epcm->voices[0]->number + i) % NUM_G]; - epcm->voices[i]->epcm = epcm; } } if (epcm->extra == NULL) { @@ -114,7 +112,7 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic err = snd_emu10k1_voice_alloc(epcm->emu, epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM_IRQ : EMU10K1_EFX_IRQ, 1, - &epcm->extra); + epcm, &epcm->extra); if (err < 0) { /* dev_dbg(emu->card->dev, "pcm_channel_alloc: " @@ -124,7 +122,6 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voic snd_emu10k1_pcm_free_voices(epcm); return err; } - epcm->extra->epcm = epcm; epcm->extra->interrupt = snd_emu10k1_pcm_interrupt; } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index 25e78fc188bf..6c58e3bd14f7 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -32,7 +32,7 @@ */ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, - struct snd_emu10k1_voice **rvoice) + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { struct snd_emu10k1_voice *voice; int i, j, k, first_voice, last_voice, skip; @@ -79,6 +79,7 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, voice->number, idx-first_voice+1, number); */ voice->use = type; + voice->epcm = epcm; } *rvoice = &emu->voices[first_voice]; return 0; @@ -95,7 +96,7 @@ static void voice_free(struct snd_emu10k1 *emu, } int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, - struct snd_emu10k1_voice **rvoice) + struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { unsigned long flags; int result; @@ -107,7 +108,7 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, spin_lock_irqsave(&emu->voice_lock, flags); for (;;) { - result = voice_alloc(emu, type, number, rvoice); + result = voice_alloc(emu, type, number, epcm, rvoice); if (result == 0 || type == EMU10K1_SYNTH) break; -- cgit v1.2.3 From a915d60426d4348a0b91f9870e299056fd604a32 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 18 May 2023 16:09:47 +0200 Subject: ALSA: emu10k1: revamp playback voice allocator Instead of separate voices, we now allocate non-interleaved channels, which may in turn contain two interleaved voices each. The higher-level code keeps only one pointer per channel. The channels are not allocated in one block any more, as there is no reason to do that. As a consequence of that, and because it is cleaner regardless, we now let the allocator store these pointers at a specified location, rather than returning only the first one and having the calling code deduce the remaining ones. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230518140947.3725394-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 +- sound/pci/emu10k1/emu10k1_callback.c | 2 +- sound/pci/emu10k1/emumixer.c | 24 ++++---- sound/pci/emu10k1/emupcm.c | 44 ++++++++------- sound/pci/emu10k1/emuproc.c | 5 +- sound/pci/emu10k1/voice.c | 106 ++++++++++++++++++----------------- 6 files changed, 95 insertions(+), 89 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 3cd1b7752c2e..0780f39f4bb6 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1454,6 +1454,7 @@ struct snd_emu10k1_voice { unsigned char number; unsigned char use; unsigned char dirty; + unsigned char last; void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); struct snd_emu10k1_pcm *epcm; @@ -1850,7 +1851,7 @@ int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_me int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk); /* voice allocation */ -int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels, struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice); int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index 2fdfed7f07c2..ad0dea0c2be9 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -287,7 +287,7 @@ get_voice(struct snd_emux *emu, struct snd_emux_port *port) if (vp->ch < 0) { /* allocate a voice */ struct snd_emu10k1_voice *hwvoice; - if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, NULL, &hwvoice) < 0 || hwvoice == NULL) + if (snd_emu10k1_voice_alloc(hw, EMU10K1_SYNTH, 1, 1, NULL, &hwvoice) < 0) continue; vp->ch = hwvoice->number; emu->num_voices++; diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 47d5e6a88a89..20a0b3afc8a5 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -1467,13 +1467,13 @@ static int snd_emu10k1_send_routing_put(struct snd_kcontrol *kcontrol, change = 1; } } - if (change && mix->epcm) { - if (mix->epcm->voices[0] && mix->epcm->voices[1]) { + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, &mix->send_routing[1][0]); - update_emu10k1_fxrt(emu, mix->epcm->voices[1]->number, + update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number + 1, &mix->send_routing[2][0]); - } else if (mix->epcm->voices[0]) { + } else { update_emu10k1_fxrt(emu, mix->epcm->voices[0]->number, &mix->send_routing[0][0]); } @@ -1535,13 +1535,13 @@ static int snd_emu10k1_send_volume_put(struct snd_kcontrol *kcontrol, change = 1; } } - if (change && mix->epcm) { - if (mix->epcm->voices[0] && mix->epcm->voices[1]) { + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, &mix->send_volume[1][0]); - update_emu10k1_send_volume(emu, mix->epcm->voices[1]->number, + update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number + 1, &mix->send_volume[2][0]); - } else if (mix->epcm->voices[0]) { + } else { update_emu10k1_send_volume(emu, mix->epcm->voices[0]->number, &mix->send_volume[0][0]); } @@ -1601,11 +1601,11 @@ static int snd_emu10k1_attn_put(struct snd_kcontrol *kcontrol, change = 1; } } - if (change && mix->epcm) { - if (mix->epcm->voices[0] && mix->epcm->voices[1]) { + if (change && mix->epcm && mix->epcm->voices[0]) { + if (!mix->epcm->voices[0]->last) { snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[1]); - snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[1]->number, mix->attn[2]); - } else if (mix->epcm->voices[0]) { + snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number + 1, mix->attn[2]); + } else { snd_emu10k1_ptr_write(emu, VTFT_VOLUMETARGET, mix->epcm->voices[0]->number, mix->attn[0]); } } diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0651e7795ecf..0036593cca7c 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -86,32 +86,26 @@ static void snd_emu10k1_pcm_free_voices(struct snd_emu10k1_pcm *epcm) } } -static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm * epcm, int voices) +static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm, + int type, int count, int channels) { - int err, i; + int err; snd_emu10k1_pcm_free_voices(epcm); err = snd_emu10k1_voice_alloc(epcm->emu, - epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM : EMU10K1_EFX, - voices, + type, count, channels, epcm, &epcm->voices[0]); - if (err < 0) return err; - if (voices > 1) { - for (i = 1; i < voices; i++) { - epcm->voices[i] = &epcm->emu->voices[(epcm->voices[0]->number + i) % NUM_G]; - } - } + if (epcm->extra == NULL) { // The hardware supports only (half-)loop interrupts, so to support an // arbitrary number of periods per buffer, we use an extra voice with a // period-sized loop as the interrupt source. Additionally, the interrupt // timing of the hardware is "suboptimal" and needs some compensation. err = snd_emu10k1_voice_alloc(epcm->emu, - epcm->type == PLAYBACK_EMUVOICE ? EMU10K1_PCM_IRQ : EMU10K1_EFX_IRQ, - 1, + type + 1, 1, 1, epcm, &epcm->extra); if (err < 0) { /* @@ -325,9 +319,19 @@ static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_runtime *runtime = substream->runtime; struct snd_emu10k1_pcm *epcm = runtime->private_data; size_t alloc_size; + int type, channels, count; int err; - err = snd_emu10k1_pcm_channel_alloc(epcm, params_channels(hw_params)); + if (epcm->type == PLAYBACK_EMUVOICE) { + type = EMU10K1_PCM; + channels = 1; + count = params_channels(hw_params); + } else { + type = EMU10K1_EFX; + channels = params_channels(hw_params); + count = 1; + } + err = snd_emu10k1_pcm_channel_alloc(epcm, type, count, channels); if (err < 0) return err; @@ -397,8 +401,8 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, start_addr, end_addr, &emu->pcm_mixer[substream->number]); - if (epcm->voices[1]) - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[1], w_16, true, + if (stereo) + snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[0] + 1, w_16, true, start_addr, end_addr, &emu->pcm_mixer[substream->number]); return 0; @@ -589,8 +593,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, unsigned int vattn; unsigned int tmp; - if (evoice == NULL) /* skip second voice for mono */ - return; tmp = stereo ? (master ? 1 : 2) : 0; vattn = mix->attn[tmp] << 16; snd_emu10k1_playback_commit_volume(emu, evoice, vattn); @@ -599,8 +601,6 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { - if (evoice == NULL) - return; snd_emu10k1_playback_commit_volume(emu, evoice, 0); } @@ -681,7 +681,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_RESUME: mix = &emu->pcm_mixer[substream->number]; snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix); - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[1], stereo, false, mix); + if (stereo) + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0] + 1, true, false, mix); snd_emu10k1_playback_set_running(emu, epcm); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); snd_emu10k1_playback_trigger_voice(emu, epcm->extra); @@ -693,7 +694,8 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]); - snd_emu10k1_playback_mute_voice(emu, epcm->voices[1]); + if (stereo) + snd_emu10k1_playback_mute_voice(emu, epcm->voices[0] + 1); break; default: result = -EINVAL; diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index abcec8a01760..89ea3adff322 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -372,12 +372,13 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, }; static_assert(ARRAY_SIZE(types) == EMU10K1_NUM_TYPES); - snd_iprintf(buffer, "ch\tdirty\tuse\n"); + snd_iprintf(buffer, "ch\tdirty\tlast\tuse\n"); for (idx = 0; idx < NUM_G; idx++) { voice = &emu->voices[idx]; - snd_iprintf(buffer, "%i\t%u\t%s\n", + snd_iprintf(buffer, "%i\t%u\t%u\t%s\n", idx, voice->dirty, + voice->last, types[voice->use]); } } diff --git a/sound/pci/emu10k1/voice.c b/sound/pci/emu10k1/voice.c index 6c58e3bd14f7..6939498e26f0 100644 --- a/sound/pci/emu10k1/voice.c +++ b/sound/pci/emu10k1/voice.c @@ -23,11 +23,7 @@ * allocator uses a round robin scheme. The next free voice is tracked in * the card record and each allocation begins where the last left off. The * hardware requires stereo interleaved voices be aligned to an even/odd - * boundary. For multichannel voice allocation we ensure than the block of - * voices does not cross the 32 voice boundary. This simplifies the - * multichannel support and ensures we can use a single write to the - * (set|clear)_loop_stop registers. Otherwise (for example) the voices would - * get out of sync when pausing/resuming a stream. + * boundary. * --rlrevell */ @@ -35,54 +31,43 @@ static int voice_alloc(struct snd_emu10k1 *emu, int type, int number, struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { struct snd_emu10k1_voice *voice; - int i, j, k, first_voice, last_voice, skip; + int i, j, k, skip; - *rvoice = NULL; - first_voice = last_voice = 0; - for (i = emu->next_free_voice, j = 0; j < NUM_G ; i += number, j += number) { + for (i = emu->next_free_voice, j = 0; j < NUM_G; i = (i + skip) % NUM_G, j += skip) { /* dev_dbg(emu->card->dev, "i %d j %d next free %d!\n", i, j, emu->next_free_voice); */ - i %= NUM_G; /* stereo voices must be even/odd */ - if ((number == 2) && (i % 2)) { - i++; + if ((number > 1) && (i % 2)) { + skip = 1; continue; } - - skip = 0; + for (k = 0; k < number; k++) { - voice = &emu->voices[(i+k) % NUM_G]; + voice = &emu->voices[i + k]; if (voice->use) { - skip = 1; - break; + skip = k + 1; + goto next; } } - if (!skip) { - /* dev_dbg(emu->card->dev, "allocated voice %d\n", i); */ - first_voice = i; - last_voice = (i + number) % NUM_G; - emu->next_free_voice = last_voice; - break; + + for (k = 0; k < number; k++) { + voice = &emu->voices[i + k]; + voice->use = type; + voice->epcm = epcm; + /* dev_dbg(emu->card->dev, "allocated voice %d\n", i + k); */ } + voice->last = 1; + + *rvoice = &emu->voices[i]; + emu->next_free_voice = (i + number) % NUM_G; + return 0; + + next: ; } - - if (first_voice == last_voice) - return -ENOMEM; - - for (i = 0; i < number; i++) { - voice = &emu->voices[(first_voice + i) % NUM_G]; - /* - dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", - voice->number, idx-first_voice+1, number); - */ - voice->use = type; - voice->epcm = epcm; - } - *rvoice = &emu->voices[first_voice]; - return 0; + return -ENOMEM; // -EBUSY would have been better } static void voice_free(struct snd_emu10k1 *emu, @@ -91,11 +76,11 @@ static void voice_free(struct snd_emu10k1 *emu, if (pvoice->dirty) snd_emu10k1_voice_init(emu, pvoice->number); pvoice->interrupt = NULL; - pvoice->use = pvoice->dirty = 0; + pvoice->use = pvoice->dirty = pvoice->last = 0; pvoice->epcm = NULL; } -int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int count, int channels, struct snd_emu10k1_pcm *epcm, struct snd_emu10k1_voice **rvoice) { unsigned long flags; @@ -103,23 +88,36 @@ int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int number, if (snd_BUG_ON(!rvoice)) return -EINVAL; - if (snd_BUG_ON(!number)) + if (snd_BUG_ON(!count)) + return -EINVAL; + if (snd_BUG_ON(!channels)) return -EINVAL; spin_lock_irqsave(&emu->voice_lock, flags); - for (;;) { - result = voice_alloc(emu, type, number, epcm, rvoice); - if (result == 0 || type == EMU10K1_SYNTH) - break; - - /* free a voice from synth */ - if (emu->get_synth_voice) { + for (int got = 0; got < channels; ) { + result = voice_alloc(emu, type, count, epcm, &rvoice[got]); + if (result == 0) { + got++; + /* + dev_dbg(emu->card->dev, "voice alloc - %i, %i of %i\n", + rvoice[got - 1]->number, got, want); + */ + continue; + } + if (type != EMU10K1_SYNTH && emu->get_synth_voice) { + /* free a voice from synth */ result = emu->get_synth_voice(emu); - if (result >= 0) + if (result >= 0) { voice_free(emu, &emu->voices[result]); + continue; + } + } + for (int i = 0; i < got; i++) { + for (int j = 0; j < count; j++) + voice_free(emu, rvoice[i] + j); + rvoice[i] = NULL; } - if (result < 0) - break; + break; } spin_unlock_irqrestore(&emu->voice_lock, flags); @@ -132,11 +130,15 @@ int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice) { unsigned long flags; + int last; if (snd_BUG_ON(!pvoice)) return -EINVAL; spin_lock_irqsave(&emu->voice_lock, flags); - voice_free(emu, pvoice); + do { + last = pvoice->last; + voice_free(emu, pvoice++); + } while (!last); spin_unlock_irqrestore(&emu->voice_lock, flags); return 0; } -- cgit v1.2.3 From 4040fc51ca37b198bb43f716e1868fe7ff5d731c Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Fri, 19 May 2023 14:54:46 -0600 Subject: ALSA: mixart: Replace one-element arrays with simple object declarations One-element arrays are deprecated, and we are replacing them with flexible array members instead. However, in this case it seems those one-element arrays have never actually been used as fake flexible arrays. See this code that dates from Linux-2.6.12-rc2 initial git repository build (commit 1da177e4c3f4 ("Linux-2.6.12-rc2")): sound/pci/mixart/mixart_core.h: 215 struct mixart_stream_state_req 216 { 217 u32 delayed; 218 u64 scheduler; 219 u32 reserved4np[3]; 220 u32 stream_count; /* set to 1 for instance */ 221 struct mixart_flow_info stream_info; /* could be an array[stream_count] */ 222 } __attribute__((packed)); sound/pci/mixart/mixart.c: 388 389 memset(&stream_state_req, 0, sizeof(stream_state_req)); 390 stream_state_req.stream_count = 1; 391 stream_state_req.stream_info.stream_desc.uid_pipe = stream->pipe->group_uid; 392 stream_state_req.stream_info.stream_desc.stream_idx = stream->substream->number; 393 So, taking the code above as example, replace multiple one-element arrays with simple object declarations, and refactor the rest of the code, accordingly. This helps with the ongoing efforts to tighten the FORTIFY_SOURCE routines on memcpy() and help us make progress towards globally enabling -fstrict-flex-arrays=3 [1]. This results in no differences in binary output. Link: https://github.com/KSPP/linux/issues/79 Link: https://github.com/KSPP/linux/issues/296 Link: https://gcc.gnu.org/pipermail/gcc-patches/2022-October/602902.html [1] Signed-off-by: Gustavo A. R. Silva Link: https://lore.kernel.org/r/ZGfiFjcL8+r3mayq@work Signed-off-by: Takashi Iwai --- sound/pci/mixart/mixart.c | 8 ++++---- sound/pci/mixart/mixart_core.h | 7 +++---- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/mixart/mixart.c b/sound/pci/mixart/mixart.c index 1b078b789604..7ceaf6a7a77e 100644 --- a/sound/pci/mixart/mixart.c +++ b/sound/pci/mixart/mixart.c @@ -98,7 +98,7 @@ static int mixart_set_pipe_state(struct mixart_mgr *mgr, memset(&group_state, 0, sizeof(group_state)); group_state.pipe_count = 1; - group_state.pipe_uid[0] = pipe->group_uid; + group_state.pipe_uid = pipe->group_uid; if(start) request.message_id = MSG_STREAM_START_STREAM_GRP_PACKET; @@ -185,7 +185,7 @@ static int mixart_set_clock(struct mixart_mgr *mgr, clock_properties.clock_mode = CM_STANDALONE; clock_properties.frequency = rate; clock_properties.nb_callers = 1; /* only one entry in uid_caller ! */ - clock_properties.uid_caller[0] = pipe->group_uid; + clock_properties.uid_caller = pipe->group_uid; dev_dbg(&mgr->pci->dev, "mixart_set_clock to %d kHz\n", rate); @@ -565,8 +565,8 @@ static int mixart_set_format(struct mixart_stream *stream, snd_pcm_format_t form stream_param.pipe_count = 1; /* set to 1 */ stream_param.stream_count = 1; /* set to 1 */ - stream_param.stream_desc[0].uid_pipe = stream->pipe->group_uid; - stream_param.stream_desc[0].stream_idx = stream->substream->number; + stream_param.stream_desc.uid_pipe = stream->pipe->group_uid; + stream_param.stream_desc.stream_idx = stream->substream->number; request.message_id = MSG_STREAM_SET_INPUT_STAGE_PARAM; request.uid = (struct mixart_uid){0,0}; diff --git a/sound/pci/mixart/mixart_core.h b/sound/pci/mixart/mixart_core.h index 2f0e29ed5d63..d39233e0e070 100644 --- a/sound/pci/mixart/mixart_core.h +++ b/sound/pci/mixart/mixart_core.h @@ -231,7 +231,7 @@ struct mixart_group_state_req u64 scheduler; u32 reserved4np[2]; u32 pipe_count; /* set to 1 for instance */ - struct mixart_uid pipe_uid[1]; /* could be an array[pipe_count] */ + struct mixart_uid pipe_uid; /* could be an array[pipe_count], in theory */ } __attribute__((packed)); struct mixart_group_state_resp @@ -314,7 +314,7 @@ struct mixart_clock_properties u32 format; u32 board_mask; u32 nb_callers; /* set to 1 (see below) */ - struct mixart_uid uid_caller[1]; + struct mixart_uid uid_caller; } __attribute__((packed)); struct mixart_clock_properties_resp @@ -401,8 +401,7 @@ struct mixart_stream_param_desc u32 reserved4np[3]; u32 pipe_count; /* set to 1 (array size !) */ u32 stream_count; /* set to 1 (array size !) */ - struct mixart_txx_stream_desc stream_desc[1]; /* only one stream per command, but this could be an array */ - + struct mixart_txx_stream_desc stream_desc; /* only one stream per command, but this could be an array, in theory */ } __attribute__((packed)); -- cgit v1.2.3 From 36a52ae64ba85f075998921ae1881c01f29ddf31 Mon Sep 17 00:00:00 2001 From: Niklas Schnelle Date: Mon, 22 May 2023 12:50:37 +0200 Subject: ALSA: add HAS_IOPORT dependencies In a future patch HAS_IOPORT=n will result in inb()/outb() and friends not being declared. We thus need to add HAS_IOPORT as dependency for those drivers using them. Co-developed-by: Arnd Bergmann Signed-off-by: Arnd Bergmann Signed-off-by: Niklas Schnelle Acked-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230522105049.1467313-33-schnelle@linux.ibm.com Signed-off-by: Takashi Iwai --- sound/drivers/Kconfig | 3 +++ sound/isa/Kconfig | 1 + sound/pci/Kconfig | 45 ++++++++++++++++++++++++++++++++++----------- sound/pcmcia/Kconfig | 1 + 4 files changed, 39 insertions(+), 11 deletions(-) (limited to 'sound') diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index be3009746f3a..864991d8776d 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -128,6 +128,7 @@ config SND_VIRMIDI config SND_MTPAV tristate "MOTU MidiTimePiece AV multiport MIDI" + depends on HAS_IOPORT select SND_RAWMIDI help To use a MOTU MidiTimePiece AV multiport MIDI adapter @@ -152,6 +153,7 @@ config SND_MTS64 config SND_SERIAL_U16550 tristate "UART16550 serial MIDI driver" + depends on HAS_IOPORT select SND_RAWMIDI help To include support for MIDI serial port interfaces, say Y here @@ -185,6 +187,7 @@ config SND_SERIAL_GENERIC config SND_MPU401 tristate "Generic MPU-401 UART driver" + depends on HAS_IOPORT select SND_MPU401_UART help Say Y here to include support for MIDI ports compatible with diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig index 6ffa48dd5983..f8159179e38d 100644 --- a/sound/isa/Kconfig +++ b/sound/isa/Kconfig @@ -23,6 +23,7 @@ menuconfig SND_ISA bool "ISA sound devices" depends on ISA || COMPILE_TEST depends on ISA_DMA_API + depends on HAS_IOPORT default y help Support for sound devices connected via the ISA bus. diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 861958451ef5..787868c9e91b 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -26,7 +26,7 @@ config SND_ALS300 select SND_PCM select SND_AC97_CODEC select SND_OPL3_LIB - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say 'Y' or 'M' to include support for Avance Logic ALS300/ALS300+ @@ -36,6 +36,7 @@ config SND_ALS300 config SND_ALS4000 tristate "Avance Logic ALS4000" depends on ISA_DMA_API + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_PCM @@ -51,7 +52,7 @@ config SND_ALI5451 tristate "ALi M5451 PCI Audio Controller" select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for the integrated AC97 sound device on motherboards using the ALi M5451 Audio Controller @@ -96,6 +97,7 @@ config SND_ATIIXP_MODEM config SND_AU8810 tristate "Aureal Advantage" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -110,6 +112,7 @@ config SND_AU8810 config SND_AU8820 tristate "Aureal Vortex" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -123,6 +126,7 @@ config SND_AU8820 config SND_AU8830 tristate "Aureal Vortex 2" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -157,7 +161,7 @@ config SND_AZT3328 select SND_RAWMIDI select SND_AC97_CODEC select SND_TIMER - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for Aztech AZF3328 (PCI168) soundcards. @@ -193,6 +197,7 @@ config SND_BT87X_OVERCLOCK config SND_CA0106 tristate "SB Audigy LS / Live 24bit" + depends on HAS_IOPORT select SND_AC97_CODEC select SND_RAWMIDI select SND_VMASTER @@ -205,6 +210,7 @@ config SND_CA0106 config SND_CMIPCI tristate "C-Media 8338, 8738, 8768, 8770" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_PCM @@ -221,6 +227,7 @@ config SND_OXYGEN_LIB config SND_OXYGEN tristate "C-Media 8786, 8787, 8788 (Oxygen)" + depends on HAS_IOPORT select SND_OXYGEN_LIB select SND_PCM select SND_MPU401_UART @@ -246,6 +253,7 @@ config SND_OXYGEN config SND_CS4281 tristate "Cirrus Logic (Sound Fusion) CS4281" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_RAWMIDI select SND_AC97_CODEC @@ -257,6 +265,7 @@ config SND_CS4281 config SND_CS46XX tristate "Cirrus Logic (Sound Fusion) CS4280/CS461x/CS462x/CS463x" + depends on HAS_IOPORT select SND_RAWMIDI select SND_AC97_CODEC select FW_LOADER @@ -290,6 +299,7 @@ config SND_CS5530 config SND_CS5535AUDIO tristate "CS5535/CS5536 Audio" depends on X86_32 || MIPS || COMPILE_TEST + depends on HAS_IOPORT select SND_PCM select SND_AC97_CODEC help @@ -307,6 +317,7 @@ config SND_CS5535AUDIO config SND_CTXFI tristate "Creative Sound Blaster X-Fi" + depends on HAS_IOPORT select SND_PCM help If you want to use soundcards based on Creative Sound Blastr X-Fi @@ -468,7 +479,7 @@ config SND_EMU10K1 select SND_AC97_CODEC select SND_TIMER select SND_SEQ_DEVICE if SND_SEQUENCER != n - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y to include support for Sound Blaster PCI 512, Live!, Audigy and E-MU APS/0404/1010/1212/1616/1820 soundcards. @@ -491,7 +502,7 @@ config SND_EMU10K1X tristate "Emu10k1X (Dell OEM Version)" select SND_AC97_CODEC select SND_RAWMIDI - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for the Dell OEM version of the Sound Blaster Live!. @@ -501,6 +512,7 @@ config SND_EMU10K1X config SND_ENS1370 tristate "(Creative) Ensoniq AudioPCI 1370" + depends on HAS_IOPORT select SND_RAWMIDI select SND_PCM help @@ -511,6 +523,7 @@ config SND_ENS1370 config SND_ENS1371 tristate "(Creative) Ensoniq AudioPCI 1371/1373" + depends on HAS_IOPORT select SND_RAWMIDI select SND_AC97_CODEC help @@ -525,7 +538,7 @@ config SND_ES1938 select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on ESS Solo-1 (ES1938, ES1946, ES1969) chips. @@ -537,7 +550,7 @@ config SND_ES1968 tristate "ESS ES1968/1978 (Maestro-1/2/2E)" select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on ESS Maestro 1/2/2E chips. @@ -569,6 +582,7 @@ config SND_ES1968_RADIO config SND_FM801 tristate "ForteMedia FM801" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC @@ -624,7 +638,7 @@ config SND_ICE1712 select SND_MPU401_UART select SND_AC97_CODEC select BITREVERSE - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on the ICE1712 (Envy24) chip. @@ -640,6 +654,7 @@ config SND_ICE1712 config SND_ICE1724 tristate "ICE/VT1724/1720 (Envy24HT/PT)" + depends on HAS_IOPORT select SND_RAWMIDI select SND_AC97_CODEC select SND_VMASTER @@ -712,7 +727,7 @@ config SND_LX6464ES config SND_MAESTRO3 tristate "ESS Allegro/Maestro3" select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on ESS Maestro 3 (Allegro) chips. @@ -753,6 +768,7 @@ config SND_NM256 config SND_PCXHR tristate "Digigram PCXHR" + depends on HAS_IOPORT select FW_LOADER select SND_PCM select SND_HWDEP @@ -764,6 +780,7 @@ config SND_PCXHR config SND_RIPTIDE tristate "Conexant Riptide" + depends on HAS_IOPORT select FW_LOADER select SND_OPL3_LIB select SND_MPU401_UART @@ -808,6 +825,7 @@ config SND_RME9652 config SND_SE6X tristate "Studio Evolution SE6X" depends on SND_OXYGEN=n && SND_VIRTUOSO=n # PCI ID conflict + depends on HAS_IOPORT select SND_OXYGEN_LIB select SND_PCM select SND_MPU401_UART @@ -830,7 +848,7 @@ config SND_SONICVIBES select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on the S3 SonicVibes chip. @@ -842,7 +860,7 @@ config SND_TRIDENT tristate "Trident 4D-Wave DX/NX; SiS 7018" select SND_MPU401_UART select SND_AC97_CODEC - depends on ZONE_DMA + depends on ZONE_DMA && HAS_IOPORT help Say Y here to include support for soundcards based on Trident 4D-Wave DX/NX or SiS 7018 chips. @@ -852,6 +870,7 @@ config SND_TRIDENT config SND_VIA82XX tristate "VIA 82C686A/B, 8233/8235 AC97 Controller" + depends on HAS_IOPORT select SND_MPU401_UART select SND_AC97_CODEC help @@ -863,6 +882,7 @@ config SND_VIA82XX config SND_VIA82XX_MODEM tristate "VIA 82C686A/B, 8233 based Modems" + depends on HAS_IOPORT select SND_AC97_CODEC help Say Y here to include support for the integrated MC97 modem on @@ -873,6 +893,7 @@ config SND_VIA82XX_MODEM config SND_VIRTUOSO tristate "Asus Virtuoso 66/100/200 (Xonar)" + depends on HAS_IOPORT select SND_OXYGEN_LIB select SND_PCM select SND_MPU401_UART @@ -889,6 +910,7 @@ config SND_VIRTUOSO config SND_VX222 tristate "Digigram VX222" + depends on HAS_IOPORT select SND_VX_LIB help Say Y here to include support for Digigram VX222 soundcards. @@ -898,6 +920,7 @@ config SND_VX222 config SND_YMFPCI tristate "Yamaha YMF724/740/744/754" + depends on HAS_IOPORT select SND_OPL3_LIB select SND_MPU401_UART select SND_AC97_CODEC diff --git a/sound/pcmcia/Kconfig b/sound/pcmcia/Kconfig index 10291c43cb18..2e3dfc1ff540 100644 --- a/sound/pcmcia/Kconfig +++ b/sound/pcmcia/Kconfig @@ -4,6 +4,7 @@ menuconfig SND_PCMCIA bool "PCMCIA sound devices" depends on PCMCIA + depends on HAS_IOPORT default y help Support for sound devices connected via the PCMCIA bus. -- cgit v1.2.3 From 09b62892ddeeb38c11979979e3c65a14dba5fdc6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:22 +0200 Subject: ALSA: rawmidi: Pass rawmidi directly to snd_rawmidi_kernel_open() snd_rawmidi_kernel_open() is used only internally from ALSA sequencer, so far, and parsing the card / device matching table at each open is redundant, as each sequencer client already gets the rawmidi object beforehand. This patch optimizes the path by passing the rawmidi object directly at snd_rawmidi_kernel_open(). This is also a preparation for the upcoming UMP rawmidi I/O support. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 2 +- sound/core/rawmidi.c | 17 ++++------------- sound/core/seq/seq_midi.c | 8 ++++---- 3 files changed, 9 insertions(+), 18 deletions(-) (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index e1f59b2940af..52b1cbfb2526 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -161,7 +161,7 @@ int snd_rawmidi_proceed(struct snd_rawmidi_substream *substream); /* main midi functions */ int snd_rawmidi_info_select(struct snd_card *card, struct snd_rawmidi_info *info); -int snd_rawmidi_kernel_open(struct snd_card *card, int device, int subdevice, +int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, int mode, struct snd_rawmidi_file *rfile); int snd_rawmidi_kernel_release(struct snd_rawmidi_file *rfile); int snd_rawmidi_output_params(struct snd_rawmidi_substream *substream, diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 7147fda66d93..589b75087d27 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -406,24 +406,15 @@ static int rawmidi_open_priv(struct snd_rawmidi *rmidi, int subdevice, int mode, } /* called from sound/core/seq/seq_midi.c */ -int snd_rawmidi_kernel_open(struct snd_card *card, int device, int subdevice, +int snd_rawmidi_kernel_open(struct snd_rawmidi *rmidi, int subdevice, int mode, struct snd_rawmidi_file *rfile) { - struct snd_rawmidi *rmidi; - int err = 0; + int err; if (snd_BUG_ON(!rfile)) return -EINVAL; - - mutex_lock(®ister_mutex); - rmidi = snd_rawmidi_search(card, device); - if (!rmidi) - err = -ENODEV; - else if (!try_module_get(rmidi->card->module)) - err = -ENXIO; - mutex_unlock(®ister_mutex); - if (err < 0) - return err; + if (!try_module_get(rmidi->card->module)) + return -ENXIO; mutex_lock(&rmidi->open_mutex); err = rawmidi_open_priv(rmidi, subdevice, mode, rfile); diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index 4589aac09154..2b5fff80de58 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -38,6 +38,7 @@ MODULE_PARM_DESC(input_buffer_size, "Input buffer size in bytes."); /* data for this midi synth driver */ struct seq_midisynth { struct snd_card *card; + struct snd_rawmidi *rmidi; int device; int subdevice; struct snd_rawmidi_file input_rfile; @@ -168,8 +169,7 @@ static int midisynth_subscribe(void *private_data, struct snd_seq_port_subscribe struct snd_rawmidi_params params; /* open midi port */ - err = snd_rawmidi_kernel_open(msynth->card, msynth->device, - msynth->subdevice, + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, SNDRV_RAWMIDI_LFLG_INPUT, &msynth->input_rfile); if (err < 0) { @@ -212,8 +212,7 @@ static int midisynth_use(void *private_data, struct snd_seq_port_subscribe *info struct snd_rawmidi_params params; /* open midi port */ - err = snd_rawmidi_kernel_open(msynth->card, msynth->device, - msynth->subdevice, + err = snd_rawmidi_kernel_open(msynth->rmidi, msynth->subdevice, SNDRV_RAWMIDI_LFLG_OUTPUT, &msynth->output_rfile); if (err < 0) { @@ -328,6 +327,7 @@ snd_seq_midisynth_probe(struct device *_dev) for (p = 0; p < ports; p++) { ms = &msynth[p]; + ms->rmidi = rmidi; if (snd_seq_midisynth_new(ms, card, device, p) < 0) goto __nomem; -- cgit v1.2.3 From fb3bd1215909866d6105224abe1566fd52695859 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:23 +0200 Subject: ALSA: rawmidi: Add ioctl callback to snd_rawmidi_global_ops A new callback, ioctl, is added to snd_rawmidi_global_ops for allowing the driver to deal with the own ioctls. This is another preparation patch for the upcoming UMP support. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 2 ++ sound/core/rawmidi.c | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 52b1cbfb2526..84413cfcdcb5 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -47,6 +47,8 @@ struct snd_rawmidi_global_ops { int (*dev_unregister) (struct snd_rawmidi * rmidi); void (*get_port_info)(struct snd_rawmidi *rmidi, int number, struct snd_seq_port_info *info); + long (*ioctl)(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp); }; struct snd_rawmidi_runtime { diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 589b75087d27..ab28cfc1fac8 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -893,6 +893,7 @@ static int snd_rawmidi_ioctl_status64(struct snd_rawmidi_file *rfile, static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct snd_rawmidi_file *rfile; + struct snd_rawmidi *rmidi; void __user *argp = (void __user *)arg; rfile = file->private_data; @@ -984,8 +985,10 @@ static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long } } default: - rmidi_dbg(rfile->rmidi, - "rawmidi: unknown command = 0x%x\n", cmd); + rmidi = rfile->rmidi; + if (rmidi->ops && rmidi->ops->ioctl) + return rmidi->ops->ioctl(rmidi, cmd, argp); + rmidi_dbg(rmidi, "rawmidi: unknown command = 0x%x\n", cmd); } return -ENOTTY; } -- cgit v1.2.3 From e3a8a5b726bdd903de52bee6ba7c935c09d07ee8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:24 +0200 Subject: ALSA: rawmidi: UMP support This patch adds the support helpers for UMP (Universal MIDI Packet) in ALSA core. The basic design is that a rawmidi instance is assigned to each UMP Endpoint. A UMP Endpoint provides a UMP stream, typically bidirectional (but can be also uni-directional, too), which may hold up to 16 UMP Groups, where each UMP (input/output) Group corresponds to the traditional MIDI I/O Endpoint. Additionally, the ALSA UMP abstraction provides the multiple UMP Blocks that can be assigned to each UMP Endpoint. A UMP Block is a metadata to hold the UMP Group clusters, and can represent the functions assigned to each UMP Group. A typical implementation of UMP Block is the Group Terminal Blocks of USB MIDI 2.0 specification. For distinguishing from the legacy byte-stream MIDI device, a new device "umpC*D*" will be created, instead of the standard (MIDI 1.0) devices "midiC*D*". The UMP instance can be identified by the new rawmidi info bit SNDRV_RAWMIDI_INFO_UMP, too. A UMP rawmidi device reads/writes only in 4-bytes words alignment, stored in CPU native endianness. The transmit and receive functions take care of the input/out data alignment, and may return zero or aligned size, and the params ioctl may return -EINVAL when the given input/output buffer size isn't aligned. A few new UMP-specific ioctls are added for obtaining the new UMP endpoint and block information. As of this commit, no ALSA sequencer instance is attached to UMP devices yet. They will be supported by later patches. Along with those changes, the protocol version for rawmidi is bumped to 2.0.3. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 8 ++ include/sound/ump.h | 118 +++++++++++++++++++++++ include/uapi/sound/asound.h | 57 ++++++++++- sound/core/Kconfig | 4 + sound/core/Makefile | 2 + sound/core/rawmidi.c | 139 ++++++++++++++++++-------- sound/core/rawmidi_compat.c | 4 + sound/core/ump.c | 230 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 521 insertions(+), 41 deletions(-) create mode 100644 include/sound/ump.h create mode 100644 sound/core/ump.c (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index 84413cfcdcb5..b8a230a7583b 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -63,6 +63,7 @@ struct snd_rawmidi_runtime { size_t avail_min; /* min avail for wakeup */ size_t avail; /* max used buffer for wakeup */ size_t xruns; /* over/underruns counter */ + size_t align; /* alignment (0 = byte stream, 3 = UMP) */ int buffer_ref; /* buffer reference count */ /* misc */ wait_queue_head_t sleep; @@ -148,6 +149,13 @@ int snd_rawmidi_new(struct snd_card *card, char *id, int device, void snd_rawmidi_set_ops(struct snd_rawmidi *rmidi, int stream, const struct snd_rawmidi_ops *ops); +/* internal */ +int snd_rawmidi_init(struct snd_rawmidi *rmidi, + struct snd_card *card, char *id, int device, + int output_count, int input_count, + unsigned int info_flags); +int snd_rawmidi_free(struct snd_rawmidi *rmidi); + /* callbacks */ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, diff --git a/include/sound/ump.h b/include/sound/ump.h new file mode 100644 index 000000000000..8a3ac97cd1d3 --- /dev/null +++ b/include/sound/ump.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Universal MIDI Packet (UMP) Support + */ +#ifndef __SOUND_UMP_H +#define __SOUND_UMP_H + +#include + +struct snd_ump_endpoint; +struct snd_ump_block; + +struct snd_ump_endpoint { + struct snd_rawmidi core; /* raw UMP access */ + + struct snd_ump_endpoint_info info; + + void *private_data; + void (*private_free)(struct snd_ump_endpoint *ump); + + struct list_head block_list; /* list of snd_ump_block objects */ +}; + +struct snd_ump_block { + struct snd_ump_block_info info; + struct snd_ump_endpoint *ump; + + void *private_data; + void (*private_free)(struct snd_ump_block *blk); + + struct list_head list; +}; + +#define rawmidi_to_ump(rmidi) container_of(rmidi, struct snd_ump_endpoint, core) + +int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, + int output, int input, + struct snd_ump_endpoint **ump_ret); +int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, + unsigned int direction, unsigned int first_group, + unsigned int num_groups, struct snd_ump_block **blk_ret); + +/* + * Some definitions for UMP + */ + +/* MIDI 2.0 Message Type */ +enum { + UMP_MSG_TYPE_UTILITY = 0x00, + UMP_MSG_TYPE_SYSTEM = 0x01, + UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE = 0x02, + UMP_MSG_TYPE_DATA = 0x03, + UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE = 0x04, + UMP_MSG_TYPE_EXTENDED_DATA = 0x05, +}; + +/* MIDI 2.0 SysEx / Data Status; same values for both 7-bit and 8-bit SysEx */ +enum { + UMP_SYSEX_STATUS_SINGLE = 0, + UMP_SYSEX_STATUS_START = 1, + UMP_SYSEX_STATUS_CONTINUE = 2, + UMP_SYSEX_STATUS_END = 3, +}; + +/* + * Helpers for retrieving / filling bits from UMP + */ +/* get the message type (4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_type(u32 data) +{ + return data >> 28; +} + +/* get the group number (0-based, 4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_group(u32 data) +{ + return (data >> 24) & 0x0f; +} + +/* get the MIDI status code (4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_status_code(u32 data) +{ + return (data >> 20) & 0x0f; +} + +/* get the MIDI channel number (0-based, 4bit) from a UMP packet (header) */ +static inline unsigned char ump_message_channel(u32 data) +{ + return (data >> 16) & 0x0f; +} + +/* get the MIDI status + channel combo byte (8bit) from a UMP packet (header) */ +static inline unsigned char ump_message_status_channel(u32 data) +{ + return (data >> 16) & 0xff; +} + +/* compose a UMP packet (header) from type, group and status values */ +static inline u32 ump_compose(unsigned char type, unsigned char group, + unsigned char status, unsigned char channel) +{ + return ((u32)type << 28) | ((u32)group << 24) | ((u32)status << 20) | + ((u32)channel << 16); +} + +/* get SysEx message status (for both 7 and 8bits) from a UMP packet (header) */ +static inline unsigned char ump_sysex_message_status(u32 data) +{ + return (data >> 20) & 0xf; +} + +/* get SysEx message length (for both 7 and 8bits) from a UMP packet (header) */ +static inline unsigned char ump_sysex_message_length(u32 data) +{ + return (data >> 16) & 0xf; +} + +#endif /* __SOUND_UMP_H */ diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 0aa955aa8246..b001df4b335e 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -708,7 +708,7 @@ enum { * Raw MIDI section - /dev/snd/midi?? */ -#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 2) +#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 3) enum { SNDRV_RAWMIDI_STREAM_OUTPUT = 0, @@ -719,6 +719,7 @@ enum { #define SNDRV_RAWMIDI_INFO_OUTPUT 0x00000001 #define SNDRV_RAWMIDI_INFO_INPUT 0x00000002 #define SNDRV_RAWMIDI_INFO_DUPLEX 0x00000004 +#define SNDRV_RAWMIDI_INFO_UMP 0x00000008 struct snd_rawmidi_info { unsigned int device; /* RO/WR (control): device number */ @@ -779,6 +780,57 @@ struct snd_rawmidi_status { }; #endif +/* UMP EP Protocol / JRTS capability bits */ +#define SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK 0x0300 +#define SNDRV_UMP_EP_INFO_PROTO_MIDI1 0x0100 /* MIDI 1.0 */ +#define SNDRV_UMP_EP_INFO_PROTO_MIDI2 0x0200 /* MIDI 2.0 */ +#define SNDRV_UMP_EP_INFO_PROTO_JRTS_MASK 0x0003 +#define SNDRV_UMP_EP_INFO_PROTO_JRTS_TX 0x0001 /* JRTS Transmit */ +#define SNDRV_UMP_EP_INFO_PROTO_JRTS_RX 0x0002 /* JRTS Receive */ + +/* UMP Endpoint information */ +struct snd_ump_endpoint_info { + int card; /* card number */ + int device; /* device number */ + unsigned int flags; /* additional info */ + unsigned int protocol_caps; /* protocol capabilities */ + unsigned int protocol; /* current protocol */ + unsigned int num_blocks; /* # of function blocks */ + unsigned short version; /* UMP major/minor version */ + unsigned short padding[7]; + unsigned char name[128]; /* endpoint name string */ + unsigned char product_id[128]; /* unique product id string */ + unsigned char reserved[32]; +} __packed; + +/* UMP direction */ +#define SNDRV_UMP_DIR_INPUT 0x01 +#define SNDRV_UMP_DIR_OUTPUT 0x02 +#define SNDRV_UMP_DIR_BIDIRECTION 0x03 + +/* UMP block info flags */ +#define SNDRV_UMP_BLOCK_IS_MIDI1 (1U << 0) /* MIDI 1.0 port w/o restrict */ +#define SNDRV_UMP_BLOCK_IS_LOWSPEED (1U << 1) /* 31.25Kbps B/W MIDI1 port */ + +/* UMP groups and blocks */ +#define SNDRV_UMP_MAX_GROUPS 16 +#define SNDRV_UMP_MAX_BLOCKS 32 + +/* UMP Block information */ +struct snd_ump_block_info { + int card; /* card number */ + int device; /* device number */ + unsigned char block_id; /* block ID (R/W) */ + unsigned char direction; /* UMP direction */ + unsigned char active; /* Activeness */ + unsigned char first_group; /* first group ID */ + unsigned char num_groups; /* number of groups */ + unsigned char padding[3]; + unsigned int flags; /* various info flags */ + unsigned char name[128]; /* block name string */ + unsigned char reserved[32]; +} __packed; + #define SNDRV_RAWMIDI_IOCTL_PVERSION _IOR('W', 0x00, int) #define SNDRV_RAWMIDI_IOCTL_INFO _IOR('W', 0x01, struct snd_rawmidi_info) #define SNDRV_RAWMIDI_IOCTL_USER_PVERSION _IOW('W', 0x02, int) @@ -786,6 +838,9 @@ struct snd_rawmidi_status { #define SNDRV_RAWMIDI_IOCTL_STATUS _IOWR('W', 0x20, struct snd_rawmidi_status) #define SNDRV_RAWMIDI_IOCTL_DROP _IOW('W', 0x30, int) #define SNDRV_RAWMIDI_IOCTL_DRAIN _IOW('W', 0x31, int) +/* Additional ioctls for UMP rawmidi devices */ +#define SNDRV_UMP_IOCTL_ENDPOINT_INFO _IOR('W', 0x40, struct snd_ump_endpoint_info) +#define SNDRV_UMP_IOCTL_BLOCK_INFO _IOR('W', 0x41, struct snd_ump_block_info) /* * Timer section - /dev/snd/timer diff --git a/sound/core/Kconfig b/sound/core/Kconfig index 12990d9a4dff..eb1c6c930de9 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -26,6 +26,10 @@ config SND_RAWMIDI tristate select SND_SEQ_DEVICE if SND_SEQUENCER != n +config SND_UMP + tristate + select SND_RAWMIDI + config SND_COMPRESS_OFFLOAD tristate diff --git a/sound/core/Makefile b/sound/core/Makefile index 2762f03d9b7b..562a05edbc50 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -28,6 +28,7 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o snd-ctl-led-objs := control_led.o snd-rawmidi-objs := rawmidi.o +snd-ump-objs := ump.o snd-timer-objs := timer.o snd-hrtimer-objs := hrtimer.o snd-rtctimer-objs := rtctimer.o @@ -45,6 +46,7 @@ obj-$(CONFIG_SND_PCM) += snd-pcm.o obj-$(CONFIG_SND_DMAENGINE_PCM) += snd-pcm-dmaengine.o obj-$(CONFIG_SND_SEQ_DEVICE) += snd-seq-device.o obj-$(CONFIG_SND_RAWMIDI) += snd-rawmidi.o +obj-$(CONFIG_SND_UMP) += snd-ump.o obj-$(CONFIG_SND_OSSEMUL) += oss/ obj-$(CONFIG_SND_SEQUENCER) += seq/ diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index ab28cfc1fac8..6360e2239a63 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -21,6 +21,7 @@ #include #include #include +#include MODULE_AUTHOR("Jaroslav Kysela "); MODULE_DESCRIPTION("Midlevel RawMidi code for ALSA."); @@ -35,7 +36,6 @@ module_param_array(amidi_map, int, NULL, 0444); MODULE_PARM_DESC(amidi_map, "Raw MIDI device number assigned to 2nd OSS device."); #endif /* CONFIG_SND_OSSEMUL */ -static int snd_rawmidi_free(struct snd_rawmidi *rmidi); static int snd_rawmidi_dev_free(struct snd_device *device); static int snd_rawmidi_dev_register(struct snd_device *device); static int snd_rawmidi_dev_disconnect(struct snd_device *device); @@ -73,6 +73,9 @@ struct snd_rawmidi_status64 { #define SNDRV_RAWMIDI_IOCTL_STATUS64 _IOWR('W', 0x20, struct snd_rawmidi_status64) +#define rawmidi_is_ump(rmidi) \ + (IS_ENABLED(CONFIG_SND_UMP) && ((rmidi)->info_flags & SNDRV_RAWMIDI_INFO_UMP)) + static struct snd_rawmidi *snd_rawmidi_search(struct snd_card *card, int device) { struct snd_rawmidi *rawmidi; @@ -181,9 +184,23 @@ static int snd_rawmidi_runtime_create(struct snd_rawmidi_substream *substream) } runtime->appl_ptr = runtime->hw_ptr = 0; substream->runtime = runtime; + if (rawmidi_is_ump(substream->rmidi)) + runtime->align = 3; return 0; } +/* get the current alignment (either 0 or 3) */ +static inline int get_align(struct snd_rawmidi_runtime *runtime) +{ + if (IS_ENABLED(CONFIG_SND_UMP)) + return runtime->align; + else + return 0; +} + +/* get the trimmed size with the current alignment */ +#define get_aligned_size(runtime, size) ((size) & ~get_align(runtime)) + static int snd_rawmidi_runtime_free(struct snd_rawmidi_substream *substream) { struct snd_rawmidi_runtime *runtime = substream->runtime; @@ -721,6 +738,8 @@ static int resize_runtime_buffer(struct snd_rawmidi_substream *substream, return -EINVAL; if (params->avail_min < 1 || params->avail_min > params->buffer_size) return -EINVAL; + if (params->buffer_size & get_align(runtime)) + return -EINVAL; if (params->buffer_size != runtime->buffer_size) { newbuf = kvzalloc(params->buffer_size, GFP_KERNEL); if (!newbuf) @@ -1046,12 +1065,13 @@ static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream, struct snd_rawmidi_framing_tstamp frame = { .tv_sec = tstamp->tv_sec, .tv_nsec = tstamp->tv_nsec }; int orig_count = src_count; int frame_size = sizeof(struct snd_rawmidi_framing_tstamp); + int align = get_align(runtime); BUILD_BUG_ON(frame_size != 0x20); if (snd_BUG_ON((runtime->hw_ptr & 0x1f) != 0)) return -EINVAL; - while (src_count > 0) { + while (src_count > align) { if ((int)(runtime->buffer_size - runtime->avail) < frame_size) { runtime->xruns += src_count; break; @@ -1059,7 +1079,9 @@ static int receive_with_tstamp_framing(struct snd_rawmidi_substream *substream, if (src_count >= SNDRV_RAWMIDI_FRAMING_DATA_LENGTH) frame.length = SNDRV_RAWMIDI_FRAMING_DATA_LENGTH; else { - frame.length = src_count; + frame.length = get_aligned_size(runtime, src_count); + if (!frame.length) + break; memset(frame.data, 0, SNDRV_RAWMIDI_FRAMING_DATA_LENGTH); } memcpy(frame.data, buffer, frame.length); @@ -1123,6 +1145,10 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, goto unlock; } + count = get_aligned_size(runtime, count); + if (!count) + goto unlock; + if (substream->framing == SNDRV_RAWMIDI_MODE_FRAMING_TSTAMP) { result = receive_with_tstamp_framing(substream, buffer, count, &ts64); } else if (count == 1) { /* special case, faster code */ @@ -1142,6 +1168,9 @@ int snd_rawmidi_receive(struct snd_rawmidi_substream *substream, count1 = count; if (count1 > (int)(runtime->buffer_size - runtime->avail)) count1 = runtime->buffer_size - runtime->avail; + count1 = get_aligned_size(runtime, count1); + if (!count1) + goto unlock; memcpy(runtime->buffer + runtime->hw_ptr, buffer, count1); runtime->hw_ptr += count1; runtime->hw_ptr %= runtime->buffer_size; @@ -1342,12 +1371,18 @@ static int __snd_rawmidi_transmit_peek(struct snd_rawmidi_substream *substream, count1 = count; if (count1 > (int)(runtime->buffer_size - runtime->avail)) count1 = runtime->buffer_size - runtime->avail; + count1 = get_aligned_size(runtime, count1); + if (!count1) + goto __skip; memcpy(buffer, runtime->buffer + runtime->hw_ptr, count1); count -= count1; result += count1; if (count > 0) { if (count > (int)(runtime->buffer_size - runtime->avail - count1)) count = runtime->buffer_size - runtime->avail - count1; + count = get_aligned_size(runtime, count); + if (!count) + goto __skip; memcpy(buffer + count1, runtime->buffer, count); result += count; } @@ -1404,6 +1439,7 @@ static int __snd_rawmidi_transmit_ack(struct snd_rawmidi_substream *substream, return -EINVAL; } snd_BUG_ON(runtime->avail + count > runtime->buffer_size); + count = get_aligned_size(runtime, count); runtime->hw_ptr += count; runtime->hw_ptr %= runtime->buffer_size; runtime->avail += count; @@ -1690,6 +1726,9 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, rmidi = entry->private_data; snd_iprintf(buffer, "%s\n\n", rmidi->name); + if (IS_ENABLED(CONFIG_SND_UMP)) + snd_iprintf(buffer, "Type: %s\n", + rawmidi_is_ump(rmidi) ? "UMP" : "Legacy"); mutex_lock(&rmidi->open_mutex); if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) { list_for_each_entry(substream, @@ -1800,25 +1839,12 @@ static void release_rawmidi_device(struct device *dev) kfree(container_of(dev, struct snd_rawmidi, dev)); } -/** - * snd_rawmidi_new - create a rawmidi instance - * @card: the card instance - * @id: the id string - * @device: the device index - * @output_count: the number of output streams - * @input_count: the number of input streams - * @rrawmidi: the pointer to store the new rawmidi instance - * - * Creates a new rawmidi instance. - * Use snd_rawmidi_set_ops() to set the operators to the new instance. - * - * Return: Zero if successful, or a negative error code on failure. - */ -int snd_rawmidi_new(struct snd_card *card, char *id, int device, - int output_count, int input_count, - struct snd_rawmidi **rrawmidi) +/* used for both rawmidi and ump */ +int snd_rawmidi_init(struct snd_rawmidi *rmidi, + struct snd_card *card, char *id, int device, + int output_count, int input_count, + unsigned int info_flags) { - struct snd_rawmidi *rmidi; int err; static const struct snd_device_ops ops = { .dev_free = snd_rawmidi_dev_free, @@ -1826,50 +1852,78 @@ int snd_rawmidi_new(struct snd_card *card, char *id, int device, .dev_disconnect = snd_rawmidi_dev_disconnect, }; - if (snd_BUG_ON(!card)) - return -ENXIO; - if (rrawmidi) - *rrawmidi = NULL; - rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); - if (!rmidi) - return -ENOMEM; rmidi->card = card; rmidi->device = device; mutex_init(&rmidi->open_mutex); init_waitqueue_head(&rmidi->open_wait); INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams); INIT_LIST_HEAD(&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams); + rmidi->info_flags = info_flags; if (id != NULL) strscpy(rmidi->id, id, sizeof(rmidi->id)); snd_device_initialize(&rmidi->dev, card); rmidi->dev.release = release_rawmidi_device; - dev_set_name(&rmidi->dev, "midiC%iD%i", card->number, device); + if (rawmidi_is_ump(rmidi)) + dev_set_name(&rmidi->dev, "umpC%iD%i", card->number, device); + else + dev_set_name(&rmidi->dev, "midiC%iD%i", card->number, device); err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], SNDRV_RAWMIDI_STREAM_INPUT, input_count); if (err < 0) - goto error; + return err; err = snd_rawmidi_alloc_substreams(rmidi, &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], SNDRV_RAWMIDI_STREAM_OUTPUT, output_count); if (err < 0) - goto error; + return err; err = snd_device_new(card, SNDRV_DEV_RAWMIDI, rmidi, &ops); if (err < 0) - goto error; + return err; + return 0; +} +EXPORT_SYMBOL_GPL(snd_rawmidi_init); + +/** + * snd_rawmidi_new - create a rawmidi instance + * @card: the card instance + * @id: the id string + * @device: the device index + * @output_count: the number of output streams + * @input_count: the number of input streams + * @rrawmidi: the pointer to store the new rawmidi instance + * + * Creates a new rawmidi instance. + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_rawmidi_new(struct snd_card *card, char *id, int device, + int output_count, int input_count, + struct snd_rawmidi **rrawmidi) +{ + struct snd_rawmidi *rmidi; + int err; + if (rrawmidi) + *rrawmidi = NULL; + rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); + if (!rmidi) + return -ENOMEM; + err = snd_rawmidi_init(rmidi, card, id, device, + output_count, input_count, 0); + if (err < 0) { + snd_rawmidi_free(rmidi); + return err; + } if (rrawmidi) *rrawmidi = rmidi; return 0; - - error: - snd_rawmidi_free(rmidi); - return err; } EXPORT_SYMBOL(snd_rawmidi_new); @@ -1884,7 +1938,8 @@ static void snd_rawmidi_free_substreams(struct snd_rawmidi_str *stream) } } -static int snd_rawmidi_free(struct snd_rawmidi *rmidi) +/* called from ump.c, too */ +int snd_rawmidi_free(struct snd_rawmidi *rmidi) { if (!rmidi) return 0; @@ -1901,6 +1956,7 @@ static int snd_rawmidi_free(struct snd_rawmidi *rmidi) put_device(&rmidi->dev); return 0; } +EXPORT_SYMBOL_GPL(snd_rawmidi_free); static int snd_rawmidi_dev_free(struct snd_device *device) { @@ -1951,7 +2007,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device) } #ifdef CONFIG_SND_OSSEMUL rmidi->ossreg = 0; - if ((int)rmidi->device == midi_map[rmidi->card->number]) { + if (!rawmidi_is_ump(rmidi) && + (int)rmidi->device == midi_map[rmidi->card->number]) { if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 0, &snd_rawmidi_f_ops, rmidi) < 0) { @@ -1965,7 +2022,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device) #endif } } - if ((int)rmidi->device == amidi_map[rmidi->card->number]) { + if (!rawmidi_is_ump(rmidi) && + (int)rmidi->device == amidi_map[rmidi->card->number]) { if (snd_register_oss_device(SNDRV_OSS_DEVICE_TYPE_MIDI, rmidi->card, 1, &snd_rawmidi_f_ops, rmidi) < 0) { @@ -1989,7 +2047,8 @@ static int snd_rawmidi_dev_register(struct snd_device *device) } rmidi->proc_entry = entry; #if IS_ENABLED(CONFIG_SND_SEQUENCER) - if (!rmidi->ops || !rmidi->ops->dev_register) { /* own registration mechanism */ + /* no own registration mechanism? */ + if (!rmidi->ops || !rmidi->ops->dev_register) { if (snd_seq_device_new(rmidi->card, rmidi->device, SNDRV_SEQ_DEV_ID_MIDISYNTH, 0, &rmidi->seq_dev) >= 0) { rmidi->seq_dev->private_data = rmidi; rmidi->seq_dev->private_free = snd_rawmidi_dev_seq_free; diff --git a/sound/core/rawmidi_compat.c b/sound/core/rawmidi_compat.c index 68a93443583c..b81b30d82f88 100644 --- a/sound/core/rawmidi_compat.c +++ b/sound/core/rawmidi_compat.c @@ -111,6 +111,10 @@ static long snd_rawmidi_ioctl_compat(struct file *file, unsigned int cmd, unsign case SNDRV_RAWMIDI_IOCTL_INFO: case SNDRV_RAWMIDI_IOCTL_DROP: case SNDRV_RAWMIDI_IOCTL_DRAIN: +#if IS_ENABLED(CONFIG_SND_UMP) + case SNDRV_UMP_IOCTL_ENDPOINT_INFO: + case SNDRV_UMP_IOCTL_BLOCK_INFO: +#endif return snd_rawmidi_ioctl(file, cmd, (unsigned long)argp); case SNDRV_RAWMIDI_IOCTL_PARAMS32: return snd_rawmidi_ioctl_params_compat(rfile, argp); diff --git a/sound/core/ump.c b/sound/core/ump.c new file mode 100644 index 000000000000..ee57ba1ee989 --- /dev/null +++ b/sound/core/ump.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal MIDI Packet (UMP) support + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args) +#define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args) +#define ump_info(ump, fmt, args...) dev_info(&(ump)->core.dev, fmt, ##args) +#define ump_dbg(ump, fmt, args...) dev_dbg(&(ump)->core.dev, fmt, ##args) + +static int snd_ump_dev_register(struct snd_rawmidi *rmidi); +static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi); +static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp); + +static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { + .dev_register = snd_ump_dev_register, + .dev_unregister = snd_ump_dev_unregister, + .ioctl = snd_ump_ioctl, +}; + +static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + struct snd_ump_block *fb; + + while (!list_empty(&ump->block_list)) { + fb = list_first_entry(&ump->block_list, struct snd_ump_block, + list); + list_del(&fb->list); + if (fb->private_free) + fb->private_free(fb); + kfree(fb); + } + + if (ump->private_free) + ump->private_free(ump); +} + +/** + * snd_ump_endpoint_new - create a UMP Endpoint object + * @card: the card instance + * @id: the id string for rawmidi + * @device: the device index for rawmidi + * @output: 1 for enabling output + * @input: 1 for enabling input + * @ump_ret: the pointer to store the new UMP instance + * + * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi + * instance with one input and/or one output rawmidi stream (either uni- + * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks + * that consist of one or multiple UMP Groups. + * + * Use snd_rawmidi_set_ops() to set the operators to the new instance. + * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself + * depending on the given @output and @input. + * + * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device + * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is + * created. + * + * Return: Zero if successful, or a negative error code on failure. + */ +int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, + int output, int input, + struct snd_ump_endpoint **ump_ret) +{ + unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP; + struct snd_ump_endpoint *ump; + int err; + + if (input) + info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + if (output) + info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + if (input && output) + info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + ump = kzalloc(sizeof(*ump), GFP_KERNEL); + if (!ump) + return -ENOMEM; + INIT_LIST_HEAD(&ump->block_list); + err = snd_rawmidi_init(&ump->core, card, id, device, + output, input, info_flags); + if (err < 0) { + snd_rawmidi_free(&ump->core); + return err; + } + + ump->info.card = card->number; + ump->info.device = device; + + ump->core.private_free = snd_ump_endpoint_free; + ump->core.ops = &snd_ump_rawmidi_ops; + + ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id); + *ump_ret = ump; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_endpoint_new); + +/* + * Device register / unregister hooks; + * do nothing, placeholders for avoiding the default rawmidi handling + */ +static int snd_ump_dev_register(struct snd_rawmidi *rmidi) +{ + return 0; +} + +static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi) +{ + return 0; +} + +static struct snd_ump_block * +snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id) +{ + struct snd_ump_block *fb; + + list_for_each_entry(fb, &ump->block_list, list) { + if (fb->info.block_id == id) + return fb; + } + return NULL; +} + +/** + * snd_ump_block_new - Create a UMP block + * @ump: UMP object + * @blk: block ID number to create + * @direction: direction (in/out/bidirection) + * @first_group: the first group ID (0-based) + * @num_groups: the number of groups in this block + * @blk_ret: the pointer to store the resultant block object + */ +int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, + unsigned int direction, unsigned int first_group, + unsigned int num_groups, struct snd_ump_block **blk_ret) +{ + struct snd_ump_block *fb, *p; + + if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS) + return -EINVAL; + + if (snd_ump_get_block(ump, blk)) + return -EBUSY; + + fb = kzalloc(sizeof(*fb), GFP_KERNEL); + if (!fb) + return -ENOMEM; + + fb->ump = ump; + fb->info.card = ump->info.card; + fb->info.device = ump->info.device; + fb->info.block_id = blk; + if (blk >= ump->info.num_blocks) + ump->info.num_blocks = blk + 1; + fb->info.direction = direction; + fb->info.active = 1; + fb->info.first_group = first_group; + fb->info.num_groups = num_groups; + /* fill the default name, may be overwritten to a better name */ + snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d", + first_group + 1, first_group + num_groups); + + /* put the entry in the ordered list */ + list_for_each_entry(p, &ump->block_list, list) { + if (p->info.block_id > blk) { + list_add_tail(&fb->list, &p->list); + goto added; + } + } + list_add_tail(&fb->list, &ump->block_list); + + added: + ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name); + *blk_ret = fb; + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_block_new); + +static int snd_ump_ioctl_block(struct snd_ump_endpoint *ump, + struct snd_ump_block_info __user *argp) +{ + struct snd_ump_block *fb; + unsigned char id; + + if (get_user(id, &argp->block_id)) + return -EFAULT; + fb = snd_ump_get_block(ump, id); + if (!fb) + return -ENOENT; + if (copy_to_user(argp, &fb->info, sizeof(fb->info))) + return -EFAULT; + return 0; +} + +/* + * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl() + */ +static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, + void __user *argp) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + + switch (cmd) { + case SNDRV_UMP_IOCTL_ENDPOINT_INFO: + if (copy_to_user(argp, &ump->info, sizeof(ump->info))) + return -EFAULT; + return 0; + case SNDRV_UMP_IOCTL_BLOCK_INFO: + return snd_ump_ioctl_block(ump, argp); + default: + ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd); + return -ENOTTY; + } +} + +MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 127ae6f6dad2edb2201e27b7e6fa72994b537fad Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:25 +0200 Subject: ALSA: rawmidi: Skip UMP devices at SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE Applications may look for rawmidi devices with the ioctl SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE. Returning a UMP device from this ioctl may confuse the existing applications that support only the legacy rawmidi. This patch changes the code to skip the UMP devices from the lookup for avoiding the confusion, and introduces a new ioctl to look for the UMP devices instead. Along with this change, bump the CTL protocol version to 2.0.9. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-5-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 3 ++- sound/core/rawmidi.c | 57 ++++++++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 22 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index b001df4b335e..1e4a21036109 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -1016,7 +1016,7 @@ struct snd_timer_tread { * * ****************************************************************************/ -#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 8) +#define SNDRV_CTL_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 9) struct snd_ctl_card_info { int card; /* card number */ @@ -1177,6 +1177,7 @@ struct snd_ctl_tlv { #define SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE _IOWR('U', 0x40, int) #define SNDRV_CTL_IOCTL_RAWMIDI_INFO _IOWR('U', 0x41, struct snd_rawmidi_info) #define SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE _IOW('U', 0x42, int) +#define SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE _IOWR('U', 0x43, int) #define SNDRV_CTL_IOCTL_POWER _IOWR('U', 0xd0, int) #define SNDRV_CTL_IOCTL_POWER_STATE _IOR('U', 0xd1, int) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 6360e2239a63..9936ed282b85 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -1012,6 +1012,37 @@ static long snd_rawmidi_ioctl(struct file *file, unsigned int cmd, unsigned long return -ENOTTY; } +/* ioctl to find the next device; either legacy or UMP depending on @find_ump */ +static int snd_rawmidi_next_device(struct snd_card *card, int __user *argp, + bool find_ump) + +{ + struct snd_rawmidi *rmidi; + int device; + bool is_ump; + + if (get_user(device, argp)) + return -EFAULT; + if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */ + device = SNDRV_RAWMIDI_DEVICES - 1; + mutex_lock(®ister_mutex); + device = device < 0 ? 0 : device + 1; + for (; device < SNDRV_RAWMIDI_DEVICES; device++) { + rmidi = snd_rawmidi_search(card, device); + if (!rmidi) + continue; + is_ump = rawmidi_is_ump(rmidi); + if (find_ump == is_ump) + break; + } + if (device == SNDRV_RAWMIDI_DEVICES) + device = -1; + mutex_unlock(®ister_mutex); + if (put_user(device, argp)) + return -EFAULT; + return 0; +} + static int snd_rawmidi_control_ioctl(struct snd_card *card, struct snd_ctl_file *control, unsigned int cmd, @@ -1021,27 +1052,11 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card, switch (cmd) { case SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE: - { - int device; - - if (get_user(device, (int __user *)argp)) - return -EFAULT; - if (device >= SNDRV_RAWMIDI_DEVICES) /* next device is -1 */ - device = SNDRV_RAWMIDI_DEVICES - 1; - mutex_lock(®ister_mutex); - device = device < 0 ? 0 : device + 1; - while (device < SNDRV_RAWMIDI_DEVICES) { - if (snd_rawmidi_search(card, device)) - break; - device++; - } - if (device == SNDRV_RAWMIDI_DEVICES) - device = -1; - mutex_unlock(®ister_mutex); - if (put_user(device, (int __user *)argp)) - return -EFAULT; - return 0; - } + return snd_rawmidi_next_device(card, argp, false); +#if IS_ENABLED(CONFIG_SND_UMP) + case SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE: + return snd_rawmidi_next_device(card, argp, true); +#endif case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: { int val; -- cgit v1.2.3 From 30fc139260d46e9bdc06e46eec91e9ff61eb387e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:26 +0200 Subject: ALSA: ump: Add ioctls to inquiry UMP EP and Block info via control API It'd be convenient to have ioctls to inquiry the UMP Endpoint and UMP Block information directly via the control API without opening the rawmidi interface, just like SNDRV_CTL_IOCTL_RAWMIDI_INFO. This patch extends the rawmidi ioctl handler to support those; new ioctls, SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO and SNDRV_CTL_IOCTL_UMP_BLOCK_INFO, return the snd_ump_endpoint and snd_ump_block data that is specified by the device field, respectively. Suggested-by: Jaroslav Kysela Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-6-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 2 ++ sound/core/rawmidi.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 1e4a21036109..5c5f41dd4001 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -1178,6 +1178,8 @@ struct snd_ctl_tlv { #define SNDRV_CTL_IOCTL_RAWMIDI_INFO _IOWR('U', 0x41, struct snd_rawmidi_info) #define SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE _IOW('U', 0x42, int) #define SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE _IOWR('U', 0x43, int) +#define SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO _IOWR('U', 0x44, struct snd_ump_endpoint_info) +#define SNDRV_CTL_IOCTL_UMP_BLOCK_INFO _IOWR('U', 0x45, struct snd_ump_block_info) #define SNDRV_CTL_IOCTL_POWER _IOWR('U', 0xd0, int) #define SNDRV_CTL_IOCTL_POWER_STATE _IOR('U', 0xd1, int) diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index 9936ed282b85..ffb5b58105f4 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -1043,6 +1043,28 @@ static int snd_rawmidi_next_device(struct snd_card *card, int __user *argp, return 0; } +#if IS_ENABLED(CONFIG_SND_UMP) +/* inquiry of UMP endpoint and block info via control API */ +static int snd_rawmidi_call_ump_ioctl(struct snd_card *card, int cmd, + void __user *argp) +{ + struct snd_ump_endpoint_info __user *info = argp; + struct snd_rawmidi *rmidi; + int device, ret; + + if (get_user(device, &info->device)) + return -EFAULT; + mutex_lock(®ister_mutex); + rmidi = snd_rawmidi_search(card, device); + if (rmidi && rmidi->ops && rmidi->ops->ioctl) + ret = rmidi->ops->ioctl(rmidi, cmd, argp); + else + ret = -ENXIO; + mutex_unlock(®ister_mutex); + return ret; +} +#endif + static int snd_rawmidi_control_ioctl(struct snd_card *card, struct snd_ctl_file *control, unsigned int cmd, @@ -1056,6 +1078,10 @@ static int snd_rawmidi_control_ioctl(struct snd_card *card, #if IS_ENABLED(CONFIG_SND_UMP) case SNDRV_CTL_IOCTL_UMP_NEXT_DEVICE: return snd_rawmidi_next_device(card, argp, true); + case SNDRV_CTL_IOCTL_UMP_ENDPOINT_INFO: + return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_ENDPOINT_INFO, argp); + case SNDRV_CTL_IOCTL_UMP_BLOCK_INFO: + return snd_rawmidi_call_ump_ioctl(card, SNDRV_UMP_IOCTL_BLOCK_INFO, argp); #endif case SNDRV_CTL_IOCTL_RAWMIDI_PREFER_SUBDEVICE: { -- cgit v1.2.3 From fa030f666d2431be5310c0c0fef254e2e205d4cc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:27 +0200 Subject: ALSA: ump: Additional proc output UMP devices may have more interesting information than the traditional rawmidi. Extend the rawmidi_global_ops to allow the optional proc info output and show some more bits in the proc file for UMP. Note that the "Groups" field shows the first and the last UMP Groups, and both numbers are 1-based (i.e. the first group is 1). Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-7-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/rawmidi.h | 3 +++ sound/core/rawmidi.c | 2 ++ sound/core/ump.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) (limited to 'sound') diff --git a/include/sound/rawmidi.h b/include/sound/rawmidi.h index b8a230a7583b..b0197b1d1fe4 100644 --- a/include/sound/rawmidi.h +++ b/include/sound/rawmidi.h @@ -18,6 +18,7 @@ #if IS_ENABLED(CONFIG_SND_SEQUENCER) #include #endif +#include /* * Raw MIDI interface @@ -49,6 +50,8 @@ struct snd_rawmidi_global_ops { struct snd_seq_port_info *info); long (*ioctl)(struct snd_rawmidi *rmidi, unsigned int cmd, void __user *argp); + void (*proc_read)(struct snd_info_entry *entry, + struct snd_info_buffer *buf); }; struct snd_rawmidi_runtime { diff --git a/sound/core/rawmidi.c b/sound/core/rawmidi.c index ffb5b58105f4..2d3cec908154 100644 --- a/sound/core/rawmidi.c +++ b/sound/core/rawmidi.c @@ -1770,6 +1770,8 @@ static void snd_rawmidi_proc_info_read(struct snd_info_entry *entry, if (IS_ENABLED(CONFIG_SND_UMP)) snd_iprintf(buffer, "Type: %s\n", rawmidi_is_ump(rmidi) ? "UMP" : "Legacy"); + if (rmidi->ops->proc_read) + rmidi->ops->proc_read(entry, buffer); mutex_lock(&rmidi->open_mutex); if (rmidi->info_flags & SNDRV_RAWMIDI_INFO_OUTPUT) { list_for_each_entry(substream, diff --git a/sound/core/ump.c b/sound/core/ump.c index ee57ba1ee989..651cd3752719 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -21,11 +21,14 @@ static int snd_ump_dev_register(struct snd_rawmidi *rmidi); static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi); static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, void __user *argp); +static void snd_ump_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer); static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .dev_register = snd_ump_dev_register, .dev_unregister = snd_ump_dev_unregister, .ioctl = snd_ump_ioctl, + .proc_read = snd_ump_proc_read, }; static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) @@ -226,5 +229,51 @@ static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, } } +static const char *ump_direction_string(int dir) +{ + switch (dir) { + case SNDRV_UMP_DIR_INPUT: + return "input"; + case SNDRV_UMP_DIR_OUTPUT: + return "output"; + case SNDRV_UMP_DIR_BIDIRECTION: + return "bidirection"; + default: + return "unknown"; + } +} + +/* Additional proc file output */ +static void snd_ump_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_rawmidi *rmidi = entry->private_data; + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + struct snd_ump_block *fb; + + snd_iprintf(buffer, "EP Name: %s\n", ump->info.name); + snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id); + snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version); + snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps); + snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol); + snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); + + list_for_each_entry(fb, &ump->block_list, list) { + snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id, + fb->info.name); + snd_iprintf(buffer, " Direction: %s\n", + ump_direction_string(fb->info.direction)); + snd_iprintf(buffer, " Active: %s\n", + fb->info.active ? "Yes" : "No"); + snd_iprintf(buffer, " Groups: %d-%d\n", + fb->info.first_group + 1, + fb->info.first_group + fb->info.num_groups); + snd_iprintf(buffer, " Is MIDI1: %s%s\n", + (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No", + (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : ""); + snd_iprintf(buffer, "\n"); + } +} + MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From bb1bf4fa5953418c131796ee745c59899d34a149 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:28 +0200 Subject: ALSA: usb-audio: Manage number of rawmidis globally We're going to create rawmidi objects for MIDI 2.0 in a different code from the current code for USB-MIDI 1.0. As a preliminary work, this patch adds the number of rawmidi objects to keep globally in a USB-audio card instance, so that it can be referred from both MIDI 1.0 and 2.0 code. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-8-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/card.c | 5 +++-- sound/usb/midi.c | 7 ++++++- sound/usb/midi.h | 5 +++-- sound/usb/quirks.c | 5 +++-- sound/usb/usbaudio.h | 1 + 5 files changed, 16 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/usb/card.c b/sound/usb/card.c index f6e99ced8068..bd051e634516 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -179,8 +179,9 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { int err = __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, NULL, - chip->usb_id); + &chip->midi_list, NULL, + chip->usb_id, + &chip->num_rawmidis); if (err < 0) { dev_err(&dev->dev, "%u:%d: cannot create sequencer device\n", diff --git a/sound/usb/midi.c b/sound/usb/midi.c index 2839f6b6f09b..6b0993258e03 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -2461,7 +2461,8 @@ int __snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk, - unsigned int usb_id) + unsigned int usb_id, + unsigned int *num_rawmidis) { struct snd_usb_midi *umidi; struct snd_usb_midi_endpoint_info endpoints[MIDI_MAX_ENDPOINTS]; @@ -2476,6 +2477,8 @@ int __snd_usbmidi_create(struct snd_card *card, umidi->iface = iface; umidi->quirk = quirk; umidi->usb_protocol_ops = &snd_usbmidi_standard_ops; + if (num_rawmidis) + umidi->next_midi_device = *num_rawmidis; spin_lock_init(&umidi->disc_lock); init_rwsem(&umidi->disc_rwsem); mutex_init(&umidi->mutex); @@ -2595,6 +2598,8 @@ int __snd_usbmidi_create(struct snd_card *card, usb_autopm_get_interface_no_resume(umidi->iface); list_add_tail(&umidi->list, midi_list); + if (num_rawmidis) + *num_rawmidis = umidi->next_midi_device; return 0; free_midi: diff --git a/sound/usb/midi.h b/sound/usb/midi.h index 3f153195c841..2100f1486b03 100644 --- a/sound/usb/midi.h +++ b/sound/usb/midi.h @@ -46,14 +46,15 @@ int __snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk, - unsigned int usb_id); + unsigned int usb_id, + unsigned int *num_rawmidis); static inline int snd_usbmidi_create(struct snd_card *card, struct usb_interface *iface, struct list_head *midi_list, const struct snd_usb_audio_quirk *quirk) { - return __snd_usbmidi_create(card, iface, midi_list, quirk, 0); + return __snd_usbmidi_create(card, iface, midi_list, quirk, 0, NULL); } void snd_usbmidi_input_stop(struct list_head *p); diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 3ecd1ba7fd4b..1cabe4cc019f 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -436,8 +436,9 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip, chip->usb_id == USB_ID(0x0582, 0x002b) ? &ua700_quirk : &uaxx_quirk; return __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, quirk, - chip->usb_id); + &chip->midi_list, quirk, + chip->usb_id, + &chip->num_rawmidis); } if (altsd->bNumEndpoints != 1) diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 38a85b2c9a73..b1fa0a377866 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -49,6 +49,7 @@ struct snd_usb_audio { struct list_head clock_ref_list; /* list of clock refcounts */ int pcm_devs; + unsigned int num_rawmidis; /* number of created rawmidi devices */ struct list_head midi_list; /* list of midi interfaces */ struct list_head mixer_list; /* list of mixer interfaces */ -- cgit v1.2.3 From ff49d1df79aef7580fe3ac99d17c3f886655d080 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:30 +0200 Subject: ALSA: usb-audio: USB MIDI 2.0 UMP support This patch provides a basic support for USB MIDI 2.0. As of this patch, the driver creates a UMP device per MIDI I/O endpoints, which serves as a dumb terminal to read/write UMP streams. A new Kconfig CONFIG_SND_USB_AUDIO_MIDI_V2 manages whether to enable or disable the MIDI 2.0 support. Also, the driver provides a new module option, midi2_enable, to allow disabling the MIDI 2.0 at runtime, too. When MIDI 2.0 support is disabled, the driver tries to fall back to the already existing MIDI 1.0 device (each MIDI 2.0 device is supposed to provide the MIDI 1.0 interface at the altset 0). For now, the driver doesn't manage any MIDI-CI or other protocol setups by itself, but relies on the default protocol given via the group terminal block descriptors. The MIDI 1.0 messages on MIDI 2.0 device will be automatically converted in ALSA sequencer in a later patch. As of this commit, the driver accepts merely the rawmidi UMP accesses. The driver builds up the topology in the following way: - Create an object for each MIDI endpoint belonging to the USB interface - Find MIDI EP "pairs" that share the same GTB; note that MIDI EP is unidirectional, while UMP is (normally) bidirectional, so two MIDI EPs can form a single UMP EP - A UMP endpoint object is created for each I/O pair - For remaining "solo" MIDI EPs, create unidirectional UMP EPs - Finally, parse GTBs and fill the protocol bits on each UMP So the driver may support multiple UMP Endpoints in theory, although most devices are supposed to have a single UMP EP that can contain up to 16 groups -- which should be large enough. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-10-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/Kconfig | 11 + sound/usb/Makefile | 1 + sound/usb/card.c | 13 +- sound/usb/midi2.c | 1052 ++++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/midi2.h | 33 ++ sound/usb/quirks.c | 3 +- sound/usb/usbaudio.h | 1 + 7 files changed, 1109 insertions(+), 5 deletions(-) create mode 100644 sound/usb/midi2.c create mode 100644 sound/usb/midi2.h (limited to 'sound') diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 059242f15d75..4a9569a3a39a 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -15,6 +15,7 @@ config SND_USB_AUDIO select SND_HWDEP select SND_RAWMIDI select SND_PCM + select SND_UMP if SND_USB_AUDIO_MIDI_V2 select BITREVERSE select SND_USB_AUDIO_USE_MEDIA_CONTROLLER if MEDIA_CONTROLLER && (MEDIA_SUPPORT=y || MEDIA_SUPPORT=SND_USB_AUDIO) help @@ -24,6 +25,16 @@ config SND_USB_AUDIO To compile this driver as a module, choose M here: the module will be called snd-usb-audio. +config SND_USB_AUDIO_MIDI_V2 + bool "MIDI 2.0 support by USB Audio driver" + depends on SND_USB_AUDIO + help + Say Y here to include the support for MIDI 2.0 by USB Audio driver. + When the config is set, the driver tries to probe MIDI 2.0 interface + at first, then falls back to MIDI 1.0 interface as default. + The MIDI 2.0 support can be disabled dynamically via midi2_enable + module option, too. + config SND_USB_AUDIO_USE_MEDIA_CONTROLLER bool diff --git a/sound/usb/Makefile b/sound/usb/Makefile index 9ccb21a4ff8a..db5ff76d0e61 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -22,6 +22,7 @@ snd-usb-audio-objs := card.o \ stream.o \ validate.o +snd-usb-audio-$(CONFIG_SND_USB_AUDIO_MIDI_V2) += midi2.o snd-usb-audio-$(CONFIG_SND_USB_AUDIO_USE_MEDIA_CONTROLLER) += media.o snd-usbmidi-lib-objs := midi.o diff --git a/sound/usb/card.c b/sound/usb/card.c index bd051e634516..1b2edc0fd2e9 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -44,6 +44,7 @@ #include "usbaudio.h" #include "card.h" #include "midi.h" +#include "midi2.h" #include "mixer.h" #include "proc.h" #include "quirks.h" @@ -178,10 +179,8 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int if ((altsd->bInterfaceClass == USB_CLASS_AUDIO || altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) && altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) { - int err = __snd_usbmidi_create(chip->card, iface, - &chip->midi_list, NULL, - chip->usb_id, - &chip->num_rawmidis); + int err = snd_usb_midi_v2_create(chip, iface, NULL, + chip->usb_id); if (err < 0) { dev_err(&dev->dev, "%u:%d: cannot create sequencer device\n", @@ -486,6 +485,7 @@ static void snd_usb_audio_free(struct snd_card *card) struct snd_usb_audio *chip = card->private_data; snd_usb_endpoint_free_all(chip); + snd_usb_midi_v2_free_all(chip); mutex_destroy(&chip->mutex); if (!atomic_read(&chip->shutdown)) @@ -645,6 +645,7 @@ static int snd_usb_audio_create(struct usb_interface *intf, INIT_LIST_HEAD(&chip->iface_ref_list); INIT_LIST_HEAD(&chip->clock_ref_list); INIT_LIST_HEAD(&chip->midi_list); + INIT_LIST_HEAD(&chip->midi_v2_list); INIT_LIST_HEAD(&chip->mixer_list); if (quirk_flags[idx]) @@ -969,6 +970,7 @@ static void usb_audio_disconnect(struct usb_interface *intf) list_for_each(p, &chip->midi_list) { snd_usbmidi_disconnect(p); } + snd_usb_midi_v2_disconnect_all(chip); /* * Nice to check quirk && quirk->shares_media_device and * then call the snd_media_device_delete(). Don't have @@ -1080,6 +1082,7 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) snd_usbmidi_suspend(p); list_for_each_entry(mixer, &chip->mixer_list, list) snd_usb_mixer_suspend(mixer); + snd_usb_midi_v2_suspend_all(chip); } if (!PMSG_IS_AUTO(message) && !chip->system_suspend) { @@ -1125,6 +1128,8 @@ static int usb_audio_resume(struct usb_interface *intf) snd_usbmidi_resume(p); } + snd_usb_midi_v2_resume_all(chip); + out: if (chip->num_suspended_intf == chip->system_suspend) { snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c new file mode 100644 index 000000000000..7b4cfbaf2ec0 --- /dev/null +++ b/sound/usb/midi2.c @@ -0,0 +1,1052 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MIDI 2.0 support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "usbaudio.h" +#include "midi.h" +#include "midi2.h" +#include "helper.h" + +static bool midi2_enable = true; +module_param(midi2_enable, bool, 0444); +MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support."); + +/* stream direction; just shorter names */ +enum { + STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT, + STR_IN = SNDRV_RAWMIDI_STREAM_INPUT +}; + +#define NUM_URBS 8 + +struct snd_usb_midi2_urb; +struct snd_usb_midi2_endpoint; +struct snd_usb_midi2_ump; +struct snd_usb_midi2_interface; + +/* URB context */ +struct snd_usb_midi2_urb { + struct urb *urb; + struct snd_usb_midi2_endpoint *ep; + unsigned int index; /* array index */ +}; + +/* A USB MIDI input/output endpoint */ +struct snd_usb_midi2_endpoint { + struct usb_device *dev; + const struct usb_ms20_endpoint_descriptor *ms_ep; /* reference to EP descriptor */ + struct snd_usb_midi2_endpoint *pair; /* bidirectional pair EP */ + struct snd_usb_midi2_ump *rmidi; /* assigned UMP EP */ + int direction; /* direction (STR_IN/OUT) */ + unsigned int endpoint; /* EP number */ + unsigned int pipe; /* URB pipe */ + unsigned int packets; /* packet buffer size in bytes */ + unsigned int interval; /* interval for INT EP */ + wait_queue_head_t wait; /* URB waiter */ + spinlock_t lock; /* URB locking */ + struct snd_rawmidi_substream *substream; /* NULL when closed */ + unsigned int num_urbs; /* number of allocated URBs */ + unsigned long urb_free; /* bitmap for free URBs */ + unsigned long urb_free_mask; /* bitmask for free URBs */ + atomic_t running; /* running status */ + atomic_t suspended; /* saved running status for suspend */ + bool disconnected; /* shadow of umidi->disconnected */ + struct list_head list; /* list to umidi->ep_list */ + struct snd_usb_midi2_urb urbs[NUM_URBS]; +}; + +/* A UMP endpoint - one or two USB MIDI endpoints are assigned */ +struct snd_usb_midi2_ump { + struct usb_device *dev; + struct snd_usb_midi2_interface *umidi; /* reference to MIDI iface */ + struct snd_ump_endpoint *ump; /* assigned UMP EP object */ + struct snd_usb_midi2_endpoint *eps[2]; /* USB MIDI endpoints */ + int index; /* rawmidi device index */ + unsigned char usb_block_id; /* USB GTB id used for finding a pair */ + struct list_head list; /* list to umidi->rawmidi_list */ +}; + +/* top-level instance per USB MIDI interface */ +struct snd_usb_midi2_interface { + struct snd_usb_audio *chip; /* assigned USB-audio card */ + struct usb_interface *iface; /* assigned USB interface */ + struct usb_host_interface *hostif; + const char *blk_descs; /* group terminal block descriptors */ + unsigned int blk_desc_size; /* size of GTB descriptors */ + bool disconnected; + struct list_head ep_list; /* list of endpoints */ + struct list_head rawmidi_list; /* list of UMP rawmidis */ + struct list_head list; /* list to chip->midi_v2_list */ +}; + +/* submit URBs as much as possible; used for both input and output */ +static void do_submit_urbs_locked(struct snd_usb_midi2_endpoint *ep, + int (*prepare)(struct snd_usb_midi2_endpoint *, + struct urb *)) +{ + struct snd_usb_midi2_urb *ctx; + int index, err = 0; + + if (ep->disconnected) + return; + + while (ep->urb_free) { + index = find_first_bit(&ep->urb_free, ep->num_urbs); + if (index >= ep->num_urbs) + return; + ctx = &ep->urbs[index]; + err = prepare(ep, ctx->urb); + if (err < 0) + return; + if (!ctx->urb->transfer_buffer_length) + return; + ctx->urb->dev = ep->dev; + err = usb_submit_urb(ctx->urb, GFP_ATOMIC); + if (err < 0) { + dev_dbg(&ep->dev->dev, + "usb_submit_urb error %d\n", err); + return; + } + clear_bit(index, &ep->urb_free); + } +} + +/* prepare for output submission: copy from rawmidi buffer to urb packet */ +static int prepare_output_urb(struct snd_usb_midi2_endpoint *ep, + struct urb *urb) +{ + int count; + + if (ep->substream) + count = snd_rawmidi_transmit(ep->substream, + urb->transfer_buffer, + ep->packets); + else + count = -ENODEV; + if (count < 0) { + dev_dbg(&ep->dev->dev, "rawmidi transmit error %d\n", count); + return count; + } + cpu_to_le32_array((u32 *)urb->transfer_buffer, count >> 2); + urb->transfer_buffer_length = count; + return 0; +} + +static void submit_output_urbs_locked(struct snd_usb_midi2_endpoint *ep) +{ + do_submit_urbs_locked(ep, prepare_output_urb); +} + +/* URB completion for output; re-filling and re-submit */ +static void output_urb_complete(struct urb *urb) +{ + struct snd_usb_midi2_urb *ctx = urb->context; + struct snd_usb_midi2_endpoint *ep = ctx->ep; + unsigned long flags; + + spin_lock_irqsave(&ep->lock, flags); + set_bit(ctx->index, &ep->urb_free); + if (urb->status >= 0 && atomic_read(&ep->running)) + submit_output_urbs_locked(ep); + if (ep->urb_free == ep->urb_free_mask) + wake_up(&ep->wait); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* prepare for input submission: just set the buffer length */ +static int prepare_input_urb(struct snd_usb_midi2_endpoint *ep, + struct urb *urb) +{ + urb->transfer_buffer_length = ep->packets; + return 0; +} + +static void submit_input_urbs_locked(struct snd_usb_midi2_endpoint *ep) +{ + do_submit_urbs_locked(ep, prepare_input_urb); +} + +/* URB completion for input; copy into rawmidi buffer and resubmit */ +static void input_urb_complete(struct urb *urb) +{ + struct snd_usb_midi2_urb *ctx = urb->context; + struct snd_usb_midi2_endpoint *ep = ctx->ep; + unsigned long flags; + int len; + + spin_lock_irqsave(&ep->lock, flags); + if (ep->disconnected || urb->status < 0) + goto dequeue; + len = urb->actual_length; + len &= ~3; /* align UMP */ + if (len > ep->packets) + len = ep->packets; + if (len > 0 && ep->substream) { + le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2); + snd_rawmidi_receive(ep->substream, urb->transfer_buffer, len); + } + dequeue: + set_bit(ctx->index, &ep->urb_free); + submit_input_urbs_locked(ep); + if (ep->urb_free == ep->urb_free_mask) + wake_up(&ep->wait); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* URB submission helper; for both direction */ +static void submit_io_urbs(struct snd_usb_midi2_endpoint *ep) +{ + unsigned long flags; + + if (!ep) + return; + spin_lock_irqsave(&ep->lock, flags); + if (ep->direction == STR_IN) + submit_input_urbs_locked(ep); + else + submit_output_urbs_locked(ep); + spin_unlock_irqrestore(&ep->lock, flags); +} + +/* kill URBs for close, suspend and disconnect */ +static void kill_midi_urbs(struct snd_usb_midi2_endpoint *ep, bool suspending) +{ + int i; + + if (!ep) + return; + if (suspending) + ep->suspended = ep->running; + atomic_set(&ep->running, 0); + for (i = 0; i < ep->num_urbs; i++) { + if (!ep->urbs[i].urb) + break; + usb_kill_urb(ep->urbs[i].urb); + } +} + +/* wait until all URBs get freed */ +static void drain_urb_queue(struct snd_usb_midi2_endpoint *ep) +{ + if (!ep) + return; + spin_lock_irq(&ep->lock); + atomic_set(&ep->running, 0); + wait_event_lock_irq_timeout(ep->wait, + ep->disconnected || + ep->urb_free == ep->urb_free_mask, + ep->lock, msecs_to_jiffies(500)); + spin_unlock_irq(&ep->lock); +} + +/* release URBs for an EP */ +static void free_midi_urbs(struct snd_usb_midi2_endpoint *ep) +{ + struct snd_usb_midi2_urb *ctx; + int i; + + if (!ep) + return; + for (i = 0; i < ep->num_urbs; ++i) { + ctx = &ep->urbs[i]; + if (!ctx->urb) + break; + usb_free_coherent(ep->dev, ep->packets, + ctx->urb->transfer_buffer, + ctx->urb->transfer_dma); + usb_free_urb(ctx->urb); + ctx->urb = NULL; + } + ep->num_urbs = 0; +} + +/* allocate URBs for an EP */ +static int alloc_midi_urbs(struct snd_usb_midi2_endpoint *ep) +{ + struct snd_usb_midi2_urb *ctx; + void (*comp)(struct urb *urb); + void *buffer; + int i, err; + int endpoint, len; + + endpoint = ep->endpoint; + len = ep->packets; + if (ep->direction == STR_IN) + comp = input_urb_complete; + else + comp = output_urb_complete; + + ep->num_urbs = 0; + ep->urb_free = ep->urb_free_mask = 0; + for (i = 0; i < NUM_URBS; i++) { + ctx = &ep->urbs[i]; + ctx->index = i; + ctx->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!ctx->urb) { + dev_err(&ep->dev->dev, "URB alloc failed\n"); + return -ENOMEM; + } + ctx->ep = ep; + buffer = usb_alloc_coherent(ep->dev, len, GFP_KERNEL, + &ctx->urb->transfer_dma); + if (!buffer) { + dev_err(&ep->dev->dev, + "URB buffer alloc failed (size %d)\n", len); + return -ENOMEM; + } + if (ep->interval) + usb_fill_int_urb(ctx->urb, ep->dev, ep->pipe, + buffer, len, comp, ctx, ep->interval); + else + usb_fill_bulk_urb(ctx->urb, ep->dev, ep->pipe, + buffer, len, comp, ctx); + err = usb_urb_ep_type_check(ctx->urb); + if (err < 0) { + dev_err(&ep->dev->dev, "invalid MIDI EP %x\n", + endpoint); + return err; + } + ctx->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + ep->num_urbs++; + } + ep->urb_free = ep->urb_free_mask = GENMASK(ep->num_urbs - 1, 0); + return 0; +} + +static struct snd_usb_midi2_endpoint * +substream_to_endpoint(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + struct snd_usb_midi2_ump *rmidi = ump->private_data; + + return rmidi->eps[substream->stream]; +} + +/* rawmidi open callback */ +static int snd_usb_midi_v2_open(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + int err = 0; + + if (!ep || !ep->endpoint) + return -ENODEV; + if (ep->disconnected) + return -EIO; + if (ep->substream) + return -EBUSY; + if (ep->direction == STR_OUT) { + err = alloc_midi_urbs(ep); + if (err) + return err; + } + spin_lock_irq(&ep->lock); + ep->substream = substream; + spin_unlock_irq(&ep->lock); + return 0; +} + +/* rawmidi close callback */ +static int snd_usb_midi_v2_close(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + + spin_lock_irq(&ep->lock); + ep->substream = NULL; + spin_unlock_irq(&ep->lock); + if (ep->direction == STR_OUT) { + kill_midi_urbs(ep, false); + drain_urb_queue(ep); + free_midi_urbs(ep); + } + return 0; +} + +/* rawmidi trigger callback */ +static void snd_usb_midi_v2_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + + atomic_set(&ep->running, up); + if (up && ep->direction == STR_OUT && !ep->disconnected) + submit_io_urbs(ep); +} + +/* rawmidi drain callback */ +static void snd_usb_midi_v2_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + + drain_urb_queue(ep); +} + +/* allocate and start all input streams */ +static int start_input_streams(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + int err; + + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) { + err = alloc_midi_urbs(ep); + if (err < 0) + goto error; + } + } + + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) + submit_io_urbs(ep); + } + + return 0; + + error: + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->direction == STR_IN) + free_midi_urbs(ep); + } + + return err; +} + +static const struct snd_rawmidi_ops output_ops = { + .open = snd_usb_midi_v2_open, + .close = snd_usb_midi_v2_close, + .trigger = snd_usb_midi_v2_trigger, + .drain = snd_usb_midi_v2_drain, +}; + +static const struct snd_rawmidi_ops input_ops = { + .open = snd_usb_midi_v2_open, + .close = snd_usb_midi_v2_close, + .trigger = snd_usb_midi_v2_trigger, +}; + +/* create a USB MIDI 2.0 endpoint object */ +static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi, + struct usb_host_endpoint *hostep, + const struct usb_ms20_endpoint_descriptor *ms_ep) +{ + struct snd_usb_midi2_endpoint *ep; + int endpoint, dir; + + usb_audio_dbg(umidi->chip, "Creating an EP 0x%02x, #GTB=%d\n", + hostep->desc.bEndpointAddress, + ms_ep->bNumGrpTrmBlock); + + ep = kzalloc(sizeof(*ep), GFP_KERNEL); + if (!ep) + return -ENOMEM; + + spin_lock_init(&ep->lock); + init_waitqueue_head(&ep->wait); + ep->dev = umidi->chip->dev; + endpoint = hostep->desc.bEndpointAddress; + dir = (endpoint & USB_DIR_IN) ? STR_IN : STR_OUT; + + ep->endpoint = endpoint; + ep->direction = dir; + ep->ms_ep = ms_ep; + if (usb_endpoint_xfer_int(&hostep->desc)) + ep->interval = hostep->desc.bInterval; + else + ep->interval = 0; + if (dir == STR_IN) { + if (ep->interval) + ep->pipe = usb_rcvintpipe(ep->dev, endpoint); + else + ep->pipe = usb_rcvbulkpipe(ep->dev, endpoint); + } else { + if (ep->interval) + ep->pipe = usb_sndintpipe(ep->dev, endpoint); + else + ep->pipe = usb_sndbulkpipe(ep->dev, endpoint); + } + ep->packets = usb_maxpacket(ep->dev, ep->pipe); + list_add_tail(&ep->list, &umidi->ep_list); + + return 0; +} + +/* destructor for endpoint; from snd_usb_midi_v2_free() */ +static void free_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + list_del(&ep->list); + free_midi_urbs(ep); + kfree(ep); +} + +/* call all endpoint destructors */ +static void free_all_midi2_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + + while (!list_empty(&umidi->ep_list)) { + ep = list_first_entry(&umidi->ep_list, + struct snd_usb_midi2_endpoint, list); + free_midi2_endpoint(ep); + } +} + +/* find a MIDI STREAMING descriptor with a given subtype */ +static void *find_usb_ms_endpoint_descriptor(struct usb_host_endpoint *hostep, + unsigned char subtype) +{ + unsigned char *extra = hostep->extra; + int extralen = hostep->extralen; + + while (extralen > 3) { + struct usb_ms_endpoint_descriptor *ms_ep = + (struct usb_ms_endpoint_descriptor *)extra; + + if (ms_ep->bLength > 3 && + ms_ep->bDescriptorType == USB_DT_CS_ENDPOINT && + ms_ep->bDescriptorSubtype == subtype) + return ms_ep; + if (!extra[0]) + break; + extralen -= extra[0]; + extra += extra[0]; + } + return NULL; +} + +/* get the full group terminal block descriptors and return the size */ +static int get_group_terminal_block_descs(struct snd_usb_midi2_interface *umidi) +{ + struct usb_host_interface *hostif = umidi->hostif; + struct usb_device *dev = umidi->chip->dev; + struct usb_ms20_gr_trm_block_header_descriptor header = { 0 }; + unsigned char *data; + int err, size; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN, + USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting, + hostif->desc.bInterfaceNumber, + &header, sizeof(header)); + if (err < 0) + return err; + size = __le16_to_cpu(header.wTotalLength); + if (!size) { + dev_err(&dev->dev, "Failed to get GTB descriptors for %d:%d\n", + hostif->desc.bInterfaceNumber, hostif->desc.bAlternateSetting); + return -EINVAL; + } + + data = kzalloc(size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, + USB_RECIP_INTERFACE | USB_TYPE_STANDARD | USB_DIR_IN, + USB_DT_CS_GR_TRM_BLOCK << 8 | hostif->desc.bAlternateSetting, + hostif->desc.bInterfaceNumber, data, size); + if (err < 0) { + kfree(data); + return err; + } + + umidi->blk_descs = data; + umidi->blk_desc_size = size; + return 0; +} + +/* find the corresponding group terminal block descriptor */ +static const struct usb_ms20_gr_trm_block_descriptor * +find_group_terminal_block(struct snd_usb_midi2_interface *umidi, int id) +{ + const unsigned char *data = umidi->blk_descs; + int size = umidi->blk_desc_size; + const struct usb_ms20_gr_trm_block_descriptor *desc; + + size -= sizeof(struct usb_ms20_gr_trm_block_header_descriptor); + data += sizeof(struct usb_ms20_gr_trm_block_header_descriptor); + while (size > 0 && *data && *data <= size) { + desc = (const struct usb_ms20_gr_trm_block_descriptor *)data; + if (desc->bLength >= sizeof(*desc) && + desc->bDescriptorType == USB_DT_CS_GR_TRM_BLOCK && + desc->bDescriptorSubtype == USB_MS_GR_TRM_BLOCK && + desc->bGrpTrmBlkID == id) + return desc; + size -= *data; + data += *data; + } + + return NULL; +} + +/* fill up the information from GTB */ +static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, + const struct usb_ms20_gr_trm_block_descriptor *desc) +{ + struct snd_usb_audio *chip = rmidi->umidi->chip; + struct snd_ump_endpoint *ump = rmidi->ump; + + usb_audio_dbg(chip, + "GTB id %d: groups = %d / %d, type = %d\n", + desc->bGrpTrmBlkID, desc->nGroupTrm, desc->nNumGroupTrm, + desc->bGrpTrmBlkType); + + /* set default protocol */ + switch (desc->bMIDIProtocol) { + case USB_MS_MIDI_PROTO_1_0_64: + case USB_MS_MIDI_PROTO_1_0_64_JRTS: + case USB_MS_MIDI_PROTO_1_0_128: + case USB_MS_MIDI_PROTO_1_0_128_JRTS: + ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1; + break; + case USB_MS_MIDI_PROTO_2_0: + case USB_MS_MIDI_PROTO_2_0_JRTS: + ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2; + break; + } + + ump->info.protocol_caps = ump->info.protocol; + switch (desc->bMIDIProtocol) { + case USB_MS_MIDI_PROTO_1_0_64_JRTS: + case USB_MS_MIDI_PROTO_1_0_128_JRTS: + case USB_MS_MIDI_PROTO_2_0_JRTS: + ump->info.protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX | + SNDRV_UMP_EP_INFO_PROTO_JRTS_RX; + break; + } + + return 0; +} + +/* allocate and parse for each assigned group terminal block */ +static int parse_group_terminal_blocks(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + const struct usb_ms20_gr_trm_block_descriptor *desc; + int err; + + err = get_group_terminal_block_descs(umidi); + if (err < 0) + return err; + if (!umidi->blk_descs) + return 0; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + desc = find_group_terminal_block(umidi, rmidi->usb_block_id); + if (!desc) + continue; + err = parse_group_terminal_block(rmidi, desc); + if (err < 0) + return err; + } + + return 0; +} + +/* parse endpoints included in the given interface and create objects */ +static int parse_midi_2_0_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct usb_host_interface *hostif = umidi->hostif; + struct usb_host_endpoint *hostep; + struct usb_ms20_endpoint_descriptor *ms_ep; + int i, err; + + for (i = 0; i < hostif->desc.bNumEndpoints; i++) { + hostep = &hostif->endpoint[i]; + if (!usb_endpoint_xfer_bulk(&hostep->desc) && + !usb_endpoint_xfer_int(&hostep->desc)) + continue; + ms_ep = find_usb_ms_endpoint_descriptor(hostep, USB_MS_GENERAL_2_0); + if (!ms_ep) + continue; + if (ms_ep->bLength <= sizeof(*ms_ep)) + continue; + if (!ms_ep->bNumGrpTrmBlock) + continue; + if (ms_ep->bLength < sizeof(*ms_ep) + ms_ep->bNumGrpTrmBlock) + continue; + err = create_midi2_endpoint(umidi, hostep, ms_ep); + if (err < 0) + return err; + } + return 0; +} + +static void free_all_midi2_umps(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + + while (!list_empty(&umidi->rawmidi_list)) { + rmidi = list_first_entry(&umidi->rawmidi_list, + struct snd_usb_midi2_ump, list); + list_del(&rmidi->list); + kfree(rmidi); + } +} + +static int create_midi2_ump(struct snd_usb_midi2_interface *umidi, + struct snd_usb_midi2_endpoint *ep_in, + struct snd_usb_midi2_endpoint *ep_out, + int blk_id) +{ + struct snd_usb_midi2_ump *rmidi; + struct snd_ump_endpoint *ump; + int input, output; + char idstr[16]; + int err; + + rmidi = kzalloc(sizeof(*rmidi), GFP_KERNEL); + if (!rmidi) + return -ENOMEM; + INIT_LIST_HEAD(&rmidi->list); + rmidi->dev = umidi->chip->dev; + rmidi->umidi = umidi; + rmidi->usb_block_id = blk_id; + + rmidi->index = umidi->chip->num_rawmidis; + snprintf(idstr, sizeof(idstr), "UMP %d", rmidi->index); + input = ep_in ? 1 : 0; + output = ep_out ? 1 : 0; + err = snd_ump_endpoint_new(umidi->chip->card, idstr, rmidi->index, + output, input, &ump); + if (err < 0) { + usb_audio_dbg(umidi->chip, "Failed to create a UMP object\n"); + kfree(rmidi); + return err; + } + + rmidi->ump = ump; + umidi->chip->num_rawmidis++; + + ump->private_data = rmidi; + + if (input) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, + &input_ops); + if (output) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, + &output_ops); + + rmidi->eps[STR_IN] = ep_in; + rmidi->eps[STR_OUT] = ep_out; + if (ep_in) { + ep_in->pair = ep_out; + ep_in->rmidi = rmidi; + } + if (ep_out) { + ep_out->pair = ep_in; + ep_out->rmidi = rmidi; + } + + list_add_tail(&rmidi->list, &umidi->rawmidi_list); + return 0; +} + +/* find the UMP EP with the given USB block id */ +static struct snd_usb_midi2_ump * +find_midi2_ump(struct snd_usb_midi2_interface *umidi, int blk_id) +{ + struct snd_usb_midi2_ump *rmidi; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (rmidi->usb_block_id == blk_id) + return rmidi; + } + return NULL; +} + +/* look for the matching output endpoint and create UMP object if found */ +static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi, + struct snd_usb_midi2_endpoint *ep, + int blk_id) +{ + struct snd_usb_midi2_endpoint *pair_ep; + int blk; + + usb_audio_dbg(umidi->chip, "Looking for a pair for EP-in 0x%02x\n", + ep->endpoint); + list_for_each_entry(pair_ep, &umidi->ep_list, list) { + if (pair_ep->direction != STR_OUT) + continue; + if (pair_ep->pair) + continue; /* already paired */ + for (blk = 0; blk < pair_ep->ms_ep->bNumGrpTrmBlock; blk++) { + if (pair_ep->ms_ep->baAssoGrpTrmBlkID[blk] == blk_id) { + usb_audio_dbg(umidi->chip, + "Found a match with EP-out 0x%02x blk %d\n", + pair_ep->endpoint, blk); + return create_midi2_ump(umidi, ep, pair_ep, blk_id); + } + } + } + return 0; +} + +static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi) +{ + free_all_midi2_endpoints(umidi); + free_all_midi2_umps(umidi); + list_del(&umidi->list); + kfree(umidi->blk_descs); + kfree(umidi); +} + +/* parse the interface for MIDI 2.0 */ +static int parse_midi_2_0(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_endpoint *ep; + int blk, id, err; + + /* First, create an object for each USB MIDI Endpoint */ + err = parse_midi_2_0_endpoints(umidi); + if (err < 0) + return err; + if (list_empty(&umidi->ep_list)) { + usb_audio_warn(umidi->chip, "No MIDI endpoints found\n"); + return -ENODEV; + } + + /* + * Next, look for EP I/O pairs that are found in group terminal blocks + * A UMP object is created for each EP I/O pair as bidirecitonal + * UMP EP + */ + list_for_each_entry(ep, &umidi->ep_list, list) { + /* only input in this loop; output is matched in find_midi_ump() */ + if (ep->direction != STR_IN) + continue; + for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) { + id = ep->ms_ep->baAssoGrpTrmBlkID[blk]; + err = find_matching_ep_partner(umidi, ep, id); + if (err < 0) + return err; + } + } + + /* + * For the remaining EPs, treat as singles, create a UMP object with + * unidirectional EP + */ + list_for_each_entry(ep, &umidi->ep_list, list) { + if (ep->rmidi) + continue; /* already paired */ + for (blk = 0; blk < ep->ms_ep->bNumGrpTrmBlock; blk++) { + id = ep->ms_ep->baAssoGrpTrmBlkID[blk]; + if (find_midi2_ump(umidi, id)) + continue; + usb_audio_dbg(umidi->chip, + "Creating a unidirection UMP for EP=0x%02x, blk=%d\n", + ep->endpoint, id); + if (ep->direction == STR_IN) + err = create_midi2_ump(umidi, ep, NULL, id); + else + err = create_midi2_ump(umidi, NULL, ep, id); + if (err < 0) + return err; + break; + } + } + + return 0; +} + +/* is the given interface for MIDI 2.0? */ +static bool is_midi2_altset(struct usb_host_interface *hostif) +{ + struct usb_ms_header_descriptor *ms_header = + (struct usb_ms_header_descriptor *)hostif->extra; + + if (hostif->extralen < 7 || + ms_header->bLength < 7 || + ms_header->bDescriptorType != USB_DT_CS_INTERFACE || + ms_header->bDescriptorSubtype != UAC_HEADER) + return false; + + return le16_to_cpu(ms_header->bcdMSC) == USB_MS_REV_MIDI_2_0; +} + +/* change the altsetting */ +static int set_altset(struct snd_usb_midi2_interface *umidi) +{ + usb_audio_dbg(umidi->chip, "Setting host iface %d:%d\n", + umidi->hostif->desc.bInterfaceNumber, + umidi->hostif->desc.bAlternateSetting); + return usb_set_interface(umidi->chip->dev, + umidi->hostif->desc.bInterfaceNumber, + umidi->hostif->desc.bAlternateSetting); +} + +/* fill the fallback name string for each rawmidi instance */ +static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!*rmidi->ump->core.name) + sprintf(rmidi->ump->core.name, "USB MIDI %d", + rmidi->index); + } +} + +/* create MIDI interface; fallback to MIDI 1.0 if needed */ +int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id) +{ + struct snd_usb_midi2_interface *umidi; + struct usb_host_interface *hostif; + int err; + + usb_audio_dbg(chip, "Parsing interface %d...\n", + iface->altsetting[0].desc.bInterfaceNumber); + + /* fallback to MIDI 1.0? */ + if (!midi2_enable) { + usb_audio_info(chip, "Falling back to MIDI 1.0 by module option\n"); + goto fallback_to_midi1; + } + if ((quirk && quirk->type != QUIRK_MIDI_STANDARD_INTERFACE) || + iface->num_altsetting < 2) { + usb_audio_info(chip, "Quirk or no altest; falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + hostif = &iface->altsetting[1]; + if (!is_midi2_altset(hostif)) { + usb_audio_info(chip, "No MIDI 2.0 at altset 1, falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + if (!hostif->desc.bNumEndpoints) { + usb_audio_info(chip, "No endpoint at altset 1, falling back to MIDI 1.0\n"); + goto fallback_to_midi1; + } + + usb_audio_dbg(chip, "Creating a MIDI 2.0 instance for %d:%d\n", + hostif->desc.bInterfaceNumber, + hostif->desc.bAlternateSetting); + + umidi = kzalloc(sizeof(*umidi), GFP_KERNEL); + if (!umidi) + return -ENOMEM; + umidi->chip = chip; + umidi->iface = iface; + umidi->hostif = hostif; + INIT_LIST_HEAD(&umidi->rawmidi_list); + INIT_LIST_HEAD(&umidi->ep_list); + + list_add_tail(&umidi->list, &chip->midi_v2_list); + + err = set_altset(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to set altset\n"); + goto error; + } + + /* assume only altset 1 corresponding to MIDI 2.0 interface */ + err = parse_midi_2_0(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse MIDI 2.0 interface\n"); + goto error; + } + + /* parse USB group terminal blocks */ + err = parse_group_terminal_blocks(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse GTB\n"); + goto error; + } + + err = start_input_streams(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to start input streams\n"); + goto error; + } + + set_fallback_rawmidi_names(umidi); + return 0; + + error: + snd_usb_midi_v2_free(umidi); + return err; + + fallback_to_midi1: + return __snd_usbmidi_create(chip->card, iface, &chip->midi_list, + quirk, usb_id, &chip->num_rawmidis); +} + +static void suspend_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + kill_midi_urbs(ep, true); + drain_urb_queue(ep); +} + +void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + list_for_each_entry(ep, &umidi->ep_list, list) + suspend_midi2_endpoint(ep); + } +} + +static void resume_midi2_endpoint(struct snd_usb_midi2_endpoint *ep) +{ + ep->running = ep->suspended; + if (ep->direction == STR_IN) + submit_io_urbs(ep); + /* FIXME: does it all? */ +} + +void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + set_altset(umidi); + list_for_each_entry(ep, &umidi->ep_list, list) + resume_midi2_endpoint(ep); + } +} + +void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi; + struct snd_usb_midi2_endpoint *ep; + + list_for_each_entry(umidi, &chip->midi_v2_list, list) { + umidi->disconnected = 1; + list_for_each_entry(ep, &umidi->ep_list, list) { + ep->disconnected = 1; + kill_midi_urbs(ep, false); + drain_urb_queue(ep); + } + } +} + +/* release the MIDI instance */ +void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) +{ + struct snd_usb_midi2_interface *umidi, *next; + + list_for_each_entry_safe(umidi, next, &chip->midi_v2_list, list) + snd_usb_midi_v2_free(umidi); +} diff --git a/sound/usb/midi2.h b/sound/usb/midi2.h new file mode 100644 index 000000000000..94a65fcbd58b --- /dev/null +++ b/sound/usb/midi2.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __USB_AUDIO_MIDI2_H +#define __USB_AUDIO_MIDI2_H + +#include "midi.h" + +#if IS_ENABLED(CONFIG_SND_USB_AUDIO_MIDI_V2) +int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id); +void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip); +void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip); +#else /* CONFIG_SND_USB_AUDIO_MIDI_V2 */ +/* fallback to MIDI 1.0 creation */ +static inline int snd_usb_midi_v2_create(struct snd_usb_audio *chip, + struct usb_interface *iface, + const struct snd_usb_audio_quirk *quirk, + unsigned int usb_id) +{ + return __snd_usbmidi_create(chip->card, iface, &chip->midi_list, + quirk, usb_id, &chip->num_rawmidis); +} + +static inline void snd_usb_midi_v2_suspend_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_resume_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_disconnect_all(struct snd_usb_audio *chip) {} +static inline void snd_usb_midi_v2_free_all(struct snd_usb_audio *chip) {} +#endif /* CONFIG_SND_USB_AUDIO_MIDI_V2 */ + +#endif /* __USB_AUDIO_MIDI2_H */ diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 1cabe4cc019f..53e079e91580 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -19,6 +19,7 @@ #include "mixer.h" #include "mixer_quirks.h" #include "midi.h" +#include "midi2.h" #include "quirks.h" #include "helper.h" #include "endpoint.h" @@ -80,7 +81,7 @@ static int create_any_midi_quirk(struct snd_usb_audio *chip, struct usb_driver *driver, const struct snd_usb_audio_quirk *quirk) { - return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk); + return snd_usb_midi_v2_create(chip, intf, quirk, 0); } /* diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index b1fa0a377866..43d4029edab4 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -51,6 +51,7 @@ struct snd_usb_audio { unsigned int num_rawmidis; /* number of created rawmidi devices */ struct list_head midi_list; /* list of midi interfaces */ + struct list_head midi_v2_list; /* list of MIDI 2 interfaces */ struct list_head mixer_list; /* list of mixer interfaces */ -- cgit v1.2.3 From 06cf3bf09d83944382b36c7617277619f25f49e4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:31 +0200 Subject: ALSA: usb-audio: Get UMP EP name string from USB interface USB descriptor may provide a nicer name for USB interface, and we may take it as the UMP Endpoint name. The UMP EP name is copied as the rawmidi name, too. Also, fill the UMP block product_id field from the iSerialNumber string of the USB device descriptor as a recommended unique id, too. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-11-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 7b4cfbaf2ec0..2ac3f96216bc 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -892,15 +892,39 @@ static int set_altset(struct snd_usb_midi2_interface *umidi) umidi->hostif->desc.bAlternateSetting); } +/* fill UMP Endpoint name string from USB descriptor */ +static void fill_ump_ep_name(struct snd_ump_endpoint *ump, + struct usb_device *dev, int id) +{ + usb_string(dev, id, ump->info.name, sizeof(ump->info.name)); +} + /* fill the fallback name string for each rawmidi instance */ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) { + struct usb_device *dev = umidi->chip->dev; struct snd_usb_midi2_ump *rmidi; + struct snd_ump_endpoint *ump; list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { - if (!*rmidi->ump->core.name) - sprintf(rmidi->ump->core.name, "USB MIDI %d", - rmidi->index); + ump = rmidi->ump; + /* fill UMP EP name from USB descriptors */ + if (!*ump->info.name && umidi->hostif->desc.iInterface) + fill_ump_ep_name(ump, dev, umidi->hostif->desc.iInterface); + else if (!*ump->info.name && dev->descriptor.iProduct) + fill_ump_ep_name(ump, dev, dev->descriptor.iProduct); + /* fill fallback name */ + if (!*ump->info.name) + sprintf(ump->info.name, "USB MIDI %d", rmidi->index); + /* copy as rawmidi name if not set */ + if (!*ump->core.name) + strscpy(ump->core.name, ump->info.name, + sizeof(ump->core.name)); + /* use serial number string as unique UMP product id */ + if (!*ump->info.product_id && dev->descriptor.iSerialNumber) + usb_string(dev, dev->descriptor.iSerialNumber, + ump->info.product_id, + sizeof(ump->info.product_id)); } } -- cgit v1.2.3 From 51701400a94e999c3109c84f88273a9face51c4b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:32 +0200 Subject: ALSA: usb-audio: Trim superfluous "MIDI" suffix from UMP EP name A single USB audio device may have multiple interfaces for different purposes (e.g. audio, MIDI and HID), where the iInterface descriptor of each interface may contain an own suffix, e.g. "MIDI" for a MIDI interface. as such a suffix is superfluous as a rawmidi and UMP Endpoint name, this patch trims the superfluous "MIDI" suffix from the name string. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-12-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 2ac3f96216bc..790e4cd5d35c 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -896,7 +896,14 @@ static int set_altset(struct snd_usb_midi2_interface *umidi) static void fill_ump_ep_name(struct snd_ump_endpoint *ump, struct usb_device *dev, int id) { + int len; + usb_string(dev, id, ump->info.name, sizeof(ump->info.name)); + + /* trim superfluous "MIDI" suffix */ + len = strlen(ump->info.name); + if (len > 5 && !strcmp(ump->info.name + len - 5, " MIDI")) + ump->info.name[len - 5] = 0; } /* fill the fallback name string for each rawmidi instance */ -- cgit v1.2.3 From d9c99876868c861afd0e9ce2cea407bbc446b3c9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:33 +0200 Subject: ALSA: usb-audio: Create UMP blocks from USB MIDI GTBs USB MIDI spec defines the Group Terminal Blocks (GTB) that associate multiple UMP Groups. Those correspond to snd_ump_block entities in ALSA UMP abstraction, and now we create those UMP Block objects for each UMP Endpoint from the parsed GTB information. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-13-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 790e4cd5d35c..5ffee06ac746 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -599,14 +599,8 @@ find_group_terminal_block(struct snd_usb_midi2_interface *umidi, int id) static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, const struct usb_ms20_gr_trm_block_descriptor *desc) { - struct snd_usb_audio *chip = rmidi->umidi->chip; struct snd_ump_endpoint *ump = rmidi->ump; - usb_audio_dbg(chip, - "GTB id %d: groups = %d / %d, type = %d\n", - desc->bGrpTrmBlkID, desc->nGroupTrm, desc->nNumGroupTrm, - desc->bGrpTrmBlkType); - /* set default protocol */ switch (desc->bMIDIProtocol) { case USB_MS_MIDI_PROTO_1_0_64: @@ -798,6 +792,94 @@ static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi, return 0; } +/* create a UMP block from a GTB entry */ +static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk) +{ + struct snd_usb_midi2_interface *umidi = rmidi->umidi; + const struct usb_ms20_gr_trm_block_descriptor *desc; + struct snd_ump_block *fb; + int type, err; + + desc = find_group_terminal_block(umidi, blk); + if (!desc) + return 0; + + usb_audio_dbg(umidi->chip, + "GTB %d: type=%d, group=%d/%d, protocol=%d, in bw=%d, out bw=%d\n", + blk, desc->bGrpTrmBlkType, desc->nGroupTrm, + desc->nNumGroupTrm, desc->bMIDIProtocol, + __le16_to_cpu(desc->wMaxInputBandwidth), + __le16_to_cpu(desc->wMaxOutputBandwidth)); + + /* assign the direction */ + switch (desc->bGrpTrmBlkType) { + case USB_MS_GR_TRM_BLOCK_TYPE_BIDIRECTIONAL: + type = SNDRV_UMP_DIR_BIDIRECTION; + break; + case USB_MS_GR_TRM_BLOCK_TYPE_INPUT_ONLY: + type = SNDRV_UMP_DIR_INPUT; + break; + case USB_MS_GR_TRM_BLOCK_TYPE_OUTPUT_ONLY: + type = SNDRV_UMP_DIR_OUTPUT; + break; + default: + usb_audio_dbg(umidi->chip, "Unsupported GTB type %d\n", + desc->bGrpTrmBlkType); + return 0; /* unsupported */ + } + + /* guess work: set blk-1 as the (0-based) block ID */ + err = snd_ump_block_new(rmidi->ump, blk - 1, type, + desc->nGroupTrm, desc->nNumGroupTrm, + &fb); + if (err == -EBUSY) + return 0; /* already present */ + else if (err) + return err; + + if (desc->iBlockItem) + usb_string(rmidi->dev, desc->iBlockItem, + fb->info.name, sizeof(fb->info.name)); + + if (__le16_to_cpu(desc->wMaxInputBandwidth) == 1 || + __le16_to_cpu(desc->wMaxOutputBandwidth) == 1) + fb->info.flags |= SNDRV_UMP_BLOCK_IS_MIDI1 | + SNDRV_UMP_BLOCK_IS_LOWSPEED; + + usb_audio_dbg(umidi->chip, + "Created a UMP block %d from GTB, name=%s\n", + blk, fb->info.name); + return 0; +} + +/* Create UMP blocks for each UMP EP */ +static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + int i, blk, err, dir; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!rmidi->ump) + continue; + /* Blocks have been already created? */ + if (rmidi->ump->info.num_blocks) + continue; + /* loop over GTBs */ + for (dir = 0; dir < 2; dir++) { + if (!rmidi->eps[dir]) + continue; + for (i = 0; i < rmidi->eps[dir]->ms_ep->bNumGrpTrmBlock; i++) { + blk = rmidi->eps[dir]->ms_ep->baAssoGrpTrmBlkID[i]; + err = create_gtb_block(rmidi, dir, blk); + if (err < 0) + return err; + } + } + } + + return 0; +} + static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi) { free_all_midi2_endpoints(umidi); @@ -1009,6 +1091,12 @@ int snd_usb_midi_v2_create(struct snd_usb_audio *chip, goto error; } + err = create_blocks_from_gtb(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to create GTB blocks\n"); + goto error; + } + set_fallback_rawmidi_names(umidi); return 0; -- cgit v1.2.3 From 6b41e64a5d17ec01380bc7ad10afd90e63beca19 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:34 +0200 Subject: ALSA: ump: Redirect rawmidi substream access via own helpers This is a code refactoring for abstracting the rawmidi access to the UMP's own helpers. It's a preliminary work for the later code refactoring of the UMP layer. Until now, we access to the rawmidi substream directly from the driver via rawmidi access helpers, but after this change, the driver is supposed to access via the newly introduced snd_ump_ops and receive/transmit via snd_ump_receive() and snd_ump_transmit() helpers. As of this commit, those are merely wrappers for the rawmidi substream, and no much function change is seen here. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-14-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 14 +++++++ sound/core/ump.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/midi2.c | 71 ++++++++++++--------------------- 3 files changed, 149 insertions(+), 47 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 8a3ac97cd1d3..6f786b462f16 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -9,18 +9,30 @@ struct snd_ump_endpoint; struct snd_ump_block; +struct snd_ump_ops; struct snd_ump_endpoint { struct snd_rawmidi core; /* raw UMP access */ struct snd_ump_endpoint_info info; + const struct snd_ump_ops *ops; /* UMP ops set by the driver */ + struct snd_rawmidi_substream *substreams[2]; /* opened substreams */ + void *private_data; void (*private_free)(struct snd_ump_endpoint *ump); struct list_head block_list; /* list of snd_ump_block objects */ }; +/* ops filled by UMP drivers */ +struct snd_ump_ops { + int (*open)(struct snd_ump_endpoint *ump, int dir); + void (*close)(struct snd_ump_endpoint *ump, int dir); + void (*trigger)(struct snd_ump_endpoint *ump, int dir, int up); + void (*drain)(struct snd_ump_endpoint *ump, int dir); +}; + struct snd_ump_block { struct snd_ump_block_info info; struct snd_ump_endpoint *ump; @@ -39,6 +51,8 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, unsigned int direction, unsigned int first_group, unsigned int num_groups, struct snd_ump_block **blk_ret); +int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count); +int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count); /* * Some definitions for UMP diff --git a/sound/core/ump.c b/sound/core/ump.c index 651cd3752719..46ec297a786c 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -23,6 +23,11 @@ static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd, void __user *argp); static void snd_ump_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer); +static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream); +static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream); +static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, + int up); +static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .dev_register = snd_ump_dev_register, @@ -31,6 +36,19 @@ static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .proc_read = snd_ump_proc_read, }; +static const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = { + .open = snd_ump_rawmidi_open, + .close = snd_ump_rawmidi_close, + .trigger = snd_ump_rawmidi_trigger, +}; + +static const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = { + .open = snd_ump_rawmidi_open, + .close = snd_ump_rawmidi_close, + .trigger = snd_ump_rawmidi_trigger, + .drain = snd_ump_rawmidi_drain, +}; + static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) { struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); @@ -104,6 +122,12 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, ump->core.private_free = snd_ump_endpoint_free; ump->core.ops = &snd_ump_rawmidi_ops; + if (input) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_ump_rawmidi_input_ops); + if (output) + snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_ump_rawmidi_output_ops); ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id); *ump_ret = ump; @@ -137,6 +161,93 @@ snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id) return NULL; } +/* + * rawmidi ops for UMP endpoint + */ +static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + int err; + + if (ump->substreams[dir]) + return -EBUSY; + err = ump->ops->open(ump, dir); + if (err < 0) + return err; + ump->substreams[dir] = substream; + return 0; +} + +static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + + ump->substreams[dir] = NULL; + ump->ops->close(ump, dir); + return 0; +} + +static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + int dir = substream->stream; + + ump->ops->trigger(ump, dir, up); +} + +static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); + + if (ump->ops->drain) + ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); +} + +/** + * snd_ump_receive - transfer UMP packets from the device + * @ump: the UMP endpoint + * @buffer: the buffer pointer to transfer + * @count: byte size to transfer + * + * Called from the driver to submit the received UMP packets from the device + * to user-space. It's essentially a wrapper of rawmidi_receive(). + * The data to receive is in CPU-native endianness. + */ +int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream = + ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; + + if (!substream) + return 0; + return snd_rawmidi_receive(substream, (const char *)buffer, count); +} +EXPORT_SYMBOL_GPL(snd_ump_receive); + +/** + * snd_ump_transmit - transmit UMP packets + * @ump: the UMP endpoint + * @buffer: the buffer pointer to transfer + * @count: byte size to transfer + * + * Called from the driver to obtain the UMP packets from user-space to the + * device. It's essentially a wrapper of rawmidi_transmit(). + * The data to transmit is in CPU-native endianness. + */ +int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream = + ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + if (!substream) + return -ENODEV; + return snd_rawmidi_transmit(substream, (char *)buffer, count); +} +EXPORT_SYMBOL_GPL(snd_ump_transmit); + /** * snd_ump_block_new - Create a UMP block * @ump: UMP object diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 5ffee06ac746..7e849b2384ee 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -52,7 +52,8 @@ struct snd_usb_midi2_endpoint { struct usb_device *dev; const struct usb_ms20_endpoint_descriptor *ms_ep; /* reference to EP descriptor */ struct snd_usb_midi2_endpoint *pair; /* bidirectional pair EP */ - struct snd_usb_midi2_ump *rmidi; /* assigned UMP EP */ + struct snd_usb_midi2_ump *rmidi; /* assigned UMP EP pair */ + struct snd_ump_endpoint *ump; /* assigned UMP EP */ int direction; /* direction (STR_IN/OUT) */ unsigned int endpoint; /* EP number */ unsigned int pipe; /* URB pipe */ @@ -133,12 +134,8 @@ static int prepare_output_urb(struct snd_usb_midi2_endpoint *ep, { int count; - if (ep->substream) - count = snd_rawmidi_transmit(ep->substream, - urb->transfer_buffer, - ep->packets); - else - count = -ENODEV; + count = snd_ump_transmit(ep->ump, urb->transfer_buffer, + ep->packets); if (count < 0) { dev_dbg(&ep->dev->dev, "rawmidi transmit error %d\n", count); return count; @@ -197,9 +194,9 @@ static void input_urb_complete(struct urb *urb) len &= ~3; /* align UMP */ if (len > ep->packets) len = ep->packets; - if (len > 0 && ep->substream) { + if (len > 0) { le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2); - snd_rawmidi_receive(ep->substream, urb->transfer_buffer, len); + snd_ump_receive(ep->ump, (u32 *)urb->transfer_buffer, len); } dequeue: set_bit(ctx->index, &ep->urb_free); @@ -330,68 +327,58 @@ static int alloc_midi_urbs(struct snd_usb_midi2_endpoint *ep) } static struct snd_usb_midi2_endpoint * -substream_to_endpoint(struct snd_rawmidi_substream *substream) +ump_to_endpoint(struct snd_ump_endpoint *ump, int dir) { - struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi); struct snd_usb_midi2_ump *rmidi = ump->private_data; - return rmidi->eps[substream->stream]; + return rmidi->eps[dir]; } -/* rawmidi open callback */ -static int snd_usb_midi_v2_open(struct snd_rawmidi_substream *substream) +/* ump open callback */ +static int snd_usb_midi_v2_open(struct snd_ump_endpoint *ump, int dir) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); int err = 0; if (!ep || !ep->endpoint) return -ENODEV; if (ep->disconnected) return -EIO; - if (ep->substream) - return -EBUSY; if (ep->direction == STR_OUT) { err = alloc_midi_urbs(ep); if (err) return err; } - spin_lock_irq(&ep->lock); - ep->substream = substream; - spin_unlock_irq(&ep->lock); return 0; } -/* rawmidi close callback */ -static int snd_usb_midi_v2_close(struct snd_rawmidi_substream *substream) +/* ump close callback */ +static void snd_usb_midi_v2_close(struct snd_ump_endpoint *ump, int dir) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); - spin_lock_irq(&ep->lock); - ep->substream = NULL; - spin_unlock_irq(&ep->lock); if (ep->direction == STR_OUT) { kill_midi_urbs(ep, false); drain_urb_queue(ep); free_midi_urbs(ep); } - return 0; } -/* rawmidi trigger callback */ -static void snd_usb_midi_v2_trigger(struct snd_rawmidi_substream *substream, +/* ump trigger callback */ +static void snd_usb_midi_v2_trigger(struct snd_ump_endpoint *ump, int dir, int up) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); atomic_set(&ep->running, up); if (up && ep->direction == STR_OUT && !ep->disconnected) submit_io_urbs(ep); } -/* rawmidi drain callback */ -static void snd_usb_midi_v2_drain(struct snd_rawmidi_substream *substream) +/* ump drain callback */ +static void snd_usb_midi_v2_drain(struct snd_ump_endpoint *ump, int dir) { - struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream); + struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir); drain_urb_queue(ep); } @@ -426,19 +413,13 @@ static int start_input_streams(struct snd_usb_midi2_interface *umidi) return err; } -static const struct snd_rawmidi_ops output_ops = { +static const struct snd_ump_ops snd_usb_midi_v2_ump_ops = { .open = snd_usb_midi_v2_open, .close = snd_usb_midi_v2_close, .trigger = snd_usb_midi_v2_trigger, .drain = snd_usb_midi_v2_drain, }; -static const struct snd_rawmidi_ops input_ops = { - .open = snd_usb_midi_v2_open, - .close = snd_usb_midi_v2_close, - .trigger = snd_usb_midi_v2_trigger, -}; - /* create a USB MIDI 2.0 endpoint object */ static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi, struct usb_host_endpoint *hostep, @@ -729,23 +710,19 @@ static int create_midi2_ump(struct snd_usb_midi2_interface *umidi, umidi->chip->num_rawmidis++; ump->private_data = rmidi; - - if (input) - snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT, - &input_ops); - if (output) - snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT, - &output_ops); + ump->ops = &snd_usb_midi_v2_ump_ops; rmidi->eps[STR_IN] = ep_in; rmidi->eps[STR_OUT] = ep_out; if (ep_in) { ep_in->pair = ep_out; ep_in->rmidi = rmidi; + ep_in->ump = ump; } if (ep_out) { ep_out->pair = ep_in; ep_out->rmidi = rmidi; + ep_out->ump = ump; } list_add_tail(&rmidi->list, &umidi->rawmidi_list); -- cgit v1.2.3 From 0b5288f5fe63eab687c14e5940b9e0d532b129f2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:35 +0200 Subject: ALSA: ump: Add legacy raw MIDI support This patch extends the UMP core code to support the legacy MIDI 1.0 rawmidi devices. When the new kconfig CONFIG_SND_UMP_LEGACY_RAWMIDI is set, the UMP core allows to attach an additional rawmidi device for each UMP Endpoint. The rawmidi device contains 16 substreams where each substream corresponds to a UMP Group belonging to the EP. The device reads/writes the legacy MIDI 1.0 byte streams and translates from/to UMP packets. The legacy rawmidi devices are exclusive with the UMP rawmidi devices, hence both of them can't be opened at the same time unless the UMP rawmidi is opened in APPEND mode. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-15-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 30 +++ include/sound/ump_msg.h | 540 +++++++++++++++++++++++++++++++++++++++++++++++ sound/core/Kconfig | 9 + sound/core/Makefile | 1 + sound/core/ump.c | 258 +++++++++++++++++++++- sound/core/ump_convert.c | 520 +++++++++++++++++++++++++++++++++++++++++++++ sound/core/ump_convert.h | 43 ++++ 7 files changed, 1398 insertions(+), 3 deletions(-) create mode 100644 include/sound/ump_msg.h create mode 100644 sound/core/ump_convert.c create mode 100644 sound/core/ump_convert.h (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 6f786b462f16..45f4c9b673b5 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -10,6 +10,7 @@ struct snd_ump_endpoint; struct snd_ump_block; struct snd_ump_ops; +struct ump_cvt_to_ump; struct snd_ump_endpoint { struct snd_rawmidi core; /* raw UMP access */ @@ -23,6 +24,24 @@ struct snd_ump_endpoint { void (*private_free)(struct snd_ump_endpoint *ump); struct list_head block_list; /* list of snd_ump_block objects */ + + /* intermediate buffer for UMP input */ + u32 input_buf[4]; + int input_buf_head; + int input_pending; + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + struct mutex open_mutex; + + spinlock_t legacy_locks[2]; + struct snd_rawmidi *legacy_rmidi; + struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS]; + + /* for legacy output; need to open the actual substream unlike input */ + int legacy_out_opens; + struct snd_rawmidi_file legacy_out_rfile; + struct ump_cvt_to_ump *out_cvts; +#endif }; /* ops filled by UMP drivers */ @@ -54,6 +73,17 @@ int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count); int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device); +#else +static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device) +{ + return 0; +} +#endif + /* * Some definitions for UMP */ diff --git a/include/sound/ump_msg.h b/include/sound/ump_msg.h new file mode 100644 index 000000000000..c76c39944a5f --- /dev/null +++ b/include/sound/ump_msg.h @@ -0,0 +1,540 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Universal MIDI Packet (UMP): Message Definitions + */ +#ifndef __SOUND_UMP_MSG_H +#define __SOUND_UMP_MSG_H + +/* MIDI 1.0 / 2.0 Status Code (4bit) */ +enum { + UMP_MSG_STATUS_PER_NOTE_RCC = 0x0, + UMP_MSG_STATUS_PER_NOTE_ACC = 0x1, + UMP_MSG_STATUS_RPN = 0x2, + UMP_MSG_STATUS_NRPN = 0x3, + UMP_MSG_STATUS_RELATIVE_RPN = 0x4, + UMP_MSG_STATUS_RELATIVE_NRPN = 0x5, + UMP_MSG_STATUS_PER_NOTE_PITCH_BEND = 0x6, + UMP_MSG_STATUS_NOTE_OFF = 0x8, + UMP_MSG_STATUS_NOTE_ON = 0x9, + UMP_MSG_STATUS_POLY_PRESSURE = 0xa, + UMP_MSG_STATUS_CC = 0xb, + UMP_MSG_STATUS_PROGRAM = 0xc, + UMP_MSG_STATUS_CHANNEL_PRESSURE = 0xd, + UMP_MSG_STATUS_PITCH_BEND = 0xe, + UMP_MSG_STATUS_PER_NOTE_MGMT = 0xf, +}; + +/* MIDI 1.0 Channel Control (7bit) */ +enum { + UMP_CC_BANK_SELECT = 0, + UMP_CC_MODULATION = 1, + UMP_CC_BREATH = 2, + UMP_CC_FOOT = 4, + UMP_CC_PORTAMENTO_TIME = 5, + UMP_CC_DATA = 6, + UMP_CC_VOLUME = 7, + UMP_CC_BALANCE = 8, + UMP_CC_PAN = 10, + UMP_CC_EXPRESSION = 11, + UMP_CC_EFFECT_CONTROL_1 = 12, + UMP_CC_EFFECT_CONTROL_2 = 13, + UMP_CC_GP_1 = 16, + UMP_CC_GP_2 = 17, + UMP_CC_GP_3 = 18, + UMP_CC_GP_4 = 19, + UMP_CC_BANK_SELECT_LSB = 32, + UMP_CC_MODULATION_LSB = 33, + UMP_CC_BREATH_LSB = 34, + UMP_CC_FOOT_LSB = 36, + UMP_CC_PORTAMENTO_TIME_LSB = 37, + UMP_CC_DATA_LSB = 38, + UMP_CC_VOLUME_LSB = 39, + UMP_CC_BALANCE_LSB = 40, + UMP_CC_PAN_LSB = 42, + UMP_CC_EXPRESSION_LSB = 43, + UMP_CC_EFFECT1_LSB = 44, + UMP_CC_EFFECT2_LSB = 45, + UMP_CC_GP_1_LSB = 48, + UMP_CC_GP_2_LSB = 49, + UMP_CC_GP_3_LSB = 50, + UMP_CC_GP_4_LSB = 51, + UMP_CC_SUSTAIN = 64, + UMP_CC_PORTAMENTO_SWITCH = 65, + UMP_CC_SOSTENUTO = 66, + UMP_CC_SOFT_PEDAL = 67, + UMP_CC_LEGATO = 68, + UMP_CC_HOLD_2 = 69, + UMP_CC_SOUND_CONTROLLER_1 = 70, + UMP_CC_SOUND_CONTROLLER_2 = 71, + UMP_CC_SOUND_CONTROLLER_3 = 72, + UMP_CC_SOUND_CONTROLLER_4 = 73, + UMP_CC_SOUND_CONTROLLER_5 = 74, + UMP_CC_SOUND_CONTROLLER_6 = 75, + UMP_CC_SOUND_CONTROLLER_7 = 76, + UMP_CC_SOUND_CONTROLLER_8 = 77, + UMP_CC_SOUND_CONTROLLER_9 = 78, + UMP_CC_SOUND_CONTROLLER_10 = 79, + UMP_CC_GP_5 = 80, + UMP_CC_GP_6 = 81, + UMP_CC_GP_7 = 82, + UMP_CC_GP_8 = 83, + UMP_CC_PORTAMENTO_CONTROL = 84, + UMP_CC_EFFECT_1 = 91, + UMP_CC_EFFECT_2 = 92, + UMP_CC_EFFECT_3 = 93, + UMP_CC_EFFECT_4 = 94, + UMP_CC_EFFECT_5 = 95, + UMP_CC_DATA_INC = 96, + UMP_CC_DATA_DEC = 97, + UMP_CC_NRPN_LSB = 98, + UMP_CC_NRPN_MSB = 99, + UMP_CC_RPN_LSB = 100, + UMP_CC_RPN_MSB = 101, + UMP_CC_ALL_SOUND_OFF = 120, + UMP_CC_RESET_ALL = 121, + UMP_CC_LOCAL_CONTROL = 122, + UMP_CC_ALL_NOTES_OFF = 123, + UMP_CC_OMNI_OFF = 124, + UMP_CC_OMNI_ON = 125, + UMP_CC_POLY_OFF = 126, + UMP_CC_POLY_ON = 127, +}; + +/* MIDI 1.0 / 2.0 System Messages (0xfx) */ +enum { + UMP_SYSTEM_STATUS_MIDI_TIME_CODE = 0xf1, + UMP_SYSTEM_STATUS_SONG_POSITION = 0xf2, + UMP_SYSTEM_STATUS_SONG_SELECT = 0xf3, + UMP_SYSTEM_STATUS_TUNE_REQUEST = 0xf6, + UMP_SYSTEM_STATUS_TIMING_CLOCK = 0xf8, + UMP_SYSTEM_STATUS_START = 0xfa, + UMP_SYSTEM_STATUS_CONTINUE = 0xfb, + UMP_SYSTEM_STATUS_STOP = 0xfc, + UMP_SYSTEM_STATUS_ACTIVE_SENSING = 0xfe, + UMP_SYSTEM_STATUS_RESET = 0xff, +}; + +/* MIDI 1.0 Realtime and SysEx status messages (0xfx) */ +enum { + UMP_MIDI1_MSG_REALTIME = 0xf0, /* mask */ + UMP_MIDI1_MSG_SYSEX_START = 0xf0, + UMP_MIDI1_MSG_SYSEX_END = 0xf7, +}; + +/* + * UMP Message Definitions + */ + +/* MIDI 1.0 Note Off / Note On (32bit) */ +struct snd_ump_midi1_msg_note { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 velocity:8; +#else + u32 velocity:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Poly Pressure (32bit) */ +struct snd_ump_midi1_msg_paf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 data:8; +#else + u32 data:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Control Change (32bit) */ +struct snd_ump_midi1_msg_cc { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 index:8; + u32 data:8; +#else + u32 data:8; + u32 index:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Program Change (32bit) */ +struct snd_ump_midi1_msg_program { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 program:8; + u32 reserved:8; +#else +#endif + u32 reserved:8; + u32 program:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +} __packed; + +/* MIDI 1.0 Channel Pressure (32bit) */ +struct snd_ump_midi1_msg_caf { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 data:8; + u32 reserved:8; +#else + u32 reserved:8; + u32 data:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 Pitch Bend (32bit) */ +struct snd_ump_midi1_msg_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 data_lsb:8; + u32 data_msb:8; +#else + u32 data_msb:8; + u32 data_lsb:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* System Common and Real Time messages (32bit); no channel field */ +struct snd_ump_system_msg { +#ifdef __BIG_ENDIAN_BITFIELD + u32 type:4; + u32 group:4; + u32 status:8; + u32 parm1:8; + u32 parm2:8; +#else + u32 parm2:8; + u32 parm1:8; + u32 status:8; + u32 group:4; + u32 type:4; +#endif +} __packed; + +/* MIDI 1.0 UMP CVM (32bit) */ +union snd_ump_midi1_msg { + struct snd_ump_midi1_msg_note note; + struct snd_ump_midi1_msg_paf paf; + struct snd_ump_midi1_msg_cc cc; + struct snd_ump_midi1_msg_program pg; + struct snd_ump_midi1_msg_caf caf; + struct snd_ump_midi1_msg_pitchbend pb; + struct snd_ump_system_msg system; + u32 raw; +}; + +/* MIDI 2.0 Note Off / Note On (64bit) */ +struct snd_ump_midi2_msg_note { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 attribute_type:8; + /* 1 */ + u32 velocity:16; + u32 attribute_data:16; +#else + /* 0 */ + u32 attribute_type:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 attribute_data:16; + u32 velocity:16; +#endif +} __packed; + +/* MIDI 2.0 Poly Pressure (64bit) */ +struct snd_ump_midi2_msg_paf { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Controller (64bit) */ +struct snd_ump_midi2_msg_pernote_cc { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 index:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 index:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Management (64bit) */ +struct snd_ump_midi2_msg_pernote_mgmt { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 flags:8; + /* 1 */ + u32 reserved; +#else + /* 0 */ + u32 flags:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 reserved; +#endif +} __packed; + +/* MIDI 2.0 Control Change (64bit) */ +struct snd_ump_midi2_msg_cc { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 index:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 index:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Registered Controller (RPN) / Assignable Controller (NRPN) (64bit) */ +struct snd_ump_midi2_msg_rpn { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 bank:8; + u32 index:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 index:8; + u32 bank:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Program Change (64bit) */ +struct snd_ump_midi2_msg_program { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:15; + u32 bank_valid:1; + /* 1 */ + u32 program:8; + u32 reserved2:8; + u32 bank_msb:8; + u32 bank_lsb:8; +#else + /* 0 */ + u32 bank_valid:1; + u32 reserved:15; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 bank_lsb:8; + u32 bank_msb:8; + u32 reserved2:8; + u32 program:8; +#endif +} __packed; + +/* MIDI 2.0 Channel Pressure (64bit) */ +struct snd_ump_midi2_msg_caf { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:16; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:16; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Pitch Bend (64bit) */ +struct snd_ump_midi2_msg_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 reserved:16; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:16; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 Per-Note Pitch Bend (64bit) */ +struct snd_ump_midi2_msg_pernote_pitchbend { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 group:4; + u32 status:4; + u32 channel:4; + u32 note:8; + u32 reserved:8; + /* 1 */ + u32 data; +#else + /* 0 */ + u32 reserved:8; + u32 note:8; + u32 channel:4; + u32 status:4; + u32 group:4; + u32 type:4; + /* 1 */ + u32 data; +#endif +} __packed; + +/* MIDI 2.0 UMP CVM (64bit) */ +union snd_ump_midi2_msg { + struct snd_ump_midi2_msg_note note; + struct snd_ump_midi2_msg_paf paf; + struct snd_ump_midi2_msg_pernote_cc pernote_cc; + struct snd_ump_midi2_msg_pernote_mgmt pernote_mgmt; + struct snd_ump_midi2_msg_cc cc; + struct snd_ump_midi2_msg_rpn rpn; + struct snd_ump_midi2_msg_program pg; + struct snd_ump_midi2_msg_caf caf; + struct snd_ump_midi2_msg_pitchbend pb; + struct snd_ump_midi2_msg_pernote_pitchbend pernote_pb; + u32 raw[2]; +}; + +#endif /* __SOUND_UMP_MSG_H */ diff --git a/sound/core/Kconfig b/sound/core/Kconfig index eb1c6c930de9..e41818e59a15 100644 --- a/sound/core/Kconfig +++ b/sound/core/Kconfig @@ -30,6 +30,15 @@ config SND_UMP tristate select SND_RAWMIDI +config SND_UMP_LEGACY_RAWMIDI + bool "Legacy raw MIDI support for UMP streams" + depends on SND_UMP + help + This option enables the legacy raw MIDI support for UMP streams. + When this option is set, an additional rawmidi device for the + legacy MIDI 1.0 byte streams is created for each UMP Endpoint. + The device contains 16 substreams corresponding to UMP groups. + config SND_COMPRESS_OFFLOAD tristate diff --git a/sound/core/Makefile b/sound/core/Makefile index 562a05edbc50..a6b444ee2832 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -29,6 +29,7 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o snd-ctl-led-objs := control_led.o snd-rawmidi-objs := rawmidi.o snd-ump-objs := ump.o +snd-ump-$(CONFIG_SND_UMP_LEGACY_RAWMIDI) += ump_convert.o snd-timer-objs := timer.o snd-hrtimer-objs := hrtimer.o snd-rtctimer-objs := rtctimer.o diff --git a/sound/core/ump.c b/sound/core/ump.c index 46ec297a786c..cbe704b5d90d 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -11,6 +11,7 @@ #include #include #include +#include "ump_convert.h" #define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args) #define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args) @@ -29,6 +30,23 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, int up); static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count); +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words); +#else +static inline int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + return 0; +} +static inline void process_legacy_input(struct snd_ump_endpoint *ump, + const u32 *src, int words) +{ +} +#endif + static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = { .dev_register = snd_ump_dev_register, .dev_unregister = snd_ump_dev_unregister, @@ -65,6 +83,10 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) if (ump->private_free) ump->private_free(ump); + +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + snd_ump_convert_free(ump); +#endif } /** @@ -110,6 +132,11 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, if (!ump) return -ENOMEM; INIT_LIST_HEAD(&ump->block_list); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + mutex_init(&ump->open_mutex); + spin_lock_init(&ump->legacy_locks[0]); + spin_lock_init(&ump->legacy_locks[1]); +#endif err = snd_rawmidi_init(&ump->core, card, id, device, output, input, info_flags); if (err < 0) { @@ -206,6 +233,33 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); } +/* number of 32bit words per message type */ +static unsigned char ump_packet_words[0x10] = { + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 +}; + +/* parse the UMP packet data; + * the data is copied onto ump->input_buf[]. + * When a full packet is completed, returns the number of words (from 1 to 4). + * OTOH, if the packet is incomplete, returns 0. + */ +static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) +{ + int words; + + if (!ump->input_pending) + ump->input_pending = ump_packet_words[ump_message_type(val)]; + + ump->input_buf[ump->input_buf_head++] = val; + ump->input_pending--; + if (!ump->input_pending) { + words = ump->input_buf_head; + ump->input_buf_head = 0; + return words; + } + return 0; +} + /** * snd_ump_receive - transfer UMP packets from the device * @ump: the UMP endpoint @@ -218,9 +272,18 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream) */ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) { - struct snd_rawmidi_substream *substream = - ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; + struct snd_rawmidi_substream *substream; + const u32 *p = buffer; + int n, words = count >> 2; + + while (words--) { + n = snd_ump_receive_ump_val(ump, *p++); + if (!n) + continue; + process_legacy_input(ump, ump->input_buf, n); + } + substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT]; if (!substream) return 0; return snd_rawmidi_receive(substream, (const char *)buffer, count); @@ -241,10 +304,15 @@ int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count) { struct snd_rawmidi_substream *substream = ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + int err; if (!substream) return -ENODEV; - return snd_rawmidi_transmit(substream, (char *)buffer, count); + err = snd_rawmidi_transmit(substream, (char *)buffer, count); + /* received either data or an error? */ + if (err) + return err; + return process_legacy_output(ump, buffer, count); } EXPORT_SYMBOL_GPL(snd_ump_transmit); @@ -386,5 +454,189 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, } } +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) +/* + * Legacy rawmidi support + */ +static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = substream->number; + int err; + + mutex_lock(&ump->open_mutex); + if (ump->legacy_substreams[dir][group]) { + err = -EBUSY; + goto unlock; + } + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!ump->legacy_out_opens) { + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, + &ump->legacy_out_rfile); + if (err < 0) + goto unlock; + } + ump->legacy_out_opens++; + snd_ump_reset_convert_to_ump(ump, group); + } + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = substream; + spin_unlock_irq(&ump->legacy_locks[dir]); + unlock: + mutex_unlock(&ump->open_mutex); + return 0; +} + +static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + int group = substream->number; + + mutex_lock(&ump->open_mutex); + spin_lock_irq(&ump->legacy_locks[dir]); + ump->legacy_substreams[dir][group] = NULL; + spin_unlock_irq(&ump->legacy_locks[dir]); + if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) { + if (!--ump->legacy_out_opens) + snd_rawmidi_kernel_release(&ump->legacy_out_rfile); + } + mutex_unlock(&ump->open_mutex); + return 0; +} + +static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + int dir = substream->stream; + + ump->ops->trigger(ump, dir, up); +} + +static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_ump_endpoint *ump = substream->rmidi->private_data; + + if (ump->ops->drain) + ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT); +} + +static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi) +{ + /* dummy, just for avoiding create superfluous seq clients */ + return 0; +} + +static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, +}; + +static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = { + .open = snd_ump_legacy_open, + .close = snd_ump_legacy_close, + .trigger = snd_ump_legacy_trigger, + .drain = snd_ump_legacy_drain, +}; + +static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = { + .dev_register = snd_ump_legacy_dev_register, +}; + +static int process_legacy_output(struct snd_ump_endpoint *ump, + u32 *buffer, int count) +{ + struct snd_rawmidi_substream *substream; + struct ump_cvt_to_ump *ctx; + const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT; + unsigned char c; + int group, size = 0; + unsigned long flags; + + if (!ump->out_cvts || !ump->legacy_out_opens) + return 0; + + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) { + substream = ump->legacy_substreams[dir][group]; + if (!substream) + continue; + ctx = &ump->out_cvts[group]; + while (!ctx->ump_bytes && + snd_rawmidi_transmit(substream, &c, 1) > 0) + snd_ump_convert_to_ump(ump, group, c); + if (ctx->ump_bytes && ctx->ump_bytes <= count) { + size = ctx->ump_bytes; + memcpy(buffer, ctx->ump, size); + ctx->ump_bytes = 0; + break; + } + } + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); + return size; +} + +static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, + int words) +{ + struct snd_rawmidi_substream *substream; + unsigned char buf[16]; + unsigned char group; + unsigned long flags; + const int dir = SNDRV_RAWMIDI_STREAM_INPUT; + int size; + + size = snd_ump_convert_from_ump(ump, src, buf, &group); + if (size <= 0) + return; + spin_lock_irqsave(&ump->legacy_locks[dir], flags); + substream = ump->legacy_substreams[dir][group]; + if (substream) + snd_rawmidi_receive(substream, buf, size); + spin_unlock_irqrestore(&ump->legacy_locks[dir], flags); +} + +int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, + char *id, int device) +{ + struct snd_rawmidi *rmidi; + bool input, output; + int err; + + err = snd_ump_convert_init(ump); + if (err < 0) + return err; + + input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT; + output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT; + err = snd_rawmidi_new(ump->core.card, id, device, + output ? 16 : 0, input ? 16 : 0, + &rmidi); + if (err < 0) { + snd_ump_convert_free(ump); + return err; + } + + if (input) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_ump_legacy_input_ops); + if (output) + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_ump_legacy_output_ops); + rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP; + rmidi->ops = &snd_ump_legacy_ops; + rmidi->private_data = ump; + ump->legacy_rmidi = rmidi; + ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id); + return 0; +} +EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi); +#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */ + MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver"); MODULE_LICENSE("GPL"); diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c new file mode 100644 index 000000000000..cb7c2f959a27 --- /dev/null +++ b/sound/core/ump_convert.c @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Helpers for UMP <-> MIDI 1.0 byte stream conversion + */ + +#include +#include +#include +#include +#include +#include "ump_convert.h" + +/* + * Upgrade / downgrade value bits + */ +static u8 downscale_32_to_7bit(u32 src) +{ + return src >> 25; +} + +static u16 downscale_32_to_14bit(u32 src) +{ + return src >> 18; +} + +static u8 downscale_16_to_7bit(u16 src) +{ + return src >> 9; +} + +static u16 upscale_7_to_16bit(u8 src) +{ + u16 val, repeat; + + val = (u16)src << 9; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 3) | (repeat >> 3); +} + +static u32 upscale_7_to_32bit(u8 src) +{ + u32 val, repeat; + + val = src << 25; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 19) | (repeat << 13) | + (repeat << 7) | (repeat << 1) | (repeat >> 5); +} + +static u32 upscale_14_to_32bit(u16 src) +{ + u32 val, repeat; + + val = src << 18; + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +/* + * UMP -> MIDI 1 byte stream conversion + */ +/* convert a UMP System message to MIDI 1.0 byte stream */ +static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + switch (ump_message_status_code(data)) { + case UMP_SYSTEM_STATUS_MIDI_TIME_CODE: + case UMP_SYSTEM_STATUS_SONG_SELECT: + buf[1] = (data >> 8) & 0x7f; + return 1; + case UMP_SYSTEM_STATUS_SONG_POSITION: + buf[1] = (data >> 8) & 0x7f; + buf[2] = data & 0x7f; + return 3; + default: + return 1; + } +} + +/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf) +{ + buf[0] = ump_message_status_channel(data); + buf[1] = (data >> 8) & 0xff; + switch (ump_message_status_code(data)) { + case UMP_MSG_STATUS_PROGRAM: + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + return 2; + default: + buf[2] = data & 0xff; + return 3; + } +} + +/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */ +static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2, + unsigned char *buf) +{ + unsigned char status = midi2->note.status; + unsigned char channel = midi2->note.channel; + u16 v; + + buf[0] = (status << 4) | channel; + switch (status) { + case UMP_MSG_STATUS_NOTE_OFF: + case UMP_MSG_STATUS_NOTE_ON: + buf[1] = midi2->note.note; + buf[2] = downscale_16_to_7bit(midi2->note.velocity); + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + buf[2] = 1; + return 3; + case UMP_MSG_STATUS_POLY_PRESSURE: + buf[1] = midi2->paf.note; + buf[2] = downscale_32_to_7bit(midi2->paf.data); + return 3; + case UMP_MSG_STATUS_CC: + buf[1] = midi2->cc.index; + buf[2] = downscale_32_to_7bit(midi2->cc.data); + return 3; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + buf[1] = downscale_32_to_7bit(midi2->caf.data); + return 2; + case UMP_MSG_STATUS_PROGRAM: + if (midi2->pg.bank_valid) { + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = UMP_CC_BANK_SELECT; + buf[2] = midi2->pg.bank_msb; + buf[3] = channel | (UMP_MSG_STATUS_CC << 4); + buf[4] = UMP_CC_BANK_SELECT_LSB; + buf[5] = midi2->pg.bank_lsb; + buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4); + buf[7] = midi2->pg.program; + return 8; + } + buf[1] = midi2->pg.program; + return 2; + case UMP_MSG_STATUS_PITCH_BEND: + v = downscale_32_to_14bit(midi2->pb.data); + buf[1] = v & 0x7f; + buf[2] = v >> 7; + return 3; + case UMP_MSG_STATUS_RPN: + case UMP_MSG_STATUS_NRPN: + buf[0] = channel | (UMP_MSG_STATUS_CC << 4); + buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB; + buf[2] = midi2->rpn.bank; + buf[3] = buf[0]; + buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB; + buf[5] = midi2->rpn.index; + buf[6] = buf[0]; + buf[7] = UMP_CC_DATA; + v = downscale_32_to_14bit(midi2->rpn.data); + buf[8] = v >> 7; + buf[9] = buf[0]; + buf[10] = UMP_CC_DATA_LSB; + buf[11] = v & 0x7f; + return 12; + default: + return 0; + } +} + +/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */ +static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf) +{ + unsigned char status; + unsigned char bytes; + int size, offset; + + status = ump_sysex_message_status(*data); + if (status > UMP_SYSEX_STATUS_END) + return 0; // unsupported, skip + bytes = ump_sysex_message_length(*data); + if (bytes > 6) + return 0; // skip + + size = 0; + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_START) { + buf[0] = UMP_MIDI1_MSG_SYSEX_START; + size = 1; + } + + offset = 8; + for (; bytes; bytes--, size++) { + buf[size] = (*data >> offset) & 0x7f; + if (!offset) { + offset = 24; + data++; + } else { + offset -= 8; + } + } + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_END) + buf[size++] = UMP_MIDI1_MSG_SYSEX_END; + + return size; +} + +/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf; + * the target group is stored at @group_ret, + * returns the number of bytes of MIDI 1.0 stream + */ +int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, + const u32 *data, + unsigned char *buf, + unsigned char *group_ret) +{ + *group_ret = ump_message_group(*data); + + switch (ump_message_type(*data)) { + case UMP_MSG_TYPE_SYSTEM: + return cvt_ump_system_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE: + return cvt_ump_midi1_to_legacy(*data, buf); + case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE: + return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data, + buf); + case UMP_MSG_TYPE_DATA: + return cvt_ump_sysex7_to_legacy(data, buf); + } + + return 0; +} + +/* + * MIDI 1 byte stream -> UMP conversion + */ +/* convert MIDI 1.0 SysEx to a UMP packet */ +static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data, bool finish) +{ + unsigned char status; + bool start = cvt->in_sysex == 1; + int i, offset; + + if (start && finish) + status = UMP_SYSEX_STATUS_SINGLE; + else if (start) + status = UMP_SYSEX_STATUS_START; + else if (finish) + status = UMP_SYSEX_STATUS_END; + else + status = UMP_SYSEX_STATUS_CONTINUE; + *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len); + offset = 8; + for (i = 0; i < cvt->len; i++) { + *data |= cvt->buf[i] << offset; + if (!offset) { + offset = 24; + data++; + } else + offset -= 8; + } + cvt->len = 0; + if (finish) + cvt->in_sysex = 0; + else + cvt->in_sysex++; + return 8; +} + +/* convert to a UMP System message */ +static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data) +{ + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]); + if (cvt->cmd_bytes > 1) + data[0] |= cvt->buf[1] << 8; + if (cvt->cmd_bytes > 2) + data[0] |= cvt->buf[2]; + return 4; +} + +static void fill_rpn(struct ump_cvt_to_ump_bank *cc, + union snd_ump_midi2_msg *midi2) +{ + if (cc->rpn_set) { + midi2->rpn.status = UMP_MSG_STATUS_RPN; + midi2->rpn.bank = cc->cc_rpn_msb; + midi2->rpn.index = cc->cc_rpn_lsb; + cc->rpn_set = 0; + cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; + } else { + midi2->rpn.status = UMP_MSG_STATUS_NRPN; + midi2->rpn.bank = cc->cc_nrpn_msb; + midi2->rpn.index = cc->cc_nrpn_lsb; + cc->nrpn_set = 0; + cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0; + } + midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | + cc->cc_data_lsb); + cc->cc_data_msb = cc->cc_data_lsb = 0; +} + +/* convert to a MIDI 1.0 Channel Voice message */ +static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, + struct ump_cvt_to_ump *cvt, + unsigned char group, u32 *data, + unsigned char bytes) +{ + const unsigned char *buf = cvt->buf; + struct ump_cvt_to_ump_bank *cc; + union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data; + unsigned char status, channel; + + BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4); + BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8); + + /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */ + if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { + data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE, + group, 0, buf[0]); + data[0] |= buf[1] << 8; + if (bytes > 2) + data[0] |= buf[2]; + return 4; + } + + status = *buf >> 4; + channel = *buf & 0x0f; + cc = &cvt->bank[channel]; + + /* special handling: treat note-on with 0 velocity as note-off */ + if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2]) + status = UMP_MSG_STATUS_NOTE_OFF; + + /* initialize the packet */ + data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE, + group, status, channel); + data[1] = 0; + + switch (status) { + case UMP_MSG_STATUS_NOTE_ON: + if (!buf[2]) + status = UMP_MSG_STATUS_NOTE_OFF; + fallthrough; + case UMP_MSG_STATUS_NOTE_OFF: + midi2->note.note = buf[1]; + midi2->note.velocity = upscale_7_to_16bit(buf[2]); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi2->paf.note = buf[1]; + midi2->paf.data = upscale_7_to_32bit(buf[2]); + break; + case UMP_MSG_STATUS_CC: + switch (buf[1]) { + case UMP_CC_RPN_MSB: + cc->rpn_set = 1; + cc->cc_rpn_msb = buf[2]; + return 0; // skip + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->nrpn_set = 1; + cc->cc_nrpn_msb = buf[2]; + return 0; // skip + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT: + cc->bank_set = 1; + cc->cc_bank_msb = buf[2]; + return 0; // skip + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = buf[2]; + return 0; // skip + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = buf[2]; + if (cc->rpn_set || cc->nrpn_set) + fill_rpn(cc, midi2); + else + return 0; // skip + break; + default: + midi2->cc.index = buf[1]; + midi2->cc.data = upscale_7_to_32bit(buf[2]); + break; + } + break; + case UMP_MSG_STATUS_PROGRAM: + midi2->pg.program = buf[1]; + if (cc->bank_set) { + midi2->pg.bank_valid = 1; + midi2->pg.bank_msb = cc->cc_bank_msb; + midi2->pg.bank_lsb = cc->cc_bank_lsb; + cc->bank_set = 0; + cc->cc_bank_msb = cc->cc_bank_lsb = 0; + } + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi2->caf.data = upscale_7_to_32bit(buf[1]); + break; + case UMP_MSG_STATUS_PITCH_BEND: + midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7)); + break; + default: + return 0; + } + + return 8; +} + +static int do_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c, u32 *data) +{ + /* bytes for 0x80-0xf0 */ + static unsigned char cmd_bytes[8] = { + 3, 3, 3, 3, 2, 2, 3, 0 + }; + /* bytes for 0xf0-0xff */ + static unsigned char system_bytes[16] = { + 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 + }; + struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; + unsigned char bytes; + + if (c == UMP_MIDI1_MSG_SYSEX_START) { + cvt->in_sysex = 1; + cvt->len = 0; + return 0; + } + if (c == UMP_MIDI1_MSG_SYSEX_END) { + if (!cvt->in_sysex) + return 0; /* skip */ + return cvt_legacy_sysex_to_ump(cvt, group, data, true); + } + + if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) { + bytes = system_bytes[c & 0x0f]; + if (!bytes) + return 0; /* skip */ + if (bytes == 1) { + data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c); + return 4; + } + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (c & 0x80) { + bytes = cmd_bytes[(c >> 8) & 7]; + cvt->buf[0] = c; + cvt->len = 1; + cvt->cmd_bytes = bytes; + cvt->in_sysex = 0; /* abort SysEx */ + return 0; + } + + if (cvt->in_sysex) { + cvt->buf[cvt->len++] = c; + if (cvt->len == 6) + return cvt_legacy_sysex_to_ump(cvt, group, data, false); + return 0; + } + + if (!cvt->len) + return 0; + + cvt->buf[cvt->len++] = c; + if (cvt->len < cvt->cmd_bytes) + return 0; + cvt->len = 1; + if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME) + return cvt_legacy_system_to_ump(cvt, group, data); + return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes); +} + +/* feed a MIDI 1.0 byte @c and convert to a UMP packet; + * the target group is @group, + * the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes + */ +void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c) +{ + struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; + + cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump); +} + +/* reset the converter context, called at each open */ +void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group) +{ + memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts)); +} + +/* initialize converters */ +int snd_ump_convert_init(struct snd_ump_endpoint *ump) +{ + ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); + if (!ump->out_cvts) + return -ENOMEM; + return 0; +} + +/* release resources */ +void snd_ump_convert_free(struct snd_ump_endpoint *ump) +{ + kfree(ump->out_cvts); + ump->out_cvts = NULL; +} diff --git a/sound/core/ump_convert.h b/sound/core/ump_convert.h new file mode 100644 index 000000000000..bbfe96084779 --- /dev/null +++ b/sound/core/ump_convert.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __UMP_CONVERT_H +#define __UMP_CONVERT_H + +#include + +/* context for converting from legacy control messages to UMP packet */ +struct ump_cvt_to_ump_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + +/* context for converting from MIDI1 byte stream to UMP packet */ +struct ump_cvt_to_ump { + /* MIDI1 intermediate buffer */ + unsigned char buf[4]; + int len; + int cmd_bytes; + + /* UMP output packet */ + u32 ump[4]; + int ump_bytes; + + /* various status */ + unsigned int in_sysex; + struct ump_cvt_to_ump_bank bank[16]; /* per channel */ +}; + +int snd_ump_convert_init(struct snd_ump_endpoint *ump); +void snd_ump_convert_free(struct snd_ump_endpoint *ump); +int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, + const u32 *data, unsigned char *dst, + unsigned char *group_ret); +void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group, unsigned char c); +void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, + unsigned char group); +#endif /* __UMP_CONVERT_H */ -- cgit v1.2.3 From ec362b63c4b560006666998c582edc76a2f77910 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:36 +0200 Subject: ALSA: usb-audio: Enable the legacy raw MIDI support Attach the legacy rawmidi devices when enabled in Kconfig accordingly. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-16-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 7e849b2384ee..f3fba8b07cb3 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -857,6 +857,25 @@ static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) return 0; } +/* attach legacy rawmidis */ +static int attach_legacy_rawmidi(struct snd_usb_midi2_interface *umidi) +{ +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + struct snd_usb_midi2_ump *rmidi; + int err; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + err = snd_ump_attach_legacy_rawmidi(rmidi->ump, + "Legacy MIDI", + umidi->chip->num_rawmidis); + if (err < 0) + return err; + umidi->chip->num_rawmidis++; + } +#endif + return 0; +} + static void snd_usb_midi_v2_free(struct snd_usb_midi2_interface *umidi) { free_all_midi2_endpoints(umidi); @@ -922,7 +941,7 @@ static int parse_midi_2_0(struct snd_usb_midi2_interface *umidi) } } - return 0; + return attach_legacy_rawmidi(umidi); } /* is the given interface for MIDI 2.0? */ @@ -991,6 +1010,12 @@ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) usb_string(dev, dev->descriptor.iSerialNumber, ump->info.product_id, sizeof(ump->info.product_id)); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) + if (ump->legacy_rmidi && !*ump->legacy_rmidi->name) + snprintf(ump->legacy_rmidi->name, + sizeof(ump->legacy_rmidi->name), + "%s (MIDI 1.0)", ump->info.name); +#endif } } -- cgit v1.2.3 From f4487c42aae596f02e0cb02a028d2a107ec1737d Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:37 +0200 Subject: ALSA: usb-audio: Inform inconsistent protocols in GTBs When parsing Group Terminal Blocks, we overwrote the preferred protocol and the protocol capabilities silently from the last parsed GTB. This patch adds the information print indicating the unexpected overrides instead of silent action. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-17-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index f3fba8b07cb3..341783418a6a 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -581,6 +581,7 @@ static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, const struct usb_ms20_gr_trm_block_descriptor *desc) { struct snd_ump_endpoint *ump = rmidi->ump; + unsigned int protocol, protocol_caps; /* set default protocol */ switch (desc->bMIDIProtocol) { @@ -588,24 +589,40 @@ static int parse_group_terminal_block(struct snd_usb_midi2_ump *rmidi, case USB_MS_MIDI_PROTO_1_0_64_JRTS: case USB_MS_MIDI_PROTO_1_0_128: case USB_MS_MIDI_PROTO_1_0_128_JRTS: - ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1; + protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI1; break; case USB_MS_MIDI_PROTO_2_0: case USB_MS_MIDI_PROTO_2_0_JRTS: - ump->info.protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2; + protocol = SNDRV_UMP_EP_INFO_PROTO_MIDI2; break; + default: + return 0; } - ump->info.protocol_caps = ump->info.protocol; + if (ump->info.protocol && ump->info.protocol != protocol) + usb_audio_info(rmidi->umidi->chip, + "Overriding preferred MIDI protocol in GTB %d: %x -> %x\n", + rmidi->usb_block_id, ump->info.protocol, + protocol); + ump->info.protocol = protocol; + + protocol_caps = protocol; switch (desc->bMIDIProtocol) { case USB_MS_MIDI_PROTO_1_0_64_JRTS: case USB_MS_MIDI_PROTO_1_0_128_JRTS: case USB_MS_MIDI_PROTO_2_0_JRTS: - ump->info.protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX | + protocol_caps |= SNDRV_UMP_EP_INFO_PROTO_JRTS_TX | SNDRV_UMP_EP_INFO_PROTO_JRTS_RX; break; } + if (ump->info.protocol_caps && ump->info.protocol_caps != protocol_caps) + usb_audio_info(rmidi->umidi->chip, + "Overriding MIDI protocol caps in GTB %d: %x -> %x\n", + rmidi->usb_block_id, ump->info.protocol_caps, + protocol_caps); + ump->info.protocol_caps = protocol_caps; + return 0; } -- cgit v1.2.3 From f80e6d60d677be1d4dbbcdbf97379b8fbcf97ff0 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:38 +0200 Subject: ALSA: seq: Clear padded bytes at expanding events There can be a small memory hole that may not be cleared at expanding an event with the variable length type. Make sure to clear it. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-18-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_memory.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c index 47ef6bc30c0e..c8d26bce69ff 100644 --- a/sound/core/seq/seq_memory.c +++ b/sound/core/seq/seq_memory.c @@ -152,12 +152,16 @@ int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char return -EINVAL; if (copy_from_user(buf, (void __force __user *)event->data.ext.ptr, len)) return -EFAULT; - return newlen; + } else { + err = snd_seq_dump_var_event(event, + in_kernel ? seq_copy_in_kernel : seq_copy_in_user, + &buf); + if (err < 0) + return err; } - err = snd_seq_dump_var_event(event, - in_kernel ? seq_copy_in_kernel : seq_copy_in_user, - &buf); - return err < 0 ? err : newlen; + if (len != newlen) + memset(buf + len, 0, newlen - len); + return newlen; } EXPORT_SYMBOL(snd_seq_expand_var_event); -- cgit v1.2.3 From ea46f79709b6262f12c8ca24f32bfe8d638152ee Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:39 +0200 Subject: ALSA: seq: Add snd_seq_expand_var_event_at() helper Create a new variant of snd_seq_expand_var_event() for expanding the data starting from the given byte offset. It'll be used by the new UMP sequencer code later. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-19-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/seq_kernel.h | 2 ++ sound/core/seq/seq_memory.c | 86 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 69 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/include/sound/seq_kernel.h b/include/sound/seq_kernel.h index 658911926f3a..527e7f8ad661 100644 --- a/include/sound/seq_kernel.h +++ b/include/sound/seq_kernel.h @@ -70,6 +70,8 @@ int snd_seq_kernel_client_ctl(int client, unsigned int cmd, void *arg); typedef int (*snd_seq_dump_func_t)(void *ptr, void *buf, int count); int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, int in_kernel, int size_aligned); +int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, + char *buf, int offset); int snd_seq_dump_var_event(const struct snd_seq_event *event, snd_seq_dump_func_t func, void *private_data); diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c index c8d26bce69ff..a8d2db439f86 100644 --- a/sound/core/seq/seq_memory.c +++ b/sound/core/seq/seq_memory.c @@ -63,8 +63,9 @@ static int get_var_len(const struct snd_seq_event *event) return event->data.ext.len & ~SNDRV_SEQ_EXT_MASK; } -int snd_seq_dump_var_event(const struct snd_seq_event *event, - snd_seq_dump_func_t func, void *private_data) +static int dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data, + int offset, int maxlen) { int len, err; struct snd_seq_event_cell *cell; @@ -72,10 +73,16 @@ int snd_seq_dump_var_event(const struct snd_seq_event *event, len = get_var_len(event); if (len <= 0) return len; + if (len <= offset) + return 0; + if (maxlen && len > offset + maxlen) + len = offset + maxlen; if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { char buf[32]; char __user *curptr = (char __force __user *)event->data.ext.ptr; + curptr += offset; + len -= offset; while (len > 0) { int size = sizeof(buf); if (len < size) @@ -91,20 +98,35 @@ int snd_seq_dump_var_event(const struct snd_seq_event *event, return 0; } if (!(event->data.ext.len & SNDRV_SEQ_EXT_CHAINED)) - return func(private_data, event->data.ext.ptr, len); + return func(private_data, event->data.ext.ptr + offset, + len - offset); cell = (struct snd_seq_event_cell *)event->data.ext.ptr; for (; len > 0 && cell; cell = cell->next) { int size = sizeof(struct snd_seq_event); + char *curptr = (char *)&cell->event; + + if (offset >= size) { + offset -= size; + len -= size; + continue; + } if (len < size) size = len; - err = func(private_data, &cell->event, size); + err = func(private_data, curptr + offset, size - offset); if (err < 0) return err; + offset = 0; len -= size; } return 0; } + +int snd_seq_dump_var_event(const struct snd_seq_event *event, + snd_seq_dump_func_t func, void *private_data) +{ + return dump_var_event(event, func, private_data, 0, 0); +} EXPORT_SYMBOL(snd_seq_dump_var_event); @@ -132,11 +154,27 @@ static int seq_copy_in_user(void *ptr, void *src, int size) return 0; } +static int expand_var_event(const struct snd_seq_event *event, + int offset, int size, char *buf, bool in_kernel) +{ + if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { + if (! in_kernel) + return -EINVAL; + if (copy_from_user(buf, + (char __force __user *)event->data.ext.ptr + offset, + size)) + return -EFAULT; + return 0; + } + return dump_var_event(event, + in_kernel ? seq_copy_in_kernel : seq_copy_in_user, + &buf, offset, size); +} + int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char *buf, int in_kernel, int size_aligned) { - int len, newlen; - int err; + int len, newlen, err; len = get_var_len(event); if (len < 0) @@ -146,25 +184,35 @@ int snd_seq_expand_var_event(const struct snd_seq_event *event, int count, char newlen = roundup(len, size_aligned); if (count < newlen) return -EAGAIN; - - if (event->data.ext.len & SNDRV_SEQ_EXT_USRPTR) { - if (! in_kernel) - return -EINVAL; - if (copy_from_user(buf, (void __force __user *)event->data.ext.ptr, len)) - return -EFAULT; - } else { - err = snd_seq_dump_var_event(event, - in_kernel ? seq_copy_in_kernel : seq_copy_in_user, - &buf); - if (err < 0) - return err; - } + err = expand_var_event(event, 0, len, buf, in_kernel); + if (err < 0) + return err; if (len != newlen) memset(buf + len, 0, newlen - len); return newlen; } EXPORT_SYMBOL(snd_seq_expand_var_event); +int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, + char *buf, int offset) +{ + int len, err; + + len = get_var_len(event); + if (len < 0) + return len; + if (len <= offset) + return 0; + len -= offset; + if (len > count) + len = count; + err = expand_var_event(event, offset, count, buf, true); + if (err < 0) + return err; + return len; +} +EXPORT_SYMBOL_GPL(snd_seq_expand_var_event_at); + /* * release this cell, free extended data if available */ -- cgit v1.2.3 From d0c8308fc58b3f02868388b294a94d501c816900 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:40 +0200 Subject: ALSA: seq: Treat snd_seq_client object directly in client drivers Introduce the new helpers, snd_seq_kernel_client_get() and _put() for kernel client drivers to treat the snd_seq_client more directly. This allows us to reduce the exported symbols and APIs at each time we need to access some field in future. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-20-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 15 +++++++++++++++ sound/core/seq/seq_clientmgr.h | 4 ++++ 2 files changed, 19 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 2d707afa1ef1..98e8032a32e2 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2390,6 +2390,21 @@ int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table } EXPORT_SYMBOL(snd_seq_kernel_client_write_poll); +/* get a sequencer client object; for internal use from a kernel client */ +struct snd_seq_client *snd_seq_kernel_client_get(int id) +{ + return snd_seq_client_use_ptr(id); +} +EXPORT_SYMBOL_GPL(snd_seq_kernel_client_get); + +/* put a sequencer client object; for internal use from a kernel client */ +void snd_seq_kernel_client_put(struct snd_seq_client *cptr) +{ + if (cptr) + snd_seq_client_unlock(cptr); +} +EXPORT_SYMBOL_GPL(snd_seq_kernel_client_put); + /*---------------------------------------------------------------------------*/ #ifdef CONFIG_SND_PROC_FS diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index 8cdd0ee53fb1..f05704e45ab4 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -88,4 +88,8 @@ void snd_seq_client_ioctl_unlock(int clientid); extern int seq_client_load[15]; +/* for internal use between kernel sequencer clients */ +struct snd_seq_client *snd_seq_kernel_client_get(int client); +void snd_seq_kernel_client_put(struct snd_seq_client *cptr); + #endif -- cgit v1.2.3 From 94c5b717ada970c0136a9369f620d11773b38a51 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:41 +0200 Subject: ALSA: seq: Drop dead code for the old broadcast support The broadcast and multicast supports have been never enabled. Let's drop the dead code. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-21-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 105 +---------------------------------------- 1 file changed, 1 insertion(+), 104 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 98e8032a32e2..019af1343325 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -711,93 +711,6 @@ static int deliver_to_subscribers(struct snd_seq_client *client, return (result < 0) ? result : num_ev; } - -#ifdef SUPPORT_BROADCAST -/* - * broadcast to all ports: - */ -static int port_broadcast_event(struct snd_seq_client *client, - struct snd_seq_event *event, - int atomic, int hop) -{ - int num_ev = 0, err, result = 0; - struct snd_seq_client *dest_client; - struct snd_seq_client_port *port; - - dest_client = get_event_dest_client(event, SNDRV_SEQ_FILTER_BROADCAST); - if (dest_client == NULL) - return 0; /* no matching destination */ - - read_lock(&dest_client->ports_lock); - list_for_each_entry(port, &dest_client->ports_list_head, list) { - event->dest.port = port->addr.port; - /* pass NULL as source client to avoid error bounce */ - err = snd_seq_deliver_single_event(NULL, event, - SNDRV_SEQ_FILTER_BROADCAST, - atomic, hop); - if (err < 0) { - /* save first error that occurs and continue */ - if (!result) - result = err; - continue; - } - num_ev++; - } - read_unlock(&dest_client->ports_lock); - snd_seq_client_unlock(dest_client); - event->dest.port = SNDRV_SEQ_ADDRESS_BROADCAST; /* restore */ - return (result < 0) ? result : num_ev; -} - -/* - * send the event to all clients: - * if destination port is also ADDRESS_BROADCAST, deliver to all ports. - */ -static int broadcast_event(struct snd_seq_client *client, - struct snd_seq_event *event, int atomic, int hop) -{ - int err, result = 0, num_ev = 0; - int dest; - struct snd_seq_addr addr; - - addr = event->dest; /* save */ - - for (dest = 0; dest < SNDRV_SEQ_MAX_CLIENTS; dest++) { - /* don't send to itself */ - if (dest == client->number) - continue; - event->dest.client = dest; - event->dest.port = addr.port; - if (addr.port == SNDRV_SEQ_ADDRESS_BROADCAST) - err = port_broadcast_event(client, event, atomic, hop); - else - /* pass NULL as source client to avoid error bounce */ - err = snd_seq_deliver_single_event(NULL, event, - SNDRV_SEQ_FILTER_BROADCAST, - atomic, hop); - if (err < 0) { - /* save first error that occurs and continue */ - if (!result) - result = err; - continue; - } - num_ev += err; - } - event->dest = addr; /* restore */ - return (result < 0) ? result : num_ev; -} - - -/* multicast - not supported yet */ -static int multicast_event(struct snd_seq_client *client, struct snd_seq_event *event, - int atomic, int hop) -{ - pr_debug("ALSA: seq: multicast not supported yet.\n"); - return 0; /* ignored */ -} -#endif /* SUPPORT_BROADCAST */ - - /* deliver an event to the destination port(s). * if the event is to subscribers or broadcast, the event is dispatched * to multiple targets. @@ -826,15 +739,6 @@ static int snd_seq_deliver_event(struct snd_seq_client *client, struct snd_seq_e if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS || event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) result = deliver_to_subscribers(client, event, atomic, hop); -#ifdef SUPPORT_BROADCAST - else if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST || - event->dest.client == SNDRV_SEQ_ADDRESS_BROADCAST) - result = broadcast_event(client, event, atomic, hop); - else if (event->dest.client >= SNDRV_SEQ_MAX_CLIENTS) - result = multicast_event(client, event, atomic, hop); - else if (event->dest.port == SNDRV_SEQ_ADDRESS_BROADCAST) - result = port_broadcast_event(client, event, atomic, hop); -#endif else result = snd_seq_deliver_single_event(client, event, 0, atomic, hop); @@ -936,14 +840,7 @@ static int snd_seq_client_enqueue_event(struct snd_seq_client *client, if (event->queue == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { event->dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; event->queue = SNDRV_SEQ_QUEUE_DIRECT; - } else -#ifdef SUPPORT_BROADCAST - if (event->queue == SNDRV_SEQ_ADDRESS_BROADCAST) { - event->dest.client = SNDRV_SEQ_ADDRESS_BROADCAST; - event->queue = SNDRV_SEQ_QUEUE_DIRECT; - } -#endif - if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { + } else if (event->dest.client == SNDRV_SEQ_ADDRESS_SUBSCRIBERS) { /* check presence of source port */ struct snd_seq_client_port *src_port = snd_seq_port_use_ptr(client, event->source.port); if (src_port == NULL) -- cgit v1.2.3 From 7c3f0d3d3a1147f7eee79e090d0b047ab5fd3068 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:42 +0200 Subject: ALSA: seq: Check the conflicting port at port creation We didn't check if a port with the given port number has been already present at creating a new port. Check it and return -EBUSY properly if the port number conflicts. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-22-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 12 ++++++++---- sound/core/seq/seq_ports.c | 25 ++++++++++++++++--------- sound/core/seq/seq_ports.h | 5 +++-- 3 files changed, 27 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 019af1343325..06743114cabf 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1194,15 +1194,19 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) struct snd_seq_port_info *info = arg; struct snd_seq_client_port *port; struct snd_seq_port_callback *callback; - int port_idx; + int port_idx, err; /* it is not allowed to create the port for an another client */ if (info->addr.client != client->number) return -EPERM; - port = snd_seq_create_port(client, (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) ? info->addr.port : -1); - if (port == NULL) - return -ENOMEM; + if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) + port_idx = info->addr.port; + else + port_idx = -1; + err = snd_seq_create_port(client, port_idx, &port); + if (err < 0) + return err; if (client->type == USER_CLIENT && info->kernel) { port_idx = port->addr.port; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 25fcf5a2c71c..500b1a5a9679 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -107,33 +107,34 @@ static void port_subs_info_init(struct snd_seq_port_subs_info *grp) } -/* create a port, port number is returned (-1 on failure); +/* create a port, port number or a negative error code is returned * the caller needs to unref the port via snd_seq_port_unlock() appropriately */ -struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, - int port) +int snd_seq_create_port(struct snd_seq_client *client, int port, + struct snd_seq_client_port **port_ret) { struct snd_seq_client_port *new_port, *p; - int num = -1; + int num; + *port_ret = NULL; + /* sanity check */ if (snd_BUG_ON(!client)) - return NULL; + return -EINVAL; if (client->num_ports >= SNDRV_SEQ_MAX_PORTS) { pr_warn("ALSA: seq: too many ports for client %d\n", client->number); - return NULL; + return -EINVAL; } /* create a new port */ new_port = kzalloc(sizeof(*new_port), GFP_KERNEL); if (!new_port) - return NULL; /* failure, out of memory */ + return -ENOMEM; /* failure, out of memory */ /* init port data */ new_port->addr.client = client->number; new_port->addr.port = -1; new_port->owner = THIS_MODULE; - sprintf(new_port->name, "port-%d", num); snd_use_lock_init(&new_port->use_lock); port_subs_info_init(&new_port->c_src); port_subs_info_init(&new_port->c_dest); @@ -143,6 +144,10 @@ struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, mutex_lock(&client->ports_mutex); write_lock_irq(&client->ports_lock); list_for_each_entry(p, &client->ports_list_head, list) { + if (p->addr.port == port) { + num = -EBUSY; + goto unlock; + } if (p->addr.port > num) break; if (port < 0) /* auto-probe mode */ @@ -153,10 +158,12 @@ struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, client->num_ports++; new_port->addr.port = num; /* store the port number in the port */ sprintf(new_port->name, "port-%d", num); + *port_ret = new_port; + unlock: write_unlock_irq(&client->ports_lock); mutex_unlock(&client->ports_mutex); - return new_port; + return num; } /* */ diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index b1f2c4943174..44f0e9e96bbf 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -86,8 +86,9 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl /* unlock the port */ #define snd_seq_port_unlock(port) snd_use_lock_free(&(port)->use_lock) -/* create a port, port number is returned (-1 on failure) */ -struct snd_seq_client_port *snd_seq_create_port(struct snd_seq_client *client, int port_index); +/* create a port, port number or a negative error code is returned */ +int snd_seq_create_port(struct snd_seq_client *client, int port_index, + struct snd_seq_client_port **port_ret); /* delete a port */ int snd_seq_delete_port(struct snd_seq_client *client, int port); -- cgit v1.2.3 From 4f92eb792e9336a46480655ed18e8b59e2a6505c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:43 +0200 Subject: ALSA: seq: Check validity before creating a port object The client type and the port info validity check should be done before actually creating a port, instead of unnecessary create-and-scratch. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-23-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 06743114cabf..2dac8c3355fd 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1199,6 +1199,8 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) /* it is not allowed to create the port for an another client */ if (info->addr.client != client->number) return -EPERM; + if (client->type == USER_CLIENT && info->kernel) + return -EINVAL; if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) port_idx = info->addr.port; @@ -1208,12 +1210,6 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) if (err < 0) return err; - if (client->type == USER_CLIENT && info->kernel) { - port_idx = port->addr.port; - snd_seq_port_unlock(port); - snd_seq_delete_port(client, port_idx); - return -EINVAL; - } if (client->type == KERNEL_CLIENT) { callback = info->kernel; if (callback) { -- cgit v1.2.3 From 1359905383834ed5fc294fad3954d40dbcf770af Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:44 +0200 Subject: ALSA: seq: Prohibit creating ports with special numbers Some port numbers are special, such as 254 for subscribers and 255 for broadcast. Return error if application tries to create such a port. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-24-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 2dac8c3355fd..0f26f20596d7 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1206,6 +1206,8 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) port_idx = info->addr.port; else port_idx = -1; + if (port_idx >= SNDRV_SEQ_ADDRESS_UNKNOWN) + return -EINVAL; err = snd_seq_create_port(client, port_idx, &port); if (err < 0) return err; -- cgit v1.2.3 From afb72505e4614a2ccefe3440d37dec3a2273c330 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:45 +0200 Subject: ALSA: seq: Introduce SNDRV_SEQ_IOCTL_USER_PVERSION ioctl For the future extension of ALSA sequencer ABI, introduce a new ioctl SNDRV_SEQ_IOCTL_USER_PVERSION. This is similar like the ioctls used in PCM and other interfaces, for an application to specify its supporting ABI version. The use of this ioctl will be mandatory for the upcoming UMP support. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-25-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 8 ++++++++ sound/core/seq/seq_clientmgr.h | 1 + sound/core/seq/seq_compat.c | 1 + 4 files changed, 11 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 00d2703e8fca..4a3c5a718bae 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -561,6 +561,7 @@ struct snd_seq_query_subs { #define SNDRV_SEQ_IOCTL_CLIENT_ID _IOR ('S', 0x01, int) #define SNDRV_SEQ_IOCTL_SYSTEM_INFO _IOWR('S', 0x02, struct snd_seq_system_info) #define SNDRV_SEQ_IOCTL_RUNNING_MODE _IOWR('S', 0x03, struct snd_seq_running_info) +#define SNDRV_SEQ_IOCTL_USER_PVERSION _IOW('S', 0x04, int) #define SNDRV_SEQ_IOCTL_GET_CLIENT_INFO _IOWR('S', 0x10, struct snd_seq_client_info) #define SNDRV_SEQ_IOCTL_SET_CLIENT_INFO _IOW ('S', 0x11, struct snd_seq_client_info) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 0f26f20596d7..89a8d14df83b 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1056,6 +1056,12 @@ static int snd_seq_ioctl_pversion(struct snd_seq_client *client, void *arg) return 0; } +static int snd_seq_ioctl_user_pversion(struct snd_seq_client *client, void *arg) +{ + client->user_pversion = *(unsigned int *)arg; + return 0; +} + static int snd_seq_ioctl_client_id(struct snd_seq_client *client, void *arg) { int *client_id = arg; @@ -1985,6 +1991,7 @@ static const struct ioctl_handler { int (*func)(struct snd_seq_client *client, void *arg); } ioctl_handlers[] = { { SNDRV_SEQ_IOCTL_PVERSION, snd_seq_ioctl_pversion }, + { SNDRV_SEQ_IOCTL_USER_PVERSION, snd_seq_ioctl_user_pversion }, { SNDRV_SEQ_IOCTL_CLIENT_ID, snd_seq_ioctl_client_id }, { SNDRV_SEQ_IOCTL_SYSTEM_INFO, snd_seq_ioctl_system_info }, { SNDRV_SEQ_IOCTL_RUNNING_MODE, snd_seq_ioctl_running_mode }, @@ -2125,6 +2132,7 @@ int snd_seq_create_kernel_client(struct snd_card *card, int client_index, client->accept_input = 1; client->accept_output = 1; client->data.kernel.card = card; + client->user_pversion = SNDRV_SEQ_VERSION; va_start(args, name_fmt); vsnprintf(client->name, sizeof(client->name), name_fmt, args); diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index f05704e45ab4..abe0ceadf3da 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -35,6 +35,7 @@ struct snd_seq_client { snd_seq_client_type_t type; unsigned int accept_input: 1, accept_output: 1; + unsigned int user_pversion; char name[64]; /* client name */ int number; /* client number */ unsigned int filter; /* filter flags */ diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index 54723566ce24..c0ce6236dc7f 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -81,6 +81,7 @@ static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned l switch (cmd) { case SNDRV_SEQ_IOCTL_PVERSION: + case SNDRV_SEQ_IOCTL_USER_PVERSION: case SNDRV_SEQ_IOCTL_CLIENT_ID: case SNDRV_SEQ_IOCTL_SYSTEM_INFO: case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: -- cgit v1.2.3 From 46397622a3fa8372b8fda0f04b33d16923b03b1b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:46 +0200 Subject: ALSA: seq: Add UMP support Starting from this commit, we add the basic support of UMP (Universal MIDI Packet) events on ALSA sequencer infrastructure. The biggest change here is that, for transferring UMP packets that are up to 128 bits, we extend the data payload of ALSA sequencer event record when the client is declared to support for the new UMP events. A new event flag bit, SNDRV_SEQ_EVENT_UMP, is defined and it shall be set for the UMP packet events that have the larger payload of 128 bits, defined as struct snd_seq_ump_event. For controlling the UMP feature enablement in kernel, a new Kconfig, CONFIG_SND_SEQ_UMP is introduced. The extended event for UMP is available only when this Kconfig item is set. Similarly, the size of the internal snd_seq_event_cell also increases (in 4 bytes) when the Kconfig item is set. (But the size increase is effective only for 32bit architectures; 64bit archs already have padding there.) Overall, when CONFIG_SND_SEQ_UMP isn't set, there is no change in the event and cell, keeping the old sizes. For applications that want to access the UMP packets, first of all, a sequencer client has to declare the user-protocol to match with the latest one via the new SNDRV_SEQ_IOCTL_USER_PVERSION; otherwise it's treated as if a legacy client without UMP support. Then the client can switch to the new UMP mode (MIDI 1.0 or MIDI 2.0) with a new field, midi_version, in snd_seq_client_info. When switched to UMP mode (midi_version = 1 or 2), the client can write the UMP events with SNDRV_SEQ_EVENT_UMP flag. For reads, the alignment size is changed from snd_seq_event (28 bytes) to snd_seq_ump_event (32 bytes). When a UMP sequencer event is delivered to a legacy sequencer client, it's ignored or handled as an error. Conceptually, ALSA sequencer client and port correspond to the UMP Endpoint and Group, respectively; each client may have multiple ports and each port has the fixed number (16) of channels, total up to 256 channels. As of this commit, ALSA sequencer core just sends and receives the UMP events as-is from/to clients. The automatic conversions between the legacy events and the new UMP events will be implemented in a later patch. Along with this commit, bump the sequencer protocol version to 1.0.3. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-26-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/asequencer.h | 4 ++ include/sound/seq_kernel.h | 8 +++ include/uapi/sound/asequencer.h | 53 +++++++++----- sound/core/seq/Kconfig | 7 ++ sound/core/seq/seq_clientmgr.c | 154 ++++++++++++++++++++++++++++------------ sound/core/seq/seq_clientmgr.h | 1 + sound/core/seq/seq_memory.c | 10 ++- sound/core/seq/seq_memory.h | 19 ++++- 8 files changed, 193 insertions(+), 63 deletions(-) (limited to 'sound') diff --git a/include/sound/asequencer.h b/include/sound/asequencer.h index 18d4bc3ee0b7..ddbb6bf801bb 100644 --- a/include/sound/asequencer.h +++ b/include/sound/asequencer.h @@ -65,6 +65,10 @@ #define snd_seq_ev_is_abstime(ev) (snd_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_ABS) #define snd_seq_ev_is_reltime(ev) (snd_seq_ev_timemode_type(ev) == SNDRV_SEQ_TIME_MODE_REL) +/* check whether the given event is a UMP event */ +#define snd_seq_ev_is_ump(ev) \ + (IS_ENABLED(CONFIG_SND_SEQ_UMP) && ((ev)->flags & SNDRV_SEQ_EVENT_UMP)) + /* queue sync port */ #define snd_seq_queue_sync_port(q) ((q) + 16) diff --git a/include/sound/seq_kernel.h b/include/sound/seq_kernel.h index 527e7f8ad661..c8621671fa70 100644 --- a/include/sound/seq_kernel.h +++ b/include/sound/seq_kernel.h @@ -75,6 +75,14 @@ int snd_seq_expand_var_event_at(const struct snd_seq_event *event, int count, int snd_seq_dump_var_event(const struct snd_seq_event *event, snd_seq_dump_func_t func, void *private_data); +/* size of the event packet; it can be greater than snd_seq_event size */ +static inline size_t snd_seq_event_packet_size(struct snd_seq_event *ev) +{ + if (snd_seq_ev_is_ump(ev)) + return sizeof(struct snd_seq_ump_event); + return sizeof(struct snd_seq_event); +} + /* interface for OSS emulation */ int snd_seq_set_queue_tempo(int client, struct snd_seq_queue_tempo *tempo); diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 4a3c5a718bae..b87950cbfb79 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -10,7 +10,7 @@ #include /** version of the sequencer */ -#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 2) +#define SNDRV_SEQ_VERSION SNDRV_PROTOCOL_VERSION(1, 0, 3) /** * definition of sequencer event types @@ -174,6 +174,7 @@ struct snd_seq_connect { #define SNDRV_SEQ_PRIORITY_HIGH (1<<4) /* event should be processed before others */ #define SNDRV_SEQ_PRIORITY_MASK (1<<4) +#define SNDRV_SEQ_EVENT_UMP (1<<5) /* event holds a UMP packet */ /* note event */ struct snd_seq_ev_note { @@ -252,6 +253,19 @@ struct snd_seq_ev_quote { struct snd_seq_event *event; /* quoted event */ } __attribute__((packed)); +union snd_seq_event_data { /* event data... */ + struct snd_seq_ev_note note; + struct snd_seq_ev_ctrl control; + struct snd_seq_ev_raw8 raw8; + struct snd_seq_ev_raw32 raw32; + struct snd_seq_ev_ext ext; + struct snd_seq_ev_queue_control queue; + union snd_seq_timestamp time; + struct snd_seq_addr addr; + struct snd_seq_connect connect; + struct snd_seq_result result; + struct snd_seq_ev_quote quote; +}; /* sequencer event */ struct snd_seq_event { @@ -262,25 +276,27 @@ struct snd_seq_event { unsigned char queue; /* schedule queue */ union snd_seq_timestamp time; /* schedule time */ - struct snd_seq_addr source; /* source address */ struct snd_seq_addr dest; /* destination address */ - union { /* event data... */ - struct snd_seq_ev_note note; - struct snd_seq_ev_ctrl control; - struct snd_seq_ev_raw8 raw8; - struct snd_seq_ev_raw32 raw32; - struct snd_seq_ev_ext ext; - struct snd_seq_ev_queue_control queue; - union snd_seq_timestamp time; - struct snd_seq_addr addr; - struct snd_seq_connect connect; - struct snd_seq_result result; - struct snd_seq_ev_quote quote; - } data; + union snd_seq_event_data data; }; + /* (compatible) event for UMP-capable clients */ +struct snd_seq_ump_event { + snd_seq_event_type_t type; /* event type */ + unsigned char flags; /* event flags */ + char tag; + unsigned char queue; /* schedule queue */ + union snd_seq_timestamp time; /* schedule time */ + struct snd_seq_addr source; /* source address */ + struct snd_seq_addr dest; /* destination address */ + + union { + union snd_seq_event_data data; + unsigned int ump[4]; + }; +}; /* * bounce event - stored as variable size data @@ -344,9 +360,14 @@ struct snd_seq_client_info { int event_lost; /* number of lost events */ int card; /* RO: card number[kernel] */ int pid; /* RO: pid[user] */ - char reserved[56]; /* for future use */ + unsigned int midi_version; /* MIDI version */ + char reserved[52]; /* for future use */ }; +/* MIDI version numbers in client info */ +#define SNDRV_SEQ_CLIENT_LEGACY_MIDI 0 /* Legacy client */ +#define SNDRV_SEQ_CLIENT_UMP_MIDI_1_0 1 /* UMP MIDI 1.0 */ +#define SNDRV_SEQ_CLIENT_UMP_MIDI_2_0 2 /* UMP MIDI 2.0 */ /* client pool size */ struct snd_seq_client_pool { diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index f84718a44980..c69d8beb09fa 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -60,4 +60,11 @@ config SND_SEQ_MIDI_EMUL config SND_SEQ_VIRMIDI tristate +config SND_SEQ_UMP + bool "Support for UMP events" + help + Say Y here to enable the support for handling UMP (Universal MIDI + Packet) events via ALSA sequencer infrastructure, which is an + essential feature for enabling MIDI 2.0 support. + endif # SND_SEQUENCER diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 89a8d14df83b..801d5eee21eb 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -387,6 +387,15 @@ static int snd_seq_release(struct inode *inode, struct file *file) return 0; } +static bool event_is_compatible(const struct snd_seq_client *client, + const struct snd_seq_event *ev) +{ + if (snd_seq_ev_is_ump(ev) && !client->midi_version) + return false; + if (snd_seq_ev_is_ump(ev) && snd_seq_ev_is_variable(ev)) + return false; + return true; +} /* handle client read() */ /* possible error values: @@ -400,6 +409,7 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, { struct snd_seq_client *client = file->private_data; struct snd_seq_fifo *fifo; + size_t aligned_size; int err; long result = 0; struct snd_seq_event_cell *cell; @@ -431,43 +441,54 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, err = 0; snd_seq_fifo_lock(fifo); + if (client->midi_version > 0) + aligned_size = sizeof(struct snd_seq_ump_event); + else + aligned_size = sizeof(struct snd_seq_event); + /* while data available in queue */ - while (count >= sizeof(struct snd_seq_event)) { + while (count >= aligned_size) { int nonblock; nonblock = (file->f_flags & O_NONBLOCK) || result > 0; err = snd_seq_fifo_cell_out(fifo, &cell, nonblock); if (err < 0) break; + if (!event_is_compatible(client, &cell->event)) { + snd_seq_cell_free(cell); + cell = NULL; + continue; + } if (snd_seq_ev_is_variable(&cell->event)) { - struct snd_seq_event tmpev; - tmpev = cell->event; + struct snd_seq_ump_event tmpev; + + memcpy(&tmpev, &cell->event, aligned_size); tmpev.data.ext.len &= ~SNDRV_SEQ_EXT_MASK; - if (copy_to_user(buf, &tmpev, sizeof(struct snd_seq_event))) { + if (copy_to_user(buf, &tmpev, aligned_size)) { err = -EFAULT; break; } - count -= sizeof(struct snd_seq_event); - buf += sizeof(struct snd_seq_event); + count -= aligned_size; + buf += aligned_size; err = snd_seq_expand_var_event(&cell->event, count, (char __force *)buf, 0, - sizeof(struct snd_seq_event)); + aligned_size); if (err < 0) break; result += err; count -= err; buf += err; } else { - if (copy_to_user(buf, &cell->event, sizeof(struct snd_seq_event))) { + if (copy_to_user(buf, &cell->event, aligned_size)) { err = -EFAULT; break; } - count -= sizeof(struct snd_seq_event); - buf += sizeof(struct snd_seq_event); + count -= aligned_size; + buf += aligned_size; } snd_seq_cell_free(cell); cell = NULL; /* to be sure */ - result += sizeof(struct snd_seq_event); + result += aligned_size; } if (err < 0) { @@ -665,15 +686,17 @@ static int deliver_to_subscribers(struct snd_seq_client *client, { struct snd_seq_subscribers *subs; int err, result = 0, num_ev = 0; - struct snd_seq_event event_saved; struct snd_seq_client_port *src_port; + union __snd_seq_event event_saved; + size_t saved_size; struct snd_seq_port_subs_info *grp; src_port = snd_seq_port_use_ptr(client, event->source.port); if (src_port == NULL) return -EINVAL; /* invalid source port */ /* save original event record */ - event_saved = *event; + saved_size = snd_seq_event_packet_size(event); + memcpy(&event_saved, event, saved_size); grp = &src_port->c_src; /* lock list */ @@ -700,14 +723,13 @@ static int deliver_to_subscribers(struct snd_seq_client *client, } num_ev++; /* restore original event record */ - *event = event_saved; + memcpy(event, &event_saved, saved_size); } if (atomic) read_unlock(&grp->list_lock); else up_read(&grp->list_mutex); - *event = event_saved; /* restore */ - snd_seq_port_unlock(src_port); + memcpy(event, &event_saved, saved_size); return (result < 0) ? result : num_ev; } @@ -769,7 +791,8 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop) return -EINVAL; } - if (cell->event.type == SNDRV_SEQ_EVENT_NOTE) { + if (!snd_seq_ev_is_ump(&cell->event) && + cell->event.type == SNDRV_SEQ_EVENT_NOTE) { /* NOTE event: * the event cell is re-used as a NOTE-OFF event and * enqueued again. @@ -793,7 +816,7 @@ int snd_seq_dispatch_event(struct snd_seq_event_cell *cell, int atomic, int hop) /* add the duration time */ switch (ev->flags & SNDRV_SEQ_TIME_STAMP_MASK) { case SNDRV_SEQ_TIME_STAMP_TICK: - ev->time.tick += ev->data.note.duration; + cell->event.time.tick += ev->data.note.duration; break; case SNDRV_SEQ_TIME_STAMP_REAL: /* unit for duration is ms */ @@ -850,7 +873,8 @@ static int snd_seq_client_enqueue_event(struct snd_seq_client *client, /* direct event processing without enqueued */ if (snd_seq_ev_is_direct(event)) { - if (event->type == SNDRV_SEQ_EVENT_NOTE) + if (!snd_seq_ev_is_ump(event) && + event->type == SNDRV_SEQ_EVENT_NOTE) return -EINVAL; /* this event must be enqueued! */ return snd_seq_deliver_event(client, event, atomic, hop); } @@ -920,7 +944,8 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf, struct snd_seq_client *client = file->private_data; int written = 0, len; int err, handled; - struct snd_seq_event event; + union __snd_seq_event __event; + struct snd_seq_event *ev = &__event.legacy; if (!(snd_seq_file_flags(file) & SNDRV_SEQ_LFLG_OUTPUT)) return -ENXIO; @@ -946,49 +971,66 @@ static ssize_t snd_seq_write(struct file *file, const char __user *buf, err = -EINVAL; while (count >= sizeof(struct snd_seq_event)) { /* Read in the event header from the user */ - len = sizeof(event); - if (copy_from_user(&event, buf, len)) { + len = sizeof(struct snd_seq_event); + if (copy_from_user(ev, buf, len)) { err = -EFAULT; break; } - event.source.client = client->number; /* fill in client number */ + /* read in the rest bytes for UMP events */ + if (snd_seq_ev_is_ump(ev)) { + if (count < sizeof(struct snd_seq_ump_event)) + break; + if (copy_from_user((char *)ev + len, buf + len, + sizeof(struct snd_seq_ump_event) - len)) { + err = -EFAULT; + break; + } + len = sizeof(struct snd_seq_ump_event); + } + + ev->source.client = client->number; /* fill in client number */ /* Check for extension data length */ - if (check_event_type_and_length(&event)) { + if (check_event_type_and_length(ev)) { err = -EINVAL; break; } - /* check for special events */ - if (event.type == SNDRV_SEQ_EVENT_NONE) - goto __skip_event; - else if (snd_seq_ev_is_reserved(&event)) { + if (!event_is_compatible(client, ev)) { err = -EINVAL; break; } - if (snd_seq_ev_is_variable(&event)) { - int extlen = event.data.ext.len & ~SNDRV_SEQ_EXT_MASK; + /* check for special events */ + if (!snd_seq_ev_is_ump(ev)) { + if (ev->type == SNDRV_SEQ_EVENT_NONE) + goto __skip_event; + else if (snd_seq_ev_is_reserved(ev)) { + err = -EINVAL; + break; + } + } + + if (snd_seq_ev_is_variable(ev)) { + int extlen = ev->data.ext.len & ~SNDRV_SEQ_EXT_MASK; if ((size_t)(extlen + len) > count) { /* back out, will get an error this time or next */ err = -EINVAL; break; } /* set user space pointer */ - event.data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; - event.data.ext.ptr = (char __force *)buf - + sizeof(struct snd_seq_event); + ev->data.ext.len = extlen | SNDRV_SEQ_EXT_USRPTR; + ev->data.ext.ptr = (char __force *)buf + len; len += extlen; /* increment data length */ } else { #ifdef CONFIG_COMPAT - if (client->convert32 && snd_seq_ev_is_varusr(&event)) { - void *ptr = (void __force *)compat_ptr(event.data.raw32.d[1]); - event.data.ext.ptr = ptr; - } + if (client->convert32 && snd_seq_ev_is_varusr(ev)) + ev->data.ext.ptr = + (void __force *)compat_ptr(ev->data.raw32.d[1]); #endif } /* ok, enqueue it */ - err = snd_seq_client_enqueue_event(client, &event, file, + err = snd_seq_client_enqueue_event(client, ev, file, !(file->f_flags & O_NONBLOCK), 0, 0, &client->ioctl_mutex); if (err < 0) @@ -1146,6 +1188,7 @@ static void get_client_info(struct snd_seq_client *cptr, else info->card = -1; + info->midi_version = cptr->midi_version; memset(info->reserved, 0, sizeof(info->reserved)); } @@ -1180,12 +1223,19 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, if (client->type != client_info->type) return -EINVAL; + /* check validity of midi_version field */ + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3) && + client_info->midi_version > SNDRV_SEQ_CLIENT_UMP_MIDI_2_0) + return -EINVAL; + /* fill the info fields */ if (client_info->name[0]) strscpy(client->name, client_info->name, sizeof(client->name)); client->filter = client_info->filter; client->event_lost = client_info->event_lost; + if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) + client->midi_version = client_info->midi_version; memcpy(client->event_filter, client_info->event_filter, 32); return 0; @@ -2181,10 +2231,12 @@ int snd_seq_kernel_client_enqueue(int client, struct snd_seq_event *ev, if (snd_BUG_ON(!ev)) return -EINVAL; - if (ev->type == SNDRV_SEQ_EVENT_NONE) - return 0; /* ignore this */ - if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) - return -EINVAL; /* quoted events can't be enqueued */ + if (!snd_seq_ev_is_ump(ev)) { + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* ignore this */ + if (ev->type == SNDRV_SEQ_EVENT_KERNEL_ERROR) + return -EINVAL; /* quoted events can't be enqueued */ + } /* fill in client number */ ev->source.client = client; @@ -2376,6 +2428,19 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, mutex_unlock(&client->ports_mutex); } +static const char *midi_version_string(unsigned int version) +{ + switch (version) { + case SNDRV_SEQ_CLIENT_LEGACY_MIDI: + return "Legacy"; + case SNDRV_SEQ_CLIENT_UMP_MIDI_1_0: + return "UMP MIDI1"; + case SNDRV_SEQ_CLIENT_UMP_MIDI_2_0: + return "UMP MIDI2"; + default: + return "Unknown"; + } +} /* exported to seq_info.c */ void snd_seq_info_clients_read(struct snd_info_entry *entry, @@ -2400,9 +2465,10 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, continue; } - snd_iprintf(buffer, "Client %3d : \"%s\" [%s]\n", + snd_iprintf(buffer, "Client %3d : \"%s\" [%s %s]\n", c, client->name, - client->type == USER_CLIENT ? "User" : "Kernel"); + client->type == USER_CLIENT ? "User" : "Kernel", + midi_version_string(client->midi_version)); snd_seq_info_dump_ports(buffer, client); if (snd_seq_write_pool_allocated(client)) { snd_iprintf(buffer, " Output pool :\n"); diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index abe0ceadf3da..5657f8091835 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -35,6 +35,7 @@ struct snd_seq_client { snd_seq_client_type_t type; unsigned int accept_input: 1, accept_output: 1; + unsigned int midi_version; unsigned int user_pversion; char name[64]; /* client name */ int number; /* client number */ diff --git a/sound/core/seq/seq_memory.c b/sound/core/seq/seq_memory.c index a8d2db439f86..174585bf59d2 100644 --- a/sound/core/seq/seq_memory.c +++ b/sound/core/seq/seq_memory.c @@ -340,6 +340,7 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, int ncells, err; unsigned int extlen; struct snd_seq_event_cell *cell; + int size; *cellp = NULL; @@ -357,7 +358,12 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, return err; /* copy the event */ - cell->event = *event; + size = snd_seq_event_packet_size(event); + memcpy(&cell->ump, event, size); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + if (size < sizeof(cell->event)) + cell->ump.raw.extra = 0; +#endif /* decompose */ if (snd_seq_ev_is_variable(event)) { @@ -375,7 +381,7 @@ int snd_seq_event_dup(struct snd_seq_pool *pool, struct snd_seq_event *event, tail = NULL; while (ncells-- > 0) { - int size = sizeof(struct snd_seq_event); + size = sizeof(struct snd_seq_event); if (len < size) size = len; err = snd_seq_cell_alloc(pool, &tmp, nonblock, file, diff --git a/sound/core/seq/seq_memory.h b/sound/core/seq/seq_memory.h index 7d7ff80f915e..7f7a2c0b187d 100644 --- a/sound/core/seq/seq_memory.h +++ b/sound/core/seq/seq_memory.h @@ -11,9 +11,26 @@ struct snd_info_buffer; +/* aliasing for legacy and UMP event packet handling */ +union __snd_seq_event { + struct snd_seq_event legacy; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + struct snd_seq_ump_event ump; +#endif + struct { + struct snd_seq_event event; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + u32 extra; +#endif + } __packed raw; +}; + /* container for sequencer event (internal use) */ struct snd_seq_event_cell { - struct snd_seq_event event; + union { + struct snd_seq_event event; + union __snd_seq_event ump; + }; struct snd_seq_pool *pool; /* used pool */ struct snd_seq_event_cell *next; /* next cell */ }; -- cgit v1.2.3 From 74661932ac5ecb12e4378f41083be6ac17804e71 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:47 +0200 Subject: ALSA: seq: Add port inactive flag This extends the ALSA sequencer port capability bit to indicate the "inactive" flag. When this flag is set, the port is essentially invisible, and doesn't appear in the port query ioctls, while the direct access and the connection to this port are still allowed. The active/inactive state can be flipped dynamically, so that it can be visible at any time later. This feature is introduced basically for UMP; some UMP Groups in a UMP Block may be unassigned, hence those are practically invisible. On ALSA sequencer, the corresponding sequencer ports will get this new "inactive" flag to indicate the invisible state. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-27-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 2 ++ sound/core/seq/seq_ports.c | 4 ++++ 3 files changed, 7 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index b87950cbfb79..c6ca6609790b 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -427,6 +427,7 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_CAP_SUBS_READ (1<<5) /* allow read subscription */ #define SNDRV_SEQ_PORT_CAP_SUBS_WRITE (1<<6) /* allow write subscription */ #define SNDRV_SEQ_PORT_CAP_NO_EXPORT (1<<7) /* routing not allowed */ +#define SNDRV_SEQ_PORT_CAP_INACTIVE (1<<8) /* inactive port */ /* port type */ #define SNDRV_SEQ_PORT_TYPE_SPECIFIC (1<<0) /* hardware specific */ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 801d5eee21eb..6508ce63f761 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2416,6 +2416,8 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, mutex_lock(&client->ports_mutex); list_for_each_entry(p, &client->ports_list_head, list) { + if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) + continue; snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n", p->addr.port, p->name, FLAG_PERM_RD(p->capability), diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 500b1a5a9679..42f4172d4766 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -69,11 +69,15 @@ struct snd_seq_client_port *snd_seq_port_query_nearest(struct snd_seq_client *cl { int num; struct snd_seq_client_port *port, *found; + bool check_inactive = (pinfo->capability & SNDRV_SEQ_PORT_CAP_INACTIVE); num = pinfo->addr.port; found = NULL; read_lock(&client->ports_lock); list_for_each_entry(port, &client->ports_list_head, list) { + if ((port->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) && + !check_inactive) + continue; /* skip inactive ports */ if (port->addr.port < num) continue; if (port->addr.port == num) { -- cgit v1.2.3 From 177ccf811df4a893df339a72dc732bb26b66d055 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:48 +0200 Subject: ALSA: seq: Support MIDI 2.0 UMP Endpoint port This is an extension to ALSA sequencer infrastructure to support the MIDI 2.0 UMP Endpoint port. It's a "catch-all" port that is supposed to be present for each UMP Endpoint. When this port is read via subscription, it sends any events from all ports (UMP Groups) found in the same client. A UMP Endpoint port can be created with the new capability bit SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT. Although the port assignment isn't strictly defined, it should be the port number 0. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-28-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 47 ++++++++++++++++++++++++++++++++++------- sound/core/seq/seq_clientmgr.h | 1 + 3 files changed, 41 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index c6ca6609790b..67532c46b115 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -428,6 +428,7 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_CAP_SUBS_WRITE (1<<6) /* allow write subscription */ #define SNDRV_SEQ_PORT_CAP_NO_EXPORT (1<<7) /* routing not allowed */ #define SNDRV_SEQ_PORT_CAP_INACTIVE (1<<8) /* inactive port */ +#define SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT (1<<9) /* MIDI 2.0 UMP Endpoint port */ /* port type */ #define SNDRV_SEQ_PORT_TYPE_SPECIFIC (1<<0) /* hardware specific */ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 6508ce63f761..061b3e2bece1 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -239,6 +239,7 @@ static struct snd_seq_client *seq_create_client1(int client_index, int poolsize) mutex_init(&client->ports_mutex); INIT_LIST_HEAD(&client->ports_list_head); mutex_init(&client->ioctl_mutex); + client->ump_endpoint_port = -1; /* find free slot in the client table */ spin_lock_irq(&clients_lock); @@ -680,20 +681,17 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, /* * send the event to all subscribers: */ -static int deliver_to_subscribers(struct snd_seq_client *client, - struct snd_seq_event *event, - int atomic, int hop) +static int __deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + struct snd_seq_client_port *src_port, + int atomic, int hop) { struct snd_seq_subscribers *subs; int err, result = 0, num_ev = 0; - struct snd_seq_client_port *src_port; union __snd_seq_event event_saved; size_t saved_size; struct snd_seq_port_subs_info *grp; - src_port = snd_seq_port_use_ptr(client, event->source.port); - if (src_port == NULL) - return -EINVAL; /* invalid source port */ /* save original event record */ saved_size = snd_seq_event_packet_size(event); memcpy(&event_saved, event, saved_size); @@ -733,6 +731,31 @@ static int deliver_to_subscribers(struct snd_seq_client *client, return (result < 0) ? result : num_ev; } +static int deliver_to_subscribers(struct snd_seq_client *client, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_client_port *src_port; + int ret = 0, ret2; + + src_port = snd_seq_port_use_ptr(client, event->source.port); + if (src_port) { + ret = __deliver_to_subscribers(client, event, src_port, atomic, hop); + snd_seq_port_unlock(src_port); + } + + if (client->ump_endpoint_port < 0 || + event->source.port == client->ump_endpoint_port) + return ret; + + src_port = snd_seq_port_use_ptr(client, client->ump_endpoint_port); + if (!src_port) + return ret; + ret2 = __deliver_to_subscribers(client, event, src_port, atomic, hop); + snd_seq_port_unlock(src_port); + return ret2 < 0 ? ret2 : ret; +} + /* deliver an event to the destination port(s). * if the event is to subscribers or broadcast, the event is dispatched * to multiple targets. @@ -1257,6 +1280,9 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) return -EPERM; if (client->type == USER_CLIENT && info->kernel) return -EINVAL; + if ((info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) && + client->ump_endpoint_port >= 0) + return -EBUSY; if (info->flags & SNDRV_SEQ_PORT_FLG_GIVEN_PORT) port_idx = info->addr.port; @@ -1286,6 +1312,8 @@ static int snd_seq_ioctl_create_port(struct snd_seq_client *client, void *arg) info->addr = port->addr; snd_seq_set_port_info(port, info); + if (info->capability & SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT) + client->ump_endpoint_port = port->addr.port; snd_seq_system_client_ev_port_start(port->addr.client, port->addr.port); snd_seq_port_unlock(port); @@ -1305,8 +1333,11 @@ static int snd_seq_ioctl_delete_port(struct snd_seq_client *client, void *arg) return -EPERM; err = snd_seq_delete_port(client, info->addr.port); - if (err >= 0) + if (err >= 0) { + if (client->ump_endpoint_port == info->addr.port) + client->ump_endpoint_port = -1; snd_seq_system_client_ev_port_exit(client->number, info->addr.port); + } return err; } diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index 5657f8091835..bb973d36ce78 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -50,6 +50,7 @@ struct snd_seq_client { struct mutex ports_mutex; struct mutex ioctl_mutex; int convert32; /* convert 32->64bit */ + int ump_endpoint_port; /* output pool */ struct snd_seq_pool *pool; /* memory pool for this client */ -- cgit v1.2.3 From ff166a9d19fab3d77f50e9413df046fb1d7c01cc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:49 +0200 Subject: ALSA: seq: Add port direction to snd_seq_port_info Add a new field "direction" to snd_seq_port_info for allowing a client to tell the expected direction of the port access. A port might still allow subscriptions for read/write (e.g. for MIDI-CI) even if the primary usage of the port is a single direction (either input or output only). This new "direction" field can help to indicate such cases. When the direction is unspecified at creating a port and the port has either read or write capability, the corresponding direction bits are set automatically as default. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-29-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 9 ++++++++- sound/core/seq/seq_clientmgr.c | 16 ++++++++++++++-- sound/core/seq/seq_dummy.c | 1 + sound/core/seq/seq_midi.c | 4 ++++ sound/core/seq/seq_ports.c | 13 +++++++++++++ sound/core/seq/seq_ports.h | 2 ++ sound/core/seq/seq_virmidi.c | 1 + 7 files changed, 43 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 67532c46b115..eae1e0b0bf37 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -455,6 +455,12 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_FLG_TIMESTAMP (1<<1) #define SNDRV_SEQ_PORT_FLG_TIME_REAL (1<<2) +/* port direction */ +#define SNDRV_SEQ_PORT_DIR_UNKNOWN 0 +#define SNDRV_SEQ_PORT_DIR_INPUT 1 +#define SNDRV_SEQ_PORT_DIR_OUTPUT 2 +#define SNDRV_SEQ_PORT_DIR_BIDIRECTION 3 + struct snd_seq_port_info { struct snd_seq_addr addr; /* client/port numbers */ char name[64]; /* port name */ @@ -471,7 +477,8 @@ struct snd_seq_port_info { void *kernel; /* reserved for kernel use (must be NULL) */ unsigned int flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ - char reserved[59]; /* for future use */ + unsigned char direction; /* port usage direction (r/w/bidir) */ + char reserved[58]; /* for future use */ }; diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 061b3e2bece1..33aa6c5c5c9e 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2440,6 +2440,17 @@ static void snd_seq_info_dump_subscribers(struct snd_info_buffer *buffer, #define FLAG_PERM_DUPLEX(perm) ((perm) & SNDRV_SEQ_PORT_CAP_DUPLEX ? 'X' : '-') +static const char *port_direction_name(unsigned char dir) +{ + static const char *names[4] = { + "-", "In", "Out", "In/Out" + }; + + if (dir > SNDRV_SEQ_PORT_DIR_BIDIRECTION) + return "Invalid"; + return names[dir]; +} + static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, struct snd_seq_client *client) { @@ -2449,12 +2460,13 @@ static void snd_seq_info_dump_ports(struct snd_info_buffer *buffer, list_for_each_entry(p, &client->ports_list_head, list) { if (p->capability & SNDRV_SEQ_PORT_CAP_INACTIVE) continue; - snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c)\n", + snd_iprintf(buffer, " Port %3d : \"%s\" (%c%c%c%c) [%s]\n", p->addr.port, p->name, FLAG_PERM_RD(p->capability), FLAG_PERM_WR(p->capability), FLAG_PERM_EX(p->capability), - FLAG_PERM_DUPLEX(p->capability)); + FLAG_PERM_DUPLEX(p->capability), + port_direction_name(p->direction)); snd_seq_info_dump_subscribers(buffer, &p->c_src, 1, " Connecting To: "); snd_seq_info_dump_subscribers(buffer, &p->c_dest, 0, " Connected From: "); } diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c index 8c18d8c4177e..2e8844ee32ed 100644 --- a/sound/core/seq/seq_dummy.c +++ b/sound/core/seq/seq_dummy.c @@ -127,6 +127,7 @@ create_port(int idx, int type) pinfo.capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; if (duplex) pinfo.capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo.direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION; pinfo.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | SNDRV_SEQ_PORT_TYPE_SOFTWARE | SNDRV_SEQ_PORT_TYPE_PORT; diff --git a/sound/core/seq/seq_midi.c b/sound/core/seq/seq_midi.c index 2b5fff80de58..44302d98950e 100644 --- a/sound/core/seq/seq_midi.c +++ b/sound/core/seq/seq_midi.c @@ -367,6 +367,10 @@ snd_seq_midisynth_probe(struct device *_dev) if ((port->capability & (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ)) == (SNDRV_SEQ_PORT_CAP_WRITE|SNDRV_SEQ_PORT_CAP_READ) && info->flags & SNDRV_RAWMIDI_INFO_DUPLEX) port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + if (port->capability & SNDRV_SEQ_PORT_CAP_READ) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (port->capability & SNDRV_SEQ_PORT_CAP_WRITE) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | SNDRV_SEQ_PORT_TYPE_HARDWARE | SNDRV_SEQ_PORT_TYPE_PORT; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 42f4172d4766..5574341f49eb 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -356,6 +356,16 @@ int snd_seq_set_port_info(struct snd_seq_client_port * port, port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; port->time_queue = info->time_queue; + /* direction */ + port->direction = info->direction; + /* fill default port direction */ + if (!port->direction) { + if (info->capability & SNDRV_SEQ_PORT_CAP_READ) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (info->capability & SNDRV_SEQ_PORT_CAP_WRITE) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + } + return 0; } @@ -393,6 +403,9 @@ int snd_seq_get_port_info(struct snd_seq_client_port * port, info->time_queue = port->time_queue; } + /* direction */ + info->direction = port->direction; + return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index 44f0e9e96bbf..dce733ab2398 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -72,6 +72,8 @@ struct snd_seq_client_port { int midi_voices; int synth_voices; + /* direction */ + unsigned char direction; }; struct snd_seq_client; diff --git a/sound/core/seq/seq_virmidi.c b/sound/core/seq/seq_virmidi.c index f5cae49500c8..1b9260108e48 100644 --- a/sound/core/seq/seq_virmidi.c +++ b/sound/core/seq/seq_virmidi.c @@ -385,6 +385,7 @@ static int snd_virmidi_dev_attach_seq(struct snd_virmidi_dev *rdev) pinfo->capability |= SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SYNC_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE; pinfo->capability |= SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SYNC_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ; pinfo->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + pinfo->direction = SNDRV_SEQ_PORT_DIR_BIDIRECTION; pinfo->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | SNDRV_SEQ_PORT_TYPE_SOFTWARE | SNDRV_SEQ_PORT_TYPE_PORT; -- cgit v1.2.3 From a3ca3b30800da0a334e2d6eb68d123ec8e2d2bf6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:50 +0200 Subject: ALSA: seq: Add UMP group number to snd_seq_port_info Add yet more new filed "ump_group" to snd_seq_port_info for specifying the associated UMP Group number for each sequencer port. This will be referred in the upcoming automatic UMP conversion in sequencer core. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-30-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 3 ++- sound/core/seq/seq_ports.c | 9 +++++++-- sound/core/seq/seq_ports.h | 3 ++- 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index eae1e0b0bf37..2470eaa5edc5 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -478,7 +478,8 @@ struct snd_seq_port_info { unsigned int flags; /* misc. conditioning */ unsigned char time_queue; /* queue # for timestamping */ unsigned char direction; /* port usage direction (r/w/bidir) */ - char reserved[58]; /* for future use */ + unsigned char ump_group; /* 0 = UMP EP (no conversion), 1-16 = UMP group number */ + char reserved[57]; /* for future use */ }; diff --git a/sound/core/seq/seq_ports.c b/sound/core/seq/seq_ports.c index 5574341f49eb..9b80f8275026 100644 --- a/sound/core/seq/seq_ports.c +++ b/sound/core/seq/seq_ports.c @@ -356,8 +356,12 @@ int snd_seq_set_port_info(struct snd_seq_client_port * port, port->time_real = (info->flags & SNDRV_SEQ_PORT_FLG_TIME_REAL) ? 1 : 0; port->time_queue = info->time_queue; - /* direction */ + /* UMP direction and group */ port->direction = info->direction; + port->ump_group = info->ump_group; + if (port->ump_group > SNDRV_UMP_MAX_GROUPS) + port->ump_group = 0; + /* fill default port direction */ if (!port->direction) { if (info->capability & SNDRV_SEQ_PORT_CAP_READ) @@ -403,8 +407,9 @@ int snd_seq_get_port_info(struct snd_seq_client_port * port, info->time_queue = port->time_queue; } - /* direction */ + /* UMP direction and group */ info->direction = port->direction; + info->ump_group = port->ump_group; return 0; } diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index dce733ab2398..c6c138edceab 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -72,8 +72,9 @@ struct snd_seq_client_port { int midi_voices; int synth_voices; - /* direction */ + /* UMP direction and group */ unsigned char direction; + unsigned char ump_group; }; struct snd_seq_client; -- cgit v1.2.3 From e9e02819a98a50fefe2f8016b1e5237742637cd1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:51 +0200 Subject: ALSA: seq: Automatic conversion of UMP events This patch enables the automatic conversion of UMP events from/to the legacy ALSA sequencer MIDI events. Also, as UMP itself has two different modes (MIDI 1.0 and MIDI 2.0), yet another converters between them are needed, too. Namely, we have conversions between the legacy and UMP like: - seq legacy event -> seq UMP MIDI 1.0 event - seq legacy event -> seq UMP MIDI 2.0 event - seq UMP MIDI 1.0 event -> seq legacy event - seq UMP MIDI 2.0 event -> seq legacy event and the conversions between UMP MIDI 1.0 and 2.0 clients like: - seq UMP MIDI 1.0 event -> seq UMP MIDI 2.0 event - seq UMP MIDI 2.0 event -> seq UMP MIDI 1.0 event The translation is per best-effort; some MIDI 2.0 specific events are ignored when translated to MIDI 1.0. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-31-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/Kconfig | 2 + sound/core/seq/Makefile | 1 + sound/core/seq/seq_clientmgr.c | 48 +- sound/core/seq/seq_clientmgr.h | 15 + sound/core/seq/seq_ports.h | 15 + sound/core/seq/seq_ump_convert.c | 1190 ++++++++++++++++++++++++++++++++++++++ sound/core/seq/seq_ump_convert.h | 22 + 7 files changed, 1279 insertions(+), 14 deletions(-) create mode 100644 sound/core/seq/seq_ump_convert.c create mode 100644 sound/core/seq/seq_ump_convert.h (limited to 'sound') diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index c69d8beb09fa..f8336134153e 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -66,5 +66,7 @@ config SND_SEQ_UMP Say Y here to enable the support for handling UMP (Universal MIDI Packet) events via ALSA sequencer infrastructure, which is an essential feature for enabling MIDI 2.0 support. + It includes the automatic conversion of ALSA sequencer events + among legacy and UMP clients. endif # SND_SEQUENCER diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile index 3a2177a7e50c..ba264a695643 100644 --- a/sound/core/seq/Makefile +++ b/sound/core/seq/Makefile @@ -8,6 +8,7 @@ snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \ seq_fifo.o seq_prioq.o seq_timer.o \ seq_system.o seq_ports.o snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o +snd-seq-$(CONFIG_SND_SEQ_UMP) += seq_ump_convert.o snd-seq-midi-objs := seq_midi.o snd-seq-midi-emul-objs := seq_midi_emul.o snd-seq-midi-event-objs := seq_midi_event.o diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 33aa6c5c5c9e..07b090f76b5f 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -20,6 +20,7 @@ #include "seq_timer.h" #include "seq_info.h" #include "seq_system.h" +#include "seq_ump_convert.h" #include #ifdef CONFIG_COMPAT #include @@ -612,6 +613,27 @@ static int update_timestamp_of_queue(struct snd_seq_event *event, return 1; } +/* deliver a single event; called from below and UMP converter */ +int __snd_seq_deliver_single_event(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + switch (dest->type) { + case USER_CLIENT: + if (!dest->data.user.fifo) + return 0; + return snd_seq_fifo_event_in(dest->data.user.fifo, event); + case KERNEL_CLIENT: + if (!dest_port->event_input) + return 0; + return dest_port->event_input(event, + snd_seq_ev_is_direct(event), + dest_port->private_data, + atomic, hop); + } + return 0; +} /* * deliver an event to the specified destination. @@ -648,22 +670,20 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, update_timestamp_of_queue(event, dest_port->time_queue, dest_port->time_real); - switch (dest->type) { - case USER_CLIENT: - if (dest->data.user.fifo) - result = snd_seq_fifo_event_in(dest->data.user.fifo, event); - break; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + if (snd_seq_ev_is_ump(event)) { + result = snd_seq_deliver_from_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } else if (snd_seq_client_is_ump(dest)) { + result = snd_seq_deliver_to_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } +#endif /* CONFIG_SND_SEQ_UMP */ - case KERNEL_CLIENT: - if (dest_port->event_input == NULL) - break; - result = dest_port->event_input(event, direct, - dest_port->private_data, + result = __snd_seq_deliver_single_event(dest, dest_port, event, atomic, hop); - break; - default: - break; - } __skip: if (dest_port) diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index bb973d36ce78..97762892ffab 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -85,6 +85,11 @@ int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table int snd_seq_client_notify_subscription(int client, int port, struct snd_seq_port_subscribe *info, int evtype); +int __snd_seq_deliver_single_event(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); + /* only for OSS sequencer */ bool snd_seq_client_ioctl_lock(int clientid); void snd_seq_client_ioctl_unlock(int clientid); @@ -95,4 +100,14 @@ extern int seq_client_load[15]; struct snd_seq_client *snd_seq_kernel_client_get(int client); void snd_seq_kernel_client_put(struct snd_seq_client *cptr); +static inline bool snd_seq_client_is_ump(struct snd_seq_client *c) +{ + return c->midi_version != SNDRV_SEQ_CLIENT_LEGACY_MIDI; +} + +static inline bool snd_seq_client_is_midi2(struct snd_seq_client *c) +{ + return c->midi_version == SNDRV_SEQ_CLIENT_UMP_MIDI_2_0; +} + #endif diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h index c6c138edceab..b111382f697a 100644 --- a/sound/core/seq/seq_ports.h +++ b/sound/core/seq/seq_ports.h @@ -42,6 +42,17 @@ struct snd_seq_port_subs_info { int (*close)(void *private_data, struct snd_seq_port_subscribe *info); }; +/* context for converting from legacy control event to UMP packet */ +struct snd_seq_ump_midi2_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + struct snd_seq_client_port { struct snd_seq_addr addr; /* client/port number */ @@ -75,6 +86,10 @@ struct snd_seq_client_port { /* UMP direction and group */ unsigned char direction; unsigned char ump_group; + +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + struct snd_seq_ump_midi2_bank midi2_bank[16]; /* per channel */ +#endif }; struct snd_seq_client; diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c new file mode 100644 index 000000000000..433fe842947e --- /dev/null +++ b/sound/core/seq/seq_ump_convert.c @@ -0,0 +1,1190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer event conversion between UMP and legacy clients + */ + +#include +#include +#include +#include +#include +#include +#include "seq_ump_convert.h" + +/* + * Upgrade / downgrade value bits + */ +static u8 downscale_32_to_7bit(u32 src) +{ + return src >> 25; +} + +static u16 downscale_32_to_14bit(u32 src) +{ + return src >> 18; +} + +static u8 downscale_16_to_7bit(u16 src) +{ + return src >> 9; +} + +static u16 upscale_7_to_16bit(u8 src) +{ + u16 val, repeat; + + val = (u16)src << 9; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 3) | (repeat >> 3); +} + +static u32 upscale_7_to_32bit(u8 src) +{ + u32 val, repeat; + + val = src << 25; + if (src <= 0x40) + return val; + repeat = src & 0x3f; + return val | (repeat << 19) | (repeat << 13) | + (repeat << 7) | (repeat << 1) | (repeat >> 5); +} + +static u32 upscale_14_to_32bit(u16 src) +{ + u32 val, repeat; + + val = src << 18; + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +static unsigned char get_ump_group(struct snd_seq_client_port *port) +{ + return port->ump_group ? (port->ump_group - 1) : 0; +} + +/* create a UMP header */ +#define make_raw_ump(port, type) \ + ump_compose(type, get_ump_group(port), 0, 0) + +/* + * UMP -> MIDI1 sequencer event + */ + +/* MIDI 1.0 CVM */ + +/* encode note event */ +static void ump_midi1_to_note_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.note.channel = val->note.channel; + ev->data.note.note = val->note.note; + ev->data.note.velocity = val->note.velocity; +} + +/* encode one parameter controls */ +static void ump_midi1_to_ctrl_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->caf.channel; + ev->data.control.value = val->caf.data; +} + +/* encode pitch wheel change */ +static void ump_midi1_to_pitchbend_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->pb.channel; + ev->data.control.value = (val->pb.data_msb << 7) | val->pb.data_lsb; + ev->data.control.value -= 8192; +} + +/* encode midi control change */ +static void ump_midi1_to_cc_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->cc.channel; + ev->data.control.param = val->cc.index; + ev->data.control.value = val->cc.data; +} + +/* Encoding MIDI 1.0 UMP packet */ +struct seq_ump_midi1_to_ev { + int seq_type; + void (*encode)(const union snd_ump_midi1_msg *val, struct snd_seq_event *ev); +}; + +/* Encoders for MIDI1 status 0x80-0xe0 */ +static struct seq_ump_midi1_to_ev midi1_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi1_to_note_ev}, /* 0x80 */ + {SNDRV_SEQ_EVENT_NOTEON, ump_midi1_to_note_ev}, /* 0x90 */ + {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi1_to_note_ev}, /* 0xa0 */ + {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi1_to_cc_ev}, /* 0xb0 */ + {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi1_to_ctrl_ev}, /* 0xc0 */ + {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi1_to_ctrl_ev}, /* 0xd0 */ + {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi1_to_pitchbend_ev}, /* 0xe0 */ +}; + +static int cvt_ump_midi1_to_event(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->note.status; + + if (status < 0x8 || status > 0xe) + return 0; /* invalid - skip */ + status -= 8; + ev->type = midi1_msg_encoders[status].seq_type; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + midi1_msg_encoders[status].encode(val, ev); + return 1; +} + +/* MIDI System message */ + +/* encode one parameter value*/ +static void ump_system_to_one_param_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.value = val->system.parm1; +} + +/* encode song position */ +static void ump_system_to_songpos_ev(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.value = (val->system.parm1 << 7) | val->system.parm2; +} + +/* Encoders for 0xf0 - 0xff */ +static struct seq_ump_midi1_to_ev system_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */ + {SNDRV_SEQ_EVENT_QFRAME, ump_system_to_one_param_ev}, /* 0xf1 */ + {SNDRV_SEQ_EVENT_SONGPOS, ump_system_to_songpos_ev}, /* 0xf2 */ + {SNDRV_SEQ_EVENT_SONGSEL, ump_system_to_one_param_ev}, /* 0xf3 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf4 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf5 */ + {SNDRV_SEQ_EVENT_TUNE_REQUEST, NULL}, /* 0xf6 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf7 */ + {SNDRV_SEQ_EVENT_CLOCK, NULL}, /* 0xf8 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf9 */ + {SNDRV_SEQ_EVENT_START, NULL}, /* 0xfa */ + {SNDRV_SEQ_EVENT_CONTINUE, NULL}, /* 0xfb */ + {SNDRV_SEQ_EVENT_STOP, NULL}, /* 0xfc */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xfd */ + {SNDRV_SEQ_EVENT_SENSING, NULL}, /* 0xfe */ + {SNDRV_SEQ_EVENT_RESET, NULL}, /* 0xff */ +}; + +static int cvt_ump_system_to_event(const union snd_ump_midi1_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->system.status; + + if ((status & 0xf0) != UMP_MIDI1_MSG_REALTIME) + return 0; /* invalid status - skip */ + status &= 0x0f; + ev->type = system_msg_encoders[status].seq_type; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; + if (system_msg_encoders[status].encode) + system_msg_encoders[status].encode(val, ev); + return 1; +} + +/* MIDI 2.0 CVM */ + +/* encode note event */ +static int ump_midi2_to_note_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.note.channel = val->note.channel; + ev->data.note.note = val->note.note; + ev->data.note.velocity = downscale_16_to_7bit(val->note.velocity); + /* correct note-on velocity 0 to 1; + * it's no longer equivalent as not-off for MIDI 2.0 + */ + if (ev->type == SNDRV_SEQ_EVENT_NOTEON && + !ev->data.note.velocity) + ev->data.note.velocity = 1; + return 1; +} + +/* encode pitch wheel change */ +static int ump_midi2_to_pitchbend_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->pb.channel; + ev->data.control.value = downscale_32_to_14bit(val->pb.data); + ev->data.control.value -= 8192; + return 1; +} + +/* encode midi control change */ +static int ump_midi2_to_cc_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->cc.channel; + ev->data.control.param = val->cc.index; + ev->data.control.value = downscale_32_to_7bit(val->cc.data); + return 1; +} + +/* encode midi program change */ +static int ump_midi2_to_pgm_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + int size = 1; + + ev->data.control.channel = val->pg.channel; + if (val->pg.bank_valid) { + ev->type = SNDRV_SEQ_EVENT_CONTROL14; + ev->data.control.param = UMP_CC_BANK_SELECT; + ev->data.control.value = (val->pg.bank_msb << 7) | val->pg.bank_lsb; + ev[1] = ev[0]; + ev++; + ev->type = SNDRV_SEQ_EVENT_PGMCHANGE; + size = 2; + } + ev->data.control.value = val->pg.program; + return size; +} + +/* encode one parameter controls */ +static int ump_midi2_to_ctrl_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->caf.channel; + ev->data.control.value = downscale_32_to_7bit(val->caf.data); + return 1; +} + +/* encode RPN/NRPN */ +static int ump_midi2_to_rpn_ev(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + ev->data.control.channel = val->rpn.channel; + ev->data.control.param = (val->rpn.bank << 7) | val->rpn.index; + ev->data.control.value = downscale_32_to_14bit(val->rpn.data); + return 1; +} + +/* Encoding MIDI 2.0 UMP Packet */ +struct seq_ump_midi2_to_ev { + int seq_type; + int (*encode)(const union snd_ump_midi2_msg *val, struct snd_seq_event *ev); +}; + +/* Encoders for MIDI2 status 0x00-0xf0 */ +static struct seq_ump_midi2_to_ev midi2_msg_encoders[] = { + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x00 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x10 */ + {SNDRV_SEQ_EVENT_REGPARAM, ump_midi2_to_rpn_ev}, /* 0x20 */ + {SNDRV_SEQ_EVENT_NONREGPARAM, ump_midi2_to_rpn_ev}, /* 0x30 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x40 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x50 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x60 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0x70 */ + {SNDRV_SEQ_EVENT_NOTEOFF, ump_midi2_to_note_ev}, /* 0x80 */ + {SNDRV_SEQ_EVENT_NOTEON, ump_midi2_to_note_ev}, /* 0x90 */ + {SNDRV_SEQ_EVENT_KEYPRESS, ump_midi2_to_note_ev}, /* 0xa0 */ + {SNDRV_SEQ_EVENT_CONTROLLER, ump_midi2_to_cc_ev}, /* 0xb0 */ + {SNDRV_SEQ_EVENT_PGMCHANGE, ump_midi2_to_pgm_ev}, /* 0xc0 */ + {SNDRV_SEQ_EVENT_CHANPRESS, ump_midi2_to_ctrl_ev}, /* 0xd0 */ + {SNDRV_SEQ_EVENT_PITCHBEND, ump_midi2_to_pitchbend_ev}, /* 0xe0 */ + {SNDRV_SEQ_EVENT_NONE, NULL}, /* 0xf0 */ +}; + +static int cvt_ump_midi2_to_event(const union snd_ump_midi2_msg *val, + struct snd_seq_event *ev) +{ + unsigned char status = val->note.status; + + ev->type = midi2_msg_encoders[status].seq_type; + if (ev->type == SNDRV_SEQ_EVENT_NONE) + return 0; /* skip */ + ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED; + return midi2_msg_encoders[status].encode(val, ev); +} + +/* parse and compose for a sysex var-length event */ +static int cvt_ump_sysex7_to_event(const u32 *data, unsigned char *buf, + struct snd_seq_event *ev) +{ + unsigned char status; + unsigned char bytes; + u32 val; + int size = 0; + + val = data[0]; + status = ump_sysex_message_status(val); + bytes = ump_sysex_message_length(val); + if (bytes > 6) + return 0; // skip + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_START) { + buf[0] = UMP_MIDI1_MSG_SYSEX_START; + size = 1; + } + + if (bytes > 0) + buf[size++] = (val >> 8) & 0x7f; + if (bytes > 1) + buf[size++] = val & 0x7f; + val = data[1]; + if (bytes > 2) + buf[size++] = (val >> 24) & 0x7f; + if (bytes > 3) + buf[size++] = (val >> 16) & 0x7f; + if (bytes > 4) + buf[size++] = (val >> 8) & 0x7f; + if (bytes > 5) + buf[size++] = val & 0x7f; + + if (status == UMP_SYSEX_STATUS_SINGLE || + status == UMP_SYSEX_STATUS_END) + buf[size++] = UMP_MIDI1_MSG_SYSEX_END; + + ev->type = SNDRV_SEQ_EVENT_SYSEX; + ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE; + ev->data.ext.len = size; + ev->data.ext.ptr = buf; + return 1; +} + +/* convert UMP packet from MIDI 1.0 to MIDI 2.0 and deliver it */ +static int cvt_ump_midi1_to_midi2(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *__event, + int atomic, int hop) +{ + struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event; + struct snd_seq_ump_event ev_cvt; + const union snd_ump_midi1_msg *midi1 = (const union snd_ump_midi1_msg *)event->ump; + union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)ev_cvt.ump; + + ev_cvt = *event; + memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump)); + + midi2->note.type = UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE; + midi2->note.group = midi1->note.group; + midi2->note.status = midi1->note.status; + midi2->note.channel = midi1->note.channel; + switch (midi1->note.status) { + case UMP_MSG_STATUS_NOTE_ON: + case UMP_MSG_STATUS_NOTE_OFF: + midi2->note.note = midi1->note.note; + midi2->note.velocity = upscale_7_to_16bit(midi1->note.velocity); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi2->paf.note = midi1->paf.note; + midi2->paf.data = upscale_7_to_32bit(midi1->paf.data); + break; + case UMP_MSG_STATUS_CC: + midi2->cc.index = midi1->cc.index; + midi2->cc.data = upscale_7_to_32bit(midi1->cc.data); + break; + case UMP_MSG_STATUS_PROGRAM: + midi2->pg.program = midi1->pg.program; + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi2->caf.data = upscale_7_to_32bit(midi1->caf.data); + break; + case UMP_MSG_STATUS_PITCH_BEND: + midi2->pb.data = upscale_14_to_32bit((midi1->pb.data_msb << 7) | + midi1->pb.data_lsb); + break; + default: + return 0; + } + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); +} + +/* convert UMP packet from MIDI 2.0 to MIDI 1.0 and deliver it */ +static int cvt_ump_midi2_to_midi1(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *__event, + int atomic, int hop) +{ + struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi1_msg *midi1 = (union snd_ump_midi1_msg *)ev_cvt.ump; + const union snd_ump_midi2_msg *midi2 = (const union snd_ump_midi2_msg *)event->ump; + u16 v; + + ev_cvt = *event; + memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump)); + + midi1->note.type = UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE; + midi1->note.group = midi2->note.group; + midi1->note.status = midi2->note.status; + midi1->note.channel = midi2->note.channel; + switch (midi2->note.status << 4) { + case UMP_MSG_STATUS_NOTE_ON: + case UMP_MSG_STATUS_NOTE_OFF: + midi1->note.note = midi2->note.note; + midi1->note.velocity = downscale_16_to_7bit(midi2->note.velocity); + break; + case UMP_MSG_STATUS_POLY_PRESSURE: + midi1->paf.note = midi2->paf.note; + midi1->paf.data = downscale_32_to_7bit(midi2->paf.data); + break; + case UMP_MSG_STATUS_CC: + midi1->cc.index = midi2->cc.index; + midi1->cc.data = downscale_32_to_7bit(midi2->cc.data); + break; + case UMP_MSG_STATUS_PROGRAM: + midi1->pg.program = midi2->pg.program; + break; + case UMP_MSG_STATUS_CHANNEL_PRESSURE: + midi1->caf.data = downscale_32_to_7bit(midi2->caf.data); + break; + case UMP_MSG_STATUS_PITCH_BEND: + v = downscale_32_to_14bit(midi2->pb.data); + midi1->pb.data_msb = v >> 7; + midi1->pb.data_lsb = v & 0x7f; + break; + default: + return 0; + } + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); +} + +/* convert UMP to a legacy ALSA seq event and deliver it */ +static int cvt_ump_to_any(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + unsigned char type, + int atomic, int hop) +{ + struct snd_seq_event ev_cvt[2]; /* up to two events */ + struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event; + /* use the second event as a temp buffer for saving stack usage */ + unsigned char *sysex_buf = (unsigned char *)(ev_cvt + 1); + unsigned char flags = event->flags & ~SNDRV_SEQ_EVENT_UMP; + int i, len, err; + + ev_cvt[0] = ev_cvt[1] = *event; + ev_cvt[0].flags = flags; + ev_cvt[1].flags = flags; + switch (type) { + case UMP_MSG_TYPE_SYSTEM: + len = cvt_ump_system_to_event((union snd_ump_midi1_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE: + len = cvt_ump_midi1_to_event((union snd_ump_midi1_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE: + len = cvt_ump_midi2_to_event((union snd_ump_midi2_msg *)ump_ev->ump, + ev_cvt); + break; + case UMP_MSG_TYPE_DATA: + len = cvt_ump_sysex7_to_event(ump_ev->ump, sysex_buf, ev_cvt); + break; + default: + return 0; + } + + for (i = 0; i < len; i++) { + err = __snd_seq_deliver_single_event(dest, dest_port, + &ev_cvt[i], atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Replace UMP group field with the destination and deliver */ +static int deliver_with_group_convert(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_ump_event *ump_ev, + int atomic, int hop) +{ + struct snd_seq_ump_event ev = *ump_ev; + + /* rewrite the group to the destination port */ + ev.ump[0] &= ~(0xfU << 24); + /* fill with the new group; the dest_port->ump_group field is 1-based */ + ev.ump[0] |= ((dest_port->ump_group - 1) << 24); + + return __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev, + atomic, hop); +} + +/* Convert from UMP packet and deliver */ +int snd_seq_deliver_from_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event; + unsigned char type; + + if (snd_seq_ev_is_variable(event)) + return 0; // skip, no variable event for UMP, so far + type = ump_message_type(ump_ev->ump[0]); + + if (snd_seq_client_is_ump(dest)) { + if (snd_seq_client_is_midi2(dest) && + type == UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE) + return cvt_ump_midi1_to_midi2(dest, dest_port, + event, atomic, hop); + else if (!snd_seq_client_is_midi2(dest) && + type == UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE) + return cvt_ump_midi2_to_midi1(dest, dest_port, + event, atomic, hop); + /* non-EP port and different group is set? */ + if (dest_port->ump_group && + ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group) + return deliver_with_group_convert(dest, dest_port, + ump_ev, atomic, hop); + /* copy as-is */ + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + } + + return cvt_ump_to_any(dest, dest_port, event, type, atomic, hop); +} + +/* + * MIDI1 sequencer event -> UMP conversion + */ + +/* Conversion to UMP MIDI 1.0 */ + +/* convert note on/off event to MIDI 1.0 UMP */ +static int note_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + if (!event->data.note.velocity) + status = UMP_MSG_STATUS_NOTE_OFF; + data->note.status = status; + data->note.channel = event->data.note.channel & 0x0f; + data->note.velocity = event->data.note.velocity & 0x7f; + data->note.note = event->data.note.note & 0x7f; + return 1; +} + +/* convert CC event to MIDI 1.0 UMP */ +static int cc_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->cc.status = status; + data->cc.channel = event->data.control.channel & 0x0f; + data->cc.index = event->data.control.param; + data->cc.data = event->data.control.value; + return 1; +} + +/* convert one-parameter control event to MIDI 1.0 UMP */ +static int ctrl_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->caf.status = status; + data->caf.channel = event->data.control.channel & 0x0f; + data->caf.data = event->data.control.value & 0x7f; + return 1; +} + +/* convert pitchbend event to MIDI 1.0 UMP */ +static int pitchbend_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + int val = event->data.control.value + 8192; + + val = clamp(val, 0, 0x3fff); + data->pb.status = status; + data->pb.channel = event->data.control.channel & 0x0f; + data->pb.data_msb = (val >> 7) & 0x7f; + data->pb.data_lsb = val & 0x7f; + return 1; +} + +/* convert 14bit control event to MIDI 1.0 UMP; split to two events */ +static int ctrl14_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = event->data.control.channel & 0x0f; + data->cc.index = event->data.control.param & 0x7f; + if (event->data.control.param < 0x20) { + data->cc.data = (event->data.control.value >> 7) & 0x7f; + data[1] = data[0]; + data[1].cc.index = event->data.control.param | 0x20; + data[1].cc.data = event->data.control.value & 0x7f; + return 2; + } + + data->cc.data = event->data.control.value & 0x7f; + return 1; +} + +/* convert RPN/NRPN event to MIDI 1.0 UMP; split to four events */ +static int rpn_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + bool is_rpn = (status == UMP_MSG_STATUS_RPN); + + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = event->data.control.channel & 0x0f; + data[1] = data[2] = data[3] = data[0]; + + data[0].cc.index = is_rpn ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB; + data[0].cc.data = (event->data.control.param >> 7) & 0x7f; + data[1].cc.index = is_rpn ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB; + data[1].cc.data = event->data.control.param & 0x7f; + data[2].cc.index = UMP_CC_DATA; + data[2].cc.data = (event->data.control.value >> 7) & 0x7f; + data[3].cc.index = UMP_CC_DATA_LSB; + data[3].cc.data = event->data.control.value & 0x7f; + return 4; +} + +/* convert system / RT message to UMP */ +static int system_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + return 1; +} + +/* convert system / RT message with 1 parameter to UMP */ +static int system_1p_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + data->system.parm1 = event->data.control.value & 0x7f; + return 1; +} + +/* convert system / RT message with two parameters to UMP */ +static int system_2p_ev_to_ump_midi1(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status) +{ + data->system.status = status; + data->system.parm1 = (event->data.control.value >> 7) & 0x7f; + data->system.parm1 = event->data.control.value & 0x7f; + return 1; +} + +/* Conversion to UMP MIDI 2.0 */ + +/* convert note on/off event to MIDI 2.0 UMP */ +static int note_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + if (!event->data.note.velocity) + status = UMP_MSG_STATUS_NOTE_OFF; + data->note.status = status; + data->note.channel = event->data.note.channel & 0x0f; + data->note.note = event->data.note.note & 0x7f; + data->note.velocity = upscale_7_to_16bit(event->data.note.velocity & 0x7f); + return 1; +} + +/* convert PAF event to MIDI 2.0 UMP */ +static int paf_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->paf.status = status; + data->paf.channel = event->data.note.channel & 0x0f; + data->paf.note = event->data.note.note & 0x7f; + data->paf.data = upscale_7_to_32bit(event->data.note.velocity & 0x7f); + return 1; +} + +/* set up the MIDI2 RPN/NRPN packet data from the parsed info */ +static void fill_rpn(struct snd_seq_ump_midi2_bank *cc, + union snd_ump_midi2_msg *data) +{ + if (cc->rpn_set) { + data->rpn.status = UMP_MSG_STATUS_RPN; + data->rpn.bank = cc->cc_rpn_msb; + data->rpn.index = cc->cc_rpn_lsb; + cc->rpn_set = 0; + cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; + } else { + data->rpn.status = UMP_MSG_STATUS_NRPN; + data->rpn.bank = cc->cc_nrpn_msb; + data->rpn.index = cc->cc_nrpn_lsb; + cc->nrpn_set = 0; + cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0; + } + data->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | + cc->cc_data_lsb); + cc->cc_data_msb = cc->cc_data_lsb = 0; +} + +/* convert CC event to MIDI 2.0 UMP */ +static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + unsigned char index = event->data.control.param & 0x7f; + unsigned char val = event->data.control.value & 0x7f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + + /* process special CC's (bank/rpn/nrpn) */ + switch (index) { + case UMP_CC_RPN_MSB: + cc->rpn_set = 1; + cc->cc_rpn_msb = val; + return 0; // skip + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = val; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->nrpn_set = 1; + cc->cc_nrpn_msb = val; + return 0; // skip + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = val; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = val; + return 0; // skip + case UMP_CC_BANK_SELECT: + cc->bank_set = 1; + cc->cc_bank_msb = val; + return 0; // skip + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = val; + return 0; // skip + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = val; + if (!(cc->rpn_set || cc->nrpn_set)) + return 0; // skip + fill_rpn(cc, data); + return 1; + } + + data->cc.status = status; + data->cc.channel = channel; + data->cc.index = index; + data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + return 1; +} + +/* convert one-parameter control event to MIDI 2.0 UMP */ +static int ctrl_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->caf.status = status; + data->caf.channel = event->data.control.channel & 0x0f; + data->caf.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + return 1; +} + +/* convert program change event to MIDI 2.0 UMP */ +static int pgm_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + + data->pg.status = status; + data->pg.channel = channel; + data->pg.program = event->data.control.value & 0x7f; + if (cc->bank_set) { + data->pg.bank_valid = 1; + data->pg.bank_msb = cc->cc_bank_msb; + data->pg.bank_lsb = cc->cc_bank_lsb; + cc->bank_set = 0; + cc->cc_bank_msb = cc->cc_bank_lsb = 0; + } + return 1; +} + +/* convert pitchbend event to MIDI 2.0 UMP */ +static int pitchbend_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + int val = event->data.control.value + 8192; + + val = clamp(val, 0, 0x3fff); + data->pb.status = status; + data->pb.channel = event->data.control.channel & 0x0f; + data->pb.data = upscale_14_to_32bit(val); + return 1; +} + +/* convert 14bit control event to MIDI 2.0 UMP; split to two events */ +static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + unsigned char channel = event->data.control.channel & 0x0f; + unsigned char index = event->data.control.param & 0x7f; + struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel]; + unsigned char msb, lsb; + + msb = (event->data.control.value >> 7) & 0x7f; + lsb = event->data.control.value & 0x7f; + /* process special CC's (bank/rpn/nrpn) */ + switch (index) { + case UMP_CC_BANK_SELECT: + cc->cc_bank_msb = msb; + fallthrough; + case UMP_CC_BANK_SELECT_LSB: + cc->bank_set = 1; + cc->cc_bank_lsb = lsb; + return 0; // skip + case UMP_CC_RPN_MSB: + cc->cc_rpn_msb = msb; + fallthrough; + case UMP_CC_RPN_LSB: + cc->rpn_set = 1; + cc->cc_rpn_lsb = lsb; + return 0; // skip + case UMP_CC_NRPN_MSB: + cc->cc_nrpn_msb = msb; + fallthrough; + case UMP_CC_NRPN_LSB: + cc->nrpn_set = 1; + cc->cc_nrpn_lsb = lsb; + return 0; // skip + case UMP_CC_DATA: + cc->cc_data_msb = msb; + fallthrough; + case UMP_CC_DATA_LSB: + cc->cc_data_lsb = lsb; + if (!(cc->rpn_set || cc->nrpn_set)) + return 0; // skip + fill_rpn(cc, data); + return 1; + } + + data->cc.status = UMP_MSG_STATUS_CC; + data->cc.channel = channel; + data->cc.index = index; + if (event->data.control.param < 0x20) { + data->cc.data = upscale_7_to_32bit(msb); + data[1] = data[0]; + data[1].cc.index = event->data.control.param | 0x20; + data[1].cc.data = upscale_7_to_32bit(lsb); + return 2; + } + + data->cc.data = upscale_7_to_32bit(lsb); + return 1; +} + +/* convert RPN/NRPN event to MIDI 2.0 UMP */ +static int rpn_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + data->rpn.status = status; + data->rpn.channel = event->data.control.channel; + data->rpn.bank = (event->data.control.param >> 7) & 0x7f; + data->rpn.index = event->data.control.param & 0x7f; + data->rpn.data = upscale_14_to_32bit(event->data.control.value & 0x3fff); + return 1; +} + +/* convert system / RT message to UMP */ +static int system_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +/* convert system / RT message with 1 parameter to UMP */ +static int system_1p_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_1p_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +/* convert system / RT message with two parameters to UMP */ +static int system_2p_ev_to_ump_midi2(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status) +{ + return system_1p_ev_to_ump_midi1(event, dest_port, + (union snd_ump_midi1_msg *)data, + status); +} + +struct seq_ev_to_ump { + int seq_type; + unsigned char status; + int (*midi1_encode)(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi1_msg *data, + unsigned char status); + int (*midi2_encode)(const struct snd_seq_event *event, + struct snd_seq_client_port *dest_port, + union snd_ump_midi2_msg *data, + unsigned char status); +}; + +static const struct seq_ev_to_ump seq_ev_ump_encoders[] = { + { SNDRV_SEQ_EVENT_NOTEON, UMP_MSG_STATUS_NOTE_ON, + note_ev_to_ump_midi1, note_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_NOTEOFF, UMP_MSG_STATUS_NOTE_OFF, + note_ev_to_ump_midi1, note_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_KEYPRESS, UMP_MSG_STATUS_POLY_PRESSURE, + note_ev_to_ump_midi1, paf_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTROLLER, UMP_MSG_STATUS_CC, + cc_ev_to_ump_midi1, cc_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_PGMCHANGE, UMP_MSG_STATUS_PROGRAM, + ctrl_ev_to_ump_midi1, pgm_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CHANPRESS, UMP_MSG_STATUS_CHANNEL_PRESSURE, + ctrl_ev_to_ump_midi1, ctrl_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_PITCHBEND, UMP_MSG_STATUS_PITCH_BEND, + pitchbend_ev_to_ump_midi1, pitchbend_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTROL14, 0, + ctrl14_ev_to_ump_midi1, ctrl14_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_NONREGPARAM, UMP_MSG_STATUS_NRPN, + rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_REGPARAM, UMP_MSG_STATUS_RPN, + rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_QFRAME, UMP_SYSTEM_STATUS_MIDI_TIME_CODE, + system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SONGPOS, UMP_SYSTEM_STATUS_SONG_POSITION, + system_2p_ev_to_ump_midi1, system_2p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SONGSEL, UMP_SYSTEM_STATUS_SONG_SELECT, + system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_TUNE_REQUEST, UMP_SYSTEM_STATUS_TUNE_REQUEST, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CLOCK, UMP_SYSTEM_STATUS_TIMING_CLOCK, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_START, UMP_SYSTEM_STATUS_START, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_CONTINUE, UMP_SYSTEM_STATUS_CONTINUE, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_STOP, UMP_SYSTEM_STATUS_STOP, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, + { SNDRV_SEQ_EVENT_SENSING, UMP_SYSTEM_STATUS_ACTIVE_SENSING, + system_ev_to_ump_midi1, system_ev_to_ump_midi2 }, +}; + +static const struct seq_ev_to_ump *find_ump_encoder(int type) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seq_ev_ump_encoders); i++) + if (seq_ev_ump_encoders[i].seq_type == type) + return &seq_ev_ump_encoders[i]; + + return NULL; +} + +static void setup_ump_event(struct snd_seq_ump_event *dest, + const struct snd_seq_event *src) +{ + memcpy(dest, src, sizeof(*src)); + dest->type = 0; + dest->flags |= SNDRV_SEQ_EVENT_UMP; + dest->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK; + memset(dest->ump, 0, sizeof(dest->ump)); +} + +/* Convert ALSA seq event to UMP MIDI 1.0 and deliver it */ +static int cvt_to_ump_midi1(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + const struct seq_ev_to_ump *encoder; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi1_msg data[4]; + int i, n, err; + + encoder = find_ump_encoder(event->type); + if (!encoder) + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + + data->raw = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE); + n = encoder->midi1_encode(event, dest_port, data, encoder->status); + if (!n) + return 0; + + setup_ump_event(&ev_cvt, event); + for (i = 0; i < n; i++) { + ev_cvt.ump[0] = data[i].raw; + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Convert ALSA seq event to UMP MIDI 2.0 and deliver it */ +static int cvt_to_ump_midi2(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + const struct seq_ev_to_ump *encoder; + struct snd_seq_ump_event ev_cvt; + union snd_ump_midi2_msg data[2]; + int i, n, err; + + encoder = find_ump_encoder(event->type); + if (!encoder) + return __snd_seq_deliver_single_event(dest, dest_port, + event, atomic, hop); + + data->raw[0] = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE); + data->raw[1] = 0; + n = encoder->midi2_encode(event, dest_port, data, encoder->status); + if (!n) + return 0; + + setup_ump_event(&ev_cvt, event); + for (i = 0; i < n; i++) { + memcpy(ev_cvt.ump, &data[i], sizeof(data[i])); + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + + return 0; +} + +/* Fill up a sysex7 UMP from the byte stream */ +static void fill_sysex7_ump(struct snd_seq_client_port *dest_port, + u32 *val, u8 status, u8 *buf, int len) +{ + memset(val, 0, 8); + memcpy((u8 *)val + 2, buf, len); +#ifdef __LITTLE_ENDIAN + swab32_array(val, 2); +#endif + val[0] |= ump_compose(UMP_MSG_TYPE_DATA, get_ump_group(dest_port), + status, len); +} + +/* Convert sysex var event to UMP sysex7 packets and deliver them */ +static int cvt_sysex_to_ump(struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + struct snd_seq_ump_event ev_cvt; + unsigned char status; + u8 buf[6], *xbuf; + int offset = 0; + int len, err; + + if (!snd_seq_ev_is_variable(event)) + return 0; + + setup_ump_event(&ev_cvt, event); + for (;;) { + len = snd_seq_expand_var_event_at(event, sizeof(buf), buf, offset); + if (len <= 0) + break; + if (WARN_ON(len > 6)) + break; + offset += len; + xbuf = buf; + if (*xbuf == UMP_MIDI1_MSG_SYSEX_START) { + status = UMP_SYSEX_STATUS_START; + xbuf++; + len--; + if (len > 0 && xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) { + status = UMP_SYSEX_STATUS_SINGLE; + len--; + } + } else { + if (xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) { + status = UMP_SYSEX_STATUS_END; + len--; + } else { + status = UMP_SYSEX_STATUS_CONTINUE; + } + } + fill_sysex7_ump(dest_port, ev_cvt.ump, status, xbuf, len); + err = __snd_seq_deliver_single_event(dest, dest_port, + (struct snd_seq_event *)&ev_cvt, + atomic, hop); + if (err < 0) + return err; + } + return 0; +} + +/* Convert to UMP packet and deliver */ +int snd_seq_deliver_to_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop) +{ + if (event->type == SNDRV_SEQ_EVENT_SYSEX) + return cvt_sysex_to_ump(dest, dest_port, event, atomic, hop); + else if (snd_seq_client_is_midi2(dest)) + return cvt_to_ump_midi2(dest, dest_port, event, atomic, hop); + else + return cvt_to_ump_midi1(dest, dest_port, event, atomic, hop); +} diff --git a/sound/core/seq/seq_ump_convert.h b/sound/core/seq/seq_ump_convert.h new file mode 100644 index 000000000000..6c146d803280 --- /dev/null +++ b/sound/core/seq/seq_ump_convert.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA sequencer event conversion between UMP and legacy clients + */ +#ifndef __SEQ_UMP_CONVERT_H +#define __SEQ_UMP_CONVERT_H + +#include "seq_clientmgr.h" +#include "seq_ports.h" + +int snd_seq_deliver_from_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); +int snd_seq_deliver_to_ump(struct snd_seq_client *source, + struct snd_seq_client *dest, + struct snd_seq_client_port *dest_port, + struct snd_seq_event *event, + int atomic, int hop); + +#endif /* __SEQ_UMP_CONVERT_H */ -- cgit v1.2.3 From 329ffe11a014834fdef9167c7ea24bd459829f86 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:52 +0200 Subject: ALSA: seq: Allow suppressing UMP conversions A sequencer client like seq_dummy rather doesn't want to convert UMP events but receives / sends as is. Add a new event filter flag to suppress the automatic UMP conversion and applies accordingly. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-32-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 1 + sound/core/seq/seq_clientmgr.c | 18 ++++++++++-------- sound/core/seq/seq_dummy.c | 8 ++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 2470eaa5edc5..c4632bd9d3a0 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -347,6 +347,7 @@ typedef int __bitwise snd_seq_client_type_t; #define SNDRV_SEQ_FILTER_BROADCAST (1U<<0) /* accept broadcast messages */ #define SNDRV_SEQ_FILTER_MULTICAST (1U<<1) /* accept multicast messages */ #define SNDRV_SEQ_FILTER_BOUNCE (1U<<2) /* accept bounce event in error */ +#define SNDRV_SEQ_FILTER_NO_CONVERT (1U<<30) /* don't convert UMP events */ #define SNDRV_SEQ_FILTER_USE_EVENT (1U<<31) /* use event filter */ struct snd_seq_client_info { diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 07b090f76b5f..3b1adcb1ccdd 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -671,14 +671,16 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, dest_port->time_real); #if IS_ENABLED(CONFIG_SND_SEQ_UMP) - if (snd_seq_ev_is_ump(event)) { - result = snd_seq_deliver_from_ump(client, dest, dest_port, - event, atomic, hop); - goto __skip; - } else if (snd_seq_client_is_ump(dest)) { - result = snd_seq_deliver_to_ump(client, dest, dest_port, - event, atomic, hop); - goto __skip; + if (!(dest->filter & SNDRV_SEQ_FILTER_NO_CONVERT)) { + if (snd_seq_ev_is_ump(event)) { + result = snd_seq_deliver_from_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } else if (snd_seq_client_is_ump(dest)) { + result = snd_seq_deliver_to_ump(client, dest, dest_port, + event, atomic, hop); + goto __skip; + } } #endif /* CONFIG_SND_SEQ_UMP */ diff --git a/sound/core/seq/seq_dummy.c b/sound/core/seq/seq_dummy.c index 2e8844ee32ed..9308194b2d9a 100644 --- a/sound/core/seq/seq_dummy.c +++ b/sound/core/seq/seq_dummy.c @@ -152,6 +152,7 @@ static int __init register_client(void) { struct snd_seq_dummy_port *rec1, *rec2; + struct snd_seq_client *client; int i; if (ports < 1) { @@ -165,6 +166,13 @@ register_client(void) if (my_client < 0) return my_client; + /* don't convert events but just pass-through */ + client = snd_seq_kernel_client_get(my_client); + if (!client) + return -EINVAL; + client->filter = SNDRV_SEQ_FILTER_NO_CONVERT; + snd_seq_kernel_client_put(client); + /* create ports */ for (i = 0; i < ports; i++) { rec1 = create_port(i, 0); -- cgit v1.2.3 From 81fd444aa371261cd33f31d4ffd80faeeeab0cc9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:53 +0200 Subject: ALSA: seq: Bind UMP device This patch introduces a new ALSA sequencer client for the kernel UMP object, snd-seq-ump-client. It's a UMP version of snd-seq-midi driver, while this driver creates a sequencer client per UMP endpoint which contains (fixed) 16 ports. The UMP rawmidi device is opened in APPEND mode for output, so that multiple sequencer clients can share the same UMP endpoint, as well as the legacy UMP rawmidi devices that are opened in APPEND mode, too. For input, on the other hand, the incoming data is processed on the fly in the dedicated hook, hence it doesn't open a rawmidi device. The UMP packet group is updated upon delivery depending on the target sequencer port (which corresponds to the actual UMP group). Each sequencer port sets a new port type bit, SNDRV_SEQ_PORT_TYPE_MIDI_UMP, in addition to the other standard types for MIDI. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-33-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/seq_device.h | 1 + include/sound/ump.h | 15 +- include/uapi/sound/asequencer.h | 1 + sound/core/seq/Kconfig | 5 + sound/core/seq/Makefile | 2 + sound/core/seq/seq_ump_client.c | 389 ++++++++++++++++++++++++++++++++++++++++ sound/core/ump.c | 28 ++- 7 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 sound/core/seq/seq_ump_client.c (limited to 'sound') diff --git a/include/sound/seq_device.h b/include/sound/seq_device.h index 8899affe9155..dead74b022f4 100644 --- a/include/sound/seq_device.h +++ b/include/sound/seq_device.h @@ -78,5 +78,6 @@ void snd_seq_driver_unregister(struct snd_seq_driver *drv); */ #define SNDRV_SEQ_DEV_ID_MIDISYNTH "seq-midi" #define SNDRV_SEQ_DEV_ID_OPL3 "opl3-synth" +#define SNDRV_SEQ_DEV_ID_UMP "seq-ump-client" #endif /* __SOUND_SEQ_DEVICE_H */ diff --git a/include/sound/ump.h b/include/sound/ump.h index 45f4c9b673b5..e4fdf7cccf12 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -11,6 +11,7 @@ struct snd_ump_endpoint; struct snd_ump_block; struct snd_ump_ops; struct ump_cvt_to_ump; +struct snd_seq_ump_ops; struct snd_ump_endpoint { struct snd_rawmidi core; /* raw UMP access */ @@ -30,9 +31,9 @@ struct snd_ump_endpoint { int input_buf_head; int input_pending; -#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) struct mutex open_mutex; +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) spinlock_t legacy_locks[2]; struct snd_rawmidi *legacy_rmidi; struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS]; @@ -42,6 +43,12 @@ struct snd_ump_endpoint { struct snd_rawmidi_file legacy_out_rfile; struct ump_cvt_to_ump *out_cvts; #endif + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + struct snd_seq_device *seq_dev; + const struct snd_seq_ump_ops *seq_ops; + void *seq_client; +#endif }; /* ops filled by UMP drivers */ @@ -52,6 +59,12 @@ struct snd_ump_ops { void (*drain)(struct snd_ump_endpoint *ump, int dir); }; +/* ops filled by sequencer binding */ +struct snd_seq_ump_ops { + void (*input_receive)(struct snd_ump_endpoint *ump, + const u32 *data, int words); +}; + struct snd_ump_block { struct snd_ump_block_info info; struct snd_ump_endpoint *ump; diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index c4632bd9d3a0..3fa6b17aa7a2 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -439,6 +439,7 @@ struct snd_seq_remove_events { #define SNDRV_SEQ_PORT_TYPE_MIDI_XG (1<<4) /* XG compatible device */ #define SNDRV_SEQ_PORT_TYPE_MIDI_MT32 (1<<5) /* MT-32 compatible device */ #define SNDRV_SEQ_PORT_TYPE_MIDI_GM2 (1<<6) /* General MIDI 2 compatible device */ +#define SNDRV_SEQ_PORT_TYPE_MIDI_UMP (1<<7) /* UMP */ /* other standards...*/ #define SNDRV_SEQ_PORT_TYPE_SYNTH (1<<10) /* Synth device (no MIDI compatible - direct wavetable) */ diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig index f8336134153e..c14981daf943 100644 --- a/sound/core/seq/Kconfig +++ b/sound/core/seq/Kconfig @@ -62,6 +62,7 @@ config SND_SEQ_VIRMIDI config SND_SEQ_UMP bool "Support for UMP events" + default y if SND_SEQ_UMP_CLIENT help Say Y here to enable the support for handling UMP (Universal MIDI Packet) events via ALSA sequencer infrastructure, which is an @@ -69,4 +70,8 @@ config SND_SEQ_UMP It includes the automatic conversion of ALSA sequencer events among legacy and UMP clients. +config SND_SEQ_UMP_CLIENT + tristate + def_tristate SND_UMP + endif # SND_SEQUENCER diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile index ba264a695643..990eec7c83ad 100644 --- a/sound/core/seq/Makefile +++ b/sound/core/seq/Makefile @@ -14,12 +14,14 @@ snd-seq-midi-emul-objs := seq_midi_emul.o snd-seq-midi-event-objs := seq_midi_event.o snd-seq-dummy-objs := seq_dummy.o snd-seq-virmidi-objs := seq_virmidi.o +snd-seq-ump-client-objs := seq_ump_client.o obj-$(CONFIG_SND_SEQUENCER) += snd-seq.o obj-$(CONFIG_SND_SEQUENCER_OSS) += oss/ obj-$(CONFIG_SND_SEQ_DUMMY) += snd-seq-dummy.o obj-$(CONFIG_SND_SEQ_MIDI) += snd-seq-midi.o +obj-$(CONFIG_SND_SEQ_UMP_CLIENT) += snd-seq-ump-client.o obj-$(CONFIG_SND_SEQ_MIDI_EMUL) += snd-seq-midi-emul.o obj-$(CONFIG_SND_SEQ_MIDI_EVENT) += snd-seq-midi-event.o obj-$(CONFIG_SND_SEQ_VIRMIDI) += snd-seq-virmidi.o diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c new file mode 100644 index 000000000000..8d360655ff5d --- /dev/null +++ b/sound/core/seq/seq_ump_client.c @@ -0,0 +1,389 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* ALSA sequencer binding for UMP device */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "seq_clientmgr.h" + +struct seq_ump_client; +struct seq_ump_group; + +enum { + STR_IN = SNDRV_RAWMIDI_STREAM_INPUT, + STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT +}; + +/* object per UMP group; corresponding to a sequencer port */ +struct seq_ump_group { + int group; /* group index (0-based) */ + unsigned int dir_bits; /* directions */ + bool active; /* activeness */ + char name[64]; /* seq port name */ +}; + +/* context for UMP input parsing, per EP */ +struct seq_ump_input_buffer { + unsigned char len; /* total length in words */ + unsigned char pending; /* pending words */ + unsigned char type; /* parsed UMP packet type */ + unsigned char group; /* parsed UMP packet group */ + u32 buf[4]; /* incoming UMP packet */ +}; + +/* sequencer client, per UMP EP (rawmidi) */ +struct seq_ump_client { + struct snd_ump_endpoint *ump; /* assigned endpoint */ + int seq_client; /* sequencer client id */ + int opened[2]; /* current opens for each direction */ + struct snd_rawmidi_file out_rfile; /* rawmidi for output */ + struct seq_ump_input_buffer input; /* input parser context */ + struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ +}; + +/* number of 32bit words for each UMP message type */ +static unsigned char ump_packet_words[0x10] = { + 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 +}; + +/* conversion between UMP group and seq port; + * assume the port number is equal with UMP group number (1-based) + */ +static unsigned char ump_group_to_seq_port(unsigned char group) +{ + return group + 1; +} + +/* process the incoming rawmidi stream */ +static void seq_ump_input_receive(struct snd_ump_endpoint *ump, + const u32 *val, int words) +{ + struct seq_ump_client *client = ump->seq_client; + struct snd_seq_ump_event ev = {}; + + if (!client->opened[STR_IN]) + return; + + ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); + ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; + ev.flags = SNDRV_SEQ_EVENT_UMP; + memcpy(ev.ump, val, words << 2); + snd_seq_kernel_client_dispatch(client->seq_client, + (struct snd_seq_event *)&ev, + true, 0); +} + +/* process an input sequencer event; only deal with UMP types */ +static int seq_ump_process_event(struct snd_seq_event *ev, int direct, + void *private_data, int atomic, int hop) +{ + struct seq_ump_client *client = private_data; + struct snd_rawmidi_substream *substream; + struct snd_seq_ump_event *ump_ev; + unsigned char type; + int len; + + substream = client->out_rfile.output; + if (!substream) + return -ENODEV; + if (!snd_seq_ev_is_ump(ev)) + return 0; /* invalid event, skip */ + ump_ev = (struct snd_seq_ump_event *)ev; + type = ump_message_type(ump_ev->ump[0]); + len = ump_packet_words[type]; + if (len > 4) + return 0; // invalid - skip + snd_rawmidi_kernel_write(substream, ev->data.raw8.d, len << 2); + return 0; +} + +/* open the rawmidi */ +static int seq_ump_client_open(struct seq_ump_client *client, int dir) +{ + struct snd_ump_endpoint *ump = client->ump; + int err = 0; + + mutex_lock(&ump->open_mutex); + if (dir == STR_OUT && !client->opened[dir]) { + err = snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT | + SNDRV_RAWMIDI_LFLG_APPEND, + &client->out_rfile); + if (err < 0) + goto unlock; + } + client->opened[dir]++; + unlock: + mutex_unlock(&ump->open_mutex); + return err; +} + +/* close the rawmidi */ +static int seq_ump_client_close(struct seq_ump_client *client, int dir) +{ + struct snd_ump_endpoint *ump = client->ump; + + mutex_lock(&ump->open_mutex); + if (!--client->opened[dir]) + if (dir == STR_OUT) + snd_rawmidi_kernel_release(&client->out_rfile); + mutex_unlock(&ump->open_mutex); + return 0; +} + +/* sequencer subscription ops for each client */ +static int seq_ump_subscribe(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_open(client, STR_IN); +} + +static int seq_ump_unsubscribe(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_close(client, STR_IN); +} + +static int seq_ump_use(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_open(client, STR_OUT); +} + +static int seq_ump_unuse(void *pdata, struct snd_seq_port_subscribe *info) +{ + struct seq_ump_client *client = pdata; + + return seq_ump_client_close(client, STR_OUT); +} + +/* fill port_info from the given UMP EP and group info */ +static void fill_port_info(struct snd_seq_port_info *port, + struct seq_ump_client *client, + struct seq_ump_group *group) +{ + unsigned int rawmidi_info = client->ump->core.info_flags; + + port->addr.client = client->seq_client; + port->addr.port = ump_group_to_seq_port(group->group); + port->capability = 0; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SYNC_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) + port->capability |= SNDRV_SEQ_PORT_CAP_READ | + SNDRV_SEQ_PORT_CAP_SYNC_READ | + SNDRV_SEQ_PORT_CAP_SUBS_READ; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + if (group->dir_bits & (1 << STR_IN)) + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + if (group->dir_bits & (1 << STR_OUT)) + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + port->ump_group = group->group + 1; + if (!group->active) + port->capability |= SNDRV_SEQ_PORT_CAP_INACTIVE; + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_MIDI_UMP | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + if (*group->name) + snprintf(port->name, sizeof(port->name), "Group %d (%s)", + group->group + 1, group->name); + else + sprintf(port->name, "Group %d", group->group + 1); +} + +/* create a new sequencer port per UMP group */ +static int seq_ump_group_init(struct seq_ump_client *client, int group_index) +{ + struct seq_ump_group *group = &client->groups[group_index]; + struct snd_seq_port_info *port; + struct snd_seq_port_callback pcallbacks; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + err = -ENOMEM; + goto error; + } + + fill_port_info(port, client, group); + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = client; + pcallbacks.subscribe = seq_ump_subscribe; + pcallbacks.unsubscribe = seq_ump_unsubscribe; + pcallbacks.use = seq_ump_use; + pcallbacks.unuse = seq_ump_unuse; + pcallbacks.event_input = seq_ump_process_event; + port->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + error: + kfree(port); + return err; +} + +/* update dir_bits and active flag for all groups in the client */ +static void update_group_attrs(struct seq_ump_client *client) +{ + struct snd_ump_block *fb; + struct seq_ump_group *group; + int i; + + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { + group = &client->groups[i]; + *group->name = 0; + group->dir_bits = 0; + group->active = 0; + group->group = i; + } + + list_for_each_entry(fb, &client->ump->block_list, list) { + if (fb->info.first_group < 0 || + fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS) + break; + group = &client->groups[fb->info.first_group]; + for (i = 0; i < fb->info.num_groups; i++, group++) { + if (fb->info.active) + group->active = 1; + switch (fb->info.direction) { + case SNDRV_UMP_DIR_INPUT: + group->dir_bits |= (1 << STR_IN); + break; + case SNDRV_UMP_DIR_OUTPUT: + group->dir_bits |= (1 << STR_OUT); + break; + case SNDRV_UMP_DIR_BIDIRECTION: + group->dir_bits |= (1 << STR_OUT) | (1 << STR_IN); + break; + } + if (!*fb->info.name) + continue; + if (!*group->name) { + /* store the first matching name */ + strscpy(group->name, fb->info.name, + sizeof(group->name)); + } else { + /* when overlapping, concat names */ + strlcat(group->name, ", ", sizeof(group->name)); + strlcat(group->name, fb->info.name, + sizeof(group->name)); + } + } + } +} + +/* release the client resources */ +static void seq_ump_client_free(struct seq_ump_client *client) +{ + if (client->seq_client >= 0) + snd_seq_delete_kernel_client(client->seq_client); + + client->ump->seq_ops = NULL; + client->ump->seq_client = NULL; + + kfree(client); +} + +/* update the MIDI version for the given client */ +static void setup_client_midi_version(struct seq_ump_client *client) +{ + struct snd_seq_client *cptr; + + cptr = snd_seq_kernel_client_get(client->seq_client); + if (!cptr) + return; + if (client->ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI2) + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_2_0; + else + cptr->midi_version = SNDRV_SEQ_CLIENT_UMP_MIDI_1_0; + snd_seq_kernel_client_put(cptr); +} + +static const struct snd_seq_ump_ops seq_ump_ops = { + .input_receive = seq_ump_input_receive, +}; + +/* create a sequencer client and ports for the given UMP endpoint */ +static int snd_seq_ump_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_ump_endpoint *ump = dev->private_data; + struct snd_card *card = dev->card; + struct seq_ump_client *client; + int p, err; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + + client->ump = ump; + + client->seq_client = + snd_seq_create_kernel_client(card, ump->core.device, + ump->core.name); + if (client->seq_client < 0) { + err = client->seq_client; + goto error; + } + + setup_client_midi_version(client); + update_group_attrs(client); + + for (p = 0; p < SNDRV_UMP_MAX_GROUPS; p++) { + err = seq_ump_group_init(client, p); + if (err < 0) + goto error; + } + + ump->seq_client = client; + ump->seq_ops = &seq_ump_ops; + return 0; + + error: + seq_ump_client_free(client); + return err; +} + +/* remove a sequencer client */ +static int snd_seq_ump_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_ump_endpoint *ump = dev->private_data; + + if (ump->seq_client) + seq_ump_client_free(ump->seq_client); + return 0; +} + +static struct snd_seq_driver seq_ump_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_seq_ump_probe, + .remove = snd_seq_ump_remove, + }, + .id = SNDRV_SEQ_DEV_ID_UMP, + .argsize = 0, +}; + +module_snd_seq_driver(seq_ump_driver); + +MODULE_DESCRIPTION("ALSA sequencer client for UMP rawmidi"); +MODULE_LICENSE("GPL"); diff --git a/sound/core/ump.c b/sound/core/ump.c index cbe704b5d90d..69993cad6772 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -132,8 +132,8 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, if (!ump) return -ENOMEM; INIT_LIST_HEAD(&ump->block_list); -#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) mutex_init(&ump->open_mutex); +#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) spin_lock_init(&ump->legacy_locks[0]); spin_lock_init(&ump->legacy_locks[1]); #endif @@ -166,8 +166,30 @@ EXPORT_SYMBOL_GPL(snd_ump_endpoint_new); * Device register / unregister hooks; * do nothing, placeholders for avoiding the default rawmidi handling */ + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +static void snd_ump_dev_seq_free(struct snd_seq_device *device) +{ + struct snd_ump_endpoint *ump = device->private_data; + + ump->seq_dev = NULL; +} +#endif + static int snd_ump_dev_register(struct snd_rawmidi *rmidi) { +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi); + int err; + + err = snd_seq_device_new(ump->core.card, ump->core.device, + SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev); + if (err < 0) + return err; + ump->seq_dev->private_data = ump; + ump->seq_dev->private_free = snd_ump_dev_seq_free; + snd_device_register(ump->core.card, ump->seq_dev); +#endif return 0; } @@ -280,6 +302,10 @@ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) n = snd_ump_receive_ump_val(ump, *p++); if (!n) continue; +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops) + ump->seq_ops->input_receive(ump, ump->input_buf, n); +#endif process_legacy_input(ump, ump->input_buf, n); } -- cgit v1.2.3 From 4025f0e627e127c79d372c6227b9a200406329f9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:54 +0200 Subject: ALSA: seq: ump: Create UMP Endpoint port for broadcast Create a sequencer port for broadcasting the all group inputs at the port number 0. This corresponds to a UMP Endpoint connection; application can read all UMP events from this port no matter which group the UMP packet belongs to. Unlike seq ports for other UMP groups, a UMP Endpoint port has no SND_SEQ_PORT_TYPE_MIDI_GENERIC bit, so that it won't be treated as a normal MIDI 1.0 device from legacy applications. The port is named as "MIDI 2.0" to align with representations on other operation systems. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-34-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_ump_client.c | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 8d360655ff5d..600b061ac8c3 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -290,6 +290,62 @@ static void update_group_attrs(struct seq_ump_client *client) } } +/* create a UMP Endpoint port */ +static int create_ump_endpoint_port(struct seq_ump_client *client) +{ + struct snd_seq_port_info *port; + struct snd_seq_port_callback pcallbacks; + unsigned int rawmidi_info = client->ump->core.info_flags; + int err; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + port->addr.client = client->seq_client; + port->addr.port = 0; /* fixed */ + port->flags = SNDRV_SEQ_PORT_FLG_GIVEN_PORT; + port->capability = SNDRV_SEQ_PORT_CAP_UMP_ENDPOINT; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) { + port->capability |= SNDRV_SEQ_PORT_CAP_READ | + SNDRV_SEQ_PORT_CAP_SYNC_READ | + SNDRV_SEQ_PORT_CAP_SUBS_READ; + port->direction |= SNDRV_SEQ_PORT_DIR_INPUT; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) { + port->capability |= SNDRV_SEQ_PORT_CAP_WRITE | + SNDRV_SEQ_PORT_CAP_SYNC_WRITE | + SNDRV_SEQ_PORT_CAP_SUBS_WRITE; + port->direction |= SNDRV_SEQ_PORT_DIR_OUTPUT; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_DUPLEX) + port->capability |= SNDRV_SEQ_PORT_CAP_DUPLEX; + port->ump_group = 0; /* no associated group, no conversion */ + port->type = SNDRV_SEQ_PORT_TYPE_MIDI_UMP | + SNDRV_SEQ_PORT_TYPE_HARDWARE | + SNDRV_SEQ_PORT_TYPE_PORT; + port->midi_channels = 16; + strcpy(port->name, "MIDI 2.0"); + memset(&pcallbacks, 0, sizeof(pcallbacks)); + pcallbacks.owner = THIS_MODULE; + pcallbacks.private_data = client; + if (rawmidi_info & SNDRV_RAWMIDI_INFO_INPUT) { + pcallbacks.subscribe = seq_ump_subscribe; + pcallbacks.unsubscribe = seq_ump_unsubscribe; + } + if (rawmidi_info & SNDRV_RAWMIDI_INFO_OUTPUT) { + pcallbacks.use = seq_ump_use; + pcallbacks.unuse = seq_ump_unuse; + pcallbacks.event_input = seq_ump_process_event; + } + port->kernel = &pcallbacks; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_CREATE_PORT, + port); + kfree(port); + return err; +} + /* release the client resources */ static void seq_ump_client_free(struct seq_ump_client *client) { @@ -353,6 +409,10 @@ static int snd_seq_ump_probe(struct device *_dev) goto error; } + err = create_ump_endpoint_port(client); + if (err < 0) + goto error; + ump->seq_client = client; ump->seq_ops = &seq_ump_ops; return 0; -- cgit v1.2.3 From d2d247e35eeea8331150d7708211a013aabccb5b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:55 +0200 Subject: ALSA: seq: Add ioctls for client UMP info query and setup Add new ioctls for sequencer clients to query and set the UMP endpoint and block information. As a sequencer client corresponds to a UMP Endpoint, one UMP Endpoint information can be assigned at most to a single sequencer client while multiple UMP block infos can be assigned by passing the type with the offset of block id (i.e. type = block_id + 1). For the kernel client, only SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO is allowed. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-35-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 14 +++++ sound/core/seq/seq_clientmgr.c | 120 +++++++++++++++++++++++++++++++++++++++- sound/core/seq/seq_clientmgr.h | 4 +- sound/core/seq/seq_compat.c | 2 + sound/core/seq/seq_ump_client.c | 15 +++++ 5 files changed, 153 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 3fa6b17aa7a2..c75f594f21e3 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -585,6 +585,18 @@ struct snd_seq_query_subs { char reserved[64]; /* for future use */ }; +/* + * UMP-specific information + */ +/* type of UMP info query */ +#define SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT 0 +#define SNDRV_SEQ_CLIENT_UMP_INFO_BLOCK 1 + +struct snd_seq_client_ump_info { + int client; /* client number to inquire/set */ + int type; /* type to inquire/set */ + unsigned char info[512]; /* info (either UMP ep or block info) */ +} __packed; /* * IOCTL commands @@ -598,6 +610,8 @@ struct snd_seq_query_subs { #define SNDRV_SEQ_IOCTL_GET_CLIENT_INFO _IOWR('S', 0x10, struct snd_seq_client_info) #define SNDRV_SEQ_IOCTL_SET_CLIENT_INFO _IOW ('S', 0x11, struct snd_seq_client_info) +#define SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO _IOWR('S', 0x12, struct snd_seq_client_ump_info) +#define SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO _IOWR('S', 0x13, struct snd_seq_client_ump_info) #define SNDRV_SEQ_IOCTL_CREATE_PORT _IOWR('S', 0x20, struct snd_seq_port_info) #define SNDRV_SEQ_IOCTL_DELETE_PORT _IOW ('S', 0x21, struct snd_seq_port_info) diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 3b1adcb1ccdd..03ca78ea2cce 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -14,6 +14,7 @@ #include #include +#include #include "seq_clientmgr.h" #include "seq_memory.h" #include "seq_queue.h" @@ -71,6 +72,10 @@ static int snd_seq_deliver_single_event(struct snd_seq_client *client, struct snd_seq_event *event, int filter, int atomic, int hop); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) +static void free_ump_info(struct snd_seq_client *client); +#endif + /* */ static inline unsigned short snd_seq_file_flags(struct file *file) @@ -382,6 +387,9 @@ static int snd_seq_release(struct inode *inode, struct file *file) seq_free_client(client); if (client->data.user.fifo) snd_seq_fifo_delete(&client->data.user.fifo); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + free_ump_info(client); +#endif put_pid(client->data.user.owner); kfree(client); } @@ -1282,7 +1290,6 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) client->midi_version = client_info->midi_version; memcpy(client->event_filter, client_info->event_filter, 32); - return 0; } @@ -2087,6 +2094,108 @@ static int snd_seq_ioctl_query_next_port(struct snd_seq_client *client, return 0; } +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) +#define NUM_UMP_INFOS (SNDRV_UMP_MAX_BLOCKS + 1) + +static void free_ump_info(struct snd_seq_client *client) +{ + int i; + + if (!client->ump_info) + return; + for (i = 0; i < NUM_UMP_INFOS; i++) + kfree(client->ump_info[i]); + kfree(client->ump_info); + client->ump_info = NULL; +} + +static void terminate_ump_info_strings(void *p, int type) +{ + if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) { + struct snd_ump_endpoint_info *ep = p; + ep->name[sizeof(ep->name) - 1] = 0; + } else { + struct snd_ump_block_info *bp = p; + bp->name[sizeof(bp->name) - 1] = 0; + } +} + +/* UMP-specific ioctls -- called directly without data copy */ +static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller, + unsigned int cmd, + unsigned long arg) +{ + struct snd_seq_client_ump_info __user *argp = + (struct snd_seq_client_ump_info __user *)arg; + struct snd_seq_client *cptr; + int client, type, err = 0; + size_t size; + void *p; + + if (get_user(client, &argp->client) || get_user(type, &argp->type)) + return -EFAULT; + if (cmd == SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO && + caller->number != client) + return -EPERM; + if (type < 0 || type >= NUM_UMP_INFOS) + return -EINVAL; + if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT) + size = sizeof(struct snd_ump_endpoint_info); + else + size = sizeof(struct snd_ump_block_info); + cptr = snd_seq_client_use_ptr(client); + if (!cptr) + return -ENOENT; + + mutex_lock(&cptr->ioctl_mutex); + if (!cptr->midi_version) { + err = -EBADFD; + goto error; + } + + if (cmd == SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO) { + if (!cptr->ump_info) + p = NULL; + else + p = cptr->ump_info[type]; + if (!p) { + err = -ENODEV; + goto error; + } + if (copy_to_user(argp->info, p, size)) { + err = -EFAULT; + goto error; + } + } else { + if (cptr->type != USER_CLIENT) { + err = -EBADFD; + goto error; + } + if (!cptr->ump_info) { + cptr->ump_info = kcalloc(NUM_UMP_INFOS, + sizeof(void *), GFP_KERNEL); + if (!cptr->ump_info) { + err = -ENOMEM; + goto error; + } + } + p = memdup_user(argp->info, size); + if (IS_ERR(p)) { + err = PTR_ERR(p); + goto error; + } + kfree(cptr->ump_info[type]); + terminate_ump_info_strings(p, type); + cptr->ump_info[type] = p; + } + + error: + mutex_unlock(&cptr->ioctl_mutex); + snd_seq_client_unlock(cptr); + return err; +} +#endif + /* -------------------------------------------------------- */ static const struct ioctl_handler { @@ -2157,6 +2266,15 @@ static long snd_seq_ioctl(struct file *file, unsigned int cmd, if (snd_BUG_ON(!client)) return -ENXIO; +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + /* exception - handling large data */ + switch (cmd) { + case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO: + return snd_seq_ioctl_client_ump_info(client, cmd, arg); + } +#endif + for (handler = ioctl_handlers; handler->cmd > 0; ++handler) { if (handler->cmd == cmd) break; diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index 97762892ffab..be3fe555f233 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -12,7 +12,6 @@ #include "seq_ports.h" #include "seq_lock.h" - /* client manager */ struct snd_seq_user_client { @@ -59,6 +58,9 @@ struct snd_seq_client { struct snd_seq_user_client user; struct snd_seq_kernel_client kernel; } data; + + /* for UMP */ + void **ump_info; }; /* usage statistics */ diff --git a/sound/core/seq/seq_compat.c b/sound/core/seq/seq_compat.c index c0ce6236dc7f..1e35bf086a51 100644 --- a/sound/core/seq/seq_compat.c +++ b/sound/core/seq/seq_compat.c @@ -86,6 +86,8 @@ static long snd_seq_ioctl_compat(struct file *file, unsigned int cmd, unsigned l case SNDRV_SEQ_IOCTL_SYSTEM_INFO: case SNDRV_SEQ_IOCTL_GET_CLIENT_INFO: case SNDRV_SEQ_IOCTL_SET_CLIENT_INFO: + case SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO: + case SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO: case SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT: case SNDRV_SEQ_IOCTL_UNSUBSCRIBE_PORT: case SNDRV_SEQ_IOCTL_CREATE_QUEUE: diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 600b061ac8c3..e24833804094 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -47,6 +47,7 @@ struct seq_ump_client { struct snd_rawmidi_file out_rfile; /* rawmidi for output */ struct seq_ump_input_buffer input; /* input parser context */ struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ + void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ }; /* number of 32bit words for each UMP message type */ @@ -384,6 +385,8 @@ static int snd_seq_ump_probe(struct device *_dev) struct snd_ump_endpoint *ump = dev->private_data; struct snd_card *card = dev->card; struct seq_ump_client *client; + struct snd_ump_block *fb; + struct snd_seq_client *cptr; int p, err; client = kzalloc(sizeof(*client), GFP_KERNEL); @@ -400,6 +403,10 @@ static int snd_seq_ump_probe(struct device *_dev) goto error; } + client->ump_info[0] = &ump->info; + list_for_each_entry(fb, &ump->block_list, list) + client->ump_info[fb->info.block_id + 1] = &fb->info; + setup_client_midi_version(client); update_group_attrs(client); @@ -413,6 +420,14 @@ static int snd_seq_ump_probe(struct device *_dev) if (err < 0) goto error; + cptr = snd_seq_kernel_client_get(client->seq_client); + if (!cptr) { + err = -EINVAL; + goto error; + } + cptr->ump_info = client->ump_info; + snd_seq_kernel_client_put(cptr); + ump->seq_client = client; ump->seq_ops = &seq_ump_ops; return 0; -- cgit v1.2.3 From e85b9260569dc3893bc084ec18ef48e199525ef8 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:56 +0200 Subject: ALSA: seq: Print UMP Endpoint and Block information in proc outputs This patch enhances the /proc/asound/seq/clients output to show a few more information about the assigned UMP Endpoint and Blocks. The "Groups" are shown in 1-based group number to align with the sequencer client name and port number. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-36-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 03ca78ea2cce..8cce8061ca55 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -2120,6 +2120,33 @@ static void terminate_ump_info_strings(void *p, int type) } } +#ifdef CONFIG_SND_PROC_FS +static void dump_ump_info(struct snd_info_buffer *buffer, + struct snd_seq_client *client) +{ + struct snd_ump_endpoint_info *ep; + struct snd_ump_block_info *bp; + int i; + + if (!client->ump_info) + return; + ep = client->ump_info[SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT]; + if (ep && *ep->name) + snd_iprintf(buffer, " UMP Endpoint: \"%s\"\n", ep->name); + for (i = 0; i < SNDRV_UMP_MAX_BLOCKS; i++) { + bp = client->ump_info[i + 1]; + if (bp && *bp->name) { + snd_iprintf(buffer, " UMP Block %d: \"%s\" [%s]\n", + i, bp->name, + bp->active ? "Active" : "Inactive"); + snd_iprintf(buffer, " Groups: %d-%d\n", + bp->first_group + 1, + bp->first_group + bp->num_groups); + } + } +} +#endif + /* UMP-specific ioctls -- called directly without data copy */ static int snd_seq_ioctl_client_ump_info(struct snd_seq_client *caller, unsigned int cmd, @@ -2654,6 +2681,9 @@ void snd_seq_info_clients_read(struct snd_info_entry *entry, c, client->name, client->type == USER_CLIENT ? "User" : "Kernel", midi_version_string(client->midi_version)); +#if IS_ENABLED(CONFIG_SND_SEQ_UMP) + dump_ump_info(buffer, client); +#endif snd_seq_info_dump_ports(buffer, client); if (snd_seq_write_pool_allocated(client)) { snd_iprintf(buffer, " Output pool :\n"); -- cgit v1.2.3 From d2b706077792a366fac8c1db2f1b4406ad7da482 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 May 2023 09:53:57 +0200 Subject: ALSA: seq: Add UMP group filter Add a new filter bitmap for UMP groups for reducing the unnecessary read/write when the client is connected to UMP EP seq port. The new group_filter field contains the bitmap for the groups, i.e. when the bit is set, the corresponding group is filtered out and the messages to that group won't be delivered. The filter bitmap consists of each bit of 1-based UMP Group number. The bit 0 is reserved for the future use. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230523075358.9672-37-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asequencer.h | 3 ++- sound/core/seq/seq_clientmgr.c | 2 ++ sound/core/seq/seq_clientmgr.h | 1 + sound/core/seq/seq_ump_convert.c | 13 +++++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index c75f594f21e3..5e91243665d8 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -362,7 +362,8 @@ struct snd_seq_client_info { int card; /* RO: card number[kernel] */ int pid; /* RO: pid[user] */ unsigned int midi_version; /* MIDI version */ - char reserved[52]; /* for future use */ + unsigned int group_filter; /* UMP group filter bitmap (for 1-based Group indices) */ + char reserved[48]; /* for future use */ }; /* MIDI version numbers in client info */ diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 8cce8061ca55..948ae45e0cc3 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -1229,6 +1229,7 @@ static void get_client_info(struct snd_seq_client *cptr, info->filter = cptr->filter; info->event_lost = cptr->event_lost; memcpy(info->event_filter, cptr->event_filter, 32); + info->group_filter = cptr->group_filter; info->num_ports = cptr->num_ports; if (cptr->type == USER_CLIENT) @@ -1290,6 +1291,7 @@ static int snd_seq_ioctl_set_client_info(struct snd_seq_client *client, if (client->user_pversion >= SNDRV_PROTOCOL_VERSION(1, 0, 3)) client->midi_version = client_info->midi_version; memcpy(client->event_filter, client_info->event_filter, 32); + client->group_filter = client_info->group_filter; return 0; } diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h index be3fe555f233..915b1017286e 100644 --- a/sound/core/seq/seq_clientmgr.h +++ b/sound/core/seq/seq_clientmgr.h @@ -40,6 +40,7 @@ struct snd_seq_client { int number; /* client number */ unsigned int filter; /* filter flags */ DECLARE_BITMAP(event_filter, 256); + unsigned short group_filter; snd_use_lock_t use_lock; int event_lost; /* ports */ diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index 433fe842947e..14ba6fed9dd1 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -527,6 +527,17 @@ static int deliver_with_group_convert(struct snd_seq_client *dest, atomic, hop); } +/* apply the UMP event filter; return true to skip the event */ +static bool ump_event_filtered(struct snd_seq_client *dest, + const struct snd_seq_ump_event *ev) +{ + unsigned char group; + + group = ump_message_group(ev->ump[0]); + /* check the bitmap for 1-based group number */ + return dest->group_filter & (1U << (group + 1)); +} + /* Convert from UMP packet and deliver */ int snd_seq_deliver_from_ump(struct snd_seq_client *source, struct snd_seq_client *dest, @@ -539,6 +550,8 @@ int snd_seq_deliver_from_ump(struct snd_seq_client *source, if (snd_seq_ev_is_variable(event)) return 0; // skip, no variable event for UMP, so far + if (ump_event_filtered(dest, ump_ev)) + return 0; // skip if group filter is set and matching type = ump_message_type(ump_ev->ump[0]); if (snd_seq_client_is_ump(dest)) { -- cgit v1.2.3 From f5192e33810afde77cf8cba75f70af4348577fba Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 12:46:11 +0200 Subject: ALSA: emu10k1: introduce higher-level voice manipulation functions This adds snd_emu10k1_pcm_init_{voices,extra_voice}() and snd_emu10k1_playback_{un,}mute_voices() to slightly abstract by voice function and potential stereo property. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523104612.198884-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 70 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 17 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0036593cca7c..65af94d08b47 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -312,6 +312,30 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, spin_unlock_irqrestore(&emu->reg_lock, flags); } +static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool w_16, bool stereo, + unsigned int start_addr, + unsigned int end_addr, + struct snd_emu10k1_pcm_mixer *mix) +{ + snd_emu10k1_pcm_init_voice(emu, 1, 0, evoice, w_16, stereo, + start_addr, end_addr, mix); + if (stereo) + snd_emu10k1_pcm_init_voice(emu, 0, 0, evoice + 1, w_16, true, + start_addr, end_addr, mix); +} + +static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool w_16, + unsigned int start_addr, + unsigned int end_addr) +{ + snd_emu10k1_pcm_init_voice(emu, 1, 1, evoice, w_16, false, + start_addr, end_addr, NULL); +} + static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { @@ -393,18 +417,15 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) start_addr = epcm->start_addr >> w_16; end_addr = start_addr + runtime->period_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, w_16, false, - start_addr, end_addr, NULL); + snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, w_16, + start_addr, end_addr); start_addr >>= stereo; epcm->ccca_start_addr = start_addr; end_addr = start_addr + runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 0, epcm->voices[0], w_16, stereo, - start_addr, end_addr, - &emu->pcm_mixer[substream->number]); - if (stereo) - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[0] + 1, w_16, true, - start_addr, end_addr, - &emu->pcm_mixer[substream->number]); + snd_emu10k1_pcm_init_voices(emu, epcm->voices[0], w_16, stereo, + start_addr, end_addr, + &emu->pcm_mixer[substream->number]); + return 0; } @@ -421,8 +442,8 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) channel_size = runtime->buffer_size; - snd_emu10k1_pcm_init_voice(emu, 1, 1, epcm->extra, true, false, - start_addr, start_addr + (channel_size / 2), NULL); + snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, true, + start_addr, start_addr + (channel_size / 2)); epcm->ccca_start_addr = start_addr; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { @@ -598,12 +619,31 @@ static void snd_emu10k1_playback_unmute_voice(struct snd_emu10k1 *emu, snd_emu10k1_playback_commit_volume(emu, evoice, vattn); } +static void snd_emu10k1_playback_unmute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool stereo, + struct snd_emu10k1_pcm_mixer *mix) +{ + snd_emu10k1_playback_unmute_voice(emu, evoice, stereo, true, mix); + if (stereo) + snd_emu10k1_playback_unmute_voice(emu, evoice + 1, true, false, mix); +} + static void snd_emu10k1_playback_mute_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { snd_emu10k1_playback_commit_volume(emu, evoice, 0); } +static void snd_emu10k1_playback_mute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_voice *evoice, + bool stereo) +{ + snd_emu10k1_playback_mute_voice(emu, evoice); + if (stereo) + snd_emu10k1_playback_mute_voice(emu, evoice + 1); +} + static void snd_emu10k1_playback_commit_pitch(struct snd_emu10k1 *emu, u32 voice, u32 pitch_target) { @@ -680,9 +720,7 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: mix = &emu->pcm_mixer[substream->number]; - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0], stereo, true, mix); - if (stereo) - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[0] + 1, true, false, mix); + snd_emu10k1_playback_unmute_voices(emu, epcm->voices[0], stereo, mix); snd_emu10k1_playback_set_running(emu, epcm); snd_emu10k1_playback_trigger_voice(emu, epcm->voices[0]); snd_emu10k1_playback_trigger_voice(emu, epcm->extra); @@ -693,9 +731,7 @@ static int snd_emu10k1_playback_trigger(struct snd_pcm_substream *substream, snd_emu10k1_playback_stop_voice(emu, epcm->voices[0]); snd_emu10k1_playback_stop_voice(emu, epcm->extra); snd_emu10k1_playback_set_stopped(emu, epcm); - snd_emu10k1_playback_mute_voice(emu, epcm->voices[0]); - if (stereo) - snd_emu10k1_playback_mute_voice(emu, epcm->voices[0] + 1); + snd_emu10k1_playback_mute_voices(emu, epcm->voices[0], stereo); break; default: result = -EINVAL; -- cgit v1.2.3 From 7195fb46dafb8750ed4055804cb131f376eb854e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 12:46:12 +0200 Subject: ALSA: emu10k1: pass raw FX send config to snd_emu10k1_pcm_init_voice() ... instead of passing in a high-level mixer struct. Let the higher-level functions handle the differences between the voice types. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523104612.198884-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 54 +++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 30 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 65af94d08b47..0572dfb80943 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -233,37 +233,21 @@ static u16 emu10k1_send_target_from_amount(u8 amount) } static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, - int master, int extra, struct snd_emu10k1_voice *evoice, bool w_16, bool stereo, unsigned int start_addr, unsigned int end_addr, - struct snd_emu10k1_pcm_mixer *mix) + const unsigned char *send_routing, + const unsigned char *send_amount) { struct snd_pcm_substream *substream = evoice->epcm->substream; struct snd_pcm_runtime *runtime = substream->runtime; - unsigned int silent_page, tmp; + unsigned int silent_page; int voice; - unsigned char send_amount[8]; - unsigned char send_routing[8]; - unsigned long flags; unsigned int pitch_target; voice = evoice->number; - spin_lock_irqsave(&emu->reg_lock, flags); - - /* volume parameters */ - if (extra) { - for (int i = 0; i < 8; i++) - send_routing[i] = i; - memset(send_amount, 0, sizeof(send_amount)); - } else { - /* mono, left, right (master voice = left) */ - tmp = stereo ? (master ? 1 : 2) : 0; - memcpy(send_routing, &mix->send_routing[tmp][0], 8); - memcpy(send_amount, &mix->send_volume[tmp][0], 8); - } if (emu->card_capabilities->emu_model) pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ else @@ -308,8 +292,6 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, } emu->voices[voice].dirty = 1; - - spin_unlock_irqrestore(&emu->reg_lock, flags); } static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu, @@ -319,11 +301,19 @@ static void snd_emu10k1_pcm_init_voices(struct snd_emu10k1 *emu, unsigned int end_addr, struct snd_emu10k1_pcm_mixer *mix) { - snd_emu10k1_pcm_init_voice(emu, 1, 0, evoice, w_16, stereo, - start_addr, end_addr, mix); + unsigned long flags; + + spin_lock_irqsave(&emu->reg_lock, flags); + snd_emu10k1_pcm_init_voice(emu, evoice, w_16, stereo, + start_addr, end_addr, + &mix->send_routing[stereo][0], + &mix->send_volume[stereo][0]); if (stereo) - snd_emu10k1_pcm_init_voice(emu, 0, 0, evoice + 1, w_16, true, - start_addr, end_addr, mix); + snd_emu10k1_pcm_init_voice(emu, evoice + 1, w_16, true, + start_addr, end_addr, + &mix->send_routing[2][0], + &mix->send_volume[2][0]); + spin_unlock_irqrestore(&emu->reg_lock, flags); } static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu, @@ -332,8 +322,12 @@ static void snd_emu10k1_pcm_init_extra_voice(struct snd_emu10k1 *emu, unsigned int start_addr, unsigned int end_addr) { - snd_emu10k1_pcm_init_voice(emu, 1, 1, evoice, w_16, false, - start_addr, end_addr, NULL); + static const unsigned char send_routing[8] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + static const unsigned char send_amount[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + snd_emu10k1_pcm_init_voice(emu, evoice, w_16, false, + start_addr, end_addr, + send_routing, send_amount); } static int snd_emu10k1_playback_hw_params(struct snd_pcm_substream *substream, @@ -447,9 +441,9 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) epcm->ccca_start_addr = start_addr; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_pcm_init_voice(emu, 0, 0, epcm->voices[i], true, false, - start_addr, start_addr + channel_size, - &emu->efx_pcm_mixer[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; } -- cgit v1.2.3 From 6dbecb9b51321bfaf8e7f26fc163d547abcbee86 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:07 +0200 Subject: ALSA: emu10k1: don't limit multi-channel playback to two periods For unclear reasons, the extra voice was set up with half the buffer size instead of the period size. Commit 27ae958cf6 ("emu10k1 driver - add multichannel device hw:x,3 [2-8/8]") mentions half-loop interrupts, so maybe this was an artifact of an earlier iteration of the patch. While at it, also fix periods_min of the regular playback - one period makes just no sense. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236023-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 0572dfb80943..2764e7867b33 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -429,15 +429,16 @@ 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; unsigned int start_addr; - unsigned int channel_size; + unsigned int extra_size, channel_size; int i; start_addr = epcm->start_addr >> 1; // 16-bit voices + extra_size = runtime->period_size; channel_size = runtime->buffer_size; snd_emu10k1_pcm_init_extra_voice(emu, epcm->extra, true, - start_addr, start_addr + (channel_size / 2)); + start_addr, start_addr + extra_size); epcm->ccca_start_addr = start_addr; for (i = 0; i < NUM_EFX_PLAYBACK; i++) { @@ -465,7 +466,7 @@ static const struct snd_pcm_hardware snd_emu10k1_efx_playback = .buffer_bytes_max = (128*1024), .period_bytes_max = (128*1024), .periods_min = 2, - .periods_max = 2, + .periods_max = 1024, .fifo_size = 0, }; @@ -925,7 +926,7 @@ static const struct snd_pcm_hardware snd_emu10k1_playback = .channels_max = 2, .buffer_bytes_max = (128*1024), .period_bytes_max = (128*1024), - .periods_min = 1, + .periods_min = 2, .periods_max = 1024, .fifo_size = 0, }; -- cgit v1.2.3 From 11ee59bdac36ae4b500301a6a3ccf586d3968d92 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:08 +0200 Subject: ALSA: emu10k1: add synchronized start of multi-channel playback We use independent voices for the channels, so we need to make an effort to ensure that they are actually in sync. The hardware doesn't provide atomicity, so we may need to retry a few times, due to NMIs, PCI contention, and the wrong phase of the moon. Solution inspired by kX-project. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236023-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 10 ++++- sound/pci/emu10k1/emupcm.c | 100 +++++++++++++++++++++++++++++++++++++-------- sound/pci/emu10k1/io.c | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 0780f39f4bb6..164a2374b4c2 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -422,7 +422,8 @@ SUB_REG(HCFG, LOCKTANKCACHE, 0x00000004) /* 1 = Cancel bustmaster accesses to ta #define CPF 0x00 /* Current pitch and fraction register */ SUB_REG(CPF, CURRENTPITCH, 0xffff0000) /* Current pitch (linear, 0x4000 == unity pitch shift) */ #define CPF_STEREO_MASK 0x00008000 /* 1 = Even channel interleave, odd channel locked */ -#define CPF_STOP_MASK 0x00004000 /* 1 = Current pitch forced to 0 */ +SUB_REG(CPF, STOP, 0x00004000) /* 1 = Current pitch forced to 0 */ + /* Can be set only while matching bit in SOLEx is 1 */ #define CPF_FRACADDRESS_MASK 0x00003fff /* Linear fractional address of the current channel */ #define PTRX 0x01 /* Pitch target and send A/B amounts register */ @@ -771,6 +772,9 @@ SUB_REG(PEFE, FILTERAMOUNT, 0x000000ff) /* Filter envlope amount */ #define CLIPL 0x5a /* Channel loop interrupt pending low register */ #define CLIPH 0x5b /* Channel loop interrupt pending high register */ +// These cause CPF_STOP_MASK to be set shortly after CCCA_CURRADDR passes DSL_LOOPENDADDR. +// Subsequent changes to the address registers don't resume; clearing the bit here or in CPF does. +// The registers are NOT synchronized; the next serviced channel picks up immediately. #define SOLEL 0x5c /* Stop on loop enable low register */ #define SOLEH 0x5d /* Stop on loop enable high register */ @@ -1476,6 +1480,7 @@ struct snd_emu10k1_pcm { struct snd_emu10k1_voice *extra; unsigned short running; unsigned short first_ptr; + snd_pcm_uframes_t resume_pos; struct snd_util_memblk *memblk; unsigned int start_addr; unsigned int ccca_start_addr; @@ -1820,6 +1825,9 @@ void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); #endif +void snd_emu10k1_voice_set_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices); +void snd_emu10k1_voice_clear_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices); +int snd_emu10k1_voice_clear_loop_stop_multiple_atomic(struct snd_emu10k1 *emu, u64 voices); void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 2764e7867b33..4df6f5285993 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -560,7 +560,7 @@ static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, // 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 += 64 - 3; + 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); @@ -584,7 +584,7 @@ static void snd_emu10k1_playback_prepare_voices(struct snd_emu10k1 *emu, // This is why all other (open) drivers for these chips use timer-based // interrupts. // - eloop_start += eloop_size - 3; + eloop_start += (epcm->resume_pos + eloop_size - 3) % eloop_size; snd_emu10k1_ptr_write(emu, CCCA_CURRADDR, epcm->extra->number, eloop_start); // It takes a moment until the cache fills complete, @@ -844,6 +844,49 @@ static snd_pcm_uframes_t snd_emu10k1_playback_pointer(struct snd_pcm_substream * return ptr; } +static u64 snd_emu10k1_efx_playback_voice_mask(struct snd_emu10k1_pcm *epcm, + int channels) +{ + u64 mask = 0; + + for (int i = 0; i < channels; i++) { + int voice = epcm->voices[i]->number; + mask |= 1ULL << voice; + } + return mask; +} + +static void snd_emu10k1_efx_playback_freeze_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) { + int voice = epcm->voices[i]->number; + snd_emu10k1_ptr_write(emu, CPF_STOP, voice, 1); + snd_emu10k1_playback_commit_pitch(emu, voice, PITCH_48000 << 16); + } +} + +static void snd_emu10k1_efx_playback_unmute_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + int channels) +{ + for (int i = 0; i < channels; i++) + snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, true, + &emu->efx_pcm_mixer[i]); +} + +static void snd_emu10k1_efx_playback_stop_voices(struct snd_emu10k1 *emu, + struct snd_emu10k1_pcm *epcm, + 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]); +} static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, int cmd) @@ -851,41 +894,62 @@ 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; - int i; + u64 mask; int result = 0; spin_lock(&emu->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - snd_emu10k1_playback_prepare_voices(emu, epcm, true, false, NUM_EFX_PLAYBACK); - fallthrough; case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_RESUME: - for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_unmute_voice(emu, epcm->voices[i], false, true, - &emu->efx_pcm_mixer[i]); + mask = snd_emu10k1_efx_playback_voice_mask( + epcm, NUM_EFX_PLAYBACK); + 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, NUM_EFX_PLAYBACK); + snd_emu10k1_playback_prepare_voices( + emu, epcm, true, false, NUM_EFX_PLAYBACK); + + // 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. + snd_emu10k1_efx_playback_unmute_voices(emu, epcm, NUM_EFX_PLAYBACK); + + snd_emu10k1_playback_set_running(emu, epcm); + result = snd_emu10k1_voice_clear_loop_stop_multiple_atomic(emu, mask); + if (result == 0) { + // The extra voice is allowed to lag a bit + snd_emu10k1_playback_trigger_voice(emu, epcm->extra); + goto leave; + } - snd_emu10k1_playback_set_running(emu, epcm); - for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_trigger_voice(emu, epcm->voices[i]); - snd_emu10k1_playback_trigger_voice(emu, epcm->extra); + snd_emu10k1_efx_playback_stop_voices( + emu, epcm, NUM_EFX_PLAYBACK); + + if (result != -EAGAIN) + break; + // The sync start can legitimately fail due to NMIs, etc. + } + snd_emu10k1_voice_clear_loop_stop_multiple(emu, mask); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - for (i = 0; i < NUM_EFX_PLAYBACK; i++) { - snd_emu10k1_playback_stop_voice(emu, epcm->voices[i]); - } snd_emu10k1_playback_stop_voice(emu, epcm->extra); - snd_emu10k1_playback_set_stopped(emu, epcm); + snd_emu10k1_efx_playback_stop_voices( + emu, epcm, NUM_EFX_PLAYBACK); - for (i = 0; i < NUM_EFX_PLAYBACK; i++) - snd_emu10k1_playback_mute_voice(emu, epcm->voices[i]); + epcm->resume_pos = snd_emu10k1_playback_pointer(substream); break; default: result = -EINVAL; break; } +leave: spin_unlock(&emu->reg_lock); return result; } diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 6419719c739c..9a839e7d283f 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -505,6 +505,88 @@ void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voi } #endif +void snd_emu10k1_voice_set_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + outl(SOLEL << 16, emu->port + PTR); + outl(inl(emu->port + DATA) | (u32)voices, emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + outl(inl(emu->port + DATA) | (u32)(voices >> 32), emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); +} + +void snd_emu10k1_voice_clear_loop_stop_multiple(struct snd_emu10k1 *emu, u64 voices) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + outl(SOLEL << 16, emu->port + PTR); + outl(inl(emu->port + DATA) & (u32)~voices, emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + outl(inl(emu->port + DATA) & (u32)(~voices >> 32), emu->port + DATA); + spin_unlock_irqrestore(&emu->emu_lock, flags); +} + +int snd_emu10k1_voice_clear_loop_stop_multiple_atomic(struct snd_emu10k1 *emu, u64 voices) +{ + unsigned long flags; + u32 soll, solh; + int ret = -EIO; + + spin_lock_irqsave(&emu->emu_lock, flags); + + outl(SOLEL << 16, emu->port + PTR); + soll = inl(emu->port + DATA); + outl(SOLEH << 16, emu->port + PTR); + solh = inl(emu->port + DATA); + + soll &= (u32)~voices; + solh &= (u32)(~voices >> 32); + + for (int tries = 0; tries < 1000; tries++) { + const u32 quart = 1U << (REG_SIZE(WC_CURRENTCHANNEL) - 2); + // First we wait for the third quarter of the sample cycle ... + u32 wc = inl(emu->port + WC); + u32 cc = REG_VAL_GET(WC_CURRENTCHANNEL, wc); + if (cc >= quart * 2 && cc < quart * 3) { + // ... and release the low voices, while the high ones are serviced. + outl(SOLEL << 16, emu->port + PTR); + outl(soll, emu->port + DATA); + // Then we wait for the first quarter of the next sample cycle ... + for (; tries < 1000; tries++) { + cc = REG_VAL_GET(WC_CURRENTCHANNEL, inl(emu->port + WC)); + if (cc < quart) + goto good; + // We will block for 10+ us with interrupts disabled. This is + // not nice at all, but necessary for reasonable reliability. + udelay(1); + } + break; + good: + // ... and release the high voices, while the low ones are serviced. + outl(SOLEH << 16, emu->port + PTR); + outl(solh, emu->port + DATA); + // Finally we verify that nothing interfered in fact. + if (REG_VAL_GET(WC_SAMPLECOUNTER, inl(emu->port + WC)) == + ((REG_VAL_GET(WC_SAMPLECOUNTER, wc) + 1) & REG_MASK0(WC_SAMPLECOUNTER))) { + ret = 0; + } else { + ret = -EAGAIN; + } + break; + } + // Don't block for too long + spin_unlock_irqrestore(&emu->emu_lock, flags); + udelay(1); + spin_lock_irqsave(&emu->emu_lock, flags); + } + + spin_unlock_irqrestore(&emu->emu_lock, flags); + return ret; +} + void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait) { volatile unsigned count; -- cgit v1.2.3 From f4ab59503989cce2c12a3c6337779218a5538ddd Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:09 +0200 Subject: ALSA: emu10k1: make channel count of multi-channel playback flexible There is no reason to nail it to 16 channels. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236023-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 4df6f5285993..e34b02e9f890 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -430,7 +430,7 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) struct snd_emu10k1_pcm *epcm = runtime->private_data; unsigned int start_addr; unsigned int extra_size, channel_size; - int i; + unsigned int i; start_addr = epcm->start_addr >> 1; // 16-bit voices @@ -441,7 +441,7 @@ 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 < NUM_EFX_PLAYBACK; i++) { + 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]); @@ -461,7 +461,7 @@ static const struct snd_pcm_hardware snd_emu10k1_efx_playback = .rates = SNDRV_PCM_RATE_48000, .rate_min = 48000, .rate_max = 48000, - .channels_min = NUM_EFX_PLAYBACK, + .channels_min = 1, .channels_max = NUM_EFX_PLAYBACK, .buffer_bytes_max = (128*1024), .period_bytes_max = (128*1024), @@ -903,21 +903,21 @@ 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, NUM_EFX_PLAYBACK); + epcm, 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, NUM_EFX_PLAYBACK); + emu, epcm, runtime->channels); snd_emu10k1_playback_prepare_voices( - emu, epcm, true, false, NUM_EFX_PLAYBACK); + emu, epcm, true, false, 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. - snd_emu10k1_efx_playback_unmute_voices(emu, epcm, NUM_EFX_PLAYBACK); + 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); @@ -928,7 +928,7 @@ static int snd_emu10k1_efx_playback_trigger(struct snd_pcm_substream *substream, } snd_emu10k1_efx_playback_stop_voices( - emu, epcm, NUM_EFX_PLAYBACK); + emu, epcm, runtime->channels); if (result != -EAGAIN) break; @@ -941,7 +941,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, NUM_EFX_PLAYBACK); + emu, epcm, runtime->channels); epcm->resume_pos = snd_emu10k1_playback_pointer(substream); break; -- cgit v1.2.3 From d2baa153c328efbbc3c4b939662e058b2cb544fd Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:06 +0200 Subject: ALSA: emu10k1: fix capture buffer size confusion The buffer size register sets the size of the whole buffer, not just one period. We actually handled it like that, except that the constraint was set on the wrong parameter. The period size is implicitly constrained by the buffer size and the fixed period count of 2. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index e34b02e9f890..40616d343d48 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -122,7 +122,7 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm, return 0; } -static const unsigned int capture_period_sizes[31] = { +static const unsigned int capture_buffer_sizes[31] = { 384, 448, 512, 640, 384*2, 448*2, 512*2, 640*2, 384*4, 448*4, 512*4, 640*4, @@ -133,9 +133,9 @@ static const unsigned int capture_period_sizes[31] = { 384*128,448*128,512*128 }; -static const struct snd_pcm_hw_constraint_list hw_constraints_capture_period_sizes = { +static const struct snd_pcm_hw_constraint_list hw_constraints_capture_buffer_sizes = { .count = 31, - .list = capture_period_sizes, + .list = capture_buffer_sizes, .mask = 0 }; @@ -499,7 +499,7 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) epcm->capture_bufsize = snd_pcm_lib_buffer_bytes(substream); epcm->capture_bs_val = 0; for (idx = 0; idx < 31; idx++) { - if (capture_period_sizes[idx] == epcm->capture_bufsize) { + if (capture_buffer_sizes[idx] == epcm->capture_bufsize) { epcm->capture_bs_val = idx + 1; break; } @@ -1219,9 +1219,10 @@ static int snd_emu10k1_capture_open(struct snd_pcm_substream *substream) runtime->private_data = epcm; runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_capture; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt; emu->pcm_capture_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates); return 0; } @@ -1258,9 +1259,10 @@ static int snd_emu10k1_capture_mic_open(struct snd_pcm_substream *substream) runtime->hw.rates = SNDRV_PCM_RATE_8000; runtime->hw.rate_min = runtime->hw.rate_max = 8000; runtime->hw.channels_min = 1; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt; emu->pcm_capture_mic_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes); return 0; } @@ -1365,9 +1367,10 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) epcm->capture_cr_val = emu->efx_voices_mask[0]; epcm->capture_cr_val2 = emu->efx_voices_mask[1]; spin_unlock_irq(&emu->reg_lock); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + &hw_constraints_capture_buffer_sizes); emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt; emu->pcm_capture_efx_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, &hw_constraints_capture_period_sizes); return 0; } -- cgit v1.2.3 From 872e5b2b5ee3f5b346d58dc4b89f0ca92065b61e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:07 +0200 Subject: ALSA: emu10k1: fix support for 24 kHz capture We need to specify that the hardware supports non-standard rates, as otherwise the sound core creates a constraint which limits the rate to the specified standard rates. That also made the rate constraint we were already adding meaningless. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 40616d343d48..8279d8a4f589 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1006,7 +1006,7 @@ static const struct snd_pcm_hardware snd_emu10k1_capture = SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_8000_48000, + .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT, .rate_min = 8000, .rate_max = 48000, .channels_min = 1, -- cgit v1.2.3 From 848ec6cf413d151eaae11ada2953d9078f8bcda9 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:08 +0200 Subject: ALSA: emu10k1: don't restrict capture channel count to powers of two The hardware can deal with primes up to 7 and power-of-two multiples thereof; the limitation is reflected by the possible buffer sizes. Note that setting the voice mask will not allow more than 16 channels even on Sound Blaster Audigy anymore, as 32 seems a bit excessive (the code overall appears to think so, just not in this case). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 8279d8a4f589..4216df399305 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -122,6 +122,17 @@ static int snd_emu10k1_pcm_channel_alloc(struct snd_emu10k1_pcm *epcm, return 0; } +// Primes 2-7 and 2^n multiples thereof, up to 16. +static const unsigned int efx_capture_channels[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_efx_capture_channels = { + .count = ARRAY_SIZE(efx_capture_channels), + .list = efx_capture_channels, + .mask = 0 +}; + static const unsigned int capture_buffer_sizes[31] = { 384, 448, 512, 640, 384*2, 448*2, 512*2, 640*2, @@ -1281,7 +1292,7 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) struct snd_emu10k1_pcm *epcm; struct snd_pcm_runtime *runtime = substream->runtime; int nefx = emu->audigy ? 64 : 32; - int idx; + int idx, err; epcm = kzalloc(sizeof(*epcm), GFP_KERNEL); if (epcm == NULL) @@ -1367,6 +1378,12 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) epcm->capture_cr_val = emu->efx_voices_mask[0]; epcm->capture_cr_val2 = emu->efx_voices_mask[1]; spin_unlock_irq(&emu->reg_lock); + err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + &hw_constraints_efx_capture_channels); + if (err < 0) { + kfree(epcm); + return err; + } snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, &hw_constraints_capture_buffer_sizes); emu->capture_efx_interrupt = snd_emu10k1_pcm_efx_interrupt; @@ -1531,7 +1548,6 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); unsigned int nval[2], bits; int nefx = emu->audigy ? 64 : 32; - int nefxb = emu->audigy ? 7 : 6; int change, idx; nval[0] = nval[1] = 0; @@ -1541,12 +1557,7 @@ static int snd_emu10k1_pcm_efx_voices_mask_put(struct snd_kcontrol *kcontrol, st bits++; } - // Check that the number of requested channels is a power of two - // not bigger than the number of available channels. - for (idx = 0; idx < nefxb; idx++) - if (1 << idx == bits) - break; - if (idx >= nefxb) + if (bits == 9 || bits == 11 || bits == 13 || bits == 15 || bits > 16) return -EINVAL; spin_lock_irq(&emu->reg_lock); -- cgit v1.2.3 From 0006fa2d3fa0320a385bd19c9c98b70ad3db7197 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Tue, 23 May 2023 22:07:09 +0200 Subject: ALSA: emu10k1: fix multi-channel capture config for E-MU cards On SB cards the number of captured channels is derived from the voice mask mixer control. But for E-MU cards this wasn't actually "wired up", so changing the mask would simply mess up the recording. We could fix that, but the channel routing through the FPGA makes the masking redundant. So instead we hide the control, and let the user specify the PCM channel count the traditional way. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230523200709.236059-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 76 +++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 44 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 4216df399305..550caefa0ce4 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -495,6 +495,12 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) snd_emu10k1_ptr_write(emu, ADCCR, 0, 0); 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); + } if (emu->audigy) { snd_emu10k1_ptr_write_multiple(emu, 0, A_FXWC1, 0, @@ -1042,8 +1048,8 @@ static const struct snd_pcm_hardware snd_emu10k1_capture_efx = SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, .rate_min = 44100, .rate_max = 192000, - .channels_min = 8, - .channels_max = 8, + .channels_min = 1, + .channels_max = 16, .buffer_bytes_max = (64*1024), .period_bytes_min = 384, .period_bytes_max = (64*1024), @@ -1269,7 +1275,6 @@ static int snd_emu10k1_capture_mic_open(struct snd_pcm_substream *substream) runtime->hw = snd_emu10k1_capture; runtime->hw.rates = SNDRV_PCM_RATE_8000; runtime->hw.rate_min = runtime->hw.rate_max = 8000; - runtime->hw.channels_min = 1; snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, &hw_constraints_capture_buffer_sizes); emu->capture_mic_interrupt = snd_emu10k1_pcm_ac97mic_interrupt; @@ -1310,7 +1315,6 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) runtime->hw = snd_emu10k1_capture_efx; runtime->hw.rates = SNDRV_PCM_RATE_48000; runtime->hw.rate_min = runtime->hw.rate_max = 48000; - spin_lock_irq(&emu->reg_lock); if (emu->card_capabilities->emu_model) { /* TODO * SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | @@ -1318,8 +1322,6 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) * SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 * rate_min = 44100, * rate_max = 192000, - * channels_min = 16, - * channels_max = 16, * Need to add mixer control to fix sample rate * * There are 32 mono channels of 16bits each. @@ -1338,15 +1340,11 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) /* For 44.1kHz */ runtime->hw.rates = SNDRV_PCM_RATE_44100; runtime->hw.rate_min = runtime->hw.rate_max = 44100; - runtime->hw.channels_min = - runtime->hw.channels_max = 16; break; case 1: /* For 48kHz */ runtime->hw.rates = SNDRV_PCM_RATE_48000; runtime->hw.rate_min = runtime->hw.rate_max = 48000; - runtime->hw.channels_min = - runtime->hw.channels_max = 16; break; } #endif @@ -1363,10 +1361,8 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) runtime->hw.channels_min = runtime->hw.channels_max = 2; #endif runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; - /* efx_voices_mask[0] is expected to be zero - * efx_voices_mask[1] is expected to have 32bits set - */ } else { + spin_lock_irq(&emu->reg_lock); runtime->hw.channels_min = runtime->hw.channels_max = 0; for (idx = 0; idx < nefx; idx++) { if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) { @@ -1374,10 +1370,10 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) runtime->hw.channels_max++; } } + epcm->capture_cr_val = emu->efx_voices_mask[0]; + epcm->capture_cr_val2 = emu->efx_voices_mask[1]; + spin_unlock_irq(&emu->reg_lock); } - epcm->capture_cr_val = emu->efx_voices_mask[0]; - epcm->capture_cr_val2 = emu->efx_voices_mask[1]; - spin_unlock_irq(&emu->reg_lock); err = snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, &hw_constraints_efx_capture_channels); if (err < 0) { @@ -1844,37 +1840,29 @@ int snd_emu10k1_pcm_efx(struct snd_emu10k1 *emu, int device) strcpy(pcm->name, "Multichannel Capture/PT Playback"); emu->pcm_efx = pcm; - /* EFX capture - record the "FXBUS2" channels, by default we connect the EXTINs - * to these - */ - - if (emu->audigy) { - emu->efx_voices_mask[0] = 0; - if (emu->card_capabilities->emu_model) - /* Pavel Hofman - 32 voices will be used for - * capture (write mode) - - * each bit = corresponding voice - */ - emu->efx_voices_mask[1] = 0xffffffff; - else + if (!emu->card_capabilities->emu_model) { + // On Sound Blasters, the DSP code copies the EXTINs to FXBUS2. + // The mask determines which of these and the EXTOUTs the multi- + // channel capture actually records (the channel order is fixed). + if (emu->audigy) { + emu->efx_voices_mask[0] = 0; emu->efx_voices_mask[1] = 0xffff; + } else { + emu->efx_voices_mask[0] = 0xffff0000; + emu->efx_voices_mask[1] = 0; + } + kctl = snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu); + if (!kctl) + return -ENOMEM; + kctl->id.device = device; + err = snd_ctl_add(emu->card, kctl); + if (err < 0) + return err; } else { - emu->efx_voices_mask[0] = 0xffff0000; - emu->efx_voices_mask[1] = 0; + // On E-MU cards, the DSP code copies the P16VINs/EMU32INs to + // FXBUS2. These are already selected & routed by the FPGA, + // so there is no need to apply additional masking. } - /* For emu1010, the control has to set 32 upper bits (voices) - * out of the 64 bits (voices) to true for the 16-channels capture - * to work correctly. Correct A_FXWC2 initial value (0xffffffff) - * is already defined but the snd_emu10k1_pcm_efx_voices_mask - * control can override this register's value. - */ - kctl = snd_ctl_new1(&snd_emu10k1_pcm_efx_voices_mask, emu); - if (!kctl) - return -ENOMEM; - kctl->id.device = device; - err = snd_ctl_add(emu->card, kctl); - if (err < 0) - return err; snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &emu->pci->dev, 64*1024, 64*1024); -- cgit v1.2.3 From ab2335daa6ef70df56c98c216261a93e28ae52b3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 25 May 2023 10:31:23 +0200 Subject: ALSA: ump: Drop redundant check of note-on with zero velocity The check of a note-on event with zero velocity is done twice, and the latter one is superfluous. Let's drop it. Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/4683198a-84f6-4238-9e87-c70667d84523@kili.mountain Suggested-by: Dan Carpenter Link: https://lore.kernel.org/r/20230525083124.15277-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/ump_convert.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'sound') diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index cb7c2f959a27..164829d3e305 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -340,9 +340,6 @@ static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, switch (status) { case UMP_MSG_STATUS_NOTE_ON: - if (!buf[2]) - status = UMP_MSG_STATUS_NOTE_OFF; - fallthrough; case UMP_MSG_STATUS_NOTE_OFF: midi2->note.note = buf[1]; midi2->note.velocity = upscale_7_to_16bit(buf[2]); -- cgit v1.2.3 From 77700b81bd0e47d89d50eb4b3f2f323492f79998 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 25 May 2023 10:31:24 +0200 Subject: ALSA: ump: Fix parsing of 0xFx command The MIDI 1.0 parser retrieved the 0xFx command with a wrong bit shift, resulting in the bogus type. Fix the bit shift size. Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/0fbc0b27-54b8-4cda-800e-37e7a03fed39@kili.mountain Suggested-by: Dan Carpenter Link: https://lore.kernel.org/r/20230525083124.15277-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/ump_convert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 164829d3e305..48ab3e1bd62e 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -454,7 +454,7 @@ static int do_convert_to_ump(struct snd_ump_endpoint *ump, } if (c & 0x80) { - bytes = cmd_bytes[(c >> 8) & 7]; + bytes = cmd_bytes[(c >> 4) & 7]; cvt->buf[0] = c; cvt->len = 1; cvt->cmd_bytes = bytes; -- cgit v1.2.3 From c894ec016c9d0418dd832202225a8c64f450d71e Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Thu, 25 May 2023 22:36:40 +0200 Subject: ALSA: Switch i2c drivers back to use .probe() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After commit b8a1a4cd5a98 ("i2c: Provide a temporary .probe_new() call-back type"), all drivers being converted to .probe_new() and then 03c835f498b5 ("i2c: Switch .probe() to not take an id parameter") convert back to (the new) .probe() to be able to eventually drop .probe_new() from struct i2c_driver. Signed-off-by: Uwe Kleine-König Reviewed-by: Luca Ceresoli Link: https://lore.kernel.org/r/20230525203640.677826-1-u.kleine-koenig@pengutronix.de Signed-off-by: Takashi Iwai --- sound/aoa/codecs/onyx.c | 2 +- sound/aoa/codecs/tas.c | 2 +- sound/pci/hda/cs35l41_hda_i2c.c | 2 +- sound/ppc/keywest.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index 4c75381f5ab8..a8a59d71dcec 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -1048,7 +1048,7 @@ static struct i2c_driver onyx_driver = { .driver = { .name = "aoa_codec_onyx", }, - .probe_new = onyx_i2c_probe, + .probe = onyx_i2c_probe, .remove = onyx_i2c_remove, .id_table = onyx_i2c_id, }; diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c index f906e9aaddcf..ab1472390061 100644 --- a/sound/aoa/codecs/tas.c +++ b/sound/aoa/codecs/tas.c @@ -936,7 +936,7 @@ static struct i2c_driver tas_driver = { .driver = { .name = "aoa_codec_tas", }, - .probe_new = tas_i2c_probe, + .probe = tas_i2c_probe, .remove = tas_i2c_remove, .id_table = tas_i2c_id, }; diff --git a/sound/pci/hda/cs35l41_hda_i2c.c b/sound/pci/hda/cs35l41_hda_i2c.c index 7826b1a12d7d..b44536fbba17 100644 --- a/sound/pci/hda/cs35l41_hda_i2c.c +++ b/sound/pci/hda/cs35l41_hda_i2c.c @@ -58,7 +58,7 @@ static struct i2c_driver cs35l41_i2c_driver = { .pm = &cs35l41_hda_pm_ops, }, .id_table = cs35l41_hda_i2c_id, - .probe_new = cs35l41_hda_i2c_probe, + .probe = cs35l41_hda_i2c_probe, .remove = cs35l41_hda_i2c_remove, }; module_i2c_driver(cs35l41_i2c_driver); diff --git a/sound/ppc/keywest.c b/sound/ppc/keywest.c index 0c4f43963c75..dfc1fc9b701d 100644 --- a/sound/ppc/keywest.c +++ b/sound/ppc/keywest.c @@ -90,7 +90,7 @@ static struct i2c_driver keywest_driver = { .driver = { .name = "PMac Keywest Audio", }, - .probe_new = keywest_probe, + .probe = keywest_probe, .remove = keywest_remove, .id_table = keywest_i2c_id, }; -- cgit v1.2.3 From 219153c6ed46b064f9c2b0f70dacf21f719751ee Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:54 +0200 Subject: ALSA: emu10k1: hide absent 2nd pointer-offset register set from /proc The 2nd register set belongs to the P16V chip (or embedded P17V module), so there is nothing to show when no such part is present. Gen2 E-MU cards have a P17V, but it's entirely unused, so we hide it there as well. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 89ea3adff322..6cf4a7e16b1d 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -561,15 +561,19 @@ int snd_emu10k1_proc_init(struct snd_emu10k1 *emu) snd_card_rw_proc_new(emu->card, "ptr_regs00b", emu, snd_emu_proc_ptr_reg_read00b, snd_emu_proc_ptr_reg_write00); - snd_card_rw_proc_new(emu->card, "ptr_regs20a", emu, - snd_emu_proc_ptr_reg_read20a, - snd_emu_proc_ptr_reg_write20); - snd_card_rw_proc_new(emu->card, "ptr_regs20b", emu, - snd_emu_proc_ptr_reg_read20b, - snd_emu_proc_ptr_reg_write20); - snd_card_rw_proc_new(emu->card, "ptr_regs20c", emu, - snd_emu_proc_ptr_reg_read20c, - snd_emu_proc_ptr_reg_write20); + if (!emu->card_capabilities->emu_model && + (emu->card_capabilities->ca0151_chip || emu->card_capabilities->ca0108_chip)) { + snd_card_rw_proc_new(emu->card, "ptr_regs20a", emu, + snd_emu_proc_ptr_reg_read20a, + snd_emu_proc_ptr_reg_write20); + snd_card_rw_proc_new(emu->card, "ptr_regs20b", emu, + snd_emu_proc_ptr_reg_read20b, + snd_emu_proc_ptr_reg_write20); + if (emu->card_capabilities->ca0108_chip) + snd_card_rw_proc_new(emu->card, "ptr_regs20c", emu, + snd_emu_proc_ptr_reg_read20c, + snd_emu_proc_ptr_reg_write20); + } #endif snd_card_ro_proc_new(emu->card, "emu10k1", emu, snd_emu10k1_proc_read); -- cgit v1.2.3 From 67ff2add9e2cef1d5d60cf5a37f1f52c65bf97c7 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:55 +0200 Subject: ALSA: emu10k1: fix writing 1st pointer-offset register set through /proc The limits were appropriate only for the 2nd set. FWIW, the channel count 4 for the 2nd set is suspicious as well - at least P17V_PLAYBACK_FIFO_PTR actually has 8 channels, and comments on HCFG2 hint at that as well. But all bitmasks are documented only for 4 channels. Anyway, rectifying that is out of scope for this patch. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 6cf4a7e16b1d..81d48cd478b7 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -483,7 +483,8 @@ static void snd_emu_proc_ptr_reg_read(struct snd_info_entry *entry, } static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry, - struct snd_info_buffer *buffer, int iobase) + struct snd_info_buffer *buffer, + int iobase, int length, int voices) { struct snd_emu10k1 *emu = entry->private_data; char line[64]; @@ -491,7 +492,7 @@ static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry, while (!snd_info_get_line(buffer, line, sizeof(line))) { if (sscanf(line, "%x %x %x", ®, &channel_id, &val) != 3) continue; - if (reg < 0xa0 && val <= 0xffffffff && channel_id <= 3) + if (reg < length && channel_id < voices) snd_ptr_write(emu, iobase, reg, channel_id, val); } } @@ -499,13 +500,15 @@ static void snd_emu_proc_ptr_reg_write(struct snd_info_entry *entry, static void snd_emu_proc_ptr_reg_write00(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { - snd_emu_proc_ptr_reg_write(entry, buffer, 0); + snd_emu_proc_ptr_reg_write(entry, buffer, 0, 0x80, 64); } static void snd_emu_proc_ptr_reg_write20(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { - snd_emu_proc_ptr_reg_write(entry, buffer, 0x20); + struct snd_emu10k1 *emu = entry->private_data; + snd_emu_proc_ptr_reg_write(entry, buffer, 0x20, + emu->card_capabilities->ca0108_chip ? 0xa0 : 0x80, 4); } -- cgit v1.2.3 From 6e91a93d1e7417e5f700fb1d10de994d1539de8e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 29 May 2023 11:55:04 +0200 Subject: ALSA: emu10k1: actually disassemble DSP instructions in /proc fx8010_acode is supposed to be a human-readable representation; the binary is already in fx8010_code. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230529095504.559054-1-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 149 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 130 insertions(+), 19 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 81d48cd478b7..750ac6a8cc24 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -275,37 +275,148 @@ static void snd_emu10k1_proc_rates_read(struct snd_info_entry *entry, } } -static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry, +struct emu10k1_reg_entry { + unsigned short base, size; + const char *name; +}; + +static const struct emu10k1_reg_entry sblive_reg_entries[] = { + { 0, 0x10, "FXBUS" }, + { 0x10, 0x10, "EXTIN" }, + { 0x20, 0x10, "EXTOUT" }, + { 0x30, 0x10, "FXBUS2" }, + { 0x40, 0x20, NULL }, // Constants + { 0x100, 0x100, "GPR" }, + { 0x200, 0x80, "ITRAM_DATA" }, + { 0x280, 0x20, "ETRAM_DATA" }, + { 0x300, 0x80, "ITRAM_ADDR" }, + { 0x380, 0x20, "ETRAM_ADDR" }, + { 0x400, 0, NULL } +}; + +static const struct emu10k1_reg_entry audigy_reg_entries[] = { + { 0, 0x40, "FXBUS" }, + { 0x40, 0x10, "EXTIN" }, + { 0x50, 0x10, "P16VIN" }, + { 0x60, 0x20, "EXTOUT" }, + { 0x80, 0x20, "FXBUS2" }, + { 0xa0, 0x10, "EMU32OUTH" }, + { 0xb0, 0x10, "EMU32OUTL" }, + { 0xc0, 0x20, NULL }, // Constants + // This can't be quite right - overlap. + //{ 0x100, 0xc0, "ITRAM_CTL" }, + //{ 0x1c0, 0x40, "ETRAM_CTL" }, + { 0x160, 0x20, "A3_EMU32IN" }, + { 0x1e0, 0x20, "A3_EMU32OUT" }, + { 0x200, 0xc0, "ITRAM_DATA" }, + { 0x2c0, 0x40, "ETRAM_DATA" }, + { 0x300, 0xc0, "ITRAM_ADDR" }, + { 0x3c0, 0x40, "ETRAM_ADDR" }, + { 0x400, 0x200, "GPR" }, + { 0x600, 0, NULL } +}; + +static const char * const emu10k1_const_entries[] = { + "C_00000000", + "C_00000001", + "C_00000002", + "C_00000003", + "C_00000004", + "C_00000008", + "C_00000010", + "C_00000020", + "C_00000100", + "C_00010000", + "C_00000800", + "C_10000000", + "C_20000000", + "C_40000000", + "C_80000000", + "C_7fffffff", + "C_ffffffff", + "C_fffffffe", + "C_c0000000", + "C_4f1bbcdc", + "C_5a7ef9db", + "C_00100000", + "GPR_ACCU", + "GPR_COND", + "GPR_NOISE0", + "GPR_NOISE1", + "GPR_IRQ", + "GPR_DBAC", + "GPR_DBACE", + "???", +}; + +static int disasm_emu10k1_reg(char *buffer, + const struct emu10k1_reg_entry *entries, + unsigned reg, const char *pfx) +{ + for (int i = 0; ; i++) { + unsigned base = entries[i].base; + unsigned size = entries[i].size; + if (!size) + return sprintf(buffer, "%s0x%03x", pfx, reg); + if (reg >= base && reg < base + size) { + const char *name = entries[i].name; + reg -= base; + if (name) + return sprintf(buffer, "%s%s(%u)", pfx, name, reg); + return sprintf(buffer, "%s%s", pfx, emu10k1_const_entries[reg]); + } + } +} + +static int disasm_sblive_reg(char *buffer, unsigned reg, const char *pfx) +{ + return disasm_emu10k1_reg(buffer, sblive_reg_entries, reg, pfx); +} + +static int disasm_audigy_reg(char *buffer, unsigned reg, const char *pfx) +{ + return disasm_emu10k1_reg(buffer, audigy_reg_entries, reg, pfx); +} + +static void snd_emu10k1_proc_acode_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { u32 pc; struct snd_emu10k1 *emu = entry->private_data; + static const char * const insns[16] = { + "MAC0", "MAC1", "MAC2", "MAC3", "MACINT0", "MACINT1", "ACC3", "MACMV", + "ANDXOR", "TSTNEG", "LIMITGE", "LIMITLT", "LOG", "EXP", "INTERP", "SKIP", + }; + static const char spaces[] = " "; + const int nspaces = sizeof(spaces) - 1; snd_iprintf(buffer, "FX8010 Instruction List '%s'\n", emu->fx8010.name); snd_iprintf(buffer, " Code dump :\n"); for (pc = 0; pc < (emu->audigy ? 1024 : 512); pc++) { u32 low, high; + int len; + char buf[100]; + char *bufp = buf; low = snd_emu10k1_efx_read(emu, pc * 2); high = snd_emu10k1_efx_read(emu, pc * 2 + 1); - if (emu->audigy) - snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n", - (high >> 24) & 0x0f, - (high >> 12) & 0x7ff, - (high >> 0) & 0x7ff, - (low >> 12) & 0x7ff, - (low >> 0) & 0x7ff, - pc, - high, low); - else - snd_iprintf(buffer, " OP(0x%02x, 0x%03x, 0x%03x, 0x%03x, 0x%03x) /* 0x%04x: 0x%08x%08x */\n", - (high >> 20) & 0x0f, - (high >> 10) & 0x3ff, - (high >> 0) & 0x3ff, - (low >> 10) & 0x3ff, - (low >> 0) & 0x3ff, - pc, - high, low); + if (emu->audigy) { + bufp += sprintf(bufp, " %-7s ", insns[(high >> 24) & 0x0f]); + bufp += disasm_audigy_reg(bufp, (high >> 12) & 0x7ff, ""); + bufp += disasm_audigy_reg(bufp, (high >> 0) & 0x7ff, ", "); + bufp += disasm_audigy_reg(bufp, (low >> 12) & 0x7ff, ", "); + bufp += disasm_audigy_reg(bufp, (low >> 0) & 0x7ff, ", "); + } else { + bufp += sprintf(bufp, " %-7s ", insns[(high >> 20) & 0x0f]); + bufp += disasm_sblive_reg(bufp, (high >> 10) & 0x3ff, ""); + bufp += disasm_sblive_reg(bufp, (high >> 0) & 0x3ff, ", "); + bufp += disasm_sblive_reg(bufp, (low >> 10) & 0x3ff, ", "); + bufp += disasm_sblive_reg(bufp, (low >> 0) & 0x3ff, ", "); + } + len = (int)(ptrdiff_t)(bufp - buf); + snd_iprintf(buffer, "%s %s /* 0x%04x: 0x%08x%08x */\n", + buf, &spaces[nspaces - clamp(65 - len, 0, nspaces)], + pc, high, low); } } -- cgit v1.2.3 From ad326d4a1364f9d677204b1e005ee8eb2a0b6558 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:57 +0200 Subject: ALSA: emu10k1: include FX send amounts in /proc output It seems to make little sense to include the FX send routing, but not the amounts. This also simplifies the code somewhat, and lines up the output. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 49 +++++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 24 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 750ac6a8cc24..f1e7084d7693 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -170,7 +170,7 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry, }; struct snd_emu10k1 *emu = entry->private_data; - unsigned int val, val1; + unsigned int val, val1, ptrx, psst, dsl, snda; int nefx = emu->audigy ? 64 : 32; const char * const *outputs = emu->audigy ? audigy_outs : creative_outs; int idx; @@ -180,34 +180,35 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry, emu->audigy ? "Audigy" : (emu->card_capabilities->ecard ? "EMU APS" : "Creative")); 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); - snd_iprintf(buffer, "\n"); - snd_iprintf(buffer, "Effect Send Routing :\n"); + + snd_iprintf(buffer, "\nEffect Send Routing & Amounts:\n"); for (idx = 0; idx < NUM_G; idx++) { - val = emu->audigy ? - snd_emu10k1_ptr_read(emu, A_FXRT1, idx) : - snd_emu10k1_ptr_read(emu, FXRT, idx); - val1 = emu->audigy ? - snd_emu10k1_ptr_read(emu, A_FXRT2, idx) : - 0; + ptrx = snd_emu10k1_ptr_read(emu, PTRX, idx); + psst = snd_emu10k1_ptr_read(emu, PSST, idx); + dsl = snd_emu10k1_ptr_read(emu, DSL, idx); if (emu->audigy) { - snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i, ", + val = snd_emu10k1_ptr_read(emu, A_FXRT1, idx); + val1 = snd_emu10k1_ptr_read(emu, A_FXRT2, idx); + snda = snd_emu10k1_ptr_read(emu, A_SENDAMOUNTS, idx); + snd_iprintf(buffer, "Ch%-2i: A=%2i:%02x, B=%2i:%02x, C=%2i:%02x, D=%2i:%02x, ", idx, - val & 0x3f, - (val >> 8) & 0x3f, - (val >> 16) & 0x3f, - (val >> 24) & 0x3f); - snd_iprintf(buffer, "E=%i, F=%i, G=%i, H=%i\n", - val1 & 0x3f, - (val1 >> 8) & 0x3f, - (val1 >> 16) & 0x3f, - (val1 >> 24) & 0x3f); + val & 0x3f, REG_VAL_GET(PTRX_FXSENDAMOUNT_A, ptrx), + (val >> 8) & 0x3f, REG_VAL_GET(PTRX_FXSENDAMOUNT_B, ptrx), + (val >> 16) & 0x3f, REG_VAL_GET(PSST_FXSENDAMOUNT_C, psst), + (val >> 24) & 0x3f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); + snd_iprintf(buffer, "E=%2i:%02x, F=%2i:%02x, G=%2i:%02x, H=%2i:%02x\n", + val1 & 0x3f, (snda >> 24) & 0xff, + (val1 >> 8) & 0x3f, (snda >> 16) & 0xff, + (val1 >> 16) & 0x3f, (snda >> 8) & 0xff, + (val1 >> 24) & 0x3f, snda & 0xff); } else { - snd_iprintf(buffer, "Ch%i: A=%i, B=%i, C=%i, D=%i\n", + val = snd_emu10k1_ptr_read(emu, FXRT, idx); + snd_iprintf(buffer, "Ch%-2i: A=%2i:%02x, B=%2i:%02x, C=%2i:%02x, D=%2i:%02x\n", idx, - (val >> 16) & 0x0f, - (val >> 20) & 0x0f, - (val >> 24) & 0x0f, - (val >> 28) & 0x0f); + (val >> 16) & 0x0f, REG_VAL_GET(PTRX_FXSENDAMOUNT_A, ptrx), + (val >> 20) & 0x0f, REG_VAL_GET(PTRX_FXSENDAMOUNT_B, ptrx), + (val >> 24) & 0x0f, REG_VAL_GET(PSST_FXSENDAMOUNT_C, psst), + (val >> 28) & 0x0f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); } } snd_iprintf(buffer, "\nCaptured FX Outputs :\n"); -- cgit v1.2.3 From 6ab13291ba82e6f0c8778cb45726dffffb9205f5 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:58 +0200 Subject: ALSA: emu10k1: make E-MU FPGA register dump in /proc more useful Include the routing information, which can be actually read back. Somewhat as a drive-by, make the register dump format less obscure - the previous one made no sense at all. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emuproc.c | 43 ++++++++++++++++++++++++++++++++++++++++++- sound/pci/emu10k1/io.c | 28 +++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 164a2374b4c2..4b9dda449917 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1812,6 +1812,7 @@ int snd_emu10k1_i2c_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value); void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src); +u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst); unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb); diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index f1e7084d7693..0d376d6e66ab 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -496,6 +496,15 @@ static void snd_emu10k1_proc_voices_read(struct snd_info_entry *entry, } #ifdef CONFIG_SND_DEBUG + +static void snd_emu_proc_emu1010_link_read(struct snd_emu10k1 *emu, + struct snd_info_buffer *buffer, + u32 dst) +{ + u32 src = snd_emu1010_fpga_link_dst_src_read(emu, dst); + snd_iprintf(buffer, "%04x: %04x\n", dst, src); +} + static void snd_emu_proc_emu1010_reg_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { @@ -506,7 +515,39 @@ static void snd_emu_proc_emu1010_reg_read(struct snd_info_entry *entry, for(i = 0; i < 0x40; i+=1) { snd_emu1010_fpga_read(emu, i, &value); - snd_iprintf(buffer, "%02X: %08X, %02X\n", i, value, (value >> 8) & 0x7f); + snd_iprintf(buffer, "%02x: %02x\n", i, value); + } + + snd_iprintf(buffer, "\nEMU1010 Routes:\n\n"); + + for (i = 0; i < 16; i++) // To Alice2/Tina[2] via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, i); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) + for (i = 0; i < 32; i++) // To Dock via EDI + snd_emu_proc_emu1010_link_read(emu, buffer, 0x100 + i); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU1616) + for (i = 0; i < 8; i++) // To Hamoa/local + snd_emu_proc_emu1010_link_read(emu, buffer, 0x200 + i); + for (i = 0; i < 8; i++) // To Hamoa/Mana/local + snd_emu_proc_emu1010_link_read(emu, buffer, 0x300 + i); + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1616) { + for (i = 0; i < 16; i++) // To Tina2 via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, 0x400 + i); + } else if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) { + for (i = 0; i < 8; i++) // To Hana ADAT + snd_emu_proc_emu1010_link_read(emu, buffer, 0x400 + i); + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU1010B) { + for (i = 0; i < 16; i++) // To Tina via EMU32 + snd_emu_proc_emu1010_link_read(emu, buffer, 0x500 + i); + } else { + // To Alice2 via I2S + snd_emu_proc_emu1010_link_read(emu, buffer, 0x500); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x501); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x600); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x601); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x700); + snd_emu_proc_emu1010_link_read(emu, buffer, 0x701); + } } } diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index 9a839e7d283f..abe69ae40499 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -298,21 +298,27 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value) spin_unlock_irqrestore(&emu->emu_lock, flags); } -void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value) +static void snd_emu1010_fpga_read_locked(struct snd_emu10k1 *emu, u32 reg, u32 *value) { // The higest input pin is used as the designated interrupt trigger, // so it needs to be masked out. u32 mask = emu->card_capabilities->ca0108_chip ? 0x1f : 0x7f; - unsigned long flags; if (snd_BUG_ON(reg > 0x3f)) return; reg += 0x40; /* 0x40 upwards are registers. */ - spin_lock_irqsave(&emu->emu_lock, flags); outw(reg, emu->port + A_GPIO); udelay(10); outw(reg | 0x80, emu->port + A_GPIO); /* High bit clocks the value into the fpga. */ udelay(10); *value = ((inw(emu->port + A_GPIO) >> 8) & mask); +} + +void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value) +{ + unsigned long flags; + + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_read_locked(emu, reg, value); spin_unlock_irqrestore(&emu->emu_lock, flags); } @@ -335,6 +341,22 @@ void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 s spin_unlock_irqrestore(&emu->emu_lock, flags); } +u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst) +{ + unsigned long flags; + u32 hi, lo; + + if (snd_BUG_ON(dst & ~0x71f)) + return 0; + spin_lock_irqsave(&emu->emu_lock, flags); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTHI, dst >> 8); + snd_emu1010_fpga_write_locked(emu, EMU_HANA_DESTLO, dst & 0x1f); + snd_emu1010_fpga_read_locked(emu, EMU_HANA_SRCHI, &hi); + snd_emu1010_fpga_read_locked(emu, EMU_HANA_SRCLO, &lo); + spin_unlock_irqrestore(&emu->emu_lock, flags); + return (hi << 8) | lo; +} + void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb) { unsigned long flags; -- cgit v1.2.3 From db987421b57cdf3ecb4859e0c7b49726baae895e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Fri, 26 May 2023 12:16:59 +0200 Subject: ALSA: emu10k1: vastly improve usefulness of info in /proc - Include the FX bus map, without which the already present send routing info would require looking up the documentation. - Include the physical I/O channels as known to the driver - Make the multi-channel capture map actually name the mapped input channels rather than "FXBUS" (Audigy) or even "???" (SbLive) - The latter two are omitted for E-MU cards, as their physical I/O is routed through the FPGA - While at it, make the "Card" field somewhat more useful This includes de-duplicating the label tables between emuproc and emufx, updating/improving the FX bus label table, and making the SB Live! 5.1 multi-track capture channel mapping hack data-driven. Tested-by: Jonathan Dowland Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230526101659.437969-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 10 +++ sound/pci/emu10k1/emufx.c | 87 +++++++++++++++-------- sound/pci/emu10k1/emuproc.c | 167 ++++++++++++++------------------------------ 3 files changed, 120 insertions(+), 144 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 4b9dda449917..cc0151e7c828 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1440,6 +1440,16 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM /* 0x600 and 0x700 no used */ + +/* ------------------- CONSTANTS -------------------- */ + +extern const char * const snd_emu10k1_fxbus[32]; +extern const char * const snd_emu10k1_sblive_ins[16]; +extern const char * const snd_emu10k1_audigy_ins[16]; +extern const char * const snd_emu10k1_sblive_outs[32]; +extern const char * const snd_emu10k1_audigy_outs[32]; +extern const s8 snd_emu10k1_sblive51_fxbus2_map[16]; + /* ------------------- STRUCTURES -------------------- */ enum { diff --git a/sound/pci/emu10k1/emufx.c b/sound/pci/emu10k1/emufx.c index e9855d37fa5c..9904bcfee106 100644 --- a/sound/pci/emu10k1/emufx.c +++ b/sound/pci/emu10k1/emufx.c @@ -46,26 +46,45 @@ MODULE_PARM_DESC(high_res_gpr_volume, "GPR mixer controls use 31-bit range."); * Tables */ -static const char * const fxbuses[16] = { +// Playback channel labels; corresponds with the public FXBUS_* defines. +// Unlike the tables below, this is not determined by the hardware. +const char * const snd_emu10k1_fxbus[32] = { /* 0x00 */ "PCM Left", /* 0x01 */ "PCM Right", - /* 0x02 */ "PCM Surround Left", - /* 0x03 */ "PCM Surround Right", + /* 0x02 */ "PCM Rear Left", + /* 0x03 */ "PCM Rear Right", /* 0x04 */ "MIDI Left", /* 0x05 */ "MIDI Right", - /* 0x06 */ "Center", - /* 0x07 */ "LFE", - /* 0x08 */ NULL, - /* 0x09 */ NULL, + /* 0x06 */ "PCM Center", + /* 0x07 */ "PCM LFE", + /* 0x08 */ "PCM Front Left", + /* 0x09 */ "PCM Front Right", /* 0x0a */ NULL, /* 0x0b */ NULL, /* 0x0c */ "MIDI Reverb", /* 0x0d */ "MIDI Chorus", - /* 0x0e */ NULL, - /* 0x0f */ NULL + /* 0x0e */ "PCM Side Left", + /* 0x0f */ "PCM Side Right", + /* 0x10 */ NULL, + /* 0x11 */ NULL, + /* 0x12 */ NULL, + /* 0x13 */ NULL, + /* 0x14 */ "Passthrough Left", + /* 0x15 */ "Passthrough Right", + /* 0x16 */ NULL, + /* 0x17 */ NULL, + /* 0x18 */ NULL, + /* 0x19 */ NULL, + /* 0x1a */ NULL, + /* 0x1b */ NULL, + /* 0x1c */ NULL, + /* 0x1d */ NULL, + /* 0x1e */ NULL, + /* 0x1f */ NULL }; -static const char * const creative_ins[16] = { +// Physical inputs; corresponds with the public EXTIN_* defines. +const char * const snd_emu10k1_sblive_ins[16] = { /* 0x00 */ "AC97 Left", /* 0x01 */ "AC97 Right", /* 0x02 */ "TTL IEC958 Left", @@ -84,7 +103,8 @@ static const char * const creative_ins[16] = { /* 0x0f */ NULL }; -static const char * const audigy_ins[16] = { +// Physical inputs; corresponds with the public A_EXTIN_* defines. +const char * const snd_emu10k1_audigy_ins[16] = { /* 0x00 */ "AC97 Left", /* 0x01 */ "AC97 Right", /* 0x02 */ "Audigy CD Left", @@ -103,7 +123,8 @@ static const char * const audigy_ins[16] = { /* 0x0f */ NULL }; -static const char * const creative_outs[32] = { +// Physical outputs; corresponds with the public EXTOUT_* defines. +const char * const snd_emu10k1_sblive_outs[32] = { /* 0x00 */ "AC97 Left", /* 0x01 */ "AC97 Right", /* 0x02 */ "Optical IEC958 Left", @@ -120,6 +141,7 @@ static const char * const creative_outs[32] = { /* 0x0d */ "AC97 Surround Left", /* 0x0e */ "AC97 Surround Right", /* 0x0f */ NULL, + // This is actually the FXBUS2 range; SB Live! 5.1 only. /* 0x10 */ NULL, /* 0x11 */ "Analog Center", /* 0x12 */ "Analog LFE", @@ -138,7 +160,8 @@ static const char * const creative_outs[32] = { /* 0x1f */ NULL, }; -static const char * const audigy_outs[32] = { +// Physical outputs; corresponds with the public A_EXTOUT_* defines. +const char * const snd_emu10k1_audigy_outs[32] = { /* 0x00 */ "Digital Front Left", /* 0x01 */ "Digital Front Right", /* 0x02 */ "Digital Center", @@ -173,6 +196,18 @@ static const char * const audigy_outs[32] = { /* 0x1f */ NULL, }; +// On the SB Live! 5.1, FXBUS2[1] and FXBUS2[2] are occupied by EXTOUT_ACENTER +// and EXTOUT_ALFE, so we can't connect inputs to them for multitrack recording. +// +// Since only 14 of the 16 EXTINs are used, this is not a big problem. +// We route AC97 to FX capture 14 and 15, SPDIF_CD to FX capture 0 and 3, +// and the rest of the EXTINs to the corresponding FX capture channel. +// Multitrack recorders will still see the center/LFE output signal +// on the second and third "input" channel. +const s8 snd_emu10k1_sblive51_fxbus2_map[16] = { + 2, -1, -1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 1 +}; + static const u32 bass_table[41][5] = { { 0x3e4f844f, 0x84ed4cc3, 0x3cc69927, 0x7b03553a, 0xc4da8486 }, { 0x3e69a17a, 0x84c280fb, 0x3cd77cd4, 0x7b2f2a6f, 0xc4b08d1d }, @@ -2290,21 +2325,11 @@ static int _snd_emu10k1_init_efx(struct snd_emu10k1 *emu) /* EFX capture - capture the 16 EXTINS */ if (emu->card_capabilities->sblive51) { - /* On the Live! 5.1, FXBUS2(1) and FXBUS(2) are shared with EXTOUT_ACENTER - * and EXTOUT_ALFE, so we can't connect inputs to them for multitrack recording. - * - * Since only 14 of the 16 EXTINs are used, this is not a big problem. - * We route AC97L and R to FX capture 14 and 15, SPDIF CD in to FX capture - * 0 and 3, then the rest of the EXTINs to the corresponding FX capture - * channel. Multitrack recorders will still see the center/lfe output signal - * on the second and third channels. - */ - OP(icode, &ptr, iACC3, FXBUS2(14), C_00000000, C_00000000, EXTIN(0)); - OP(icode, &ptr, iACC3, FXBUS2(15), C_00000000, C_00000000, EXTIN(1)); - OP(icode, &ptr, iACC3, FXBUS2(0), C_00000000, C_00000000, EXTIN(2)); - OP(icode, &ptr, iACC3, FXBUS2(3), C_00000000, C_00000000, EXTIN(3)); - for (z = 4; z < 14; z++) - OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z)); + for (z = 0; z < 16; z++) { + s8 c = snd_emu10k1_sblive51_fxbus2_map[z]; + if (c != -1) + OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(c)); + } } else { for (z = 0; z < 16; z++) OP(icode, &ptr, iACC3, FXBUS2(z), C_00000000, C_00000000, EXTIN(z)); @@ -2448,9 +2473,9 @@ static void snd_emu10k1_fx8010_info(struct snd_emu10k1 *emu, info->internal_tram_size = emu->fx8010.itram_size; info->external_tram_size = emu->fx8010.etram_pages.bytes / 2; - fxbus = fxbuses; - extin = emu->audigy ? audigy_ins : creative_ins; - extout = emu->audigy ? audigy_outs : creative_outs; + fxbus = snd_emu10k1_fxbus; + extin = emu->audigy ? snd_emu10k1_audigy_ins : snd_emu10k1_sblive_ins; + extout = emu->audigy ? snd_emu10k1_audigy_outs : snd_emu10k1_sblive_outs; extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask; extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask; for (res = 0; res < 16; res++, fxbus++, extin++, extout++) { diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 0d376d6e66ab..ca7b4dddbea8 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -66,118 +66,22 @@ static void snd_emu10k1_proc_spdif_status(struct snd_emu10k1 * emu, static void snd_emu10k1_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { - /* FIXME - output names are in emufx.c too */ - static const char * const creative_outs[32] = { - /* 00 */ "AC97 Left", - /* 01 */ "AC97 Right", - /* 02 */ "Optical IEC958 Left", - /* 03 */ "Optical IEC958 Right", - /* 04 */ "Center", - /* 05 */ "LFE", - /* 06 */ "Headphone Left", - /* 07 */ "Headphone Right", - /* 08 */ "Surround Left", - /* 09 */ "Surround Right", - /* 10 */ "PCM Capture Left", - /* 11 */ "PCM Capture Right", - /* 12 */ "MIC Capture", - /* 13 */ "AC97 Surround Left", - /* 14 */ "AC97 Surround Right", - /* 15 */ "???", - /* 16 */ "???", - /* 17 */ "Analog Center", - /* 18 */ "Analog LFE", - /* 19 */ "???", - /* 20 */ "???", - /* 21 */ "???", - /* 22 */ "???", - /* 23 */ "???", - /* 24 */ "???", - /* 25 */ "???", - /* 26 */ "???", - /* 27 */ "???", - /* 28 */ "???", - /* 29 */ "???", - /* 30 */ "???", - /* 31 */ "???" - }; - - static const char * const audigy_outs[64] = { - /* 00 */ "Digital Front Left", - /* 01 */ "Digital Front Right", - /* 02 */ "Digital Center", - /* 03 */ "Digital LEF", - /* 04 */ "Headphone Left", - /* 05 */ "Headphone Right", - /* 06 */ "Digital Rear Left", - /* 07 */ "Digital Rear Right", - /* 08 */ "Front Left", - /* 09 */ "Front Right", - /* 10 */ "Center", - /* 11 */ "LFE", - /* 12 */ "???", - /* 13 */ "???", - /* 14 */ "Rear Left", - /* 15 */ "Rear Right", - /* 16 */ "AC97 Front Left", - /* 17 */ "AC97 Front Right", - /* 18 */ "ADC Capture Left", - /* 19 */ "ADC Capture Right", - /* 20 */ "???", - /* 21 */ "???", - /* 22 */ "???", - /* 23 */ "???", - /* 24 */ "???", - /* 25 */ "???", - /* 26 */ "???", - /* 27 */ "???", - /* 28 */ "???", - /* 29 */ "???", - /* 30 */ "???", - /* 31 */ "???", - /* 32 */ "FXBUS2_0", - /* 33 */ "FXBUS2_1", - /* 34 */ "FXBUS2_2", - /* 35 */ "FXBUS2_3", - /* 36 */ "FXBUS2_4", - /* 37 */ "FXBUS2_5", - /* 38 */ "FXBUS2_6", - /* 39 */ "FXBUS2_7", - /* 40 */ "FXBUS2_8", - /* 41 */ "FXBUS2_9", - /* 42 */ "FXBUS2_10", - /* 43 */ "FXBUS2_11", - /* 44 */ "FXBUS2_12", - /* 45 */ "FXBUS2_13", - /* 46 */ "FXBUS2_14", - /* 47 */ "FXBUS2_15", - /* 48 */ "FXBUS2_16", - /* 49 */ "FXBUS2_17", - /* 50 */ "FXBUS2_18", - /* 51 */ "FXBUS2_19", - /* 52 */ "FXBUS2_20", - /* 53 */ "FXBUS2_21", - /* 54 */ "FXBUS2_22", - /* 55 */ "FXBUS2_23", - /* 56 */ "FXBUS2_24", - /* 57 */ "FXBUS2_25", - /* 58 */ "FXBUS2_26", - /* 59 */ "FXBUS2_27", - /* 60 */ "FXBUS2_28", - /* 61 */ "FXBUS2_29", - /* 62 */ "FXBUS2_30", - /* 63 */ "FXBUS2_31" - }; - struct snd_emu10k1 *emu = entry->private_data; + const char * const *inputs = emu->audigy ? + snd_emu10k1_audigy_ins : snd_emu10k1_sblive_ins; + const char * const *outputs = emu->audigy ? + snd_emu10k1_audigy_outs : snd_emu10k1_sblive_outs; + unsigned short extin_mask = emu->audigy ? ~0 : emu->fx8010.extin_mask; + unsigned short extout_mask = emu->audigy ? ~0 : emu->fx8010.extout_mask; unsigned int val, val1, ptrx, psst, dsl, snda; - int nefx = emu->audigy ? 64 : 32; - const char * const *outputs = emu->audigy ? audigy_outs : creative_outs; + int nefx = emu->audigy ? 32 : 16; int idx; snd_iprintf(buffer, "EMU10K1\n\n"); snd_iprintf(buffer, "Card : %s\n", - emu->audigy ? "Audigy" : (emu->card_capabilities->ecard ? "EMU APS" : "Creative")); + emu->card_capabilities->emu_model ? "E-MU D.A.S." : + emu->card_capabilities->ecard ? "E-MU A.P.S." : + emu->audigy ? "SB Audigy" : "SB Live!"); 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); @@ -211,14 +115,51 @@ static void snd_emu10k1_proc_read(struct snd_info_entry *entry, (val >> 28) & 0x0f, REG_VAL_GET(DSL_FXSENDAMOUNT_D, dsl)); } } - snd_iprintf(buffer, "\nCaptured FX Outputs :\n"); - for (idx = 0; idx < nefx; idx++) { - if (emu->efx_voices_mask[idx/32] & (1 << (idx%32))) - snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]); + snd_iprintf(buffer, "\nEffect Send Targets:\n"); + // Audigy actually has 64, but we don't use them all. + for (idx = 0; idx < 32; idx++) { + const char *c = snd_emu10k1_fxbus[idx]; + if (c) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, c); + } + if (!emu->card_capabilities->emu_model) { + snd_iprintf(buffer, "\nOutput Channels:\n"); + for (idx = 0; idx < 32; idx++) + if (outputs[idx] && (extout_mask & (1 << idx))) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, outputs[idx]); + snd_iprintf(buffer, "\nInput Channels:\n"); + for (idx = 0; idx < 16; idx++) + if (inputs[idx] && (extin_mask & (1 << idx))) + snd_iprintf(buffer, " Channel %02i [%s]\n", idx, inputs[idx]); + snd_iprintf(buffer, "\nMultichannel Capture Sources:\n"); + for (idx = 0; idx < nefx; idx++) + if (emu->efx_voices_mask[0] & (1 << idx)) + snd_iprintf(buffer, " Channel %02i [Output: %s]\n", + idx, outputs[idx] ? outputs[idx] : "???"); + if (emu->audigy) { + for (idx = 0; idx < 32; idx++) + if (emu->efx_voices_mask[1] & (1 << idx)) + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 32, inputs[idx] ? inputs[idx] : "???"); + } else { + for (idx = 0; idx < 16; idx++) { + if (emu->efx_voices_mask[0] & ((1 << 16) << idx)) { + if (emu->card_capabilities->sblive51) { + s8 c = snd_emu10k1_sblive51_fxbus2_map[idx]; + if (c == -1) + snd_iprintf(buffer, " Channel %02i [Output: %s]\n", + idx + 16, outputs[idx + 16]); + else + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 16, inputs[c]); + } else { + snd_iprintf(buffer, " Channel %02i [Input: %s]\n", + idx + 16, inputs[idx] ? inputs[idx] : "???"); + } + } + } + } } - snd_iprintf(buffer, "\nAll FX Outputs :\n"); - for (idx = 0; idx < (emu->audigy ? 64 : 32); idx++) - snd_iprintf(buffer, " Output %02i [%s]\n", idx, outputs[idx]); } static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, -- cgit v1.2.3 From 8c15a18331191b67bdce54d21af068baec044baf Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 5 Jun 2023 16:47:58 +0200 Subject: ALSA: seq: Avoid confusion of aligned read size Currently the read event packet size in snd_seq_read() is defined by client->midi_version value that is guaranteed to be zero if UMP isn't enabled. But the static analyzer doesn't know of the fact, and it still suspects as if it were leading to a potential overflow. Add the more explicit check of CONFIG_SND_SEQ_UMP to determine the aligned_size value for avoiding the confusion. Fixes: 46397622a3fa ("ALSA: seq: Add UMP support") Reported-by: kernel test robot Reported-by: Dan Carpenter Closes: https://lore.kernel.org/r/202305261415.NY0vapZK-lkp@intel.com/ Link: https://lore.kernel.org/r/20230605144758.6677-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_clientmgr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c index 948ae45e0cc3..e3f9ea67d019 100644 --- a/sound/core/seq/seq_clientmgr.c +++ b/sound/core/seq/seq_clientmgr.c @@ -451,7 +451,7 @@ static ssize_t snd_seq_read(struct file *file, char __user *buf, size_t count, err = 0; snd_seq_fifo_lock(fifo); - if (client->midi_version > 0) + if (IS_ENABLED(CONFIG_SND_SEQ_UMP) && client->midi_version > 0) aligned_size = sizeof(struct snd_seq_ump_event); else aligned_size = sizeof(struct snd_seq_event); -- cgit v1.2.3 From 448425f05b16f124290ad82b836c04da63b63035 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 6 Jun 2023 11:34:34 +0100 Subject: ALSA: hda: cs35l41: Clean up Firmware Load Controls Ensure Firmware Load control and Firmware Type control returns 1 when the value changes. Remove fw_mutex from firmware load control put, since it is unnecessary, and prevents any possibility of mutex inversion. Signed-off-by: Stefan Binding Link: https://lore.kernel.org/r/20230606103436.455348-2-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/cs35l41_hda.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index b5210abb5141..d100189e15b8 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -835,34 +835,26 @@ static int cs35l41_fw_load_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); - unsigned int ret = 0; - - mutex_lock(&cs35l41->fw_mutex); if (cs35l41->request_fw_load == ucontrol->value.integer.value[0]) - goto err; + return 0; if (cs35l41->fw_request_ongoing) { dev_dbg(cs35l41->dev, "Existing request not complete\n"); - ret = -EBUSY; - goto err; + return -EBUSY; } /* Check if playback is ongoing when initial request is made */ if (cs35l41->playback_started) { dev_err(cs35l41->dev, "Cannot Load/Unload firmware during Playback\n"); - ret = -EBUSY; - goto err; + return -EBUSY; } cs35l41->fw_request_ongoing = true; cs35l41->request_fw_load = ucontrol->value.integer.value[0]; schedule_work(&cs35l41->fw_load_work); -err: - mutex_unlock(&cs35l41->fw_mutex); - - return ret; + return 1; } static int cs35l41_fw_type_ctl_get(struct snd_kcontrol *kcontrol, @@ -881,8 +873,12 @@ static int cs35l41_fw_type_ctl_put(struct snd_kcontrol *kcontrol, struct cs35l41_hda *cs35l41 = snd_kcontrol_chip(kcontrol); if (ucontrol->value.enumerated.item[0] < HDA_CS_DSP_NUM_FW) { - cs35l41->firmware_type = ucontrol->value.enumerated.item[0]; - return 0; + if (cs35l41->firmware_type != ucontrol->value.enumerated.item[0]) { + cs35l41->firmware_type = ucontrol->value.enumerated.item[0]; + return 1; + } else { + return 0; + } } return -EINVAL; -- cgit v1.2.3 From 31dbb503f07a059419f16773e47df4a31093ba31 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 6 Jun 2023 11:34:35 +0100 Subject: ALSA: hda: cs35l41: Fix endian conversions Found during static analysis, ensure variables are correct types before endian conversion. Signed-off-by: Stefan Binding Link: https://lore.kernel.org/r/20230606103436.455348-3-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/cs35l41_hda.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c index d100189e15b8..ce5faa620517 100644 --- a/sound/pci/hda/cs35l41_hda.c +++ b/sound/pci/hda/cs35l41_hda.c @@ -308,8 +308,8 @@ out: } #if IS_ENABLED(CONFIG_EFI) -static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, unsigned int ambient, - unsigned int r0, unsigned int status, unsigned int checksum) +static int cs35l41_apply_calibration(struct cs35l41_hda *cs35l41, __be32 ambient, __be32 r0, + __be32 status, __be32 checksum) { int ret; @@ -745,7 +745,7 @@ err: static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) { - int halo_sts; + __be32 halo_sts; int ret; ret = cs35l41_init_dsp(cs35l41); @@ -773,7 +773,7 @@ static int cs35l41_smart_amp(struct cs35l41_hda *cs35l41) &halo_sts, sizeof(halo_sts)); if (ret) { - dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %d\n", + dev_err(cs35l41->dev, "Timeout waiting for HALO Core to start. State: %u\n", halo_sts); goto clean_dsp; } -- cgit v1.2.3 From ebcbfd846367c980e105c787d372c4239e9ed922 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Tue, 6 Jun 2023 11:34:36 +0100 Subject: ALSA: hda/realtek: Delete cs35l41 component master during free This ensures that the driver is properly cleaned up when freed. Signed-off-by: Stefan Binding Link: https://lore.kernel.org/r/20230606103436.455348-4-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 172ffc2c332b..ce9a9cb41e4b 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -6757,6 +6757,9 @@ static void cs35l41_generic_fixup(struct hda_codec *cdc, int action, const char else spec->gen.pcm_playback_hook = comp_generic_playback_hook; break; + case HDA_FIXUP_ACT_FREE: + component_master_del(dev, &comp_master_ops); + break; } } -- cgit v1.2.3 From 306f3f78a5ff578bdfd97c658a862cb2c2419fb6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 6 Jun 2023 11:40:35 +0200 Subject: ALSA: control: Keep the previous numid at snd_ctl_rename_id() We don't need to change the numid at each time snd_ctl_rename_id() is called, as the control element size itself doesn't change. Let's keep the previous numid value. Along with it, add a note about calling this function only in the card init phase. Reviewed-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230606094035.14808-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/control.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/core/control.c b/sound/core/control.c index 82aa1af1d1d8..8386b53acdcd 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -730,12 +730,20 @@ EXPORT_SYMBOL_GPL(snd_ctl_activate_id); * Finds the control with the old id from the card, and replaces the * id with the new one. * + * The function tries to keep the already assigned numid while replacing + * the rest. + * + * Note that this function should be used only in the card initialization + * phase. Calling after the card instantiation may cause issues with + * user-space expecting persistent numids. + * * Return: Zero if successful, or a negative error code on failure. */ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, struct snd_ctl_elem_id *dst_id) { struct snd_kcontrol *kctl; + int saved_numid; down_write(&card->controls_rwsem); kctl = snd_ctl_find_id(card, src_id); @@ -743,10 +751,10 @@ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id, up_write(&card->controls_rwsem); return -ENOENT; } + saved_numid = kctl->id.numid; remove_hash_entries(card, kctl); kctl->id = *dst_id; - kctl->id.numid = card->last_numid + 1; - card->last_numid += kctl->count; + kctl->id.numid = saved_numid; add_hash_entries(card, kctl); up_write(&card->controls_rwsem); return 0; -- cgit v1.2.3 From 28bd137a3c8e105587ba8c55b68ef43b519b270f Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:49 +0800 Subject: ALSA: hda: Add Loongson LS7A HD-Audio support Add the new PCI ID 0x0014 0x7a07 and the new PCI ID 0x0014 0x7a37 Loongson HDA controller. Signed-off-by: Yanteng Si Acked-by: Huacai Chen Link: https://lore.kernel.org/r/993587483b9509796b29a416f257fcfb4b15c6ea.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- include/linux/pci_ids.h | 3 +++ sound/hda/hdac_device.c | 1 + sound/pci/hda/hda_intel.c | 7 +++++++ sound/pci/hda/patch_hdmi.c | 1 + 4 files changed, 12 insertions(+) (limited to 'sound') diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 95f33dadb2be..c0c4ca8e2851 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -158,6 +158,9 @@ #define PCI_VENDOR_ID_LOONGSON 0x0014 +#define PCI_DEVICE_ID_LOONGSON_HDA 0x7a07 +#define PCI_DEVICE_ID_LOONGSON_HDMI 0x7a37 + #define PCI_VENDOR_ID_TTTECH 0x0357 #define PCI_DEVICE_ID_TTTECH_MC322 0x000a diff --git a/sound/hda/hdac_device.c b/sound/hda/hdac_device.c index accc9d279ce5..89bed32b5379 100644 --- a/sound/hda/hdac_device.c +++ b/sound/hda/hdac_device.c @@ -645,6 +645,7 @@ struct hda_vendor_id { }; static const struct hda_vendor_id hda_vendor_ids[] = { + { 0x0014, "Loongson" }, { 0x1002, "ATI" }, { 0x1013, "Cirrus Logic" }, { 0x1057, "Motorola" }, diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 3226691ac923..9c353dc7740c 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -237,6 +237,7 @@ enum { AZX_DRIVER_CTHDA, AZX_DRIVER_CMEDIA, AZX_DRIVER_ZHAOXIN, + AZX_DRIVER_LOONGSON, AZX_DRIVER_GENERIC, AZX_NUM_DRIVERS, /* keep this as last entry */ }; @@ -360,6 +361,7 @@ static const char * const driver_short_names[] = { [AZX_DRIVER_CTHDA] = "HDA Creative", [AZX_DRIVER_CMEDIA] = "HDA C-Media", [AZX_DRIVER_ZHAOXIN] = "HDA Zhaoxin", + [AZX_DRIVER_LOONGSON] = "HDA Loongson", [AZX_DRIVER_GENERIC] = "HD-Audio Generic", }; @@ -2809,6 +2811,11 @@ static const struct pci_device_id azx_ids[] = { .driver_data = AZX_DRIVER_GENERIC | AZX_DCAPS_PRESET_ATI_HDMI }, /* Zhaoxin */ { PCI_DEVICE(0x1d17, 0x3288), .driver_data = AZX_DRIVER_ZHAOXIN }, + /* Loongson HDAudio*/ + {PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_HDA), + .driver_data = AZX_DRIVER_LOONGSON }, + {PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, PCI_DEVICE_ID_LOONGSON_HDMI), + .driver_data = AZX_DRIVER_LOONGSON }, { 0, } }; MODULE_DEVICE_TABLE(pci, azx_ids); diff --git a/sound/pci/hda/patch_hdmi.c b/sound/pci/hda/patch_hdmi.c index 64a944016c78..44b55ba38e45 100644 --- a/sound/pci/hda/patch_hdmi.c +++ b/sound/pci/hda/patch_hdmi.c @@ -4505,6 +4505,7 @@ static int patch_gf_hdmi(struct hda_codec *codec) * patch entries */ static const struct hda_device_id snd_hda_id_hdmi[] = { +HDA_CODEC_ENTRY(0x00147a47, "Loongson HDMI", patch_generic_hdmi), HDA_CODEC_ENTRY(0x1002793c, "RS600 HDMI", patch_atihdmi), HDA_CODEC_ENTRY(0x10027919, "RS600 HDMI", patch_atihdmi), HDA_CODEC_ENTRY(0x1002791a, "RS690/780 HDMI", patch_atihdmi), -- cgit v1.2.3 From cbc3e98acf802c8939e14103a059db60499d69eb Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:50 +0800 Subject: ALSA: hda: Using polling mode for loongson controller by default On loongson controller, RIRBSTS.RINTFL cannot be cleared, azx_interrupt() is called all the time. We disable RIRB interrupt, and use polling mode by default. Signed-off-by: Yanteng Si Signed-off-by: Yingkun Meng Acked-by: Huacai Chen Link: https://lore.kernel.org/r/d309a75424d438b958d90d797b4f1ba45468e090.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 1 + sound/hda/hdac_controller.c | 5 ++++- sound/pci/hda/hda_intel.c | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 97f09acae302..a0bb40a4b721 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -347,6 +347,7 @@ struct hdac_bus { bool corbrp_self_clear:1; /* CORBRP clears itself after reset */ bool polling_mode:1; bool needs_damn_long_delay:1; + bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ int poll_count; diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c index 3c7af6558249..7f3a000fab0c 100644 --- a/sound/hda/hdac_controller.c +++ b/sound/hda/hdac_controller.c @@ -79,7 +79,10 @@ void snd_hdac_bus_init_cmd_io(struct hdac_bus *bus) /* set N=1, get RIRB response interrupt for new entry */ snd_hdac_chip_writew(bus, RINTCNT, 1); /* enable rirb dma and response irq */ - snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN); + if (bus->not_use_interrupts) + snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN); + else + snd_hdac_chip_writeb(bus, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN); /* Accept unsolicited responses */ snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL); spin_unlock_irq(&bus->reg_lock); diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 9c353dc7740c..b7a7a92d03ef 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1875,6 +1875,11 @@ static int azx_first_init(struct azx *chip) if (chip->driver_type == AZX_DRIVER_GFHDMI) bus->polling_mode = 1; + if (chip->driver_type == AZX_DRIVER_LOONGSON) { + bus->polling_mode = 1; + bus->not_use_interrupts = 1; + } + err = pcim_iomap_regions(pci, 1 << 0, "ICH HD audio"); if (err < 0) return err; -- cgit v1.2.3 From 942ccdd834f43b498abc3f022b73fb831d78f5f7 Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:51 +0800 Subject: ALSA: hda: Workaround for SDnCTL register on loongson On loongson controller, after calling snd_hdac_stream_updateb() to enable DMA engine, the SDnCTL.STRM will become to zero. We need to access SDnCTL in dword to keep SDnCTL.STRM is not changed. Signed-off-by: Yanteng Si Signed-off-by: Yingkun Meng Acked-by: Huacai Chen Link: https://lore.kernel.org/r/27aeddf5ebbe7c69631cec0e489c1b264be94990.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- include/sound/hdaudio.h | 1 + sound/hda/hdac_stream.c | 6 +++++- sound/pci/hda/hda_intel.c | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index a0bb40a4b721..2ffdf58bd6d4 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -348,6 +348,7 @@ struct hdac_bus { bool polling_mode:1; bool needs_damn_long_delay:1; bool not_use_interrupts:1; /* prohibiting the RIRB IRQ */ + bool access_sdnctl_in_dword:1; /* accessing the sdnctl register by dword */ int poll_count; diff --git a/sound/hda/hdac_stream.c b/sound/hda/hdac_stream.c index 1f56fd33b9af..2633a4bb1d85 100644 --- a/sound/hda/hdac_stream.c +++ b/sound/hda/hdac_stream.c @@ -150,7 +150,11 @@ void snd_hdac_stream_start(struct hdac_stream *azx_dev) stripe_ctl); } /* set DMA start and interrupt mask */ - snd_hdac_stream_updateb(azx_dev, SD_CTL, + if (bus->access_sdnctl_in_dword) + snd_hdac_stream_updatel(azx_dev, SD_CTL, + 0, SD_CTL_DMA_START | SD_INT_MASK); + else + snd_hdac_stream_updateb(azx_dev, SD_CTL, 0, SD_CTL_DMA_START | SD_INT_MASK); azx_dev->running = true; } diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index b7a7a92d03ef..fc4787c7782a 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -1878,6 +1878,7 @@ static int azx_first_init(struct azx *chip) if (chip->driver_type == AZX_DRIVER_LOONGSON) { bus->polling_mode = 1; bus->not_use_interrupts = 1; + bus->access_sdnctl_in_dword = 1; } err = pcim_iomap_regions(pci, 1 << 0, "ICH HD audio"); -- cgit v1.2.3 From a4d2b8537845c9a4f4b16dd31793af9c08548341 Mon Sep 17 00:00:00 2001 From: Yanteng Si Date: Wed, 7 Jun 2023 17:21:52 +0800 Subject: ALSA: hda/intel: Workaround for WALLCLK register for loongson controller On loongson controller, the value of WALLCLK register is always 0, which is meaningless, so we return directly. Signed-off-by: Yanteng Si Signed-off-by: Yingkun Meng Acked-by: Huacai Chen Link: https://lore.kernel.org/r/185df71ef413ab190460eb377703214ee7288aeb.1686128807.git.siyanteng@loongson.cn Signed-off-by: Takashi Iwai --- sound/pci/hda/hda_intel.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index fc4787c7782a..ef831770ca7d 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -655,6 +655,13 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) unsigned int pos; snd_pcm_uframes_t hwptr, target; + /* + * The value of the WALLCLK register is always 0 + * on the Loongson controller, so we return directly. + */ + if (chip->driver_type == AZX_DRIVER_LOONGSON) + return 1; + wallclk = azx_readl(chip, WALLCLK) - azx_dev->core.start_wallclk; if (wallclk < (azx_dev->core.period_wallclk * 2) / 3) return -1; /* bogus (too early) interrupt */ -- cgit v1.2.3 From 315a3d57c64c55901d6fe5a5eef3b3e51d215381 Mon Sep 17 00:00:00 2001 From: Ivan Orlov Date: Tue, 6 Jun 2023 23:32:53 +0400 Subject: ALSA: Implement the new Virtual PCM Test Driver We have a lot of different virtual media drivers, which can be used for testing of the userspace applications and media subsystem middle layer. However, all of them are aimed at testing the video functionality and simulating the video devices. For audio devices we have only snd-dummy module, which is good in simulating the correct behavior of an ALSA device. I decided to write a tool, which would help to test the userspace ALSA programs (and the PCM middle layer as well) under unusual circumstances to figure out how they would behave. So I came up with this Virtual PCM Test Driver. This new Virtual PCM Test Driver has several features which can be useful during the userspace ALSA applications testing/fuzzing, or testing/fuzzing of the PCM middle layer. Not all of them can be implemented using the existing virtual drivers (like dummy or loopback). Here is what can this driver do: - Simulate both capture and playback processes - Generate random or pattern-based capture data - Inject delays into the playback and capturing processes - Inject errors during the PCM callbacks Also, this driver can check the playback stream for containing the predefined pattern, which is used in the corresponding selftest to check the PCM middle layer data transferring functionality. Additionally, this driver redefines the default RESET ioctl, and the selftest covers this PCM API functionality as well. The driver supports both interleaved and non-interleaved access modes, and have separate pattern buffers for each channel. The driver supports up to 4 channels and up to 8 substreams. Signed-off-by: Ivan Orlov Acked-by: Jaroslav Kysela Link: https://lore.kernel.org/r/20230606193254.20791-2-ivan.orlov0322@gmail.com Signed-off-by: Takashi Iwai --- MAINTAINERS | 8 + sound/drivers/Kconfig | 16 ++ sound/drivers/Makefile | 2 + sound/drivers/pcmtest.c | 727 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 753 insertions(+) create mode 100644 sound/drivers/pcmtest.c (limited to 'sound') diff --git a/MAINTAINERS b/MAINTAINERS index e0ad886d3163..eba84c63d849 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22423,6 +22423,14 @@ L: linux-fsdevel@vger.kernel.org S: Maintained F: fs/vboxsf/* +VIRTUAL PCM TEST DRIVER +M: Ivan Orlov +L: alsa-devel@alsa-project.org +S: Maintained +F: Documentation/sound/cards/pcmtest.rst +F: sound/drivers/pcmtest.c +F: tools/testing/selftests/alsa/test-pcmtest-driver.c + VIRTUAL SERIO DEVICE DRIVER M: Stephen Chandler Paul S: Maintained diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index 864991d8776d..41c171468c1e 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -109,6 +109,22 @@ config SND_ALOOP To compile this driver as a module, choose M here: the module will be called snd-aloop. +config SND_PCMTEST + tristate "Virtual PCM test driver" + select SND_PCM + help + Say 'Y' or 'M' to include support for the Virtual PCM test driver. + This driver is aimed at extended testing of the userspace applications + which use the ALSA API, as well as the PCM middle layer testing. + + It can generate random or pattern-based data into the capture stream, + check the playback stream for containing the selected pattern, inject + time delays during capture/playback, redefine the RESET ioctl operation + to perform the PCM middle layer testing and inject errors during the + PCM callbacks. It supports both interleaved and non-interleaved access + modes. You can find the corresponding selftest in the 'alsa' + selftests folder. + config SND_VIRMIDI tristate "Virtual MIDI soundcard" depends on SND_SEQUENCER diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile index b60303180a1b..2c0c7092d396 100644 --- a/sound/drivers/Makefile +++ b/sound/drivers/Makefile @@ -8,6 +8,7 @@ snd-dummy-objs := dummy.o snd-aloop-objs := aloop.o snd-mtpav-objs := mtpav.o snd-mts64-objs := mts64.o +snd-pcmtest-objs := pcmtest.o snd-portman2x4-objs := portman2x4.o snd-serial-u16550-objs := serial-u16550.o snd-serial-generic-objs := serial-generic.o @@ -17,6 +18,7 @@ snd-virmidi-objs := virmidi.o obj-$(CONFIG_SND_DUMMY) += snd-dummy.o obj-$(CONFIG_SND_ALOOP) += snd-aloop.o obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o +obj-$(CONFIG_SND_PCMTEST) += snd-pcmtest.o obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c new file mode 100644 index 000000000000..2ae912a64d16 --- /dev/null +++ b/sound/drivers/pcmtest.c @@ -0,0 +1,727 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Virtual ALSA driver for PCM testing/fuzzing + * + * Copyright 2023 Ivan Orlov + * + * This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer + * testing or fuzzing. + * It can: + * - Simulate 'playback' and 'capture' actions + * - Generate random or pattern-based capture data + * - Check playback buffer for containing looped template, and notify about the results + * through the debugfs entry + * - Inject delays into the playback and capturing processes. See 'inject_delay' parameter. + * - Inject errors during the PCM callbacks. + * - Register custom RESET ioctl and notify when it is called through the debugfs entry + * - Work in interleaved and non-interleaved modes + * - Support up to 8 substreams + * - Support up to 4 channels + * - Support framerates from 8 kHz to 48 kHz + * + * When driver works in the capture mode with multiple channels, it duplicates the looped + * pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved + * access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for + * each channel will contain abacabaabacaba... Same for the non-interleaved mode. + * + * However, it may break the capturing on the higher framerates with small period size, so it is + * better to choose larger period sizes. + * + * You can find the corresponding selftest in the 'alsa' selftests folder. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEVNAME "pcmtestd" +#define CARD_NAME "pcm-test-card" +#define TIMER_PER_SEC 5 +#define TIMER_INTERVAL (HZ / TIMER_PER_SEC) +#define DELAY_JIFFIES HZ +#define PLAYBACK_SUBSTREAM_CNT 8 +#define CAPTURE_SUBSTREAM_CNT 8 +#define MAX_CHANNELS_NUM 4 + +#define DEFAULT_PATTERN "abacaba" +#define DEFAULT_PATTERN_LEN 7 + +#define FILL_MODE_RAND 0 +#define FILL_MODE_PAT 1 + +#define MAX_PATTERN_LEN 4096 + +static int index = -1; +static char *id = "pcmtest"; +static bool enable = true; +static int inject_delay; +static bool inject_hwpars_err; +static bool inject_prepare_err; +static bool inject_trigger_err; + +static short fill_mode = FILL_MODE_PAT; + +static u8 playback_capture_test; +static u8 ioctl_reset_test; +static struct dentry *driver_debug_dir; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard"); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard"); +module_param(enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); +module_param(fill_mode, short, 0600); +MODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)"); +module_param(inject_delay, int, 0600); +MODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)"); +module_param(inject_hwpars_err, bool, 0600); +MODULE_PARM_DESC(inject_hwpars_err, "Inject EBUSY error in the 'hw_params' callback"); +module_param(inject_prepare_err, bool, 0600); +MODULE_PARM_DESC(inject_prepare_err, "Inject EINVAL error in the 'prepare' callback"); +module_param(inject_trigger_err, bool, 0600); +MODULE_PARM_DESC(inject_trigger_err, "Inject EINVAL error in the 'trigger' callback"); + +struct pcmtst { + struct snd_pcm *pcm; + struct snd_card *card; + struct platform_device *pdev; +}; + +struct pcmtst_buf_iter { + size_t buf_pos; // position in the DMA buffer + size_t period_pos; // period-relative position + size_t b_rw; // Bytes to write on every timer tick + size_t s_rw_ch; // Samples to write to one channel on every tick + unsigned int sample_bytes; // sample_bits / 8 + bool is_buf_corrupted; // playback test result indicator + size_t period_bytes; // bytes in a one period + bool interleaved; // Interleaved/Non-interleaved mode + size_t total_bytes; // Total bytes read/written + size_t chan_block; // Bytes in one channel buffer when non-interleaved + struct snd_pcm_substream *substream; + struct timer_list timer_instance; +}; + +static struct pcmtst *pcmtst; + +static struct snd_pcm_hardware snd_pcmtst_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = MAX_CHANNELS_NUM, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +struct pattern_buf { + char *buf; + u32 len; +}; + +static int buf_allocated; +static struct pattern_buf patt_bufs[MAX_CHANNELS_NUM]; + +static inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes) +{ + v_iter->total_bytes += by; + v_iter->buf_pos += by; + v_iter->buf_pos %= bytes; +} + +/* + * Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos + * every time we write a byte to any channel, so the position in the current channel buffer is + * (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel + */ +static inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels, + unsigned int chan_num) +{ + return v_iter->buf_pos / channels + v_iter->chan_block * chan_num; +} + +/* + * Get the count of bytes written for the current channel in the interleaved mode. + * This is (count of samples written for the current channel) * bytes_in_sample + + * (relative position in the current sample) + */ +static inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample) +{ + return b_total / channels / b_sample * b_sample + (b_total % b_sample); +} + +static void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + short ch_num; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + current_byte = runtime->dma_area[v_iter->buf_pos]; + if (!current_byte) + break; + ch_num = (v_iter->total_bytes / v_iter->sample_bytes) % runtime->channels; + if (current_byte != patt_bufs[ch_num].buf[ch_pos_i(v_iter->total_bytes, + runtime->channels, + v_iter->sample_bytes) + % patt_bufs[ch_num].len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + // If we broke during the loop, add remaining bytes to the buffer position. + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +static void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + size_t i; + short ch_num; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + current_byte = runtime->dma_area[buf_pos_n(v_iter, channels, i % channels)]; + if (!current_byte) + break; + ch_num = i % channels; + if (current_byte != patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) + % patt_bufs[ch_num].len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +/* + * Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is + * necessary because we need to detect when the reading/writing ends, so we assume that the pattern + * doesn't contain zeros. + */ +static void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + check_buf_block_i(v_iter, runtime); + else + check_buf_block_ni(v_iter, runtime); +} + +/* + * Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2... + * The channel buffers lay in the DMA buffer continuously (see default copy_user and copy_kernel + * handlers in the pcm_lib.c file). + * + * Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'. + * We need this to simulate the correct hardware pointer moving. + */ +static void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + unsigned int channels = runtime->channels; + short ch_num; + + for (i = 0; i < v_iter->b_rw; i++) { + ch_num = i % channels; + runtime->dma_area[buf_pos_n(v_iter, channels, i % channels)] = + patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) + % patt_bufs[ch_num].len]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } +} + +// Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ... +static void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t sample; + size_t pos_in_ch, pos_pattern; + short ch, pos_sample; + + pos_in_ch = ch_pos_i(v_iter->total_bytes, runtime->channels, v_iter->sample_bytes); + + for (sample = 0; sample < v_iter->s_rw_ch; sample++) { + for (ch = 0; ch < runtime->channels; ch++) { + for (pos_sample = 0; pos_sample < v_iter->sample_bytes; pos_sample++) { + pos_pattern = (pos_in_ch + sample * v_iter->sample_bytes + + pos_sample) % patt_bufs[ch].len; + runtime->dma_area[v_iter->buf_pos] = patt_bufs[ch].buf[pos_pattern]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + } + } +} + +static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_pattern_i(v_iter, runtime); + else + fill_block_pattern_n(v_iter, runtime); +} + +static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + // Remaining space in all channel buffers + size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + unsigned int i; + + for (i = 0; i < channels; i++) { + if (v_iter->b_rw <= bytes_remain) { + //b_rw - count of bytes must be written for all channels at each timer tick + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + v_iter->b_rw / channels); + } else { + // Write to the end of buffer and start from the beginning of it + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + bytes_remain / channels); + get_random_bytes(runtime->dma_area + v_iter->chan_block * i, + (v_iter->b_rw - bytes_remain) / channels); + } + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; + + if (v_iter->b_rw <= in_cur_block) { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); + } else { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); + get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_rand_i(v_iter, runtime); + else + fill_block_rand_n(v_iter, runtime); +} + +static void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + switch (fill_mode) { + case FILL_MODE_RAND: + fill_block_random(v_iter, runtime); + break; + case FILL_MODE_PAT: + fill_block_pattern(v_iter, runtime); + break; + } +} + +/* + * Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes. + * The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer + * about period elapsed. + */ +static void timer_timeout(struct timer_list *data) +{ + struct pcmtst_buf_iter *v_iter; + struct snd_pcm_substream *substream; + + v_iter = from_timer(v_iter, data, timer_instance); + substream = v_iter->substream; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !v_iter->is_buf_corrupted) + check_buf_block(v_iter, substream->runtime); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + fill_block(v_iter, substream->runtime); + else + inc_buf_pos(v_iter, v_iter->b_rw, substream->runtime->dma_bytes); + + v_iter->period_pos += v_iter->b_rw; + if (v_iter->period_pos >= v_iter->period_bytes) { + v_iter->period_pos %= v_iter->period_bytes; + snd_pcm_period_elapsed(substream); + } + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL + inject_delay); +} + +static int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter; + + v_iter = kzalloc(sizeof(*v_iter), GFP_KERNEL); + if (!v_iter) + return -ENOMEM; + + runtime->hw = snd_pcmtst_hw; + runtime->private_data = v_iter; + v_iter->substream = substream; + v_iter->buf_pos = 0; + v_iter->is_buf_corrupted = false; + v_iter->period_pos = 0; + v_iter->total_bytes = 0; + + playback_capture_test = 0; + ioctl_reset_test = 0; + + timer_setup(&v_iter->timer_instance, timer_timeout, 0); + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL); + return 0; +} + +static int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + timer_shutdown_sync(&v_iter->timer_instance); + v_iter->substream = NULL; + playback_capture_test = !v_iter->is_buf_corrupted; + kfree(v_iter); + return 0; +} + +static int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter = runtime->private_data; + + if (inject_trigger_err) + return -EINVAL; + + v_iter->sample_bytes = runtime->sample_bits / 8; + v_iter->period_bytes = frames_to_bytes(runtime, runtime->period_size); + if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) { + v_iter->chan_block = runtime->dma_bytes / runtime->channels; + v_iter->interleaved = false; + } else { + v_iter->interleaved = true; + } + // We want to record RATE * ch_cnt samples per sec, it is rate * sample_bytes * ch_cnt bytes + v_iter->s_rw_ch = runtime->rate / TIMER_PER_SEC; + v_iter->b_rw = v_iter->s_rw_ch * v_iter->sample_bytes * runtime->channels; + + return 0; +} + +static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, v_iter->buf_pos); +} + +static int snd_pcmtst_free(struct pcmtst *pcmtst) +{ + if (!pcmtst) + return 0; + kfree(pcmtst); + return 0; +} + +// These callbacks are required, but empty - all freeing occurs in pdev_remove +static int snd_pcmtst_dev_free(struct snd_device *device) +{ + return 0; +} + +static void pcmtst_pdev_release(struct device *dev) +{ +} + +static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) +{ + if (inject_prepare_err) + return -EINVAL; + return 0; +} + +static int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + if (inject_hwpars_err) + return -EBUSY; + return 0; +} + +static int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + ioctl_reset_test = 1; + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static const struct snd_pcm_ops snd_pcmtst_playback_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .ioctl = snd_pcmtst_ioctl, + .hw_free = snd_pcmtst_pcm_hw_free, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_pcmtst_capture_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .hw_free = snd_pcmtst_pcm_hw_free, + .ioctl = snd_pcmtst_ioctl, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(pcmtst->card, "PCMTest", 0, PLAYBACK_SUBSTREAM_CNT, + CAPTURE_SUBSTREAM_CNT, &pcm); + if (err < 0) + return err; + pcm->private_data = pcmtst; + strcpy(pcm->name, "PCMTest"); + pcmtst->pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pcmtst_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pcmtst_capture_ops); + + err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &pcmtst->pdev->dev, + 0, 128 * 1024); + return err; +} + +static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, + struct pcmtst **r_pcmtst) +{ + struct pcmtst *pcmtst; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_pcmtst_dev_free, + }; + + pcmtst = kzalloc(sizeof(*pcmtst), GFP_KERNEL); + if (!pcmtst) + return -ENOMEM; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); + if (err < 0) + goto _err_free_chip; + + err = snd_pcmtst_new_pcm(pcmtst); + if (err < 0) + goto _err_free_chip; + + *r_pcmtst = pcmtst; + return 0; + +_err_free_chip: + snd_pcmtst_free(pcmtst); + return err; +} + +static int pcmtst_probe(struct platform_device *pdev) +{ + struct snd_card *card; + int err; + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + err = snd_pcmtst_create(card, pdev, &pcmtst); + if (err < 0) + return err; + + strcpy(card->driver, "PCM-TEST Driver"); + strcpy(card->shortname, "PCM-Test"); + strcpy(card->longname, "PCM-Test virtual driver"); + + err = snd_card_register(card); + if (err < 0) + return err; + + return 0; +} + +static int pdev_remove(struct platform_device *dev) +{ + snd_pcmtst_free(pcmtst); + return 0; +} + +static struct platform_device pcmtst_pdev = { + .name = "pcmtest", + .dev.release = pcmtst_pdev_release, +}; + +static struct platform_driver pcmtst_pdrv = { + .probe = pcmtst_probe, + .remove = pdev_remove, + .driver = { + .name = "pcmtest", + }, +}; + +static ssize_t pattern_write(struct file *file, const char __user *u_buff, size_t len, loff_t *off) +{ + struct pattern_buf *patt_buf = file->f_inode->i_private; + ssize_t to_write = len; + + if (*off + to_write > MAX_PATTERN_LEN) + to_write = MAX_PATTERN_LEN - *off; + + // Crop silently everything over the buffer + if (to_write <= 0) + return len; + + if (copy_from_user(patt_buf->buf + *off, u_buff, to_write)) + return -EFAULT; + + patt_buf->len = *off + to_write; + *off += to_write; + + return to_write; +} + +static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, loff_t *off) +{ + struct pattern_buf *patt_buf = file->f_inode->i_private; + ssize_t to_read = len; + + if (*off + to_read >= MAX_PATTERN_LEN) + to_read = MAX_PATTERN_LEN - *off; + if (to_read <= 0) + return 0; + + if (copy_to_user(u_buff, patt_buf->buf + *off, to_read)) + to_read = 0; + else + *off += to_read; + + return to_read; +} + +static const struct file_operations fill_pattern_fops = { + .read = pattern_read, + .write = pattern_write, +}; + +static int setup_patt_bufs(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(patt_bufs); i++) { + patt_bufs[i].buf = kzalloc(MAX_PATTERN_LEN, GFP_KERNEL); + if (!patt_bufs[i].buf) + break; + strcpy(patt_bufs[i].buf, DEFAULT_PATTERN); + patt_bufs[i].len = DEFAULT_PATTERN_LEN; + } + + return i; +} + +static const char * const pattern_files[] = { "fill_pattern0", "fill_pattern1", + "fill_pattern2", "fill_pattern3"}; +static int init_debug_files(int buf_count) +{ + size_t i; + char len_file_name[32]; + + driver_debug_dir = debugfs_create_dir("pcmtest", NULL); + if (IS_ERR(driver_debug_dir)) + return PTR_ERR(driver_debug_dir); + debugfs_create_u8("pc_test", 0444, driver_debug_dir, &playback_capture_test); + debugfs_create_u8("ioctl_test", 0444, driver_debug_dir, &ioctl_reset_test); + + for (i = 0; i < buf_count; i++) { + debugfs_create_file(pattern_files[i], 0600, driver_debug_dir, + &patt_bufs[i], &fill_pattern_fops); + snprintf(len_file_name, sizeof(len_file_name), "%s_len", pattern_files[i]); + debugfs_create_u32(len_file_name, 0444, driver_debug_dir, &patt_bufs[i].len); + } + + return 0; +} + +static void free_pattern_buffers(void) +{ + int i; + + for (i = 0; i < buf_allocated; i++) + kfree(patt_bufs[i].buf); +} + +static void clear_debug_files(void) +{ + debugfs_remove_recursive(driver_debug_dir); +} + +static int __init mod_init(void) +{ + int err = 0; + + buf_allocated = setup_patt_bufs(); + if (!buf_allocated) + return -ENOMEM; + + snd_pcmtst_hw.channels_max = buf_allocated; + + err = init_debug_files(buf_allocated); + if (err) + return err; + err = platform_device_register(&pcmtst_pdev); + if (err) + return err; + err = platform_driver_register(&pcmtst_pdrv); + if (err) + platform_device_unregister(&pcmtst_pdev); + return err; +} + +static void __exit mod_exit(void) +{ + clear_debug_files(); + free_pattern_buffers(); + + platform_driver_unregister(&pcmtst_pdrv); + platform_device_unregister(&pcmtst_pdev); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Orlov"); +module_init(mod_init); +module_exit(mod_exit); -- cgit v1.2.3 From 15253079ca300160c92c9c0ee2541836463043f6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 10 Jun 2023 15:26:37 +0100 Subject: ALSA: hda: Use maple tree register cache HDA can only support single register read and write operations so does not benefit from block writes. This means it gets no benefit from using the rbtree register cache over the maple tree register cache so convert it to use maple trees instead, it is more modern. Signed-off-by: Mark Brown Link: https://lore.kernel.org/r/20230609-alsa-hda-maple-v1-1-a2b725c8b8f5@kernel.org Signed-off-by: Takashi Iwai --- sound/hda/hdac_regmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/hda/hdac_regmap.c b/sound/hda/hdac_regmap.c index fe3587547cfe..2caa1f9b858e 100644 --- a/sound/hda/hdac_regmap.c +++ b/sound/hda/hdac_regmap.c @@ -358,7 +358,7 @@ static const struct regmap_config hda_regmap_cfg = { .writeable_reg = hda_writeable_reg, .readable_reg = hda_readable_reg, .volatile_reg = hda_volatile_reg, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_read = hda_reg_read, .reg_write = hda_reg_write, .use_single_read = true, -- cgit v1.2.3 From 81c29435073355b8194986a2193d3e7b9d449225 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 11 Jun 2023 23:44:44 +0900 Subject: ALSA: firewire: use 'GPL' string for module license contributed by Takashi Sakamoto In MODULE_LICENSE macro, "GPL" string obsoletes "GPL v2" string by a commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity"). This commit uses the preferable expression. Signed-off-by: Takashi Sakamoto Link: https://lore.kernel.org/r/20230611144445.221529-2-o-takashi@sakamocchi.jp Signed-off-by: Takashi Iwai --- sound/firewire/bebob/bebob.c | 2 +- sound/firewire/digi00x/digi00x.c | 2 +- sound/firewire/fireface/ff.c | 2 +- sound/firewire/fireworks/fireworks.c | 2 +- sound/firewire/motu/motu.c | 2 +- sound/firewire/tascam/tascam.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 06a7ced218e2..2ba5962beb30 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -15,7 +15,7 @@ MODULE_DESCRIPTION("BridgeCo BeBoB driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 995302808c27..704ae2a5316b 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -9,7 +9,7 @@ MODULE_DESCRIPTION("Digidesign Digi 002/003 family Driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); #define VENDOR_DIGIDESIGN 0x00a07e #define MODEL_CONSOLE 0x000001 diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 448e972028d9..82241058ea14 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -11,7 +11,7 @@ MODULE_DESCRIPTION("RME Fireface series Driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static void name_card(struct snd_ff *ff) { diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index ffb6dd796243..dd4298876ac0 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -18,7 +18,7 @@ MODULE_DESCRIPTION("Echo Fireworks driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index f8b7fe38751c..d73599eb7d5a 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -11,7 +11,7 @@ MODULE_DESCRIPTION("MOTU FireWire driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { /* mode 0 */ diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index eb58d3fcf087..86880089de28 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -9,7 +9,7 @@ MODULE_DESCRIPTION("TASCAM FireWire series Driver"); MODULE_AUTHOR("Takashi Sakamoto "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static const struct snd_tscm_spec model_specs[] = { { -- cgit v1.2.3 From 9b4469410cf9a0fcbccc92c480fd42f7c815a745 Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Sun, 11 Jun 2023 23:44:45 +0900 Subject: ALSA: firewire: use 'GPL' string for module license contributed by Clemens Ladisch In MODULE_LICENSE macro, "GPL" string obsoletes "GPL v2" string by a commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity"). This commit uses the preferable expression. Signed-off-by: Takashi Sakamoto Link: https://lore.kernel.org/r/20230611144445.221529-3-o-takashi@sakamocchi.jp Signed-off-by: Takashi Iwai --- sound/firewire/dice/dice.c | 2 +- sound/firewire/isight.c | 2 +- sound/firewire/lib.c | 2 +- sound/firewire/oxfw/oxfw.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c index 6036a5edbcb8..6c93e6e4982c 100644 --- a/sound/firewire/dice/dice.c +++ b/sound/firewire/dice/dice.c @@ -9,7 +9,7 @@ MODULE_DESCRIPTION("DICE driver"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); #define OUI_WEISS 0x001c6a #define OUI_LOUD 0x000ff2 diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 6655af53b367..806f82c9ceee 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -77,7 +77,7 @@ struct audio_payload { MODULE_DESCRIPTION("iSight audio driver"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); static struct fw_iso_packet audio_packet = { .payload_length = sizeof(struct audio_payload), diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index e0a2337e8f27..654e1a6050a9 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -69,4 +69,4 @@ EXPORT_SYMBOL(snd_fw_transaction); MODULE_DESCRIPTION("FireWire audio helper functions"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index b496f87841ae..9523479fa94a 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -32,7 +32,7 @@ MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver"); MODULE_AUTHOR("Clemens Ladisch "); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); MODULE_ALIAS("snd-firewire-speakers"); MODULE_ALIAS("snd-scs1x"); -- cgit v1.2.3 From e375b8a045873cf5fb8bf61bf9a0ddfcd484243a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:45 +0200 Subject: ALSA: ump: Add more attributes to UMP EP and FB info Add a few more fields to snd_ump_endpoint_info and snd_ump_block_info that are added in the new v1.1 spec. Those are filled by the UMP Stream messages. The rawmidi protocol version is bumped to 2.0.4 to indicate those updates. Also, update the proc outputs to show the newly introduced fields. Link: https://lore.kernel.org/r/20230612081054.17200-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 18 +++++++++++++++--- sound/core/ump.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 5c5f41dd4001..79ee48b2ed6d 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -708,7 +708,7 @@ enum { * Raw MIDI section - /dev/snd/midi?? */ -#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 3) +#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 4) enum { SNDRV_RAWMIDI_STREAM_OUTPUT = 0, @@ -797,7 +797,11 @@ struct snd_ump_endpoint_info { unsigned int protocol; /* current protocol */ unsigned int num_blocks; /* # of function blocks */ unsigned short version; /* UMP major/minor version */ - unsigned short padding[7]; + unsigned short family_id; /* MIDI device family ID */ + unsigned short model_id; /* MIDI family model ID */ + unsigned int manufacturer_id; /* MIDI manufacturer ID */ + unsigned char sw_revision[4]; /* software revision */ + unsigned short padding; unsigned char name[128]; /* endpoint name string */ unsigned char product_id[128]; /* unique product id string */ unsigned char reserved[32]; @@ -812,6 +816,12 @@ struct snd_ump_endpoint_info { #define SNDRV_UMP_BLOCK_IS_MIDI1 (1U << 0) /* MIDI 1.0 port w/o restrict */ #define SNDRV_UMP_BLOCK_IS_LOWSPEED (1U << 1) /* 31.25Kbps B/W MIDI1 port */ +/* UMP block user-interface hint */ +#define SNDRV_UMP_BLOCK_UI_HINT_UNKNOWN 0x00 +#define SNDRV_UMP_BLOCK_UI_HINT_RECEIVER 0x01 +#define SNDRV_UMP_BLOCK_UI_HINT_SENDER 0x02 +#define SNDRV_UMP_BLOCK_UI_HINT_BOTH 0x03 + /* UMP groups and blocks */ #define SNDRV_UMP_MAX_GROUPS 16 #define SNDRV_UMP_MAX_BLOCKS 32 @@ -825,7 +835,9 @@ struct snd_ump_block_info { unsigned char active; /* Activeness */ unsigned char first_group; /* first group ID */ unsigned char num_groups; /* number of groups */ - unsigned char padding[3]; + unsigned char midi_ci_version; /* MIDI-CI support version */ + unsigned char sysex8_streams; /* max number of sysex8 streams */ + unsigned char ui_hint; /* user interface hint */ unsigned int flags; /* various info flags */ unsigned char name[128]; /* block name string */ unsigned char reserved[32]; diff --git a/sound/core/ump.c b/sound/core/ump.c index 69993cad6772..839873fb0f33 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -448,6 +448,20 @@ static const char *ump_direction_string(int dir) } } +static const char *ump_ui_hint_string(int dir) +{ + switch (dir) { + case SNDRV_UMP_BLOCK_UI_HINT_RECEIVER: + return "receiver"; + case SNDRV_UMP_BLOCK_UI_HINT_SENDER: + return "sender"; + case SNDRV_UMP_BLOCK_UI_HINT_BOTH: + return "both"; + default: + return "unknown"; + } +} + /* Additional proc file output */ static void snd_ump_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer) @@ -461,6 +475,17 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version); snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps); snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol); + if (ump->info.version) { + snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n", + ump->info.manufacturer_id); + snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id); + snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id); + snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n", + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + } snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); list_for_each_entry(fb, &ump->block_list, list) { @@ -476,6 +501,14 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, snd_iprintf(buffer, " Is MIDI1: %s%s\n", (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No", (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : ""); + if (ump->info.version) { + snd_iprintf(buffer, " MIDI-CI Version: %d\n", + fb->info.midi_ci_version); + snd_iprintf(buffer, " Sysex8 Streams: %d\n", + fb->info.sysex8_streams); + snd_iprintf(buffer, " UI Hint: %s\n", + ump_ui_hint_string(fb->info.ui_hint)); + } snd_iprintf(buffer, "\n"); } } -- cgit v1.2.3 From 37e0e14128e0685267dc5c037bf655421a6ce2ea Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:46 +0200 Subject: ALSA: ump: Support UMP Endpoint and Function Block parsing This patch adds the basic support for UMP Endpoint and UMP Function Block parsing, which are extended in the new UMP v1.1 spec. The patch provides a new helper function to perform the query of the UMP Endpoint information and builds up the UMP blocks based on UMP Function Block information. For the communication over the UMP Endpoint, it opens the rawmidi device once internally, inquiries the UMP Endpoint and Function Block info by sending new UMP Stream messages, and waits for the response for each query. The new UMP spec allows to update the FB info and change its associated groups or its activeness on the fly, too. For catching it, the UMP core keeps watching the incoming UMP messages, and snd_ump_receive() handles the incoming UMP Stream messages to refresh the FB info. Link: https://lore.kernel.org/r/20230612081054.17200-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 83 +++++++++++ include/sound/ump_msg.h | 225 +++++++++++++++++++++++++++++ sound/core/ump.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 684 insertions(+) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index e4fdf7cccf12..aef4748842d0 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -24,6 +24,13 @@ struct snd_ump_endpoint { void *private_data; void (*private_free)(struct snd_ump_endpoint *ump); + /* UMP Stream message processing */ + u32 stream_wait_for; /* expected stream message status */ + bool stream_finished; /* set when message has been processed */ + bool parsed; /* UMP / FB parse finished? */ + wait_queue_head_t stream_wait; + struct snd_rawmidi_file stream_rfile; + struct list_head block_list; /* list of snd_ump_block objects */ /* intermediate buffer for UMP input */ @@ -80,6 +87,7 @@ struct snd_ump_block { int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, int output, int input, struct snd_ump_endpoint **ump_ret); +int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump); int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk, unsigned int direction, unsigned int first_group, unsigned int num_groups, struct snd_ump_block **blk_ret); @@ -109,6 +117,8 @@ enum { UMP_MSG_TYPE_DATA = 0x03, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE = 0x04, UMP_MSG_TYPE_EXTENDED_DATA = 0x05, + UMP_MSG_TYPE_FLEX_DATA = 0x0d, + UMP_MSG_TYPE_STREAM = 0x0f, }; /* MIDI 2.0 SysEx / Data Status; same values for both 7-bit and 8-bit SysEx */ @@ -119,6 +129,62 @@ enum { UMP_SYSEX_STATUS_END = 3, }; +/* UMP Utility Type Status (type 0x0) */ +enum { + UMP_UTILITY_MSG_STATUS_NOOP = 0x00, + UMP_UTILITY_MSG_STATUS_JR_CLOCK = 0x01, + UMP_UTILITY_MSG_STATUS_JR_TSTAMP = 0x02, + UMP_UTILITY_MSG_STATUS_DCTPQ = 0x03, + UMP_UTILITY_MSG_STATUS_DC = 0x04, +}; + +/* UMP Stream Message Status (type 0xf) */ +enum { + UMP_STREAM_MSG_STATUS_EP_DISCOVERY = 0x00, + UMP_STREAM_MSG_STATUS_EP_INFO = 0x01, + UMP_STREAM_MSG_STATUS_DEVICE_INFO = 0x02, + UMP_STREAM_MSG_STATUS_EP_NAME = 0x03, + UMP_STREAM_MSG_STATUS_PRODUCT_ID = 0x04, + UMP_STREAM_MSG_STATUS_STREAM_CFG_REQUEST = 0x05, + UMP_STREAM_MSG_STATUS_STREAM_CFG = 0x06, + UMP_STREAM_MSG_STATUS_FB_DISCOVERY = 0x10, + UMP_STREAM_MSG_STATUS_FB_INFO = 0x11, + UMP_STREAM_MSG_STATUS_FB_NAME = 0x12, + UMP_STREAM_MSG_STATUS_START_CLIP = 0x20, + UMP_STREAM_MSG_STATUS_END_CLIP = 0x21, +}; + +/* UMP Endpoint Discovery filter bitmap */ +enum { + UMP_STREAM_MSG_REQUEST_EP_INFO = (1U << 0), + UMP_STREAM_MSG_REQUEST_DEVICE_INFO = (1U << 1), + UMP_STREAM_MSG_REQUEST_EP_NAME = (1U << 2), + UMP_STREAM_MSG_REQUEST_PRODUCT_ID = (1U << 3), + UMP_STREAM_MSG_REQUEST_STREAM_CFG = (1U << 4), +}; + +/* UMP Function Block Discovery filter bitmap */ +enum { + UMP_STREAM_MSG_REQUEST_FB_INFO = (1U << 0), + UMP_STREAM_MSG_REQUEST_FB_NAME = (1U << 1), +}; + +/* UMP Endpoint Info capability bits (used for protocol request/notify, too) */ +enum { + UMP_STREAM_MSG_EP_INFO_CAP_TXJR = (1U << 0), /* Sending JRTS */ + UMP_STREAM_MSG_EP_INFO_CAP_RXJR = (1U << 1), /* Receiving JRTS */ + UMP_STREAM_MSG_EP_INFO_CAP_MIDI1 = (1U << 8), /* MIDI 1.0 */ + UMP_STREAM_MSG_EP_INFO_CAP_MIDI2 = (1U << 9), /* MIDI 2.0 */ +}; + +/* UMP EP / FB name string format; same as SysEx string handling */ +enum { + UMP_STREAM_MSG_FORMAT_SINGLE = 0, + UMP_STREAM_MSG_FORMAT_START = 1, + UMP_STREAM_MSG_FORMAT_CONTINUE = 2, + UMP_STREAM_MSG_FORMAT_END = 3, +}; + /* * Helpers for retrieving / filling bits from UMP */ @@ -172,4 +238,21 @@ static inline unsigned char ump_sysex_message_length(u32 data) return (data >> 16) & 0xf; } +/* For Stream Messages */ +static inline unsigned char ump_stream_message_format(u32 data) +{ + return (data >> 26) & 0x03; +} + +static inline unsigned int ump_stream_message_status(u32 data) +{ + return (data >> 16) & 0x3ff; +} + +static inline u32 ump_stream_compose(unsigned char status, unsigned short form) +{ + return (UMP_MSG_TYPE_STREAM << 28) | ((u32)form << 26) | + ((u32)status << 16); +} + #endif /* __SOUND_UMP_H */ diff --git a/include/sound/ump_msg.h b/include/sound/ump_msg.h index a594ef951b54..72f60ddfea75 100644 --- a/include/sound/ump_msg.h +++ b/include/sound/ump_msg.h @@ -537,4 +537,229 @@ union snd_ump_midi2_msg { u32 raw[2]; }; +/* UMP Stream Message: Endpoint Discovery (128bit) */ +struct snd_ump_stream_msg_ep_discovery { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 ump_version_major:8; + u32 ump_version_minor:8; + /* 1 */ + u32 reserved:24; + u32 filter_bitmap:8; + /* 2-3 */ + u32 reserved2[2]; +#else + /* 0 */ + u32 ump_version_minor:8; + u32 ump_version_major:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 filter_bitmap:8; + u32 reserved:24; + /* 2-3 */ + u32 reserved2[2]; +#endif +} __packed; + +/* UMP Stream Message: Endpoint Info Notification (128bit) */ +struct snd_ump_stream_msg_ep_info { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 ump_version_major:8; + u32 ump_version_minor:8; + /* 1 */ + u32 static_function_block:1; + u32 num_function_blocks:7; + u32 reserved:8; + u32 protocol:8; + u32 reserved2:6; + u32 jrts:2; + /* 2-3 */ + u32 reserved3[2]; +#else + /* 0 */ + u32 ump_version_minor:8; + u32 ump_version_major:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 jrts:2; + u32 reserved2:6; + u32 protocol:8; + u32 reserved:8; + u32 num_function_blocks:7; + u32 static_function_block:1; + /* 2-3 */ + u32 reserved3[2]; +#endif +} __packed; + +/* UMP Stream Message: Device Info Notification (128bit) */ +struct snd_ump_stream_msg_devince_info { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 reserved:16; + /* 1 */ + u32 manufacture_id; + /* 2 */ + u8 family_lsb; + u8 family_msb; + u8 model_lsb; + u8 model_msb; + /* 3 */ + u32 sw_revision; +#else + /* 0 */ + u32 reserved:16; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 manufacture_id; + /* 2 */ + u8 model_msb; + u8 model_lsb; + u8 family_msb; + u8 family_lsb; + /* 3 */ + u32 sw_revision; +#endif +} __packed; + +/* UMP Stream Message: Stream Config Request / Notification (128bit) */ +struct snd_ump_stream_msg_stream_cfg { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 protocol:8; + u32 reserved:6; + u32 jrts:2; + /* 1-3 */ + u32 reserved2[3]; +#else + /* 0 */ + u32 jrts:2; + u32 reserved:6; + u32 protocol:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1-3 */ + u32 reserved2[3]; +#endif +} __packed; + +/* UMP Stream Message: Function Block Discovery (128bit) */ +struct snd_ump_stream_msg_fb_discovery { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 function_block_id:8; + u32 filter:8; + /* 1-3 */ + u32 reserved[3]; +#else + /* 0 */ + u32 filter:8; + u32 function_block_id:8; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1-3 */ + u32 reserved[3]; +#endif +} __packed; + +/* UMP Stream Message: Function Block Info Notification (128bit) */ +struct snd_ump_stream_msg_fb_info { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u32 type:4; + u32 format:2; + u32 status:10; + u32 active:1; + u32 function_block_id:7; + u32 reserved:2; + u32 ui_hint:2; + u32 midi_10:2; + u32 direction:2; + /* 1 */ + u32 first_group:8; + u32 num_groups:8; + u32 midi_ci_version:8; + u32 sysex8_streams:8; + /* 2-3 */ + u32 reserved2[2]; +#else + /* 0 */ + u32 direction:2; + u32 midi_10:2; + u32 ui_hint:2; + u32 reserved:2; + u32 function_block_id:7; + u32 active:1; + u32 status:10; + u32 format:2; + u32 type:4; + /* 1 */ + u32 sysex8_streams:8; + u32 midi_ci_version:8; + u32 num_groups:8; + u32 first_group:8; + /* 2-3 */ + u32 reserved2[2]; +#endif +} __packed; + +/* UMP Stream Message: Function Block Name Notification (128bit) */ +struct snd_ump_stream_msg_fb_name { +#ifdef __BIG_ENDIAN_BITFIELD + /* 0 */ + u16 type:4; + u16 format:2; + u16 status:10; + u8 function_block_id; + u8 name0; + /* 1-3 */ + u8 name[12]; +#else + /* 0 */ + u8 name0; + u8 function_block_id; + u16 status:10; + u16 format:2; + u16 type:4; + /* 1-3 */ + u8 name[12]; // FIXME: byte order +#endif +} __packed; + +/* MIDI 2.0 Stream Messages (128bit) */ +union snd_ump_stream_msg { + struct snd_ump_stream_msg_ep_discovery ep_discovery; + struct snd_ump_stream_msg_ep_info ep_info; + struct snd_ump_stream_msg_devince_info device_info; + struct snd_ump_stream_msg_stream_cfg stream_cfg; + struct snd_ump_stream_msg_fb_discovery fb_discovery; + struct snd_ump_stream_msg_fb_info fb_info; + struct snd_ump_stream_msg_fb_name fb_name; + u32 raw[4]; +}; + #endif /* __SOUND_UMP_MSG_H */ diff --git a/sound/core/ump.c b/sound/core/ump.c index 839873fb0f33..7df50f0affe9 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -30,6 +30,8 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream, int up); static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream); +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size); #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) static int process_legacy_output(struct snd_ump_endpoint *ump, u32 *buffer, int count); @@ -133,6 +135,7 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device, return -ENOMEM; INIT_LIST_HEAD(&ump->block_list); mutex_init(&ump->open_mutex); + init_waitqueue_head(&ump->stream_wait); #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) spin_lock_init(&ump->legacy_locks[0]); spin_lock_init(&ump->legacy_locks[1]); @@ -302,6 +305,7 @@ int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count) n = snd_ump_receive_ump_val(ump, *p++); if (!n) continue; + ump_handle_stream_msg(ump, ump->input_buf, n); #if IS_ENABLED(CONFIG_SND_SEQUENCER) if (ump->seq_ops) ump->seq_ops->input_receive(ump, ump->input_buf, n); @@ -513,6 +517,378 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, } } +/* + * UMP endpoint and function block handling + */ + +/* open / close UMP streams for the internal stream msg communication */ +static int ump_request_open(struct snd_ump_endpoint *ump) +{ + return snd_rawmidi_kernel_open(&ump->core, 0, + SNDRV_RAWMIDI_LFLG_OUTPUT, + &ump->stream_rfile); +} + +static void ump_request_close(struct snd_ump_endpoint *ump) +{ + snd_rawmidi_kernel_release(&ump->stream_rfile); +} + +/* request a command and wait for the given response; + * @req1 and @req2 are u32 commands + * @reply is the expected UMP stream status + */ +static int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2, + u32 reply) +{ + u32 buf[4]; + + ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n", + __func__, req1, req2, reply); + memset(buf, 0, sizeof(buf)); + buf[0] = req1; + buf[1] = req2; + ump->stream_finished = 0; + ump->stream_wait_for = reply; + snd_rawmidi_kernel_write(ump->stream_rfile.output, + (unsigned char *)&buf, 16); + wait_event_timeout(ump->stream_wait, ump->stream_finished, + msecs_to_jiffies(500)); + if (!READ_ONCE(ump->stream_finished)) { + ump_dbg(ump, "%s: request timed out\n", __func__); + return -ETIMEDOUT; + } + ump->stream_finished = 0; + ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n", + __func__, buf[0], buf[1], buf[2], buf[3]); + return 0; +} + +/* append the received letters via UMP packet to the given string buffer; + * return 1 if the full string is received or 0 to continue + */ +static int ump_append_string(struct snd_ump_endpoint *ump, char *dest, + int maxsize, const u32 *buf, int offset) +{ + unsigned char format; + int c; + + format = ump_stream_message_format(buf[0]); + if (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_START) { + c = 0; + } else { + c = strlen(dest); + if (c >= maxsize - 1) + return 1; + } + + for (; offset < 16; offset++) { + dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8; + if (!dest[c]) + break; + if (++c >= maxsize - 1) + break; + } + dest[c] = 0; + return (format == UMP_STREAM_MSG_FORMAT_SINGLE || + format == UMP_STREAM_MSG_FORMAT_END); +} + +/* handle EP info stream message; update the UMP attributes */ +static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.version = (buf->ep_info.ump_version_major << 8) | + buf->ep_info.ump_version_minor; + ump->info.num_blocks = buf->ep_info.num_function_blocks; + if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) { + ump_info(ump, "Invalid function blocks %d, fallback to 1\n", + ump->info.num_blocks); + ump->info.num_blocks = 1; + } + + ump->info.protocol_caps = (buf->ep_info.protocol << 8) | + buf->ep_info.jrts; + + ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n", + ump->info.version, ump->info.num_blocks, ump->info.protocol_caps); + return 1; /* finished */ +} + +/* handle EP device info stream message; update the UMP attributes */ +static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f; + ump->info.family_id = (buf->device_info.family_msb << 8) | + buf->device_info.family_lsb; + ump->info.model_id = (buf->device_info.model_msb << 8) | + buf->device_info.model_lsb; + ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f; + ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f; + ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f; + ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f; + ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n", + ump->info.manufacturer_id, + ump->info.family_id, + ump->info.model_id, + ump->info.sw_revision[0], + ump->info.sw_revision[1], + ump->info.sw_revision[2], + ump->info.sw_revision[3]); + return 1; /* finished */ +} + +/* handle EP name stream message; update the UMP name string */ +static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.name, sizeof(ump->info.name), + buf->raw, 2); +} + +/* handle EP product id stream message; update the UMP product_id string */ +static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + return ump_append_string(ump, ump->info.product_id, + sizeof(ump->info.product_id), + buf->raw, 2); +} + +/* handle EP stream config message; update the UMP protocol */ +static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + ump->info.protocol = + (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; + ump_dbg(ump, "Current protocol = %x (caps = %x)\n", + ump->info.protocol, ump->info.protocol_caps); + return 1; /* finished */ +} + +/* Extract Function Block info from UMP packet */ +static void fill_fb_info(struct snd_ump_endpoint *ump, + struct snd_ump_block_info *info, + const union snd_ump_stream_msg *buf) +{ + info->direction = buf->fb_info.direction; + info->ui_hint = buf->fb_info.ui_hint; + info->first_group = buf->fb_info.first_group; + info->num_groups = buf->fb_info.num_groups; + info->flags = buf->fb_info.midi_10; + info->active = buf->fb_info.active; + info->midi_ci_version = buf->fb_info.midi_ci_version; + info->sysex8_streams = buf->fb_info.sysex8_streams; + + ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n", + info->block_id, info->direction, info->active, + info->first_group, info->num_groups, info->midi_ci_version, + info->sysex8_streams, info->flags); +} + +/* handle FB info message; update FB info if the block is present */ +static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + + blk = buf->fb_info.function_block_id; + fb = snd_ump_get_block(ump, blk); + if (fb) { + fill_fb_info(ump, &fb->info, buf); + } else if (ump->parsed) { + /* complain only if updated after parsing */ + ump_info(ump, "Function Block Info Update for non-existing block %d\n", + blk); + return -ENODEV; + } + return 1; /* finished */ +} + +/* handle FB name message; update the FB name string */ +static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump, + const union snd_ump_stream_msg *buf) +{ + unsigned char blk; + struct snd_ump_block *fb; + + blk = buf->fb_name.function_block_id; + fb = snd_ump_get_block(ump, blk); + if (!fb) + return -ENODEV; + + return ump_append_string(ump, fb->info.name, sizeof(fb->info.name), + buf->raw, 3); +} + +static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk) +{ + struct snd_ump_block *fb; + unsigned char direction, first_group, num_groups; + const union snd_ump_stream_msg *buf = + (const union snd_ump_stream_msg *)ump->input_buf; + u32 msg; + int err; + + /* query the FB info once */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get FB info for block %d\n", blk); + return err; + } + + /* the last input must be the FB info */ + if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) { + ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw); + return -EINVAL; + } + + direction = buf->fb_info.direction; + first_group = buf->fb_info.first_group; + num_groups = buf->fb_info.num_groups; + + err = snd_ump_block_new(ump, blk, direction, first_group, num_groups, + &fb); + if (err < 0) + return err; + + fill_fb_info(ump, &fb->info, buf); + + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) | + (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME; + err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME); + if (err) + ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk); + + return 0; +} + +/* handle stream messages, called from snd_ump_receive() */ +static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, + const u32 *buf, int size) +{ + const union snd_ump_stream_msg *msg; + unsigned int status; + int ret; + + BUILD_BUG_ON(sizeof(*msg) != 16); + ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n", + buf[0], buf[1], buf[2], buf[3]); + + if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM) + return; + + msg = (const union snd_ump_stream_msg *)buf; + status = ump_stream_message_status(*buf); + switch (status) { + case UMP_STREAM_MSG_STATUS_EP_INFO: + ret = ump_handle_ep_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_DEVICE_INFO: + ret = ump_handle_device_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_EP_NAME: + ret = ump_handle_ep_name_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_PRODUCT_ID: + ret = ump_handle_product_id_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_STREAM_CFG: + ret = ump_handle_stream_cfg_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_INFO: + ret = ump_handle_fb_info_msg(ump, msg); + break; + case UMP_STREAM_MSG_STATUS_FB_NAME: + ret = ump_handle_fb_name_msg(ump, msg); + break; + default: + return; + } + + /* when the message has been processed fully, wake up */ + if (ret > 0 && ump->stream_wait_for == status) { + WRITE_ONCE(ump->stream_finished, 1); + wake_up(&ump->stream_wait); + } +} + +/** + * snd_ump_parse_endpoint - parse endpoint and create function blocks + * @ump: UMP object + * + * Returns 0 for successful parse, -ENODEV if device doesn't respond + * (or the query is unsupported), or other error code for serious errors. + */ +int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump) +{ + int blk, err; + u32 msg; + + if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) + return -ENODEV; + + err = ump_request_open(ump); + if (err < 0) { + ump_dbg(ump, "Unable to open rawmidi device: %d\n", err); + return err; + } + + /* Check Endpoint Information */ + msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) | + 0x0101; /* UMP version 1.1 */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO, + UMP_STREAM_MSG_STATUS_EP_INFO); + if (err < 0) { + ump_dbg(ump, "Unable to get UMP EP info\n"); + goto error; + } + + /* Request Endpoint Device Info */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO, + UMP_STREAM_MSG_STATUS_DEVICE_INFO); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP device info\n"); + + /* Request Endpoint Name */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME, + UMP_STREAM_MSG_STATUS_EP_NAME); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP name string\n"); + + /* Request Endpoint Product ID */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID, + UMP_STREAM_MSG_STATUS_PRODUCT_ID); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP product ID string\n"); + + /* Get the current stream configuration */ + err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG, + UMP_STREAM_MSG_STATUS_STREAM_CFG); + if (err < 0) + ump_dbg(ump, "Unable to get UMP EP stream config\n"); + + /* Query and create blocks from Function Blocks */ + for (blk = 0; blk < ump->info.num_blocks; blk++) { + err = create_block_from_fb_info(ump, blk); + if (err < 0) + continue; + } + + error: + ump->parsed = true; + ump_request_close(ump); + if (err == -ETIMEDOUT) + err = -ENODEV; + return err; +} +EXPORT_SYMBOL_GPL(snd_ump_parse_endpoint); + #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) /* * Legacy rawmidi support -- cgit v1.2.3 From 54852e8f401a70b3a0197737122be523639dc62f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:47 +0200 Subject: ALSA: usb-audio: Parse UMP Endpoint and Function Blocks at first Try to parse the UMP Endpoint and UMP Function Blocks for building the topology at first. Only when those are missing (e.g. on an older USB MIDI 2.0 spec or a unidirectional endpoint), the driver still creates blocks based on USB group terminal block information as fallback. Link: https://lore.kernel.org/r/20230612081054.17200-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 341783418a6a..fad094e15999 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -80,6 +80,7 @@ struct snd_usb_midi2_ump { struct snd_usb_midi2_endpoint *eps[2]; /* USB MIDI endpoints */ int index; /* rawmidi device index */ unsigned char usb_block_id; /* USB GTB id used for finding a pair */ + bool ump_parsed; /* Parsed UMP 1.1 EP/FB info*/ struct list_head list; /* list to umidi->rawmidi_list */ }; @@ -786,6 +787,31 @@ static int find_matching_ep_partner(struct snd_usb_midi2_interface *umidi, return 0; } +/* Call UMP helper to parse UMP endpoints; + * this needs to be called after starting the input streams for bi-directional + * communications + */ +static int parse_ump_endpoints(struct snd_usb_midi2_interface *umidi) +{ + struct snd_usb_midi2_ump *rmidi; + int err; + + list_for_each_entry(rmidi, &umidi->rawmidi_list, list) { + if (!rmidi->ump || + !(rmidi->ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX)) + continue; + err = snd_ump_parse_endpoint(rmidi->ump); + if (!err) { + rmidi->ump_parsed = true; + } else { + if (err == -ENOMEM) + return err; + /* fall back to GTB later */ + } + } + return 0; +} + /* create a UMP block from a GTB entry */ static int create_gtb_block(struct snd_usb_midi2_ump *rmidi, int dir, int blk) { @@ -856,7 +882,7 @@ static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) if (!rmidi->ump) continue; /* Blocks have been already created? */ - if (rmidi->ump->info.num_blocks) + if (rmidi->ump_parsed || rmidi->ump->info.num_blocks) continue; /* loop over GTBs */ for (dir = 0; dir < 2; dir++) { @@ -1110,6 +1136,12 @@ int snd_usb_midi_v2_create(struct snd_usb_audio *chip, goto error; } + err = parse_ump_endpoints(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse UMP endpoint\n"); + goto error; + } + err = create_blocks_from_gtb(umidi); if (err < 0) { usb_audio_err(chip, "Failed to create GTB blocks\n"); -- cgit v1.2.3 From 960a1149c8fa70c221c70eaa13903ff873ba1873 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:48 +0200 Subject: ALSA: usb-audio: Add midi2_ump_probe option Add a new option to enable/disable the UMP Endpoint probing. Some firmware seems screwed up when such a new command issued, and this option allows user to suppress it. Link: https://lore.kernel.org/r/20230612081054.17200-5-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/midi2.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'sound') diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index fad094e15999..13fa1978267a 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -27,6 +27,10 @@ static bool midi2_enable = true; module_param(midi2_enable, bool, 0444); MODULE_PARM_DESC(midi2_enable, "Enable MIDI 2.0 support."); +static bool midi2_ump_probe = true; +module_param(midi2_ump_probe, bool, 0444); +MODULE_PARM_DESC(midi2_ump_probe, "Probe UMP v1.1 support at first."); + /* stream direction; just shorter names */ enum { STR_OUT = SNDRV_RAWMIDI_STREAM_OUTPUT, @@ -1136,10 +1140,12 @@ int snd_usb_midi_v2_create(struct snd_usb_audio *chip, goto error; } - err = parse_ump_endpoints(umidi); - if (err < 0) { - usb_audio_err(chip, "Failed to parse UMP endpoint\n"); - goto error; + if (midi2_ump_probe) { + err = parse_ump_endpoints(umidi); + if (err < 0) { + usb_audio_err(chip, "Failed to parse UMP endpoint\n"); + goto error; + } } err = create_blocks_from_gtb(umidi); -- cgit v1.2.3 From 5437ac9bad639bb9112e1a749acbe4a143562cdc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:49 +0200 Subject: ALSA: seq: ump: Handle groupless messages The UMP Utility and Stream messages are "groupless", i.e. an incoming groupless packet should be sent only to the UMP EP port, and the event with the groupless message is sent to UMP EP as is without the group translation per port. Also, the former reserved bit 0 for the client group filter is now used for groupless events. When the bit 0 is set, the groupless events are filtered out and skipped. Link: https://lore.kernel.org/r/20230612081054.17200-6-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 3 +++ include/uapi/sound/asequencer.h | 5 ++++- sound/core/seq/seq_ump_client.c | 5 ++++- sound/core/seq/seq_ump_convert.c | 3 +++ 4 files changed, 14 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index aef4748842d0..5b50a2fc0d79 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -255,4 +255,7 @@ static inline u32 ump_stream_compose(unsigned char status, unsigned short form) ((u32)status << 16); } +#define ump_is_groupless_msg(type) \ + ((type) == UMP_MSG_TYPE_UTILITY || (type) == UMP_MSG_TYPE_STREAM) + #endif /* __SOUND_UMP_H */ diff --git a/include/uapi/sound/asequencer.h b/include/uapi/sound/asequencer.h index 5e91243665d8..b5bc8604efe8 100644 --- a/include/uapi/sound/asequencer.h +++ b/include/uapi/sound/asequencer.h @@ -362,7 +362,10 @@ struct snd_seq_client_info { int card; /* RO: card number[kernel] */ int pid; /* RO: pid[user] */ unsigned int midi_version; /* MIDI version */ - unsigned int group_filter; /* UMP group filter bitmap (for 1-based Group indices) */ + unsigned int group_filter; /* UMP group filter bitmap + * (bit 0 = groupless messages, + * bit 1-16 = messages for groups 1-16) + */ char reserved[48]; /* for future use */ }; diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index e24833804094..7739fb3ebf34 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -73,7 +73,10 @@ static void seq_ump_input_receive(struct snd_ump_endpoint *ump, if (!client->opened[STR_IN]) return; - ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); + if (ump_is_groupless_msg(ump_message_type(*val))) + ev.source.port = 0; /* UMP EP port */ + else + ev.source.port = ump_group_to_seq_port(ump_message_group(*val)); ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS; ev.flags = SNDRV_SEQ_EVENT_UMP; memcpy(ev.ump, val, words << 2); diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index 14ba6fed9dd1..eb1d86ff6166 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -534,6 +534,8 @@ static bool ump_event_filtered(struct snd_seq_client *dest, unsigned char group; group = ump_message_group(ev->ump[0]); + if (ump_is_groupless_msg(ump_message_type(ev->ump[0]))) + return dest->group_filter & (1U << 0); /* check the bitmap for 1-based group number */ return dest->group_filter & (1U << (group + 1)); } @@ -565,6 +567,7 @@ int snd_seq_deliver_from_ump(struct snd_seq_client *source, event, atomic, hop); /* non-EP port and different group is set? */ if (dest_port->ump_group && + !ump_is_groupless_msg(type) && ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group) return deliver_with_group_convert(dest, dest_port, ump_ev, atomic, hop); -- cgit v1.2.3 From 4a16a3af05712e7fd5a205f34e2908055bd9fb5e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:50 +0200 Subject: ALSA: seq: ump: Handle FB info update This patch implements the handling of the dynamic update of FB info. When the FB info update is received after the initial parsing, it means the dynamic FB info update. We compare the result, and if the actual update is detected, it's notified via a new ops, notify_fb_change, to the sequencer client, and the corresponding sequencer ports are updated accordingly. Link: https://lore.kernel.org/r/20230612081054.17200-7-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 2 ++ sound/core/seq/seq_ump_client.c | 61 +++++++++++++++++++++++++++++++++++++++++ sound/core/ump.c | 49 +++++++++++++++++++++++++++++---- 3 files changed, 106 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 5b50a2fc0d79..0e9c048346fa 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -70,6 +70,8 @@ struct snd_ump_ops { struct snd_seq_ump_ops { void (*input_receive)(struct snd_ump_endpoint *ump, const u32 *data, int words); + int (*notify_fb_change)(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb); }; struct snd_ump_block { diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 7739fb3ebf34..2f93d76b05ce 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -48,6 +48,7 @@ struct seq_ump_client { struct seq_ump_input_buffer input; /* input parser context */ struct seq_ump_group groups[SNDRV_UMP_MAX_GROUPS]; /* table of groups */ void *ump_info[SNDRV_UMP_MAX_BLOCKS + 1]; /* shadow of seq client ump_info */ + struct work_struct group_notify_work; /* FB change notification */ }; /* number of 32bit words for each UMP message type */ @@ -244,6 +245,40 @@ static int seq_ump_group_init(struct seq_ump_client *client, int group_index) return err; } +/* update the sequencer ports; called from notify_fb_change callback */ +static void update_port_infos(struct seq_ump_client *client) +{ + struct snd_seq_port_info *old, *new; + int i, err; + + old = kzalloc(sizeof(*old), GFP_KERNEL); + new = kzalloc(sizeof(*new), GFP_KERNEL); + if (!old || !new) + goto error; + + for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) { + old->addr.client = client->seq_client; + old->addr.port = i; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_GET_PORT_INFO, + old); + if (err < 0) + goto error; + fill_port_info(new, client, &client->groups[i]); + if (old->capability == new->capability && + !strcmp(old->name, new->name)) + continue; + err = snd_seq_kernel_client_ctl(client->seq_client, + SNDRV_SEQ_IOCTL_SET_PORT_INFO, + new); + if (err < 0) + goto error; + } + error: + kfree(new); + kfree(old); +} + /* update dir_bits and active flag for all groups in the client */ static void update_group_attrs(struct seq_ump_client *client) { @@ -353,6 +388,8 @@ static int create_ump_endpoint_port(struct seq_ump_client *client) /* release the client resources */ static void seq_ump_client_free(struct seq_ump_client *client) { + cancel_work_sync(&client->group_notify_work); + if (client->seq_client >= 0) snd_seq_delete_kernel_client(client->seq_client); @@ -377,8 +414,31 @@ static void setup_client_midi_version(struct seq_ump_client *client) snd_seq_kernel_client_put(cptr); } +/* UMP group change notification */ +static void handle_group_notify(struct work_struct *work) +{ + struct seq_ump_client *client = + container_of(work, struct seq_ump_client, group_notify_work); + + update_group_attrs(client); + update_port_infos(client); +} + +/* UMP FB change notification */ +static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ + struct seq_ump_client *client = ump->seq_client; + + if (!client) + return -ENODEV; + schedule_work(&client->group_notify_work); + return 0; +} + static const struct snd_seq_ump_ops seq_ump_ops = { .input_receive = seq_ump_input_receive, + .notify_fb_change = seq_ump_notify_fb_change, }; /* create a sequencer client and ports for the given UMP endpoint */ @@ -396,6 +456,7 @@ static int snd_seq_ump_probe(struct device *_dev) if (!client) return -ENOMEM; + INIT_WORK(&client->group_notify_work, handle_group_notify); client->ump = ump; client->seq_client = diff --git a/sound/core/ump.c b/sound/core/ump.c index 7df50f0affe9..c0cda12bce10 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -688,6 +688,28 @@ static void fill_fb_info(struct snd_ump_endpoint *ump, info->sysex8_streams, info->flags); } +/* check whether the FB info gets updated by the current message */ +static bool is_fb_info_updated(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb, + const union snd_ump_stream_msg *buf) +{ + char tmpbuf[offsetof(struct snd_ump_block_info, name)]; + + memcpy(tmpbuf, &fb->info, sizeof(tmpbuf)); + fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf); + return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0; +} + +/* notify the FB info/name change to sequencer */ +static void seq_notify_fb_change(struct snd_ump_endpoint *ump, + struct snd_ump_block *fb) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->notify_fb_change) + ump->seq_ops->notify_fb_change(ump, fb); +#endif +} + /* handle FB info message; update FB info if the block is present */ static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, const union snd_ump_stream_msg *buf) @@ -697,14 +719,24 @@ static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump, blk = buf->fb_info.function_block_id; fb = snd_ump_get_block(ump, blk); - if (fb) { - fill_fb_info(ump, &fb->info, buf); - } else if (ump->parsed) { - /* complain only if updated after parsing */ + + /* complain only if updated after parsing */ + if (!fb && ump->parsed) { ump_info(ump, "Function Block Info Update for non-existing block %d\n", blk); return -ENODEV; } + + /* When updated after the initial parse, check the FB info update */ + if (ump->parsed && !is_fb_info_updated(ump, fb, buf)) + return 1; /* no content change */ + + if (fb) { + fill_fb_info(ump, &fb->info, buf); + if (ump->parsed) + seq_notify_fb_change(ump, fb); + } + return 1; /* finished */ } @@ -714,14 +746,19 @@ static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump, { unsigned char blk; struct snd_ump_block *fb; + int ret; blk = buf->fb_name.function_block_id; fb = snd_ump_get_block(ump, blk); if (!fb) return -ENODEV; - return ump_append_string(ump, fb->info.name, sizeof(fb->info.name), - buf->raw, 3); + ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name), + buf->raw, 3); + /* notify the FB name update to sequencer, too */ + if (ret > 0 && ump->parsed) + seq_notify_fb_change(ump, fb); + return ret; } static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk) -- cgit v1.2.3 From 174a6dfbc17ee9defed4daeeea6061ef34644f02 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:51 +0200 Subject: ALSA: seq: ump: Notify port changes to system port For allowing applications to track the FB active changes, this patch adds the notification from the system port at each time a FB change is handled and the active flag or re-grouping happens. Link: https://lore.kernel.org/r/20230612081054.17200-8-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/seq/seq_system.c | 1 + sound/core/seq/seq_ump_client.c | 3 +++ 2 files changed, 4 insertions(+) (limited to 'sound') diff --git a/sound/core/seq/seq_system.c b/sound/core/seq/seq_system.c index 32c2d9b57751..80267290190d 100644 --- a/sound/core/seq/seq_system.c +++ b/sound/core/seq/seq_system.c @@ -85,6 +85,7 @@ void snd_seq_system_broadcast(int client, int port, int type) ev.type = type; snd_seq_kernel_client_dispatch(sysclient, &ev, 0, 0); } +EXPORT_SYMBOL_GPL(snd_seq_system_broadcast); /* entry points for broadcasting system events */ int snd_seq_system_notify(int client, int port, struct snd_seq_event *ev) diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 2f93d76b05ce..901a670dcb36 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -13,6 +13,7 @@ #include #include #include "seq_clientmgr.h" +#include "seq_system.h" struct seq_ump_client; struct seq_ump_group; @@ -273,6 +274,8 @@ static void update_port_infos(struct seq_ump_client *client) new); if (err < 0) goto error; + /* notify to system port */ + snd_seq_system_client_ev_port_change(client->seq_client, i); } error: kfree(new); -- cgit v1.2.3 From 6a8b4800ae54e9ceb8d017bcfb61d04ff0da90f2 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:52 +0200 Subject: ALSA: seq: ump: Notify UMP protocol change to sequencer UMP v1.1 supports the protocol switch via a UMP Stream message. When it's received, we need to take care of the midi_version field in the corresponding sequencer client, too. This patch introduces a new ops to notify the protocol change to snd_seq_ump_ops for handling it. Link: https://lore.kernel.org/r/20230612081054.17200-9-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 1 + sound/core/seq/seq_ump_client.c | 10 ++++++++++ sound/core/ump.c | 13 +++++++++++++ 3 files changed, 24 insertions(+) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 0e9c048346fa..68478e7be3b4 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -72,6 +72,7 @@ struct snd_seq_ump_ops { const u32 *data, int words); int (*notify_fb_change)(struct snd_ump_endpoint *ump, struct snd_ump_block *fb); + int (*switch_protocol)(struct snd_ump_endpoint *ump); }; struct snd_ump_block { diff --git a/sound/core/seq/seq_ump_client.c b/sound/core/seq/seq_ump_client.c index 901a670dcb36..fe21c801af74 100644 --- a/sound/core/seq/seq_ump_client.c +++ b/sound/core/seq/seq_ump_client.c @@ -439,9 +439,19 @@ static int seq_ump_notify_fb_change(struct snd_ump_endpoint *ump, return 0; } +/* UMP protocol change notification; just update the midi_version field */ +static int seq_ump_switch_protocol(struct snd_ump_endpoint *ump) +{ + if (!ump->seq_client) + return -ENODEV; + setup_client_midi_version(ump->seq_client); + return 0; +} + static const struct snd_seq_ump_ops seq_ump_ops = { .input_receive = seq_ump_input_receive, .notify_fb_change = seq_ump_notify_fb_change, + .switch_protocol = seq_ump_switch_protocol, }; /* create a sequencer client and ports for the given UMP endpoint */ diff --git a/sound/core/ump.c b/sound/core/ump.c index c0cda12bce10..f364bb290d3a 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -657,14 +657,27 @@ static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump, buf->raw, 2); } +/* notify the protocol change to sequencer */ +static void seq_notify_protocol(struct snd_ump_endpoint *ump) +{ +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + if (ump->seq_ops && ump->seq_ops->switch_protocol) + ump->seq_ops->switch_protocol(ump); +#endif /* CONFIG_SND_SEQUENCER */ +} + /* handle EP stream config message; update the UMP protocol */ static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, const union snd_ump_stream_msg *buf) { + unsigned int old_protocol = ump->info.protocol; + ump->info.protocol = (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; ump_dbg(ump, "Current protocol = %x (caps = %x)\n", ump->info.protocol, ump->info.protocol_caps); + if (ump->parsed && ump->info.protocol != old_protocol) + seq_notify_protocol(ump); return 1; /* finished */ } -- cgit v1.2.3 From 01dfa8e969dbbc72fc4564e8d61c905c4f3a2352 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 12 Jun 2023 10:10:53 +0200 Subject: ALSA: ump: Add info flag bit for static blocks UMP v1.1 spec allows to inform whether the function blocks are static and not dynamically updated. Add a new flag bit to snd_ump_endpoint_info to reflect that attribute, too. The flag is set when a USB MIDI device is still in the old MIDI 2.0 without UMP 1.1 support. Then the driver falls back to GTBs, and they are supposed to be static-only. Link: https://lore.kernel.org/r/20230612081054.17200-10-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/uapi/sound/asound.h | 3 +++ sound/core/ump.c | 11 +++++++++++ sound/usb/midi2.c | 2 ++ 3 files changed, 16 insertions(+) (limited to 'sound') diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 79ee48b2ed6d..4d1ac0797d56 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -780,6 +780,9 @@ struct snd_rawmidi_status { }; #endif +/* UMP EP info flags */ +#define SNDRV_UMP_EP_INFO_STATIC_BLOCKS 0x01 + /* UMP EP Protocol / JRTS capability bits */ #define SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK 0x0300 #define SNDRV_UMP_EP_INFO_PROTO_MIDI1 0x0100 /* MIDI 1.0 */ diff --git a/sound/core/ump.c b/sound/core/ump.c index f364bb290d3a..a64dc2d8a129 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -490,6 +490,8 @@ static void snd_ump_proc_read(struct snd_info_entry *entry, ump->info.sw_revision[2], ump->info.sw_revision[3]); } + snd_iprintf(buffer, "Static Blocks: %s\n", + (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No"); snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks); list_for_each_entry(fb, &ump->block_list, list) { @@ -608,6 +610,9 @@ static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump, ump->info.num_blocks = 1; } + if (buf->ep_info.static_function_block) + ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; + ump->info.protocol_caps = (buf->ep_info.protocol << 8) | buf->ep_info.jrts; @@ -708,6 +713,12 @@ static bool is_fb_info_updated(struct snd_ump_endpoint *ump, { char tmpbuf[offsetof(struct snd_ump_block_info, name)]; + if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) { + ump_info(ump, "Skipping static FB info update (blk#%d)\n", + fb->info.block_id); + return 0; + } + memcpy(tmpbuf, &fb->info, sizeof(tmpbuf)); fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf); return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0; diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index 13fa1978267a..ee2835741479 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -888,6 +888,8 @@ static int create_blocks_from_gtb(struct snd_usb_midi2_interface *umidi) /* Blocks have been already created? */ if (rmidi->ump_parsed || rmidi->ump->info.num_blocks) continue; + /* GTB is static-only */ + rmidi->ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS; /* loop over GTBs */ for (dir = 0; dir < 2; dir++) { if (!rmidi->eps[dir]) -- cgit v1.2.3 From 1359886227e52c27c0a230769f3be4c486e36299 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:17 +0200 Subject: ALSA: emu10k1: split off E-MU fallback clock from clock source So far, we set the fallback as a side effect of setting the source. But the fallback makes no sense at all when an internal clock is selected. Defaulting to 48k for S/PDIF & ADAT makes sense, but as that is the global default and we're not changing it automatically any more, it's just fine to leave it entirely to the explicit setting. This changes the name of the pre-existing control to something more appropriate (regardless of the split), so users will need to adjust their mixer settings. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-2-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 3 +- sound/pci/emu10k1/emu10k1_main.c | 3 +- sound/pci/emu10k1/emumixer.c | 89 +++++++++++++++++++++++++++++----------- sound/pci/emu10k1/emupcm.c | 4 +- 4 files changed, 72 insertions(+), 27 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index cc0151e7c828..59e79ea1f75e 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1668,7 +1668,8 @@ struct snd_emu1010 { unsigned char input_source[NUM_INPUT_DESTS]; unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ - unsigned int internal_clock; /* 44100 or 48000 */ + unsigned int clock_source; + unsigned int clock_fallback; unsigned int optical_in; /* 0:SPDIF, 1:ADAT */ unsigned int optical_out; /* 0:SPDIF, 1:ADAT */ struct delayed_work firmware_work; diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 65207ef689cb..2aa11d70e285 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -900,7 +900,8 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) /* IRQ Enable: All off */ snd_emu1010_fpga_write(emu, EMU_HANA_IRQ_ENABLE, 0x00); - emu->emu1010.internal_clock = 1; /* 48000 */ + emu->emu1010.clock_source = 1; /* 48000 */ + emu->emu1010.clock_fallback = 1; /* 48000 */ /* Default WCLK set to 48kHz. */ snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K); /* Word Clock source, Internal 48kHz x1 */ diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 20a0b3afc8a5..5b50d9c07a60 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -888,7 +888,7 @@ static const struct snd_emu1010_pads_info emu1010_pads_info[] = { }; -static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol, +static int snd_emu1010_clock_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { static const char * const texts[4] = { @@ -898,16 +898,16 @@ static int snd_emu1010_internal_clock_info(struct snd_kcontrol *kcontrol, return snd_ctl_enum_info(uinfo, 1, 4, texts); } -static int snd_emu1010_internal_clock_get(struct snd_kcontrol *kcontrol, +static int snd_emu1010_clock_source_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.internal_clock; + ucontrol->value.enumerated.item[0] = emu->emu1010.clock_source; return 0; } -static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, +static int snd_emu1010_clock_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); @@ -918,16 +918,14 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, /* Limit: uinfo->value.enumerated.items = 4; */ if (val >= 4) return -EINVAL; - change = (emu->emu1010.internal_clock != val); + change = (emu->emu1010.clock_source != val); if (change) { - emu->emu1010.internal_clock = val; + emu->emu1010.clock_source = val; switch (val) { case 0: /* 44100 */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 44.1kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_44_1K ); /* Word Clock source, Internal 44.1kHz x1 */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X ); @@ -943,8 +941,6 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, /* 48000 */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 48kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K ); /* Word Clock source, Internal 48kHz x1 */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X ); @@ -960,8 +956,6 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, case 2: /* Take clock from S/PDIF IN */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 48kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K ); /* Word Clock source, sync to S/PDIF input */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_HANA_SPDIF_IN | EMU_HANA_WCLOCK_1X ); @@ -979,8 +973,6 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, /* Take clock from ADAT IN */ /* Mute all */ snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Default fallback clock 48kHz */ - snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, EMU_HANA_DEFCLOCK_48K ); /* Word Clock source, sync to ADAT input */ snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, EMU_HANA_WCLOCK_HANA_ADAT_IN | EMU_HANA_WCLOCK_1X ); @@ -999,15 +991,62 @@ static int snd_emu1010_internal_clock_put(struct snd_kcontrol *kcontrol, return change; } -static const struct snd_kcontrol_new snd_emu1010_internal_clock = +static const struct snd_kcontrol_new snd_emu1010_clock_source = { - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .name = "Clock Internal Rate", - .count = 1, - .info = snd_emu1010_internal_clock_info, - .get = snd_emu1010_internal_clock_get, - .put = snd_emu1010_internal_clock_put + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Source", + .count = 1, + .info = snd_emu1010_clock_source_info, + .get = snd_emu1010_clock_source_get, + .put = snd_emu1010_clock_source_put +}; + +static int snd_emu1010_clock_fallback_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[2] = { + "44100", "48000" + }; + + return snd_ctl_enum_info(uinfo, 1, 2, texts); +} + +static int snd_emu1010_clock_fallback_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_fallback; + return 0; +} + +static int snd_emu1010_clock_fallback_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 >= 2) + return -EINVAL; + change = (emu->emu1010.clock_fallback != val); + if (change) { + emu->emu1010.clock_fallback = val; + snd_emu1010_fpga_write(emu, EMU_HANA_DEFCLOCK, 1 - val); + } + return change; +} + +static const struct snd_kcontrol_new snd_emu1010_clock_fallback = +{ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Fallback", + .count = 1, + .info = snd_emu1010_clock_fallback_info, + .get = snd_emu1010_clock_fallback_get, + .put = snd_emu1010_clock_fallback_put }; static int snd_emu1010_optical_out_info(struct snd_kcontrol *kcontrol, @@ -2297,7 +2336,11 @@ int snd_emu10k1_mixer(struct snd_emu10k1 *emu, snd_emu1010_apply_sources(emu); err = snd_ctl_add(card, - snd_ctl_new1(&snd_emu1010_internal_clock, emu)); + snd_ctl_new1(&snd_emu1010_clock_source, emu)); + if (err < 0) + return err; + err = snd_ctl_add(card, + snd_ctl_new1(&snd_emu1010_clock_fallback, emu)); if (err < 0) return err; diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 550caefa0ce4..fab537788587 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -1185,7 +1185,7 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) kfree(epcm); return err; } - if (emu->card_capabilities->emu_model && emu->emu1010.internal_clock == 0) + if (emu->card_capabilities->emu_model && emu->emu1010.clock_source == 0) sample_rate = 44100; else sample_rate = 48000; @@ -1335,7 +1335,7 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) * but we don't exceed 16 channels anyway. */ #if 1 - switch (emu->emu1010.internal_clock) { + switch (emu->emu1010.clock_source) { case 0: /* For 44.1kHz */ runtime->hw.rates = SNDRV_PCM_RATE_44100; -- cgit v1.2.3 From 60985241bfc61c6d6d85a78e0f29c866c546c7f5 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:18 +0200 Subject: ALSA: emu10k1: make available E-MU clock sources card-specific The actually available clock sources depend on the available audio input ports and dedicated clock input ports. This includes refactoring the code to be data-driven to remain manageable. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-3-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 2 + sound/pci/emu10k1/emu10k1_main.c | 4 +- sound/pci/emu10k1/emumixer.c | 153 ++++++++++++++++++++------------------- sound/pci/emu10k1/io.c | 23 ++++++ 4 files changed, 107 insertions(+), 75 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 59e79ea1f75e..703ef441bb2a 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1668,6 +1668,7 @@ struct snd_emu1010 { unsigned char input_source[NUM_INPUT_DESTS]; unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ + unsigned int wclock; /* Cached register value */ unsigned int clock_source; unsigned int clock_fallback; unsigned int optical_in; /* 0:SPDIF, 1:ADAT */ @@ -1824,6 +1825,7 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value); void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src); u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst); +void snd_emu1010_update_clock(struct snd_emu10k1 *emu); unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb); diff --git a/sound/pci/emu10k1/emu10k1_main.c b/sound/pci/emu10k1/emu10k1_main.c index 2aa11d70e285..58ed72de6403 100644 --- a/sound/pci/emu10k1/emu10k1_main.c +++ b/sound/pci/emu10k1/emu10k1_main.c @@ -905,10 +905,10 @@ static int snd_emu10k1_emu1010_init(struct snd_emu10k1 *emu) /* 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); */ - /* Audio Dock LEDs. */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_LOCK | EMU_HANA_DOCK_LEDS_2_48K); + snd_emu1010_update_clock(emu); // The routes are all set to EMU_SRC_SILENCE due to the reset, // so it is safe to simply enable the outputs. diff --git a/sound/pci/emu10k1/emumixer.c b/sound/pci/emu10k1/emumixer.c index 5b50d9c07a60..f9500cd50a4b 100644 --- a/sound/pci/emu10k1/emumixer.c +++ b/sound/pci/emu10k1/emumixer.c @@ -887,15 +887,79 @@ static const struct snd_emu1010_pads_info emu1010_pads_info[] = { }, }; +static const char * const emu1010_clock_texts[] = { + "44100", "48000", "SPDIF", "ADAT", "Dock", "BNC" +}; + +static const u8 emu1010_clock_vals[] = { + EMU_HANA_WCLOCK_INT_44_1K, + EMU_HANA_WCLOCK_INT_48K, + EMU_HANA_WCLOCK_HANA_SPDIF_IN, + EMU_HANA_WCLOCK_HANA_ADAT_IN, + EMU_HANA_WCLOCK_2ND_HANA, + EMU_HANA_WCLOCK_SYNC_BNC, +}; + +static const char * const emu0404_clock_texts[] = { + "44100", "48000", "SPDIF", "BNC" +}; + +static const u8 emu0404_clock_vals[] = { + EMU_HANA_WCLOCK_INT_44_1K, + EMU_HANA_WCLOCK_INT_48K, + EMU_HANA_WCLOCK_HANA_SPDIF_IN, + EMU_HANA_WCLOCK_SYNC_BNC, +}; + +struct snd_emu1010_clock_info { + const char * const *texts; + const u8 *vals; + unsigned num; +}; + +static const struct snd_emu1010_clock_info emu1010_clock_info[] = { + { + // rev1 1010 + .texts = emu1010_clock_texts, + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals), + }, + { + // rev2 1010 + .texts = emu1010_clock_texts, + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals) - 1, + }, + { + // 1616(m) CardBus + .texts = emu1010_clock_texts, + // TODO: determine what is actually available. + // Pedantically, *every* source comes from the 2nd FPGA, as the + // card itself has no own (digital) audio ports. The user manual + // claims that ADAT and S/PDIF clock sources are separate, which + // can mean two things: either E-MU mapped the dock's sources to + // the primary ones, or they determine the meaning of the "Dock" + // source depending on how the ports are actually configured + // (which the 2nd FPGA must be doing anyway). + .vals = emu1010_clock_vals, + .num = ARRAY_SIZE(emu1010_clock_vals), + }, + { + // 0404 + .texts = emu0404_clock_texts, + .vals = emu0404_clock_vals, + .num = ARRAY_SIZE(emu0404_clock_vals), + }, +}; static int snd_emu1010_clock_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { - static const char * const texts[4] = { - "44100", "48000", "SPDIF", "ADAT" - }; + struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_clock_info *emu_ci = + &emu1010_clock_info[emu1010_idx(emu)]; - return snd_ctl_enum_info(uinfo, 1, 4, texts); + return snd_ctl_enum_info(uinfo, 1, emu_ci->num, emu_ci->texts); } static int snd_emu1010_clock_source_get(struct snd_kcontrol *kcontrol, @@ -911,84 +975,27 @@ static int snd_emu1010_clock_source_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_emu10k1 *emu = snd_kcontrol_chip(kcontrol); + const struct snd_emu1010_clock_info *emu_ci = + &emu1010_clock_info[emu1010_idx(emu)]; unsigned int val; int change = 0; val = ucontrol->value.enumerated.item[0] ; - /* Limit: uinfo->value.enumerated.items = 4; */ - if (val >= 4) + if (val >= emu_ci->num) return -EINVAL; change = (emu->emu1010.clock_source != val); if (change) { emu->emu1010.clock_source = val; - switch (val) { - case 0: - /* 44100 */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, Internal 44.1kHz x1 */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, - EMU_HANA_DOCK_LEDS_2_44K | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - break; - case 1: - /* 48000 */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, Internal 48kHz x1 */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, - EMU_HANA_DOCK_LEDS_2_48K | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - break; - - case 2: /* Take clock from S/PDIF IN */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, sync to S/PDIF input */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_HANA_SPDIF_IN | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, - EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */ - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - break; - - case 3: - /* Take clock from ADAT IN */ - /* Mute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_MUTE ); - /* Word Clock source, sync to ADAT input */ - snd_emu1010_fpga_write(emu, EMU_HANA_WCLOCK, - EMU_HANA_WCLOCK_HANA_ADAT_IN | EMU_HANA_WCLOCK_1X ); - /* Set LEDs on Audio Dock */ - snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_LOCK ); - /* FIXME: We should set EMU_HANA_DOCK_LEDS_2_LOCK only when clock signal is present and valid */ - /* Allow DLL to settle */ - msleep(10); - /* Unmute all */ - snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE ); - - - break; - } + emu->emu1010.wclock = 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); + msleep(10); // Allow DLL to settle + snd_emu1010_fpga_write(emu, EMU_HANA_UNMUTE, EMU_UNMUTE); + + snd_emu1010_update_clock(emu); } - return change; + return change; } static const struct snd_kcontrol_new snd_emu1010_clock_source = diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index abe69ae40499..e7a44443023a 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -357,6 +357,29 @@ u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst) return (hi << 8) | lo; } +void snd_emu1010_update_clock(struct snd_emu10k1 *emu) +{ + u32 leds; + + switch (emu->emu1010.wclock) { + case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X: + leds = EMU_HANA_DOCK_LEDS_2_44K; + break; + case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X: + leds = EMU_HANA_DOCK_LEDS_2_48K; + break; + default: + leds = EMU_HANA_DOCK_LEDS_2_EXT; + break; + } + + // FIXME: this should probably represent the AND of all currently + // used sources' lock status. But we don't know how to get that ... + leds |= EMU_HANA_DOCK_LEDS_2_LOCK; + + snd_emu1010_fpga_write(emu, EMU_HANA_DOCK_LEDS_2, leds); +} + void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb) { unsigned long flags; -- cgit v1.2.3 From e73b597e63ebad26d9dee5feb5d47251ed53b8a4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:19 +0200 Subject: ALSA: emu10k1: query rate of external clock sources on E-MU cards The value isn't used yet; the subsequent commits will do that. This ignores the existence of rates above 48 kHz, which is fine, as the hardware will just switch to the fallback clock source when fed with a rate which is incompatible with the base clock multiplier, which currently is always x1. The sample rate display in /proc spdif-in is adjusted to reflect our understanding of the input rates. This is tested only with an 0404b card without sync card, so there is a lot of room for improvement. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-4-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 5 +++++ sound/pci/emu10k1/emuproc.c | 43 ++++++++++++++++++++------------------ sound/pci/emu10k1/io.c | 51 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 21 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index 703ef441bb2a..d64cf1697586 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1110,6 +1110,9 @@ SUB_REG_NC(A_EHC, A_I2S_CAPTURE_RATE, 0x00000e00) /* This sets the capture PCM #define EMU_DOCK_BOARD_ID0 0x00 /* ID bit 0 */ #define EMU_DOCK_BOARD_ID1 0x03 /* ID bit 1 */ +// The actual code disagrees about the bit width of the registers - +// the formula used is freq = 0x1770000 / (((X_HI << 5) | X_LO) + 1) + #define EMU_HANA_WC_SPDIF_HI 0x28 /* 0xxxxxx 6 bit SPDIF IN Word clock, upper 6 bits */ #define EMU_HANA_WC_SPDIF_LO 0x29 /* 0xxxxxx 6 bit SPDIF IN Word clock, lower 6 bits */ @@ -1669,6 +1672,7 @@ struct snd_emu1010 { unsigned int adc_pads; /* bit mask */ unsigned int dac_pads; /* bit mask */ unsigned int wclock; /* Cached register value */ + unsigned int word_clock; /* Cached effective value */ unsigned int clock_source; unsigned int clock_fallback; unsigned int optical_in; /* 0:SPDIF, 1:ADAT */ @@ -1825,6 +1829,7 @@ void snd_emu1010_fpga_write(struct snd_emu10k1 *emu, u32 reg, u32 value); void snd_emu1010_fpga_read(struct snd_emu10k1 *emu, u32 reg, u32 *value); void snd_emu1010_fpga_link_dst_src_write(struct snd_emu10k1 *emu, u32 dst, u32 src); u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst); +int snd_emu1010_get_raw_rate(struct snd_emu10k1 *emu, u8 src); void snd_emu1010_update_clock(struct snd_emu10k1 *emu); unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index ca7b4dddbea8..993b35362499 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -168,29 +168,32 @@ static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, struct snd_emu10k1 *emu = entry->private_data; u32 value; u32 value2; - u32 rate; if (emu->card_capabilities->emu_model) { - if (!emu->card_capabilities->no_adat) { - snd_emu1010_fpga_read(emu, 0x38, &value); - if ((value & 0x1) == 0) { - snd_emu1010_fpga_read(emu, 0x2a, &value); - snd_emu1010_fpga_read(emu, 0x2b, &value2); - rate = 0x1770000 / (((value << 5) | value2)+1); - snd_iprintf(buffer, "ADAT Locked : %u\n", rate); - } else { - snd_iprintf(buffer, "ADAT Unlocked\n"); - } - } - snd_emu1010_fpga_read(emu, 0x20, &value); - if ((value & 0x4) == 0) { - snd_emu1010_fpga_read(emu, 0x28, &value); - snd_emu1010_fpga_read(emu, 0x29, &value2); - rate = 0x1770000 / (((value << 5) | value2)+1); - snd_iprintf(buffer, "SPDIF Locked : %d\n", rate); - } else { - snd_iprintf(buffer, "SPDIF Unlocked\n"); + // This represents the S/PDIF lock status on 0404b, which is + // kinda weird and unhelpful, because monitoring it via IRQ is + // impractical (one gets an IRQ flood as long as it is desynced). + snd_emu1010_fpga_read(emu, EMU_HANA_IRQ_STATUS, &value); + snd_iprintf(buffer, "Lock status 1: %#x\n", value & 0x10); + + // Bit 0x1 in LO being 0 is supposedly for ADAT lock. + // The registers are always all zero on 0404b. + snd_emu1010_fpga_read(emu, EMU_HANA_LOCK_STS_LO, &value); + snd_emu1010_fpga_read(emu, EMU_HANA_LOCK_STS_HI, &value2); + snd_iprintf(buffer, "Lock status 2: %#x %#x\n", value, value2); + + snd_iprintf(buffer, "S/PDIF rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_HANA_SPDIF_IN)); + if (emu->card_capabilities->emu_model != EMU_MODEL_EMU0404) { + snd_iprintf(buffer, "ADAT rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_HANA_ADAT_IN)); + snd_iprintf(buffer, "Dock rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_2ND_HANA)); } + if (emu->card_capabilities->emu_model == EMU_MODEL_EMU0404 || + emu->card_capabilities->emu_model == EMU_MODEL_EMU1010) + snd_iprintf(buffer, "BNC rate: %dHz\n", + snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_SYNC_BNC)); } else { snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF In", CDCS, CDSRCS); snd_emu10k1_proc_spdif_status(emu, buffer, "Optical or Coax S/PDIF In", GPSCS, GPSRCS); diff --git a/sound/pci/emu10k1/io.c b/sound/pci/emu10k1/io.c index e7a44443023a..a0d66ce3ee83 100644 --- a/sound/pci/emu10k1/io.c +++ b/sound/pci/emu10k1/io.c @@ -357,21 +357,70 @@ u32 snd_emu1010_fpga_link_dst_src_read(struct snd_emu10k1 *emu, u32 dst) return (hi << 8) | lo; } +int snd_emu1010_get_raw_rate(struct snd_emu10k1 *emu, u8 src) +{ + u32 reg_lo, reg_hi, value, value2; + + switch (src) { + case EMU_HANA_WCLOCK_HANA_SPDIF_IN: + snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &value); + if (value & EMU_HANA_SPDIF_MODE_RX_INVALID) + return 0; + reg_lo = EMU_HANA_WC_SPDIF_LO; + reg_hi = EMU_HANA_WC_SPDIF_HI; + break; + case EMU_HANA_WCLOCK_HANA_ADAT_IN: + reg_lo = EMU_HANA_WC_ADAT_LO; + reg_hi = EMU_HANA_WC_ADAT_HI; + break; + case EMU_HANA_WCLOCK_SYNC_BNC: + reg_lo = EMU_HANA_WC_BNC_LO; + reg_hi = EMU_HANA_WC_BNC_HI; + break; + case EMU_HANA_WCLOCK_2ND_HANA: + reg_lo = EMU_HANA2_WC_SPDIF_LO; + reg_hi = EMU_HANA2_WC_SPDIF_HI; + break; + default: + return 0; + } + snd_emu1010_fpga_read(emu, reg_hi, &value); + snd_emu1010_fpga_read(emu, reg_lo, &value2); + // FIXME: The /4 is valid for 0404b, but contradicts all other info. + return 0x1770000 / 4 / (((value << 5) | value2) + 1); +} + void snd_emu1010_update_clock(struct snd_emu10k1 *emu) { + int clock; u32 leds; switch (emu->emu1010.wclock) { case EMU_HANA_WCLOCK_INT_44_1K | EMU_HANA_WCLOCK_1X: + clock = 44100; leds = EMU_HANA_DOCK_LEDS_2_44K; break; case EMU_HANA_WCLOCK_INT_48K | EMU_HANA_WCLOCK_1X: + clock = 48000; leds = EMU_HANA_DOCK_LEDS_2_48K; break; default: - leds = EMU_HANA_DOCK_LEDS_2_EXT; + 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 { + clock = 48000; + leds = EMU_HANA_DOCK_LEDS_2_EXT | EMU_HANA_DOCK_LEDS_2_48K; + } break; } + emu->emu1010.word_clock = clock; // FIXME: this should probably represent the AND of all currently // used sources' lock status. But we don't know how to get that ... -- cgit v1.2.3 From 19b89d15fa978c7e6327287f90d1dde15aff01c4 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:20 +0200 Subject: ALSA: emu10k1: fix sample rates for E-MU cards at 44.1 kHz word clock Now that we know the actual word clock, we can: - Put the resulting rate into the hardware info - At 44.1 kHz word clock shift the rate for the pitch calculations, which presume a 48 kHz word clock Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-5-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emu10k1.h | 1 + sound/pci/emu10k1/emupcm.c | 112 ++++++++++++++++++++++----------------------- 2 files changed, 56 insertions(+), 57 deletions(-) (limited to 'sound') diff --git a/include/sound/emu10k1.h b/include/sound/emu10k1.h index d64cf1697586..386a5f3be3e0 100644 --- a/include/sound/emu10k1.h +++ b/include/sound/emu10k1.h @@ -1495,6 +1495,7 @@ struct snd_emu10k1_pcm { unsigned short first_ptr; snd_pcm_uframes_t resume_pos; struct snd_util_memblk *memblk; + unsigned int pitch_target; unsigned int start_addr; unsigned int ccca_start_addr; unsigned int capture_ipr; /* interrupt acknowledge mask */ diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index fab537788587..3ef9130a9577 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -195,6 +195,33 @@ static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate) } } +static void snd_emu10k1_constrain_capture_rates(struct snd_emu10k1 *emu, + struct snd_pcm_runtime *runtime) +{ + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) { + // This also sets the rate constraint by deleting SNDRV_PCM_RATE_KNOT + runtime->hw.rates = SNDRV_PCM_RATE_11025 | \ + SNDRV_PCM_RATE_22050 | \ + SNDRV_PCM_RATE_44100; + runtime->hw.rate_min = 11025; + runtime->hw.rate_max = 44100; + return; + } + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_capture_rates); +} + +static void snd_emu1010_constrain_efx_rate(struct snd_emu10k1 *emu, + struct snd_pcm_runtime *runtime) +{ + 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 unsigned int emu10k1_calc_pitch_target(unsigned int rate) { unsigned int pitch_target; @@ -251,18 +278,11 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, const unsigned char *send_routing, const unsigned char *send_amount) { - struct snd_pcm_substream *substream = evoice->epcm->substream; - struct snd_pcm_runtime *runtime = substream->runtime; unsigned int silent_page; int voice; - unsigned int pitch_target; voice = evoice->number; - if (emu->card_capabilities->emu_model) - pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ - else - pitch_target = emu10k1_calc_pitch_target(runtime->rate); silent_page = ((unsigned int)emu->silent_page.addr << emu->address_mode) | (emu->address_mode ? MAP_PTI_MASK1 : MAP_PTI_MASK0); snd_emu10k1_ptr_write_multiple(emu, voice, @@ -273,7 +293,7 @@ static void snd_emu10k1_pcm_init_voice(struct snd_emu10k1 *emu, // Stereo slaves don't need to have the addresses set, but it doesn't hurt DSL, end_addr | (send_amount[3] << 24), PSST, start_addr | (send_amount[2] << 24), - CCCA, emu10k1_select_interprom(pitch_target) | + CCCA, emu10k1_select_interprom(evoice->epcm->pitch_target) | (w_16 ? 0 : CCCA_8BITSELECT), // Clear filter delay memory Z1, 0, @@ -419,6 +439,13 @@ static int snd_emu10k1_playback_prepare(struct snd_pcm_substream *substream) bool w_16 = snd_pcm_format_width(runtime->format) == 16; bool stereo = runtime->channels == 2; unsigned int start_addr, end_addr; + unsigned int rate; + + rate = runtime->rate; + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) + rate = rate * 480 / 441; + epcm->pitch_target = emu10k1_calc_pitch_target(rate); start_addr = epcm->start_addr >> w_16; end_addr = start_addr + runtime->period_size; @@ -443,6 +470,8 @@ static int snd_emu10k1_efx_playback_prepare(struct snd_pcm_substream *substream) unsigned int extra_size, channel_size; unsigned int i; + epcm->pitch_target = PITCH_48000; + start_addr = epcm->start_addr >> 1; // 16-bit voices extra_size = runtime->period_size; @@ -526,12 +555,16 @@ static int snd_emu10k1_capture_prepare(struct snd_pcm_substream *substream) epcm->capture_bs_val++; } if (epcm->type == CAPTURE_AC97ADC) { + unsigned rate = runtime->rate; + if (!(runtime->hw.rates & SNDRV_PCM_RATE_48000)) + rate = rate * 480 / 441; + epcm->capture_cr_val = emu->audigy ? A_ADCCR_LCHANENABLE : ADCCR_LCHANENABLE; if (runtime->channels > 1) epcm->capture_cr_val |= emu->audigy ? A_ADCCR_RCHANENABLE : ADCCR_RCHANENABLE; epcm->capture_cr_val |= emu->audigy ? - snd_emu10k1_audigy_capture_rate_reg(runtime->rate) : - snd_emu10k1_capture_rate_reg(runtime->rate); + snd_emu10k1_audigy_capture_rate_reg(rate) : + snd_emu10k1_capture_rate_reg(rate); } return 0; } @@ -670,19 +703,10 @@ static void snd_emu10k1_playback_commit_pitch(struct snd_emu10k1 *emu, static void snd_emu10k1_playback_trigger_voice(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *evoice) { - struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; - unsigned int voice, pitch_target; + unsigned int voice; - substream = evoice->epcm->substream; - runtime = substream->runtime; voice = evoice->number; - - if (emu->card_capabilities->emu_model) - pitch_target = PITCH_48000; /* Disable interpolators on emu1010 card */ - else - pitch_target = emu10k1_calc_pitch_target(runtime->rate); - snd_emu10k1_playback_commit_pitch(emu, voice, pitch_target << 16); + snd_emu10k1_playback_commit_pitch(emu, voice, evoice->epcm->pitch_target << 16); } static void snd_emu10k1_playback_stop_voice(struct snd_emu10k1 *emu, @@ -1043,11 +1067,9 @@ static const struct snd_pcm_hardware snd_emu10k1_capture_efx = SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID), .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | - SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000, - .rate_min = 44100, - .rate_max = 192000, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, .channels_min = 1, .channels_max = 16, .buffer_bytes_max = (64*1024), @@ -1144,6 +1166,8 @@ 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) + snd_emu1010_constrain_efx_rate(emu, runtime); err = snd_emu10k1_playback_set_constraints(runtime); if (err < 0) { kfree(epcm); @@ -1185,8 +1209,8 @@ static int snd_emu10k1_playback_open(struct snd_pcm_substream *substream) kfree(epcm); return err; } - if (emu->card_capabilities->emu_model && emu->emu1010.clock_source == 0) - sample_rate = 44100; + if (emu->card_capabilities->emu_model) + sample_rate = emu->emu1010.word_clock; else sample_rate = 48000; err = snd_pcm_hw_rule_noresample(runtime, sample_rate); @@ -1236,11 +1260,11 @@ static int snd_emu10k1_capture_open(struct snd_pcm_substream *substream) runtime->private_data = epcm; runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_capture; + snd_emu10k1_constrain_capture_rates(emu, runtime); snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, &hw_constraints_capture_buffer_sizes); emu->capture_interrupt = snd_emu10k1_pcm_ac97adc_interrupt; emu->pcm_capture_substream = substream; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_capture_rates); return 0; } @@ -1313,17 +1337,9 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) substream->runtime->private_data = epcm; substream->runtime->private_free = snd_emu10k1_pcm_free_substream; runtime->hw = snd_emu10k1_capture_efx; - runtime->hw.rates = SNDRV_PCM_RATE_48000; - runtime->hw.rate_min = runtime->hw.rate_max = 48000; if (emu->card_capabilities->emu_model) { - /* TODO - * SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | - * SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | - * SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000 - * rate_min = 44100, - * rate_max = 192000, - * Need to add mixer control to fix sample rate - * + snd_emu1010_constrain_efx_rate(emu, runtime); + /* * There are 32 mono channels of 16bits each. * 24bit Audio uses 2x channels over 16bit, * 96kHz uses 2x channels over 48kHz, @@ -1334,30 +1350,12 @@ static int snd_emu10k1_capture_efx_open(struct snd_pcm_substream *substream) * 1010rev2 and 1616(m) cards have double that, * but we don't exceed 16 channels anyway. */ -#if 1 - switch (emu->emu1010.clock_source) { - case 0: - /* For 44.1kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_44100; - runtime->hw.rate_min = runtime->hw.rate_max = 44100; - break; - case 1: - /* For 48kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_48000; - runtime->hw.rate_min = runtime->hw.rate_max = 48000; - break; - } -#endif #if 0 /* For 96kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_96000; - runtime->hw.rate_min = runtime->hw.rate_max = 96000; runtime->hw.channels_min = runtime->hw.channels_max = 4; #endif #if 0 /* For 192kHz */ - runtime->hw.rates = SNDRV_PCM_RATE_192000; - runtime->hw.rate_min = runtime->hw.rate_max = 192000; runtime->hw.channels_min = runtime->hw.channels_max = 2; #endif runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; -- cgit v1.2.3 From e68235c8aae9af08a868e4a4337daf2bcb4f6a92 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:21 +0200 Subject: ALSA: emu10k1: fix synthesizer pitch for E-MU cards at 44.1 kHz This is only a very partial fix - the frequency-dependent envelope & LFO register values aren't adjusted. But I'm not sure they were even correct at 48 kHz to start with, as most of them are precalculated by common code which assumes an EMU8K-specific 44.1 kHz word clock, and it seems somewhat unlikely that the hardware's register interpretation was adjusted to compensate for the different word clock. In any case I'm not going to spend time on fixing that, as this code is unlikely to be actually used by anyone today. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-6-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- include/sound/emux_synth.h | 2 +- sound/pci/emu10k1/emu10k1_callback.c | 10 ++++++++++ sound/pci/emu10k1/emu10k1_synth.c | 1 - sound/synth/emux/emux_synth.c | 3 ++- 4 files changed, 13 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/sound/emux_synth.h b/include/sound/emux_synth.h index d499b68122a3..1cc530434b97 100644 --- a/include/sound/emux_synth.h +++ b/include/sound/emux_synth.h @@ -54,6 +54,7 @@ struct snd_emux_operators { #if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) int (*oss_ioctl)(struct snd_emux *emu, int cmd, int p1, int p2); #endif + int (*get_pitch_shift)(struct snd_emux *emu); }; @@ -82,7 +83,6 @@ struct snd_emux { int max_voices; /* Number of voices */ int mem_size; /* memory size (in byte) */ int num_ports; /* number of ports to be created */ - int pitch_shift; /* pitch shift value (for Emu10k1) */ struct snd_emux_operators ops; /* operators */ void *hw; /* hardware */ unsigned long flags; /* other conditions */ diff --git a/sound/pci/emu10k1/emu10k1_callback.c b/sound/pci/emu10k1/emu10k1_callback.c index ad0dea0c2be9..d36234b88fb4 100644 --- a/sound/pci/emu10k1/emu10k1_callback.c +++ b/sound/pci/emu10k1/emu10k1_callback.c @@ -35,6 +35,7 @@ static void terminate_voice(struct snd_emux_voice *vp); static void free_voice(struct snd_emux_voice *vp); static u32 make_fmmod(struct snd_emux_voice *vp); static u32 make_fm2frq2(struct snd_emux_voice *vp); +static int get_pitch_shift(struct snd_emux *emu); /* * Ensure a value is between two points @@ -58,6 +59,7 @@ static const struct snd_emux_operators emu10k1_ops = { .free_voice = free_voice, .sample_new = snd_emu10k1_sample_new, .sample_free = snd_emu10k1_sample_free, + .get_pitch_shift = get_pitch_shift, }; void @@ -508,3 +510,11 @@ make_fm2frq2(struct snd_emux_voice *vp) LIMITVALUE(pitch, -128, 127); return ((unsigned char)pitch << 8) | freq; } + +static int get_pitch_shift(struct snd_emux *emu) +{ + struct snd_emu10k1 *hw = emu->hw; + + return (hw->card_capabilities->emu_model && + hw->emu1010.word_clock == 44100) ? 0 : -501; +} diff --git a/sound/pci/emu10k1/emu10k1_synth.c b/sound/pci/emu10k1/emu10k1_synth.c index 549013a4a80b..759e66e1105a 100644 --- a/sound/pci/emu10k1/emu10k1_synth.c +++ b/sound/pci/emu10k1/emu10k1_synth.c @@ -43,7 +43,6 @@ static int snd_emu10k1_synth_probe(struct device *_dev) emux->hw = hw; emux->max_voices = arg->max_voices; emux->num_ports = arg->seq_ports; - emux->pitch_shift = -501; emux->memhdr = hw->memhdr; /* maximum two ports */ emux->midi_ports = arg->seq_ports < 2 ? arg->seq_ports : 2; diff --git a/sound/synth/emux/emux_synth.c b/sound/synth/emux/emux_synth.c index a5385efcedb6..075358a533a0 100644 --- a/sound/synth/emux/emux_synth.c +++ b/sound/synth/emux/emux_synth.c @@ -845,7 +845,8 @@ calc_pitch(struct snd_emux_voice *vp) /* 0xe000: root pitch */ offset += 0xe000 + vp->reg.rate_offset; - offset += vp->emu->pitch_shift; + if (vp->emu->ops.get_pitch_shift) + offset += vp->emu->ops.get_pitch_shift(vp->emu); LIMITVALUE(offset, 0, 0xffff); if (offset == vp->apitch) return 0; /* unchanged */ -- cgit v1.2.3 From 6cc844504638b0d1429be729b2d66be92276e24b Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:22 +0200 Subject: ALSA: timer: minimize open-coded access to hw.resolution Some info-querying code still used hw.resolution directly instead of calling snd_timer_hw_resolution(), thus missing a possible hw.c_resolution callback. This patch rectifies that. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-7-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/core/timer.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'sound') diff --git a/sound/core/timer.c b/sound/core/timer.c index e08a37c23add..9d0d2a5c2e15 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -1246,6 +1246,7 @@ static void snd_timer_proc_read(struct snd_info_entry *entry, { struct snd_timer *timer; struct snd_timer_instance *ti; + unsigned long resolution; mutex_lock(®ister_mutex); list_for_each_entry(timer, &snd_timer_list, device_list) { @@ -1269,10 +1270,13 @@ static void snd_timer_proc_read(struct snd_info_entry *entry, timer->tmr_device, timer->tmr_subdevice); } snd_iprintf(buffer, "%s :", timer->name); - if (timer->hw.resolution) + spin_lock_irq(&timer->lock); + resolution = snd_timer_hw_resolution(timer); + spin_unlock_irq(&timer->lock); + if (resolution) snd_iprintf(buffer, " %lu.%03luus (%lu ticks)", - timer->hw.resolution / 1000, - timer->hw.resolution % 1000, + resolution / 1000, + resolution % 1000, timer->hw.ticks); if (timer->hw.flags & SNDRV_TIMER_HW_SLAVE) snd_iprintf(buffer, " SLAVE"); @@ -1662,7 +1666,9 @@ static int snd_timer_user_ginfo(struct file *file, ginfo->flags |= SNDRV_TIMER_FLG_SLAVE; strscpy(ginfo->id, t->id, sizeof(ginfo->id)); strscpy(ginfo->name, t->name, sizeof(ginfo->name)); - ginfo->resolution = t->hw.resolution; + spin_lock_irq(&t->lock); + ginfo->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); if (t->hw.resolution_min > 0) { ginfo->resolution_min = t->hw.resolution_min; ginfo->resolution_max = t->hw.resolution_max; @@ -1817,7 +1823,9 @@ static int snd_timer_user_info(struct file *file, info->flags |= SNDRV_TIMER_FLG_SLAVE; strscpy(info->id, t->id, sizeof(info->id)); strscpy(info->name, t->name, sizeof(info->name)); - info->resolution = t->hw.resolution; + spin_lock_irq(&t->lock); + info->resolution = snd_timer_hw_resolution(t); + spin_unlock_irq(&t->lock); if (copy_to_user(_info, info, sizeof(*_info))) err = -EFAULT; kfree(info); -- cgit v1.2.3 From ca533448a0936cd1c9f8e158af36f0657ed4d66e Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:23 +0200 Subject: ALSA: emu10k1: fix timer for E-MU cards at 44.1 kHz word clock The timer was presuming a fixed 48 kHz word clock, like the rest of the code. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-8-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/timer.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/timer.c b/sound/pci/emu10k1/timer.c index 993663fef885..f3c78adf3248 100644 --- a/sound/pci/emu10k1/timer.c +++ b/sound/pci/emu10k1/timer.c @@ -38,20 +38,36 @@ static int snd_emu10k1_timer_stop(struct snd_timer *timer) return 0; } +static unsigned long snd_emu10k1_timer_c_resolution(struct snd_timer *timer) +{ + struct snd_emu10k1 *emu = snd_timer_chip(timer); + + if (emu->card_capabilities->emu_model && + emu->emu1010.word_clock == 44100) + return 22676; // 1 sample @ 44.1 kHz = 22.675736...us + else + return 20833; // 1 sample @ 48 kHz = 20.833...us +} + static int snd_emu10k1_timer_precise_resolution(struct snd_timer *timer, unsigned long *num, unsigned long *den) { + struct snd_emu10k1 *emu = snd_timer_chip(timer); + *num = 1; - *den = 48000; + if (emu->card_capabilities->emu_model) + *den = emu->emu1010.word_clock; + else + *den = 48000; return 0; } static const struct snd_timer_hardware snd_emu10k1_timer_hw = { .flags = SNDRV_TIMER_HW_AUTO, - .resolution = 20833, /* 1 sample @ 48KHZ = 20.833...us */ .ticks = 1024, .start = snd_emu10k1_timer_start, .stop = snd_emu10k1_timer_stop, + .c_resolution = snd_emu10k1_timer_c_resolution, .precise_resolution = snd_emu10k1_timer_precise_resolution, }; -- cgit v1.2.3 From 3ac251420be2bbc9f5e83281661dbfaf05983f69 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:24 +0200 Subject: ALSA: emu10k1: add support for 12 kHz capture on Audigy Fixes a tentative FIXME. Because we can. Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-9-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emupcm.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/emu10k1/emupcm.c b/sound/pci/emu10k1/emupcm.c index 3ef9130a9577..387288d623d7 100644 --- a/sound/pci/emu10k1/emupcm.c +++ b/sound/pci/emu10k1/emupcm.c @@ -177,12 +177,22 @@ static unsigned int snd_emu10k1_capture_rate_reg(unsigned int rate) } } +static const unsigned int audigy_capture_rates[9] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 +}; + +static const struct snd_pcm_hw_constraint_list hw_constraints_audigy_capture_rates = { + .count = 9, + .list = audigy_capture_rates, + .mask = 0 +}; + static unsigned int snd_emu10k1_audigy_capture_rate_reg(unsigned int rate) { switch (rate) { case 8000: return A_ADCCR_SAMPLERATE_8; case 11025: return A_ADCCR_SAMPLERATE_11; - case 12000: return A_ADCCR_SAMPLERATE_12; /* really supported? */ + case 12000: return A_ADCCR_SAMPLERATE_12; case 16000: return ADCCR_SAMPLERATE_16; case 22050: return ADCCR_SAMPLERATE_22; case 24000: return ADCCR_SAMPLERATE_24; @@ -209,7 +219,8 @@ static void snd_emu10k1_constrain_capture_rates(struct snd_emu10k1 *emu, return; } snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - &hw_constraints_capture_rates); + emu->audigy ? &hw_constraints_audigy_capture_rates : + &hw_constraints_capture_rates); } static void snd_emu1010_constrain_efx_rate(struct snd_emu10k1 *emu, -- cgit v1.2.3 From 58cc6133cc27496c1f041f302e13c33f0867be8a Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Mon, 12 Jun 2023 21:13:25 +0200 Subject: ALSA: emu10k1: actually show some S/PDIF status in /proc for E-MU cards The file is called spdif-in, but we abused it to show only sample rates from various sources. Rectify it as far as possible (the FPGA doesn't give us a lot of information). Signed-off-by: Oswald Buddenhagen Link: https://lore.kernel.org/r/20230612191325.1315854-10-oswald.buddenhagen@gmx.de Signed-off-by: Takashi Iwai --- sound/pci/emu10k1/emuproc.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sound') diff --git a/sound/pci/emu10k1/emuproc.c b/sound/pci/emu10k1/emuproc.c index 993b35362499..7e2cc532471f 100644 --- a/sound/pci/emu10k1/emuproc.c +++ b/sound/pci/emu10k1/emuproc.c @@ -194,6 +194,14 @@ static void snd_emu10k1_proc_spdif_read(struct snd_info_entry *entry, emu->card_capabilities->emu_model == EMU_MODEL_EMU1010) snd_iprintf(buffer, "BNC rate: %dHz\n", snd_emu1010_get_raw_rate(emu, EMU_HANA_WCLOCK_SYNC_BNC)); + + snd_emu1010_fpga_read(emu, EMU_HANA_SPDIF_MODE, &value); + if (value & EMU_HANA_SPDIF_MODE_RX_INVALID) + snd_iprintf(buffer, "\nS/PDIF input invalid\n"); + else + snd_iprintf(buffer, "\nS/PDIF mode: %s%s\n", + value & EMU_HANA_SPDIF_MODE_RX_PRO ? "professional" : "consumer", + value & EMU_HANA_SPDIF_MODE_RX_NOCOPY ? ", no copy" : ""); } else { snd_emu10k1_proc_spdif_status(emu, buffer, "CD-ROM S/PDIF In", CDCS, CDSRCS); snd_emu10k1_proc_spdif_status(emu, buffer, "Optical or Coax S/PDIF In", GPSCS, GPSRCS); -- cgit v1.2.3 From 79597c8bf64ca99eab385115743131d260339da5 Mon Sep 17 00:00:00 2001 From: Su Hui Date: Thu, 15 Jun 2023 10:17:32 +0800 Subject: ALSA: ac97: Fix possible NULL dereference in snd_ac97_mixer smatch error: sound/pci/ac97/ac97_codec.c:2354 snd_ac97_mixer() error: we previously assumed 'rac97' could be null (see line 2072) remove redundant assignment, return error if rac97 is NULL. Fixes: da3cec35dd3c ("ALSA: Kill snd_assert() in sound/pci/*") Signed-off-by: Su Hui Link: https://lore.kernel.org/r/20230615021732.1972194-1-suhui@nfschina.com Signed-off-by: Takashi Iwai --- sound/pci/ac97/ac97_codec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/pci/ac97/ac97_codec.c b/sound/pci/ac97/ac97_codec.c index 9afc5906d662..80a65b8ad7b9 100644 --- a/sound/pci/ac97/ac97_codec.c +++ b/sound/pci/ac97/ac97_codec.c @@ -2069,8 +2069,8 @@ int snd_ac97_mixer(struct snd_ac97_bus *bus, struct snd_ac97_template *template, .dev_disconnect = snd_ac97_dev_disconnect, }; - if (rac97) - *rac97 = NULL; + if (!rac97) + return -EINVAL; if (snd_BUG_ON(!bus || !template)) return -EINVAL; if (snd_BUG_ON(template->num >= 4)) -- cgit v1.2.3 From 7ea9ee0064281ef630e038f8dd2f6e8f4030a696 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Mon, 19 Jun 2023 10:28:05 +0100 Subject: ALSA: compress: allow setting codec params after next track For gapless playback it is possible that each track can have different codec profile with same decoder, for example we have WMA album, we may have different tracks as WMA v9, WMA v10 and so on Or if DSP's like QDSP have abililty to switch decoders on single stream for each track, then this call could be used to set new codec parameters. Existing code does not allow to change this profile while doing gapless playback. Reuse existing SNDRV_COMPRESS_SET_PARAMS to set this new track params along some additional checks to enforce proper state machine. With this new changes now the user can call SNDRV_COMPRESS_SET_PARAMS anytime after setting next track and additional check in write should also ensure that params are set before writing new data. Signed-off-by: Srinivas Kandagatla Acked-by: Vinod Koul Link: https://lore.kernel.org/r/20230619092805.21649-1-srinivas.kandagatla@linaro.org Signed-off-by: Takashi Iwai --- Documentation/sound/designs/compress-offload.rst | 11 ++++++----- sound/core/compress_offload.c | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) (limited to 'sound') diff --git a/Documentation/sound/designs/compress-offload.rst b/Documentation/sound/designs/compress-offload.rst index 935f325dbc77..655624f77092 100644 --- a/Documentation/sound/designs/compress-offload.rst +++ b/Documentation/sound/designs/compress-offload.rst @@ -268,11 +268,12 @@ with setting of meta_data and signalling for next track :: | | | V | +----------+ - | | | - | |NEXT_TRACK| - | | | - | +----------+ - | | + | compr_set_params() | | + | +-----------|NEXT_TRACK| + | | | | + | | +--+-------+ + | | | | + | +--------------+ | | | | | compr_partial_drain() | | diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 243acad89fd3..30f73097447b 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -589,7 +589,7 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) struct snd_compr_params *params; int retval; - if (stream->runtime->state == SNDRV_PCM_STATE_OPEN) { + if (stream->runtime->state == SNDRV_PCM_STATE_OPEN || stream->next_track) { /* * we should allow parameter change only when stream has been * opened not in other cases @@ -612,6 +612,9 @@ snd_compr_set_params(struct snd_compr_stream *stream, unsigned long arg) if (retval) goto out; + if (stream->next_track) + goto out; + stream->metadata_set = false; stream->next_track = false; -- cgit v1.2.3 From 8d0cf150d299148a97653610c256f10c42f85ce0 Mon Sep 17 00:00:00 2001 From: Ivan Orlov Date: Tue, 20 Jun 2023 19:56:34 +0200 Subject: sound: make all 'class' structures const MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now that the driver core allows for struct class to be in read-only memory, making all 'class' structures to be declared at build time placing them into read-only memory, instead of having to be dynamically allocated at load time. Cc: Jaroslav Kysela Cc: Takashi Iwai Cc: Ivan Orlov Cc: Greg Kroah-Hartman Cc: Geoff Levand Cc: Thierry Reding Cc: "Uwe Kleine-König" Cc: alsa-devel@alsa-project.org Suggested-by: Greg Kroah-Hartman Signed-off-by: Ivan Orlov Signed-off-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20230620175633.641141-2-gregkh@linuxfoundation.org Signed-off-by: Takashi Iwai --- include/sound/core.h | 2 +- sound/core/control_led.c | 2 +- sound/core/init.c | 4 ++-- sound/sound_core.c | 23 ++++++++++++----------- 4 files changed, 16 insertions(+), 15 deletions(-) (limited to 'sound') diff --git a/include/sound/core.h b/include/sound/core.h index 4ea5f66b59d7..f6e0dd648b80 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -232,7 +232,7 @@ static inline struct device *snd_card_get_device_link(struct snd_card *card) extern int snd_major; extern int snd_ecards_limit; -extern struct class *sound_class; +extern const struct class sound_class; #ifdef CONFIG_SND_DEBUG extern struct dentry *sound_debugfs_root; #endif diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 3cadd40100f3..ee77547bf8dc 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -737,7 +737,7 @@ static int __init snd_ctl_led_init(void) unsigned int group; device_initialize(&snd_ctl_led_dev); - snd_ctl_led_dev.class = sound_class; + snd_ctl_led_dev.class = &sound_class; snd_ctl_led_dev.release = snd_ctl_led_dev_release; dev_set_name(&snd_ctl_led_dev, "ctl-led"); if (device_add(&snd_ctl_led_dev)) { diff --git a/sound/core/init.c b/sound/core/init.c index df0c22480375..baef2688d0cf 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -129,7 +129,7 @@ void snd_device_initialize(struct device *dev, struct snd_card *card) device_initialize(dev); if (card) dev->parent = &card->card_dev; - dev->class = sound_class; + dev->class = &sound_class; dev->release = default_release; } EXPORT_SYMBOL_GPL(snd_device_initialize); @@ -331,7 +331,7 @@ static int snd_card_init(struct snd_card *card, struct device *parent, device_initialize(&card->card_dev); card->card_dev.parent = parent; - card->card_dev.class = sound_class; + card->card_dev.class = &sound_class; card->card_dev.release = release_card_device; card->card_dev.groups = card->dev_groups; card->dev_groups[0] = &card_dev_attr_group; diff --git a/sound/sound_core.c b/sound/sound_core.c index 4f6911274d56..d81fed1c1226 100644 --- a/sound/sound_core.c +++ b/sound/sound_core.c @@ -23,9 +23,6 @@ static inline int init_oss_soundcore(void) { return 0; } static inline void cleanup_oss_soundcore(void) { } #endif -struct class *sound_class; -EXPORT_SYMBOL(sound_class); - MODULE_DESCRIPTION("Core sound module"); MODULE_AUTHOR("Alan Cox"); MODULE_LICENSE("GPL"); @@ -37,6 +34,12 @@ static char *sound_devnode(const struct device *dev, umode_t *mode) return kasprintf(GFP_KERNEL, "snd/%s", dev_name(dev)); } +const struct class sound_class = { + .name = "sound", + .devnode = sound_devnode, +}; +EXPORT_SYMBOL(sound_class); + static int __init init_soundcore(void) { int rc; @@ -45,21 +48,19 @@ static int __init init_soundcore(void) if (rc) return rc; - sound_class = class_create("sound"); - if (IS_ERR(sound_class)) { + rc = class_register(&sound_class); + if (rc) { cleanup_oss_soundcore(); - return PTR_ERR(sound_class); + return rc; } - sound_class->devnode = sound_devnode; - return 0; } static void __exit cleanup_soundcore(void) { cleanup_oss_soundcore(); - class_destroy(sound_class); + class_unregister(&sound_class); } subsys_initcall(init_soundcore); @@ -276,7 +277,7 @@ retry: } } - device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), + device_create(&sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor), NULL, "%s", s->name+6); return s->unit_minor; @@ -302,7 +303,7 @@ static void sound_remove_unit(struct sound_unit **list, int unit) if (!preclaim_oss) __unregister_chrdev(SOUND_MAJOR, p->unit_minor, 1, p->name); - device_destroy(sound_class, MKDEV(SOUND_MAJOR, p->unit_minor)); + device_destroy(&sound_class, MKDEV(SOUND_MAJOR, p->unit_minor)); kfree(p); } } -- cgit v1.2.3 From a79807683781d3f215e9d958494e52ed70f4ad27 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jun 2023 13:02:39 +0200 Subject: ALSA: ump: Add helper to change MIDI protocol This is a preliminary patch for MIDI 2.0 USB gadget driver. Export a new helper to allow changing the current MIDI protocol from the outside. Link: https://lore.kernel.org/r/20230621110241.4751-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 2 ++ sound/core/ump.c | 31 ++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 68478e7be3b4..3c7e67475676 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -108,6 +108,8 @@ static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, } #endif +int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol); + /* * Some definitions for UMP */ diff --git a/sound/core/ump.c b/sound/core/ump.c index a64dc2d8a129..4150b9c0b35b 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -671,18 +671,35 @@ static void seq_notify_protocol(struct snd_ump_endpoint *ump) #endif /* CONFIG_SND_SEQUENCER */ } +/** + * snd_ump_switch_protocol - switch MIDI protocol + * @ump: UMP endpoint + * @protocol: protocol to switch to + * + * Returns 1 if the protocol is actually switched, 0 if unchanged + */ +int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol) +{ + protocol &= ump->info.protocol_caps; + if (protocol == ump->info.protocol) + return 0; + + ump->info.protocol = protocol; + ump_dbg(ump, "New protocol = %x (caps = %x)\n", + protocol, ump->info.protocol_caps); + seq_notify_protocol(ump); + return 1; +} +EXPORT_SYMBOL_GPL(snd_ump_switch_protocol); + /* handle EP stream config message; update the UMP protocol */ static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump, const union snd_ump_stream_msg *buf) { - unsigned int old_protocol = ump->info.protocol; - - ump->info.protocol = + unsigned int protocol = (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts; - ump_dbg(ump, "Current protocol = %x (caps = %x)\n", - ump->info.protocol, ump->info.protocol_caps); - if (ump->parsed && ump->info.protocol != old_protocol) - seq_notify_protocol(ump); + + snd_ump_switch_protocol(ump, protocol); return 1; /* finished */ } -- cgit v1.2.3 From eacd9c7f1d3ab8381a99b98b36652b5cf6ae8387 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jun 2023 13:02:40 +0200 Subject: ALSA: ump: Add no_process_stream flag This is another preliminary patch for USB MIDI 2.0 gadget driver. Add a new flag, no_process_stream, to snd_ump for suppressing the UMP Stream message handling in UMP core. Link: https://lore.kernel.org/r/20230621110241.4751-3-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 1 + sound/core/ump.c | 4 ++++ 2 files changed, 5 insertions(+) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 3c7e67475676..2f6a9944c6ef 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -28,6 +28,7 @@ struct snd_ump_endpoint { u32 stream_wait_for; /* expected stream message status */ bool stream_finished; /* set when message has been processed */ bool parsed; /* UMP / FB parse finished? */ + bool no_process_stream; /* suppress UMP stream messages handling */ wait_queue_head_t stream_wait; struct snd_rawmidi_file stream_rfile; diff --git a/sound/core/ump.c b/sound/core/ump.c index 4150b9c0b35b..5e73c9cf5919 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -854,6 +854,10 @@ static void ump_handle_stream_msg(struct snd_ump_endpoint *ump, unsigned int status; int ret; + /* UMP stream message suppressed (for gadget UMP)? */ + if (ump->no_process_stream) + return; + BUILD_BUG_ON(sizeof(*msg) != 16); ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n", buf[0], buf[1], buf[2], buf[3]); -- cgit v1.2.3 From 4dce2f076b7d0a0a99867b58eea7c212ff4e2be5 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 21 Jun 2023 13:02:41 +0200 Subject: ALSA: ump: Export snd_ump_receive_ump_val() This is another preliminary patch for USB MIDI 2.0 gadget driver. Export the currently local snd_ump_receive_ump_val(). It can be used by the gadget driver for processing the UMP data. Link: https://lore.kernel.org/r/20230621110241.4751-4-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump.h | 1 + sound/core/ump.c | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/include/sound/ump.h b/include/sound/ump.h index 2f6a9944c6ef..44d2c2fd021d 100644 --- a/include/sound/ump.h +++ b/include/sound/ump.h @@ -109,6 +109,7 @@ static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, } #endif +int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val); int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol); /* diff --git a/sound/core/ump.c b/sound/core/ump.c index 5e73c9cf5919..5e17351ca984 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -263,12 +263,16 @@ static unsigned char ump_packet_words[0x10] = { 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4 }; -/* parse the UMP packet data; - * the data is copied onto ump->input_buf[]. +/** + * snd_ump_receive_ump_val - parse the UMP packet data + * @ump: UMP endpoint + * @val: UMP packet data + * + * The data is copied onto ump->input_buf[]. * When a full packet is completed, returns the number of words (from 1 to 4). * OTOH, if the packet is incomplete, returns 0. */ -static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) +int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) { int words; @@ -284,6 +288,7 @@ static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val) } return 0; } +EXPORT_SYMBOL_GPL(snd_ump_receive_ump_val); /** * snd_ump_receive - transfer UMP packets from the device -- cgit v1.2.3 From 724418b84e6248cd27599607b7e5fac365b8e3f5 Mon Sep 17 00:00:00 2001 From: Matthew Anderson Date: Wed, 21 Jun 2023 11:17:14 -0500 Subject: ALSA: hda/realtek: Add quirks for ROG ALLY CS35l41 audio This requires a patched ACPI table or a firmware from ASUS to work because the system does not come with the _DSD field for the CSC3551. Link: https://bugzilla.kernel.org/show_bug.cgi?id=217550 Signed-off-by: Matthew Anderson Tested-by: Philip Mueller Link: https://lore.kernel.org/r/20230621161714.9442-1-ruinairas1992@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/hda/patch_realtek.c | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'sound') diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index ce9a9cb41e4b..2ab1065d5a1c 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -7121,6 +7121,10 @@ enum { ALC294_FIXUP_ASUS_DUAL_SPK, ALC285_FIXUP_THINKPAD_X1_GEN7, ALC285_FIXUP_THINKPAD_HEADSET_JACK, + ALC294_FIXUP_ASUS_ALLY, + ALC294_FIXUP_ASUS_ALLY_PINS, + ALC294_FIXUP_ASUS_ALLY_VERBS, + ALC294_FIXUP_ASUS_ALLY_SPEAKER, ALC294_FIXUP_ASUS_HPE, ALC294_FIXUP_ASUS_COEF_1B, ALC294_FIXUP_ASUS_GX502_HP, @@ -8417,6 +8421,47 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC294_FIXUP_SPK2_TO_DAC1 }, + [ALC294_FIXUP_ASUS_ALLY] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs35l41_fixup_i2c_two, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_PINS + }, + [ALC294_FIXUP_ASUS_ALLY_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x03a11050 }, + { 0x1a, 0x03a11c30 }, + { 0x21, 0x03211420 }, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_VERBS + }, + [ALC294_FIXUP_ASUS_ALLY_VERBS] = { + .type = HDA_FIXUP_VERBS, + .v.verbs = (const struct hda_verb[]) { + { 0x20, AC_VERB_SET_COEF_INDEX, 0x45 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x5089 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x46 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0004 }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x47 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0xa47a }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x49 }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x0049}, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x4a }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x201b }, + { 0x20, AC_VERB_SET_COEF_INDEX, 0x6b }, + { 0x20, AC_VERB_SET_PROC_COEF, 0x4278}, + { } + }, + .chained = true, + .chain_id = ALC294_FIXUP_ASUS_ALLY_SPEAKER + }, + [ALC294_FIXUP_ASUS_ALLY_SPEAKER] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc285_fixup_speaker2_to_dac1, + }, [ALC285_FIXUP_THINKPAD_X1_GEN7] = { .type = HDA_FIXUP_FUNC, .v.func = alc285_fixup_thinkpad_x1_gen7, @@ -9510,6 +9555,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x16e3, "ASUS UX50", ALC269_FIXUP_STEREO_DMIC), SND_PCI_QUIRK(0x1043, 0x1740, "ASUS UX430UA", ALC295_FIXUP_ASUS_DACS), SND_PCI_QUIRK(0x1043, 0x17d1, "ASUS UX431FL", ALC294_FIXUP_ASUS_DUAL_SPK), + SND_PCI_QUIRK(0x1043, 0x17f3, "ROG Ally RC71L_RC71L", ALC294_FIXUP_ASUS_ALLY), SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), SND_PCI_QUIRK(0x1043, 0x18f1, "Asus FX505DT", ALC256_FIXUP_ASUS_HEADSET_MIC), -- cgit v1.2.3 From 33cd7630782df2230529c3e8f1a6d0ae9cd6ab49 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 23 Jun 2023 09:55:30 +0200 Subject: ALSA: ump: Export MIDI1 / UMP conversion helpers Yet more preliminary work for the upcoming USB gadget support. Now export the helpers to convert between legacy MIDI1 and UMP data for handling the MIDI 1.0 USB interface. The header file is moved to include/sound. The API functions are slightly changed, so that they can be used without the direct access to snd_ump object. The allocation is done in ump.c itself as it's a simple kcalloc(). Link: https://lore.kernel.org/r/20230623075530.10976-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/ump_convert.h | 46 ++++++++++++++++++++++++++ sound/core/ump.c | 18 +++++----- sound/core/ump_convert.c | 80 +++++++++++++++++++-------------------------- sound/core/ump_convert.h | 43 ------------------------ 4 files changed, 89 insertions(+), 98 deletions(-) create mode 100644 include/sound/ump_convert.h delete mode 100644 sound/core/ump_convert.h (limited to 'sound') diff --git a/include/sound/ump_convert.h b/include/sound/ump_convert.h new file mode 100644 index 000000000000..28c364c63245 --- /dev/null +++ b/include/sound/ump_convert.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __SOUND_UMP_CONVERT_H +#define __SOUND_UMP_CONVERT_H + +#include + +/* context for converting from legacy control messages to UMP packet */ +struct ump_cvt_to_ump_bank { + bool rpn_set; + bool nrpn_set; + bool bank_set; + unsigned char cc_rpn_msb, cc_rpn_lsb; + unsigned char cc_nrpn_msb, cc_nrpn_lsb; + unsigned char cc_data_msb, cc_data_lsb; + unsigned char cc_bank_msb, cc_bank_lsb; +}; + +/* context for converting from MIDI1 byte stream to UMP packet */ +struct ump_cvt_to_ump { + /* MIDI1 intermediate buffer */ + unsigned char buf[4]; + int len; + int cmd_bytes; + + /* UMP output packet */ + u32 ump[4]; + int ump_bytes; + + /* various status */ + unsigned int in_sysex; + struct ump_cvt_to_ump_bank bank[16]; /* per channel */ +}; + +int snd_ump_convert_from_ump(const u32 *data, unsigned char *dst, + unsigned char *group_ret); +void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c); + +/* reset the converter context, called at each open to ump */ +static inline void snd_ump_convert_reset(struct ump_cvt_to_ump *ctx) +{ + memset(ctx, 0, sizeof(*ctx)); + +} + +#endif /* __SOUND_UMP_CONVERT_H */ diff --git a/sound/core/ump.c b/sound/core/ump.c index 5e17351ca984..246348766ec1 100644 --- a/sound/core/ump.c +++ b/sound/core/ump.c @@ -11,7 +11,7 @@ #include #include #include -#include "ump_convert.h" +#include #define ump_err(ump, fmt, args...) dev_err(&(ump)->core.dev, fmt, ##args) #define ump_warn(ump, fmt, args...) dev_warn(&(ump)->core.dev, fmt, ##args) @@ -87,7 +87,7 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi) ump->private_free(ump); #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI) - snd_ump_convert_free(ump); + kfree(ump->out_cvts); #endif } @@ -1002,7 +1002,7 @@ static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream) goto unlock; } ump->legacy_out_opens++; - snd_ump_reset_convert_to_ump(ump, group); + snd_ump_convert_reset(&ump->out_cvts[group]); } spin_lock_irq(&ump->legacy_locks[dir]); ump->legacy_substreams[dir][group] = substream; @@ -1091,7 +1091,7 @@ static int process_legacy_output(struct snd_ump_endpoint *ump, ctx = &ump->out_cvts[group]; while (!ctx->ump_bytes && snd_rawmidi_transmit(substream, &c, 1) > 0) - snd_ump_convert_to_ump(ump, group, c); + snd_ump_convert_to_ump(ctx, group, ump->info.protocol, c); if (ctx->ump_bytes && ctx->ump_bytes <= count) { size = ctx->ump_bytes; memcpy(buffer, ctx->ump, size); @@ -1113,7 +1113,7 @@ static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src, const int dir = SNDRV_RAWMIDI_STREAM_INPUT; int size; - size = snd_ump_convert_from_ump(ump, src, buf, &group); + size = snd_ump_convert_from_ump(src, buf, &group); if (size <= 0) return; spin_lock_irqsave(&ump->legacy_locks[dir], flags); @@ -1130,9 +1130,9 @@ int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, bool input, output; int err; - err = snd_ump_convert_init(ump); - if (err < 0) - return err; + ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); + if (!ump->out_cvts) + return -ENOMEM; input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT; output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT; @@ -1140,7 +1140,7 @@ int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump, output ? 16 : 0, input ? 16 : 0, &rmidi); if (err < 0) { - snd_ump_convert_free(ump); + kfree(ump->out_cvts); return err; } diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 48ab3e1bd62e..fb61df424a87 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -8,7 +8,7 @@ #include #include #include -#include "ump_convert.h" +#include /* * Upgrade / downgrade value bits @@ -205,12 +205,18 @@ static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf) return size; } -/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf; - * the target group is stored at @group_ret, - * returns the number of bytes of MIDI 1.0 stream +/** + * snd_ump_convert_from_ump - convert from UMP to legacy MIDI + * @data: UMP packet + * @buf: buffer to store legacy MIDI data + * @group_ret: pointer to store the target group + * + * Convert from a UMP packet @data to MIDI 1.0 bytes at @buf. + * The target group is stored at @group_ret. + * + * The function returns the number of bytes of MIDI 1.0 stream. */ -int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, - const u32 *data, +int snd_ump_convert_from_ump(const u32 *data, unsigned char *buf, unsigned char *group_ret) { @@ -230,6 +236,7 @@ int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, return 0; } +EXPORT_SYMBOL_GPL(snd_ump_convert_from_ump); /* * MIDI 1 byte stream -> UMP conversion @@ -302,10 +309,10 @@ static void fill_rpn(struct ump_cvt_to_ump_bank *cc, } /* convert to a MIDI 1.0 Channel Voice message */ -static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, - struct ump_cvt_to_ump *cvt, - unsigned char group, u32 *data, - unsigned char bytes) +static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, + unsigned char group, + unsigned int protocol, + u32 *data, unsigned char bytes) { const unsigned char *buf = cvt->buf; struct ump_cvt_to_ump_bank *cc; @@ -316,7 +323,7 @@ static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8); /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */ - if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { + if (protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) { data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE, group, 0, buf[0]); data[0] |= buf[1] << 8; @@ -413,8 +420,8 @@ static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump, return 8; } -static int do_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group, unsigned char c, u32 *data) +static int do_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c, u32 *data) { /* bytes for 0x80-0xf0 */ static unsigned char cmd_bytes[8] = { @@ -424,7 +431,6 @@ static int do_convert_to_ump(struct snd_ump_endpoint *ump, static unsigned char system_bytes[16] = { 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1 }; - struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; unsigned char bytes; if (c == UMP_MIDI1_MSG_SYSEX_START) { @@ -478,40 +484,22 @@ static int do_convert_to_ump(struct snd_ump_endpoint *ump, cvt->len = 1; if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME) return cvt_legacy_system_to_ump(cvt, group, data); - return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes); + return cvt_legacy_cmd_to_ump(cvt, group, protocol, data, cvt->cmd_bytes); } -/* feed a MIDI 1.0 byte @c and convert to a UMP packet; - * the target group is @group, - * the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes +/** + * snd_ump_convert_to_ump - convert legacy MIDI byte to UMP packet + * @cvt: converter context + * @group: target UMP group + * @protocol: target UMP protocol + * @c: MIDI 1.0 byte data + * + * Feed a MIDI 1.0 byte @c and convert to a UMP packet if completed. + * The result is stored in the buffer in @cvt. */ -void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group, unsigned char c) +void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group, + unsigned int protocol, unsigned char c) { - struct ump_cvt_to_ump *cvt = &ump->out_cvts[group]; - - cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump); -} - -/* reset the converter context, called at each open */ -void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group) -{ - memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts)); -} - -/* initialize converters */ -int snd_ump_convert_init(struct snd_ump_endpoint *ump) -{ - ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL); - if (!ump->out_cvts) - return -ENOMEM; - return 0; -} - -/* release resources */ -void snd_ump_convert_free(struct snd_ump_endpoint *ump) -{ - kfree(ump->out_cvts); - ump->out_cvts = NULL; + cvt->ump_bytes = do_convert_to_ump(cvt, group, protocol, c, cvt->ump); } +EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump); diff --git a/sound/core/ump_convert.h b/sound/core/ump_convert.h deleted file mode 100644 index bbfe96084779..000000000000 --- a/sound/core/ump_convert.h +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#ifndef __UMP_CONVERT_H -#define __UMP_CONVERT_H - -#include - -/* context for converting from legacy control messages to UMP packet */ -struct ump_cvt_to_ump_bank { - bool rpn_set; - bool nrpn_set; - bool bank_set; - unsigned char cc_rpn_msb, cc_rpn_lsb; - unsigned char cc_nrpn_msb, cc_nrpn_lsb; - unsigned char cc_data_msb, cc_data_lsb; - unsigned char cc_bank_msb, cc_bank_lsb; -}; - -/* context for converting from MIDI1 byte stream to UMP packet */ -struct ump_cvt_to_ump { - /* MIDI1 intermediate buffer */ - unsigned char buf[4]; - int len; - int cmd_bytes; - - /* UMP output packet */ - u32 ump[4]; - int ump_bytes; - - /* various status */ - unsigned int in_sysex; - struct ump_cvt_to_ump_bank bank[16]; /* per channel */ -}; - -int snd_ump_convert_init(struct snd_ump_endpoint *ump); -void snd_ump_convert_free(struct snd_ump_endpoint *ump); -int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump, - const u32 *data, unsigned char *dst, - unsigned char *group_ret); -void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group, unsigned char c); -void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump, - unsigned char group); -#endif /* __UMP_CONVERT_H */ -- cgit v1.2.3 From 04b49b90caeed0b5544ff616d654900d27d403b6 Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Sat, 24 Jun 2023 18:52:16 +0200 Subject: ALSA: pcm: fix ELD constraints for (E)AC3, DTS(-HD) and MLP formats The SADs of compressed formats contain the channel and sample rate info of the audio data inside the compressed stream, but when building constraints we must use the rates and channels used to transport the compressed streams. eg 48kHz 6ch EAC3 needs to be transmitted as a 2ch 192kHz stream. This patch fixes the constraints for the common AC3 and DTS formats, the constraints for the less common MPEG, DSD etc formats are copied directly from the info in the SADs as before as I don't have the specs and equipment to test those. Signed-off-by: Matthias Reichl Link: https://lore.kernel.org/r/20230624165216.5719-1-hias@horus.com Signed-off-by: Takashi Iwai --- sound/core/pcm_drm_eld.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/core/pcm_drm_eld.c b/sound/core/pcm_drm_eld.c index 4b5faae5d16e..07075071972d 100644 --- a/sound/core/pcm_drm_eld.c +++ b/sound/core/pcm_drm_eld.c @@ -2,11 +2,25 @@ /* * PCM DRM helpers */ +#include #include +#include #include #include #include +#define SAD0_CHANNELS_MASK GENMASK(2, 0) /* max number of channels - 1 */ +#define SAD0_FORMAT_MASK GENMASK(6, 3) /* audio format */ + +#define SAD1_RATE_MASK GENMASK(6, 0) /* bitfield of supported rates */ +#define SAD1_RATE_32000_MASK BIT(0) +#define SAD1_RATE_44100_MASK BIT(1) +#define SAD1_RATE_48000_MASK BIT(2) +#define SAD1_RATE_88200_MASK BIT(3) +#define SAD1_RATE_96000_MASK BIT(4) +#define SAD1_RATE_176400_MASK BIT(5) +#define SAD1_RATE_192000_MASK BIT(6) + static const unsigned int eld_rates[] = { 32000, 44100, @@ -17,9 +31,62 @@ static const unsigned int eld_rates[] = { 192000, }; +static unsigned int map_rate_families(const u8 *sad, + unsigned int mask_32000, + unsigned int mask_44100, + unsigned int mask_48000) +{ + unsigned int rate_mask = 0; + + if (sad[1] & SAD1_RATE_32000_MASK) + rate_mask |= mask_32000; + if (sad[1] & (SAD1_RATE_44100_MASK | SAD1_RATE_88200_MASK | SAD1_RATE_176400_MASK)) + rate_mask |= mask_44100; + if (sad[1] & (SAD1_RATE_48000_MASK | SAD1_RATE_96000_MASK | SAD1_RATE_192000_MASK)) + rate_mask |= mask_48000; + return rate_mask; +} + +static unsigned int sad_rate_mask(const u8 *sad) +{ + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return sad[1] & SAD1_RATE_MASK; + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + return map_rate_families(sad, + SAD1_RATE_32000_MASK, + SAD1_RATE_44100_MASK, + SAD1_RATE_48000_MASK); + case HDMI_AUDIO_CODING_TYPE_EAC3: + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return map_rate_families(sad, + 0, + SAD1_RATE_176400_MASK, + SAD1_RATE_192000_MASK); + default: + /* TODO adjust for other compressed formats as well */ + return sad[1] & SAD1_RATE_MASK; + } +} + static unsigned int sad_max_channels(const u8 *sad) { - return 1 + (sad[0] & 7); + switch (FIELD_GET(SAD0_FORMAT_MASK, sad[0])) { + case HDMI_AUDIO_CODING_TYPE_PCM: + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + case HDMI_AUDIO_CODING_TYPE_AC3: + case HDMI_AUDIO_CODING_TYPE_DTS: + case HDMI_AUDIO_CODING_TYPE_EAC3: + return 2; + case HDMI_AUDIO_CODING_TYPE_DTS_HD: + case HDMI_AUDIO_CODING_TYPE_MLP: + return 8; + default: + /* TODO adjust for other compressed formats as well */ + return 1 + FIELD_GET(SAD0_CHANNELS_MASK, sad[0]); + } } static int eld_limit_rates(struct snd_pcm_hw_params *params, @@ -42,7 +109,7 @@ static int eld_limit_rates(struct snd_pcm_hw_params *params, * requested number of channels. */ if (c->min <= max_channels) - rate_mask |= sad[1]; + rate_mask |= sad_rate_mask(sad); } } @@ -70,7 +137,7 @@ static int eld_limit_channels(struct snd_pcm_hw_params *params, rate_mask |= BIT(i); for (i = drm_eld_sad_count(eld); i > 0; i--, sad += 3) - if (rate_mask & sad[1]) + if (rate_mask & sad_rate_mask(sad)) t.max = max(t.max, sad_max_channels(sad)); } -- cgit v1.2.3 From 4e0871333661d2ec0ed3dc00a945c2160eccae77 Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Sat, 24 Jun 2023 18:52:32 +0200 Subject: ASoC: hdmi-codec: fix channel info for compressed formats According to CTA 861 the channel/speaker allocation info in the audio infoframe only applies to uncompressed (PCM) audio streams. The channel count info should indicate the number of channels in the transmitted audio, which usually won't match the number of channels used to transmit the compressed bitstream. Some devices (eg some Sony TVs) will refuse to decode compressed audio if these values are not set correctly. To fix this we can simply set the channel count to 0 (which means "refer to stream header") and set the channel/speaker allocation to 0 as well (which would mean stereo FL/FR for PCM, a safe value all sinks will support) when transmitting compressed audio. Signed-off-by: Matthias Reichl Link: https://lore.kernel.org/r/20230624165232.5751-1-hias@horus.com Signed-off-by: Takashi Iwai --- sound/soc/codecs/hdmi-codec.c | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/hdmi-codec.c b/sound/soc/codecs/hdmi-codec.c index 6d980fbc4207..d21f69f05342 100644 --- a/sound/soc/codecs/hdmi-codec.c +++ b/sound/soc/codecs/hdmi-codec.c @@ -495,31 +495,43 @@ static int hdmi_codec_fill_codec_params(struct snd_soc_dai *dai, struct hdmi_codec_params *hp) { struct hdmi_codec_priv *hcp = snd_soc_dai_get_drvdata(dai); - int idx; - - /* Select a channel allocation that matches with ELD and pcm channels */ - idx = hdmi_codec_get_ch_alloc_table_idx(hcp, channels); - if (idx < 0) { - dev_err(dai->dev, "Not able to map channels to speakers (%d)\n", - idx); - hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; - return idx; + int idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + u8 ca_id = 0; + bool pcm_audio = !(hcp->iec_status[0] & IEC958_AES0_NONAUDIO); + + if (pcm_audio) { + /* Select a channel allocation that matches with ELD and pcm channels */ + idx = hdmi_codec_get_ch_alloc_table_idx(hcp, channels); + + if (idx < 0) { + dev_err(dai->dev, "Not able to map channels to speakers (%d)\n", + idx); + hcp->chmap_idx = HDMI_CODEC_CHMAP_IDX_UNKNOWN; + return idx; + } + + ca_id = hdmi_codec_channel_alloc[idx].ca_id; } memset(hp, 0, sizeof(*hp)); hdmi_audio_infoframe_init(&hp->cea); - hp->cea.channels = channels; + + if (pcm_audio) + hp->cea.channels = channels; + else + hp->cea.channels = 0; + hp->cea.coding_type = HDMI_AUDIO_CODING_TYPE_STREAM; hp->cea.sample_size = HDMI_AUDIO_SAMPLE_SIZE_STREAM; hp->cea.sample_frequency = HDMI_AUDIO_SAMPLE_FREQUENCY_STREAM; - hp->cea.channel_allocation = hdmi_codec_channel_alloc[idx].ca_id; + hp->cea.channel_allocation = ca_id; hp->sample_width = sample_width; hp->sample_rate = sample_rate; hp->channels = channels; - hcp->chmap_idx = hdmi_codec_channel_alloc[idx].ca_id; + hcp->chmap_idx = idx; return 0; } -- cgit v1.2.3