From patchwork Fri Nov 7 00:40:38 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kim Taylor X-Patchwork-Id: 5248911 Return-Path: X-Original-To: patchwork-alsa-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 28C569F2F1 for ; Fri, 7 Nov 2014 00:41:39 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 877312012B for ; Fri, 7 Nov 2014 00:41:36 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.kernel.org (Postfix) with ESMTP id 60D52200F4 for ; Fri, 7 Nov 2014 00:41:32 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 2910C26550C; Fri, 7 Nov 2014 01:41:30 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,FREEMAIL_FROM, NO_DNS_FOR_FROM,RCVD_IN_DNSWL_NONE,UNPARSEABLE_RELAY autolearn=no version=3.3.1 Received: from alsa0.perex.cz (localhost [IPv6:::1]) by alsa0.perex.cz (Postfix) with ESMTP id 8F6252654D1; Fri, 7 Nov 2014 01:41:19 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id AD4E22654D4; Fri, 7 Nov 2014 01:41:17 +0100 (CET) Received: from mout.gmx.com (mout.gmx.com [74.208.4.200]) by alsa0.perex.cz (Postfix) with ESMTP id 3404C2654CE for ; Fri, 7 Nov 2014 01:41:09 +0100 (CET) Received: from [140.159.160.7] ([140.159.160.7]) by mail.gmx.com (mrgmxus002) with ESMTPSA (Nemesis) id 0M0gga-1Y1bUA1FJ0-00uu2G; Fri, 07 Nov 2014 01:40:59 +0100 Message-ID: <1415320838.11009.12.camel@linux-host> From: Kim Taylor To: Takashi Iwai Date: Fri, 07 Nov 2014 11:40:38 +1100 In-Reply-To: References: <1413847835.13334.8.camel@linux-host> <1413848459.13334.9.camel@linux-host> <54460930.6060603@zonque.org> <1413930275.13334.19.camel@linux-host> X-Mailer: Evolution 3.10.4 Mime-Version: 1.0 X-Provags-ID: V03:K0:F/dOXPIfYdrh4LrlMQ7Co77ztGA++SVU1VdBTY1xtJ89bMgbomZ xrDXZXjjJqkmERIoeLc0sVKxwhmrumYhq6PtivDi1KdhqdnPGaDs6tyiFE7YN7rpeU87RRr wU1t3yqLv8/jcwi/xRLC0HGk+aa9rL2Pd61jCfQW6rnFXI9iWKiFK4li3XT9MbdomckvogJ 98qmYKT+RDUxsygy/ukag== X-UI-Out-Filterresults: notjunk:1; Cc: alsa-devel@alsa-project.org, Daniel Mack Subject: Re: [alsa-devel] Serial MIDI driver for PL011 - as found on BCM2835 hardware X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP On Wed, 2014-10-22 at 08:51 +0200, Takashi Iwai wrote: > At Wed, 22 Oct 2014 09:24:35 +1100, > Kim Taylor wrote: > > > > On Tue, 2014-10-21 at 09:20 +0200, Daniel Mack wrote: > > > Hi, > > > > > > Thanks for sharing this! > > > > > > On 10/21/2014 01:40 AM, Kim Taylor wrote: > > > > I've written a driver for raw MIDI using the PL011. It is obviously > > > > based on the snd-serial-u16550 module, with a couple of improvements: > > > > > > > > - I've implemented a drain() callback. > > > > > > > > - I've also implemented a half duplex mode based on a timer callback > > > > which was necessary for reliable communication with my eMagic Unitor 8 > > > > hardware. (Testing with a serial link to other hardware shows that the > > > > full duplex mode is also working.) > > > > > > So I guess you could patch the existing driver to make it work for your > > > use case, right? I haven't looked at the code in detail, but from what > > > you describe, that should be possible. > > > > I'm not sure about that. As it uses a different UART chip, there is a > > new probe() function, it uses memory mapped I/O, instead of i386 > > inb/outb, it gathers information from the ARM AMBA bus... > > > > The only things that would remain unchanged are the output_trigger() and > > output_write() functions. One possibility might be to abstract out all > > read/write operations using function pointers, however, I believe that > > the new driver is different enough to constitute a new module. > > > > Or maybe there should be a third module (for example snd-serial-core, > > containing any common functions, exporting them to both > > snd-serial-u16550 and snd-serial-pl011? > > Yes, that sounds good. Of course, it depends on the volume they can > share. If there are so few, no big merit to split to modules. Let's > see. > > > Takashi > Here's the patch. In line with Takashi's comment, I've not split the modules. There is not much to be shared. Recent changes include using a hrtimer instead of a timer for half duplex mode. I've been using the driver for a couple of weeks now and it appears to be stable. diff -uprN sound-orig/sound/drivers/Makefile sound-new/sound/drivers/Makefile --- sound-orig/sound/drivers/Makefile 2014-11-07 11:24:19.000000000 +1100 +++ sound-new/sound/drivers/Makefile 2014-11-07 11:16:49.000000000 +1100 @@ -9,6 +9,7 @@ snd-mtpav-objs := mtpav.o snd-mts64-objs := mts64.o snd-portman2x4-objs := portman2x4.o snd-serial-u16550-objs := serial-u16550.o +snd-serial-pl011-objs := serial-pl011.o snd-virmidi-objs := virmidi.o snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o @@ -17,6 +18,7 @@ obj-$(CONFIG_SND_DUMMY) += snd-dummy.o obj-$(CONFIG_SND_ALOOP) += snd-aloop.o obj-$(CONFIG_SND_VIRMIDI) += snd-virmidi.o obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o +obj-$(CONFIG_SND_SERIAL_PL011) += snd-serial-pl011.o obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o obj-$(CONFIG_SND_MTS64) += snd-mts64.o obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o diff -uprN sound-orig/sound/drivers/serial-pl011.c sound-new/sound/drivers/serial-pl011.c --- sound-orig/sound/drivers/serial-pl011.c 1970-01-01 10:00:00.000000000 +1000 +++ sound-new/sound/drivers/serial-pl011.c 2014-11-07 11:32:04.000000000 +1100 @@ -0,0 +1,1089 @@ +/* + * serial-pl011.c + * Copyright (c) by Jaroslav Kysela , + * Isaku Yamahata , + * George Hansper , + * Hannu Savolainen + * Kim Taylor + * + * This code is based on the code from serial-u16550. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Sat Mar 31 17:27:57 PST 2001 tim.mann@compaq.com + * Added support for the Midiator MS-124T and for the MS-124W in + * Single Addressed (S/A) or Multiple Burst (M/B) mode, with + * power derived either parasitically from the serial port or + * from a separate power supply. + * + * More documentation can be found in serial-u16550.txt. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +MODULE_DESCRIPTION("MIDI serial pl011"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ALSA, MIDI serial pl011}}"); + +#define SNDRV_SERIAL_SOUNDCANVAS 0 /* Roland Soundcanvas; F5 NN selects part */ +#define SNDRV_SERIAL_MS124T 1 /* Midiator MS-124T */ +#define SNDRV_SERIAL_MS124W_SA 2 /* Midiator MS-124W in S/A mode */ +#define SNDRV_SERIAL_MS124W_MB 3 /* Midiator MS-124W in M/B mode */ +#define SNDRV_SERIAL_GENERIC 4 /* Generic Interface */ +#define SNDRV_SERIAL_MAX_ADAPTOR SNDRV_SERIAL_GENERIC +static char *adaptor_names[] = { + "Soundcanvas", + "MS-124T", + "MS-124W S/A", + "MS-124W M/B", + "Generic" +}; + +#define SNDRV_SERIAL_NORMALBUFF 0 /* Normal blocking buffer operation */ +#define SNDRV_SERIAL_DROPBUFF 1 /* Non-blocking discard operation */ +#define SNDRV_SERIAL_NOTHROTTLE 0 +#define SNDRV_SERIAL_NORTSCTS 0 +#define SNDRV_SERIAL_DEFAULT_FIFO 16 +#define TIMER_ATTEMPTS_LIMIT 255 + +static int speed = 115200; /* 9600,19200,38400,57600,115200 */ +static int outs = 1; /* 1 to 16 */ +static int ins = 1; /* 1 to 16 */ +static int adaptor = SNDRV_SERIAL_GENERIC; +static bool droponfull = SNDRV_SERIAL_NORMALBUFF; +static bool throttle_tx = SNDRV_SERIAL_NOTHROTTLE; +static int throttle_delay = 1000; +static int fifo_limit = SNDRV_SERIAL_DEFAULT_FIFO; +static bool flow_control = SNDRV_SERIAL_NORTSCTS; + +module_param(speed, int, 0444); +MODULE_PARM_DESC(speed, "Speed in bauds."); +module_param(outs, int, 0444); +MODULE_PARM_DESC(outs, "Number of MIDI outputs."); +module_param(ins, int, 0444); +MODULE_PARM_DESC(ins, "Number of MIDI inputs."); +module_param(droponfull, bool, 0444); +MODULE_PARM_DESC(droponfull, "Flag to enable drop-on-full buffer mode"); +module_param(throttle_tx, bool, 0444); +MODULE_PARM_DESC(throttle_tx, "Do not continuously fill TX FIFO"); +module_param(throttle_delay, int, 0444); +MODULE_PARM_DESC(throttle_delay, "Time in us between each TX write"); +module_param(flow_control, bool, 0444); +MODULE_PARM_DESC(flow_control, "Use RTS/CTS flow control"); +module_param(fifo_limit, int, 0444); +MODULE_PARM_DESC(fifo_limit, "Maximum TX bytes per write"); + +module_param(adaptor, int, 0444); +MODULE_PARM_DESC(adaptor, "Type of adaptor."); + +/*#define SNDRV_SERIAL_MS124W_MB_NOCOMBO 1*/ /* Address outs as 0-3 instead of bitmap */ + +#define SNDRV_SERIAL_MAX_OUTS 16 /* max 64, min 16 */ +#define SNDRV_SERIAL_MAX_INS 16 /* max 64, min 16 */ + +#define TX_BUFF_SIZE (1<<15) /* Must be 2^n */ +#define TX_BUFF_MASK (TX_BUFF_SIZE - 1) + +#define AMBA_ISR_PASS_LIMIT 256 + +#define SERIAL_MODE_NOT_OPENED (0) +#define SERIAL_MODE_INPUT_OPEN (1 << 0) +#define SERIAL_MODE_OUTPUT_OPEN (1 << 1) +#define SERIAL_MODE_INPUT_TRIGGERED (1 << 2) +#define SERIAL_MODE_OUTPUT_TRIGGERED (1 << 3) + +struct snd_uart_pl011 { + struct amba_device *dev; + struct snd_card *card; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *midi_output[SNDRV_SERIAL_MAX_OUTS]; + struct snd_rawmidi_substream *midi_input[SNDRV_SERIAL_MAX_INS]; + + int filemode; /* open status of file */ + + spinlock_t open_lock; + wait_queue_head_t drain_wait; + int draining; + + int irq; + + void __iomem *membase; + unsigned long mapbase; + struct resource *res_base; + + struct clk *clk; + unsigned long clk_rate; + unsigned int speed; + + /* parameter for using of write loop */ + short int fifo_limit; + short int fifo_count; + + /* type of adaptor */ + int adaptor; + + /* inputs */ + int prev_in; + unsigned char rstatus; + + /* outputs */ + int prev_out; + unsigned char prev_status[SNDRV_SERIAL_MAX_OUTS]; + + /* write buffer and its writing/reading position */ + unsigned char tx_buff[TX_BUFF_SIZE]; + int buff_in_count; + int buff_in; + int buff_out; + int drop_on_full; + int throttle_tx; + int flow_control; + ktime_t throttle_delay; + + int timer_running; + u16 control_reg; + enum { + TX_IDLE, + TX_BLOCK_RX, + TX_BUSY, + } tx_state; + int tx_attempts; + struct hrtimer buffer_timer; +}; + +static inline void snd_uart_pl011_stop_rx(struct snd_uart_pl011 *uart) +{ + writew(uart->control_reg & ~UART011_CR_RTS, uart->membase + UART011_CR); +} + +static inline void snd_uart_pl011_start_rx(struct snd_uart_pl011 *uart) +{ + writew(uart->control_reg | UART011_CR_RTS, uart->membase + UART011_CR); +} + +/* This macro is only used in snd_uart_pl011_io_loop */ +static inline void snd_uart_pl011_buffer_output(struct snd_uart_pl011 *uart) +{ + unsigned short buff_out = uart->buff_out; + if (uart->buff_in_count > 0) { + writeb(uart->tx_buff[buff_out], uart->membase + UART01x_DR); + uart->fifo_count++; + buff_out++; + buff_out &= TX_BUFF_MASK; + uart->buff_out = buff_out; + uart->buff_in_count--; + } +} + +static inline int snd_uart_pl011_write_fifo_timer(struct snd_uart_pl011 * uart) +{ + /* Check CTS - FIFO is empty */ + if (!uart->flow_control || + readw(uart->membase + UART01x_FR) & UART01x_FR_CTS) { + while (uart->fifo_count < uart->fifo_limit + && uart->buff_in_count > 0) + snd_uart_pl011_buffer_output(uart); + + uart->tx_state = TX_BUSY; + if (unlikely(uart->draining)) + wake_up(&uart->drain_wait); + return 1; + } + return 0; +} + +static inline void snd_uart_pl011_start_timer(struct snd_uart_pl011 *uart) +{ + if (!uart->timer_running) { + if (uart->flow_control) { + snd_uart_pl011_stop_rx(uart); + uart->tx_state = TX_BLOCK_RX; + uart->tx_attempts = TIMER_ATTEMPTS_LIMIT; + } else { + snd_uart_pl011_write_fifo_timer(uart); + } + hrtimer_start(&uart->buffer_timer, uart->throttle_delay, + HRTIMER_MODE_REL); + uart->timer_running = 1; + } +} + +static inline void snd_uart_pl011_del_timer(struct snd_uart_pl011 *uart) +{ + hrtimer_cancel(&uart->buffer_timer); + uart->timer_running = 0; +} + +/* This loop should be called with interrupts disabled + * We don't want to interrupt this, + * as we're already handling an interrupt + * + * PL011 interrupts that must be serviced (and cleared): + * UART011_RXIC (RX FIFO becoming full) + * UART011_TXIC (TX FIFO becoming empty) + * UART011_RTIC (RX timeout reached) + */ +static void snd_uart_pl011_io_loop(struct snd_uart_pl011 * uart) +{ + unsigned char c; + int substream; + int pass_counter = AMBA_ISR_PASS_LIMIT; + + /* recall previous stream */ + substream = uart->prev_in; + + /* Read Loop */ + while (!(readw(uart->membase + UART01x_FR) & UART01x_FR_RXFE)) { + /* while receive data ready */ + c = readb(uart->membase + UART01x_DR); + + /* keep track of last status byte */ + if (c & 0x80) + uart->rstatus = c; + + /* handle stream switch */ + if (uart->adaptor == SNDRV_SERIAL_GENERIC) { + if (uart->rstatus == 0xf5) { + if (c <= SNDRV_SERIAL_MAX_INS && c > 0) + substream = c - 1; + if (c != 0xf5) + /* prevent future bytes from being + interpreted as streams */ + uart->rstatus = 0; + } else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) + && uart->midi_input[substream]) + snd_rawmidi_receive(uart->midi_input[substream], + &c, 1); + } else if ((uart->filemode & SERIAL_MODE_INPUT_OPEN) && + uart->midi_input[substream]) + snd_rawmidi_receive(uart->midi_input[substream], &c, 1); + + if (readw(uart->membase + UART01x_FR) & UART011_FR_RXFF) + snd_printk(KERN_WARNING + "%s: Overrun on device at 0x%lx\n", + uart->rmidi->name, uart->mapbase); + + if (pass_counter-- == 0) break; + } + + /* remember the last stream */ + uart->prev_in = substream; + + if (uart->throttle_tx) return; + + /* Check write status, if we get a TX fifo interrupt, + * it's possible that there are still 2 bytes of data + * in the FIFO */ + if (readw(uart->membase + UART011_MIS) & UART011_TXIS) { + writew(UART011_TXIC, uart->membase + UART011_ICR); + uart->fifo_count = 2; + if (unlikely(uart->draining)) wake_up(&uart->drain_wait); + } + + if (readw(uart->membase + UART01x_FR) & UART011_FR_TXFE) + uart->fifo_count = 0; + + /* Write loop */ + while (uart->fifo_count < uart->fifo_limit /* Can we write ? */ + && uart->buff_in_count > 0) /* Do we want to? */ + snd_uart_pl011_buffer_output(uart); +} + +static irqreturn_t snd_uart_pl011_interrupt(int irq, void *dev_id) +{ + struct snd_uart_pl011 *uart; + + uart = dev_id; + spin_lock(&uart->open_lock); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) { + spin_unlock(&uart->open_lock); + return IRQ_NONE; + } + + snd_uart_pl011_io_loop(uart); + spin_unlock(&uart->open_lock); + return IRQ_HANDLED; +} + +static enum hrtimer_restart snd_uart_pl011_buffer_timer(struct hrtimer *handle) +{ + struct snd_uart_pl011 *uart = + container_of(handle, struct snd_uart_pl011, buffer_timer); + enum hrtimer_restart restart = HRTIMER_NORESTART; + int double_delay = 0; + + spin_lock(&uart->open_lock); + + switch (uart->tx_state) { + case TX_IDLE: + snd_uart_pl011_stop_rx(uart); + uart->tx_attempts = TIMER_ATTEMPTS_LIMIT; + uart->tx_state = TX_BLOCK_RX; + restart = HRTIMER_RESTART; + break; + + case TX_BLOCK_RX: + /* Write FIFO, reschedule if necessary */ + if (snd_uart_pl011_write_fifo_timer(uart)) { + restart = HRTIMER_RESTART; + } else { + /* If the cable becomes disconnected, we may never get + * CTS. Therefore limit the number of times we call the + * timer */ + if (uart->tx_attempts--) restart = HRTIMER_RESTART; + else uart->tx_state = TX_IDLE; + } + + break; + + case TX_BUSY: + if (readw(uart->membase + UART01x_FR) & UART01x_FR_BUSY) { + /* Still writing, reschedule */ + restart = HRTIMER_RESTART; + } else { + /* Finished writing allow receive */ + uart->fifo_count = 0; + if (uart->flow_control) { + double_delay = 1; + uart->tx_state = TX_IDLE; + snd_uart_pl011_start_rx(uart); + } else { + uart->tx_state = TX_BLOCK_RX; + } + if (uart->buff_in_count > 0) { + restart = HRTIMER_RESTART; + } + } + break; + } + + if (restart == HRTIMER_RESTART) { + hrtimer_forward_now(&uart->buffer_timer, + double_delay ? ktime_add(uart->throttle_delay, + uart->throttle_delay) : uart->throttle_delay); + } else + uart->timer_running = 0; + + spin_unlock(&uart->open_lock); + + return restart; +} + +static int snd_uart_pl011_detect(struct snd_uart_pl011 *uart) +{ + int ok = 0; + u16 status; + int timeout = 1000; + + writew(0, uart->membase + UART011_IMSC); /* Disable interrupts */ + + writew(UART01x_CR_UARTEN + | UART011_CR_TXE + | UART011_CR_LBE + | UART011_CR_RXE + , uart->membase + UART011_CR); + + writew(0, uart->membase + UART011_FBRD); + writew(1, uart->membase + UART011_IBRD); + writew(0, uart->membase + UART011_LCRH); + writew(0x55, uart->membase + UART01x_DR); + while (timeout && + (readw(uart->membase + UART01x_FR) & UART01x_FR_BUSY)) { + timeout--; + barrier(); + } + status = readw(uart->membase + UART01x_DR) & 0xff; + writew(0xffff, uart->membase + UART011_ICR); /* Clear interrupts */ + + /* Loopback with WLEN == 5 turns 0x55 into 0x15 */ + if (status == 0x15) ok = 1; + + return ok; +} + +static void snd_uart_pl011_do_open(struct snd_uart_pl011 * uart) +{ + unsigned int quot; + u16 reg; + + /* Initialize basic variables */ + uart->buff_in_count = 0; + uart->buff_in = 0; + uart->buff_out = 0; + uart->fifo_count = 0; + uart->tx_state = TX_IDLE; + + writew(UART01x_CR_UARTEN /* Enable UART */ + | UART011_CR_TXE /* Enable UART TX */ + | UART011_CR_RXE /* Enable UART RX */ + , uart->membase + UART011_CR); + + uart->clk_rate = clk_get_rate(uart->clk); + quot = DIV_ROUND_CLOSEST(uart->clk_rate * 4, uart->speed); + + writew(quot & 0x3f, uart->membase + UART011_FBRD); + writew(quot >> 6, uart->membase + UART011_IBRD); + + writew(UART01x_LCRH_FEN /* Enable FIFOs */ + | UART01x_LCRH_WLEN_8 /* 8 Bit words, 1 Stop, No Parity */ + , uart->membase + UART011_LCRH); /* FIFO Control Register */ + + writew(UART011_IFLS_RX2_8 /* Set RX FIFO trigger at 4-bytes */ + | UART011_IFLS_TX1_8 /* Set TX FIFO trigger at 2-bytes */ + , uart->membase + UART011_IFLS); + + reg = readw(uart->membase + UART011_CR); + switch (uart->adaptor) { + default: + reg |= UART011_CR_RTS + /* Hardware RTS unless throttling*/ + | (uart->throttle_tx ? 0 : UART011_CR_RTSEN) + /* Hardware CTS unless throttling */ + | (uart->throttle_tx ? 0 : UART011_CR_CTSEN); + writew(reg, uart->membase + UART011_CR); + break; + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* FIXME: MS-124W can draw power from RTS and DTR if they + are in opposite states. */ + break; + case SNDRV_SERIAL_MS124T: + /* FIXME: MS-124T can draw power from RTS and/or DTR (preferably + both) if they are both asserted. */ + break; + } + + uart->control_reg = reg; + + writew(UART011_RXIC /* Clear corresponting interrupts */ + | UART011_TXIC + | UART011_RTIC + , uart->membase + UART011_ICR); + + if (uart->adaptor == SNDRV_SERIAL_MS124W_SA) { + /* FIXME: Enable RX data and Modem Status */ + } else if (uart->adaptor == SNDRV_SERIAL_GENERIC) { + writew(UART011_RXIM /* Enable RX FIFO interrupt */ + | UART011_RTIM /* Enable RX timeout interrupt */ + /* Enable TX FIFO if not using throttling */ + | (uart->throttle_tx ? 0 : UART011_TXIM) + , uart->membase + UART011_IMSC); + } else { + /* FIXME: Enable RX data and THRI */ + } +} + +static void snd_uart_pl011_do_close(struct snd_uart_pl011 * uart) +{ + writew(0, uart->membase + UART011_IMSC); /* Interrupt enable Register */ + writew(0xffff, uart->membase + UART011_ICR); + + switch (uart->adaptor) { + default: + /* Disable everything */ + writew(0, uart->membase + UART011_CR); + break; + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* FIXME: MS-124W can draw power from RTS and DTR if they + are in opposite states; leave it powered. */ + break; + case SNDRV_SERIAL_MS124T: + /* FIXME: MS-124T can draw power from RTS and/or DTR (preferably + both) if they are both asserted; leave it powered. */ + break; + } + +} + +static int snd_uart_pl011_input_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart_pl011_do_open(uart); + uart->filemode |= SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = substream; + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +} + +static int snd_uart_pl011_input_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + uart->filemode &= ~SERIAL_MODE_INPUT_OPEN; + uart->midi_input[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart_pl011_do_close(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +} + +static void snd_uart_pl011_input_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (up) + uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED; + else + uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED; + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +static int snd_uart_pl011_output_open(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart_pl011_do_open(uart); + uart->filemode |= SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = substream; + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +}; + +static int snd_uart_pl011_output_close(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN; + uart->midi_output[substream->number] = NULL; + if (uart->filemode == SERIAL_MODE_NOT_OPENED) + snd_uart_pl011_do_close(uart); + spin_unlock_irqrestore(&uart->open_lock, flags); + return 0; +}; + +static inline int snd_uart_pl011_buffer_can_write(struct snd_uart_pl011 *uart, + int Num) +{ + if (uart->buff_in_count + Num < TX_BUFF_SIZE) + return 1; + else + return 0; +} + +static inline int snd_uart_pl011_write_buffer(struct snd_uart_pl011 *uart, + unsigned char byte) +{ + unsigned short buff_in = uart->buff_in; + if (uart->buff_in_count < TX_BUFF_SIZE) { + uart->tx_buff[buff_in] = byte; + buff_in++; + buff_in &= TX_BUFF_MASK; + uart->buff_in = buff_in; + uart->buff_in_count++; + if (uart->throttle_tx) + snd_uart_pl011_start_timer(uart); + return 1; + } else + return 0; +} + +static int snd_uart_pl011_output_byte(struct snd_uart_pl011 *uart, + struct snd_rawmidi_substream *substream, + unsigned char midi_byte) +{ + if ((uart->buff_in_count == 0) && !uart->throttle_tx) { + /* Tx Buffer Empty - try to write immediately */ + if (readw(uart->membase + UART01x_FR) & UART011_FR_TXFE) { + uart->fifo_count = 1; + writeb(midi_byte, uart->membase + UART01x_DR); + } else { + if (uart->fifo_count < uart->fifo_limit) { + uart->fifo_count++; + writeb(midi_byte, uart->membase + UART01x_DR); + } else { + /* Cannot write (buffer empty) - + * put char in buffer */ + snd_uart_pl011_write_buffer(uart, midi_byte); + } + } + } else { + if (!snd_uart_pl011_write_buffer(uart, midi_byte)) { + snd_printk(KERN_WARNING + "%s: Buffer overrun on device at 0x%lx\n", + uart->rmidi->name, uart->mapbase); + return 0; + } + } + return 1; +} + +static void snd_uart_pl011_output_write(struct snd_rawmidi_substream *substream) +{ + unsigned char midi_byte, addr_byte; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + char first; + static unsigned long lasttime = 0; + + /* Interrupts are disabled during the updating of the tx_buff, + * since it is 'bad' to have two processes updating the same + * variables (ie buff_in & buff_out) + */ + + if (uart->adaptor == SNDRV_SERIAL_MS124W_MB) { + while (1) { + /* buffer full? */ + /* in this mode we need two bytes of space */ + if (uart->buff_in_count > TX_BUFF_SIZE - 2) + break; + if (snd_rawmidi_transmit(substream, &midi_byte, 1) != 1) + break; +#ifdef SNDRV_SERIAL_MS124W_MB_NOCOMBO + /* select exactly one of the four ports */ + addr_byte = (1 << (substream->number + 4)) | 0x08; +#else + /* select any combination of the four ports */ + addr_byte = (substream->number << 4) | 0x08; + /* ...except none */ + if (addr_byte == 0x08) + addr_byte = 0xf8; +#endif + snd_uart_pl011_output_byte(uart, substream, addr_byte); + /* send midi byte */ + snd_uart_pl011_output_byte(uart, substream, midi_byte); + } + } else { + first = 0; + while (snd_rawmidi_transmit_peek(substream, &midi_byte, 1) == 1) { + /* Also send F5 after 3 seconds with no data + * to handle device disconnect */ + if (first == 0 && + (uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS || + uart->adaptor == SNDRV_SERIAL_GENERIC) && + (uart->prev_out != substream->number || + time_after(jiffies, lasttime + 3*HZ))) { + + if (snd_uart_pl011_buffer_can_write(uart, 3)) { + /* Roland Soundcanvas part selection */ + /* If this substream of the data is + * different previous substream + * in this uart, send the change part + * event + */ + uart->prev_out = substream->number; + /* change part */ + snd_uart_pl011_output_byte(uart, substream, + 0xf5); + /* data */ + snd_uart_pl011_output_byte(uart, substream, + uart->prev_out + 1); + /* If midi_byte is a data byte, + * send the previous status byte */ + if (midi_byte < 0x80 && + uart->adaptor == SNDRV_SERIAL_SOUNDCANVAS) + snd_uart_pl011_output_byte(uart, substream, uart->prev_status[uart->prev_out]); + } else if (!uart->drop_on_full) + break; + + } + + /* send midi byte */ + if (!snd_uart_pl011_output_byte(uart, substream, midi_byte) && + !uart->drop_on_full ) + break; + + if (midi_byte >= 0x80 && midi_byte < 0xf0) + uart->prev_status[uart->prev_out] = midi_byte; + first = 1; + + snd_rawmidi_transmit_ack( substream, 1 ); + } + lasttime = jiffies; + } +} + +static void snd_uart_pl011_output_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + unsigned long flags; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + + spin_lock_irqsave(&uart->open_lock, flags); + if (up) { + uart->filemode |= SERIAL_MODE_OUTPUT_TRIGGERED; + snd_uart_pl011_output_write(substream); + } else { + uart->filemode &= ~SERIAL_MODE_OUTPUT_TRIGGERED; + snd_uart_pl011_del_timer(uart); + } + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +static void snd_uart_pl011_output_drain(struct snd_rawmidi_substream *substream) +{ + unsigned long flags; + struct snd_uart_pl011 *uart = substream->rmidi->private_data; + DEFINE_WAIT(wait); + long timeout; + + spin_lock_irqsave(&uart->open_lock, flags); + + if (uart->filemode == SERIAL_MODE_NOT_OPENED) { + spin_unlock_irqrestore(&uart->open_lock, flags); + return; + } + + if (uart->buff_in_count || uart->fifo_count) { + uart->draining++; + do { + timeout = msecs_to_jiffies(50); + prepare_to_wait(&uart->drain_wait, &wait, + TASK_UNINTERRUPTIBLE); + spin_unlock_irqrestore(&uart->open_lock, flags); + timeout = schedule_timeout(timeout); + spin_lock_irqsave(&uart->open_lock, flags); + } while ((uart->buff_in_count || uart->fifo_count) && timeout); + uart->draining = 0; + finish_wait(&uart->drain_wait, &wait); + } + spin_unlock_irqrestore(&uart->open_lock, flags); +} + +static struct snd_rawmidi_ops snd_uart_pl011_output = +{ + .open = snd_uart_pl011_output_open, + .close = snd_uart_pl011_output_close, + .trigger = snd_uart_pl011_output_trigger, + .drain = snd_uart_pl011_output_drain, +}; + +static struct snd_rawmidi_ops snd_uart_pl011_input = +{ + .open = snd_uart_pl011_input_open, + .close = snd_uart_pl011_input_close, + .trigger = snd_uart_pl011_input_trigger, +}; + +static int snd_uart_pl011_free(struct snd_uart_pl011 *uart) +{ + if (uart->irq >= 0) + free_irq(uart->irq, uart); + if (!IS_ERR(uart->clk) && uart->clk) clk_disable_unprepare(uart->clk); + if (uart->dev) pinctrl_pm_select_sleep_state(&uart->dev->dev); + release_and_free_resource(uart->res_base); + kfree(uart); + return 0; +}; + +static int snd_uart_pl011_dev_free(struct snd_device *device) +{ + struct snd_uart_pl011 *uart = device->device_data; + return snd_uart_pl011_free(uart); +} + +static int snd_uart_pl011_create(struct snd_card *card, + struct amba_device *devptr, + unsigned int speed, + int adaptor, + int droponfull, + int throttle_tx, + int throttle_delay, + int flow_control, + int fifo_limit, + struct snd_uart_pl011 **ruart) +{ + static struct snd_device_ops ops = { + .dev_free = snd_uart_pl011_dev_free, + }; + struct snd_uart_pl011 *uart; + int err; + void __iomem *membase; + + + if ((uart = kzalloc(sizeof(*uart), GFP_KERNEL)) == NULL) + return -ENOMEM; + + uart->irq = -1; + uart->res_base = request_mem_region(devptr->res.start, + resource_size(&devptr->res), "Serial MIDI"); + + if (uart->res_base == NULL) { + snd_printk(KERN_ERR "pl011: can't grab port\n"); + return -EBUSY; + } + + membase = devm_ioremap(&devptr->dev, devptr->res.start, + resource_size(&devptr->res)); + + if (!membase) { + snd_printk(KERN_ERR "pl011: ioremap error\n"); + snd_uart_pl011_free(uart); + return -ENOMEM; + } + + uart->clk = devm_clk_get(&devptr->dev, NULL); + if (IS_ERR(uart->clk)) { + snd_printk(KERN_ERR "pl011: unable to get clock resource\n"); + snd_uart_pl011_free(uart); + return -ENODEV; + } + + clk_prepare_enable(uart->clk); + + uart->adaptor = adaptor; + uart->card = card; + spin_lock_init(&uart->open_lock); + uart->membase = membase; + uart->mapbase = devptr->res.start; + uart->drop_on_full = droponfull; + uart->throttle_tx = throttle_tx; + uart->throttle_delay = ns_to_ktime(throttle_delay * 1000); + uart->flow_control = flow_control; + uart->fifo_limit = fifo_limit; + uart->speed = speed; + uart->prev_out = -1; + memset(uart->prev_status, 0x80, + sizeof(unsigned char) * SNDRV_SERIAL_MAX_OUTS); + hrtimer_init(&uart->buffer_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + uart->buffer_timer.function = snd_uart_pl011_buffer_timer; + + if (snd_uart_pl011_detect(uart) == 0) { + snd_printk(KERN_ERR "no UART detected\n"); + snd_uart_pl011_free(uart); + return -ENODEV; + } + + if (request_irq(devptr->irq[0], snd_uart_pl011_interrupt, + 0, "Serial MIDI", uart)) { + snd_printk(KERN_ERR "unable to request IRQ\n"); + snd_uart_pl011_free(uart); + return -ENODEV; + } + uart->irq = devptr->irq[0]; + + init_waitqueue_head(&uart->drain_wait); + + snd_printk(KERN_INFO "Detected PL011 at 0x%lx using irq: %i\n", + uart->mapbase, uart->irq); + + /* Register device */ + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, uart, &ops)) < 0) { + snd_uart_pl011_free(uart); + return err; + } + + /* FIXME: CTS/RTS pins */ + uart->dev = devptr; + pinctrl_pm_select_default_state(&uart->dev->dev); + + switch (uart->adaptor) { + case SNDRV_SERIAL_MS124W_SA: + case SNDRV_SERIAL_MS124W_MB: + /* FIXME: MS-124W can draw power from RTS and DTR if they + are in opposite states. */ + break; + case SNDRV_SERIAL_MS124T: + /* FIXME: MS-124T can draw power from RTS and/or DTR (preferably + both) if they are asserted. */ + break; + default: + break; + } + + if (ruart) + *ruart = uart; + + return 0; +} + +static void snd_uart_pl011_substreams(struct snd_rawmidi_str *stream) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + sprintf(substream->name, "Serial MIDI %d", substream->number + 1); + } +} + +static int snd_uart_pl011_rmidi(struct snd_uart_pl011 *uart, int device, + int outs, int ins, + struct snd_rawmidi **rmidi) +{ + struct snd_rawmidi *rrawmidi; + int err; + + err = snd_rawmidi_new(uart->card, "UART Serial MIDI", device, + outs, ins, &rrawmidi); + if (err < 0) + return err; + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &snd_uart_pl011_input); + snd_rawmidi_set_ops(rrawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &snd_uart_pl011_output); + strcpy(rrawmidi->name, "Serial MIDI"); + snd_uart_pl011_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]); + snd_uart_pl011_substreams(&rrawmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]); + rrawmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + rrawmidi->private_data = uart; + if (rmidi) + *rmidi = rrawmidi; + return 0; +} + +static int snd_serial_probe(struct amba_device *devptr, + const struct amba_id *id) +{ + struct snd_card *card; + struct snd_uart_pl011 *uart; + int err; + + switch (adaptor) { + case SNDRV_SERIAL_SOUNDCANVAS: + ins = 1; + break; + case SNDRV_SERIAL_MS124T: + case SNDRV_SERIAL_MS124W_SA: + outs = 1; + ins = 1; + break; + case SNDRV_SERIAL_MS124W_MB: + outs = 16; + ins = 1; + break; + case SNDRV_SERIAL_GENERIC: + break; + default: + snd_printk(KERN_ERR + "Adaptor type is out of range 0-%d (%d)\n", + SNDRV_SERIAL_MAX_ADAPTOR, adaptor); + return -ENODEV; + } + + if (outs < 1 || outs > SNDRV_SERIAL_MAX_OUTS) { + snd_printk(KERN_ERR + "Count of outputs is out of range 1-%d (%d)\n", + SNDRV_SERIAL_MAX_OUTS, outs); + return -ENODEV; + } + + if (ins < 1 || ins > SNDRV_SERIAL_MAX_INS) { + snd_printk(KERN_ERR + "Count of inputs is out of range 1-%d (%d)\n", + SNDRV_SERIAL_MAX_INS, ins); + return -ENODEV; + } + + err = snd_card_create(-1, NULL, THIS_MODULE, 0, &card); + if (err < 0) + return err; + + strcpy(card->driver, "Serial"); + strcpy(card->shortname, "Serial MIDI (PL011)"); + + if ((err = snd_uart_pl011_create(card, devptr, + speed, + adaptor, + droponfull, + throttle_tx, + throttle_delay, + flow_control, + fifo_limit, + &uart)) < 0) + goto _err; + + err = snd_uart_pl011_rmidi(uart, 0, outs, ins, &uart->rmidi); + if (err < 0) + goto _err; + + sprintf(card->longname, "%s [%s] at %#lx, irq %d", + card->shortname, + adaptor_names[uart->adaptor], + (unsigned long) uart->membase, + uart->irq); + + snd_card_set_dev(card, &devptr->dev); + + if ((err = snd_card_register(card)) < 0) + goto _err; + + amba_set_drvdata(devptr, card); + return 0; + + _err: + snd_card_free(card); + return err; +} + +static int snd_serial_remove(struct amba_device *devptr) +{ + snd_card_free(amba_get_drvdata(devptr)); + return 0; +} + +#define SND_SERIAL_DRIVER "snd_serial_pl011" + +static struct amba_id snd_serial_ids[] = { + { + .id = 0x00041011, + .mask = 0x000fffff, + }, + { 0, 0 }, +}; + +static struct amba_driver snd_serial_driver = { + .drv = { + .name = SND_SERIAL_DRIVER, + }, + .id_table = snd_serial_ids, + .probe = snd_serial_probe, + .remove = snd_serial_remove, +}; + +static int __init alsa_card_serial_init(void) +{ + snd_printk(KERN_INFO "snd-serial-pl011: PL011 based MIDI device\n"); + return amba_driver_register(&snd_serial_driver); +} + +static void __exit alsa_card_serial_exit(void) +{ + amba_driver_unregister(&snd_serial_driver); +} + +module_init(alsa_card_serial_init) +module_exit(alsa_card_serial_exit) diff -uprN sound-orig/sound/drivers/Kconfig sound-new/sound/drivers/Kconfig --- sound-orig/sound/drivers/Kconfig 2014-11-07 11:24:19.000000000 +1100 +++ sound-new/sound/drivers/Kconfig 2014-11-07 11:19:21.000000000 +1100 @@ -148,6 +148,22 @@ config SND_SERIAL_U16550 To compile this driver as a module, choose M here: the module will be called snd-serial-u16550. +config SND_SERIAL_PL011 + tristate "PL011 serial MIDI driver" + select SND_RAWMIDI + help + To include support for MIDI serial port interfaces, say Y here + and read . + This driver works with serial PL011 uarts, as found on the + Raspberry pi. + + This driver accesses the serial port hardware directly, so + make sure that the standard serial driver isn't used or + deactivated with setserial before loading this driver. + + To compile this driver as a module, choose M here: the module + will be called snd-serial-pl011. + config SND_MPU401 tristate "Generic MPU-401 UART driver" select SND_MPU401_UART