diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /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/Makefile | 18 | ||||
-rw-r--r-- | sound/drivers/opl4/opl4_lib.c | 281 | ||||
-rw-r--r-- | sound/drivers/opl4/opl4_local.h | 232 | ||||
-rw-r--r-- | sound/drivers/opl4/opl4_mixer.c | 95 | ||||
-rw-r--r-- | sound/drivers/opl4/opl4_proc.c | 166 | ||||
-rw-r--r-- | sound/drivers/opl4/opl4_seq.c | 223 | ||||
-rw-r--r-- | sound/drivers/opl4/opl4_synth.c | 630 | ||||
-rw-r--r-- | sound/drivers/opl4/yrw801.c | 961 |
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 |