diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /sound/isa/gus | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'sound/isa/gus')
| -rw-r--r-- | sound/isa/gus/Makefile | 25 | ||||
| -rw-r--r-- | sound/isa/gus/gus_dma.c | 268 | ||||
| -rw-r--r-- | sound/isa/gus/gus_dram.c | 83 | ||||
| -rw-r--r-- | sound/isa/gus/gus_io.c | 340 | ||||
| -rw-r--r-- | sound/isa/gus/gus_irq.c | 130 | ||||
| -rw-r--r-- | sound/isa/gus/gus_main.c | 481 | ||||
| -rw-r--r-- | sound/isa/gus/gus_mem.c | 316 | ||||
| -rw-r--r-- | sound/isa/gus/gus_mem_proc.c | 87 | ||||
| -rw-r--r-- | sound/isa/gus/gus_mixer.c | 171 | ||||
| -rw-r--r-- | sound/isa/gus/gus_pcm.c | 862 | ||||
| -rw-r--r-- | sound/isa/gus/gus_reset.c | 401 | ||||
| -rw-r--r-- | sound/isa/gus/gus_tables.h | 75 | ||||
| -rw-r--r-- | sound/isa/gus/gus_timer.c | 194 | ||||
| -rw-r--r-- | sound/isa/gus/gus_uart.c | 281 | ||||
| -rw-r--r-- | sound/isa/gus/gus_volume.c | 205 | ||||
| -rw-r--r-- | sound/isa/gus/gusclassic.c | 226 | ||||
| -rw-r--r-- | sound/isa/gus/gusextreme.c | 375 | ||||
| -rw-r--r-- | sound/isa/gus/gusmax.c | 368 | ||||
| -rw-r--r-- | sound/isa/gus/interwave-stb.c | 3 | ||||
| -rw-r--r-- | sound/isa/gus/interwave.c | 1038 |
20 files changed, 5929 insertions, 0 deletions
diff --git a/sound/isa/gus/Makefile b/sound/isa/gus/Makefile new file mode 100644 index 000000000..4924c1904 --- /dev/null +++ b/sound/isa/gus/Makefile @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-gus-lib-y := gus_main.o \ + gus_io.o gus_irq.o gus_timer.o \ + gus_mem.o gus_mem_proc.o gus_dram.o gus_dma.o gus_volume.o \ + gus_pcm.o gus_mixer.o \ + gus_uart.o \ + gus_reset.o + +snd-gusclassic-y := gusclassic.o +snd-gusextreme-y := gusextreme.o +snd-gusmax-y := gusmax.o +snd-interwave-y := interwave.o +snd-interwave-stb-y := interwave-stb.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_GUSCLASSIC) += snd-gusclassic.o snd-gus-lib.o +obj-$(CONFIG_SND_GUSMAX) += snd-gusmax.o snd-gus-lib.o +obj-$(CONFIG_SND_GUSEXTREME) += snd-gusextreme.o snd-gus-lib.o +obj-$(CONFIG_SND_INTERWAVE) += snd-interwave.o snd-gus-lib.o +obj-$(CONFIG_SND_INTERWAVE_STB) += snd-interwave-stb.o snd-gus-lib.o diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c new file mode 100644 index 000000000..30bd76eee --- /dev/null +++ b/sound/isa/gus/gus_dma.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Routines for GF1 DMA control + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <asm/dma.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/gus.h> + +static void snd_gf1_dma_ack(struct snd_gus_card * gus) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00); + snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL); +} + +static void snd_gf1_dma_program(struct snd_gus_card * gus, + unsigned int addr, + unsigned long buf_addr, + unsigned int count, + unsigned int cmd) +{ + unsigned int address; + unsigned char dma_cmd; + unsigned int address_high; + + dev_dbg(gus->card->dev, + "dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n", + addr, buf_addr, count); + + if (gus->gf1.dma1 > 3) { + if (gus->gf1.enh_mode) { + address = addr >> 1; + } else { + if (addr & 0x1f) { + dev_dbg(gus->card->dev, + "%s: unaligned address (0x%x)?\n", + __func__, addr); + return; + } + address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1); + } + } else { + address = addr; + } + + dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd; +#if 0 + dma_cmd |= 0x08; +#endif + if (dma_cmd & SNDRV_GF1_DMA_16BIT) { + count++; + count &= ~1; /* align */ + } + if (gus->gf1.dma1 > 3) { + dma_cmd |= SNDRV_GF1_DMA_WIDTH16; + count++; + count &= ~1; /* align */ + } + snd_gf1_dma_ack(gus); + snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE); +#if 0 + dev_dbg(gus->card->dev, + "address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n", + address << 1, count, dma_cmd); +#endif + guard(spinlock_irqsave)(&gus->reg_lock); + if (gus->gf1.enh_mode) { + address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f); + snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high); + } else + snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd); +} + +static struct snd_gf1_dma_block *snd_gf1_dma_next_block(struct snd_gus_card * gus) +{ + struct snd_gf1_dma_block *block; + + /* PCM block have bigger priority than synthesizer one */ + if (gus->gf1.dma_data_pcm) { + block = gus->gf1.dma_data_pcm; + if (gus->gf1.dma_data_pcm_last == block) { + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = NULL; + } else { + gus->gf1.dma_data_pcm = block->next; + } + } else if (gus->gf1.dma_data_synth) { + block = gus->gf1.dma_data_synth; + if (gus->gf1.dma_data_synth_last == block) { + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = NULL; + } else { + gus->gf1.dma_data_synth = block->next; + } + } else { + block = NULL; + } + if (block) { + gus->gf1.dma_ack = block->ack; + gus->gf1.dma_private_data = block->private_data; + } + return block; +} + + +static void snd_gf1_dma_interrupt(struct snd_gus_card * gus) +{ + struct snd_gf1_dma_block *block; + + snd_gf1_dma_ack(gus); + if (gus->gf1.dma_ack) + gus->gf1.dma_ack(gus, gus->gf1.dma_private_data); + scoped_guard(spinlock, &gus->dma_lock) { + if (gus->gf1.dma_data_pcm == NULL && + gus->gf1.dma_data_synth == NULL) { + gus->gf1.dma_ack = NULL; + gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER; + return; + } + block = snd_gf1_dma_next_block(gus); + } + if (!block) + return; + snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); + kfree(block); +#if 0 + dev_dbg(gus->card->dev, + "program dma (IRQ) - addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", + block->addr, block->buf_addr, block->count, block->cmd); +#endif +} + +int snd_gf1_dma_init(struct snd_gus_card * gus) +{ + guard(mutex)(&gus->dma_mutex); + gus->gf1.dma_shared++; + if (gus->gf1.dma_shared > 1) + return 0; + gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt; + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = NULL; + return 0; +} + +int snd_gf1_dma_done(struct snd_gus_card * gus) +{ + struct snd_gf1_dma_block *block; + + guard(mutex)(&gus->dma_mutex); + gus->gf1.dma_shared--; + if (!gus->gf1.dma_shared) { + snd_dma_disable(gus->gf1.dma1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE); + snd_gf1_dma_ack(gus); + while ((block = gus->gf1.dma_data_pcm)) { + gus->gf1.dma_data_pcm = block->next; + kfree(block); + } + while ((block = gus->gf1.dma_data_synth)) { + gus->gf1.dma_data_synth = block->next; + kfree(block); + } + gus->gf1.dma_data_pcm_last = + gus->gf1.dma_data_synth_last = NULL; + } + 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, + int synth) +{ + struct snd_gf1_dma_block *block; + struct snd_gf1_dma_block *free_block = NULL; + + block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL); + if (!block) + return -ENOMEM; + + *block = *__block; + block->next = NULL; + + dev_dbg(gus->card->dev, + "addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", + block->addr, (long) block->buffer, block->count, + block->cmd); + + dev_dbg(gus->card->dev, + "gus->gf1.dma_data_pcm_last = 0x%lx\n", + (long)gus->gf1.dma_data_pcm_last); + dev_dbg(gus->card->dev, + "gus->gf1.dma_data_pcm = 0x%lx\n", + (long)gus->gf1.dma_data_pcm); + + scoped_guard(spinlock_irqsave, &gus->dma_lock) { + if (synth) { + if (gus->gf1.dma_data_synth_last) { + gus->gf1.dma_data_synth_last->next = block; + gus->gf1.dma_data_synth_last = block; + } else { + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = block; + } + } else { + if (gus->gf1.dma_data_pcm_last) { + gus->gf1.dma_data_pcm_last->next = block; + gus->gf1.dma_data_pcm_last = block; + } else { + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = block; + } + } + if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) { + gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER; + free_block = snd_gf1_dma_next_block(gus); + } + } + + if (free_block) { + snd_gf1_dma_program(gus, free_block->addr, free_block->buf_addr, + free_block->count, + (unsigned short)free_block->cmd); + kfree(free_block); + } + + return 0; +} diff --git a/sound/isa/gus/gus_dram.c b/sound/isa/gus/gus_dram.c new file mode 100644 index 000000000..50fe738ee --- /dev/null +++ b/sound/isa/gus/gus_dram.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * DRAM access routines + */ + +#include <linux/time.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/info.h> + + +static int snd_gus_dram_poke(struct snd_gus_card *gus, char __user *_buffer, + unsigned int address, unsigned int size) +{ + unsigned int size1, size2; + char buffer[256], *pbuffer; + + while (size > 0) { + size1 = size > sizeof(buffer) ? sizeof(buffer) : size; + if (copy_from_user(buffer, _buffer, size1)) + return -EFAULT; + if (gus->interwave) { + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + snd_gf1_dram_addr(gus, address); + outsb(GUSP(gus, DRAM), buffer, size1); + address += size1; + } else { + pbuffer = buffer; + size2 = size1; + while (size2--) + snd_gf1_poke(gus, address++, *pbuffer++); + } + size -= size1; + _buffer += size1; + } + return 0; +} + + +int snd_gus_dram_write(struct snd_gus_card *gus, char __user *buffer, + unsigned int address, unsigned int size) +{ + return snd_gus_dram_poke(gus, buffer, address, size); +} + +static int snd_gus_dram_peek(struct snd_gus_card *gus, char __user *_buffer, + unsigned int address, unsigned int size, + int rom) +{ + unsigned int size1, size2; + char buffer[256], *pbuffer; + + while (size > 0) { + size1 = size > sizeof(buffer) ? sizeof(buffer) : size; + if (gus->interwave) { + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, rom ? 0x03 : 0x01); + snd_gf1_dram_addr(gus, address); + insb(GUSP(gus, DRAM), buffer, size1); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + address += size1; + } else { + pbuffer = buffer; + size2 = size1; + while (size2--) + *pbuffer++ = snd_gf1_peek(gus, address++); + } + if (copy_to_user(_buffer, buffer, size1)) + return -EFAULT; + size -= size1; + _buffer += size1; + } + return 0; +} + +int snd_gus_dram_read(struct snd_gus_card *gus, char __user *buffer, + unsigned int address, unsigned int size, + int rom) +{ + return snd_gus_dram_peek(gus, buffer, address, size, rom); +} diff --git a/sound/isa/gus/gus_io.c b/sound/isa/gus/gus_io.c new file mode 100644 index 000000000..3e6f35084 --- /dev/null +++ b/sound/isa/gus/gus_io.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * I/O routines for GF1/InterWave synthesizer chips + */ + +#include <linux/delay.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +void snd_gf1_delay(struct snd_gus_card * gus) +{ + int i; + + for (i = 0; i < 6; i++) { + mb(); + inb(GUSP(gus, DRAM)); + } +} + +/* + * ======================================================================= + */ + +/* + * ok.. stop of control registers (wave & ramp) need some special things.. + * big UltraClick (tm) elimination... + */ + +static inline void __snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg) +{ + unsigned char value; + + outb(reg | 0x80, gus->gf1.reg_regsel); + mb(); + value = inb(gus->gf1.reg_data8); + mb(); + outb(reg, gus->gf1.reg_regsel); + mb(); + outb((value | 0x03) & ~(0x80 | 0x20), gus->gf1.reg_data8); + mb(); +} + +static inline void __snd_gf1_write8(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + outb(data, gus->gf1.reg_data8); + mb(); +} + +static inline unsigned char __snd_gf1_look8(struct snd_gus_card * gus, + unsigned char reg) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + return inb(gus->gf1.reg_data8); +} + +static inline void __snd_gf1_write16(struct snd_gus_card * gus, + unsigned char reg, unsigned int data) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) data, gus->gf1.reg_data16); + mb(); +} + +static inline unsigned short __snd_gf1_look16(struct snd_gus_card * gus, + unsigned char reg) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + return inw(gus->gf1.reg_data16); +} + +static inline void __snd_gf1_adlib_write(struct snd_gus_card * gus, + unsigned char reg, unsigned char data) +{ + outb(reg, gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); + outb(data, gus->gf1.reg_timerdata); + inb(gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); +} + +static inline void __snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg, + unsigned int addr, int w_16bit) +{ + if (gus->gf1.enh_mode) { + if (w_16bit) + addr = ((addr >> 1) & ~0x0000000f) | (addr & 0x0000000f); + __snd_gf1_write8(gus, SNDRV_GF1_VB_UPPER_ADDRESS, (unsigned char) ((addr >> 26) & 0x03)); + } else if (w_16bit) + addr = (addr & 0x00c0000f) | ((addr & 0x003ffff0) >> 1); + __snd_gf1_write16(gus, reg, (unsigned short) (addr >> 11)); + __snd_gf1_write16(gus, reg + 1, (unsigned short) (addr << 5)); +} + +static inline unsigned int __snd_gf1_read_addr(struct snd_gus_card * gus, + unsigned char reg, short w_16bit) +{ + unsigned int res; + + res = ((unsigned int) __snd_gf1_look16(gus, reg | 0x80) << 11) & 0xfff800; + res |= ((unsigned int) __snd_gf1_look16(gus, (reg + 1) | 0x80) >> 5) & 0x0007ff; + if (gus->gf1.enh_mode) { + res |= (unsigned int) __snd_gf1_look8(gus, SNDRV_GF1_VB_UPPER_ADDRESS | 0x80) << 26; + if (w_16bit) + res = ((res << 1) & 0xffffffe0) | (res & 0x0000000f); + } else if (w_16bit) + res = ((res & 0x001ffff0) << 1) | (res & 0x00c0000f); + return res; +} + + +/* + * ======================================================================= + */ + +void snd_gf1_ctrl_stop(struct snd_gus_card * gus, unsigned char reg) +{ + __snd_gf1_ctrl_stop(gus, reg); +} + +void snd_gf1_write8(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + __snd_gf1_write8(gus, reg, data); +} + +unsigned char snd_gf1_look8(struct snd_gus_card * gus, unsigned char reg) +{ + return __snd_gf1_look8(gus, reg); +} + +void snd_gf1_write16(struct snd_gus_card * gus, + unsigned char reg, + unsigned int data) +{ + __snd_gf1_write16(gus, reg, data); +} + +unsigned short snd_gf1_look16(struct snd_gus_card * gus, unsigned char reg) +{ + return __snd_gf1_look16(gus, reg); +} + +void snd_gf1_adlib_write(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + __snd_gf1_adlib_write(gus, reg, data); +} + +void snd_gf1_write_addr(struct snd_gus_card * gus, unsigned char reg, + unsigned int addr, short w_16bit) +{ + __snd_gf1_write_addr(gus, reg, addr, w_16bit); +} + +unsigned int snd_gf1_read_addr(struct snd_gus_card * gus, + unsigned char reg, + short w_16bit) +{ + return __snd_gf1_read_addr(gus, reg, w_16bit); +} + +/* + + */ + +void snd_gf1_i_ctrl_stop(struct snd_gus_card * gus, unsigned char reg) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + __snd_gf1_ctrl_stop(gus, reg); +} + +void snd_gf1_i_write8(struct snd_gus_card * gus, + unsigned char reg, + unsigned char data) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + __snd_gf1_write8(gus, reg, data); +} + +unsigned char snd_gf1_i_look8(struct snd_gus_card * gus, unsigned char reg) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + return __snd_gf1_look8(gus, reg); +} + +void snd_gf1_i_write16(struct snd_gus_card * gus, + unsigned char reg, + unsigned int data) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + __snd_gf1_write16(gus, reg, data); +} + +unsigned short snd_gf1_i_look16(struct snd_gus_card * gus, unsigned char reg) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + return __snd_gf1_look16(gus, reg); +} + +void snd_gf1_dram_addr(struct snd_gus_card * gus, unsigned int addr) +{ + outb(0x43, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(0x44, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); +} + +void snd_gf1_poke(struct snd_gus_card * gus, unsigned int addr, unsigned char data) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(data, gus->gf1.reg_dram); +} + +unsigned char snd_gf1_peek(struct snd_gus_card * gus, unsigned int addr) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + return inb(gus->gf1.reg_dram); +} + +#if 0 + +void snd_gf1_pokew(struct snd_gus_card * gus, unsigned int addr, unsigned short data) +{ + if (!gus->interwave) + dev_dbg(gus->card->dev, "%s - GF1!!!\n", __func__); + guard(spinlock_irqsave)(&gus->reg_lock); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + mb(); + outw(data, gus->gf1.reg_data16); +} + +unsigned short snd_gf1_peekw(struct snd_gus_card * gus, unsigned int addr) +{ + if (!gus->interwave) + dev_dbg(gus->card->dev, "%s - GF1!!!\n", __func__); + guard(spinlock_irqsave)(&gus->reg_lock); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + mb(); + return inw(gus->gf1.reg_data16); +} + +void snd_gf1_dram_setmem(struct snd_gus_card * gus, unsigned int addr, + unsigned short value, unsigned int count) +{ + unsigned long port; + + if (!gus->interwave) + dev_dbg(gus->card->dev, "%s - GF1!!!\n", __func__); + addr &= ~1; + count >>= 1; + port = GUSP(gus, GF1DATALOW); + guard(spinlock_irqsave)(&gus->reg_lock); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + while (count--) + outw(value, port); +} + +#endif /* 0 */ + +void snd_gf1_select_active_voices(struct snd_gus_card * gus) +{ + unsigned short voices; + + static const unsigned short voices_tbl[32 - 14 + 1] = + { + 44100, 41160, 38587, 36317, 34300, 32494, 30870, 29400, 28063, 26843, + 25725, 24696, 23746, 22866, 22050, 21289, 20580, 19916, 19293 + }; + + voices = gus->gf1.active_voices; + if (voices > 32) + voices = 32; + if (voices < 14) + voices = 14; + if (gus->gf1.enh_mode) + voices = 32; + gus->gf1.active_voices = voices; + gus->gf1.playback_freq = + gus->gf1.enh_mode ? 44100 : voices_tbl[voices - 14]; + if (!gus->gf1.enh_mode) { + snd_gf1_i_write8(gus, SNDRV_GF1_GB_ACTIVE_VOICES, 0xc0 | (voices - 1)); + udelay(100); + } +} diff --git a/sound/isa/gus/gus_irq.c b/sound/isa/gus/gus_irq.c new file mode 100644 index 000000000..0e1054402 --- /dev/null +++ b/sound/isa/gus/gus_irq.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Routine for IRQ handling from GF1/InterWave chip + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <sound/core.h> +#include <sound/info.h> +#include <sound/gus.h> + +#ifdef CONFIG_SND_DEBUG +#define STAT_ADD(x) ((x)++) +#else +#define STAT_ADD(x) while (0) { ; } +#endif + +irqreturn_t snd_gus_interrupt(int irq, void *dev_id) +{ + struct snd_gus_card * gus = dev_id; + unsigned char status; + int loop = 100; + int handled = 0; + +__again: + status = inb(gus->gf1.reg_irqstat); + if (status == 0) + return IRQ_RETVAL(handled); + handled = 1; + if (status & 0x02) { + STAT_ADD(gus->gf1.interrupt_stat_midi_in); + if (gus->gf1.interrupt_handler_midi_in) + gus->gf1.interrupt_handler_midi_in(gus); + } + if (status & 0x01) { + STAT_ADD(gus->gf1.interrupt_stat_midi_out); + if (gus->gf1.interrupt_handler_midi_out) + gus->gf1.interrupt_handler_midi_out(gus); + } + if (status & (0x20 | 0x40)) { + unsigned int already, _current_; + unsigned char voice_status, voice; + struct snd_gus_voice *pvoice; + + already = 0; + while (((voice_status = snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ)) & 0xc0) != 0xc0) { + voice = voice_status & 0x1f; + _current_ = 1 << voice; + if (already & _current_) + continue; /* multi request */ + already |= _current_; /* mark request */ +#if 0 + dev_dbg(gus->card->dev, + "voice = %i, voice_status = 0x%x, voice_verify = %i\n", + voice, voice_status, inb(GUSP(gus, GF1PAGE))); +#endif + pvoice = &gus->gf1.voices[voice]; + if (pvoice->use) { + if (!(voice_status & 0x80)) { /* voice position IRQ */ + STAT_ADD(pvoice->interrupt_stat_wave); + pvoice->handler_wave(gus, pvoice); + } + if (!(voice_status & 0x40)) { /* volume ramp IRQ */ + STAT_ADD(pvoice->interrupt_stat_volume); + pvoice->handler_volume(gus, pvoice); + } + } else { + STAT_ADD(gus->gf1.interrupt_stat_voice_lost); + snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + } + } + } + if (status & 0x04) { + STAT_ADD(gus->gf1.interrupt_stat_timer1); + if (gus->gf1.interrupt_handler_timer1) + gus->gf1.interrupt_handler_timer1(gus); + } + if (status & 0x08) { + STAT_ADD(gus->gf1.interrupt_stat_timer2); + if (gus->gf1.interrupt_handler_timer2) + gus->gf1.interrupt_handler_timer2(gus); + } + if (status & 0x80) { + if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL) & 0x40) { + STAT_ADD(gus->gf1.interrupt_stat_dma_write); + if (gus->gf1.interrupt_handler_dma_write) + gus->gf1.interrupt_handler_dma_write(gus); + } + if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL) & 0x40) { + STAT_ADD(gus->gf1.interrupt_stat_dma_read); + if (gus->gf1.interrupt_handler_dma_read) + gus->gf1.interrupt_handler_dma_read(gus); + } + } + if (--loop > 0) + goto __again; + return IRQ_NONE; +} + +#ifdef CONFIG_SND_DEBUG +static void snd_gus_irq_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_gus_card *gus; + struct snd_gus_voice *pvoice; + int idx; + + gus = entry->private_data; + snd_iprintf(buffer, "midi out = %u\n", gus->gf1.interrupt_stat_midi_out); + snd_iprintf(buffer, "midi in = %u\n", gus->gf1.interrupt_stat_midi_in); + snd_iprintf(buffer, "timer1 = %u\n", gus->gf1.interrupt_stat_timer1); + snd_iprintf(buffer, "timer2 = %u\n", gus->gf1.interrupt_stat_timer2); + snd_iprintf(buffer, "dma write = %u\n", gus->gf1.interrupt_stat_dma_write); + snd_iprintf(buffer, "dma read = %u\n", gus->gf1.interrupt_stat_dma_read); + snd_iprintf(buffer, "voice lost = %u\n", gus->gf1.interrupt_stat_voice_lost); + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + snd_iprintf(buffer, "voice %i: wave = %u, volume = %u\n", + idx, + pvoice->interrupt_stat_wave, + pvoice->interrupt_stat_volume); + } +} + +void snd_gus_irq_profile_init(struct snd_gus_card *gus) +{ + snd_card_ro_proc_new(gus->card, "gusirq", gus, snd_gus_irq_info_read); +} + +#endif diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c new file mode 100644 index 000000000..6adf8b698 --- /dev/null +++ b/sound/isa/gus/gus_main.c @@ -0,0 +1,481 @@ +// 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); diff --git a/sound/isa/gus/gus_mem.c b/sound/isa/gus/gus_mem.c new file mode 100644 index 000000000..927a2bc80 --- /dev/null +++ b/sound/isa/gus/gus_mem.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * GUS's memory allocation routines / bottom layer + */ + +#include <linux/slab.h> +#include <linux/string.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/info.h> + +#ifdef CONFIG_SND_DEBUG +static void snd_gf1_mem_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer); +#endif + +static struct snd_gf1_mem_block * +snd_gf1_mem_xalloc(struct snd_gf1_mem *alloc, struct snd_gf1_mem_block *block, + const char *name) +{ + struct snd_gf1_mem_block *pblock, *nblock; + + nblock = kmalloc_obj(struct snd_gf1_mem_block); + if (nblock == NULL) + return NULL; + *nblock = *block; + nblock->name = kstrdup(name, GFP_KERNEL); + if (!nblock->name) { + kfree(nblock); + return NULL; + } + + pblock = alloc->first; + while (pblock) { + if (pblock->ptr > nblock->ptr) { + nblock->prev = pblock->prev; + nblock->next = pblock; + pblock->prev = nblock; + if (pblock == alloc->first) + alloc->first = nblock; + else + nblock->prev->next = nblock; + return nblock; + } + pblock = pblock->next; + } + nblock->next = NULL; + if (alloc->last == NULL) { + nblock->prev = NULL; + alloc->first = alloc->last = nblock; + } else { + nblock->prev = alloc->last; + alloc->last->next = nblock; + alloc->last = nblock; + } + return nblock; +} + +int snd_gf1_mem_xfree(struct snd_gf1_mem * alloc, struct snd_gf1_mem_block * block) +{ + if (block->share) { /* ok.. shared block */ + block->share--; + return 0; + } + if (alloc->first == block) { + alloc->first = block->next; + if (block->next) + block->next->prev = NULL; + } else { + block->prev->next = block->next; + if (block->next) + block->next->prev = block->prev; + } + if (alloc->last == block) { + alloc->last = block->prev; + if (block->prev) + block->prev->next = NULL; + } else { + block->next->prev = block->prev; + if (block->prev) + block->prev->next = block->next; + } + kfree(block->name); + kfree(block); + return 0; +} + +static struct snd_gf1_mem_block *snd_gf1_mem_look(struct snd_gf1_mem * alloc, + unsigned int address) +{ + struct snd_gf1_mem_block *block; + + for (block = alloc->first; block; block = block->next) { + if (block->ptr == address) { + return block; + } + } + return NULL; +} + +static struct snd_gf1_mem_block *snd_gf1_mem_share(struct snd_gf1_mem * alloc, + unsigned int *share_id) +{ + struct snd_gf1_mem_block *block; + + if (!share_id[0] && !share_id[1] && + !share_id[2] && !share_id[3]) + return NULL; + for (block = alloc->first; block; block = block->next) + if (!memcmp(share_id, block->share_id, + sizeof(block->share_id))) + return block; + return NULL; +} + +static int snd_gf1_mem_find(struct snd_gf1_mem * alloc, + struct snd_gf1_mem_block * block, + unsigned int size, int w_16, int align) +{ + struct snd_gf1_bank_info *info = w_16 ? alloc->banks_16 : alloc->banks_8; + unsigned int idx, boundary; + int size1; + struct snd_gf1_mem_block *pblock; + unsigned int ptr1, ptr2; + + if (w_16 && align < 2) + align = 2; + block->flags = w_16 ? SNDRV_GF1_MEM_BLOCK_16BIT : 0; + block->owner = SNDRV_GF1_MEM_OWNER_DRIVER; + block->share = 0; + block->share_id[0] = block->share_id[1] = + block->share_id[2] = block->share_id[3] = 0; + block->name = NULL; + block->prev = block->next = NULL; + for (pblock = alloc->first, idx = 0; pblock; pblock = pblock->next) { + while (pblock->ptr >= (boundary = info[idx].address + info[idx].size)) + idx++; + while (pblock->ptr + pblock->size >= (boundary = info[idx].address + info[idx].size)) + idx++; + ptr2 = boundary; + if (pblock->next) { + if (pblock->ptr + pblock->size == pblock->next->ptr) + continue; + if (pblock->next->ptr < boundary) + ptr2 = pblock->next->ptr; + } + ptr1 = ALIGN(pblock->ptr + pblock->size, align); + if (ptr1 >= ptr2) + continue; + size1 = ptr2 - ptr1; + if ((int)size <= size1) { + block->ptr = ptr1; + block->size = size; + return 0; + } + } + while (++idx < 4) { + if (size <= info[idx].size) { + /* I assume that bank address is already aligned.. */ + block->ptr = info[idx].address; + block->size = size; + return 0; + } + } + return -ENOMEM; +} + +struct snd_gf1_mem_block *snd_gf1_mem_alloc(struct snd_gf1_mem * alloc, int owner, + char *name, int size, int w_16, int align, + unsigned int *share_id) +{ + struct snd_gf1_mem_block block, *nblock; + + guard(mutex)(&alloc->memory_mutex); + if (share_id != NULL) { + nblock = snd_gf1_mem_share(alloc, share_id); + if (nblock != NULL) { + if (size != (int)nblock->size) { + /* TODO: remove in the future */ + pr_err("%s - share: sizes differ\n", __func__); + goto __std; + } + nblock->share++; + return NULL; + } + } + __std: + if (snd_gf1_mem_find(alloc, &block, size, w_16, align) < 0) + return NULL; + if (share_id != NULL) + memcpy(&block.share_id, share_id, sizeof(block.share_id)); + block.owner = owner; + nblock = snd_gf1_mem_xalloc(alloc, &block, name); + return nblock; +} + +int snd_gf1_mem_free(struct snd_gf1_mem * alloc, unsigned int address) +{ + struct snd_gf1_mem_block *block; + + guard(mutex)(&alloc->memory_mutex); + block = snd_gf1_mem_look(alloc, address); + if (block) + return snd_gf1_mem_xfree(alloc, block); + return -EINVAL; +} + +int snd_gf1_mem_init(struct snd_gus_card * gus) +{ + struct snd_gf1_mem *alloc; + struct snd_gf1_mem_block block; + + alloc = &gus->gf1.mem_alloc; + mutex_init(&alloc->memory_mutex); + alloc->first = alloc->last = NULL; + if (!gus->gf1.memory) + return 0; + + memset(&block, 0, sizeof(block)); + block.owner = SNDRV_GF1_MEM_OWNER_DRIVER; + if (gus->gf1.enh_mode) { + block.ptr = 0; + block.size = 1024; + if (!snd_gf1_mem_xalloc(alloc, &block, "InterWave LFOs")) + return -ENOMEM; + } + block.ptr = gus->gf1.default_voice_address; + block.size = 4; + if (!snd_gf1_mem_xalloc(alloc, &block, "Voice default (NULL's)")) + return -ENOMEM; +#ifdef CONFIG_SND_DEBUG + snd_card_ro_proc_new(gus->card, "gusmem", gus, snd_gf1_mem_info_read); +#endif + return 0; +} + +int snd_gf1_mem_done(struct snd_gus_card * gus) +{ + struct snd_gf1_mem *alloc; + struct snd_gf1_mem_block *block, *nblock; + + alloc = &gus->gf1.mem_alloc; + block = alloc->first; + while (block) { + nblock = block->next; + snd_gf1_mem_xfree(alloc, block); + block = nblock; + } + return 0; +} + +#ifdef CONFIG_SND_DEBUG +static void snd_gf1_mem_info_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_gus_card *gus; + struct snd_gf1_mem *alloc; + struct snd_gf1_mem_block *block; + unsigned int total, used; + int i; + + gus = entry->private_data; + alloc = &gus->gf1.mem_alloc; + guard(mutex)(&alloc->memory_mutex); + snd_iprintf(buffer, "8-bit banks : \n "); + for (i = 0; i < 4; i++) + snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_8[i].address, alloc->banks_8[i].size >> 10, i + 1 < 4 ? "," : ""); + snd_iprintf(buffer, "\n" + "16-bit banks : \n "); + for (i = total = 0; i < 4; i++) { + snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_16[i].address, alloc->banks_16[i].size >> 10, i + 1 < 4 ? "," : ""); + total += alloc->banks_16[i].size; + } + snd_iprintf(buffer, "\n"); + used = 0; + for (block = alloc->first, i = 0; block; block = block->next, i++) { + used += block->size; + snd_iprintf(buffer, "Block %i onboard 0x%x size %i (0x%x):\n", i, block->ptr, block->size, block->size); + if (block->share || + block->share_id[0] || block->share_id[1] || + block->share_id[2] || block->share_id[3]) + snd_iprintf(buffer, " Share : %i [id0 0x%x] [id1 0x%x] [id2 0x%x] [id3 0x%x]\n", + block->share, + block->share_id[0], block->share_id[1], + block->share_id[2], block->share_id[3]); + snd_iprintf(buffer, " Flags :%s\n", + block->flags & SNDRV_GF1_MEM_BLOCK_16BIT ? " 16-bit" : ""); + snd_iprintf(buffer, " Owner : "); + switch (block->owner) { + case SNDRV_GF1_MEM_OWNER_DRIVER: + snd_iprintf(buffer, "driver - %s\n", block->name); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE: + snd_iprintf(buffer, "SIMPLE wave\n"); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_GF1: + snd_iprintf(buffer, "GF1 wave\n"); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF: + snd_iprintf(buffer, "IWFFFF wave\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + } + } + snd_iprintf(buffer, " Total: memory = %i, used = %i, free = %i\n", + total, used, total - used); +#if 0 + ultra_iprintf(buffer, " Verify: free = %i, max 8-bit block = %i, max 16-bit block = %i\n", + ultra_memory_free_size(card, &card->gf1.mem_alloc), + ultra_memory_free_block(card, &card->gf1.mem_alloc, 0), + ultra_memory_free_block(card, &card->gf1.mem_alloc, 1)); +#endif +} +#endif diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c new file mode 100644 index 000000000..04c212882 --- /dev/null +++ b/sound/isa/gus/gus_mem_proc.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * GUS's memory access via proc filesystem + */ + +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/info.h> + +struct gus_proc_private { + int rom; /* data are in ROM */ + unsigned int address; + unsigned int size; + struct snd_gus_card * gus; +}; + +static ssize_t snd_gf1_mem_proc_dump(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *buf, + size_t count, loff_t pos) +{ + struct gus_proc_private *priv = entry->private_data; + struct snd_gus_card *gus = priv->gus; + int err; + + err = snd_gus_dram_read(gus, buf, pos, count, priv->rom); + if (err < 0) + return err; + return count; +} + +static void snd_gf1_mem_proc_free(struct snd_info_entry *entry) +{ + struct gus_proc_private *priv = entry->private_data; + kfree(priv); +} + +static const struct snd_info_entry_ops snd_gf1_mem_proc_ops = { + .read = snd_gf1_mem_proc_dump, +}; + +int snd_gf1_mem_proc_init(struct snd_gus_card * gus) +{ + int idx; + char name[16]; + struct gus_proc_private *priv; + struct snd_info_entry *entry; + + for (idx = 0; idx < 4; idx++) { + if (gus->gf1.mem_alloc.banks_8[idx].size > 0) { + priv = kzalloc_obj(*priv); + if (priv == NULL) + return -ENOMEM; + priv->gus = gus; + sprintf(name, "gus-ram-%i", idx); + if (! snd_card_proc_new(gus->card, name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = priv; + entry->private_free = snd_gf1_mem_proc_free; + entry->c.ops = &snd_gf1_mem_proc_ops; + priv->address = gus->gf1.mem_alloc.banks_8[idx].address; + priv->size = entry->size = gus->gf1.mem_alloc.banks_8[idx].size; + } + } + } + for (idx = 0; idx < 4; idx++) { + if (gus->gf1.rom_present & (1 << idx)) { + priv = kzalloc_obj(*priv); + if (priv == NULL) + return -ENOMEM; + priv->rom = 1; + priv->gus = gus; + sprintf(name, "gus-rom-%i", idx); + if (! snd_card_proc_new(gus->card, name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = priv; + entry->private_free = snd_gf1_mem_proc_free; + entry->c.ops = &snd_gf1_mem_proc_ops; + priv->address = idx * 4096 * 1024; + priv->size = entry->size = gus->gf1.rom_memory; + } + } + } + return 0; +} diff --git a/sound/isa/gus/gus_mixer.c b/sound/isa/gus/gus_mixer.c new file mode 100644 index 000000000..9bfdb4e45 --- /dev/null +++ b/sound/isa/gus/gus_mixer.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Routines for control of ICS 2101 chip and "mixer" in GF1 chip + */ + +#include <linux/time.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/gus.h> + +/* + * + */ + +#define GF1_SINGLE(xname, xindex, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_gf1_info_single, \ + .get = snd_gf1_get_single, .put = snd_gf1_put_single, \ + .private_value = shift | (invert << 8) } + +#define snd_gf1_info_single snd_ctl_boolean_mono_info + +static int snd_gf1_get_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + + ucontrol->value.integer.value[0] = (gus->mix_cntrl_reg >> shift) & 1; + if (invert) + ucontrol->value.integer.value[0] ^= 1; + return 0; +} + +static int snd_gf1_put_single(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + int change; + unsigned char oval, nval; + + nval = ucontrol->value.integer.value[0] & 1; + if (invert) + nval ^= 1; + nval <<= shift; + guard(spinlock_irqsave)(&gus->reg_lock); + oval = gus->mix_cntrl_reg; + nval = (oval & ~(1 << shift)) | nval; + change = nval != oval; + outb(gus->mix_cntrl_reg = nval, GUSP(gus, MIXCNTRLREG)); + outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE)); + return change; +} + +#define ICS_DOUBLE(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ics_info_double, \ + .get = snd_ics_get_double, .put = snd_ics_put_double, \ + .private_value = addr } + +static int snd_ics_info_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int snd_ics_get_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value & 0xff; + unsigned char left, right; + + guard(spinlock_irqsave)(&gus->reg_lock); + left = gus->gf1.ics_regs[addr][0]; + right = gus->gf1.ics_regs[addr][1]; + ucontrol->value.integer.value[0] = left & 127; + ucontrol->value.integer.value[1] = right & 127; + return 0; +} + +static int snd_ics_put_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + int addr = kcontrol->private_value & 0xff; + int change; + unsigned char val1, val2, oval1, oval2; + + val1 = ucontrol->value.integer.value[0] & 127; + val2 = ucontrol->value.integer.value[1] & 127; + guard(spinlock_irqsave)(&gus->reg_lock); + oval1 = gus->gf1.ics_regs[addr][0]; + oval2 = gus->gf1.ics_regs[addr][1]; + change = val1 != oval1 || val2 != oval2; + gus->gf1.ics_regs[addr][0] = val1; + gus->gf1.ics_regs[addr][1] = val2; + if (gus->ics_flag && gus->ics_flipped && + (addr == SNDRV_ICS_GF1_DEV || addr == SNDRV_ICS_MASTER_DEV)) + swap(val1, val2); + addr <<= 3; + outb(addr | 0, GUSP(gus, MIXCNTRLPORT)); + outb(1, GUSP(gus, MIXDATAPORT)); + outb(addr | 2, GUSP(gus, MIXCNTRLPORT)); + outb((unsigned char) val1, GUSP(gus, MIXDATAPORT)); + outb(addr | 1, GUSP(gus, MIXCNTRLPORT)); + outb(2, GUSP(gus, MIXDATAPORT)); + outb(addr | 3, GUSP(gus, MIXCNTRLPORT)); + outb((unsigned char) val2, GUSP(gus, MIXDATAPORT)); + return change; +} + +static const struct snd_kcontrol_new snd_gf1_controls[] = { +GF1_SINGLE("Master Playback Switch", 0, 1, 1), +GF1_SINGLE("Line Switch", 0, 0, 1), +GF1_SINGLE("Mic Switch", 0, 2, 0) +}; + +static const struct snd_kcontrol_new snd_ics_controls[] = { +GF1_SINGLE("Master Playback Switch", 0, 1, 1), +ICS_DOUBLE("Master Playback Volume", 0, SNDRV_ICS_MASTER_DEV), +ICS_DOUBLE("Synth Playback Volume", 0, SNDRV_ICS_GF1_DEV), +GF1_SINGLE("Line Switch", 0, 0, 1), +ICS_DOUBLE("Line Playback Volume", 0, SNDRV_ICS_LINE_DEV), +GF1_SINGLE("Mic Switch", 0, 2, 0), +ICS_DOUBLE("Mic Playback Volume", 0, SNDRV_ICS_MIC_DEV), +ICS_DOUBLE("CD Playback Volume", 0, SNDRV_ICS_CD_DEV) +}; + +int snd_gf1_new_mixer(struct snd_gus_card * gus) +{ + struct snd_card *card; + unsigned int idx, max; + int err; + + if (snd_BUG_ON(!gus)) + return -EINVAL; + card = gus->card; + if (snd_BUG_ON(!card)) + return -EINVAL; + + if (gus->ics_flag) + snd_component_add(card, "ICS2101"); + if (card->mixername[0] == '\0') { + strscpy(card->mixername, gus->ics_flag ? "GF1,ICS2101" : "GF1"); + } else { + if (gus->ics_flag) + strcat(card->mixername, ",ICS2101"); + strcat(card->mixername, ",GF1"); + } + + if (!gus->ics_flag) { + max = gus->ess_flag ? 1 : ARRAY_SIZE(snd_gf1_controls); + for (idx = 0; idx < max; idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_gf1_controls[idx], gus)); + if (err < 0) + return err; + } + } else { + for (idx = 0; idx < ARRAY_SIZE(snd_ics_controls); idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_ics_controls[idx], gus)); + if (err < 0) + return err; + } + } + return 0; +} diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c new file mode 100644 index 000000000..08ccb4d80 --- /dev/null +++ b/sound/isa/gus/gus_pcm.c @@ -0,0 +1,862 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Routines for control of GF1 chip (PCM things) + * + * InterWave chips supports interleaved DMA, but this feature isn't used in + * this code. + * + * This code emulates autoinit DMA transfer for playback, recording by GF1 + * chip doesn't support autoinit DMA. + */ + +#include <asm/dma.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/gus.h> +#include <sound/pcm_params.h> +#include "gus_tables.h" + +/* maximum rate */ + +#define SNDRV_GF1_PCM_RATE 48000 + +#define SNDRV_GF1_PCM_PFLG_NONE 0 +#define SNDRV_GF1_PCM_PFLG_ACTIVE (1<<0) +#define SNDRV_GF1_PCM_PFLG_NEUTRAL (2<<0) + +struct gus_pcm_private { + struct snd_gus_card * gus; + struct snd_pcm_substream *substream; + spinlock_t lock; + unsigned int voices; + struct snd_gus_voice *pvoices[2]; + unsigned int memory; + unsigned short flags; + unsigned char voice_ctrl, ramp_ctrl; + unsigned int bpos; + unsigned int blocks; + unsigned int block_size; + unsigned int dma_size; + wait_queue_head_t sleep; + atomic_t dma_count; + int final_volume; +}; + +static void snd_gf1_pcm_block_change_ack(struct snd_gus_card * gus, void *private_data) +{ + struct gus_pcm_private *pcmp = private_data; + + if (pcmp) { + atomic_dec(&pcmp->dma_count); + wake_up(&pcmp->sleep); + } +} + +static int snd_gf1_pcm_block_change(struct snd_pcm_substream *substream, + unsigned int offset, + unsigned int addr, + unsigned int count) +{ + struct snd_gf1_dma_block block; + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + count += offset & 31; + offset &= ~31; + memset(&block, 0, sizeof(block)); + block.cmd = SNDRV_GF1_DMA_IRQ; + if (snd_pcm_format_unsigned(runtime->format)) + block.cmd |= SNDRV_GF1_DMA_UNSIGNED; + if (snd_pcm_format_width(runtime->format) == 16) + block.cmd |= SNDRV_GF1_DMA_16BIT; + block.addr = addr & ~31; + block.buffer = runtime->dma_area + offset; + block.buf_addr = runtime->dma_addr + offset; + block.count = count; + block.private_data = pcmp; + block.ack = snd_gf1_pcm_block_change_ack; + if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0)) + atomic_inc(&pcmp->dma_count); + return 0; +} + +static void snd_gf1_pcm_trigger_up(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + struct snd_gus_card * gus = pcmp->gus; + unsigned char voice_ctrl, ramp_ctrl; + unsigned short rate; + unsigned int curr, begin, end; + unsigned short vol; + unsigned char pan; + unsigned int voice; + + scoped_guard(spinlock_irqsave, &pcmp->lock) { + if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) + return; + pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE; + pcmp->final_volume = 0; + } + rate = snd_gf1_translate_freq(gus, runtime->rate << 4); + /* enable WAVE IRQ */ + voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20; + /* enable RAMP IRQ + rollover */ + ramp_ctrl = 0x24; + if (pcmp->blocks == 1) { + voice_ctrl |= 0x08; /* loop enable */ + ramp_ctrl &= ~0x04; /* disable rollover */ + } + for (voice = 0; voice < pcmp->voices; voice++) { + begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels); + curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels; + end = curr + (pcmp->block_size / runtime->channels); + end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1; + pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8; + vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan); + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + } + } + + guard(spinlock_irqsave)(&gus->reg_lock); + for (voice = 0; voice < pcmp->voices; voice++) { + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00); /* deactivate voice */ + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + voice_ctrl &= ~0x20; + } + voice_ctrl |= 0x20; + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + for (voice = 0; voice < pcmp->voices; voice++) { + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + voice_ctrl &= ~0x20; /* disable IRQ for next voice */ + } + } +} + +static void snd_gf1_pcm_interrupt_wave(struct snd_gus_card * gus, + struct snd_gus_voice *pvoice) +{ + struct gus_pcm_private * pcmp; + struct snd_pcm_runtime *runtime; + unsigned char voice_ctrl, ramp_ctrl; + unsigned int idx; + unsigned int end, step; + + if (!pvoice->private_data) { + dev_dbg(gus->card->dev, "%s: unknown wave irq?\n", __func__); + snd_gf1_smart_stop_voice(gus, pvoice->number); + return; + } + pcmp = pvoice->private_data; + if (pcmp == NULL) { + dev_dbg(gus->card->dev, "%s: unknown wave irq?\n", __func__); + snd_gf1_smart_stop_voice(gus, pvoice->number); + return; + } + gus = pcmp->gus; + runtime = pcmp->substream->runtime; + + scoped_guard(spinlock, &gus->reg_lock) { + snd_gf1_select_voice(gus, pvoice->number); + voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b; + ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03; +#if 0 + snd_gf1_select_voice(gus, pvoice->number); + dev_dbg(gus->card->dev, "position = 0x%x\n", + (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); + snd_gf1_select_voice(gus, pcmp->pvoices[1]->number); + dev_dbg(gus->card->dev, "position = 0x%x\n", + (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); + snd_gf1_select_voice(gus, pvoice->number); +#endif + pcmp->bpos++; + pcmp->bpos %= pcmp->blocks; + if (pcmp->bpos + 1 >= pcmp->blocks) { /* last block? */ + voice_ctrl |= 0x08; /* enable loop */ + } else { + ramp_ctrl |= 0x04; /* enable rollover */ + } + end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels); + end -= voice_ctrl & 4 ? 2 : 1; + step = pcmp->dma_size / runtime->channels; + voice_ctrl |= 0x20; + if (!pcmp->final_volume) { + ramp_ctrl |= 0x20; + ramp_ctrl &= ~0x03; + } + for (idx = 0; idx < pcmp->voices; idx++, end += step) { + snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + voice_ctrl &= ~0x20; + } + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + voice_ctrl |= 0x20; + for (idx = 0; idx < pcmp->voices; idx++) { + snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + voice_ctrl &= ~0x20; + } + } + } + + snd_pcm_period_elapsed(pcmp->substream); +#if 0 + if ((runtime->flags & SNDRV_PCM_FLG_MMAP) && + *runtime->state == SNDRV_PCM_STATE_RUNNING) { + end = pcmp->bpos * pcmp->block_size; + if (runtime->channels > 1) { + snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + (end / 2), pcmp->block_size / 2); + snd_gf1_pcm_block_change(pcmp->substream, end + (pcmp->block_size / 2), pcmp->memory + (pcmp->dma_size / 2) + (end / 2), pcmp->block_size / 2); + } else { + snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + end, pcmp->block_size); + } + } +#endif +} + +static void snd_gf1_pcm_interrupt_volume(struct snd_gus_card * gus, + struct snd_gus_voice * pvoice) +{ + unsigned short vol; + int cvoice; + struct gus_pcm_private *pcmp = pvoice->private_data; + + /* stop ramp, but leave rollover bit untouched */ + scoped_guard(spinlock, &gus->reg_lock) { + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + } + if (pcmp == NULL) + return; + /* are we active? */ + if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE)) + return; + /* load real volume - better precision */ + cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1; + if (pcmp->substream == NULL) + return; + vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + guard(spinlock)(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol); + pcmp->final_volume = 1; +} + +static void snd_gf1_pcm_volume_change(struct snd_gus_card * gus) +{ +} + +static int snd_gf1_pcm_poke_block(struct snd_gus_card *gus, unsigned char *buf, + unsigned int pos, unsigned int count, + int w16, int invert) +{ + unsigned int len; + + while (count > 0) { + len = count; + if (len > 512) /* limit, to allow IRQ */ + len = 512; + count -= len; + if (gus->interwave) { + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01 | (invert ? 0x08 : 0x00)); + snd_gf1_dram_addr(gus, pos); + if (w16) { + outb(SNDRV_GF1_GW_DRAM_IO16, GUSP(gus, GF1REGSEL)); + outsw(GUSP(gus, GF1DATALOW), buf, len >> 1); + } else { + outsb(GUSP(gus, DRAM), buf, len); + } + buf += 512; + pos += 512; + } else { + invert = invert ? 0x80 : 0x00; + if (w16) { + len >>= 1; + while (len--) { + snd_gf1_poke(gus, pos++, *buf++); + snd_gf1_poke(gus, pos++, *buf++ ^ invert); + } + } else { + while (len--) + snd_gf1_poke(gus, pos++, *buf++ ^ invert); + } + } + if (count > 0 && !in_interrupt()) { + schedule_timeout_interruptible(1); + if (signal_pending(current)) + return -EAGAIN; + } + } + return 0; +} + +static int get_bpos(struct gus_pcm_private *pcmp, int voice, unsigned int pos, + unsigned int len) +{ + unsigned int bpos = pos + (voice * (pcmp->dma_size / 2)); + if (snd_BUG_ON(bpos > pcmp->dma_size)) + return -EIO; + if (snd_BUG_ON(bpos + len > pcmp->dma_size)) + return -EIO; + return bpos; +} + +static int playback_copy_ack(struct snd_pcm_substream *substream, + unsigned int bpos, unsigned int len) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + struct snd_gus_card *gus = pcmp->gus; + int w16, invert; + + if (len > 32) + return snd_gf1_pcm_block_change(substream, bpos, + pcmp->memory + bpos, len); + + w16 = (snd_pcm_format_width(runtime->format) == 16); + invert = snd_pcm_format_unsigned(runtime->format); + return snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, + pcmp->memory + bpos, len, w16, invert); +} + +static int snd_gf1_pcm_playback_copy(struct snd_pcm_substream *substream, + int voice, unsigned long pos, + struct iov_iter *src, unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + unsigned int len = count; + int bpos; + + bpos = get_bpos(pcmp, voice, pos, len); + if (bpos < 0) + return bpos; + if (copy_from_iter(runtime->dma_area + bpos, len, src) != len) + return -EFAULT; + return playback_copy_ack(substream, bpos, len); +} + +static int snd_gf1_pcm_playback_silence(struct snd_pcm_substream *substream, + int voice, unsigned long pos, + unsigned long count) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + unsigned int len = count; + int bpos; + + bpos = get_bpos(pcmp, voice, pos, len); + if (bpos < 0) + return bpos; + snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos, + bytes_to_samples(runtime, count)); + return playback_copy_ack(substream, bpos, len); +} + +static int snd_gf1_pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + if (runtime->buffer_changed) { + struct snd_gf1_mem_block *block; + if (pcmp->memory > 0) { + snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory); + pcmp->memory = 0; + } + block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc, + SNDRV_GF1_MEM_OWNER_DRIVER, + "GF1 PCM", + runtime->dma_bytes, 1, 32, + NULL); + if (!block) + return -ENOMEM; + pcmp->memory = block->ptr; + } + pcmp->voices = params_channels(hw_params); + if (pcmp->pvoices[0] == NULL) { + pcmp->pvoices[0] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0); + if (!pcmp->pvoices[0]) + return -ENOMEM; + pcmp->pvoices[0]->handler_wave = snd_gf1_pcm_interrupt_wave; + pcmp->pvoices[0]->handler_volume = snd_gf1_pcm_interrupt_volume; + pcmp->pvoices[0]->volume_change = snd_gf1_pcm_volume_change; + pcmp->pvoices[0]->private_data = pcmp; + } + if (pcmp->voices > 1 && pcmp->pvoices[1] == NULL) { + pcmp->pvoices[1] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0); + if (!pcmp->pvoices[1]) + return -ENOMEM; + pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave; + pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume; + pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change; + pcmp->pvoices[1]->private_data = pcmp; + } else if (pcmp->voices == 1) { + if (pcmp->pvoices[1]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]); + pcmp->pvoices[1] = NULL; + } + } + return 0; +} + +static int snd_gf1_pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + if (pcmp->pvoices[0]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[0]); + pcmp->pvoices[0] = NULL; + } + if (pcmp->pvoices[1]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]); + pcmp->pvoices[1] = NULL; + } + if (pcmp->memory > 0) { + snd_gf1_mem_free(&pcmp->gus->gf1.mem_alloc, pcmp->memory); + pcmp->memory = 0; + } + return 0; +} + +static int snd_gf1_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + pcmp->bpos = 0; + pcmp->dma_size = snd_pcm_lib_buffer_bytes(substream); + pcmp->block_size = snd_pcm_lib_period_bytes(substream); + pcmp->blocks = pcmp->dma_size / pcmp->block_size; + return 0; +} + +static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + int voice; + + if (cmd == SNDRV_PCM_TRIGGER_START) { + snd_gf1_pcm_trigger_up(substream); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND) { + scoped_guard(spinlock, &pcmp->lock) { + pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE; + } + voice = pcmp->pvoices[0]->number; + snd_gf1_stop_voices(gus, voice, voice); + if (pcmp->pvoices[1]) { + voice = pcmp->pvoices[1]->number; + snd_gf1_stop_voices(gus, voice, voice); + } + } else { + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_gf1_pcm_playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + unsigned int pos; + unsigned char voice_ctrl; + + pos = 0; + guard(spinlock)(&gus->reg_lock); + if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) { + snd_gf1_select_voice(gus, pcmp->pvoices[0]->number); + voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + pos = (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - pcmp->memory; + if (substream->runtime->channels > 1) + pos <<= 1; + pos = bytes_to_frames(runtime, pos); + } + return pos; +} + +static const struct snd_ratnum clock = { + .num = 9878400/16, + .den_min = 2, + .den_max = 257, + .den_step = 1, +}; + +static const struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = { + .nrats = 1, + .rats = &clock, +}; + +static int snd_gf1_pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + + gus->c_dma_size = params_buffer_bytes(hw_params); + gus->c_period_size = params_period_bytes(hw_params); + gus->c_pos = 0; + gus->gf1.pcm_rcntrl_reg = 0x21; /* IRQ at end, enable & start */ + if (params_channels(hw_params) > 1) + gus->gf1.pcm_rcntrl_reg |= 2; + if (gus->gf1.dma2 > 3) + gus->gf1.pcm_rcntrl_reg |= 4; + if (snd_pcm_format_unsigned(params_format(hw_params))) + gus->gf1.pcm_rcntrl_reg |= 0x80; + return 0; +} + +static int snd_gf1_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RECORD_RATE, runtime->rate_den - 2); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */ + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */ + snd_dma_program(gus->gf1.dma2, runtime->dma_addr, gus->c_period_size, DMA_MODE_READ); + return 0; +} + +static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + int val; + + if (cmd == SNDRV_PCM_TRIGGER_START) { + val = gus->gf1.pcm_rcntrl_reg; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND) { + val = 0; + } else { + return -EINVAL; + } + + guard(spinlock)(&gus->reg_lock); + snd_gf1_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, val); + snd_gf1_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); + return 0; +} + +static snd_pcm_uframes_t snd_gf1_pcm_capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + int pos = snd_dma_pointer(gus->gf1.dma2, gus->c_period_size); + pos = bytes_to_frames(substream->runtime, (gus->c_pos + pos) % gus->c_dma_size); + return pos; +} + +static void snd_gf1_pcm_interrupt_dma_read(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */ + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */ + if (gus->pcm_cap_substream != NULL) { + snd_gf1_pcm_capture_prepare(gus->pcm_cap_substream); + snd_gf1_pcm_capture_trigger(gus->pcm_cap_substream, SNDRV_PCM_TRIGGER_START); + gus->c_pos += gus->c_period_size; + snd_pcm_period_elapsed(gus->pcm_cap_substream); + } +} + +static const struct snd_pcm_hardware snd_gf1_pcm_playback = +{ + .info = SNDRV_PCM_INFO_NONINTERLEAVED, + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static const struct snd_pcm_hardware snd_gf1_pcm_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100, + .rate_min = 5510, + .rate_max = 44100, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void snd_gf1_pcm_playback_free(struct snd_pcm_runtime *runtime) +{ + kfree(runtime->private_data); +} + +static int snd_gf1_pcm_playback_open(struct snd_pcm_substream *substream) +{ + struct gus_pcm_private *pcmp; + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + pcmp = kzalloc_obj(*pcmp); + if (pcmp == NULL) + return -ENOMEM; + pcmp->gus = gus; + spin_lock_init(&pcmp->lock); + init_waitqueue_head(&pcmp->sleep); + atomic_set(&pcmp->dma_count, 0); + + runtime->private_data = pcmp; + runtime->private_free = snd_gf1_pcm_playback_free; + +#if 0 + dev_dbg(gus->card->dev, + "playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n", + (long) pcm->playback.buffer, (long) gus->gf1.pcm_buffer); +#endif + err = snd_gf1_dma_init(gus); + if (err < 0) + return err; + pcmp->flags = SNDRV_GF1_PCM_PFLG_NONE; + pcmp->substream = substream; + runtime->hw = snd_gf1_pcm_playback; + snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + return 0; +} + +static int snd_gf1_pcm_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + struct gus_pcm_private *pcmp = runtime->private_data; + + if (!wait_event_timeout(pcmp->sleep, (atomic_read(&pcmp->dma_count) <= 0), 2*HZ)) + dev_err(gus->card->dev, "gf1 pcm - serious DMA problem\n"); + + snd_gf1_dma_done(gus); + return 0; +} + +static int snd_gf1_pcm_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + + gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read; + gus->pcm_cap_substream = substream; + substream->runtime->hw = snd_gf1_pcm_capture; + snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + return 0; +} + +static int snd_gf1_pcm_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_gus_card *gus = snd_pcm_substream_chip(substream); + + gus->pcm_cap_substream = NULL; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_READ); + return 0; +} + +static int snd_gf1_pcm_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int snd_gf1_pcm_volume_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + + guard(spinlock_irqsave)(&gus->pcm_volume_level_lock); + ucontrol->value.integer.value[0] = gus->gf1.pcm_volume_level_left1; + ucontrol->value.integer.value[1] = gus->gf1.pcm_volume_level_right1; + return 0; +} + +static int snd_gf1_pcm_volume_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_gus_card *gus = snd_kcontrol_chip(kcontrol); + int change; + unsigned int idx; + unsigned short val1, val2, vol; + struct gus_pcm_private *pcmp; + struct snd_gus_voice *pvoice; + + val1 = ucontrol->value.integer.value[0] & 127; + val2 = ucontrol->value.integer.value[1] & 127; + scoped_guard(spinlock_irqsave, &gus->pcm_volume_level_lock) { + change = val1 != gus->gf1.pcm_volume_level_left1 || + val2 != gus->gf1.pcm_volume_level_right1; + gus->gf1.pcm_volume_level_left1 = val1; + gus->gf1.pcm_volume_level_right1 = val2; + gus->gf1.pcm_volume_level_left = snd_gf1_lvol_to_gvol_raw(val1 << 9) << 4; + gus->gf1.pcm_volume_level_right = snd_gf1_lvol_to_gvol_raw(val2 << 9) << 4; + } + /* are we active? */ + scoped_guard(spinlock_irqsave, &gus->voice_alloc) { + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (!pvoice->pcm) + continue; + pcmp = pvoice->private_data; + if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE)) + continue; + /* load real volume - better precision */ + guard(spinlock)(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + vol = pvoice == pcmp->pvoices[0] ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol); + pcmp->final_volume = 1; + } + } + return change; +} + +static const struct snd_kcontrol_new snd_gf1_pcm_volume_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = snd_gf1_pcm_volume_info, + .get = snd_gf1_pcm_volume_get, + .put = snd_gf1_pcm_volume_put +}; + +static const struct snd_kcontrol_new snd_gf1_pcm_volume_control1 = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "GPCM Playback Volume", + .info = snd_gf1_pcm_volume_info, + .get = snd_gf1_pcm_volume_get, + .put = snd_gf1_pcm_volume_put +}; + +static const struct snd_pcm_ops snd_gf1_pcm_playback_ops = { + .open = snd_gf1_pcm_playback_open, + .close = snd_gf1_pcm_playback_close, + .hw_params = snd_gf1_pcm_playback_hw_params, + .hw_free = snd_gf1_pcm_playback_hw_free, + .prepare = snd_gf1_pcm_playback_prepare, + .trigger = snd_gf1_pcm_playback_trigger, + .pointer = snd_gf1_pcm_playback_pointer, + .copy = snd_gf1_pcm_playback_copy, + .fill_silence = snd_gf1_pcm_playback_silence, +}; + +static const struct snd_pcm_ops snd_gf1_pcm_capture_ops = { + .open = snd_gf1_pcm_capture_open, + .close = snd_gf1_pcm_capture_close, + .hw_params = snd_gf1_pcm_capture_hw_params, + .prepare = snd_gf1_pcm_capture_prepare, + .trigger = snd_gf1_pcm_capture_trigger, + .pointer = snd_gf1_pcm_capture_pointer, +}; + +int snd_gf1_pcm_new(struct snd_gus_card *gus, int pcm_dev, int control_index) +{ + struct snd_card *card; + struct snd_kcontrol *kctl; + struct snd_pcm *pcm; + struct snd_pcm_substream *substream; + int capture, err; + + card = gus->card; + capture = !gus->interwave && !gus->ess_flag && !gus->ace_flag ? 1 : 0; + err = snd_pcm_new(card, + gus->interwave ? "AMD InterWave" : "GF1", + pcm_dev, + gus->gf1.pcm_channels / 2, + capture, + &pcm); + if (err < 0) + return err; + pcm->private_data = gus; + /* playback setup */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_gf1_pcm_playback_ops); + + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) + snd_pcm_set_managed_buffer(substream, SNDRV_DMA_TYPE_DEV, + card->dev, + 64*1024, gus->gf1.dma1 > 3 ? 128*1024 : 64*1024); + + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + if (capture) { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_gf1_pcm_capture_ops); + if (gus->gf1.dma2 == gus->gf1.dma1) + pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX; + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV, card->dev, + 64*1024, gus->gf1.dma2 > 3 ? 128*1024 : 64*1024); + } + strscpy(pcm->name, pcm->id); + if (gus->interwave) { + sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A'); + } + strcat(pcm->name, " (synth)"); + gus->pcm = pcm; + + if (gus->codec_flag) + kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control1, gus); + else + kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control, gus); + if (!kctl) + return -ENOMEM; + kctl->id.index = control_index; + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + + return 0; +} diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c new file mode 100644 index 000000000..998fa2457 --- /dev/null +++ b/sound/isa/gus/gus_reset.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/gus.h> + +/* + * ok.. default interrupt handlers... + */ + +static void snd_gf1_default_interrupt_handler_midi_out(struct snd_gus_card * gus) +{ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x20); +} + +static void snd_gf1_default_interrupt_handler_midi_in(struct snd_gus_card * gus) +{ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x80); +} + +static void snd_gf1_default_interrupt_handler_timer1(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~4); +} + +static void snd_gf1_default_interrupt_handler_timer2(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~8); +} + +static void snd_gf1_default_interrupt_handler_wave_and_volume(struct snd_gus_card * gus, struct snd_gus_voice * voice) +{ + snd_gf1_i_ctrl_stop(gus, 0x00); + snd_gf1_i_ctrl_stop(gus, 0x0d); +} + +static void snd_gf1_default_interrupt_handler_dma_write(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, 0x41, 0x00); +} + +static void snd_gf1_default_interrupt_handler_dma_read(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, 0x49, 0x00); +} + +void snd_gf1_set_default_handlers(struct snd_gus_card * gus, unsigned int what) +{ + if (what & SNDRV_GF1_HANDLER_MIDI_OUT) + gus->gf1.interrupt_handler_midi_out = snd_gf1_default_interrupt_handler_midi_out; + if (what & SNDRV_GF1_HANDLER_MIDI_IN) + gus->gf1.interrupt_handler_midi_in = snd_gf1_default_interrupt_handler_midi_in; + if (what & SNDRV_GF1_HANDLER_TIMER1) + gus->gf1.interrupt_handler_timer1 = snd_gf1_default_interrupt_handler_timer1; + if (what & SNDRV_GF1_HANDLER_TIMER2) + gus->gf1.interrupt_handler_timer2 = snd_gf1_default_interrupt_handler_timer2; + if (what & SNDRV_GF1_HANDLER_VOICE) { + struct snd_gus_voice *voice; + + voice = &gus->gf1.voices[what & 0xffff]; + voice->handler_wave = + voice->handler_volume = snd_gf1_default_interrupt_handler_wave_and_volume; + voice->handler_effect = NULL; + voice->volume_change = NULL; + } + if (what & SNDRV_GF1_HANDLER_DMA_WRITE) + gus->gf1.interrupt_handler_dma_write = snd_gf1_default_interrupt_handler_dma_write; + if (what & SNDRV_GF1_HANDLER_DMA_READ) + gus->gf1.interrupt_handler_dma_read = snd_gf1_default_interrupt_handler_dma_read; +} + +/* + + */ + +static void snd_gf1_clear_regs(struct snd_gus_card * gus) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + inb(GUSP(gus, IRQSTAT)); + snd_gf1_write8(gus, 0x41, 0); /* DRAM DMA Control Register */ + snd_gf1_write8(gus, 0x45, 0); /* Timer Control */ + snd_gf1_write8(gus, 0x49, 0); /* Sampling Control Register */ +} + +static void snd_gf1_look_regs(struct snd_gus_card * gus) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_look8(gus, 0x41); /* DRAM DMA Control Register */ + snd_gf1_look8(gus, 0x49); /* Sampling Control Register */ + inb(GUSP(gus, IRQSTAT)); + snd_gf1_read8(gus, 0x0f); /* IRQ Source Register */ +} + +/* + * put selected GF1 voices to initial stage... + */ + +void snd_gf1_smart_stop_voice(struct snd_gus_card * gus, unsigned short voice) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_select_voice(gus, voice); +#if 0 + dev_dbg(gus->card->dev, + " -%i- smart stop voice - volume = 0x%x\n", + voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME)); +#endif + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); +} + +void snd_gf1_stop_voice(struct snd_gus_card * gus, unsigned short voice) +{ + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_select_voice(gus, voice); +#if 0 + dev_dbg(gus->card->dev, + " -%i- stop voice - volume = 0x%x\n", + voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME)); +#endif + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0); +} + +static void snd_gf1_clear_voices(struct snd_gus_card * gus, unsigned short v_min, + unsigned short v_max) +{ + unsigned int daddr; + unsigned short i, w_16; + + daddr = gus->gf1.default_voice_address << 4; + for (i = v_min; i <= v_max; i++) { +#if 0 + if (gus->gf1.syn_voices) + gus->gf1.syn_voices[i].flags = ~VFLG_DYNAMIC; +#endif + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_select_voice(gus, i); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); /* Voice Control Register = voice stop */ + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); /* Volume Ramp Control Register = ramp off */ + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, gus->gf1.memory ? 0x02 : 0x82); /* Deactivate voice */ + w_16 = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & 0x04; + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, 0x400); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, daddr, w_16); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, daddr, w_16); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, 0); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, 0); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, daddr, w_16); + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, 7); + if (gus->gf1.enh_mode) { + snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, 0); + } + } +} + +void snd_gf1_stop_voices(struct snd_gus_card * gus, unsigned short v_min, unsigned short v_max) +{ + short i, ramp_ok; + unsigned short ramp_end; + + if (!in_interrupt()) { /* this can't be done in interrupt */ + for (i = v_min, ramp_ok = 0; i <= v_max; i++) { + guard(spinlock_irqsave)(&gus->reg_lock); + snd_gf1_select_voice(gus, i); + ramp_end = snd_gf1_read16(gus, 9) >> 8; + if (ramp_end > SNDRV_GF1_MIN_OFFSET) { + ramp_ok++; + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 20); /* ramp rate */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); /* ramp start */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, ramp_end); /* ramp end */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40); /* ramp down */ + if (gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40); + } + } + } + msleep_interruptible(50); + } + snd_gf1_clear_voices(gus, v_min, v_max); +} + +static void snd_gf1_alloc_voice_use(struct snd_gus_card * gus, + struct snd_gus_voice * pvoice, + int type, int client, int port) +{ + pvoice->use = 1; + switch (type) { + case SNDRV_GF1_VOICE_TYPE_PCM: + gus->gf1.pcm_alloc_voices++; + pvoice->pcm = 1; + break; + case SNDRV_GF1_VOICE_TYPE_SYNTH: + pvoice->synth = 1; + pvoice->client = client; + pvoice->port = port; + break; + case SNDRV_GF1_VOICE_TYPE_MIDI: + pvoice->midi = 1; + pvoice->client = client; + pvoice->port = port; + break; + } +} + +struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, int client, int port) +{ + struct snd_gus_voice *pvoice; + int idx; + + guard(spinlock_irqsave)(&gus->voice_alloc); + if (type == SNDRV_GF1_VOICE_TYPE_PCM) { + if (gus->gf1.pcm_alloc_voices >= gus->gf1.pcm_channels) + return NULL; + } + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (!pvoice->use) { + snd_gf1_alloc_voice_use(gus, pvoice, type, client, port); + return pvoice; + } + } + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (pvoice->midi && !pvoice->client) { + snd_gf1_clear_voices(gus, pvoice->number, pvoice->number); + snd_gf1_alloc_voice_use(gus, pvoice, type, client, port); + return pvoice; + } + } + return NULL; +} + +void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice) +{ + void (*private_free)(struct snd_gus_voice *voice); + + if (voice == NULL || !voice->use) + return; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | voice->number); + snd_gf1_clear_voices(gus, voice->number, voice->number); + scoped_guard(spinlock_irqsave, &gus->voice_alloc) { + private_free = voice->private_free; + voice->private_free = NULL; + voice->private_data = NULL; + if (voice->pcm) + gus->gf1.pcm_alloc_voices--; + voice->use = voice->pcm = 0; + voice->sample_ops = NULL; + } + if (private_free) + private_free(voice); +} + +static void snd_gf1_init_software_state(struct snd_gus_card *gus) +{ + unsigned int i; + + 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; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac); + + 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)); + } + + 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); + } + snd_gf1_clear_regs(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; + gus->gf1.default_voice_address += 1024; + } else { + gus->gf1.sw_lfo = 1; + } + + if (gus->gf1.memory > 0) + for (i = 0; i < 4; i++) + snd_gf1_poke(gus, gus->gf1.default_voice_address + i, 0); + snd_gf1_clear_regs(gus); + snd_gf1_clear_voices(gus, 0, 31); + snd_gf1_look_regs(gus); + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */ + 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); + } + while ((snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ) & 0xc0) != 0xc0); + + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + 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); + snd_gf1_mem_proc_init(gus); +#ifdef CONFIG_SND_DEBUG + snd_gus_irq_profile_init(gus); +#endif + +#if 0 + if (gus->pnp_flag) { + if (gus->chip.playback_fifo_size > 0) + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR, gus->chip.playback_fifo_block->ptr >> 8); + if (gus->chip.record_fifo_size > 0) + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR, gus->chip.record_fifo_block->ptr >> 8); + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_SIZE, gus->chip.interwave_fifo_reg); + } +#endif + + return 0; +} + +/* + * call this function only by shutdown of driver + */ + +int snd_gf1_stop(struct snd_gus_card * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); /* stop all timers */ + snd_gf1_stop_voices(gus, 0, 31); /* stop all voices */ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */ + snd_gf1_timers_done(gus); + snd_gf1_mem_done(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_tables.h b/sound/isa/gus/gus_tables.h new file mode 100644 index 000000000..ea0f007fa --- /dev/null +++ b/sound/isa/gus/gus_tables.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#define SNDRV_GF1_SCALE_TABLE_SIZE 128 +#define SNDRV_GF1_ATTEN_TABLE_SIZE 128 + +#ifdef __GUS_TABLES_ALLOC__ + +#if 0 + +unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE] = +{ + 8372, 8870, 9397, 9956, 10548, 11175, + 11840, 12544, 13290, 14080, 14917, 15804, + 16744, 17740, 18795, 19912, 21096, 22351, + 23680, 25088, 26580, 28160, 29834, 31609, + 33488, 35479, 37589, 39824, 42192, 44701, + 47359, 50175, 53159, 56320, 59669, 63217, + 66976, 70959, 75178, 79649, 84385, 89402, + 94719, 100351, 106318, 112640, 119338, 126434, + 133952, 141918, 150356, 159297, 168769, 178805, + 189437, 200702, 212636, 225280, 238676, 252868, + 267905, 283835, 300713, 318594, 337539, 357610, + 378874, 401403, 425272, 450560, 477352, 505737, + 535809, 567670, 601425, 637188, 675077, 715219, + 757749, 802807, 850544, 901120, 954703, 1011473, + 1071618, 1135340, 1202851, 1274376, 1350154, 1430439, + 1515497, 1605613, 1701088, 1802240, 1909407, 2022946, + 2143237, 2270680, 2405702, 2548752, 2700309, 2860878, + 3030994, 3211227, 3402176, 3604480, 3818814, 4045892, + 4286473, 4541360, 4811404, 5097505, 5400618, 5721755, + 6061989, 6422453, 6804352, 7208960, 7637627, 8091784, + 8572947, 9082720, 9622807, 10195009, 10801236, 11443511, + 12123977, 12844906 +}; + +#endif /* 0 */ + +unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE] = { + 4095 /* 0 */,1789 /* 1 */,1533 /* 2 */,1383 /* 3 */,1277 /* 4 */, + 1195 /* 5 */,1127 /* 6 */,1070 /* 7 */,1021 /* 8 */,978 /* 9 */, + 939 /* 10 */,903 /* 11 */,871 /* 12 */,842 /* 13 */,814 /* 14 */, + 789 /* 15 */,765 /* 16 */,743 /* 17 */,722 /* 18 */,702 /* 19 */, + 683 /* 20 */,665 /* 21 */,647 /* 22 */,631 /* 23 */,615 /* 24 */, + 600 /* 25 */,586 /* 26 */,572 /* 27 */,558 /* 28 */,545 /* 29 */, + 533 /* 30 */,521 /* 31 */,509 /* 32 */,498 /* 33 */,487 /* 34 */, + 476 /* 35 */,466 /* 36 */,455 /* 37 */,446 /* 38 */,436 /* 39 */, + 427 /* 40 */,418 /* 41 */,409 /* 42 */,400 /* 43 */,391 /* 44 */, + 383 /* 45 */,375 /* 46 */,367 /* 47 */,359 /* 48 */,352 /* 49 */, + 344 /* 50 */,337 /* 51 */,330 /* 52 */,323 /* 53 */,316 /* 54 */, + 309 /* 55 */,302 /* 56 */,296 /* 57 */,289 /* 58 */,283 /* 59 */, + 277 /* 60 */,271 /* 61 */,265 /* 62 */,259 /* 63 */,253 /* 64 */, + 247 /* 65 */,242 /* 66 */,236 /* 67 */,231 /* 68 */,225 /* 69 */, + 220 /* 70 */,215 /* 71 */,210 /* 72 */,205 /* 73 */,199 /* 74 */, + 195 /* 75 */,190 /* 76 */,185 /* 77 */,180 /* 78 */,175 /* 79 */, + 171 /* 80 */,166 /* 81 */,162 /* 82 */,157 /* 83 */,153 /* 84 */, + 148 /* 85 */,144 /* 86 */,140 /* 87 */,135 /* 88 */,131 /* 89 */, + 127 /* 90 */,123 /* 91 */,119 /* 92 */,115 /* 93 */,111 /* 94 */, + 107 /* 95 */,103 /* 96 */,100 /* 97 */,96 /* 98 */,92 /* 99 */, + 88 /* 100 */,85 /* 101 */,81 /* 102 */,77 /* 103 */,74 /* 104 */, + 70 /* 105 */,67 /* 106 */,63 /* 107 */,60 /* 108 */,56 /* 109 */, + 53 /* 110 */,50 /* 111 */,46 /* 112 */,43 /* 113 */,40 /* 114 */, + 37 /* 115 */,33 /* 116 */,30 /* 117 */,27 /* 118 */,24 /* 119 */, + 21 /* 120 */,18 /* 121 */,15 /* 122 */,12 /* 123 */,9 /* 124 */, + 6 /* 125 */,3 /* 126 */,0 /* 127 */, +}; + +#else + +extern unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE]; +extern unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE]; + +#endif diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c new file mode 100644 index 000000000..14dcde138 --- /dev/null +++ b/sound/isa/gus/gus_timer.c @@ -0,0 +1,194 @@ +// 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); + } +} diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c new file mode 100644 index 000000000..25057a5a8 --- /dev/null +++ b/sound/isa/gus/gus_uart.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Routines for the GF1 MIDI interface - like UART 6850 + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +static void snd_gf1_interrupt_midi_in(struct snd_gus_card * gus) +{ + int count; + unsigned char stat, byte; + __always_unused unsigned char data; + unsigned long flags; + + count = 10; + while (count) { + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + stat = snd_gf1_uart_stat(gus); + if (!(stat & 0x01)) { /* data in Rx FIFO? */ + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + count--; + continue; + } + count = 100; /* arm counter to new value */ + data = snd_gf1_uart_get(gus); + if (!(gus->gf1.uart_cmd & 0x80)) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + continue; + } + if (stat & 0x10) { /* framing error */ + gus->gf1.uart_framing++; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + continue; + } + byte = snd_gf1_uart_get(gus); + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + snd_rawmidi_receive(gus->midi_substream_input, &byte, 1); + if (stat & 0x20) { + gus->gf1.uart_overrun++; + } + } +} + +static void snd_gf1_interrupt_midi_out(struct snd_gus_card * gus) +{ + char byte; + + /* try unlock output */ + if (snd_gf1_uart_stat(gus) & 0x01) + snd_gf1_interrupt_midi_in(gus); + + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (snd_gf1_uart_stat(gus) & 0x02) { /* Tx FIFO free? */ + if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) { /* no other bytes or error */ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */ + } else { + snd_gf1_uart_put(gus, byte); + } + } +} + +static void snd_gf1_uart_reset(struct snd_gus_card * gus, int close) +{ + snd_gf1_uart_cmd(gus, 0x03); /* reset */ + if (!close && gus->uart_enable) { + udelay(160); + snd_gf1_uart_cmd(gus, 0x00); /* normal operations */ + } +} + +static int snd_gf1_uart_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_gus_card *gus; + + gus = substream->rmidi->private_data; + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (!(gus->gf1.uart_cmd & 0x80)) { /* input active? */ + snd_gf1_uart_reset(gus, 0); + } + gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out; + gus->midi_substream_output = substream; +#if 0 + dev_dbg(gus->card->dev, + "write init - cmd = 0x%x, stat = 0x%x\n", + gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); +#endif + return 0; +} + +static int snd_gf1_uart_input_open(struct snd_rawmidi_substream *substream) +{ + struct snd_gus_card *gus; + int i; + + gus = substream->rmidi->private_data; + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) { + snd_gf1_uart_reset(gus, 0); + } + gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in; + gus->midi_substream_input = substream; + if (gus->uart_enable) { + for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++) + snd_gf1_uart_get(gus); /* clean Rx */ + if (i >= 1000) + dev_err(gus->card->dev, "gus midi uart init read - cleanup error\n"); + } +#if 0 + dev_dbg(gus->card->dev, + "read init - enable = %i, cmd = 0x%x, stat = 0x%x\n", + gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); + dev_dbg(gus->card->dev, + "[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x (page = 0x%x)\n", + gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100), + inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102)); +#endif + return 0; +} + +static int snd_gf1_uart_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_gus_card *gus; + + gus = substream->rmidi->private_data; + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in) + snd_gf1_uart_reset(gus, 1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT); + gus->midi_substream_output = NULL; + return 0; +} + +static int snd_gf1_uart_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_gus_card *gus; + + gus = substream->rmidi->private_data; + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) + snd_gf1_uart_reset(gus, 1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN); + gus->midi_substream_input = NULL; + return 0; +} + +static void snd_gf1_uart_input_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_gus_card *gus; + + gus = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (up) { + if ((gus->gf1.uart_cmd & 0x80) == 0) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */ + } else { + if (gus->gf1.uart_cmd & 0x80) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */ + } +} + +static void snd_gf1_uart_output_trigger(struct snd_rawmidi_substream *substream, int up) +{ + unsigned long flags; + struct snd_gus_card *gus; + char byte; + int timeout; + + gus = substream->rmidi->private_data; + + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (up) { + if ((gus->gf1.uart_cmd & 0x20) == 0) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + /* wait for empty Rx - Tx is probably unlocked */ + timeout = 10000; + while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01); + /* Tx FIFO free? */ + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.uart_cmd & 0x20) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return; + } + if (snd_gf1_uart_stat(gus) & 0x02) { + if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return; + } + snd_gf1_uart_put(gus, byte); + } + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20); /* enable Tx interrupt */ + } + } else { + if (gus->gf1.uart_cmd & 0x20) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +} + +static const struct snd_rawmidi_ops snd_gf1_uart_output = +{ + .open = snd_gf1_uart_output_open, + .close = snd_gf1_uart_output_close, + .trigger = snd_gf1_uart_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_gf1_uart_input = +{ + .open = snd_gf1_uart_input_open, + .close = snd_gf1_uart_input_close, + .trigger = snd_gf1_uart_input_trigger, +}; + +int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device) +{ + struct snd_rawmidi *rmidi; + int err; + + err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi); + if (err < 0) + return err; + strscpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = gus; + 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); +} diff --git a/sound/isa/gus/gus_volume.c b/sound/isa/gus/gus_volume.c new file mode 100644 index 000000000..e72962175 --- /dev/null +++ b/sound/isa/gus/gus_volume.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/time.h> +#include <linux/export.h> +#include <sound/core.h> +#include <sound/gus.h> +#define __GUS_TABLES_ALLOC__ +#include "gus_tables.h" + +EXPORT_SYMBOL(snd_gf1_atten_table); /* for snd-gus-synth module */ + +unsigned short snd_gf1_lvol_to_gvol_raw(unsigned int vol) +{ + unsigned short e, m, tmp; + + if (vol > 65535) + vol = 65535; + tmp = vol; + e = 7; + if (tmp < 128) { + while (e > 0 && tmp < (1 << e)) + e--; + } else { + while (tmp > 255) { + tmp >>= 1; + e++; + } + } + m = vol - (1 << e); + if (m > 0) { + if (e > 8) + m >>= e - 8; + else if (e < 8) + m <<= 8 - e; + m &= 255; + } + return (e << 8) | m; +} + +#if 0 + +unsigned int snd_gf1_gvol_to_lvol_raw(unsigned short gf1_vol) +{ + unsigned int rvol; + unsigned short e, m; + + if (!gf1_vol) + return 0; + e = gf1_vol >> 8; + m = (unsigned char) gf1_vol; + rvol = 1 << e; + if (e > 8) + return rvol | (m << (e - 8)); + return rvol | (m >> (8 - e)); +} + +unsigned int snd_gf1_calc_ramp_rate(struct snd_gus_card * gus, + unsigned short start, + unsigned short end, + unsigned int us) +{ + static const unsigned char vol_rates[19] = + { + 23, 24, 26, 28, 29, 31, 32, 34, + 36, 37, 39, 40, 42, 44, 45, 47, + 49, 50, 52 + }; + unsigned short range, increment, value, i; + + start >>= 4; + end >>= 4; + if (start < end) + us /= end - start; + else + us /= start - end; + range = 4; + value = gus->gf1.enh_mode ? + vol_rates[0] : + vol_rates[gus->gf1.active_voices - 14]; + for (i = 0; i < 3; i++) { + if (us < value) { + range = i; + break; + } else + value <<= 3; + } + if (range == 4) { + range = 3; + increment = 1; + } else + increment = (value + (value >> 1)) / us; + return (range << 6) | (increment & 0x3f); +} + +#endif /* 0 */ + +unsigned short snd_gf1_translate_freq(struct snd_gus_card * gus, unsigned int freq16) +{ + freq16 >>= 3; + if (freq16 < 50) + freq16 = 50; + if (freq16 & 0xf8000000) { + freq16 = ~0xf8000000; + dev_err(gus->card->dev, "%s: overflow - freq = 0x%x\n", + __func__, freq16); + } + return ((freq16 << 9) + (gus->gf1.playback_freq >> 1)) / gus->gf1.playback_freq; +} + +#if 0 + +short snd_gf1_compute_vibrato(short cents, unsigned short fc_register) +{ + static const short vibrato_table[] = + { + 0, 0, 32, 592, 61, 1175, 93, 1808, + 124, 2433, 152, 3007, 182, 3632, 213, 4290, + 241, 4834, 255, 5200 + }; + + long depth; + const short *vi1, *vi2; + short pcents, v1; + + pcents = cents < 0 ? -cents : cents; + for (vi1 = vibrato_table, vi2 = vi1 + 2; pcents > *vi2; vi1 = vi2, vi2 += 2); + v1 = *(vi1 + 1); + /* The FC table above is a list of pairs. The first number in the pair */ + /* is the cents index from 0-255 cents, and the second number in the */ + /* pair is the FC adjustment needed to change the pitch by the indexed */ + /* number of cents. The table was created for an FC of 32768. */ + /* The following expression does a linear interpolation against the */ + /* approximated log curve in the table above, and then scales the number */ + /* by the FC before the LFO. This calculation also adjusts the output */ + /* value to produce the appropriate depth for the hardware. The depth */ + /* is 2 * desired FC + 1. */ + depth = (((int) (*(vi2 + 1) - *vi1) * (pcents - *vi1) / (*vi2 - *vi1)) + v1) * fc_register >> 14; + if (depth) + depth++; + if (depth > 255) + depth = 255; + return cents < 0 ? -(short) depth : (short) depth; +} + +unsigned short snd_gf1_compute_pitchbend(unsigned short pitchbend, unsigned short sens) +{ + static const long log_table[] = {1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933}; + int wheel, sensitivity; + unsigned int mantissa, f1, f2; + unsigned short semitones, f1_index, f2_index, f1_power, f2_power; + char bend_down = 0; + int bend; + + if (!sens) + return 1024; + wheel = (int) pitchbend - 8192; + sensitivity = ((int) sens * wheel) / 128; + if (sensitivity < 0) { + bend_down = 1; + sensitivity = -sensitivity; + } + semitones = (unsigned int) (sensitivity >> 13); + mantissa = sensitivity % 8192; + f1_index = semitones % 12; + f2_index = (semitones + 1) % 12; + f1_power = semitones / 12; + f2_power = (semitones + 1) / 12; + f1 = log_table[f1_index] << f1_power; + f2 = log_table[f2_index] << f2_power; + bend = (int) ((((f2 - f1) * mantissa) >> 13) + f1); + if (bend_down) + bend = 1048576L / bend; + return bend; +} + +unsigned short snd_gf1_compute_freq(unsigned int freq, + unsigned int rate, + unsigned short mix_rate) +{ + unsigned int fc; + int scale = 0; + + while (freq >= 4194304L) { + scale++; + freq >>= 1; + } + fc = (freq << 10) / rate; + if (fc > 97391L) { + fc = 97391; + pr_err("patch: (1) fc frequency overflow - %u\n", fc); + } + fc = (fc * 44100UL) / mix_rate; + while (scale--) + fc <<= 1; + if (fc > 65535L) { + fc = 65535; + pr_err("patch: (2) fc frequency overflow - %u\n", fc); + } + return (unsigned short) fc; +} + +#endif /* 0 */ diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c new file mode 100644 index 000000000..363c819ce --- /dev/null +++ b/sound/isa/gus/gusclassic.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Gravis UltraSound Classic soundcard + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/isa.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/module.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/gus.h> +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +#define CRD_NAME "Gravis UltraSound Classic" +#define DEV_NAME "gusclassic" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x230,0x240,0x250,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for " CRD_NAME " driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for " CRD_NAME " driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver."); + +static int snd_gusclassic_match(struct device *dev, unsigned int n) +{ + return enable[n]; +} + +static int snd_gusclassic_create(struct snd_card *card, + struct device *dev, unsigned int n, + struct snd_gus_card **rgus) +{ + static const long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260}; + static const int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1}; + static const int possible_dmas[] = {5, 6, 7, 1, 3, -1}; + + int i, error; + + if (irq[n] == SNDRV_AUTO_IRQ) { + irq[n] = snd_legacy_find_free_irq(possible_irqs); + if (irq[n] < 0) { + dev_err(dev, "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma1[n] == SNDRV_AUTO_DMA) { + dma1[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma1[n] < 0) { + dev_err(dev, "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (dma2[n] == SNDRV_AUTO_DMA) { + dma2[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma2[n] < 0) { + dev_err(dev, "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if (port[n] != SNDRV_AUTO_PORT) + return snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n], + 0, channels[n], pcm_channels[n], 0, rgus); + + i = 0; + do { + port[n] = possible_ports[i]; + error = snd_gus_create(card, port[n], irq[n], dma1[n], dma2[n], + 0, channels[n], pcm_channels[n], 0, rgus); + } while (error < 0 && ++i < ARRAY_SIZE(possible_ports)); + + return error; +} + +static int snd_gusclassic_detect(struct snd_gus_card *gus) +{ + unsigned char d; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 0) { + dev_dbg(gus->card->dev, "[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 1) { + dev_dbg(gus->card->dev, "[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + return 0; +} + +static int snd_gusclassic_probe(struct device *dev, unsigned int n) +{ + struct snd_card *card; + struct snd_gus_card *gus; + int error; + + error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, 0, &card); + if (error < 0) + return error; + + if (pcm_channels[n] < 2) + pcm_channels[n] = 2; + + 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) + return error; + + gus->joystick_dac = joystick_dac[n]; + + error = snd_gus_initialize(gus); + if (error < 0) + return error; + + error = -ENODEV; + if (gus->max_flag || gus->ess_flag) { + dev_err(dev, "GUS Classic or ACE soundcard was " + "not detected at 0x%lx\n", gus->gf1.port); + return error; + } + + error = snd_gf1_new_mixer(gus); + if (error < 0) + return error; + + error = snd_gf1_pcm_new(gus, 0, 0); + if (error < 0) + return error; + + if (!gus->ace_flag) { + error = snd_gf1_rawmidi_new(gus, 0); + if (error < 0) + return error; + } + + sprintf(card->longname + strlen(card->longname), + " at 0x%lx, irq %d, dma %d", + gus->gf1.port, gus->gf1.irq, gus->gf1.dma1); + + if (gus->gf1.dma2 >= 0) + sprintf(card->longname + strlen(card->longname), + "&%d", gus->gf1.dma2); + + error = snd_card_register(card); + if (error < 0) + return error; + + dev_set_drvdata(dev, card); + 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, +#ifdef CONFIG_PM + .suspend = snd_gusclassic_suspend, + .resume = snd_gusclassic_resume, +#endif + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_gusclassic_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c new file mode 100644 index 000000000..098473174 --- /dev/null +++ b/sound/isa/gus/gusextreme.c @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Gravis UltraSound Extreme soundcards + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/isa.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/module.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/es1688.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +#define CRD_NAME "Gravis UltraSound Extreme" +#define DEV_NAME "gusextreme" + +MODULE_DESCRIPTION(CRD_NAME); +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */ +static long gf1_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x210,0x220,0x230,0x240,0x250,0x260,0x270 */ +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x300,0x310,0x320 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int gf1_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +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); +MODULE_PARM_DESC(id, "ID string for " CRD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CRD_NAME " soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " CRD_NAME " driver."); +module_param_hw_array(gf1_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(gf1_port, "GF1 port # for " CRD_NAME " driver (optional)."); +module_param_hw_array(mpu_port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " CRD_NAME " driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " CRD_NAME " driver."); +module_param_hw_array(mpu_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " CRD_NAME " driver."); +module_param_hw_array(gf1_irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(gf1_irq, "GF1 IRQ # for " CRD_NAME " driver."); +module_param_hw_array(dma8, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for " CRD_NAME " driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "GF1 DMA # for " CRD_NAME " driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for " CRD_NAME " driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "GF1 channels for " CRD_NAME " driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for " CRD_NAME " driver."); + +static int snd_gusextreme_match(struct device *dev, unsigned int n) +{ + return enable[n]; +} + +static int snd_gusextreme_es1688_create(struct snd_card *card, + struct snd_es1688 *chip, + struct device *dev, unsigned int n) +{ + static const long possible_ports[] = {0x220, 0x240, 0x260}; + static const int possible_irqs[] = {5, 9, 10, 7, -1}; + static const int possible_dmas[] = {1, 3, 0, -1}; + + int i, error; + + if (irq[n] == SNDRV_AUTO_IRQ) { + irq[n] = snd_legacy_find_free_irq(possible_irqs); + if (irq[n] < 0) { + dev_err(dev, "unable to find a free IRQ for ES1688\n"); + return -EBUSY; + } + } + if (dma8[n] == SNDRV_AUTO_DMA) { + dma8[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma8[n] < 0) { + dev_err(dev, "unable to find a free DMA for ES1688\n"); + return -EBUSY; + } + } + + if (port[n] != SNDRV_AUTO_PORT) + return snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688); + + i = 0; + do { + port[n] = possible_ports[i]; + error = snd_es1688_create(card, chip, port[n], mpu_port[n], + irq[n], mpu_irq[n], dma8[n], ES1688_HW_1688); + } while (error < 0 && ++i < ARRAY_SIZE(possible_ports)); + + return error; +} + +static int snd_gusextreme_gus_card_create(struct snd_card *card, + struct device *dev, unsigned int n, + struct snd_gus_card **rgus) +{ + static const int possible_irqs[] = {11, 12, 15, 9, 5, 7, 3, -1}; + static const int possible_dmas[] = {5, 6, 7, 3, 1, -1}; + + if (gf1_irq[n] == SNDRV_AUTO_IRQ) { + gf1_irq[n] = snd_legacy_find_free_irq(possible_irqs); + if (gf1_irq[n] < 0) { + dev_err(dev, "unable to find a free IRQ for GF1\n"); + return -EBUSY; + } + } + if (dma1[n] == SNDRV_AUTO_DMA) { + dma1[n] = snd_legacy_find_free_dma(possible_dmas); + if (dma1[n] < 0) { + dev_err(dev, "unable to find a free DMA for GF1\n"); + return -EBUSY; + } + } + return snd_gus_create(card, gf1_port[n], gf1_irq[n], dma1[n], -1, + 0, channels[n], pcm_channels[n], 0, rgus); +} + +static void snd_gusextreme_enable_gf1(struct snd_gus_card *gus, + struct snd_es1688 *es1688) +{ + /* + * 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] + * + * ULTRINIT.EXE: + * 0x230 = 0,2,3 + * 0x240 = 2,0,1 + * 0x250 = 2,0,3 + * 0x260 = 2,2,1 + */ + + scoped_guard(spinlock_irqsave, &es1688->mixer_lock) { + snd_es1688_mixer_write(es1688, 0x40, 0x0b); /* don't change!!! */ + } + + scoped_guard(spinlock_irqsave, &es1688->reg_lock) { + outb(gus->gf1.port & 0x040 ? 2 : 0, ES1688P(es1688, INIT1)); + outb(0, 0x201); + outb(gus->gf1.port & 0x020 ? 2 : 0, ES1688P(es1688, INIT1)); + 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 */ + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 0) { + dev_dbg(gus->card->dev, "[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -EIO; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 1) { + dev_dbg(gus->card->dev, "[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -EIO; + } + + return 0; +} + +static int snd_gusextreme_mixer(struct snd_card *card) +{ + struct snd_ctl_elem_id id1, id2; + int error; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + + /* reassign AUX to SYNTHESIZER */ + strscpy(id1.name, "Aux Playback Volume"); + strscpy(id2.name, "Synth Playback Volume"); + error = snd_ctl_rename_id(card, &id1, &id2); + if (error < 0) + return error; + + /* reassign Master Playback Switch to Synth Playback Switch */ + strscpy(id1.name, "Master Playback Switch"); + strscpy(id2.name, "Synth Playback Switch"); + error = snd_ctl_rename_id(card, &id1, &id2); + if (error < 0) + return error; + + return 0; +} + +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(*gusextreme), &card); + if (error < 0) + return error; + + gusextreme = card->private_data; + es1688 = &gusextreme->es1688; + + if (mpu_port[n] == SNDRV_AUTO_PORT) + mpu_port[n] = 0; + + if (mpu_irq[n] > 15) + mpu_irq[n] = -1; + + error = snd_gusextreme_es1688_create(card, es1688, dev, n); + if (error < 0) + return error; + + if (gf1_port[n] < 0) + gf1_port[n] = es1688->port + 0x20; + + 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) + return error; + + gus->joystick_dac = joystick_dac[n]; + + error = snd_gus_initialize(gus); + if (error < 0) + return error; + + error = -ENODEV; + if (!gus->ess_flag) { + dev_err(dev, "GUS Extreme soundcard was not " + "detected at 0x%lx\n", gus->gf1.port); + return error; + } + gus->codec_flag = 1; + + error = snd_es1688_pcm(card, es1688, 0); + if (error < 0) + return error; + + error = snd_es1688_mixer(card, es1688); + if (error < 0) + return error; + + snd_component_add(card, "ES1688"); + + if (pcm_channels[n] > 0) { + error = snd_gf1_pcm_new(gus, 1, 1); + if (error < 0) + return error; + } + + error = snd_gf1_new_mixer(gus); + if (error < 0) + return error; + + error = snd_gusextreme_mixer(card); + if (error < 0) + return error; + + if (snd_opl3_create(card, es1688->port, es1688->port + 2, + OPL3_HW_OPL3, 0, &opl3) < 0) + dev_warn(dev, "opl3 not detected at 0x%lx\n", es1688->port); + else { + error = snd_opl3_hwdep_new(opl3, 0, 2, NULL); + if (error < 0) + return error; + } + + if (es1688->mpu_port >= 0x300) { + error = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688, + es1688->mpu_port, 0, mpu_irq[n], NULL); + if (error < 0) + return error; + } + + sprintf(card->longname, "Gravis UltraSound Extreme at 0x%lx, " + "irq %i&%i, dma %i&%i", es1688->port, + gus->gf1.irq, es1688->irq, gus->gf1.dma1, es1688->dma8); + + error = snd_card_register(card); + if (error < 0) + return error; + + dev_set_drvdata(dev, card); + 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, +#ifdef CONFIG_PM + .suspend = snd_gusextreme_suspend, + .resume = snd_gusextreme_resume, +#endif + .driver = { + .name = DEV_NAME + } +}; + +module_isa_driver(snd_gusextreme_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c new file mode 100644 index 000000000..f1fd7ff21 --- /dev/null +++ b/sound/isa/gus/gusmax.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Gravis UltraSound MAX soundcard + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/isa.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/module.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/wss.h> +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Gravis UltraSound MAX"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x230,0x240,0x250,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for GUS MAX soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for GUS MAX soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable GUS MAX soundcard."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for GUS MAX driver."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for GUS MAX driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for GUS MAX driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for GUS MAX driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS MAX driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "Used GF1 channels for GUS MAX driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS MAX driver."); + +struct snd_gusmax { + int irq; + struct snd_card *card; + struct snd_gus_card *gus; + struct snd_wss *wss; + unsigned short gus_status_reg; + unsigned short pcm_status_reg; +}; + +static int snd_gusmax_detect(struct snd_gus_card *gus) +{ + unsigned char d; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 0) { + dev_dbg(gus->card->dev, "[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 1) { + dev_dbg(gus->card->dev, "[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + + return 0; +} + +static irqreturn_t snd_gusmax_interrupt(int irq, void *dev_id) +{ + struct snd_gusmax *maxcard = dev_id; + int loop, max = 5; + int handled = 0; + + do { + loop = 0; + if (inb(maxcard->gus_status_reg)) { + handled = 1; + snd_gus_interrupt(irq, maxcard->gus); + loop++; + } + if (inb(maxcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */ + handled = 1; + snd_wss_interrupt(irq, maxcard->wss); + loop++; + } + } while (loop && --max > 0); + return IRQ_RETVAL(handled); +} + +static void snd_gusmax_init(int dev, struct snd_card *card, + struct snd_gus_card *gus) +{ + gus->equal_irq = 1; + gus->codec_flag = 1; + gus->joystick_dac = joystick_dac[dev]; + /* init control register */ + gus->max_cntrl_val = (gus->gf1.port >> 4) & 0x0f; + if (gus->gf1.dma1 > 3) + gus->max_cntrl_val |= 0x10; + if (gus->gf1.dma2 > 3) + gus->max_cntrl_val |= 0x20; + gus->max_cntrl_val |= 0x40; + outb(gus->max_cntrl_val, GUSP(gus, MAXCNTRLPORT)); +} + +static int snd_gusmax_mixer(struct snd_wss *chip) +{ + struct snd_card *card = chip->card; + struct snd_ctl_elem_id id1, id2; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUXA to SYNTHESIZER */ + strscpy(id1.name, "Aux Playback Switch"); + strscpy(id2.name, "Synth Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + strscpy(id1.name, "Aux Playback Volume"); + strscpy(id2.name, "Synth Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + /* reassign AUXB to CD */ + strscpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strscpy(id2.name, "CD Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + strscpy(id1.name, "Aux Playback Volume"); + strscpy(id2.name, "CD Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; +#if 0 + /* reassign Mono Input to MIC */ + if (snd_mixer_group_rename(mixer, + SNDRV_MIXER_IN_MONO, 0, + SNDRV_MIXER_IN_MIC, 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + SNDRV_MIXER_IN_MONO, 0, SNDRV_MIXER_ETYPE_INPUT, + SNDRV_MIXER_IN_MIC, 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + "Mono Capture Volume", 0, SNDRV_MIXER_ETYPE_VOLUME1, + "Mic Capture Volume", 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + "Mono Capture Switch", 0, SNDRV_MIXER_ETYPE_SWITCH1, + "Mic Capture Switch", 0) < 0) + goto __error; +#endif + return 0; +} + +static int snd_gusmax_match(struct device *pdev, unsigned int dev) +{ + return enable[dev]; +} + +static int snd_gusmax_probe(struct device *pdev, unsigned int dev) +{ + static const int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1}; + static const int possible_dmas[] = {5, 6, 7, 1, 3, -1}; + int xirq, xdma1, xdma2, err; + struct snd_card *card; + struct snd_gus_card *gus = NULL; + struct snd_wss *wss; + struct snd_gusmax *maxcard; + + err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_gusmax), &card); + if (err < 0) + return err; + maxcard = card->private_data; + maxcard->card = card; + maxcard->irq = -1; + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + xirq = snd_legacy_find_free_irq(possible_irqs); + if (xirq < 0) { + dev_err(pdev, "unable to find a free IRQ\n"); + return -EBUSY; + } + } + xdma1 = dma1[dev]; + if (xdma1 == SNDRV_AUTO_DMA) { + xdma1 = snd_legacy_find_free_dma(possible_dmas); + if (xdma1 < 0) { + dev_err(pdev, "unable to find a free DMA1\n"); + return -EBUSY; + } + } + xdma2 = dma2[dev]; + if (xdma2 == SNDRV_AUTO_DMA) { + xdma2 = snd_legacy_find_free_dma(possible_dmas); + if (xdma2 < 0) { + dev_err(pdev, "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if (port[dev] != SNDRV_AUTO_PORT) { + err = snd_gus_create(card, + port[dev], + -xirq, xdma1, xdma2, + 0, channels[dev], + pcm_channels[dev], + 0, &gus); + } else { + static const unsigned long possible_ports[] = { + 0x220, 0x230, 0x240, 0x250, 0x260 + }; + int i; + for (i = 0; i < ARRAY_SIZE(possible_ports); i++) { + err = snd_gus_create(card, + possible_ports[i], + -xirq, xdma1, xdma2, + 0, channels[dev], + pcm_channels[dev], + 0, &gus); + if (err >= 0) { + port[dev] = possible_ports[i]; + break; + } + } + } + if (err < 0) + return err; + + err = snd_gusmax_detect(gus); + if (err < 0) + return err; + + maxcard->gus_status_reg = gus->gf1.reg_irqstat; + maxcard->pcm_status_reg = gus->gf1.port + 0x10c + 2; + snd_gusmax_init(dev, card, gus); + err = snd_gus_initialize(gus); + if (err < 0) + return err; + + if (!gus->max_flag) { + dev_err(pdev, "GUS MAX soundcard was not detected at 0x%lx\n", gus->gf1.port); + return -ENODEV; + } + + if (devm_request_irq(card->dev, xirq, snd_gusmax_interrupt, 0, + "GUS MAX", (void *)maxcard)) { + dev_err(pdev, "unable to grab IRQ %d\n", xirq); + return -EBUSY; + } + maxcard->irq = xirq; + card->sync_irq = maxcard->irq; + + err = snd_wss_create(card, + gus->gf1.port + 0x10c, -1, xirq, + xdma2 < 0 ? xdma1 : xdma2, xdma1, + WSS_HW_DETECT, + WSS_HWSHARE_IRQ | + WSS_HWSHARE_DMA1 | + WSS_HWSHARE_DMA2, + &wss); + if (err < 0) + return err; + + err = snd_wss_pcm(wss, 0); + if (err < 0) + return err; + + err = snd_wss_mixer(wss); + if (err < 0) + return err; + + err = snd_wss_timer(wss, 2); + if (err < 0) + return err; + + if (pcm_channels[dev] > 0) { + err = snd_gf1_pcm_new(gus, 1, 1); + if (err < 0) + return err; + } + err = snd_gusmax_mixer(wss); + if (err < 0) + return err; + + err = snd_gf1_rawmidi_new(gus, 0); + if (err < 0) + return err; + + sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %i, dma %i", gus->gf1.port, xirq, xdma1); + if (xdma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%i", xdma2); + + err = snd_card_register(card); + if (err < 0) + return err; + + maxcard->gus = gus; + maxcard->wss = wss; + + dev_set_drvdata(pdev, card); + 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, +#ifdef CONFIG_PM + .suspend = snd_gusmax_suspend, + .resume = snd_gusmax_resume, +#endif + .driver = { + .name = DEV_NAME + }, +}; + +module_isa_driver(snd_gusmax_driver, SNDRV_CARDS); diff --git a/sound/isa/gus/interwave-stb.c b/sound/isa/gus/interwave-stb.c new file mode 100644 index 000000000..9b3468c8b --- /dev/null +++ b/sound/isa/gus/interwave-stb.c @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: GPL-2.0 +#define SNDRV_STB +#include "interwave.c" diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c new file mode 100644 index 000000000..115f900fa --- /dev/null +++ b/sound/isa/gus/interwave.c @@ -0,0 +1,1038 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for AMD InterWave soundcard + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * + * 1999/07/22 Erik Inge Bolso <knan@mo.himolde.no> + * * mixer group handlers + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/isa.h> +#include <linux/delay.h> +#include <linux/pnp.h> +#include <linux/module.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/wss.h> +#ifdef SNDRV_STB +#include <sound/tea6330t.h> +#endif +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_LICENSE("GPL"); +#ifndef SNDRV_STB +MODULE_DESCRIPTION("AMD InterWave"); +#else +MODULE_DESCRIPTION("AMD InterWave STB with TEA6330T"); +#endif + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +#ifdef CONFIG_PNP +static bool isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x210,0x220,0x230,0x240,0x250,0x260 */ +#ifdef SNDRV_STB +static long port_tc[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x350,0x360,0x370,0x380 */ +#endif +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int midi[SNDRV_CARDS]; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +static int effect[SNDRV_CARDS]; + +#ifdef SNDRV_STB +#define INTERWAVE_DRIVER "snd_interwave_stb" +#define INTERWAVE_PNP_DRIVER "interwave-stb" +#else +#define INTERWAVE_DRIVER "snd_interwave" +#define INTERWAVE_PNP_DRIVER "interwave" +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for InterWave soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for InterWave soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable InterWave soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard."); +#endif +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for InterWave driver."); +#ifdef SNDRV_STB +module_param_hw_array(port_tc, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver."); +#endif +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for InterWave driver."); +module_param_hw_array(dma1, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for InterWave driver."); +module_param_hw_array(dma2, int, dma, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for InterWave driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver."); +module_param_array(midi, int, NULL, 0444); +MODULE_PARM_DESC(midi, "MIDI UART enable for InterWave driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for InterWave driver."); +module_param_array(effect, int, NULL, 0444); +MODULE_PARM_DESC(effect, "Effects enable for InterWave driver."); + +struct snd_interwave { + int irq; + struct snd_card *card; + struct snd_gus_card *gus; + struct snd_wss *wss; +#ifdef SNDRV_STB + struct snd_i2c_bus *i2c_bus; + struct resource *i2c_res; +#endif + unsigned short gus_status_reg; + unsigned short pcm_status_reg; +#ifdef CONFIG_PNP + struct pnp_dev *dev; +#ifdef SNDRV_STB + struct pnp_dev *devtc; +#endif +#endif +}; + + +#ifdef CONFIG_PNP +static int isa_registered __ro_after_init; +static int pnp_registered __ro_after_init; + +static const struct pnp_card_device_id snd_interwave_pnpids[] = { +#ifndef SNDRV_STB + /* Gravis UltraSound Plug & Play */ + { .id = "GRV0001", .devs = { { .id = "GRV0000" } } }, + /* STB SoundRage32 */ + { .id = "STB011a", .devs = { { .id = "STB0010" } } }, + /* MED3210 */ + { .id = "DXP3201", .devs = { { .id = "DXP0010" } } }, + /* Dynasonic Pro */ + /* This device also have CDC1117:DynaSonix Pro Audio Effects Processor */ + { .id = "CDC1111", .devs = { { .id = "CDC1112" } } }, + /* Panasonic PCA761AW Audio Card */ + { .id = "ADV55ff", .devs = { { .id = "ADV0010" } } }, + /* InterWave STB without TEA6330T */ + { .id = "ADV550a", .devs = { { .id = "ADV0010" } } }, +#else + /* InterWave STB with TEA6330T */ + { .id = "ADV550a", .devs = { { .id = "ADV0010" }, { .id = "ADV0015" } } }, +#endif + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_interwave_pnpids); + +#endif /* CONFIG_PNP */ + + +#ifdef SNDRV_STB +static void snd_interwave_i2c_setlines(struct snd_i2c_bus *bus, int ctrl, int data) +{ + unsigned long port = bus->private_value; + +#if 0 + dev_dbg(bus->card->dev, "i2c_setlines - 0x%lx <- %i,%i\n", port, ctrl, data); +#endif + outb((data << 1) | ctrl, port); + udelay(10); +} + +static int snd_interwave_i2c_getclockline(struct snd_i2c_bus *bus) +{ + unsigned long port = bus->private_value; + unsigned char res; + + res = inb(port) & 1; +#if 0 + dev_dbg(bus->card->dev, "i2c_getclockline - 0x%lx -> %i\n", port, res); +#endif + return res; +} + +static int snd_interwave_i2c_getdataline(struct snd_i2c_bus *bus, int ack) +{ + unsigned long port = bus->private_value; + unsigned char res; + + if (ack) + udelay(10); + res = (inb(port) & 2) >> 1; +#if 0 + dev_dbg(bus->card->dev, "i2c_getdataline - 0x%lx -> %i\n", port, res); +#endif + return res; +} + +static struct snd_i2c_bit_ops snd_interwave_i2c_bit_ops = { + .setlines = snd_interwave_i2c_setlines, + .getclock = snd_interwave_i2c_getclockline, + .getdata = snd_interwave_i2c_getdataline, +}; + +static int snd_interwave_detect_stb(struct snd_interwave *iwcard, + struct snd_gus_card *gus, int dev, + struct snd_i2c_bus **rbus) +{ + unsigned long port; + struct snd_i2c_bus *bus; + struct snd_card *card = iwcard->card; + char name[32]; + int err; + + *rbus = NULL; + port = port_tc[dev]; + if (port == SNDRV_AUTO_PORT) { + port = 0x350; + if (gus->gf1.port == 0x250) { + port = 0x360; + } + while (port <= 0x380) { + iwcard->i2c_res = devm_request_region(card->dev, port, 1, + "InterWave (I2C bus)"); + if (iwcard->i2c_res) + break; + port += 0x10; + } + } else { + iwcard->i2c_res = devm_request_region(card->dev, port, 1, + "InterWave (I2C bus)"); + } + if (iwcard->i2c_res == NULL) { + dev_err(card->dev, "interwave: can't grab i2c bus port\n"); + return -ENODEV; + } + + sprintf(name, "InterWave-%i", card->number); + err = snd_i2c_bus_create(card, name, NULL, &bus); + if (err < 0) + return err; + bus->private_value = port; + bus->hw_ops.bit = &snd_interwave_i2c_bit_ops; + err = snd_tea6330t_detect(bus, 0); + if (err < 0) + return err; + *rbus = bus; + return 0; +} +#endif + +static int snd_interwave_detect(struct snd_interwave *iwcard, + struct snd_gus_card *gus, + int dev +#ifdef SNDRV_STB + , struct snd_i2c_bus **rbus +#endif + ) +{ + unsigned char rev1, rev2; + int d; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 0) { + dev_dbg(gus->card->dev, "[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); + d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET); + if ((d & 0x07) != 1) { + dev_dbg(gus->card->dev, "[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + rev1 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, ~rev1); + rev2 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, rev1); + } + dev_dbg(gus->card->dev, + "[0x%lx] InterWave check - rev1=0x%x, rev2=0x%x\n", + gus->gf1.port, rev1, rev2); + if ((rev1 & 0xf0) == (rev2 & 0xf0) && + (rev1 & 0x0f) != (rev2 & 0x0f)) { + dev_dbg(gus->card->dev, + "[0x%lx] InterWave check - passed\n", gus->gf1.port); + gus->interwave = 1; + strscpy(gus->card->shortname, "AMD InterWave"); + gus->revision = rev1 >> 4; +#ifndef SNDRV_STB + return 0; /* ok.. We have an InterWave board */ +#else + return snd_interwave_detect_stb(iwcard, gus, dev, rbus); +#endif + } + dev_dbg(gus->card->dev, "[0x%lx] InterWave check - failed\n", gus->gf1.port); + return -ENODEV; +} + +static irqreturn_t snd_interwave_interrupt(int irq, void *dev_id) +{ + struct snd_interwave *iwcard = dev_id; + int loop, max = 5; + int handled = 0; + + do { + loop = 0; + if (inb(iwcard->gus_status_reg)) { + handled = 1; + snd_gus_interrupt(irq, iwcard->gus); + loop++; + } + if (inb(iwcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */ + handled = 1; + snd_wss_interrupt(irq, iwcard->wss); + loop++; + } + } while (loop && --max > 0); + return IRQ_RETVAL(handled); +} + +static void snd_interwave_reset(struct snd_gus_card *gus) +{ + snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x00); + udelay(160); + snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x01); + udelay(160); +} + +static void snd_interwave_bank_sizes(struct snd_gus_card *gus, int *sizes) +{ + unsigned int idx; + unsigned int local; + unsigned char d; + + for (idx = 0; idx < 4; idx++) { + sizes[idx] = 0; + d = 0x55; + for (local = idx << 22; + local < (idx << 22) + 0x400000; + local += 0x40000, d++) { + snd_gf1_poke(gus, local, d); + snd_gf1_poke(gus, local + 1, d + 1); +#if 0 + dev_dbg(gus->card->dev, "d = 0x%x, local = 0x%x, " + "local + 1 = 0x%x, idx << 22 = 0x%x\n", + d, + snd_gf1_peek(gus, local), + snd_gf1_peek(gus, local + 1), + snd_gf1_peek(gus, idx << 22)); +#endif + if (snd_gf1_peek(gus, local) != d || + snd_gf1_peek(gus, local + 1) != d + 1 || + snd_gf1_peek(gus, idx << 22) != 0x55) + break; + sizes[idx]++; + } + } +#if 0 + dev_dbg(gus->card->dev, "sizes: %i %i %i %i\n", + sizes[0], sizes[1], sizes[2], sizes[3]); +#endif +} + +struct rom_hdr { + /* 000 */ unsigned char iwave[8]; + /* 008 */ unsigned char rom_hdr_revision; + /* 009 */ unsigned char series_number; + /* 010 */ unsigned char series_name[16]; + /* 026 */ unsigned char date[10]; + /* 036 */ unsigned short vendor_revision_major; + /* 038 */ unsigned short vendor_revision_minor; + /* 040 */ unsigned int rom_size; + /* 044 */ unsigned char copyright[128]; + /* 172 */ unsigned char vendor_name[64]; + /* 236 */ unsigned char rom_description[128]; + /* 364 */ unsigned char pad[147]; + /* 511 */ unsigned char csum; +}; + +static const unsigned int snd_interwave_memory_configs[] = { + 0x00000001, 0x00000101, 0x01010101, 0x00000401, + 0x04040401, 0x00040101, 0x04040101, 0x00000004, + 0x00000404, 0x04040404, 0x00000010, 0x00001010, + 0x10101010 +}; + +static int snd_interwave_find_memory_config(unsigned int lmct) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_interwave_memory_configs); i++) { + if (lmct == snd_interwave_memory_configs[i]) + return i; + } + + return -EINVAL; +} + +static void snd_interwave_detect_memory(struct snd_gus_card *gus) +{ + int bank_pos, pages; + unsigned int i, lmct; + int lmc_cfg; + int psizes[4]; + unsigned char iwave[8]; + unsigned char csum; + + snd_interwave_reset(gus); + snd_gf1_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); /* enhanced mode */ + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); /* DRAM I/O cycles selected */ + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff10) | 0x004c); + /* ok.. simple test of memory size */ + pages = 0; + snd_gf1_poke(gus, 0, 0x55); + snd_gf1_poke(gus, 1, 0xaa); +#if 1 + if (snd_gf1_peek(gus, 0) == 0x55 && snd_gf1_peek(gus, 1) == 0xaa) +#else + if (0) /* ok.. for testing of 0k RAM */ +#endif + { + snd_interwave_bank_sizes(gus, psizes); + lmct = (psizes[3] << 24) | (psizes[2] << 16) | + (psizes[1] << 8) | psizes[0]; +#if 0 + dev_dbg(gus->card->dev, "lmct = 0x%08x\n", lmct); +#endif + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg >= 0) { +#if 0 + dev_dbg(gus->card->dev, "found !!! %i\n", lmc_cfg); +#endif + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, + (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | + lmc_cfg); + snd_interwave_bank_sizes(gus, psizes); + } else if (!gus->gf1.enh_mode) { + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, + (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | + 2); + } + for (i = 0; i < 4; i++) { + gus->gf1.mem_alloc.banks_8[i].address = + gus->gf1.mem_alloc.banks_16[i].address = i << 22; + gus->gf1.mem_alloc.banks_8[i].size = + gus->gf1.mem_alloc.banks_16[i].size = psizes[i] << 18; + pages += psizes[i]; + } + } + pages <<= 18; + gus->gf1.memory = pages; + + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x03); /* select ROM */ + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff1f) | (4 << 5)); + gus->gf1.rom_banks = 0; + gus->gf1.rom_memory = 0; + for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) { + for (i = 0; i < 8; ++i) + iwave[i] = snd_gf1_peek(gus, bank_pos + i); + if (strncmp(iwave, "INTRWAVE", 8)) + continue; /* first check */ + csum = 0; + for (i = 0; i < sizeof(struct rom_hdr); i++) + csum += snd_gf1_peek(gus, bank_pos + i); + if (csum != 0) + continue; /* not valid rom */ + gus->gf1.rom_banks++; + gus->gf1.rom_present |= 1 << (bank_pos >> 22); + gus->gf1.rom_memory = snd_gf1_peek(gus, bank_pos + 40) | + (snd_gf1_peek(gus, bank_pos + 41) << 8) | + (snd_gf1_peek(gus, bank_pos + 42) << 16) | + (snd_gf1_peek(gus, bank_pos + 43) << 24); + } +#if 0 + if (gus->gf1.rom_memory > 0) { + if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8) + gus->card->type = SNDRV_CARD_TYPE_IW_DYNASONIC; + } +#endif + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x00); /* select RAM */ + + if (!gus->gf1.enh_mode) + snd_interwave_reset(gus); +} + +static void __snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f); + snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30); + snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); +} + +static void snd_interwave_init(int dev, struct snd_gus_card *gus) +{ + /* Probe-time setup also clears the timer control register. */ + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00); + __snd_interwave_restore_regs(gus); + } + gus->equal_irq = 1; + gus->codec_flag = 1; + gus->interwave = 1; + gus->max_flag = 1; + gus->joystick_dac = joystick_dac[dev]; +} + +static const struct snd_kcontrol_new snd_interwave_controls[] = { +WSS_DOUBLE("Master Playback Switch", 0, + CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 7, 7, 1, 1), +WSS_DOUBLE("Master Playback Volume", 0, + CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0, 31, 1), +WSS_DOUBLE("Mic Playback Switch", 0, + CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 7, 7, 1, 1), +WSS_DOUBLE("Mic Playback Volume", 0, + CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0, 31, 1) +}; + +static int snd_interwave_mixer(struct snd_wss *chip) +{ + struct snd_card *card = chip->card; + struct snd_ctl_elem_id id1, id2; + unsigned int idx; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; +#if 0 + /* remove mono microphone controls */ + strscpy(id1.name, "Mic Playback Switch"); + err = snd_ctl_remove_id(card, &id1); + if (err < 0) + return err; + strscpy(id1.name, "Mic Playback Volume"); + err = snd_ctl_remove_id(card, &id1); + if (err < 0) + return err; +#endif + /* add new master and mic controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_interwave_controls); idx++) { + err = snd_ctl_add(card, snd_ctl_new1(&snd_interwave_controls[idx], chip)); + if (err < 0) + return err; + } + snd_wss_out(chip, CS4231_LINE_LEFT_OUTPUT, 0x9f); + snd_wss_out(chip, CS4231_LINE_RIGHT_OUTPUT, 0x9f); + snd_wss_out(chip, CS4231_LEFT_MIC_INPUT, 0x9f); + snd_wss_out(chip, CS4231_RIGHT_MIC_INPUT, 0x9f); + /* reassign AUXA to SYNTHESIZER */ + strscpy(id1.name, "Aux Playback Switch"); + strscpy(id2.name, "Synth Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + strscpy(id1.name, "Aux Playback Volume"); + strscpy(id2.name, "Synth Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + /* reassign AUXB to CD */ + strscpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strscpy(id2.name, "CD Playback Switch"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + strscpy(id1.name, "Aux Playback Volume"); + strscpy(id2.name, "CD Playback Volume"); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + return 0; +} + +#ifdef CONFIG_PNP + +static int snd_interwave_pnp(int dev, struct snd_interwave *iwcard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + int err; + + iwcard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (iwcard->dev == NULL) + return -EBUSY; + +#ifdef SNDRV_STB + iwcard->devtc = pnp_request_card_device(card, id->devs[1].id, NULL); + if (iwcard->devtc == NULL) + return -EBUSY; +#endif + /* Synth & Codec initialization */ + pdev = iwcard->dev; + + err = pnp_activate_dev(pdev); + if (err < 0) { + dev_err(&pdev->dev, "InterWave PnP configure failure (out of resources?)\n"); + return err; + } + if (pnp_port_start(pdev, 0) + 0x100 != pnp_port_start(pdev, 1) || + pnp_port_start(pdev, 0) + 0x10c != pnp_port_start(pdev, 2)) { + dev_err(&pdev->dev, "PnP configure failure (wrong ports)\n"); + return -ENOENT; + } + port[dev] = pnp_port_start(pdev, 0); + dma1[dev] = pnp_dma(pdev, 0); + if (dma2[dev] >= 0) + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + dev_dbg(&pdev->dev, + "isapnp IW: sb port=0x%llx, gf1 port=0x%llx, codec port=0x%llx\n", + (unsigned long long)pnp_port_start(pdev, 0), + (unsigned long long)pnp_port_start(pdev, 1), + (unsigned long long)pnp_port_start(pdev, 2)); + dev_dbg(&pdev->dev, + "isapnp IW: dma1=%i, dma2=%i, irq=%i\n", + dma1[dev], dma2[dev], irq[dev]); +#ifdef SNDRV_STB + /* Tone Control initialization */ + pdev = iwcard->devtc; + + err = pnp_activate_dev(pdev); + if (err < 0) { + dev_err(&pdev->dev, + "InterWave ToneControl PnP configure failure (out of resources?)\n"); + return err; + } + port_tc[dev] = pnp_port_start(pdev, 0); + dev_dbg(&pdev->dev, "isapnp IW: tone control port=0x%lx\n", port_tc[dev]); +#endif + return 0; +} +#endif /* CONFIG_PNP */ + +static int snd_interwave_card_new(struct device *pdev, int dev, + struct snd_card **cardp) +{ + struct snd_card *card; + struct snd_interwave *iwcard; + int err; + + err = snd_devm_card_new(pdev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_interwave), &card); + if (err < 0) + return err; + iwcard = card->private_data; + iwcard->card = card; + iwcard->irq = -1; + *cardp = card; + return 0; +} + +static int snd_interwave_probe_gus(struct snd_card *card, int dev, + struct snd_gus_card **gusp) +{ + return snd_gus_create(card, port[dev], -irq[dev], dma1[dev], dma2[dev], + 0, 32, pcm_channels[dev], effect[dev], gusp); +} + +static int snd_interwave_probe(struct snd_card *card, int dev, + struct snd_gus_card *gus) +{ + int xirq, xdma1, xdma2; + struct snd_interwave *iwcard = card->private_data; + struct snd_wss *wss; +#ifdef SNDRV_STB + struct snd_i2c_bus *i2c_bus; +#endif + char *str; + int err; + + xirq = irq[dev]; + xdma1 = dma1[dev]; + xdma2 = dma2[dev]; + + err = snd_interwave_detect(iwcard, gus, dev +#ifdef SNDRV_STB + , &i2c_bus +#endif + ); + if (err < 0) + return err; + + iwcard->gus_status_reg = gus->gf1.reg_irqstat; + iwcard->pcm_status_reg = gus->gf1.port + 0x10c + 2; + + snd_interwave_init(dev, gus); + snd_interwave_detect_memory(gus); + err = snd_gus_initialize(gus); + if (err < 0) + return err; + + if (devm_request_irq(card->dev, xirq, snd_interwave_interrupt, 0, + "InterWave", iwcard)) { + dev_err(card->dev, "unable to grab IRQ %d\n", xirq); + return -EBUSY; + } + iwcard->irq = xirq; + card->sync_irq = iwcard->irq; + + err = snd_wss_create(card, + gus->gf1.port + 0x10c, -1, xirq, + xdma2 < 0 ? xdma1 : xdma2, xdma1, + WSS_HW_INTERWAVE, + WSS_HWSHARE_IRQ | + WSS_HWSHARE_DMA1 | + WSS_HWSHARE_DMA2, + &wss); + if (err < 0) + return err; + + err = snd_wss_pcm(wss, 0); + if (err < 0) + return err; + + sprintf(wss->pcm->name + strlen(wss->pcm->name), " rev %c", + gus->revision + 'A'); + strcat(wss->pcm->name, " (codec)"); + + err = snd_wss_timer(wss, 2); + if (err < 0) + return err; + + err = snd_wss_mixer(wss); + if (err < 0) + return err; + + if (pcm_channels[dev] > 0) { + err = snd_gf1_pcm_new(gus, 1, 1); + if (err < 0) + return err; + } + err = snd_interwave_mixer(wss); + if (err < 0) + return err; + +#ifdef SNDRV_STB + { + struct snd_ctl_elem_id id1, id2; + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strscpy(id1.name, "Master Playback Switch"); + strscpy(id2.name, id1.name); + id2.index = 1; + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + strscpy(id1.name, "Master Playback Volume"); + strscpy(id2.name, id1.name); + err = snd_ctl_rename_id(card, &id1, &id2); + if (err < 0) + return err; + err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1); + if (err < 0) + return err; + iwcard->i2c_bus = i2c_bus; + } +#endif + + gus->uart_enable = midi[dev]; + err = snd_gf1_rawmidi_new(gus, 0); + if (err < 0) + return err; + +#ifndef SNDRV_STB + str = "AMD InterWave"; + if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8) + str = "Dynasonic 3-D"; +#else + str = "InterWave STB"; +#endif + strscpy(card->driver, str); + strscpy(card->shortname, str); + sprintf(card->longname, "%s at 0x%lx, irq %i, dma %d", + str, + gus->gf1.port, + xirq, + xdma1); + if (xdma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", xdma2); + + err = snd_card_register(card); + if (err < 0) + return err; + + iwcard->wss = wss; + iwcard->gus = gus; + return 0; +} + +static int snd_interwave_isa_match(struct device *pdev, + unsigned int dev) +{ + if (!enable[dev]) + return 0; +#ifdef CONFIG_PNP + if (isapnp[dev]) + return 0; +#endif + return 1; +} + +static int snd_interwave_isa_probe(struct device *pdev, + unsigned int dev) +{ + struct snd_card *card; + struct snd_gus_card *gus; + int err; + static const int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1}; + static const int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1}; + + if (irq[dev] == SNDRV_AUTO_IRQ) { + irq[dev] = snd_legacy_find_free_irq(possible_irqs); + if (irq[dev] < 0) { + dev_err(pdev, "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (dma1[dev] == SNDRV_AUTO_DMA) { + dma1[dev] = snd_legacy_find_free_dma(possible_dmas); + if (dma1[dev] < 0) { + dev_err(pdev, "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (dma2[dev] == SNDRV_AUTO_DMA) { + dma2[dev] = snd_legacy_find_free_dma(possible_dmas); + if (dma2[dev] < 0) { + dev_err(pdev, "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + err = snd_interwave_card_new(pdev, dev, &card); + if (err < 0) + return err; + + if (port[dev] != SNDRV_AUTO_PORT) + err = snd_interwave_probe_gus(card, dev, &gus); + else { + static const long possible_ports[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260}; + int i; + for (i = 0; i < ARRAY_SIZE(possible_ports); i++) { + port[dev] = possible_ports[i]; + err = snd_interwave_probe_gus(card, dev, &gus); + if (! err) + return 0; + } + } + if (err < 0) + return err; + + err = snd_interwave_probe(card, dev, gus); + if (err < 0) + return err; + + dev_set_drvdata(pdev, card); + return 0; +} + +#ifdef CONFIG_PM +static void snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + scoped_guard(spinlock_irqsave, &gus->reg_lock) + __snd_interwave_restore_regs(gus); +} + +static void snd_interwave_restore_memory(struct snd_gus_card *gus) +{ + unsigned short mem_cfg; + unsigned int lmct = 0; + int i, lmc_cfg; + + if (!gus->gf1.memory) + return; + + for (i = 0; i < 4; i++) + lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8); + + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg < 0) { + if (!gus->gf1.enh_mode) { + lmc_cfg = 2; + } else { + dev_warn(gus->card->dev, + "cannot restore InterWave memory layout 0x%08x\n", + lmct); + return; + } + } + + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG); + mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg; + mem_cfg = (mem_cfg & 0xff1f) | (4 << 5); + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg); + } +} + +static int snd_interwave_card_suspend(struct snd_card *card) +{ + struct snd_interwave *iwcard = card->private_data; + + iwcard->wss->suspend(iwcard->wss); + return snd_gus_suspend(iwcard->gus); +} + +static int snd_interwave_card_resume(struct snd_card *card) +{ + struct snd_interwave *iwcard = card->private_data; + int err; + + err = snd_gus_resume(iwcard->gus); + if (err < 0) + return err; + + snd_interwave_restore_regs(iwcard->gus); + snd_interwave_restore_memory(iwcard->gus); + iwcard->wss->resume(iwcard->wss); +#ifdef SNDRV_STB + if (iwcard->i2c_bus) { + err = snd_tea6330t_restore_mixer(iwcard->i2c_bus); + if (err < 0) + dev_warn(card->dev, + "failed to restore TEA6330T mixer state: %d\n", + err); + } +#endif + + return 0; +} + +static int snd_interwave_isa_suspend(struct device *pdev, unsigned int dev, + pm_message_t state) +{ + return snd_interwave_card_suspend(dev_get_drvdata(pdev)); +} + +static int snd_interwave_isa_resume(struct device *pdev, unsigned int dev) +{ + return snd_interwave_card_resume(dev_get_drvdata(pdev)); +} +#endif + +static struct isa_driver snd_interwave_driver = { + .match = snd_interwave_isa_match, + .probe = snd_interwave_isa_probe, +#ifdef CONFIG_PM + .suspend = snd_interwave_isa_suspend, + .resume = snd_interwave_isa_resume, +#endif + .driver = { + .name = INTERWAVE_DRIVER + }, +}; + +#ifdef CONFIG_PNP +static int snd_interwave_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int dev; + struct snd_card *card; + struct snd_gus_card *gus; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (enable[dev] && isapnp[dev]) + break; + } + if (dev >= SNDRV_CARDS) + return -ENODEV; + + res = snd_interwave_card_new(&pcard->card->dev, dev, &card); + if (res < 0) + return res; + + res = snd_interwave_pnp(dev, card->private_data, pcard, pid); + if (res < 0) + return res; + res = snd_interwave_probe_gus(card, dev, &gus); + if (res < 0) + return res; + res = snd_interwave_probe(card, dev, gus); + if (res < 0) + return res; + pnp_set_card_drvdata(pcard, card); + dev++; + return 0; +} + +#ifdef CONFIG_PM +static int snd_interwave_pnpc_suspend(struct pnp_card_link *pcard, + pm_message_t state) +{ + return snd_interwave_card_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_interwave_pnpc_resume(struct pnp_card_link *pcard) +{ + return snd_interwave_card_resume(pnp_get_card_drvdata(pcard)); +} +#endif + +static struct pnp_card_driver interwave_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = INTERWAVE_PNP_DRIVER, + .id_table = snd_interwave_pnpids, + .probe = snd_interwave_pnp_detect, +#ifdef CONFIG_PM + .suspend = snd_interwave_pnpc_suspend, + .resume = snd_interwave_pnpc_resume, +#endif +}; + +#endif /* CONFIG_PNP */ + +static int __init alsa_card_interwave_init(void) +{ + int err; + + err = isa_register_driver(&snd_interwave_driver, SNDRV_CARDS); +#ifdef CONFIG_PNP + if (!err) + isa_registered = 1; + + err = pnp_register_card_driver(&interwave_pnpc_driver); + if (!err) + pnp_registered = 1; + + if (isa_registered) + err = 0; +#endif + return err; +} + +static void __exit alsa_card_interwave_exit(void) +{ +#ifdef CONFIG_PNP + if (pnp_registered) + pnp_unregister_card_driver(&interwave_pnpc_driver); + if (isa_registered) +#endif + isa_unregister_driver(&snd_interwave_driver); +} + +module_init(alsa_card_interwave_init) +module_exit(alsa_card_interwave_exit) |
