diff options
Diffstat (limited to 'sound/drivers')
45 files changed, 19228 insertions, 0 deletions
diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig new file mode 100644 index 0000000000..6debd8e95c --- /dev/null +++ b/sound/drivers/Kconfig @@ -0,0 +1,266 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SND_MPU401_UART + tristate + select SND_RAWMIDI + +config SND_OPL3_LIB + tristate + select SND_TIMER + select SND_HWDEP + select SND_SEQ_DEVICE if SND_SEQUENCER != n + +config SND_OPL4_LIB + tristate + select SND_TIMER + select SND_HWDEP + select SND_SEQ_DEVICE if SND_SEQUENCER != n + +# select SEQ stuff to min(SND_SEQUENCER,SND_XXX) +config SND_OPL3_LIB_SEQ + def_tristate SND_SEQUENCER && SND_OPL3_LIB + select SND_SEQ_MIDI_EMUL + select SND_SEQ_MIDI_EVENT + +config SND_OPL4_LIB_SEQ + def_tristate SND_SEQUENCER && SND_OPL4_LIB + select SND_SEQ_MIDI_EMUL + select SND_SEQ_MIDI_EVENT + +config SND_VX_LIB + tristate + select FW_LOADER + select SND_HWDEP + select SND_PCM + +config SND_AC97_CODEC + tristate + select SND_PCM + select AC97_BUS + select SND_VMASTER + +menuconfig SND_DRIVERS + bool "Generic sound devices" + default y + help + Support for generic sound devices. + +if SND_DRIVERS + +config SND_PCSP + tristate "PC-Speaker support (READ HELP!)" + depends on PCSPKR_PLATFORM && X86 && HIGH_RES_TIMERS + depends on INPUT + select SND_PCM + help + If you don't have a sound card in your computer, you can include a + driver for the PC speaker which allows it to act like a primitive + sound card. + This driver also replaces the pcspkr driver for beeps. + + You can compile this as a module which will be called snd-pcsp. + + WARNING: if you already have a soundcard, enabling this + driver may lead to a problem. Namely, it may get loaded + before the other sound driver of yours, making the + pc-speaker a default sound device. Which is likely not + what you want. To make this driver play nicely with other + sound driver, you can add this in a configuration file under + /etc/modprobe.d/ directory: + options snd-pcsp index=2 + + You don't need this driver if you only want your pc-speaker to beep. + You don't need this driver if you have a tablet piezo beeper + in your PC instead of the real speaker. + + Say N if you have a sound card. + Say M if you don't. + Say Y only if you really know what you do. + +config SND_DUMMY + tristate "Dummy (/dev/null) soundcard" + 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_ALOOP + tristate "Generic loopback driver (PCM)" + select SND_PCM + select SND_TIMER + help + Say 'Y' or 'M' to include support for the PCM loopback device. + This module returns played samples back to the user space using + the standard ALSA PCM device. The devices are routed 0->1 and + 1->0, where first number is the playback PCM device and second + number is the capture device. Module creates two PCM devices and + configured number of substreams (see the pcm_substreams module + parameter). + + The loopback device allows time synchronization with an external + timing source using the time shift universal control (+-20% + of system time). + + To compile this driver as a module, choose M here: the module + will be called snd-aloop. + +config SND_PCMTEST + tristate "Virtual PCM test driver" + depends on DEBUG_FS + select SND_PCM + help + Say 'Y' or 'M' to include support for the Virtual PCM test driver. + This driver is aimed at extended testing of the userspace applications + which use the ALSA API, as well as the PCM middle layer testing. + + It can generate random or pattern-based data into the capture stream, + check the playback stream for containing the selected pattern, inject + time delays during capture/playback, redefine the RESET ioctl operation + to perform the PCM middle layer testing and inject errors during the + PCM callbacks. It supports both interleaved and non-interleaved access + modes. You can find the corresponding selftest in the 'alsa' + selftests folder. + +config SND_VIRMIDI + tristate "Virtual MIDI soundcard" + depends on SND_SEQUENCER + select SND_TIMER + select SND_RAWMIDI + select SND_SEQ_VIRMIDI + select SND_SEQ_MIDI_EVENT + 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 HAS_IOPORT + 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 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 HAS_IOPORT + select SND_RAWMIDI + help + To include support for MIDI serial port interfaces, say Y here + and read <file:Documentation/sound/cards/serial-u16550.rst>. + 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_SERIAL_GENERIC + tristate "Generic serial MIDI driver" + depends on SERIAL_DEV_BUS + depends on OF + select SND_RAWMIDI + help + To include support for mapping generic serial devices as raw + ALSA MIDI devices, say Y here. The driver only supports setting + the serial port to standard baudrates. To attain the standard MIDI + baudrate of 31.25 kBaud, configure the clock of the underlying serial + device so that a requested 38.4 kBaud will result in the standard speed. + + Use this devicetree binding to configure serial port mapping + <file:Documentation/devicetree/bindings/sound/serial-midi.yaml> + + To compile this driver as a module, choose M here: the module + will be called snd-serial-generic. + +config SND_MPU401 + tristate "Generic MPU-401 UART driver" + depends on HAS_IOPORT + 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 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. + +config SND_AC97_POWER_SAVE + bool "AC97 Power-Saving Mode" + depends on SND_AC97_CODEC + default n + help + Say Y here to enable the aggressive power-saving support of + AC97 codecs. In this mode, the power-mode is dynamically + controlled at each open/close. + + The mode is activated by passing 'power_save=X' to the + snd-ac97-codec driver module, where 'X' is the time-out + value, a nonnegative integer that specifies how many + seconds of idle time the driver must count before it may + put the AC97 into power-save mode; a value of 0 (zero) + disables the use of this power-save mode. + + After the snd-ac97-codec driver module has been loaded, + the 'power_save' parameter can be set via sysfs as follows: + + echo 10 > /sys/module/snd_ac97_codec/parameters/power_save + + In this case, the time-out is set to 10 seconds; setting + the time-out to 1 second (the minimum activation value) + isn't recommended because many applications try to reopen + the device frequently. A value of 10 seconds would be a + good choice for normal operations. + + See Documentation/sound/designs/powersave.rst for more details. + +config SND_AC97_POWER_SAVE_DEFAULT + int "Default time-out for AC97 power-save mode" + depends on SND_AC97_POWER_SAVE + default 0 + help + The default time-out value in seconds for AC97 automatic + power-save mode. 0 means to disable the power-save mode. + + See SND_AC97_POWER_SAVE for more details. + +endif # SND_DRIVERS diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile new file mode 100644 index 0000000000..2c0c7092d3 --- /dev/null +++ b/sound/drivers/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-dummy-objs := dummy.o +snd-aloop-objs := aloop.o +snd-mtpav-objs := mtpav.o +snd-mts64-objs := mts64.o +snd-pcmtest-objs := pcmtest.o +snd-portman2x4-objs := portman2x4.o +snd-serial-u16550-objs := serial-u16550.o +snd-serial-generic-objs := serial-generic.o +snd-virmidi-objs := virmidi.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_DUMMY) += snd-dummy.o +obj-$(CONFIG_SND_ALOOP) += snd-aloop.o +obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o +obj-$(CONFIG_SND_PCMTEST) += snd-pcmtest.o +obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o +obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.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/ pcsp/ diff --git a/sound/drivers/aloop.c b/sound/drivers/aloop.c new file mode 100644 index 0000000000..a38e602b4f --- /dev/null +++ b/sound/drivers/aloop.c @@ -0,0 +1,1837 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Loopback soundcard + * + * Original code: + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * + * More accurate positioning and full-duplex support: + * Copyright (c) Ahmet İnan <ainan at mathematik.uni-freiburg.de> + * + * Major (almost complete) rewrite: + * Copyright (c) by Takashi Iwai <tiwai@suse.de> + * + * A next major update in 2010 (separate timers for playback and capture): + * Copyright (c) Jaroslav Kysela <perex@perex.cz> + */ + +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/info.h> +#include <sound/initval.h> +#include <sound/timer.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("A loopback soundcard"); +MODULE_LICENSE("GPL"); + +#define MAX_PCM_SUBSTREAMS 8 + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; +static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8}; +static int pcm_notify[SNDRV_CARDS]; +static char *timer_source[SNDRV_CARDS]; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for loopback soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for loopback soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this loopback soundcard."); +module_param_array(pcm_substreams, int, NULL, 0444); +MODULE_PARM_DESC(pcm_substreams, "PCM substreams # (1-8) for loopback driver."); +module_param_array(pcm_notify, int, NULL, 0444); +MODULE_PARM_DESC(pcm_notify, "Break capture when PCM format/rate/channels changes."); +module_param_array(timer_source, charp, NULL, 0444); +MODULE_PARM_DESC(timer_source, "Sound card name or number and device/subdevice number of timer to be used. Empty string for jiffies timer [default]."); + +#define NO_PITCH 100000 + +#define CABLE_VALID_PLAYBACK BIT(SNDRV_PCM_STREAM_PLAYBACK) +#define CABLE_VALID_CAPTURE BIT(SNDRV_PCM_STREAM_CAPTURE) +#define CABLE_VALID_BOTH (CABLE_VALID_PLAYBACK | CABLE_VALID_CAPTURE) + +struct loopback_cable; +struct loopback_pcm; + +struct loopback_ops { + /* optional + * call in loopback->cable_lock + */ + int (*open)(struct loopback_pcm *dpcm); + /* required + * call in cable->lock + */ + int (*start)(struct loopback_pcm *dpcm); + /* required + * call in cable->lock + */ + int (*stop)(struct loopback_pcm *dpcm); + /* optional */ + int (*stop_sync)(struct loopback_pcm *dpcm); + /* optional */ + int (*close_substream)(struct loopback_pcm *dpcm); + /* optional + * call in loopback->cable_lock + */ + int (*close_cable)(struct loopback_pcm *dpcm); + /* optional + * call in cable->lock + */ + unsigned int (*pos_update)(struct loopback_cable *cable); + /* optional */ + void (*dpcm_info)(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer); +}; + +struct loopback_cable { + spinlock_t lock; + struct loopback_pcm *streams[2]; + struct snd_pcm_hardware hw; + /* flags */ + unsigned int valid; + unsigned int running; + unsigned int pause; + /* timer specific */ + const struct loopback_ops *ops; + /* If sound timer is used */ + struct { + int stream; + struct snd_timer_id id; + struct work_struct event_work; + struct snd_timer_instance *instance; + } snd_timer; +}; + +struct loopback_setup { + unsigned int notify: 1; + unsigned int rate_shift; + snd_pcm_format_t format; + unsigned int rate; + unsigned int channels; + struct snd_ctl_elem_id active_id; + struct snd_ctl_elem_id format_id; + struct snd_ctl_elem_id rate_id; + struct snd_ctl_elem_id channels_id; +}; + +struct loopback { + struct snd_card *card; + struct mutex cable_lock; + struct loopback_cable *cables[MAX_PCM_SUBSTREAMS][2]; + struct snd_pcm *pcm[2]; + struct loopback_setup setup[MAX_PCM_SUBSTREAMS][2]; + const char *timer_source; +}; + +struct loopback_pcm { + struct loopback *loopback; + struct snd_pcm_substream *substream; + struct loopback_cable *cable; + unsigned int pcm_buffer_size; + unsigned int buf_pos; /* position in buffer */ + unsigned int silent_size; + /* PCM parameters */ + unsigned int pcm_period_size; + unsigned int pcm_bps; /* bytes per second */ + unsigned int pcm_salign; /* bytes per sample * channels */ + unsigned int pcm_rate_shift; /* rate shift value */ + /* flags */ + unsigned int period_update_pending :1; + /* timer stuff */ + unsigned int irq_pos; /* fractional IRQ position in jiffies + * ticks + */ + unsigned int period_size_frac; /* period size in jiffies ticks */ + unsigned int last_drift; + unsigned long last_jiffies; + /* If jiffies timer is used */ + struct timer_list timer; +}; + +static struct platform_device *devices[SNDRV_CARDS]; + +static inline unsigned int byte_pos(struct loopback_pcm *dpcm, unsigned int x) +{ + if (dpcm->pcm_rate_shift == NO_PITCH) { + x /= HZ; + } else { + x = div_u64(NO_PITCH * (unsigned long long)x, + HZ * (unsigned long long)dpcm->pcm_rate_shift); + } + return x - (x % dpcm->pcm_salign); +} + +static inline unsigned int frac_pos(struct loopback_pcm *dpcm, unsigned int x) +{ + if (dpcm->pcm_rate_shift == NO_PITCH) { /* no pitch */ + return x * HZ; + } else { + x = div_u64(dpcm->pcm_rate_shift * (unsigned long long)x * HZ, + NO_PITCH); + } + return x; +} + +static inline struct loopback_setup *get_setup(struct loopback_pcm *dpcm) +{ + int device = dpcm->substream->pstr->pcm->device; + + if (dpcm->substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + device ^= 1; + return &dpcm->loopback->setup[dpcm->substream->number][device]; +} + +static inline unsigned int get_notify(struct loopback_pcm *dpcm) +{ + return get_setup(dpcm)->notify; +} + +static inline unsigned int get_rate_shift(struct loopback_pcm *dpcm) +{ + return get_setup(dpcm)->rate_shift; +} + +/* call in cable->lock */ +static int loopback_jiffies_timer_start(struct loopback_pcm *dpcm) +{ + unsigned long tick; + unsigned int rate_shift = get_rate_shift(dpcm); + + if (rate_shift != dpcm->pcm_rate_shift) { + dpcm->pcm_rate_shift = rate_shift; + dpcm->period_size_frac = frac_pos(dpcm, dpcm->pcm_period_size); + } + if (dpcm->period_size_frac <= dpcm->irq_pos) { + dpcm->irq_pos %= dpcm->period_size_frac; + dpcm->period_update_pending = 1; + } + tick = dpcm->period_size_frac - dpcm->irq_pos; + tick = DIV_ROUND_UP(tick, dpcm->pcm_bps); + mod_timer(&dpcm->timer, jiffies + tick); + + return 0; +} + +/* call in cable->lock */ +static int loopback_snd_timer_start(struct loopback_pcm *dpcm) +{ + struct loopback_cable *cable = dpcm->cable; + int err; + + /* Loopback device has to use same period as timer card. Therefore + * wake up for each snd_pcm_period_elapsed() call of timer card. + */ + err = snd_timer_start(cable->snd_timer.instance, 1); + if (err < 0) { + /* do not report error if trying to start but already + * running. For example called by opposite substream + * of the same cable + */ + if (err == -EBUSY) + return 0; + + pcm_err(dpcm->substream->pcm, + "snd_timer_start(%d,%d,%d) failed with %d", + cable->snd_timer.id.card, + cable->snd_timer.id.device, + cable->snd_timer.id.subdevice, + err); + } + + return err; +} + +/* call in cable->lock */ +static inline int loopback_jiffies_timer_stop(struct loopback_pcm *dpcm) +{ + del_timer(&dpcm->timer); + dpcm->timer.expires = 0; + + return 0; +} + +/* call in cable->lock */ +static int loopback_snd_timer_stop(struct loopback_pcm *dpcm) +{ + struct loopback_cable *cable = dpcm->cable; + int err; + + /* only stop if both devices (playback and capture) are not running */ + if (cable->running ^ cable->pause) + return 0; + + err = snd_timer_stop(cable->snd_timer.instance); + if (err < 0) { + pcm_err(dpcm->substream->pcm, + "snd_timer_stop(%d,%d,%d) failed with %d", + cable->snd_timer.id.card, + cable->snd_timer.id.device, + cable->snd_timer.id.subdevice, + err); + } + + return err; +} + +static inline int loopback_jiffies_timer_stop_sync(struct loopback_pcm *dpcm) +{ + del_timer_sync(&dpcm->timer); + + return 0; +} + +/* call in loopback->cable_lock */ +static int loopback_snd_timer_close_cable(struct loopback_pcm *dpcm) +{ + struct loopback_cable *cable = dpcm->cable; + + /* snd_timer was not opened */ + if (!cable->snd_timer.instance) + return 0; + + /* will only be called from free_cable() when other stream was + * already closed. Other stream cannot be reopened as long as + * loopback->cable_lock is locked. Therefore no need to lock + * cable->lock; + */ + snd_timer_close(cable->snd_timer.instance); + + /* wait till drain work has finished if requested */ + cancel_work_sync(&cable->snd_timer.event_work); + + snd_timer_instance_free(cable->snd_timer.instance); + memset(&cable->snd_timer, 0, sizeof(cable->snd_timer)); + + return 0; +} + +static int loopback_check_format(struct loopback_cable *cable, int stream) +{ + struct snd_pcm_runtime *runtime, *cruntime; + struct loopback_setup *setup; + struct snd_card *card; + int check; + + if (cable->valid != CABLE_VALID_BOTH) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + goto __notify; + return 0; + } + runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]-> + substream->runtime; + cruntime = cable->streams[SNDRV_PCM_STREAM_CAPTURE]-> + substream->runtime; + check = runtime->format != cruntime->format || + runtime->rate != cruntime->rate || + runtime->channels != cruntime->channels; + if (!check) + return 0; + if (stream == SNDRV_PCM_STREAM_CAPTURE) { + return -EIO; + } else { + snd_pcm_stop(cable->streams[SNDRV_PCM_STREAM_CAPTURE]-> + substream, SNDRV_PCM_STATE_DRAINING); + __notify: + runtime = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]-> + substream->runtime; + setup = get_setup(cable->streams[SNDRV_PCM_STREAM_PLAYBACK]); + card = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]->loopback->card; + if (setup->format != runtime->format) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->format_id); + setup->format = runtime->format; + } + if (setup->rate != runtime->rate) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->rate_id); + setup->rate = runtime->rate; + } + if (setup->channels != runtime->channels) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->channels_id); + setup->channels = runtime->channels; + } + } + return 0; +} + +static void loopback_active_notify(struct loopback_pcm *dpcm) +{ + snd_ctl_notify(dpcm->loopback->card, + SNDRV_CTL_EVENT_MASK_VALUE, + &get_setup(dpcm)->active_id); +} + +static int loopback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + int err = 0, stream = 1 << substream->stream; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = loopback_check_format(cable, substream->stream); + if (err < 0) + return err; + dpcm->last_jiffies = jiffies; + dpcm->pcm_rate_shift = 0; + dpcm->last_drift = 0; + spin_lock(&cable->lock); + cable->running |= stream; + cable->pause &= ~stream; + err = cable->ops->start(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_STOP: + spin_lock(&cable->lock); + cable->running &= ~stream; + cable->pause &= ~stream; + err = cable->ops->stop(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + spin_lock(&cable->lock); + cable->pause |= stream; + err = cable->ops->stop(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + spin_lock(&cable->lock); + dpcm->last_jiffies = jiffies; + cable->pause &= ~stream; + err = cable->ops->start(dpcm); + spin_unlock(&cable->lock); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + default: + return -EINVAL; + } + return err; +} + +static void params_change(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + + cable->hw.formats = pcm_format_to_bits(runtime->format); + cable->hw.rate_min = runtime->rate; + cable->hw.rate_max = runtime->rate; + cable->hw.channels_min = runtime->channels; + cable->hw.channels_max = runtime->channels; + + if (cable->snd_timer.instance) { + cable->hw.period_bytes_min = + frames_to_bytes(runtime, runtime->period_size); + cable->hw.period_bytes_max = cable->hw.period_bytes_min; + } + +} + +static int loopback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + int err, bps, salign; + + if (cable->ops->stop_sync) { + err = cable->ops->stop_sync(dpcm); + if (err < 0) + return err; + } + + salign = (snd_pcm_format_physical_width(runtime->format) * + runtime->channels) / 8; + bps = salign * runtime->rate; + if (bps <= 0 || salign <= 0) + return -EINVAL; + + dpcm->buf_pos = 0; + dpcm->pcm_buffer_size = frames_to_bytes(runtime, runtime->buffer_size); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + /* clear capture buffer */ + dpcm->silent_size = dpcm->pcm_buffer_size; + snd_pcm_format_set_silence(runtime->format, runtime->dma_area, + runtime->buffer_size * runtime->channels); + } + + dpcm->irq_pos = 0; + dpcm->period_update_pending = 0; + dpcm->pcm_bps = bps; + dpcm->pcm_salign = salign; + dpcm->pcm_period_size = frames_to_bytes(runtime, runtime->period_size); + + mutex_lock(&dpcm->loopback->cable_lock); + if (!(cable->valid & ~(1 << substream->stream)) || + (get_setup(dpcm)->notify && + substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) + params_change(substream); + cable->valid |= 1 << substream->stream; + mutex_unlock(&dpcm->loopback->cable_lock); + + return 0; +} + +static void clear_capture_buf(struct loopback_pcm *dpcm, unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = dpcm->substream->runtime; + char *dst = runtime->dma_area; + unsigned int dst_off = dpcm->buf_pos; + + if (dpcm->silent_size >= dpcm->pcm_buffer_size) + return; + if (dpcm->silent_size + bytes > dpcm->pcm_buffer_size) + bytes = dpcm->pcm_buffer_size - dpcm->silent_size; + + for (;;) { + unsigned int size = bytes; + if (dst_off + size > dpcm->pcm_buffer_size) + size = dpcm->pcm_buffer_size - dst_off; + snd_pcm_format_set_silence(runtime->format, dst + dst_off, + bytes_to_frames(runtime, size) * + runtime->channels); + dpcm->silent_size += size; + bytes -= size; + if (!bytes) + break; + dst_off = 0; + } +} + +static void copy_play_buf(struct loopback_pcm *play, + struct loopback_pcm *capt, + unsigned int bytes) +{ + struct snd_pcm_runtime *runtime = play->substream->runtime; + char *src = runtime->dma_area; + char *dst = capt->substream->runtime->dma_area; + unsigned int src_off = play->buf_pos; + unsigned int dst_off = capt->buf_pos; + unsigned int clear_bytes = 0; + + /* check if playback is draining, trim the capture copy size + * when our pointer is at the end of playback ring buffer */ + if (runtime->state == SNDRV_PCM_STATE_DRAINING && + snd_pcm_playback_hw_avail(runtime) < runtime->buffer_size) { + snd_pcm_uframes_t appl_ptr, appl_ptr1, diff; + appl_ptr = appl_ptr1 = runtime->control->appl_ptr; + appl_ptr1 -= appl_ptr1 % runtime->buffer_size; + appl_ptr1 += play->buf_pos / play->pcm_salign; + if (appl_ptr < appl_ptr1) + appl_ptr1 -= runtime->buffer_size; + diff = (appl_ptr - appl_ptr1) * play->pcm_salign; + if (diff < bytes) { + clear_bytes = bytes - diff; + bytes = diff; + } + } + + for (;;) { + unsigned int size = bytes; + if (src_off + size > play->pcm_buffer_size) + size = play->pcm_buffer_size - src_off; + if (dst_off + size > capt->pcm_buffer_size) + size = capt->pcm_buffer_size - dst_off; + memcpy(dst + dst_off, src + src_off, size); + capt->silent_size = 0; + bytes -= size; + if (!bytes) + break; + src_off = (src_off + size) % play->pcm_buffer_size; + dst_off = (dst_off + size) % capt->pcm_buffer_size; + } + + if (clear_bytes > 0) { + clear_capture_buf(capt, clear_bytes); + capt->silent_size = 0; + } +} + +static inline unsigned int bytepos_delta(struct loopback_pcm *dpcm, + unsigned int jiffies_delta) +{ + unsigned long last_pos; + unsigned int delta; + + last_pos = byte_pos(dpcm, dpcm->irq_pos); + dpcm->irq_pos += jiffies_delta * dpcm->pcm_bps; + delta = byte_pos(dpcm, dpcm->irq_pos) - last_pos; + if (delta >= dpcm->last_drift) + delta -= dpcm->last_drift; + dpcm->last_drift = 0; + if (dpcm->irq_pos >= dpcm->period_size_frac) { + dpcm->irq_pos %= dpcm->period_size_frac; + dpcm->period_update_pending = 1; + } + return delta; +} + +static inline void bytepos_finish(struct loopback_pcm *dpcm, + unsigned int delta) +{ + dpcm->buf_pos += delta; + dpcm->buf_pos %= dpcm->pcm_buffer_size; +} + +/* call in cable->lock */ +static unsigned int loopback_jiffies_timer_pos_update + (struct loopback_cable *cable) +{ + struct loopback_pcm *dpcm_play = + cable->streams[SNDRV_PCM_STREAM_PLAYBACK]; + struct loopback_pcm *dpcm_capt = + cable->streams[SNDRV_PCM_STREAM_CAPTURE]; + unsigned long delta_play = 0, delta_capt = 0, cur_jiffies; + unsigned int running, count1, count2; + + cur_jiffies = jiffies; + running = cable->running ^ cable->pause; + if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) { + delta_play = cur_jiffies - dpcm_play->last_jiffies; + dpcm_play->last_jiffies += delta_play; + } + + if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) { + delta_capt = cur_jiffies - dpcm_capt->last_jiffies; + dpcm_capt->last_jiffies += delta_capt; + } + + if (delta_play == 0 && delta_capt == 0) + goto unlock; + + if (delta_play > delta_capt) { + count1 = bytepos_delta(dpcm_play, delta_play - delta_capt); + bytepos_finish(dpcm_play, count1); + delta_play = delta_capt; + } else if (delta_play < delta_capt) { + count1 = bytepos_delta(dpcm_capt, delta_capt - delta_play); + clear_capture_buf(dpcm_capt, count1); + bytepos_finish(dpcm_capt, count1); + delta_capt = delta_play; + } + + if (delta_play == 0 && delta_capt == 0) + goto unlock; + + /* note delta_capt == delta_play at this moment */ + count1 = bytepos_delta(dpcm_play, delta_play); + count2 = bytepos_delta(dpcm_capt, delta_capt); + if (count1 < count2) { + dpcm_capt->last_drift = count2 - count1; + count1 = count2; + } else if (count1 > count2) { + dpcm_play->last_drift = count1 - count2; + } + copy_play_buf(dpcm_play, dpcm_capt, count1); + bytepos_finish(dpcm_play, count1); + bytepos_finish(dpcm_capt, count1); + unlock: + return running; +} + +static void loopback_jiffies_timer_function(struct timer_list *t) +{ + struct loopback_pcm *dpcm = from_timer(dpcm, t, timer); + unsigned long flags; + + spin_lock_irqsave(&dpcm->cable->lock, flags); + if (loopback_jiffies_timer_pos_update(dpcm->cable) & + (1 << dpcm->substream->stream)) { + loopback_jiffies_timer_start(dpcm); + if (dpcm->period_update_pending) { + dpcm->period_update_pending = 0; + spin_unlock_irqrestore(&dpcm->cable->lock, flags); + /* need to unlock before calling below */ + snd_pcm_period_elapsed(dpcm->substream); + return; + } + } + spin_unlock_irqrestore(&dpcm->cable->lock, flags); +} + +/* call in cable->lock */ +static int loopback_snd_timer_check_resolution(struct snd_pcm_runtime *runtime, + unsigned long resolution) +{ + if (resolution != runtime->timer_resolution) { + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + /* Worst case estimation of possible values for resolution + * resolution <= (512 * 1024) frames / 8kHz in nsec + * resolution <= 65.536.000.000 nsec + * + * period_size <= 65.536.000.000 nsec / 1000nsec/usec * 192kHz + + * 500.000 + * period_size <= 12.582.912.000.000 <64bit + * / 1.000.000 usec/sec + */ + snd_pcm_uframes_t period_size_usec = + resolution / 1000 * runtime->rate; + /* round to nearest sample rate */ + snd_pcm_uframes_t period_size = + (period_size_usec + 500 * 1000) / (1000 * 1000); + + pcm_err(dpcm->substream->pcm, + "Period size (%lu frames) of loopback device is not corresponding to timer resolution (%lu nsec = %lu frames) of card timer %d,%d,%d. Use period size of %lu frames for loopback device.", + runtime->period_size, resolution, period_size, + cable->snd_timer.id.card, + cable->snd_timer.id.device, + cable->snd_timer.id.subdevice, + period_size); + return -EINVAL; + } + return 0; +} + +static void loopback_snd_timer_period_elapsed(struct loopback_cable *cable, + int event, + unsigned long resolution) +{ + struct loopback_pcm *dpcm_play, *dpcm_capt; + struct snd_pcm_substream *substream_play, *substream_capt; + struct snd_pcm_runtime *valid_runtime; + unsigned int running, elapsed_bytes; + unsigned long flags; + + spin_lock_irqsave(&cable->lock, flags); + running = cable->running ^ cable->pause; + /* no need to do anything if no stream is running */ + if (!running) { + spin_unlock_irqrestore(&cable->lock, flags); + return; + } + + dpcm_play = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]; + dpcm_capt = cable->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (event == SNDRV_TIMER_EVENT_MSTOP) { + if (!dpcm_play || + dpcm_play->substream->runtime->state != + SNDRV_PCM_STATE_DRAINING) { + spin_unlock_irqrestore(&cable->lock, flags); + return; + } + } + + substream_play = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ? + dpcm_play->substream : NULL; + substream_capt = (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) ? + dpcm_capt->substream : NULL; + valid_runtime = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ? + dpcm_play->substream->runtime : + dpcm_capt->substream->runtime; + + /* resolution is only valid for SNDRV_TIMER_EVENT_TICK events */ + if (event == SNDRV_TIMER_EVENT_TICK) { + /* The hardware rules guarantee that playback and capture period + * are the same. Therefore only one device has to be checked + * here. + */ + if (loopback_snd_timer_check_resolution(valid_runtime, + resolution) < 0) { + spin_unlock_irqrestore(&cable->lock, flags); + if (substream_play) + snd_pcm_stop_xrun(substream_play); + if (substream_capt) + snd_pcm_stop_xrun(substream_capt); + return; + } + } + + elapsed_bytes = frames_to_bytes(valid_runtime, + valid_runtime->period_size); + /* The same timer interrupt is used for playback and capture device */ + if ((running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) && + (running & (1 << SNDRV_PCM_STREAM_CAPTURE))) { + copy_play_buf(dpcm_play, dpcm_capt, elapsed_bytes); + bytepos_finish(dpcm_play, elapsed_bytes); + bytepos_finish(dpcm_capt, elapsed_bytes); + } else if (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) { + bytepos_finish(dpcm_play, elapsed_bytes); + } else if (running & (1 << SNDRV_PCM_STREAM_CAPTURE)) { + clear_capture_buf(dpcm_capt, elapsed_bytes); + bytepos_finish(dpcm_capt, elapsed_bytes); + } + spin_unlock_irqrestore(&cable->lock, flags); + + if (substream_play) + snd_pcm_period_elapsed(substream_play); + if (substream_capt) + snd_pcm_period_elapsed(substream_capt); +} + +static void loopback_snd_timer_function(struct snd_timer_instance *timeri, + unsigned long resolution, + unsigned long ticks) +{ + struct loopback_cable *cable = timeri->callback_data; + + loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_TICK, + resolution); +} + +static void loopback_snd_timer_work(struct work_struct *work) +{ + struct loopback_cable *cable; + + cable = container_of(work, struct loopback_cable, snd_timer.event_work); + loopback_snd_timer_period_elapsed(cable, SNDRV_TIMER_EVENT_MSTOP, 0); +} + +static void loopback_snd_timer_event(struct snd_timer_instance *timeri, + int event, + struct timespec64 *tstamp, + unsigned long resolution) +{ + /* Do not lock cable->lock here because timer->lock is already hold. + * There are other functions which first lock cable->lock and than + * timer->lock e.g. + * loopback_trigger() + * spin_lock(&cable->lock) + * loopback_snd_timer_start() + * snd_timer_start() + * spin_lock(&timer->lock) + * Therefore when using the oposit order of locks here it could result + * in a deadlock. + */ + + if (event == SNDRV_TIMER_EVENT_MSTOP) { + struct loopback_cable *cable = timeri->callback_data; + + /* sound card of the timer was stopped. Therefore there will not + * be any further timer callbacks. Due to this forward audio + * data from here if in draining state. When still in running + * state the streaming will be aborted by the usual timeout. It + * should not be aborted here because may be the timer sound + * card does only a recovery and the timer is back soon. + * This work triggers loopback_snd_timer_work() + */ + schedule_work(&cable->snd_timer.event_work); + } +} + +static void loopback_jiffies_timer_dpcm_info(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer) +{ + snd_iprintf(buffer, " update_pending:\t%u\n", + dpcm->period_update_pending); + snd_iprintf(buffer, " irq_pos:\t\t%u\n", dpcm->irq_pos); + snd_iprintf(buffer, " period_frac:\t%u\n", dpcm->period_size_frac); + snd_iprintf(buffer, " last_jiffies:\t%lu (%lu)\n", + dpcm->last_jiffies, jiffies); + snd_iprintf(buffer, " timer_expires:\t%lu\n", dpcm->timer.expires); +} + +static void loopback_snd_timer_dpcm_info(struct loopback_pcm *dpcm, + struct snd_info_buffer *buffer) +{ + struct loopback_cable *cable = dpcm->cable; + + snd_iprintf(buffer, " sound timer:\thw:%d,%d,%d\n", + cable->snd_timer.id.card, + cable->snd_timer.id.device, + cable->snd_timer.id.subdevice); + snd_iprintf(buffer, " timer open:\t\t%s\n", + (cable->snd_timer.stream == SNDRV_PCM_STREAM_CAPTURE) ? + "capture" : "playback"); +} + +static snd_pcm_uframes_t loopback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + snd_pcm_uframes_t pos; + + spin_lock(&dpcm->cable->lock); + if (dpcm->cable->ops->pos_update) + dpcm->cable->ops->pos_update(dpcm->cable); + pos = dpcm->buf_pos; + spin_unlock(&dpcm->cable->lock); + return bytes_to_frames(runtime, pos); +} + +static const struct snd_pcm_hardware loopback_pcm_hardware = +{ + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | + SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 32, + .buffer_bytes_max = 2 * 1024 * 1024, + .period_bytes_min = 64, + /* note check overflow in frac_pos() using pcm_rate_shift before + changing period_bytes_max value */ + .period_bytes_max = 1024 * 1024, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static void loopback_runtime_free(struct snd_pcm_runtime *runtime) +{ + struct loopback_pcm *dpcm = runtime->private_data; + kfree(dpcm); +} + +static int loopback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback_pcm *dpcm = runtime->private_data; + struct loopback_cable *cable = dpcm->cable; + + mutex_lock(&dpcm->loopback->cable_lock); + cable->valid &= ~(1 << substream->stream); + mutex_unlock(&dpcm->loopback->cable_lock); + return 0; +} + +static unsigned int get_cable_index(struct snd_pcm_substream *substream) +{ + if (!substream->pcm->device) + return substream->stream; + else + return !substream->stream; +} + +static int rule_format(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct loopback_pcm *dpcm = rule->private; + struct loopback_cable *cable = dpcm->cable; + struct snd_mask m; + + snd_mask_none(&m); + mutex_lock(&dpcm->loopback->cable_lock); + m.bits[0] = (u_int32_t)cable->hw.formats; + m.bits[1] = (u_int32_t)(cable->hw.formats >> 32); + mutex_unlock(&dpcm->loopback->cable_lock); + return snd_mask_refine(hw_param_mask(params, rule->var), &m); +} + +static int rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct loopback_pcm *dpcm = rule->private; + struct loopback_cable *cable = dpcm->cable; + struct snd_interval t; + + mutex_lock(&dpcm->loopback->cable_lock); + t.min = cable->hw.rate_min; + t.max = cable->hw.rate_max; + mutex_unlock(&dpcm->loopback->cable_lock); + t.openmin = t.openmax = 0; + t.integer = 0; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct loopback_pcm *dpcm = rule->private; + struct loopback_cable *cable = dpcm->cable; + struct snd_interval t; + + mutex_lock(&dpcm->loopback->cable_lock); + t.min = cable->hw.channels_min; + t.max = cable->hw.channels_max; + mutex_unlock(&dpcm->loopback->cable_lock); + t.openmin = t.openmax = 0; + t.integer = 0; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static int rule_period_bytes(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct loopback_pcm *dpcm = rule->private; + struct loopback_cable *cable = dpcm->cable; + struct snd_interval t; + + mutex_lock(&dpcm->loopback->cable_lock); + t.min = cable->hw.period_bytes_min; + t.max = cable->hw.period_bytes_max; + mutex_unlock(&dpcm->loopback->cable_lock); + t.openmin = 0; + t.openmax = 0; + t.integer = 0; + return snd_interval_refine(hw_param_interval(params, rule->var), &t); +} + +static void free_cable(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + int dev = get_cable_index(substream); + struct loopback_cable *cable; + + cable = loopback->cables[substream->number][dev]; + if (!cable) + return; + if (cable->streams[!substream->stream]) { + /* other stream is still alive */ + spin_lock_irq(&cable->lock); + cable->streams[substream->stream] = NULL; + spin_unlock_irq(&cable->lock); + } else { + struct loopback_pcm *dpcm = substream->runtime->private_data; + + if (cable->ops && cable->ops->close_cable && dpcm) + cable->ops->close_cable(dpcm); + /* free the cable */ + loopback->cables[substream->number][dev] = NULL; + kfree(cable); + } +} + +static int loopback_jiffies_timer_open(struct loopback_pcm *dpcm) +{ + timer_setup(&dpcm->timer, loopback_jiffies_timer_function, 0); + + return 0; +} + +static const struct loopback_ops loopback_jiffies_timer_ops = { + .open = loopback_jiffies_timer_open, + .start = loopback_jiffies_timer_start, + .stop = loopback_jiffies_timer_stop, + .stop_sync = loopback_jiffies_timer_stop_sync, + .close_substream = loopback_jiffies_timer_stop_sync, + .pos_update = loopback_jiffies_timer_pos_update, + .dpcm_info = loopback_jiffies_timer_dpcm_info, +}; + +static int loopback_parse_timer_id(const char *str, + struct snd_timer_id *tid) +{ + /* [<pref>:](<card name>|<card idx>)[{.,}<dev idx>[{.,}<subdev idx>]] */ + const char * const sep_dev = ".,"; + const char * const sep_pref = ":"; + const char *name = str; + char *sep, save = '\0'; + int card_idx = 0, dev = 0, subdev = 0; + int err; + + sep = strpbrk(str, sep_pref); + if (sep) + name = sep + 1; + sep = strpbrk(name, sep_dev); + if (sep) { + save = *sep; + *sep = '\0'; + } + err = kstrtoint(name, 0, &card_idx); + if (err == -EINVAL) { + /* Must be the name, not number */ + for (card_idx = 0; card_idx < snd_ecards_limit; card_idx++) { + struct snd_card *card = snd_card_ref(card_idx); + + if (card) { + if (!strcmp(card->id, name)) + err = 0; + snd_card_unref(card); + } + if (!err) + break; + } + } + if (sep) { + *sep = save; + if (!err) { + char *sep2, save2 = '\0'; + + sep2 = strpbrk(sep + 1, sep_dev); + if (sep2) { + save2 = *sep2; + *sep2 = '\0'; + } + err = kstrtoint(sep + 1, 0, &dev); + if (sep2) { + *sep2 = save2; + if (!err) + err = kstrtoint(sep2 + 1, 0, &subdev); + } + } + } + if (!err && tid) { + tid->card = card_idx; + tid->device = dev; + tid->subdevice = subdev; + } + return err; +} + +/* call in loopback->cable_lock */ +static int loopback_snd_timer_open(struct loopback_pcm *dpcm) +{ + int err = 0; + struct snd_timer_id tid = { + .dev_class = SNDRV_TIMER_CLASS_PCM, + .dev_sclass = SNDRV_TIMER_SCLASS_APPLICATION, + }; + struct snd_timer_instance *timeri; + struct loopback_cable *cable = dpcm->cable; + + /* check if timer was already opened. It is only opened once + * per playback and capture subdevice (aka cable). + */ + if (cable->snd_timer.instance) + goto exit; + + err = loopback_parse_timer_id(dpcm->loopback->timer_source, &tid); + if (err < 0) { + pcm_err(dpcm->substream->pcm, + "Parsing timer source \'%s\' failed with %d", + dpcm->loopback->timer_source, err); + goto exit; + } + + cable->snd_timer.stream = dpcm->substream->stream; + cable->snd_timer.id = tid; + + timeri = snd_timer_instance_new(dpcm->loopback->card->id); + if (!timeri) { + err = -ENOMEM; + goto exit; + } + /* The callback has to be called from another work. If + * SNDRV_TIMER_IFLG_FAST is specified it will be called from the + * snd_pcm_period_elapsed() call of the selected sound card. + * snd_pcm_period_elapsed() helds snd_pcm_stream_lock_irqsave(). + * Due to our callback loopback_snd_timer_function() also calls + * snd_pcm_period_elapsed() which calls snd_pcm_stream_lock_irqsave(). + * This would end up in a dead lock. + */ + timeri->flags |= SNDRV_TIMER_IFLG_AUTO; + timeri->callback = loopback_snd_timer_function; + timeri->callback_data = (void *)cable; + timeri->ccallback = loopback_snd_timer_event; + + /* initialise a work used for draining */ + INIT_WORK(&cable->snd_timer.event_work, loopback_snd_timer_work); + + /* The mutex loopback->cable_lock is kept locked. + * Therefore snd_timer_open() cannot be called a second time + * by the other device of the same cable. + * Therefore the following issue cannot happen: + * [proc1] Call loopback_timer_open() -> + * Unlock cable->lock for snd_timer_close/open() call + * [proc2] Call loopback_timer_open() -> snd_timer_open(), + * snd_timer_start() + * [proc1] Call snd_timer_open() and overwrite running timer + * instance + */ + err = snd_timer_open(timeri, &cable->snd_timer.id, current->pid); + if (err < 0) { + pcm_err(dpcm->substream->pcm, + "snd_timer_open (%d,%d,%d) failed with %d", + cable->snd_timer.id.card, + cable->snd_timer.id.device, + cable->snd_timer.id.subdevice, + err); + snd_timer_instance_free(timeri); + goto exit; + } + + cable->snd_timer.instance = timeri; + +exit: + return err; +} + +/* stop_sync() is not required for sound timer because it does not need to be + * restarted in loopback_prepare() on Xrun recovery + */ +static const struct loopback_ops loopback_snd_timer_ops = { + .open = loopback_snd_timer_open, + .start = loopback_snd_timer_start, + .stop = loopback_snd_timer_stop, + .close_cable = loopback_snd_timer_close_cable, + .dpcm_info = loopback_snd_timer_dpcm_info, +}; + +static int loopback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct loopback *loopback = substream->private_data; + struct loopback_pcm *dpcm; + struct loopback_cable *cable = NULL; + int err = 0; + int dev = get_cable_index(substream); + + mutex_lock(&loopback->cable_lock); + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) { + err = -ENOMEM; + goto unlock; + } + dpcm->loopback = loopback; + dpcm->substream = substream; + + cable = loopback->cables[substream->number][dev]; + if (!cable) { + cable = kzalloc(sizeof(*cable), GFP_KERNEL); + if (!cable) { + err = -ENOMEM; + goto unlock; + } + spin_lock_init(&cable->lock); + cable->hw = loopback_pcm_hardware; + if (loopback->timer_source) + cable->ops = &loopback_snd_timer_ops; + else + cable->ops = &loopback_jiffies_timer_ops; + loopback->cables[substream->number][dev] = cable; + } + dpcm->cable = cable; + runtime->private_data = dpcm; + + if (cable->ops->open) { + err = cable->ops->open(dpcm); + if (err < 0) + goto unlock; + } + + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + + /* use dynamic rules based on actual runtime->hw values */ + /* note that the default rules created in the PCM midlevel code */ + /* are cached -> they do not reflect the actual state */ + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_FORMAT, + rule_format, dpcm, + SNDRV_PCM_HW_PARAM_FORMAT, -1); + if (err < 0) + goto unlock; + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + rule_rate, dpcm, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto unlock; + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + rule_channels, dpcm, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto unlock; + + /* In case of sound timer the period time of both devices of the same + * loop has to be the same. + * This rule only takes effect if a sound timer was chosen + */ + if (cable->snd_timer.instance) { + err = snd_pcm_hw_rule_add(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + rule_period_bytes, dpcm, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, -1); + if (err < 0) + goto unlock; + } + + /* loopback_runtime_free() has not to be called if kfree(dpcm) was + * already called here. Otherwise it will end up with a double free. + */ + runtime->private_free = loopback_runtime_free; + if (get_notify(dpcm)) + runtime->hw = loopback_pcm_hardware; + else + runtime->hw = cable->hw; + + spin_lock_irq(&cable->lock); + cable->streams[substream->stream] = dpcm; + spin_unlock_irq(&cable->lock); + + unlock: + if (err < 0) { + free_cable(substream); + kfree(dpcm); + } + mutex_unlock(&loopback->cable_lock); + return err; +} + +static int loopback_close(struct snd_pcm_substream *substream) +{ + struct loopback *loopback = substream->private_data; + struct loopback_pcm *dpcm = substream->runtime->private_data; + int err = 0; + + if (dpcm->cable->ops->close_substream) + err = dpcm->cable->ops->close_substream(dpcm); + mutex_lock(&loopback->cable_lock); + free_cable(substream); + mutex_unlock(&loopback->cable_lock); + return err; +} + +static const struct snd_pcm_ops loopback_pcm_ops = { + .open = loopback_open, + .close = loopback_close, + .hw_free = loopback_hw_free, + .prepare = loopback_prepare, + .trigger = loopback_trigger, + .pointer = loopback_pointer, +}; + +static int loopback_pcm_new(struct loopback *loopback, + int device, int substreams) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(loopback->card, "Loopback PCM", device, + substreams, substreams, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &loopback_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &loopback_pcm_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + pcm->private_data = loopback; + pcm->info_flags = 0; + strcpy(pcm->name, "Loopback PCM"); + + loopback->pcm[device] = pcm; + return 0; +} + +static int loopback_rate_shift_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 80000; + uinfo->value.integer.max = 120000; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_rate_shift_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate_shift; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static int loopback_rate_shift_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + val = ucontrol->value.integer.value[0]; + if (val < 80000) + val = 80000; + if (val > 120000) + val = 120000; + mutex_lock(&loopback->cable_lock); + if (val != loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate_shift) { + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate_shift = val; + change = 1; + } + mutex_unlock(&loopback->cable_lock); + return change; +} + +static int loopback_notify_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].notify; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static int loopback_notify_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + unsigned int val; + int change = 0; + + val = ucontrol->value.integer.value[0] ? 1 : 0; + mutex_lock(&loopback->cable_lock); + if (val != loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].notify) { + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].notify = val; + change = 1; + } + mutex_unlock(&loopback->cable_lock); + return change; +} + +static int loopback_active_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + struct loopback_cable *cable; + + unsigned int val = 0; + + mutex_lock(&loopback->cable_lock); + cable = loopback->cables[kcontrol->id.subdevice][kcontrol->id.device ^ 1]; + if (cable != NULL) { + unsigned int running = cable->running ^ cable->pause; + + val = (running & (1 << SNDRV_PCM_STREAM_PLAYBACK)) ? 1 : 0; + } + mutex_unlock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int loopback_format_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = (__force int)SNDRV_PCM_FORMAT_LAST; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_format_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = + (__force int)loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].format; + return 0; +} + +static int loopback_rate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 192000; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_rate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static int loopback_channels_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 1024; + uinfo->value.integer.step = 1; + return 0; +} + +static int loopback_channels_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + mutex_lock(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].channels; + mutex_unlock(&loopback->cable_lock); + return 0; +} + +static const struct snd_kcontrol_new loopback_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Rate Shift 100000", + .info = loopback_rate_shift_info, + .get = loopback_rate_shift_get, + .put = loopback_rate_shift_put, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Notify", + .info = snd_ctl_boolean_mono_info, + .get = loopback_notify_get, + .put = loopback_notify_put, +}, +#define ACTIVE_IDX 2 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Active", + .info = snd_ctl_boolean_mono_info, + .get = loopback_active_get, +}, +#define FORMAT_IDX 3 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Format", + .info = loopback_format_info, + .get = loopback_format_get +}, +#define RATE_IDX 4 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Rate", + .info = loopback_rate_info, + .get = loopback_rate_get +}, +#define CHANNELS_IDX 5 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Channels", + .info = loopback_channels_info, + .get = loopback_channels_get +} +}; + +static int loopback_mixer_new(struct loopback *loopback, int notify) +{ + struct snd_card *card = loopback->card; + struct snd_pcm *pcm; + struct snd_kcontrol *kctl; + struct loopback_setup *setup; + int err, dev, substr, substr_count, idx; + + strcpy(card->mixername, "Loopback Mixer"); + for (dev = 0; dev < 2; dev++) { + pcm = loopback->pcm[dev]; + substr_count = + pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream_count; + for (substr = 0; substr < substr_count; substr++) { + setup = &loopback->setup[substr][dev]; + setup->notify = notify; + setup->rate_shift = NO_PITCH; + setup->format = SNDRV_PCM_FORMAT_S16_LE; + setup->rate = 48000; + setup->channels = 2; + for (idx = 0; idx < ARRAY_SIZE(loopback_controls); + idx++) { + kctl = snd_ctl_new1(&loopback_controls[idx], + loopback); + if (!kctl) + return -ENOMEM; + kctl->id.device = dev; + kctl->id.subdevice = substr; + + /* Add the control before copying the id so that + * the numid field of the id is set in the copy. + */ + err = snd_ctl_add(card, kctl); + if (err < 0) + return err; + + switch (idx) { + case ACTIVE_IDX: + setup->active_id = kctl->id; + break; + case FORMAT_IDX: + setup->format_id = kctl->id; + break; + case RATE_IDX: + setup->rate_id = kctl->id; + break; + case CHANNELS_IDX: + setup->channels_id = kctl->id; + break; + default: + break; + } + } + } + } + return 0; +} + +static void print_dpcm_info(struct snd_info_buffer *buffer, + struct loopback_pcm *dpcm, + const char *id) +{ + snd_iprintf(buffer, " %s\n", id); + if (dpcm == NULL) { + snd_iprintf(buffer, " inactive\n"); + return; + } + snd_iprintf(buffer, " buffer_size:\t%u\n", dpcm->pcm_buffer_size); + snd_iprintf(buffer, " buffer_pos:\t\t%u\n", dpcm->buf_pos); + snd_iprintf(buffer, " silent_size:\t%u\n", dpcm->silent_size); + snd_iprintf(buffer, " period_size:\t%u\n", dpcm->pcm_period_size); + snd_iprintf(buffer, " bytes_per_sec:\t%u\n", dpcm->pcm_bps); + snd_iprintf(buffer, " sample_align:\t%u\n", dpcm->pcm_salign); + snd_iprintf(buffer, " rate_shift:\t\t%u\n", dpcm->pcm_rate_shift); + if (dpcm->cable->ops->dpcm_info) + dpcm->cable->ops->dpcm_info(dpcm, buffer); +} + +static void print_substream_info(struct snd_info_buffer *buffer, + struct loopback *loopback, + int sub, + int num) +{ + struct loopback_cable *cable = loopback->cables[sub][num]; + + snd_iprintf(buffer, "Cable %i substream %i:\n", num, sub); + if (cable == NULL) { + snd_iprintf(buffer, " inactive\n"); + return; + } + snd_iprintf(buffer, " valid: %u\n", cable->valid); + snd_iprintf(buffer, " running: %u\n", cable->running); + snd_iprintf(buffer, " pause: %u\n", cable->pause); + print_dpcm_info(buffer, cable->streams[0], "Playback"); + print_dpcm_info(buffer, cable->streams[1], "Capture"); +} + +static void print_cable_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct loopback *loopback = entry->private_data; + int sub, num; + + mutex_lock(&loopback->cable_lock); + num = entry->name[strlen(entry->name)-1]; + num = num == '0' ? 0 : 1; + for (sub = 0; sub < MAX_PCM_SUBSTREAMS; sub++) + print_substream_info(buffer, loopback, sub, num); + mutex_unlock(&loopback->cable_lock); +} + +static int loopback_cable_proc_new(struct loopback *loopback, int cidx) +{ + char name[32]; + + snprintf(name, sizeof(name), "cable#%d", cidx); + return snd_card_ro_proc_new(loopback->card, name, loopback, + print_cable_info); +} + +static void loopback_set_timer_source(struct loopback *loopback, + const char *value) +{ + if (loopback->timer_source) { + devm_kfree(loopback->card->dev, loopback->timer_source); + loopback->timer_source = NULL; + } + if (value && *value) + loopback->timer_source = devm_kstrdup(loopback->card->dev, + value, GFP_KERNEL); +} + +static void print_timer_source_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct loopback *loopback = entry->private_data; + + mutex_lock(&loopback->cable_lock); + snd_iprintf(buffer, "%s\n", + loopback->timer_source ? loopback->timer_source : ""); + mutex_unlock(&loopback->cable_lock); +} + +static void change_timer_source_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct loopback *loopback = entry->private_data; + char line[64]; + + mutex_lock(&loopback->cable_lock); + if (!snd_info_get_line(buffer, line, sizeof(line))) + loopback_set_timer_source(loopback, strim(line)); + mutex_unlock(&loopback->cable_lock); +} + +static int loopback_timer_source_proc_new(struct loopback *loopback) +{ + return snd_card_rw_proc_new(loopback->card, "timer_source", loopback, + print_timer_source_info, + change_timer_source_info); +} + +static int loopback_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct loopback *loopback; + int dev = devptr->id; + int err; + + err = snd_devm_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct loopback), &card); + if (err < 0) + return err; + loopback = card->private_data; + + if (pcm_substreams[dev] < 1) + pcm_substreams[dev] = 1; + if (pcm_substreams[dev] > MAX_PCM_SUBSTREAMS) + pcm_substreams[dev] = MAX_PCM_SUBSTREAMS; + + loopback->card = card; + loopback_set_timer_source(loopback, timer_source[dev]); + + mutex_init(&loopback->cable_lock); + + err = loopback_pcm_new(loopback, 0, pcm_substreams[dev]); + if (err < 0) + return err; + err = loopback_pcm_new(loopback, 1, pcm_substreams[dev]); + if (err < 0) + return err; + err = loopback_mixer_new(loopback, pcm_notify[dev] ? 1 : 0); + if (err < 0) + return err; + loopback_cable_proc_new(loopback, 0); + loopback_cable_proc_new(loopback, 1); + loopback_timer_source_proc_new(loopback); + strcpy(card->driver, "Loopback"); + strcpy(card->shortname, "Loopback"); + sprintf(card->longname, "Loopback %i", dev + 1); + err = snd_card_register(card); + if (err < 0) + return err; + platform_set_drvdata(devptr, card); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int loopback_suspend(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int loopback_resume(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(loopback_pm, loopback_suspend, loopback_resume); +#define LOOPBACK_PM_OPS &loopback_pm +#else +#define LOOPBACK_PM_OPS NULL +#endif + +#define SND_LOOPBACK_DRIVER "snd_aloop" + +static struct platform_driver loopback_driver = { + .probe = loopback_probe, + .driver = { + .name = SND_LOOPBACK_DRIVER, + .pm = LOOPBACK_PM_OPS, + }, +}; + +static void loopback_unregister_all(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(devices); ++i) + platform_device_unregister(devices[i]); + platform_driver_unregister(&loopback_driver); +} + +static int __init alsa_card_loopback_init(void) +{ + int i, err, cards; + + err = platform_driver_register(&loopback_driver); + if (err < 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_LOOPBACK_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 "aloop: No loopback enabled\n"); +#endif + loopback_unregister_all(); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_loopback_exit(void) +{ + loopback_unregister_all(); +} + +module_init(alsa_card_loopback_init) +module_exit(alsa_card_loopback_exit) diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c new file mode 100644 index 0000000000..4317677ba2 --- /dev/null +++ b/sound/drivers/dummy.c @@ -0,0 +1,1190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dummy soundcard + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +#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/hrtimer.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include <sound/pcm.h> +#include <sound/rawmidi.h> +#include <sound/info.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); +MODULE_DESCRIPTION("Dummy soundcard (/dev/null)"); +MODULE_LICENSE("GPL"); + +#define MAX_PCM_DEVICES 4 +#define MAX_PCM_SUBSTREAMS 128 +#define MAX_MIDI_DEVICES 2 + +/* defaults */ +#define MAX_BUFFER_SIZE (64*1024) +#define MIN_PERIOD_SIZE 64 +#define MAX_PERIOD_SIZE MAX_BUFFER_SIZE +#define USE_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE) +#define USE_RATE SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000 +#define USE_RATE_MIN 5500 +#define USE_RATE_MAX 48000 +#define USE_CHANNELS_MIN 1 +#define USE_CHANNELS_MAX 2 +#define USE_PERIODS_MIN 1 +#define USE_PERIODS_MAX 1024 +#define USE_MIXER_VOLUME_LEVEL_MIN -50 +#define USE_MIXER_VOLUME_LEVEL_MAX 100 + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0}; +static char *model[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = NULL}; +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}; +static int mixer_volume_level_min = USE_MIXER_VOLUME_LEVEL_MIN; +static int mixer_volume_level_max = USE_MIXER_VOLUME_LEVEL_MAX; +#ifdef CONFIG_HIGH_RES_TIMERS +static bool hrtimer = 1; +#endif +static bool fake_buffer = 1; + +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(model, charp, NULL, 0444); +MODULE_PARM_DESC(model, "Soundcard model."); +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-128) for dummy driver."); +//module_param_array(midi_devs, int, NULL, 0444); +//MODULE_PARM_DESC(midi_devs, "MIDI devices # (0-2) for dummy driver."); +module_param(mixer_volume_level_min, int, 0444); +MODULE_PARM_DESC(mixer_volume_level_min, "Minimum mixer volume level for dummy driver. Default: -50"); +module_param(mixer_volume_level_max, int, 0444); +MODULE_PARM_DESC(mixer_volume_level_max, "Maximum mixer volume level for dummy driver. Default: 100"); +module_param(fake_buffer, bool, 0444); +MODULE_PARM_DESC(fake_buffer, "Fake buffer allocations."); +#ifdef CONFIG_HIGH_RES_TIMERS +module_param(hrtimer, bool, 0644); +MODULE_PARM_DESC(hrtimer, "Use hrtimer as the timer source."); +#endif + +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 dummy_timer_ops { + int (*create)(struct snd_pcm_substream *); + void (*free)(struct snd_pcm_substream *); + int (*prepare)(struct snd_pcm_substream *); + int (*start)(struct snd_pcm_substream *); + int (*stop)(struct snd_pcm_substream *); + snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *); +}; + +#define get_dummy_ops(substream) \ + (*(const struct dummy_timer_ops **)(substream)->runtime->private_data) + +struct dummy_model { + const char *name; + int (*playback_constraints)(struct snd_pcm_runtime *runtime); + int (*capture_constraints)(struct snd_pcm_runtime *runtime); + u64 formats; + size_t buffer_bytes_max; + size_t period_bytes_min; + size_t period_bytes_max; + unsigned int periods_min; + unsigned int periods_max; + unsigned int rates; + unsigned int rate_min; + unsigned int rate_max; + unsigned int channels_min; + unsigned int channels_max; +}; + +struct snd_dummy { + struct snd_card *card; + const struct dummy_model *model; + struct snd_pcm *pcm; + struct snd_pcm_hardware pcm_hw; + spinlock_t mixer_lock; + int mixer_volume[MIXER_ADDR_LAST+1][2]; + int capture_source[MIXER_ADDR_LAST+1][2]; + int iobox; + struct snd_kcontrol *cd_volume_ctl; + struct snd_kcontrol *cd_switch_ctl; +}; + +/* + * card models + */ + +static int emu10k1_playback_constraints(struct snd_pcm_runtime *runtime) +{ + int err; + err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX); + if (err < 0) + return err; + return 0; +} + +static const struct dummy_model model_emu10k1 = { + .name = "emu10k1", + .playback_constraints = emu10k1_playback_constraints, + .buffer_bytes_max = 128 * 1024, +}; + +static const struct dummy_model model_rme9652 = { + .name = "rme9652", + .buffer_bytes_max = 26 * 64 * 1024, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 26, + .channels_max = 26, + .periods_min = 2, + .periods_max = 2, +}; + +static const struct dummy_model model_ice1712 = { + .name = "ice1712", + .buffer_bytes_max = 256 * 1024, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels_min = 10, + .channels_max = 10, + .periods_min = 1, + .periods_max = 1024, +}; + +static const struct dummy_model model_uda1341 = { + .name = "uda1341", + .buffer_bytes_max = 16380, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .periods_min = 2, + .periods_max = 255, +}; + +static const struct dummy_model model_ac97 = { + .name = "ac97", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, +}; + +static const struct dummy_model model_ca0106 = { + .name = "ca0106", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .buffer_bytes_max = ((65536-64)*8), + .period_bytes_max = (65536-64), + .periods_min = 2, + .periods_max = 8, + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_192000, + .rate_min = 48000, + .rate_max = 192000, +}; + +static const struct dummy_model *dummy_models[] = { + &model_emu10k1, + &model_rme9652, + &model_ice1712, + &model_uda1341, + &model_ac97, + &model_ca0106, + NULL +}; + +/* + * system timer interface + */ + +struct dummy_systimer_pcm { + /* ops must be the first item */ + const struct dummy_timer_ops *timer_ops; + spinlock_t lock; + struct timer_list timer; + unsigned long base_time; + unsigned int frac_pos; /* fractional sample position (based HZ) */ + unsigned int frac_period_rest; + unsigned int frac_buffer_size; /* buffer_size * HZ */ + unsigned int frac_period_size; /* period_size * HZ */ + unsigned int rate; + int elapsed; + struct snd_pcm_substream *substream; +}; + +static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm) +{ + mod_timer(&dpcm->timer, jiffies + + DIV_ROUND_UP(dpcm->frac_period_rest, dpcm->rate)); +} + +static void dummy_systimer_update(struct dummy_systimer_pcm *dpcm) +{ + unsigned long delta; + + delta = jiffies - dpcm->base_time; + if (!delta) + return; + dpcm->base_time += delta; + delta *= dpcm->rate; + dpcm->frac_pos += delta; + while (dpcm->frac_pos >= dpcm->frac_buffer_size) + dpcm->frac_pos -= dpcm->frac_buffer_size; + while (dpcm->frac_period_rest <= delta) { + dpcm->elapsed++; + dpcm->frac_period_rest += dpcm->frac_period_size; + } + dpcm->frac_period_rest -= delta; +} + +static int dummy_systimer_start(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); + dpcm->base_time = jiffies; + dummy_systimer_rearm(dpcm); + spin_unlock(&dpcm->lock); + return 0; +} + +static int dummy_systimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + spin_lock(&dpcm->lock); + del_timer(&dpcm->timer); + spin_unlock(&dpcm->lock); + return 0; +} + +static int dummy_systimer_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_systimer_pcm *dpcm = runtime->private_data; + + dpcm->frac_pos = 0; + dpcm->rate = runtime->rate; + dpcm->frac_buffer_size = runtime->buffer_size * HZ; + dpcm->frac_period_size = runtime->period_size * HZ; + dpcm->frac_period_rest = dpcm->frac_period_size; + dpcm->elapsed = 0; + + return 0; +} + +static void dummy_systimer_callback(struct timer_list *t) +{ + struct dummy_systimer_pcm *dpcm = from_timer(dpcm, t, timer); + unsigned long flags; + int elapsed = 0; + + spin_lock_irqsave(&dpcm->lock, flags); + dummy_systimer_update(dpcm); + dummy_systimer_rearm(dpcm); + elapsed = dpcm->elapsed; + dpcm->elapsed = 0; + spin_unlock_irqrestore(&dpcm->lock, flags); + if (elapsed) + snd_pcm_period_elapsed(dpcm->substream); +} + +static snd_pcm_uframes_t +dummy_systimer_pointer(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + snd_pcm_uframes_t pos; + + spin_lock(&dpcm->lock); + dummy_systimer_update(dpcm); + pos = dpcm->frac_pos / HZ; + spin_unlock(&dpcm->lock); + return pos; +} + +static int dummy_systimer_create(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + timer_setup(&dpcm->timer, dummy_systimer_callback, 0); + spin_lock_init(&dpcm->lock); + dpcm->substream = substream; + return 0; +} + +static void dummy_systimer_free(struct snd_pcm_substream *substream) +{ + kfree(substream->runtime->private_data); +} + +static const struct dummy_timer_ops dummy_systimer_ops = { + .create = dummy_systimer_create, + .free = dummy_systimer_free, + .prepare = dummy_systimer_prepare, + .start = dummy_systimer_start, + .stop = dummy_systimer_stop, + .pointer = dummy_systimer_pointer, +}; + +#ifdef CONFIG_HIGH_RES_TIMERS +/* + * hrtimer interface + */ + +struct dummy_hrtimer_pcm { + /* ops must be the first item */ + const struct dummy_timer_ops *timer_ops; + ktime_t base_time; + ktime_t period_time; + atomic_t running; + struct hrtimer timer; + struct snd_pcm_substream *substream; +}; + +static enum hrtimer_restart dummy_hrtimer_callback(struct hrtimer *timer) +{ + struct dummy_hrtimer_pcm *dpcm; + + dpcm = container_of(timer, struct dummy_hrtimer_pcm, timer); + if (!atomic_read(&dpcm->running)) + return HRTIMER_NORESTART; + /* + * In cases of XRUN and draining, this calls .trigger to stop PCM + * substream. + */ + snd_pcm_period_elapsed(dpcm->substream); + if (!atomic_read(&dpcm->running)) + return HRTIMER_NORESTART; + + hrtimer_forward_now(timer, dpcm->period_time); + return HRTIMER_RESTART; +} + +static int dummy_hrtimer_start(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + + dpcm->base_time = hrtimer_cb_get_time(&dpcm->timer); + hrtimer_start(&dpcm->timer, dpcm->period_time, HRTIMER_MODE_REL_SOFT); + atomic_set(&dpcm->running, 1); + return 0; +} + +static int dummy_hrtimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + + atomic_set(&dpcm->running, 0); + if (!hrtimer_callback_running(&dpcm->timer)) + hrtimer_cancel(&dpcm->timer); + return 0; +} + +static inline void dummy_hrtimer_sync(struct dummy_hrtimer_pcm *dpcm) +{ + hrtimer_cancel(&dpcm->timer); +} + +static snd_pcm_uframes_t +dummy_hrtimer_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_hrtimer_pcm *dpcm = runtime->private_data; + u64 delta; + u32 pos; + + delta = ktime_us_delta(hrtimer_cb_get_time(&dpcm->timer), + dpcm->base_time); + delta = div_u64(delta * runtime->rate + 999999, 1000000); + div_u64_rem(delta, runtime->buffer_size, &pos); + return pos; +} + +static int dummy_hrtimer_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dummy_hrtimer_pcm *dpcm = runtime->private_data; + unsigned int period, rate; + long sec; + unsigned long nsecs; + + dummy_hrtimer_sync(dpcm); + period = runtime->period_size; + rate = runtime->rate; + sec = period / rate; + period %= rate; + nsecs = div_u64((u64)period * 1000000000UL + rate - 1, rate); + dpcm->period_time = ktime_set(sec, nsecs); + + return 0; +} + +static int dummy_hrtimer_create(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm; + + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + hrtimer_init(&dpcm->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + dpcm->timer.function = dummy_hrtimer_callback; + dpcm->substream = substream; + atomic_set(&dpcm->running, 0); + return 0; +} + +static void dummy_hrtimer_free(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + dummy_hrtimer_sync(dpcm); + kfree(dpcm); +} + +static const struct dummy_timer_ops dummy_hrtimer_ops = { + .create = dummy_hrtimer_create, + .free = dummy_hrtimer_free, + .prepare = dummy_hrtimer_prepare, + .start = dummy_hrtimer_start, + .stop = dummy_hrtimer_stop, + .pointer = dummy_hrtimer_pointer, +}; + +#endif /* CONFIG_HIGH_RES_TIMERS */ + +/* + * PCM interface + */ + +static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return get_dummy_ops(substream)->start(substream); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + return get_dummy_ops(substream)->stop(substream); + } + return -EINVAL; +} + +static int dummy_pcm_prepare(struct snd_pcm_substream *substream) +{ + return get_dummy_ops(substream)->prepare(substream); +} + +static snd_pcm_uframes_t dummy_pcm_pointer(struct snd_pcm_substream *substream) +{ + return get_dummy_ops(substream)->pointer(substream); +} + +static const struct snd_pcm_hardware dummy_pcm_hardware = { + .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 = MIN_PERIOD_SIZE, + .period_bytes_max = MAX_PERIOD_SIZE, + .periods_min = USE_PERIODS_MIN, + .periods_max = USE_PERIODS_MAX, + .fifo_size = 0, +}; + +static int dummy_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + if (fake_buffer) { + /* runtime->dma_bytes has to be set manually to allow mmap */ + substream->runtime->dma_bytes = params_buffer_bytes(hw_params); + return 0; + } + return 0; +} + +static int dummy_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_dummy *dummy = snd_pcm_substream_chip(substream); + const struct dummy_model *model = dummy->model; + struct snd_pcm_runtime *runtime = substream->runtime; + const struct dummy_timer_ops *ops; + int err; + + ops = &dummy_systimer_ops; +#ifdef CONFIG_HIGH_RES_TIMERS + if (hrtimer) + ops = &dummy_hrtimer_ops; +#endif + + err = ops->create(substream); + if (err < 0) + return err; + get_dummy_ops(substream) = ops; + + runtime->hw = dummy->pcm_hw; + 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 (model == NULL) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (model->playback_constraints) + err = model->playback_constraints(substream->runtime); + } else { + if (model->capture_constraints) + err = model->capture_constraints(substream->runtime); + } + if (err < 0) { + get_dummy_ops(substream)->free(substream); + return err; + } + return 0; +} + +static int dummy_pcm_close(struct snd_pcm_substream *substream) +{ + get_dummy_ops(substream)->free(substream); + return 0; +} + +/* + * dummy buffer handling + */ + +static void *dummy_page[2]; + +static void free_fake_buffer(void) +{ + if (fake_buffer) { + int i; + for (i = 0; i < 2; i++) + if (dummy_page[i]) { + free_page((unsigned long)dummy_page[i]); + dummy_page[i] = NULL; + } + } +} + +static int alloc_fake_buffer(void) +{ + int i; + + if (!fake_buffer) + return 0; + for (i = 0; i < 2; i++) { + dummy_page[i] = (void *)get_zeroed_page(GFP_KERNEL); + if (!dummy_page[i]) { + free_fake_buffer(); + return -ENOMEM; + } + } + return 0; +} + +static int dummy_pcm_copy(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + struct iov_iter *iter, unsigned long bytes) +{ + return 0; /* do nothing */ +} + +static int dummy_pcm_silence(struct snd_pcm_substream *substream, + int channel, unsigned long pos, + unsigned long bytes) +{ + return 0; /* do nothing */ +} + +static struct page *dummy_pcm_page(struct snd_pcm_substream *substream, + unsigned long offset) +{ + return virt_to_page(dummy_page[substream->stream]); /* the same page */ +} + +static const struct snd_pcm_ops dummy_pcm_ops = { + .open = dummy_pcm_open, + .close = dummy_pcm_close, + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, + .pointer = dummy_pcm_pointer, +}; + +static const struct snd_pcm_ops dummy_pcm_ops_no_buf = { + .open = dummy_pcm_open, + .close = dummy_pcm_close, + .hw_params = dummy_pcm_hw_params, + .prepare = dummy_pcm_prepare, + .trigger = dummy_pcm_trigger, + .pointer = dummy_pcm_pointer, + .copy = dummy_pcm_copy, + .fill_silence = dummy_pcm_silence, + .page = dummy_pcm_page, +}; + +static int snd_card_dummy_pcm(struct snd_dummy *dummy, int device, + int substreams) +{ + struct snd_pcm *pcm; + const struct snd_pcm_ops *ops; + int err; + + err = snd_pcm_new(dummy->card, "Dummy PCM", device, + substreams, substreams, &pcm); + if (err < 0) + return err; + dummy->pcm = pcm; + if (fake_buffer) + ops = &dummy_pcm_ops_no_buf; + else + ops = &dummy_pcm_ops; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops); + pcm->private_data = dummy; + pcm->info_flags = 0; + strcpy(pcm->name, "Dummy PCM"); + if (!fake_buffer) { + snd_pcm_set_managed_buffer_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 0, 64*1024); + } + return 0; +} + +/* + * mixer interface + */ + +#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 = mixer_volume_level_min; + uinfo->value.integer.max = mixer_volume_level_max; + 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 < mixer_volume_level_min) + left = mixer_volume_level_min; + if (left > mixer_volume_level_max) + left = mixer_volume_level_max; + right = ucontrol->value.integer.value[1]; + if (right < mixer_volume_level_min) + right = mixer_volume_level_min; + if (right > mixer_volume_level_max) + right = mixer_volume_level_max; + 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 } + +#define snd_dummy_capsrc_info snd_ctl_boolean_stereo_info + +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 int snd_dummy_iobox_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *info) +{ + static const char *const names[] = { "None", "CD Player" }; + + return snd_ctl_enum_info(info, 1, 2, names); +} + +static int snd_dummy_iobox_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + + value->value.enumerated.item[0] = dummy->iobox; + return 0; +} + +static int snd_dummy_iobox_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *value) +{ + struct snd_dummy *dummy = snd_kcontrol_chip(kcontrol); + int changed; + + if (value->value.enumerated.item[0] > 1) + return -EINVAL; + + changed = value->value.enumerated.item[0] != dummy->iobox; + if (changed) { + dummy->iobox = value->value.enumerated.item[0]; + + if (dummy->iobox) { + dummy->cd_volume_ctl->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + dummy->cd_switch_ctl->vd[0].access &= + ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } else { + dummy->cd_volume_ctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_INACTIVE; + dummy->cd_switch_ctl->vd[0].access |= + SNDRV_CTL_ELEM_ACCESS_INACTIVE; + } + + snd_ctl_notify(dummy->card, SNDRV_CTL_EVENT_MASK_INFO, + &dummy->cd_volume_ctl->id); + snd_ctl_notify(dummy->card, SNDRV_CTL_EVENT_MASK_INFO, + &dummy->cd_switch_ctl->id); + } + + return changed; +} + +static const 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), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "External I/O Box", + .info = snd_dummy_iobox_info, + .get = snd_dummy_iobox_get, + .put = snd_dummy_iobox_put, +}, +}; + +static int snd_card_dummy_new_mixer(struct snd_dummy *dummy) +{ + struct snd_card *card = dummy->card; + struct snd_kcontrol *kcontrol; + unsigned int idx; + int err; + + spin_lock_init(&dummy->mixer_lock); + strcpy(card->mixername, "Dummy Mixer"); + dummy->iobox = 1; + + for (idx = 0; idx < ARRAY_SIZE(snd_dummy_controls); idx++) { + kcontrol = snd_ctl_new1(&snd_dummy_controls[idx], dummy); + err = snd_ctl_add(card, kcontrol); + if (err < 0) + return err; + if (!strcmp(kcontrol->id.name, "CD Volume")) + dummy->cd_volume_ctl = kcontrol; + else if (!strcmp(kcontrol->id.name, "CD Capture Switch")) + dummy->cd_switch_ctl = kcontrol; + + } + return 0; +} + +#if defined(CONFIG_SND_DEBUG) && defined(CONFIG_SND_PROC_FS) +/* + * proc interface + */ +static void print_formats(struct snd_dummy *dummy, + struct snd_info_buffer *buffer) +{ + snd_pcm_format_t i; + + pcm_for_each_format(i) { + if (dummy->pcm_hw.formats & pcm_format_to_bits(i)) + snd_iprintf(buffer, " %s", snd_pcm_format_name(i)); + } +} + +static void print_rates(struct snd_dummy *dummy, + struct snd_info_buffer *buffer) +{ + static const int rates[] = { + 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, + 64000, 88200, 96000, 176400, 192000, + }; + int i; + + if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_CONTINUOUS) + snd_iprintf(buffer, " continuous"); + if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_KNOT) + snd_iprintf(buffer, " knot"); + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (dummy->pcm_hw.rates & (1 << i)) + snd_iprintf(buffer, " %d", rates[i]); +} + +#define get_dummy_int_ptr(dummy, ofs) \ + (unsigned int *)((char *)&((dummy)->pcm_hw) + (ofs)) +#define get_dummy_ll_ptr(dummy, ofs) \ + (unsigned long long *)((char *)&((dummy)->pcm_hw) + (ofs)) + +struct dummy_hw_field { + const char *name; + const char *format; + unsigned int offset; + unsigned int size; +}; +#define FIELD_ENTRY(item, fmt) { \ + .name = #item, \ + .format = fmt, \ + .offset = offsetof(struct snd_pcm_hardware, item), \ + .size = sizeof(dummy_pcm_hardware.item) } + +static const struct dummy_hw_field fields[] = { + FIELD_ENTRY(formats, "%#llx"), + FIELD_ENTRY(rates, "%#x"), + FIELD_ENTRY(rate_min, "%d"), + FIELD_ENTRY(rate_max, "%d"), + FIELD_ENTRY(channels_min, "%d"), + FIELD_ENTRY(channels_max, "%d"), + FIELD_ENTRY(buffer_bytes_max, "%ld"), + FIELD_ENTRY(period_bytes_min, "%ld"), + FIELD_ENTRY(period_bytes_max, "%ld"), + FIELD_ENTRY(periods_min, "%d"), + FIELD_ENTRY(periods_max, "%d"), +}; + +static void dummy_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_dummy *dummy = entry->private_data; + int i; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + snd_iprintf(buffer, "%s ", fields[i].name); + if (fields[i].size == sizeof(int)) + snd_iprintf(buffer, fields[i].format, + *get_dummy_int_ptr(dummy, fields[i].offset)); + else + snd_iprintf(buffer, fields[i].format, + *get_dummy_ll_ptr(dummy, fields[i].offset)); + if (!strcmp(fields[i].name, "formats")) + print_formats(dummy, buffer); + else if (!strcmp(fields[i].name, "rates")) + print_rates(dummy, buffer); + snd_iprintf(buffer, "\n"); + } +} + +static void dummy_proc_write(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_dummy *dummy = entry->private_data; + char line[64]; + + while (!snd_info_get_line(buffer, line, sizeof(line))) { + char item[20]; + const char *ptr; + unsigned long long val; + int i; + + ptr = snd_info_get_str(item, line, sizeof(item)); + for (i = 0; i < ARRAY_SIZE(fields); i++) { + if (!strcmp(item, fields[i].name)) + break; + } + if (i >= ARRAY_SIZE(fields)) + continue; + snd_info_get_str(item, ptr, sizeof(item)); + if (kstrtoull(item, 0, &val)) + continue; + if (fields[i].size == sizeof(int)) + *get_dummy_int_ptr(dummy, fields[i].offset) = val; + else + *get_dummy_ll_ptr(dummy, fields[i].offset) = val; + } +} + +static void dummy_proc_init(struct snd_dummy *chip) +{ + snd_card_rw_proc_new(chip->card, "dummy_pcm", chip, + dummy_proc_read, dummy_proc_write); +} +#else +#define dummy_proc_init(x) +#endif /* CONFIG_SND_DEBUG && CONFIG_SND_PROC_FS */ + +static int snd_dummy_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_dummy *dummy; + const struct dummy_model *m = NULL, **mdl; + int idx, err; + int dev = devptr->id; + + err = snd_devm_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_dummy), &card); + if (err < 0) + return err; + dummy = card->private_data; + dummy->card = card; + for (mdl = dummy_models; *mdl && model[dev]; mdl++) { + if (strcmp(model[dev], (*mdl)->name) == 0) { + printk(KERN_INFO + "snd-dummy: Using model '%s' for card %i\n", + (*mdl)->name, card->number); + m = dummy->model = *mdl; + break; + } + } + 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; + err = snd_card_dummy_pcm(dummy, idx, pcm_substreams[dev]); + if (err < 0) + return err; + } + + dummy->pcm_hw = dummy_pcm_hardware; + if (m) { + if (m->formats) + dummy->pcm_hw.formats = m->formats; + if (m->buffer_bytes_max) + dummy->pcm_hw.buffer_bytes_max = m->buffer_bytes_max; + if (m->period_bytes_min) + dummy->pcm_hw.period_bytes_min = m->period_bytes_min; + if (m->period_bytes_max) + dummy->pcm_hw.period_bytes_max = m->period_bytes_max; + if (m->periods_min) + dummy->pcm_hw.periods_min = m->periods_min; + if (m->periods_max) + dummy->pcm_hw.periods_max = m->periods_max; + if (m->rates) + dummy->pcm_hw.rates = m->rates; + if (m->rate_min) + dummy->pcm_hw.rate_min = m->rate_min; + if (m->rate_max) + dummy->pcm_hw.rate_max = m->rate_max; + if (m->channels_min) + dummy->pcm_hw.channels_min = m->channels_min; + if (m->channels_max) + dummy->pcm_hw.channels_max = m->channels_max; + } + + if (mixer_volume_level_min > mixer_volume_level_max) { + pr_warn("snd-dummy: Invalid mixer volume level: min=%d, max=%d. Fall back to default value.\n", + mixer_volume_level_min, mixer_volume_level_max); + mixer_volume_level_min = USE_MIXER_VOLUME_LEVEL_MIN; + mixer_volume_level_max = USE_MIXER_VOLUME_LEVEL_MAX; + } + err = snd_card_dummy_new_mixer(dummy); + if (err < 0) + return err; + strcpy(card->driver, "Dummy"); + strcpy(card->shortname, "Dummy"); + sprintf(card->longname, "Dummy %i", dev + 1); + + dummy_proc_init(dummy); + + err = snd_card_register(card); + if (err < 0) + return err; + platform_set_drvdata(devptr, card); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int snd_dummy_suspend(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int snd_dummy_resume(struct device *pdev) +{ + struct snd_card *card = dev_get_drvdata(pdev); + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(snd_dummy_pm, snd_dummy_suspend, snd_dummy_resume); +#define SND_DUMMY_PM_OPS &snd_dummy_pm +#else +#define SND_DUMMY_PM_OPS NULL +#endif + +#define SND_DUMMY_DRIVER "snd_dummy" + +static struct platform_driver snd_dummy_driver = { + .probe = snd_dummy_probe, + .driver = { + .name = SND_DUMMY_DRIVER, + .pm = SND_DUMMY_PM_OPS, + }, +}; + +static void 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); + free_fake_buffer(); +} + +static int __init alsa_card_dummy_init(void) +{ + int i, cards, err; + + err = platform_driver_register(&snd_dummy_driver); + if (err < 0) + return err; + + err = alloc_fake_buffer(); + if (err < 0) { + platform_driver_unregister(&snd_dummy_driver); + 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) diff --git a/sound/drivers/mpu401/Makefile b/sound/drivers/mpu401/Makefile new file mode 100644 index 0000000000..3dfd5b374c --- /dev/null +++ b/sound/drivers/mpu401/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.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 diff --git a/sound/drivers/mpu401/mpu401.c b/sound/drivers/mpu401/mpu401.c new file mode 100644 index 0000000000..3398aee33b --- /dev/null +++ b/sound/drivers/mpu401/mpu401.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for generic MPU-401 boards (UART mode only) + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + * Copyright (c) 2004 by Castet Matthieu <castet.matthieu@free.fr> + */ + +#include <linux/init.h> +#include <linux/pnp.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/mpu401.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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 bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +#ifdef CONFIG_PNP +static bool 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 */ +static bool uart_enter[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; + +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_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for MPU-401 device."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for MPU-401 device."); +module_param_array(uart_enter, bool, NULL, 0444); +MODULE_PARM_DESC(uart_enter, "Issue UART_ENTER command at open."); + +static struct platform_device *platform_devices[SNDRV_CARDS]; +static int pnp_registered; +static unsigned int snd_mpu401_devices; + +static int snd_mpu401_create(struct device *devptr, int dev, + struct snd_card **rcard) +{ + struct snd_card *card; + int err; + + if (!uart_enter[dev]) + snd_printk(KERN_ERR "the uart_enter option is obsolete; remove it\n"); + + *rcard = NULL; + err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) + return err; + 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"); + } + + err = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, port[dev], 0, + irq[dev], NULL); + if (err < 0) { + printk(KERN_ERR "MPU401 not detected at 0x%lx\n", port[dev]); + return err; + } + + *rcard = card; + return 0; +} + +static int 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(&devptr->dev, dev, &card); + if (err < 0) + return err; + err = snd_card_register(card); + if (err < 0) + return err; + platform_set_drvdata(devptr, card); + return 0; +} + +#define SND_MPU401_DRIVER "snd_mpu401" + +static struct platform_driver snd_mpu401_driver = { + .probe = snd_mpu401_probe, + .driver = { + .name = SND_MPU401_DRIVER, + }, +}; + + +#ifdef CONFIG_PNP + +#define IO_EXTENT 2 + +static const struct pnp_device_id snd_mpu401_pnpids[] = { + { .id = "PNPb006" }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp, snd_mpu401_pnpids); + +static int 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 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(&pnp_dev->dev, dev, &card); + if (err < 0) + return err; + err = snd_card_register(card); + if (err < 0) + return err; + pnp_set_drvdata(pnp_dev, card); + snd_mpu401_devices++; + ++dev; + return 0; + } + return -ENODEV; +} + +static struct pnp_driver snd_mpu401_pnp_driver = { + .name = "mpu401", + .id_table = snd_mpu401_pnpids, + .probe = snd_mpu401_pnp_probe, +}; +#else +static struct pnp_driver snd_mpu401_pnp_driver; +#endif + +static void 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; + + err = platform_driver_register(&snd_mpu401_driver); + if (err < 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) diff --git a/sound/drivers/mpu401/mpu401_uart.c b/sound/drivers/mpu401/mpu401_uart.c new file mode 100644 index 0000000000..f435b9b4ae --- /dev/null +++ b/sound/drivers/mpu401/mpu401_uart.c @@ -0,0 +1,611 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.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. Without interrupt, + * input is done also via polling. Do not expect good performance. + * + * 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 <linux/io.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/errno.h> +#include <sound/core.h> +#include <sound/mpu401.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.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)) & MPU401_RX_EMPTY)) +#define snd_mpu401_output_ready(mpu) \ + (!(mpu->read(mpu, MPU401C(mpu)) & MPU401_TX_FULL)) + +/* 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) +{ + unsigned long flags; + + if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) && + test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) { + spin_lock_irqsave(&mpu->output_lock, flags); + snd_mpu401_uart_output_write(mpu); + spin_unlock_irqrestore(&mpu->output_lock, flags); + } +} + +static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu) +{ + unsigned long flags; + + if (mpu->info_flags & MPU401_INFO_INPUT) { + spin_lock_irqsave(&mpu->input_lock, flags); + if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) + snd_mpu401_uart_input_read(mpu); + else + snd_mpu401_uart_clear_rx(mpu); + spin_unlock_irqrestore(&mpu->input_lock, flags); + } + 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. + * + * Return: %IRQ_HANDLED if the interrupt was handled. %IRQ_NONE otherwise. + */ +irqreturn_t snd_mpu401_uart_interrupt(int irq, void *dev_id) +{ + struct snd_mpu401 *mpu = dev_id; + + if (!mpu) + 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. + * + * Return: %IRQ_HANDLED if the interrupt was handled. %IRQ_NONE otherwise. + */ +irqreturn_t snd_mpu401_uart_interrupt_tx(int irq, void *dev_id) +{ + struct snd_mpu401 *mpu = dev_id; + + if (!mpu) + 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(struct timer_list *t) +{ + struct snd_mpu401 *mpu = from_timer(mpu, t, timer); + unsigned long flags; + + spin_lock_irqsave(&mpu->timer_lock, flags); + /*mpu->mode |= MPU401_MODE_TIMER;*/ + mod_timer(&mpu->timer, 1 + jiffies); + 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) { + timer_setup(&mpu->timer, snd_mpu401_uart_timer, 0); + mod_timer(&mpu->timer, 1 + jiffies); + } + 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 && !(mpu->info_flags & MPU401_INFO_NO_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; +} + +static int snd_mpu401_do_reset(struct snd_mpu401 *mpu) +{ + if (snd_mpu401_uart_cmd(mpu, MPU401_RESET, 1)) + return -EIO; + if (snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 0)) + return -EIO; + 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); + if (err < 0) + return err; + } + if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode)) { + if (snd_mpu401_do_reset(mpu) < 0) + 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); + if (err < 0) + return err; + } + if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) { + if (snd_mpu401_do_reset(mpu) < 0) + 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->info_flags & MPU401_INFO_USE_TIMER) + 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->info_flags & MPU401_INFO_USE_TIMER) + 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; + + do { + if (snd_rawmidi_transmit_peek(mpu->substream_output, + &byte, 1) == 1) { + /* + * Try twice because there is hardware that insists on + * setting the output busy bit after each write. + */ + if (!snd_mpu401_output_ready(mpu) && + !snd_mpu401_output_ready(mpu)) + 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 const 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 const 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 >= 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 ISA irq number, -1 if not to be allocated + * @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). + * + * Return: 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, + 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; + err = snd_rawmidi_new(card, "MPU-401U", device, + out_enable, in_enable, &rmidi); + if (err < 0) + return err; + mpu = kzalloc(sizeof(*mpu), GFP_KERNEL); + if (!mpu) { + err = -ENOMEM; + goto free_device; + } + 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; + mpu->irq = -1; + 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) { + snd_printk(KERN_ERR "mpu401_uart: " + "unable to grab port 0x%lx size %d\n", + port, res_size); + err = -EBUSY; + goto free_device; + } + } + 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) { + if (request_irq(irq, snd_mpu401_uart_interrupt, 0, + "MPU401 UART", (void *) mpu)) { + snd_printk(KERN_ERR "mpu401_uart: " + "unable to grab IRQ %d\n", irq); + err = -EBUSY; + goto free_device; + } + } + if (irq < 0 && !(info_flags & MPU401_INFO_IRQ_HOOK)) + info_flags |= MPU401_INFO_USE_TIMER; + mpu->info_flags = info_flags; + mpu->irq = irq; + 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; +free_device: + snd_device_free(card, rmidi); + return err; +} + +EXPORT_SYMBOL(snd_mpu401_uart_new); diff --git a/sound/drivers/mtpav.c b/sound/drivers/mtpav.c new file mode 100644 index 0000000000..f212f233ea --- /dev/null +++ b/sound/drivers/mtpav.c @@ -0,0 +1,760 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MOTU Midi Timepiece ALSA Main routines + * Copyright by Michael T. Mayers (c) Jan 09, 2000 + * mail: michael@tweakoz.com + * Thanks to John Galbraith + * + * 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 <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> +#include <linux/delay.h> + +/* + * globals + */ +MODULE_AUTHOR("Michael T. Mayers"); +MODULE_DESCRIPTION("MOTU MidiTimePiece AV multiport MIDI"); +MODULE_LICENSE("GPL"); + +// 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_hw(port, long, ioport, 0444); +MODULE_PARM_DESC(port, "Parallel port # for MotuMTPAV MIDI."); +module_param_hw(irq, int, irq, 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(KERN_DEBUG "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(struct timer_list *t) +{ + unsigned long flags; + struct mtpav *chip = from_timer(chip, t, timer); + int p; + + spin_lock_irqsave(&chip->spinlock, flags); + /* reprogram timer */ + mod_timer(&chip->timer, 1 + jiffies); + /* 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) +{ + mod_timer(&chip->timer, 1 + jiffies); +} + +/* 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(KERN_DEBUG "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 snd_mtpav_get_ISA(struct mtpav *mcard) +{ + mcard->res_port = devm_request_region(mcard->card->dev, port, 3, + "MotuMTPAV MIDI"); + if (!mcard->res_port) { + snd_printk(KERN_ERR "MTVAP port 0x%lx is busy\n", port); + return -EBUSY; + } + mcard->port = port; + if (devm_request_irq(mcard->card->dev, irq, snd_mtpav_irqh, 0, + "MOTU MTPAV", mcard)) { + snd_printk(KERN_ERR "MTVAP IRQ %d busy\n", irq); + return -EBUSY; + } + mcard->irq = irq; + return 0; +} + + +/* + */ + +static const struct snd_rawmidi_ops snd_mtpav_output = { + .open = snd_mtpav_output_open, + .close = snd_mtpav_output_close, + .trigger = snd_mtpav_output_trigger, +}; + +static const 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 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 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; + + 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); + if (rval < 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); +} + +/* + */ +static int snd_mtpav_probe(struct platform_device *dev) +{ + struct snd_card *card; + int err; + struct mtpav *mtp_card; + + err = snd_devm_card_new(&dev->dev, index, id, THIS_MODULE, + sizeof(*mtp_card), &card); + if (err < 0) + return err; + + mtp_card = card->private_data; + spin_lock_init(&mtp_card->spinlock); + mtp_card->card = card; + mtp_card->irq = -1; + mtp_card->share_irq = 0; + mtp_card->inmidistate = 0; + mtp_card->outmidihwport = 0xffffffff; + timer_setup(&mtp_card->timer, snd_mtpav_output_timer, 0); + + err = snd_mtpav_get_RAWMIDI(mtp_card); + if (err < 0) + return err; + + mtp_card->inmidiport = mtp_card->num_ports + MTPAV_PIDX_BROADCAST; + + err = snd_mtpav_get_ISA(mtp_card); + if (err < 0) + return err; + + 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); + + snd_mtpav_portscan(mtp_card); + + err = snd_card_register(mtp_card->card); + if (err < 0) + return err; + + card->private_free = snd_mtpav_free; + + platform_set_drvdata(dev, card); + printk(KERN_INFO "Motu MidiTimePiece on parallel port irq: %d ioport: 0x%lx\n", irq, port); + return 0; +} + +#define SND_MTPAV_DRIVER "snd_mtpav" + +static struct platform_driver snd_mtpav_driver = { + .probe = snd_mtpav_probe, + .driver = { + .name = SND_MTPAV_DRIVER, + }, +}; + +static int __init alsa_card_mtpav_init(void) +{ + int err; + + err = platform_driver_register(&snd_mtpav_driver); + if (err < 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) diff --git a/sound/drivers/mts64.c b/sound/drivers/mts64.c new file mode 100644 index 0000000000..5cfd0e99a1 --- /dev/null +++ b/sound/drivers/mts64.c @@ -0,0 +1,1062 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA Driver for Ego Systems Inc. (ESI) Miditerminal 4140 + * Copyright (c) 2006 by Matthias König <mk@phasorlab.de> + */ + +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/parport.h> +#include <linux/spinlock.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/rawmidi.h> +#include <sound/control.h> + +#define CARD_NAME "Miditerminal 4140" +#define DRIVER_NAME "MTS64" +#define PLATFORM_DRIVER "snd_mts64" + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool 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, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); + +MODULE_AUTHOR("Matthias Koenig <mk@phasorlab.de>"); +MODULE_DESCRIPTION("ESI Miditerminal 4140"); +MODULE_LICENSE("GPL"); + +/********************************************************************* + * Chip specific + *********************************************************************/ +#define MTS64_NUM_INPUT_PORTS 5 +#define MTS64_NUM_OUTPUT_PORTS 4 +#define MTS64_SMPTE_SUBSTREAM 4 + +struct mts64 { + spinlock_t lock; + struct snd_card *card; + struct snd_rawmidi *rmidi; + struct pardevice *pardev; + int open_count; + int current_midi_output_port; + int current_midi_input_port; + u8 mode[MTS64_NUM_INPUT_PORTS]; + struct snd_rawmidi_substream *midi_input_substream[MTS64_NUM_INPUT_PORTS]; + int smpte_switch; + u8 time[4]; /* [0]=hh, [1]=mm, [2]=ss, [3]=ff */ + u8 fps; +}; + +static int snd_mts64_free(struct mts64 *mts) +{ + kfree(mts); + return 0; +} + +static int snd_mts64_create(struct snd_card *card, + struct pardevice *pardev, + struct mts64 **rchip) +{ + struct mts64 *mts; + + *rchip = NULL; + + mts = kzalloc(sizeof(struct mts64), GFP_KERNEL); + if (mts == NULL) + return -ENOMEM; + + /* Init chip specific data */ + spin_lock_init(&mts->lock); + mts->card = card; + mts->pardev = pardev; + mts->current_midi_output_port = -1; + mts->current_midi_input_port = -1; + + *rchip = mts; + + return 0; +} + +/********************************************************************* + * HW register related constants + *********************************************************************/ + +/* Status Bits */ +#define MTS64_STAT_BSY 0x80 +#define MTS64_STAT_BIT_SET 0x20 /* readout process, bit is set */ +#define MTS64_STAT_PORT 0x10 /* read byte is a port number */ + +/* Control Bits */ +#define MTS64_CTL_READOUT 0x08 /* enable readout */ +#define MTS64_CTL_WRITE_CMD 0x06 +#define MTS64_CTL_WRITE_DATA 0x02 +#define MTS64_CTL_STROBE 0x01 + +/* Command */ +#define MTS64_CMD_RESET 0xfe +#define MTS64_CMD_PROBE 0x8f /* Used in probing procedure */ +#define MTS64_CMD_SMPTE_SET_TIME 0xe8 +#define MTS64_CMD_SMPTE_SET_FPS 0xee +#define MTS64_CMD_SMPTE_STOP 0xef +#define MTS64_CMD_SMPTE_FPS_24 0xe3 +#define MTS64_CMD_SMPTE_FPS_25 0xe2 +#define MTS64_CMD_SMPTE_FPS_2997 0xe4 +#define MTS64_CMD_SMPTE_FPS_30D 0xe1 +#define MTS64_CMD_SMPTE_FPS_30 0xe0 +#define MTS64_CMD_COM_OPEN 0xf8 /* setting the communication mode */ +#define MTS64_CMD_COM_CLOSE1 0xff /* clearing communication mode */ +#define MTS64_CMD_COM_CLOSE2 0xf5 + +/********************************************************************* + * Hardware specific functions + *********************************************************************/ +static void mts64_enable_readout(struct parport *p); +static void mts64_disable_readout(struct parport *p); +static int mts64_device_ready(struct parport *p); +static int mts64_device_init(struct parport *p); +static int mts64_device_open(struct mts64 *mts); +static int mts64_device_close(struct mts64 *mts); +static u8 mts64_map_midi_input(u8 c); +static int mts64_probe(struct parport *p); +static u16 mts64_read(struct parport *p); +static u8 mts64_read_char(struct parport *p); +static void mts64_smpte_start(struct parport *p, + u8 hours, u8 minutes, + u8 seconds, u8 frames, + u8 idx); +static void mts64_smpte_stop(struct parport *p); +static void mts64_write_command(struct parport *p, u8 c); +static void mts64_write_data(struct parport *p, u8 c); +static void mts64_write_midi(struct mts64 *mts, u8 c, int midiport); + + +/* Enables the readout procedure + * + * Before we can read a midi byte from the device, we have to set + * bit 3 of control port. + */ +static void mts64_enable_readout(struct parport *p) +{ + u8 c; + + c = parport_read_control(p); + c |= MTS64_CTL_READOUT; + parport_write_control(p, c); +} + +/* Disables readout + * + * Readout is disabled by clearing bit 3 of control + */ +static void mts64_disable_readout(struct parport *p) +{ + u8 c; + + c = parport_read_control(p); + c &= ~MTS64_CTL_READOUT; + parport_write_control(p, c); +} + +/* waits for device ready + * + * Checks if BUSY (Bit 7 of status) is clear + * 1 device ready + * 0 failure + */ +static int mts64_device_ready(struct parport *p) +{ + int i; + u8 c; + + for (i = 0; i < 0xffff; ++i) { + c = parport_read_status(p); + c &= MTS64_STAT_BSY; + if (c != 0) + return 1; + } + + return 0; +} + +/* Init device (LED blinking startup magic) + * + * Returns: + * 0 init ok + * -EIO failure + */ +static int mts64_device_init(struct parport *p) +{ + int i; + + mts64_write_command(p, MTS64_CMD_RESET); + + for (i = 0; i < 64; ++i) { + msleep(100); + + if (mts64_probe(p) == 0) { + /* success */ + mts64_disable_readout(p); + return 0; + } + } + mts64_disable_readout(p); + + return -EIO; +} + +/* + * Opens the device (set communication mode) + */ +static int mts64_device_open(struct mts64 *mts) +{ + int i; + struct parport *p = mts->pardev->port; + + for (i = 0; i < 5; ++i) + mts64_write_command(p, MTS64_CMD_COM_OPEN); + + return 0; +} + +/* + * Close device (clear communication mode) + */ +static int mts64_device_close(struct mts64 *mts) +{ + int i; + struct parport *p = mts->pardev->port; + + for (i = 0; i < 5; ++i) { + mts64_write_command(p, MTS64_CMD_COM_CLOSE1); + mts64_write_command(p, MTS64_CMD_COM_CLOSE2); + } + + return 0; +} + +/* map hardware port to substream number + * + * When reading a byte from the device, the device tells us + * on what port the byte is. This HW port has to be mapped to + * the midiport (substream number). + * substream 0-3 are Midiports 1-4 + * substream 4 is SMPTE Timecode + * The mapping is done by the table: + * HW | 0 | 1 | 2 | 3 | 4 + * SW | 0 | 1 | 4 | 2 | 3 + */ +static u8 mts64_map_midi_input(u8 c) +{ + static const u8 map[] = { 0, 1, 4, 2, 3 }; + + return map[c]; +} + + +/* Probe parport for device + * + * Do we have a Miditerminal 4140 on parport? + * Returns: + * 0 device found + * -ENODEV no device + */ +static int mts64_probe(struct parport *p) +{ + u8 c; + + mts64_smpte_stop(p); + mts64_write_command(p, MTS64_CMD_PROBE); + + msleep(50); + + c = mts64_read(p); + + c &= 0x00ff; + if (c != MTS64_CMD_PROBE) + return -ENODEV; + else + return 0; + +} + +/* Read byte incl. status from device + * + * Returns: + * data in lower 8 bits and status in upper 8 bits + */ +static u16 mts64_read(struct parport *p) +{ + u8 data, status; + + mts64_device_ready(p); + mts64_enable_readout(p); + status = parport_read_status(p); + data = mts64_read_char(p); + mts64_disable_readout(p); + + return (status << 8) | data; +} + +/* Read a byte from device + * + * Note, that readout mode has to be enabled. + * readout procedure is as follows: + * - Write number of the Bit to read to DATA + * - Read STATUS + * - Bit 5 of STATUS indicates if Bit is set + * + * Returns: + * Byte read from device + */ +static u8 mts64_read_char(struct parport *p) +{ + u8 c = 0; + u8 status; + u8 i; + + for (i = 0; i < 8; ++i) { + parport_write_data(p, i); + c >>= 1; + status = parport_read_status(p); + if (status & MTS64_STAT_BIT_SET) + c |= 0x80; + } + + return c; +} + +/* Starts SMPTE Timecode generation + * + * The device creates SMPTE Timecode by hardware. + * 0 24 fps + * 1 25 fps + * 2 29.97 fps + * 3 30 fps (Drop-frame) + * 4 30 fps + */ +static void mts64_smpte_start(struct parport *p, + u8 hours, u8 minutes, + u8 seconds, u8 frames, + u8 idx) +{ + static const u8 fps[5] = { MTS64_CMD_SMPTE_FPS_24, + MTS64_CMD_SMPTE_FPS_25, + MTS64_CMD_SMPTE_FPS_2997, + MTS64_CMD_SMPTE_FPS_30D, + MTS64_CMD_SMPTE_FPS_30 }; + + mts64_write_command(p, MTS64_CMD_SMPTE_SET_TIME); + mts64_write_command(p, frames); + mts64_write_command(p, seconds); + mts64_write_command(p, minutes); + mts64_write_command(p, hours); + + mts64_write_command(p, MTS64_CMD_SMPTE_SET_FPS); + mts64_write_command(p, fps[idx]); +} + +/* Stops SMPTE Timecode generation + */ +static void mts64_smpte_stop(struct parport *p) +{ + mts64_write_command(p, MTS64_CMD_SMPTE_STOP); +} + +/* Write a command byte to device + */ +static void mts64_write_command(struct parport *p, u8 c) +{ + mts64_device_ready(p); + + parport_write_data(p, c); + + parport_write_control(p, MTS64_CTL_WRITE_CMD); + parport_write_control(p, MTS64_CTL_WRITE_CMD | MTS64_CTL_STROBE); + parport_write_control(p, MTS64_CTL_WRITE_CMD); +} + +/* Write a data byte to device + */ +static void mts64_write_data(struct parport *p, u8 c) +{ + mts64_device_ready(p); + + parport_write_data(p, c); + + parport_write_control(p, MTS64_CTL_WRITE_DATA); + parport_write_control(p, MTS64_CTL_WRITE_DATA | MTS64_CTL_STROBE); + parport_write_control(p, MTS64_CTL_WRITE_DATA); +} + +/* Write a MIDI byte to midiport + * + * midiport ranges from 0-3 and maps to Ports 1-4 + * assumptions: communication mode is on + */ +static void mts64_write_midi(struct mts64 *mts, u8 c, + int midiport) +{ + struct parport *p = mts->pardev->port; + + /* check current midiport */ + if (mts->current_midi_output_port != midiport) + mts64_write_command(p, midiport); + + /* write midi byte */ + mts64_write_data(p, c); +} + +/********************************************************************* + * Control elements + *********************************************************************/ + +/* SMPTE Switch */ +#define snd_mts64_ctl_smpte_switch_info snd_ctl_boolean_mono_info + +static int snd_mts64_ctl_smpte_switch_get(struct snd_kcontrol* kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + + spin_lock_irq(&mts->lock); + uctl->value.integer.value[0] = mts->smpte_switch; + spin_unlock_irq(&mts->lock); + + return 0; +} + +/* smpte_switch is not accessed from IRQ handler, so we just need + to protect the HW access */ +static int snd_mts64_ctl_smpte_switch_put(struct snd_kcontrol* kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int changed = 0; + int val = !!uctl->value.integer.value[0]; + + spin_lock_irq(&mts->lock); + if (mts->smpte_switch == val) + goto __out; + + changed = 1; + mts->smpte_switch = val; + if (mts->smpte_switch) { + mts64_smpte_start(mts->pardev->port, + mts->time[0], mts->time[1], + mts->time[2], mts->time[3], + mts->fps); + } else { + mts64_smpte_stop(mts->pardev->port); + } +__out: + spin_unlock_irq(&mts->lock); + return changed; +} + +static const struct snd_kcontrol_new mts64_ctl_smpte_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Playback Switch", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_mts64_ctl_smpte_switch_info, + .get = snd_mts64_ctl_smpte_switch_get, + .put = snd_mts64_ctl_smpte_switch_put +}; + +/* Time */ +static int snd_mts64_ctl_smpte_time_h_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 23; + return 0; +} + +static int snd_mts64_ctl_smpte_time_f_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 99; + return 0; +} + +static int snd_mts64_ctl_smpte_time_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 59; + return 0; +} + +static int snd_mts64_ctl_smpte_time_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int idx = kctl->private_value; + + spin_lock_irq(&mts->lock); + uctl->value.integer.value[0] = mts->time[idx]; + spin_unlock_irq(&mts->lock); + + return 0; +} + +static int snd_mts64_ctl_smpte_time_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int idx = kctl->private_value; + unsigned int time = uctl->value.integer.value[0] % 60; + int changed = 0; + + spin_lock_irq(&mts->lock); + if (mts->time[idx] != time) { + changed = 1; + mts->time[idx] = time; + } + spin_unlock_irq(&mts->lock); + + return changed; +} + +static const struct snd_kcontrol_new mts64_ctl_smpte_time_hours = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Hours", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_mts64_ctl_smpte_time_h_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +static const struct snd_kcontrol_new mts64_ctl_smpte_time_minutes = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Minutes", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 1, + .info = snd_mts64_ctl_smpte_time_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +static const struct snd_kcontrol_new mts64_ctl_smpte_time_seconds = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Seconds", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 2, + .info = snd_mts64_ctl_smpte_time_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +static const struct snd_kcontrol_new mts64_ctl_smpte_time_frames = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Time Frames", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 3, + .info = snd_mts64_ctl_smpte_time_f_info, + .get = snd_mts64_ctl_smpte_time_get, + .put = snd_mts64_ctl_smpte_time_put +}; + +/* FPS */ +static int snd_mts64_ctl_smpte_fps_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[5] = { + "24", "25", "29.97", "30D", "30" + }; + + return snd_ctl_enum_info(uinfo, 1, 5, texts); +} + +static int snd_mts64_ctl_smpte_fps_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + + spin_lock_irq(&mts->lock); + uctl->value.enumerated.item[0] = mts->fps; + spin_unlock_irq(&mts->lock); + + return 0; +} + +static int snd_mts64_ctl_smpte_fps_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uctl) +{ + struct mts64 *mts = snd_kcontrol_chip(kctl); + int changed = 0; + + if (uctl->value.enumerated.item[0] >= 5) + return -EINVAL; + spin_lock_irq(&mts->lock); + if (mts->fps != uctl->value.enumerated.item[0]) { + changed = 1; + mts->fps = uctl->value.enumerated.item[0]; + } + spin_unlock_irq(&mts->lock); + + return changed; +} + +static const struct snd_kcontrol_new mts64_ctl_smpte_fps = { + .iface = SNDRV_CTL_ELEM_IFACE_RAWMIDI, + .name = "SMPTE Fps", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .private_value = 0, + .info = snd_mts64_ctl_smpte_fps_info, + .get = snd_mts64_ctl_smpte_fps_get, + .put = snd_mts64_ctl_smpte_fps_put +}; + + +static int snd_mts64_ctl_create(struct snd_card *card, + struct mts64 *mts) +{ + int err, i; + static const struct snd_kcontrol_new *control[] = { + &mts64_ctl_smpte_switch, + &mts64_ctl_smpte_time_hours, + &mts64_ctl_smpte_time_minutes, + &mts64_ctl_smpte_time_seconds, + &mts64_ctl_smpte_time_frames, + &mts64_ctl_smpte_fps, + NULL }; + + for (i = 0; control[i]; ++i) { + err = snd_ctl_add(card, snd_ctl_new1(control[i], mts)); + if (err < 0) { + snd_printd("Cannot create control: %s\n", + control[i]->name); + return err; + } + } + + return 0; +} + +/********************************************************************* + * Rawmidi + *********************************************************************/ +#define MTS64_MODE_INPUT_TRIGGERED 0x01 + +static int snd_mts64_rawmidi_open(struct snd_rawmidi_substream *substream) +{ + struct mts64 *mts = substream->rmidi->private_data; + + if (mts->open_count == 0) { + /* We don't need a spinlock here, because this is just called + if the device has not been opened before. + So there aren't any IRQs from the device */ + mts64_device_open(mts); + + msleep(50); + } + ++(mts->open_count); + + return 0; +} + +static int snd_mts64_rawmidi_close(struct snd_rawmidi_substream *substream) +{ + struct mts64 *mts = substream->rmidi->private_data; + unsigned long flags; + + --(mts->open_count); + if (mts->open_count == 0) { + /* We need the spinlock_irqsave here because we can still + have IRQs at this point */ + spin_lock_irqsave(&mts->lock, flags); + mts64_device_close(mts); + spin_unlock_irqrestore(&mts->lock, flags); + + msleep(500); + + } else if (mts->open_count < 0) + mts->open_count = 0; + + return 0; +} + +static void snd_mts64_rawmidi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct mts64 *mts = substream->rmidi->private_data; + u8 data; + unsigned long flags; + + spin_lock_irqsave(&mts->lock, flags); + while (snd_rawmidi_transmit_peek(substream, &data, 1) == 1) { + mts64_write_midi(mts, data, substream->number+1); + snd_rawmidi_transmit_ack(substream, 1); + } + spin_unlock_irqrestore(&mts->lock, flags); +} + +static void snd_mts64_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct mts64 *mts = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&mts->lock, flags); + if (up) + mts->mode[substream->number] |= MTS64_MODE_INPUT_TRIGGERED; + else + mts->mode[substream->number] &= ~MTS64_MODE_INPUT_TRIGGERED; + + spin_unlock_irqrestore(&mts->lock, flags); +} + +static const struct snd_rawmidi_ops snd_mts64_rawmidi_output_ops = { + .open = snd_mts64_rawmidi_open, + .close = snd_mts64_rawmidi_close, + .trigger = snd_mts64_rawmidi_output_trigger +}; + +static const struct snd_rawmidi_ops snd_mts64_rawmidi_input_ops = { + .open = snd_mts64_rawmidi_open, + .close = snd_mts64_rawmidi_close, + .trigger = snd_mts64_rawmidi_input_trigger +}; + +/* Create and initialize the rawmidi component */ +static int snd_mts64_rawmidi_create(struct snd_card *card) +{ + struct mts64 *mts = card->private_data; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream; + struct list_head *list; + int err; + + err = snd_rawmidi_new(card, CARD_NAME, 0, + MTS64_NUM_OUTPUT_PORTS, + MTS64_NUM_INPUT_PORTS, + &rmidi); + if (err < 0) + return err; + + rmidi->private_data = mts; + strcpy(rmidi->name, CARD_NAME); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + mts->rmidi = rmidi; + + /* register rawmidi ops */ + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_mts64_rawmidi_output_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_mts64_rawmidi_input_ops); + + /* name substreams */ + /* output */ + list_for_each(list, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT].substreams) { + substream = list_entry(list, struct snd_rawmidi_substream, list); + sprintf(substream->name, + "Miditerminal %d", substream->number+1); + } + /* input */ + list_for_each(list, + &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT].substreams) { + substream = list_entry(list, struct snd_rawmidi_substream, list); + mts->midi_input_substream[substream->number] = substream; + switch(substream->number) { + case MTS64_SMPTE_SUBSTREAM: + strcpy(substream->name, "Miditerminal SMPTE"); + break; + default: + sprintf(substream->name, + "Miditerminal %d", substream->number+1); + } + } + + /* controls */ + err = snd_mts64_ctl_create(card, mts); + + return err; +} + +/********************************************************************* + * parport stuff + *********************************************************************/ +static void snd_mts64_interrupt(void *private) +{ + struct mts64 *mts = ((struct snd_card*)private)->private_data; + u16 ret; + u8 status, data; + struct snd_rawmidi_substream *substream; + + if (!mts) + return; + + spin_lock(&mts->lock); + ret = mts64_read(mts->pardev->port); + data = ret & 0x00ff; + status = ret >> 8; + + if (status & MTS64_STAT_PORT) { + mts->current_midi_input_port = mts64_map_midi_input(data); + } else { + if (mts->current_midi_input_port == -1) + goto __out; + substream = mts->midi_input_substream[mts->current_midi_input_port]; + if (mts->mode[substream->number] & MTS64_MODE_INPUT_TRIGGERED) + snd_rawmidi_receive(substream, &data, 1); + } +__out: + spin_unlock(&mts->lock); +} + +static void snd_mts64_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_add(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_mts64_detach(struct parport *p) +{ + /* nothing to do here */ +} + +static int snd_mts64_dev_probe(struct pardevice *pardev) +{ + if (strcmp(pardev->name, DRIVER_NAME)) + return -ENODEV; + + return 0; +} + +static struct parport_driver mts64_parport_driver = { + .name = "mts64", + .probe = snd_mts64_dev_probe, + .match_port = snd_mts64_attach, + .detach = snd_mts64_detach, + .devmodel = true, +}; + +/********************************************************************* + * platform stuff + *********************************************************************/ +static void snd_mts64_card_private_free(struct snd_card *card) +{ + struct mts64 *mts = card->private_data; + struct pardevice *pardev = mts->pardev; + + if (pardev) { + parport_release(pardev); + parport_unregister_device(pardev); + } + + snd_mts64_free(mts); +} + +static int snd_mts64_probe(struct platform_device *pdev) +{ + struct pardevice *pardev; + struct parport *p; + int dev = pdev->id; + struct snd_card *card = NULL; + struct mts64 *mts = NULL; + int err; + struct pardev_cb mts64_cb = { + .preempt = NULL, + .wakeup = NULL, + .irq_func = snd_mts64_interrupt, /* ISR */ + .flags = PARPORT_DEV_EXCL, /* flags */ + }; + + p = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) + return -ENOENT; + + err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + snd_printd("Cannot create card\n"); + return err; + } + strcpy(card->driver, DRIVER_NAME); + strcpy(card->shortname, "ESI " CARD_NAME); + sprintf(card->longname, "%s at 0x%lx, irq %i", + card->shortname, p->base, p->irq); + + mts64_cb.private = card; /* private */ + pardev = parport_register_dev_model(p, /* port */ + DRIVER_NAME, /* name */ + &mts64_cb, /* callbacks */ + pdev->id); /* device number */ + if (!pardev) { + snd_printd("Cannot register pardevice\n"); + err = -EIO; + goto __err; + } + + /* claim parport */ + if (parport_claim(pardev)) { + snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base); + err = -EIO; + goto free_pardev; + } + + err = snd_mts64_create(card, pardev, &mts); + if (err < 0) { + snd_printd("Cannot create main component\n"); + goto release_pardev; + } + card->private_data = mts; + card->private_free = snd_mts64_card_private_free; + + err = mts64_probe(p); + if (err) { + err = -EIO; + goto __err; + } + + err = snd_mts64_rawmidi_create(card); + if (err < 0) { + snd_printd("Creating Rawmidi component failed\n"); + goto __err; + } + + /* init device */ + err = mts64_device_init(p); + if (err < 0) + goto __err; + + platform_set_drvdata(pdev, card); + + /* At this point card will be usable */ + err = snd_card_register(card); + if (err < 0) { + snd_printd("Cannot register card\n"); + goto __err; + } + + snd_printk(KERN_INFO "ESI Miditerminal 4140 on 0x%lx\n", p->base); + return 0; + +release_pardev: + parport_release(pardev); +free_pardev: + parport_unregister_device(pardev); +__err: + snd_card_free(card); + return err; +} + +static void snd_mts64_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + + if (card) + snd_card_free(card); +} + +static struct platform_driver snd_mts64_driver = { + .probe = snd_mts64_probe, + .remove_new = snd_mts64_remove, + .driver = { + .name = PLATFORM_DRIVER, + } +}; + +/********************************************************************* + * module init stuff + *********************************************************************/ +static void snd_mts64_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_mts64_driver); + parport_unregister_driver(&mts64_parport_driver); +} + +static int __init snd_mts64_module_init(void) +{ + int err; + + err = platform_driver_register(&snd_mts64_driver); + if (err < 0) + return err; + + if (parport_register_driver(&mts64_parport_driver) != 0) { + platform_driver_unregister(&snd_mts64_driver); + return -EIO; + } + + if (device_count == 0) { + snd_mts64_unregister_all(); + return -ENODEV; + } + + return 0; +} + +static void __exit snd_mts64_module_exit(void) +{ + snd_mts64_unregister_all(); +} + +module_init(snd_mts64_module_init); +module_exit(snd_mts64_module_exit); diff --git a/sound/drivers/opl3/Makefile b/sound/drivers/opl3/Makefile new file mode 100644 index 0000000000..83bca9f1fb --- /dev/null +++ b/sound/drivers/opl3/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-opl3-lib-objs := opl3_lib.o opl3_synth.o +snd-opl3-synth-y := opl3_seq.o opl3_midi.o opl3_drums.o +ifneq ($(CONFIG_SND_SEQUENCER_OSS),) +snd-opl3-synth-y += opl3_oss.o +endif + +obj-$(CONFIG_SND_OPL3_LIB) += snd-opl3-lib.o +obj-$(CONFIG_SND_OPL4_LIB) += snd-opl3-lib.o +obj-$(CONFIG_SND_OPL3_LIB_SEQ) += snd-opl3-synth.o diff --git a/sound/drivers/opl3/opl3_drums.c b/sound/drivers/opl3/opl3_drums.c new file mode 100644 index 0000000000..ccc49f3940 --- /dev/null +++ b/sound/drivers/opl3/opl3_drums.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Uros Bizjak <uros@kss-loka.si> + * + * OPL2/OPL3/OPL4 FM routines for internal percussion channels + */ + +#include "opl3_voice.h" + +static const 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 const struct snd_opl3_drum_voice bass_op0 = {6, 0, 0x00, 0x32, 0xf8, 0x66, 0x30, 0x00}; +static const struct snd_opl3_drum_voice bass_op1 = {6, 1, 0x00, 0x03, 0xf6, 0x57, 0x30, 0x00}; +static const struct snd_opl3_drum_note bass_note = {6, 0x90, 0x09}; + +static const struct snd_opl3_drum_voice hihat = {7, 0, 0x00, 0x03, 0xf0, 0x06, 0x20, 0x00}; + +static const struct snd_opl3_drum_voice snare = {7, 1, 0x00, 0x03, 0xf0, 0x07, 0x20, 0x02}; +static const struct snd_opl3_drum_note snare_note = {7, 0xf4, 0x0d}; + +static const struct snd_opl3_drum_voice tomtom = {8, 0, 0x02, 0x03, 0xf0, 0x06, 0x10, 0x00}; +static const struct snd_opl3_drum_note tomtom_note = {8, 0xf4, 0x09}; + +static const 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, + const 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, + const 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, + const 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(®_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; + const 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); +} diff --git a/sound/drivers/opl3/opl3_lib.c b/sound/drivers/opl3/opl3_lib.c new file mode 100644 index 0000000000..6c1f1cc092 --- /dev/null +++ b/sound/drivers/opl3/opl3_lib.c @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela <perex@perex.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. + */ + +#include <sound/opl3.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <sound/minors.h> +#include "opl3_voice.h" + +MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Hannu Savolainen 1993-1996, Rob Hooft"); +MODULE_DESCRIPTION("Routines for control of AdLib FM cards (OPL2/OPL3/OPL4 chips)"); +MODULE_LICENSE("GPL"); + +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. + */ + if (snd_BUG_ON(!opl3->r_port)) + 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 const 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 const 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; + err = snd_timer_new(opl3->card, "AdLib timer #1", &tid, &timer); + if (err >= 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; + err = snd_timer_new(opl3->card, "AdLib timer #2", &tid, &timer); + if (err >= 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(KERN_DEBUG "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) +{ + if (snd_BUG_ON(!opl3)) + return -ENXIO; + if (opl3->private_free) + opl3->private_free(opl3); + snd_opl3_clear_patches(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 const 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) + return -ENOMEM; + + opl3->card = card; + opl3->hardware = hardware; + spin_lock_init(&opl3->reg_lock); + spin_lock_init(&opl3->timer_lock); + + err = snd_device_new(card, SNDRV_DEV_CODEC, opl3, &ops); + if (err < 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; + err = snd_opl3_new(card, hardware, &opl3); + if (err < 0) + return err; + if (! integrated) { + opl3->res_l_port = request_region(l_port, 2, "OPL2/3 (left)"); + if (!opl3->res_l_port) { + 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)"); + if (!opl3->res_r_port) { + 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; + err = snd_opl3_detect(opl3); + if (err < 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) { + err = snd_opl3_timer1_init(opl3, timer1_dev); + if (err < 0) + return err; + } + if (timer2_dev >= 0) { + err = snd_opl3_timer2_init(opl3, timer2_dev); + if (err < 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) */ + + err = snd_hwdep_new(card, "OPL2/OPL3", device, &hw); + if (err < 0) { + snd_device_free(card, opl3); + return err; + } + hw->private_data = opl3; + hw->exclusive = 1; +#ifdef CONFIG_SND_OSSEMUL + if (device == 0) + hw->oss_type = SNDRV_OSS_DEVICE_TYPE_DMFM; +#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.write = snd_opl3_write; + hw->ops.release = snd_opl3_release; + + opl3->hwdep = hw; + opl3->seq_dev_num = seq_device; +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + 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); diff --git a/sound/drivers/opl3/opl3_midi.c b/sound/drivers/opl3/opl3_midi.c new file mode 100644 index 0000000000..e2b7be67f0 --- /dev/null +++ b/sound/drivers/opl3/opl3_midi.c @@ -0,0 +1,855 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Uros Bizjak <uros@kss-loka.si> + * + * Midi synth routines for OPL2/OPL3/OPL4 FM + */ + +#undef DEBUG_ALLOC +#undef DEBUG_MIDI + +#include "opl3_voice.h" +#include <sound/asoundef.h> + +static void snd_opl3_note_off_unsafe(void *p, int note, int vel, + struct snd_midi_channel *chan); +/* + * 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 const 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 const 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 < -0x2000) + pitchbend = -0x2000; + 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(KERN_DEBUG "time %.5i: %s [%.2i]: ", opl3->use_time, s, voice); + for (i = 0; i < opl3->max_voices; i++) + printk(KERN_CONT "%c", *(str + opl3->voices[i].state + 1)); + printk(KERN_CONT "\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 = max(voice_time, vp2->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(KERN_DEBUG "%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(struct timer_list *t) +{ + + struct snd_opl3 *opl3 = from_timer(opl3, t, tlist); + unsigned long flags; + int again = 0; + int i; + + spin_lock_irqsave(&opl3->voice_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_unsafe(opl3, vp->note, 0, + vp->chan); + else + again++; + } + } + spin_unlock_irqrestore(&opl3->voice_lock, flags); + + spin_lock_irqsave(&opl3->sys_timer_lock, flags); + if (again) + mod_timer(&opl3->tlist, jiffies + 1); /* invoke again */ + 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) { + mod_timer(&opl3->tlist, jiffies + 1); + opl3->sys_timer_status = 1; + } + spin_unlock_irqrestore(&opl3->sys_timer_lock, flags); +} + +/* ------------------------------ */ + + +static const 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; + 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; + unsigned char prg, bank; + + int key = note; + unsigned char fnum, blocknum; + int i; + + struct fm_patch *patch; + struct fm_instrument *fm; + unsigned long flags; + + opl3 = p; + +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "Note on, ch %i, inst %i, note %i, vel %i\n", + chan->number, chan->midi_program, note, vel); +#endif + + /* 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 */ + bank = 128; + prg = note; + } else { + bank = chan->gm_bank_select; + prg = chan->midi_program; + } + } else { + /* Prepare for OSS mode */ + if (chan->number >= MAX_OPL3_VOICES) + return; + + /* OSS instruments are located in bank 127 */ + bank = 127; + 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: + patch = snd_opl3_find_patch(opl3, prg, bank, 0); + if (!patch) { + spin_unlock_irqrestore(&opl3->voice_lock, flags); + return; + } + + fm = &patch->inst; + switch (patch->type) { + case FM_PATCH_OPL2: + instr_4op = 0; + break; + case FM_PATCH_OPL3: + if (opl3->hardware >= OPL3_HW_OPL3) { + instr_4op = 1; + break; + } + fallthrough; + default: + spin_unlock_irqrestore(&opl3->voice_lock, flags); + return; + } +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " --> OPL%i instrument: %s\n", + instr_4op ? 3 : 2, patch->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 < 0) { + spin_unlock_irqrestore(&opl3->voice_lock, flags); + return; + } + + 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 (vp2->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(KERN_DEBUG " --> 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); + fallthrough; + 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(KERN_DEBUG " --> 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(KERN_DEBUG " --> 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; + + /* 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) { + bank = 128; + /* percussions start at 35 */ + prg = extra_prg - 128 + 35 - 1; + } else { + bank = 0; + prg = extra_prg - 1; + } +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG " *** 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; + + if (snd_BUG_ON(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(KERN_DEBUG " --> 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. + */ +static void snd_opl3_note_off_unsafe(void *p, int note, int vel, + struct snd_midi_channel *chan) +{ + struct snd_opl3 *opl3; + + int voice; + struct snd_opl3_voice *vp; + + opl3 = p; + +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "Note off, ch %i, inst %i, note %i\n", + chan->number, chan->midi_program, note); +#endif + + if (opl3->synth_mode == SNDRV_OPL3_MODE_SEQ) { + if (chan->drum_channel && use_internal_drums) { + snd_opl3_drum_switch(opl3, note, vel, 0, chan); + 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); + } + } +} + +void snd_opl3_note_off(void *p, int note, int vel, + struct snd_midi_channel *chan) +{ + struct snd_opl3 *opl3 = p; + unsigned long flags; + + spin_lock_irqsave(&opl3->voice_lock, flags); + snd_opl3_note_off_unsafe(p, note, vel, chan); + 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) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "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) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "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; + + if (snd_BUG_ON(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 controller 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(KERN_DEBUG "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) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "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) +{ +#ifdef DEBUG_MIDI + snd_printk(KERN_DEBUG "SYSEX\n"); +#endif +} diff --git a/sound/drivers/opl3/opl3_oss.c b/sound/drivers/opl3/opl3_oss.c new file mode 100644 index 0000000000..7645365eec --- /dev/null +++ b/sound/drivers/opl3/opl3_oss.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Interface for OSS sequencer emulation + * + * Copyright (C) 2000 Uros Bizjak <uros@kss-loka.si> + */ + +#include <linux/export.h> +#include "opl3_voice.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); + +/* operators */ + +static const 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; + strscpy(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; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + + err = snd_opl3_synth_setup(opl3); + if (err < 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; + + err = snd_opl3_synth_use_inc(opl3); + if (err < 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; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + opl3 = arg->private_data; + + snd_opl3_synth_cleanup(opl3); + + snd_opl3_synth_use_dec(opl3); + return 0; +} + +/* load patch */ + +/* 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; + struct sbi_instrument sbi; + char name[32]; + int err, type; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + opl3 = arg->private_data; + + if (format == FM_PATCH) + type = FM_PATCH_OPL2; + else if (format == OPL3_PATCH) + type = FM_PATCH_OPL3; + else + return -EINVAL; + + if (count < (int)sizeof(sbi)) { + snd_printk(KERN_ERR "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(KERN_ERR "FM Error: Invalid instrument number %d\n", + sbi.channel); + return -EINVAL; + } + + memset(name, 0, sizeof(name)); + sprintf(name, "Chan%d", sbi.channel); + + err = snd_opl3_load_patch(opl3, sbi.channel, 127, type, name, NULL, + sbi.operators); + if (err < 0) + return err; + + return sizeof(sbi); +} + +/* ioctl */ +static int snd_opl3_ioctl_seq_oss(struct snd_seq_oss_arg *arg, unsigned int cmd, + unsigned long ioarg) +{ + if (snd_BUG_ON(!arg)) + return -ENXIO; + switch (cmd) { + case SNDCTL_FM_LOAD_INSTR: + snd_printk(KERN_ERR "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) +{ + if (snd_BUG_ON(!arg)) + return -ENXIO; + + return 0; +} diff --git a/sound/drivers/opl3/opl3_seq.c b/sound/drivers/opl3/opl3_seq.c new file mode 100644 index 0000000000..75de1299c3 --- /dev/null +++ b/sound/drivers/opl3/opl3_seq.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * 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/ + */ + +#include "opl3_voice.h" +#include <linux/init.h> +#include <linux/moduleparam.h> +#include <linux/module.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ALSA driver for OPL3 FM synth"); + +bool 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; + struct snd_hwdep *hwdep = opl3->hwdep; + + mutex_lock(&hwdep->open_mutex); + if (hwdep->used) { + mutex_unlock(&hwdep->open_mutex); + return -EBUSY; + } + hwdep->used++; + mutex_unlock(&hwdep->open_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; + struct snd_hwdep *hwdep; + + /* 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); + hwdep = opl3->hwdep; + mutex_lock(&hwdep->open_mutex); + hwdep->used--; + mutex_unlock(&hwdep->open_mutex); + wake_up(&hwdep->open_wait); +} + +static int snd_opl3_synth_use(void *private_data, struct snd_seq_port_subscribe * info) +{ + struct snd_opl3 *opl3 = private_data; + int err; + + err = snd_opl3_synth_setup(opl3); + if (err < 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) { + err = snd_opl3_synth_use_inc(opl3); + if (err < 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 + */ +const 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; + + 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_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_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; + + err = snd_opl3_synth_create_port(opl3); + if (err < 0) { + snd_seq_delete_kernel_client(client); + opl3->seq_client = -1; + return err; + } + + /* setup system timer */ + timer_setup(&opl3->tlist, snd_opl3_timer_func, 0); + spin_lock_init(&opl3->sys_timer_lock); + opl3->sys_timer_status = 0; + +#if IS_ENABLED(CONFIG_SND_SEQUENCER_OSS) + snd_opl3_init_seq_oss(opl3, name); +#endif + return 0; +} + +static int snd_opl3_seq_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_dev); + struct snd_opl3 *opl3; + + opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (opl3 == NULL) + return -EINVAL; + +#if IS_ENABLED(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; + } + return 0; +} + +static struct snd_seq_driver opl3_seq_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_opl3_seq_probe, + .remove = snd_opl3_seq_remove, + }, + .id = SNDRV_SEQ_DEV_ID_OPL3, + .argsize = sizeof(struct snd_opl3 *), +}; + +module_snd_seq_driver(opl3_seq_driver); diff --git a/sound/drivers/opl3/opl3_synth.c b/sound/drivers/opl3/opl3_synth.c new file mode 100644 index 0000000000..97d30a833a --- /dev/null +++ b/sound/drivers/opl3/opl3_synth.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Uros Bizjak <uros@kss-loka.si> + * + * Routines for OPL2/OPL3/OPL4 control + */ + +#include <linux/slab.h> +#include <linux/export.h> +#include <linux/nospec.h> +#include <sound/opl3.h> +#include <sound/asound_fm.h> +#include "opl3_voice.h" + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) +#define OPL3_SUPPORT_SYNTH +#endif + +/* + * 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) +{ + 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; + + if (snd_BUG_ON(!opl3)) + return -EINVAL; + + switch (cmd) { + /* get information */ + case SNDRV_DM_FM_IOCTL_INFO: + { + struct snd_dm_fm_info info; + + memset(&info, 0, sizeof(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(¬e, argp, sizeof(struct snd_dm_fm_note))) + return -EFAULT; + return snd_opl3_play_note(opl3, ¬e); + } + + 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(¶ms, argp, sizeof(struct snd_dm_fm_params))) + return -EFAULT; + return snd_opl3_set_params(opl3, ¶ms); + } + + 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 OPL3_SUPPORT_SYNTH + case SNDRV_DM_FM_IOCTL_CLEAR_PATCHES: + snd_opl3_clear_patches(opl3); + return 0; +#endif + +#ifdef CONFIG_SND_DEBUG + default: + snd_printk(KERN_WARNING "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); + return 0; +} + +#ifdef OPL3_SUPPORT_SYNTH +/* + * write the device - load patches + */ +long snd_opl3_write(struct snd_hwdep *hw, const char __user *buf, long count, + loff_t *offset) +{ + struct snd_opl3 *opl3 = hw->private_data; + long result = 0; + int err = 0; + struct sbi_patch inst; + + while (count >= sizeof(inst)) { + unsigned char type; + if (copy_from_user(&inst, buf, sizeof(inst))) + return -EFAULT; + if (!memcmp(inst.key, FM_KEY_SBI, 4) || + !memcmp(inst.key, FM_KEY_2OP, 4)) + type = FM_PATCH_OPL2; + else if (!memcmp(inst.key, FM_KEY_4OP, 4)) + type = FM_PATCH_OPL3; + else /* invalid type */ + break; + err = snd_opl3_load_patch(opl3, inst.prog, inst.bank, type, + inst.name, inst.extension, + inst.data); + if (err < 0) + break; + result += sizeof(inst); + count -= sizeof(inst); + } + return result > 0 ? result : err; +} + + +/* + * Patch management + */ + +/* 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 + +/* + * load a patch, obviously. + * + * loaded on the given program and bank numbers with the given type + * (FM_PATCH_OPLx). + * data is the pointer of SBI record _without_ header (key and name). + * name is the name string of the patch. + * ext is the extension data of 7 bytes long (stored in name of SBI + * data up to offset 25), or NULL to skip. + * return 0 if successful or a negative error code. + */ +int snd_opl3_load_patch(struct snd_opl3 *opl3, + int prog, int bank, int type, + const char *name, + const unsigned char *ext, + const unsigned char *data) +{ + struct fm_patch *patch; + int i; + + patch = snd_opl3_find_patch(opl3, prog, bank, 1); + if (!patch) + return -ENOMEM; + + patch->type = type; + + for (i = 0; i < 2; i++) { + patch->inst.op[i].am_vib = data[AM_VIB + i]; + patch->inst.op[i].ksl_level = data[KSL_LEVEL + i]; + patch->inst.op[i].attack_decay = data[ATTACK_DECAY + i]; + patch->inst.op[i].sustain_release = data[SUSTAIN_RELEASE + i]; + patch->inst.op[i].wave_select = data[WAVE_SELECT + i]; + } + patch->inst.feedback_connection[0] = data[CONNECTION]; + + if (type == FM_PATCH_OPL3) { + for (i = 0; i < 2; i++) { + patch->inst.op[i+2].am_vib = + data[OFFSET_4OP + AM_VIB + i]; + patch->inst.op[i+2].ksl_level = + data[OFFSET_4OP + KSL_LEVEL + i]; + patch->inst.op[i+2].attack_decay = + data[OFFSET_4OP + ATTACK_DECAY + i]; + patch->inst.op[i+2].sustain_release = + data[OFFSET_4OP + SUSTAIN_RELEASE + i]; + patch->inst.op[i+2].wave_select = + data[OFFSET_4OP + WAVE_SELECT + i]; + } + patch->inst.feedback_connection[1] = + data[OFFSET_4OP + CONNECTION]; + } + + if (ext) { + patch->inst.echo_delay = ext[0]; + patch->inst.echo_atten = ext[1]; + patch->inst.chorus_spread = ext[2]; + patch->inst.trnsps = ext[3]; + patch->inst.fix_dur = ext[4]; + patch->inst.modes = ext[5]; + patch->inst.fix_key = ext[6]; + } + + if (name) + strscpy(patch->name, name, sizeof(patch->name)); + + return 0; +} +EXPORT_SYMBOL(snd_opl3_load_patch); + +/* + * find a patch with the given program and bank numbers, returns its pointer + * if no matching patch is found and create_patch is set, it creates a + * new patch object. + */ +struct fm_patch *snd_opl3_find_patch(struct snd_opl3 *opl3, int prog, int bank, + int create_patch) +{ + /* pretty dumb hash key */ + unsigned int key = (prog + bank) % OPL3_PATCH_HASH_SIZE; + struct fm_patch *patch; + + for (patch = opl3->patch_table[key]; patch; patch = patch->next) { + if (patch->prog == prog && patch->bank == bank) + return patch; + } + if (!create_patch) + return NULL; + + patch = kzalloc(sizeof(*patch), GFP_KERNEL); + if (!patch) + return NULL; + patch->prog = prog; + patch->bank = bank; + patch->next = opl3->patch_table[key]; + opl3->patch_table[key] = patch; + return patch; +} +EXPORT_SYMBOL(snd_opl3_find_patch); + +/* + * Clear all patches of the given OPL3 instance + */ +void snd_opl3_clear_patches(struct snd_opl3 *opl3) +{ + int i; + for (i = 0; i < OPL3_PATCH_HASH_SIZE; i++) { + struct fm_patch *patch, *next; + for (patch = opl3->patch_table[i]; patch; patch = next) { + next = patch->next; + kfree(patch); + } + } + memset(opl3->patch_table, 0, sizeof(opl3->patch_table)); +} +#endif /* OPL3_SUPPORT_SYNTH */ + +/* ------------------------------ */ + +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, voice_op; + + 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 */ + voice_offset = array_index_nospec(voice_offset, MAX_OPL2_VOICES); + voice_op = array_index_nospec(voice->op, 4); + 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; +} + diff --git a/sound/drivers/opl3/opl3_voice.h b/sound/drivers/opl3/opl3_voice.h new file mode 100644 index 0000000000..be9ccca2d9 --- /dev/null +++ b/sound/drivers/opl3/opl3_voice.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __OPL3_VOICE_H +#define __OPL3_VOICE_H + +/* + * Copyright (c) 2000 Uros Bizjak <uros@kss-loka.si> + */ + +#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(struct timer_list *t); + +/* 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 vel, int on_off, struct snd_midi_channel *chan); + +/* Prototypes for opl3_oss.c */ +#if IS_ENABLED(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); +#else +#define snd_opl3_init_seq_oss(opl3, name) /* NOP */ +#define snd_opl3_free_seq_oss(opl3) /* NOP */ +#endif + +extern char snd_opl3_regmap[MAX_OPL2_VOICES][4]; +extern bool use_internal_drums; +extern const struct snd_midi_op opl3_ops; + +#endif diff --git a/sound/drivers/opl4/Makefile b/sound/drivers/opl4/Makefile new file mode 100644 index 0000000000..6e86a4092b --- /dev/null +++ b/sound/drivers/opl4/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-opl4-lib-objs := opl4_lib.o opl4_mixer.o +snd-opl4-lib-$(CONFIG_SND_PROC_FS) += opl4_proc.o +snd-opl4-synth-objs := opl4_seq.o opl4_synth.o yrw801.o + +obj-$(CONFIG_SND_OPL4_LIB) += snd-opl4-lib.o +obj-$(CONFIG_SND_OPL4_LIB_SEQ) += 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 0000000000..035645eb5e --- /dev/null +++ b/sound/drivers/opl4/opl4_lib.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Functions for accessing OPL4 devices + * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de> + */ + +#include "opl4_local.h" +#include <sound/initval.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> + +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_DESCRIPTION("OPL4 driver"); +MODULE_LICENSE("GPL"); + +static inline void 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 IS_ENABLED(CONFIG_SND_SEQUENCER) +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) +{ + snd_opl4_free_proc(opl4); + 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 const 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); + snd_opl4_create_proc(opl4); + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + 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); diff --git a/sound/drivers/opl4/opl4_local.h b/sound/drivers/opl4/opl4_local.h new file mode 100644 index 0000000000..a16b4677c1 --- /dev/null +++ b/sound/drivers/opl4/opl4_local.h @@ -0,0 +1,235 @@ +/* + * 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_SND_PROC_FS + struct snd_info_entry *proc_entry; + int memory_access; +#endif + struct mutex access_mutex; + +#if IS_ENABLED(CONFIG_SND_SEQUENCER) + 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_SND_PROC_FS +/* opl4_proc.c */ +int snd_opl4_create_proc(struct snd_opl4 *opl4); +void snd_opl4_free_proc(struct snd_opl4 *opl4); +#else +static inline int snd_opl4_create_proc(struct snd_opl4 *opl4) { return 0; } +static inline 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 */ diff --git a/sound/drivers/opl4/opl4_mixer.c b/sound/drivers/opl4/opl4_mixer.c new file mode 100644 index 0000000000..fa1e6eff43 --- /dev/null +++ b/sound/drivers/opl4/opl4_mixer.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OPL4 mixer functions + * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de> + */ + +#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 const 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; +} diff --git a/sound/drivers/opl4/opl4_proc.c b/sound/drivers/opl4/opl4_proc.c new file mode 100644 index 0000000000..f2149091e1 --- /dev/null +++ b/sound/drivers/opl4/opl4_proc.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Functions for the OPL4 proc file + * Copyright (c) 2003 by Clemens Ladisch <clemens@ladisch.de> + */ + +#include "opl4_local.h" +#include <linux/vmalloc.h> +#include <linux/export.h> +#include <sound/info.h> + +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 ssize_t snd_opl4_mem_proc_read(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, char __user *_buf, + size_t count, loff_t pos) +{ + struct snd_opl4 *opl4 = entry->private_data; + char* buf; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + snd_opl4_read_memory(opl4, buf, pos, count); + if (copy_to_user(_buf, buf, count)) { + vfree(buf); + return -EFAULT; + } + vfree(buf); + return count; +} + +static ssize_t snd_opl4_mem_proc_write(struct snd_info_entry *entry, + void *file_private_data, + struct file *file, + const char __user *_buf, + size_t count, loff_t pos) +{ + struct snd_opl4 *opl4 = entry->private_data; + char *buf; + + buf = vmalloc(count); + if (!buf) + return -ENOMEM; + if (copy_from_user(buf, _buf, count)) { + vfree(buf); + return -EFAULT; + } + snd_opl4_write_memory(opl4, buf, pos, count); + vfree(buf); + return count; +} + +static const 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, +}; + +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 |= 0200; + 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; + } + opl4->proc_entry = entry; + return 0; +} + +void snd_opl4_free_proc(struct snd_opl4 *opl4) +{ + snd_info_free_entry(opl4->proc_entry); +} diff --git a/sound/drivers/opl4/opl4_seq.c b/sound/drivers/opl4/opl4_seq.c new file mode 100644 index 0000000000..f59ca660c6 --- /dev/null +++ b/sound/drivers/opl4/opl4_seq.c @@ -0,0 +1,210 @@ +/* + * 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 <linux/module.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 const 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_probe(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_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_remove(struct device *_dev) +{ + struct snd_seq_device *dev = to_seq_dev(_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 struct snd_seq_driver opl4_seq_driver = { + .driver = { + .name = KBUILD_MODNAME, + .probe = snd_opl4_seq_probe, + .remove = snd_opl4_seq_remove, + }, + .id = SNDRV_SEQ_DEV_ID_OPL4, + .argsize = sizeof(struct snd_opl4 *), +}; + +module_snd_seq_driver(opl4_seq_driver); diff --git a/sound/drivers/opl4/opl4_synth.c b/sound/drivers/opl4/opl4_synth.c new file mode 100644 index 0000000000..34e2bd52bb --- /dev/null +++ b/sound/drivers/opl4/opl4_synth.c @@ -0,0 +1,631 @@ +/* + * 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 <linux/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 const 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_BUG_ON(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] = ®ions->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_move_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_move_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_move_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); +} diff --git a/sound/drivers/opl4/yrw801.c b/sound/drivers/opl4/yrw801.c new file mode 100644 index 0000000000..6c335492d0 --- /dev/null +++ b/sound/drivers/opl4/yrw801.c @@ -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) +}; diff --git a/sound/drivers/pcmtest.c b/sound/drivers/pcmtest.c new file mode 100644 index 0000000000..b8bff5522b --- /dev/null +++ b/sound/drivers/pcmtest.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Virtual ALSA driver for PCM testing/fuzzing + * + * Copyright 2023 Ivan Orlov <ivan.orlov0322@gmail.com> + * + * This is a simple virtual ALSA driver, which can be used for audio applications/PCM middle layer + * testing or fuzzing. + * It can: + * - Simulate 'playback' and 'capture' actions + * - Generate random or pattern-based capture data + * - Check playback buffer for containing looped template, and notify about the results + * through the debugfs entry + * - Inject delays into the playback and capturing processes. See 'inject_delay' parameter. + * - Inject errors during the PCM callbacks. + * - Register custom RESET ioctl and notify when it is called through the debugfs entry + * - Work in interleaved and non-interleaved modes + * - Support up to 8 substreams + * - Support up to 4 channels + * - Support framerates from 8 kHz to 48 kHz + * + * When driver works in the capture mode with multiple channels, it duplicates the looped + * pattern to each separate channel. For example, if we have 2 channels, format = U8, interleaved + * access mode and pattern 'abacaba', the DMA buffer will look like aabbccaabbaaaa..., so buffer for + * each channel will contain abacabaabacaba... Same for the non-interleaved mode. + * + * However, it may break the capturing on the higher framerates with small period size, so it is + * better to choose larger period sizes. + * + * You can find the corresponding selftest in the 'alsa' selftests folder. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <sound/pcm.h> +#include <sound/core.h> +#include <linux/dma-mapping.h> +#include <linux/platform_device.h> +#include <linux/timer.h> +#include <linux/random.h> +#include <linux/debugfs.h> +#include <linux/delay.h> + +#define TIMER_PER_SEC 5 +#define TIMER_INTERVAL (HZ / TIMER_PER_SEC) +#define DELAY_JIFFIES HZ +#define PLAYBACK_SUBSTREAM_CNT 8 +#define CAPTURE_SUBSTREAM_CNT 8 +#define MAX_CHANNELS_NUM 4 + +#define DEFAULT_PATTERN "abacaba" +#define DEFAULT_PATTERN_LEN 7 + +#define FILL_MODE_RAND 0 +#define FILL_MODE_PAT 1 + +#define MAX_PATTERN_LEN 4096 + +static int index = -1; +static char *id = "pcmtest"; +static bool enable = true; +static int inject_delay; +static bool inject_hwpars_err; +static bool inject_prepare_err; +static bool inject_trigger_err; +static bool inject_open_err; + +static short fill_mode = FILL_MODE_PAT; + +static u8 playback_capture_test; +static u8 ioctl_reset_test; +static struct dentry *driver_debug_dir; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for pcmtest soundcard"); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for pcmtest soundcard"); +module_param(enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable pcmtest soundcard."); +module_param(fill_mode, short, 0600); +MODULE_PARM_DESC(fill_mode, "Buffer fill mode: rand(0) or pattern(1)"); +module_param(inject_delay, int, 0600); +MODULE_PARM_DESC(inject_delay, "Inject delays during playback/capture (in jiffies)"); +module_param(inject_hwpars_err, bool, 0600); +MODULE_PARM_DESC(inject_hwpars_err, "Inject EBUSY error in the 'hw_params' callback"); +module_param(inject_prepare_err, bool, 0600); +MODULE_PARM_DESC(inject_prepare_err, "Inject EINVAL error in the 'prepare' callback"); +module_param(inject_trigger_err, bool, 0600); +MODULE_PARM_DESC(inject_trigger_err, "Inject EINVAL error in the 'trigger' callback"); +module_param(inject_open_err, bool, 0600); +MODULE_PARM_DESC(inject_open_err, "Inject EBUSY error in the 'open' callback"); + +struct pcmtst { + struct snd_pcm *pcm; + struct snd_card *card; + struct platform_device *pdev; +}; + +struct pcmtst_buf_iter { + size_t buf_pos; // position in the DMA buffer + size_t period_pos; // period-relative position + size_t b_rw; // Bytes to write on every timer tick + size_t s_rw_ch; // Samples to write to one channel on every tick + unsigned int sample_bytes; // sample_bits / 8 + bool is_buf_corrupted; // playback test result indicator + size_t period_bytes; // bytes in a one period + bool interleaved; // Interleaved/Non-interleaved mode + size_t total_bytes; // Total bytes read/written + size_t chan_block; // Bytes in one channel buffer when non-interleaved + struct snd_pcm_substream *substream; + bool suspend; // We need to pause timer without shutting it down + struct timer_list timer_instance; +}; + +static struct snd_pcm_hardware snd_pcmtst_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_NONINTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = MAX_CHANNELS_NUM, + .buffer_bytes_max = 128 * 1024, + .period_bytes_min = 4096, + .period_bytes_max = 32768, + .periods_min = 1, + .periods_max = 1024, +}; + +struct pattern_buf { + char *buf; + u32 len; +}; + +static int buf_allocated; +static struct pattern_buf patt_bufs[MAX_CHANNELS_NUM]; + +static inline void inc_buf_pos(struct pcmtst_buf_iter *v_iter, size_t by, size_t bytes) +{ + v_iter->total_bytes += by; + v_iter->buf_pos += by; + if (v_iter->buf_pos >= bytes) + v_iter->buf_pos %= bytes; +} + +/* + * Position in the DMA buffer when we are in the non-interleaved mode. We increment buf_pos + * every time we write a byte to any channel, so the position in the current channel buffer is + * (position in the DMA buffer) / count_of_channels + size_of_channel_buf * current_channel + */ +static inline size_t buf_pos_n(struct pcmtst_buf_iter *v_iter, unsigned int channels, + unsigned int chan_num) +{ + return v_iter->buf_pos / channels + v_iter->chan_block * chan_num; +} + +/* + * Get the count of bytes written for the current channel in the interleaved mode. + * This is (count of samples written for the current channel) * bytes_in_sample + + * (relative position in the current sample) + */ +static inline size_t ch_pos_i(size_t b_total, unsigned int channels, unsigned int b_sample) +{ + return b_total / channels / b_sample * b_sample + (b_total % b_sample); +} + +static void check_buf_block_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + short ch_num; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + current_byte = runtime->dma_area[v_iter->buf_pos]; + if (!current_byte) + break; + ch_num = (v_iter->total_bytes / v_iter->sample_bytes) % runtime->channels; + if (current_byte != patt_bufs[ch_num].buf[ch_pos_i(v_iter->total_bytes, + runtime->channels, + v_iter->sample_bytes) + % patt_bufs[ch_num].len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + // If we broke during the loop, add remaining bytes to the buffer position. + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +static void check_buf_block_ni(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + size_t i; + short ch_num; + u8 current_byte; + + for (i = 0; i < v_iter->b_rw; i++) { + ch_num = i % channels; + current_byte = runtime->dma_area[buf_pos_n(v_iter, channels, ch_num)]; + if (!current_byte) + break; + if (current_byte != patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) + % patt_bufs[ch_num].len]) { + v_iter->is_buf_corrupted = true; + break; + } + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + inc_buf_pos(v_iter, v_iter->b_rw - i, runtime->dma_bytes); +} + +/* + * Check one block of the buffer. Here we iterate the buffer until we find '0'. This condition is + * necessary because we need to detect when the reading/writing ends, so we assume that the pattern + * doesn't contain zeros. + */ +static void check_buf_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + check_buf_block_i(v_iter, runtime); + else + check_buf_block_ni(v_iter, runtime); +} + +/* + * Fill buffer in the non-interleaved mode. The order of samples is C0, ..., C0, C1, ..., C1, C2... + * The channel buffers lay in the DMA buffer continuously (see default copy + * handlers in the pcm_lib.c file). + * + * Here we increment the DMA buffer position every time we write a byte to any channel 'buffer'. + * We need this to simulate the correct hardware pointer moving. + */ +static void fill_block_pattern_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t i; + unsigned int channels = runtime->channels; + short ch_num; + + for (i = 0; i < v_iter->b_rw; i++) { + ch_num = i % channels; + runtime->dma_area[buf_pos_n(v_iter, channels, ch_num)] = + patt_bufs[ch_num].buf[(v_iter->total_bytes / channels) + % patt_bufs[ch_num].len]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } +} + +// Fill buffer in the interleaved mode. The order of samples is C0, C1, C2, C0, C1, C2, ... +static void fill_block_pattern_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t sample; + size_t pos_in_ch, pos_pattern; + short ch, pos_sample; + + pos_in_ch = ch_pos_i(v_iter->total_bytes, runtime->channels, v_iter->sample_bytes); + + for (sample = 0; sample < v_iter->s_rw_ch; sample++) { + for (ch = 0; ch < runtime->channels; ch++) { + for (pos_sample = 0; pos_sample < v_iter->sample_bytes; pos_sample++) { + pos_pattern = (pos_in_ch + sample * v_iter->sample_bytes + + pos_sample) % patt_bufs[ch].len; + runtime->dma_area[v_iter->buf_pos] = patt_bufs[ch].buf[pos_pattern]; + inc_buf_pos(v_iter, 1, runtime->dma_bytes); + } + } + } +} + +static void fill_block_pattern(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_pattern_i(v_iter, runtime); + else + fill_block_pattern_n(v_iter, runtime); +} + +static void fill_block_rand_n(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + unsigned int channels = runtime->channels; + // Remaining space in all channel buffers + size_t bytes_remain = runtime->dma_bytes - v_iter->buf_pos; + unsigned int i; + + for (i = 0; i < channels; i++) { + if (v_iter->b_rw <= bytes_remain) { + //b_rw - count of bytes must be written for all channels at each timer tick + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + v_iter->b_rw / channels); + } else { + // Write to the end of buffer and start from the beginning of it + get_random_bytes(runtime->dma_area + buf_pos_n(v_iter, channels, i), + bytes_remain / channels); + get_random_bytes(runtime->dma_area + v_iter->chan_block * i, + (v_iter->b_rw - bytes_remain) / channels); + } + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_rand_i(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + size_t in_cur_block = runtime->dma_bytes - v_iter->buf_pos; + + if (v_iter->b_rw <= in_cur_block) { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], v_iter->b_rw); + } else { + get_random_bytes(&runtime->dma_area[v_iter->buf_pos], in_cur_block); + get_random_bytes(runtime->dma_area, v_iter->b_rw - in_cur_block); + } + inc_buf_pos(v_iter, v_iter->b_rw, runtime->dma_bytes); +} + +static void fill_block_random(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + if (v_iter->interleaved) + fill_block_rand_i(v_iter, runtime); + else + fill_block_rand_n(v_iter, runtime); +} + +static void fill_block(struct pcmtst_buf_iter *v_iter, struct snd_pcm_runtime *runtime) +{ + switch (fill_mode) { + case FILL_MODE_RAND: + fill_block_random(v_iter, runtime); + break; + case FILL_MODE_PAT: + fill_block_pattern(v_iter, runtime); + break; + } +} + +/* + * Here we iterate through the buffer by (buffer_size / iterates_per_second) bytes. + * The driver uses timer to simulate the hardware pointer moving, and notify the PCM middle layer + * about period elapsed. + */ +static void timer_timeout(struct timer_list *data) +{ + struct pcmtst_buf_iter *v_iter; + struct snd_pcm_substream *substream; + + v_iter = from_timer(v_iter, data, timer_instance); + substream = v_iter->substream; + + if (v_iter->suspend) + return; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !v_iter->is_buf_corrupted) + check_buf_block(v_iter, substream->runtime); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + fill_block(v_iter, substream->runtime); + else + inc_buf_pos(v_iter, v_iter->b_rw, substream->runtime->dma_bytes); + + v_iter->period_pos += v_iter->b_rw; + if (v_iter->period_pos >= v_iter->period_bytes) { + v_iter->period_pos %= v_iter->period_bytes; + snd_pcm_period_elapsed(substream); + } + + if (!v_iter->suspend) + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL + inject_delay); +} + +static int snd_pcmtst_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter; + + if (inject_open_err) + return -EBUSY; + + v_iter = kzalloc(sizeof(*v_iter), GFP_KERNEL); + if (!v_iter) + return -ENOMEM; + + v_iter->substream = substream; + runtime->hw = snd_pcmtst_hw; + runtime->private_data = v_iter; + + playback_capture_test = 0; + ioctl_reset_test = 0; + + timer_setup(&v_iter->timer_instance, timer_timeout, 0); + + return 0; +} + +static int snd_pcmtst_pcm_close(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + timer_shutdown_sync(&v_iter->timer_instance); + playback_capture_test = !v_iter->is_buf_corrupted; + kfree(v_iter); + return 0; +} + +static inline void reset_buf_iterator(struct pcmtst_buf_iter *v_iter) +{ + v_iter->buf_pos = 0; + v_iter->is_buf_corrupted = false; + v_iter->period_pos = 0; + v_iter->total_bytes = 0; +} + +static inline void start_pcmtest_timer(struct pcmtst_buf_iter *v_iter) +{ + v_iter->suspend = false; + mod_timer(&v_iter->timer_instance, jiffies + TIMER_INTERVAL); +} + +static int snd_pcmtst_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + if (inject_trigger_err) + return -EINVAL; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + reset_buf_iterator(v_iter); + start_pcmtest_timer(v_iter); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + start_pcmtest_timer(v_iter); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + // We can't call timer_shutdown_sync here, as it is forbidden to sleep here + v_iter->suspend = true; + timer_delete(&v_iter->timer_instance); + break; + } + + return 0; +} + +static snd_pcm_uframes_t snd_pcmtst_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, v_iter->buf_pos); +} + +static int snd_pcmtst_free(struct pcmtst *pcmtst) +{ + if (!pcmtst) + return 0; + kfree(pcmtst); + return 0; +} + +// These callbacks are required, but empty - all freeing occurs in pdev_remove +static int snd_pcmtst_dev_free(struct snd_device *device) +{ + return 0; +} + +static void pcmtst_pdev_release(struct device *dev) +{ +} + +static int snd_pcmtst_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct pcmtst_buf_iter *v_iter = runtime->private_data; + + if (inject_prepare_err) + return -EINVAL; + + v_iter->sample_bytes = samples_to_bytes(runtime, 1); + v_iter->period_bytes = snd_pcm_lib_period_bytes(substream); + v_iter->interleaved = true; + if (runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED || + runtime->access == SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED) { + v_iter->chan_block = snd_pcm_lib_buffer_bytes(substream) / runtime->channels; + v_iter->interleaved = false; + } + // We want to record RATE * ch_cnt samples per sec, it is rate * sample_bytes * ch_cnt bytes + v_iter->s_rw_ch = runtime->rate / TIMER_PER_SEC; + v_iter->b_rw = v_iter->s_rw_ch * v_iter->sample_bytes * runtime->channels; + + return 0; +} + +static int snd_pcmtst_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + if (inject_hwpars_err) + return -EBUSY; + return 0; +} + +static int snd_pcmtst_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_pcmtst_ioctl(struct snd_pcm_substream *substream, unsigned int cmd, void *arg) +{ + switch (cmd) { + case SNDRV_PCM_IOCTL1_RESET: + ioctl_reset_test = 1; + break; + } + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_pcmtst_sync_stop(struct snd_pcm_substream *substream) +{ + struct pcmtst_buf_iter *v_iter = substream->runtime->private_data; + + timer_delete_sync(&v_iter->timer_instance); + + return 0; +} + +static const struct snd_pcm_ops snd_pcmtst_playback_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .ioctl = snd_pcmtst_ioctl, + .sync_stop = snd_pcmtst_sync_stop, + .hw_free = snd_pcmtst_pcm_hw_free, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static const struct snd_pcm_ops snd_pcmtst_capture_ops = { + .open = snd_pcmtst_pcm_open, + .close = snd_pcmtst_pcm_close, + .trigger = snd_pcmtst_pcm_trigger, + .hw_params = snd_pcmtst_pcm_hw_params, + .hw_free = snd_pcmtst_pcm_hw_free, + .ioctl = snd_pcmtst_ioctl, + .sync_stop = snd_pcmtst_sync_stop, + .prepare = snd_pcmtst_pcm_prepare, + .pointer = snd_pcmtst_pcm_pointer, +}; + +static int snd_pcmtst_new_pcm(struct pcmtst *pcmtst) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(pcmtst->card, "PCMTest", 0, PLAYBACK_SUBSTREAM_CNT, + CAPTURE_SUBSTREAM_CNT, &pcm); + if (err < 0) + return err; + pcm->private_data = pcmtst; + strcpy(pcm->name, "PCMTest"); + pcmtst->pcm = pcm; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_pcmtst_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_pcmtst_capture_ops); + + err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, &pcmtst->pdev->dev, + 0, 128 * 1024); + return err; +} + +static int snd_pcmtst_create(struct snd_card *card, struct platform_device *pdev, + struct pcmtst **r_pcmtst) +{ + struct pcmtst *pcmtst; + int err; + static const struct snd_device_ops ops = { + .dev_free = snd_pcmtst_dev_free, + }; + + pcmtst = kzalloc(sizeof(*pcmtst), GFP_KERNEL); + if (!pcmtst) + return -ENOMEM; + pcmtst->card = card; + pcmtst->pdev = pdev; + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, pcmtst, &ops); + if (err < 0) + goto _err_free_chip; + + err = snd_pcmtst_new_pcm(pcmtst); + if (err < 0) + goto _err_free_chip; + + *r_pcmtst = pcmtst; + return 0; + +_err_free_chip: + snd_pcmtst_free(pcmtst); + return err; +} + +static int pcmtst_probe(struct platform_device *pdev) +{ + struct snd_card *card; + struct pcmtst *pcmtst; + int err; + + err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + if (err) + return err; + + err = snd_devm_card_new(&pdev->dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + err = snd_pcmtst_create(card, pdev, &pcmtst); + if (err < 0) + return err; + + strcpy(card->driver, "PCM-TEST Driver"); + strcpy(card->shortname, "PCM-Test"); + strcpy(card->longname, "PCM-Test virtual driver"); + + err = snd_card_register(card); + if (err < 0) + return err; + + platform_set_drvdata(pdev, pcmtst); + + return 0; +} + +static void pdev_remove(struct platform_device *pdev) +{ + struct pcmtst *pcmtst = platform_get_drvdata(pdev); + + snd_pcmtst_free(pcmtst); +} + +static struct platform_device pcmtst_pdev = { + .name = "pcmtest", + .dev.release = pcmtst_pdev_release, +}; + +static struct platform_driver pcmtst_pdrv = { + .probe = pcmtst_probe, + .remove_new = pdev_remove, + .driver = { + .name = "pcmtest", + }, +}; + +static ssize_t pattern_write(struct file *file, const char __user *u_buff, size_t len, loff_t *off) +{ + struct pattern_buf *patt_buf = file->f_inode->i_private; + ssize_t to_write = len; + + if (*off + to_write > MAX_PATTERN_LEN) + to_write = MAX_PATTERN_LEN - *off; + + // Crop silently everything over the buffer + if (to_write <= 0) + return len; + + if (copy_from_user(patt_buf->buf + *off, u_buff, to_write)) + return -EFAULT; + + patt_buf->len = *off + to_write; + *off += to_write; + + return to_write; +} + +static ssize_t pattern_read(struct file *file, char __user *u_buff, size_t len, loff_t *off) +{ + struct pattern_buf *patt_buf = file->f_inode->i_private; + ssize_t to_read = len; + + if (*off + to_read >= MAX_PATTERN_LEN) + to_read = MAX_PATTERN_LEN - *off; + if (to_read <= 0) + return 0; + + if (copy_to_user(u_buff, patt_buf->buf + *off, to_read)) + to_read = 0; + else + *off += to_read; + + return to_read; +} + +static const struct file_operations fill_pattern_fops = { + .read = pattern_read, + .write = pattern_write, +}; + +static int setup_patt_bufs(void) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(patt_bufs); i++) { + patt_bufs[i].buf = kzalloc(MAX_PATTERN_LEN, GFP_KERNEL); + if (!patt_bufs[i].buf) + break; + strcpy(patt_bufs[i].buf, DEFAULT_PATTERN); + patt_bufs[i].len = DEFAULT_PATTERN_LEN; + } + + return i; +} + +static const char * const pattern_files[] = { "fill_pattern0", "fill_pattern1", + "fill_pattern2", "fill_pattern3"}; +static int init_debug_files(int buf_count) +{ + size_t i; + char len_file_name[32]; + + driver_debug_dir = debugfs_create_dir("pcmtest", NULL); + if (IS_ERR(driver_debug_dir)) + return PTR_ERR(driver_debug_dir); + debugfs_create_u8("pc_test", 0444, driver_debug_dir, &playback_capture_test); + debugfs_create_u8("ioctl_test", 0444, driver_debug_dir, &ioctl_reset_test); + + for (i = 0; i < buf_count; i++) { + debugfs_create_file(pattern_files[i], 0600, driver_debug_dir, + &patt_bufs[i], &fill_pattern_fops); + snprintf(len_file_name, sizeof(len_file_name), "%s_len", pattern_files[i]); + debugfs_create_u32(len_file_name, 0444, driver_debug_dir, &patt_bufs[i].len); + } + + return 0; +} + +static void free_pattern_buffers(void) +{ + int i; + + for (i = 0; i < buf_allocated; i++) + kfree(patt_bufs[i].buf); +} + +static void clear_debug_files(void) +{ + debugfs_remove_recursive(driver_debug_dir); +} + +static int __init mod_init(void) +{ + int err = 0; + + buf_allocated = setup_patt_bufs(); + if (!buf_allocated) + return -ENOMEM; + + snd_pcmtst_hw.channels_max = buf_allocated; + + err = init_debug_files(buf_allocated); + if (err) + return err; + err = platform_device_register(&pcmtst_pdev); + if (err) + return err; + err = platform_driver_register(&pcmtst_pdrv); + if (err) + platform_device_unregister(&pcmtst_pdev); + return err; +} + +static void __exit mod_exit(void) +{ + clear_debug_files(); + free_pattern_buffers(); + + platform_driver_unregister(&pcmtst_pdrv); + platform_device_unregister(&pcmtst_pdev); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Orlov"); +module_init(mod_init); +module_exit(mod_exit); diff --git a/sound/drivers/pcsp/Makefile b/sound/drivers/pcsp/Makefile new file mode 100644 index 0000000000..77dc0ee1b5 --- /dev/null +++ b/sound/drivers/pcsp/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-pcsp-objs := pcsp.o pcsp_lib.o pcsp_mixer.o pcsp_input.o +obj-$(CONFIG_SND_PCSP) += snd-pcsp.o diff --git a/sound/drivers/pcsp/pcsp.c b/sound/drivers/pcsp/pcsp.c new file mode 100644 index 0000000000..c7be1c395b --- /dev/null +++ b/sound/drivers/pcsp/pcsp.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 1997-2001 David Woodhouse + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/bitops.h> +#include <linux/mm.h> +#include "pcsp_input.h" +#include "pcsp.h" + +MODULE_AUTHOR("Stas Sergeev <stsp@users.sourceforge.net>"); +MODULE_DESCRIPTION("PC-Speaker driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcspkr"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static bool enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ +static bool nopcm; /* Disable PCM capability of the driver */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for pcsp soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for pcsp soundcard."); +module_param(enable, bool, 0444); +MODULE_PARM_DESC(enable, "Enable PC-Speaker sound."); +module_param(nopcm, bool, 0444); +MODULE_PARM_DESC(nopcm, "Disable PC-Speaker PCM sound. Only beeps remain."); + +struct snd_pcsp pcsp_chip; + +static int snd_pcsp_create(struct snd_card *card) +{ + unsigned int resolution = hrtimer_resolution; + int div, min_div, order; + + if (!nopcm) { + if (resolution > PCSP_MAX_PERIOD_NS) { + printk(KERN_ERR "PCSP: Timer resolution is not sufficient " + "(%unS)\n", resolution); + printk(KERN_ERR "PCSP: Make sure you have HPET and ACPI " + "enabled.\n"); + printk(KERN_ERR "PCSP: Turned into nopcm mode.\n"); + nopcm = 1; + } + } + + if (loops_per_jiffy >= PCSP_MIN_LPJ && resolution <= PCSP_MIN_PERIOD_NS) + min_div = MIN_DIV; + else + min_div = MAX_DIV; +#if PCSP_DEBUG + printk(KERN_DEBUG "PCSP: lpj=%li, min_div=%i, res=%u\n", + loops_per_jiffy, min_div, resolution); +#endif + + div = MAX_DIV / min_div; + order = fls(div) - 1; + + pcsp_chip.max_treble = min(order, PCSP_MAX_TREBLE); + pcsp_chip.treble = min(pcsp_chip.max_treble, PCSP_DEFAULT_TREBLE); + pcsp_chip.playback_ptr = 0; + pcsp_chip.period_ptr = 0; + atomic_set(&pcsp_chip.timer_active, 0); + pcsp_chip.enable = 1; + pcsp_chip.pcspkr = 1; + + spin_lock_init(&pcsp_chip.substream_lock); + + pcsp_chip.card = card; + pcsp_chip.port = 0x61; + pcsp_chip.irq = -1; + pcsp_chip.dma = -1; + card->private_data = &pcsp_chip; + + return 0; +} + +static void pcsp_stop_beep(struct snd_pcsp *chip); + +static void alsa_card_pcsp_free(struct snd_card *card) +{ + pcsp_stop_beep(card->private_data); +} + +static int snd_card_pcsp_probe(int devnum, struct device *dev) +{ + struct snd_card *card; + int err; + + if (devnum != 0) + return -EINVAL; + + hrtimer_init(&pcsp_chip.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pcsp_chip.timer.function = pcsp_do_timer; + + err = snd_devm_card_new(dev, index, id, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + err = snd_pcsp_create(card); + if (err < 0) + return err; + + if (!nopcm) { + err = snd_pcsp_new_pcm(&pcsp_chip); + if (err < 0) + return err; + } + err = snd_pcsp_new_mixer(&pcsp_chip, nopcm); + if (err < 0) + return err; + + strcpy(card->driver, "PC-Speaker"); + strcpy(card->shortname, "pcsp"); + sprintf(card->longname, "Internal PC-Speaker at port 0x%x", + pcsp_chip.port); + + err = snd_card_register(card); + if (err < 0) + return err; + card->private_free = alsa_card_pcsp_free; + + return 0; +} + +static int alsa_card_pcsp_init(struct device *dev) +{ + int err; + + err = snd_card_pcsp_probe(0, dev); + if (err) { + printk(KERN_ERR "PC-Speaker initialization failed.\n"); + return err; + } + + /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */ + if (debug_pagealloc_enabled()) { + printk(KERN_WARNING "PCSP: CONFIG_DEBUG_PAGEALLOC is enabled, " + "which may make the sound noisy.\n"); + } + + return 0; +} + +static int pcsp_probe(struct platform_device *dev) +{ + int err; + + err = pcspkr_input_init(&pcsp_chip.input_dev, &dev->dev); + if (err < 0) + return err; + + err = alsa_card_pcsp_init(&dev->dev); + if (err < 0) + return err; + + platform_set_drvdata(dev, &pcsp_chip); + return 0; +} + +static void pcsp_stop_beep(struct snd_pcsp *chip) +{ + pcsp_sync_stop(chip); + pcspkr_stop_sound(); +} + +#ifdef CONFIG_PM_SLEEP +static int pcsp_suspend(struct device *dev) +{ + struct snd_pcsp *chip = dev_get_drvdata(dev); + pcsp_stop_beep(chip); + return 0; +} + +static SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL); +#define PCSP_PM_OPS &pcsp_pm +#else +#define PCSP_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static void pcsp_shutdown(struct platform_device *dev) +{ + struct snd_pcsp *chip = platform_get_drvdata(dev); + pcsp_stop_beep(chip); +} + +static struct platform_driver pcsp_platform_driver = { + .driver = { + .name = "pcspkr", + .pm = PCSP_PM_OPS, + }, + .probe = pcsp_probe, + .shutdown = pcsp_shutdown, +}; + +static int __init pcsp_init(void) +{ + if (!enable) + return -ENODEV; + return platform_driver_register(&pcsp_platform_driver); +} + +static void __exit pcsp_exit(void) +{ + platform_driver_unregister(&pcsp_platform_driver); +} + +module_init(pcsp_init); +module_exit(pcsp_exit); diff --git a/sound/drivers/pcsp/pcsp.h b/sound/drivers/pcsp/pcsp.h new file mode 100644 index 0000000000..036ad3c99a --- /dev/null +++ b/sound/drivers/pcsp/pcsp.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 1993-1997 Michael Beck + * Copyright (C) 1997-2001 David Woodhouse + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#ifndef __PCSP_H__ +#define __PCSP_H__ + +#include <linux/hrtimer.h> +#include <linux/i8253.h> +#include <linux/timex.h> + +#define PCSP_SOUND_VERSION 0x400 /* read 4.00 */ +#define PCSP_DEBUG 0 + +/* default timer freq for PC-Speaker: 18643 Hz */ +#define DIV_18KHZ 64 +#define MAX_DIV DIV_18KHZ +#define CALC_DIV(d) (MAX_DIV >> (d)) +#define CUR_DIV() CALC_DIV(chip->treble) +#define PCSP_MAX_TREBLE 1 + +/* unfortunately, with hrtimers 37KHz does not work very well :( */ +#define PCSP_DEFAULT_TREBLE 0 +#define MIN_DIV (MAX_DIV >> PCSP_MAX_TREBLE) + +/* wild guess */ +#define PCSP_MIN_LPJ 1000000 +#define PCSP_DEFAULT_SDIV (DIV_18KHZ >> 1) +#define PCSP_DEFAULT_SRATE (PIT_TICK_RATE / PCSP_DEFAULT_SDIV) +#define PCSP_INDEX_INC() (1 << (PCSP_MAX_TREBLE - chip->treble)) +#define PCSP_CALC_RATE(i) (PIT_TICK_RATE / CALC_DIV(i)) +#define PCSP_RATE() PCSP_CALC_RATE(chip->treble) +#define PCSP_MIN_RATE__1 MAX_DIV/PIT_TICK_RATE +#define PCSP_MAX_RATE__1 MIN_DIV/PIT_TICK_RATE +#define PCSP_MAX_PERIOD_NS (1000000000ULL * PCSP_MIN_RATE__1) +#define PCSP_MIN_PERIOD_NS (1000000000ULL * PCSP_MAX_RATE__1) +#define PCSP_CALC_NS(div) ({ \ + u64 __val = 1000000000ULL * (div); \ + do_div(__val, PIT_TICK_RATE); \ + __val; \ +}) +#define PCSP_PERIOD_NS() PCSP_CALC_NS(CUR_DIV()) + +#define PCSP_MAX_PERIOD_SIZE (64*1024) +#define PCSP_MAX_PERIODS 512 +#define PCSP_BUFFER_SIZE (128*1024) + +struct snd_pcsp { + struct snd_card *card; + struct snd_pcm *pcm; + struct input_dev *input_dev; + struct hrtimer timer; + unsigned short port, irq, dma; + spinlock_t substream_lock; + struct snd_pcm_substream *playback_substream; + unsigned int fmt_size; + unsigned int is_signed; + size_t playback_ptr; + size_t period_ptr; + atomic_t timer_active; + int thalf; + u64 ns_rem; + unsigned char val61; + int enable; + int max_treble; + int treble; + int pcspkr; +}; + +extern struct snd_pcsp pcsp_chip; + +extern enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle); +extern void pcsp_sync_stop(struct snd_pcsp *chip); + +extern int snd_pcsp_new_pcm(struct snd_pcsp *chip); +extern int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm); + +#endif diff --git a/sound/drivers/pcsp/pcsp_input.c b/sound/drivers/pcsp/pcsp_input.c new file mode 100644 index 0000000000..5a799f7f00 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_input.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PC Speaker beeper driver for Linux + * + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 1992 Orest Zborowski + */ + + +#include <linux/init.h> +#include <linux/input.h> +#include <linux/io.h> +#include "pcsp.h" +#include "pcsp_input.h" + +static void pcspkr_do_sound(unsigned int count) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&i8253_lock, flags); + + if (count) { + /* set command for counter 2, 2 byte write */ + outb_p(0xB6, 0x43); + /* select desired HZ */ + outb_p(count & 0xff, 0x42); + outb((count >> 8) & 0xff, 0x42); + /* enable counter 2 */ + outb_p(inb_p(0x61) | 3, 0x61); + } else { + /* disable counter 2 */ + outb(inb_p(0x61) & 0xFC, 0x61); + } + + raw_spin_unlock_irqrestore(&i8253_lock, flags); +} + +void pcspkr_stop_sound(void) +{ + pcspkr_do_sound(0); +} + +static int pcspkr_input_event(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + unsigned int count = 0; + + if (atomic_read(&pcsp_chip.timer_active) || !pcsp_chip.pcspkr) + return 0; + + switch (type) { + case EV_SND: + switch (code) { + case SND_BELL: + if (value) + value = 1000; + break; + case SND_TONE: + break; + default: + return -1; + } + break; + + default: + return -1; + } + + if (value > 20 && value < 32767) + count = PIT_TICK_RATE / value; + + pcspkr_do_sound(count); + + return 0; +} + +int pcspkr_input_init(struct input_dev **rdev, struct device *dev) +{ + int err; + + struct input_dev *input_dev = devm_input_allocate_device(dev); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "PC Speaker"; + input_dev->phys = "isa0061/input0"; + input_dev->id.bustype = BUS_ISA; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = dev; + + input_dev->evbit[0] = BIT(EV_SND); + input_dev->sndbit[0] = BIT(SND_BELL) | BIT(SND_TONE); + input_dev->event = pcspkr_input_event; + + err = input_register_device(input_dev); + if (err) + return err; + + *rdev = input_dev; + return 0; +} diff --git a/sound/drivers/pcsp/pcsp_input.h b/sound/drivers/pcsp/pcsp_input.h new file mode 100644 index 0000000000..42bfc9eab6 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_input.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#ifndef __PCSP_INPUT_H__ +#define __PCSP_INPUT_H__ + +int pcspkr_input_init(struct input_dev **rdev, struct device *dev); +void pcspkr_stop_sound(void); + +#endif diff --git a/sound/drivers/pcsp/pcsp_lib.c b/sound/drivers/pcsp/pcsp_lib.c new file mode 100644 index 0000000000..773db4bf08 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_lib.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PC-Speaker driver for Linux + * + * Copyright (C) 1993-1997 Michael Beck + * Copyright (C) 1997-2001 David Woodhouse + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#include <linux/module.h> +#include <linux/gfp.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <sound/pcm.h> +#include "pcsp.h" + +static bool nforce_wa; +module_param(nforce_wa, bool, 0444); +MODULE_PARM_DESC(nforce_wa, "Apply NForce chipset workaround " + "(expect bad sound)"); + +#define DMIX_WANTS_S16 1 + +/* + * Call snd_pcm_period_elapsed in a work + * This avoids spinlock messes and long-running irq contexts + */ +static void pcsp_call_pcm_elapsed(struct work_struct *work) +{ + if (atomic_read(&pcsp_chip.timer_active)) { + struct snd_pcm_substream *substream; + substream = pcsp_chip.playback_substream; + if (substream) + snd_pcm_period_elapsed(substream); + } +} + +static DECLARE_WORK(pcsp_pcm_work, pcsp_call_pcm_elapsed); + +/* write the port and returns the next expire time in ns; + * called at the trigger-start and in hrtimer callback + */ +static u64 pcsp_timer_update(struct snd_pcsp *chip) +{ + unsigned char timer_cnt, val; + u64 ns; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned long flags; + + if (chip->thalf) { + outb(chip->val61, 0x61); + chip->thalf = 0; + return chip->ns_rem; + } + + substream = chip->playback_substream; + if (!substream) + return 0; + + runtime = substream->runtime; + /* assume it is mono! */ + val = runtime->dma_area[chip->playback_ptr + chip->fmt_size - 1]; + if (chip->is_signed) + val ^= 0x80; + timer_cnt = val * CUR_DIV() / 256; + + if (timer_cnt && chip->enable) { + raw_spin_lock_irqsave(&i8253_lock, flags); + if (!nforce_wa) { + outb_p(chip->val61, 0x61); + outb_p(timer_cnt, 0x42); + outb(chip->val61 ^ 1, 0x61); + } else { + outb(chip->val61 ^ 2, 0x61); + chip->thalf = 1; + } + raw_spin_unlock_irqrestore(&i8253_lock, flags); + } + + chip->ns_rem = PCSP_PERIOD_NS(); + ns = (chip->thalf ? PCSP_CALC_NS(timer_cnt) : chip->ns_rem); + chip->ns_rem -= ns; + return ns; +} + +static void pcsp_pointer_update(struct snd_pcsp *chip) +{ + struct snd_pcm_substream *substream; + size_t period_bytes, buffer_bytes; + int periods_elapsed; + unsigned long flags; + + /* update the playback position */ + substream = chip->playback_substream; + if (!substream) + return; + + period_bytes = snd_pcm_lib_period_bytes(substream); + buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + + spin_lock_irqsave(&chip->substream_lock, flags); + chip->playback_ptr += PCSP_INDEX_INC() * chip->fmt_size; + periods_elapsed = chip->playback_ptr - chip->period_ptr; + if (periods_elapsed < 0) { +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: buffer_bytes mod period_bytes != 0 ? " + "(%zi %zi %zi)\n", + chip->playback_ptr, period_bytes, buffer_bytes); +#endif + periods_elapsed += buffer_bytes; + } + periods_elapsed /= period_bytes; + /* wrap the pointer _before_ calling snd_pcm_period_elapsed(), + * or ALSA will BUG on us. */ + chip->playback_ptr %= buffer_bytes; + + if (periods_elapsed) { + chip->period_ptr += periods_elapsed * period_bytes; + chip->period_ptr %= buffer_bytes; + queue_work(system_highpri_wq, &pcsp_pcm_work); + } + spin_unlock_irqrestore(&chip->substream_lock, flags); +} + +enum hrtimer_restart pcsp_do_timer(struct hrtimer *handle) +{ + struct snd_pcsp *chip = container_of(handle, struct snd_pcsp, timer); + int pointer_update; + u64 ns; + + if (!atomic_read(&chip->timer_active) || !chip->playback_substream) + return HRTIMER_NORESTART; + + pointer_update = !chip->thalf; + ns = pcsp_timer_update(chip); + if (!ns) { + printk(KERN_WARNING "PCSP: unexpected stop\n"); + return HRTIMER_NORESTART; + } + + if (pointer_update) + pcsp_pointer_update(chip); + + hrtimer_forward_now(handle, ns_to_ktime(ns)); + + return HRTIMER_RESTART; +} + +static int pcsp_start_playing(struct snd_pcsp *chip) +{ +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: start_playing called\n"); +#endif + if (atomic_read(&chip->timer_active)) { + printk(KERN_ERR "PCSP: Timer already active\n"); + return -EIO; + } + + raw_spin_lock(&i8253_lock); + chip->val61 = inb(0x61) | 0x03; + outb_p(0x92, 0x43); /* binary, mode 1, LSB only, ch 2 */ + raw_spin_unlock(&i8253_lock); + atomic_set(&chip->timer_active, 1); + chip->thalf = 0; + + hrtimer_start(&pcsp_chip.timer, 0, HRTIMER_MODE_REL); + return 0; +} + +static void pcsp_stop_playing(struct snd_pcsp *chip) +{ +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: stop_playing called\n"); +#endif + if (!atomic_read(&chip->timer_active)) + return; + + atomic_set(&chip->timer_active, 0); + raw_spin_lock(&i8253_lock); + /* restore the timer */ + outb_p(0xb6, 0x43); /* binary, mode 3, LSB/MSB, ch 2 */ + outb(chip->val61 & 0xFC, 0x61); + raw_spin_unlock(&i8253_lock); +} + +/* + * Force to stop and sync the stream + */ +void pcsp_sync_stop(struct snd_pcsp *chip) +{ + local_irq_disable(); + pcsp_stop_playing(chip); + local_irq_enable(); + hrtimer_cancel(&chip->timer); + cancel_work_sync(&pcsp_pcm_work); +} + +static int snd_pcsp_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: close called\n"); +#endif + pcsp_sync_stop(chip); + chip->playback_substream = NULL; + return 0; +} + +static int snd_pcsp_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + pcsp_sync_stop(chip); + return 0; +} + +static int snd_pcsp_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: hw_free called\n"); +#endif + pcsp_sync_stop(chip); + return 0; +} + +static int snd_pcsp_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + pcsp_sync_stop(chip); + chip->playback_ptr = 0; + chip->period_ptr = 0; + chip->fmt_size = + snd_pcm_format_physical_width(substream->runtime->format) >> 3; + chip->is_signed = snd_pcm_format_signed(substream->runtime->format); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: prepare called, " + "size=%zi psize=%zi f=%zi f1=%i fsize=%i\n", + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), + snd_pcm_lib_buffer_bytes(substream) / + snd_pcm_lib_period_bytes(substream), + substream->runtime->periods, + chip->fmt_size); +#endif + return 0; +} + +static int snd_pcsp_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: trigger called\n"); +#endif + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + return pcsp_start_playing(chip); + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + pcsp_stop_playing(chip); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_pcsp_playback_pointer(struct snd_pcm_substream + *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + unsigned int pos; + spin_lock(&chip->substream_lock); + pos = chip->playback_ptr; + spin_unlock(&chip->substream_lock); + return bytes_to_frames(substream->runtime, pos); +} + +static const struct snd_pcm_hardware snd_pcsp_playback = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_HALF_DUPLEX | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 +#if DMIX_WANTS_S16 + | SNDRV_PCM_FMTBIT_S16_LE +#endif + ), + .rates = SNDRV_PCM_RATE_KNOT, + .rate_min = PCSP_DEFAULT_SRATE, + .rate_max = PCSP_DEFAULT_SRATE, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = PCSP_BUFFER_SIZE, + .period_bytes_min = 64, + .period_bytes_max = PCSP_MAX_PERIOD_SIZE, + .periods_min = 2, + .periods_max = PCSP_MAX_PERIODS, + .fifo_size = 0, +}; + +static int snd_pcsp_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_pcsp *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: open called\n"); +#endif + if (atomic_read(&chip->timer_active)) { + printk(KERN_ERR "PCSP: still active!!\n"); + return -EBUSY; + } + runtime->hw = snd_pcsp_playback; + chip->playback_substream = substream; + return 0; +} + +static const struct snd_pcm_ops snd_pcsp_playback_ops = { + .open = snd_pcsp_playback_open, + .close = snd_pcsp_playback_close, + .hw_params = snd_pcsp_playback_hw_params, + .hw_free = snd_pcsp_playback_hw_free, + .prepare = snd_pcsp_playback_prepare, + .trigger = snd_pcsp_trigger, + .pointer = snd_pcsp_playback_pointer, +}; + +int snd_pcsp_new_pcm(struct snd_pcsp *chip) +{ + int err; + + err = snd_pcm_new(chip->card, "pcspeaker", 0, 1, 0, &chip->pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(chip->pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_pcsp_playback_ops); + + chip->pcm->private_data = chip; + chip->pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + strcpy(chip->pcm->name, "pcsp"); + + snd_pcm_set_managed_buffer_all(chip->pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + PCSP_BUFFER_SIZE, + PCSP_BUFFER_SIZE); + + return 0; +} diff --git a/sound/drivers/pcsp/pcsp_mixer.c b/sound/drivers/pcsp/pcsp_mixer.c new file mode 100644 index 0000000000..da33e5b620 --- /dev/null +++ b/sound/drivers/pcsp/pcsp_mixer.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PC-Speaker driver for Linux + * + * Mixer implementation. + * Copyright (C) 2001-2008 Stas Sergeev + */ + +#include <sound/core.h> +#include <sound/control.h> +#include "pcsp.h" + + +static int pcsp_enable_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int pcsp_enable_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->enable; + return 0; +} + +static int pcsp_enable_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int enab = ucontrol->value.integer.value[0]; + if (enab != chip->enable) { + chip->enable = enab; + changed = 1; + } + return changed; +} + +static int pcsp_treble_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = chip->max_treble + 1; + if (uinfo->value.enumerated.item > chip->max_treble) + uinfo->value.enumerated.item = chip->max_treble; + sprintf(uinfo->value.enumerated.name, "%lu", + (unsigned long)PCSP_CALC_RATE(uinfo->value.enumerated.item)); + return 0; +} + +static int pcsp_treble_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->treble; + return 0; +} + +static int pcsp_treble_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int treble = ucontrol->value.enumerated.item[0]; + if (treble != chip->treble) { + chip->treble = treble; +#if PCSP_DEBUG + printk(KERN_INFO "PCSP: rate set to %li\n", PCSP_RATE()); +#endif + changed = 1; + } + return changed; +} + +static int pcsp_pcspkr_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int pcsp_pcspkr_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = chip->pcspkr; + return 0; +} + +static int pcsp_pcspkr_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_pcsp *chip = snd_kcontrol_chip(kcontrol); + int changed = 0; + int spkr = ucontrol->value.integer.value[0]; + if (spkr != chip->pcspkr) { + chip->pcspkr = spkr; + changed = 1; + } + return changed; +} + +#define PCSP_MIXER_CONTROL(ctl_type, ctl_name) \ +{ \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = ctl_name, \ + .info = pcsp_##ctl_type##_info, \ + .get = pcsp_##ctl_type##_get, \ + .put = pcsp_##ctl_type##_put, \ +} + +static const struct snd_kcontrol_new snd_pcsp_controls_pcm[] = { + PCSP_MIXER_CONTROL(enable, "Master Playback Switch"), + PCSP_MIXER_CONTROL(treble, "BaseFRQ Playback Volume"), +}; + +static const struct snd_kcontrol_new snd_pcsp_controls_spkr[] = { + PCSP_MIXER_CONTROL(pcspkr, "Beep Playback Switch"), +}; + +static int snd_pcsp_ctls_add(struct snd_pcsp *chip, + const struct snd_kcontrol_new *ctls, int num) +{ + int i, err; + struct snd_card *card = chip->card; + for (i = 0; i < num; i++) { + err = snd_ctl_add(card, snd_ctl_new1(ctls + i, chip)); + if (err < 0) + return err; + } + return 0; +} + +int snd_pcsp_new_mixer(struct snd_pcsp *chip, int nopcm) +{ + int err; + struct snd_card *card = chip->card; + + if (!nopcm) { + err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_pcm, + ARRAY_SIZE(snd_pcsp_controls_pcm)); + if (err < 0) + return err; + } + err = snd_pcsp_ctls_add(chip, snd_pcsp_controls_spkr, + ARRAY_SIZE(snd_pcsp_controls_spkr)); + if (err < 0) + return err; + + strcpy(card->mixername, "PC-Speaker"); + + return 0; +} diff --git a/sound/drivers/portman2x4.c b/sound/drivers/portman2x4.c new file mode 100644 index 0000000000..619e3f5944 --- /dev/null +++ b/sound/drivers/portman2x4.c @@ -0,0 +1,849 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Midiman Portman2x4 parallel port midi interface + * + * Copyright (c) by Levent Guendogdu <levon@feature-it.com> + * + * 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 <linux/init.h> +#include <linux/platform_device.h> +#include <linux/parport.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.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 bool 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, 0444); +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard."); + +MODULE_AUTHOR("Levent Guendogdu, Tobias Gehrig, Matthias Koenig"); +MODULE_DESCRIPTION("Midiman Portman2x4"); +MODULE_LICENSE("GPL"); + +/********************************************************************* + * 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 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 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_status(struct portman *pm) +{ + return parport_read_status(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 it 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 const 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 const 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 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(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 void 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_add(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 int snd_portman_dev_probe(struct pardevice *pardev) +{ + if (strcmp(pardev->name, DRIVER_NAME)) + return -ENODEV; + + return 0; +} + +static struct parport_driver portman_parport_driver = { + .name = "portman2x4", + .probe = snd_portman_dev_probe, + .match_port = snd_portman_attach, + .detach = snd_portman_detach, + .devmodel = true, +}; + +/********************************************************************* + * 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) { + parport_release(pardev); + parport_unregister_device(pardev); + } + + portman_free(pm); +} + +static int 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; + struct pardev_cb portman_cb = { + .preempt = NULL, + .wakeup = NULL, + .irq_func = snd_portman_interrupt, /* ISR */ + .flags = PARPORT_DEV_EXCL, /* flags */ + }; + + p = platform_get_drvdata(pdev); + platform_set_drvdata(pdev, NULL); + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) + return -ENOENT; + + err = snd_card_new(&pdev->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) { + snd_printd("Cannot create card\n"); + return err; + } + 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); + + portman_cb.private = card; /* private */ + pardev = parport_register_dev_model(p, /* port */ + DRIVER_NAME, /* name */ + &portman_cb, /* callbacks */ + pdev->id); /* device number */ + if (pardev == NULL) { + snd_printd("Cannot register pardevice\n"); + err = -EIO; + goto __err; + } + + /* claim parport */ + if (parport_claim(pardev)) { + snd_printd("Cannot claim parport 0x%lx\n", pardev->port->base); + err = -EIO; + goto free_pardev; + } + + err = portman_create(card, pardev, &pm); + if (err < 0) { + snd_printd("Cannot create main component\n"); + goto release_pardev; + } + card->private_data = pm; + card->private_free = snd_portman_card_private_free; + + err = portman_probe(p); + if (err) { + err = -EIO; + goto __err; + } + + err = snd_portman_rawmidi_create(card); + if (err < 0) { + snd_printd("Creating Rawmidi component failed\n"); + goto __err; + } + + /* init device */ + err = portman_device_init(pm); + if (err < 0) + goto __err; + + platform_set_drvdata(pdev, card); + + /* At this point card will be usable */ + err = snd_card_register(card); + if (err < 0) { + snd_printd("Cannot register card\n"); + goto __err; + } + + snd_printk(KERN_INFO "Portman 2x4 on 0x%lx\n", p->base); + return 0; + +release_pardev: + parport_release(pardev); +free_pardev: + parport_unregister_device(pardev); +__err: + snd_card_free(card); + return err; +} + +static void snd_portman_remove(struct platform_device *pdev) +{ + struct snd_card *card = platform_get_drvdata(pdev); + + if (card) + snd_card_free(card); +} + + +static struct platform_driver snd_portman_driver = { + .probe = snd_portman_probe, + .remove_new = snd_portman_remove, + .driver = { + .name = PLATFORM_DRIVER, + } +}; + +/********************************************************************* + * module init stuff + *********************************************************************/ +static void 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; + + err = platform_driver_register(&snd_portman_driver); + if (err < 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); diff --git a/sound/drivers/serial-generic.c b/sound/drivers/serial-generic.c new file mode 100644 index 0000000000..c8db6c75d1 --- /dev/null +++ b/sound/drivers/serial-generic.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * serial-generic.c + * Copyright (c) by Daniel Kaehn <kaehndan@gmail.com + * Based on serial-u16550.c by Jaroslav Kysela <perex@perex.cz>, + * Isaku Yamahata <yamahata@private.email.ne.jp>, + * George Hansper <ghansper@apana.org.au>, + * Hannu Savolainen + * + * Generic serial MIDI driver using the serdev serial bus API for hardware interaction + */ + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/serdev.h> +#include <linux/serial_reg.h> +#include <linux/slab.h> +#include <linux/dev_printk.h> + +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/initval.h> + +MODULE_DESCRIPTION("Generic serial MIDI driver"); +MODULE_LICENSE("GPL"); + +#define SERIAL_MODE_INPUT_OPEN 1 +#define SERIAL_MODE_OUTPUT_OPEN 2 +#define SERIAL_MODE_INPUT_TRIGGERED 3 +#define SERIAL_MODE_OUTPUT_TRIGGERED 4 + +#define SERIAL_TX_STATE_ACTIVE 1 +#define SERIAL_TX_STATE_WAKEUP 2 + +struct snd_serial_generic { + struct serdev_device *serdev; + + struct snd_card *card; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_output; + struct snd_rawmidi_substream *midi_input; + + unsigned int baudrate; + + unsigned long filemode; /* open status of file */ + struct work_struct tx_work; + unsigned long tx_state; + +}; + +static void snd_serial_generic_tx_wakeup(struct snd_serial_generic *drvdata) +{ + if (test_and_set_bit(SERIAL_TX_STATE_ACTIVE, &drvdata->tx_state)) + set_bit(SERIAL_TX_STATE_WAKEUP, &drvdata->tx_state); + + schedule_work(&drvdata->tx_work); +} + +#define INTERNAL_BUF_SIZE 256 + +static void snd_serial_generic_tx_work(struct work_struct *work) +{ + static char buf[INTERNAL_BUF_SIZE]; + int num_bytes; + struct snd_serial_generic *drvdata = container_of(work, struct snd_serial_generic, + tx_work); + struct snd_rawmidi_substream *substream = drvdata->midi_output; + + clear_bit(SERIAL_TX_STATE_WAKEUP, &drvdata->tx_state); + + while (!snd_rawmidi_transmit_empty(substream)) { + + if (!test_bit(SERIAL_MODE_OUTPUT_OPEN, &drvdata->filemode)) + break; + + num_bytes = snd_rawmidi_transmit_peek(substream, buf, INTERNAL_BUF_SIZE); + num_bytes = serdev_device_write_buf(drvdata->serdev, buf, num_bytes); + + if (!num_bytes) + break; + + snd_rawmidi_transmit_ack(substream, num_bytes); + + if (!test_bit(SERIAL_TX_STATE_WAKEUP, &drvdata->tx_state)) + break; + } + + clear_bit(SERIAL_TX_STATE_ACTIVE, &drvdata->tx_state); +} + +static void snd_serial_generic_write_wakeup(struct serdev_device *serdev) +{ + struct snd_serial_generic *drvdata = serdev_device_get_drvdata(serdev); + + snd_serial_generic_tx_wakeup(drvdata); +} + +static int snd_serial_generic_receive_buf(struct serdev_device *serdev, + const unsigned char *buf, size_t count) +{ + int ret; + struct snd_serial_generic *drvdata = serdev_device_get_drvdata(serdev); + + if (!test_bit(SERIAL_MODE_INPUT_OPEN, &drvdata->filemode)) + return 0; + + ret = snd_rawmidi_receive(drvdata->midi_input, buf, count); + return ret < 0 ? 0 : ret; +} + +static const struct serdev_device_ops snd_serial_generic_serdev_device_ops = { + .receive_buf = snd_serial_generic_receive_buf, + .write_wakeup = snd_serial_generic_write_wakeup +}; + +static int snd_serial_generic_ensure_serdev_open(struct snd_serial_generic *drvdata) +{ + int err; + unsigned int actual_baud; + + if (drvdata->filemode) + return 0; + + dev_dbg(drvdata->card->dev, "Opening serial port for card %s\n", + drvdata->card->shortname); + err = serdev_device_open(drvdata->serdev); + if (err < 0) + return err; + + actual_baud = serdev_device_set_baudrate(drvdata->serdev, + drvdata->baudrate); + if (actual_baud != drvdata->baudrate) { + dev_warn(drvdata->card->dev, "requested %d baud for card %s but it was actually set to %d\n", + drvdata->baudrate, drvdata->card->shortname, actual_baud); + } + + return 0; +} + +static int snd_serial_generic_input_open(struct snd_rawmidi_substream *substream) +{ + int err; + struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; + + dev_dbg(drvdata->card->dev, "Opening input for card %s\n", + drvdata->card->shortname); + + err = snd_serial_generic_ensure_serdev_open(drvdata); + if (err < 0) + return err; + + set_bit(SERIAL_MODE_INPUT_OPEN, &drvdata->filemode); + drvdata->midi_input = substream; + return 0; +} + +static int snd_serial_generic_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; + + dev_dbg(drvdata->card->dev, "Closing input for card %s\n", + drvdata->card->shortname); + + clear_bit(SERIAL_MODE_INPUT_OPEN, &drvdata->filemode); + clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); + + drvdata->midi_input = NULL; + + if (!drvdata->filemode) + serdev_device_close(drvdata->serdev); + return 0; +} + +static void snd_serial_generic_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; + + if (up) + set_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); + else + clear_bit(SERIAL_MODE_INPUT_TRIGGERED, &drvdata->filemode); +} + +static int snd_serial_generic_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; + int err; + + dev_dbg(drvdata->card->dev, "Opening output for card %s\n", + drvdata->card->shortname); + + err = snd_serial_generic_ensure_serdev_open(drvdata); + if (err < 0) + return err; + + set_bit(SERIAL_MODE_OUTPUT_OPEN, &drvdata->filemode); + + drvdata->midi_output = substream; + return 0; +}; + +static int snd_serial_generic_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; + + dev_dbg(drvdata->card->dev, "Closing output for card %s\n", + drvdata->card->shortname); + + clear_bit(SERIAL_MODE_OUTPUT_OPEN, &drvdata->filemode); + clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); + + if (!drvdata->filemode) + serdev_device_close(drvdata->serdev); + + drvdata->midi_output = NULL; + + return 0; +}; + +static void snd_serial_generic_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; + + if (up) + set_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); + else + clear_bit(SERIAL_MODE_OUTPUT_TRIGGERED, &drvdata->filemode); + + if (up) + snd_serial_generic_tx_wakeup(drvdata); +} + +static void snd_serial_generic_output_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_serial_generic *drvdata = substream->rmidi->card->private_data; + + /* Flush any pending characters */ + serdev_device_write_flush(drvdata->serdev); + cancel_work_sync(&drvdata->tx_work); +} + +static const struct snd_rawmidi_ops snd_serial_generic_output = { + .open = snd_serial_generic_output_open, + .close = snd_serial_generic_output_close, + .trigger = snd_serial_generic_output_trigger, + .drain = snd_serial_generic_output_drain, +}; + +static const struct snd_rawmidi_ops snd_serial_generic_input = { + .open = snd_serial_generic_input_open, + .close = snd_serial_generic_input_close, + .trigger = snd_serial_generic_input_trigger, +}; + +static void snd_serial_generic_parse_dt(struct serdev_device *serdev, + struct snd_serial_generic *drvdata) +{ + int err; + + err = of_property_read_u32(serdev->dev.of_node, "current-speed", + &drvdata->baudrate); + if (err < 0) { + dev_dbg(drvdata->card->dev, + "MIDI device reading of current-speed DT param failed with error %d, using default of 38400\n", + err); + drvdata->baudrate = 38400; + } + +} + +static void snd_serial_generic_substreams(struct snd_rawmidi_str *stream, int dev_num) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + sprintf(substream->name, "Serial MIDI %d-%d", dev_num, substream->number); + } +} + +static int snd_serial_generic_rmidi(struct snd_serial_generic *drvdata, + int outs, int ins, struct snd_rawmidi **rmidi) +{ + struct snd_rawmidi *rrawmidi; + int err; + + err = snd_rawmidi_new(drvdata->card, drvdata->card->driver, 0, + outs, ins, &rrawmidi); + + if (err < 0) + return err; + + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_serial_generic_input); + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_serial_generic_output); + strcpy(rrawmidi->name, drvdata->card->shortname); + + snd_serial_generic_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT], + drvdata->serdev->ctrl->nr); + snd_serial_generic_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT], + drvdata->serdev->ctrl->nr); + + rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + if (rmidi) + *rmidi = rrawmidi; + return 0; +} + +static int snd_serial_generic_probe(struct serdev_device *serdev) +{ + struct snd_card *card; + struct snd_serial_generic *drvdata; + int err; + + err = snd_devm_card_new(&serdev->dev, SNDRV_DEFAULT_IDX1, + SNDRV_DEFAULT_STR1, THIS_MODULE, + sizeof(struct snd_serial_generic), &card); + + if (err < 0) + return err; + + strcpy(card->driver, "SerialMIDI"); + sprintf(card->shortname, "SerialMIDI-%d", serdev->ctrl->nr); + sprintf(card->longname, "Serial MIDI device at serial%d", serdev->ctrl->nr); + + drvdata = card->private_data; + + drvdata->serdev = serdev; + drvdata->card = card; + + snd_serial_generic_parse_dt(serdev, drvdata); + + INIT_WORK(&drvdata->tx_work, snd_serial_generic_tx_work); + + err = snd_serial_generic_rmidi(drvdata, 1, 1, &drvdata->rmidi); + if (err < 0) + return err; + + serdev_device_set_client_ops(serdev, &snd_serial_generic_serdev_device_ops); + serdev_device_set_drvdata(drvdata->serdev, drvdata); + + err = snd_card_register(card); + if (err < 0) + return err; + + return 0; +} + +static const struct of_device_id snd_serial_generic_dt_ids[] = { + { .compatible = "serial-midi" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, snd_serial_generic_dt_ids); + +static struct serdev_device_driver snd_serial_generic_driver = { + .driver = { + .name = "snd-serial-generic", + .of_match_table = snd_serial_generic_dt_ids, + }, + .probe = snd_serial_generic_probe, +}; + +module_serdev_device_driver(snd_serial_generic_driver); diff --git a/sound/drivers/serial-u16550.c b/sound/drivers/serial-u16550.c new file mode 100644 index 0000000000..3cbc7a4adc --- /dev/null +++ b/sound/drivers/serial-u16550.c @@ -0,0 +1,992 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * serial.c + * Copyright (c) by Jaroslav Kysela <perex@perex.cz>, + * Isaku Yamahata <yamahata@private.email.ne.jp>, + * George Hansper <ghansper@apana.org.au>, + * Hannu Savolainen + * + * This code is based on the code from ALSA 0.5.9, but heavily rewritten. + * + * Sat Mar 31 17:27:57 PST 2001 tim.mann@compaq.com + * Added support for the Midiator MS-124T and for the MS-124W in + * Single Addressed (S/A) or Multiple Burst (M/B) mode, with + * power derived either parasitically from the serial port or + * from a separate power supply. + * + * More documentation can be found in serial-u16550.txt. + */ + +#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/module.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/rawmidi.h> +#include <sound/initval.h> + +#include <linux/serial_reg.h> +#include <linux/jiffies.h> + +MODULE_DESCRIPTION("MIDI serial u16550"); +MODULE_LICENSE("GPL"); + +#define SNDRV_SERIAL_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */ +#define SNDRV_SERIAL_MS124T 1 /* Midiator MS-124T */ +#define SNDRV_SERIAL_MS124W_SA 2 /* Midiator MS-124W in S/A mode */ +#define SNDRV_SERIAL_MS124W_MB 3 /* Midiator MS-124W in M/B mode */ +#define SNDRV_SERIAL_GENERIC 4 /* Generic Interface */ +#define SNDRV_SERIAL_MAX_ADAPTOR SNDRV_SERIAL_GENERIC +static const char * const adaptor_names[] = { + "Soundcanvas", + "MS-124T", + "MS-124W S/A", + "MS-124W M/B", + "Generic" +}; + +#define SNDRV_SERIAL_NORMALBUFF 0 /* Normal blocking buffer operation */ +#define SNDRV_SERIAL_DROPBUFF 1 /* Non-blocking discard operation */ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x3f8,0x2f8,0x3e8,0x2e8 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,4,5,7,9,10,11,14,15 */ +static int speed[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 38400}; /* 9600,19200,38400,57600,115200 */ +static int base[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 115200}; /* baud base */ +static int outs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */ +static int ins[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; /* 1 to 16 */ +static int adaptor[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = SNDRV_SERIAL_SOUNDCANVAS}; +static bool droponfull[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS -1)] = SNDRV_SERIAL_NORMALBUFF }; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Serial MIDI."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Serial MIDI."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable UART16550A chip."); +module_param_hw_array(port, long, ioport, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for UART16550A chip."); +module_param_hw_array(irq, int, irq, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for UART16550A chip."); +module_param_array(speed, int, NULL, 0444); +MODULE_PARM_DESC(speed, "Speed in bauds."); +module_param_array(base, int, NULL, 0444); +MODULE_PARM_DESC(base, "Base for divisor in bauds."); +module_param_array(outs, int, NULL, 0444); +MODULE_PARM_DESC(outs, "Number of MIDI outputs."); +module_param_array(ins, int, NULL, 0444); +MODULE_PARM_DESC(ins, "Number of MIDI inputs."); +module_param_array(droponfull, bool, NULL, 0444); +MODULE_PARM_DESC(droponfull, "Flag to enable drop-on-full buffer mode"); + +module_param_array(adaptor, int, NULL, 0444); +MODULE_PARM_DESC(adaptor, "Type of adaptor."); + +/*#define SNDRV_SERIAL_MS124W_MB_NOCOMBO 1*/ /* Address outs as 0-3 instead of bitmap */ + +#define SNDRV_SERIAL_MAX_OUTS 16 /* max 64, min 16 */ +#define SNDRV_SERIAL_MAX_INS 16 /* max 64, min 16 */ + +#define TX_BUFF_SIZE (1<<15) /* Must be 2^n */ +#define TX_BUFF_MASK (TX_BUFF_SIZE - 1) + +#define SERIAL_MODE_NOT_OPENED (0) +#define SERIAL_MODE_INPUT_OPEN (1 << 0) +#define SERIAL_MODE_OUTPUT_OPEN (1 << 1) +#define SERIAL_MODE_INPUT_TRIGGERED (1 << 2) +#define SERIAL_MODE_OUTPUT_TRIGGERED (1 << 3) + +struct snd_uart16550 { + struct snd_card *card; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_output[SNDRV_SERIAL_MAX_OUTS]; + struct snd_rawmidi_substream *midi_input[SNDRV_SERIAL_MAX_INS]; + + int filemode; /* open status of file */ + + spinlock_t open_lock; + + int irq; + + unsigned long base; + + unsigned int speed; + unsigned int speed_base; + unsigned char divisor; + + unsigned char old_divisor_lsb; + unsigned char old_divisor_msb; + unsigned char old_line_ctrl_reg; + + /* parameter for using of write loop */ + short int fifo_limit; /* used in uart16550 */ + short int fifo_count; /* used in uart16550 */ + + /* type of adaptor */ + int adaptor; + + /* inputs */ + int prev_in; + unsigned char rstatus; + + /* outputs */ + int prev_out; + unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS]; + + /* write buffer and its writing/reading position */ + unsigned char tx_buff[TX_BUFF_SIZE]; + int buff_in_count; + int buff_in; + int buff_out; + int drop_on_full; + + /* wait timer */ + unsigned int timer_running:1; + struct timer_list buffer_timer; + +}; + +static struct platform_device *devices[SNDRV_CARDS]; + +static inline void snd_uart16550_add_timer(struct snd_uart16550 *uart) +{ + if (!uart->timer_running) { + /* timer 38600bps * 10bit * 16byte */ + mod_timer(&uart->buffer_timer, jiffies + (HZ + 255) / 256); + uart->timer_running = 1; + } +} + +static inline void snd_uart16550_del_timer(struct snd_uart16550 *uart) +{ + if (uart->timer_running) { + del_timer(&uart->buffer_timer); + uart->timer_running = 0; + } +} + +/* This macro is only used in snd_uart16550_io_loop */ +static inline void snd_uart16550_buffer_output(struct snd_uart16550 *uart) +{ + unsigned short buff_out = uart->buff_out; + if (uart->buff_in_count > 0) { + outb(uart->tx_buff[buff_out], uart->base + UART_TX); + uart->fifo_count++; + buff_out++; + buff_out &= TX_BUFF_MASK; + uart->buff_out = buff_out; + uart->buff_in_count--; + } +} + +/* This loop should be called with interrupts disabled + * We don't want to interrupt this, + * as we're already handling an interrupt + */ +static void snd_uart16550_io_loop(struct snd_uart16550 * uart) +{ + unsigned char c, status; + int substream; + + /* recall previous stream */ + substream = uart->prev_in; + + /* Read Loop */ + while ((status = inb(uart->base + UART_LSR)) & UART_LSR_DR) { + /* while receive data ready */ + c = inb(uart->base + UART_RX); + + /* keep track of last status byte */ + if (c & 0x80) + uart->rstatus = c; + + /* handle stream switch */ + if (uart->adaptor == SNDRV_SERIAL_GENERIC) { + if (uart->rstatus == 0xf5) { + if (c <= SNDRV_SERIAL_MAX_INS && c > 0) + substream = c - 1; + if (c != 0xf5) + /* prevent future bytes from being + interpreted as streams */ + uart->rstatus = 0; + } else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) + && uart->midi_input[substream]) + snd_rawmidi_receive(uart->midi_input[substream], + &c, 1); + } else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) && + uart->midi_input[substream]) + snd_rawmidi_receive(uart->midi_input[substream], &c, 1); + + if (status & UART_LSR_OE) + snd_printk(KERN_WARNING + "%s: Overrun on device at 0x%lx\n", + uart->rmidi->name, uart->base); + } + + /* remember the last stream */ + uart->prev_in = substream; + + /* no need of check SERIAL_MODE_OUTPUT_OPEN because if not, + buffer is never filled. */ + /* Check write status */ + if (status & UART_LSR_THRE) + uart->fifo_count = 0; + if (uart->adaptor == SNDRV_SERIAL_MS124W_SA + || uart->adaptor == SNDRV_SERIAL_GENERIC) { + /* Can't use FIFO, must send only when CTS is true */ + status = inb(uart->base + UART_MSR); + while (uart->fifo_count == 0 && (status & UART_MSR_CTS) && + uart->buff_in_count > 0) { + snd_uart16550_buffer_output(uart); + status = inb(uart->base + UART_MSR); + } + } else { + /* Write loop */ + while (uart->fifo_count < uart->fifo_limit /* Can we write ? */ + && uart->buff_in_count > 0) /* Do we want to? */ + snd_uart16550_buffer_output(uart); + } + if (uart->irq < 0 && uart->buff_in_count > 0) + snd_uart16550_add_timer(uart); +} + +/* NOTES ON SERVICING INTERUPTS + * --------------------------- + * After receiving a interrupt, it is important to indicate to the UART that + * this has been done. + * For a Rx interrupt, this is done by reading the received byte. + * For a Tx interrupt this is done by either: + * a) Writing a byte + * b) Reading the IIR + * It is particularly important to read the IIR if a Tx interrupt is received + * when there is no data in tx_buff[], as in this case there no other + * indication that the interrupt has been serviced, and it remains outstanding + * indefinitely. This has the curious side effect that and no further interrupts + * will be generated from this device AT ALL!!. + * It is also desirable to clear outstanding interrupts when the device is + * opened/closed. + * + * + * Note that some devices need OUT2 to be set before they will generate + * interrupts at all. (Possibly tied to an internal pull-up on CTS?) + */ +static irqreturn_t snd_uart16550_interrupt(int irq, void *dev_id) +{ + struct snd_uart16550 *uart; + + uart = dev_id; + spin_lock(&uart->open_lock); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) { + spin_unlock(&uart->open_lock); + return IRQ_NONE; + } + /* indicate to the UART that the interrupt has been serviced */ + inb(uart->base + UART_IIR); + snd_uart16550_io_loop(uart); + spin_unlock(&uart->open_lock); + return IRQ_HANDLED; +} + +/* When the polling mode, this function calls snd_uart16550_io_loop. */ +static void snd_uart16550_buffer_timer(struct timer_list *t) +{ + unsigned long flags; + struct snd_uart16550 *uart; + + uart = from_timer(uart, t, buffer_timer); + spin_lock_irqsave(&uart->open_lock, flags); + snd_uart16550_del_timer(uart); + snd_uart16550_io_loop(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +/* + * this method probes, if an uart sits on given port + * return 0 if found + * return negative error if not found + */ +static int snd_uart16550_detect(struct snd_uart16550 *uart) +{ + unsigned long io_base = uart->base; + int ok; + unsigned char c; + + /* Do some vague tests for the presence of the uart */ + if (io_base == 0 || io_base == SNDRV_AUTO_PORT) { + return -ENODEV; /* Not configured */ + } + + if (!devm_request_region(uart->card->dev, io_base, 8, "Serial MIDI")) { + snd_printk(KERN_ERR "u16550: can't grab port 0x%lx\n", io_base); + return -EBUSY; + } + + /* uart detected unless one of the following tests should fail */ + ok = 1; + /* 8 data-bits, 1 stop-bit, parity off, DLAB = 0 */ + outb(UART_LCR_WLEN8, io_base + UART_LCR); /* Line Control Register */ + c = inb(io_base + UART_IER); + /* The top four bits of the IER should always == 0 */ + if ((c & 0xf0) != 0) + ok = 0; /* failed */ + + outb(0xaa, io_base + UART_SCR); + /* Write arbitrary data into the scratch reg */ + c = inb(io_base + UART_SCR); + /* If it comes back, it's OK */ + if (c != 0xaa) + ok = 0; /* failed */ + + outb(0x55, io_base + UART_SCR); + /* Write arbitrary data into the scratch reg */ + c = inb(io_base + UART_SCR); + /* If it comes back, it's OK */ + if (c != 0x55) + ok = 0; /* failed */ + + return ok; +} + +static void snd_uart16550_do_open(struct snd_uart16550 * uart) +{ + char byte; + + /* Initialize basic variables */ + uart->buff_in_count = 0; + uart->buff_in = 0; + uart->buff_out = 0; + uart->fifo_limit = 1; + uart->fifo_count = 0; + uart->timer_running = 0; + + outb(UART_FCR_ENABLE_FIFO /* Enable FIFO's (if available) */ + | UART_FCR_CLEAR_RCVR /* Clear receiver FIFO */ + | UART_FCR_CLEAR_XMIT /* Clear transmitter FIFO */ + | UART_FCR_TRIGGER_4 /* Set FIFO trigger at 4-bytes */ + /* NOTE: interrupt generated after T=(time)4-bytes + * if less than UART_FCR_TRIGGER bytes received + */ + ,uart->base + UART_FCR); /* FIFO Control Register */ + + if ((inb(uart->base + UART_IIR) & 0xf0) == 0xc0) + uart->fifo_limit = 16; + if (uart->divisor != 0) { + uart->old_line_ctrl_reg = inb(uart->base + UART_LCR); + outb(UART_LCR_DLAB /* Divisor latch access bit */ + ,uart->base + UART_LCR); /* Line Control Register */ + uart->old_divisor_lsb = inb(uart->base + UART_DLL); + uart->old_divisor_msb = inb(uart->base + UART_DLM); + + outb(uart->divisor + ,uart->base + UART_DLL); /* Divisor Latch Low */ + outb(0 + ,uart->base + UART_DLM); /* Divisor Latch High */ + /* DLAB is reset to 0 in next outb() */ + } + /* Set serial parameters (parity off, etc) */ + outb(UART_LCR_WLEN8 /* 8 data-bits */ + | 0 /* 1 stop-bit */ + | 0 /* parity off */ + | 0 /* DLAB = 0 */ + ,uart->base + UART_LCR); /* Line Control Register */ + + switch (uart->adaptor) { + default: + outb(UART_MCR_RTS /* Set Request-To-Send line active */ + | UART_MCR_DTR /* Set Data-Terminal-Ready line active */ + | UART_MCR_OUT2 /* Set OUT2 - not always required, but when + * it is, it is ESSENTIAL for enabling interrupts + */ + ,uart->base + UART_MCR); /* Modem Control Register */ + break; + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* MS-124W can draw power from RTS and DTR if they + are in opposite states. */ + outb(UART_MCR_RTS | (0&UART_MCR_DTR) | UART_MCR_OUT2, + uart->base + UART_MCR); + break; + case SNDRV_SERIAL_MS124T: + /* MS-124T can draw power from RTS and/or DTR (preferably + both) if they are both asserted. */ + outb(UART_MCR_RTS | UART_MCR_DTR | UART_MCR_OUT2, + uart->base + UART_MCR); + break; + } + + if (uart->irq < 0) { + byte = (0 & UART_IER_RDI) /* Disable Receiver data interrupt */ + |(0 & UART_IER_THRI) /* Disable Transmitter holding register empty interrupt */ + ; + } else if (uart->adaptor == SNDRV_SERIAL_MS124W_SA) { + byte = UART_IER_RDI /* Enable Receiver data interrupt */ + | UART_IER_MSI /* Enable Modem status interrupt */ + ; + } else if (uart->adaptor == SNDRV_SERIAL_GENERIC) { + byte = UART_IER_RDI /* Enable Receiver data interrupt */ + | UART_IER_MSI /* Enable Modem status interrupt */ + | UART_IER_THRI /* Enable Transmitter holding register empty interrupt */ + ; + } else { + byte = UART_IER_RDI /* Enable Receiver data interrupt */ + | UART_IER_THRI /* Enable Transmitter holding register empty interrupt */ + ; + } + outb(byte, uart->base + UART_IER); /* Interrupt enable Register */ + + inb(uart->base + UART_LSR); /* Clear any pre-existing overrun indication */ + inb(uart->base + UART_IIR); /* Clear any pre-existing transmit interrupt */ + inb(uart->base + UART_RX); /* Clear any pre-existing receive interrupt */ +} + +static void snd_uart16550_do_close(struct snd_uart16550 * uart) +{ + if (uart->irq < 0) + snd_uart16550_del_timer(uart); + + /* NOTE: may need to disable interrupts before de-registering out handler. + * For now, the consequences are harmless. + */ + + outb((0 & UART_IER_RDI) /* Disable Receiver data interrupt */ + |(0 & UART_IER_THRI) /* Disable Transmitter holding register empty interrupt */ + ,uart->base + UART_IER); /* Interrupt enable Register */ + + switch (uart->adaptor) { + default: + outb((0 & UART_MCR_RTS) /* Deactivate Request-To-Send line */ + |(0 & UART_MCR_DTR) /* Deactivate Data-Terminal-Ready line */ + |(0 & UART_MCR_OUT2) /* Deactivate OUT2 */ + ,uart->base + UART_MCR); /* Modem Control Register */ + break; + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* MS-124W can draw power from RTS and DTR if they + are in opposite states; leave it powered. */ + outb(UART_MCR_RTS | (0&UART_MCR_DTR) | (0&UART_MCR_OUT2), + uart->base + UART_MCR); + break; + case SNDRV_SERIAL_MS124T: + /* MS-124T can draw power from RTS and/or DTR (preferably + both) if they are both asserted; leave it powered. */ + outb(UART_MCR_RTS | UART_MCR_DTR | (0&UART_MCR_OUT2), + uart->base + UART_MCR); + break; + } + + inb(uart->base + UART_IIR); /* Clear any outstanding interrupts */ + + /* Restore old divisor */ + if (uart->divisor != 0) { + outb(UART_LCR_DLAB /* Divisor latch access bit */ + ,uart->base + UART_LCR); /* Line Control Register */ + outb(uart->old_divisor_lsb + ,uart->base + UART_DLL); /* Divisor Latch Low */ + outb(uart->old_divisor_msb + ,uart->base + UART_DLM); /* Divisor Latch High */ + /* Restore old LCR (data bits, stop bits, parity, DLAB) */ + outb(uart->old_line_ctrl_reg + ,uart->base + UART_LCR); /* Line Control Register */ + } +} + +static int snd_uart16550_input_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_open(uart); + uart->filemode |= SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = substream; + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +} + +static int snd_uart16550_input_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + uart->filemode &= ~SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_close(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +} + +static void snd_uart16550_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (up) + uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED; + else + uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED; + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +static int snd_uart16550_output_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_open(uart); + uart->filemode |= SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = substream; + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +}; + +static int snd_uart16550_output_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_close(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +}; + +static inline int snd_uart16550_buffer_can_write(struct snd_uart16550 *uart, + int Num) +{ + if (uart->buff_in_count + Num < TX_BUFF_SIZE) + return 1; + else + return 0; +} + +static inline int snd_uart16550_write_buffer(struct snd_uart16550 *uart, + unsigned char byte) +{ + unsigned short buff_in = uart->buff_in; + if (uart->buff_in_count < TX_BUFF_SIZE) { + uart->tx_buff[buff_in] = byte; + buff_in++; + buff_in &= TX_BUFF_MASK; + uart->buff_in = buff_in; + uart->buff_in_count++; + if (uart->irq < 0) /* polling mode */ + snd_uart16550_add_timer(uart); + return 1; + } else + return 0; +} + +static int snd_uart16550_output_byte(struct snd_uart16550 *uart, + struct snd_rawmidi_substream *substream, + unsigned char midi_byte) +{ + if (uart->buff_in_count == 0 /* Buffer empty? */ + && ((uart->adaptor != SNDRV_SERIAL_MS124W_SA && + uart->adaptor != SNDRV_SERIAL_GENERIC) || + (uart->fifo_count == 0 /* FIFO empty? */ + && (inb(uart->base + UART_MSR) & UART_MSR_CTS)))) { /* CTS? */ + + /* Tx Buffer Empty - try to write immediately */ + if ((inb(uart->base + UART_LSR) & UART_LSR_THRE) != 0) { + /* Transmitter holding register (and Tx FIFO) empty */ + uart->fifo_count = 1; + outb(midi_byte, uart->base + UART_TX); + } else { + if (uart->fifo_count < uart->fifo_limit) { + uart->fifo_count++; + outb(midi_byte, uart->base + UART_TX); + } else { + /* Cannot write (buffer empty) - + * put char in buffer */ + snd_uart16550_write_buffer(uart, midi_byte); + } + } + } else { + if (!snd_uart16550_write_buffer(uart, midi_byte)) { + snd_printk(KERN_WARNING + "%s: Buffer overrun on device at 0x%lx\n", + uart->rmidi->name, uart->base); + return 0; + } + } + + return 1; +} + +static void snd_uart16550_output_write(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + unsigned char midi_byte, addr_byte; + struct snd_uart16550 *uart = substream->rmidi->private_data; + char first; + static unsigned long lasttime = 0; + + /* Interrupts are disabled during the updating of the tx_buff, + * since it is 'bad' to have two processes updating the same + * variables (ie buff_in & buff_out) + */ + + spin_lock_irqsave(&uart->open_lock, flags); + + if (uart->irq < 0) /* polling */ + snd_uart16550_io_loop(uart); + + if (uart->adaptor == SNDRV_SERIAL_MS124W_MB) { + while (1) { + /* buffer full? */ + /* in this mode we need two bytes of space */ + if (uart->buff_in_count > TX_BUFF_SIZE - 2) + break; + if (snd_rawmidi_transmit(substream, &midi_byte, 1) != 1) + break; +#ifdef SNDRV_SERIAL_MS124W_MB_NOCOMBO + /* select exactly one of the four ports */ + addr_byte = (1 << (substream->number + 4)) | 0x08; +#else + /* select any combination of the four ports */ + addr_byte = (substream->number << 4) | 0x08; + /* ...except none */ + if (addr_byte == 0x08) + addr_byte = 0xf8; +#endif + snd_uart16550_output_byte(uart, substream, addr_byte); + /* send midi byte */ + snd_uart16550_output_byte(uart, substream, midi_byte); + } + } else { + first = 0; + while (snd_rawmidi_transmit_peek(substream, &midi_byte, 1) == 1) { + /* Also send F5 after 3 seconds with no data + * to handle device disconnect */ + if (first == 0 && + (uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS || + uart->adaptor == SNDRV_SERIAL_GENERIC) && + (uart->prev_out != substream->number || + time_after(jiffies, lasttime + 3*HZ))) { + + if (snd_uart16550_buffer_can_write(uart, 3)) { + /* Roland Soundcanvas part selection */ + /* If this substream of the data is + * different previous substream + * in this uart, send the change part + * event + */ + uart->prev_out = substream->number; + /* change part */ + snd_uart16550_output_byte(uart, substream, + 0xf5); + /* data */ + snd_uart16550_output_byte(uart, substream, + uart->prev_out + 1); + /* If midi_byte is a data byte, + * send the previous status byte */ + if (midi_byte < 0x80 && + uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS) + snd_uart16550_output_byte(uart, substream, uart->prev_status[uart->prev_out]); + } else if (!uart->drop_on_full) + break; + + } + + /* send midi byte */ + if (!snd_uart16550_output_byte(uart, substream, midi_byte) && + !uart->drop_on_full ) + break; + + if (midi_byte >= 0x80 && midi_byte < 0xf0) + uart->prev_status[uart->prev_out] = midi_byte; + first = 1; + + snd_rawmidi_transmit_ack( substream, 1 ); + } + lasttime = jiffies; + } + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +static void snd_uart16550_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct snd_uart16550 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (up) + uart->filemode |= SERIAL_MODE_OUTPUT_TRIGGERED; + else + uart->filemode &= ~SERIAL_MODE_OUTPUT_TRIGGERED; + spin_unlock_irqrestore(&uart->open_lock, flags); + if (up) + snd_uart16550_output_write(substream); +} + +static const struct snd_rawmidi_ops snd_uart16550_output = +{ + .open = snd_uart16550_output_open, + .close = snd_uart16550_output_close, + .trigger = snd_uart16550_output_trigger, +}; + +static const struct snd_rawmidi_ops snd_uart16550_input = +{ + .open = snd_uart16550_input_open, + .close = snd_uart16550_input_close, + .trigger = snd_uart16550_input_trigger, +}; + +static int snd_uart16550_create(struct snd_card *card, + unsigned long iobase, + int irq, + unsigned int speed, + unsigned int base, + int adaptor, + int droponfull, + struct snd_uart16550 **ruart) +{ + struct snd_uart16550 *uart; + int err; + + + uart = devm_kzalloc(card->dev, sizeof(*uart), GFP_KERNEL); + if (!uart) + return -ENOMEM; + uart->adaptor = adaptor; + uart->card = card; + spin_lock_init(&uart->open_lock); + uart->irq = -1; + uart->base = iobase; + uart->drop_on_full = droponfull; + + err = snd_uart16550_detect(uart); + if (err <= 0) { + printk(KERN_ERR "no UART detected at 0x%lx\n", iobase); + return -ENODEV; + } + + if (irq >= 0 && irq != SNDRV_AUTO_IRQ) { + if (devm_request_irq(card->dev, irq, snd_uart16550_interrupt, + 0, "Serial MIDI", uart)) { + snd_printk(KERN_WARNING + "irq %d busy. Using Polling.\n", irq); + } else { + uart->irq = irq; + } + } + uart->divisor = base / speed; + uart->speed = base / (unsigned int)uart->divisor; + uart->speed_base = base; + uart->prev_out = -1; + uart->prev_in = 0; + uart->rstatus = 0; + memset(uart->prev_status, 0x80, sizeof(unsigned char) * SNDRV_SERIAL_MAX_OUTS); + timer_setup(&uart->buffer_timer, snd_uart16550_buffer_timer, 0); + uart->timer_running = 0; + + switch (uart->adaptor) { + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* MS-124W can draw power from RTS and DTR if they + are in opposite states. */ + outb(UART_MCR_RTS | (0&UART_MCR_DTR), uart->base + UART_MCR); + break; + case SNDRV_SERIAL_MS124T: + /* MS-124T can draw power from RTS and/or DTR (preferably + both) if they are asserted. */ + outb(UART_MCR_RTS | UART_MCR_DTR, uart->base + UART_MCR); + break; + default: + break; + } + + if (ruart) + *ruart = uart; + + return 0; +} + +static void snd_uart16550_substreams(struct snd_rawmidi_str *stream) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + sprintf(substream->name, "Serial MIDI %d", substream->number + 1); + } +} + +static int snd_uart16550_rmidi(struct snd_uart16550 *uart, int device, + int outs, int ins, + struct snd_rawmidi **rmidi) +{ + struct snd_rawmidi *rrawmidi; + int err; + + err = snd_rawmidi_new(uart->card, "UART Serial MIDI", device, + outs, ins, &rrawmidi); + if (err < 0) + return err; + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_uart16550_input); + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_uart16550_output); + strcpy(rrawmidi->name, "Serial MIDI"); + snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]); + snd_uart16550_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]); + rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rrawmidi->private_data = uart; + if (rmidi) + *rmidi = rrawmidi; + return 0; +} + +static int snd_serial_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_uart16550 *uart; + int err; + int dev = devptr->id; + + switch (adaptor[dev]) { + case SNDRV_SERIAL_SOUNDCANVAS: + ins[dev] = 1; + break; + case SNDRV_SERIAL_MS124T: + case SNDRV_SERIAL_MS124W_SA: + outs[dev] = 1; + ins[dev] = 1; + break; + case SNDRV_SERIAL_MS124W_MB: + outs[dev] = 16; + ins[dev] = 1; + break; + case SNDRV_SERIAL_GENERIC: + break; + default: + snd_printk(KERN_ERR + "Adaptor type is out of range 0-%d (%d)\n", + SNDRV_SERIAL_MAX_ADAPTOR, adaptor[dev]); + return -ENODEV; + } + + if (outs[dev] < 1 || outs[dev] > SNDRV_SERIAL_MAX_OUTS) { + snd_printk(KERN_ERR + "Count of outputs is out of range 1-%d (%d)\n", + SNDRV_SERIAL_MAX_OUTS, outs[dev]); + return -ENODEV; + } + + if (ins[dev] < 1 || ins[dev] > SNDRV_SERIAL_MAX_INS) { + snd_printk(KERN_ERR + "Count of inputs is out of range 1-%d (%d)\n", + SNDRV_SERIAL_MAX_INS, ins[dev]); + return -ENODEV; + } + + err = snd_devm_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + 0, &card); + if (err < 0) + return err; + + strcpy(card->driver, "Serial"); + strcpy(card->shortname, "Serial MIDI (UART16550A)"); + + err = snd_uart16550_create(card, port[dev], irq[dev], speed[dev], + base[dev], adaptor[dev], droponfull[dev], + &uart); + if (err < 0) + return err; + + err = snd_uart16550_rmidi(uart, 0, outs[dev], ins[dev], &uart->rmidi); + if (err < 0) + return err; + + sprintf(card->longname, "%s [%s] at %#lx, irq %d", + card->shortname, + adaptor_names[uart->adaptor], + uart->base, + uart->irq); + + err = snd_card_register(card); + if (err < 0) + return err; + + platform_set_drvdata(devptr, card); + return 0; +} + +#define SND_SERIAL_DRIVER "snd_serial_u16550" + +static struct platform_driver snd_serial_driver = { + .probe = snd_serial_probe, + .driver = { + .name = SND_SERIAL_DRIVER, + }, +}; + +static void snd_serial_unregister_all(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(devices); ++i) + platform_device_unregister(devices[i]); + platform_driver_unregister(&snd_serial_driver); +} + +static int __init alsa_card_serial_init(void) +{ + int i, cards, err; + + err = platform_driver_register(&snd_serial_driver); + if (err < 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_SERIAL_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 "serial midi soundcard not found or device busy\n"); +#endif + snd_serial_unregister_all(); + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_serial_exit(void) +{ + snd_serial_unregister_all(); +} + +module_init(alsa_card_serial_init) +module_exit(alsa_card_serial_exit) diff --git a/sound/drivers/virmidi.c b/sound/drivers/virmidi.c new file mode 100644 index 0000000000..58012de90c --- /dev/null +++ b/sound/drivers/virmidi.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dummy soundcard for virtual rawmidi devices + * + * Copyright (c) 2000 by Takashi Iwai <tiwai@suse.de> + */ + +/* + * 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 <linux/init.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/module.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"); + +#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 bool 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 snd_virmidi_probe(struct platform_device *devptr) +{ + struct snd_card *card; + struct snd_card_virmidi *vmidi; + int idx, err; + int dev = devptr->id; + + err = snd_devm_card_new(&devptr->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_virmidi), &card); + if (err < 0) + return err; + vmidi = card->private_data; + vmidi->card = card; + + if (midi_devs[dev] > MAX_MIDI_DEVICES) { + snd_printk(KERN_WARNING + "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; + + err = snd_virmidi_new(card, idx, &rmidi); + if (err < 0) + return err; + vmidi->midi[idx] = rmidi; + strcpy(rmidi->name, "Virtual Raw MIDI"); + } + + strcpy(card->driver, "VirMIDI"); + strcpy(card->shortname, "VirMIDI"); + sprintf(card->longname, "Virtual MIDI Card %i", dev + 1); + + err = snd_card_register(card); + if (err) + return err; + + platform_set_drvdata(devptr, card); + return 0; +} + +#define SND_VIRMIDI_DRIVER "snd_virmidi" + +static struct platform_driver snd_virmidi_driver = { + .probe = snd_virmidi_probe, + .driver = { + .name = SND_VIRMIDI_DRIVER, + }, +}; + +static void 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; + + err = platform_driver_register(&snd_virmidi_driver); + if (err < 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) diff --git a/sound/drivers/vx/Makefile b/sound/drivers/vx/Makefile new file mode 100644 index 0000000000..d9f9ac6703 --- /dev/null +++ b/sound/drivers/vx/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.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 diff --git a/sound/drivers/vx/vx_cmd.c b/sound/drivers/vx/vx_cmd.c new file mode 100644 index 0000000000..b0970a0488 --- /dev/null +++ b/sound/drivers/vx/vx_cmd.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * DSP commands + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + +/* + * Array of DSP commands + */ +static const 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) +{ + if (snd_BUG_ON(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; +} + diff --git a/sound/drivers/vx/vx_cmd.h b/sound/drivers/vx/vx_cmd.h new file mode 100644 index 0000000000..c2a5202744 --- /dev/null +++ b/sound/drivers/vx/vx_cmd.h @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Digigram VX soundcards + * + * Definitions of DSP commands + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#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 */ diff --git a/sound/drivers/vx/vx_core.c b/sound/drivers/vx/vx_core.c new file mode 100644 index 0000000000..18901e5bcf --- /dev/null +++ b/sound/drivers/vx/vx_core.c @@ -0,0 +1,827 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * Hardware core part + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#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 <linux/module.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/asoundef.h> +#include <sound/info.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; + static const char * const 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" + }; + + 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 mutex held! + */ +static int vx_transfer_end(struct vx_core *chip, int cmd) +{ + int err; + + err = vx_reset_chk(chip); + if (err < 0) + return err; + + /* irq MESS_READ/WRITE_END */ + err = vx_send_irq_dsp(chip, cmd); + if (err < 0) + return err; + + /* Wait CHK = 1 */ + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 0) + return err; + + /* If error, Read RX */ + err = vx_inb(chip, ISR); + if (err & ISR_ERR) { + err = vx_wait_for_rx_full(chip); + if (err < 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 mutex 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; + if (snd_BUG_ON(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 mutex lock 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; + + err = vx_reset_chk(chip); + if (err < 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(KERN_CONT "0x%06x ", rmh->Cmd[i]); + printk(KERN_CONT "\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 */ + err = vx_wait_isr_bit(chip, ISR_TX_EMPTY); + if (err < 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 */ + err = vx_send_irq_dsp(chip, IRQ_MESSAGE); + if (err < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: send IRQ_MESSAGE error\n"); + return err; + } + + /* Wait for CHK = 1 */ + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 0) + return err; + + /* If error, get error value from RX */ + if (vx_inb(chip, ISR) & ISR_ERR) { + err = vx_wait_for_rx_full(chip); + if (err < 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 */ + err = vx_wait_isr_bit(chip, ISR_TX_READY); + if (err < 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 */ + err = vx_send_irq_dsp(chip, IRQ_MESS_READ_NEXT); + if (err < 0) { + snd_printd(KERN_DEBUG "vx_send_msg: IRQ_READ_NEXT error\n"); + return err; + } + } + /* Wait for TX empty */ + err = vx_wait_isr_bit(chip, ISR_TX_READY); + if (err < 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 mutex + * @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) +{ + int err; + + mutex_lock(&chip->lock); + err = vx_send_msg_nolock(chip, rmh); + mutex_unlock(&chip->lock); + 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 mutex 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 + err = vx_reset_chk(chip); + if (err < 0) + return err; + /* send the IRQ */ + err = vx_send_irq_dsp(chip, cmd); + if (err < 0) + return err; + /* Wait CHK = 1 */ + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 0) + return err; + /* If error, read RX */ + if (vx_inb(chip, ISR) & ISR_ERR) { + err = vx_wait_for_rx_full(chip); + if (err < 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 mutex + * @cmd: the command to send + * + * see vx_send_rih_nolock(). + */ +int vx_send_rih(struct vx_core *chip, int cmd) +{ + int err; + + mutex_lock(&chip->lock); + err = vx_send_rih_nolock(chip, cmd); + mutex_unlock(&chip->lock); + return err; +} + +#define END_OF_RESET_WAIT_TIME 500 /* us */ + +/** + * snd_vx_load_boot_image - boot up the xilinx interface + * @chip: VX core instance + * @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 */ + if (boot->size <= 0) + return -EINVAL; + if (boot->size % 3) + return -EINVAL; +#if 0 + { + /* more strict check */ + unsigned int c = ((u32)boot->data[0] << 16) | ((u32)boot->data[1] << 8) | boot->data[2]; + if (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 { + const 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); + mutex_lock(&chip->lock); + err = vx_send_msg_nolock(chip, &chip->irq_rmh); + if (err < 0) + *ret = 0; + else + *ret = chip->irq_rmh.Stat[0]; + mutex_unlock(&chip->lock); + return err; +} + + +/* + * snd_vx_threaded_irq_handler - threaded irq handler + */ +irqreturn_t snd_vx_threaded_irq_handler(int irq, void *dev) +{ + struct vx_core *chip = dev; + unsigned int events; + + if (chip->chip_status & VX_STAT_IS_STALE) + return IRQ_HANDLED; + + if (vx_test_irq_src(chip, &events) < 0) + return IRQ_HANDLED; + +#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 IRQ_HANDLED; + } + + /* 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); + return IRQ_HANDLED; +} +EXPORT_SYMBOL(snd_vx_threaded_irq_handler); + +/** + * snd_vx_irq_handler - interrupt handler + * @irq: irq number + * @dev: VX core instance + */ +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)) + return IRQ_WAKE_THREAD; + return IRQ_NONE; +} + +EXPORT_SYMBOL(snd_vx_irq_handler); + +/* + */ +static void vx_reset_board(struct vx_core *chip, int cold_reset) +{ + if (snd_BUG_ON(!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 const char * const audio_src_vxp[] = { "Line", "Mic", "Digital" }; + static const char * const audio_src_vx2[] = { "Analog", "Analog", "Digital" }; + static const char * const clock_mode[] = { "Auto", "Internal", "External" }; + static const char * const clock_src[] = { "Internal", "External" }; + static const char * const 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) +{ + snd_card_ro_proc_new(chip->card, "vx-status", chip, vx_proc_read); +} + + +/** + * snd_vx_dsp_boot - load the DSP boot + * @chip: VX core instance + * @boot: firmware data + */ +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); + + err = snd_vx_load_boot_image(chip, boot); + if (err < 0) + return err; + msleep(10); + + return 0; +} + +EXPORT_SYMBOL(snd_vx_dsp_boot); + +/** + * snd_vx_dsp_load - load the DSP image + * @chip: VX core instance + * @dsp: firmware data + */ +int snd_vx_dsp_load(struct vx_core *chip, const struct firmware *dsp) +{ + unsigned int i; + int err; + unsigned int csum = 0; + const unsigned char *image, *cptr; + + if (dsp->size % 3) + 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 */ + err = vx_wait_isr_bit(chip, ISR_TX_EMPTY); + if (err < 0) { + printk(KERN_ERR + "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); + + err = vx_wait_isr_bit(chip, ISR_CHK); + if (err < 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) +{ + snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot); + chip->chip_status |= VX_STAT_IN_SUSPEND; + + 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 + +static void snd_vx_release(struct device *dev, void *data) +{ + snd_vx_free_firmware(data); +} + +/** + * snd_vx_create - constructor for struct vx_core + * @card: card instance + * @hw: hardware specific record + * @ops: VX ops pointer + * @extra_size: extra byte size to allocate appending to chip + * + * this function allocates the instance and prepare for the hardware + * initialization. + * + * The object is managed via devres, and will be automatically released. + * + * return the instance pointer if successful, NULL in error. + */ +struct vx_core *snd_vx_create(struct snd_card *card, + const struct snd_vx_hardware *hw, + const struct snd_vx_ops *ops, + int extra_size) +{ + struct vx_core *chip; + + if (snd_BUG_ON(!card || !hw || !ops)) + return NULL; + + chip = devres_alloc(snd_vx_release, sizeof(*chip) + extra_size, + GFP_KERNEL); + if (!chip) + return NULL; + mutex_init(&chip->lock); + chip->irq = -1; + chip->hw = hw; + chip->type = hw->type; + chip->ops = ops; + 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); diff --git a/sound/drivers/vx/vx_hwdep.c b/sound/drivers/vx/vx_hwdep.c new file mode 100644 index 0000000000..efbb644edb --- /dev/null +++ b/sound/drivers/vx/vx_hwdep.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * DSP firmware management + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/hwdep.h> +#include <sound/vx_core.h> + +MODULE_FIRMWARE("vx/bx_1_vxp.b56"); +MODULE_FIRMWARE("vx/bx_1_vp4.b56"); +MODULE_FIRMWARE("vx/x1_1_vx2.xlx"); +MODULE_FIRMWARE("vx/x1_2_v22.xlx"); +MODULE_FIRMWARE("vx/x1_1_vxp.xlx"); +MODULE_FIRMWARE("vx/x1_1_vp4.xlx"); +MODULE_FIRMWARE("vx/bd56002.boot"); +MODULE_FIRMWARE("vx/bd563v2.boot"); +MODULE_FIRMWARE("vx/bd563s3.boot"); +MODULE_FIRMWARE("vx/l_1_vx2.d56"); +MODULE_FIRMWARE("vx/l_1_v22.d56"); +MODULE_FIRMWARE("vx/l_1_vxp.d56"); +MODULE_FIRMWARE("vx/l_1_vp4.d56"); + +int snd_vx_setup_firmware(struct vx_core *chip) +{ + static const char * const 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 */ + err = snd_vx_pcm_new(chip); + if (err < 0) + return err; + + err = snd_vx_mixer_new(chip); + if (err < 0) + return err; + + if (chip->ops->add_controls) { + err = chip->ops->add_controls(chip); + if (err < 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 +} + +EXPORT_SYMBOL(snd_vx_setup_firmware); +EXPORT_SYMBOL(snd_vx_free_firmware); diff --git a/sound/drivers/vx/vx_mixer.c b/sound/drivers/vx/vx_mixer.c new file mode 100644 index 0000000000..53d78eb13c --- /dev/null +++ b/sound/drivers/vx/vx_mixer.c @@ -0,0 +1,1005 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * Common mixer part + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + + +/* + * write a codec data (24bit) + */ +static void vx_write_codec_reg(struct vx_core *chip, int codec, unsigned int data) +{ + if (snd_BUG_ON(!chip->ops->write_codec)) + return; + + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + mutex_lock(&chip->lock); + chip->ops->write_codec(chip, codec, data); + mutex_unlock(&chip->lock); +} + +/* + * Data type used to access the Codec + */ +union vx_codec_data { + u32 l; +#ifdef SNDRV_BIG_ENDIAN + struct w { + u16 h; + u16 l; + } w; + struct b { + u8 hh; + u8 mh; + u8 ml; + u8 ll; + } b; +#else /* LITTLE_ENDIAN */ + struct w { + u16 l; + u16 h; + } w; + struct b { + u8 ll; + u8 ml; + u8 mh; + u8 hh; + } b; +#endif +}; + +#define SET_CDC_DATA_SEL(di,s) ((di).b.mh = (u8) (s)) +#define SET_CDC_DATA_REG(di,r) ((di).b.ml = (u8) (r)) +#define SET_CDC_DATA_VAL(di,d) ((di).b.ll = (u8) (d)) +#define SET_CDC_DATA_INIT(di) ((di).l = 0L, SET_CDC_DATA_SEL(di,XX_CODEC_SELECTOR)) + +/* + * set up codec register and write the value + * @codec: the codec id, 0 or 1 + * @reg: register index + * @val: data value + */ +static void vx_set_codec_reg(struct vx_core *chip, int codec, int reg, int val) +{ + union vx_codec_data data; + /* DAC control register */ + SET_CDC_DATA_INIT(data); + SET_CDC_DATA_REG(data, reg); + SET_CDC_DATA_VAL(data, val); + vx_write_codec_reg(chip, codec, data.l); +} + + +/* + * vx_set_analog_output_level - set the output attenuation level + * @codec: the output codec, 0 or 1. (1 for VXP440 only) + * @left: left output level, 0 = mute + * @right: right output level + */ +static void vx_set_analog_output_level(struct vx_core *chip, int codec, int left, int right) +{ + left = chip->hw->output_level_max - left; + right = chip->hw->output_level_max - right; + + if (chip->ops->akm_write) { + chip->ops->akm_write(chip, XX_CODEC_LEVEL_LEFT_REGISTER, left); + chip->ops->akm_write(chip, XX_CODEC_LEVEL_RIGHT_REGISTER, right); + } else { + /* convert to attenuation level: 0 = 0dB (max), 0xe3 = -113.5 dB (min) */ + vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_LEFT_REGISTER, left); + vx_set_codec_reg(chip, codec, XX_CODEC_LEVEL_RIGHT_REGISTER, right); + } +} + + +/* + * vx_toggle_dac_mute - mute/unmute DAC + * @mute: 0 = unmute, 1 = mute + */ + +#define DAC_ATTEN_MIN 0x08 +#define DAC_ATTEN_MAX 0x38 + +void vx_toggle_dac_mute(struct vx_core *chip, int mute) +{ + unsigned int i; + for (i = 0; i < chip->hw->num_codecs; i++) { + if (chip->ops->akm_write) + chip->ops->akm_write(chip, XX_CODEC_DAC_CONTROL_REGISTER, mute); /* XXX */ + else + vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, + mute ? DAC_ATTEN_MAX : DAC_ATTEN_MIN); + } +} + +/* + * vx_reset_codec - reset and initialize the codecs + */ +void vx_reset_codec(struct vx_core *chip, int cold_reset) +{ + unsigned int i; + int port = chip->type >= VX_TYPE_VXPOCKET ? 0x75 : 0x65; + + chip->ops->reset_codec(chip); + + /* AKM codecs should be initialized in reset_codec callback */ + if (! chip->ops->akm_write) { + /* initialize old codecs */ + for (i = 0; i < chip->hw->num_codecs; i++) { + /* DAC control register (change level when zero crossing + mute) */ + vx_set_codec_reg(chip, i, XX_CODEC_DAC_CONTROL_REGISTER, DAC_ATTEN_MAX); + /* ADC control register */ + vx_set_codec_reg(chip, i, XX_CODEC_ADC_CONTROL_REGISTER, 0x00); + /* Port mode register */ + vx_set_codec_reg(chip, i, XX_CODEC_PORT_MODE_REGISTER, port); + /* Clock control register */ + vx_set_codec_reg(chip, i, XX_CODEC_CLOCK_CONTROL_REGISTER, 0x00); + } + } + + /* mute analog output */ + for (i = 0; i < chip->hw->num_codecs; i++) { + chip->output_level[i][0] = 0; + chip->output_level[i][1] = 0; + vx_set_analog_output_level(chip, i, 0, 0); + } +} + +/* + * change the audio input source + * @src: the target source (VX_AUDIO_SRC_XXX) + */ +static void vx_change_audio_source(struct vx_core *chip, int src) +{ + if (chip->chip_status & VX_STAT_IS_STALE) + return; + + mutex_lock(&chip->lock); + chip->ops->change_audio_source(chip, src); + mutex_unlock(&chip->lock); +} + + +/* + * change the audio source if necessary and possible + * returns 1 if the source is actually changed. + */ +int vx_sync_audio_source(struct vx_core *chip) +{ + if (chip->audio_source_target == chip->audio_source || + chip->pcm_running) + return 0; + vx_change_audio_source(chip, chip->audio_source_target); + chip->audio_source = chip->audio_source_target; + return 1; +} + + +/* + * audio level, mute, monitoring + */ +struct vx_audio_level { + unsigned int has_level: 1; + unsigned int has_monitor_level: 1; + unsigned int has_mute: 1; + unsigned int has_monitor_mute: 1; + unsigned int mute; + unsigned int monitor_mute; + short level; + short monitor_level; +}; + +static int vx_adjust_audio_level(struct vx_core *chip, int audio, int capture, + struct vx_audio_level *info) +{ + struct vx_rmh rmh; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + vx_init_rmh(&rmh, CMD_AUDIO_LEVEL_ADJUST); + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + /* Add Audio IO mask */ + rmh.Cmd[1] = 1 << audio; + rmh.Cmd[2] = 0; + if (info->has_level) { + rmh.Cmd[0] |= VALID_AUDIO_IO_DIGITAL_LEVEL; + rmh.Cmd[2] |= info->level; + } + if (info->has_monitor_level) { + rmh.Cmd[0] |= VALID_AUDIO_IO_MONITORING_LEVEL; + rmh.Cmd[2] |= ((unsigned int)info->monitor_level << 10); + } + if (info->has_mute) { + rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_LEVEL; + if (info->mute) + rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_LEVEL; + } + if (info->has_monitor_mute) { + /* validate flag for M2 at least to unmute it */ + rmh.Cmd[0] |= VALID_AUDIO_IO_MUTE_MONITORING_1 | VALID_AUDIO_IO_MUTE_MONITORING_2; + if (info->monitor_mute) + rmh.Cmd[2] |= AUDIO_IO_HAS_MUTE_MONITORING_1; + } + + return vx_send_msg(chip, &rmh); +} + + +#if 0 // not used +static int vx_read_audio_level(struct vx_core *chip, int audio, int capture, + struct vx_audio_level *info) +{ + int err; + struct vx_rmh rmh; + + memset(info, 0, sizeof(*info)); + vx_init_rmh(&rmh, CMD_GET_AUDIO_LEVELS); + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + /* Add Audio IO mask */ + rmh.Cmd[1] = 1 << audio; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + info.level = rmh.Stat[0] & MASK_DSP_WORD_LEVEL; + info.monitor_level = (rmh.Stat[0] >> 10) & MASK_DSP_WORD_LEVEL; + info.mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_LEVEL) ? 1 : 0; + info.monitor_mute = (rmh.Stat[i] & AUDIO_IO_HAS_MUTE_MONITORING_1) ? 1 : 0; + return 0; +} +#endif // not used + +/* + * set the monitoring level and mute state of the given audio + * no more static, because must be called from vx_pcm to demute monitoring + */ +int vx_set_monitor_level(struct vx_core *chip, int audio, int level, int active) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_monitor_level = 1; + info.monitor_level = level; + info.has_monitor_mute = 1; + info.monitor_mute = !active; + chip->audio_monitor[audio] = level; + chip->audio_monitor_active[audio] = active; + return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */ +} + + +/* + * set the mute status of the given audio + */ +static int vx_set_audio_switch(struct vx_core *chip, int audio, int active) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_mute = 1; + info.mute = !active; + chip->audio_active[audio] = active; + return vx_adjust_audio_level(chip, audio, 0, &info); /* playback only */ +} + +/* + * set the mute status of the given audio + */ +static int vx_set_audio_gain(struct vx_core *chip, int audio, int capture, int level) +{ + struct vx_audio_level info; + + memset(&info, 0, sizeof(info)); + info.has_level = 1; + info.level = level; + chip->audio_gain[capture][audio] = level; + return vx_adjust_audio_level(chip, audio, capture, &info); +} + +/* + * reset all audio levels + */ +static void vx_reset_audio_levels(struct vx_core *chip) +{ + unsigned int i, c; + struct vx_audio_level info; + + memset(chip->audio_gain, 0, sizeof(chip->audio_gain)); + memset(chip->audio_active, 0, sizeof(chip->audio_active)); + memset(chip->audio_monitor, 0, sizeof(chip->audio_monitor)); + memset(chip->audio_monitor_active, 0, sizeof(chip->audio_monitor_active)); + + for (c = 0; c < 2; c++) { + for (i = 0; i < chip->hw->num_ins * 2; i++) { + memset(&info, 0, sizeof(info)); + if (c == 0) { + info.has_monitor_level = 1; + info.has_mute = 1; + info.has_monitor_mute = 1; + } + info.has_level = 1; + info.level = CVAL_0DB; /* default: 0dB */ + vx_adjust_audio_level(chip, i, c, &info); + chip->audio_gain[c][i] = CVAL_0DB; + chip->audio_monitor[i] = CVAL_0DB; + } + } +} + + +/* + * VU, peak meter record + */ + +#define VU_METER_CHANNELS 2 + +struct vx_vu_meter { + int saturated; + int vu_level; + int peak_level; +}; + +/* + * get the VU and peak meter values + * @audio: the audio index + * @capture: 0 = playback, 1 = capture operation + * @info: the array of vx_vu_meter records (size = 2). + */ +static int vx_get_audio_vu_meter(struct vx_core *chip, int audio, int capture, struct vx_vu_meter *info) +{ + struct vx_rmh rmh; + int i, err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + vx_init_rmh(&rmh, CMD_AUDIO_VU_PIC_METER); + rmh.LgStat += 2 * VU_METER_CHANNELS; + if (capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + + /* Add Audio IO mask */ + rmh.Cmd[1] = 0; + for (i = 0; i < VU_METER_CHANNELS; i++) + rmh.Cmd[1] |= 1 << (audio + i); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + /* Read response */ + for (i = 0; i < 2 * VU_METER_CHANNELS; i +=2) { + info->saturated = (rmh.Stat[0] & (1 << (audio + i))) ? 1 : 0; + info->vu_level = rmh.Stat[i + 1]; + info->peak_level = rmh.Stat[i + 2]; + info++; + } + return 0; +} + + +/* + * control API entries + */ + +/* + * output level control + */ +static int vx_output_level_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = chip->hw->output_level_max; + return 0; +} + +static int vx_output_level_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->id.index; + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->output_level[codec][0]; + ucontrol->value.integer.value[1] = chip->output_level[codec][1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_output_level_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int codec = kcontrol->id.index; + unsigned int val[2], vmax; + + vmax = chip->hw->output_level_max; + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > vmax || val[1] > vmax) + return -EINVAL; + mutex_lock(&chip->mixer_mutex); + if (val[0] != chip->output_level[codec][0] || + val[1] != chip->output_level[codec][1]) { + vx_set_analog_output_level(chip, codec, val[0], val[1]); + chip->output_level[codec][0] = val[0]; + chip->output_level[codec][1] = val[1]; + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_output_level = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .name = "Master Playback Volume", + .info = vx_output_level_info, + .get = vx_output_level_get, + .put = vx_output_level_put, + /* tlv will be filled later */ +}; + +/* + * audio source select + */ +static int vx_audio_src_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts_mic[3] = { + "Digital", "Line", "Mic" + }; + static const char * const texts_vx2[2] = { + "Digital", "Analog" + }; + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (chip->type >= VX_TYPE_VXPOCKET) + return snd_ctl_enum_info(uinfo, 1, 3, texts_mic); + else + return snd_ctl_enum_info(uinfo, 1, 2, texts_vx2); +} + +static int vx_audio_src_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->audio_source_target; + return 0; +} + +static int vx_audio_src_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (chip->type >= VX_TYPE_VXPOCKET) { + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + } else { + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + } + mutex_lock(&chip->mixer_mutex); + if (chip->audio_source_target != ucontrol->value.enumerated.item[0]) { + chip->audio_source_target = ucontrol->value.enumerated.item[0]; + vx_sync_audio_source(chip); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_audio_src = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = vx_audio_src_info, + .get = vx_audio_src_get, + .put = vx_audio_src_put, +}; + +/* + * clock mode selection + */ +static int vx_clock_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[3] = { + "Auto", "Internal", "External" + }; + + return snd_ctl_enum_info(uinfo, 1, 3, texts); +} + +static int vx_clock_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = chip->clock_mode; + return 0; +} + +static int vx_clock_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + mutex_lock(&chip->mixer_mutex); + if (chip->clock_mode != ucontrol->value.enumerated.item[0]) { + chip->clock_mode = ucontrol->value.enumerated.item[0]; + vx_set_clock(chip, chip->freq); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_clock_mode = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Clock Mode", + .info = vx_clock_mode_info, + .get = vx_clock_mode_get, + .put = vx_clock_mode_put, +}; + +/* + * Audio Gain + */ +static int vx_audio_gain_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 = CVAL_MAX; + return 0; +} + +static int vx_audio_gain_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_gain[capture][audio]; + ucontrol->value.integer.value[1] = chip->audio_gain[capture][audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_gain_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + unsigned int val[2]; + + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > CVAL_MAX || val[1] > CVAL_MAX) + return -EINVAL; + mutex_lock(&chip->mixer_mutex); + if (val[0] != chip->audio_gain[capture][audio] || + val[1] != chip->audio_gain[capture][audio+1]) { + vx_set_audio_gain(chip, audio, capture, val[0]); + vx_set_audio_gain(chip, audio+1, capture, val[1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_monitor_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor[audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_monitor_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + unsigned int val[2]; + + val[0] = ucontrol->value.integer.value[0]; + val[1] = ucontrol->value.integer.value[1]; + if (val[0] > CVAL_MAX || val[1] > CVAL_MAX) + return -EINVAL; + + mutex_lock(&chip->mixer_mutex); + if (val[0] != chip->audio_monitor[audio] || + val[1] != chip->audio_monitor[audio+1]) { + vx_set_monitor_level(chip, audio, val[0], + chip->audio_monitor_active[audio]); + vx_set_monitor_level(chip, audio+1, val[1], + chip->audio_monitor_active[audio+1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +#define vx_audio_sw_info snd_ctl_boolean_stereo_info + +static int vx_audio_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_active[audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_audio_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + if (ucontrol->value.integer.value[0] != chip->audio_active[audio] || + ucontrol->value.integer.value[1] != chip->audio_active[audio+1]) { + vx_set_audio_switch(chip, audio, + !!ucontrol->value.integer.value[0]); + vx_set_audio_switch(chip, audio+1, + !!ucontrol->value.integer.value[1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_monitor_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor_active[audio+1]; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_monitor_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + int audio = kcontrol->private_value & 0xff; + + mutex_lock(&chip->mixer_mutex); + if (ucontrol->value.integer.value[0] != chip->audio_monitor_active[audio] || + ucontrol->value.integer.value[1] != chip->audio_monitor_active[audio+1]) { + vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], + !!ucontrol->value.integer.value[0]); + vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], + !!ucontrol->value.integer.value[1]); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const DECLARE_TLV_DB_SCALE(db_scale_audio_gain, -10975, 25, 0); + +static const struct snd_kcontrol_new vx_control_audio_gain = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + /* name will be filled later */ + .info = vx_audio_gain_info, + .get = vx_audio_gain_get, + .put = vx_audio_gain_put, + .tlv = { .p = db_scale_audio_gain }, +}; +static const struct snd_kcontrol_new vx_control_output_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = vx_audio_sw_info, + .get = vx_audio_sw_get, + .put = vx_audio_sw_put +}; +static const struct snd_kcontrol_new vx_control_monitor_gain = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Volume", + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = vx_audio_gain_info, /* shared */ + .get = vx_audio_monitor_get, + .put = vx_audio_monitor_put, + .tlv = { .p = db_scale_audio_gain }, +}; +static const struct snd_kcontrol_new vx_control_monitor_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Monitoring Switch", + .info = vx_audio_sw_info, /* shared */ + .get = vx_monitor_sw_get, + .put = vx_monitor_sw_put +}; + + +/* + * IEC958 status bits + */ +static int vx_iec958_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int vx_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + + mutex_lock(&chip->mixer_mutex); + ucontrol->value.iec958.status[0] = (chip->uer_bits >> 0) & 0xff; + ucontrol->value.iec958.status[1] = (chip->uer_bits >> 8) & 0xff; + ucontrol->value.iec958.status[2] = (chip->uer_bits >> 16) & 0xff; + ucontrol->value.iec958.status[3] = (chip->uer_bits >> 24) & 0xff; + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static int vx_iec958_mask_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.iec958.status[0] = 0xff; + ucontrol->value.iec958.status[1] = 0xff; + ucontrol->value.iec958.status[2] = 0xff; + ucontrol->value.iec958.status[3] = 0xff; + return 0; +} + +static int vx_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + unsigned int val; + + val = (ucontrol->value.iec958.status[0] << 0) | + (ucontrol->value.iec958.status[1] << 8) | + (ucontrol->value.iec958.status[2] << 16) | + (ucontrol->value.iec958.status[3] << 24); + mutex_lock(&chip->mixer_mutex); + if (chip->uer_bits != val) { + chip->uer_bits = val; + vx_set_iec958_status(chip, val); + mutex_unlock(&chip->mixer_mutex); + return 1; + } + mutex_unlock(&chip->mixer_mutex); + return 0; +} + +static const struct snd_kcontrol_new vx_control_iec958_mask = { + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,MASK), + .info = vx_iec958_info, /* shared */ + .get = vx_iec958_mask_get, +}; + +static const struct snd_kcontrol_new vx_control_iec958 = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), + .info = vx_iec958_info, + .get = vx_iec958_get, + .put = vx_iec958_put +}; + + +/* + * VU meter + */ + +#define METER_MAX 0xff +#define METER_SHIFT 16 + +static int vx_vu_meter_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 = METER_MAX; + return 0; +} + +static int vx_vu_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + vx_get_audio_vu_meter(chip, audio, capture, meter); + ucontrol->value.integer.value[0] = meter[0].vu_level >> METER_SHIFT; + ucontrol->value.integer.value[1] = meter[1].vu_level >> METER_SHIFT; + return 0; +} + +static int vx_peak_meter_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + int capture = (kcontrol->private_value >> 8) & 1; + + vx_get_audio_vu_meter(chip, audio, capture, meter); + ucontrol->value.integer.value[0] = meter[0].peak_level >> METER_SHIFT; + ucontrol->value.integer.value[1] = meter[1].peak_level >> METER_SHIFT; + return 0; +} + +#define vx_saturation_info snd_ctl_boolean_stereo_info + +static int vx_saturation_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct vx_core *chip = snd_kcontrol_chip(kcontrol); + struct vx_vu_meter meter[2]; + int audio = kcontrol->private_value & 0xff; + + vx_get_audio_vu_meter(chip, audio, 1, meter); /* capture only */ + ucontrol->value.integer.value[0] = meter[0].saturated; + ucontrol->value.integer.value[1] = meter[1].saturated; + return 0; +} + +static const struct snd_kcontrol_new vx_control_vu_meter = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + /* name will be filled later */ + .info = vx_vu_meter_info, + .get = vx_vu_meter_get, +}; + +static const struct snd_kcontrol_new vx_control_peak_meter = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + /* name will be filled later */ + .info = vx_vu_meter_info, /* shared */ + .get = vx_peak_meter_get, +}; + +static const struct snd_kcontrol_new vx_control_saturation = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Saturation", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = vx_saturation_info, + .get = vx_saturation_get, +}; + + + +/* + * + */ + +int snd_vx_mixer_new(struct vx_core *chip) +{ + unsigned int i, c; + int err; + struct snd_kcontrol_new temp; + struct snd_card *card = chip->card; + char name[32]; + + strcpy(card->mixername, card->driver); + + /* output level controls */ + for (i = 0; i < chip->hw->num_outs; i++) { + temp = vx_control_output_level; + temp.index = i; + temp.tlv.p = chip->hw->output_level_db_scale; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + + /* PCM volumes, switches, monitoring */ + for (i = 0; i < chip->hw->num_outs; i++) { + int val = i * 2; + temp = vx_control_audio_gain; + temp.index = i; + temp.name = "PCM Playback Volume"; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + temp = vx_control_output_switch; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + temp = vx_control_monitor_gain; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + temp = vx_control_monitor_switch; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + for (i = 0; i < chip->hw->num_outs; i++) { + temp = vx_control_audio_gain; + temp.index = i; + temp.name = "PCM Capture Volume"; + temp.private_value = (i * 2) | (1 << 8); + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + + /* Audio source */ + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_audio_src, chip)); + if (err < 0) + return err; + /* clock mode */ + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_clock_mode, chip)); + if (err < 0) + return err; + /* IEC958 controls */ + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958_mask, chip)); + if (err < 0) + return err; + err = snd_ctl_add(card, snd_ctl_new1(&vx_control_iec958, chip)); + if (err < 0) + return err; + /* VU, peak, saturation meters */ + for (c = 0; c < 2; c++) { + static const char * const dir[2] = { "Output", "Input" }; + for (i = 0; i < chip->hw->num_ins; i++) { + int val = (i * 2) | (c << 8); + if (c == 1) { + temp = vx_control_saturation; + temp.index = i; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + sprintf(name, "%s VU Meter", dir[c]); + temp = vx_control_vu_meter; + temp.index = i; + temp.name = name; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + sprintf(name, "%s Peak Meter", dir[c]); + temp = vx_control_peak_meter; + temp.index = i; + temp.name = name; + temp.private_value = val; + err = snd_ctl_add(card, snd_ctl_new1(&temp, chip)); + if (err < 0) + return err; + } + } + vx_reset_audio_levels(chip); + return 0; +} diff --git a/sound/drivers/vx/vx_pcm.c b/sound/drivers/vx/vx_pcm.c new file mode 100644 index 0000000000..ceaeb25700 --- /dev/null +++ b/sound/drivers/vx/vx_pcm.c @@ -0,0 +1,1229 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * PCM part + * + * Copyright (c) 2002,2003 by Takashi Iwai <tiwai@suse.de> + * + * STRATEGY + * for playback, we send series of "chunks", which size is equal with the + * IBL size, typically 126 samples. at each end of chunk, the end-of-buffer + * interrupt is notified, and the interrupt handler will feed the next chunk. + * + * the current position is calculated from the sample count RMH. + * pipe->transferred is the counter of data which has been already transferred. + * if this counter reaches to the period size, snd_pcm_period_elapsed() will + * be issued. + * + * for capture, the situation is much easier. + * to get a low latency response, we'll check the capture streams at each + * interrupt (capture stream has no EOB notification). if the pending + * data is accumulated to the period size, snd_pcm_period_elapsed() is + * called and the pointer is updated. + * + * the current point of read buffer is kept in pipe->hw_ptr. note that + * this is in bytes. + * + * TODO + * - linked trigger for full-duplex mode. + * - scheduled action on the stream. + */ + +#include <linux/slab.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/asoundef.h> +#include <sound/pcm.h> +#include <sound/vx_core.h> +#include "vx_cmd.h" + + +/* + * read three pending pcm bytes via inb() + */ +static void vx_pcm_read_per_bytes(struct vx_core *chip, struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe) +{ + int offset = pipe->hw_ptr; + unsigned char *buf = (unsigned char *)(runtime->dma_area + offset); + *buf++ = vx_inb(chip, RXH); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + buf = (unsigned char *)runtime->dma_area; + } + *buf++ = vx_inb(chip, RXM); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + buf = (unsigned char *)runtime->dma_area; + } + *buf++ = vx_inb(chip, RXL); + if (++offset >= pipe->buffer_bytes) { + offset = 0; + } + pipe->hw_ptr = offset; +} + +/* + * vx_set_pcx_time - convert from the PC time to the RMH status time. + * @pc_time: the pointer for the PC-time to set + * @dsp_time: the pointer for RMH status time array + */ +static void vx_set_pcx_time(struct vx_core *chip, pcx_time_t *pc_time, + unsigned int *dsp_time) +{ + dsp_time[0] = (unsigned int)((*pc_time) >> 24) & PCX_TIME_HI_MASK; + dsp_time[1] = (unsigned int)(*pc_time) & MASK_DSP_WORD; +} + +/* + * vx_set_differed_time - set the differed time if specified + * @rmh: the rmh record to modify + * @pipe: the pipe to be checked + * + * if the pipe is programmed with the differed time, set the DSP time + * on the rmh and changes its command length. + * + * returns the increase of the command length. + */ +static int vx_set_differed_time(struct vx_core *chip, struct vx_rmh *rmh, + struct vx_pipe *pipe) +{ + /* Update The length added to the RMH command by the timestamp */ + if (! (pipe->differed_type & DC_DIFFERED_DELAY)) + return 0; + + /* Set the T bit */ + rmh->Cmd[0] |= DSP_DIFFERED_COMMAND_MASK; + + /* Time stamp is the 1st following parameter */ + vx_set_pcx_time(chip, &pipe->pcx_time, &rmh->Cmd[1]); + + /* Add the flags to a notified differed command */ + if (pipe->differed_type & DC_NOTIFY_DELAY) + rmh->Cmd[1] |= NOTIFY_MASK_TIME_HIGH ; + + /* Add the flags to a multiple differed command */ + if (pipe->differed_type & DC_MULTIPLE_DELAY) + rmh->Cmd[1] |= MULTIPLE_MASK_TIME_HIGH; + + /* Add the flags to a stream-time differed command */ + if (pipe->differed_type & DC_STREAM_TIME_DELAY) + rmh->Cmd[1] |= STREAM_MASK_TIME_HIGH; + + rmh->LgCmd += 2; + return 2; +} + +/* + * vx_set_stream_format - send the stream format command + * @pipe: the affected pipe + * @data: format bitmask + */ +static int vx_set_stream_format(struct vx_core *chip, struct vx_pipe *pipe, + unsigned int data) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, pipe->is_capture ? + CMD_FORMAT_STREAM_IN : CMD_FORMAT_STREAM_OUT); + rmh.Cmd[0] |= pipe->number << FIELD_SIZE; + + /* Command might be longer since we may have to add a timestamp */ + vx_set_differed_time(chip, &rmh, pipe); + + rmh.Cmd[rmh.LgCmd] = (data & 0xFFFFFF00) >> 8; + rmh.Cmd[rmh.LgCmd + 1] = (data & 0xFF) << 16 /*| (datal & 0xFFFF00) >> 8*/; + rmh.LgCmd += 2; + + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_set_format - set the format of a pipe + * @pipe: the affected pipe + * @runtime: pcm runtime instance to be referred + * + * returns 0 if successful, or a negative error code. + */ +static int vx_set_format(struct vx_core *chip, struct vx_pipe *pipe, + struct snd_pcm_runtime *runtime) +{ + unsigned int header = HEADER_FMT_BASE; + + if (runtime->channels == 1) + header |= HEADER_FMT_MONO; + if (snd_pcm_format_little_endian(runtime->format)) + header |= HEADER_FMT_INTEL; + if (runtime->rate < 32000 && runtime->rate > 11025) + header |= HEADER_FMT_UPTO32; + else if (runtime->rate <= 11025) + header |= HEADER_FMT_UPTO11; + + switch (snd_pcm_format_physical_width(runtime->format)) { + // case 8: break; + case 16: header |= HEADER_FMT_16BITS; break; + case 24: header |= HEADER_FMT_24BITS; break; + default : + snd_BUG(); + return -EINVAL; + } + + return vx_set_stream_format(chip, pipe, header); +} + +/* + * set / query the IBL size + */ +static int vx_set_ibl(struct vx_core *chip, struct vx_ibl_info *info) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_IBL); + rmh.Cmd[0] |= info->size & 0x03ffff; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + info->size = rmh.Stat[0]; + info->max_size = rmh.Stat[1]; + info->min_size = rmh.Stat[2]; + info->granularity = rmh.Stat[3]; + snd_printdd(KERN_DEBUG "vx_set_ibl: size = %d, max = %d, min = %d, gran = %d\n", + info->size, info->max_size, info->min_size, info->granularity); + return 0; +} + + +/* + * vx_get_pipe_state - get the state of a pipe + * @pipe: the pipe to be checked + * @state: the pointer for the returned state + * + * checks the state of a given pipe, and stores the state (1 = running, + * 0 = paused) on the given pointer. + * + * called from trigger callback only + */ +static int vx_get_pipe_state(struct vx_core *chip, struct vx_pipe *pipe, int *state) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_PIPE_STATE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (! err) + *state = (rmh.Stat[0] & (1 << pipe->number)) ? 1 : 0; + return err; +} + + +/* + * vx_query_hbuffer_size - query available h-buffer size in bytes + * @pipe: the pipe to be checked + * + * return the available size on h-buffer in bytes, + * or a negative error code. + * + * NOTE: calling this function always switches to the stream mode. + * you'll need to disconnect the host to get back to the + * normal mode. + */ +static int vx_query_hbuffer_size(struct vx_core *chip, struct vx_pipe *pipe) +{ + int result; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_SIZE_HBUFFER); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + if (pipe->is_capture) + rmh.Cmd[0] |= 0x00000001; + result = vx_send_msg(chip, &rmh); + if (! result) + result = rmh.Stat[0] & 0xffff; + return result; +} + + +/* + * vx_pipe_can_start - query whether a pipe is ready for start + * @pipe: the pipe to be checked + * + * return 1 if ready, 0 if not ready, and negative value on error. + * + * called from trigger callback only + */ +static int vx_pipe_can_start(struct vx_core *chip, struct vx_pipe *pipe) +{ + int err; + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_CAN_START_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + rmh.Cmd[0] |= 1; + + err = vx_send_msg(chip, &rmh); + if (! err) { + if (rmh.Stat[0]) + err = 1; + } + return err; +} + +/* + * vx_conf_pipe - tell the pipe to stand by and wait for IRQA. + * @pipe: the pipe to be configured + */ +static int vx_conf_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_CONF_PIPE); + if (pipe->is_capture) + rmh.Cmd[0] |= COMMAND_RECORD_MASK; + rmh.Cmd[1] = 1 << pipe->number; + return vx_send_msg(chip, &rmh); +} + +/* + * vx_send_irqa - trigger IRQA + */ +static int vx_send_irqa(struct vx_core *chip) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_SEND_IRQA); + return vx_send_msg(chip, &rmh); +} + + +#define MAX_WAIT_FOR_DSP 250 +/* + * vx boards do not support inter-card sync, besides + * only 126 samples require to be prepared before a pipe can start + */ +#define CAN_START_DELAY 2 /* wait 2ms only before asking if the pipe is ready*/ +#define WAIT_STATE_DELAY 2 /* wait 2ms after irqA was requested and check if the pipe state toggled*/ + +/* + * vx_toggle_pipe - start / pause a pipe + * @pipe: the pipe to be triggered + * @state: start = 1, pause = 0 + * + * called from trigger callback only + * + */ +static int vx_toggle_pipe(struct vx_core *chip, struct vx_pipe *pipe, int state) +{ + int err, i, cur_state; + + /* Check the pipe is not already in the requested state */ + if (vx_get_pipe_state(chip, pipe, &cur_state) < 0) + return -EBADFD; + if (state == cur_state) + return 0; + + /* If a start is requested, ask the DSP to get prepared + * and wait for a positive acknowledge (when there are + * enough sound buffer for this pipe) + */ + if (state) { + for (i = 0 ; i < MAX_WAIT_FOR_DSP; i++) { + err = vx_pipe_can_start(chip, pipe); + if (err > 0) + break; + /* Wait for a few, before asking again + * to avoid flooding the DSP with our requests + */ + mdelay(1); + } + } + + err = vx_conf_pipe(chip, pipe); + if (err < 0) + return err; + + err = vx_send_irqa(chip); + if (err < 0) + return err; + + /* If it completes successfully, wait for the pipes + * reaching the expected state before returning + * Check one pipe only (since they are synchronous) + */ + for (i = 0; i < MAX_WAIT_FOR_DSP; i++) { + err = vx_get_pipe_state(chip, pipe, &cur_state); + if (err < 0 || cur_state == state) + break; + err = -EIO; + mdelay(1); + } + return err < 0 ? -EIO : 0; +} + + +/* + * vx_stop_pipe - stop a pipe + * @pipe: the pipe to be stopped + * + * called from trigger callback only + */ +static int vx_stop_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + vx_init_rmh(&rmh, CMD_STOP_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_alloc_pipe - allocate a pipe and initialize the pipe instance + * @capture: 0 = playback, 1 = capture operation + * @audioid: the audio id to be assigned + * @num_audio: number of audio channels + * @pipep: the returned pipe instance + * + * return 0 on success, or a negative error code. + */ +static int vx_alloc_pipe(struct vx_core *chip, int capture, + int audioid, int num_audio, + struct vx_pipe **pipep) +{ + int err; + struct vx_pipe *pipe; + struct vx_rmh rmh; + int data_mode; + + *pipep = NULL; + vx_init_rmh(&rmh, CMD_RES_PIPE); + vx_set_pipe_cmd_params(&rmh, capture, audioid, num_audio); +#if 0 // NYI + if (underrun_skip_sound) + rmh.Cmd[0] |= BIT_SKIP_SOUND; +#endif // NYI + data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; + if (! capture && data_mode) + rmh.Cmd[0] |= BIT_DATA_MODE; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + + /* initialize the pipe record */ + pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); + if (! pipe) { + /* release the pipe */ + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, capture, audioid, 0); + vx_send_msg(chip, &rmh); + return -ENOMEM; + } + + /* the pipe index should be identical with the audio index */ + pipe->number = audioid; + pipe->is_capture = capture; + pipe->channels = num_audio; + pipe->differed_type = 0; + pipe->pcx_time = 0; + pipe->data_mode = data_mode; + *pipep = pipe; + + return 0; +} + + +/* + * vx_free_pipe - release a pipe + * @pipe: pipe to be released + */ +static int vx_free_pipe(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + vx_send_msg(chip, &rmh); + + kfree(pipe); + return 0; +} + + +/* + * vx_start_stream - start the stream + * + * called from trigger callback only + */ +static int vx_start_stream(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_START_ONE_STREAM); + vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); + vx_set_differed_time(chip, &rmh, pipe); + return vx_send_msg(chip, &rmh); +} + + +/* + * vx_stop_stream - stop the stream + * + * called from trigger callback only + */ +static int vx_stop_stream(struct vx_core *chip, struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + + vx_init_rmh(&rmh, CMD_STOP_STREAM); + vx_set_stream_cmd_params(&rmh, pipe->is_capture, pipe->number); + return vx_send_msg(chip, &rmh); +} + + +/* + * playback hw information + */ + +static const struct snd_pcm_hardware vx_pcm_playback_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ + /*SNDRV_PCM_INFO_RESUME*/), + .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 126, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = VX_MAX_PERIODS, + .fifo_size = 126, +}; + + +/* + * vx_pcm_playback_open - open callback for playback + */ +static int vx_pcm_playback_open(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe = NULL; + unsigned int audio; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + audio = subs->pcm->device * 2; + if (snd_BUG_ON(audio >= chip->audio_outs)) + return -EINVAL; + + /* playback pipe may have been already allocated for monitoring */ + pipe = chip->playback_pipes[audio]; + if (! pipe) { + /* not allocated yet */ + err = vx_alloc_pipe(chip, 0, audio, 2, &pipe); /* stereo playback */ + if (err < 0) + return err; + } + /* open for playback */ + pipe->references++; + + pipe->substream = subs; + chip->playback_pipes[audio] = pipe; + + runtime->hw = vx_pcm_playback_hw; + runtime->hw.period_bytes_min = chip->ibl.size; + runtime->private_data = pipe; + + /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); + + return 0; +} + +/* + * vx_pcm_playback_close - close callback for playback + */ +static int vx_pcm_playback_close(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + + if (! subs->runtime->private_data) + return -EINVAL; + + pipe = subs->runtime->private_data; + + if (--pipe->references == 0) { + chip->playback_pipes[pipe->number] = NULL; + vx_free_pipe(chip, pipe); + } + + return 0; + +} + + +/* + * vx_notify_end_of_buffer - send "end-of-buffer" notifier at the given pipe + * @pipe: the pipe to notify + * + * NB: call with a certain lock. + */ +static int vx_notify_end_of_buffer(struct vx_core *chip, struct vx_pipe *pipe) +{ + int err; + struct vx_rmh rmh; /* use a temporary rmh here */ + + /* Toggle Dsp Host Interface into Message mode */ + vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); + vx_init_rmh(&rmh, CMD_NOTIFY_END_OF_BUFFER); + vx_set_stream_cmd_params(&rmh, 0, pipe->number); + err = vx_send_msg_nolock(chip, &rmh); + if (err < 0) + return err; + /* Toggle Dsp Host Interface back to sound transfer mode */ + vx_send_rih_nolock(chip, IRQ_PAUSE_START_CONNECT); + return 0; +} + +/* + * vx_pcm_playback_transfer_chunk - transfer a single chunk + * @subs: substream + * @pipe: the pipe to transfer + * @size: chunk size in bytes + * + * transfer a single buffer chunk. EOB notificaton is added after that. + * called from the interrupt handler, too. + * + * return 0 if ok. + */ +static int vx_pcm_playback_transfer_chunk(struct vx_core *chip, + struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe, int size) +{ + int space, err = 0; + + space = vx_query_hbuffer_size(chip, pipe); + if (space < 0) { + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + snd_printd("error hbuffer\n"); + return space; + } + if (space < size) { + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + snd_printd("no enough hbuffer space %d\n", space); + return -EIO; /* XRUN */ + } + + /* we don't need irqsave here, because this function + * is called from either trigger callback or irq handler + */ + mutex_lock(&chip->lock); + vx_pseudo_dma_write(chip, runtime, pipe, size); + err = vx_notify_end_of_buffer(chip, pipe); + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih_nolock(chip, IRQ_CONNECT_STREAM_NEXT); + mutex_unlock(&chip->lock); + return err; +} + +/* + * update the position of the given pipe. + * pipe->position is updated and wrapped within the buffer size. + * pipe->transferred is updated, too, but the size is not wrapped, + * so that the caller can check the total transferred size later + * (to call snd_pcm_period_elapsed). + */ +static int vx_update_pipe_position(struct vx_core *chip, + struct snd_pcm_runtime *runtime, + struct vx_pipe *pipe) +{ + struct vx_rmh rmh; + int err, update; + u64 count; + + vx_init_rmh(&rmh, CMD_STREAM_SAMPLE_COUNT); + vx_set_pipe_cmd_params(&rmh, pipe->is_capture, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + + count = ((u64)(rmh.Stat[0] & 0xfffff) << 24) | (u64)rmh.Stat[1]; + update = (int)(count - pipe->cur_count); + pipe->cur_count = count; + pipe->position += update; + if (pipe->position >= (int)runtime->buffer_size) + pipe->position %= runtime->buffer_size; + pipe->transferred += update; + return 0; +} + +/* + * transfer the pending playback buffer data to DSP + * called from interrupt handler + */ +static void vx_pcm_playback_transfer(struct vx_core *chip, + struct snd_pcm_substream *subs, + struct vx_pipe *pipe, int nchunks) +{ + int i, err; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (! pipe->prepared || (chip->chip_status & VX_STAT_IS_STALE)) + return; + for (i = 0; i < nchunks; i++) { + err = vx_pcm_playback_transfer_chunk(chip, runtime, pipe, + chip->ibl.size); + if (err < 0) + return; + } +} + +/* + * update the playback position and call snd_pcm_period_elapsed() if necessary + * called from interrupt handler + */ +static void vx_pcm_playback_update(struct vx_core *chip, + struct snd_pcm_substream *subs, + struct vx_pipe *pipe) +{ + int err; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (pipe->running && ! (chip->chip_status & VX_STAT_IS_STALE)) { + err = vx_update_pipe_position(chip, runtime, pipe); + if (err < 0) + return; + if (pipe->transferred >= (int)runtime->period_size) { + pipe->transferred %= runtime->period_size; + snd_pcm_period_elapsed(subs); + } + } +} + +/* + * vx_pcm_playback_trigger - trigger callback for playback + */ +static int vx_pcm_trigger(struct snd_pcm_substream *subs, int cmd) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe = subs->runtime->private_data; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (! pipe->is_capture) + vx_pcm_playback_transfer(chip, subs, pipe, 2); + err = vx_start_stream(chip, pipe); + if (err < 0) { + pr_debug("vx: cannot start stream\n"); + return err; + } + err = vx_toggle_pipe(chip, pipe, 1); + if (err < 0) { + pr_debug("vx: cannot start pipe\n"); + vx_stop_stream(chip, pipe); + return err; + } + chip->pcm_running++; + pipe->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + vx_toggle_pipe(chip, pipe, 0); + vx_stop_pipe(chip, pipe); + vx_stop_stream(chip, pipe); + chip->pcm_running--; + pipe->running = 0; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + err = vx_toggle_pipe(chip, pipe, 0); + if (err < 0) + return err; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + err = vx_toggle_pipe(chip, pipe, 1); + if (err < 0) + return err; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * vx_pcm_playback_pointer - pointer callback for playback + */ +static snd_pcm_uframes_t vx_pcm_playback_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + return pipe->position; +} + +/* + * vx_pcm_prepare - prepare callback for playback and capture + */ +static int vx_pcm_prepare(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + int err, data_mode; + // int max_size, nchunks; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + data_mode = (chip->uer_bits & IEC958_AES0_NONAUDIO) != 0; + if (data_mode != pipe->data_mode && ! pipe->is_capture) { + /* IEC958 status (raw-mode) was changed */ + /* we reopen the pipe */ + struct vx_rmh rmh; + snd_printdd(KERN_DEBUG "reopen the pipe with data_mode = %d\n", data_mode); + vx_init_rmh(&rmh, CMD_FREE_PIPE); + vx_set_pipe_cmd_params(&rmh, 0, pipe->number, 0); + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + vx_init_rmh(&rmh, CMD_RES_PIPE); + vx_set_pipe_cmd_params(&rmh, 0, pipe->number, pipe->channels); + if (data_mode) + rmh.Cmd[0] |= BIT_DATA_MODE; + err = vx_send_msg(chip, &rmh); + if (err < 0) + return err; + pipe->data_mode = data_mode; + } + + if (chip->pcm_running && chip->freq != runtime->rate) { + snd_printk(KERN_ERR "vx: cannot set different clock %d " + "from the current %d\n", runtime->rate, chip->freq); + return -EINVAL; + } + vx_set_clock(chip, runtime->rate); + + err = vx_set_format(chip, pipe, runtime); + if (err < 0) + return err; + + if (vx_is_pcmcia(chip)) { + pipe->align = 2; /* 16bit word */ + } else { + pipe->align = 4; /* 32bit word */ + } + + pipe->buffer_bytes = frames_to_bytes(runtime, runtime->buffer_size); + pipe->period_bytes = frames_to_bytes(runtime, runtime->period_size); + pipe->hw_ptr = 0; + + /* set the timestamp */ + vx_update_pipe_position(chip, runtime, pipe); + /* clear again */ + pipe->transferred = 0; + pipe->position = 0; + + pipe->prepared = 1; + + return 0; +} + + +/* + * operators for PCM playback + */ +static const struct snd_pcm_ops vx_pcm_playback_ops = { + .open = vx_pcm_playback_open, + .close = vx_pcm_playback_close, + .prepare = vx_pcm_prepare, + .trigger = vx_pcm_trigger, + .pointer = vx_pcm_playback_pointer, +}; + + +/* + * playback hw information + */ + +static const struct snd_pcm_hardware vx_pcm_capture_hw = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_MMAP_VALID /*|*/ + /*SNDRV_PCM_INFO_RESUME*/), + .formats = (/*SNDRV_PCM_FMTBIT_U8 |*/ + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 126, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = VX_MAX_PERIODS, + .fifo_size = 126, +}; + + +/* + * vx_pcm_capture_open - open callback for capture + */ +static int vx_pcm_capture_open(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + struct vx_pipe *pipe_out_monitoring = NULL; + unsigned int audio; + int err; + + if (chip->chip_status & VX_STAT_IS_STALE) + return -EBUSY; + + audio = subs->pcm->device * 2; + if (snd_BUG_ON(audio >= chip->audio_ins)) + return -EINVAL; + err = vx_alloc_pipe(chip, 1, audio, 2, &pipe); + if (err < 0) + return err; + pipe->substream = subs; + chip->capture_pipes[audio] = pipe; + + /* check if monitoring is needed */ + if (chip->audio_monitor_active[audio]) { + pipe_out_monitoring = chip->playback_pipes[audio]; + if (! pipe_out_monitoring) { + /* allocate a pipe */ + err = vx_alloc_pipe(chip, 0, audio, 2, &pipe_out_monitoring); + if (err < 0) + return err; + chip->playback_pipes[audio] = pipe_out_monitoring; + } + pipe_out_monitoring->references++; + /* + if an output pipe is available, it's audios still may need to be + unmuted. hence we'll have to call a mixer entry point. + */ + vx_set_monitor_level(chip, audio, chip->audio_monitor[audio], + chip->audio_monitor_active[audio]); + /* assuming stereo */ + vx_set_monitor_level(chip, audio+1, chip->audio_monitor[audio+1], + chip->audio_monitor_active[audio+1]); + } + + pipe->monitoring_pipe = pipe_out_monitoring; /* default value NULL */ + + runtime->hw = vx_pcm_capture_hw; + runtime->hw.period_bytes_min = chip->ibl.size; + runtime->private_data = pipe; + + /* align to 4 bytes (otherwise will be problematic when 24bit is used) */ + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 4); + + return 0; +} + +/* + * vx_pcm_capture_close - close callback for capture + */ +static int vx_pcm_capture_close(struct snd_pcm_substream *subs) +{ + struct vx_core *chip = snd_pcm_substream_chip(subs); + struct vx_pipe *pipe; + struct vx_pipe *pipe_out_monitoring; + + if (! subs->runtime->private_data) + return -EINVAL; + pipe = subs->runtime->private_data; + chip->capture_pipes[pipe->number] = NULL; + + pipe_out_monitoring = pipe->monitoring_pipe; + + /* + if an output pipe is attached to this input, + check if it needs to be released. + */ + if (pipe_out_monitoring) { + if (--pipe_out_monitoring->references == 0) { + vx_free_pipe(chip, pipe_out_monitoring); + chip->playback_pipes[pipe->number] = NULL; + pipe->monitoring_pipe = NULL; + } + } + + vx_free_pipe(chip, pipe); + return 0; +} + + + +#define DMA_READ_ALIGN 6 /* hardware alignment for read */ + +/* + * vx_pcm_capture_update - update the capture buffer + */ +static void vx_pcm_capture_update(struct vx_core *chip, struct snd_pcm_substream *subs, + struct vx_pipe *pipe) +{ + int size, space, count; + struct snd_pcm_runtime *runtime = subs->runtime; + + if (!pipe->running || (chip->chip_status & VX_STAT_IS_STALE)) + return; + + size = runtime->buffer_size - snd_pcm_capture_avail(runtime); + if (! size) + return; + size = frames_to_bytes(runtime, size); + space = vx_query_hbuffer_size(chip, pipe); + if (space < 0) + goto _error; + if (size > space) + size = space; + size = (size / 3) * 3; /* align to 3 bytes */ + if (size < DMA_READ_ALIGN) + goto _error; + + /* keep the last 6 bytes, they will be read after disconnection */ + count = size - DMA_READ_ALIGN; + /* read bytes until the current pointer reaches to the aligned position + * for word-transfer + */ + while (count > 0) { + if ((pipe->hw_ptr % pipe->align) == 0) + break; + if (vx_wait_for_rx_full(chip) < 0) + goto _error; + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + if (count > 0) { + /* ok, let's accelerate! */ + int align = pipe->align * 3; + space = (count / align) * align; + if (space > 0) { + vx_pseudo_dma_read(chip, runtime, pipe, space); + count -= space; + } + } + /* read the rest of bytes */ + while (count > 0) { + if (vx_wait_for_rx_full(chip) < 0) + goto _error; + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + /* read the last pending 6 bytes */ + count = DMA_READ_ALIGN; + while (count > 0) { + vx_pcm_read_per_bytes(chip, runtime, pipe); + count -= 3; + } + /* update the position */ + pipe->transferred += size; + if (pipe->transferred >= pipe->period_bytes) { + pipe->transferred %= pipe->period_bytes; + snd_pcm_period_elapsed(subs); + } + return; + + _error: + /* disconnect the host, SIZE_HBUF command always switches to the stream mode */ + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + return; +} + +/* + * vx_pcm_capture_pointer - pointer callback for capture + */ +static snd_pcm_uframes_t vx_pcm_capture_pointer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + struct vx_pipe *pipe = runtime->private_data; + return bytes_to_frames(runtime, pipe->hw_ptr); +} + +/* + * operators for PCM capture + */ +static const struct snd_pcm_ops vx_pcm_capture_ops = { + .open = vx_pcm_capture_open, + .close = vx_pcm_capture_close, + .prepare = vx_pcm_prepare, + .trigger = vx_pcm_trigger, + .pointer = vx_pcm_capture_pointer, +}; + + +/* + * interrupt handler for pcm streams + */ +void vx_pcm_update_intr(struct vx_core *chip, unsigned int events) +{ + unsigned int i; + struct vx_pipe *pipe; + +#define EVENT_MASK (END_OF_BUFFER_EVENTS_PENDING|ASYNC_EVENTS_PENDING) + + if (events & EVENT_MASK) { + vx_init_rmh(&chip->irq_rmh, CMD_ASYNC); + if (events & ASYNC_EVENTS_PENDING) + chip->irq_rmh.Cmd[0] |= 0x00000001; /* SEL_ASYNC_EVENTS */ + if (events & END_OF_BUFFER_EVENTS_PENDING) + chip->irq_rmh.Cmd[0] |= 0x00000002; /* SEL_END_OF_BUF_EVENTS */ + + if (vx_send_msg(chip, &chip->irq_rmh) < 0) { + snd_printdd(KERN_ERR "msg send error!!\n"); + return; + } + + i = 1; + while (i < chip->irq_rmh.LgStat) { + int p, buf, capture, eob; + p = chip->irq_rmh.Stat[i] & MASK_FIRST_FIELD; + capture = (chip->irq_rmh.Stat[i] & 0x400000) ? 1 : 0; + eob = (chip->irq_rmh.Stat[i] & 0x800000) ? 1 : 0; + i++; + if (events & ASYNC_EVENTS_PENDING) + i++; + buf = 1; /* force to transfer */ + if (events & END_OF_BUFFER_EVENTS_PENDING) { + if (eob) + buf = chip->irq_rmh.Stat[i]; + i++; + } + if (capture) + continue; + if (snd_BUG_ON(p < 0 || p >= chip->audio_outs)) + continue; + pipe = chip->playback_pipes[p]; + if (pipe && pipe->substream) { + vx_pcm_playback_update(chip, pipe->substream, pipe); + vx_pcm_playback_transfer(chip, pipe->substream, pipe, buf); + } + } + } + + /* update the capture pcm pointers as frequently as possible */ + for (i = 0; i < chip->audio_ins; i++) { + pipe = chip->capture_pipes[i]; + if (pipe && pipe->substream) + vx_pcm_capture_update(chip, pipe->substream, pipe); + } +} + + +/* + * vx_init_audio_io - check the available audio i/o and allocate pipe arrays + */ +static int vx_init_audio_io(struct vx_core *chip) +{ + struct vx_rmh rmh; + int preferred; + + vx_init_rmh(&rmh, CMD_SUPPORTED); + if (vx_send_msg(chip, &rmh) < 0) { + snd_printk(KERN_ERR "vx: cannot get the supported audio data\n"); + return -ENXIO; + } + + chip->audio_outs = rmh.Stat[0] & MASK_FIRST_FIELD; + chip->audio_ins = (rmh.Stat[0] >> (FIELD_SIZE*2)) & MASK_FIRST_FIELD; + chip->audio_info = rmh.Stat[1]; + + /* allocate pipes */ + chip->playback_pipes = kcalloc(chip->audio_outs, sizeof(struct vx_pipe *), GFP_KERNEL); + if (!chip->playback_pipes) + return -ENOMEM; + chip->capture_pipes = kcalloc(chip->audio_ins, sizeof(struct vx_pipe *), GFP_KERNEL); + if (!chip->capture_pipes) { + kfree(chip->playback_pipes); + return -ENOMEM; + } + + preferred = chip->ibl.size; + chip->ibl.size = 0; + vx_set_ibl(chip, &chip->ibl); /* query the info */ + if (preferred > 0) { + chip->ibl.size = roundup(preferred, chip->ibl.granularity); + if (chip->ibl.size > chip->ibl.max_size) + chip->ibl.size = chip->ibl.max_size; + } else + chip->ibl.size = chip->ibl.min_size; /* set to the minimum */ + vx_set_ibl(chip, &chip->ibl); + + return 0; +} + + +/* + * free callback for pcm + */ +static void snd_vx_pcm_free(struct snd_pcm *pcm) +{ + struct vx_core *chip = pcm->private_data; + chip->pcm[pcm->device] = NULL; + kfree(chip->playback_pipes); + chip->playback_pipes = NULL; + kfree(chip->capture_pipes); + chip->capture_pipes = NULL; +} + +/* + * snd_vx_pcm_new - create and initialize a pcm + */ +int snd_vx_pcm_new(struct vx_core *chip) +{ + struct snd_pcm *pcm; + unsigned int i; + int err; + + err = vx_init_audio_io(chip); + if (err < 0) + return err; + + for (i = 0; i < chip->hw->num_codecs; i++) { + unsigned int outs, ins; + outs = chip->audio_outs > i * 2 ? 1 : 0; + ins = chip->audio_ins > i * 2 ? 1 : 0; + if (! outs && ! ins) + break; + err = snd_pcm_new(chip->card, "VX PCM", i, + outs, ins, &pcm); + if (err < 0) + return err; + if (outs) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &vx_pcm_playback_ops); + if (ins) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &vx_pcm_capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); + + pcm->private_data = chip; + pcm->private_free = snd_vx_pcm_free; + pcm->info_flags = 0; + pcm->nonatomic = true; + strcpy(pcm->name, chip->card->shortname); + chip->pcm[i] = pcm; + } + + return 0; +} diff --git a/sound/drivers/vx/vx_uer.c b/sound/drivers/vx/vx_uer.c new file mode 100644 index 0000000000..884c40be19 --- /dev/null +++ b/sound/drivers/vx/vx_uer.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * IEC958 stuff + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + */ + +#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) +{ + int val; + + mutex_lock(&chip->lock); + 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; + } + mutex_unlock(&chip->lock); + 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) +{ + val = !!val; /* 0 or 1 */ + mutex_lock(&chip->lock); + 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)); + } + mutex_unlock(&chip->lock); +} + +/* + * 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, unsigned 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; + + if (snd_BUG_ON(freq <= 0)) + return 0; + + hexfreq = (28224000 * 10) / freq; + hexfreq = (hexfreq + 5) / 10; + + /* max freq = 55125 Hz */ + if (snd_BUG_ON(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) +{ + /* we mute DAC to prevent clicks */ + vx_toggle_dac_mute(chip, 1); + mutex_lock(&chip->lock); + chip->ops->set_clock_source(chip, source); + chip->clock_source = source; + mutex_unlock(&chip->lock); + /* 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; + + /* 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); + mutex_lock(&chip->lock); + 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); + } + mutex_unlock(&chip->lock); +} + + +/* + * 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; +} |