mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-29 11:37:59 -04:00
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
195 lines
4.6 KiB
C
195 lines
4.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Routines for Gravis UltraSound soundcards - Timers
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
|
*
|
|
* GUS have similar timers as AdLib (OPL2/OPL3 chips).
|
|
*/
|
|
|
|
#include <linux/time.h>
|
|
#include <sound/core.h>
|
|
#include <sound/gus.h>
|
|
|
|
/*
|
|
* Timer 1 - 80us
|
|
*/
|
|
|
|
static int snd_gf1_timer1_start(struct snd_timer * timer)
|
|
{
|
|
unsigned char tmp;
|
|
unsigned int ticks;
|
|
struct snd_gus_card *gus;
|
|
|
|
gus = snd_timer_chip(timer);
|
|
guard(spinlock_irqsave)(&gus->reg_lock);
|
|
ticks = timer->sticks;
|
|
tmp = (gus->gf1.timer_enabled |= 4);
|
|
snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1, 256 - ticks); /* timer 1 count */
|
|
snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 1 IRQ */
|
|
snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */
|
|
return 0;
|
|
}
|
|
|
|
static int snd_gf1_timer1_stop(struct snd_timer * timer)
|
|
{
|
|
unsigned char tmp;
|
|
struct snd_gus_card *gus;
|
|
|
|
gus = snd_timer_chip(timer);
|
|
guard(spinlock_irqsave)(&gus->reg_lock);
|
|
tmp = (gus->gf1.timer_enabled &= ~4);
|
|
snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Timer 2 - 320us
|
|
*/
|
|
|
|
static int snd_gf1_timer2_start(struct snd_timer * timer)
|
|
{
|
|
unsigned char tmp;
|
|
unsigned int ticks;
|
|
struct snd_gus_card *gus;
|
|
|
|
gus = snd_timer_chip(timer);
|
|
guard(spinlock_irqsave)(&gus->reg_lock);
|
|
ticks = timer->sticks;
|
|
tmp = (gus->gf1.timer_enabled |= 8);
|
|
snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2, 256 - ticks); /* timer 2 count */
|
|
snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 2 IRQ */
|
|
snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */
|
|
return 0;
|
|
}
|
|
|
|
static int snd_gf1_timer2_stop(struct snd_timer * timer)
|
|
{
|
|
unsigned char tmp;
|
|
struct snd_gus_card *gus;
|
|
|
|
gus = snd_timer_chip(timer);
|
|
guard(spinlock_irqsave)(&gus->reg_lock);
|
|
tmp = (gus->gf1.timer_enabled &= ~8);
|
|
snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
static void snd_gf1_interrupt_timer1(struct snd_gus_card * gus)
|
|
{
|
|
struct snd_timer *timer = gus->gf1.timer1;
|
|
|
|
if (timer == NULL)
|
|
return;
|
|
snd_timer_interrupt(timer, timer->sticks);
|
|
}
|
|
|
|
static void snd_gf1_interrupt_timer2(struct snd_gus_card * gus)
|
|
{
|
|
struct snd_timer *timer = gus->gf1.timer2;
|
|
|
|
if (timer == NULL)
|
|
return;
|
|
snd_timer_interrupt(timer, timer->sticks);
|
|
}
|
|
|
|
/*
|
|
|
|
*/
|
|
|
|
static const struct snd_timer_hardware snd_gf1_timer1 =
|
|
{
|
|
.flags = SNDRV_TIMER_HW_STOP,
|
|
.resolution = 80000,
|
|
.ticks = 256,
|
|
.start = snd_gf1_timer1_start,
|
|
.stop = snd_gf1_timer1_stop,
|
|
};
|
|
|
|
static const struct snd_timer_hardware snd_gf1_timer2 =
|
|
{
|
|
.flags = SNDRV_TIMER_HW_STOP,
|
|
.resolution = 320000,
|
|
.ticks = 256,
|
|
.start = snd_gf1_timer2_start,
|
|
.stop = snd_gf1_timer2_stop,
|
|
};
|
|
|
|
static void snd_gf1_timer1_free(struct snd_timer *timer)
|
|
{
|
|
struct snd_gus_card *gus = timer->private_data;
|
|
gus->gf1.timer1 = NULL;
|
|
}
|
|
|
|
static void snd_gf1_timer2_free(struct snd_timer *timer)
|
|
{
|
|
struct snd_gus_card *gus = timer->private_data;
|
|
gus->gf1.timer2 = NULL;
|
|
}
|
|
|
|
void snd_gf1_timers_init(struct snd_gus_card * gus)
|
|
{
|
|
struct snd_timer *timer;
|
|
struct snd_timer_id tid;
|
|
|
|
if (gus->gf1.timer1 != NULL || gus->gf1.timer2 != NULL)
|
|
return;
|
|
|
|
gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1;
|
|
gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2;
|
|
|
|
tid.dev_class = SNDRV_TIMER_CLASS_CARD;
|
|
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
|
|
tid.card = gus->card->number;
|
|
tid.device = gus->timer_dev;
|
|
tid.subdevice = 0;
|
|
|
|
if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
|
|
strscpy(timer->name, "GF1 timer #1");
|
|
timer->private_data = gus;
|
|
timer->private_free = snd_gf1_timer1_free;
|
|
timer->hw = snd_gf1_timer1;
|
|
}
|
|
gus->gf1.timer1 = timer;
|
|
|
|
tid.device++;
|
|
|
|
if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) {
|
|
strscpy(timer->name, "GF1 timer #2");
|
|
timer->private_data = gus;
|
|
timer->private_free = snd_gf1_timer2_free;
|
|
timer->hw = snd_gf1_timer2;
|
|
}
|
|
gus->gf1.timer2 = timer;
|
|
}
|
|
|
|
void snd_gf1_timers_done(struct snd_gus_card * gus)
|
|
{
|
|
snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_TIMER1 | SNDRV_GF1_HANDLER_TIMER2);
|
|
if (gus->gf1.timer1) {
|
|
snd_device_free(gus->card, gus->gf1.timer1);
|
|
gus->gf1.timer1 = NULL;
|
|
}
|
|
if (gus->gf1.timer2) {
|
|
snd_device_free(gus->card, gus->gf1.timer2);
|
|
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);
|
|
}
|
|
}
|