aboutsummaryrefslogtreecommitdiff
path: root/sound/drivers/opl4
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/drivers/opl4
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'sound/drivers/opl4')
-rw-r--r--sound/drivers/opl4/Makefile18
-rw-r--r--sound/drivers/opl4/opl4_lib.c281
-rw-r--r--sound/drivers/opl4/opl4_local.h232
-rw-r--r--sound/drivers/opl4/opl4_mixer.c95
-rw-r--r--sound/drivers/opl4/opl4_proc.c166
-rw-r--r--sound/drivers/opl4/opl4_seq.c223
-rw-r--r--sound/drivers/opl4/opl4_synth.c630
-rw-r--r--sound/drivers/opl4/yrw801.c961
8 files changed, 2606 insertions, 0 deletions
diff --git a/sound/drivers/opl4/Makefile b/sound/drivers/opl4/Makefile
new file mode 100644
index 00000000000..141aacbaf31
--- /dev/null
+++ b/sound/drivers/opl4/Makefile
@@ -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
diff --git a/sound/drivers/opl4/opl4_lib.c b/sound/drivers/opl4/opl4_lib.c
new file mode 100644
index 00000000000..8261464dade
--- /dev/null
+++ b/sound/drivers/opl4/opl4_lib.c
@@ -0,0 +1,281 @@
+/*
+ * Functions for accessing OPL4 devices
+ * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "opl4_local.h"
+#include <sound/initval.h>
+#include <linux/ioport.h>
+#include <linux/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(opl4_t *opl4)
+{
+ int timeout = 10;
+ while ((inb(opl4->fm_port) & OPL4_STATUS_BUSY) && --timeout > 0)
+ ;
+}
+
+void snd_opl4_write(opl4_t *opl4, u8 reg, u8 value)
+{
+ snd_opl4_wait(opl4);
+ outb(reg, opl4->pcm_port);
+
+ snd_opl4_wait(opl4);
+ outb(value, opl4->pcm_port + 1);
+}
+
+u8 snd_opl4_read(opl4_t *opl4, u8 reg)
+{
+ snd_opl4_wait(opl4);
+ outb(reg, opl4->pcm_port);
+
+ snd_opl4_wait(opl4);
+ return inb(opl4->pcm_port + 1);
+}
+
+void snd_opl4_read_memory(opl4_t *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);
+}
+
+void snd_opl4_write_memory(opl4_t *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);
+}
+
+static void snd_opl4_enable_opl4(opl4_t *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(opl4_t *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(snd_seq_device_t *seq_dev)
+{
+ opl4_t *opl4 = seq_dev->private_data;
+ opl4->seq_dev = NULL;
+}
+
+static int snd_opl4_create_seq_dev(opl4_t *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(opl4_t *), &opl4->seq_dev) >= 0) {
+ strcpy(opl4->seq_dev->name, "OPL4 Wavetable");
+ *(opl4_t **)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(opl4_t *opl4)
+{
+#ifdef CONFIG_PROC_FS
+ snd_opl4_free_proc(opl4);
+#endif
+ if (opl4->res_fm_port) {
+ release_resource(opl4->res_fm_port);
+ kfree_nocheck(opl4->res_fm_port);
+ }
+ if (opl4->res_pcm_port) {
+ release_resource(opl4->res_pcm_port);
+ kfree_nocheck(opl4->res_pcm_port);
+ }
+ kfree(opl4);
+}
+
+static int snd_opl4_dev_free(snd_device_t *device)
+{
+ opl4_t *opl4 = device->device_data;
+ snd_opl4_free(opl4);
+ return 0;
+}
+
+int snd_opl4_create(snd_card_t *card,
+ unsigned long fm_port, unsigned long pcm_port,
+ int seq_device,
+ opl3_t **ropl3, opl4_t **ropl4)
+{
+ opl4_t *opl4;
+ opl3_t *opl3;
+ int err;
+ static snd_device_ops_t ops = {
+ .dev_free = snd_opl4_dev_free
+ };
+
+ if (ropl3)
+ *ropl3 = NULL;
+ if (ropl4)
+ *ropl4 = NULL;
+
+ opl4 = kcalloc(1, 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);
+ init_MUTEX(&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_write);
+EXPORT_SYMBOL(snd_opl4_read);
+EXPORT_SYMBOL(snd_opl4_write_memory);
+EXPORT_SYMBOL(snd_opl4_read_memory);
+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)
diff --git a/sound/drivers/opl4/opl4_local.h b/sound/drivers/opl4/opl4_local.h
new file mode 100644
index 00000000000..c455680843f
--- /dev/null
+++ b/sound/drivers/opl4/opl4_local.h
@@ -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"
+
+
+typedef 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;
+} opl4_sound_t;
+
+typedef struct opl4_region {
+ u8 key_min, key_max;
+ opl4_sound_t sound;
+} opl4_region_t;
+
+typedef struct opl4_region_ptr {
+ int count;
+ const opl4_region_t *regions;
+} opl4_region_ptr_t;
+
+typedef struct opl4_voice {
+ struct list_head list;
+ int number;
+ snd_midi_channel_t *chan;
+ int note;
+ int velocity;
+ const opl4_sound_t *sound;
+ u8 level_direct;
+ u8 reg_f_number;
+ u8 reg_misc;
+ u8 reg_lfo_vibrato;
+} opl4_voice_t;
+
+struct 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;
+ snd_card_t *card;
+
+#ifdef CONFIG_PROC_FS
+ snd_info_entry_t *proc_entry;
+ int memory_access;
+#endif
+ struct semaphore access_mutex;
+
+#if defined(CONFIG_SND_SEQUENCER) || defined(CONFIG_SND_SEQUENCER_MODULE)
+ int used;
+
+ int seq_dev_num;
+ int seq_client;
+ snd_seq_device_t *seq_dev;
+
+ snd_midi_channel_set_t *chset;
+ opl4_voice_t voices[OPL4_MAX_VOICES];
+ struct list_head off_voices;
+ struct list_head on_voices;
+#endif
+};
+
+/* opl4_lib.c */
+void snd_opl4_write(opl4_t *opl4, u8 reg, u8 value);
+u8 snd_opl4_read(opl4_t *opl4, u8 reg);
+void snd_opl4_read_memory(opl4_t *opl4, char *buf, int offset, int size);
+void snd_opl4_write_memory(opl4_t *opl4, const char *buf, int offset, int size);
+
+/* opl4_mixer.c */
+int snd_opl4_create_mixer(opl4_t *opl4);
+
+#ifdef CONFIG_PROC_FS
+/* opl4_proc.c */
+int snd_opl4_create_proc(opl4_t *opl4);
+void snd_opl4_free_proc(opl4_t *opl4);
+#endif
+
+/* opl4_seq.c */
+extern int volume_boost;
+
+/* opl4_synth.c */
+void snd_opl4_synth_reset(opl4_t *opl4);
+void snd_opl4_synth_shutdown(opl4_t *opl4);
+void snd_opl4_note_on(void *p, int note, int vel, snd_midi_channel_t *chan);
+void snd_opl4_note_off(void *p, int note, int vel, snd_midi_channel_t *chan);
+void snd_opl4_terminate_note(void *p, int note, snd_midi_channel_t *chan);
+void snd_opl4_control(void *p, int type, snd_midi_channel_t *chan);
+void snd_opl4_sysex(void *p, unsigned char *buf, int len, int parsed, snd_midi_channel_set_t *chset);
+
+/* yrw801.c */
+int snd_yrw801_detect(opl4_t *opl4);
+extern const opl4_region_ptr_t snd_yrw801_regions[];
+
+#endif /* __OPL4_LOCAL_H */
diff --git a/sound/drivers/opl4/opl4_mixer.c b/sound/drivers/opl4/opl4_mixer.c
new file mode 100644
index 00000000000..ec7a228fbe7
--- /dev/null
+++ b/sound/drivers/opl4/opl4_mixer.c
@@ -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(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *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(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ opl4_t *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(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ opl4_t *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 snd_kcontrol_new_t 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(opl4_t *opl4)
+{
+ snd_card_t *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;
+}
diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c
new file mode 100644
index 00000000000..6a1486258ac
--- /dev/null
+++ b/sound/drivers/opl4/opl4_proc.c
@@ -0,0 +1,166 @@
+/*
+ * 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(snd_info_entry_t *entry,
+ unsigned short mode, void **file_private_data)
+{
+ opl4_t *opl4 = entry->private_data;
+
+ down(&opl4->access_mutex);
+ if (opl4->memory_access) {
+ up(&opl4->access_mutex);
+ return -EBUSY;
+ }
+ opl4->memory_access++;
+ up(&opl4->access_mutex);
+ return 0;
+}
+
+static int snd_opl4_mem_proc_release(snd_info_entry_t *entry,
+ unsigned short mode, void *file_private_data)
+{
+ opl4_t *opl4 = entry->private_data;
+
+ down(&opl4->access_mutex);
+ opl4->memory_access--;
+ up(&opl4->access_mutex);
+ return 0;
+}
+
+static long snd_opl4_mem_proc_read(snd_info_entry_t *entry, void *file_private_data,
+ struct file *file, char __user *_buf,
+ unsigned long count, unsigned long pos)
+{
+ opl4_t *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(snd_info_entry_t *entry, void *file_private_data,
+ struct file *file, const char __user *_buf,
+ unsigned long count, unsigned long pos)
+{
+ opl4_t *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(snd_info_entry_t *entry, void *file_private_data,
+ struct file *file, long long offset, int orig)
+{
+ switch (orig) {
+ case 0: /* SEEK_SET */
+ file->f_pos = offset;
+ break;
+ case 1: /* SEEK_CUR */
+ file->f_pos += offset;
+ break;
+ case 2: /* 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(opl4_t *opl4)
+{
+ snd_info_entry_t *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(opl4_t *opl4)
+{
+ if (opl4->proc_entry)
+ snd_info_unregister(opl4->proc_entry);
+}
+
+#endif /* CONFIG_PROC_FS */
diff --git a/sound/drivers/opl4/opl4_seq.c b/sound/drivers/opl4/opl4_seq.c
new file mode 100644
index 00000000000..958dfe88479
--- /dev/null
+++ b/sound/drivers/opl4/opl4_seq.c
@@ -0,0 +1,223 @@
+/*
+ * 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(opl4_t *opl4)
+{
+ if (!try_module_get(opl4->card->module))
+ return -EFAULT;
+ return 0;
+}
+
+static void snd_opl4_seq_use_dec(opl4_t *opl4)
+{
+ module_put(opl4->card->module);
+}
+
+static int snd_opl4_seq_use(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ opl4_t *opl4 = private_data;
+ int err;
+
+ down(&opl4->access_mutex);
+
+ if (opl4->used) {
+ up(&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) {
+ up(&opl4->access_mutex);
+ return err;
+ }
+ }
+
+ up(&opl4->access_mutex);
+
+ snd_opl4_synth_reset(opl4);
+ return 0;
+}
+
+static int snd_opl4_seq_unuse(void *private_data, snd_seq_port_subscribe_t *info)
+{
+ opl4_t *opl4 = private_data;
+
+ snd_opl4_synth_shutdown(opl4);
+
+ down(&opl4->access_mutex);
+ opl4->used--;
+ up(&opl4->access_mutex);
+
+ if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)
+ snd_opl4_seq_use_dec(opl4);
+ return 0;
+}
+
+static snd_midi_op_t 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(snd_seq_event_t *ev, int direct,
+ void *private_data, int atomic, int hop)
+{
+ opl4_t *opl4 = private_data;
+
+ snd_midi_process_event(&opl4_ops, ev, opl4->chset);
+ return 0;
+}
+
+static void snd_opl4_seq_free_port(void *private_data)
+{
+ opl4_t *opl4 = private_data;
+
+ snd_midi_channel_free_set(opl4->chset);
+}
+
+static int snd_opl4_seq_new_device(snd_seq_device_t *dev)
+{
+ opl4_t *opl4;
+ int client;
+ snd_seq_client_callback_t callbacks;
+ snd_seq_client_info_t cinfo;
+ snd_seq_port_callback_t pcallbacks;
+
+ opl4 = *(opl4_t **)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 */
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.private_data = opl4;
+ callbacks.allow_output = callbacks.allow_input = 1;
+ client = snd_seq_create_kernel_client(opl4->card, opl4->seq_dev_num, &callbacks);
+ if (client < 0) {
+ snd_midi_channel_free_set(opl4->chset);
+ return client;
+ }
+ opl4->seq_client = client;
+ opl4->chset->client = client;
+
+ /* change name of client */
+ memset(&cinfo, 0, sizeof(cinfo));
+ cinfo.client = client;
+ cinfo.type = KERNEL_CLIENT;
+ strcpy(cinfo.name, "OPL4 Wavetable");
+ snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &cinfo);
+
+ /* 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,
+ 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(snd_seq_device_t *dev)
+{
+ opl4_t *opl4;
+
+ opl4 = *(opl4_t **)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 snd_seq_dev_ops_t ops = {
+ snd_opl4_seq_new_device,
+ snd_opl4_seq_delete_device
+ };
+
+ return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_OPL4, &ops,
+ sizeof(opl4_t*));
+}
+
+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)
diff --git a/sound/drivers/opl4/opl4_synth.c b/sound/drivers/opl4/opl4_synth.c
new file mode 100644
index 00000000000..b146a1c995d
--- /dev/null
+++ b/sound/drivers/opl4/opl4_synth.c
@@ -0,0 +1,630 @@
+/*
+ * 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 IMPL