From 4352b8aee98005853aa63f57d6377282de17a33f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 23 Aug 2026 13:28:57 -0700 Subject: Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ... --- sound/drivers/Kconfig | 266 +++++ sound/drivers/Makefile | 28 + sound/drivers/aloop.c | 2039 ++++++++++++++++++++++++++++++++++++ sound/drivers/dummy.c | 1208 +++++++++++++++++++++ sound/drivers/mpu401/Makefile | 13 + sound/drivers/mpu401/mpu401.c | 257 +++++ sound/drivers/mpu401/mpu401_uart.c | 595 +++++++++++ sound/drivers/mtpav.c | 738 +++++++++++++ sound/drivers/mts64.c | 1049 +++++++++++++++++++ sound/drivers/opl3/Makefile | 15 + sound/drivers/opl3/opl3_drums.c | 210 ++++ sound/drivers/opl3/opl3_lib.c | 522 +++++++++ sound/drivers/opl3/opl3_midi.c | 823 +++++++++++++++ sound/drivers/opl3/opl3_oss.c | 254 +++++ sound/drivers/opl3/opl3_seq.c | 273 +++++ sound/drivers/opl3/opl3_synth.c | 606 +++++++++++ sound/drivers/opl3/opl3_voice.h | 46 + sound/drivers/opl4/Makefile | 12 + sound/drivers/opl4/opl4_lib.c | 246 +++++ sound/drivers/opl4/opl4_local.h | 235 +++++ sound/drivers/opl4/opl4_mixer.c | 78 ++ sound/drivers/opl4/opl4_proc.c | 108 ++ sound/drivers/opl4/opl4_seq.c | 201 ++++ sound/drivers/opl4/opl4_synth.c | 620 +++++++++++ sound/drivers/opl4/yrw801.c | 961 +++++++++++++++++ sound/drivers/pcmtest.c | 789 ++++++++++++++ sound/drivers/pcsp/Makefile | 3 + sound/drivers/pcsp/pcsp.c | 216 ++++ sound/drivers/pcsp/pcsp.h | 83 ++ sound/drivers/pcsp/pcsp_input.c | 103 ++ sound/drivers/pcsp/pcsp_input.h | 14 + sound/drivers/pcsp/pcsp_lib.c | 352 +++++++ sound/drivers/pcsp/pcsp_mixer.c | 164 +++ sound/drivers/portman2x4.c | 847 +++++++++++++++ sound/drivers/serial-generic.c | 376 +++++++ sound/drivers/serial-u16550.c | 981 +++++++++++++++++ sound/drivers/virmidi.c | 178 ++++ sound/drivers/vx/Makefile | 9 + sound/drivers/vx/vx_cmd.c | 96 ++ sound/drivers/vx/vx_cmd.h | 233 ++++ sound/drivers/vx/vx_core.c | 808 ++++++++++++++ sound/drivers/vx/vx_hwdep.c | 112 ++ sound/drivers/vx/vx_mixer.c | 982 +++++++++++++++++ sound/drivers/vx/vx_pcm.c | 1233 ++++++++++++++++++++++ sound/drivers/vx/vx_uer.c | 294 ++++++ 45 files changed, 19276 insertions(+) create mode 100644 sound/drivers/Kconfig create mode 100644 sound/drivers/Makefile create mode 100644 sound/drivers/aloop.c create mode 100644 sound/drivers/dummy.c create mode 100644 sound/drivers/mpu401/Makefile create mode 100644 sound/drivers/mpu401/mpu401.c create mode 100644 sound/drivers/mpu401/mpu401_uart.c create mode 100644 sound/drivers/mtpav.c create mode 100644 sound/drivers/mts64.c create mode 100644 sound/drivers/opl3/Makefile create mode 100644 sound/drivers/opl3/opl3_drums.c create mode 100644 sound/drivers/opl3/opl3_lib.c create mode 100644 sound/drivers/opl3/opl3_midi.c create mode 100644 sound/drivers/opl3/opl3_oss.c create mode 100644 sound/drivers/opl3/opl3_seq.c create mode 100644 sound/drivers/opl3/opl3_synth.c create mode 100644 sound/drivers/opl3/opl3_voice.h create mode 100644 sound/drivers/opl4/Makefile create mode 100644 sound/drivers/opl4/opl4_lib.c create mode 100644 sound/drivers/opl4/opl4_local.h create mode 100644 sound/drivers/opl4/opl4_mixer.c create mode 100644 sound/drivers/opl4/opl4_proc.c create mode 100644 sound/drivers/opl4/opl4_seq.c create mode 100644 sound/drivers/opl4/opl4_synth.c create mode 100644 sound/drivers/opl4/yrw801.c create mode 100644 sound/drivers/pcmtest.c create mode 100644 sound/drivers/pcsp/Makefile create mode 100644 sound/drivers/pcsp/pcsp.c create mode 100644 sound/drivers/pcsp/pcsp.h create mode 100644 sound/drivers/pcsp/pcsp_input.c create mode 100644 sound/drivers/pcsp/pcsp_input.h create mode 100644 sound/drivers/pcsp/pcsp_lib.c create mode 100644 sound/drivers/pcsp/pcsp_mixer.c create mode 100644 sound/drivers/portman2x4.c create mode 100644 sound/drivers/serial-generic.c create mode 100644 sound/drivers/serial-u16550.c create mode 100644 sound/drivers/virmidi.c create mode 100644 sound/drivers/vx/Makefile create mode 100644 sound/drivers/vx/vx_cmd.c create mode 100644 sound/drivers/vx/vx_cmd.h create mode 100644 sound/drivers/vx/vx_core.c create mode 100644 sound/drivers/vx/vx_hwdep.c create mode 100644 sound/drivers/vx/vx_mixer.c create mode 100644 sound/drivers/vx/vx_pcm.c create mode 100644 sound/drivers/vx/vx_uer.c (limited to 'sound/drivers') diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig new file mode 100644 index 000000000..6debd8e95 --- /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 . + 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 + + + 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 000000000..a08bdd70e --- /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 +# + +snd-dummy-y := dummy.o +snd-aloop-y := aloop.o +snd-mtpav-y := mtpav.o +snd-mts64-y := mts64.o +snd-pcmtest-y := pcmtest.o +snd-portman2x4-y := portman2x4.o +snd-serial-u16550-y := serial-u16550.o +snd-serial-generic-y := serial-generic.o +snd-virmidi-y := 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 000000000..4e3ea23ca --- /dev/null +++ b/sound/drivers/aloop.c @@ -0,0 +1,2039 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Loopback soundcard + * + * Original code: + * Copyright (c) by Jaroslav Kysela + * + * More accurate positioning and full-duplex support: + * Copyright (c) Ahmet İnan + * + * Major (almost complete) rewrite: + * Copyright (c) by Takashi Iwai + * + * A next major update in 2010 (separate timers for playback and capture): + * Copyright (c) Jaroslav Kysela + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +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], 'hrtimer' for high-resolution timer."); + +#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]; + /* in-flight peer stops running outside cable->lock */ + struct snd_refcount stop_count; + 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; + snd_pcm_access_t access; + 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 snd_ctl_elem_id access_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 / hrtimer is used */ + struct timer_list timer; +#ifdef CONFIG_HIGH_RES_TIMERS + struct hrtimer hrtimer; +#endif + + /* size of per channel buffer in case of non-interleaved access */ + unsigned int channel_buf_n; +}; + +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; +} + +#ifdef CONFIG_HIGH_RES_TIMERS +/* call in cable->lock */ +static int loopback_hrtimer_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); + hrtimer_start(&dpcm->hrtimer, + ns_to_ktime(div_u64((u64)tick * NSEC_PER_SEC, HZ)), + HRTIMER_MODE_REL_SOFT); + + return 0; +} +#endif + +/* 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) +{ + timer_delete(&dpcm->timer); + dpcm->timer.expires = 0; + + return 0; +} + +#ifdef CONFIG_HIGH_RES_TIMERS +/* call in cable->lock */ +static inline int loopback_hrtimer_stop(struct loopback_pcm *dpcm) +{ + hrtimer_try_to_cancel(&dpcm->hrtimer); + + return 0; +} +#endif + +/* 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) +{ + timer_delete_sync(&dpcm->timer); + + return 0; +} + +#ifdef CONFIG_HIGH_RES_TIMERS +static inline int loopback_hrtimer_stop_sync(struct loopback_pcm *dpcm) +{ + hrtimer_cancel(&dpcm->hrtimer); + + return 0; +} +#endif + +/* 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 bool is_access_interleaved(snd_pcm_access_t access) +{ + switch (access) { + case SNDRV_PCM_ACCESS_MMAP_INTERLEAVED: + case SNDRV_PCM_ACCESS_RW_INTERLEAVED: + return true; + default: + return false; + } +}; + +static int loopback_check_format(struct loopback_cable *cable, int stream) +{ + struct loopback_pcm *dpcm_play, *dpcm_capt; + struct snd_pcm_runtime *runtime, *cruntime; + struct loopback_setup *setup; + struct snd_card *card; + bool stop_capture = false; + int check; + + scoped_guard(spinlock_irqsave, &cable->lock) { + dpcm_play = cable->streams[SNDRV_PCM_STREAM_PLAYBACK]; + dpcm_capt = cable->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (cable->valid != CABLE_VALID_BOTH) { + if (stream == SNDRV_PCM_STREAM_CAPTURE || !dpcm_play) + return 0; + } else { + if (!dpcm_play || !dpcm_capt) + return -EIO; + runtime = dpcm_play->substream->runtime; + cruntime = dpcm_capt->substream->runtime; + if (!runtime || !cruntime) + return -EIO; + check = runtime->format != cruntime->format || + runtime->rate != cruntime->rate || + runtime->channels != cruntime->channels || + is_access_interleaved(runtime->access) != + is_access_interleaved(cruntime->access); + if (!check) + return 0; + if (stream == SNDRV_PCM_STREAM_CAPTURE) + return -EIO; + else if (cruntime->state == SNDRV_PCM_STATE_RUNNING) { + /* close must not free the peer runtime below */ + snd_refcount_get(&cable->stop_count); + stop_capture = true; + } + } + + setup = get_setup(dpcm_play); + card = dpcm_play->loopback->card; + runtime = dpcm_play->substream->runtime; + 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; + } + if (is_access_interleaved(setup->access) != + is_access_interleaved(runtime->access)) { + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, + &setup->access_id); + setup->access = runtime->access; + } + } + + if (stop_capture) { + snd_pcm_stop(dpcm_capt->substream, SNDRV_PCM_STATE_DRAINING); + snd_refcount_put(&cable->stop_count); + } + + 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; + scoped_guard(spinlock, &cable->lock) { + cable->running |= stream; + cable->pause &= ~stream; + err = cable->ops->start(dpcm); + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_STOP: + scoped_guard(spinlock, &cable->lock) { + cable->running &= ~stream; + cable->pause &= ~stream; + err = cable->ops->stop(dpcm); + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + scoped_guard(spinlock, &cable->lock) { + cable->pause |= stream; + err = cable->ops->stop(dpcm); + } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + loopback_active_notify(dpcm); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + scoped_guard(spinlock, &cable->lock) { + dpcm->last_jiffies = jiffies; + cable->pause &= ~stream; + err = cable->ops->start(dpcm); + } + 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); + dpcm->channel_buf_n = dpcm->pcm_buffer_size / runtime->channels; + 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); + + guard(mutex)(&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; + + 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_part_n(struct loopback_pcm *play, struct loopback_pcm *capt, + unsigned int size, unsigned int src_off, unsigned int dst_off) +{ + unsigned int channels = capt->substream->runtime->channels; + unsigned int size_p_ch = size / channels; + unsigned int src_off_ch = src_off / channels; + unsigned int dst_off_ch = dst_off / channels; + int i; + + for (i = 0; i < channels; i++) { + memcpy(capt->substream->runtime->dma_area + capt->channel_buf_n * i + dst_off_ch, + play->substream->runtime->dma_area + play->channel_buf_n * i + src_off_ch, + size_p_ch); + } +} + +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; + if (!is_access_interleaved(runtime->access)) + copy_play_buf_part_n(play, capt, size, src_off, dst_off); + else + 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 = timer_container_of(dpcm, t, timer); + bool period_elapsed = false; + + scoped_guard(spinlock_irqsave, &dpcm->cable->lock) { + 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; + period_elapsed = true; + } + } + } + + if (period_elapsed) + snd_pcm_period_elapsed(dpcm->substream); +} + +#ifdef CONFIG_HIGH_RES_TIMERS +static enum hrtimer_restart loopback_hrtimer_function(struct hrtimer *t) +{ + struct loopback_pcm *dpcm = container_of(t, struct loopback_pcm, hrtimer); + bool period_elapsed = false; + + scoped_guard(spinlock_irqsave, &dpcm->cable->lock) { + if (loopback_jiffies_timer_pos_update(dpcm->cable) & + (1 << dpcm->substream->stream)) { + loopback_hrtimer_start(dpcm); + if (dpcm->period_update_pending) { + dpcm->period_update_pending = 0; + period_elapsed = true; + } + } + } + + if (period_elapsed) + snd_pcm_period_elapsed(dpcm->substream); + + return HRTIMER_NORESTART; +} +#endif + +/* 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; + bool xrun = false; + + scoped_guard(spinlock_irqsave, &cable->lock) { + running = cable->running ^ cable->pause; + /* no need to do anything if no stream is running */ + if (!running) + 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) + 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) { + xrun = true; + break; + } + } + + 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); + } + } + + if (xrun) { + if (substream_play) + snd_pcm_stop_xrun(substream_play); + if (substream_capt) + snd_pcm_stop_xrun(substream_capt); + return; + } + + 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); +} + +#ifdef CONFIG_HIGH_RES_TIMERS +static void loopback_hrtimer_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%llu\n", + ktime_to_ns(hrtimer_get_expires(&dpcm->hrtimer))); +} +#endif + +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", + snd_pcm_direction_name(cable->snd_timer.stream)); +} + +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; + + guard(spinlock)(&dpcm->cable->lock); + if (dpcm->cable->ops->pos_update) + dpcm->cable->ops->pos_update(dpcm->cable); + pos = dpcm->buf_pos; + 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 | SNDRV_PCM_INFO_NONINTERLEAVED), + .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 | + SNDRV_PCM_FMTBIT_DSD_U8 | + SNDRV_PCM_FMTBIT_DSD_U16_LE | SNDRV_PCM_FMTBIT_DSD_U16_BE | + SNDRV_PCM_FMTBIT_DSD_U32_LE | SNDRV_PCM_FMTBIT_DSD_U32_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_768000, + .rate_min = 8000, + .rate_max = 768000, + .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; + + guard(mutex)(&dpcm->loopback->cable_lock); + cable->valid &= ~(1 << substream->stream); + 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); + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + m.bits[0] = (u_int32_t)cable->hw.formats; + m.bits[1] = (u_int32_t)(cable->hw.formats >> 32); + } + 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; + + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + t.min = cable->hw.rate_min; + t.max = cable->hw.rate_max; + } + 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; + + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + t.min = cable->hw.channels_min; + t.max = cable->hw.channels_max; + } + 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; + + scoped_guard(mutex, &dpcm->loopback->cable_lock) { + t.min = cable->hw.period_bytes_min; + t.max = cable->hw.period_bytes_max; + } + 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; + struct loopback_pcm *dpcm; + bool other_alive; + + cable = loopback->cables[substream->number][dev]; + if (!cable) + return; + + scoped_guard(spinlock_irq, &cable->lock) { + cable->streams[substream->stream] = NULL; + other_alive = cable->streams[!substream->stream]; + } + + /* Pair with the stop_count increment in loopback_check_format(). */ + snd_refcount_sync(&cable->stop_count); + if (other_alive) + return; + + 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, +}; + +#ifdef CONFIG_HIGH_RES_TIMERS +static int loopback_hrtimer_open(struct loopback_pcm *dpcm) +{ + hrtimer_setup(&dpcm->hrtimer, loopback_hrtimer_function, + CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + + return 0; +} + +static const struct loopback_ops loopback_hrtimer_ops = { + .open = loopback_hrtimer_open, + .start = loopback_hrtimer_start, + .stop = loopback_hrtimer_stop, + .stop_sync = loopback_hrtimer_stop_sync, + .close_substream = loopback_hrtimer_stop_sync, + .pos_update = loopback_jiffies_timer_pos_update, + .dpcm_info = loopback_hrtimer_dpcm_info, +}; +#endif + +static int loopback_parse_timer_id(const char *str, + struct snd_timer_id *tid) +{ + /* [:](|)[{.,}[{.,}]] */ + 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 (card_idx == -1) + tid->dev_class = SNDRV_TIMER_CLASS_GLOBAL; + 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); + + guard(mutex)(&loopback->cable_lock); + dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL); + if (!dpcm) + return -ENOMEM; + 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); + snd_refcount_init(&cable->stop_count); + cable->hw = loopback_pcm_hardware; +#ifdef CONFIG_HIGH_RES_TIMERS + if (loopback->timer_source && !strcmp(loopback->timer_source, "hrtimer")) + cable->ops = &loopback_hrtimer_ops; + else +#endif + if (loopback->timer_source && loopback->timer_source[0]) + 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; + + scoped_guard(spinlock_irq, &cable->lock) { + cable->streams[substream->stream] = dpcm; + } + + unlock: + if (err < 0) { + free_cable(substream); + kfree(dpcm); + } + 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); + guard(mutex)(&loopback->cable_lock); + free_cable(substream); + 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; + strscpy(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); + + guard(mutex)(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate_shift; + 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; + guard(mutex)(&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; + } + return change; +} + +static int loopback_notify_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + + guard(mutex)(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].notify; + 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; + guard(mutex)(&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; + } + 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; + + guard(mutex)(&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; + } + 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 = 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] = + 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); + + guard(mutex)(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].rate; + 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); + + guard(mutex)(&loopback->cable_lock); + ucontrol->value.integer.value[0] = + loopback->setup[kcontrol->id.subdevice] + [kcontrol->id.device].channels; + return 0; +} + +static int loopback_access_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static const char * const texts[] = {"Interleaved", "Non-interleaved"}; + + return snd_ctl_enum_info(uinfo, 1, ARRAY_SIZE(texts), texts); +} + +static int loopback_access_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct loopback *loopback = snd_kcontrol_chip(kcontrol); + snd_pcm_access_t access; + + guard(mutex)(&loopback->cable_lock); + access = loopback->setup[kcontrol->id.subdevice][kcontrol->id.device].access; + + ucontrol->value.enumerated.item[0] = !is_access_interleaved(access); + + 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 +}, +#define ACCESS_IDX 6 +{ + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Slave Access Mode", + .info = loopback_access_info, + .get = loopback_access_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; + + strscpy(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->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; + 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; + case ACCESS_IDX: + setup->access_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; + + guard(mutex)(&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); +} + +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; + + guard(mutex)(&loopback->cable_lock); + snd_iprintf(buffer, "%s\n", + loopback->timer_source ? loopback->timer_source : ""); +} + +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]; + + guard(mutex)(&loopback->cable_lock); + if (!snd_info_get_line(buffer, line, sizeof(line))) + loopback_set_timer_source(loopback, strim(line)); +} + +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; + + if (dev < 0 || dev >= SNDRV_CARDS) { + dev_warn(&devptr->dev, + "Invalid card index %d, using default 0\n", dev); + dev = 0; + } + + 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); + strscpy(card->driver, "Loopback"); + strscpy(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; +} + +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 DEFINE_SIMPLE_DEV_PM_OPS(loopback_pm, loopback_suspend, loopback_resume); + +#define SND_LOOPBACK_DRIVER "snd_aloop" + +static struct platform_driver loopback_driver = { + .probe = loopback_probe, + .driver = { + .name = SND_LOOPBACK_DRIVER, + .pm = &loopback_pm, + }, +}; + +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 + pr_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 000000000..b908d2564 --- /dev/null +++ b/sound/drivers/dummy.c @@ -0,0 +1,1208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dummy soundcard + * Copyright (c) by Jaroslav Kysela + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +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 *); + int (*sync_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; + + guard(spinlock)(&dpcm->lock); + dpcm->base_time = jiffies; + dummy_systimer_rearm(dpcm); + return 0; +} + +static int dummy_systimer_stop(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + + guard(spinlock)(&dpcm->lock); + timer_delete(&dpcm->timer); + return 0; +} + +static int dummy_systimer_sync_stop(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + + timer_delete_sync(&dpcm->timer); + 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 = timer_container_of(dpcm, t, timer); + int elapsed = 0; + + scoped_guard(spinlock_irqsave, &dpcm->lock) { + dummy_systimer_update(dpcm); + dummy_systimer_rearm(dpcm); + elapsed = dpcm->elapsed; + dpcm->elapsed = 0; + } + 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; + + guard(spinlock)(&dpcm->lock); + dummy_systimer_update(dpcm); + return dpcm->frac_pos / HZ; +} + +static int dummy_systimer_create(struct snd_pcm_substream *substream) +{ + struct dummy_systimer_pcm *dpcm; + + dpcm = kzalloc_obj(*dpcm); + 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) +{ + struct dummy_systimer_pcm *dpcm = substream->runtime->private_data; + + timer_shutdown_sync(&dpcm->timer); + kfree(dpcm); +} + +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, + .sync_stop = dummy_systimer_sync_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 int dummy_hrtimer_sync_stop(struct snd_pcm_substream *substream) +{ + struct dummy_hrtimer_pcm *dpcm = substream->runtime->private_data; + + hrtimer_cancel(&dpcm->timer); + return 0; +} + +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; + + 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_obj(*dpcm); + if (!dpcm) + return -ENOMEM; + substream->runtime->private_data = dpcm; + hrtimer_setup(&dpcm->timer, dummy_hrtimer_callback, CLOCK_MONOTONIC, HRTIMER_MODE_REL_SOFT); + 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; + + 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, + .sync_stop = dummy_hrtimer_sync_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_sync_stop(struct snd_pcm_substream *substream) +{ + if (get_dummy_ops(substream)->sync_stop) + return get_dummy_ops(substream)->sync_stop(substream); + return 0; +} + +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, + .sync_stop = dummy_pcm_sync_stop, + .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, + .sync_stop = dummy_pcm_sync_stop, + .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; + strscpy(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; + + guard(spinlock_irq)(&dummy->mixer_lock); + ucontrol->value.integer.value[0] = dummy->mixer_volume[addr][0]; + ucontrol->value.integer.value[1] = dummy->mixer_volume[addr][1]; + 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; + guard(spinlock_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; + 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; + + guard(spinlock_irq)(&dummy->mixer_lock); + ucontrol->value.integer.value[0] = dummy->capture_source[addr][0]; + ucontrol->value.integer.value[1] = dummy->capture_source[addr][1]; + 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; + guard(spinlock_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; + 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); + strscpy(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; + + if (dev < 0 || dev >= SNDRV_CARDS) { + dev_warn(&devptr->dev, + "Invalid card index %d, using default 0\n", dev); + dev = 0; + } + + 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) { + pr_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; + strscpy(card->driver, "Dummy"); + strscpy(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; +} + +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 DEFINE_SIMPLE_DEV_PM_OPS(snd_dummy_pm, snd_dummy_suspend, snd_dummy_resume); + +#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, + }, +}; + +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 + pr_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 000000000..0a96e238e --- /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 +# + +snd-mpu401-y := mpu401.o +snd-mpu401-uart-y := 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 000000000..b93291ad0 --- /dev/null +++ b/sound/drivers/mpu401/mpu401.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for generic MPU-401 boards (UART mode only) + * Copyright (c) by Jaroslav Kysela + * Copyright (c) 2004 by Castet Matthieu + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +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 __ro_after_init; +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]) + dev_err(devptr, "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; + strscpy(card->driver, "MPU-401 UART"); + strscpy(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) { + dev_err(devptr, "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 (dev < 0 || dev >= SNDRV_CARDS) { + dev_warn(&devptr->dev, + "Invalid card index %d, using default 0\n", dev); + dev = 0; + } + + if (port[dev] == SNDRV_AUTO_PORT) { + dev_err(&devptr->dev, "specify port\n"); + return -EINVAL; + } + if (irq[dev] == SNDRV_AUTO_IRQ) { + dev_err(&devptr->dev, "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" }, + { } +}; + +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) { + dev_err(&device->dev, "no PnP port\n"); + return -ENODEV; + } + if (pnp_port_len(device, 0) < IO_EXTENT) { + dev_err(&device->dev, "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) { + dev_warn(&device->dev, "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 + pr_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 000000000..6de03d72a --- /dev/null +++ b/sound/drivers/mpu401/mpu401_uart.c @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Jaroslav Kysela "); +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) + dev_err(mpu->rmidi->dev, + "cmd: clear rx timeout (status = 0x%x)\n", + mpu->read(mpu, MPU401C(mpu))); +#endif +} + +static void uart_interrupt_tx(struct snd_mpu401 *mpu) +{ + if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) && + test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) { + guard(spinlock_irqsave)(&mpu->output_lock); + snd_mpu401_uart_output_write(mpu); + } +} + +static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu) +{ + if (mpu->info_flags & MPU401_INFO_INPUT) { + guard(spinlock_irqsave)(&mpu->input_lock); + if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) + snd_mpu401_uart_input_read(mpu); + else + snd_mpu401_uart_clear_rx(mpu); + } + 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 = timer_container_of(mpu, t, timer); + + scoped_guard(spinlock_irqsave, &mpu->timer_lock) { + /*mpu->mode |= MPU401_MODE_TIMER;*/ + mod_timer(&mpu->timer, 1 + jiffies); + } + 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) +{ + guard(spinlock_irqsave)(&mpu->timer_lock); + 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; +} + +/* + * remove the timer callback if still active + */ +static void snd_mpu401_uart_remove_timer (struct snd_mpu401 *mpu, int input) +{ + guard(spinlock_irqsave)(&mpu->timer_lock); + if (mpu->timer_invoked) { + mpu->timer_invoked &= input ? ~MPU401_MODE_INPUT_TIMER : + ~MPU401_MODE_OUTPUT_TIMER; + if (! mpu->timer_invoked) + timer_delete(&mpu->timer); + } +} + +/* + * 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) +{ + int timeout, ok; + + guard(spinlock_irqsave)(&mpu->input_lock); + 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) + dev_err(mpu->rmidi->dev, + "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; + if (!ok) { + dev_err(mpu->rmidi->dev, + "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) +{ + 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 */ + guard(spinlock_irqsave)(&mpu->input_lock); + snd_mpu401_uart_input_read(mpu); + } 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) +{ + 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 */ + guard(spinlock_irqsave)(&mpu->output_lock); + snd_mpu401_uart_output_write(mpu); + } 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_obj(*mpu); + 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; + mpu->rmidi = rmidi; + 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) { + dev_err(rmidi->dev, + "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)) { + dev_err(rmidi->dev, + "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; + } + 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 000000000..d31eadf4b --- /dev/null +++ b/sound/drivers/mtpav.c @@ -0,0 +1,738 @@ +// 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 + * - 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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); + 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]; + + guard(spinlock_irqsave)(&mtp_card->spinlock); + snd_mtpav_output_port_write(mtp_card, portp, substream); +} + + +/* + * 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]; + + guard(spinlock_irqsave)(&mtp_card->spinlock); + 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 + 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]; + + guard(spinlock_irqsave)(&mtp_card->spinlock); + 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 + 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]; + + guard(spinlock_irqsave)(&mtp_card->spinlock); + if (up) + portp->mode |= MTPAV_MODE_INPUT_TRIGGERED; + else + portp->mode &= ~MTPAV_MODE_INPUT_TRIGGERED; +} + + +/* + * timer interrupt for outputs + */ + +static void snd_mtpav_output_timer(struct timer_list *t) +{ + struct mtpav *chip = timer_container_of(chip, t, timer); + int p; + + guard(spinlock_irqsave)(&chip->spinlock); + /* 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); + } +} + +/* 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) +{ + timer_delete(&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]; + + guard(spinlock_irqsave)(&mtp_card->spinlock); + portp->mode |= MTPAV_MODE_OUTPUT_OPENED; + portp->output = substream; + 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]; + + guard(spinlock_irqsave)(&mtp_card->spinlock); + portp->mode &= ~MTPAV_MODE_OUTPUT_OPENED; + portp->output = NULL; + 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]; + + scoped_guard(spinlock_irqsave, &mtp_card->spinlock) { + 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); + } + } + + 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); + + 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; + + guard(spinlock)(&mcard->spinlock); + snd_mtpav_read_bytes(mcard); + 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) { + dev_err(mcard->card->dev, "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)) { + dev_err(mcard->card->dev, "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) + strscpy(substream->name, "MTP computer"); + else if (substream->number == chip->num_ports * 2 + 1) + strscpy(substream->name, "MTP ADAT"); + else + strscpy(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; + + guard(spinlock_irqsave)(&crd->spinlock); + if (crd->istimer > 0) + snd_mtpav_remove_output_timer(crd); +} + +/* + */ +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; + + strscpy(card->driver, "MTPAV"); + strscpy(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); + dev_info(card->dev, + "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 000000000..cefaa00b8 --- /dev/null +++ b/sound/drivers/mts64.c @@ -0,0 +1,1049 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA Driver for Ego Systems Inc. (ESI) Miditerminal 4140 + * Copyright (c) 2006 by Matthias König + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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_obj(struct mts64); + 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); + + guard(spinlock_irq)(&mts->lock); + uctl->value.integer.value[0] = mts->smpte_switch; + + 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 val = !!uctl->value.integer.value[0]; + + guard(spinlock_irq)(&mts->lock); + if (mts->smpte_switch == val) + return 0; + + 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); + } + return 1; +} + +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; + + guard(spinlock_irq)(&mts->lock); + uctl->value.integer.value[0] = mts->time[idx]; + + 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; + + guard(spinlock_irq)(&mts->lock); + if (mts->time[idx] != time) { + mts->time[idx] = time; + return 1; + } + + return 0; +} + +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); + + guard(spinlock_irq)(&mts->lock); + uctl->value.enumerated.item[0] = mts->fps; + + 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); + + if (uctl->value.enumerated.item[0] >= 5) + return -EINVAL; + guard(spinlock_irq)(&mts->lock); + if (mts->fps != uctl->value.enumerated.item[0]) { + mts->fps = uctl->value.enumerated.item[0]; + return 1; + } + + return 0; +} + +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) { + dev_dbg(card->dev, "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; + + --(mts->open_count); + if (mts->open_count == 0) { + /* We need the spinlock_irqsave here because we can still + have IRQs at this point */ + scoped_guard(spinlock_irqsave, &mts->lock) { + mts64_device_close(mts); + } + + 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; + + guard(spinlock_irqsave)(&mts->lock); + while (snd_rawmidi_transmit_peek(substream, &data, 1) == 1) { + mts64_write_midi(mts, data, substream->number+1); + snd_rawmidi_transmit_ack(substream, 1); + } +} + +static void snd_mts64_rawmidi_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct mts64 *mts = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&mts->lock); + if (up) + mts->mode[substream->number] |= MTS64_MODE_INPUT_TRIGGERED; + else + mts->mode[substream->number] &= ~MTS64_MODE_INPUT_TRIGGERED; +} + +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; + strscpy(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: + strscpy(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; + + guard(spinlock)(&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) + return; + 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); + } +} + +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, +}; + +/********************************************************************* + * 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 < 0) { + dev_warn(&pdev->dev, + "Invalid card index %d, using default 0\n", dev); + dev = 0; + } + + 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) { + dev_dbg(&pdev->dev, "Cannot create card\n"); + return err; + } + strscpy(card->driver, DRIVER_NAME); + strscpy(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) { + dev_dbg(card->dev, "Cannot register pardevice\n"); + err = -EIO; + goto __err; + } + + /* claim parport */ + if (parport_claim(pardev)) { + dev_dbg(card->dev, "Cannot claim parport 0x%lx\n", pardev->port->base); + err = -EIO; + goto free_pardev; + } + + err = snd_mts64_create(card, pardev, &mts); + if (err < 0) { + dev_dbg(card->dev, "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) { + dev_dbg(card->dev, "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) { + dev_dbg(card->dev, "Cannot register card\n"); + goto __err; + } + + dev_info(card->dev, "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 = 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 000000000..cf4826308 --- /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 +# + +snd-opl3-lib-y := 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 000000000..ccc49f394 --- /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 + * + * 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 000000000..ea9b258b1 --- /dev/null +++ b/sound/drivers/opl3/opl3_lib.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Jaroslav Kysela , + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "opl3_voice.h" + +MODULE_AUTHOR("Jaroslav Kysela , 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 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; + + guard(spinlock_irqsave)(&opl3->reg_lock); + + outb((unsigned char) cmd, port); + udelay(10); + + outb((unsigned char) val, port + 1); + udelay(30); +} + +static void snd_opl3_command(struct snd_opl3 * opl3, unsigned short cmd, unsigned char val) +{ + 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; + + guard(spinlock_irqsave)(&opl3->reg_lock); + + 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); +} + +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 */ + dev_dbg(opl3->card->dev, "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 */ + dev_dbg(opl3->card->dev, "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 char tmp; + unsigned int ticks; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + guard(spinlock_irqsave)(&opl3->timer_lock); + 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 */ + return 0; +} + +static int snd_opl3_timer1_stop(struct snd_timer * timer) +{ + unsigned char tmp; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + guard(spinlock_irqsave)(&opl3->timer_lock); + 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 */ + return 0; +} + +/* + * Timer 2 - 320us + */ + +static int snd_opl3_timer2_start(struct snd_timer * timer) +{ + unsigned char tmp; + unsigned int ticks; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + guard(spinlock_irqsave)(&opl3->timer_lock); + 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 */ + return 0; +} + +static int snd_opl3_timer2_stop(struct snd_timer * timer) +{ + unsigned char tmp; + struct snd_opl3 *opl3; + + opl3 = snd_timer_chip(timer); + guard(spinlock_irqsave)(&opl3->timer_lock); + 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 */ + 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) { + strscpy(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) { + strscpy(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 (!(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_obj(*opl3); + 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) { + dev_err(opl3->card->dev, + "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) { + dev_err(card->dev, "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) { + dev_err(card->dev, "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) { + dev_dbg(card->dev, "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 + strscpy(hw->name, hw->id); + switch (opl3->hardware & OPL3_HW_MASK) { + case OPL3_HW_OPL2: + strscpy(hw->name, "OPL2 FM"); + hw->iface = SNDRV_HWDEP_IFACE_OPL2; + break; + case OPL3_HW_OPL3: + strscpy(hw->name, "OPL3 FM"); + hw->iface = SNDRV_HWDEP_IFACE_OPL3; + break; + case OPL3_HW_OPL4: + strscpy(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) { + strscpy(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 000000000..6d3c5b5a3 --- /dev/null +++ b/sound/drivers/opl3/opl3_midi.c @@ -0,0 +1,823 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Uros Bizjak + * + * Midi synth routines for OPL2/OPL3/OPL4 FM + */ + +#undef DEBUG_ALLOC +#undef DEBUG_MIDI + +#include "opl3_voice.h" +#include + +#ifdef DEBUG_MIDI +#define opl3_dbg(opl3, fmt, ...) \ + dev_dbg(((struct snd_opl3 *)(opl3))->card->dev, fmt, ##__VA_ARGS__) +#else +#define opl3_dbg(opl3, fmt, ...) do {} while (0) +#endif + +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 ) + */ + +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; + const char *str = "x.24"; + char buf[MAX_OPL3_VOICES + 1]; + + for (i = 0; i < opl3->max_voices; i++) + buf[i] = str[opl3->voices[i].state + 1]; + buf[i] = 0; + dev_dbg(opl3->card->dev, "time %.5i: %s [%.2i]: %s\n", + opl3->use_time, s, voice, buf); +} +#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 + dev_dbg(opl3->card->dev, + "%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 = timer_container_of(opl3, t, tlist); + int again = 0; + int i; + + scoped_guard(spinlock_irqsave, &opl3->voice_lock) { + 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++; + } + } + } + + guard(spinlock_irqsave)(&opl3->sys_timer_lock); + if (again) + mod_timer(&opl3->tlist, jiffies + 1); /* invoke again */ + else + opl3->sys_timer_status = 0; +} + +/* + * Start system timer + */ +static void snd_opl3_start_timer(struct snd_opl3 *opl3) +{ + guard(spinlock_irqsave)(&opl3->sys_timer_lock); + if (! opl3->sys_timer_status) { + mod_timer(&opl3->tlist, jiffies + 1); + opl3->sys_timer_status = 1; + } +} + +/* ------------------------------ */ + + +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; + + opl3 = p; + + opl3_dbg(opl3, "Note on, ch %i, inst %i, note %i, vel %i\n", + chan->number, chan->midi_program, note, vel); + + /* 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; + } + + guard(spinlock_irqsave)(&opl3->voice_lock); + + if (use_internal_drums) { + snd_opl3_drum_switch(opl3, note, vel, 1, chan); + return; + } + + __extra_prg: + patch = snd_opl3_find_patch(opl3, prg, bank, 0); + if (!patch) + 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: + return; + } + opl3_dbg(opl3, " --> OPL%i instrument: %s\n", + instr_4op ? 3 : 2, patch->name); + /* 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) + 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); + } + } + + opl3_dbg(opl3, " --> setting OPL3 connection: 0x%x\n", + opl3->connection_reg); + /* + * 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++) { + opl3_dbg(opl3, " --> programming operator %i\n", i); + 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; + + opl3_dbg(opl3, " --> trigger voice %i\n", voice); + /* 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; + } + opl3_dbg(opl3, " *** allocating extra program\n"); + goto __extra_prg; + } +} + +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 */ + opl3_dbg(opl3, " --> kill voice %i\n", voice); + 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; + + opl3_dbg(opl3, "Note off, ch %i, inst %i, note %i\n", + chan->number, chan->midi_program, note); + + 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; + + guard(spinlock_irqsave)(&opl3->voice_lock); + snd_opl3_note_off_unsafe(p, note, vel, chan); +} + +/* + * key pressure change + */ +void snd_opl3_key_press(void *p, int note, int vel, struct snd_midi_channel *chan) +{ + opl3_dbg(p, "Key pressure, ch#: %i, inst#: %i\n", + chan->number, chan->midi_program); +} + +/* + * terminate note + */ +void snd_opl3_terminate_note(void *p, int note, struct snd_midi_channel *chan) +{ + opl3_dbg(p, "Terminate note, ch#: %i, inst#: %i\n", + chan->number, chan->midi_program); +} + +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; + + guard(spinlock_irqsave)(&opl3->voice_lock); + + 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); + } + } +} + +/* + * 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; + opl3_dbg(opl3, "Controller, TYPE = %i, ch#: %i, inst#: %i\n", + type, chan->number, chan->midi_program); + + 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) +{ + opl3_dbg(p, "NRPN, ch#: %i, inst#: %i\n", + chan->number, chan->midi_program); +} + +/* + * receive sysex + */ +void snd_opl3_sysex(void *p, unsigned char *buf, int len, + int parsed, struct snd_midi_channel_set *chset) +{ + opl3_dbg(p, "SYSEX\n"); +} diff --git a/sound/drivers/opl3/opl3_oss.c b/sound/drivers/opl3/opl3_oss.c new file mode 100644 index 000000000..6d39b2b77 --- /dev/null +++ b/sound/drivers/opl3/opl3_oss.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Interface for OSS sequencer emulation + * + * Copyright (C) 2000 Uros Bizjak + */ + +#include +#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)) { + dev_err(opl3->card->dev, "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) { + dev_err(opl3->card->dev, "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) +{ + struct snd_opl3 *opl3; + + if (snd_BUG_ON(!arg)) + return -ENXIO; + opl3 = arg->private_data; + switch (cmd) { + case SNDCTL_FM_LOAD_INSTR: + dev_err(opl3->card->dev, + "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 000000000..b77d8e8f1 --- /dev/null +++ b/sound/drivers/opl3/opl3_seq.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Uros Bizjak + * + * Midi Sequencer interface routines for OPL2/OPL3/OPL4 FM + * + * OPL2/3 FM instrument loader: + * alsa-tools/seq/sbiload/ + */ + +#include "opl3_voice.h" +#include +#include +#include +#include + +MODULE_AUTHOR("Uros Bizjak "); +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; + + scoped_guard(mutex, &hwdep->open_mutex) { + if (hwdep->used) + return -EBUSY; + hwdep->used++; + } + + 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) +{ + struct snd_hwdep *hwdep; + + /* Stop system timer */ + scoped_guard(spinlock_irq, &opl3->sys_timer_lock) { + if (opl3->sys_timer_status) { + timer_delete(&opl3->tlist); + opl3->sys_timer_status = 0; + } + } + + snd_opl3_reset(opl3); + hwdep = opl3->hwdep; + scoped_guard(mutex, &hwdep->open_mutex) { + hwdep->used--; + } + 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 snd_seq_device *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 void snd_opl3_seq_remove(struct snd_seq_device *dev) +{ + struct snd_opl3 *opl3; + + opl3 = *(struct snd_opl3 **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (opl3 == NULL) + return; + +#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; + } +} + +static struct snd_seq_driver opl3_seq_driver = { + .probe = snd_opl3_seq_probe, + .remove = snd_opl3_seq_remove, + .driver = { + .name = KBUILD_MODNAME, + }, + .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 000000000..47a2486be --- /dev/null +++ b/sound/drivers/opl3/opl3_synth.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) by Uros Bizjak + * + * Routines for OPL2/OPL3/OPL4 control + */ + +#include +#include +#include +#include +#include +#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 + + default: + dev_dbg(opl3->card->dev, "unknown IOCTL: 0x%x\n", cmd); + } + 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_obj(*patch); + 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 000000000..be9ccca2d --- /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 + */ + +#include + +/* 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 000000000..a841630b4 --- /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 +# + +snd-opl4-lib-y := opl4_lib.o opl4_mixer.o +snd-opl4-lib-$(CONFIG_SND_PROC_FS) += opl4_proc.o +snd-opl4-synth-y := 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 000000000..6364475a3 --- /dev/null +++ b/sound/drivers/opl4/opl4_lib.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Functions for accessing OPL4 devices + * Copyright (c) 2003 by Clemens Ladisch + */ + +#include "opl4_local.h" +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Clemens Ladisch "); +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) +{ + u8 memcfg; + + guard(spinlock_irqsave)(&opl4->reg_lock); + + 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); +} + +EXPORT_SYMBOL(snd_opl4_read_memory); + +void snd_opl4_write_memory(struct snd_opl4 *opl4, const char *buf, int offset, int size) +{ + u8 memcfg; + + guard(spinlock_irqsave)(&opl4->reg_lock); + + 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); +} + +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); + dev_dbg(opl4->card->dev, "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); + dev_dbg(opl4->card->dev, "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) { + strscpy(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_obj(*opl4); + 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) { + dev_err(card->dev, "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); + dev_dbg(card->dev, "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 000000000..a16b4677c --- /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 + * 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 + +/* + * 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 000000000..deebb8636 --- /dev/null +++ b/sound/drivers/opl4/opl4_mixer.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OPL4 mixer functions + * Copyright (c) 2003 by Clemens Ladisch + */ + +#include "opl4_local.h" +#include + +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); + u8 reg = kcontrol->private_value; + u8 value; + + guard(spinlock_irqsave)(&opl4->reg_lock); + value = snd_opl4_read(opl4, reg); + 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); + 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); + guard(spinlock_irqsave)(&opl4->reg_lock); + old_value = snd_opl4_read(opl4, reg); + snd_opl4_write(opl4, reg, value); + 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 000000000..fd0ba4704 --- /dev/null +++ b/sound/drivers/opl4/opl4_proc.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Functions for the OPL4 proc file + * Copyright (c) 2003 by Clemens Ladisch + */ + +#include "opl4_local.h" +#include +#include +#include + +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; + + guard(mutex)(&opl4->access_mutex); + if (opl4->memory_access) + return -EBUSY; + opl4->memory_access++; + 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; + + guard(mutex)(&opl4->access_mutex); + opl4->memory_access--; + 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 000000000..fd6f15be6 --- /dev/null +++ b/sound/drivers/opl4/opl4_seq.c @@ -0,0 +1,201 @@ +/* + * OPL4 sequencer functions + * + * Copyright (c) 2003 by Clemens Ladisch + * 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 +#include +#include +#include + +MODULE_AUTHOR("Clemens Ladisch "); +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; + + scoped_guard(mutex, &opl4->access_mutex) { + if (opl4->used) + return -EBUSY; + opl4->used++; + + if (info->sender.client != SNDRV_SEQ_CLIENT_SYSTEM) { + err = snd_opl4_seq_use_inc(opl4); + if (err < 0) + return err; + } + } + + 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); + + scoped_guard(mutex, &opl4->access_mutex) { + opl4->used--; + } + + 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 snd_seq_device *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 void snd_opl4_seq_remove(struct snd_seq_device *dev) +{ + struct snd_opl4 *opl4; + + opl4 = *(struct snd_opl4 **)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (!opl4) + return; + + if (opl4->seq_client >= 0) { + snd_seq_delete_kernel_client(opl4->seq_client); + opl4->seq_client = -1; + } +} + +static struct snd_seq_driver opl4_seq_driver = { + .probe = snd_opl4_seq_probe, + .remove = snd_opl4_seq_remove, + .driver = { + .name = KBUILD_MODNAME, + }, + .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 000000000..82dbb8519 --- /dev/null +++ b/sound/drivers/opl4/opl4_synth.c @@ -0,0 +1,620 @@ +/* + * OPL4 MIDI synthesizer functions + * + * Copyright (c) 2003 by Clemens Ladisch + * 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 +#include +#include + +/* 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) +{ + int i; + + scoped_guard(spinlock_irqsave, &opl4->reg_lock) { + for (i = 0; i < OPL4_MAX_VOICES; i++) + snd_opl4_write(opl4, OPL4_REG_MISC + i, OPL4_DAMP_BIT); + } + + 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) +{ + int i; + + guard(spinlock_irqsave)(&opl4->reg_lock); + 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); +} + +/* + * 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; + struct opl4_voice *voice; + + guard(spinlock_irqsave)(&opl4->reg_lock); + for (i = 0; i < OPL4_MAX_VOICES; i++) { + voice = &opl4->voices[i]; + if (voice->chan == chan && voice->note == note) { + func(opl4, voice); + } + } +} + +/* + * 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; + struct opl4_voice *voice; + + guard(spinlock_irqsave)(&opl4->reg_lock); + for (i = 0; i < OPL4_MAX_VOICES; i++) { + voice = &opl4->voices[i]; + if (voice->chan == chan) { + func(opl4, voice); + } + } +} + +/* + * 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; + struct opl4_voice *voice; + + guard(spinlock_irqsave)(&opl4->reg_lock); + for (i = 0; i < OPL4_MAX_VOICES; i++) { + voice = &opl4->voices[i]; + if (voice->chan) + func(opl4, voice); + } +} + +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; + + /* 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 */ + scoped_guard(spinlock_irqsave, &opl4->reg_lock) { + 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]); + } + } + + /* wait for completion of loading */ + snd_opl4_wait_for_wave_headers(opl4); + + /* set remaining parameters */ + guard(spinlock_irqsave)(&opl4->reg_lock); + 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); + } +} + +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 000000000..9e464b84b --- /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 + * 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; + dev_dbg(opl4->card->dev, "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 000000000..5d5281e4d --- /dev/null +++ b/sound/drivers/pcmtest.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Virtual ALSA driver for PCM testing/fuzzing + * + * Copyright 2023 Ivan Orlov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 __ro_after_init = { + .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 __ro_after_init; +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 = timer_container_of(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_obj(*v_iter); + 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; + strscpy(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_obj(*pcmtst); + 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; + + strscpy(card->driver, "PCM-TEST Driver"); + strscpy(card->shortname, "PCM-Test"); + strscpy(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 = 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)) + return -EFAULT; + + *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 = kmalloc(MAX_PATTERN_LEN, GFP_KERNEL); + if (!patt_bufs[i].buf) + break; + strscpy_pad(patt_bufs[i].buf, DEFAULT_PATTERN, MAX_PATTERN_LEN); + 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) + goto err_free_patterns; + err = platform_device_register(&pcmtst_pdev); + if (err) + goto err_clear_debug; + err = platform_driver_register(&pcmtst_pdrv); + if (err) { + platform_device_unregister(&pcmtst_pdev); + goto err_clear_debug; + } + + return 0; + +err_clear_debug: + clear_debug_files(); +err_free_patterns: + free_pattern_buffers(); + 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_DESCRIPTION("Virtual ALSA driver for PCM testing/fuzzing"); +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 000000000..309c09497 --- /dev/null +++ b/sound/drivers/pcsp/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-pcsp-y := 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 000000000..ff6bb375c --- /dev/null +++ b/sound/drivers/pcsp/pcsp.c @@ -0,0 +1,216 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcsp_input.h" +#include "pcsp.h" + +MODULE_AUTHOR("Stas Sergeev "); +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) { + dev_err(card->dev, + "PCSP: Timer resolution is not sufficient (%unS)\n", + resolution); + dev_err(card->dev, + "PCSP: Make sure you have HPET and ACPI enabled.\n"); + dev_err(card->dev, "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 + dev_dbg(card->dev, "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_setup(&pcsp_chip.timer, pcsp_do_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + + 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; + + strscpy(card->driver, "PC-Speaker"); + strscpy(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) { + dev_err(dev, "PC-Speaker initialization failed.\n"); + return err; + } + + /* Well, CONFIG_DEBUG_PAGEALLOC makes the sound horrible. Lets alert */ + if (debug_pagealloc_enabled()) { + dev_warn(dev, + "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(); +} + +static int pcsp_suspend(struct device *dev) +{ + struct snd_pcsp *chip = dev_get_drvdata(dev); + pcsp_stop_beep(chip); + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(pcsp_pm, pcsp_suspend, NULL); + +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, + }, + .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 000000000..036ad3c99 --- /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 +#include +#include + +#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 000000000..5a799f7f0 --- /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 +#include +#include +#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 000000000..42bfc9eab --- /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 000000000..80b313f4f --- /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 +#include +#include +#include +#include +#include +#include +#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 + dev_dbg(chip->card->dev, + "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) { + dev_warn(chip->card->dev, "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 + dev_dbg(chip->card->dev, "PCSP: start_playing called\n"); +#endif + if (atomic_read(&chip->timer_active)) { + dev_err(chip->card->dev, "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 + dev_dbg(chip->card->dev, "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 + dev_dbg(chip->card->dev, "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 + dev_dbg(chip->card->dev, "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 + dev_dbg(chip->card->dev, "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 + dev_dbg(chip->card->dev, "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 + dev_dbg(chip->card->dev, "PCSP: open called\n"); +#endif + if (atomic_read(&chip->timer_active)) { + dev_err(chip->card->dev, "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; + strscpy(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 000000000..27d615032 --- /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 +#include +#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 + dev_dbg(chip->card->dev, "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; + + strscpy(card->mixername, "PC-Speaker"); + + return 0; +} diff --git a/sound/drivers/portman2x4.c b/sound/drivers/portman2x4.c new file mode 100644 index 000000000..03ed3c3c2 --- /dev/null +++ b/sound/drivers/portman2x4.c @@ -0,0 +1,847 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Midiman Portman2x4 parallel port midi interface + * + * Copyright (c) by Levent Guendogdu + * + * ChangeLog + * Jan 24 2007 Matthias Koenig + * - cleanup and rewrite + * Sep 30 2004 Tobias Gehrig + * - source code cleanup + * Sep 03 2004 Tobias Gehrig + * - 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 + * - added 2.6 kernel support + * Mar 18 2004 Tobias Gehrig + * - 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 + * - added checks for opened input device in interrupt handler + * Feb 20 2004 Tobias Gehrig + * - ported from alsa 0.5 to 1.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_obj(struct portman); + 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: + dev_warn(pm->card->dev, "%s Won't flush port %i\n", + __func__, 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; + + guard(spinlock_irqsave)(&pm->reg_lock); + if (up) + pm->mode[substream->number] |= PORTMAN2X4_MODE_INPUT_TRIGGERED; + else + pm->mode[substream->number] &= ~PORTMAN2X4_MODE_INPUT_TRIGGERED; +} + +static void snd_portman_midi_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct portman *pm = substream->rmidi->private_data; + unsigned char byte; + + guard(spinlock_irqsave)(&pm->reg_lock); + if (up) { + while ((snd_rawmidi_transmit(substream, &byte, 1) == 1)) + portman_write_midi(pm, substream->number, byte); + } +} + +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; + strscpy(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; + + guard(spinlock)(&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); + } + + } +} + +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, +}; + +/********************************************************************* + * 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 < 0) { + dev_warn(&pdev->dev, + "Invalid card index %d, using default 0\n", dev); + dev = 0; + } + + 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) { + dev_dbg(&pdev->dev, "Cannot create card\n"); + return err; + } + strscpy(card->driver, DRIVER_NAME); + strscpy(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) { + dev_dbg(card->dev, "Cannot register pardevice\n"); + err = -EIO; + goto __err; + } + + /* claim parport */ + if (parport_claim(pardev)) { + dev_dbg(card->dev, "Cannot claim parport 0x%lx\n", pardev->port->base); + err = -EIO; + goto free_pardev; + } + + err = portman_create(card, pardev, &pm); + if (err < 0) { + dev_dbg(card->dev, "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) { + dev_dbg(card->dev, "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) { + dev_dbg(card->dev, "Cannot register card\n"); + goto __err; + } + + dev_info(card->dev, "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 = 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 000000000..766206c6c --- /dev/null +++ b/sound/drivers/serial-generic.c @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * serial-generic.c + * Copyright (c) by Daniel Kaehn , + * Isaku Yamahata , + * George Hansper , + * Hannu Savolainen + * + * Generic serial MIDI driver using the serdev serial bus API for hardware interaction + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 + +#define INTERNAL_BUF_SIZE 256 + +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; + + char tx_buf[INTERNAL_BUF_SIZE]; +}; + +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); +} + +static void snd_serial_generic_tx_work(struct work_struct *work) +{ + 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, drvdata->tx_buf, + INTERNAL_BUF_SIZE); + num_bytes = serdev_device_write_buf(drvdata->serdev, drvdata->tx_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 size_t snd_serial_generic_receive_buf(struct serdev_device *serdev, + const u8 *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); + strscpy(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; + + strscpy(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 000000000..cc9c325df --- /dev/null +++ b/sound/drivers/serial-u16550.c @@ -0,0 +1,981 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * serial.c + * Copyright (c) by Jaroslav Kysela , + * Isaku Yamahata , + * George Hansper , + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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) { + timer_delete(&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) + dev_warn(uart->card->dev, + "%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; + guard(spinlock)(&uart->open_lock); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + return IRQ_NONE; + /* indicate to the UART that the interrupt has been serviced */ + inb(uart->base + UART_IIR); + snd_uart16550_io_loop(uart); + return IRQ_HANDLED; +} + +/* When the polling mode, this function calls snd_uart16550_io_loop. */ +static void snd_uart16550_buffer_timer(struct timer_list *t) +{ + struct snd_uart16550 *uart; + + uart = timer_container_of(uart, t, buffer_timer); + guard(spinlock_irqsave)(&uart->open_lock); + snd_uart16550_del_timer(uart); + snd_uart16550_io_loop(uart); +} + +/* + * 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")) { + dev_err(uart->card->dev, + "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) +{ + struct snd_uart16550 *uart = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&uart->open_lock); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_open(uart); + uart->filemode |= SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = substream; + return 0; +} + +static int snd_uart16550_input_close(struct snd_rawmidi_substream *substream) +{ + struct snd_uart16550 *uart = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&uart->open_lock); + uart->filemode &= ~SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_close(uart); + return 0; +} + +static void snd_uart16550_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_uart16550 *uart = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&uart->open_lock); + if (up) + uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED; + else + uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED; +} + +static int snd_uart16550_output_open(struct snd_rawmidi_substream *substream) +{ + struct snd_uart16550 *uart = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&uart->open_lock); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_open(uart); + uart->filemode |= SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = substream; + return 0; +}; + +static int snd_uart16550_output_close(struct snd_rawmidi_substream *substream) +{ + struct snd_uart16550 *uart = substream->rmidi->private_data; + + guard(spinlock_irqsave)(&uart->open_lock); + uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart16550_do_close(uart); + 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)) { + dev_warn(uart->card->dev, + "%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 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) + */ + + guard(spinlock_irqsave)(&uart->open_lock); + + 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; + } +} + +static void snd_uart16550_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_uart16550 *uart = substream->rmidi->private_data; + + scoped_guard(spinlock_irqsave, &uart->open_lock) { + if (up) + uart->filemode |= SERIAL_MODE_OUTPUT_TRIGGERED; + else + uart->filemode &= ~SERIAL_MODE_OUTPUT_TRIGGERED; + } + 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) { + dev_err(card->dev, "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)) { + dev_warn(card->dev, + "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); + strscpy(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; + + if (dev < 0 || dev >= SNDRV_CARDS) { + dev_warn(&devptr->dev, + "Invalid card index %d, using default 0\n", dev); + dev = 0; + } + + 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: + dev_err(&devptr->dev, + "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) { + dev_err(&devptr->dev, + "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) { + dev_err(&devptr->dev, + "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; + + strscpy(card->driver, "Serial"); + strscpy(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 + pr_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 000000000..70e16105f --- /dev/null +++ b/sound/drivers/virmidi.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dummy soundcard for virtual rawmidi devices + * + * Copyright (c) 2000 by Takashi Iwai + */ + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* hack: OSS defines midi_devs, so undefine it (versioned symbols) */ +#undef midi_devs + +MODULE_AUTHOR("Takashi Iwai "); +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; + + if (dev < 0 || dev >= SNDRV_CARDS) { + dev_warn(&devptr->dev, + "Invalid card index %d, using default 0\n", dev); + dev = 0; + } + + 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) { + dev_warn(&devptr->dev, + "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; + strscpy(rmidi->name, "Virtual Raw MIDI"); + } + + strscpy(card->driver, "VirMIDI"); + strscpy(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 + pr_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 000000000..ae1b3e092 --- /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 +# + +snd-vx-lib-y := 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 000000000..b0970a048 --- /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 + */ + +#include +#include +#include +#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 000000000..1fc70c98f --- /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 + */ + +#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<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 000000000..52b93407b --- /dev/null +++ b/sound/drivers/vx/vx_core.c @@ -0,0 +1,808 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * Hardware core part + * + * Copyright (c) 2002 by Takashi Iwai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vx_cmd.h" + +MODULE_AUTHOR("Takashi Iwai "); +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)); + dev_dbg(chip->card->dev, + "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) { + dev_dbg(chip->card->dev, + "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); + dev_dbg(chip->card->dev, "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) { + dev_dbg(chip->card->dev, "vx_send_msg: vx_reset_chk error\n"); + return err; + } + + /* 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) { + dev_dbg(chip->card->dev, "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) { + dev_dbg(chip->card->dev, + "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) { + dev_dbg(chip->card->dev, + "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); + dev_dbg(chip->card->dev, + "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) { + dev_dbg(chip->card->dev, + "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) { + dev_dbg(chip->card->dev, + "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) { + dev_dbg(chip->card->dev, + "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) +{ + guard(mutex)(&chip->lock); + return vx_send_msg_nolock(chip, rmh); +} + + +/* + * 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; + + 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) +{ + guard(mutex)(&chip->lock); + return vx_send_rih_nolock(chip, cmd); +} + +#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) { + dev_err(chip->card->dev, "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) { + dev_err(chip->card->dev, "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); + guard(mutex)(&chip->lock); + err = vx_send_msg_nolock(chip, &chip->irq_rmh); + if (err < 0) + *ret = 0; + else + *ret = chip->irq_rmh.Stat[0]; + 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; + + /* 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) { + dev_err(chip->card->dev, "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) { + dev_err(chip->card->dev, + "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++); + } + + 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) { + dev_err(chip->card->dev, + "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; + strscpy(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 000000000..a7f8ddf4d --- /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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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->card->dev)) { + dev_err(chip->card->dev, "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 000000000..9dc5cecaa --- /dev/null +++ b/sound/drivers/vx/vx_mixer.c @@ -0,0 +1,982 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * Common mixer part + * + * Copyright (c) 2002 by Takashi Iwai + */ + +#include +#include +#include +#include +#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; + + guard(mutex)(&chip->lock); + chip->ops->write_codec(chip, codec, data); +} + +/* + * 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; + + guard(mutex)(&chip->lock); + chip->ops->change_audio_source(chip, src); +} + + +/* + * 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; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->output_level[codec][0]; + ucontrol->value.integer.value[1] = chip->output_level[codec][1]; + 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; + guard(mutex)(&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]; + return 1; + } + 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; + } + guard(mutex)(&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); + return 1; + } + 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; + guard(mutex)(&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); + return 1; + } + 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; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_gain[capture][audio]; + ucontrol->value.integer.value[1] = chip->audio_gain[capture][audio+1]; + 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; + guard(mutex)(&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]); + return 1; + } + 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; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor[audio+1]; + 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; + + guard(mutex)(&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]); + return 1; + } + 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; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_active[audio+1]; + 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; + + guard(mutex)(&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]); + return 1; + } + 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; + + guard(mutex)(&chip->mixer_mutex); + ucontrol->value.integer.value[0] = chip->audio_monitor_active[audio]; + ucontrol->value.integer.value[1] = chip->audio_monitor_active[audio+1]; + 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; + + guard(mutex)(&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]); + return 1; + } + 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); + + guard(mutex)(&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; + 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); + guard(mutex)(&chip->mixer_mutex); + if (chip->uer_bits != val) { + chip->uer_bits = val; + vx_set_iec958_status(chip, val); + return 1; + } + 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]; + + strscpy(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 000000000..d9b945807 --- /dev/null +++ b/sound/drivers/vx/vx_pcm.c @@ -0,0 +1,1233 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * PCM part + * + * Copyright (c) 2002,2003 by Takashi Iwai + * + * 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 +#include +#include +#include +#include +#include +#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]; + dev_dbg(chip->card->dev, + "%s: size = %d, max = %d, min = %d, gran = %d\n", + __func__, 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_obj(*pipe); + 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); + dev_dbg(chip->card->dev, "error hbuffer\n"); + return space; + } + if (space < size) { + vx_send_rih(chip, IRQ_CONNECT_STREAM_NEXT); + dev_dbg(chip->card->dev, "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 + */ + guard(mutex)(&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); + 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; + dev_dbg(chip->card->dev, + "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) { + dev_err(chip->card->dev, + "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) { + dev_dbg(chip->card->dev, "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) { + dev_err(chip->card->dev, + "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 = kzalloc_objs(struct vx_pipe *, chip->audio_outs); + if (!chip->playback_pipes) + return -ENOMEM; + chip->capture_pipes = kzalloc_objs(struct vx_pipe *, chip->audio_ins); + 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; + strscpy(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 000000000..1d90db3b0 --- /dev/null +++ b/sound/drivers/vx/vx_uer.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Digigram VX soundcards + * + * IEC958 stuff + * + * Copyright (c) 2002 by Takashi Iwai + */ + +#include +#include +#include +#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; + + guard(mutex)(&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; + } + 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 */ + guard(mutex)(&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)); + } +} + +/* + * 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); + scoped_guard(mutex, &chip->lock) { + chip->ops->set_clock_source(chip, source); + chip->clock_source = source; + } + /* 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); + dev_dbg(chip->card->dev, + "set internal clock to 0x%x from freq %d\n", clock, freq); + guard(mutex)(&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); + } +} + + +/* + * 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; +} -- cgit v1.3.1