diff options
author | jkh <jkh@FreeBSD.org> | 1993-10-23 10:55:52 +0000 |
---|---|---|
committer | jkh <jkh@FreeBSD.org> | 1993-10-23 10:55:52 +0000 |
commit | 6fff94e5f07f0d167d9ddbd386e366596f36081a (patch) | |
tree | 02792126c665934c13fb381bfaf87335b0633267 /sys/i386 | |
parent | 47553b3ec63c85480e289f6f8157885799107ee6 (diff) | |
download | FreeBSD-src-6fff94e5f07f0d167d9ddbd386e366596f36081a.zip FreeBSD-src-6fff94e5f07f0d167d9ddbd386e366596f36081a.tar.gz |
This is the Linux generic soundcard driver, version 1.0c. Supports
SBlaster/Adlib/ProAudio Spectrum/Gravis/etc cards. This is a BETA test
driver, please test it and get back to me!
Diffstat (limited to 'sys/i386')
46 files changed, 15667 insertions, 0 deletions
diff --git a/sys/i386/isa/sound/COPYING b/sys/i386/isa/sound/COPYING new file mode 100644 index 0000000..be7703a --- /dev/null +++ b/sys/i386/isa/sound/COPYING @@ -0,0 +1,27 @@ +/* + * Copyright by Hannu Savolainen 1993 + * + * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Unmodified version of this file (COPYING) must be provided with + * any source or binary distributions. + * + * 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. + * + */ diff --git a/sys/i386/isa/sound/HOWTO_MIDI b/sys/i386/isa/sound/HOWTO_MIDI new file mode 100644 index 0000000..f0601e5 --- /dev/null +++ b/sys/i386/isa/sound/HOWTO_MIDI @@ -0,0 +1,51 @@ +The following file describes the procedure for adding modules to MIDI +Please READ the main documentation files for the driver first!!! + + + Example: We have a sound card with a MIDI chip & port on it + and, we call it the 'MYBLASTER' card: + + ************************************************************************** + + 0: Run 'configure'. Select the MIDI on CHIP support option. + + 1: Write a midi driver module; 'blast_midi.c' + (with functions for open,close,read,write,attach.) + + 1a: Write all functions except the 'attach' the way you want. + + For the 'attach' function, look at a model in the 'pro_midi.c' + file. Just dup it in the same fashion. For the 'generic_midi_operations' + structure which is required, see file 'dev_table.h'. + + 2: We called the 'attach' function: 'blast_attach'. + + Go to the file 'dev_table.h' and add your driver name and the function + pointer ( which is 'blast_attach' ) to the 'midi_supported' table. + + 3: You are almost set. Go and make a reference + to an 'exclude constant'; say EXLCUDE_BLAST_MIDI in your module + (refer to the 'pro_midi.c' file for model). Also make sure to + add the constant to the file 'sound_config.h' (for example, look + where the constant EXCLUDE_PRO_MIDI is in the file.) + + 4: Add the line + + #define ALL_EXTERNAL_TO_ME + + as the 1st line of your 'blast_midi.c' file. This happily, makes + you ignorant of everything else specific to the driver! :). + + 4a: And of course, don't forget to make a device :). Note that your + minor number should be = ( 15 + position of your driver in the + 'midi_supported' table in the 'dev_table.h' file ). + + Eg: Your driver is the second one in the table. viz midi_supported[1]. + Your device minor number should be ( 15 + 1 = 16 ). Then, make the + reference to your device as, say CMIDI_DEV_BLAST in the file + 'sound_config.h'. Also add this in 'soundcard.c' for the open, read, + write and close routines. See files for example using CMIDI_DEV_PRO + (which is the ProAudioSpectrum on chip MIDI). + + 5: You are all set. If ever you have problems, follow the same model + as the file 'pro_midi.c', except for substituting your own functions! diff --git a/sys/i386/isa/sound/README b/sys/i386/isa/sound/README new file mode 100644 index 0000000..efb0b11 --- /dev/null +++ b/sys/i386/isa/sound/README @@ -0,0 +1,17 @@ +CAUTION! + +This is a prototype version of the Linux Sound Driver for FreeBSD. + +The official and supported version is 1.0c. + +This version 'should work' but there may be some bugs and the programmers +API may change before the final version. + +There are some additional programs for GUS owners in the +gustest subdirectory of this directory, namely a module +(.MOD, .STM and .669) player and a patch file loader. +Additionally, there is a midithru program which allows +you to play the synth on the soundcard with a midi keyboard +(also usable for OPL-3 owners). + +Hannu & FreeBSD team. diff --git a/sys/i386/isa/sound/RELNOTES b/sys/i386/isa/sound/RELNOTES new file mode 100644 index 0000000..03d492d --- /dev/null +++ b/sys/i386/isa/sound/RELNOTES @@ -0,0 +1,38 @@ +Welcome to use the Linux sound driver for FreeBSD. This +driver supports the SoundBlaster, SB Pro, Pro Audio Spectrum 16, +AdLib and Gravis UltraSound sound cards. + +In addition there is rather limited support for MPU-401 +(and compatible) midi cards. Also, the OPL-3 synthesizer +of the SB Pro and PAS16 cards is now supported in the 4 OP +modes. + +Most of the features of the /dev/sequencer device file are +available just for GUS owners. + +The SoundBlaster 16 and SB 16 ASP cards are not supported, +though they may work in mono mode with speeds < 22 kHz. +The OPL-3 chicp of the SB 16 should work (without problems?). +Is there anybody willing to implement the SB 16 support +(have the SB 16 and the SDK for it)? + +Since this driver is a sound driver, it does not contain support +for SCSI/CD-ROM/Joystick -devices. + +Known bugs +---------- + +- It's not possible to open /dev/dsp (or /dev/audio) while the + /dev/sequencer is open for output and GUS is the only soundcard + installed. It's possible if /dev/dsp is opened before /dev/sequencer + but at this time the GUS is not available for access via /dev/sequencer. + This is a limitation of the driver. +- MPU-401 driver hangs the computer on boot if there is no MPU-401 installed. + It uses by default the I/O port 0x330, which is also used by the + Adaptec 1542 SCSI adapter. +- The /dev/sequencer playback to GUS sounds sometimes rather weird. Hitting + ^C and playing again should solve this problem. This is probably caused by + incompatibilities between the GUS and certain VLB motherboards. Try to avoid + switching between VTs while patches are being loaded to the GUS. +- There is a skeleton of the patch manager support. It doesn't work in + this version. diff --git a/sys/i386/isa/sound/RELNOTES.Linux b/sys/i386/isa/sound/RELNOTES.Linux new file mode 100644 index 0000000..4082f4e --- /dev/null +++ b/sys/i386/isa/sound/RELNOTES.Linux @@ -0,0 +1,191 @@ +Release notes for the Linux Sound Driver 1.99.9 +----------------------------------------------- + +******** THIS IS A BETA TEST RELEASE ******** +which means that there can be some untested things. In theory +there is a risk that this driver causes some trouble to your system. +You should not use this driver before backing up your disks. + + + + +Welcome to use the Gravis UltraSound driver for Linux. This +driver still supports the same cards than version 1.0c +(SoundBlaster, SB Pro, Pro Audio Spectrum 16 and AdLib). +In addition there is rather limited support for MPU-401 +(and compatible) midi cards. Also the OPL-3 synthesizer +of the SB Pro and PAS16 cards is now supported in the 4 OP +modes. +Most of the features of the /dev/sequencer device file are +available just for GUS owners. + +The SoundBlaster 16 and SB 16 ASP cards are not supported. +They could work in mono mode with speeds < 22 kHz. +The OPL-3 chicp of the SB 16 should work (without problems?). +Is there anybody willing to implement the SB 16 support +(have the SB 16 and the SDK for it)? + + +This is the first version of the driver which has almost +all of the features which I have planned to include into +version 2.0. Some features are still missing and some ones +doesn't work. + +NOTE! There are separate driver for CD-ROMS supported by + some soundcards. The driver for CDU31A (Fusion 16) is + called cdu31a-0.6.diff.z. It will be contained in the + Linux version 0.99.12. The driver for the CD-ROM of SB Pro + is sbpcd0.4.tar.gz (these were the latest versions when I wrote + this). These files should be at least at sunsite.unc.edu. + As far as I know, there is no driver for the SCSI interface of PAS16 + (yet). + + There is also a driver for joystick. Look for file joystick-0.5.tar.gz + (sunsite). + + Since this driver is a sound driver, it will not contain support + for SCSI/CD-ROM/Joystick -devices. + +Compatibility with the earlier versions +--------------------------------------- + +This is just like the version 1.99.7/1.99.8. There is just some minor +enhancements. Most of them are portability fixes. If you are porting +this driver to any OS, please look at the 386bsd/os.h. There is some +new macros and some macros have more parameters. In addition this file +contains some usefull comments. + +**** There is some ISC and 386bsd stuff in this driver. Please stay away **** +This stuff is here just because I want to be in sync with the porters. These +ports don't work yet. + +The ioctl() interface has changed completely since version 1.0c. All +programs using this driver must be at least recompiled. +The snd-util-1.99.6 package contains some utilities for this version. + +The version 1.0c and earlier used a 'nonportable' ioctl calling scheme +where the input argument was passed by value and the output value was +returned as the functional return. For example setting the speed of +/dev/dsp were done as the following: + + int actual_speed; + actual_speed = ioctl(fd, SOUND_PCM_WRITE_RATE, 44100); + +After version 1.99.0 this must be done as the following: + + int actual_speed = 44100; + ioctl(fd, SOUND_PCM_WRITE_RATE, &actual_speed); + +If you have an application written for the version 1.0, you should search +for the strings SNDCTL_ and SOUND_ and to check the parameters. + +Since the this version will support more than one synthesizer devices +at the same time, the ioctl(SNDCTL_FM_LOAD_INSTR) is obsolete. In addition +there is some new fields which must be initialized. Look at the sbiset.c in +the snd-util-1.99.6 package for further info. + +The GUS patch format has changed since the version 1.99.3. You have to +use latest versions of the programs in the sound/gustest directory. In +addition the version 0.4g of the Adagio package supports this format. + +New features +------------ + +There is also some changes which make this version more usable than +the version 1.0c. + +- /dev/dsp and /dev/audio + +The DMA buffering is now little bit more intelligent than earlier. The +buffer size is selected run-time so that a buffer holds data for 0.5 to +1.0 seconds of recording or playback. This makes recording more comfortable +than with version 1.0. With the previous version there was sometimes more +than 10 seconds of delay before the driver returned the first input byte. + +There is also support for more than one digitized voice devices. The device +files /dev/dsp1 and /dev/audio1 (minor 19 and 20) are available with PAS16. +The /dev/dsp (/dev/audio) is connected to the PCM circuit of the PAS16 itself +and the /dev/dsp1 (/dev/audio1) to the SB emulation of PAS16 card. Two +dsp/audio devices are available also if you have combination of SB and GUS. +With GUS and PAS16 you will have even three dsp/audio devices. These devices +can be used independently and can be active at the same time (3 channels +at the same time propably don't work). + +The dsp/audio support of PAS16 should be much cleaner now since the +constant clicking sound between the DMA blocks (about once per second) has +been eliminated. + +The stereo playback of GUS doesn't work perfectly. There is lot of +clicking in the output. + +- /dev/mixer + +No changes. + +There is no mixer for the GUS yet. + +- /dev/sequencer + +This part has the most changes. Mostly to support the rich +features of the Gravis UltraSound. There is also the support +for the OPL-3 synthesizer chip. + +- /dev/sndstat + +This is a new devicefile for debugging purposes. A better place for +it is in the /proc -directory but I was just too lazy to implement it +properly. The /dev/sndstat (major 14, minor 6) is a file which returns +info about the current configuration (see the example below). If you +send me a error/problem report, please include a printout from this +device to your message (cat /dev/sndstat). + +------ cut here --- cat /dev/sndstat example -------- +Sound Driver:1.99.7 (Fri Jul 9 17:01:47 GMT 1993 root@lucifer.savolai.fi) +Config options: 0x00000d4b + +Major number: 14 +HW config: +Type 4: Gravis Ultrasound at 0x210 irq 15 drq 6 +Type 3: ProAudioSpectrum at 0x388 irq 10 drq 3 +Type 2: SoundBlaster at 0x220 irq 7 drq 1 +Type 1: AdLib at 0x388 irq 0 drq 0 + +PCM devices: +00: Gravis UltraSound +01: Pro Audio Spectrum +02: SoundBlaster 2.0 + +Synth devices: +00: Gravis UltraSound +01: Yamaha OPL-3 + +Midi devices: +00: Gravis UltraSound +01: Pro Audio Spectrum + +Mixer(s) installed +------ cut here ---- End of Example ----------- + + +Known bugs +---------- + +- There was clicking during stereo playback to /dev/dsp with GUS. + * Fixed in 1.99.9 * +- It's not possible to open /dev/dsp (or /dev/audio) while the + /dev/sequencer is open for output and GUS is the only soundcard + installed. It's possible if /dev/dsp is opened before /dev/sequencer + but at this time the GUS is not available for access via /dev/sequencer. + This is a limitation of the driver. +- MPU-401 driver hangs the computer on boot if there is no MPU-401 installed. + It uses by default the I/O port 0x330 whic is used by Adaptec 1542 SCSI + adapter. +- The /dev/sequencer playback to GUS sounds sometimes rather weird. Hitting + ^C and playing again should solve this problem. This is propably caused by + incompatibilities between GUS and certain VLB motherboards. Try to avoid + switching between VTs while patches are being loaded to the GUS. +- There was some problems with GUS and Mitsumi CD in version 1.99.8. Fixed + in 1.99.9. +- /dev/audio sounded like stereo with GUS. Fixed in 1.99.9. +- There is a skeleton of the patch manager support. It don't work in + this version. diff --git a/sys/i386/isa/sound/adlib_card.c b/sys/i386/isa/sound/adlib_card.c new file mode 100644 index 0000000..a9cf568 --- /dev/null +++ b/sys/i386/isa/sound/adlib_card.c @@ -0,0 +1,32 @@ + +/* + * linux/kernel/chr_drv/sound/adlib_card.c + * + * Detection routine for the AdLib card. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_YM3812) + +long +attach_adlib_card (long mem_start, struct address_info *hw_config) +{ + + if (opl3_detect (FM_MONO)) + { + mem_start = opl3_init (mem_start); + } + return mem_start; +} + +int +probe_adlib (struct address_info *hw_config) +{ + return opl3_detect (FM_MONO); +} + +#endif diff --git a/sys/i386/isa/sound/audio.c b/sys/i386/isa/sound/audio.c new file mode 100644 index 0000000..e35366e --- /dev/null +++ b/sys/i386/isa/sound/audio.c @@ -0,0 +1,278 @@ +/* + * linux/kernel/chr_drv/sound/audio.c + * + * Device file manager for /dev/audio + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD +#ifndef EXCLUDE_AUDIO + +#include "ulaw.h" + +#define ON 1 +#define OFF 0 + +static int wr_buff_no[MAX_DSP_DEV]; /* != -1, if there is a + * incomplete output block */ +static int wr_buff_size[MAX_DSP_DEV], wr_buff_ptr[MAX_DSP_DEV]; +static char *wr_dma_buf[MAX_DSP_DEV]; + +int +audio_open (int dev, struct fileinfo *file) +{ + int mode; + int ret; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + if ((ret = DMAbuf_open (dev, mode)) < 0) + return ret; + + wr_buff_no[dev] = -1; + return ret; +} + +void +audio_release (int dev, struct fileinfo *file) +{ + int mode; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + + DMAbuf_release (dev, mode); +} + +#ifdef NO_INLINE_ASM +static void +translate_bytes (const unsigned char *table, unsigned char *buff, unsigned long n) +{ + unsigned long i; + + for (i = 0; i < n; ++i) + buff[i] = table[buff[i]]; +} + +#else +extern inline void +translate_bytes (const void *table, void *buff, unsigned long n) +{ + __asm__ ("cld\n" + "1:\tlodsb\n\t" + "xlatb\n\t" + "stosb\n\t" + "loop 1b\n\t": + :"b" ((long) table), "c" (n), "D" ((long) buff), "S" ((long) buff) + :"bx", "cx", "di", "si", "ax"); +} + +#endif + +int +audio_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + int c, p, l; + int err; + + dev = dev >> 4; + + p = 0; + c = count; + + if (!count) /* Flush output */ + { + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + return 0; + } + + while (c) + { /* Perform output blocking */ + if (wr_buff_no[dev] < 0) /* There is no incomplete buffers */ + { + if ((wr_buff_no[dev] = DMAbuf_getwrbuffer (dev, &wr_dma_buf[dev], &wr_buff_size[dev])) < 0) + return wr_buff_no[dev]; + wr_buff_ptr[dev] = 0; + } + + l = c; + if (l > (wr_buff_size[dev] - wr_buff_ptr[dev])) + l = (wr_buff_size[dev] - wr_buff_ptr[dev]); + + COPY_FROM_USER (&wr_dma_buf[dev][wr_buff_ptr[dev]], buf, p, l); + + /* Insert local processing here */ + +#ifdef linux + /* This just allows interrupts while the conversion is running */ + __asm__ ("sti"); +#endif + translate_bytes (ulaw_dsp, &wr_dma_buf[dev][wr_buff_ptr[dev]], l); + + c -= l; + p += l; + wr_buff_ptr[dev] += l; + + if (wr_buff_ptr[dev] >= wr_buff_size[dev]) + { + if ((err = DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev])) < 0) + return err; + + wr_buff_no[dev] = -1; + } + + } + + return count; +} + +int +audio_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + int c, p, l; + char *dmabuf; + int buff_no; + + dev = dev >> 4; + p = 0; + c = count; + + while (c) + { + if ((buff_no = DMAbuf_getrdbuffer (dev, &dmabuf, &l)) < 0) + return buff_no; + + if (l > c) + l = c; + + /* Insert any local processing here. */ +#ifdef linux + /* This just allows interrupts while the conversion is running */ + __asm__ ("sti"); +#endif + + translate_bytes (dsp_ulaw, dmabuf, l); + + COPY_TO_USER (buf, p, dmabuf, l); + + DMAbuf_rmchars (dev, buff_no, l); + + p += l; + c -= l; + } + + return count - c; +} + +int +audio_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + dev = dev >> 4; + + switch (cmd) + { + case SNDCTL_DSP_SYNC: + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + return DMAbuf_ioctl (dev, cmd, arg, 0); + break; + + case SNDCTL_DSP_POST: + if (wr_buff_no[dev] >= 0) + { + DMAbuf_start_output (dev, wr_buff_no[dev], wr_buff_ptr[dev]); + + wr_buff_no[dev] = -1; + } + return 0; + break; + + case SNDCTL_DSP_RESET: + wr_buff_no[dev] = -1; + return DMAbuf_ioctl (dev, cmd, arg, 0); + break; + + default: +#if 1 + return RET_ERROR (EIO); +#else + return DMAbuf_ioctl (dev, cmd, arg, 0); +#endif + } +} + +long +audio_init (long mem_start) +{ + return mem_start; +} + +#else +/* Stub versions */ + +int +audio_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +audio_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +audio_open (int dev, struct fileinfo *file) + { + return RET_ERROR (ENXIO); + } + +void +audio_release (int dev, struct fileinfo *file) + { + }; +int +audio_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + return RET_ERROR (EIO); +} + +int +audio_lseek (int dev, struct fileinfo *file, off_t offset, int orig) +{ + return RET_ERROR (EIO); +} + +long +audio_init (long mem_start) +{ + return mem_start; +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/dev_table.c b/sys/i386/isa/sound/dev_table.c new file mode 100644 index 0000000..96765cd8d4 --- /dev/null +++ b/sys/i386/isa/sound/dev_table.c @@ -0,0 +1,83 @@ +/* + * linux/kernel/chr_drv/sound/dev_table.c + * + * Device call tables. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#define _DEV_TABLE_C_ +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +long +sndtable_init (long mem_start) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + for (i = 0; i < (n - 1); i++) + if (supported_drivers[i].probe (&supported_drivers[i].config)) + { +#ifndef SHORT_BANNERS + printk ("snd%d", + supported_drivers[i].card_type); +#endif + + mem_start = supported_drivers[i].attach (mem_start, &supported_drivers[i].config); +#ifndef SHORT_BANNERS + printk (" at 0x%03x irq %d drq %d\n", + supported_drivers[i].config.io_base, + supported_drivers[i].config.irq, + supported_drivers[i].config.dma); +#endif + } + return mem_start; +} + +int +sndtable_probe (int unit, struct address_info *hw_config) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + if (!unit) + return TRUE; + + for (i = 0; i < (n - 1); i++) + if (supported_drivers[i].card_type == unit) + return supported_drivers[i].probe (hw_config); + + return FALSE; +} + +int +sndtable_init_card (int unit, struct address_info *hw_config) +{ + int i, n = sizeof (supported_drivers) / sizeof (struct card_info); + + if (!unit) + { + if (sndtable_init (0) != 0) + panic ("snd: Invalid memory allocation\n"); + return TRUE; + } + + for (i = 0; i < (n - 1); i++) + if (supported_drivers[i].card_type == unit) + { + if (supported_drivers[i].attach (0, hw_config) != 0) + panic ("snd#: Invalid memory allocation\n"); + return TRUE; + } + + return FALSE; +} + +int +sndtable_get_cardcount (void) +{ + return num_dspdevs + num_mixers + num_synths + num_midis; +} + +#endif diff --git a/sys/i386/isa/sound/dev_table.h b/sys/i386/isa/sound/dev_table.h new file mode 100644 index 0000000..64482ce --- /dev/null +++ b/sys/i386/isa/sound/dev_table.h @@ -0,0 +1,229 @@ +/* + linux/kernel/chr_drv/sound/dev_table.h + + Global definitions for device call tables + + (C) Hannu Savolainen 1992 + See COPYING for further details. Should be distributed with this file. + +*/ + +#ifndef _DEV_TABLE_H_ +#define _DEV_TABLE_H_ + +/* + * NOTE! NOTE! NOTE! NOTE! + * + * If you modify this file, please check the dev_table.c also. + * + * NOTE! NOTE! NOTE! NOTE! + */ + +struct card_info { + int card_type; /* From soundcard.c */ + char *name; + long (*attach) (long mem_start, struct address_info *hw_config); + int (*probe) (struct address_info *hw_config); + struct address_info config; +}; + +/** UWM -- new MIDI structure here.. **/ + +struct generic_midi_info{ + char *name; /* Name of the MIDI device.. */ + long (*attach) (long mem_start); +}; + +struct audio_operations { + char name[32]; + int (*open) (int dev, int mode); + void (*close) (int dev); + void (*output_block) (int dev, unsigned long buf, int count, int intrflag); + void (*start_input) (int dev, unsigned long buf, int count, int intrflag); + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg, int local); + int (*prepare_for_input) (int dev, int bufsize, int nbufs); + int (*prepare_for_output) (int dev, int bufsize, int nbufs); + void (*reset) (int dev); + void (*halt_xfer) (int dev); + int (*has_output_drained)(int dev); + void (*copy_from_user)(int dev, char *localbuf, int localoffs, + snd_rw_buf *userbuf, int useroffs, int len); +}; + +struct mixer_operations { + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg); +}; + +struct synth_operations { + struct synth_info *info; + int synth_type; + int synth_subtype; + + int (*open) (int dev, int mode); + void (*close) (int dev); + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg); + int (*kill_note) (int dev, int voice, int velocity); + int (*start_note) (int dev, int voice, int note, int velocity); + int (*set_instr) (int dev, int voice, int instr); + void (*reset) (int dev); + void (*hw_control) (int dev, unsigned char *event); + int (*load_patch) (int dev, int format, snd_rw_buf *addr, + int offs, int count, int pmgr_flag); + void (*aftertouch) (int dev, int voice, int pressure); + void (*controller) (int dev, int voice, int ctrl_num, int value); + void (*panning) (int dev, int voice, int value); + int (*pmgr_interface) (int dev, struct patmgr_info *info); +}; + +struct midi_operations { + struct midi_info info; + int (*open) (int dev, int mode); + void (*close) (int dev); + int (*ioctl) (int dev, unsigned int cmd, unsigned int arg); + int (*putc) (int dev, unsigned char data); + int (*start_read) (int dev); + int (*end_read) (int dev); + void (*kick)(int dev); + int (*command) (int dev, unsigned char data); + int (*buffer_status) (int dev); +}; + +/** UWM -- new structure for MIDI **/ + +struct generic_midi_operations { + struct midi_info info; + int (*open) (int dev, int mode); + void (*close) (int dev); + int (*write) (int dev, struct uio *data); + int (*read) (int dev, struct uio *data); +}; + +#ifndef ALL_EXTERNAL_TO_ME + +#ifdef _MIDI_TABLE_C_ + +/** UWM **/ + struct generic_midi_operations * generic_midi_devs[MAX_MIDI_DEV] = {NULL}; + int num_generic_midis = 0, pro_midi_dev = 0; + + struct generic_midi_info midi_supported[] = { + +#ifndef EXCLUDE_PRO_MIDI + {"ProAudioSpectrum MV101",pro_midi_attach} +#endif + }; + + int num_midi_drivers = + sizeof (midi_supported) / sizeof(struct generic_midi_info); + +#endif + + +#ifdef _DEV_TABLE_C_ + struct audio_operations * dsp_devs[MAX_DSP_DEV] = {NULL}; int num_dspdevs = 0; + struct mixer_operations * mixer_devs[MAX_MIXER_DEV] = {NULL}; int num_mixers = 0; + struct synth_operations * synth_devs[MAX_SYNTH_DEV] = {NULL}; int num_synths = 0; + struct midi_operations * midi_devs[MAX_MIDI_DEV] = {NULL}; int num_midis = 0; + + +# ifndef EXCLUDE_MPU401 + int mpu401_dev = 0; +# endif + +/* + * Note! The detection order is significant. Don't change it. + */ + + struct card_info supported_drivers[] = { +#ifndef EXCLUDE_MPU401 + {SNDCARD_MPU401,"Roland MPU-401", attach_mpu401, probe_mpu401, + {MPU_BASE, MPU_IRQ, 0}}, +#endif + +#ifndef EXCLUDE_GUS + {SNDCARD_GUS, "Gravis Ultrasound", attach_gus_card, probe_gus, + {GUS_BASE, GUS_IRQ, GUS_DMA}}, +#endif + +#ifndef EXCLUDE_PAS + {SNDCARD_PAS, "ProAudioSpectrum", attach_pas_card, probe_pas, + {PAS_BASE, PAS_IRQ, PAS_DMA}}, +#endif + +#ifndef EXCLUDE_SB + {SNDCARD_SB, "SoundBlaster", attach_sb_card, probe_sb, + {SBC_BASE, SBC_IRQ, SBC_DMA}}, +#endif + +#ifndef EXCLUDE_YM3812 + {SNDCARD_ADLIB, "AdLib", attach_adlib_card, probe_adlib, + {FM_MONO, 0, 0}}, +#endif + {0, "*?*", NULL} + }; + + int num_sound_drivers = + sizeof(supported_drivers) / sizeof (struct card_info); + + +# ifndef EXCLUDE_AUDIO + int sound_buffcounts[MAX_DSP_DEV] = {0}; + long sound_buffsizes[MAX_DSP_DEV] = {0}; + int sound_dsp_dmachan[MAX_DSP_DEV] = {0}; + int sound_dma_automode[MAX_DSP_DEV] = {0}; +# endif +#else + extern struct audio_operations * dsp_devs[MAX_DSP_DEV]; int num_dspdevs; + extern struct mixer_operations * mixer_devs[MAX_MIXER_DEV]; extern int num_mixers; + extern struct synth_operations * synth_devs[MAX_SYNTH_DEV]; extern int num_synths; + extern struct midi_operations * midi_devs[MAX_MIDI_DEV]; extern int num_midis; +# ifndef EXCLUDE_MPU401 + extern int mpu401_dev; +# endif + + extern struct card_info supported_drivers[]; + extern int num_sound_drivers; + +# ifndef EXCLUDE_AUDIO + extern int sound_buffcounts[MAX_DSP_DEV]; + extern long sound_buffsizes[MAX_DSP_DEV]; + extern int sound_dsp_dmachan[MAX_DSP_DEV]; + extern int sound_dma_automode[MAX_DSP_DEV]; +# endif + +#endif + +long sndtable_init(long mem_start); +int sndtable_get_cardcount (void); +long CMIDI_init(long mem_start); /* */ +#endif + +#endif + +/* If external to me.... :) */ + +#ifdef ALL_EXTERNAL_TO_ME + + extern struct audio_operations * dsp_devs[MAX_DSP_DEV]; int num_dspdevs; + extern struct mixer_operations * mixer_devs[MAX_MIXER_DEV]; extern int num_mixers; + extern struct synth_operations * synth_devs[MAX_SYNTH_DEV]; extern int num_synths; + extern struct midi_operations * midi_devs[MAX_MIDI_DEV]; extern int num_midis; + extern struct generic_midi_operations *generic_midi_devs[]; + extern int num_generic_midis, pro_midi_dev; + +#ifndef EXCLUDE_MPU401 + extern int mpu401_dev; +#endif + + extern struct generic_midi_info midi_supported[]; + extern struct card_info supported_drivers[]; + extern int num_sound_drivers; + extern int num_midi_drivers; +#ifndef EXCLUDE_AUDIO + extern int sound_buffcounts[MAX_DSP_DEV]; + extern long sound_buffsizes[MAX_DSP_DEV]; + extern int sound_dsp_dmachan[MAX_DSP_DEV]; + extern int sound_dma_automode[MAX_DSP_DEV]; +#endif + +#endif diff --git a/sys/i386/isa/sound/dmabuf.c b/sys/i386/isa/sound/dmabuf.c new file mode 100644 index 0000000..5123022 --- /dev/null +++ b/sys/i386/isa/sound/dmabuf.c @@ -0,0 +1,773 @@ +/* + * linux/kernel/chr_drv/sound/dmabuf.c + * + * The DMA buffer manager for digitized voice applications + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "sound_calls.h" + +#if !defined(EXCLUDE_AUDIO) || !defined(EXCLUDE_GUS) + +#define MAX_SUB_BUFFERS 16 + +/* + * The DSP channel can be used either for input or output. Variable + * 'dma_mode' will be set when the program calls read or write first time + * after open. Current version doesn't support mode changes without closing + * and reopening the device. Support for this feature may be implemented in a + * future version of this driver. + */ + +#define DMODE_NONE 0 +#define DMODE_OUTPUT 1 +#define DMODE_INPUT 2 +#define DMODE_INIT 3 + +DEFINE_WAIT_QUEUES (dev_sleeper[MAX_DSP_DEV], dev_sleep_flag[MAX_DSP_DEV]); + +static int dma_mode[MAX_DSP_DEV] = +{0}; /* DMODE_INPUT, DMODE_OUTPUT or DMODE_NONE */ + +static volatile int dmabuf_interrupted[MAX_DSP_DEV] = +{0}; + +#ifdef ISC +/* I don't like this. */ +#undef INTERRUPTIBLE_SLEEP_ON +#define INTERRUPTIBLE_SLEEP_ON(A,F) { \ + A = F = 1; \ + if (sleep(&(A), (PZERO + 5) | PCATCH)) { \ + A = F = 0; \ + dmabuf_interrupted[dev] = 1; \ + dev_busy[dev] = 0; \ + dma_reset(dev); \ + dmabuf_interrupted[dev] = 0; \ + /* longjmp(u.u_qsav, 1); Where it goes??? */ \ + } \ + } +#endif + +/* + * Pointers to raw buffers + */ + +char *snd_raw_buf[MAX_DSP_DEV][DSP_BUFFCOUNT]; +unsigned long snd_raw_buf_phys[MAX_DSP_DEV][DSP_BUFFCOUNT]; +int snd_raw_count[MAX_DSP_DEV]; + +/* + * Device state tables + */ + +static int dev_busy[MAX_DSP_DEV]; +static int dev_active[MAX_DSP_DEV]; +static int dev_qlen[MAX_DSP_DEV]; +static int dev_qhead[MAX_DSP_DEV]; +static int dev_qtail[MAX_DSP_DEV]; +static int dev_underrun[MAX_DSP_DEV]; +static int bufferalloc_done[MAX_DSP_DEV] = +{0}; + +/* + * Logical buffers for each devices + */ + +static int dev_nbufs[MAX_DSP_DEV]; /* # of logical buffers ( >= + * sound_buffcounts[dev] */ +static int dev_counts[MAX_DSP_DEV][MAX_SUB_BUFFERS]; +static unsigned long dev_buf_phys[MAX_DSP_DEV][MAX_SUB_BUFFERS]; +static char *dev_buf[MAX_DSP_DEV][MAX_SUB_BUFFERS]; +static int dev_buffsize[MAX_DSP_DEV]; + +static void +reorganize_buffers (int dev) +{ + /* + * This routine breaks the physical device buffers to logical ones. + */ + + unsigned long i, p, n; + unsigned long sr, nc, sz, bsz; + + sr = dsp_devs[dev]->ioctl (dev, SOUND_PCM_READ_RATE, 0, 1); + nc = dsp_devs[dev]->ioctl (dev, SOUND_PCM_READ_CHANNELS, 0, 1); + sz = dsp_devs[dev]->ioctl (dev, SOUND_PCM_READ_BITS, 0, 1); + + if (sr < 1 || nc < 1 || sz < 1) + { + printk ("SOUND: Invalid PCM parameters[%d] sr=%d, nc=%d, sz=%d\n", dev, sr, nc, sz); + sr = DSP_DEFAULT_SPEED; + nc = 1; + sz = 8; + } + + sz /= 8; /* Convert # of bits -> # of bytes */ + + sz = sr * nc * sz; + + /* + * Compute a buffer size not exeeding 1 second. + */ + + bsz = sound_buffsizes[dev]; + + while (bsz > sz) + bsz >>= 1; /* Divide by 2 */ + + if (sound_buffcounts[dev] == 1 && bsz == sound_buffsizes[dev]) + bsz >>= 1; /* Need at least 2 buffers */ + + dev_buffsize[dev] = bsz; + n = 0; + + /* + * Now computing addresses for the logical buffers + */ + + for (i = 0; i < snd_raw_count[dev]; i++) + { + p = 0; + + while ((p + bsz) <= sound_buffsizes[dev]) + { + dev_buf[dev][n] = snd_raw_buf[dev][i] + p; + dev_buf_phys[dev][n] = snd_raw_buf_phys[dev][i] + p; + p += bsz; + n++; + } + } + + dev_nbufs[dev] = n; + + for (i = 0; i < dev_nbufs[dev]; i++) + { + dev_counts[dev][i] = 0; + } + + bufferalloc_done[dev] = 1; +} + +int +DMAbuf_open (int dev, int mode) +{ + int retval; + + if (dev >= num_dspdevs) + { + printk ("PCM device %d not installed.\n", dev); + return RET_ERROR (ENXIO); + } + + if (dev_busy[dev]) + return RET_ERROR (EBUSY); + + if (!dsp_devs[dev]) + { + printk ("DSP device %d not initialized\n", dev); + return RET_ERROR (ENXIO); + } + + if (sound_buffcounts[dev] <= 0) + return RET_ERROR (ENOSPC); /* Memory allocation failed during boot */ + + if ((retval = dsp_devs[dev]->open (dev, mode)) < 0) + return retval; + + dev_underrun[dev] = 0; + + dev_busy[dev] = 1; + + reorganize_buffers (dev); + bufferalloc_done[dev] = 0; + + dev_qlen[dev] = dev_qtail[dev] = dev_qhead[dev] = 0; + + return 0; +} + +static void +dma_reset (int dev) +{ + dsp_devs[dev]->reset (dev); + + dev_qlen[dev] = 0; + dev_qhead[dev] = 0; + dev_qtail[dev] = 0; + dev_active[dev] = 0; +} + +static int +dma_sync (int dev) +{ + unsigned long flags; + unsigned long time; + int timed_out; + + if (dma_mode[dev] == DMODE_OUTPUT) + { + DISABLE_INTR (flags); + + timed_out = 0; + time = GET_TIME (); + + while ((!(PROCESS_ABORTING || dmabuf_interrupted[dev]) && !timed_out) + && dev_qlen[dev]) + { + REQUEST_TIMEOUT (10 * HZ, dev_sleeper[dev]); + INTERRUPTIBLE_SLEEP_ON (dev_sleeper[dev], dev_sleep_flag[dev]); + if ((GET_TIME () - time) > (10 * HZ)) + timed_out = 1; + } + RESTORE_INTR (flags); + + /* + * Some devices such as GUS have huge amount of on board RAM for the + * audio data. We have to wait util the device has finished playing. + */ + + DISABLE_INTR (flags); + if (dsp_devs[dev]->has_output_drained) /* Device has hidden buffers */ + { + while (!(PROCESS_ABORTING || dmabuf_interrupted[dev]) + && !dsp_devs[dev]->has_output_drained (dev)) + { + REQUEST_TIMEOUT (HZ / 4, dev_sleeper[dev]); + INTERRUPTIBLE_SLEEP_ON (dev_sleeper[dev], dev_sleep_flag[dev]); + } + } + RESTORE_INTR (flags); + } + return dev_qlen[dev]; +} + +int +DMAbuf_release (int dev, int mode) +{ + + if (!(PROCESS_ABORTING || dmabuf_interrupted[dev]) + && (dma_mode[dev] == DMODE_OUTPUT)) + { + dma_sync (dev); + } + + dma_reset (dev); + + if (!dev_active[dev]) + dsp_devs[dev]->close (dev); + + dma_mode[dev] = DMODE_NONE; + dev_busy[dev] = 0; + + return 0; +} + +int +DMAbuf_getrdbuffer (int dev, char **buf, int *len) +{ + unsigned long flags; + + if (!bufferalloc_done[dev]) + reorganize_buffers (dev); + + if (!dma_mode[dev]) + { + int err; + + if ((err = dsp_devs[dev]->prepare_for_input (dev, + dev_buffsize[dev], dev_nbufs[dev])) < 0) + return err; + dma_mode[dev] = DMODE_INPUT; + } + + if (dma_mode[dev] != DMODE_INPUT) + return RET_ERROR (EBUSY); /* Can't change mode on fly */ + + DISABLE_INTR (flags); + if (!dev_qlen[dev]) + { + if (!dev_active[dev]) + { + dsp_devs[dev]->start_input (dev, dev_buf_phys[dev][dev_qtail[dev]], dev_buffsize[dev], 0); + dev_active[dev] = 1; + } + + /* Wait for the next block */ +#ifdef CRYPTO + REQUEST_TIMEOUT (60 * HZ, dev_sleeper[dev]); +#else + REQUEST_TIMEOUT (10 * HZ, dev_sleeper[dev]); +#endif + INTERRUPTIBLE_SLEEP_ON (dev_sleeper[dev], dev_sleep_flag[dev]); + } + RESTORE_INTR (flags); + + if (!dev_qlen[dev]) + return RET_ERROR (EINTR); + + *buf = &dev_buf[dev][dev_qhead[dev]][dev_counts[dev][dev_qhead[dev]]]; + *len = dev_buffsize[dev] - dev_counts[dev][dev_qhead[dev]]; + + return dev_qhead[dev]; +} + +int +DMAbuf_rmchars (int dev, int buff_no, int c) +{ + int p = dev_counts[dev][dev_qhead[dev]] + c; + + if (p >= dev_buffsize[dev]) + { /* This buffer is now empty */ + dev_counts[dev][dev_qhead[dev]] = 0; + dev_qlen[dev]--; + dev_qhead[dev] = (dev_qhead[dev] + 1) % dev_nbufs[dev]; + } + else + dev_counts[dev][dev_qhead[dev]] = p; + + return 0; +} + +int +DMAbuf_read (int dev, snd_rw_buf * user_buf, int count) +{ + char *dmabuf; + int buff_no, c, err; + + /* + * This routine returns at most 'count' bytes from the dsp input buffers. + * Returns negative value if there is an error. + */ + + if ((buff_no = DMAbuf_getrdbuffer (dev, &dmabuf, &c)) < 0) + return buff_no; + + if (c > count) + c = count; + + COPY_TO_USER (user_buf, 0, dmabuf, c); + + if ((err = DMAbuf_rmchars (dev, buff_no, c)) < 0) + return err; + return c; + +} + +int +DMAbuf_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + switch (cmd) + { + case SNDCTL_DSP_RESET: + dma_reset (dev); + return 0; + break; + + case SNDCTL_DSP_SYNC: + dma_sync (dev); + return 0; + break; + + case SNDCTL_DSP_GETBLKSIZE: + if (!bufferalloc_done[dev]) + reorganize_buffers (dev); + + return IOCTL_OUT (arg, dev_buffsize[dev]); + break; + + default: + return dsp_devs[dev]->ioctl (dev, cmd, arg, local); + } + + return RET_ERROR (EIO); +} + +int +DMAbuf_getwrbuffer (int dev, char **buf, int *size) +{ + unsigned long flags; + + if (!bufferalloc_done[dev]) + reorganize_buffers (dev); + + if (!dma_mode[dev]) + { + int err; + + dma_mode[dev] = DMODE_OUTPUT; + if ((err = dsp_devs[dev]->prepare_for_output (dev, + dev_buffsize[dev], dev_nbufs[dev])) < 0) + return err; + } + + if (dma_mode[dev] != DMODE_OUTPUT) + return RET_ERROR (EBUSY); /* Can't change mode on fly */ + + DISABLE_INTR (flags); + if (dev_qlen[dev] == dev_nbufs[dev]) + { + if (!dev_active[dev]) + { + printk ("Soundcard warning: DMA not activated %d/%d\n", + dev_qlen[dev], dev_nbufs[dev]); + return RET_ERROR (EIO); + } + + /* Wait for free space */ + REQUEST_TIMEOUT (60 * HZ, dev_sleeper[dev]); /* Overestimated timeout */ + INTERRUPTIBLE_SLEEP_ON (dev_sleeper[dev], dev_sleep_flag[dev]); + } + RESTORE_INTR (flags); + + if (dev_qlen[dev] == dev_nbufs[dev]) + return RET_ERROR (EIO); /* We have got signal (?) */ + + *buf = dev_buf[dev][dev_qtail[dev]]; + *size = dev_buffsize[dev]; + dev_counts[dev][dev_qtail[dev]] = 0; + + return dev_qtail[dev]; +} + +int +DMAbuf_start_output (int dev, int buff_no, int l) +{ + if (buff_no != dev_qtail[dev]) + printk ("Soundcard warning: DMA buffers out of sync %d != %d\n", buff_no, dev_qtail[dev]); + + dev_qlen[dev]++; + + dev_counts[dev][dev_qtail[dev]] = l; + + dev_qtail[dev] = (dev_qtail[dev] + 1) % dev_nbufs[dev]; + + if (!dev_active[dev]) + { + dev_active[dev] = 1; + dsp_devs[dev]->output_block (dev, dev_buf_phys[dev][dev_qhead[dev]], dev_counts[dev][dev_qhead[dev]], 0); + } + + return 0; +} + +int +DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode) +{ + int chan = sound_dsp_dmachan[dev]; + unsigned long flags; + + /* + * This function is not as portable as it should be. + */ + + /* + * The count must be one less than the actual size. This is handled by + * set_dma_addr() + */ + + if (sound_dma_automode[dev]) + { /* Auto restart mode. Transfer the whole + * buffer */ +#ifdef linux + DISABLE_INTR (flags); + disable_dma (chan); + clear_dma_ff (chan); + set_dma_mode (chan, dma_mode | DMA_AUTOINIT); + set_dma_addr (chan, snd_raw_buf_phys[dev][0]); + set_dma_count (chan, sound_buffsizes[dev]); + enable_dma (chan); + RESTORE_INTR (flags); +#else + +#ifdef __386BSD__ + printk ("sound: Invalid DMA mode for device %d\n", dev); + + isa_dmastart ((dma_mode == DMA_MODE_READ) ? B_READ : B_WRITE, + snd_raw_buf_phys[dev][0], + sound_buffsizes[dev], + chan); +#else +#ifdef ISC + printk ("sound: Invalid DMA mode for device %d\n", dev); + dma_param (chan, ((dma_mode == DMA_MODE_READ) ? DMA_Rdmode : DMA_Wrmode) | DMAMODE_AUTO, + snd_raw_buf_phys[dev][0], count - 1); + dma_enable (chan); +#else +# error This routine is not valid for this OS. +#endif +#endif + +#endif + } + else + { +#ifdef linux + DISABLE_INTR (flags); + disable_dma (chan); + clear_dma_ff (chan); + set_dma_mode (chan, dma_mode); + set_dma_addr (chan, physaddr); + set_dma_count (chan, count); + enable_dma (chan); + RESTORE_INTR (flags); +#else +#ifdef __386BSD__ + isa_dmastart ((dma_mode == DMA_MODE_READ) ? B_READ : B_WRITE, + physaddr, + count, + chan); +#else + +#ifdef ISC + dma_param (chan, ((dma_mode == DMA_MODE_READ) ? DMA_Rdmode : DMA_Wrmode), + physaddr, count - 1); + dma_enable (chan); +#else +# error This routine is not valid for this OS. +#endif /* !ISC */ +#endif + +#endif + } + + return count; +} + +long +DMAbuf_init (long mem_start) +{ + int i; + + /* + * In this version the DMA buffer allocation is done by sound_mem_init() + * which is called by init/main.c + */ + + for (i = 0; i < MAX_DSP_DEV; i++) + { + dev_qlen[i] = 0; + dev_qhead[i] = 0; + dev_qtail[i] = 0; + dev_active[i] = 0; + dev_busy[i] = 0; + bufferalloc_done[i] = 0; + } + + return mem_start; +} + +void +DMAbuf_outputintr (int dev) +{ + unsigned long flags; + + dev_active[dev] = 0; + dev_qlen[dev]--; + dev_qhead[dev] = (dev_qhead[dev] + 1) % dev_nbufs[dev]; + + if (dev_qlen[dev]) + { + dsp_devs[dev]->output_block (dev, dev_buf_phys[dev][dev_qhead[dev]], dev_counts[dev][dev_qhead[dev]], 1); + dev_active[dev] = 1; + } + else + { + if (dev_busy[dev]) + { + dev_underrun[dev]++; + dsp_devs[dev]->halt_xfer (dev); + } + else + { /* Device has been closed */ + dsp_devs[dev]->close (dev); + } + } + + DISABLE_INTR (flags); + if (dev_sleep_flag[dev]) + { + dev_sleep_flag[dev] = 0; + WAKE_UP (dev_sleeper[dev]); + } + RESTORE_INTR (flags); +} + +void +DMAbuf_inputintr (int dev) +{ + unsigned long flags; + + dev_active[dev] = 0; + if (!dev_busy[dev]) + { + dsp_devs[dev]->close (dev); + } + else if (dev_qlen[dev] == (dev_nbufs[dev] - 1)) + { + dev_underrun[dev]++; + dsp_devs[dev]->halt_xfer (dev); + } + else + { + dev_qlen[dev]++; + dev_qtail[dev] = (dev_qtail[dev] + 1) % dev_nbufs[dev]; + + dsp_devs[dev]->start_input (dev, dev_buf_phys[dev][dev_qtail[dev]], dev_buffsize[dev], 1); + dev_active[dev] = 1; + } + + DISABLE_INTR (flags); + if (dev_sleep_flag[dev]) + { + dev_sleep_flag[dev] = 0; + WAKE_UP (dev_sleeper[dev]); + } + RESTORE_INTR (flags); +} + +int +DMAbuf_open_dma (int dev) +{ + unsigned long flags; + int chan = sound_dsp_dmachan[dev]; + + if (ALLOC_DMA_CHN (chan)) + { + printk ("Unable to grab DMA%d for the audio driver\n", chan); + return 0; + } + + DISABLE_INTR (flags); +#ifdef linux + disable_dma (chan); + clear_dma_ff (chan); +#endif + RESTORE_INTR (flags); + + return 1; +} + +void +DMAbuf_close_dma (int dev) +{ + int chan = sound_dsp_dmachan[dev]; + + DMAbuf_reset_dma (chan); + RELEASE_DMA_CHN (chan); +} + +void +DMAbuf_reset_dma (int chan) +{ +} + +/* + * The sound_mem_init() is called by mem_init() immediately after mem_map is + * initialized and before free_page_list is created. + * + * This routine allocates DMA buffers at the end of available physical memory ( + * <16M) and marks pages reserved at mem_map. + */ + +#else +/* Stub versions if audio services not included */ + +int +DMAbuf_open (int dev, int mode) +{ + return RET_ERROR (ENXIO); +} + +int +DMAbuf_release (int dev, int mode) +{ + return 0; +} + +int +DMAbuf_read (int dev, snd_rw_buf * user_buf, int count) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_getwrbuffer (int dev, char **buf, int *size) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_getrdbuffer (int dev, char **buf, int *len) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_rmchars (int dev, int buff_no, int c) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_start_output (int dev, int buff_no, int l) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + return RET_ERROR (EIO); +} + +long +DMAbuf_init (long mem_start) +{ + return mem_start; +} + +int +DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode) +{ + return RET_ERROR (EIO); +} + +int +DMAbuf_open_dma (int chan) +{ + return RET_ERROR (ENXIO); +} + +void +DMAbuf_close_dma (int chan) +{ + return; +} + +void +DMAbuf_reset_dma (int chan) +{ + return; +} + +void +DMAbuf_inputintr (int dev) +{ + return; +} + +void +DMAbuf_outputintr (int dev) +{ + return; +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/finetune.h b/sys/i386/isa/sound/finetune.h new file mode 100644 index 0000000..07fd3e0 --- /dev/null +++ b/sys/i386/isa/sound/finetune.h @@ -0,0 +1,28 @@ +#ifdef SEQUENCER_C +/* + * Copyright Hannu Savolainen 1993 + * See COPYING for further details. Should be distributed with this file. + */ + + unsigned short finetune_table[128] = + { +/* 0 */ 9439, 9447, 9456, 9464, 9473, 9481, 9490, 9499, +/* 8 */ 9507, 9516, 9524, 9533, 9542, 9550, 9559, 9567, +/* 16 */ 9576, 9585, 9593, 9602, 9611, 9619, 9628, 9637, +/* 24 */ 9645, 9654, 9663, 9672, 9680, 9689, 9698, 9707, +/* 32 */ 9715, 9724, 9733, 9742, 9750, 9759, 9768, 9777, +/* 40 */ 9786, 9795, 9803, 9812, 9821, 9830, 9839, 9848, +/* 48 */ 9857, 9866, 9874, 9883, 9892, 9901, 9910, 9919, +/* 56 */ 9928, 9937, 9946, 9955, 9964, 9973, 9982, 9991, +/* 64 */ 10000, 10009, 10018, 10027, 10036, 10045, 10054, 10063, +/* 72 */ 10072, 10082, 10091, 10100, 10109, 10118, 10127, 10136, +/* 80 */ 10145, 10155, 10164, 10173, 10182, 10191, 10201, 10210, +/* 88 */ 10219, 10228, 10237, 10247, 10256, 10265, 10274, 10284, +/* 96 */ 10293, 10302, 10312, 10321, 10330, 10340, 10349, 10358, +/* 104 */ 10368, 10377, 10386, 10396, 10405, 10415, 10424, 10433, +/* 112 */ 10443, 10452, 10462, 10471, 10481, 10490, 10499, 10509, +/* 120 */ 10518, 10528, 10537, 10547, 10556, 10566, 10576, 10585 + }; +#else + extern unsigned short finetune_table[128]; +#endif diff --git a/sys/i386/isa/sound/gus_card.c b/sys/i386/isa/sound/gus_card.c new file mode 100644 index 0000000..39aca78 --- /dev/null +++ b/sys/i386/isa/sound/gus_card.c @@ -0,0 +1,183 @@ + +/* + * linux/kernel/chr_drv/sound/gus_card.c + * + * Detection routine for the Gravis Ultrasound. + * + * (C) 1993 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_GUS) + +#include "gus_hw.h" + +void gusintr (int); + +int gus_base, gus_irq, gus_dma; + +static int +set_gus_irq (int interrupt_level) +{ + int retcode; + +#ifdef linux + struct sigaction sa; + + sa.sa_handler = gusintr; + +#ifdef SND_SA_INTERRUPT + sa.sa_flags = SA_INTERRUPT; +#else + sa.sa_flags = 0; +#endif + + sa.sa_mask = 0; + sa.sa_restorer = NULL; + + retcode = irqaction (interrupt_level, &sa); + + if (retcode < 0) + { + printk ("GUS: IRQ%d already in use\n", interrupt_level); + } + +#else + /* # error Unimplemented for this OS */ +#endif + return retcode; +} + +int +gus_set_midi_irq (int interrupt_level) +{ + int retcode; + +#ifdef linux + struct sigaction sa; + + sa.sa_handler = gus_midi_interrupt; + +#ifdef SND_SA_INTERRUPT + sa.sa_flags = SA_INTERRUPT; +#else + sa.sa_flags = 0; +#endif + + sa.sa_mask = 0; + sa.sa_restorer = NULL; + + retcode = irqaction (interrupt_level, &sa); + + if (retcode < 0) + { + printk ("GUS: IRQ%d already in use\n", interrupt_level); + } + +#else + /* # error Unimplemented for this OS */ +#endif + return retcode; +} + +long +attach_gus_card (long mem_start, struct address_info *hw_config) +{ + int io_addr; + + set_gus_irq (hw_config->irq); + + if (gus_wave_detect (hw_config->io_base)) /* Try first the default */ + { + mem_start = gus_wave_init (mem_start, hw_config->irq, hw_config->dma); +#ifndef EXCLUDE_MIDI + mem_start = gus_midi_init (mem_start); +#endif + return mem_start; + } + +#ifndef EXCLUDE_GUS_IODETECT + + /* + * Look at the possible base addresses (0x2X0, X=1, 2, 3, 4, 5, 6) + */ + + for (io_addr = 0x210; io_addr <= 0x260; io_addr += 0x10) + if (io_addr != hw_config->io_base) /* Already tested */ + if (gus_wave_detect (io_addr)) + { + printk (" WARNING! GUS found at %03x, config was %03x ", io_addr, hw_config->io_base); + mem_start = gus_wave_init (mem_start, hw_config->irq, hw_config->dma); +#ifndef EXCLUDE_MIDI + mem_start = gus_midi_init (mem_start); +#endif + return mem_start; + } + +#endif + + return mem_start; /* Not detected */ +} + +int +probe_gus (struct address_info *hw_config) +{ + int io_addr; + + if (gus_wave_detect (hw_config->io_base)) + return 1; + +#ifndef EXCLUDE_GUS_IODETECT + + /* + * Look at the possible base addresses (0x2X0, X=1, 2, 3, 4, 5, 6) + */ + + for (io_addr = 0x210; io_addr <= 0x260; io_addr += 0x10) + if (io_addr != hw_config->io_base) /* Already tested */ + if (gus_wave_detect (io_addr)) + return 1; + +#endif + + return 0; +} + +void +gusintr (int unit) +{ + unsigned char src; + + while (1) + { + if (!(src = INB (u_IrqStatus))) + return; + + if (src & DMA_TC_IRQ) + { + guswave_dma_irq (); + } + + if (src & (MIDI_TX_IRQ | MIDI_RX_IRQ)) + { +#ifndef EXCLUDE_MIDI + gus_midi_interrupt (0); +#endif + } + + if (src & (GF1_TIMER1_IRQ | GF1_TIMER2_IRQ)) + { + printk ("T"); + gus_write8 (0x45, 0); /* Timer control */ + } + + if (src & (WAVETABLE_IRQ | ENVELOPE_IRQ)) + { + gus_voice_irq (); + } + } +} + +#endif diff --git a/sys/i386/isa/sound/gus_hw.h b/sys/i386/isa/sound/gus_hw.h new file mode 100644 index 0000000..48233e7 --- /dev/null +++ b/sys/i386/isa/sound/gus_hw.h @@ -0,0 +1,35 @@ + +/* + * I/O addresses + */ + +#define u_Base (gus_base + 0x000) +#define u_Mixer u_Base +#define u_Status (gus_base + 0x006) +#define u_TimerControl (gus_base + 0x008) +#define u_TimerData (gus_base + 0x009) +#define u_IRQDMAControl (gus_base + 0x00b) +#define u_MidiControl (gus_base + 0x100) +#define MIDI_RESET 0x03 +#define MIDI_ENABLE_XMIT 0x20 +#define MIDI_ENABLE_RCV 0x80 +#define u_MidiStatus u_MidiControl +#define MIDI_RCV_FULL 0x01 +#define MIDI_XMIT_EMPTY 0x02 +#define MIDI_FRAME_ERR 0x10 +#define MIDI_OVERRUN 0x20 +#define MIDI_IRQ_PEND 0x80 +#define u_MidiData (gus_base + 0x101) +#define u_Voice (gus_base + 0x102) +#define u_Command (gus_base + 0x103) +#define u_DataLo (gus_base + 0x104) +#define u_DataHi (gus_base + 0x105) +#define u_IrqStatus u_Status +# define MIDI_TX_IRQ 0x01 /* pending MIDI xmit IRQ */ +# define MIDI_RX_IRQ 0x02 /* pending MIDI recv IRQ */ +# define GF1_TIMER1_IRQ 0x04 /* general purpose timer */ +# define GF1_TIMER2_IRQ 0x08 /* general purpose timer */ +# define WAVETABLE_IRQ 0x20 /* pending wavetable IRQ */ +# define ENVELOPE_IRQ 0x40 /* pending volume envelope IRQ */ +# define DMA_TC_IRQ 0x80 /* pending dma tc IRQ */ +#define u_DRAMIO (gus_base + 0x107) diff --git a/sys/i386/isa/sound/gus_midi.c b/sys/i386/isa/sound/gus_midi.c new file mode 100644 index 0000000..6978eed --- /dev/null +++ b/sys/i386/isa/sound/gus_midi.c @@ -0,0 +1,257 @@ +/* + * linux/kernel/chr_drv/sound/gus2_midi.c + * + * The low level driver for the GUS Midi Interface. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "gus_hw.h" + +#if !defined(EXCLUDE_GUS) && !defined(EXCLUDE_MIDI) + +static int midi_busy = 0, input_opened = 0; +static int my_dev; +static int output_used = 0; +static volatile unsigned char gus_midi_control; + +static unsigned char tmp_queue[256]; +static volatile int qlen; +static volatile unsigned char qhead, qtail; +extern int gus_base, gus_irq, gus_dma; + +#define GUS_MIDI_STATUS() INB(u_MidiStatus) + +static int +gus_midi_open (int dev, int mode) +{ + + if (midi_busy) + { + printk ("GUS: Midi busy\n"); + return RET_ERROR (EBUSY); + } + + OUTB (MIDI_RESET, u_MidiControl); + gus_delay (); + + gus_midi_control = 0; + input_opened = 0; + + if (mode == OPEN_READ || mode == OPEN_READWRITE) + { + gus_midi_control |= MIDI_ENABLE_RCV; + input_opened = 1; + } + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + { + gus_midi_control |= MIDI_ENABLE_XMIT; + } + + OUTB (gus_midi_control, u_MidiControl); /* Enable */ + + midi_busy = 1; + qlen = qhead = qtail = output_used = 0; + + return 0; +} + +static int +dump_to_midi (unsigned char midi_byte) +{ + unsigned long flags; + int ok = 0; + + output_used = 1; + + DISABLE_INTR (flags); + + if (GUS_MIDI_STATUS () & MIDI_XMIT_EMPTY) + { + ok = 1; + OUTB (midi_byte, u_MidiData); + } + else + { + /* Enable Midi xmit interrupts (again) */ + gus_midi_control |= MIDI_ENABLE_XMIT; + OUTB (gus_midi_control, u_MidiControl); + } + + RESTORE_INTR (flags); + return ok; +} + +static void +gus_midi_close (int dev) +{ + /* Reset FIFO pointers, disable intrs */ + + OUTB (MIDI_RESET, u_MidiControl); + midi_busy = 0; +} + +static int +gus_midi_out (int dev, unsigned char midi_byte) +{ + + unsigned long flags; + + /* + * Drain the local queue first + */ + + DISABLE_INTR (flags); + + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + + /* + * Output the byte if the local queue is empty. + */ + + if (!qlen) + if (dump_to_midi (midi_byte)) + return 1; /* OK */ + + /* + * Put to the local queue + */ + + if (qlen >= 256) + return 0; /* Local queue full */ + + DISABLE_INTR (flags); + + tmp_queue[qtail] = midi_byte; + qlen++; + qtail++; + + RESTORE_INTR (flags); + + return 1; +} + +static int +gus_midi_start_read (int dev) +{ + return 0; +} + +static int +gus_midi_end_read (int dev) +{ + return 0; +} + +static int +gus_midi_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EINVAL); +} + +static void +gus_midi_kick (int dev) +{ +} + +static int +gus_midi_buffer_status (int dev) +{ + unsigned long flags; + + if (!output_used) + return 0; + + DISABLE_INTR (flags); + + if (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + + return (qlen > 0) | !(GUS_MIDI_STATUS () & MIDI_XMIT_EMPTY); +} + +static struct midi_operations gus_midi_operations = +{ + {"Gravis UltraSound", 0}, + gus_midi_open, + gus_midi_close, + gus_midi_ioctl, + gus_midi_out, + gus_midi_start_read, + gus_midi_end_read, + gus_midi_kick, + NULL, /* command */ + gus_midi_buffer_status +}; + +long +gus_midi_init (long mem_start) +{ + OUTB (MIDI_RESET, u_MidiControl); + + my_dev = num_midis; + midi_devs[num_midis++] = &gus_midi_operations; + return mem_start; +} + +void +gus_midi_interrupt (int dummy) +{ + unsigned char stat, data; + unsigned long flags; + + DISABLE_INTR (flags); + + stat = GUS_MIDI_STATUS (); + + if (stat & MIDI_RCV_FULL) + { + data = INB (u_MidiData); + if (input_opened) + sequencer_midi_input (my_dev, data); + } + + if (stat & MIDI_XMIT_EMPTY) + { + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + if (!qlen) + { + /* Disable Midi output interrupts, since no data in the buffer */ + gus_midi_control &= ~MIDI_ENABLE_XMIT; + OUTB (gus_midi_control, u_MidiControl); + } + } + + if (stat & MIDI_FRAME_ERR) + printk ("Midi framing error\n"); + if (stat & MIDI_OVERRUN && input_opened) + printk ("GUS: Midi input overrun\n"); + + RESTORE_INTR (flags); +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/gus_vol.c b/sys/i386/isa/sound/gus_vol.c new file mode 100644 index 0000000..b3f1e84 --- /dev/null +++ b/sys/i386/isa/sound/gus_vol.c @@ -0,0 +1,101 @@ +/* + * gus_vol.c - Compute volume for GUS. + * + * Greg Lee 1993. + */ +#include "sound_config.h" +#ifndef EXCLUDE_GUS + +#define GUS_VOLUME gus_wave_volume + + +extern int gus_wave_volume; + +/* + * Calculate gus volume from note velocity, main volume, expression, and + * intrinsic patch volume given in patch library. Expression is multiplied + * in, so it emphasizes differences in note velocity, while main volume is + * added in -- I don't know whether this is right, but it seems reasonable to + * me. (In the previous stage, main volume controller messages were changed + * to expression controller messages, if they were found to be used for + * dynamic volume adjustments, so here, main volume can be assumed to be + * constant throughout a song.) + * + * Intrinsic patch volume is added in, but if over 64 is also multiplied in, so + * we can give a big boost to very weak voices like nylon guitar and the + * basses. The normal value is 64. Strings are assigned lower values. + */ +unsigned short +gus_adagio_vol (int vel, int mainv, int xpn, int voicev) +{ + int i, m, n, x; + + + /* + * A voice volume of 64 is considered neutral, so adjust the main volume if + * something other than this neutral value was assigned in the patch + * library. + */ + x = 256 + 6 * (voicev - 64); + + /* Boost expression by voice volume above neutral. */ + if (voicev > 65) + xpn += voicev - 64; + xpn += (voicev - 64) / 2; + + /* Combine multiplicative and level components. */ + x = vel * xpn * 6 + (voicev / 4) * x; + +#ifdef GUS_VOLUME + /* + * Further adjustment by installation-specific master volume control + * (default 50). + */ + x = (x * GUS_VOLUME * GUS_VOLUME) / 10000; +#endif + + if (x < (1 << 11)) + return (11 << 8); + else if (x >= 65535) + return ((15 << 8) | 255); + + /* + * Convert to gus's logarithmic form with 4 bit exponent i and 8 bit + * mantissa m. + */ + n = x; + i = 7; + if (n < 128) + { + while (i > 0 && n < (1 << i)) + i--; + } + else + while (n > 255) + { + n >>= 1; + i++; + } + /* + * Mantissa is part of linear volume not expressed in exponent. (This is + * not quite like real logs -- I wonder if it's right.) + */ + m = x - (1 << i); + + /* Adjust mantissa to 8 bits. */ + if (m > 0) + { + if (i > 8) + m >>= i - 8; + else if (i < 8) + m <<= 8 - i; + } + + /* low volumes give occasional sour notes */ + if (i < 11) + return (11 << 8); + + return ((i << 8) + m); +} + +#endif diff --git a/sys/i386/isa/sound/gus_wave.c b/sys/i386/isa/sound/gus_wave.c new file mode 100644 index 0000000..bc00af9 --- /dev/null +++ b/sys/i386/isa/sound/gus_wave.c @@ -0,0 +1,2523 @@ + +/* + * linux/kernel/chr_drv/sound/gus_wave.c + * + * Driver for the Gravis UltraSound wave table synth. + * + * (C) 1993 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +/* #define GUS_LINEAR_VOLUME */ + +#include "sound_config.h" +#include "ultrasound.h" +#include "gus_hw.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_GUS) + +#define MAX_SAMPLE 256 +#define MAX_PATCH 256 + +struct voice_info + { + unsigned long orig_freq; + unsigned long current_freq; + unsigned long mode; + int bender; + int bender_range; + int panning; + int midi_volume; + unsigned int initial_volume; + unsigned int current_volume; + int loop_irq_mode, loop_irq_parm; +#define LMODE_FINISH 1 +#define LMODE_PCM 2 +#define LMODE_PCM_STOP 3 + int volume_irq_mode, volume_irq_parm; +#define VMODE_HALT 1 +#define VMODE_ENVELOPE 2 + + int env_phase; + unsigned char env_rate[6]; + unsigned char env_offset[6]; + + /* + * Volume computation parameters for gus_adagio_vol() + */ + int main_vol, expression_vol, patch_vol; + + }; + +extern int gus_base; +extern int gus_irq, gus_dma; +extern char *snd_raw_buf[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern unsigned long snd_raw_buf_phys[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern int snd_raw_count[MAX_DSP_DEV]; +static long gus_mem_size = 0; +static long free_mem_ptr = 0; +static int gus_busy = 0; +static int nr_voices = 0; /* Number of currently allowed voices */ +static int gus_devnum = 0; +static int volume_base, volume_scale, volume_method; + +#define VOL_METHOD_ADAGIO 1 +int gus_wave_volume = 60; /* Master wolume for wave (0 to 100) */ +static unsigned char mix_image = 0x00; + +/* + * Current version of this_one driver doesn't allow synth and PCM functions + * at the same time. The active_device specifies the active driver + */ +static int active_device = 0; + +#define GUS_DEV_WAVE 1 /* Wave table synth */ +#define GUS_DEV_PCM_DONE 2 /* PCM device, transfer done */ +#define GUS_DEV_PCM_CONTINUE 3 /* PCM device, transfer the second + * chn */ + +static int gus_sampling_speed; +static int gus_sampling_channels; +static int gus_sampling_bits; + +DEFINE_WAIT_QUEUE (dram_sleeper, dram_sleep_flag); + +/* + * Variables and buffers for PCM output + */ +#define MAX_PCM_BUFFERS 32 /* Don't change */ +static int pcm_bsize, /* Current blocksize */ + pcm_nblk, /* Current # of blocks */ + pcm_banksize; /* # bytes allocated for channels */ +static int pcm_datasize[MAX_PCM_BUFFERS]; /* Actual # of bytes in blk */ +static volatile int pcm_head, pcm_tail, pcm_qlen; /* DRAM queue */ +static volatile int pcm_active; +static int pcm_current_dev; +static int pcm_current_block; +static unsigned long pcm_current_buf; +static int pcm_current_count; +static int pcm_current_intrflag; + +struct voice_info voices[32]; + +static int freq_div_table[] = +{ + 44100, /* 14 */ + 41160, /* 15 */ + 38587, /* 16 */ + 36317, /* 17 */ + 34300, /* 18 */ + 32494, /* 19 */ + 30870, /* 20 */ + 29400, /* 21 */ + 28063, /* 22 */ + 26843, /* 23 */ + 25725, /* 24 */ + 24696, /* 25 */ + 23746, /* 26 */ + 22866, /* 27 */ + 22050, /* 28 */ + 21289, /* 29 */ + 20580, /* 30 */ + 19916, /* 31 */ + 19293 /* 32 */ +}; + +static struct patch_info samples[MAX_SAMPLE + 1]; +static long sample_ptrs[MAX_SAMPLE + 1]; +static int sample_map[32]; +static int free_sample; + + +static int patch_table[MAX_PATCH]; +static int patch_map[32]; + +static struct synth_info gus_info = +{"Gravis UltraSound", 0, SYNTH_TYPE_SAMPLE, SAMPLE_TYPE_GUS, 0, 16, 0, MAX_PATCH}; + +static void gus_poke (long addr, unsigned char data); +static void compute_and_set_volume (int voice, int volume, int ramp_time); +extern unsigned short gus_adagio_vol (int vel, int mainv, int xpn, int voicev); +static void compute_volume (int voice, int volume); + +#define INSTANT_RAMP -1 /* Dont use ramping */ +#define FAST_RAMP 0 /* Fastest possible ramp */ + +static void +reset_sample_memory (void) +{ + int i; + + for (i = 0; i <= MAX_SAMPLE; i++) + sample_ptrs[i] = -1; + for (i = 0; i < 32; i++) + sample_map[i] = -1; + for (i = 0; i < 32; i++) + patch_map[i] = -1; + + gus_poke (0, 0); /* Put silence here */ + gus_poke (1, 0); + + free_mem_ptr = 2; + free_sample = 0; + + for (i = 0; i < MAX_PATCH; i++) + patch_table[i] = -1; +} + +void +gus_delay (void) +{ + int i; + + for (i = 0; i < 7; i++) + INB (u_DRAMIO); +} + +static void +gus_poke (long addr, unsigned char data) +{ + unsigned long flags; + + DISABLE_INTR (flags); + OUTB (0x43, u_Command); + OUTB (addr & 0xff, u_DataLo); + OUTB ((addr >> 8) & 0xff, u_DataHi); + + OUTB (0x44, u_Command); + OUTB ((addr >> 16) & 0xff, u_DataHi); + OUTB (data, u_DRAMIO); + RESTORE_INTR (flags); +} + +static unsigned char +gus_peek (long addr) +{ + unsigned long flags; + unsigned char tmp; + + DISABLE_INTR (flags); + OUTB (0x43, u_Command); + OUTB (addr & 0xff, u_DataLo); + OUTB ((addr >> 8) & 0xff, u_DataHi); + + OUTB (0x44, u_Command); + OUTB ((addr >> 16) & 0xff, u_DataHi); + tmp = INB (u_DRAMIO); + RESTORE_INTR (flags); + + return tmp; +} + +void +gus_write8 (int reg, unsigned char data) +{ + unsigned long flags; + + DISABLE_INTR (flags); + + OUTB (reg, u_Command); + OUTB (data, u_DataHi); + + RESTORE_INTR (flags); +} + +unsigned char +gus_read8 (int reg) +{ + unsigned long flags; + unsigned char val; + + DISABLE_INTR (flags); + OUTB (reg | 0x80, u_Command); + val = INB (u_DataHi); + RESTORE_INTR (flags); + + return val; +} + +unsigned char +gus_look8 (int reg) +{ + unsigned long flags; + unsigned char val; + + DISABLE_INTR (flags); + OUTB (reg, u_Command); + val = INB (u_DataHi); + RESTORE_INTR (flags); + + return val; +} + +void +gus_write16 (int reg, unsigned short data) +{ + unsigned long flags; + + DISABLE_INTR (flags); + + OUTB (reg, u_Command); + + OUTB (data & 0xff, u_DataLo); + OUTB ((data >> 8) & 0xff, u_DataHi); + + RESTORE_INTR (flags); +} + +unsigned short +gus_read16 (int reg) +{ + unsigned long flags; + unsigned char hi, lo; + + DISABLE_INTR (flags); + + OUTB (reg | 0x80, u_Command); + + lo = INB (u_DataLo); + hi = INB (u_DataHi); + + RESTORE_INTR (flags); + + return ((hi << 8) & 0xff00) | lo; +} + +void +gus_write_addr (int reg, unsigned long address, int is16bit) +{ + unsigned long hold_address; + + if (is16bit) + { + /* + * Special processing required for 16 bit patches + */ + + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + + gus_write16 (reg, (unsigned short) ((address >> 7) & 0xffff)); + gus_write16 (reg + 1, (unsigned short) ((address << 9) & 0xffff)); +} + +static void +gus_select_voice (int voice) +{ + if (voice < 0 || voice > 31) + return; + + OUTB (voice, u_Voice); +} + +static void +gus_select_max_voices (int nvoices) +{ + if (nvoices < 14) + nvoices = 14; + if (nvoices > 32) + nvoices = 32; + + nr_voices = nvoices; + + gus_write8 (0x0e, (nvoices - 1) | 0xc0); +} + +static void +gus_voice_on (unsigned char mode) +{ + gus_write8 (0x00, mode & 0xfc); + gus_delay (); + gus_write8 (0x00, mode & 0xfc); +} + +static void +gus_voice_off (void) +{ + gus_write8 (0x00, gus_read8 (0x00) | 0x03); +} + +static void +gus_voice_mode (unsigned char mode) +{ + gus_write8 (0x00, (gus_read8 (0x00) & 0x03) | (mode & 0xfc)); /* Don't start or stop + * voice */ + gus_delay (); + gus_write8 (0x00, (gus_read8 (0x00) & 0x03) | (mode & 0xfc)); +} + +static void +gus_voice_freq (unsigned long freq) +{ + unsigned long divisor = freq_div_table[nr_voices - 14]; + unsigned short fc; + + fc = (unsigned short) (((freq << 9) + (divisor >> 1)) / divisor); + fc = fc << 1; + + gus_write16 (0x01, fc); +} + +static void +gus_voice_volume (unsigned short vol) +{ + gus_write8 (0x0d, 0x03); /* Stop ramp before setting volume */ + gus_write16 (0x09, vol << 4); +} + +static void +gus_voice_balance (unsigned char balance) +{ + gus_write8 (0x0c, balance); +} + +static void +gus_ramp_range (unsigned short low, unsigned short high) +{ + gus_write8 (0x07, (low >> 4) & 0xff); + gus_write8 (0x08, (high >> 4) & 0xff); +} + +static void +gus_ramp_rate (unsigned char scale, unsigned char rate) +{ + gus_write8 (0x06, ((scale & 0x03) << 6) | (rate & 0x3f)); +} + +static void +gus_rampon (unsigned char mode) +{ + gus_write8 (0x0d, mode & 0xfc); + gus_delay (); + gus_write8 (0x0d, mode & 0xfc); +} + +static void +gus_ramp_mode (unsigned char mode) +{ + gus_write8 (0x0d, (gus_read8 (0x0d) & 0x03) | (mode & 0xfc)); /* Don't start or stop + * ramping */ + gus_delay (); + gus_write8 (0x0d, (gus_read8 (0x0d) & 0x03) | (mode & 0xfc)); +} + +static void +gus_rampoff (void) +{ + gus_write8 (0x0d, 0x03); +} + +static void +gus_voice_init (int voice) +{ + unsigned long flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_volume (0); + gus_write_addr (0x0a, 0, 0); /* Set current position to 0 */ + gus_write8 (0x00, 0x03); /* Voice off */ + gus_write8 (0x0d, 0x03); /* Ramping off */ + RESTORE_INTR (flags); + + voices[voice].panning = 0; + voices[voice].mode = 0; + voices[voice].orig_freq = 20000; + voices[voice].current_freq = 20000; + voices[voice].bender = 0; + voices[voice].bender_range = 200; + voices[voice].initial_volume = 0; + voices[voice].current_volume = 0; + voices[voice].loop_irq_mode = 0; + voices[voice].loop_irq_parm = 0; + voices[voice].volume_irq_mode = 0; + voices[voice].volume_irq_parm = 0; + voices[voice].env_phase = 0; + voices[voice].main_vol = 127; + voices[voice].patch_vol = 127; + voices[voice].expression_vol = 127; +} + +static void +step_envelope (int voice) +{ + unsigned vol, prev_vol, phase; + unsigned char rate; + + if (voices[voice].mode & WAVE_SUSTAIN_ON && voices[voice].env_phase == 2) + { + gus_rampoff (); + return; /* Sustain */ + } + + if (voices[voice].env_phase >= 5) + { + /* + * Shoot the voice off + */ + + gus_voice_init (voice); + return; + } + + prev_vol = voices[voice].current_volume; + gus_voice_volume (prev_vol); + phase = ++voices[voice].env_phase; + + compute_volume (voice, voices[voice].midi_volume); + + vol = voices[voice].initial_volume * voices[voice].env_offset[phase] / 255; + rate = voices[voice].env_rate[phase]; + gus_write8 (0x06, rate); /* Ramping rate */ + + voices[voice].volume_irq_mode = VMODE_ENVELOPE; + + if (((vol - prev_vol) / 64) == 0) /* No significant volume change */ + { + step_envelope (voice); /* Continue with the next phase */ + return; + } + + if (vol > prev_vol) + { + if (vol >= (4096 - 64)) + vol = 4096 - 65; + gus_ramp_range (0, vol); + gus_rampon (0x20); /* Increasing, irq */ + } + else + { + if (vol <= 64) + vol = 65; + gus_ramp_range (vol, 4095); + gus_rampon (0x60); /* Decreasing, irq */ + } + voices[voice].current_volume = vol; +} + +static void +init_envelope (int voice) +{ + voices[voice].env_phase = -1; + voices[voice].current_volume = 64; + + step_envelope (voice); +} + +static void +start_release (int voice) +{ + if (gus_read8 (0x00) & 0x03) + return; /* Voice already stopped */ + + voices[voice].env_phase = 2; /* Will be incremented by step_envelope */ + + voices[voice].current_volume = + voices[voice].initial_volume = + gus_read16 (0x09) >> 4; /* Get current volume */ + + voices[voice].mode &= ~WAVE_SUSTAIN_ON; + gus_rampoff (); + step_envelope (voice); +} + +static void +gus_voice_fade (int voice) +{ + int instr_no = sample_map[voice], is16bits; + + if (instr_no < 0 || instr_no > MAX_SAMPLE) + { + gus_write8 (0x00, 0x03); /* Hard stop */ + return; + } + + is16bits = (samples[instr_no].mode & WAVE_16_BITS) ? 1 : 0; /* 8 or 16 bit samples */ + + if (voices[voice].mode & WAVE_ENVELOPES) + { + start_release (voice); + return; + } + + /* + * Ramp the volume down but not too quickly. + */ + if ((gus_read16 (0x09) >> 4) < 100) /* Get current volume */ + { + gus_voice_off (); + gus_rampoff (); + gus_voice_init (voice); + return; + } + + gus_ramp_range (65, 4095); + gus_ramp_rate (2, 4); + gus_rampon (0x40 | 0x20); /* Down, once, irq */ + voices[voice].volume_irq_mode = VMODE_HALT; +} + +static void +gus_reset (void) +{ + int i; + + gus_select_max_voices (24); + volume_base = 3071; + volume_scale = 4; + volume_method = VOL_METHOD_ADAGIO; + + for (i = 0; i < 32; i++) + { + gus_voice_init (i); /* Turn voice off */ + } + + INB (u_Status); /* Touch the status register */ + + gus_look8 (0x41); /* Clear any pending DMA IRQs */ + gus_look8 (0x49); /* Clear any pending sample IRQs */ + gus_read8 (0x0f); /* Clear pending IRQs */ + +} + +static void +gus_initialize (void) +{ + unsigned long flags; + unsigned char dma_image, irq_image, tmp; + + static unsigned char gus_irq_map[16] = + {0, 0, 1, 3, 0, 2, 0, 4, 0, 0, 0, 5, 6, 0, 0, 7}; + + static unsigned char gus_dma_map[8] = + {0, 1, 0, 2, 0, 3, 4, 5}; + + DISABLE_INTR (flags); + + gus_write8 (0x4c, 0); /* Reset GF1 */ + gus_delay (); + gus_delay (); + + gus_write8 (0x4c, 1); /* Release Reset */ + gus_delay (); + gus_delay (); + + /* + * Clear all interrupts + */ + + gus_write8 (0x41, 0); /* DMA control */ + gus_write8 (0x45, 0); /* Timer control */ + gus_write8 (0x49, 0); /* Sample control */ + + gus_select_max_voices (24); + + INB (u_Status); /* Touch the status register */ + + gus_look8 (0x41); /* Clear any pending DMA IRQs */ + gus_look8 (0x49); /* Clear any pending sample IRQs */ + gus_read8 (0x0f); /* Clear pending IRQs */ + + gus_reset (); /* Resets all voices */ + + gus_look8 (0x41); /* Clear any pending DMA IRQs */ + gus_look8 (0x49); /* Clear any pending sample IRQs */ + gus_read8 (0x0f); /* Clear pending IRQs */ + + gus_write8 (0x4c, 7); /* Master reset | DAC enable | IRQ enable */ + + /* + * Set up for Digital ASIC + */ + + OUTB (0x05, gus_base + 0x0f); + + mix_image |= 0x02; /* Disable line out */ + OUTB (mix_image, u_Mixer); + + OUTB (0x00, u_IRQDMAControl); + + OUTB (0x00, gus_base + 0x0f); + + /* + * Now set up the DMA and IRQ interface + * + * The GUS supports two IRQs and two DMAs. + * + * If GUS_MIDI_IRQ is defined and if it's != GUS_IRQ, separate Midi IRQ is set + * up. Otherwise the same IRQ is shared by the both devices. + * + * Just one DMA channel is used. This prevents simultaneous ADC and DAC. + * Adding this support requires significant changes to the dmabuf.c, dsp.c + * and audio.c also. + */ + + irq_image = 0; + tmp = gus_irq_map[gus_irq]; + if (!tmp) + printk ("Warning! GUS IRQ not selected\n"); + irq_image |= tmp; + + if (GUS_MIDI_IRQ != gus_irq) + { /* The midi irq was defined and != wave irq */ + tmp = gus_irq_map[GUS_MIDI_IRQ]; + tmp <<= 3; + + if (!tmp) + printk ("Warning! GUS Midi IRQ not selected\n"); + else + gus_set_midi_irq (GUS_MIDI_IRQ); + + irq_image |= tmp; + } + else + irq_image |= 0x40; /* Combine IRQ1 (GF1) and IRQ2 (Midi) */ + + dma_image = 0x40; /* Combine DMA1 (DRAM) and IRQ2 (ADC) */ + tmp = gus_dma_map[gus_dma]; + if (!tmp) + printk ("Warning! GUS DMA not selected\n"); + dma_image |= tmp; + + /* + * For some reason the IRQ and DMA addresses must be written twice + */ + + /* Doing it first time */ + + OUTB (mix_image, u_Mixer); /* Select DMA control */ + OUTB (dma_image, u_IRQDMAControl); /* Set DMA address */ + + OUTB (mix_image | 0x40, u_Mixer); /* Select IRQ control */ + OUTB (irq_image, u_IRQDMAControl); /* Set IRQ address */ + + /* Doing it second time */ + + OUTB (mix_image, u_Mixer); /* Select DMA control */ + OUTB (dma_image, u_IRQDMAControl); /* Set DMA address */ + + OUTB (mix_image | 0x40, u_Mixer); /* Select IRQ control */ + OUTB (irq_image, u_IRQDMAControl); /* Set IRQ address */ + + gus_select_voice (0); /* This disables writes to IRQ/DMA reg */ + + mix_image &= ~0x02; /* Enable line out */ + mix_image |= 0x08; /* Enable IRQ */ + OUTB (mix_image, u_Mixer); /* Turn mixer channels on */ + + gus_select_voice (0); /* This disables writes to IRQ/DMA reg */ + + gusintr (0); /* Serve pending interrupts */ + RESTORE_INTR (flags); +} + +int +gus_wave_detect (int baseaddr) +{ + gus_base = baseaddr; + + gus_write8 (0x4c, 0); /* Reset GF1 */ + gus_delay (); + gus_delay (); + + gus_write8 (0x4c, 1); /* Release Reset */ + gus_delay (); + gus_delay (); + + gus_poke (0x000, 0xaa); + gus_poke (0x100, 0x55); + + if (gus_peek (0x000) != 0xaa) + return 0; + if (gus_peek (0x100) != 0x55) + return 0; + + gus_mem_size = 0x40000; /* 256k */ + gus_poke (0x40000, 0xaa); + if (gus_peek (0x40000) != 0xaa) + return 1; + + gus_mem_size = 0x80000; /* 512k */ + gus_poke (0x80000, 0xaa); + if (gus_peek (0x80000) != 0xaa) + return 1; + + gus_mem_size = 0xc0000; /* 768k */ + gus_poke (0xc0000, 0xaa); + if (gus_peek (0xc0000) != 0xaa) + return 1; + + gus_mem_size = 0x100000; /* 1M */ + + return 1; +} + +static int +guswave_ioctl (int dev, + unsigned int cmd, unsigned int arg) +{ + + switch (cmd) + { + case SNDCTL_SYNTH_INFO: + gus_info.nr_voices = nr_voices; + IOCTL_TO_USER ((char *) arg, 0, &gus_info, sizeof (gus_info)); + return 0; + break; + + case SNDCTL_SEQ_RESETSAMPLES: + reset_sample_memory (); + return 0; + break; + + case SNDCTL_SEQ_PERCMODE: + return 0; + break; + + case SNDCTL_SYNTH_MEMAVL: + return gus_mem_size - free_mem_ptr - 32; + + default: + return RET_ERROR (EINVAL); + } +} + +static int +guswave_set_instr (int dev, int voice, int instr_no) +{ + int sample_no; + + if (instr_no < 0 || instr_no > MAX_PATCH) + return RET_ERROR (EINVAL); + + if (voice < 0 || voice > 31) + return RET_ERROR (EINVAL); + + sample_no = patch_table[instr_no]; + patch_map[voice] = -1; + + if (sample_no < 0) + { + printk ("GUS: Undefined patch %d for voice %d\n", instr_no, voice); + return RET_ERROR (EINVAL);/* Patch not defined */ + } + + if (sample_ptrs[sample_no] == -1) /* Sample not loaded */ + { + printk ("GUS: Sample #%d not loaded for patch %d (voice %d)\n", sample_no, instr_no, voice); + return RET_ERROR (EINVAL); + } + + sample_map[voice] = sample_no; + patch_map[voice] = instr_no; + return 0; +} + +static int +guswave_kill_note (int dev, int voice, int velocity) +{ + unsigned long flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_fade (voice); + RESTORE_INTR (flags); + + return 0; +} + +static void +guswave_aftertouch (int dev, int voice, int pressure) +{ + short lo_limit, hi_limit; + unsigned long flags; + + return; /* Currently disabled */ + + if (voice < 0 || voice > 31) + return; + + if (voices[voice].mode & WAVE_ENVELOPES && voices[voice].env_phase != 2) + return; /* Don't mix with envelopes */ + + if (pressure < 32) + { + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_rampoff (); + compute_and_set_volume (voice, 255, 0); /* Back to original volume */ + RESTORE_INTR (flags); + return; + } + + hi_limit = voices[voice].current_volume; + lo_limit = hi_limit * 99 / 100; + if (lo_limit < 65) + lo_limit = 65; + + DISABLE_INTR (flags); + gus_select_voice (voice); + if (hi_limit > (4095 - 65)) + { + hi_limit = 4095 - 65; + gus_voice_volume (hi_limit); + } + gus_ramp_range (lo_limit, hi_limit); + gus_ramp_rate (3, 8); + gus_rampon (0x58); /* Bidirectional, Down, Loop */ + RESTORE_INTR (flags); +} + +static void +guswave_panning (int dev, int voice, int value) +{ + if (voice >= 0 || voice < 32) + voices[voice].panning = value; +} + +static void +compute_volume (int voice, int volume) +{ + if (volume < 128) + { + voices[voice].midi_volume = volume; + + switch (volume_method) + { + case VOL_METHOD_ADAGIO: + voices[voice].initial_volume = + gus_adagio_vol (volume, voices[voice].main_vol, + voices[voice].expression_vol, + voices[voice].patch_vol); + break; + + default: + voices[voice].initial_volume = volume_base + (volume * volume_scale); + } + } + + if (voices[voice].initial_volume > 4095) + voices[voice].initial_volume = 4095; +} + +static void +compute_and_set_volume (int voice, int volume, int ramp_time) +{ + int current, target, rate; + + compute_volume (voice, volume); + voices[voice].current_volume = voices[voice].initial_volume; + + current = gus_read16 (0x09) >> 4; + target = voices[voice].initial_volume; + + if (ramp_time == INSTANT_RAMP) + { + gus_rampoff (); + gus_voice_volume (target); + return; + } + + if (ramp_time == FAST_RAMP) + rate = 63; + else + rate = 16; + gus_ramp_rate (0, rate); + + if ((target - current) / 64 == 0) /* Too close */ + { + gus_rampoff (); + gus_voice_volume (target); + return; + } + + if (target > current) + { + if (target > (4095 - 65)) + target = 4095 - 65; + gus_ramp_range (current, target); + gus_rampon (0x00); /* Ramp up, once, no irq */ + } + else + { + if (target < 65) + target = 65; + + gus_ramp_range (target, current); + gus_rampon (0x40); /* Ramp down, once, no irq */ + } +} + +static void +dynamic_volume_change (int voice) +{ + unsigned char status; + unsigned long flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + status = gus_read8 (0x00); /* Voice status */ + RESTORE_INTR (flags); + + if (status & 0x03) + return; /* Voice not started */ + + if (!(voices[voice].mode & WAVE_ENVELOPES)) + { + compute_and_set_volume (voice, voices[voice].midi_volume, 1); + return; + } + + /* + * Voice is running and has envelopes. + */ + + DISABLE_INTR (flags); + gus_select_voice (voice); + status = gus_read8 (0x0d); /* Ramping status */ + RESTORE_INTR (flags); + + if (status & 0x03) /* Sustain phase? */ + { + compute_and_set_volume (voice, voices[voice].midi_volume, 1); + return; + } + + if (voices[voice].env_phase < 0) + return; + + compute_volume (voice, voices[voice].midi_volume); + +#if 0 /* Is this really required */ + voices[voice].current_volume = + gus_read16 (0x09) >> 4; /* Get current volume */ + + voices[voice].env_phase--; + step_envelope (voice); +#endif +} + +static void +guswave_controller (int dev, int voice, int ctrl_num, int value) +{ + unsigned long flags; + unsigned long freq; + + if (voice < 0 || voice > 31) + return; + + switch (ctrl_num) + { + case CTRL_PITCH_BENDER: + voices[voice].bender = value; + freq = compute_finetune (voices[voice].orig_freq, value, voices[voice].bender_range); + voices[voice].current_freq = freq; + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_freq (freq); + RESTORE_INTR (flags); + break; + + case CTRL_PITCH_BENDER_RANGE: + voices[voice].bender_range = value; + break; + + case CTRL_EXPRESSION: + volume_method = VOL_METHOD_ADAGIO; + voices[voice].expression_vol = value; + dynamic_volume_change (voice); + break; + + case CTRL_MAIN_VOLUME: + volume_method = VOL_METHOD_ADAGIO; + voices[voice].main_vol = value; + dynamic_volume_change (voice); + break; + + default: /* Ignore */ + break; + } +} + +static int +guswave_start_note (int dev, int voice, int note_num, int volume) +{ + int sample, best_sample, best_delta, delta_freq; + int is16bits, samplep, patch, pan; + unsigned long note_freq, base_note, freq, flags; + unsigned char mode = 0; + + if (voice < 0 || voice > 31) + { + printk ("GUS: Invalid voice\n"); + return RET_ERROR (EINVAL); + } + + if (note_num == 255) + { + if (voices[voice].mode & WAVE_ENVELOPES) + { + voices[voice].midi_volume = volume; + dynamic_volume_change (voice); + return 0; + } + + compute_and_set_volume (voice, volume, 1); + return 0; + } + + if ((patch = patch_map[voice]) == -1) + { + return RET_ERROR (EINVAL); + } + + if ((samplep = patch_table[patch]) == -1) + { + return RET_ERROR (EINVAL); + } + + note_freq = note_to_freq (note_num); + + /* + * Find a sample within a patch so that the note_freq is between low_note + * and high_note. + */ + sample = -1; + + best_sample = samplep; + best_delta = 1000000; + while (samplep >= 0 && sample == -1) + { + delta_freq = note_freq - samples[samplep].base_note; + if (delta_freq < 0) + delta_freq = -delta_freq; + if (delta_freq < best_delta) + { + best_sample = samplep; + best_delta = delta_freq; + } + if (samples[samplep].low_note <= note_freq && note_freq <= samples[samplep].high_note) + sample = samplep; + else + samplep = samples[samplep].key; /* Follow link */ + } + if (sample == -1) + sample = best_sample; + + if (sample == -1) + { + printk ("GUS: Patch %d not defined for note %d\n", patch, note_num); + return 0; /* Should play default patch ??? */ + } + + is16bits = (samples[sample].mode & WAVE_16_BITS) ? 1 : 0; /* 8 or 16 bit samples */ + voices[voice].mode = samples[sample].mode; + voices[voice].patch_vol = samples[sample].volume; + + if (voices[voice].mode & WAVE_ENVELOPES) + { + int i; + + for (i = 0; i < 6; i++) + { + voices[voice].env_rate[i] = samples[sample].env_rate[i]; + voices[voice].env_offset[i] = samples[sample].env_offset[i]; + } + } + + sample_map[voice] = sample; + + base_note = samples[sample].base_note / 100; /* To avoid overflows */ + note_freq /= 100; + + freq = samples[sample].base_freq * note_freq / base_note; + + voices[voice].orig_freq = freq; + + /* + * Since the pitch bender may have been set before playing the note, we + * have to calculate the bending now. + */ + + freq = compute_finetune (voices[voice].orig_freq, voices[voice].bender, voices[voice].bender_range); + voices[voice].current_freq = freq; + + pan = (samples[sample].panning + voices[voice].panning) / 32; + pan += 7; + if (pan < 0) + pan = 0; + if (pan > 15) + pan = 15; + + if (samples[sample].mode & WAVE_16_BITS) + { + mode |= 0x04; /* 16 bits */ + if ((sample_ptrs[sample] >> 18) != + ((sample_ptrs[sample] + samples[sample].len) >> 18)) + printk ("GUS: Sample address error\n"); + } + + /************************************************************************* + * CAUTION! Interrupts disabled. Don't return before enabling + *************************************************************************/ + + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_off (); /* It may still be running */ + gus_rampoff (); + if (voices[voice].mode & WAVE_ENVELOPES) + { + compute_volume (voice, volume); + init_envelope (voice); + } + else + compute_and_set_volume (voice, volume, 0); + + if (samples[sample].mode & WAVE_LOOP_BACK) + gus_write_addr (0x0a, sample_ptrs[sample] + samples[sample].len, is16bits); /* Sample start=end */ + else + gus_write_addr (0x0a, sample_ptrs[sample], is16bits); /* Sample start=begin */ + + if (samples[sample].mode & WAVE_LOOPING) + { + mode |= 0x08; /* Looping on */ + + if (samples[sample].mode & WAVE_BIDIR_LOOP) + mode |= 0x10; /* Bidirectional looping on */ + + if (samples[sample].mode & WAVE_LOOP_BACK) + { + gus_write_addr (0x0a, /* Put the current location = loop_end */ + sample_ptrs[sample] + samples[sample].loop_end, is16bits); + mode |= 0x40; /* Loop backwards */ + } + + gus_write_addr (0x02, sample_ptrs[sample] + samples[sample].loop_start, is16bits); /* Loop start location */ + gus_write_addr (0x04, sample_ptrs[sample] + samples[sample].loop_end, is16bits); /* Loop end location */ + } + else + { + mode |= 0x20; /* Loop irq at the end */ + voices[voice].loop_irq_mode = LMODE_FINISH; /* Ramp it down at the + * end */ + voices[voice].loop_irq_parm = 1; + gus_write_addr (0x02, sample_ptrs[sample], is16bits); /* Loop start location */ + gus_write_addr (0x04, sample_ptrs[sample] + samples[sample].len, is16bits); /* Loop end location */ + } + gus_voice_freq (freq); + gus_voice_balance (pan); + gus_voice_on (mode); + RESTORE_INTR (flags); + + return 0; +} + +static void +guswave_reset (int dev) +{ + int i; + + for (i = 0; i < 32; i++) + gus_voice_init (i); +} + +static int +guswave_open (int dev, int mode) +{ + int err; + + if (gus_busy) + return RET_ERROR (EBUSY); + + if ((err = DMAbuf_open_dma (gus_devnum))) + return err; + + gus_busy = 1; + active_device = GUS_DEV_WAVE; + + gus_reset (); + + return 0; +} + +static void +guswave_close (int dev) +{ + gus_busy = 0; + active_device = 0; + gus_reset (); + + DMAbuf_close_dma (gus_devnum); +} + +static int +guswave_load_patch (int dev, int format, snd_rw_buf * addr, + int offs, int count, int pmgr_flag) +{ + struct patch_info patch; + int instr; + + unsigned long blk_size, blk_end, left, src_offs, target; + + if (format != GUS_PATCH) + { + printk ("GUS Error: Invalid patch format (key) 0x%04x\n", format); +#ifndef CPU_I486 + if (format == OBSOLETE_GUS_PATCH) + printk ("GUS Error: obsolete patch format\n"); +#endif + return RET_ERROR (EINVAL); + } + + if (count < sizeof (patch)) + { + printk ("GUS Error: Patch header too short\n"); + return RET_ERROR (EINVAL); + } + + count -= sizeof (patch); + + if (free_sample >= MAX_SAMPLE) + { + printk ("GUS: Sample table full\n"); + return RET_ERROR (ENOSPC); + } + + /* + * Copy the header from user space but ignore the first bytes which have + * been transferred already. + */ + + COPY_FROM_USER (&((char *) &patch)[offs], addr, offs, sizeof (patch) - offs); + + instr = patch.instr_no; + + if (instr < 0 || instr > MAX_PATCH) + { + printk ("GUS: Invalid patch number %d\n", instr); + return RET_ERROR (EINVAL); + } + + if (count < patch.len) + { + printk ("GUS Warning: Patch record too short (%d<%d)\n", + count, patch.len); + patch.len = count; + } + + if (patch.len <= 0 || patch.len > gus_mem_size) + { + printk ("GUS: Invalid sample length %d\n", patch.len); + return RET_ERROR (EINVAL); + } + + if (patch.mode & WAVE_LOOPING) + { + if (patch.loop_start < 0 || patch.loop_start >= patch.len) + { + printk ("GUS: Invalid loop start\n"); + return RET_ERROR (EINVAL); + } + + if (patch.loop_end < patch.loop_start || patch.loop_end > patch.len) + { + printk ("GUS: Invalid loop end\n"); + return RET_ERROR (EINVAL); + } + } + + free_mem_ptr = (free_mem_ptr + 31) & ~31; /* Alignment 32 bytes */ + +#define GUS_BANK_SIZE (256*1024) + + if (patch.mode & WAVE_16_BITS) + { + /* + * 16 bit samples must fit one 256k bank. + */ + if (patch.len >= GUS_BANK_SIZE) + { + printk ("GUS: Sample (16 bit) too long %d\n", patch.len); + return RET_ERROR (ENOSPC); + } + + if ((free_mem_ptr / GUS_BANK_SIZE) != + ((free_mem_ptr + patch.len) / GUS_BANK_SIZE)) + { + unsigned long tmp_mem = /* Align to 256K*N */ + ((free_mem_ptr / GUS_BANK_SIZE) + 1) * GUS_BANK_SIZE; + + if ((tmp_mem + patch.len) > gus_mem_size) + return RET_ERROR (ENOSPC); + + free_mem_ptr = tmp_mem; /* This leaves unusable memory */ + } + } + + if ((free_mem_ptr + patch.len) > gus_mem_size) + return RET_ERROR (ENOSPC); + + sample_ptrs[free_sample] = free_mem_ptr; + + /* Tremolo is not possible with envelopes */ + + if (patch.mode & WAVE_ENVELOPES) + patch.mode &= ~WAVE_TREMOLO; + + memcpy ((char *) &samples[free_sample], &patch, sizeof (patch)); + + /* + * Link this_one sample to the list of samples for patch 'instr'. + */ + + samples[free_sample].key = patch_table[instr]; + patch_table[instr] = free_sample; + + /* + * Use DMA to transfer the wave data to the DRAM + */ + + left = patch.len; + src_offs = 0; + target = free_mem_ptr; + + while (left) /* Not all moved */ + { + blk_size = sound_buffsizes[gus_devnum]; + if (blk_size > left) + blk_size = left; + + /* + * DMA cannot cross 256k bank boundaries. Check for that. + */ + blk_end = target + blk_size; + + if ((target >> 18) != (blk_end >> 18)) + { /* Have to split the block */ + + blk_end &= ~(256 * 1024 - 1); + blk_size = blk_end - target; + } + +#ifdef GUS_NO_DMA + /* + * For some reason the DMA is not possible. We have to use PIO. + */ + { + long i; + unsigned char data; + + for (i = 0; i < blk_size; i++) + { + GET_BYTE_FROM_USER (data, addr, sizeof (patch) + i); + gus_poke (target + i, data); + } + } +#else /* GUS_NO_DMA */ + { + unsigned long address, hold_address; + unsigned char dma_command; + + /* + * OK, move now. First in and then out. + */ + + COPY_FROM_USER (snd_raw_buf[gus_devnum][0], + addr, sizeof (patch) + src_offs, + blk_size); + + gus_write8 (0x41, 0); /* Disable GF1 DMA */ + DMAbuf_start_dma (gus_devnum, snd_raw_buf_phys[gus_devnum][0], + blk_size, DMA_MODE_WRITE); + + /* + * Set the DRAM address for the wave data + */ + + address = target; + + if (sound_dsp_dmachan[gus_devnum] > 3) + { + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + + gus_write16 (0x42, (address >> 4) & 0xffff); /* DRAM DMA address */ + + /* + * Start the DMA transfer + */ + + dma_command = 0x21; /* IRQ enable, DMA start */ + if (patch.mode & WAVE_UNSIGNED) + dma_command |= 0x80; /* Invert MSB */ + if (patch.mode & WAVE_16_BITS) + dma_command |= 0x40; /* 16 bit _DATA_ */ + if (sound_dsp_dmachan[gus_devnum] > 3) + dma_command |= 0x04; /* 16 bit DMA channel */ + + gus_write8 (0x41, dma_command); /* Let's go luteet (=bugs) */ + + /* + * Sleep here until the DRAM DMA done interrupt is served + */ + active_device = GUS_DEV_WAVE; + + INTERRUPTIBLE_SLEEP_ON (dram_sleeper, dram_sleep_flag); + } +#endif /* GUS_NO_DMA */ + + /* + * Now the next part + */ + + left -= blk_size; + src_offs += blk_size; + target += blk_size; + + gus_write8 (0x41, 0); /* Stop DMA */ + } + + free_mem_ptr += patch.len; + + if (!pmgr_flag) + pmgr_inform (dev, PM_E_PATCH_LOADED, instr, free_sample, 0, 0); + free_sample++; + return 0; +} + +static void +guswave_hw_control (int dev, unsigned char *event) +{ + int voice, cmd; + unsigned short p1, p2; + unsigned long plong, flags; + + cmd = event[2]; + voice = event[3]; + p1 = *(unsigned short *) &event[4]; + p2 = *(unsigned short *) &event[6]; + plong = *(unsigned long *) &event[4]; + + switch (cmd) + { + + case _GUS_NUMVOICES: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_select_max_voices (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICESAMPLE: + guswave_set_instr (dev, voice, p1); + break; + + case _GUS_VOICEON: + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* Disable intr */ + gus_voice_on (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEOFF: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_off (); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEFADE: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_fade (voice); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEMODE: + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* Disable intr */ + gus_voice_mode (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEBALA: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_balance (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEFREQ: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_freq (plong); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEVOL: + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_voice_volume (p1); + RESTORE_INTR (flags); + break; + + case _GUS_VOICEVOL2: /* Just update the voice value */ + voices[voice].initial_volume = + voices[voice].current_volume = p1; + break; + + case _GUS_RAMPRANGE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NO-NO */ + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_ramp_range (p1, p2); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPRATE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NO-NO */ + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_ramp_rate (p1, p2); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPMODE: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NO-NO */ + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* Disable intr */ + gus_ramp_mode (p1); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPON: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NO-NO */ + DISABLE_INTR (flags); + gus_select_voice (voice); + p1 &= ~0x20; /* Disable intr */ + gus_rampon (p1); + RESTORE_INTR (flags); + break; + + case _GUS_RAMPOFF: + if (voices[voice].mode & WAVE_ENVELOPES) + break; /* NO-NO */ + DISABLE_INTR (flags); + gus_select_voice (voice); + gus_rampoff (); + RESTORE_INTR (flags); + break; + + case _GUS_VOLUME_SCALE: + volume_base = p1; + volume_scale = p2; + break; + + default:; + } +} + +static int +gus_sampling_set_speed (int speed) +{ + if (speed <= 0) + return gus_sampling_speed; + + if (speed > 44100) + speed = 44100; + + gus_sampling_speed = speed; + return speed; +} + +static int +gus_sampling_set_channels (int channels) +{ + if (!channels) + return gus_sampling_channels; + if (channels > 2) + channels = 2; + if (channels < 1) + channels = 1; + gus_sampling_channels = channels; + return channels; +} + +static int +gus_sampling_set_bits (int bits) +{ + if (!bits) + return gus_sampling_bits; + + if (bits != 8 && bits != 16) + bits = 8; + + gus_sampling_bits = bits; + return bits; +} + +static int +gus_sampling_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (local) + return gus_sampling_set_speed (arg); + return IOCTL_OUT (arg, gus_sampling_set_speed (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_RATE: + if (local) + return gus_sampling_speed; + return IOCTL_OUT (arg, gus_sampling_speed); + break; + + case SNDCTL_DSP_STEREO: + if (local) + return gus_sampling_set_channels (arg + 1) - 1; + return IOCTL_OUT (arg, gus_sampling_set_channels (IOCTL_IN (arg) + 1) - 1); + break; + + case SOUND_PCM_WRITE_CHANNELS: + return IOCTL_OUT (arg, gus_sampling_set_channels (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_CHANNELS: + if (local) + return gus_sampling_channels; + return IOCTL_OUT (arg, gus_sampling_channels); + break; + + case SNDCTL_DSP_SAMPLESIZE: + if (local) + return gus_sampling_set_bits (arg); + return IOCTL_OUT (arg, gus_sampling_set_bits (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_BITS: + if (local) + return gus_sampling_bits; + return IOCTL_OUT (arg, gus_sampling_bits); + + case SOUND_PCM_WRITE_FILTER: /* NOT YET IMPLEMENTED */ + return IOCTL_OUT (arg, RET_ERROR (EINVAL)); + break; + + case SOUND_PCM_READ_FILTER: + return IOCTL_OUT (arg, RET_ERROR (EINVAL)); + break; + + default: + return RET_ERROR (EINVAL); + } + return RET_ERROR (EINVAL); +} + +static void +gus_sampling_reset (int dev) +{ +} + +static int +gus_sampling_open (int dev, int mode) +{ +#ifdef GUS_NO_DMA + printk ("GUS: DMA mode not enabled. Device not supported\n"); + return RET_ERROR (ENXIO); +#endif + + if (gus_busy) + return RET_ERROR (EBUSY); + + gus_busy = 1; + active_device = 0; + + gus_reset (); + reset_sample_memory (); + gus_select_max_voices (14); + + gus_sampling_set_bits (8); + gus_sampling_set_channels (1); + gus_sampling_set_speed (DSP_DEFAULT_SPEED); + pcm_active = 0; + + return 0; +} + +static void +gus_sampling_close (int dev) +{ + gus_reset (); + gus_busy = 0; + active_device = 0; +} + +static void +play_next_pcm_block (void) +{ + unsigned long flags; + int speed = gus_sampling_speed; + int this_one, is16bits, chn; + unsigned long dram_loc; + unsigned char mode[2], ramp_mode[2]; + + if (!pcm_qlen) + return; + + this_one = pcm_head; + + for (chn = 0; chn < gus_sampling_channels; chn++) + { + mode[chn] = 0x00; + ramp_mode[chn] = 0x03; /* Ramping and rollover off */ + + if (chn == 0) + { + mode[chn] |= 0x20; /* Loop irq */ + voices[chn].loop_irq_mode = LMODE_PCM; + } + + if (gus_sampling_bits != 8) + { + is16bits = 1; + mode[chn] |= 0x04; /* 16 bit data */ + } + else + is16bits = 0; + + dram_loc = this_one * pcm_bsize; + dram_loc += chn * pcm_banksize; + + if (this_one == (pcm_nblk - 1)) /* Last of the DRAM buffers */ + { + mode[chn] |= 0x08; /* Enable loop */ + ramp_mode[chn] = 0x03;/* Disable rollover */ + } + else + { + if (chn == 0) + ramp_mode[chn] = 0x04; /* Enable rollover bit */ + } + + DISABLE_INTR (flags); + gus_select_voice (chn); + gus_voice_freq (speed); + + if (gus_sampling_channels == 1) + gus_voice_balance (7); /* mono */ + else if (chn == 0) + gus_voice_balance (0); /* left */ + else + gus_voice_balance (15); /* right */ + + if (!pcm_active) /* Voice not started yet */ + { + /* + * The playback was not started yet (or there has been a pause). + * Start the voice (again) and ask for a rollover irq at the end of + * this_one block. If this_one one is last of the buffers, use just + * the normal loop with irq. + */ + + gus_voice_off (); /* It could already be running */ + gus_rampoff (); + gus_voice_volume (4000); + gus_ramp_range (65, 4030); + + gus_write_addr (0x0a, dram_loc, is16bits); /* Starting position */ + gus_write_addr (0x02, chn * pcm_banksize, is16bits); /* Loop start location */ + + if (chn != 0) + gus_write_addr (0x04, pcm_banksize + (pcm_bsize * pcm_nblk), + is16bits); /* Loop end location */ + } + + if (chn == 0) + gus_write_addr (0x04, dram_loc + pcm_datasize[this_one], is16bits); /* Loop end location */ + else + mode[chn] |= 0x08; /* Enable loop */ + + if (pcm_datasize[this_one] != pcm_bsize) + { + /* Incomplete block. Possibly the last one. */ + if (chn == 0) + { + mode[chn] &= ~0x08; /* Disable loop */ + mode[chn] |= 0x20;/* Enable loop IRQ */ + voices[0].loop_irq_mode = LMODE_PCM_STOP; + ramp_mode[chn] = 0x03; /* No rollover bit */ + } + else + { + gus_write_addr (0x04, dram_loc + pcm_datasize[this_one], is16bits); /* Loop end location */ + mode[chn] &= ~0x08; /* Disable loop */ + } + } + + RESTORE_INTR (flags); + } + + for (chn = 0; chn < gus_sampling_channels; chn++) + { + DISABLE_INTR (flags); + gus_select_voice (chn); + gus_write8 (0x0d, ramp_mode[chn]); + gus_voice_on (mode[chn]); + RESTORE_INTR (flags); + } + + pcm_active = 1; +} + +static void +gus_transfer_output_block (int dev, unsigned long buf, + int total_count, int intrflag, int chn) +{ + /* + * This routine transfers one block of audio data to the DRAM. In mono mode + * it's called just once. When in stereo mode, this_one routine is called + * once for both channels. + * + * The left/mono channel data is transferred to the beginning of dram and the + * right data to the area pointed by gus_page_size. + */ + + int this_one, count; + unsigned long flags; + unsigned char dma_command; + unsigned long address, hold_address; + + DISABLE_INTR (flags); + + count = total_count / gus_sampling_channels; + + if (chn == 0) + { + if (pcm_qlen >= pcm_nblk) + printk ("GUS Warning: PCM buffers out of sync\n"); + + this_one = pcm_current_block = pcm_tail; + pcm_qlen++; + pcm_tail = (pcm_tail + 1) % pcm_nblk; + pcm_datasize[this_one] = count; + } + else + this_one = pcm_current_block; + + gus_write8 (0x41, 0); /* Disable GF1 DMA */ + DMAbuf_start_dma (dev, buf + (chn * count), count, DMA_MODE_WRITE); + + address = this_one * pcm_bsize; + address += chn * pcm_banksize; + + if (sound_dsp_dmachan[dev] > 3) + { + hold_address = address; + address = address >> 1; + address &= 0x0001ffffL; + address |= (hold_address & 0x000c0000L); + } + + gus_write16 (0x42, (address >> 4) & 0xffff); /* DRAM DMA address */ + + dma_command = 0x21; /* IRQ enable, DMA start */ + + if (gus_sampling_bits != 8) + dma_command |= 0x40; /* 16 bit _DATA_ */ + else + dma_command |= 0x80; /* Invert MSB */ + + if (sound_dsp_dmachan[dev] > 3) + dma_command |= 0x04; /* 16 bit DMA channel */ + + gus_write8 (0x41, dma_command); /* Kick on */ + + if (chn == (gus_sampling_channels - 1)) /* Last channel */ + { + /* Last (right or mono) channel data */ + active_device = GUS_DEV_PCM_DONE; + if (!pcm_active && (pcm_qlen > 2 || count < pcm_bsize)) + { + play_next_pcm_block (); + } + } + else /* Left channel data. The right channel is + * transferred after DMA interrupt */ + active_device = GUS_DEV_PCM_CONTINUE; + + RESTORE_INTR (flags); +} + +static void +gus_sampling_output_block (int dev, unsigned long buf, int total_count, int intrflag) +{ + pcm_current_buf = buf; + pcm_current_count = total_count; + pcm_current_intrflag = intrflag; + pcm_current_dev = dev; + gus_transfer_output_block (dev, buf, total_count, intrflag, 0); +} + +static void +gus_sampling_start_input (int dev, unsigned long buf, int count, int intrflag) +{ + unsigned long flags; + unsigned char mode; + + DISABLE_INTR (flags); + + DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); + + mode = 0xa0; /* DMA IRQ enable, invert MSB */ + + if (sound_dsp_dmachan[dev] > 3) + mode |= 0x04; /* 16 bit DMA channel */ + if (gus_sampling_channels > 1) + mode |= 0x02; /* Stereo */ + mode |= 0x01; /* DMA enable */ + + gus_write8 (0x49, mode); + + RESTORE_INTR (flags); +} + +static int +gus_sampling_prepare_for_input (int dev, int bsize, int bcount) +{ + unsigned int rate; + + rate = (9878400 / (gus_sampling_speed + 2)) / 16; + + gus_write8 (0x48, rate & 0xff); /* Set sampling frequency */ + + if (gus_sampling_bits != 8) + { + printk ("GUS Error: 16 bit recording not supported\n"); + return RET_ERROR (EINVAL); + } + + return 0; +} + +static int +gus_sampling_prepare_for_output (int dev, int bsize, int bcount) +{ + int i; + + long mem_ptr, mem_size; + + mem_ptr = 0; + mem_size = gus_mem_size / gus_sampling_channels; + + if (mem_size > (256 * 1024)) + mem_size = 256 * 1024; + + pcm_bsize = bsize / gus_sampling_channels; + pcm_head = pcm_tail = pcm_qlen = 0; + + pcm_nblk = MAX_PCM_BUFFERS; + if ((pcm_bsize * pcm_nblk) > mem_size) + pcm_nblk = mem_size / pcm_bsize; + + for (i = 0; i < pcm_nblk; i++) + pcm_datasize[i] = 0; + + pcm_banksize = pcm_nblk * pcm_bsize; + + if (gus_sampling_bits != 8 && pcm_banksize == (256 * 1024)) + pcm_nblk--; + + return 0; +} + +static int +gus_has_output_drained (int dev) +{ + return !pcm_qlen; +} + +static void +gus_copy_from_user (int dev, char *localbuf, int localoffs, + snd_rw_buf * userbuf, int useroffs, int len) +{ + if (gus_sampling_channels == 1) + COPY_FROM_USER (&localbuf[localoffs], userbuf, useroffs, len); + else if (gus_sampling_bits == 8) + { + int in_left = useroffs; + int in_right = useroffs + 1; + char *out_left, *out_right; + int i; + + len /= 2; + localoffs /= 2; + out_left = &localbuf[localoffs]; + out_right = out_left + pcm_bsize; + + for (i = 0; i < len; i++) + { + GET_BYTE_FROM_USER (*out_left++, userbuf, in_left); + in_left += 2; + GET_BYTE_FROM_USER (*out_right++, userbuf, in_right); + in_right += 2; + } + } + else + { + int in_left = useroffs; + int in_right = useroffs + 1; + short *out_left, *out_right; + int i; + + len /= 4; + localoffs /= 4; + + out_left = (short *) &localbuf[localoffs]; + out_right = out_left + (pcm_bsize / 2); + + for (i = 0; i < len; i++) + { + GET_SHORT_FROM_USER (*out_left++, (short *) userbuf, in_left); + in_left += 2; + GET_SHORT_FROM_USER (*out_right++, (short *) userbuf, in_right); + in_right += 2; + } + } +} + +static struct audio_operations gus_sampling_operations = +{ + "Gravis UltraSound", + gus_sampling_open, /* */ + gus_sampling_close, /* */ + gus_sampling_output_block, /* */ + gus_sampling_start_input, /* */ + gus_sampling_ioctl, /* */ + gus_sampling_prepare_for_input, /* */ + gus_sampling_prepare_for_output, /* */ + gus_sampling_reset, /* */ + gus_sampling_reset, /* halt_xfer */ + gus_has_output_drained, + gus_copy_from_user +}; + +static int +guswave_patchmgr (int dev, struct patmgr_info *rec) +{ + int i, n; + + switch (rec->command) + { + case PM_GET_DEVTYPE: + rec->parm1 = PMTYPE_WAVE; + return 0; + break; + + case PM_GET_NRPGM: + rec->parm1 = MAX_PATCH; + return 0; + break; + + case PM_GET_PGMMAP: + rec->parm1 = MAX_PATCH; + + for (i = 0; i < MAX_PATCH; i++) + { + int ptr = patch_table[i]; + + rec->data.data8[i] = 0; + + while (ptr >= 0 && ptr < free_sample) + { + rec->data.data8[i]++; + ptr = samples[ptr].key; /* Follow link */ + } + } + return 0; + break; + + case PM_GET_PGM_PATCHES: + { + int ptr = patch_table[rec->parm1]; + + n = 0; + + while (ptr >= 0 && ptr < free_sample) + { + rec->data.data32[n++] = ptr; + ptr = samples[ptr].key; /* Follow link */ + } + } + rec->parm1 = n; + return 0; + break; + + case PM_GET_PATCH: + { + int ptr = rec->parm1; + struct patch_info *pat; + + if (ptr < 0 || ptr >= free_sample) + return RET_ERROR (EINVAL); + + memcpy (rec->data.data8, (char *) &samples[ptr], + sizeof (struct patch_info)); + + pat = (struct patch_info *) rec->data.data8; + + pat->key = GUS_PATCH; /* Restore patch type */ + rec->parm1 = sample_ptrs[ptr]; /* DRAM address */ + rec->parm2 = sizeof (struct patch_info); + } + return 0; + break; + + case PM_SET_PATCH: + { + int ptr = rec->parm1; + struct patch_info *pat; + + if (ptr < 0 || ptr >= free_sample) + return RET_ERROR (EINVAL); + + pat = (struct patch_info *) rec->data.data8; + + if (pat->len > samples[ptr].len) /* Cannot expand sample */ + return RET_ERROR (EINVAL); + + pat->key = samples[ptr].key; /* Ensure the link is correct */ + + memcpy ((char *) &samples[ptr], rec->data.data8, + sizeof (struct patch_info)); + + pat->key = GUS_PATCH; + } + return 0; + break; + + case PM_READ_PATCH: /* Returns a block of wave data from the DRAM */ + { + int sample = rec->parm1; + int n; + long offs = rec->parm2; + int l = rec->parm3; + + if (sample < 0 || sample >= free_sample) + return RET_ERROR (EINVAL); + + if (offs < 0 || offs >= samples[sample].len) + return RET_ERROR (EINVAL); /* Invalid offset */ + + n = samples[sample].len - offs; /* Nr of bytes left */ + + if (l > n) + l = n; + + if (l > sizeof (rec->data.data8)) + l = sizeof (rec->data.data8); + + if (l <= 0) + return RET_ERROR (EINVAL); /* Was there a bug? */ + + offs += sample_ptrs[sample]; /* Begin offsess + offset to DRAM */ + + for (n = 0; n < l; n++) + rec->data.data8[n] = gus_peek (offs++); + rec->parm1 = n; /* Nr of bytes copied */ + } + return 0; + break; + + case PM_WRITE_PATCH: /* Writes a block of wave data to the DRAM */ + { + int sample = rec->parm1; + int n; + long offs = rec->parm2; + int l = rec->parm3; + + if (sample < 0 || sample >= free_sample) + return RET_ERROR (EINVAL); + + if (offs < 0 || offs >= samples[sample].len) + return RET_ERROR (EINVAL); /* Invalid offset */ + + n = samples[sample].len - offs; /* Nr of bytes left */ + + if (l > n) + l = n; + + if (l > sizeof (rec->data.data8)) + l = sizeof (rec->data.data8); + + if (l <= 0) + return RET_ERROR (EINVAL); /* Was there a bug? */ + + offs += sample_ptrs[sample]; /* Begin offsess + offset to DRAM */ + + for (n = 0; n < l; n++) + gus_poke (offs++, rec->data.data8[n]); + rec->parm1 = n; /* Nr of bytes copied */ + } + return 0; + break; + + default: + return RET_ERROR (EINVAL); + } +} + +static struct synth_operations guswave_operations = +{ + &gus_info, + SYNTH_TYPE_SAMPLE, + SAMPLE_TYPE_GUS, + guswave_open, + guswave_close, + guswave_ioctl, + guswave_kill_note, + guswave_start_note, + guswave_set_instr, + guswave_reset, + guswave_hw_control, + guswave_load_patch, + guswave_aftertouch, + guswave_controller, + guswave_panning, + guswave_patchmgr +}; + +long +gus_wave_init (long mem_start, int irq, int dma) +{ + printk (" <Gravis UltraSound %dk>", gus_mem_size / 1024); + + if (irq < 0 || irq > 15) + { + printk ("ERROR! Invalid IRQ#%d. GUS Disabled", irq); + return mem_start; + } + + if (dma < 0 || dma > 7) + { + printk ("ERROR! Invalid DMA#%d. GUS Disabled", dma); + return mem_start; + } + + gus_irq = irq; + gus_dma = dma; + + if (num_synths >= MAX_SYNTH_DEV) + printk ("GUS Error: Too many synthesizers\n"); + else + synth_devs[num_synths++] = &guswave_operations; + + reset_sample_memory (); + + gus_initialize (); + + if (num_dspdevs < MAX_DSP_DEV) + { + dsp_devs[gus_devnum = num_dspdevs++] = &gus_sampling_operations; + sound_dsp_dmachan[gus_devnum] = dma; + sound_buffcounts[gus_devnum] = 1; + sound_buffsizes[gus_devnum] = DSP_BUFFSIZE; + sound_dma_automode[gus_devnum] = 0; + } + else + printk ("GUS: Too many PCM devices available\n"); + + return mem_start; +} + +static void +do_loop_irq (int voice) +{ + unsigned char tmp; + int mode, parm; + unsigned long flags; + + DISABLE_INTR (flags); + gus_select_voice (voice); + + tmp = gus_read8 (0x00); + tmp &= ~0x20; /* Disable wave IRQ for this_one voice */ + gus_write8 (0x00, tmp); + + mode = voices[voice].loop_irq_mode; + voices[voice].loop_irq_mode = 0; + parm = voices[voice].loop_irq_parm; + + switch (mode) + { + + case LMODE_FINISH: /* Final loop finished, shoot volume down */ + + if ((gus_read16 (0x09) >> 4) < 100) /* Get current volume */ + { + gus_voice_off (); + gus_rampoff (); + gus_voice_init (voice); + return; + } + gus_ramp_range (65, 4065); + gus_ramp_rate (0, 63); /* Fastest possible rate */ + gus_rampon (0x20 | 0x40); /* Ramp down, once, irq */ + voices[voice].volume_irq_mode = VMODE_HALT; + break; + + case LMODE_PCM_STOP: + pcm_active = 0; /* Requires extensive processing */ + case LMODE_PCM: + { + int orig_qlen = pcm_qlen; + + pcm_qlen--; + pcm_head = (pcm_head + 1) % pcm_nblk; + if (pcm_qlen) + { + play_next_pcm_block (); + } + else + { /* Out of data. Just stop the voice */ + gus_voice_off (); + gus_rampoff (); + pcm_active = 0; + } + + if (orig_qlen == pcm_nblk) + { + DMAbuf_outputintr (gus_devnum); + } + } + break; + + default:; + } + RESTORE_INTR (flags); +} + +static void +do_volume_irq (int voice) +{ + unsigned char tmp; + int mode, parm; + unsigned long flags; + + DISABLE_INTR (flags); + + gus_select_voice (voice); + + tmp = gus_read8 (0x0d); + tmp &= ~0x20; /* Disable volume ramp IRQ */ + gus_write8 (0x0d, tmp); + + mode = voices[voice].volume_irq_mode; + voices[voice].volume_irq_mode = 0; + parm = voices[voice].volume_irq_parm; + + switch (mode) + { + case VMODE_HALT: /* Decay phase finished */ + gus_voice_init (voice); + break; + + case VMODE_ENVELOPE: + gus_rampoff (); + step_envelope (voice); + break; + + default:; + } + + RESTORE_INTR (flags); +} + +void +gus_voice_irq (void) +{ + unsigned long wave_ignore = 0, volume_ignore = 0; + unsigned long voice_bit; + + unsigned char src, voice; + + while (1) + { + src = gus_read8 (0x0f); /* Get source info */ + voice = src & 0x1f; + src &= 0xc0; + + if (src == (0x80 | 0x40)) + return; /* No interrupt */ + + voice_bit = 1 << voice; + + if (!(src & 0x80)) /* Wave IRQ pending */ + if (!(wave_ignore & voice_bit) && voice < nr_voices) /* Not done yet */ + { + wave_ignore |= voice_bit; + do_loop_irq (voice); + } + + if (!(src & 0x40)) /* Volume IRQ pending */ + if (!(volume_ignore & voice_bit) && voice < nr_voices) /* Not done yet */ + { + volume_ignore |= voice_bit; + do_volume_irq (voice); + } + } +} + +void +guswave_dma_irq (void) +{ + unsigned char status; + + status = gus_look8 (0x41); /* Get DMA IRQ Status */ + if (status & 0x40) /* DMA Irq pending */ + switch (active_device) + { + case GUS_DEV_WAVE: + if (dram_sleep_flag) + WAKE_UP (dram_sleeper); + break; + + case GUS_DEV_PCM_CONTINUE: + gus_transfer_output_block (pcm_current_dev, pcm_current_buf, + pcm_current_count, + pcm_current_intrflag, 1); + break; + + case GUS_DEV_PCM_DONE: + if (pcm_qlen < pcm_nblk) + { + DMAbuf_outputintr (gus_devnum); + } + break; + + default:; + } + + status = gus_look8 (0x49); /* Get Sampling IRQ Status */ + if (status & 0x40) /* Sampling Irq pending */ + { + DMAbuf_inputintr (gus_devnum); + } + +} + +#endif diff --git a/sys/i386/isa/sound/gustest/Makefile b/sys/i386/isa/sound/gustest/Makefile new file mode 100644 index 0000000..d161e5b --- /dev/null +++ b/sys/i386/isa/sound/gustest/Makefile @@ -0,0 +1,16 @@ +all: gustest gusload gmod midithru + +gustest: gustest.c + $(CC) -o gustest gustest.c -lm + +gusload: gusload.c + $(CC) -o gusload gusload.c + +gmod: gmod.c + $(CC) -o gmod gmod.c + +midithru: midithru.c + $(CC) -o midithru midithru.c + +clean: + rm -f gusload gustest gmod midithru *.o diff --git a/sys/i386/isa/sound/gustest/Readme b/sys/i386/isa/sound/gustest/Readme new file mode 100644 index 0000000..7640bf8 --- /dev/null +++ b/sys/i386/isa/sound/gustest/Readme @@ -0,0 +1,67 @@ +The programs in this directory are more or less incompletely implemented. +I have used them for debugging purposes while developing the driver. + +Files in this directory: + + +../ultrasound.h (sys/ultrasound.h) + This file contains some macros which are similar than + the procedures provided by GUSUNIT.PAS. See gustest.c + for more information. + INSTALL THIS FILE TO YOUR /usr/include/sys !!!!!!!!!!!! + +gusload.c This program can be used to load patches (samples) to + the DRAM of GUS. It understands the format used in the + .pat files shipped with GUS. + + Usage: gusload pgm# patchfile. + or gusload reset #Removes all patches from memory. + + You should load just the patches you will need to play + a Midi file, since the memory capacity of GUS is rather + limited (256k-1M). + + Example: + + gusload 0 acpiano.pat + gusload 1 britepno.pat + gusload 19 church.pat + + This program is not required if the adagio package is + used. It can do the patch uploading itself. + +gmod.c This is a simple module player which demonstrates + programming with GUS. It doesn't try to interpret + most of the effect commands. In fact this program + may interpret the modules incorrectly since I am + not a module player expert. + This version plays .MOD, .STM and .669 modules. + +midithru.c This program reads messages from the Midi interface + and plays the notes with an internal synthesizer + (FM or GUS). The program accepts one argument, the + synthesizer device number. In addition to the note on + and note off messages it interprets also program changes + and channel pressure messages. + If you need an example on programming the /dev/sequencer, + this is a good one. The voice allocation algorithm is + not good so don't look at it. + + NOTE! This program is useful with gmod. Jus load + a module with gmod. Wait until the module has + finished or hit ^C. Now you can play the samples + with the midithru program. + + NOTE2! You need a Midi keyboard to use this program. In + addition the Midi interface of GUS is not supported + yet which means you need also PAS16 or MPU-401. + +pmtest.c +gpatinfo.c ******* For information only ******* + These programs demonstrate the patch manager interface + which will be included to some later driver version. + This interface is not complete in version 1.99.9. + Using pmtest will hang your system sooner or later. + +Hannu Savolainen +hsavolai@cs.helsinki.fi diff --git a/sys/i386/isa/sound/gustest/gmidi.h b/sys/i386/isa/sound/gustest/gmidi.h new file mode 100644 index 0000000..ab951e7 --- /dev/null +++ b/sys/i386/isa/sound/gustest/gmidi.h @@ -0,0 +1,131 @@ + char patch_names[][9] = + { + /* 0 */ "acpiano", + /* 1 */ "britepno", + /* 2 */ "synpiano", + /* 3 */ "honktonk", + /* 4 */ "epiano1", + /* 5 */ "epiano2", + /* 6 */ "hrpschrd", + /* 7 */ "clavinet", + /* 8 */ "celeste", + /* 9 */ "glocken", + /* 10 */ "musicbox", + /* 11 */ "vibes", + /* 12 */ "marimba", + /* 13 */ "xylophon", + /* 14 */ "tubebell", + /* 15 */ "santur", + /* 16 */ "homeorg", + /* 17 */ "percorg", + /* 18 */ "rockorg", + /* 19 */ "church", + /* 20 */ "reedorg", + /* 21 */ "accordn", + /* 22 */ "harmonca", + /* 23 */ "concrtna", + /* 24 */ "nyguitar", + /* 25 */ "acguitar", + /* 26 */ "jazzgtr", + /* 27 */ "cleangtr", + /* 28 */ "mutegtr", + /* 29 */ "odguitar", + /* 30 */ "distgtr", + /* 31 */ "gtrharm", + /* 32 */ "acbass", + /* 33 */ "fngrbass", + /* 34 */ "pickbass", + /* 35 */ "fretless", + /* 36 */ "slapbas1", + /* 37 */ "slapbas2", + /* 38 */ "synbass1", + /* 39 */ "synbass2", + /* 40 */ "violin", + /* 41 */ "viola", + /* 42 */ "cello", + /* 43 */ "contraba", + /* 44 */ "marcato", + /* 45 */ "pizzcato", + /* 46 */ "harp", + /* 47 */ "timpani", + /* 48 */ "marcato", + /* 49 */ "slowstr", + /* 50 */ "synstr1", + /* 51 */ "synstr2", + /* 52 */ "choir", + /* 53 */ "doo", + /* 54 */ "voices", + /* 55 */ "orchhit", + /* 56 */ "trumpet", + /* 57 */ "trombone", + /* 58 */ "tuba", + /* 59 */ "mutetrum", + /* 60 */ "frenchrn", + /* 61 */ "hitbrass", + /* 62 */ "synbras1", + /* 63 */ "synbras2", + /* 64 */ "sprnosax", + /* 65 */ "altosax", + /* 66 */ "tenorsax", + /* 67 */ "barisax", + /* 68 */ "oboe", + /* 69 */ "englhorn", + /* 70 */ "bassoon", + /* 71 */ "clarinet", + /* 72 */ "piccolo", + /* 73 */ "flute", + /* 74 */ "recorder", + /* 75 */ "woodflut", + /* 76 */ "bottle", + /* 77 */ "shakazul", + /* 78 */ "whistle", + /* 79 */ "ocarina", + /* 80 */ "sqrwave", + /* 81 */ "sawwave", + /* 82 */ "calliope", + /* 83 */ "chiflead", + /* 84 */ "voxlead", + /* 85 */ "voxlead", + /* 86 */ "lead5th", + /* 87 */ "basslead", + /* 88 */ "fantasia", + /* 89 */ "warmpad", + /* 90 */ "polysyn", + /* 91 */ "ghostie", + /* 92 */ "bowglass", + /* 93 */ "metalpad", + /* 94 */ "halopad", + /* 95 */ "sweeper", + /* 96 */ "aurora", + /* 97 */ "soundtrk", + /* 98 */ "crystal", + /* 99 */ "atmosphr", + /* 100 */ "freshair", + /* 101 */ "unicorn", + /* 102 */ "sweeper", + /* 103 */ "startrak", + /* 104 */ "sitar", + /* 105 */ "banjo", + /* 106 */ "shamisen", + /* 107 */ "koto", + /* 108 */ "kalimba", + /* 109 */ "bagpipes", + /* 110 */ "fiddle", + /* 111 */ "Shannai", + /* 112 */ "carillon", + /* 113 */ "agogo", + /* 114 */ "steeldrm", + /* 115 */ "woodblk", + /* 116 */ "taiko", + /* 117 */ "toms", + /* 118 */ "syntom", + /* 119 */ "revcym", + /* 120 */ "fx-fret", + /* 121 */ "fx-blow", + /* 122 */ "seashore", + /* 123 */ "jungle", + /* 124 */ "telephon", + /* 125 */ "helicptr", + /* 126 */ "applause", + /* 127 */ "ringwhsl" + }; diff --git a/sys/i386/isa/sound/gustest/gmod.c b/sys/i386/isa/sound/gustest/gmod.c new file mode 100644 index 0000000..d730e26 --- /dev/null +++ b/sys/i386/isa/sound/gustest/gmod.c @@ -0,0 +1,1589 @@ +/* + * gmod.c - Module player for GUS and Linux. + * (C) Hannu Savolainen, 1993 + * + * NOTE! This program doesn't try to be a complete module player. + * It's just a too I used while developing the driver. In + * addition it can be used as an example on programming + * the LInux Sound Driver with GUS. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/ultrasound.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <math.h> +#include <string.h> + +#define CMD_ARPEG 0x00 +#define CMD_SLIDEUP 0x01 +#define CMD_SLIDEDOWN 0x02 +#define CMD_SLIDETO 0x03 +#define SLIDE_SIZE 8 +#define CMD_VOLSLIDE 0x0a +#define CMD_JUMP 0x0b +#define CMD_VOLUME 0x0c +#define CMD_BREAK 0x0d +#define CMD_SPEED 0x0f +#define CMD_NOP 0xfe +#define CMD_NONOTE 0xff + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define MAX_TRACK 8 +#define MAX_PATTERN 128 +#define MAX_POSITION 128 + +struct note_info + { + unsigned char note; + unsigned char vol; + unsigned char sample; + unsigned char command; + short parm1, parm2; + }; + +struct voice_info + { + int sample; + int note; + int volume; + int pitchbender; + + /* Pitch sliding */ + + int slide_pitch; + int slide_goal; + int slide_rate; + + int volslide; + }; + +typedef struct note_info pattern[MAX_TRACK][64]; +int pattern_len[MAX_POSITION]; +int pattern_tempo[MAX_POSITION]; +pattern *pattern_table[MAX_PATTERN]; + +struct voice_info voices[MAX_TRACK]; + +int nr_channels, nr_patterns, songlength; +int tune[MAX_POSITION]; +double tick_duration; + +int period_table[] = +{ + 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, + 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, + 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113 +}; + +SEQ_DEFINEBUF (2048); + +int seqfd; +int sample_ok[128], sample_vol[128]; +int tmp, gus_dev; +double this_time, next_time; +int ticks_per_division; +double clock_rate; /* HZ */ + +/* + * The function seqbuf_dump() must always be provided + */ + +void play_module (char *name); +int load_module (char *name); +int play_note (int channel, struct note_info *pat); +void lets_play_voice (int channel, struct voice_info *v); + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +void +init_voices () +{ + int i; + + for (i = 0; i < MAX_TRACK; i++) + { + voices[i].sample = 0; + voices[i].note = 0; + voices[i].volume = 64; + + voices[i].slide_pitch = 0; + voices[i].slide_goal = 0; + voices[i].slide_rate = 0; + voices[i].pitchbender = 0; + + voices[i].volslide = 0; + } +} + +int +main (int argc, char *argv[]) +{ + int i, n, j; + struct synth_info info; + + if ((seqfd = open ("/dev/sequencer", O_WRONLY, 0)) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Gravis Ultrasound not detected\n"); + exit (-1); + } + + GUS_NUMVOICES (gus_dev, 14); + + for (i = 1; i < argc; i++) + { + for (j = 0; j < MAX_PATTERN; j++) + pattern_table[j] = NULL; + + if (load_module (argv[i])) + { + tick_duration = 100.0 / clock_rate; + play_module (argv[i]); + } + + } + + SEQ_DUMPBUF (); + close (seqfd); + + exit (0); +} + +unsigned short +intelize (unsigned short v) +{ + return ((v & 0xff) << 8) | ((v >> 8) & 0xff); +} + +unsigned long +intelize4 (unsigned long v) +{ + return + (((v >> 16) & 0xff) << 8) | (((v >> 16) >> 8) & 0xff) | + (((v & 0xff) << 8) | ((v >> 8) & 0xff) << 16); +} + +int +load_stm_module (int mod_fd, char *name) +{ + + struct sample_header + { + char name[12]; + unsigned char instr_disk; + unsigned short reserved1; + unsigned short length; /* In bytes */ + unsigned short loop_start; + unsigned short loop_end; + unsigned char volume; + unsigned char reserved2; + unsigned short C2_speed; + unsigned short reserved3; + + }; + + int i, total_mem; + int sample_ptr; + + int position; + + unsigned char *tune_ptr; /* array 0-127 */ + + char header[1105], sname[21]; + + int nr_samples; /* 16 or 32 samples (or 64 or ???) */ + int slen, npat; + + fprintf (stderr, "Loading .STM module: %s\n", name); + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + strncpy (sname, header, 20); + + fprintf (stderr, "\nModule: %s - ", sname); + + if (header[28] != 0x1a) + { + fprintf (stderr, "Not a STM module\n"); + close (mod_fd); + return 0; + } + + npat = header[33]; + slen = 0; + tune_ptr = &header[48 + (31 * 32)]; + + for (i = 0; i < 64; i++) + { + tune[i] = tune_ptr[i]; + if (tune[i] < npat) + slen = i; + } + + fprintf (stderr, "Song lenght %d, %d patterns.\n", slen, npat); + + nr_samples = 31; + + sample_ptr = 48 + (31 * 32) + 64 + (npat * 1024); /* Location where the + * first sample is + * stored */ + total_mem = 0; + + for (i = 0; i < 32; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end, base_freq; + unsigned short loop_flags = 0; + + struct sample_header *sample; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[48 + (i * 32)]; + + len = sample->length; + loop_start = sample->loop_start; + loop_end = sample->loop_end; + base_freq = sample->C2_speed; + + if (strlen (sample->name) > 21) + { + fprintf (stderr, "\nInvalid name for sample #%d\n", i); + close (mod_fd); + return 0; + } + + if (len > 0) + { + int x; + + if (loop_end > len) + loop_end = 1; + else if (loop_end < loop_start) + { + loop_start = 0; + loop_end = 0; + } + else + loop_flags = WAVE_LOOPING; + + total_mem += len; + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_freq = base_freq; + patch->base_note = 261630; /* Mid C */ + patch->low_note = 0; + patch->high_note = 0x7fffffff; + patch->volume = 120; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if ((x = read (mod_fd, patch->data, len)) != len) + { + fprintf (stderr, "Short file (sample at %d (%d!=%d)\n", sample_ptr, x, len); + close (mod_fd); + free (patch); + return 0; + } + + fprintf (stderr, "Sample %02d: %05d, %05d, %05d, %07d %s\n", + i, + len, + loop_start, + loop_end, + base_freq, + sample->name); + + if (write (seqfd, patch, sizeof (*patch) + len) == -1) + { + perror ("ioctl /dev/sequencer"); + exit (-1); + } + else + sample_ok[i] = 1; + + free (patch); + } + } + + nr_patterns = slen; + songlength = slen; + nr_channels = 4; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[64][4][4]; + int pat, channel, x; + + int pp = 1104 + (position * 1024); + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if ((x = read (mod_fd, patterns, 1024)) != 1024) + { + fprintf (stderr, "Short file (pattern at %d), %d!=%d\n", pp, x, 1024); + close (mod_fd); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + + for (channel = 0; channel < 4; channel++) + { + unsigned char *p; + + unsigned vol, note, octave, sample, effect, params; + + p = &patterns[pat][channel][0]; + + if (p[0] < 251) + { + note = p[0] & 15; + octave = p[0] / 16; + + note = 48 + octave * 12 + note; + + sample = p[1] / 8; + vol = (p[1] & 7) + (p[2] / 2); + effect = p[2] & 0xF; + params = p[3]; + } + else + { + note = 0; + octave = 0; + + sample = 0; + vol = 0; + effect = CMD_NONOTE; + params = 0; + } + + (*pattern_table[position])[channel][pat].note = note; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + + } + + } + + close (mod_fd); + return 1; +} + +int +load_669_module (int mod_fd, char *name) +{ + struct sample_header + { + char name[13]; + unsigned long length; /* In bytes */ + unsigned long loop_start; + unsigned long loop_end; + }; + + int i, total_mem; + int sample_ptr; + + int position; + + unsigned char *tune_ptr, *len_ptr, *tempo_ptr; /* array 0-127 */ + + char header[1084]; + char msg[110]; + + int nr_samples; /* 16 or 32 samples */ + int slen, npat; + + clock_rate = 25.0; + + fprintf (stderr, "Loading .669 module: %s\n", name); + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + if (*(unsigned short *) &header[0] != 0x6669) + { + fprintf (stderr, "Not a 669 file\n"); + close (mod_fd); + return 0; + } + + strncpy (msg, &header[2], 108); + + for (i = 0; i < strlen (msg); i++) + if ((msg[i] >= ' ' && msg[i] <= 'z') || msg[i] == '\n') + printf ("%c", msg[i]); + printf ("\n"); + + npat = header[0x6f]; + + tune_ptr = &header[0x71]; + + for (slen = 0; slen < 128 && tune_ptr[slen] != 0xff; slen++); + slen--; + + for (i = 0; i < slen; i++) + tune[i] = tune_ptr[i]; + + len_ptr = &header[0x171]; + for (i = 0; i < slen; i++) + pattern_len[i] = len_ptr[i] - 1; + + tempo_ptr = &header[0xf1]; + for (i = 0; i < slen; i++) + pattern_tempo[i] = tempo_ptr[i]; + + nr_samples = header[0x6e]; + + fprintf (stderr, "Song lenght %d, %d patterns, %d samples.\n", slen, npat, nr_samples); + + sample_ptr = 0x1f1 + (nr_samples * 0x19) + (npat * 0x600); /* Location where the + * first sample is + * stored */ + total_mem = 0; + + for (i = 0; i < 64; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end; + unsigned short loop_flags = 0; + + struct sample_header *sample; + char sname[14]; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[0x1f1 + (i * 0x19)]; + + len = *(unsigned long *) &sample->name[13]; + loop_start = *(unsigned long *) &sample->name[17]; + loop_end = *(unsigned long *) &sample->name[21]; + if (loop_end > len) + loop_end = 1; + else if (loop_end == len) + loop_end--; + + if (loop_end < loop_start) + { + loop_start = 0; + loop_end = 0; + } + + strncpy (sname, sample->name, 13); + + if (len > 0 && len < 200000) + { + total_mem += len; + + fprintf (stderr, "Sample %02d: %05d, %05d, %05d %s\n", + i, + len, + loop_start, + loop_end, + sname); + + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + if (loop_end == 0) + loop_end = 1; + if (loop_end >= len) + loop_end = 1; + + if (loop_end > 1) loop_flags = WAVE_LOOPING; + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = WAVE_UNSIGNED | loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_freq = 8448; + patch->base_note = 261630; + patch->low_note = 1000; + patch->high_note = 0x7fffffff; + patch->volume = 120; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + fprintf (stderr, "Seek failed\n"); + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if (read (mod_fd, patch->data, len) != len) + { + fprintf (stderr, "Short file (sample at %d)\n", sample_ptr); + close (mod_fd); + free (patch); + return 0; + } + + if (write (seqfd, patch, sizeof (*patch) + len) == -1) + { + perror ("ioctl /dev/sequencer"); + /* exit (-1); */ + } + else + sample_ok[i] = 1; + + free (patch); + } + } + + nr_patterns = slen; + songlength = slen; + nr_channels = 8; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[0x600]; + int pat, channel, x; + + int pp = 0x1f1 + (nr_samples * 0x19) + (position * 0x600); + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if ((x = read (mod_fd, patterns, 1024)) != 1024) + { + fprintf (stderr, "Short file (pattern at %d) %d!=1024\n", pp, x); + close (mod_fd); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + + for (channel = 0; channel < 8; channel++) + { + unsigned char *p; + + unsigned vol, period, sample, effect, params; + + p = &patterns[pat * 24 + channel * 3]; + + if (p[0] >= 0xfe || + (p[0] == 0xff && p[1] == 0xff && p[2] == 0xff) || + (p[0] == 0 && p[1] == 0 && p[2] == 0) || + *(int *) p == -1) + { + period = 0; + effect = CMD_NONOTE; + sample = 0; + vol = 0; + params = 0; + + if (p[0] == 0) + { + effect = CMD_BREAK; + params = -2; + } + } + else + { + period = (p[0] >> 2) + 48; + effect = (p[2] >> 4); + params = p[2] & 0x0f; + vol = p[1] & 0x0f; + + if (p[2] == 0xfe) + { + effect = CMD_VOLUME; + params = vol; + } + else if (p[2] == 0xff) + { + effect = CMD_NOP; + } + else + switch (effect) + { + case 0: /* a - Portamento up */ + effect = CMD_SLIDEUP; + break; + + case 1: /* b - Portamento down */ + effect = CMD_SLIDEDOWN; + break; + + case 2: /* c - Port to note */ + effect = CMD_SLIDETO; + break; + + case 3: /* d - Frequency adjust */ + effect = CMD_NOP; /* To be implemented */ + break; + + case 4: /* e - Frequency vibrato */ + effect = CMD_NOP; /* To be implemented */ + break; + + case 5: /* f - Set tempo */ + effect = CMD_SPEED; + break; + + default: + effect = CMD_NOP; + } + + sample = (((p[0] << 4) & 0x30) | ((p[1] >> 4) & 0x0f)) + 1; + } + + (*pattern_table[position])[channel][pat].note = period; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + + } + + } + + close (mod_fd); + return 1; +} + +int +load_mmd0_module (int mod_fd, char *name) +{ + + struct sample_header + { + unsigned short loop_start; + unsigned short loop_end; + unsigned char midich; + unsigned char midipreset; + unsigned char volume; + unsigned char strans; + }; + + int i, total_mem; + int sample_ptr; + + int position; + + unsigned char *tune_ptr; /* array 0-127 */ + + char header[1105]; + + int nr_samples; /* 16 or 32 samples (or 64 or ???) */ + int slen, npat; + + fprintf (stderr, "Loading .MED module: %s\n", name); + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + if (strncmp (header, "MMD0", 4)) + { + fprintf (stderr, "Not a MED module\n"); + close (mod_fd); + return 0; + } + + printf ("Module len %d\n", intelize4 (*(long *) &header[4])); + printf ("Song info %d\n", intelize4 (*(long *) &header[8])); + printf ("Song len %d\n", intelize4 (*(long *) &header[12])); + printf ("Blockarr %x\n", intelize4 (*(long *) &header[16])); + printf ("Blockarr len %d\n", intelize4 (*(long *) &header[20])); + printf ("Sample array %x\n", intelize4 (*(long *) &header[24])); + printf ("Sample array len %d\n", intelize4 (*(long *) &header[28])); + printf ("Exp data %x\n", intelize4 (*(long *) &header[32])); + printf ("Exp size %d\n", intelize4 (*(long *) &header[36])); + printf ("Pstate %d\n", intelize (*(long *) &header[40])); + printf ("Pblock %d\n", intelize (*(long *) &header[42])); + + return 0; + + npat = header[33]; + slen = 0; + tune_ptr = &header[48 + (31 * 32)]; + + for (i = 0; i < 64; i++) + { + tune[i] = tune_ptr[i]; + if (tune[i] < npat) + slen = i; + } + + fprintf (stderr, "Song lenght %d, %d patterns.\n", slen, npat); + + nr_samples = 31; + + sample_ptr = 48 + (31 * 32) + 64 + (npat * 1024); /* Location where the + * first sample is + * stored */ + total_mem = 0; + + for (i = 0; i < 32; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end, base_freq; + unsigned short loop_flags = 0; + + struct sample_header *sample; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[48 + (i * 32)]; + + /* + * len = sample->length; loop_start = sample->loop_start; loop_end = + * sample->loop_end; base_freq = sample->C2_speed; + * + * if (strlen (sample->name) > 21) { fprintf (stderr, "\nInvalid name for + * sample #%d\n", i); close (mod_fd); return 0; } + */ + if (len > 0) + { + int x; + + if (loop_end > len) + loop_end = 1; + + if (loop_end < loop_start) + { + loop_start = 0; + loop_end = 0; + } + + if (loop_end > 2) loop_flags = WAVE_LOOPING; + + total_mem += len; + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_freq = base_freq; + patch->base_note = 261630; /* Mid C */ + patch->low_note = 0; + patch->high_note = 0x7fffffff; + patch->volume = 120; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if ((x = read (mod_fd, patch->data, len)) != len) + { + fprintf (stderr, "Short file (sample at %d (%d!=%d)\n", sample_ptr, x, len); + close (mod_fd); + free (patch); + return 0; + } + /* + * fprintf (stderr, "Sample %02d: %05d, %05d, %05d, %07d %s\n", i, + * len, loop_start, loop_end, base_freq, sample->name); + */ + if (write (seqfd, patch, sizeof (*patch) + len) == -1) + { + perror ("ioctl /dev/sequencer"); + exit (-1); + } + else + sample_ok[i] = 1; + + free (patch); + } + } + + nr_patterns = slen; + songlength = slen; + nr_channels = 4; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[64][4][4]; + int pat, channel, x; + + int pp = 1104 + (position * 1024); + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if ((x = read (mod_fd, patterns, 1024)) != 1024) + { + fprintf (stderr, "Short file (pattern at %d), %d!=%d\n", pp, x, 1024); + close (mod_fd); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + + for (channel = 0; channel < 4; channel++) + { + unsigned char *p; + + unsigned vol, note, octave, sample, effect, params; + + p = &patterns[pat][channel][0]; + + if (p[0] < 251) + { + note = p[0] & 15; + octave = p[0] / 16; + + note = 48 + octave * 12 + note; + + sample = p[1] / 8; + vol = (p[1] & 7) + (p[2] / 2); + effect = p[2] & 0xF; + params = p[3]; + } + else + { + note = 0; + octave = 0; + + sample = 0; + vol = 0; + effect = CMD_NONOTE; + params = 0; + } + + (*pattern_table[position])[channel][pat].note = note; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + + } + + } + + close (mod_fd); + return 1; +} + +int +load_module (char *name) +{ + + struct sample_header + { + char name[22]; + unsigned short length; /* In words */ + + unsigned char finetune; + unsigned char volume; + + unsigned short repeat_point; /* In words */ + unsigned short repeat_length; /* In words */ + }; + + int i, mod_fd, total_mem; + int sample_ptr, pattern_loc; + + int position; + + unsigned char *tune_ptr; /* array 0-127 */ + + char header[1084]; + + int nr_samples; /* 16 or 32 samples */ + int slen, npat; + char mname[23]; + + ioctl (seqfd, SNDCTL_SEQ_SYNC, 0); + ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev); + + clock_rate = 50.0; + + for (i = 0; i < MAX_POSITION; i++) + pattern_len[i] = 64; + + for (i = 0; i < MAX_POSITION; i++) + pattern_tempo[i] = 0; + + if ((mod_fd = open (name, O_RDONLY, 0)) == -1) + { + perror (name); + return 0; + } + + if (read (mod_fd, header, sizeof (header)) != sizeof (header)) + { + fprintf (stderr, "%s: Short file (header)\n", name); + close (mod_fd); + return 0; + } + + if (lseek (mod_fd, 0, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if (header[28] == 0x1a) + return load_stm_module (mod_fd, name); + + if (*(unsigned short *) &header[0] == 0x6669) + return load_669_module (mod_fd, name); + + if (!strncmp (header, "MMD0", 4)) + return load_mmd0_module (mod_fd, name); + + fprintf (stderr, "Loading .MOD module: %s\n", name); + + strncpy (mname, header, 22); + fprintf (stderr, "\nModule: %s - ", mname); + + if (!strncmp (&header[1080], "M.K.", 4) || !strncmp (&header[1080], "FLT8", 4)) + { + fprintf (stderr, "31 samples\n"); + nr_samples = 31; + } + else + { + fprintf (stderr, "15 samples\n"); + nr_samples = 15; + } + + if (nr_samples == 31) + { + sample_ptr = pattern_loc = 1084; + slen = header[950]; + tune_ptr = (unsigned char *) &header[952]; + } + else + { + sample_ptr = pattern_loc = 600; + slen = header[470]; + tune_ptr = (unsigned char *) &header[472]; + } + + npat = 0; + for (i = 0; i < 128; i++) + { + tune[i] = tune_ptr[i]; + + if (tune_ptr[i] > npat) + npat = tune_ptr[i]; + } + npat++; + + fprintf (stderr, "Song lenght %d, %d patterns.\n", slen, npat); + + sample_ptr += (npat * 1024); /* Location where the first sample is stored */ + total_mem = 0; + + for (i = 0; i < 32; i++) + sample_ok[i] = 0; + + for (i = 0; i < nr_samples; i++) + { + int len, loop_start, loop_end; + unsigned short loop_flags = 0; + char pname[22]; + + struct sample_header *sample; + + struct patch_info *patch; + + sample = (struct sample_header *) &header[20 + (i * 30)]; + + len = intelize (sample->length) * 2; + loop_start = intelize (sample->repeat_point) * 2; + loop_end = loop_start + (intelize (sample->repeat_length) * 2); + + if (loop_start > len) + loop_start = 0; + if (loop_end > len) + loop_end = len; + + if (loop_end <= loop_start) + loop_end = loop_start + 1; + + if (loop_end > 2 && loop_end > loop_start) + loop_flags = WAVE_LOOPING; + + strncpy (pname, sample->name, 20); + + if (len > 0) + { + fprintf (stderr, "Sample %02d: L%05d, S%05d, E%05d V%02d %s\n", + i, + len, + loop_start, + loop_end, + sample->volume, + pname); + + total_mem += len; + + patch = (struct patch_info *) malloc (sizeof (*patch) + len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = i; + patch->mode = loop_flags; + patch->len = len; + patch->loop_start = loop_start; + patch->loop_end = loop_end; + patch->base_note = 261630; /* Middle C */ + patch->base_freq = 8448; + patch->low_note = 0; + patch->high_note = 20000000; + patch->volume = 120; + patch->panning = 0; + + if (lseek (mod_fd, sample_ptr, 0) == -1) + { + perror (name); + close (mod_fd); + free (patch); + return 0; + } + + sample_ptr += len; + + if (read (mod_fd, patch->data, len) != len) + { + fprintf (stderr, "Short file (sample) %d\n", sample_ptr); + close (mod_fd); + free (patch); + return 0; + } + + SEQ_WRPATCH (patch, sizeof (*patch) + len); + + sample_ok[i] = 1; + if (sample->volume == 0) sample->volume = 64; + sample_vol[i] = sample->volume; + + free (patch); + } + } + + nr_patterns = npat; + songlength = slen; + nr_channels = 4; + + for (position = 0; position < npat; position++) + { + unsigned char patterns[64][4][4]; + int pat, channel; + + int pp = pattern_loc + (position * 1024); + + if (lseek (mod_fd, pp, 0) == -1) + { + perror (name); + close (mod_fd); + return 0; + } + + if (read (mod_fd, patterns, 1024) != 1024) + { + fprintf (stderr, "Short file (pattern %d) %d\n", tune[position], pp); + close (mod_fd); + return 0; + } + + if ((pattern_table[position] = (pattern *) malloc (sizeof (struct note_info) * 64 * nr_channels)) == NULL) + { + fprintf (stderr, "Can't allocate memory for a pattern\n"); + return 0; + } + + for (pat = 0; pat < 64; pat++) + { + for (channel = 0; channel < 4; channel++) + { + unsigned short tmp; + unsigned char *p; + + unsigned period, sample, effect, params, note, vol; + + p = &patterns[pat][channel][0]; + + tmp = (p[0] << 8) | p[1]; + sample = (tmp >> 8) & 0x10; + period = + MIN (tmp & 0xFFF, 1023); + tmp = (p[2] << 8) | p[3]; + sample |= tmp >> 12; + effect = (tmp >> 8) & 0xF; + params = tmp & 0xFF; + + note = 0; + + if (period) + { + /* + * Convert period to a Midi note number + */ + + for (note = 0; note < 37 && period != period_table[note]; note++); + if (note >= 37) + note = 0; + + note += 48; + } + + vol = 64; + + if (sample) + if (effect == 0xc) + { + vol = params; + } + else + vol = sample_vol[sample - 1]; + + vol *= 2; + if (vol>64)vol--; + + (*pattern_table[position])[channel][pat].note = note; + (*pattern_table[position])[channel][pat].sample = sample; + (*pattern_table[position])[channel][pat].command = effect; + (*pattern_table[position])[channel][pat].parm1 = params; + (*pattern_table[position])[channel][pat].parm2 = 0; + (*pattern_table[position])[channel][pat].vol = vol; + } + } + } + + close (mod_fd); + return 1; +} + +int +panning (int ch) +{ + static int panning_tab[] = + {-110, 110, 110, -110}; + + return panning_tab[ch % 4]; +} + +void +set_speed (int parm) +{ + if (!parm) + parm = 1; + + if (parm < 32) + { + ticks_per_division = parm; + } + else + { + tick_duration = (60.0 / parm) * 10.0; + } + +} + +void +play_module (char *name) +{ + int i, position, jump_to_pos; + + init_voices (); + + SEQ_START_TIMER (); +#if 1 + for (i=0;i<32;i++) + { + SEQ_EXPRESSION(gus_dev, i, 127); + SEQ_MAIN_VOLUME(gus_dev, i, 100); + } +#endif + next_time = 0.0; + + set_speed (6); + + for (position = 0; position < songlength; position++) + { + int tick, pattern, channel, pos, go_to; + + pos = tune[position]; + if (pattern_tempo[position]) + set_speed (pattern_tempo[position]); + + jump_to_pos = -1; + for (pattern = 0; pattern < pattern_len[position] && jump_to_pos == -1; pattern++) + { + this_time = 0.0; + + for (channel = 0; channel < nr_channels; channel++) + { + if ((go_to = play_note (channel, &(*pattern_table[pos])[channel][pattern])) != -1) + jump_to_pos = go_to; + + } + + next_time += tick_duration; + + for (tick = 1; tick < ticks_per_division; tick++) + { + for (channel = 0; channel < nr_channels; channel++) + lets_play_voice (channel, &voices[channel]); + next_time += tick_duration; + } + + } + + if (jump_to_pos >= 0) + position = jump_to_pos; + } + + SEQ_WAIT_TIME ((int) next_time + 200); /* Wait extra 2 secs */ + + for (i = 0; i < nr_channels; i++) + SEQ_STOP_NOTE (gus_dev, i, 0, 127); + SEQ_DUMPBUF (); + + for (i = 0; i < nr_patterns; i++) + free (pattern_table[i]); +} + +void +sync_time () +{ + if (next_time > this_time) + { + SEQ_WAIT_TIME ((long) next_time); + this_time = next_time; + } +} + +void +set_volslide (int channel, struct note_info *pat) +{ + int n; + + voices[channel].volslide = 0; + + if ((n = (pat->parm1 & 0xf0) >> 4)) + voices[channel].volslide = n; + else + voices[channel].volslide = pat->parm1 & 0xf; +} + +void +set_slideto (int channel, struct note_info *pat) +{ + int size, rate, dir, range = 200; + + rate = pat->parm1; + size = voices[channel].note - pat->note; + if (!size) + return; + + if (size < 0) + { + size *= -1; + dir = -1; + } + else + dir = 1; + + if (size > 2) + { + range = size * 100; + rate = rate * size / 200; + } + + rate = pat->parm1 * dir / 30; + if (!rate) + rate = 1; + + voices[channel].slide_pitch = 1; + voices[channel].slide_goal = (dir * 8192 * 200 * 2 / size) / range; + voices[channel].pitchbender = 0; + voices[channel].slide_rate = rate; + SEQ_BENDER_RANGE (gus_dev, channel, range); +} + +int +play_note (int channel, struct note_info *pat) +{ + int jump = -1; + int sample; + + if (pat->sample == 0x3f) + pat->sample = 0; + + if (pat->command == CMD_NONOTE) + return -1; /* Undefined */ + + sample = pat->sample; + + if (sample && !pat->note) + { + pat->note = voices[channel].note; + } + + if (sample) + voices[channel].sample = sample; + else + sample = voices[channel].sample; + + sample--; + + if (pat->note && pat->command != 3) /* Have a note -> play */ + { + if (sample < 0) + sample = voices[channel].sample - 1; + + if (!sample_ok[sample]) + sample = voices[channel].sample - 1; + + if (sample < 0) + sample = 0; + + if (sample_ok[sample]) + { + sync_time (); + + if (pat->vol > 127) pat->vol=127; + SEQ_SET_PATCH (gus_dev, channel, sample); + SEQ_PANNING (gus_dev, channel, panning (channel)); + SEQ_PITCHBEND (gus_dev, channel, 0); + SEQ_START_NOTE (gus_dev, channel, pat->note, pat->vol); + + voices[channel].volume = pat->vol; + voices[channel].note = pat->note; + voices[channel].slide_pitch = 0; + } + else + SEQ_STOP_NOTE (gus_dev, channel, pat->note, pat->vol); + } + + switch (pat->command) + { + + case CMD_NOP:; + break; + + case CMD_JUMP: + jump = pat->parm1; + break; + + case CMD_BREAK: + jump = -2; + break; + + case CMD_SPEED: + set_speed (pat->parm1); + break; + + case CMD_SLIDEUP: + voices[channel].slide_pitch = 1; + voices[channel].slide_goal = 8191; + voices[channel].pitchbender = 0; + voices[channel].slide_rate = pat->parm1 * SLIDE_SIZE; + SEQ_BENDER_RANGE (gus_dev, channel, 200); + break; + + case CMD_SLIDEDOWN: + voices[channel].slide_pitch = 1; + voices[channel].slide_goal = -8192; + voices[channel].pitchbender = 0; + voices[channel].slide_rate = -pat->parm1 * SLIDE_SIZE; + SEQ_BENDER_RANGE (gus_dev, channel, 200); + break; + + case CMD_SLIDETO: + set_slideto (channel, pat); + break; + + case CMD_VOLUME: + { + int vol = pat->parm1*2; + if (vol>127) vol=127; + if (pat->note && pat->command != 3) + break; + SEQ_START_NOTE (gus_dev, channel, 255, vol); + } + break; + + case CMD_ARPEG: + break; + + case 0x0e: + /* printf ("Cmd 0xE%02x\n", pat->parm1); */ + break; + + case CMD_VOLSLIDE: + set_slideto (channel, pat); + break; + + default: + /* printf ("Command %x %02x\n", pat->command, pat->parm1); */ + } + + return jump; +} + +void +lets_play_voice (int channel, struct voice_info *v) +{ + if (v->slide_pitch) + { + v->pitchbender += v->slide_rate; + if (v->slide_goal < 0) + { + if (v->pitchbender <= v->slide_goal) + { + v->pitchbender = v->slide_goal; + v->slide_pitch = 0; /* Stop */ + } + } + else + { + if (v->pitchbender >= v->slide_goal) + { + v->pitchbender = v->slide_goal; + v->slide_pitch = 0; /* Stop */ + } + } + + sync_time (); + SEQ_PITCHBEND (gus_dev, channel, v->pitchbender); + } + + if (v->volslide) + { + v->volume += v->volslide; + sync_time (); + + if (v->volume > 127) v->volume = 127; + SEQ_START_NOTE (gus_dev, channel, 255, v->volume); + } +} diff --git a/sys/i386/isa/sound/gustest/gpatinfo.c b/sys/i386/isa/sound/gustest/gpatinfo.c new file mode 100644 index 0000000..3e21fc5 --- /dev/null +++ b/sys/i386/isa/sound/gustest/gpatinfo.c @@ -0,0 +1,176 @@ +/* + * gpatinfo.c: This program demonstrates the patch management + * interface of the GUS driver. + * + * NOTE! The patch manager interface is highly device dependent, + * currently incompletely implemented prototype and + * will change before final implementation. + * + */ + +#include <stdio.h> +#include <sys/ultrasound.h> +#include <sys/ioctl.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <fcntl.h> +#include "gmidi.h" + +#define GUS_DEV gus_dev + +#define patch_access(cmd, rec) \ + rec.command = cmd;\ + rec.device = gus_dev;\ + if (ioctl(seqfd, SNDCTL_PMGR_IFACE, &rec)==-1)\ + {\ + perror("/dev/sequencer(SNDCTL_PMGR_IFACE/" #cmd ")");\ + exit(-1);\ + } + +SEQ_DEFINEBUF (2048); + +int seqfd; + +int gus_dev = -1; + +/* + * The function seqbuf_dump() must always be provided + */ + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +int +main (int argc, char *argv[]) +{ + int i, j, n; + struct synth_info info; + struct patch_info *patch; + struct patmgr_info mgr, mgr2, mgr3; + + if ((seqfd = open ("/dev/sequencer", O_WRONLY, 0)) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + +/* + * First locate the GUS device + */ + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Error: Gravis Ultrasound not detected\n"); + exit (-1); + } + + printf("Gravis UltraSound device = %d\n", gus_dev); + + /* + * Get type of the Patch Manager interface of the GUS device + */ + + patch_access(PM_GET_DEVTYPE, mgr); + printf("Patch manager type: %d\n", mgr.parm1); + + if (mgr.parm1 != PMTYPE_WAVE) + { + fprintf(stderr, "Hups, this program seems to be obsolete\n"); + exit(-1); + } + + /* + * The GUS driver supports up to 256 different midi program numbers but + * this limit can be changed before compiling the driver. The following + * call returns the value compiled to the driver. + */ + + patch_access(PM_GET_PGMMAP, mgr); + printf("Device supports %d midi programs.\n", mgr.parm1); + + /* + * Each program can be undefined or it may have one or more patches. + * A patch consists of header and the waveform data. If there is more + * than one patch in a program, the right one is selected by checking the + * note number when the program is played. + * + * The following call reads an array indexed by program number. Each + * element defines the number of patches defined for the corresponding + * program. + */ + printf("Loaded programs:\n"); + + for (i=0;i<mgr.parm1;i++) + if (mgr.data.data8[i]) + { + printf("%03d: %2d patches\n", i, mgr.data.data8[i]); + + /* + * Next get the magic keys of the patches associated with this program. + * This key can be used to access the patc data. + */ + mgr2.parm1=i; + patch_access(PM_GET_PGM_PATCHES, mgr2); + for (j = 0;j<mgr2.parm1;j++) + { + printf("\tPatch %d: %3d ", j, mgr2.data.data32[j]); + + /* + * The last step is to read the patch header (without wave data). + * The header is returned in the mgr3.data. The field parm1 returns + * address of the wave data in tge GUS DRAM. Parm2 returns + * size of the struct patch_info in the kernel. + * + * There is also the PM_SET_PATCH call which allows modification of the + * header data. The only limitation is that the sample len cannot be + * increased. + */ + mgr3.parm1 = mgr2.data.data32[j]; + patch_access(PM_GET_PATCH, mgr3); + patch = (struct patch_info *)&mgr3.data; /* Pointer to the patch hdr */ + + printf("DRAM ptr = %7d, sample len =%6d bytes.\n", + mgr3.parm1, patch->len); + + } + } + + i = gus_dev; + + if (ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &i)==-1) exit(-1); + printf("%d bytes of DRAM available for wave data\n", i); + + + exit(0); +} diff --git a/sys/i386/isa/sound/gustest/gusload.c b/sys/i386/isa/sound/gustest/gusload.c new file mode 100644 index 0000000..216150a --- /dev/null +++ b/sys/i386/isa/sound/gustest/gusload.c @@ -0,0 +1,350 @@ +/* + * patutil.c - A sample program which loads patches to the Gravis + * Ultrasound + * + */ + +#ifndef PATCH_PATH +#define PATCH_PATH "/D/ultrasnd/midi" +#endif + +#include <stdio.h> +#include <sys/ultrasound.h> +#include <sys/ioctl.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <fcntl.h> +#include "gmidi.h" + +struct pat_header + { + char magic[12]; + char version[10]; + char description[60]; + unsigned char instruments; + char voices; + char channels; + unsigned short nr_waveforms; + unsigned short master_volume; + unsigned long data_size; + }; + +struct sample_header + { + char name[7]; + unsigned char fractions; + long len; + long loop_start; + long loop_end; + unsigned short base_freq; + long low_note; + long high_note; + long base_note; + short detune; + unsigned char panning; + + unsigned char envelope_rate[6]; + unsigned char envelope_offset[6]; + + unsigned char tremolo_sweep; + unsigned char tremolo_rate; + unsigned char tremolo_depth; + + unsigned char vibrato_sweep; + unsigned char vibrato_rate; + unsigned char vibrato_depth; + + char modes; + + short scale_frequency; + unsigned short scale_factor; + }; + +#define GUS_DEV gus_dev + +SEQ_DEFINEBUF (2048); + +int seqfd; + +int gus_dev = -1; + +struct patch_info *patch; + +/* + * The function seqbuf_dump() must always be provided + */ + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +int +main (int argc, char *argv[]) +{ + int i, n, patfd, pgm, print_only = 0; + struct synth_info info; + struct pat_header header; + struct sample_header sample; + char buf[256]; + char name[256]; + long offset; + + if ((seqfd = open ("/dev/sequencer", O_WRONLY, 0)) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("/dev/sequencer"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Error: Gravis Ultrasound not detected\n"); + exit (-1); + } + + if (argc == 2) + { + if (!strcmp (argv[1], "reset")) + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + perror ("Sample reset"); + exit (0); + } + + if (argc != 3) + { + fprintf (stderr, "Usage: %s pgm# patchfile\n", argv[0]); + fprintf (stderr, " or : %s pgm# GM\n", argv[0]); + fprintf (stderr, " or : %s pgm# -l\n", argv[0]); + fprintf (stderr, " or : %s reset\n", argv[0]); + fprintf (stderr, " or : %s -l patchfile\n", argv[0]); + exit (-1); + } + + pgm = atoi (argv[1]); + strcpy (name, argv[2]); + + if (strcmp (name, "GM") == 0 || strcmp(name, "-l")==0) + { + if (strcmp (name, "-l") == 0) print_only = 1; + if (pgm < 0 || pgm > 127) + { + fprintf (stderr, "pgm# must be between 0 and 127\n"); + exit (-1); + } + + sprintf (name, PATCH_PATH "/%s.pat", patch_names[pgm]); + + if (!print_only) + fprintf (stderr, "Loading program %d from %s\n", pgm, name); + } + else if (strcmp (argv[1], "-l") == 0) + print_only = 1; + + if ((patfd = open (name, O_RDONLY, 0)) == -1) + { + perror (name); + exit (-1); + } + + if (read (patfd, buf, 0xef) != 0xef) + { + fprintf (stderr, "%s: Short file\n", name); + exit (-1); + } + + memcpy ((char *) &header, buf, sizeof (header)); + + if (strncmp (header.magic, "GF1PATCH110", 12)) + { + fprintf (stderr, "%s: Not a patch file\n", name); + exit (-1); + } + + if (strncmp (header.version, "ID#000002", 10)) + { + fprintf (stderr, "%s: Incompatible patch file version\n", name); + exit (-1); + } + + header.nr_waveforms = *(unsigned short *) &buf[85]; + header.master_volume = *(unsigned short *) &buf[87]; + + if (print_only) + { + printf ("Patch file: %s contains %d samples\n\n", name, header.nr_waveforms); + printf ("Master volume: %d\n", header.master_volume); + } + + offset = 0xef; + + for (i = 0; i < header.nr_waveforms; i++) + { + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + exit (-1); + } + + if (read (patfd, &buf, sizeof (sample)) != sizeof (sample)) + { + fprintf (stderr, "%s: Short file\n", name); + exit (-1); + } + + memcpy ((char *) &sample, buf, sizeof (sample)); + + /* + * Since some fields of the patch record are not 32bit aligned, we must + * handle them specially. + */ + sample.low_note = *(long *) &buf[22]; + sample.high_note = *(long *) &buf[26]; + sample.base_note = *(long *) &buf[30]; + sample.detune = *(short *) &buf[34]; + sample.panning = (unsigned char) buf[36]; + + memcpy (sample.envelope_rate, &buf[37], 6); + memcpy (sample.envelope_offset, &buf[43], 6); + + sample.tremolo_sweep = (unsigned char) buf[49]; + sample.tremolo_rate = (unsigned char) buf[50]; + sample.tremolo_depth = (unsigned char) buf[51]; + + sample.vibrato_sweep = (unsigned char) buf[52]; + sample.vibrato_rate = (unsigned char) buf[53]; + sample.vibrato_depth = (unsigned char) buf[54]; + sample.modes = (unsigned char) buf[55]; + sample.scale_frequency = *(short *) &buf[56]; + sample.scale_factor = *(unsigned short *) &buf[58]; + + if (print_only) + { + printf("\nSample: %03d / %s\n", i, sample.name); + printf ("Len: %d, Loop start: %d, Loop end: %d\n", sample.len, sample.loop_start, sample.loop_end); + printf ("Flags: "); + if (sample.modes & WAVE_16_BITS) + printf ("16 bit "); + if (sample.modes & WAVE_UNSIGNED) + printf ("unsigned "); + if (sample.modes & WAVE_LOOP_BACK) + printf("reverse "); + if (sample.modes & WAVE_BIDIR_LOOP) + printf("bidir "); + if (sample.modes & WAVE_LOOPING) + printf ("looping "); else printf("one_shot" ); + if (sample.modes & WAVE_SUSTAIN_ON) + printf ("sustain "); + if (sample.modes & WAVE_ENVELOPES) + printf ("enveloped "); + printf ("\n"); + + if (sample.modes & WAVE_ENVELOPES) + { + int i; + + printf ("Envelope info: "); + for (i = 0; i < 6; i++) + { + printf ("%d/%d ", sample.envelope_rate[i], + sample.envelope_offset[i]); + } + printf ("\n"); + } + + printf("Tremolo: sweep=%d, rate=%d, depth=%d\n", + sample.tremolo_sweep, + sample.tremolo_rate, + sample.tremolo_depth); + + printf("Vibrato: sweep=%d, rate=%d, depth=%d\n", + sample.vibrato_sweep, + sample.vibrato_rate, + sample.vibrato_depth); + } + + offset = offset + 96; + patch = (struct patch_info *) malloc (sizeof (*patch) + sample.len); + + patch->key = GUS_PATCH; + patch->device_no = GUS_DEV; + patch->instr_no = pgm; + patch->mode = sample.modes | WAVE_TREMOLO | + WAVE_VIBRATO | WAVE_SCALE; + patch->len = sample.len; + patch->loop_start = sample.loop_start; + patch->loop_end = sample.loop_end; + patch->base_note = sample.base_note; + patch->high_note = sample.high_note; + patch->low_note = sample.low_note; + patch->base_freq = sample.base_freq; + patch->detuning = sample.detune; + patch->panning = (sample.panning - 7) * 16; + + memcpy (patch->env_rate, sample.envelope_rate, 6); + memcpy (patch->env_offset, sample.envelope_offset, 6); + + patch->tremolo_sweep = sample.tremolo_sweep; + patch->tremolo_rate = sample.tremolo_rate; + patch->tremolo_depth = sample.tremolo_depth; + + patch->vibrato_sweep = sample.vibrato_sweep; + patch->vibrato_rate = sample.vibrato_rate; + patch->vibrato_depth = sample.vibrato_depth; + + patch->scale_frequency = sample.scale_frequency; + patch->scale_factor = sample.scale_factor; + + patch->volume = header.master_volume; + + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + exit (-1); + } + + if (!print_only) + { + if (read (patfd, patch->data, sample.len) != sample.len) + { + fprintf (stderr, "%s: Short file\n", name); + exit (-1); + } + + SEQ_WRPATCH (patch, sizeof (*patch) + sample.len); + } + + offset = offset + sample.len; + } + + exit (0); +} diff --git a/sys/i386/isa/sound/gustest/midithru.c b/sys/i386/isa/sound/gustest/midithru.c new file mode 100644 index 0000000..36e1e07 --- /dev/null +++ b/sys/i386/isa/sound/gustest/midithru.c @@ -0,0 +1,325 @@ +#include <stdio.h> +#include <sys/soundcard.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/errno.h> + +SEQ_DEFINEBUF (2048); +SEQ_PM_DEFINES; + +int seqfd, dev = 0; +unsigned char buf[100]; +int bufp; + +/* LRU list for free operators */ + +unsigned char free_list[256]; +int fhead=0, ftail=0, flen=0; + +/* LRU list for still playing notes */ + +unsigned char note_list[256]; +int nhead=0, ntail=0, nlen=0; +unsigned char oper_note[32]; + +int pgm = 0; +int num_voices; +int bender = 0; /* Initially off */ + +void +seqbuf_dump () +{ + if (_seqbufptr) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + perror ("write /dev/sequencer"); + exit (-1); + } + _seqbufptr = 0; +} + +void +stop_note(int note, int velocity) +{ + int i, op; + + op=255; + + for (i=0;i<num_voices && op==255;i++) + { + if (oper_note[i]== note) op=i; + } + + if (op==255) + { + fprintf(stderr, "Note %d off, note not started\n", note); + fprintf(stderr, "%d, %d\n", flen, nlen); + return; /* Has already been killed ??? */ + } + + SEQ_STOP_NOTE(dev, op, note, velocity); + SEQ_DUMPBUF(); + + oper_note[op] = 255; + + free_list[ftail]=op; + flen++; + ftail = (ftail+1) % num_voices; + + for (i=0;i<16;i++) + if (note_list[i] == op) note_list[i] = 255; + + while (nlen && note_list[nhead] == 255) + { + nlen--; + /* printf("Remove from note queue %d, len %d\n", nhead, nlen); */ + nhead = (nhead+1) % 256; + } +} + +void +kill_one_note() +{ + int oldest; + + if (!nlen) {fprintf(stderr, "Free list empty but no notes playing\n");return;} /* No notes playing */ + + oldest = note_list[nhead]; + nlen--; + nhead = (nhead+1) % 256; + + fprintf(stderr, "Killing oper %d, note %d\n", oldest, oper_note[oldest]); + + if (oldest== 255) return; /* Was already stopped. Why? */ + + stop_note(oper_note[oldest], 127); +} + +void +start_note(int note, int velocity) +{ + int free; + + if (!flen) kill_one_note(); + + if (!flen) {printf("** no free voices\n");return;} /* Panic??? */ + + free = free_list[fhead]; + flen--; + fhead = (fhead+1) % num_voices; + + note_list[ntail] = free; + + if (nlen>255) + { +#if 0 + fprintf(stderr, "Note list overflow %d, %d, %d\n", + nlen, nhead, ntail); +#endif + nlen=0; /* Overflow -> hard reset */ + } + nlen++; + ntail = (ntail+1) % 256; + + oper_note[free] = note; + + SEQ_SET_PATCH(dev, free, pgm); + SEQ_PITCHBEND(dev, free, bender); + SEQ_START_NOTE(dev, free, note, velocity); + SEQ_DUMPBUF(); +} + +void +channel_pressure(int ch, int pressure) +{ + int i; + + for (i=0;i<num_voices;i++) + { + if (oper_note[i] != 255) + { +#if 1 + SEQ_CHN_PRESSURE(dev, i, pressure); +#else + SEQ_EXPRESSION(dev, i, pressure); +#endif + SEQ_DUMPBUF(); + } + } +} + +void +pitch_bender(int ch, int value) +{ + int i; + + value -= 8192; + + bender = value; + + for (i=0;i<num_voices;i++) + { + if (oper_note[i] != 255) + { + bender = value; + SEQ_PITCHBEND(dev, i, value); + SEQ_DUMPBUF(); + } + } +} + +void +do_buf() +{ + int ch = buf[0] & 0x0f; + int value; + + switch (buf[0] & 0xf0) + { + case 0x90: /* Note on */ + if (bufp < 3) break; + /* printf("Note on %d %d %d\n", ch, buf[1], buf[2]); */ + if (buf[2]) + start_note(buf[1], buf[2]); + else + stop_note(buf[1], buf[2]); + bufp=1; + break; + + case 0xb0: /* Control change */ + if (bufp < 3) break; + /* printf("Control change %d %d %d\n", ch, buf[1], buf[2]); */ + bufp=1; + break; + + case 0x80: /* Note off */ + if (bufp < 3) break; + /* printf("Note off %d %d %d\n", ch, buf[1], buf[2]); */ + stop_note(buf[1], buf[2]); + bufp=1; + break; + + case 0xe0: /* Pitch bender */ + if (bufp < 3) break; + value = ((buf[2] & 0x7f) << 7) | (buf[1] & 0x7f); + /* printf("Pitch bender %d %d\n", ch, value >> 7); */ + pitch_bender(ch, value); + bufp=1; + break; + + case 0xc0: /* Pgm change */ + if (bufp < 2) break; + /* printf("Pgm change %d %d\n", ch, buf[1]); */ + pgm = buf[1]; + if (PM_LOAD_PATCH(dev, 0, pgm) < 0) + if (errno != ESRCH) /* No such process */ + perror("PM_LOAD_PATCH"); + bufp=0; + break; + + case 0xd0: /* Channel pressure */ + if (bufp < 2) break; + /* printf("Channel pressure %d %d\n", ch, buf[1]); */ + channel_pressure(ch, buf[1]); + bufp=1; + break; + + default: + bufp=0; + } +} + +int +main (int argc, char *argv[]) +{ + int i, n, max_voice = 999; + + struct synth_info info; + + unsigned char ev[4], *p; + + if (argc >= 2) dev = atoi(argv[1]); + + for (i=0;i<16;i++) oper_note[i] = 255; + + if ((seqfd = open ("/dev/sequencer", O_RDWR, 0)) == -1) + { + perror ("open /dev/sequencer"); + exit (-1); + } + + if (argc >= 3) + { + int d = dev; + ioctl(seqfd, SNDCTL_FM_4OP_ENABLE, &d); + } + + info.device = dev; + + if (ioctl(seqfd, SNDCTL_SYNTH_INFO, &info)==-1) + { + perror ("info /dev/sequencer"); + exit (-1); + } + + num_voices = info.nr_voices; + if (num_voices>max_voice)num_voices = max_voice; + fprintf(stderr, "Output to synth device %d (%s)\n", dev, info.name); + fprintf(stderr, "%d voices available\n", num_voices); + + for (i=0;i<num_voices;i++) + { + flen++; + free_list[fhead] = i; + fhead = (fhead+1) % num_voices; + } + + bufp = 0; + if (PM_LOAD_PATCH(dev, 0, 0) < 0) /* Load the default instrument */ + if (errno != ESRCH) /* No such process */ + perror("PM_LOAD_PATCH"); + + while (1) + { + if ((n = read (seqfd, ev, sizeof (ev))) == -1) + { + perror ("read /dev/sequencer"); + exit (-1); + } + + for (i = 0; i <= (n / 4); i++) + { + p = &ev[i * 4]; + + if (p[0] == SEQ_MIDIPUTC && p[2] == 0 /* Midi if# == 0 */) + { +/* printf("%02x ", p[1]);fflush(stdout); */ + if (p[1] & 0x80) /* Status */ + { + if (bufp) + do_buf (); + buf[0] = p[1]; + bufp = 1; + } + else if (bufp) + { + buf[bufp++] = p[1]; + if ((buf[0] & 0xf0) == 0x90 || (buf[0] & 0xf0) == 0x80 || (buf[0] & 0xf0) == 0xb0 || + (buf[0] & 0xf0) == 0xe0) + { + if (bufp == 3) + do_buf (); + } + else + if ((buf[0] & 0xf0) == 0xc0 || (buf[0] & 0xf0) == 0xd0) + { + if (bufp == 2) do_buf(); + } + } + } + } + } + + exit (0); +} diff --git a/sys/i386/isa/sound/gustest/pmtest.c b/sys/i386/isa/sound/gustest/pmtest.c new file mode 100644 index 0000000..3b6b1d5 --- /dev/null +++ b/sys/i386/isa/sound/gustest/pmtest.c @@ -0,0 +1,411 @@ +/* + * CAUTION! This program is just an incompletely implemented version + * of the patch manager daemon for GUS. Using this program + * with the driver version 1.99.9 will hang your system + * completely (sooner or later). + * + * This program is for information only. The final + * implementation of the patch manager will not be + * compatible with this one. + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/soundcard.h> +#include <sys/ultrasound.h> +#include <strings.h> +#include <sys/errno.h> +#include "gmidi.h" + +#ifndef PATCH_PATH +#define PATCH_PATH "/D/ultrasnd/midi" +#endif + +char loadmap[256] = +{0}; /* 1 if the patch is already loaded */ + +struct pat_header + { + char magic[12]; + char version[10]; + char description[60]; + unsigned char instruments; + char voices; + char channels; + unsigned short nr_waveforms; + unsigned short master_volume; + unsigned long data_size; + }; + +struct sample_header + { + char name[7]; + unsigned char fractions; + long len; + long loop_start; + long loop_end; + unsigned short base_freq; + long low_note; + long high_note; + long base_note; + short detune; + unsigned char panning; + + unsigned char envelope_rate[6]; + unsigned char envelope_offset[6]; + + unsigned char tremolo_sweep; + unsigned char tremolo_rate; + unsigned char tremolo_depth; + + unsigned char vibrato_sweep; + unsigned char vibrato_rate; + unsigned char vibrato_depth; + + char modes; + + short scale_frequency; + unsigned short scale_factor; + }; +int seqfd = 0, gus_dev = -1; + +struct patch_info *patch; + +int +do_load_patch (struct patmgr_info *rec) +{ + int i, patfd, pgm, print_only = 0; + struct pat_header header; + struct sample_header sample; + char buf[256]; + char name[256]; + long offset; + + pgm = rec->data.data8[0]; + + if (loadmap[pgm]) + return 0; /* Already loaded */ + + sprintf (name, PATCH_PATH "/%s.pat", patch_names[pgm]); + + if ((patfd = open (name, O_RDONLY, 0)) == -1) + { + perror (name); + return errno; + } + + if (read (patfd, buf, 0xef) != 0xef) + { + fprintf (stderr, "%s: Short file\n", name); + return EIO; + } + + memcpy ((char *) &header, buf, sizeof (header)); + + if (strncmp (header.magic, "GF1PATCH110", 12)) + { + fprintf (stderr, "%s: Not a patch file\n", name); + return EINVAL; + } + + if (strncmp (header.version, "ID#000002", 10)) + { + fprintf (stderr, "%s: Incompatible patch file version\n", name); + return EINVAL; + } + + header.nr_waveforms = *(unsigned short *) &buf[85]; + header.master_volume = *(unsigned short *) &buf[87]; + + printf ("GUS: Loading: %s\n", name); + + offset = 0xef; + + for (i = 0; i < header.nr_waveforms; i++) + { + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + return errno; + } + + if (read (patfd, &buf, sizeof (sample)) != sizeof (sample)) + { + fprintf (stderr, "%s: Short file\n", name); + return EIO; + } + + memcpy ((char *) &sample, buf, sizeof (sample)); + + /* + * Since some fields of the patch record are not 32bit aligned, we must + * handle them specially. + */ + sample.low_note = *(long *) &buf[22]; + sample.high_note = *(long *) &buf[26]; + sample.base_note = *(long *) &buf[30]; + sample.detune = *(short *) &buf[34]; + sample.panning = (unsigned char) buf[36]; + + memcpy (sample.envelope_rate, &buf[37], 6); + memcpy (sample.envelope_offset, &buf[43], 6); + + sample.tremolo_sweep = (unsigned char) buf[49]; + sample.tremolo_rate = (unsigned char) buf[50]; + sample.tremolo_depth = (unsigned char) buf[51]; + + sample.vibrato_sweep = (unsigned char) buf[52]; + sample.vibrato_rate = (unsigned char) buf[53]; + sample.vibrato_depth = (unsigned char) buf[54]; + sample.modes = (unsigned char) buf[55]; + sample.scale_frequency = *(short *) &buf[56]; + sample.scale_factor = *(unsigned short *) &buf[58]; + + if (print_only) + { + printf ("\nSample: %03d / %s\n", i, sample.name); + printf ("Len: %d, Loop start: %d, Loop end: %d\n", sample.len, sample.loop_start, sample.loop_end); + printf ("Flags: "); + if (sample.modes & WAVE_16_BITS) + printf ("16 bit "); + if (sample.modes & WAVE_UNSIGNED) + printf ("unsigned "); + if (sample.modes & WAVE_LOOP_BACK) + printf ("reverse "); + if (sample.modes & WAVE_BIDIR_LOOP) + printf ("bidir "); + if (sample.modes & WAVE_LOOPING) + printf ("looping "); + else + printf ("one_shot"); + if (sample.modes & WAVE_SUSTAIN_ON) + printf ("sustain "); + if (sample.modes & WAVE_ENVELOPES) + printf ("enveloped "); + printf ("\n"); + + if (sample.modes & WAVE_ENVELOPES) + { + int i; + + printf ("Envelope info: "); + for (i = 0; i < 6; i++) + { + printf ("%d/%d ", sample.envelope_rate[i], + sample.envelope_offset[i]); + } + printf ("\n"); + } + + printf ("Tremolo: sweep=%d, rate=%d, depth=%d\n", + sample.tremolo_sweep, + sample.tremolo_rate, + sample.tremolo_depth); + + printf ("Vibrato: sweep=%d, rate=%d, depth=%d\n", + sample.vibrato_sweep, + sample.vibrato_rate, + sample.vibrato_depth); + } + + offset = offset + 96; + patch = (struct patch_info *) malloc (sizeof (*patch) + sample.len); + + patch->key = GUS_PATCH; + patch->device_no = gus_dev; + patch->instr_no = pgm; + patch->mode = sample.modes | WAVE_TREMOLO | + WAVE_VIBRATO | WAVE_SCALE; + patch->len = sample.len; + patch->loop_start = sample.loop_start; + patch->loop_end = sample.loop_end; + patch->base_note = sample.base_note; + patch->high_note = sample.high_note; + patch->low_note = sample.low_note; + patch->base_freq = sample.base_freq; + patch->detuning = sample.detune; + patch->panning = (sample.panning - 7) * 16; + + memcpy (patch->env_rate, sample.envelope_rate, 6); + memcpy (patch->env_offset, sample.envelope_offset, 6); + + patch->tremolo_sweep = sample.tremolo_sweep; + patch->tremolo_rate = sample.tremolo_rate; + patch->tremolo_depth = sample.tremolo_depth; + + patch->vibrato_sweep = sample.vibrato_sweep; + patch->vibrato_rate = sample.vibrato_rate; + patch->vibrato_depth = sample.vibrato_depth; + + patch->scale_frequency = sample.scale_frequency; + patch->scale_factor = sample.scale_factor; + + patch->volume = header.master_volume; + + if (lseek (patfd, offset, 0) == -1) + { + perror (name); + return errno; + } + + if (!print_only) + { + if (read (patfd, patch->data, sample.len) != sample.len) + { + fprintf (stderr, "%s: Short file\n", name); + return EIO; + } + + if (write (seqfd, patch, sizeof (*patch) + sample.len) == -1) + { + perror ("/dev/pmgr0"); + return errno; + } + } + + offset = offset + sample.len; + } + + loadmap[pgm] = 1; + return 0; +} + +int +main (int argc, char *argv[]) +{ + struct patmgr_info inf; + int err, i, n; + struct synth_info info; + + if ((seqfd = open ("/dev/patmgr0", O_RDWR, 0)) == -1) + { + fprintf (stderr, "Cannot open\n"); + perror ("/dev/patmgr0"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_NRSYNTHS, &n) == -1) + { + perror ("NRSYNTH: /dev/patmgr0"); + exit (-1); + } + + for (i = 0; i < n; i++) + { + info.device = i; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + { + perror ("SYNTH_INFO: /dev/patmgr0"); + exit (-1); + } + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_GUS) + gus_dev = i; + } + + if (gus_dev == -1) + { + fprintf (stderr, "Error: Gravis Ultrasound not detected\n"); + exit (-1); + } + + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + perror ("Sample reset"); + + for (i = 0; i < 256; i++) + loadmap[i] = 0; + + while (1) + { + if (read (seqfd, (char *) &inf, sizeof (inf)) != sizeof (inf)) + { + perror ("Read"); + exit (-1); + } + + if (inf.key == PM_K_EVENT) + switch (inf.command) + { + case PM_E_OPENED: + printf ("Opened\n"); + break; + + case PM_E_CLOSED: + printf ("Closed\n"); + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + perror ("Sample reset"); + for (i = 0; i < 256; i++) + loadmap[i] = 0; + break; + + case PM_E_PATCH_RESET: + printf ("Patch reset called\n"); + for (i = 0; i < 256; i++) + loadmap[i] = 0; + break; + + case PM_E_PATCH_LOADED: + printf ("Patch loaded by client\n"); + break; + + default: + printf ("Unknown event %d\n", inf.command); + inf.key = PM_ERROR; + inf.parm1 = EINVAL; + } + else if (inf.key == PM_K_COMMAND) + switch (inf.command) + { + case _PM_LOAD_PATCH: + if ((err = do_load_patch (&inf))) + if (err == ENOSPC) + { + if (ioctl (seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) + { + perror ("Sample reset"); + return errno; + } + + for (i = 0; i < 256; i++) + loadmap[i] = 0; + err = do_load_patch (&inf); + } + + if (err) + { + inf.key = PM_ERROR; + inf.parm1 = err; + printf("Error = %d\n", err); + } + else + { + inf.key = PM_K_COMMAND; + inf.parm1 = 0; + } + break; + + default: + printf ("Unknown command %d\n", inf.command); + inf.key = PM_ERROR; + inf.parm1 = EINVAL; + } + else + { + printf ("Unknown event %d/%d\n", inf.key, inf.command); + inf.key = PM_ERROR; + inf.parm1 = EINVAL; + } + + if (write (seqfd, (char *) &inf, sizeof (inf)) != sizeof (inf)) + { + perror ("write"); + exit (-1); + } + } + + exit (0); +} diff --git a/sys/i386/isa/sound/local.h b/sys/i386/isa/sound/local.h new file mode 100644 index 0000000..f705863 --- /dev/null +++ b/sys/i386/isa/sound/local.h @@ -0,0 +1,17 @@ +/* Generated by configure. Don't edit!!!! */ + +#define KERNEL_SOUNDCARD +#undef EXCLUDE_PAS +#undef EXCLUDE_SB +#undef EXCLUDE_ADLIB +#undef EXCLUDE_GUS +#define EXCLUDE_MPU401 /* Not ready yet */ +#undef EXCLUDE_SBPRO +#undef EXCLUDE_AUDIO +#undef EXCLUDE_MIDI +#undef EXCLUDE_YM3812 +#undef EXCLUDE_SEQUENCER +#undef EXCLUDE_CHIP_MIDI + +#define DSP_BUFFSIZE 32768 +#define SELECTED_SOUND_OPTIONS 0xffffffff diff --git a/sys/i386/isa/sound/midi.c b/sys/i386/isa/sound/midi.c new file mode 100644 index 0000000..83088d2 --- /dev/null +++ b/sys/i386/isa/sound/midi.c @@ -0,0 +1,176 @@ +/* UWM - comments to soft-eng@cs.uwm.edu */ +#define _MIDI_TABLE_C_ +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#ifndef EXCLUDE_CHIP_MIDI + + +static int generic_midi_busy[MAX_MIDI_DEV]; + +long CMIDI_init (long mem_start) +{ + + int i; + int n = num_midi_drivers; + /* int n = sizeof (midi_supported) / sizeof( struct generic_midi_info ); + */ + for (i = 0; i < n; i++) + { + if ( midi_supported[i].attach (mem_start) ) + { + printk("MIDI: Successfully attached %s\n",midi_supported[i].name); + } + + } + return (mem_start); +} + + +int +CMIDI_open (int dev, struct fileinfo *file) +{ + + int mode, err, retval; + + dev = dev >> 4; + + mode = file->mode & O_ACCMODE; + + + if (generic_midi_busy[dev]) + return (RET_ERROR(EBUSY)); + + + if (dev >= num_generic_midis) + { + printk(" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + if (!generic_midi_devs[dev]) + { + printk(" MIDI device %d not initialized\n",dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + retval = generic_midi_devs[dev]->open (dev, mode) ; + + /* If everything ok, set device as busy */ + + if ( retval >= 0 ) + generic_midi_busy[dev] = 1; + + return ( retval ); + +} + +int +CMIDI_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + + int retval; + + dev = dev >> 4; + + if (dev >= num_generic_midis) + { + printk(" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + /* Make double sure of healthiness -- doubt + * Need we check this again?? + * + */ + + if (!generic_midi_devs[dev]) + { + printk(" MIDI device %d not initialized\n",dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + retval = generic_midi_devs[dev]->write (dev, buf); + + return ( retval ); + +} + +int +CMIDI_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count) +{ + int retval; + + dev = dev >> 4; + + if (dev >= num_generic_midis) + { + printk(" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + /* Make double sure of healthiness -- doubt + * Need we check this again?? + * + */ + + if (!generic_midi_devs[dev]) + { + printk(" MIDI device %d not initialized\n",dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + retval = generic_midi_devs[dev]->read(dev,buf); + + return (retval); + +} + +int +CMIDI_close (int dev, struct fileinfo *file) +{ + + int retval; + dev = dev >> 4; + + if (dev >= num_generic_midis) + { + printk(" MIDI device %d not installed.\n", dev); + return (ENXIO); + } + + /* Make double sure of healthiness -- doubt + * Need we check this again?? + * + */ + + if (!generic_midi_devs[dev]) + { + printk(" MIDI device %d not initialized\n",dev); + return (ENXIO); + } + + /* If all good and healthy, go ahead and issue call! */ + + + generic_midi_devs[dev]->close(dev); + + generic_midi_busy[dev] = 0; /* Free the device */ + + return (0) ; + +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/midibuf.c b/sys/i386/isa/sound/midibuf.c new file mode 100644 index 0000000..ca1bdf7 --- /dev/null +++ b/sys/i386/isa/sound/midibuf.c @@ -0,0 +1,105 @@ +/* + * linux/kernel/chr_drv/sound/midibuf.c + * + * Device file manager for /dev/midi + * + * NOTE! This part of the driver is currently just a stub. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + * + * Based on the Midi driver for bsd386 by Mike Durian. + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_MPU401) + +#if 0 +#include "midiioctl.h" +#include "midivar.h" +#endif + +static int midibuf_busy = 0; + +int +MIDIbuf_open (int dev, struct fileinfo *file) +{ + int mode, err; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + if (midibuf_busy) + return RET_ERROR (EBUSY); + + if (!mpu401_dev) + { + printk ("Midi: MPU-401 compatible Midi interface not present\n"); + return RET_ERROR (ENXIO); + } + + if ((err = midi_devs[mpu401_dev]->open (mpu401_dev, mode)) < 0) + return err; + + midibuf_busy = 1; + + return RET_ERROR (ENXIO); +} + +void +MIDIbuf_release (int dev, struct fileinfo *file) +{ + int mode; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + midi_devs[mpu401_dev]->close (mpu401_dev); + midibuf_busy = 0; +} + +int +MIDIbuf_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + + dev = dev >> 4; + + return count; +} + + +int +MIDIbuf_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + dev = dev >> 4; + + return RET_ERROR (EIO); +} + +int +MIDIbuf_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + dev = dev >> 4; + + switch (cmd) + { + + default: + return midi_devs[0]->ioctl (dev, cmd, arg); + } +} + +void +MIDIbuf_bytes_received (int dev, unsigned char *buf, int count) +{ +} + +long +MIDIbuf_init (long mem_start) +{ + return mem_start; +} + +#endif diff --git a/sys/i386/isa/sound/mpu401.c b/sys/i386/isa/sound/mpu401.c new file mode 100644 index 0000000..6c3ef9a --- /dev/null +++ b/sys/i386/isa/sound/mpu401.c @@ -0,0 +1,332 @@ +/* + * linux/kernel/chr_drv/sound/mpu401.c + * + * The low level driver for Roland MPU-401 compatible Midi cards. + * + * This version supports just the DUMB UART mode. + * + * (C) 1993 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#if !defined(EXCLUDE_MPU401) && !defined(EXCLUDE_MIDI) + +#define DATAPORT (mpu401_base)/* MPU-401 Data I/O Port on IBM */ +#define COMDPORT (mpu401_base+1) /* MPU-401 Command Port on IBM */ +#define STATPORT (mpu401_base+1) /* MPU-401 Status Port on IBM */ + +#define mpu401_status() INB(STATPORT) +#define input_avail() (!(mpu401_status()&INPUT_AVAIL)) +#define output_ready() (!(mpu401_status()&OUTPUT_READY)) +#define mpu401_cmd(cmd) OUTB(cmd, COMDPORT) +#define mpu401_read() INB(DATAPORT) +#define mpu401_write(byte) OUTB(byte, DATAPORT) + +#define OUTPUT_READY 0x40 /* Mask for Data Read Redy Bit */ +#define INPUT_AVAIL 0x80 /* Mask for Data Send Ready Bit */ +#define MPU_ACK 0xFE /* MPU-401 Acknowledge Response */ +#define MPU_RESET 0xFF /* MPU-401 Total Reset Command */ +#define UART_MODE_ON 0x3F /* MPU-401 "Dumb UART Mode" */ + +static int mpu401_opened = 0; +static int mpu401_base = 0x330; +static int mpu401_irq; +static int mpu401_detected = 0; +static int my_dev; + +static int reset_mpu401 (void); + +static void +mpu401_input_loop (void) +{ + int count; + + count = 10; + + while (count) /* Not timed out */ + if (input_avail ()) + { + unsigned char c = mpu401_read (); + + count = 100; + + if (mpu401_opened & OPEN_READ) + sequencer_midi_input (my_dev, c); + } + else + while (!input_avail () && count) + count--; +} + +void +mpuintr (int unit) +{ + unsigned char c; + + if (input_avail ()) + mpu401_input_loop (); +} + +/* + * It looks like there is no input interrupts in the UART mode. Let's try + * polling. + */ + +static void +poll_mpu401 (unsigned long dummy) +{ + unsigned long flags; + + static struct timer_list mpu401_timer = + {NULL, 0, 0, poll_mpu401}; + + if (!(mpu401_opened & OPEN_READ)) + return; /* No longer required */ + + DISABLE_INTR (flags); + + if (input_avail ()) + mpu401_input_loop (); + + mpu401_timer.expires = 1; + add_timer (&mpu401_timer); /* Come back later */ + + RESTORE_INTR (flags); +} + +static int +set_mpu401_irq (int interrupt_level) +{ + int retcode; + +#ifdef linux + struct sigaction sa; + + sa.sa_handler = mpuintr; + +#ifdef SND_SA_INTERRUPT + sa.sa_flags = SA_INTERRUPT; +#else + sa.sa_flags = 0; +#endif + + sa.sa_mask = 0; + sa.sa_restorer = NULL; + + retcode = irqaction (interrupt_level, &sa); + + if (retcode < 0) + { + printk ("MPU-401: IRQ%d already in use\n", interrupt_level); + } + +#else + /* # error Unimplemented for this OS */ +#endif + return retcode; +} + +static int +mpu401_open (int dev, int mode) +{ + if (mpu401_opened) + { + printk ("MPU-401: Midi busy\n"); + return RET_ERROR (EBUSY); + } + + mpu401_input_loop (); + + mpu401_opened = mode; + poll_mpu401 (0); /* Enable input polling */ + + return 0; +} + +static void +mpu401_close (int dev) +{ + mpu401_opened = 0; +} + +static int +mpu401_out (int dev, unsigned char midi_byte) +{ + int timeout; + unsigned long flags; + + /* + * Test for input since pending input seems to block the output. + */ + + DISABLE_INTR (flags); + + if (input_avail ()) + mpu401_input_loop (); + + RESTORE_INTR (flags); + + /* + * Sometimes it takes about 13000 loops before the output becomes ready + * (After reset). Normally it takes just about 10 loops. + */ + + for (timeout = 30000; timeout > 0 && !output_ready (); timeout--); /* Wait */ + + if (!output_ready ()) + { + printk ("MPU-401: Timeout\n"); + return 0; + } + + mpu401_write (midi_byte); + return 1; +} + +static int +mpu401_command (int dev, unsigned char midi_byte) +{ + return 1; +} + +static int +mpu401_start_read (int dev) +{ + return 0; +} + +static int +mpu401_end_read (int dev) +{ + return 0; +} + +static int +mpu401_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EINVAL); +} + +static void +mpu401_kick (int dev) +{ +} + +static int +mpu401_buffer_status (int dev) +{ + return 0; /* No data in buffers */ +} + +static struct midi_operations mpu401_operations = +{ + {"MPU-401", 0}, + mpu401_open, + mpu401_close, + mpu401_ioctl, + mpu401_out, + mpu401_start_read, + mpu401_end_read, + mpu401_kick, + mpu401_command, + mpu401_buffer_status +}; + + +long +attach_mpu401 (long mem_start, struct address_info *hw_config) +{ + int ok, timeout; + unsigned long flags; + + mpu401_base = hw_config->io_base; + mpu401_irq = hw_config->irq; + + if (!mpu401_detected) + return RET_ERROR (EIO); + + DISABLE_INTR (flags); + for (timeout = 30000; timeout < 0 && !output_ready (); timeout--); /* Wait */ + mpu401_cmd (UART_MODE_ON); + + ok = 0; + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (input_avail ()) + if (mpu401_read () == MPU_ACK) + ok = 1; + + RESTORE_INTR (flags); + + printk (" <Roland MPU-401>"); + + my_dev = num_midis; + mpu401_dev = num_midis; + midi_devs[num_midis++] = &mpu401_operations; + return mem_start; +} + +static int +reset_mpu401 (void) +{ + unsigned long flags; + int ok, timeout, n; + + /* + * Send the RESET command. Try twice if no success at the first time. + */ + + ok = 0; + + DISABLE_INTR (flags); + + for (n = 0; n < 2 && !ok; n++) + { + for (timeout = 30000; timeout < 0 && !output_ready (); timeout--); /* Wait */ + mpu401_cmd (MPU_RESET); /* Send MPU-401 RESET Command */ + + /* + * Wait at least 25 msec. This method is not accurate so let's make the + * loop bit longer. Cannot sleep since this is called during boot. + */ + + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (input_avail ()) + if (mpu401_read () == MPU_ACK) + ok = 1; + + } + + mpu401_opened = 0; + if (ok) + mpu401_input_loop (); /* Flush input before enabling interrupts */ + + RESTORE_INTR (flags); + + return ok; +} + + +int +probe_mpu401 (struct address_info *hw_config) +{ + int ok = 0; + + mpu401_base = hw_config->io_base; + mpu401_irq = hw_config->irq; + + if (set_mpu401_irq (mpu401_irq) < 0) + return 0; + + ok = reset_mpu401 (); + + mpu401_detected = ok; + return ok; +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/opl3.c b/sys/i386/isa/sound/opl3.c new file mode 100644 index 0000000..c0d70e2 --- /dev/null +++ b/sys/i386/isa/sound/opl3.c @@ -0,0 +1,913 @@ +/* + * linux/kernel/chr_drv/sound/opl3.c + * + * A low level driver for Yamaha YM3812 and OPL-3 -chips + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +/* Major improvements to the FM handling 30AUG92 by Rob Hooft, */ +/* hooft@chem.ruu.nl */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_YM3812) + +#include "opl3.h" + +#define MAX_VOICE 18 +#define OFFS_4OP 11 /* Definitions for the operators OP3 and OP4 + * begin here */ + +static int opl3_enabled = 0; +static int left_address = 0x388, right_address = 0x388, both_address = 0; + +static int nr_voices = 9; +static int logical_voices[MAX_VOICE] = +{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}; + +struct voice_info + { + unsigned char keyon_byte; + long bender; + long bender_range; + unsigned long orig_freq; + unsigned long current_freq; + int mode; + }; + +static struct voice_info voices[MAX_VOICE]; + +typedef struct sbi_instrument instr_array[SBFM_MAXINSTR]; +static instr_array instrmap; +static struct sbi_instrument *active_instrument[MAX_VOICE] = +{NULL}; + +static struct synth_info fm_info = +{"AdLib", 0, SYNTH_TYPE_FM, FM_TYPE_ADLIB, 0, 9, 0, SBFM_MAXINSTR, 0}; + +static int already_initialized = 0; + +static int opl3_ok = 0; +static int opl3_busy = 0; +static int fm_model = 0; /* 0=no fm, 1=mono, 2=SB Pro 1, 3=SB Pro 2 */ + +static int store_instr (int instr_no, struct sbi_instrument *instr); +static void freq_to_fnum (int freq, int *block, int *fnum); +static void opl3_command (int io_addr, const unsigned char addr, const unsigned char val); +static int opl3_kill_note (int dev, int voice, int velocity); +static unsigned char connection_mask = 0x00; + +void +enable_opl3_mode (int left, int right, int both) +{ + opl3_enabled = 1; + left_address = left; + right_address = right; + both_address = both; + fm_info.capabilities = SYNTH_CAP_OPL3; + fm_info.synth_subtype = FM_TYPE_OPL3; +} + +static void +enter_4op_mode (void) +{ + int i; + static int voices_4op[MAX_VOICE] = + {0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17}; + + connection_mask = 0x3f; + opl3_command (right_address, CONNECTION_SELECT_REGISTER, 0x3f); /* Select all 4-OP + * voices */ + for (i = 0; i < 3; i++) + physical_voices[i].voice_mode = 4; + for (i = 3; i < 6; i++) + physical_voices[i].voice_mode = 0; + + for (i = 9; i < 12; i++) + physical_voices[i].voice_mode = 4; + for (i = 12; i < 15; i++) + physical_voices[i].voice_mode = 0; + + for (i = 0; i < 12; i++) + logical_voices[i] = voices_4op[i]; + nr_voices = 6; +} + +static int +opl3_ioctl (int dev, + unsigned int cmd, unsigned int arg) +{ + switch (cmd) + { + + case SNDCTL_FM_LOAD_INSTR: + { + struct sbi_instrument ins; + + IOCTL_FROM_USER ((char *) &ins, (char *) arg, 0, sizeof (ins)); + + if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) + { + printk ("FM Error: Invalid instrument number %d\n", ins.channel); + return RET_ERROR (EINVAL); + } + + pmgr_inform (dev, PM_E_PATCH_LOADED, ins.channel, 0, 0, 0); + return store_instr (ins.channel, &ins); + } + break; + + case SNDCTL_SYNTH_INFO: + fm_info.nr_voices = nr_voices; + + IOCTL_TO_USER ((char *) arg, 0, &fm_info, sizeof (fm_info)); + return 0; + break; + + case SNDCTL_SYNTH_MEMAVL: + return 0x7fffffff; + break; + + case SNDCTL_FM_4OP_ENABLE: + if (opl3_enabled) + enter_4op_mode (); + return 0; + break; + + default: + return RET_ERROR (EINVAL); + } + +} + +int +opl3_detect (int ioaddr) +{ + /* + * This function returns 1 if the FM chicp 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; + int i; + + if (already_initialized) + { + return 0; /* Do avoid duplicate initializations */ + } + + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); /* Reset timers 1 and 2 */ + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, IRQ_RESET); /* Reset the IRQ of FM + * chicp */ + + stat1 = INB (ioaddr); /* Read status register */ + + if ((stat1 & 0xE0) != 0x00) + { + return 0; /* Should be 0x00 */ + } + + opl3_command (ioaddr, TIMER1_REGISTER, 0xff); /* Set timer 1 to 0xff */ + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, + TIMER2_MASK | TIMER1_START); /* Unmask and start timer 1 */ + + /* + * Now we have to delay at least 80 msec + */ + + for (i = 0; i < 50; i++) + tenmicrosec (); /* To be sure */ + + stat2 = INB (ioaddr); /* Read status after timers have expired */ + + /* Stop the timers */ + + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); /* Reset timers 1 and 2 */ + opl3_command (ioaddr, TIMER_CONTROL_REGISTER, IRQ_RESET); /* Reset the IRQ of FM + * chicp */ + + if ((stat2 & 0xE0) != 0xc0) + { + return 0; /* There is no YM3812 */ + } + + /* There is a FM chicp in this address. Now set some default values. */ + + for (i = 0; i < 9; i++) + opl3_command (ioaddr, KEYON_BLOCK + i, 0); /* Note off */ + + opl3_command (ioaddr, TEST_REGISTER, ENABLE_WAVE_SELECT); + opl3_command (ioaddr, PERCUSSION_REGISTER, 0x00); /* Melodic mode. */ + + return 1; +} + +static int +opl3_kill_note (int dev, int voice, int velocity) +{ + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return 0; + + map = &physical_voices[logical_voices[voice]]; + + DEB (printk ("Kill note %d\n", voice)); + + if (map->voice_mode == 0) + return 0; + + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, voices[voice].keyon_byte & ~0x20); + + voices[voice].keyon_byte = 0; + voices[voice].bender = 0; + voices[voice].bender_range = 200; /* 200 cents = 2 semitones */ + voices[voice].orig_freq = 0; + voices[voice].current_freq = 0; + voices[voice].mode = 0; + + return 0; +} + +#define HIHAT 0 +#define CYMBAL 1 +#define TOMTOM 2 +#define SNARE 3 +#define BDRUM 4 +#define UNDEFINED TOMTOM +#define DEFAULT TOMTOM + +static int +store_instr (int instr_no, struct sbi_instrument *instr) +{ + + if (instr->key != FM_PATCH && (instr->key != OPL3_PATCH || !opl3_enabled)) + printk ("FM warning: Invalid patch format field (key) 0x%04x\n", instr->key); + memcpy ((char *) &(instrmap[instr_no]), (char *) instr, sizeof (*instr)); + + return 0; +} + +static int +opl3_set_instr (int dev, int voice, int instr_no) +{ + if (voice < 0 || voice >= nr_voices) + return 0; + + if (instr_no < 0 || instr_no >= SBFM_MAXINSTR) + return 0; + + active_instrument[voice] = &instrmap[instr_no]; + return 0; +} + +/* + * 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. (RH) + */ +char fm_volume_table[128] = +{-64, -48, -40, -35, -32, -29, -27, -26, /* 0 - 7 */ + -24, -23, -21, -20, -19, -18, -18, -17, /* 8 - 15 */ + -16, -15, -15, -14, -13, -13, -12, -12, /* 16 - 23 */ + -11, -11, -10, -10, -10, -9, -9, -8, /* 24 - 31 */ + -8, -8, -7, -7, -7, -6, -6, -6,/* 32 - 39 */ + -5, -5, -5, -5, -4, -4, -4, -4,/* 40 - 47 */ + -3, -3, -3, -3, -2, -2, -2, -2,/* 48 - 55 */ + -2, -1, -1, -1, -1, 0, 0, 0, /* 56 - 63 */ + 0, 0, 0, 1, 1, 1, 1, 1, /* 64 - 71 */ + 1, 2, 2, 2, 2, 2, 2, 2, /* 72 - 79 */ + 3, 3, 3, 3, 3, 3, 3, 4, /* 80 - 87 */ + 4, 4, 4, 4, 4, 4, 4, 5, /* 88 - 95 */ + 5, 5, 5, 5, 5, 5, 5, 5, /* 96 - 103 */ + 6, 6, 6, 6, 6, 6, 6, 6, /* 104 - 111 */ + 6, 7, 7, 7, 7, 7, 7, 7, /* 112 - 119 */ + 7, 7, 7, 8, 8, 8, 8, 8}; /* 120 - 127 */ + +static void +calc_vol (unsigned char *regbyte, int volume) +{ + int level = (~*regbyte & 0x3f); + + if (level) + level += fm_volume_table[volume]; + + if (level > 0x3f) + level = 0x3f; + if (level < 0) + level = 0; + + *regbyte = (*regbyte & 0xc0) | (~level & 0x3f); +} + +static void +set_voice_volume (int voice, int volume) +{ + unsigned char vol1, vol2, vol3, vol4; + struct sbi_instrument *instr; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return; + + map = &physical_voices[logical_voices[voice]]; + + instr = active_instrument[voice]; + + if (!instr) + instr = &instrmap[0]; + + if (instr->channel < 0) + return; + + if (voices[voice].mode == 0) + return; + + if (voices[voice].mode == 2) + { /* 2 OP voice */ + + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + + if ((instr->operators[10] & 0x01)) + { /* Additive synthesis */ + calc_vol (&vol1, volume); + calc_vol (&vol2, volume); + } + else + { /* FM synthesis */ + calc_vol (&vol2, volume); + } + + opl3_command (map->ioaddr, KSL_LEVEL + map->op[0], vol1); /* Modulator volume */ + opl3_command (map->ioaddr, KSL_LEVEL + map->op[1], vol2); /* Carrier volume */ + } + else + { /* 4 OP voice */ + int connection; + + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + vol3 = instr->operators[OFFS_4OP + 2]; + vol4 = instr->operators[OFFS_4OP + 3]; + + /* + * The connection method for 4 OP voices is defined by the rightmost + * bits at the offsets 10 and 10+OFFS_4OP + */ + + connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01); + + switch (connection) + { + case 0: + calc_vol (&vol4, volume); /* Just the OP 4 is carrier */ + break; + + case 1: + calc_vol (&vol2, volume); + calc_vol (&vol4, volume); + break; + + case 2: + calc_vol (&vol1, volume); + calc_vol (&vol4, volume); + break; + + case 3: + calc_vol (&vol1, volume); + calc_vol (&vol3, volume); + calc_vol (&vol4, volume); + break; + + default:/* Why ?? */ ; + } + + opl3_command (map->ioaddr, KSL_LEVEL + map->op[0], vol1); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[1], vol2); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[2], vol3); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[3], vol4); + } +} + +static int +opl3_start_note (int dev, int voice, int note, int volume) +{ + unsigned char data; + int block, fnum, freq, voice_mode; + struct sbi_instrument *instr; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return 0; + + map = &physical_voices[logical_voices[voice]]; + + if (map->voice_mode == 0) + return 0; + + if (note == 255) /* Just change the volume */ + { + set_voice_volume (voice, volume); + return 0; + } + + /* Kill previous note before playing */ + opl3_command (map->ioaddr, KSL_LEVEL + map->op[1], 0xff); /* Carrier volume to min */ + opl3_command (map->ioaddr, KSL_LEVEL + map->op[0], 0xff); /* Modulator volume to */ + + if (map->voice_mode == 4) + { + opl3_command (map->ioaddr, KSL_LEVEL + map->op[2], 0xff); + opl3_command (map->ioaddr, KSL_LEVEL + map->op[3], 0xff); + } + + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, 0x00); /* Note off */ + + instr = active_instrument[voice]; + + if (!instr) + instr = &instrmap[0]; + + if (instr->channel < 0) + { + printk ( + "OPL3: Initializing voice %d with undefined instrument\n", + voice); + return 0; + } + + if (map->voice_mode == 2 && instr->key == OPL3_PATCH) + return 0; /* Cannot play */ + + voice_mode = map->voice_mode; + + if (voice_mode == 4) + { + int voice_shift; + + voice_shift = (map->ioaddr == left_address) ? 0 : 3; + voice_shift += map->voice_num; + + if (instr->key != OPL3_PATCH) /* Just 2 OP patch */ + { + voice_mode = 2; + connection_mask &= ~(1 << voice_shift); + } + else + { + connection_mask |= (1 << voice_shift); + } + + opl3_command (right_address, CONNECTION_SELECT_REGISTER, connection_mask); + } + + /* Set Sound Characteristics */ + opl3_command (map->ioaddr, AM_VIB + map->op[0], instr->operators[0]); + opl3_command (map->ioaddr, AM_VIB + map->op[1], instr->operators[1]); + + /* Set Attack/Decay */ + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[0], instr->operators[4]); + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[1], instr->operators[5]); + + /* Set Sustain/Release */ + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[0], instr->operators[6]); + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[1], instr->operators[7]); + + /* Set Wave Select */ + opl3_command (map->ioaddr, WAVE_SELECT + map->op[0], instr->operators[8]); + opl3_command (map->ioaddr, WAVE_SELECT + map->op[1], instr->operators[9]); + + /* Set Feedback/Connection */ + /* Connect the voice to both stereo channels */ + opl3_command (map->ioaddr, FEEDBACK_CONNECTION + map->voice_num, instr->operators[10] | 0x30); + + /* + * If the voice is a 4 OP one, initialize the operators 3 and 4 also + */ + + if (voice_mode == 4) + { + + /* Set Sound Characteristics */ + opl3_command (map->ioaddr, AM_VIB + map->op[2], instr->operators[OFFS_4OP + 0]); + opl3_command (map->ioaddr, AM_VIB + map->op[3], instr->operators[OFFS_4OP + 1]); + + /* Set Attack/Decay */ + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[2], instr->operators[OFFS_4OP + 4]); + opl3_command (map->ioaddr, ATTACK_DECAY + map->op[3], instr->operators[OFFS_4OP + 5]); + + /* Set Sustain/Release */ + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[2], instr->operators[OFFS_4OP + 6]); + opl3_command (map->ioaddr, SUSTAIN_RELEASE + map->op[3], instr->operators[OFFS_4OP + 7]); + + /* Set Wave Select */ + opl3_command (map->ioaddr, WAVE_SELECT + map->op[2], instr->operators[OFFS_4OP + 8]); + opl3_command (map->ioaddr, WAVE_SELECT + map->op[3], instr->operators[OFFS_4OP + 9]); + + /* Set Feedback/Connection */ + /* Connect the voice to both stereo channels */ + opl3_command (map->ioaddr, FEEDBACK_CONNECTION + map->voice_num + 3, instr->operators[OFFS_4OP + 10] | 0x30); + } + + voices[voice].mode = voice_mode; + + set_voice_volume (voice, volume); + + freq = voices[voice].orig_freq = note_to_freq (note) / 1000; + + /* + * Since the pitch bender may have been set before playing the note, we + * have to calculate the bending now. + */ + + freq = compute_finetune (voices[voice].orig_freq, voices[voice].bender, voices[voice].bender_range); + voices[voice].current_freq = freq; + + freq_to_fnum (freq, &block, &fnum); + + /* Play note */ + + data = fnum & 0xff; /* Least significant bits of fnumber */ + opl3_command (map->ioaddr, FNUM_LOW + map->voice_num, data); + + data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3); + voices[voice].keyon_byte = data; + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, data); + if (voice_mode == 4) + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num + 3, data); + + return 0; +} + +static void +freq_to_fnum (int freq, int *block, int *fnum) +{ + int f, octave; + + /* Converts the note frequency to block and fnum values for the FM chip */ + /* First try to compute the block -value (octave) where the note belongs */ + + f = freq; + + octave = 5; + + if (f == 0) + octave = 0; + else if (f < 261) + { + while (f < 261) + { + octave--; + f <<= 1; + } + } + else if (f > 493) + { + while (f > 493) + { + octave++; + f >>= 1; + } + } + + if (octave > 7) + octave = 7; + + *fnum = freq * (1 << (20 - octave)) / 49716; + *block = octave; +} + +static void +opl3_command (int io_addr, const unsigned char addr, const unsigned char val) +{ + int i; + + /* + * The original 2-OP synth requires a quite long delay after writing to a + * register. The OPL-3 survives with just two INBs + */ + + OUTB (addr, io_addr); /* Select register */ + + if (!opl3_enabled) + tenmicrosec (); + else + for (i = 0; i < 2; i++) + INB (io_addr); + + OUTB (val, io_addr + 1); /* Write to register */ + + if (!opl3_enabled) + { + tenmicrosec (); + tenmicrosec (); + tenmicrosec (); + } + else + for (i = 0; i < 2; i++) + INB (io_addr); +} + +static void +opl3_reset (int dev) +{ + int i; + + for (i = 0; i < nr_voices; i++) + { + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[0], 0xff); /* OP1 volume to min */ + + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[1], 0xff); /* OP2 volume to min */ + + if (physical_voices[logical_voices[i]].voice_mode == 4) /* 4 OP voice */ + { + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[2], 0xff); /* OP3 volume to min */ + + opl3_command (physical_voices[logical_voices[i]].ioaddr, + KSL_LEVEL + physical_voices[logical_voices[i]].op[3], 0xff); /* OP4 volume to min */ + } + + opl3_kill_note (dev, i, 64); + } + + if (opl3_enabled) + { + nr_voices = 18; + + for (i = 0; i < 18; i++) + logical_voices[i] = i; + + for (i = 0; i < 18; i++) + physical_voices[i].voice_mode = 2; + + } + +} + +static int +opl3_open (int dev, int mode) +{ + if (!opl3_ok) + return RET_ERROR (ENXIO); + if (opl3_busy) + return RET_ERROR (EBUSY); + opl3_busy = 1; + + connection_mask = 0x00; /* Just 2 OP voices */ + if (opl3_enabled) + opl3_command (right_address, CONNECTION_SELECT_REGISTER, connection_mask); + return 0; +} + +static void +opl3_close (int dev) +{ + opl3_busy = 0; + nr_voices = opl3_enabled ? 18 : 9; + fm_info.nr_drums = 0; + fm_info.perc_mode = 0; + + opl3_reset (dev); +} + +static void +opl3_hw_control (int dev, unsigned char *event) +{ +} + +static int +opl3_load_patch (int dev, int format, snd_rw_buf * addr, + int offs, int count, int pmgr_flag) +{ + struct sbi_instrument ins; + + if (count < sizeof (ins)) + { + printk ("FM Error: Patch record too short\n"); + return RET_ERROR (EINVAL); + } + + COPY_FROM_USER (&((char *) &ins)[offs], (char *) addr, offs, sizeof (ins) - offs); + + if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) + { + printk ("FM Error: Invalid instrument number %d\n", ins.channel); + return RET_ERROR (EINVAL); + } + ins.key = format; + + return store_instr (ins.channel, &ins); +} + +static void +opl3_panning (int dev, int voice, int pressure) +{ +} + +#define SET_VIBRATO(cell) { \ + tmp = instr->operators[(cell-1)+(((cell-1)/2)*OFFS_4OP)]; \ + if (pressure > 110) \ + tmp |= 0x40; /* Vibrato on */ \ + opl3_command (map->ioaddr, AM_VIB + map->op[cell-1], tmp);} + +static void +opl3_aftertouch (int dev, int voice, int pressure) +{ + int tmp; + struct sbi_instrument *instr; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return; + + map = &physical_voices[logical_voices[voice]]; + + DEB (printk ("Aftertouch %d\n", voice)); + + if (map->voice_mode == 0) + return; + + /* + * Adjust the amount of vibrato depending the pressure + */ + + instr = active_instrument[voice]; + + if (!instr) + instr = &instrmap[0]; + + if (voices[voice].mode == 4) + { + int connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01); + + switch (connection) + { + case 0: + SET_VIBRATO (4); + break; + + case 1: + SET_VIBRATO (2); + SET_VIBRATO (4); + break; + + case 2: + SET_VIBRATO (1); + SET_VIBRATO (4); + break; + + case 3: + SET_VIBRATO (1); + SET_VIBRATO (3); + SET_VIBRATO (4); + break; + + } + /* Not implemented yet */ + } + else + { + SET_VIBRATO (1); + + if ((instr->operators[10] & 0x01)) /* Additive synthesis */ + SET_VIBRATO (2); + } +} + +#undef SET_VIBRATO + +static void +opl3_controller (int dev, int voice, int ctrl_num, int value) +{ + unsigned char data; + int block, fnum, freq; + struct physical_voice_info *map; + + if (voice < 0 || voice >= nr_voices) + return; + + map = &physical_voices[logical_voices[voice]]; + + if (map->voice_mode == 0) + return; + + switch (ctrl_num) + { + case CTRL_PITCH_BENDER: + voices[voice].bender = value; + if (!value) + return; + if (!(voices[voice].keyon_byte & 0x20)) + return; /* Not keyed on */ + + freq = compute_finetune (voices[voice].orig_freq, voices[voice].bender, voices[voice].bender_range); + voices[voice].current_freq = freq; + + freq_to_fnum (freq, &block, &fnum); + + data = fnum & 0xff; /* Least significant bits of fnumber */ + opl3_command (map->ioaddr, FNUM_LOW + map->voice_num, data); + + data = 0x20 | ((block & 0x7) << 2) | ((fnum >> 8) & 0x3); /* KEYON|OCTAVE|MS bits + * of f-num */ + voices[voice].keyon_byte = data; + opl3_command (map->ioaddr, KEYON_BLOCK + map->voice_num, data); + break; + + case CTRL_PITCH_BENDER_RANGE: + voices[voice].bender_range = value; + break; + } +} + +static int +opl3_patchmgr (int dev, struct patmgr_info *rec) +{ + return RET_ERROR (EINVAL); +} + +static struct synth_operations opl3_operations = +{ + &fm_info, + SYNTH_TYPE_FM, + FM_TYPE_ADLIB, + opl3_open, + opl3_close, + opl3_ioctl, + opl3_kill_note, + opl3_start_note, + opl3_set_instr, + opl3_reset, + opl3_hw_control, + opl3_load_patch, + opl3_aftertouch, + opl3_controller, + opl3_panning, + opl3_patchmgr +}; + +long +opl3_init (long mem_start) +{ + int i; + + synth_devs[num_synths++] = &opl3_operations; + fm_model = 0; + opl3_ok = 1; + if (opl3_enabled) + { + printk (" <Yamaha OPL-3 FM>"); + fm_model = 2; + nr_voices = 18; + fm_info.nr_drums = 0; + fm_info.capabilities |= SYNTH_CAP_OPL3; + strcpy (fm_info.name, "Yamaha OPL-3"); + + for (i = 0; i < 18; i++) + if (physical_voices[i].ioaddr == USE_LEFT) + physical_voices[i].ioaddr = left_address; + else + physical_voices[i].ioaddr = right_address; + + + opl3_command (right_address, OPL3_MODE_REGISTER, OPL3_ENABLE); /* Enable OPL-3 mode */ + opl3_command (right_address, CONNECTION_SELECT_REGISTER, 0x00); /* Select all 2-OP + * voices */ + } + else + { + printk (" <Yamaha 2-OP FM>"); + fm_model = 1; + nr_voices = 9; + fm_info.nr_drums = 0; + + for (i = 0; i < 18; i++) + physical_voices[i].ioaddr = left_address; + }; + + already_initialized = 1; + for (i = 0; i < SBFM_MAXINSTR; i++) + instrmap[i].channel = -1; + + printk("\n"); + + return mem_start; +} + +#endif diff --git a/sys/i386/isa/sound/opl3.h b/sys/i386/isa/sound/opl3.h new file mode 100644 index 0000000..b577af7 --- /dev/null +++ b/sys/i386/isa/sound/opl3.h @@ -0,0 +1,263 @@ +/* + * opl3.h - Definitions of the OPL-3 registers + * + */ + +/* + * Copyright by Hannu Savolainen 1993 + * + * 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. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + * + */ + +/* + * The OPL-3 mode is switched on by writing 0x01, to the offset 5 + * of the right side. + * + * Another special register at the right side is at offset 4. It contains + * a bit mask defining which voices are used as 4 OP voices. + * + * The percussive mode is implemented in the left side only. + * + * With the above exeptions the both sides can be operated independently. + * + * A 4 OP voice can be created by setting the corresponding + * bit at offset 4 of the right side. + * + * For example setting the rightmost bit (0x01) changes the + * first voice on the right side to the 4 OP mode. The fourth + * voice is made inaccessible. + * + * If a voice is set to the 2 OP mode, it works like 2 OP modes + * of the original YM3812 (AdLib). In addition the voice can + * be connected the left, right or both stereo channels. It can + * even be left unconnected. This works with 4 OP voices also. + * + * The stereo connection bits are located in the FEEDBACK_CONNECTION + * register of the voice (0xC0-0xC8). In 4 OP voices these bits are + * in the second half of the voice. + */ + +/* + * Register numbers for the global registers + */ + +#define TEST_REGISTER 0x01 +#define ENABLE_WAVE_SELECT 0x20 + +#define TIMER1_REGISTER 0x02 +#define TIMER2_REGISTER 0x03 +#define TIMER_CONTROL_REGISTER 0x04 /* Left side */ +#define IRQ_RESET 0x80 +#define TIMER1_MASK 0x40 +#define TIMER2_MASK 0x20 +#define TIMER1_START 0x01 +#define TIMER2_START 0x02 + +#define CONNECTION_SELECT_REGISTER 0x04 /* Right side */ +#define RIGHT_4OP_0 0x01 +#define RIGHT_4OP_1 0x02 +#define RIGHT_4OP_2 0x04 +#define LEFT_4OP_0 0x08 +#define LEFT_4OP_1 0x10 +#define LEFT_4OP_2 0x20 + +#define OPL3_MODE_REGISTER 0x05 /* Right side */ +#define OPL3_ENABLE 0x01 + +#define KBD_SPLIT_REGISTER 0x08 /* Left side */ +#define COMPOSITE_SINE_WAVE_MODE 0x80 /* Don't use with OPL-3? */ +#define KEYBOARD_SPLIT 0x40 + +#define PERCUSSION_REGISTER 0xbd /* Left side only */ +#define TREMOLO_DEPTH 0x80 +#define VIBRATO_DEPTH 0x40 +#define PERCUSSION_ENABLE 0x20 +#define BASSDRUM_ON 0x10 +#define SNAREDRUM_ON 0x08 +#define TOMTOM_ON 0x04 +#define CYMBAL_ON 0x02 +#define HIHAT_ON 0x01 + +/* + * Offsets to the register banks for operators. To get the + * register number just add the operator offset to the bank offset + * + * AM/VIB/EG/KSR/Multiple (0x20 to 0x35) + */ + #define AM_VIB 0x20 + #define TREMOLO_ON 0x80 + #define VIBRATO_ON 0x40 + #define SUSTAIN_ON 0x20 + #define KSR 0x10 /* Key scaling rate */ + #define MULTIPLE_MASK 0x0f /* Frequency multiplier */ + + /* + * KSL/Total level (0x40 to 0x55) + */ +#define KSL_LEVEL 0x40 +#define KSL_MASK 0xc0 /* Envelope scaling bits */ +#define TOTAL_LEVEL_MASK 0x3f /* Strength (volume) of OP */ + +/* + * Attack / Decay rate (0x60 to 0x75) + */ +#define ATTACK_DECAY 0x60 +#define ATTACK_MASK 0xf0 +#define DECAY_MASK 0x0f + +/* + * Sustain level / Release rate (0x80 to 0x95) + */ +#define SUSTAIN_RELEASE 0x80 +#define SUSTAIN_MASK 0xf0 +#define RELEASE_MASK 0x0f + +/* + * Wave select (0xE0 to 0xF5) + */ +#define WAVE_SELECT 0xe0 + +/* + * Offsets to the register banks for voices. Just add to the + * voice number to get the register number. + * + * F-Number low bits (0xA0 to 0xA8). + */ +#define FNUM_LOW 0xa0 + +/* + * F-number high bits / Key on / Block (octave) (0xB0 to 0xB8) + */ +#define KEYON_BLOCK 0xb0 +#define KEYON_BIT 0x20 +#define BLOCKNUM_MASK 0x1c +#define FNUM_HIGH_MASK 0x03 + +/* + * Feedback / Connection (0xc0 to 0xc8) + * + * These registers have two new bits when the OPL-3 mode + * is selected. These bits controls connecting the voice + * to the stereo channels. For 4 OP voices this bit is + * defined in the second half of the voice (add 3 to the + * register offset). + * + * For 4 OP voices the connection bit is used in the + * both halfs (gives 4 ways to connect the operators). + */ +#define FEEDBACK_CONNECTION 0xc0 +#define FEEDBACK_MASK 0x0e /* Valid just for 1st OP of a voice */ +#define CONNECTION_BIT 0x01 +/* + * In the 4 OP mode there is four possible configurations how the + * operators can be connected together (in 2 OP modes there is just + * AM or FM). The 4 OP connection mode is defined by the rightmost + * bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halfs. + * + * First half Second half Mode + * + * +---+ + * v | + * 0 0 >+-1-+--2--3--4--> + * + * + * + * +---+ + * | | + * 0 1 >+-1-+--2-+ + * |-> + * >--3----4-+ + * + * +---+ + * | | + * 1 0 >+-1-+-----+ + * |-> + * >--2--3--4-+ + * + * +---+ + * | | + * 1 1 >+-1-+--+ + * | + * >--2--3-+-> + * | + * >--4----+ + */ +#define STEREO_BITS 0x30 /* OPL-3 only */ +#define VOICE_TO_LEFT 0x10 +#define VOICE_TO_RIGHT 0x20 + +/* + * Definition table for the physical voices + */ + +struct physical_voice_info { + unsigned char voice_num; + unsigned char voice_mode; /* 0=unavailable, 2=2 OP, 4=4 OP */ + unsigned short ioaddr; /* I/O port (left or right side) */ + unsigned char op[4]; /* Operator offsets */ + }; + +/* + * 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. + */ + +#define USE_LEFT 0 +#define USE_RIGHT 1 + +static struct physical_voice_info physical_voices[18] = +{ +/* No Mode Side OP1 OP2 OP3 OP4 */ +/* --------------------------------------------------- */ + { 0, 2, USE_LEFT, {0x00, 0x03, 0x08, 0x0b}}, + { 1, 2, USE_LEFT, {0x01, 0x04, 0x09, 0x0c}}, + { 2, 2, USE_LEFT, {0x02, 0x05, 0x0a, 0x0d}}, + + { 3, 2, USE_LEFT, {0x08, 0x0b, 0x00, 0x00}}, + { 4, 2, USE_LEFT, {0x09, 0x0c, 0x00, 0x00}}, + { 5, 2, USE_LEFT, {0x0a, 0x0d, 0x00, 0x00}}, + + { 6, 2, USE_LEFT, {0x10, 0x13, 0x00, 0x00}}, /* Used by percussive voices */ + { 7, 2, USE_LEFT, {0x11, 0x14, 0x00, 0x00}}, /* if the percussive mode */ + { 8, 2, USE_LEFT, {0x12, 0x15, 0x00, 0x00}}, /* is selected */ + + { 0, 2, USE_RIGHT, {0x00, 0x03, 0x08, 0x0b}}, + { 1, 2, USE_RIGHT, {0x01, 0x04, 0x09, 0x0c}}, + { 2, 2, USE_RIGHT, {0x02, 0x05, 0x0a, 0x0d}}, + + { 3, 2, USE_RIGHT, {0x08, 0x0b, 0x00, 0x00}}, + { 4, 2, USE_RIGHT, {0x09, 0x0c, 0x00, 0x00}}, + { 5, 2, USE_RIGHT, {0x0a, 0x0d, 0x00, 0x00}}, + + { 6, 2, USE_RIGHT, {0x10, 0x13, 0x00, 0x00}}, + { 7, 2, USE_RIGHT, {0x11, 0x14, 0x00, 0x00}}, + { 8, 2, USE_RIGHT, {0x12, 0x15, 0x00, 0x00}} +}; diff --git a/sys/i386/isa/sound/os.h b/sys/i386/isa/sound/os.h new file mode 100644 index 0000000..2ea9e5a --- /dev/null +++ b/sys/i386/isa/sound/os.h @@ -0,0 +1,273 @@ +#ifndef _OS_H_ +#define _OS_H_ +/* + * OS specific settings for FreeBSD + * + * This chould be used as an example when porting the driver to a new + * operating systems. + * + * What you should do is to rewrite the soundcard.c and os.h (this file). + * You should create a new subdirectory and put these two files there. + * In addition you have to do a makefile.<OS>. + * + * If you have to make changes to other than these two files, please contact me + * before making the changes. It's possible that I have already made the + * change. + */ + +/* + * Insert here the includes required by your kernel. + */ + +#include "param.h" +#include "systm.h" +#include "ioctl.h" +#include "tty.h" +#include "proc.h" +#include "user.h" +#include "conf.h" +#include "file.h" +#include "uio.h" +/* #include "kernel.h" */ +#include "syslog.h" +#include "errno.h" +#include "malloc.h" +#include "buf.h" +#include "i386/isa/isa_device.h" + +/* These few lines are used by FreeBSD (only??). */ + +#if NSND > 0 +#define KERNEL_SOUNDCARD +#else +#undef KERNEL_SOUNDCARD +#endif + + +/* + * Rest of the file is compiled only if the driver is really required. + */ +#ifdef CONFIGURE_SOUNDCARD + +/* lbolt is required by the FreeBSD version (only???) */ +extern int __timeout_val; +extern int __process_aborting; + +/* + * select() is currently implemented in Linux specific way. Don't enable. + * I don't remember what the SHORT_BANNERS means so forget it. + */ + +#undef ALLOW_SELECT +#define SHORT_BANNERS + +/* The soundcard.h could be in a nonstandard place so inclyde it here. */ +#include "soundcard.h" + +/* + * Here is the first portability problem. Every OS has it's own way to + * pass a pointer to the buffer in read() and write() calls. In Linux it's + * just a char*. In BSD it's struct uio. This parameter is passed to + * all functions called from read() or write(). Since nothing can be + * assumed about this structure, the driver uses set of macros for + * accessing the user buffer. + * + * The driver reads/writes bytes in the user buffer sequentially which + * means that calls like uiomove() can be used. + * + * snd_rw_buf is the type which is passed to the device file specific + * read() and write() calls. + * + * The following macros are used to move date to and from the + * user buffer. These macros should be used only when the + * target or source parameter has snd_rw_buf type. + * The offs parameter is a offset relative to the beginning of + * the user buffer. In Linux the offset is required but for example + * BSD passes the offset info in the uio structure. It could be usefull + * if these macros verify that the offs parameter and the value in + * the snd_rw_buf structure are equal. + */ +typedef struct uio snd_rw_buf; + +/* + * Move bytes from the buffer which the application given in a + * write() call. + * offs is position relative to the beginning of the buffer in + * user space. The count is number of bytes to be moved. + */ +#define COPY_FROM_USER(target, source, offs, count) \ + if (uiomove(target, count, source)) { \ + printf ("sb: Bad copyin()!\n"); \ + } else +/* Like COPY_FOM_USER but for writes. */ +#define COPY_TO_USER(target, offs, source, count) \ + if (uiomove(source, count, target)) { \ + printf ("sb: Bad copyout()!\n"); \ + } else +/* + * The following macros are like COPY_*_USER but work just with one byte (8bit), + * short (16 bit) or long (32 bit) at a time. + * The same restrictions apply than for COPY_*_USER + */ +#define GET_BYTE_FROM_USER(target, addr, offs) {uiomove((char*)&(target), 1, addr);} +#define GET_SHORT_FROM_USER(target, addr, offs) {uiomove((char*)&(target), 2, addr);} +#define GET_WORD_FROM_USER(target, addr, offs) {uiomove((char*)&(target), 4, addr);} +#define PUT_WORD_TO_USER(addr, offs, data) {uiomove((char*)&(data), 4, addr);} + +/* + * The way how the ioctl arguments are passed is another nonportable thing. + * In Linux the argument is just a pointer directly to the user segment. On + * 386bsd the data is already moved to the kernel space. The following + * macros should handle the difference. + */ + +/* + * IOCTL_FROM_USER is used to copy a record pointed by the argument to + * a buffer in the kernel space. On 386bsd it can be done just by calling + * memcpy. With Linux a memcpy_from_fs should be called instead. + * Parameters of the following macros are like in the COPY_*_USER macros. + */ + +/* + * When the ioctl argument points to a record or array (longer than 32 bits), + * the macros IOCTL_*_USER are used. It's assumed that the source and target + * parameters are direct memory addresses. + */ +#define IOCTL_FROM_USER(target, source, offs, count) {memcpy(target, &((source)[offs]), count);} +#define IOCTL_TO_USER(target, offs, source, count) {memcpy(&((target)[offs]), source, count);} +/* The following macros are used if the ioctl argument points to 32 bit int */ +#define IOCTL_IN(arg) (*(int*)arg) +#define IOCTL_OUT(arg, ret) *(int*)arg = ret + +/* + * When the driver displays something to the console, printk() will be called. + * The name can be changed here. + */ +#define printk printf + +/* + * The following macros define an interface to the process management. + */ + +/* + * DEFINE_WAIT_QUEUE is used where a wait queue is required. It must define + * a structure which can be passed as a parameter to a sleep(). The second + * parameter is name of a flag variable (must be defined as int). + */ +#define DEFINE_WAIT_QUEUE(qname, flag) static int *qname = NULL; static int flag = 0 +/* Like the above but defines an array of wait queues and flags */ +#define DEFINE_WAIT_QUEUES(qname, flag) static int *qname = {NULL}; static int flag = {0} +/* + * This driver handles interrupts little bit nonstandard way. The following + * macro is used to test if the current process has received a signal which + * is aborts the process. This macro is called from close() to see if the + * buffers should be discarded. If this kind info is not available, a constant + * 1 or 0 could be returned (1 should be better than 0). + * I'm not sure if the following is correct for FreeBSD. + */ +#define PROCESS_ABORTING (__process_aborting | curproc->p_sig) +/* + * REQUEST_TIMEOUT is called before sleep. It shoud ensure that the + * process is woken up after given number of ticks (1/HZ secs.). + * The wqueue gives the wait queue. + */ +#define REQUEST_TIMEOUT(nticks, wqueue) __timeout_val = nticks; + +/* + * The following macro calls sleep. It should be implemented such that + * the process is resumed if it receives a signal. The following is propably + * not the way how it should be done on 386bsd. + * The on_what parameter is a wait_queue defined with DEFINE_WAIT_QUEUE() + * The second parameter is a flag. It must be initialized to 1 before sleep + * and to zero after proces continues. + */ +#define INTERRUPTIBLE_SLEEP_ON(on_what, flag) \ + { \ + flag = 1; \ + flag=tsleep(&(on_what), (PRIBIO-5)|PCATCH, "sndint", __timeout_val); \ + if(flag == ERESTART) __process_aborting = 1;\ + else __process_aborting = 0;\ + __timeout_val = 0; \ + flag = 0; \ + } + +/* An the following wakes up a process */ +#define WAKE_UP(who) wakeup(&(who)) + +/* + * Timing macros. This driver assumes that there is a timer running in the + * kernel. The timer should return a value which is increased once at every + * timer tick. The macro HZ should return the number of such ticks/sec. + */ + +#ifndef HZ +extern int hz; +#define HZ hz +#endif + +/* + * GET_TIME() returns current value of the counter incremented at timer + * ticks. This can overflow, so the timeout might be real big... + * + */ +#define GET_TIME() get_time() +/*#define GET_TIME() (lbolt) /* Returns current time (1/HZ secs since boot) */ + +/* + * The following three macros are called before and after atomic + * code sequences. The flags parameter has always type of unsigned long. + * The macro DISABLE_INTR() should ensure that all interrupts which + * may invoke any part of the driver (timer, soundcard interrupts) are + * disabled. + * RESTORE_INTR() should return the interrupt status back to the + * state when DISABLE_INTR() was called. The flags parameter is + * a variable which can carry 32 bits of state information between + * DISABLE_INTR() and RESTORE_INTR() calls. + */ +#define DISABLE_INTR(flags) flags = splhigh() +#define RESTORE_INTR(flags) splx(flags) + +/* + * INB() and OUTB() should be obvious. NOTE! The order of + * paratemeters of OUTB() is different than on some other + * operating systems. + */ + +#define INB inb +#define OUTB(addr, data) outb(data, addr) + +/* memcpy() was not defined og 386bsd. Lets define it here */ +#define memcpy(d, s, c) bcopy(s, d, c) + +/* + * When a error (such as EINVAL) is returned by a function, + * the following macro is used. The driver assumes that a + * error is signalled by returning a negative value. + */ + +#define RET_ERROR(err) -(err) + +/* + KERNEL_MALLOC() allocates requested number of memory and + KERNEL_FREE is used to free it. + These macros are never called from interrupt, in addition the + nbytes will never be more than 4096 bytes. Generally the driver + will allocate memory in blocks of 4k. If the kernel has just a + page level memory allocation, 4K can be safely used as the size + (the nbytes parameter can be ignored). +*/ +#define KERNEL_MALLOC(nbytes) malloc(nbytes, M_TEMP, M_WAITOK) +#define KERNEL_FREE(addr) free(addr, M_TEMP) + +/* + * The rest of this file is not complete yet. The functions using these + * macros will not work + */ +#define ALLOC_DMA_CHN(chn) (0) +#define RELEASE_DMA_CHN(chn) (0) +#define DMA_MODE_READ 0 +#define DMA_MODE_WRITE 1 +#define RELEASE_IRQ(irq_no) + +#endif +#endif diff --git a/sys/i386/isa/sound/pas.h b/sys/i386/isa/sound/pas.h new file mode 100644 index 0000000..4dadea3 --- /dev/null +++ b/sys/i386/isa/sound/pas.h @@ -0,0 +1,249 @@ +/* */ +/* Port addresses and bit fields for the Media Vision Pro AudioSpectrum second generation sound cards. */ +/* */ +/* Feel free to use this header file in any application you create that has support for the Media Vision */ +/* Pro AudioSpectrum second generation sound cards. Other uses prohibited without prior permission. */ +/* */ +/* - cmetz@thor.tjhsst.edu */ +/* */ +/* Notes: */ +/* */ +/* * All of these ports go into the MVD101 multimedia controller chip, which then signals the other chips to do */ +/* the actual work. Many ports like the FM ones functionally attach directly to the destination chip though */ +/* they don't actually have a direct connection. */ +/* */ +/* * The PAS2 series cards have an MVD101 multimedia controller chip, the original PAS cards don't. The original */ +/* PAS cards are pretty defunct now, so no attempt is made here to support them. */ +/* */ +/* * The PAS2 series cards are all really different at the hardware level, though the MVD101 hides some of the */ +/* incompatibilities, there still are differences that need to be accounted for. */ +/* */ +/* Card CD-ROM interface PCM chip Mixer chip FM chip */ +/* PAS Plus Sony proprietary (Crystal?) 8-bit DAC National OPL3 */ +/* PAS 16 Zilog SCSI MVA416 16-bit Codec MVA508 OPL3 */ +/* CDPC Sony proprietary Sony 16-bit Codec National OPL3 */ +/* Fusion CD 16 Sony proprietary MVA416 16-bit Codec MVA508 OPL3 */ +/* Fusion CD Sony proprietary (Crystal?) 8-bit DAC National OPL3 */ +/* */ +#define PAS_DEFAULT_BASE 0x388 + +/* Symbolic Name Value R W Subsystem Description */ +#define SPEAKER_CONTROL 0x61 /* W PC speaker Control register */ +#define SPEAKER_CONTROL_GHOST 0x738B /* R W PC speaker Control ghost register */ +#define SPEAKER_TIMER_CONTROL 0x43 /* W PC speaker Timer control register */ +#define SPEAKER_TIMER_CONTROL_GHOST 0x778B /* R W PC speaker Timer control register ghost */ +#define SPEAKER_TIMER_DATA 0x42 /* W PC speaker Timer data register */ +#define SPEAKER_TIMER_DATA_GHOST 0x138A /* R W PC speaker Timer data register ghost */ + +#define WARM_BOOT 0x41 /* W Control Used to detect system warm boot */ +#define WARM_BOOT_GHOST 0x7789 /* ? W Control Use to get the card to fake warm boot */ +#define MASTER_DECODE 0x9A01 /* W Control Address >> 2 of card base address */ +#define PRESCALE_DIVIDER 0xBF8A /* R W PCM Ration between Codec clock and master clock */ +#define WAIT_STATE 0xBF88 /* R W Control Four-bit bus wait-state count (~140ns ea.) */ +#define BOARD_REV_ID 0x2789 /* R Control Extended Board Revision ID */ + +#define SYSTEM_CONFIGURATION_1 0x8388 /* R W Control */ + #define S_C_1_PCS_ENABLE 0x01 /* R W PC speaker 1=enable, 0=disable PC speaker emulation */ + #define S_C_1_PCM_CLOCK_SELECT 0x02 /* R W PCM 1=14.31818Mhz/12, 0=28.224Mhz master clock */ + #define S_C_1_FM_EMULATE_CLOCK 0x04 /* R W FM 1=use 28.224Mhz/2, 0=use 14.31818Mhz clock */ + #define S_C_1_PCS_STEREO 0x10 /* R W PC speaker 1=enable PC speaker stereo effect, 0=disable */ + #define S_C_1_PCS_REALSOUND 0x20 /* R W PC speaker 1=enable RealSound enhancement, 0=disable */ + #define S_C_1_FORCE_EXT_RESET 0x40 /* R W Control Force external reset */ + #define S_C_1_FORCE_INT_RESET 0x80 /* R W Control Force internal reset */ +#define SYSTEM_CONFIGURATION_2 0x8389 /* R W Control */ + #define S_C_2_PCM_OVERSAMPLING 0x03 /* R W PCM 00=0x, 01=2x, 10=4x, 11=reserved */ + #define S_C_2_PCM_16_BIT 0x04 /* R W PCM 1=16-bit, 0=8-bit samples */ +#define SYSTEM_CONFIGURATION_3 0x838A /* R W Control */ + #define S_C_3_PCM_CLOCK_SELECT 0x02 /* R W PCM 1=use 1.008Mhz clock for PCM, 0=don't */ +#define SYSTEM_CONFIGURATION_4 0x838B /* R W Control CD-ROM interface controls */ + +#define IO_CONFIGURATION_1 0xF388 /* R W Control */ + #define I_C_1_BOOT_RESET_ENABLE 0x80 /* R W Control 1=reset board on warm boot, 0=don't */ +#define IO_CONFIGURATION_2 0xF389 /* R W Control */ + #define I_C_2_PCM_DMA_DISABLED 0x00 /* R W PCM PCM DMA disabled */ +#define IO_CONFIGURATION_3 0xF38A /* R W Control */ + #define I_C_3_PCM_IRQ_DISABLED 0x00 /* R W PCM PCM IRQ disabled */ + +#define COMPATIBILITY_ENABLE 0xF788 /* R W Control */ + #define C_E_MPU401_ENABLE 0x01 /* R W MIDI 1=enable, 0=disable MPU401 MIDI emulation */ + #define C_E_SB_ENABLE 0x02 /* R W PCM 1=enable, 0=disable Sound Blaster emulation */ + #define C_E_SB_ACTIVE 0x04 /* R PCM "Sound Blaster Interrupt active" */ + #define C_E_MPU401_ACTIVE 0x08 /* R MIDI "MPU UART mode active" */ + #define C_E_PCM_COMPRESSION 0x10 /* R W PCM 1=enable, 0=disabled compression */ +#define EMULATION_ADDRESS 0xF789 /* R W Control */ + #define E_A_SB_BASE 0x0f /* R W PCM bits A4-A7 for SB base port */ + #define E_A_MPU401_BASE 0xf0 /* R W MIDI bits A4-A7 for MPU401 base port */ +#define EMULATION_CONFIGURATION 0xFB8A /* R W ***** Only valid on newer PAS2 cards (?) ***** */ + #define E_C_MPU401_IRQ 0x07 /* R W MIDI MPU401 emulation IRQ */ + #define E_C_SB_IRQ 0x38 /* R W PCM SB emulation IRQ */ + #define E_C_SB_DMA 0xC0 /* R W PCM SB emulation DMA */ + +#define OPERATION_MODE_1 0xEF8B /* R Control */ + #define O_M_1_CDROM_TYPE 0x03 /* R CD-ROM 3=SCSI, 2=Sony, 0=no CD-ROM interface */ + #define O_M_1_FM_TYPE 0x04 /* R FM 1=sterero, 0=mono FM chip */ + #define O_M_1_PCM_TYPE 0x08 /* R PCM 1=16-bit Codec, 0=8-bit DAC */ +#define OPERATION_MODE_2 0xFF8B /* R Control */ + #define O_M_2_PCS_ENABLED 0x02 /* R PC speaker PC speaker emulation 1=enabled, 0=disabled */ + #define O_M_2_BUS_TIMING 0x10 /* R Control 1=AT bus timing, 0=XT bus timing */ + #define O_M_2_BOARD_REVISION 0xe0 /* R Control Board revision */ + +#define INTERRUPT_MASK 0x0B8B /* R W Control */ + #define I_M_FM_LEFT_IRQ_ENABLE 0x01 /* R W FM Enable FM left interrupt */ + #define I_M_FM_RIGHT_IRQ_ENABLE 0x02 /* R W FM Enable FM right interrupt */ + #define I_M_PCM_RATE_IRQ_ENABLE 0x04 /* R W PCM Enable Sample Rate interrupt */ + #define I_M_PCM_BUFFER_IRQ_ENABLE 0x08 /* R W PCM Enable Sample Buffer interrupt */ + #define I_M_MIDI_IRQ_ENABLE 0x10 /* R W MIDI Enable MIDI interrupt */ + #define I_M_BOARD_REV 0xE0 /* R Control Board revision */ + +#define INTERRUPT_STATUS 0x0B89 /* R W Control */ + #define I_S_FM_LEFT_IRQ 0x01 /* R W FM Left FM Interrupt Pending */ + #define I_S_FM_RIGHT_IRQ 0x02 /* R W FM Right FM Interrupt Pending */ + #define I_S_PCM_SAMPLE_RATE_IRQ 0x04 /* R W PCM Sample Rate Interrupt Pending */ + #define I_S_PCM_SAMPLE_BUFFER_IRQ 0x08 /* R W PCM Sample Buffer Interrupt Pending */ + #define I_S_MIDI_IRQ 0x10 /* R W MIDI MIDI Interrupt Pending */ + #define I_S_PCM_CHANNEL 0x20 /* R W PCM 1=right, 0=left */ + #define I_S_RESET_ACTIVE 0x40 /* R W Control Reset is active (Timed pulse not finished) */ + #define I_S_PCM_CLIPPING 0x80 /* R W PCM Clipping has occurred */ + +#define FILTER_FREQUENCY 0x0B8A /* R W Control */ + #define F_F_FILTER_DISABLED 0x00 /* R W Mixer No filter */ +#if 0 + struct { /* R W Mixer Filter translation */ + unsigned int freq:24; + unsigned int value:8; + } F_F_FILTER_translate[] = + { { 73500, 0x01 }, /* 73500Hz - divide by 16 */ + { 65333, 0x02 }, /* 65333Hz - divide by 18 */ + { 49000, 0x09 }, /* 49000Hz - divide by 24 */ + { 36750, 0x11 }, /* 36750Hz - divide by 32 */ + { 24500, 0x19 }, /* 24500Hz - divide by 48 */ + { 18375, 0x07 }, /* 18375Hz - divide by 64 */ + { 12783, 0x0f }, /* 12783Hz - divide by 92 */ + { 12250, 0x04 }, /* 12250Hz - divide by 96 */ + { 9188, 0x17 }, /* 9188Hz - divide by 128 */ + { 6125, 0x1f }, /* 6125Hz - divide by 192 */ + }; +#endif + #define F_F_MIXER_UNMUTE 0x20 /* R W Mixer 1=disable, 0=enable board mute */ + #define F_F_PCM_RATE_COUNTER 0x40 /* R W PCM 1=enable, 0=disable sample rate counter */ + #define F_F_PCM_BUFFER_COUNTER 0x80 /* R W PCM 1=enable, 0=disable sample buffer counter */ + +#define PAS_NONE 0 +#define PAS_PLUS 1 +#define PAS_CDPC 2 +#define PAS_16 3 + +#ifdef DEFINE_TRANSLATIONS + char I_C_2_PCM_DMA_translate[] = /* R W PCM PCM DMA channel value translations */ + { 4, 1, 2, 3, 0, 5, 6, 7 }; + char I_C_3_PCM_IRQ_translate[] = /* R W PCM PCM IRQ level value translation */ + { 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 7, 8, 9, 0, 10, 11 }; + char E_C_MPU401_IRQ_translate[] = /* R W MIDI MPU401 emulation IRQ value translation */ + { 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x05, 0x06, 0x07 }; + char E_C_SB_IRQ_translate[] = /* R W PCM SB emulation IRQ translate */ + { 0x00, 0x00, 0x08, 0x10, 0x00, 0x18, 0x00, 0x20, 0x00, 0x00, 0x28, 0x30, 0x38 }; + char E_C_SB_DMA_translate[] = /* R W PCM SB emulation DMA translate */ + { 0x00, 0x40, 0x80, 0xC0 }; + char O_M_1_to_card[] = /* R W Control Translate (OM1 & 0x0f) to card type */ + { 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 3, 0, 2, 3 }; +#else + extern char I_C_2_PCM_DMA_translate[]; /* R W PCM PCM DMA channel value translations */ + extern char I_C_3_PCM_IRQ_translate[]; /* R W PCM PCM IRQ level value translation */ + extern char E_C_MPU401_IRQ_translate[]; /* R W MIDI MPU401 emulation IRQ value translation */ + extern char E_C_SB_IRQ_translate[]; /* R W PCM SB emulation IRQ translate */ + extern char E_C_SB_DMA_translate[]; /* R W PCM SB emulation DMA translate */ + extern char O_M_1_to_card[]; /* R W Control Translate (OM1 & 0x0f) to card type */ +#endif + +#define PARALLEL_MIXER 0x078B /* W Mixer Documented for MVD101 as FM Mono Right decode?? */ + #define P_M_MV508_ADDRESS 0x80 /* W Mixer MVD508 Address/mixer select */ + #define P_M_MV508_DATA 0x00 + #define P_M_MV508_LEFT 0x20 /* W Mixer MVD508 Left channel select */ + #define P_M_MV508_RIGHT 0x40 /* W Mixer MVD508 Right channel select */ + #define P_M_MV508_BOTH 0x00 /* W Mixer MVD508 Both channel select */ + #define P_M_MV508_MIXER 0x10 /* W Mixer MVD508 Select a mixer (rather than a volume) */ + #define P_M_MV508_VOLUME 0x00 + + #define P_M_MV508_INPUTMIX 0x20 /* W Mixer MVD508 Select mixer A */ + #define P_M_MV508_OUTPUTMIX 0x00 /* W Mixer MVD508 Select mixer B */ + + #define P_M_MV508_MASTER_A 0x01 /* W Mixer MVD508 Master volume control A (output) */ + #define P_M_MV508_MASTER_B 0x02 /* W Mixer MVD508 Master volume control B (DSP input) */ + #define P_M_MV508_BASS 0x03 /* W Mixer MVD508 Bass control */ + #define P_M_MV508_TREBLE 0x04 /* W Mixer MVD508 Treble control */ + #define P_M_MV508_MODE 0x05 /* W Mixer MVD508 Master mode control */ + + #define P_M_MV508_LOUDNESS 0x04 /* W Mixer MVD508 Mode control - Loudness filter */ + #define P_M_MV508_ENHANCE_BITS 0x03 + #define P_M_MV508_ENHANCE_NONE 0x00 /* W Mixer MVD508 Mode control - No stereo enhancement */ + #define P_M_MV508_ENHANCE_40 0x01 /* W Mixer MVD508 Mode control - 40% stereo enhancement */ + #define P_M_MV508_ENHANCE_60 0x02 /* W Mixer MVD508 Mode control - 60% stereo enhancement */ + #define P_M_MV508_ENHANCE_80 0x03 /* W Mixer MVD508 Mode control - 80% stereo enhancement */ + + #define P_M_MV508_FM 0x00 /* W Mixer MVD508 Channel 0 - FM */ + #define P_M_MV508_IMIXER 0x01 /* W Mixer MVD508 Channel 1 - Input mixer (rec monitor) */ + #define P_M_MV508_LINE 0x02 /* W Mixer MVD508 Channel 2 - Line in */ + #define P_M_MV508_CDROM 0x03 /* W Mixer MVD508 Channel 3 - CD-ROM */ + #define P_M_MV508_MIC 0x04 /* W Mixer MVD508 Channel 4 - Microphone */ + #define P_M_MV508_PCM 0x05 /* W Mixer MVD508 Channel 5 - PCM */ + #define P_M_MV508_SPEAKER 0x06 /* W Mixer MVD508 Channel 6 - PC Speaker */ + #define P_M_MV508_SB 0x07 /* W Mixer MVD508 Channel 7 - SB DSP */ + +#define SERIAL_MIXER 0xB88 /* R W Control Serial mixer control (used other ways) */ + #define S_M_PCM_RESET 0x01 /* R W PCM Codec/DSP reset */ + #define S_M_FM_RESET 0x02 /* R W FM FM chip reset */ + #define S_M_SB_RESET 0x04 /* R W PCM SB emulation chip reset */ + #define S_M_MIXER_RESET 0x10 /* R W Mixer Mixer chip reset */ + #define S_M_INTEGRATOR_ENABLE 0x40 /* R W Speaker Enable PC speaker integrator (FORCE RealSound) */ + #define S_M_OPL3_DUAL_MONO 0x80 /* R W FM Set the OPL-3 to dual mono mode */ + +#define PCM_CONTROL 0xF8A /* R W PCM PCM Control Register */ + #define P_C_MIXER_CROSS_FIELD 0x0f + #define P_C_MIXER_CROSS_R_TO_R 0x01 /* R W Mixer Connect Right to Right */ + #define P_C_MIXER_CROSS_L_TO_R 0x02 /* R W Mixer Connect Left to Right */ + #define P_C_MIXER_CROSS_R_TO_L 0x04 /* R W Mixer Connect Right to Left */ + #define P_C_MIXER_CROSS_L_TO_L 0x08 /* R W Mixer Connect Left to Left */ + #define P_C_PCM_DAC_MODE 0x10 /* R W PCM Playback (DAC) mode */ + #define P_C_PCM_ADC_MODE 0x00 /* R W PCM Record (ADC) mode */ + #define P_C_PCM_MONO 0x20 /* R W PCM Mono mode */ + #define P_C_PCM_STEREO 0x00 /* R W PCM Stereo mode */ + #define P_C_PCM_ENABLE 0x40 /* R W PCM Enable PCM engine */ + #define P_C_PCM_DMA_ENABLE 0x80 /* R W PCM Enable DRQ */ + +#define SAMPLE_COUNTER_CONTROL 0x138B /* R W PCM Sample counter control register */ + #define S_C_C_SQUARE_WAVE 0x04 /* R W PCM Square wave generator (use for sample rate) */ + #define S_C_C_RATE 0x06 /* R W PCM Rate generator (use for sample buffer count) */ + #define S_C_C_LSB_THEN_MSB 0x30 /* R W PCM Change all 16 bits, LSB first, then MSB */ + + /* MVD101 and SDK documentations have S_C_C_SAMPLE_RATE and S_C_C_SAMPLE_BUFFER transposed. Only one works :-) */ + #define S_C_C_SAMPLE_RATE 0x00 /* R W PCM Select sample rate timer */ + #define S_C_C_SAMPLE_BUFFER 0x40 /* R W PCM Select sample buffer counter */ + + #define S_C_C_PC_SPEAKER 0x80 /* R W PCM Select PC speaker counter */ + +#define SAMPLE_RATE_TIMER 0x1388 /* W PCM Sample rate timer register (PCM wait interval) */ +#define SAMPLE_BUFFER_COUNTER 0x1389 /* R W PCM Sample buffer counter (DMA buffer size) */ + +#define MIDI_CONTROL 0x178b /* R W MIDI Midi control register */ + #define M_C_ENA_TSTAMP_IRQ 0x01 /* R W MIDI Enable Time Stamp Interrupts */ + #define M_C_ENA_TME_COMP_IRQ 0x02 /* R W MIDI Enable time compare interrupts */ + #define M_C_ENA_INPUT_IRQ 0x04 /* R W MIDI Enable input FIFO interrupts */ + #define M_C_ENA_OUTPUT_IRQ 0x08 /* R W MIDI Enable output FIFO interrupts */ + #define M_C_ENA_OUTPUT_HALF_IRQ 0x10 /* R W MIDI Enable output FIFO half full interrupts */ + #define M_C_RESET_INPUT_FIFO 0x20 /* R W MIDI Reset input FIFO pointer */ + #define M_C_RESET_OUTPUT_FIFO 0x40 /* R W MIDI Reset output FIFO pointer */ + #define M_C_ENA_THRU_MODE 0x80 /* R W MIDI Echo input to output (THRU) */ + +#define MIDI_STATUS 0x1B88 /* R W MIDI Midi (interrupt) status register */ + #define M_S_TIMESTAMP 0x01 /* R W MIDI Midi time stamp interrupt occurred */ + #define M_S_COMPARE 0x02 /* R W MIDI Midi compare time interrupt occurred */ + #define M_S_INPUT_AVAIL 0x04 /* R W MIDI Midi input data available interrupt occurred */ + #define M_S_OUTPUT_EMPTY 0x08 /* R W MIDI Midi output FIFO empty interrupt occurred */ + #define M_S_OUTPUT_HALF_EMPTY 0x10 /* R W MIDI Midi output FIFO half empty interrupt occurred */ + #define M_S_INPUT_OVERRUN 0x20 /* R W MIDI Midi input overrun error occurred */ + #define M_S_OUTPUT_OVERRUN 0x40 /* R W MIDI Midi output overrun error occurred */ + #define M_S_FRAMING_ERROR 0x80 /* R W MIDI Midi input framing error occurred */ + +#define MIDI_FIFO_STATUS 0x1B89 /* R W MIDI Midi fifo status */ +#define MIDI_DATA 0x178A /* R W MIDI Midi data register */ +#define MIDI_INPUT_AVAILABLE 0x0f /* RW MIDI */ diff --git a/sys/i386/isa/sound/pas2_card.c b/sys/i386/isa/sound/pas2_card.c new file mode 100644 index 0000000..19df65d --- /dev/null +++ b/sys/i386/isa/sound/pas2_card.c @@ -0,0 +1,343 @@ +#define _PAS2_CARD_C_ +#define SND_SA_INTERRUPT +/* + * linux/kernel/chr_drv/sound/pas2_card.c + * + * Detection routine for the Pro Audio Spectrum cards. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) Craig Metz + * (cmetz@thor.tjhsst.edu) See COPYING for further details. Should be + * distributed with this file. + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS) + +#define DEFINE_TRANSLATIONS +#include "pas.h" + +/* + * The Address Translation code is used to convert I/O register addresses to + * be relative to the given base -register + */ + +int translat_code; +static int pas_intr_mask = 0; +static int pas_irq = 0; + +static char pas_model; +static char *pas_model_names[] = +{"", "Pro AudioSpectrum+", "CDPC", "Pro AudioSpectrum 16"}; + +/* pas_read() and pas_write() are equivalents of INB() and OUTB() */ +/* These routines perform the I/O address translation required */ +/* to support other than the default base address */ + +unsigned char +pas_read (int ioaddr) +{ + return INB (ioaddr ^ translat_code); +} + +void +pas_write (unsigned char data, int ioaddr) +{ + OUTB (data, ioaddr ^ translat_code); +} + +void +pas2_msg (char *foo) +{ + printk (" PAS2: %s.\n", foo); +} + +/******************* Begin of the Interrupt Handler ********************/ + +void +pasintr (int unused) +{ + int status; + + status = pas_read (INTERRUPT_STATUS); + pas_write (status, INTERRUPT_STATUS); /* Clear interrupt */ + + if (status & I_S_PCM_SAMPLE_BUFFER_IRQ) + { +#ifndef EXCLUDE_AUDIO + pas_pcm_interrupt (status, 1); +#endif + status &= ~I_S_PCM_SAMPLE_BUFFER_IRQ; + } + if (status & I_S_MIDI_IRQ) + { +#ifndef EXCLUDE_MIDI +#ifdef EXCLUDE_PRO_MIDI + pas_midi_interrupt (); +#endif +#endif + status &= ~I_S_MIDI_IRQ; + } + +} + +static int +set_pas_irq (int interrupt_level) +{ +#ifdef linux + int retcode; + struct sigaction sa; + + pas_write (0xff, INTERRUPT_STATUS); /* Reset pending interrupts */ + + sa.sa_handler = pasintr; + +#ifdef SND_SA_INTERRUPT + sa.sa_flags = SA_INTERRUPT; +#else + sa.sa_flags = 0; +#endif + + sa.sa_mask = 0; + sa.sa_restorer = NULL; + + retcode = irqaction (interrupt_level, &sa); + + if (retcode < 0) + { + printk ("ProAudioSpectrum: IRQ%d already in use\n", interrupt_level); + } + return retcode; +#else + /* # error This routine does not work with this OS */ +#endif +} + +int +pas_set_intr (int mask) +{ + int err; + + if (!mask) + return 0; + + if (!pas_intr_mask) + { + if ((err = set_pas_irq (pas_irq)) < 0) + return err; + } + pas_intr_mask |= mask; + + pas_write (pas_intr_mask, INTERRUPT_MASK); + return 0; +} + +int +pas_remove_intr (int mask) +{ + if (!mask) + return 0; + + pas_intr_mask &= ~mask; + pas_write (pas_intr_mask, INTERRUPT_MASK); + + if (!pas_intr_mask) + { + RELEASE_IRQ (pas_irq); + } + return 0; +} + +/******************* End of the Interrupt handler **********************/ + +/******************* Begin of the Initialization Code ******************/ + +int +config_pas_hw (struct address_info *hw_config) +{ + char ok = 1; + + pas_irq = hw_config->irq; + + pas_write (0x00, INTERRUPT_MASK); + + pas_write (0x36, SAMPLE_COUNTER_CONTROL); /* Local timer control + * register */ + + pas_write (0x36, SAMPLE_RATE_TIMER); /* Sample rate timer (16 bit) */ + pas_write (0, SAMPLE_RATE_TIMER); + + pas_write (0x74, SAMPLE_COUNTER_CONTROL); /* Local timer control + * register */ + + pas_write (0x74, SAMPLE_BUFFER_COUNTER); /* Sample count register (16 + * bit) */ + pas_write (0, SAMPLE_BUFFER_COUNTER); + + pas_write (F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER | F_F_MIXER_UNMUTE | 1, FILTER_FREQUENCY); + pas_write (P_C_PCM_DMA_ENABLE | P_C_PCM_MONO | P_C_PCM_DAC_MODE | P_C_MIXER_CROSS_L_TO_L | P_C_MIXER_CROSS_R_TO_R, PCM_CONTROL); + pas_write (S_M_PCM_RESET | S_M_FM_RESET | S_M_SB_RESET | S_M_MIXER_RESET /* | S_M_OPL3_DUAL_MONO */ , SERIAL_MIXER); + + pas_write (I_C_1_BOOT_RESET_ENABLE, IO_CONFIGURATION_1); + + if (pas_irq < 0 || pas_irq > 15) + { + printk ("PAS2: Invalid IRQ %d", pas_irq); + ok = 0; + } + else + { + pas_write (I_C_3_PCM_IRQ_translate[pas_irq], IO_CONFIGURATION_3); + if (!I_C_3_PCM_IRQ_translate[pas_irq]) + { + printk ("PAS2: Invalid IRQ %d", pas_irq); + ok = 0; + } + } + + if (hw_config->dma < 0 || hw_config->dma > 7) + { + printk ("PAS2: Invalid DMA selection %d", hw_config->dma); + ok = 0; + } + else + { + pas_write (I_C_2_PCM_DMA_translate[hw_config->dma], IO_CONFIGURATION_2); + if (!I_C_2_PCM_DMA_translate[hw_config->dma]) + { + printk ("PAS2: Invalid DMA selection %d", hw_config->dma); + ok = 0; + } + } + +#ifdef BROKEN_BUS_CLOCK + pas_write (S_C_1_PCS_ENABLE | S_C_1_PCS_STEREO | S_C_1_PCS_REALSOUND | S_C_1_FM_EMULATE_CLOCK, SYSTEM_CONFIGURATION_1); +#else + /* pas_write(S_C_1_PCS_ENABLE, SYSTEM_CONFIGURATION_1); */ + pas_write (S_C_1_PCS_ENABLE | S_C_1_PCS_STEREO | S_C_1_PCS_REALSOUND, SYSTEM_CONFIGURATION_1); +#endif + /* pas_write(S_C_2_PCM_16_BIT, SYSTEM_CONFIGURATION_2); Don't do this */ + pas_write (0x18, SYSTEM_CONFIGURATION_3); /* ??? */ + + pas_write (F_F_MIXER_UNMUTE | 0x01, FILTER_FREQUENCY); /* Sets mute off and + * selects filter rate + * of 17.897 kHz */ + + if (pas_model == PAS_16) + pas_write (8, PRESCALE_DIVIDER); + else + pas_write (0, PRESCALE_DIVIDER); + + pas_write (P_M_MV508_ADDRESS | 5, PARALLEL_MIXER); + pas_write (5, PARALLEL_MIXER); + +#if !defined(EXCLUDE_SB_EMULATION) || !defined(EXCLUDE_SB) + + /* Turn on Sound Blaster compatibility */ + /* bit 1 = SB emulation */ + /* bit 0 = MPU401 emulation (CDPC only :-( ) */ + pas_write (0x02, COMPATIBILITY_ENABLE); + + /* "Emulation address" */ + pas_write ((SBC_BASE >> 4) & 0x0f, EMULATION_ADDRESS); +#endif + + if (!ok) + pas2_msg ("Driver not enabled"); + + return ok; +} + +int +detect_pas_hw (struct address_info *hw_config) +{ + unsigned char board_id, foo; + + /* + * WARNING: Setting an option like W:1 or so that disables warm boot reset + * of the card will screw up this detect code something fierce. Adding code + * to handle this means possibly interfering with other cards on the bus if + * you have something on base port 0x388. SO be forewarned. + */ + + OUTB (0xBC, MASTER_DECODE); /* Talk to first board */ + OUTB (hw_config->io_base >> 2, MASTER_DECODE); /* Set base address */ + translat_code = PAS_DEFAULT_BASE ^ hw_config->io_base; + pas_write (1, WAIT_STATE); /* One wait-state */ + + board_id = pas_read (INTERRUPT_MASK); + + if (board_id == 0xff) + return 0; + + /* + * We probably have a PAS-series board, now check for a PAS2-series board + * by trying to change the board revision bits. PAS2-series hardware won't + * let you do this - the bits are read-only. + */ + + foo = board_id ^ 0xe0; + + pas_write (foo, INTERRUPT_MASK); + foo = INB (INTERRUPT_MASK); + pas_write (board_id, INTERRUPT_MASK); + + if (board_id != foo) /* Not a PAS2 */ + return 0; + + if ((pas_model = O_M_1_to_card[pas_read (OPERATION_MODE_1) & 0x0f])); + + return pas_model; +} + +long +attach_pas_card (long mem_start, struct address_info *hw_config) +{ + pas_irq = hw_config->irq; + + if (detect_pas_hw (hw_config)) + { + + if ((pas_model = O_M_1_to_card[pas_read (OPERATION_MODE_1) & 0x0f])) + { + printk (" <%s rev %d>", pas_model_names[(int) pas_model], pas_read (BOARD_REV_ID)); + } + + if (config_pas_hw (hw_config)) + { + +#ifndef EXCLUDE_AUDIO + mem_start = pas_pcm_init (mem_start, hw_config); +#endif + +# if !defined(EXCLUDE_SB_EMULATION) && !defined(EXCLUDE_SB) + + sb_dsp_disable_midi (); /* The SB emulation don't support + * midi */ +# endif + +#ifndef EXCLUDE_YM3812 + enable_opl3_mode (0x388, 0x38a, 0); +#endif + +#ifndef EXCLUDE_MIDI +#ifdef EXCLUDE_PRO_MIDI + mem_start = pas_midi_init (mem_start); +#endif +#endif + + pas_init_mixer (); + } + } + + printk("\n"); + return mem_start; +} + +int +probe_pas (struct address_info *hw_config) +{ + return detect_pas_hw (hw_config); +} + +#endif diff --git a/sys/i386/isa/sound/pas2_midi.c b/sys/i386/isa/sound/pas2_midi.c new file mode 100644 index 0000000..a5fd604 --- /dev/null +++ b/sys/i386/isa/sound/pas2_midi.c @@ -0,0 +1,269 @@ +/* + * linux/kernel/chr_drv/sound/pas2_midi.c + * + * The low level driver for the PAS Midi Interface. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "pas.h" + +#if !defined(EXCLUDE_PAS) && !defined(EXCLUDE_MIDI) && defined(EXCLUDE_PRO_MIDI) + +static int midi_busy = 0, input_opened = 0; +static int my_dev; +static volatile int ofifo_bytes = 0; + +static unsigned char tmp_queue[256]; +static volatile int qlen; +static volatile unsigned char qhead, qtail; + +static int +pas_midi_open (int dev, int mode) +{ + int err; + unsigned long flags; + unsigned char ctrl; + + + if (midi_busy) + { + printk ("PAS2: Midi busy\n"); + return RET_ERROR (EBUSY); + } + + /* Reset input and output FIFO pointers */ + pas_write (M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO, + MIDI_CONTROL); + + DISABLE_INTR (flags); + + if ((err = pas_set_intr (I_M_MIDI_IRQ_ENABLE)) < 0) + return err; + + /* Enable input available and output FIFO empty interrupts */ + + ctrl = 0; + input_opened = 0; + + if (mode == OPEN_READ || mode == OPEN_READWRITE) + { + ctrl |= M_C_ENA_INPUT_IRQ;/* Enable input */ + input_opened = 1; + } + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + { + ctrl |= M_C_ENA_OUTPUT_IRQ | /* Enable output */ + M_C_ENA_OUTPUT_HALF_IRQ; + } + + pas_write (ctrl, + MIDI_CONTROL); + + /* Acknowledge any pending interrupts */ + + pas_write (0xff, MIDI_STATUS); + ofifo_bytes = 0; + + RESTORE_INTR (flags); + + midi_busy = 1; + qlen = qhead = qtail = 0; + return 0; +} + +static void +pas_midi_close (int dev) +{ + + /* Reset FIFO pointers, disable intrs */ + pas_write (M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO, MIDI_CONTROL); + + pas_remove_intr (I_M_MIDI_IRQ_ENABLE); + midi_busy = 0; +} + +static int +dump_to_midi (unsigned char midi_byte) +{ + int fifo_space, x; + + fifo_space = ((x = pas_read (MIDI_FIFO_STATUS)) >> 4) & 0x0f; + + if (fifo_space == 15 || (fifo_space < 2 && ofifo_bytes > 13)) /* Fifo full */ + { + return 0; /* Upper layer will call again */ + } + + ofifo_bytes++; + + pas_write (midi_byte, MIDI_DATA); + + return 1; +} + +static int +pas_midi_out (int dev, unsigned char midi_byte) +{ + + unsigned long flags; + + /* + * Drain the local queue first + */ + + DISABLE_INTR (flags); + + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + + /* + * Output the byte if the local queue is empty. + */ + + if (!qlen) + if (dump_to_midi (midi_byte)) + return 1; /* OK */ + + /* + * Put to the local queue + */ + + if (qlen >= 256) + return 0; /* Local queue full */ + + DISABLE_INTR (flags); + + tmp_queue[qtail] = midi_byte; + qlen++; + qtail++; + + RESTORE_INTR (flags); + + return 1; +} + +static int +pas_midi_start_read (int dev) +{ + return 0; +} + +static int +pas_midi_end_read (int dev) +{ + return 0; +} + +static int +pas_midi_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EINVAL); +} + +static void +pas_midi_kick (int dev) +{ + ofifo_bytes = 0; +} + +static int +pas_buffer_status (int dev) +{ + return !qlen; +} + +static struct midi_operations pas_midi_operations = +{ + {"Pro Audio Spectrum", 0}, + pas_midi_open, + pas_midi_close, + pas_midi_ioctl, + pas_midi_out, + pas_midi_start_read, + pas_midi_end_read, + pas_midi_kick, + NULL, /* command */ + pas_buffer_status +}; + +long +pas_midi_init (long mem_start) +{ + my_dev = num_midis; + midi_devs[num_midis++] = &pas_midi_operations; + return mem_start; +} + +void +pas_midi_interrupt (void) +{ + unsigned char stat; + int i, incount; + unsigned long flags; + + stat = pas_read (MIDI_STATUS); + + if (stat & M_S_INPUT_AVAIL) /* Input byte available */ + { + incount = pas_read (MIDI_FIFO_STATUS) & 0x0f; /* Input FIFO count */ + if (!incount) + incount = 16; + + for (i = 0; i < incount; i++) + if (input_opened) + { + sequencer_midi_input (my_dev, pas_read (MIDI_DATA)); + } + else + pas_read (MIDI_DATA); /* Flush */ + } + + if (stat & (M_S_OUTPUT_EMPTY | M_S_OUTPUT_HALF_EMPTY)) + { + if (!(stat & M_S_OUTPUT_EMPTY)) + { + ofifo_bytes = 8; + } + else + { + ofifo_bytes = 0; + } + + DISABLE_INTR (flags); + + while (qlen && dump_to_midi (tmp_queue[qhead])) + { + qlen--; + qhead++; + } + + RESTORE_INTR (flags); + } + + if (stat & M_S_FRAMING_ERROR) + printk ("MIDI framing error\n"); + + if (stat & M_S_OUTPUT_OVERRUN) + { + printk ("MIDI output overrun %02x,%02x,%d \n", pas_read (MIDI_FIFO_STATUS), stat, ofifo_bytes); + ofifo_bytes = 100; + } + + pas_write (stat, MIDI_STATUS);/* Acknowledge interrupts */ +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/pas2_mixer.c b/sys/i386/isa/sound/pas2_mixer.c new file mode 100644 index 0000000..3aa2072 --- /dev/null +++ b/sys/i386/isa/sound/pas2_mixer.c @@ -0,0 +1,481 @@ +#define _PAS2_MIXER_C_ + +/* + * linux/kernel/chr_drv/sound/pas2_mixer.c + * + * Mixer routines for the Pro Audio Spectrum cards. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) Craig Metz + * (cmetz@thor.tjhsst.edu) See COPYING for further details. Should be + * distributed with this file. + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS) + +#include "pas.h" + +#define TRACE(what) /* (what) */ + +extern int translat_code; + +static int rec_devices = (SOUND_MASK_MIC); /* Default recording source */ +static int mode_control = 0; + +#define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_ALTPCM) + +#define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD /*|SOUND_MASK_ALTPCM*/ | SOUND_MASK_IMIX | \ + SOUND_MASK_VOLUME | SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_RECLEV | \ + SOUND_MASK_MUTE | SOUND_MASK_ENHANCE | SOUND_MASK_LOUD) + +static unsigned short levels[SOUND_MIXER_NRDEVICES] = +{ + 0x3232, /* Master Volume */ + 0x3232, /* Bass */ + 0x3232, /* Treble */ + 0x5050, /* FM */ + 0x4b4b, /* PCM */ + 0x3232, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x4b4b, /* Mic */ + 0x4b4b, /* CD */ + 0x6464, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x6464}; /* Recording level */ + +static int +mixer_output (int right_vol, int left_vol, int div, int bits, + int mixer /* Input or output mixer */ ) +{ + int left = left_vol * div / 100; + int right = right_vol * div / 100; + + /* + * The Revision D cards have a problem with their MVA508 interface. The + * kludge-o-rama fix is to make a 16-bit quantity with identical LSB and + * MSBs out of the output byte and to do a 16-bit out to the mixer port - + * 1. We don't need to do this because the call to pas_write more than + * compensates for the timing problems. + */ + + if (bits & P_M_MV508_MIXER) + { /* Select input or output mixer */ + left |= mixer; + right |= mixer; + } + + if (bits == P_M_MV508_BASS || bits == P_M_MV508_TREBLE) + { /* Bass and trebble are mono devices */ + pas_write (P_M_MV508_ADDRESS | bits, PARALLEL_MIXER); + pas_write (left, PARALLEL_MIXER); + right_vol = left_vol; + } + else + { + pas_write (P_M_MV508_ADDRESS | P_M_MV508_LEFT | bits, PARALLEL_MIXER); + pas_write (left, PARALLEL_MIXER); + pas_write (P_M_MV508_ADDRESS | P_M_MV508_RIGHT | bits, PARALLEL_MIXER); + pas_write (right, PARALLEL_MIXER); + } + + return (left_vol | (right_vol << 8)); +} + +void +set_mode (int new_mode) +{ + pas_write (P_M_MV508_ADDRESS | P_M_MV508_MODE, PARALLEL_MIXER); + pas_write (new_mode, PARALLEL_MIXER); + + mode_control = new_mode; +} + +static int +pas_mixer_set (int whichDev, unsigned int level) +{ + int left, right, devmask, changed, i, mixer = 0; + + TRACE (printk ("static int pas_mixer_set(int whichDev = %d, unsigned int level = %X)\n", whichDev, level)); + + left = level & 0x7f; + right = (level & 0x7f00) >> 8; + + if (whichDev < SOUND_MIXER_NRDEVICES) + if ((1 << whichDev) & rec_devices) + mixer = P_M_MV508_INPUTMIX; + else + mixer = P_M_MV508_OUTPUTMIX; + + switch (whichDev) + { + case SOUND_MIXER_VOLUME: /* Master volume (0-63) */ + levels[whichDev] = mixer_output (right, left, 63, P_M_MV508_MASTER_A, 0); + break; + + /* + * Note! Bass and Treble are mono devices. Will use just the left + * channel. + */ + case SOUND_MIXER_BASS: /* Bass (0-12) */ + levels[whichDev] = mixer_output (right, left, 12, P_M_MV508_BASS, 0); + break; + case SOUND_MIXER_TREBLE: /* Treble (0-12) */ + levels[whichDev] = mixer_output (right, left, 12, P_M_MV508_TREBLE, 0); + break; + + case SOUND_MIXER_SYNTH: /* Internal synthesizer (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_FM, mixer); + break; + case SOUND_MIXER_PCM: /* PAS PCM (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_PCM, mixer); + break; + case SOUND_MIXER_ALTPCM: /* SB PCM (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_SB, mixer); + break; + case SOUND_MIXER_SPEAKER: /* PC speaker (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_SPEAKER, mixer); + break; + case SOUND_MIXER_LINE: /* External line (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_LINE, mixer); + break; + case SOUND_MIXER_CD: /* CD (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_CDROM, mixer); + break; + case SOUND_MIXER_MIC: /* External microphone (0-31) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_MIC, mixer); + break; + case SOUND_MIXER_IMIX: /* Recording monitor (0-31) (Only available + * on the Output Mixer) */ + levels[whichDev] = mixer_output (right, left, 31, P_M_MV508_MIXER | P_M_MV508_IMIXER, + P_M_MV508_OUTPUTMIX); + break; + case SOUND_MIXER_RECLEV: /* Recording level (0-15) */ + levels[whichDev] = mixer_output (right, left, 15, P_M_MV508_MASTER_B, 0); + break; + + case SOUND_MIXER_MUTE: + return 0; + break; + + case SOUND_MIXER_ENHANCE: + i = 0; + level &= 0x7f; + if (level) + i = (level / 20) - 1; + + mode_control &= ~P_M_MV508_ENHANCE_BITS; + mode_control |= P_M_MV508_ENHANCE_BITS; + set_mode (mode_control); + + if (i) + i = (i + 1) * 20; + return i; + break; + + case SOUND_MIXER_LOUD: + mode_control &= ~P_M_MV508_LOUDNESS; + if (level) + mode_control |= P_M_MV508_LOUDNESS; + set_mode (mode_control); + return !!level; /* 0 or 1 */ + break; + + case SOUND_MIXER_RECSRC: + devmask = level & POSSIBLE_RECORDING_DEVICES; + + changed = devmask ^ rec_devices; + rec_devices = devmask; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (changed & (1 << i)) + { + pas_mixer_set (i, levels[i]); + } + return rec_devices; + break; + + default: + return RET_ERROR (EINVAL); + } + + return (levels[whichDev]); +} + +/*****/ + +static int +mixer_set_levels (struct sb_mixer_levels *user_l) +{ +#define cmix(v) ((((v.r*100+7)/15)<<8)| ((v.l*100+7)/15)) + + struct sb_mixer_levels l; + + IOCTL_FROM_USER ((char *) &l, (char *) user_l, 0, sizeof (l)); + + if (l.master.l & ~0xF || l.master.r & ~0xF + || l.line.l & ~0xF || l.line.r & ~0xF + || l.voc.l & ~0xF || l.voc.r & ~0xF + || l.fm.l & ~0xF || l.fm.r & ~0xF + || l.cd.l & ~0xF || l.cd.r & ~0xF + || l.mic & ~0x7) + return (RET_ERROR (EINVAL)); + + pas_mixer_set (SOUND_MIXER_VOLUME, cmix (l.master)); + pas_mixer_set (SOUND_MIXER_LINE, cmix (l.line)); + pas_mixer_set (SOUND_MIXER_PCM, cmix (l.voc)); + pas_mixer_set (SOUND_MIXER_ALTPCM, cmix (l.voc)); + pas_mixer_set (SOUND_MIXER_SYNTH, cmix (l.fm)); + pas_mixer_set (SOUND_MIXER_CD, cmix (l.cd)); + pas_mixer_set (SOUND_MIXER_MIC, ((l.mic * 100 + 3) / 7) | (((l.mic * 100 + 3) / 7) << 8)); + return (0); +} + +/* + * This sets aspects of the Mixer that are not volume levels. (Recording + * source, filter level, I/O filtering, and stereo.) + */ +static int +mixer_set_params (struct sb_mixer_params *user_p) +{ + struct sb_mixer_params p; + S_BYTE val; + int src; + unsigned long flags; + + IOCTL_FROM_USER ((char *) &p, (char *) user_p, 0, sizeof (p)); + + if (p.record_source != SRC_MIC + && p.record_source != SRC_CD + && p.record_source != SRC_LINE) + return (RET_ERROR (EINVAL)); + + /* + * I'm not sure if this is The Right Thing. Should stereo be entirely + * under control of DSP? I like being able to toggle it while a sound is + * playing, so I do this... because I can. + */ + + DISABLE_INTR (flags); + + val = (pas_read (PCM_CONTROL) & ~P_C_MIXER_CROSS_FIELD) | P_C_MIXER_CROSS_R_TO_R | P_C_MIXER_CROSS_L_TO_L; + if (!p.dsp_stereo) + val |= (P_C_MIXER_CROSS_R_TO_L | P_C_MIXER_CROSS_L_TO_R); /* Mono */ + pas_write (val, PCM_CONTROL); + + RESTORE_INTR (flags); + + switch (p.record_source) + { + case SRC_CD: + src = SOUND_MASK_CD; + break; + + case SRC_LINE: + src = SOUND_MASK_LINE; + break; + + default: + src = SOUND_MASK_MIC; + break; + } + + pas_mixer_set (SOUND_MIXER_RECSRC, src); + + /* + * setmixer (OUT_FILTER, ((dsp_stereo ? STEREO_DAC : MONO_DAC) | + * (p.filter_output ? FILT_ON : FILT_OFF))); + */ + return (0); +} + +static int +getmixer (int dev, int chn) +{ + if (chn == P_M_MV508_RIGHT) + { + return (levels[dev] >> 8) & 0x7f; + } + else + { + return levels[dev] & 0x7f; + } +} + +/* Read the current mixer level settings into the user's struct. */ +static int +mixer_get_levels (struct sb_mixer_levels *user_l) +{ + + struct sb_mixer_levels l; + + l.master.r = ((((levels[SOUND_MIXER_VOLUME] >> 8) & 0x7f) * 15) + 50) / 100; /* Master */ + l.master.l = (((levels[SOUND_MIXER_VOLUME] & 0x7f) * 15) + 50) / 100; /* Master */ + + l.line.r = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_RIGHT) * 15) + 50) / 100; /* Line */ + l.line.l = ((getmixer (SOUND_MIXER_LINE, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.voc.r = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_RIGHT) * 15) + 50) / 100; /* DAC */ + l.voc.l = ((getmixer (SOUND_MIXER_PCM, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.fm.r = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_RIGHT) * 15) + 50) / 100; /* FM */ + l.fm.l = ((getmixer (SOUND_MIXER_SYNTH, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.cd.r = ((getmixer (SOUND_MIXER_CD, P_M_MV508_RIGHT) * 15) + 50) / 100; /* CD */ + l.cd.l = ((getmixer (SOUND_MIXER_CD, P_M_MV508_LEFT) * 15) + 50) / 100; + + l.mic = ((getmixer (SOUND_MIXER_MIC, P_M_MV508_LEFT) * 7) + 50) / 100; /* Microphone */ + + IOCTL_TO_USER ((char *) user_l, 0, (char *) &l, sizeof (l)); + return (0); +} + +/* Read the current mixer parameters into the user's struct. */ +static int +mixer_get_params (struct sb_mixer_params *user_params) +{ + S_BYTE val; + struct sb_mixer_params params; + + switch (rec_devices) + { + case SOUND_MASK_CD: + params.record_source = SRC_CD; + break; + + case SOUND_MASK_LINE: + params.record_source = SRC_LINE; + break; + + case SOUND_MASK_MIC: + params.record_source = SRC_MIC; + break; + + default: + params.record_source = SRC_MIC; + pas_mixer_set (SOUND_MIXER_RECSRC, SOUND_MASK_MIC); /* Adjust */ + } + + params.hifreq_filter = OFF; + params.filter_input = OFF; + params.filter_output = OFF; + + val = INB (PCM_CONTROL); + params.dsp_stereo = ((val & P_C_MIXER_CROSS_FIELD) == (P_C_MIXER_CROSS_L_TO_L | P_C_MIXER_CROSS_R_TO_R)); + + IOCTL_TO_USER ((char *) user_params, 0, (char *) ¶ms, sizeof (params)); + return (0); +} + +/*****/ + +static void +pas_mixer_reset (void) +{ + int foo; + + TRACE (printk ("pas2_mixer.c: void pas_mixer_reset(void)\n")); + + for (foo = 0; foo < SOUND_MIXER_NRDEVICES; foo++) + pas_mixer_set (foo, levels[foo]); + + set_mode (P_M_MV508_LOUDNESS | P_M_MV508_ENHANCE_40); +} + +int +pas_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg) +{ + TRACE (printk ("pas2_mixer.c: int pas_mixer_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg)); + + if (((cmd >> 8) & 0xff) == 'M') + { + if (cmd & IOC_IN) + return IOCTL_OUT (arg, pas_mixer_set (cmd & 0xff, IOCTL_IN (arg))); + else + { /* Read parameters */ + + switch (cmd & 0xff) + { + + case SOUND_MIXER_RECSRC: + return IOCTL_OUT (arg, rec_devices); + break; + + case SOUND_MIXER_STEREODEVS: + return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES & ~(SOUND_MASK_BASS | SOUND_MASK_TREBLE)); + break; + + case SOUND_MIXER_DEVMASK: + return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES); + break; + + case SOUND_MIXER_RECMASK: + return IOCTL_OUT (arg, POSSIBLE_RECORDING_DEVICES & SUPPORTED_MIXER_DEVICES); + break; + + case SOUND_MIXER_CAPS: + return IOCTL_OUT (arg, 0); /* No special capabilities */ + break; + + case SOUND_MIXER_MUTE: + return IOCTL_OUT (arg, 0); /* No mute yet */ + break; + + case SOUND_MIXER_ENHANCE: + if (!(mode_control & P_M_MV508_ENHANCE_BITS)) + return IOCTL_OUT (arg, 0); + return IOCTL_OUT (arg, ((mode_control & P_M_MV508_ENHANCE_BITS) + 1) * 20); + break; + + case SOUND_MIXER_LOUD: + if (mode_control & P_M_MV508_LOUDNESS) + return IOCTL_OUT (arg, 1); + return IOCTL_OUT (arg, 0); + break; + + default: + return IOCTL_OUT (arg, levels[cmd & 0xff]); + } + } + } + else + { + switch (cmd) + { + case MIXER_IOCTL_SET_LEVELS: + mixer_set_levels ((struct sb_mixer_levels *) arg); + return mixer_get_levels ((struct sb_mixer_levels *) arg); + case MIXER_IOCTL_SET_PARAMS: + mixer_set_params ((struct sb_mixer_params *) arg); + return mixer_get_params ((struct sb_mixer_params *) arg); + case MIXER_IOCTL_READ_LEVELS: + return mixer_get_levels ((struct sb_mixer_levels *) arg); + case MIXER_IOCTL_READ_PARAMS: + return mixer_get_params ((struct sb_mixer_params *) arg); + case MIXER_IOCTL_RESET: + pas_mixer_reset (); + return (0); + default: + return RET_ERROR (EINVAL); + } + } + return RET_ERROR (EINVAL); +} + +static struct mixer_operations pas_mixer_operations = +{ + pas_mixer_ioctl +}; + +int +pas_init_mixer (void) +{ + pas_mixer_reset (); + + mixer_devs[num_mixers++] = &pas_mixer_operations; + return 1; +} + +#endif diff --git a/sys/i386/isa/sound/pas2_pcm.c b/sys/i386/isa/sound/pas2_pcm.c new file mode 100644 index 0000000..f4634c5 --- /dev/null +++ b/sys/i386/isa/sound/pas2_pcm.c @@ -0,0 +1,412 @@ +#define _PAS2_PCM_C_ +/* + * linux/kernel/chr_drv/sound/pas2_pcm.c + * + * The low level driver for the Pro Audio Spectrum ADC/DAC. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) Craig Metz + * (cmetz@thor.tjhsst.edu) See COPYING for further details. Should be + * distributed with this file. + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "pas.h" + +#if !defined(EXCLUDE_PAS) && !defined(EXCLUDE_AUDIO) + +#define TRACE(WHAT) /* (WHAT) */ + +#define PAS_PCM_INTRBITS (0x08) +/* Sample buffer timer interrupt enable */ + +#define PCM_NON 0 +#define PCM_DAC 1 +#define PCM_ADC 2 + +static unsigned long pcm_speed = 0; /* sampling rate */ +static unsigned char pcm_channels = 1; /* channels/sample (1 or 2) */ +static unsigned char pcm_bits = 8; /* bits/sample (8 or 16) */ +static unsigned char pcm_filter = 0; /* filter FLAG */ +static unsigned char pcm_mode = PCM_NON; +static unsigned long pcm_count = 0; +static unsigned short pcm_bitsok = 8; /* mask of OK bits */ +static int my_devnum = 0; + +int +pcm_set_speed (int arg) +{ + int foo, tmp; + unsigned long flags; + + if (arg > 44100) + arg = 44100; + if (arg < 5000) + arg = 5000; + + foo = 1193180 / arg; + arg = 1193180 / foo; + + if (pcm_channels & 2) + foo = foo >> 1; + + pcm_speed = arg; + + tmp = pas_read (FILTER_FREQUENCY); + + DISABLE_INTR (flags); + + pas_write (tmp & ~(F_F_PCM_RATE_COUNTER | F_F_PCM_BUFFER_COUNTER), FILTER_FREQUENCY); + pas_write (S_C_C_SAMPLE_RATE | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write (foo & 0xff, SAMPLE_RATE_TIMER); + pas_write ((foo >> 8) & 0xff, SAMPLE_RATE_TIMER); + pas_write (tmp, FILTER_FREQUENCY); + + RESTORE_INTR (flags); + + return pcm_speed; +} + +int +pcm_set_channels (int arg) +{ + + if ((arg != 1) && (arg != 2)) + return pcm_channels; + + if (arg != pcm_channels) + { + pas_write (pas_read (PCM_CONTROL) ^ P_C_PCM_MONO, PCM_CONTROL); + + pcm_channels = arg; + pcm_set_speed (pcm_speed);/* The speed must be reinitialized */ + } + + return pcm_channels; +} + +int +pcm_set_bits (int arg) +{ + if ((arg & pcm_bitsok) != arg) + return pcm_bits; + + if (arg != pcm_bits) + { + pas_write (pas_read (SYSTEM_CONFIGURATION_2) ^ S_C_2_PCM_16_BIT, SYSTEM_CONFIGURATION_2); + + pcm_bits = arg; + } + + return pcm_bits; +} + +static int +pas_pcm_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + TRACE (printk ("pas2_pcm.c: static int pas_pcm_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg)); + + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (local) + return pcm_set_speed (arg); + return IOCTL_OUT (arg, pcm_set_speed (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_RATE: + if (local) + return pcm_speed; + return IOCTL_OUT (arg, pcm_speed); + break; + + case SNDCTL_DSP_STEREO: + if (local) + return pcm_set_channels (arg + 1) - 1; + return IOCTL_OUT (arg, pcm_set_channels (IOCTL_IN (arg) + 1) - 1); + break; + + case SOUND_PCM_WRITE_CHANNELS: + return IOCTL_OUT (arg, pcm_set_channels (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_CHANNELS: + if (local) + return pcm_channels; + return IOCTL_OUT (arg, pcm_channels); + break; + + case SNDCTL_DSP_SAMPLESIZE: + if (local) + return pcm_set_bits (arg); + return IOCTL_OUT (arg, pcm_set_bits (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_BITS: + if (local) + return pcm_bits; + return IOCTL_OUT (arg, pcm_bits); + + case SOUND_PCM_WRITE_FILTER: /* NOT YET IMPLEMENTED */ + if (IOCTL_IN (arg) > 1) + return IOCTL_OUT (arg, RET_ERROR (EINVAL)); + break; + + pcm_filter = IOCTL_IN (arg); + case SOUND_PCM_READ_FILTER: + return IOCTL_OUT (arg, pcm_filter); + break; + + default: + return RET_ERROR (EINVAL); + } + + return RET_ERROR (EINVAL); +} + +static void +pas_pcm_reset (int dev) +{ + TRACE (printk ("pas2_pcm.c: static void pas_pcm_reset(void)\n")); + + pas_write (pas_read (PCM_CONTROL) & ~P_C_PCM_ENABLE, PCM_CONTROL); +} + +static int +pas_pcm_open (int dev, int mode) +{ + int err; + + TRACE (printk ("pas2_pcm.c: static int pas_pcm_open(int mode = %X)\n", mode)); + + if (mode != OPEN_READ && mode != OPEN_WRITE) + { + printk ("PAS2: Attempt to open PCM device for simultaneous read and write"); + return RET_ERROR (EINVAL); + } + + if ((err = pas_set_intr (PAS_PCM_INTRBITS)) < 0) + return err; + + if (!DMAbuf_open_dma (dev)) + { + pas_remove_intr (PAS_PCM_INTRBITS); + return RET_ERROR (EBUSY); + } + + pcm_count = 0; + + pcm_set_bits (8); + pcm_set_channels (1); + pcm_set_speed (DSP_DEFAULT_SPEED); + + return 0; +} + +static void +pas_pcm_close (int dev) +{ + unsigned long flags; + + TRACE (printk ("pas2_pcm.c: static void pas_pcm_close(void)\n")); + + DISABLE_INTR (flags); + + pas_pcm_reset (dev); + DMAbuf_close_dma (dev); + pas_remove_intr (PAS_PCM_INTRBITS); + pcm_mode = PCM_NON; + + RESTORE_INTR (flags); +} + +static void +pas_pcm_output_block (int dev, unsigned long buf, int count, int intrflag) +{ + unsigned long flags, cnt; + + TRACE (printk ("pas2_pcm.c: static void pas_pcm_output_block(char *buf = %P, int count = %X)\n", buf, count)); + + cnt = count; + if (sound_dsp_dmachan[dev] > 3) + cnt >>= 1; + cnt--; + + if (sound_dma_automode[dev] && + intrflag && + cnt == pcm_count) + return; /* Auto mode on. No need to react */ + + DISABLE_INTR (flags); + + pas_write (pas_read (PCM_CONTROL) & ~P_C_PCM_ENABLE, + PCM_CONTROL); + + DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + count--; + + if (count != pcm_count) + { + pas_write (pas_read (FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + pas_write (S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write (count & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write ((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + + pcm_count = count; + } + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY); + pas_write (pas_read (PCM_CONTROL) | P_C_PCM_ENABLE | P_C_PCM_DAC_MODE, PCM_CONTROL); + + pcm_mode = PCM_DAC; + + RESTORE_INTR (flags); +} + +static void +pas_pcm_start_input (int dev, unsigned long buf, int count, int intrflag) +{ + unsigned long flags; + int cnt; + + TRACE (printk ("pas2_pcm.c: static void pas_pcm_start_input(char *buf = %P, int count = %X)\n", buf, count)); + + cnt = count; + if (sound_dsp_dmachan[dev] > 3) + cnt >>= 1; + cnt--; + + if (sound_dma_automode[my_devnum] && + intrflag && + cnt == pcm_count) + return; /* Auto mode on. No need to react */ + + DISABLE_INTR (flags); + + DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + + count--; + + if (count != pcm_count) + { + pas_write (pas_read (FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + pas_write (S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write (count & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write ((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + + pcm_count = count; + } + pas_write (pas_read (FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY); + pas_write ((pas_read (PCM_CONTROL) | P_C_PCM_ENABLE) & ~P_C_PCM_DAC_MODE, PCM_CONTROL); + + pcm_mode = PCM_ADC; + + RESTORE_INTR (flags); +} + +static int +pas_pcm_prepare_for_input (int dev, int bsize, int bcount) +{ + return 0; +} +static int +pas_pcm_prepare_for_output (int dev, int bsize, int bcount) +{ + return 0; +} + +static struct audio_operations pas_pcm_operations = +{ + "Pro Audio Spectrum", + pas_pcm_open, /* */ + pas_pcm_close, /* */ + pas_pcm_output_block, /* */ + pas_pcm_start_input, /* */ + pas_pcm_ioctl, /* */ + pas_pcm_prepare_for_input, /* */ + pas_pcm_prepare_for_output, /* */ + pas_pcm_reset, /* */ + pas_pcm_reset, /* halt_xfer */ + NULL, /* has_output_drained */ + NULL /* copy_from_user */ +}; + +long +pas_pcm_init (long mem_start, struct address_info *hw_config) +{ + TRACE (printk ("pas2_pcm.c: long pas_pcm_init(long mem_start = %X)\n", mem_start)); + + pcm_bitsok = 8; + if (pas_read (OPERATION_MODE_1) & O_M_1_PCM_TYPE) + pcm_bitsok |= 16; + + pcm_set_speed (DSP_DEFAULT_SPEED); + + if (num_dspdevs < MAX_DSP_DEV) + { + dsp_devs[my_devnum = num_dspdevs++] = &pas_pcm_operations; + sound_dsp_dmachan[my_devnum] = hw_config->dma; + if (hw_config->dma > 3) + { + sound_buffcounts[my_devnum] = 1; + sound_buffsizes[my_devnum] = 2 * 65536; + sound_dma_automode[my_devnum] = 1; + } + else + { + sound_buffcounts[my_devnum] = 1; + sound_buffsizes[my_devnum] = DSP_BUFFSIZE; + sound_dma_automode[my_devnum] = 1; + } + } + else + printk ("PAS2: Too many PCM devices available\n"); + + return mem_start; +} + +void +pas_pcm_interrupt (unsigned char status, int cause) +{ + if (cause == 1) /* PCM buffer done */ + { + /* + * Halt the PCM first. Otherwise we don't have time to start a new + * block before the PCM chip proceeds to the next sample + */ + + if (!sound_dma_automode[my_devnum]) + { + pas_write (pas_read (PCM_CONTROL) & ~P_C_PCM_ENABLE, + PCM_CONTROL); + } + + switch (pcm_mode) + { + + case PCM_DAC: + DMAbuf_outputintr (my_devnum); + break; + + case PCM_ADC: + DMAbuf_inputintr (my_devnum); + break; + + default: + printk ("PAS: Unexpected PCM interrupt\n"); + } + } +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/patmgr.c b/sys/i386/isa/sound/patmgr.c new file mode 100644 index 0000000..48c1004 --- /dev/null +++ b/sys/i386/isa/sound/patmgr.c @@ -0,0 +1,239 @@ +/* + * linux/kernel/chr_drv/sound/patmgr.c + * + * The patch maneger interface for the /dev/sequencer + * + * (C) 1993 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#define PATMGR_C +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SEQUENCER) + +DEFINE_WAIT_QUEUES (server_procs[MAX_SYNTH_DEV], + server_wait_flag[MAX_SYNTH_DEV]); + +static struct patmgr_info *mbox[MAX_SYNTH_DEV] = +{NULL}; +static volatile int msg_direction[MAX_SYNTH_DEV] = +{0}; + +static int pmgr_opened[MAX_SYNTH_DEV] = +{0}; + +#define A_TO_S 1 +#define S_TO_A 2 + +DEFINE_WAIT_QUEUE (appl_proc, appl_wait_flag); + +int +pmgr_open (int dev) +{ + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (pmgr_opened[dev]) + return RET_ERROR (EBUSY); + pmgr_opened[dev] = 1; + + return 0; +} + +void +pmgr_release (int dev) +{ + + if (mbox[dev]) /* Killed in action. Inform the client */ + { + + mbox[dev]->key = PM_ERROR; + mbox[dev]->parm1 = RET_ERROR (EIO); + + if (appl_wait_flag) + WAKE_UP (appl_proc); + } + + pmgr_opened[dev] = 0; +} + +int +pmgr_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + unsigned long flags; + int ok = 0; + + if (count != sizeof (struct patmgr_info)) + { + printk ("PATMGR%d: Invalid read count\n", dev); + return RET_ERROR (EIO); + } + + while (!ok && !PROCESS_ABORTING) + { + DISABLE_INTR (flags); + + while (!(mbox[dev] && msg_direction[dev] == A_TO_S) && !PROCESS_ABORTING) + { + INTERRUPTIBLE_SLEEP_ON (server_procs[dev], server_wait_flag[dev]); + } + + if (mbox[dev] && msg_direction[dev] == A_TO_S) + { + COPY_TO_USER (buf, 0, (char *) mbox[dev], count); + msg_direction[dev] = 0; + ok = 1; + } + + RESTORE_INTR (flags); + + } + + if (!ok) + return RET_ERROR (EINTR); + return count; +} + +int +pmgr_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + unsigned long flags; + + if (count < 4) + { + printk ("PATMGR%d: Write count < 4\n", dev); + return RET_ERROR (EIO); + } + + COPY_FROM_USER (mbox[dev], buf, 0, 4); + + if (*(unsigned char *) mbox[dev] == SEQ_FULLSIZE) + { + int tmp_dev; + + tmp_dev = ((unsigned short *) mbox[dev])[2]; + if (tmp_dev != dev) + return RET_ERROR (ENXIO); + + return synth_devs[dev]->load_patch (dev, *(unsigned short *) mbox[dev], + buf, 4, count, 1); + } + + if (count != sizeof (struct patmgr_info)) + { + printk ("PATMGR%d: Invalid write count\n", dev); + return RET_ERROR (EIO); + } + + /* + * If everything went OK, there should be a preallocated buffer in the + * mailbox and a client waiting. + */ + + DISABLE_INTR (flags); + + if (mbox[dev] && !msg_direction[dev]) + { + COPY_FROM_USER (&((char *) mbox[dev])[4], buf, 4, count - 4); + msg_direction[dev] = S_TO_A; + + if (appl_wait_flag) + { + WAKE_UP (appl_proc); + } + } + + RESTORE_INTR (flags); + + return count; +} + +int +pmgr_access (int dev, struct patmgr_info *rec) +{ + unsigned long flags; + int err = 0; + + DISABLE_INTR (flags); + + if (mbox[dev]) + printk (" PATMGR: Server %d mbox full. Why?\n", dev); + else + { + rec->key = PM_K_COMMAND; + mbox[dev] = rec; + msg_direction[dev] = A_TO_S; + + if (server_wait_flag[dev]) + { + WAKE_UP (server_procs[dev]); + } + + INTERRUPTIBLE_SLEEP_ON (appl_proc, appl_wait_flag); + + if (msg_direction[dev] != S_TO_A) + { + rec->key = PM_ERROR; + rec->parm1 = RET_ERROR (EIO); + } + else if (rec->key == PM_ERROR) + { + err = rec->parm1; + if (err > 0) + err = -err; + } + + mbox[dev] = NULL; + msg_direction[dev] = 0; + } + + RESTORE_INTR (flags); + + return err; +} + +int +pmgr_inform (int dev, int event, unsigned long p1, unsigned long p2, + unsigned long p3, unsigned long p4) +{ + unsigned long flags; + int err = 0; + + if (!pmgr_opened[dev]) + return 0; + + DISABLE_INTR (flags); + + if (mbox[dev]) + printk (" PATMGR: Server %d mbox full. Why?\n", dev); + else + { + mbox[dev] = + (struct patmgr_info *) KERNEL_MALLOC (sizeof (struct patmgr_info)); + + mbox[dev]->key = PM_K_EVENT; + mbox[dev]->command = event; + mbox[dev]->parm1 = p1; + mbox[dev]->parm2 = p2; + mbox[dev]->parm3 = p3; + msg_direction[dev] = A_TO_S; + + if (server_wait_flag[dev]) + { + WAKE_UP (server_procs[dev]); + } + + INTERRUPTIBLE_SLEEP_ON (appl_proc, appl_wait_flag); + if (mbox[dev]) + KERNEL_FREE (mbox[dev]); + mbox[dev] = NULL; + msg_direction[dev] = 0; + } + + RESTORE_INTR (flags); + + return err; +} + +#endif diff --git a/sys/i386/isa/sound/pro_midi.c b/sys/i386/isa/sound/pro_midi.c new file mode 100644 index 0000000..b7f6b9a --- /dev/null +++ b/sys/i386/isa/sound/pro_midi.c @@ -0,0 +1,155 @@ +/* UWM -- comments to soft-eng@cs.uwm.edu */ +#define ALL_EXTERNAL_TO_ME +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "pas.h" +#define ESUCCESS 0 + +#if !defined(EXCLUDE_PRO_MIDI) && !defined(EXCLUDE_CHIP_MIDI) + + +/** Structure for handling operations **/ + + +static struct generic_midi_operations pro_midi_operations = { + + {"Pro_Audio_Spectrum 16 MV101", 0}, + pro_midi_open, + pro_midi_close, + pro_midi_write, + pro_midi_read +}; + +/* + * Note! Note! Note! + * Follow the same model for any other attach function you + * may write + */ + +long pro_midi_attach( long mem_start) +{ + pro_midi_dev = num_generic_midis; + generic_midi_devs[num_generic_midis++] = &pro_midi_operations; + return mem_start; +} + +int pro_midi_open(int dev, int mode) +{ + + int intr_mask, s; + + + s = splhigh(); + + + /* Reset the input and output FIFO pointers */ + + + outb(MIDI_CONTROL,M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO); + + /* Get the interrupt status */ + + intr_mask = inb(INTERRUPT_MASK); + + + /* Enable MIDI IRQ */ + + intr_mask |= I_M_MIDI_IRQ_ENABLE; + outb(INTERRUPT_MASK, intr_mask); + + + /* Enable READ/WRITE on MIDI port. This part is quite unsure though */ + + outb(MIDI_CONTROL,M_C_ENA_OUTPUT_IRQ | M_C_ENA_INPUT_IRQ); + + /* Acknowledge pending interrupts */ + + outb(MIDI_STATUS,0xff); + + + splx(s); + + return(ESUCCESS); + + +} + + +void pro_midi_close(int dev) +{ + + int intr_mask; + + /* Clean up */ + + outb(MIDI_CONTROL,M_C_RESET_INPUT_FIFO | M_C_RESET_OUTPUT_FIFO); + intr_mask = inb(INTERRUPT_MASK); + intr_mask &= ~I_M_MIDI_IRQ_ENABLE; + outb(INTERRUPT_MASK,intr_mask); + + return; +} + +int pro_midi_write(int dev, struct uio *uio) +{ + + int s; + unsigned char data; + + /* printf("midi: Going to do write routine..\n"); */ + while(uio->uio_resid) { + + if ( uiomove(&data,1,uio) ) return(ENOTTY); + + s = splhigh(); + + DELAY(30); + outb(MIDI_DATA,data); + DELAY(70); /* Ze best pause.. find a better one if + * you can :) + */ + splx(s); + } + + return(ESUCCESS); + +} + + +int pro_midi_read(int dev, struct uio *uio) +{ + + int s; + unsigned char data; + + s = splhigh(); + + /* For each uio_iov[] entry .... */ + + while (uio->uio_resid) { + + if((( inb(MIDI_STATUS) & M_S_INPUT_AVAIL) == 0 ) && + ((inb(MIDI_FIFO_STATUS) & MIDI_INPUT_AVAILABLE) == 0 ) ) + + data = 0xfe; + else + data = inb(MIDI_DATA); + + if ( uiomove(&data, 1 , uio)) { + + printf("midi: Bad copyout()!\n"); + return(ENOTTY); + + } + + } + splx(s); + return(ESUCCESS); + +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/sb_card.c b/sys/i386/isa/sound/sb_card.c new file mode 100644 index 0000000..b9d0701 --- /dev/null +++ b/sys/i386/isa/sound/sb_card.c @@ -0,0 +1,33 @@ + +/* + * linux/kernel/chr_drv/sound/sb_card.c + * + * Detection routine for the SoundBlaster cards. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB) + +long +attach_sb_card (long mem_start, struct address_info *hw_config) +{ +#if !defined(EXCLUDE_AUDIO) || !defined(EXCLUDE_MIDI) + if (!sb_dsp_detect (hw_config)) + return mem_start; + mem_start = sb_dsp_init (mem_start, hw_config); +#endif + + return mem_start; +} + +int +probe_sb (struct address_info *hw_config) +{ + return sb_dsp_detect (hw_config); +} + +#endif diff --git a/sys/i386/isa/sound/sb_dsp.c b/sys/i386/isa/sound/sb_dsp.c new file mode 100644 index 0000000..d1adbde --- /dev/null +++ b/sys/i386/isa/sound/sb_dsp.c @@ -0,0 +1,1303 @@ +/* + * linux/kernel/chr_drv/sound/sb_dsp.c + * + * The low level driver for the SoundBlaster DS chips. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + * + * The mixer support is based on the SB-BSD 1.5 driver by (C) Steve Haehnichen + * <shaehnic@ucsd.edu> + */ + +#include "sound_config.h" + +#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB) + +#undef SB_TEST_IRQ + +#define DSP_RESET (sbc_base + 0x6) +#define DSP_READ (sbc_base + 0xA) +#define DSP_WRITE (sbc_base + 0xC) +#define DSP_COMMAND (sbc_base + 0xC) +#define DSP_STATUS (sbc_base + 0xC) +#define DSP_DATA_AVAIL (sbc_base + 0xE) +#define MIXER_ADDR (sbc_base + 0x4) +#define MIXER_DATA (sbc_base + 0x5) +#define OPL3_LEFT (sbc_base + 0x0) +#define OPL3_RIGHT (sbc_base + 0x2) +#define OPL3_BOTH (sbc_base + 0x8) + +static int sbc_base = 0; +static int sbc_irq = 0; + +#define POSSIBLE_RECORDING_DEVICES (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD) + +#define SUPPORTED_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_CD | SOUND_MASK_VOLUME) + +/* + * Mixer registers + * + * NOTE! RECORD_SRC == IN_FILTER + */ + +#define VOC_VOL 0x04 +#define MIC_VOL 0x0A +#define MIC_MIX 0x0A +#define RECORD_SRC 0x0C +#define IN_FILTER 0x0C +#define OUT_FILTER 0x0E +#define MASTER_VOL 0x22 +#define FM_VOL 0x26 +#define CD_VOL 0x28 +#define LINE_VOL 0x2E + +#define FREQ_HI (1 << 3)/* Use High-frequency ANFI filters */ +#define FREQ_LOW 0 /* Use Low-frequency ANFI filters */ +#define FILT_ON 0 /* Yes, 0 to turn it on, 1 for off */ +#define FILT_OFF (1 << 5) + +/* Convenient byte masks */ +#define B1(x) ((x) & 0x01) +#define B2(x) ((x) & 0x03) +#define B3(x) ((x) & 0x07) +#define B4(x) ((x) & 0x0f) +#define B5(x) ((x) & 0x1f) +#define B6(x) ((x) & 0x3f) +#define B7(x) ((x) & 0x7f) +#define B8(x) ((x) & 0xff) +#define F(x) (!!(x)) /* 0 or 1 only */ + +#define MONO_DAC 0x00 +#define STEREO_DAC 0x02 + +/* DSP Commands */ + +#define DSP_CMD_SPKON 0xD1 +#define DSP_CMD_SPKOFF 0xD3 + +/* + * The DSP channel can be used either for input or output. Variable + * 'irq_mode' will be set when the program calls read or write first time + * after open. Current version doesn't support mode changes without closing + * and reopening the device. Support for this feature may be implemented in a + * future version of this driver. + */ + +#define IMODE_NONE 0 +#define IMODE_OUTPUT 1 +#define IMODE_INPUT 2 +#define IMODE_INIT 3 +#define IMODE_MIDI 4 + +#define NORMAL_MIDI 0 +#define UART_MIDI 1 + +static int sb_dsp_ok = 0; /* Set to 1 after successful initialization */ +static int midi_disabled = 0; +static int dsp_highspeed = 0, dsp_stereo = 0; +static int dsp_current_speed = DSP_DEFAULT_SPEED; + +#ifndef EXCLUDE_SBPRO +static int rec_devices = SOUND_MASK_MIC; +static int hi_filter = 0, filter_in = 0, filter_out = 0; + +#endif + +static int midi_mode = NORMAL_MIDI; +static int midi_busy = 0; /* 1 if the process has output to MIDI */ +static int dsp_busy = 0; + +static volatile int irq_mode = IMODE_NONE; /* IMODE_INPUT, IMODE_OUTPUT + * or IMODE_NONE */ +static volatile int irq_ok = 0; + +static int dsp_model = 1; /* 1=SB, 2=SB Pro */ +static int duplex_midi = 0; +static int my_dev = 0; + +static volatile int intr_active = 0; + +static int dsp_speed (int); +static int dsp_set_stereo (int mode); +static int dsp_command (unsigned char val); + +#ifndef EXCLUDE_SBPRO +static void setmixer (unsigned char port, unsigned char value); +static int getmixer (unsigned char port); +static void init_mixer (void); +static int detect_mixer (void); + +#endif + +#if !defined(EXCLUDE_MIDI) || !defined(EXCLUDE_AUDIO) + +/* Common code for the midi and pcm functions */ + +static int +dsp_command (unsigned char val) +{ + int i, limit; + + limit = GET_TIME () + 10; /* The timeout is 0.1 secods */ + + /* + * Note! the i<5000000 is an emergency exit. The dsp_command() is sometimes + * called while interrupts are disabled. This means that the timer is + * disabled also. However the timeout situation is a abnormal condition. + * Normally the DSP should be ready to accept commands after just couple of + * loops. + */ + + for (i = 0; i < 5000000 && GET_TIME () < limit; i++) + { + if ((INB (DSP_STATUS) & 0x80) == 0) + { + OUTB (val, DSP_COMMAND); + return 1; + } + } + + printk ("SoundBlaster: DSP Command(%02x) Timeout.\n", val); + printk ("IRQ conflict???\n"); + return 0; +} + +void +sbintr (int unused) +{ + int status, data; + + status = INB (DSP_DATA_AVAIL);/* Clear interrupt */ + + if (intr_active) + switch (irq_mode) + { + case IMODE_OUTPUT: + intr_active = 0; + DMAbuf_outputintr (my_dev); + break; + + case IMODE_INPUT: + intr_active = 0; + DMAbuf_inputintr (my_dev); + /* A complete buffer has been input. Let's start new one */ + break; + + case IMODE_INIT: + intr_active = 0; + irq_ok = 1; + break; + + case IMODE_MIDI: + printk ("+"); + data = INB (DSP_READ); + printk ("%02x", data); + + break; + + default: + printk ("SoundBlaster: Unexpected interrupt\n"); + } +} + +static int +set_dsp_irq (int interrupt_level) +{ + int retcode; + +#ifdef linux + struct sigaction sa; + + sa.sa_handler = sbintr; + +#ifdef SND_SA_INTERRUPT + sa.sa_flags = SA_INTERRUPT; +#else + sa.sa_flags = 0; +#endif + + sa.sa_mask = 0; + sa.sa_restorer = NULL; + + retcode = irqaction (interrupt_level, &sa); + + if (retcode < 0) + { + printk ("SoundBlaster: IRQ%d already in use\n", interrupt_level); + } + +#else + /* # error Unimplemented for this OS */ +#endif + return retcode; +} + +static int +reset_dsp (void) +{ + int loopc; + + OUTB (1, DSP_RESET); + tenmicrosec (); + OUTB (0, DSP_RESET); + tenmicrosec (); + tenmicrosec (); + tenmicrosec (); + + for (loopc = 0; loopc < 1000 && !(INB (DSP_DATA_AVAIL) & 0x80); loopc++); /* Wait for data + * available status */ + + if (INB (DSP_READ) != 0xAA) + return 0; /* Sorry */ + + return 1; +} + +#endif + +#ifndef EXCLUDE_AUDIO + +static void +dsp_speaker (char state) +{ + if (state) + dsp_command (DSP_CMD_SPKON); + else + dsp_command (DSP_CMD_SPKOFF); +} + +static int +dsp_speed (int speed) +{ + unsigned char tconst; + unsigned long flags; + + + if (speed < 4000) + speed = 4000; + + if (speed > 44100) + speed = 44100; /* Invalid speed */ + + if (dsp_model == 1 && speed > 22050) + speed = 22050; + /* SB Classic doesn't support higher speed */ + + + if (dsp_stereo && speed > 22050) + speed = 22050; + /* Max. stereo speed is 22050 */ + + if ((speed > 22050) && midi_busy) + { + printk ("SB Warning: High speed DSP not possible simultaneously with MIDI output\n"); + speed = 22050; + } + + if (dsp_stereo) + speed <<= 1; + + /* Now the speed should be valid */ + + if (speed > 22050) + { /* High speed mode */ + tconst = (unsigned char) ((65536 - (256000000 / speed)) >> 8); + dsp_highspeed = 1; + + DISABLE_INTR (flags); + if (dsp_command (0x40)) + dsp_command (tconst); + RESTORE_INTR (flags); + + speed = (256000000 / (65536 - (tconst << 8))); + } + else + { + dsp_highspeed = 0; + tconst = (256 - (1000000 / speed)) & 0xff; + + DISABLE_INTR (flags); + if (dsp_command (0x40)) /* Set time constant */ + dsp_command (tconst); + RESTORE_INTR (flags); + + speed = 1000000 / (256 - tconst); + } + + if (dsp_stereo) + speed >>= 1; + + dsp_current_speed = speed; + return speed; +} + +static int +dsp_set_stereo (int mode) +{ + dsp_stereo = 0; + + if (dsp_model == 1) + return 0; /* Sorry no stereo */ + + if (mode && midi_busy) + { + printk ("SB Warning: Stereo DSP not possible simultaneously with MIDI output\n"); + return 0; + } + + dsp_stereo = !!mode; + +#ifndef EXCLUDE_SBPRO + setmixer (OUT_FILTER, ((getmixer (OUT_FILTER) & ~STEREO_DAC) + | (mode ? STEREO_DAC : MONO_DAC))); +#endif + dsp_speed (dsp_current_speed);/* Speed must be recalculated if #channels + * changes */ + return mode; +} + +static void +sb_dsp_output_block (int dev, unsigned long buf, int count, int intrflag) +{ + unsigned long flags; + + if (!irq_mode) + dsp_speaker (ON); + + irq_mode = IMODE_OUTPUT; + DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + count--; + + if (dsp_highspeed) + { + DISABLE_INTR (flags); + if (dsp_command (0x48)) /* High speed size */ + { + dsp_command (count & 0xff); + dsp_command ((count >> 8) & 0xff); + dsp_command (0x91); /* High speed 8 bit DAC */ + } + else + printk ("SB Error: Unable to start (high speed) DAC\n"); + RESTORE_INTR (flags); + } + else + { + DISABLE_INTR (flags); + if (dsp_command (0x14)) /* 8-bit DAC (DMA) */ + { + dsp_command (count & 0xff); + dsp_command ((count >> 8) & 0xff); + } + else + printk ("SB Error: Unable to start DAC\n"); + RESTORE_INTR (flags); + } + intr_active = 1; +} + +static void +sb_dsp_start_input (int dev, unsigned long buf, int count, int intrflag) +{ + /* Start a DMA input to the buffer pointed by dmaqtail */ + + unsigned long flags; + + if (!irq_mode) + dsp_speaker (OFF); + + irq_mode = IMODE_INPUT; + DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); + + if (sound_dsp_dmachan[dev] > 3) + count >>= 1; + count--; + + if (dsp_highspeed) + { + DISABLE_INTR (flags); + if (dsp_command (0x48)) /* High speed size */ + { + dsp_command (count & 0xff); + dsp_command ((count >> 8) & 0xff); + dsp_command (0x99); /* High speed 8 bit ADC */ + } + else + printk ("SB Error: Unable to start (high speed) ADC\n"); + RESTORE_INTR (flags); + } + else + { + DISABLE_INTR (flags); + if (dsp_command (0x24)) /* 8-bit ADC (DMA) */ + { + dsp_command (count & 0xff); + dsp_command ((count >> 8) & 0xff); + } + else + printk ("SB Error: Unable to start ADC\n"); + RESTORE_INTR (flags); + } + + intr_active = 1; +} + +static void +dsp_cleanup (void) +{ + intr_active = 0; +} + +static int +sb_dsp_prepare_for_input (int dev, int bsize, int bcount) +{ + dsp_cleanup (); + dsp_speaker (OFF); + return 0; +} + +static int +sb_dsp_prepare_for_output (int dev, int bsize, int bcount) +{ + dsp_cleanup (); + dsp_speaker (ON); + return 0; +} + +static void +sb_dsp_halt_xfer (int dev) +{ +} + +static int +sb_dsp_open (int dev, int mode) +{ + int retval; + + if (!sb_dsp_ok) + { + printk ("SB Error: SoundBlaster board not installed\n"); + return RET_ERROR (ENXIO); + } + + if (!irq_ok) + { + printk ("SB Error: Incorrect IRQ setting (%d)\n", sbc_irq); + return RET_ERROR (ENXIO); + } + + if (intr_active || (midi_busy && midi_mode == UART_MIDI)) + { + printk ("SB: PCM not possible during MIDI input\n"); + return RET_ERROR (EBUSY); + } + + if (mode != OPEN_READ && mode != OPEN_WRITE) + { + printk ("SoundBlaster error: DAC and ACD not possible simultaneously\n"); + return RET_ERROR (EINVAL); + } + + retval = set_dsp_irq (sbc_irq); + if (retval) + return retval; + + if (!DMAbuf_open_dma (dev)) + { + RELEASE_IRQ (sbc_irq); + printk ("SB: DMA Busy\n"); + return RET_ERROR (EBUSY); + } + + dsp_set_stereo (OFF); + dsp_speed (DSP_DEFAULT_SPEED); + irq_mode = IMODE_NONE; + + dsp_busy = 1; + + return 0; +} + +static void +sb_dsp_close (int dev) +{ + DMAbuf_close_dma (dev); + RELEASE_IRQ (sbc_irq); + dsp_cleanup (); + dsp_speed (DSP_DEFAULT_SPEED); + dsp_set_stereo (OFF); + dsp_speaker (OFF); + dsp_busy = 0; +} + +static int +sb_dsp_ioctl (int dev, unsigned int cmd, unsigned int arg, int local) +{ + switch (cmd) + { + case SOUND_PCM_WRITE_RATE: + if (local) + return dsp_speed (arg); + return IOCTL_OUT (arg, dsp_speed (IOCTL_IN (arg))); + break; + + case SOUND_PCM_READ_RATE: + if (local) + return dsp_current_speed; + return IOCTL_OUT (arg, dsp_current_speed); + break; + + case SOUND_PCM_WRITE_CHANNELS: + return IOCTL_OUT (arg, dsp_set_stereo (IOCTL_IN (arg) - 1) + 1); + break; + + case SOUND_PCM_READ_CHANNELS: + if (local) + return dsp_stereo + 1; + return IOCTL_OUT (arg, dsp_stereo + 1); + break; + + case SNDCTL_DSP_STEREO: + if (local) + return dsp_set_stereo (arg); + return IOCTL_OUT (arg, dsp_set_stereo (IOCTL_IN (arg))); + break; + + case SOUND_PCM_WRITE_BITS: + case SOUND_PCM_READ_BITS: + if (local) + return 8; + return IOCTL_OUT (arg, 8);/* Only 8 bits/sample supported */ + break; + + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + return RET_ERROR (EINVAL); + break; + + default: + return RET_ERROR (EINVAL); + } + + return RET_ERROR (EINVAL); +} + +static void +sb_dsp_reset (int dev) +{ + unsigned long flags; + + DISABLE_INTR (flags); + + reset_dsp (); + dsp_cleanup (); + + RESTORE_INTR (flags); +} + +#endif + +int +sb_dsp_detect (struct address_info *hw_config) +{ + sbc_base = hw_config->io_base; + sbc_irq = hw_config->irq; + + if (sb_dsp_ok) + return 0; /* Already initialized */ + + if (!reset_dsp ()) + return 0; + + return 1; /* Detected */ +} + +#ifndef EXCLUDE_SBPRO + +static void +setmixer (unsigned char port, unsigned char value) +{ + OUTB (port, MIXER_ADDR); /* Select register */ + tenmicrosec (); + OUTB (value, MIXER_DATA); + tenmicrosec (); +} + +static int +getmixer (unsigned char port) +{ + int val; + + OUTB (port, MIXER_ADDR); /* Select register */ + tenmicrosec (); + val = INB (MIXER_DATA); + tenmicrosec (); + + return val; +} + +static int +detect_mixer (void) +{ + /* + * Detect the mixer by changing parameters of two volume channels. If the + * values read back match with the values written, the mixer is there (is + * it?) + */ + setmixer (FM_VOL, 0xff); + setmixer (VOC_VOL, 0x33); + + if (getmixer (FM_VOL) != 0xff) + return 0; /* No match */ + if (getmixer (VOC_VOL) != 0x33) + return 0; + + return 1; +} + +static void +init_mixer (void) +{ + setmixer (MASTER_VOL, 0xbb); + setmixer (VOC_VOL, 0x99); + setmixer (LINE_VOL, 0xbb); + setmixer (FM_VOL, 0x99); + setmixer (CD_VOL, 0x11); + setmixer (MIC_MIX, 0x11); + setmixer (RECORD_SRC, 0x31); + setmixer (OUT_FILTER, 0x31); +} + +static void +set_filter (int record_source, int hifreq_filter, int filter_input, int filter_output) +{ + setmixer (RECORD_SRC, (record_source + | (hifreq_filter ? FREQ_HI : FREQ_LOW) + | (filter_input ? FILT_ON : FILT_OFF))); + + setmixer (OUT_FILTER, ((dsp_stereo ? STEREO_DAC : MONO_DAC) + | (filter_output ? FILT_ON : FILT_OFF))); + + hi_filter = hifreq_filter; + filter_in = filter_input; + filter_out = filter_output; +} + +static int +mixer_output (int right_vol, int left_vol, int div, int device) +{ + int left = ((left_vol * div) + 50) / 100; + int right = ((right_vol * div) + 50) / 100; + + setmixer (device, ((left & 0xf) << 4) | (right & 0xf)); + + return (left_vol | (right_vol << 8)); +} + +static int +sbp_mixer_set (int whichDev, unsigned int level) +{ + int left, right, devmask; + + left = level & 0x7f; + right = (level & 0x7f00) >> 8; + + switch (whichDev) + { + case SOUND_MIXER_VOLUME: /* Master volume (0-15) */ + return mixer_output (right, left, 15, MASTER_VOL); + break; + case SOUND_MIXER_SYNTH: /* Internal synthesizer (0-15) */ + return mixer_output (right, left, 15, FM_VOL); + break; + case SOUND_MIXER_PCM: /* PAS PCM (0-15) */ + return mixer_output (right, left, 15, VOC_VOL); + break; + case SOUND_MIXER_LINE: /* External line (0-15) */ + return mixer_output (right, left, 15, LINE_VOL); + break; + case SOUND_MIXER_CD: /* CD (0-15) */ + return mixer_output (right, left, 15, CD_VOL); + break; + case SOUND_MIXER_MIC: /* External microphone (0-7) */ + return mixer_output (right, left, 7, MIC_VOL); + break; + + case SOUND_MIXER_RECSRC: + devmask = level & POSSIBLE_RECORDING_DEVICES; + + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && + devmask != SOUND_MASK_CD) + { /* More than one devices selected. Drop the + * previous selection */ + devmask &= ~rec_devices; + } + + if (devmask != SOUND_MASK_MIC && + devmask != SOUND_MASK_LINE && + devmask != SOUND_MASK_CD) + { /* More than one devices selected. Default to + * mic */ + devmask = SOUND_MASK_MIC; + } + + if (devmask ^ rec_devices)/* Input source changed */ + { + switch (devmask) + { + + case SOUND_MASK_MIC: + set_filter (SRC_MIC, hi_filter, filter_in, filter_out); + break; + + case SOUND_MASK_LINE: + set_filter (SRC_LINE, hi_filter, filter_in, filter_out); + break; + + case SOUND_MASK_CD: + set_filter (SRC_CD, hi_filter, filter_in, filter_out); + break; + + default: + set_filter (SRC_MIC, hi_filter, filter_in, filter_out); + } + } + + rec_devices = devmask; + + return rec_devices; + break; + + default: + return RET_ERROR (EINVAL); + } + +} + +static int +mixer_input (int div, int device) +{ + int level, left, right, half; + + level = getmixer (device); + half = div / 2; + + left = ((((level & 0xf0) >> 4) * 100) + half) / div; + right = (((level & 0x0f) * 100) + half) / div; + + return (right << 8) | left; +} + +static int +sbp_mixer_get (int whichDev) +{ + + switch (whichDev) + { + case SOUND_MIXER_VOLUME: /* Master volume (0-15) */ + return mixer_input (15, MASTER_VOL); + break; + case SOUND_MIXER_SYNTH: /* Internal synthesizer (0-15) */ + return mixer_input (15, FM_VOL); + break; + case SOUND_MIXER_PCM: /* PAS PCM (0-15) */ + return mixer_input (15, VOC_VOL); + break; + case SOUND_MIXER_LINE: /* External line (0-15) */ + return mixer_input (15, LINE_VOL); + break; + case SOUND_MIXER_CD: /* CD (0-15) */ + return mixer_input (15, CD_VOL); + break; + case SOUND_MIXER_MIC: /* External microphone (0-7) */ + return mixer_input (7, MIC_VOL); + break; + + default: + return RET_ERROR (EINVAL); + } + +} + +/* + * Sets mixer volume levels. All levels except mic are 0 to 15, mic is 7. See + * sbinfo.doc for details on granularity and such. Basically, the mixer + * forces the lowest bit high, effectively reducing the possible settings by + * one half. Yes, that's right, volume levels have 8 settings, and + * microphone has four. Sucks. + */ +static int +mixer_set_levels (struct sb_mixer_levels *user_l) +{ + struct sb_mixer_levels l; + + IOCTL_FROM_USER ((char *) &l, ((char *) user_l), 0, sizeof (l)); + + if (l.master.l & ~0xF || l.master.r & ~0xF + || l.line.l & ~0xF || l.line.r & ~0xF + || l.voc.l & ~0xF || l.voc.r & ~0xF + || l.fm.l & ~0xF || l.fm.r & ~0xF + || l.cd.l & ~0xF || l.cd.r & ~0xF + || l.mic & ~0x7) + return (RET_ERROR (EINVAL)); + + setmixer (MASTER_VOL, (l.master.l << 4) | l.master.r); + setmixer (LINE_VOL, (l.line.l << 4) | l.line.r); + setmixer (VOC_VOL, (l.voc.l << 4) | l.voc.r); + setmixer (FM_VOL, (l.fm.l << 4) | l.fm.r); + setmixer (CD_VOL, (l.cd.l << 4) | l.cd.r); + setmixer (MIC_VOL, l.mic); + return (0); +} + +/* + * This sets aspects of the Mixer that are not volume levels. (Recording + * source, filter level, I/O filtering, and stereo.) + */ + +static int +mixer_set_params (struct sb_mixer_params *user_p) +{ + struct sb_mixer_params p; + + IOCTL_FROM_USER ((char *) &p, (char *) user_p, 0, sizeof (p)); + + if (p.record_source != SRC_MIC + && p.record_source != SRC_CD + && p.record_source != SRC_LINE) + return (EINVAL); + + /* + * I'm not sure if this is The Right Thing. Should stereo be entirely + * under control of DSP? I like being able to toggle it while a sound is + * playing, so I do this... because I can. + */ + + dsp_stereo = !!p.dsp_stereo; + + set_filter (p.record_source, p.hifreq_filter, p.filter_input, p.filter_output); + + switch (p.record_source) + { + + case SRC_MIC: + rec_devices = SOUND_MASK_MIC; + break; + + case SRC_LINE: + rec_devices = SOUND_MASK_LINE; + break; + + case SRC_CD: + rec_devices = SOUND_MASK_CD; + } + + return (0); +} + +/* Read the current mixer level settings into the user's struct. */ +static int +mixer_get_levels (struct sb_mixer_levels *user_l) +{ + S_BYTE val; + struct sb_mixer_levels l; + + val = getmixer (MASTER_VOL); /* Master */ + l.master.l = B4 (val >> 4); + l.master.r = B4 (val); + + val = getmixer (LINE_VOL); /* FM */ + l.line.l = B4 (val >> 4); + l.line.r = B4 (val); + + val = getmixer (VOC_VOL); /* DAC */ + l.voc.l = B4 (val >> 4); + l.voc.r = B4 (val); + + val = getmixer (FM_VOL); /* FM */ + l.fm.l = B4 (val >> 4); + l.fm.r = B4 (val); + + val = getmixer (CD_VOL); /* CD */ + l.cd.l = B4 (val >> 4); + l.cd.r = B4 (val); + + val = getmixer (MIC_VOL); /* Microphone */ + l.mic = B3 (val); + + IOCTL_TO_USER ((char *) user_l, 0, (char *) &l, sizeof (l)); + + return (0); +} + +/* Read the current mixer parameters into the user's struct. */ +static int +mixer_get_params (struct sb_mixer_params *user_params) +{ + S_BYTE val; + struct sb_mixer_params params; + + val = getmixer (RECORD_SRC); + params.record_source = val & 0x07; + params.hifreq_filter = !!(val & FREQ_HI); + params.filter_input = (val & FILT_OFF) ? OFF : ON; + params.filter_output = (getmixer (OUT_FILTER) & FILT_OFF) ? OFF : ON; + params.dsp_stereo = dsp_stereo; + + IOCTL_TO_USER ((char *) user_params, 0, (char *) ¶ms, sizeof (params)); + return (0); +} + +static int +sb_mixer_ioctl (int dev, unsigned int cmd, unsigned int arg) +{ + if (((cmd >> 8) & 0xff) == 'M') + { + if (cmd & IOC_IN) + return IOCTL_OUT (arg, sbp_mixer_set (cmd & 0xff, IOCTL_IN (arg))); + else + { /* Read parameters */ + + switch (cmd & 0xff) + { + + case SOUND_MIXER_RECSRC: + return IOCTL_OUT (arg, rec_devices); + break; + + case SOUND_MIXER_DEVMASK: + return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES); + break; + + case SOUND_MIXER_STEREODEVS: + return IOCTL_OUT (arg, SUPPORTED_MIXER_DEVICES & ~SOUND_MASK_MIC); + break; + + case SOUND_MIXER_RECMASK: + return IOCTL_OUT (arg, POSSIBLE_RECORDING_DEVICES); + break; + + case SOUND_MIXER_CAPS: + return IOCTL_OUT (arg, SOUND_CAP_EXCL_INPUT); + break; + + default: + return IOCTL_OUT (arg, sbp_mixer_get (cmd & 0xff)); + } + } + } + else + { + switch (cmd) + { + case MIXER_IOCTL_SET_LEVELS: + return (mixer_set_levels ((struct sb_mixer_levels *) arg)); + case MIXER_IOCTL_SET_PARAMS: + return (mixer_set_params ((struct sb_mixer_params *) arg)); + case MIXER_IOCTL_READ_LEVELS: + return (mixer_get_levels ((struct sb_mixer_levels *) arg)); + case MIXER_IOCTL_READ_PARAMS: + return (mixer_get_params ((struct sb_mixer_params *) arg)); + case MIXER_IOCTL_RESET: + init_mixer (); + return (0); + default: + return RET_ERROR (EINVAL); + } + } +} + +/* End of mixer code */ +#endif + +#ifndef EXCLUDE_MIDI + +/* Midi code */ + +static int +sb_midi_open (int dev, int mode) +{ + int ret; + + if (!sb_dsp_ok) + { + printk ("SB Error: MIDI hardware not installed\n"); + return RET_ERROR (ENXIO); + } + + if (mode != OPEN_WRITE && !duplex_midi) + { + printk ("SoundBlaster: Midi input not currently supported\n"); + return RET_ERROR (EPERM); + } + + midi_mode = NORMAL_MIDI; + if (mode != OPEN_WRITE) + { + if (dsp_busy || intr_active) + return RET_ERROR (EBUSY); + midi_mode = UART_MIDI; + } + + if (dsp_highspeed || dsp_stereo) + { + printk ("SB Error: Midi output not possible during stereo or high speed audio\n"); + return RET_ERROR (EBUSY); + } + + if (midi_mode == UART_MIDI) + { + irq_mode = IMODE_MIDI; + + reset_dsp (); + dsp_speaker (OFF); + + if (!dsp_command (0x35)) + return RET_ERROR (EIO); /* Enter the UART mode */ + intr_active = 1; + + if ((ret = set_dsp_irq (sbc_irq)) < 0) + { + reset_dsp (); + return 0; /* IRQ not free */ + } + } + + midi_busy = 1; + + return 0; +} + +static void +sb_midi_close (int dev) +{ + if (midi_mode == UART_MIDI) + { + reset_dsp (); /* The only way to kill the UART mode */ + RELEASE_IRQ (sbc_irq); + } + intr_active = 0; + midi_busy = 0; +} + +static int +sb_midi_out (int dev, unsigned char midi_byte) +{ + unsigned long flags; + + midi_busy = 1; /* Kill all notes after close */ + + if (midi_mode == NORMAL_MIDI) + { + DISABLE_INTR (flags); + if (dsp_command (0x38)) + dsp_command (midi_byte); + else + printk ("SB Error: Unable to send a MIDI byte\n"); + RESTORE_INTR (flags); + } + else + dsp_command (midi_byte); /* UART write */ + + return 1; +} + +static int +sb_midi_start_read (int dev) +{ + if (midi_mode != UART_MIDI) + { + printk ("SoundBlaster: MIDI input not implemented.\n"); + return RET_ERROR (EPERM); + } + return 0; +} + +static int +sb_midi_end_read (int dev) +{ + if (midi_mode == UART_MIDI) + { + reset_dsp (); + intr_active = 0; + } + return 0; +} + +static int +sb_midi_ioctl (int dev, unsigned cmd, unsigned arg) +{ + return RET_ERROR (EPERM); +} + +/* End of midi code */ +#endif + +#ifndef EXCLUDE_AUDIO +static struct audio_operations sb_dsp_operations = +{ + "SoundBlaster", + sb_dsp_open, + sb_dsp_close, + sb_dsp_output_block, + sb_dsp_start_input, + sb_dsp_ioctl, + sb_dsp_prepare_for_input, + sb_dsp_prepare_for_output, + sb_dsp_reset, + sb_dsp_halt_xfer, + NULL, /* has_output_drained */ + NULL /* copy_from_user */ +}; + +#endif + +#ifndef EXCLUDE_SBPRO +static struct mixer_operations sb_mixer_operations = +{ + sb_mixer_ioctl +}; + +#endif + +#ifndef EXCLUDE_MIDI +static struct midi_operations sb_midi_operations = +{ + {"SoundBlaster", 0}, + sb_midi_open, + sb_midi_close, + sb_midi_ioctl, + sb_midi_out, + sb_midi_start_read, + sb_midi_end_read, + NULL, /* Kick */ + NULL, /* command */ + NULL /* buffer_status */ +}; + +#endif + +static int +verify_irq (void) +{ +#if 0 + unsigned long loop; + + irq_ok = 0; + + if (set_dsp_irq (sbc_irq) == -1) + { + printk ("*** SB Error: Irq %d already in use\n", sbc_irq); + return 0; + } + + + irq_mode = IMODE_INIT; + + dsp_command (0xf2); /* This should cause immediate interrupt */ + + for (loop = 100000; loop > 0 && !irq_ok; loop--); + + RELEASE_IRQ (sbc_irq); + + if (!irq_ok) + { + printk ("SB Warning: IRQ test not passed!"); + irq_ok = 1; + } +#else + irq_ok = 1; +#endif + return irq_ok; +} + +long +sb_dsp_init (long mem_start, struct address_info *hw_config) +{ + int i, major, minor; + + major = minor = 0; + dsp_command (0xe1); /* Get version */ + + for (i = 1000; i; i--) + { + if (inb (DSP_DATA_AVAIL) & 0x80) + { /* wait for Data Ready */ + if (major == 0) + major = inb (DSP_READ); + else + { + minor = inb (DSP_READ); + break; + } + } + } + +#ifndef EXCLUDE_SBPRO + if (detect_mixer ()) + { + sprintf (sb_dsp_operations.name, "SoundBlaster Pro %d.%d", major, minor); + init_mixer (); +#if SBC_DMA < 4 + /* This is a kludge for SB16 cards */ + if (major == 3) + dsp_model = 2; /* Do not enable if SB16 */ +#endif + mixer_devs[num_mixers++] = &sb_mixer_operations; + + if (major == 2 || major == 3) + duplex_midi = 1; + +#ifndef EXCLUDE_YM8312 + if (major > 3 || (major == 3 && minor > 0)) /* SB Pro2 or later */ + { + enable_opl3_mode (OPL3_LEFT, OPL3_RIGHT, OPL3_BOTH); + } +#endif + } + else +#endif + sprintf (sb_dsp_operations.name, "SoundBlaster %d.%d", major, minor); + + printk (" <%s>", sb_dsp_operations.name); + + if (!verify_irq ()) + return mem_start; + +#ifndef EXCLUDE_AUDIO + if (num_dspdevs < MAX_DSP_DEV) + { + dsp_devs[my_dev = num_dspdevs++] = &sb_dsp_operations; + sound_buffcounts[my_dev] = DSP_BUFFCOUNT; + sound_buffsizes[my_dev] = DSP_BUFFSIZE; + sound_dsp_dmachan[my_dev] = hw_config->dma; + sound_dma_automode[my_dev] = 0; + } + else + printk ("SB: Too many DSP devices available\n"); +#endif + +#ifndef EXCLUDE_MIDI + if (!midi_disabled) /* Midi don't work in the SB emulation mode + * of PAS */ + midi_devs[num_midis++] = &sb_midi_operations; +#endif + + sb_dsp_ok = 1; + printk("\n"); + return mem_start; +} + +void +sb_dsp_disable_midi (void) +{ + midi_disabled = 1; +} + +#endif diff --git a/sys/i386/isa/sound/sequencer.c b/sys/i386/isa/sound/sequencer.c new file mode 100644 index 0000000..2d4ef37 --- /dev/null +++ b/sys/i386/isa/sound/sequencer.c @@ -0,0 +1,1137 @@ +/* + * linux/kernel/chr_drv/sound/sequencer.c + * + * The sequencer personality manager. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) See COPYING for further + * details. Should be distributed with this file. + */ + +#define SEQUENCER_C +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#ifndef EXCLUDE_SEQUENCER + +static int sequencer_ok = 0; + +DEFINE_WAIT_QUEUE (seq_sleeper, seq_sleep_flag); +DEFINE_WAIT_QUEUE (midi_sleeper, midi_sleep_flag); + +static int midi_opened[MAX_MIDI_DEV] = +{0}; /* 1 if the process has opened MIDI */ +static int midi_written[MAX_MIDI_DEV] = +{0}; + +long seq_time = 0; /* Reference point for the timer */ + +#include "tuning.h" + +#define EV_SZ 8 +static unsigned char queue[SEQ_MAX_QUEUE][EV_SZ]; +static unsigned char iqueue[SEQ_MAX_QUEUE][4]; +static volatile int qhead = 0, qtail = 0, qlen = 0; +static volatile int iqhead = 0, iqtail = 0, iqlen = 0; +static volatile int seq_playing = 0; +static int sequencer_busy = 0; +static int output_treshold; +static unsigned synth_open_mask; + +static int seq_queue (unsigned char *note); +static void seq_startplay (void); +static int seq_sync (void); +static void seq_reset (void); +static int pmgr_present[MAX_SYNTH_DEV] = +{0}; + +#if MAX_SYNTH_DEV > 15 +#error Too many synthesizer devices +#endif + +int +sequencer_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + int c = count, p = 0; + + dev = dev >> 4; + + if (dev) /* Patch manager device */ + return pmgr_read (dev - 1, file, buf, count); + + while (c > 3) + { + if (!iqlen) + { + INTERRUPTIBLE_SLEEP_ON (midi_sleeper, midi_sleep_flag); + + if (!iqlen) + return count - c; + } + + COPY_TO_USER (buf, p, &iqueue[iqhead][0], 4); + p += 4; + c -= 4; + + iqhead = (iqhead + 1) % SEQ_MAX_QUEUE; + iqlen--; + } + + return count - c; +} + +void +sequencer_midi_output (int dev) +{ + /* Currently NOP */ +} + +static void +copy_to_input (unsigned char *event) +{ + unsigned long flags; + + if (iqlen >= (SEQ_MAX_QUEUE - 1)) + return; /* Overflow */ + + memcpy (iqueue[iqtail], event, 4); + iqlen++; + iqtail = (iqtail + 1) % SEQ_MAX_QUEUE; + + DISABLE_INTR (flags); + if (midi_sleep_flag) + { + WAKE_UP (midi_sleeper); + } + RESTORE_INTR (flags); +} + +void +sequencer_midi_input (int dev, unsigned char data) +{ + int tstamp; + unsigned char event[4]; + + if (data == 0xfe) /* Active sensing */ + return; /* Ignore */ + + tstamp = GET_TIME () - seq_time; /* Time since open() */ + tstamp = (tstamp << 8) | SEQ_WAIT; + + copy_to_input ((unsigned char *) &tstamp); + + event[0] = SEQ_MIDIPUTC; + event[1] = data; + event[2] = dev; + event[3] = 0; + + copy_to_input (event); +} + +int +sequencer_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + unsigned char event[EV_SZ], ev_code; + int p = 0, c, ev_size; + int err; + int mode = file->mode & O_ACCMODE; + + dev = dev >> 4; + + DEB (printk ("sequencer_write(dev=%d, count=%d)\n", dev, count)); + + if (mode == OPEN_READ) + return RET_ERROR (EIO); + + if (dev) /* Patch manager device */ + return pmgr_write (dev - 1, file, buf, count); + + c = count; + + while (c >= 4) + { + COPY_FROM_USER (event, buf, p, 4); + ev_code = event[0]; + + if (ev_code == SEQ_FULLSIZE) + { + int err; + + dev = *(unsigned short *) &event[2]; + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev))) + return RET_ERROR (ENXIO); + + err = synth_devs[dev]->load_patch (dev, *(short *) &event[0], buf, p + 4, c, 0); + if (err < 0) + return err; + + return err; + } + + if (ev_code == SEQ_EXTENDED || ev_code == SEQ_PRIVATE) + { + + ev_size = 8; + + if (c < ev_size) + { + if (!seq_playing) + seq_startplay (); + return count - c; + } + + COPY_FROM_USER (&event[4], buf, p + 4, 4); + + } + else + ev_size = 4; + + if (event[0] == SEQ_MIDIPUTC) + { + + if (!midi_opened[event[2]]) + { + int mode; + int dev = event[2]; + + if (dev >= num_midis) + { + printk ("Sequencer Error: Nonexistent MIDI device %d\n", dev); + return RET_ERROR (ENXIO); + } + + mode = file->mode & O_ACCMODE; + + if ((err = midi_devs[dev]->open (dev, mode)) < 0) + { + seq_reset (); + printk ("Sequencer Error: Unable to open Midi #%d\n", dev); + return err; + } + + midi_opened[dev] = 1; + } + + } + + if (!seq_queue (event)) + { + + if (!seq_playing) + seq_startplay (); + return count - c; + } + + p += ev_size; + c -= ev_size; + } + + if (!seq_playing) + seq_startplay (); + + return count; +} + +static int +seq_queue (unsigned char *note) +{ + + /* Test if there is space in the queue */ + + if (qlen >= SEQ_MAX_QUEUE) + if (!seq_playing) + seq_startplay (); /* Give chance to drain the queue */ + + if (qlen >= SEQ_MAX_QUEUE && !seq_sleep_flag) + { + /* Sleep until there is enough space on the queue */ + INTERRUPTIBLE_SLEEP_ON (seq_sleeper, seq_sleep_flag); + } + + if (qlen >= SEQ_MAX_QUEUE) + return 0; /* To be sure */ + + memcpy (&queue[qtail][0], note, EV_SZ); + + qtail = (qtail + 1) % SEQ_MAX_QUEUE; + qlen++; + + return 1; +} + +static int +extended_event (unsigned char *q) +{ + int dev = q[2]; + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev))) + return RET_ERROR (ENXIO); + + switch (q[1]) + { + case SEQ_NOTEOFF: + synth_devs[dev]->kill_note (dev, q[3], q[5]); + break; + + case SEQ_NOTEON: + if (q[4] > 127 && q[4] != 255) + return 0; + + synth_devs[dev]->start_note (dev, q[3], q[4], q[5]); + break; + + case SEQ_PGMCHANGE: + synth_devs[dev]->set_instr (dev, q[3], q[4]); + break; + + case SEQ_AFTERTOUCH: + synth_devs[dev]->aftertouch (dev, q[3], q[4]); + break; + + case SEQ_BALANCE: + synth_devs[dev]->panning (dev, q[3], (char) q[4]); + break; + + case SEQ_CONTROLLER: + synth_devs[dev]->controller (dev, q[3], q[4], *(short *) &q[5]); + break; + + default: + return RET_ERROR (EINVAL); + } + + return 0; +} + +static void +seq_startplay (void) +{ + int this_one; + unsigned long *delay; + unsigned char *q; + + while (qlen > 0) + { + qhead = ((this_one = qhead) + 1) % SEQ_MAX_QUEUE; + qlen--; + + q = &queue[this_one][0]; + + switch (q[0]) + { + case SEQ_NOTEOFF: + if (synth_open_mask & (1 << 0)) + if (synth_devs[0]) + synth_devs[0]->kill_note (0, q[1], q[3]); + break; + + case SEQ_NOTEON: + if (q[4] < 128 || q[4] == 255) + if (synth_open_mask & (1 << 0)) + if (synth_devs[0]) + synth_devs[0]->start_note (0, q[1], q[2], q[3]); + break; + + case SEQ_WAIT: + delay = (unsigned long *) q; /* Bytes 1 to 3 are containing the + * delay in GET_TIME() */ + *delay = (*delay >> 8) & 0xffffff; + + if (*delay > 0) + { + long time; + + seq_playing = 1; + time = *delay; + + request_sound_timer (time); + + if ((SEQ_MAX_QUEUE - qlen) >= output_treshold) + { + unsigned long flags; + + DISABLE_INTR (flags); + if (seq_sleep_flag) + { + seq_sleep_flag = 0; + WAKE_UP (seq_sleeper); + } + RESTORE_INTR (flags); + } + return; /* Stop here. Timer routine will continue + * playing after the delay */ + } + break; + + case SEQ_PGMCHANGE: + if (synth_open_mask & (1 << 0)) + if (synth_devs[0]) + synth_devs[0]->set_instr (0, q[1], q[2]); + break; + + case SEQ_SYNCTIMER: /* Reset timer */ + seq_time = GET_TIME (); + break; + + case SEQ_MIDIPUTC: /* Put a midi character */ + if (midi_opened[q[2]]) + { + int dev; + + dev = q[2]; + + if (!midi_devs[dev]->putc (dev, q[1])) + { + /* + * Output FIFO is full. Wait one timer cycle and try again. + */ + + qlen++; + qhead = this_one; /* Restore queue */ + seq_playing = 1; + request_sound_timer (-1); + return; + } + else + midi_written[dev] = 1; + } + break; + + case SEQ_ECHO: + copy_to_input (q); /* Echo back to the process */ + break; + + case SEQ_PRIVATE: + if (q[1] < num_synths) + synth_devs[q[1]]->hw_control (q[1], q); + break; + + case SEQ_EXTENDED: + extended_event (q); + break; + + default:; + } + + } + + seq_playing = 0; + + if ((SEQ_MAX_QUEUE - qlen) >= output_treshold) + { + unsigned long flags; + + DISABLE_INTR (flags); + if (seq_sleep_flag) + { + seq_sleep_flag = 0; + WAKE_UP (seq_sleeper); + } + RESTORE_INTR (flags); + } + +} + +int +sequencer_open (int dev, struct fileinfo *file) + { + int retval, mode, i; + + dev = dev >> 4; + mode = file->mode & O_ACCMODE; + + DEB (printk ("sequencer_open(dev=%d)\n", dev)); + + if (!sequencer_ok) + { + printk ("Soundcard: Sequencer not initialized\n"); + return RET_ERROR (ENXIO); + } + + if (dev) /* Patch manager device */ + { + int err; + + dev--; + if (pmgr_present[dev]) + return RET_ERROR (EBUSY); + if ((err = pmgr_open (dev)) < 0) + return err; /* Failed */ + + pmgr_present[dev] = 1; + return err; + } + + if (sequencer_busy) + { + printk ("Sequencer busy\n"); + return RET_ERROR (EBUSY); + } + + if (!(num_synths + num_midis)) + return RET_ERROR (ENXIO); + + synth_open_mask = 0; + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + for (i = 0; i < num_synths; i++) /* Open synth devices */ + if (synth_devs[i]->open (i, mode) < 0) + printk ("Sequencer: Warning! Cannot open synth device #%d\n", i); + else + synth_open_mask |= (1 << i); + + seq_time = GET_TIME (); + + for (i = 0; i < num_midis; i++) + { + midi_opened[i] = 0; + midi_written[i] = 0; + } + + if (mode == OPEN_READ || mode == OPEN_READWRITE) + { /* Initialize midi input devices */ + if (!num_midis) + { + printk ("Sequencer: No Midi devices. Input not possible\n"); + return RET_ERROR (ENXIO); + } + + for (i = 0; i < num_midis; i++) + { + if ((retval = midi_devs[i]->open (i, mode)) >= 0) + midi_opened[i] = 1; + } + } + + sequencer_busy = 1; + seq_sleep_flag = midi_sleep_flag = 0; + output_treshold = SEQ_MAX_QUEUE / 2; + + for (i = 0; i < num_synths; i++) + if (pmgr_present[i]) + pmgr_inform (i, PM_E_OPENED, 0, 0, 0, 0); + + return 0; + } + +void +seq_drain_midi_queues (void) +{ + int i, n; + + /* + * Give the Midi drivers time to drain their output queues + */ + + n = 1; + + while (!PROCESS_ABORTING && n) + { + n = 0; + + for (i = 0; i < num_midis; i++) + if (midi_opened[i] && midi_written[i]) + if (midi_devs[i]->buffer_status != NULL) + if (midi_devs[i]->buffer_status (i)) + n++; + + /* + * Let's have a delay + */ + if (n) + { + REQUEST_TIMEOUT (HZ / 10, seq_sleeper); + INTERRUPTIBLE_SLEEP_ON (seq_sleeper, seq_sleep_flag); + } + } +} + +void +sequencer_release (int dev, struct fileinfo *file) + { + int i; + int mode = file->mode & O_ACCMODE; + + dev = dev >> 4; + + DEB (printk ("sequencer_release(dev=%d)\n", dev)); + + if (dev) /* Patch manager device */ + { + dev--; + pmgr_release (dev); + pmgr_present[dev] = 0; + return; + } + + /* + * Wait until the queue is empty + */ + while (!PROCESS_ABORTING && qlen) + { + seq_sync (); + } + + if (mode != OPEN_READ) + seq_drain_midi_queues (); /* Ensure the output queues are empty */ + seq_reset (); + if (mode != OPEN_READ) + seq_drain_midi_queues (); /* Flush the all notes off messages */ + + for (i = 0; i < num_midis; i++) + if (midi_opened[i]) + midi_devs[i]->close (i); + + if (mode == OPEN_WRITE || mode == OPEN_READWRITE) + for (i = 0; i < num_synths; i++) + if (synth_open_mask & (1 << i)) /* Actually opened */ + if (synth_devs[i]) + synth_devs[i]->close (i); + + for (i = 0; i < num_synths; i++) + if (pmgr_present[i]) + pmgr_inform (i, PM_E_CLOSED, 0, 0, 0, 0); + + sequencer_busy = 0; + } + +static int +seq_sync (void) +{ + if (qlen && !seq_playing && !PROCESS_ABORTING) + seq_startplay (); + + if (qlen && !seq_sleep_flag) /* Queue not empty */ + { + INTERRUPTIBLE_SLEEP_ON (seq_sleeper, seq_sleep_flag); + } + + return qlen; +} + +static void +midi_outc (int dev, unsigned char data) +{ + /* + * NOTE! Calls sleep(). Don't call this from interrupt. + */ + + int n; + + /* This routine sends one byte to the Midi channel. */ + /* If the output Fifo is full, it waits until there */ + /* is space in the queue */ + + n = 300; /* Timeout in jiffies */ + + while (n && !midi_devs[dev]->putc (dev, data)) + { + REQUEST_TIMEOUT (1, seq_sleeper); + INTERRUPTIBLE_SLEEP_ON (seq_sleeper, seq_sleep_flag); + n--; + } +} + +static void +seq_reset (void) +{ + /* + * NOTE! Calls sleep(). Don't call this from interrupt. + */ + + int i, chn; + + sound_stop_timer (); + + qlen = qhead = qtail = 0; + iqlen = iqhead = iqtail = 0; + + for (i = 0; i < num_synths; i++) + if (synth_open_mask & (1 << i)) + if (synth_devs[i]) + synth_devs[i]->reset (i); + + for (i = 0; i < num_midis; i++) + if (midi_written[i]) /* Midi used. Some notes may still be playing */ + { + for (chn = 0; chn < 16; chn++) + { + midi_outc (i, 0xb0 + chn); /* Channel message */ + midi_outc (i, 0x7b);/* All notes off */ + midi_outc (i, 0); /* Dummy parameter */ + } + + midi_devs[i]->close (i); + + midi_written[i] = 0; + midi_opened[i] = 0; + } + + seq_playing = 0; + + if (seq_sleep_flag) + printk ("Sequencer Warning: Unexpected sleeping process\n"); + +} + +int +sequencer_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + int midi_dev, orig_dev; + int mode = file->mode & O_ACCMODE; + + orig_dev = dev = dev >> 4; + + switch (cmd) + { + + case SNDCTL_SEQ_SYNC: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (mode == OPEN_READ) + return 0; + while (qlen && !PROCESS_ABORTING) + seq_sync (); + return 0; + break; + + case SNDCTL_SEQ_RESET: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + seq_reset (); + return 0; + break; + + case SNDCTL_SEQ_TESTMIDI: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + midi_dev = IOCTL_IN (arg); + if (midi_dev >= num_midis) + return RET_ERROR (ENXIO); + + if (!midi_opened[midi_dev]) + { + int err, mode; + + mode = file->mode & O_ACCMODE; + if ((err = midi_devs[midi_dev]->open (midi_dev, mode)) < 0) + return err; + } + + midi_opened[midi_dev] = 1; + + return 0; + break; + + case SNDCTL_SEQ_GETINCOUNT: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (mode == OPEN_WRITE) + return 0; + return IOCTL_OUT (arg, iqlen); + break; + + case SNDCTL_SEQ_GETOUTCOUNT: + + if (mode == OPEN_READ) + return 0; + return IOCTL_OUT (arg, SEQ_MAX_QUEUE - qlen); + break; + + case SNDCTL_SEQ_CTRLRATE: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + /* If *arg == 0, just return the current rate */ + return IOCTL_OUT (arg, HZ); + break; + + case SNDCTL_SEQ_RESETSAMPLES: + dev = IOCTL_IN (arg); + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev)) && !orig_dev) + return RET_ERROR (EBUSY); + + if (!orig_dev && pmgr_present[dev]) + pmgr_inform (dev, PM_E_PATCH_RESET, 0, 0, 0, 0); + + return synth_devs[dev]->ioctl (dev, cmd, arg); + break; + + case SNDCTL_SEQ_NRSYNTHS: + return IOCTL_OUT (arg, num_synths); + break; + + case SNDCTL_SEQ_NRMIDIS: + return IOCTL_OUT (arg, num_midis); + break; + + case SNDCTL_SYNTH_MEMAVL: + { + int dev = IOCTL_IN (arg); + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev)) && !orig_dev) + return RET_ERROR (EBUSY); + + return IOCTL_OUT (arg, synth_devs[dev]->ioctl (dev, cmd, arg)); + } + break; + + case SNDCTL_FM_4OP_ENABLE: + { + int dev = IOCTL_IN (arg); + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev))) + return RET_ERROR (ENXIO); + + synth_devs[dev]->ioctl (dev, cmd, arg); + return 0; + } + break; + + case SNDCTL_SYNTH_INFO: + { + struct synth_info inf; + int dev; + + IOCTL_FROM_USER ((char *) &inf, (char *) arg, 0, sizeof (inf)); + dev = inf.device; + + if (dev < 0 || dev >= num_synths) + return RET_ERROR (ENXIO); + + if (!(synth_open_mask & (1 << dev)) && !orig_dev) + return RET_ERROR (EBUSY); + + return synth_devs[dev]->ioctl (dev, cmd, arg); + } + break; + + case SNDCTL_MIDI_INFO: + { + struct midi_info inf; + int dev; + + IOCTL_FROM_USER ((char *) &inf, (char *) arg, 0, sizeof (inf)); + dev = inf.device; + + if (dev < 0 || dev >= num_midis) + return RET_ERROR (ENXIO); + + IOCTL_TO_USER ((char *) arg, 0, (char *) &(midi_devs[dev]->info), sizeof (inf)); + return 0; + } + break; + + case SNDCTL_PMGR_IFACE: + { + struct patmgr_info *inf; + int dev, err; + + inf = (struct patmgr_info *) KERNEL_MALLOC (sizeof (*inf)); + + IOCTL_FROM_USER ((char *) inf, (char *) arg, 0, sizeof (*inf)); + dev = inf->device; + + if (dev < 0 || dev >= num_synths) + { + KERNEL_FREE (inf); + return RET_ERROR (ENXIO); + } + + if (!synth_devs[dev]->pmgr_interface) + { + KERNEL_FREE (inf); + return RET_ERROR (ENXIO); + } + + if ((err = synth_devs[dev]->pmgr_interface (dev, inf)) == -1) + { + KERNEL_FREE (inf); + return err; + } + + IOCTL_TO_USER ((char *) arg, 0, (char *) inf, sizeof (*inf)); + KERNEL_FREE (inf); + return 0; + } + break; + + case SNDCTL_PMGR_ACCESS: + { + struct patmgr_info *inf; + int dev, err; + + inf = (struct patmgr_info *) KERNEL_MALLOC (sizeof (*inf)); + + IOCTL_FROM_USER ((char *) inf, (char *) arg, 0, sizeof (*inf)); + dev = inf->device; + + if (dev < 0 || dev >= num_synths) + { + KERNEL_FREE (inf); + return RET_ERROR (ENXIO); + } + + if (!pmgr_present[dev]) + { + KERNEL_FREE (inf); + return RET_ERROR (ESRCH); + } + + if ((err = pmgr_access (dev, inf)) < 0) + { + KERNEL_FREE (inf); + return err; + } + + IOCTL_TO_USER ((char *) arg, 0, (char *) inf, sizeof (*inf)); + KERNEL_FREE (inf); + return 0; + } + break; + + case SNDCTL_SEQ_TRESHOLD: + { + int tmp = IOCTL_IN (arg); + + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (tmp < 1) + tmp = 1; + if (tmp >= SEQ_MAX_QUEUE) + tmp = SEQ_MAX_QUEUE - 1; + output_treshold = tmp; + return 0; + } + break; + + default: + if (dev) /* Patch manager */ + return RET_ERROR (EIO); + + if (mode == OPEN_READ) + return RET_ERROR (EIO); + + if (!synth_devs[0]) + return RET_ERROR (ENXIO); + if (!(synth_open_mask & (1 << 0))) + return RET_ERROR (ENXIO); + return synth_devs[0]->ioctl (0, cmd, arg); + break; + } + + return RET_ERROR (EINVAL); +} + +#ifdef ALLOW_SELECT +int +sequencer_select (int dev, struct fileinfo *file, int sel_type, select_table * wait) +{ + dev = dev >> 4; + + switch (sel_type) + { + case SEL_IN: + if (!iqlen) + { + midi_sleep_flag = 1; + select_wait (&midi_sleeper, wait); + return 0; + } + return 1; + + break; + + case SEL_OUT: + if (qlen >= SEQ_MAX_QUEUE) + { + seq_sleep_flag = 1; + select_wait (&seq_sleeper, wait); + return 0; + } + return 1; + break; + + case SEL_EX: + return 0; + } + + return 0; +} + +#endif + +void +sequencer_timer (void) +{ + seq_startplay (); +} + +int +note_to_freq (int note_num) +{ + + /* + * This routine converts a midi note to a frequency (multiplied by 1000) + */ + + int note, octave, note_freq; + int notes[] = + { + 261632, 277189, 293671, 311132, 329632, 349232, + 369998, 391998, 415306, 440000, 466162, 493880 + }; /* Note freq*1000 for octave 5 */ + +#define BASE_OCTAVE 5 + + octave = note_num / 12; + note = note_num % 12; + + note_freq = notes[note]; + + if (octave < BASE_OCTAVE) + note_freq >>= (BASE_OCTAVE - octave); + else if (octave > BASE_OCTAVE) + note_freq <<= (octave - BASE_OCTAVE); + + /* note_freq >>= 1; */ + + return note_freq; +} + +unsigned long +compute_finetune (unsigned long base_freq, int bend, int range) +{ + unsigned long amount; + int negative, semitones, cents; + + if (!bend) + return base_freq; + if (!range) + return base_freq; + + if (!base_freq) + return base_freq; + + if (range >= 8192) + range = 8191; + + bend = bend * range / 8192; + if (!bend) + return base_freq; + + negative = bend < 0 ? 1 : 0; + + if (bend < 0) + bend *= -1; + if (bend > range) + bend = range; + + if (bend > 2399) + bend = 2399; + + semitones = bend / 100; + cents = bend % 100; + + amount = semitone_tuning[semitones] * cent_tuning[cents] / 10000; + + if (negative) + return (base_freq * 10000) / amount; /* Bend down */ + else + return (base_freq * amount) / 10000; /* Bend up */ +} + + +long +sequencer_init (long mem_start) +{ + + sequencer_ok = 1; + return mem_start; +} + +#else +/* Stub version */ +int +sequencer_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +sequencer_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count) +{ + return RET_ERROR (EIO); +} + +int +sequencer_open (int dev, struct fileinfo *file) + { + return RET_ERROR (ENXIO); + } + +void +sequencer_release (int dev, struct fileinfo *file) + { + } +int +sequencer_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg) +{ + return RET_ERROR (EIO); +} + +int +sequencer_lseek (int dev, struct fileinfo *file, off_t offset, int orig) +{ + return RET_ERROR (EIO); +} + +long +sequencer_init (long mem_start) +{ + return mem_start; +} + +void +sequencer_midi_input (int dev, unsigned char data) +{ + return; +} + +void +sequencer_midi_output (int dev) +{ + return; +} + +int +sequencer_select (int dev, struct fileinfo *file, int sel_type, select_table * wait) +{ + return RET_ERROR (EIO); +} + +#endif + +#endif diff --git a/sys/i386/isa/sound/sound_calls.h b/sys/i386/isa/sound/sound_calls.h new file mode 100644 index 0000000..fdf1fe1 --- /dev/null +++ b/sys/i386/isa/sound/sound_calls.h @@ -0,0 +1,183 @@ +/* + * DMA buffer calls + */ + +int DMAbuf_open(int dev, int mode); +int DMAbuf_release(int dev, int mode); +int DMAbuf_read (int dev, snd_rw_buf *user_buf, int count); +int DMAbuf_getwrbuffer(int dev, char **buf, int *size); +int DMAbuf_getrdbuffer(int dev, char **buf, int *len); +int DMAbuf_rmchars(int dev, int buff_no, int c); +int DMAbuf_start_output(int dev, int buff_no, int l); +int DMAbuf_ioctl(int dev, unsigned int cmd, unsigned int arg, int local); +long DMAbuf_init(long mem_start); +int DMAbuf_start_dma (int dev, unsigned long physaddr, int count, int dma_mode); +int DMAbuf_open_dma (int chan); +void DMAbuf_close_dma (int chan); +void DMAbuf_reset_dma (int chan); +void DMAbuf_inputintr(int dev); +void DMAbuf_outputintr(int dev); + +/* + * System calls for the /dev/dsp + */ + +int dsp_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int dsp_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int dsp_open (int dev, struct fileinfo *file, int bits); +void dsp_release (int dev, struct fileinfo *file); +int dsp_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg); +int dsp_lseek (int dev, struct fileinfo *file, off_t offset, int orig); +long dsp_init (long mem_start); + +/* + * System calls for the /dev/audio + */ + +int audio_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int audio_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int audio_open (int dev, struct fileinfo *file); +void audio_release (int dev, struct fileinfo *file); +int audio_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg); +int audio_lseek (int dev, struct fileinfo *file, off_t offset, int orig); +long audio_init (long mem_start); + +/* + * System calls for the /dev/sequencer + */ + +int sequencer_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int sequencer_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int sequencer_open (int dev, struct fileinfo *file); +void sequencer_release (int dev, struct fileinfo *file); +int sequencer_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg); +int sequencer_lseek (int dev, struct fileinfo *file, off_t offset, int orig); +long sequencer_init (long mem_start); +void sequencer_midi_input(int dev, unsigned char data); +void sequencer_midi_output(int dev); +void sequencer_timer(void); +int note_to_freq(int note_num); +unsigned long compute_finetune(unsigned long base_freq, int bend, int range); + +#ifdef ALLOW_SELECT +int sequencer_select(int dev, struct fileinfo *file, int sel_type, select_table * wait); +#endif + +/* + * System calls for the /dev/midi + */ + +int MIDIbuf_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int MIDIbuf_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int MIDIbuf_open (int dev, struct fileinfo *file); +void MIDIbuf_release (int dev, struct fileinfo *file); +int MIDIbuf_ioctl (int dev, struct fileinfo *file, + unsigned int cmd, unsigned int arg); +int MIDIbuf_lseek (int dev, struct fileinfo *file, off_t offset, int orig); +void MIDIbuf_bytes_received(int dev, unsigned char *buf, int count); +long MIDIbuf_init(long mem_start); + +/* + * System calls for the generic midi interface. + * + */ + +long CMIDI_init (long mem_start); +int CMIDI_open (int dev, struct fileinfo *file); +int CMIDI_write (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int CMIDI_read (int dev, struct fileinfo *file, snd_rw_buf *buf, int count); +int CMIDI_close (int dev, struct fileinfo *file); + +/* + * + * Misc calls from various sources + */ + +/* From pro_midi.c */ + +long pro_midi_attach(long mem_start); +int pro_midi_open(int dev, int mode); +void pro_midi_close(int dev); +int pro_midi_write(int dev, struct uio *uio); +int pro_midi_read(int dev, struct uio *uio); + +/* From soundcard.c */ +long soundcard_init(long mem_start); +void tenmicrosec(void); +void request_sound_timer (int count); +void sound_stop_timer(void); +int snd_ioctl_return(int *addr, int value); + +/* From sb_dsp.c */ +int sb_dsp_detect (struct address_info *hw_config); +long sb_dsp_init (long mem_start, struct address_info *hw_config); +void sb_dsp_disable_midi(void); + +/* From opl3.c */ +int opl3_detect (int ioaddr); +long opl3_init(long mem_start); + +/* From sb_card.c */ +long attach_sb_card(long mem_start, struct address_info *hw_config); +int probe_sb(struct address_info *hw_config); + +/* From adlib_card.c */ +long attach_adlib_card(long mem_start, struct address_info *hw_config); +int probe_adlib(struct address_info *hw_config); + +/* From pas_card.c */ +long attach_pas_card(long mem_start, struct address_info *hw_config); +int probe_pas(struct address_info *hw_config); +int pas_set_intr(int mask); +int pas_remove_intr(int mask); +unsigned char pas_read(int ioaddr); +void pas_write(unsigned char data, int ioaddr); + +/* From pas_audio.c */ +void pas_pcm_interrupt(unsigned char status, int cause); +long pas_pcm_init(long mem_start, struct address_info *hw_config); + +/* From pas_mixer.c */ +int pas_init_mixer(void); + +/* From pas_midi.c */ +long pas_midi_init(long mem_start); +void pas_midi_interrupt(void); + +/* From gus_card.c */ +long attach_gus_card(long mem_start, struct address_info * hw_config); +int probe_gus(struct address_info *hw_config); +int gus_set_midi_irq(int num); +void gusintr(int); + +/* From gus_wave.c */ +int gus_wave_detect(int baseaddr); +long gus_wave_init(long mem_start, int irq, int dma); +void gus_voice_irq(void); +unsigned char gus_read8 (int reg); +void gus_write8(int reg, unsigned char data); +void guswave_dma_irq(void); +void gus_delay(void); + +/* From gus_midi.c */ +long gus_midi_init(long mem_start); +void gus_midi_interrupt(int dummy); + +/* From mpu401.c */ +long attach_mpu401(long mem_start, struct address_info * hw_config); +int probe_mpu401(struct address_info *hw_config); + +/* From opl3.c */ +void enable_opl3_mode(int left, int right, int both); + +/* From patmgr.c */ +int pmgr_open(int dev); +void pmgr_release(int dev); +int pmgr_read (int dev, struct fileinfo *file, snd_rw_buf * buf, int count); +int pmgr_write (int dev, struct fileinfo *file, snd_rw_buf * buf, int count); +int pmgr_access(int dev, struct patmgr_info *rec); +int pmgr_inform(int dev, int event, unsigned long parm1, unsigned long parm2, + unsigned long parm3, unsigned long parm4); diff --git a/sys/i386/isa/sound/sound_config.h b/sys/i386/isa/sound/sound_config.h new file mode 100644 index 0000000..09d36e5 --- /dev/null +++ b/sys/i386/isa/sound/sound_config.h @@ -0,0 +1,182 @@ +/* linux/kernel/chr_drv/sound/sound-config.h + +A driver for Soundcards, misc configuration parameters. + +(C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) */ + +#include "local.h" + + +#undef CONFIGURE_SOUNDCARD +#undef DYNAMIC_BUFFER + +#ifdef KERNEL_SOUNDCARD +#define CONFIGURE_SOUNDCARD +#define DYNAMIC_BUFFER +#undef LOADABLE_SOUNDCARD +#endif + +#ifdef EXCLUDE_SEQUENCER +#define EXCLUDE_MIDI +#define EXCLUDE_YM3812 +#define EXCLUDE_OPL3 +#endif + +/** UWM - new MIDI stuff **/ + +#ifdef EXCLUDE_CHIP_MIDI +#define EXCLUDE_PRO_MIDI +#endif + +/** UWM - stuff **/ + +#if defined(EXCLUDE_SEQUENCER) && defined(EXCLUDE_AUDIO) +#undef CONFIGURE_SOUNDCARD +#endif + +#ifdef CONFIGURE_SOUNDCARD + +/* ****** IO-address, DMA and IRQ settings **** + +If your card has nonstandard I/O address or IRQ number, change defines + for the following settings in your kernel Makefile */ + +#ifndef SBC_BASE +#define SBC_BASE 0x220 /* 0x220 is the factory default. */ +#endif + +#ifndef SBC_IRQ +#define SBC_IRQ 7 /* IQR7 is the factory default. */ +#endif + +#ifndef SBC_DMA +#define SBC_DMA 1 +#endif + +#ifndef PAS_BASE +#define PAS_BASE 0x388 +#endif + +#ifndef PAS_IRQ +#define PAS_IRQ 5 +#endif + +#ifndef PAS_DMA +#define PAS_DMA 3 +#endif + +#ifndef GUS_BASE +#define GUS_BASE 0x220 +#endif + +#ifndef GUS_IRQ +#define GUS_IRQ 15 +#endif + +#ifndef GUS_MIDI_IRQ +#define GUS_MIDI_IRQ GUS_IRQ +#endif + +#ifndef GUS_DMA +#define GUS_DMA 6 +#endif + +#ifndef MPU_BASE +#define MPU_BASE 0x330 +#endif + +#ifndef MPU_IRQ +#define MPU_IRQ 6 +#endif + +/************* PCM DMA buffer sizes *******************/ + +/* If you are using high playback or recording speeds, the default buffersize + is too small. DSP_BUFFSIZE must be 64k or less. + + A rule of thumb is 64k for PAS16, 32k for PAS+, 16k for SB Pro and + 4k for SB. + + If you change the DSP_BUFFSIZE, don't modify this file. + Use the make config command instead. */ + +#ifndef DSP_BUFFSIZE +#define DSP_BUFFSIZE (4096) +#endif + +#ifndef DSP_BUFFCOUNT +#define DSP_BUFFCOUNT 2 /* 2 is recommended. */ +#endif + +#define DMA_AUTOINIT 0x10 + +#define SND_MAJOR 14 + +#define FM_MONO 0x388 /* This is the I/O address used by AdLib */ + +/* SEQ_MAX_QUEUE is the maximum number of sequencer events buffered by the + driver. (There is no need to alter this) */ +#define SEQ_MAX_QUEUE 512 + +#define SBFM_MAXINSTR (256) /* Size of the FM Instrument + bank */ +/* 128 instruments for general MIDI setup and 16 unassigned */ + +#define SND_NDEVS 50 /* Number of supported devices */ +#define SND_DEV_CTL 0 /* Control port /dev/mixer */ +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define SND_DEV_MIDIN 2 /* MIDI input /dev/midin (not implemented + yet) */ +#define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ +#define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ +#define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ +#define SND_DEV_STATUS 6 /* /dev/sndstatus */ + +/* UWM ... note add new MIDI devices here.. + * Also do not forget to add table midi_supported[] + * Minor numbers for on-chip midi devices start from 15.. and + * should be contiguous.. viz. 15,16,17.... + * Also note the max # of midi devices as MAX_MIDI_DEV + */ + +#define CMIDI_DEV_PRO 15 /* Chip midi device == /dev/pro_midi */ + +/* + * Add other midis here... + . + . + . + . + */ + +#define DSP_DEFAULT_SPEED 8000 + +#define ON 1 +#define OFF 0 + +#define MAX_DSP_DEV 3 +#define MAX_MIXER_DEV 1 +#define MAX_SYNTH_DEV 3 +#define MAX_MIDI_DEV 3 + +struct fileinfo { + int mode; /* Open mode */ + }; + +struct address_info { + int io_base; + int irq; + int dma; +}; + +#define OPEN_READ 1 +#define OPEN_WRITE 2 +#define OPEN_READWRITE 3 + +#include "os.h" +#include "sound_calls.h" +#include "dev_table.h" +#include "debug.h" + +#endif diff --git a/sys/i386/isa/sound/soundcard.c b/sys/i386/isa/sound/soundcard.c new file mode 100644 index 0000000..a0ded01 --- /dev/null +++ b/sys/i386/isa/sound/soundcard.c @@ -0,0 +1,593 @@ +/* + * sound/386bsd/soundcard.c + * + * Soundcard driver for 386BSD. + * + * (C) 1992 Hannu Savolainen (hsavolai@cs.helsinki.fi) + * See COPYING for further details. Should be distributed with this file. + */ + +#include "sound_config.h" + +#ifdef CONFIGURE_SOUNDCARD + +#include "dev_table.h" + +int __timeout_val = 0; +int __process_aborting = 0; + +u_int snd1mask; +u_int snd2mask; +u_int snd3mask; +u_int snd4mask; +u_int snd5mask; + +struct sbc_device +{ + int usecount; +}; + +#define FIX_RETURN(ret) {if ((ret)<0) return -(ret); else return 0;} + +static struct sbc_device sbc_devices[SND_NDEVS]; +static int timer_running = 0; + +static int in_use = 0; /* Total # of open device files (excluding + * minor 0) */ + +static int soundcards_installed = 0; /* Number of installed + * soundcards */ +static int soundcard_configured = 0; +extern char *snd_raw_buf[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern unsigned long snd_raw_buf_phys[MAX_DSP_DEV][DSP_BUFFCOUNT]; +extern int snd_raw_count[MAX_DSP_DEV]; + +static struct fileinfo files[SND_NDEVS]; + +int sndprobe (struct isa_device *dev); +int sndattach (struct isa_device *dev); +int sndopen (dev_t dev, int flags); +int sndclose (dev_t dev, int flags); +int sndioctl (dev_t dev, int cmd, caddr_t arg, int mode); +int sndread (int dev, struct uio *uio); +int sndwrite (int dev, struct uio *uio); +int sndselect (int dev, int rw); +static void sound_mem_init(void); + +int +get_time() +{ +extern struct timeval time; + + return(time.tv_usec + (time.tv_sec*1000000)); +} + + +int +sndread (int dev, struct uio *buf) +{ + int count = buf->uio_resid; + + dev = minor (dev); + + DEB (printk ("sound_read(dev=%d, count=%d)\n", dev, count)); + + switch (dev & 0xff) /* Changed to 0xff from 0x0f */ + { + case SND_DEV_AUDIO: + FIX_RETURN (audio_read (dev, &files[dev], buf, count)); + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + FIX_RETURN (dsp_read (dev, &files[dev], buf, count)); + break; + + case SND_DEV_SEQ: + FIX_RETURN (sequencer_read (dev, &files[dev], buf, count)); + break; + +#ifndef EXCLUDE_CHIP_MIDI + case CMIDI_DEV_PRO: + FIX_RETURN (CMIDI_read (dev, &files[dev], buf, count)); + + break; +#endif + + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + FIX_RETURN (MIDIbuf_read (dev, &files[dev], buf, count)); +#endif + + default: + ; + } + + FIX_RETURN (-EPERM); +} + +int +sndwrite (int dev, struct uio *buf) +{ + int count = buf->uio_resid; + + DEB (printk ("sound_write(dev=%d, count=%d)\n", dev, count)); + + dev = minor (dev); + + switch (dev & 0xff) /* Changed to 0xff from 0x0f */ + { + + case SND_DEV_SEQ: + FIX_RETURN (sequencer_write (dev, &files[dev], buf, count)); + break; + + case SND_DEV_AUDIO: + FIX_RETURN (audio_write (dev, &files[dev], buf, count)); + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + FIX_RETURN (dsp_write (dev, &files[dev], buf, count)); + break; + +#ifndef EXCLUDE_CHIP_MIDI + case CMIDI_DEV_PRO: + FIX_RETURN (CMIDI_write (dev, &files[dev], buf, count)); + break; +#endif + + default: + FIX_RETURN (-EPERM); + } + + FIX_RETURN (count); +} + +int +sndopen (dev_t dev, int flags) +{ + int retval; + + dev = minor (dev); + + /* printf("SND: Minor number is now : %ld\n",dev); */ + + DEB (printk ("sound_open(dev=%d) : usecount=%d\n", dev, sbc_devices[dev].usecount)); + + if ((dev >= SND_NDEVS) || (dev < 0)) + { + printk ("Invalid minor device %d\n", dev); + FIX_RETURN (-ENODEV); + } + + if (!soundcard_configured && dev) + { + printk ("SoundCard Error: The soundcard system has not been configured\n"); + FIX_RETURN (-ENODEV); + } + + files[dev].mode = 0; + + if (flags & FREAD && flags & FWRITE) + files[dev].mode = OPEN_READWRITE; + else if (flags & FREAD) + files[dev].mode = OPEN_READ; + else if (flags & FWRITE) + files[dev].mode = OPEN_WRITE; + + switch (dev & 0xff) /* Changed to 0xff from 0x0f */ + { + case SND_DEV_CTL: + if (!soundcards_installed) + if (soundcard_configured) + { + printk ("Soundcard not installed\n"); + FIX_RETURN (-ENODEV); + } + break; + + case SND_DEV_SEQ: + if ((retval = sequencer_open (dev, &files[dev])) < 0) + FIX_RETURN (retval); + break; + +/** UWM stuff **/ + +#ifndef EXCLUDE_CHIP_MIDI + case CMIDI_DEV_PRO: + FIX_RETURN ( CMIDI_open (dev, &files[dev]) ); + break; +#endif + + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + if ((retval = MIDIbuf_open (dev, &files[dev])) < 0) + FIX_RETURN (retval); + break; +#endif + + case SND_DEV_AUDIO: + if ((retval = audio_open (dev, &files[dev])) < 0) + FIX_RETURN (retval); + break; + + case SND_DEV_DSP: + if ((retval = dsp_open (dev, &files[dev], 8)) < 0) + FIX_RETURN (retval); + break; + + case SND_DEV_DSP16: + if ((retval = dsp_open (dev, &files[dev], 16)) < 0) + FIX_RETURN (retval); + break; + + default: + printk ("Invalid minor device %d\n", dev); + FIX_RETURN (-ENODEV); + } + + sbc_devices[dev].usecount++; + in_use++; + + FIX_RETURN (0); +} + +int +sndclose (dev_t dev, int flags) +{ + + dev = minor (dev); + + DEB (printk ("sound_release(dev=%d)\n", dev)); + + switch (dev & 0xff) /* Changed to 0xff from 0x0f */ + { + case SND_DEV_SEQ: + sequencer_release (dev, &files[dev]); + break; + +#ifndef EXCLUDE_CHIP_MIDI + case CMIDI_DEV_PRO: + CMIDI_close (dev, &files[dev]); + break; +#endif + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + MIDIbuf_release (dev, &files[dev]); + break; +#endif + + case SND_DEV_AUDIO: + audio_release (dev, &files[dev]); + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + dsp_release (dev, &files[dev]); + break; + + default:; + } + + sbc_devices[dev].usecount--; + in_use--; /* If not control port */ + + FIX_RETURN (0); +} + +int +sndioctl (dev_t dev, int cmd, caddr_t arg, int mode) +{ + dev = minor (dev); + + DEB (printk ("sound_ioctl(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg)); + + switch (dev & 0x0f) + { + + case SND_DEV_CTL: + if (!num_mixers) + FIX_RETURN (-ENODEV); + + if (dev >= num_mixers) + FIX_RETURN (-ENODEV); + + FIX_RETURN (mixer_devs[dev]->ioctl (dev, cmd, (unsigned int) arg)); + break; + + case SND_DEV_SEQ: + FIX_RETURN (sequencer_ioctl (dev, &files[dev], cmd, (unsigned int) arg)); + break; + + case SND_DEV_AUDIO: + FIX_RETURN (audio_ioctl (dev, &files[dev], cmd, (unsigned int) arg)); + break; + + case SND_DEV_DSP: + case SND_DEV_DSP16: + FIX_RETURN (dsp_ioctl (dev, &files[dev], cmd, (unsigned int) arg)); + break; + +#ifndef EXCLUDE_MPU401 + case SND_DEV_MIDIN: + FIX_RETURN (MIDIbuf_ioctl (dev, &files[dev], cmd, (unsigned int) arg)); + break; +#endif + + default: + FIX_RETURN (-EPERM); + break; + } + + FIX_RETURN (-EPERM); +} + +int +sndselect (int dev, int rw) +{ + dev = minor (dev); + + DEB (printk ("sound_ioctl(dev=%d, cmd=0x%x, arg=0x%x)\n", dev, cmd, arg)); + + FIX_RETURN (0); +} + +static short +ipri_to_irq (short ipri) +{ + /* + * Converts the ipri (bitmask) to the corresponding irq number + */ + int irq; + + for (irq = 0; irq < 16; irq++) + if (ipri == (1 << irq)) + return irq; + + return -1; /* Invalid argument */ +} + +int +sndprobe (struct isa_device *dev) +{ + struct address_info hw_config; + + hw_config.io_base = dev->id_iobase; + hw_config.irq = ipri_to_irq (dev->id_irq); + hw_config.dma = dev->id_drq; + + + return sndtable_probe (dev->id_unit, &hw_config); +} + +int +sndattach (struct isa_device *dev) +{ + int i; + static int dsp_initialized = 0; + static int midi_initialized = 0; + static int seq_initialized = 0; + static int generic_midi_initialized = 0; + unsigned long mem_start = 0xefffffff; + struct address_info hw_config; + + hw_config.io_base = dev->id_iobase; + hw_config.irq = ipri_to_irq (dev->id_irq); + hw_config.dma = dev->id_drq; + + if (dev->id_unit) /* Card init */ + if (!sndtable_init_card (dev->id_unit, &hw_config)) + { + printf (" <Driver not configured>"); + return FALSE; + } + + /* + * Init the high level sound driver + */ + + if (!(soundcards_installed = sndtable_get_cardcount ())) + { + printf (" <No such hardware>"); + return FALSE; /* No cards detected */ + } + +#ifndef EXCLUDE_AUDIO + soundcard_configured = 1; + if (num_dspdevs) + sound_mem_init (); +#endif + + if (num_dspdevs && !dsp_initialized) /* Audio devices present */ + { + dsp_initialized = 1; + mem_start = DMAbuf_init (mem_start); + mem_start = audio_init (mem_start); + mem_start = dsp_init (mem_start); + } + +/** UWM stuff **/ + +#ifndef EXCLUDE_CHIP_MIDI + + if (!generic_midi_initialized) + { + generic_midi_initialized = 1; + mem_start = CMIDI_init (mem_start); + } + +#endif + +#ifndef EXCLUDE_MPU401 + if (num_midis && !midi_initialized) + { + midi_initialized = 1; + mem_start = MIDIbuf_init (mem_start); + } +#endif + + if ((num_midis + num_synths) && !seq_initialized) + { + seq_initialized = 1; + mem_start = sequencer_init (mem_start); + } + + for (i = 0; i < SND_NDEVS; i++) + { + sbc_devices[i].usecount = 0; + } + + return TRUE; +} + +void +tenmicrosec (void) +{ + int i; + + for (i = 0; i < 16; i++) + inb (0x80); +} + +#ifdef EXCLUDE_GUS +void +gusintr (int unit) +{ + return (0); +} +#endif + +void +request_sound_timer (int count) +{ + static int current = 0; + int tmp = count; + + if (count < 0) + timeout (sequencer_timer, 0, -count); + else + { + + if (count < current) + current = 0; /* Timer restarted */ + + count = count - current; + + current = tmp; + + if (!count) + count = 1; + + timeout (sequencer_timer, 0, count); + } + timer_running = 1; +} + +void +sound_stop_timer (void) +{ + if (timer_running) + untimeout (sequencer_timer, 0); + timer_running = 0; +} + +#ifndef EXCLUDE_AUDIO +static void +sound_mem_init (void) +{ + int i, dev; + unsigned long dma_pagesize; + static unsigned long dsp_init_mask = 0; + + for (dev = 0; dev < num_dspdevs; dev++) /* Enumerate devices */ + if (!(dsp_init_mask & (1 << dev))) /* Not already done */ + if (sound_buffcounts[dev] > 0 && sound_dsp_dmachan[dev] > 0) + { + dsp_init_mask |= (1 << dev); + + if (sound_dma_automode[dev]) + { + sound_dma_automode[dev] = 0; /* Not possible with 386BSD */ + } + + if (sound_buffcounts[dev] == 1) + { + sound_buffcounts[dev] = 2; + sound_buffsizes[dev] /= 2; + } + + if (sound_buffsizes[dev] > 65536) /* Larger is not possible (yet) */ + sound_buffsizes[dev] = 65536; + + if (sound_dsp_dmachan[dev] > 3 && sound_buffsizes[dev] > 65536) + dma_pagesize = 131072; /* 128k */ + else + dma_pagesize = 65536; + + /* More sanity checks */ + + if (sound_buffsizes[dev] > dma_pagesize) + sound_buffsizes[dev] = dma_pagesize; + sound_buffsizes[dev] &= 0xfffff000; /* Truncate to n*4k */ + if (sound_buffsizes[dev] < 4096) + sound_buffsizes[dev] = 4096; + + /* Now allocate the buffers */ + + for (snd_raw_count[dev] = 0; snd_raw_count[dev] < sound_buffcounts[dev]; snd_raw_count[dev]++) + { + /* + * The DMA buffer allocation algorithm hogs memory. We allocate + * a memory area which is two times the requires size. This + * guarantees that it contains at least one valid DMA buffer. + * + * This really needs some kind of finetuning. + */ + char *tmpbuf = malloc (2*sound_buffsizes[dev], M_DEVBUF, M_NOWAIT); + unsigned long addr, rounded; + + if (tmpbuf == NULL) + { + printk ("snd: Unable to allocate %d bytes of buffer\n", + 2 * sound_buffsizes[dev]); + return; + } + + addr = kvtop (tmpbuf); + /* + * Align the start address + */ + rounded = (addr & ~(dma_pagesize - 1)) + dma_pagesize; + + snd_raw_buf[dev][snd_raw_count[dev]] = + &tmpbuf[rounded - addr]; /* Compute offset */ + /* + * Use virtual address as the physical address, since + * isa_dmastart performs the phys address computation. + */ + snd_raw_buf_phys[dev][snd_raw_count[dev]] = + (unsigned long) snd_raw_buf[dev][snd_raw_count[dev]]; + } + } /* for dev */ + +} + +#endif + +struct isa_driver snddriver = +{sndprobe, sndattach, "snd"}; + +int +snd_ioctl_return (int *addr, int value) +{ + if (value < 0) + return value; /* Error */ + suword (addr, value); + return 0; +} + +#endif diff --git a/sys/i386/isa/sound/tuning.h b/sys/i386/isa/sound/tuning.h new file mode 100644 index 0000000..858e1fe --- /dev/null +++ b/sys/i386/isa/sound/tuning.h @@ -0,0 +1,29 @@ +#ifdef SEQUENCER_C + +unsigned short semitone_tuning[24] = +{ +/* 0 */ 10000, 10595, 11225, 11892, 12599, 13348, 14142, 14983, +/* 8 */ 15874, 16818, 17818, 18877, 20000, 21189, 22449, 23784, +/* 16 */ 25198, 26697, 28284, 29966, 31748, 33636, 35636, 37755 +}; + +unsigned short cent_tuning[100] = +{ +/* 0 */ 10000, 10006, 10012, 10017, 10023, 10029, 10035, 10041, +/* 8 */ 10046, 10052, 10058, 10064, 10070, 10075, 10081, 10087, +/* 16 */ 10093, 10099, 10105, 10110, 10116, 10122, 10128, 10134, +/* 24 */ 10140, 10145, 10151, 10157, 10163, 10169, 10175, 10181, +/* 32 */ 10187, 10192, 10198, 10204, 10210, 10216, 10222, 10228, +/* 40 */ 10234, 10240, 10246, 10251, 10257, 10263, 10269, 10275, +/* 48 */ 10281, 10287, 10293, 10299, 10305, 10311, 10317, 10323, +/* 56 */ 10329, 10335, 10341, 10347, 10353, 10359, 10365, 10371, +/* 64 */ 10377, 10383, 10389, 10395, 10401, 10407, 10413, 10419, +/* 72 */ 10425, 10431, 10437, 10443, 10449, 10455, 10461, 10467, +/* 80 */ 10473, 10479, 10485, 10491, 10497, 10503, 10509, 10515, +/* 88 */ 10521, 10528, 10534, 10540, 10546, 10552, 10558, 10564, +/* 96 */ 10570, 10576, 10582, 10589 +}; +#else +extern unsigned short semitone_tuning[24]; +extern unsigned short cent_tuning[100]; +#endif diff --git a/sys/i386/isa/sound/ulaw.h b/sys/i386/isa/sound/ulaw.h new file mode 100644 index 0000000..be9f92d --- /dev/null +++ b/sys/i386/isa/sound/ulaw.h @@ -0,0 +1,69 @@ +static unsigned char ulaw_dsp[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2, + 5, 9, 13, 17, 21, 25, 29, 33, + 37, 41, 45, 49, 53, 57, 61, 65, + 68, 70, 72, 74, 76, 78, 80, 82, + 84, 86, 88, 90, 92, 94, 96, 98, + 100, 101, 102, 103, 104, 105, 106, 107, + 108, 109, 110, 111, 112, 113, 114, 115, + 115, 116, 116, 117, 117, 118, 118, 119, + 119, 120, 120, 121, 121, 122, 122, 123, + 123, 123, 124, 124, 124, 124, 125, 125, + 125, 125, 126, 126, 126, 126, 127, 127, + 127, 127, 127, 127, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 252, 248, 244, 240, 236, 232, 228, 224, + 220, 216, 212, 208, 204, 200, 196, 192, + 189, 187, 185, 183, 181, 179, 177, 175, + 173, 171, 169, 167, 165, 163, 161, 159, + 157, 156, 155, 154, 153, 152, 151, 150, + 149, 148, 147, 146, 145, 144, 143, 142, + 142, 141, 141, 140, 140, 139, 139, 138, + 138, 137, 137, 136, 136, 135, 135, 134, + 134, 134, 133, 133, 133, 133, 132, 132, + 132, 132, 131, 131, 131, 131, 130, 130, + 130, 130, 130, 130, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, +}; + +static unsigned char dsp_ulaw[] = { + 31, 31, 31, 32, 32, 32, 32, 33, + 33, 33, 33, 34, 34, 34, 34, 35, + 35, 35, 35, 36, 36, 36, 36, 37, + 37, 37, 37, 38, 38, 38, 38, 39, + 39, 39, 39, 40, 40, 40, 40, 41, + 41, 41, 41, 42, 42, 42, 42, 43, + 43, 43, 43, 44, 44, 44, 44, 45, + 45, 45, 45, 46, 46, 46, 46, 47, + 47, 47, 47, 48, 48, 49, 49, 50, + 50, 51, 51, 52, 52, 53, 53, 54, + 54, 55, 55, 56, 56, 57, 57, 58, + 58, 59, 59, 60, 60, 61, 61, 62, + 62, 63, 63, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, + 77, 78, 79, 81, 83, 85, 87, 89, + 91, 93, 95, 99, 103, 107, 111, 119, + 255, 247, 239, 235, 231, 227, 223, 221, + 219, 217, 215, 213, 211, 209, 207, 206, + 205, 204, 203, 202, 201, 200, 199, 198, + 197, 196, 195, 194, 193, 192, 191, 191, + 190, 190, 189, 189, 188, 188, 187, 187, + 186, 186, 185, 185, 184, 184, 183, 183, + 182, 182, 181, 181, 180, 180, 179, 179, + 178, 178, 177, 177, 176, 176, 175, 175, + 175, 175, 174, 174, 174, 174, 173, 173, + 173, 173, 172, 172, 172, 172, 171, 171, + 171, 171, 170, 170, 170, 170, 169, 169, + 169, 169, 168, 168, 168, 168, 167, 167, + 167, 167, 166, 166, 166, 166, 165, 165, + 165, 165, 164, 164, 164, 164, 163, 163, + 163, 163, 162, 162, 162, 162, 161, 161, + 161, 161, 160, 160, 160, 160, 159, 159, +}; |