diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/serial/atmel_serial.c | 245 |
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 @@ | |||
103 | static int (*atmel_open_hook)(struct uart_port *); | 103 | static int (*atmel_open_hook)(struct uart_port *); |
104 | static void (*atmel_close_hook)(struct uart_port *); | 104 | static void (*atmel_close_hook)(struct uart_port *); |
105 | 105 | ||
106 | struct 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 | ||
116 | static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART]; | 129 | static 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 | */ | ||
258 | static void | ||
259 | atmel_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 | */ |
245 | static void atmel_rx_chars(struct uart_port *port) | 283 | static 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 | |||
311 | ignore_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 | */ |
321 | static void atmel_tx_chars(struct uart_port *port) | 332 | static 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 | */ |
376 | static void | 384 | static void |
377 | atmel_handle_transmit(struct uart_port *port, unsigned int pending) | 385 | atmel_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 | |||
388 | atmel_handle_status(struct uart_port *port, unsigned int pending, | 400 | atmel_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 | ||
436 | static 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 | */ | ||
507 | static 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) | |||
997 | static int __devinit atmel_serial_probe(struct platform_device *pdev) | 1117 | static 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 | ||
1014 | err_add_port: | 1143 | err_add_port: |
1144 | kfree(port->rx_ring.buf); | ||
1145 | port->rx_ring.buf = NULL; | ||
1146 | err_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); |