* [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX
@ 2026-04-06 3:20 Cássio Gabriel
2026-04-06 3:20 ` [PATCH 1/4] ALSA: gus: add shared GF1 suspend and resume helpers Cássio Gabriel
` (4 more replies)
0 siblings, 5 replies; 7+ messages in thread
From: Cássio Gabriel @ 2026-04-06 3:20 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: linux-sound, linux-kernel, Cássio Gabriel
gusclassic, gusextreme and gusmax still leave their ISA PM callbacks
disabled, so a suspend/resume cycle does not restore usable GF1 operation
on these cards.
A simple wrapper around the existing GF1 start/stop paths is not enough.
Those helpers are probe and shutdown paths: they reset software handlers
and tear down runtime state such as the DRAM allocator, timer state, DMA
queues, PCM state and UART setup. Resume instead needs a narrower recovery
path that rebuilds the GF1 hardware state without rerunning probe-only
detection or discarding the bookkeeping kept by the card instance.
gusextreme and gusmax also need board-specific resume glue around that
shared GF1 recovery path. GUS Extreme has to restore the ES1688-side GF1
routing before the GF1 is touched again. GUS MAX has to rewrite the MAX
control register and restore the attached WSS codec state before the full
card is brought back.
This series does that in four steps:
- add shared GF1 suspend/resume helpers that quiesce PCM, DMA and UART
activity and restart the GF1 hardware without rerunning memory detection
or allocator initialization
- wire gusclassic up to the shared helpers
- wire gusextreme up as well, restoring the ES1688-side GF1 routing
before the shared resume path runs
- wire gusmax up too, restoring the MAX control register and the WSS codec
state around the shared GF1 resume path
The scope is limited to restoring post-resume usability for these ISA
cards. It does not attempt transparent continuation of active GF1 PCM or
synth state across suspend, and userspace may still need to reprepare
streams or reload onboard sample data after resume. Open rawmidi
substreams are restored only to a usable post-resume state.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
Cássio Gabriel (4):
ALSA: gus: add shared GF1 suspend and resume helpers
ALSA: gusclassic: add ISA suspend and resume callbacks
ALSA: gusextreme: add ISA suspend and resume callbacks
ALSA: gusmax: add ISA suspend and resume callbacks
include/sound/gus.h | 8 ++++++
sound/isa/gus/gus_dma.c | 33 ++++++++++++++++++++++++
sound/isa/gus/gus_main.c | 36 +++++++++++++++++++++++++++
sound/isa/gus/gus_pcm.c | 7 +++---
sound/isa/gus/gus_reset.c | 62 ++++++++++++++++++++++++++++++++++++++--------
sound/isa/gus/gus_timer.c | 14 +++++++++++
sound/isa/gus/gus_uart.c | 47 +++++++++++++++++++++++++++++++++++
sound/isa/gus/gusclassic.c | 21 +++++++++++++++-
sound/isa/gus/gusextreme.c | 57 +++++++++++++++++++++++++++++++++++-------
sound/isa/gus/gusmax.c | 28 ++++++++++++++++++++-
10 files changed, 289 insertions(+), 24 deletions(-)
---
base-commit: 9f2a23bd5d30dc0968e56a2b6e5edb58aff1bc6d
change-id: 20260405-b4-alsa-gus-isa-pm-34f42868428e
Best regards,
--
Cássio Gabriel <cassiogabrielcontato@gmail.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH 1/4] ALSA: gus: add shared GF1 suspend and resume helpers
2026-04-06 3:20 [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Cássio Gabriel
@ 2026-04-06 3:20 ` Cássio Gabriel
2026-04-06 3:20 ` [PATCH 2/4] ALSA: gusclassic: add ISA suspend and resume callbacks Cássio Gabriel
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Cássio Gabriel @ 2026-04-06 3:20 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: linux-sound, linux-kernel, Cássio Gabriel
gusclassic and gusextreme still leave their ISA PM callbacks disabled
because the shared GF1 core only provides probe-time startup and full
shutdown paths.
Those helpers are not suitable for suspend and resume. They reset software
handlers and tear down runtime state such as the DRAM allocator, timer
state, DMA queues, PCM state and UART setup. Resume instead needs a
narrower recovery path that rebuilds the GF1 hardware state without
rerunning probe-only detection or discarding the bookkeeping kept by the
card instance.
Add shared GF1 suspend and resume helpers for that recovery path. Suspend
now quiesces GF1 PCM, aborts queued GF1 DMA work, resets the UART and
powers the chip down without tearing down allocator, timer or rawmidi
bookkeeping. Resume rebuilds the GF1 hardware state, restores timer and
UART handlers, and brings the chip back to a usable post-resume state for
the ISA front-ends.
The scope is limited to restoring post-resume usability. It does not
attempt transparent continuation of active GF1 PCM or synth state across
suspend, and userspace may still need to reprepare streams or reload
onboard sample data after resume. Open rawmidi substreams are restored
only to a usable post-resume state.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
include/sound/gus.h | 8 ++++++
sound/isa/gus/gus_dma.c | 33 +++++++++++++++++++++++++
sound/isa/gus/gus_main.c | 36 +++++++++++++++++++++++++++
sound/isa/gus/gus_pcm.c | 7 +++---
sound/isa/gus/gus_reset.c | 62 +++++++++++++++++++++++++++++++++++++++--------
sound/isa/gus/gus_timer.c | 14 +++++++++++
sound/isa/gus/gus_uart.c | 47 +++++++++++++++++++++++++++++++++++
7 files changed, 194 insertions(+), 13 deletions(-)
diff --git a/include/sound/gus.h b/include/sound/gus.h
index 321ae93625eb..3feb42627de1 100644
--- a/include/sound/gus.h
+++ b/include/sound/gus.h
@@ -536,6 +536,7 @@ int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
struct snd_gf1_dma_block * block,
int atomic,
int synth);
+void snd_gf1_dma_suspend(struct snd_gus_card *gus);
/* gus_volume.c */
@@ -552,6 +553,8 @@ struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, i
void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice);
int snd_gf1_start(struct snd_gus_card * gus);
int snd_gf1_stop(struct snd_gus_card * gus);
+int snd_gf1_suspend(struct snd_gus_card *gus);
+int snd_gf1_resume(struct snd_gus_card *gus);
/* gus_mixer.c */
@@ -572,6 +575,8 @@ int snd_gus_create(struct snd_card *card,
int effect,
struct snd_gus_card ** rgus);
int snd_gus_initialize(struct snd_gus_card * gus);
+int snd_gus_suspend(struct snd_gus_card *gus);
+int snd_gus_resume(struct snd_gus_card *gus);
/* gus_irq.c */
@@ -583,6 +588,8 @@ void snd_gus_irq_profile_init(struct snd_gus_card *gus);
/* gus_uart.c */
int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device);
+void snd_gf1_uart_suspend(struct snd_gus_card *gus);
+void snd_gf1_uart_resume(struct snd_gus_card *gus);
/* gus_dram.c */
int snd_gus_dram_write(struct snd_gus_card *gus, char __user *ptr,
@@ -593,5 +600,6 @@ int snd_gus_dram_read(struct snd_gus_card *gus, char __user *ptr,
/* gus_timer.c */
void snd_gf1_timers_init(struct snd_gus_card *gus);
void snd_gf1_timers_done(struct snd_gus_card *gus);
+void snd_gf1_timers_resume(struct snd_gus_card *gus);
#endif /* __SOUND_GUS_H */
diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c
index ffc69e26227e..30bd76eee96e 100644
--- a/sound/isa/gus/gus_dma.c
+++ b/sound/isa/gus/gus_dma.c
@@ -173,6 +173,39 @@ int snd_gf1_dma_done(struct snd_gus_card * gus)
return 0;
}
+void snd_gf1_dma_suspend(struct snd_gus_card *gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ guard(mutex)(&gus->dma_mutex);
+ if (!gus->gf1.dma_shared)
+ return;
+
+ snd_dma_disable(gus->gf1.dma1);
+ snd_gf1_dma_ack(gus);
+ if (gus->gf1.dma_ack)
+ gus->gf1.dma_ack(gus, gus->gf1.dma_private_data);
+ gus->gf1.dma_ack = NULL;
+ gus->gf1.dma_private_data = NULL;
+
+ while ((block = gus->gf1.dma_data_pcm)) {
+ gus->gf1.dma_data_pcm = block->next;
+ if (block->ack)
+ block->ack(gus, block->private_data);
+ kfree(block);
+ }
+ while ((block = gus->gf1.dma_data_synth)) {
+ gus->gf1.dma_data_synth = block->next;
+ if (block->ack)
+ block->ack(gus, block->private_data);
+ kfree(block);
+ }
+
+ gus->gf1.dma_data_pcm_last = NULL;
+ gus->gf1.dma_data_synth_last = NULL;
+ gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER;
+}
+
int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
struct snd_gf1_dma_block * __block,
int atomic,
diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c
index b2b189c83569..6adf8b698e2b 100644
--- a/sound/isa/gus/gus_main.c
+++ b/sound/isa/gus/gus_main.c
@@ -404,6 +404,42 @@ int snd_gus_initialize(struct snd_gus_card *gus)
return 0;
}
+int snd_gus_suspend(struct snd_gus_card *gus)
+{
+ int err;
+
+ if (gus->pcm) {
+ err = snd_pcm_suspend_all(gus->pcm);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_gf1_suspend(gus);
+ if (err < 0)
+ return err;
+
+ snd_power_change_state(gus->card, SNDRV_CTL_POWER_D3hot);
+ return 0;
+}
+EXPORT_SYMBOL(snd_gus_suspend);
+
+int snd_gus_resume(struct snd_gus_card *gus)
+{
+ int err;
+
+ err = snd_gus_init_dma_irq(gus, 1);
+ if (err < 0)
+ return err;
+
+ err = snd_gf1_resume(gus);
+ if (err < 0)
+ return err;
+
+ snd_power_change_state(gus->card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+EXPORT_SYMBOL(snd_gus_resume);
+
/* gus_io.c */
EXPORT_SYMBOL(snd_gf1_delay);
EXPORT_SYMBOL(snd_gf1_write8);
diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c
index caf371897b78..a0757e1ede46 100644
--- a/sound/isa/gus/gus_pcm.c
+++ b/sound/isa/gus/gus_pcm.c
@@ -471,7 +471,8 @@ static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream,
if (cmd == SNDRV_PCM_TRIGGER_START) {
snd_gf1_pcm_trigger_up(substream);
- } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP ||
+ cmd == SNDRV_PCM_TRIGGER_SUSPEND) {
scoped_guard(spinlock, &pcmp->lock) {
pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE;
}
@@ -558,7 +559,8 @@ static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream,
if (cmd == SNDRV_PCM_TRIGGER_START) {
val = gus->gf1.pcm_rcntrl_reg;
- } else if (cmd == SNDRV_PCM_TRIGGER_STOP) {
+ } else if (cmd == SNDRV_PCM_TRIGGER_STOP ||
+ cmd == SNDRV_PCM_TRIGGER_SUSPEND) {
val = 0;
} else {
return -EINVAL;
@@ -856,4 +858,3 @@ int snd_gf1_pcm_new(struct snd_gus_card *gus, int pcm_dev, int control_index)
return 0;
}
-
diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c
index a7a3e764bb77..998fa245708c 100644
--- a/sound/isa/gus/gus_reset.c
+++ b/sound/isa/gus/gus_reset.c
@@ -6,6 +6,7 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/time.h>
+#include <asm/dma.h>
#include <sound/core.h>
#include <sound/gus.h>
@@ -263,11 +264,18 @@ void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice)
private_free(voice);
}
-/*
- * call this function only by start of driver
- */
+static void snd_gf1_init_software_state(struct snd_gus_card *gus)
+{
+ unsigned int i;
-int snd_gf1_start(struct snd_gus_card * gus)
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL);
+ for (i = 0; i < 32; i++) {
+ gus->gf1.voices[i].number = i;
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i);
+ }
+}
+
+static void snd_gf1_hw_start(struct snd_gus_card *gus, bool initial)
{
unsigned int i;
@@ -277,14 +285,14 @@ int snd_gf1_start(struct snd_gus_card * gus)
udelay(160);
snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
- snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL);
- for (i = 0; i < 32; i++) {
- gus->gf1.voices[i].number = i;
- snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i);
+ if (initial) {
+ snd_gf1_init_software_state(gus);
+ snd_gf1_uart_cmd(gus, 0x03);
+ } else {
+ guard(spinlock_irqsave)(&gus->uart_cmd_lock);
+ outb(0x03, GUSP(gus, MIDICTRL));
}
- snd_gf1_uart_cmd(gus, 0x03); /* huh.. this cleanup took me some time... */
-
if (gus->gf1.enh_mode) { /* enhanced mode !!!! */
snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01);
snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01);
@@ -293,6 +301,8 @@ int snd_gf1_start(struct snd_gus_card * gus)
snd_gf1_select_active_voices(gus);
snd_gf1_delay(gus);
gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8;
+ gus->gf1.hw_lfo = 0;
+ gus->gf1.sw_lfo = 0;
/* initialize LFOs & clear LFOs memory */
if (gus->gf1.enh_mode && gus->gf1.memory) {
gus->gf1.hw_lfo = 1;
@@ -321,7 +331,15 @@ int snd_gf1_start(struct snd_gus_card * gus)
outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE));
outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
}
+}
+int snd_gf1_start(struct snd_gus_card *gus)
+{
+ /*
+ * Probe-time startup initializes both GF1 hardware and the
+ * software state that suspend/resume keeps across PM cycles.
+ */
+ snd_gf1_hw_start(gus, true);
snd_gf1_timers_init(gus);
snd_gf1_look_regs(gus);
snd_gf1_mem_init(gus);
@@ -357,3 +375,27 @@ int snd_gf1_stop(struct snd_gus_card * gus)
return 0;
}
+
+int snd_gf1_suspend(struct snd_gus_card *gus)
+{
+ snd_gf1_dma_suspend(gus);
+ snd_gf1_uart_suspend(gus);
+
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0);
+ snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL);
+ snd_gf1_stop_voices(gus, 0, 31);
+ snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1);
+ snd_dma_disable(gus->gf1.dma2);
+
+ return 0;
+}
+
+int snd_gf1_resume(struct snd_gus_card *gus)
+{
+ snd_gf1_hw_start(gus, false);
+ snd_gf1_timers_resume(gus);
+ snd_gf1_uart_resume(gus);
+
+ return 0;
+}
diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c
index e3a8847e02cf..14dcde138bc7 100644
--- a/sound/isa/gus/gus_timer.c
+++ b/sound/isa/gus/gus_timer.c
@@ -178,3 +178,17 @@ void snd_gf1_timers_done(struct snd_gus_card * gus)
gus->gf1.timer2 = NULL;
}
}
+
+void snd_gf1_timers_resume(struct snd_gus_card *gus)
+{
+ if (gus->gf1.timer1) {
+ gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1;
+ if (gus->gf1.timer_enabled & 4)
+ snd_gf1_timer1_start(gus->gf1.timer1);
+ }
+ if (gus->gf1.timer2) {
+ gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2;
+ if (gus->gf1.timer_enabled & 8)
+ snd_gf1_timer2_start(gus->gf1.timer2);
+ }
+}
diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c
index 770d8f3e4cff..25057a5a81b0 100644
--- a/sound/isa/gus/gus_uart.c
+++ b/sound/isa/gus/gus_uart.c
@@ -232,3 +232,50 @@ int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device)
gus->midi_uart = rmidi;
return err;
}
+
+void snd_gf1_uart_suspend(struct snd_gus_card *gus)
+{
+ guard(spinlock_irqsave)(&gus->uart_cmd_lock);
+ outb(0x03, GUSP(gus, MIDICTRL));
+}
+
+void snd_gf1_uart_resume(struct snd_gus_card *gus)
+{
+ unsigned short uart_cmd;
+ bool active;
+ int i;
+
+ scoped_guard(spinlock_irqsave, &gus->uart_cmd_lock) {
+ active = gus->midi_substream_input || gus->midi_substream_output;
+ }
+ if (!active)
+ return;
+
+ /* snd_gf1_hw_start() already left MIDICTRL in reset. */
+ usleep_range(160, 200);
+
+ guard(spinlock_irqsave)(&gus->uart_cmd_lock);
+ if (!gus->midi_substream_input && !gus->midi_substream_output)
+ return;
+
+ if (gus->midi_substream_output)
+ gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out;
+ if (gus->midi_substream_input)
+ gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;
+
+ if (!gus->uart_enable)
+ return;
+
+ uart_cmd = gus->gf1.uart_cmd;
+ snd_gf1_uart_cmd(gus, 0x00);
+
+ if (gus->midi_substream_input) {
+ for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++)
+ snd_gf1_uart_get(gus);
+ if (i >= 1000)
+ dev_err(gus->card->dev,
+ "gus midi uart resume - cleanup error\n");
+ }
+
+ snd_gf1_uart_cmd(gus, uart_cmd);
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 2/4] ALSA: gusclassic: add ISA suspend and resume callbacks
2026-04-06 3:20 [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Cássio Gabriel
2026-04-06 3:20 ` [PATCH 1/4] ALSA: gus: add shared GF1 suspend and resume helpers Cássio Gabriel
@ 2026-04-06 3:20 ` Cássio Gabriel
2026-04-06 3:20 ` [PATCH 3/4] ALSA: gusextreme: " Cássio Gabriel
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Cássio Gabriel @ 2026-04-06 3:20 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: linux-sound, linux-kernel, Cássio Gabriel
gusclassic still leaves its ISA PM callbacks disabled because the shared
GF1 core had no suspend and resume path suitable for PM recovery.
Wire the driver up to the new shared GUS suspend and resume helpers so a
suspend/resume cycle restores usable GF1 operation without rerunning
probe-only detection or tearing down the runtime bookkeeping kept by the
card instance.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
sound/isa/gus/gusclassic.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c
index 101202acefb3..363c819ced89 100644
--- a/sound/isa/gus/gusclassic.c
+++ b/sound/isa/gus/gusclassic.c
@@ -145,6 +145,7 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n)
error = snd_gusclassic_create(card, dev, n, &gus);
if (error < 0)
return error;
+ card->private_data = gus;
error = snd_gusclassic_detect(gus);
if (error < 0)
@@ -193,11 +194,29 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_gusclassic_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+
+ return snd_gus_suspend(card->private_data);
+}
+
+static int snd_gusclassic_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+
+ return snd_gus_resume(card->private_data);
+}
+#endif
+
static struct isa_driver snd_gusclassic_driver = {
.match = snd_gusclassic_match,
.probe = snd_gusclassic_probe,
-#if 0 /* FIXME */
+#ifdef CONFIG_PM
.suspend = snd_gusclassic_suspend,
+ .resume = snd_gusclassic_resume,
#endif
.driver = {
.name = DEV_NAME
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 3/4] ALSA: gusextreme: add ISA suspend and resume callbacks
2026-04-06 3:20 [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Cássio Gabriel
2026-04-06 3:20 ` [PATCH 1/4] ALSA: gus: add shared GF1 suspend and resume helpers Cássio Gabriel
2026-04-06 3:20 ` [PATCH 2/4] ALSA: gusclassic: add ISA suspend and resume callbacks Cássio Gabriel
@ 2026-04-06 3:20 ` Cássio Gabriel
2026-04-06 3:20 ` [PATCH 4/4] ALSA: gusmax: " Cássio Gabriel
2026-04-06 9:11 ` [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Takashi Iwai
4 siblings, 0 replies; 7+ messages in thread
From: Cássio Gabriel @ 2026-04-06 3:20 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: linux-sound, linux-kernel, Cássio Gabriel
gusextreme still leaves its ISA PM callbacks disabled because the shared
GF1 core had no suspend and resume path suitable for PM recovery.
Resume on this board needs one extra step before the shared GF1 path can
touch the chip again: the ES1688 side must restore the GF1 routing. Split
that routing sequence into a helper, reuse it for probe and resume, reset
the ES1688 side first on resume, and then wire the driver up to the shared
GUS PM helpers.
This restores usable post-resume GF1 operation on GUS Extreme without
rerunning probe-only detection in the shared GF1 path.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
sound/isa/gus/gusextreme.c | 57 ++++++++++++++++++++++++++++++++++++++--------
1 file changed, 48 insertions(+), 9 deletions(-)
diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c
index ed921b89b00a..0984731740c4 100644
--- a/sound/isa/gus/gusextreme.c
+++ b/sound/isa/gus/gusextreme.c
@@ -44,6 +44,11 @@ static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29};
static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24};
static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
+struct snd_gusextreme {
+ struct snd_es1688 es1688;
+ struct snd_gus_card *gus;
+};
+
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard.");
module_param_array(id, charp, NULL, 0444);
@@ -142,17 +147,15 @@ static int snd_gusextreme_gus_card_create(struct snd_card *card,
0, channels[n], pcm_channels[n], 0, rgus);
}
-static int snd_gusextreme_detect(struct snd_gus_card *gus,
- struct snd_es1688 *es1688)
+static void snd_gusextreme_enable_gf1(struct snd_gus_card *gus,
+ struct snd_es1688 *es1688)
{
- unsigned char d;
-
/*
* This is main stuff - enable access to GF1 chip...
* I'm not sure, if this will work for card which have
* ES1688 chip in another place than 0x220.
- *
- * I used reverse-engineering in DOSEMU. [--jk]
+ *
+ * I used reverse-engineering in DOSEMU. [--jk]
*
* ULTRINIT.EXE:
* 0x230 = 0,2,3
@@ -172,7 +175,14 @@ static int snd_gusextreme_detect(struct snd_gus_card *gus,
outb(0, 0x201);
outb(gus->gf1.port & 0x010 ? 3 : 1, ES1688P(es1688, INIT1));
}
+}
+
+static int snd_gusextreme_detect(struct snd_gus_card *gus,
+ struct snd_es1688 *es1688)
+{
+ unsigned char d;
+ snd_gusextreme_enable_gf1(gus, es1688);
udelay(100);
snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */
@@ -223,16 +233,18 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n)
{
struct snd_card *card;
struct snd_gus_card *gus;
+ struct snd_gusextreme *gusextreme;
struct snd_es1688 *es1688;
struct snd_opl3 *opl3;
int error;
error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE,
- sizeof(struct snd_es1688), &card);
+ sizeof(*gusextreme), &card);
if (error < 0)
return error;
- es1688 = card->private_data;
+ gusextreme = card->private_data;
+ es1688 = &gusextreme->es1688;
if (mpu_port[n] == SNDRV_AUTO_PORT)
mpu_port[n] = 0;
@@ -250,6 +262,7 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n)
error = snd_gusextreme_gus_card_create(card, dev, n, &gus);
if (error < 0)
return error;
+ gusextreme->gus = gus;
error = snd_gusextreme_detect(gus, es1688);
if (error < 0)
@@ -321,10 +334,36 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_gusextreme_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusextreme *gusextreme = card->private_data;
+
+ return snd_gus_suspend(gusextreme->gus);
+}
+
+static int snd_gusextreme_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusextreme *gusextreme = card->private_data;
+ int err;
+
+ err = snd_es1688_reset(&gusextreme->es1688);
+ if (err < 0)
+ return err;
+
+ snd_gusextreme_enable_gf1(gusextreme->gus, &gusextreme->es1688);
+ usleep_range(100, 200);
+ return snd_gus_resume(gusextreme->gus);
+}
+#endif
+
static struct isa_driver snd_gusextreme_driver = {
.match = snd_gusextreme_match,
.probe = snd_gusextreme_probe,
-#if 0 /* FIXME */
+#ifdef CONFIG_PM
.suspend = snd_gusextreme_suspend,
.resume = snd_gusextreme_resume,
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH 4/4] ALSA: gusmax: add ISA suspend and resume callbacks
2026-04-06 3:20 [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Cássio Gabriel
` (2 preceding siblings ...)
2026-04-06 3:20 ` [PATCH 3/4] ALSA: gusextreme: " Cássio Gabriel
@ 2026-04-06 3:20 ` Cássio Gabriel
2026-04-06 9:11 ` [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Takashi Iwai
4 siblings, 0 replies; 7+ messages in thread
From: Cássio Gabriel @ 2026-04-06 3:20 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: linux-sound, linux-kernel, Cássio Gabriel
gusmax still leaves its ISA PM callbacks disabled even though the shared
GF1 suspend and resume path now exists.
This board needs one extra piece of PM glue around the shared GF1 helpers.
The attached WSS codec has its own register image that must be saved and
restored across suspend, and the MAX control register must be rewritten on
resume before the codec and GF1 sides are brought back.
Use the existing wss->suspend() and wss->resume() hooks for the codec, then
wire the driver up to the shared GUS suspend and resume helpers for the GF1
side.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
sound/isa/gus/gusmax.c | 28 +++++++++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c
index b572411c4422..f1fd7ff2121d 100644
--- a/sound/isa/gus/gusmax.c
+++ b/sound/isa/gus/gusmax.c
@@ -328,12 +328,38 @@ static int snd_gusmax_probe(struct device *pdev, unsigned int dev)
return 0;
}
+#ifdef CONFIG_PM
+static int snd_gusmax_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusmax *maxcard = card->private_data;
+
+ maxcard->wss->suspend(maxcard->wss);
+ return snd_gus_suspend(maxcard->gus);
+}
+
+static int snd_gusmax_resume(struct device *dev, unsigned int n)
+{
+ struct snd_card *card = dev_get_drvdata(dev);
+ struct snd_gusmax *maxcard = card->private_data;
+
+ /* Restore the board routing latch before resuming the codec and GF1. */
+ outb(maxcard->gus->max_cntrl_val, GUSP(maxcard->gus, MAXCNTRLPORT));
+ maxcard->wss->resume(maxcard->wss);
+ return snd_gus_resume(maxcard->gus);
+}
+#endif
+
#define DEV_NAME "gusmax"
static struct isa_driver snd_gusmax_driver = {
.match = snd_gusmax_match,
.probe = snd_gusmax_probe,
- /* FIXME: suspend/resume */
+#ifdef CONFIG_PM
+ .suspend = snd_gusmax_suspend,
+ .resume = snd_gusmax_resume,
+#endif
.driver = {
.name = DEV_NAME
},
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX
2026-04-06 3:20 [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Cássio Gabriel
` (3 preceding siblings ...)
2026-04-06 3:20 ` [PATCH 4/4] ALSA: gusmax: " Cássio Gabriel
@ 2026-04-06 9:11 ` Takashi Iwai
2026-04-06 13:52 ` Cássio Gabriel Monteiro Pires
4 siblings, 1 reply; 7+ messages in thread
From: Takashi Iwai @ 2026-04-06 9:11 UTC (permalink / raw)
To: Cássio Gabriel
Cc: Takashi Iwai, Jaroslav Kysela, linux-sound, linux-kernel
On Mon, 06 Apr 2026 05:20:02 +0200,
Cássio Gabriel wrote:
>
> gusclassic, gusextreme and gusmax still leave their ISA PM callbacks
> disabled, so a suspend/resume cycle does not restore usable GF1 operation
> on these cards.
>
> A simple wrapper around the existing GF1 start/stop paths is not enough.
> Those helpers are probe and shutdown paths: they reset software handlers
> and tear down runtime state such as the DRAM allocator, timer state, DMA
> queues, PCM state and UART setup. Resume instead needs a narrower recovery
> path that rebuilds the GF1 hardware state without rerunning probe-only
> detection or discarding the bookkeeping kept by the card instance.
>
> gusextreme and gusmax also need board-specific resume glue around that
> shared GF1 recovery path. GUS Extreme has to restore the ES1688-side GF1
> routing before the GF1 is touched again. GUS MAX has to rewrite the MAX
> control register and restore the attached WSS codec state before the full
> card is brought back.
>
> This series does that in four steps:
>
> - add shared GF1 suspend/resume helpers that quiesce PCM, DMA and UART
> activity and restart the GF1 hardware without rerunning memory detection
> or allocator initialization
> - wire gusclassic up to the shared helpers
> - wire gusextreme up as well, restoring the ES1688-side GF1 routing
> before the shared resume path runs
> - wire gusmax up too, restoring the MAX control register and the WSS codec
> state around the shared GF1 resume path
>
> The scope is limited to restoring post-resume usability for these ISA
> cards. It does not attempt transparent continuation of active GF1 PCM or
> synth state across suspend, and userspace may still need to reprepare
> streams or reload onboard sample data after resume. Open rawmidi
> substreams are restored only to a usable post-resume state.
>
> Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
Applied all four patches to for-next branch.
thanks,
Takashi
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX
2026-04-06 9:11 ` [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Takashi Iwai
@ 2026-04-06 13:52 ` Cássio Gabriel Monteiro Pires
0 siblings, 0 replies; 7+ messages in thread
From: Cássio Gabriel Monteiro Pires @ 2026-04-06 13:52 UTC (permalink / raw)
To: Takashi Iwai; +Cc: Jaroslav Kysela, linux-sound, linux-kernel
[-- Attachment #1.1: Type: text/plain, Size: 2119 bytes --]
On 4/6/26 06:11, Takashi Iwai wrote:
> On Mon, 06 Apr 2026 05:20:02 +0200,
> Cássio Gabriel wrote:
>>
>> gusclassic, gusextreme and gusmax still leave their ISA PM callbacks
>> disabled, so a suspend/resume cycle does not restore usable GF1 operation
>> on these cards.
>>
>> A simple wrapper around the existing GF1 start/stop paths is not enough.
>> Those helpers are probe and shutdown paths: they reset software handlers
>> and tear down runtime state such as the DRAM allocator, timer state, DMA
>> queues, PCM state and UART setup. Resume instead needs a narrower recovery
>> path that rebuilds the GF1 hardware state without rerunning probe-only
>> detection or discarding the bookkeeping kept by the card instance.
>>
>> gusextreme and gusmax also need board-specific resume glue around that
>> shared GF1 recovery path. GUS Extreme has to restore the ES1688-side GF1
>> routing before the GF1 is touched again. GUS MAX has to rewrite the MAX
>> control register and restore the attached WSS codec state before the full
>> card is brought back.
>>
>> This series does that in four steps:
>>
>> - add shared GF1 suspend/resume helpers that quiesce PCM, DMA and UART
>> activity and restart the GF1 hardware without rerunning memory detection
>> or allocator initialization
>> - wire gusclassic up to the shared helpers
>> - wire gusextreme up as well, restoring the ES1688-side GF1 routing
>> before the shared resume path runs
>> - wire gusmax up too, restoring the MAX control register and the WSS codec
>> state around the shared GF1 resume path
>>
>> The scope is limited to restoring post-resume usability for these ISA
>> cards. It does not attempt transparent continuation of active GF1 PCM or
>> synth state across suspend, and userspace may still need to reprepare
>> streams or reload onboard sample data after resume. Open rawmidi
>> substreams are restored only to a usable post-resume state.
>>
>> Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
>
> Applied all four patches to for-next branch.
Awesome, thank you very much.
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 236 bytes --]
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-04-06 13:52 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-06 3:20 [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Cássio Gabriel
2026-04-06 3:20 ` [PATCH 1/4] ALSA: gus: add shared GF1 suspend and resume helpers Cássio Gabriel
2026-04-06 3:20 ` [PATCH 2/4] ALSA: gusclassic: add ISA suspend and resume callbacks Cássio Gabriel
2026-04-06 3:20 ` [PATCH 3/4] ALSA: gusextreme: " Cássio Gabriel
2026-04-06 3:20 ` [PATCH 4/4] ALSA: gusmax: " Cássio Gabriel
2026-04-06 9:11 ` [PATCH 0/4] ALSA: isa/gus: add ISA PM support for GUS Classic, GUS Extreme and GUS MAX Takashi Iwai
2026-04-06 13:52 ` Cássio Gabriel Monteiro Pires
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox