Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+266
View File
@@ -0,0 +1,266 @@
# SPDX-License-Identifier: GPL-2.0-only
config SND_MPU401_UART
tristate
select SND_RAWMIDI
config SND_OPL3_LIB
tristate
select SND_TIMER
select SND_HWDEP
select SND_SEQ_DEVICE if SND_SEQUENCER != n
config SND_OPL4_LIB
tristate
select SND_TIMER
select SND_HWDEP
select SND_SEQ_DEVICE if SND_SEQUENCER != n
# select SEQ stuff to min(SND_SEQUENCER,SND_XXX)
config SND_OPL3_LIB_SEQ
def_tristate SND_SEQUENCER && SND_OPL3_LIB
select SND_SEQ_MIDI_EMUL
select SND_SEQ_MIDI_EVENT
config SND_OPL4_LIB_SEQ
def_tristate SND_SEQUENCER && SND_OPL4_LIB
select SND_SEQ_MIDI_EMUL
select SND_SEQ_MIDI_EVENT
config SND_VX_LIB
tristate
select FW_LOADER
select SND_HWDEP
select SND_PCM
config SND_AC97_CODEC
tristate
select SND_PCM
select AC97_BUS
select SND_VMASTER
menuconfig SND_DRIVERS
bool "Generic sound devices"
default y
help
Support for generic sound devices.
if SND_DRIVERS
config SND_PCSP
tristate "PC-Speaker support (READ HELP!)"
depends on PCSPKR_PLATFORM && X86 && HIGH_RES_TIMERS
depends on INPUT
select SND_PCM
help
If you don't have a sound card in your computer, you can include a
driver for the PC speaker which allows it to act like a primitive
sound card.
This driver also replaces the pcspkr driver for beeps.
You can compile this as a module which will be called snd-pcsp.
WARNING: if you already have a soundcard, enabling this
driver may lead to a problem. Namely, it may get loaded
before the other sound driver of yours, making the
pc-speaker a default sound device. Which is likely not
what you want. To make this driver play nicely with other
sound driver, you can add this in a configuration file under
/etc/modprobe.d/ directory:
options snd-pcsp index=2
You don't need this driver if you only want your pc-speaker to beep.
You don't need this driver if you have a tablet piezo beeper
in your PC instead of the real speaker.
Say N if you have a sound card.
Say M if you don't.
Say Y only if you really know what you do.
config SND_DUMMY
tristate "Dummy (/dev/null) soundcard"
select SND_PCM
help
Say Y here to include the dummy driver. This driver does
nothing, but emulates various mixer controls and PCM devices.
You don't need this unless you're testing the hardware support
of programs using the ALSA API.
To compile this driver as a module, choose M here: the module
will be called snd-dummy.
config SND_ALOOP
tristate "Generic loopback driver (PCM)"
select SND_PCM
select SND_TIMER
help
Say 'Y' or 'M' to include support for the PCM loopback device.
This module returns played samples back to the user space using
the standard ALSA PCM device. The devices are routed 0->1 and
1->0, where first number is the playback PCM device and second
number is the capture device. Module creates two PCM devices and
configured number of substreams (see the pcm_substreams module
parameter).
The loopback device allows time synchronization with an external
timing source using the time shift universal control (+-20%
of system time).
To compile this driver as a module, choose M here: the module
will be called snd-aloop.
config SND_PCMTEST
tristate "Virtual PCM test driver"
depends on DEBUG_FS
select SND_PCM
help
Say 'Y' or 'M' to include support for the Virtual PCM test driver.
This driver is aimed at extended testing of the userspace applications
which use the ALSA API, as well as the PCM middle layer testing.
It can generate random or pattern-based data into the capture stream,
check the playback stream for containing the selected pattern, inject
time delays during capture/playback, redefine the RESET ioctl operation
to perform the PCM middle layer testing and inject errors during the
PCM callbacks. It supports both interleaved and non-interleaved access
modes. You can find the corresponding selftest in the 'alsa'
selftests folder.
config SND_VIRMIDI
tristate "Virtual MIDI soundcard"
depends on SND_SEQUENCER
select SND_TIMER
select SND_RAWMIDI
select SND_SEQ_VIRMIDI
select SND_SEQ_MIDI_EVENT
help
Say Y here to include the virtual MIDI driver. This driver
allows to connect applications using raw MIDI devices to
sequencer clients.
If you don't know what MIDI is, say N here.
To compile this driver as a module, choose M here: the module
will be called snd-virmidi.
config SND_MTPAV
tristate "MOTU MidiTimePiece AV multiport MIDI"
depends on HAS_IOPORT
select SND_RAWMIDI
help
To use a MOTU MidiTimePiece AV multiport MIDI adapter
connected to the parallel port, say Y here and make sure that
the standard parallel port driver isn't used for the port.
To compile this driver as a module, choose M here: the module
will be called snd-mtpav.
config SND_MTS64
tristate "ESI Miditerminal 4140 driver"
depends on PARPORT
select SND_RAWMIDI
help
The ESI Miditerminal 4140 is a 4 In 4 Out MIDI Interface with
additional SMPTE Timecode capabilities for the parallel port.
Say 'Y' to include support for this device.
To compile this driver as a module, chose 'M' here: the module
will be called snd-mts64.
config SND_SERIAL_U16550
tristate "UART16550 serial MIDI driver"
depends on HAS_IOPORT
select SND_RAWMIDI
help
To include support for MIDI serial port interfaces, say Y here
and read <file:Documentation/sound/cards/serial-u16550.rst>.
This driver works with serial UARTs 16550 and better.
This driver accesses the serial port hardware directly, so
make sure that the standard serial driver isn't used or
deactivated with setserial before loading this driver.
To compile this driver as a module, choose M here: the module
will be called snd-serial-u16550.
config SND_SERIAL_GENERIC
tristate "Generic serial MIDI driver"
depends on SERIAL_DEV_BUS
depends on OF
select SND_RAWMIDI
help
To include support for mapping generic serial devices as raw
ALSA MIDI devices, say Y here. The driver only supports setting
the serial port to standard baudrates. To attain the standard MIDI
baudrate of 31.25 kBaud, configure the clock of the underlying serial
device so that a requested 38.4 kBaud will result in the standard speed.
Use this devicetree binding to configure serial port mapping
<file:Documentation/devicetree/bindings/sound/serial-midi.yaml>
To compile this driver as a module, choose M here: the module
will be called snd-serial-generic.
config SND_MPU401
tristate "Generic MPU-401 UART driver"
depends on HAS_IOPORT
select SND_MPU401_UART
help
Say Y here to include support for MIDI ports compatible with
the Roland MPU-401 interface in UART mode.
To compile this driver as a module, choose M here: the module
will be called snd-mpu401.
config SND_PORTMAN2X4
tristate "Portman 2x4 driver"
depends on PARPORT
select SND_RAWMIDI
help
Say Y here to include support for Midiman Portman 2x4 parallel
port MIDI device.
To compile this driver as a module, choose M here: the module
will be called snd-portman2x4.
config SND_AC97_POWER_SAVE
bool "AC97 Power-Saving Mode"
depends on SND_AC97_CODEC
default n
help
Say Y here to enable the aggressive power-saving support of
AC97 codecs. In this mode, the power-mode is dynamically
controlled at each open/close.
The mode is activated by passing 'power_save=X' to the
snd-ac97-codec driver module, where 'X' is the time-out
value, a nonnegative integer that specifies how many
seconds of idle time the driver must count before it may
put the AC97 into power-save mode; a value of 0 (zero)
disables the use of this power-save mode.
After the snd-ac97-codec driver module has been loaded,
the 'power_save' parameter can be set via sysfs as follows:
echo 10 > /sys/module/snd_ac97_codec/parameters/power_save
In this case, the time-out is set to 10 seconds; setting
the time-out to 1 second (the minimum activation value)
isn't recommended because many applications try to reopen
the device frequently. A value of 10 seconds would be a
good choice for normal operations.
See Documentation/sound/designs/powersave.rst for more details.
config SND_AC97_POWER_SAVE_DEFAULT
int "Default time-out for AC97 power-save mode"
depends on SND_AC97_POWER_SAVE
default 0
help
The default time-out value in seconds for AC97 automatic
power-save mode. 0 means to disable the power-save mode.
See SND_AC97_POWER_SAVE for more details.
endif # SND_DRIVERS
+28
View File
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-dummy-y := dummy.o
snd-aloop-y := aloop.o
snd-mtpav-y := mtpav.o
snd-mts64-y := mts64.o
snd-pcmtest-y := pcmtest.o
snd-portman2x4-y := portman2x4.o
snd-serial-u16550-y := serial-u16550.o
snd-serial-generic-y := serial-generic.o
snd-virmidi-y := virmidi.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_DUMMY) += snd-dummy.o
obj-$(CONFIG_SND_ALOOP) += snd-aloop.o
obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o
obj-$(CONFIG_SND_PCMTEST) += snd-pcmtest.o
obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o
obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o
obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
obj-$(CONFIG_SND_MTS64) += snd-mts64.o
obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-mpu401-y := mpu401.o
snd-mpu401-uart-y := mpu401_uart.o
obj-$(CONFIG_SND_MPU401_UART) += snd-mpu401-uart.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_MPU401) += snd-mpu401.o
+251
View File
@@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for generic MPU-401 boards (UART mode only)
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Copyright (c) 2004 by Castet Matthieu <castet.matthieu@free.fr>
*/
#include <linux/init.h>
#include <linux/pnp.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/mpu401.h>
#include <sound/initval.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("MPU-401 UART");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -2}; /* exclude the first card */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
#ifdef CONFIG_PNP
static bool pnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
#endif
static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* MPU-401 port number */
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* MPU-401 IRQ */
static bool uart_enter[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for MPU-401 device.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for MPU-401 device.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable MPU-401 device.");
#ifdef CONFIG_PNP
module_param_array(pnp, bool, NULL, 0444);
MODULE_PARM_DESC(pnp, "PnP detection for MPU-401 device.");
#endif
module_param_hw_array(port, long, ioport, NULL, 0444);
MODULE_PARM_DESC(port, "Port # for MPU-401 device.");
module_param_hw_array(irq, int, irq, NULL, 0444);
MODULE_PARM_DESC(irq, "IRQ # for MPU-401 device.");
module_param_array(uart_enter, bool, NULL, 0444);
MODULE_PARM_DESC(uart_enter, "Issue UART_ENTER command at open.");
static struct platform_device *platform_devices[SNDRV_CARDS];
static int pnp_registered __ro_after_init;
static unsigned int snd_mpu401_devices;
static int snd_mpu401_create(struct device *devptr, int dev,
struct snd_card **rcard)
{
struct snd_card *card;
int err;
if (!uart_enter[dev])
dev_err(devptr, "the uart_enter option is obsolete; remove it\n");
*rcard = NULL;
err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
strscpy(card->driver, "MPU-401 UART");
strscpy(card->shortname, card->driver);
sprintf(card->longname, "%s at %#lx, ", card->shortname, port[dev]);
if (irq[dev] >= 0) {
sprintf(card->longname + strlen(card->longname), "irq %d", irq[dev]);
} else {
strcat(card->longname, "polled");
}
err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, port[dev], 0,
irq[dev], NULL);
if (err < 0) {
dev_err(devptr, "MPU401 not detected at 0x%lx\n", port[dev]);
return err;
}
*rcard = card;
return 0;
}
static int snd_mpu401_probe(struct platform_device *devptr)
{
int dev = devptr->id;
int err;
struct snd_card *card;
if (port[dev] == SNDRV_AUTO_PORT) {
dev_err(&devptr->dev, "specify port\n");
return -EINVAL;
}
if (irq[dev] == SNDRV_AUTO_IRQ) {
dev_err(&devptr->dev, "specify or disable IRQ\n");
return -EINVAL;
}
err = snd_mpu401_create(&devptr->dev, dev, &card);
if (err < 0)
return err;
err = snd_card_register(card);
if (err < 0)
return err;
platform_set_drvdata(devptr, card);
return 0;
}
#define SND_MPU401_DRIVER "snd_mpu401"
static struct platform_driver snd_mpu401_driver = {
.probe = snd_mpu401_probe,
.driver = {
.name = SND_MPU401_DRIVER,
},
};
#ifdef CONFIG_PNP
#define IO_EXTENT 2
static const struct pnp_device_id snd_mpu401_pnpids[] = {
{ .id = "PNPb006" },
{ }
};
MODULE_DEVICE_TABLE(pnp, snd_mpu401_pnpids);
static int snd_mpu401_pnp(int dev, struct pnp_dev *device,
const struct pnp_device_id *id)
{
if (!pnp_port_valid(device, 0) ||
pnp_port_flags(device, 0) & IORESOURCE_DISABLED) {
dev_err(&device->dev, "no PnP port\n");
return -ENODEV;
}
if (pnp_port_len(device, 0) < IO_EXTENT) {
dev_err(&device->dev, "PnP port length is %llu, expected %d\n",
(unsigned long long)pnp_port_len(device, 0),
IO_EXTENT);
return -ENODEV;
}
port[dev] = pnp_port_start(device, 0);
if (!pnp_irq_valid(device, 0) ||
pnp_irq_flags(device, 0) & IORESOURCE_DISABLED) {
dev_warn(&device->dev, "no PnP irq, using polling\n");
irq[dev] = -1;
} else {
irq[dev] = pnp_irq(device, 0);
}
return 0;
}
static int snd_mpu401_pnp_probe(struct pnp_dev *pnp_dev,
const struct pnp_device_id *id)
{
static int dev;
struct snd_card *card;
int err;
for ( ; dev < SNDRV_CARDS; ++dev) {
if (!enable[dev] || !pnp[dev])
continue;
err = snd_mpu401_pnp(dev, pnp_dev, id);
if (err < 0)
return err;
err = snd_mpu401_create(&pnp_dev->dev, dev, &card);
if (err < 0)
return err;
err = snd_card_register(card);
if (err < 0)
return err;
pnp_set_drvdata(pnp_dev, card);
snd_mpu401_devices++;
++dev;
return 0;
}
return -ENODEV;
}
static struct pnp_driver snd_mpu401_pnp_driver = {
.name = "mpu401",
.id_table = snd_mpu401_pnpids,
.probe = snd_mpu401_pnp_probe,
};
#else
static struct pnp_driver snd_mpu401_pnp_driver;
#endif
static void snd_mpu401_unregister_all(void)
{
int i;
if (pnp_registered)
pnp_unregister_driver(&snd_mpu401_pnp_driver);
for (i = 0; i < ARRAY_SIZE(platform_devices); ++i)
platform_device_unregister(platform_devices[i]);
platform_driver_unregister(&snd_mpu401_driver);
}
static int __init alsa_card_mpu401_init(void)
{
int i, err;
err = platform_driver_register(&snd_mpu401_driver);
if (err < 0)
return err;
for (i = 0; i < SNDRV_CARDS; i++) {
struct platform_device *device;
if (! enable[i])
continue;
#ifdef CONFIG_PNP
if (pnp[i])
continue;
#endif
device = platform_device_register_simple(SND_MPU401_DRIVER,
i, NULL, 0);
if (IS_ERR(device))
continue;
if (!platform_get_drvdata(device)) {
platform_device_unregister(device);
continue;
}
platform_devices[i] = device;
snd_mpu401_devices++;
}
err = pnp_register_driver(&snd_mpu401_pnp_driver);
if (!err)
pnp_registered = 1;
if (!snd_mpu401_devices) {
#ifdef MODULE
pr_err("MPU-401 device not found or device busy\n");
#endif
snd_mpu401_unregister_all();
return -ENODEV;
}
return 0;
}
static void __exit alsa_card_mpu401_exit(void)
{
snd_mpu401_unregister_all();
}
module_init(alsa_card_mpu401_init)
module_exit(alsa_card_mpu401_exit)
@@ -0,0 +1,595 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
* Routines for control of MPU-401 in UART mode
*
* MPU-401 supports UART mode which is not capable generate transmit
* interrupts thus output is done via polling. Without interrupt,
* input is done also via polling. Do not expect good performance.
*
* 13-03-2003:
* Added support for different kind of hardware I/O. Build in choices
* are port and mmio. For other kind of I/O, set mpu->read and
* mpu->write to your own I/O functions.
*/
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <sound/core.h>
#include <sound/mpu401.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
MODULE_DESCRIPTION("Routines for control of MPU-401 in UART mode");
MODULE_LICENSE("GPL");
static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu);
static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu);
/*
*/
#define snd_mpu401_input_avail(mpu) \
(!(mpu->read(mpu, MPU401C(mpu)) & MPU401_RX_EMPTY))
#define snd_mpu401_output_ready(mpu) \
(!(mpu->read(mpu, MPU401C(mpu)) & MPU401_TX_FULL))
/* Build in lowlevel io */
static void mpu401_write_port(struct snd_mpu401 *mpu, unsigned char data,
unsigned long addr)
{
outb(data, addr);
}
static unsigned char mpu401_read_port(struct snd_mpu401 *mpu,
unsigned long addr)
{
return inb(addr);
}
static void mpu401_write_mmio(struct snd_mpu401 *mpu, unsigned char data,
unsigned long addr)
{
writeb(data, (void __iomem *)addr);
}
static unsigned char mpu401_read_mmio(struct snd_mpu401 *mpu,
unsigned long addr)
{
return readb((void __iomem *)addr);
}
/* */
static void snd_mpu401_uart_clear_rx(struct snd_mpu401 *mpu)
{
int timeout = 100000;
for (; timeout > 0 && snd_mpu401_input_avail(mpu); timeout--)
mpu->read(mpu, MPU401D(mpu));
#ifdef CONFIG_SND_DEBUG
if (timeout <= 0)
dev_err(mpu->rmidi->dev,
"cmd: clear rx timeout (status = 0x%x)\n",
mpu->read(mpu, MPU401C(mpu)));
#endif
}
static void uart_interrupt_tx(struct snd_mpu401 *mpu)
{
if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) &&
test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) {
guard(spinlock_irqsave)(&mpu->output_lock);
snd_mpu401_uart_output_write(mpu);
}
}
static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu)
{
if (mpu->info_flags & MPU401_INFO_INPUT) {
guard(spinlock_irqsave)(&mpu->input_lock);
if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))
snd_mpu401_uart_input_read(mpu);
else
snd_mpu401_uart_clear_rx(mpu);
}
if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))
/* ok. for better Tx performance try do some output
when input is done */
uart_interrupt_tx(mpu);
}
/**
* snd_mpu401_uart_interrupt - generic MPU401-UART interrupt handler
* @irq: the irq number
* @dev_id: mpu401 instance
*
* Processes the interrupt for MPU401-UART i/o.
*
* Return: %IRQ_HANDLED if the interrupt was handled. %IRQ_NONE otherwise.
*/
irqreturn_t snd_mpu401_uart_interrupt(int irq, void *dev_id)
{
struct snd_mpu401 *mpu = dev_id;
if (!mpu)
return IRQ_NONE;
_snd_mpu401_uart_interrupt(mpu);
return IRQ_HANDLED;
}
EXPORT_SYMBOL(snd_mpu401_uart_interrupt);
/**
* snd_mpu401_uart_interrupt_tx - generic MPU401-UART transmit irq handler
* @irq: the irq number
* @dev_id: mpu401 instance
*
* Processes the interrupt for MPU401-UART output.
*
* Return: %IRQ_HANDLED if the interrupt was handled. %IRQ_NONE otherwise.
*/
irqreturn_t snd_mpu401_uart_interrupt_tx(int irq, void *dev_id)
{
struct snd_mpu401 *mpu = dev_id;
if (!mpu)
return IRQ_NONE;
uart_interrupt_tx(mpu);
return IRQ_HANDLED;
}
EXPORT_SYMBOL(snd_mpu401_uart_interrupt_tx);
/*
* timer callback
* reprogram the timer and call the interrupt job
*/
static void snd_mpu401_uart_timer(struct timer_list *t)
{
struct snd_mpu401 *mpu = timer_container_of(mpu, t, timer);
scoped_guard(spinlock_irqsave, &mpu->timer_lock) {
/*mpu->mode |= MPU401_MODE_TIMER;*/
mod_timer(&mpu->timer, 1 + jiffies);
}
if (mpu->rmidi)
_snd_mpu401_uart_interrupt(mpu);
}
/*
* initialize the timer callback if not programmed yet
*/
static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input)
{
guard(spinlock_irqsave)(&mpu->timer_lock);
if (mpu->timer_invoked == 0) {
timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0);
mod_timer(&mpu->timer, 1 + jiffies);
}
mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER :
MPU401_MODE_OUTPUT_TIMER;
}
/*
* remove the timer callback if still active
*/
static void snd_mpu401_uart_remove_timer (struct snd_mpu401 *mpu, int input)
{
guard(spinlock_irqsave)(&mpu->timer_lock);
if (mpu->timer_invoked) {
mpu->timer_invoked &= input ? ~MPU401_MODE_INPUT_TIMER :
~MPU401_MODE_OUTPUT_TIMER;
if (! mpu->timer_invoked)
timer_delete(&mpu->timer);
}
}
/*
* send a UART command
* return zero if successful, non-zero for some errors
*/
static int snd_mpu401_uart_cmd(struct snd_mpu401 * mpu, unsigned char cmd,
int ack)
{
int timeout, ok;
guard(spinlock_irqsave)(&mpu->input_lock);
if (mpu->hardware != MPU401_HW_TRID4DWAVE) {
mpu->write(mpu, 0x00, MPU401D(mpu));
/*snd_mpu401_uart_clear_rx(mpu);*/
}
/* ok. standard MPU-401 initialization */
if (mpu->hardware != MPU401_HW_SB) {
for (timeout = 1000; timeout > 0 &&
!snd_mpu401_output_ready(mpu); timeout--)
udelay(10);
#ifdef CONFIG_SND_DEBUG
if (!timeout)
dev_err(mpu->rmidi->dev,
"cmd: tx timeout (status = 0x%x)\n",
mpu->read(mpu, MPU401C(mpu)));
#endif
}
mpu->write(mpu, cmd, MPU401C(mpu));
if (ack && !(mpu->info_flags & MPU401_INFO_NO_ACK)) {
ok = 0;
timeout = 10000;
while (!ok && timeout-- > 0) {
if (snd_mpu401_input_avail(mpu)) {
if (mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)
ok = 1;
}
}
if (!ok && mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)
ok = 1;
} else
ok = 1;
if (!ok) {
dev_err(mpu->rmidi->dev,
"cmd: 0x%x failed at 0x%lx (status = 0x%x, data = 0x%x)\n",
cmd, mpu->port,
mpu->read(mpu, MPU401C(mpu)),
mpu->read(mpu, MPU401D(mpu)));
return 1;
}
return 0;
}
static int snd_mpu401_do_reset(struct snd_mpu401 *mpu)
{
if (snd_mpu401_uart_cmd(mpu, MPU401_RESET, 1))
return -EIO;
if (snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 0))
return -EIO;
return 0;
}
/*
* input/output open/close - protected by open_mutex in rawmidi.c
*/
static int snd_mpu401_uart_input_open(struct snd_rawmidi_substream *substream)
{
struct snd_mpu401 *mpu;
int err;
mpu = substream->rmidi->private_data;
if (mpu->open_input) {
err = mpu->open_input(mpu);
if (err < 0)
return err;
}
if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode)) {
if (snd_mpu401_do_reset(mpu) < 0)
goto error_out;
}
mpu->substream_input = substream;
set_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);
return 0;
error_out:
if (mpu->open_input && mpu->close_input)
mpu->close_input(mpu);
return -EIO;
}
static int snd_mpu401_uart_output_open(struct snd_rawmidi_substream *substream)
{
struct snd_mpu401 *mpu;
int err;
mpu = substream->rmidi->private_data;
if (mpu->open_output) {
err = mpu->open_output(mpu);
if (err < 0)
return err;
}
if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) {
if (snd_mpu401_do_reset(mpu) < 0)
goto error_out;
}
mpu->substream_output = substream;
set_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);
return 0;
error_out:
if (mpu->open_output && mpu->close_output)
mpu->close_output(mpu);
return -EIO;
}
static int snd_mpu401_uart_input_close(struct snd_rawmidi_substream *substream)
{
struct snd_mpu401 *mpu;
int err = 0;
mpu = substream->rmidi->private_data;
clear_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);
mpu->substream_input = NULL;
if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode))
err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);
if (mpu->close_input)
mpu->close_input(mpu);
if (err)
return -EIO;
return 0;
}
static int snd_mpu401_uart_output_close(struct snd_rawmidi_substream *substream)
{
struct snd_mpu401 *mpu;
int err = 0;
mpu = substream->rmidi->private_data;
clear_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);
mpu->substream_output = NULL;
if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))
err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);
if (mpu->close_output)
mpu->close_output(mpu);
if (err)
return -EIO;
return 0;
}
/*
* trigger input callback
*/
static void
snd_mpu401_uart_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_mpu401 *mpu;
int max = 64;
mpu = substream->rmidi->private_data;
if (up) {
if (! test_and_set_bit(MPU401_MODE_BIT_INPUT_TRIGGER,
&mpu->mode)) {
/* first time - flush FIFO */
while (max-- > 0)
mpu->read(mpu, MPU401D(mpu));
if (mpu->info_flags & MPU401_INFO_USE_TIMER)
snd_mpu401_uart_add_timer(mpu, 1);
}
/* read data in advance */
guard(spinlock_irqsave)(&mpu->input_lock);
snd_mpu401_uart_input_read(mpu);
} else {
if (mpu->info_flags & MPU401_INFO_USE_TIMER)
snd_mpu401_uart_remove_timer(mpu, 1);
clear_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode);
}
}
/*
* transfer input pending data
* call with input_lock spinlock held
*/
static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu)
{
int max = 128;
unsigned char byte;
while (max-- > 0) {
if (! snd_mpu401_input_avail(mpu))
break; /* input not available */
byte = mpu->read(mpu, MPU401D(mpu));
if (test_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode))
snd_rawmidi_receive(mpu->substream_input, &byte, 1);
}
}
/*
* Tx FIFO sizes:
* CS4237B - 16 bytes
* AudioDrive ES1688 - 12 bytes
* S3 SonicVibes - 8 bytes
* SoundBlaster AWE 64 - 2 bytes (ugly hardware)
*/
/*
* write output pending bytes
* call with output_lock spinlock held
*/
static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu)
{
unsigned char byte;
int max = 256;
do {
if (snd_rawmidi_transmit_peek(mpu->substream_output,
&byte, 1) == 1) {
/*
* Try twice because there is hardware that insists on
* setting the output busy bit after each write.
*/
if (!snd_mpu401_output_ready(mpu) &&
!snd_mpu401_output_ready(mpu))
break; /* Tx FIFO full - try again later */
mpu->write(mpu, byte, MPU401D(mpu));
snd_rawmidi_transmit_ack(mpu->substream_output, 1);
} else {
snd_mpu401_uart_remove_timer (mpu, 0);
break; /* no other data - leave the tx loop */
}
} while (--max > 0);
}
/*
* output trigger callback
*/
static void
snd_mpu401_uart_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct snd_mpu401 *mpu;
mpu = substream->rmidi->private_data;
if (up) {
set_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);
/* try to add the timer at each output trigger,
* since the output timer might have been removed in
* snd_mpu401_uart_output_write().
*/
if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))
snd_mpu401_uart_add_timer(mpu, 0);
/* output pending data */
guard(spinlock_irqsave)(&mpu->output_lock);
snd_mpu401_uart_output_write(mpu);
} else {
if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))
snd_mpu401_uart_remove_timer(mpu, 0);
clear_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);
}
}
/*
*/
static const struct snd_rawmidi_ops snd_mpu401_uart_output =
{
.open = snd_mpu401_uart_output_open,
.close = snd_mpu401_uart_output_close,
.trigger = snd_mpu401_uart_output_trigger,
};
static const struct snd_rawmidi_ops snd_mpu401_uart_input =
{
.open = snd_mpu401_uart_input_open,
.close = snd_mpu401_uart_input_close,
.trigger = snd_mpu401_uart_input_trigger,
};
static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi)
{
struct snd_mpu401 *mpu = rmidi->private_data;
if (mpu->irq >= 0)
free_irq(mpu->irq, (void *) mpu);
release_and_free_resource(mpu->res);
kfree(mpu);
}
/**
* snd_mpu401_uart_new - create an MPU401-UART instance
* @card: the card instance
* @device: the device index, zero-based
* @hardware: the hardware type, MPU401_HW_XXXX
* @port: the base address of MPU401 port
* @info_flags: bitflags MPU401_INFO_XXX
* @irq: the ISA irq number, -1 if not to be allocated
* @rrawmidi: the pointer to store the new rawmidi instance
*
* Creates a new MPU-401 instance.
*
* Note that the rawmidi instance is returned on the rrawmidi argument,
* not the mpu401 instance itself. To access to the mpu401 instance,
* cast from rawmidi->private_data (with struct snd_mpu401 magic-cast).
*
* Return: Zero if successful, or a negative error code.
*/
int snd_mpu401_uart_new(struct snd_card *card, int device,
unsigned short hardware,
unsigned long port,
unsigned int info_flags,
int irq,
struct snd_rawmidi ** rrawmidi)
{
struct snd_mpu401 *mpu;
struct snd_rawmidi *rmidi;
int in_enable, out_enable;
int err;
if (rrawmidi)
*rrawmidi = NULL;
if (! (info_flags & (MPU401_INFO_INPUT | MPU401_INFO_OUTPUT)))
info_flags |= MPU401_INFO_INPUT | MPU401_INFO_OUTPUT;
in_enable = (info_flags & MPU401_INFO_INPUT) ? 1 : 0;
out_enable = (info_flags & MPU401_INFO_OUTPUT) ? 1 : 0;
err = snd_rawmidi_new(card, "MPU-401U", device,
out_enable, in_enable, &rmidi);
if (err < 0)
return err;
mpu = kzalloc_obj(*mpu);
if (!mpu) {
err = -ENOMEM;
goto free_device;
}
rmidi->private_data = mpu;
rmidi->private_free = snd_mpu401_uart_free;
spin_lock_init(&mpu->input_lock);
spin_lock_init(&mpu->output_lock);
spin_lock_init(&mpu->timer_lock);
mpu->hardware = hardware;
mpu->irq = -1;
mpu->rmidi = rmidi;
if (! (info_flags & MPU401_INFO_INTEGRATED)) {
int res_size = hardware == MPU401_HW_PC98II ? 4 : 2;
mpu->res = request_region(port, res_size, "MPU401 UART");
if (!mpu->res) {
dev_err(rmidi->dev,
"mpu401_uart: unable to grab port 0x%lx size %d\n",
port, res_size);
err = -EBUSY;
goto free_device;
}
}
if (info_flags & MPU401_INFO_MMIO) {
mpu->write = mpu401_write_mmio;
mpu->read = mpu401_read_mmio;
} else {
mpu->write = mpu401_write_port;
mpu->read = mpu401_read_port;
}
mpu->port = port;
if (hardware == MPU401_HW_PC98II)
mpu->cport = port + 2;
else
mpu->cport = port + 1;
if (irq >= 0) {
if (request_irq(irq, snd_mpu401_uart_interrupt, 0,
"MPU401 UART", (void *) mpu)) {
dev_err(rmidi->dev,
"mpu401_uart: unable to grab IRQ %d\n", irq);
err = -EBUSY;
goto free_device;
}
}
if (irq < 0 && !(info_flags & MPU401_INFO_IRQ_HOOK))
info_flags |= MPU401_INFO_USE_TIMER;
mpu->info_flags = info_flags;
mpu->irq = irq;
if (card->shortname[0])
snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI",
card->shortname);
else
sprintf(rmidi->name, "MPU-401 MIDI %d-%d",card->number, device);
if (out_enable) {
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&snd_mpu401_uart_output);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
}
if (in_enable) {
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&snd_mpu401_uart_input);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
if (out_enable)
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
}
if (rrawmidi)
*rrawmidi = rmidi;
return 0;
free_device:
snd_device_free(card, rmidi);
return err;
}
EXPORT_SYMBOL(snd_mpu401_uart_new);
+738
View File
@@ -0,0 +1,738 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* MOTU Midi Timepiece ALSA Main routines
* Copyright by Michael T. Mayers (c) Jan 09, 2000
* mail: michael@tweakoz.com
* Thanks to John Galbraith
*
* This driver is for the 'Mark Of The Unicorn' (MOTU)
* MidiTimePiece AV multiport MIDI interface
*
* IOPORTS
* -------
* 8 MIDI Ins and 8 MIDI outs
* Video Sync In (BNC), Word Sync Out (BNC),
* ADAT Sync Out (DB9)
* SMPTE in/out (1/4")
* 2 programmable pedal/footswitch inputs and 4 programmable MIDI controller knobs.
* Macintosh RS422 serial port
* RS422 "network" port for ganging multiple MTP's
* PC Parallel Port ( which this driver currently uses )
*
* MISC FEATURES
* -------------
* Hardware MIDI routing, merging, and filtering
* MIDI Synchronization to Video, ADAT, SMPTE and other Clock sources
* 128 'scene' memories, recallable from MIDI program change
*
* ChangeLog
* Jun 11 2001 Takashi Iwai <tiwai@suse.de>
* - Recoded & debugged
* - Added timer interrupt for midi outputs
* - hwports is between 1 and 8, which specifies the number of hardware ports.
* The three global ports, computer, adat and broadcast ports, are created
* always after h/w and remote ports.
*/
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include <linux/delay.h>
#include <linux/string.h>
/*
* globals
*/
MODULE_AUTHOR("Michael T. Mayers");
MODULE_DESCRIPTION("MOTU MidiTimePiece AV multiport MIDI");
MODULE_LICENSE("GPL");
// io resources
#define MTPAV_IOBASE 0x378
#define MTPAV_IRQ 7
#define MTPAV_MAX_PORTS 8
static int index = SNDRV_DEFAULT_IDX1;
static char *id = SNDRV_DEFAULT_STR1;
static long port = MTPAV_IOBASE; /* 0x378, 0x278 */
static int irq = MTPAV_IRQ; /* 7, 5 */
static int hwports = MTPAV_MAX_PORTS; /* use hardware ports 1-8 */
module_param(index, int, 0444);
MODULE_PARM_DESC(index, "Index value for MotuMTPAV MIDI.");
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for MotuMTPAV MIDI.");
module_param_hw(port, long, ioport, 0444);
MODULE_PARM_DESC(port, "Parallel port # for MotuMTPAV MIDI.");
module_param_hw(irq, int, irq, 0444);
MODULE_PARM_DESC(irq, "Parallel IRQ # for MotuMTPAV MIDI.");
module_param(hwports, int, 0444);
MODULE_PARM_DESC(hwports, "Hardware ports # for MotuMTPAV MIDI.");
static struct platform_device *device;
/*
* defines
*/
//#define USE_FAKE_MTP // don't actually read/write to MTP device (for debugging without an actual unit) (does not work yet)
// parallel port usage masks
#define SIGS_BYTE 0x08
#define SIGS_RFD 0x80
#define SIGS_IRQ 0x40
#define SIGS_IN0 0x10
#define SIGS_IN1 0x20
#define SIGC_WRITE 0x04
#define SIGC_READ 0x08
#define SIGC_INTEN 0x10
#define DREG 0
#define SREG 1
#define CREG 2
//
#define MTPAV_MODE_INPUT_OPENED 0x01
#define MTPAV_MODE_OUTPUT_OPENED 0x02
#define MTPAV_MODE_INPUT_TRIGGERED 0x04
#define MTPAV_MODE_OUTPUT_TRIGGERED 0x08
#define NUMPORTS (0x12+1)
/*
*/
struct mtpav_port {
u8 number;
u8 hwport;
u8 mode;
u8 running_status;
struct snd_rawmidi_substream *input;
struct snd_rawmidi_substream *output;
};
struct mtpav {
struct snd_card *card;
unsigned long port;
struct resource *res_port;
int irq; /* interrupt (for inputs) */
spinlock_t spinlock;
int share_irq; /* number of accesses to input interrupts */
int istimer; /* number of accesses to timer interrupts */
struct timer_list timer; /* timer interrupts for outputs */
struct snd_rawmidi *rmidi;
int num_ports; /* number of hw ports (1-8) */
struct mtpav_port ports[NUMPORTS]; /* all ports including computer, adat and bc */
u32 inmidiport; /* selected input midi port */
u32 inmidistate; /* during midi command 0xf5 */
u32 outmidihwport; /* selected output midi hw port */
};
/*
* possible hardware ports (selected by 0xf5 port message)
* 0x00 all ports
* 0x01 .. 0x08 this MTP's ports 1..8
* 0x09 .. 0x10 networked MTP's ports (9..16)
* 0x11 networked MTP's computer port
* 0x63 to ADAT
*
* mappig:
* subdevice 0 - (X-1) ports
* X - (2*X-1) networked ports
* X computer
* X+1 ADAT
* X+2 all ports
*
* where X = chip->num_ports
*/
#define MTPAV_PIDX_COMPUTER 0
#define MTPAV_PIDX_ADAT 1
#define MTPAV_PIDX_BROADCAST 2
static int translate_subdevice_to_hwport(struct mtpav *chip, int subdev)
{
if (subdev < 0)
return 0x01; /* invalid - use port 0 as default */
else if (subdev < chip->num_ports)
return subdev + 1; /* single mtp port */
else if (subdev < chip->num_ports * 2)
return subdev - chip->num_ports + 0x09; /* remote port */
else if (subdev == chip->num_ports * 2 + MTPAV_PIDX_COMPUTER)
return 0x11; /* computer port */
else if (subdev == chip->num_ports + MTPAV_PIDX_ADAT)
return 0x63; /* ADAT */
return 0; /* all ports */
}
static int translate_hwport_to_subdevice(struct mtpav *chip, int hwport)
{
int p;
if (hwport <= 0x00) /* all ports */
return chip->num_ports + MTPAV_PIDX_BROADCAST;
else if (hwport <= 0x08) { /* single port */
p = hwport - 1;
if (p >= chip->num_ports)
p = 0;
return p;
} else if (hwport <= 0x10) { /* remote port */
p = hwport - 0x09 + chip->num_ports;
if (p >= chip->num_ports * 2)
p = chip->num_ports;
return p;
} else if (hwport == 0x11) /* computer port */
return chip->num_ports + MTPAV_PIDX_COMPUTER;
else /* ADAT */
return chip->num_ports + MTPAV_PIDX_ADAT;
}
/*
*/
static u8 snd_mtpav_getreg(struct mtpav *chip, u16 reg)
{
u8 rval = 0;
if (reg == SREG) {
rval = inb(chip->port + SREG);
rval = (rval & 0xf8);
} else if (reg == CREG) {
rval = inb(chip->port + CREG);
rval = (rval & 0x1c);
}
return rval;
}
/*
*/
static inline void snd_mtpav_mputreg(struct mtpav *chip, u16 reg, u8 val)
{
if (reg == DREG || reg == CREG)
outb(val, chip->port + reg);
}
/*
*/
static void snd_mtpav_wait_rfdhi(struct mtpav *chip)
{
int counts = 10000;
u8 sbyte;
sbyte = snd_mtpav_getreg(chip, SREG);
while (!(sbyte & SIGS_RFD) && counts--) {
sbyte = snd_mtpav_getreg(chip, SREG);
udelay(10);
}
}
static void snd_mtpav_send_byte(struct mtpav *chip, u8 byte)
{
u8 tcbyt;
u8 clrwrite;
u8 setwrite;
snd_mtpav_wait_rfdhi(chip);
/////////////////
tcbyt = snd_mtpav_getreg(chip, CREG);
clrwrite = tcbyt & (SIGC_WRITE ^ 0xff);
setwrite = tcbyt | SIGC_WRITE;
snd_mtpav_mputreg(chip, DREG, byte);
snd_mtpav_mputreg(chip, CREG, clrwrite); // clear write bit
snd_mtpav_mputreg(chip, CREG, setwrite); // set write bit
}
/*
*/
/* call this with spin lock held */
static void snd_mtpav_output_port_write(struct mtpav *mtp_card,
struct mtpav_port *portp,
struct snd_rawmidi_substream *substream)
{
u8 outbyte;
// Get the outbyte first, so we can emulate running status if
// necessary
if (snd_rawmidi_transmit(substream, &outbyte, 1) != 1)
return;
// send port change command if necessary
if (portp->hwport != mtp_card->outmidihwport) {
mtp_card->outmidihwport = portp->hwport;
snd_mtpav_send_byte(mtp_card, 0xf5);
snd_mtpav_send_byte(mtp_card, portp->hwport);
if (!(outbyte & 0x80) && portp->running_status)
snd_mtpav_send_byte(mtp_card, portp->running_status);
}
// send data
do {
if (outbyte & 0x80)
portp->running_status = outbyte;
snd_mtpav_send_byte(mtp_card, outbyte);
} while (snd_rawmidi_transmit(substream, &outbyte, 1) == 1);
}
static void snd_mtpav_output_write(struct snd_rawmidi_substream *substream)
{
struct mtpav *mtp_card = substream->rmidi->private_data;
struct mtpav_port *portp = &mtp_card->ports[substream->number];
guard(spinlock_irqsave)(&mtp_card->spinlock);
snd_mtpav_output_port_write(mtp_card, portp, substream);
}
/*
* mtpav control
*/
static void snd_mtpav_portscan(struct mtpav *chip) // put mtp into smart routing mode
{
u8 p;
for (p = 0; p < 8; p++) {
snd_mtpav_send_byte(chip, 0xf5);
snd_mtpav_send_byte(chip, p);
snd_mtpav_send_byte(chip, 0xfe);
}
}
/*
*/
static int snd_mtpav_input_open(struct snd_rawmidi_substream *substream)
{
struct mtpav *mtp_card = substream->rmidi->private_data;
struct mtpav_port *portp = &mtp_card->ports[substream->number];
guard(spinlock_irqsave)(&mtp_card->spinlock);
portp->mode |= MTPAV_MODE_INPUT_OPENED;
portp->input = substream;
if (mtp_card->share_irq++ == 0)
snd_mtpav_mputreg(mtp_card, CREG, (SIGC_INTEN | SIGC_WRITE)); // enable pport interrupts
return 0;
}
/*
*/
static int snd_mtpav_input_close(struct snd_rawmidi_substream *substream)
{
struct mtpav *mtp_card = substream->rmidi->private_data;
struct mtpav_port *portp = &mtp_card->ports[substream->number];
guard(spinlock_irqsave)(&mtp_card->spinlock);
portp->mode &= ~MTPAV_MODE_INPUT_OPENED;
portp->input = NULL;
if (--mtp_card->share_irq == 0)
snd_mtpav_mputreg(mtp_card, CREG, 0); // disable pport interrupts
return 0;
}
/*
*/
static void snd_mtpav_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct mtpav *mtp_card = substream->rmidi->private_data;
struct mtpav_port *portp = &mtp_card->ports[substream->number];
guard(spinlock_irqsave)(&mtp_card->spinlock);
if (up)
portp->mode |= MTPAV_MODE_INPUT_TRIGGERED;
else
portp->mode &= ~MTPAV_MODE_INPUT_TRIGGERED;
}
/*
* timer interrupt for outputs
*/
static void snd_mtpav_output_timer(struct timer_list *t)
{
struct mtpav *chip = timer_container_of(chip, t, timer);
int p;
guard(spinlock_irqsave)(&chip->spinlock);
/* reprogram timer */
mod_timer(&chip->timer, 1 + jiffies);
/* process each port */
for (p = 0; p <= chip->num_ports * 2 + MTPAV_PIDX_BROADCAST; p++) {
struct mtpav_port *portp = &chip->ports[p];
if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED) && portp->output)
snd_mtpav_output_port_write(chip, portp, portp->output);
}
}
/* spinlock held! */
static void snd_mtpav_add_output_timer(struct mtpav *chip)
{
mod_timer(&chip->timer, 1 + jiffies);
}
/* spinlock held! */
static void snd_mtpav_remove_output_timer(struct mtpav *chip)
{
timer_delete(&chip->timer);
}
/*
*/
static int snd_mtpav_output_open(struct snd_rawmidi_substream *substream)
{
struct mtpav *mtp_card = substream->rmidi->private_data;
struct mtpav_port *portp = &mtp_card->ports[substream->number];
guard(spinlock_irqsave)(&mtp_card->spinlock);
portp->mode |= MTPAV_MODE_OUTPUT_OPENED;
portp->output = substream;
return 0;
};
/*
*/
static int snd_mtpav_output_close(struct snd_rawmidi_substream *substream)
{
struct mtpav *mtp_card = substream->rmidi->private_data;
struct mtpav_port *portp = &mtp_card->ports[substream->number];
guard(spinlock_irqsave)(&mtp_card->spinlock);
portp->mode &= ~MTPAV_MODE_OUTPUT_OPENED;
portp->output = NULL;
return 0;
};
/*
*/
static void snd_mtpav_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct mtpav *mtp_card = substream->rmidi->private_data;
struct mtpav_port *portp = &mtp_card->ports[substream->number];
scoped_guard(spinlock_irqsave, &mtp_card->spinlock) {
if (up) {
if ((portp->mode & MTPAV_MODE_OUTPUT_TRIGGERED)) {
if (mtp_card->istimer++ == 0)
snd_mtpav_add_output_timer(mtp_card);
portp->mode |= MTPAV_MODE_OUTPUT_TRIGGERED;
}
} else {
portp->mode &= ~MTPAV_MODE_OUTPUT_TRIGGERED;
if (--mtp_card->istimer == 0)
snd_mtpav_remove_output_timer(mtp_card);
}
}
if (up)
snd_mtpav_output_write(substream);
}
/*
* midi interrupt for inputs
*/
static void snd_mtpav_inmidi_process(struct mtpav *mcrd, u8 inbyte)
{
struct mtpav_port *portp;
if ((int)mcrd->inmidiport > mcrd->num_ports * 2 + MTPAV_PIDX_BROADCAST)
return;
portp = &mcrd->ports[mcrd->inmidiport];
if (portp->mode & MTPAV_MODE_INPUT_TRIGGERED)
snd_rawmidi_receive(portp->input, &inbyte, 1);
}
static void snd_mtpav_inmidi_h(struct mtpav *mcrd, u8 inbyte)
{
if (inbyte >= 0xf8) {
/* real-time midi code */
snd_mtpav_inmidi_process(mcrd, inbyte);
return;
}
if (mcrd->inmidistate == 0) { // awaiting command
if (inbyte == 0xf5) // MTP port #
mcrd->inmidistate = 1;
else
snd_mtpav_inmidi_process(mcrd, inbyte);
} else if (mcrd->inmidistate) {
mcrd->inmidiport = translate_hwport_to_subdevice(mcrd, inbyte);
mcrd->inmidistate = 0;
}
}
static void snd_mtpav_read_bytes(struct mtpav *mcrd)
{
u8 clrread, setread;
u8 mtp_read_byte;
u8 sr, cbyt;
int i;
u8 sbyt = snd_mtpav_getreg(mcrd, SREG);
if (!(sbyt & SIGS_BYTE))
return;
cbyt = snd_mtpav_getreg(mcrd, CREG);
clrread = cbyt & (SIGC_READ ^ 0xff);
setread = cbyt | SIGC_READ;
do {
mtp_read_byte = 0;
for (i = 0; i < 4; i++) {
snd_mtpav_mputreg(mcrd, CREG, setread);
sr = snd_mtpav_getreg(mcrd, SREG);
snd_mtpav_mputreg(mcrd, CREG, clrread);
sr &= SIGS_IN0 | SIGS_IN1;
sr >>= 4;
mtp_read_byte |= sr << (i * 2);
}
snd_mtpav_inmidi_h(mcrd, mtp_read_byte);
sbyt = snd_mtpav_getreg(mcrd, SREG);
} while (sbyt & SIGS_BYTE);
}
static irqreturn_t snd_mtpav_irqh(int irq, void *dev_id)
{
struct mtpav *mcard = dev_id;
guard(spinlock)(&mcard->spinlock);
snd_mtpav_read_bytes(mcard);
return IRQ_HANDLED;
}
/*
* get ISA resources
*/
static int snd_mtpav_get_ISA(struct mtpav *mcard)
{
mcard->res_port = devm_request_region(mcard->card->dev, port, 3,
"MotuMTPAV MIDI");
if (!mcard->res_port) {
dev_err(mcard->card->dev, "MTVAP port 0x%lx is busy\n", port);
return -EBUSY;
}
mcard->port = port;
if (devm_request_irq(mcard->card->dev, irq, snd_mtpav_irqh, 0,
"MOTU MTPAV", mcard)) {
dev_err(mcard->card->dev, "MTVAP IRQ %d busy\n", irq);
return -EBUSY;
}
mcard->irq = irq;
return 0;
}
/*
*/
static const struct snd_rawmidi_ops snd_mtpav_output = {
.open = snd_mtpav_output_open,
.close = snd_mtpav_output_close,
.trigger = snd_mtpav_output_trigger,
};
static const struct snd_rawmidi_ops snd_mtpav_input = {
.open = snd_mtpav_input_open,
.close = snd_mtpav_input_close,
.trigger = snd_mtpav_input_trigger,
};
/*
* get RAWMIDI resources
*/
static void snd_mtpav_set_name(struct mtpav *chip,
struct snd_rawmidi_substream *substream)
{
if (substream->number >= 0 && substream->number < chip->num_ports)
sprintf(substream->name, "MTP direct %d", (substream->number % chip->num_ports) + 1);
else if (substream->number >= 8 && substream->number < chip->num_ports * 2)
sprintf(substream->name, "MTP remote %d", (substream->number % chip->num_ports) + 1);
else if (substream->number == chip->num_ports * 2)
strscpy(substream->name, "MTP computer");
else if (substream->number == chip->num_ports * 2 + 1)
strscpy(substream->name, "MTP ADAT");
else
strscpy(substream->name, "MTP broadcast");
}
static int snd_mtpav_get_RAWMIDI(struct mtpav *mcard)
{
int rval;
struct snd_rawmidi *rawmidi;
struct snd_rawmidi_substream *substream;
struct list_head *list;
if (hwports < 1)
hwports = 1;
else if (hwports > 8)
hwports = 8;
mcard->num_ports = hwports;
rval = snd_rawmidi_new(mcard->card, "MotuMIDI", 0,
mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,
mcard->num_ports * 2 + MTPAV_PIDX_BROADCAST + 1,
&mcard->rmidi);
if (rval < 0)
return rval;
rawmidi = mcard->rmidi;
rawmidi->private_data = mcard;
list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) {
substream = list_entry(list, struct snd_rawmidi_substream, list);
snd_mtpav_set_name(mcard, substream);
substream->ops = &snd_mtpav_input;
}
list_for_each(list, &rawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) {
substream = list_entry(list, struct snd_rawmidi_substream, list);
snd_mtpav_set_name(mcard, substream);
substream->ops = &snd_mtpav_output;
mcard->ports[substream->number].hwport = translate_subdevice_to_hwport(mcard, substream->number);
}
rawmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
sprintf(rawmidi->name, "MTP AV MIDI");
return 0;
}
/*
*/
static void snd_mtpav_free(struct snd_card *card)
{
struct mtpav *crd = card->private_data;
guard(spinlock_irqsave)(&crd->spinlock);
if (crd->istimer > 0)
snd_mtpav_remove_output_timer(crd);
}
/*
*/
static int snd_mtpav_probe(struct platform_device *dev)
{
struct snd_card *card;
int err;
struct mtpav *mtp_card;
err = snd_devm_card_new(&dev->dev, index, id, THIS_MODULE,
sizeof(*mtp_card), &card);
if (err < 0)
return err;
mtp_card = card->private_data;
spin_lock_init(&mtp_card->spinlock);
mtp_card->card = card;
mtp_card->irq = -1;
mtp_card->share_irq = 0;
mtp_card->inmidistate = 0;
mtp_card->outmidihwport = 0xffffffff;
timer_setup(&mtp_card->timer, snd_mtpav_output_timer, 0);
err = snd_mtpav_get_RAWMIDI(mtp_card);
if (err < 0)
return err;
mtp_card->inmidiport = mtp_card->num_ports + MTPAV_PIDX_BROADCAST;
err = snd_mtpav_get_ISA(mtp_card);
if (err < 0)
return err;
strscpy(card->driver, "MTPAV");
strscpy(card->shortname, "MTPAV on parallel port");
snprintf(card->longname, sizeof(card->longname),
"MTPAV on parallel port at 0x%lx", port);
snd_mtpav_portscan(mtp_card);
err = snd_card_register(mtp_card->card);
if (err < 0)
return err;
card->private_free = snd_mtpav_free;
platform_set_drvdata(dev, card);
dev_info(card->dev,
"Motu MidiTimePiece on parallel port irq: %d ioport: 0x%lx\n",
irq, port);
return 0;
}
#define SND_MTPAV_DRIVER "snd_mtpav"
static struct platform_driver snd_mtpav_driver = {
.probe = snd_mtpav_probe,
.driver = {
.name = SND_MTPAV_DRIVER,
},
};
static int __init alsa_card_mtpav_init(void)
{
int err;
err = platform_driver_register(&snd_mtpav_driver);
if (err < 0)
return err;
device = platform_device_register_simple(SND_MTPAV_DRIVER, -1, NULL, 0);
if (!IS_ERR(device)) {
if (platform_get_drvdata(device))
return 0;
platform_device_unregister(device);
err = -ENODEV;
} else
err = PTR_ERR(device);
platform_driver_unregister(&snd_mtpav_driver);
return err;
}
static void __exit alsa_card_mtpav_exit(void)
{
platform_device_unregister(device);
platform_driver_unregister(&snd_mtpav_driver);
}
module_init(alsa_card_mtpav_init)
module_exit(alsa_card_mtpav_exit)
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-opl3-lib-y := opl3_lib.o opl3_synth.o
snd-opl3-synth-y := opl3_seq.o opl3_midi.o opl3_drums.o
ifneq ($(CONFIG_SND_SEQUENCER_OSS),)
snd-opl3-synth-y += opl3_oss.o
endif
obj-$(CONFIG_SND_OPL3_LIB) += snd-opl3-lib.o
obj-$(CONFIG_SND_OPL4_LIB) += snd-opl3-lib.o
obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-opl3-synth.o
+210
View File
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Uros Bizjak <uros@kss-loka.si>
*
* OPL2/OPL3/OPL4 FM routines for internal percussion channels
*/
#include "opl3_voice.h"
static const char snd_opl3_drum_table[47] =
{
OPL3_BASSDRUM_ON, OPL3_BASSDRUM_ON, OPL3_HIHAT_ON, /* 35 - 37 */
OPL3_SNAREDRUM_ON, OPL3_HIHAT_ON, OPL3_SNAREDRUM_ON, /* 38 - 40 */
OPL3_BASSDRUM_ON, OPL3_HIHAT_ON, OPL3_BASSDRUM_ON, /* 41 - 43 */
OPL3_HIHAT_ON, OPL3_TOMTOM_ON, OPL3_HIHAT_ON, /* 44 - 46 */
OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_CYMBAL_ON, /* 47 - 49 */
OPL3_TOMTOM_ON, OPL3_CYMBAL_ON, OPL3_CYMBAL_ON, /* 50 - 52 */
OPL3_CYMBAL_ON, OPL3_CYMBAL_ON, OPL3_CYMBAL_ON, /* 53 - 55 */
OPL3_HIHAT_ON, OPL3_CYMBAL_ON, OPL3_TOMTOM_ON, /* 56 - 58 */
OPL3_CYMBAL_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 59 - 61 */
OPL3_HIHAT_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 62 - 64 */
OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 65 - 67 */
OPL3_TOMTOM_ON, OPL3_HIHAT_ON, OPL3_HIHAT_ON, /* 68 - 70 */
OPL3_HIHAT_ON, OPL3_HIHAT_ON, OPL3_TOMTOM_ON, /* 71 - 73 */
OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 74 - 76 */
OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, OPL3_TOMTOM_ON, /* 77 - 79 */
OPL3_CYMBAL_ON, OPL3_CYMBAL_ON /* 80 - 81 */
};
struct snd_opl3_drum_voice {
int voice;
int op;
unsigned char am_vib;
unsigned char ksl_level;
unsigned char attack_decay;
unsigned char sustain_release;
unsigned char feedback_connection;
unsigned char wave_select;
};
struct snd_opl3_drum_note {
int voice;
unsigned char fnum;
unsigned char octave_f;
unsigned char feedback_connection;
};
static const struct snd_opl3_drum_voice bass_op0 = {6, 0, 0x00, 0x32, 0xf8, 0x66, 0x30, 0x00};
static const struct snd_opl3_drum_voice bass_op1 = {6, 1, 0x00, 0x03, 0xf6, 0x57, 0x30, 0x00};
static const struct snd_opl3_drum_note bass_note = {6, 0x90, 0x09};
static const struct snd_opl3_drum_voice hihat = {7, 0, 0x00, 0x03, 0xf0, 0x06, 0x20, 0x00};
static const struct snd_opl3_drum_voice snare = {7, 1, 0x00, 0x03, 0xf0, 0x07, 0x20, 0x02};
static const struct snd_opl3_drum_note snare_note = {7, 0xf4, 0x0d};
static const struct snd_opl3_drum_voice tomtom = {8, 0, 0x02, 0x03, 0xf0, 0x06, 0x10, 0x00};
static const struct snd_opl3_drum_note tomtom_note = {8, 0xf4, 0x09};
static const struct snd_opl3_drum_voice cymbal = {8, 1, 0x04, 0x03, 0xf0, 0x06, 0x10, 0x00};
/*
* set drum voice characteristics
*/
static void snd_opl3_drum_voice_set(struct snd_opl3 *opl3,
const struct snd_opl3_drum_voice *data)
{
unsigned char op_offset = snd_opl3_regmap[data->voice][data->op];
unsigned char voice_offset = data->voice;
unsigned short opl3_reg;
/* Set OPL3 AM_VIB register */
opl3_reg = OPL3_LEFT | (OPL3_REG_AM_VIB + op_offset);
opl3->command(opl3, opl3_reg, data->am_vib);
/* Set OPL3 KSL_LEVEL register */
opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset);
opl3->command(opl3, opl3_reg, data->ksl_level);
/* Set OPL3 ATTACK_DECAY register */
opl3_reg = OPL3_LEFT | (OPL3_REG_ATTACK_DECAY + op_offset);
opl3->command(opl3, opl3_reg, data->attack_decay);
/* Set OPL3 SUSTAIN_RELEASE register */
opl3_reg = OPL3_LEFT | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
opl3->command(opl3, opl3_reg, data->sustain_release);
/* Set OPL3 FEEDBACK_CONNECTION register */
opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
opl3->command(opl3, opl3_reg, data->feedback_connection);
/* Select waveform */
opl3_reg = OPL3_LEFT | (OPL3_REG_WAVE_SELECT + op_offset);
opl3->command(opl3, opl3_reg, data->wave_select);
}
/*
* Set drum voice pitch
*/
static void snd_opl3_drum_note_set(struct snd_opl3 *opl3,
const struct snd_opl3_drum_note *data)
{
unsigned char voice_offset = data->voice;
unsigned short opl3_reg;
/* Set OPL3 FNUM_LOW register */
opl3_reg = OPL3_LEFT | (OPL3_REG_FNUM_LOW + voice_offset);
opl3->command(opl3, opl3_reg, data->fnum);
/* Set OPL3 KEYON_BLOCK register */
opl3_reg = OPL3_LEFT | (OPL3_REG_KEYON_BLOCK + voice_offset);
opl3->command(opl3, opl3_reg, data->octave_f);
}
/*
* Set drum voice volume and position
*/
static void snd_opl3_drum_vol_set(struct snd_opl3 *opl3,
const struct snd_opl3_drum_voice *data,
int vel, struct snd_midi_channel *chan)
{
unsigned char op_offset = snd_opl3_regmap[data->voice][data->op];
unsigned char voice_offset = data->voice;
unsigned char reg_val;
unsigned short opl3_reg;
/* Set OPL3 KSL_LEVEL register */
reg_val = data->ksl_level;
snd_opl3_calc_volume(&reg_val, vel, chan);
opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set OPL3 FEEDBACK_CONNECTION register */
/* Set output voice connection */
reg_val = data->feedback_connection | OPL3_STEREO_BITS;
if (chan->gm_pan < 43)
reg_val &= ~OPL3_VOICE_TO_RIGHT;
if (chan->gm_pan > 85)
reg_val &= ~OPL3_VOICE_TO_LEFT;
opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
opl3->command(opl3, opl3_reg, reg_val);
}
/*
* Loads drum voices at init time
*/
void snd_opl3_load_drums(struct snd_opl3 *opl3)
{
snd_opl3_drum_voice_set(opl3, &bass_op0);
snd_opl3_drum_voice_set(opl3, &bass_op1);
snd_opl3_drum_note_set(opl3, &bass_note);
snd_opl3_drum_voice_set(opl3, &hihat);
snd_opl3_drum_voice_set(opl3, &snare);
snd_opl3_drum_note_set(opl3, &snare_note);
snd_opl3_drum_voice_set(opl3, &tomtom);
snd_opl3_drum_note_set(opl3, &tomtom_note);
snd_opl3_drum_voice_set(opl3, &cymbal);
}
/*
* Switch drum voice on or off
*/
void snd_opl3_drum_switch(struct snd_opl3 *opl3, int note, int vel, int on_off,
struct snd_midi_channel *chan)
{
unsigned char drum_mask;
const struct snd_opl3_drum_voice *drum_voice;
if (!(opl3->drum_reg & OPL3_PERCUSSION_ENABLE))
return;
if ((note < 35) || (note > 81))
return;
drum_mask = snd_opl3_drum_table[note - 35];
if (on_off) {
switch (drum_mask) {
case OPL3_BASSDRUM_ON:
drum_voice = &bass_op1;
break;
case OPL3_HIHAT_ON:
drum_voice = &hihat;
break;
case OPL3_SNAREDRUM_ON:
drum_voice = &snare;
break;
case OPL3_TOMTOM_ON:
drum_voice = &tomtom;
break;
case OPL3_CYMBAL_ON:
drum_voice = &cymbal;
break;
default:
drum_voice = &tomtom;
}
snd_opl3_drum_vol_set(opl3, drum_voice, vel, chan);
opl3->drum_reg |= drum_mask;
} else {
opl3->drum_reg &= ~drum_mask;
}
opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
opl3->drum_reg);
}
+522
View File
@@ -0,0 +1,522 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
* Hannu Savolainen 1993-1996,
* Rob Hooft
*
* Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)
*
* Most if code is ported from OSS/Lite.
*/
#include <sound/opl3.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <sound/minors.h>
#include "opl3_voice.h"
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Hannu Savolainen 1993-1996, Rob Hooft");
MODULE_DESCRIPTION("Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)");
MODULE_LICENSE("GPL");
static void snd_opl2_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
{
unsigned long port;
/*
* The original 2-OP synth requires a quite long delay
* after writing to a register.
*/
port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
guard(spinlock_irqsave)(&opl3->reg_lock);
outb((unsigned char) cmd, port);
udelay(10);
outb((unsigned char) val, port + 1);
udelay(30);
}
static void snd_opl3_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
{
unsigned long port;
/*
* The OPL-3 survives with just two INBs
* after writing to a register.
*/
port = (cmd & OPL3_RIGHT) ? opl3->r_port : opl3->l_port;
guard(spinlock_irqsave)(&opl3->reg_lock);
outb((unsigned char) cmd, port);
inb(opl3->l_port);
inb(opl3->l_port);
outb((unsigned char) val, port + 1);
inb(opl3->l_port);
inb(opl3->l_port);
}
static int snd_opl3_detect(struct snd_opl3 * opl3)
{
/*
* This function returns 1 if the FM chip is present at the given I/O port
* The detection algorithm plays with the timer built in the FM chip and
* looks for a change in the status register.
*
* Note! The timers of the FM chip are not connected to AdLib (and compatible)
* boards.
*
* Note2! The chip is initialized if detected.
*/
unsigned char stat1, stat2, signature;
/* Reset timers 1 and 2 */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
/* Reset the IRQ of the FM chip */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
signature = stat1 = inb(opl3->l_port); /* Status register */
if ((stat1 & 0xe0) != 0x00) { /* Should be 0x00 */
dev_dbg(opl3->card->dev, "OPL3: stat1 = 0x%x\n", stat1);
return -ENODEV;
}
/* Set timer1 to 0xff */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 0xff);
/* Unmask and start timer 1 */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER2_MASK | OPL3_TIMER1_START);
/* Now we have to delay at least 80us */
udelay(200);
/* Read status after timers have expired */
stat2 = inb(opl3->l_port);
/* Stop the timers */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_TIMER1_MASK | OPL3_TIMER2_MASK);
/* Reset the IRQ of the FM chip */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, OPL3_IRQ_RESET);
if ((stat2 & 0xe0) != 0xc0) { /* There is no YM3812 */
dev_dbg(opl3->card->dev, "OPL3: stat2 = 0x%x\n", stat2);
return -ENODEV;
}
/* If the toplevel code knows exactly the type of chip, don't try
to detect it. */
if (opl3->hardware != OPL3_HW_AUTO)
return 0;
/* There is a FM chip on this address. Detect the type (OPL2 to OPL4) */
if (signature == 0x06) { /* OPL2 */
opl3->hardware = OPL3_HW_OPL2;
} else {
/*
* If we had an OPL4 chip, opl3->hardware would have been set
* by the OPL4 driver; so we can assume OPL3 here.
*/
if (snd_BUG_ON(!opl3->r_port))
return -ENODEV;
opl3->hardware = OPL3_HW_OPL3;
}
return 0;
}
/*
* AdLib timers
*/
/*
* Timer 1 - 80us
*/
static int snd_opl3_timer1_start(struct snd_timer * timer)
{
unsigned char tmp;
unsigned int ticks;
struct snd_opl3 *opl3;
opl3 = snd_timer_chip(timer);
guard(spinlock_irqsave)(&opl3->timer_lock);
ticks = timer->sticks;
tmp = (opl3->timer_enable | OPL3_TIMER1_START) & ~OPL3_TIMER1_MASK;
opl3->timer_enable = tmp;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER1, 256 - ticks); /* timer 1 count */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* enable timer 1 IRQ */
return 0;
}
static int snd_opl3_timer1_stop(struct snd_timer * timer)
{
unsigned char tmp;
struct snd_opl3 *opl3;
opl3 = snd_timer_chip(timer);
guard(spinlock_irqsave)(&opl3->timer_lock);
tmp = (opl3->timer_enable | OPL3_TIMER1_MASK) & ~OPL3_TIMER1_START;
opl3->timer_enable = tmp;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* disable timer #1 */
return 0;
}
/*
* Timer 2 - 320us
*/
static int snd_opl3_timer2_start(struct snd_timer * timer)
{
unsigned char tmp;
unsigned int ticks;
struct snd_opl3 *opl3;
opl3 = snd_timer_chip(timer);
guard(spinlock_irqsave)(&opl3->timer_lock);
ticks = timer->sticks;
tmp = (opl3->timer_enable | OPL3_TIMER2_START) & ~OPL3_TIMER2_MASK;
opl3->timer_enable = tmp;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER2, 256 - ticks); /* timer 1 count */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* enable timer 1 IRQ */
return 0;
}
static int snd_opl3_timer2_stop(struct snd_timer * timer)
{
unsigned char tmp;
struct snd_opl3 *opl3;
opl3 = snd_timer_chip(timer);
guard(spinlock_irqsave)(&opl3->timer_lock);
tmp = (opl3->timer_enable | OPL3_TIMER2_MASK) & ~OPL3_TIMER2_START;
opl3->timer_enable = tmp;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TIMER_CONTROL, tmp); /* disable timer #1 */
return 0;
}
/*
*/
static const struct snd_timer_hardware snd_opl3_timer1 =
{
.flags = SNDRV_TIMER_HW_STOP,
.resolution = 80000,
.ticks = 256,
.start = snd_opl3_timer1_start,
.stop = snd_opl3_timer1_stop,
};
static const struct snd_timer_hardware snd_opl3_timer2 =
{
.flags = SNDRV_TIMER_HW_STOP,
.resolution = 320000,
.ticks = 256,
.start = snd_opl3_timer2_start,
.stop = snd_opl3_timer2_stop,
};
static int snd_opl3_timer1_init(struct snd_opl3 * opl3, int timer_no)
{
struct snd_timer *timer = NULL;
struct snd_timer_id tid;
int err;
tid.dev_class = SNDRV_TIMER_CLASS_CARD;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.card = opl3->card->number;
tid.device = timer_no;
tid.subdevice = 0;
err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer);
if (err >= 0) {
strscpy(timer->name, "AdLib timer #1");
timer->private_data = opl3;
timer->hw = snd_opl3_timer1;
}
opl3->timer1 = timer;
return err;
}
static int snd_opl3_timer2_init(struct snd_opl3 * opl3, int timer_no)
{
struct snd_timer *timer = NULL;
struct snd_timer_id tid;
int err;
tid.dev_class = SNDRV_TIMER_CLASS_CARD;
tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE;
tid.card = opl3->card->number;
tid.device = timer_no;
tid.subdevice = 0;
err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer);
if (err >= 0) {
strscpy(timer->name, "AdLib timer #2");
timer->private_data = opl3;
timer->hw = snd_opl3_timer2;
}
opl3->timer2 = timer;
return err;
}
/*
*/
void snd_opl3_interrupt(struct snd_hwdep * hw)
{
unsigned char status;
struct snd_opl3 *opl3;
struct snd_timer *timer;
if (hw == NULL)
return;
opl3 = hw->private_data;
status = inb(opl3->l_port);
if (!(status & 0x80))
return;
if (status & 0x40) {
timer = opl3->timer1;
snd_timer_interrupt(timer, timer->sticks);
}
if (status & 0x20) {
timer = opl3->timer2;
snd_timer_interrupt(timer, timer->sticks);
}
}
EXPORT_SYMBOL(snd_opl3_interrupt);
/*
*/
static int snd_opl3_free(struct snd_opl3 *opl3)
{
if (snd_BUG_ON(!opl3))
return -ENXIO;
if (opl3->private_free)
opl3->private_free(opl3);
snd_opl3_clear_patches(opl3);
release_and_free_resource(opl3->res_l_port);
release_and_free_resource(opl3->res_r_port);
kfree(opl3);
return 0;
}
static int snd_opl3_dev_free(struct snd_device *device)
{
struct snd_opl3 *opl3 = device->device_data;
return snd_opl3_free(opl3);
}
int snd_opl3_new(struct snd_card *card,
unsigned short hardware,
struct snd_opl3 **ropl3)
{
static const struct snd_device_ops ops = {
.dev_free = snd_opl3_dev_free,
};
struct snd_opl3 *opl3;
int err;
*ropl3 = NULL;
opl3 = kzalloc_obj(*opl3);
if (!opl3)
return -ENOMEM;
opl3->card = card;
opl3->hardware = hardware;
spin_lock_init(&opl3->reg_lock);
spin_lock_init(&opl3->timer_lock);
err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops);
if (err < 0) {
snd_opl3_free(opl3);
return err;
}
*ropl3 = opl3;
return 0;
}
EXPORT_SYMBOL(snd_opl3_new);
int snd_opl3_init(struct snd_opl3 *opl3)
{
if (! opl3->command) {
dev_err(opl3->card->dev,
"snd_opl3_init: command not defined!\n");
return -EINVAL;
}
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
/* Melodic mode */
opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00);
switch (opl3->hardware & OPL3_HW_MASK) {
case OPL3_HW_OPL2:
opl3->max_voices = MAX_OPL2_VOICES;
break;
case OPL3_HW_OPL3:
case OPL3_HW_OPL4:
opl3->max_voices = MAX_OPL3_VOICES;
/* Enter OPL3 mode */
opl3->command(opl3, OPL3_RIGHT | OPL3_REG_MODE, OPL3_OPL3_ENABLE);
}
return 0;
}
EXPORT_SYMBOL(snd_opl3_init);
int snd_opl3_create(struct snd_card *card,
unsigned long l_port,
unsigned long r_port,
unsigned short hardware,
int integrated,
struct snd_opl3 ** ropl3)
{
struct snd_opl3 *opl3;
int err;
*ropl3 = NULL;
err = snd_opl3_new(card, hardware, &opl3);
if (err < 0)
return err;
if (! integrated) {
opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)");
if (!opl3->res_l_port) {
dev_err(card->dev, "opl3: can't grab left port 0x%lx\n", l_port);
snd_device_free(card, opl3);
return -EBUSY;
}
if (r_port != 0) {
opl3->res_r_port = request_region(r_port, 2, "OPL2/3 (right)");
if (!opl3->res_r_port) {
dev_err(card->dev, "opl3: can't grab right port 0x%lx\n", r_port);
snd_device_free(card, opl3);
return -EBUSY;
}
}
}
opl3->l_port = l_port;
opl3->r_port = r_port;
switch (opl3->hardware) {
/* some hardware doesn't support timers */
case OPL3_HW_OPL3_SV:
case OPL3_HW_OPL3_CS:
case OPL3_HW_OPL3_FM801:
opl3->command = &snd_opl3_command;
break;
default:
opl3->command = &snd_opl2_command;
err = snd_opl3_detect(opl3);
if (err < 0) {
dev_dbg(card->dev, "OPL2/3 chip not detected at 0x%lx/0x%lx\n",
opl3->l_port, opl3->r_port);
snd_device_free(card, opl3);
return err;
}
/* detect routine returns correct hardware type */
switch (opl3->hardware & OPL3_HW_MASK) {
case OPL3_HW_OPL3:
case OPL3_HW_OPL4:
opl3->command = &snd_opl3_command;
}
}
snd_opl3_init(opl3);
*ropl3 = opl3;
return 0;
}
EXPORT_SYMBOL(snd_opl3_create);
int snd_opl3_timer_new(struct snd_opl3 * opl3, int timer1_dev, int timer2_dev)
{
int err;
if (timer1_dev >= 0) {
err = snd_opl3_timer1_init(opl3, timer1_dev);
if (err < 0)
return err;
}
if (timer2_dev >= 0) {
err = snd_opl3_timer2_init(opl3, timer2_dev);
if (err < 0) {
snd_device_free(opl3->card, opl3->timer1);
opl3->timer1 = NULL;
return err;
}
}
return 0;
}
EXPORT_SYMBOL(snd_opl3_timer_new);
int snd_opl3_hwdep_new(struct snd_opl3 * opl3,
int device, int seq_device,
struct snd_hwdep ** rhwdep)
{
struct snd_hwdep *hw;
struct snd_card *card = opl3->card;
int err;
if (rhwdep)
*rhwdep = NULL;
/* create hardware dependent device (direct FM) */
err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw);
if (err < 0) {
snd_device_free(card, opl3);
return err;
}
hw->private_data = opl3;
hw->exclusive = 1;
#ifdef CONFIG_SND_OSSEMUL
if (device == 0)
hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM;
#endif
strscpy(hw->name, hw->id);
switch (opl3->hardware & OPL3_HW_MASK) {
case OPL3_HW_OPL2:
strscpy(hw->name, "OPL2 FM");
hw->iface = SNDRV_HWDEP_IFACE_OPL2;
break;
case OPL3_HW_OPL3:
strscpy(hw->name, "OPL3 FM");
hw->iface = SNDRV_HWDEP_IFACE_OPL3;
break;
case OPL3_HW_OPL4:
strscpy(hw->name, "OPL4 FM");
hw->iface = SNDRV_HWDEP_IFACE_OPL4;
break;
}
/* operators - only ioctl */
hw->ops.open = snd_opl3_open;
hw->ops.ioctl = snd_opl3_ioctl;
hw->ops.write = snd_opl3_write;
hw->ops.release = snd_opl3_release;
opl3->hwdep = hw;
opl3->seq_dev_num = seq_device;
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
if (snd_seq_device_new(card, seq_device, SNDRV_SEQ_DEV_ID_OPL3,
sizeof(struct snd_opl3 *), &opl3->seq_dev) >= 0) {
strscpy(opl3->seq_dev->name, hw->name);
*(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(opl3->seq_dev) = opl3;
}
#endif
if (rhwdep)
*rhwdep = hw;
return 0;
}
EXPORT_SYMBOL(snd_opl3_hwdep_new);
+823
View File
@@ -0,0 +1,823 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Uros Bizjak <uros@kss-loka.si>
*
* Midi synth routines for OPL2/OPL3/OPL4 FM
*/
#undef DEBUG_ALLOC
#undef DEBUG_MIDI
#include "opl3_voice.h"
#include <sound/asoundef.h>
#ifdef DEBUG_MIDI
#define opl3_dbg(opl3, fmt, ...) \
dev_dbg(((struct snd_opl3 *)(opl3))->card->dev, fmt, ##__VA_ARGS__)
#else
#define opl3_dbg(opl3, fmt, ...) do {} while (0)
#endif
static void snd_opl3_note_off_unsafe(void *p, int note, int vel,
struct snd_midi_channel *chan);
/*
* The next table looks magical, but it certainly is not. Its values have
* been calculated as table[i]=8*log(i/64)/log(2) with an obvious exception
* for i=0. This log-table converts a linear volume-scaling (0..127) to a
* logarithmic scaling as present in the FM-synthesizer chips. so : Volume
* 64 = 0 db = relative volume 0 and: Volume 32 = -6 db = relative
* volume -8 it was implemented as a table because it is only 128 bytes and
* it saves a lot of log() calculations. (Rob Hooft <hooft@chem.ruu.nl>)
*/
static const char opl3_volume_table[128] =
{
-63, -48, -40, -35, -32, -29, -27, -26,
-24, -23, -21, -20, -19, -18, -18, -17,
-16, -15, -15, -14, -13, -13, -12, -12,
-11, -11, -10, -10, -10, -9, -9, -8,
-8, -8, -7, -7, -7, -6, -6, -6,
-5, -5, -5, -5, -4, -4, -4, -4,
-3, -3, -3, -3, -2, -2, -2, -2,
-2, -1, -1, -1, -1, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 4,
4, 4, 4, 4, 4, 4, 4, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6,
6, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 8, 8, 8, 8, 8
};
void snd_opl3_calc_volume(unsigned char *volbyte, int vel,
struct snd_midi_channel *chan)
{
int oldvol, newvol, n;
int volume;
volume = (vel * chan->gm_volume * chan->gm_expression) / (127*127);
if (volume > 127)
volume = 127;
oldvol = OPL3_TOTAL_LEVEL_MASK - (*volbyte & OPL3_TOTAL_LEVEL_MASK);
newvol = opl3_volume_table[volume] + oldvol;
if (newvol > OPL3_TOTAL_LEVEL_MASK)
newvol = OPL3_TOTAL_LEVEL_MASK;
else if (newvol < 0)
newvol = 0;
n = OPL3_TOTAL_LEVEL_MASK - (newvol & OPL3_TOTAL_LEVEL_MASK);
*volbyte = (*volbyte & OPL3_KSL_MASK) | (n & OPL3_TOTAL_LEVEL_MASK);
}
/*
* Converts the note frequency to block and fnum values for the FM chip
*/
static const short opl3_note_table[16] =
{
305, 323, /* for pitch bending, -2 semitones */
343, 363, 385, 408, 432, 458, 485, 514, 544, 577, 611, 647,
686, 726 /* for pitch bending, +2 semitones */
};
static void snd_opl3_calc_pitch(unsigned char *fnum, unsigned char *blocknum,
int note, struct snd_midi_channel *chan)
{
int block = ((note / 12) & 0x07) - 1;
int idx = (note % 12) + 2;
int freq;
if (chan->midi_pitchbend) {
int pitchbend = chan->midi_pitchbend;
int segment;
if (pitchbend < -0x2000)
pitchbend = -0x2000;
if (pitchbend > 0x1FFF)
pitchbend = 0x1FFF;
segment = pitchbend / 0x1000;
freq = opl3_note_table[idx+segment];
freq += ((opl3_note_table[idx+segment+1] - freq) *
(pitchbend % 0x1000)) / 0x1000;
} else {
freq = opl3_note_table[idx];
}
*fnum = (unsigned char) freq;
*blocknum = ((freq >> 8) & OPL3_FNUM_HIGH_MASK) |
((block << 2) & OPL3_BLOCKNUM_MASK);
}
#ifdef DEBUG_ALLOC
static void debug_alloc(struct snd_opl3 *opl3, char *s, int voice)
{
int i;
const char *str = "x.24";
char buf[MAX_OPL3_VOICES + 1];
for (i = 0; i < opl3->max_voices; i++)
buf[i] = str[opl3->voices[i].state + 1];
buf[i] = 0;
dev_dbg(opl3->card->dev, "time %.5i: %s [%.2i]: %s\n",
opl3->use_time, s, voice, buf);
}
#endif
/*
* Get a FM voice (channel) to play a note on.
*/
static int opl3_get_voice(struct snd_opl3 *opl3, int instr_4op,
struct snd_midi_channel *chan) {
int chan_4op_1; /* first voice for 4op instrument */
int chan_4op_2; /* second voice for 4op instrument */
struct snd_opl3_voice *vp, *vp2;
unsigned int voice_time;
int i;
#ifdef DEBUG_ALLOC
char *alloc_type[3] = { "FREE ", "CHEAP ", "EXPENSIVE" };
#endif
/* This is our "allocation cost" table */
enum {
FREE = 0, CHEAP, EXPENSIVE, END
};
/* Keeps track of what we are finding */
struct best {
unsigned int time;
int voice;
} best[END];
struct best *bp;
for (i = 0; i < END; i++) {
best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */
best[i].voice = -1;
}
/* Look through all the channels for the most suitable. */
for (i = 0; i < opl3->max_voices; i++) {
vp = &opl3->voices[i];
if (vp->state == SNDRV_OPL3_ST_NOT_AVAIL)
/* skip unavailable channels, allocated by
drum voices or by bounded 4op voices) */
continue;
voice_time = vp->time;
bp = best;
chan_4op_1 = ((i < 3) || (i > 8 && i < 12));
chan_4op_2 = ((i > 2 && i < 6) || (i > 11 && i < 15));
if (instr_4op) {
/* allocate 4op voice */
/* skip channels unavailable to 4op instrument */
if (!chan_4op_1)
continue;
if (vp->state)
/* kill one voice, CHEAP */
bp++;
/* get state of bounded 2op channel
to be allocated for 4op instrument */
vp2 = &opl3->voices[i + 3];
if (vp2->state == SNDRV_OPL3_ST_ON_2OP) {
/* kill two voices, EXPENSIVE */
bp++;
voice_time = max(voice_time, vp2->time);
}
} else {
/* allocate 2op voice */
if ((chan_4op_1) || (chan_4op_2))
/* use bounded channels for 2op, CHEAP */
bp++;
else if (vp->state)
/* kill one voice on 2op channel, CHEAP */
bp++;
/* raise kill cost to EXPENSIVE for all channels */
if (vp->state)
bp++;
}
if (voice_time < bp->time) {
bp->time = voice_time;
bp->voice = i;
}
}
for (i = 0; i < END; i++) {
if (best[i].voice >= 0) {
#ifdef DEBUG_ALLOC
dev_dbg(opl3->card->dev,
"%s %iop allocation on voice %i\n",
alloc_type[i], instr_4op ? 4 : 2,
best[i].voice);
#endif
return best[i].voice;
}
}
/* not found */
return -1;
}
/* ------------------------------ */
/*
* System timer interrupt function
*/
void snd_opl3_timer_func(struct timer_list *t)
{
struct snd_opl3 *opl3 = timer_container_of(opl3, t, tlist);
int again = 0;
int i;
scoped_guard(spinlock_irqsave, &opl3->voice_lock) {
for (i = 0; i < opl3->max_voices; i++) {
struct snd_opl3_voice *vp = &opl3->voices[i];
if (vp->state > 0 && vp->note_off_check) {
if (vp->note_off == jiffies)
snd_opl3_note_off_unsafe(opl3, vp->note, 0,
vp->chan);
else
again++;
}
}
}
guard(spinlock_irqsave)(&opl3->sys_timer_lock);
if (again)
mod_timer(&opl3->tlist, jiffies + 1); /* invoke again */
else
opl3->sys_timer_status = 0;
}
/*
* Start system timer
*/
static void snd_opl3_start_timer(struct snd_opl3 *opl3)
{
guard(spinlock_irqsave)(&opl3->sys_timer_lock);
if (! opl3->sys_timer_status) {
mod_timer(&opl3->tlist, jiffies + 1);
opl3->sys_timer_status = 1;
}
}
/* ------------------------------ */
static const int snd_opl3_oss_map[MAX_OPL3_VOICES] = {
0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, 3, 4 ,5, 12, 13, 14
};
/*
* Start a note.
*/
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan)
{
struct snd_opl3 *opl3;
int instr_4op;
int voice;
struct snd_opl3_voice *vp, *vp2;
unsigned short connect_mask;
unsigned char connection;
unsigned char vol_op[4];
int extra_prg = 0;
unsigned short reg_side;
unsigned char op_offset;
unsigned char voice_offset;
unsigned short opl3_reg;
unsigned char reg_val;
unsigned char prg, bank;
int key = note;
unsigned char fnum, blocknum;
int i;
struct fm_patch *patch;
struct fm_instrument *fm;
opl3 = p;
opl3_dbg(opl3, "Note on, ch %i, inst %i, note %i, vel %i\n",
chan->number, chan->midi_program, note, vel);
/* in SYNTH mode, application takes care of voices */
/* in SEQ mode, drum voice numbers are notes on drum channel */
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
if (chan->drum_channel) {
/* percussion instruments are located in bank 128 */
bank = 128;
prg = note;
} else {
bank = chan->gm_bank_select;
prg = chan->midi_program;
}
} else {
/* Prepare for OSS mode */
if (chan->number >= MAX_OPL3_VOICES)
return;
/* OSS instruments are located in bank 127 */
bank = 127;
prg = chan->midi_program;
}
guard(spinlock_irqsave)(&opl3->voice_lock);
if (use_internal_drums) {
snd_opl3_drum_switch(opl3, note, vel, 1, chan);
return;
}
__extra_prg:
patch = snd_opl3_find_patch(opl3, prg, bank, 0);
if (!patch)
return;
fm = &patch->inst;
switch (patch->type) {
case FM_PATCH_OPL2:
instr_4op = 0;
break;
case FM_PATCH_OPL3:
if (opl3->hardware >= OPL3_HW_OPL3) {
instr_4op = 1;
break;
}
fallthrough;
default:
return;
}
opl3_dbg(opl3, " --> OPL%i instrument: %s\n",
instr_4op ? 3 : 2, patch->name);
/* in SYNTH mode, application takes care of voices */
/* in SEQ mode, allocate voice on free OPL3 channel */
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
voice = opl3_get_voice(opl3, instr_4op, chan);
} else {
/* remap OSS voice */
voice = snd_opl3_oss_map[chan->number];
}
if (voice < 0)
return;
if (voice < MAX_OPL2_VOICES) {
/* Left register block for voices 0 .. 8 */
reg_side = OPL3_LEFT;
voice_offset = voice;
connect_mask = (OPL3_LEFT_4OP_0 << voice_offset) & 0x07;
} else {
/* Right register block for voices 9 .. 17 */
reg_side = OPL3_RIGHT;
voice_offset = voice - MAX_OPL2_VOICES;
connect_mask = (OPL3_RIGHT_4OP_0 << voice_offset) & 0x38;
}
/* kill voice on channel */
vp = &opl3->voices[voice];
if (vp->state > 0) {
opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
opl3->command(opl3, opl3_reg, reg_val);
}
if (instr_4op) {
vp2 = &opl3->voices[voice + 3];
if (vp2->state > 0) {
opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK +
voice_offset + 3);
reg_val = vp->keyon_reg & ~OPL3_KEYON_BIT;
opl3->command(opl3, opl3_reg, reg_val);
}
}
/* set connection register */
if (instr_4op) {
if ((opl3->connection_reg ^ connect_mask) & connect_mask) {
opl3->connection_reg |= connect_mask;
/* set connection bit */
opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
opl3->command(opl3, opl3_reg, opl3->connection_reg);
}
} else {
if ((opl3->connection_reg ^ ~connect_mask) & connect_mask) {
opl3->connection_reg &= ~connect_mask;
/* clear connection bit */
opl3_reg = OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT;
opl3->command(opl3, opl3_reg, opl3->connection_reg);
}
}
opl3_dbg(opl3, " --> setting OPL3 connection: 0x%x\n",
opl3->connection_reg);
/*
* calculate volume depending on connection
* between FM operators (see include/opl3.h)
*/
for (i = 0; i < (instr_4op ? 4 : 2); i++)
vol_op[i] = fm->op[i].ksl_level;
connection = fm->feedback_connection[0] & 0x01;
if (instr_4op) {
connection <<= 1;
connection |= fm->feedback_connection[1] & 0x01;
snd_opl3_calc_volume(&vol_op[3], vel, chan);
switch (connection) {
case 0x03:
snd_opl3_calc_volume(&vol_op[2], vel, chan);
fallthrough;
case 0x02:
snd_opl3_calc_volume(&vol_op[0], vel, chan);
break;
case 0x01:
snd_opl3_calc_volume(&vol_op[1], vel, chan);
}
} else {
snd_opl3_calc_volume(&vol_op[1], vel, chan);
if (connection)
snd_opl3_calc_volume(&vol_op[0], vel, chan);
}
/* Program the FM voice characteristics */
for (i = 0; i < (instr_4op ? 4 : 2); i++) {
opl3_dbg(opl3, " --> programming operator %i\n", i);
op_offset = snd_opl3_regmap[voice_offset][i];
/* Set OPL3 AM_VIB register of requested voice/operator */
reg_val = fm->op[i].am_vib;
opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set OPL3 KSL_LEVEL register of requested voice/operator */
reg_val = vol_op[i];
opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set OPL3 ATTACK_DECAY register of requested voice/operator */
reg_val = fm->op[i].attack_decay;
opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */
reg_val = fm->op[i].sustain_release;
opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Select waveform */
reg_val = fm->op[i].wave_select;
opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
}
/* Set operator feedback and 2op inter-operator connection */
reg_val = fm->feedback_connection[0];
/* Set output voice connection */
reg_val |= OPL3_STEREO_BITS;
if (chan->gm_pan < 43)
reg_val &= ~OPL3_VOICE_TO_RIGHT;
if (chan->gm_pan > 85)
reg_val &= ~OPL3_VOICE_TO_LEFT;
opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
opl3->command(opl3, opl3_reg, reg_val);
if (instr_4op) {
/* Set 4op inter-operator connection */
reg_val = fm->feedback_connection[1] & OPL3_CONNECTION_BIT;
/* Set output voice connection */
reg_val |= OPL3_STEREO_BITS;
if (chan->gm_pan < 43)
reg_val &= ~OPL3_VOICE_TO_RIGHT;
if (chan->gm_pan > 85)
reg_val &= ~OPL3_VOICE_TO_LEFT;
opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION +
voice_offset + 3);
opl3->command(opl3, opl3_reg, reg_val);
}
/*
* Special treatment of percussion notes for fm:
* Requested pitch is really program, and pitch for
* device is whatever was specified in the patch library.
*/
if (fm->fix_key)
note = fm->fix_key;
/*
* use transpose if defined in patch library
*/
if (fm->trnsps)
note += (fm->trnsps - 64);
snd_opl3_calc_pitch(&fnum, &blocknum, note, chan);
/* Set OPL3 FNUM_LOW register of requested voice */
opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
opl3->command(opl3, opl3_reg, fnum);
opl3->voices[voice].keyon_reg = blocknum;
/* Set output sound flag */
blocknum |= OPL3_KEYON_BIT;
opl3_dbg(opl3, " --> trigger voice %i\n", voice);
/* Set OPL3 KEYON_BLOCK register of requested voice */
opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
opl3->command(opl3, opl3_reg, blocknum);
/* kill note after fixed duration (in centiseconds) */
if (fm->fix_dur) {
opl3->voices[voice].note_off = jiffies +
(fm->fix_dur * HZ) / 100;
snd_opl3_start_timer(opl3);
opl3->voices[voice].note_off_check = 1;
} else
opl3->voices[voice].note_off_check = 0;
/* get extra pgm, but avoid possible loops */
extra_prg = (extra_prg) ? 0 : fm->modes;
/* do the bookkeeping */
vp->time = opl3->use_time++;
vp->note = key;
vp->chan = chan;
if (instr_4op) {
vp->state = SNDRV_OPL3_ST_ON_4OP;
vp2 = &opl3->voices[voice + 3];
vp2->time = opl3->use_time++;
vp2->note = key;
vp2->chan = chan;
vp2->state = SNDRV_OPL3_ST_NOT_AVAIL;
} else {
if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
/* 4op killed by 2op, release bounded voice */
vp2 = &opl3->voices[voice + 3];
vp2->time = opl3->use_time++;
vp2->state = SNDRV_OPL3_ST_OFF;
}
vp->state = SNDRV_OPL3_ST_ON_2OP;
}
#ifdef DEBUG_ALLOC
debug_alloc(opl3, "note on ", voice);
#endif
/* allocate extra program if specified in patch library */
if (extra_prg) {
if (extra_prg > 128) {
bank = 128;
/* percussions start at 35 */
prg = extra_prg - 128 + 35 - 1;
} else {
bank = 0;
prg = extra_prg - 1;
}
opl3_dbg(opl3, " *** allocating extra program\n");
goto __extra_prg;
}
}
static void snd_opl3_kill_voice(struct snd_opl3 *opl3, int voice)
{
unsigned short reg_side;
unsigned char voice_offset;
unsigned short opl3_reg;
struct snd_opl3_voice *vp, *vp2;
if (snd_BUG_ON(voice >= MAX_OPL3_VOICES))
return;
vp = &opl3->voices[voice];
if (voice < MAX_OPL2_VOICES) {
/* Left register block for voices 0 .. 8 */
reg_side = OPL3_LEFT;
voice_offset = voice;
} else {
/* Right register block for voices 9 .. 17 */
reg_side = OPL3_RIGHT;
voice_offset = voice - MAX_OPL2_VOICES;
}
/* kill voice */
opl3_dbg(opl3, " --> kill voice %i\n", voice);
opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
/* clear Key ON bit */
opl3->command(opl3, opl3_reg, vp->keyon_reg);
/* do the bookkeeping */
vp->time = opl3->use_time++;
if (vp->state == SNDRV_OPL3_ST_ON_4OP) {
vp2 = &opl3->voices[voice + 3];
vp2->time = opl3->use_time++;
vp2->state = SNDRV_OPL3_ST_OFF;
}
vp->state = SNDRV_OPL3_ST_OFF;
#ifdef DEBUG_ALLOC
debug_alloc(opl3, "note off", voice);
#endif
}
/*
* Release a note in response to a midi note off.
*/
static void snd_opl3_note_off_unsafe(void *p, int note, int vel,
struct snd_midi_channel *chan)
{
struct snd_opl3 *opl3;
int voice;
struct snd_opl3_voice *vp;
opl3 = p;
opl3_dbg(opl3, "Note off, ch %i, inst %i, note %i\n",
chan->number, chan->midi_program, note);
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
if (chan->drum_channel && use_internal_drums) {
snd_opl3_drum_switch(opl3, note, vel, 0, chan);
return;
}
/* this loop will hopefully kill all extra voices, because
they are grouped by the same channel and note values */
for (voice = 0; voice < opl3->max_voices; voice++) {
vp = &opl3->voices[voice];
if (vp->state > 0 && vp->chan == chan && vp->note == note) {
snd_opl3_kill_voice(opl3, voice);
}
}
} else {
/* remap OSS voices */
if (chan->number < MAX_OPL3_VOICES) {
voice = snd_opl3_oss_map[chan->number];
snd_opl3_kill_voice(opl3, voice);
}
}
}
void snd_opl3_note_off(void *p, int note, int vel,
struct snd_midi_channel *chan)
{
struct snd_opl3 *opl3 = p;
guard(spinlock_irqsave)(&opl3->voice_lock);
snd_opl3_note_off_unsafe(p, note, vel, chan);
}
/*
* key pressure change
*/
void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan)
{
opl3_dbg(p, "Key pressure, ch#: %i, inst#: %i\n",
chan->number, chan->midi_program);
}
/*
* terminate note
*/
void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan)
{
opl3_dbg(p, "Terminate note, ch#: %i, inst#: %i\n",
chan->number, chan->midi_program);
}
static void snd_opl3_update_pitch(struct snd_opl3 *opl3, int voice)
{
unsigned short reg_side;
unsigned char voice_offset;
unsigned short opl3_reg;
unsigned char fnum, blocknum;
struct snd_opl3_voice *vp;
if (snd_BUG_ON(voice >= MAX_OPL3_VOICES))
return;
vp = &opl3->voices[voice];
if (vp->chan == NULL)
return; /* not allocated? */
if (voice < MAX_OPL2_VOICES) {
/* Left register block for voices 0 .. 8 */
reg_side = OPL3_LEFT;
voice_offset = voice;
} else {
/* Right register block for voices 9 .. 17 */
reg_side = OPL3_RIGHT;
voice_offset = voice - MAX_OPL2_VOICES;
}
snd_opl3_calc_pitch(&fnum, &blocknum, vp->note, vp->chan);
/* Set OPL3 FNUM_LOW register of requested voice */
opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
opl3->command(opl3, opl3_reg, fnum);
vp->keyon_reg = blocknum;
/* Set output sound flag */
blocknum |= OPL3_KEYON_BIT;
/* Set OPL3 KEYON_BLOCK register of requested voice */
opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
opl3->command(opl3, opl3_reg, blocknum);
vp->time = opl3->use_time++;
}
/*
* Update voice pitch controller
*/
static void snd_opl3_pitch_ctrl(struct snd_opl3 *opl3, struct snd_midi_channel *chan)
{
int voice;
struct snd_opl3_voice *vp;
guard(spinlock_irqsave)(&opl3->voice_lock);
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
for (voice = 0; voice < opl3->max_voices; voice++) {
vp = &opl3->voices[voice];
if (vp->state > 0 && vp->chan == chan) {
snd_opl3_update_pitch(opl3, voice);
}
}
} else {
/* remap OSS voices */
if (chan->number < MAX_OPL3_VOICES) {
voice = snd_opl3_oss_map[chan->number];
snd_opl3_update_pitch(opl3, voice);
}
}
}
/*
* Deal with a controller type event. This includes all types of
* control events, not just the midi controllers
*/
void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan)
{
struct snd_opl3 *opl3;
opl3 = p;
opl3_dbg(opl3, "Controller, TYPE = %i, ch#: %i, inst#: %i\n",
type, chan->number, chan->midi_program);
switch (type) {
case MIDI_CTL_MSB_MODWHEEL:
if (chan->control[MIDI_CTL_MSB_MODWHEEL] > 63)
opl3->drum_reg |= OPL3_VIBRATO_DEPTH;
else
opl3->drum_reg &= ~OPL3_VIBRATO_DEPTH;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
opl3->drum_reg);
break;
case MIDI_CTL_E2_TREMOLO_DEPTH:
if (chan->control[MIDI_CTL_E2_TREMOLO_DEPTH] > 63)
opl3->drum_reg |= OPL3_TREMOLO_DEPTH;
else
opl3->drum_reg &= ~OPL3_TREMOLO_DEPTH;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION,
opl3->drum_reg);
break;
case MIDI_CTL_PITCHBEND:
snd_opl3_pitch_ctrl(opl3, chan);
break;
}
}
/*
* NRPN events
*/
void snd_opl3_nrpn(void *p, struct snd_midi_channel *chan,
struct snd_midi_channel_set *chset)
{
opl3_dbg(p, "NRPN, ch#: %i, inst#: %i\n",
chan->number, chan->midi_program);
}
/*
* receive sysex
*/
void snd_opl3_sysex(void *p, unsigned char *buf, int len,
int parsed, struct snd_midi_channel_set *chset)
{
opl3_dbg(p, "SYSEX\n");
}
+254
View File
@@ -0,0 +1,254 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Interface for OSS sequencer emulation
*
* Copyright (C) 2000 Uros Bizjak <uros@kss-loka.si>
*/
#include <linux/export.h>
#include "opl3_voice.h"
static int snd_opl3_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure);
static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg);
static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg);
static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format, const char __user *buf, int offs, int count);
static int snd_opl3_reset_seq_oss(struct snd_seq_oss_arg *arg);
/* operators */
static const struct snd_seq_oss_callback oss_callback = {
.owner = THIS_MODULE,
.open = snd_opl3_open_seq_oss,
.close = snd_opl3_close_seq_oss,
.ioctl = snd_opl3_ioctl_seq_oss,
.load_patch = snd_opl3_load_patch_seq_oss,
.reset = snd_opl3_reset_seq_oss,
};
static int snd_opl3_oss_event_input(struct snd_seq_event *ev, int direct,
void *private_data, int atomic, int hop)
{
struct snd_opl3 *opl3 = private_data;
if (ev->type != SNDRV_SEQ_EVENT_OSS)
snd_midi_process_event(&opl3_ops, ev, opl3->oss_chset);
return 0;
}
/* ------------------------------ */
static void snd_opl3_oss_free_port(void *private_data)
{
struct snd_opl3 *opl3 = private_data;
snd_midi_channel_free_set(opl3->oss_chset);
}
static int snd_opl3_oss_create_port(struct snd_opl3 * opl3)
{
struct snd_seq_port_callback callbacks;
char name[32];
int voices, opl_ver;
voices = (opl3->hardware < OPL3_HW_OPL3) ?
MAX_OPL2_VOICES : MAX_OPL3_VOICES;
opl3->oss_chset = snd_midi_channel_alloc_set(voices);
if (opl3->oss_chset == NULL)
return -ENOMEM;
opl3->oss_chset->private_data = opl3;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.owner = THIS_MODULE;
callbacks.event_input = snd_opl3_oss_event_input;
callbacks.private_free = snd_opl3_oss_free_port;
callbacks.private_data = opl3;
opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
sprintf(name, "OPL%i OSS Port", opl_ver);
opl3->oss_chset->client = opl3->seq_client;
opl3->oss_chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks,
SNDRV_SEQ_PORT_CAP_WRITE,
SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
SNDRV_SEQ_PORT_TYPE_MIDI_GM |
SNDRV_SEQ_PORT_TYPE_HARDWARE |
SNDRV_SEQ_PORT_TYPE_SYNTHESIZER,
voices, voices,
name);
if (opl3->oss_chset->port < 0) {
int port;
port = opl3->oss_chset->port;
snd_midi_channel_free_set(opl3->oss_chset);
return port;
}
return 0;
}
/* ------------------------------ */
/* register OSS synth */
void snd_opl3_init_seq_oss(struct snd_opl3 *opl3, char *name)
{
struct snd_seq_oss_reg *arg;
struct snd_seq_device *dev;
if (snd_seq_device_new(opl3->card, 0, SNDRV_SEQ_DEV_ID_OSS,
sizeof(struct snd_seq_oss_reg), &dev) < 0)
return;
opl3->oss_seq_dev = dev;
strscpy(dev->name, name, sizeof(dev->name));
arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
arg->type = SYNTH_TYPE_FM;
if (opl3->hardware < OPL3_HW_OPL3) {
arg->subtype = FM_TYPE_ADLIB;
arg->nvoices = MAX_OPL2_VOICES;
} else {
arg->subtype = FM_TYPE_OPL3;
arg->nvoices = MAX_OPL3_VOICES;
}
arg->oper = oss_callback;
arg->private_data = opl3;
if (snd_opl3_oss_create_port(opl3)) {
/* register to OSS synth table */
snd_device_register(opl3->card, dev);
}
}
/* unregister */
void snd_opl3_free_seq_oss(struct snd_opl3 *opl3)
{
if (opl3->oss_seq_dev) {
/* The instance should have been released in prior */
opl3->oss_seq_dev = NULL;
}
}
/* ------------------------------ */
/* open OSS sequencer */
static int snd_opl3_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
{
struct snd_opl3 *opl3 = closure;
int err;
if (snd_BUG_ON(!arg))
return -ENXIO;
err = snd_opl3_synth_setup(opl3);
if (err < 0)
return err;
/* fill the argument data */
arg->private_data = opl3;
arg->addr.client = opl3->oss_chset->client;
arg->addr.port = opl3->oss_chset->port;
err = snd_opl3_synth_use_inc(opl3);
if (err < 0)
return err;
opl3->synth_mode = SNDRV_OPL3_MODE_SYNTH;
return 0;
}
/* close OSS sequencer */
static int snd_opl3_close_seq_oss(struct snd_seq_oss_arg *arg)
{
struct snd_opl3 *opl3;
if (snd_BUG_ON(!arg))
return -ENXIO;
opl3 = arg->private_data;
snd_opl3_synth_cleanup(opl3);
snd_opl3_synth_use_dec(opl3);
return 0;
}
/* load patch */
/* from sound_config.h */
#define SBFM_MAXINSTR 256
static int snd_opl3_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
const char __user *buf, int offs, int count)
{
struct snd_opl3 *opl3;
struct sbi_instrument sbi;
char name[32];
int err, type;
if (snd_BUG_ON(!arg))
return -ENXIO;
opl3 = arg->private_data;
if (format == FM_PATCH)
type = FM_PATCH_OPL2;
else if (format == OPL3_PATCH)
type = FM_PATCH_OPL3;
else
return -EINVAL;
if (count < (int)sizeof(sbi)) {
dev_err(opl3->card->dev, "FM Error: Patch record too short\n");
return -EINVAL;
}
if (copy_from_user(&sbi, buf, sizeof(sbi)))
return -EFAULT;
if (sbi.channel < 0 || sbi.channel >= SBFM_MAXINSTR) {
dev_err(opl3->card->dev, "FM Error: Invalid instrument number %d\n",
sbi.channel);
return -EINVAL;
}
memset(name, 0, sizeof(name));
sprintf(name, "Chan%d", sbi.channel);
err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL,
sbi.operators);
if (err < 0)
return err;
return sizeof(sbi);
}
/* ioctl */
static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd,
unsigned long ioarg)
{
struct snd_opl3 *opl3;
if (snd_BUG_ON(!arg))
return -ENXIO;
opl3 = arg->private_data;
switch (cmd) {
case SNDCTL_FM_LOAD_INSTR:
dev_err(opl3->card->dev,
"OPL3: Obsolete ioctl(SNDCTL_FM_LOAD_INSTR) used. Fix the program.\n");
return -EINVAL;
case SNDCTL_SYNTH_MEMAVL:
return 0x7fffffff;
case SNDCTL_FM_4OP_ENABLE:
// handled automatically by OPL instrument type
return 0;
default:
return -EINVAL;
}
return 0;
}
/* reset device */
static int snd_opl3_reset_seq_oss(struct snd_seq_oss_arg *arg)
{
if (snd_BUG_ON(!arg))
return -ENXIO;
return 0;
}
+273
View File
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Uros Bizjak <uros@kss-loka.si>
*
* Midi Sequencer interface routines for OPL2/OPL3/OPL4 FM
*
* OPL2/3 FM instrument loader:
* alsa-tools/seq/sbiload/
*/
#include "opl3_voice.h"
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/module.h>
#include <sound/initval.h>
MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth");
bool use_internal_drums = 0;
module_param(use_internal_drums, bool, 0444);
MODULE_PARM_DESC(use_internal_drums, "Enable internal OPL2/3 drums.");
int snd_opl3_synth_use_inc(struct snd_opl3 * opl3)
{
if (!try_module_get(opl3->card->module))
return -EFAULT;
return 0;
}
void snd_opl3_synth_use_dec(struct snd_opl3 * opl3)
{
module_put(opl3->card->module);
}
int snd_opl3_synth_setup(struct snd_opl3 * opl3)
{
int idx;
struct snd_hwdep *hwdep = opl3->hwdep;
scoped_guard(mutex, &hwdep->open_mutex) {
if (hwdep->used)
return -EBUSY;
hwdep->used++;
}
snd_opl3_reset(opl3);
for (idx = 0; idx < MAX_OPL3_VOICES; idx++) {
opl3->voices[idx].state = SNDRV_OPL3_ST_OFF;
opl3->voices[idx].time = 0;
opl3->voices[idx].keyon_reg = 0x00;
}
opl3->use_time = 0;
opl3->connection_reg = 0x00;
if (opl3->hardware >= OPL3_HW_OPL3) {
/* Clear 4-op connections */
opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT,
opl3->connection_reg);
opl3->max_voices = MAX_OPL3_VOICES;
}
return 0;
}
void snd_opl3_synth_cleanup(struct snd_opl3 * opl3)
{
struct snd_hwdep *hwdep;
/* Stop system timer */
scoped_guard(spinlock_irq, &opl3->sys_timer_lock) {
if (opl3->sys_timer_status) {
timer_delete(&opl3->tlist);
opl3->sys_timer_status = 0;
}
}
snd_opl3_reset(opl3);
hwdep = opl3->hwdep;
scoped_guard(mutex, &hwdep->open_mutex) {
hwdep->used--;
}
wake_up(&hwdep->open_wait);
}
static int snd_opl3_synth_use(void *private_data, struct snd_seq_port_subscribe * info)
{
struct snd_opl3 *opl3 = private_data;
int err;
err = snd_opl3_synth_setup(opl3);
if (err < 0)
return err;
if (use_internal_drums) {
/* Percussion mode */
opl3->voices[6].state = opl3->voices[7].state =
opl3->voices[8].state = SNDRV_OPL3_ST_NOT_AVAIL;
snd_opl3_load_drums(opl3);
opl3->drum_reg = OPL3_PERCUSSION_ENABLE;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, opl3->drum_reg);
} else {
opl3->drum_reg = 0x00;
}
if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {
err = snd_opl3_synth_use_inc(opl3);
if (err < 0)
return err;
}
opl3->synth_mode = SNDRV_OPL3_MODE_SEQ;
return 0;
}
static int snd_opl3_synth_unuse(void *private_data, struct snd_seq_port_subscribe * info)
{
struct snd_opl3 *opl3 = private_data;
snd_opl3_synth_cleanup(opl3);
if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
snd_opl3_synth_use_dec(opl3);
return 0;
}
/*
* MIDI emulation operators
*/
const struct snd_midi_op opl3_ops = {
.note_on = snd_opl3_note_on,
.note_off = snd_opl3_note_off,
.key_press = snd_opl3_key_press,
.note_terminate = snd_opl3_terminate_note,
.control = snd_opl3_control,
.nrpn = snd_opl3_nrpn,
.sysex = snd_opl3_sysex,
};
static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,
void *private_data, int atomic, int hop)
{
struct snd_opl3 *opl3 = private_data;
snd_midi_process_event(&opl3_ops, ev, opl3->chset);
return 0;
}
/* ------------------------------ */
static void snd_opl3_synth_free_port(void *private_data)
{
struct snd_opl3 *opl3 = private_data;
snd_midi_channel_free_set(opl3->chset);
}
static int snd_opl3_synth_create_port(struct snd_opl3 * opl3)
{
struct snd_seq_port_callback callbacks;
char name[32];
int voices, opl_ver;
voices = (opl3->hardware < OPL3_HW_OPL3) ?
MAX_OPL2_VOICES : MAX_OPL3_VOICES;
opl3->chset = snd_midi_channel_alloc_set(16);
if (opl3->chset == NULL)
return -ENOMEM;
opl3->chset->private_data = opl3;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.owner = THIS_MODULE;
callbacks.use = snd_opl3_synth_use;
callbacks.unuse = snd_opl3_synth_unuse;
callbacks.event_input = snd_opl3_synth_event_input;
callbacks.private_free = snd_opl3_synth_free_port;
callbacks.private_data = opl3;
opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
sprintf(name, "OPL%i FM Port", opl_ver);
opl3->chset->client = opl3->seq_client;
opl3->chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks,
SNDRV_SEQ_PORT_CAP_WRITE |
SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
SNDRV_SEQ_PORT_TYPE_MIDI_GM |
SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE |
SNDRV_SEQ_PORT_TYPE_HARDWARE |
SNDRV_SEQ_PORT_TYPE_SYNTHESIZER,
16, voices,
name);
if (opl3->chset->port < 0) {
int port;
port = opl3->chset->port;
snd_midi_channel_free_set(opl3->chset);
return port;
}
return 0;
}
/* ------------------------------ */
static int snd_opl3_seq_probe(struct snd_seq_device *dev)
{
struct snd_opl3 *opl3;
int client, err;
char name[32];
int opl_ver;
opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
if (opl3 == NULL)
return -EINVAL;
spin_lock_init(&opl3->voice_lock);
opl3->seq_client = -1;
/* allocate new client */
opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;
sprintf(name, "OPL%i FM synth", opl_ver);
client = opl3->seq_client =
snd_seq_create_kernel_client(opl3->card, opl3->seq_dev_num,
name);
if (client < 0)
return client;
err = snd_opl3_synth_create_port(opl3);
if (err < 0) {
snd_seq_delete_kernel_client(client);
opl3->seq_client = -1;
return err;
}
/* setup system timer */
timer_setup(&opl3->tlist, snd_opl3_timer_func, 0);
spin_lock_init(&opl3->sys_timer_lock);
opl3->sys_timer_status = 0;
#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
snd_opl3_init_seq_oss(opl3, name);
#endif
return 0;
}
static void snd_opl3_seq_remove(struct snd_seq_device *dev)
{
struct snd_opl3 *opl3;
opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
if (opl3 == NULL)
return;
#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
snd_opl3_free_seq_oss(opl3);
#endif
if (opl3->seq_client >= 0) {
snd_seq_delete_kernel_client(opl3->seq_client);
opl3->seq_client = -1;
}
}
static struct snd_seq_driver opl3_seq_driver = {
.probe = snd_opl3_seq_probe,
.remove = snd_opl3_seq_remove,
.driver = {
.name = KBUILD_MODNAME,
},
.id = SNDRV_SEQ_DEV_ID_OPL3,
.argsize = sizeof(struct snd_opl3 *),
};
module_snd_seq_driver(opl3_seq_driver);
+606
View File
@@ -0,0 +1,606 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) by Uros Bizjak <uros@kss-loka.si>
*
* Routines for OPL2/OPL3/OPL4 control
*/
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/nospec.h>
#include <sound/opl3.h>
#include <sound/asound_fm.h>
#include "opl3_voice.h"
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
#define OPL3_SUPPORT_SYNTH
#endif
/*
* There is 18 possible 2 OP voices
* (9 in the left and 9 in the right).
* The first OP is the modulator and 2nd is the carrier.
*
* The first three voices in the both sides may be connected
* with another voice to a 4 OP voice. For example voice 0
* can be connected with voice 3. The operators of voice 3 are
* used as operators 3 and 4 of the new 4 OP voice.
* In this case the 2 OP voice number 0 is the 'first half' and
* voice 3 is the second.
*/
/*
* Register offset table for OPL2/3 voices,
* OPL2 / one OPL3 register array side only
*/
char snd_opl3_regmap[MAX_OPL2_VOICES][4] =
{
/* OP1 OP2 OP3 OP4 */
/* ------------------------ */
{ 0x00, 0x03, 0x08, 0x0b },
{ 0x01, 0x04, 0x09, 0x0c },
{ 0x02, 0x05, 0x0a, 0x0d },
{ 0x08, 0x0b, 0x00, 0x00 },
{ 0x09, 0x0c, 0x00, 0x00 },
{ 0x0a, 0x0d, 0x00, 0x00 },
{ 0x10, 0x13, 0x00, 0x00 }, /* used by percussive voices */
{ 0x11, 0x14, 0x00, 0x00 }, /* if the percussive mode */
{ 0x12, 0x15, 0x00, 0x00 } /* is selected (only left reg block) */
};
EXPORT_SYMBOL(snd_opl3_regmap);
/*
* prototypes
*/
static int snd_opl3_play_note(struct snd_opl3 * opl3, struct snd_dm_fm_note * note);
static int snd_opl3_set_voice(struct snd_opl3 * opl3, struct snd_dm_fm_voice * voice);
static int snd_opl3_set_params(struct snd_opl3 * opl3, struct snd_dm_fm_params * params);
static int snd_opl3_set_mode(struct snd_opl3 * opl3, int mode);
static int snd_opl3_set_connection(struct snd_opl3 * opl3, int connection);
/* ------------------------------ */
/*
* open the device exclusively
*/
int snd_opl3_open(struct snd_hwdep * hw, struct file *file)
{
return 0;
}
/*
* ioctl for hwdep device:
*/
int snd_opl3_ioctl(struct snd_hwdep * hw, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct snd_opl3 *opl3 = hw->private_data;
void __user *argp = (void __user *)arg;
if (snd_BUG_ON(!opl3))
return -EINVAL;
switch (cmd) {
/* get information */
case SNDRV_DM_FM_IOCTL_INFO:
{
struct snd_dm_fm_info info;
memset(&info, 0, sizeof(info));
info.fm_mode = opl3->fm_mode;
info.rhythm = opl3->rhythm;
if (copy_to_user(argp, &info, sizeof(struct snd_dm_fm_info)))
return -EFAULT;
return 0;
}
case SNDRV_DM_FM_IOCTL_RESET:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_RESET:
#endif
snd_opl3_reset(opl3);
return 0;
case SNDRV_DM_FM_IOCTL_PLAY_NOTE:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_PLAY_NOTE:
#endif
{
struct snd_dm_fm_note note;
if (copy_from_user(&note, argp, sizeof(struct snd_dm_fm_note)))
return -EFAULT;
return snd_opl3_play_note(opl3, &note);
}
case SNDRV_DM_FM_IOCTL_SET_VOICE:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_VOICE:
#endif
{
struct snd_dm_fm_voice voice;
if (copy_from_user(&voice, argp, sizeof(struct snd_dm_fm_voice)))
return -EFAULT;
return snd_opl3_set_voice(opl3, &voice);
}
case SNDRV_DM_FM_IOCTL_SET_PARAMS:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_PARAMS:
#endif
{
struct snd_dm_fm_params params;
if (copy_from_user(&params, argp, sizeof(struct snd_dm_fm_params)))
return -EFAULT;
return snd_opl3_set_params(opl3, &params);
}
case SNDRV_DM_FM_IOCTL_SET_MODE:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_MODE:
#endif
return snd_opl3_set_mode(opl3, (int) arg);
case SNDRV_DM_FM_IOCTL_SET_CONNECTION:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_OPL:
#endif
return snd_opl3_set_connection(opl3, (int) arg);
#ifdef OPL3_SUPPORT_SYNTH
case SNDRV_DM_FM_IOCTL_CLEAR_PATCHES:
snd_opl3_clear_patches(opl3);
return 0;
#endif
default:
dev_dbg(opl3->card->dev, "unknown IOCTL: 0x%x\n", cmd);
}
return -ENOTTY;
}
/*
* close the device
*/
int snd_opl3_release(struct snd_hwdep * hw, struct file *file)
{
struct snd_opl3 *opl3 = hw->private_data;
snd_opl3_reset(opl3);
return 0;
}
#ifdef OPL3_SUPPORT_SYNTH
/*
* write the device - load patches
*/
long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count,
loff_t *offset)
{
struct snd_opl3 *opl3 = hw->private_data;
long result = 0;
int err = 0;
struct sbi_patch inst;
while (count >= sizeof(inst)) {
unsigned char type;
if (copy_from_user(&inst, buf, sizeof(inst)))
return -EFAULT;
if (!memcmp(inst.key, FM_KEY_SBI, 4) ||
!memcmp(inst.key, FM_KEY_2OP, 4))
type = FM_PATCH_OPL2;
else if (!memcmp(inst.key, FM_KEY_4OP, 4))
type = FM_PATCH_OPL3;
else /* invalid type */
break;
err = snd_opl3_load_patch(opl3, inst.prog, inst.bank, type,
inst.name, inst.extension,
inst.data);
if (err < 0)
break;
result += sizeof(inst);
count -= sizeof(inst);
}
return result > 0 ? result : err;
}
/*
* Patch management
*/
/* offsets for SBI params */
#define AM_VIB 0
#define KSL_LEVEL 2
#define ATTACK_DECAY 4
#define SUSTAIN_RELEASE 6
#define WAVE_SELECT 8
/* offset for SBI instrument */
#define CONNECTION 10
#define OFFSET_4OP 11
/*
* load a patch, obviously.
*
* loaded on the given program and bank numbers with the given type
* (FM_PATCH_OPLx).
* data is the pointer of SBI record _without_ header (key and name).
* name is the name string of the patch.
* ext is the extension data of 7 bytes long (stored in name of SBI
* data up to offset 25), or NULL to skip.
* return 0 if successful or a negative error code.
*/
int snd_opl3_load_patch(struct snd_opl3 *opl3,
int prog, int bank, int type,
const char *name,
const unsigned char *ext,
const unsigned char *data)
{
struct fm_patch *patch;
int i;
patch = snd_opl3_find_patch(opl3, prog, bank, 1);
if (!patch)
return -ENOMEM;
patch->type = type;
for (i = 0; i < 2; i++) {
patch->inst.op[i].am_vib = data[AM_VIB + i];
patch->inst.op[i].ksl_level = data[KSL_LEVEL + i];
patch->inst.op[i].attack_decay = data[ATTACK_DECAY + i];
patch->inst.op[i].sustain_release = data[SUSTAIN_RELEASE + i];
patch->inst.op[i].wave_select = data[WAVE_SELECT + i];
}
patch->inst.feedback_connection[0] = data[CONNECTION];
if (type == FM_PATCH_OPL3) {
for (i = 0; i < 2; i++) {
patch->inst.op[i+2].am_vib =
data[OFFSET_4OP + AM_VIB + i];
patch->inst.op[i+2].ksl_level =
data[OFFSET_4OP + KSL_LEVEL + i];
patch->inst.op[i+2].attack_decay =
data[OFFSET_4OP + ATTACK_DECAY + i];
patch->inst.op[i+2].sustain_release =
data[OFFSET_4OP + SUSTAIN_RELEASE + i];
patch->inst.op[i+2].wave_select =
data[OFFSET_4OP + WAVE_SELECT + i];
}
patch->inst.feedback_connection[1] =
data[OFFSET_4OP + CONNECTION];
}
if (ext) {
patch->inst.echo_delay = ext[0];
patch->inst.echo_atten = ext[1];
patch->inst.chorus_spread = ext[2];
patch->inst.trnsps = ext[3];
patch->inst.fix_dur = ext[4];
patch->inst.modes = ext[5];
patch->inst.fix_key = ext[6];
}
if (name)
strscpy(patch->name, name, sizeof(patch->name));
return 0;
}
EXPORT_SYMBOL(snd_opl3_load_patch);
/*
* find a patch with the given program and bank numbers, returns its pointer
* if no matching patch is found and create_patch is set, it creates a
* new patch object.
*/
struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank,
int create_patch)
{
/* pretty dumb hash key */
unsigned int key = (prog + bank) % OPL3_PATCH_HASH_SIZE;
struct fm_patch *patch;
for (patch = opl3->patch_table[key]; patch; patch = patch->next) {
if (patch->prog == prog && patch->bank == bank)
return patch;
}
if (!create_patch)
return NULL;
patch = kzalloc_obj(*patch);
if (!patch)
return NULL;
patch->prog = prog;
patch->bank = bank;
patch->next = opl3->patch_table[key];
opl3->patch_table[key] = patch;
return patch;
}
EXPORT_SYMBOL(snd_opl3_find_patch);
/*
* Clear all patches of the given OPL3 instance
*/
void snd_opl3_clear_patches(struct snd_opl3 *opl3)
{
int i;
for (i = 0; i < OPL3_PATCH_HASH_SIZE; i++) {
struct fm_patch *patch, *next;
for (patch = opl3->patch_table[i]; patch; patch = next) {
next = patch->next;
kfree(patch);
}
}
memset(opl3->patch_table, 0, sizeof(opl3->patch_table));
}
#endif /* OPL3_SUPPORT_SYNTH */
/* ------------------------------ */
void snd_opl3_reset(struct snd_opl3 * opl3)
{
unsigned short opl3_reg;
unsigned short reg_side;
unsigned char voice_offset;
int max_voices, i;
max_voices = (opl3->hardware < OPL3_HW_OPL3) ?
MAX_OPL2_VOICES : MAX_OPL3_VOICES;
for (i = 0; i < max_voices; i++) {
/* Get register array side and offset of voice */
if (i < MAX_OPL2_VOICES) {
/* Left register block for voices 0 .. 8 */
reg_side = OPL3_LEFT;
voice_offset = i;
} else {
/* Right register block for voices 9 .. 17 */
reg_side = OPL3_RIGHT;
voice_offset = i - MAX_OPL2_VOICES;
}
opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][0]);
opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 1 volume */
opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + snd_opl3_regmap[voice_offset][1]);
opl3->command(opl3, opl3_reg, OPL3_TOTAL_LEVEL_MASK); /* Operator 2 volume */
opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
opl3->command(opl3, opl3_reg, 0x00); /* Note off */
}
opl3->max_voices = MAX_OPL2_VOICES;
opl3->fm_mode = SNDRV_DM_FM_MODE_OPL2;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_TEST, OPL3_ENABLE_WAVE_SELECT);
opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, 0x00); /* Melodic mode */
opl3->rhythm = 0;
}
EXPORT_SYMBOL(snd_opl3_reset);
static int snd_opl3_play_note(struct snd_opl3 * opl3, struct snd_dm_fm_note * note)
{
unsigned short reg_side;
unsigned char voice_offset;
unsigned short opl3_reg;
unsigned char reg_val;
/* Voices 0 - 8 in OPL2 mode */
/* Voices 0 - 17 in OPL3 mode */
if (note->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ?
MAX_OPL3_VOICES : MAX_OPL2_VOICES))
return -EINVAL;
/* Get register array side and offset of voice */
if (note->voice < MAX_OPL2_VOICES) {
/* Left register block for voices 0 .. 8 */
reg_side = OPL3_LEFT;
voice_offset = note->voice;
} else {
/* Right register block for voices 9 .. 17 */
reg_side = OPL3_RIGHT;
voice_offset = note->voice - MAX_OPL2_VOICES;
}
/* Set lower 8 bits of note frequency */
reg_val = (unsigned char) note->fnum;
opl3_reg = reg_side | (OPL3_REG_FNUM_LOW + voice_offset);
opl3->command(opl3, opl3_reg, reg_val);
reg_val = 0x00;
/* Set output sound flag */
if (note->key_on)
reg_val |= OPL3_KEYON_BIT;
/* Set octave */
reg_val |= (note->octave << 2) & OPL3_BLOCKNUM_MASK;
/* Set higher 2 bits of note frequency */
reg_val |= (unsigned char) (note->fnum >> 8) & OPL3_FNUM_HIGH_MASK;
/* Set OPL3 KEYON_BLOCK register of requested voice */
opl3_reg = reg_side | (OPL3_REG_KEYON_BLOCK + voice_offset);
opl3->command(opl3, opl3_reg, reg_val);
return 0;
}
static int snd_opl3_set_voice(struct snd_opl3 * opl3, struct snd_dm_fm_voice * voice)
{
unsigned short reg_side;
unsigned char op_offset;
unsigned char voice_offset, voice_op;
unsigned short opl3_reg;
unsigned char reg_val;
/* Only operators 1 and 2 */
if (voice->op > 1)
return -EINVAL;
/* Voices 0 - 8 in OPL2 mode */
/* Voices 0 - 17 in OPL3 mode */
if (voice->voice >= ((opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) ?
MAX_OPL3_VOICES : MAX_OPL2_VOICES))
return -EINVAL;
/* Get register array side and offset of voice */
if (voice->voice < MAX_OPL2_VOICES) {
/* Left register block for voices 0 .. 8 */
reg_side = OPL3_LEFT;
voice_offset = voice->voice;
} else {
/* Right register block for voices 9 .. 17 */
reg_side = OPL3_RIGHT;
voice_offset = voice->voice - MAX_OPL2_VOICES;
}
/* Get register offset of operator */
voice_offset = array_index_nospec(voice_offset, MAX_OPL2_VOICES);
voice_op = array_index_nospec(voice->op, 4);
op_offset = snd_opl3_regmap[voice_offset][voice_op];
reg_val = 0x00;
/* Set amplitude modulation (tremolo) effect */
if (voice->am)
reg_val |= OPL3_TREMOLO_ON;
/* Set vibrato effect */
if (voice->vibrato)
reg_val |= OPL3_VIBRATO_ON;
/* Set sustaining sound phase */
if (voice->do_sustain)
reg_val |= OPL3_SUSTAIN_ON;
/* Set keyboard scaling bit */
if (voice->kbd_scale)
reg_val |= OPL3_KSR;
/* Set harmonic or frequency multiplier */
reg_val |= voice->harmonic & OPL3_MULTIPLE_MASK;
/* Set OPL3 AM_VIB register of requested voice/operator */
opl3_reg = reg_side | (OPL3_REG_AM_VIB + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set decreasing volume of higher notes */
reg_val = (voice->scale_level << 6) & OPL3_KSL_MASK;
/* Set output volume */
reg_val |= ~voice->volume & OPL3_TOTAL_LEVEL_MASK;
/* Set OPL3 KSL_LEVEL register of requested voice/operator */
opl3_reg = reg_side | (OPL3_REG_KSL_LEVEL + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set attack phase level */
reg_val = (voice->attack << 4) & OPL3_ATTACK_MASK;
/* Set decay phase level */
reg_val |= voice->decay & OPL3_DECAY_MASK;
/* Set OPL3 ATTACK_DECAY register of requested voice/operator */
opl3_reg = reg_side | (OPL3_REG_ATTACK_DECAY + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set sustain phase level */
reg_val = (voice->sustain << 4) & OPL3_SUSTAIN_MASK;
/* Set release phase level */
reg_val |= voice->release & OPL3_RELEASE_MASK;
/* Set OPL3 SUSTAIN_RELEASE register of requested voice/operator */
opl3_reg = reg_side | (OPL3_REG_SUSTAIN_RELEASE + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set inter-operator feedback */
reg_val = (voice->feedback << 1) & OPL3_FEEDBACK_MASK;
/* Set inter-operator connection */
if (voice->connection)
reg_val |= OPL3_CONNECTION_BIT;
/* OPL-3 only */
if (opl3->fm_mode == SNDRV_DM_FM_MODE_OPL3) {
if (voice->left)
reg_val |= OPL3_VOICE_TO_LEFT;
if (voice->right)
reg_val |= OPL3_VOICE_TO_RIGHT;
}
/* Feedback/connection bits are applicable to voice */
opl3_reg = reg_side | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Select waveform */
reg_val = voice->waveform & OPL3_WAVE_SELECT_MASK;
opl3_reg = reg_side | (OPL3_REG_WAVE_SELECT + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
return 0;
}
static int snd_opl3_set_params(struct snd_opl3 * opl3, struct snd_dm_fm_params * params)
{
unsigned char reg_val;
reg_val = 0x00;
/* Set keyboard split method */
if (params->kbd_split)
reg_val |= OPL3_KEYBOARD_SPLIT;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_KBD_SPLIT, reg_val);
reg_val = 0x00;
/* Set amplitude modulation (tremolo) depth */
if (params->am_depth)
reg_val |= OPL3_TREMOLO_DEPTH;
/* Set vibrato depth */
if (params->vib_depth)
reg_val |= OPL3_VIBRATO_DEPTH;
/* Set percussion mode */
if (params->rhythm) {
reg_val |= OPL3_PERCUSSION_ENABLE;
opl3->rhythm = 1;
} else {
opl3->rhythm = 0;
}
/* Play percussion instruments */
if (params->bass)
reg_val |= OPL3_BASSDRUM_ON;
if (params->snare)
reg_val |= OPL3_SNAREDRUM_ON;
if (params->tomtom)
reg_val |= OPL3_TOMTOM_ON;
if (params->cymbal)
reg_val |= OPL3_CYMBAL_ON;
if (params->hihat)
reg_val |= OPL3_HIHAT_ON;
opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, reg_val);
return 0;
}
static int snd_opl3_set_mode(struct snd_opl3 * opl3, int mode)
{
if ((mode == SNDRV_DM_FM_MODE_OPL3) && (opl3->hardware < OPL3_HW_OPL3))
return -EINVAL;
opl3->fm_mode = mode;
if (opl3->hardware >= OPL3_HW_OPL3)
opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, 0x00); /* Clear 4-op connections */
return 0;
}
static int snd_opl3_set_connection(struct snd_opl3 * opl3, int connection)
{
unsigned char reg_val;
/* OPL-3 only */
if (opl3->fm_mode != SNDRV_DM_FM_MODE_OPL3)
return -EINVAL;
reg_val = connection & (OPL3_RIGHT_4OP_0 | OPL3_RIGHT_4OP_1 | OPL3_RIGHT_4OP_2 |
OPL3_LEFT_4OP_0 | OPL3_LEFT_4OP_1 | OPL3_LEFT_4OP_2);
/* Set 4-op connections */
opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT, reg_val);
return 0;
}
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef __OPL3_VOICE_H
#define __OPL3_VOICE_H
/*
* Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si>
*/
#include <sound/opl3.h>
/* Prototypes for opl3_seq.c */
int snd_opl3_synth_use_inc(struct snd_opl3 * opl3);
void snd_opl3_synth_use_dec(struct snd_opl3 * opl3);
int snd_opl3_synth_setup(struct snd_opl3 * opl3);
void snd_opl3_synth_cleanup(struct snd_opl3 * opl3);
/* Prototypes for opl3_midi.c */
void snd_opl3_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
void snd_opl3_note_off(void *p, int note, int vel, struct snd_midi_channel *chan);
void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan);
void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan);
void snd_opl3_control(void *p, int type, struct snd_midi_channel *chan);
void snd_opl3_nrpn(void *p, struct snd_midi_channel *chan, struct snd_midi_channel_set *chset);
void snd_opl3_sysex(void *p, unsigned char *buf, int len, int parsed, struct snd_midi_channel_set *chset);
void snd_opl3_calc_volume(unsigned char *reg, int vel, struct snd_midi_channel *chan);
void snd_opl3_timer_func(struct timer_list *t);
/* Prototypes for opl3_drums.c */
void snd_opl3_load_drums(struct snd_opl3 *opl3);
void snd_opl3_drum_switch(struct snd_opl3 *opl3, int note, int vel, int on_off, struct snd_midi_channel *chan);
/* Prototypes for opl3_oss.c */
#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)
void snd_opl3_init_seq_oss(struct snd_opl3 *opl3, char *name);
void snd_opl3_free_seq_oss(struct snd_opl3 *opl3);
#else
#define snd_opl3_init_seq_oss(opl3, name) /* NOP */
#define snd_opl3_free_seq_oss(opl3) /* NOP */
#endif
extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
extern bool use_internal_drums;
extern const struct snd_midi_op opl3_ops;
#endif
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-opl4-lib-y := opl4_lib.o opl4_mixer.o
snd-opl4-lib-$(CONFIG_SND_PROC_FS) += opl4_proc.o
snd-opl4-synth-y := opl4_seq.o opl4_synth.o yrw801.o
obj-$(CONFIG_SND_OPL4_LIB) += snd-opl4-lib.o
obj-$(CONFIG_SND_OPL4_LIB_SEQ) += snd-opl4-synth.o
+246
View File
@@ -0,0 +1,246 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Functions for accessing OPL4 devices
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
*/
#include "opl4_local.h"
#include <sound/initval.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_DESCRIPTION("OPL4 driver");
MODULE_LICENSE("GPL");
static inline void snd_opl4_wait(struct snd_opl4 *opl4)
{
int timeout = 10;
while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0)
;
}
void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value)
{
snd_opl4_wait(opl4);
outb(reg, opl4->pcm_port);
snd_opl4_wait(opl4);
outb(value, opl4->pcm_port + 1);
}
EXPORT_SYMBOL(snd_opl4_write);
u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg)
{
snd_opl4_wait(opl4);
outb(reg, opl4->pcm_port);
snd_opl4_wait(opl4);
return inb(opl4->pcm_port + 1);
}
EXPORT_SYMBOL(snd_opl4_read);
void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size)
{
u8 memcfg;
guard(spinlock_irqsave)(&opl4->reg_lock);
memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
snd_opl4_wait(opl4);
outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
snd_opl4_wait(opl4);
insb(opl4->pcm_port + 1, buf, size);
snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
}
EXPORT_SYMBOL(snd_opl4_read_memory);
void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size)
{
u8 memcfg;
guard(spinlock_irqsave)(&opl4->reg_lock);
memcfg = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg | OPL4_MODE_BIT);
snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_HIGH, offset >> 16);
snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_MID, offset >> 8);
snd_opl4_write(opl4, OPL4_REG_MEMORY_ADDRESS_LOW, offset);
snd_opl4_wait(opl4);
outb(OPL4_REG_MEMORY_DATA, opl4->pcm_port);
snd_opl4_wait(opl4);
outsb(opl4->pcm_port + 1, buf, size);
snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, memcfg);
}
EXPORT_SYMBOL(snd_opl4_write_memory);
static void snd_opl4_enable_opl4(struct snd_opl4 *opl4)
{
outb(OPL3_REG_MODE, opl4->fm_port + 2);
inb(opl4->fm_port);
inb(opl4->fm_port);
outb(OPL3_OPL3_ENABLE | OPL3_OPL4_ENABLE, opl4->fm_port + 3);
inb(opl4->fm_port);
inb(opl4->fm_port);
}
static int snd_opl4_detect(struct snd_opl4 *opl4)
{
u8 id1, id2;
snd_opl4_enable_opl4(opl4);
id1 = snd_opl4_read(opl4, OPL4_REG_MEMORY_CONFIGURATION);
dev_dbg(opl4->card->dev, "OPL4[02]=%02x\n", id1);
switch (id1 & OPL4_DEVICE_ID_MASK) {
case 0x20:
opl4->hardware = OPL3_HW_OPL4;
break;
case 0x40:
opl4->hardware = OPL3_HW_OPL4_ML;
break;
default:
return -ENODEV;
}
snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x00);
snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0xff);
id1 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_FM);
id2 = snd_opl4_read(opl4, OPL4_REG_MIX_CONTROL_PCM);
dev_dbg(opl4->card->dev, "OPL4 id1=%02x id2=%02x\n", id1, id2);
if (id1 != 0x00 || id2 != 0xff)
return -ENODEV;
snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_FM, 0x3f);
snd_opl4_write(opl4, OPL4_REG_MIX_CONTROL_PCM, 0x3f);
snd_opl4_write(opl4, OPL4_REG_MEMORY_CONFIGURATION, 0x00);
return 0;
}
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
static void snd_opl4_seq_dev_free(struct snd_seq_device *seq_dev)
{
struct snd_opl4 *opl4 = seq_dev->private_data;
opl4->seq_dev = NULL;
}
static int snd_opl4_create_seq_dev(struct snd_opl4 *opl4, int seq_device)
{
opl4->seq_dev_num = seq_device;
if (snd_seq_device_new(opl4->card, seq_device, SNDRV_SEQ_DEV_ID_OPL4,
sizeof(struct snd_opl4 *), &opl4->seq_dev) >= 0) {
strscpy(opl4->seq_dev->name, "OPL4 Wavetable");
*(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(opl4->seq_dev) = opl4;
opl4->seq_dev->private_data = opl4;
opl4->seq_dev->private_free = snd_opl4_seq_dev_free;
}
return 0;
}
#endif
static void snd_opl4_free(struct snd_opl4 *opl4)
{
snd_opl4_free_proc(opl4);
release_and_free_resource(opl4->res_fm_port);
release_and_free_resource(opl4->res_pcm_port);
kfree(opl4);
}
static int snd_opl4_dev_free(struct snd_device *device)
{
struct snd_opl4 *opl4 = device->device_data;
snd_opl4_free(opl4);
return 0;
}
int snd_opl4_create(struct snd_card *card,
unsigned long fm_port, unsigned long pcm_port,
int seq_device,
struct snd_opl3 **ropl3, struct snd_opl4 **ropl4)
{
struct snd_opl4 *opl4;
struct snd_opl3 *opl3;
int err;
static const struct snd_device_ops ops = {
.dev_free = snd_opl4_dev_free
};
if (ropl3)
*ropl3 = NULL;
if (ropl4)
*ropl4 = NULL;
opl4 = kzalloc_obj(*opl4);
if (!opl4)
return -ENOMEM;
opl4->res_fm_port = request_region(fm_port, 8, "OPL4 FM");
opl4->res_pcm_port = request_region(pcm_port, 8, "OPL4 PCM/MIX");
if (!opl4->res_fm_port || !opl4->res_pcm_port) {
dev_err(card->dev, "opl4: can't grab ports 0x%lx, 0x%lx\n", fm_port, pcm_port);
snd_opl4_free(opl4);
return -EBUSY;
}
opl4->card = card;
opl4->fm_port = fm_port;
opl4->pcm_port = pcm_port;
spin_lock_init(&opl4->reg_lock);
mutex_init(&opl4->access_mutex);
err = snd_opl4_detect(opl4);
if (err < 0) {
snd_opl4_free(opl4);
dev_dbg(card->dev, "OPL4 chip not detected at %#lx/%#lx\n", fm_port, pcm_port);
return err;
}
err = snd_device_new(card, SNDRV_DEV_CODEC, opl4, &ops);
if (err < 0) {
snd_opl4_free(opl4);
return err;
}
err = snd_opl3_create(card, fm_port, fm_port + 2, opl4->hardware, 1, &opl3);
if (err < 0) {
snd_device_free(card, opl4);
return err;
}
/* opl3 initialization disabled opl4, so reenable */
snd_opl4_enable_opl4(opl4);
snd_opl4_create_mixer(opl4);
snd_opl4_create_proc(opl4);
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
opl4->seq_client = -1;
if (opl4->hardware < OPL3_HW_OPL4_ML)
snd_opl4_create_seq_dev(opl4, seq_device);
#endif
if (ropl3)
*ropl3 = opl3;
if (ropl4)
*ropl4 = opl4;
return 0;
}
EXPORT_SYMBOL(snd_opl4_create);
+235
View File
@@ -0,0 +1,235 @@
/*
* Local definitions for the OPL4 driver
*
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef __OPL4_LOCAL_H
#define __OPL4_LOCAL_H
#include <sound/opl4.h>
/*
* Register numbers
*/
#define OPL4_REG_TEST0 0x00
#define OPL4_REG_TEST1 0x01
#define OPL4_REG_MEMORY_CONFIGURATION 0x02
#define OPL4_MODE_BIT 0x01
#define OPL4_MTYPE_BIT 0x02
#define OPL4_TONE_HEADER_MASK 0x1c
#define OPL4_DEVICE_ID_MASK 0xe0
#define OPL4_REG_MEMORY_ADDRESS_HIGH 0x03
#define OPL4_REG_MEMORY_ADDRESS_MID 0x04
#define OPL4_REG_MEMORY_ADDRESS_LOW 0x05
#define OPL4_REG_MEMORY_DATA 0x06
/*
* Offsets to the register banks for voices. To get the
* register number just add the voice number to the bank offset.
*
* Wave Table Number low bits (0x08 to 0x1F)
*/
#define OPL4_REG_TONE_NUMBER 0x08
/* Wave Table Number high bit, F-Number low bits (0x20 to 0x37) */
#define OPL4_REG_F_NUMBER 0x20
#define OPL4_TONE_NUMBER_BIT8 0x01
#define OPL4_F_NUMBER_LOW_MASK 0xfe
/* F-Number high bits, Octave, Pseudo-Reverb (0x38 to 0x4F) */
#define OPL4_REG_OCTAVE 0x38
#define OPL4_F_NUMBER_HIGH_MASK 0x07
#define OPL4_BLOCK_MASK 0xf0
#define OPL4_PSEUDO_REVERB_BIT 0x08
/* Total Level, Level Direct (0x50 to 0x67) */
#define OPL4_REG_LEVEL 0x50
#define OPL4_TOTAL_LEVEL_MASK 0xfe
#define OPL4_LEVEL_DIRECT_BIT 0x01
/* Key On, Damp, LFO RST, CH, Panpot (0x68 to 0x7F) */
#define OPL4_REG_MISC 0x68
#define OPL4_KEY_ON_BIT 0x80
#define OPL4_DAMP_BIT 0x40
#define OPL4_LFO_RESET_BIT 0x20
#define OPL4_OUTPUT_CHANNEL_BIT 0x10
#define OPL4_PAN_POT_MASK 0x0f
/* LFO, VIB (0x80 to 0x97) */
#define OPL4_REG_LFO_VIBRATO 0x80
#define OPL4_LFO_FREQUENCY_MASK 0x38
#define OPL4_VIBRATO_DEPTH_MASK 0x07
#define OPL4_CHORUS_SEND_MASK 0xc0 /* ML only */
/* Attack / Decay 1 rate (0x98 to 0xAF) */
#define OPL4_REG_ATTACK_DECAY1 0x98
#define OPL4_ATTACK_RATE_MASK 0xf0
#define OPL4_DECAY1_RATE_MASK 0x0f
/* Decay level / 2 rate (0xB0 to 0xC7) */
#define OPL4_REG_LEVEL_DECAY2 0xb0
#define OPL4_DECAY_LEVEL_MASK 0xf0
#define OPL4_DECAY2_RATE_MASK 0x0f
/* Release rate / Rate correction (0xC8 to 0xDF) */
#define OPL4_REG_RELEASE_CORRECTION 0xc8
#define OPL4_RELEASE_RATE_MASK 0x0f
#define OPL4_RATE_INTERPOLATION_MASK 0xf0
/* AM (0xE0 to 0xF7) */
#define OPL4_REG_TREMOLO 0xe0
#define OPL4_TREMOLO_DEPTH_MASK 0x07
#define OPL4_REVERB_SEND_MASK 0xe0 /* ML only */
/* Mixer */
#define OPL4_REG_MIX_CONTROL_FM 0xf8
#define OPL4_REG_MIX_CONTROL_PCM 0xf9
#define OPL4_MIX_LEFT_MASK 0x07
#define OPL4_MIX_RIGHT_MASK 0x38
#define OPL4_REG_ATC 0xfa
#define OPL4_ATC_BIT 0x01 /* ???, ML only */
/* bits in the OPL3 Status register */
#define OPL4_STATUS_BUSY 0x01
#define OPL4_STATUS_LOAD 0x02
#define OPL4_MAX_VOICES 24
#define SNDRV_SEQ_DEV_ID_OPL4 "opl4-synth"
struct opl4_sound {
u16 tone;
s16 pitch_offset;
u8 key_scaling;
s8 panpot;
u8 vibrato;
u8 tone_attenuate;
u8 volume_factor;
u8 reg_lfo_vibrato;
u8 reg_attack_decay1;
u8 reg_level_decay2;
u8 reg_release_correction;
u8 reg_tremolo;
};
struct opl4_region {
u8 key_min, key_max;
struct opl4_sound sound;
};
struct opl4_region_ptr {
int count;
const struct opl4_region *regions;
};
struct opl4_voice {
struct list_head list;
int number;
struct snd_midi_channel *chan;
int note;
int velocity;
const struct opl4_sound *sound;
u8 level_direct;
u8 reg_f_number;
u8 reg_misc;
u8 reg_lfo_vibrato;
};
struct snd_opl4 {
unsigned long fm_port;
unsigned long pcm_port;
struct resource *res_fm_port;
struct resource *res_pcm_port;
unsigned short hardware;
spinlock_t reg_lock;
struct snd_card *card;
#ifdef CONFIG_SND_PROC_FS
struct snd_info_entry *proc_entry;
int memory_access;
#endif
struct mutex access_mutex;
#if IS_ENABLED(CONFIG_SND_SEQUENCER)
int used;
int seq_dev_num;
int seq_client;
struct snd_seq_device *seq_dev;
struct snd_midi_channel_set *chset;
struct opl4_voice voices[OPL4_MAX_VOICES];
struct list_head off_voices;
struct list_head on_voices;
#endif
};
/* opl4_lib.c */
void snd_opl4_write(struct snd_opl4 *opl4, u8 reg, u8 value);
u8 snd_opl4_read(struct snd_opl4 *opl4, u8 reg);
void snd_opl4_read_memory(struct snd_opl4 *opl4, char *buf, int offset, int size);
void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size);
/* opl4_mixer.c */
int snd_opl4_create_mixer(struct snd_opl4 *opl4);
#ifdef CONFIG_SND_PROC_FS
/* opl4_proc.c */
int snd_opl4_create_proc(struct snd_opl4 *opl4);
void snd_opl4_free_proc(struct snd_opl4 *opl4);
#else
static inline int snd_opl4_create_proc(struct snd_opl4 *opl4) { return 0; }
static inline void snd_opl4_free_proc(struct snd_opl4 *opl4) {}
#endif
/* opl4_seq.c */
extern int volume_boost;
/* opl4_synth.c */
void snd_opl4_synth_reset(struct snd_opl4 *opl4);
void snd_opl4_synth_shutdown(struct snd_opl4 *opl4);
void snd_opl4_note_on(void *p, int note, int vel, struct snd_midi_channel *chan);
void snd_opl4_note_off(void *p, int note, int vel, struct snd_midi_channel *chan);
void snd_opl4_terminate_note(void *p, int note, struct snd_midi_channel *chan);
void snd_opl4_control(void *p, int type, struct snd_midi_channel *chan);
void snd_opl4_sysex(void *p, unsigned char *buf, int len, int parsed, struct snd_midi_channel_set *chset);
/* yrw801.c */
int snd_yrw801_detect(struct snd_opl4 *opl4);
extern const struct opl4_region_ptr snd_yrw801_regions[];
#endif /* __OPL4_LOCAL_H */
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* OPL4 mixer functions
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
*/
#include "opl4_local.h"
#include <sound/control.h>
static int snd_opl4_ctl_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 = 7;
return 0;
}
static int snd_opl4_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_opl4 *opl4 = snd_kcontrol_chip(kcontrol);
u8 reg = kcontrol->private_value;
u8 value;
guard(spinlock_irqsave)(&opl4->reg_lock);
value = snd_opl4_read(opl4, reg);
ucontrol->value.integer.value[0] = 7 - (value & 7);
ucontrol->value.integer.value[1] = 7 - ((value >> 3) & 7);
return 0;
}
static int snd_opl4_ctl_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_opl4 *opl4 = snd_kcontrol_chip(kcontrol);
u8 reg = kcontrol->private_value;
u8 value, old_value;
value = (7 - (ucontrol->value.integer.value[0] & 7)) |
((7 - (ucontrol->value.integer.value[1] & 7)) << 3);
guard(spinlock_irqsave)(&opl4->reg_lock);
old_value = snd_opl4_read(opl4, reg);
snd_opl4_write(opl4, reg, value);
return value != old_value;
}
static const struct snd_kcontrol_new snd_opl4_controls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "FM Playback Volume",
.info = snd_opl4_ctl_info,
.get = snd_opl4_ctl_get,
.put = snd_opl4_ctl_put,
.private_value = OPL4_REG_MIX_CONTROL_FM
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Wavetable Playback Volume",
.info = snd_opl4_ctl_info,
.get = snd_opl4_ctl_get,
.put = snd_opl4_ctl_put,
.private_value = OPL4_REG_MIX_CONTROL_PCM
}
};
int snd_opl4_create_mixer(struct snd_opl4 *opl4)
{
struct snd_card *card = opl4->card;
int i, err;
strcat(card->mixername, ",OPL4");
for (i = 0; i < 2; ++i) {
err = snd_ctl_add(card, snd_ctl_new1(&snd_opl4_controls[i], opl4));
if (err < 0)
return err;
}
return 0;
}
+108
View File
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Functions for the OPL4 proc file
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
*/
#include "opl4_local.h"
#include <linux/vmalloc.h>
#include <linux/export.h>
#include <sound/info.h>
static int snd_opl4_mem_proc_open(struct snd_info_entry *entry,
unsigned short mode, void **file_private_data)
{
struct snd_opl4 *opl4 = entry->private_data;
guard(mutex)(&opl4->access_mutex);
if (opl4->memory_access)
return -EBUSY;
opl4->memory_access++;
return 0;
}
static int snd_opl4_mem_proc_release(struct snd_info_entry *entry,
unsigned short mode, void *file_private_data)
{
struct snd_opl4 *opl4 = entry->private_data;
guard(mutex)(&opl4->access_mutex);
opl4->memory_access--;
return 0;
}
static ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry,
void *file_private_data,
struct file *file, char __user *_buf,
size_t count, loff_t pos)
{
struct snd_opl4 *opl4 = entry->private_data;
char* buf;
buf = vmalloc(count);
if (!buf)
return -ENOMEM;
snd_opl4_read_memory(opl4, buf, pos, count);
if (copy_to_user(_buf, buf, count)) {
vfree(buf);
return -EFAULT;
}
vfree(buf);
return count;
}
static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry,
void *file_private_data,
struct file *file,
const char __user *_buf,
size_t count, loff_t pos)
{
struct snd_opl4 *opl4 = entry->private_data;
char *buf;
buf = vmalloc(count);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, _buf, count)) {
vfree(buf);
return -EFAULT;
}
snd_opl4_write_memory(opl4, buf, pos, count);
vfree(buf);
return count;
}
static const struct snd_info_entry_ops snd_opl4_mem_proc_ops = {
.open = snd_opl4_mem_proc_open,
.release = snd_opl4_mem_proc_release,
.read = snd_opl4_mem_proc_read,
.write = snd_opl4_mem_proc_write,
};
int snd_opl4_create_proc(struct snd_opl4 *opl4)
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(opl4->card, "opl4-mem", opl4->card->proc_root);
if (entry) {
if (opl4->hardware < OPL3_HW_OPL4_ML) {
/* OPL4 can access 4 MB external ROM/SRAM */
entry->mode |= 0200;
entry->size = 4 * 1024 * 1024;
} else {
/* OPL4-ML has 1 MB internal ROM */
entry->size = 1 * 1024 * 1024;
}
entry->content = SNDRV_INFO_CONTENT_DATA;
entry->c.ops = &snd_opl4_mem_proc_ops;
entry->module = THIS_MODULE;
entry->private_data = opl4;
}
opl4->proc_entry = entry;
return 0;
}
void snd_opl4_free_proc(struct snd_opl4 *opl4)
{
snd_info_free_entry(opl4->proc_entry);
}
+201
View File
@@ -0,0 +1,201 @@
/*
* OPL4 sequencer functions
*
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "opl4_local.h"
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/module.h>
#include <sound/initval.h>
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_DESCRIPTION("OPL4 wavetable synth driver");
MODULE_LICENSE("Dual BSD/GPL");
int volume_boost = 8;
module_param(volume_boost, int, 0644);
MODULE_PARM_DESC(volume_boost, "Additional volume for OPL4 wavetable sounds.");
static int snd_opl4_seq_use_inc(struct snd_opl4 *opl4)
{
if (!try_module_get(opl4->card->module))
return -EFAULT;
return 0;
}
static void snd_opl4_seq_use_dec(struct snd_opl4 *opl4)
{
module_put(opl4->card->module);
}
static int snd_opl4_seq_use(void *private_data, struct snd_seq_port_subscribe *info)
{
struct snd_opl4 *opl4 = private_data;
int err;
scoped_guard(mutex, &opl4->access_mutex) {
if (opl4->used)
return -EBUSY;
opl4->used++;
if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {
err = snd_opl4_seq_use_inc(opl4);
if (err < 0)
return err;
}
}
snd_opl4_synth_reset(opl4);
return 0;
}
static int snd_opl4_seq_unuse(void *private_data, struct snd_seq_port_subscribe *info)
{
struct snd_opl4 *opl4 = private_data;
snd_opl4_synth_shutdown(opl4);
scoped_guard(mutex, &opl4->access_mutex) {
opl4->used--;
}
if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
snd_opl4_seq_use_dec(opl4);
return 0;
}
static const struct snd_midi_op opl4_ops = {
.note_on = snd_opl4_note_on,
.note_off = snd_opl4_note_off,
.note_terminate = snd_opl4_terminate_note,
.control = snd_opl4_control,
.sysex = snd_opl4_sysex,
};
static int snd_opl4_seq_event_input(struct snd_seq_event *ev, int direct,
void *private_data, int atomic, int hop)
{
struct snd_opl4 *opl4 = private_data;
snd_midi_process_event(&opl4_ops, ev, opl4->chset);
return 0;
}
static void snd_opl4_seq_free_port(void *private_data)
{
struct snd_opl4 *opl4 = private_data;
snd_midi_channel_free_set(opl4->chset);
}
static int snd_opl4_seq_probe(struct snd_seq_device *dev)
{
struct snd_opl4 *opl4;
int client;
struct snd_seq_port_callback pcallbacks;
opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
if (!opl4)
return -EINVAL;
if (snd_yrw801_detect(opl4) < 0)
return -ENODEV;
opl4->chset = snd_midi_channel_alloc_set(16);
if (!opl4->chset)
return -ENOMEM;
opl4->chset->private_data = opl4;
/* allocate new client */
client = snd_seq_create_kernel_client(opl4->card, opl4->seq_dev_num,
"OPL4 Wavetable");
if (client < 0) {
snd_midi_channel_free_set(opl4->chset);
return client;
}
opl4->seq_client = client;
opl4->chset->client = client;
/* create new port */
memset(&pcallbacks, 0, sizeof(pcallbacks));
pcallbacks.owner = THIS_MODULE;
pcallbacks.use = snd_opl4_seq_use;
pcallbacks.unuse = snd_opl4_seq_unuse;
pcallbacks.event_input = snd_opl4_seq_event_input;
pcallbacks.private_free = snd_opl4_seq_free_port;
pcallbacks.private_data = opl4;
opl4->chset->port = snd_seq_event_port_attach(client, &pcallbacks,
SNDRV_SEQ_PORT_CAP_WRITE |
SNDRV_SEQ_PORT_CAP_SUBS_WRITE,
SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
SNDRV_SEQ_PORT_TYPE_MIDI_GM |
SNDRV_SEQ_PORT_TYPE_HARDWARE |
SNDRV_SEQ_PORT_TYPE_SYNTHESIZER,
16, 24,
"OPL4 Wavetable Port");
if (opl4->chset->port < 0) {
int err = opl4->chset->port;
snd_midi_channel_free_set(opl4->chset);
snd_seq_delete_kernel_client(client);
opl4->seq_client = -1;
return err;
}
return 0;
}
static void snd_opl4_seq_remove(struct snd_seq_device *dev)
{
struct snd_opl4 *opl4;
opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
if (!opl4)
return;
if (opl4->seq_client >= 0) {
snd_seq_delete_kernel_client(opl4->seq_client);
opl4->seq_client = -1;
}
}
static struct snd_seq_driver opl4_seq_driver = {
.probe = snd_opl4_seq_probe,
.remove = snd_opl4_seq_remove,
.driver = {
.name = KBUILD_MODNAME,
},
.id = SNDRV_SEQ_DEV_ID_OPL4,
.argsize = sizeof(struct snd_opl4 *),
};
module_snd_seq_driver(opl4_seq_driver);
+620
View File
@@ -0,0 +1,620 @@
/*
* OPL4 MIDI synthesizer functions
*
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "opl4_local.h"
#include <linux/delay.h>
#include <linux/io.h>
#include <sound/asoundef.h>
/* GM2 controllers */
#ifndef MIDI_CTL_RELEASE_TIME
#define MIDI_CTL_RELEASE_TIME 0x48
#define MIDI_CTL_ATTACK_TIME 0x49
#define MIDI_CTL_DECAY_TIME 0x4b
#define MIDI_CTL_VIBRATO_RATE 0x4c
#define MIDI_CTL_VIBRATO_DEPTH 0x4d
#define MIDI_CTL_VIBRATO_DELAY 0x4e
#endif
/*
* This table maps 100/128 cents to F_NUMBER.
*/
static const s16 snd_opl4_pitch_map[0x600] = {
0x000,0x000,0x001,0x001,0x002,0x002,0x003,0x003,
0x004,0x004,0x005,0x005,0x006,0x006,0x006,0x007,
0x007,0x008,0x008,0x009,0x009,0x00a,0x00a,0x00b,
0x00b,0x00c,0x00c,0x00d,0x00d,0x00d,0x00e,0x00e,
0x00f,0x00f,0x010,0x010,0x011,0x011,0x012,0x012,
0x013,0x013,0x014,0x014,0x015,0x015,0x015,0x016,
0x016,0x017,0x017,0x018,0x018,0x019,0x019,0x01a,
0x01a,0x01b,0x01b,0x01c,0x01c,0x01d,0x01d,0x01e,
0x01e,0x01e,0x01f,0x01f,0x020,0x020,0x021,0x021,
0x022,0x022,0x023,0x023,0x024,0x024,0x025,0x025,
0x026,0x026,0x027,0x027,0x028,0x028,0x029,0x029,
0x029,0x02a,0x02a,0x02b,0x02b,0x02c,0x02c,0x02d,
0x02d,0x02e,0x02e,0x02f,0x02f,0x030,0x030,0x031,
0x031,0x032,0x032,0x033,0x033,0x034,0x034,0x035,
0x035,0x036,0x036,0x037,0x037,0x038,0x038,0x038,
0x039,0x039,0x03a,0x03a,0x03b,0x03b,0x03c,0x03c,
0x03d,0x03d,0x03e,0x03e,0x03f,0x03f,0x040,0x040,
0x041,0x041,0x042,0x042,0x043,0x043,0x044,0x044,
0x045,0x045,0x046,0x046,0x047,0x047,0x048,0x048,
0x049,0x049,0x04a,0x04a,0x04b,0x04b,0x04c,0x04c,
0x04d,0x04d,0x04e,0x04e,0x04f,0x04f,0x050,0x050,
0x051,0x051,0x052,0x052,0x053,0x053,0x054,0x054,
0x055,0x055,0x056,0x056,0x057,0x057,0x058,0x058,
0x059,0x059,0x05a,0x05a,0x05b,0x05b,0x05c,0x05c,
0x05d,0x05d,0x05e,0x05e,0x05f,0x05f,0x060,0x060,
0x061,0x061,0x062,0x062,0x063,0x063,0x064,0x064,
0x065,0x065,0x066,0x066,0x067,0x067,0x068,0x068,
0x069,0x069,0x06a,0x06a,0x06b,0x06b,0x06c,0x06c,
0x06d,0x06d,0x06e,0x06e,0x06f,0x06f,0x070,0x071,
0x071,0x072,0x072,0x073,0x073,0x074,0x074,0x075,
0x075,0x076,0x076,0x077,0x077,0x078,0x078,0x079,
0x079,0x07a,0x07a,0x07b,0x07b,0x07c,0x07c,0x07d,
0x07d,0x07e,0x07e,0x07f,0x07f,0x080,0x081,0x081,
0x082,0x082,0x083,0x083,0x084,0x084,0x085,0x085,
0x086,0x086,0x087,0x087,0x088,0x088,0x089,0x089,
0x08a,0x08a,0x08b,0x08b,0x08c,0x08d,0x08d,0x08e,
0x08e,0x08f,0x08f,0x090,0x090,0x091,0x091,0x092,
0x092,0x093,0x093,0x094,0x094,0x095,0x096,0x096,
0x097,0x097,0x098,0x098,0x099,0x099,0x09a,0x09a,
0x09b,0x09b,0x09c,0x09c,0x09d,0x09d,0x09e,0x09f,
0x09f,0x0a0,0x0a0,0x0a1,0x0a1,0x0a2,0x0a2,0x0a3,
0x0a3,0x0a4,0x0a4,0x0a5,0x0a6,0x0a6,0x0a7,0x0a7,
0x0a8,0x0a8,0x0a9,0x0a9,0x0aa,0x0aa,0x0ab,0x0ab,
0x0ac,0x0ad,0x0ad,0x0ae,0x0ae,0x0af,0x0af,0x0b0,
0x0b0,0x0b1,0x0b1,0x0b2,0x0b2,0x0b3,0x0b4,0x0b4,
0x0b5,0x0b5,0x0b6,0x0b6,0x0b7,0x0b7,0x0b8,0x0b8,
0x0b9,0x0ba,0x0ba,0x0bb,0x0bb,0x0bc,0x0bc,0x0bd,
0x0bd,0x0be,0x0be,0x0bf,0x0c0,0x0c0,0x0c1,0x0c1,
0x0c2,0x0c2,0x0c3,0x0c3,0x0c4,0x0c4,0x0c5,0x0c6,
0x0c6,0x0c7,0x0c7,0x0c8,0x0c8,0x0c9,0x0c9,0x0ca,
0x0cb,0x0cb,0x0cc,0x0cc,0x0cd,0x0cd,0x0ce,0x0ce,
0x0cf,0x0d0,0x0d0,0x0d1,0x0d1,0x0d2,0x0d2,0x0d3,
0x0d3,0x0d4,0x0d5,0x0d5,0x0d6,0x0d6,0x0d7,0x0d7,
0x0d8,0x0d8,0x0d9,0x0da,0x0da,0x0db,0x0db,0x0dc,
0x0dc,0x0dd,0x0de,0x0de,0x0df,0x0df,0x0e0,0x0e0,
0x0e1,0x0e1,0x0e2,0x0e3,0x0e3,0x0e4,0x0e4,0x0e5,
0x0e5,0x0e6,0x0e7,0x0e7,0x0e8,0x0e8,0x0e9,0x0e9,
0x0ea,0x0eb,0x0eb,0x0ec,0x0ec,0x0ed,0x0ed,0x0ee,
0x0ef,0x0ef,0x0f0,0x0f0,0x0f1,0x0f1,0x0f2,0x0f3,
0x0f3,0x0f4,0x0f4,0x0f5,0x0f5,0x0f6,0x0f7,0x0f7,
0x0f8,0x0f8,0x0f9,0x0f9,0x0fa,0x0fb,0x0fb,0x0fc,
0x0fc,0x0fd,0x0fd,0x0fe,0x0ff,0x0ff,0x100,0x100,
0x101,0x101,0x102,0x103,0x103,0x104,0x104,0x105,
0x106,0x106,0x107,0x107,0x108,0x108,0x109,0x10a,
0x10a,0x10b,0x10b,0x10c,0x10c,0x10d,0x10e,0x10e,
0x10f,0x10f,0x110,0x111,0x111,0x112,0x112,0x113,
0x114,0x114,0x115,0x115,0x116,0x116,0x117,0x118,
0x118,0x119,0x119,0x11a,0x11b,0x11b,0x11c,0x11c,
0x11d,0x11e,0x11e,0x11f,0x11f,0x120,0x120,0x121,
0x122,0x122,0x123,0x123,0x124,0x125,0x125,0x126,
0x126,0x127,0x128,0x128,0x129,0x129,0x12a,0x12b,
0x12b,0x12c,0x12c,0x12d,0x12e,0x12e,0x12f,0x12f,
0x130,0x131,0x131,0x132,0x132,0x133,0x134,0x134,
0x135,0x135,0x136,0x137,0x137,0x138,0x138,0x139,
0x13a,0x13a,0x13b,0x13b,0x13c,0x13d,0x13d,0x13e,
0x13e,0x13f,0x140,0x140,0x141,0x141,0x142,0x143,
0x143,0x144,0x144,0x145,0x146,0x146,0x147,0x148,
0x148,0x149,0x149,0x14a,0x14b,0x14b,0x14c,0x14c,
0x14d,0x14e,0x14e,0x14f,0x14f,0x150,0x151,0x151,
0x152,0x153,0x153,0x154,0x154,0x155,0x156,0x156,
0x157,0x157,0x158,0x159,0x159,0x15a,0x15b,0x15b,
0x15c,0x15c,0x15d,0x15e,0x15e,0x15f,0x160,0x160,
0x161,0x161,0x162,0x163,0x163,0x164,0x165,0x165,
0x166,0x166,0x167,0x168,0x168,0x169,0x16a,0x16a,
0x16b,0x16b,0x16c,0x16d,0x16d,0x16e,0x16f,0x16f,
0x170,0x170,0x171,0x172,0x172,0x173,0x174,0x174,
0x175,0x175,0x176,0x177,0x177,0x178,0x179,0x179,
0x17a,0x17a,0x17b,0x17c,0x17c,0x17d,0x17e,0x17e,
0x17f,0x180,0x180,0x181,0x181,0x182,0x183,0x183,
0x184,0x185,0x185,0x186,0x187,0x187,0x188,0x188,
0x189,0x18a,0x18a,0x18b,0x18c,0x18c,0x18d,0x18e,
0x18e,0x18f,0x190,0x190,0x191,0x191,0x192,0x193,
0x193,0x194,0x195,0x195,0x196,0x197,0x197,0x198,
0x199,0x199,0x19a,0x19a,0x19b,0x19c,0x19c,0x19d,
0x19e,0x19e,0x19f,0x1a0,0x1a0,0x1a1,0x1a2,0x1a2,
0x1a3,0x1a4,0x1a4,0x1a5,0x1a6,0x1a6,0x1a7,0x1a8,
0x1a8,0x1a9,0x1a9,0x1aa,0x1ab,0x1ab,0x1ac,0x1ad,
0x1ad,0x1ae,0x1af,0x1af,0x1b0,0x1b1,0x1b1,0x1b2,
0x1b3,0x1b3,0x1b4,0x1b5,0x1b5,0x1b6,0x1b7,0x1b7,
0x1b8,0x1b9,0x1b9,0x1ba,0x1bb,0x1bb,0x1bc,0x1bd,
0x1bd,0x1be,0x1bf,0x1bf,0x1c0,0x1c1,0x1c1,0x1c2,
0x1c3,0x1c3,0x1c4,0x1c5,0x1c5,0x1c6,0x1c7,0x1c7,
0x1c8,0x1c9,0x1c9,0x1ca,0x1cb,0x1cb,0x1cc,0x1cd,
0x1cd,0x1ce,0x1cf,0x1cf,0x1d0,0x1d1,0x1d1,0x1d2,
0x1d3,0x1d3,0x1d4,0x1d5,0x1d5,0x1d6,0x1d7,0x1d7,
0x1d8,0x1d9,0x1d9,0x1da,0x1db,0x1db,0x1dc,0x1dd,
0x1dd,0x1de,0x1df,0x1df,0x1e0,0x1e1,0x1e1,0x1e2,
0x1e3,0x1e4,0x1e4,0x1e5,0x1e6,0x1e6,0x1e7,0x1e8,
0x1e8,0x1e9,0x1ea,0x1ea,0x1eb,0x1ec,0x1ec,0x1ed,
0x1ee,0x1ee,0x1ef,0x1f0,0x1f0,0x1f1,0x1f2,0x1f3,
0x1f3,0x1f4,0x1f5,0x1f5,0x1f6,0x1f7,0x1f7,0x1f8,
0x1f9,0x1f9,0x1fa,0x1fb,0x1fb,0x1fc,0x1fd,0x1fe,
0x1fe,0x1ff,0x200,0x200,0x201,0x202,0x202,0x203,
0x204,0x205,0x205,0x206,0x207,0x207,0x208,0x209,
0x209,0x20a,0x20b,0x20b,0x20c,0x20d,0x20e,0x20e,
0x20f,0x210,0x210,0x211,0x212,0x212,0x213,0x214,
0x215,0x215,0x216,0x217,0x217,0x218,0x219,0x21a,
0x21a,0x21b,0x21c,0x21c,0x21d,0x21e,0x21e,0x21f,
0x220,0x221,0x221,0x222,0x223,0x223,0x224,0x225,
0x226,0x226,0x227,0x228,0x228,0x229,0x22a,0x22b,
0x22b,0x22c,0x22d,0x22d,0x22e,0x22f,0x230,0x230,
0x231,0x232,0x232,0x233,0x234,0x235,0x235,0x236,
0x237,0x237,0x238,0x239,0x23a,0x23a,0x23b,0x23c,
0x23c,0x23d,0x23e,0x23f,0x23f,0x240,0x241,0x241,
0x242,0x243,0x244,0x244,0x245,0x246,0x247,0x247,
0x248,0x249,0x249,0x24a,0x24b,0x24c,0x24c,0x24d,
0x24e,0x24f,0x24f,0x250,0x251,0x251,0x252,0x253,
0x254,0x254,0x255,0x256,0x257,0x257,0x258,0x259,
0x259,0x25a,0x25b,0x25c,0x25c,0x25d,0x25e,0x25f,
0x25f,0x260,0x261,0x262,0x262,0x263,0x264,0x265,
0x265,0x266,0x267,0x267,0x268,0x269,0x26a,0x26a,
0x26b,0x26c,0x26d,0x26d,0x26e,0x26f,0x270,0x270,
0x271,0x272,0x273,0x273,0x274,0x275,0x276,0x276,
0x277,0x278,0x279,0x279,0x27a,0x27b,0x27c,0x27c,
0x27d,0x27e,0x27f,0x27f,0x280,0x281,0x282,0x282,
0x283,0x284,0x285,0x285,0x286,0x287,0x288,0x288,
0x289,0x28a,0x28b,0x28b,0x28c,0x28d,0x28e,0x28e,
0x28f,0x290,0x291,0x291,0x292,0x293,0x294,0x294,
0x295,0x296,0x297,0x298,0x298,0x299,0x29a,0x29b,
0x29b,0x29c,0x29d,0x29e,0x29e,0x29f,0x2a0,0x2a1,
0x2a1,0x2a2,0x2a3,0x2a4,0x2a5,0x2a5,0x2a6,0x2a7,
0x2a8,0x2a8,0x2a9,0x2aa,0x2ab,0x2ab,0x2ac,0x2ad,
0x2ae,0x2af,0x2af,0x2b0,0x2b1,0x2b2,0x2b2,0x2b3,
0x2b4,0x2b5,0x2b5,0x2b6,0x2b7,0x2b8,0x2b9,0x2b9,
0x2ba,0x2bb,0x2bc,0x2bc,0x2bd,0x2be,0x2bf,0x2c0,
0x2c0,0x2c1,0x2c2,0x2c3,0x2c4,0x2c4,0x2c5,0x2c6,
0x2c7,0x2c7,0x2c8,0x2c9,0x2ca,0x2cb,0x2cb,0x2cc,
0x2cd,0x2ce,0x2ce,0x2cf,0x2d0,0x2d1,0x2d2,0x2d2,
0x2d3,0x2d4,0x2d5,0x2d6,0x2d6,0x2d7,0x2d8,0x2d9,
0x2da,0x2da,0x2db,0x2dc,0x2dd,0x2dd,0x2de,0x2df,
0x2e0,0x2e1,0x2e1,0x2e2,0x2e3,0x2e4,0x2e5,0x2e5,
0x2e6,0x2e7,0x2e8,0x2e9,0x2e9,0x2ea,0x2eb,0x2ec,
0x2ed,0x2ed,0x2ee,0x2ef,0x2f0,0x2f1,0x2f1,0x2f2,
0x2f3,0x2f4,0x2f5,0x2f5,0x2f6,0x2f7,0x2f8,0x2f9,
0x2f9,0x2fa,0x2fb,0x2fc,0x2fd,0x2fd,0x2fe,0x2ff,
0x300,0x301,0x302,0x302,0x303,0x304,0x305,0x306,
0x306,0x307,0x308,0x309,0x30a,0x30a,0x30b,0x30c,
0x30d,0x30e,0x30f,0x30f,0x310,0x311,0x312,0x313,
0x313,0x314,0x315,0x316,0x317,0x318,0x318,0x319,
0x31a,0x31b,0x31c,0x31c,0x31d,0x31e,0x31f,0x320,
0x321,0x321,0x322,0x323,0x324,0x325,0x326,0x326,
0x327,0x328,0x329,0x32a,0x32a,0x32b,0x32c,0x32d,
0x32e,0x32f,0x32f,0x330,0x331,0x332,0x333,0x334,
0x334,0x335,0x336,0x337,0x338,0x339,0x339,0x33a,
0x33b,0x33c,0x33d,0x33e,0x33e,0x33f,0x340,0x341,
0x342,0x343,0x343,0x344,0x345,0x346,0x347,0x348,
0x349,0x349,0x34a,0x34b,0x34c,0x34d,0x34e,0x34e,
0x34f,0x350,0x351,0x352,0x353,0x353,0x354,0x355,
0x356,0x357,0x358,0x359,0x359,0x35a,0x35b,0x35c,
0x35d,0x35e,0x35f,0x35f,0x360,0x361,0x362,0x363,
0x364,0x364,0x365,0x366,0x367,0x368,0x369,0x36a,
0x36a,0x36b,0x36c,0x36d,0x36e,0x36f,0x370,0x370,
0x371,0x372,0x373,0x374,0x375,0x376,0x377,0x377,
0x378,0x379,0x37a,0x37b,0x37c,0x37d,0x37d,0x37e,
0x37f,0x380,0x381,0x382,0x383,0x383,0x384,0x385,
0x386,0x387,0x388,0x389,0x38a,0x38a,0x38b,0x38c,
0x38d,0x38e,0x38f,0x390,0x391,0x391,0x392,0x393,
0x394,0x395,0x396,0x397,0x398,0x398,0x399,0x39a,
0x39b,0x39c,0x39d,0x39e,0x39f,0x39f,0x3a0,0x3a1,
0x3a2,0x3a3,0x3a4,0x3a5,0x3a6,0x3a7,0x3a7,0x3a8,
0x3a9,0x3aa,0x3ab,0x3ac,0x3ad,0x3ae,0x3ae,0x3af,
0x3b0,0x3b1,0x3b2,0x3b3,0x3b4,0x3b5,0x3b6,0x3b6,
0x3b7,0x3b8,0x3b9,0x3ba,0x3bb,0x3bc,0x3bd,0x3be,
0x3bf,0x3bf,0x3c0,0x3c1,0x3c2,0x3c3,0x3c4,0x3c5,
0x3c6,0x3c7,0x3c7,0x3c8,0x3c9,0x3ca,0x3cb,0x3cc,
0x3cd,0x3ce,0x3cf,0x3d0,0x3d1,0x3d1,0x3d2,0x3d3,
0x3d4,0x3d5,0x3d6,0x3d7,0x3d8,0x3d9,0x3da,0x3da,
0x3db,0x3dc,0x3dd,0x3de,0x3df,0x3e0,0x3e1,0x3e2,
0x3e3,0x3e4,0x3e4,0x3e5,0x3e6,0x3e7,0x3e8,0x3e9,
0x3ea,0x3eb,0x3ec,0x3ed,0x3ee,0x3ef,0x3ef,0x3f0,
0x3f1,0x3f2,0x3f3,0x3f4,0x3f5,0x3f6,0x3f7,0x3f8,
0x3f9,0x3fa,0x3fa,0x3fb,0x3fc,0x3fd,0x3fe,0x3ff
};
/*
* Attenuation according to GM recommendations, in -0.375 dB units.
* table[v] = 40 * log(v / 127) / -0.375
*/
static const unsigned char snd_opl4_volume_table[128] = {
255,224,192,173,160,150,141,134,
128,122,117,113,109,105,102, 99,
96, 93, 90, 88, 85, 83, 81, 79,
77, 75, 73, 71, 70, 68, 67, 65,
64, 62, 61, 59, 58, 57, 56, 54,
53, 52, 51, 50, 49, 48, 47, 46,
45, 44, 43, 42, 41, 40, 39, 39,
38, 37, 36, 35, 34, 34, 33, 32,
31, 31, 30, 29, 29, 28, 27, 27,
26, 25, 25, 24, 24, 23, 22, 22,
21, 21, 20, 19, 19, 18, 18, 17,
17, 16, 16, 15, 15, 14, 14, 13,
13, 12, 12, 11, 11, 10, 10, 9,
9, 9, 8, 8, 7, 7, 6, 6,
6, 5, 5, 4, 4, 4, 3, 3,
2, 2, 2, 1, 1, 0, 0, 0
};
/*
* Initializes all voices.
*/
void snd_opl4_synth_reset(struct snd_opl4 *opl4)
{
int i;
scoped_guard(spinlock_irqsave, &opl4->reg_lock) {
for (i = 0; i < OPL4_MAX_VOICES; i++)
snd_opl4_write(opl4, OPL4_REG_MISC + i, OPL4_DAMP_BIT);
}
INIT_LIST_HEAD(&opl4->off_voices);
INIT_LIST_HEAD(&opl4->on_voices);
memset(opl4->voices, 0, sizeof(opl4->voices));
for (i = 0; i < OPL4_MAX_VOICES; i++) {
opl4->voices[i].number = i;
list_add_tail(&opl4->voices[i].list, &opl4->off_voices);
}
snd_midi_channel_set_clear(opl4->chset);
}
/*
* Shuts down all voices.
*/
void snd_opl4_synth_shutdown(struct snd_opl4 *opl4)
{
int i;
guard(spinlock_irqsave)(&opl4->reg_lock);
for (i = 0; i < OPL4_MAX_VOICES; i++)
snd_opl4_write(opl4, OPL4_REG_MISC + i,
opl4->voices[i].reg_misc & ~OPL4_KEY_ON_BIT);
}
/*
* Executes the callback for all voices playing the specified note.
*/
static void snd_opl4_do_for_note(struct snd_opl4 *opl4, int note, struct snd_midi_channel *chan,
void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice))
{
int i;
struct opl4_voice *voice;
guard(spinlock_irqsave)(&opl4->reg_lock);
for (i = 0; i < OPL4_MAX_VOICES; i++) {
voice = &opl4->voices[i];
if (voice->chan == chan && voice->note == note) {
func(opl4, voice);
}
}
}
/*
* Executes the callback for all voices of to the specified channel.
*/
static void snd_opl4_do_for_channel(struct snd_opl4 *opl4,
struct snd_midi_channel *chan,
void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice))
{
int i;
struct opl4_voice *voice;
guard(spinlock_irqsave)(&opl4->reg_lock);
for (i = 0; i < OPL4_MAX_VOICES; i++) {
voice = &opl4->voices[i];
if (voice->chan == chan) {
func(opl4, voice);
}
}
}
/*
* Executes the callback for all active voices.
*/
static void snd_opl4_do_for_all(struct snd_opl4 *opl4,
void (*func)(struct snd_opl4 *opl4, struct opl4_voice *voice))
{
int i;
struct opl4_voice *voice;
guard(spinlock_irqsave)(&opl4->reg_lock);
for (i = 0; i < OPL4_MAX_VOICES; i++) {
voice = &opl4->voices[i];
if (voice->chan)
func(opl4, voice);
}
}
static void snd_opl4_update_volume(struct snd_opl4 *opl4, struct opl4_voice *voice)
{
int att;
att = voice->sound->tone_attenuate;
att += snd_opl4_volume_table[opl4->chset->gs_master_volume & 0x7f];
att += snd_opl4_volume_table[voice->chan->gm_volume & 0x7f];
att += snd_opl4_volume_table[voice->chan->gm_expression & 0x7f];
att += snd_opl4_volume_table[voice->velocity];
att = 0x7f - (0x7f - att) * (voice->sound->volume_factor) / 0xfe - volume_boost;
if (att < 0)
att = 0;
else if (att > 0x7e)
att = 0x7e;
snd_opl4_write(opl4, OPL4_REG_LEVEL + voice->number,
(att << 1) | voice->level_direct);
voice->level_direct = 0;
}
static void snd_opl4_update_pan(struct snd_opl4 *opl4, struct opl4_voice *voice)
{
int pan = voice->sound->panpot;
if (!voice->chan->drum_channel)
pan += (voice->chan->control[MIDI_CTL_MSB_PAN] - 0x40) >> 3;
if (pan < -7)
pan = -7;
else if (pan > 7)
pan = 7;
voice->reg_misc = (voice->reg_misc & ~OPL4_PAN_POT_MASK)
| (pan & OPL4_PAN_POT_MASK);
snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
}
static void snd_opl4_update_vibrato_depth(struct snd_opl4 *opl4,
struct opl4_voice *voice)
{
int depth;
if (voice->chan->drum_channel)
return;
depth = (7 - voice->sound->vibrato)
* (voice->chan->control[MIDI_CTL_VIBRATO_DEPTH] & 0x7f);
depth = (depth >> 7) + voice->sound->vibrato;
voice->reg_lfo_vibrato &= ~OPL4_VIBRATO_DEPTH_MASK;
voice->reg_lfo_vibrato |= depth & OPL4_VIBRATO_DEPTH_MASK;
snd_opl4_write(opl4, OPL4_REG_LFO_VIBRATO + voice->number,
voice->reg_lfo_vibrato);
}
static void snd_opl4_update_pitch(struct snd_opl4 *opl4,
struct opl4_voice *voice)
{
struct snd_midi_channel *chan = voice->chan;
int note, pitch, octave;
note = chan->drum_channel ? 60 : voice->note;
/*
* pitch is in 100/128 cents, so 0x80 is one semitone and
* 0x600 is one octave.
*/
pitch = ((note - 60) << 7) * voice->sound->key_scaling / 100 + (60 << 7);
pitch += voice->sound->pitch_offset;
if (!chan->drum_channel)
pitch += chan->gm_rpn_coarse_tuning;
pitch += chan->gm_rpn_fine_tuning >> 7;
pitch += chan->midi_pitchbend * chan->gm_rpn_pitch_bend_range / 0x2000;
if (pitch < 0)
pitch = 0;
else if (pitch >= 0x6000)
pitch = 0x5fff;
octave = pitch / 0x600 - 8;
pitch = snd_opl4_pitch_map[pitch % 0x600];
snd_opl4_write(opl4, OPL4_REG_OCTAVE + voice->number,
(octave << 4) | ((pitch >> 7) & OPL4_F_NUMBER_HIGH_MASK));
voice->reg_f_number = (voice->reg_f_number & OPL4_TONE_NUMBER_BIT8)
| ((pitch << 1) & OPL4_F_NUMBER_LOW_MASK);
snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice->number, voice->reg_f_number);
}
static void snd_opl4_update_tone_parameters(struct snd_opl4 *opl4,
struct opl4_voice *voice)
{
snd_opl4_write(opl4, OPL4_REG_ATTACK_DECAY1 + voice->number,
voice->sound->reg_attack_decay1);
snd_opl4_write(opl4, OPL4_REG_LEVEL_DECAY2 + voice->number,
voice->sound->reg_level_decay2);
snd_opl4_write(opl4, OPL4_REG_RELEASE_CORRECTION + voice->number,
voice->sound->reg_release_correction);
snd_opl4_write(opl4, OPL4_REG_TREMOLO + voice->number,
voice->sound->reg_tremolo);
}
/* allocate one voice */
static struct opl4_voice *snd_opl4_get_voice(struct snd_opl4 *opl4)
{
/* first, try to get the oldest key-off voice */
if (!list_empty(&opl4->off_voices))
return list_entry(opl4->off_voices.next, struct opl4_voice, list);
/* then get the oldest key-on voice */
snd_BUG_ON(list_empty(&opl4->on_voices));
return list_entry(opl4->on_voices.next, struct opl4_voice, list);
}
static void snd_opl4_wait_for_wave_headers(struct snd_opl4 *opl4)
{
int timeout = 200;
while ((inb(opl4->fm_port) & OPL4_STATUS_LOAD) && --timeout > 0)
udelay(10);
}
void snd_opl4_note_on(void *private_data, int note, int vel, struct snd_midi_channel *chan)
{
struct snd_opl4 *opl4 = private_data;
const struct opl4_region_ptr *regions;
struct opl4_voice *voice[2];
const struct opl4_sound *sound[2];
int voices = 0, i;
/* determine the number of voices and voice parameters */
i = chan->drum_channel ? 0x80 : (chan->midi_program & 0x7f);
regions = &snd_yrw801_regions[i];
for (i = 0; i < regions->count; i++) {
if (note >= regions->regions[i].key_min &&
note <= regions->regions[i].key_max) {
sound[voices] = &regions->regions[i].sound;
if (++voices >= 2)
break;
}
}
/* allocate and initialize the needed voices */
scoped_guard(spinlock_irqsave, &opl4->reg_lock) {
for (i = 0; i < voices; i++) {
voice[i] = snd_opl4_get_voice(opl4);
list_move_tail(&voice[i]->list, &opl4->on_voices);
voice[i]->chan = chan;
voice[i]->note = note;
voice[i]->velocity = vel & 0x7f;
voice[i]->sound = sound[i];
}
/* set tone number (triggers header loading) */
for (i = 0; i < voices; i++) {
voice[i]->reg_f_number =
(sound[i]->tone >> 8) & OPL4_TONE_NUMBER_BIT8;
snd_opl4_write(opl4, OPL4_REG_F_NUMBER + voice[i]->number,
voice[i]->reg_f_number);
snd_opl4_write(opl4, OPL4_REG_TONE_NUMBER + voice[i]->number,
sound[i]->tone & 0xff);
}
/* set parameters which can be set while loading */
for (i = 0; i < voices; i++) {
voice[i]->reg_misc = OPL4_LFO_RESET_BIT;
snd_opl4_update_pan(opl4, voice[i]);
snd_opl4_update_pitch(opl4, voice[i]);
voice[i]->level_direct = OPL4_LEVEL_DIRECT_BIT;
snd_opl4_update_volume(opl4, voice[i]);
}
}
/* wait for completion of loading */
snd_opl4_wait_for_wave_headers(opl4);
/* set remaining parameters */
guard(spinlock_irqsave)(&opl4->reg_lock);
for (i = 0; i < voices; i++) {
snd_opl4_update_tone_parameters(opl4, voice[i]);
voice[i]->reg_lfo_vibrato = voice[i]->sound->reg_lfo_vibrato;
snd_opl4_update_vibrato_depth(opl4, voice[i]);
}
/* finally, switch on all voices */
for (i = 0; i < voices; i++) {
voice[i]->reg_misc =
(voice[i]->reg_misc & 0x1f) | OPL4_KEY_ON_BIT;
snd_opl4_write(opl4, OPL4_REG_MISC + voice[i]->number,
voice[i]->reg_misc);
}
}
static void snd_opl4_voice_off(struct snd_opl4 *opl4, struct opl4_voice *voice)
{
list_move_tail(&voice->list, &opl4->off_voices);
voice->reg_misc &= ~OPL4_KEY_ON_BIT;
snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
}
void snd_opl4_note_off(void *private_data, int note, int vel, struct snd_midi_channel *chan)
{
struct snd_opl4 *opl4 = private_data;
snd_opl4_do_for_note(opl4, note, chan, snd_opl4_voice_off);
}
static void snd_opl4_terminate_voice(struct snd_opl4 *opl4, struct opl4_voice *voice)
{
list_move_tail(&voice->list, &opl4->off_voices);
voice->reg_misc = (voice->reg_misc & ~OPL4_KEY_ON_BIT) | OPL4_DAMP_BIT;
snd_opl4_write(opl4, OPL4_REG_MISC + voice->number, voice->reg_misc);
}
void snd_opl4_terminate_note(void *private_data, int note, struct snd_midi_channel *chan)
{
struct snd_opl4 *opl4 = private_data;
snd_opl4_do_for_note(opl4, note, chan, snd_opl4_terminate_voice);
}
void snd_opl4_control(void *private_data, int type, struct snd_midi_channel *chan)
{
struct snd_opl4 *opl4 = private_data;
switch (type) {
case MIDI_CTL_MSB_MODWHEEL:
chan->control[MIDI_CTL_VIBRATO_DEPTH] = chan->control[MIDI_CTL_MSB_MODWHEEL];
snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth);
break;
case MIDI_CTL_MSB_MAIN_VOLUME:
snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume);
break;
case MIDI_CTL_MSB_PAN:
snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pan);
break;
case MIDI_CTL_MSB_EXPRESSION:
snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_volume);
break;
case MIDI_CTL_VIBRATO_RATE:
/* not yet supported */
break;
case MIDI_CTL_VIBRATO_DEPTH:
snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_vibrato_depth);
break;
case MIDI_CTL_VIBRATO_DELAY:
/* not yet supported */
break;
case MIDI_CTL_E1_REVERB_DEPTH:
/*
* Each OPL4 voice has a bit called "Pseudo-Reverb", but
* IMHO _not_ using it enhances the listening experience.
*/
break;
case MIDI_CTL_PITCHBEND:
snd_opl4_do_for_channel(opl4, chan, snd_opl4_update_pitch);
break;
}
}
void snd_opl4_sysex(void *private_data, unsigned char *buf, int len,
int parsed, struct snd_midi_channel_set *chset)
{
struct snd_opl4 *opl4 = private_data;
if (parsed == SNDRV_MIDI_SYSEX_GS_MASTER_VOLUME)
snd_opl4_do_for_all(opl4, snd_opl4_update_volume);
}
+961
View File
@@ -0,0 +1,961 @@
/*
* Information about the Yamaha YRW801 wavetable ROM chip
*
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "opl4_local.h"
int snd_yrw801_detect(struct snd_opl4 *opl4)
{
char buf[15];
snd_opl4_read_memory(opl4, buf, 0x001200, 15);
if (memcmp(buf, "CopyrightYAMAHA", 15))
return -ENODEV;
snd_opl4_read_memory(opl4, buf, 0x1ffffe, 2);
if (buf[0] != 0x01)
return -ENODEV;
dev_dbg(opl4->card->dev, "YRW801 ROM version %02x.%02x\n", buf[0], buf[1]);
return 0;
}
/*
* The instrument definitions are stored statically because, in practice, the
* OPL4 is always coupled with a YRW801. Dynamic instrument loading would be
* required if downloading sample data to external SRAM was actually supported
* by this driver.
*/
static const struct opl4_region regions_00[] = { /* Acoustic Grand Piano */
{0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}},
{0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xc8,0x20,0xf2,0x14,0x18,0x0}},
{0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xc8,0x20,0xf3,0x14,0x18,0x0}},
{0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}}
};
static const struct opl4_region regions_01[] = { /* Bright Acoustic Piano */
{0x14, 0x2d, {0x12c,7474,100, 0,0,0x00,0xc8,0x20,0xf2,0x13,0x08,0x0}},
{0x2e, 0x33, {0x12d,6816,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x34, 0x39, {0x12e,5899,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x3a, 0x3f, {0x12f,5290,100, 0,0,0x00,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x40, 0x45, {0x130,4260,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x46, 0x4b, {0x131,3625,100, 0,0,0x0a,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x4c, 0x52, {0x132,3116,100, 0,0,0x04,0xc8,0x20,0xf2,0x14,0x08,0x0}},
{0x53, 0x58, {0x133,2081,100, 0,0,0x07,0xc8,0x20,0xf2,0x14,0x18,0x0}},
{0x59, 0x5e, {0x134,1444,100, 0,0,0x0a,0xc8,0x20,0xf3,0x14,0x18,0x0}},
{0x5f, 0x6d, {0x135,1915,100, 0,0,0x00,0xc8,0x20,0xf4,0x15,0x08,0x0}}
};
static const struct opl4_region regions_02[] = { /* Electric Grand Piano */
{0x14, 0x2d, {0x12c,7476,100, 1,0,0x00,0xae,0x20,0xf2,0x13,0x07,0x0}},
{0x2e, 0x33, {0x12d,6818,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
{0x34, 0x39, {0x12e,5901,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
{0x3a, 0x3f, {0x12f,5292,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
{0x40, 0x45, {0x130,4262,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
{0x46, 0x4b, {0x131,3627,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
{0x4c, 0x52, {0x132,3118,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x07,0x0}},
{0x53, 0x58, {0x133,2083,100, 1,0,0x00,0xae,0x20,0xf2,0x14,0x17,0x0}},
{0x59, 0x5e, {0x134,1446,100, 1,0,0x00,0xae,0x20,0xf3,0x14,0x17,0x0}},
{0x5f, 0x6d, {0x135,1917,100, 1,0,0x00,0xae,0x20,0xf4,0x15,0x07,0x0}},
{0x00, 0x7f, {0x06c,6375,100,-1,0,0x00,0xc2,0x28,0xf4,0x23,0x18,0x0}}
};
static const struct opl4_region regions_03[] = { /* Honky-Tonk Piano */
{0x14, 0x27, {0x12c,7474,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}},
{0x28, 0x2d, {0x12d,6816,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x2e, 0x33, {0x12e,5899,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x34, 0x39, {0x12f,5290,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x3a, 0x3f, {0x130,4260,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x40, 0x45, {0x131,3625,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x46, 0x4b, {0x132,3116,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x4c, 0x52, {0x133,2081,100, 0,0,0x03,0xb4,0x20,0xf2,0x14,0x18,0x0}},
{0x53, 0x58, {0x134,1444,100, 0,0,0x07,0xb4,0x20,0xf3,0x14,0x18,0x0}},
{0x59, 0x6d, {0x135,1915,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}},
{0x14, 0x27, {0x12c,7486,100, 0,0,0x00,0xb4,0x20,0xf2,0x13,0x08,0x0}},
{0x28, 0x2d, {0x12d,6803,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x2e, 0x33, {0x12e,5912,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x34, 0x39, {0x12f,5275,100, 0,0,0x00,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x3a, 0x3f, {0x130,4274,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x40, 0x45, {0x131,3611,100, 0,0,0x0a,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x46, 0x4b, {0x132,3129,100, 0,0,0x04,0xb4,0x20,0xf2,0x14,0x08,0x0}},
{0x4c, 0x52, {0x133,2074,100, 0,0,0x07,0xb4,0x20,0xf2,0x14,0x18,0x0}},
{0x53, 0x58, {0x134,1457,100, 0,0,0x01,0xb4,0x20,0xf3,0x14,0x18,0x0}},
{0x59, 0x6d, {0x135,1903,100, 0,0,0x00,0xb4,0x20,0xf4,0x15,0x08,0x0}}
};
static const struct opl4_region regions_04[] = { /* Electric Piano 1 */
{0x15, 0x6c, {0x00b,6570,100, 0,0,0x00,0x28,0x38,0xf0,0x00,0x0c,0x0}},
{0x00, 0x7f, {0x06c,6375,100, 0,2,0x00,0xb0,0x22,0xf4,0x23,0x19,0x0}}
};
static const struct opl4_region regions_05[] = { /* Electric Piano 2 */
{0x14, 0x27, {0x12c,7476,100, 0,3,0x00,0xa2,0x1b,0xf2,0x13,0x08,0x0}},
{0x28, 0x2d, {0x12d,6818,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
{0x2e, 0x33, {0x12e,5901,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
{0x34, 0x39, {0x12f,5292,100, 0,3,0x00,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
{0x3a, 0x3f, {0x130,4262,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
{0x40, 0x45, {0x131,3627,100, 0,3,0x0a,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
{0x46, 0x4b, {0x132,3118,100, 0,3,0x04,0xa2,0x1b,0xf2,0x14,0x08,0x0}},
{0x4c, 0x52, {0x133,2083,100, 0,3,0x03,0xa2,0x1b,0xf2,0x14,0x18,0x0}},
{0x53, 0x58, {0x134,1446,100, 0,3,0x07,0xa2,0x1b,0xf3,0x14,0x18,0x0}},
{0x59, 0x6d, {0x135,1917,100, 0,3,0x00,0xa2,0x1b,0xf4,0x15,0x08,0x0}},
{0x14, 0x2d, {0x12c,7472,100, 0,0,0x00,0xa2,0x18,0xf2,0x13,0x08,0x0}},
{0x2e, 0x33, {0x12d,6814,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
{0x34, 0x39, {0x12e,5897,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
{0x3a, 0x3f, {0x12f,5288,100, 0,0,0x00,0xa2,0x18,0xf2,0x14,0x08,0x0}},
{0x40, 0x45, {0x130,4258,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}},
{0x46, 0x4b, {0x131,3623,100, 0,0,0x0a,0xa2,0x18,0xf2,0x14,0x08,0x0}},
{0x4c, 0x52, {0x132,3114,100, 0,0,0x04,0xa2,0x18,0xf2,0x14,0x08,0x0}},
{0x53, 0x58, {0x133,2079,100, 0,0,0x07,0xa2,0x18,0xf2,0x14,0x18,0x0}},
{0x59, 0x5e, {0x134,1442,100, 0,0,0x0a,0xa2,0x18,0xf3,0x14,0x18,0x0}},
{0x5f, 0x6d, {0x135,1913,100, 0,0,0x00,0xa2,0x18,0xf4,0x15,0x08,0x0}}
};
static const struct opl4_region regions_06[] = { /* Harpsichord */
{0x15, 0x39, {0x080,5158,100, 0,0,0x00,0xb2,0x20,0xf5,0x24,0x19,0x0}},
{0x3a, 0x3f, {0x081,4408,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}},
{0x40, 0x45, {0x082,3622,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x09,0x0}},
{0x46, 0x4d, {0x083,2843,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x19,0x0}},
{0x4e, 0x6c, {0x084,1307,100, 0,0,0x00,0xb2,0x20,0xf5,0x25,0x29,0x0}}
};
static const struct opl4_region regions_07[] = { /* Clavinet */
{0x15, 0x51, {0x027,5009,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x2b,0x0}},
{0x52, 0x6c, {0x028,3495,100, 0,0,0x00,0xd2,0x28,0xf5,0x13,0x3b,0x0}}
};
static const struct opl4_region regions_08[] = { /* Celesta */
{0x15, 0x6c, {0x02b,3267,100, 0,0,0x00,0xdc,0x20,0xf4,0x15,0x07,0x3}}
};
static const struct opl4_region regions_09[] = { /* Glockenspiel */
{0x15, 0x78, {0x0f3, 285,100, 0,0,0x00,0xc2,0x28,0xf6,0x25,0x25,0x0}}
};
static const struct opl4_region regions_0a[] = { /* Music Box */
{0x15, 0x6c, {0x0f3,3362,100, 0,0,0x00,0xb6,0x20,0xa6,0x25,0x25,0x0}},
{0x15, 0x6c, {0x101,4773,100, 0,0,0x00,0xaa,0x20,0xd4,0x14,0x16,0x0}}
};
static const struct opl4_region regions_0b[] = { /* Vibraphone */
{0x15, 0x6c, {0x101,4778,100, 0,0,0x00,0xc0,0x28,0xf4,0x14,0x16,0x4}}
};
static const struct opl4_region regions_0c[] = { /* Marimba */
{0x15, 0x3f, {0x0f4,4778,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}},
{0x40, 0x4c, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x47,0x08,0x0}},
{0x4d, 0x5a, {0x0f5,3217,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x08,0x0}},
{0x5b, 0x7f, {0x0f5,3218,100, 0,0,0x00,0xc4,0x38,0xf7,0x48,0x18,0x0}}
};
static const struct opl4_region regions_0d[] = { /* Xylophone */
{0x00, 0x7f, {0x136,1729,100, 0,0,0x00,0xd2,0x38,0xf0,0x06,0x36,0x0}}
};
static const struct opl4_region regions_0e[] = { /* Tubular Bell */
{0x01, 0x7f, {0x0ff,3999,100, 0,1,0x00,0x90,0x21,0xf4,0xa3,0x25,0x1}}
};
static const struct opl4_region regions_0f[] = { /* Dulcimer */
{0x00, 0x7f, {0x03f,4236,100, 0,1,0x00,0xbc,0x29,0xf5,0x16,0x07,0x0}},
{0x00, 0x7f, {0x040,4236,100, 0,2,0x0e,0x94,0x2a,0xf5,0x16,0x07,0x0}}
};
static const struct opl4_region regions_10[] = { /* Drawbar Organ */
{0x01, 0x7f, {0x08e,4394,100, 0,2,0x14,0xc2,0x3a,0xf0,0x00,0x0a,0x0}}
};
static const struct opl4_region regions_11[] = { /* Percussive Organ */
{0x15, 0x3b, {0x08c,6062,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}},
{0x3c, 0x6c, {0x08d,2984,100, 0,3,0x00,0xbe,0x3b,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_12[] = { /* Rock Organ */
{0x15, 0x30, {0x128,6574,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
{0x31, 0x3c, {0x129,5040,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
{0x3d, 0x48, {0x12a,3498,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
{0x49, 0x54, {0x12b,1957,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}},
{0x55, 0x6c, {0x127, 423,100, 0,1,0x00,0xcc,0x39,0xf0,0x00,0x0a,0x0}}
};
static const struct opl4_region regions_13[] = { /* Church Organ */
{0x15, 0x29, {0x087,7466,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
{0x2a, 0x30, {0x088,6456,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
{0x31, 0x38, {0x089,5428,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
{0x39, 0x41, {0x08a,4408,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}},
{0x42, 0x6c, {0x08b,3406,100, 0,1,0x00,0xc4,0x11,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_14[] = { /* Reed Organ */
{0x00, 0x53, {0x0ac,5570,100, 0,0,0x06,0xc0,0x38,0xf0,0x00,0x09,0x1}},
{0x54, 0x7f, {0x0ad,2497,100, 0,0,0x00,0xc0,0x38,0xf0,0x00,0x09,0x1}}
};
static const struct opl4_region regions_15[] = { /* Accordion */
{0x15, 0x4c, {0x006,4261,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}},
{0x4d, 0x6c, {0x007,1530,100, 0,2,0x00,0xa4,0x22,0x90,0x00,0x09,0x0}},
{0x15, 0x6c, {0x070,4391,100, 0,3,0x00,0x8a,0x23,0xa0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_16[] = { /* Harmonica */
{0x15, 0x6c, {0x070,4408,100, 0,0,0x00,0xae,0x30,0xa0,0x00,0x09,0x2}}
};
static const struct opl4_region regions_17[] = { /* Tango Accordion */
{0x00, 0x53, {0x0ac,5573,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}},
{0x54, 0x7f, {0x0ad,2500,100, 0,0,0x00,0xae,0x38,0xf0,0x00,0x09,0x0}},
{0x15, 0x6c, {0x041,8479,100, 0,2,0x00,0x6a,0x3a,0x75,0x20,0x0a,0x0}}
};
static const struct opl4_region regions_18[] = { /* Nylon Guitar */
{0x15, 0x2f, {0x0b3,6964,100, 0,0,0x05,0xca,0x28,0xf5,0x34,0x09,0x0}},
{0x30, 0x36, {0x0b7,5567,100, 0,0,0x0c,0xca,0x28,0xf5,0x34,0x09,0x0}},
{0x37, 0x3c, {0x0b5,4653,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
{0x3d, 0x43, {0x0b4,3892,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x09,0x0}},
{0x44, 0x60, {0x0b6,2723,100, 0,0,0x00,0xca,0x28,0xf6,0x35,0x19,0x0}}
};
static const struct opl4_region regions_19[] = { /* Steel Guitar */
{0x15, 0x31, {0x00c,6937,100, 0,0,0x00,0xbc,0x28,0xf0,0x04,0x19,0x0}},
{0x32, 0x38, {0x00d,5410,100, 0,0,0x00,0xbc,0x28,0xf0,0x05,0x09,0x0}},
{0x39, 0x47, {0x00e,4379,100, 0,0,0x00,0xbc,0x28,0xf5,0x94,0x09,0x0}},
{0x48, 0x6c, {0x00f,2843,100, 0,0,0x00,0xbc,0x28,0xf6,0x95,0x09,0x0}}
};
static const struct opl4_region regions_1a[] = { /* Jazz Guitar */
{0x15, 0x31, {0x05a,6832,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
{0x32, 0x3f, {0x05b,4897,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}},
{0x40, 0x6c, {0x05c,3218,100, 0,0,0x00,0xca,0x28,0xf6,0x34,0x09,0x0}}
};
static const struct opl4_region regions_1b[] = { /* Clean Guitar */
{0x15, 0x2c, {0x061,7053,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}},
{0x2d, 0x31, {0x060,6434,100, 0,1,0x00,0xb4,0x29,0xf5,0x54,0x0a,0x0}},
{0x32, 0x38, {0x063,5764,100, 0,1,0x00,0xbe,0x29,0xf5,0x55,0x0a,0x0}},
{0x39, 0x3f, {0x062,4627,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x0a,0x0}},
{0x40, 0x44, {0x065,3963,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}},
{0x45, 0x4b, {0x064,3313,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x1a,0x0}},
{0x4c, 0x54, {0x066,2462,100, 0,1,0x00,0xb4,0x29,0xf5,0x55,0x2a,0x0}},
{0x55, 0x6c, {0x067,1307,100, 0,1,0x00,0xb4,0x29,0xf6,0x56,0x0a,0x0}}
};
static const struct opl4_region regions_1c[] = { /* Muted Guitar */
{0x01, 0x7f, {0x068,4408,100, 0,0,0x00,0xcc,0x28,0xf6,0x15,0x09,0x0}}
};
static const struct opl4_region regions_1d[] = { /* Overdriven Guitar */
{0x00, 0x40, {0x0a5,6589,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}},
{0x41, 0x7f, {0x0a6,5428,100, 0,1,0x00,0xc0,0x29,0xf2,0x11,0x09,0x0}}
};
static const struct opl4_region regions_1e[] = { /* Distortion Guitar */
{0x15, 0x2a, {0x051,6928,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x2b, 0x2e, {0x052,6433,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x2f, 0x32, {0x053,5944,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x33, 0x36, {0x054,5391,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x37, 0x3a, {0x055,4897,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x3b, 0x3e, {0x056,4408,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x3f, 0x42, {0x057,3892,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x43, 0x46, {0x058,3361,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}},
{0x47, 0x6c, {0x059,2784,100, 0,1,0x00,0xbc,0x21,0xa2,0x12,0x0a,0x0}}
};
static const struct opl4_region regions_1f[] = { /* Guitar Harmonics */
{0x15, 0x44, {0x05e,5499,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}},
{0x45, 0x49, {0x05d,4850,100, 0,0,0x00,0xe2,0x28,0xf4,0x24,0x09,0x0}},
{0x4a, 0x6c, {0x05f,4259,100, 0,0,0x00,0xce,0x28,0xf4,0x24,0x09,0x0}}
};
static const struct opl4_region regions_20[] = { /* Acoustic Bass */
{0x15, 0x30, {0x004,8053,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}},
{0x31, 0x6c, {0x005,4754,100, 0,0,0x00,0xe2,0x18,0xf5,0x15,0x09,0x0}}
};
static const struct opl4_region regions_21[] = { /* Fingered Bass */
{0x01, 0x20, {0x04a,8762,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
{0x21, 0x25, {0x04b,8114,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
{0x26, 0x2a, {0x04c,7475,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}},
{0x2b, 0x7f, {0x04d,6841,100, 0,0,0x00,0xde,0x18,0xf6,0x14,0x09,0x0}}
};
static const struct opl4_region regions_22[] = { /* Picked Bass */
{0x15, 0x23, {0x04f,7954,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x0a,0x0}},
{0x24, 0x2a, {0x050,7318,100, 0,0,0x05,0xcc,0x18,0xf3,0x90,0x1a,0x0}},
{0x2b, 0x2f, {0x06b,6654,100, 0,0,0x00,0xcc,0x18,0xf3,0x90,0x2a,0x0}},
{0x30, 0x47, {0x069,6031,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}},
{0x48, 0x6c, {0x06a,5393,100, 0,0,0x00,0xcc,0x18,0xf5,0xb0,0x0a,0x0}}
};
static const struct opl4_region regions_23[] = { /* Fretless Bass */
{0x01, 0x7f, {0x04e,5297,100, 0,0,0x00,0xd2,0x10,0xf3,0x63,0x19,0x0}}
};
static const struct opl4_region regions_24[] = { /* Slap Bass 1 */
{0x15, 0x6c, {0x0a3,7606,100, 0,1,0x00,0xde,0x19,0xf5,0x32,0x1a,0x0}}
};
static const struct opl4_region regions_25[] = { /* Slap Bass 2 */
{0x01, 0x7f, {0x0a2,6694,100, 0,0,0x00,0xda,0x20,0xb0,0x02,0x09,0x0}}
};
static const struct opl4_region regions_26[] = { /* Synth Bass 1 */
{0x15, 0x6c, {0x0be,7466,100, 0,1,0x00,0xb8,0x39,0xf4,0x14,0x09,0x0}}
};
static const struct opl4_region regions_27[] = { /* Synth Bass 2 */
{0x00, 0x7f, {0x117,8103,100, 0,1,0x00,0xca,0x39,0xf3,0x50,0x08,0x0}}
};
static const struct opl4_region regions_28[] = { /* Violin */
{0x15, 0x3a, {0x105,5158,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x3b, 0x3f, {0x102,4754,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x40, 0x41, {0x106,4132,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x42, 0x44, {0x107,4033,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x45, 0x47, {0x108,3580,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x48, 0x4a, {0x10a,2957,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x4b, 0x4c, {0x10b,2724,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x4d, 0x4e, {0x10c,2530,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x4f, 0x51, {0x10d,2166,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}},
{0x52, 0x6c, {0x109,1825,100, 0,3,0x00,0xcc,0x3b,0xf3,0x20,0x09,0x0}}
};
static const struct opl4_region regions_29[] = { /* Viola */
{0x15, 0x32, {0x103,5780,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
{0x33, 0x35, {0x104,5534,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
{0x36, 0x38, {0x105,5158,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
{0x39, 0x3d, {0x102,4754,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
{0x3e, 0x3f, {0x106,4132,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
{0x40, 0x42, {0x107,4033,100, 0,3,0x00,0xc4,0x3b,0xa3,0x20,0x09,0x0}},
{0x43, 0x45, {0x108,3580,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
{0x46, 0x48, {0x10a,2957,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
{0x49, 0x4a, {0x10b,2724,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
{0x4b, 0x4c, {0x10c,2530,100, 0,3,0x00,0xca,0x3b,0xa3,0x20,0x09,0x0}},
{0x4d, 0x4f, {0x10d,2166,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}},
{0x50, 0x6c, {0x109,1825,100, 0,3,0x00,0xd0,0x3b,0xa3,0x20,0x09,0x0}}
};
static const struct opl4_region regions_2a[] = { /* Cello */
{0x15, 0x2d, {0x112,6545,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}},
{0x2e, 0x37, {0x113,5764,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x08,0x0}},
{0x38, 0x3e, {0x115,4378,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}},
{0x3f, 0x44, {0x116,3998,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}},
{0x45, 0x6c, {0x114,3218,100, 0,3,0x00,0xc0,0x33,0xa0,0x00,0x18,0x0}}
};
static const struct opl4_region regions_2b[] = { /* Contrabass */
{0x15, 0x29, {0x110,7713,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}},
{0x2a, 0x6c, {0x111,6162,100, 0,1,0x00,0xc2,0x19,0x90,0x00,0x09,0x0}}
};
static const struct opl4_region regions_2c[] = { /* Tremolo Strings */
{0x15, 0x3b, {0x0b0,4810,100, 0,0,0x0a,0xde,0x38,0xf0,0x00,0x07,0x6}},
{0x3c, 0x41, {0x035,4035,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
{0x42, 0x47, {0x033,3129,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
{0x48, 0x52, {0x034,2625,100, 0,0,0x05,0xde,0x38,0xf0,0x00,0x07,0x6}},
{0x53, 0x6c, {0x0af, 936,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x07,0x6}}
};
static const struct opl4_region regions_2d[] = { /* Pizzicato Strings */
{0x15, 0x32, {0x0b8,6186,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
{0x33, 0x3b, {0x0b9,5031,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
{0x3c, 0x42, {0x0bb,4146,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}},
{0x43, 0x48, {0x0ba,3245,100, 0,0,0x00,0xc2,0x28,0xf0,0x00,0x05,0x0}},
{0x49, 0x6c, {0x0bc,2352,100, 0,0,0x00,0xbc,0x28,0xf0,0x00,0x05,0x0}}
};
static const struct opl4_region regions_2e[] = { /* Harp */
{0x15, 0x46, {0x07e,3740,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}},
{0x47, 0x6c, {0x07f,2319,100, 0,1,0x00,0xd2,0x29,0xf5,0x25,0x07,0x0}}
};
static const struct opl4_region regions_2f[] = { /* Timpani */
{0x15, 0x6c, {0x100,6570,100, 0,0,0x00,0xf8,0x28,0xf0,0x05,0x16,0x0}}
};
static const struct opl4_region regions_30[] = { /* Strings */
{0x15, 0x3b, {0x13c,4806,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
{0x3c, 0x41, {0x13e,4035,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
{0x42, 0x47, {0x13d,3122,100, 0,0,0x00,0xc8,0x20,0x80,0x00,0x07,0x0}},
{0x48, 0x52, {0x13f,2629,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}},
{0x53, 0x6c, {0x140, 950,100, 0,0,0x00,0xbe,0x20,0x80,0x00,0x07,0x0}}
};
static const struct opl4_region regions_31[] = { /* Slow Strings */
{0x15, 0x3b, {0x0b0,4810,100, 0,1,0x0a,0xbe,0x19,0xf0,0x00,0x07,0x0}},
{0x3c, 0x41, {0x035,4035,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
{0x42, 0x47, {0x033,3129,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
{0x48, 0x52, {0x034,2625,100, 0,1,0x05,0xbe,0x19,0xf0,0x00,0x07,0x0}},
{0x53, 0x6c, {0x0af, 936,100, 0,1,0x00,0xbe,0x19,0xf0,0x00,0x07,0x0}}
};
static const struct opl4_region regions_32[] = { /* Synth Strings 1 */
{0x05, 0x71, {0x002,6045,100,-2,0,0x00,0xa6,0x20,0x93,0x22,0x06,0x0}},
{0x15, 0x6c, {0x0ae,3261,100, 2,0,0x00,0xc6,0x20,0x70,0x01,0x06,0x0}}
};
static const struct opl4_region regions_33[] = { /* Synth Strings 2 */
{0x15, 0x6c, {0x002,4513,100, 5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}},
{0x15, 0x6c, {0x002,4501,100,-5,1,0x00,0xb4,0x19,0x70,0x00,0x06,0x0}}
};
static const struct opl4_region regions_34[] = { /* Choir Aahs */
{0x15, 0x3a, {0x018,5010,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
{0x3b, 0x40, {0x019,4370,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
{0x41, 0x47, {0x01a,3478,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}},
{0x48, 0x6c, {0x01b,2197,100, 0,2,0x00,0xc2,0x1a,0x70,0x00,0x08,0x0}}
};
static const struct opl4_region regions_35[] = { /* Voice Oohs */
{0x15, 0x6c, {0x029,3596,100, 0,0,0x00,0xe6,0x20,0xf7,0x20,0x08,0x0}}
};
static const struct opl4_region regions_36[] = { /* Synth Voice */
{0x15, 0x6c, {0x02a,3482,100, 0,1,0x00,0xc2,0x19,0x85,0x21,0x07,0x0}}
};
static const struct opl4_region regions_37[] = { /* Orchestra Hit */
{0x15, 0x6c, {0x049,4394,100, 0,0,0x00,0xfe,0x30,0x80,0x05,0x05,0x0}}
};
static const struct opl4_region regions_38[] = { /* Trumpet */
{0x15, 0x3c, {0x0f6,4706,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
{0x3d, 0x43, {0x0f8,3894,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
{0x44, 0x48, {0x0f7,3118,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
{0x49, 0x4e, {0x0fa,2322,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
{0x4f, 0x55, {0x0f9,1634,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}},
{0x56, 0x6c, {0x0fb, 786,100, 0,2,0x00,0xd6,0x32,0xf3,0x20,0x0a,0x0}}
};
static const struct opl4_region regions_39[] = { /* Trombone */
{0x15, 0x3a, {0x0f0,5053,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}},
{0x3b, 0x3f, {0x0f1,4290,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}},
{0x40, 0x6c, {0x0f2,3580,100, 0,1,0x00,0xd6,0x21,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_3a[] = { /* Tuba */
{0x15, 0x2d, {0x085,7096,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}},
{0x2e, 0x6c, {0x086,6014,100, 0,1,0x00,0xde,0x21,0xf5,0x10,0x09,0x0}}
};
static const struct opl4_region regions_3b[] = { /* Muted Trumpet */
{0x15, 0x45, {0x0b1,4135,100, 0,0,0x00,0xcc,0x28,0xf3,0x10,0x0a,0x1}},
{0x46, 0x6c, {0x0b2,2599,100, 0,0,0x00,0xcc,0x28,0x83,0x10,0x0a,0x1}}
};
static const struct opl4_region regions_3c[] = { /* French Horns */
{0x15, 0x49, {0x07c,3624,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}},
{0x4a, 0x6c, {0x07d,2664,100, 0,2,0x00,0xd0,0x1a,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_3d[] = { /* Brass Section */
{0x15, 0x42, {0x0fc,4375,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}},
{0x43, 0x6c, {0x0fd,2854,100, 0,0,0x00,0xd6,0x28,0xf0,0x00,0x0a,0x0}}
};
static const struct opl4_region regions_3e[] = { /* Synth Brass 1 */
{0x01, 0x27, {0x0d3,9094,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x28, 0x2d, {0x0da,8335,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x2e, 0x33, {0x0d4,7558,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x34, 0x39, {0x0db,6785,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x3a, 0x3f, {0x0d5,6042,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x40, 0x45, {0x0dc,5257,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x46, 0x4b, {0x0d6,4493,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x4c, 0x51, {0x0dd,3741,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x52, 0x57, {0x0d7,3012,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x58, 0x5d, {0x0de,2167,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x5e, 0x63, {0x0d8,1421,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x64, 0x7f, {0x0d9,-115,100,-1,0,0x00,0xbe,0x18,0xa5,0x11,0x08,0x0}},
{0x01, 0x27, {0x118,9103,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x28, 0x2d, {0x119,8340,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x2e, 0x33, {0x11a,7565,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x34, 0x39, {0x11b,6804,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x3a, 0x3f, {0x11c,6042,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x40, 0x45, {0x11d,5277,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x46, 0x4b, {0x11e,4520,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x4c, 0x51, {0x11f,3741,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x52, 0x57, {0x120,3012,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x58, 0x5d, {0x121,2166,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x5e, 0x64, {0x122,1421,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}},
{0x65, 0x7f, {0x123,-115,100, 1,1,0x00,0xbe,0x19,0x85,0x23,0x08,0x0}}
};
static const struct opl4_region regions_3f[] = { /* Synth Brass 2 */
{0x01, 0x27, {0x118,9113,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x28, 0x2d, {0x119,8350,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x2e, 0x33, {0x11a,7575,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x34, 0x39, {0x11b,6814,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x3a, 0x3f, {0x11c,6052,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x40, 0x45, {0x11d,5287,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x46, 0x4b, {0x11e,4530,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x4c, 0x51, {0x11f,3751,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x52, 0x57, {0x120,3022,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x58, 0x5d, {0x121,2176,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x5e, 0x64, {0x122,1431,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x65, 0x7f, {0x123,-105,100, 3,6,0x00,0xae,0x26,0x85,0x23,0x08,0x0}},
{0x00, 0x7f, {0x124,4034,100,-3,2,0x00,0xea,0x22,0x85,0x23,0x08,0x0}}
};
static const struct opl4_region regions_40[] = { /* Soprano Sax */
{0x15, 0x3f, {0x0e3,4228,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}},
{0x40, 0x45, {0x0e4,3495,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}},
{0x46, 0x4b, {0x0e5,2660,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
{0x4c, 0x51, {0x0e6,2002,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
{0x52, 0x59, {0x0e7,1186,100, 0,1,0x00,0xd6,0x21,0xf5,0x20,0x0a,0x0}},
{0x59, 0x6c, {0x0e8,1730,100, 0,1,0x00,0xc8,0x21,0xf5,0x20,0x0a,0x0}}
};
static const struct opl4_region regions_41[] = { /* Alto Sax */
{0x15, 0x32, {0x092,6204,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x33, 0x35, {0x096,5812,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x36, 0x3a, {0x099,5318,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x3b, 0x3b, {0x08f,5076,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x3c, 0x3e, {0x093,4706,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x3f, 0x41, {0x097,4321,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x42, 0x44, {0x09a,3893,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x45, 0x47, {0x090,3497,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x48, 0x4a, {0x094,3119,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x4b, 0x4d, {0x098,2726,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x4e, 0x50, {0x09b,2393,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x51, 0x53, {0x091,2088,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}},
{0x54, 0x6c, {0x095,1732,100, 0,1,0x00,0xbe,0x19,0xf5,0x20,0x0b,0x0}}
};
static const struct opl4_region regions_42[] = { /* Tenor Sax */
{0x24, 0x30, {0x0e9,6301,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
{0x31, 0x34, {0x0ea,5781,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
{0x35, 0x3a, {0x0eb,5053,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
{0x3b, 0x41, {0x0ed,4165,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
{0x42, 0x47, {0x0ec,3218,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
{0x48, 0x51, {0x0ee,2462,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}},
{0x52, 0x6c, {0x0ef,1421,100, 0,1,0x00,0xbc,0x19,0xf4,0x10,0x0b,0x0}}
};
static const struct opl4_region regions_43[] = { /* Baritone Sax */
{0x15, 0x2d, {0x0df,6714,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
{0x2e, 0x34, {0x0e1,5552,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
{0x35, 0x39, {0x0e2,5178,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}},
{0x3a, 0x6c, {0x0e0,4437,100, 0,1,0x00,0xce,0x19,0xf0,0x00,0x0a,0x0}}
};
static const struct opl4_region regions_44[] = { /* Oboe */
{0x15, 0x3c, {0x042,4493,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}},
{0x3d, 0x43, {0x044,3702,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
{0x44, 0x49, {0x043,2956,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
{0x4a, 0x4f, {0x046,2166,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
{0x50, 0x55, {0x045,1420,100, 0,1,0x00,0xdc,0x39,0xf4,0x10,0x0a,0x0}},
{0x56, 0x6c, {0x047, 630,100, 0,1,0x00,0xe6,0x39,0xf4,0x10,0x0a,0x0}}
};
static const struct opl4_region regions_45[] = { /* English Horn */
{0x15, 0x38, {0x03c,5098,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}},
{0x39, 0x3e, {0x03b,4291,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}},
{0x3f, 0x6c, {0x03d,3540,100, 0,1,0x00,0xc4,0x31,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_46[] = { /* Bassoon */
{0x15, 0x22, {0x038,7833,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}},
{0x23, 0x2e, {0x03a,7070,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}},
{0x2f, 0x6c, {0x039,6302,100, 0,1,0x00,0xc6,0x31,0xf0,0x00,0x0b,0x0}}
};
static const struct opl4_region regions_47[] = { /* Clarinet */
{0x15, 0x3b, {0x09e,5900,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
{0x3c, 0x41, {0x0a0,5158,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
{0x42, 0x4a, {0x09f,4260,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}},
{0x4b, 0x6c, {0x0a1,2957,100, 0,1,0x00,0xc8,0x29,0xf3,0x20,0x0a,0x0}}
};
static const struct opl4_region regions_48[] = { /* Piccolo */
{0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
{0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
{0x4e, 0x53, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
{0x54, 0x5f, {0x074,2085,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}},
{0x60, 0x6c, {0x075,1421,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}
};
static const struct opl4_region regions_49[] = { /* Flute */
{0x15, 0x40, {0x071,4803,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}},
{0x41, 0x4d, {0x072,3314,100, 0,0,0x00,0xdc,0x38,0xf0,0x00,0x0a,0x2}},
{0x4e, 0x6c, {0x073,1731,100, 0,0,0x00,0xe6,0x38,0xf0,0x00,0x0a,0x2}}
};
static const struct opl4_region regions_4a[] = { /* Recorder */
{0x15, 0x6f, {0x0bd,4897,100, 0,0,0x00,0xec,0x30,0x70,0x00,0x09,0x1}}
};
static const struct opl4_region regions_4b[] = { /* Pan Flute */
{0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xde,0x38,0xf0,0x00,0x09,0x3}}
};
static const struct opl4_region regions_4c[] = { /* Bottle Blow */
{0x15, 0x6c, {0x077,2359,100, 0,0,0x00,0xc8,0x38,0xf0,0x00,0x09,0x1}},
{0x01, 0x7f, {0x125,7372,100, 0,0,0x1e,0x80,0x00,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_4d[] = { /* Shakuhachi */
{0x00, 0x7f, {0x0ab,4548,100, 0,0,0x00,0xd6,0x30,0xf0,0x00,0x0a,0x3}},
{0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xa2,0x28,0x70,0x00,0x09,0x2}}
};
static const struct opl4_region regions_4e[] = { /* Whistle */
{0x00, 0x7f, {0x0aa,1731,100, 0,4,0x00,0xd2,0x2c,0x70,0x00,0x0a,0x0}}
};
static const struct opl4_region regions_4f[] = { /* Ocarina */
{0x00, 0x7f, {0x0aa,1731,100, 0,1,0x00,0xce,0x29,0x90,0x00,0x0a,0x1}}
};
static const struct opl4_region regions_50[] = { /* Square Lead */
{0x01, 0x2a, {0x0cc,9853,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
{0x2b, 0x36, {0x0cd,6785,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
{0x37, 0x42, {0x0ca,5248,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
{0x43, 0x4e, {0x0cf,3713,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
{0x4f, 0x5a, {0x0ce,2176,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
{0x5b, 0x7f, {0x0cb, 640,100, 3,0,0x00,0xac,0x38,0xc6,0x21,0x09,0x0}},
{0x01, 0x2a, {0x0cc,9844,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
{0x2b, 0x36, {0x0cd,6776,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
{0x37, 0x42, {0x0ca,5239,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
{0x43, 0x4e, {0x0cf,3704,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
{0x4f, 0x5a, {0x0ce,2167,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}},
{0x5b, 0x7f, {0x0cb, 631,100,-3,0,0x00,0xac,0x08,0xc6,0x21,0x09,0x0}}
};
static const struct opl4_region regions_51[] = { /* Sawtooth Lead */
{0x01, 0x27, {0x118,9108,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x28, 0x2d, {0x119,8345,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x2e, 0x33, {0x11a,7570,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x34, 0x39, {0x11b,6809,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x3a, 0x3f, {0x11c,6047,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x40, 0x45, {0x11d,5282,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x46, 0x4b, {0x11e,4525,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x4c, 0x51, {0x11f,3746,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x52, 0x57, {0x120,3017,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x58, 0x5d, {0x121,2171,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x5e, 0x66, {0x122,1426,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x67, 0x7f, {0x123,-110,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x01, 0x27, {0x118,9098,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x28, 0x2d, {0x119,8335,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x2e, 0x33, {0x11a,7560,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x34, 0x39, {0x11b,6799,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x3a, 0x3f, {0x11c,6037,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x40, 0x45, {0x11d,5272,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x46, 0x4b, {0x11e,4515,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x4c, 0x51, {0x11f,3736,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x52, 0x57, {0x120,3007,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x58, 0x5d, {0x121,2161,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x5e, 0x66, {0x122,1416,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}},
{0x67, 0x7f, {0x123,-120,100, 0,0,0x00,0xc8,0x30,0xf2,0x22,0x0a,0x0}}
};
static const struct opl4_region regions_52[] = { /* Calliope Lead */
{0x00, 0x7f, {0x0aa,1731,100, 0,0,0x00,0xc2,0x28,0x90,0x00,0x0a,0x2}},
{0x15, 0x6c, {0x076,3716,100, 0,0,0x00,0xb6,0x28,0xb0,0x00,0x09,0x2}}
};
static const struct opl4_region regions_53[] = { /* Chiffer Lead */
{0x00, 0x7f, {0x13a,3665,100, 0,2,0x00,0xcc,0x2a,0xf0,0x10,0x09,0x1}},
{0x01, 0x7f, {0x0fe,3660,100, 0,0,0x00,0xbe,0x28,0xf3,0x10,0x17,0x0}}
};
static const struct opl4_region regions_54[] = { /* Charang Lead */
{0x00, 0x40, {0x0a5,6594,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}},
{0x41, 0x7f, {0x0a6,5433,100, 0,3,0x00,0xba,0x33,0xf2,0x11,0x09,0x0}},
{0x01, 0x27, {0x118,9098,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x28, 0x2d, {0x119,8335,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x2e, 0x33, {0x11a,7560,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x34, 0x39, {0x11b,6799,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x3a, 0x3f, {0x11c,6037,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x40, 0x45, {0x11d,5272,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x46, 0x4b, {0x11e,4515,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x4c, 0x51, {0x11f,3736,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x52, 0x57, {0x120,3007,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x58, 0x5d, {0x121,2161,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x5e, 0x66, {0x122,1416,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}},
{0x67, 0x7f, {0x123,-120,100, 0,2,0x00,0xa4,0x2a,0xf2,0x22,0x0e,0x0}}
};
static const struct opl4_region regions_55[] = { /* Voice Lead */
{0x00, 0x7f, {0x0aa,1739,100, 0,6,0x00,0x8c,0x2e,0x90,0x00,0x0a,0x0}},
{0x15, 0x6c, {0x02a,3474,100, 0,1,0x00,0xd8,0x29,0xf0,0x05,0x0a,0x0}}
};
static const struct opl4_region regions_56[] = { /* 5ths Lead */
{0x01, 0x27, {0x118,8468,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x28, 0x2d, {0x119,7705,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x2e, 0x33, {0x11a,6930,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x34, 0x39, {0x11b,6169,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x3a, 0x3f, {0x11c,5407,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x40, 0x45, {0x11d,4642,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x46, 0x4b, {0x11e,3885,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x4c, 0x51, {0x11f,3106,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x52, 0x57, {0x120,2377,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x58, 0x5d, {0x121,1531,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x5e, 0x64, {0x122, 786,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x65, 0x7f, {0x123,-750,100, 0,2,0x00,0xd0,0x32,0xf5,0x20,0x08,0x0}},
{0x05, 0x71, {0x002,4503,100, 0,1,0x00,0xb8,0x31,0xb3,0x20,0x0b,0x0}}
};
static const struct opl4_region regions_57[] = { /* Bass & Lead */
{0x00, 0x7f, {0x117,8109,100, 0,1,0x00,0xbc,0x29,0xf3,0x50,0x08,0x0}},
{0x01, 0x27, {0x118,9097,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x28, 0x2d, {0x119,8334,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x2e, 0x33, {0x11a,7559,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x34, 0x39, {0x11b,6798,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x3a, 0x3f, {0x11c,6036,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x40, 0x45, {0x11d,5271,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x46, 0x4b, {0x11e,4514,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x4c, 0x51, {0x11f,3735,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x52, 0x57, {0x120,3006,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x58, 0x5d, {0x121,2160,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x5e, 0x66, {0x122,1415,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}},
{0x67, 0x7f, {0x123,-121,100, 0,2,0x00,0xbc,0x2a,0xf2,0x20,0x0a,0x0}}
};
static const struct opl4_region regions_58[] = { /* New Age Pad */
{0x15, 0x6c, {0x002,4501,100, 0,4,0x00,0xa4,0x24,0x80,0x01,0x05,0x0}},
{0x15, 0x6c, {0x0f3,4253,100, 0,3,0x00,0x8c,0x23,0xa2,0x14,0x06,0x1}}
};
static const struct opl4_region regions_59[] = { /* Warm Pad */
{0x15, 0x6c, {0x04e,5306,100, 2,2,0x00,0x92,0x2a,0x34,0x23,0x05,0x2}},
{0x15, 0x6c, {0x029,3575,100,-2,2,0x00,0xbe,0x22,0x31,0x23,0x06,0x0}}
};
static const struct opl4_region regions_5a[] = { /* Polysynth Pad */
{0x01, 0x27, {0x118,9111,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x28, 0x2d, {0x119,8348,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x2e, 0x33, {0x11a,7573,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x34, 0x39, {0x11b,6812,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x3a, 0x3f, {0x11c,6050,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x40, 0x45, {0x11d,5285,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x46, 0x4b, {0x11e,4528,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x4c, 0x51, {0x11f,3749,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x52, 0x57, {0x120,3020,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x58, 0x5d, {0x121,2174,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x5e, 0x66, {0x122,1429,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x67, 0x7f, {0x123,-107,100, 0,3,0x00,0xae,0x23,0xf2,0x20,0x07,0x1}},
{0x00, 0x7f, {0x124,4024,100, 0,2,0x00,0xae,0x22,0xe5,0x20,0x08,0x0}}
};
static const struct opl4_region regions_5b[] = { /* Choir Pad */
{0x15, 0x3a, {0x018,5010,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
{0x3b, 0x40, {0x019,4370,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
{0x41, 0x47, {0x01a,3478,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
{0x48, 0x6c, {0x01b,2197,100, 0,5,0x00,0xb0,0x25,0x70,0x00,0x06,0x0}},
{0x15, 0x6c, {0x02a,3482,100, 0,4,0x00,0x98,0x24,0x65,0x21,0x06,0x0}}
};
static const struct opl4_region regions_5c[] = { /* Bowed Pad */
{0x15, 0x6c, {0x101,4790,100,-1,1,0x00,0xbe,0x19,0x44,0x14,0x16,0x0}},
{0x00, 0x7f, {0x0aa,1720,100, 1,1,0x00,0x94,0x19,0x40,0x00,0x06,0x0}}
};
static const struct opl4_region regions_5d[] = { /* Metallic Pad */
{0x15, 0x31, {0x00c,6943,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
{0x32, 0x38, {0x00d,5416,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
{0x39, 0x47, {0x00e,4385,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
{0x48, 0x6c, {0x00f,2849,100, 0,2,0x00,0xa0,0x0a,0x60,0x03,0x06,0x0}},
{0x00, 0x7f, {0x03f,4224,100, 0,1,0x00,0x9c,0x31,0x65,0x16,0x07,0x0}}
};
static const struct opl4_region regions_5e[] = { /* Halo Pad */
{0x00, 0x7f, {0x124,4038,100, 0,2,0x00,0xa6,0x1a,0x85,0x23,0x08,0x0}},
{0x15, 0x6c, {0x02a,3471,100, 0,3,0x00,0xc0,0x1b,0xc0,0x05,0x06,0x0}}
};
static const struct opl4_region regions_5f[] = { /* Sweep Pad */
{0x01, 0x27, {0x0d3,9100,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x28, 0x2d, {0x0da,8341,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x2e, 0x33, {0x0d4,7564,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x34, 0x39, {0x0db,6791,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x3a, 0x3f, {0x0d5,6048,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x40, 0x45, {0x0dc,5263,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x46, 0x4b, {0x0d6,4499,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x4c, 0x51, {0x0dd,3747,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x52, 0x57, {0x0d7,3018,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x58, 0x5d, {0x0de,2173,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x5e, 0x63, {0x0d8,1427,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x64, 0x7f, {0x0d9,-109,100, 0,1,0x00,0xce,0x19,0x13,0x11,0x06,0x0}},
{0x01, 0x27, {0x0d3,9088,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x28, 0x2d, {0x0da,8329,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x2e, 0x33, {0x0d4,7552,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x34, 0x39, {0x0db,6779,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x3a, 0x3f, {0x0d5,6036,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x40, 0x45, {0x0dc,5251,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x46, 0x4b, {0x0d6,4487,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x4c, 0x51, {0x0dd,3735,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x52, 0x57, {0x0d7,3006,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x58, 0x5d, {0x0de,2161,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x5e, 0x63, {0x0d8,1415,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}},
{0x64, 0x7f, {0x0d9,-121,100, 0,0,0x00,0xce,0x18,0x13,0x11,0x06,0x0}}
};
static const struct opl4_region regions_60[] = { /* Ice Rain */
{0x01, 0x7f, {0x04e,9345,100, 0,2,0x00,0xcc,0x22,0xa3,0x63,0x17,0x0}},
{0x00, 0x7f, {0x143,5586, 20, 0,2,0x00,0x6e,0x2a,0xf0,0x05,0x05,0x0}}
};
static const struct opl4_region regions_61[] = { /* Soundtrack */
{0x15, 0x6c, {0x002,4501,100, 0,2,0x00,0xb6,0x2a,0x60,0x01,0x05,0x0}},
{0x15, 0x6c, {0x0f3,1160,100, 0,5,0x00,0xa8,0x2d,0x52,0x14,0x06,0x2}}
};
static const struct opl4_region regions_62[] = { /* Crystal */
{0x15, 0x6c, {0x0f3,1826,100, 0,3,0x00,0xb8,0x33,0xf6,0x25,0x25,0x0}},
{0x15, 0x2c, {0x06d,7454,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}},
{0x2d, 0x36, {0x06e,5925,100, 0,3,0x00,0xac,0x3b,0x85,0x24,0x06,0x0}},
{0x37, 0x6c, {0x06f,4403,100, 0,3,0x09,0xac,0x3b,0x85,0x24,0x06,0x0}}
};
static const struct opl4_region regions_63[] = { /* Atmosphere */
{0x05, 0x71, {0x002,4509,100, 0,2,0x00,0xc8,0x32,0x73,0x22,0x06,0x1}},
{0x15, 0x2f, {0x0b3,6964,100, 0,2,0x05,0xc2,0x32,0xf5,0x34,0x07,0x2}},
{0x30, 0x36, {0x0b7,5567,100, 0,2,0x0c,0xc2,0x32,0xf5,0x34,0x07,0x2}},
{0x37, 0x3c, {0x0b5,4653,100, 0,2,0x00,0xc2,0x32,0xf6,0x34,0x07,0x2}},
{0x3d, 0x43, {0x0b4,3892,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x07,0x2}},
{0x44, 0x60, {0x0b6,2723,100, 0,2,0x00,0xc2,0x32,0xf6,0x35,0x17,0x2}}
};
static const struct opl4_region regions_64[] = { /* Brightness */
{0x00, 0x7f, {0x137,5285,100, 0,2,0x00,0xbe,0x2a,0xa5,0x18,0x08,0x0}},
{0x15, 0x6c, {0x02a,3481,100, 0,1,0x00,0xc8,0x29,0x80,0x05,0x05,0x0}}
};
static const struct opl4_region regions_65[] = { /* Goblins */
{0x15, 0x6c, {0x002,4501,100,-1,2,0x00,0xca,0x2a,0x40,0x01,0x05,0x0}},
{0x15, 0x6c, {0x009,9679, 20, 1,4,0x00,0x3c,0x0c,0x22,0x11,0x06,0x0}}
};
static const struct opl4_region regions_66[] = { /* Echoes */
{0x15, 0x6c, {0x02a,3487,100, 0,3,0x00,0xae,0x2b,0xf5,0x21,0x06,0x0}},
{0x00, 0x7f, {0x124,4027,100, 0,3,0x00,0xae,0x2b,0x85,0x23,0x07,0x0}}
};
static const struct opl4_region regions_67[] = { /* Sci-Fi */
{0x15, 0x31, {0x00c,6940,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
{0x32, 0x38, {0x00d,5413,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
{0x39, 0x47, {0x00e,4382,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
{0x48, 0x6c, {0x00f,2846,100, 0,3,0x00,0xc8,0x2b,0x90,0x05,0x06,0x3}},
{0x15, 0x6c, {0x002,4498,100, 0,2,0x00,0xd4,0x22,0x80,0x01,0x05,0x0}}
};
static const struct opl4_region regions_68[] = { /* Sitar */
{0x00, 0x7f, {0x10f,4408,100, 0,2,0x00,0xc4,0x32,0xf4,0x15,0x16,0x1}}
};
static const struct opl4_region regions_69[] = { /* Banjo */
{0x15, 0x34, {0x013,5685,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
{0x35, 0x38, {0x014,5009,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
{0x39, 0x3c, {0x012,4520,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
{0x3d, 0x44, {0x015,3622,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
{0x45, 0x4c, {0x017,2661,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}},
{0x4d, 0x6d, {0x016,1632,100, 0,0,0x00,0xdc,0x38,0xf6,0x15,0x09,0x0}}
};
static const struct opl4_region regions_6a[] = { /* Shamisen */
{0x15, 0x6c, {0x10e,3273,100, 0,0,0x00,0xc0,0x28,0xf7,0x76,0x08,0x0}}
};
static const struct opl4_region regions_6b[] = { /* Koto */
{0x00, 0x7f, {0x0a9,4033,100, 0,0,0x00,0xc6,0x20,0xf0,0x06,0x07,0x0}}
};
static const struct opl4_region regions_6c[] = { /* Kalimba */
{0x00, 0x7f, {0x137,3749,100, 0,0,0x00,0xce,0x38,0xf5,0x18,0x08,0x0}}
};
static const struct opl4_region regions_6d[] = { /* Bagpipe */
{0x15, 0x39, {0x0a4,7683,100, 0,4,0x00,0xc0,0x1c,0xf0,0x00,0x09,0x0}},
{0x15, 0x39, {0x0a7,7680,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}},
{0x3a, 0x6c, {0x0a8,3697,100, 0,1,0x00,0xaa,0x19,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_6e[] = { /* Fiddle */
{0x15, 0x3a, {0x105,5158,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x3b, 0x3f, {0x102,4754,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x40, 0x41, {0x106,4132,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x42, 0x44, {0x107,4033,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x45, 0x47, {0x108,3580,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x48, 0x4a, {0x10a,2957,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x4b, 0x4c, {0x10b,2724,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x4d, 0x4e, {0x10c,2530,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x4f, 0x51, {0x10d,2166,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}},
{0x52, 0x6c, {0x109,1825,100, 0,1,0x00,0xca,0x31,0xf3,0x20,0x09,0x0}}
};
static const struct opl4_region regions_6f[] = { /* Shanai */
{0x15, 0x6c, {0x041,6946,100, 0,1,0x00,0xc4,0x31,0x95,0x20,0x09,0x0}}
};
static const struct opl4_region regions_70[] = { /* Tinkle Bell */
{0x15, 0x73, {0x0f3,1821,100, 0,3,0x00,0xc8,0x3b,0xd6,0x25,0x25,0x0}},
{0x00, 0x7f, {0x137,5669,100, 0,3,0x00,0x66,0x3b,0xf5,0x18,0x08,0x0}}
};
static const struct opl4_region regions_71[] = { /* Agogo */
{0x15, 0x74, {0x00b,2474,100, 0,0,0x00,0xd2,0x38,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_72[] = { /* Steel Drums */
{0x01, 0x7f, {0x0fe,3670,100, 0,0,0x00,0xca,0x38,0xf3,0x06,0x17,0x1}},
{0x15, 0x6c, {0x100,9602,100, 0,0,0x00,0x54,0x38,0xb0,0x05,0x16,0x1}}
};
static const struct opl4_region regions_73[] = { /* Woodblock */
{0x15, 0x6c, {0x02c,2963, 50, 0,0,0x07,0xd4,0x00,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_74[] = { /* Taiko Drum */
{0x13, 0x6c, {0x03e,1194, 50, 0,0,0x00,0xaa,0x38,0xf0,0x04,0x04,0x0}}
};
static const struct opl4_region regions_75[] = { /* Melodic Tom */
{0x15, 0x6c, {0x0c7,6418, 50, 0,0,0x00,0xe4,0x38,0xf0,0x05,0x01,0x0}}
};
static const struct opl4_region regions_76[] = { /* Synth Drum */
{0x15, 0x6c, {0x026,3898, 50, 0,0,0x00,0xd0,0x38,0xf0,0x04,0x04,0x0}}
};
static const struct opl4_region regions_77[] = { /* Reverse Cymbal */
{0x15, 0x6c, {0x031,4138, 50, 0,0,0x00,0xfe,0x38,0x3a,0xf0,0x09,0x0}}
};
static const struct opl4_region regions_78[] = { /* Guitar Fret Noise */
{0x15, 0x6c, {0x138,5266,100, 0,0,0x00,0xa0,0x38,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_79[] = { /* Breath Noise */
{0x01, 0x7f, {0x125,4269,100, 0,0,0x1e,0xd0,0x38,0xf0,0x00,0x09,0x0}}
};
static const struct opl4_region regions_7a[] = { /* Seashore */
{0x15, 0x6c, {0x008,2965, 20,-2,0,0x00,0xfe,0x00,0x20,0x03,0x04,0x0}},
{0x01, 0x7f, {0x037,4394, 20, 2,0,0x14,0xfe,0x00,0x20,0x04,0x05,0x0}}
};
static const struct opl4_region regions_7b[] = { /* Bird Tweet */
{0x15, 0x6c, {0x009,8078, 5,-4,7,0x00,0xc2,0x0f,0x22,0x12,0x07,0x0}},
{0x15, 0x6c, {0x009,3583, 5, 4,5,0x00,0xae,0x15,0x72,0x12,0x07,0x0}}
};
static const struct opl4_region regions_7c[] = { /* Telephone Ring */
{0x15, 0x6c, {0x003,3602, 10, 0,0,0x00,0xce,0x00,0xf0,0x00,0x0f,0x0}}
};
static const struct opl4_region regions_7d[] = { /* Helicopter */
{0x0c, 0x7f, {0x001,2965, 10,-2,0,0x00,0xe0,0x08,0x30,0x01,0x07,0x0}},
{0x01, 0x7f, {0x037,4394, 10, 2,0,0x44,0x76,0x00,0x30,0x01,0x07,0x0}}
};
static const struct opl4_region regions_7e[] = { /* Applause */
{0x15, 0x6c, {0x036,8273, 20,-6,7,0x00,0xc4,0x0f,0x70,0x01,0x05,0x0}},
{0x15, 0x6c, {0x036,8115, 5, 6,7,0x00,0xc6,0x07,0x70,0x01,0x05,0x0}}
};
static const struct opl4_region regions_7f[] = { /* Gun Shot */
{0x15, 0x6c, {0x139,2858, 20, 0,0,0x00,0xbe,0x38,0xf0,0x03,0x00,0x0}}
};
static const struct opl4_region regions_drums[] = {
{0x18, 0x18, {0x0cb,6397,100, 3,0,0x00,0xf4,0x38,0xc9,0x1c,0x0c,0x0}},
{0x19, 0x19, {0x0c4,3714,100, 0,0,0x00,0xe0,0x00,0x97,0x19,0x09,0x0}},
{0x1a, 0x1a, {0x0c4,3519,100, 0,0,0x00,0xea,0x00,0x61,0x01,0x07,0x0}},
{0x1b, 0x1b, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0xf7,0x19,0x09,0x0}},
{0x1c, 0x1c, {0x0c4,3586,100, 0,0,0x00,0xea,0x00,0x81,0x01,0x07,0x0}},
{0x1e, 0x1e, {0x0c3,4783,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x1f, 0x1f, {0x0d1,4042,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
{0x20, 0x20, {0x0d2,5943,100, 0,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}},
{0x21, 0x21, {0x011,3842,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}},
{0x23, 0x23, {0x011,4098,100, 0,0,0x00,0xea,0x00,0xf0,0x16,0x06,0x0}},
{0x24, 0x24, {0x011,4370,100, 0,0,0x00,0xea,0x00,0xf0,0x00,0x06,0x0}},
{0x25, 0x25, {0x0d2,4404,100, 0,0,0x00,0xd6,0x00,0xf0,0x00,0x06,0x0}},
{0x26, 0x26, {0x0d1,4298,100, 0,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
{0x27, 0x27, {0x00a,4403,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
{0x28, 0x28, {0x0d1,4554,100, 0,0,0x00,0xdc,0x00,0xf0,0x07,0x07,0x0}},
{0x29, 0x29, {0x0c8,4242,100,-4,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
{0x2a, 0x2a, {0x079,6160,100, 2,0,0x00,0xe0,0x00,0xf5,0x19,0x09,0x0}},
{0x2b, 0x2b, {0x0c8,4626,100,-3,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
{0x2c, 0x2c, {0x07b,6039,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
{0x2d, 0x2d, {0x0c8,5394,100,-2,0,0x00,0xd6,0x00,0xf6,0x16,0x06,0x0}},
{0x2e, 0x2e, {0x07a,5690,100, 2,0,0x00,0xd6,0x00,0xf0,0x00,0x05,0x0}},
{0x2f, 0x2f, {0x0c7,5185,100, 2,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
{0x30, 0x30, {0x0c7,5650,100, 3,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
{0x31, 0x31, {0x031,4395,100, 2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}},
{0x32, 0x32, {0x0c7,6162,100, 4,0,0x00,0xe0,0x00,0xf6,0x17,0x07,0x0}},
{0x33, 0x33, {0x02e,4391,100,-2,0,0x00,0xea,0x00,0xf0,0x05,0x05,0x0}},
{0x34, 0x34, {0x07a,3009,100,-2,0,0x00,0xea,0x00,0xf2,0x15,0x05,0x0}},
{0x35, 0x35, {0x021,4522,100,-3,0,0x00,0xd6,0x00,0xf0,0x05,0x05,0x0}},
{0x36, 0x36, {0x025,5163,100, 1,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
{0x37, 0x37, {0x031,5287,100,-1,0,0x00,0xea,0x00,0xf5,0x16,0x06,0x0}},
{0x38, 0x38, {0x01d,4395,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
{0x39, 0x39, {0x031,4647,100,-2,0,0x00,0xea,0x00,0xf4,0x16,0x06,0x0}},
{0x3a, 0x3a, {0x09d,4426,100,-4,0,0x00,0xe0,0x00,0xf4,0x17,0x07,0x0}},
{0x3b, 0x3b, {0x02e,4659,100,-2,0,0x00,0xea,0x00,0xf0,0x06,0x06,0x0}},
{0x3c, 0x3c, {0x01c,4769,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x3d, 0x3d, {0x01c,4611,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x3e, 0x3e, {0x01e,4402,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x3f, 0x3f, {0x01f,4387,100,-3,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x40, 0x40, {0x01f,3983,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x41, 0x41, {0x09c,4526,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x42, 0x42, {0x09c,4016,100, 2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x43, 0x43, {0x00b,4739,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x44, 0x44, {0x00b,4179,100,-4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x45, 0x45, {0x02f,4787,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
{0x46, 0x46, {0x030,4665,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
{0x47, 0x47, {0x144,4519,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}},
{0x48, 0x48, {0x144,4111,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x0b,0x0}},
{0x49, 0x49, {0x024,6408,100, 3,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
{0x4a, 0x4a, {0x024,4144,100, 3,0,0x00,0xcc,0x00,0xf0,0x00,0x09,0x0}},
{0x4b, 0x4b, {0x020,4001,100, 2,0,0x00,0xe0,0x00,0xf0,0x00,0x09,0x0}},
{0x4c, 0x4c, {0x02c,4402,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x4d, 0x4d, {0x02c,3612,100, 4,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x4e, 0x4e, {0x022,4129,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x4f, 0x4f, {0x023,4147,100,-2,0,0x00,0xea,0x00,0xf0,0x00,0x09,0x0}},
{0x50, 0x50, {0x032,4412,100,-4,0,0x00,0xd6,0x00,0xf0,0x08,0x09,0x0}},
{0x51, 0x51, {0x032,4385,100,-4,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}},
{0x52, 0x52, {0x02f,5935,100,-1,0,0x00,0xd6,0x00,0xf0,0x00,0x09,0x0}}
};
#define REGION(num) { ARRAY_SIZE(regions ## num), regions ## num }
const struct opl4_region_ptr snd_yrw801_regions[0x81] = {
REGION(_00), REGION(_01), REGION(_02), REGION(_03),
REGION(_04), REGION(_05), REGION(_06), REGION(_07),
REGION(_08), REGION(_09), REGION(_0a), REGION(_0b),
REGION(_0c), REGION(_0d), REGION(_0e), REGION(_0f),
REGION(_10), REGION(_11), REGION(_12), REGION(_13),
REGION(_14), REGION(_15), REGION(_16), REGION(_17),
REGION(_18), REGION(_19), REGION(_1a), REGION(_1b),
REGION(_1c), REGION(_1d), REGION(_1e), REGION(_1f),
REGION(_20), REGION(_21), REGION(_22), REGION(_23),
REGION(_24), REGION(_25), REGION(_26), REGION(_27),
REGION(_28), REGION(_29), REGION(_2a), REGION(_2b),
REGION(_2c), REGION(_2d), REGION(_2e), REGION(_2f),
REGION(_30), REGION(_31), REGION(_32), REGION(_33),
REGION(_34), REGION(_35), REGION(_36), REGION(_37),
REGION(_38), REGION(_39), REGION(_3a), REGION(_3b),
REGION(_3c), REGION(_3d), REGION(_3e), REGION(_3f),
REGION(_40), REGION(_41), REGION(_42), REGION(_43),
REGION(_44), REGION(_45), REGION(_46), REGION(_47),
REGION(_48), REGION(_49), REGION(_4a), REGION(_4b),
REGION(_4c), REGION(_4d), REGION(_4e), REGION(_4f),
REGION(_50), REGION(_51), REGION(_52), REGION(_53),
REGION(_54), REGION(_55), REGION(_56), REGION(_57),
REGION(_58), REGION(_59), REGION(_5a), REGION(_5b),
REGION(_5c), REGION(_5d), REGION(_5e), REGION(_5f),
REGION(_60), REGION(_61), REGION(_62), REGION(_63),
REGION(_64), REGION(_65), REGION(_66), REGION(_67),
REGION(_68), REGION(_69), REGION(_6a), REGION(_6b),
REGION(_6c), REGION(_6d), REGION(_6e), REGION(_6f),
REGION(_70), REGION(_71), REGION(_72), REGION(_73),
REGION(_74), REGION(_75), REGION(_76), REGION(_77),
REGION(_78), REGION(_79), REGION(_7a), REGION(_7b),
REGION(_7c), REGION(_7d), REGION(_7e), REGION(_7f),
REGION(_drums)
};
+789
View File
@@ -0,0 +1,789 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Virtual ALSA driver for PCM testing/fuzzing
*
* Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com>
*
* This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer
* testing or fuzzing.
* It can:
* - Simulate 'playback' and 'capture' actions
* - Generate random or pattern-based capture data
* - Check playback buffer for containing looped template, and notify about the results
* through the debugfs entry
* - Inject delays into the playback and capturing processes. See 'inject_delay' parameter.
* - Inject errors during the PCM callbacks.
* - Register custom RESET ioctl and notify when it is called through the debugfs entry
* - Work in interleaved and non-interleaved modes
* - Support up to 8 substreams
* - Support up to 4 channels
* - Support framerates from 8 kHz to 48 kHz
*
* When driver works in the capture mode with multiple channels, it duplicates the looped
* pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved
* access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for
* each channel will contain abacabaabacaba... Same for the non-interleaved mode.
*
* However, it may break the capturing on the higher framerates with small period size, so it is
* better to choose larger period sizes.
*
* You can find the corresponding selftest in the 'alsa' selftests folder.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <sound/pcm.h>
#include <sound/core.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/random.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#define TIMER_PER_SEC 5
#define TIMER_INTERVAL (HZ / TIMER_PER_SEC)
#define DELAY_JIFFIES HZ
#define PLAYBACK_SUBSTREAM_CNT 8
#define CAPTURE_SUBSTREAM_CNT 8
#define MAX_CHANNELS_NUM 4
#define DEFAULT_PATTERN "abacaba"
#define DEFAULT_PATTERN_LEN 7
#define FILL_MODE_RAND 0
#define FILL_MODE_PAT 1
#define MAX_PATTERN_LEN 4096
static int index = -1;
static char *id = "pcmtest";
static bool enable = true;
static int inject_delay;
static bool inject_hwpars_err;
static bool inject_prepare_err;
static bool inject_trigger_err;
static bool inject_open_err;
static short fill_mode = FILL_MODE_PAT;
static u8 playback_capture_test;
static u8 ioctl_reset_test;
static struct dentry *driver_debug_dir;
module_param(index, int, 0444);
MODULE_PARM_DESC(index, "Index value for pcmtest soundcard");
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for pcmtest soundcard");
module_param(enable, bool, 0444);
MODULE_PARM_DESC(enable, "Enable pcmtest soundcard.");
module_param(fill_mode, short, 0600);
MODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)");
module_param(inject_delay, int, 0600);
MODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)");
module_param(inject_hwpars_err, bool, 0600);
MODULE_PARM_DESC(inject_hwpars_err, "Inject EBUSY error in the 'hw_params' callback");
module_param(inject_prepare_err, bool, 0600);
MODULE_PARM_DESC(inject_prepare_err, "Inject EINVAL error in the 'prepare' callback");
module_param(inject_trigger_err, bool, 0600);
MODULE_PARM_DESC(inject_trigger_err, "Inject EINVAL error in the 'trigger' callback");
module_param(inject_open_err, bool, 0600);
MODULE_PARM_DESC(inject_open_err, "Inject EBUSY error in the 'open' callback");
struct pcmtst {
struct snd_pcm *pcm;
struct snd_card *card;
struct platform_device *pdev;
};
struct pcmtst_buf_iter {
size_t buf_pos; // position in the DMA buffer
size_t period_pos; // period-relative position
size_t b_rw; // Bytes to write on every timer tick
size_t s_rw_ch; // Samples to write to one channel on every tick
unsigned int sample_bytes; // sample_bits / 8
bool is_buf_corrupted; // playback test result indicator
size_t period_bytes; // bytes in a one period
bool interleaved; // Interleaved/Non-interleaved mode
size_t total_bytes; // Total bytes read/written
size_t chan_block; // Bytes in one channel buffer when non-interleaved
struct snd_pcm_substream *substream;
bool suspend; // We need to pause timer without shutting it down
struct timer_list timer_instance;
};
static struct snd_pcm_hardware snd_pcmtst_hw __ro_after_init = {
.info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_NONINTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE),
.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 1,
.channels_max = MAX_CHANNELS_NUM,
.buffer_bytes_max = 128 * 1024,
.period_bytes_min = 4096,
.period_bytes_max = 32768,
.periods_min = 1,
.periods_max = 1024,
};
struct pattern_buf {
char *buf;
u32 len;
};
static int buf_allocated __ro_after_init;
static struct pattern_buf patt_bufs[MAX_CHANNELS_NUM];
static inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes)
{
v_iter->total_bytes += by;
v_iter->buf_pos += by;
if (v_iter->buf_pos >= bytes)
v_iter->buf_pos %= bytes;
}
/*
* Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos
* every time we write a byte to any channel, so the position in the current channel buffer is
* (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel
*/
static inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels,
unsigned int chan_num)
{
return v_iter->buf_pos / channels + v_iter->chan_block * chan_num;
}
/*
* Get the count of bytes written for the current channel in the interleaved mode.
* This is (count of samples written for the current channel) * bytes_in_sample +
* (relative position in the current sample)
*/
static inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample)
{
return b_total / channels / b_sample * b_sample + (b_total % b_sample);
}
static void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
size_t i;
short ch_num;
u8 current_byte;
for (i = 0; i < v_iter->b_rw; i++) {
current_byte = runtime->dma_area[v_iter->buf_pos];
if (!current_byte)
break;
ch_num = (v_iter->total_bytes / v_iter->sample_bytes) % runtime->channels;
if (current_byte != patt_bufs[ch_num].buf[ch_pos_i(v_iter->total_bytes,
runtime->channels,
v_iter->sample_bytes)
% patt_bufs[ch_num].len]) {
v_iter->is_buf_corrupted = true;
break;
}
inc_buf_pos(v_iter, 1, runtime->dma_bytes);
}
// If we broke during the loop, add remaining bytes to the buffer position.
inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes);
}
static void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
unsigned int channels = runtime->channels;
size_t i;
short ch_num;
u8 current_byte;
for (i = 0; i < v_iter->b_rw; i++) {
ch_num = i % channels;
current_byte = runtime->dma_area[buf_pos_n(v_iter, channels, ch_num)];
if (!current_byte)
break;
if (current_byte != patt_bufs[ch_num].buf[(v_iter->total_bytes / channels)
% patt_bufs[ch_num].len]) {
v_iter->is_buf_corrupted = true;
break;
}
inc_buf_pos(v_iter, 1, runtime->dma_bytes);
}
inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes);
}
/*
* Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is
* necessary because we need to detect when the reading/writing ends, so we assume that the pattern
* doesn't contain zeros.
*/
static void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
if (v_iter->interleaved)
check_buf_block_i(v_iter, runtime);
else
check_buf_block_ni(v_iter, runtime);
}
/*
* Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2...
* The channel buffers lay in the DMA buffer continuously (see default copy
* handlers in the pcm_lib.c file).
*
* Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'.
* We need this to simulate the correct hardware pointer moving.
*/
static void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
size_t i;
unsigned int channels = runtime->channels;
short ch_num;
for (i = 0; i < v_iter->b_rw; i++) {
ch_num = i % channels;
runtime->dma_area[buf_pos_n(v_iter, channels, ch_num)] =
patt_bufs[ch_num].buf[(v_iter->total_bytes / channels)
% patt_bufs[ch_num].len];
inc_buf_pos(v_iter, 1, runtime->dma_bytes);
}
}
// Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ...
static void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
size_t sample;
size_t pos_in_ch, pos_pattern;
short ch, pos_sample;
pos_in_ch = ch_pos_i(v_iter->total_bytes, runtime->channels, v_iter->sample_bytes);
for (sample = 0; sample < v_iter->s_rw_ch; sample++) {
for (ch = 0; ch < runtime->channels; ch++) {
for (pos_sample = 0; pos_sample < v_iter->sample_bytes; pos_sample++) {
pos_pattern = (pos_in_ch + sample * v_iter->sample_bytes
+ pos_sample) % patt_bufs[ch].len;
runtime->dma_area[v_iter->buf_pos] = patt_bufs[ch].buf[pos_pattern];
inc_buf_pos(v_iter, 1, runtime->dma_bytes);
}
}
}
}
static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
if (v_iter->interleaved)
fill_block_pattern_i(v_iter, runtime);
else
fill_block_pattern_n(v_iter, runtime);
}
static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
unsigned int channels = runtime->channels;
// Remaining space in all channel buffers
size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos;
unsigned int i;
for (i = 0; i < channels; i++) {
if (v_iter->b_rw <= bytes_remain) {
//b_rw - count of bytes must be written for all channels at each timer tick
get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i),
v_iter->b_rw / channels);
} else {
// Write to the end of buffer and start from the beginning of it
get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i),
bytes_remain / channels);
get_random_bytes(runtime->dma_area + v_iter->chan_block * i,
(v_iter->b_rw - bytes_remain) / channels);
}
}
inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes);
}
static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos;
if (v_iter->b_rw <= in_cur_block) {
get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw);
} else {
get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block);
get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block);
}
inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes);
}
static void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
if (v_iter->interleaved)
fill_block_rand_i(v_iter, runtime);
else
fill_block_rand_n(v_iter, runtime);
}
static void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime)
{
switch (fill_mode) {
case FILL_MODE_RAND:
fill_block_random(v_iter, runtime);
break;
case FILL_MODE_PAT:
fill_block_pattern(v_iter, runtime);
break;
}
}
/*
* Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes.
* The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer
* about period elapsed.
*/
static void timer_timeout(struct timer_list *data)
{
struct pcmtst_buf_iter *v_iter;
struct snd_pcm_substream *substream;
v_iter = timer_container_of(v_iter, data, timer_instance);
substream = v_iter->substream;
if (v_iter->suspend)
return;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !v_iter->is_buf_corrupted)
check_buf_block(v_iter, substream->runtime);
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
fill_block(v_iter, substream->runtime);
else
inc_buf_pos(v_iter, v_iter->b_rw, substream->runtime->dma_bytes);
v_iter->period_pos += v_iter->b_rw;
if (v_iter->period_pos >= v_iter->period_bytes) {
v_iter->period_pos %= v_iter->period_bytes;
snd_pcm_period_elapsed(substream);
}
if (!v_iter->suspend)
mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL + inject_delay);
}
static int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcmtst_buf_iter *v_iter;
if (inject_open_err)
return -EBUSY;
v_iter = kzalloc_obj(*v_iter);
if (!v_iter)
return -ENOMEM;
v_iter->substream = substream;
runtime->hw = snd_pcmtst_hw;
runtime->private_data = v_iter;
playback_capture_test = 0;
ioctl_reset_test = 0;
timer_setup(&v_iter->timer_instance, timer_timeout, 0);
return 0;
}
static int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream)
{
struct pcmtst_buf_iter *v_iter = substream->runtime->private_data;
timer_shutdown_sync(&v_iter->timer_instance);
playback_capture_test = !v_iter->is_buf_corrupted;
kfree(v_iter);
return 0;
}
static inline void reset_buf_iterator(struct pcmtst_buf_iter *v_iter)
{
v_iter->buf_pos = 0;
v_iter->is_buf_corrupted = false;
v_iter->period_pos = 0;
v_iter->total_bytes = 0;
}
static inline void start_pcmtest_timer(struct pcmtst_buf_iter *v_iter)
{
v_iter->suspend = false;
mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL);
}
static int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct pcmtst_buf_iter *v_iter = substream->runtime->private_data;
if (inject_trigger_err)
return -EINVAL;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
reset_buf_iterator(v_iter);
start_pcmtest_timer(v_iter);
break;
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
start_pcmtest_timer(v_iter);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
// We can't call timer_shutdown_sync here, as it is forbidden to sleep here
v_iter->suspend = true;
timer_delete(&v_iter->timer_instance);
break;
}
return 0;
}
static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream)
{
struct pcmtst_buf_iter *v_iter = substream->runtime->private_data;
return bytes_to_frames(substream->runtime, v_iter->buf_pos);
}
static int snd_pcmtst_free(struct pcmtst *pcmtst)
{
if (!pcmtst)
return 0;
kfree(pcmtst);
return 0;
}
// These callbacks are required, but empty - all freeing occurs in pdev_remove
static int snd_pcmtst_dev_free(struct snd_device *device)
{
return 0;
}
static void pcmtst_pdev_release(struct device *dev)
{
}
static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct pcmtst_buf_iter *v_iter = runtime->private_data;
if (inject_prepare_err)
return -EINVAL;
v_iter->sample_bytes = samples_to_bytes(runtime, 1);
v_iter->period_bytes = snd_pcm_lib_period_bytes(substream);
v_iter->interleaved = true;
if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED ||
runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) {
v_iter->chan_block = snd_pcm_lib_buffer_bytes(substream) / runtime->channels;
v_iter->interleaved = false;
}
// We want to record RATE * ch_cnt samples per sec, it is rate * sample_bytes * ch_cnt bytes
v_iter->s_rw_ch = runtime->rate / TIMER_PER_SEC;
v_iter->b_rw = v_iter->s_rw_ch * v_iter->sample_bytes * runtime->channels;
return 0;
}
static int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
if (inject_hwpars_err)
return -EBUSY;
return 0;
}
static int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream)
{
return 0;
}
static int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg)
{
switch (cmd) {
case SNDRV_PCM_IOCTL1_RESET:
ioctl_reset_test = 1;
break;
}
return snd_pcm_lib_ioctl(substream, cmd, arg);
}
static int snd_pcmtst_sync_stop(struct snd_pcm_substream *substream)
{
struct pcmtst_buf_iter *v_iter = substream->runtime->private_data;
timer_delete_sync(&v_iter->timer_instance);
return 0;
}
static const struct snd_pcm_ops snd_pcmtst_playback_ops = {
.open = snd_pcmtst_pcm_open,
.close = snd_pcmtst_pcm_close,
.trigger = snd_pcmtst_pcm_trigger,
.hw_params = snd_pcmtst_pcm_hw_params,
.ioctl = snd_pcmtst_ioctl,
.sync_stop = snd_pcmtst_sync_stop,
.hw_free = snd_pcmtst_pcm_hw_free,
.prepare = snd_pcmtst_pcm_prepare,
.pointer = snd_pcmtst_pcm_pointer,
};
static const struct snd_pcm_ops snd_pcmtst_capture_ops = {
.open = snd_pcmtst_pcm_open,
.close = snd_pcmtst_pcm_close,
.trigger = snd_pcmtst_pcm_trigger,
.hw_params = snd_pcmtst_pcm_hw_params,
.hw_free = snd_pcmtst_pcm_hw_free,
.ioctl = snd_pcmtst_ioctl,
.sync_stop = snd_pcmtst_sync_stop,
.prepare = snd_pcmtst_pcm_prepare,
.pointer = snd_pcmtst_pcm_pointer,
};
static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(pcmtst->card, "PCMTest", 0, PLAYBACK_SUBSTREAM_CNT,
CAPTURE_SUBSTREAM_CNT, &pcm);
if (err < 0)
return err;
pcm->private_data = pcmtst;
strscpy(pcm->name, "PCMTest");
pcmtst->pcm = pcm;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pcmtst_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pcmtst_capture_ops);
err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &pcmtst->pdev->dev,
0, 128 * 1024);
return err;
}
static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev,
struct pcmtst **r_pcmtst)
{
struct pcmtst *pcmtst;
int err;
static const struct snd_device_ops ops = {
.dev_free = snd_pcmtst_dev_free,
};
pcmtst = kzalloc_obj(*pcmtst);
if (!pcmtst)
return -ENOMEM;
pcmtst->card = card;
pcmtst->pdev = pdev;
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops);
if (err < 0)
goto _err_free_chip;
err = snd_pcmtst_new_pcm(pcmtst);
if (err < 0)
goto _err_free_chip;
*r_pcmtst = pcmtst;
return 0;
_err_free_chip:
snd_pcmtst_free(pcmtst);
return err;
}
static int pcmtst_probe(struct platform_device *pdev)
{
struct snd_card *card;
struct pcmtst *pcmtst;
int err;
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (err)
return err;
err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card);
if (err < 0)
return err;
err = snd_pcmtst_create(card, pdev, &pcmtst);
if (err < 0)
return err;
strscpy(card->driver, "PCM-TEST Driver");
strscpy(card->shortname, "PCM-Test");
strscpy(card->longname, "PCM-Test virtual driver");
err = snd_card_register(card);
if (err < 0)
return err;
platform_set_drvdata(pdev, pcmtst);
return 0;
}
static void pdev_remove(struct platform_device *pdev)
{
struct pcmtst *pcmtst = platform_get_drvdata(pdev);
snd_pcmtst_free(pcmtst);
}
static struct platform_device pcmtst_pdev = {
.name = "pcmtest",
.dev.release = pcmtst_pdev_release,
};
static struct platform_driver pcmtst_pdrv = {
.probe = pcmtst_probe,
.remove = pdev_remove,
.driver = {
.name = "pcmtest",
},
};
static ssize_t pattern_write(struct file *file, const char __user *u_buff, size_t len, loff_t *off)
{
struct pattern_buf *patt_buf = file->f_inode->i_private;
ssize_t to_write = len;
if (*off + to_write > MAX_PATTERN_LEN)
to_write = MAX_PATTERN_LEN - *off;
// Crop silently everything over the buffer
if (to_write <= 0)
return len;
if (copy_from_user(patt_buf->buf + *off, u_buff, to_write))
return -EFAULT;
patt_buf->len = *off + to_write;
*off += to_write;
return to_write;
}
static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, loff_t *off)
{
struct pattern_buf *patt_buf = file->f_inode->i_private;
ssize_t to_read = len;
if (*off + to_read >= MAX_PATTERN_LEN)
to_read = MAX_PATTERN_LEN - *off;
if (to_read <= 0)
return 0;
if (copy_to_user(u_buff, patt_buf->buf + *off, to_read))
return -EFAULT;
*off += to_read;
return to_read;
}
static const struct file_operations fill_pattern_fops = {
.read = pattern_read,
.write = pattern_write,
};
static int setup_patt_bufs(void)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(patt_bufs); i++) {
patt_bufs[i].buf = kmalloc(MAX_PATTERN_LEN, GFP_KERNEL);
if (!patt_bufs[i].buf)
break;
strscpy_pad(patt_bufs[i].buf, DEFAULT_PATTERN, MAX_PATTERN_LEN);
patt_bufs[i].len = DEFAULT_PATTERN_LEN;
}
return i;
}
static const char * const pattern_files[] = { "fill_pattern0", "fill_pattern1",
"fill_pattern2", "fill_pattern3"};
static int init_debug_files(int buf_count)
{
size_t i;
char len_file_name[32];
driver_debug_dir = debugfs_create_dir("pcmtest", NULL);
if (IS_ERR(driver_debug_dir))
return PTR_ERR(driver_debug_dir);
debugfs_create_u8("pc_test", 0444, driver_debug_dir, &playback_capture_test);
debugfs_create_u8("ioctl_test", 0444, driver_debug_dir, &ioctl_reset_test);
for (i = 0; i < buf_count; i++) {
debugfs_create_file(pattern_files[i], 0600, driver_debug_dir,
&patt_bufs[i], &fill_pattern_fops);
snprintf(len_file_name, sizeof(len_file_name), "%s_len", pattern_files[i]);
debugfs_create_u32(len_file_name, 0444, driver_debug_dir, &patt_bufs[i].len);
}
return 0;
}
static void free_pattern_buffers(void)
{
int i;
for (i = 0; i < buf_allocated; i++)
kfree(patt_bufs[i].buf);
}
static void clear_debug_files(void)
{
debugfs_remove_recursive(driver_debug_dir);
}
static int __init mod_init(void)
{
int err = 0;
buf_allocated = setup_patt_bufs();
if (!buf_allocated)
return -ENOMEM;
snd_pcmtst_hw.channels_max = buf_allocated;
err = init_debug_files(buf_allocated);
if (err)
goto err_free_patterns;
err = platform_device_register(&pcmtst_pdev);
if (err)
goto err_clear_debug;
err = platform_driver_register(&pcmtst_pdrv);
if (err) {
platform_device_unregister(&pcmtst_pdev);
goto err_clear_debug;
}
return 0;
err_clear_debug:
clear_debug_files();
err_free_patterns:
free_pattern_buffers();
return err;
}
static void __exit mod_exit(void)
{
clear_debug_files();
free_pattern_buffers();
platform_driver_unregister(&pcmtst_pdrv);
platform_device_unregister(&pcmtst_pdev);
}
MODULE_DESCRIPTION("Virtual ALSA driver for PCM testing/fuzzing");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Ivan Orlov");
module_init(mod_init);
module_exit(mod_exit);
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-pcsp-y := pcsp.o pcsp_lib.o pcsp_mixer.o pcsp_input.o
obj-$(CONFIG_SND_PCSP) += snd-pcsp.o
+216
View File
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PC-Speaker driver for Linux
*
* Copyright (C) 1997-2001 David Woodhouse
* Copyright (C) 2001-2008 Stas Sergeev
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <linux/mm.h>
#include "pcsp_input.h"
#include "pcsp.h"
MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>");
MODULE_DESCRIPTION("PC-Speaker driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pcspkr");
static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */
static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */
static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */
static bool nopcm; /* Disable PCM capability of the driver */
module_param(index, int, 0444);
MODULE_PARM_DESC(index, "Index value for pcsp soundcard.");
module_param(id, charp, 0444);
MODULE_PARM_DESC(id, "ID string for pcsp soundcard.");
module_param(enable, bool, 0444);
MODULE_PARM_DESC(enable, "Enable PC-Speaker sound.");
module_param(nopcm, bool, 0444);
MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain.");
struct snd_pcsp pcsp_chip;
static int snd_pcsp_create(struct snd_card *card)
{
unsigned int resolution = hrtimer_resolution;
int div, min_div, order;
if (!nopcm) {
if (resolution > PCSP_MAX_PERIOD_NS) {
dev_err(card->dev,
"PCSP: Timer resolution is not sufficient (%unS)\n",
resolution);
dev_err(card->dev,
"PCSP: Make sure you have HPET and ACPI enabled.\n");
dev_err(card->dev, "PCSP: Turned into nopcm mode.\n");
nopcm = 1;
}
}
if (loops_per_jiffy >= PCSP_MIN_LPJ && resolution <= PCSP_MIN_PERIOD_NS)
min_div = MIN_DIV;
else
min_div = MAX_DIV;
#if PCSP_DEBUG
dev_dbg(card->dev, "PCSP: lpj=%li, min_div=%i, res=%u\n",
loops_per_jiffy, min_div, resolution);
#endif
div = MAX_DIV / min_div;
order = fls(div) - 1;
pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE);
pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE);
pcsp_chip.playback_ptr = 0;
pcsp_chip.period_ptr = 0;
atomic_set(&pcsp_chip.timer_active, 0);
pcsp_chip.enable = 1;
pcsp_chip.pcspkr = 1;
spin_lock_init(&pcsp_chip.substream_lock);
pcsp_chip.card = card;
pcsp_chip.port = 0x61;
pcsp_chip.irq = -1;
pcsp_chip.dma = -1;
card->private_data = &pcsp_chip;
return 0;
}
static void pcsp_stop_beep(struct snd_pcsp *chip);
static void alsa_card_pcsp_free(struct snd_card *card)
{
pcsp_stop_beep(card->private_data);
}
static int snd_card_pcsp_probe(int devnum, struct device *dev)
{
struct snd_card *card;
int err;
if (devnum != 0)
return -EINVAL;
hrtimer_setup(&pcsp_chip.timer, pcsp_do_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
err = snd_devm_card_new(dev, index, id, THIS_MODULE, 0, &card);
if (err < 0)
return err;
err = snd_pcsp_create(card);
if (err < 0)
return err;
if (!nopcm) {
err = snd_pcsp_new_pcm(&pcsp_chip);
if (err < 0)
return err;
}
err = snd_pcsp_new_mixer(&pcsp_chip, nopcm);
if (err < 0)
return err;
strscpy(card->driver, "PC-Speaker");
strscpy(card->shortname, "pcsp");
sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
pcsp_chip.port);
err = snd_card_register(card);
if (err < 0)
return err;
card->private_free = alsa_card_pcsp_free;
return 0;
}
static int alsa_card_pcsp_init(struct device *dev)
{
int err;
err = snd_card_pcsp_probe(0, dev);
if (err) {
dev_err(dev, "PC-Speaker initialization failed.\n");
return err;
}
/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
if (debug_pagealloc_enabled()) {
dev_warn(dev,
"PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, which may make the sound noisy.\n");
}
return 0;
}
static int pcsp_probe(struct platform_device *dev)
{
int err;
err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev);
if (err < 0)
return err;
err = alsa_card_pcsp_init(&dev->dev);
if (err < 0)
return err;
platform_set_drvdata(dev, &pcsp_chip);
return 0;
}
static void pcsp_stop_beep(struct snd_pcsp *chip)
{
pcsp_sync_stop(chip);
pcspkr_stop_sound();
}
static int pcsp_suspend(struct device *dev)
{
struct snd_pcsp *chip = dev_get_drvdata(dev);
pcsp_stop_beep(chip);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL);
static void pcsp_shutdown(struct platform_device *dev)
{
struct snd_pcsp *chip = platform_get_drvdata(dev);
pcsp_stop_beep(chip);
}
static struct platform_driver pcsp_platform_driver = {
.driver = {
.name = "pcspkr",
.pm = &pcsp_pm,
},
.probe = pcsp_probe,
.shutdown = pcsp_shutdown,
};
static int __init pcsp_init(void)
{
if (!enable)
return -ENODEV;
return platform_driver_register(&pcsp_platform_driver);
}
static void __exit pcsp_exit(void)
{
platform_driver_unregister(&pcsp_platform_driver);
}
module_init(pcsp_init);
module_exit(pcsp_exit);
+83
View File
@@ -0,0 +1,83 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* PC-Speaker driver for Linux
*
* Copyright (C) 1993-1997 Michael Beck
* Copyright (C) 1997-2001 David Woodhouse
* Copyright (C) 2001-2008 Stas Sergeev
*/
#ifndef __PCSP_H__
#define __PCSP_H__
#include <linux/hrtimer.h>
#include <linux/i8253.h>
#include <linux/timex.h>
#define PCSP_SOUND_VERSION 0x400 /* read 4.00 */
#define PCSP_DEBUG 0
/* default timer freq for PC-Speaker: 18643 Hz */
#define DIV_18KHZ 64
#define MAX_DIV DIV_18KHZ
#define CALC_DIV(d) (MAX_DIV >> (d))
#define CUR_DIV() CALC_DIV(chip->treble)
#define PCSP_MAX_TREBLE 1
/* unfortunately, with hrtimers 37KHz does not work very well :( */
#define PCSP_DEFAULT_TREBLE 0
#define MIN_DIV (MAX_DIV >> PCSP_MAX_TREBLE)
/* wild guess */
#define PCSP_MIN_LPJ 1000000
#define PCSP_DEFAULT_SDIV (DIV_18KHZ >> 1)
#define PCSP_DEFAULT_SRATE (PIT_TICK_RATE / PCSP_DEFAULT_SDIV)
#define PCSP_INDEX_INC() (1 << (PCSP_MAX_TREBLE - chip->treble))
#define PCSP_CALC_RATE(i) (PIT_TICK_RATE / CALC_DIV(i))
#define PCSP_RATE() PCSP_CALC_RATE(chip->treble)
#define PCSP_MIN_RATE__1 MAX_DIV/PIT_TICK_RATE
#define PCSP_MAX_RATE__1 MIN_DIV/PIT_TICK_RATE
#define PCSP_MAX_PERIOD_NS (1000000000ULL * PCSP_MIN_RATE__1)
#define PCSP_MIN_PERIOD_NS (1000000000ULL * PCSP_MAX_RATE__1)
#define PCSP_CALC_NS(div) ({ \
u64 __val = 1000000000ULL * (div); \
do_div(__val, PIT_TICK_RATE); \
__val; \
})
#define PCSP_PERIOD_NS() PCSP_CALC_NS(CUR_DIV())
#define PCSP_MAX_PERIOD_SIZE (64*1024)
#define PCSP_MAX_PERIODS 512
#define PCSP_BUFFER_SIZE (128*1024)
struct snd_pcsp {
struct snd_card *card;
struct snd_pcm *pcm;
struct input_dev *input_dev;
struct hrtimer timer;
unsigned short port, irq, dma;
spinlock_t substream_lock;
struct snd_pcm_substream *playback_substream;
unsigned int fmt_size;
unsigned int is_signed;
size_t playback_ptr;
size_t period_ptr;
atomic_t timer_active;
int thalf;
u64 ns_rem;
unsigned char val61;
int enable;
int max_treble;
int treble;
int pcspkr;
};
extern struct snd_pcsp pcsp_chip;
extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle);
extern void pcsp_sync_stop(struct snd_pcsp *chip);
extern int snd_pcsp_new_pcm(struct snd_pcsp *chip);
extern int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm);
#endif
+103
View File
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* PC Speaker beeper driver for Linux
*
* Copyright (c) 2002 Vojtech Pavlik
* Copyright (c) 1992 Orest Zborowski
*/
#include <linux/init.h>
#include <linux/input.h>
#include <linux/io.h>
#include "pcsp.h"
#include "pcsp_input.h"
static void pcspkr_do_sound(unsigned int count)
{
unsigned long flags;
raw_spin_lock_irqsave(&i8253_lock, flags);
if (count) {
/* set command for counter 2, 2 byte write */
outb_p(0xB6, 0x43);
/* select desired HZ */
outb_p(count & 0xff, 0x42);
outb((count >> 8) & 0xff, 0x42);
/* enable counter 2 */
outb_p(inb_p(0x61) | 3, 0x61);
} else {
/* disable counter 2 */
outb(inb_p(0x61) & 0xFC, 0x61);
}
raw_spin_unlock_irqrestore(&i8253_lock, flags);
}
void pcspkr_stop_sound(void)
{
pcspkr_do_sound(0);
}
static int pcspkr_input_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
unsigned int count = 0;
if (atomic_read(&pcsp_chip.timer_active) || !pcsp_chip.pcspkr)
return 0;
switch (type) {
case EV_SND:
switch (code) {
case SND_BELL:
if (value)
value = 1000;
break;
case SND_TONE:
break;
default:
return -1;
}
break;
default:
return -1;
}
if (value > 20 && value < 32767)
count = PIT_TICK_RATE / value;
pcspkr_do_sound(count);
return 0;
}
int pcspkr_input_init(struct input_dev **rdev, struct device *dev)
{
int err;
struct input_dev *input_dev = devm_input_allocate_device(dev);
if (!input_dev)
return -ENOMEM;
input_dev->name = "PC Speaker";
input_dev->phys = "isa0061/input0";
input_dev->id.bustype = BUS_ISA;
input_dev->id.vendor = 0x001f;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
input_dev->dev.parent = dev;
input_dev->evbit[0] = BIT(EV_SND);
input_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE);
input_dev->event = pcspkr_input_event;
err = input_register_device(input_dev);
if (err)
return err;
*rdev = input_dev;
return 0;
}
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* PC-Speaker driver for Linux
*
* Copyright (C) 2001-2008 Stas Sergeev
*/
#ifndef __PCSP_INPUT_H__
#define __PCSP_INPUT_H__
int pcspkr_input_init(struct input_dev **rdev, struct device *dev);
void pcspkr_stop_sound(void);
#endif
+352
View File
@@ -0,0 +1,352 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PC-Speaker driver for Linux
*
* Copyright (C) 1993-1997 Michael Beck
* Copyright (C) 1997-2001 David Woodhouse
* Copyright (C) 2001-2008 Stas Sergeev
*/
#include <linux/module.h>
#include <linux/gfp.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include "pcsp.h"
static bool nforce_wa;
module_param(nforce_wa, bool, 0444);
MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround "
"(expect bad sound)");
#define DMIX_WANTS_S16 1
/*
* Call snd_pcm_period_elapsed in a work
* This avoids spinlock messes and long-running irq contexts
*/
static void pcsp_call_pcm_elapsed(struct work_struct *work)
{
if (atomic_read(&pcsp_chip.timer_active)) {
struct snd_pcm_substream *substream;
substream = pcsp_chip.playback_substream;
if (substream)
snd_pcm_period_elapsed(substream);
}
}
static DECLARE_WORK(pcsp_pcm_work, pcsp_call_pcm_elapsed);
/* write the port and returns the next expire time in ns;
* called at the trigger-start and in hrtimer callback
*/
static u64 pcsp_timer_update(struct snd_pcsp *chip)
{
unsigned char timer_cnt, val;
u64 ns;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
unsigned long flags;
if (chip->thalf) {
outb(chip->val61, 0x61);
chip->thalf = 0;
return chip->ns_rem;
}
substream = chip->playback_substream;
if (!substream)
return 0;
runtime = substream->runtime;
/* assume it is mono! */
val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1];
if (chip->is_signed)
val ^= 0x80;
timer_cnt = val * CUR_DIV() / 256;
if (timer_cnt && chip->enable) {
raw_spin_lock_irqsave(&i8253_lock, flags);
if (!nforce_wa) {
outb_p(chip->val61, 0x61);
outb_p(timer_cnt, 0x42);
outb(chip->val61 ^ 1, 0x61);
} else {
outb(chip->val61 ^ 2, 0x61);
chip->thalf = 1;
}
raw_spin_unlock_irqrestore(&i8253_lock, flags);
}
chip->ns_rem = PCSP_PERIOD_NS();
ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem);
chip->ns_rem -= ns;
return ns;
}
static void pcsp_pointer_update(struct snd_pcsp *chip)
{
struct snd_pcm_substream *substream;
size_t period_bytes, buffer_bytes;
int periods_elapsed;
unsigned long flags;
/* update the playback position */
substream = chip->playback_substream;
if (!substream)
return;
period_bytes = snd_pcm_lib_period_bytes(substream);
buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
spin_lock_irqsave(&chip->substream_lock, flags);
chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size;
periods_elapsed = chip->playback_ptr - chip->period_ptr;
if (periods_elapsed < 0) {
#if PCSP_DEBUG
dev_dbg(chip->card->dev,
"PCSP: buffer_bytes mod period_bytes != 0 ? (%zi %zi %zi)\n",
chip->playback_ptr, period_bytes, buffer_bytes);
#endif
periods_elapsed += buffer_bytes;
}
periods_elapsed /= period_bytes;
/* wrap the pointer _before_ calling snd_pcm_period_elapsed(),
* or ALSA will BUG on us. */
chip->playback_ptr %= buffer_bytes;
if (periods_elapsed) {
chip->period_ptr += periods_elapsed * period_bytes;
chip->period_ptr %= buffer_bytes;
queue_work(system_highpri_wq, &pcsp_pcm_work);
}
spin_unlock_irqrestore(&chip->substream_lock, flags);
}
enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle)
{
struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer);
int pointer_update;
u64 ns;
if (!atomic_read(&chip->timer_active) || !chip->playback_substream)
return HRTIMER_NORESTART;
pointer_update = !chip->thalf;
ns = pcsp_timer_update(chip);
if (!ns) {
dev_warn(chip->card->dev, "PCSP: unexpected stop\n");
return HRTIMER_NORESTART;
}
if (pointer_update)
pcsp_pointer_update(chip);
hrtimer_forward_now(handle, ns_to_ktime(ns));
return HRTIMER_RESTART;
}
static int pcsp_start_playing(struct snd_pcsp *chip)
{
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: start_playing called\n");
#endif
if (atomic_read(&chip->timer_active)) {
dev_err(chip->card->dev, "PCSP: Timer already active\n");
return -EIO;
}
raw_spin_lock(&i8253_lock);
chip->val61 = inb(0x61) | 0x03;
outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */
raw_spin_unlock(&i8253_lock);
atomic_set(&chip->timer_active, 1);
chip->thalf = 0;
hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL);
return 0;
}
static void pcsp_stop_playing(struct snd_pcsp *chip)
{
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: stop_playing called\n");
#endif
if (!atomic_read(&chip->timer_active))
return;
atomic_set(&chip->timer_active, 0);
raw_spin_lock(&i8253_lock);
/* restore the timer */
outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */
outb(chip->val61 & 0xFC, 0x61);
raw_spin_unlock(&i8253_lock);
}
/*
* Force to stop and sync the stream
*/
void pcsp_sync_stop(struct snd_pcsp *chip)
{
local_irq_disable();
pcsp_stop_playing(chip);
local_irq_enable();
hrtimer_cancel(&chip->timer);
cancel_work_sync(&pcsp_pcm_work);
}
static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
{
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: close called\n");
#endif
pcsp_sync_stop(chip);
chip->playback_substream = NULL;
return 0;
}
static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
pcsp_sync_stop(chip);
return 0;
}
static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: hw_free called\n");
#endif
pcsp_sync_stop(chip);
return 0;
}
static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
pcsp_sync_stop(chip);
chip->playback_ptr = 0;
chip->period_ptr = 0;
chip->fmt_size =
snd_pcm_format_physical_width(substream->runtime->format) >> 3;
chip->is_signed = snd_pcm_format_signed(substream->runtime->format);
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: prepare called, size=%zi psize=%zi f=%zi f1=%i fsize=%i\n",
snd_pcm_lib_buffer_bytes(substream),
snd_pcm_lib_period_bytes(substream),
snd_pcm_lib_buffer_bytes(substream) /
snd_pcm_lib_period_bytes(substream),
substream->runtime->periods,
chip->fmt_size);
#endif
return 0;
}
static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: trigger called\n");
#endif
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
return pcsp_start_playing(chip);
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
pcsp_stop_playing(chip);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream
*substream)
{
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
unsigned int pos;
spin_lock(&chip->substream_lock);
pos = chip->playback_ptr;
spin_unlock(&chip->substream_lock);
return bytes_to_frames(substream->runtime, pos);
}
static const struct snd_pcm_hardware snd_pcsp_playback = {
.info = (SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_HALF_DUPLEX |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
.formats = (SNDRV_PCM_FMTBIT_U8
#if DMIX_WANTS_S16
| SNDRV_PCM_FMTBIT_S16_LE
#endif
),
.rates = SNDRV_PCM_RATE_KNOT,
.rate_min = PCSP_DEFAULT_SRATE,
.rate_max = PCSP_DEFAULT_SRATE,
.channels_min = 1,
.channels_max = 1,
.buffer_bytes_max = PCSP_BUFFER_SIZE,
.period_bytes_min = 64,
.period_bytes_max = PCSP_MAX_PERIOD_SIZE,
.periods_min = 2,
.periods_max = PCSP_MAX_PERIODS,
.fifo_size = 0,
};
static int snd_pcsp_playback_open(struct snd_pcm_substream *substream)
{
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: open called\n");
#endif
if (atomic_read(&chip->timer_active)) {
dev_err(chip->card->dev, "PCSP: still active!!\n");
return -EBUSY;
}
runtime->hw = snd_pcsp_playback;
chip->playback_substream = substream;
return 0;
}
static const struct snd_pcm_ops snd_pcsp_playback_ops = {
.open = snd_pcsp_playback_open,
.close = snd_pcsp_playback_close,
.hw_params = snd_pcsp_playback_hw_params,
.hw_free = snd_pcsp_playback_hw_free,
.prepare = snd_pcsp_playback_prepare,
.trigger = snd_pcsp_trigger,
.pointer = snd_pcsp_playback_pointer,
};
int snd_pcsp_new_pcm(struct snd_pcsp *chip)
{
int err;
err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm);
if (err < 0)
return err;
snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK,
&snd_pcsp_playback_ops);
chip->pcm->private_data = chip;
chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;
strscpy(chip->pcm->name, "pcsp");
snd_pcm_set_managed_buffer_all(chip->pcm,
SNDRV_DMA_TYPE_CONTINUOUS,
NULL,
PCSP_BUFFER_SIZE,
PCSP_BUFFER_SIZE);
return 0;
}
+164
View File
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0
/*
* PC-Speaker driver for Linux
*
* Mixer implementation.
* Copyright (C) 2001-2008 Stas Sergeev
*/
#include <sound/core.h>
#include <sound/control.h>
#include "pcsp.h"
static int pcsp_enable_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int pcsp_enable_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = chip->enable;
return 0;
}
static int pcsp_enable_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
int changed = 0;
int enab = ucontrol->value.integer.value[0];
if (enab != chip->enable) {
chip->enable = enab;
changed = 1;
}
return changed;
}
static int pcsp_treble_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = chip->max_treble + 1;
if (uinfo->value.enumerated.item > chip->max_treble)
uinfo->value.enumerated.item = chip->max_treble;
sprintf(uinfo->value.enumerated.name, "%lu",
(unsigned long)PCSP_CALC_RATE(uinfo->value.enumerated.item));
return 0;
}
static int pcsp_treble_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = chip->treble;
return 0;
}
static int pcsp_treble_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
int changed = 0;
int treble = ucontrol->value.enumerated.item[0];
if (treble != chip->treble) {
chip->treble = treble;
#if PCSP_DEBUG
dev_dbg(chip->card->dev, "PCSP: rate set to %li\n", PCSP_RATE());
#endif
changed = 1;
}
return changed;
}
static int pcsp_pcspkr_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int pcsp_pcspkr_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.integer.value[0] = chip->pcspkr;
return 0;
}
static int pcsp_pcspkr_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol);
int changed = 0;
int spkr = ucontrol->value.integer.value[0];
if (spkr != chip->pcspkr) {
chip->pcspkr = spkr;
changed = 1;
}
return changed;
}
#define PCSP_MIXER_CONTROL(ctl_type, ctl_name) \
{ \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = ctl_name, \
.info = pcsp_##ctl_type##_info, \
.get = pcsp_##ctl_type##_get, \
.put = pcsp_##ctl_type##_put, \
}
static const struct snd_kcontrol_new snd_pcsp_controls_pcm[] = {
PCSP_MIXER_CONTROL(enable, "Master Playback Switch"),
PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"),
};
static const struct snd_kcontrol_new snd_pcsp_controls_spkr[] = {
PCSP_MIXER_CONTROL(pcspkr, "Beep Playback Switch"),
};
static int snd_pcsp_ctls_add(struct snd_pcsp *chip,
const struct snd_kcontrol_new *ctls, int num)
{
int i, err;
struct snd_card *card = chip->card;
for (i = 0; i < num; i++) {
err = snd_ctl_add(card, snd_ctl_new1(ctls + i, chip));
if (err < 0)
return err;
}
return 0;
}
int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm)
{
int err;
struct snd_card *card = chip->card;
if (!nopcm) {
err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_pcm,
ARRAY_SIZE(snd_pcsp_controls_pcm));
if (err < 0)
return err;
}
err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_spkr,
ARRAY_SIZE(snd_pcsp_controls_spkr));
if (err < 0)
return err;
strscpy(card->mixername, "PC-Speaker");
return 0;
}
+841
View File
@@ -0,0 +1,841 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Midiman Portman2x4 parallel port midi interface
*
* Copyright (c) by Levent Guendogdu <levon@feature-it.com>
*
* ChangeLog
* Jan 24 2007 Matthias Koenig <mkoenig@suse.de>
* - cleanup and rewrite
* Sep 30 2004 Tobias Gehrig <tobias@gehrig.tk>
* - source code cleanup
* Sep 03 2004 Tobias Gehrig <tobias@gehrig.tk>
* - fixed compilation problem with alsa 1.0.6a (removed MODULE_CLASSES,
* MODULE_PARM_SYNTAX and changed MODULE_DEVICES to
* MODULE_SUPPORTED_DEVICE)
* Mar 24 2004 Tobias Gehrig <tobias@gehrig.tk>
* - added 2.6 kernel support
* Mar 18 2004 Tobias Gehrig <tobias@gehrig.tk>
* - added parport_unregister_driver to the startup routine if the driver fails to detect a portman
* - added support for all 4 output ports in portman_putmidi
* Mar 17 2004 Tobias Gehrig <tobias@gehrig.tk>
* - added checks for opened input device in interrupt handler
* Feb 20 2004 Tobias Gehrig <tobias@gehrig.tk>
* - ported from alsa 0.5 to 1.0
*/
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/parport.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include <sound/control.h>
#define CARD_NAME "Portman 2x4"
#define DRIVER_NAME "portman"
#define PLATFORM_DRIVER "snd_portman2x4"
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
static struct platform_device *platform_devices[SNDRV_CARDS];
static int device_count;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
MODULE_AUTHOR("Levent Guendogdu, Tobias Gehrig, Matthias Koenig");
MODULE_DESCRIPTION("Midiman Portman2x4");
MODULE_LICENSE("GPL");
/*********************************************************************
* Chip specific
*********************************************************************/
#define PORTMAN_NUM_INPUT_PORTS 2
#define PORTMAN_NUM_OUTPUT_PORTS 4
struct portman {
spinlock_t reg_lock;
struct snd_card *card;
struct snd_rawmidi *rmidi;
struct pardevice *pardev;
int open_count;
int mode[PORTMAN_NUM_INPUT_PORTS];
struct snd_rawmidi_substream *midi_input[PORTMAN_NUM_INPUT_PORTS];
};
static int portman_free(struct portman *pm)
{
kfree(pm);
return 0;
}
static int portman_create(struct snd_card *card,
struct pardevice *pardev,
struct portman **rchip)
{
struct portman *pm;
*rchip = NULL;
pm = kzalloc_obj(struct portman);
if (pm == NULL)
return -ENOMEM;
/* Init chip specific data */
spin_lock_init(&pm->reg_lock);
pm->card = card;
pm->pardev = pardev;
*rchip = pm;
return 0;
}
/*********************************************************************
* HW related constants
*********************************************************************/
/* Standard PC parallel port status register equates. */
#define PP_STAT_BSY 0x80 /* Busy status. Inverted. */
#define PP_STAT_ACK 0x40 /* Acknowledge. Non-Inverted. */
#define PP_STAT_POUT 0x20 /* Paper Out. Non-Inverted. */
#define PP_STAT_SEL 0x10 /* Select. Non-Inverted. */
#define PP_STAT_ERR 0x08 /* Error. Non-Inverted. */
/* Standard PC parallel port command register equates. */
#define PP_CMD_IEN 0x10 /* IRQ Enable. Non-Inverted. */
#define PP_CMD_SELI 0x08 /* Select Input. Inverted. */
#define PP_CMD_INIT 0x04 /* Init Printer. Non-Inverted. */
#define PP_CMD_FEED 0x02 /* Auto Feed. Inverted. */
#define PP_CMD_STB 0x01 /* Strobe. Inverted. */
/* Parallel Port Command Register as implemented by PCP2x4. */
#define INT_EN PP_CMD_IEN /* Interrupt enable. */
#define STROBE PP_CMD_STB /* Command strobe. */
/* The parallel port command register field (b1..b3) selects the
* various "registers" within the PC/P 2x4. These are the internal
* address of these "registers" that must be written to the parallel
* port command register.
*/
#define RXDATA0 (0 << 1) /* PCP RxData channel 0. */
#define RXDATA1 (1 << 1) /* PCP RxData channel 1. */
#define GEN_CTL (2 << 1) /* PCP General Control Register. */
#define SYNC_CTL (3 << 1) /* PCP Sync Control Register. */
#define TXDATA0 (4 << 1) /* PCP TxData channel 0. */
#define TXDATA1 (5 << 1) /* PCP TxData channel 1. */
#define TXDATA2 (6 << 1) /* PCP TxData channel 2. */
#define TXDATA3 (7 << 1) /* PCP TxData channel 3. */
/* Parallel Port Status Register as implemented by PCP2x4. */
#define ESTB PP_STAT_POUT /* Echoed strobe. */
#define INT_REQ PP_STAT_ACK /* Input data int request. */
#define BUSY PP_STAT_ERR /* Interface Busy. */
/* Parallel Port Status Register BUSY and SELECT lines are multiplexed
* between several functions. Depending on which 2x4 "register" is
* currently selected (b1..b3), the BUSY and SELECT lines are
* assigned as follows:
*
* SELECT LINE: A3 A2 A1
* --------
*/
#define RXAVAIL PP_STAT_SEL /* Rx Available, channel 0. 0 0 0 */
// RXAVAIL1 PP_STAT_SEL /* Rx Available, channel 1. 0 0 1 */
#define SYNC_STAT PP_STAT_SEL /* Reserved - Sync Status. 0 1 0 */
// /* Reserved. 0 1 1 */
#define TXEMPTY PP_STAT_SEL /* Tx Empty, channel 0. 1 0 0 */
// TXEMPTY1 PP_STAT_SEL /* Tx Empty, channel 1. 1 0 1 */
// TXEMPTY2 PP_STAT_SEL /* Tx Empty, channel 2. 1 1 0 */
// TXEMPTY3 PP_STAT_SEL /* Tx Empty, channel 3. 1 1 1 */
/* BUSY LINE: A3 A2 A1
* --------
*/
#define RXDATA PP_STAT_BSY /* Rx Input Data, channel 0. 0 0 0 */
// RXDATA1 PP_STAT_BSY /* Rx Input Data, channel 1. 0 0 1 */
#define SYNC_DATA PP_STAT_BSY /* Reserved - Sync Data. 0 1 0 */
/* Reserved. 0 1 1 */
#define DATA_ECHO PP_STAT_BSY /* Parallel Port Data Echo. 1 0 0 */
#define A0_ECHO PP_STAT_BSY /* Address 0 Echo. 1 0 1 */
#define A1_ECHO PP_STAT_BSY /* Address 1 Echo. 1 1 0 */
#define A2_ECHO PP_STAT_BSY /* Address 2 Echo. 1 1 1 */
#define PORTMAN2X4_MODE_INPUT_TRIGGERED 0x01
/*********************************************************************
* Hardware specific functions
*********************************************************************/
static inline void portman_write_command(struct portman *pm, u8 value)
{
parport_write_control(pm->pardev->port, value);
}
static inline u8 portman_read_status(struct portman *pm)
{
return parport_read_status(pm->pardev->port);
}
static inline void portman_write_data(struct portman *pm, u8 value)
{
parport_write_data(pm->pardev->port, value);
}
static void portman_write_midi(struct portman *pm,
int port, u8 mididata)
{
int command = ((port + 4) << 1);
/* Get entering data byte and port number in BL and BH respectively.
* Set up Tx Channel address field for use with PP Cmd Register.
* Store address field in BH register.
* Inputs: AH = Output port number (0..3).
* AL = Data byte.
* command = TXDATA0 | INT_EN;
* Align port num with address field (b1...b3),
* set address for TXDatax, Strobe=0
*/
command |= INT_EN;
/* Disable interrupts so that the process is not interrupted, then
* write the address associated with the current Tx channel to the
* PP Command Reg. Do not set the Strobe signal yet.
*/
do {
portman_write_command(pm, command);
/* While the address lines settle, write parallel output data to
* PP Data Reg. This has no effect until Strobe signal is asserted.
*/
portman_write_data(pm, mididata);
/* If PCP channel's TxEmpty is set (TxEmpty is read through the PP
* Status Register), then go write data. Else go back and wait.
*/
} while ((portman_read_status(pm) & TXEMPTY) != TXEMPTY);
/* TxEmpty is set. Maintain PC/P destination address and assert
* Strobe through the PP Command Reg. This will Strobe data into
* the PC/P transmitter and set the PC/P BUSY signal.
*/
portman_write_command(pm, command | STROBE);
/* Wait for strobe line to settle and echo back through hardware.
* Once it has echoed back, assume that the address and data lines
* have settled!
*/
while ((portman_read_status(pm) & ESTB) == 0)
cpu_relax();
/* Release strobe and immediately re-allow interrupts. */
portman_write_command(pm, command);
while ((portman_read_status(pm) & ESTB) == ESTB)
cpu_relax();
/* PC/P BUSY is now set. We must wait until BUSY resets itself.
* We'll reenable ints while we're waiting.
*/
while ((portman_read_status(pm) & BUSY) == BUSY)
cpu_relax();
/* Data sent. */
}
/*
* Read MIDI byte from port
* Attempt to read input byte from specified hardware input port (0..).
* Return -1 if no data
*/
static int portman_read_midi(struct portman *pm, int port)
{
unsigned char midi_data = 0;
unsigned char cmdout; /* Saved address+IE bit. */
/* Make sure clocking edge is down before starting... */
portman_write_data(pm, 0); /* Make sure edge is down. */
/* Set destination address to PCP. */
cmdout = (port << 1) | INT_EN; /* Address + IE + No Strobe. */
portman_write_command(pm, cmdout);
while ((portman_read_status(pm) & ESTB) == ESTB)
cpu_relax(); /* Wait for strobe echo. */
/* After the address lines settle, check multiplexed RxAvail signal.
* If data is available, read it.
*/
if ((portman_read_status(pm) & RXAVAIL) == 0)
return -1; /* No data. */
/* Set the Strobe signal to enable the Rx clocking circuitry. */
portman_write_command(pm, cmdout | STROBE); /* Write address+IE+Strobe. */
while ((portman_read_status(pm) & ESTB) == 0)
cpu_relax(); /* Wait for strobe echo. */
/* The first data bit (msb) is already sitting on the input line. */
midi_data = (portman_read_status(pm) & 128);
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
/* Data bit 6. */
portman_write_data(pm, 0); /* Cause falling edge while data settles. */
midi_data |= (portman_read_status(pm) >> 1) & 64;
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
/* Data bit 5. */
portman_write_data(pm, 0); /* Cause falling edge while data settles. */
midi_data |= (portman_read_status(pm) >> 2) & 32;
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
/* Data bit 4. */
portman_write_data(pm, 0); /* Cause falling edge while data settles. */
midi_data |= (portman_read_status(pm) >> 3) & 16;
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
/* Data bit 3. */
portman_write_data(pm, 0); /* Cause falling edge while data settles. */
midi_data |= (portman_read_status(pm) >> 4) & 8;
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
/* Data bit 2. */
portman_write_data(pm, 0); /* Cause falling edge while data settles. */
midi_data |= (portman_read_status(pm) >> 5) & 4;
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
/* Data bit 1. */
portman_write_data(pm, 0); /* Cause falling edge while data settles. */
midi_data |= (portman_read_status(pm) >> 6) & 2;
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
/* Data bit 0. */
portman_write_data(pm, 0); /* Cause falling edge while data settles. */
midi_data |= (portman_read_status(pm) >> 7) & 1;
portman_write_data(pm, 1); /* Cause rising edge, which shifts data. */
portman_write_data(pm, 0); /* Return data clock low. */
/* De-assert Strobe and return data. */
portman_write_command(pm, cmdout); /* Output saved address+IE. */
/* Wait for strobe echo. */
while ((portman_read_status(pm) & ESTB) == ESTB)
cpu_relax();
return (midi_data & 255); /* Shift back and return value. */
}
/*
* Checks if any input data on the given channel is available
* Checks RxAvail
*/
static int portman_data_avail(struct portman *pm, int channel)
{
int command = INT_EN;
switch (channel) {
case 0:
command |= RXDATA0;
break;
case 1:
command |= RXDATA1;
break;
}
/* Write hardware (assumme STROBE=0) */
portman_write_command(pm, command);
/* Check multiplexed RxAvail signal */
if ((portman_read_status(pm) & RXAVAIL) == RXAVAIL)
return 1; /* Data available */
/* No Data available */
return 0;
}
/*
* Flushes any input
*/
static void portman_flush_input(struct portman *pm, unsigned char port)
{
/* Local variable for counting things */
unsigned int i = 0;
unsigned char command = 0;
switch (port) {
case 0:
command = RXDATA0;
break;
case 1:
command = RXDATA1;
break;
default:
dev_warn(pm->card->dev, "%s Won't flush port %i\n",
__func__, port);
return;
}
/* Set address for specified channel in port and allow to settle. */
portman_write_command(pm, command);
/* Assert the Strobe and wait for echo back. */
portman_write_command(pm, command | STROBE);
/* Wait for ESTB */
while ((portman_read_status(pm) & ESTB) == 0)
cpu_relax();
/* Output clock cycles to the Rx circuitry. */
portman_write_data(pm, 0);
/* Flush 250 bits... */
for (i = 0; i < 250; i++) {
portman_write_data(pm, 1);
portman_write_data(pm, 0);
}
/* Deassert the Strobe signal of the port and wait for it to settle. */
portman_write_command(pm, command | INT_EN);
/* Wait for settling */
while ((portman_read_status(pm) & ESTB) == ESTB)
cpu_relax();
}
static int portman_probe(struct parport *p)
{
/* Initialize the parallel port data register. Will set Rx clocks
* low in case we happen to be addressing the Rx ports at this time.
*/
/* 1 */
parport_write_data(p, 0);
/* Initialize the parallel port command register, thus initializing
* hardware handshake lines to midi box:
*
* Strobe = 0
* Interrupt Enable = 0
*/
/* 2 */
parport_write_control(p, 0);
/* Check if Portman PC/P 2x4 is out there. */
/* 3 */
parport_write_control(p, RXDATA0); /* Write Strobe=0 to command reg. */
/* Check for ESTB to be clear */
/* 4 */
if ((parport_read_status(p) & ESTB) == ESTB)
return 1; /* CODE 1 - Strobe Failure. */
/* Set for RXDATA0 where no damage will be done. */
/* 5 */
parport_write_control(p, RXDATA0 | STROBE); /* Write Strobe=1 to command reg. */
/* 6 */
if ((parport_read_status(p) & ESTB) != ESTB)
return 1; /* CODE 1 - Strobe Failure. */
/* 7 */
parport_write_control(p, 0); /* Reset Strobe=0. */
/* Check if Tx circuitry is functioning properly. If initialized
* unit TxEmpty is false, send out char and see if it goes true.
*/
/* 8 */
parport_write_control(p, TXDATA0); /* Tx channel 0, strobe off. */
/* If PCP channel's TxEmpty is set (TxEmpty is read through the PP
* Status Register), then go write data. Else go back and wait.
*/
/* 9 */
if ((parport_read_status(p) & TXEMPTY) == 0)
return 2;
/* Return OK status. */
return 0;
}
static int portman_device_init(struct portman *pm)
{
portman_flush_input(pm, 0);
portman_flush_input(pm, 1);
return 0;
}
/*********************************************************************
* Rawmidi
*********************************************************************/
static int snd_portman_midi_open(struct snd_rawmidi_substream *substream)
{
return 0;
}
static int snd_portman_midi_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
static void snd_portman_midi_input_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct portman *pm = substream->rmidi->private_data;
guard(spinlock_irqsave)(&pm->reg_lock);
if (up)
pm->mode[substream->number] |= PORTMAN2X4_MODE_INPUT_TRIGGERED;
else
pm->mode[substream->number] &= ~PORTMAN2X4_MODE_INPUT_TRIGGERED;
}
static void snd_portman_midi_output_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct portman *pm = substream->rmidi->private_data;
unsigned char byte;
guard(spinlock_irqsave)(&pm->reg_lock);
if (up) {
while ((snd_rawmidi_transmit(substream, &byte, 1) == 1))
portman_write_midi(pm, substream->number, byte);
}
}
static const struct snd_rawmidi_ops snd_portman_midi_output = {
.open = snd_portman_midi_open,
.close = snd_portman_midi_close,
.trigger = snd_portman_midi_output_trigger,
};
static const struct snd_rawmidi_ops snd_portman_midi_input = {
.open = snd_portman_midi_open,
.close = snd_portman_midi_close,
.trigger = snd_portman_midi_input_trigger,
};
/* Create and initialize the rawmidi component */
static int snd_portman_rawmidi_create(struct snd_card *card)
{
struct portman *pm = card->private_data;
struct snd_rawmidi *rmidi;
struct snd_rawmidi_substream *substream;
int err;
err = snd_rawmidi_new(card, CARD_NAME, 0,
PORTMAN_NUM_OUTPUT_PORTS,
PORTMAN_NUM_INPUT_PORTS,
&rmidi);
if (err < 0)
return err;
rmidi->private_data = pm;
strscpy(rmidi->name, CARD_NAME);
rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
pm->rmidi = rmidi;
/* register rawmidi ops */
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&snd_portman_midi_output);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&snd_portman_midi_input);
/* name substreams */
/* output */
list_for_each_entry(substream,
&rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams,
list) {
sprintf(substream->name,
"Portman2x4 %d", substream->number+1);
}
/* input */
list_for_each_entry(substream,
&rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams,
list) {
pm->midi_input[substream->number] = substream;
sprintf(substream->name,
"Portman2x4 %d", substream->number+1);
}
return err;
}
/*********************************************************************
* parport stuff
*********************************************************************/
static void snd_portman_interrupt(void *userdata)
{
unsigned char midivalue = 0;
struct portman *pm = ((struct snd_card*)userdata)->private_data;
guard(spinlock)(&pm->reg_lock);
/* While any input data is waiting */
while ((portman_read_status(pm) & INT_REQ) == INT_REQ) {
/* If data available on channel 0,
read it and stuff it into the queue. */
if (portman_data_avail(pm, 0)) {
/* Read Midi */
midivalue = portman_read_midi(pm, 0);
/* put midi into queue... */
if (pm->mode[0] & PORTMAN2X4_MODE_INPUT_TRIGGERED)
snd_rawmidi_receive(pm->midi_input[0],
&midivalue, 1);
}
/* If data available on channel 1,
read it and stuff it into the queue. */
if (portman_data_avail(pm, 1)) {
/* Read Midi */
midivalue = portman_read_midi(pm, 1);
/* put midi into queue... */
if (pm->mode[1] & PORTMAN2X4_MODE_INPUT_TRIGGERED)
snd_rawmidi_receive(pm->midi_input[1],
&midivalue, 1);
}
}
}
static void snd_portman_attach(struct parport *p)
{
struct platform_device *device;
device = platform_device_alloc(PLATFORM_DRIVER, device_count);
if (!device)
return;
/* Temporary assignment to forward the parport */
platform_set_drvdata(device, p);
if (platform_device_add(device) < 0) {
platform_device_put(device);
return;
}
/* Since we dont get the return value of probe
* We need to check if device probing succeeded or not */
if (!platform_get_drvdata(device)) {
platform_device_unregister(device);
return;
}
/* register device in global table */
platform_devices[device_count] = device;
device_count++;
}
static void snd_portman_detach(struct parport *p)
{
/* nothing to do here */
}
static int snd_portman_dev_probe(struct pardevice *pardev)
{
if (strcmp(pardev->name, DRIVER_NAME))
return -ENODEV;
return 0;
}
static struct parport_driver portman_parport_driver = {
.name = "portman2x4",
.probe = snd_portman_dev_probe,
.match_port = snd_portman_attach,
.detach = snd_portman_detach,
};
/*********************************************************************
* platform stuff
*********************************************************************/
static void snd_portman_card_private_free(struct snd_card *card)
{
struct portman *pm = card->private_data;
struct pardevice *pardev = pm->pardev;
if (pardev) {
parport_release(pardev);
parport_unregister_device(pardev);
}
portman_free(pm);
}
static int snd_portman_probe(struct platform_device *pdev)
{
struct pardevice *pardev;
struct parport *p;
int dev = pdev->id;
struct snd_card *card = NULL;
struct portman *pm = NULL;
int err;
struct pardev_cb portman_cb = {
.preempt = NULL,
.wakeup = NULL,
.irq_func = snd_portman_interrupt, /* ISR */
.flags = PARPORT_DEV_EXCL, /* flags */
};
p = platform_get_drvdata(pdev);
platform_set_drvdata(pdev, NULL);
if (dev >= SNDRV_CARDS)
return -ENODEV;
if (!enable[dev])
return -ENOENT;
err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0) {
dev_dbg(&pdev->dev, "Cannot create card\n");
return err;
}
strscpy(card->driver, DRIVER_NAME);
strscpy(card->shortname, CARD_NAME);
sprintf(card->longname, "%s at 0x%lx, irq %i",
card->shortname, p->base, p->irq);
portman_cb.private = card; /* private */
pardev = parport_register_dev_model(p, /* port */
DRIVER_NAME, /* name */
&portman_cb, /* callbacks */
pdev->id); /* device number */
if (pardev == NULL) {
dev_dbg(card->dev, "Cannot register pardevice\n");
err = -EIO;
goto __err;
}
/* claim parport */
if (parport_claim(pardev)) {
dev_dbg(card->dev, "Cannot claim parport 0x%lx\n", pardev->port->base);
err = -EIO;
goto free_pardev;
}
err = portman_create(card, pardev, &pm);
if (err < 0) {
dev_dbg(card->dev, "Cannot create main component\n");
goto release_pardev;
}
card->private_data = pm;
card->private_free = snd_portman_card_private_free;
err = portman_probe(p);
if (err) {
err = -EIO;
goto __err;
}
err = snd_portman_rawmidi_create(card);
if (err < 0) {
dev_dbg(card->dev, "Creating Rawmidi component failed\n");
goto __err;
}
/* init device */
err = portman_device_init(pm);
if (err < 0)
goto __err;
platform_set_drvdata(pdev, card);
/* At this point card will be usable */
err = snd_card_register(card);
if (err < 0) {
dev_dbg(card->dev, "Cannot register card\n");
goto __err;
}
dev_info(card->dev, "Portman 2x4 on 0x%lx\n", p->base);
return 0;
release_pardev:
parport_release(pardev);
free_pardev:
parport_unregister_device(pardev);
__err:
snd_card_free(card);
return err;
}
static void snd_portman_remove(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
if (card)
snd_card_free(card);
}
static struct platform_driver snd_portman_driver = {
.probe = snd_portman_probe,
.remove = snd_portman_remove,
.driver = {
.name = PLATFORM_DRIVER,
}
};
/*********************************************************************
* module init stuff
*********************************************************************/
static void snd_portman_unregister_all(void)
{
int i;
for (i = 0; i < SNDRV_CARDS; ++i) {
if (platform_devices[i]) {
platform_device_unregister(platform_devices[i]);
platform_devices[i] = NULL;
}
}
platform_driver_unregister(&snd_portman_driver);
parport_unregister_driver(&portman_parport_driver);
}
static int __init snd_portman_module_init(void)
{
int err;
err = platform_driver_register(&snd_portman_driver);
if (err < 0)
return err;
if (parport_register_driver(&portman_parport_driver) != 0) {
platform_driver_unregister(&snd_portman_driver);
return -EIO;
}
if (device_count == 0) {
snd_portman_unregister_all();
return -ENODEV;
}
return 0;
}
static void __exit snd_portman_module_exit(void)
{
snd_portman_unregister_all();
}
module_init(snd_portman_module_init);
module_exit(snd_portman_module_exit);
+376
View File
@@ -0,0 +1,376 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* serial-generic.c
* Copyright (c) by Daniel Kaehn <kaehndan@gmail.com
* Based on serial-u16550.c by Jaroslav Kysela <perex@perex.cz>,
* Isaku Yamahata <yamahata@private.email.ne.jp>,
* George Hansper <ghansper@apana.org.au>,
* Hannu Savolainen
*
* Generic serial MIDI driver using the serdev serial bus API for hardware interaction
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/serdev.h>
#include <linux/serial_reg.h>
#include <linux/slab.h>
#include <linux/dev_printk.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/initval.h>
MODULE_DESCRIPTION("Generic serial MIDI driver");
MODULE_LICENSE("GPL");
#define SERIAL_MODE_INPUT_OPEN 1
#define SERIAL_MODE_OUTPUT_OPEN 2
#define SERIAL_MODE_INPUT_TRIGGERED 3
#define SERIAL_MODE_OUTPUT_TRIGGERED 4
#define SERIAL_TX_STATE_ACTIVE 1
#define SERIAL_TX_STATE_WAKEUP 2
#define INTERNAL_BUF_SIZE 256
struct snd_serial_generic {
struct serdev_device *serdev;
struct snd_card *card;
struct snd_rawmidi *rmidi;
struct snd_rawmidi_substream *midi_output;
struct snd_rawmidi_substream *midi_input;
unsigned int baudrate;
unsigned long filemode; /* open status of file */
struct work_struct tx_work;
unsigned long tx_state;
char tx_buf[INTERNAL_BUF_SIZE];
};
static void snd_serial_generic_tx_wakeup(struct snd_serial_generic *drvdata)
{
if (test_and_set_bit(SERIAL_TX_STATE_ACTIVE, &drvdata->tx_state))
set_bit(SERIAL_TX_STATE_WAKEUP, &drvdata->tx_state);
schedule_work(&drvdata->tx_work);
}
static void snd_serial_generic_tx_work(struct work_struct *work)
{
int num_bytes;
struct snd_serial_generic *drvdata = container_of(work, struct snd_serial_generic,
tx_work);
struct snd_rawmidi_substream *substream = drvdata->midi_output;
clear_bit(SERIAL_TX_STATE_WAKEUP, &drvdata->tx_state);
while (!snd_rawmidi_transmit_empty(substream)) {
if (!test_bit(SERIAL_MODE_OUTPUT_OPEN, &drvdata->filemode))
break;
num_bytes = snd_rawmidi_transmit_peek(substream, drvdata->tx_buf,
INTERNAL_BUF_SIZE);
num_bytes = serdev_device_write_buf(drvdata->serdev, drvdata->tx_buf,
num_bytes);
if (!num_bytes)
break;
snd_rawmidi_transmit_ack(substream, num_bytes);
if (!test_bit(SERIAL_TX_STATE_WAKEUP, &drvdata->tx_state))
break;
}
clear_bit(SERIAL_TX_STATE_ACTIVE, &drvdata->tx_state);
}
static void snd_serial_generic_write_wakeup(struct serdev_device *serdev)
{
struct snd_serial_generic *drvdata = serdev_device_get_drvdata(serdev);
snd_serial_generic_tx_wakeup(drvdata);
}
static size_t snd_serial_generic_receive_buf(struct serdev_device *serdev,
const u8 *buf, size_t count)
{
int ret;
struct snd_serial_generic *drvdata = serdev_device_get_drvdata(serdev);
if (!test_bit(SERIAL_MODE_INPUT_OPEN, &drvdata->filemode))
return 0;
ret = snd_rawmidi_receive(drvdata->midi_input, buf, count);
return ret < 0 ? 0 : ret;
}
static const struct serdev_device_ops snd_serial_generic_serdev_device_ops = {
.receive_buf = snd_serial_generic_receive_buf,
.write_wakeup = snd_serial_generic_write_wakeup
};
static int snd_serial_generic_ensure_serdev_open(struct snd_serial_generic *drvdata)
{
int err;
unsigned int actual_baud;
if (drvdata->filemode)
return 0;
dev_dbg(drvdata->card->dev, "Opening serial port for card %s\n",
drvdata->card->shortname);
err = serdev_device_open(drvdata->serdev);
if (err < 0)
return err;
actual_baud = serdev_device_set_baudrate(drvdata->serdev,
drvdata->baudrate);
if (actual_baud != drvdata->baudrate) {
dev_warn(drvdata->card->dev, "requested %d baud for card %s but it was actually set to %d\n",
drvdata->baudrate, drvdata->card->shortname, actual_baud);
}
return 0;
}
static int snd_serial_generic_input_open(struct snd_rawmidi_substream *substream)
{
int err;
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
dev_dbg(drvdata->card->dev, "Opening input for card %s\n",
drvdata->card->shortname);
err = snd_serial_generic_ensure_serdev_open(drvdata);
if (err < 0)
return err;
set_bit(SERIAL_MODE_INPUT_OPEN, &drvdata->filemode);
drvdata->midi_input = substream;
return 0;
}
static int snd_serial_generic_input_close(struct snd_rawmidi_substream *substream)
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
dev_dbg(drvdata->card->dev, "Closing input for card %s\n",
drvdata->card->shortname);
clear_bit(SERIAL_MODE_INPUT_OPEN, &drvdata->filemode);
clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode);
drvdata->midi_input = NULL;
if (!drvdata->filemode)
serdev_device_close(drvdata->serdev);
return 0;
}
static void snd_serial_generic_input_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
if (up)
set_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode);
else
clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode);
}
static int snd_serial_generic_output_open(struct snd_rawmidi_substream *substream)
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
int err;
dev_dbg(drvdata->card->dev, "Opening output for card %s\n",
drvdata->card->shortname);
err = snd_serial_generic_ensure_serdev_open(drvdata);
if (err < 0)
return err;
set_bit(SERIAL_MODE_OUTPUT_OPEN, &drvdata->filemode);
drvdata->midi_output = substream;
return 0;
};
static int snd_serial_generic_output_close(struct snd_rawmidi_substream *substream)
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
dev_dbg(drvdata->card->dev, "Closing output for card %s\n",
drvdata->card->shortname);
clear_bit(SERIAL_MODE_OUTPUT_OPEN, &drvdata->filemode);
clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode);
if (!drvdata->filemode)
serdev_device_close(drvdata->serdev);
drvdata->midi_output = NULL;
return 0;
};
static void snd_serial_generic_output_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
if (up)
set_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode);
else
clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode);
if (up)
snd_serial_generic_tx_wakeup(drvdata);
}
static void snd_serial_generic_output_drain(struct snd_rawmidi_substream *substream)
{
struct snd_serial_generic *drvdata = substream->rmidi->card->private_data;
/* Flush any pending characters */
serdev_device_write_flush(drvdata->serdev);
cancel_work_sync(&drvdata->tx_work);
}
static const struct snd_rawmidi_ops snd_serial_generic_output = {
.open = snd_serial_generic_output_open,
.close = snd_serial_generic_output_close,
.trigger = snd_serial_generic_output_trigger,
.drain = snd_serial_generic_output_drain,
};
static const struct snd_rawmidi_ops snd_serial_generic_input = {
.open = snd_serial_generic_input_open,
.close = snd_serial_generic_input_close,
.trigger = snd_serial_generic_input_trigger,
};
static void snd_serial_generic_parse_dt(struct serdev_device *serdev,
struct snd_serial_generic *drvdata)
{
int err;
err = of_property_read_u32(serdev->dev.of_node, "current-speed",
&drvdata->baudrate);
if (err < 0) {
dev_dbg(drvdata->card->dev,
"MIDI device reading of current-speed DT param failed with error %d, using default of 38400\n",
err);
drvdata->baudrate = 38400;
}
}
static void snd_serial_generic_substreams(struct snd_rawmidi_str *stream, int dev_num)
{
struct snd_rawmidi_substream *substream;
list_for_each_entry(substream, &stream->substreams, list) {
sprintf(substream->name, "Serial MIDI %d-%d", dev_num, substream->number);
}
}
static int snd_serial_generic_rmidi(struct snd_serial_generic *drvdata,
int outs, int ins, struct snd_rawmidi **rmidi)
{
struct snd_rawmidi *rrawmidi;
int err;
err = snd_rawmidi_new(drvdata->card, drvdata->card->driver, 0,
outs, ins, &rrawmidi);
if (err < 0)
return err;
snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&snd_serial_generic_input);
snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&snd_serial_generic_output);
strscpy(rrawmidi->name, drvdata->card->shortname);
snd_serial_generic_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT],
drvdata->serdev->ctrl->nr);
snd_serial_generic_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT],
drvdata->serdev->ctrl->nr);
rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
if (rmidi)
*rmidi = rrawmidi;
return 0;
}
static int snd_serial_generic_probe(struct serdev_device *serdev)
{
struct snd_card *card;
struct snd_serial_generic *drvdata;
int err;
err = snd_devm_card_new(&serdev->dev, SNDRV_DEFAULT_IDX1,
SNDRV_DEFAULT_STR1, THIS_MODULE,
sizeof(struct snd_serial_generic), &card);
if (err < 0)
return err;
strscpy(card->driver, "SerialMIDI");
sprintf(card->shortname, "SerialMIDI-%d", serdev->ctrl->nr);
sprintf(card->longname, "Serial MIDI device at serial%d", serdev->ctrl->nr);
drvdata = card->private_data;
drvdata->serdev = serdev;
drvdata->card = card;
snd_serial_generic_parse_dt(serdev, drvdata);
INIT_WORK(&drvdata->tx_work, snd_serial_generic_tx_work);
err = snd_serial_generic_rmidi(drvdata, 1, 1, &drvdata->rmidi);
if (err < 0)
return err;
serdev_device_set_client_ops(serdev, &snd_serial_generic_serdev_device_ops);
serdev_device_set_drvdata(drvdata->serdev, drvdata);
err = snd_card_register(card);
if (err < 0)
return err;
return 0;
}
static const struct of_device_id snd_serial_generic_dt_ids[] = {
{ .compatible = "serial-midi" },
{},
};
MODULE_DEVICE_TABLE(of, snd_serial_generic_dt_ids);
static struct serdev_device_driver snd_serial_generic_driver = {
.driver = {
.name = "snd-serial-generic",
.of_match_table = snd_serial_generic_dt_ids,
},
.probe = snd_serial_generic_probe,
};
module_serdev_device_driver(snd_serial_generic_driver);
+975
View File
@@ -0,0 +1,975 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* serial.c
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>,
* Isaku Yamahata <yamahata@private.email.ne.jp>,
* George Hansper <ghansper@apana.org.au>,
* Hannu Savolainen
*
* This code is based on the code from ALSA 0.5.9, but heavily rewritten.
*
* Sat Mar 31 17:27:57 PST 2001 tim.mann@compaq.com
* Added support for the Midiator MS-124T and for the MS-124W in
* Single Addressed (S/A) or Multiple Burst (M/B) mode, with
* power derived either parasitically from the serial port or
* from a separate power supply.
*
* More documentation can be found in serial-u16550.txt.
*/
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/rawmidi.h>
#include <sound/initval.h>
#include <linux/serial_reg.h>
#include <linux/jiffies.h>
MODULE_DESCRIPTION("MIDI serial u16550");
MODULE_LICENSE("GPL");
#define SNDRV_SERIAL_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */
#define SNDRV_SERIAL_MS124T 1 /* Midiator MS-124T */
#define SNDRV_SERIAL_MS124W_SA 2 /* Midiator MS-124W in S/A mode */
#define SNDRV_SERIAL_MS124W_MB 3 /* Midiator MS-124W in M/B mode */
#define SNDRV_SERIAL_GENERIC 4 /* Generic Interface */
#define SNDRV_SERIAL_MAX_ADAPTOR SNDRV_SERIAL_GENERIC
static const char * const adaptor_names[] = {
"Soundcanvas",
"MS-124T",
"MS-124W S/A",
"MS-124W M/B",
"Generic"
};
#define SNDRV_SERIAL_NORMALBUFF 0 /* Normal blocking buffer operation */
#define SNDRV_SERIAL_DROPBUFF 1 /* Non-blocking discard operation */
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; /* 0x3f8,0x2f8,0x3e8,0x2e8 */
static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,4,5,7,9,10,11,14,15 */
static int speed[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 38400}; /* 9600,19200,38400,57600,115200 */
static int base[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 115200}; /* baud base */
static int outs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */
static int ins[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */
static int adaptor[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = SNDRV_SERIAL_SOUNDCANVAS};
static bool droponfull[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS -1)] = SNDRV_SERIAL_NORMALBUFF };
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for Serial MIDI.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for Serial MIDI.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable UART16550A chip.");
module_param_hw_array(port, long, ioport, NULL, 0444);
MODULE_PARM_DESC(port, "Port # for UART16550A chip.");
module_param_hw_array(irq, int, irq, NULL, 0444);
MODULE_PARM_DESC(irq, "IRQ # for UART16550A chip.");
module_param_array(speed, int, NULL, 0444);
MODULE_PARM_DESC(speed, "Speed in bauds.");
module_param_array(base, int, NULL, 0444);
MODULE_PARM_DESC(base, "Base for divisor in bauds.");
module_param_array(outs, int, NULL, 0444);
MODULE_PARM_DESC(outs, "Number of MIDI outputs.");
module_param_array(ins, int, NULL, 0444);
MODULE_PARM_DESC(ins, "Number of MIDI inputs.");
module_param_array(droponfull, bool, NULL, 0444);
MODULE_PARM_DESC(droponfull, "Flag to enable drop-on-full buffer mode");
module_param_array(adaptor, int, NULL, 0444);
MODULE_PARM_DESC(adaptor, "Type of adaptor.");
/*#define SNDRV_SERIAL_MS124W_MB_NOCOMBO 1*/ /* Address outs as 0-3 instead of bitmap */
#define SNDRV_SERIAL_MAX_OUTS 16 /* max 64, min 16 */
#define SNDRV_SERIAL_MAX_INS 16 /* max 64, min 16 */
#define TX_BUFF_SIZE (1<<15) /* Must be 2^n */
#define TX_BUFF_MASK (TX_BUFF_SIZE - 1)
#define SERIAL_MODE_NOT_OPENED (0)
#define SERIAL_MODE_INPUT_OPEN (1 << 0)
#define SERIAL_MODE_OUTPUT_OPEN (1 << 1)
#define SERIAL_MODE_INPUT_TRIGGERED (1 << 2)
#define SERIAL_MODE_OUTPUT_TRIGGERED (1 << 3)
struct snd_uart16550 {
struct snd_card *card;
struct snd_rawmidi *rmidi;
struct snd_rawmidi_substream *midi_output[SNDRV_SERIAL_MAX_OUTS];
struct snd_rawmidi_substream *midi_input[SNDRV_SERIAL_MAX_INS];
int filemode; /* open status of file */
spinlock_t open_lock;
int irq;
unsigned long base;
unsigned int speed;
unsigned int speed_base;
unsigned char divisor;
unsigned char old_divisor_lsb;
unsigned char old_divisor_msb;
unsigned char old_line_ctrl_reg;
/* parameter for using of write loop */
short int fifo_limit; /* used in uart16550 */
short int fifo_count; /* used in uart16550 */
/* type of adaptor */
int adaptor;
/* inputs */
int prev_in;
unsigned char rstatus;
/* outputs */
int prev_out;
unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS];
/* write buffer and its writing/reading position */
unsigned char tx_buff[TX_BUFF_SIZE];
int buff_in_count;
int buff_in;
int buff_out;
int drop_on_full;
/* wait timer */
unsigned int timer_running:1;
struct timer_list buffer_timer;
};
static struct platform_device *devices[SNDRV_CARDS];
static inline void snd_uart16550_add_timer(struct snd_uart16550 *uart)
{
if (!uart->timer_running) {
/* timer 38600bps * 10bit * 16byte */
mod_timer(&uart->buffer_timer, jiffies + (HZ + 255) / 256);
uart->timer_running = 1;
}
}
static inline void snd_uart16550_del_timer(struct snd_uart16550 *uart)
{
if (uart->timer_running) {
timer_delete(&uart->buffer_timer);
uart->timer_running = 0;
}
}
/* This macro is only used in snd_uart16550_io_loop */
static inline void snd_uart16550_buffer_output(struct snd_uart16550 *uart)
{
unsigned short buff_out = uart->buff_out;
if (uart->buff_in_count > 0) {
outb(uart->tx_buff[buff_out], uart->base + UART_TX);
uart->fifo_count++;
buff_out++;
buff_out &= TX_BUFF_MASK;
uart->buff_out = buff_out;
uart->buff_in_count--;
}
}
/* This loop should be called with interrupts disabled
* We don't want to interrupt this,
* as we're already handling an interrupt
*/
static void snd_uart16550_io_loop(struct snd_uart16550 * uart)
{
unsigned char c, status;
int substream;
/* recall previous stream */
substream = uart->prev_in;
/* Read Loop */
while ((status = inb(uart->base + UART_LSR)) & UART_LSR_DR) {
/* while receive data ready */
c = inb(uart->base + UART_RX);
/* keep track of last status byte */
if (c & 0x80)
uart->rstatus = c;
/* handle stream switch */
if (uart->adaptor == SNDRV_SERIAL_GENERIC) {
if (uart->rstatus == 0xf5) {
if (c <= SNDRV_SERIAL_MAX_INS && c > 0)
substream = c - 1;
if (c != 0xf5)
/* prevent future bytes from being
interpreted as streams */
uart->rstatus = 0;
} else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN)
&& uart->midi_input[substream])
snd_rawmidi_receive(uart->midi_input[substream],
&c, 1);
} else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) &&
uart->midi_input[substream])
snd_rawmidi_receive(uart->midi_input[substream], &c, 1);
if (status & UART_LSR_OE)
dev_warn(uart->card->dev,
"%s: Overrun on device at 0x%lx\n",
uart->rmidi->name, uart->base);
}
/* remember the last stream */
uart->prev_in = substream;
/* no need of check SERIAL_MODE_OUTPUT_OPEN because if not,
buffer is never filled. */
/* Check write status */
if (status & UART_LSR_THRE)
uart->fifo_count = 0;
if (uart->adaptor == SNDRV_SERIAL_MS124W_SA
|| uart->adaptor == SNDRV_SERIAL_GENERIC) {
/* Can't use FIFO, must send only when CTS is true */
status = inb(uart->base + UART_MSR);
while (uart->fifo_count == 0 && (status & UART_MSR_CTS) &&
uart->buff_in_count > 0) {
snd_uart16550_buffer_output(uart);
status = inb(uart->base + UART_MSR);
}
} else {
/* Write loop */
while (uart->fifo_count < uart->fifo_limit /* Can we write ? */
&& uart->buff_in_count > 0) /* Do we want to? */
snd_uart16550_buffer_output(uart);
}
if (uart->irq < 0 && uart->buff_in_count > 0)
snd_uart16550_add_timer(uart);
}
/* NOTES ON SERVICING INTERUPTS
* ---------------------------
* After receiving a interrupt, it is important to indicate to the UART that
* this has been done.
* For a Rx interrupt, this is done by reading the received byte.
* For a Tx interrupt this is done by either:
* a) Writing a byte
* b) Reading the IIR
* It is particularly important to read the IIR if a Tx interrupt is received
* when there is no data in tx_buff[], as in this case there no other
* indication that the interrupt has been serviced, and it remains outstanding
* indefinitely. This has the curious side effect that and no further interrupts
* will be generated from this device AT ALL!!.
* It is also desirable to clear outstanding interrupts when the device is
* opened/closed.
*
*
* Note that some devices need OUT2 to be set before they will generate
* interrupts at all. (Possibly tied to an internal pull-up on CTS?)
*/
static irqreturn_t snd_uart16550_interrupt(int irq, void *dev_id)
{
struct snd_uart16550 *uart;
uart = dev_id;
guard(spinlock)(&uart->open_lock);
if (uart->filemode == SERIAL_MODE_NOT_OPENED)
return IRQ_NONE;
/* indicate to the UART that the interrupt has been serviced */
inb(uart->base + UART_IIR);
snd_uart16550_io_loop(uart);
return IRQ_HANDLED;
}
/* When the polling mode, this function calls snd_uart16550_io_loop. */
static void snd_uart16550_buffer_timer(struct timer_list *t)
{
struct snd_uart16550 *uart;
uart = timer_container_of(uart, t, buffer_timer);
guard(spinlock_irqsave)(&uart->open_lock);
snd_uart16550_del_timer(uart);
snd_uart16550_io_loop(uart);
}
/*
* this method probes, if an uart sits on given port
* return 0 if found
* return negative error if not found
*/
static int snd_uart16550_detect(struct snd_uart16550 *uart)
{
unsigned long io_base = uart->base;
int ok;
unsigned char c;
/* Do some vague tests for the presence of the uart */
if (io_base == 0 || io_base == SNDRV_AUTO_PORT) {
return -ENODEV; /* Not configured */
}
if (!devm_request_region(uart->card->dev, io_base, 8, "Serial MIDI")) {
dev_err(uart->card->dev,
"u16550: can't grab port 0x%lx\n", io_base);
return -EBUSY;
}
/* uart detected unless one of the following tests should fail */
ok = 1;
/* 8 data-bits, 1 stop-bit, parity off, DLAB = 0 */
outb(UART_LCR_WLEN8, io_base + UART_LCR); /* Line Control Register */
c = inb(io_base + UART_IER);
/* The top four bits of the IER should always == 0 */
if ((c & 0xf0) != 0)
ok = 0; /* failed */
outb(0xaa, io_base + UART_SCR);
/* Write arbitrary data into the scratch reg */
c = inb(io_base + UART_SCR);
/* If it comes back, it's OK */
if (c != 0xaa)
ok = 0; /* failed */
outb(0x55, io_base + UART_SCR);
/* Write arbitrary data into the scratch reg */
c = inb(io_base + UART_SCR);
/* If it comes back, it's OK */
if (c != 0x55)
ok = 0; /* failed */
return ok;
}
static void snd_uart16550_do_open(struct snd_uart16550 * uart)
{
char byte;
/* Initialize basic variables */
uart->buff_in_count = 0;
uart->buff_in = 0;
uart->buff_out = 0;
uart->fifo_limit = 1;
uart->fifo_count = 0;
uart->timer_running = 0;
outb(UART_FCR_ENABLE_FIFO /* Enable FIFO's (if available) */
| UART_FCR_CLEAR_RCVR /* Clear receiver FIFO */
| UART_FCR_CLEAR_XMIT /* Clear transmitter FIFO */
| UART_FCR_TRIGGER_4 /* Set FIFO trigger at 4-bytes */
/* NOTE: interrupt generated after T=(time)4-bytes
* if less than UART_FCR_TRIGGER bytes received
*/
,uart->base + UART_FCR); /* FIFO Control Register */
if ((inb(uart->base + UART_IIR) & 0xf0) == 0xc0)
uart->fifo_limit = 16;
if (uart->divisor != 0) {
uart->old_line_ctrl_reg = inb(uart->base + UART_LCR);
outb(UART_LCR_DLAB /* Divisor latch access bit */
,uart->base + UART_LCR); /* Line Control Register */
uart->old_divisor_lsb = inb(uart->base + UART_DLL);
uart->old_divisor_msb = inb(uart->base + UART_DLM);
outb(uart->divisor
,uart->base + UART_DLL); /* Divisor Latch Low */
outb(0
,uart->base + UART_DLM); /* Divisor Latch High */
/* DLAB is reset to 0 in next outb() */
}
/* Set serial parameters (parity off, etc) */
outb(UART_LCR_WLEN8 /* 8 data-bits */
| 0 /* 1 stop-bit */
| 0 /* parity off */
| 0 /* DLAB = 0 */
,uart->base + UART_LCR); /* Line Control Register */
switch (uart->adaptor) {
default:
outb(UART_MCR_RTS /* Set Request-To-Send line active */
| UART_MCR_DTR /* Set Data-Terminal-Ready line active */
| UART_MCR_OUT2 /* Set OUT2 - not always required, but when
* it is, it is ESSENTIAL for enabling interrupts
*/
,uart->base + UART_MCR); /* Modem Control Register */
break;
case SNDRV_SERIAL_MS124W_SA:
case SNDRV_SERIAL_MS124W_MB:
/* MS-124W can draw power from RTS and DTR if they
are in opposite states. */
outb(UART_MCR_RTS | (0&UART_MCR_DTR) | UART_MCR_OUT2,
uart->base + UART_MCR);
break;
case SNDRV_SERIAL_MS124T:
/* MS-124T can draw power from RTS and/or DTR (preferably
both) if they are both asserted. */
outb(UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2,
uart->base + UART_MCR);
break;
}
if (uart->irq < 0) {
byte = (0 & UART_IER_RDI) /* Disable Receiver data interrupt */
|(0 & UART_IER_THRI) /* Disable Transmitter holding register empty interrupt */
;
} else if (uart->adaptor == SNDRV_SERIAL_MS124W_SA) {
byte = UART_IER_RDI /* Enable Receiver data interrupt */
| UART_IER_MSI /* Enable Modem status interrupt */
;
} else if (uart->adaptor == SNDRV_SERIAL_GENERIC) {
byte = UART_IER_RDI /* Enable Receiver data interrupt */
| UART_IER_MSI /* Enable Modem status interrupt */
| UART_IER_THRI /* Enable Transmitter holding register empty interrupt */
;
} else {
byte = UART_IER_RDI /* Enable Receiver data interrupt */
| UART_IER_THRI /* Enable Transmitter holding register empty interrupt */
;
}
outb(byte, uart->base + UART_IER); /* Interrupt enable Register */
inb(uart->base + UART_LSR); /* Clear any pre-existing overrun indication */
inb(uart->base + UART_IIR); /* Clear any pre-existing transmit interrupt */
inb(uart->base + UART_RX); /* Clear any pre-existing receive interrupt */
}
static void snd_uart16550_do_close(struct snd_uart16550 * uart)
{
if (uart->irq < 0)
snd_uart16550_del_timer(uart);
/* NOTE: may need to disable interrupts before de-registering out handler.
* For now, the consequences are harmless.
*/
outb((0 & UART_IER_RDI) /* Disable Receiver data interrupt */
|(0 & UART_IER_THRI) /* Disable Transmitter holding register empty interrupt */
,uart->base + UART_IER); /* Interrupt enable Register */
switch (uart->adaptor) {
default:
outb((0 & UART_MCR_RTS) /* Deactivate Request-To-Send line */
|(0 & UART_MCR_DTR) /* Deactivate Data-Terminal-Ready line */
|(0 & UART_MCR_OUT2) /* Deactivate OUT2 */
,uart->base + UART_MCR); /* Modem Control Register */
break;
case SNDRV_SERIAL_MS124W_SA:
case SNDRV_SERIAL_MS124W_MB:
/* MS-124W can draw power from RTS and DTR if they
are in opposite states; leave it powered. */
outb(UART_MCR_RTS | (0&UART_MCR_DTR) | (0&UART_MCR_OUT2),
uart->base + UART_MCR);
break;
case SNDRV_SERIAL_MS124T:
/* MS-124T can draw power from RTS and/or DTR (preferably
both) if they are both asserted; leave it powered. */
outb(UART_MCR_RTS | UART_MCR_DTR | (0&UART_MCR_OUT2),
uart->base + UART_MCR);
break;
}
inb(uart->base + UART_IIR); /* Clear any outstanding interrupts */
/* Restore old divisor */
if (uart->divisor != 0) {
outb(UART_LCR_DLAB /* Divisor latch access bit */
,uart->base + UART_LCR); /* Line Control Register */
outb(uart->old_divisor_lsb
,uart->base + UART_DLL); /* Divisor Latch Low */
outb(uart->old_divisor_msb
,uart->base + UART_DLM); /* Divisor Latch High */
/* Restore old LCR (data bits, stop bits, parity, DLAB) */
outb(uart->old_line_ctrl_reg
,uart->base + UART_LCR); /* Line Control Register */
}
}
static int snd_uart16550_input_open(struct snd_rawmidi_substream *substream)
{
struct snd_uart16550 *uart = substream->rmidi->private_data;
guard(spinlock_irqsave)(&uart->open_lock);
if (uart->filemode == SERIAL_MODE_NOT_OPENED)
snd_uart16550_do_open(uart);
uart->filemode |= SERIAL_MODE_INPUT_OPEN;
uart->midi_input[substream->number] = substream;
return 0;
}
static int snd_uart16550_input_close(struct snd_rawmidi_substream *substream)
{
struct snd_uart16550 *uart = substream->rmidi->private_data;
guard(spinlock_irqsave)(&uart->open_lock);
uart->filemode &= ~SERIAL_MODE_INPUT_OPEN;
uart->midi_input[substream->number] = NULL;
if (uart->filemode == SERIAL_MODE_NOT_OPENED)
snd_uart16550_do_close(uart);
return 0;
}
static void snd_uart16550_input_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_uart16550 *uart = substream->rmidi->private_data;
guard(spinlock_irqsave)(&uart->open_lock);
if (up)
uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED;
else
uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED;
}
static int snd_uart16550_output_open(struct snd_rawmidi_substream *substream)
{
struct snd_uart16550 *uart = substream->rmidi->private_data;
guard(spinlock_irqsave)(&uart->open_lock);
if (uart->filemode == SERIAL_MODE_NOT_OPENED)
snd_uart16550_do_open(uart);
uart->filemode |= SERIAL_MODE_OUTPUT_OPEN;
uart->midi_output[substream->number] = substream;
return 0;
};
static int snd_uart16550_output_close(struct snd_rawmidi_substream *substream)
{
struct snd_uart16550 *uart = substream->rmidi->private_data;
guard(spinlock_irqsave)(&uart->open_lock);
uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN;
uart->midi_output[substream->number] = NULL;
if (uart->filemode == SERIAL_MODE_NOT_OPENED)
snd_uart16550_do_close(uart);
return 0;
};
static inline int snd_uart16550_buffer_can_write(struct snd_uart16550 *uart,
int Num)
{
if (uart->buff_in_count + Num < TX_BUFF_SIZE)
return 1;
else
return 0;
}
static inline int snd_uart16550_write_buffer(struct snd_uart16550 *uart,
unsigned char byte)
{
unsigned short buff_in = uart->buff_in;
if (uart->buff_in_count < TX_BUFF_SIZE) {
uart->tx_buff[buff_in] = byte;
buff_in++;
buff_in &= TX_BUFF_MASK;
uart->buff_in = buff_in;
uart->buff_in_count++;
if (uart->irq < 0) /* polling mode */
snd_uart16550_add_timer(uart);
return 1;
} else
return 0;
}
static int snd_uart16550_output_byte(struct snd_uart16550 *uart,
struct snd_rawmidi_substream *substream,
unsigned char midi_byte)
{
if (uart->buff_in_count == 0 /* Buffer empty? */
&& ((uart->adaptor != SNDRV_SERIAL_MS124W_SA &&
uart->adaptor != SNDRV_SERIAL_GENERIC) ||
(uart->fifo_count == 0 /* FIFO empty? */
&& (inb(uart->base + UART_MSR) & UART_MSR_CTS)))) { /* CTS? */
/* Tx Buffer Empty - try to write immediately */
if ((inb(uart->base + UART_LSR) & UART_LSR_THRE) != 0) {
/* Transmitter holding register (and Tx FIFO) empty */
uart->fifo_count = 1;
outb(midi_byte, uart->base + UART_TX);
} else {
if (uart->fifo_count < uart->fifo_limit) {
uart->fifo_count++;
outb(midi_byte, uart->base + UART_TX);
} else {
/* Cannot write (buffer empty) -
* put char in buffer */
snd_uart16550_write_buffer(uart, midi_byte);
}
}
} else {
if (!snd_uart16550_write_buffer(uart, midi_byte)) {
dev_warn(uart->card->dev,
"%s: Buffer overrun on device at 0x%lx\n",
uart->rmidi->name, uart->base);
return 0;
}
}
return 1;
}
static void snd_uart16550_output_write(struct snd_rawmidi_substream *substream)
{
unsigned char midi_byte, addr_byte;
struct snd_uart16550 *uart = substream->rmidi->private_data;
char first;
static unsigned long lasttime = 0;
/* Interrupts are disabled during the updating of the tx_buff,
* since it is 'bad' to have two processes updating the same
* variables (ie buff_in & buff_out)
*/
guard(spinlock_irqsave)(&uart->open_lock);
if (uart->irq < 0) /* polling */
snd_uart16550_io_loop(uart);
if (uart->adaptor == SNDRV_SERIAL_MS124W_MB) {
while (1) {
/* buffer full? */
/* in this mode we need two bytes of space */
if (uart->buff_in_count > TX_BUFF_SIZE - 2)
break;
if (snd_rawmidi_transmit(substream, &midi_byte, 1) != 1)
break;
#ifdef SNDRV_SERIAL_MS124W_MB_NOCOMBO
/* select exactly one of the four ports */
addr_byte = (1 << (substream->number + 4)) | 0x08;
#else
/* select any combination of the four ports */
addr_byte = (substream->number << 4) | 0x08;
/* ...except none */
if (addr_byte == 0x08)
addr_byte = 0xf8;
#endif
snd_uart16550_output_byte(uart, substream, addr_byte);
/* send midi byte */
snd_uart16550_output_byte(uart, substream, midi_byte);
}
} else {
first = 0;
while (snd_rawmidi_transmit_peek(substream, &midi_byte, 1) == 1) {
/* Also send F5 after 3 seconds with no data
* to handle device disconnect */
if (first == 0 &&
(uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS ||
uart->adaptor == SNDRV_SERIAL_GENERIC) &&
(uart->prev_out != substream->number ||
time_after(jiffies, lasttime + 3*HZ))) {
if (snd_uart16550_buffer_can_write(uart, 3)) {
/* Roland Soundcanvas part selection */
/* If this substream of the data is
* different previous substream
* in this uart, send the change part
* event
*/
uart->prev_out = substream->number;
/* change part */
snd_uart16550_output_byte(uart, substream,
0xf5);
/* data */
snd_uart16550_output_byte(uart, substream,
uart->prev_out + 1);
/* If midi_byte is a data byte,
* send the previous status byte */
if (midi_byte < 0x80 &&
uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS)
snd_uart16550_output_byte(uart, substream, uart->prev_status[uart->prev_out]);
} else if (!uart->drop_on_full)
break;
}
/* send midi byte */
if (!snd_uart16550_output_byte(uart, substream, midi_byte) &&
!uart->drop_on_full )
break;
if (midi_byte >= 0x80 && midi_byte < 0xf0)
uart->prev_status[uart->prev_out] = midi_byte;
first = 1;
snd_rawmidi_transmit_ack( substream, 1 );
}
lasttime = jiffies;
}
}
static void snd_uart16550_output_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_uart16550 *uart = substream->rmidi->private_data;
scoped_guard(spinlock_irqsave, &uart->open_lock) {
if (up)
uart->filemode |= SERIAL_MODE_OUTPUT_TRIGGERED;
else
uart->filemode &= ~SERIAL_MODE_OUTPUT_TRIGGERED;
}
if (up)
snd_uart16550_output_write(substream);
}
static const struct snd_rawmidi_ops snd_uart16550_output =
{
.open = snd_uart16550_output_open,
.close = snd_uart16550_output_close,
.trigger = snd_uart16550_output_trigger,
};
static const struct snd_rawmidi_ops snd_uart16550_input =
{
.open = snd_uart16550_input_open,
.close = snd_uart16550_input_close,
.trigger = snd_uart16550_input_trigger,
};
static int snd_uart16550_create(struct snd_card *card,
unsigned long iobase,
int irq,
unsigned int speed,
unsigned int base,
int adaptor,
int droponfull,
struct snd_uart16550 **ruart)
{
struct snd_uart16550 *uart;
int err;
uart = devm_kzalloc(card->dev, sizeof(*uart), GFP_KERNEL);
if (!uart)
return -ENOMEM;
uart->adaptor = adaptor;
uart->card = card;
spin_lock_init(&uart->open_lock);
uart->irq = -1;
uart->base = iobase;
uart->drop_on_full = droponfull;
err = snd_uart16550_detect(uart);
if (err <= 0) {
dev_err(card->dev, "no UART detected at 0x%lx\n", iobase);
return -ENODEV;
}
if (irq >= 0 && irq != SNDRV_AUTO_IRQ) {
if (devm_request_irq(card->dev, irq, snd_uart16550_interrupt,
0, "Serial MIDI", uart)) {
dev_warn(card->dev,
"irq %d busy. Using Polling.\n", irq);
} else {
uart->irq = irq;
}
}
uart->divisor = base / speed;
uart->speed = base / (unsigned int)uart->divisor;
uart->speed_base = base;
uart->prev_out = -1;
uart->prev_in = 0;
uart->rstatus = 0;
memset(uart->prev_status, 0x80, sizeof(unsigned char) * SNDRV_SERIAL_MAX_OUTS);
timer_setup(&uart->buffer_timer, snd_uart16550_buffer_timer, 0);
uart->timer_running = 0;
switch (uart->adaptor) {
case SNDRV_SERIAL_MS124W_SA:
case SNDRV_SERIAL_MS124W_MB:
/* MS-124W can draw power from RTS and DTR if they
are in opposite states. */
outb(UART_MCR_RTS | (0&UART_MCR_DTR), uart->base + UART_MCR);
break;
case SNDRV_SERIAL_MS124T:
/* MS-124T can draw power from RTS and/or DTR (preferably
both) if they are asserted. */
outb(UART_MCR_RTS | UART_MCR_DTR, uart->base + UART_MCR);
break;
default:
break;
}
if (ruart)
*ruart = uart;
return 0;
}
static void snd_uart16550_substreams(struct snd_rawmidi_str *stream)
{
struct snd_rawmidi_substream *substream;
list_for_each_entry(substream, &stream->substreams, list) {
sprintf(substream->name, "Serial MIDI %d", substream->number + 1);
}
}
static int snd_uart16550_rmidi(struct snd_uart16550 *uart, int device,
int outs, int ins,
struct snd_rawmidi **rmidi)
{
struct snd_rawmidi *rrawmidi;
int err;
err = snd_rawmidi_new(uart->card, "UART Serial MIDI", device,
outs, ins, &rrawmidi);
if (err < 0)
return err;
snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&snd_uart16550_input);
snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&snd_uart16550_output);
strscpy(rrawmidi->name, "Serial MIDI");
snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]);
snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]);
rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
rrawmidi->private_data = uart;
if (rmidi)
*rmidi = rrawmidi;
return 0;
}
static int snd_serial_probe(struct platform_device *devptr)
{
struct snd_card *card;
struct snd_uart16550 *uart;
int err;
int dev = devptr->id;
switch (adaptor[dev]) {
case SNDRV_SERIAL_SOUNDCANVAS:
ins[dev] = 1;
break;
case SNDRV_SERIAL_MS124T:
case SNDRV_SERIAL_MS124W_SA:
outs[dev] = 1;
ins[dev] = 1;
break;
case SNDRV_SERIAL_MS124W_MB:
outs[dev] = 16;
ins[dev] = 1;
break;
case SNDRV_SERIAL_GENERIC:
break;
default:
dev_err(&devptr->dev,
"Adaptor type is out of range 0-%d (%d)\n",
SNDRV_SERIAL_MAX_ADAPTOR, adaptor[dev]);
return -ENODEV;
}
if (outs[dev] < 1 || outs[dev] > SNDRV_SERIAL_MAX_OUTS) {
dev_err(&devptr->dev,
"Count of outputs is out of range 1-%d (%d)\n",
SNDRV_SERIAL_MAX_OUTS, outs[dev]);
return -ENODEV;
}
if (ins[dev] < 1 || ins[dev] > SNDRV_SERIAL_MAX_INS) {
dev_err(&devptr->dev,
"Count of inputs is out of range 1-%d (%d)\n",
SNDRV_SERIAL_MAX_INS, ins[dev]);
return -ENODEV;
}
err = snd_devm_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE,
0, &card);
if (err < 0)
return err;
strscpy(card->driver, "Serial");
strscpy(card->shortname, "Serial MIDI (UART16550A)");
err = snd_uart16550_create(card, port[dev], irq[dev], speed[dev],
base[dev], adaptor[dev], droponfull[dev],
&uart);
if (err < 0)
return err;
err = snd_uart16550_rmidi(uart, 0, outs[dev], ins[dev], &uart->rmidi);
if (err < 0)
return err;
sprintf(card->longname, "%s [%s] at %#lx, irq %d",
card->shortname,
adaptor_names[uart->adaptor],
uart->base,
uart->irq);
err = snd_card_register(card);
if (err < 0)
return err;
platform_set_drvdata(devptr, card);
return 0;
}
#define SND_SERIAL_DRIVER "snd_serial_u16550"
static struct platform_driver snd_serial_driver = {
.probe = snd_serial_probe,
.driver = {
.name = SND_SERIAL_DRIVER,
},
};
static void snd_serial_unregister_all(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(devices); ++i)
platform_device_unregister(devices[i]);
platform_driver_unregister(&snd_serial_driver);
}
static int __init alsa_card_serial_init(void)
{
int i, cards, err;
err = platform_driver_register(&snd_serial_driver);
if (err < 0)
return err;
cards = 0;
for (i = 0; i < SNDRV_CARDS; i++) {
struct platform_device *device;
if (! enable[i])
continue;
device = platform_device_register_simple(SND_SERIAL_DRIVER,
i, NULL, 0);
if (IS_ERR(device))
continue;
if (!platform_get_drvdata(device)) {
platform_device_unregister(device);
continue;
}
devices[i] = device;
cards++;
}
if (! cards) {
#ifdef MODULE
pr_err("serial midi soundcard not found or device busy\n");
#endif
snd_serial_unregister_all();
return -ENODEV;
}
return 0;
}
static void __exit alsa_card_serial_exit(void)
{
snd_serial_unregister_all();
}
module_init(alsa_card_serial_init)
module_exit(alsa_card_serial_exit)
+172
View File
@@ -0,0 +1,172 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Dummy soundcard for virtual rawmidi devices
*
* Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
*/
/*
* VIRTUAL RAW MIDI DEVICE CARDS
*
* This dummy card contains up to 4 virtual rawmidi devices.
* They are not real rawmidi devices but just associated with sequencer
* clients, so that any input/output sources can be connected as a raw
* MIDI device arbitrary.
* Also, multiple access is allowed to a single rawmidi device.
*
* Typical usage is like following:
* - Load snd-virmidi module.
* # modprobe snd-virmidi index=2
* Then, sequencer clients 72:0 to 75:0 will be created, which are
* mapped from /dev/snd/midiC1D0 to /dev/snd/midiC1D3, respectively.
*
* - Connect input/output via aconnect.
* % aconnect 64:0 72:0 # keyboard input redirection 64:0 -> 72:0
* % aconnect 72:0 65:0 # output device redirection 72:0 -> 65:0
*
* - Run application using a midi device (eg. /dev/snd/midiC1D0)
*/
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/seq_kernel.h>
#include <sound/seq_virmidi.h>
#include <sound/initval.h>
/* hack: OSS defines midi_devs, so undefine it (versioned symbols) */
#undef midi_devs
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("Dummy soundcard for virtual rawmidi devices");
MODULE_LICENSE("GPL");
#define MAX_MIDI_DEVICES 4
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] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4};
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for virmidi soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for virmidi soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable this soundcard.");
module_param_array(midi_devs, int, NULL, 0444);
MODULE_PARM_DESC(midi_devs, "MIDI devices # (1-4)");
struct snd_card_virmidi {
struct snd_card *card;
struct snd_rawmidi *midi[MAX_MIDI_DEVICES];
};
static struct platform_device *devices[SNDRV_CARDS];
static int snd_virmidi_probe(struct platform_device *devptr)
{
struct snd_card *card;
struct snd_card_virmidi *vmidi;
int idx, err;
int dev = devptr->id;
err = snd_devm_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE,
sizeof(struct snd_card_virmidi), &card);
if (err < 0)
return err;
vmidi = card->private_data;
vmidi->card = card;
if (midi_devs[dev] > MAX_MIDI_DEVICES) {
dev_warn(&devptr->dev,
"too much midi devices for virmidi %d: force to use %d\n",
dev, MAX_MIDI_DEVICES);
midi_devs[dev] = MAX_MIDI_DEVICES;
}
for (idx = 0; idx < midi_devs[dev]; idx++) {
struct snd_rawmidi *rmidi;
err = snd_virmidi_new(card, idx, &rmidi);
if (err < 0)
return err;
vmidi->midi[idx] = rmidi;
strscpy(rmidi->name, "Virtual Raw MIDI");
}
strscpy(card->driver, "VirMIDI");
strscpy(card->shortname, "VirMIDI");
sprintf(card->longname, "Virtual MIDI Card %i", dev + 1);
err = snd_card_register(card);
if (err)
return err;
platform_set_drvdata(devptr, card);
return 0;
}
#define SND_VIRMIDI_DRIVER "snd_virmidi"
static struct platform_driver snd_virmidi_driver = {
.probe = snd_virmidi_probe,
.driver = {
.name = SND_VIRMIDI_DRIVER,
},
};
static void snd_virmidi_unregister_all(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(devices); ++i)
platform_device_unregister(devices[i]);
platform_driver_unregister(&snd_virmidi_driver);
}
static int __init alsa_card_virmidi_init(void)
{
int i, cards, err;
err = platform_driver_register(&snd_virmidi_driver);
if (err < 0)
return err;
cards = 0;
for (i = 0; i < SNDRV_CARDS; i++) {
struct platform_device *device;
if (!enable[i])
continue;
device = platform_device_register_simple(SND_VIRMIDI_DRIVER,
i, NULL, 0);
if (IS_ERR(device))
continue;
if (!platform_get_drvdata(device)) {
platform_device_unregister(device);
continue;
}
devices[i] = device;
cards++;
}
if (!cards) {
#ifdef MODULE
pr_err("Card-VirMIDI soundcard not found or device busy\n");
#endif
snd_virmidi_unregister_all();
return -ENODEV;
}
return 0;
}
static void __exit alsa_card_virmidi_exit(void)
{
snd_virmidi_unregister_all();
}
module_init(alsa_card_virmidi_init)
module_exit(alsa_card_virmidi_exit)
+9
View File
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
#
snd-vx-lib-y := vx_core.o vx_hwdep.o vx_pcm.o vx_mixer.o vx_cmd.o vx_uer.o
obj-$(CONFIG_SND_VX_LIB) += snd-vx-lib.o
+96
View File
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Digigram VX soundcards
*
* DSP commands
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*/
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/vx_core.h>
#include "vx_cmd.h"
/*
* Array of DSP commands
*/
static const struct vx_cmd_info vx_dsp_cmds[] = {
[CMD_VERSION] = { 0x010000, 2, RMH_SSIZE_FIXED, 1 },
[CMD_SUPPORTED] = { 0x020000, 1, RMH_SSIZE_FIXED, 2 },
[CMD_TEST_IT] = { 0x040000, 1, RMH_SSIZE_FIXED, 1 },
[CMD_SEND_IRQA] = { 0x070001, 1, RMH_SSIZE_FIXED, 0 },
[CMD_IBL] = { 0x080000, 1, RMH_SSIZE_FIXED, 4 },
[CMD_ASYNC] = { 0x0A0000, 1, RMH_SSIZE_ARG, 0 },
[CMD_RES_PIPE] = { 0x400000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_FREE_PIPE] = { 0x410000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_CONF_PIPE] = { 0x42A101, 2, RMH_SSIZE_FIXED, 0 },
[CMD_ABORT_CONF_PIPE] = { 0x42A100, 2, RMH_SSIZE_FIXED, 0 },
[CMD_PARAM_OUTPUT_PIPE] = { 0x43A000, 2, RMH_SSIZE_FIXED, 0 },
[CMD_STOP_PIPE] = { 0x470004, 1, RMH_SSIZE_FIXED, 0 },
[CMD_PIPE_STATE] = { 0x480000, 1, RMH_SSIZE_FIXED, 1 },
[CMD_PIPE_SPL_COUNT] = { 0x49A000, 2, RMH_SSIZE_FIXED, 2 },
[CMD_CAN_START_PIPE] = { 0x4b0000, 1, RMH_SSIZE_FIXED, 1 },
[CMD_SIZE_HBUFFER] = { 0x4C0000, 1, RMH_SSIZE_FIXED, 1 },
[CMD_START_STREAM] = { 0x80A000, 2, RMH_SSIZE_FIXED, 0 },
[CMD_START_ONE_STREAM] = { 0x800000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_PAUSE_STREAM] = { 0x81A000, 2, RMH_SSIZE_FIXED, 0 },
[CMD_PAUSE_ONE_STREAM] = { 0x810000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_STREAM_OUT_LEVEL_ADJUST] = { 0x828000, 2, RMH_SSIZE_FIXED, 0 },
[CMD_STOP_STREAM] = { 0x830000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_FORMAT_STREAM_OUT] = { 0x868000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_FORMAT_STREAM_IN] = { 0x878800, 1, RMH_SSIZE_FIXED, 0 },
[CMD_GET_STREAM_STATE] = { 0x890001, 2, RMH_SSIZE_FIXED, 1 },
[CMD_DROP_BYTES_AWAY] = { 0x8A8000, 2, RMH_SSIZE_FIXED, 0 },
[CMD_GET_REMAINING_BYTES] = { 0x8D0800, 1, RMH_SSIZE_FIXED, 2 },
[CMD_CONNECT_AUDIO] = { 0xC10000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_AUDIO_LEVEL_ADJUST] = { 0xC2A000, 3, RMH_SSIZE_FIXED, 0 },
[CMD_AUDIO_VU_PIC_METER] = { 0xC3A003, 2, RMH_SSIZE_FIXED, 1 },
[CMD_GET_AUDIO_LEVELS] = { 0xC4A000, 2, RMH_SSIZE_FIXED, 0 },
[CMD_GET_NOTIFY_EVENT] = { 0x4D0000, 1, RMH_SSIZE_ARG, 0 },
[CMD_INFO_NOTIFIED] = { 0x0B0000, 1, RMH_SSIZE_FIXED, 2 },
[CMD_ACCESS_IO_FCT] = { 0x098000, 1, RMH_SSIZE_ARG, 0 },
[CMD_STATUS_R_BUFFERS] = { 0x440000, 1, RMH_SSIZE_ARG, 0 },
[CMD_UPDATE_R_BUFFERS] = { 0x848000, 4, RMH_SSIZE_FIXED, 0 },
[CMD_LOAD_EFFECT_CONTEXT] = { 0x0c8000, 3, RMH_SSIZE_FIXED, 1 },
[CMD_EFFECT_ONE_PIPE] = { 0x458000, 0, RMH_SSIZE_FIXED, 0 },
[CMD_MODIFY_CLOCK] = { 0x0d0000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_STREAM1_OUT_SET_N_LEVELS] ={ 0x858000, 3, RMH_SSIZE_FIXED, 0 },
[CMD_PURGE_STREAM_DCMDS] = { 0x8b8000, 3, RMH_SSIZE_FIXED, 0 },
[CMD_NOTIFY_PIPE_TIME] = { 0x4e0000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_LOAD_EFFECT_CONTEXT_PACKET] = { 0x0c8000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_RELIC_R_BUFFER] = { 0x8e0800, 1, RMH_SSIZE_FIXED, 1 },
[CMD_RESYNC_AUDIO_INPUTS] = { 0x0e0000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_NOTIFY_STREAM_TIME] = { 0x8f0000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_STREAM_SAMPLE_COUNT] = { 0x900000, 1, RMH_SSIZE_FIXED, 2 },
[CMD_CONFIG_TIME_CODE] = { 0x050000, 2, RMH_SSIZE_FIXED, 0 },
[CMD_GET_TIME_CODE] = { 0x060000, 1, RMH_SSIZE_FIXED, 5 },
[CMD_MANAGE_SIGNAL] = { 0x0f0000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_PARAMETER_STREAM_OUT] = { 0x91A000, 3, RMH_SSIZE_FIXED, 0 },
[CMD_READ_BOARD_FREQ] = { 0x030000, 1, RMH_SSIZE_FIXED, 2 },
[CMD_GET_STREAM_LEVELS] = { 0x8c0000, 1, RMH_SSIZE_FIXED, 3 },
[CMD_PURGE_PIPE_DCMDS] = { 0x4f8000, 3, RMH_SSIZE_FIXED, 0 },
// [CMD_SET_STREAM_OUT_EFFECTS] = { 0x888000, 34, RMH_SSIZE_FIXED, 0 },
// [CMD_GET_STREAM_OUT_EFFECTS] = { 0x928000, 2, RMH_SSIZE_FIXED, 32 },
[CMD_CONNECT_MONITORING] = { 0xC00000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_STREAM2_OUT_SET_N_LEVELS] = { 0x938000, 3, RMH_SSIZE_FIXED, 0 },
[CMD_CANCEL_R_BUFFERS] = { 0x948000, 4, RMH_SSIZE_FIXED, 0 },
[CMD_NOTIFY_END_OF_BUFFER] = { 0x950000, 1, RMH_SSIZE_FIXED, 0 },
[CMD_GET_STREAM_VU_METER] = { 0x95A000, 2, RMH_SSIZE_ARG, 0 },
};
/**
* vx_init_rmh - initialize the RMH instance
* @rmh: the rmh pointer to be initialized
* @cmd: the rmh command to be set
*/
void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd)
{
if (snd_BUG_ON(cmd >= CMD_LAST_INDEX))
return;
rmh->LgCmd = vx_dsp_cmds[cmd].length;
rmh->LgStat = vx_dsp_cmds[cmd].st_length;
rmh->DspStat = vx_dsp_cmds[cmd].st_type;
rmh->Cmd[0] = vx_dsp_cmds[cmd].opcode;
}
+233
View File
@@ -0,0 +1,233 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Digigram VX soundcards
*
* Definitions of DSP commands
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*/
#ifndef __VX_CMD_H
#define __VX_CMD_H
enum {
CMD_VERSION,
CMD_SUPPORTED,
CMD_TEST_IT,
CMD_SEND_IRQA,
CMD_IBL,
CMD_ASYNC,
CMD_RES_PIPE,
CMD_FREE_PIPE,
CMD_CONF_PIPE,
CMD_ABORT_CONF_PIPE,
CMD_PARAM_OUTPUT_PIPE,
CMD_STOP_PIPE,
CMD_PIPE_STATE,
CMD_PIPE_SPL_COUNT,
CMD_CAN_START_PIPE,
CMD_SIZE_HBUFFER,
CMD_START_STREAM,
CMD_START_ONE_STREAM,
CMD_PAUSE_STREAM,
CMD_PAUSE_ONE_STREAM,
CMD_STREAM_OUT_LEVEL_ADJUST,
CMD_STOP_STREAM,
CMD_FORMAT_STREAM_OUT,
CMD_FORMAT_STREAM_IN,
CMD_GET_STREAM_STATE,
CMD_DROP_BYTES_AWAY,
CMD_GET_REMAINING_BYTES,
CMD_CONNECT_AUDIO,
CMD_AUDIO_LEVEL_ADJUST,
CMD_AUDIO_VU_PIC_METER,
CMD_GET_AUDIO_LEVELS,
CMD_GET_NOTIFY_EVENT,
CMD_INFO_NOTIFIED,
CMD_ACCESS_IO_FCT,
CMD_STATUS_R_BUFFERS,
CMD_UPDATE_R_BUFFERS,
CMD_LOAD_EFFECT_CONTEXT,
CMD_EFFECT_ONE_PIPE,
CMD_MODIFY_CLOCK,
CMD_STREAM1_OUT_SET_N_LEVELS,
CMD_PURGE_STREAM_DCMDS,
CMD_NOTIFY_PIPE_TIME,
CMD_LOAD_EFFECT_CONTEXT_PACKET,
CMD_RELIC_R_BUFFER,
CMD_RESYNC_AUDIO_INPUTS,
CMD_NOTIFY_STREAM_TIME,
CMD_STREAM_SAMPLE_COUNT,
CMD_CONFIG_TIME_CODE,
CMD_GET_TIME_CODE,
CMD_MANAGE_SIGNAL,
CMD_PARAMETER_STREAM_OUT,
CMD_READ_BOARD_FREQ,
CMD_GET_STREAM_LEVELS,
CMD_PURGE_PIPE_DCMDS,
// CMD_SET_STREAM_OUT_EFFECTS,
// CMD_GET_STREAM_OUT_EFFECTS,
CMD_CONNECT_MONITORING,
CMD_STREAM2_OUT_SET_N_LEVELS,
CMD_CANCEL_R_BUFFERS,
CMD_NOTIFY_END_OF_BUFFER,
CMD_GET_STREAM_VU_METER,
CMD_LAST_INDEX
};
struct vx_cmd_info {
unsigned int opcode; /* command word */
int length; /* command length (in words) */
int st_type; /* status type (RMH_SSIZE_XXX) */
int st_length; /* fixed length */
};
/* Family and code op of some DSP requests. */
#define CODE_OP_PIPE_TIME 0x004e0000
#define CODE_OP_START_STREAM 0x00800000
#define CODE_OP_PAUSE_STREAM 0x00810000
#define CODE_OP_OUT_STREAM_LEVEL 0x00820000
#define CODE_OP_UPDATE_R_BUFFERS 0x00840000
#define CODE_OP_OUT_STREAM1_LEVEL_CURVE 0x00850000
#define CODE_OP_OUT_STREAM2_LEVEL_CURVE 0x00930000
#define CODE_OP_OUT_STREAM_FORMAT 0x00860000
#define CODE_OP_STREAM_TIME 0x008f0000
#define CODE_OP_OUT_STREAM_EXTRAPARAMETER 0x00910000
#define CODE_OP_OUT_AUDIO_LEVEL 0x00c20000
#define NOTIFY_LAST_COMMAND 0x00400000
/* Values for a user delay */
#define DC_DIFFERED_DELAY (1<<BIT_DIFFERED_COMMAND)
#define DC_NOTIFY_DELAY (1<<BIT_NOTIFIED_COMMAND)
#define DC_HBUFFER_DELAY (1<<BIT_TIME_RELATIVE_TO_BUFFER)
#define DC_MULTIPLE_DELAY (1<<BIT_RESERVED)
#define DC_STREAM_TIME_DELAY (1<<BIT_STREAM_TIME)
#define DC_CANCELLED_DELAY (1<<BIT_CANCELLED_COMMAND)
/* Values for tiDelayed field in TIME_INFO structure,
* and for pbPause field in PLAY_BUFFER_INFO structure
*/
#define BIT_DIFFERED_COMMAND 0
#define BIT_NOTIFIED_COMMAND 1
#define BIT_TIME_RELATIVE_TO_BUFFER 2
#define BIT_RESERVED 3
#define BIT_STREAM_TIME 4
#define BIT_CANCELLED_COMMAND 5
/* Access to the "Size" field of the response of the CMD_GET_NOTIFY_EVENT request. */
#define GET_NOTIFY_EVENT_SIZE_FIELD_MASK 0x000000ff
/* DSP commands general masks */
#define OPCODE_MASK 0x00ff0000
#define DSP_DIFFERED_COMMAND_MASK 0x0000C000
/* Notifications (NOTIFY_INFO) */
#define ALL_CMDS_NOTIFIED 0x0000 // reserved
#define START_STREAM_NOTIFIED 0x0001
#define PAUSE_STREAM_NOTIFIED 0x0002
#define OUT_STREAM_LEVEL_NOTIFIED 0x0003
#define OUT_STREAM_PARAMETER_NOTIFIED 0x0004 // left for backward compatibility
#define OUT_STREAM_FORMAT_NOTIFIED 0x0004
#define PIPE_TIME_NOTIFIED 0x0005
#define OUT_AUDIO_LEVEL_NOTIFIED 0x0006
#define OUT_STREAM_LEVEL_CURVE_NOTIFIED 0x0007
#define STREAM_TIME_NOTIFIED 0x0008
#define OUT_STREAM_EXTRAPARAMETER_NOTIFIED 0x0009
#define UNKNOWN_COMMAND_NOTIFIED 0xffff
/* Output pipe parameters setting */
#define MASK_VALID_PIPE_MPEG_PARAM 0x000040
#define MASK_VALID_PIPE_BACKWARD_PARAM 0x000020
#define MASK_SET_PIPE_MPEG_PARAM 0x000002
#define MASK_SET_PIPE_BACKWARD_PARAM 0x000001
#define MASK_DSP_WORD 0x00FFFFFF
#define MASK_ALL_STREAM 0x00FFFFFF
#define MASK_DSP_WORD_LEVEL 0x000001FF
#define MASK_FIRST_FIELD 0x0000001F
#define FIELD_SIZE 5
#define COMMAND_RECORD_MASK 0x000800
/* PipeManagement definition bits (PIPE_DECL_INFO) */
#define P_UNDERRUN_SKIP_SOUND_MASK 0x01
#define P_PREPARE_FOR_MPEG3_MASK 0x02
#define P_DO_NOT_RESET_ANALOG_LEVELS 0x04
#define P_ALLOW_UNDER_ALLOCATION_MASK 0x08
#define P_DATA_MODE_MASK 0x10
#define P_ASIO_BUFFER_MANAGEMENT_MASK 0x20
#define BIT_SKIP_SOUND 0x08 // bit 3
#define BIT_DATA_MODE 0x10 // bit 4
/* Bits in the CMD_MODIFY_CLOCK request. */
#define CMD_MODIFY_CLOCK_FD_BIT 0x00000001
#define CMD_MODIFY_CLOCK_T_BIT 0x00000002
#define CMD_MODIFY_CLOCK_S_BIT 0x00000004
/* Access to the results of the CMD_GET_TIME_CODE RMH. */
#define TIME_CODE_V_MASK 0x00800000
#define TIME_CODE_N_MASK 0x00400000
#define TIME_CODE_B_MASK 0x00200000
#define TIME_CODE_W_MASK 0x00100000
/* Values for the CMD_MANAGE_SIGNAL RMH. */
#define MANAGE_SIGNAL_TIME_CODE 0x01
#define MANAGE_SIGNAL_MIDI 0x02
/* Values for the CMD_CONFIG_TIME_CODE RMH. */
#define CONFIG_TIME_CODE_CANCEL 0x00001000
/* Mask to get only the effective time from the
* high word out of the 2 returned by the DSP
*/
#define PCX_TIME_HI_MASK 0x000fffff
/* Values for setting a H-Buffer time */
#define HBUFFER_TIME_HIGH 0x00200000
#define HBUFFER_TIME_LOW 0x00000000
#define NOTIFY_MASK_TIME_HIGH 0x00400000
#define MULTIPLE_MASK_TIME_HIGH 0x00100000
#define STREAM_MASK_TIME_HIGH 0x00800000
/*
*
*/
void vx_init_rmh(struct vx_rmh *rmh, unsigned int cmd);
/**
* vx_set_pipe_cmd_params - fill first command word for pipe commands
* @rmh: the rmh to be modified
* @is_capture: 0 = playback, 1 = capture operation
* @param1: first pipe-parameter
* @param2: second pipe-parameter
*/
static inline void vx_set_pipe_cmd_params(struct vx_rmh *rmh, int is_capture,
int param1, int param2)
{
if (is_capture)
rmh->Cmd[0] |= COMMAND_RECORD_MASK;
rmh->Cmd[0] |= (((u32)param1 & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD;
if (param2)
rmh->Cmd[0] |= ((u32)param2 & MASK_FIRST_FIELD) & MASK_DSP_WORD;
}
/**
* vx_set_stream_cmd_params - fill first command word for stream commands
* @rmh: the rmh to be modified
* @is_capture: 0 = playback, 1 = capture operation
* @pipe: the pipe index (zero-based)
*/
static inline void vx_set_stream_cmd_params(struct vx_rmh *rmh, int is_capture, int pipe)
{
if (is_capture)
rmh->Cmd[0] |= COMMAND_RECORD_MASK;
rmh->Cmd[0] |= (((u32)pipe & MASK_FIRST_FIELD) << FIELD_SIZE) & MASK_DSP_WORD;
}
#endif /* __VX_CMD_H */
+808
View File
@@ -0,0 +1,808 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Digigram VX soundcards
*
* Hardware core part
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*/
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/io.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/asoundef.h>
#include <sound/info.h>
#include <sound/vx_core.h>
#include "vx_cmd.h"
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("Common routines for Digigram VX drivers");
MODULE_LICENSE("GPL");
/*
* vx_check_reg_bit - wait for the specified bit is set/reset on a register
* @reg: register to check
* @mask: bit mask
* @bit: resultant bit to be checked
* @time: time-out of loop in msec
*
* returns zero if a bit matches, or a negative error code.
*/
int snd_vx_check_reg_bit(struct vx_core *chip, int reg, int mask, int bit, int time)
{
unsigned long end_time = jiffies + (time * HZ + 999) / 1000;
static const char * const reg_names[VX_REG_MAX] = {
"ICR", "CVR", "ISR", "IVR", "RXH", "RXM", "RXL",
"DMA", "CDSP", "RFREQ", "RUER/V2", "DATA", "MEMIRQ",
"ACQ", "BIT0", "BIT1", "MIC0", "MIC1", "MIC2",
"MIC3", "INTCSR", "CNTRL", "GPIOC",
"LOFREQ", "HIFREQ", "CSUER", "RUER"
};
do {
if ((snd_vx_inb(chip, reg) & mask) == bit)
return 0;
//msleep(10);
} while (time_after_eq(end_time, jiffies));
dev_dbg(chip->card->dev,
"vx_check_reg_bit: timeout, reg=%s, mask=0x%x, val=0x%x\n",
reg_names[reg], mask, snd_vx_inb(chip, reg));
return -EIO;
}
EXPORT_SYMBOL(snd_vx_check_reg_bit);
/*
* vx_send_irq_dsp - set command irq bit
* @num: the requested IRQ type, IRQ_XXX
*
* this triggers the specified IRQ request
* returns 0 if successful, or a negative error code.
*
*/
static int vx_send_irq_dsp(struct vx_core *chip, int num)
{
int nirq;
/* wait for Hc = 0 */
if (snd_vx_check_reg_bit(chip, VX_CVR, CVR_HC, 0, 200) < 0)
return -EIO;
nirq = num;
if (vx_has_new_dsp(chip))
nirq += VXP_IRQ_OFFSET;
vx_outb(chip, CVR, (nirq >> 1) | CVR_HC);
return 0;
}
/*
* vx_reset_chk - reset CHK bit on ISR
*
* returns 0 if successful, or a negative error code.
*/
static int vx_reset_chk(struct vx_core *chip)
{
/* Reset irq CHK */
if (vx_send_irq_dsp(chip, IRQ_RESET_CHK) < 0)
return -EIO;
/* Wait until CHK = 0 */
if (vx_check_isr(chip, ISR_CHK, 0, 200) < 0)
return -EIO;
return 0;
}
/*
* vx_transfer_end - terminate message transfer
* @cmd: IRQ message to send (IRQ_MESS_XXX_END)
*
* returns 0 if successful, or a negative error code.
* the error code can be VX-specific, retrieved via vx_get_error().
* NB: call with mutex held!
*/
static int vx_transfer_end(struct vx_core *chip, int cmd)
{
int err;
err = vx_reset_chk(chip);
if (err < 0)
return err;
/* irq MESS_READ/WRITE_END */
err = vx_send_irq_dsp(chip, cmd);
if (err < 0)
return err;
/* Wait CHK = 1 */
err = vx_wait_isr_bit(chip, ISR_CHK);
if (err < 0)
return err;
/* If error, Read RX */
err = vx_inb(chip, ISR);
if (err & ISR_ERR) {
err = vx_wait_for_rx_full(chip);
if (err < 0) {
dev_dbg(chip->card->dev,
"transfer_end: error in rx_full\n");
return err;
}
err = vx_inb(chip, RXH) << 16;
err |= vx_inb(chip, RXM) << 8;
err |= vx_inb(chip, RXL);
dev_dbg(chip->card->dev, "transfer_end: error = 0x%x\n", err);
return -(VX_ERR_MASK | err);
}
return 0;
}
/*
* vx_read_status - return the status rmh
* @rmh: rmh record to store the status
*
* returns 0 if successful, or a negative error code.
* the error code can be VX-specific, retrieved via vx_get_error().
* NB: call with mutex held!
*/
static int vx_read_status(struct vx_core *chip, struct vx_rmh *rmh)
{
int i, err, val, size;
/* no read necessary? */
if (rmh->DspStat == RMH_SSIZE_FIXED && rmh->LgStat == 0)
return 0;
/* Wait for RX full (with timeout protection)
* The first word of status is in RX
*/
err = vx_wait_for_rx_full(chip);
if (err < 0)
return err;
/* Read RX */
val = vx_inb(chip, RXH) << 16;
val |= vx_inb(chip, RXM) << 8;
val |= vx_inb(chip, RXL);
/* If status given by DSP, let's decode its size */
switch (rmh->DspStat) {
case RMH_SSIZE_ARG:
size = val & 0xff;
rmh->Stat[0] = val & 0xffff00;
rmh->LgStat = size + 1;
break;
case RMH_SSIZE_MASK:
/* Let's count the arg numbers from a mask */
rmh->Stat[0] = val;
size = 0;
while (val) {
if (val & 0x01)
size++;
val >>= 1;
}
rmh->LgStat = size + 1;
break;
default:
/* else retrieve the status length given by the driver */
size = rmh->LgStat;
rmh->Stat[0] = val; /* Val is the status 1st word */
size--; /* hence adjust remaining length */
break;
}
if (size < 1)
return 0;
if (snd_BUG_ON(size >= SIZE_MAX_STATUS))
return -EINVAL;
for (i = 1; i <= size; i++) {
/* trigger an irq MESS_WRITE_NEXT */
err = vx_send_irq_dsp(chip, IRQ_MESS_WRITE_NEXT);
if (err < 0)
return err;
/* Wait for RX full (with timeout protection) */
err = vx_wait_for_rx_full(chip);
if (err < 0)
return err;
rmh->Stat[i] = vx_inb(chip, RXH) << 16;
rmh->Stat[i] |= vx_inb(chip, RXM) << 8;
rmh->Stat[i] |= vx_inb(chip, RXL);
}
return vx_transfer_end(chip, IRQ_MESS_WRITE_END);
}
#define MASK_MORE_THAN_1_WORD_COMMAND 0x00008000
#define MASK_1_WORD_COMMAND 0x00ff7fff
/*
* vx_send_msg_nolock - send a DSP message and read back the status
* @rmh: the rmh record to send and receive
*
* returns 0 if successful, or a negative error code.
* the error code can be VX-specific, retrieved via vx_get_error().
*
* this function doesn't call mutex lock at all.
*/
int vx_send_msg_nolock(struct vx_core *chip, struct vx_rmh *rmh)
{
int i, err;
if (chip->chip_status & VX_STAT_IS_STALE)
return -EBUSY;
err = vx_reset_chk(chip);
if (err < 0) {
dev_dbg(chip->card->dev, "vx_send_msg: vx_reset_chk error\n");
return err;
}
/* Check bit M is set according to length of the command */
if (rmh->LgCmd > 1)
rmh->Cmd[0] |= MASK_MORE_THAN_1_WORD_COMMAND;
else
rmh->Cmd[0] &= MASK_1_WORD_COMMAND;
/* Wait for TX empty */
err = vx_wait_isr_bit(chip, ISR_TX_EMPTY);
if (err < 0) {
dev_dbg(chip->card->dev, "vx_send_msg: wait tx empty error\n");
return err;
}
/* Write Cmd[0] */
vx_outb(chip, TXH, (rmh->Cmd[0] >> 16) & 0xff);
vx_outb(chip, TXM, (rmh->Cmd[0] >> 8) & 0xff);
vx_outb(chip, TXL, rmh->Cmd[0] & 0xff);
/* Trigger irq MESSAGE */
err = vx_send_irq_dsp(chip, IRQ_MESSAGE);
if (err < 0) {
dev_dbg(chip->card->dev,
"vx_send_msg: send IRQ_MESSAGE error\n");
return err;
}
/* Wait for CHK = 1 */
err = vx_wait_isr_bit(chip, ISR_CHK);
if (err < 0)
return err;
/* If error, get error value from RX */
if (vx_inb(chip, ISR) & ISR_ERR) {
err = vx_wait_for_rx_full(chip);
if (err < 0) {
dev_dbg(chip->card->dev,
"vx_send_msg: rx_full read error\n");
return err;
}
err = vx_inb(chip, RXH) << 16;
err |= vx_inb(chip, RXM) << 8;
err |= vx_inb(chip, RXL);
dev_dbg(chip->card->dev,
"msg got error = 0x%x at cmd[0]\n", err);
err = -(VX_ERR_MASK | err);
return err;
}
/* Send the other words */
if (rmh->LgCmd > 1) {
for (i = 1; i < rmh->LgCmd; i++) {
/* Wait for TX ready */
err = vx_wait_isr_bit(chip, ISR_TX_READY);
if (err < 0) {
dev_dbg(chip->card->dev,
"vx_send_msg: tx_ready error\n");
return err;
}
/* Write Cmd[i] */
vx_outb(chip, TXH, (rmh->Cmd[i] >> 16) & 0xff);
vx_outb(chip, TXM, (rmh->Cmd[i] >> 8) & 0xff);
vx_outb(chip, TXL, rmh->Cmd[i] & 0xff);
/* Trigger irq MESS_READ_NEXT */
err = vx_send_irq_dsp(chip, IRQ_MESS_READ_NEXT);
if (err < 0) {
dev_dbg(chip->card->dev,
"vx_send_msg: IRQ_READ_NEXT error\n");
return err;
}
}
/* Wait for TX empty */
err = vx_wait_isr_bit(chip, ISR_TX_READY);
if (err < 0) {
dev_dbg(chip->card->dev,
"vx_send_msg: TX_READY error\n");
return err;
}
/* End of transfer */
err = vx_transfer_end(chip, IRQ_MESS_READ_END);
if (err < 0)
return err;
}
return vx_read_status(chip, rmh);
}
/*
* vx_send_msg - send a DSP message with mutex
* @rmh: the rmh record to send and receive
*
* returns 0 if successful, or a negative error code.
* see vx_send_msg_nolock().
*/
int vx_send_msg(struct vx_core *chip, struct vx_rmh *rmh)
{
guard(mutex)(&chip->lock);
return vx_send_msg_nolock(chip, rmh);
}
/*
* vx_send_rih_nolock - send an RIH to xilinx
* @cmd: the command to send
*
* returns 0 if successful, or a negative error code.
* the error code can be VX-specific, retrieved via vx_get_error().
*
* this function doesn't call mutex at all.
*
* unlike RMH, no command is sent to DSP.
*/
int vx_send_rih_nolock(struct vx_core *chip, int cmd)
{
int err;
if (chip->chip_status & VX_STAT_IS_STALE)
return -EBUSY;
err = vx_reset_chk(chip);
if (err < 0)
return err;
/* send the IRQ */
err = vx_send_irq_dsp(chip, cmd);
if (err < 0)
return err;
/* Wait CHK = 1 */
err = vx_wait_isr_bit(chip, ISR_CHK);
if (err < 0)
return err;
/* If error, read RX */
if (vx_inb(chip, ISR) & ISR_ERR) {
err = vx_wait_for_rx_full(chip);
if (err < 0)
return err;
err = vx_inb(chip, RXH) << 16;
err |= vx_inb(chip, RXM) << 8;
err |= vx_inb(chip, RXL);
return -(VX_ERR_MASK | err);
}
return 0;
}
/*
* vx_send_rih - send an RIH with mutex
* @cmd: the command to send
*
* see vx_send_rih_nolock().
*/
int vx_send_rih(struct vx_core *chip, int cmd)
{
guard(mutex)(&chip->lock);
return vx_send_rih_nolock(chip, cmd);
}
#define END_OF_RESET_WAIT_TIME 500 /* us */
/**
* snd_vx_load_boot_image - boot up the xilinx interface
* @chip: VX core instance
* @boot: the boot record to load
*/
int snd_vx_load_boot_image(struct vx_core *chip, const struct firmware *boot)
{
unsigned int i;
int no_fillup = vx_has_new_dsp(chip);
/* check the length of boot image */
if (boot->size <= 0)
return -EINVAL;
if (boot->size % 3)
return -EINVAL;
#if 0
{
/* more strict check */
unsigned int c = ((u32)boot->data[0] << 16) | ((u32)boot->data[1] << 8) | boot->data[2];
if (boot->size != (c + 2) * 3)
return -EINVAL;
}
#endif
/* reset dsp */
vx_reset_dsp(chip);
udelay(END_OF_RESET_WAIT_TIME); /* another wait? */
/* download boot strap */
for (i = 0; i < 0x600; i += 3) {
if (i >= boot->size) {
if (no_fillup)
break;
if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) {
dev_err(chip->card->dev, "dsp boot failed at %d\n", i);
return -EIO;
}
vx_outb(chip, TXH, 0);
vx_outb(chip, TXM, 0);
vx_outb(chip, TXL, 0);
} else {
const unsigned char *image = boot->data + i;
if (vx_wait_isr_bit(chip, ISR_TX_EMPTY) < 0) {
dev_err(chip->card->dev, "dsp boot failed at %d\n", i);
return -EIO;
}
vx_outb(chip, TXH, image[0]);
vx_outb(chip, TXM, image[1]);
vx_outb(chip, TXL, image[2]);
}
}
return 0;
}
EXPORT_SYMBOL(snd_vx_load_boot_image);
/*
* vx_test_irq_src - query the source of interrupts
*
* called from irq handler only
*/
static int vx_test_irq_src(struct vx_core *chip, unsigned int *ret)
{
int err;
vx_init_rmh(&chip->irq_rmh, CMD_TEST_IT);
guard(mutex)(&chip->lock);
err = vx_send_msg_nolock(chip, &chip->irq_rmh);
if (err < 0)
*ret = 0;
else
*ret = chip->irq_rmh.Stat[0];
return err;
}
/*
* snd_vx_threaded_irq_handler - threaded irq handler
*/
irqreturn_t snd_vx_threaded_irq_handler(int irq, void *dev)
{
struct vx_core *chip = dev;
unsigned int events;
if (chip->chip_status & VX_STAT_IS_STALE)
return IRQ_HANDLED;
if (vx_test_irq_src(chip, &events) < 0)
return IRQ_HANDLED;
/* We must prevent any application using this DSP
* and block any further request until the application
* either unregisters or reloads the DSP
*/
if (events & FATAL_DSP_ERROR) {
dev_err(chip->card->dev, "vx_core: fatal DSP error!!\n");
return IRQ_HANDLED;
}
/* The start on time code conditions are filled (ie the time code
* received by the board is equal to one of those given to it).
*/
if (events & TIME_CODE_EVENT_PENDING) {
; /* so far, nothing to do yet */
}
/* The frequency has changed on the board (UER mode). */
if (events & FREQUENCY_CHANGE_EVENT_PENDING)
vx_change_frequency(chip);
/* update the pcm streams */
vx_pcm_update_intr(chip, events);
return IRQ_HANDLED;
}
EXPORT_SYMBOL(snd_vx_threaded_irq_handler);
/**
* snd_vx_irq_handler - interrupt handler
* @irq: irq number
* @dev: VX core instance
*/
irqreturn_t snd_vx_irq_handler(int irq, void *dev)
{
struct vx_core *chip = dev;
if (! (chip->chip_status & VX_STAT_CHIP_INIT) ||
(chip->chip_status & VX_STAT_IS_STALE))
return IRQ_NONE;
if (! vx_test_and_ack(chip))
return IRQ_WAKE_THREAD;
return IRQ_NONE;
}
EXPORT_SYMBOL(snd_vx_irq_handler);
/*
*/
static void vx_reset_board(struct vx_core *chip, int cold_reset)
{
if (snd_BUG_ON(!chip->ops->reset_board))
return;
/* current source, later sync'ed with target */
chip->audio_source = VX_AUDIO_SRC_LINE;
if (cold_reset) {
chip->audio_source_target = chip->audio_source;
chip->clock_source = INTERNAL_QUARTZ;
chip->clock_mode = VX_CLOCK_MODE_AUTO;
chip->freq = 48000;
chip->uer_detected = VX_UER_MODE_NOT_PRESENT;
chip->uer_bits = SNDRV_PCM_DEFAULT_CON_SPDIF;
}
chip->ops->reset_board(chip, cold_reset);
vx_reset_codec(chip, cold_reset);
vx_set_internal_clock(chip, chip->freq);
/* Reset the DSP */
vx_reset_dsp(chip);
if (vx_is_pcmcia(chip)) {
/* Acknowledge any pending IRQ and reset the MEMIRQ flag. */
vx_test_and_ack(chip);
vx_validate_irq(chip, 1);
}
/* init CBits */
vx_set_iec958_status(chip, chip->uer_bits);
}
/*
* proc interface
*/
static void vx_proc_read(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
{
struct vx_core *chip = entry->private_data;
static const char * const audio_src_vxp[] = { "Line", "Mic", "Digital" };
static const char * const audio_src_vx2[] = { "Analog", "Analog", "Digital" };
static const char * const clock_mode[] = { "Auto", "Internal", "External" };
static const char * const clock_src[] = { "Internal", "External" };
static const char * const uer_type[] = { "Consumer", "Professional", "Not Present" };
snd_iprintf(buffer, "%s\n", chip->card->longname);
snd_iprintf(buffer, "Xilinx Firmware: %s\n",
(chip->chip_status & VX_STAT_XILINX_LOADED) ? "Loaded" : "No");
snd_iprintf(buffer, "Device Initialized: %s\n",
(chip->chip_status & VX_STAT_DEVICE_INIT) ? "Yes" : "No");
snd_iprintf(buffer, "DSP audio info:");
if (chip->audio_info & VX_AUDIO_INFO_REAL_TIME)
snd_iprintf(buffer, " realtime");
if (chip->audio_info & VX_AUDIO_INFO_OFFLINE)
snd_iprintf(buffer, " offline");
if (chip->audio_info & VX_AUDIO_INFO_MPEG1)
snd_iprintf(buffer, " mpeg1");
if (chip->audio_info & VX_AUDIO_INFO_MPEG2)
snd_iprintf(buffer, " mpeg2");
if (chip->audio_info & VX_AUDIO_INFO_LINEAR_8)
snd_iprintf(buffer, " linear8");
if (chip->audio_info & VX_AUDIO_INFO_LINEAR_16)
snd_iprintf(buffer, " linear16");
if (chip->audio_info & VX_AUDIO_INFO_LINEAR_24)
snd_iprintf(buffer, " linear24");
snd_iprintf(buffer, "\n");
snd_iprintf(buffer, "Input Source: %s\n", vx_is_pcmcia(chip) ?
audio_src_vxp[chip->audio_source] :
audio_src_vx2[chip->audio_source]);
snd_iprintf(buffer, "Clock Mode: %s\n", clock_mode[chip->clock_mode]);
snd_iprintf(buffer, "Clock Source: %s\n", clock_src[chip->clock_source]);
snd_iprintf(buffer, "Frequency: %d\n", chip->freq);
snd_iprintf(buffer, "Detected Frequency: %d\n", chip->freq_detected);
snd_iprintf(buffer, "Detected UER type: %s\n", uer_type[chip->uer_detected]);
snd_iprintf(buffer, "Min/Max/Cur IBL: %d/%d/%d (granularity=%d)\n",
chip->ibl.min_size, chip->ibl.max_size, chip->ibl.size,
chip->ibl.granularity);
}
static void vx_proc_init(struct vx_core *chip)
{
snd_card_ro_proc_new(chip->card, "vx-status", chip, vx_proc_read);
}
/**
* snd_vx_dsp_boot - load the DSP boot
* @chip: VX core instance
* @boot: firmware data
*/
int snd_vx_dsp_boot(struct vx_core *chip, const struct firmware *boot)
{
int err;
int cold_reset = !(chip->chip_status & VX_STAT_DEVICE_INIT);
vx_reset_board(chip, cold_reset);
vx_validate_irq(chip, 0);
err = snd_vx_load_boot_image(chip, boot);
if (err < 0)
return err;
msleep(10);
return 0;
}
EXPORT_SYMBOL(snd_vx_dsp_boot);
/**
* snd_vx_dsp_load - load the DSP image
* @chip: VX core instance
* @dsp: firmware data
*/
int snd_vx_dsp_load(struct vx_core *chip, const struct firmware *dsp)
{
unsigned int i;
int err;
unsigned int csum = 0;
const unsigned char *image, *cptr;
if (dsp->size % 3)
return -EINVAL;
vx_toggle_dac_mute(chip, 1);
/* Transfert data buffer from PC to DSP */
for (i = 0; i < dsp->size; i += 3) {
image = dsp->data + i;
/* Wait DSP ready for a new read */
err = vx_wait_isr_bit(chip, ISR_TX_EMPTY);
if (err < 0) {
dev_err(chip->card->dev,
"dsp loading error at position %d\n", i);
return err;
}
cptr = image;
csum ^= *cptr;
csum = (csum >> 24) | (csum << 8);
vx_outb(chip, TXH, *cptr++);
csum ^= *cptr;
csum = (csum >> 24) | (csum << 8);
vx_outb(chip, TXM, *cptr++);
csum ^= *cptr;
csum = (csum >> 24) | (csum << 8);
vx_outb(chip, TXL, *cptr++);
}
msleep(200);
err = vx_wait_isr_bit(chip, ISR_CHK);
if (err < 0)
return err;
vx_toggle_dac_mute(chip, 0);
vx_test_and_ack(chip);
vx_validate_irq(chip, 1);
return 0;
}
EXPORT_SYMBOL(snd_vx_dsp_load);
#ifdef CONFIG_PM
/*
* suspend
*/
int snd_vx_suspend(struct vx_core *chip)
{
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
chip->chip_status |= VX_STAT_IN_SUSPEND;
return 0;
}
EXPORT_SYMBOL(snd_vx_suspend);
/*
* resume
*/
int snd_vx_resume(struct vx_core *chip)
{
int i, err;
chip->chip_status &= ~VX_STAT_CHIP_INIT;
for (i = 0; i < 4; i++) {
if (! chip->firmware[i])
continue;
err = chip->ops->load_dsp(chip, i, chip->firmware[i]);
if (err < 0) {
dev_err(chip->card->dev,
"vx: firmware resume error at DSP %d\n", i);
return -EIO;
}
}
chip->chip_status |= VX_STAT_CHIP_INIT;
chip->chip_status &= ~VX_STAT_IN_SUSPEND;
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
return 0;
}
EXPORT_SYMBOL(snd_vx_resume);
#endif
static void snd_vx_release(struct device *dev, void *data)
{
snd_vx_free_firmware(data);
}
/**
* snd_vx_create - constructor for struct vx_core
* @card: card instance
* @hw: hardware specific record
* @ops: VX ops pointer
* @extra_size: extra byte size to allocate appending to chip
*
* this function allocates the instance and prepare for the hardware
* initialization.
*
* The object is managed via devres, and will be automatically released.
*
* return the instance pointer if successful, NULL in error.
*/
struct vx_core *snd_vx_create(struct snd_card *card,
const struct snd_vx_hardware *hw,
const struct snd_vx_ops *ops,
int extra_size)
{
struct vx_core *chip;
if (snd_BUG_ON(!card || !hw || !ops))
return NULL;
chip = devres_alloc(snd_vx_release, sizeof(*chip) + extra_size,
GFP_KERNEL);
if (!chip)
return NULL;
mutex_init(&chip->lock);
chip->irq = -1;
chip->hw = hw;
chip->type = hw->type;
chip->ops = ops;
mutex_init(&chip->mixer_mutex);
chip->card = card;
card->private_data = chip;
strscpy(card->driver, hw->name);
sprintf(card->shortname, "Digigram %s", hw->name);
vx_proc_init(chip);
return chip;
}
EXPORT_SYMBOL(snd_vx_create);
+112
View File
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Digigram VX soundcards
*
* DSP firmware management
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*/
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/hwdep.h>
#include <sound/vx_core.h>
MODULE_FIRMWARE("vx/bx_1_vxp.b56");
MODULE_FIRMWARE("vx/bx_1_vp4.b56");
MODULE_FIRMWARE("vx/x1_1_vx2.xlx");
MODULE_FIRMWARE("vx/x1_2_v22.xlx");
MODULE_FIRMWARE("vx/x1_1_vxp.xlx");
MODULE_FIRMWARE("vx/x1_1_vp4.xlx");
MODULE_FIRMWARE("vx/bd56002.boot");
MODULE_FIRMWARE("vx/bd563v2.boot");
MODULE_FIRMWARE("vx/bd563s3.boot");
MODULE_FIRMWARE("vx/l_1_vx2.d56");
MODULE_FIRMWARE("vx/l_1_v22.d56");
MODULE_FIRMWARE("vx/l_1_vxp.d56");
MODULE_FIRMWARE("vx/l_1_vp4.d56");
int snd_vx_setup_firmware(struct vx_core *chip)
{
static const char * const fw_files[VX_TYPE_NUMS][4] = {
[VX_TYPE_BOARD] = {
NULL, "x1_1_vx2.xlx", "bd56002.boot", "l_1_vx2.d56",
},
[VX_TYPE_V2] = {
NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56",
},
[VX_TYPE_MIC] = {
NULL, "x1_2_v22.xlx", "bd563v2.boot", "l_1_v22.d56",
},
[VX_TYPE_VXPOCKET] = {
"bx_1_vxp.b56", "x1_1_vxp.xlx", "bd563s3.boot", "l_1_vxp.d56"
},
[VX_TYPE_VXP440] = {
"bx_1_vp4.b56", "x1_1_vp4.xlx", "bd563s3.boot", "l_1_vp4.d56"
},
};
int i, err;
for (i = 0; i < 4; i++) {
char path[32];
const struct firmware *fw;
if (! fw_files[chip->type][i])
continue;
sprintf(path, "vx/%s", fw_files[chip->type][i]);
if (request_firmware(&fw, path, chip->card->dev)) {
dev_err(chip->card->dev, "vx: can't load firmware %s\n", path);
return -ENOENT;
}
err = chip->ops->load_dsp(chip, i, fw);
if (err < 0) {
release_firmware(fw);
return err;
}
if (i == 1)
chip->chip_status |= VX_STAT_XILINX_LOADED;
#ifdef CONFIG_PM
chip->firmware[i] = fw;
#else
release_firmware(fw);
#endif
}
/* ok, we reached to the last one */
/* create the devices if not built yet */
err = snd_vx_pcm_new(chip);
if (err < 0)
return err;
err = snd_vx_mixer_new(chip);
if (err < 0)
return err;
if (chip->ops->add_controls) {
err = chip->ops->add_controls(chip);
if (err < 0)
return err;
}
chip->chip_status |= VX_STAT_DEVICE_INIT;
chip->chip_status |= VX_STAT_CHIP_INIT;
return snd_card_register(chip->card);
}
/* exported */
void snd_vx_free_firmware(struct vx_core *chip)
{
#ifdef CONFIG_PM
int i;
for (i = 0; i < 4; i++)
release_firmware(chip->firmware[i]);
#endif
}
EXPORT_SYMBOL(snd_vx_setup_firmware);
EXPORT_SYMBOL(snd_vx_free_firmware);
+982
View File
@@ -0,0 +1,982 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Digigram VX soundcards
*
* Common mixer part
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*/
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/vx_core.h>
#include "vx_cmd.h"
/*
* write a codec data (24bit)
*/
static void vx_write_codec_reg(struct vx_core *chip, int codec, unsigned int data)
{
if (snd_BUG_ON(!chip->ops->write_codec))
return;
if (chip->chip_status & VX_STAT_IS_STALE)
return;
guard(mutex)(&chip->lock);
chip->ops->write_codec(chip, codec, data);
}
/*
* Data type used to access the Codec
*/
union vx_codec_data {
u32 l;
#ifdef SNDRV_BIG_ENDIAN
struct w {
u16 h;
u16 l;
} w;
struct b {
u8 hh;
u8 mh;
u8 ml;
u8 ll;
} b;
#else /* LITTLE_ENDIAN */
struct w {
u16 l;
u16 h;
} w;
struct b {
u8 ll;
u8 ml;
u8 mh;
u8 hh;
} b;
#endif
};
#define SET_CDC_DATA_SEL(di,s) ((di).b.mh = (u8) (s))
#define SET_CDC_DATA_REG(di,r) ((di).b.ml = (u8) (r))
#define SET_CDC_DATA_VAL(di,d) ((di).b.ll = (u8) (d))
#define SET_CDC_DATA_INIT(di) ((di).l = 0L, SET_CDC_DATA_SEL(di,XX_CODEC_SELECTOR))
/*
* set up codec register and write the value
* @codec: the codec id, 0 or 1
* @reg: register index
* @val: data value
*/
static void vx_set_codec_reg(struct vx_core *chip, int codec, int reg, int val)
{
union vx_codec_data data;
/* DAC control register */
SET_CDC_DATA_INIT(data);
SET_CDC_DATA_REG(data, reg);
SET_CDC_DATA_VAL(data, val);
vx_write_codec_reg(chip, codec, data.l);
}
/*
* vx_set_analog_output_level - set the output attenuation level
* @codec: the output codec, 0 or 1. (1 for VXP440 only)
* @left: left output level, 0 = mute
* @right: right output level
*/
static void vx_set_analog_output_level(struct vx_core *chip, int codec, int left, int right)
{
left = chip->hw->output_level_max - left;
right = chip->hw->output_level_max - right;
if (chip->ops->akm_write) {
chip->ops->akm_write(chip, XX_CODEC_LEVEL_LEFT_REGISTER, left);
chip->ops->akm_write(chip, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
} else {
/* convert to attenuation level: 0 = 0dB (max), 0xe3 = -113.5 dB (min) */
vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_LEFT_REGISTER, left);
vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_RIGHT_REGISTER, right);
}
}
/*
* vx_toggle_dac_mute - mute/unmute DAC
* @mute: 0 = unmute, 1 = mute
*/
#define DAC_ATTEN_MIN 0x08
#define DAC_ATTEN_MAX 0x38
void vx_toggle_dac_mute(struct vx_core *chip, int mute)
{
unsigned int i;
for (i = 0; i < chip->hw->num_codecs; i++) {
if (chip->ops->akm_write)
chip->ops->akm_write(chip, XX_CODEC_DAC_CONTROL_REGISTER, mute); /* XXX */
else
vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER,
mute ? DAC_ATTEN_MAX : DAC_ATTEN_MIN);
}
}
/*
* vx_reset_codec - reset and initialize the codecs
*/
void vx_reset_codec(struct vx_core *chip, int cold_reset)
{
unsigned int i;
int port = chip->type >= VX_TYPE_VXPOCKET ? 0x75 : 0x65;
chip->ops->reset_codec(chip);
/* AKM codecs should be initialized in reset_codec callback */
if (! chip->ops->akm_write) {
/* initialize old codecs */
for (i = 0; i < chip->hw->num_codecs; i++) {
/* DAC control register (change level when zero crossing + mute) */
vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, DAC_ATTEN_MAX);
/* ADC control register */
vx_set_codec_reg(chip, i, XX_CODEC_ADC_CONTROL_REGISTER, 0x00);
/* Port mode register */
vx_set_codec_reg(chip, i, XX_CODEC_PORT_MODE_REGISTER, port);
/* Clock control register */
vx_set_codec_reg(chip, i, XX_CODEC_CLOCK_CONTROL_REGISTER, 0x00);
}
}
/* mute analog output */
for (i = 0; i < chip->hw->num_codecs; i++) {
chip->output_level[i][0] = 0;
chip->output_level[i][1] = 0;
vx_set_analog_output_level(chip, i, 0, 0);
}
}
/*
* change the audio input source
* @src: the target source (VX_AUDIO_SRC_XXX)
*/
static void vx_change_audio_source(struct vx_core *chip, int src)
{
if (chip->chip_status & VX_STAT_IS_STALE)
return;
guard(mutex)(&chip->lock);
chip->ops->change_audio_source(chip, src);
}
/*
* change the audio source if necessary and possible
* returns 1 if the source is actually changed.
*/
int vx_sync_audio_source(struct vx_core *chip)
{
if (chip->audio_source_target == chip->audio_source ||
chip->pcm_running)
return 0;
vx_change_audio_source(chip, chip->audio_source_target);
chip->audio_source = chip->audio_source_target;
return 1;
}
/*
* audio level, mute, monitoring
*/
struct vx_audio_level {
unsigned int has_level: 1;
unsigned int has_monitor_level: 1;
unsigned int has_mute: 1;
unsigned int has_monitor_mute: 1;
unsigned int mute;
unsigned int monitor_mute;
short level;
short monitor_level;
};
static int vx_adjust_audio_level(struct vx_core *chip, int audio, int capture,
struct vx_audio_level *info)
{
struct vx_rmh rmh;
if (chip->chip_status & VX_STAT_IS_STALE)
return -EBUSY;
vx_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST);
if (capture)
rmh.Cmd[0] |= COMMAND_RECORD_MASK;
/* Add Audio IO mask */
rmh.Cmd[1] = 1 << audio;
rmh.Cmd[2] = 0;
if (info->has_level) {
rmh.Cmd[0] |= VALID_AUDIO_IO_DIGITAL_LEVEL;
rmh.Cmd[2] |= info->level;
}
if (info->has_monitor_level) {
rmh.Cmd[0] |= VALID_AUDIO_IO_MONITORING_LEVEL;
rmh.Cmd[2] |= ((unsigned int)info->monitor_level << 10);
}
if (info->has_mute) {
rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_LEVEL;
if (info->mute)
rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_LEVEL;
}
if (info->has_monitor_mute) {
/* validate flag for M2 at least to unmute it */
rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_MONITORING_1 | VALID_AUDIO_IO_MUTE_MONITORING_2;
if (info->monitor_mute)
rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_MONITORING_1;
}
return vx_send_msg(chip, &rmh);
}
#if 0 // not used
static int vx_read_audio_level(struct vx_core *chip, int audio, int capture,
struct vx_audio_level *info)
{
int err;
struct vx_rmh rmh;
memset(info, 0, sizeof(*info));
vx_init_rmh(&rmh, CMD_GET_AUDIO_LEVELS);
if (capture)
rmh.Cmd[0] |= COMMAND_RECORD_MASK;
/* Add Audio IO mask */
rmh.Cmd[1] = 1 << audio;
err = vx_send_msg(chip, &rmh);
if (err < 0)
return err;
info.level = rmh.Stat[0] & MASK_DSP_WORD_LEVEL;
info.monitor_level = (rmh.Stat[0] >> 10) & MASK_DSP_WORD_LEVEL;
info.mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_LEVEL) ? 1 : 0;
info.monitor_mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_MONITORING_1) ? 1 : 0;
return 0;
}
#endif // not used
/*
* set the monitoring level and mute state of the given audio
* no more static, because must be called from vx_pcm to demute monitoring
*/
int vx_set_monitor_level(struct vx_core *chip, int audio, int level, int active)
{
struct vx_audio_level info;
memset(&info, 0, sizeof(info));
info.has_monitor_level = 1;
info.monitor_level = level;
info.has_monitor_mute = 1;
info.monitor_mute = !active;
chip->audio_monitor[audio] = level;
chip->audio_monitor_active[audio] = active;
return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */
}
/*
* set the mute status of the given audio
*/
static int vx_set_audio_switch(struct vx_core *chip, int audio, int active)
{
struct vx_audio_level info;
memset(&info, 0, sizeof(info));
info.has_mute = 1;
info.mute = !active;
chip->audio_active[audio] = active;
return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */
}
/*
* set the mute status of the given audio
*/
static int vx_set_audio_gain(struct vx_core *chip, int audio, int capture, int level)
{
struct vx_audio_level info;
memset(&info, 0, sizeof(info));
info.has_level = 1;
info.level = level;
chip->audio_gain[capture][audio] = level;
return vx_adjust_audio_level(chip, audio, capture, &info);
}
/*
* reset all audio levels
*/
static void vx_reset_audio_levels(struct vx_core *chip)
{
unsigned int i, c;
struct vx_audio_level info;
memset(chip->audio_gain, 0, sizeof(chip->audio_gain));
memset(chip->audio_active, 0, sizeof(chip->audio_active));
memset(chip->audio_monitor, 0, sizeof(chip->audio_monitor));
memset(chip->audio_monitor_active, 0, sizeof(chip->audio_monitor_active));
for (c = 0; c < 2; c++) {
for (i = 0; i < chip->hw->num_ins * 2; i++) {
memset(&info, 0, sizeof(info));
if (c == 0) {
info.has_monitor_level = 1;
info.has_mute = 1;
info.has_monitor_mute = 1;
}
info.has_level = 1;
info.level = CVAL_0DB; /* default: 0dB */
vx_adjust_audio_level(chip, i, c, &info);
chip->audio_gain[c][i] = CVAL_0DB;
chip->audio_monitor[i] = CVAL_0DB;
}
}
}
/*
* VU, peak meter record
*/
#define VU_METER_CHANNELS 2
struct vx_vu_meter {
int saturated;
int vu_level;
int peak_level;
};
/*
* get the VU and peak meter values
* @audio: the audio index
* @capture: 0 = playback, 1 = capture operation
* @info: the array of vx_vu_meter records (size = 2).
*/
static int vx_get_audio_vu_meter(struct vx_core *chip, int audio, int capture, struct vx_vu_meter *info)
{
struct vx_rmh rmh;
int i, err;
if (chip->chip_status & VX_STAT_IS_STALE)
return -EBUSY;
vx_init_rmh(&rmh, CMD_AUDIO_VU_PIC_METER);
rmh.LgStat += 2 * VU_METER_CHANNELS;
if (capture)
rmh.Cmd[0] |= COMMAND_RECORD_MASK;
/* Add Audio IO mask */
rmh.Cmd[1] = 0;
for (i = 0; i < VU_METER_CHANNELS; i++)
rmh.Cmd[1] |= 1 << (audio + i);
err = vx_send_msg(chip, &rmh);
if (err < 0)
return err;
/* Read response */
for (i = 0; i < 2 * VU_METER_CHANNELS; i +=2) {
info->saturated = (rmh.Stat[0] & (1 << (audio + i))) ? 1 : 0;
info->vu_level = rmh.Stat[i + 1];
info->peak_level = rmh.Stat[i + 2];
info++;
}
return 0;
}
/*
* control API entries
*/
/*
* output level control
*/
static int vx_output_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = chip->hw->output_level_max;
return 0;
}
static int vx_output_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int codec = kcontrol->id.index;
guard(mutex)(&chip->mixer_mutex);
ucontrol->value.integer.value[0] = chip->output_level[codec][0];
ucontrol->value.integer.value[1] = chip->output_level[codec][1];
return 0;
}
static int vx_output_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int codec = kcontrol->id.index;
unsigned int val[2], vmax;
vmax = chip->hw->output_level_max;
val[0] = ucontrol->value.integer.value[0];
val[1] = ucontrol->value.integer.value[1];
if (val[0] > vmax || val[1] > vmax)
return -EINVAL;
guard(mutex)(&chip->mixer_mutex);
if (val[0] != chip->output_level[codec][0] ||
val[1] != chip->output_level[codec][1]) {
vx_set_analog_output_level(chip, codec, val[0], val[1]);
chip->output_level[codec][0] = val[0];
chip->output_level[codec][1] = val[1];
return 1;
}
return 0;
}
static const struct snd_kcontrol_new vx_control_output_level = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
.name = "Master Playback Volume",
.info = vx_output_level_info,
.get = vx_output_level_get,
.put = vx_output_level_put,
/* tlv will be filled later */
};
/*
* audio source select
*/
static int vx_audio_src_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts_mic[3] = {
"Digital", "Line", "Mic"
};
static const char * const texts_vx2[2] = {
"Digital", "Analog"
};
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
if (chip->type >= VX_TYPE_VXPOCKET)
return snd_ctl_enum_info(uinfo, 1, 3, texts_mic);
else
return snd_ctl_enum_info(uinfo, 1, 2, texts_vx2);
}
static int vx_audio_src_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = chip->audio_source_target;
return 0;
}
static int vx_audio_src_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
if (chip->type >= VX_TYPE_VXPOCKET) {
if (ucontrol->value.enumerated.item[0] > 2)
return -EINVAL;
} else {
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
}
guard(mutex)(&chip->mixer_mutex);
if (chip->audio_source_target != ucontrol->value.enumerated.item[0]) {
chip->audio_source_target = ucontrol->value.enumerated.item[0];
vx_sync_audio_source(chip);
return 1;
}
return 0;
}
static const struct snd_kcontrol_new vx_control_audio_src = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Capture Source",
.info = vx_audio_src_info,
.get = vx_audio_src_get,
.put = vx_audio_src_put,
};
/*
* clock mode selection
*/
static int vx_clock_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
static const char * const texts[3] = {
"Auto", "Internal", "External"
};
return snd_ctl_enum_info(uinfo, 1, 3, texts);
}
static int vx_clock_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
ucontrol->value.enumerated.item[0] = chip->clock_mode;
return 0;
}
static int vx_clock_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
if (ucontrol->value.enumerated.item[0] > 2)
return -EINVAL;
guard(mutex)(&chip->mixer_mutex);
if (chip->clock_mode != ucontrol->value.enumerated.item[0]) {
chip->clock_mode = ucontrol->value.enumerated.item[0];
vx_set_clock(chip, chip->freq);
return 1;
}
return 0;
}
static const struct snd_kcontrol_new vx_control_clock_mode = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Clock Mode",
.info = vx_clock_mode_info,
.get = vx_clock_mode_get,
.put = vx_clock_mode_put,
};
/*
* Audio Gain
*/
static int vx_audio_gain_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 = CVAL_MAX;
return 0;
}
static int vx_audio_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
int capture = (kcontrol->private_value >> 8) & 1;
guard(mutex)(&chip->mixer_mutex);
ucontrol->value.integer.value[0] = chip->audio_gain[capture][audio];
ucontrol->value.integer.value[1] = chip->audio_gain[capture][audio+1];
return 0;
}
static int vx_audio_gain_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
int capture = (kcontrol->private_value >> 8) & 1;
unsigned int val[2];
val[0] = ucontrol->value.integer.value[0];
val[1] = ucontrol->value.integer.value[1];
if (val[0] > CVAL_MAX || val[1] > CVAL_MAX)
return -EINVAL;
guard(mutex)(&chip->mixer_mutex);
if (val[0] != chip->audio_gain[capture][audio] ||
val[1] != chip->audio_gain[capture][audio+1]) {
vx_set_audio_gain(chip, audio, capture, val[0]);
vx_set_audio_gain(chip, audio+1, capture, val[1]);
return 1;
}
return 0;
}
static int vx_audio_monitor_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
guard(mutex)(&chip->mixer_mutex);
ucontrol->value.integer.value[0] = chip->audio_monitor[audio];
ucontrol->value.integer.value[1] = chip->audio_monitor[audio+1];
return 0;
}
static int vx_audio_monitor_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
unsigned int val[2];
val[0] = ucontrol->value.integer.value[0];
val[1] = ucontrol->value.integer.value[1];
if (val[0] > CVAL_MAX || val[1] > CVAL_MAX)
return -EINVAL;
guard(mutex)(&chip->mixer_mutex);
if (val[0] != chip->audio_monitor[audio] ||
val[1] != chip->audio_monitor[audio+1]) {
vx_set_monitor_level(chip, audio, val[0],
chip->audio_monitor_active[audio]);
vx_set_monitor_level(chip, audio+1, val[1],
chip->audio_monitor_active[audio+1]);
return 1;
}
return 0;
}
#define vx_audio_sw_info snd_ctl_boolean_stereo_info
static int vx_audio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
guard(mutex)(&chip->mixer_mutex);
ucontrol->value.integer.value[0] = chip->audio_active[audio];
ucontrol->value.integer.value[1] = chip->audio_active[audio+1];
return 0;
}
static int vx_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
guard(mutex)(&chip->mixer_mutex);
if (ucontrol->value.integer.value[0] != chip->audio_active[audio] ||
ucontrol->value.integer.value[1] != chip->audio_active[audio+1]) {
vx_set_audio_switch(chip, audio,
!!ucontrol->value.integer.value[0]);
vx_set_audio_switch(chip, audio+1,
!!ucontrol->value.integer.value[1]);
return 1;
}
return 0;
}
static int vx_monitor_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
guard(mutex)(&chip->mixer_mutex);
ucontrol->value.integer.value[0] = chip->audio_monitor_active[audio];
ucontrol->value.integer.value[1] = chip->audio_monitor_active[audio+1];
return 0;
}
static int vx_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
int audio = kcontrol->private_value & 0xff;
guard(mutex)(&chip->mixer_mutex);
if (ucontrol->value.integer.value[0] != chip->audio_monitor_active[audio] ||
ucontrol->value.integer.value[1] != chip->audio_monitor_active[audio+1]) {
vx_set_monitor_level(chip, audio, chip->audio_monitor[audio],
!!ucontrol->value.integer.value[0]);
vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1],
!!ucontrol->value.integer.value[1]);
return 1;
}
return 0;
}
static const DECLARE_TLV_DB_SCALE(db_scale_audio_gain, -10975, 25, 0);
static const struct snd_kcontrol_new vx_control_audio_gain = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
/* name will be filled later */
.info = vx_audio_gain_info,
.get = vx_audio_gain_get,
.put = vx_audio_gain_put,
.tlv = { .p = db_scale_audio_gain },
};
static const struct snd_kcontrol_new vx_control_output_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Switch",
.info = vx_audio_sw_info,
.get = vx_audio_sw_get,
.put = vx_audio_sw_put
};
static const struct snd_kcontrol_new vx_control_monitor_gain = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Monitoring Volume",
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
.info = vx_audio_gain_info, /* shared */
.get = vx_audio_monitor_get,
.put = vx_audio_monitor_put,
.tlv = { .p = db_scale_audio_gain },
};
static const struct snd_kcontrol_new vx_control_monitor_switch = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Monitoring Switch",
.info = vx_audio_sw_info, /* shared */
.get = vx_monitor_sw_get,
.put = vx_monitor_sw_put
};
/*
* IEC958 status bits
*/
static int vx_iec958_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
uinfo->count = 1;
return 0;
}
static int vx_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
guard(mutex)(&chip->mixer_mutex);
ucontrol->value.iec958.status[0] = (chip->uer_bits >> 0) & 0xff;
ucontrol->value.iec958.status[1] = (chip->uer_bits >> 8) & 0xff;
ucontrol->value.iec958.status[2] = (chip->uer_bits >> 16) & 0xff;
ucontrol->value.iec958.status[3] = (chip->uer_bits >> 24) & 0xff;
return 0;
}
static int vx_iec958_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
ucontrol->value.iec958.status[0] = 0xff;
ucontrol->value.iec958.status[1] = 0xff;
ucontrol->value.iec958.status[2] = 0xff;
ucontrol->value.iec958.status[3] = 0xff;
return 0;
}
static int vx_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
unsigned int val;
val = (ucontrol->value.iec958.status[0] << 0) |
(ucontrol->value.iec958.status[1] << 8) |
(ucontrol->value.iec958.status[2] << 16) |
(ucontrol->value.iec958.status[3] << 24);
guard(mutex)(&chip->mixer_mutex);
if (chip->uer_bits != val) {
chip->uer_bits = val;
vx_set_iec958_status(chip, val);
return 1;
}
return 0;
}
static const struct snd_kcontrol_new vx_control_iec958_mask = {
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK),
.info = vx_iec958_info, /* shared */
.get = vx_iec958_mask_get,
};
static const struct snd_kcontrol_new vx_control_iec958 = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
.info = vx_iec958_info,
.get = vx_iec958_get,
.put = vx_iec958_put
};
/*
* VU meter
*/
#define METER_MAX 0xff
#define METER_SHIFT 16
static int vx_vu_meter_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 = METER_MAX;
return 0;
}
static int vx_vu_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
struct vx_vu_meter meter[2];
int audio = kcontrol->private_value & 0xff;
int capture = (kcontrol->private_value >> 8) & 1;
vx_get_audio_vu_meter(chip, audio, capture, meter);
ucontrol->value.integer.value[0] = meter[0].vu_level >> METER_SHIFT;
ucontrol->value.integer.value[1] = meter[1].vu_level >> METER_SHIFT;
return 0;
}
static int vx_peak_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
struct vx_vu_meter meter[2];
int audio = kcontrol->private_value & 0xff;
int capture = (kcontrol->private_value >> 8) & 1;
vx_get_audio_vu_meter(chip, audio, capture, meter);
ucontrol->value.integer.value[0] = meter[0].peak_level >> METER_SHIFT;
ucontrol->value.integer.value[1] = meter[1].peak_level >> METER_SHIFT;
return 0;
}
#define vx_saturation_info snd_ctl_boolean_stereo_info
static int vx_saturation_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct vx_core *chip = snd_kcontrol_chip(kcontrol);
struct vx_vu_meter meter[2];
int audio = kcontrol->private_value & 0xff;
vx_get_audio_vu_meter(chip, audio, 1, meter); /* capture only */
ucontrol->value.integer.value[0] = meter[0].saturated;
ucontrol->value.integer.value[1] = meter[1].saturated;
return 0;
}
static const struct snd_kcontrol_new vx_control_vu_meter = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
/* name will be filled later */
.info = vx_vu_meter_info,
.get = vx_vu_meter_get,
};
static const struct snd_kcontrol_new vx_control_peak_meter = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
/* name will be filled later */
.info = vx_vu_meter_info, /* shared */
.get = vx_peak_meter_get,
};
static const struct snd_kcontrol_new vx_control_saturation = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Input Saturation",
.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
.info = vx_saturation_info,
.get = vx_saturation_get,
};
/*
*
*/
int snd_vx_mixer_new(struct vx_core *chip)
{
unsigned int i, c;
int err;
struct snd_kcontrol_new temp;
struct snd_card *card = chip->card;
char name[32];
strscpy(card->mixername, card->driver);
/* output level controls */
for (i = 0; i < chip->hw->num_outs; i++) {
temp = vx_control_output_level;
temp.index = i;
temp.tlv.p = chip->hw->output_level_db_scale;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
}
/* PCM volumes, switches, monitoring */
for (i = 0; i < chip->hw->num_outs; i++) {
int val = i * 2;
temp = vx_control_audio_gain;
temp.index = i;
temp.name = "PCM Playback Volume";
temp.private_value = val;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
temp = vx_control_output_switch;
temp.index = i;
temp.private_value = val;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
temp = vx_control_monitor_gain;
temp.index = i;
temp.private_value = val;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
temp = vx_control_monitor_switch;
temp.index = i;
temp.private_value = val;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
}
for (i = 0; i < chip->hw->num_outs; i++) {
temp = vx_control_audio_gain;
temp.index = i;
temp.name = "PCM Capture Volume";
temp.private_value = (i * 2) | (1 << 8);
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
}
/* Audio source */
err = snd_ctl_add(card, snd_ctl_new1(&vx_control_audio_src, chip));
if (err < 0)
return err;
/* clock mode */
err = snd_ctl_add(card, snd_ctl_new1(&vx_control_clock_mode, chip));
if (err < 0)
return err;
/* IEC958 controls */
err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958_mask, chip));
if (err < 0)
return err;
err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958, chip));
if (err < 0)
return err;
/* VU, peak, saturation meters */
for (c = 0; c < 2; c++) {
static const char * const dir[2] = { "Output", "Input" };
for (i = 0; i < chip->hw->num_ins; i++) {
int val = (i * 2) | (c << 8);
if (c == 1) {
temp = vx_control_saturation;
temp.index = i;
temp.private_value = val;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
}
sprintf(name, "%s VU Meter", dir[c]);
temp = vx_control_vu_meter;
temp.index = i;
temp.name = name;
temp.private_value = val;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
sprintf(name, "%s Peak Meter", dir[c]);
temp = vx_control_peak_meter;
temp.index = i;
temp.name = name;
temp.private_value = val;
err = snd_ctl_add(card, snd_ctl_new1(&temp, chip));
if (err < 0)
return err;
}
}
vx_reset_audio_levels(chip);
return 0;
}
File diff suppressed because it is too large Load Diff
+294
View File
@@ -0,0 +1,294 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Digigram VX soundcards
*
* IEC958 stuff
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*/
#include <linux/delay.h>
#include <sound/core.h>
#include <sound/vx_core.h>
#include "vx_cmd.h"
/*
* vx_modify_board_clock - tell the board that its clock has been modified
* @sync: DSP needs to resynchronize its FIFO
*/
static int vx_modify_board_clock(struct vx_core *chip, int sync)
{
struct vx_rmh rmh;
vx_init_rmh(&rmh, CMD_MODIFY_CLOCK);
/* Ask the DSP to resynchronize its FIFO. */
if (sync)
rmh.Cmd[0] |= CMD_MODIFY_CLOCK_S_BIT;
return vx_send_msg(chip, &rmh);
}
/*
* vx_modify_board_inputs - resync audio inputs
*/
static int vx_modify_board_inputs(struct vx_core *chip)
{
struct vx_rmh rmh;
vx_init_rmh(&rmh, CMD_RESYNC_AUDIO_INPUTS);
rmh.Cmd[0] |= 1 << 0; /* reference: AUDIO 0 */
return vx_send_msg(chip, &rmh);
}
/*
* vx_read_one_cbit - read one bit from UER config
* @index: the bit index
* returns 0 or 1.
*/
static int vx_read_one_cbit(struct vx_core *chip, int index)
{
int val;
guard(mutex)(&chip->lock);
if (chip->type >= VX_TYPE_VXPOCKET) {
vx_outb(chip, CSUER, 1); /* read */
vx_outb(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK);
val = (vx_inb(chip, RUER) >> 7) & 0x01;
} else {
vx_outl(chip, CSUER, 1); /* read */
vx_outl(chip, RUER, index & XX_UER_CBITS_OFFSET_MASK);
val = (vx_inl(chip, RUER) >> 7) & 0x01;
}
return val;
}
/*
* vx_write_one_cbit - write one bit to UER config
* @index: the bit index
* @val: bit value, 0 or 1
*/
static void vx_write_one_cbit(struct vx_core *chip, int index, int val)
{
val = !!val; /* 0 or 1 */
guard(mutex)(&chip->lock);
if (vx_is_pcmcia(chip)) {
vx_outb(chip, CSUER, 0); /* write */
vx_outb(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK));
} else {
vx_outl(chip, CSUER, 0); /* write */
vx_outl(chip, RUER, (val << 7) | (index & XX_UER_CBITS_OFFSET_MASK));
}
}
/*
* vx_read_uer_status - read the current UER status
* @mode: pointer to store the UER mode, VX_UER_MODE_XXX
*
* returns the frequency of UER, or 0 if not sync,
* or a negative error code.
*/
static int vx_read_uer_status(struct vx_core *chip, unsigned int *mode)
{
int val, freq;
/* Default values */
freq = 0;
/* Read UER status */
if (vx_is_pcmcia(chip))
val = vx_inb(chip, CSUER);
else
val = vx_inl(chip, CSUER);
if (val < 0)
return val;
/* If clock is present, read frequency */
if (val & VX_SUER_CLOCK_PRESENT_MASK) {
switch (val & VX_SUER_FREQ_MASK) {
case VX_SUER_FREQ_32KHz_MASK:
freq = 32000;
break;
case VX_SUER_FREQ_44KHz_MASK:
freq = 44100;
break;
case VX_SUER_FREQ_48KHz_MASK:
freq = 48000;
break;
}
}
if (val & VX_SUER_DATA_PRESENT_MASK)
/* bit 0 corresponds to consumer/professional bit */
*mode = vx_read_one_cbit(chip, 0) ?
VX_UER_MODE_PROFESSIONAL : VX_UER_MODE_CONSUMER;
else
*mode = VX_UER_MODE_NOT_PRESENT;
return freq;
}
/*
* compute the sample clock value from frequency
*
* The formula is as follows:
*
* HexFreq = (dword) ((double) ((double) 28224000 / (double) Frequency))
* switch ( HexFreq & 0x00000F00 )
* case 0x00000100: ;
* case 0x00000200:
* case 0x00000300: HexFreq -= 0x00000201 ;
* case 0x00000400:
* case 0x00000500:
* case 0x00000600:
* case 0x00000700: HexFreq = (dword) (((double) 28224000 / (double) (Frequency*2)) - 1)
* default : HexFreq = (dword) ((double) 28224000 / (double) (Frequency*4)) - 0x000001FF
*/
static int vx_calc_clock_from_freq(struct vx_core *chip, int freq)
{
int hexfreq;
if (snd_BUG_ON(freq <= 0))
return 0;
hexfreq = (28224000 * 10) / freq;
hexfreq = (hexfreq + 5) / 10;
/* max freq = 55125 Hz */
if (snd_BUG_ON(hexfreq <= 0x00000200))
return 0;
if (hexfreq <= 0x03ff)
return hexfreq - 0x00000201;
if (hexfreq <= 0x07ff)
return (hexfreq / 2) - 1;
if (hexfreq <= 0x0fff)
return (hexfreq / 4) + 0x000001ff;
return 0x5fe; /* min freq = 6893 Hz */
}
/*
* vx_change_clock_source - change the clock source
* @source: the new source
*/
static void vx_change_clock_source(struct vx_core *chip, int source)
{
/* we mute DAC to prevent clicks */
vx_toggle_dac_mute(chip, 1);
scoped_guard(mutex, &chip->lock) {
chip->ops->set_clock_source(chip, source);
chip->clock_source = source;
}
/* unmute */
vx_toggle_dac_mute(chip, 0);
}
/*
* set the internal clock
*/
void vx_set_internal_clock(struct vx_core *chip, unsigned int freq)
{
int clock;
/* Get real clock value */
clock = vx_calc_clock_from_freq(chip, freq);
dev_dbg(chip->card->dev,
"set internal clock to 0x%x from freq %d\n", clock, freq);
guard(mutex)(&chip->lock);
if (vx_is_pcmcia(chip)) {
vx_outb(chip, HIFREQ, (clock >> 8) & 0x0f);
vx_outb(chip, LOFREQ, clock & 0xff);
} else {
vx_outl(chip, HIFREQ, (clock >> 8) & 0x0f);
vx_outl(chip, LOFREQ, clock & 0xff);
}
}
/*
* set the iec958 status bits
* @bits: 32-bit status bits
*/
void vx_set_iec958_status(struct vx_core *chip, unsigned int bits)
{
int i;
if (chip->chip_status & VX_STAT_IS_STALE)
return;
for (i = 0; i < 32; i++)
vx_write_one_cbit(chip, i, bits & (1 << i));
}
/*
* vx_set_clock - change the clock and audio source if necessary
*/
int vx_set_clock(struct vx_core *chip, unsigned int freq)
{
int src_changed = 0;
if (chip->chip_status & VX_STAT_IS_STALE)
return 0;
/* change the audio source if possible */
vx_sync_audio_source(chip);
if (chip->clock_mode == VX_CLOCK_MODE_EXTERNAL ||
(chip->clock_mode == VX_CLOCK_MODE_AUTO &&
chip->audio_source == VX_AUDIO_SRC_DIGITAL)) {
if (chip->clock_source != UER_SYNC) {
vx_change_clock_source(chip, UER_SYNC);
mdelay(6);
src_changed = 1;
}
} else if (chip->clock_mode == VX_CLOCK_MODE_INTERNAL ||
(chip->clock_mode == VX_CLOCK_MODE_AUTO &&
chip->audio_source != VX_AUDIO_SRC_DIGITAL)) {
if (chip->clock_source != INTERNAL_QUARTZ) {
vx_change_clock_source(chip, INTERNAL_QUARTZ);
src_changed = 1;
}
if (chip->freq == freq)
return 0;
vx_set_internal_clock(chip, freq);
if (src_changed)
vx_modify_board_inputs(chip);
}
if (chip->freq == freq)
return 0;
chip->freq = freq;
vx_modify_board_clock(chip, 1);
return 0;
}
/*
* vx_change_frequency - called from interrupt handler
*/
int vx_change_frequency(struct vx_core *chip)
{
int freq;
if (chip->chip_status & VX_STAT_IS_STALE)
return 0;
if (chip->clock_source == INTERNAL_QUARTZ)
return 0;
/*
* Read the real UER board frequency
*/
freq = vx_read_uer_status(chip, &chip->uer_detected);
if (freq < 0)
return freq;
/*
* The frequency computed by the DSP is good and
* is different from the previous computed.
*/
if (freq == 48000 || freq == 44100 || freq == 32000)
chip->freq_detected = freq;
return 0;
}