mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 21:39:35 -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
482 lines
12 KiB
C
482 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Routines for Gravis UltraSound soundcards
|
|
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/module.h>
|
|
#include <sound/core.h>
|
|
#include <sound/gus.h>
|
|
#include <sound/control.h>
|
|
|
|
#include <asm/dma.h>
|
|
|
|
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
|
|
MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches);
|
|
|
|
static int snd_gus_joystick_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = 31;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_gus_joystick_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
|
|
|
|
ucontrol->value.integer.value[0] = gus->joystick_dac & 31;
|
|
return 0;
|
|
}
|
|
|
|
static int snd_gus_joystick_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol);
|
|
int change;
|
|
unsigned char nval;
|
|
|
|
nval = ucontrol->value.integer.value[0] & 31;
|
|
guard(spinlock_irqsave)(&gus->reg_lock);
|
|
change = gus->joystick_dac != nval;
|
|
gus->joystick_dac = nval;
|
|
snd_gf1_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
|
|
return change;
|
|
}
|
|
|
|
static const struct snd_kcontrol_new snd_gus_joystick_control = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_CARD,
|
|
.name = "Joystick Speed",
|
|
.info = snd_gus_joystick_info,
|
|
.get = snd_gus_joystick_get,
|
|
.put = snd_gus_joystick_put
|
|
};
|
|
|
|
static void snd_gus_init_control(struct snd_gus_card *gus)
|
|
{
|
|
if (!gus->ace_flag)
|
|
snd_ctl_add(gus->card, snd_ctl_new1(&snd_gus_joystick_control, gus));
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
|
|
static int snd_gus_free(struct snd_gus_card *gus)
|
|
{
|
|
if (gus->gf1.res_port2 == NULL)
|
|
goto __hw_end;
|
|
snd_gf1_stop(gus);
|
|
snd_gus_init_dma_irq(gus, 0);
|
|
__hw_end:
|
|
release_and_free_resource(gus->gf1.res_port1);
|
|
release_and_free_resource(gus->gf1.res_port2);
|
|
if (gus->gf1.irq >= 0)
|
|
free_irq(gus->gf1.irq, (void *) gus);
|
|
if (gus->gf1.dma1 >= 0) {
|
|
disable_dma(gus->gf1.dma1);
|
|
free_dma(gus->gf1.dma1);
|
|
}
|
|
if (!gus->equal_dma && gus->gf1.dma2 >= 0) {
|
|
disable_dma(gus->gf1.dma2);
|
|
free_dma(gus->gf1.dma2);
|
|
}
|
|
kfree(gus);
|
|
return 0;
|
|
}
|
|
|
|
static int snd_gus_dev_free(struct snd_device *device)
|
|
{
|
|
struct snd_gus_card *gus = device->device_data;
|
|
return snd_gus_free(gus);
|
|
}
|
|
|
|
int snd_gus_create(struct snd_card *card,
|
|
unsigned long port,
|
|
int irq, int dma1, int dma2,
|
|
int timer_dev,
|
|
int voices,
|
|
int pcm_channels,
|
|
int effect,
|
|
struct snd_gus_card **rgus)
|
|
{
|
|
struct snd_gus_card *gus;
|
|
int err;
|
|
static const struct snd_device_ops ops = {
|
|
.dev_free = snd_gus_dev_free,
|
|
};
|
|
|
|
*rgus = NULL;
|
|
gus = kzalloc_obj(*gus);
|
|
if (gus == NULL)
|
|
return -ENOMEM;
|
|
spin_lock_init(&gus->reg_lock);
|
|
spin_lock_init(&gus->voice_alloc);
|
|
spin_lock_init(&gus->active_voice_lock);
|
|
spin_lock_init(&gus->event_lock);
|
|
spin_lock_init(&gus->dma_lock);
|
|
spin_lock_init(&gus->pcm_volume_level_lock);
|
|
spin_lock_init(&gus->uart_cmd_lock);
|
|
mutex_init(&gus->dma_mutex);
|
|
gus->gf1.irq = -1;
|
|
gus->gf1.dma1 = -1;
|
|
gus->gf1.dma2 = -1;
|
|
gus->card = card;
|
|
gus->gf1.port = port;
|
|
/* fill register variables for speedup */
|
|
gus->gf1.reg_page = GUSP(gus, GF1PAGE);
|
|
gus->gf1.reg_regsel = GUSP(gus, GF1REGSEL);
|
|
gus->gf1.reg_data8 = GUSP(gus, GF1DATAHIGH);
|
|
gus->gf1.reg_data16 = GUSP(gus, GF1DATALOW);
|
|
gus->gf1.reg_irqstat = GUSP(gus, IRQSTAT);
|
|
gus->gf1.reg_dram = GUSP(gus, DRAM);
|
|
gus->gf1.reg_timerctrl = GUSP(gus, TIMERCNTRL);
|
|
gus->gf1.reg_timerdata = GUSP(gus, TIMERDATA);
|
|
/* allocate resources */
|
|
gus->gf1.res_port1 = request_region(port, 16, "GUS GF1 (Adlib/SB)");
|
|
if (!gus->gf1.res_port1) {
|
|
dev_err(card->dev, "gus: can't grab SB port 0x%lx\n", port);
|
|
snd_gus_free(gus);
|
|
return -EBUSY;
|
|
}
|
|
gus->gf1.res_port2 = request_region(port + 0x100, 12, "GUS GF1 (Synth)");
|
|
if (!gus->gf1.res_port2) {
|
|
dev_err(card->dev, "gus: can't grab synth port 0x%lx\n", port + 0x100);
|
|
snd_gus_free(gus);
|
|
return -EBUSY;
|
|
}
|
|
if (irq >= 0 && request_irq(irq, snd_gus_interrupt, 0, "GUS GF1", (void *) gus)) {
|
|
dev_err(card->dev, "gus: can't grab irq %d\n", irq);
|
|
snd_gus_free(gus);
|
|
return -EBUSY;
|
|
}
|
|
gus->gf1.irq = irq;
|
|
card->sync_irq = irq;
|
|
if (request_dma(dma1, "GUS - 1")) {
|
|
dev_err(card->dev, "gus: can't grab DMA1 %d\n", dma1);
|
|
snd_gus_free(gus);
|
|
return -EBUSY;
|
|
}
|
|
gus->gf1.dma1 = dma1;
|
|
if (dma2 >= 0 && dma1 != dma2) {
|
|
if (request_dma(dma2, "GUS - 2")) {
|
|
dev_err(card->dev, "gus: can't grab DMA2 %d\n", dma2);
|
|
snd_gus_free(gus);
|
|
return -EBUSY;
|
|
}
|
|
gus->gf1.dma2 = dma2;
|
|
} else {
|
|
gus->gf1.dma2 = gus->gf1.dma1;
|
|
gus->equal_dma = 1;
|
|
}
|
|
gus->timer_dev = timer_dev;
|
|
if (voices < 14)
|
|
voices = 14;
|
|
if (voices > 32)
|
|
voices = 32;
|
|
if (pcm_channels < 0)
|
|
pcm_channels = 0;
|
|
if (pcm_channels > 8)
|
|
pcm_channels = 8;
|
|
pcm_channels++;
|
|
pcm_channels &= ~1;
|
|
gus->gf1.effect = effect ? 1 : 0;
|
|
gus->gf1.active_voices = voices;
|
|
gus->gf1.pcm_channels = pcm_channels;
|
|
gus->gf1.volume_ramp = 25;
|
|
gus->gf1.smooth_pan = 1;
|
|
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, gus, &ops);
|
|
if (err < 0) {
|
|
snd_gus_free(gus);
|
|
return err;
|
|
}
|
|
*rgus = gus;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Memory detection routine for plain GF1 soundcards
|
|
*/
|
|
|
|
static int snd_gus_detect_memory(struct snd_gus_card * gus)
|
|
{
|
|
int l, idx, local;
|
|
unsigned char d;
|
|
|
|
snd_gf1_poke(gus, 0L, 0xaa);
|
|
snd_gf1_poke(gus, 1L, 0x55);
|
|
if (snd_gf1_peek(gus, 0L) != 0xaa || snd_gf1_peek(gus, 1L) != 0x55) {
|
|
dev_err(gus->card->dev,
|
|
"plain GF1 card at 0x%lx without onboard DRAM?\n",
|
|
gus->gf1.port);
|
|
return -ENOMEM;
|
|
}
|
|
for (idx = 1, d = 0xab; idx < 4; idx++, d++) {
|
|
local = idx << 18;
|
|
snd_gf1_poke(gus, local, d);
|
|
snd_gf1_poke(gus, local + 1, d + 1);
|
|
if (snd_gf1_peek(gus, local) != d ||
|
|
snd_gf1_peek(gus, local + 1) != d + 1 ||
|
|
snd_gf1_peek(gus, 0L) != 0xaa)
|
|
break;
|
|
}
|
|
#if 1
|
|
gus->gf1.memory = idx << 18;
|
|
#else
|
|
gus->gf1.memory = 256 * 1024;
|
|
#endif
|
|
for (l = 0, local = gus->gf1.memory; l < 4; l++, local -= 256 * 1024) {
|
|
gus->gf1.mem_alloc.banks_8[l].address =
|
|
gus->gf1.mem_alloc.banks_8[l].size = 0;
|
|
gus->gf1.mem_alloc.banks_16[l].address = l << 18;
|
|
gus->gf1.mem_alloc.banks_16[l].size = local > 0 ? 256 * 1024 : 0;
|
|
}
|
|
gus->gf1.mem_alloc.banks_8[0].size = gus->gf1.memory;
|
|
return 0; /* some memory were detected */
|
|
}
|
|
|
|
static int snd_gus_init_dma_irq(struct snd_gus_card * gus, int latches)
|
|
{
|
|
struct snd_card *card;
|
|
int irq, dma1, dma2;
|
|
static const unsigned char irqs[16] =
|
|
{0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7};
|
|
static const unsigned char dmas[8] =
|
|
{6, 1, 0, 2, 0, 3, 4, 5};
|
|
|
|
if (snd_BUG_ON(!gus))
|
|
return -EINVAL;
|
|
card = gus->card;
|
|
if (snd_BUG_ON(!card))
|
|
return -EINVAL;
|
|
|
|
gus->mix_cntrl_reg &= 0xf8;
|
|
gus->mix_cntrl_reg |= 0x01; /* disable MIC, LINE IN, enable LINE OUT */
|
|
if (gus->codec_flag || gus->ess_flag) {
|
|
gus->mix_cntrl_reg &= ~1; /* enable LINE IN */
|
|
gus->mix_cntrl_reg |= 4; /* enable MIC */
|
|
}
|
|
dma1 = gus->gf1.dma1;
|
|
dma1 = abs(dma1);
|
|
dma1 = dmas[dma1 & 7];
|
|
dma2 = gus->gf1.dma2;
|
|
dma2 = abs(dma2);
|
|
dma2 = dmas[dma2 & 7];
|
|
dma1 |= gus->equal_dma ? 0x40 : (dma2 << 3);
|
|
|
|
if ((dma1 & 7) == 0 || (dma2 & 7) == 0) {
|
|
dev_err(gus->card->dev, "Error! DMA isn't defined.\n");
|
|
return -EINVAL;
|
|
}
|
|
irq = gus->gf1.irq;
|
|
irq = abs(irq);
|
|
irq = irqs[irq & 0x0f];
|
|
if (irq == 0) {
|
|
dev_err(gus->card->dev, "Error! IRQ isn't defined.\n");
|
|
return -EINVAL;
|
|
}
|
|
irq |= 0x40;
|
|
#if 0
|
|
card->mixer.mix_ctrl_reg |= 0x10;
|
|
#endif
|
|
|
|
scoped_guard(spinlock_irqsave, &gus->reg_lock) {
|
|
outb(5, GUSP(gus, REGCNTRLS));
|
|
outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
|
|
outb(0x00, GUSP(gus, IRQDMACNTRLREG));
|
|
outb(0, GUSP(gus, REGCNTRLS));
|
|
}
|
|
|
|
udelay(100);
|
|
|
|
scoped_guard(spinlock_irqsave, &gus->reg_lock) {
|
|
outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
|
|
outb(dma1, GUSP(gus, IRQDMACNTRLREG));
|
|
if (latches) {
|
|
outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
|
|
outb(irq, GUSP(gus, IRQDMACNTRLREG));
|
|
}
|
|
}
|
|
|
|
udelay(100);
|
|
|
|
scoped_guard(spinlock_irqsave, &gus->reg_lock) {
|
|
outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
|
|
outb(dma1, GUSP(gus, IRQDMACNTRLREG));
|
|
if (latches) {
|
|
outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
|
|
outb(irq, GUSP(gus, IRQDMACNTRLREG));
|
|
}
|
|
}
|
|
|
|
snd_gf1_delay(gus);
|
|
|
|
if (latches)
|
|
gus->mix_cntrl_reg |= 0x08; /* enable latches */
|
|
else
|
|
gus->mix_cntrl_reg &= ~0x08; /* disable latches */
|
|
scoped_guard(spinlock_irqsave, &gus->reg_lock) {
|
|
outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
|
|
outb(0, GUSP(gus, GF1PAGE));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int snd_gus_check_version(struct snd_gus_card * gus)
|
|
{
|
|
unsigned char val, rev;
|
|
struct snd_card *card;
|
|
|
|
card = gus->card;
|
|
scoped_guard(spinlock_irqsave, &gus->reg_lock) {
|
|
outb(0x20, GUSP(gus, REGCNTRLS));
|
|
val = inb(GUSP(gus, REGCNTRLS));
|
|
rev = inb(GUSP(gus, BOARDVERSION));
|
|
}
|
|
dev_dbg(card->dev, "GF1 [0x%lx] init - val = 0x%x, rev = 0x%x\n", gus->gf1.port, val, rev);
|
|
strscpy(card->driver, "GUS");
|
|
strscpy(card->longname, "Gravis UltraSound Classic (2.4)");
|
|
if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) {
|
|
if (rev >= 5 && rev <= 9) {
|
|
gus->ics_flag = 1;
|
|
if (rev == 5)
|
|
gus->ics_flipped = 1;
|
|
card->longname[27] = '3';
|
|
card->longname[29] = rev == 5 ? '5' : '7';
|
|
}
|
|
if (rev >= 10 && rev != 255) {
|
|
if (rev >= 10 && rev <= 11) {
|
|
strscpy(card->driver, "GUS MAX");
|
|
strscpy(card->longname, "Gravis UltraSound MAX");
|
|
gus->max_flag = 1;
|
|
} else if (rev == 0x30) {
|
|
strscpy(card->driver, "GUS ACE");
|
|
strscpy(card->longname, "Gravis UltraSound Ace");
|
|
gus->ace_flag = 1;
|
|
} else if (rev == 0x50) {
|
|
strscpy(card->driver, "GUS Extreme");
|
|
strscpy(card->longname, "Gravis UltraSound Extreme");
|
|
gus->ess_flag = 1;
|
|
} else {
|
|
dev_err(card->dev,
|
|
"unknown GF1 revision number at 0x%lx - 0x%x (0x%x)\n",
|
|
gus->gf1.port, rev, val);
|
|
dev_err(card->dev,
|
|
" please - report to <perex@perex.cz>\n");
|
|
}
|
|
}
|
|
}
|
|
strscpy(card->shortname, card->longname, sizeof(card->shortname));
|
|
gus->uart_enable = 1; /* standard GUSes doesn't have midi uart trouble */
|
|
snd_gus_init_control(gus);
|
|
return 0;
|
|
}
|
|
|
|
int snd_gus_initialize(struct snd_gus_card *gus)
|
|
{
|
|
int err;
|
|
|
|
if (!gus->interwave) {
|
|
err = snd_gus_check_version(gus);
|
|
if (err < 0) {
|
|
dev_err(gus->card->dev, "version check failed\n");
|
|
return err;
|
|
}
|
|
err = snd_gus_detect_memory(gus);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
err = snd_gus_init_dma_irq(gus, 1);
|
|
if (err < 0)
|
|
return err;
|
|
snd_gf1_start(gus);
|
|
gus->initialized = 1;
|
|
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);
|
|
EXPORT_SYMBOL(snd_gf1_look8);
|
|
EXPORT_SYMBOL(snd_gf1_write16);
|
|
EXPORT_SYMBOL(snd_gf1_look16);
|
|
EXPORT_SYMBOL(snd_gf1_i_write8);
|
|
EXPORT_SYMBOL(snd_gf1_i_look8);
|
|
EXPORT_SYMBOL(snd_gf1_i_look16);
|
|
EXPORT_SYMBOL(snd_gf1_dram_addr);
|
|
EXPORT_SYMBOL(snd_gf1_write_addr);
|
|
EXPORT_SYMBOL(snd_gf1_poke);
|
|
EXPORT_SYMBOL(snd_gf1_peek);
|
|
/* gus_reset.c */
|
|
EXPORT_SYMBOL(snd_gf1_alloc_voice);
|
|
EXPORT_SYMBOL(snd_gf1_free_voice);
|
|
EXPORT_SYMBOL(snd_gf1_ctrl_stop);
|
|
EXPORT_SYMBOL(snd_gf1_stop_voice);
|
|
/* gus_mixer.c */
|
|
EXPORT_SYMBOL(snd_gf1_new_mixer);
|
|
/* gus_pcm.c */
|
|
EXPORT_SYMBOL(snd_gf1_pcm_new);
|
|
/* gus.c */
|
|
EXPORT_SYMBOL(snd_gus_create);
|
|
EXPORT_SYMBOL(snd_gus_initialize);
|
|
/* gus_irq.c */
|
|
EXPORT_SYMBOL(snd_gus_interrupt);
|
|
/* gus_uart.c */
|
|
EXPORT_SYMBOL(snd_gf1_rawmidi_new);
|
|
/* gus_dram.c */
|
|
EXPORT_SYMBOL(snd_gus_dram_write);
|
|
EXPORT_SYMBOL(snd_gus_dram_read);
|
|
/* gus_volume.c */
|
|
EXPORT_SYMBOL(snd_gf1_lvol_to_gvol_raw);
|
|
EXPORT_SYMBOL(snd_gf1_translate_freq);
|
|
/* gus_mem.c */
|
|
EXPORT_SYMBOL(snd_gf1_mem_alloc);
|
|
EXPORT_SYMBOL(snd_gf1_mem_xfree);
|
|
EXPORT_SYMBOL(snd_gf1_mem_free);
|