* [PATCH 1/2] ALSA: sscape: Cache per-card resources for board reinitialization
2026-04-11 4:54 [PATCH 0/2] ALSA: sscape: add suspend/resume support Cássio Gabriel
@ 2026-04-11 4:54 ` Cássio Gabriel
2026-04-11 8:07 ` Takashi Iwai
2026-04-11 4:54 ` [PATCH 2/2] ALSA: sscape: Add suspend and resume support Cássio Gabriel
1 sibling, 1 reply; 7+ messages in thread
From: Cássio Gabriel @ 2026-04-11 4:54 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: linux-sound, linux-kernel, Cássio Gabriel
The SoundScape driver programs the gate-array directly from the global
resource arrays during probe. That is sufficient for initial bring-up,
but a PM resume path also needs the resolved per-card IRQ, DMA, MPU IRQ
and joystick settings after probe has finished.
Store the resolved resources in struct soundscape and move the board
setup into a reusable helper. Also factor the MIDI state programming so
the same sequence can be reused by a later PM resume path.
This is preparatory work for suspend/resume support and is not intended
to change runtime behaviour.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
sound/isa/sscape.c | 210 +++++++++++++++++++++++++++++++----------------------
1 file changed, 125 insertions(+), 85 deletions(-)
diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c
index a31ca75774a6..3e9465bc64ed 100644
--- a/sound/isa/sscape.c
+++ b/sound/isa/sscape.c
@@ -131,6 +131,11 @@ enum card_type {
struct soundscape {
spinlock_t lock;
unsigned io_base;
+ unsigned long wss_base;
+ int irq;
+ int mpu_irq;
+ int dma1;
+ int dma2;
int ic_type;
enum card_type type;
struct resource *io_res;
@@ -138,6 +143,7 @@ struct soundscape {
struct snd_wss *chip;
unsigned char midi_vol;
+ bool joystick;
struct device *dev;
};
@@ -149,6 +155,21 @@ static inline struct soundscape *get_card_soundscape(struct snd_card *c)
return (struct soundscape *) (c->private_data);
}
+/*
+ * Store the resolved board settings in the per-card state so that
+ * the same configuration can be replayed later if necessary.
+ */
+static void sscape_store_settings(struct soundscape *sscape, int dev)
+{
+ sscape->io_base = port[dev];
+ sscape->wss_base = wss_port[dev];
+ sscape->irq = irq[dev];
+ sscape->mpu_irq = mpu_irq[dev];
+ sscape->dma1 = dma[dev];
+ sscape->dma2 = dma2[dev];
+ sscape->joystick = joystick[dev];
+}
+
/*
* Allocates some kernel memory that we can use for DMA.
* I think this means that the memory has to map to
@@ -560,6 +581,28 @@ static int sscape_upload_microcode(struct snd_card *card, int version)
return err;
}
+/*
+ * Restore the SoundScape's MIDI control state after the firmware
+ * upload has made the host interface available again.
+ */
+static int sscape_restore_midi_state(struct soundscape *sscape)
+{
+ int err = 1;
+
+ guard(spinlock_irqsave)(&sscape->lock);
+ set_host_mode_unsafe(sscape->io_base);
+ err &= host_write_ctrl_unsafe(sscape->io_base, CMD_SET_MIDI_VOL, 100);
+ err &= host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100);
+ err &= host_write_ctrl_unsafe(sscape->io_base, CMD_XXX_MIDI_VOL, 100);
+ err &= host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100);
+ err &= host_write_ctrl_unsafe(sscape->io_base, CMD_SET_EXTMIDI, 100);
+ err &= host_write_ctrl_unsafe(sscape->io_base, 0, 100);
+ err &= host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
+ set_midi_mode_unsafe(sscape->io_base);
+
+ return err ? 0 : -EIO;
+}
+
/*
* Mixer control for the SoundScape's MIDI device.
*/
@@ -660,6 +703,59 @@ static unsigned get_irq_config(int sscape_type, int irq)
return INVALID_IRQ;
}
+/*
+ * Program the SoundScape's board-specific routing and enable the
+ * codec path using the resolved IRQ, DMA and joystick settings.
+ */
+static int sscape_configure_board(struct soundscape *sscape)
+{
+ unsigned int dma_cfg;
+ unsigned int irq_cfg;
+ unsigned int mpu_irq_cfg;
+ int val;
+
+ irq_cfg = get_irq_config(sscape->type, sscape->irq);
+ if (irq_cfg == INVALID_IRQ)
+ return -ENXIO;
+
+ mpu_irq_cfg = get_irq_config(sscape->type, sscape->mpu_irq);
+ if (mpu_irq_cfg == INVALID_IRQ)
+ return -ENXIO;
+
+ scoped_guard(spinlock_irqsave, &sscape->lock) {
+ if (sscape->ic_type == IC_OPUS)
+ activate_ad1845_unsafe(sscape->io_base);
+
+ sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
+ sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
+
+ /*
+ * Enable and configure the DMA channels ...
+ */
+ sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
+ dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70);
+ sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
+ sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
+
+ mpu_irq_cfg |= mpu_irq_cfg << 2;
+ val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xf7;
+ if (sscape->joystick)
+ val |= 0x08;
+ sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0xd0);
+ sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG,
+ 0xf0 | mpu_irq_cfg);
+ sscape_write_unsafe(sscape->io_base, GA_CDCFG_REG,
+ 0x09 | DMA_8BIT |
+ (sscape->dma1 << 4) | (irq_cfg << 1));
+ /*
+ * Enable the master IRQ ...
+ */
+ sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
+ }
+
+ return 0;
+}
+
/*
* Perform certain arcane port-checks to see whether there
* is a SoundScape board lurking behind the given ports.
@@ -890,37 +986,33 @@ static int create_ad1845(struct snd_card *card, unsigned port,
/*
* Create an ALSA soundcard entry for the SoundScape, using
- * the given list of port, IRQ and DMA resources.
+ * the resolved port, IRQ and DMA resources.
*/
-static int create_sscape(int dev, struct snd_card *card)
+static int create_sscape(struct snd_card *card)
{
struct soundscape *sscape = get_card_soundscape(card);
- unsigned dma_cfg;
- unsigned irq_cfg;
- unsigned mpu_irq_cfg;
struct resource *io_res;
struct resource *wss_res;
int err;
- int val;
const char *name;
/*
* Grab IO ports that we will need to probe so that we
* can detect and control this hardware ...
*/
- io_res = devm_request_region(card->dev, port[dev], 8, "SoundScape");
+ io_res = devm_request_region(card->dev, sscape->io_base, 8, "SoundScape");
if (!io_res) {
dev_err(card->dev,
- "sscape: can't grab port 0x%lx\n", port[dev]);
+ "sscape: can't grab port 0x%x\n", sscape->io_base);
return -EBUSY;
}
wss_res = NULL;
if (sscape->type == SSCAPE_VIVO) {
- wss_res = devm_request_region(card->dev, wss_port[dev], 4,
+ wss_res = devm_request_region(card->dev, sscape->wss_base, 4,
"SoundScape");
if (!wss_res) {
dev_err(card->dev, "sscape: can't grab port 0x%lx\n",
- wss_port[dev]);
+ sscape->wss_base);
return -EBUSY;
}
}
@@ -928,18 +1020,17 @@ static int create_sscape(int dev, struct snd_card *card)
/*
* Grab one DMA channel ...
*/
- err = snd_devm_request_dma(card->dev, dma[dev], "SoundScape");
+ err = snd_devm_request_dma(card->dev, sscape->dma1, "SoundScape");
if (err < 0) {
- dev_err(card->dev, "sscape: can't grab DMA %d\n", dma[dev]);
+ dev_err(card->dev, "sscape: can't grab DMA %d\n", sscape->dma1);
return err;
}
spin_lock_init(&sscape->lock);
sscape->io_res = io_res;
sscape->wss_res = wss_res;
- sscape->io_base = port[dev];
- if (!detect_sscape(sscape, wss_port[dev])) {
+ if (!detect_sscape(sscape, sscape->wss_base)) {
dev_err(card->dev, "sscape: hardware not detected at 0x%x\n",
sscape->io_base);
return -ENODEV;
@@ -964,66 +1055,28 @@ static int create_sscape(int dev, struct snd_card *card)
}
dev_info(card->dev, "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n",
- name, sscape->io_base, irq[dev], dma[dev]);
-
- /*
- * Check that the user didn't pass us garbage data ...
- */
- irq_cfg = get_irq_config(sscape->type, irq[dev]);
- if (irq_cfg == INVALID_IRQ) {
- dev_err(card->dev, "sscape: Invalid IRQ %d\n", irq[dev]);
- return -ENXIO;
- }
-
- mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]);
- if (mpu_irq_cfg == INVALID_IRQ) {
- dev_err(card->dev, "sscape: Invalid IRQ %d\n", mpu_irq[dev]);
- return -ENXIO;
- }
+ name, sscape->io_base, sscape->irq, sscape->dma1);
/*
* Tell the on-board devices where their resources are (I think -
* I can't be sure without a datasheet ... So many magic values!)
*/
- scoped_guard(spinlock_irqsave, &sscape->lock) {
-
- sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e);
- sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00);
-
- /*
- * Enable and configure the DMA channels ...
- */
- sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50);
- dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70);
- sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg);
- sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20);
-
- mpu_irq_cfg |= mpu_irq_cfg << 2;
- val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7;
- if (joystick[dev])
- val |= 8;
- sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10);
- sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg);
- sscape_write_unsafe(sscape->io_base,
- GA_CDCFG_REG, 0x09 | DMA_8BIT
- | (dma[dev] << 4) | (irq_cfg << 1));
- /*
- * Enable the master IRQ ...
- */
- sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80);
-
+ err = sscape_configure_board(sscape);
+ if (err < 0) {
+ dev_err(card->dev, "sscape: Invalid IRQ configuration\n");
+ return err;
}
/*
* We have now enabled the codec chip, and so we should
* detect the AD1845 device ...
*/
- err = create_ad1845(card, wss_port[dev], irq[dev],
- dma[dev], dma2[dev]);
+ err = create_ad1845(card, sscape->wss_base, sscape->irq,
+ sscape->dma1, sscape->dma2);
if (err < 0) {
dev_err(card->dev,
"sscape: No AD1845 device at 0x%lx, IRQ %d\n",
- wss_port[dev], irq[dev]);
+ sscape->wss_base, sscape->irq);
return err;
}
strscpy(card->driver, "SoundScape");
@@ -1040,35 +1093,21 @@ static int create_sscape(int dev, struct snd_card *card)
err = sscape_upload_microcode(card, err);
if (err == 0) {
- err = create_mpu401(card, MIDI_DEVNUM, port[dev],
- mpu_irq[dev]);
+ err = create_mpu401(card, MIDI_DEVNUM, sscape->io_base,
+ sscape->mpu_irq);
if (err < 0) {
dev_err(card->dev,
"sscape: Failed to create MPU-401 device at 0x%lx\n",
- port[dev]);
+ (unsigned long)sscape->io_base);
return err;
}
- /*
- * Initialize mixer
- */
- guard(spinlock_irqsave)(&sscape->lock);
sscape->midi_vol = 0;
- host_write_ctrl_unsafe(sscape->io_base,
- CMD_SET_MIDI_VOL, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- sscape->midi_vol, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- CMD_XXX_MIDI_VOL, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- sscape->midi_vol, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- CMD_SET_EXTMIDI, 100);
- host_write_ctrl_unsafe(sscape->io_base,
- 0, 100);
- host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100);
-
- set_midi_mode_unsafe(sscape->io_base);
+ err = sscape_restore_midi_state(sscape);
+ if (err < 0)
+ dev_warn(card->dev,
+ "sscape: MIDI init incomplete: %d\n",
+ err);
}
}
@@ -1111,8 +1150,9 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev)
sscape->type = SSCAPE;
dma[dev] &= 0x03;
+ sscape_store_settings(sscape, dev);
- ret = create_sscape(dev, card);
+ ret = create_sscape(card);
if (ret < 0)
return ret;
@@ -1130,7 +1170,6 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev)
static struct isa_driver snd_sscape_driver = {
.match = snd_sscape_match,
.probe = snd_sscape_probe,
- /* FIXME: suspend/resume */
.driver = {
.name = DEV_NAME
},
@@ -1211,8 +1250,9 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard,
wss_port[idx] = pnp_port_start(dev, 1);
dma2[idx] = pnp_dma(dev, 1);
}
+ sscape_store_settings(sscape, idx);
- ret = create_sscape(idx, card);
+ ret = create_sscape(card);
if (ret < 0)
return ret;
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH 2/2] ALSA: sscape: Add suspend and resume support
2026-04-11 4:54 [PATCH 0/2] ALSA: sscape: add suspend/resume support Cássio Gabriel
2026-04-11 4:54 ` [PATCH 1/2] ALSA: sscape: Cache per-card resources for board reinitialization Cássio Gabriel
@ 2026-04-11 4:54 ` Cássio Gabriel
1 sibling, 0 replies; 7+ messages in thread
From: Cássio Gabriel @ 2026-04-11 4:54 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: linux-sound, linux-kernel, Cássio Gabriel
The SoundScape ISA driver has lacked suspend and resume callbacks since
commit 277e926c9b27 ("[ALSA] sscape - Use platform_device").
A plain snd_wss resume is not sufficient for SoundScape. Resume also
needs to restore the board-specific gate-array routing, and non-VIVO
boards need to reinitialize the probe-time MIDI firmware and MIDI
control state when the MPU-401 side was enabled during probe.
That firmware reload can be handled in-kernel because
commit acd47100914b ("ALSA: sscape: convert to firmware loader framework")
moved the driver to request_firmware().
Add ISA and ISA-PnP PM callbacks, reconfigure the board on resume,
reload the non-VIVO MIDI firmware, restore the MIDI state, and then
resume the WSS codec. If MIDI firmware reload fails, keep the WSS resume
path alive and leave MIDI unavailable instead of failing the whole
device resume.
Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com>
---
sound/isa/sscape.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 93 insertions(+)
diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c
index 3e9465bc64ed..ff3d9e2498d9 100644
--- a/sound/isa/sscape.c
+++ b/sound/isa/sscape.c
@@ -144,6 +144,7 @@ struct soundscape {
unsigned char midi_vol;
bool joystick;
+ bool midi_enabled;
struct device *dev;
};
@@ -1103,6 +1104,7 @@ static int create_sscape(struct snd_card *card)
}
sscape->midi_vol = 0;
+ sscape->midi_enabled = true;
err = sscape_restore_midi_state(sscape);
if (err < 0)
dev_warn(card->dev,
@@ -1114,6 +1116,77 @@ static int create_sscape(struct snd_card *card)
return 0;
}
+#ifdef CONFIG_PM
+/*
+ * Reload the MIDI firmware and restore the saved MIDI state for
+ * boards whose MPU-401 side was enabled during probe.
+ */
+static int sscape_resume_midi(struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ int err, version;
+
+ if (!sscape->midi_enabled)
+ return 0;
+
+ version = sscape_upload_bootblock(card);
+ if (version < 0)
+ return version;
+
+ err = sscape_upload_microcode(card, version);
+ if (err < 0)
+ return err;
+
+ outb(0, sscape->io_base);
+
+ return sscape_restore_midi_state(sscape);
+}
+
+/*
+ * Save the WSS codec state before the SoundScape is suspended.
+ */
+static int snd_sscape_suspend_card(struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+
+ snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
+ sscape->chip->suspend(sscape->chip);
+ return 0;
+}
+
+/*
+ * Restore the board-specific state before resuming the WSS codec.
+ */
+static int snd_sscape_resume_card(struct snd_card *card)
+{
+ struct soundscape *sscape = get_card_soundscape(card);
+ int err;
+
+ err = sscape_configure_board(sscape);
+ if (err < 0)
+ return err;
+
+ err = sscape_resume_midi(card);
+ if (err < 0)
+ dev_warn(card->dev, "sscape: MIDI restore failed: %d\n", err);
+
+ sscape->chip->resume(sscape->chip);
+ snd_power_change_state(card, SNDRV_CTL_POWER_D0);
+ return 0;
+}
+
+static int snd_sscape_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ return snd_sscape_suspend_card(dev_get_drvdata(dev));
+}
+
+static int snd_sscape_resume(struct device *dev, unsigned int n)
+{
+ return snd_sscape_resume_card(dev_get_drvdata(dev));
+}
+#endif
+
static int snd_sscape_match(struct device *pdev, unsigned int i)
{
@@ -1170,6 +1243,10 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev)
static struct isa_driver snd_sscape_driver = {
.match = snd_sscape_match,
.probe = snd_sscape_probe,
+#ifdef CONFIG_PM
+ .suspend = snd_sscape_suspend,
+ .resume = snd_sscape_resume,
+#endif
.driver = {
.name = DEV_NAME
},
@@ -1267,11 +1344,27 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard,
return 0;
}
+#ifdef CONFIG_PM
+static int sscape_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state)
+{
+ return snd_sscape_suspend_card(pnp_get_card_drvdata(pcard));
+}
+
+static int sscape_pnp_resume(struct pnp_card_link *pcard)
+{
+ return snd_sscape_resume_card(pnp_get_card_drvdata(pcard));
+}
+#endif
+
static struct pnp_card_driver sscape_pnpc_driver = {
.flags = PNP_DRIVER_RES_DO_NOT_CHANGE,
.name = "sscape",
.id_table = sscape_pnpids,
.probe = sscape_pnp_detect,
+#ifdef CONFIG_PM
+ .suspend = sscape_pnp_suspend,
+ .resume = sscape_pnp_resume,
+#endif
};
#endif /* CONFIG_PNP */
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread