Creation of Cybook 2416 (actually Gen4) repository

This commit is contained in:
mlt
2009-12-18 17:10:00 +00:00
committed by godzil
commit 76f20f4d40
13791 changed files with 6812321 additions and 0 deletions

123
sound/drivers/Kconfig Normal file
View File

@@ -0,0 +1,123 @@
# ALSA generic drivers
menu "Generic devices"
depends on SND!=n
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 SND_HWDEP
select SND_PCM
config SND_AC97_CODEC
tristate
select SND_PCM
select AC97_BUS
config SND_DUMMY
tristate "Dummy (/dev/null) soundcard"
depends on SND
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_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"
depends on SND
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 SND && PARPORT
select SND_RAWMIDI
help
The ESI Miditerminal 4140 is a 4 In 4 Out MIDI Interface with
additional SMPTE Timecode capabilities for the parallel port.
Say 'Y' to include support for this device.
To compile this driver as a module, chose 'M' here: the module
will be called snd-mts64.
config SND_SERIAL_U16550
tristate "UART16550 serial MIDI driver"
depends on SND
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"
depends on SND
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 SND && 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.
endmenu

21
sound/drivers/Makefile Normal file
View File

@@ -0,0 +1,21 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
#
snd-dummy-objs := dummy.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
# Toplevel Module Dependency
obj-$(CONFIG_SND_DUMMY) += snd-dummy.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) += opl3/ opl4/ mpu401/ vx/

710
sound/drivers/dummy.c Normal file
View File

@@ -0,0 +1,710 @@
/*
* Dummy soundcard
* Copyright (c) by 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include <sound/initval.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_DESCRIPTION("Dummy soundcard (/dev/null)");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{ALSA,Dummy soundcard}}");
#define MAX_PCM_DEVICES 4
#define MAX_PCM_SUBSTREAMS 16
#define MAX_MIDI_DEVICES 2
#if 0 /* emu10k1 emulation */
#define MAX_BUFFER_SIZE (128 * 1024)
static int emu10k1_playback_constraints(struct snd_pcm_runtime *runtime)
{
int err;
if ((err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS)) < 0)
return err;
if ((err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX)) < 0)
return err;
return 0;
}
#define add_playback_constraints emu10k1_playback_constraints
#endif
#if 0 /* RME9652 emulation */
#define MAX_BUFFER_SIZE (26 * 64 * 1024)
#define USE_FORMATS SNDRV_PCM_FMTBIT_S32_LE
#define USE_CHANNELS_MIN 26
#define USE_CHANNELS_MAX 26
#define USE_PERIODS_MIN 2
#define USE_PERIODS_MAX 2
#endif
#if 0 /* ICE1712 emulation */
#define MAX_BUFFER_SIZE (256 * 1024)
#define USE_FORMATS SNDRV_PCM_FMTBIT_S32_LE
#define USE_CHANNELS_MIN 10
#define USE_CHANNELS_MAX 10
#define USE_PERIODS_MIN 1
#define USE_PERIODS_MAX 1024
#endif
#if 0 /* UDA1341 emulation */
#define MAX_BUFFER_SIZE (16380)
#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE
#define USE_CHANNELS_MIN 2
#define USE_CHANNELS_MAX 2
#define USE_PERIODS_MIN 2
#define USE_PERIODS_MAX 255
#endif
#if 0 /* simple AC97 bridge (intel8x0) with 48kHz AC97 only codec */
#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE
#define USE_CHANNELS_MIN 2
#define USE_CHANNELS_MAX 2
#define USE_RATE SNDRV_PCM_RATE_48000
#define USE_RATE_MIN 48000
#define USE_RATE_MAX 48000
#endif
#if 0 /* CA0106 */
#define USE_FORMATS SNDRV_PCM_FMTBIT_S16_LE
#define USE_CHANNELS_MIN 2
#define USE_CHANNELS_MAX 2
#define USE_RATE (SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_192000)
#define USE_RATE_MIN 48000
#define USE_RATE_MAX 192000
#define MAX_BUFFER_SIZE ((65536-64)*8)
#define MAX_PERIOD_SIZE (65536-64)
#define USE_PERIODS_MIN 2
#define USE_PERIODS_MAX 8
#endif
/* defaults */
#ifndef MAX_BUFFER_SIZE
#define MAX_BUFFER_SIZE (64*1024)
#endif
#ifndef MAX_PERIOD_SIZE
#define MAX_PERIOD_SIZE MAX_BUFFER_SIZE
#endif
#ifndef USE_FORMATS
#define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE)
#endif
#ifndef USE_RATE
#define USE_RATE SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000
#define USE_RATE_MIN 5500
#define USE_RATE_MAX 48000
#endif
#ifndef USE_CHANNELS_MIN
#define USE_CHANNELS_MIN 1
#endif
#ifndef USE_CHANNELS_MAX
#define USE_CHANNELS_MAX 2
#endif
#ifndef USE_PERIODS_MIN
#define USE_PERIODS_MIN 1
#endif
#ifndef USE_PERIODS_MAX
#define USE_PERIODS_MAX 1024
#endif
#ifndef add_playback_constraints
#define add_playback_constraints(x) 0
#endif
#ifndef add_capture_constraints
#define add_capture_constraints(x) 0
#endif
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
//static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for dummy soundcard.");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for dummy soundcard.");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable this dummy soundcard.");
module_param_array(pcm_devs, int, NULL, 0444);
MODULE_PARM_DESC(pcm_devs, "PCM devices # (0-4) for dummy driver.");
module_param_array(pcm_substreams, int, NULL, 0444);
MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-16) for dummy driver.");
//module_param_array(midi_devs, int, NULL, 0444);
//MODULE_PARM_DESC(midi_devs, "MIDI devices # (0-2) for dummy driver.");
static struct platform_device *devices[SNDRV_CARDS];
#define MIXER_ADDR_MASTER 0
#define MIXER_ADDR_LINE 1
#define MIXER_ADDR_MIC 2
#define MIXER_ADDR_SYNTH 3
#define MIXER_ADDR_CD 4
#define MIXER_ADDR_LAST 4
struct snd_dummy {
struct snd_card *card;
struct snd_pcm *pcm;
spinlock_t mixer_lock;
int mixer_volume[MIXER_ADDR_LAST+1][2];
int capture_source[MIXER_ADDR_LAST+1][2];
};
struct snd_dummy_pcm {
struct snd_dummy *dummy;
spinlock_t lock;
struct timer_list timer;
unsigned int pcm_size;
unsigned int pcm_count;
unsigned int pcm_bps; /* bytes per second */
unsigned int pcm_jiffie; /* bytes per one jiffie */
unsigned int pcm_irq_pos; /* IRQ position */
unsigned int pcm_buf_pos; /* position in buffer */
struct snd_pcm_substream *substream;
};
static inline void snd_card_dummy_pcm_timer_start(struct snd_dummy_pcm *dpcm)
{
dpcm->timer.expires = 1 + jiffies;
add_timer(&dpcm->timer);
}
static inline void snd_card_dummy_pcm_timer_stop(struct snd_dummy_pcm *dpcm)
{
del_timer(&dpcm->timer);
}
static int snd_card_dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dummy_pcm *dpcm = runtime->private_data;
int err = 0;
spin_lock(&dpcm->lock);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
snd_card_dummy_pcm_timer_start(dpcm);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
snd_card_dummy_pcm_timer_stop(dpcm);
break;
default:
err = -EINVAL;
break;
}
spin_unlock(&dpcm->lock);
return 0;
}
static int snd_card_dummy_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dummy_pcm *dpcm = runtime->private_data;
unsigned int bps;
bps = runtime->rate * runtime->channels;
bps *= snd_pcm_format_width(runtime->format);
bps /= 8;
if (bps <= 0)
return -EINVAL;
dpcm->pcm_bps = bps;
dpcm->pcm_jiffie = bps / HZ;
dpcm->pcm_size = snd_pcm_lib_buffer_bytes(substream);
dpcm->pcm_count = snd_pcm_lib_period_bytes(substream);
dpcm->pcm_irq_pos = 0;
dpcm->pcm_buf_pos = 0;
return 0;
}
static void snd_card_dummy_pcm_timer_function(unsigned long data)
{
struct snd_dummy_pcm *dpcm = (struct snd_dummy_pcm *)data;
unsigned long flags;
spin_lock_irqsave(&dpcm->lock, flags);
dpcm->timer.expires = 1 + jiffies;
add_timer(&dpcm->timer);
dpcm->pcm_irq_pos += dpcm->pcm_jiffie;
dpcm->pcm_buf_pos += dpcm->pcm_jiffie;
dpcm->pcm_buf_pos %= dpcm->pcm_size;
if (dpcm->pcm_irq_pos >= dpcm->pcm_count) {
dpcm->pcm_irq_pos %= dpcm->pcm_count;
spin_unlock_irqrestore(&dpcm->lock, flags);
snd_pcm_period_elapsed(dpcm->substream);
} else
spin_unlock_irqrestore(&dpcm->lock, flags);
}
static snd_pcm_uframes_t snd_card_dummy_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dummy_pcm *dpcm = runtime->private_data;
return bytes_to_frames(runtime, dpcm->pcm_buf_pos);
}
static struct snd_pcm_hardware snd_card_dummy_playback =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
.formats = USE_FORMATS,
.rates = USE_RATE,
.rate_min = USE_RATE_MIN,
.rate_max = USE_RATE_MAX,
.channels_min = USE_CHANNELS_MIN,
.channels_max = USE_CHANNELS_MAX,
.buffer_bytes_max = MAX_BUFFER_SIZE,
.period_bytes_min = 64,
.period_bytes_max = MAX_PERIOD_SIZE,
.periods_min = USE_PERIODS_MIN,
.periods_max = USE_PERIODS_MAX,
.fifo_size = 0,
};
static struct snd_pcm_hardware snd_card_dummy_capture =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
.formats = USE_FORMATS,
.rates = USE_RATE,
.rate_min = USE_RATE_MIN,
.rate_max = USE_RATE_MAX,
.channels_min = USE_CHANNELS_MIN,
.channels_max = USE_CHANNELS_MAX,
.buffer_bytes_max = MAX_BUFFER_SIZE,
.period_bytes_min = 64,
.period_bytes_max = MAX_PERIOD_SIZE,
.periods_min = USE_PERIODS_MIN,
.periods_max = USE_PERIODS_MAX,
.fifo_size = 0,
};
static void snd_card_dummy_runtime_free(struct snd_pcm_runtime *runtime)
{
kfree(runtime->private_data);
}
static int snd_card_dummy_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
static int snd_card_dummy_hw_free(struct snd_pcm_substream *substream)
{
return snd_pcm_lib_free_pages(substream);
}
static struct snd_dummy_pcm *new_pcm_stream(struct snd_pcm_substream *substream)
{
struct snd_dummy_pcm *dpcm;
dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
if (! dpcm)
return dpcm;
init_timer(&dpcm->timer);
dpcm->timer.data = (unsigned long) dpcm;
dpcm->timer.function = snd_card_dummy_pcm_timer_function;
spin_lock_init(&dpcm->lock);
dpcm->substream = substream;
return dpcm;
}
static int snd_card_dummy_playback_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dummy_pcm *dpcm;
int err;
if ((dpcm = new_pcm_stream(substream)) == NULL)
return -ENOMEM;
runtime->private_data = dpcm;
runtime->private_free = snd_card_dummy_runtime_free;
runtime->hw = snd_card_dummy_playback;
if (substream->pcm->device & 1) {
runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
}
if (substream->pcm->device & 2)
runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP|SNDRV_PCM_INFO_MMAP_VALID);
if ((err = add_playback_constraints(runtime)) < 0) {
kfree(dpcm);
return err;
}
return 0;
}
static int snd_card_dummy_capture_open(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dummy_pcm *dpcm;
int err;
if ((dpcm = new_pcm_stream(substream)) == NULL)
return -ENOMEM;
runtime->private_data = dpcm;
runtime->private_free = snd_card_dummy_runtime_free;
runtime->hw = snd_card_dummy_capture;
if (substream->pcm->device == 1) {
runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
}
if (substream->pcm->device & 2)
runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP|SNDRV_PCM_INFO_MMAP_VALID);
if ((err = add_capture_constraints(runtime)) < 0) {
kfree(dpcm);
return err;
}
return 0;
}
static int snd_card_dummy_playback_close(struct snd_pcm_substream *substream)
{
return 0;
}
static int snd_card_dummy_capture_close(struct snd_pcm_substream *substream)
{
return 0;
}
static struct snd_pcm_ops snd_card_dummy_playback_ops = {
.open = snd_card_dummy_playback_open,
.close = snd_card_dummy_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_card_dummy_hw_params,
.hw_free = snd_card_dummy_hw_free,
.prepare = snd_card_dummy_pcm_prepare,
.trigger = snd_card_dummy_pcm_trigger,
.pointer = snd_card_dummy_pcm_pointer,
};
static struct snd_pcm_ops snd_card_dummy_capture_ops = {
.open = snd_card_dummy_capture_open,
.close = snd_card_dummy_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_card_dummy_hw_params,
.hw_free = snd_card_dummy_hw_free,
.prepare = snd_card_dummy_pcm_prepare,
.trigger = snd_card_dummy_pcm_trigger,
.pointer = snd_card_dummy_pcm_pointer,
};
static int __devinit snd_card_dummy_pcm(struct snd_dummy *dummy, int device,
int substreams)
{
struct snd_pcm *pcm;
int err;
if ((err = snd_pcm_new(dummy->card, "Dummy PCM", device,
substreams, substreams, &pcm)) < 0)
return err;
dummy->pcm = pcm;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_card_dummy_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_card_dummy_capture_ops);
pcm->private_data = dummy;
pcm->info_flags = 0;
strcpy(pcm->name, "Dummy PCM");
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
0, 64*1024);
return 0;
}
#define DUMMY_VOLUME(xname, xindex, addr) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.name = xname, .index = xindex, \
.info = snd_dummy_volume_info, \
.get = snd_dummy_volume_get, .put = snd_dummy_volume_put, \
.private_value = addr, \
.tlv = { .p = db_scale_dummy } }
static int snd_dummy_volume_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = -50;
uinfo->value.integer.max = 100;
return 0;
}
static int snd_dummy_volume_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
int addr = kcontrol->private_value;
spin_lock_irq(&dummy->mixer_lock);
ucontrol->value.integer.value[0] = dummy->mixer_volume[addr][0];
ucontrol->value.integer.value[1] = dummy->mixer_volume[addr][1];
spin_unlock_irq(&dummy->mixer_lock);
return 0;
}
static int snd_dummy_volume_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
int change, addr = kcontrol->private_value;
int left, right;
left = ucontrol->value.integer.value[0];
if (left < -50)
left = -50;
if (left > 100)
left = 100;
right = ucontrol->value.integer.value[1];
if (right < -50)
right = -50;
if (right > 100)
right = 100;
spin_lock_irq(&dummy->mixer_lock);
change = dummy->mixer_volume[addr][0] != left ||
dummy->mixer_volume[addr][1] != right;
dummy->mixer_volume[addr][0] = left;
dummy->mixer_volume[addr][1] = right;
spin_unlock_irq(&dummy->mixer_lock);
return change;
}
static const DECLARE_TLV_DB_SCALE(db_scale_dummy, -4500, 30, 0);
#define DUMMY_CAPSRC(xname, xindex, addr) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \
.info = snd_dummy_capsrc_info, \
.get = snd_dummy_capsrc_get, .put = snd_dummy_capsrc_put, \
.private_value = addr }
static int snd_dummy_capsrc_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
return 0;
}
static int snd_dummy_capsrc_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
int addr = kcontrol->private_value;
spin_lock_irq(&dummy->mixer_lock);
ucontrol->value.integer.value[0] = dummy->capture_source[addr][0];
ucontrol->value.integer.value[1] = dummy->capture_source[addr][1];
spin_unlock_irq(&dummy->mixer_lock);
return 0;
}
static int snd_dummy_capsrc_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol);
int change, addr = kcontrol->private_value;
int left, right;
left = ucontrol->value.integer.value[0] & 1;
right = ucontrol->value.integer.value[1] & 1;
spin_lock_irq(&dummy->mixer_lock);
change = dummy->capture_source[addr][0] != left &&
dummy->capture_source[addr][1] != right;
dummy->capture_source[addr][0] = left;
dummy->capture_source[addr][1] = right;
spin_unlock_irq(&dummy->mixer_lock);
return change;
}
static struct snd_kcontrol_new snd_dummy_controls[] = {
DUMMY_VOLUME("Master Volume", 0, MIXER_ADDR_MASTER),
DUMMY_CAPSRC("Master Capture Switch", 0, MIXER_ADDR_MASTER),
DUMMY_VOLUME("Synth Volume", 0, MIXER_ADDR_SYNTH),
DUMMY_CAPSRC("Synth Capture Switch", 0, MIXER_ADDR_SYNTH),
DUMMY_VOLUME("Line Volume", 0, MIXER_ADDR_LINE),
DUMMY_CAPSRC("Line Capture Switch", 0, MIXER_ADDR_LINE),
DUMMY_VOLUME("Mic Volume", 0, MIXER_ADDR_MIC),
DUMMY_CAPSRC("Mic Capture Switch", 0, MIXER_ADDR_MIC),
DUMMY_VOLUME("CD Volume", 0, MIXER_ADDR_CD),
DUMMY_CAPSRC("CD Capture Switch", 0, MIXER_ADDR_CD)
};
static int __devinit snd_card_dummy_new_mixer(struct snd_dummy *dummy)
{
struct snd_card *card = dummy->card;
unsigned int idx;
int err;
snd_assert(dummy != NULL, return -EINVAL);
spin_lock_init(&dummy->mixer_lock);
strcpy(card->mixername, "Dummy Mixer");
for (idx = 0; idx < ARRAY_SIZE(snd_dummy_controls); idx++) {
if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_dummy_controls[idx], dummy))) < 0)
return err;
}
return 0;
}
static int __devinit snd_dummy_probe(struct platform_device *devptr)
{
struct snd_card *card;
struct snd_dummy *dummy;
int idx, err;
int dev = devptr->id;
card = snd_card_new(index[dev], id[dev], THIS_MODULE,
sizeof(struct snd_dummy));
if (card == NULL)
return -ENOMEM;
dummy = card->private_data;
dummy->card = card;
for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) {
if (pcm_substreams[dev] < 1)
pcm_substreams[dev] = 1;
if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS)
pcm_substreams[dev] = MAX_PCM_SUBSTREAMS;
if ((err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev])) < 0)
goto __nodev;
}
if ((err = snd_card_dummy_new_mixer(dummy)) < 0)
goto __nodev;
strcpy(card->driver, "Dummy");
strcpy(card->shortname, "Dummy");
sprintf(card->longname, "Dummy %i", dev + 1);
snd_card_set_dev(card, &devptr->dev);
if ((err = snd_card_register(card)) == 0) {
platform_set_drvdata(devptr, card);
return 0;
}
__nodev:
snd_card_free(card);
return err;
}
static int __devexit snd_dummy_remove(struct platform_device *devptr)
{
snd_card_free(platform_get_drvdata(devptr));
platform_set_drvdata(devptr, NULL);
return 0;
}
#ifdef CONFIG_PM
static int snd_dummy_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_card *card = platform_get_drvdata(pdev);
struct snd_dummy *dummy = card->private_data;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
snd_pcm_suspend_all(dummy->pcm);
return 0;
}
static int snd_dummy_resume(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return 0;
}
#endif
#define SND_DUMMY_DRIVER "snd_dummy"
static struct platform_driver snd_dummy_driver = {
.probe = snd_dummy_probe,
.remove = __devexit_p(snd_dummy_remove),
#ifdef CONFIG_PM
.suspend = snd_dummy_suspend,
.resume = snd_dummy_resume,
#endif
.driver = {
.name = SND_DUMMY_DRIVER
},
};
static void __init_or_module snd_dummy_unregister_all(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(devices); ++i)
platform_device_unregister(devices[i]);
platform_driver_unregister(&snd_dummy_driver);
}
static int __init alsa_card_dummy_init(void)
{
int i, cards, err;
if ((err = platform_driver_register(&snd_dummy_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_DUMMY_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 "Dummy soundcard not found or device busy\n");
#endif
snd_dummy_unregister_all();
return -ENODEV;
}
return 0;
}
static void __exit alsa_card_dummy_exit(void)
{
snd_dummy_unregister_all();
}
module_init(alsa_card_dummy_init)
module_exit(alsa_card_dummy_exit)

View File

@@ -0,0 +1,12 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.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

View File

@@ -0,0 +1,284 @@
/*
* Driver for generic MPU-401 boards (UART mode only)
* Copyright (c) by Jaroslav Kysela <perex@suse.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 <sound/driver.h>
#include <linux/init.h>
#include <linux/pnp.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/mpu401.h>
#include <sound/initval.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.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 int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */
#ifdef CONFIG_PNP
static int 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 */
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.");
static struct platform_device *platform_devices[SNDRV_CARDS];
static int pnp_registered;
static unsigned int snd_mpu401_devices;
static int snd_mpu401_create(int dev, struct snd_card **rcard)
{
struct snd_card *card;
int err;
*rcard = NULL;
card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
if (card == NULL)
return -ENOMEM;
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");
}
if ((err = snd_mpu401_uart_new(card, 0,
MPU401_HW_MPU401,
port[dev], 0,
irq[dev], irq[dev] >= 0 ? IRQF_DISABLED : 0, NULL)) < 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 __devinit 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(dev, &card);
if (err < 0)
return err;
snd_card_set_dev(card, &devptr->dev);
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
platform_set_drvdata(devptr, card);
return 0;
}
static int __devexit snd_mpu401_remove(struct platform_device *devptr)
{
snd_card_free(platform_get_drvdata(devptr));
platform_set_drvdata(devptr, NULL);
return 0;
}
#define SND_MPU401_DRIVER "snd_mpu401"
static struct platform_driver snd_mpu401_driver = {
.probe = snd_mpu401_probe,
.remove = __devexit_p(snd_mpu401_remove),
.driver = {
.name = SND_MPU401_DRIVER
},
};
#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 __devinit 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 __devinit 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(dev, &card);
if (err < 0)
return err;
if ((err = snd_card_register(card)) < 0) {
snd_card_free(card);
return err;
}
snd_card_set_dev(card, &pnp_dev->dev);
pnp_set_drvdata(pnp_dev, card);
snd_mpu401_devices++;
++dev;
return 0;
}
return -ENODEV;
}
static void __devexit 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 = __devexit_p(snd_mpu401_pnp_remove),
};
#else
static struct pnp_driver snd_mpu401_pnp_driver;
#endif
static void __init_or_module 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)

View File

@@ -0,0 +1,624 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@suse.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. Also, if irq < 0, then
* 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 <sound/driver.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <sound/core.h>
#include <sound/mpu401.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.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)) & 0x80))
#define snd_mpu401_output_ready(mpu) (!(mpu->read(mpu, MPU401C(mpu)) & 0x40))
#define MPU401_RESET 0xff
#define MPU401_ENTER_UART 0x3f
#define MPU401_ACK 0xfe
/* 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)
{
if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) &&
test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) {
spin_lock(&mpu->output_lock);
snd_mpu401_uart_output_write(mpu);
spin_unlock(&mpu->output_lock);
}
}
static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu)
{
if (mpu->info_flags & MPU401_INFO_INPUT) {
spin_lock(&mpu->input_lock);
if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))
snd_mpu401_uart_input_read(mpu);
else
snd_mpu401_uart_clear_rx(mpu);
spin_unlock(&mpu->input_lock);
}
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.
*/
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.
*/
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) {
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;
}
/*
* 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_uart_cmd(mpu, MPU401_RESET, 1))
goto error_out;
if (snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 1))
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_uart_cmd(mpu, MPU401_RESET, 1))
goto error_out;
if (snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 1))
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->irq < 0)
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->irq < 0)
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, timeout;
do {
if (snd_rawmidi_transmit_peek(mpu->substream_output,
&byte, 1) == 1) {
for (timeout = 100; timeout > 0; timeout--) {
if (snd_mpu401_output_ready(mpu))
break;
}
if (timeout == 0)
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_flags && 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 irq number, -1 if no interrupt for mpu
* @irq_flags: the irq request flags (SA_XXX), 0 if irq was already reserved.
* @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).
*
* Returns 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, int irq_flags,
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;
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 && irq_flags) {
if (request_irq(irq, snd_mpu401_uart_interrupt, irq_flags,
"MPU401 UART", (void *) mpu)) {
snd_printk(KERN_ERR "mpu401_uart: "
"unable to grab IRQ %d\n", irq);
snd_device_free(card, rmidi);
return -EBUSY;
}
}
mpu->info_flags = info_flags;
mpu->irq = irq;
mpu->irq_flags = irq_flags;
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)

791
sound/drivers/mtpav.c Normal file
View File

@@ -0,0 +1,791 @@
/*
* 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 <sound/driver.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/moduleparam.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include <linux/delay.h>
#include <asm/io.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("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("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 __devinit snd_mtpav_get_ISA(struct mtpav * mcard)
{
if ((mcard->res_port = request_region(port, 3, "MotuMTPAV MIDI")) == NULL) {
snd_printk("MTVAP port 0x%lx is busy\n", port);
return -EBUSY;
}
mcard->port = port;
if (request_irq(irq, snd_mtpav_irqh, IRQF_DISABLED, "MOTU MTPAV", mcard)) {
snd_printk("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 __devinit 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 __devinit 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 __devinit snd_mtpav_probe(struct platform_device *dev)
{
struct snd_card *card;
int err;
struct mtpav *mtp_card;
card = snd_card_new(index, id, THIS_MODULE, sizeof(*mtp_card));
if (! card)
return -ENOMEM;
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->inmidiport = 0xffffffff;
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_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);
err = snd_mtpav_get_RAWMIDI(mtp_card);
if (err < 0)
goto __error;
snd_mtpav_portscan(mtp_card);
snd_card_set_dev(card, &dev->dev);
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 __devexit snd_mtpav_remove(struct platform_device *devptr)
{
snd_card_free(platform_get_drvdata(devptr));
platform_set_drvdata(devptr, NULL);
return 0;
}
#define SND_MTPAV_DRIVER "snd_mtpav"
static struct platform_driver snd_mtpav_driver = {
.probe = snd_mtpav_probe,
.remove = __devexit_p(snd_mtpav_remove),
.driver = {
.name = SND_MTPAV_DRIVER
},
};
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)

1091
sound/drivers/mts64.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,22 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz>
#
snd-opl3-lib-objs := opl3_lib.o opl3_synth.o
snd-opl3-synth-objs := opl3_seq.o opl3_midi.o opl3_drums.o
ifeq ($(CONFIG_SND_SEQUENCER_OSS),y)
snd-opl3-synth-objs += opl3_oss.o
endif
#
# this function returns:
# "m" - CONFIG_SND_SEQUENCER is m
# <empty string> - CONFIG_SND_SEQUENCER is undefined
# otherwise parameter #1 value
#
sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
obj-$(CONFIG_SND_OPL3_LIB) += snd-opl3-lib.o
obj-$(CONFIG_SND_OPL4_LIB) += snd-opl3-lib.o
obj-$(call sequencer,$(CONFIG_SND_OPL3_LIB)) += snd-opl3-synth.o

View 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(&reg_val, vel, chan);
opl3_reg = OPL3_LEFT | (OPL3_REG_KSL_LEVEL + op_offset);
opl3->command(opl3, opl3_reg, reg_val);
/* Set OPL3 FEEDBACK_CONNECTION register */
/* Set output voice connection */
reg_val = data->feedback_connection | OPL3_STEREO_BITS;
if (chan->gm_pan < 43)
reg_val &= ~OPL3_VOICE_TO_RIGHT;
if (chan->gm_pan > 85)
reg_val &= ~OPL3_VOICE_TO_LEFT;
opl3_reg = OPL3_LEFT | (OPL3_REG_FEEDBACK_CONNECTION + voice_offset);
opl3->command(opl3, opl3_reg, reg_val);
}
/*
* Loads drum voices at init time
*/
void snd_opl3_load_drums(struct snd_opl3 *opl3)
{
snd_opl3_drum_voice_set(opl3, &bass_op0);
snd_opl3_drum_voice_set(opl3, &bass_op1);
snd_opl3_drum_note_set(opl3, &bass_note);
snd_opl3_drum_voice_set(opl3, &hihat);
snd_opl3_drum_voice_set(opl3, &snare);
snd_opl3_drum_note_set(opl3, &snare_note);
snd_opl3_drum_voice_set(opl3, &tomtom);
snd_opl3_drum_note_set(opl3, &tomtom_note);
snd_opl3_drum_voice_set(opl3, &cymbal);
}
/*
* Switch drum voice on or off
*/
void snd_opl3_drum_switch(struct snd_opl3 *opl3, int note, int vel, int on_off,
struct snd_midi_channel *chan)
{
unsigned char drum_mask;
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);
}

View File

@@ -0,0 +1,555 @@
/*
* Copyright (c) by Jaroslav Kysela <perex@suse.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/init.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <sound/minors.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.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.
*/
snd_assert(opl3->r_port != 0, 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("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)
{
snd_assert(opl3 != NULL, return -ENXIO);
if (opl3->private_free)
opl3->private_free(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);
mutex_init(&opl3->access_mutex);
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;
#ifdef CONFIG_SND_OSSEMUL
if (device == 0) {
hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM;
sprintf(hw->oss_dev, "dmfm%i", card->number);
}
#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.release = snd_opl3_release;
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)

View File

@@ -0,0 +1,874 @@
/*
* 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 int use_internal_drums;
/*
* 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("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("%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->sys_timer_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(opl3, vp->note, 0, vp->chan);
else
again++;
}
}
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;
struct snd_seq_instr wanted;
struct snd_seq_kinstr *kinstr;
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;
int key = note;
unsigned char fnum, blocknum;
int i;
struct fm_instrument *fm;
unsigned long flags;
opl3 = p;
#ifdef DEBUG_MIDI
snd_printk("Note on, ch %i, inst %i, note %i, vel %i\n",
chan->number, chan->midi_program, note, vel);
#endif
wanted.cluster = 0;
wanted.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
/* 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 */
wanted.bank = 128;
wanted.prg = note;
} else {
wanted.bank = chan->gm_bank_select;
wanted.prg = chan->midi_program;
}
} else {
/* Prepare for OSS mode */
if (chan->number >= MAX_OPL3_VOICES)
return;
/* OSS instruments are located in bank 127 */
wanted.bank = 127;
wanted.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:
kinstr = snd_seq_instr_find(opl3->ilist, &wanted, 1, 0);
if (kinstr == NULL) {
spin_unlock_irqrestore(&opl3->voice_lock, flags);
return;
}
fm = KINSTR_DATA(kinstr);
switch (fm->type) {
case FM_PATCH_OPL2:
instr_4op = 0;
break;
case FM_PATCH_OPL3:
if (opl3->hardware >= OPL3_HW_OPL3) {
instr_4op = 1;
break;
}
default:
snd_seq_instr_free_use(opl3->ilist, kinstr);
spin_unlock_irqrestore(&opl3->voice_lock, flags);
return;
}
#ifdef DEBUG_MIDI
snd_printk(" --> OPL%i instrument: %s\n",
instr_4op ? 3 : 2, kinstr->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 < 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(" --> 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(" --> 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(" --> 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;
snd_seq_instr_free_use(opl3->ilist, kinstr);
/* 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) {
wanted.bank = 128;
/* percussions start at 35 */
wanted.prg = extra_prg - 128 + 35 - 1;
} else {
wanted.bank = 0;
wanted.prg = extra_prg - 1;
}
#ifdef DEBUG_MIDI
snd_printk(" *** 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;
snd_assert(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(" --> 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.
*/
void snd_opl3_note_off(void *p, int note, int vel, struct snd_midi_channel *chan)
{
struct snd_opl3 *opl3;
int voice;
struct snd_opl3_voice *vp;
unsigned long flags;
opl3 = p;
#ifdef DEBUG_MIDI
snd_printk("Note off, ch %i, inst %i, note %i\n",
chan->number, chan->midi_program, note);
#endif
spin_lock_irqsave(&opl3->voice_lock, flags);
if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) {
if (chan->drum_channel && use_internal_drums) {
snd_opl3_drum_switch(opl3, note, vel, 0, chan);
spin_unlock_irqrestore(&opl3->voice_lock, flags);
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);
}
}
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("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("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;
snd_assert(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 controler 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("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("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("SYSEX\n");
#endif
}

View File

@@ -0,0 +1,359 @@
/*
* 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 "opl3_voice.h"
#include <linux/slab.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;
snd_assert(arg != NULL, 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;
snd_assert(arg != NULL, return -ENXIO);
opl3 = arg->private_data;
snd_opl3_synth_cleanup(opl3);
snd_opl3_synth_use_dec(opl3);
return 0;
}
/* load patch */
/* 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
/* 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;
int err = -EINVAL;
snd_assert(arg != NULL, return -ENXIO);
opl3 = arg->private_data;
if ((format == FM_PATCH) || (format == OPL3_PATCH)) {
struct sbi_instrument sbi;
size_t size;
struct snd_seq_instr_header *put;
struct snd_seq_instr_data *data;
struct fm_xinstrument *xinstr;
struct snd_seq_event ev;
int i;
mm_segment_t fs;
if (count < (int)sizeof(sbi)) {
snd_printk("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("FM Error: Invalid instrument number %d\n", sbi.channel);
return -EINVAL;
}
size = sizeof(*put) + sizeof(struct fm_xinstrument);
put = kzalloc(size, GFP_KERNEL);
if (put == NULL)
return -ENOMEM;
/* build header */
data = &put->data;
data->type = SNDRV_SEQ_INSTR_ATYPE_DATA;
strcpy(data->data.format, SNDRV_SEQ_INSTR_ID_OPL2_3);
/* build data section */
xinstr = (struct fm_xinstrument *)(data + 1);
xinstr->stype = FM_STRU_INSTR;
for (i = 0; i < 2; i++) {
xinstr->op[i].am_vib = sbi.operators[AM_VIB + i];
xinstr->op[i].ksl_level = sbi.operators[KSL_LEVEL + i];
xinstr->op[i].attack_decay = sbi.operators[ATTACK_DECAY + i];
xinstr->op[i].sustain_release = sbi.operators[SUSTAIN_RELEASE + i];
xinstr->op[i].wave_select = sbi.operators[WAVE_SELECT + i];
}
xinstr->feedback_connection[0] = sbi.operators[CONNECTION];
if (format == OPL3_PATCH) {
xinstr->type = FM_PATCH_OPL3;
for (i = 0; i < 2; i++) {
xinstr->op[i+2].am_vib = sbi.operators[OFFSET_4OP + AM_VIB + i];
xinstr->op[i+2].ksl_level = sbi.operators[OFFSET_4OP + KSL_LEVEL + i];
xinstr->op[i+2].attack_decay = sbi.operators[OFFSET_4OP + ATTACK_DECAY + i];
xinstr->op[i+2].sustain_release = sbi.operators[OFFSET_4OP + SUSTAIN_RELEASE + i];
xinstr->op[i+2].wave_select = sbi.operators[OFFSET_4OP + WAVE_SELECT + i];
}
xinstr->feedback_connection[1] = sbi.operators[OFFSET_4OP + CONNECTION];
} else {
xinstr->type = FM_PATCH_OPL2;
}
put->id.instr.std = SNDRV_SEQ_INSTR_TYPE2_OPL2_3;
put->id.instr.bank = 127;
put->id.instr.prg = sbi.channel;
put->cmd = SNDRV_SEQ_INSTR_PUT_CMD_CREATE;
memset (&ev, 0, sizeof(ev));
ev.source.client = SNDRV_SEQ_CLIENT_OSS;
ev.dest = arg->addr;
ev.flags = SNDRV_SEQ_EVENT_LENGTH_VARUSR;
ev.queue = SNDRV_SEQ_QUEUE_DIRECT;
fs = snd_enter_user();
__again:
ev.type = SNDRV_SEQ_EVENT_INSTR_PUT;
ev.data.ext.len = size;
ev.data.ext.ptr = put;
err = snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
opl3->seq_client, 0, 0);
if (err == -EBUSY) {
struct snd_seq_instr_header remove;
memset (&remove, 0, sizeof(remove));
remove.cmd = SNDRV_SEQ_INSTR_FREE_CMD_SINGLE;
remove.id.instr = put->id.instr;
/* remove instrument */
ev.type = SNDRV_SEQ_EVENT_INSTR_FREE;
ev.data.ext.len = sizeof(remove);
ev.data.ext.ptr = &remove;
snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, &ev,
opl3->seq_client, 0, 0);
goto __again;
}
snd_leave_user(fs);
kfree(put);
}
return err;
}
/* ioctl */
static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd,
unsigned long ioarg)
{
struct snd_opl3 *opl3;
snd_assert(arg != NULL, return -ENXIO);
opl3 = arg->private_data;
switch (cmd) {
case SNDCTL_FM_LOAD_INSTR:
snd_printk("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;
snd_assert(arg != NULL, return -ENXIO);
opl3 = arg->private_data;
return 0;
}

View File

@@ -0,0 +1,313 @@
/*
* 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 <sound/initval.h>
MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth");
int 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;
mutex_lock(&opl3->access_mutex);
if (opl3->used) {
mutex_unlock(&opl3->access_mutex);
return -EBUSY;
}
opl3->used++;
mutex_unlock(&opl3->access_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;
/* 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);
mutex_lock(&opl3->access_mutex);
opl3->used--;
mutex_unlock(&opl3->access_mutex);
}
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;
if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN &&
ev->type <= SNDRV_SEQ_EVENT_INSTR_CHANGE) {
if (direct) {
snd_seq_instr_event(&opl3->fm_ops, opl3->ilist, ev,
opl3->seq_client, atomic, hop);
}
} else {
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;
}
/* initialize instrument list */
opl3->ilist = snd_seq_instr_list_new();
if (opl3->ilist == NULL) {
snd_seq_delete_kernel_client(client);
opl3->seq_client = -1;
return -ENOMEM;
}
opl3->ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT;
snd_seq_fm_init(&opl3->fm_ops, NULL);
/* 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;
}
if (opl3->ilist)
snd_seq_instr_list_free(&opl3->ilist);
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)

View File

@@ -0,0 +1,451 @@
/*
* 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 <sound/opl3.h>
#include <sound/asound_fm.h>
/*
* 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)
{
struct snd_opl3 *opl3 = hw->private_data;
mutex_lock(&opl3->access_mutex);
if (opl3->used) {
mutex_unlock(&opl3->access_mutex);
return -EAGAIN;
}
opl3->used++;
mutex_unlock(&opl3->access_mutex);
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;
snd_assert(opl3 != NULL, 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(&note, argp, sizeof(struct snd_dm_fm_note)))
return -EFAULT;
return snd_opl3_play_note(opl3, &note);
}
case SNDRV_DM_FM_IOCTL_SET_VOICE:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_VOICE:
#endif
{
struct snd_dm_fm_voice voice;
if (copy_from_user(&voice, argp, sizeof(struct snd_dm_fm_voice)))
return -EFAULT;
return snd_opl3_set_voice(opl3, &voice);
}
case SNDRV_DM_FM_IOCTL_SET_PARAMS:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_PARAMS:
#endif
{
struct snd_dm_fm_params params;
if (copy_from_user(&params, argp, sizeof(struct snd_dm_fm_params)))
return -EFAULT;
return snd_opl3_set_params(opl3, &params);
}
case SNDRV_DM_FM_IOCTL_SET_MODE:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_MODE:
#endif
return snd_opl3_set_mode(opl3, (int) arg);
case SNDRV_DM_FM_IOCTL_SET_CONNECTION:
#ifdef CONFIG_SND_OSSEMUL
case SNDRV_DM_FM_OSS_IOCTL_SET_OPL:
#endif
return snd_opl3_set_connection(opl3, (int) arg);
#ifdef CONFIG_SND_DEBUG
default:
snd_printk("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);
mutex_lock(&opl3->access_mutex);
opl3->used--;
mutex_unlock(&opl3->access_mutex);
return 0;
}
/* ------------------------------ */
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;
}

View 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

View File

@@ -0,0 +1,18 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.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
#
# this function returns:
# "m" - CONFIG_SND_SEQUENCER is m
# <empty string> - CONFIG_SND_SEQUENCER is undefined
# otherwise parameter #1 value
#
sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1)))
obj-$(CONFIG_SND_OPL4_LIB) += snd-opl4-lib.o
obj-$(call sequencer,$(CONFIG_SND_OPL4_LIB)) += snd-opl4-synth.o

View File

@@ -0,0 +1,279 @@
/*
* 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/init.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)

View 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 */

View 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;
}

View File

@@ -0,0 +1,165 @@
/*
* 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 <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 long snd_opl4_mem_proc_read(struct snd_info_entry *entry, void *file_private_data,
struct file *file, char __user *_buf,
unsigned long count, unsigned long pos)
{
struct snd_opl4 *opl4 = entry->private_data;
long size;
char* buf;
size = count;
if (pos + size > entry->size)
size = entry->size - pos;
if (size > 0) {
buf = vmalloc(size);
if (!buf)
return -ENOMEM;
snd_opl4_read_memory(opl4, buf, pos, size);
if (copy_to_user(_buf, buf, size)) {
vfree(buf);
return -EFAULT;
}
vfree(buf);
return size;
}
return 0;
}
static long snd_opl4_mem_proc_write(struct snd_info_entry *entry, void *file_private_data,
struct file *file, const char __user *_buf,
unsigned long count, unsigned long pos)
{
struct snd_opl4 *opl4 = entry->private_data;
long size;
char *buf;
size = count;
if (pos + size > entry->size)
size = entry->size - pos;
if (size > 0) {
buf = vmalloc(size);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, _buf, size)) {
vfree(buf);
return -EFAULT;
}
snd_opl4_write_memory(opl4, buf, pos, size);
vfree(buf);
return size;
}
return 0;
}
static long long snd_opl4_mem_proc_llseek(struct snd_info_entry *entry, void *file_private_data,
struct file *file, long long offset, int orig)
{
switch (orig) {
case SEEK_SET:
file->f_pos = offset;
break;
case SEEK_CUR:
file->f_pos += offset;
break;
case SEEK_END: /* offset is negative */
file->f_pos = entry->size + offset;
break;
default:
return -EINVAL;
}
if (file->f_pos > entry->size)
file->f_pos = entry->size;
return file->f_pos;
}
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,
.llseek = snd_opl4_mem_proc_llseek,
};
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 */

View File

@@ -0,0 +1,214 @@
/*
* 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 <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)

View File

@@ -0,0 +1,634 @@
/*
* 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_assert(!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] = &regions->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_del(&voice[i]->list);
list_add_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_del(&voice->list);
list_add_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_del(&voice->list);
list_add_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
View File

@@ -0,0 +1,961 @@
/*
* Information about the Yamaha YRW801 wavetable ROM chip
*
* Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "opl4_local.h"
int snd_yrw801_detect(struct snd_opl4 *opl4)
{
char buf[15];
snd_opl4_read_memory(opl4, buf, 0x001200, 15);
if (memcmp(buf, "CopyrightYAMAHA", 15))
return -ENODEV;
snd_opl4_read_memory(opl4, buf, 0x1ffffe, 2);
if (buf[0] != 0x01)
return -ENODEV;
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)
};

876
sound/drivers/portman2x4.c Normal file
View File

@@ -0,0 +1,876 @@
/*
* 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 <sound/driver.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/parport.h>
#include <linux/spinlock.h>
#include <linux/delay.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 int 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 __devinit 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 __devinit 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(int irq, 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 __devinit 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;
}
static void __devinit 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_register(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 __devinit 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;
card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
if (card == NULL) {
snd_printd("Cannot create card\n");
return -ENOMEM;
}
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 __devexit 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 = __devexit_p(snd_portman_remove),
.driver = {
.name = PLATFORM_DRIVER
}
};
/*********************************************************************
* module init stuff
*********************************************************************/
static void __init_or_module 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);

File diff suppressed because it is too large Load Diff

196
sound/drivers/virmidi.c Normal file
View File

@@ -0,0 +1,196 @@
/*
* 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 <sound/driver.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/moduleparam.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 int 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 __devinit snd_virmidi_probe(struct platform_device *devptr)
{
struct snd_card *card;
struct snd_card_virmidi *vmidi;
int idx, err;
int dev = devptr->id;
card = snd_card_new(index[dev], id[dev], THIS_MODULE,
sizeof(struct snd_card_virmidi));
if (card == NULL)
return -ENOMEM;
vmidi = (struct snd_card_virmidi *)card->private_data;
vmidi->card = card;
if (midi_devs[dev] > MAX_MIDI_DEVICES) {
snd_printk("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);
snd_card_set_dev(card, &devptr->dev);
if ((err = snd_card_register(card)) == 0) {
platform_set_drvdata(devptr, card);
return 0;
}
__nodev:
snd_card_free(card);
return err;
}
static int __devexit snd_virmidi_remove(struct platform_device *devptr)
{
snd_card_free(platform_get_drvdata(devptr));
platform_set_drvdata(devptr, NULL);
return 0;
}
#define SND_VIRMIDI_DRIVER "snd_virmidi"
static struct platform_driver snd_virmidi_driver = {
.probe = snd_virmidi_probe,
.remove = __devexit_p(snd_virmidi_remove),
.driver = {
.name = SND_VIRMIDI_DRIVER
},
};
static void __init_or_module 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)

View File

@@ -0,0 +1,8 @@
#
# Makefile for ALSA
# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.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
View 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/driver.h>
#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)
{
snd_assert(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
View 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 */

820
sound/drivers/vx/vx_core.c Normal file
View File

@@ -0,0 +1,820 @@
/*
* 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 <sound/driver.h>
#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 <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;
#ifdef CONFIG_SND_DEBUG
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"
};
#endif
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 spinlock 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 spinlock 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;
snd_assert(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 spinlock 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 spinlock
* @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)
{
unsigned long flags;
int err;
spin_lock_irqsave(&chip->lock, flags);
err = vx_send_msg_nolock(chip, rmh);
spin_unlock_irqrestore(&chip->lock, flags);
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 spinlock 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 spinlock
* @cmd: the command to send
*
* see vx_send_rih_nolock().
*/
int vx_send_rih(struct vx_core *chip, int cmd)
{
unsigned long flags;
int err;
spin_lock_irqsave(&chip->lock, flags);
err = vx_send_rih_nolock(chip, cmd);
spin_unlock_irqrestore(&chip->lock, flags);
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 */
snd_assert(boot->size > 0, return -EINVAL);
snd_assert(boot->size % 3 == 0, return -EINVAL);
#if 0
{
/* more strict check */
unsigned int c = ((u32)boot->data[0] << 16) | ((u32)boot->data[1] << 8) | boot->data[2];
snd_assert(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 {
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);
spin_lock(&chip->lock);
err = vx_send_msg_nolock(chip, &chip->irq_rmh);
if (err < 0)
*ret = 0;
else
*ret = chip->irq_rmh.Stat[0];
spin_unlock(&chip->lock);
return err;
}
/*
* vx_interrupt - soft irq handler
*/
static void vx_interrupt(unsigned long private_data)
{
struct vx_core *chip = (struct vx_core *) private_data;
unsigned int events;
if (chip->chip_status & VX_STAT_IS_STALE)
return;
if (vx_test_irq_src(chip, &events) < 0)
return;
#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;
}
/* 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);
}
/**
* 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))
tasklet_hi_schedule(&chip->tq);
return IRQ_HANDLED;
}
EXPORT_SYMBOL(snd_vx_irq_handler);
/*
*/
static void vx_reset_board(struct vx_core *chip, int cold_reset)
{
snd_assert(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;
unsigned char *image, *cptr;
snd_assert(dsp->size % 3 == 0, 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("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, pm_message_t state)
{
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;
snd_assert(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;
}
spin_lock_init(&chip->lock);
spin_lock_init(&chip->irq_lock);
chip->irq = -1;
chip->hw = hw;
chip->type = hw->type;
chip->ops = ops;
tasklet_init(&chip->tq, vx_interrupt, (unsigned long)chip);
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)

255
sound/drivers/vx/vx_hwdep.c Normal file
View File

@@ -0,0 +1,255 @@
/*
* 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 <sound/driver.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/vmalloc.h>
#include <sound/core.h>
#include <sound/hwdep.h>
#include <sound/vx_core.h>
#ifdef SND_VX_FW_LOADER
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
}
#else /* old style firmware loading */
static int vx_hwdep_open(struct snd_hwdep *hw, struct file *file)
{
return 0;
}
static int vx_hwdep_release(struct snd_hwdep *hw, struct file *file)
{
return 0;
}
static int vx_hwdep_dsp_status(struct snd_hwdep *hw,
struct snd_hwdep_dsp_status *info)
{
static char *type_ids[VX_TYPE_NUMS] = {
[VX_TYPE_BOARD] = "vxboard",
[VX_TYPE_V2] = "vx222",
[VX_TYPE_MIC] = "vx222",
[VX_TYPE_VXPOCKET] = "vxpocket",
[VX_TYPE_VXP440] = "vxp440",
};
struct vx_core *vx = hw->private_data;
snd_assert(type_ids[vx->type], return -EINVAL);
strcpy(info->id, type_ids[vx->type]);
if (vx_is_pcmcia(vx))
info->num_dsps = 4;
else
info->num_dsps = 3;
if (vx->chip_status & VX_STAT_CHIP_INIT)
info->chip_ready = 1;
info->version = VX_DRIVER_VERSION;
return 0;
}
static void free_fw(const struct firmware *fw)
{
if (fw) {
vfree(fw->data);
kfree(fw);
}
}
static int vx_hwdep_dsp_load(struct snd_hwdep *hw,
struct snd_hwdep_dsp_image *dsp)
{
struct vx_core *vx = hw->private_data;
int index, err;
struct firmware *fw;
snd_assert(vx->ops->load_dsp, return -ENXIO);
fw = kmalloc(sizeof(*fw), GFP_KERNEL);
if (! fw) {
snd_printk(KERN_ERR "cannot allocate firmware\n");
return -ENOMEM;
}
fw->size = dsp->length;
fw->data = vmalloc(fw->size);
if (! fw->data) {
snd_printk(KERN_ERR "cannot allocate firmware image (length=%d)\n",
(int)fw->size);
kfree(fw);
return -ENOMEM;
}
if (copy_from_user(fw->data, dsp->image, dsp->length)) {
free_fw(fw);
return -EFAULT;
}
index = dsp->index;
if (! vx_is_pcmcia(vx))
index++;
err = vx->ops->load_dsp(vx, index, fw);
if (err < 0) {
free_fw(fw);
return err;
}
#ifdef CONFIG_PM
vx->firmware[index] = fw;
#else
free_fw(fw);
#endif
if (index == 1)
vx->chip_status |= VX_STAT_XILINX_LOADED;
if (index < 3)
return 0;
/* ok, we reached to the last one */
/* create the devices if not built yet */
if (! (vx->chip_status & VX_STAT_DEVICE_INIT)) {
if ((err = snd_vx_pcm_new(vx)) < 0)
return err;
if ((err = snd_vx_mixer_new(vx)) < 0)
return err;
if (vx->ops->add_controls)
if ((err = vx->ops->add_controls(vx)) < 0)
return err;
if ((err = snd_card_register(vx->card)) < 0)
return err;
vx->chip_status |= VX_STAT_DEVICE_INIT;
}
vx->chip_status |= VX_STAT_CHIP_INIT;
return 0;
}
/* exported */
int snd_vx_setup_firmware(struct vx_core *chip)
{
int err;
struct snd_hwdep *hw;
if ((err = snd_hwdep_new(chip->card, SND_VX_HWDEP_ID, 0, &hw)) < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_VX;
hw->private_data = chip;
hw->ops.open = vx_hwdep_open;
hw->ops.release = vx_hwdep_release;
hw->ops.dsp_status = vx_hwdep_dsp_status;
hw->ops.dsp_load = vx_hwdep_dsp_load;
hw->exclusive = 1;
sprintf(hw->name, "VX Loader (%s)", chip->card->driver);
chip->hwdep = hw;
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++)
free_fw(chip->firmware[i]);
#endif
}
#endif /* SND_VX_FW_LOADER */
EXPORT_SYMBOL(snd_vx_setup_firmware);
EXPORT_SYMBOL(snd_vx_free_firmware);

1013
sound/drivers/vx/vx_mixer.c Normal file

File diff suppressed because it is too large Load Diff

1328
sound/drivers/vx/vx_pcm.c Normal file

File diff suppressed because it is too large Load Diff

311
sound/drivers/vx/vx_uer.c Normal file
View File

@@ -0,0 +1,311 @@
/*
* 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 <sound/driver.h>
#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)
{
unsigned long flags;
int val;
spin_lock_irqsave(&chip->lock, flags);
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;
}
spin_unlock_irqrestore(&chip->lock, flags);
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)
{
unsigned long flags;
val = !!val; /* 0 or 1 */
spin_lock_irqsave(&chip->lock, flags);
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));
}
spin_unlock_irqrestore(&chip->lock, flags);
}
/*
* 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, 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;
snd_assert(freq > 0, return 0);
hexfreq = (28224000 * 10) / freq;
hexfreq = (hexfreq + 5) / 10;
/* max freq = 55125 Hz */
snd_assert(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)
{
unsigned long flags;
/* we mute DAC to prevent clicks */
vx_toggle_dac_mute(chip, 1);
spin_lock_irqsave(&chip->lock, flags);
chip->ops->set_clock_source(chip, source);
chip->clock_source = source;
spin_unlock_irqrestore(&chip->lock, flags);
/* 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;
unsigned long flags;
/* 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);
spin_lock_irqsave(&chip->lock, flags);
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);
}
spin_unlock_irqrestore(&chip->lock, flags);
}
/*
* 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;
}