Path: blob/master/sound/drivers/mpu401/mpu401_uart.c
10817 views
/*1* Copyright (c) by Jaroslav Kysela <[email protected]>2* Routines for control of MPU-401 in UART mode3*4* MPU-401 supports UART mode which is not capable generate transmit5* interrupts thus output is done via polling. Also, if irq < 0, then6* input is done also via polling. Do not expect good performance.7*8*9* This program is free software; you can redistribute it and/or modify10* it under the terms of the GNU General Public License as published by11* the Free Software Foundation; either version 2 of the License, or12* (at your option) any later version.13*14* This program is distributed in the hope that it will be useful,15* but WITHOUT ANY WARRANTY; without even the implied warranty of16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the17* GNU General Public License for more details.18*19* You should have received a copy of the GNU General Public License20* along with this program; if not, write to the Free Software21* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA22*23* 13-03-2003:24* Added support for different kind of hardware I/O. Build in choices25* are port and mmio. For other kind of I/O, set mpu->read and26* mpu->write to your own I/O functions.27*28*/2930#include <asm/io.h>31#include <linux/delay.h>32#include <linux/init.h>33#include <linux/slab.h>34#include <linux/ioport.h>35#include <linux/interrupt.h>36#include <linux/errno.h>37#include <sound/core.h>38#include <sound/mpu401.h>3940MODULE_AUTHOR("Jaroslav Kysela <[email protected]>");41MODULE_DESCRIPTION("Routines for control of MPU-401 in UART mode");42MODULE_LICENSE("GPL");4344static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu);45static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu);4647/*4849*/5051#define snd_mpu401_input_avail(mpu) \52(!(mpu->read(mpu, MPU401C(mpu)) & MPU401_RX_EMPTY))53#define snd_mpu401_output_ready(mpu) \54(!(mpu->read(mpu, MPU401C(mpu)) & MPU401_TX_FULL))5556/* Build in lowlevel io */57static void mpu401_write_port(struct snd_mpu401 *mpu, unsigned char data,58unsigned long addr)59{60outb(data, addr);61}6263static unsigned char mpu401_read_port(struct snd_mpu401 *mpu,64unsigned long addr)65{66return inb(addr);67}6869static void mpu401_write_mmio(struct snd_mpu401 *mpu, unsigned char data,70unsigned long addr)71{72writeb(data, (void __iomem *)addr);73}7475static unsigned char mpu401_read_mmio(struct snd_mpu401 *mpu,76unsigned long addr)77{78return readb((void __iomem *)addr);79}80/* */8182static void snd_mpu401_uart_clear_rx(struct snd_mpu401 *mpu)83{84int timeout = 100000;85for (; timeout > 0 && snd_mpu401_input_avail(mpu); timeout--)86mpu->read(mpu, MPU401D(mpu));87#ifdef CONFIG_SND_DEBUG88if (timeout <= 0)89snd_printk(KERN_ERR "cmd: clear rx timeout (status = 0x%x)\n",90mpu->read(mpu, MPU401C(mpu)));91#endif92}9394static void uart_interrupt_tx(struct snd_mpu401 *mpu)95{96unsigned long flags;9798if (test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode) &&99test_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode)) {100spin_lock_irqsave(&mpu->output_lock, flags);101snd_mpu401_uart_output_write(mpu);102spin_unlock_irqrestore(&mpu->output_lock, flags);103}104}105106static void _snd_mpu401_uart_interrupt(struct snd_mpu401 *mpu)107{108unsigned long flags;109110if (mpu->info_flags & MPU401_INFO_INPUT) {111spin_lock_irqsave(&mpu->input_lock, flags);112if (test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))113snd_mpu401_uart_input_read(mpu);114else115snd_mpu401_uart_clear_rx(mpu);116spin_unlock_irqrestore(&mpu->input_lock, flags);117}118if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))119/* ok. for better Tx performance try do some output120when input is done */121uart_interrupt_tx(mpu);122}123124/**125* snd_mpu401_uart_interrupt - generic MPU401-UART interrupt handler126* @irq: the irq number127* @dev_id: mpu401 instance128*129* Processes the interrupt for MPU401-UART i/o.130*/131irqreturn_t snd_mpu401_uart_interrupt(int irq, void *dev_id)132{133struct snd_mpu401 *mpu = dev_id;134135if (mpu == NULL)136return IRQ_NONE;137_snd_mpu401_uart_interrupt(mpu);138return IRQ_HANDLED;139}140141EXPORT_SYMBOL(snd_mpu401_uart_interrupt);142143/**144* snd_mpu401_uart_interrupt_tx - generic MPU401-UART transmit irq handler145* @irq: the irq number146* @dev_id: mpu401 instance147*148* Processes the interrupt for MPU401-UART output.149*/150irqreturn_t snd_mpu401_uart_interrupt_tx(int irq, void *dev_id)151{152struct snd_mpu401 *mpu = dev_id;153154if (mpu == NULL)155return IRQ_NONE;156uart_interrupt_tx(mpu);157return IRQ_HANDLED;158}159160EXPORT_SYMBOL(snd_mpu401_uart_interrupt_tx);161162/*163* timer callback164* reprogram the timer and call the interrupt job165*/166static void snd_mpu401_uart_timer(unsigned long data)167{168struct snd_mpu401 *mpu = (struct snd_mpu401 *)data;169unsigned long flags;170171spin_lock_irqsave(&mpu->timer_lock, flags);172/*mpu->mode |= MPU401_MODE_TIMER;*/173mpu->timer.expires = 1 + jiffies;174add_timer(&mpu->timer);175spin_unlock_irqrestore(&mpu->timer_lock, flags);176if (mpu->rmidi)177_snd_mpu401_uart_interrupt(mpu);178}179180/*181* initialize the timer callback if not programmed yet182*/183static void snd_mpu401_uart_add_timer (struct snd_mpu401 *mpu, int input)184{185unsigned long flags;186187spin_lock_irqsave (&mpu->timer_lock, flags);188if (mpu->timer_invoked == 0) {189init_timer(&mpu->timer);190mpu->timer.data = (unsigned long)mpu;191mpu->timer.function = snd_mpu401_uart_timer;192mpu->timer.expires = 1 + jiffies;193add_timer(&mpu->timer);194}195mpu->timer_invoked |= input ? MPU401_MODE_INPUT_TIMER :196MPU401_MODE_OUTPUT_TIMER;197spin_unlock_irqrestore (&mpu->timer_lock, flags);198}199200/*201* remove the timer callback if still active202*/203static void snd_mpu401_uart_remove_timer (struct snd_mpu401 *mpu, int input)204{205unsigned long flags;206207spin_lock_irqsave (&mpu->timer_lock, flags);208if (mpu->timer_invoked) {209mpu->timer_invoked &= input ? ~MPU401_MODE_INPUT_TIMER :210~MPU401_MODE_OUTPUT_TIMER;211if (! mpu->timer_invoked)212del_timer(&mpu->timer);213}214spin_unlock_irqrestore (&mpu->timer_lock, flags);215}216217/*218* send a UART command219* return zero if successful, non-zero for some errors220*/221222static int snd_mpu401_uart_cmd(struct snd_mpu401 * mpu, unsigned char cmd,223int ack)224{225unsigned long flags;226int timeout, ok;227228spin_lock_irqsave(&mpu->input_lock, flags);229if (mpu->hardware != MPU401_HW_TRID4DWAVE) {230mpu->write(mpu, 0x00, MPU401D(mpu));231/*snd_mpu401_uart_clear_rx(mpu);*/232}233/* ok. standard MPU-401 initialization */234if (mpu->hardware != MPU401_HW_SB) {235for (timeout = 1000; timeout > 0 &&236!snd_mpu401_output_ready(mpu); timeout--)237udelay(10);238#ifdef CONFIG_SND_DEBUG239if (!timeout)240snd_printk(KERN_ERR "cmd: tx timeout (status = 0x%x)\n",241mpu->read(mpu, MPU401C(mpu)));242#endif243}244mpu->write(mpu, cmd, MPU401C(mpu));245if (ack && !(mpu->info_flags & MPU401_INFO_NO_ACK)) {246ok = 0;247timeout = 10000;248while (!ok && timeout-- > 0) {249if (snd_mpu401_input_avail(mpu)) {250if (mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)251ok = 1;252}253}254if (!ok && mpu->read(mpu, MPU401D(mpu)) == MPU401_ACK)255ok = 1;256} else257ok = 1;258spin_unlock_irqrestore(&mpu->input_lock, flags);259if (!ok) {260snd_printk(KERN_ERR "cmd: 0x%x failed at 0x%lx "261"(status = 0x%x, data = 0x%x)\n", cmd, mpu->port,262mpu->read(mpu, MPU401C(mpu)),263mpu->read(mpu, MPU401D(mpu)));264return 1;265}266return 0;267}268269static int snd_mpu401_do_reset(struct snd_mpu401 *mpu)270{271if (snd_mpu401_uart_cmd(mpu, MPU401_RESET, 1))272return -EIO;273if (snd_mpu401_uart_cmd(mpu, MPU401_ENTER_UART, 0))274return -EIO;275return 0;276}277278/*279* input/output open/close - protected by open_mutex in rawmidi.c280*/281static int snd_mpu401_uart_input_open(struct snd_rawmidi_substream *substream)282{283struct snd_mpu401 *mpu;284int err;285286mpu = substream->rmidi->private_data;287if (mpu->open_input && (err = mpu->open_input(mpu)) < 0)288return err;289if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode)) {290if (snd_mpu401_do_reset(mpu) < 0)291goto error_out;292}293mpu->substream_input = substream;294set_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);295return 0;296297error_out:298if (mpu->open_input && mpu->close_input)299mpu->close_input(mpu);300return -EIO;301}302303static int snd_mpu401_uart_output_open(struct snd_rawmidi_substream *substream)304{305struct snd_mpu401 *mpu;306int err;307308mpu = substream->rmidi->private_data;309if (mpu->open_output && (err = mpu->open_output(mpu)) < 0)310return err;311if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode)) {312if (snd_mpu401_do_reset(mpu) < 0)313goto error_out;314}315mpu->substream_output = substream;316set_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);317return 0;318319error_out:320if (mpu->open_output && mpu->close_output)321mpu->close_output(mpu);322return -EIO;323}324325static int snd_mpu401_uart_input_close(struct snd_rawmidi_substream *substream)326{327struct snd_mpu401 *mpu;328int err = 0;329330mpu = substream->rmidi->private_data;331clear_bit(MPU401_MODE_BIT_INPUT, &mpu->mode);332mpu->substream_input = NULL;333if (! test_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode))334err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);335if (mpu->close_input)336mpu->close_input(mpu);337if (err)338return -EIO;339return 0;340}341342static int snd_mpu401_uart_output_close(struct snd_rawmidi_substream *substream)343{344struct snd_mpu401 *mpu;345int err = 0;346347mpu = substream->rmidi->private_data;348clear_bit(MPU401_MODE_BIT_OUTPUT, &mpu->mode);349mpu->substream_output = NULL;350if (! test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode))351err = snd_mpu401_uart_cmd(mpu, MPU401_RESET, 0);352if (mpu->close_output)353mpu->close_output(mpu);354if (err)355return -EIO;356return 0;357}358359/*360* trigger input callback361*/362static void363snd_mpu401_uart_input_trigger(struct snd_rawmidi_substream *substream, int up)364{365unsigned long flags;366struct snd_mpu401 *mpu;367int max = 64;368369mpu = substream->rmidi->private_data;370if (up) {371if (! test_and_set_bit(MPU401_MODE_BIT_INPUT_TRIGGER,372&mpu->mode)) {373/* first time - flush FIFO */374while (max-- > 0)375mpu->read(mpu, MPU401D(mpu));376if (mpu->irq < 0)377snd_mpu401_uart_add_timer(mpu, 1);378}379380/* read data in advance */381spin_lock_irqsave(&mpu->input_lock, flags);382snd_mpu401_uart_input_read(mpu);383spin_unlock_irqrestore(&mpu->input_lock, flags);384} else {385if (mpu->irq < 0)386snd_mpu401_uart_remove_timer(mpu, 1);387clear_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode);388}389390}391392/*393* transfer input pending data394* call with input_lock spinlock held395*/396static void snd_mpu401_uart_input_read(struct snd_mpu401 * mpu)397{398int max = 128;399unsigned char byte;400401while (max-- > 0) {402if (! snd_mpu401_input_avail(mpu))403break; /* input not available */404byte = mpu->read(mpu, MPU401D(mpu));405if (test_bit(MPU401_MODE_BIT_INPUT_TRIGGER, &mpu->mode))406snd_rawmidi_receive(mpu->substream_input, &byte, 1);407}408}409410/*411* Tx FIFO sizes:412* CS4237B - 16 bytes413* AudioDrive ES1688 - 12 bytes414* S3 SonicVibes - 8 bytes415* SoundBlaster AWE 64 - 2 bytes (ugly hardware)416*/417418/*419* write output pending bytes420* call with output_lock spinlock held421*/422static void snd_mpu401_uart_output_write(struct snd_mpu401 * mpu)423{424unsigned char byte;425int max = 256;426427do {428if (snd_rawmidi_transmit_peek(mpu->substream_output,429&byte, 1) == 1) {430/*431* Try twice because there is hardware that insists on432* setting the output busy bit after each write.433*/434if (!snd_mpu401_output_ready(mpu) &&435!snd_mpu401_output_ready(mpu))436break; /* Tx FIFO full - try again later */437mpu->write(mpu, byte, MPU401D(mpu));438snd_rawmidi_transmit_ack(mpu->substream_output, 1);439} else {440snd_mpu401_uart_remove_timer (mpu, 0);441break; /* no other data - leave the tx loop */442}443} while (--max > 0);444}445446/*447* output trigger callback448*/449static void450snd_mpu401_uart_output_trigger(struct snd_rawmidi_substream *substream, int up)451{452unsigned long flags;453struct snd_mpu401 *mpu;454455mpu = substream->rmidi->private_data;456if (up) {457set_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);458459/* try to add the timer at each output trigger,460* since the output timer might have been removed in461* snd_mpu401_uart_output_write().462*/463if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))464snd_mpu401_uart_add_timer(mpu, 0);465466/* output pending data */467spin_lock_irqsave(&mpu->output_lock, flags);468snd_mpu401_uart_output_write(mpu);469spin_unlock_irqrestore(&mpu->output_lock, flags);470} else {471if (! (mpu->info_flags & MPU401_INFO_TX_IRQ))472snd_mpu401_uart_remove_timer(mpu, 0);473clear_bit(MPU401_MODE_BIT_OUTPUT_TRIGGER, &mpu->mode);474}475}476477/*478479*/480481static struct snd_rawmidi_ops snd_mpu401_uart_output =482{483.open = snd_mpu401_uart_output_open,484.close = snd_mpu401_uart_output_close,485.trigger = snd_mpu401_uart_output_trigger,486};487488static struct snd_rawmidi_ops snd_mpu401_uart_input =489{490.open = snd_mpu401_uart_input_open,491.close = snd_mpu401_uart_input_close,492.trigger = snd_mpu401_uart_input_trigger,493};494495static void snd_mpu401_uart_free(struct snd_rawmidi *rmidi)496{497struct snd_mpu401 *mpu = rmidi->private_data;498if (mpu->irq_flags && mpu->irq >= 0)499free_irq(mpu->irq, (void *) mpu);500release_and_free_resource(mpu->res);501kfree(mpu);502}503504/**505* snd_mpu401_uart_new - create an MPU401-UART instance506* @card: the card instance507* @device: the device index, zero-based508* @hardware: the hardware type, MPU401_HW_XXXX509* @port: the base address of MPU401 port510* @info_flags: bitflags MPU401_INFO_XXX511* @irq: the irq number, -1 if no interrupt for mpu512* @irq_flags: the irq request flags (SA_XXX), 0 if irq was already reserved.513* @rrawmidi: the pointer to store the new rawmidi instance514*515* Creates a new MPU-401 instance.516*517* Note that the rawmidi instance is returned on the rrawmidi argument,518* not the mpu401 instance itself. To access to the mpu401 instance,519* cast from rawmidi->private_data (with struct snd_mpu401 magic-cast).520*521* Returns zero if successful, or a negative error code.522*/523int snd_mpu401_uart_new(struct snd_card *card, int device,524unsigned short hardware,525unsigned long port,526unsigned int info_flags,527int irq, int irq_flags,528struct snd_rawmidi ** rrawmidi)529{530struct snd_mpu401 *mpu;531struct snd_rawmidi *rmidi;532int in_enable, out_enable;533int err;534535if (rrawmidi)536*rrawmidi = NULL;537if (! (info_flags & (MPU401_INFO_INPUT | MPU401_INFO_OUTPUT)))538info_flags |= MPU401_INFO_INPUT | MPU401_INFO_OUTPUT;539in_enable = (info_flags & MPU401_INFO_INPUT) ? 1 : 0;540out_enable = (info_flags & MPU401_INFO_OUTPUT) ? 1 : 0;541if ((err = snd_rawmidi_new(card, "MPU-401U", device,542out_enable, in_enable, &rmidi)) < 0)543return err;544mpu = kzalloc(sizeof(*mpu), GFP_KERNEL);545if (mpu == NULL) {546snd_printk(KERN_ERR "mpu401_uart: cannot allocate\n");547snd_device_free(card, rmidi);548return -ENOMEM;549}550rmidi->private_data = mpu;551rmidi->private_free = snd_mpu401_uart_free;552spin_lock_init(&mpu->input_lock);553spin_lock_init(&mpu->output_lock);554spin_lock_init(&mpu->timer_lock);555mpu->hardware = hardware;556if (! (info_flags & MPU401_INFO_INTEGRATED)) {557int res_size = hardware == MPU401_HW_PC98II ? 4 : 2;558mpu->res = request_region(port, res_size, "MPU401 UART");559if (mpu->res == NULL) {560snd_printk(KERN_ERR "mpu401_uart: "561"unable to grab port 0x%lx size %d\n",562port, res_size);563snd_device_free(card, rmidi);564return -EBUSY;565}566}567if (info_flags & MPU401_INFO_MMIO) {568mpu->write = mpu401_write_mmio;569mpu->read = mpu401_read_mmio;570} else {571mpu->write = mpu401_write_port;572mpu->read = mpu401_read_port;573}574mpu->port = port;575if (hardware == MPU401_HW_PC98II)576mpu->cport = port + 2;577else578mpu->cport = port + 1;579if (irq >= 0 && irq_flags) {580if (request_irq(irq, snd_mpu401_uart_interrupt, irq_flags,581"MPU401 UART", (void *) mpu)) {582snd_printk(KERN_ERR "mpu401_uart: "583"unable to grab IRQ %d\n", irq);584snd_device_free(card, rmidi);585return -EBUSY;586}587}588mpu->info_flags = info_flags;589mpu->irq = irq;590mpu->irq_flags = irq_flags;591if (card->shortname[0])592snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI",593card->shortname);594else595sprintf(rmidi->name, "MPU-401 MIDI %d-%d",card->number, device);596if (out_enable) {597snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,598&snd_mpu401_uart_output);599rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;600}601if (in_enable) {602snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,603&snd_mpu401_uart_input);604rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;605if (out_enable)606rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;607}608mpu->rmidi = rmidi;609if (rrawmidi)610*rrawmidi = rmidi;611return 0;612}613614EXPORT_SYMBOL(snd_mpu401_uart_new);615616/*617* INIT part618*/619620static int __init alsa_mpu401_uart_init(void)621{622return 0;623}624625static void __exit alsa_mpu401_uart_exit(void)626{627}628629module_init(alsa_mpu401_uart_init)630module_exit(alsa_mpu401_uart_exit)631632633