⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 sdio_uart.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * 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 + -