280 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (c) by Uros Bizjak <uros@kss-loka.si>4 *5 * Midi Sequencer interface routines for OPL2/OPL3/OPL4 FM6 *7 * OPL2/3 FM instrument loader:8 * alsa-tools/seq/sbiload/9 */10 11#include "opl3_voice.h"12#include <linux/init.h>13#include <linux/moduleparam.h>14#include <linux/module.h>15#include <sound/initval.h>16 17MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>");18MODULE_LICENSE("GPL");19MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth");20 21bool use_internal_drums = 0;22module_param(use_internal_drums, bool, 0444);23MODULE_PARM_DESC(use_internal_drums, "Enable internal OPL2/3 drums.");24 25int snd_opl3_synth_use_inc(struct snd_opl3 * opl3)26{27 if (!try_module_get(opl3->card->module))28 return -EFAULT;29 return 0;30 31}32 33void snd_opl3_synth_use_dec(struct snd_opl3 * opl3)34{35 module_put(opl3->card->module);36}37 38int snd_opl3_synth_setup(struct snd_opl3 * opl3)39{40 int idx;41 struct snd_hwdep *hwdep = opl3->hwdep;42 43 mutex_lock(&hwdep->open_mutex);44 if (hwdep->used) {45 mutex_unlock(&hwdep->open_mutex);46 return -EBUSY;47 }48 hwdep->used++;49 mutex_unlock(&hwdep->open_mutex);50 51 snd_opl3_reset(opl3);52 53 for (idx = 0; idx < MAX_OPL3_VOICES; idx++) {54 opl3->voices[idx].state = SNDRV_OPL3_ST_OFF;55 opl3->voices[idx].time = 0;56 opl3->voices[idx].keyon_reg = 0x00;57 }58 opl3->use_time = 0;59 opl3->connection_reg = 0x00;60 if (opl3->hardware >= OPL3_HW_OPL3) {61 /* Clear 4-op connections */62 opl3->command(opl3, OPL3_RIGHT | OPL3_REG_CONNECTION_SELECT,63 opl3->connection_reg);64 opl3->max_voices = MAX_OPL3_VOICES;65 }66 return 0;67}68 69void snd_opl3_synth_cleanup(struct snd_opl3 * opl3)70{71 unsigned long flags;72 struct snd_hwdep *hwdep;73 74 /* Stop system timer */75 spin_lock_irqsave(&opl3->sys_timer_lock, flags);76 if (opl3->sys_timer_status) {77 del_timer(&opl3->tlist);78 opl3->sys_timer_status = 0;79 }80 spin_unlock_irqrestore(&opl3->sys_timer_lock, flags);81 82 snd_opl3_reset(opl3);83 hwdep = opl3->hwdep;84 mutex_lock(&hwdep->open_mutex);85 hwdep->used--;86 mutex_unlock(&hwdep->open_mutex);87 wake_up(&hwdep->open_wait);88}89 90static int snd_opl3_synth_use(void *private_data, struct snd_seq_port_subscribe * info)91{92 struct snd_opl3 *opl3 = private_data;93 int err;94 95 err = snd_opl3_synth_setup(opl3);96 if (err < 0)97 return err;98 99 if (use_internal_drums) {100 /* Percussion mode */101 opl3->voices[6].state = opl3->voices[7].state = 102 opl3->voices[8].state = SNDRV_OPL3_ST_NOT_AVAIL;103 snd_opl3_load_drums(opl3);104 opl3->drum_reg = OPL3_PERCUSSION_ENABLE;105 opl3->command(opl3, OPL3_LEFT | OPL3_REG_PERCUSSION, opl3->drum_reg);106 } else {107 opl3->drum_reg = 0x00;108 }109 110 if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) {111 err = snd_opl3_synth_use_inc(opl3);112 if (err < 0)113 return err;114 }115 opl3->synth_mode = SNDRV_OPL3_MODE_SEQ;116 return 0;117}118 119static int snd_opl3_synth_unuse(void *private_data, struct snd_seq_port_subscribe * info)120{121 struct snd_opl3 *opl3 = private_data;122 123 snd_opl3_synth_cleanup(opl3);124 125 if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM)126 snd_opl3_synth_use_dec(opl3);127 return 0;128}129 130/*131 * MIDI emulation operators132 */133const struct snd_midi_op opl3_ops = {134 .note_on = snd_opl3_note_on,135 .note_off = snd_opl3_note_off,136 .key_press = snd_opl3_key_press,137 .note_terminate = snd_opl3_terminate_note,138 .control = snd_opl3_control,139 .nrpn = snd_opl3_nrpn,140 .sysex = snd_opl3_sysex,141};142 143static int snd_opl3_synth_event_input(struct snd_seq_event * ev, int direct,144 void *private_data, int atomic, int hop)145{146 struct snd_opl3 *opl3 = private_data;147 148 snd_midi_process_event(&opl3_ops, ev, opl3->chset);149 return 0;150}151 152/* ------------------------------ */153 154static void snd_opl3_synth_free_port(void *private_data)155{156 struct snd_opl3 *opl3 = private_data;157 158 snd_midi_channel_free_set(opl3->chset);159}160 161static int snd_opl3_synth_create_port(struct snd_opl3 * opl3)162{163 struct snd_seq_port_callback callbacks;164 char name[32];165 int voices, opl_ver;166 167 voices = (opl3->hardware < OPL3_HW_OPL3) ?168 MAX_OPL2_VOICES : MAX_OPL3_VOICES;169 opl3->chset = snd_midi_channel_alloc_set(16);170 if (opl3->chset == NULL)171 return -ENOMEM;172 opl3->chset->private_data = opl3;173 174 memset(&callbacks, 0, sizeof(callbacks));175 callbacks.owner = THIS_MODULE;176 callbacks.use = snd_opl3_synth_use;177 callbacks.unuse = snd_opl3_synth_unuse;178 callbacks.event_input = snd_opl3_synth_event_input;179 callbacks.private_free = snd_opl3_synth_free_port;180 callbacks.private_data = opl3;181 182 opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;183 sprintf(name, "OPL%i FM Port", opl_ver);184 185 opl3->chset->client = opl3->seq_client;186 opl3->chset->port = snd_seq_event_port_attach(opl3->seq_client, &callbacks,187 SNDRV_SEQ_PORT_CAP_WRITE |188 SNDRV_SEQ_PORT_CAP_SUBS_WRITE,189 SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |190 SNDRV_SEQ_PORT_TYPE_MIDI_GM |191 SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE |192 SNDRV_SEQ_PORT_TYPE_HARDWARE |193 SNDRV_SEQ_PORT_TYPE_SYNTHESIZER,194 16, voices,195 name);196 if (opl3->chset->port < 0) {197 int port;198 port = opl3->chset->port;199 snd_midi_channel_free_set(opl3->chset);200 return port;201 }202 return 0;203}204 205/* ------------------------------ */206 207static int snd_opl3_seq_probe(struct device *_dev)208{209 struct snd_seq_device *dev = to_seq_dev(_dev);210 struct snd_opl3 *opl3;211 int client, err;212 char name[32];213 int opl_ver;214 215 opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);216 if (opl3 == NULL)217 return -EINVAL;218 219 spin_lock_init(&opl3->voice_lock);220 221 opl3->seq_client = -1;222 223 /* allocate new client */224 opl_ver = (opl3->hardware & OPL3_HW_MASK) >> 8;225 sprintf(name, "OPL%i FM synth", opl_ver);226 client = opl3->seq_client =227 snd_seq_create_kernel_client(opl3->card, opl3->seq_dev_num,228 name);229 if (client < 0)230 return client;231 232 err = snd_opl3_synth_create_port(opl3);233 if (err < 0) {234 snd_seq_delete_kernel_client(client);235 opl3->seq_client = -1;236 return err;237 }238 239 /* setup system timer */240 timer_setup(&opl3->tlist, snd_opl3_timer_func, 0);241 spin_lock_init(&opl3->sys_timer_lock);242 opl3->sys_timer_status = 0;243 244#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)245 snd_opl3_init_seq_oss(opl3, name);246#endif247 return 0;248}249 250static int snd_opl3_seq_remove(struct device *_dev)251{252 struct snd_seq_device *dev = to_seq_dev(_dev);253 struct snd_opl3 *opl3;254 255 opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev);256 if (opl3 == NULL)257 return -EINVAL;258 259#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS)260 snd_opl3_free_seq_oss(opl3);261#endif262 if (opl3->seq_client >= 0) {263 snd_seq_delete_kernel_client(opl3->seq_client);264 opl3->seq_client = -1;265 }266 return 0;267}268 269static struct snd_seq_driver opl3_seq_driver = {270 .driver = {271 .name = KBUILD_MODNAME,272 .probe = snd_opl3_seq_probe,273 .remove = snd_opl3_seq_remove,274 },275 .id = SNDRV_SEQ_DEV_ID_OPL3,276 .argsize = sizeof(struct snd_opl3 *),277};278 279module_snd_seq_driver(opl3_seq_driver);280