mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-28 23:08:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
223
sound/drivers/Kconfig
Normal file
223
sound/drivers/Kconfig
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
config SND_MPU401_UART
|
||||
tristate
|
||||
select SND_RAWMIDI
|
||||
|
||||
config SND_OPL3_LIB
|
||||
tristate
|
||||
select SND_TIMER
|
||||
select SND_HWDEP
|
||||
|
||||
config SND_OPL4_LIB
|
||||
tristate
|
||||
select SND_TIMER
|
||||
select SND_HWDEP
|
||||
|
||||
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
|
||||
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 sychronization 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_VIRMIDI
|
||||
tristate "Virtual MIDI soundcard"
|
||||
depends on SND_SEQUENCER
|
||||
select SND_TIMER
|
||||
select SND_RAWMIDI
|
||||
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"
|
||||
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"
|
||||
select SND_RAWMIDI
|
||||
help
|
||||
To include support for MIDI serial port interfaces, say Y here
|
||||
and read <file:Documentation/sound/alsa/serial-u16550.txt>.
|
||||
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_MPU401
|
||||
tristate "Generic MPU-401 UART driver"
|
||||
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_ML403_AC97CR
|
||||
tristate "Xilinx ML403 AC97 Controller Reference"
|
||||
depends on XILINX_VIRTEX
|
||||
select SND_AC97_CODEC
|
||||
help
|
||||
Say Y here to include support for the
|
||||
opb_ac97_controller_ref_v1_00_a ip core found in Xilinx's ML403
|
||||
reference design.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called snd-ml403_ac97cr.
|
||||
|
||||
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/alsa/powersave.txt 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
|
||||
25
sound/drivers/Makefile
Normal file
25
sound/drivers/Makefile
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-dummy-objs := dummy.o
|
||||
snd-aloop-objs := aloop.o
|
||||
snd-mtpav-objs := mtpav.o
|
||||
snd-mts64-objs := mts64.o
|
||||
snd-portman2x4-objs := portman2x4.o
|
||||
snd-serial-u16550-objs := serial-u16550.o
|
||||
snd-virmidi-objs := virmidi.o
|
||||
snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.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_SERIAL_U16550) += snd-serial-u16550.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_ML403_AC97CR) += snd-ml403-ac97cr.o
|
||||
|
||||
obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
|
||||
1278
sound/drivers/aloop.c
Normal file
1278
sound/drivers/aloop.c
Normal file
File diff suppressed because it is too large
Load diff
1226
sound/drivers/dummy.c
Normal file
1226
sound/drivers/dummy.c
Normal file
File diff suppressed because it is too large
Load diff
1342
sound/drivers/ml403-ac97cr.c
Normal file
1342
sound/drivers/ml403-ac97cr.c
Normal file
File diff suppressed because it is too large
Load diff
12
sound/drivers/mpu401/Makefile
Normal file
12
sound/drivers/mpu401/Makefile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-mpu401-objs := mpu401.o
|
||||
snd-mpu401-uart-objs := mpu401_uart.o
|
||||
|
||||
obj-$(CONFIG_SND_MPU401_UART) += snd-mpu401-uart.o
|
||||
|
||||
# Toplevel Module Dependency
|
||||
obj-$(CONFIG_SND_MPU401) += snd-mpu401.o
|
||||
288
sound/drivers/mpu401/mpu401.c
Normal file
288
sound/drivers/mpu401/mpu401.c
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
/*
|
||||
* 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>
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#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_array(port, long, NULL, 0444);
|
||||
MODULE_PARM_DESC(port, "Port # for MPU-401 device.");
|
||||
module_param_array(irq, int, 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;
|
||||
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])
|
||||
snd_printk(KERN_ERR "the uart_enter option is obsolete; remove it\n");
|
||||
|
||||
*rcard = NULL;
|
||||
err = snd_card_new(devptr, index[dev], id[dev], THIS_MODULE,
|
||||
0, &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
strcpy(card->driver, "MPU-401 UART");
|
||||
strcpy(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) {
|
||||
printk(KERN_ERR "MPU401 not detected at 0x%lx\n", port[dev]);
|
||||
goto _err;
|
||||
}
|
||||
|
||||
*rcard = card;
|
||||
return 0;
|
||||
|
||||
_err:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
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) {
|
||||
snd_printk(KERN_ERR "specify port\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (irq[dev] == SNDRV_AUTO_IRQ) {
|
||||
snd_printk(KERN_ERR "specify or disable IRQ\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
err = snd_mpu401_create(&devptr->dev, dev, &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
if ((err = snd_card_register(card)) < 0) {
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
platform_set_drvdata(devptr, card);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_mpu401_remove(struct platform_device *devptr)
|
||||
{
|
||||
snd_card_free(platform_get_drvdata(devptr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SND_MPU401_DRIVER "snd_mpu401"
|
||||
|
||||
static struct platform_driver snd_mpu401_driver = {
|
||||
.probe = snd_mpu401_probe,
|
||||
.remove = snd_mpu401_remove,
|
||||
.driver = {
|
||||
.name = SND_MPU401_DRIVER,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
#ifdef CONFIG_PNP
|
||||
|
||||
#define IO_EXTENT 2
|
||||
|
||||
static struct pnp_device_id snd_mpu401_pnpids[] = {
|
||||
{ .id = "PNPb006" },
|
||||
{ .id = "" }
|
||||
};
|
||||
|
||||
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) {
|
||||
snd_printk(KERN_ERR "no PnP port\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
if (pnp_port_len(device, 0) < IO_EXTENT) {
|
||||
snd_printk(KERN_ERR "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) {
|
||||
snd_printk(KERN_WARNING "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;
|
||||
if ((err = snd_card_register(card)) < 0) {
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
pnp_set_drvdata(pnp_dev, card);
|
||||
snd_mpu401_devices++;
|
||||
++dev;
|
||||
return 0;
|
||||
}
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static void snd_mpu401_pnp_remove(struct pnp_dev *dev)
|
||||
{
|
||||
struct snd_card *card = (struct snd_card *) pnp_get_drvdata(dev);
|
||||
|
||||
snd_card_disconnect(card);
|
||||
snd_card_free_when_closed(card);
|
||||
}
|
||||
|
||||
static struct pnp_driver snd_mpu401_pnp_driver = {
|
||||
.name = "mpu401",
|
||||
.id_table = snd_mpu401_pnpids,
|
||||
.probe = snd_mpu401_pnp_probe,
|
||||
.remove = snd_mpu401_pnp_remove,
|
||||
};
|
||||
#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;
|
||||
|
||||
if ((err = platform_driver_register(&snd_mpu401_driver)) < 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
|
||||
printk(KERN_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)
|
||||
637
sound/drivers/mpu401/mpu401_uart.c
Normal file
637
sound/drivers/mpu401/mpu401_uart.c
Normal file
|
|
@ -0,0 +1,637 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* 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 <asm/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)
|
||||
snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n",
|
||||
mpu->read(mpu, MPU401C(mpu)));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void uart_interrupt_tx(struct snd_mpu401 *mpu)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) &&
|
||||
test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) {
|
||||
spin_lock_irqsave(&mpu->output_lock, flags);
|
||||
snd_mpu401_uart_output_write(mpu);
|
||||
spin_unlock_irqrestore(&mpu->output_lock, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (mpu->info_flags & MPU401_INFO_INPUT) {
|
||||
spin_lock_irqsave(&mpu->input_lock, flags);
|
||||
if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))
|
||||
snd_mpu401_uart_input_read(mpu);
|
||||
else
|
||||
snd_mpu401_uart_clear_rx(mpu);
|
||||
spin_unlock_irqrestore(&mpu->input_lock, flags);
|
||||
}
|
||||
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 == NULL)
|
||||
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 == NULL)
|
||||
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(unsigned long data)
|
||||
{
|
||||
struct snd_mpu401 *mpu = (struct snd_mpu401 *)data;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mpu->timer_lock, flags);
|
||||
/*mpu->mode |= MPU401_MODE_TIMER;*/
|
||||
mpu->timer.expires = 1 + jiffies;
|
||||
add_timer(&mpu->timer);
|
||||
spin_unlock_irqrestore(&mpu->timer_lock, flags);
|
||||
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)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave (&mpu->timer_lock, flags);
|
||||
if (mpu->timer_invoked == 0) {
|
||||
init_timer(&mpu->timer);
|
||||
mpu->timer.data = (unsigned long)mpu;
|
||||
mpu->timer.function = snd_mpu401_uart_timer;
|
||||
mpu->timer.expires = 1 + jiffies;
|
||||
add_timer(&mpu->timer);
|
||||
}
|
||||
mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER :
|
||||
MPU401_MODE_OUTPUT_TIMER;
|
||||
spin_unlock_irqrestore (&mpu->timer_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* remove the timer callback if still active
|
||||
*/
|
||||
static void snd_mpu401_uart_remove_timer (struct snd_mpu401 *mpu, int input)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave (&mpu->timer_lock, flags);
|
||||
if (mpu->timer_invoked) {
|
||||
mpu->timer_invoked &= input ? ~MPU401_MODE_INPUT_TIMER :
|
||||
~MPU401_MODE_OUTPUT_TIMER;
|
||||
if (! mpu->timer_invoked)
|
||||
del_timer(&mpu->timer);
|
||||
}
|
||||
spin_unlock_irqrestore (&mpu->timer_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
unsigned long flags;
|
||||
int timeout, ok;
|
||||
|
||||
spin_lock_irqsave(&mpu->input_lock, flags);
|
||||
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)
|
||||
snd_printk(KERN_ERR "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;
|
||||
spin_unlock_irqrestore(&mpu->input_lock, flags);
|
||||
if (!ok) {
|
||||
snd_printk(KERN_ERR "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)) < 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)) < 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)
|
||||
{
|
||||
unsigned long flags;
|
||||
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 */
|
||||
spin_lock_irqsave(&mpu->input_lock, flags);
|
||||
snd_mpu401_uart_input_read(mpu);
|
||||
spin_unlock_irqrestore(&mpu->input_lock, flags);
|
||||
} 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)
|
||||
{
|
||||
unsigned long flags;
|
||||
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 */
|
||||
spin_lock_irqsave(&mpu->output_lock, flags);
|
||||
snd_mpu401_uart_output_write(mpu);
|
||||
spin_unlock_irqrestore(&mpu->output_lock, flags);
|
||||
} 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 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 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;
|
||||
if ((err = snd_rawmidi_new(card, "MPU-401U", device,
|
||||
out_enable, in_enable, &rmidi)) < 0)
|
||||
return err;
|
||||
mpu = kzalloc(sizeof(*mpu), GFP_KERNEL);
|
||||
if (mpu == NULL) {
|
||||
snd_printk(KERN_ERR "mpu401_uart: cannot allocate\n");
|
||||
snd_device_free(card, rmidi);
|
||||
return -ENOMEM;
|
||||
}
|
||||
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;
|
||||
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 == NULL) {
|
||||
snd_printk(KERN_ERR "mpu401_uart: "
|
||||
"unable to grab port 0x%lx size %d\n",
|
||||
port, res_size);
|
||||
snd_device_free(card, rmidi);
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
snd_printk(KERN_ERR "mpu401_uart: "
|
||||
"unable to grab IRQ %d\n", irq);
|
||||
snd_device_free(card, rmidi);
|
||||
return -EBUSY;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
mpu->rmidi = rmidi;
|
||||
if (rrawmidi)
|
||||
*rrawmidi = rmidi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_mpu401_uart_new);
|
||||
|
||||
/*
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_mpu401_uart_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_mpu401_uart_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(alsa_mpu401_uart_init)
|
||||
module_exit(alsa_mpu401_uart_exit)
|
||||
792
sound/drivers/mtpav.c
Normal file
792
sound/drivers/mtpav.c
Normal file
|
|
@ -0,0 +1,792 @@
|
|||
/*
|
||||
* MOTU Midi Timepiece ALSA Main routines
|
||||
* Copyright by Michael T. Mayers (c) Jan 09, 2000
|
||||
* mail: michael@tweakoz.com
|
||||
* Thanks to John Galbraith
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*
|
||||
* 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>
|
||||
|
||||
/*
|
||||
* globals
|
||||
*/
|
||||
MODULE_AUTHOR("Michael T. Mayers");
|
||||
MODULE_DESCRIPTION("MOTU MidiTimePiece AV multiport MIDI");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_SUPPORTED_DEVICE("{{MOTU,MidiTimePiece AV multiport MIDI}}");
|
||||
|
||||
// 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(port, long, 0444);
|
||||
MODULE_PARM_DESC(port, "Parallel port # for MotuMTPAV MIDI.");
|
||||
module_param(irq, int, 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);
|
||||
/*
|
||||
snd_printk(KERN_DEBUG "new outport: 0x%x\n",
|
||||
(unsigned int) 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];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mtp_card->spinlock, flags);
|
||||
snd_mtpav_output_port_write(mtp_card, portp, substream);
|
||||
spin_unlock_irqrestore(&mtp_card->spinlock, flags);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mtp_card->spinlock, flags);
|
||||
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
|
||||
spin_unlock_irqrestore(&mtp_card->spinlock, flags);
|
||||
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];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mtp_card->spinlock, flags);
|
||||
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
|
||||
spin_unlock_irqrestore(&mtp_card->spinlock, flags);
|
||||
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];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mtp_card->spinlock, flags);
|
||||
if (up)
|
||||
portp->mode |= MTPAV_MODE_INPUT_TRIGGERED;
|
||||
else
|
||||
portp->mode &= ~MTPAV_MODE_INPUT_TRIGGERED;
|
||||
spin_unlock_irqrestore(&mtp_card->spinlock, flags);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* timer interrupt for outputs
|
||||
*/
|
||||
|
||||
static void snd_mtpav_output_timer(unsigned long data)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct mtpav *chip = (struct mtpav *)data;
|
||||
int p;
|
||||
|
||||
spin_lock_irqsave(&chip->spinlock, flags);
|
||||
/* reprogram timer */
|
||||
chip->timer.expires = 1 + jiffies;
|
||||
add_timer(&chip->timer);
|
||||
/* 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);
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->spinlock, flags);
|
||||
}
|
||||
|
||||
/* spinlock held! */
|
||||
static void snd_mtpav_add_output_timer(struct mtpav *chip)
|
||||
{
|
||||
chip->timer.expires = 1 + jiffies;
|
||||
add_timer(&chip->timer);
|
||||
}
|
||||
|
||||
/* spinlock held! */
|
||||
static void snd_mtpav_remove_output_timer(struct mtpav *chip)
|
||||
{
|
||||
del_timer(&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];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mtp_card->spinlock, flags);
|
||||
portp->mode |= MTPAV_MODE_OUTPUT_OPENED;
|
||||
portp->output = substream;
|
||||
spin_unlock_irqrestore(&mtp_card->spinlock, flags);
|
||||
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];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mtp_card->spinlock, flags);
|
||||
portp->mode &= ~MTPAV_MODE_OUTPUT_OPENED;
|
||||
portp->output = NULL;
|
||||
spin_unlock_irqrestore(&mtp_card->spinlock, flags);
|
||||
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];
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&mtp_card->spinlock, flags);
|
||||
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);
|
||||
}
|
||||
spin_unlock_irqrestore(&mtp_card->spinlock, flags);
|
||||
|
||||
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);
|
||||
|
||||
/* printk(KERN_DEBUG "snd_mtpav_read_bytes() sbyt: 0x%x\n", sbyt); */
|
||||
|
||||
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;
|
||||
|
||||
spin_lock(&mcard->spinlock);
|
||||
snd_mtpav_read_bytes(mcard);
|
||||
spin_unlock(&mcard->spinlock);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* get ISA resources
|
||||
*/
|
||||
static int snd_mtpav_get_ISA(struct mtpav *mcard)
|
||||
{
|
||||
if ((mcard->res_port = request_region(port, 3, "MotuMTPAV MIDI")) == NULL) {
|
||||
snd_printk(KERN_ERR "MTVAP port 0x%lx is busy\n", port);
|
||||
return -EBUSY;
|
||||
}
|
||||
mcard->port = port;
|
||||
if (request_irq(irq, snd_mtpav_irqh, 0, "MOTU MTPAV", mcard)) {
|
||||
snd_printk(KERN_ERR "MTVAP IRQ %d busy\n", irq);
|
||||
return -EBUSY;
|
||||
}
|
||||
mcard->irq = irq;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
static struct snd_rawmidi_ops snd_mtpav_output = {
|
||||
.open = snd_mtpav_output_open,
|
||||
.close = snd_mtpav_output_close,
|
||||
.trigger = snd_mtpav_output_trigger,
|
||||
};
|
||||
|
||||
static 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)
|
||||
strcpy(substream->name, "MTP computer");
|
||||
else if (substream->number == chip->num_ports * 2 + 1)
|
||||
strcpy(substream->name, "MTP ADAT");
|
||||
else
|
||||
strcpy(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;
|
||||
|
||||
if ((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)) < 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;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&crd->spinlock, flags);
|
||||
if (crd->istimer > 0)
|
||||
snd_mtpav_remove_output_timer(crd);
|
||||
spin_unlock_irqrestore(&crd->spinlock, flags);
|
||||
if (crd->irq >= 0)
|
||||
free_irq(crd->irq, (void *)crd);
|
||||
release_and_free_resource(crd->res_port);
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
static int snd_mtpav_probe(struct platform_device *dev)
|
||||
{
|
||||
struct snd_card *card;
|
||||
int err;
|
||||
struct mtpav *mtp_card;
|
||||
|
||||
err = snd_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);
|
||||
init_timer(&mtp_card->timer);
|
||||
mtp_card->card = card;
|
||||
mtp_card->irq = -1;
|
||||
mtp_card->share_irq = 0;
|
||||
mtp_card->inmidistate = 0;
|
||||
mtp_card->outmidihwport = 0xffffffff;
|
||||
init_timer(&mtp_card->timer);
|
||||
mtp_card->timer.function = snd_mtpav_output_timer;
|
||||
mtp_card->timer.data = (unsigned long) mtp_card;
|
||||
|
||||
card->private_free = snd_mtpav_free;
|
||||
|
||||
err = snd_mtpav_get_RAWMIDI(mtp_card);
|
||||
if (err < 0)
|
||||
goto __error;
|
||||
|
||||
mtp_card->inmidiport = mtp_card->num_ports + MTPAV_PIDX_BROADCAST;
|
||||
|
||||
err = snd_mtpav_get_ISA(mtp_card);
|
||||
if (err < 0)
|
||||
goto __error;
|
||||
|
||||
strcpy(card->driver, "MTPAV");
|
||||
strcpy(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)
|
||||
goto __error;
|
||||
|
||||
platform_set_drvdata(dev, card);
|
||||
printk(KERN_INFO "Motu MidiTimePiece on parallel port irq: %d ioport: 0x%lx\n", irq, port);
|
||||
return 0;
|
||||
|
||||
__error:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_mtpav_remove(struct platform_device *devptr)
|
||||
{
|
||||
snd_card_free(platform_get_drvdata(devptr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SND_MTPAV_DRIVER "snd_mtpav"
|
||||
|
||||
static struct platform_driver snd_mtpav_driver = {
|
||||
.probe = snd_mtpav_probe,
|
||||
.remove = snd_mtpav_remove,
|
||||
.driver = {
|
||||
.name = SND_MTPAV_DRIVER,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init alsa_card_mtpav_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
if ((err = platform_driver_register(&snd_mtpav_driver)) < 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)
|
||||
1090
sound/drivers/mts64.c
Normal file
1090
sound/drivers/mts64.c
Normal file
File diff suppressed because it is too large
Load diff
12
sound/drivers/opl3/Makefile
Normal file
12
sound/drivers/opl3/Makefile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-opl3-lib-objs := opl3_lib.o opl3_synth.o
|
||||
snd-opl3-synth-y := opl3_seq.o opl3_midi.o opl3_drums.o
|
||||
snd-opl3-synth-$(CONFIG_SND_SEQUENCER_OSS) += opl3_oss.o
|
||||
|
||||
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
|
||||
226
sound/drivers/opl3/opl3_drums.c
Normal file
226
sound/drivers/opl3/opl3_drums.c
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Copyright (c) by Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* OPL2/OPL3/OPL4 FM routines for internal percussion channels
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include "opl3_voice.h"
|
||||
|
||||
extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
|
||||
|
||||
static 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 struct snd_opl3_drum_voice bass_op0 = {6, 0, 0x00, 0x32, 0xf8, 0x66, 0x30, 0x00};
|
||||
static struct snd_opl3_drum_voice bass_op1 = {6, 1, 0x00, 0x03, 0xf6, 0x57, 0x30, 0x00};
|
||||
static struct snd_opl3_drum_note bass_note = {6, 0x90, 0x09};
|
||||
|
||||
static struct snd_opl3_drum_voice hihat = {7, 0, 0x00, 0x03, 0xf0, 0x06, 0x20, 0x00};
|
||||
|
||||
static struct snd_opl3_drum_voice snare = {7, 1, 0x00, 0x03, 0xf0, 0x07, 0x20, 0x02};
|
||||
static struct snd_opl3_drum_note snare_note = {7, 0xf4, 0x0d};
|
||||
|
||||
static struct snd_opl3_drum_voice tomtom = {8, 0, 0x02, 0x03, 0xf0, 0x06, 0x10, 0x00};
|
||||
static struct snd_opl3_drum_note tomtom_note = {8, 0xf4, 0x09};
|
||||
|
||||
static 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,
|
||||
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,
|
||||
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,
|
||||
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(®_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;
|
||||
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);
|
||||
}
|
||||
559
sound/drivers/opl3/opl3_lib.c
Normal file
559
sound/drivers/opl3/opl3_lib.c
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <sound/opl3.h>
|
||||
#include <asm/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>
|
||||
|
||||
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");
|
||||
|
||||
extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
|
||||
|
||||
static void snd_opl2_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
|
||||
{
|
||||
unsigned long flags;
|
||||
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;
|
||||
|
||||
spin_lock_irqsave(&opl3->reg_lock, flags);
|
||||
|
||||
outb((unsigned char) cmd, port);
|
||||
udelay(10);
|
||||
|
||||
outb((unsigned char) val, port + 1);
|
||||
udelay(30);
|
||||
|
||||
spin_unlock_irqrestore(&opl3->reg_lock, flags);
|
||||
}
|
||||
|
||||
static void snd_opl3_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val)
|
||||
{
|
||||
unsigned long flags;
|
||||
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;
|
||||
|
||||
spin_lock_irqsave(&opl3->reg_lock, flags);
|
||||
|
||||
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);
|
||||
|
||||
spin_unlock_irqrestore(&opl3->reg_lock, flags);
|
||||
}
|
||||
|
||||
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 */
|
||||
snd_printd("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 */
|
||||
snd_printd("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 long flags;
|
||||
unsigned char tmp;
|
||||
unsigned int ticks;
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = snd_timer_chip(timer);
|
||||
spin_lock_irqsave(&opl3->timer_lock, flags);
|
||||
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 */
|
||||
spin_unlock_irqrestore(&opl3->timer_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_opl3_timer1_stop(struct snd_timer * timer)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned char tmp;
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = snd_timer_chip(timer);
|
||||
spin_lock_irqsave(&opl3->timer_lock, flags);
|
||||
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 */
|
||||
spin_unlock_irqrestore(&opl3->timer_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Timer 2 - 320us
|
||||
*/
|
||||
|
||||
static int snd_opl3_timer2_start(struct snd_timer * timer)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned char tmp;
|
||||
unsigned int ticks;
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = snd_timer_chip(timer);
|
||||
spin_lock_irqsave(&opl3->timer_lock, flags);
|
||||
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 */
|
||||
spin_unlock_irqrestore(&opl3->timer_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_opl3_timer2_stop(struct snd_timer * timer)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned char tmp;
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = snd_timer_chip(timer);
|
||||
spin_lock_irqsave(&opl3->timer_lock, flags);
|
||||
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 */
|
||||
spin_unlock_irqrestore(&opl3->timer_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
static 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 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;
|
||||
if ((err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer)) >= 0) {
|
||||
strcpy(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;
|
||||
if ((err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer)) >= 0) {
|
||||
strcpy(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 0
|
||||
snd_printk(KERN_DEBUG "AdLib IRQ status = 0x%x\n", status);
|
||||
#endif
|
||||
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 struct snd_device_ops ops = {
|
||||
.dev_free = snd_opl3_dev_free,
|
||||
};
|
||||
struct snd_opl3 *opl3;
|
||||
int err;
|
||||
|
||||
*ropl3 = NULL;
|
||||
opl3 = kzalloc(sizeof(*opl3), GFP_KERNEL);
|
||||
if (opl3 == NULL) {
|
||||
snd_printk(KERN_ERR "opl3: cannot allocate\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
opl3->card = card;
|
||||
opl3->hardware = hardware;
|
||||
spin_lock_init(&opl3->reg_lock);
|
||||
spin_lock_init(&opl3->timer_lock);
|
||||
|
||||
if ((err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops)) < 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) {
|
||||
printk(KERN_ERR "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;
|
||||
if ((err = snd_opl3_new(card, hardware, &opl3)) < 0)
|
||||
return err;
|
||||
if (! integrated) {
|
||||
if ((opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)")) == NULL) {
|
||||
snd_printk(KERN_ERR "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)")) == NULL) {
|
||||
snd_printk(KERN_ERR "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;
|
||||
if ((err = snd_opl3_detect(opl3)) < 0) {
|
||||
snd_printd("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)
|
||||
if ((err = snd_opl3_timer1_init(opl3, timer1_dev)) < 0)
|
||||
return err;
|
||||
if (timer2_dev >= 0) {
|
||||
if ((err = snd_opl3_timer2_init(opl3, timer2_dev)) < 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) */
|
||||
|
||||
if ((err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw)) < 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
|
||||
strcpy(hw->name, hw->id);
|
||||
switch (opl3->hardware & OPL3_HW_MASK) {
|
||||
case OPL3_HW_OPL2:
|
||||
strcpy(hw->name, "OPL2 FM");
|
||||
hw->iface = SNDRV_HWDEP_IFACE_OPL2;
|
||||
break;
|
||||
case OPL3_HW_OPL3:
|
||||
strcpy(hw->name, "OPL3 FM");
|
||||
hw->iface = SNDRV_HWDEP_IFACE_OPL3;
|
||||
break;
|
||||
case OPL3_HW_OPL4:
|
||||
strcpy(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 defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
|
||||
if (snd_seq_device_new(card, seq_device, SNDRV_SEQ_DEV_ID_OPL3,
|
||||
sizeof(struct snd_opl3 *), &opl3->seq_dev) >= 0) {
|
||||
strcpy(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);
|
||||
|
||||
/*
|
||||
* INIT part
|
||||
*/
|
||||
|
||||
static int __init alsa_opl3_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_opl3_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(alsa_opl3_init)
|
||||
module_exit(alsa_opl3_exit)
|
||||
886
sound/drivers/opl3/opl3_midi.c
Normal file
886
sound/drivers/opl3/opl3_midi.c
Normal file
|
|
@ -0,0 +1,886 @@
|
|||
/*
|
||||
* Copyright (c) by Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* Midi synth routines for OPL2/OPL3/OPL4 FM
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#undef DEBUG_ALLOC
|
||||
#undef DEBUG_MIDI
|
||||
|
||||
#include "opl3_voice.h"
|
||||
#include <sound/asoundef.h>
|
||||
|
||||
extern char snd_opl3_regmap[MAX_OPL2_VOICES][4];
|
||||
|
||||
extern bool use_internal_drums;
|
||||
|
||||
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 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 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 > 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;
|
||||
char *str = "x.24";
|
||||
|
||||
printk(KERN_DEBUG "time %.5i: %s [%.2i]: ", opl3->use_time, s, voice);
|
||||
for (i = 0; i < opl3->max_voices; i++)
|
||||
printk("%c", *(str + opl3->voices[i].state + 1));
|
||||
printk("\n");
|
||||
}
|
||||
#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 = (voice_time > vp->time) ?
|
||||
voice_time : vp->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
|
||||
printk(KERN_DEBUG "%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(unsigned long data)
|
||||
{
|
||||
|
||||
struct snd_opl3 *opl3 = (struct snd_opl3 *)data;
|
||||
unsigned long flags;
|
||||
int again = 0;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&opl3->voice_lock, flags);
|
||||
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++;
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
|
||||
spin_lock_irqsave(&opl3->sys_timer_lock, flags);
|
||||
if (again) {
|
||||
opl3->tlist.expires = jiffies + 1; /* invoke again */
|
||||
add_timer(&opl3->tlist);
|
||||
} else {
|
||||
opl3->sys_timer_status = 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* Start system timer
|
||||
*/
|
||||
static void snd_opl3_start_timer(struct snd_opl3 *opl3)
|
||||
{
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&opl3->sys_timer_lock, flags);
|
||||
if (! opl3->sys_timer_status) {
|
||||
opl3->tlist.expires = jiffies + 1;
|
||||
add_timer(&opl3->tlist);
|
||||
opl3->sys_timer_status = 1;
|
||||
}
|
||||
spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
|
||||
}
|
||||
|
||||
/* ------------------------------ */
|
||||
|
||||
|
||||
static 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;
|
||||
unsigned long flags;
|
||||
|
||||
opl3 = p;
|
||||
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG "Note on, ch %i, inst %i, note %i, vel %i\n",
|
||||
chan->number, chan->midi_program, note, vel);
|
||||
#endif
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&opl3->voice_lock, flags);
|
||||
|
||||
if (use_internal_drums) {
|
||||
snd_opl3_drum_switch(opl3, note, vel, 1, chan);
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
__extra_prg:
|
||||
patch = snd_opl3_find_patch(opl3, prg, bank, 0);
|
||||
if (!patch) {
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
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;
|
||||
}
|
||||
default:
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
return;
|
||||
}
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG " --> OPL%i instrument: %s\n",
|
||||
instr_4op ? 3 : 2, patch->name);
|
||||
#endif
|
||||
/* 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) {
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
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 (vp->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);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG " --> setting OPL3 connection: 0x%x\n",
|
||||
opl3->connection_reg);
|
||||
#endif
|
||||
/*
|
||||
* 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);
|
||||
/* fallthru */
|
||||
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++) {
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG " --> programming operator %i\n", i);
|
||||
#endif
|
||||
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;
|
||||
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG " --> trigger voice %i\n", voice);
|
||||
#endif
|
||||
/* 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;
|
||||
}
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG " *** allocating extra program\n");
|
||||
#endif
|
||||
goto __extra_prg;
|
||||
}
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
}
|
||||
|
||||
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 */
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG " --> kill voice %i\n", voice);
|
||||
#endif
|
||||
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;
|
||||
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG "Note off, ch %i, inst %i, note %i\n",
|
||||
chan->number, chan->midi_program, note);
|
||||
#endif
|
||||
|
||||
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;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&opl3->voice_lock, flags);
|
||||
snd_opl3_note_off_unsafe(p, note, vel, chan);
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* key pressure change
|
||||
*/
|
||||
void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan)
|
||||
{
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = p;
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG "Key pressure, ch#: %i, inst#: %i\n",
|
||||
chan->number, chan->midi_program);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* terminate note
|
||||
*/
|
||||
void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan)
|
||||
{
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = p;
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG "Terminate note, ch#: %i, inst#: %i\n",
|
||||
chan->number, chan->midi_program);
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&opl3->voice_lock, flags);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&opl3->voice_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG "Controller, TYPE = %i, ch#: %i, inst#: %i\n",
|
||||
type, chan->number, chan->midi_program);
|
||||
#endif
|
||||
|
||||
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)
|
||||
{
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = p;
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG "NRPN, ch#: %i, inst#: %i\n",
|
||||
chan->number, chan->midi_program);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* receive sysex
|
||||
*/
|
||||
void snd_opl3_sysex(void *p, unsigned char *buf, int len,
|
||||
int parsed, struct snd_midi_channel_set *chset)
|
||||
{
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = p;
|
||||
#ifdef DEBUG_MIDI
|
||||
snd_printk(KERN_DEBUG "SYSEX\n");
|
||||
#endif
|
||||
}
|
||||
285
sound/drivers/opl3/opl3_oss.c
Normal file
285
sound/drivers/opl3/opl3_oss.c
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* Interface for OSS sequencer emulation
|
||||
*
|
||||
* Copyright (C) 2000 Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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);
|
||||
|
||||
/* */
|
||||
|
||||
static inline mm_segment_t snd_enter_user(void)
|
||||
{
|
||||
mm_segment_t fs = get_fs();
|
||||
set_fs(get_ds());
|
||||
return fs;
|
||||
}
|
||||
|
||||
static inline void snd_leave_user(mm_segment_t fs)
|
||||
{
|
||||
set_fs(fs);
|
||||
}
|
||||
|
||||
/* operators */
|
||||
|
||||
extern struct snd_midi_op opl3_ops;
|
||||
|
||||
static 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;
|
||||
strlcpy(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;
|
||||
|
||||
if ((err = snd_opl3_synth_setup(opl3)) < 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;
|
||||
|
||||
if ((err = snd_opl3_synth_use_inc(opl3)) < 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)) {
|
||||
snd_printk(KERN_ERR "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) {
|
||||
snd_printk(KERN_ERR "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:
|
||||
snd_printk(KERN_ERR "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)
|
||||
{
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
if (snd_BUG_ON(!arg))
|
||||
return -ENXIO;
|
||||
opl3 = arg->private_data;
|
||||
|
||||
return 0;
|
||||
}
|
||||
298
sound/drivers/opl3/opl3_seq.c
Normal file
298
sound/drivers/opl3/opl3_seq.c
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* 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/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
mutex_lock(&hwdep->open_mutex);
|
||||
if (hwdep->used) {
|
||||
mutex_unlock(&hwdep->open_mutex);
|
||||
return -EBUSY;
|
||||
}
|
||||
hwdep->used++;
|
||||
mutex_unlock(&hwdep->open_mutex);
|
||||
|
||||
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)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct snd_hwdep *hwdep;
|
||||
|
||||
/* Stop system timer */
|
||||
spin_lock_irqsave(&opl3->sys_timer_lock, flags);
|
||||
if (opl3->sys_timer_status) {
|
||||
del_timer(&opl3->tlist);
|
||||
opl3->sys_timer_status = 0;
|
||||
}
|
||||
spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);
|
||||
|
||||
snd_opl3_reset(opl3);
|
||||
hwdep = opl3->hwdep;
|
||||
mutex_lock(&hwdep->open_mutex);
|
||||
hwdep->used--;
|
||||
mutex_unlock(&hwdep->open_mutex);
|
||||
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;
|
||||
|
||||
if ((err = snd_opl3_synth_setup(opl3)) < 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) {
|
||||
if ((err = snd_opl3_synth_use_inc(opl3)) < 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
|
||||
*/
|
||||
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_new_device(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;
|
||||
|
||||
if ((err = snd_opl3_synth_create_port(opl3)) < 0) {
|
||||
snd_seq_delete_kernel_client(client);
|
||||
opl3->seq_client = -1;
|
||||
return err;
|
||||
}
|
||||
|
||||
/* setup system timer */
|
||||
init_timer(&opl3->tlist);
|
||||
opl3->tlist.function = snd_opl3_timer_func;
|
||||
opl3->tlist.data = (unsigned long) opl3;
|
||||
spin_lock_init(&opl3->sys_timer_lock);
|
||||
opl3->sys_timer_status = 0;
|
||||
|
||||
#ifdef CONFIG_SND_SEQUENCER_OSS
|
||||
snd_opl3_init_seq_oss(opl3, name);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_opl3_seq_delete_device(struct snd_seq_device *dev)
|
||||
{
|
||||
struct snd_opl3 *opl3;
|
||||
|
||||
opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
|
||||
if (opl3 == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
#ifdef 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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init alsa_opl3_seq_init(void)
|
||||
{
|
||||
static struct snd_seq_dev_ops ops =
|
||||
{
|
||||
snd_opl3_seq_new_device,
|
||||
snd_opl3_seq_delete_device
|
||||
};
|
||||
|
||||
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL3, &ops,
|
||||
sizeof(struct snd_opl3 *));
|
||||
}
|
||||
|
||||
static void __exit alsa_opl3_seq_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL3);
|
||||
}
|
||||
|
||||
module_init(alsa_opl3_seq_init)
|
||||
module_exit(alsa_opl3_seq_exit)
|
||||
616
sound/drivers/opl3/opl3_synth.c
Normal file
616
sound/drivers/opl3/opl3_synth.c
Normal file
|
|
@ -0,0 +1,616 @@
|
|||
/*
|
||||
* Copyright (c) by Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* Routines for OPL2/OPL3/OPL4 control
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/opl3.h>
|
||||
#include <sound/asound_fm.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;
|
||||
|
||||
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(¬e, argp, sizeof(struct snd_dm_fm_note)))
|
||||
return -EFAULT;
|
||||
return snd_opl3_play_note(opl3, ¬e);
|
||||
}
|
||||
|
||||
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(¶ms, argp, sizeof(struct snd_dm_fm_params)))
|
||||
return -EFAULT;
|
||||
return snd_opl3_set_params(opl3, ¶ms);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
default:
|
||||
snd_printk(KERN_WARNING "unknown IOCTL: 0x%x\n", cmd);
|
||||
#endif
|
||||
}
|
||||
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)
|
||||
strlcpy(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(sizeof(*patch), GFP_KERNEL);
|
||||
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;
|
||||
|
||||
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 */
|
||||
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;
|
||||
}
|
||||
|
||||
52
sound/drivers/opl3/opl3_voice.h
Normal file
52
sound/drivers/opl3/opl3_voice.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#ifndef __OPL3_VOICE_H
|
||||
#define __OPL3_VOICE_H
|
||||
|
||||
/*
|
||||
* Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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(unsigned long data);
|
||||
|
||||
/* 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 on_off, int vel, struct snd_midi_channel *chan);
|
||||
|
||||
/* Prototypes for opl3_oss.c */
|
||||
#ifdef 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);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
10
sound/drivers/opl4/Makefile
Normal file
10
sound/drivers/opl4/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-opl4-lib-objs := opl4_lib.o opl4_mixer.o opl4_proc.o
|
||||
snd-opl4-synth-objs := 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
|
||||
281
sound/drivers/opl4/opl4_lib.c
Normal file
281
sound/drivers/opl4/opl4_lib.c
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* Functions for accessing OPL4 devices
|
||||
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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 <asm/io.h>
|
||||
|
||||
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
|
||||
MODULE_DESCRIPTION("OPL4 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static void inline 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)
|
||||
{
|
||||
unsigned long flags;
|
||||
u8 memcfg;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
|
||||
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);
|
||||
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_opl4_read_memory);
|
||||
|
||||
void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size)
|
||||
{
|
||||
unsigned long flags;
|
||||
u8 memcfg;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
|
||||
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);
|
||||
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
}
|
||||
|
||||
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);
|
||||
snd_printdd("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);
|
||||
snd_printdd("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 defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
|
||||
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) {
|
||||
strcpy(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)
|
||||
{
|
||||
#ifdef CONFIG_PROC_FS
|
||||
snd_opl4_free_proc(opl4);
|
||||
#endif
|
||||
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 struct snd_device_ops ops = {
|
||||
.dev_free = snd_opl4_dev_free
|
||||
};
|
||||
|
||||
if (ropl3)
|
||||
*ropl3 = NULL;
|
||||
if (ropl4)
|
||||
*ropl4 = NULL;
|
||||
|
||||
opl4 = kzalloc(sizeof(*opl4), GFP_KERNEL);
|
||||
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) {
|
||||
snd_printk(KERN_ERR "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);
|
||||
snd_printd("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);
|
||||
#ifdef CONFIG_PROC_FS
|
||||
snd_opl4_create_proc(opl4);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))
|
||||
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);
|
||||
|
||||
static int __init alsa_opl4_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_opl4_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(alsa_opl4_init)
|
||||
module_exit(alsa_opl4_exit)
|
||||
232
sound/drivers/opl4/opl4_local.h
Normal file
232
sound/drivers/opl4/opl4_local.h
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* 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_PROC_FS
|
||||
struct snd_info_entry *proc_entry;
|
||||
int memory_access;
|
||||
#endif
|
||||
struct mutex access_mutex;
|
||||
|
||||
#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
|
||||
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_PROC_FS
|
||||
/* opl4_proc.c */
|
||||
int snd_opl4_create_proc(struct snd_opl4 *opl4);
|
||||
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 */
|
||||
95
sound/drivers/opl4/opl4_mixer.c
Normal file
95
sound/drivers/opl4/opl4_mixer.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* OPL4 mixer functions
|
||||
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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);
|
||||
unsigned long flags;
|
||||
u8 reg = kcontrol->private_value;
|
||||
u8 value;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
value = snd_opl4_read(opl4, reg);
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
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);
|
||||
unsigned long flags;
|
||||
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);
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
old_value = snd_opl4_read(opl4, reg);
|
||||
snd_opl4_write(opl4, reg, value);
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
return value != old_value;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
133
sound/drivers/opl4/opl4_proc.c
Normal file
133
sound/drivers/opl4/opl4_proc.c
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Functions for the OPL4 proc file
|
||||
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "opl4_local.h"
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/export.h>
|
||||
#include <sound/info.h>
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
|
||||
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;
|
||||
|
||||
mutex_lock(&opl4->access_mutex);
|
||||
if (opl4->memory_access) {
|
||||
mutex_unlock(&opl4->access_mutex);
|
||||
return -EBUSY;
|
||||
}
|
||||
opl4->memory_access++;
|
||||
mutex_unlock(&opl4->access_mutex);
|
||||
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;
|
||||
|
||||
mutex_lock(&opl4->access_mutex);
|
||||
opl4->memory_access--;
|
||||
mutex_unlock(&opl4->access_mutex);
|
||||
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 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 |= S_IWUSR;
|
||||
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;
|
||||
if (snd_info_register(entry) < 0) {
|
||||
snd_info_free_entry(entry);
|
||||
entry = NULL;
|
||||
}
|
||||
}
|
||||
opl4->proc_entry = entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void snd_opl4_free_proc(struct snd_opl4 *opl4)
|
||||
{
|
||||
snd_info_free_entry(opl4->proc_entry);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PROC_FS */
|
||||
215
sound/drivers/opl4/opl4_seq.c
Normal file
215
sound/drivers/opl4/opl4_seq.c
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
mutex_lock(&opl4->access_mutex);
|
||||
|
||||
if (opl4->used) {
|
||||
mutex_unlock(&opl4->access_mutex);
|
||||
return -EBUSY;
|
||||
}
|
||||
opl4->used++;
|
||||
|
||||
if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {
|
||||
err = snd_opl4_seq_use_inc(opl4);
|
||||
if (err < 0) {
|
||||
mutex_unlock(&opl4->access_mutex);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&opl4->access_mutex);
|
||||
|
||||
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);
|
||||
|
||||
mutex_lock(&opl4->access_mutex);
|
||||
opl4->used--;
|
||||
mutex_unlock(&opl4->access_mutex);
|
||||
|
||||
if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
|
||||
snd_opl4_seq_use_dec(opl4);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static 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_new_device(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 int snd_opl4_seq_delete_device(struct snd_seq_device *dev)
|
||||
{
|
||||
struct snd_opl4 *opl4;
|
||||
|
||||
opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);
|
||||
if (!opl4)
|
||||
return -EINVAL;
|
||||
|
||||
if (opl4->seq_client >= 0) {
|
||||
snd_seq_delete_kernel_client(opl4->seq_client);
|
||||
opl4->seq_client = -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init alsa_opl4_synth_init(void)
|
||||
{
|
||||
static struct snd_seq_dev_ops ops = {
|
||||
snd_opl4_seq_new_device,
|
||||
snd_opl4_seq_delete_device
|
||||
};
|
||||
|
||||
return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL4, &ops,
|
||||
sizeof(struct snd_opl4 *));
|
||||
}
|
||||
|
||||
static void __exit alsa_opl4_synth_exit(void)
|
||||
{
|
||||
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_OPL4);
|
||||
}
|
||||
|
||||
module_init(alsa_opl4_synth_init)
|
||||
module_exit(alsa_opl4_synth_exit)
|
||||
631
sound/drivers/opl4/opl4_synth.c
Normal file
631
sound/drivers/opl4/opl4_synth.c
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
/*
|
||||
* 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 <asm/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 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)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
for (i = 0; i < OPL4_MAX_VOICES; i++)
|
||||
snd_opl4_write(opl4, OPL4_REG_MISC + i, OPL4_DAMP_BIT);
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
|
||||
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)
|
||||
{
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
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);
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
unsigned long flags;
|
||||
struct opl4_voice *voice;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
for (i = 0; i < OPL4_MAX_VOICES; i++) {
|
||||
voice = &opl4->voices[i];
|
||||
if (voice->chan == chan && voice->note == note) {
|
||||
func(opl4, voice);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
unsigned long flags;
|
||||
struct opl4_voice *voice;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
for (i = 0; i < OPL4_MAX_VOICES; i++) {
|
||||
voice = &opl4->voices[i];
|
||||
if (voice->chan == chan) {
|
||||
func(opl4, voice);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
unsigned long flags;
|
||||
struct opl4_voice *voice;
|
||||
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
for (i = 0; i < OPL4_MAX_VOICES; i++) {
|
||||
voice = &opl4->voices[i];
|
||||
if (voice->chan)
|
||||
func(opl4, voice);
|
||||
}
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
}
|
||||
|
||||
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;
|
||||
unsigned long flags;
|
||||
|
||||
/* 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] = ®ions->regions[i].sound;
|
||||
if (++voices >= 2)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* allocate and initialize the needed voices */
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
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]);
|
||||
}
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
|
||||
/* wait for completion of loading */
|
||||
snd_opl4_wait_for_wave_headers(opl4);
|
||||
|
||||
/* set remaining parameters */
|
||||
spin_lock_irqsave(&opl4->reg_lock, flags);
|
||||
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);
|
||||
}
|
||||
spin_unlock_irqrestore(&opl4->reg_lock, flags);
|
||||
}
|
||||
|
||||
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
sound/drivers/opl4/yrw801.c
Normal file
961
sound/drivers/opl4/yrw801.c
Normal 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;
|
||||
snd_printdd("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)
|
||||
};
|
||||
573
sound/drivers/pcm-indirect2.c
Normal file
573
sound/drivers/pcm-indirect2.c
Normal file
|
|
@ -0,0 +1,573 @@
|
|||
/*
|
||||
* Helper functions for indirect PCM data transfer to a simple FIFO in
|
||||
* hardware (small, no possibility to read "hardware io position",
|
||||
* updating position done by interrupt, ...)
|
||||
*
|
||||
* Copyright (c) by 2007 Joachim Foerster <JOFT@gmx.de>
|
||||
*
|
||||
* Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
|
||||
*
|
||||
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
||||
* Jaroslav Kysela <perex@suse.cz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
/* snd_printk/d() */
|
||||
#include <sound/core.h>
|
||||
/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t
|
||||
* snd_pcm_period_elapsed() */
|
||||
#include <sound/pcm.h>
|
||||
|
||||
#include "pcm-indirect2.h"
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
/* jiffies */
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
int i;
|
||||
int j;
|
||||
int k;
|
||||
int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ;
|
||||
|
||||
snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, "
|
||||
"irq_occured: %d\n",
|
||||
rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured);
|
||||
snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n",
|
||||
rec->min_multiple);
|
||||
snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, "
|
||||
"firstzerotime: %lu\n",
|
||||
rec->firstbytetime, rec->lastbytetime, rec->firstzerotime);
|
||||
snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) "
|
||||
"length: %d s\n",
|
||||
rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate);
|
||||
snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => "
|
||||
"rate: %d Bytes/s = %d Frames/s|Hz\n",
|
||||
seconds, rec->bytes2hw / seconds,
|
||||
rec->bytes2hw / 2 / 2 / seconds);
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n",
|
||||
rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) /
|
||||
runtime->rate,
|
||||
rec->zeros2hw / (rec->hw_buffer_size / 2),
|
||||
(rec->hw_buffer_size / 2));
|
||||
snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n",
|
||||
rec->pointer_calls, rec->lastdifftime);
|
||||
snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io,
|
||||
rec->sw_data);
|
||||
snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n");
|
||||
k = 0;
|
||||
for (j = 0; j < 8; j++) {
|
||||
for (i = j * 8; i < (j + 1) * 8; i++)
|
||||
if (rec->byte_sizes[i] != 0) {
|
||||
snd_printk(KERN_DEBUG "%u: %u",
|
||||
i, rec->byte_sizes[i]);
|
||||
k++;
|
||||
}
|
||||
if (((k % 8) == 0) && (k != 0)) {
|
||||
snd_printk(KERN_DEBUG "\n");
|
||||
k = 0;
|
||||
}
|
||||
}
|
||||
snd_printk(KERN_DEBUG "\n");
|
||||
snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n");
|
||||
for (j = 0; j < 8; j++) {
|
||||
k = 0;
|
||||
for (i = j * 8; i < (j + 1) * 8; i++)
|
||||
if (rec->zero_sizes[i] != 0)
|
||||
snd_printk(KERN_DEBUG "%u: %u",
|
||||
i, rec->zero_sizes[i]);
|
||||
else
|
||||
k++;
|
||||
if (!k)
|
||||
snd_printk(KERN_DEBUG "\n");
|
||||
}
|
||||
snd_printk(KERN_DEBUG "\n");
|
||||
snd_printk(KERN_DEBUG "STAT: min_adds[]:\n");
|
||||
for (j = 0; j < 8; j++) {
|
||||
if (rec->min_adds[j] != 0)
|
||||
snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]);
|
||||
}
|
||||
snd_printk(KERN_DEBUG "\n");
|
||||
snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n");
|
||||
for (j = 0; j < 8; j++) {
|
||||
if (rec->mul_adds[j] != 0)
|
||||
snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]);
|
||||
}
|
||||
snd_printk(KERN_DEBUG "\n");
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: zero_times_saved: %d, zero_times_notsaved: %d\n",
|
||||
rec->zero_times_saved, rec->zero_times_notsaved);
|
||||
/* snd_printk(KERN_DEBUG "STAT: zero_times[]\n");
|
||||
i = 0;
|
||||
for (j = 0; j < 3750; j++) {
|
||||
if (rec->zero_times[j] != 0) {
|
||||
snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]);
|
||||
i++;
|
||||
}
|
||||
if (((i % 8) == 0) && (i != 0))
|
||||
snd_printk(KERN_DEBUG "\n");
|
||||
}
|
||||
snd_printk(KERN_DEBUG "\n"); */
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* _internal_ helper function for playback/capture transfer function
|
||||
*/
|
||||
static void
|
||||
snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
int isplay, int iscopy,
|
||||
unsigned int bytes)
|
||||
{
|
||||
if (rec->min_periods >= 0) {
|
||||
if (iscopy) {
|
||||
rec->sw_io += bytes;
|
||||
if (rec->sw_io >= rec->sw_buffer_size)
|
||||
rec->sw_io -= rec->sw_buffer_size;
|
||||
} else if (isplay) {
|
||||
/* If application does not write data in multiples of
|
||||
* a period, move sw_data to the next correctly aligned
|
||||
* position, so that sw_io can converge to it (in the
|
||||
* next step).
|
||||
*/
|
||||
if (!rec->check_alignment) {
|
||||
if (rec->bytes2hw %
|
||||
snd_pcm_lib_period_bytes(substream)) {
|
||||
unsigned bytes2hw_aligned =
|
||||
(1 +
|
||||
(rec->bytes2hw /
|
||||
snd_pcm_lib_period_bytes
|
||||
(substream))) *
|
||||
snd_pcm_lib_period_bytes
|
||||
(substream);
|
||||
rec->sw_data =
|
||||
bytes2hw_aligned %
|
||||
rec->sw_buffer_size;
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: @re-align: aligned "
|
||||
"bytes2hw to next period "
|
||||
"size boundary: %d "
|
||||
"(instead of %d)\n",
|
||||
bytes2hw_aligned,
|
||||
rec->bytes2hw);
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: @re-align: sw_data "
|
||||
"moves to: %d\n",
|
||||
rec->sw_data);
|
||||
#endif
|
||||
}
|
||||
rec->check_alignment = 1;
|
||||
}
|
||||
/* We are at the end and are copying zeros into the
|
||||
* fifo.
|
||||
* Now, we have to make sure that sw_io is increased
|
||||
* until the position of sw_data: Filling the fifo with
|
||||
* the first zeros means, the last bytes were played.
|
||||
*/
|
||||
if (rec->sw_io != rec->sw_data) {
|
||||
unsigned int diff;
|
||||
if (rec->sw_data > rec->sw_io)
|
||||
diff = rec->sw_data - rec->sw_io;
|
||||
else
|
||||
diff = (rec->sw_buffer_size -
|
||||
rec->sw_io) +
|
||||
rec->sw_data;
|
||||
if (bytes >= diff)
|
||||
rec->sw_io = rec->sw_data;
|
||||
else {
|
||||
rec->sw_io += bytes;
|
||||
if (rec->sw_io >= rec->sw_buffer_size)
|
||||
rec->sw_io -=
|
||||
rec->sw_buffer_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
rec->min_period_count += bytes;
|
||||
if (rec->min_period_count >= (rec->hw_buffer_size / 2)) {
|
||||
rec->min_periods += (rec->min_period_count /
|
||||
(rec->hw_buffer_size / 2));
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if ((rec->min_period_count /
|
||||
(rec->hw_buffer_size / 2)) > 7)
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: more than 7 (%d) min_adds "
|
||||
"at once - too big to save!\n",
|
||||
(rec->min_period_count /
|
||||
(rec->hw_buffer_size / 2)));
|
||||
else
|
||||
rec->min_adds[(rec->min_period_count /
|
||||
(rec->hw_buffer_size / 2))]++;
|
||||
#endif
|
||||
rec->min_period_count = (rec->min_period_count %
|
||||
(rec->hw_buffer_size / 2));
|
||||
}
|
||||
} else if (isplay && iscopy)
|
||||
rec->min_periods = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* helper function for playback/capture pointer callback
|
||||
*/
|
||||
snd_pcm_uframes_t
|
||||
snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec)
|
||||
{
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
rec->pointer_calls++;
|
||||
#endif
|
||||
return bytes_to_frames(substream->runtime, rec->sw_io);
|
||||
}
|
||||
|
||||
/*
|
||||
* _internal_ helper function for playback interrupt callback
|
||||
*/
|
||||
static void
|
||||
snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
snd_pcm_indirect2_copy_t copy,
|
||||
snd_pcm_indirect2_zero_t zero)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
|
||||
|
||||
/* runtime->control->appl_ptr: position where ALSA will write next time
|
||||
* rec->appl_ptr: position where ALSA was last time
|
||||
* diff: obviously ALSA wrote that much bytes into the intermediate
|
||||
* buffer since we checked last time
|
||||
*/
|
||||
snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
|
||||
|
||||
if (diff) {
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
rec->lastdifftime = jiffies;
|
||||
#endif
|
||||
if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
|
||||
diff += runtime->boundary;
|
||||
/* number of bytes "added" by ALSA increases the number of
|
||||
* bytes which are ready to "be transferred to HW"/"played"
|
||||
* Then, set rec->appl_ptr to not count bytes twice next time.
|
||||
*/
|
||||
rec->sw_ready += (int)frames_to_bytes(runtime, diff);
|
||||
rec->appl_ptr = appl_ptr;
|
||||
}
|
||||
if (rec->hw_ready && (rec->sw_ready <= 0)) {
|
||||
unsigned int bytes;
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if (rec->firstzerotime == 0) {
|
||||
rec->firstzerotime = jiffies;
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: @firstzerotime: mul_elapsed: %d, "
|
||||
"min_period_count: %d\n",
|
||||
rec->mul_elapsed, rec->min_period_count);
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: @firstzerotime: sw_io: %d, "
|
||||
"sw_data: %d, appl_ptr: %u\n",
|
||||
rec->sw_io, rec->sw_data,
|
||||
(unsigned int)appl_ptr);
|
||||
}
|
||||
if ((jiffies - rec->firstzerotime) < 3750) {
|
||||
rec->zero_times[(jiffies - rec->firstzerotime)]++;
|
||||
rec->zero_times_saved++;
|
||||
} else
|
||||
rec->zero_times_notsaved++;
|
||||
#endif
|
||||
bytes = zero(substream, rec);
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
rec->zeros2hw += bytes;
|
||||
if (bytes < 64)
|
||||
rec->zero_sizes[bytes]++;
|
||||
else
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: %d zero Bytes copied to hardware at "
|
||||
"once - too big to save!\n",
|
||||
bytes);
|
||||
#endif
|
||||
snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0,
|
||||
bytes);
|
||||
return;
|
||||
}
|
||||
while (rec->hw_ready && (rec->sw_ready > 0)) {
|
||||
/* sw_to_end: max. number of bytes that can be read/take from
|
||||
* the current position (sw_data) in _one_ step
|
||||
*/
|
||||
unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data;
|
||||
|
||||
/* bytes: number of bytes we have available (for reading) */
|
||||
unsigned int bytes = rec->sw_ready;
|
||||
|
||||
if (sw_to_end < bytes)
|
||||
bytes = sw_to_end;
|
||||
if (!bytes)
|
||||
break;
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if (rec->firstbytetime == 0)
|
||||
rec->firstbytetime = jiffies;
|
||||
rec->lastbytetime = jiffies;
|
||||
#endif
|
||||
/* copy bytes from intermediate buffer position sw_data to the
|
||||
* HW and return number of bytes actually written
|
||||
* Furthermore, set hw_ready to 0, if the fifo isn't empty
|
||||
* now => more could be transferred to fifo
|
||||
*/
|
||||
bytes = copy(substream, rec, bytes);
|
||||
rec->bytes2hw += bytes;
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if (bytes < 64)
|
||||
rec->byte_sizes[bytes]++;
|
||||
else
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: %d Bytes copied to hardware at once "
|
||||
"- too big to save!\n",
|
||||
bytes);
|
||||
#endif
|
||||
/* increase sw_data by the number of actually written bytes
|
||||
* (= number of taken bytes from intermediate buffer)
|
||||
*/
|
||||
rec->sw_data += bytes;
|
||||
if (rec->sw_data == rec->sw_buffer_size)
|
||||
rec->sw_data = 0;
|
||||
/* now sw_data is the position where ALSA is going to write
|
||||
* in the intermediate buffer next time = position we are going
|
||||
* to read from next time
|
||||
*/
|
||||
|
||||
snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1,
|
||||
bytes);
|
||||
|
||||
/* we read bytes from intermediate buffer, so we need to say
|
||||
* that the number of bytes ready for transfer are decreased
|
||||
* now
|
||||
*/
|
||||
rec->sw_ready -= bytes;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* helper function for playback interrupt routine
|
||||
*/
|
||||
void
|
||||
snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
snd_pcm_indirect2_copy_t copy,
|
||||
snd_pcm_indirect2_zero_t zero)
|
||||
{
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
rec->irq_occured++;
|
||||
#endif
|
||||
/* hardware played some bytes, so there is room again (in fifo) */
|
||||
rec->hw_ready = 1;
|
||||
|
||||
/* don't call ack() now, instead call transfer() function directly
|
||||
* (normally called by ack() )
|
||||
*/
|
||||
snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero);
|
||||
|
||||
if (rec->min_periods >= rec->min_multiple) {
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if ((rec->min_periods / rec->min_multiple) > 7)
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: more than 7 (%d) mul_adds - too big "
|
||||
"to save!\n",
|
||||
(rec->min_periods / rec->min_multiple));
|
||||
else
|
||||
rec->mul_adds[(rec->min_periods /
|
||||
rec->min_multiple)]++;
|
||||
rec->mul_elapsed_real += (rec->min_periods /
|
||||
rec->min_multiple);
|
||||
rec->mul_elapsed++;
|
||||
#endif
|
||||
rec->min_periods = (rec->min_periods % rec->min_multiple);
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* _internal_ helper function for capture interrupt callback
|
||||
*/
|
||||
static void
|
||||
snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
snd_pcm_indirect2_copy_t copy,
|
||||
snd_pcm_indirect2_zero_t null)
|
||||
{
|
||||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr;
|
||||
snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr;
|
||||
|
||||
if (diff) {
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
rec->lastdifftime = jiffies;
|
||||
#endif
|
||||
if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2))
|
||||
diff += runtime->boundary;
|
||||
rec->sw_ready -= frames_to_bytes(runtime, diff);
|
||||
rec->appl_ptr = appl_ptr;
|
||||
}
|
||||
/* if hardware has something, but the intermediate buffer is full
|
||||
* => skip contents of buffer
|
||||
*/
|
||||
if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) {
|
||||
unsigned int bytes;
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if (rec->firstzerotime == 0) {
|
||||
rec->firstzerotime = jiffies;
|
||||
snd_printk(KERN_DEBUG "STAT: (capture) "
|
||||
"@firstzerotime: mul_elapsed: %d, "
|
||||
"min_period_count: %d\n",
|
||||
rec->mul_elapsed, rec->min_period_count);
|
||||
snd_printk(KERN_DEBUG "STAT: (capture) "
|
||||
"@firstzerotime: sw_io: %d, sw_data: %d, "
|
||||
"appl_ptr: %u\n",
|
||||
rec->sw_io, rec->sw_data,
|
||||
(unsigned int)appl_ptr);
|
||||
}
|
||||
if ((jiffies - rec->firstzerotime) < 3750) {
|
||||
rec->zero_times[(jiffies - rec->firstzerotime)]++;
|
||||
rec->zero_times_saved++;
|
||||
} else
|
||||
rec->zero_times_notsaved++;
|
||||
#endif
|
||||
bytes = null(substream, rec);
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
rec->zeros2hw += bytes;
|
||||
if (bytes < 64)
|
||||
rec->zero_sizes[bytes]++;
|
||||
else
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: (capture) %d zero Bytes copied to "
|
||||
"hardware at once - too big to save!\n",
|
||||
bytes);
|
||||
#endif
|
||||
snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0,
|
||||
bytes);
|
||||
/* report an overrun */
|
||||
rec->sw_io = SNDRV_PCM_POS_XRUN;
|
||||
return;
|
||||
}
|
||||
while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) {
|
||||
/* sw_to_end: max. number of bytes that we can write to the
|
||||
* intermediate buffer (until it's end)
|
||||
*/
|
||||
size_t sw_to_end = rec->sw_buffer_size - rec->sw_data;
|
||||
|
||||
/* bytes: max. number of bytes, which may be copied to the
|
||||
* intermediate buffer without overflow (in _one_ step)
|
||||
*/
|
||||
size_t bytes = rec->sw_buffer_size - rec->sw_ready;
|
||||
|
||||
/* limit number of bytes (for transfer) by available room in
|
||||
* the intermediate buffer
|
||||
*/
|
||||
if (sw_to_end < bytes)
|
||||
bytes = sw_to_end;
|
||||
if (!bytes)
|
||||
break;
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if (rec->firstbytetime == 0)
|
||||
rec->firstbytetime = jiffies;
|
||||
rec->lastbytetime = jiffies;
|
||||
#endif
|
||||
/* copy bytes from the intermediate buffer (position sw_data)
|
||||
* to the HW at most and return number of bytes actually copied
|
||||
* from HW
|
||||
* Furthermore, set hw_ready to 0, if the fifo is empty now.
|
||||
*/
|
||||
bytes = copy(substream, rec, bytes);
|
||||
rec->bytes2hw += bytes;
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if (bytes < 64)
|
||||
rec->byte_sizes[bytes]++;
|
||||
else
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: (capture) %d Bytes copied to "
|
||||
"hardware at once - too big to save!\n",
|
||||
bytes);
|
||||
#endif
|
||||
/* increase sw_data by the number of actually copied bytes from
|
||||
* HW
|
||||
*/
|
||||
rec->sw_data += bytes;
|
||||
if (rec->sw_data == rec->sw_buffer_size)
|
||||
rec->sw_data = 0;
|
||||
|
||||
snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1,
|
||||
bytes);
|
||||
|
||||
/* number of bytes in the intermediate buffer, which haven't
|
||||
* been fetched by ALSA yet.
|
||||
*/
|
||||
rec->sw_ready += bytes;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* helper function for capture interrupt routine
|
||||
*/
|
||||
void
|
||||
snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
snd_pcm_indirect2_copy_t copy,
|
||||
snd_pcm_indirect2_zero_t null)
|
||||
{
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
rec->irq_occured++;
|
||||
#endif
|
||||
/* hardware recorded some bytes, so there is something to read from the
|
||||
* record fifo:
|
||||
*/
|
||||
rec->hw_ready = 1;
|
||||
|
||||
/* don't call ack() now, instead call transfer() function directly
|
||||
* (normally called by ack() )
|
||||
*/
|
||||
snd_pcm_indirect2_capture_transfer(substream, rec, copy, null);
|
||||
|
||||
if (rec->min_periods >= rec->min_multiple) {
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
if ((rec->min_periods / rec->min_multiple) > 7)
|
||||
snd_printk(KERN_DEBUG
|
||||
"STAT: more than 7 (%d) mul_adds - "
|
||||
"too big to save!\n",
|
||||
(rec->min_periods / rec->min_multiple));
|
||||
else
|
||||
rec->mul_adds[(rec->min_periods /
|
||||
rec->min_multiple)]++;
|
||||
rec->mul_elapsed_real += (rec->min_periods /
|
||||
rec->min_multiple);
|
||||
rec->mul_elapsed++;
|
||||
#endif
|
||||
rec->min_periods = (rec->min_periods % rec->min_multiple);
|
||||
snd_pcm_period_elapsed(substream);
|
||||
}
|
||||
}
|
||||
140
sound/drivers/pcm-indirect2.h
Normal file
140
sound/drivers/pcm-indirect2.h
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Helper functions for indirect PCM data transfer to a simple FIFO in
|
||||
* hardware (small, no possibility to read "hardware io position",
|
||||
* updating position done by interrupt, ...)
|
||||
*
|
||||
* Copyright (c) by 2007 Joachim Foerster <JOFT@gmx.de>
|
||||
*
|
||||
* Based on "pcm-indirect.h" (alsa-driver-1.0.13) by
|
||||
*
|
||||
* Copyright (c) by Takashi Iwai <tiwai@suse.de>
|
||||
* Jaroslav Kysela <perex@suse.cz>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef __SOUND_PCM_INDIRECT2_H
|
||||
#define __SOUND_PCM_INDIRECT2_H
|
||||
|
||||
/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t */
|
||||
#include <sound/pcm.h>
|
||||
|
||||
/* Debug options for code which may be removed completely in a final version */
|
||||
#ifdef CONFIG_SND_DEBUG
|
||||
#define SND_PCM_INDIRECT2_STAT /* turn on some "statistics" about the
|
||||
* process of copying bytes from the
|
||||
* intermediate buffer to the hardware
|
||||
* fifo and the other way round
|
||||
*/
|
||||
#endif
|
||||
|
||||
struct snd_pcm_indirect2 {
|
||||
unsigned int hw_buffer_size; /* Byte size of hardware buffer */
|
||||
int hw_ready; /* playback: 1 = hw fifo has room left,
|
||||
* 0 = hw fifo is full
|
||||
*/
|
||||
unsigned int min_multiple;
|
||||
int min_periods; /* counts number of min. periods until
|
||||
* min_multiple is reached
|
||||
*/
|
||||
int min_period_count; /* counts bytes to count number of
|
||||
* min. periods
|
||||
*/
|
||||
|
||||
unsigned int sw_buffer_size; /* Byte size of software buffer */
|
||||
|
||||
/* sw_data: position in intermediate buffer, where we will read (or
|
||||
* write) from/to next time (to transfer data to/from HW)
|
||||
*/
|
||||
unsigned int sw_data; /* Offset to next dst (or src) in sw
|
||||
* ring buffer
|
||||
*/
|
||||
/* easiest case (playback):
|
||||
* sw_data is nearly the same as ~ runtime->control->appl_ptr, with the
|
||||
* exception that sw_data is "behind" by the number if bytes ALSA wrote
|
||||
* to the intermediate buffer last time.
|
||||
* A call to ack() callback synchronizes both indirectly.
|
||||
*/
|
||||
|
||||
/* We have no real sw_io pointer here. Usually sw_io is pointing to the
|
||||
* current playback/capture position _inside_ the hardware. Devices
|
||||
* with plain FIFOs often have no possibility to publish this position.
|
||||
* So we say: if sw_data is updated, that means bytes were copied to
|
||||
* the hardware, we increase sw_io by that amount, because there have
|
||||
* to be as much bytes which were played. So sw_io will stay behind
|
||||
* sw_data all the time and has to converge to sw_data at the end of
|
||||
* playback.
|
||||
*/
|
||||
unsigned int sw_io; /* Current software pointer in bytes */
|
||||
|
||||
/* sw_ready: number of bytes ALSA copied to the intermediate buffer, so
|
||||
* it represents the number of bytes which wait for transfer to the HW
|
||||
*/
|
||||
int sw_ready; /* Bytes ready to be transferred to/from hw */
|
||||
|
||||
/* appl_ptr: last known position of ALSA (where ALSA is going to write
|
||||
* next time into the intermediate buffer
|
||||
*/
|
||||
snd_pcm_uframes_t appl_ptr; /* Last seen appl_ptr */
|
||||
|
||||
unsigned int bytes2hw;
|
||||
int check_alignment;
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
unsigned int zeros2hw;
|
||||
unsigned int mul_elapsed;
|
||||
unsigned int mul_elapsed_real;
|
||||
unsigned long firstbytetime;
|
||||
unsigned long lastbytetime;
|
||||
unsigned long firstzerotime;
|
||||
unsigned int byte_sizes[64];
|
||||
unsigned int zero_sizes[64];
|
||||
unsigned int min_adds[8];
|
||||
unsigned int mul_adds[8];
|
||||
unsigned int zero_times[3750]; /* = 15s */
|
||||
unsigned int zero_times_saved;
|
||||
unsigned int zero_times_notsaved;
|
||||
unsigned int irq_occured;
|
||||
unsigned int pointer_calls;
|
||||
unsigned int lastdifftime;
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef size_t (*snd_pcm_indirect2_copy_t) (struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
size_t bytes);
|
||||
typedef size_t (*snd_pcm_indirect2_zero_t) (struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec);
|
||||
|
||||
#ifdef SND_PCM_INDIRECT2_STAT
|
||||
void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec);
|
||||
#endif
|
||||
|
||||
snd_pcm_uframes_t
|
||||
snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec);
|
||||
void
|
||||
snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
snd_pcm_indirect2_copy_t copy,
|
||||
snd_pcm_indirect2_zero_t zero);
|
||||
void
|
||||
snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream,
|
||||
struct snd_pcm_indirect2 *rec,
|
||||
snd_pcm_indirect2_copy_t copy,
|
||||
snd_pcm_indirect2_zero_t null);
|
||||
|
||||
#endif /* __SOUND_PCM_INDIRECT2_H */
|
||||
2
sound/drivers/pcsp/Makefile
Normal file
2
sound/drivers/pcsp/Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
snd-pcsp-objs := pcsp.o pcsp_lib.o pcsp_mixer.o pcsp_input.o
|
||||
obj-$(CONFIG_SND_PCSP) += snd-pcsp.o
|
||||
245
sound/drivers/pcsp/pcsp.c
Normal file
245
sound/drivers/pcsp/pcsp.c
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* 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 <asm/bitops.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_SUPPORTED_DEVICE("{{PC-Speaker, pcsp}}");
|
||||
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)
|
||||
{
|
||||
static struct snd_device_ops ops = { };
|
||||
struct timespec tp;
|
||||
int err;
|
||||
int div, min_div, order;
|
||||
|
||||
hrtimer_get_res(CLOCK_MONOTONIC, &tp);
|
||||
|
||||
if (!nopcm) {
|
||||
if (tp.tv_sec || tp.tv_nsec > PCSP_MAX_PERIOD_NS) {
|
||||
printk(KERN_ERR "PCSP: Timer resolution is not sufficient "
|
||||
"(%linS)\n", tp.tv_nsec);
|
||||
printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI "
|
||||
"enabled.\n");
|
||||
printk(KERN_ERR "PCSP: Turned into nopcm mode.\n");
|
||||
nopcm = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (loops_per_jiffy >= PCSP_MIN_LPJ && tp.tv_nsec <= PCSP_MIN_PERIOD_NS)
|
||||
min_div = MIN_DIV;
|
||||
else
|
||||
min_div = MAX_DIV;
|
||||
#if PCSP_DEBUG
|
||||
printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%li\n",
|
||||
loops_per_jiffy, min_div, tp.tv_nsec);
|
||||
#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;
|
||||
|
||||
/* Register device */
|
||||
err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, &pcsp_chip, &ops);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int snd_card_pcsp_probe(int devnum, struct device *dev)
|
||||
{
|
||||
struct snd_card *card;
|
||||
int err;
|
||||
|
||||
if (devnum != 0)
|
||||
return -EINVAL;
|
||||
|
||||
hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
||||
pcsp_chip.timer.function = pcsp_do_timer;
|
||||
|
||||
err = snd_card_new(dev, index, id, THIS_MODULE, 0, &card);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = snd_pcsp_create(card);
|
||||
if (err < 0) {
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
if (!nopcm) {
|
||||
err = snd_pcsp_new_pcm(&pcsp_chip);
|
||||
if (err < 0) {
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
err = snd_pcsp_new_mixer(&pcsp_chip, nopcm);
|
||||
if (err < 0) {
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
strcpy(card->driver, "PC-Speaker");
|
||||
strcpy(card->shortname, "pcsp");
|
||||
sprintf(card->longname, "Internal PC-Speaker at port 0x%x",
|
||||
pcsp_chip.port);
|
||||
|
||||
err = snd_card_register(card);
|
||||
if (err < 0) {
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int alsa_card_pcsp_init(struct device *dev)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = snd_card_pcsp_probe(0, dev);
|
||||
if (err) {
|
||||
printk(KERN_ERR "PC-Speaker initialization failed.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_PAGEALLOC
|
||||
/* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */
|
||||
printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, "
|
||||
"which may make the sound noisy.\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void alsa_card_pcsp_exit(struct snd_pcsp *chip)
|
||||
{
|
||||
snd_card_free(chip->card);
|
||||
}
|
||||
|
||||
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) {
|
||||
pcspkr_input_remove(pcsp_chip.input_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(dev, &pcsp_chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pcsp_remove(struct platform_device *dev)
|
||||
{
|
||||
struct snd_pcsp *chip = platform_get_drvdata(dev);
|
||||
pcspkr_input_remove(chip->input_dev);
|
||||
alsa_card_pcsp_exit(chip);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pcsp_stop_beep(struct snd_pcsp *chip)
|
||||
{
|
||||
pcsp_sync_stop(chip);
|
||||
pcspkr_stop_sound();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int pcsp_suspend(struct device *dev)
|
||||
{
|
||||
struct snd_pcsp *chip = dev_get_drvdata(dev);
|
||||
pcsp_stop_beep(chip);
|
||||
snd_pcm_suspend_all(chip->pcm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL);
|
||||
#define PCSP_PM_OPS &pcsp_pm
|
||||
#else
|
||||
#define PCSP_PM_OPS NULL
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
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",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = PCSP_PM_OPS,
|
||||
},
|
||||
.probe = pcsp_probe,
|
||||
.remove = pcsp_remove,
|
||||
.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);
|
||||
82
sound/drivers/pcsp/pcsp.h
Normal file
82
sound/drivers/pcsp/pcsp.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* 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
|
||||
117
sound/drivers/pcsp/pcsp_input.c
Normal file
117
sound/drivers/pcsp/pcsp_input.c
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* PC Speaker beeper driver for Linux
|
||||
*
|
||||
* Copyright (c) 2002 Vojtech Pavlik
|
||||
* Copyright (c) 1992 Orest Zborowski
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <asm/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;
|
||||
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 = input_allocate_device();
|
||||
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) {
|
||||
input_free_device(input_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
*rdev = input_dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pcspkr_input_remove(struct input_dev *dev)
|
||||
{
|
||||
pcspkr_stop_sound();
|
||||
input_unregister_device(dev); /* this also does kfree() */
|
||||
|
||||
return 0;
|
||||
}
|
||||
14
sound/drivers/pcsp/pcsp_input.h
Normal file
14
sound/drivers/pcsp/pcsp_input.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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);
|
||||
int pcspkr_input_remove(struct input_dev *dev);
|
||||
void pcspkr_stop_sound(void);
|
||||
|
||||
#endif
|
||||
359
sound/drivers/pcsp/pcsp_lib.c
Normal file
359
sound/drivers/pcsp/pcsp_lib.c
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
* 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 <sound/pcm.h>
|
||||
#include <asm/io.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 tasklet
|
||||
* This avoids spinlock messes and long-running irq contexts
|
||||
*/
|
||||
static void pcsp_call_pcm_elapsed(unsigned long priv)
|
||||
{
|
||||
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_TASKLET(pcsp_pcm_tasklet, pcsp_call_pcm_elapsed, 0);
|
||||
|
||||
/* 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
|
||||
printk(KERN_INFO "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;
|
||||
}
|
||||
spin_unlock_irqrestore(&chip->substream_lock, flags);
|
||||
|
||||
if (periods_elapsed)
|
||||
tasklet_schedule(&pcsp_pcm_tasklet);
|
||||
}
|
||||
|
||||
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) {
|
||||
printk(KERN_WARNING "PCSP: unexpected stop\n");
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
if (pointer_update)
|
||||
pcsp_pointer_update(chip);
|
||||
|
||||
hrtimer_forward(handle, hrtimer_get_expires(handle), ns_to_ktime(ns));
|
||||
|
||||
return HRTIMER_RESTART;
|
||||
}
|
||||
|
||||
static int pcsp_start_playing(struct snd_pcsp *chip)
|
||||
{
|
||||
#if PCSP_DEBUG
|
||||
printk(KERN_INFO "PCSP: start_playing called\n");
|
||||
#endif
|
||||
if (atomic_read(&chip->timer_active)) {
|
||||
printk(KERN_ERR "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, ktime_set(0, 0), HRTIMER_MODE_REL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pcsp_stop_playing(struct snd_pcsp *chip)
|
||||
{
|
||||
#if PCSP_DEBUG
|
||||
printk(KERN_INFO "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);
|
||||
tasklet_kill(&pcsp_pcm_tasklet);
|
||||
}
|
||||
|
||||
static int snd_pcsp_playback_close(struct snd_pcm_substream *substream)
|
||||
{
|
||||
struct snd_pcsp *chip = snd_pcm_substream_chip(substream);
|
||||
#if PCSP_DEBUG
|
||||
printk(KERN_INFO "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);
|
||||
int err;
|
||||
pcsp_sync_stop(chip);
|
||||
err = snd_pcm_lib_malloc_pages(substream,
|
||||
params_buffer_bytes(hw_params));
|
||||
if (err < 0)
|
||||
return err;
|
||||
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
|
||||
printk(KERN_INFO "PCSP: hw_free called\n");
|
||||
#endif
|
||||
pcsp_sync_stop(chip);
|
||||
return snd_pcm_lib_free_pages(substream);
|
||||
}
|
||||
|
||||
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
|
||||
printk(KERN_INFO "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
|
||||
printk(KERN_INFO "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 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
|
||||
printk(KERN_INFO "PCSP: open called\n");
|
||||
#endif
|
||||
if (atomic_read(&chip->timer_active)) {
|
||||
printk(KERN_ERR "PCSP: still active!!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
runtime->hw = snd_pcsp_playback;
|
||||
chip->playback_substream = substream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct snd_pcm_ops snd_pcsp_playback_ops = {
|
||||
.open = snd_pcsp_playback_open,
|
||||
.close = snd_pcsp_playback_close,
|
||||
.ioctl = snd_pcm_lib_ioctl,
|
||||
.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;
|
||||
strcpy(chip->pcm->name, "pcsp");
|
||||
|
||||
snd_pcm_lib_preallocate_pages_for_all(chip->pcm,
|
||||
SNDRV_DMA_TYPE_CONTINUOUS,
|
||||
snd_dma_continuous_data
|
||||
(GFP_KERNEL), PCSP_BUFFER_SIZE,
|
||||
PCSP_BUFFER_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
163
sound/drivers/pcsp/pcsp_mixer.c
Normal file
163
sound/drivers/pcsp/pcsp_mixer.c
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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
|
||||
printk(KERN_INFO "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 struct snd_kcontrol_new snd_pcsp_controls_pcm[] = {
|
||||
PCSP_MIXER_CONTROL(enable, "Master Playback Switch"),
|
||||
PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"),
|
||||
};
|
||||
|
||||
static 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,
|
||||
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;
|
||||
|
||||
strcpy(card->mixername, "PC-Speaker");
|
||||
|
||||
return 0;
|
||||
}
|
||||
879
sound/drivers/portman2x4.c
Normal file
879
sound/drivers/portman2x4.c
Normal file
|
|
@ -0,0 +1,879 @@
|
|||
/*
|
||||
* Driver for Midiman Portman2x4 parallel port midi interface
|
||||
*
|
||||
* Copyright (c) by Levent Guendogdu <levon@feature-it.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* 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, S_IRUGO);
|
||||
MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
|
||||
module_param_array(id, charp, NULL, S_IRUGO);
|
||||
MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
|
||||
module_param_array(enable, bool, NULL, S_IRUGO);
|
||||
MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
|
||||
|
||||
MODULE_AUTHOR("Levent Guendogdu, Tobias Gehrig, Matthias Koenig");
|
||||
MODULE_DESCRIPTION("Midiman Portman2x4");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_SUPPORTED_DEVICE("{{Midiman,Portman2x4}}");
|
||||
|
||||
/*********************************************************************
|
||||
* 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 pardev_claimed;
|
||||
|
||||
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(sizeof(struct portman), GFP_KERNEL);
|
||||
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_command(struct portman *pm)
|
||||
{
|
||||
return parport_read_control(pm->pardev->port);
|
||||
}
|
||||
|
||||
static inline u8 portman_read_status(struct portman *pm)
|
||||
{
|
||||
return parport_read_status(pm->pardev->port);
|
||||
}
|
||||
|
||||
static inline u8 portman_read_data(struct portman *pm)
|
||||
{
|
||||
return parport_read_data(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:
|
||||
snd_printk(KERN_WARNING
|
||||
"portman_flush_input() Won't flush port %i\n",
|
||||
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 if 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;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&pm->reg_lock, flags);
|
||||
if (up)
|
||||
pm->mode[substream->number] |= PORTMAN2X4_MODE_INPUT_TRIGGERED;
|
||||
else
|
||||
pm->mode[substream->number] &= ~PORTMAN2X4_MODE_INPUT_TRIGGERED;
|
||||
spin_unlock_irqrestore(&pm->reg_lock, flags);
|
||||
}
|
||||
|
||||
static void snd_portman_midi_output_trigger(struct snd_rawmidi_substream *substream,
|
||||
int up)
|
||||
{
|
||||
struct portman *pm = substream->rmidi->private_data;
|
||||
unsigned long flags;
|
||||
unsigned char byte;
|
||||
|
||||
spin_lock_irqsave(&pm->reg_lock, flags);
|
||||
if (up) {
|
||||
while ((snd_rawmidi_transmit(substream, &byte, 1) == 1))
|
||||
portman_write_midi(pm, substream->number, byte);
|
||||
}
|
||||
spin_unlock_irqrestore(&pm->reg_lock, flags);
|
||||
}
|
||||
|
||||
static 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 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;
|
||||
strcpy(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;
|
||||
|
||||
spin_lock(&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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
spin_unlock(&pm->reg_lock);
|
||||
}
|
||||
|
||||
static int snd_portman_probe_port(struct parport *p)
|
||||
{
|
||||
struct pardevice *pardev;
|
||||
int res;
|
||||
|
||||
pardev = parport_register_device(p, DRIVER_NAME,
|
||||
NULL, NULL, NULL,
|
||||
0, NULL);
|
||||
if (!pardev)
|
||||
return -EIO;
|
||||
|
||||
if (parport_claim(pardev)) {
|
||||
parport_unregister_device(pardev);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
res = portman_probe(p);
|
||||
|
||||
parport_release(pardev);
|
||||
parport_unregister_device(pardev);
|
||||
|
||||
return res ? -EIO : 0;
|
||||
}
|
||||
|
||||
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 struct parport_driver portman_parport_driver = {
|
||||
.name = "portman2x4",
|
||||
.attach = 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) {
|
||||
if (pm->pardev_claimed)
|
||||
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;
|
||||
|
||||
p = platform_get_drvdata(pdev);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
|
||||
if (dev >= SNDRV_CARDS)
|
||||
return -ENODEV;
|
||||
if (!enable[dev])
|
||||
return -ENOENT;
|
||||
|
||||
if ((err = snd_portman_probe_port(p)) < 0)
|
||||
return err;
|
||||
|
||||
err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE,
|
||||
0, &card);
|
||||
if (err < 0) {
|
||||
snd_printd("Cannot create card\n");
|
||||
return err;
|
||||
}
|
||||
strcpy(card->driver, DRIVER_NAME);
|
||||
strcpy(card->shortname, CARD_NAME);
|
||||
sprintf(card->longname, "%s at 0x%lx, irq %i",
|
||||
card->shortname, p->base, p->irq);
|
||||
|
||||
pardev = parport_register_device(p, /* port */
|
||||
DRIVER_NAME, /* name */
|
||||
NULL, /* preempt */
|
||||
NULL, /* wakeup */
|
||||
snd_portman_interrupt, /* ISR */
|
||||
PARPORT_DEV_EXCL, /* flags */
|
||||
(void *)card); /* private */
|
||||
if (pardev == NULL) {
|
||||
snd_printd("Cannot register pardevice\n");
|
||||
err = -EIO;
|
||||
goto __err;
|
||||
}
|
||||
|
||||
if ((err = portman_create(card, pardev, &pm)) < 0) {
|
||||
snd_printd("Cannot create main component\n");
|
||||
parport_unregister_device(pardev);
|
||||
goto __err;
|
||||
}
|
||||
card->private_data = pm;
|
||||
card->private_free = snd_portman_card_private_free;
|
||||
|
||||
if ((err = snd_portman_rawmidi_create(card)) < 0) {
|
||||
snd_printd("Creating Rawmidi component failed\n");
|
||||
goto __err;
|
||||
}
|
||||
|
||||
/* claim parport */
|
||||
if (parport_claim(pardev)) {
|
||||
snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base);
|
||||
err = -EIO;
|
||||
goto __err;
|
||||
}
|
||||
pm->pardev_claimed = 1;
|
||||
|
||||
/* init device */
|
||||
if ((err = portman_device_init(pm)) < 0)
|
||||
goto __err;
|
||||
|
||||
platform_set_drvdata(pdev, card);
|
||||
|
||||
/* At this point card will be usable */
|
||||
if ((err = snd_card_register(card)) < 0) {
|
||||
snd_printd("Cannot register card\n");
|
||||
goto __err;
|
||||
}
|
||||
|
||||
snd_printk(KERN_INFO "Portman 2x4 on 0x%lx\n", p->base);
|
||||
return 0;
|
||||
|
||||
__err:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_portman_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snd_card *card = platform_get_drvdata(pdev);
|
||||
|
||||
if (card)
|
||||
snd_card_free(card);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct platform_driver snd_portman_driver = {
|
||||
.probe = snd_portman_probe,
|
||||
.remove = snd_portman_remove,
|
||||
.driver = {
|
||||
.name = PLATFORM_DRIVER,
|
||||
.owner = THIS_MODULE,
|
||||
}
|
||||
};
|
||||
|
||||
/*********************************************************************
|
||||
* 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;
|
||||
|
||||
if ((err = platform_driver_register(&snd_portman_driver)) < 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);
|
||||
1049
sound/drivers/serial-u16550.c
Normal file
1049
sound/drivers/serial-u16550.c
Normal file
File diff suppressed because it is too large
Load diff
195
sound/drivers/virmidi.c
Normal file
195
sound/drivers/virmidi.c
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Dummy soundcard for virtual rawmidi devices
|
||||
*
|
||||
* Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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");
|
||||
MODULE_SUPPORTED_DEVICE("{{ALSA,Virtual rawmidi device}}");
|
||||
|
||||
#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_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) {
|
||||
snd_printk(KERN_WARNING
|
||||
"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;
|
||||
struct snd_virmidi_dev *rdev;
|
||||
if ((err = snd_virmidi_new(card, idx, &rmidi)) < 0)
|
||||
goto __nodev;
|
||||
rdev = rmidi->private_data;
|
||||
vmidi->midi[idx] = rmidi;
|
||||
strcpy(rmidi->name, "Virtual Raw MIDI");
|
||||
rdev->seq_mode = SNDRV_VIRMIDI_SEQ_DISPATCH;
|
||||
}
|
||||
|
||||
strcpy(card->driver, "VirMIDI");
|
||||
strcpy(card->shortname, "VirMIDI");
|
||||
sprintf(card->longname, "Virtual MIDI Card %i", dev + 1);
|
||||
|
||||
if ((err = snd_card_register(card)) == 0) {
|
||||
platform_set_drvdata(devptr, card);
|
||||
return 0;
|
||||
}
|
||||
__nodev:
|
||||
snd_card_free(card);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int snd_virmidi_remove(struct platform_device *devptr)
|
||||
{
|
||||
snd_card_free(platform_get_drvdata(devptr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SND_VIRMIDI_DRIVER "snd_virmidi"
|
||||
|
||||
static struct platform_driver snd_virmidi_driver = {
|
||||
.probe = snd_virmidi_probe,
|
||||
.remove = snd_virmidi_remove,
|
||||
.driver = {
|
||||
.name = SND_VIRMIDI_DRIVER,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
if ((err = platform_driver_register(&snd_virmidi_driver)) < 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
|
||||
printk(KERN_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)
|
||||
8
sound/drivers/vx/Makefile
Normal file
8
sound/drivers/vx/Makefile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#
|
||||
# Makefile for ALSA
|
||||
# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz>
|
||||
#
|
||||
|
||||
snd-vx-lib-objs := 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
|
||||
109
sound/drivers/vx/vx_cmd.c
Normal file
109
sound/drivers/vx/vx_cmd.c
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Driver for Digigram VX soundcards
|
||||
*
|
||||
* DSP commands
|
||||
*
|
||||
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/vx_core.h>
|
||||
#include "vx_cmd.h"
|
||||
|
||||
/*
|
||||
* Array of DSP commands
|
||||
*/
|
||||
static 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;
|
||||
}
|
||||
|
||||
246
sound/drivers/vx/vx_cmd.h
Normal file
246
sound/drivers/vx/vx_cmd.h
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* Driver for Digigram VX soundcards
|
||||
*
|
||||
* Definitions of DSP commands
|
||||
*
|
||||
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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_send_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 */
|
||||
824
sound/drivers/vx/vx_core.c
Normal file
824
sound/drivers/vx/vx_core.c
Normal file
|
|
@ -0,0 +1,824 @@
|
|||
/*
|
||||
* Driver for Digigram VX soundcards
|
||||
*
|
||||
* Hardware core part
|
||||
*
|
||||
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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 <sound/core.h>
|
||||
#include <sound/pcm.h>
|
||||
#include <sound/asoundef.h>
|
||||
#include <sound/info.h>
|
||||
#include <asm/io.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 char *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));
|
||||
snd_printd(KERN_DEBUG "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;
|
||||
|
||||
if ((err = vx_reset_chk(chip)) < 0)
|
||||
return err;
|
||||
|
||||
/* irq MESS_READ/WRITE_END */
|
||||
if ((err = vx_send_irq_dsp(chip, cmd)) < 0)
|
||||
return err;
|
||||
|
||||
/* Wait CHK = 1 */
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
|
||||
return err;
|
||||
|
||||
/* If error, Read RX */
|
||||
if ((err = vx_inb(chip, ISR)) & ISR_ERR) {
|
||||
if ((err = vx_wait_for_rx_full(chip)) < 0) {
|
||||
snd_printd(KERN_DEBUG "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);
|
||||
snd_printd(KERN_DEBUG "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;
|
||||
|
||||
if ((err = vx_reset_chk(chip)) < 0) {
|
||||
snd_printd(KERN_DEBUG "vx_send_msg: vx_reset_chk error\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
#if 0
|
||||
printk(KERN_DEBUG "rmh: cmd = 0x%06x, length = %d, stype = %d\n",
|
||||
rmh->Cmd[0], rmh->LgCmd, rmh->DspStat);
|
||||
if (rmh->LgCmd > 1) {
|
||||
printk(KERN_DEBUG " ");
|
||||
for (i = 1; i < rmh->LgCmd; i++)
|
||||
printk("0x%06x ", rmh->Cmd[i]);
|
||||
printk("\n");
|
||||
}
|
||||
#endif
|
||||
/* 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 */
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) {
|
||||
snd_printd(KERN_DEBUG "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 */
|
||||
if ((err = vx_send_irq_dsp(chip, IRQ_MESSAGE)) < 0) {
|
||||
snd_printd(KERN_DEBUG "vx_send_msg: send IRQ_MESSAGE error\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Wait for CHK = 1 */
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
|
||||
return err;
|
||||
|
||||
/* If error, get error value from RX */
|
||||
if (vx_inb(chip, ISR) & ISR_ERR) {
|
||||
if ((err = vx_wait_for_rx_full(chip)) < 0) {
|
||||
snd_printd(KERN_DEBUG "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);
|
||||
snd_printd(KERN_DEBUG "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 */
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) {
|
||||
snd_printd(KERN_DEBUG "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 */
|
||||
if ((err = vx_send_irq_dsp(chip, IRQ_MESS_READ_NEXT)) < 0) {
|
||||
snd_printd(KERN_DEBUG "vx_send_msg: IRQ_READ_NEXT error\n");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
/* Wait for TX empty */
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_TX_READY)) < 0) {
|
||||
snd_printd(KERN_DEBUG "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)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
err = vx_send_msg_nolock(chip, rmh);
|
||||
mutex_unlock(&chip->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
#if 0
|
||||
printk(KERN_DEBUG "send_rih: cmd = 0x%x\n", cmd);
|
||||
#endif
|
||||
if ((err = vx_reset_chk(chip)) < 0)
|
||||
return err;
|
||||
/* send the IRQ */
|
||||
if ((err = vx_send_irq_dsp(chip, cmd)) < 0)
|
||||
return err;
|
||||
/* Wait CHK = 1 */
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 0)
|
||||
return err;
|
||||
/* If error, read RX */
|
||||
if (vx_inb(chip, ISR) & ISR_ERR) {
|
||||
if ((err = vx_wait_for_rx_full(chip)) < 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)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
err = vx_send_rih_nolock(chip, cmd);
|
||||
mutex_unlock(&chip->lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
#define END_OF_RESET_WAIT_TIME 500 /* us */
|
||||
|
||||
/**
|
||||
* snd_vx_boot_xilinx - boot up the xilinx interface
|
||||
* @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) {
|
||||
snd_printk(KERN_ERR "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) {
|
||||
snd_printk(KERN_ERR "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);
|
||||
mutex_lock(&chip->lock);
|
||||
err = vx_send_msg_nolock(chip, &chip->irq_rmh);
|
||||
if (err < 0)
|
||||
*ret = 0;
|
||||
else
|
||||
*ret = chip->irq_rmh.Stat[0];
|
||||
mutex_unlock(&chip->lock);
|
||||
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;
|
||||
|
||||
#if 0
|
||||
if (events & 0x000800)
|
||||
printk(KERN_ERR "DSP Stream underrun ! IRQ events = 0x%x\n", events);
|
||||
#endif
|
||||
// printk(KERN_DEBUG "IRQ events = 0x%x\n", events);
|
||||
|
||||
/* 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) {
|
||||
snd_printk(KERN_ERR "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
|
||||
*/
|
||||
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 char *audio_src_vxp[] = { "Line", "Mic", "Digital" };
|
||||
static char *audio_src_vx2[] = { "Analog", "Analog", "Digital" };
|
||||
static char *clock_mode[] = { "Auto", "Internal", "External" };
|
||||
static char *clock_src[] = { "Internal", "External" };
|
||||
static char *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)
|
||||
{
|
||||
struct snd_info_entry *entry;
|
||||
|
||||
if (! snd_card_proc_new(chip->card, "vx-status", &entry))
|
||||
snd_info_set_text_ops(entry, chip, vx_proc_read);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* snd_vx_dsp_boot - load the DSP boot
|
||||
*/
|
||||
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);
|
||||
|
||||
if ((err = snd_vx_load_boot_image(chip, boot)) < 0)
|
||||
return err;
|
||||
msleep(10);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_vx_dsp_boot);
|
||||
|
||||
/**
|
||||
* snd_vx_dsp_load - load the DSP image
|
||||
*/
|
||||
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 */
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_TX_EMPTY)) < 0) {
|
||||
printk(KERN_ERR
|
||||
"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++);
|
||||
}
|
||||
snd_printdd(KERN_DEBUG "checksum = 0x%08x\n", csum);
|
||||
|
||||
msleep(200);
|
||||
|
||||
if ((err = vx_wait_isr_bit(chip, ISR_CHK)) < 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)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
|
||||
chip->chip_status |= VX_STAT_IN_SUSPEND;
|
||||
for (i = 0; i < chip->hw->num_codecs; i++)
|
||||
snd_pcm_suspend_all(chip->pcm[i]);
|
||||
|
||||
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) {
|
||||
snd_printk(KERN_ERR "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
|
||||
|
||||
/**
|
||||
* snd_vx_create - constructor for struct vx_core
|
||||
* @hw: hardware specific record
|
||||
*
|
||||
* this function allocates the instance and prepare for the hardware
|
||||
* initialization.
|
||||
*
|
||||
* return the instance pointer if successful, NULL in error.
|
||||
*/
|
||||
struct vx_core *snd_vx_create(struct snd_card *card, struct snd_vx_hardware *hw,
|
||||
struct snd_vx_ops *ops,
|
||||
int extra_size)
|
||||
{
|
||||
struct vx_core *chip;
|
||||
|
||||
if (snd_BUG_ON(!card || !hw || !ops))
|
||||
return NULL;
|
||||
|
||||
chip = kzalloc(sizeof(*chip) + extra_size, GFP_KERNEL);
|
||||
if (! chip) {
|
||||
snd_printk(KERN_ERR "vx_core: no memory\n");
|
||||
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;
|
||||
strcpy(card->driver, hw->name);
|
||||
sprintf(card->shortname, "Digigram %s", hw->name);
|
||||
|
||||
vx_proc_init(chip);
|
||||
|
||||
return chip;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(snd_vx_create);
|
||||
|
||||
/*
|
||||
* module entries
|
||||
*/
|
||||
static int __init alsa_vx_core_init(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit alsa_vx_core_exit(void)
|
||||
{
|
||||
}
|
||||
|
||||
module_init(alsa_vx_core_init)
|
||||
module_exit(alsa_vx_core_exit)
|
||||
121
sound/drivers/vx/vx_hwdep.c
Normal file
121
sound/drivers/vx/vx_hwdep.c
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Driver for Digigram VX soundcards
|
||||
*
|
||||
* DSP firmware management
|
||||
*
|
||||
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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 char *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->dev)) {
|
||||
snd_printk(KERN_ERR "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 */
|
||||
if ((err = snd_vx_pcm_new(chip)) < 0)
|
||||
return err;
|
||||
|
||||
if ((err = snd_vx_mixer_new(chip)) < 0)
|
||||
return err;
|
||||
|
||||
if (chip->ops->add_controls)
|
||||
if ((err = chip->ops->add_controls(chip)) < 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);
|
||||
1024
sound/drivers/vx/vx_mixer.c
Normal file
1024
sound/drivers/vx/vx_mixer.c
Normal file
File diff suppressed because it is too large
Load diff
1261
sound/drivers/vx/vx_pcm.c
Normal file
1261
sound/drivers/vx/vx_pcm.c
Normal file
File diff suppressed because it is too large
Load diff
309
sound/drivers/vx/vx_uer.c
Normal file
309
sound/drivers/vx/vx_uer.c
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* Driver for Digigram VX soundcards
|
||||
*
|
||||
* IEC958 stuff
|
||||
*
|
||||
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it 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 program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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;
|
||||
|
||||
mutex_lock(&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;
|
||||
}
|
||||
mutex_unlock(&chip->lock);
|
||||
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 */
|
||||
mutex_lock(&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));
|
||||
}
|
||||
mutex_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
mutex_lock(&chip->lock);
|
||||
chip->ops->set_clock_source(chip, source);
|
||||
chip->clock_source = source;
|
||||
mutex_unlock(&chip->lock);
|
||||
/* 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);
|
||||
snd_printdd(KERN_DEBUG "set internal clock to 0x%x from freq %d\n", clock, freq);
|
||||
mutex_lock(&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);
|
||||
}
|
||||
mutex_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue