767 lines · 18.3 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Sunplus SoC UART driver * * Author: Hammer Hsieh <hammerh0314@gmail.com> * * Note1: This driver is 8250-like uart, but are not register compatible. * * Note2: On some buses, for preventing data incoherence, must do a read * for ensure write made it to hardware. In this driver, function startup * and shutdown did not do a read but only do a write directly. For what? * In Sunplus bus communication between memory bus and peripheral bus with * posted write, it will send a specific command after last write command * to make sure write done. Then memory bus identify the specific command * and send done signal back to master device. After master device received * done signal, then proceed next write command. It is no need to do a read * before write. */#include <linux/clk.h>#include <linux/console.h>#include <linux/interrupt.h>#include <linux/io.h>#include <linux/iopoll.h>#include <linux/module.h>#include <linux/of.h>#include <linux/of_platform.h>#include <linux/platform_device.h>#include <linux/reset.h>#include <linux/serial_core.h>#include <linux/serial_reg.h>#include <linux/sysrq.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <asm/irq.h> /* Register offsets */#define SUP_UART_DATA			0x00#define SUP_UART_LSR			0x04#define SUP_UART_MSR			0x08#define SUP_UART_LCR			0x0C#define SUP_UART_MCR			0x10#define SUP_UART_DIV_L			0x14#define SUP_UART_DIV_H			0x18#define SUP_UART_ISC			0x1C#define SUP_UART_TX_RESIDUE		0x20#define SUP_UART_RX_RESIDUE		0x24 /* Line Status Register bits */#define SUP_UART_LSR_BC			BIT(5) /* break condition status */#define SUP_UART_LSR_FE			BIT(4) /* frame error status */#define SUP_UART_LSR_OE			BIT(3) /* overrun error status */#define SUP_UART_LSR_PE			BIT(2) /* parity error status */#define SUP_UART_LSR_RX			BIT(1) /* 1: receive fifo not empty */#define SUP_UART_LSR_TX			BIT(0) /* 1: transmit fifo is not full */#define SUP_UART_LSR_TX_NOT_FULL	1#define SUP_UART_LSR_BRK_ERROR_BITS	GENMASK(5, 2) /* Line Control Register bits */#define SUP_UART_LCR_SBC		BIT(5) /* select break condition */ /* Modem Control Register bits */#define SUP_UART_MCR_RI			BIT(3) /* ring indicator */#define SUP_UART_MCR_DCD		BIT(2) /* data carrier detect */ /* Interrupt Status/Control Register bits */#define SUP_UART_ISC_RXM		BIT(5) /* RX interrupt enable */#define SUP_UART_ISC_TXM		BIT(4) /* TX interrupt enable */#define SUP_UART_ISC_RX			BIT(1) /* RX interrupt status */#define SUP_UART_ISC_TX			BIT(0) /* TX interrupt status */ #define SUP_DUMMY_READ			BIT(16) /* drop bytes received on a !CREAD port */#define SUP_UART_NR			5 struct sunplus_uart_port {	struct uart_port port;	struct clk *clk;	struct reset_control *rstc;}; static void sp_uart_put_char(struct uart_port *port, unsigned int ch){	writel(ch, port->membase + SUP_UART_DATA);} static u32 sunplus_tx_buf_not_full(struct uart_port *port){	unsigned int lsr = readl(port->membase + SUP_UART_LSR); 	return (lsr & SUP_UART_LSR_TX) ? SUP_UART_LSR_TX_NOT_FULL : 0;} static unsigned int sunplus_tx_empty(struct uart_port *port){	unsigned int lsr = readl(port->membase + SUP_UART_LSR); 	return (lsr & UART_LSR_TEMT) ? TIOCSER_TEMT : 0;} static void sunplus_set_mctrl(struct uart_port *port, unsigned int mctrl){	unsigned int mcr = readl(port->membase + SUP_UART_MCR); 	if (mctrl & TIOCM_DTR)		mcr |= UART_MCR_DTR;	else		mcr &= ~UART_MCR_DTR; 	if (mctrl & TIOCM_RTS)		mcr |= UART_MCR_RTS;	else		mcr &= ~UART_MCR_RTS; 	if (mctrl & TIOCM_CAR)		mcr |= SUP_UART_MCR_DCD;	else		mcr &= ~SUP_UART_MCR_DCD; 	if (mctrl & TIOCM_RI)		mcr |= SUP_UART_MCR_RI;	else		mcr &= ~SUP_UART_MCR_RI; 	if (mctrl & TIOCM_LOOP)		mcr |= UART_MCR_LOOP;	else		mcr &= ~UART_MCR_LOOP; 	writel(mcr, port->membase + SUP_UART_MCR);} static unsigned int sunplus_get_mctrl(struct uart_port *port){	unsigned int mcr, ret = 0; 	mcr = readl(port->membase + SUP_UART_MCR); 	if (mcr & UART_MCR_DTR)		ret |= TIOCM_DTR; 	if (mcr & UART_MCR_RTS)		ret |= TIOCM_RTS; 	if (mcr & SUP_UART_MCR_DCD)		ret |= TIOCM_CAR; 	if (mcr & SUP_UART_MCR_RI)		ret |= TIOCM_RI; 	if (mcr & UART_MCR_LOOP)		ret |= TIOCM_LOOP; 	return ret;} static void sunplus_stop_tx(struct uart_port *port){	unsigned int isc; 	isc = readl(port->membase + SUP_UART_ISC);	isc &= ~SUP_UART_ISC_TXM;	writel(isc, port->membase + SUP_UART_ISC);} static void sunplus_start_tx(struct uart_port *port){	unsigned int isc; 	isc = readl(port->membase + SUP_UART_ISC);	isc |= SUP_UART_ISC_TXM;	writel(isc, port->membase + SUP_UART_ISC);} static void sunplus_stop_rx(struct uart_port *port){	unsigned int isc; 	isc = readl(port->membase + SUP_UART_ISC);	isc &= ~SUP_UART_ISC_RXM;	writel(isc, port->membase + SUP_UART_ISC);} static void sunplus_break_ctl(struct uart_port *port, int ctl){	unsigned long flags;	unsigned int lcr; 	uart_port_lock_irqsave(port, &flags); 	lcr = readl(port->membase + SUP_UART_LCR); 	if (ctl)		lcr |= SUP_UART_LCR_SBC; /* start break */	else		lcr &= ~SUP_UART_LCR_SBC; /* stop break */ 	writel(lcr, port->membase + SUP_UART_LCR); 	uart_port_unlock_irqrestore(port, flags);} static void transmit_chars(struct uart_port *port){	struct tty_port *tport = &port->state->port; 	if (port->x_char) {		sp_uart_put_char(port, port->x_char);		port->icount.tx++;		port->x_char = 0;		return;	} 	if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(port)) {		sunplus_stop_tx(port);		return;	} 	do {		unsigned char ch; 		if (!uart_fifo_get(port, &ch))			break; 		sp_uart_put_char(port, ch);	} while (sunplus_tx_buf_not_full(port)); 	if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)		uart_write_wakeup(port); 	if (kfifo_is_empty(&tport->xmit_fifo))		sunplus_stop_tx(port);} static void receive_chars(struct uart_port *port){	unsigned int lsr = readl(port->membase + SUP_UART_LSR);	u8 ch, flag; 	do {		ch = readl(port->membase + SUP_UART_DATA);		flag = TTY_NORMAL;		port->icount.rx++; 		if (unlikely(lsr & SUP_UART_LSR_BRK_ERROR_BITS)) {			if (lsr & SUP_UART_LSR_BC) {				lsr &= ~(SUP_UART_LSR_FE | SUP_UART_LSR_PE);				port->icount.brk++;				flag = TTY_BREAK;				if (uart_handle_break(port))					goto ignore_char;			} else if (lsr & SUP_UART_LSR_PE) {				port->icount.parity++;				flag = TTY_PARITY;			} else if (lsr & SUP_UART_LSR_FE) {				port->icount.frame++;				flag = TTY_FRAME;			} 			if (lsr & SUP_UART_LSR_OE)				port->icount.overrun++;		} 		if (port->ignore_status_mask & SUP_DUMMY_READ)			goto ignore_char; 		if (uart_prepare_sysrq_char(port, ch))			goto ignore_char; 		uart_insert_char(port, lsr, SUP_UART_LSR_OE, ch, flag); ignore_char:		lsr = readl(port->membase + SUP_UART_LSR);	} while (lsr & SUP_UART_LSR_RX); 	tty_flip_buffer_push(&port->state->port);} static irqreturn_t sunplus_uart_irq(int irq, void *args){	struct uart_port *port = args;	unsigned int isc; 	uart_port_lock(port); 	isc = readl(port->membase + SUP_UART_ISC); 	if (isc & SUP_UART_ISC_RX)		receive_chars(port); 	if (isc & SUP_UART_ISC_TX)		transmit_chars(port); 	uart_unlock_and_check_sysrq(port); 	return IRQ_HANDLED;} static int sunplus_startup(struct uart_port *port){	unsigned long flags;	unsigned int isc = 0;	int ret; 	ret = request_irq(port->irq, sunplus_uart_irq, 0, "sunplus_uart", port);	if (ret)		return ret; 	uart_port_lock_irqsave(port, &flags);	/* isc define Bit[7:4] int setting, Bit[3:0] int status	 * isc register will clean Bit[3:0] int status after read	 * only do a write to Bit[7:4] int setting	 */	isc |= SUP_UART_ISC_RXM;	writel(isc, port->membase + SUP_UART_ISC);	uart_port_unlock_irqrestore(port, flags); 	return 0;} static void sunplus_shutdown(struct uart_port *port){	unsigned long flags; 	uart_port_lock_irqsave(port, &flags);	/* isc define Bit[7:4] int setting, Bit[3:0] int status	 * isc register will clean Bit[3:0] int status after read	 * only do a write to Bit[7:4] int setting	 */	writel(0, port->membase + SUP_UART_ISC); /* disable all interrupt */	uart_port_unlock_irqrestore(port, flags); 	free_irq(port->irq, port);} static void sunplus_set_termios(struct uart_port *port,				struct ktermios *termios,				const struct ktermios *oldtermios){	u32 ext, div, div_l, div_h, baud, lcr;	u32 clk = port->uartclk;	unsigned long flags; 	baud = uart_get_baud_rate(port, termios, oldtermios, 0, port->uartclk / 16); 	/* baud rate = uartclk / ((16 * divisor + 1) + divisor_ext) */	clk += baud >> 1;	div = clk / baud;	ext = div & 0x0F;	div = (div >> 4) - 1;	div_l = (div & 0xFF) | (ext << 12);	div_h = div >> 8; 	switch (termios->c_cflag & CSIZE) {	case CS5:		lcr = UART_LCR_WLEN5;		break;	case CS6:		lcr = UART_LCR_WLEN6;		break;	case CS7:		lcr = UART_LCR_WLEN7;		break;	default:		lcr = UART_LCR_WLEN8;		break;	} 	if (termios->c_cflag & CSTOPB)		lcr |= UART_LCR_STOP; 	if (termios->c_cflag & PARENB) {		lcr |= UART_LCR_PARITY; 		if (!(termios->c_cflag & PARODD))			lcr |= UART_LCR_EPAR;	} 	uart_port_lock_irqsave(port, &flags); 	uart_update_timeout(port, termios->c_cflag, baud); 	port->read_status_mask = 0;	if (termios->c_iflag & INPCK)		port->read_status_mask |= SUP_UART_LSR_PE | SUP_UART_LSR_FE; 	if (termios->c_iflag & (BRKINT | PARMRK))		port->read_status_mask |= SUP_UART_LSR_BC; 	/* Characters to ignore */	port->ignore_status_mask = 0;	if (termios->c_iflag & IGNPAR)		port->ignore_status_mask |= SUP_UART_LSR_FE | SUP_UART_LSR_PE; 	if (termios->c_iflag & IGNBRK) {		port->ignore_status_mask |= SUP_UART_LSR_BC; 		if (termios->c_iflag & IGNPAR)			port->ignore_status_mask |= SUP_UART_LSR_OE;	} 	/* Ignore all characters if CREAD is not set */	if ((termios->c_cflag & CREAD) == 0) {		port->ignore_status_mask |= SUP_DUMMY_READ;		/* flush rx data FIFO */		writel(0, port->membase + SUP_UART_RX_RESIDUE);	} 	/* Settings for baud rate divisor and lcr */	writel(div_h, port->membase + SUP_UART_DIV_H);	writel(div_l, port->membase + SUP_UART_DIV_L);	writel(lcr, port->membase + SUP_UART_LCR); 	uart_port_unlock_irqrestore(port, flags);} static void sunplus_set_ldisc(struct uart_port *port, struct ktermios *termios){	int new = termios->c_line; 	if (new == N_PPS)		port->flags |= UPF_HARDPPS_CD;	else		port->flags &= ~UPF_HARDPPS_CD;} static const char *sunplus_type(struct uart_port *port){	return port->type == PORT_SUNPLUS ? "sunplus_uart" : NULL;} static void sunplus_config_port(struct uart_port *port, int type){	if (type & UART_CONFIG_TYPE)		port->type = PORT_SUNPLUS;} static int sunplus_verify_port(struct uart_port *port, struct serial_struct *ser){	if (ser->type != PORT_UNKNOWN && ser->type != PORT_SUNPLUS)		return -EINVAL; 	return 0;} #if defined(CONFIG_SERIAL_SUNPLUS_CONSOLE) || defined(CONFIG_CONSOLE_POLL)static void wait_for_xmitr(struct uart_port *port){	unsigned int val;	int ret; 	/* Wait while FIFO is full or timeout */	ret = readl_poll_timeout_atomic(port->membase + SUP_UART_LSR, val,					(val & SUP_UART_LSR_TX), 1, 10000); 	if (ret == -ETIMEDOUT) {		dev_err(port->dev, "Timeout waiting while UART TX FULL\n");		return;	}}#endif #ifdef CONFIG_CONSOLE_POLLstatic void sunplus_poll_put_char(struct uart_port *port, unsigned char data){	wait_for_xmitr(port);	sp_uart_put_char(port, data);} static int sunplus_poll_get_char(struct uart_port *port){	unsigned int lsr = readl(port->membase + SUP_UART_LSR); 	if (!(lsr & SUP_UART_LSR_RX))		return NO_POLL_CHAR; 	return readl(port->membase + SUP_UART_DATA);}#endif static const struct uart_ops sunplus_uart_ops = {	.tx_empty	= sunplus_tx_empty,	.set_mctrl	= sunplus_set_mctrl,	.get_mctrl	= sunplus_get_mctrl,	.stop_tx	= sunplus_stop_tx,	.start_tx	= sunplus_start_tx,	.stop_rx	= sunplus_stop_rx,	.break_ctl	= sunplus_break_ctl,	.startup	= sunplus_startup,	.shutdown	= sunplus_shutdown,	.set_termios	= sunplus_set_termios,	.set_ldisc	= sunplus_set_ldisc,	.type		= sunplus_type,	.config_port	= sunplus_config_port,	.verify_port	= sunplus_verify_port,#ifdef CONFIG_CONSOLE_POLL	.poll_put_char	= sunplus_poll_put_char,	.poll_get_char	= sunplus_poll_get_char,#endif}; #ifdef CONFIG_SERIAL_SUNPLUS_CONSOLE

Lines 1–500 of 767.