]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ALSA: gus: add shared GF1 suspend and resume helpers
authorCássio Gabriel <cassiogabrielcontato@gmail.com>
Mon, 6 Apr 2026 03:20:03 +0000 (00:20 -0300)
committerTakashi Iwai <tiwai@suse.de>
Mon, 6 Apr 2026 09:08:35 +0000 (11:08 +0200)
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>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-1-b6829a7457cd@gmail.com
include/sound/gus.h
sound/isa/gus/gus_dma.c
sound/isa/gus/gus_main.c
sound/isa/gus/gus_pcm.c
sound/isa/gus/gus_reset.c
sound/isa/gus/gus_timer.c
sound/isa/gus/gus_uart.c

index 321ae93625ebd90e6aa3f8f0913f4ed7f284af16..3feb42627de1eb54e7b3ac8e087d586c6be3dbf9 100644 (file)
@@ -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 */
index ffc69e26227eb793844d951e857c2ce2661eec2b..30bd76eee96e68b296de981e638b4c84e88058fc 100644 (file)
@@ -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,
index b2b189c8356996514d8c9a5d98b7992b9498beae..6adf8b698e2bcdc54c132e156324dce00807f40c 100644 (file)
@@ -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);
index caf371897b780735c81b68cd209b745943916c63..a0757e1ede465c352dfab854a3068bdac9c3563b 100644 (file)
@@ -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;
 }
-
index a7a3e764bb77cf71165f75f03cf04f528a410794..998fa245708c2e75bdb2f03809fdff2bc5529282 100644 (file)
@@ -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;
+}
index e3a8847e02cf1ad9e6bd535f8a75989b1c19fc32..14dcde138bc74d79fc32f73f15b1fdcd08d1cbfd 100644 (file)
@@ -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);
+       }
+}
index 770d8f3e4cfff67ef6e53ee3c096227254533760..25057a5a81b0a1d83a353acc40d07e40375145b9 100644 (file)
@@ -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);
+}