diff options
Diffstat (limited to 'drivers/tty/serial/atmel_serial.c')
-rw-r--r-- | drivers/tty/serial/atmel_serial.c | 128 |
1 files changed, 111 insertions, 17 deletions
diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index b922f5d2e61e..9988c0c305c2 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -33,6 +33,8 @@ #include <linux/sysrq.h> #include <linux/tty_flip.h> #include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> #include <linux/dma-mapping.h> #include <linux/atmel_pdc.h> #include <linux/atmel_serial.h> @@ -157,11 +159,22 @@ struct atmel_uart_port { }; static struct atmel_uart_port atmel_ports[ATMEL_MAX_UART]; +static unsigned long atmel_ports_in_use; #ifdef SUPPORT_SYSRQ static struct console atmel_console; #endif +#if defined(CONFIG_OF) +static const struct of_device_id atmel_serial_dt_ids[] = { + { .compatible = "atmel,at91rm9200-usart" }, + { .compatible = "atmel,at91sam9260-usart" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, atmel_serial_dt_ids); +#endif + static inline struct atmel_uart_port * to_atmel_uart_port(struct uart_port *uart) { @@ -339,7 +352,8 @@ static void atmel_stop_tx(struct uart_port *port) /* Disable interrupts */ UART_PUT_IDR(port, atmel_port->tx_done_mask); - if (atmel_port->rs485.flags & SER_RS485_ENABLED) + if ((atmel_port->rs485.flags & SER_RS485_ENABLED) && + !(atmel_port->rs485.flags & SER_RS485_RX_DURING_TX)) atmel_start_rx(port); } @@ -356,7 +370,8 @@ static void atmel_start_tx(struct uart_port *port) really need this.*/ return; - if (atmel_port->rs485.flags & SER_RS485_ENABLED) + if ((atmel_port->rs485.flags & SER_RS485_ENABLED) && + !(atmel_port->rs485.flags & SER_RS485_RX_DURING_TX)) atmel_stop_rx(port); /* re-enable PDC transmit */ @@ -680,7 +695,8 @@ static void atmel_tx_dma(struct uart_port *port) /* Enable interrupts */ UART_PUT_IER(port, atmel_port->tx_done_mask); } else { - if (atmel_port->rs485.flags & SER_RS485_ENABLED) { + if ((atmel_port->rs485.flags & SER_RS485_ENABLED) && + !(atmel_port->rs485.flags & SER_RS485_RX_DURING_TX)) { /* DMA done, stop TX, start RX for RS485 */ atmel_start_rx(port); } @@ -1407,6 +1423,48 @@ static struct uart_ops atmel_pops = { #endif }; +static void __devinit atmel_of_init_port(struct atmel_uart_port *atmel_port, + struct device_node *np) +{ + u32 rs485_delay[2]; + + /* DMA/PDC usage specification */ + if (of_get_property(np, "atmel,use-dma-rx", NULL)) + atmel_port->use_dma_rx = 1; + else + atmel_port->use_dma_rx = 0; + if (of_get_property(np, "atmel,use-dma-tx", NULL)) + atmel_port->use_dma_tx = 1; + else + atmel_port->use_dma_tx = 0; + + /* rs485 properties */ + if (of_property_read_u32_array(np, "rs485-rts-delay", + rs485_delay, 2) == 0) { + struct serial_rs485 *rs485conf = &atmel_port->rs485; + + rs485conf->delay_rts_before_send = rs485_delay[0]; + rs485conf->delay_rts_after_send = rs485_delay[1]; + rs485conf->flags = 0; + + if (rs485conf->delay_rts_before_send == 0 && + rs485conf->delay_rts_after_send == 0) { + rs485conf->flags |= SER_RS485_RTS_ON_SEND; + } else { + if (rs485conf->delay_rts_before_send) + rs485conf->flags |= SER_RS485_RTS_BEFORE_SEND; + if (rs485conf->delay_rts_after_send) + rs485conf->flags |= SER_RS485_RTS_AFTER_SEND; + } + + if (of_get_property(np, "rs485-rx-during-tx", NULL)) + rs485conf->flags |= SER_RS485_RX_DURING_TX; + + if (of_get_property(np, "linux,rs485-enabled-at-boot-time", NULL)) + rs485conf->flags |= SER_RS485_ENABLED; + } +} + /* * Configure the port from the platform device resource info. */ @@ -1414,13 +1472,20 @@ static void __devinit atmel_init_port(struct atmel_uart_port *atmel_port, struct platform_device *pdev) { struct uart_port *port = &atmel_port->uart; - struct atmel_uart_data *data = pdev->dev.platform_data; + struct atmel_uart_data *pdata = pdev->dev.platform_data; + + if (pdev->dev.of_node) { + atmel_of_init_port(atmel_port, pdev->dev.of_node); + } else { + atmel_port->use_dma_rx = pdata->use_dma_rx; + atmel_port->use_dma_tx = pdata->use_dma_tx; + atmel_port->rs485 = pdata->rs485; + } port->iotype = UPIO_MEM; port->flags = UPF_BOOT_AUTOCONF; port->ops = &atmel_pops; port->fifosize = 1; - port->line = data->num; port->dev = &pdev->dev; port->mapbase = pdev->resource[0].start; port->irq = pdev->resource[1].start; @@ -1430,10 +1495,10 @@ static void __devinit atmel_init_port(struct atmel_uart_port *atmel_port, memset(&atmel_port->rx_ring, 0, sizeof(atmel_port->rx_ring)); - if (data->regs) + if (pdata && pdata->regs) { /* Already mapped by setup code */ - port->membase = data->regs; - else { + port->membase = pdata->regs; + } else { port->flags |= UPF_IOREMAP; port->membase = NULL; } @@ -1447,9 +1512,6 @@ static void __devinit atmel_init_port(struct atmel_uart_port *atmel_port, /* only enable clock when USART is in use */ } - atmel_port->use_dma_rx = data->use_dma_rx; - atmel_port->use_dma_tx = data->use_dma_tx; - atmel_port->rs485 = data->rs485; /* Use TXEMPTY for interrupt when rs485 else TXRDY or ENDTX|TXBUFE */ if (atmel_port->rs485.flags & SER_RS485_ENABLED) atmel_port->tx_done_mask = ATMEL_US_TXEMPTY; @@ -1611,10 +1673,14 @@ static int __init atmel_console_init(void) if (atmel_default_console_device) { struct atmel_uart_data *pdata = atmel_default_console_device->dev.platform_data; + int id = pdata->num; + struct atmel_uart_port *port = &atmel_ports[id]; + + port->backup_imr = 0; + port->uart.line = id; - add_preferred_console(ATMEL_DEVICENAME, pdata->num, NULL); - atmel_init_port(&atmel_ports[pdata->num], - atmel_default_console_device); + add_preferred_console(ATMEL_DEVICENAME, id, NULL); + atmel_init_port(port, atmel_default_console_device); register_console(&atmel_console); } @@ -1711,14 +1777,39 @@ static int atmel_serial_resume(struct platform_device *pdev) static int __devinit atmel_serial_probe(struct platform_device *pdev) { struct atmel_uart_port *port; + struct device_node *np = pdev->dev.of_node; struct atmel_uart_data *pdata = pdev->dev.platform_data; void *data; - int ret; + int ret = -ENODEV; BUILD_BUG_ON(ATMEL_SERIAL_RINGSIZE & (ATMEL_SERIAL_RINGSIZE - 1)); - port = &atmel_ports[pdata->num]; + if (np) + ret = of_alias_get_id(np, "serial"); + else + if (pdata) + ret = pdata->num; + + if (ret < 0) + /* port id not found in platform data nor device-tree aliases: + * auto-enumerate it */ + ret = find_first_zero_bit(&atmel_ports_in_use, + sizeof(atmel_ports_in_use)); + + if (ret > ATMEL_MAX_UART) { + ret = -ENODEV; + goto err; + } + + if (test_and_set_bit(ret, &atmel_ports_in_use)) { + /* port already in use */ + ret = -EBUSY; + goto err; + } + + port = &atmel_ports[ret]; port->backup_imr = 0; + port->uart.line = ret; atmel_init_port(port, pdev); @@ -1764,7 +1855,7 @@ err_alloc_ring: clk_put(port->clk); port->clk = NULL; } - +err: return ret; } @@ -1784,6 +1875,8 @@ static int __devexit atmel_serial_remove(struct platform_device *pdev) /* "port" is allocated statically, so we shouldn't free it */ + clear_bit(port->line, &atmel_ports_in_use); + clk_put(atmel_port->clk); return ret; @@ -1797,6 +1890,7 @@ static struct platform_driver atmel_serial_driver = { .driver = { .name = "atmel_usart", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(atmel_serial_dt_ids), }, }; |