1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Driver for Conexant Digicolor serial ports (USART) |
4 | * |
5 | * Author: Baruch Siach <baruch@tkos.co.il> |
6 | * |
7 | * Copyright (C) 2014 Paradox Innovation Ltd. |
8 | */ |
9 | |
10 | #include <linux/module.h> |
11 | #include <linux/console.h> |
12 | #include <linux/serial_core.h> |
13 | #include <linux/serial.h> |
14 | #include <linux/clk.h> |
15 | #include <linux/io.h> |
16 | #include <linux/tty.h> |
17 | #include <linux/tty_flip.h> |
18 | #include <linux/of.h> |
19 | #include <linux/platform_device.h> |
20 | #include <linux/workqueue.h> |
21 | |
22 | #define UA_ENABLE 0x00 |
23 | #define UA_ENABLE_ENABLE BIT(0) |
24 | |
25 | #define UA_CONTROL 0x01 |
26 | #define UA_CONTROL_RX_ENABLE BIT(0) |
27 | #define UA_CONTROL_TX_ENABLE BIT(1) |
28 | #define UA_CONTROL_SOFT_RESET BIT(2) |
29 | |
30 | #define UA_STATUS 0x02 |
31 | #define UA_STATUS_PARITY_ERR BIT(0) |
32 | #define UA_STATUS_FRAME_ERR BIT(1) |
33 | #define UA_STATUS_OVERRUN_ERR BIT(2) |
34 | #define UA_STATUS_TX_READY BIT(6) |
35 | |
36 | #define UA_CONFIG 0x03 |
37 | #define UA_CONFIG_CHAR_LEN BIT(0) |
38 | #define UA_CONFIG_STOP_BITS BIT(1) |
39 | #define UA_CONFIG_PARITY BIT(2) |
40 | #define UA_CONFIG_ODD_PARITY BIT(4) |
41 | |
42 | #define UA_EMI_REC 0x04 |
43 | |
44 | #define UA_HBAUD_LO 0x08 |
45 | #define UA_HBAUD_HI 0x09 |
46 | |
47 | #define UA_STATUS_FIFO 0x0a |
48 | #define UA_STATUS_FIFO_RX_EMPTY BIT(2) |
49 | #define UA_STATUS_FIFO_RX_INT_ALMOST BIT(3) |
50 | #define UA_STATUS_FIFO_TX_FULL BIT(4) |
51 | #define UA_STATUS_FIFO_TX_INT_ALMOST BIT(7) |
52 | |
53 | #define UA_CONFIG_FIFO 0x0b |
54 | #define UA_CONFIG_FIFO_RX_THRESH 7 |
55 | #define UA_CONFIG_FIFO_RX_FIFO_MODE BIT(3) |
56 | #define UA_CONFIG_FIFO_TX_FIFO_MODE BIT(7) |
57 | |
58 | #define UA_INTFLAG_CLEAR 0x1c |
59 | #define UA_INTFLAG_SET 0x1d |
60 | #define UA_INT_ENABLE 0x1e |
61 | #define UA_INT_STATUS 0x1f |
62 | |
63 | #define UA_INT_TX BIT(0) |
64 | #define UA_INT_RX BIT(1) |
65 | |
66 | #define DIGICOLOR_USART_NR 3 |
67 | |
68 | /* |
69 | * We use the 16 bytes hardware FIFO to buffer Rx traffic. Rx interrupt is |
70 | * only produced when the FIFO is filled more than a certain configurable |
71 | * threshold. Unfortunately, there is no way to set this threshold below half |
72 | * FIFO. This means that we must periodically poll the FIFO status register to |
73 | * see whether there are waiting Rx bytes. |
74 | */ |
75 | |
76 | struct digicolor_port { |
77 | struct uart_port port; |
78 | struct delayed_work rx_poll_work; |
79 | }; |
80 | |
81 | static struct uart_port *digicolor_ports[DIGICOLOR_USART_NR]; |
82 | |
83 | static bool digicolor_uart_tx_full(struct uart_port *port) |
84 | { |
85 | return !!(readb_relaxed(port->membase + UA_STATUS_FIFO) & |
86 | UA_STATUS_FIFO_TX_FULL); |
87 | } |
88 | |
89 | static bool digicolor_uart_rx_empty(struct uart_port *port) |
90 | { |
91 | return !!(readb_relaxed(port->membase + UA_STATUS_FIFO) & |
92 | UA_STATUS_FIFO_RX_EMPTY); |
93 | } |
94 | |
95 | static void digicolor_uart_stop_tx(struct uart_port *port) |
96 | { |
97 | u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE); |
98 | |
99 | int_enable &= ~UA_INT_TX; |
100 | writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE); |
101 | } |
102 | |
103 | static void digicolor_uart_start_tx(struct uart_port *port) |
104 | { |
105 | u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE); |
106 | |
107 | int_enable |= UA_INT_TX; |
108 | writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE); |
109 | } |
110 | |
111 | static void digicolor_uart_stop_rx(struct uart_port *port) |
112 | { |
113 | u8 int_enable = readb_relaxed(port->membase + UA_INT_ENABLE); |
114 | |
115 | int_enable &= ~UA_INT_RX; |
116 | writeb_relaxed(int_enable, port->membase + UA_INT_ENABLE); |
117 | } |
118 | |
119 | static void digicolor_rx_poll(struct work_struct *work) |
120 | { |
121 | struct digicolor_port *dp = |
122 | container_of(to_delayed_work(work), |
123 | struct digicolor_port, rx_poll_work); |
124 | |
125 | if (!digicolor_uart_rx_empty(port: &dp->port)) |
126 | /* force RX interrupt */ |
127 | writeb_relaxed(UA_INT_RX, dp->port.membase + UA_INTFLAG_SET); |
128 | |
129 | schedule_delayed_work(dwork: &dp->rx_poll_work, delay: msecs_to_jiffies(m: 100)); |
130 | } |
131 | |
132 | static void digicolor_uart_rx(struct uart_port *port) |
133 | { |
134 | unsigned long flags; |
135 | |
136 | uart_port_lock_irqsave(up: port, flags: &flags); |
137 | |
138 | while (1) { |
139 | u8 status, ch, ch_flag; |
140 | |
141 | if (digicolor_uart_rx_empty(port)) |
142 | break; |
143 | |
144 | ch = readb_relaxed(port->membase + UA_EMI_REC); |
145 | status = readb_relaxed(port->membase + UA_STATUS); |
146 | |
147 | port->icount.rx++; |
148 | ch_flag = TTY_NORMAL; |
149 | |
150 | if (status) { |
151 | if (status & UA_STATUS_PARITY_ERR) |
152 | port->icount.parity++; |
153 | else if (status & UA_STATUS_FRAME_ERR) |
154 | port->icount.frame++; |
155 | else if (status & UA_STATUS_OVERRUN_ERR) |
156 | port->icount.overrun++; |
157 | |
158 | status &= port->read_status_mask; |
159 | |
160 | if (status & UA_STATUS_PARITY_ERR) |
161 | ch_flag = TTY_PARITY; |
162 | else if (status & UA_STATUS_FRAME_ERR) |
163 | ch_flag = TTY_FRAME; |
164 | else if (status & UA_STATUS_OVERRUN_ERR) |
165 | ch_flag = TTY_OVERRUN; |
166 | } |
167 | |
168 | if (status & port->ignore_status_mask) |
169 | continue; |
170 | |
171 | uart_insert_char(port, status, UA_STATUS_OVERRUN_ERR, ch, |
172 | flag: ch_flag); |
173 | } |
174 | |
175 | uart_port_unlock_irqrestore(up: port, flags); |
176 | |
177 | tty_flip_buffer_push(port: &port->state->port); |
178 | } |
179 | |
180 | static void digicolor_uart_tx(struct uart_port *port) |
181 | { |
182 | struct circ_buf *xmit = &port->state->xmit; |
183 | unsigned long flags; |
184 | |
185 | if (digicolor_uart_tx_full(port)) |
186 | return; |
187 | |
188 | uart_port_lock_irqsave(up: port, flags: &flags); |
189 | |
190 | if (port->x_char) { |
191 | writeb_relaxed(port->x_char, port->membase + UA_EMI_REC); |
192 | port->icount.tx++; |
193 | port->x_char = 0; |
194 | goto out; |
195 | } |
196 | |
197 | if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { |
198 | digicolor_uart_stop_tx(port); |
199 | goto out; |
200 | } |
201 | |
202 | while (!uart_circ_empty(xmit)) { |
203 | writeb(val: xmit->buf[xmit->tail], addr: port->membase + UA_EMI_REC); |
204 | uart_xmit_advance(up: port, chars: 1); |
205 | |
206 | if (digicolor_uart_tx_full(port)) |
207 | break; |
208 | } |
209 | |
210 | if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) |
211 | uart_write_wakeup(port); |
212 | |
213 | out: |
214 | uart_port_unlock_irqrestore(up: port, flags); |
215 | } |
216 | |
217 | static irqreturn_t digicolor_uart_int(int irq, void *dev_id) |
218 | { |
219 | struct uart_port *port = dev_id; |
220 | u8 int_status = readb_relaxed(port->membase + UA_INT_STATUS); |
221 | |
222 | writeb_relaxed(UA_INT_RX | UA_INT_TX, |
223 | port->membase + UA_INTFLAG_CLEAR); |
224 | |
225 | if (int_status & UA_INT_RX) |
226 | digicolor_uart_rx(port); |
227 | if (int_status & UA_INT_TX) |
228 | digicolor_uart_tx(port); |
229 | |
230 | return IRQ_HANDLED; |
231 | } |
232 | |
233 | static unsigned int digicolor_uart_tx_empty(struct uart_port *port) |
234 | { |
235 | u8 status = readb_relaxed(port->membase + UA_STATUS); |
236 | |
237 | return (status & UA_STATUS_TX_READY) ? TIOCSER_TEMT : 0; |
238 | } |
239 | |
240 | static unsigned int digicolor_uart_get_mctrl(struct uart_port *port) |
241 | { |
242 | return TIOCM_CTS; |
243 | } |
244 | |
245 | static void digicolor_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) |
246 | { |
247 | } |
248 | |
249 | static void digicolor_uart_break_ctl(struct uart_port *port, int state) |
250 | { |
251 | } |
252 | |
253 | static int digicolor_uart_startup(struct uart_port *port) |
254 | { |
255 | struct digicolor_port *dp = |
256 | container_of(port, struct digicolor_port, port); |
257 | |
258 | writeb_relaxed(UA_ENABLE_ENABLE, port->membase + UA_ENABLE); |
259 | writeb_relaxed(UA_CONTROL_SOFT_RESET, port->membase + UA_CONTROL); |
260 | writeb_relaxed(0, port->membase + UA_CONTROL); |
261 | |
262 | writeb_relaxed(UA_CONFIG_FIFO_RX_FIFO_MODE |
263 | | UA_CONFIG_FIFO_TX_FIFO_MODE | UA_CONFIG_FIFO_RX_THRESH, |
264 | port->membase + UA_CONFIG_FIFO); |
265 | writeb_relaxed(UA_STATUS_FIFO_RX_INT_ALMOST, |
266 | port->membase + UA_STATUS_FIFO); |
267 | writeb_relaxed(UA_CONTROL_RX_ENABLE | UA_CONTROL_TX_ENABLE, |
268 | port->membase + UA_CONTROL); |
269 | writeb_relaxed(UA_INT_TX | UA_INT_RX, |
270 | port->membase + UA_INT_ENABLE); |
271 | |
272 | schedule_delayed_work(dwork: &dp->rx_poll_work, delay: msecs_to_jiffies(m: 100)); |
273 | |
274 | return 0; |
275 | } |
276 | |
277 | static void digicolor_uart_shutdown(struct uart_port *port) |
278 | { |
279 | struct digicolor_port *dp = |
280 | container_of(port, struct digicolor_port, port); |
281 | |
282 | writeb_relaxed(0, port->membase + UA_ENABLE); |
283 | cancel_delayed_work_sync(dwork: &dp->rx_poll_work); |
284 | } |
285 | |
286 | static void digicolor_uart_set_termios(struct uart_port *port, |
287 | struct ktermios *termios, |
288 | const struct ktermios *old) |
289 | { |
290 | unsigned int baud, divisor; |
291 | u8 config = 0; |
292 | unsigned long flags; |
293 | |
294 | /* Mask termios capabilities we don't support */ |
295 | termios->c_cflag &= ~CMSPAR; |
296 | termios->c_iflag &= ~(BRKINT | IGNBRK); |
297 | |
298 | /* Limit baud rates so that we don't need the fractional divider */ |
299 | baud = uart_get_baud_rate(port, termios, old, |
300 | min: port->uartclk / (0x10000*16), |
301 | max: port->uartclk / 256); |
302 | divisor = uart_get_divisor(port, baud) - 1; |
303 | |
304 | switch (termios->c_cflag & CSIZE) { |
305 | case CS7: |
306 | break; |
307 | case CS8: |
308 | default: |
309 | config |= UA_CONFIG_CHAR_LEN; |
310 | termios->c_cflag &= ~CSIZE; |
311 | termios->c_cflag |= CS8; |
312 | break; |
313 | } |
314 | |
315 | if (termios->c_cflag & CSTOPB) |
316 | config |= UA_CONFIG_STOP_BITS; |
317 | |
318 | if (termios->c_cflag & PARENB) { |
319 | config |= UA_CONFIG_PARITY; |
320 | if (termios->c_cflag & PARODD) |
321 | config |= UA_CONFIG_ODD_PARITY; |
322 | } |
323 | |
324 | /* Set read status mask */ |
325 | port->read_status_mask = UA_STATUS_OVERRUN_ERR; |
326 | if (termios->c_iflag & INPCK) |
327 | port->read_status_mask |= UA_STATUS_PARITY_ERR |
328 | | UA_STATUS_FRAME_ERR; |
329 | |
330 | /* Set status ignore mask */ |
331 | port->ignore_status_mask = 0; |
332 | if (!(termios->c_cflag & CREAD)) |
333 | port->ignore_status_mask |= UA_STATUS_OVERRUN_ERR |
334 | | UA_STATUS_PARITY_ERR | UA_STATUS_FRAME_ERR; |
335 | |
336 | uart_port_lock_irqsave(up: port, flags: &flags); |
337 | |
338 | uart_update_timeout(port, cflag: termios->c_cflag, baud); |
339 | |
340 | writeb_relaxed(config, port->membase + UA_CONFIG); |
341 | writeb_relaxed(divisor & 0xff, port->membase + UA_HBAUD_LO); |
342 | writeb_relaxed(divisor >> 8, port->membase + UA_HBAUD_HI); |
343 | |
344 | uart_port_unlock_irqrestore(up: port, flags); |
345 | } |
346 | |
347 | static const char *digicolor_uart_type(struct uart_port *port) |
348 | { |
349 | return (port->type == PORT_DIGICOLOR) ? "DIGICOLOR USART" : NULL; |
350 | } |
351 | |
352 | static void digicolor_uart_config_port(struct uart_port *port, int flags) |
353 | { |
354 | if (flags & UART_CONFIG_TYPE) |
355 | port->type = PORT_DIGICOLOR; |
356 | } |
357 | |
358 | static void digicolor_uart_release_port(struct uart_port *port) |
359 | { |
360 | } |
361 | |
362 | static int digicolor_uart_request_port(struct uart_port *port) |
363 | { |
364 | return 0; |
365 | } |
366 | |
367 | static const struct uart_ops digicolor_uart_ops = { |
368 | .tx_empty = digicolor_uart_tx_empty, |
369 | .set_mctrl = digicolor_uart_set_mctrl, |
370 | .get_mctrl = digicolor_uart_get_mctrl, |
371 | .stop_tx = digicolor_uart_stop_tx, |
372 | .start_tx = digicolor_uart_start_tx, |
373 | .stop_rx = digicolor_uart_stop_rx, |
374 | .break_ctl = digicolor_uart_break_ctl, |
375 | .startup = digicolor_uart_startup, |
376 | .shutdown = digicolor_uart_shutdown, |
377 | .set_termios = digicolor_uart_set_termios, |
378 | .type = digicolor_uart_type, |
379 | .config_port = digicolor_uart_config_port, |
380 | .release_port = digicolor_uart_release_port, |
381 | .request_port = digicolor_uart_request_port, |
382 | }; |
383 | |
384 | static void digicolor_uart_console_putchar(struct uart_port *port, unsigned char ch) |
385 | { |
386 | while (digicolor_uart_tx_full(port)) |
387 | cpu_relax(); |
388 | |
389 | writeb_relaxed(ch, port->membase + UA_EMI_REC); |
390 | } |
391 | |
392 | static void digicolor_uart_console_write(struct console *co, const char *c, |
393 | unsigned n) |
394 | { |
395 | struct uart_port *port = digicolor_ports[co->index]; |
396 | u8 status; |
397 | unsigned long flags; |
398 | int locked = 1; |
399 | |
400 | if (oops_in_progress) |
401 | locked = uart_port_trylock_irqsave(up: port, flags: &flags); |
402 | else |
403 | uart_port_lock_irqsave(up: port, flags: &flags); |
404 | |
405 | uart_console_write(port, s: c, count: n, putchar: digicolor_uart_console_putchar); |
406 | |
407 | if (locked) |
408 | uart_port_unlock_irqrestore(up: port, flags); |
409 | |
410 | /* Wait for transmitter to become empty */ |
411 | do { |
412 | status = readb_relaxed(port->membase + UA_STATUS); |
413 | } while ((status & UA_STATUS_TX_READY) == 0); |
414 | } |
415 | |
416 | static int digicolor_uart_console_setup(struct console *co, char *options) |
417 | { |
418 | int baud = 115200, bits = 8, parity = 'n', flow = 'n'; |
419 | struct uart_port *port; |
420 | |
421 | if (co->index < 0 || co->index >= DIGICOLOR_USART_NR) |
422 | return -EINVAL; |
423 | |
424 | port = digicolor_ports[co->index]; |
425 | if (!port) |
426 | return -ENODEV; |
427 | |
428 | if (options) |
429 | uart_parse_options(options, baud: &baud, parity: &parity, bits: &bits, flow: &flow); |
430 | |
431 | return uart_set_options(port, co, baud, parity, bits, flow); |
432 | } |
433 | |
434 | static struct console digicolor_console = { |
435 | .name = "ttyS" , |
436 | .device = uart_console_device, |
437 | .write = digicolor_uart_console_write, |
438 | .setup = digicolor_uart_console_setup, |
439 | .flags = CON_PRINTBUFFER, |
440 | .index = -1, |
441 | }; |
442 | |
443 | static struct uart_driver digicolor_uart = { |
444 | .driver_name = "digicolor-usart" , |
445 | .dev_name = "ttyS" , |
446 | .nr = DIGICOLOR_USART_NR, |
447 | }; |
448 | |
449 | static int digicolor_uart_probe(struct platform_device *pdev) |
450 | { |
451 | struct device_node *np = pdev->dev.of_node; |
452 | int irq, ret, index; |
453 | struct digicolor_port *dp; |
454 | struct resource *res; |
455 | struct clk *uart_clk; |
456 | |
457 | if (!np) { |
458 | dev_err(&pdev->dev, "Missing device tree node\n" ); |
459 | return -ENXIO; |
460 | } |
461 | |
462 | index = of_alias_get_id(np, stem: "serial" ); |
463 | if (index < 0 || index >= DIGICOLOR_USART_NR) |
464 | return -EINVAL; |
465 | |
466 | dp = devm_kzalloc(dev: &pdev->dev, size: sizeof(*dp), GFP_KERNEL); |
467 | if (!dp) |
468 | return -ENOMEM; |
469 | |
470 | uart_clk = devm_clk_get(dev: &pdev->dev, NULL); |
471 | if (IS_ERR(ptr: uart_clk)) |
472 | return PTR_ERR(ptr: uart_clk); |
473 | |
474 | dp->port.membase = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
475 | if (IS_ERR(ptr: dp->port.membase)) |
476 | return PTR_ERR(ptr: dp->port.membase); |
477 | dp->port.mapbase = res->start; |
478 | |
479 | irq = platform_get_irq(pdev, 0); |
480 | if (irq < 0) |
481 | return irq; |
482 | dp->port.irq = irq; |
483 | |
484 | dp->port.iotype = UPIO_MEM; |
485 | dp->port.uartclk = clk_get_rate(clk: uart_clk); |
486 | dp->port.fifosize = 16; |
487 | dp->port.dev = &pdev->dev; |
488 | dp->port.ops = &digicolor_uart_ops; |
489 | dp->port.line = index; |
490 | dp->port.type = PORT_DIGICOLOR; |
491 | spin_lock_init(&dp->port.lock); |
492 | |
493 | digicolor_ports[index] = &dp->port; |
494 | platform_set_drvdata(pdev, data: &dp->port); |
495 | |
496 | INIT_DELAYED_WORK(&dp->rx_poll_work, digicolor_rx_poll); |
497 | |
498 | ret = devm_request_irq(dev: &pdev->dev, irq: dp->port.irq, handler: digicolor_uart_int, irqflags: 0, |
499 | devname: dev_name(dev: &pdev->dev), dev_id: &dp->port); |
500 | if (ret) |
501 | return ret; |
502 | |
503 | return uart_add_one_port(reg: &digicolor_uart, port: &dp->port); |
504 | } |
505 | |
506 | static int digicolor_uart_remove(struct platform_device *pdev) |
507 | { |
508 | struct uart_port *port = platform_get_drvdata(pdev); |
509 | |
510 | uart_remove_one_port(reg: &digicolor_uart, port); |
511 | |
512 | return 0; |
513 | } |
514 | |
515 | static const struct of_device_id digicolor_uart_dt_ids[] = { |
516 | { .compatible = "cnxt,cx92755-usart" , }, |
517 | { } |
518 | }; |
519 | MODULE_DEVICE_TABLE(of, digicolor_uart_dt_ids); |
520 | |
521 | static struct platform_driver digicolor_uart_platform = { |
522 | .driver = { |
523 | .name = "digicolor-usart" , |
524 | .of_match_table = of_match_ptr(digicolor_uart_dt_ids), |
525 | }, |
526 | .probe = digicolor_uart_probe, |
527 | .remove = digicolor_uart_remove, |
528 | }; |
529 | |
530 | static int __init digicolor_uart_init(void) |
531 | { |
532 | int ret; |
533 | |
534 | if (IS_ENABLED(CONFIG_SERIAL_CONEXANT_DIGICOLOR_CONSOLE)) { |
535 | digicolor_uart.cons = &digicolor_console; |
536 | digicolor_console.data = &digicolor_uart; |
537 | } |
538 | |
539 | ret = uart_register_driver(uart: &digicolor_uart); |
540 | if (ret) |
541 | return ret; |
542 | |
543 | ret = platform_driver_register(&digicolor_uart_platform); |
544 | if (ret) |
545 | uart_unregister_driver(uart: &digicolor_uart); |
546 | |
547 | return ret; |
548 | } |
549 | module_init(digicolor_uart_init); |
550 | |
551 | static void __exit digicolor_uart_exit(void) |
552 | { |
553 | platform_driver_unregister(&digicolor_uart_platform); |
554 | uart_unregister_driver(uart: &digicolor_uart); |
555 | } |
556 | module_exit(digicolor_uart_exit); |
557 | |
558 | MODULE_AUTHOR("Baruch Siach <baruch@tkos.co.il>" ); |
559 | MODULE_DESCRIPTION("Conexant Digicolor USART serial driver" ); |
560 | MODULE_LICENSE("GPL" ); |
561 | |