diff options
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 |