diff options
Diffstat (limited to 'drivers/tty/serial/bfin_sport_uart.c')
| -rw-r--r-- | drivers/tty/serial/bfin_sport_uart.c | 937 | 
1 files changed, 937 insertions, 0 deletions
diff --git a/drivers/tty/serial/bfin_sport_uart.c b/drivers/tty/serial/bfin_sport_uart.c new file mode 100644 index 00000000000..4f229703328 --- /dev/null +++ b/drivers/tty/serial/bfin_sport_uart.c @@ -0,0 +1,937 @@ +/* + * Blackfin On-Chip Sport Emulated UART Driver + * + * Copyright 2006-2009 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +/* + * This driver and the hardware supported are in term of EE-191 of ADI. + * http://www.analog.com/static/imported-files/application_notes/EE191.pdf  + * This application note describe how to implement a UART on a Sharc DSP, + * but this driver is implemented on Blackfin Processor. + * Transmit Frame Sync is not used by this driver to transfer data out. + */ + +/* #define DEBUG */ + +#define DRV_NAME "bfin-sport-uart" +#define DEVICE_NAME	"ttySS" +#define pr_fmt(fmt) DRV_NAME ": " fmt + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/sysrq.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial_core.h> + +#include <asm/bfin_sport.h> +#include <asm/delay.h> +#include <asm/portmux.h> + +#include "bfin_sport_uart.h" + +struct sport_uart_port { +	struct uart_port	port; +	int			err_irq; +	unsigned short		csize; +	unsigned short		rxmask; +	unsigned short		txmask1; +	unsigned short		txmask2; +	unsigned char		stopb; +/*	unsigned char		parib; */ +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS +	int cts_pin; +	int rts_pin; +#endif +}; + +static int sport_uart_tx_chars(struct sport_uart_port *up); +static void sport_stop_tx(struct uart_port *port); + +static inline void tx_one_byte(struct sport_uart_port *up, unsigned int value) +{ +	pr_debug("%s value:%x, mask1=0x%x, mask2=0x%x\n", __func__, value, +		up->txmask1, up->txmask2); + +	/* Place Start and Stop bits */ +	__asm__ __volatile__ ( +		"%[val] <<= 1;" +		"%[val] = %[val] & %[mask1];" +		"%[val] = %[val] | %[mask2];" +		: [val]"+d"(value) +		: [mask1]"d"(up->txmask1), [mask2]"d"(up->txmask2) +		: "ASTAT" +	); +	pr_debug("%s value:%x\n", __func__, value); + +	SPORT_PUT_TX(up, value); +} + +static inline unsigned char rx_one_byte(struct sport_uart_port *up) +{ +	unsigned int value; +	unsigned char extract; +	u32 tmp_mask1, tmp_mask2, tmp_shift, tmp; + +	if ((up->csize + up->stopb) > 7) +		value = SPORT_GET_RX32(up); +	else +		value = SPORT_GET_RX(up); + +	pr_debug("%s value:%x, cs=%d, mask=0x%x\n", __func__, value, +		up->csize, up->rxmask); + +	/* Extract data */ +	__asm__ __volatile__ ( +		"%[extr] = 0;" +		"%[mask1] = %[rxmask];" +		"%[mask2] = 0x0200(Z);" +		"%[shift] = 0;" +		"LSETUP(.Lloop_s, .Lloop_e) LC0 = %[lc];" +		".Lloop_s:" +		"%[tmp] = extract(%[val], %[mask1].L)(Z);" +		"%[tmp] <<= %[shift];" +		"%[extr] = %[extr] | %[tmp];" +		"%[mask1] = %[mask1] - %[mask2];" +		".Lloop_e:" +		"%[shift] += 1;" +		: [extr]"=&d"(extract), [shift]"=&d"(tmp_shift), [tmp]"=&d"(tmp), +		  [mask1]"=&d"(tmp_mask1), [mask2]"=&d"(tmp_mask2) +		: [val]"d"(value), [rxmask]"d"(up->rxmask), [lc]"a"(up->csize) +		: "ASTAT", "LB0", "LC0", "LT0" +	); + +	pr_debug("	extract:%x\n", extract); +	return extract; +} + +static int sport_uart_setup(struct sport_uart_port *up, int size, int baud_rate) +{ +	int tclkdiv, rclkdiv; +	unsigned int sclk = get_sclk(); + +	/* Set TCR1 and TCR2, TFSR is not enabled for uart */ +	SPORT_PUT_TCR1(up, (LATFS | ITFS | TFSR | TLSBIT | ITCLK)); +	SPORT_PUT_TCR2(up, size + 1); +	pr_debug("%s TCR1:%x, TCR2:%x\n", __func__, SPORT_GET_TCR1(up), SPORT_GET_TCR2(up)); + +	/* Set RCR1 and RCR2 */ +	SPORT_PUT_RCR1(up, (RCKFE | LARFS | LRFS | RFSR | IRCLK)); +	SPORT_PUT_RCR2(up, (size + 1) * 2 - 1); +	pr_debug("%s RCR1:%x, RCR2:%x\n", __func__, SPORT_GET_RCR1(up), SPORT_GET_RCR2(up)); + +	tclkdiv = sclk / (2 * baud_rate) - 1; +	/* The actual uart baud rate of devices vary between +/-2%. The sport +	 * RX sample rate should be faster than the double of the worst case, +	 * otherwise, wrong data are received. So, set sport RX clock to be +	 * 3% faster. +	 */ +	rclkdiv = sclk / (2 * baud_rate * 2 * 97 / 100) - 1; +	SPORT_PUT_TCLKDIV(up, tclkdiv); +	SPORT_PUT_RCLKDIV(up, rclkdiv); +	SSYNC(); +	pr_debug("%s sclk:%d, baud_rate:%d, tclkdiv:%d, rclkdiv:%d\n", +			__func__, sclk, baud_rate, tclkdiv, rclkdiv); + +	return 0; +} + +static irqreturn_t sport_uart_rx_irq(int irq, void *dev_id) +{ +	struct sport_uart_port *up = dev_id; +	struct tty_port *port = &up->port.state->port; +	unsigned int ch; + +	spin_lock(&up->port.lock); + +	while (SPORT_GET_STAT(up) & RXNE) { +		ch = rx_one_byte(up); +		up->port.icount.rx++; + +		if (!uart_handle_sysrq_char(&up->port, ch)) +			tty_insert_flip_char(port, ch, TTY_NORMAL); +	} + +	spin_unlock(&up->port.lock); + +	/* XXX this won't deadlock with lowlat? */ +	tty_flip_buffer_push(port); + +	return IRQ_HANDLED; +} + +static irqreturn_t sport_uart_tx_irq(int irq, void *dev_id) +{ +	struct sport_uart_port *up = dev_id; + +	spin_lock(&up->port.lock); +	sport_uart_tx_chars(up); +	spin_unlock(&up->port.lock); + +	return IRQ_HANDLED; +} + +static irqreturn_t sport_uart_err_irq(int irq, void *dev_id) +{ +	struct sport_uart_port *up = dev_id; +	unsigned int stat = SPORT_GET_STAT(up); + +	spin_lock(&up->port.lock); + +	/* Overflow in RX FIFO */ +	if (stat & ROVF) { +		up->port.icount.overrun++; +		tty_insert_flip_char(&up->port.state->port, 0, TTY_OVERRUN); +		SPORT_PUT_STAT(up, ROVF); /* Clear ROVF bit */ +	} +	/* These should not happen */ +	if (stat & (TOVF | TUVF | RUVF)) { +		pr_err("SPORT Error:%s %s %s\n", +		       (stat & TOVF) ? "TX overflow" : "", +		       (stat & TUVF) ? "TX underflow" : "", +		       (stat & RUVF) ? "RX underflow" : ""); +		SPORT_PUT_TCR1(up, SPORT_GET_TCR1(up) & ~TSPEN); +		SPORT_PUT_RCR1(up, SPORT_GET_RCR1(up) & ~RSPEN); +	} +	SSYNC(); + +	spin_unlock(&up->port.lock); +	/* XXX we don't push the overrun bit to TTY? */ + +	return IRQ_HANDLED; +} + +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS +static unsigned int sport_get_mctrl(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; +	if (up->cts_pin < 0) +		return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; + +	/* CTS PIN is negative assertive. */ +	if (SPORT_UART_GET_CTS(up)) +		return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +	else +		return TIOCM_DSR | TIOCM_CAR; +} + +static void sport_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; +	if (up->rts_pin < 0) +		return; + +	/* RTS PIN is negative assertive. */ +	if (mctrl & TIOCM_RTS) +		SPORT_UART_ENABLE_RTS(up); +	else +		SPORT_UART_DISABLE_RTS(up); +} + +/* + * Handle any change of modem status signal. + */ +static irqreturn_t sport_mctrl_cts_int(int irq, void *dev_id) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)dev_id; +	unsigned int status; + +	status = sport_get_mctrl(&up->port); +	uart_handle_cts_change(&up->port, status & TIOCM_CTS); + +	return IRQ_HANDLED; +} +#else +static unsigned int sport_get_mctrl(struct uart_port *port) +{ +	pr_debug("%s enter\n", __func__); +	return TIOCM_CTS | TIOCM_CD | TIOCM_DSR; +} + +static void sport_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +	pr_debug("%s enter\n", __func__); +} +#endif + +/* Reqeust IRQ, Setup clock */ +static int sport_startup(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; +	int ret; + +	pr_debug("%s enter\n", __func__); +	ret = request_irq(up->port.irq, sport_uart_rx_irq, 0, +		"SPORT_UART_RX", up); +	if (ret) { +		dev_err(port->dev, "unable to request SPORT RX interrupt\n"); +		return ret; +	} + +	ret = request_irq(up->port.irq+1, sport_uart_tx_irq, 0, +		"SPORT_UART_TX", up); +	if (ret) { +		dev_err(port->dev, "unable to request SPORT TX interrupt\n"); +		goto fail1; +	} + +	ret = request_irq(up->err_irq, sport_uart_err_irq, 0, +		"SPORT_UART_STATUS", up); +	if (ret) { +		dev_err(port->dev, "unable to request SPORT status interrupt\n"); +		goto fail2; +	} + +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS +	if (up->cts_pin >= 0) { +		if (request_irq(gpio_to_irq(up->cts_pin), +			sport_mctrl_cts_int, +			IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | +			0, "BFIN_SPORT_UART_CTS", up)) { +			up->cts_pin = -1; +			dev_info(port->dev, "Unable to attach BlackFin UART over SPORT CTS interrupt. So, disable it.\n"); +		} +	} +	if (up->rts_pin >= 0) { +		if (gpio_request(up->rts_pin, DRV_NAME)) { +			dev_info(port->dev, "fail to request RTS PIN at GPIO_%d\n", up->rts_pin); +			up->rts_pin = -1; +		} else +			gpio_direction_output(up->rts_pin, 0); +	} +#endif + +	return 0; + fail2: +	free_irq(up->port.irq+1, up); + fail1: +	free_irq(up->port.irq, up); + +	return ret; +} + +/* + * sport_uart_tx_chars + * + * ret 1 means need to enable sport. + * ret 0 means do nothing. + */ +static int sport_uart_tx_chars(struct sport_uart_port *up) +{ +	struct circ_buf *xmit = &up->port.state->xmit; + +	if (SPORT_GET_STAT(up) & TXF) +		return 0; + +	if (up->port.x_char) { +		tx_one_byte(up, up->port.x_char); +		up->port.icount.tx++; +		up->port.x_char = 0; +		return 1; +	} + +	if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port)) { +		/* The waiting loop to stop SPORT TX from TX interrupt is +		 * too long. This may block SPORT RX interrupts and cause +		 * RX FIFO overflow. So, do stop sport TX only after the last +		 * char in TX FIFO is moved into the shift register. +		 */ +		if (SPORT_GET_STAT(up) & TXHRE) +			sport_stop_tx(&up->port); +		return 0; +	} + +	while(!(SPORT_GET_STAT(up) & TXF) && !uart_circ_empty(xmit)) { +		tx_one_byte(up, xmit->buf[xmit->tail]); +		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE -1); +		up->port.icount.tx++; +	} + +	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) +		uart_write_wakeup(&up->port); + +	return 1; +} + +static unsigned int sport_tx_empty(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; +	unsigned int stat; + +	stat = SPORT_GET_STAT(up); +	pr_debug("%s stat:%04x\n", __func__, stat); +	if (stat & TXHRE) { +		return TIOCSER_TEMT; +	} else +		return 0; +} + +static void sport_stop_tx(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; + +	pr_debug("%s enter\n", __func__); + +	if (!(SPORT_GET_TCR1(up) & TSPEN)) +		return; + +	/* Although the hold register is empty, last byte is still in shift +	 * register and not sent out yet. So, put a dummy data into TX FIFO. +	 * Then, sport tx stops when last byte is shift out and the dummy +	 * data is moved into the shift register. +	 */ +	SPORT_PUT_TX(up, 0xffff); +	while (!(SPORT_GET_STAT(up) & TXHRE)) +		cpu_relax(); + +	SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) & ~TSPEN)); +	SSYNC(); + +	return; +} + +static void sport_start_tx(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; + +	pr_debug("%s enter\n", __func__); + +	/* Write data into SPORT FIFO before enable SPROT to transmit */ +	if (sport_uart_tx_chars(up)) { +		/* Enable transmit, then an interrupt will generated */ +		SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN)); +		SSYNC(); +	} + +	pr_debug("%s exit\n", __func__); +} + +static void sport_stop_rx(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; + +	pr_debug("%s enter\n", __func__); +	/* Disable sport to stop rx */ +	SPORT_PUT_RCR1(up, (SPORT_GET_RCR1(up) & ~RSPEN)); +	SSYNC(); +} + +static void sport_enable_ms(struct uart_port *port) +{ +	pr_debug("%s enter\n", __func__); +} + +static void sport_break_ctl(struct uart_port *port, int break_state) +{ +	pr_debug("%s enter\n", __func__); +} + +static void sport_shutdown(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; + +	dev_dbg(port->dev, "%s enter\n", __func__); + +	/* Disable sport */ +	SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) & ~TSPEN)); +	SPORT_PUT_RCR1(up, (SPORT_GET_RCR1(up) & ~RSPEN)); +	SSYNC(); + +	free_irq(up->port.irq, up); +	free_irq(up->port.irq+1, up); +	free_irq(up->err_irq, up); +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS +	if (up->cts_pin >= 0) +		free_irq(gpio_to_irq(up->cts_pin), up); +	if (up->rts_pin >= 0) +		gpio_free(up->rts_pin); +#endif +} + +static const char *sport_type(struct uart_port *port) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; + +	pr_debug("%s enter\n", __func__); +	return up->port.type == PORT_BFIN_SPORT ? "BFIN-SPORT-UART" : NULL; +} + +static void sport_release_port(struct uart_port *port) +{ +	pr_debug("%s enter\n", __func__); +} + +static int sport_request_port(struct uart_port *port) +{ +	pr_debug("%s enter\n", __func__); +	return 0; +} + +static void sport_config_port(struct uart_port *port, int flags) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; + +	pr_debug("%s enter\n", __func__); +	up->port.type = PORT_BFIN_SPORT; +} + +static int sport_verify_port(struct uart_port *port, struct serial_struct *ser) +{ +	pr_debug("%s enter\n", __func__); +	return 0; +} + +static void sport_set_termios(struct uart_port *port, +		struct ktermios *termios, struct ktermios *old) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; +	unsigned long flags; +	int i; + +	pr_debug("%s enter, c_cflag:%08x\n", __func__, termios->c_cflag); + +	switch (termios->c_cflag & CSIZE) { +	case CS8: +		up->csize = 8; +		break; +	case CS7: +		up->csize = 7; +		break; +	case CS6: +		up->csize = 6; +		break; +	case CS5: +		up->csize = 5; +		break; +	default: +		pr_warning("requested word length not supported\n"); +	} + +	if (termios->c_cflag & CSTOPB) { +		up->stopb = 1; +	} +	if (termios->c_cflag & PARENB) { +		pr_warning("PAREN bits is not supported yet\n"); +		/* up->parib = 1; */ +	} + +	spin_lock_irqsave(&up->port.lock, flags); + +	port->read_status_mask = 0; + +	/* +	 * Characters to ignore +	 */ +	port->ignore_status_mask = 0; + +	/* RX extract mask */ +	up->rxmask = 0x01 | (((up->csize + up->stopb) * 2 - 1) << 0x8); +	/* TX masks, 8 bit data and 1 bit stop for example: +	 * mask1 = b#0111111110 +	 * mask2 = b#1000000000 +	 */ +	for (i = 0, up->txmask1 = 0; i < up->csize; i++) +		up->txmask1 |= (1<<i); +	up->txmask2 = (1<<i); +	if (up->stopb) { +		++i; +		up->txmask2 |= (1<<i); +	} +	up->txmask1 <<= 1; +	up->txmask2 <<= 1; +	/* uart baud rate */ +	port->uartclk = uart_get_baud_rate(port, termios, old, 0, get_sclk()/16); + +	/* Disable UART */ +	SPORT_PUT_TCR1(up, SPORT_GET_TCR1(up) & ~TSPEN); +	SPORT_PUT_RCR1(up, SPORT_GET_RCR1(up) & ~RSPEN); + +	sport_uart_setup(up, up->csize + up->stopb, port->uartclk); + +	/* driver TX line high after config, one dummy data is +	 * necessary to stop sport after shift one byte +	 */ +	SPORT_PUT_TX(up, 0xffff); +	SPORT_PUT_TX(up, 0xffff); +	SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN)); +	SSYNC(); +	while (!(SPORT_GET_STAT(up) & TXHRE)) +		cpu_relax(); +	SPORT_PUT_TCR1(up, SPORT_GET_TCR1(up) & ~TSPEN); +	SSYNC(); + +	/* Port speed changed, update the per-port timeout. */ +	uart_update_timeout(port, termios->c_cflag, port->uartclk); + +	/* Enable sport rx */ +	SPORT_PUT_RCR1(up, SPORT_GET_RCR1(up) | RSPEN); +	SSYNC(); + +	spin_unlock_irqrestore(&up->port.lock, flags); +} + +struct uart_ops sport_uart_ops = { +	.tx_empty	= sport_tx_empty, +	.set_mctrl	= sport_set_mctrl, +	.get_mctrl	= sport_get_mctrl, +	.stop_tx	= sport_stop_tx, +	.start_tx	= sport_start_tx, +	.stop_rx	= sport_stop_rx, +	.enable_ms	= sport_enable_ms, +	.break_ctl	= sport_break_ctl, +	.startup	= sport_startup, +	.shutdown	= sport_shutdown, +	.set_termios	= sport_set_termios, +	.type		= sport_type, +	.release_port	= sport_release_port, +	.request_port	= sport_request_port, +	.config_port	= sport_config_port, +	.verify_port	= sport_verify_port, +}; + +#define BFIN_SPORT_UART_MAX_PORTS 4 + +static struct sport_uart_port *bfin_sport_uart_ports[BFIN_SPORT_UART_MAX_PORTS]; + +#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE +#define CLASS_BFIN_SPORT_CONSOLE	"bfin-sport-console" + +static int __init +sport_uart_console_setup(struct console *co, char *options) +{ +	struct sport_uart_port *up; +	int baud = 57600; +	int bits = 8; +	int parity = 'n'; +# ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS +	int flow = 'r'; +# else +	int flow = 'n'; +# endif + +	/* Check whether an invalid uart number has been specified */ +	if (co->index < 0 || co->index >= BFIN_SPORT_UART_MAX_PORTS) +		return -ENODEV; + +	up = bfin_sport_uart_ports[co->index]; +	if (!up) +		return -ENODEV; + +	if (options) +		uart_parse_options(options, &baud, &parity, &bits, &flow); + +	return uart_set_options(&up->port, co, baud, parity, bits, flow); +} + +static void sport_uart_console_putchar(struct uart_port *port, int ch) +{ +	struct sport_uart_port *up = (struct sport_uart_port *)port; + +	while (SPORT_GET_STAT(up) & TXF) +		barrier(); + +	tx_one_byte(up, ch); +} + +/* + * Interrupts are disabled on entering + */ +static void +sport_uart_console_write(struct console *co, const char *s, unsigned int count) +{ +	struct sport_uart_port *up = bfin_sport_uart_ports[co->index]; +	unsigned long flags; + +	spin_lock_irqsave(&up->port.lock, flags); + +	if (SPORT_GET_TCR1(up) & TSPEN) +		uart_console_write(&up->port, s, count, sport_uart_console_putchar); +	else { +		/* dummy data to start sport */ +		while (SPORT_GET_STAT(up) & TXF) +			barrier(); +		SPORT_PUT_TX(up, 0xffff); +		/* Enable transmit, then an interrupt will generated */ +		SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) | TSPEN)); +		SSYNC(); + +		uart_console_write(&up->port, s, count, sport_uart_console_putchar); + +		/* Although the hold register is empty, last byte is still in shift +		 * register and not sent out yet. So, put a dummy data into TX FIFO. +		 * Then, sport tx stops when last byte is shift out and the dummy +		 * data is moved into the shift register. +		 */ +		while (SPORT_GET_STAT(up) & TXF) +			barrier(); +		SPORT_PUT_TX(up, 0xffff); +		while (!(SPORT_GET_STAT(up) & TXHRE)) +			barrier(); + +		/* Stop sport tx transfer */ +		SPORT_PUT_TCR1(up, (SPORT_GET_TCR1(up) & ~TSPEN)); +		SSYNC(); +	} + +	spin_unlock_irqrestore(&up->port.lock, flags); +} + +static struct uart_driver sport_uart_reg; + +static struct console sport_uart_console = { +	.name		= DEVICE_NAME, +	.write		= sport_uart_console_write, +	.device		= uart_console_device, +	.setup		= sport_uart_console_setup, +	.flags		= CON_PRINTBUFFER, +	.index		= -1, +	.data		= &sport_uart_reg, +}; + +#define SPORT_UART_CONSOLE	(&sport_uart_console) +#else +#define SPORT_UART_CONSOLE	NULL +#endif /* CONFIG_SERIAL_BFIN_SPORT_CONSOLE */ + + +static struct uart_driver sport_uart_reg = { +	.owner		= THIS_MODULE, +	.driver_name	= DRV_NAME, +	.dev_name	= DEVICE_NAME, +	.major		= 204, +	.minor		= 84, +	.nr		= BFIN_SPORT_UART_MAX_PORTS, +	.cons		= SPORT_UART_CONSOLE, +}; + +#ifdef CONFIG_PM +static int sport_uart_suspend(struct device *dev) +{ +	struct sport_uart_port *sport = dev_get_drvdata(dev); + +	dev_dbg(dev, "%s enter\n", __func__); +	if (sport) +		uart_suspend_port(&sport_uart_reg, &sport->port); + +	return 0; +} + +static int sport_uart_resume(struct device *dev) +{ +	struct sport_uart_port *sport = dev_get_drvdata(dev); + +	dev_dbg(dev, "%s enter\n", __func__); +	if (sport) +		uart_resume_port(&sport_uart_reg, &sport->port); + +	return 0; +} + +static struct dev_pm_ops bfin_sport_uart_dev_pm_ops = { +	.suspend	= sport_uart_suspend, +	.resume		= sport_uart_resume, +}; +#endif + +static int sport_uart_probe(struct platform_device *pdev) +{ +	struct resource *res; +	struct sport_uart_port *sport; +	int ret = 0; + +	dev_dbg(&pdev->dev, "%s enter\n", __func__); + +	if (pdev->id < 0 || pdev->id >= BFIN_SPORT_UART_MAX_PORTS) { +		dev_err(&pdev->dev, "Wrong sport uart platform device id.\n"); +		return -ENOENT; +	} + +	if (bfin_sport_uart_ports[pdev->id] == NULL) { +		bfin_sport_uart_ports[pdev->id] = +			kzalloc(sizeof(struct sport_uart_port), GFP_KERNEL); +		sport = bfin_sport_uart_ports[pdev->id]; +		if (!sport) { +			dev_err(&pdev->dev, +				"Fail to malloc sport_uart_port\n"); +			return -ENOMEM; +		} + +		ret = peripheral_request_list(dev_get_platdata(&pdev->dev), +						DRV_NAME); +		if (ret) { +			dev_err(&pdev->dev, +				"Fail to request SPORT peripherals\n"); +			goto out_error_free_mem; +		} + +		spin_lock_init(&sport->port.lock); +		sport->port.fifosize  = SPORT_TX_FIFO_SIZE, +		sport->port.ops       = &sport_uart_ops; +		sport->port.line      = pdev->id; +		sport->port.iotype    = UPIO_MEM; +		sport->port.flags     = UPF_BOOT_AUTOCONF; + +		res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +		if (res == NULL) { +			dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n"); +			ret = -ENOENT; +			goto out_error_free_peripherals; +		} + +		sport->port.membase = ioremap(res->start, resource_size(res)); +		if (!sport->port.membase) { +			dev_err(&pdev->dev, "Cannot map sport IO\n"); +			ret = -ENXIO; +			goto out_error_free_peripherals; +		} +		sport->port.mapbase = res->start; + +		sport->port.irq = platform_get_irq(pdev, 0); +		if ((int)sport->port.irq < 0) { +			dev_err(&pdev->dev, "No sport RX/TX IRQ specified\n"); +			ret = -ENOENT; +			goto out_error_unmap; +		} + +		sport->err_irq = platform_get_irq(pdev, 1); +		if (sport->err_irq < 0) { +			dev_err(&pdev->dev, "No sport status IRQ specified\n"); +			ret = -ENOENT; +			goto out_error_unmap; +		} +#ifdef CONFIG_SERIAL_BFIN_SPORT_CTSRTS +		res = platform_get_resource(pdev, IORESOURCE_IO, 0); +		if (res == NULL) +			sport->cts_pin = -1; +		else { +			sport->cts_pin = res->start; +			sport->port.flags |= ASYNC_CTS_FLOW; +		} + +		res = platform_get_resource(pdev, IORESOURCE_IO, 1); +		if (res == NULL) +			sport->rts_pin = -1; +		else +			sport->rts_pin = res->start; +#endif +	} + +#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE +	if (!is_early_platform_device(pdev)) { +#endif +		sport = bfin_sport_uart_ports[pdev->id]; +		sport->port.dev = &pdev->dev; +		dev_set_drvdata(&pdev->dev, sport); +		ret = uart_add_one_port(&sport_uart_reg, &sport->port); +#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE +	} +#endif +	if (!ret) +		return 0; + +	if (sport) { +out_error_unmap: +		iounmap(sport->port.membase); +out_error_free_peripherals: +		peripheral_free_list(dev_get_platdata(&pdev->dev)); +out_error_free_mem: +		kfree(sport); +		bfin_sport_uart_ports[pdev->id] = NULL; +	} + +	return ret; +} + +static int sport_uart_remove(struct platform_device *pdev) +{ +	struct sport_uart_port *sport = platform_get_drvdata(pdev); + +	dev_dbg(&pdev->dev, "%s enter\n", __func__); +	dev_set_drvdata(&pdev->dev, NULL); + +	if (sport) { +		uart_remove_one_port(&sport_uart_reg, &sport->port); +		iounmap(sport->port.membase); +		peripheral_free_list(dev_get_platdata(&pdev->dev)); +		kfree(sport); +		bfin_sport_uart_ports[pdev->id] = NULL; +	} + +	return 0; +} + +static struct platform_driver sport_uart_driver = { +	.probe		= sport_uart_probe, +	.remove		= sport_uart_remove, +	.driver		= { +		.name	= DRV_NAME, +#ifdef CONFIG_PM +		.pm	= &bfin_sport_uart_dev_pm_ops, +#endif +	}, +}; + +#ifdef CONFIG_SERIAL_BFIN_SPORT_CONSOLE +static struct early_platform_driver early_sport_uart_driver __initdata = { +	.class_str = CLASS_BFIN_SPORT_CONSOLE, +	.pdrv = &sport_uart_driver, +	.requested_id = EARLY_PLATFORM_ID_UNSET, +}; + +static int __init sport_uart_rs_console_init(void) +{ +	early_platform_driver_register(&early_sport_uart_driver, DRV_NAME); + +	early_platform_driver_probe(CLASS_BFIN_SPORT_CONSOLE, +		BFIN_SPORT_UART_MAX_PORTS, 0); + +	register_console(&sport_uart_console); + +	return 0; +} +console_initcall(sport_uart_rs_console_init); +#endif + +static int __init sport_uart_init(void) +{ +	int ret; + +	pr_info("Blackfin uart over sport driver\n"); + +	ret = uart_register_driver(&sport_uart_reg); +	if (ret) { +		pr_err("failed to register %s:%d\n", +				sport_uart_reg.driver_name, ret); +		return ret; +	} + +	ret = platform_driver_register(&sport_uart_driver); +	if (ret) { +		pr_err("failed to register sport uart driver:%d\n", ret); +		uart_unregister_driver(&sport_uart_reg); +	} + +	return ret; +} +module_init(sport_uart_init); + +static void __exit sport_uart_exit(void) +{ +	platform_driver_unregister(&sport_uart_driver); +	uart_unregister_driver(&sport_uart_reg); +} +module_exit(sport_uart_exit); + +MODULE_AUTHOR("Sonic Zhang, Roy Huang"); +MODULE_DESCRIPTION("Blackfin serial over SPORT driver"); +MODULE_LICENSE("GPL");  | 
