diff options
Diffstat (limited to 'drivers/tty/serial/sirfsoc_uart.c')
-rw-r--r-- | drivers/tty/serial/sirfsoc_uart.c | 783 |
1 files changed, 783 insertions, 0 deletions
diff --git a/drivers/tty/serial/sirfsoc_uart.c b/drivers/tty/serial/sirfsoc_uart.c new file mode 100644 index 0000000..a60523f --- /dev/null +++ b/drivers/tty/serial/sirfsoc_uart.c @@ -0,0 +1,783 @@ +/* + * Driver for CSR SiRFprimaII onboard UARTs. + * + * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. + * + * Licensed under GPLv2 or later. + */ + +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/init.h> +#include <linux/sysrq.h> +#include <linux/console.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial_core.h> +#include <linux/serial.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <asm/irq.h> +#include <asm/mach/irq.h> +#include <linux/pinctrl/pinmux.h> + +#include "sirfsoc_uart.h" + +static unsigned int +sirfsoc_uart_pio_tx_chars(struct sirfsoc_uart_port *sirfport, int count); +static unsigned int +sirfsoc_uart_pio_rx_chars(struct uart_port *port, unsigned int max_rx_count); +static struct uart_driver sirfsoc_uart_drv; + +static const struct sirfsoc_baudrate_to_regv baudrate_to_regv[] = { + {4000000, 2359296}, + {3500000, 1310721}, + {3000000, 1572865}, + {2500000, 1245186}, + {2000000, 1572866}, + {1500000, 1245188}, + {1152000, 1638404}, + {1000000, 1572869}, + {921600, 1114120}, + {576000, 1245196}, + {500000, 1245198}, + {460800, 1572876}, + {230400, 1310750}, + {115200, 1310781}, + {57600, 1310843}, + {38400, 1114328}, + {19200, 1114545}, + {9600, 1114979}, +}; + +static struct sirfsoc_uart_port sirfsoc_uart_ports[SIRFSOC_UART_NR] = { + [0] = { + .port = { + .iotype = UPIO_MEM, + .flags = UPF_BOOT_AUTOCONF, + .line = 0, + }, + }, + [1] = { + .port = { + .iotype = UPIO_MEM, + .flags = UPF_BOOT_AUTOCONF, + .line = 1, + }, + }, + [2] = { + .port = { + .iotype = UPIO_MEM, + .flags = UPF_BOOT_AUTOCONF, + .line = 2, + }, + }, +}; + +static inline struct sirfsoc_uart_port *to_sirfport(struct uart_port *port) +{ + return container_of(port, struct sirfsoc_uart_port, port); +} + +static inline unsigned int sirfsoc_uart_tx_empty(struct uart_port *port) +{ + unsigned long reg; + reg = rd_regl(port, SIRFUART_TX_FIFO_STATUS); + if (reg & SIRFUART_FIFOEMPTY_MASK(port)) + return TIOCSER_TEMT; + else + return 0; +} + +static unsigned int sirfsoc_uart_get_mctrl(struct uart_port *port) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + if (!(sirfport->ms_enabled)) { + goto cts_asserted; + } else if (sirfport->hw_flow_ctrl) { + if (!(rd_regl(port, SIRFUART_AFC_CTRL) & + SIRFUART_CTS_IN_STATUS)) + goto cts_asserted; + else + goto cts_deasserted; + } +cts_deasserted: + return TIOCM_CAR | TIOCM_DSR; +cts_asserted: + return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; +} + +static void sirfsoc_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + unsigned int assert = mctrl & TIOCM_RTS; + unsigned int val = assert ? SIRFUART_AFC_CTRL_RX_THD : 0x0; + unsigned int current_val; + if (sirfport->hw_flow_ctrl) { + current_val = rd_regl(port, SIRFUART_AFC_CTRL) & ~0xFF; + val |= current_val; + wr_regl(port, SIRFUART_AFC_CTRL, val); + } +} + +static void sirfsoc_uart_stop_tx(struct uart_port *port) +{ + unsigned int regv; + regv = rd_regl(port, SIRFUART_INT_EN); + wr_regl(port, SIRFUART_INT_EN, regv & ~SIRFUART_TX_INT_EN); +} + +void sirfsoc_uart_start_tx(struct uart_port *port) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + unsigned long regv; + sirfsoc_uart_pio_tx_chars(sirfport, 1); + wr_regl(port, SIRFUART_TX_FIFO_OP, SIRFUART_TX_FIFO_START); + regv = rd_regl(port, SIRFUART_INT_EN); + wr_regl(port, SIRFUART_INT_EN, regv | SIRFUART_TX_INT_EN); +} + +static void sirfsoc_uart_stop_rx(struct uart_port *port) +{ + unsigned long regv; + wr_regl(port, SIRFUART_RX_FIFO_OP, 0); + regv = rd_regl(port, SIRFUART_INT_EN); + wr_regl(port, SIRFUART_INT_EN, regv & ~SIRFUART_RX_IO_INT_EN); +} + +static void sirfsoc_uart_disable_ms(struct uart_port *port) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + unsigned long reg; + sirfport->ms_enabled = 0; + if (!sirfport->hw_flow_ctrl) + return; + reg = rd_regl(port, SIRFUART_AFC_CTRL); + wr_regl(port, SIRFUART_AFC_CTRL, reg & ~0x3FF); + reg = rd_regl(port, SIRFUART_INT_EN); + wr_regl(port, SIRFUART_INT_EN, reg & ~SIRFUART_CTS_INT_EN); +} + +static void sirfsoc_uart_enable_ms(struct uart_port *port) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + unsigned long reg; + unsigned long flg; + if (!sirfport->hw_flow_ctrl) + return; + flg = SIRFUART_AFC_RX_EN | SIRFUART_AFC_TX_EN; + reg = rd_regl(port, SIRFUART_AFC_CTRL); + wr_regl(port, SIRFUART_AFC_CTRL, reg | flg); + reg = rd_regl(port, SIRFUART_INT_EN); + wr_regl(port, SIRFUART_INT_EN, reg | SIRFUART_CTS_INT_EN); + uart_handle_cts_change(port, + !(rd_regl(port, SIRFUART_AFC_CTRL) & SIRFUART_CTS_IN_STATUS)); + sirfport->ms_enabled = 1; +} + +static void sirfsoc_uart_break_ctl(struct uart_port *port, int break_state) +{ + unsigned long ulcon = rd_regl(port, SIRFUART_LINE_CTRL); + if (break_state) + ulcon |= SIRFUART_SET_BREAK; + else + ulcon &= ~SIRFUART_SET_BREAK; + wr_regl(port, SIRFUART_LINE_CTRL, ulcon); +} + +static unsigned int +sirfsoc_uart_pio_rx_chars(struct uart_port *port, unsigned int max_rx_count) +{ + unsigned int ch, rx_count = 0; + struct tty_struct *tty; + + tty = tty_port_tty_get(&port->state->port); + if (!tty) + return -ENODEV; + + while (!(rd_regl(port, SIRFUART_RX_FIFO_STATUS) & + SIRFUART_FIFOEMPTY_MASK(port))) { + ch = rd_regl(port, SIRFUART_RX_FIFO_DATA) | SIRFUART_DUMMY_READ; + if (unlikely(uart_handle_sysrq_char(port, ch))) + continue; + uart_insert_char(port, 0, 0, ch, TTY_NORMAL); + rx_count++; + if (rx_count >= max_rx_count) + break; + } + + port->icount.rx += rx_count; + tty_flip_buffer_push(tty); + tty_kref_put(tty); + + return rx_count; +} + +static unsigned int +sirfsoc_uart_pio_tx_chars(struct sirfsoc_uart_port *sirfport, int count) +{ + struct uart_port *port = &sirfport->port; + struct circ_buf *xmit = &port->state->xmit; + unsigned int num_tx = 0; + while (!uart_circ_empty(xmit) && + !(rd_regl(port, SIRFUART_TX_FIFO_STATUS) & + SIRFUART_FIFOFULL_MASK(port)) && + count--) { + wr_regl(port, SIRFUART_TX_FIFO_DATA, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + num_tx++; + } + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + return num_tx; +} + +static irqreturn_t sirfsoc_uart_isr(int irq, void *dev_id) +{ + unsigned long intr_status; + unsigned long cts_status; + unsigned long flag = TTY_NORMAL; + struct sirfsoc_uart_port *sirfport = (struct sirfsoc_uart_port *)dev_id; + struct uart_port *port = &sirfport->port; + struct uart_state *state = port->state; + struct circ_buf *xmit = &port->state->xmit; + intr_status = rd_regl(port, SIRFUART_INT_STATUS); + wr_regl(port, SIRFUART_INT_STATUS, intr_status); + intr_status &= rd_regl(port, SIRFUART_INT_EN); + if (unlikely(intr_status & (SIRFUART_ERR_INT_STAT))) { + if (intr_status & SIRFUART_RXD_BREAK) { + if (uart_handle_break(port)) + goto recv_char; + uart_insert_char(port, intr_status, + SIRFUART_RX_OFLOW, 0, TTY_BREAK); + return IRQ_HANDLED; + } + if (intr_status & SIRFUART_RX_OFLOW) + port->icount.overrun++; + if (intr_status & SIRFUART_FRM_ERR) { + port->icount.frame++; + flag = TTY_FRAME; + } + if (intr_status & SIRFUART_PARITY_ERR) + flag = TTY_PARITY; + wr_regl(port, SIRFUART_RX_FIFO_OP, SIRFUART_RX_FIFO_RESET); + wr_regl(port, SIRFUART_RX_FIFO_OP, 0); + wr_regl(port, SIRFUART_RX_FIFO_OP, SIRFUART_RX_FIFO_START); + intr_status &= port->read_status_mask; + uart_insert_char(port, intr_status, + SIRFUART_RX_OFLOW_INT, 0, flag); + } +recv_char: + if (intr_status & SIRFUART_CTS_INT_EN) { + cts_status = !(rd_regl(port, SIRFUART_AFC_CTRL) & + SIRFUART_CTS_IN_STATUS); + if (cts_status != 0) { + uart_handle_cts_change(port, 1); + } else { + uart_handle_cts_change(port, 0); + wake_up_interruptible(&state->port.delta_msr_wait); + } + } + if (intr_status & SIRFUART_RX_IO_INT_EN) + sirfsoc_uart_pio_rx_chars(port, SIRFSOC_UART_IO_RX_MAX_CNT); + if (intr_status & SIRFUART_TX_INT_EN) { + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + return IRQ_HANDLED; + } else { + sirfsoc_uart_pio_tx_chars(sirfport, + SIRFSOC_UART_IO_TX_REASONABLE_CNT); + if ((uart_circ_empty(xmit)) && + (rd_regl(port, SIRFUART_TX_FIFO_STATUS) & + SIRFUART_FIFOEMPTY_MASK(port))) + sirfsoc_uart_stop_tx(port); + } + } + return IRQ_HANDLED; +} + +static void sirfsoc_uart_start_rx(struct uart_port *port) +{ + unsigned long regv; + regv = rd_regl(port, SIRFUART_INT_EN); + wr_regl(port, SIRFUART_INT_EN, regv | SIRFUART_RX_IO_INT_EN); + wr_regl(port, SIRFUART_RX_FIFO_OP, SIRFUART_RX_FIFO_RESET); + wr_regl(port, SIRFUART_RX_FIFO_OP, 0); + wr_regl(port, SIRFUART_RX_FIFO_OP, SIRFUART_RX_FIFO_START); +} + +static unsigned int +sirfsoc_calc_sample_div(unsigned long baud_rate, + unsigned long ioclk_rate, unsigned long *setted_baud) +{ + unsigned long min_delta = ~0UL; + unsigned short sample_div; + unsigned int regv = 0; + unsigned long ioclk_div; + unsigned long baud_tmp; + int temp_delta; + + for (sample_div = SIRF_MIN_SAMPLE_DIV; + sample_div <= SIRF_MAX_SAMPLE_DIV; sample_div++) { + ioclk_div = (ioclk_rate / (baud_rate * (sample_div + 1))) - 1; + if (ioclk_div > SIRF_IOCLK_DIV_MAX) + continue; + baud_tmp = ioclk_rate / ((ioclk_div + 1) * (sample_div + 1)); + temp_delta = baud_tmp - baud_rate; + temp_delta = (temp_delta > 0) ? temp_delta : -temp_delta; + if (temp_delta < min_delta) { + regv = regv & (~SIRF_IOCLK_DIV_MASK); + regv = regv | ioclk_div; + regv = regv & (~SIRF_SAMPLE_DIV_MASK); + regv = regv | (sample_div << SIRF_SAMPLE_DIV_SHIFT); + min_delta = temp_delta; + *setted_baud = baud_tmp; + } + } + return regv; +} + +static void sirfsoc_uart_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + unsigned long ioclk_rate; + unsigned long config_reg = 0; + unsigned long baud_rate; + unsigned long setted_baud; + unsigned long flags; + unsigned long ic; + unsigned int clk_div_reg = 0; + unsigned long temp_reg_val; + unsigned long rx_time_out; + int threshold_div; + int temp; + + ioclk_rate = 150000000; + switch (termios->c_cflag & CSIZE) { + default: + case CS8: + config_reg |= SIRFUART_DATA_BIT_LEN_8; + break; + case CS7: + config_reg |= SIRFUART_DATA_BIT_LEN_7; + break; + case CS6: + config_reg |= SIRFUART_DATA_BIT_LEN_6; + break; + case CS5: + config_reg |= SIRFUART_DATA_BIT_LEN_5; + break; + } + if (termios->c_cflag & CSTOPB) + config_reg |= SIRFUART_STOP_BIT_LEN_2; + baud_rate = uart_get_baud_rate(port, termios, old, 0, 4000000); + spin_lock_irqsave(&port->lock, flags); + port->read_status_mask = SIRFUART_RX_OFLOW_INT; + port->ignore_status_mask = 0; + /* read flags */ + if (termios->c_iflag & INPCK) + port->read_status_mask |= + SIRFUART_FRM_ERR_INT | SIRFUART_PARITY_ERR_INT; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= SIRFUART_RXD_BREAK_INT; + /* ignore flags */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= + SIRFUART_FRM_ERR_INT | SIRFUART_PARITY_ERR_INT; + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= SIRFUART_DUMMY_READ; + /* enable parity if PARENB is set*/ + if (termios->c_cflag & PARENB) { + if (termios->c_cflag & CMSPAR) { + if (termios->c_cflag & PARODD) + config_reg |= SIRFUART_STICK_BIT_MARK; + else + config_reg |= SIRFUART_STICK_BIT_SPACE; + } else if (termios->c_cflag & PARODD) { + config_reg |= SIRFUART_STICK_BIT_ODD; + } else { + config_reg |= SIRFUART_STICK_BIT_EVEN; + } + } + /* Hardware Flow Control Settings */ + if (UART_ENABLE_MS(port, termios->c_cflag)) { + if (!sirfport->ms_enabled) + sirfsoc_uart_enable_ms(port); + } else { + if (sirfport->ms_enabled) + sirfsoc_uart_disable_ms(port); + } + + /* common rate: fast calculation */ + for (ic = 0; ic < SIRF_BAUD_RATE_SUPPORT_NR; ic++) + if (baud_rate == baudrate_to_regv[ic].baud_rate) + clk_div_reg = baudrate_to_regv[ic].reg_val; + setted_baud = baud_rate; + /* arbitary rate setting */ + if (unlikely(clk_div_reg == 0)) + clk_div_reg = sirfsoc_calc_sample_div(baud_rate, ioclk_rate, + &setted_baud); + wr_regl(port, SIRFUART_DIVISOR, clk_div_reg); + + if (tty_termios_baud_rate(termios)) + tty_termios_encode_baud_rate(termios, setted_baud, setted_baud); + + /* set receive timeout */ + rx_time_out = SIRFSOC_UART_RX_TIMEOUT(baud_rate, 20000); + rx_time_out = (rx_time_out > 0xFFFF) ? 0xFFFF : rx_time_out; + config_reg |= SIRFUART_RECV_TIMEOUT(rx_time_out); + temp_reg_val = rd_regl(port, SIRFUART_TX_FIFO_OP); + wr_regl(port, SIRFUART_RX_FIFO_OP, 0); + wr_regl(port, SIRFUART_TX_FIFO_OP, + temp_reg_val & ~SIRFUART_TX_FIFO_START); + wr_regl(port, SIRFUART_TX_DMA_IO_CTRL, SIRFUART_TX_MODE_IO); + wr_regl(port, SIRFUART_RX_DMA_IO_CTRL, SIRFUART_RX_MODE_IO); + wr_regl(port, SIRFUART_LINE_CTRL, config_reg); + + /* Reset Rx/Tx FIFO Threshold level for proper baudrate */ + if (baud_rate < 1000000) + threshold_div = 1; + else + threshold_div = 2; + temp = port->line == 1 ? 16 : 64; + wr_regl(port, SIRFUART_TX_FIFO_CTRL, temp / threshold_div); + wr_regl(port, SIRFUART_RX_FIFO_CTRL, temp / threshold_div); + temp_reg_val |= SIRFUART_TX_FIFO_START; + wr_regl(port, SIRFUART_TX_FIFO_OP, temp_reg_val); + uart_update_timeout(port, termios->c_cflag, baud_rate); + sirfsoc_uart_start_rx(port); + wr_regl(port, SIRFUART_TX_RX_EN, SIRFUART_TX_EN | SIRFUART_RX_EN); + spin_unlock_irqrestore(&port->lock, flags); +} + +static void startup_uart_controller(struct uart_port *port) +{ + unsigned long temp_regv; + int temp; + temp_regv = rd_regl(port, SIRFUART_TX_DMA_IO_CTRL); + wr_regl(port, SIRFUART_TX_DMA_IO_CTRL, temp_regv | SIRFUART_TX_MODE_IO); + temp_regv = rd_regl(port, SIRFUART_RX_DMA_IO_CTRL); + wr_regl(port, SIRFUART_RX_DMA_IO_CTRL, temp_regv | SIRFUART_RX_MODE_IO); + wr_regl(port, SIRFUART_TX_DMA_IO_LEN, 0); + wr_regl(port, SIRFUART_RX_DMA_IO_LEN, 0); + wr_regl(port, SIRFUART_TX_RX_EN, SIRFUART_RX_EN | SIRFUART_TX_EN); + wr_regl(port, SIRFUART_TX_FIFO_OP, SIRFUART_TX_FIFO_RESET); + wr_regl(port, SIRFUART_TX_FIFO_OP, 0); + wr_regl(port, SIRFUART_RX_FIFO_OP, SIRFUART_RX_FIFO_RESET); + wr_regl(port, SIRFUART_RX_FIFO_OP, 0); + temp = port->line == 1 ? 16 : 64; + wr_regl(port, SIRFUART_TX_FIFO_CTRL, temp); + wr_regl(port, SIRFUART_RX_FIFO_CTRL, temp); +} + +static int sirfsoc_uart_startup(struct uart_port *port) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + unsigned int index = port->line; + int ret; + set_irq_flags(port->irq, IRQF_VALID | IRQF_NOAUTOEN); + ret = request_irq(port->irq, + sirfsoc_uart_isr, + 0, + SIRFUART_PORT_NAME, + sirfport); + if (ret != 0) { + dev_err(port->dev, "UART%d request IRQ line (%d) failed.\n", + index, port->irq); + goto irq_err; + } + startup_uart_controller(port); + enable_irq(port->irq); +irq_err: + return ret; +} + +static void sirfsoc_uart_shutdown(struct uart_port *port) +{ + struct sirfsoc_uart_port *sirfport = to_sirfport(port); + wr_regl(port, SIRFUART_INT_EN, 0); + free_irq(port->irq, sirfport); + if (sirfport->ms_enabled) { + sirfsoc_uart_disable_ms(port); + sirfport->ms_enabled = 0; + } +} + +static const char *sirfsoc_uart_type(struct uart_port *port) +{ + return port->type == SIRFSOC_PORT_TYPE ? SIRFUART_PORT_NAME : NULL; +} + +static int sirfsoc_uart_request_port(struct uart_port *port) +{ + void *ret; + ret = request_mem_region(port->mapbase, + SIRFUART_MAP_SIZE, SIRFUART_PORT_NAME); + return ret ? 0 : -EBUSY; +} + +static void sirfsoc_uart_release_port(struct uart_port *port) +{ + release_mem_region(port->mapbase, SIRFUART_MAP_SIZE); +} + +static void sirfsoc_uart_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) { + port->type = SIRFSOC_PORT_TYPE; + sirfsoc_uart_request_port(port); + } +} + +static struct uart_ops sirfsoc_uart_ops = { + .tx_empty = sirfsoc_uart_tx_empty, + .get_mctrl = sirfsoc_uart_get_mctrl, + .set_mctrl = sirfsoc_uart_set_mctrl, + .stop_tx = sirfsoc_uart_stop_tx, + .start_tx = sirfsoc_uart_start_tx, + .stop_rx = sirfsoc_uart_stop_rx, + .enable_ms = sirfsoc_uart_enable_ms, + .break_ctl = sirfsoc_uart_break_ctl, + .startup = sirfsoc_uart_startup, + .shutdown = sirfsoc_uart_shutdown, + .set_termios = sirfsoc_uart_set_termios, + .type = sirfsoc_uart_type, + .release_port = sirfsoc_uart_release_port, + .request_port = sirfsoc_uart_request_port, + .config_port = sirfsoc_uart_config_port, +}; + +#ifdef CONFIG_SERIAL_SIRFSOC_CONSOLE +static int __init sirfsoc_uart_console_setup(struct console *co, char *options) +{ + unsigned int baud = 115200; + unsigned int bits = 8; + unsigned int parity = 'n'; + unsigned int flow = 'n'; + struct uart_port *port = &sirfsoc_uart_ports[co->index].port; + + if (co->index < 0 || co->index >= SIRFSOC_UART_NR) + return -EINVAL; + + if (!port->mapbase) + return -ENODEV; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + port->cons = co; + return uart_set_options(port, co, baud, parity, bits, flow); +} + +static void sirfsoc_uart_console_putchar(struct uart_port *port, int ch) +{ + while (rd_regl(port, + SIRFUART_TX_FIFO_STATUS) & SIRFUART_FIFOFULL_MASK(port)) + cpu_relax(); + wr_regb(port, SIRFUART_TX_FIFO_DATA, ch); +} + +static void sirfsoc_uart_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct uart_port *port = &sirfsoc_uart_ports[co->index].port; + uart_console_write(port, s, count, sirfsoc_uart_console_putchar); +} + +static struct console sirfsoc_uart_console = { + .name = SIRFSOC_UART_NAME, + .device = uart_console_device, + .flags = CON_PRINTBUFFER, + .index = -1, + .write = sirfsoc_uart_console_write, + .setup = sirfsoc_uart_console_setup, + .data = &sirfsoc_uart_drv, +}; + +static int __init sirfsoc_uart_console_init(void) +{ + register_console(&sirfsoc_uart_console); + return 0; +} +console_initcall(sirfsoc_uart_console_init); +#endif + +static struct uart_driver sirfsoc_uart_drv = { + .owner = THIS_MODULE, + .driver_name = SIRFUART_PORT_NAME, + .nr = SIRFSOC_UART_NR, + .dev_name = SIRFSOC_UART_NAME, + .major = SIRFSOC_UART_MAJOR, + .minor = SIRFSOC_UART_MINOR, +#ifdef CONFIG_SERIAL_SIRFSOC_CONSOLE + .cons = &sirfsoc_uart_console, +#else + .cons = NULL, +#endif +}; + +int sirfsoc_uart_probe(struct platform_device *pdev) +{ + struct sirfsoc_uart_port *sirfport; + struct uart_port *port; + struct resource *res; + int ret; + + if (of_property_read_u32(pdev->dev.of_node, "cell-index", &pdev->id)) { + dev_err(&pdev->dev, + "Unable to find cell-index in uart node.\n"); + ret = -EFAULT; + goto err; + } + + sirfport = &sirfsoc_uart_ports[pdev->id]; + port = &sirfport->port; + port->dev = &pdev->dev; + port->private_data = sirfport; + + if (of_find_property(pdev->dev.of_node, "hw_flow_ctrl", NULL)) + sirfport->hw_flow_ctrl = 1; + + if (of_property_read_u32(pdev->dev.of_node, + "fifosize", + &port->fifosize)) { + dev_err(&pdev->dev, + "Unable to find fifosize in uart node.\n"); + ret = -EFAULT; + goto err; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Insufficient resources.\n"); + ret = -EFAULT; + goto err; + } + port->mapbase = res->start; + port->membase = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!port->membase) { + dev_err(&pdev->dev, "Cannot remap resource.\n"); + ret = -ENOMEM; + goto err; + } + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + dev_err(&pdev->dev, "Insufficient resources.\n"); + ret = -EFAULT; + goto irq_err; + } + port->irq = res->start; + + if (sirfport->hw_flow_ctrl) { + sirfport->pmx = pinmux_get(&pdev->dev, NULL); + ret = IS_ERR(sirfport->pmx); + if (ret) + goto pmx_err; + + pinmux_enable(sirfport->pmx); + } + + port->ops = &sirfsoc_uart_ops; + spin_lock_init(&port->lock); + + platform_set_drvdata(pdev, sirfport); + ret = uart_add_one_port(&sirfsoc_uart_drv, port); + if (ret != 0) { + dev_err(&pdev->dev, "Cannot add UART port(%d).\n", pdev->id); + goto port_err; + } + + return 0; + +port_err: + platform_set_drvdata(pdev, NULL); + if (sirfport->hw_flow_ctrl) { + pinmux_disable(sirfport->pmx); + pinmux_put(sirfport->pmx); + } +pmx_err: +irq_err: + devm_iounmap(&pdev->dev, port->membase); +err: + return ret; +} + +static int sirfsoc_uart_remove(struct platform_device *pdev) +{ + struct sirfsoc_uart_port *sirfport = platform_get_drvdata(pdev); + struct uart_port *port = &sirfport->port; + platform_set_drvdata(pdev, NULL); + if (sirfport->hw_flow_ctrl) { + pinmux_disable(sirfport->pmx); + pinmux_put(sirfport->pmx); + } + devm_iounmap(&pdev->dev, port->membase); + uart_remove_one_port(&sirfsoc_uart_drv, port); + return 0; +} + +static int +sirfsoc_uart_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct sirfsoc_uart_port *sirfport = platform_get_drvdata(pdev); + struct uart_port *port = &sirfport->port; + uart_suspend_port(&sirfsoc_uart_drv, port); + return 0; +} + +static int sirfsoc_uart_resume(struct platform_device *pdev) +{ + struct sirfsoc_uart_port *sirfport = platform_get_drvdata(pdev); + struct uart_port *port = &sirfport->port; + uart_resume_port(&sirfsoc_uart_drv, port); + return 0; +} + +static struct of_device_id sirfsoc_uart_ids[] __devinitdata = { + { .compatible = "sirf,prima2-uart", }, + {} +}; +MODULE_DEVICE_TABLE(of, sirfsoc_serial_of_match); + +static struct platform_driver sirfsoc_uart_driver = { + .probe = sirfsoc_uart_probe, + .remove = __devexit_p(sirfsoc_uart_remove), + .suspend = sirfsoc_uart_suspend, + .resume = sirfsoc_uart_resume, + .driver = { + .name = SIRFUART_PORT_NAME, + .owner = THIS_MODULE, + .of_match_table = sirfsoc_uart_ids, + }, +}; + +static int __init sirfsoc_uart_init(void) +{ + int ret = 0; + + ret = uart_register_driver(&sirfsoc_uart_drv); + if (ret) + goto out; + + ret = platform_driver_register(&sirfsoc_uart_driver); + if (ret) + uart_unregister_driver(&sirfsoc_uart_drv); +out: + return ret; +} +module_init(sirfsoc_uart_init); + +static void __exit sirfsoc_uart_exit(void) +{ + platform_driver_unregister(&sirfsoc_uart_driver); + uart_unregister_driver(&sirfsoc_uart_drv); +} +module_exit(sirfsoc_uart_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bin Shi <Bin.Shi@csr.com>, Rong Wang<Rong.Wang@csr.com>"); +MODULE_DESCRIPTION("CSR SiRFprimaII Uart Driver"); |