📄 sdio_uart.c
字号:
/* * 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, unsigned 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.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -