Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / sound / synth / emux / emux_oss.c
diff --git a/kernel/sound/synth/emux/emux_oss.c b/kernel/sound/synth/emux/emux_oss.c
new file mode 100644 (file)
index 0000000..82e350e
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ *  Interface for OSS sequencer emulation
+ *
+ *  Copyright (C) 1999 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
+ *
+ * Changes
+ * 19990227   Steve Ratcliffe   Made separate file and merged in latest
+ *                             midi emulation.
+ */
+
+
+#ifdef CONFIG_SND_SEQUENCER_OSS
+
+#include <linux/export.h>
+#include <linux/uaccess.h>
+#include <sound/core.h>
+#include "emux_voice.h"
+#include <sound/asoundef.h>
+
+static int snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure);
+static int snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg);
+static int snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd,
+                                 unsigned long ioarg);
+static int snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
+                                      const char __user *buf, int offs, int count);
+static int snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg);
+static int snd_emux_event_oss_input(struct snd_seq_event *ev, int direct,
+                                   void *private, int atomic, int hop);
+static void reset_port_mode(struct snd_emux_port *port, int midi_mode);
+static void emuspec_control(struct snd_emux *emu, struct snd_emux_port *port,
+                           int cmd, unsigned char *event, int atomic, int hop);
+static void gusspec_control(struct snd_emux *emu, struct snd_emux_port *port,
+                           int cmd, unsigned char *event, int atomic, int hop);
+static void fake_event(struct snd_emux *emu, struct snd_emux_port *port,
+                      int ch, int param, int val, int atomic, int hop);
+
+/* operators */
+static struct snd_seq_oss_callback oss_callback = {
+       .owner = THIS_MODULE,
+       .open = snd_emux_open_seq_oss,
+       .close = snd_emux_close_seq_oss,
+       .ioctl = snd_emux_ioctl_seq_oss,
+       .load_patch = snd_emux_load_patch_seq_oss,
+       .reset = snd_emux_reset_seq_oss,
+};
+
+
+/*
+ * register OSS synth
+ */
+
+void
+snd_emux_init_seq_oss(struct snd_emux *emu)
+{
+       struct snd_seq_oss_reg *arg;
+       struct snd_seq_device *dev;
+
+       if (snd_seq_device_new(emu->card, 0, SNDRV_SEQ_DEV_ID_OSS,
+                              sizeof(struct snd_seq_oss_reg), &dev) < 0)
+               return;
+
+       emu->oss_synth = dev;
+       strcpy(dev->name, emu->name);
+       arg = SNDRV_SEQ_DEVICE_ARGPTR(dev);
+       arg->type = SYNTH_TYPE_SAMPLE;
+       arg->subtype = SAMPLE_TYPE_AWE32;
+       arg->nvoices = emu->max_voices;
+       arg->oper = oss_callback;
+       arg->private_data = emu;
+
+       /* register to OSS synth table */
+       snd_device_register(emu->card, dev);
+}
+
+
+/*
+ * unregister
+ */
+void
+snd_emux_detach_seq_oss(struct snd_emux *emu)
+{
+       if (emu->oss_synth) {
+               snd_device_free(emu->card, emu->oss_synth);
+               emu->oss_synth = NULL;
+       }
+}
+
+
+/* use port number as a unique soundfont client number */
+#define SF_CLIENT_NO(p)        ((p) + 0x1000)
+
+/*
+ * open port for OSS sequencer
+ */
+static int
+snd_emux_open_seq_oss(struct snd_seq_oss_arg *arg, void *closure)
+{
+       struct snd_emux *emu;
+       struct snd_emux_port *p;
+       struct snd_seq_port_callback callback;
+       char tmpname[64];
+
+       emu = closure;
+       if (snd_BUG_ON(!arg || !emu))
+               return -ENXIO;
+
+       if (!snd_emux_inc_count(emu))
+               return -EFAULT;
+
+       memset(&callback, 0, sizeof(callback));
+       callback.owner = THIS_MODULE;
+       callback.event_input = snd_emux_event_oss_input;
+
+       sprintf(tmpname, "%s OSS Port", emu->name);
+       p = snd_emux_create_port(emu, tmpname, 32,
+                                1, &callback);
+       if (p == NULL) {
+               snd_printk(KERN_ERR "can't create port\n");
+               snd_emux_dec_count(emu);
+               return -ENOMEM;
+       }
+
+       /* fill the argument data */
+       arg->private_data = p;
+       arg->addr.client = p->chset.client;
+       arg->addr.port = p->chset.port;
+       p->oss_arg = arg;
+
+       reset_port_mode(p, arg->seq_mode);
+
+       snd_emux_reset_port(p);
+       return 0;
+}
+
+
+#define DEFAULT_DRUM_FLAGS     ((1<<9) | (1<<25))
+
+/*
+ * reset port mode
+ */
+static void
+reset_port_mode(struct snd_emux_port *port, int midi_mode)
+{
+       if (midi_mode) {
+               port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_MIDI;
+               port->drum_flags = DEFAULT_DRUM_FLAGS;
+               port->volume_atten = 0;
+               port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_KEYPRESS;
+       } else {
+               port->port_mode = SNDRV_EMUX_PORT_MODE_OSS_SYNTH;
+               port->drum_flags = 0;
+               port->volume_atten = 32;
+               port->oss_arg->event_passing = SNDRV_SEQ_OSS_PROCESS_EVENTS;
+       }
+}
+
+
+/*
+ * close port
+ */
+static int
+snd_emux_close_seq_oss(struct snd_seq_oss_arg *arg)
+{
+       struct snd_emux *emu;
+       struct snd_emux_port *p;
+
+       if (snd_BUG_ON(!arg))
+               return -ENXIO;
+       p = arg->private_data;
+       if (snd_BUG_ON(!p))
+               return -ENXIO;
+
+       emu = p->emu;
+       if (snd_BUG_ON(!emu))
+               return -ENXIO;
+
+       snd_emux_sounds_off_all(p);
+       snd_soundfont_close_check(emu->sflist, SF_CLIENT_NO(p->chset.port));
+       snd_seq_event_port_detach(p->chset.client, p->chset.port);
+       snd_emux_dec_count(emu);
+
+       return 0;
+}
+
+
+/*
+ * load patch
+ */
+static int
+snd_emux_load_patch_seq_oss(struct snd_seq_oss_arg *arg, int format,
+                           const char __user *buf, int offs, int count)
+{
+       struct snd_emux *emu;
+       struct snd_emux_port *p;
+       int rc;
+
+       if (snd_BUG_ON(!arg))
+               return -ENXIO;
+       p = arg->private_data;
+       if (snd_BUG_ON(!p))
+               return -ENXIO;
+
+       emu = p->emu;
+       if (snd_BUG_ON(!emu))
+               return -ENXIO;
+
+       if (format == GUS_PATCH)
+               rc = snd_soundfont_load_guspatch(emu->sflist, buf, count,
+                                                SF_CLIENT_NO(p->chset.port));
+       else if (format == SNDRV_OSS_SOUNDFONT_PATCH) {
+               struct soundfont_patch_info patch;
+               if (count < (int)sizeof(patch))
+                       rc = -EINVAL;
+               if (copy_from_user(&patch, buf, sizeof(patch)))
+                       rc = -EFAULT;
+               if (patch.type >= SNDRV_SFNT_LOAD_INFO &&
+                   patch.type <= SNDRV_SFNT_PROBE_DATA)
+                       rc = snd_soundfont_load(emu->sflist, buf, count, SF_CLIENT_NO(p->chset.port));
+               else {
+                       if (emu->ops.load_fx)
+                               rc = emu->ops.load_fx(emu, patch.type, patch.optarg, buf, count);
+                       else
+                               rc = -EINVAL;
+               }
+       } else
+               rc = 0;
+       return rc;
+}
+
+
+/*
+ * ioctl
+ */
+static int
+snd_emux_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, unsigned long ioarg)
+{
+       struct snd_emux_port *p;
+       struct snd_emux *emu;
+
+       if (snd_BUG_ON(!arg))
+               return -ENXIO;
+       p = arg->private_data;
+       if (snd_BUG_ON(!p))
+               return -ENXIO;
+
+       emu = p->emu;
+       if (snd_BUG_ON(!emu))
+               return -ENXIO;
+
+       switch (cmd) {
+       case SNDCTL_SEQ_RESETSAMPLES:
+               snd_soundfont_remove_samples(emu->sflist);
+               return 0;
+                       
+       case SNDCTL_SYNTH_MEMAVL:
+               if (emu->memhdr)
+                       return snd_util_mem_avail(emu->memhdr);
+               return 0;
+       }
+
+       return 0;
+}
+
+
+/*
+ * reset device
+ */
+static int
+snd_emux_reset_seq_oss(struct snd_seq_oss_arg *arg)
+{
+       struct snd_emux_port *p;
+
+       if (snd_BUG_ON(!arg))
+               return -ENXIO;
+       p = arg->private_data;
+       if (snd_BUG_ON(!p))
+               return -ENXIO;
+       snd_emux_reset_port(p);
+       return 0;
+}
+
+
+/*
+ * receive raw events: only SEQ_PRIVATE is accepted.
+ */
+static int
+snd_emux_event_oss_input(struct snd_seq_event *ev, int direct, void *private_data,
+                        int atomic, int hop)
+{
+       struct snd_emux *emu;
+       struct snd_emux_port *p;
+       unsigned char cmd, *data;
+
+       p = private_data;
+       if (snd_BUG_ON(!p))
+               return -EINVAL;
+       emu = p->emu;
+       if (snd_BUG_ON(!emu))
+               return -EINVAL;
+       if (ev->type != SNDRV_SEQ_EVENT_OSS)
+               return snd_emux_event_input(ev, direct, private_data, atomic, hop);
+
+       data = ev->data.raw8.d;
+       /* only SEQ_PRIVATE is accepted */
+       if (data[0] != 0xfe)
+               return 0;
+       cmd = data[2] & _EMUX_OSS_MODE_VALUE_MASK;
+       if (data[2] & _EMUX_OSS_MODE_FLAG)
+               emuspec_control(emu, p, cmd, data, atomic, hop);
+       else
+               gusspec_control(emu, p, cmd, data, atomic, hop);
+       return 0;
+}
+
+
+/*
+ * OSS/AWE driver specific h/w controls
+ */
+static void
+emuspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd,
+               unsigned char *event, int atomic, int hop)
+{
+       int voice;
+       unsigned short p1;
+       short p2;
+       int i;
+       struct snd_midi_channel *chan;
+
+       voice = event[3];
+       if (voice < 0 || voice >= port->chset.max_channels)
+               chan = NULL;
+       else
+               chan = &port->chset.channels[voice];
+
+       p1 = *(unsigned short *) &event[4];
+       p2 = *(short *) &event[6];
+
+       switch (cmd) {
+#if 0 /* don't do this atomically */
+       case _EMUX_OSS_REMOVE_LAST_SAMPLES:
+               snd_soundfont_remove_unlocked(emu->sflist);
+               break;
+#endif
+       case _EMUX_OSS_SEND_EFFECT:
+               if (chan)
+                       snd_emux_send_effect_oss(port, chan, p1, p2);
+               break;
+               
+       case _EMUX_OSS_TERMINATE_ALL:
+               snd_emux_terminate_all(emu);
+               break;
+
+       case _EMUX_OSS_TERMINATE_CHANNEL:
+               /*snd_emux_mute_channel(emu, chan);*/
+               break;
+       case _EMUX_OSS_RESET_CHANNEL:
+               /*snd_emux_channel_init(chset, chan);*/
+               break;
+
+       case _EMUX_OSS_RELEASE_ALL:
+               fake_event(emu, port, voice, MIDI_CTL_ALL_NOTES_OFF, 0, atomic, hop);
+               break;
+       case _EMUX_OSS_NOTEOFF_ALL:
+               fake_event(emu, port, voice, MIDI_CTL_ALL_SOUNDS_OFF, 0, atomic, hop);
+               break;
+
+       case _EMUX_OSS_INITIAL_VOLUME:
+               if (p2) {
+                       port->volume_atten = (short)p1;
+                       snd_emux_update_port(port, SNDRV_EMUX_UPDATE_VOLUME);
+               }
+               break;
+
+       case _EMUX_OSS_CHN_PRESSURE:
+               if (chan) {
+                       chan->midi_pressure = p1;
+                       snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_FMMOD|SNDRV_EMUX_UPDATE_FM2FRQ2);
+               }
+               break;
+
+       case _EMUX_OSS_CHANNEL_MODE:
+               reset_port_mode(port, p1);
+               snd_emux_reset_port(port);
+               break;
+
+       case _EMUX_OSS_DRUM_CHANNELS:
+               port->drum_flags = *(unsigned int*)&event[4];
+               for (i = 0; i < port->chset.max_channels; i++) {
+                       chan = &port->chset.channels[i];
+                       chan->drum_channel = ((port->drum_flags >> i) & 1) ? 1 : 0;
+               }
+               break;
+
+       case _EMUX_OSS_MISC_MODE:
+               if (p1 < EMUX_MD_END)
+                       port->ctrls[p1] = p2;
+               break;
+       case _EMUX_OSS_DEBUG_MODE:
+               break;
+
+       default:
+               if (emu->ops.oss_ioctl)
+                       emu->ops.oss_ioctl(emu, cmd, p1, p2);
+               break;
+       }
+}
+
+/*
+ * GUS specific h/w controls
+ */
+
+#include <linux/ultrasound.h>
+
+static void
+gusspec_control(struct snd_emux *emu, struct snd_emux_port *port, int cmd,
+               unsigned char *event, int atomic, int hop)
+{
+       int voice;
+       unsigned short p1;
+       short p2;
+       int plong;
+       struct snd_midi_channel *chan;
+
+       if (port->port_mode != SNDRV_EMUX_PORT_MODE_OSS_SYNTH)
+               return;
+       if (cmd == _GUS_NUMVOICES)
+               return;
+       voice = event[3];
+       if (voice < 0 || voice >= port->chset.max_channels)
+               return;
+
+       chan = &port->chset.channels[voice];
+
+       p1 = *(unsigned short *) &event[4];
+       p2 = *(short *) &event[6];
+       plong = *(int*) &event[4];
+
+       switch (cmd) {
+       case _GUS_VOICESAMPLE:
+               chan->midi_program = p1;
+               return;
+
+       case _GUS_VOICEBALA:
+               /* 0 to 15 --> 0 to 127 */
+               chan->control[MIDI_CTL_MSB_PAN] = (int)p1 << 3;
+               snd_emux_update_channel(port, chan, SNDRV_EMUX_UPDATE_PAN);
+               return;
+
+       case _GUS_VOICEVOL:
+       case _GUS_VOICEVOL2:
+               /* not supported yet */
+               return;
+
+       case _GUS_RAMPRANGE:
+       case _GUS_RAMPRATE:
+       case _GUS_RAMPMODE:
+       case _GUS_RAMPON:
+       case _GUS_RAMPOFF:
+               /* volume ramping not supported */
+               return;
+
+       case _GUS_VOLUME_SCALE:
+               return;
+
+       case _GUS_VOICE_POS:
+#ifdef SNDRV_EMUX_USE_RAW_EFFECT
+               snd_emux_send_effect(port, chan, EMUX_FX_SAMPLE_START,
+                                    (short)(plong & 0x7fff),
+                                    EMUX_FX_FLAG_SET);
+               snd_emux_send_effect(port, chan, EMUX_FX_COARSE_SAMPLE_START,
+                                    (plong >> 15) & 0xffff,
+                                    EMUX_FX_FLAG_SET);
+#endif
+               return;
+       }
+}
+
+
+/*
+ * send an event to midi emulation
+ */
+static void
+fake_event(struct snd_emux *emu, struct snd_emux_port *port, int ch, int param, int val, int atomic, int hop)
+{
+       struct snd_seq_event ev;
+       memset(&ev, 0, sizeof(ev));
+       ev.type = SNDRV_SEQ_EVENT_CONTROLLER;
+       ev.data.control.channel = ch;
+       ev.data.control.param = param;
+       ev.data.control.value = val;
+       snd_emux_event_input(&ev, 0, port, atomic, hop);
+}
+
+#endif /* CONFIG_SND_SEQUENCER_OSS */