aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/serial/atmel_serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/serial/atmel_serial.c')
-rw-r--r--drivers/serial/atmel_serial.c245
1 files changed, 190 insertions, 55 deletions
diff --git a/drivers/serial/atmel_serial.c b/drivers/serial/atmel_serial.c
index e06c6c8f4dd8..f0f6ea3a9eed 100644
--- a/drivers/serial/atmel_serial.c
+++ b/drivers/serial/atmel_serial.c
@@ -103,6 +103,13 @@
103static int (*atmel_open_hook)(struct uart_port *); 103static int (*atmel_open_hook)(struct uart_port *);
104static void (*atmel_close_hook)(struct uart_port *); 104static void (*atmel_close_hook)(struct uart_port *);
105 105
106struct atmel_uart_char {
107 u16 status;
108 u16 ch;
109};
110
111#define ATMEL_SERIAL_RINGSIZE 1024
112
106/* 113/*
107 * We wrap our port structure around the generic uart_port. 114 * We wrap our port structure around the generic uart_port.
108 */ 115 */
@@ -111,6 +118,12 @@ struct atmel_uart_port {
111 struct clk *clk; /* uart clock */ 118 struct clk *clk; /* uart clock */
112 unsigned short suspended; /* is port suspended? */ 119 unsigned short suspended; /* is port suspended? */
113 int break_active; /* break being received */ 120 int break_active; /* break being received */
121
122 struct tasklet_struct tasklet;
123 unsigned int irq_status;
124 unsigned int irq_status_prev;
125
126 struct circ_buf rx_ring;
114}; 127};
115 128
116static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART]; 129static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART];
@@ -240,22 +253,42 @@ static void atmel_break_ctl(struct uart_port *port, int break_state)
240} 253}
241 254
242/* 255/*
256 * Stores the incoming character in the ring buffer
257 */
258static void
259atmel_buffer_rx_char(struct uart_port *port, unsigned int status,
260 unsigned int ch)
261{
262 struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
263 struct circ_buf *ring = &atmel_port->rx_ring;
264 struct atmel_uart_char *c;
265
266 if (!CIRC_SPACE(ring->head, ring->tail, ATMEL_SERIAL_RINGSIZE))
267 /* Buffer overflow, ignore char */
268 return;
269
270 c = &((struct atmel_uart_char *)ring->buf)[ring->head];
271 c->status = status;
272 c->ch = ch;
273
274 /* Make sure the character is stored before we update head. */
275 smp_wmb();
276
277 ring->head = (ring->head + 1) & (ATMEL_SERIAL_RINGSIZE - 1);
278}
279
280/*
243 * Characters received (called from interrupt handler) 281 * Characters received (called from interrupt handler)
244 */ 282 */
245static void atmel_rx_chars(struct uart_port *port) 283static void atmel_rx_chars(struct uart_port *port)
246{ 284{
247 struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port; 285 struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
248 struct tty_struct *tty = port->info->tty; 286 unsigned int status, ch;
249 unsigned int status, ch, flg;
250 287
251 status = UART_GET_CSR(port); 288 status = UART_GET_CSR(port);
252 while (status & ATMEL_US_RXRDY) { 289 while (status & ATMEL_US_RXRDY) {
253 ch = UART_GET_CHAR(port); 290 ch = UART_GET_CHAR(port);
254 291
255 port->icount.rx++;
256
257 flg = TTY_NORMAL;
258
259 /* 292 /*
260 * note that the error handling code is 293 * note that the error handling code is
261 * out of the main execution path 294 * out of the main execution path
@@ -263,17 +296,14 @@ static void atmel_rx_chars(struct uart_port *port)
263 if (unlikely(status & (ATMEL_US_PARE | ATMEL_US_FRAME 296 if (unlikely(status & (ATMEL_US_PARE | ATMEL_US_FRAME
264 | ATMEL_US_OVRE | ATMEL_US_RXBRK) 297 | ATMEL_US_OVRE | ATMEL_US_RXBRK)
265 || atmel_port->break_active)) { 298 || atmel_port->break_active)) {
299
266 /* clear error */ 300 /* clear error */
267 UART_PUT_CR(port, ATMEL_US_RSTSTA); 301 UART_PUT_CR(port, ATMEL_US_RSTSTA);
302
268 if (status & ATMEL_US_RXBRK 303 if (status & ATMEL_US_RXBRK
269 && !atmel_port->break_active) { 304 && !atmel_port->break_active) {
270 /* ignore side-effect */
271 status &= ~(ATMEL_US_PARE | ATMEL_US_FRAME);
272 port->icount.brk++;
273 atmel_port->break_active = 1; 305 atmel_port->break_active = 1;
274 UART_PUT_IER(port, ATMEL_US_RXBRK); 306 UART_PUT_IER(port, ATMEL_US_RXBRK);
275 if (uart_handle_break(port))
276 goto ignore_char;
277 } else { 307 } else {
278 /* 308 /*
279 * This is either the end-of-break 309 * This is either the end-of-break
@@ -286,52 +316,30 @@ static void atmel_rx_chars(struct uart_port *port)
286 status &= ~ATMEL_US_RXBRK; 316 status &= ~ATMEL_US_RXBRK;
287 atmel_port->break_active = 0; 317 atmel_port->break_active = 0;
288 } 318 }
289 if (status & ATMEL_US_PARE)
290 port->icount.parity++;
291 if (status & ATMEL_US_FRAME)
292 port->icount.frame++;
293 if (status & ATMEL_US_OVRE)
294 port->icount.overrun++;
295
296 status &= port->read_status_mask;
297
298 if (status & ATMEL_US_RXBRK)
299 flg = TTY_BREAK;
300 else if (status & ATMEL_US_PARE)
301 flg = TTY_PARITY;
302 else if (status & ATMEL_US_FRAME)
303 flg = TTY_FRAME;
304 } 319 }
305 320
306 if (uart_handle_sysrq_char(port, ch)) 321 atmel_buffer_rx_char(port, status, ch);
307 goto ignore_char;
308
309 uart_insert_char(port, status, ATMEL_US_OVRE, ch, flg);
310
311ignore_char:
312 status = UART_GET_CSR(port); 322 status = UART_GET_CSR(port);
313 } 323 }
314 324
315 tty_flip_buffer_push(tty); 325 tasklet_schedule(&atmel_port->tasklet);
316} 326}
317 327
318/* 328/*
319 * Transmit characters (called from interrupt handler) 329 * Transmit characters (called from tasklet with TXRDY interrupt
330 * disabled)
320 */ 331 */
321static void atmel_tx_chars(struct uart_port *port) 332static void atmel_tx_chars(struct uart_port *port)
322{ 333{
323 struct circ_buf *xmit = &port->info->xmit; 334 struct circ_buf *xmit = &port->info->xmit;
324 335
325 if (port->x_char) { 336 if (port->x_char && UART_GET_CSR(port) & ATMEL_US_TXRDY) {
326 UART_PUT_CHAR(port, port->x_char); 337 UART_PUT_CHAR(port, port->x_char);
327 port->icount.tx++; 338 port->icount.tx++;
328 port->x_char = 0; 339 port->x_char = 0;
329 return;
330 } 340 }
331 if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { 341 if (uart_circ_empty(xmit) || uart_tx_stopped(port))
332 atmel_stop_tx(port);
333 return; 342 return;
334 }
335 343
336 while (UART_GET_CSR(port) & ATMEL_US_TXRDY) { 344 while (UART_GET_CSR(port) & ATMEL_US_TXRDY) {
337 UART_PUT_CHAR(port, xmit->buf[xmit->tail]); 345 UART_PUT_CHAR(port, xmit->buf[xmit->tail]);
@@ -344,8 +352,8 @@ static void atmel_tx_chars(struct uart_port *port)
344 if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) 352 if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
345 uart_write_wakeup(port); 353 uart_write_wakeup(port);
346 354
347 if (uart_circ_empty(xmit)) 355 if (!uart_circ_empty(xmit))
348 atmel_stop_tx(port); 356 UART_PUT_IER(port, ATMEL_US_TXRDY);
349} 357}
350 358
351/* 359/*
@@ -371,14 +379,18 @@ atmel_handle_receive(struct uart_port *port, unsigned int pending)
371} 379}
372 380
373/* 381/*
374 * transmit interrupt handler. 382 * transmit interrupt handler. (Transmit is IRQF_NODELAY safe)
375 */ 383 */
376static void 384static void
377atmel_handle_transmit(struct uart_port *port, unsigned int pending) 385atmel_handle_transmit(struct uart_port *port, unsigned int pending)
378{ 386{
387 struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
388
379 /* Interrupt transmit */ 389 /* Interrupt transmit */
380 if (pending & ATMEL_US_TXRDY) 390 if (pending & ATMEL_US_TXRDY) {
381 atmel_tx_chars(port); 391 UART_PUT_IDR(port, ATMEL_US_TXRDY);
392 tasklet_schedule(&atmel_port->tasklet);
393 }
382} 394}
383 395
384/* 396/*
@@ -388,18 +400,13 @@ static void
388atmel_handle_status(struct uart_port *port, unsigned int pending, 400atmel_handle_status(struct uart_port *port, unsigned int pending,
389 unsigned int status) 401 unsigned int status)
390{ 402{
391 /* TODO: All reads to CSR will clear these interrupts! */ 403 struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
392 if (pending & ATMEL_US_RIIC) 404
393 port->icount.rng++;
394 if (pending & ATMEL_US_DSRIC)
395 port->icount.dsr++;
396 if (pending & ATMEL_US_DCDIC)
397 uart_handle_dcd_change(port, !(status & ATMEL_US_DCD));
398 if (pending & ATMEL_US_CTSIC)
399 uart_handle_cts_change(port, !(status & ATMEL_US_CTS));
400 if (pending & (ATMEL_US_RIIC | ATMEL_US_DSRIC | ATMEL_US_DCDIC 405 if (pending & (ATMEL_US_RIIC | ATMEL_US_DSRIC | ATMEL_US_DCDIC
401 | ATMEL_US_CTSIC)) 406 | ATMEL_US_CTSIC)) {
402 wake_up_interruptible(&port->info->delta_msr_wait); 407 atmel_port->irq_status = status;
408 tasklet_schedule(&atmel_port->tasklet);
409 }
403} 410}
404 411
405/* 412/*
@@ -426,6 +433,114 @@ static irqreturn_t atmel_interrupt(int irq, void *dev_id)
426 return IRQ_HANDLED; 433 return IRQ_HANDLED;
427} 434}
428 435
436static void atmel_rx_from_ring(struct uart_port *port)
437{
438 struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
439 struct circ_buf *ring = &atmel_port->rx_ring;
440 unsigned int flg;
441 unsigned int status;
442
443 while (ring->head != ring->tail) {
444 struct atmel_uart_char c;
445
446 /* Make sure c is loaded after head. */
447 smp_rmb();
448
449 c = ((struct atmel_uart_char *)ring->buf)[ring->tail];
450
451 ring->tail = (ring->tail + 1) & (ATMEL_SERIAL_RINGSIZE - 1);
452
453 port->icount.rx++;
454 status = c.status;
455 flg = TTY_NORMAL;
456
457 /*
458 * note that the error handling code is
459 * out of the main execution path
460 */
461 if (unlikely(status & (ATMEL_US_PARE | ATMEL_US_FRAME
462 | ATMEL_US_OVRE | ATMEL_US_RXBRK))) {
463 if (status & ATMEL_US_RXBRK) {
464 /* ignore side-effect */
465 status &= ~(ATMEL_US_PARE | ATMEL_US_FRAME);
466
467 port->icount.brk++;
468 if (uart_handle_break(port))
469 continue;
470 }
471 if (status & ATMEL_US_PARE)
472 port->icount.parity++;
473 if (status & ATMEL_US_FRAME)
474 port->icount.frame++;
475 if (status & ATMEL_US_OVRE)
476 port->icount.overrun++;
477
478 status &= port->read_status_mask;
479
480 if (status & ATMEL_US_RXBRK)
481 flg = TTY_BREAK;
482 else if (status & ATMEL_US_PARE)
483 flg = TTY_PARITY;
484 else if (status & ATMEL_US_FRAME)
485 flg = TTY_FRAME;
486 }
487
488
489 if (uart_handle_sysrq_char(port, c.ch))
490 continue;
491
492 uart_insert_char(port, status, ATMEL_US_OVRE, c.ch, flg);
493 }
494
495 /*
496 * Drop the lock here since it might end up calling
497 * uart_start(), which takes the lock.
498 */
499 spin_unlock(&port->lock);
500 tty_flip_buffer_push(port->info->tty);
501 spin_lock(&port->lock);
502}
503
504/*
505 * tasklet handling tty stuff outside the interrupt handler.
506 */
507static void atmel_tasklet_func(unsigned long data)
508{
509 struct uart_port *port = (struct uart_port *)data;
510 struct atmel_uart_port *atmel_port = (struct atmel_uart_port *)port;
511 unsigned int status;
512 unsigned int status_change;
513
514 /* The interrupt handler does not take the lock */
515 spin_lock(&port->lock);
516
517 atmel_tx_chars(port);
518
519 status = atmel_port->irq_status;
520 status_change = status ^ atmel_port->irq_status_prev;
521
522 if (status_change & (ATMEL_US_RI | ATMEL_US_DSR
523 | ATMEL_US_DCD | ATMEL_US_CTS)) {
524 /* TODO: All reads to CSR will clear these interrupts! */
525 if (status_change & ATMEL_US_RI)
526 port->icount.rng++;
527 if (status_change & ATMEL_US_DSR)
528 port->icount.dsr++;
529 if (status_change & ATMEL_US_DCD)
530 uart_handle_dcd_change(port, !(status & ATMEL_US_DCD));
531 if (status_change & ATMEL_US_CTS)
532 uart_handle_cts_change(port, !(status & ATMEL_US_CTS));
533
534 wake_up_interruptible(&port->info->delta_msr_wait);
535
536 atmel_port->irq_status_prev = status;
537 }
538
539 atmel_rx_from_ring(port);
540
541 spin_unlock(&port->lock);
542}
543
429/* 544/*
430 * Perform initialization and enable port for reception 545 * Perform initialization and enable port for reception
431 */ 546 */
@@ -757,6 +872,11 @@ static void __devinit atmel_init_port(struct atmel_uart_port *atmel_port,
757 port->mapbase = pdev->resource[0].start; 872 port->mapbase = pdev->resource[0].start;
758 port->irq = pdev->resource[1].start; 873 port->irq = pdev->resource[1].start;
759 874
875 tasklet_init(&atmel_port->tasklet, atmel_tasklet_func,
876 (unsigned long)port);
877
878 memset(&atmel_port->rx_ring, 0, sizeof(atmel_port->rx_ring));
879
760 if (data->regs) 880 if (data->regs)
761 /* Already mapped by setup code */ 881 /* Already mapped by setup code */
762 port->membase = data->regs; 882 port->membase = data->regs;
@@ -997,11 +1117,20 @@ static int atmel_serial_resume(struct platform_device *pdev)
997static int __devinit atmel_serial_probe(struct platform_device *pdev) 1117static int __devinit atmel_serial_probe(struct platform_device *pdev)
998{ 1118{
999 struct atmel_uart_port *port; 1119 struct atmel_uart_port *port;
1120 void *data;
1000 int ret; 1121 int ret;
1001 1122
1123 BUILD_BUG_ON(!is_power_of_2(ATMEL_SERIAL_RINGSIZE));
1124
1002 port = &atmel_ports[pdev->id]; 1125 port = &atmel_ports[pdev->id];
1003 atmel_init_port(port, pdev); 1126 atmel_init_port(port, pdev);
1004 1127
1128 ret = -ENOMEM;
1129 data = kmalloc(ATMEL_SERIAL_RINGSIZE, GFP_KERNEL);
1130 if (!data)
1131 goto err_alloc_ring;
1132 port->rx_ring.buf = data;
1133
1005 ret = uart_add_one_port(&atmel_uart, &port->uart); 1134 ret = uart_add_one_port(&atmel_uart, &port->uart);
1006 if (ret) 1135 if (ret)
1007 goto err_add_port; 1136 goto err_add_port;
@@ -1012,6 +1141,9 @@ static int __devinit atmel_serial_probe(struct platform_device *pdev)
1012 return 0; 1141 return 0;
1013 1142
1014err_add_port: 1143err_add_port:
1144 kfree(port->rx_ring.buf);
1145 port->rx_ring.buf = NULL;
1146err_alloc_ring:
1015 if (!atmel_is_console_port(&port->uart)) { 1147 if (!atmel_is_console_port(&port->uart)) {
1016 clk_disable(port->clk); 1148 clk_disable(port->clk);
1017 clk_put(port->clk); 1149 clk_put(port->clk);
@@ -1032,6 +1164,9 @@ static int __devexit atmel_serial_remove(struct platform_device *pdev)
1032 1164
1033 ret = uart_remove_one_port(&atmel_uart, port); 1165 ret = uart_remove_one_port(&atmel_uart, port);
1034 1166
1167 tasklet_kill(&atmel_port->tasklet);
1168 kfree(atmel_port->rx_ring.buf);
1169
1035 /* "port" is allocated statically, so we shouldn't free it */ 1170 /* "port" is allocated statically, so we shouldn't free it */
1036 1171
1037 clk_disable(atmel_port->clk); 1172 clk_disable(atmel_port->clk);