tty/serial: Add Spreadtrum sc9836-uart driver support
Add a full sc9836-uart driver for SC9836 SoC which is based on the spreadtrum sharkl64 platform. This driver also support earlycon. Originally-by: Lanqing Liu <lanqing.liu@spreadtrum.com> Signed-off-by: Orson Zhai <orson.zhai@spreadtrum.com> Signed-off-by: Chunyan Zhang <chunyan.zhang@spreadtrum.com> Acked-by: Arnd Bergmann <arnd@arndb.de> Reviewed-by: Peter Hurley <peter@hurleysoftware.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
		
							parent
							
								
									e570f6bca4
								
							
						
					
					
						commit
						b7396a38fb
					
				| @ -1583,6 +1583,24 @@ config SERIAL_MEN_Z135 | |||||||
| 	  This driver can also be build as a module. If so, the module will be called | 	  This driver can also be build as a module. If so, the module will be called | ||||||
| 	  men_z135_uart.ko | 	  men_z135_uart.ko | ||||||
| 
 | 
 | ||||||
|  | config SERIAL_SPRD | ||||||
|  | 	tristate "Support for Spreadtrum serial" | ||||||
|  | 	depends on ARCH_SPRD | ||||||
|  | 	select SERIAL_CORE | ||||||
|  | 	help | ||||||
|  | 	  This enables the driver for the Spreadtrum's serial. | ||||||
|  | 
 | ||||||
|  | config SERIAL_SPRD_CONSOLE | ||||||
|  | 	bool "Spreadtrum UART console support" | ||||||
|  | 	depends on SERIAL_SPRD=y | ||||||
|  | 	select SERIAL_CORE_CONSOLE | ||||||
|  | 	select SERIAL_EARLYCON | ||||||
|  | 	help | ||||||
|  | 	  Support for early debug console using Spreadtrum's serial. This enables | ||||||
|  | 	  the console before standard serial driver is probed. This is enabled | ||||||
|  | 	  with "earlycon" on the kernel command line. The console is | ||||||
|  | 	  enabled when early_param is processed. | ||||||
|  | 
 | ||||||
| endmenu | endmenu | ||||||
| 
 | 
 | ||||||
| config SERIAL_MCTRL_GPIO | config SERIAL_MCTRL_GPIO | ||||||
|  | |||||||
| @ -93,6 +93,7 @@ obj-$(CONFIG_SERIAL_RP2)	+= rp2.o | |||||||
| obj-$(CONFIG_SERIAL_FSL_LPUART)	+= fsl_lpuart.o | obj-$(CONFIG_SERIAL_FSL_LPUART)	+= fsl_lpuart.o | ||||||
| obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR)	+= digicolor-usart.o | obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR)	+= digicolor-usart.o | ||||||
| obj-$(CONFIG_SERIAL_MEN_Z135)	+= men_z135_uart.o | obj-$(CONFIG_SERIAL_MEN_Z135)	+= men_z135_uart.o | ||||||
|  | obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o | ||||||
| 
 | 
 | ||||||
| # GPIOLIB helpers for modem control lines
 | # GPIOLIB helpers for modem control lines
 | ||||||
| obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o | obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o | ||||||
|  | |||||||
							
								
								
									
										793
									
								
								drivers/tty/serial/sprd_serial.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										793
									
								
								drivers/tty/serial/sprd_serial.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,793 @@ | |||||||
|  | /*
 | ||||||
|  |  * Copyright (C) 2012-2015 Spreadtrum Communications Inc. | ||||||
|  |  * | ||||||
|  |  * This software is licensed under the terms of the GNU General Public | ||||||
|  |  * License version 2, as published by the Free Software Foundation, and | ||||||
|  |  * may be copied, distributed, and modified under those terms. | ||||||
|  |  * | ||||||
|  |  * This program is distributed in the hope that it will be useful, | ||||||
|  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |  * GNU General Public License for more details. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #if defined(CONFIG_SERIAL_SPRD_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) | ||||||
|  | #define SUPPORT_SYSRQ | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <linux/clk.h> | ||||||
|  | #include <linux/console.h> | ||||||
|  | #include <linux/delay.h> | ||||||
|  | #include <linux/io.h> | ||||||
|  | #include <linux/ioport.h> | ||||||
|  | #include <linux/kernel.h> | ||||||
|  | #include <linux/module.h> | ||||||
|  | #include <linux/of.h> | ||||||
|  | #include <linux/platform_device.h> | ||||||
|  | #include <linux/serial_core.h> | ||||||
|  | #include <linux/serial.h> | ||||||
|  | #include <linux/slab.h> | ||||||
|  | #include <linux/tty.h> | ||||||
|  | #include <linux/tty_flip.h> | ||||||
|  | 
 | ||||||
|  | /* device name */ | ||||||
|  | #define UART_NR_MAX		8 | ||||||
|  | #define SPRD_TTY_NAME		"ttyS" | ||||||
|  | #define SPRD_FIFO_SIZE		128 | ||||||
|  | #define SPRD_DEF_RATE		26000000 | ||||||
|  | #define SPRD_BAUD_IO_LIMIT	3000000 | ||||||
|  | #define SPRD_TIMEOUT		256 | ||||||
|  | 
 | ||||||
|  | /* the offset of serial registers and BITs for them */ | ||||||
|  | /* data registers */ | ||||||
|  | #define SPRD_TXD		0x0000 | ||||||
|  | #define SPRD_RXD		0x0004 | ||||||
|  | 
 | ||||||
|  | /* line status register and its BITs  */ | ||||||
|  | #define SPRD_LSR		0x0008 | ||||||
|  | #define SPRD_LSR_OE		BIT(4) | ||||||
|  | #define SPRD_LSR_FE		BIT(3) | ||||||
|  | #define SPRD_LSR_PE		BIT(2) | ||||||
|  | #define SPRD_LSR_BI		BIT(7) | ||||||
|  | #define SPRD_LSR_TX_OVER	BIT(15) | ||||||
|  | 
 | ||||||
|  | /* data number in TX and RX fifo */ | ||||||
|  | #define SPRD_STS1		0x000C | ||||||
|  | 
 | ||||||
|  | /* interrupt enable register and its BITs */ | ||||||
|  | #define SPRD_IEN		0x0010 | ||||||
|  | #define SPRD_IEN_RX_FULL	BIT(0) | ||||||
|  | #define SPRD_IEN_TX_EMPTY	BIT(1) | ||||||
|  | #define SPRD_IEN_BREAK_DETECT	BIT(7) | ||||||
|  | #define SPRD_IEN_TIMEOUT	BIT(13) | ||||||
|  | 
 | ||||||
|  | /* interrupt clear register */ | ||||||
|  | #define SPRD_ICLR		0x0014 | ||||||
|  | 
 | ||||||
|  | /* line control register */ | ||||||
|  | #define SPRD_LCR		0x0018 | ||||||
|  | #define SPRD_LCR_STOP_1BIT	0x10 | ||||||
|  | #define SPRD_LCR_STOP_2BIT	0x30 | ||||||
|  | #define SPRD_LCR_DATA_LEN	(BIT(2) | BIT(3)) | ||||||
|  | #define SPRD_LCR_DATA_LEN5	0x0 | ||||||
|  | #define SPRD_LCR_DATA_LEN6	0x4 | ||||||
|  | #define SPRD_LCR_DATA_LEN7	0x8 | ||||||
|  | #define SPRD_LCR_DATA_LEN8	0xc | ||||||
|  | #define SPRD_LCR_PARITY	(BIT(0) | BIT(1)) | ||||||
|  | #define SPRD_LCR_PARITY_EN	0x2 | ||||||
|  | #define SPRD_LCR_EVEN_PAR	0x0 | ||||||
|  | #define SPRD_LCR_ODD_PAR	0x1 | ||||||
|  | 
 | ||||||
|  | /* control register 1 */ | ||||||
|  | #define SPRD_CTL1			0x001C | ||||||
|  | #define RX_HW_FLOW_CTL_THLD	BIT(6) | ||||||
|  | #define RX_HW_FLOW_CTL_EN	BIT(7) | ||||||
|  | #define TX_HW_FLOW_CTL_EN	BIT(8) | ||||||
|  | #define RX_TOUT_THLD_DEF	0x3E00 | ||||||
|  | #define RX_HFC_THLD_DEF	0x40 | ||||||
|  | 
 | ||||||
|  | /* fifo threshold register */ | ||||||
|  | #define SPRD_CTL2		0x0020 | ||||||
|  | #define THLD_TX_EMPTY	0x40 | ||||||
|  | #define THLD_RX_FULL	0x40 | ||||||
|  | 
 | ||||||
|  | /* config baud rate register */ | ||||||
|  | #define SPRD_CLKD0		0x0024 | ||||||
|  | #define SPRD_CLKD1		0x0028 | ||||||
|  | 
 | ||||||
|  | /* interrupt mask status register */ | ||||||
|  | #define SPRD_IMSR			0x002C | ||||||
|  | #define SPRD_IMSR_RX_FIFO_FULL		BIT(0) | ||||||
|  | #define SPRD_IMSR_TX_FIFO_EMPTY	BIT(1) | ||||||
|  | #define SPRD_IMSR_BREAK_DETECT		BIT(7) | ||||||
|  | #define SPRD_IMSR_TIMEOUT		BIT(13) | ||||||
|  | 
 | ||||||
|  | struct reg_backup { | ||||||
|  | 	u32 ien; | ||||||
|  | 	u32 ctrl0; | ||||||
|  | 	u32 ctrl1; | ||||||
|  | 	u32 ctrl2; | ||||||
|  | 	u32 clkd0; | ||||||
|  | 	u32 clkd1; | ||||||
|  | 	u32 dspwait; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct sprd_uart_port { | ||||||
|  | 	struct uart_port port; | ||||||
|  | 	struct reg_backup reg_bak; | ||||||
|  | 	char name[16]; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct sprd_uart_port *sprd_port[UART_NR_MAX]; | ||||||
|  | static int sprd_ports_num; | ||||||
|  | 
 | ||||||
|  | static inline unsigned int serial_in(struct uart_port *port, int offset) | ||||||
|  | { | ||||||
|  | 	return readl_relaxed(port->membase + offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void serial_out(struct uart_port *port, int offset, int value) | ||||||
|  | { | ||||||
|  | 	writel_relaxed(value, port->membase + offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static unsigned int sprd_tx_empty(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	if (serial_in(port, SPRD_STS1) & 0xff00) | ||||||
|  | 		return 0; | ||||||
|  | 	else | ||||||
|  | 		return TIOCSER_TEMT; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static unsigned int sprd_get_mctrl(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	return TIOCM_DSR | TIOCM_CTS; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_set_mctrl(struct uart_port *port, unsigned int mctrl) | ||||||
|  | { | ||||||
|  | 	/* nothing to do */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_stop_tx(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	unsigned int ien, iclr; | ||||||
|  | 
 | ||||||
|  | 	iclr = serial_in(port, SPRD_ICLR); | ||||||
|  | 	ien = serial_in(port, SPRD_IEN); | ||||||
|  | 
 | ||||||
|  | 	iclr |= SPRD_IEN_TX_EMPTY; | ||||||
|  | 	ien &= ~SPRD_IEN_TX_EMPTY; | ||||||
|  | 
 | ||||||
|  | 	serial_out(port, SPRD_ICLR, iclr); | ||||||
|  | 	serial_out(port, SPRD_IEN, ien); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_start_tx(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	unsigned int ien; | ||||||
|  | 
 | ||||||
|  | 	ien = serial_in(port, SPRD_IEN); | ||||||
|  | 	if (!(ien & SPRD_IEN_TX_EMPTY)) { | ||||||
|  | 		ien |= SPRD_IEN_TX_EMPTY; | ||||||
|  | 		serial_out(port, SPRD_IEN, ien); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_stop_rx(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	unsigned int ien, iclr; | ||||||
|  | 
 | ||||||
|  | 	iclr = serial_in(port, SPRD_ICLR); | ||||||
|  | 	ien = serial_in(port, SPRD_IEN); | ||||||
|  | 
 | ||||||
|  | 	ien &= ~(SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT); | ||||||
|  | 	iclr |= SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT; | ||||||
|  | 
 | ||||||
|  | 	serial_out(port, SPRD_IEN, ien); | ||||||
|  | 	serial_out(port, SPRD_ICLR, iclr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* The Sprd serial does not support this function. */ | ||||||
|  | static void sprd_break_ctl(struct uart_port *port, int break_state) | ||||||
|  | { | ||||||
|  | 	/* nothing to do */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int handle_lsr_errors(struct uart_port *port, | ||||||
|  | 			     unsigned int *flag, | ||||||
|  | 			     unsigned int *lsr) | ||||||
|  | { | ||||||
|  | 	int ret = 0; | ||||||
|  | 
 | ||||||
|  | 	/* statistics */ | ||||||
|  | 	if (*lsr & SPRD_LSR_BI) { | ||||||
|  | 		*lsr &= ~(SPRD_LSR_FE | SPRD_LSR_PE); | ||||||
|  | 		port->icount.brk++; | ||||||
|  | 		ret = uart_handle_break(port); | ||||||
|  | 		if (ret) | ||||||
|  | 			return ret; | ||||||
|  | 	} else if (*lsr & SPRD_LSR_PE) | ||||||
|  | 		port->icount.parity++; | ||||||
|  | 	else if (*lsr & SPRD_LSR_FE) | ||||||
|  | 		port->icount.frame++; | ||||||
|  | 	if (*lsr & SPRD_LSR_OE) | ||||||
|  | 		port->icount.overrun++; | ||||||
|  | 
 | ||||||
|  | 	/* mask off conditions which should be ignored */ | ||||||
|  | 	*lsr &= port->read_status_mask; | ||||||
|  | 	if (*lsr & SPRD_LSR_BI) | ||||||
|  | 		*flag = TTY_BREAK; | ||||||
|  | 	else if (*lsr & SPRD_LSR_PE) | ||||||
|  | 		*flag = TTY_PARITY; | ||||||
|  | 	else if (*lsr & SPRD_LSR_FE) | ||||||
|  | 		*flag = TTY_FRAME; | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void sprd_rx(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	struct tty_port *tty = &port->state->port; | ||||||
|  | 	unsigned int ch, flag, lsr, max_count = SPRD_TIMEOUT; | ||||||
|  | 
 | ||||||
|  | 	while ((serial_in(port, SPRD_STS1) & 0x00ff) && max_count--) { | ||||||
|  | 		lsr = serial_in(port, SPRD_LSR); | ||||||
|  | 		ch = serial_in(port, SPRD_RXD); | ||||||
|  | 		flag = TTY_NORMAL; | ||||||
|  | 		port->icount.rx++; | ||||||
|  | 
 | ||||||
|  | 		if (lsr & (SPRD_LSR_BI | SPRD_LSR_PE | | ||||||
|  | 			SPRD_LSR_FE | SPRD_LSR_OE)) | ||||||
|  | 			if (handle_lsr_errors(port, &lsr, &flag)) | ||||||
|  | 				continue; | ||||||
|  | 		if (uart_handle_sysrq_char(port, ch)) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		uart_insert_char(port, lsr, SPRD_LSR_OE, ch, flag); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tty_flip_buffer_push(tty); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void sprd_tx(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	struct circ_buf *xmit = &port->state->xmit; | ||||||
|  | 	int count; | ||||||
|  | 
 | ||||||
|  | 	if (port->x_char) { | ||||||
|  | 		serial_out(port, SPRD_TXD, port->x_char); | ||||||
|  | 		port->icount.tx++; | ||||||
|  | 		port->x_char = 0; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { | ||||||
|  | 		sprd_stop_tx(port); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	count = THLD_TX_EMPTY; | ||||||
|  | 	do { | ||||||
|  | 		serial_out(port, SPRD_TXD, xmit->buf[xmit->tail]); | ||||||
|  | 		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); | ||||||
|  | 		port->icount.tx++; | ||||||
|  | 		if (uart_circ_empty(xmit)) | ||||||
|  | 			break; | ||||||
|  | 	} while (--count > 0); | ||||||
|  | 
 | ||||||
|  | 	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) | ||||||
|  | 		uart_write_wakeup(port); | ||||||
|  | 
 | ||||||
|  | 	if (uart_circ_empty(xmit)) | ||||||
|  | 		sprd_stop_tx(port); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* this handles the interrupt from one port */ | ||||||
|  | static irqreturn_t sprd_handle_irq(int irq, void *dev_id) | ||||||
|  | { | ||||||
|  | 	struct uart_port *port = dev_id; | ||||||
|  | 	unsigned int ims; | ||||||
|  | 
 | ||||||
|  | 	spin_lock(&port->lock); | ||||||
|  | 
 | ||||||
|  | 	ims = serial_in(port, SPRD_IMSR); | ||||||
|  | 
 | ||||||
|  | 	if (!ims) | ||||||
|  | 		return IRQ_NONE; | ||||||
|  | 
 | ||||||
|  | 	serial_out(port, SPRD_ICLR, ~0); | ||||||
|  | 
 | ||||||
|  | 	if (ims & (SPRD_IMSR_RX_FIFO_FULL | | ||||||
|  | 		SPRD_IMSR_BREAK_DETECT | SPRD_IMSR_TIMEOUT)) | ||||||
|  | 		sprd_rx(port); | ||||||
|  | 
 | ||||||
|  | 	if (ims & SPRD_IMSR_TX_FIFO_EMPTY) | ||||||
|  | 		sprd_tx(port); | ||||||
|  | 
 | ||||||
|  | 	spin_unlock(&port->lock); | ||||||
|  | 
 | ||||||
|  | 	return IRQ_HANDLED; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sprd_startup(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	int ret = 0; | ||||||
|  | 	unsigned int ien, fc; | ||||||
|  | 	unsigned int timeout; | ||||||
|  | 	struct sprd_uart_port *sp; | ||||||
|  | 	unsigned long flags; | ||||||
|  | 
 | ||||||
|  | 	serial_out(port, SPRD_CTL2, ((THLD_TX_EMPTY << 8) | THLD_RX_FULL)); | ||||||
|  | 
 | ||||||
|  | 	/* clear rx fifo */ | ||||||
|  | 	timeout = SPRD_TIMEOUT; | ||||||
|  | 	while (timeout-- && serial_in(port, SPRD_STS1) & 0x00ff) | ||||||
|  | 		serial_in(port, SPRD_RXD); | ||||||
|  | 
 | ||||||
|  | 	/* clear tx fifo */ | ||||||
|  | 	timeout = SPRD_TIMEOUT; | ||||||
|  | 	while (timeout-- && serial_in(port, SPRD_STS1) & 0xff00) | ||||||
|  | 		cpu_relax(); | ||||||
|  | 
 | ||||||
|  | 	/* clear interrupt */ | ||||||
|  | 	serial_out(port, SPRD_IEN, 0); | ||||||
|  | 	serial_out(port, SPRD_ICLR, ~0); | ||||||
|  | 
 | ||||||
|  | 	/* allocate irq */ | ||||||
|  | 	sp = container_of(port, struct sprd_uart_port, port); | ||||||
|  | 	snprintf(sp->name, sizeof(sp->name), "sprd_serial%d", port->line); | ||||||
|  | 	ret = devm_request_irq(port->dev, port->irq, sprd_handle_irq, | ||||||
|  | 				IRQF_SHARED, sp->name, port); | ||||||
|  | 	if (ret) { | ||||||
|  | 		dev_err(port->dev, "fail to request serial irq %d, ret=%d\n", | ||||||
|  | 			port->irq, ret); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  | 	fc = serial_in(port, SPRD_CTL1); | ||||||
|  | 	fc |= RX_TOUT_THLD_DEF | RX_HFC_THLD_DEF; | ||||||
|  | 	serial_out(port, SPRD_CTL1, fc); | ||||||
|  | 
 | ||||||
|  | 	/* enable interrupt */ | ||||||
|  | 	spin_lock_irqsave(&port->lock, flags); | ||||||
|  | 	ien = serial_in(port, SPRD_IEN); | ||||||
|  | 	ien |= SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT | SPRD_IEN_TIMEOUT; | ||||||
|  | 	serial_out(port, SPRD_IEN, ien); | ||||||
|  | 	spin_unlock_irqrestore(&port->lock, flags); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_shutdown(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	serial_out(port, SPRD_IEN, 0); | ||||||
|  | 	serial_out(port, SPRD_ICLR, ~0); | ||||||
|  | 	devm_free_irq(port->dev, port->irq, port); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_set_termios(struct uart_port *port, | ||||||
|  | 				    struct ktermios *termios, | ||||||
|  | 				    struct ktermios *old) | ||||||
|  | { | ||||||
|  | 	unsigned int baud, quot; | ||||||
|  | 	unsigned int lcr = 0, fc; | ||||||
|  | 	unsigned long flags; | ||||||
|  | 
 | ||||||
|  | 	/* ask the core to calculate the divisor for us */ | ||||||
|  | 	baud = uart_get_baud_rate(port, termios, old, 0, SPRD_BAUD_IO_LIMIT); | ||||||
|  | 
 | ||||||
|  | 	quot = (unsigned int)((port->uartclk + baud / 2) / baud); | ||||||
|  | 
 | ||||||
|  | 	/* set data length */ | ||||||
|  | 	switch (termios->c_cflag & CSIZE) { | ||||||
|  | 	case CS5: | ||||||
|  | 		lcr |= SPRD_LCR_DATA_LEN5; | ||||||
|  | 		break; | ||||||
|  | 	case CS6: | ||||||
|  | 		lcr |= SPRD_LCR_DATA_LEN6; | ||||||
|  | 		break; | ||||||
|  | 	case CS7: | ||||||
|  | 		lcr |= SPRD_LCR_DATA_LEN7; | ||||||
|  | 		break; | ||||||
|  | 	case CS8: | ||||||
|  | 	default: | ||||||
|  | 		lcr |= SPRD_LCR_DATA_LEN8; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* calculate stop bits */ | ||||||
|  | 	lcr &= ~(SPRD_LCR_STOP_1BIT | SPRD_LCR_STOP_2BIT); | ||||||
|  | 	if (termios->c_cflag & CSTOPB) | ||||||
|  | 		lcr |= SPRD_LCR_STOP_2BIT; | ||||||
|  | 	else | ||||||
|  | 		lcr |= SPRD_LCR_STOP_1BIT; | ||||||
|  | 
 | ||||||
|  | 	/* calculate parity */ | ||||||
|  | 	lcr &= ~SPRD_LCR_PARITY; | ||||||
|  | 	termios->c_cflag &= ~CMSPAR;	/* no support mark/space */ | ||||||
|  | 	if (termios->c_cflag & PARENB) { | ||||||
|  | 		lcr |= SPRD_LCR_PARITY_EN; | ||||||
|  | 		if (termios->c_cflag & PARODD) | ||||||
|  | 			lcr |= SPRD_LCR_ODD_PAR; | ||||||
|  | 		else | ||||||
|  | 			lcr |= SPRD_LCR_EVEN_PAR; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	spin_lock_irqsave(&port->lock, flags); | ||||||
|  | 
 | ||||||
|  | 	/* update the per-port timeout */ | ||||||
|  | 	uart_update_timeout(port, termios->c_cflag, baud); | ||||||
|  | 
 | ||||||
|  | 	port->read_status_mask = SPRD_LSR_OE; | ||||||
|  | 	if (termios->c_iflag & INPCK) | ||||||
|  | 		port->read_status_mask |= SPRD_LSR_FE | SPRD_LSR_PE; | ||||||
|  | 	if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK)) | ||||||
|  | 		port->read_status_mask |= SPRD_LSR_BI; | ||||||
|  | 
 | ||||||
|  | 	/* characters to ignore */ | ||||||
|  | 	port->ignore_status_mask = 0; | ||||||
|  | 	if (termios->c_iflag & IGNPAR) | ||||||
|  | 		port->ignore_status_mask |= SPRD_LSR_PE | SPRD_LSR_FE; | ||||||
|  | 	if (termios->c_iflag & IGNBRK) { | ||||||
|  | 		port->ignore_status_mask |= SPRD_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 |= SPRD_LSR_OE; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* flow control */ | ||||||
|  | 	fc = serial_in(port, SPRD_CTL1); | ||||||
|  | 	fc &= ~(RX_HW_FLOW_CTL_THLD | RX_HW_FLOW_CTL_EN | TX_HW_FLOW_CTL_EN); | ||||||
|  | 	if (termios->c_cflag & CRTSCTS) { | ||||||
|  | 		fc |= RX_HW_FLOW_CTL_THLD; | ||||||
|  | 		fc |= RX_HW_FLOW_CTL_EN; | ||||||
|  | 		fc |= TX_HW_FLOW_CTL_EN; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* clock divider bit0~bit15 */ | ||||||
|  | 	serial_out(port, SPRD_CLKD0, quot & 0xffff); | ||||||
|  | 
 | ||||||
|  | 	/* clock divider bit16~bit20 */ | ||||||
|  | 	serial_out(port, SPRD_CLKD1, (quot & 0x1f0000) >> 16); | ||||||
|  | 	serial_out(port, SPRD_LCR, lcr); | ||||||
|  | 	fc |= RX_TOUT_THLD_DEF | RX_HFC_THLD_DEF; | ||||||
|  | 	serial_out(port, SPRD_CTL1, fc); | ||||||
|  | 
 | ||||||
|  | 	spin_unlock_irqrestore(&port->lock, flags); | ||||||
|  | 
 | ||||||
|  | 	/* Don't rewrite B0 */ | ||||||
|  | 	if (tty_termios_baud_rate(termios)) | ||||||
|  | 		tty_termios_encode_baud_rate(termios, baud, baud); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const char *sprd_type(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	return "SPX"; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_release_port(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	/* nothing to do */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sprd_request_port(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_config_port(struct uart_port *port, int flags) | ||||||
|  | { | ||||||
|  | 	if (flags & UART_CONFIG_TYPE) | ||||||
|  | 		port->type = PORT_SPRD; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sprd_verify_port(struct uart_port *port, | ||||||
|  | 				   struct serial_struct *ser) | ||||||
|  | { | ||||||
|  | 	if (ser->type != PORT_SPRD) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	if (port->irq != ser->irq) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct uart_ops serial_sprd_ops = { | ||||||
|  | 	.tx_empty = sprd_tx_empty, | ||||||
|  | 	.get_mctrl = sprd_get_mctrl, | ||||||
|  | 	.set_mctrl = sprd_set_mctrl, | ||||||
|  | 	.stop_tx = sprd_stop_tx, | ||||||
|  | 	.start_tx = sprd_start_tx, | ||||||
|  | 	.stop_rx = sprd_stop_rx, | ||||||
|  | 	.break_ctl = sprd_break_ctl, | ||||||
|  | 	.startup = sprd_startup, | ||||||
|  | 	.shutdown = sprd_shutdown, | ||||||
|  | 	.set_termios = sprd_set_termios, | ||||||
|  | 	.type = sprd_type, | ||||||
|  | 	.release_port = sprd_release_port, | ||||||
|  | 	.request_port = sprd_request_port, | ||||||
|  | 	.config_port = sprd_config_port, | ||||||
|  | 	.verify_port = sprd_verify_port, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #ifdef CONFIG_SERIAL_SPRD_CONSOLE | ||||||
|  | static inline void wait_for_xmitr(struct uart_port *port) | ||||||
|  | { | ||||||
|  | 	unsigned int status, tmout = 10000; | ||||||
|  | 
 | ||||||
|  | 	/* wait up to 10ms for the character(s) to be sent */ | ||||||
|  | 	do { | ||||||
|  | 		status = serial_in(port, SPRD_STS1); | ||||||
|  | 		if (--tmout == 0) | ||||||
|  | 			break; | ||||||
|  | 		udelay(1); | ||||||
|  | 	} while (status & 0xff00); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_console_putchar(struct uart_port *port, int ch) | ||||||
|  | { | ||||||
|  | 	wait_for_xmitr(port); | ||||||
|  | 	serial_out(port, SPRD_TXD, ch); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_console_write(struct console *co, const char *s, | ||||||
|  | 				      unsigned int count) | ||||||
|  | { | ||||||
|  | 	struct uart_port *port = &sprd_port[co->index]->port; | ||||||
|  | 	int locked = 1; | ||||||
|  | 	unsigned long flags; | ||||||
|  | 
 | ||||||
|  | 	if (port->sysrq) | ||||||
|  | 		locked = 0; | ||||||
|  | 	else if (oops_in_progress) | ||||||
|  | 		locked = spin_trylock_irqsave(&port->lock, flags); | ||||||
|  | 	else | ||||||
|  | 		spin_lock_irqsave(&port->lock, flags); | ||||||
|  | 
 | ||||||
|  | 	uart_console_write(port, s, count, sprd_console_putchar); | ||||||
|  | 
 | ||||||
|  | 	/* wait for transmitter to become empty */ | ||||||
|  | 	wait_for_xmitr(port); | ||||||
|  | 
 | ||||||
|  | 	if (locked) | ||||||
|  | 		spin_unlock_irqrestore(&port->lock, flags); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int __init sprd_console_setup(struct console *co, char *options) | ||||||
|  | { | ||||||
|  | 	struct uart_port *port; | ||||||
|  | 	int baud = 115200; | ||||||
|  | 	int bits = 8; | ||||||
|  | 	int parity = 'n'; | ||||||
|  | 	int flow = 'n'; | ||||||
|  | 
 | ||||||
|  | 	if (co->index >= UART_NR_MAX || co->index < 0) | ||||||
|  | 		co->index = 0; | ||||||
|  | 
 | ||||||
|  | 	port = &sprd_port[co->index]->port; | ||||||
|  | 	if (port == NULL) { | ||||||
|  | 		pr_info("serial port %d not yet initialized\n", co->index); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 	if (options) | ||||||
|  | 		uart_parse_options(options, &baud, &parity, &bits, &flow); | ||||||
|  | 
 | ||||||
|  | 	return uart_set_options(port, co, baud, parity, bits, flow); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct uart_driver sprd_uart_driver; | ||||||
|  | static struct console sprd_console = { | ||||||
|  | 	.name = SPRD_TTY_NAME, | ||||||
|  | 	.write = sprd_console_write, | ||||||
|  | 	.device = uart_console_device, | ||||||
|  | 	.setup = sprd_console_setup, | ||||||
|  | 	.flags = CON_PRINTBUFFER, | ||||||
|  | 	.index = -1, | ||||||
|  | 	.data = &sprd_uart_driver, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #define SPRD_CONSOLE	(&sprd_console) | ||||||
|  | 
 | ||||||
|  | /* Support for earlycon */ | ||||||
|  | static void sprd_putc(struct uart_port *port, int c) | ||||||
|  | { | ||||||
|  | 	unsigned int timeout = SPRD_TIMEOUT; | ||||||
|  | 
 | ||||||
|  | 	while (timeout-- && | ||||||
|  | 		   !(readl(port->membase + SPRD_LSR) & SPRD_LSR_TX_OVER)) | ||||||
|  | 		cpu_relax(); | ||||||
|  | 
 | ||||||
|  | 	writeb(c, port->membase + SPRD_TXD); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void sprd_early_write(struct console *con, const char *s, | ||||||
|  | 				    unsigned n) | ||||||
|  | { | ||||||
|  | 	struct earlycon_device *dev = con->data; | ||||||
|  | 
 | ||||||
|  | 	uart_console_write(&dev->port, s, n, sprd_putc); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int __init sprd_early_console_setup( | ||||||
|  | 				struct earlycon_device *device, | ||||||
|  | 				const char *opt) | ||||||
|  | { | ||||||
|  | 	if (!device->port.membase) | ||||||
|  | 		return -ENODEV; | ||||||
|  | 
 | ||||||
|  | 	device->con->write = sprd_early_write; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | EARLYCON_DECLARE(sprd_serial, sprd_early_console_setup); | ||||||
|  | OF_EARLYCON_DECLARE(sprd_serial, "sprd,sc9836-uart", | ||||||
|  | 		    sprd_early_console_setup); | ||||||
|  | 
 | ||||||
|  | #else /* !CONFIG_SERIAL_SPRD_CONSOLE */ | ||||||
|  | #define SPRD_CONSOLE		NULL | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | static struct uart_driver sprd_uart_driver = { | ||||||
|  | 	.owner = THIS_MODULE, | ||||||
|  | 	.driver_name = "sprd_serial", | ||||||
|  | 	.dev_name = SPRD_TTY_NAME, | ||||||
|  | 	.major = 0, | ||||||
|  | 	.minor = 0, | ||||||
|  | 	.nr = UART_NR_MAX, | ||||||
|  | 	.cons = SPRD_CONSOLE, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int sprd_probe_dt_alias(int index, struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct device_node *np; | ||||||
|  | 	int ret = index; | ||||||
|  | 
 | ||||||
|  | 	if (!IS_ENABLED(CONFIG_OF)) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	np = dev->of_node; | ||||||
|  | 	if (!np) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	ret = of_alias_get_id(np, "serial"); | ||||||
|  | 	if (IS_ERR_VALUE(ret)) | ||||||
|  | 		ret = index; | ||||||
|  | 	else if (ret >= ARRAY_SIZE(sprd_port) || sprd_port[ret] != NULL) { | ||||||
|  | 		dev_warn(dev, "requested serial port %d not available.\n", ret); | ||||||
|  | 		ret = index; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sprd_remove(struct platform_device *dev) | ||||||
|  | { | ||||||
|  | 	struct sprd_uart_port *sup = platform_get_drvdata(dev); | ||||||
|  | 
 | ||||||
|  | 	if (sup) { | ||||||
|  | 		uart_remove_one_port(&sprd_uart_driver, &sup->port); | ||||||
|  | 		sprd_port[sup->port.line] = NULL; | ||||||
|  | 		sprd_ports_num--; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!sprd_ports_num) | ||||||
|  | 		uart_unregister_driver(&sprd_uart_driver); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sprd_probe(struct platform_device *pdev) | ||||||
|  | { | ||||||
|  | 	struct resource *res; | ||||||
|  | 	struct uart_port *up; | ||||||
|  | 	struct clk *clk; | ||||||
|  | 	int irq; | ||||||
|  | 	int index; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	for (index = 0; index < ARRAY_SIZE(sprd_port); index++) | ||||||
|  | 		if (sprd_port[index] == NULL) | ||||||
|  | 			break; | ||||||
|  | 
 | ||||||
|  | 	if (index == ARRAY_SIZE(sprd_port)) | ||||||
|  | 		return -EBUSY; | ||||||
|  | 
 | ||||||
|  | 	index = sprd_probe_dt_alias(index, &pdev->dev); | ||||||
|  | 
 | ||||||
|  | 	sprd_port[index] = devm_kzalloc(&pdev->dev, | ||||||
|  | 		sizeof(*sprd_port[index]), GFP_KERNEL); | ||||||
|  | 	if (!sprd_port[index]) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	up = &sprd_port[index]->port; | ||||||
|  | 	up->dev = &pdev->dev; | ||||||
|  | 	up->line = index; | ||||||
|  | 	up->type = PORT_SPRD; | ||||||
|  | 	up->iotype = SERIAL_IO_PORT; | ||||||
|  | 	up->uartclk = SPRD_DEF_RATE; | ||||||
|  | 	up->fifosize = SPRD_FIFO_SIZE; | ||||||
|  | 	up->ops = &serial_sprd_ops; | ||||||
|  | 	up->flags = UPF_BOOT_AUTOCONF; | ||||||
|  | 
 | ||||||
|  | 	clk = devm_clk_get(&pdev->dev, NULL); | ||||||
|  | 	if (!IS_ERR(clk)) | ||||||
|  | 		up->uartclk = clk_get_rate(clk); | ||||||
|  | 
 | ||||||
|  | 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||||||
|  | 	if (!res) { | ||||||
|  | 		dev_err(&pdev->dev, "not provide mem resource\n"); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 	up->mapbase = res->start; | ||||||
|  | 	up->membase = devm_ioremap_resource(&pdev->dev, res); | ||||||
|  | 	if (IS_ERR(up->membase)) | ||||||
|  | 		return PTR_ERR(up->membase); | ||||||
|  | 
 | ||||||
|  | 	irq = platform_get_irq(pdev, 0); | ||||||
|  | 	if (irq < 0) { | ||||||
|  | 		dev_err(&pdev->dev, "not provide irq resource\n"); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 	up->irq = irq; | ||||||
|  | 
 | ||||||
|  | 	if (!sprd_ports_num) { | ||||||
|  | 		ret = uart_register_driver(&sprd_uart_driver); | ||||||
|  | 		if (ret < 0) { | ||||||
|  | 			pr_err("Failed to register SPRD-UART driver\n"); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	sprd_ports_num++; | ||||||
|  | 
 | ||||||
|  | 	ret = uart_add_one_port(&sprd_uart_driver, up); | ||||||
|  | 	if (ret) { | ||||||
|  | 		sprd_port[index] = NULL; | ||||||
|  | 		sprd_remove(pdev); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	platform_set_drvdata(pdev, up); | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sprd_suspend(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct sprd_uart_port *sup = dev_get_drvdata(dev); | ||||||
|  | 
 | ||||||
|  | 	uart_suspend_port(&sprd_uart_driver, &sup->port); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sprd_resume(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct sprd_uart_port *sup = dev_get_drvdata(dev); | ||||||
|  | 
 | ||||||
|  | 	uart_resume_port(&sprd_uart_driver, &sup->port); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static SIMPLE_DEV_PM_OPS(sprd_pm_ops, sprd_suspend, sprd_resume); | ||||||
|  | 
 | ||||||
|  | static const struct of_device_id serial_ids[] = { | ||||||
|  | 	{.compatible = "sprd,sc9836-uart",}, | ||||||
|  | 	{} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct platform_driver sprd_platform_driver = { | ||||||
|  | 	.probe		= sprd_probe, | ||||||
|  | 	.remove		= sprd_remove, | ||||||
|  | 	.driver		= { | ||||||
|  | 		.name	= "sprd_serial", | ||||||
|  | 		.of_match_table = of_match_ptr(serial_ids), | ||||||
|  | 		.pm	= &sprd_pm_ops, | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module_platform_driver(sprd_platform_driver); | ||||||
|  | 
 | ||||||
|  | MODULE_LICENSE("GPL v2"); | ||||||
|  | MODULE_DESCRIPTION("Spreadtrum SoC serial driver series"); | ||||||
| @ -252,4 +252,7 @@ | |||||||
| /* Conexant Digicolor */ | /* Conexant Digicolor */ | ||||||
| #define PORT_DIGICOLOR	110 | #define PORT_DIGICOLOR	110 | ||||||
| 
 | 
 | ||||||
|  | /* SPRD SERIAL  */ | ||||||
|  | #define PORT_SPRD	111 | ||||||
|  | 
 | ||||||
| #endif /* _UAPILINUX_SERIAL_CORE_H */ | #endif /* _UAPILINUX_SERIAL_CORE_H */ | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user