xref: /openbmc/linux/drivers/tty/serial/clps711x.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1e3b3d0f5SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0+
2ab4382d2SGreg Kroah-Hartman /*
3ab4382d2SGreg Kroah-Hartman  *  Driver for CLPS711x serial ports
4ab4382d2SGreg Kroah-Hartman  *
5ab4382d2SGreg Kroah-Hartman  *  Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o.
6ab4382d2SGreg Kroah-Hartman  *
7ab4382d2SGreg Kroah-Hartman  *  Copyright 1999 ARM Limited
8ab4382d2SGreg Kroah-Hartman  *  Copyright (C) 2000 Deep Blue Solutions Ltd.
9ab4382d2SGreg Kroah-Hartman  */
10ab4382d2SGreg Kroah-Hartman 
11ab4382d2SGreg Kroah-Hartman #include <linux/module.h>
12ab4382d2SGreg Kroah-Hartman #include <linux/device.h>
13a1c25f2bSAlexander Shiyan #include <linux/console.h>
14ab4382d2SGreg Kroah-Hartman #include <linux/serial_core.h>
15ab4382d2SGreg Kroah-Hartman #include <linux/serial.h>
16c08f0153SAlexander Shiyan #include <linux/clk.h>
17bc000245SAlexander Shiyan #include <linux/io.h>
18a1c25f2bSAlexander Shiyan #include <linux/tty.h>
19a1c25f2bSAlexander Shiyan #include <linux/tty_flip.h>
20a1c25f2bSAlexander Shiyan #include <linux/ioport.h>
21bc000245SAlexander Shiyan #include <linux/of.h>
2295113728SAlexander Shiyan #include <linux/platform_device.h>
23bc000245SAlexander Shiyan #include <linux/regmap.h>
24ab4382d2SGreg Kroah-Hartman 
25bc000245SAlexander Shiyan #include <linux/mfd/syscon.h>
26bc000245SAlexander Shiyan #include <linux/mfd/syscon/clps711x.h>
27ab4382d2SGreg Kroah-Hartman 
2862b0a1b3SAlexander Shiyan #include "serial_mctrl_gpio.h"
2962b0a1b3SAlexander Shiyan 
30bc000245SAlexander Shiyan #define UART_CLPS711X_DEVNAME	"ttyCL"
31117d5d42SAlexander Shiyan #define UART_CLPS711X_NR	2
32117d5d42SAlexander Shiyan #define UART_CLPS711X_MAJOR	204
33117d5d42SAlexander Shiyan #define UART_CLPS711X_MINOR	40
3495113728SAlexander Shiyan 
35bc000245SAlexander Shiyan #define UARTDR_OFFSET		(0x00)
36bc000245SAlexander Shiyan #define UBRLCR_OFFSET		(0x40)
37bc000245SAlexander Shiyan 
38bc000245SAlexander Shiyan #define UARTDR_FRMERR		(1 << 8)
39bc000245SAlexander Shiyan #define UARTDR_PARERR		(1 << 9)
40bc000245SAlexander Shiyan #define UARTDR_OVERR		(1 << 10)
41bc000245SAlexander Shiyan 
42bc000245SAlexander Shiyan #define UBRLCR_BAUD_MASK	((1 << 12) - 1)
43bc000245SAlexander Shiyan #define UBRLCR_BREAK		(1 << 12)
44bc000245SAlexander Shiyan #define UBRLCR_PRTEN		(1 << 13)
45bc000245SAlexander Shiyan #define UBRLCR_EVENPRT		(1 << 14)
46bc000245SAlexander Shiyan #define UBRLCR_XSTOP		(1 << 15)
47bc000245SAlexander Shiyan #define UBRLCR_FIFOEN		(1 << 16)
48bc000245SAlexander Shiyan #define UBRLCR_WRDLEN5		(0 << 17)
49bc000245SAlexander Shiyan #define UBRLCR_WRDLEN6		(1 << 17)
50bc000245SAlexander Shiyan #define UBRLCR_WRDLEN7		(2 << 17)
51bc000245SAlexander Shiyan #define UBRLCR_WRDLEN8		(3 << 17)
52bc000245SAlexander Shiyan #define UBRLCR_WRDLEN_MASK	(3 << 17)
53ab4382d2SGreg Kroah-Hartman 
54117d5d42SAlexander Shiyan struct clps711x_port {
55bc000245SAlexander Shiyan 	struct uart_port	port;
56bc000245SAlexander Shiyan 	unsigned int		tx_enabled;
57bc000245SAlexander Shiyan 	int			rx_irq;
58bc000245SAlexander Shiyan 	struct regmap		*syscon;
5962b0a1b3SAlexander Shiyan 	struct mctrl_gpios	*gpios;
60bc000245SAlexander Shiyan };
61bc000245SAlexander Shiyan 
62bc000245SAlexander Shiyan static struct uart_driver clps711x_uart = {
63bc000245SAlexander Shiyan 	.owner		= THIS_MODULE,
64bc000245SAlexander Shiyan 	.driver_name	= UART_CLPS711X_DEVNAME,
65bc000245SAlexander Shiyan 	.dev_name	= UART_CLPS711X_DEVNAME,
66bc000245SAlexander Shiyan 	.major		= UART_CLPS711X_MAJOR,
67bc000245SAlexander Shiyan 	.minor		= UART_CLPS711X_MINOR,
68bc000245SAlexander Shiyan 	.nr		= UART_CLPS711X_NR,
69117d5d42SAlexander Shiyan };
70117d5d42SAlexander Shiyan 
uart_clps711x_stop_tx(struct uart_port * port)71a1c25f2bSAlexander Shiyan static void uart_clps711x_stop_tx(struct uart_port *port)
72ab4382d2SGreg Kroah-Hartman {
733c7e9eb1SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
743c7e9eb1SAlexander Shiyan 
75bc000245SAlexander Shiyan 	if (s->tx_enabled) {
76bc000245SAlexander Shiyan 		disable_irq(port->irq);
77bc000245SAlexander Shiyan 		s->tx_enabled = 0;
78ab4382d2SGreg Kroah-Hartman 	}
79ab4382d2SGreg Kroah-Hartman }
80ab4382d2SGreg Kroah-Hartman 
uart_clps711x_start_tx(struct uart_port * port)81a1c25f2bSAlexander Shiyan static void uart_clps711x_start_tx(struct uart_port *port)
82ab4382d2SGreg Kroah-Hartman {
833c7e9eb1SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
843c7e9eb1SAlexander Shiyan 
85bc000245SAlexander Shiyan 	if (!s->tx_enabled) {
86bc000245SAlexander Shiyan 		s->tx_enabled = 1;
87bc000245SAlexander Shiyan 		enable_irq(port->irq);
88ab4382d2SGreg Kroah-Hartman 	}
89ab4382d2SGreg Kroah-Hartman }
90ab4382d2SGreg Kroah-Hartman 
uart_clps711x_int_rx(int irq,void * dev_id)91135cc790SAlexander Shiyan static irqreturn_t uart_clps711x_int_rx(int irq, void *dev_id)
92ab4382d2SGreg Kroah-Hartman {
93ab4382d2SGreg Kroah-Hartman 	struct uart_port *port = dev_id;
94bc000245SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
95fd2b55f8SJiri Slaby 	unsigned int status;
96bc000245SAlexander Shiyan 	u16 ch;
97fd2b55f8SJiri Slaby 	u8 flg;
98ab4382d2SGreg Kroah-Hartman 
99f27de95cSAlexander Shiyan 	for (;;) {
100093a9e2aSAlexander Shiyan 		u32 sysflg = 0;
101093a9e2aSAlexander Shiyan 
102bc000245SAlexander Shiyan 		regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);
103bc000245SAlexander Shiyan 		if (sysflg & SYSFLG_URXFE)
104f27de95cSAlexander Shiyan 			break;
105f27de95cSAlexander Shiyan 
106093a9e2aSAlexander Shiyan 		ch = readw(port->membase + UARTDR_OFFSET);
107f27de95cSAlexander Shiyan 		status = ch & (UARTDR_FRMERR | UARTDR_PARERR | UARTDR_OVERR);
108f27de95cSAlexander Shiyan 		ch &= 0xff;
109ab4382d2SGreg Kroah-Hartman 
110ab4382d2SGreg Kroah-Hartman 		port->icount.rx++;
111ab4382d2SGreg Kroah-Hartman 		flg = TTY_NORMAL;
112ab4382d2SGreg Kroah-Hartman 
113f27de95cSAlexander Shiyan 		if (unlikely(status)) {
114f27de95cSAlexander Shiyan 			if (status & UARTDR_PARERR)
115ab4382d2SGreg Kroah-Hartman 				port->icount.parity++;
116f27de95cSAlexander Shiyan 			else if (status & UARTDR_FRMERR)
117ab4382d2SGreg Kroah-Hartman 				port->icount.frame++;
118f27de95cSAlexander Shiyan 			else if (status & UARTDR_OVERR)
119ab4382d2SGreg Kroah-Hartman 				port->icount.overrun++;
120ab4382d2SGreg Kroah-Hartman 
121f27de95cSAlexander Shiyan 			status &= port->read_status_mask;
122ab4382d2SGreg Kroah-Hartman 
123f27de95cSAlexander Shiyan 			if (status & UARTDR_PARERR)
124ab4382d2SGreg Kroah-Hartman 				flg = TTY_PARITY;
125f27de95cSAlexander Shiyan 			else if (status & UARTDR_FRMERR)
126ab4382d2SGreg Kroah-Hartman 				flg = TTY_FRAME;
127f27de95cSAlexander Shiyan 			else if (status & UARTDR_OVERR)
128f27de95cSAlexander Shiyan 				flg = TTY_OVERRUN;
129ab4382d2SGreg Kroah-Hartman 		}
130ab4382d2SGreg Kroah-Hartman 
131ab4382d2SGreg Kroah-Hartman 		if (uart_handle_sysrq_char(port, ch))
132f27de95cSAlexander Shiyan 			continue;
133ab4382d2SGreg Kroah-Hartman 
134f27de95cSAlexander Shiyan 		if (status & port->ignore_status_mask)
135f27de95cSAlexander Shiyan 			continue;
136ab4382d2SGreg Kroah-Hartman 
137f27de95cSAlexander Shiyan 		uart_insert_char(port, status, UARTDR_OVERR, ch, flg);
138ab4382d2SGreg Kroah-Hartman 	}
139f27de95cSAlexander Shiyan 
1402e124b4aSJiri Slaby 	tty_flip_buffer_push(&port->state->port);
141f27de95cSAlexander Shiyan 
142ab4382d2SGreg Kroah-Hartman 	return IRQ_HANDLED;
143ab4382d2SGreg Kroah-Hartman }
144ab4382d2SGreg Kroah-Hartman 
uart_clps711x_int_tx(int irq,void * dev_id)145135cc790SAlexander Shiyan static irqreturn_t uart_clps711x_int_tx(int irq, void *dev_id)
146ab4382d2SGreg Kroah-Hartman {
147ab4382d2SGreg Kroah-Hartman 	struct uart_port *port = dev_id;
1483c7e9eb1SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
149ab4382d2SGreg Kroah-Hartman 	struct circ_buf *xmit = &port->state->xmit;
150ab4382d2SGreg Kroah-Hartman 
151ab4382d2SGreg Kroah-Hartman 	if (port->x_char) {
152093a9e2aSAlexander Shiyan 		writew(port->x_char, port->membase + UARTDR_OFFSET);
153ab4382d2SGreg Kroah-Hartman 		port->icount.tx++;
154ab4382d2SGreg Kroah-Hartman 		port->x_char = 0;
155ab4382d2SGreg Kroah-Hartman 		return IRQ_HANDLED;
156ab4382d2SGreg Kroah-Hartman 	}
1577a6fbc9aSAlexander Shiyan 
1583c7e9eb1SAlexander Shiyan 	if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
159bc000245SAlexander Shiyan 		if (s->tx_enabled) {
160bc000245SAlexander Shiyan 			disable_irq_nosync(port->irq);
161bc000245SAlexander Shiyan 			s->tx_enabled = 0;
162bc000245SAlexander Shiyan 		}
1633c7e9eb1SAlexander Shiyan 		return IRQ_HANDLED;
1643c7e9eb1SAlexander Shiyan 	}
165ab4382d2SGreg Kroah-Hartman 
166cf03a884SAlexander Shiyan 	while (!uart_circ_empty(xmit)) {
167093a9e2aSAlexander Shiyan 		u32 sysflg = 0;
168093a9e2aSAlexander Shiyan 
169093a9e2aSAlexander Shiyan 		writew(xmit->buf[xmit->tail], port->membase + UARTDR_OFFSET);
1704146765cSIlpo Järvinen 		uart_xmit_advance(port, 1);
171bc000245SAlexander Shiyan 
172bc000245SAlexander Shiyan 		regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);
173bc000245SAlexander Shiyan 		if (sysflg & SYSFLG_UTXFF)
174ab4382d2SGreg Kroah-Hartman 			break;
175cf03a884SAlexander Shiyan 	}
176ab4382d2SGreg Kroah-Hartman 
177ab4382d2SGreg Kroah-Hartman 	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
178ab4382d2SGreg Kroah-Hartman 		uart_write_wakeup(port);
179ab4382d2SGreg Kroah-Hartman 
180ab4382d2SGreg Kroah-Hartman 	return IRQ_HANDLED;
181ab4382d2SGreg Kroah-Hartman }
182ab4382d2SGreg Kroah-Hartman 
uart_clps711x_tx_empty(struct uart_port * port)183a1c25f2bSAlexander Shiyan static unsigned int uart_clps711x_tx_empty(struct uart_port *port)
184ab4382d2SGreg Kroah-Hartman {
185bc000245SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
186093a9e2aSAlexander Shiyan 	u32 sysflg = 0;
187bc000245SAlexander Shiyan 
188bc000245SAlexander Shiyan 	regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);
189bc000245SAlexander Shiyan 
190bc000245SAlexander Shiyan 	return (sysflg & SYSFLG_UBUSY) ? 0 : TIOCSER_TEMT;
191ab4382d2SGreg Kroah-Hartman }
192ab4382d2SGreg Kroah-Hartman 
uart_clps711x_get_mctrl(struct uart_port * port)193a1c25f2bSAlexander Shiyan static unsigned int uart_clps711x_get_mctrl(struct uart_port *port)
194ab4382d2SGreg Kroah-Hartman {
19562b0a1b3SAlexander Shiyan 	unsigned int result = TIOCM_DSR | TIOCM_CTS | TIOCM_CAR;
196bc000245SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
197ab4382d2SGreg Kroah-Hartman 
19862b0a1b3SAlexander Shiyan 	return mctrl_gpio_get(s->gpios, &result);
199ab4382d2SGreg Kroah-Hartman }
200ab4382d2SGreg Kroah-Hartman 
uart_clps711x_set_mctrl(struct uart_port * port,unsigned int mctrl)201a1c25f2bSAlexander Shiyan static void uart_clps711x_set_mctrl(struct uart_port *port, unsigned int mctrl)
202ab4382d2SGreg Kroah-Hartman {
20362b0a1b3SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
20462b0a1b3SAlexander Shiyan 
20562b0a1b3SAlexander Shiyan 	mctrl_gpio_set(s->gpios, mctrl);
206ab4382d2SGreg Kroah-Hartman }
207ab4382d2SGreg Kroah-Hartman 
uart_clps711x_break_ctl(struct uart_port * port,int break_state)208a1c25f2bSAlexander Shiyan static void uart_clps711x_break_ctl(struct uart_port *port, int break_state)
209ab4382d2SGreg Kroah-Hartman {
210ab4382d2SGreg Kroah-Hartman 	unsigned int ubrlcr;
211ab4382d2SGreg Kroah-Hartman 
212093a9e2aSAlexander Shiyan 	ubrlcr = readl(port->membase + UBRLCR_OFFSET);
213ec335526SAlexander Shiyan 	if (break_state)
214ab4382d2SGreg Kroah-Hartman 		ubrlcr |= UBRLCR_BREAK;
215ab4382d2SGreg Kroah-Hartman 	else
216ab4382d2SGreg Kroah-Hartman 		ubrlcr &= ~UBRLCR_BREAK;
217093a9e2aSAlexander Shiyan 	writel(ubrlcr, port->membase + UBRLCR_OFFSET);
218ab4382d2SGreg Kroah-Hartman }
219ab4382d2SGreg Kroah-Hartman 
uart_clps711x_set_ldisc(struct uart_port * port,struct ktermios * termios)220732a84a0SPeter Hurley static void uart_clps711x_set_ldisc(struct uart_port *port,
221732a84a0SPeter Hurley 				    struct ktermios *termios)
22271b9e8c6SAlexander Shiyan {
22371b9e8c6SAlexander Shiyan 	if (!port->line) {
22471b9e8c6SAlexander Shiyan 		struct clps711x_port *s = dev_get_drvdata(port->dev);
22571b9e8c6SAlexander Shiyan 
22671b9e8c6SAlexander Shiyan 		regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON1_SIREN,
227732a84a0SPeter Hurley 				   (termios->c_line == N_IRDA) ? SYSCON1_SIREN : 0);
22871b9e8c6SAlexander Shiyan 	}
22971b9e8c6SAlexander Shiyan }
23071b9e8c6SAlexander Shiyan 
uart_clps711x_startup(struct uart_port * port)231a1c25f2bSAlexander Shiyan static int uart_clps711x_startup(struct uart_port *port)
232ab4382d2SGreg Kroah-Hartman {
2333c7e9eb1SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
234ab4382d2SGreg Kroah-Hartman 
235f52ede2aSAlexander Shiyan 	/* Disable break */
236093a9e2aSAlexander Shiyan 	writel(readl(port->membase + UBRLCR_OFFSET) & ~UBRLCR_BREAK,
237093a9e2aSAlexander Shiyan 	       port->membase + UBRLCR_OFFSET);
238f52ede2aSAlexander Shiyan 
239f52ede2aSAlexander Shiyan 	/* Enable the port */
240bc000245SAlexander Shiyan 	return regmap_update_bits(s->syscon, SYSCON_OFFSET,
241bc000245SAlexander Shiyan 				  SYSCON_UARTEN, SYSCON_UARTEN);
242ab4382d2SGreg Kroah-Hartman }
243ab4382d2SGreg Kroah-Hartman 
uart_clps711x_shutdown(struct uart_port * port)244a1c25f2bSAlexander Shiyan static void uart_clps711x_shutdown(struct uart_port *port)
245ab4382d2SGreg Kroah-Hartman {
246bc000245SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
247ab4382d2SGreg Kroah-Hartman 
248f52ede2aSAlexander Shiyan 	/* Disable the port */
249bc000245SAlexander Shiyan 	regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON_UARTEN, 0);
250ab4382d2SGreg Kroah-Hartman }
251ab4382d2SGreg Kroah-Hartman 
uart_clps711x_set_termios(struct uart_port * port,struct ktermios * termios,const struct ktermios * old)252a1c25f2bSAlexander Shiyan static void uart_clps711x_set_termios(struct uart_port *port,
253a1c25f2bSAlexander Shiyan 				      struct ktermios *termios,
254bec5b814SIlpo Järvinen 				      const struct ktermios *old)
255ab4382d2SGreg Kroah-Hartman {
256bc000245SAlexander Shiyan 	u32 ubrlcr;
257bc000245SAlexander Shiyan 	unsigned int baud, quot;
258ab4382d2SGreg Kroah-Hartman 
2597ae75e94SAlexander Shiyan 	/* Mask termios capabilities we don't support */
2607ae75e94SAlexander Shiyan 	termios->c_cflag &= ~CMSPAR;
2617ae75e94SAlexander Shiyan 	termios->c_iflag &= ~(BRKINT | IGNBRK);
262ab4382d2SGreg Kroah-Hartman 
263c08f0153SAlexander Shiyan 	/* Ask the core to calculate the divisor for us */
264c08f0153SAlexander Shiyan 	baud = uart_get_baud_rate(port, termios, old, port->uartclk / 4096,
265c08f0153SAlexander Shiyan 						      port->uartclk / 16);
266ab4382d2SGreg Kroah-Hartman 	quot = uart_get_divisor(port, baud);
267ab4382d2SGreg Kroah-Hartman 
268ab4382d2SGreg Kroah-Hartman 	switch (termios->c_cflag & CSIZE) {
269ab4382d2SGreg Kroah-Hartman 	case CS5:
270ab4382d2SGreg Kroah-Hartman 		ubrlcr = UBRLCR_WRDLEN5;
271ab4382d2SGreg Kroah-Hartman 		break;
272ab4382d2SGreg Kroah-Hartman 	case CS6:
273ab4382d2SGreg Kroah-Hartman 		ubrlcr = UBRLCR_WRDLEN6;
274ab4382d2SGreg Kroah-Hartman 		break;
275ab4382d2SGreg Kroah-Hartman 	case CS7:
276ab4382d2SGreg Kroah-Hartman 		ubrlcr = UBRLCR_WRDLEN7;
277ab4382d2SGreg Kroah-Hartman 		break;
278a1c25f2bSAlexander Shiyan 	case CS8:
279a1c25f2bSAlexander Shiyan 	default:
280ab4382d2SGreg Kroah-Hartman 		ubrlcr = UBRLCR_WRDLEN8;
281ab4382d2SGreg Kroah-Hartman 		break;
282ab4382d2SGreg Kroah-Hartman 	}
2837ae75e94SAlexander Shiyan 
284ab4382d2SGreg Kroah-Hartman 	if (termios->c_cflag & CSTOPB)
285ab4382d2SGreg Kroah-Hartman 		ubrlcr |= UBRLCR_XSTOP;
2867ae75e94SAlexander Shiyan 
287ab4382d2SGreg Kroah-Hartman 	if (termios->c_cflag & PARENB) {
288ab4382d2SGreg Kroah-Hartman 		ubrlcr |= UBRLCR_PRTEN;
289ab4382d2SGreg Kroah-Hartman 		if (!(termios->c_cflag & PARODD))
290ab4382d2SGreg Kroah-Hartman 			ubrlcr |= UBRLCR_EVENPRT;
291ab4382d2SGreg Kroah-Hartman 	}
292cf03a884SAlexander Shiyan 
293cf03a884SAlexander Shiyan 	/* Enable FIFO */
294ab4382d2SGreg Kroah-Hartman 	ubrlcr |= UBRLCR_FIFOEN;
295ab4382d2SGreg Kroah-Hartman 
2967ae75e94SAlexander Shiyan 	/* Set read status mask */
297ab4382d2SGreg Kroah-Hartman 	port->read_status_mask = UARTDR_OVERR;
298ab4382d2SGreg Kroah-Hartman 	if (termios->c_iflag & INPCK)
299ab4382d2SGreg Kroah-Hartman 		port->read_status_mask |= UARTDR_PARERR | UARTDR_FRMERR;
300ab4382d2SGreg Kroah-Hartman 
3017ae75e94SAlexander Shiyan 	/* Set status ignore mask */
302ab4382d2SGreg Kroah-Hartman 	port->ignore_status_mask = 0;
3037ae75e94SAlexander Shiyan 	if (!(termios->c_cflag & CREAD))
3047ae75e94SAlexander Shiyan 		port->ignore_status_mask |= UARTDR_OVERR | UARTDR_PARERR |
3057ae75e94SAlexander Shiyan 					    UARTDR_FRMERR;
306ab4382d2SGreg Kroah-Hartman 
3077ae75e94SAlexander Shiyan 	uart_update_timeout(port, termios->c_cflag, baud);
308ab4382d2SGreg Kroah-Hartman 
309093a9e2aSAlexander Shiyan 	writel(ubrlcr | (quot - 1), port->membase + UBRLCR_OFFSET);
310ab4382d2SGreg Kroah-Hartman }
311ab4382d2SGreg Kroah-Hartman 
uart_clps711x_type(struct uart_port * port)312a1c25f2bSAlexander Shiyan static const char *uart_clps711x_type(struct uart_port *port)
313ab4382d2SGreg Kroah-Hartman {
314a1c25f2bSAlexander Shiyan 	return (port->type == PORT_CLPS711X) ? "CLPS711X" : NULL;
315ab4382d2SGreg Kroah-Hartman }
316ab4382d2SGreg Kroah-Hartman 
uart_clps711x_config_port(struct uart_port * port,int flags)317a1c25f2bSAlexander Shiyan static void uart_clps711x_config_port(struct uart_port *port, int flags)
318ab4382d2SGreg Kroah-Hartman {
319ab4382d2SGreg Kroah-Hartman 	if (flags & UART_CONFIG_TYPE)
320ab4382d2SGreg Kroah-Hartman 		port->type = PORT_CLPS711X;
321ab4382d2SGreg Kroah-Hartman }
322ab4382d2SGreg Kroah-Hartman 
uart_clps711x_nop_void(struct uart_port * port)323bc000245SAlexander Shiyan static void uart_clps711x_nop_void(struct uart_port *port)
324ab4382d2SGreg Kroah-Hartman {
325ab4382d2SGreg Kroah-Hartman }
326ab4382d2SGreg Kroah-Hartman 
uart_clps711x_nop_int(struct uart_port * port)327bc000245SAlexander Shiyan static int uart_clps711x_nop_int(struct uart_port *port)
328ab4382d2SGreg Kroah-Hartman {
329ab4382d2SGreg Kroah-Hartman 	return 0;
330ab4382d2SGreg Kroah-Hartman }
331ab4382d2SGreg Kroah-Hartman 
332a1c25f2bSAlexander Shiyan static const struct uart_ops uart_clps711x_ops = {
333a1c25f2bSAlexander Shiyan 	.tx_empty	= uart_clps711x_tx_empty,
334a1c25f2bSAlexander Shiyan 	.set_mctrl	= uart_clps711x_set_mctrl,
335a1c25f2bSAlexander Shiyan 	.get_mctrl	= uart_clps711x_get_mctrl,
336a1c25f2bSAlexander Shiyan 	.stop_tx	= uart_clps711x_stop_tx,
337a1c25f2bSAlexander Shiyan 	.start_tx	= uart_clps711x_start_tx,
338bc000245SAlexander Shiyan 	.stop_rx	= uart_clps711x_nop_void,
339a1c25f2bSAlexander Shiyan 	.break_ctl	= uart_clps711x_break_ctl,
34071b9e8c6SAlexander Shiyan 	.set_ldisc	= uart_clps711x_set_ldisc,
341a1c25f2bSAlexander Shiyan 	.startup	= uart_clps711x_startup,
342a1c25f2bSAlexander Shiyan 	.shutdown	= uart_clps711x_shutdown,
343a1c25f2bSAlexander Shiyan 	.set_termios	= uart_clps711x_set_termios,
344a1c25f2bSAlexander Shiyan 	.type		= uart_clps711x_type,
345a1c25f2bSAlexander Shiyan 	.config_port	= uart_clps711x_config_port,
346bc000245SAlexander Shiyan 	.release_port	= uart_clps711x_nop_void,
347bc000245SAlexander Shiyan 	.request_port	= uart_clps711x_nop_int,
348ab4382d2SGreg Kroah-Hartman };
349ab4382d2SGreg Kroah-Hartman 
350ab4382d2SGreg Kroah-Hartman #ifdef CONFIG_SERIAL_CLPS711X_CONSOLE
uart_clps711x_console_putchar(struct uart_port * port,unsigned char ch)3513f8bab17SJiri Slaby static void uart_clps711x_console_putchar(struct uart_port *port, unsigned char ch)
352ab4382d2SGreg Kroah-Hartman {
353bc000245SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
354093a9e2aSAlexander Shiyan 	u32 sysflg = 0;
355117d5d42SAlexander Shiyan 
3562f310b8eSAlexander Shiyan 	/* Wait for FIFO is not full */
3572f310b8eSAlexander Shiyan 	do {
358bc000245SAlexander Shiyan 		regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);
3592f310b8eSAlexander Shiyan 	} while (sysflg & SYSFLG_UTXFF);
360bc000245SAlexander Shiyan 
361093a9e2aSAlexander Shiyan 	writew(ch, port->membase + UARTDR_OFFSET);
362ab4382d2SGreg Kroah-Hartman }
363ab4382d2SGreg Kroah-Hartman 
uart_clps711x_console_write(struct console * co,const char * c,unsigned n)364117d5d42SAlexander Shiyan static void uart_clps711x_console_write(struct console *co, const char *c,
365117d5d42SAlexander Shiyan 					unsigned n)
366ab4382d2SGreg Kroah-Hartman {
367bc000245SAlexander Shiyan 	struct uart_port *port = clps711x_uart.state[co->index].uart_port;
368bc000245SAlexander Shiyan 	struct clps711x_port *s = dev_get_drvdata(port->dev);
3692f310b8eSAlexander Shiyan 	u32 sysflg = 0;
370ab4382d2SGreg Kroah-Hartman 
371117d5d42SAlexander Shiyan 	uart_console_write(port, c, n, uart_clps711x_console_putchar);
372ab4382d2SGreg Kroah-Hartman 
373117d5d42SAlexander Shiyan 	/* Wait for transmitter to become empty */
3742f310b8eSAlexander Shiyan 	do {
375bc000245SAlexander Shiyan 		regmap_read(s->syscon, SYSFLG_OFFSET, &sysflg);
3762f310b8eSAlexander Shiyan 	} while (sysflg & SYSFLG_UBUSY);
377ab4382d2SGreg Kroah-Hartman }
378ab4382d2SGreg Kroah-Hartman 
uart_clps711x_console_setup(struct console * co,char * options)379117d5d42SAlexander Shiyan static int uart_clps711x_console_setup(struct console *co, char *options)
380ab4382d2SGreg Kroah-Hartman {
381117d5d42SAlexander Shiyan 	int baud = 38400, bits = 8, parity = 'n', flow = 'n';
382bc000245SAlexander Shiyan 	int ret, index = co->index;
383bc000245SAlexander Shiyan 	struct clps711x_port *s;
384bc000245SAlexander Shiyan 	struct uart_port *port;
385bc000245SAlexander Shiyan 	unsigned int quot;
386093a9e2aSAlexander Shiyan 	u32 ubrlcr;
387ab4382d2SGreg Kroah-Hartman 
388bc000245SAlexander Shiyan 	if (index < 0 || index >= UART_CLPS711X_NR)
389bc000245SAlexander Shiyan 		return -EINVAL;
390bc000245SAlexander Shiyan 
391bc000245SAlexander Shiyan 	port = clps711x_uart.state[index].uart_port;
392bc000245SAlexander Shiyan 	if (!port)
393bc000245SAlexander Shiyan 		return -ENODEV;
394bc000245SAlexander Shiyan 
395bc000245SAlexander Shiyan 	s = dev_get_drvdata(port->dev);
396bc000245SAlexander Shiyan 
397bc000245SAlexander Shiyan 	if (!options) {
398093a9e2aSAlexander Shiyan 		u32 syscon = 0;
399093a9e2aSAlexander Shiyan 
400bc000245SAlexander Shiyan 		regmap_read(s->syscon, SYSCON_OFFSET, &syscon);
401bc000245SAlexander Shiyan 		if (syscon & SYSCON_UARTEN) {
402093a9e2aSAlexander Shiyan 			ubrlcr = readl(port->membase + UBRLCR_OFFSET);
403bc000245SAlexander Shiyan 
404bc000245SAlexander Shiyan 			if (ubrlcr & UBRLCR_PRTEN) {
405bc000245SAlexander Shiyan 				if (ubrlcr & UBRLCR_EVENPRT)
406bc000245SAlexander Shiyan 					parity = 'e';
407ab4382d2SGreg Kroah-Hartman 				else
408bc000245SAlexander Shiyan 					parity = 'o';
409ab4382d2SGreg Kroah-Hartman 			}
410bc000245SAlexander Shiyan 
411bc000245SAlexander Shiyan 			if ((ubrlcr & UBRLCR_WRDLEN_MASK) == UBRLCR_WRDLEN7)
412bc000245SAlexander Shiyan 				bits = 7;
413bc000245SAlexander Shiyan 
414bc000245SAlexander Shiyan 			quot = ubrlcr & UBRLCR_BAUD_MASK;
415bc000245SAlexander Shiyan 			baud = port->uartclk / (16 * (quot + 1));
416bc000245SAlexander Shiyan 		}
417bc000245SAlexander Shiyan 	} else
418bc000245SAlexander Shiyan 		uart_parse_options(options, &baud, &parity, &bits, &flow);
419bc000245SAlexander Shiyan 
420bc000245SAlexander Shiyan 	ret = uart_set_options(port, co, baud, parity, bits, flow);
421bc000245SAlexander Shiyan 	if (ret)
422bc000245SAlexander Shiyan 		return ret;
423bc000245SAlexander Shiyan 
424bc000245SAlexander Shiyan 	return regmap_update_bits(s->syscon, SYSCON_OFFSET,
425bc000245SAlexander Shiyan 				  SYSCON_UARTEN, SYSCON_UARTEN);
426bc000245SAlexander Shiyan }
427bc000245SAlexander Shiyan 
428bc000245SAlexander Shiyan static struct console clps711x_console = {
429bc000245SAlexander Shiyan 	.name	= UART_CLPS711X_DEVNAME,
430bc000245SAlexander Shiyan 	.device	= uart_console_device,
431bc000245SAlexander Shiyan 	.write	= uart_clps711x_console_write,
432bc000245SAlexander Shiyan 	.setup	= uart_clps711x_console_setup,
433bc000245SAlexander Shiyan 	.flags	= CON_PRINTBUFFER,
434bc000245SAlexander Shiyan 	.index	= -1,
435bc000245SAlexander Shiyan };
436ab4382d2SGreg Kroah-Hartman #endif
437ab4382d2SGreg Kroah-Hartman 
uart_clps711x_probe(struct platform_device * pdev)4389671f099SBill Pemberton static int uart_clps711x_probe(struct platform_device *pdev)
439ab4382d2SGreg Kroah-Hartman {
440bc000245SAlexander Shiyan 	struct device_node *np = pdev->dev.of_node;
441117d5d42SAlexander Shiyan 	struct clps711x_port *s;
442bc000245SAlexander Shiyan 	struct resource *res;
443bc000245SAlexander Shiyan 	struct clk *uart_clk;
444db4a6cbfSAlexander Shiyan 	int irq, ret;
445bc000245SAlexander Shiyan 
446bc000245SAlexander Shiyan 	s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
447bc000245SAlexander Shiyan 	if (!s)
448117d5d42SAlexander Shiyan 		return -ENOMEM;
449bc000245SAlexander Shiyan 
450bc000245SAlexander Shiyan 	uart_clk = devm_clk_get(&pdev->dev, NULL);
451bc000245SAlexander Shiyan 	if (IS_ERR(uart_clk))
452bc000245SAlexander Shiyan 		return PTR_ERR(uart_clk);
453bc000245SAlexander Shiyan 
454*0bb60bdaSYangtao Li 	s->port.membase = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
455bc000245SAlexander Shiyan 	if (IS_ERR(s->port.membase))
456bc000245SAlexander Shiyan 		return PTR_ERR(s->port.membase);
457bc000245SAlexander Shiyan 
4588f5405c9SGuenter Roeck 	irq = platform_get_irq(pdev, 0);
4598f5405c9SGuenter Roeck 	if (irq < 0)
4608f5405c9SGuenter Roeck 		return irq;
4618f5405c9SGuenter Roeck 	s->port.irq = irq;
462bc000245SAlexander Shiyan 
463bc000245SAlexander Shiyan 	s->rx_irq = platform_get_irq(pdev, 1);
4648f5405c9SGuenter Roeck 	if (s->rx_irq < 0)
465bc000245SAlexander Shiyan 		return s->rx_irq;
466bc000245SAlexander Shiyan 
467bc000245SAlexander Shiyan 	s->syscon = syscon_regmap_lookup_by_phandle(np, "syscon");
468bc000245SAlexander Shiyan 	if (IS_ERR(s->syscon))
469bc000245SAlexander Shiyan 		return PTR_ERR(s->syscon);
470bc000245SAlexander Shiyan 
471db4a6cbfSAlexander Shiyan 	s->port.line		= of_alias_get_id(np, "serial");
472bc000245SAlexander Shiyan 	s->port.dev		= &pdev->dev;
473bc000245SAlexander Shiyan 	s->port.iotype		= UPIO_MEM32;
474bc000245SAlexander Shiyan 	s->port.mapbase		= res->start;
475bc000245SAlexander Shiyan 	s->port.type		= PORT_CLPS711X;
476bc000245SAlexander Shiyan 	s->port.fifosize	= 16;
47776f82db9SDmitry Safonov 	s->port.has_sysrq	= IS_ENABLED(CONFIG_SERIAL_CLPS711X_CONSOLE);
478bc000245SAlexander Shiyan 	s->port.flags		= UPF_SKIP_TEST | UPF_FIXED_TYPE;
479bc000245SAlexander Shiyan 	s->port.uartclk		= clk_get_rate(uart_clk);
480bc000245SAlexander Shiyan 	s->port.ops		= &uart_clps711x_ops;
481bc000245SAlexander Shiyan 
482117d5d42SAlexander Shiyan 	platform_set_drvdata(pdev, s);
483ab4382d2SGreg Kroah-Hartman 
4847d8c70d8SUwe Kleine-König 	s->gpios = mctrl_gpio_init_noauto(&pdev->dev, 0);
485f059a455SUwe Kleine-König 	if (IS_ERR(s->gpios))
486f059a455SUwe Kleine-König 	    return PTR_ERR(s->gpios);
48762b0a1b3SAlexander Shiyan 
488bc000245SAlexander Shiyan 	ret = uart_add_one_port(&clps711x_uart, &s->port);
489bc000245SAlexander Shiyan 	if (ret)
490bc000245SAlexander Shiyan 		return ret;
491c08f0153SAlexander Shiyan 
492bc000245SAlexander Shiyan 	/* Disable port */
493bc000245SAlexander Shiyan 	if (!uart_console(&s->port))
494bc000245SAlexander Shiyan 		regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON_UARTEN, 0);
495bc000245SAlexander Shiyan 
496bc000245SAlexander Shiyan 	s->tx_enabled = 1;
497bc000245SAlexander Shiyan 
498bc000245SAlexander Shiyan 	ret = devm_request_irq(&pdev->dev, s->port.irq, uart_clps711x_int_tx, 0,
499bc000245SAlexander Shiyan 			       dev_name(&pdev->dev), &s->port);
500117d5d42SAlexander Shiyan 	if (ret) {
501bc000245SAlexander Shiyan 		uart_remove_one_port(&clps711x_uart, &s->port);
50243b829b3SJingoo Han 		return ret;
503117d5d42SAlexander Shiyan 	}
504ab4382d2SGreg Kroah-Hartman 
505bc000245SAlexander Shiyan 	ret = devm_request_irq(&pdev->dev, s->rx_irq, uart_clps711x_int_rx, 0,
506bc000245SAlexander Shiyan 			       dev_name(&pdev->dev), &s->port);
507bc000245SAlexander Shiyan 	if (ret)
508bc000245SAlexander Shiyan 		uart_remove_one_port(&clps711x_uart, &s->port);
509ab4382d2SGreg Kroah-Hartman 
510bc000245SAlexander Shiyan 	return ret;
511ab4382d2SGreg Kroah-Hartman }
512ab4382d2SGreg Kroah-Hartman 
uart_clps711x_remove(struct platform_device * pdev)513ae8d8a14SBill Pemberton static int uart_clps711x_remove(struct platform_device *pdev)
514ab4382d2SGreg Kroah-Hartman {
515117d5d42SAlexander Shiyan 	struct clps711x_port *s = platform_get_drvdata(pdev);
516ab4382d2SGreg Kroah-Hartman 
517d5b3d02dSUwe Kleine-König 	uart_remove_one_port(&clps711x_uart, &s->port);
518d5b3d02dSUwe Kleine-König 
519d5b3d02dSUwe Kleine-König 	return 0;
520ab4382d2SGreg Kroah-Hartman }
521ab4382d2SGreg Kroah-Hartman 
522bc000245SAlexander Shiyan static const struct of_device_id __maybe_unused clps711x_uart_dt_ids[] = {
523d305345cSAlexander Shiyan 	{ .compatible = "cirrus,ep7209-uart", },
524bc000245SAlexander Shiyan 	{ }
525bc000245SAlexander Shiyan };
526bc000245SAlexander Shiyan MODULE_DEVICE_TABLE(of, clps711x_uart_dt_ids);
527bc000245SAlexander Shiyan 
528bc000245SAlexander Shiyan static struct platform_driver clps711x_uart_platform = {
52995113728SAlexander Shiyan 	.driver = {
530bc000245SAlexander Shiyan 		.name		= "clps711x-uart",
531bc000245SAlexander Shiyan 		.of_match_table	= of_match_ptr(clps711x_uart_dt_ids),
53295113728SAlexander Shiyan 	},
53395113728SAlexander Shiyan 	.probe	= uart_clps711x_probe,
5342d47b716SBill Pemberton 	.remove	= uart_clps711x_remove,
53595113728SAlexander Shiyan };
53695113728SAlexander Shiyan 
uart_clps711x_init(void)53795113728SAlexander Shiyan static int __init uart_clps711x_init(void)
53895113728SAlexander Shiyan {
539bc000245SAlexander Shiyan 	int ret;
540bc000245SAlexander Shiyan 
541bc000245SAlexander Shiyan #ifdef CONFIG_SERIAL_CLPS711X_CONSOLE
542bc000245SAlexander Shiyan 	clps711x_uart.cons = &clps711x_console;
543bc000245SAlexander Shiyan 	clps711x_console.data = &clps711x_uart;
544bc000245SAlexander Shiyan #endif
545bc000245SAlexander Shiyan 
546bc000245SAlexander Shiyan 	ret = uart_register_driver(&clps711x_uart);
547bc000245SAlexander Shiyan 	if (ret)
548bc000245SAlexander Shiyan 		return ret;
549bc000245SAlexander Shiyan 
550bc000245SAlexander Shiyan 	return platform_driver_register(&clps711x_uart_platform);
55195113728SAlexander Shiyan }
55295113728SAlexander Shiyan module_init(uart_clps711x_init);
55395113728SAlexander Shiyan 
uart_clps711x_exit(void)55495113728SAlexander Shiyan static void __exit uart_clps711x_exit(void)
55595113728SAlexander Shiyan {
556bc000245SAlexander Shiyan 	platform_driver_unregister(&clps711x_uart_platform);
557bc000245SAlexander Shiyan 	uart_unregister_driver(&clps711x_uart);
55895113728SAlexander Shiyan }
55995113728SAlexander Shiyan module_exit(uart_clps711x_exit);
560ab4382d2SGreg Kroah-Hartman 
561ab4382d2SGreg Kroah-Hartman MODULE_AUTHOR("Deep Blue Solutions Ltd");
56295113728SAlexander Shiyan MODULE_DESCRIPTION("CLPS711X serial driver");
563ab4382d2SGreg Kroah-Hartman MODULE_LICENSE("GPL");
564