diff options
Diffstat (limited to 'drivers/tty/serial/sb1250-duart.c')
| -rw-r--r-- | drivers/tty/serial/sb1250-duart.c | 976 | 
1 files changed, 976 insertions, 0 deletions
diff --git a/drivers/tty/serial/sb1250-duart.c b/drivers/tty/serial/sb1250-duart.c new file mode 100644 index 00000000000..771f361c47e --- /dev/null +++ b/drivers/tty/serial/sb1250-duart.c @@ -0,0 +1,976 @@ +/* + *	Support for the asynchronous serial interface (DUART) included + *	in the BCM1250 and derived System-On-a-Chip (SOC) devices. + * + *	Copyright (c) 2007  Maciej W. Rozycki + * + *	Derived from drivers/char/sb1250_duart.c for which the following + *	copyright applies: + * + *	Copyright (c) 2000, 2001, 2002, 2003, 2004  Broadcom Corporation + * + *	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. + * + *	References: + * + *	"BCM1250/BCM1125/BCM1125H User Manual", Broadcom Corporation + */ + +#if defined(CONFIG_SERIAL_SB1250_DUART_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include <linux/compiler.h> +#include <linux/console.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/major.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/spinlock.h> +#include <linux/sysrq.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/types.h> + +#include <linux/atomic.h> +#include <asm/io.h> +#include <asm/war.h> + +#include <asm/sibyte/sb1250.h> +#include <asm/sibyte/sb1250_uart.h> +#include <asm/sibyte/swarm.h> + + +#if defined(CONFIG_SIBYTE_BCM1x55) || defined(CONFIG_SIBYTE_BCM1x80) +#include <asm/sibyte/bcm1480_regs.h> +#include <asm/sibyte/bcm1480_int.h> + +#define SBD_CHANREGS(line)	A_BCM1480_DUART_CHANREG((line), 0) +#define SBD_CTRLREGS(line)	A_BCM1480_DUART_CTRLREG((line), 0) +#define SBD_INT(line)		(K_BCM1480_INT_UART_0 + (line)) + +#define DUART_CHANREG_SPACING	BCM1480_DUART_CHANREG_SPACING + +#define R_DUART_IMRREG(line)	R_BCM1480_DUART_IMRREG(line) +#define R_DUART_INCHREG(line)	R_BCM1480_DUART_INCHREG(line) +#define R_DUART_ISRREG(line)	R_BCM1480_DUART_ISRREG(line) + +#elif defined(CONFIG_SIBYTE_SB1250) || defined(CONFIG_SIBYTE_BCM112X) +#include <asm/sibyte/sb1250_regs.h> +#include <asm/sibyte/sb1250_int.h> + +#define SBD_CHANREGS(line)	A_DUART_CHANREG((line), 0) +#define SBD_CTRLREGS(line)	A_DUART_CTRLREG(0) +#define SBD_INT(line)		(K_INT_UART_0 + (line)) + +#else +#error invalid SB1250 UART configuration + +#endif + + +MODULE_AUTHOR("Maciej W. Rozycki <macro@linux-mips.org>"); +MODULE_DESCRIPTION("BCM1xxx on-chip DUART serial driver"); +MODULE_LICENSE("GPL"); + + +#define DUART_MAX_CHIP 2 +#define DUART_MAX_SIDE 2 + +/* + * Per-port state. + */ +struct sbd_port { +	struct sbd_duart	*duart; +	struct uart_port	port; +	unsigned char __iomem	*memctrl; +	int			tx_stopped; +	int			initialised; +}; + +/* + * Per-DUART state for the shared register space. + */ +struct sbd_duart { +	struct sbd_port		sport[2]; +	unsigned long		mapctrl; +	atomic_t		map_guard; +}; + +#define to_sport(uport) container_of(uport, struct sbd_port, port) + +static struct sbd_duart sbd_duarts[DUART_MAX_CHIP]; + + +/* + * Reading and writing SB1250 DUART registers. + * + * There are three register spaces: two per-channel ones and + * a shared one.  We have to define accessors appropriately. + * All registers are 64-bit and all but the Baud Rate Clock + * registers only define 8 least significant bits.  There is + * also a workaround to take into account.  Raw accessors use + * the full register width, but cooked ones truncate it + * intentionally so that the rest of the driver does not care. + */ +static u64 __read_sbdchn(struct sbd_port *sport, int reg) +{ +	void __iomem *csr = sport->port.membase + reg; + +	return __raw_readq(csr); +} + +static u64 __read_sbdshr(struct sbd_port *sport, int reg) +{ +	void __iomem *csr = sport->memctrl + reg; + +	return __raw_readq(csr); +} + +static void __write_sbdchn(struct sbd_port *sport, int reg, u64 value) +{ +	void __iomem *csr = sport->port.membase + reg; + +	__raw_writeq(value, csr); +} + +static void __write_sbdshr(struct sbd_port *sport, int reg, u64 value) +{ +	void __iomem *csr = sport->memctrl + reg; + +	__raw_writeq(value, csr); +} + +/* + * In bug 1956, we get glitches that can mess up uart registers.  This + * "read-mode-reg after any register access" is an accepted workaround. + */ +static void __war_sbd1956(struct sbd_port *sport) +{ +	__read_sbdchn(sport, R_DUART_MODE_REG_1); +	__read_sbdchn(sport, R_DUART_MODE_REG_2); +} + +static unsigned char read_sbdchn(struct sbd_port *sport, int reg) +{ +	unsigned char retval; + +	retval = __read_sbdchn(sport, reg); +	if (SIBYTE_1956_WAR) +		__war_sbd1956(sport); +	return retval; +} + +static unsigned char read_sbdshr(struct sbd_port *sport, int reg) +{ +	unsigned char retval; + +	retval = __read_sbdshr(sport, reg); +	if (SIBYTE_1956_WAR) +		__war_sbd1956(sport); +	return retval; +} + +static void write_sbdchn(struct sbd_port *sport, int reg, unsigned int value) +{ +	__write_sbdchn(sport, reg, value); +	if (SIBYTE_1956_WAR) +		__war_sbd1956(sport); +} + +static void write_sbdshr(struct sbd_port *sport, int reg, unsigned int value) +{ +	__write_sbdshr(sport, reg, value); +	if (SIBYTE_1956_WAR) +		__war_sbd1956(sport); +} + + +static int sbd_receive_ready(struct sbd_port *sport) +{ +	return read_sbdchn(sport, R_DUART_STATUS) & M_DUART_RX_RDY; +} + +static int sbd_receive_drain(struct sbd_port *sport) +{ +	int loops = 10000; + +	while (sbd_receive_ready(sport) && --loops) +		read_sbdchn(sport, R_DUART_RX_HOLD); +	return loops; +} + +static int __maybe_unused sbd_transmit_ready(struct sbd_port *sport) +{ +	return read_sbdchn(sport, R_DUART_STATUS) & M_DUART_TX_RDY; +} + +static int __maybe_unused sbd_transmit_drain(struct sbd_port *sport) +{ +	int loops = 10000; + +	while (!sbd_transmit_ready(sport) && --loops) +		udelay(2); +	return loops; +} + +static int sbd_transmit_empty(struct sbd_port *sport) +{ +	return read_sbdchn(sport, R_DUART_STATUS) & M_DUART_TX_EMT; +} + +static int sbd_line_drain(struct sbd_port *sport) +{ +	int loops = 10000; + +	while (!sbd_transmit_empty(sport) && --loops) +		udelay(2); +	return loops; +} + + +static unsigned int sbd_tx_empty(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); + +	return sbd_transmit_empty(sport) ? TIOCSER_TEMT : 0; +} + +static unsigned int sbd_get_mctrl(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); +	unsigned int mctrl, status; + +	status = read_sbdshr(sport, R_DUART_IN_PORT); +	status >>= (uport->line) % 2; +	mctrl = (!(status & M_DUART_IN_PIN0_VAL) ? TIOCM_CTS : 0) | +		(!(status & M_DUART_IN_PIN4_VAL) ? TIOCM_CAR : 0) | +		(!(status & M_DUART_RIN0_PIN) ? TIOCM_RNG : 0) | +		(!(status & M_DUART_IN_PIN2_VAL) ? TIOCM_DSR : 0); +	return mctrl; +} + +static void sbd_set_mctrl(struct uart_port *uport, unsigned int mctrl) +{ +	struct sbd_port *sport = to_sport(uport); +	unsigned int clr = 0, set = 0, mode2; + +	if (mctrl & TIOCM_DTR) +		set |= M_DUART_SET_OPR2; +	else +		clr |= M_DUART_CLR_OPR2; +	if (mctrl & TIOCM_RTS) +		set |= M_DUART_SET_OPR0; +	else +		clr |= M_DUART_CLR_OPR0; +	clr <<= (uport->line) % 2; +	set <<= (uport->line) % 2; + +	mode2 = read_sbdchn(sport, R_DUART_MODE_REG_2); +	mode2 &= ~M_DUART_CHAN_MODE; +	if (mctrl & TIOCM_LOOP) +		mode2 |= V_DUART_CHAN_MODE_LCL_LOOP; +	else +		mode2 |= V_DUART_CHAN_MODE_NORMAL; + +	write_sbdshr(sport, R_DUART_CLEAR_OPR, clr); +	write_sbdshr(sport, R_DUART_SET_OPR, set); +	write_sbdchn(sport, R_DUART_MODE_REG_2, mode2); +} + +static void sbd_stop_tx(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); + +	write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS); +	sport->tx_stopped = 1; +}; + +static void sbd_start_tx(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); +	unsigned int mask; + +	/* Enable tx interrupts.  */ +	mask = read_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2)); +	mask |= M_DUART_IMR_TX; +	write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), mask); + +	/* Go!, go!, go!...  */ +	write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_EN); +	sport->tx_stopped = 0; +}; + +static void sbd_stop_rx(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); + +	write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), 0); +}; + +static void sbd_enable_ms(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); + +	write_sbdchn(sport, R_DUART_AUXCTL_X, +		     M_DUART_CIN_CHNG_ENA | M_DUART_CTS_CHNG_ENA); +} + +static void sbd_break_ctl(struct uart_port *uport, int break_state) +{ +	struct sbd_port *sport = to_sport(uport); + +	if (break_state == -1) +		write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_START_BREAK); +	else +		write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_STOP_BREAK); +} + + +static void sbd_receive_chars(struct sbd_port *sport) +{ +	struct uart_port *uport = &sport->port; +	struct uart_icount *icount; +	unsigned int status, ch, flag; +	int count; + +	for (count = 16; count; count--) { +		status = read_sbdchn(sport, R_DUART_STATUS); +		if (!(status & M_DUART_RX_RDY)) +			break; + +		ch = read_sbdchn(sport, R_DUART_RX_HOLD); + +		flag = TTY_NORMAL; + +		icount = &uport->icount; +		icount->rx++; + +		if (unlikely(status & +			     (M_DUART_RCVD_BRK | M_DUART_FRM_ERR | +			      M_DUART_PARITY_ERR | M_DUART_OVRUN_ERR))) { +			if (status & M_DUART_RCVD_BRK) { +				icount->brk++; +				if (uart_handle_break(uport)) +					continue; +			} else if (status & M_DUART_FRM_ERR) +				icount->frame++; +			else if (status & M_DUART_PARITY_ERR) +				icount->parity++; +			if (status & M_DUART_OVRUN_ERR) +				icount->overrun++; + +			status &= uport->read_status_mask; +			if (status & M_DUART_RCVD_BRK) +				flag = TTY_BREAK; +			else if (status & M_DUART_FRM_ERR) +				flag = TTY_FRAME; +			else if (status & M_DUART_PARITY_ERR) +				flag = TTY_PARITY; +		} + +		if (uart_handle_sysrq_char(uport, ch)) +			continue; + +		uart_insert_char(uport, status, M_DUART_OVRUN_ERR, ch, flag); +	} + +	tty_flip_buffer_push(&uport->state->port); +} + +static void sbd_transmit_chars(struct sbd_port *sport) +{ +	struct uart_port *uport = &sport->port; +	struct circ_buf *xmit = &sport->port.state->xmit; +	unsigned int mask; +	int stop_tx; + +	/* XON/XOFF chars.  */ +	if (sport->port.x_char) { +		write_sbdchn(sport, R_DUART_TX_HOLD, sport->port.x_char); +		sport->port.icount.tx++; +		sport->port.x_char = 0; +		return; +	} + +	/* If nothing to do or stopped or hardware stopped.  */ +	stop_tx = (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)); + +	/* Send char.  */ +	if (!stop_tx) { +		write_sbdchn(sport, R_DUART_TX_HOLD, xmit->buf[xmit->tail]); +		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); +		sport->port.icount.tx++; + +		if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) +			uart_write_wakeup(&sport->port); +	} + +	/* Are we are done?  */ +	if (stop_tx || uart_circ_empty(xmit)) { +		/* Disable tx interrupts.  */ +		mask = read_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2)); +		mask &= ~M_DUART_IMR_TX; +		write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), mask); +	} +} + +static void sbd_status_handle(struct sbd_port *sport) +{ +	struct uart_port *uport = &sport->port; +	unsigned int delta; + +	delta = read_sbdshr(sport, R_DUART_INCHREG((uport->line) % 2)); +	delta >>= (uport->line) % 2; + +	if (delta & (M_DUART_IN_PIN0_VAL << S_DUART_IN_PIN_CHNG)) +		uart_handle_cts_change(uport, !(delta & M_DUART_IN_PIN0_VAL)); + +	if (delta & (M_DUART_IN_PIN2_VAL << S_DUART_IN_PIN_CHNG)) +		uport->icount.dsr++; + +	if (delta & ((M_DUART_IN_PIN2_VAL | M_DUART_IN_PIN0_VAL) << +		     S_DUART_IN_PIN_CHNG)) +		wake_up_interruptible(&uport->state->port.delta_msr_wait); +} + +static irqreturn_t sbd_interrupt(int irq, void *dev_id) +{ +	struct sbd_port *sport = dev_id; +	struct uart_port *uport = &sport->port; +	irqreturn_t status = IRQ_NONE; +	unsigned int intstat; +	int count; + +	for (count = 16; count; count--) { +		intstat = read_sbdshr(sport, +				      R_DUART_ISRREG((uport->line) % 2)); +		intstat &= read_sbdshr(sport, +				       R_DUART_IMRREG((uport->line) % 2)); +		intstat &= M_DUART_ISR_ALL; +		if (!intstat) +			break; + +		if (intstat & M_DUART_ISR_RX) +			sbd_receive_chars(sport); +		if (intstat & M_DUART_ISR_IN) +			sbd_status_handle(sport); +		if (intstat & M_DUART_ISR_TX) +			sbd_transmit_chars(sport); + +		status = IRQ_HANDLED; +	} + +	return status; +} + + +static int sbd_startup(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); +	unsigned int mode1; +	int ret; + +	ret = request_irq(sport->port.irq, sbd_interrupt, +			  IRQF_SHARED, "sb1250-duart", sport); +	if (ret) +		return ret; + +	/* Clear the receive FIFO.  */ +	sbd_receive_drain(sport); + +	/* Clear the interrupt registers.  */ +	write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_RESET_BREAK_INT); +	read_sbdshr(sport, R_DUART_INCHREG((uport->line) % 2)); + +	/* Set rx/tx interrupt to FIFO available.  */ +	mode1 = read_sbdchn(sport, R_DUART_MODE_REG_1); +	mode1 &= ~(M_DUART_RX_IRQ_SEL_RXFULL | M_DUART_TX_IRQ_SEL_TXEMPT); +	write_sbdchn(sport, R_DUART_MODE_REG_1, mode1); + +	/* Disable tx, enable rx.  */ +	write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS | M_DUART_RX_EN); +	sport->tx_stopped = 1; + +	/* Enable interrupts.  */ +	write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), +		     M_DUART_IMR_IN | M_DUART_IMR_RX); + +	return 0; +} + +static void sbd_shutdown(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); + +	write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS | M_DUART_RX_DIS); +	sport->tx_stopped = 1; +	free_irq(sport->port.irq, sport); +} + + +static void sbd_init_port(struct sbd_port *sport) +{ +	struct uart_port *uport = &sport->port; + +	if (sport->initialised) +		return; + +	/* There is no DUART reset feature, so just set some sane defaults.  */ +	write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_RESET_TX); +	write_sbdchn(sport, R_DUART_CMD, V_DUART_MISC_CMD_RESET_RX); +	write_sbdchn(sport, R_DUART_MODE_REG_1, V_DUART_BITS_PER_CHAR_8); +	write_sbdchn(sport, R_DUART_MODE_REG_2, 0); +	write_sbdchn(sport, R_DUART_FULL_CTL, +		     V_DUART_INT_TIME(0) | V_DUART_SIG_FULL(15)); +	write_sbdchn(sport, R_DUART_OPCR_X, 0); +	write_sbdchn(sport, R_DUART_AUXCTL_X, 0); +	write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), 0); + +	sport->initialised = 1; +} + +static void sbd_set_termios(struct uart_port *uport, struct ktermios *termios, +			    struct ktermios *old_termios) +{ +	struct sbd_port *sport = to_sport(uport); +	unsigned int mode1 = 0, mode2 = 0, aux = 0; +	unsigned int mode1mask = 0, mode2mask = 0, auxmask = 0; +	unsigned int oldmode1, oldmode2, oldaux; +	unsigned int baud, brg; +	unsigned int command; + +	mode1mask |= ~(M_DUART_PARITY_MODE | M_DUART_PARITY_TYPE_ODD | +		       M_DUART_BITS_PER_CHAR); +	mode2mask |= ~M_DUART_STOP_BIT_LEN_2; +	auxmask |= ~M_DUART_CTS_CHNG_ENA; + +	/* Byte size.  */ +	switch (termios->c_cflag & CSIZE) { +	case CS5: +	case CS6: +		/* Unsupported, leave unchanged.  */ +		mode1mask |= M_DUART_PARITY_MODE; +		break; +	case CS7: +		mode1 |= V_DUART_BITS_PER_CHAR_7; +		break; +	case CS8: +	default: +		mode1 |= V_DUART_BITS_PER_CHAR_8; +		break; +	} + +	/* Parity and stop bits.  */ +	if (termios->c_cflag & CSTOPB) +		mode2 |= M_DUART_STOP_BIT_LEN_2; +	else +		mode2 |= M_DUART_STOP_BIT_LEN_1; +	if (termios->c_cflag & PARENB) +		mode1 |= V_DUART_PARITY_MODE_ADD; +	else +		mode1 |= V_DUART_PARITY_MODE_NONE; +	if (termios->c_cflag & PARODD) +		mode1 |= M_DUART_PARITY_TYPE_ODD; +	else +		mode1 |= M_DUART_PARITY_TYPE_EVEN; + +	baud = uart_get_baud_rate(uport, termios, old_termios, 1200, 5000000); +	brg = V_DUART_BAUD_RATE(baud); +	/* The actual lower bound is 1221bps, so compensate.  */ +	if (brg > M_DUART_CLK_COUNTER) +		brg = M_DUART_CLK_COUNTER; + +	uart_update_timeout(uport, termios->c_cflag, baud); + +	uport->read_status_mask = M_DUART_OVRUN_ERR; +	if (termios->c_iflag & INPCK) +		uport->read_status_mask |= M_DUART_FRM_ERR | +					   M_DUART_PARITY_ERR; +	if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK)) +		uport->read_status_mask |= M_DUART_RCVD_BRK; + +	uport->ignore_status_mask = 0; +	if (termios->c_iflag & IGNPAR) +		uport->ignore_status_mask |= M_DUART_FRM_ERR | +					     M_DUART_PARITY_ERR; +	if (termios->c_iflag & IGNBRK) { +		uport->ignore_status_mask |= M_DUART_RCVD_BRK; +		if (termios->c_iflag & IGNPAR) +			uport->ignore_status_mask |= M_DUART_OVRUN_ERR; +	} + +	if (termios->c_cflag & CREAD) +		command = M_DUART_RX_EN; +	else +		command = M_DUART_RX_DIS; + +	if (termios->c_cflag & CRTSCTS) +		aux |= M_DUART_CTS_CHNG_ENA; +	else +		aux &= ~M_DUART_CTS_CHNG_ENA; + +	spin_lock(&uport->lock); + +	if (sport->tx_stopped) +		command |= M_DUART_TX_DIS; +	else +		command |= M_DUART_TX_EN; + +	oldmode1 = read_sbdchn(sport, R_DUART_MODE_REG_1) & mode1mask; +	oldmode2 = read_sbdchn(sport, R_DUART_MODE_REG_2) & mode2mask; +	oldaux = read_sbdchn(sport, R_DUART_AUXCTL_X) & auxmask; + +	if (!sport->tx_stopped) +		sbd_line_drain(sport); +	write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS | M_DUART_RX_DIS); + +	write_sbdchn(sport, R_DUART_MODE_REG_1, mode1 | oldmode1); +	write_sbdchn(sport, R_DUART_MODE_REG_2, mode2 | oldmode2); +	write_sbdchn(sport, R_DUART_CLK_SEL, brg); +	write_sbdchn(sport, R_DUART_AUXCTL_X, aux | oldaux); + +	write_sbdchn(sport, R_DUART_CMD, command); + +	spin_unlock(&uport->lock); +} + + +static const char *sbd_type(struct uart_port *uport) +{ +	return "SB1250 DUART"; +} + +static void sbd_release_port(struct uart_port *uport) +{ +	struct sbd_port *sport = to_sport(uport); +	struct sbd_duart *duart = sport->duart; +	int map_guard; + +	iounmap(sport->memctrl); +	sport->memctrl = NULL; +	iounmap(uport->membase); +	uport->membase = NULL; + +	map_guard = atomic_add_return(-1, &duart->map_guard); +	if (!map_guard) +		release_mem_region(duart->mapctrl, DUART_CHANREG_SPACING); +	release_mem_region(uport->mapbase, DUART_CHANREG_SPACING); +} + +static int sbd_map_port(struct uart_port *uport) +{ +	const char *err = KERN_ERR "sbd: Cannot map MMIO\n"; +	struct sbd_port *sport = to_sport(uport); +	struct sbd_duart *duart = sport->duart; + +	if (!uport->membase) +		uport->membase = ioremap_nocache(uport->mapbase, +						 DUART_CHANREG_SPACING); +	if (!uport->membase) { +		printk(err); +		return -ENOMEM; +	} + +	if (!sport->memctrl) +		sport->memctrl = ioremap_nocache(duart->mapctrl, +						 DUART_CHANREG_SPACING); +	if (!sport->memctrl) { +		printk(err); +		iounmap(uport->membase); +		uport->membase = NULL; +		return -ENOMEM; +	} + +	return 0; +} + +static int sbd_request_port(struct uart_port *uport) +{ +	const char *err = KERN_ERR "sbd: Unable to reserve MMIO resource\n"; +	struct sbd_duart *duart = to_sport(uport)->duart; +	int map_guard; +	int ret = 0; + +	if (!request_mem_region(uport->mapbase, DUART_CHANREG_SPACING, +				"sb1250-duart")) { +		printk(err); +		return -EBUSY; +	} +	map_guard = atomic_add_return(1, &duart->map_guard); +	if (map_guard == 1) { +		if (!request_mem_region(duart->mapctrl, DUART_CHANREG_SPACING, +					"sb1250-duart")) { +			atomic_add(-1, &duart->map_guard); +			printk(err); +			ret = -EBUSY; +		} +	} +	if (!ret) { +		ret = sbd_map_port(uport); +		if (ret) { +			map_guard = atomic_add_return(-1, &duart->map_guard); +			if (!map_guard) +				release_mem_region(duart->mapctrl, +						   DUART_CHANREG_SPACING); +		} +	} +	if (ret) { +		release_mem_region(uport->mapbase, DUART_CHANREG_SPACING); +		return ret; +	} +	return 0; +} + +static void sbd_config_port(struct uart_port *uport, int flags) +{ +	struct sbd_port *sport = to_sport(uport); + +	if (flags & UART_CONFIG_TYPE) { +		if (sbd_request_port(uport)) +			return; + +		uport->type = PORT_SB1250_DUART; + +		sbd_init_port(sport); +	} +} + +static int sbd_verify_port(struct uart_port *uport, struct serial_struct *ser) +{ +	int ret = 0; + +	if (ser->type != PORT_UNKNOWN && ser->type != PORT_SB1250_DUART) +		ret = -EINVAL; +	if (ser->irq != uport->irq) +		ret = -EINVAL; +	if (ser->baud_base != uport->uartclk / 16) +		ret = -EINVAL; +	return ret; +} + + +static const struct uart_ops sbd_ops = { +	.tx_empty	= sbd_tx_empty, +	.set_mctrl	= sbd_set_mctrl, +	.get_mctrl	= sbd_get_mctrl, +	.stop_tx	= sbd_stop_tx, +	.start_tx	= sbd_start_tx, +	.stop_rx	= sbd_stop_rx, +	.enable_ms	= sbd_enable_ms, +	.break_ctl	= sbd_break_ctl, +	.startup	= sbd_startup, +	.shutdown	= sbd_shutdown, +	.set_termios	= sbd_set_termios, +	.type		= sbd_type, +	.release_port	= sbd_release_port, +	.request_port	= sbd_request_port, +	.config_port	= sbd_config_port, +	.verify_port	= sbd_verify_port, +}; + +/* Initialize SB1250 DUART port structures.  */ +static void __init sbd_probe_duarts(void) +{ +	static int probed; +	int chip, side; +	int max_lines, line; + +	if (probed) +		return; + +	/* Set the number of available units based on the SOC type.  */ +	switch (soc_type) { +	case K_SYS_SOC_TYPE_BCM1x55: +	case K_SYS_SOC_TYPE_BCM1x80: +		max_lines = 4; +		break; +	default: +		/* Assume at least two serial ports at the normal address.  */ +		max_lines = 2; +		break; +	} + +	probed = 1; + +	for (chip = 0, line = 0; chip < DUART_MAX_CHIP && line < max_lines; +	     chip++) { +		sbd_duarts[chip].mapctrl = SBD_CTRLREGS(line); + +		for (side = 0; side < DUART_MAX_SIDE && line < max_lines; +		     side++, line++) { +			struct sbd_port *sport = &sbd_duarts[chip].sport[side]; +			struct uart_port *uport = &sport->port; + +			sport->duart	= &sbd_duarts[chip]; + +			uport->irq	= SBD_INT(line); +			uport->uartclk	= 100000000 / 20 * 16; +			uport->fifosize	= 16; +			uport->iotype	= UPIO_MEM; +			uport->flags	= UPF_BOOT_AUTOCONF; +			uport->ops	= &sbd_ops; +			uport->line	= line; +			uport->mapbase	= SBD_CHANREGS(line); +		} +	} +} + + +#ifdef CONFIG_SERIAL_SB1250_DUART_CONSOLE +/* + * Serial console stuff.  Very basic, polling driver for doing serial + * console output.  The console_lock is held by the caller, so we + * shouldn't be interrupted for more console activity. + */ +static void sbd_console_putchar(struct uart_port *uport, int ch) +{ +	struct sbd_port *sport = to_sport(uport); + +	sbd_transmit_drain(sport); +	write_sbdchn(sport, R_DUART_TX_HOLD, ch); +} + +static void sbd_console_write(struct console *co, const char *s, +			      unsigned int count) +{ +	int chip = co->index / DUART_MAX_SIDE; +	int side = co->index % DUART_MAX_SIDE; +	struct sbd_port *sport = &sbd_duarts[chip].sport[side]; +	struct uart_port *uport = &sport->port; +	unsigned long flags; +	unsigned int mask; + +	/* Disable transmit interrupts and enable the transmitter. */ +	spin_lock_irqsave(&uport->lock, flags); +	mask = read_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2)); +	write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), +		     mask & ~M_DUART_IMR_TX); +	write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_EN); +	spin_unlock_irqrestore(&uport->lock, flags); + +	uart_console_write(&sport->port, s, count, sbd_console_putchar); + +	/* Restore transmit interrupts and the transmitter enable. */ +	spin_lock_irqsave(&uport->lock, flags); +	sbd_line_drain(sport); +	if (sport->tx_stopped) +		write_sbdchn(sport, R_DUART_CMD, M_DUART_TX_DIS); +	write_sbdshr(sport, R_DUART_IMRREG((uport->line) % 2), mask); +	spin_unlock_irqrestore(&uport->lock, flags); +} + +static int __init sbd_console_setup(struct console *co, char *options) +{ +	int chip = co->index / DUART_MAX_SIDE; +	int side = co->index % DUART_MAX_SIDE; +	struct sbd_port *sport = &sbd_duarts[chip].sport[side]; +	struct uart_port *uport = &sport->port; +	int baud = 115200; +	int bits = 8; +	int parity = 'n'; +	int flow = 'n'; +	int ret; + +	if (!sport->duart) +		return -ENXIO; + +	ret = sbd_map_port(uport); +	if (ret) +		return ret; + +	sbd_init_port(sport); + +	if (options) +		uart_parse_options(options, &baud, &parity, &bits, &flow); +	return uart_set_options(uport, co, baud, parity, bits, flow); +} + +static struct uart_driver sbd_reg; +static struct console sbd_console = { +	.name	= "duart", +	.write	= sbd_console_write, +	.device	= uart_console_device, +	.setup	= sbd_console_setup, +	.flags	= CON_PRINTBUFFER, +	.index	= -1, +	.data	= &sbd_reg +}; + +static int __init sbd_serial_console_init(void) +{ +	sbd_probe_duarts(); +	register_console(&sbd_console); + +	return 0; +} + +console_initcall(sbd_serial_console_init); + +#define SERIAL_SB1250_DUART_CONSOLE	&sbd_console +#else +#define SERIAL_SB1250_DUART_CONSOLE	NULL +#endif /* CONFIG_SERIAL_SB1250_DUART_CONSOLE */ + + +static struct uart_driver sbd_reg = { +	.owner		= THIS_MODULE, +	.driver_name	= "sb1250_duart", +	.dev_name	= "duart", +	.major		= TTY_MAJOR, +	.minor		= SB1250_DUART_MINOR_BASE, +	.nr		= DUART_MAX_CHIP * DUART_MAX_SIDE, +	.cons		= SERIAL_SB1250_DUART_CONSOLE, +}; + +/* Set up the driver and register it.  */ +static int __init sbd_init(void) +{ +	int i, ret; + +	sbd_probe_duarts(); + +	ret = uart_register_driver(&sbd_reg); +	if (ret) +		return ret; + +	for (i = 0; i < DUART_MAX_CHIP * DUART_MAX_SIDE; i++) { +		struct sbd_duart *duart = &sbd_duarts[i / DUART_MAX_SIDE]; +		struct sbd_port *sport = &duart->sport[i % DUART_MAX_SIDE]; +		struct uart_port *uport = &sport->port; + +		if (sport->duart) +			uart_add_one_port(&sbd_reg, uport); +	} + +	return 0; +} + +/* Unload the driver.  Unregister stuff, get ready to go away.  */ +static void __exit sbd_exit(void) +{ +	int i; + +	for (i = DUART_MAX_CHIP * DUART_MAX_SIDE - 1; i >= 0; i--) { +		struct sbd_duart *duart = &sbd_duarts[i / DUART_MAX_SIDE]; +		struct sbd_port *sport = &duart->sport[i % DUART_MAX_SIDE]; +		struct uart_port *uport = &sport->port; + +		if (sport->duart) +			uart_remove_one_port(&sbd_reg, uport); +	} + +	uart_unregister_driver(&sbd_reg); +} + +module_init(sbd_init); +module_exit(sbd_exit);  | 
