diff options
author | tanimura <tanimura@FreeBSD.org> | 2000-07-11 11:49:33 +0000 |
---|---|---|
committer | tanimura <tanimura@FreeBSD.org> | 2000-07-11 11:49:33 +0000 |
commit | 7716c5370ad4c724650bb6def35750d561bc11d2 (patch) | |
tree | 9a29f2cea6e8dc8339d0261df8deae0201372989 | |
parent | 6cecb051f3990a02c86edb2060aa93c44e6d49da (diff) | |
download | FreeBSD-src-7716c5370ad4c724650bb6def35750d561bc11d2.zip FreeBSD-src-7716c5370ad4c724650bb6def35750d561bc11d2.tar.gz |
Finally merge newmidi.
(I had been busy for my own research activity until the last weekend)
Supported devices:
SB Midi Port (sbc + midi)
SB OPL3 (sbc + midi)
16550 UART (midi, needs a trick in your hint)
CS461x Midi Port (csa + midi)
OSS-compatible sequencer (seq)
Supported playing software:
playmidi (We definitely need more)
Notes:
/dev/midistat now reports installed midi drivers. /dev/sndstat reports
only pcm drivers. We need the new name(pcmstat?).
EMU8000(SB AWE) does not sound yet but does get probed so that the OPL3
synth on an AWE card works.
TODO:
MSS/PCI bridge drivers
Midi-tty interface to support general serial devices
Modules
30 files changed, 11069 insertions, 90 deletions
diff --git a/etc/MAKEDEV b/etc/MAKEDEV index 3dbeb3c..69756f7 100644 --- a/etc/MAKEDEV +++ b/etc/MAKEDEV @@ -1344,7 +1344,9 @@ snd*) # minor number 7 is unused mknod music$unit c $chr `expr $unit '*' 16 + 8` mknod pss$unit c $chr `expr $unit '*' 16 + 9` - # minor numbers 10-15 are unused + # minor number 10 is unused + mknod midistat c $chr 11 + # minor numbers 12-15 are unused umask 77 ;; diff --git a/sys/amd64/amd64/bios.c b/sys/amd64/amd64/bios.c index 4a5a942..06eafe0 100644 --- a/sys/amd64/amd64/bios.c +++ b/sys/amd64/amd64/bios.c @@ -588,7 +588,8 @@ pnpbios_identify(driver_t *driver, device_t parent) isa_set_logicalid(dev, pd->devid); ISA_SET_CONFIG_CALLBACK(parent, dev, pnpbios_set_config, 0); pnp_parse_resources(dev, &pd->devdata[0], - pd->size - sizeof(struct pnp_sysdev)); + pd->size - sizeof(struct pnp_sysdev), + isa_get_vendorid(dev), isa_get_logicalid(dev), 0); if (!device_get_desc(dev)) device_set_desc_copy(dev, pnp_eisaformat(pd->devid)); diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 1c46e4c..2a8214e 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -1649,8 +1649,32 @@ hint.pcm.0.flags="0x0" # For PnP/PCI sound cards, no hints are required. +# +# midi: MIDI interfaces and synthesizers +# + +device midi + +# For non-pnp sound cards with no bridge drivers: +hint.midi.0.at="isa" +hint.midi.0.irq="5" +hint.midi.0.flags="0x0" + +# For serial ports (this example configures port 2): +# TODO: implement generic tty-midi interface so that we can use +# other uarts. +hint.midi.0.at="isa" +hint.midi.0.port="0x2F8" +hint.midi.0.irq="3" + +# +# seq: MIDI sequencer +# + +device seq + # The bridge drivers for sound cards. These can be seperately configured -# for providing services to the likes of new-midi (not in the tree yet). +# for providing services to the likes of new-midi. # When used with 'device pcm' they also provide pcm sound services. # # sbc: Creative SoundBlaster ISA PnP/non-PnP diff --git a/sys/conf/files b/sys/conf/files index 6ea2a2e..84130e1 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -248,17 +248,27 @@ dev/sn/if_sn.c optional sn dev/sn/if_sn_isa.c optional sn isa dev/sn/if_sn_pccard.c optional sn card dev/sound/isa/ad1816.c optional pcm isa +dev/sound/isa/emu8000.c optional midi isa dev/sound/isa/es1888.c optional pcm isa dev/sound/isa/ess.c optional pcm isa dev/sound/isa/gusc.c optional gusc isa dev/sound/isa/gusc.c optional pcm isa +dev/sound/isa/gusmidi.c optional midi isa +dev/sound/isa/mpu.c optional midi isa dev/sound/isa/mss.c optional pcm isa +dev/sound/isa/opl.c optional midi isa dev/sound/isa/sb.c optional pcm isa dev/sound/isa/sbc.c optional pcm isa dev/sound/isa/sbc.c optional sbc isa +dev/sound/isa/uartsio.c optional midi isa +dev/sound/midi/midi.c optional midi +dev/sound/midi/midibuf.c optional midi +dev/sound/midi/midisynth.c optional midi +dev/sound/midi/sequencer.c optional seq midi #dev/sound/pci/aureal.c optional pcm pci dev/sound/pci/csa.c optional csa pci dev/sound/pci/csa.c optional pcm pci +dev/sound/pci/csamidi.c optional midi csa dev/sound/pci/csapcm.c optional pcm pci dev/sound/pci/ds1.c optional pcm pci dev/sound/pci/emu10k1.c optional pcm pci diff --git a/sys/dev/sound/isa/emu8000.c b/sys/dev/sound/isa/emu8000.c new file mode 100644 index 0000000..d69d67c --- /dev/null +++ b/sys/dev/sound/isa/emu8000.c @@ -0,0 +1,2037 @@ +/* + * Low level EMU8000 chip driver for FreeBSD. This handles io against + * /dev/midi, the midi {in, out}put event queues and the event/message + * operation to the EMU8000 chip. + * + * (C) 1999 Seigo Tanimura + * + * 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 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. + * + * $FreeBSD$ + * + */ + + +#include "opt_devfs.h" + +#include <dev/sound/midi/midi.h> + +#include <isa/isavar.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +/* These are the specs of EMU8000. */ +#define EMU8K_MAXVOICE 32 +#define EMU8K_MAXINFO 256 + +#define EMU8K_IDX_DATA0 0 +#define EMU8K_IDX_DATA1 1 +#define EMU8K_IDX_DATA2 1 +#define EMU8K_IDX_DATA3 2 +#define EMU8K_IDX_PTR 2 + +#define EMU8K_PORT_DATA0 0 +#define EMU8K_PORT_DATA1 0 +#define EMU8K_PORT_DATA2 2 +#define EMU8K_PORT_DATA3 0 +#define EMU8K_PORT_PTR 2 + +#define EMU8K_DRAM_RAM 0x200000 +#define EMU8K_DRAM_MAX 0xffffe0 + +/* And some convinient macros. */ +#define EMU8K_DMA_LEFT 0x00 +#define EMU8K_DMA_RIGHT 0x01 +#define EMU8K_DMA_LR 0x01 +#define EMU8K_DMA_READ 0x00 +#define EMU8K_DMA_WRITE 0x02 +#define EMU8K_DMA_RW 0x02 +#define EMU8K_DMA_MASK 0x03 + +/* The followings are the init array for EMU8000, originally in ADIP. */ + +/* Set 1 */ +static u_short init1_1[32] = +{ + 0x03ff, 0x0030, 0x07ff, 0x0130, 0x0bff, 0x0230, 0x0fff, 0x0330, + 0x13ff, 0x0430, 0x17ff, 0x0530, 0x1bff, 0x0630, 0x1fff, 0x0730, + 0x23ff, 0x0830, 0x27ff, 0x0930, 0x2bff, 0x0a30, 0x2fff, 0x0b30, + 0x33ff, 0x0c30, 0x37ff, 0x0d30, 0x3bff, 0x0e30, 0x3fff, 0x0f30, +}; + +static u_short init1_2[32] = +{ + 0x43ff, 0x0030, 0x47ff, 0x0130, 0x4bff, 0x0230, 0x4fff, 0x0330, + 0x53ff, 0x0430, 0x57ff, 0x0530, 0x5bff, 0x0630, 0x5fff, 0x0730, + 0x63ff, 0x0830, 0x67ff, 0x0930, 0x6bff, 0x0a30, 0x6fff, 0x0b30, + 0x73ff, 0x0c30, 0x77ff, 0x0d30, 0x7bff, 0x0e30, 0x7fff, 0x0f30, +}; + +static u_short init1_3[32] = +{ + 0x83ff, 0x0030, 0x87ff, 0x0130, 0x8bff, 0x0230, 0x8fff, 0x0330, + 0x93ff, 0x0430, 0x97ff, 0x0530, 0x9bff, 0x0630, 0x9fff, 0x0730, + 0xa3ff, 0x0830, 0xa7ff, 0x0930, 0xabff, 0x0a30, 0xafff, 0x0b30, + 0xb3ff, 0x0c30, 0xb7ff, 0x0d30, 0xbbff, 0x0e30, 0xbfff, 0x0f30, +}; + +static u_short init1_4[32] = +{ + 0xc3ff, 0x0030, 0xc7ff, 0x0130, 0xcbff, 0x0230, 0xcfff, 0x0330, + 0xd3ff, 0x0430, 0xd7ff, 0x0530, 0xdbff, 0x0630, 0xdfff, 0x0730, + 0xe3ff, 0x0830, 0xe7ff, 0x0930, 0xebff, 0x0a30, 0xefff, 0x0b30, + 0xf3ff, 0x0c30, 0xf7ff, 0x0d30, 0xfbff, 0x0e30, 0xffff, 0x0f30, +}; + +/* Set 2 */ + +static u_short init2_1[32] = +{ + 0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330, + 0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730, + 0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30, + 0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30, +}; + +static u_short init2_2[32] = +{ + 0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330, + 0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730, + 0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30, + 0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30, +}; + +static u_short init2_3[32] = +{ + 0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330, + 0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730, + 0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30, + 0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30, +}; + +static u_short init2_4[32] = +{ + 0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330, + 0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730, + 0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30, + 0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30, +}; + +/* Set 3 */ + +static u_short init3_1[32] = +{ + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224, +}; + +static u_short init3_2[32] = +{ + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3, +}; + +static u_short init3_3[32] = +{ + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287, + 0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386, + 0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55, +}; + +static u_short init3_4[32] = +{ + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F, + 0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +/* Set 4 */ + +static u_short init4_1[32] = +{ + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224, +}; + +static u_short init4_2[32] = +{ + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3, +}; + +static u_short init4_3[32] = +{ + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287, + 0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386, + 0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55, +}; + +static u_short init4_4[32] = +{ + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F, + 0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +/* The followings are the register, the channel and the port for the EMU8000 registers. */ +struct _emu_register { + int reg; /* Register */ + int index; /* Index */ + int port; /* Port */ + int chn; /* Channel */ + int size; /* Size, 0 == word, 1 == double word */ +}; + +#define EMU8K_CHN_ANY (-1) + +static struct _emu_register emu_regs[] = +{ + /* Reg, Index, Port, Channel, Size */ + { 0, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* CPF */ + { 1, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* PTRX */ + { 2, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* CVCF */ + { 3, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* VTFT */ + { 6, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* PSST */ + { 7, EMU8K_IDX_DATA0, EMU8K_PORT_DATA0, EMU8K_CHN_ANY, 1}, /* CSL */ + { 0, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 1}, /* CCCA */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 9, 1}, /* HWCF4 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 10, 1}, /* HWCF5 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 13, 1}, /* HWCF6 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 20, 1}, /* SMALR */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 21, 1}, /* SMARR */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 22, 1}, /* SMALW */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 23, 1}, /* SMARW */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 26, 0}, /* SMLD */ + { 1, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, 26, 0}, /* SMRD */ + { 1, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, 27, 0}, /* WC */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 29, 0}, /* HWCF1 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 30, 0}, /* HWCF2 */ + { 1, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, 31, 0}, /* HWCF3 */ + { 2, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* INIT1 */ + { 2, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* INIT2 */ + { 3, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* INIT3 */ + { 3, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* INIT4 */ + { 4, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* ENVVOL */ + { 5, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* DCYSUSV */ + { 6, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* ENVVAL */ + { 7, EMU8K_IDX_DATA1, EMU8K_PORT_DATA1, EMU8K_CHN_ANY, 0}, /* DCYSUS */ + { 4, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* ATKHLDV */ + { 5, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* LFO1VAL */ + { 6, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* ATKHLD */ + { 7, EMU8K_IDX_DATA2, EMU8K_PORT_DATA2, EMU8K_CHN_ANY, 0}, /* LFO2VAL */ + { 0, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* IP */ + { 1, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* IFATN */ + { 2, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* PEFE */ + { 3, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* FMMOD */ + { 4, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* TREMFRQ */ + { 5, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, EMU8K_CHN_ANY, 0}, /* FM2FRQ2 */ + { 7, EMU8K_IDX_DATA3, EMU8K_PORT_DATA3, 0, 0}, /* PROBE */ +}; + +/* These are the EMU8000 register names. */ +enum { + EMU8K_CPF = 0, + EMU8K_PTRX, + EMU8K_CVCF, + EMU8K_VTFT, + EMU8K_PSST, + EMU8K_CSL, + EMU8K_CCCA, + EMU8K_HWCF4, + EMU8K_HWCF5, + EMU8K_HWCF6, + EMU8K_SMALR, + EMU8K_SMARR, + EMU8K_SMALW, + EMU8K_SMARW, + EMU8K_SMLD, + EMU8K_SMRD, + EMU8K_WC, + EMU8K_HWCF1, + EMU8K_HWCF2, + EMU8K_HWCF3, + EMU8K_INIT1, + EMU8K_INIT2, + EMU8K_INIT3, + EMU8K_INIT4, + EMU8K_ENVVOL, + EMU8K_DCYSUSV, + EMU8K_ENVVAL, + EMU8K_DCYSUS, + EMU8K_ATKHLDV, + EMU8K_LFO1VAL, + EMU8K_ATKHLD, + EMU8K_LFO2VAL, + EMU8K_IP, + EMU8K_IFATN, + EMU8K_PEFE, + EMU8K_FMMOD, + EMU8K_TREMFRQ, + EMU8K_FM2FRQ2, + EMU8K_PROBE, + EMU8K_REGLAST, /* keep this! */ +}; +#define EMU8K_REGNUM (EMU8K_REGLAST) + +/* These are the synthesizer and the midi device information. */ +static struct synth_info emu_synthinfo = { + "EMU8000 Wavetable Synth", + 0, + SYNTH_TYPE_SAMPLE, + SAMPLE_TYPE_AWE32, + 0, + EMU8K_MAXVOICE, + 0, + EMU8K_MAXINFO, + 0, +}; + +static struct midi_info emu_midiinfo = { + "EMU8000 Wavetable Synth", + 0, + 0, + 0, +}; + +#if notyet +/* + * These functions goes into emusynthdev_op_desc. + */ +static mdsy_killnote_t emu_killnote; +static mdsy_setinstr_t emu_setinstr; +static mdsy_startnote_t emu_startnote; +static mdsy_reset_t emu_reset; +static mdsy_hwcontrol_t emu_hwcontrol; +static mdsy_loadpatch_t emu_loadpatch; +static mdsy_panning_t emu_panning; +static mdsy_aftertouch_t emu_aftertouch; +static mdsy_controller_t emu_controller; +static mdsy_patchmgr_t emu_patchmgr; +static mdsy_bender_t emu_bender; +static mdsy_allocvoice_t emu_allocvoice; +static mdsy_setupvoice_t emu_setupvoice; +static mdsy_sendsysex_t emu_sendsysex; +static mdsy_prefixcmd_t emu_prefixcmd; +static mdsy_volumemethod_t emu_volumemethod; + +/* + * This is the synthdev_info for an EMU8000 chip. + */ +static synthdev_info emusynth_op_desc = { + emu_killnote, + emu_setinstr, + emu_startnote, + emu_reset, + emu_hwcontrol, + emu_loadpatch, + emu_panning, + emu_aftertouch, + emu_controller, + emu_patchmgr, + emu_bender, + emu_allocvoice, + emu_setupvoice, + emu_sendsysex, + emu_prefixcmd, + emu_volumemethod, +}; +#endif /* notyet */ + +/* + * These functions goes into emu_op_desc to get called + * from sound.c. + */ + +static int emu_probe(device_t dev); +static int emu_attach(device_t dev); + +static d_open_t emu_open; +static d_close_t emu_close; +static d_read_t emu_read; +static d_write_t emu_write; +static d_ioctl_t emu_ioctl; +static midi_callback_t emu_callback; + +/* These go to mididev_info. */ +static mdsy_readraw_t emu_readraw; +static mdsy_writeraw_t emu_writeraw; + +/* Here is the parameter structure per a device. */ +struct emu_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct resource *io[3]; /* Base of io port */ + int io_rid[3]; /* Io resource ID */ + + u_int dramsize; /* DRAM size */ + struct synth_info synthinfo; /* Synthesizer information */ + + int fflags; /* File flags */ +}; + +typedef struct emu_softc *sc_p; + +/* These functions are local. */ +static u_int emu_dramsize(sc_p scp); +static void emu_allocdmachn(sc_p scp, int chn, int mode); +static void emu_dmaaddress(sc_p scp, int mode, u_int addr); +static void emu_waitstream(sc_p scp, int mode); +static void emu_readblkstream(sc_p scp, int mode, u_short *data, size_t len); +static void emu_writeblkstream(sc_p scp, int mode, u_short *data, size_t len); +static void emu_readstream(sc_p scp, int mode, u_short *data); +static void emu_writestream(sc_p scp, int mode, u_short data); +static void emu_releasedmachn(sc_p scp, int chn, int mode); +static void emu_delay(sc_p scp, short n); +static void emu_readcpf(sc_p scp, int chn, u_int *cp, u_int *f); +static void emu_writecpf(sc_p scp, int chn, u_int cp, u_int f); +static void emu_readptrx(sc_p scp, int chn, u_int *pt, u_int *rs, u_int *auxd); +static void emu_writeptrx(sc_p scp, int chn, u_int pt, u_int rs, u_int auxd); +static void emu_readcvcf(sc_p scp, int chn, u_int *cv, u_int *cf); +static void emu_writecvcf(sc_p scp, int chn, u_int cv, u_int cf); +static void emu_readvtft(sc_p scp, int chn, u_int *vt, u_int *ft); +static void emu_writevtft(sc_p scp, int chn, u_int vt, u_int ft); +static void emu_readpsst(sc_p scp, int chn, u_int *pan, u_int *st); +static void emu_writepsst(sc_p scp, int chn, u_int pan, u_int st); +static void emu_readcsl(sc_p scp, int chn, u_int *cs, u_int *lp); +static void emu_writecsl(sc_p scp, int chn, u_int cs, u_int lp); +static void emu_readccca(sc_p scp, int chn, u_int *q, u_int *dma, u_int *wr, u_int *right, u_int *ca); +static void emu_writeccca(sc_p scp, int chn, u_int q, u_int dma, u_int wr, u_int right, u_int ca); +static void emu_readhwcf4(sc_p scp, u_int *val); +static void emu_writehwcf4(sc_p scp, u_int val); +static void emu_readhwcf5(sc_p scp, u_int *val); +static void emu_writehwcf5(sc_p scp, u_int val); +static void emu_readhwcf6(sc_p scp, u_int *val); +static void emu_writehwcf6(sc_p scp, u_int val); +static void emu_readsmalr(sc_p scp, u_int *mt, u_int *smalr); +static void emu_writesmalr(sc_p scp, u_int mt, u_int smalr); +static void emu_readsmarr(sc_p scp, u_int *mt, u_int *smarr); +static void emu_writesmarr(sc_p scp, u_int mt, u_int smarr); +static void emu_readsmalw(sc_p scp, u_int *full, u_int *smalw); +static void emu_writesmalw(sc_p scp, u_int full, u_int smalw); +static void emu_readsmarw(sc_p scp, u_int *full, u_int *smarw); +static void emu_writesmarw(sc_p scp, u_int full, u_int smarw); +static void emu_readsmld(sc_p scp, u_short *smld); +static void emu_writesmld(sc_p scp, u_short smld); +static void emu_readsmrd(sc_p scp, u_short *smrd); +static void emu_writesmrd(sc_p scp, u_short smrd); +static void emu_readwc(sc_p scp, u_int *wc); +static void emu_writewc(sc_p scp, u_int wc); +static void emu_readhwcf1(sc_p scp, u_int *val); +static void emu_writehwcf1(sc_p scp, u_int val); +static void emu_readhwcf2(sc_p scp, u_int *val); +static void emu_writehwcf2(sc_p scp, u_int val); +static void emu_readhwcf3(sc_p scp, u_int *val); +static void emu_writehwcf3(sc_p scp, u_int val); +static void emu_readinit1(sc_p scp, int chn, u_int *val); +static void emu_writeinit1(sc_p scp, int chn, u_int val); +static void emu_readinit2(sc_p scp, int chn, u_int *val); +static void emu_writeinit2(sc_p scp, int chn, u_int val); +static void emu_readinit3(sc_p scp, int chn, u_int *val); +static void emu_writeinit3(sc_p scp, int chn, u_int val); +static void emu_readinit4(sc_p scp, int chn, u_int *val); +static void emu_writeinit4(sc_p scp, int chn, u_int val); +static void emu_readenvvol(sc_p scp, int chn, u_int *envvol); +static void emu_writeenvvol(sc_p scp, int chn, u_int envvol); +static void emu_readdcysusv(sc_p scp, int chn, u_int *ph1v, u_int *susv, u_int *off, u_int *dcyv); +static void emu_writedcysusv(sc_p scp, int chn, u_int ph1v, u_int susv, u_int off, u_int dcyv); +static void emu_readenvval(sc_p scp, int chn, u_int *envval); +static void emu_writeenvval(sc_p scp, int chn, u_int envval); +static void emu_readdcysus(sc_p scp, int chn, u_int *ph1, u_int *sus, u_int *dcy); +static void emu_writedcysus(sc_p scp, int chn, u_int ph1, u_int sus, u_int dcy); +static void emu_readatkhldv(sc_p scp, int chn, u_int *atkhldv); +static void emu_writeatkhldv(sc_p scp, int chn, u_int atkhldv); +static void emu_readlfo1val(sc_p scp, int chn, u_int *lfo1val); +static void emu_writelfo1val(sc_p scp, int chn, u_int lfo1val); +static void emu_readatkhld(sc_p scp, int chn, u_int *atkhld); +static void emu_writeatkhld(sc_p scp, int chn, u_int atkhld); +static void emu_readlfo2val(sc_p scp, int chn, u_int *lfo2val); +static void emu_writelfo2val(sc_p scp, int chn, u_int lfo2val); +static void emu_readip(sc_p scp, int chn, u_int *ip); +static void emu_writeip(sc_p scp, int chn, u_int ip); +static void emu_readifatn(sc_p scp, int chn, u_int *ifc, u_int *atn); +static void emu_writeifatn(sc_p scp, int chn, u_int ifc, u_int atn); +static void emu_readpefe(sc_p scp, int chn, u_int *pe, u_int *fe); +static void emu_writepefe(sc_p scp, int chn, u_int pe, u_int fe); +static void emu_readfmmod(sc_p scp, int chn, u_int *fm, u_int *mod); +static void emu_writefmmod(sc_p scp, int chn, u_int fm, u_int mod); +static void emu_readtremfrq(sc_p scp, int chn, u_int *trem, u_int *frq); +static void emu_writetremfrq(sc_p scp, int chn, u_int trem, u_int frq); +static void emu_readfm2frq2(sc_p scp, int chn, u_int *fm2, u_int *frq2); +static void emu_writefm2frq2(sc_p scp, int chn, u_int fm2, u_int frq2); +static void emu_readprobe(sc_p scp, u_int *val); +static void emu_writeprobe(sc_p scp, u_int val); +static void emu_command(sc_p scp, int reg, int chn, u_long val); +static u_long emu_status(sc_p scp, int reg, int chn); +static int emu_allocres(sc_p scp, device_t dev); +static void emu_releaseres(sc_p scp, device_t dev); + +/* PnP IDs */ +static struct isa_pnp_id emu_ids[] = { + {0x21008c0e, "CTL0021 WaveTable Synthesizer"}, /* CTL0021 */ + {0x22008c0e, "CTL0022 WaveTable Synthesizer"}, /* CTL0022 */ +}; + +/* + * This is the device descriptor for the midi device. + */ +mididev_info emu_op_desc = { + "EMU8000 Wavetable Synth", + + SNDCARD_AWE32, + + emu_open, + emu_close, + emu_read, + emu_write, + emu_ioctl, + NULL, + + emu_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + */ + +static int +emu_probe(device_t dev) +{ + sc_p scp; + int unit; + u_int probe, hwcf1, hwcf2; + + /* Check isapnp ids */ + if (isa_get_logicalid(dev) != 0) + return (ISA_PNP_PROBE(device_get_parent(dev), dev, emu_ids)); + /* XXX non-pnp emu? */ + + unit = device_get_unit(dev); + scp = device_get_softc(dev); + + device_set_desc(dev, "EMU8000 Wavetable Synth"); + bzero(scp, sizeof(*scp)); + + DEB(printf("emu%d: probing.\n", unit)); + + if (emu_allocres(scp, dev)) { + emu_releaseres(scp, dev); + return (ENXIO); + } + + emu_readprobe(scp, &probe); + emu_readhwcf1(scp, &hwcf1); + emu_readhwcf2(scp, &hwcf2); + if ((probe & 0x000f) != 0x000c + || (hwcf1 & 0x007e) != 0x0058 + || (hwcf2 & 0x0003) != 0x0003) { + emu_releaseres(scp, dev); + return (ENXIO); + } + + DEB(printf("emu%d: probed.\n", unit)); + + return (0); +} + +extern synthdev_info midisynth_op_desc; + +static int +emu_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + int unit, i; + + unit = device_get_unit(dev); + scp = device_get_softc(dev); + + DEB(printf("emu%d: attaching.\n", unit)); + + if (emu_allocres(scp, dev)) { + emu_releaseres(scp, dev); + return (ENXIO); + } + + /* EMU8000 needs some initialization processes. */ + + /* 1. Write HWCF{1,2}. */ + emu_writehwcf1(scp, 0x0059); + emu_writehwcf2(scp, 0x0020); + + /* Disable the audio. */ + emu_writehwcf3(scp, 0); + + /* 2. Initialize the channels. */ + + /* 2a. Write DCYSUSV. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writedcysusv(scp, i, 0, 0, 1, 0); + + /* 2b. Clear the envelope and sound engine registers. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) { + emu_writeenvvol(scp, i, 0); + emu_writeenvval(scp, i, 0); + emu_writedcysus(scp, i, 0, 0, 0); + emu_writeatkhldv(scp, i, 0); + emu_writelfo1val(scp, i, 0); + emu_writeatkhld(scp, i, 0); + emu_writelfo2val(scp, i, 0); + emu_writeip(scp, i, 0); + emu_writeifatn(scp, i, 0, 0); + emu_writepefe(scp, i, 0, 0); + emu_writefmmod(scp, i, 0, 0); + emu_writetremfrq(scp, i, 0, 0); + emu_writefm2frq2(scp, i, 0, 0); + emu_writeptrx(scp, i, 0, 0, 0); + emu_writevtft(scp, i, 0, 0); + emu_writepsst(scp, i, 0, 0); + emu_writecsl(scp, i, 0, 0); + emu_writeccca(scp, i, 0, 0, 0, 0, 0); + } + + /* 2c. Clear the current registers. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) { + emu_writecpf(scp, i, 0, 0); + emu_writecvcf(scp, i, 0, 0); + } + + /* 3. Initialize the sound memory DMA registers. */ + emu_writesmalr(scp, 0, 0); + emu_writesmarr(scp, 0, 0); + emu_writesmalw(scp, 0, 0); + emu_writesmarw(scp, 0, 0); + + /* 4. Fill the array. */ + + /* 4a. Set 1. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init1_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init1_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init1_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init1_4[i]); + + /* 4b. Have a rest. */ + emu_delay(scp, 1024); /* 1024 samples. */ + + /* 4c. Set 2. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init2_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init2_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init2_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init2_4[i]); + + /* 4d. Set 3. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init3_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init3_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init3_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init3_4[i]); + + /* 4e. Write to HWCF{4,5,6}. */ + emu_writehwcf4(scp, 0); + emu_writehwcf5(scp, 0x00000083); + emu_writehwcf6(scp, 0x00008000); + + /* 4f. Set 4. */ + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit1(scp, i, init4_1[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit2(scp, i, init4_2[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit3(scp, i, init4_3[i]); + for (i = 0 ; i < EMU8K_MAXVOICE ; i++) + emu_writeinit4(scp, i, init4_4[i]); + + /* 5. Determine the size of DRAM. */ + scp->dev = dev; + scp->dramsize = emu_dramsize(scp); + printf("emu%d: DRAM size = %dKB\n", unit, scp->dramsize / 1024); + + /* We have inited the EMU8000. Now work on FM. */ + + /* Write parameters for the left channel. */ + emu_writedcysusv(scp, 30, 0, 0, 1, 0); + emu_writepsst(scp, 30, 0x80, 0xffffe0); /* full left */ + emu_writecsl(scp, 30, 0, 0xfffff8); /* chorus */ + emu_writeptrx(scp, 30, 0, 0, 0); /* reverb */ + emu_writecpf(scp, 30, 0, 0); + emu_writeccca(scp, 30, 0, 0, 0, 0, 0xffffe3); + + /* Then the right channel. */ + emu_writedcysusv(scp, 31, 0, 0, 1, 0); + emu_writepsst(scp, 31, 0x80, 0xfffff0); /* full right */ + emu_writecsl(scp, 31, 0, 0xfffff8); /* chorus */ + emu_writeptrx(scp, 31, 0, 0, 0xff); /* reverb */ + emu_writecpf(scp, 31, 0, 0); + emu_writeccca(scp, 31, 0, 0, 0, 0, 0xfffff3); + + /* Skew volume and cutoff. */ + emu_writevtft(scp, 30, 0x8000, 0xffff); + emu_writevtft(scp, 31, 0x8000, 0xffff); + + /* Ready to sound. */ + emu_writehwcf3(scp, 0x0004); + + /* Fill the softc for this unit. */ + scp->devinfo = devinfo = &midi_info[unit]; + bcopy(&emu_synthinfo, &scp->synthinfo, sizeof(emu_synthinfo)); + + /* Fill the midi info. */ + bcopy(&emu_op_desc, devinfo, sizeof(emu_op_desc)); + midiinit(devinfo, dev); + devinfo->flags = 0; + bcopy(&midisynth_op_desc, &devinfo->synth, sizeof(midisynth_op_desc)); + devinfo->synth.readraw = emu_readraw; + devinfo->synth.writeraw = emu_writeraw; + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x, 0x%x, 0x%x", + (u_int)rman_get_start(scp->io[0]), (u_int)rman_get_start(scp->io[1]), (u_int)rman_get_start(scp->io[2])); + + /* Init the queue. */ + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = 1; + midibuf_init(&devinfo->midi_dbuf_in); + midibuf_init(&devinfo->midi_dbuf_out); + + /* Increase the number of the synthesizers. */ + nsynth++; + + DEB(printf("emu%d: attached.\n", unit)); + + return (0); +} + +static int +emupnp_attach(device_t dev) +{ + return (emu_attach(dev)); +} + +static int +emu_open(dev_t i_dev, int flags, int mode, struct proc *p) +{ + return (0); +} + +static int +emu_close(dev_t i_dev, int flags, int mode, struct proc *p) +{ + return (0); +} + +static int +emu_read(dev_t i_dev, struct uio *buf, int flag) +{ + sc_p scp; + mididev_info *devinfo; + int unit/*, s, len, ret*/; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("emu_read: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("emu_read: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + if ((devinfo->fflags & FREAD) == 0) { + DEB(printf("emu_read: unit %d is not for reading.\n", unit)); + return (EIO); + } + + /* Drain the data. */ + midibuf_init(&devinfo->midi_dbuf_in); + + return (0); +} + +static int +emu_write(dev_t i_dev, struct uio *buf, int flag) +{ + sc_p scp; + mididev_info *devinfo; + int unit/*, s, len, ret*/; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("emu_write: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("emu_write: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + if ((devinfo->fflags & FWRITE) == 0) { + DEB(printf("emu_write: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* Drain the data. */ + midibuf_init(&devinfo->midi_dbuf_out); + midibuf_init(&devinfo->midi_dbuf_passthru); + + return (0); +} + +static int +emu_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + DEB(printf("emu%d: ioctlling, cmd 0x%x.\n", unit, (int)cmd)); + + if (unit >= nmidi + nsynth) { + DEB(printf("emu_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("emu_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device >= nmidi + nsynth || synthinfo->device != unit) + return (ENXIO); + bcopy(&scp->synthinfo, synthinfo, sizeof(scp->synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device >= nmidi + nsynth || midiinfo->device != unit) + return (ENXIO); + bcopy(&emu_midiinfo, midiinfo, sizeof(emu_midiinfo)); + strcpy(midiinfo->name, scp->synthinfo.name); + midiinfo->device = unit; + return (0); + break; + case SNDCTL_SYNTH_MEMAVL: + return 0x7fffffff; + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static int +emu_callback(mididev_info *devinfo, int reason) +{ + return (0); +} + +static int +emu_readraw(mididev_info *md, u_char *buf, int len, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + unit = md->unit; + scp = md->softc; + if ((md->fflags & FREAD) == 0) { + DEB(printf("emu_readraw: unit %d is not for reading.\n", unit)); + return (EIO); + } + + /* NOP. */ + return (0); +} + +static int +emu_writeraw(mididev_info *md, u_char *buf, int len, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + unit = md->unit; + scp = md->softc; + if ((md->fflags & FWRITE) == 0) { + DEB(printf("emu_writeraw: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* NOP. */ + return (0); +} + +/* + * The functions below here are the synthesizer interfaces. + */ + +/* + * The functions below here are the libraries for the above ones. + */ + +/* Determine the size of DRAM. */ +static u_int +emu_dramsize(sc_p scp) +{ + u_int dramsize; + static u_short magiccode[] = {0x386d, 0xbd2a, 0x73df, 0xf2d8}; + static u_short magiccode2[] = {0x5ef3, 0x2b90, 0xa4c8, 0x6a13}; + u_short buf[sizeof(magiccode) / sizeof(*magiccode)]; + + /* + * Write the magic code to the bottom of DRAM. + * Writing to a wrapped address clobbers the code. + */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, EMU8K_DRAM_RAM); + emu_writeblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, magiccode, sizeof(magiccode) / sizeof(*magiccode)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + + for (dramsize = 0 ; dramsize + EMU8K_DRAM_RAM < EMU8K_DRAM_MAX ; ) { + + /* Read the magic code. */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, EMU8K_DRAM_RAM); + emu_readblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, buf, sizeof(buf) / sizeof(*buf)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + + /* Compare the code. */ + if (bcmp(magiccode, buf, sizeof(magiccode))) + break; + + /* Increase the DRAM size. */ + dramsize += 0x8000; + + /* Try writing a different magic code to dramsize. */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, dramsize + EMU8K_DRAM_RAM); + emu_writeblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE, magiccode2, sizeof(magiccode2) / sizeof(*magiccode2)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_WRITE); + + /* Then read the magic code. */ + emu_allocdmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + emu_dmaaddress(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, dramsize + EMU8K_DRAM_RAM); + emu_readblkstream(scp, EMU8K_DMA_LEFT | EMU8K_DMA_READ, buf, sizeof(buf) / sizeof(*buf)); + emu_releasedmachn(scp, 31, EMU8K_DMA_LEFT | EMU8K_DMA_READ); + + /* Compare the code. */ + if (bcmp(magiccode2, buf, sizeof(magiccode2))) + break; + } + if (dramsize + EMU8K_DRAM_RAM > EMU8K_DRAM_MAX) + dramsize = EMU8K_DRAM_MAX - EMU8K_DRAM_RAM; + + return dramsize * 2; /* dramsize is in words. */ +} + +/* Allocates a channel to a DMA stream. */ +static void +emu_allocdmachn(sc_p scp, int chn, int mode) +{ + /* Turn off the sound, prepare for a DMA stream. */ + emu_writedcysusv(scp, chn, 0, 0, 1, 0); + emu_writevtft(scp, chn, 0, 0); + emu_writecvcf(scp, chn, 0, 0); + emu_writeptrx(scp, chn, 0x4000, 0, 0); + emu_writecpf(scp, chn, 0x4000, 0); + emu_writepsst(scp, chn, 0, 0); + emu_writecsl(scp, chn, 0, 0); + + /* Enter DMA mode. */ + emu_writeccca(scp, chn, 0, 1, + ((mode & EMU8K_DMA_WRITE) > 0) ? 1 : 0, + ((mode & EMU8K_DMA_RIGHT) > 0) ? 1 : 0, + 0); +} + +/* Programs the initial address to a DMA. */ +static void +emu_dmaaddress(sc_p scp, int mode, u_int addr) +{ + /* Wait until the stream comes ready. */ + emu_waitstream(scp, mode); + + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_READ: + emu_writesmalr(scp, 0, addr); + emu_readsmld(scp, NULL); /* Read the stale data. */ + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_READ: + emu_writesmarr(scp, 0, addr); + emu_readsmrd(scp, NULL); /* Read the stale data. */ + break; + case EMU8K_DMA_LEFT | EMU8K_DMA_WRITE: + emu_writesmalw(scp, 0, addr); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_WRITE: + emu_writesmarw(scp, 0, addr); + break; + } +} + +/* Waits until a stream gets ready. */ +static void +emu_waitstream(sc_p scp, int mode) +{ + int i; + u_int busy; + + for (i = 0 ; i < 100000 ; i++) { + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_READ: + emu_readsmalr(scp, &busy, NULL); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_READ: + emu_readsmarr(scp, &busy, NULL); + break; + case EMU8K_DMA_LEFT | EMU8K_DMA_WRITE: + emu_readsmalw(scp, &busy, NULL); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_WRITE: + emu_readsmarw(scp, &busy, NULL); + break; + } + if (!busy) + break; + emu_delay(scp, 1); + } + if (busy) + printf("emu%d: stream data still busy, timed out.\n", device_get_unit(scp->dev)); +} + +/* Reads a word block from a stream. */ +static void +emu_readblkstream(sc_p scp, int mode, u_short *data, size_t len) +{ + while((len--) > 0) + emu_readstream(scp, mode, data++); +} + +/* Writes a word block stream to a stream. */ +static void +emu_writeblkstream(sc_p scp, int mode, u_short *data, size_t len) +{ + while((len--) > 0) + emu_writestream(scp, mode, *(data++)); +} + +/* Reads a word from a stream. */ +static void +emu_readstream(sc_p scp, int mode, u_short *data) +{ + if ((mode & EMU8K_DMA_RW) != EMU8K_DMA_READ) + return; + + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_READ: + emu_readsmld(scp, data); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_READ: + emu_readsmrd(scp, data); + break; + } +} + +/* Writes a word to a stream. */ +static void +emu_writestream(sc_p scp, int mode, u_short data) +{ + if ((mode & EMU8K_DMA_RW) != EMU8K_DMA_WRITE) + return; + + switch(mode & EMU8K_DMA_MASK) + { + case EMU8K_DMA_LEFT | EMU8K_DMA_WRITE: + emu_writesmld(scp, data); + break; + case EMU8K_DMA_RIGHT | EMU8K_DMA_WRITE: + emu_writesmrd(scp, data); + break; + } +} + +/* Releases a channel from a DMA stream. */ +static void +emu_releasedmachn(sc_p scp, int chn, int mode) +{ + /* Wait until the stream comes ready. */ + emu_waitstream(scp, mode); + + /* Leave DMA mode. */ + emu_writeccca(scp, chn, 0, 0, 0, 0, 0); +} + +/* + * Waits cycles. + * Idea-stolen-from: sys/i386/isa/clock.c:DELAY() + */ +static void +emu_delay(sc_p scp, short n) +{ + int wc_prev, wc, wc_left, wc_delta; + + emu_readwc(scp, &wc_prev); + wc_left = n; + + while (wc_left > 0) { + emu_readwc(scp, &wc); + wc_delta = wc - wc_prev; /* The counter increases. */ + wc_prev = wc; + if (wc_delta < 0) + wc_delta += 0xffff; + wc_left -= wc_delta; + } +} + +/* The followings provide abstruct access to the registers. */ +#define DECBIT(sts, shift, len) (((sts) >> (shift))) & (0xffffffff >> (32 - len)) +#define GENBIT(val, shift, len) (((val) & (0xffffffff >> (32 - len))) << (shift)) + +/* CPF: Current Pitch and Fractional Address */ +static void +emu_readcpf(sc_p scp, int chn, u_int *cp, u_int *f) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CPF, chn); + if (cp != NULL) + *cp = DECBIT(sts, 16, 16); + if (f != NULL) + *f = DECBIT(sts, 0, 16); +} + +static void +emu_writecpf(sc_p scp, int chn, u_int cp, u_int f) +{ + emu_command(scp, EMU8K_CPF, chn, + GENBIT(cp, 16, 16) + | GENBIT(f, 0, 16)); +} + +/* PTRX: Pitch Target, Rvb Send and Aux Byte */ +static void +emu_readptrx(sc_p scp, int chn, u_int *pt, u_int *rs, u_int *auxd) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PTRX, chn); + if (pt != NULL) + *pt = DECBIT(sts, 16, 16); + if (rs != NULL) + *rs = DECBIT(sts, 8, 8); + if (auxd != NULL) + *auxd = DECBIT(sts, 0, 8); +} + +static void +emu_writeptrx(sc_p scp, int chn, u_int pt, u_int rs, u_int auxd) +{ + emu_command(scp, EMU8K_PTRX, chn, + GENBIT(pt, 16, 16) + | GENBIT(rs, 8, 8) + | GENBIT(auxd, 0, 8)); +} + +/* CVCF: Current Volume and Filter Cutoff */ +static void +emu_readcvcf(sc_p scp, int chn, u_int *cv, u_int *cf) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CVCF, chn); + if (cv != NULL) + *cv = DECBIT(sts, 16, 16); + if (cf != NULL) + *cf = DECBIT(sts, 0, 16); +} + +static void +emu_writecvcf(sc_p scp, int chn, u_int cv, u_int cf) +{ + emu_command(scp, EMU8K_CVCF, chn, + GENBIT(cv, 16, 16) + | GENBIT(cf, 0, 16)); +} + +/* VTFT: Volume and Filter Cutoff Targets */ +static void +emu_readvtft(sc_p scp, int chn, u_int *vt, u_int *ft) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_VTFT, chn); + if (vt != NULL) + *vt = DECBIT(sts, 16, 16); + if (ft != NULL) + *ft = DECBIT(sts, 0, 16); +} + +static void +emu_writevtft(sc_p scp, int chn, u_int vt, u_int ft) +{ + emu_command(scp, EMU8K_VTFT, chn, + GENBIT(vt, 16, 16) + | GENBIT(ft, 0, 16)); +} + +/* PSST: Pan Send and Loop Start Address */ +static void +emu_readpsst(sc_p scp, int chn, u_int *pan, u_int *st) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PSST, chn); + if (pan != NULL) + *pan = DECBIT(sts, 24, 8); + if (st != NULL) + *st = DECBIT(sts, 0, 24); +} + +static void +emu_writepsst(sc_p scp, int chn, u_int pan, u_int st) +{ + emu_command(scp, EMU8K_PSST, chn, + GENBIT(pan, 24, 8) + | GENBIT(st, 0, 24)); +} + +/* CSL: Chorus Send and Loop End Address */ +static void +emu_readcsl(sc_p scp, int chn, u_int *cs, u_int *lp) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CSL, chn); + if (cs != NULL) + *cs = DECBIT(sts, 24, 8); + if (lp != NULL) + *lp = DECBIT(sts, 0, 24); +} + +static void +emu_writecsl(sc_p scp, int chn, u_int cs, u_int lp) +{ + emu_command(scp, EMU8K_CSL, chn, + GENBIT(cs, 24, 8) + | GENBIT(lp, 0, 24)); +} + +/* CCCA: Q, Control Bits and Current Address */ +static void +emu_readccca(sc_p scp, int chn, u_int *q, u_int *dma, u_int *wr, u_int *right, u_int *ca) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_CCCA, chn); + if (q != NULL) + *q = DECBIT(sts, 28, 4); + if (dma != NULL) + *dma = DECBIT(sts, 26, 1); + if (wr != NULL) + *wr = DECBIT(sts, 25, 1); + if (right != NULL) + *right = DECBIT(sts, 24, 1); + if (ca != NULL) + *ca = DECBIT(sts, 0, 24); +} + +static void +emu_writeccca(sc_p scp, int chn, u_int q, u_int dma, u_int wr, u_int right, u_int ca) +{ + emu_command(scp, EMU8K_CCCA, chn, + GENBIT(q, 28, 4) + | GENBIT(dma, 26, 1) + | GENBIT(wr, 25, 1) + | GENBIT(right, 24, 1) + | GENBIT(ca, 0, 24)); +} + +/* HWCF4: Configuration Double Word 4 */ +static void +emu_readhwcf4(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF4, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf4(sc_p scp, u_int val) +{ + if (val != 0) + printf("emu%d: writing value 0x%x to HWCF4.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF4, 0, val); +} + +/* HWCF5: Configuration Double Word 5 */ +static void +emu_readhwcf5(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF5, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf5(sc_p scp, u_int val) +{ + if (val != 0x00000083) + printf("emu%d: writing value 0x%x to HWCF5.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF5, 0, val); +} + +/* HWCF6: Configuration Double Word 6 */ +static void +emu_readhwcf6(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF6, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf6(sc_p scp, u_int val) +{ + if (val != 0x00008000) + printf("emu%d: writing value 0x%x to HWCF6.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF6, 0, val); +} + +/* SMALR: Sound Memory Address for Left SM Reads */ +static void +emu_readsmalr(sc_p scp, u_int *mt, u_int *smalr) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMALR, 0); + if (mt != NULL) + *mt = DECBIT(sts, 31, 1); + if (smalr != NULL) + *smalr = DECBIT(sts, 0, 24); +} + +static void +emu_writesmalr(sc_p scp, u_int mt, u_int smalr) +{ + emu_command(scp, EMU8K_SMALR, 0, + GENBIT(mt, 31, 1) + | GENBIT(smalr, 0, 24)); +} + +/* SMARR: Sound Memory Address for Right SM Reads */ +static void +emu_readsmarr(sc_p scp, u_int *mt, u_int *smarr) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMARR, 0); + if (mt != NULL) + *mt = DECBIT(sts, 31, 1); + if (smarr != NULL) + *smarr = DECBIT(sts, 0, 24); +} + +static void +emu_writesmarr(sc_p scp, u_int mt, u_int smarr) +{ + emu_command(scp, EMU8K_SMARR, 0, + GENBIT(mt, 31, 1) + | GENBIT(smarr, 0, 24)); +} + +/* SMALW: Sound Memory Address for Left SM Writes */ +static void +emu_readsmalw(sc_p scp, u_int *full, u_int *smalw) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMALW, 0); + if (full != NULL) + *full = DECBIT(sts, 31, 1); + if (smalw != NULL) + *smalw = DECBIT(sts, 0, 24); +} + +static void +emu_writesmalw(sc_p scp, u_int full, u_int smalw) +{ + emu_command(scp, EMU8K_SMALW, 0, + GENBIT(full, 31, 1) + | GENBIT(smalw, 0, 24)); +} + +/* SMARW: Sound Memory Address for Right SM Writes */ +static void +emu_readsmarw(sc_p scp, u_int *full, u_int *smarw) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMARW, 0); + if (full != NULL) + *full = DECBIT(sts, 31, 1); + if (smarw != NULL) + *smarw = DECBIT(sts, 0, 24); +} + +static void +emu_writesmarw(sc_p scp, u_int full, u_int smarw) +{ + emu_command(scp, EMU8K_SMARW, 0, + GENBIT(full, 31, 1) + | GENBIT(smarw, 0, 24)); +} + +/* SMLD: Sound Memory Left Data */ +static void +emu_readsmld(sc_p scp, u_short *smld) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMLD, 0); + if (smld != NULL) + *smld = sts; +} + +static void +emu_writesmld(sc_p scp, u_short smld) +{ + emu_command(scp, EMU8K_SMLD, 0, smld); +} + +/* SMRD: Sound Memory Right Data */ +static void +emu_readsmrd(sc_p scp, u_short *smrd) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_SMRD, 0); + if (smrd != NULL) + *smrd = sts; +} + +static void +emu_writesmrd(sc_p scp, u_short smrd) +{ + emu_command(scp, EMU8K_SMRD, 0, smrd); +} + +/* WC: Sample COunter */ +static void +emu_readwc(sc_p scp, u_int *wc) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_WC, 0); + if (wc != NULL) + *wc = sts; +} + +static void +emu_writewc(sc_p scp, u_int wc) +{ + emu_command(scp, EMU8K_WC, 0, wc); +} + +/* HWCF1: Configuration Double Word 1 */ +static void +emu_readhwcf1(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF1, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf1(sc_p scp, u_int val) +{ + if (val != 0x0059) + printf("emu%d: writing value 0x%x to HWCF1.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF1, 0, val); +} + +/* HWCF2: Configuration Double Word 2 */ +static void +emu_readhwcf2(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF2, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf2(sc_p scp, u_int val) +{ + if (val != 0x0020) + printf("emu%d: writing value 0x%x to HWCF2.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF2, 0, val); +} + +/* HWCF3: Configuration Double Word 3 */ +static void +emu_readhwcf3(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_HWCF3, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writehwcf3(sc_p scp, u_int val) +{ + if (val != 0x0004 && val != 0) + printf("emu%d: writing value 0x%x to HWCF3.\n", device_get_unit(scp->dev), val); + emu_command(scp, EMU8K_HWCF3, 0, val); +} + +/* INIT1: Initialization Array 1 */ +static void +emu_readinit1(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT1, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit1(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT1, chn, val); +} + +/* INIT2: Initialization Array 2 */ +static void +emu_readinit2(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT2, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit2(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT2, chn, val); +} + +/* INIT3: Initialization Array 3 */ +static void +emu_readinit3(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT3, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit3(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT3, chn, val); +} + +/* INIT4: Initialization Array 4 */ +static void +emu_readinit4(sc_p scp, int chn, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_INIT4, chn); + if (val != NULL) + *val = sts; +} + +static void +emu_writeinit4(sc_p scp, int chn, u_int val) +{ + emu_command(scp, EMU8K_INIT4, chn, val); +} + +/* ENVVOL: Volume Envelope Decay */ +static void +emu_readenvvol(sc_p scp, int chn, u_int *envvol) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ENVVOL, chn); + if (envvol != NULL) + *envvol = sts; +} + +static void +emu_writeenvvol(sc_p scp, int chn, u_int envvol) +{ + emu_command(scp, EMU8K_ENVVOL, chn, envvol); +} + +/* DCYSUSV: Volume Envelope Sustain and Decay */ +static void +emu_readdcysusv(sc_p scp, int chn, u_int *ph1v, u_int *susv, u_int *off, u_int *dcyv) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_DCYSUSV, chn); + if (ph1v != NULL) + *ph1v = DECBIT(sts, 15, 1); + if (susv != NULL) + *susv = DECBIT(sts, 8, 7); + if (off != NULL) + *off = DECBIT(sts, 7, 1); + if (dcyv != NULL) + *dcyv = DECBIT(sts, 0, 7); +} + +static void +emu_writedcysusv(sc_p scp, int chn, u_int ph1v, u_int susv, u_int off, u_int dcyv) +{ + emu_command(scp, EMU8K_DCYSUSV, chn, + GENBIT(ph1v, 15, 1) + | GENBIT(susv, 8, 7) + | GENBIT(off, 7, 1) + | GENBIT(dcyv, 0, 7)); +} + +/* ENVVAL: Modulation Envelope Decay */ +static void +emu_readenvval(sc_p scp, int chn, u_int *envval) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ENVVAL, chn); + if (envval != NULL) + *envval = sts; +} + +static void +emu_writeenvval(sc_p scp, int chn, u_int envval) +{ + emu_command(scp, EMU8K_ENVVAL, chn, envval); +} + +/* DCYSUS: Modulation Envelope Sustain and Decay */ +static void +emu_readdcysus(sc_p scp, int chn, u_int *ph1, u_int *sus, u_int *dcy) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_DCYSUS, chn); + if (ph1 != NULL) + *ph1 = DECBIT(sts, 15, 1); + if (sus != NULL) + *sus = DECBIT(sts, 8, 7); + if (dcy != NULL) + *dcy = DECBIT(sts, 0, 7); +} + +static void +emu_writedcysus(sc_p scp, int chn, u_int ph1, u_int sus, u_int dcy) +{ + emu_command(scp, EMU8K_DCYSUS, chn, + GENBIT(ph1, 15, 1) + | GENBIT(sus, 8, 7) + | GENBIT(dcy, 0, 7)); +} + +/* ATKHLDV: Volume Envelope Hold and Attack */ +static void +emu_readatkhldv(sc_p scp, int chn, u_int *atkhldv) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ATKHLDV, chn); + if (atkhldv != NULL) + *atkhldv = sts; +} + +static void +emu_writeatkhldv(sc_p scp, int chn, u_int atkhldv) +{ + emu_command(scp, EMU8K_ATKHLDV, chn, atkhldv); +} + +/* LFO1VAL: LFO #1 Delay */ +static void +emu_readlfo1val(sc_p scp, int chn, u_int *lfo1val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_LFO1VAL, chn); + if (lfo1val != NULL) + *lfo1val = sts; +} + +static void +emu_writelfo1val(sc_p scp, int chn, u_int lfo1val) +{ + emu_command(scp, EMU8K_LFO1VAL, chn, lfo1val); +} + +/* ATKHLD: Modulation Envelope Hold and Attack */ +static void +emu_readatkhld(sc_p scp, int chn, u_int *atkhld) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_ATKHLD, chn); + if (atkhld != NULL) + *atkhld = sts; +} + +static void +emu_writeatkhld(sc_p scp, int chn, u_int atkhld) +{ + emu_command(scp, EMU8K_ATKHLD, chn, atkhld); +} + +/* LFO2VAL: LFO #2 Delay */ +static void +emu_readlfo2val(sc_p scp, int chn, u_int *lfo2val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_LFO2VAL, chn); + if (lfo2val != NULL) + *lfo2val = sts; +} + +static void +emu_writelfo2val(sc_p scp, int chn, u_int lfo2val) +{ + emu_command(scp, EMU8K_LFO2VAL, chn, lfo2val); +} + +/* IP: Initial Pitch */ +static void +emu_readip(sc_p scp, int chn, u_int *ip) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_IP, chn); + if (ip != NULL) + *ip = sts; +} + +static void +emu_writeip(sc_p scp, int chn, u_int ip) +{ + emu_command(scp, EMU8K_IP, chn, ip); +} + +/* IFATN: Initial Filter Cutoff and Attenuation */ +static void +emu_readifatn(sc_p scp, int chn, u_int *ifc, u_int *atn) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_IFATN, chn); + if (ifc != NULL) + *ifc = DECBIT(sts, 8, 8); + if (atn != NULL) + *atn = DECBIT(sts, 0, 8); +} + +static void +emu_writeifatn(sc_p scp, int chn, u_int ifc, u_int atn) +{ + emu_command(scp, EMU8K_IFATN, chn, + GENBIT(ifc, 8, 8) + | GENBIT(atn, 0, 8)); +} + +/* PEFE: Pitch and Filter Envelope Heights */ +static void +emu_readpefe(sc_p scp, int chn, u_int *pe, u_int *fe) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PEFE, chn); + if (pe != NULL) + *pe = DECBIT(sts, 8, 8); + if (fe != NULL) + *fe = DECBIT(sts, 0, 8); +} + +static void +emu_writepefe(sc_p scp, int chn, u_int pe, u_int fe) +{ + emu_command(scp, EMU8K_PEFE, chn, + GENBIT(pe, 8, 8) + | GENBIT(fe, 0, 8)); +} + +/* FMMOD: Vibrato and Filter Modulation from LFO #1 */ +static void +emu_readfmmod(sc_p scp, int chn, u_int *fm, u_int *mod) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_FMMOD, chn); + if (fm != NULL) + *fm = DECBIT(sts, 8, 8); + if (mod != NULL) + *mod = DECBIT(sts, 0, 8); +} + +static void +emu_writefmmod(sc_p scp, int chn, u_int fm, u_int mod) +{ + emu_command(scp, EMU8K_FMMOD, chn, + GENBIT(fm, 8, 8) + | GENBIT(mod, 0, 8)); +} + +/* TREMFRQ: LFO #1 Tremolo Amount and Frequency */ +static void +emu_readtremfrq(sc_p scp, int chn, u_int *trem, u_int *frq) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_TREMFRQ, chn); + if (trem != NULL) + *trem = DECBIT(sts, 8, 8); + if (frq != NULL) + *frq = DECBIT(sts, 0, 8); +} + +static void +emu_writetremfrq(sc_p scp, int chn, u_int trem, u_int frq) +{ + emu_command(scp, EMU8K_TREMFRQ, chn, + GENBIT(trem, 8, 8) + | GENBIT(frq, 0, 8)); +} + +/* FM2FRQ2: LFO #2 Vibrato Amount and Frequency */ +static void +emu_readfm2frq2(sc_p scp, int chn, u_int *fm2, u_int *frq2) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_FM2FRQ2, chn); + if (fm2 != NULL) + *fm2 = DECBIT(sts, 8, 8); + if (frq2 != NULL) + *frq2 = DECBIT(sts, 0, 8); +} + +static void +emu_writefm2frq2(sc_p scp, int chn, u_int fm2, u_int frq2) +{ + emu_command(scp, EMU8K_FM2FRQ2, chn, + GENBIT(fm2, 8, 8) + | GENBIT(frq2, 0, 8)); +} + +/* PROBE: Probe Register */ +static void +emu_readprobe(sc_p scp, u_int *val) +{ + u_long sts; + + sts = emu_status(scp, EMU8K_PROBE, 0); + if (val != NULL) + *val = sts; +} + +static void +emu_writeprobe(sc_p scp, u_int val) +{ + emu_command(scp, EMU8K_PROBE, 0, val); +} + +/* Writes to a register. */ +static void +emu_command(sc_p scp, int reg, int chn, u_long val) +{ + if (chn < 0 || chn >= EMU8K_MAXVOICE || reg < 0 || reg >= EMU8K_REGNUM) + return; + + /* Override the channel if necessary. */ + if (emu_regs[reg].chn != EMU8K_CHN_ANY) + chn = emu_regs[reg].chn; + + /* Select the register first. */ + bus_space_write_2(rman_get_bustag(scp->io[EMU8K_IDX_PTR]), rman_get_bushandle(scp->io[EMU8K_IDX_PTR]), EMU8K_PORT_PTR, (chn & 0x1f) | ((emu_regs[reg].reg & 0x07) << 5)); + + /* Then we write the data. */ + bus_space_write_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port, val & 0xffff); + if (emu_regs[reg].size) + /* double word */ + bus_space_write_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port + 2, (val >> 16) & 0xffff); +} + +/* Reads from a register. */ +static u_long +emu_status(sc_p scp, int reg, int chn) +{ + u_long status; + + if (chn < 0 || chn >= EMU8K_MAXVOICE || reg < 0 || reg >= EMU8K_REGNUM) + return (0xffffffff); + + /* Override the channel if necessary. */ + if (emu_regs[reg].chn != EMU8K_CHN_ANY) + chn = emu_regs[reg].chn; + + /* Select the register first. */ + bus_space_write_2(rman_get_bustag(scp->io[EMU8K_IDX_PTR]), rman_get_bushandle(scp->io[EMU8K_IDX_PTR]), EMU8K_PORT_PTR, (chn & 0x1f) | ((emu_regs[reg].reg & 0x07) << 5)); + + /* Then we read the data. */ + status = bus_space_read_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port) & 0xffff; + if (emu_regs[reg].size) + /* double word */ + status |= (bus_space_read_2(rman_get_bustag(scp->io[emu_regs[reg].index]), rman_get_bushandle(scp->io[emu_regs[reg].index]), emu_regs[reg].port + 2) & 0xffff) << 16; + + return (status); +} + +/* Allocates resources. */ +static int +emu_allocres(sc_p scp, device_t dev) +{ + int iobase; + + if (scp->io[0] == NULL) { + scp->io_rid[0] = 0; + scp->io[0] = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid[0], 0, ~0, 4, RF_ACTIVE); + } + if (scp->io[0] == NULL) + return (1); + iobase = rman_get_start(scp->io[0]); + if (scp->io[1] == NULL) { + scp->io_rid[1] = 1; + scp->io[1] = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid[1], iobase + 0x400, iobase + 0x400 + 3, 4, RF_ACTIVE); + } + if (scp->io[2] == NULL) { + scp->io_rid[2] = 2; + scp->io[2] = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid[2], iobase + 0x800, iobase + 0x800 + 3, 4, RF_ACTIVE); + } + + if (scp->io[0] == NULL || scp->io[1] == NULL || scp->io[2] == NULL) { + printf("emu_allocres: failed.\n"); + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +emu_releaseres(sc_p scp, device_t dev) +{ + if (scp->io[0] != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]); + scp->io[0] = NULL; + } + if (scp->io[1] != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid[1], scp->io[1]); + scp->io[1] = NULL; + } + if (scp->io[2] != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid[2], scp->io[2]); + scp->io[2] = NULL; + } +} + +static device_method_t emu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , emu_probe ), + DEVMETHOD(device_attach, emu_attach), + + { 0, 0 }, +}; + +static driver_t emu_driver = { + "midi", + emu_methods, + sizeof(struct emu_softc), +}; + +DRIVER_MODULE(emu, isa, emu_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/gusc.c b/sys/dev/sound/isa/gusc.c index 7235e16..f75f2f0 100644 --- a/sys/dev/sound/isa/gusc.c +++ b/sys/dev/sound/isa/gusc.c @@ -53,6 +53,13 @@ #define LOGICALID_OPL 0x0300561e #define LOGICALID_MIDI 0x0400561e +/* PnP IDs */ +static struct isa_pnp_id gusc_ids[] = { + {LOGICALID_PCM, "GRV0000 Gravis UltraSound PnP PCM"}, /* GRV0000 */ + {LOGICALID_OPL, "GRV0003 Gravis UltraSound PnP OPL"}, /* GRV0003 */ + {LOGICALID_MIDI, "GRV0004 Gravis UltraSound PnP MIDI"}, /* GRV0004 */ +}; + /* Interrupt handler. */ struct gusc_ihandler { void (*intr)(void *); @@ -88,9 +95,7 @@ static struct resource *gusc_alloc_resource(device_t bus, device_t child, int ty static int gusc_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); -#if notyet static device_t find_masterdev(sc_p scp); -#endif /* notyet */ static int alloc_resource(sc_p scp); static int release_resource(sc_p scp); @@ -100,52 +105,53 @@ static int gusc_probe(device_t dev) { device_t child; - u_int32_t vend_id, logical_id; + u_int32_t logical_id; char *s; struct sndcard_func *func; - - vend_id = isa_get_vendorid(dev); - if (vend_id == 0) - return gusisa_probe(dev); + int ret; logical_id = isa_get_logicalid(dev); s = NULL; - if (vend_id == 0x0100561e) { /* Gravis */ - switch (logical_id) { - case LOGICALID_PCM: - s = "Gravis UltraSound Plug & Play PCM"; - func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); - if (func == NULL) - return (ENOMEM); - bzero(func, sizeof(*func)); - func->func = SCF_PCM; - child = device_add_child(dev, "pcm", -1); - device_set_ivars(child, func); - break; -#if notyet - case LOGICALID_OPL: - s = "Gravis UltraSound Plug & Play OPL"; - func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); - if (func == NULL) - return (ENOMEM); - bzero(func, sizeof(*func)); - func->func = SCF_SYNTH; - child = device_add_child(dev, "midi", -1); - device_set_ivars(child, func); - break; - case LOGICALID_MIDI: - s = "Gravis UltraSound Plug & Play MIDI"; - func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); - if (func == NULL) - return (ENOMEM); - bzero(func, sizeof(*func)); - func->func = SCF_MIDI; - child = device_add_child(dev, "midi", -1); - device_set_ivars(child, func); - break; -#endif /* notyet */ - } + /* Check isapnp ids */ + if (logical_id != 0 && (ret = ISA_PNP_PROBE(device_get_parent(dev), dev, gusc_ids)) != 0) + return (ret); + else { + if (logical_id == 0) + return gusisa_probe(dev); + } + + switch (logical_id) { + case LOGICALID_PCM: + s = "Gravis UltraSound Plug & Play PCM"; + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); + if (func == NULL) + return (ENOMEM); + bzero(func, sizeof(*func)); + func->func = SCF_PCM; + child = device_add_child(dev, "pcm", -1); + device_set_ivars(child, func); + break; + case LOGICALID_OPL: + s = "Gravis UltraSound Plug & Play OPL"; + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); + if (func == NULL) + return (ENOMEM); + bzero(func, sizeof(*func)); + func->func = SCF_SYNTH; + child = device_add_child(dev, "midi", -1); + device_set_ivars(child, func); + break; + case LOGICALID_MIDI: + s = "Gravis UltraSound Plug & Play MIDI"; + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); + if (func == NULL) + return (ENOMEM); + bzero(func, sizeof(*func)); + func->func = SCF_MIDI; + child = device_add_child(dev, "midi", -1); + device_set_ivars(child, func); + break; } if (s != NULL) { @@ -265,7 +271,6 @@ gusisa_probe(device_t dev) bus_set_resource(dev, SYS_RES_DRQ, 1, flags & DV_F_DRQ_MASK, 1); -#if notyet /* We can support the CS4231 and MIDI devices. */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); @@ -275,7 +280,6 @@ gusisa_probe(device_t dev) func->func = SCF_MIDI; child = device_add_child(dev, "midi", -1); device_set_ivars(child, func); -#endif /* notyet */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); if (func == NULL) @@ -342,13 +346,11 @@ gusc_intr(void *arg) (*scp->pcm_intr.intr)(scp->pcm_intr.arg); did_something = 1; } -#if notyet if (scp->midi_intr.intr != NULL && (port_rd(scp->io[1], 0) & 0x80)) { (*scp->midi_intr.intr)(scp->midi_intr.arg); did_something = 1; } -#endif /* notyet */ } while (did_something != 0); } @@ -444,7 +446,6 @@ gusc_setup_intr(device_t dev, device_t child, struct resource *irq, arg, cookiep); } -#if notyet static device_t find_masterdev(sc_p scp) { @@ -467,7 +468,6 @@ find_masterdev(sc_p scp) return (dev); } -#endif /* notyet */ static int io_range[3] = {0x10, 0x8 , 0x4 }; static int io_offset[3] = {0x0 , 0x100, 0x10c}; @@ -475,9 +475,7 @@ static int alloc_resource(sc_p scp) { int i, base, lid, flags; -#if notyet device_t dev; -#endif /* notyet */ flags = 0; if (isa_get_vendorid(scp->dev)) @@ -534,7 +532,6 @@ alloc_resource(sc_p scp) } } break; -#if notyet case LOGICALID_OPL: if (scp->io[0] == NULL) { scp->io_rid[0] = 0; @@ -567,7 +564,6 @@ alloc_resource(sc_p scp) scp->irq_alloced = 0; } break; -#endif /* notyet */ } return (0); } @@ -576,9 +572,7 @@ static int release_resource(sc_p scp) { int i, lid, flags; -#if notyet device_t dev; -#endif /* notyet */ flags = 0; if (isa_get_vendorid(scp->dev)) @@ -607,7 +601,6 @@ release_resource(sc_p scp) } } break; -#if notyet case LOGICALID_OPL: if (scp->io[0] != NULL) { bus_release_resource(scp->dev, SYS_RES_IOPORT, scp->io_rid[0], scp->io[0]); @@ -628,7 +621,6 @@ release_resource(sc_p scp) scp->irq = NULL; } break; -#endif /* notyet */ } return (0); } diff --git a/sys/dev/sound/isa/gusmidi.c b/sys/dev/sound/isa/gusmidi.c new file mode 100644 index 0000000..11644f8 --- /dev/null +++ b/sys/dev/sound/isa/gusmidi.c @@ -0,0 +1,523 @@ +/* + * GUS midi interface driver. + * Based on the newmidi MPU401 driver. + * + * Copyright (c) 1999 Ville-Pertti Keinonen + * 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 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. + * + * Modified: Riccardo Facchetti 24 Mar 1995 - Added the Audio Excel DSP 16 + * initialization routine. + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura <tanimura@r.dl.itc.u-tokyo.ac.jp> + * + * $FreeBSD$ + * + */ + +#include <dev/sound/midi/midi.h> + +#include <dev/sound/chip.h> +#include <machine/cpufunc.h> + +static devclass_t midi_devclass; + +extern synthdev_info midisynth_op_desc; + +/* These are the synthesizer and the midi interface information. */ +static struct synth_info gusmidi_synthinfo = { + "GUS MIDI", + 0, + SYNTH_TYPE_MIDI, + 0, + 0, + 128, + 128, + 128, + SYNTH_CAP_INPUT, +}; + +static struct midi_info gusmidi_midiinfo = { + "GUS MIDI", + 0, + 0, + 0, +}; + +#define MIDICTL_MASTER_RESET 0x03 +#define MIDICTL_TX_IRQ_EN 0x20 +#define MIDICTL_RX_IRQ_EN 0x80 + +#define MIDIST_RXFULL 0x01 +#define MIDIST_TXDONE 0x02 +#define MIDIST_ERR_FR 0x10 +#define MIDIST_ERR_OVR 0x20 +#define MIDIST_INTR_PEND 0x80 + +#define PORT_CTL 0 +#define PORT_ST 0 +#define PORT_TX 1 +#define PORT_RX 1 + +/* + * These functions goes into gusmidi_op_desc to get called + * from sound.c. + */ + +static int gusmidi_probe(device_t dev); +static int gusmidi_attach(device_t dev); + +static d_open_t gusmidi_open; +static d_ioctl_t gusmidi_ioctl; +driver_intr_t gusmidi_intr; +static midi_callback_t gusmidi_callback; + +/* Here is the parameter structure per a device. */ +struct gusmidi_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + struct resource *irq; /* Irq */ + int irq_rid; /* Irq resource ID */ + void *ih; /* Interrupt cookie */ + + struct callout_handle dh; /* Callout handler for delay */ + + int ctl; /* Control bits. */ +}; + +typedef struct gusmidi_softc *sc_p; + +/* These functions are local. */ +static int gusmidi_init(device_t dev); +static int gusmidi_allocres(sc_p scp, device_t dev); +static void gusmidi_releaseres(sc_p scp, device_t dev); +static void gusmidi_startplay(sc_p scp); +static void gusmidi_xmit(sc_p scp); +static u_int gusmidi_readport(sc_p scp, int off); +static void gusmidi_writeport(sc_p scp, int off, u_int8_t value); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info gusmidi_op_desc = { + "GUS midi", + + SNDCARD_GUS, + + gusmidi_open, + NULL, + NULL, + NULL, + gusmidi_ioctl, + NULL, + + gusmidi_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +static int +gusmidi_probe(device_t dev) +{ + char *s; + sc_p scp; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_MIDI) + return (ENXIO); + + s = "GUS Midi Interface"; + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->io_rid = 1; + scp->irq_rid = 0; +#if notdef + ret = mpu_probe2(dev); + if (ret != 0) + return (ret); +#endif /* notdef */ + device_set_desc(dev, s); + return (0); +} + +static int +gusmidi_attach(device_t dev) +{ + sc_p scp; + + scp = device_get_softc(dev); + + /* Allocate the resources, switch to uart mode. */ + if (gusmidi_allocres(scp, dev)) { + gusmidi_releaseres(scp, dev); + return (ENXIO); + } + + gusmidi_init(dev); + + return (0); +} + +static int +gusmidi_init(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + /* Fill the softc. */ + scp->dev = dev; + scp->devinfo = devinfo = &midi_info[unit]; + + /* Fill the midi info. */ + bcopy(&gusmidi_op_desc, devinfo, sizeof(gusmidi_op_desc)); + midiinit(devinfo, dev); + devinfo->flags = 0; + bcopy(&midisynth_op_desc, &devinfo->synth, sizeof(midisynth_op_desc)); + + if (scp->irq != NULL) + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x irq %d", + (u_int)rman_get_start(scp->io), (int)rman_get_start(scp->irq)); + else + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x", + (u_int)rman_get_start(scp->io)); + + /* Init the queue. */ + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = 1; + midibuf_init(&devinfo->midi_dbuf_in); + midibuf_init(&devinfo->midi_dbuf_out); + + bus_setup_intr(dev, scp->irq, INTR_TYPE_TTY, gusmidi_intr, scp, + &scp->ih); + + /* Increase the number of midi devices. */ + nmidi++; + + return (0); +} + +static int +gusmidi_open(dev_t i_dev, int flags, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("gusmidi_open: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("gusmidi_open: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + gusmidi_writeport(scp, PORT_CTL, MIDICTL_MASTER_RESET); + DELAY(100); + + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + + return (0); +} + +static int +gusmidi_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("gusmidi_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("gusmidi_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device > nmidi + nsynth || synthinfo->device != unit) + return (ENXIO); + bcopy(&gusmidi_synthinfo, synthinfo, sizeof(gusmidi_synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device > nmidi + nsynth || midiinfo->device != unit) + return (ENXIO); + bcopy(&gusmidi_midiinfo, midiinfo, sizeof(gusmidi_midiinfo)); + midiinfo->device = unit; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +void +gusmidi_intr(void *arg) +{ + sc_p scp; + int s; + u_char c; + mididev_info *devinfo; + int stat, did_something; + + scp = (sc_p)arg; + devinfo = scp->devinfo; + + s = splclock(); + + /* XXX No framing/overrun checks... */ + do { + stat = gusmidi_readport(scp, PORT_ST); + did_something = 0; + if (stat & MIDIST_RXFULL) { + c = gusmidi_readport(scp, PORT_RX); + if ((devinfo->flags & MIDI_F_PASSTHRU) && + (!(devinfo->flags & MIDI_F_BUSY) || + !(devinfo->fflags & FWRITE))) { + midibuf_input_intr(&devinfo->midi_dbuf_passthru, + &c, sizeof c); + devinfo->callback(devinfo, + MIDI_CB_START | MIDI_CB_WR); + } + if ((devinfo->flags & MIDI_F_READING) && c != 0xfe) + midibuf_input_intr(&devinfo->midi_dbuf_in, + &c, sizeof c); + did_something = 1; + } + if (stat & MIDIST_TXDONE) { + if (devinfo->flags & MIDI_F_WRITING) { + gusmidi_xmit(scp); + did_something = 1; + } else if (scp->ctl & MIDICTL_TX_IRQ_EN) { + /* This shouldn't happen. */ + scp->ctl &= ~MIDICTL_TX_IRQ_EN; + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + } + } + } while (did_something != 0); + + /* Invoke the upper layer. */ + midi_intr(devinfo); + + splx(s); +} + +static int +gusmidi_callback(mididev_info *d, int reason) +{ + int unit; + sc_p scp; + + if (d == NULL) { + DEB(printf("gusmidi_callback: device not configured.\n")); + return (ENXIO); + } + + unit = d->unit; + scp = d->softc; + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) == 0) { + /* Begin recording. */ + d->flags |= MIDI_F_READING; + scp->ctl |= MIDICTL_RX_IRQ_EN; + } + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + gusmidi_startplay(scp); + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) != 0) { + /* Stop recording. */ + d->flags &= ~MIDI_F_READING; + scp->ctl &= ~MIDICTL_RX_IRQ_EN; + } + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) != 0) { + /* Stop Playing. */ + d->flags &= ~MIDI_F_WRITING; + scp->ctl &= ~MIDICTL_TX_IRQ_EN; + } + break; + } + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + * Call this at >=splclock. + */ +static void +gusmidi_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + devinfo->flags |= MIDI_F_WRITING; + scp->ctl |= MIDICTL_TX_IRQ_EN; +} + +static void +gusmidi_xmit(sc_p scp) +{ + register mididev_info *devinfo; + register midi_dbuf *dbuf; + u_char c; + + devinfo = scp->devinfo; + + /* See which source to use. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) == 0 || ((devinfo->flags & MIDI_F_BUSY) != 0 && (devinfo->fflags & FWRITE) != 0)) + dbuf = &devinfo->midi_dbuf_out; + else + dbuf = &devinfo->midi_dbuf_passthru; + + /* Transmit the data in the queue. */ + while ((devinfo->flags & MIDI_F_WRITING) && + (gusmidi_readport(scp, PORT_ST) & MIDIST_TXDONE)) { + /* Do we have the data to transmit? */ + if (dbuf->rl == 0) { + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + scp->ctl &= ~MIDICTL_TX_IRQ_EN; + gusmidi_writeport(scp, PORT_CTL, scp->ctl); + break; + } else { + /* Send the data. */ + midibuf_output_intr(dbuf, &c, sizeof(c)); + gusmidi_writeport(scp, PORT_TX, c); + /* We are playing now. */ + } + } +} + +/* Reads from a port. */ +static u_int +gusmidi_readport(sc_p scp, int off) +{ + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off) & 0xff; +} + +/* Writes to a port. */ +static void +gusmidi_writeport(sc_p scp, int off, u_int8_t value) +{ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off, value); +} + +/* Allocates resources. */ +static int +gusmidi_allocres(sc_p scp, device_t dev) +{ + if (scp->io == NULL) { + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 2, RF_ACTIVE); + if (scp->io == NULL) + return (1); + } +#if notdef + if (scp->irq == NULL && !(device_get_flags(dev) & MPU_DF_NO_IRQ)) { +#else + if (scp->irq == NULL) { +#endif /* notdef */ + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (scp->irq == NULL) + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +gusmidi_releaseres(sc_p scp, device_t dev) +{ + if (scp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } +} + +static device_method_t gusmidi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , gusmidi_probe ), + DEVMETHOD(device_attach, gusmidi_attach), + + { 0, 0 }, +}; + +driver_t gusmidi_driver = { + "midi", + gusmidi_methods, + sizeof(struct gusmidi_softc), +}; + +DRIVER_MODULE(gusmidi, gusc, gusmidi_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/mpu.c b/sys/dev/sound/isa/mpu.c new file mode 100644 index 0000000..7fd89bb --- /dev/null +++ b/sys/dev/sound/isa/mpu.c @@ -0,0 +1,835 @@ +/* + * The low level driver for Roland MPU-401 compatible Midi interfaces. + * + * 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 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. + * + * Modified: Riccardo Facchetti 24 Mar 1995 - Added the Audio Excel DSP 16 + * initialization routine. + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura + * + * This is the MPU401 midi interface driver for FreeBSD, based on the Luigi Sound Driver. + * This handles io against /dev/midi, the midi {in, out}put event queues + * and the event/message transmittion to/from an MPU401 interface. + * + * $FreeBSD$ + * + */ + +#include "opt_devfs.h" + +#include <dev/sound/midi/midi.h> +#include <dev/sound/chip.h> +#include <machine/cpufunc.h> + +#include <isa/isavar.h> +#include <isa/sioreg.h> +#include <isa/ic/ns16550.h> + +#define MPU_USEMICROTIMER 0 + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +#define MPU_DATAPORT 0 +#define MPU_CMDPORT 1 +#define MPU_STATPORT 1 + +#define MPU_RESET 0xff +#define MPU_UART 0x3f +#define MPU_ACK 0xfe + +#define MPU_STATMASK 0xc0 +#define MPU_OUTPUTBUSY 0x40 +#define MPU_INPUTBUSY 0x80 + +#define MPU_TRYDATA 50 +#define MPU_DELAY 25000 + +/* Device flag. */ +#define MPU_DF_NO_IRQ 1 + +extern synthdev_info midisynth_op_desc; + +/* PnP IDs */ +static struct isa_pnp_id mpu_ids[] = { + {0x01200001, "@H@2001 Midi Interface"}, /* @H@2001 */ + {0x01100001, "@H@1001 Midi Interface"}, /* @H@1001 */ +#if notdef + /* TODO: write bridge driver for these devices */ + {0x0000630e, "CSC0000 Midi Interface"}, /* CSC0000 */ + {0x2100a865, "YMH0021 Midi Interface"}, /* YMH0021 */ + {0x80719304, "ADS7180 Midi Interface"}, /* ADS7180 */ + {0x0300561e, "GRV0003 Midi Interface"}, /* GRV0003 */ +#endif +}; + +/* These are the synthesizer and the midi interface information. */ +static struct synth_info mpu_synthinfo = { + "MPU401 MIDI", + 0, + SYNTH_TYPE_MIDI, + 0, + 0, + 128, + 128, + 128, + SYNTH_CAP_INPUT, +}; + +static struct midi_info mpu_midiinfo = { + "MPU401 MIDI", + 0, + 0, + 0, +}; + +/* + * These functions goes into mpu_op_desc to get called + * from sound.c. + */ + +static int mpu_probe(device_t dev); +static int mpu_probe1(device_t dev); +static int mpu_probe2(device_t dev); +static int mpu_attach(device_t dev); +static int mpusbc_probe(device_t dev); +static int mpusbc_attach(device_t dev); + +static d_ioctl_t mpu_ioctl; +static driver_intr_t mpu_intr; +static midi_callback_t mpu_callback; + +/* Here is the parameter structure per a device. */ +struct mpu_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + u_long irq_val; /* Irq value */ + struct resource *irq; /* Irq */ + int irq_rid; /* Irq resource ID */ + void *ih; /* Interrupt cookie */ + + struct callout_handle dh; /* Callout handler for delay */ + + int fflags; /* File flags */ +}; + +typedef struct mpu_softc *sc_p; + +/* These functions are local. */ +static void mpu_startplay(sc_p scp); +static void mpu_xmit(sc_p scp); +#if MPU_USEMICROTIMER +static void mpu_timeout(sc_p scp); +static timeout_t mpu_timer; +#endif /* MPU_USEMICROTIMER */ +static int mpu_resetmode(sc_p scp); +static int mpu_uartmode(sc_p scp); +static int mpu_waitack(sc_p scp); +static int mpu_status(sc_p scp); +static int mpu_command(sc_p scp, u_int8_t value); +static int mpu_readdata(sc_p scp); +static int mpu_writedata(sc_p scp, u_int8_t value); +static u_int mpu_readport(sc_p scp, int off); +static void mpu_writeport(sc_p scp, int off, u_int8_t value); +static int mpu_allocres(sc_p scp, device_t dev); +static void mpu_releaseres(sc_p scp, device_t dev); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info mpu_op_desc = { + "MPU401 midi", + + SNDCARD_MPU401, + + NULL, + NULL, + NULL, + NULL, + mpu_ioctl, + NULL, + + mpu_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + */ + +static int +mpu_probe(device_t dev) +{ + sc_p scp; + int ret; + + /* Check isapnp ids */ + if (isa_get_logicalid(dev) != 0) + return (ISA_PNP_PROBE(device_get_parent(dev), dev, mpu_ids)); + + scp = device_get_softc(dev); + + device_set_desc(dev, mpu_op_desc.name); + bzero(scp, sizeof(*scp)); + + scp->io_rid = 0; + ret = mpu_probe1(dev); + if (ret != 0) + return (ret); + ret = mpu_probe2(dev); + if (ret != 0) + return (ret); + + return (0); +} + +/* + * Make sure this is an MPU401, not an 16550 uart. + * Called only for non-pnp devices. + */ +static int +mpu_probe1(device_t dev) +{ + sc_p scp; + int iir; + struct resource *io; + + scp = device_get_softc(dev); + + /* + * If an MPU401 is ready to both input and output, + * the status register value is zero, which may + * confuse an 16550 uart to probe as an MPU401. + * We read the IIR (base + 2), which is not used + * by an MPU401. + */ + io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 3, RF_ACTIVE); + iir = bus_space_read_1(rman_get_bustag(io), rman_get_bushandle(io), com_iir) & 0xff; + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, io); + if ((iir & ~(IIR_IMASK | IIR_FIFO_MASK)) == 0) + /* Likely to be an 16550. */ + return (ENXIO); + + return (0); +} + +/* Look up the irq. */ +static int +mpu_probe2(device_t dev) +{ + sc_p scp; + int unit, i; + intrmask_t irqp0, irqp1; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 2, RF_ACTIVE); + if (scp->io == NULL) + return (ENXIO); + + DEB(printf("mpu%d: probing.\n", unit)); + + /* Reset the interface. */ + if (mpu_resetmode(scp) != 0 || mpu_waitack(scp) != 0) { + printf("mpu%d: reset failed.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + + /* + * At this point, we are likely to have an interface. + * + * Switching the interface to uart mode gives us an interrupt. + * We can make use of it to determine the irq. + * Idea-stolen-from: sys/isa/sio.c:sioprobe() + */ + + disable_intr(); + + /* + * See the initial irq. We have to do this now, + * otherwise a midi module/instrument might send + * an active sensing, to mess up the irq. + */ + irqp0 = isa_irq_pending(); + irqp1 = 0; + + /* Switch to uart mode. */ + if (mpu_uartmode(scp) != 0) { + enable_intr(); + printf("mpu%d: mode switching failed.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + + if (device_get_flags(dev) & MPU_DF_NO_IRQ) { + irqp0 = irqp1 = 0; + goto no_irq; + } + + /* See which irq we have now. */ + for (i = 0 ; i < MPU_TRYDATA ; i++) { + DELAY(MPU_DELAY); + irqp1 = isa_irq_pending(); + if (irqp1 != irqp0) + break; + } + if (irqp1 == irqp0) { + enable_intr(); + printf("mpu%d: switching the mode gave no interrupt.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + +no_irq: + /* Wait to see an ACK. */ + if (mpu_waitack(scp) != 0) { + enable_intr(); + printf("mpu%d: not acked.\n", unit); + mpu_releaseres(scp, dev); + return (ENXIO); + } + + enable_intr(); + + if (device_get_flags(dev) & MPU_DF_NO_IRQ) + scp->irq_val = 0; + else + /* We have found the irq. */ + scp->irq_val = ffs(~irqp0 & irqp1) - 1; + + DEB(printf("mpu%d: probed.\n", unit)); + + return (0); +} + +static int +mpusbc_probe(device_t dev) +{ + char *s; + sc_p scp; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_MIDI) + return (ENXIO); + + s = "SB Midi Interface"; + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->io_rid = 1; + scp->irq_rid = 0; + device_set_desc(dev, s); + return (0); +} + +static int +mpu_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + DEB(printf("mpu%d: attaching.\n", unit)); + + /* Allocate the resources, switch to uart mode. */ + if (mpu_allocres(scp, dev) || mpu_uartmode(scp)) { + mpu_releaseres(scp, dev); + return (ENXIO); + } + + /* mpu_probe() has put the interface to uart mode. */ + + /* Fill the softc. */ + scp->dev = dev; + scp->devinfo = devinfo = &midi_info[unit]; + callout_handle_init(&scp->dh); + + /* Fill the midi info. */ + bcopy(&mpu_op_desc, devinfo, sizeof(mpu_op_desc)); + midiinit(devinfo, dev); + devinfo->flags = 0; + bcopy(&midisynth_op_desc, &devinfo->synth, sizeof(midisynth_op_desc)); + if (scp->irq != NULL) + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x irq %d", + (u_int)rman_get_start(scp->io), (int)rman_get_start(scp->irq)); + else + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x", + (u_int)rman_get_start(scp->io)); + + /* Init the queue. */ + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = 1; + midibuf_init(&devinfo->midi_dbuf_in); + midibuf_init(&devinfo->midi_dbuf_out); + + /* Increase the number of midi devices. */ + nmidi++; + + /* Now we can handle the interrupts. */ + if (scp->irq != NULL) + bus_setup_intr(dev, scp->irq, INTR_TYPE_TTY, mpu_intr, scp, + &scp->ih); + + DEB(printf("mpu%d: attached.\n", unit)); + + return (0); +} + +static int +mpusbc_attach(device_t dev) +{ + sc_p scp; + int unit; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + mpu_attach(dev); + + return (0); +} + +static int +mpu_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("mpu_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("mpu_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device > nmidi + nsynth || synthinfo->device != unit) + return (ENXIO); + bcopy(&mpu_synthinfo, synthinfo, sizeof(mpu_synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device > nmidi + nsynth || midiinfo->device != unit) + return (ENXIO); + bcopy(&mpu_midiinfo, midiinfo, sizeof(mpu_midiinfo)); + midiinfo->device = unit; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static void +mpu_intr(void *arg) +{ + sc_p scp; + u_char c; + mididev_info *devinfo; + + scp = (sc_p)arg; + devinfo = scp->devinfo; + + /* Read the received data. */ + while ((mpu_status(scp) & MPU_INPUTBUSY) == 0) { + /* Receive the data. */ + c = mpu_readdata(scp); + /* Queue into the passthru buffer and start transmitting if we can. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) != 0 && ((devinfo->flags & MIDI_F_BUSY) == 0 || (devinfo->fflags & FWRITE) == 0)) { + midibuf_input_intr(&devinfo->midi_dbuf_passthru, &c, sizeof(c)); + devinfo->callback(devinfo, MIDI_CB_START | MIDI_CB_WR); + } + /* Queue if we are reading. Discard an active sensing. */ + if ((devinfo->flags & MIDI_F_READING) != 0 && c != 0xfe) + midibuf_input_intr(&devinfo->midi_dbuf_in, &c, sizeof(c)); + } + + /* Invoke the upper layer. */ + midi_intr(devinfo); + +} + +static int +mpu_callback(mididev_info *d, int reason) +{ + int unit; + sc_p scp; + + if (d == NULL) { + DEB(printf("mpu_callback: device not configured.\n")); + return (ENXIO); + } + + unit = d->unit; + scp = d->softc; + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) == 0) + /* Begin recording. */ + d->flags |= MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + mpu_startplay(scp); + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) != 0) + /* Stop recording. */ + d->flags &= ~MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) != 0) + /* Stop Playing. */ + d->flags &= ~MIDI_F_WRITING; + break; + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + * Call this at >=splclock. + */ +static void +mpu_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + devinfo->flags |= MIDI_F_WRITING; +#if MPU_USEMICROTIMER + mpu_timeout(scp); +#else + mpu_xmit(scp); +#endif /* MPU_USEMICROTIMER */ +} + +static void +mpu_xmit(sc_p scp) +{ + register mididev_info *devinfo; + register midi_dbuf *dbuf; + u_char c; + + devinfo = scp->devinfo; + + /* See which source to use. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) == 0 || ((devinfo->flags & MIDI_F_BUSY) != 0 && (devinfo->fflags & FWRITE) != 0)) + dbuf = &devinfo->midi_dbuf_out; + else + dbuf = &devinfo->midi_dbuf_passthru; + + /* Transmit the data in the queue. */ +#if MPU_USEMICROTIMER + while ((devinfo->flags & MIDI_F_WRITING) != 0 && (mpu_status(scp) & MPU_OUTPUTBUSY) == 0) { + /* Do we have the data to transmit? */ + if (dbuf->rl == 0) { + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + break; + } else { + /* Send the data. */ + midibuf_output_intr(dbuf, &c, sizeof(c)); + mpu_writedata(scp, c); + /* We are playing now. */ + devinfo->flags |= MIDI_F_WRITING; + } + } + + /* De we have still more? */ + if ((devinfo->flags & MIDI_F_WRITING) != 0) + /* Handle them on the next interrupt. */ + mpu_timeout(scp); +#else + while ((devinfo->flags & MIDI_F_WRITING) != 0 && dbuf->rl > 0) { + /* XXX Wait until we can write the data. */ + while ((mpu_status(scp) & MPU_OUTPUTBUSY) != 0); + /* Send the data. */ + midibuf_output_intr(dbuf, &c, sizeof(c)); + mpu_writedata(scp, c); + /* We are playing now. */ + devinfo->flags |= MIDI_F_WRITING; + } + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; +#endif /* MPU_USEMICROTIMER */ +} + +#if MPU_USEMICROTIMER +/* Arm a timer. */ +static void +mpu_timeout(sc_p scp) +{ + microtimeout(mpu_timer, scp, hz * hzmul / 3125); +} + +/* Called when a timer has beeped. */ +static void +mpu_timer(void *arg) +{ + sc_p scp; + + scp = arg; + mpu_xmit(scp); +} +#endif /* MPU_USEMICROTIMER */ + +/* Reset mpu. */ +static int +mpu_resetmode(sc_p scp) +{ + int i, resp; + + /* Reset the mpu. */ + resp = 0; + for (i = 0 ; i < MPU_TRYDATA ; i++) { + resp = mpu_command(scp, MPU_RESET); + if (resp == 0) + break; + } + if (resp != 0) + return (1); + + DELAY(MPU_DELAY); + return (0); +} + +/* Switch to uart mode. */ +static int +mpu_uartmode(sc_p scp) +{ + int i, resp; + + /* Switch to uart mode. */ + resp = 0; + for (i = 0 ; i < MPU_TRYDATA ; i++) { + resp = mpu_command(scp, MPU_UART); + if (resp == 0) + break; + } + if (resp != 0) + return (1); + + DELAY(MPU_DELAY); + return (0); +} + +/* Wait to see an ACK. */ +static int +mpu_waitack(sc_p scp) +{ + int i, resp; + + resp = 0; + for (i = 0 ; i < MPU_TRYDATA ; i++) { + resp = mpu_readdata(scp); + if (resp >= 0) + break; + } + if (resp != MPU_ACK) + return (1); + + DELAY(MPU_DELAY); + return (0); +} + +/* Reads the status. */ +static int +mpu_status(sc_p scp) +{ + return mpu_readport(scp, MPU_STATPORT); +} + +/* Writes a command. */ +static int +mpu_command(sc_p scp, u_int8_t value) +{ + u_int status; + + /* Is the interface ready to write? */ + status = mpu_status(scp); + if ((status & MPU_OUTPUTBUSY) != 0) + /* The interface is busy. */ + return (EAGAIN); + + mpu_writeport(scp, MPU_CMDPORT, value); + + return (0); +} + +/* Reads a byte of data. */ +static int +mpu_readdata(sc_p scp) +{ + u_int status; + + /* Is the interface ready to write? */ + status = mpu_status(scp); + if ((status & MPU_INPUTBUSY) != 0) + /* The interface is busy. */ + return (-EAGAIN); + + return (int)mpu_readport(scp, MPU_DATAPORT) & 0xff; +} + +/* Writes a byte of data. */ +static int +mpu_writedata(sc_p scp, u_int8_t value) +{ + u_int status; + + /* Is the interface ready to write? */ + status = mpu_status(scp); + if ((status & MPU_OUTPUTBUSY) != 0) + /* The interface is busy. */ + return (EAGAIN); + + mpu_writeport(scp, MPU_DATAPORT, value); + + return (0); +} + +/* Reads from a port. */ +static u_int +mpu_readport(sc_p scp, int off) +{ + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off) & 0xff; +} + +/* Writes to a port. */ +static void +mpu_writeport(sc_p scp, int off, u_int8_t value) +{ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off, value); +} + +/* Allocates resources. */ +static int +mpu_allocres(sc_p scp, device_t dev) +{ + if (scp->io == NULL) { + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 2, RF_ACTIVE); + if (scp->io == NULL) + return (1); + } + if (scp->irq == NULL && !(device_get_flags(dev) & MPU_DF_NO_IRQ)) { + if (scp->irq_val == 0) + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + else + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, scp->irq_val, scp->irq_val, 1, RF_ACTIVE | RF_SHAREABLE); + if (scp->irq == NULL) + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +mpu_releaseres(sc_p scp, device_t dev) +{ + if (scp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } +} + +static device_method_t mpu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , mpu_probe ), + DEVMETHOD(device_attach, mpu_attach), + + { 0, 0 }, +}; + +static driver_t mpu_driver = { + "midi", + mpu_methods, + sizeof(struct mpu_softc), +}; + +DRIVER_MODULE(mpu, isa, mpu_driver, midi_devclass, 0, 0); + +static device_method_t mpusbc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , mpusbc_probe ), + DEVMETHOD(device_attach, mpusbc_attach), + + { 0, 0 }, +}; + +static driver_t mpusbc_driver = { + "midi", + mpusbc_methods, + sizeof(struct mpu_softc), +}; + +DRIVER_MODULE(mpusbc, sbc, mpusbc_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/mss.c b/sys/dev/sound/isa/mss.c index 374dc31..4e1967f 100644 --- a/sys/dev/sound/isa/mss.c +++ b/sys/dev/sound/isa/mss.c @@ -34,10 +34,6 @@ #include <dev/sound/isa/mss.h> #include <dev/sound/chip.h> -#if notyet -#include "midi.h" -#endif /* notyet */ - #define MSS_BUFFSIZE (65536 - 256) #define abs(x) (((x) < 0) ? -(x) : (x)) @@ -352,12 +348,8 @@ gusmax_setup(struct mss_info *mss, device_t dev, struct resource *alt) port_wr(alt, 0x0f, 0x00); irqctl = irq_bits[isa_get_irq(parent)]; -#if notyet -#if NMIDI > 0 /* Share the IRQ with the MIDI driver. */ irqctl |= 0x40; -#endif /* NMIDI > 0 */ -#endif /* notyet */ dmactl = dma_bits[isa_get_drq(parent)]; if (device_get_flags(parent) & DV_F_DUAL_DMA) dmactl |= dma_bits[device_get_flags(parent) & DV_F_DRQ_MASK] @@ -495,7 +487,7 @@ mss_probe(device_t dev) int flags, irq, drq, result = ENXIO, setres = 0; struct mss_info *mss; - if (isa_get_vendorid(dev)) return ENXIO; /* not yet */ + if (isa_get_logicalid(dev)) return ENXIO; /* not yet */ mss = (struct mss_info *)malloc(sizeof *mss, M_DEVBUF, M_NOWAIT); if (!mss) return ENXIO; @@ -1556,7 +1548,7 @@ guspcm_attach(device_t dev) mss->drq1_rid = 1; mss->drq2_rid = -1; - if (isa_get_vendorid(parent) == 0) + if (isa_get_logicalid(parent) == 0) mss->bd_id = MD_GUSMAX; else { mss->bd_id = MD_GUSPNP; diff --git a/sys/dev/sound/isa/opl.c b/sys/dev/sound/isa/opl.c new file mode 100644 index 0000000..3d2c9d6 --- /dev/null +++ b/sys/dev/sound/isa/opl.c @@ -0,0 +1,1904 @@ +/* + * A low level driver for Yamaha YM3812 and OPL-3 -chips + * + * 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 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. + * + */ + +/* + * Major improvements to the FM handling 30AUG92 by Rob Hooft, + */ +/* + * hooft@chem.ruu.nl + */ +/* + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura + * + * This is the OPL2/3/4 chip driver for FreeBSD, based on the Luigi Sound Driver. + * This handles io against /dev/midi, the midi {in, out}put event queues + * and the event/message operation to the OPL chip. + * + * $FreeBSD$ + * + */ + +#include "opt_devfs.h" + +#include <dev/sound/midi/midi.h> +#include <dev/sound/chip.h> + +#include <isa/isavar.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +/* + * 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 OPL4_ENABLE 0x02 + +#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 */ + int ch; /* channel (left=USE_LEFT, right=USE_RIGHT) */ + 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 pv_map[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}} +}; + +/* These are the tuning parameters. */ +static 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 +}; + +static 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 +}; + +/* + * 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) + */ +static char opl_volumetable[128] = +{ + -64, -48, -40, -35, -32, -29, -27, -26, + -24, -23, -21, -20, -19, -18, -18, -17, + -16, -15, -15, -14, -13, -13, -12, -12, + -11, -11, -10, -10, -10, -9, -9, -8, + -8, -8, -7, -7, -7, -6, -6, -6, + -5, -5, -5, -5, -4, -4, -4, -4, + -3, -3, -3, -3, -2, -2, -2, -2, + -2, -1, -1, -1, -1, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 4, + 4, 4, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8}; + +#define MAX_VOICE 18 +#define OFFS_4OP 11 +#define SBFM_MAXINSTR 256 + +/* These are the OPL Models. */ +#define MODEL_NONE 0 +#define MODEL_OPL2 2 +#define MODEL_OPL3 3 +#define MODEL_OPL4 4 + +/* These are the OPL Voice modes. */ +#define VOICE_NONE 0 +#define VOICE_2OP 2 +#define VOICE_4OP 4 + +/* PnP IDs */ +static struct isa_pnp_id opl_ids[] = { + {0x01200001, "@H@2001 FM Synthesizer"}, /* @H@2001 */ + {0x01100001, "@H@1001 FM Synthesizer"}, /* @H@1001 */ +#if notdef + /* TODO: write bridge drivers for these devices. */ + {0x0000630e, "CSC0000 FM Synthesizer"}, /* CSC0000 */ + {0x68187316, "ESS1868 FM Synthesizer"}, /* ESS1868 */ + {0x79187316, "ESS1879 FM Synthesizer"}, /* ESS1879 */ + {0x2100a865, "YMH0021 FM Synthesizer"}, /* YMH0021 */ + {0x80719304, "ADS7180 FM Synthesizer"}, /* ADS7180 */ + {0x0300561e, "GRV0003 FM Synthesizer"}, /* GRV0003 */ +#endif /* notdef */ +}; + +/* These are the default io bases. */ +static int opl_defaultiobase[] = { + 0x388, + 0x380, +}; + +/* These are the per-voice information. */ +struct voice_info { + u_char keyon_byte; + long bender; + long bender_range; + u_long orig_freq; + u_long current_freq; + int volume; + int mode; +}; + +/* These are the synthesizer and the midi device information. */ +static struct synth_info opl_synthinfo = { + "OPL FM Synthesizer", + 0, + SYNTH_TYPE_FM, + FM_TYPE_ADLIB, + 0, + 9, + 0, + SBFM_MAXINSTR, + 0, +}; + +static struct midi_info opl_midiinfo = { + "OPL FM Synthesizer", + 0, + 0, + 0, +}; + +/* + * These functions goes into oplsynthdev_op_desc. + */ +static mdsy_killnote_t opl_killnote; +static mdsy_setinstr_t opl_setinstr; +static mdsy_startnote_t opl_startnote; +static mdsy_reset_t opl_reset; +static mdsy_hwcontrol_t opl_hwcontrol; +static mdsy_loadpatch_t opl_loadpatch; +static mdsy_panning_t opl_panning; +static mdsy_aftertouch_t opl_aftertouch; +static mdsy_controller_t opl_controller; +static mdsy_patchmgr_t opl_patchmgr; +static mdsy_bender_t opl_bender; +static mdsy_allocvoice_t opl_allocvoice; +static mdsy_setupvoice_t opl_setupvoice; +static mdsy_sendsysex_t opl_sendsysex; +static mdsy_prefixcmd_t opl_prefixcmd; +static mdsy_volumemethod_t opl_volumemethod; + +/* + * This is the synthdev_info for an OPL3 chip. + */ +static synthdev_info oplsynth_op_desc = { + opl_killnote, + opl_setinstr, + opl_startnote, + opl_reset, + opl_hwcontrol, + opl_loadpatch, + opl_panning, + opl_aftertouch, + opl_controller, + opl_patchmgr, + opl_bender, + opl_allocvoice, + opl_setupvoice, + opl_sendsysex, + opl_prefixcmd, + opl_volumemethod, +}; + +/* Here is the parameter structure per a device. */ +struct opl_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + + int model; /* OPL model */ + struct synth_info synthinfo; /* Synthesizer information */ + + struct sbi_instrument i_map[SBFM_MAXINSTR]; /* Instrument map */ + struct sbi_instrument *act_i[SBFM_MAXINSTR]; /* Active instruments */ + struct physical_voice_info pv_map[MAX_VOICE]; /* Physical voice map */ + int cmask; /* Connection mask */ + int lv_map[MAX_VOICE]; /* Level map */ + struct voice_info voc[MAX_VOICE]; /* Voice information */ +}; + +typedef struct opl_softc *sc_p; + +/* + * These functions goes into opl_op_desc to get called + * from sound.c. + */ + +static int opl_probe(device_t dev); +static int opl_probe1(sc_p scp); +static int opl_attach(device_t dev); +static int oplsbc_probe(device_t dev); +static int oplsbc_attach(device_t dev); + +static d_open_t opl_open; +static d_close_t opl_close; +static d_read_t opl_read; +static d_write_t opl_write; +static d_ioctl_t opl_ioctl; +static midi_callback_t opl_callback; + +/* These go to snddev_info. */ +static mdsy_readraw_t opl_readraw; +static mdsy_writeraw_t opl_writeraw; + +/* These functions are local. */ +static void opl_startplay(sc_p scp); +static void opl_command(sc_p scp, int ch, int addr, u_int val); +static int opl_status(sc_p scp); +static void opl_enter4opmode(sc_p scp); +static void opl_storeinstr(sc_p scp, int instr_no, struct sbi_instrument *instr); +static void opl_calcvol(u_char *regbyte, int volume, int main_vol); +static void opl_setvoicevolume(sc_p scp, int voice, int volume, int main_vol); +static void opl_freqtofnum(int freq, int *block, int *fnum); +static int opl_bendpitch(sc_p scp, int voice, int val); +static int opl_notetofreq(int note_num); +static u_long opl_computefinetune(u_long base_freq, int bend, int range); +static int opl_allocres(sc_p scp, device_t dev); +static void opl_releaseres(sc_p scp, device_t dev); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info opl_op_desc = { + "OPL FM Synthesizer", + + SNDCARD_OPL, + + opl_open, + opl_close, + opl_read, + opl_write, + opl_ioctl, + NULL, + + opl_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + */ + +static int +opl_probe(device_t dev) +{ + sc_p scp; + int unit, i; + + /* Check isapnp ids */ + if (isa_get_logicalid(dev) != 0) + return (ISA_PNP_PROBE(device_get_parent(dev), dev, opl_ids)); + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + device_set_desc(dev, opl_op_desc.name); + bzero(scp, sizeof(*scp)); + + DEB(printf("opl%d: probing.\n", unit)); + + scp->io_rid = 0; + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 4, RF_ACTIVE); + if (opl_allocres(scp, dev)) { + /* We try the defaults in opl_defaultiobase. */ + DEB(printf("opl%d: port is omitted, trying the defaults.\n", unit)); + for (i = 0 ; i < sizeof(opl_defaultiobase) / sizeof(*opl_defaultiobase) ; i++) { + scp->io_rid = 0; + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, opl_defaultiobase[i], opl_defaultiobase[i] + 1, 4, RF_ACTIVE); + if (scp->io != NULL) { + if (opl_probe1(scp)) + opl_releaseres(scp, dev); + else + break; + } + } + if (scp->io == NULL) + return (ENXIO); + } else if(opl_probe1(scp)) { + opl_releaseres(scp, dev); + return (ENXIO); + } + + /* We now have some kind of OPL. */ + + DEB(printf("opl%d: probed.\n", unit)); + + return (0); +} + +/* We do probe in this function. */ +static int +opl_probe1(sc_p scp) +{ + u_char stat1, stat2; + + /* Reset the timers and the interrupt. */ + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, IRQ_RESET); + + /* Read the status. */ + stat1 = opl_status(scp); + if ((stat1 & 0xe0) != 0) + return (1); + + /* Try firing the timer1. */ + opl_command(scp, USE_LEFT, TIMER1_REGISTER, 0xff); /* Set the timer value. */ + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, TIMER1_START | TIMER2_MASK); /* Start the timer. */ + DELAY(150); /* Wait for the timer. */ + + /* Read the status. */ + stat2 = opl_status(scp); + + /* Reset the timers and the interrupt. */ + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, TIMER1_MASK | TIMER2_MASK); + opl_command(scp, USE_LEFT, TIMER_CONTROL_REGISTER, IRQ_RESET); + + if ((stat2 & 0xe0) != 0xc0) + return (1); + + return (0); +} + +static int +oplsbc_probe(device_t dev) +{ + char *s; + sc_p scp; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_SYNTH) + return (ENXIO); + + s = "SB OPL FM Synthesizer"; + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->io_rid = 2; + device_set_desc(dev, s); + return (0); +} + +static int +opl_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + int unit, i, opl4_io, opl4_id; + struct resource *opl4; + u_char signature, tmp; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + DEB(printf("opl%d: attaching.\n", unit)); + + /* Fill the softc for this unit. */ + scp->dev = dev; + scp->devinfo = devinfo = &midi_info[unit]; + + /* Allocate other resources. */ + if (opl_allocres(scp, dev)) { + opl_releaseres(scp, dev); + return (ENXIO); + } + + /* Detect the OPL type. */ + signature = opl_status(scp); + if (signature == 0x06) + scp->model = MODEL_OPL2; + else { + /* OPL3 or later, might be OPL4. */ + + /* Enable OPL3 and OPL4. */ + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, 0); + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, OPL3_ENABLE | OPL4_ENABLE); + + tmp = opl_status(scp); + if (tmp != 0x02) + scp->model = MODEL_OPL3; +#if notdef + else { +#endif /* notdef */ + /* Alloc OPL4 ID register. */ + opl4_id = 2; + opl4_io = rman_get_start(scp->io) - 8; + opl4 = bus_alloc_resource(dev, SYS_RES_IOPORT, &opl4_id, opl4_io, opl4_io + 1, 2, RF_ACTIVE); + if (opl4 != NULL) { + /* Select OPL4 ID register. */ + bus_space_write_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 0, 0x02); + DELAY(10); + tmp = bus_space_read_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 1); + DELAY(10); + + if (tmp != 0x20) + scp->model = MODEL_OPL3; + else { + scp->model = MODEL_OPL4; + + /* Select back OPL4 FM mixer control. */ + bus_space_write_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 0, 0xf8); + DELAY(10); + bus_space_write_1(rman_get_bustag(opl4), rman_get_bushandle(opl4), 1, 0x1b); + DELAY(10); + } + bus_release_resource(dev, SYS_RES_IOPORT, opl4_id, opl4); + } +#if notdef + } +#endif /* notdef */ + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, 0); + } + + /* Kill any previous notes. */ + for (i = 0 ; i < 9 ; i++) + opl_command(scp, USE_RIGHT, KEYON_BLOCK + i, 0); + + /* Select melodic mode. */ + opl_command(scp, USE_LEFT, TEST_REGISTER, ENABLE_WAVE_SELECT); + opl_command(scp, USE_LEFT, PERCUSSION_REGISTER, 0); + + for (i = 0 ; i < SBFM_MAXINSTR ; i++) + scp->i_map[i].channel = -1; + + /* Fill the softc. */ + bcopy(&opl_synthinfo, &scp->synthinfo, sizeof(opl_synthinfo)); + snprintf(scp->synthinfo.name, 64, "Yamaha OPL%d FM", scp->model); + bcopy(pv_map, scp->pv_map, sizeof(pv_map)); + if (scp->model < MODEL_OPL3) { /* OPL2. */ + scp->synthinfo.nr_voices = 9; + scp->synthinfo.nr_drums = 0; + for (i = 0 ; i < MAX_VOICE ; i++) + scp->pv_map[i].ch = USE_LEFT; + } else { /* OPL3 or later. */ + scp->synthinfo.capabilities |= SYNTH_CAP_OPL3; + scp->synthinfo.nr_voices = 18; + scp->synthinfo.nr_drums = 0; +#if notdef + for (i = 0 ; i < MAX_VOICE ; i++) { + if (scp->pv_map[i].ch == USE_LEFT) + scp->pv_map[i].ch = USE_LEFT; + else + scp->pv_map[i].ch = USE_RIGHT; + } +#endif /* notdef */ + opl_command(scp, USE_RIGHT, OPL3_MODE_REGISTER, OPL3_ENABLE); + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, 0); + } + + /* Fill the midi info. */ + bcopy(&opl_op_desc, devinfo, sizeof(opl_op_desc)); + midiinit(devinfo, dev); + devinfo->flags = 0; + bcopy(&oplsynth_op_desc, &devinfo->synth, sizeof(oplsynth_op_desc)); + devinfo->synth.readraw = opl_readraw; + devinfo->synth.writeraw = opl_writeraw; + devinfo->synth.alloc.max_voice = scp->synthinfo.nr_voices; + strcpy(devinfo->name, scp->synthinfo.name); + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x", (u_int)rman_get_start(scp->io)); + + /* Init the queue. */ + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = 1; + midibuf_init(&devinfo->midi_dbuf_in); + midibuf_init(&devinfo->midi_dbuf_out); + + /* Increase the number of the synthesizers. */ + nsynth++; + + DEB(printf("opl%d: attached.\n", unit)); + DEB(printf("opl%d: the chip is OPL%d.\n", unit, scp->model)); + + return (0); +} + +static int +oplsbc_attach(device_t dev) +{ + return (opl_attach(dev)); +} + +static int +opl_open(dev_t i_dev, int flags, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit, i; + + unit = MIDIUNIT(i_dev); + + DEB(printf("opl%d: opening.\n", unit)); + + if (unit >= nmidi + nsynth) { + DEB(printf("opl_open: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("opl_open: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + if (scp->model < MODEL_OPL3) + devinfo->synth.alloc.max_voice = 9; + else + devinfo->synth.alloc.max_voice = 18; + devinfo->synth.alloc.timestamp = 0; + for (i = 0 ; i < MAX_VOICE ; i++) { + devinfo->synth.alloc.map[i] = 0; + devinfo->synth.alloc.alloc_times[i] = 0; + } + scp->cmask = 0; /* We are in 2 OP mode initially. */ + if (scp->model >= MODEL_OPL3) + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, scp->cmask); + + DEB(printf("opl%d: opened.\n", unit)); + + return (0); +} + +static int +opl_close(dev_t i_dev, int flags, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + + unit = MIDIUNIT(i_dev); + + DEB(printf("opl%d: closing.\n", unit)); + + if (unit >= nmidi + nsynth) { + DEB(printf("opl_close: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("opl_close: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + if (scp->model < MODEL_OPL3) + devinfo->synth.alloc.max_voice = 9; + else + devinfo->synth.alloc.max_voice = 18; + + /* Stop the OPL. */ + opl_reset(scp->devinfo); + + DEB(printf("opl%d: closed.\n", unit)); + + return (0); +} + +static int +opl_read(dev_t i_dev, struct uio *buf, int flag) +{ + sc_p scp; + mididev_info *devinfo; + int unit, ret; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("opl_read: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("opl_read: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + if ((devinfo->fflags & FREAD) == 0) { + DEB(printf("opl_read: unit %d is not for reading.\n", unit)); + return (EIO); + } + + /* Drain the data. */ + midibuf_init(&devinfo->midi_dbuf_in); + ret = 0; + + return (ret); +} + +static int +opl_write(dev_t i_dev, struct uio *buf, int flag) +{ + sc_p scp; + mididev_info *devinfo; + int unit, ret; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("opl_write: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("opl_write: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + if ((devinfo->fflags & FWRITE) == 0) { + DEB(printf("opl_write: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* Drain the data. */ + midibuf_init(&devinfo->midi_dbuf_out); + midibuf_init(&devinfo->midi_dbuf_passthru); + ret = 0; + + return (ret); +} + +static int +opl_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + struct sbi_instrument ins; + + unit = MIDIUNIT(i_dev); + + DEB(printf("opl%d: ioctlling, cmd 0x%x.\n", unit, (int)cmd)); + + if (unit >= nmidi + nsynth) { + DEB(printf("opl_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("opl_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device >= nmidi + nsynth || synthinfo->device != unit) + return (ENXIO); + bcopy(&scp->synthinfo, synthinfo, sizeof(scp->synthinfo)); + synthinfo->device = unit; + if (devinfo->synth.alloc.max_voice == 12) + synthinfo->nr_voices = 6; + else + synthinfo->nr_voices = devinfo->synth.alloc.max_voice; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device >= nmidi + nsynth || midiinfo->device != unit) + return (ENXIO); + bcopy(&opl_midiinfo, midiinfo, sizeof(opl_midiinfo)); + strcpy(midiinfo->name, scp->synthinfo.name); + midiinfo->device = unit; + return (0); + break; + case SNDCTL_FM_LOAD_INSTR: + bcopy(arg, &ins, sizeof(ins)); + if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) { + printf("opl_ioctl: Instrument number %d is not valid.\n", ins.channel); + return (EINVAL); + } +#if notyet + pmgr_inform(scp, PM_E_PATCH_LOADED, inc.channel, 0, 0, 0); +#endif /* notyet */ + opl_storeinstr(scp, ins.channel, &ins); + return (0); + break; + case SNDCTL_SYNTH_MEMAVL: + return 0x7fffffff; + break; + case SNDCTL_FM_4OP_ENABLE: + if (scp->model >= MODEL_OPL3) + opl_enter4opmode(scp); + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static int +opl_callback(mididev_info *devinfo, int reason) +{ + int unit; + sc_p scp; + + if (devinfo == NULL) { + DEB(printf("opl_callback: device not configured.\n")); + return (ENXIO); + } + + unit = devinfo->unit; + scp = devinfo->softc; + + DEB(printf("opl%d: callback, reason 0x%x.\n", unit, reason)); + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (devinfo->flags & MIDI_F_READING) == 0) + /* Begin recording. */ + devinfo->flags |= MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (devinfo->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + devinfo->flags |= MIDI_F_WRITING; + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (devinfo->flags & MIDI_F_READING) != 0) + /* Stop recording. */ + devinfo->flags &= ~MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (devinfo->flags & MIDI_F_WRITING) != 0) + /* Stop Playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + break; + } + + return (0); +} + +static int +opl_readraw(mididev_info *md, u_char *buf, int len, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + unit = md->unit; + scp = md->softc; + if ((md->fflags & FREAD) == 0) { + DEB(printf("opl_readraw: unit %d is not for reading.\n", unit)); + return (EIO); + } + + /* NOP. */ + return (0); +} + +static int +opl_writeraw(mididev_info *md, u_char *buf, int len, int nonblock) +{ + sc_p scp; + int unit; + + if (md == NULL) + return (ENXIO); + unit = md->unit; + scp = md->softc; + if ((md->fflags & FWRITE) == 0) { + DEB(printf("opl_writeraw: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* NOP. */ + return (0); +} + +/* The functions below here are the synthesizer interfaces. */ + +static int +opl_killnote(mididev_info *md, int voice, int note, int vel) +{ + int unit; + sc_p scp; + struct physical_voice_info *map; + + scp = md->softc; + unit = md->unit; + + DEB(printf("opl%d: killing a note, voice %d, note %d, vel %d.\n", unit, voice, note, vel)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + md->synth.alloc.map[voice] = 0; + map = &scp->pv_map[scp->lv_map[voice]]; + + if (map->voice_mode == VOICE_NONE) + return (0); + + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, scp->voc[voice].keyon_byte & ~0x20); + + scp->voc[voice].keyon_byte = 0; + scp->voc[voice].bender = 0; + scp->voc[voice].volume = 64; + scp->voc[voice].bender_range = 200; + scp->voc[voice].orig_freq = 0; + scp->voc[voice].current_freq = 0; + scp->voc[voice].mode = 0; + + return (0); +} + +static int +opl_setinstr(mididev_info *md, int voice, int instr_no) +{ + int unit; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + DEB(printf("opl%d: setting an instrument, voice %d, instr_no %d.\n", unit, voice, instr_no)); + + + if (voice < 0 || voice >= md->synth.alloc.max_voice || instr_no < 0 || instr_no >= SBFM_MAXINSTR) + return (0); + + scp->act_i[voice] = &scp->i_map[instr_no]; + + return (0); +} + +static int +opl_startnote(mididev_info *md, int voice, int note, int volume) +{ + u_char fpc; + int unit, block, fnum, freq, voice_mode, voice_shift; + struct sbi_instrument *instr; + struct physical_voice_info *map; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + DEB(printf("opl%d: starting a note, voice %d, note %d, volume %d.\n", unit, voice, note, volume)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + map = &scp->pv_map[scp->lv_map[voice]]; + if (map->voice_mode == VOICE_NONE) + return (0); + + if (note == 255) { + /* Change the volume. */ + opl_setvoicevolume(scp, voice, volume, scp->voc[voice].volume); + return (0); + } + + /* Kill the previous note. */ + opl_command(scp, map->ch, KSL_LEVEL + map->op[1], 0xff); /* Carrier volume */ + opl_command(scp, map->ch, KSL_LEVEL + map->op[0], 0xff); /* Modulator volume */ + if (map->voice_mode == VOICE_4OP) { + opl_command(scp, map->ch, KSL_LEVEL + map->op[3], 0xff); /* Carrier volume */ + opl_command(scp, map->ch, KSL_LEVEL + map->op[2], 0xff); /* Modulator volume */ + } + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, 0); /* Note off. */ + + instr = scp->act_i[voice]; + if (instr == NULL) + instr = &scp->i_map[0]; + if (instr->channel < 0) { + printf("opl_startnote: the instrument for voice %d is undefined.\n", voice); + return (0); + } + if (map->voice_mode == VOICE_2OP && instr->key == OPL3_PATCH) { + printf("opl_startnote: the voice mode %d mismatches the key 0x%x.\n", map->voice_mode, instr->key); + return (0); + } + + voice_mode = map->voice_mode; + if (voice_mode == VOICE_4OP) { + if (map->ch == USE_LEFT) + voice_shift = 0; + else + voice_shift = 3; + voice_shift += map->voice_num; + if (instr->key != OPL3_PATCH) { + voice_mode = VOICE_2OP; + scp->cmask &= ~(1 << voice_shift); + } else + scp->cmask |= 1 << voice_shift; + + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, scp->cmask); + } + + /* Set the sound characteristics, attack, decay, sustain, release, wave select, feedback, connection. */ + opl_command(scp, map->ch, AM_VIB + map->op[0], instr->operators[0]); /* Sound characteristics. */ + opl_command(scp, map->ch, AM_VIB + map->op[1], instr->operators[1]); + opl_command(scp, map->ch, ATTACK_DECAY + map->op[0], instr->operators[4]); /* Attack and decay. */ + opl_command(scp, map->ch, ATTACK_DECAY + map->op[1], instr->operators[5]); + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[0], instr->operators[6]); /* Sustain and release. */ + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[1], instr->operators[7]); + opl_command(scp, map->ch, WAVE_SELECT + map->op[0], instr->operators[8]); /* Wave select. */ + opl_command(scp, map->ch, WAVE_SELECT + map->op[1], instr->operators[9]); + fpc = instr->operators[10]; + if ((fpc & 0x30) == 0) + fpc |= 0x30; /* So that at least one channel is enabled. */ + opl_command(scp, map->ch, FEEDBACK_CONNECTION + map->voice_num, fpc); /* Feedback and connection. */ + + if (voice_mode == VOICE_4OP) { + /* Do not forget the operators 3 and 4. */ + opl_command(scp, map->ch, AM_VIB + map->op[2], instr->operators[OFFS_4OP + 0]); /* Sound characteristics. */ + opl_command(scp, map->ch, AM_VIB + map->op[3], instr->operators[OFFS_4OP + 1]); + opl_command(scp, map->ch, ATTACK_DECAY + map->op[2], instr->operators[OFFS_4OP + 4]); /* Attack and decay. */ + opl_command(scp, map->ch, ATTACK_DECAY + map->op[3], instr->operators[OFFS_4OP + 5]); + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[2], instr->operators[OFFS_4OP + 6]); /* Sustain and release. */ + opl_command(scp, map->ch, SUSTAIN_RELEASE + map->op[3], instr->operators[OFFS_4OP + 7]); + opl_command(scp, map->ch, WAVE_SELECT + map->op[2], instr->operators[OFFS_4OP + 8]); /* Wave select. */ + opl_command(scp, map->ch, WAVE_SELECT + map->op[3], instr->operators[OFFS_4OP + 9]); + fpc = instr->operators[OFFS_4OP + 10]; + if ((fpc & 0x30) == 0) + fpc |= 0x30; /* So that at least one channel is enabled. */ + opl_command(scp, map->ch, FEEDBACK_CONNECTION + map->voice_num + 3, fpc); /* Feedback and connection. */ + } + scp->voc[voice].mode = voice_mode; + + opl_setvoicevolume(scp, voice, volume, scp->voc[voice].volume); + + /* Calcurate the frequency. */ + scp->voc[voice].orig_freq = opl_notetofreq(note) / 1000; + /* Tune for the pitch bend. */ + freq = scp->voc[voice].current_freq = opl_computefinetune(scp->voc[voice].orig_freq, scp->voc[voice].bender, scp->voc[voice].bender_range); + opl_freqtofnum(freq, &block, &fnum); + + /* Now we can play the note. */ + opl_command(scp, map->ch, FNUM_LOW + map->voice_num, fnum & 0xff); + scp->voc[voice].keyon_byte = 0x20 | ((block & 0x07) << 2) | ((fnum >> 8) & 0x03); + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, scp->voc[voice].keyon_byte); + if (voice_mode == VOICE_4OP) + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num + 3, scp->voc[voice].keyon_byte); + + return (0); +} + +static int +opl_reset(mididev_info *md) +{ + int unit, i; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + DEB(printf("opl%d: resetting.\n", unit)); + + for (i = 0 ; i < MAX_VOICE ; i++) + scp->lv_map[i] = i; + + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) { + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[0], 0xff); + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[1], 0xff); + if (scp->pv_map[scp->lv_map[i]].voice_mode == VOICE_4OP) { + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[2], 0xff); + opl_command(scp, scp->pv_map[scp->lv_map[i]].ch, KSL_LEVEL + scp->pv_map[scp->lv_map[i]].op[3], 0xff); + } + opl_killnote(md, i, 0, 64); + } + if (scp->model >= MODEL_OPL3) { + md->synth.alloc.max_voice = 18; + for (i = 0 ; i < MAX_VOICE ; i++) + scp->pv_map[i].voice_mode = VOICE_2OP; + } + + return (0); +} + +static int +opl_hwcontrol(mididev_info *md, u_char *event) +{ + /* NOP. */ + return (0); +} + +static int +opl_loadpatch(mididev_info *md, int format, struct uio *buf, int offs, int count, int pmgr_flag) +{ + int unit; + struct sbi_instrument ins; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + if (count < sizeof(ins)) { + printf("opl_loadpatch: The patch record is too short.\n"); + return (EINVAL); + } + if (uiomove(&((char *)&ins)[offs], sizeof(ins) - offs, buf) != 0) + printf("opl_loadpatch: User memory mangled?\n"); + if (ins.channel < 0 || ins.channel >= SBFM_MAXINSTR) { + printf("opl_loadpatch: Instrument number %d is not valid.\n", ins.channel); + return (EINVAL); + } + ins.key = format; + + opl_storeinstr(scp, ins.channel, &ins); + return (0); +} + +static int +opl_panning(mididev_info *md, int chn, int pan) +{ + /* NOP. */ + return (0); +} + +#define SET_VIBRATO(cell) { \ + int tmp; \ + tmp = instr->operators[(cell-1)+(((cell-1)/2)*OFFS_4OP)]; \ + if (press > 110) \ + tmp |= 0x40; /* Vibrato on */ \ + opl_command(scp, map->ch, AM_VIB + map->op[cell-1], tmp);} + +static int +opl_aftertouch(mididev_info *md, int voice, int press) +{ + int unit, connection; + struct sbi_instrument *instr; + struct physical_voice_info *map; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + DEB(printf("opl%d: setting the aftertouch, voice %d, press %d.\n", unit, voice, press)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + map = &scp->pv_map[scp->lv_map[voice]]; + if (map->voice_mode == VOICE_NONE) + return (0); + + /* Adjust the vibrato. */ + instr = scp->act_i[voice]; + if (instr == NULL) + instr = &scp->i_map[0]; + + if (scp->voc[voice].mode == VOICE_4OP) { + 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; + } + } else { + SET_VIBRATO(1); + if ((instr->operators[10] & 0x01)) + SET_VIBRATO(2); + } + + return (0); +} + +static int +opl_bendpitch(sc_p scp, int voice, int value) +{ + int unit, block, fnum, freq; + struct physical_voice_info *map; + mididev_info *md; + + md = scp->devinfo; + unit = md->unit; + + DEB(printf("opl%d: setting the pitch bend, voice %d, value %d.\n", unit, voice, value)); + + map = &scp->pv_map[scp->lv_map[voice]]; + if (map->voice_mode == 0) + return (0); + scp->voc[voice].bender = value; + if (value == 0) + return (0); + if ((scp->voc[voice].keyon_byte & 0x20) == 0) + return (0); + + freq = opl_computefinetune(scp->voc[voice].orig_freq, scp->voc[voice].bender, scp->voc[voice].bender_range); + scp->voc[voice].current_freq = freq; + + opl_freqtofnum(freq, &block, &fnum); + + opl_command(scp, map->ch, FNUM_LOW + map->voice_num, fnum & 0xff); + scp->voc[voice].keyon_byte = 0x20 | ((block & 0x07) << 2) | ((fnum >> 8) & 0x03); + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num, scp->voc[voice].keyon_byte); + if (map->voice_mode == VOICE_4OP) + opl_command(scp, map->ch, KEYON_BLOCK + map->voice_num + 3, scp->voc[voice].keyon_byte); + + return (0); +} + +static int +opl_controller(mididev_info *md, int voice, int ctrlnum, int val) +{ + int unit; + sc_p scp; + + scp = md->softc; + unit = md->unit; + + DEB(printf("opl%d: setting the controller, voice %d, ctrlnum %d, val %d.\n", unit, voice, ctrlnum, val)); + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + switch (ctrlnum) { + case CTRL_PITCH_BENDER: + opl_bendpitch(scp, voice, val); + break; + case CTRL_PITCH_BENDER_RANGE: + scp->voc[voice].bender_range = val; + break; + case CTRL_MAIN_VOLUME: + scp->voc[voice].volume = val / 128; + break; + } + + return (0); +} + +static int +opl_patchmgr(mididev_info *md, struct patmgr_info *rec) +{ + return (EINVAL); +} + +static int +opl_bender(mididev_info *md, int voice, int val) +{ + sc_p scp; + + scp = md->softc; + + if (voice < 0 || voice >= md->synth.alloc.max_voice) + return (0); + + return opl_bendpitch(scp, voice, val - 8192); +} + +static int +opl_allocvoice(mididev_info *md, int chn, int note, struct voice_alloc_info *alloc) +{ + int i, p, best, first, avail, best_time, is4op, instr_no; + struct sbi_instrument *instr; + sc_p scp; + + scp = md->softc; + + DEB(printf("opl%d: allocating a voice, chn %d, note %d.\n", unit, chn, note)); + + best_time = 0x7fffffff; + + if (chn < 0 || chn >= 15) + instr_no = 0; + else + instr_no = md->synth.chn_info[chn].pgm_num; + + instr = &scp->i_map[instr_no]; + if (instr->channel < 0 || md->synth.alloc.max_voice != 12) + is4op = 0; + else if (md->synth.alloc.max_voice == 12) { + if (instr->key == OPL3_PATCH) + is4op = 1; + else + is4op = 0; + } else + is4op = 0; + + if (is4op) { + first = p = 0; + avail = 6; + } else { + if (md->synth.alloc.max_voice == 12) + first = p = 6; + else + first = p = 0; + avail = md->synth.alloc.max_voice; + } + + /* Look up a free voice. */ + best = first; + + for (i = 0 ; i < avail ; i++) { + if (alloc->map[p] == 0) + return (p); + } + if (alloc->alloc_times[p] < best_time) { + best_time = alloc->alloc_times[p]; + best = p; + } + p = (p + 1) % avail; + + if (best < 0) + best = 0; + else if (best > md->synth.alloc.max_voice) + best -= md->synth.alloc.max_voice; + + return best; +} + +static int +opl_setupvoice(mididev_info *md, int voice, int chn) +{ + struct channel_info *info; + sc_p scp; + + scp = md->softc; + + DEB(printf("opl%d: setting up a voice, voice %d, chn %d.\n", unit, voice, chn)); + + info = &md->synth.chn_info[chn]; + + opl_setinstr(md, voice, info->pgm_num); + scp->voc[voice].bender = info->bender_value; + scp->voc[voice].volume = info->controllers[CTL_MAIN_VOLUME]; + + return (0); +} + +static int +opl_sendsysex(mididev_info *md, u_char *sysex, int len) +{ + /* NOP. */ + return (0); +} + +static int +opl_prefixcmd(mididev_info *md, int status) +{ + /* NOP. */ + return (0); +} + +static int +opl_volumemethod(mididev_info *md, int mode) +{ + /* NOP. */ + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + * Call this at >=splmidi. + */ +static void +opl_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + /* Begin playing. */ + devinfo->callback(devinfo, MIDI_CB_START | MIDI_CB_WR); +} + +/* Writes a command to the OPL chip. */ +static void +opl_command(sc_p scp, int ch, int addr, u_int val) +{ + int model; + + DEB(printf("opl%d: sending a command, iobase 0x%x, addr 0x%x, val 0x%x.\n", unit, iobase, addr, val)); + + model = scp->model; + + /* Write the addr first. */ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2, (u_char)(addr & 0xff)); + if (model < MODEL_OPL3) + DELAY(10); + else { + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + } + + /* Next write the value. */ + bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2 + 1, (u_char)(val & 0xff)); + if (model < MODEL_OPL3) + DELAY(30); + else { + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), ch * 2); + } +} + +/* Reads the status of the OPL chip. */ +static int +opl_status(sc_p scp) +{ + DEB(printf("opl%d: reading the status.\n", unit)); + + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), 0); +} + +static void +opl_enter4opmode(sc_p scp) +{ + int i; + mididev_info *devinfo; + static int v4op[MAX_VOICE] = { + 0, 1, 2, 9, 10, 11, 6, 7, 8, 15, 16, 17, + }; + + devinfo = scp->devinfo; + + DEB(printf("opl%d: entering 4 OP mode.\n", unit)); + + /* Connect all possible 4 OP voice operators. */ + scp->cmask = 0x3f; + opl_command(scp, USE_RIGHT, CONNECTION_SELECT_REGISTER, scp->cmask); + + for (i = 0 ; i < 3 ; i++) + scp->pv_map[i].voice_mode = VOICE_4OP; + for (i = 3 ; i < 6 ; i++) + scp->pv_map[i].voice_mode = VOICE_NONE; + for (i = 9 ; i < 12 ; i++) + scp->pv_map[i].voice_mode = VOICE_4OP; + for (i = 12 ; i < 15 ; i++) + scp->pv_map[i].voice_mode = VOICE_NONE; + + for (i = 0 ; i < 12 ; i++) + scp->lv_map[i] = v4op[i]; + devinfo->synth.alloc.max_voice = 12; +} + +static void +opl_storeinstr(sc_p scp, int instr_no, struct sbi_instrument *instr) +{ + if (instr->key != FM_PATCH && (instr->key != OPL3_PATCH || scp->model < MODEL_OPL3)) + printf("opl_storeinstr: The patch format field 0x%x is not valid.\n", instr->key); + + bcopy(instr, &scp->i_map[instr_no], sizeof(*instr)); +} + +static void +opl_calcvol(u_char *regbyte, int volume, int main_vol) +{ + int level; + + level = (~*regbyte & 0x3f); + + if (main_vol > 127) + main_vol = 127; + + volume = (volume * main_vol) / 127; + + if (level > 0) + level += opl_volumetable[volume]; + + RANGE(level, 0, 0x3f); + + *regbyte = (*regbyte & 0xc0) | (~level & 0x3f); +} + +static void +opl_setvoicevolume(sc_p scp, int voice, int volume, int main_vol) +{ + u_char vol1, vol2, vol3, vol4; + int connection; + struct sbi_instrument *instr; + struct physical_voice_info *map; + mididev_info *devinfo; + + devinfo = scp->devinfo; + + if (voice < 0 || voice >= devinfo->synth.alloc.max_voice) + return; + + map = &scp->pv_map[scp->lv_map[voice]]; + instr = scp->act_i[voice]; + if (instr == NULL) + instr = &scp->i_map[0]; + + if (instr->channel < 0) + return; + if (scp->voc[voice].mode == VOICE_NONE) + return; + if (scp->voc[voice].mode == VOICE_2OP) { /* 2 OP mode. */ + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + if ((instr->operators[10] & 0x01)) + opl_calcvol(&vol1, volume, main_vol); + opl_calcvol(&vol2, volume, main_vol); + opl_command(scp, map->ch, KSL_LEVEL + map->op[0], vol1); + opl_command(scp, map->ch, KSL_LEVEL + map->op[1], vol2); + } else { /* 4 OP mode. */ + vol1 = instr->operators[2]; + vol2 = instr->operators[3]; + vol3 = instr->operators[OFFS_4OP + 2]; + vol4 = instr->operators[OFFS_4OP + 3]; + connection = ((instr->operators[10] & 0x01) << 1) | (instr->operators[10 + OFFS_4OP] & 0x01); + switch(connection) { + case 0: + opl_calcvol(&vol4, volume, main_vol); + break; + case 1: + opl_calcvol(&vol2, volume, main_vol); + opl_calcvol(&vol4, volume, main_vol); + break; + case 2: + opl_calcvol(&vol1, volume, main_vol); + opl_calcvol(&vol4, volume, main_vol); + break; + case 3: + opl_calcvol(&vol1, volume, main_vol); + opl_calcvol(&vol3, volume, main_vol); + opl_calcvol(&vol4, volume, main_vol); + break; + } + opl_command(scp, map->ch, KSL_LEVEL + map->op[0], vol1); + opl_command(scp, map->ch, KSL_LEVEL + map->op[1], vol2); + opl_command(scp, map->ch, KSL_LEVEL + map->op[2], vol3); + opl_command(scp, map->ch, KSL_LEVEL + map->op[3], vol4); + } +} + +static void +opl_freqtofnum(int freq, int *block, int *fnum) +{ + int f, octave; + + 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 int notes[] = +{ + 261632, + 277189, + 293671, + 311132, + 329632, + 349232, + 369998, + 391998, + 415306, + 440000, + 466162, + 493880 +}; + +#define BASE_OCTAVE 5 + +static int +opl_notetofreq(int note_num) +{ + int note, octave, note_freq; + + 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); + + return (note_freq); +} + +static u_long +opl_computefinetune(u_long base_freq, int bend, int range) +{ + u_long amount; + int negative, semitones, cents, multiplier; + + if (bend == 0 || range == 0 || base_freq == 0) + return (base_freq); + + multiplier = 1; + + if (range > 8192) + range = 8192; + + bend = bend * range / 8192; + if (bend == 0) + return (base_freq); + + if (bend < 0) { + negative = 1; + bend = -bend; + } + else + negative = 0; + if (bend > range) + bend = range; + + while (bend > 2399) { + multiplier *= 4; + bend -= 2400; + } + + semitones = bend / 100; + cents = bend % 100; + + amount = (u_long)(semitone_tuning[semitones] * multiplier * cent_tuning[cents]) / 10000; + + if (negative) + return (base_freq * 10000) / amount; + else + return (base_freq * amount) / 10000; +} + +/* Allocates resources other than IO ports. */ +static int +opl_allocres(sc_p scp, device_t dev) +{ + if (scp->io == NULL) { + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 4, RF_ACTIVE); + if (scp->io == NULL) + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +opl_releaseres(sc_p scp, device_t dev) +{ + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } +} + +static device_method_t opl_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , opl_probe ), + DEVMETHOD(device_attach, opl_attach), + + { 0, 0 }, +}; + +static driver_t opl_driver = { + "midi", + opl_methods, + sizeof(struct opl_softc), +}; + +DRIVER_MODULE(opl, isa, opl_driver, midi_devclass, 0, 0); + +static device_method_t oplsbc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , oplsbc_probe ), + DEVMETHOD(device_attach, oplsbc_attach), + + { 0, 0 }, +}; + +static driver_t oplsbc_driver = { + "midi", + oplsbc_methods, + sizeof(struct opl_softc), +}; + +DRIVER_MODULE(oplsbc, sbc, oplsbc_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/isa/sbc.c b/sys/dev/sound/isa/sbc.c index efec890..0c2933b 100644 --- a/sys/dev/sound/isa/sbc.c +++ b/sys/dev/sound/isa/sbc.c @@ -400,7 +400,6 @@ sbc_attach(device_t dev) child = device_add_child(dev, "pcm", -1); device_set_ivars(child, func); -#if notyet /* Midi Interface */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); if (func == NULL) goto bad; @@ -416,7 +415,6 @@ sbc_attach(device_t dev) func->func = SCF_SYNTH; child = device_add_child(dev, "midi", -1); device_set_ivars(child, func); -#endif /* notyet */ /* probe/attach kids */ bus_generic_attach(dev); diff --git a/sys/dev/sound/isa/uartsio.c b/sys/dev/sound/isa/uartsio.c new file mode 100644 index 0000000..9b43331 --- /dev/null +++ b/sys/dev/sound/isa/uartsio.c @@ -0,0 +1,524 @@ +/* + * Copyright by George Hansper 1996 + * + * Tue Jan 23 22:32:10 EST 1996 ghansper@daemon.apana.org.au + * added 16450/16550 support for standard serial-port UARTs + * + * 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 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. + * + * + * Wed Apl 1 02:25:30 JST 1998 zinnia@jan.ne.jp + * ported to FreeBSD 2.2.5R-RELEASE + * + * Fri Apl 1 21:16:20 JST 1999 zinnia@jan.ne.jp + * ported to FreeBSD 3.1-STABLE + * + * + * Ported to the new Audio Driver by Luigi Rizzo: + * (C) 1999 Seigo Tanimura + * + * This is the 16550 midi uart driver for FreeBSD, based on the Luigi Sound Driver. + * This handles io against /dev/midi, the midi {in, out}put event queues + * and the event/message transmittion to/from a serial port interface. + * + * $FreeBSD$ + * + */ + +#include "opt_devfs.h" + +#include <isa/sioreg.h> +#include <isa/ic/ns16550.h> +#include <dev/sound/midi/midi.h> + +/* XXX What about a PCI uart? */ +#include <isa/isavar.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +#define TX_FIFO_SIZE 16 + +extern synthdev_info midisynth_op_desc; + +/* These are the synthesizer and the midi interface information. */ +static struct synth_info uartsio_synthinfo = { + "uart16550A MIDI", + 0, + SYNTH_TYPE_MIDI, + 0, + 0, + 128, + 128, + 128, + SYNTH_CAP_INPUT, +}; + +static struct midi_info uartsio_midiinfo = { + "uart16550A MIDI", + 0, + 0, + 0, +}; + +/* + * These functions goes into uartsio_op_desc to get called + * from sound.c. + */ + +static int uartsio_probe(device_t dev); +static int uartsio_attach(device_t dev); + +static d_ioctl_t uartsio_ioctl; +static driver_intr_t uartsio_intr; +static midi_callback_t uartsio_callback; + +/* Here is the parameter structure per a device. */ +struct uartsio_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + + struct resource *io; /* Base of io port */ + int io_rid; /* Io resource ID */ + struct resource *irq; /* Irq */ + int irq_rid; /* Irq resource ID */ + void *ih; /* Interrupt cookie */ + + int fflags; /* File flags */ + + int has_fifo; /* TX/RX fifo in the uart */ + int tx_size; /* Size of TX on a transmission */ + +}; + +typedef struct uartsio_softc *sc_p; + +/* These functions are local. */ +static void uartsio_startplay(sc_p scp); +static int uartsio_xmit(sc_p scp); +static int uartsio_readport(sc_p scp, int off); +static void uartsio_writeport(sc_p scp, int off, u_int8_t value); +static int uartsio_allocres(sc_p scp, device_t dev); +static void uartsio_releaseres(sc_p scp, device_t dev); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info uartsio_op_desc = { + "16550 uart midi", + + SNDCARD_UART16550, + + NULL, + NULL, + NULL, + NULL, + uartsio_ioctl, + NULL, + + uartsio_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + * These are called from snd* functions in sys/i386/isa/snd/sound.c. + */ + +static int +uartsio_probe(device_t dev) +{ + sc_p scp; + int unit; + u_char c; + + if (isa_get_logicalid(dev) != 0) + /* This is NOT a PnP device! */ + return (ENXIO); + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + device_set_desc(dev, uartsio_op_desc.name); + bzero(scp, sizeof(*scp)); + + scp->io_rid = 0; + scp->io = bus_alloc_resource(dev, SYS_RES_IOPORT, &scp->io_rid, 0, ~0, 8, RF_ACTIVE); + if (scp->io == NULL) + return (ENXIO); + + DEB(printf("uartsio%d: probing.\n", unit)); + +/* Read the IER. The upper four bits should all be zero. */ + c = uartsio_readport(scp, com_ier); + if ((c & 0xf0) != 0) { + uartsio_releaseres(scp, dev); + return (ENXIO); + } + +/* Read the MSR. The upper three bits should all be zero. */ + c = uartsio_readport(scp, com_mcr); + if ((c & 0xe0) != 0) { + uartsio_releaseres(scp, dev); + return (ENXIO); + } + + /* XXX Do we need a loopback test? */ + + DEB(printf("uartsio%d: probed.\n", unit)); + + return (0); +} + +static int +uartsio_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + + DEB(printf("uartsio%d: attaching.\n", unit)); + + /* Allocate resources. */ + if (uartsio_allocres(scp, dev)) { + uartsio_releaseres(scp, dev); + return (ENXIO); + } + + /* See the size of the tx fifo. */ + uartsio_writeport(scp, com_fifo, FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST | FIFO_RX_HIGH); + if ((uartsio_readport(scp, com_iir) & IIR_FIFO_MASK) == FIFO_RX_HIGH) { + scp->has_fifo = 1; + scp->tx_size = TX_FIFO_SIZE; + DEB(printf("uartsio%d: uart is 16550A, tx size is %d bytes.\n", unit, scp->tx_size)); + } else { + scp->has_fifo = 0; + scp->tx_size = 1; + DEB(printf("uartsio%d: uart is not 16550A.\n", unit)); + } + + /* Fill the softc. */ + scp->dev = dev; + scp->devinfo = devinfo = &midi_info[unit]; + + /* Fill the midi info. */ + bcopy(&uartsio_op_desc, devinfo, sizeof(uartsio_op_desc)); + midiinit(devinfo, dev); + devinfo->flags = 0; + bcopy(&midisynth_op_desc, &devinfo->synth, sizeof(midisynth_op_desc)); + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at 0x%x irq %d", + (u_int)rman_get_start(scp->io), (int)rman_get_start(scp->irq)); + + /* Init the queue. */ + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = 1; + midibuf_init(&devinfo->midi_dbuf_in); + midibuf_init(&devinfo->midi_dbuf_out); + midibuf_init(&devinfo->midi_dbuf_passthru); + + /* Configure the uart. */ + uartsio_writeport(scp, com_cfcr, CFCR_DLAB); /* Latch the divisor. */ + uartsio_writeport(scp, com_dlbl, 0x03); + uartsio_writeport(scp, com_dlbh, 0x00); /* We want a bitrate of 38.4kbps. */ + uartsio_writeport(scp, com_cfcr, CFCR_8BITS); /* We want 8bits, 1 stop bit, no parity. */ + uartsio_writeport(scp, com_mcr, MCR_IENABLE | MCR_RTS | MCR_DTR); /* Enable interrupt, set RTS and DTR. */ + uartsio_writeport(scp, com_ier, IER_ERXRDY | IER_ETXRDY | IER_EMSC | IER_ERLS); /* Give us an interrupt on RXRDY, TXRDY, MSC and RLS. */ + if (scp->has_fifo) + uartsio_writeport(scp, com_fifo, FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST | FIFO_RX_LOW); /* We use the fifo. */ + else + uartsio_writeport(scp, com_fifo, FIFO_RCV_RST | FIFO_XMT_RST | FIFO_RX_LOW); /* We do not use the fifo. */ + + /* Clear the gabage. */ + uartsio_readport(scp, com_lsr); + uartsio_readport(scp, com_lsr); + uartsio_readport(scp, com_iir); + uartsio_readport(scp, com_data); + + /* Increase the number of midi devices. */ + nmidi++; + + /* Now we can handle the interrupts. */ + bus_setup_intr(dev, scp->irq, INTR_TYPE_TTY, uartsio_intr, scp, &scp->ih); + + DEB(printf("uartsio%d: attached.\n", unit)); + + return (0); +} + +static int +uartsio_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("uartsio_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("uartsio_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device > nmidi + nsynth || synthinfo->device != unit) + return (ENXIO); + bcopy(&uartsio_synthinfo, synthinfo, sizeof(uartsio_synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device > nmidi + nsynth || midiinfo->device != unit) + return (ENXIO); + bcopy(&uartsio_midiinfo, midiinfo, sizeof(uartsio_midiinfo)); + midiinfo->device = unit; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static void +uartsio_intr(void *arg) +{ + sc_p scp; + mididev_info *devinfo; + + scp = (sc_p)arg; + devinfo = scp->devinfo; + + uartsio_xmit(scp); + + /* Invoke the upper layer. */ + midi_intr(devinfo); +} + +static int +uartsio_callback(mididev_info *d, int reason) +{ + int unit; + sc_p scp; + + if (d == NULL) { + DEB(printf("uartsio_callback: device not configured.\n")); + return (ENXIO); + } + + unit = d->unit; + scp = d->softc; + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) == 0) + /* Begin recording. */ + d->flags |= MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + uartsio_startplay(scp); + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) != 0) + /* Stop recording. */ + d->flags &= ~MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) != 0) + /* Stop Playing. */ + d->flags &= ~MIDI_F_WRITING; + break; + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + * Call this at >=splmidi. + */ +static void +uartsio_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + devinfo->flags |= MIDI_F_WRITING; + uartsio_xmit(scp); +} + +static int +uartsio_xmit(sc_p scp) +{ + mididev_info *devinfo; + midi_dbuf *dbuf; + int lsr, msr, iir, i, txsize; + u_char c[TX_FIFO_SIZE]; + + devinfo = scp->devinfo; + + do { + /* Read the received data. */ + while (((lsr = uartsio_readport(scp, com_lsr)) & LSR_RCV_MASK) != 0) { + /* Is this a data or an error/break? */ + if ((lsr & LSR_RXRDY) == 0) + printf("uartsio_xmit: receive error or break in unit %d.\n", devinfo->unit); + else { + /* Receive the data. */ + c[0] = uartsio_readport(scp, com_data); + /* Queue into the passthru buffer and start transmitting if we can. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) != 0 && ((devinfo->flags & MIDI_F_BUSY) == 0 || (devinfo->fflags & FWRITE) == 0)) { + midibuf_input_intr(&devinfo->midi_dbuf_passthru, &c[0], sizeof(c[0])); + devinfo->flags |= MIDI_F_WRITING; + } + /* Queue if we are reading. Discard an active sensing. */ + if ((devinfo->flags & MIDI_F_READING) != 0 && c[0] != 0xfe) + midibuf_input_intr(&devinfo->midi_dbuf_in, &c[0], sizeof(c[0])); + } + } + + /* Read MSR. */ + msr = uartsio_readport(scp, com_msr); + + /* See which source to use. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) == 0 || ((devinfo->flags & MIDI_F_BUSY) != 0 && (devinfo->fflags & FWRITE) != 0)) + dbuf = &devinfo->midi_dbuf_out; + else + dbuf = &devinfo->midi_dbuf_passthru; + + /* Transmit the data in the queue. */ + if ((devinfo->flags & MIDI_F_WRITING) != 0 && (lsr & LSR_TXRDY) != 0 && (msr & MSR_CTS) != 0) { + + /* Do we have the data to transmit? */ + if (dbuf->rl == 0) { + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + } else { + /* send the data. */ + txsize = scp->tx_size; + if (dbuf->rl < txsize) + txsize = dbuf->rl; + midibuf_output_intr(dbuf, c, txsize); + for (i = 0 ; i < txsize ; i++) + uartsio_writeport(scp, com_data, c[i]); + /* We are playing now. */ + devinfo->flags |= MIDI_F_WRITING; + } + } else { + /* Do we have the data to transmit? */ + if (dbuf->rl > 0) + /* Wait for the next interrupt. */ + devinfo->flags |= MIDI_F_WRITING; + } + } while (((iir = uartsio_readport(scp, com_iir)) & IIR_IMASK) != IIR_NOPEND); + + return (0); +} + +/* Reads from a port. */ +static int +uartsio_readport(sc_p scp, int off) +{ + return bus_space_read_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off); +} + +/* Writes to a port. */ +static void +uartsio_writeport(sc_p scp, int off, u_int8_t value) +{ + return bus_space_write_1(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), off, value); +} + +/* Allocates resources other than IO ports. */ +static int +uartsio_allocres(sc_p scp, device_t dev) +{ + if (scp->irq == NULL) { + scp->irq_rid = 0; + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE); + } + if (scp->irq == NULL) + return (1); + + return (0); +} + +/* Releases resources. */ +static void +uartsio_releaseres(sc_p scp, device_t dev) +{ + if (scp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_IOPORT, scp->io_rid, scp->io); + scp->io = NULL; + } +} + +static device_method_t uartsio_methods[] = { +/* Device interface */ + DEVMETHOD(device_probe , uartsio_probe ), + DEVMETHOD(device_attach, uartsio_attach), + + { 0, 0 }, +}; + +static driver_t uartsio_driver = { + "midi", + uartsio_methods, + sizeof(struct uartsio_softc), +}; + +DRIVER_MODULE(uartsio, isa, uartsio_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/midi/midi.c b/sys/dev/sound/midi/midi.c new file mode 100644 index 0000000..83152cf --- /dev/null +++ b/sys/dev/sound/midi/midi.c @@ -0,0 +1,702 @@ +/* + * Main midi driver for FreeBSD. This file provides the main + * entry points for probe/attach and all i/o demultiplexing, including + * default routines for generic devices. + * + * (C) 1999 Seigo Tanimura + * + * 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 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. + * + * + * For each card type a template "mididev_info" structure contains + * all the relevant parameters, both for configuration and runtime. + * + * In this file we build tables of pointers to the descriptors for + * the various supported cards. The generic probe routine scans + * the table(s) looking for a matching entry, then invokes the + * board-specific probe routine. If successful, a pointer to the + * correct mididev_info is stored in mididev_last_probed, for subsequent + * use in the attach routine. The generic attach routine copies + * the template to a permanent descriptor (midi_info[unit] and + * friends), initializes all generic parameters, and calls the + * board-specific attach routine. + * + * On device calls, the generic routines do the checks on unit and + * device parameters, then call the board-specific routines if + * available, or try to perform the task using the default code. + * + * $FreeBSD$ + * + */ + +#include "opt_devfs.h" + +#include <dev/sound/midi/midi.h> + +static devclass_t midi_devclass; + +static d_open_t midiopen; +static d_close_t midiclose; +static d_ioctl_t midiioctl; +static d_read_t midiread; +static d_write_t midiwrite; +static d_poll_t midipoll; + +/* These functions are local. */ +static d_open_t midistat_open; +static d_close_t midistat_close; +static d_read_t midistat_read; +static int midi_initstatus(char *buf, int size); +static int midi_readstatus(char *buf, int *ptr, struct uio *uio); + +#define CDEV_MAJOR MIDI_CDEV_MAJOR +static struct cdevsw midi_cdevsw = { + /* open */ midiopen, + /* close */ midiclose, + /* read */ midiread, + /* write */ midiwrite, + /* ioctl */ midiioctl, + /* poll */ midipoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "midi", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* bmaj */ -1 +}; + +/* + * descriptors for active devices. also used as the public softc + * of a device. + */ +mididev_info midi_info[NMIDI_MAX]; + +u_long nmidi; /* total number of midi devices, filled in by the driver */ +u_long nsynth; /* total number of synthesizers, filled in by the driver */ + +/* These make the buffer for /dev/midistat */ +static int midistatbusy; +static char midistatbuf[4096]; +static int midistatptr; + +/* + * This is the generic init routine + */ +int +midiinit(mididev_info *d, device_t dev) +{ + int unit; + + if (midi_devclass == NULL) { + midi_devclass = device_get_devclass(dev); + make_dev(&midi_cdevsw, MIDIMKMINOR(0, MIDI_DEV_STATUS), + UID_ROOT, GID_WHEEL, 0444, "midistat"); + } + + unit = device_get_unit(dev); + make_dev(&midi_cdevsw, MIDIMKMINOR(unit, MIDI_DEV_MIDIN), + UID_ROOT, GID_WHEEL, 0666, "midi%d", unit); + + /* + * initialize standard parameters for the device. This can be + * overridden by device-specific configurations but better do + * here the generic things. + */ + + d->unit = device_get_unit(dev); + d->softc = device_get_softc(dev); + d->dev = dev; + d->magic = MAGIC(d->unit); /* debugging... */ + + return 0 ; +} + +/* + * a small utility function which, given a device number, returns + * a pointer to the associated mididev_info struct, and sets the unit + * number. + */ +mididev_info * +get_mididev_info(dev_t i_dev, int *unit) +{ + int u; + mididev_info *d = NULL; + + if (MIDIDEV(i_dev) != MIDI_DEV_MIDIN) + return NULL; + u = MIDIUNIT(i_dev); + if (unit) + *unit = u; + + if (u >= nmidi + nsynth) { + DEB(printf("get_mididev_info: unit %d is not configured.\n", u)); + return NULL; + } + d = &midi_info[u]; + + return d; +} + +/* + * here are the switches for the main functions. The switches do + * all necessary checks on the device number to make sure + * that the device is configured. They also provide some default + * functionalities so that device-specific drivers have to deal + * only with special cases. + */ + +static int +midiopen(dev_t i_dev, int flags, int mode, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + return midi_open(i_dev, flags, mode, p); + case MIDI_DEV_STATUS: + return midistat_open(i_dev, flags, mode, p); + } + + return (ENXIO); +} + +static int +midiclose(dev_t i_dev, int flags, int mode, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + return midi_close(i_dev, flags, mode, p); + case MIDI_DEV_STATUS: + return midistat_close(i_dev, flags, mode, p); + } + + return (ENXIO); +} + +static int +midiread(dev_t i_dev, struct uio * buf, int flag) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + return midi_read(i_dev, buf, flag); + case MIDI_DEV_STATUS: + return midistat_read(i_dev, buf, flag); + } + + return (ENXIO); +} + +static int +midiwrite(dev_t i_dev, struct uio * buf, int flag) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + return midi_write(i_dev, buf, flag); + } + + return (ENXIO); +} + +static int +midiioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + return midi_ioctl(i_dev, cmd, arg, mode, p); + } + + return (ENXIO); +} + +static int +midipoll(dev_t i_dev, int events, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_MIDIN: + return midi_poll(i_dev, events, p); + } + + return (ENXIO); +} + +/* + * Followings are the generic methods in midi drivers. + */ + +int +midi_open(dev_t i_dev, int flags, int mode, struct proc * p) +{ + int dev, unit, s, ret; + mididev_info *d; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + DEB(printf("open midi%d subdev %d flags 0x%08x mode 0x%08x\n", + unit, dev & 0xf, flags, mode)); + + if (d == NULL) + return (ENXIO); + + s = splmidi(); + + /* Mark this device busy. */ + device_busy(d->dev); + if ((d->flags & MIDI_F_BUSY) != 0) { + splx(s); + DEB(printf("opl_open: unit %d is busy.\n", unit)); + return (EBUSY); + } + d->flags |= MIDI_F_BUSY; + d->flags &= ~(MIDI_F_READING | MIDI_F_WRITING); + d->fflags = flags; + + /* Init the queue. */ + if ((d->fflags & FREAD) != 0) + midibuf_init(&d->midi_dbuf_in); + if ((d->fflags & FWRITE) != 0) { + midibuf_init(&d->midi_dbuf_out); + midibuf_init(&d->midi_dbuf_passthru); + } + + if (d->open == NULL) + ret = 0; + else + ret = d->open(i_dev, flags, mode, p); + + splx(s); + + return (ret); +} + +int +midi_close(dev_t i_dev, int flags, int mode, struct proc * p) +{ + int dev, unit, s, ret; + mididev_info *d; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + DEB(printf("close midi%d subdev %d\n", unit, dev & 0xf)); + + if (d == NULL) + return (ENXIO); + + s = splmidi(); + + /* Clear the queues. */ + if ((d->fflags & FREAD) != 0) + midibuf_init(&d->midi_dbuf_in); + if ((d->fflags & FWRITE) != 0) { + midibuf_init(&d->midi_dbuf_out); + midibuf_init(&d->midi_dbuf_passthru); + } + + /* Stop playing and unmark this device busy. */ + d->flags &= ~MIDI_F_BUSY; + d->fflags = 0; + + device_unbusy(d->dev); + + if (d->close == NULL) + ret = 0; + else + ret = d->close(i_dev, flags, mode, p); + + splx(s); + + return (ret); +} + +int +midi_read(dev_t i_dev, struct uio * buf, int flag) +{ + int dev, unit, s, len, ret; + mididev_info *d ; + + dev = minor(i_dev); + + d = get_mididev_info(i_dev, &unit); + DEB(printf("read midi%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); + + if (d == NULL) + return (ENXIO); + + ret = 0; + s = splmidi(); + + /* Begin recording. */ + d->callback(d, MIDI_CB_START | MIDI_CB_RD); + + /* Have we got the data to read? */ + if ((d->flags & MIDI_F_NBIO) != 0 && d->midi_dbuf_in.rl == 0) + ret = EAGAIN; + else { + len = buf->uio_resid; + ret = midibuf_uioread(&d->midi_dbuf_in, buf, len); + if (ret < 0) + ret = -ret; + else + ret = 0; + } + + if (ret == 0 && d->read != NULL) + ret = d->read(i_dev, buf, flag); + + splx(s); + + return (ret); +} + +int +midi_write(dev_t i_dev, struct uio * buf, int flag) +{ + int dev, unit, s, len, ret; + mididev_info *d; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + DEB(printf("write midi%d subdev %d flag 0x%08x\n", unit, dev & 0xf, flag)); + + if (d == NULL) + return (ENXIO); + + ret = 0; + s = splmidi(); + + /* Begin playing. */ + d->callback(d, MIDI_CB_START | MIDI_CB_WR); + + /* Have we got the data to write? */ + if ((d->flags & MIDI_F_NBIO) != 0 && d->midi_dbuf_out.fl == 0) + ret = EAGAIN; + else { + len = buf->uio_resid; + if (len > d->midi_dbuf_out.fl && + (d->flags & MIDI_F_NBIO)) + len = d->midi_dbuf_out.fl; + ret = midibuf_uiowrite(&d->midi_dbuf_out, buf, len); + if (ret < 0) + ret = -ret; + else + ret = 0; + } + + /* Begin playing. */ + d->callback(d, MIDI_CB_START | MIDI_CB_WR); + + if (ret == 0 && d->write != NULL) + ret = d->write(i_dev, buf, flag); + + splx(s); + + return (ret); +} + +/* + * generic midi ioctl. Functions of the default driver can be + * overridden by the device-specific ioctl call. + * If a device-specific call returns ENOSYS (Function not implemented), + * the default driver is called. Otherwise, the returned value + * is passed up. + * + * The default handler, for many parameters, sets the value in the + * descriptor, sets MIDI_F_INIT, and calls the callback function with + * reason INIT. If successful, the callback returns 1 and the caller + * can update the parameter. + */ + +int +midi_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc * p) +{ + int ret = ENOSYS, dev, unit; + mididev_info *d; + struct snd_size *sndsize; + u_long s; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + if (d == NULL) + return (ENXIO); + + if (d->ioctl) + ret = d->ioctl(i_dev, cmd, arg, mode, p); + if (ret != ENOSYS) + return ret; + + /* + * pass control to the default ioctl handler. Set ret to 0 now. + */ + ret = 0; + + /* + * all routines are called with int. blocked. Make sure that + * ints are re-enabled when calling slow or blocking functions! + */ + s = splmidi(); + switch(cmd) { + + /* + * we start with the new ioctl interface. + */ + case AIONWRITE: /* how many bytes can write ? */ + *(int *)arg = d->midi_dbuf_out.fl; + break; + + case AIOSSIZE: /* set the current blocksize */ + sndsize = (struct snd_size *)arg; + if (sndsize->play_size <= d->midi_dbuf_out.unit_size && sndsize->rec_size <= d->midi_dbuf_in.unit_size) { + d->flags &= ~MIDI_F_HAS_SIZE; + d->midi_dbuf_out.blocksize = d->midi_dbuf_out.unit_size; + d->midi_dbuf_in.blocksize = d->midi_dbuf_in.unit_size; + } + else { + if (sndsize->play_size > d->midi_dbuf_out.bufsize / 4) + sndsize->play_size = d->midi_dbuf_out.bufsize / 4; + if (sndsize->rec_size > d->midi_dbuf_in.bufsize / 4) + sndsize->rec_size = d->midi_dbuf_in.bufsize / 4; + /* Round up the size to the multiple of EV_SZ. */ + d->midi_dbuf_out.blocksize = + ((sndsize->play_size + d->midi_dbuf_out.unit_size - 1) + / d->midi_dbuf_out.unit_size) * d->midi_dbuf_out.unit_size; + d->midi_dbuf_in.blocksize = + ((sndsize->rec_size + d->midi_dbuf_in.unit_size - 1) + / d->midi_dbuf_in.unit_size) * d->midi_dbuf_in.unit_size; + d->flags |= MIDI_F_HAS_SIZE; + } + /* FALLTHROUGH */ + case AIOGSIZE: /* get the current blocksize */ + sndsize = (struct snd_size *)arg; + sndsize->play_size = d->midi_dbuf_out.blocksize; + sndsize->rec_size = d->midi_dbuf_in.blocksize; + + ret = 0; + break; + + case AIOSTOP: + if (*(int *)arg == AIOSYNC_PLAY) /* play */ + *(int *)arg = d->callback(d, MIDI_CB_STOP | MIDI_CB_WR); + else if (*(int *)arg == AIOSYNC_CAPTURE) + *(int *)arg = d->callback(d, MIDI_CB_STOP | MIDI_CB_RD); + else { + splx(s); + DEB(printf("AIOSTOP: bad channel 0x%x\n", *(int *)arg)); + *(int *)arg = 0 ; + } + break ; + + case AIOSYNC: + DEB(printf("AIOSYNC chan 0x%03lx pos %lu unimplemented\n", + ((snd_sync_parm *)arg)->chan, + ((snd_sync_parm *)arg)->pos)); + break; + /* + * here follow the standard ioctls (filio.h etc.) + */ + case FIONREAD: /* get # bytes to read */ + *(int *)arg = d->midi_dbuf_in.rl; + break; + + case FIOASYNC: /*set/clear async i/o */ + DEB( printf("FIOASYNC\n") ; ) + break; + + case FIONBIO: /* set/clear non-blocking i/o */ + if ( *(int *)arg == 0 ) + d->flags &= ~MIDI_F_NBIO ; + else + d->flags |= MIDI_F_NBIO ; + break ; + + case MIOSPASSTHRU: /* set/clear passthru */ + if ( *(int *)arg == 0 ) + d->flags &= ~MIDI_F_PASSTHRU ; + else + d->flags |= MIDI_F_PASSTHRU ; + + /* Init the queue. */ + midibuf_init(&d->midi_dbuf_passthru); + + /* FALLTHROUGH */ + case MIOGPASSTHRU: /* get passthru */ + if ((d->flags & MIDI_F_PASSTHRU) != 0) + (int *)arg = 1; + else + (int *)arg = 0; + break ; + + default: + DEB(printf("default ioctl midi%d subdev %d fn 0x%08x fail\n", + unit, dev & 0xf, cmd)); + ret = EINVAL; + break ; + } + splx(s); + return ret ; +} + +int +midi_poll(dev_t i_dev, int events, struct proc * p) +{ + int unit, dev, ret, s, lim; + mididev_info *d; + + dev = minor(i_dev); + d = get_mididev_info(i_dev, &unit); + + if (d == NULL) + return (ENXIO); + + if (d->poll) + ret = d->poll(i_dev, events, p); + + ret = 0; + s = splmidi(); + + /* Look up the apropriate queue and select it. */ + if ((events & (POLLOUT | POLLWRNORM)) != 0) { + /* Start playing. */ + d->callback(d, MIDI_CB_START | MIDI_CB_WR); + + /* Find out the boundary. */ + if ((d->flags & MIDI_F_HAS_SIZE) != 0) + lim = d->midi_dbuf_out.blocksize; + else + lim = d->midi_dbuf_out.unit_size; + if (d->midi_dbuf_out.fl < lim) + /* No enough space, record select. */ + selrecord(p, &d->midi_dbuf_out.sel); + else + /* We can write now. */ + ret |= events & (POLLOUT | POLLWRNORM); + } + if ((events & (POLLIN | POLLRDNORM)) != 0) { + /* Start recording. */ + d->callback(d, MIDI_CB_START | MIDI_CB_RD); + + /* Find out the boundary. */ + if ((d->flags & MIDI_F_HAS_SIZE) != 0) + lim = d->midi_dbuf_in.blocksize; + else + lim = d->midi_dbuf_in.unit_size; + if (d->midi_dbuf_in.rl < lim) + /* No data ready, record select. */ + selrecord(p, &d->midi_dbuf_in.sel); + else + /* We can write now. */ + ret |= events & (POLLIN | POLLRDNORM); + } + splx(s); + + return (ret); +} + +void +midi_intr(mididev_info *d) +{ + if (d->intr != NULL) + d->intr(d->intrarg, d); +} + +/* + * These handle the status message of the midi drivers. + */ + +int +midistat_open(dev_t i_dev, int flags, int mode, struct proc * p) +{ + if (midistatbusy) + return (EBUSY); + + bzero(midistatbuf, sizeof(midistatbuf)); + midistatptr = 0; + if (midi_initstatus(midistatbuf, sizeof(midistatbuf) - 1)) + return (ENOMEM); + + midistatbusy = 1; + + return (0); +} + +int +midistat_close(dev_t i_dev, int flags, int mode, struct proc * p) +{ + midistatbusy = 0; + + return (0); +} + +int +midistat_read(dev_t i_dev, struct uio * buf, int flag) +{ + return midi_readstatus(midistatbuf, &midistatptr, buf); +} + +/* + * finally, some "libraries" + */ + +/* Inits the buffer for /dev/midistat. */ +static int +midi_initstatus(char *buf, int size) +{ + int i, p; + device_t dev; + mididev_info *md; + + p = 0; + p += snprintf(buf, size, "FreeBSD Midi Driver (newmidi) %s %s\nInstalled devices:\n", __DATE__, __TIME__); + for (i = 0 ; i < NMIDI_MAX ; i++) { + md = &midi_info[i]; + if (!MIDICONFED(md)) + continue; + dev = devclass_get_device(midi_devclass, i); + if (p < size) + p += snprintf(&buf[p], size - p, "midi%d: <%s> %s\n", i, device_get_desc(dev), md->midistat); + else + return (1); + } + + return (0); +} + +/* Reads the status message. */ +static int +midi_readstatus(char *buf, int *ptr, struct uio *uio) +{ + int s, len; + + s = splmidi(); + len = min(uio->uio_resid, strlen(&buf[*ptr])); + if (len > 0) { + uiomove(&buf[*ptr], len, uio); + *ptr += len; + } + splx(s); + + return (0); +} diff --git a/sys/dev/sound/midi/midi.h b/sys/dev/sound/midi/midi.h new file mode 100644 index 0000000..2e36ed0 --- /dev/null +++ b/sys/dev/sound/midi/midi.h @@ -0,0 +1,294 @@ +/* + * Include file for midi driver. + * + * Copyright by Seigo Tanimura 1999. + * + * 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 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. + * + * $FreeBSD$ + * + */ + +/* + * first, include kernel header files. + */ + +#ifndef _MIDI_H_ +#define _MIDI_H_ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ioccom.h> + +#include <sys/filio.h> +#include <sys/sockio.h> +#include <sys/fcntl.h> +#include <sys/tty.h> +#include <sys/proc.h> + +#include <sys/kernel.h> /* for DATA_SET */ + +#include <sys/module.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <machine/clock.h> /* for DELAY */ +#include <machine/resource.h> +#include <machine/bus_memio.h> +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/clock.h> /* for DELAY */ +#include <sys/soundcard.h> +#include <sys/rman.h> +#include <sys/mman.h> +#include <sys/poll.h> + +#include <dev/sound/midi/miditypes.h> +#include <dev/sound/midi/midibuf.h> +#include <dev/sound/midi/midisynth.h> + +#define MIDI_CDEV_MAJOR 30 + +/* + * descriptor of midi operations ... + * + */ + +struct _mididev_info { + + /* + * the first part of the descriptor is filled up from a + * template. + */ + char name[64]; + + int type; + + d_open_t *open; + d_close_t *close; + d_read_t *read; + d_write_t *write; + d_ioctl_t *ioctl; + d_poll_t *poll; + midi_callback_t *callback; + + /* + * combinations of the following flags are used as second argument in + * the callback from the dma module to the device-specific routines. + */ + +#define MIDI_CB_RD 0x100 /* read callback */ +#define MIDI_CB_WR 0x200 /* write callback */ +#define MIDI_CB_REASON_MASK 0xff +#define MIDI_CB_START 0x01 /* start dma op */ +#define MIDI_CB_STOP 0x03 /* stop dma op */ +#define MIDI_CB_ABORT 0x04 /* abort dma op */ +#define MIDI_CB_INIT 0x05 /* init board parameters */ + + /* + * callback extensions + */ +#define MIDI_CB_DMADONE 0x10 +#define MIDI_CB_DMAUPDATE 0x11 +#define MIDI_CB_DMASTOP 0x12 + + /* init can only be called with int enabled and + * no pending DMA activity. + */ + + /* + * whereas from here, parameters are set at runtime. + * resources are stored in the softc of the device, + * not in the common structure. + */ + + int unit; /* unit number of the device */ + void *softc; /* softc for the device */ + device_t dev; /* device_t for the device */ + + int bd_id ; /* used to hold board-id info, eg. sb version, + * mss codec type, etc. etc. + */ + + midi_dbuf midi_dbuf_in; /* midi input event/message queue */ + midi_dbuf midi_dbuf_out; /* midi output event/message queue */ + midi_dbuf midi_dbuf_passthru; /* midi passthru event/message queue */ + + /* + * these parameters describe the operation of the board. + * Generic things like busy flag, speed, etc are here. + */ + + volatile u_long flags ; /* 32 bits, used for various purposes. */ + int fflags; /* file flag */ + + /* + * we have separate flags for read and write, although in some + * cases this is probably not necessary (e.g. because we cannot + * know how many processes are using the device, we cannot + * distinguish if open, close, abort are for a write or for a + * read). + */ + + /* + * the following flag is used by open-close routines + * to mark the status of the device. + */ +#define MIDI_F_BUSY 0x0001 /* has been opened */ + /* + * the next two are used to allow only one pending operation of + * each type. + */ +#define MIDI_F_READING 0x0004 /* have a pending read */ +#define MIDI_F_WRITING 0x0008 /* have a pending write */ + + /* + * flag used to mark a pending close. + */ +#define MIDI_F_CLOSING 0x0040 /* a pending close */ + + /* + * if user has not set block size, then make it adaptive + * (0.25s, or the perhaps last read/write ?) + */ +#define MIDI_F_HAS_SIZE 0x0080 /* user set block size */ + /* + * assorted flags related to operating mode. + */ +#define MIDI_F_STEREO 0x0100 /* doing stereo */ +#define MIDI_F_NBIO 0x0200 /* do non-blocking i/o */ +#define MIDI_F_PASSTHRU 0x0400 /* pass received data to output port */ + + /* + * these flags mark a pending abort on a r/w operation. + */ +#define MIDI_F_ABORTING 0x1000 /* a pending abort */ + + /* + * this is used to mark that board initialization is needed, e.g. + * because of a change in sampling rate, format, etc. -- It will + * be done at the next convenient time. + */ +#define MIDI_F_INIT 0x4000 /* changed parameters. need init */ + + int play_blocksize, rec_blocksize; /* blocksize for io and dma ops */ + +#define mwsel midi_dbuf_out.sel +#define mrsel midi_dbuf_in.sel + u_long interrupts; /* counter of interrupts */ + u_long magic; +#define MAGIC(unit) ( 0xa4d10de0 + unit ) + void *device_data ; /* just in case it is needed...*/ + + midi_intr_t *intr; /* interrupt handler of the upper layer (ie sequencer) */ + void *intrarg; /* argument to interrupt handler */ + + /* The following is the interface from a midi sequencer to a midi device. */ + synthdev_info synth; + + /* This is the status message to display via /dev/midistat */ + char midistat[128]; +} ; + +/* + * then ioctls and other stuff + */ + +#define NMIDI_MAX 64 /* Number of supported devices */ + +/* + * many variables should be reduced to a range. Here define a macro + */ + +#define RANGE(var, low, high) (var) = \ +((var)<(low)?(low) : (var)>(high)?(high) : (var)) + +/* + * convert dev_t to unit and dev + */ +#define MIDIMINOR(x) (minor(x)) +#define MIDIUNIT(x) ((MIDIMINOR(x) & 0x000000f0) >> 4) +#define MIDIDEV(x) (MIDIMINOR(x) & 0x0000000f) +#define MIDIMKMINOR(u, d) (((u) & 0x0f) << 4 | ((d) & 0x0f)) +#define MIDIMKDEV(m, u, d) (makedev((m), MIDIMKMINOR((u), (d)))) + +/* + * see if the device is configured + */ +#define MIDICONFED(x) ((x)->ioctl != NULL) + +/* + * finally, all default parameters + */ +#define MIDI_BUFFSIZE (4 * 1024) /* XXX */ + +/* + * some macros for debugging purposes + * DDB/DEB to enable/disable debugging stuff + * BVDDB to enable debugging when bootverbose + */ +#define DDB(x) x /* XXX */ +#define BVDDB(x) if (bootverbose) x + +#ifndef DEB +#define DEB(x) +#endif + + extern mididev_info midi_info[NMIDI_MAX]; + + extern u_long nmidi; + extern u_long nsynth; + +/* This is the generic midi drvier initializer. */ + int midiinit(mididev_info *d, device_t dev); + +/* This provides an access to the mididev_info. */ + mididev_info *get_mididev_info(dev_t i_dev, int *unit); + +/* These are the generic methods for a midi driver. */ + d_open_t midi_open; + d_close_t midi_close; + d_ioctl_t midi_ioctl; + d_read_t midi_read; + d_write_t midi_write; + d_poll_t midi_poll; + +/* Common interrupt handler */ +void midi_intr(mididev_info *); + +/* + * library functions (in midi.c) + */ +#define splmidi() spltty() + +/* + * Minor numbers for the midi driver. + */ + +#define MIDI_DEV_MIDIN 2 /* Raw midi access */ +#define MIDI_DEV_STATUS 11 /* /dev/midistat */ + +#endif /* _MIDI_H_ */ diff --git a/sys/dev/sound/midi/midibuf.c b/sys/dev/sound/midi/midibuf.c new file mode 100644 index 0000000..4e23800 --- /dev/null +++ b/sys/dev/sound/midi/midibuf.c @@ -0,0 +1,424 @@ +/* + * Copyright (C) 1999 Seigo Tanimura + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 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 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. + * + * $FreeBSD$ + * + */ + +/* + * This file implements a midi event/message queue. A midi + * event/message queue holds midi events and messages to + * transmit to or received from a midi interface. + */ + +#include "opt_devfs.h" + +#include <dev/sound/midi/midi.h> + +/* Some macros to handle the queue. */ +#define DATA_AVAIL(dbuf) ((dbuf)->rl) +#define SPACE_AVAIL(dbuf) ((dbuf)->fl) + +static void queuerawdata(midi_dbuf *dbuf, char *data, int len); +static void queueuiodata(midi_dbuf *dbuf, struct uio *buf, int len); +static void dequeuerawdata(midi_dbuf *dbuf, char *data, int len); +static void copyrawdata(midi_dbuf *dbuf, char *data, int len); +static void dequeueuiodata(midi_dbuf *dbuf, struct uio *buf, int len); + +/* + * Here are the functions to interact to the midi device drivers. + * These are called from midi device driver functions under sys/i386/isa/snd. + */ + +int +midibuf_init(midi_dbuf *dbuf) +{ + if (dbuf->buf != NULL) + free(dbuf->buf, M_DEVBUF); + dbuf->buf = malloc(MIDI_BUFFSIZE, M_DEVBUF, M_NOWAIT); + bzero(dbuf->buf, MIDI_BUFFSIZE); + dbuf->bufsize = MIDI_BUFFSIZE; + dbuf->rp = dbuf->fp = 0; + dbuf->dl = 0; + dbuf->rl = 0; + dbuf->fl = dbuf->bufsize; + dbuf->int_count = 0; + dbuf->chan = 0; + /*dbuf->unit_size = 1;*/ /* The drivers are responsible. */ + bzero(&dbuf->sel, sizeof(dbuf->sel)); + dbuf->total = 0; + dbuf->prev_total = 0; + dbuf->blocksize = dbuf->bufsize / 4; + + return (0); +} + +/* The sequencer calls this function to queue data. */ +int +midibuf_seqwrite(midi_dbuf *dbuf, u_char* data, int len) +{ + int i, lwrt, lwritten; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (0); + + lwritten = 0; + /* Write down every single byte. */ + while (len > 0) { + /* Find out the number of bytes to write. */ + lwrt = SPACE_AVAIL(dbuf); + if (lwrt > len) + lwrt = len; + if (lwrt > 0) { + /* We can write some now. Queue the data. */ + queuerawdata(dbuf, data, lwrt); + + lwritten += lwrt; + len -= lwrt; + data += lwrt; + } + + /* Have we got still more data to write? */ + if (len > 0) { + /* Yes, sleep until we have enough space. */ + i = tsleep((void *)&dbuf->tsleep_out, PRIBIO | PCATCH, "mbsqwt", 0); + if (i == EINTR || i == ERESTART) + return (-i); + } + } + + return (lwritten); +} + +/* sndwrite calls this function to queue data. */ +int +midibuf_uiowrite(midi_dbuf *dbuf, struct uio *buf, int len) +{ + int i, lwrt, lwritten; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (0); + + lwritten = 0; + /* Write down every single byte. */ + while (len > 0) { + /* Find out the number of bytes to write. */ + lwrt = SPACE_AVAIL(dbuf); + if (lwrt > len) + lwrt = len; + if (lwrt > 0) { + /* We can write some now. Queue the data. */ + queueuiodata(dbuf, buf, lwrt); + + lwritten += lwrt; + len -= lwrt; + } + + /* Have we got still more data to write? */ + if (len > 0) { + /* Yes, sleep until we have enough space. */ + i = tsleep(&dbuf->tsleep_out, PRIBIO | PCATCH, "mbuiwt", 0); + if (i == EINTR || i == ERESTART) + return (-i); + } + } + + return (lwritten); +} + +int +midibuf_output_intr(midi_dbuf *dbuf, u_char *data, int len) +{ + int lrd; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (0); + + /* Have we got any data in the queue? */ + if ((lrd = DATA_AVAIL(dbuf)) == 0) + return (0); + + /* Dequeue the data. */ + if (lrd > len) + lrd = len; + dequeuerawdata(dbuf, data, lrd); + + return (lrd); +} + +int +midibuf_input_intr(midi_dbuf *dbuf, u_char *data, int len) +{ + int lwritten; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (0); + + lwritten = 0; + + /* Have we got any data to write? */ + if (len == 0) + return (0); + /* Can we write now? */ + if (SPACE_AVAIL(dbuf) < len) + return (-EAGAIN); + + /* We can write some now. Queue the data. */ + queuerawdata(dbuf, data, len); + lwritten = len; + + /* Have we managed to write the whole data? */ + if (lwritten < len) + printf("midibuf_input_intr: queue did not have enough space, discarded %d bytes out of %d bytes.\n", len - lwritten, len); + + return (lwritten); +} + +/* The sequencer calls this function to dequeue data. */ +int +midibuf_seqread(midi_dbuf *dbuf, u_char* data, int len) +{ + int i, lrd, lread; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (0); + + lread = 0; + /* Write down every single byte. */ + while (len > 0) { + /* Have we got data to read? */ + if ((lrd = DATA_AVAIL(dbuf)) == 0) { + /* No, sleep until we have data ready to read. */ + i = tsleep(&dbuf->tsleep_in, PRIBIO | PCATCH, "mbsqrd", 0); + if (i == EINTR || i == ERESTART) + return (-i); + if (i == EWOULDBLOCK) + continue; + /* Find out the number of bytes to read. */ + lrd = DATA_AVAIL(dbuf); + } + + if (lrd > len) + lrd = len; + if (lrd > 0) { + /* We can read some data now. Dequeue the data. */ + dequeuerawdata(dbuf, data, lrd); + + lread += lrd; + len -= lrd; + data += lrd; + } + } + + return (lread); +} + +/* The sequencer calls this function to copy data without dequeueing. */ +int +midibuf_seqcopy(midi_dbuf *dbuf, u_char* data, int len) +{ + int i, lrd, lread; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (0); + + lread = 0; + /* Write down every single byte. */ + while (len > 0) { + /* Have we got data to read? */ + if ((lrd = DATA_AVAIL(dbuf)) == 0) { + /* No, sleep until we have data ready to read. */ + i = tsleep(&dbuf->tsleep_in, PRIBIO | PCATCH, "mbsqrd", 0); + if (i == EINTR || i == ERESTART) + return (-i); + if (i == EWOULDBLOCK) + continue; + /* Find out the number of bytes to read. */ + lrd = DATA_AVAIL(dbuf); + } + + if (lrd > len) + lrd = len; + if (lrd > 0) { + /* We can read some data now. Copy the data. */ + copyrawdata(dbuf, data, lrd); + + lread += lrd; + len -= lrd; + data += lrd; + } + } + + return (lread); +} + +/* sndread calls this function to dequeue data. */ +int +midibuf_uioread(midi_dbuf *dbuf, struct uio *buf, int len) +{ + int i, lrd, lread; + + /* Is this a real queue? */ + if (dbuf == (midi_dbuf *)NULL) + return (0); + + lread = 0; + while (len > 0 && lread == 0) { + /* Have we got data to read? */ + if ((lrd = DATA_AVAIL(dbuf)) == 0) { + /* No, sleep until we have data ready to read. */ + i = tsleep(&dbuf->tsleep_in, PRIBIO | PCATCH, "mbuird", 0); + if (i == EINTR || i == ERESTART) + return (-i); + if (i == EWOULDBLOCK) + continue; + /* Find out the number of bytes to read. */ + lrd = DATA_AVAIL(dbuf); + } + + if (lrd > len) + lrd = len; + if (lrd > 0) { + /* We can read some data now. Dequeue the data. */ + dequeueuiodata(dbuf, buf, lrd); + + lread += lrd; + len -= lrd; + } + } + + return (lread); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +static void +queuerawdata(midi_dbuf *dbuf, char *data, int len) +{ + /* dbuf->fp might wrap around dbuf->bufsize. */ + if (dbuf->bufsize - dbuf->fp < len) { + /* The new data wraps, copy them twice. */ + memcpy(dbuf->buf + dbuf->fp, data, dbuf->bufsize - dbuf->fp); + memcpy(dbuf->buf, data + dbuf->bufsize - dbuf->fp, len - (dbuf->bufsize - dbuf->fp)); + } else + /* The new data do not wrap, once is enough. */ + memcpy(dbuf->buf + dbuf->fp, data, len); + + /* Adjust the pointer and the length counters. */ + dbuf->fp = (dbuf->fp + len) % dbuf->bufsize; + dbuf->fl -= len; + dbuf->rl += len; + + /* Wake up the processes sleeping on input data. */ + wakeup(&dbuf->tsleep_in); + if (dbuf->sel.si_pid && dbuf->rl >= dbuf->blocksize) + selwakeup(&dbuf->sel); +} + +static void +queueuiodata(midi_dbuf *dbuf, struct uio *buf, int len) +{ + /* dbuf->fp might wrap around dbuf->bufsize. */ + if (dbuf->bufsize - dbuf->fp < len) { + /* The new data wraps, copy them twice. */ + uiomove((caddr_t)(dbuf->buf + dbuf->fp), dbuf->bufsize - dbuf->fp, buf); + uiomove((caddr_t)(dbuf->buf), len - (dbuf->bufsize - dbuf->fp), buf); + } else + /* The new data do not wrap, once is enough. */ + uiomove((caddr_t)(dbuf->buf + dbuf->fp), len, buf); + + /* Adjust the pointer and the length counters. */ + dbuf->fp = (dbuf->fp + len) % dbuf->bufsize; + dbuf->fl -= len; + dbuf->rl += len; + + /* Wake up the processes sleeping on queueing. */ + wakeup(&dbuf->tsleep_in); + if (dbuf->sel.si_pid && dbuf->rl >= dbuf->blocksize) + selwakeup(&dbuf->sel); +} + +static void +dequeuerawdata(midi_dbuf *dbuf, char *data, int len) +{ + /* Copy the data. */ + copyrawdata(dbuf, data, len); + + /* Adjust the pointer and the length counters. */ + dbuf->rp = (dbuf->rp + len) % dbuf->bufsize; + dbuf->rl -= len; + dbuf->fl += len; + + /* Wake up the processes sleeping on queueing. */ + wakeup(&dbuf->tsleep_out); + if (dbuf->sel.si_pid && dbuf->fl >= dbuf->blocksize) + selwakeup(&dbuf->sel); +} + +static void +copyrawdata(midi_dbuf *dbuf, char *data, int len) +{ + /* dbuf->rp might wrap around dbuf->bufsize. */ + if (dbuf->bufsize - dbuf->rp < len) { + /* The data to be read wraps, copy them twice. */ + memcpy(data, dbuf->buf + dbuf->rp, dbuf->bufsize - dbuf->rp); + memcpy(data + dbuf->bufsize - dbuf->rp, dbuf->buf, len - (dbuf->bufsize - dbuf->rp)); + } else + /* The new data do not wrap, once is enough. */ + memcpy(data, dbuf->buf + dbuf->rp, len); +} + +static void +dequeueuiodata(midi_dbuf *dbuf, struct uio *buf, int len) +{ + /* dbuf->rp might wrap around dbuf->bufsize. */ + if (dbuf->bufsize - dbuf->rp < len) { + /* The new data wraps, copy them twice. */ + uiomove((caddr_t)(dbuf->buf + dbuf->rp), dbuf->bufsize - dbuf->rp, buf); + uiomove((caddr_t)(dbuf->buf), len - (dbuf->bufsize - dbuf->rp), buf); + } else + /* The new data do not wrap, once is enough. */ + uiomove((caddr_t)(dbuf->buf + dbuf->rp), len, buf); + + /* Adjust the pointer and the length counters. */ + dbuf->rp = (dbuf->rp + len) % dbuf->bufsize; + dbuf->rl -= len; + dbuf->fl += len; + + /* Wake up the processes sleeping on queueing. */ + wakeup(&dbuf->tsleep_out); + if (dbuf->sel.si_pid && dbuf->fl >= dbuf->blocksize) + selwakeup(&dbuf->sel); +} diff --git a/sys/dev/sound/midi/midibuf.h b/sys/dev/sound/midi/midibuf.h new file mode 100644 index 0000000..d40019d --- /dev/null +++ b/sys/dev/sound/midi/midibuf.h @@ -0,0 +1,64 @@ +/* + * Include file for midi buffer. + * + * Copyright by Seigo Tanimura 1999. + * + * 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 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. + * + * $FreeBSD$ + * + */ + +/* + * descriptor of a midi buffer. See midibuf.c for documentation. + * (rp,rl) and (fp,fl) identify the READY and FREE regions of the + * buffer. dl contains the length used for dma transfer, dl>0 also + * means that the channel is busy and there is a DMA transfer in progress. + */ + +typedef struct _midi_dbuf { + char *buf; + int bufsize ; + volatile int rp, fp; /* pointers to the ready and free area */ + volatile int dl; /* transfer size */ + volatile int rl, fl; /* length of ready and free areas. */ + int int_count; + int chan; /* dma channel */ + int unit_size ; /* unit size */ + struct selinfo sel; + u_long total; /* total bytes processed */ + u_long prev_total; /* copy of the above when GETxPTR called */ + int tsleep_in, tsleep_out; /* pillows to tsleep on */ + int blocksize; /* block size */ +} midi_dbuf ; + +/* + * These are the midi buffer methods, used in midi interface devices. + */ +int midibuf_init(midi_dbuf *dbuf); +int midibuf_seqwrite(midi_dbuf *dbuf, u_char* data, int len); +int midibuf_uiowrite(midi_dbuf *dbuf, struct uio *buf, int len); +int midibuf_output_intr(midi_dbuf *dbuf, u_char *data, int len); +int midibuf_input_intr(midi_dbuf *dbuf, u_char *data, int len); +int midibuf_seqread(midi_dbuf *dbuf, u_char* data, int len); +int midibuf_seqcopy(midi_dbuf *dbuf, u_char* data, int len); +int midibuf_uioread(midi_dbuf *dbuf, struct uio *buf, int len); diff --git a/sys/dev/sound/midi/midisynth.c b/sys/dev/sound/midi/midisynth.c new file mode 100644 index 0000000..7d668fd --- /dev/null +++ b/sys/dev/sound/midi/midisynth.c @@ -0,0 +1,632 @@ +/* + * 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 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. + * + * $FreeBSD$ + * + */ + +/* + * This is the interface for a sequencer to interact a midi driver. + * This interface translates the sequencer operations to the corresponding + * midi messages, and vice versa. + */ + +#include "opt_devfs.h" + +#include <stddef.h> +#include <dev/sound/midi/midi.h> + +#define TYPEDRANGE(type, x, lower, upper) \ +{ \ + type tl, tu; \ + tl = (lower); \ + tu = (upper); \ + if (x < tl) { \ + x = tl; \ + } else if(x > tu) { \ + x = tu; \ + } \ +} + +/* + * These functions goes into midisynthdev_op_desc. + */ +static mdsy_killnote_t synth_killnote; +static mdsy_setinstr_t synth_setinstr; +static mdsy_startnote_t synth_startnote; +static mdsy_reset_t synth_reset; +static mdsy_hwcontrol_t synth_hwcontrol; +static mdsy_loadpatch_t synth_loadpatch; +static mdsy_panning_t synth_panning; +static mdsy_aftertouch_t synth_aftertouch; +static mdsy_controller_t synth_controller; +static mdsy_patchmgr_t synth_patchmgr; +static mdsy_bender_t synth_bender; +static mdsy_allocvoice_t synth_allocvoice; +static mdsy_setupvoice_t synth_setupvoice; +static mdsy_sendsysex_t synth_sendsysex; +static mdsy_prefixcmd_t synth_prefixcmd; +static mdsy_volumemethod_t synth_volumemethod; +static mdsy_readraw_t synth_readraw; +static mdsy_writeraw_t synth_writeraw; + +/* + * This is the synthdev_info for a midi interface device. + * You may have to replace a few of functions for an internal + * synthesizer. + */ +synthdev_info midisynth_op_desc = { + synth_killnote, + synth_setinstr, + synth_startnote, + synth_reset, + synth_hwcontrol, + synth_loadpatch, + synth_panning, + synth_aftertouch, + synth_controller, + synth_patchmgr, + synth_bender, + synth_allocvoice, + synth_setupvoice, + synth_sendsysex, + synth_prefixcmd, + synth_volumemethod, + synth_readraw, + synth_writeraw, +}; + +/* The following functions are local. */ +static int synth_leavesysex(mididev_info *md); + +/* + * Here are the main functions to interact to the midi sequencer. + * These are called from the sequencer functions in sys/i386/isa/snd/sequencer.c. + */ + +static int +synth_killnote(mididev_info *md, int chn, int note, int vel) +{ + int unit, msg, chp; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (note < 0 || note > 127 || chn < 0 || chn > 15) + return (EINVAL); + TYPEDRANGE(int, vel, 0, 127); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + msg = sd->prev_out_status & 0xf0; + chp = sd->prev_out_status & 0x0f; + + if (chp == chn && ((msg == 0x90 && vel == 64) || msg == 0x80)) { + /* Use running status. */ + c[0] = (u_char)note; + if (msg == 0x90) + /* The note was on. */ + c[1] = 0; + else + c[1] = (u_char)vel; + + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 2, 1) == EAGAIN) + return (EAGAIN); + } else { + if (vel == 64) { + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = 0; + } else { + c[0] = 0x80 | (chn & 0x0f); /* Note off. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + } + + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 3, 1) == EAGAIN) + return EAGAIN; + /* Update the status. */ + sd->prev_out_status = c[0]; + } + + return (0); +} + +static int +synth_setinstr(mididev_info *md, int chn, int instr) +{ + int unit; + synthdev_info *sd; + u_char c[2]; + + unit = md->unit; + sd = &md->synth; + + if (instr < 0 || instr > 127 || chn < 0 || chn > 15) + return (EINVAL); + + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + c[0] = 0xc0 | (chn & 0x0f); /* Progamme change. */ + c[1] = (u_char)instr; + if (md->synth.writeraw(md, c, 3, 1) == EAGAIN) + return (EAGAIN); + /* Update the status. */ + sd->prev_out_status = c[0]; + + return (0); +} + +static int +synth_startnote(mididev_info *md, int chn, int note, int vel) +{ + int unit, msg, chp; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (note < 0 || note > 127 || chn < 0 || chn > 15) + return (EINVAL); + TYPEDRANGE(int, vel, 0, 127); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + msg = sd->prev_out_status & 0xf0; + chp = sd->prev_out_status & 0x0f; + + if (chp == chn && msg == 0x90) { + /* Use running status. */ + c[0] = (u_char)note; + c[1] = (u_char)vel; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 2, 1) == EAGAIN) + return (EAGAIN); + } else { + c[0] = 0x90 | (chn & 0x0f); /* Note on. */ + c[1] = (u_char)note; + c[2] = (u_char)vel; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 3, 1) == EAGAIN) + return (EAGAIN); + /* Update the status. */ + sd->prev_out_status = c[0]; + } + + return (0); +} + +static int +synth_reset(mididev_info *md) +{ + synth_leavesysex(md); + return (0); +} + +static int +synth_hwcontrol(mididev_info *md, u_char *event) +{ + /* NOP. */ + return (0); +} + +static int +synth_loadpatch(mididev_info *md, int format, struct uio *buf, int offs, int count, int pmgr_flag) +{ + struct sysex_info sysex; + synthdev_info *sd; + int unit, i, eox_seen, first_byte, left, src_offs, hdr_size; + u_char c[count]; + + unit = md->unit; + sd = &md->synth; + + eox_seen = 0; + first_byte = 1; + hdr_size = offsetof(struct sysex_info, data); + + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + if (synth_prefixcmd(md, 0xf0)) + return (0); + if (format != SYSEX_PATCH) { + printf("synth_loadpatch: patch format 0x%x is invalid.\n", format); + return (EINVAL); + } + if (count < hdr_size) { + printf("synth_loadpatch: patch header is too short.\n"); + return (EINVAL); + } + count -= hdr_size; + + /* Copy the patch data. */ + if (uiomove((caddr_t)&((char *)&sysex)[offs], hdr_size - offs, buf)) + printf("synth_loadpatch: memory mangled?\n"); + + if (count < sysex.len) { + sysex.len = (long)count; + printf("synth_loadpatch: sysex record of %d bytes is too long, adjusted to %d bytes.\n", (int)sysex.len, count); + } + left = sysex.len; + src_offs = 0; + + for (i = 0 ; i < left ; i++) { + uiomove((caddr_t)&c[i], 1, buf); + eox_seen = i > 0 && (c[i] & 0x80) != 0; + if (eox_seen && c[i] != 0xf7) + c[i] = 0xf7; + if (i == 0 && c[i] != 0x80) { + printf("synth_loadpatch: sysex does not begin with the status.\n"); + return (EINVAL); + } + if (!first_byte && (c[i] & 0x80) != 0) { + md->synth.writeraw(md, c, i + 1, 0); + /* Update the status. */ + sd->prev_out_status = c[i]; + return (0); + } + first_byte = 0; + } + + if (!eox_seen) { + c[0] = 0xf7; + md->synth.writeraw(md, c, 1, 0); + sd->prev_out_status = c[0]; + } + + return (0); +} + +static int +synth_panning(mididev_info *md, int chn, int pan) +{ + /* NOP. */ + return (0); +} + +static int +synth_aftertouch(mididev_info *md, int chn, int press) +{ + int unit, msg, chp; + synthdev_info *sd; + u_char c[2]; + + unit = md->unit; + sd = &md->synth; + + if (press < 0 || press > 127 || chn < 0 || chn > 15) + return (EINVAL); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + msg = sd->prev_out_status & 0xf0; + chp = sd->prev_out_status & 0x0f; + + if (chp == chn && msg == 0xd0) { + /* Use running status. */ + c[0] = (u_char)press; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 1, 1) == EAGAIN) + return (EAGAIN); + } else { + c[0] = 0xd0 | (chn & 0x0f); /* Channel Pressure. */ + c[1] = (u_char)press; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 2, 1) == EAGAIN) + return (EAGAIN); + /* Update the status. */ + sd->prev_out_status = c[0]; + } + + return (0); +} + +static int +synth_controller(mididev_info *md, int chn, int ctrlnum, int val) +{ + int unit, msg, chp; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (ctrlnum < 1 || ctrlnum > 127 || chn < 0 || chn > 15) + return (EINVAL); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + msg = sd->prev_out_status & 0xf0; + chp = sd->prev_out_status & 0x0f; + + if (chp == chn && msg == 0xb0) { + /* Use running status. */ + c[0] = (u_char)ctrlnum; + c[1] = (u_char)val & 0x7f; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 2, 1) == EAGAIN) + return (EAGAIN); + } else { + c[0] = 0xb0 | (chn & 0x0f); /* Control Message. */ + c[1] = (u_char)ctrlnum; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 3, 1) == EAGAIN) + return (EAGAIN); + /* Update the status. */ + sd->prev_out_status = c[0]; + } + + return (0); +} + +static int +synth_patchmgr(mididev_info *md, struct patmgr_info *rec) +{ + return (EINVAL); +} + +static int +synth_bender(mididev_info *md, int chn, int val) +{ + int unit, msg, chp; + synthdev_info *sd; + u_char c[3]; + + unit = md->unit; + sd = &md->synth; + + if (val < 0 || val > 16383 || chn < 0 || chn > 15) + return (EINVAL); + if (synth_leavesysex(md) == EAGAIN) + return (EAGAIN); + + msg = sd->prev_out_status & 0xf0; + chp = sd->prev_out_status & 0x0f; + + if (chp == chn && msg == 0xe0) { + /* Use running status. */ + c[0] = (u_char)val & 0x7f; + c[1] = (u_char)(val >> 7) & 0x7f; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 2, 1) == EAGAIN) + return (EAGAIN); + } else { + c[0] = 0xe0 | (chn & 0x0f); /* Pitch bend. */ + c[1] = (u_char)val & 0x7f; + c[2] = (u_char)(val >> 7) & 0x7f; + if (synth_prefixcmd(md, c[0])) + return (0); + if (md->synth.writeraw(md, c, 3, 1) == EAGAIN) + return (EAGAIN); + /* Update the status. */ + sd->prev_out_status = c[0]; + } + + return (0); +} + +static int +synth_allocvoice(mididev_info *md, int chn, int note, struct voice_alloc_info *alloc) +{ + /* NOP. */ + return (0); +} + +static int +synth_setupvoice(mididev_info *md, int voice, int chn) +{ + /* NOP. */ + return (0); +} + +static int +synth_sendsysex(mididev_info *md, u_char *sysex, int len) +{ + int unit, i, j; + synthdev_info *sd; + u_char c[len]; + + unit = md->unit; + sd = &md->synth; + + for (i = 0 ; i < len ; i++) { + switch (sysex[i]) { + case 0xf0: + /* Sysex begins. */ + if (synth_prefixcmd(md, 0xf0)) + return (0); + sd->sysex_state = 1; + break; + case 0xf7: + /* Sysex ends. */ + if (!sd->sysex_state) + return (0); + sd->sysex_state = 0; + break; + default: + if (!sd->sysex_state) + return (0); + if ((sysex[i] & 0x80) != 0) { + /* A status in a sysex? */ + sysex[i] = 0xf7; + sd->sysex_state = 0; + } + break; + } + c[i] = sysex[i]; + if (!sd->sysex_state) + break; + } + if (md->synth.writeraw(md, c, i, 1) == EAGAIN) + return (EAGAIN); + + /* Update the status. */ + for (j = i - 1 ; j >= 0 ; j--) + if ((c[j] & 0x80) != 0) { + sd->prev_out_status = c[j]; + break; + } + + return (0); +} + +static int +synth_prefixcmd(mididev_info *md, int status) +{ + /* NOP. */ + return (0); +} + +static int +synth_volumemethod(mididev_info *md, int mode) +{ + /* NOP. */ + return (0); +} + +static int +synth_readraw(mididev_info *md, u_char *buf, int len, int nonblock) +{ + int unit, ret, s; + + if (md == NULL) + return (ENXIO); + + unit = md->unit; + if (unit >= nmidi + nsynth) { + DEB(printf("synth_readraw: unit %d does not exist.\n", unit)); + return (ENXIO); + } + if ((md->fflags & FREAD) == 0) { + DEB(printf("mpu_readraw: unit %d is not for reading.\n", unit)); + return (EIO); + } + + s = splmidi(); + + /* Begin recording. */ + md->callback(md, MIDI_CB_START | MIDI_CB_RD); + + if (nonblock) { + /* Have we got enough data to read? */ + if (md->midi_dbuf_in.rl < len) + return (EAGAIN); + } + + ret = midibuf_seqread(&md->midi_dbuf_in, buf, len); + + splx(s); + + if (ret < 0) + ret = -ret; + else + ret = 0; + + return (ret); +} + +static int +synth_writeraw(mididev_info *md, u_char *buf, int len, int nonblock) +{ + int unit, ret, s; + + if (md == NULL) + return (ENXIO); + + unit = md->unit; + + if (unit >= nmidi + nsynth) { + DEB(printf("synth_writeraw: unit %d does not exist.\n", unit)); + return (ENXIO); + } + if ((md->fflags & FWRITE) == 0) { + DEB(printf("synth_writeraw: unit %d is not for writing.\n", unit)); + return (EIO); + } + + /* For nonblocking, have we got enough space to write? */ + if (nonblock && md->midi_dbuf_out.fl < len) + return (EAGAIN); + + s = splmidi(); + + ret = midibuf_seqwrite(&md->midi_dbuf_out, buf, len); + if (ret < 0) + ret = -ret; + else + ret = 0; + + /* Begin playing. */ + md->callback(md, MIDI_CB_START | MIDI_CB_WR); + + splx(s); + + return (ret); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +static int +synth_leavesysex(mididev_info *md) +{ + int unit; + synthdev_info *sd; + u_char c; + + unit = md->unit; + sd = &md->synth; + + if (!sd->sysex_state) + return (0); + + sd->sysex_state = 0; + c = 0xf7; + if (md->synth.writeraw(md, &c, sizeof(c), 1) == EAGAIN) + return (EAGAIN); + sd->sysex_state = 0; + /* Update the status. */ + sd->prev_out_status = c; + + return (0); +} diff --git a/sys/dev/sound/midi/midisynth.h b/sys/dev/sound/midi/midisynth.h new file mode 100644 index 0000000..d27e62b --- /dev/null +++ b/sys/dev/sound/midi/midisynth.h @@ -0,0 +1,97 @@ +/* + * include file for midi synthesizer interface. + * + * Copyright by Seigo Tanimura 1999. + * + * 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 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. + * + * $FreeBSD$ + * + */ + +#define SYNTH_MAX_VOICES 32 + +/* This is the voice allocation state for a synthesizer. */ +struct voice_alloc_info { + int max_voice; + int used_voices; + int ptr; /* For device specific use */ + u_short map[SYNTH_MAX_VOICES]; /* (ch << 8) | (note+1) */ + int timestamp; + int alloc_times[SYNTH_MAX_VOICES]; +}; + +/* This is the channel information for a synthesizer. */ +struct channel_info { + int pgm_num; + int bender_value; + u_char controllers[128]; +}; + +/* These are the function types for a midi synthesizer interface. */ +typedef int (mdsy_killnote_t)(mididev_info *md, int chn, int note, int vel); +typedef int (mdsy_setinstr_t)(mididev_info *md, int chn, int instr); +typedef int (mdsy_startnote_t)(mididev_info *md, int chn, int note, int vel); +typedef int (mdsy_reset_t)(mididev_info *md); +typedef int (mdsy_hwcontrol_t)(mididev_info *md, u_char *event); +typedef int (mdsy_loadpatch_t)(mididev_info *md, int format, struct uio *buf, int offs, int count, int pmgr_flag); +typedef int (mdsy_panning_t)(mididev_info *md, int chn, int pan); +typedef int (mdsy_aftertouch_t)(mididev_info *md, int chn, int press); +typedef int (mdsy_controller_t)(mididev_info *md, int chn, int ctrlnum, int val); +typedef int (mdsy_patchmgr_t)(mididev_info *md, struct patmgr_info *rec); +typedef int (mdsy_bender_t)(mididev_info *md, int chn, int val); +typedef int (mdsy_allocvoice_t)(mididev_info *md, int chn, int note, struct voice_alloc_info *alloc); +typedef int (mdsy_setupvoice_t)(mididev_info *md, int voice, int chn); +typedef int (mdsy_sendsysex_t)(mididev_info *md, u_char *sysex, int len); +typedef int (mdsy_prefixcmd_t)(mididev_info *md, int status); +typedef int (mdsy_volumemethod_t)(mididev_info *md, int mode); +typedef int (mdsy_readraw_t)(mididev_info *md, u_char *buf, int len, int nonblock); +typedef int (mdsy_writeraw_t)(mididev_info *md, u_char *buf, int len, int nonblock); + +/* This is a midi synthesizer interface and state. */ +struct _synthdev_info { + mdsy_killnote_t *killnote; + mdsy_setinstr_t *setinstr; + mdsy_startnote_t *startnote; + mdsy_reset_t *reset; + mdsy_hwcontrol_t *hwcontrol; + mdsy_loadpatch_t *loadpatch; + mdsy_panning_t *panning; + mdsy_aftertouch_t *aftertouch; + mdsy_controller_t *controller; + mdsy_patchmgr_t *patchmgr; + mdsy_bender_t *bender; + mdsy_allocvoice_t *allocvoice; + mdsy_setupvoice_t *setupvoice; + mdsy_sendsysex_t *sendsysex; + mdsy_prefixcmd_t *prefixcmd; + mdsy_volumemethod_t *volumemethod; + mdsy_readraw_t *readraw; + mdsy_writeraw_t *writeraw; + + struct voice_alloc_info alloc; /* Voice allocation. */ + struct channel_info chn_info[16]; /* Channel information. */ + + u_char prev_out_status; /* Previous status. */ + int sysex_state; /* State of sysex transmission. */ +}; +typedef struct _synthdev_info synthdev_info; diff --git a/sys/dev/sound/midi/miditypes.h b/sys/dev/sound/midi/miditypes.h new file mode 100644 index 0000000..c0dfdf6 --- /dev/null +++ b/sys/dev/sound/midi/miditypes.h @@ -0,0 +1,34 @@ +/* + * Include file for type definitions in midi driver. + * + * Copyright by Seigo Tanimura 1999. + * + * 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 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. + * + * $FreeBSD$ + * + */ + +typedef struct _mididev_info mididev_info; + +typedef int (midi_callback_t)(mididev_info *d, int reason); +typedef void (midi_intr_t)(void *p, mididev_info *md); diff --git a/sys/dev/sound/midi/sequencer.c b/sys/dev/sound/midi/sequencer.c new file mode 100644 index 0000000..ba6a21d --- /dev/null +++ b/sys/dev/sound/midi/sequencer.c @@ -0,0 +1,2040 @@ +/* + * The sequencer personality manager. + * + * 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 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. + * + * $FreeBSD$ + * + */ + +/* + * This is the newmidi sequencer driver. This driver handles io against + * /dev/sequencer, midi input and output event queues and event transmittion + * to and from a midi device or synthesizer. + */ + +#include "opt_devfs.h" + +#include <dev/sound/midi/midi.h> +#include <dev/sound/midi/sequencer.h> + +#ifndef DDB +#define DDB(x) +#endif /* DDB */ + +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define SND_DEV_MIDIN 2 /* Raw midi access */ +#define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ + +#define MIDIDEV_MODE 0x2000 + +/* Length of a sequencer event. */ +#define EV_SZ 8 +#define IEV_SZ 8 + +/* Return value from seq_playevent and the helpers. */ +enum { + MORE, + TIMERARMED, + QUEUEFULL +}; + +/* + * These functions goes into seq_op_desc to get called + * from sound.c. + */ + +static midi_intr_t seq_intr; +static seq_callback_t seq_callback; + +/* These are the entries to the sequencer driver. */ +static d_open_t seq_open; +static d_close_t seq_close; +static d_ioctl_t seq_ioctl; +static d_read_t seq_read; +static d_write_t seq_write; +static d_poll_t seq_poll; + +/* + * This is the device descriptor for the midi sequencer. + */ +seqdev_info seq_op_desc = { + "midi sequencer", + + 0, + + seq_open, + seq_close, + seq_read, + seq_write, + seq_ioctl, + seq_poll, + + seq_callback, + + SEQ_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* Here is the parameter structure per a device. */ +struct seq_softc { + seqdev_info *devinfo; /* sequencer device information */ + + int fflags; /* Access mode */ + + u_long seq_time; /* The beggining time of this sequence */ + u_long prev_event_time; /* The time of the previous event output */ + u_long prev_input_time; /* The time of the previous event input */ + u_long prev_wakeup_time; /* The time of the previous wakeup */ + struct callout_handle timeout_ch; /* Timer callout handler */ + long timer_current; /* Current timer value */ + int timer_running; /* State of timer */ + int midi_open[NMIDI_MAX]; /* State of midi devices. */ + int pending_timer; /* Timer change operation */ + int output_threshould; /* Sequence output threshould */ + int pre_event_timeout; /* Time to wait event input */ + int queueout_pending; /* Pending for the output queue */ + snd_sync_parm sync_parm; /* AIOSYNC parameter set */ + struct proc *sync_proc; /* AIOSYNCing process */ +}; + +typedef struct seq_softc *sc_p; + +static d_open_t seqopen; +static d_close_t seqclose; +static d_ioctl_t seqioctl; +static d_read_t seqread; +static d_write_t seqwrite; +static d_poll_t seqpoll; + +#define CDEV_MAJOR SEQ_CDEV_MAJOR +static struct cdevsw seq_cdevsw = { + /* open */ seqopen, + /* close */ seqclose, + /* read */ seqread, + /* write */ seqwrite, + /* ioctl */ seqioctl, + /* poll */ seqpoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "midi", /* XXX */ + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* bmaj */ -1 +}; + +seqdev_info seq_info[NSEQ_MAX] ; +static int seq_info_inited; +u_long nseq = NSEQ_MAX; /* total number of sequencers */ + +/* The followings are the local function. */ +static int seq_init(void); +static int seq_queue(sc_p scp, u_char *note); +static void seq_startplay(sc_p scp); +static int seq_playevent(sc_p scp, u_char *event); +static u_long seq_gettime(void); +static int seq_requesttimer(sc_p scp, int delay); +static void seq_stoptimer(sc_p scp); +static void seq_midiinput(sc_p scp, mididev_info *md); +static int seq_copytoinput(sc_p scp, u_char *event, int len); +static int seq_extended(sc_p scp, u_char *event); +static int seq_chnvoice(sc_p scp, u_char *event); +static int seq_findvoice(mididev_info *md, int chn, int note); +static int seq_allocvoice(mididev_info *md, int chn, int note); +static int seq_chncommon(sc_p scp, u_char *event); +static int seq_timing(sc_p scp, u_char *event); +static int seq_local(sc_p scp, u_char *event); +static int seq_sysex(sc_p scp, u_char *event); +static void seq_timer(void *arg); +static int seq_reset(sc_p scp); +static int seq_openmidi(sc_p scp, mididev_info *md, int flags, int mode, struct proc *p); +static int seq_closemidi(sc_p scp, mididev_info *md, int flags, int mode, struct proc *p); +static void seq_panic(sc_p scp); +static int seq_sync(sc_p scp); + +static seqdev_info * get_seqdev_info(dev_t i_dev, int *unit); + +/* + * Here are the main functions to interact to the user process. + * These are called from snd* functions in sys/i386/isa/snd/sound.c. + */ + +static int +seq_init(void) +{ + int unit; + sc_p scp; + seqdev_info *devinfo; + + DEB(printf("seq: initing.\n")); + + /* Have we already inited? */ + if (seq_info_inited) + return (1); + + for (unit = 0 ; unit < nseq ; unit++) { + /* Allocate the softc. */ + scp = malloc(sizeof(*scp), M_DEVBUF, M_NOWAIT); + if (scp == (sc_p)NULL) { + printf("seq%d: softc allocation failed.\n", unit); + return (1); + } + bzero(scp, sizeof(*scp)); + + /* Fill the softc and the seq_info for this unit. */ + scp->seq_time = seq_gettime(); + scp->prev_event_time = 0; + scp->prev_input_time = 0; + scp->prev_wakeup_time = scp->seq_time; + callout_handle_init(&scp->timeout_ch); + scp->timer_current = 0; + scp->timer_running = 0; + scp->queueout_pending = 0; + + scp->devinfo = devinfo = &seq_info[unit]; + bcopy(&seq_op_desc, devinfo, sizeof(seq_op_desc)); + devinfo->unit = unit; + devinfo->softc = scp; + devinfo->flags = 0; + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = EV_SZ; + midibuf_init(&devinfo->midi_dbuf_in); + midibuf_init(&devinfo->midi_dbuf_out); + + make_dev(&seq_cdevsw, MIDIMKMINOR(unit, SND_DEV_SEQ), + UID_ROOT, GID_WHEEL, 0666, "sequencer%d", unit); + } + + /* We have inited. */ + seq_info_inited = 1; + + if (nseq == 1) + printf("seq0: Midi sequencer.\n"); + else + printf("seq0-%lu: Midi sequencers.\n", nseq - 1); + + DEB(printf("seq: inited.\n")); + + return (0); +} + +int +seq_open(dev_t i_dev, int flags, int mode, struct proc *p) +{ + int unit, s, midiunit; + sc_p scp; + seqdev_info *sd; + mididev_info *md; + + unit = MIDIUNIT(i_dev); + + DEB(printf("seq%d: opening.\n", unit)); + + if (unit >= NSEQ_MAX) { + DEB(printf("seq_open: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + DEB(printf("seq_open: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + s = splmidi(); + /* Mark this device busy. */ + if ((sd->flags & SEQ_F_BUSY) != 0) { + splx(s); + DEB(printf("seq_open: unit %d is busy.\n", unit)); + return (EBUSY); + } + sd->flags |= SEQ_F_BUSY; + sd->flags &= ~(SEQ_F_READING | SEQ_F_WRITING); + scp->fflags = flags; + + /* Init the queue. */ + midibuf_init(&sd->midi_dbuf_in); + midibuf_init(&sd->midi_dbuf_out); + + /* Init timestamp. */ + scp->seq_time = seq_gettime(); + scp->prev_event_time = 0; + scp->prev_input_time = 0; + scp->prev_wakeup_time = scp->seq_time; + + splx(s); + + /* Open midi devices. */ + for (midiunit = 0 ; midiunit < nmidi + nsynth ; midiunit++) { + md = &midi_info[midiunit]; + if (MIDICONFED(md)) + seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, p); + } + + DEB(printf("seq%d: opened.\n", unit)); + + return (0); +} + +int +seq_close(dev_t i_dev, int flags, int mode, struct proc *p) +{ + int unit, s, i; + sc_p scp; + seqdev_info *sd; + mididev_info *md; + + unit = MIDIUNIT(i_dev); + + DEB(printf("seq%d: closing.\n", unit)); + + if (unit >= NSEQ_MAX) { + DEB(printf("seq_close: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + DEB(printf("seq_close: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + s = splmidi(); + + if (!(sd->flags & MIDI_F_NBIO)) + seq_sync(scp); + + /* Stop the timer. */ + seq_stoptimer(scp); + + /* Reset the sequencer. */ + seq_reset(scp); + seq_sync(scp); + + /* Clean up the midi device. */ + for (i = 0 ; i < nmidi + nsynth ; i++) { + md = &midi_info[i]; + if (MIDICONFED(md)) + seq_closemidi(scp, md, scp->fflags, MIDIDEV_MODE, p); + } + + /* Stop playing and unmark this device busy. */ + sd->flags &= ~(SEQ_F_BUSY | SEQ_F_READING | SEQ_F_WRITING); + + splx(s); + + DEB(printf("seq%d: closed.\n", unit)); + + return (0); +} + +int +seq_read(dev_t i_dev, struct uio *buf, int flag) +{ + int unit, ret, s, len; + sc_p scp; + seqdev_info *sd; + + unit = MIDIUNIT(i_dev); + + /*DEB(printf("seq%d: reading.\n", unit));*/ + + if (unit >= NSEQ_MAX) { + DEB(printf("seq_read: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + DEB(printf("seq_read: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + if ((scp->fflags & FREAD) == 0) { + DEB(printf("seq_read: unit %d is not for reading.\n", unit)); + return (EIO); + } + + s = splmidi(); + + /* Begin recording. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_RD); + + /* Have we got the data to read? */ + if ((sd->flags & SEQ_F_NBIO) != 0 && sd->midi_dbuf_in.rl == 0) + ret = EAGAIN; + else { + len = buf->uio_resid; + ret = midibuf_uioread(&sd->midi_dbuf_in, buf, len); + if (ret < 0) + ret = -ret; + else + ret = 0; + } + splx(s); + + return (ret); +} + +int +seq_write(dev_t i_dev, struct uio *buf, int flag) +{ + u_char event[EV_SZ], ev_code; + int unit, count, countorg, midiunit, ev_size, p, ret, s; + sc_p scp; + seqdev_info *sd; + mididev_info *md; + + unit = MIDIUNIT(i_dev); + + /*DEB(printf("seq%d: writing.\n", unit));*/ + + if (unit >= NSEQ_MAX) { + DEB(printf("seq_write: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + DEB(printf("seq_write: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + if ((scp->fflags & FWRITE) == 0) { + DEB(printf("seq_write: unit %d is not for writing.\n", unit)); + return (EIO); + } + + p = 0; + countorg = buf->uio_resid; + count = countorg; + + s = splmidi(); + /* Begin playing. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + splx(s); + + /* Pick up an event. */ + while (count >= 4) { + if (uiomove((caddr_t)event, 4, buf)) + printf("seq_write: user memory mangled?\n"); + ev_code = event[0]; + + /* Have a look at the event code. */ + if (ev_code == SEQ_FULLSIZE) { + + /* A long event, these are the patches/samples for a synthesizer. */ + midiunit = *(u_short *)&event[2]; + if (midiunit < 0 || midiunit >= nmidi + nsynth) + return (ENXIO); + md = &midi_info[midiunit]; + if (!MIDICONFED(md)) + return (ENXIO); + s = splmidi(); + if ((md->flags & MIDI_F_BUSY) == 0 + && seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) != 0) { + splx(s); + return (ENXIO); + } + + DEB(printf("seq_write: loading a patch to the unit %d.\n", midiunit)); + + ret = md->synth.loadpatch(md, *(short *)&event[0], buf, p + 4, count, 0); + splx(s); + return (ret); + } + + if (ev_code >= 128) { + + /* Some sort of an extended event. The size is eight bytes. */ +#if notyet + if (scp->seq_mode == SEQ_2 && ev_code == SEQ_EXTENDED) { + printf("seq%d: invalid level two event %x.\n", unit, ev_code); + return (EINVAL); + } +#endif /* notyet */ + ev_size = 8; + + if (count < ev_size) { + /* No more data. Start playing now. */ + s = splmidi(); + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + splx(s); + + return (0); + } + if (uiomove((caddr_t)&event[4], 4, buf)) + printf("seq_write: user memory mangled?\n"); + } else { + + /* Not an extended event. The size is four bytes. */ +#if notyet + if (scp->seq_mode == SEQ_2) { + printf("seq%d: four byte event in level two mode.\n", unit); + return (EINVAL); + } +#endif /* notyet */ + ev_size = 4; + } + if (ev_code == SEQ_MIDIPUTC) { + /* An event passed to the midi device itself. */ + midiunit = event[2]; + if (midiunit < 0 || midiunit >= nmidi + nsynth) + return (ENXIO); + md = &midi_info[midiunit]; + if (!MIDICONFED(md)) + return (ENXIO); + if ((md->flags & MIDI_F_BUSY) == 0 + && (ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc)) != 0) { + seq_reset(scp); + return (ret); + } + } + + /*DEB(printf("seq_write: queueing event %d.\n", event[0]));*/ + /* Now we queue the event. */ + switch (seq_queue(scp, event)) { + case EAGAIN: + s = splmidi(); + /* The queue is full. Start playing now. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + splx(s); + return (0); + case EINTR: + return (EINTR); + case ERESTART: + return (ERESTART); + } + p += ev_size; + count -= ev_size; + } + + /* We have written every single data. Start playing now. */ + s = splmidi(); + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + splx(s); + + return (0); +} + +int +seq_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc *p) +{ + int unit, midiunit, ret, tmp, s, arg2; + sc_p scp; + seqdev_info *sd; + mididev_info *md; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + struct patmgr_info *patinfo; + snd_sync_parm *syncparm; + struct seq_event_rec *event; + struct snd_size *sndsize; + + unit = MIDIUNIT(i_dev); + + DEB(printf("seq%d: ioctlling, cmd 0x%x.\n", unit, (int)cmd)); + + if (unit >= NSEQ_MAX) { + DEB(printf("seq_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + DEB(printf("seq_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + ret = 0; + + switch (cmd) { + + /* + * we start with the new ioctl interface. + */ + case AIONWRITE: /* how many bytes can be written ? */ + *(int *)arg = sd->midi_dbuf_out.fl; + break; + + case AIOSSIZE: /* set the current blocksize */ + sndsize = (struct snd_size *)arg; + if (sndsize->play_size <= sd->midi_dbuf_out.unit_size && sndsize->rec_size <= sd->midi_dbuf_in.unit_size) { + sd->flags &= ~MIDI_F_HAS_SIZE; + sd->midi_dbuf_out.blocksize = sd->midi_dbuf_out.unit_size; + sd->midi_dbuf_in.blocksize = sd->midi_dbuf_in.unit_size; + } + else { + if (sndsize->play_size > sd->midi_dbuf_out.bufsize / 4) + sndsize->play_size = sd->midi_dbuf_out.bufsize / 4; + if (sndsize->rec_size > sd->midi_dbuf_in.bufsize / 4) + sndsize->rec_size = sd->midi_dbuf_in.bufsize / 4; + /* Round up the size to the multiple of EV_SZ. */ + sd->midi_dbuf_out.blocksize = + ((sndsize->play_size + sd->midi_dbuf_out.unit_size - 1) + / sd->midi_dbuf_out.unit_size) * sd->midi_dbuf_out.unit_size; + sd->midi_dbuf_in.blocksize = + ((sndsize->rec_size + sd->midi_dbuf_in.unit_size - 1) + / sd->midi_dbuf_in.unit_size) * sd->midi_dbuf_in.unit_size; + sd->flags |= MIDI_F_HAS_SIZE; + } + /* FALLTHROUGH */ + case AIOGSIZE: /* get the current blocksize */ + sndsize = (struct snd_size *)arg; + sndsize->play_size = sd->midi_dbuf_out.blocksize; + sndsize->rec_size = sd->midi_dbuf_in.blocksize; + + ret = 0; + break; + + case AIOSTOP: + if (*(int *)arg == AIOSYNC_PLAY) { + s = splmidi(); + + /* Stop writing. */ + sd->callback(sd, SEQ_CB_ABORT | SEQ_CB_WR); + + /* Pass the ioctl to the midi devices. */ + for (midiunit = 0 ; midiunit < nmidi + nsynth ; midiunit++) { + md = &midi_info[midiunit]; + if (MIDICONFED(md) && scp->midi_open[midiunit] && (md->flags & MIDI_F_WRITING) != 0) { + arg2 = *(int *)arg; + midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, (caddr_t)&arg2, mode, p); + } + } + + *(int *)arg = sd->midi_dbuf_out.rl; + splx(s); + } + else if (*(int *)arg == AIOSYNC_CAPTURE) { + s = splmidi(); + + /* Stop reading. */ + sd->callback(sd, SEQ_CB_ABORT | SEQ_CB_RD); + + /* Pass the ioctl to the midi devices. */ + for (midiunit = 0 ; midiunit < nmidi + nsynth ; midiunit++) { + md = &midi_info[midiunit]; + if (MIDICONFED(md) && scp->midi_open[midiunit] && (md->flags & MIDI_F_WRITING) != 0) { + arg2 = *(int *)arg; + midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, (caddr_t)&arg2, mode, p); + } + } + + *(int *)arg = sd->midi_dbuf_in.rl; + splx(s); + } + + ret = 0; + break; + + case AIOSYNC: + syncparm = (snd_sync_parm *)arg; + scp->sync_parm = *syncparm; + + /* XXX Should select(2) against us watch the blocksize, or sync_parm? */ + + ret = 0; + break; + + case SNDCTL_TMR_TIMEBASE: + case SNDCTL_TMR_TEMPO: + case SNDCTL_TMR_START: + case SNDCTL_TMR_STOP: + case SNDCTL_TMR_CONTINUE: + case SNDCTL_TMR_METRONOME: + case SNDCTL_TMR_SOURCE: +#if notyet + if (scp->seq_mode != SEQ_2) { + ret = EINVAL; + break; + } + ret = tmr->ioctl(tmr_no, cmd, arg); +#endif /* notyet */ + break; + case SNDCTL_TMR_SELECT: +#if notyet + if (scp->seq_mode != SEQ_2) { + ret = EINVAL; + break; + } +#endif /* notyet */ + scp->pending_timer = *(int *)arg; + if (scp->pending_timer < 0 || scp->pending_timer >= /*NTIMER*/1) { + scp->pending_timer = -1; + ret = EINVAL; + break; + } + *(int *)arg = scp->pending_timer; + ret = 0; + break; + case SNDCTL_SEQ_PANIC: + seq_panic(scp); + ret = 0; + break; + case SNDCTL_SEQ_SYNC: + if (mode == O_RDONLY) { + ret = 0; + break; + } + ret = seq_sync(scp); + break; + case SNDCTL_SEQ_RESET: + seq_reset(scp); + ret = 0; + break; + case SNDCTL_SEQ_TESTMIDI: + midiunit = *(int *)arg; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + md = &midi_info[midiunit]; + if (MIDICONFED(md) && !scp->midi_open[midiunit]) { + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc); + break; + } + ret = 0; + break; + case SNDCTL_SEQ_GETINCOUNT: + if (mode == O_WRONLY) + *(int *)arg = 0; + else + *(int *)arg = sd->midi_dbuf_in.rl; + ret = 0; + break; + case SNDCTL_SEQ_GETOUTCOUNT: + if (mode == O_RDONLY) + *(int *)arg = 0; + else + *(int *)arg = sd->midi_dbuf_out.fl; + ret = 0; + break; + case SNDCTL_SEQ_CTRLRATE: +#if notyet + if (scp->seq_mode != SEQ_2) { + ret = tmr->ioctl(tmr_no, cmd, arg); + break; + } +#endif /* notyet */ + if (*(int *)arg != 0) { + ret = EINVAL; + break; + } + *(int *)arg = hz; + ret = 0; + break; + case SNDCTL_SEQ_RESETSAMPLES: + midiunit = *(int *)arg; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + if (!scp->midi_open[midiunit]) { + md = &midi_info[midiunit]; + if (MIDICONFED(md)) { + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc); + if (ret != 0) + break; + } else { + ret = EBUSY; + break; + } + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + case SNDCTL_SEQ_NRSYNTHS: + *(int *)arg = nmidi + nsynth; + ret = 0; + break; + case SNDCTL_SEQ_NRMIDIS: + *(int *)arg = nmidi + nsynth; + ret = 0; + break; + case SNDCTL_SYNTH_MEMAVL: + midiunit = *(int *)arg; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + if (!scp->midi_open[midiunit]) { + md = &midi_info[midiunit]; + if (MIDICONFED(md)) { + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc); + if (ret != 0) + break; + } else { + ret = EBUSY; + break; + } + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + case SNDCTL_FM_4OP_ENABLE: + midiunit = *(int *)arg; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + if (!scp->midi_open[midiunit]) { + md = &midi_info[midiunit]; + if (MIDICONFED(md)) { + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc); + if (ret != 0) + break; + } else { + ret = EBUSY; + break; + } + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + midiunit = synthinfo->device; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + case SNDCTL_SEQ_OUTOFBAND: + event = (struct seq_event_rec *)arg; + s = splmidi(); + ret = seq_playevent(scp, event->arr); + splx(s); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + midiunit = midiinfo->device; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + case SNDCTL_PMGR_IFACE: + patinfo = (struct patmgr_info *)arg; + midiunit = patinfo->device; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + if (!scp->midi_open[midiunit]) { + ret = EBUSY; + break; + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + case SNDCTL_PMGR_ACCESS: + patinfo = (struct patmgr_info *)arg; + midiunit = patinfo->device; + if (midiunit >= nmidi + nsynth) { + ret = ENXIO; + break; + } + if (!scp->midi_open[midiunit]) { + md = &midi_info[midiunit]; + if (MIDICONFED(md)) { + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc); + if (ret != 0) + break; + } else { + ret = EBUSY; + break; + } + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), midiunit, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + case SNDCTL_SEQ_THRESHOLD: + tmp = *(int *)arg; + RANGE(tmp, 1, sd->midi_dbuf_out.bufsize - 1); + scp->output_threshould = tmp; + ret = 0; + break; + case SNDCTL_MIDI_PRETIME: + tmp = *(int *)arg; + if (tmp < 0) + tmp = 0; + tmp = (hz * tmp) / 10; + scp->pre_event_timeout = tmp; + ret = 0; + break; + default: + if (scp->fflags == O_RDONLY) { + ret = EIO; + break; + } + if (!scp->midi_open[0]) { + md = &midi_info[0]; + if (MIDICONFED(md)) { + ret = seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc); + if (ret != 0) + break; + } else { + ret = EBUSY; + break; + } + } + ret = midi_ioctl(MIDIMKDEV(major(i_dev), 0, SND_DEV_MIDIN), cmd, arg, mode, p); + break; + } + + return (ret); +} + +int +seq_poll(dev_t i_dev, int events, struct proc *p) +{ + int unit, ret, s, lim; + sc_p scp; + seqdev_info *sd; + + unit = MIDIUNIT(i_dev); + + DEB(printf("seq%d: polling.\n", unit)); + + if (unit >= NSEQ_MAX) { + DEB(printf("seq_poll: unit %d does not exist.\n", unit)); + return (ENXIO); + } + sd = get_seqdev_info(i_dev, &unit); + if (sd == NULL) { + DEB(printf("seq_poll: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = sd->softc; + + ret = 0; + s = splmidi(); + + /* Look up the apropriate queue and select it. */ + if ((events & (POLLOUT | POLLWRNORM)) != 0) { + /* Start playing. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + + /* Find out the boundary. */ + if ((sd->flags & SEQ_F_HAS_SIZE) != 0) + lim = sd->midi_dbuf_out.blocksize; + else + lim = sd->midi_dbuf_out.unit_size; + if (sd->midi_dbuf_out.fl < lim) + /* No enough space, record select. */ + selrecord(p, &sd->midi_dbuf_out.sel); + else + /* We can write now. */ + ret |= events & (POLLOUT | POLLWRNORM); + } + if ((events & (POLLIN | POLLRDNORM)) != 0) { + /* Start recording. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_RD); + + /* Find out the boundary. */ + if ((sd->flags & SEQ_F_HAS_SIZE) != 0) + lim = sd->midi_dbuf_in.blocksize; + else + lim = sd->midi_dbuf_in.unit_size; + if (sd->midi_dbuf_in.rl < lim) + /* No data ready, record select. */ + selrecord(p, &sd->midi_dbuf_in.sel); + else + /* We can write now. */ + ret |= events & (POLLIN | POLLRDNORM); + } + splx(s); + + return (ret); +} + +static void +seq_intr(void *p, mididev_info *md) +{ + sc_p scp; + seqdev_info *sd; + + sd = (seqdev_info *)p; + scp = sd->softc; + + /* Restart playing if we have the data to output. */ + if (scp->queueout_pending) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + /* Check the midi device if we are reading. */ + if ((sd->flags & SEQ_F_READING) != 0) + seq_midiinput(scp, md); +} + +static int +seq_callback(seqdev_info *sd, int reason) +{ + int unit; + sc_p scp; + + /*DEB(printf("seq_callback: reason 0x%x.\n", reason));*/ + + if (sd == NULL) { + DEB(printf("seq_callback: device not configured.\n")); + return (ENXIO); + } + scp = sd->softc; + unit = sd->unit; + + switch (reason & SEQ_CB_REASON_MASK) { + case SEQ_CB_START: + if ((reason & SEQ_CB_RD) != 0 && (sd->flags & SEQ_F_READING) == 0) + /* Begin recording. */ + sd->flags |= SEQ_F_READING; + if ((reason & SEQ_CB_WR) != 0 && (sd->flags & SEQ_F_WRITING) == 0) + /* Start playing. */ + seq_startplay(scp); + break; + case SEQ_CB_STOP: + case SEQ_CB_ABORT: + if ((reason & SEQ_CB_RD) != 0 && (sd->flags & SEQ_F_READING) != 0) { + /* Stop the timer. */ + scp->seq_time = seq_gettime(); + scp->prev_input_time = 0; + + /* Stop recording. */ + sd->flags &= ~SEQ_F_READING; + } + if ((reason & SEQ_CB_WR) != 0 && (sd->flags & SEQ_F_WRITING) != 0) { + /* Stop the timer. */ + seq_stoptimer(scp); + scp->seq_time = seq_gettime(); + scp->prev_event_time = 0; + + /* Stop Playing. */ + sd->flags &= ~SEQ_F_WRITING; + scp->queueout_pending = 0; + } + break; + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +static int +seq_queue(sc_p scp, u_char *note) +{ + int unit, err, s; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + /*DEB(printf("seq%d: queueing.\n", unit));*/ + + s = splmidi(); + + /* Start playing if we have some data in the queue. */ + if (sd->midi_dbuf_out.rl >= EV_SZ) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + + if (sd->midi_dbuf_out.fl < EV_SZ) { + /* We have no space. Start playing if not yet. */ + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + if ((sd->flags & SEQ_F_NBIO) != 0 && sd->midi_dbuf_out.fl < EV_SZ) { + /* We would block. */ + splx(s); + return (EAGAIN); + } else + while (sd->midi_dbuf_out.fl < EV_SZ) { + /* We have no space. Good night. */ + err = tsleep(&sd->midi_dbuf_out.tsleep_out, PRIBIO | PCATCH, "seqque", 0); + if (err == EINTR) + sd->callback(sd, SEQ_CB_STOP | SEQ_CB_WR); + if (err == EINTR || err == ERESTART) { + splx(s); + return (err); + } + } + } + + /* We now have enough space to write. */ + err = midibuf_seqwrite(&sd->midi_dbuf_out, note, EV_SZ); + + splx(s); + + if (err < 0) + err = -err; + else + err = 0; + + return (err); +} + +static void +seq_startplay(sc_p scp) +{ + int unit; + u_char event[EV_SZ]; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + /* Dequeue the events to play. */ + while (sd->midi_dbuf_out.rl >= EV_SZ) { + + /* We are playing now. */ + sd->flags |= SEQ_F_WRITING; + + /* We only copy the event, not dequeue. */ + midibuf_seqcopy(&sd->midi_dbuf_out, event, EV_SZ); + + switch (seq_playevent(scp, event)) { + case TIMERARMED: + /* Dequeue the event. */ + midibuf_seqread(&sd->midi_dbuf_out, event, EV_SZ); + /* FALLTHRU */ + case QUEUEFULL: + /* We cannot play further. */ + return; + case MORE: + /* Dequeue the event. */ + midibuf_seqread(&sd->midi_dbuf_out, event, EV_SZ); + break; + } + } + + /* Played every event in the queue. */ + sd->flags &= ~SEQ_F_WRITING; +} + +static int +seq_playevent(sc_p scp, u_char *event) +{ + int unit, ret; + long *delay; + seqdev_info *sd; + mididev_info *md; + + sd = scp->devinfo; + unit = sd->unit; + + md = &midi_info[0]; + if (!MIDICONFED(md)) + return (MORE); + + switch(event[0]) { + case SEQ_NOTEOFF: + if ((md->flags & MIDI_F_BUSY) != 0 || seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) == 0) + if (md->synth.killnote(md, event[1], 255, event[3]) == EAGAIN) { + ret = QUEUEFULL; + break; + } + ret = MORE; + break; + case SEQ_NOTEON: + if (((md->flags & MIDI_F_BUSY) != 0 || seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) == 0) + && (event[4] < 128 || event[4] == 255)) + if (md->synth.startnote(md, event[1], event[2], event[3]) == EAGAIN) { + ret = QUEUEFULL; + break; + } + ret = MORE; + break; + case SEQ_WAIT: + + /* Extract the delay. */ + delay = (long *)event; + *delay = (*delay >> 8) & 0xffffff; + if (*delay > 0) { + /* Arm the timer. */ + sd->flags |= SEQ_F_WRITING; + scp->prev_event_time = *delay; + if (seq_requesttimer(scp, *delay)) { + ret = TIMERARMED; + break; + } + } + ret = MORE; + break; + case SEQ_PGMCHANGE: + if ((md->flags & MIDI_F_BUSY) != 0 || seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) == 0) + if (md->synth.setinstr(md, event[1], event[2]) == EAGAIN) { + ret = QUEUEFULL; + break; + } + ret = MORE; + break; + case SEQ_SYNCTIMER: + /* Reset the timer. */ + scp->seq_time = seq_gettime(); + scp->prev_input_time = 0; + scp->prev_event_time = 0; + scp->prev_wakeup_time = scp->seq_time; + ret = MORE; + break; + case SEQ_MIDIPUTC: + /* Pass through to the midi device. */ + if (event[2] < nmidi + nsynth) { + md = &midi_info[event[2]]; + if (MIDICONFED(md) && ((md->flags & MIDI_F_BUSY) != 0 || seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) == 0)) { + if (md->synth.writeraw(md, &event[1], sizeof(event[1]), 1) == EAGAIN) { + /* The queue was full. Try again later. */ + ret = QUEUEFULL; + break; + } + } + } + ret = MORE; + break; + case SEQ_ECHO: + /* Echo this event back. */ + if (seq_copytoinput(scp, event, 4) == EAGAIN) { + ret = QUEUEFULL; + break; + } + ret = MORE; + break; + case SEQ_PRIVATE: + if (event[1] < nmidi + nsynth) { + md = &midi_info[event[1]]; + if (MIDICONFED(md) && md->synth.hwcontrol(md, event) == EAGAIN) { + ret = QUEUEFULL; + break; + } + } + ret = MORE; + break; + case SEQ_EXTENDED: + ret = seq_extended(scp, event); + break; + case EV_CHN_VOICE: + ret = seq_chnvoice(scp, event); + break; + case EV_CHN_COMMON: + ret = seq_chncommon(scp, event); + break; + case EV_TIMING: + ret = seq_timing(scp, event); + break; + case EV_SEQ_LOCAL: + ret = seq_local(scp, event); + break; + case EV_SYSEX: + ret = seq_sysex(scp, event); + break; + default: + ret = MORE; + break; + } + + switch (ret) { + case QUEUEFULL: + /*DEB(printf("seq_playevent: the queue is full.\n"));*/ + /* The queue was full. Try again on the interrupt by the midi device. */ + sd->flags |= SEQ_F_WRITING; + scp->queueout_pending = 1; + break; + case TIMERARMED: + sd->flags |= SEQ_F_WRITING; + /* FALLTHRU */ + case MORE: + scp->queueout_pending = 0; + break; + } + + return (ret); +} + +static u_long +seq_gettime(void) +{ + struct timeval timecopy; + + getmicrotime(&timecopy); + return timecopy.tv_usec / (1000000 / hz) + (u_long) timecopy.tv_sec * hz; +} + +static int +seq_requesttimer(sc_p scp, int delay) +{ + u_long cur_time, rel_base; + + /*DEB(printf("seq%d: requested timer at delay of %d.\n", unit, delay));*/ + + cur_time = seq_gettime(); + + if (delay < 0) + /* Request a new timer. */ + delay = -delay; + else { + rel_base = cur_time - scp->seq_time; + if (delay <= rel_base) + return 0; + delay -= rel_base; + } + +#if notdef + /* + * Compensate the delay of midi message transmission. + * XXX Do we have to consider the accumulation of errors + * less than 1/hz second? + */ + delay -= (cur_time - scp->prev_wakeup_time); + if (delay < 1) { + printf("sequencer: prev = %lu, cur = %lu, delay = %d, skip sleeping.\n", + scp->prev_wakeup_time, cur_time, delay); + seq_stoptimer(scp); + return 0; + } +#endif /* notdef */ + + scp->timeout_ch = timeout(seq_timer, (void *)scp, delay); + scp->timer_running = 1; + return 1; +} + +static void +seq_stoptimer(sc_p scp) +{ + /*DEB(printf("seq%d: stopping timer.\n", unit));*/ + + if (scp->timer_running) { + untimeout(seq_timer, (void *)scp, scp->timeout_ch); + scp->timer_running = 0; + } +} + +static void +seq_midiinput(sc_p scp, mididev_info *md) +{ + int unit, midiunit; + u_long tstamp; + u_char event[4]; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + /* Can this midi device interrupt for input? */ + midiunit = md->unit; + if (scp->midi_open[midiunit] + && (md->flags & MIDI_F_READING) != 0 + && md->intrarg == sd) + /* Read the input data. */ + while (md->synth.readraw(md, &event[1], sizeof(event[1]), 1) == 0) { + tstamp = seq_gettime() - scp->seq_time; + if (tstamp != scp->prev_input_time) { + /* Insert a wait between events. */ + tstamp = (tstamp << 8) | SEQ_WAIT; + seq_copytoinput(scp, (u_char *)&tstamp, 4); + scp->prev_input_time = tstamp; + } + event[0] = SEQ_MIDIPUTC; + event[2] = midiunit; + event[3] = 0; + seq_copytoinput(scp, event, sizeof(event)); + } +} + +static int +seq_copytoinput(sc_p scp, u_char *event, int len) +{ + seqdev_info *sd; + + sd = scp->devinfo; + + if (midibuf_input_intr(&sd->midi_dbuf_in, event, len) == -EAGAIN) + return (EAGAIN); + + return (0); +} + +static int +seq_extended(sc_p scp, u_char *event) +{ + int unit; + seqdev_info *sd; + mididev_info *md; + + sd = scp->devinfo; + unit = sd->unit; + + if (event[2] >= nmidi + nsynth) + return (MORE); + md = &midi_info[event[2]]; + if (!MIDICONFED(md) && (md->flags & MIDI_F_BUSY) == 0 && seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) != 0) + return (MORE); + + switch (event[1]) { + case SEQ_NOTEOFF: + if (md->synth.killnote(md, event[3], event[4], event[5]) == EAGAIN) + return (QUEUEFULL); + break; + case SEQ_NOTEON: + if (event[4] < 128 || event[4] == 255) + if (md->synth.startnote(md, event[3], event[4], event[5]) == EAGAIN) + return (QUEUEFULL); + break; + case SEQ_PGMCHANGE: + if (md->synth.setinstr(md, event[3], event[4]) == EAGAIN) + return (QUEUEFULL); + break; + case SEQ_AFTERTOUCH: + if (md->synth.aftertouch(md, event[3], event[4]) == EAGAIN) + return (QUEUEFULL); + break; + case SEQ_BALANCE: + if (md->synth.panning(md, event[3], (char)event[4]) == EAGAIN) + return (QUEUEFULL); + break; + case SEQ_CONTROLLER: + if (md->synth.controller(md, event[3], event[4], *(short *)&event[5]) == EAGAIN) + return (QUEUEFULL); + break; + case SEQ_VOLMODE: + if (md->synth.volumemethod != NULL) + if (md->synth.volumemethod(md, event[3]) == EAGAIN) + return (QUEUEFULL); + break; + } + + return (MORE); +} + +static int +seq_chnvoice(sc_p scp, u_char *event) +{ + int voice; + seqdev_info *sd; + mididev_info *md; + u_char dev, cmd, chn, note, parm; + + voice = -1; + dev = event[1]; + cmd = event[2]; + chn = event[3]; + note = event[4]; + parm = event[5]; + + sd = scp->devinfo; + + if (dev >= nmidi + nsynth) + return (MORE); + md = &midi_info[dev]; + if (!MIDICONFED(md) && (md->flags & MIDI_F_BUSY) == 0 && seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) != 0) + return (MORE); + +#if notyet + if (scp->seq_mode == SEQ_2) { + if (md->synth.allocvoice) + voice = seq_allocvoice(md, chn, note); + } +#endif /* notyet */ + switch (cmd) { + case MIDI_NOTEON: + if (note < 128 || note == 255) { +#if notyet + if (voice == -1 && scp->seq_mode == SEQ_2 && md->synth.allocvoice) + /* This is an internal synthesizer. (FM, GUS, etc) */ + if ((voice = seq_allocvoice(md, chn, note)) == -EAGAIN) + return (QUEUEFULL); +#endif /* notyet */ + if (voice == -1) + voice = chn; + +#if notyet + if (scp->seq_mode == SEQ_2 && dev < nmidi + nsynth && chn == 9) { + /* This channel is a percussion. The note number is the patch number. */ + if (md->synth.setinstr(md, voice, 128 + note) == EAGAIN) + return (QUEUEFULL); + note = 60; /* Middle C. */ + } + if (scp->seq_mode == SEQ_2) + if (md->synth.setupvoice(md, voice, chn) == EAGAIN) + return (QUEUEFULL); +#endif /* notyet */ + if (md->synth.startnote(md, voice, note, parm) == EAGAIN) + return (QUEUEFULL); + } + break; + case MIDI_NOTEOFF: + if (voice == -1) + voice = chn; + if (md->synth.killnote(md, voice, note, parm) == EAGAIN) + return (QUEUEFULL); + break; + case MIDI_KEY_PRESSURE: + if (voice == -1) + voice = chn; + if (md->synth.aftertouch(md, voice, parm) == EAGAIN) + return (QUEUEFULL); + break; + } + + return (MORE); +} + +static int +seq_findvoice(mididev_info *md, int chn, int note) +{ + int i; + u_short key; + + key = (chn << 8) | (note + 1); + + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) + if (md->synth.alloc.map[i] == key) + return (i); + + return (-1); +} + +static int +seq_allocvoice(mididev_info *md, int chn, int note) +{ + int voice; + u_short key; + + key = (chn << 8) | (note + 1); + + if ((voice = md->synth.allocvoice(md, chn, note, &md->synth.alloc)) == -EAGAIN) + return (-EAGAIN); + md->synth.alloc.map[voice] = key; + md->synth.alloc.alloc_times[voice] = md->synth.alloc.timestamp++; + + return (voice); +} + +static int +seq_chncommon(sc_p scp, u_char *event) +{ + int unit/*, i, val, key*/; + u_short w14; + u_char dev, cmd, chn, p1; + seqdev_info *sd; + mididev_info *md; + + dev = event[1]; + cmd = event[2]; + chn = event[3]; + p1 = event[4]; + w14 = *(u_short *)&event[6]; + + sd = scp->devinfo; + unit = sd->unit; + + if (dev >= nmidi + nsynth) + return (MORE); + md = &midi_info[dev]; + if (!MIDICONFED(md) && (md->flags & MIDI_F_BUSY) == 0 && seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) != 0) + return (MORE); + + switch (cmd) { + case MIDI_PGM_CHANGE: +#if notyet + if (scp->seq_mode == SEQ_2) { + md->synth.chn_info[chn].pgm_num = p1; + if (dev < nmidi + nsynth) + if (md->synth.setinstr(md, chn, p1) == EAGAIN) + return (QUEUEFULL); + } else +#endif /* notyet */ + /* For Mode 1. */ + if (md->synth.setinstr(md, chn, p1) == EAGAIN) + return (QUEUEFULL); + break; + case MIDI_CTL_CHANGE: +#if notyet + if (scp->seq_mode == SEQ_2) { + if (chn < 16 && p1 < 128) { + md->synth.chn_info[chn].controllers[p1] = w14 & 0x7f; + if (p1 < 32) + /* We have set the MSB, clear the LSB. */ + md->synth.chn_info[chn].controllers[p1 + 32] = 0; + if (dev < nmidi + nsynth) { + val = w14 & 0x7f; + if (p1 < 64) { + /* Combine the MSB and the LSB. */ + val = ((md->synth.chn_info[chn].controllers[p1 & ~32] & 0x7f) << 7) + | (md->synth.chn_info[chn].controllers[p1 | 32] & 0x7f); + p1 &= ~32; + } + /* Handle all of the notes playing on this channel. */ + key = ((int)chn << 8); + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) + if ((md->synth.alloc.map[i] & 0xff00) == key) + if (md->synth.controller(md, i, p1, val) == EAGAIN) + return (QUEUEFULL); + } else + if (md->synth.controller(md, chn, p1, w14) == EAGAIN) + return (QUEUEFULL); + } + } else +#endif /* notyet */ + /* For Mode 1. */ + if (md->synth.controller(md, chn, p1, w14) == EAGAIN) + return (QUEUEFULL); + break; + case MIDI_PITCH_BEND: +#if notyet + if (scp->seq_mode == SEQ_2) { + md->synth.chn_info[chn].bender_value = w14; + if (dev < nmidi + nsynth) { + /* Handle all of the notes playing on this channel. */ + key = ((int)chn << 8); + for (i = 0 ; i < md->synth.alloc.max_voice ; i++) + if ((md->synth.alloc.map[i] & 0xff00) == key) + if (md->synth.bender(md, i, w14) == EAGAIN) + return (QUEUEFULL); + } else + if (md->synth.bender(md, chn, w14) == EAGAIN) + return (QUEUEFULL); + } else +#endif /* notyet */ + /* For Mode 1. */ + if (md->synth.bender(md, chn, w14) == EAGAIN) + return (QUEUEFULL); + break; + } + + return (MORE); +} + +static int +seq_timing(sc_p scp, u_char *event) +{ + int unit/*, ret*/; + long parm; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + parm = *(long *)&event[4]; + +#if notyet + if (scp->seq_mode == SEQ_2 && (ret = tmr->event(tmr_no, event)) == TIMERARMED) + return (ret); +#endif /* notyet */ + switch (event[1]) { + case TMR_WAIT_REL: + parm += scp->prev_event_time; + /* FALLTHRU */ + case TMR_WAIT_ABS: + if (parm > 0) { + sd->flags |= SEQ_F_WRITING; + scp->prev_event_time = parm; + if (seq_requesttimer(scp, parm)) + return (TIMERARMED); + } + break; + case TMR_START: + scp->seq_time = seq_gettime(); + scp->prev_input_time = 0; + scp->prev_event_time = 0; + scp->prev_wakeup_time = scp->seq_time; + break; + case TMR_STOP: + break; + case TMR_CONTINUE: + break; + case TMR_TEMPO: + break; + case TMR_ECHO: +#if notyet + if (scp->seq_mode == SEQ_2) + seq_copytoinput(scp, event, 8); + else { +#endif /* notyet */ + parm = (parm << 8 | SEQ_ECHO); + seq_copytoinput(scp, (u_char *)&parm, 4); +#if notyet + } +#endif /* notyet */ + break; + } + + return (MORE); +} + +static int +seq_local(sc_p scp, u_char *event) +{ + int unit; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + switch (event[1]) { + case LOCL_STARTAUDIO: +#if notyet + DMAbuf_start_devices(*(u_int *)&event[4]); +#endif /* notyet */ + break; + } + + return (MORE); +} + +static int +seq_sysex(sc_p scp, u_char *event) +{ + int unit, i, l; + seqdev_info *sd; + mididev_info *md; + + sd = scp->devinfo; + unit = sd->unit; + + if (event[1] >= nmidi + nsynth) + return (MORE); + md = &midi_info[event[1]]; + if (!MIDICONFED(md) || md->synth.sendsysex == NULL + || ((md->flags & MIDI_F_BUSY) == 0 && seq_openmidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc) != 0)) + return (MORE); + + l = 0; + for (i = 0 ; i < 6 && event[i + 2] != 0xff ; i++) + l = i + 1; + if (l > 0) + if (md->synth.sendsysex(md, &event[2], l) == EAGAIN) + return (QUEUEFULL); + + return (MORE); +} + +static void +seq_timer(void *arg) +{ + sc_p scp; + + scp = arg; + + /*DEB(printf("seq_timer: timer fired.\n"));*/ + + /* Record the current timestamp. */ + scp->prev_wakeup_time = seq_gettime(); + + seq_startplay(scp); +} + +static int +seq_openmidi(sc_p scp, mididev_info *md, int flags, int mode, struct proc *p) +{ + int midiunit, s, err; + + if (md == NULL || !MIDICONFED(md)) { + DEB(printf("seq_openmidi: midi device does not exist.\n")); + return (ENXIO); + } + midiunit = md->unit; + + DEB(printf("seq_openmidi: opening midi unit %d.\n", midiunit)); + + if (!scp->midi_open[midiunit]) { + err = midi_open(MIDIMKDEV(MIDI_CDEV_MAJOR, midiunit, SND_DEV_MIDIN), flags, mode, p); + if (err != 0) { + printf("seq_openmidi: failed to open midi device %d.\n", midiunit); + return (err); + } + s = splmidi(); + scp->midi_open[midiunit] = 1; + md->intr = seq_intr; + md->intrarg = scp->devinfo; + md->synth.prev_out_status = 0; + md->synth.sysex_state = 0; + splx(s); + } + + return (0); +} + +static int +seq_closemidi(sc_p scp, mididev_info *md, int flags, int mode, struct proc *p) +{ + int midiunit, s; + + if (md == NULL || !MIDICONFED(md)) { + DEB(printf("seq_closemidi: midi device does not exist.\n")); + return (ENXIO); + } + midiunit = md->unit; + + DEB(printf("seq_closemidi: closing midi unit %d.\n", midiunit)); + + if (scp->midi_open[midiunit]) { + midi_close(MIDIMKDEV(MIDI_CDEV_MAJOR, midiunit, SND_DEV_MIDIN), flags, mode, p); + s = splmidi(); + scp->midi_open[midiunit] = 0; + md->intr = NULL; + md->intrarg = NULL; + splx(s); + } + + return (0); +} + +static void +seq_panic(sc_p scp) +{ + seq_reset(scp); +} + +static int +seq_reset(sc_p scp) +{ + int unit, i, s, chn; + seqdev_info *sd; + mididev_info *md; + u_char c[3]; + + sd = scp->devinfo; + unit = sd->unit; + + s = splmidi(); + + /* Stop reading and writing. */ + sd->callback(sd, SEQ_CB_ABORT | SEQ_CB_RD | SEQ_CB_WR); + + /* Clear the queues. */ + midibuf_init(&sd->midi_dbuf_in); + midibuf_init(&sd->midi_dbuf_out); + +#if notyet + /* Reset the synthesizers. */ + for (i = 0 ; i < nmidi + nsynth ; i++) { + md = &midi_info[i]; + if (MIDICONFED(md) && scp->midi_open[i]) + md->synth.reset(md); + } +#endif /* notyet */ + +#if notyet + if (scp->seq_mode == SEQ_2) { + for (chn = 0 ; chn < 16 ; chn++) + for (i = 0 ; i < nmidi + nsynth ; i++) + if (midi_open[i]) { + md = &midi_info[i]; + if (!MIDICONFED(md)) + continue; + if (md->synth.controller(md, chn, 123, 0) == EAGAIN /* All notes off. */ + || md->synth.controller(md, chn, 121, 0) == EAGAIN /* Reset all controllers. */ + || md->synth.bender(md, chn, 1 << 13) == EAGAIN) { /* Reset pitch bend. */ + splx(s); + return (EAGAIN); + } + } + splx(s); + } else { +#endif /* notyet */ + splx(s); + for (i = 0 ; i < nmidi + nsynth ; i++) { + md = &midi_info[i]; + if (!MIDICONFED(md)) + continue; + + /* Send active sensing. */ + c[0] = 0xfe; /* Active Sensing. */ + if (md->synth.writeraw(md, c, 1, 0) == EAGAIN) + return (EAGAIN); + /* + * We need a sleep to reset a midi device using an active sensing. + * SC-88 resets after 420ms... + */ + tsleep(md, PRIBIO, "seqrst", 500 * hz / 1000); + for (chn = 0 ; chn < 16 ; chn++) { + c[0] = 0xb0 | (chn & 0x0f); + c[1] = (u_char)0x78; /* All sound off */ + c[2] = (u_char)0; + if (md->synth.writeraw(md, c, 3, 0) == EAGAIN) + return (EAGAIN); + c[1] = (u_char)0x7b; /* All note off */ + if (md->synth.writeraw(md, c, 3, 0) == EAGAIN) + return (EAGAIN); + c[1] = (u_char)0x79; /* Reset all controller */ + if (md->synth.writeraw(md, c, 3, 0) == EAGAIN) + return (EAGAIN); + } + } + for (i = 0 ; i < nmidi + nsynth ; i++){ + md = &midi_info[i]; + if (MIDICONFED(md)) + seq_closemidi(scp, md, scp->fflags, MIDIDEV_MODE, curproc); + } +#if notyet + } +#endif /* notyet */ + + return (0); +} + +static int +seq_sync(sc_p scp) +{ + int unit, s, i; + seqdev_info *sd; + + sd = scp->devinfo; + unit = sd->unit; + + s = splmidi(); + + if (sd->midi_dbuf_out.rl >= EV_SZ) + sd->callback(sd, SEQ_CB_START | SEQ_CB_WR); + + while ((sd->flags & SEQ_F_WRITING) != 0 && sd->midi_dbuf_out.rl >= EV_SZ) { + i = tsleep(&sd->midi_dbuf_out.tsleep_out, PRIBIO | PCATCH, "seqsnc", 0); + if (i == EINTR) + sd->callback(sd, SEQ_CB_STOP | SEQ_CB_WR); + if (i == EINTR || i == ERESTART) { + splx(s); + return (i); + } + } + splx(s); + + return (0); +} + +/* + * a small utility function which, given a device number, returns + * a pointer to the associated seqdev_info struct, and sets the unit + * number. + */ +static seqdev_info * +get_seqdev_info(dev_t i_dev, int *unit) +{ + int u; + seqdev_info *d = NULL ; + + if (MIDIDEV(i_dev) != SND_DEV_SEQ && MIDIDEV(i_dev) != SND_DEV_SEQ2) + return NULL; + u = MIDIUNIT(i_dev); + if (unit) + *unit = u ; + + if (u >= NSEQ_MAX) { + DEB(printf("get_seqdev_info: unit %d is not configured.\n", u)); + return NULL; + } + d = &seq_info[u]; + + return d ; +} + +/* XXX These functions are actually redundant. */ +static int +seqopen(dev_t i_dev, int flags, int mode, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + return seq_open(i_dev, flags, mode, p); + } + + return (ENXIO); +} + +static int +seqclose(dev_t i_dev, int flags, int mode, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + return seq_close(i_dev, flags, mode, p); + } + + return (ENXIO); +} + +static int +seqread(dev_t i_dev, struct uio * buf, int flag) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + return seq_read(i_dev, buf, flag); + } + + return (ENXIO); +} + +static int +seqwrite(dev_t i_dev, struct uio * buf, int flag) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + return seq_write(i_dev, buf, flag); + } + + return (ENXIO); +} + +static int +seqioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + return seq_ioctl(i_dev, cmd, arg, mode, p); + } + + return (ENXIO); +} + +static int +seqpoll(dev_t i_dev, int events, struct proc * p) +{ + switch (MIDIDEV(i_dev)) { + case MIDI_DEV_SEQ: + return seq_poll(i_dev, events, p); + } + + return (ENXIO); +} + +static int +seq_modevent(module_t mod, int type, void *data) +{ + int retval; + + retval = 0; + + switch (type) { + case MOD_LOAD: + seq_init(); + break; + + case MOD_UNLOAD: + printf("sequencer: unload not supported yet.\n"); + retval = EOPNOTSUPP; + break; + + default: + break; + } + + return retval; +} + +DEV_MODULE(seq, seq_modevent, NULL); diff --git a/sys/dev/sound/midi/sequencer.h b/sys/dev/sound/midi/sequencer.h new file mode 100644 index 0000000..7275b77 --- /dev/null +++ b/sys/dev/sound/midi/sequencer.h @@ -0,0 +1,234 @@ +/* + * Include file for midi sequencer driver. + * + * Copyright by Seigo Tanimura 1999. + * + * 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 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. + * + * $FreeBSD$ + * + */ + +/* + * first, include kernel header files. + */ + +#ifndef _SEQUENCER_H_ +#define _SEQUENCER_H_ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/ioccom.h> + +#include <sys/filio.h> +#include <sys/sockio.h> +#include <sys/fcntl.h> +#include <sys/tty.h> +#include <sys/proc.h> + +#include <sys/kernel.h> /* for DATA_SET */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <machine/clock.h> /* for DELAY */ +#include <sys/soundcard.h> + +#define SEQ_CDEV_MAJOR MIDI_CDEV_MAJOR + +/* + * the following assumes that FreeBSD 3.X uses poll(2) instead of select(2). + * This change dates to late 1997. + */ +#include <sys/poll.h> +#define d_select_t d_poll_t + +typedef struct _seqdev_info seqdev_info; + +typedef int (seq_callback_t)(seqdev_info *sd, int reason); + +/* + * descriptor of sequencer operations ... + * + */ + +struct _seqdev_info { + + /* + * the first part of the descriptor is filled up from a + * template. + */ + char name[64]; + + int type ; + + d_open_t *open; + d_close_t *close; + d_read_t *read; + d_write_t *write; + d_ioctl_t *ioctl; + d_poll_t *poll; + seq_callback_t *callback; + + /* + * combinations of the following flags are used as second argument in + * the callback from the dma module to the device-specific routines. + */ + +#define SEQ_CB_RD 0x100 /* read callback */ +#define SEQ_CB_WR 0x200 /* write callback */ +#define SEQ_CB_REASON_MASK 0xff +#define SEQ_CB_START 0x01 /* start dma op */ +#define SEQ_CB_STOP 0x03 /* stop dma op */ +#define SEQ_CB_ABORT 0x04 /* abort dma op */ +#define SEQ_CB_INIT 0x05 /* init board parameters */ + + /* + * callback extensions + */ +#define SEQ_CB_DMADONE 0x10 +#define SEQ_CB_DMAUPDATE 0x11 +#define SEQ_CB_DMASTOP 0x12 + + /* init can only be called with int enabled and + * no pending DMA activity. + */ + + /* + * whereas from here, parameters are set at runtime. + * io_base == 0 means that the board is not configured. + */ + + int unit; /* unit number of the device */ + void *softc; /* softc for a device */ + + int bd_id ; /* used to hold board-id info, eg. sb version, + * mss codec type, etc. etc. + */ + + midi_dbuf midi_dbuf_in; /* midi input event/message queue */ + midi_dbuf midi_dbuf_out; /* midi output event/message queue */ + + /* + * these parameters describe the operation of the board. + * Generic things like busy flag, speed, etc are here. + */ + + volatile u_long flags ; /* 32 bits, used for various purposes. */ + + /* + * we have separate flags for read and write, although in some + * cases this is probably not necessary (e.g. because we cannot + * know how many processes are using the device, we cannot + * distinguish if open, close, abort are for a write or for a + * read). + */ + + /* + * the following flag is used by open-close routines + * to mark the status of the device. + */ +#define SEQ_F_BUSY 0x0001 /* has been opened */ + /* + * the next two are used to allow only one pending operation of + * each type. + */ +#define SEQ_F_READING 0x0004 /* have a pending read */ +#define SEQ_F_WRITING 0x0008 /* have a pending write */ + + /* + * flag used to mark a pending close. + */ +#define SEQ_F_CLOSING 0x0040 /* a pending close */ + + /* + * if user has not set block size, then make it adaptive + * (0.25s, or the perhaps last read/write ?) + */ +#define SEQ_F_HAS_SIZE 0x0080 /* user set block size */ + /* + * assorted flags related to operating mode. + */ +#define SEQ_F_STEREO 0x0100 /* doing stereo */ +#define SEQ_F_NBIO 0x0200 /* do non-blocking i/o */ + + /* + * these flags mark a pending abort on a r/w operation. + */ +#define SEQ_F_ABORTING 0x1000 /* a pending abort */ + + /* + * this is used to mark that board initialization is needed, e.g. + * because of a change in sampling rate, format, etc. -- It will + * be done at the next convenient time. + */ +#define SEQ_F_INIT 0x4000 /* changed parameters. need init */ + + int play_blocksize, rec_blocksize; /* blocksize for io and dma ops */ + +#define swsel midi_dbuf_out.sel +#define srsel midi_dbuf_in.sel + u_long interrupts; /* counter of interrupts */ + u_long magic; +#define MAGIC(unit) ( 0xa4d10de0 + unit ) + void *device_data ; /* just in case it is needed...*/ +} ; + + +/* + * then ioctls and other stuff + */ +#define NSEQ_MAX 64 /* Number of supported devices */ + +/* + * many variables should be reduced to a range. Here define a macro + */ + +#define RANGE(var, low, high) (var) = \ +((var)<(low)?(low) : (var)>(high)?(high) : (var)) + +/* + * finally, all default parameters + */ +#define SEQ_BUFFSIZE (4 * 1024) /* XXX */ + +/* + * some macros for debugging purposes + * DDB/DEB to enable/disable debugging stuff + * BVDDB to enable debugging when bootverbose + */ +#define DDB(x) x /* XXX */ +#define BVDDB(x) if (bootverbose) x + +#ifndef DEB +#define DEB(x) +#endif + + extern seqdev_info seq_info[NSEQ_MAX] ; + +#define MIDI_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ + +#endif /* _SEQUENCER_H_ */ diff --git a/sys/dev/sound/pci/csa.c b/sys/dev/sound/pci/csa.c index b474574..7d51311 100644 --- a/sys/dev/sound/pci/csa.c +++ b/sys/dev/sound/pci/csa.c @@ -59,11 +59,9 @@ struct csa_softc { device_t pcm; /* pcm device */ driver_intr_t* pcmintr; /* pcm intr */ void *pcmintr_arg; /* pcm intr arg */ -#if notyet device_t midi; /* midi device */ driver_intr_t* midiintr; /* midi intr */ void *midiintr_arg; /* midi intr arg */ -#endif /* notyet */ void *ih; /* cookie */ struct csa_bridgeinfo binfo; /* The state of this bridge. */ @@ -198,7 +196,6 @@ csa_attach(device_t dev) scp->pcm = device_add_child(dev, "pcm", -1); device_set_ivars(scp->pcm, func); -#if notyet /* Midi Interface */ func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); if (func == NULL) @@ -208,7 +205,6 @@ csa_attach(device_t dev) func->func = SCF_MIDI; scp->midi = device_add_child(dev, "midi", -1); device_set_ivars(scp->midi, func); -#endif /* notyet */ bus_generic_attach(dev); @@ -292,12 +288,10 @@ csa_setup_intr(device_t bus, device_t child, scp->pcmintr_arg = arg; break; -#if notyet case SCF_MIDI: scp->midiintr = intr; scp->midiintr_arg = arg; break; -#endif /* notyet */ default: return (EINVAL); @@ -334,12 +328,10 @@ csa_teardown_intr(device_t bus, device_t child, scp->pcmintr_arg = NULL; break; -#if notyet case SCF_MIDI: scp->midiintr = NULL; scp->midiintr_arg = NULL; break; -#endif /* notyet */ default: return (EINVAL); @@ -375,10 +367,8 @@ csa_intr(void *arg) /* Invoke the handlers of the children. */ if ((hisr & (HISR_VC0 | HISR_VC1)) != 0 && scp->pcmintr != NULL) scp->pcmintr(scp->pcmintr_arg); -#if notyet if ((hisr & HISR_MIDI) != 0 && scp->midiintr != NULL) scp->midiintr(scp->midiintr_arg); -#endif /* notyet */ /* Throw an eoi. */ csa_writeio(resp, BA0_HICR, HICR_IEV | HICR_CHGM); diff --git a/sys/dev/sound/pci/csamidi.c b/sys/dev/sound/pci/csamidi.c new file mode 100644 index 0000000..39ecda6 --- /dev/null +++ b/sys/dev/sound/pci/csamidi.c @@ -0,0 +1,554 @@ +/*- + * Copyright (c) 1999 Seigo Tanimura + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 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 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. + * + * $FreeBSD$ + */ + +#include "opt_devfs.h" + +#include <dev/sound/midi/midi.h> +#include <dev/sound/chip.h> +#include <dev/sound/pci/csareg.h> +#include <machine/cpufunc.h> + +static devclass_t midi_devclass; + +#ifndef DDB +#undef DDB +#define DDB(x) +#endif /* DDB */ + +#define CSAMIDI_RESET 0xff +#define CSAMIDI_UART 0x3f +#define CSAMIDI_ACK 0xfe + +#define CSAMIDI_STATMASK 0xc0 +#define CSAMIDI_OUTPUTBUSY 0x40 +#define CSAMIDI_INPUTBUSY 0x80 + +#define CSAMIDI_TRYDATA 50 +#define CSAMIDI_DELAY 25000 + +extern synthdev_info midisynth_op_desc; + +/* These are the synthesizer and the midi interface information. */ +static struct synth_info csamidi_synthinfo = { + "CS461x MIDI", + 0, + SYNTH_TYPE_MIDI, + 0, + 0, + 128, + 128, + 128, + SYNTH_CAP_INPUT, +}; + +static struct midi_info csamidi_midiinfo = { + "CS461x MIDI", + 0, + 0, + 0, +}; + +/* + * These functions goes into csamidi_op_desc to get called + * from sound.c. + */ + +static int csamidi_probe(device_t dev); +static int csamidi_attach(device_t dev); + +static d_ioctl_t csamidi_ioctl; +static driver_intr_t csamidi_intr; +static midi_callback_t csamidi_callback; + +/* Here is the parameter structure per a device. */ +struct csamidi_softc { + device_t dev; /* device information */ + mididev_info *devinfo; /* midi device information */ + struct csa_bridgeinfo *binfo; /* The state of the parent. */ + + struct resource *io; /* Base of io map */ + int io_rid; /* Io map resource ID */ + struct resource *mem; /* Base of memory map */ + int mem_rid; /* Memory map resource ID */ + struct resource *irq; /* Irq */ + int irq_rid; /* Irq resource ID */ + void *ih; /* Interrupt cookie */ + + int fflags; /* File flags */ +}; + +typedef struct csamidi_softc *sc_p; + +/* These functions are local. */ +static void csamidi_startplay(sc_p scp); +static void csamidi_xmit(sc_p scp); +static int csamidi_reset(sc_p scp); +static int csamidi_status(sc_p scp); +static int csamidi_command(sc_p scp, u_int32_t value); +static int csamidi_readdata(sc_p scp); +static int csamidi_writedata(sc_p scp, u_int32_t value); +static u_int32_t csamidi_readio(sc_p scp, u_long offset); +static void csamidi_writeio(sc_p scp, u_long offset, u_int32_t data); +static u_int32_t csamidi_readmem(sc_p scp, u_long offset); +static void csamidi_writemem(sc_p scp, u_long offset, u_int32_t data); +static int csamidi_allocres(sc_p scp, device_t dev); +static void csamidi_releaseres(sc_p scp, device_t dev); + +/* + * This is the device descriptor for the midi device. + */ +static mididev_info csamidi_op_desc = { + "CS461x midi", + + SNDCARD_MPU401, + + NULL, + NULL, + NULL, + NULL, + csamidi_ioctl, + NULL, + + csamidi_callback, + + MIDI_BUFFSIZE, /* Queue Length */ + + 0, /* XXX This is not an *audio* device! */ +}; + +/* + * Here are the main functions to interact to the user process. + */ + +static int +csamidi_probe(device_t dev) +{ + char *s; + sc_p scp; + struct sndcard_func *func; + + /* The parent device has already been probed. */ + + func = device_get_ivars(dev); + if (func == NULL || func->func != SCF_MIDI) + return (ENXIO); + + s = "CS461x Midi Interface"; + + scp = device_get_softc(dev); + bzero(scp, sizeof(*scp)); + scp->io_rid = CS461x_IO_OFFSET; + scp->mem_rid = CS461x_MEM_OFFSET; + scp->irq_rid = 0; + + device_set_desc(dev, s); + return (0); +} + +static int +csamidi_attach(device_t dev) +{ + sc_p scp; + mididev_info *devinfo; + struct sndcard_func *func; + int unit; + + scp = device_get_softc(dev); + unit = device_get_unit(dev); + func = device_get_ivars(dev); + scp->binfo = func->varinfo; + + /* Allocate the resources. */ + if (csamidi_allocres(scp, dev)) { + csamidi_releaseres(scp, dev); + return (ENXIO); + } + + /* Fill the softc. */ + scp->dev = dev; + scp->devinfo = devinfo = &midi_info[unit]; + + /* Fill the midi info. */ + bcopy(&csamidi_op_desc, devinfo, sizeof(csamidi_op_desc)); + midiinit(devinfo, dev); + devinfo->flags = 0; + bcopy(&midisynth_op_desc, &devinfo->synth, sizeof(midisynth_op_desc)); + snprintf(devinfo->midistat, sizeof(devinfo->midistat), "at irq %d", + (int)rman_get_start(scp->irq)); + + /* Init the queue. */ + devinfo->midi_dbuf_in.unit_size = devinfo->midi_dbuf_out.unit_size = 1; + midibuf_init(&devinfo->midi_dbuf_in); + midibuf_init(&devinfo->midi_dbuf_out); + + /* Increase the number of midi devices. */ + nmidi++; + + /* Enable interrupt. */ + if (bus_setup_intr(dev, scp->irq, INTR_TYPE_TTY, csamidi_intr, scp, &scp->ih)) { + csamidi_releaseres(scp, dev); + return (ENXIO); + } + + /* Reset the interface. */ + csamidi_reset(scp); + + return (0); +} + +static int +csamidi_ioctl(dev_t i_dev, u_long cmd, caddr_t arg, int mode, struct proc *p) +{ + sc_p scp; + mididev_info *devinfo; + int unit; + struct synth_info *synthinfo; + struct midi_info *midiinfo; + + unit = MIDIUNIT(i_dev); + + if (unit >= nmidi + nsynth) { + DEB(printf("csamidi_ioctl: unit %d does not exist.\n", unit)); + return (ENXIO); + } + + devinfo = get_mididev_info(i_dev, &unit); + if (devinfo == NULL) { + DEB(printf("csamidi_ioctl: unit %d is not configured.\n", unit)); + return (ENXIO); + } + scp = devinfo->softc; + + switch (cmd) { + case SNDCTL_SYNTH_INFO: + synthinfo = (struct synth_info *)arg; + if (synthinfo->device > nmidi + nsynth || synthinfo->device != unit) + return (ENXIO); + bcopy(&csamidi_synthinfo, synthinfo, sizeof(csamidi_synthinfo)); + synthinfo->device = unit; + return (0); + break; + case SNDCTL_MIDI_INFO: + midiinfo = (struct midi_info *)arg; + if (midiinfo->device > nmidi + nsynth || midiinfo->device != unit) + return (ENXIO); + bcopy(&csamidi_midiinfo, midiinfo, sizeof(csamidi_midiinfo)); + midiinfo->device = unit; + return (0); + break; + default: + return (ENOSYS); + } + /* NOTREACHED */ + return (EINVAL); +} + +static void +csamidi_intr(void *arg) +{ + sc_p scp; + u_char c; + mididev_info *devinfo; + + scp = (sc_p)arg; + devinfo = scp->devinfo; + + /* Read the received data. */ + while ((csamidi_status(scp) & MIDSR_RBE) == 0) { + /* Receive the data. */ + c = (u_char)csamidi_readdata(scp); + /* Queue into the passthru buffer and start transmitting if we can. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) != 0 && ((devinfo->flags & MIDI_F_BUSY) == 0 || (devinfo->fflags & FWRITE) == 0)) { + midibuf_input_intr(&devinfo->midi_dbuf_passthru, &c, sizeof(c)); + devinfo->callback(devinfo, MIDI_CB_START | MIDI_CB_WR); + } + /* Queue if we are reading. Discard an active sensing. */ + if ((devinfo->flags & MIDI_F_READING) != 0 && c != 0xfe) + midibuf_input_intr(&devinfo->midi_dbuf_in, &c, sizeof(c)); + } + + /* Transmit out data. */ + if ((devinfo->flags & MIDI_F_WRITING) != 0 && (csamidi_status(scp) & MIDSR_TBF) == 0) + csamidi_xmit(scp); + + /* Invoke the upper layer. */ + midi_intr(devinfo); +} + +static int +csamidi_callback(mididev_info *d, int reason) +{ + int unit; + sc_p scp; + + if (d == NULL) { + DEB(printf("csamidi_callback: device not configured.\n")); + return (ENXIO); + } + + unit = d->unit; + scp = d->softc; + + switch (reason & MIDI_CB_REASON_MASK) { + case MIDI_CB_START: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) == 0) + /* Begin recording. */ + d->flags |= MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) == 0) + /* Start playing. */ + csamidi_startplay(scp); + break; + case MIDI_CB_STOP: + case MIDI_CB_ABORT: + if ((reason & MIDI_CB_RD) != 0 && (d->flags & MIDI_F_READING) != 0) + /* Stop recording. */ + d->flags &= ~MIDI_F_READING; + if ((reason & MIDI_CB_WR) != 0 && (d->flags & MIDI_F_WRITING) != 0) + /* Stop Playing. */ + d->flags &= ~MIDI_F_WRITING; + break; + } + + return (0); +} + +/* + * The functions below here are the libraries for the above ones. + */ + +/* + * Starts to play the data in the output queue. + * Call this at >=splclock. + */ +static void +csamidi_startplay(sc_p scp) +{ + mididev_info *devinfo; + + devinfo = scp->devinfo; + + /* Can we play now? */ + if (devinfo->midi_dbuf_out.rl == 0) + return; + + devinfo->flags |= MIDI_F_WRITING; + csamidi_xmit(scp); +} + +static void +csamidi_xmit(sc_p scp) +{ + register mididev_info *devinfo; + register midi_dbuf *dbuf; + u_char c; + + devinfo = scp->devinfo; + + /* See which source to use. */ + if ((devinfo->flags & MIDI_F_PASSTHRU) == 0 || ((devinfo->flags & MIDI_F_BUSY) != 0 && (devinfo->fflags & FWRITE) != 0)) + dbuf = &devinfo->midi_dbuf_out; + else + dbuf = &devinfo->midi_dbuf_passthru; + + /* Transmit the data in the queue. */ + while ((devinfo->flags & MIDI_F_WRITING) != 0 && (csamidi_status(scp) & MIDSR_TBF) == 0) { + /* Do we have the data to transmit? */ + if (dbuf->rl == 0) { + /* Stop playing. */ + devinfo->flags &= ~MIDI_F_WRITING; + break; + } else { + /* Send the data. */ + midibuf_output_intr(dbuf, &c, sizeof(c)); + csamidi_writedata(scp, c); + /* We are playing now. */ + devinfo->flags |= MIDI_F_WRITING; + } + } +} + +/* Reset midi. */ +static int +csamidi_reset(sc_p scp) +{ + int i, resp; + + /* Reset the midi. */ + resp = 0; + for (i = 0 ; i < CSAMIDI_TRYDATA ; i++) { + resp = csamidi_command(scp, MIDCR_MRST); + if (resp == 0) + break; + } + if (resp != 0) + return (1); + for (i = 0 ; i < CSAMIDI_TRYDATA ; i++) { + resp = csamidi_command(scp, MIDCR_TXE | MIDCR_RXE | MIDCR_RIE | MIDCR_TIE); + if (resp == 0) + break; + } + if (resp != 0) + return (1); + + DELAY(CSAMIDI_DELAY); + + return (0); +} + +/* Reads the status. */ +static int +csamidi_status(sc_p scp) +{ + return csamidi_readio(scp, BA0_MIDSR); +} + +/* Writes a command. */ +static int +csamidi_command(sc_p scp, u_int32_t value) +{ + csamidi_writeio(scp, BA0_MIDCR, value); + + return (0); +} + +/* Reads a byte of data. */ +static int +csamidi_readdata(sc_p scp) +{ + u_int status; + + /* Is the interface ready to read? */ + status = csamidi_status(scp); + if ((status & MIDSR_RBE) != 0) + /* The interface is busy. */ + return (-EAGAIN); + + return (int)csamidi_readio(scp, BA0_MIDRP) & 0xff; +} + +/* Writes a byte of data. */ +static int +csamidi_writedata(sc_p scp, u_int32_t value) +{ + u_int status; + + /* Is the interface ready to write? */ + status = csamidi_status(scp); + if ((status & MIDSR_TBF) != 0) + /* The interface is busy. */ + return (EAGAIN); + + csamidi_writeio(scp, BA0_MIDWP, value & 0xff); + + return (0); +} + +static u_int32_t +csamidi_readio(sc_p scp, u_long offset) +{ + if (offset < BA0_AC97_RESET) + return bus_space_read_4(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), offset) & 0xffffffff; + else + return (0); +} + +static void +csamidi_writeio(sc_p scp, u_long offset, u_int32_t data) +{ + if (offset < BA0_AC97_RESET) + bus_space_write_4(rman_get_bustag(scp->io), rman_get_bushandle(scp->io), offset, data); +} + +static u_int32_t +csamidi_readmem(sc_p scp, u_long offset) +{ + return bus_space_read_4(rman_get_bustag(scp->mem), rman_get_bushandle(scp->mem), offset) & 0xffffffff; +} + +static void +csamidi_writemem(sc_p scp, u_long offset, u_int32_t data) +{ + bus_space_write_4(rman_get_bustag(scp->mem), rman_get_bushandle(scp->mem), offset, data); +} + +/* Allocates resources. */ +static int +csamidi_allocres(sc_p scp, device_t dev) +{ + if (scp->io == NULL) { + scp->io = bus_alloc_resource(dev, SYS_RES_MEMORY, &scp->io_rid, 0, ~0, CS461x_IO_SIZE, RF_ACTIVE); + if (scp->io == NULL) + return (1); + } + if (scp->mem == NULL) { + scp->mem = bus_alloc_resource(dev, SYS_RES_MEMORY, &scp->mem_rid, 0, ~0, CS461x_MEM_SIZE, RF_ACTIVE); + if (scp->mem == NULL) + return (1); + } + if (scp->irq == NULL) { + scp->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &scp->irq_rid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (scp->irq == NULL) + return (1); + } + + return (0); +} + +/* Releases resources. */ +static void +csamidi_releaseres(sc_p scp, device_t dev) +{ + if (scp->irq != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, scp->irq_rid, scp->irq); + scp->irq = NULL; + } + if (scp->io != NULL) { + bus_release_resource(dev, SYS_RES_MEMORY, scp->io_rid, scp->io); + scp->io = NULL; + } + if (scp->mem != NULL) { + bus_release_resource(dev, SYS_RES_MEMORY, scp->mem_rid, scp->mem); + scp->mem = NULL; + } +} + +static device_method_t csamidi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe , csamidi_probe ), + DEVMETHOD(device_attach, csamidi_attach), + + { 0, 0 }, +}; + +static driver_t csamidi_driver = { + "midi", + csamidi_methods, + sizeof(struct csamidi_softc), +}; + +DRIVER_MODULE(csamidi, csa, csamidi_driver, midi_devclass, 0, 0); diff --git a/sys/dev/sound/pcm/mixer.c b/sys/dev/sound/pcm/mixer.c index 5733568..68e45d9 100644 --- a/sys/dev/sound/pcm/mixer.c +++ b/sys/dev/sound/pcm/mixer.c @@ -32,6 +32,7 @@ static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = 75, [SOUND_MIXER_BASS] = 50, [SOUND_MIXER_TREBLE] = 50, + [SOUND_MIXER_SYNTH] = 75, [SOUND_MIXER_PCM] = 75, [SOUND_MIXER_SPEAKER] = 75, [SOUND_MIXER_LINE] = 75, diff --git a/sys/i386/conf/NOTES b/sys/i386/conf/NOTES index 1c46e4c..2a8214e 100644 --- a/sys/i386/conf/NOTES +++ b/sys/i386/conf/NOTES @@ -1649,8 +1649,32 @@ hint.pcm.0.flags="0x0" # For PnP/PCI sound cards, no hints are required. +# +# midi: MIDI interfaces and synthesizers +# + +device midi + +# For non-pnp sound cards with no bridge drivers: +hint.midi.0.at="isa" +hint.midi.0.irq="5" +hint.midi.0.flags="0x0" + +# For serial ports (this example configures port 2): +# TODO: implement generic tty-midi interface so that we can use +# other uarts. +hint.midi.0.at="isa" +hint.midi.0.port="0x2F8" +hint.midi.0.irq="3" + +# +# seq: MIDI sequencer +# + +device seq + # The bridge drivers for sound cards. These can be seperately configured -# for providing services to the likes of new-midi (not in the tree yet). +# for providing services to the likes of new-midi. # When used with 'device pcm' they also provide pcm sound services. # # sbc: Creative SoundBlaster ISA PnP/non-PnP diff --git a/sys/i386/i386/bios.c b/sys/i386/i386/bios.c index 4a5a942..06eafe0 100644 --- a/sys/i386/i386/bios.c +++ b/sys/i386/i386/bios.c @@ -588,7 +588,8 @@ pnpbios_identify(driver_t *driver, device_t parent) isa_set_logicalid(dev, pd->devid); ISA_SET_CONFIG_CALLBACK(parent, dev, pnpbios_set_config, 0); pnp_parse_resources(dev, &pd->devdata[0], - pd->size - sizeof(struct pnp_sysdev)); + pd->size - sizeof(struct pnp_sysdev), + isa_get_vendorid(dev), isa_get_logicalid(dev), 0); if (!device_get_desc(dev)) device_set_desc_copy(dev, pnp_eisaformat(pd->devid)); diff --git a/sys/isa/pnp.c b/sys/isa/pnp.c index 12b5dab..f9266bb 100644 --- a/sys/isa/pnp.c +++ b/sys/isa/pnp.c @@ -54,6 +54,7 @@ struct pnp_quirk { u_int32_t logical_id; /* ID of the device with quirk */ int type; #define PNP_QUIRK_WRITE_REG 1 /* Need to write a pnp register */ +#define PNP_QUIRK_EXTRA_IO 2 /* Has extra io ports */ int arg1; int arg2; }; @@ -66,6 +67,24 @@ struct pnp_quirk pnp_quirks[] = { */ { 0x0100561e /* GRV0001 */, 0, PNP_QUIRK_WRITE_REG, 0xf2, 0xff }, + /* + * An emu8000 does not give us other than the first + * port. + */ + { 0x26008c0e /* SB16 */, 0x21008c0e, + PNP_QUIRK_EXTRA_IO, 0x400, 0x800 }, + { 0x42008c0e /* SB32(CTL0042) */, 0x21008c0e, + PNP_QUIRK_EXTRA_IO, 0x400, 0x800 }, + { 0x44008c0e /* SB32(CTL0044) */, 0x21008c0e, + PNP_QUIRK_EXTRA_IO, 0x400, 0x800 }, + { 0x49008c0e /* SB32(CTL0049) */, 0x21008c0e, + PNP_QUIRK_EXTRA_IO, 0x400, 0x800 }, + { 0xf1008c0e /* SB32(CTL00f1) */, 0x21008c0e, + PNP_QUIRK_EXTRA_IO, 0x400, 0x800 }, + { 0xc1008c0e /* SB64(CTL00c1) */, 0x22008c0e, + PNP_QUIRK_EXTRA_IO, 0x400, 0x800 }, + { 0xe4008c0e /* SB64(CTL00e4) */, 0x22008c0e, + PNP_QUIRK_EXTRA_IO, 0x400, 0x800 }, { 0 } }; @@ -363,8 +382,8 @@ pnp_set_config(void *arg, struct isa_config *config, int enable) /* * Process quirks for a logical device.. The card must be in Config state. */ -static void -pnp_check_quirks(u_int32_t vendor_id, u_int32_t logical_id, int ldn) +void +pnp_check_quirks(u_int32_t vendor_id, u_int32_t logical_id, int ldn, struct isa_config *config) { struct pnp_quirk *qp; @@ -377,6 +396,22 @@ pnp_check_quirks(u_int32_t vendor_id, u_int32_t logical_id, int ldn) pnp_write(PNP_SET_LDN, ldn); pnp_write(qp->arg1, qp->arg2); break; + case PNP_QUIRK_EXTRA_IO: + if (config == NULL) + break; + if (qp->arg1 != 0) { + config->ic_nport++; + config->ic_port[config->ic_nport - 1] = config->ic_port[0]; + config->ic_port[config->ic_nport - 1].ir_start += qp->arg1; + config->ic_port[config->ic_nport - 1].ir_end += qp->arg1; + } + if (qp->arg2 != 0) { + config->ic_nport++; + config->ic_port[config->ic_nport - 1] = config->ic_port[0]; + config->ic_port[config->ic_nport - 1].ir_start += qp->arg2; + config->ic_port[config->ic_nport - 1].ir_end += qp->arg2; + } + break; } } } @@ -461,7 +496,8 @@ pnp_create_devices(device_t parent, pnp_id *p, int csn, */ if (startres) { pnp_parse_resources(dev, startres, - resinfo - startres - 1); + resinfo - startres - 1, + p->vendor_id, logical_id, ldn); dev = 0; startres = 0; } @@ -471,7 +507,7 @@ pnp_create_devices(device_t parent, pnp_id *p, int csn, * resources. */ bcopy(resinfo, &logical_id, 4); - pnp_check_quirks(p->vendor_id, logical_id, ldn); + pnp_check_quirks(p->vendor_id, logical_id, ldn, NULL); compat_id = 0; dev = BUS_ADD_CHILD(parent, ISA_ORDER_PNP, NULL, -1); if (desc) @@ -502,7 +538,8 @@ pnp_create_devices(device_t parent, pnp_id *p, int csn, break; } pnp_parse_resources(dev, startres, - resinfo - startres - 1); + resinfo - startres - 1, + p->vendor_id, logical_id, ldn); dev = 0; startres = 0; scanning = 0; diff --git a/sys/isa/pnpparse.c b/sys/isa/pnpparse.c index c6c0869..dfb5c33 100644 --- a/sys/isa/pnpparse.c +++ b/sys/isa/pnpparse.c @@ -47,7 +47,7 @@ * Resource Data or it reaches the end of Resource Data. */ void -pnp_parse_resources(device_t dev, u_char *resources, int len) +pnp_parse_resources(device_t dev, u_char *resources, int len, u_int32_t vendor_id, u_int32_t logical_id, int ldn) { device_t parent = device_get_parent(dev); u_char tag, *resp, *resinfo; @@ -189,6 +189,9 @@ pnp_parse_resources(device_t dev, u_char *resources, int len) config->ic_port[config->ic_nport].ir_align = resinfo[5]; config->ic_nport++; + pnp_check_quirks(vendor_id, + logical_id, + ldn, config); break; case PNP_TAG_IO_FIXED: diff --git a/sys/isa/pnpvar.h b/sys/isa/pnpvar.h index 0e7dee9..050adb9 100644 --- a/sys/isa/pnpvar.h +++ b/sys/isa/pnpvar.h @@ -53,7 +53,9 @@ u_char pnp_read(int d); /* currently unused, but who knows... */ | (PNP_HEXTONUM(s[5]) << 28)) char *pnp_eisaformat(u_int32_t id); -void pnp_parse_resources(device_t dev, u_char *resources, int len); +void pnp_parse_resources(device_t dev, u_char *resources, int len, u_int32_t vendor_id, u_int32_t logical_id, int ldn); + +void pnp_check_quirks(u_int32_t vendor_id, u_int32_t logical_id, int ldn, struct isa_config *config); #endif /* _KERNEL */ diff --git a/sys/sys/soundcard.h b/sys/sys/soundcard.h index e608b34..762250d 100644 --- a/sys/sys/soundcard.h +++ b/sys/sys/soundcard.h @@ -91,6 +91,8 @@ #define SNDCARD_PSEUDO_MSS 24 #define SNDCARD_AWE32 25 #define SNDCARD_NSS 26 +#define SNDCARD_UART16550 27 +#define SNDCARD_OPL 28 #include <sys/types.h> #ifndef _IOWR @@ -704,6 +706,8 @@ typedef struct { #define SNDCTL_MIDI_PRETIME _IOWR('m', 0, int) #define SNDCTL_MIDI_MPUMODE _IOWR('m', 1, int) #define SNDCTL_MIDI_MPUCMD _IOWR('m', 2, mpu_command_rec) +#define MIOSPASSTHRU _IOWR('m', 3, int) +#define MIOGPASSTHRU _IOWR('m', 4, int) /* * IOCTL commands for /dev/dsp and /dev/audio |