diff options
52 files changed, 6259 insertions, 430 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 9a91d9e3f1f..0df29ce05e5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2561,12 +2561,18 @@ L: linux-kernel@vger.kernel.org W: http://www.atnf.csiro.au/~rgooch/linux/kernel-patches.html S: Maintained -MULTIMEDIA CARD (MMC) AND SECURE DIGITAL (SD) SUBSYSTEM +MULTIMEDIA CARD (MMC), SECURE DIGITAL (SD) AND SDIO SUBSYSTEM P: Pierre Ossman M: drzeus-mmc@drzeus.cx L: linux-kernel@vger.kernel.org S: Maintained +MULTIMEDIA CARD (MMC) ETC. OVER SPI +P: David Brownell +M: dbrownell@users.sourceforge.net +L: linux-kernel@vger.kernel.org +S: Odd fixes + MULTISOUND SOUND DRIVER P: Andrew Veliath M: andrewtv@usa.net diff --git a/arch/arm/mach-imx/mx1ads.c b/arch/arm/mach-imx/mx1ads.c index da893c80d47..a9778c1587a 100644 --- a/arch/arm/mach-imx/mx1ads.c +++ b/arch/arm/mach-imx/mx1ads.c @@ -116,7 +116,7 @@ static struct platform_device *devices[] __initdata = { }; #ifdef CONFIG_MMC_IMX -static int mx1ads_mmc_card_present(void) +static int mx1ads_mmc_card_present(struct device *dev) { /* MMC/SD Card Detect is PB 20 on MX1ADS V1.0.7 */ return (SSR(1) & (1 << 20) ? 0 : 1); diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index a49cb9737cd..aa8a4e46194 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -32,3 +32,10 @@ config MMC_BLOCK_BOUNCE If unsure, say Y here. +config SDIO_UART + tristate "SDIO UART/GPS class support" + depends on MMC + help + SDIO function driver for SDIO cards that implements the UART + class, as well as the GPS class which appears like a UART. + diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index cf8c939867f..fc5a784cfa1 100644 --- a/drivers/mmc/card/Makefile +++ b/drivers/mmc/card/Makefile @@ -9,3 +9,5 @@ endif obj-$(CONFIG_MMC_BLOCK) += mmc_block.o mmc_block-objs := block.o queue.o +obj-$(CONFIG_SDIO_UART) += sdio_uart.o + diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 93fe2e5dd61..e38d5a3b2a8 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -151,17 +151,19 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) cmd.opcode = MMC_APP_CMD; cmd.arg = card->rca << 16; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, 0); - if ((err != MMC_ERR_NONE) || !(cmd.resp[0] & R1_APP_CMD)) + if (err) + return (u32)-1; + if (!mmc_host_is_spi(card->host) && !(cmd.resp[0] & R1_APP_CMD)) return (u32)-1; memset(&cmd, 0, sizeof(struct mmc_command)); cmd.opcode = SD_APP_SEND_NUM_WR_BLKS; cmd.arg = 0; - cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; memset(&data, 0, sizeof(struct mmc_data)); @@ -192,7 +194,7 @@ static u32 mmc_sd_num_wr_blocks(struct mmc_card *card) mmc_wait_for_req(card->host, &mrq); - if (cmd.error != MMC_ERR_NONE || data.error != MMC_ERR_NONE) + if (cmd.error || data.error) return (u32)-1; blocks = ntohl(blocks); @@ -220,17 +222,15 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) brq.cmd.arg = req->sector; if (!mmc_card_blockaddr(card)) brq.cmd.arg <<= 9; - brq.cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + brq.cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC; brq.data.blksz = 1 << md->block_bits; brq.stop.opcode = MMC_STOP_TRANSMISSION; brq.stop.arg = 0; - brq.stop.flags = MMC_RSP_R1B | MMC_CMD_AC; + brq.stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC; brq.data.blocks = req->nr_sectors >> (md->block_bits - 9); if (brq.data.blocks > card->host->max_blk_count) brq.data.blocks = card->host->max_blk_count; - mmc_set_data_timeout(&brq.data, card, rq_data_dir(req) != READ); - /* * If the host doesn't support multiple block writes, force * block writes to single block. SD cards are excepted from @@ -243,8 +243,12 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) brq.data.blocks = 1; if (brq.data.blocks > 1) { - brq.data.flags |= MMC_DATA_MULTI; - brq.mrq.stop = &brq.stop; + /* SPI multiblock writes terminate using a special + * token, not a STOP_TRANSMISSION request. + */ + if (!mmc_host_is_spi(card->host) + || rq_data_dir(req) == READ) + brq.mrq.stop = &brq.stop; readcmd = MMC_READ_MULTIPLE_BLOCK; writecmd = MMC_WRITE_MULTIPLE_BLOCK; } else { @@ -261,6 +265,8 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) brq.data.flags |= MMC_DATA_WRITE; } + mmc_set_data_timeout(&brq.data, card); + brq.data.sg = mq->sg; brq.data.sg_len = mmc_queue_map_sg(mq); @@ -302,7 +308,7 @@ static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) goto cmd_err; } - if (rq_data_dir(req) != READ) { + if (!mmc_host_is_spi(card->host) && rq_data_dir(req) != READ) { do { int err; @@ -510,7 +516,7 @@ mmc_blk_set_blksize(struct mmc_blk_data *md, struct mmc_card *card) mmc_claim_host(card->host); cmd.opcode = MMC_SET_BLOCKLEN; cmd.arg = 1 << md->block_bits; - cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC; err = mmc_wait_for_cmd(card->host, &cmd, 5); mmc_release_host(card->host); diff --git a/drivers/mmc/card/sdio_uart.c b/drivers/mmc/card/sdio_uart.c new file mode 100644 index 00000000000..d552de68311 --- /dev/null +++ b/drivers/mmc/card/sdio_uart.c @@ -0,0 +1,1158 @@ +/* + * linux/drivers/mmc/card/sdio_uart.c - SDIO UART/GPS driver + * + * Based on drivers/serial/8250.c and drivers/serial/serial_core.c + * by Russell King. + * + * Author: Nicolas Pitre + * Created: June 15, 2007 + * Copyright: MontaVista Software, Inc. + * + * 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. + */ + +/* + * Note: Although this driver assumes a 16550A-like UART implementation, + * it is not possible to leverage the common 8250/16550 driver, nor the + * core UART infrastructure, as they assumes direct access to the hardware + * registers, often under a spinlock. This is not possible in the SDIO + * context as SDIO access functions must be able to sleep. + * + * Because we need to lock the SDIO host to ensure an exclusive access to + * the card, we simply rely on that lock to also prevent and serialize + * concurrent access to the same port. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/serial_reg.h> +#include <linux/circ_buf.h> +#include <linux/gfp.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> + +#include <linux/mmc/core.h> +#include <linux/mmc/card.h> +#include <linux/mmc/sdio_func.h> +#include <linux/mmc/sdio_ids.h> + + +#define UART_NR 8 /* Number of UARTs this driver can handle */ + + +#define UART_XMIT_SIZE PAGE_SIZE +#define WAKEUP_CHARS 256 + +#define circ_empty(circ) ((circ)->head == (circ)->tail) +#define circ_clear(circ) ((circ)->head = (circ)->tail = 0) + +#define circ_chars_pending(circ) \ + (CIRC_CNT((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + +#define circ_chars_free(circ) \ + (CIRC_SPACE((circ)->head, (circ)->tail, UART_XMIT_SIZE)) + + +struct uart_icount { + __u32 cts; + __u32 dsr; + __u32 rng; + __u32 dcd; + __u32 rx; + __u32 tx; + __u32 frame; + __u32 overrun; + __u32 parity; + __u32 brk; +}; + +struct sdio_uart_port { + struct kref kref; + struct tty_struct *tty; + unsigned int index; + unsigned int opened; + struct mutex open_lock; + struct sdio_func *func; + struct mutex func_lock; + struct task_struct *in_sdio_uart_irq; + unsigned int regs_offset; + struct circ_buf xmit; + spinlock_t write_lock; + struct uart_icount icount; + unsigned int uartclk; + unsigned int mctrl; + unsigned int read_status_mask; + unsigned int ignore_status_mask; + unsigned char x_char; + unsigned char ier; + unsigned char lcr; +}; + +static struct sdio_uart_port *sdio_uart_table[UART_NR]; +static DEFINE_SPINLOCK(sdio_uart_table_lock); + +static int sdio_uart_add_port(struct sdio_uart_port *port) +{ + int index, ret = -EBUSY; + + kref_init(&port->kref); + mutex_init(&port->open_lock); + mutex_init(&port->func_lock); + spin_lock_init(&port->write_lock); + + spin_lock(&sdio_uart_table_lock); + for (index = 0; index < UART_NR; index++) { + if (!sdio_uart_table[index]) { + port->index = index; + sdio_uart_table[index] = port; + ret = 0; + break; + } + } + spin_unlock(&sdio_uart_table_lock); + + return ret; +} + +static struct sdio_uart_port *sdio_uart_port_get(unsigned index) +{ + struct sdio_uart_port *port; + + if (index >= UART_NR) + return NULL; + + spin_lock(&sdio_uart_table_lock); + port = sdio_uart_table[index]; + if (port) + kref_get(&port->kref); + spin_unlock(&sdio_uart_table_lock); + + return port; +} + +static void sdio_uart_port_destroy(struct kref *kref) +{ + struct sdio_uart_port *port = + container_of(kref, struct sdio_uart_port, kref); + kfree(port); +} + +static void sdio_uart_port_put(struct sdio_uart_port *port) +{ + kref_put(&port->kref, sdio_uart_port_destroy); +} + +static void sdio_uart_port_remove(struct sdio_uart_port *port) +{ + struct sdio_func *func; + + BUG_ON(sdio_uart_table[port->index] != port); + + spin_lock(&sdio_uart_table_lock); + sdio_uart_table[port->index] = NULL; + spin_unlock(&sdio_uart_table_lock); + + /* + * We're killing a port that potentially still is in use by + * the tty layer. Be careful to prevent any further access + * to the SDIO function and arrange for the tty layer to + * give up on that port ASAP. + * Beware: the lock ordering is critical. + */ + mutex_lock(&port->open_lock); + mutex_lock(&port->func_lock); + func = port->func; + sdio_claim_host(func); + port->func = NULL; + mutex_unlock(&port->func_lock); + if (port->opened) + tty_hangup(port->tty); + mutex_unlock(&port->open_lock); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + + sdio_uart_port_put(port); +} + +static int sdio_uart_claim_func(struct sdio_uart_port *port) +{ + mutex_lock(&port->func_lock); + if (unlikely(!port->func)) { + mutex_unlock(&port->func_lock); + return -ENODEV; + } + if (likely(port->in_sdio_uart_irq != current)) + sdio_claim_host(port->func); + mutex_unlock(&port->func_lock); + return 0; +} + +static inline void sdio_uart_release_func(struct sdio_uart_port *port) +{ + if (likely(port->in_sdio_uart_irq != current)) + sdio_release_host(port->func); +} + +static inline unsigned int sdio_in(struct sdio_uart_port *port, int offset) +{ + unsigned char c; + c = sdio_readb(port->func, port->regs_offset + offset, NULL); + return c; +} + +static inline void sdio_out(struct sdio_uart_port *port, int offset, int value) +{ + sdio_writeb(port->func, value, port->regs_offset + offset, NULL); +} + +static unsigned int sdio_uart_get_mctrl(struct sdio_uart_port *port) +{ + unsigned char status; + unsigned int ret; + + status = sdio_in(port, UART_MSR); + + ret = 0; + if (status & UART_MSR_DCD) + ret |= TIOCM_CAR; + if (status & UART_MSR_RI) + ret |= TIOCM_RNG; + if (status & UART_MSR_DSR) + ret |= TIOCM_DSR; + if (status & UART_MSR_CTS) + ret |= TIOCM_CTS; + return ret; +} + +static void sdio_uart_write_mctrl(struct sdio_uart_port *port, unsigned int mctrl) +{ + unsigned char mcr = 0; + + if (mctrl & TIOCM_RTS) + mcr |= UART_MCR_RTS; + if (mctrl & TIOCM_DTR) + mcr |= UART_MCR_DTR; + if (mctrl & TIOCM_OUT1) + mcr |= UART_MCR_OUT1; + if (mctrl & TIOCM_OUT2) + mcr |= UART_MCR_OUT2; + if (mctrl & TIOCM_LOOP) + mcr |= UART_MCR_LOOP; + + sdio_out(port, UART_MCR, mcr); +} + +static inline void sdio_uart_update_mctrl(struct sdio_uart_port *port, + unsigned int set, unsigned int clear) +{ + unsigned int old; + + old = port->mctrl; + port->mctrl = (old & ~clear) | set; + if (old != port->mctrl) + sdio_uart_write_mctrl(port, port->mctrl); +} + +#define sdio_uart_set_mctrl(port, x) sdio_uart_update_mctrl(port, x, 0) +#define sdio_uart_clear_mctrl(port, x) sdio_uart_update_mctrl(port, 0, x) + +static void sdio_uart_change_speed(struct sdio_uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned char cval, fcr = 0; + unsigned int baud, quot; + + switch (termios->c_cflag & CSIZE) { + case CS5: + cval = UART_LCR_WLEN5; + break; + case CS6: + cval = UART_LCR_WLEN6; + break; + case CS7: + cval = UART_LCR_WLEN7; + break; + default: + case CS8: + cval = UART_LCR_WLEN8; + break; + } + + if (termios->c_cflag & CSTOPB) + cval |= UART_LCR_STOP; + if (termios->c_cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(termios->c_cflag & PARODD)) + cval |= UART_LCR_EPAR; + + for (;;) { + baud = tty_termios_baud_rate(termios); + if (baud == 0) + baud = 9600; /* Special case: B0 rate. */ + if (baud <= port->uartclk) + break; + /* + * Oops, the quotient was zero. Try again with the old + * baud rate if possible, otherwise default to 9600. + */ + termios->c_cflag &= ~CBAUD; + if (old) { + termios->c_cflag |= old->c_cflag & CBAUD; + old = NULL; + } else + termios->c_cflag |= B9600; + } + quot = (2 * port->uartclk + baud) / (2 * baud); + + if (baud < 2400) + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_TRIGGER_1; + else + fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10; + + port->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UART_LSR_FE | UART_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= UART_LSR_BI; + + /* + * Characters to ignore + */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= UART_LSR_BI; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_OE; + } + + /* + * ignore all characters if CREAD is not set + */ + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= UART_LSR_DR; + + /* + * CTS flow control flag and modem status interrupts + */ + port->ier &= ~UART_IER_MSI; + if ((termios->c_cflag & CRTSCTS) || !(termios->c_cflag & CLOCAL)) + port->ier |= UART_IER_MSI; + + port->lcr = cval; + + sdio_out(port, UART_IER, port->ier); + sdio_out(port, UART_LCR, cval | UART_LCR_DLAB); + sdio_out(port, UART_DLL, quot & 0xff); + sdio_out(port, UART_DLM, quot >> 8); + sdio_out(port, UART_LCR, cval); + sdio_out(port, UART_FCR, fcr); + + sdio_uart_write_mctrl(port, port->mctrl); +} + +static void sdio_uart_start_tx(struct sdio_uart_port *port) +{ + if (!(port->ier & UART_IER_THRI)) { + port->ier |= UART_IER_THRI; + sdio_out(port, UART_IER, port->ier); + } +} + +static void sdio_uart_stop_tx(struct sdio_uart_port *port) +{ + if (port->ier & UART_IER_THRI) { + port->ier &= ~UART_IER_THRI; + sdio_out(port, UART_IER, port->ier); + } +} + +static void sdio_uart_stop_rx(struct sdio_uart_port *port) +{ + port->ier &= ~UART_IER_RLSI; + port->read_status_mask &= ~UART_LSR_DR; + sdio_out(port, UART_IER, port->ier); +} + +static void sdio_uart_receive_chars(struct sdio_uart_port *port, int *status) +{ + struct tty_struct *tty = port->tty; + unsigned int ch, flag; + int max_count = 256; + + do { + ch = sdio_in(port, UART_RX); + flag = TTY_NORMAL; + port->icount.rx++; + + if (unlikely(*status & (UART_LSR_BI | UART_LSR_PE | + UART_LSR_FE | UART_LSR_OE))) { + /* + * For statistics only + */ + if (*status & UART_LSR_BI) { + *status &= ~(UART_LSR_FE | UART_LSR_PE); + port->icount.brk++; + } else if (*status & UART_LSR_PE) + port->icount.parity++; + else if (*status & UART_LSR_FE) + port->icount.frame++; + if (*status & UART_LSR_OE) + port->icount.overrun++; + + /* + * Mask off conditions which should be ignored. + */ + *status &= port->read_status_mask; + if (*status & UART_LSR_BI) { + flag = TTY_BREAK; + } else if (*status & UART_LSR_PE) + flag = TTY_PARITY; + else if (*status & UART_LSR_FE) + flag = TTY_FRAME; + } + + if ((*status & port->ignore_status_mask & ~UART_LSR_OE) == 0) + tty_insert_flip_char(tty, ch, flag); + + /* + * Overrun is special. Since it's reported immediately, + * it doesn't affect the current character. + */ + if (*status & ~port->ignore_status_mask & UART_LSR_OE) + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + + *status = sdio_in(port, UART_LSR); + } while ((*status & UART_LSR_DR) && (max_count-- > 0)); + tty_flip_buffer_push(tty); +} + +static void sdio_uart_transmit_chars(struct sdio_uart_port *port) +{ + struct circ_buf *xmit = &port->xmit; + int count; + + if (port->x_char) { + sdio_out(port, UART_TX, port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (circ_empty(xmit) || port->tty->stopped || port->tty->hw_stopped) { + sdio_uart_stop_tx(port); + return; + } + + count = 16; + do { + sdio_out(port, UART_TX, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (circ_empty(xmit)) + break; + } while (--count > 0); + + if (circ_chars_pending(xmit) < WAKEUP_CHARS) + tty_wakeup(port->tty); + + if (circ_empty(xmit)) + sdio_uart_stop_tx(port); +} + +static void sdio_uart_check_modem_status(struct sdio_uart_port *port) +{ + int status; + + status = sdio_in(port, UART_MSR); + + if ((status & UART_MSR_ANY_DELTA) == 0) + return; + + if (status & UART_MSR_TERI) + port->icount.rng++; + if (status & UART_MSR_DDSR) + port->icount.dsr++; + if (status & UART_MSR_DDCD) + port->icount.dcd++; + if (status & UART_MSR_DCTS) { + port->icount.cts++; + if (port->tty->termios->c_cflag & CRTSCTS) { + int cts = (status & UART_MSR_CTS); + if (port->tty->hw_stopped) { + if (cts) { + port->tty->hw_stopped = 0; + sdio_uart_start_tx(port); + tty_wakeup(port->tty); + } + } else { + if (!cts) { + port->tty->hw_stopped = 1; + sdio_uart_stop_tx(port); + } + } + } + } +} + +/* + * This handles the interrupt from one port. + */ +static void sdio_uart_irq(struct sdio_func *func) +{ + struct sdio_uart_port *port = sdio_get_drvdata(func); + unsigned int iir, lsr; + + /* + * In a few places sdio_uart_irq() is called directly instead of + * waiting for the actual interrupt to be raised and the SDIO IRQ + * thread scheduled in order to reduce latency. However, some + * interaction with the tty core may end up calling us back + * (serial echo, flow control, etc.) through those same places + * causing undesirable effects. Let's stop the recursion here. + */ + if (unlikely(port->in_sdio_uart_irq == current)) + return; + + iir = sdio_in(port, UART_IIR); + if (iir & UART_IIR_NO_INT) + return; + + port->in_sdio_uart_irq = current; + lsr = sdio_in(port, UART_LSR); + if (lsr & UART_LSR_DR) + sdio_uart_receive_chars(port, &lsr); + sdio_uart_check_modem_status(port); + if (lsr & UART_LSR_THRE) + sdio_uart_transmit_chars(port); + port->in_sdio_uart_irq = NULL; +} + +static int sdio_uart_startup(struct sdio_uart_port *port) +{ + unsigned long page; + int ret; + + /* + * Set the TTY IO error marker - we will only clear this + * once we have successfully opened the port. + */ + set_bit(TTY_IO_ERROR, &port->tty->flags); + + /* Initialise and allocate the transmit buffer. */ + page = __get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + port->xmit.buf = (unsigned char *)page; + circ_clear(&port->xmit); + + ret = sdio_uart_claim_func(port); + if (ret) + goto err1; + ret = sdio_enable_func(port->func); + if (ret) + goto err2; + ret = sdio_claim_irq(port->func, sdio_uart_irq); + if (ret) + goto err3; + + /* + * Clear the FIFO buffers and disable them. + * (they will be reenabled in sdio_change_speed()) + */ + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO); + sdio_out(port, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT); + sdio_out(port, UART_FCR, 0); + + /* + * Clear the interrupt registers. + */ + (void) sdio_in(port, UART_LSR); + (void) sdio_in(port, UART_RX); + (void) sdio_in(port, UART_IIR); + (void) sdio_in(port, UART_MSR); + + /* + * Now, initialize the UART + */ + sdio_out(port, UART_LCR, UART_LCR_WLEN8); + + port->ier = UART_IER_RLSI | UART_IER_RDI | UART_IER_RTOIE | UART_IER_UUE; + port->mctrl = TIOCM_OUT2; + + sdio_uart_change_speed(port, port->tty->termios, NULL); + + if (port->tty->termios->c_cflag & CBAUD) + sdio_uart_set_mctrl(port, TIOCM_RTS | TIOCM_DTR); |