Add a full sc9836-uart driver for SC9836 SoC which is based on the spreadtrum sharkl64 platform. This driver also support earlycon. Signed-off-by: Chunyan Zhang <chunyan.zhang@xxxxxxxxxxxxxx> Signed-off-by: Orson Zhai <orson.zhai@xxxxxxxxxxxxxx> Originally-by: Lanqing Liu <lanqing.liu@xxxxxxxxxxxxxx> --- Documentation/devices.txt | 3 + drivers/tty/serial/Kconfig | 23 ++ drivers/tty/serial/Makefile | 1 + drivers/tty/serial/sprd_serial.c | 752 ++++++++++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 5 files changed, 782 insertions(+) create mode 100644 drivers/tty/serial/sprd_serial.c diff --git a/Documentation/devices.txt b/Documentation/devices.txt index 87b4c5e..1da0432 100644 --- a/Documentation/devices.txt +++ b/Documentation/devices.txt @@ -2816,6 +2816,9 @@ Your cooperation is appreciated. 210 = /dev/ttyMAX1 MAX3100 serial port 1 211 = /dev/ttyMAX2 MAX3100 serial port 2 212 = /dev/ttyMAX3 MAX3100 serial port 3 + 213 = /dev/ttySPX0 SPRD serial port 0 + ... + 216 = /dev/ttySPX3 SPRD serial port 3 205 char Low-density serial ports (alternate device) 0 = /dev/culu0 Callout device for ttyLU0 diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 649b784..2c2cf60 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1573,6 +1573,29 @@ config SERIAL_MEN_Z135 This driver can also be build as a module. If so, the module will be called men_z135_uart.ko +config SERIAL_SPRD + tristate "Support for SPRD serial" + depends on ARCH_SPRD + select SERIAL_CORE + help + This enables the driver for the Spreadtrum's serial. + +config SERIAL_SPRD_NR + int "Maximum number of sprd serial ports" + depends on SERIAL_SPRD + default "4" + +config SERIAL_SPRD_CONSOLE + bool "SPRD UART console support" + depends on SERIAL_SPRD=y + select SERIAL_CORE_CONSOLE + select SERIAL_EARLYCON + help + Support for early debug console using Spreadtrum's serial. This enables + the console before standard serial driver is probed. This is enabled + with "earlycon" on the kernel command line. The console is + enabled when early_param is processed. + endmenu config SERIAL_MCTRL_GPIO diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 9a548ac..4801aca 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -93,6 +93,7 @@ obj-$(CONFIG_SERIAL_ARC) += arc_uart.o obj-$(CONFIG_SERIAL_RP2) += rp2.o obj-$(CONFIG_SERIAL_FSL_LPUART) += fsl_lpuart.o obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o +obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o # GPIOLIB helpers for modem control lines obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o diff --git a/drivers/tty/serial/sprd_serial.c b/drivers/tty/serial/sprd_serial.c new file mode 100644 index 0000000..58214c8 --- /dev/null +++ b/drivers/tty/serial/sprd_serial.c @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/tty.h> +#include <linux/ioport.h> +#include <linux/console.h> +#include <linux/platform_device.h> +#include <linux/tty_flip.h> +#include <linux/serial_core.h> +#include <linux/serial.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <asm/irq.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/clk.h> + +/* device name */ +#define UART_NR_MAX CONFIG_SERIAL_SPRD_NR +#define SPRD_TTY_NAME "ttySPX" +#define SPRD_TTY_MAJOR 204 +#define SPRD_TTY_MINOR_START 213 +#define SPRD_FIFO_SIZE 128 +#define SPRD_DEF_RATE 26000000 + +/* the offset of serial registers and BITs for them */ +/* data registers */ +#define SPRD_TXD 0x0000 +#define SPRD_RXD 0x0004 + +/* line status register and its BITs */ +#define SPRD_LSR 0x0008 +#define SPRD_LSR_OE BIT(4) +#define SPRD_LSR_FE BIT(3) +#define SPRD_LSR_PE BIT(2) +#define SPRD_LSR_BI BIT(7) +#define SPRD_LSR_TX_OVER BIT(15) + +/* data number in TX and RX fifo */ +#define SPRD_STS1 0x000C + +/* interrupt enable register and its BITs */ +#define SPRD_IEN 0x0010 +#define SPRD_IEN_RX_FULL BIT(0) +#define SPRD_IEN_TX_EMPTY BIT(1) +#define SPRD_IEN_BREAK_DETECT BIT(7) +#define SPRD_IEN_TIMEOUT BIT(13) + +/* interrupt clear register */ +#define SPRD_ICLR 0x0014 + +/* line control register */ +#define SPRD_LCR 0x0018 +#define SPRD_LCR_STOP_1BIT 0x10 +#define SPRD_LCR_STOP_2BIT 0x30 +#define SPRD_LCR_DATA_LEN (BIT(2) | BIT(3)) +#define SPRD_LCR_DATA_LEN5 0x0 +#define SPRD_LCR_DATA_LEN6 0x4 +#define SPRD_LCR_DATA_LEN7 0x8 +#define SPRD_LCR_DATA_LEN8 0xc +#define SPRD_LCR_PARITY (BIT(0) | BIT(1)) +#define SPRD_LCR_PARITY_EN 0x2 +#define SPRD_LCR_EVEN_PAR 0x0 +#define SPRD_LCR_ODD_PAR 0x1 + +/* control register 1 */ +#define SPRD_CTL1 0x001C +#define RX_HW_FLOW_CTL_THLD BIT(6) +#define RX_HW_FLOW_CTL_EN BIT(7) +#define TX_HW_FLOW_CTL_EN BIT(8) + +/* fifo threshold register */ +#define SPRD_CTL2 0x0020 +#define THLD_TX_EMPTY 0x40 +#define THLD_RX_FULL 0x40 + +/* config baud rate register */ +#define SPRD_CLKD0 0x0024 +#define SPRD_CLKD1 0x0028 + +/* interrupt mask status register */ +#define SPRD_IMSR 0x002C +#define SPRD_IMSR_RX_FIFO_FULL BIT(0) +#define SPRD_IMSR_TX_FIFO_EMPTY BIT(1) +#define SPRD_IMSR_BREAK_DETECT BIT(7) +#define SPRD_IMSR_TIMEOUT BIT(13) + +struct reg_backup { + uint32_t ien; + uint32_t ctrl0; + uint32_t ctrl1; + uint32_t ctrl2; + uint32_t clkd0; + uint32_t clkd1; + uint32_t dspwait; +}; +struct sprd_uart_port { + struct uart_port port; + struct reg_backup reg_bak; + char name[16]; +}; +static struct sprd_uart_port *sprd_port[UART_NR_MAX] = { NULL }; + +static inline unsigned int serial_in(struct uart_port *port, int offset) +{ + return readl_relaxed(port->membase + offset); +} + +static inline void serial_out(struct uart_port *port, int offset, int value) +{ + writel_relaxed(value, port->membase + offset); +} + +static unsigned int sprd_tx_empty(struct uart_port *port) +{ + if (serial_in(port, SPRD_STS1) & 0xff00) + return 0; + else + return TIOCSER_TEMT; +} + +static unsigned int sprd_get_mctrl(struct uart_port *port) +{ + return TIOCM_DSR | TIOCM_CTS; +} + +static void sprd_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* nothing to do */ +} + +static void sprd_stop_tx(struct uart_port *port) +{ + unsigned int ien, iclr; + + iclr = serial_in(port, SPRD_ICLR); + ien = serial_in(port, SPRD_IEN); + + iclr |= SPRD_IEN_TX_EMPTY; + ien &= ~SPRD_IEN_TX_EMPTY; + + serial_out(port, SPRD_ICLR, iclr); + serial_out(port, SPRD_IEN, ien); +} + +static void sprd_start_tx(struct uart_port *port) +{ + unsigned int ien; + + ien = serial_in(port, SPRD_IEN); + if (!(ien & SPRD_IEN_TX_EMPTY)) { + ien |= SPRD_IEN_TX_EMPTY; + serial_out(port, SPRD_IEN, ien); + } +} + +static void sprd_stop_rx(struct uart_port *port) +{ + unsigned int ien, iclr; + + iclr = serial_in(port, SPRD_ICLR); + ien = serial_in(port, SPRD_IEN); + + ien &= ~(SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT); + iclr |= SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT; + + serial_out(port, SPRD_IEN, ien); + serial_out(port, SPRD_ICLR, iclr); +} + +/* The Sprd serial does not support this function. */ +static void sprd_break_ctl(struct uart_port *port, int break_state) +{ + /* nothing to do */ +} + +static inline int handle_lsr_errors(struct uart_port *port, + unsigned int *flag, unsigned int *lsr) +{ + int ret = 0; + + /* stastics */ + if (*lsr & SPRD_LSR_BI) { + *lsr &= ~(SPRD_LSR_FE | SPRD_LSR_PE); + port->icount.brk++; + ret = uart_handle_break(port); + if (ret) + return ret; + } else if (*lsr & SPRD_LSR_PE) + port->icount.parity++; + else if (*lsr & SPRD_LSR_FE) + port->icount.frame++; + if (*lsr & SPRD_LSR_OE) + port->icount.overrun++; + + /* mask off conditions which should be ignored */ + *lsr &= port->read_status_mask; + if (*lsr & SPRD_LSR_BI) + *flag = TTY_BREAK; + else if (*lsr & SPRD_LSR_PE) + *flag = TTY_PARITY; + else if (*lsr & SPRD_LSR_FE) + *flag = TTY_FRAME; + + return ret; +} + +static inline void sprd_rx(int irq, void *dev_id) +{ + struct uart_port *port = (struct uart_port *)dev_id; + struct tty_port *tty = &port->state->port; + unsigned int ch, flag, lsr, max_count = 2048; + + while ((serial_in(port, SPRD_STS1) & 0x00ff) && max_count--) { + lsr = serial_in(port, SPRD_LSR); + ch = serial_in(port, SPRD_RXD); + flag = TTY_NORMAL; + port->icount.rx++; + + if (unlikely(lsr & (SPRD_LSR_BI | SPRD_LSR_PE + | SPRD_LSR_FE | SPRD_LSR_OE))) + if (handle_lsr_errors(port, &lsr, &flag)) + continue; + if (uart_handle_sysrq_char(port, ch)) + continue; + + uart_insert_char(port, lsr, SPRD_LSR_OE, ch, flag); + } + + tty_flip_buffer_push(tty); +} + +static inline void sprd_tx(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + struct circ_buf *xmit = &port->state->xmit; + int count; + + if (port->x_char) { + serial_out(port, SPRD_TXD, port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + sprd_stop_tx(port); + return; + } + count = THLD_TX_EMPTY; + do { + serial_out(port, SPRD_TXD, xmit->buf[xmit->tail]); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + port->icount.tx++; + if (uart_circ_empty(xmit)) + break; + } while (--count > 0); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(port); + + if (uart_circ_empty(xmit)) + sprd_stop_tx(port); +} + +/* + *this handles the interrupt from one port + */ +static irqreturn_t sprd_handle_irq(int irq, void *dev_id) +{ + struct uart_port *port = (struct uart_port *)dev_id; + u32 ims; + + ims = serial_in(port, SPRD_IMSR); + + serial_out(port, SPRD_ICLR, ~0); + + if (ims & (SPRD_IMSR_RX_FIFO_FULL | + SPRD_IMSR_BREAK_DETECT | SPRD_IMSR_TIMEOUT)) { + sprd_rx(irq, port); + } + if (ims & SPRD_IMSR_TX_FIFO_EMPTY) + sprd_tx(irq, port); + + return IRQ_HANDLED; +} + +static int sprd_startup(struct uart_port *port) +{ + int ret = 0; + unsigned int ien, ctrl1; + struct sprd_uart_port *sp; + + serial_out(port, SPRD_CTL2, ((THLD_TX_EMPTY << 8) | THLD_RX_FULL)); + + /* clear rx fifo */ + while (serial_in(port, SPRD_STS1) & 0x00ff) + serial_in(port, SPRD_RXD); + + /* clear tx fifo */ + while (serial_in(port, SPRD_STS1) & 0xff00) + ; + + /* clear interrupt */ + serial_out(port, SPRD_IEN, 0x0); + serial_out(port, SPRD_ICLR, ~0); + + /* allocate irq */ + sp = container_of(port, struct sprd_uart_port, port); + snprintf(sp->name, sizeof(sp->name), "sprd_serial%d", port->line); + ret = devm_request_irq(port->dev, port->irq, sprd_handle_irq, + IRQF_SHARED, sp->name, port); + if (ret) { + dev_err(port->dev, "fail to request serial irq %d\n", + port->irq); + return ret; + } + ctrl1 = serial_in(port, SPRD_CTL1); + ctrl1 |= 0x3e00 | THLD_RX_FULL; + serial_out(port, SPRD_CTL1, ctrl1); + + /* enable interrupt */ + spin_lock(&port->lock); + ien = serial_in(port, SPRD_IEN); + ien |= SPRD_IEN_RX_FULL | SPRD_IEN_BREAK_DETECT | SPRD_IEN_TIMEOUT; + serial_out(port, SPRD_IEN, ien); + spin_unlock(&port->lock); + + return 0; +} + +static void sprd_shutdown(struct uart_port *port) +{ + serial_out(port, SPRD_IEN, 0x0); + serial_out(port, SPRD_ICLR, ~0); + devm_free_irq(port->dev, port->irq, port); +} + +static void sprd_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned int baud, quot; + unsigned int lcr, fc; + + /* ask the core to calculate the divisor for us */ + baud = uart_get_baud_rate(port, termios, old, 1200, 3000000); + + quot = (unsigned int)((port->uartclk + baud / 2) / baud); + + /* set data length */ + lcr = serial_in(port, SPRD_LCR); + lcr &= ~SPRD_LCR_DATA_LEN; + switch (termios->c_cflag & CSIZE) { + case CS5: + lcr |= SPRD_LCR_DATA_LEN5; + break; + case CS6: + lcr |= SPRD_LCR_DATA_LEN6; + break; + case CS7: + lcr |= SPRD_LCR_DATA_LEN7; + break; + case CS8: + default: + lcr |= SPRD_LCR_DATA_LEN8; + break; + } + + /* calculate stop bits */ + lcr &= ~(SPRD_LCR_STOP_1BIT | SPRD_LCR_STOP_2BIT); + if (termios->c_cflag & CSTOPB) + lcr |= SPRD_LCR_STOP_2BIT; + else + lcr |= SPRD_LCR_STOP_1BIT; + + /* calculate parity */ + lcr &= ~SPRD_LCR_PARITY; + if (termios->c_cflag & PARENB) { + lcr |= SPRD_LCR_PARITY_EN; + if (termios->c_cflag & PARODD) + lcr |= SPRD_LCR_ODD_PAR; + else + lcr |= SPRD_LCR_EVEN_PAR; + } + + /* change the port state. */ + /* update the per-port timeout */ + uart_update_timeout(port, termios->c_cflag, baud); + + port->read_status_mask = SPRD_LSR_OE; + if (termios->c_iflag & INPCK) + port->read_status_mask |= SPRD_LSR_FE | SPRD_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= SPRD_LSR_BI; + + /* characters to ignore */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= SPRD_LSR_PE | SPRD_LSR_FE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= SPRD_LSR_BI; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= SPRD_LSR_OE; + } + + /* flow control */ + fc = serial_in(port, SPRD_CTL1); + fc &= ~(RX_HW_FLOW_CTL_THLD | RX_HW_FLOW_CTL_EN | TX_HW_FLOW_CTL_EN); + if (termios->c_cflag & CRTSCTS) { + fc |= RX_HW_FLOW_CTL_THLD; + fc |= RX_HW_FLOW_CTL_EN; + fc |= TX_HW_FLOW_CTL_EN; + } + + /* clock divider bit0~bit15 */ + serial_out(port, SPRD_CLKD0, quot & 0xffff); + + /* clock divider bit16~bit20 */ + serial_out(port, SPRD_CLKD1, (quot & 0x1f0000) >> 16); + serial_out(port, SPRD_LCR, lcr); + fc |= 0x3e00 | THLD_RX_FULL; + serial_out(port, SPRD_CTL1, fc); +} + +static const char *sprd_type(struct uart_port *port) +{ + return "SPX"; +} + +static void sprd_release_port(struct uart_port *port) +{ + /* nothing to do */ +} + +static int sprd_request_port(struct uart_port *port) +{ + return 0; +} + +static void sprd_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_SPRD; +} + +static int sprd_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (unlikely(ser->type != PORT_SPRD)) + return -EINVAL; + if (unlikely(port->irq != ser->irq)) + return -EINVAL; + return 0; +} + +static struct uart_ops serial_sprd_ops = { + .tx_empty = sprd_tx_empty, + .get_mctrl = sprd_get_mctrl, + .set_mctrl = sprd_set_mctrl, + .stop_tx = sprd_stop_tx, + .start_tx = sprd_start_tx, + .stop_rx = sprd_stop_rx, + .break_ctl = sprd_break_ctl, + .startup = sprd_startup, + .shutdown = sprd_shutdown, + .set_termios = sprd_set_termios, + .type = sprd_type, + .release_port = sprd_release_port, + .request_port = sprd_request_port, + .config_port = sprd_config_port, + .verify_port = sprd_verify_port, +}; + +#ifdef CONFIG_SERIAL_SPRD_CONSOLE +static inline void wait_for_xmitr(struct uart_port *port) +{ + unsigned int status, tmout = 10000; + + /* wait up to 10ms for the character(s) to be sent */ + do { + status = serial_in(port, SPRD_STS1); + if (--tmout == 0) + break; + udelay(1); + } while (status & 0xff00); +} + +static void sprd_console_putchar(struct uart_port *port, int ch) +{ + wait_for_xmitr(port); + serial_out(port, SPRD_TXD, ch); +} + +static void sprd_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct uart_port *port = (struct uart_port *)sprd_port[co->index]; + int ien; + int locked = 1; + + if (oops_in_progress) + locked = spin_trylock(&port->lock); + else + spin_lock(&port->lock); + /* save the IEN then disable the interrupts */ + ien = serial_in(port, SPRD_IEN); + serial_out(port, SPRD_IEN, 0x0); + + uart_console_write(port, s, count, sprd_console_putchar); + + /* wait for transmitter to become empty and restore the IEN */ + wait_for_xmitr(port); + serial_out(port, SPRD_IEN, ien); + if (locked) + spin_unlock(&port->lock); +} + +static int __init sprd_console_setup(struct console *co, char *options) +{ + struct uart_port *port; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + if (unlikely(co->index >= UART_NR_MAX || co->index < 0)) + co->index = 0; + + port = (struct uart_port *)sprd_port[co->index]; + if (port == NULL) { + pr_info("srial port %d not yet initialized\n", co->index); + return -ENODEV; + } + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + + return uart_set_options(port, co, baud, parity, bits, flow); +} + +static struct uart_driver sprd_uart_driver; +static struct console sprd_console = { + .name = SPRD_TTY_NAME, + .write = sprd_console_write, + .device = uart_console_device, + .setup = sprd_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &sprd_uart_driver, +}; + +#define SPRD_CONSOLE (&sprd_console) + +/* Support for earlycon */ +static void sprd_putc(struct uart_port *port, int c) +{ + while (!(readl(port->membase + SPRD_LSR) & SPRD_LSR_TX_OVER)) + ; + writeb(c, port->membase + SPRD_TXD); +} + +static void sprd_early_write(struct console *con, const char *s, + unsigned n) +{ + struct earlycon_device *dev = con->data; + + uart_console_write(&dev->port, s, n, sprd_putc); +} + +static int __init sprd_early_console_setup( + struct earlycon_device *device, + const char *opt) +{ + if (!device->port.membase) + return -ENODEV; + + device->con->write = sprd_early_write; + return 0; +} + +EARLYCON_DECLARE(sprd_serial, sprd_early_console_setup); +OF_EARLYCON_DECLARE(sprd_serial, "sprd,sc9836-uart", + sprd_early_console_setup); + +#else /* !CONFIG_SERIAL_SPRD_CONSOLE */ +#define SPRD_CONSOLE NULL +#endif + +static struct uart_driver sprd_uart_driver = { + .owner = THIS_MODULE, + .driver_name = "sprd_serial", + .dev_name = SPRD_TTY_NAME, + .major = SPRD_TTY_MAJOR, + .minor = SPRD_TTY_MINOR_START, + .nr = UART_NR_MAX, + .cons = SPRD_CONSOLE, +}; + +static int sprd_probe(struct platform_device *pdev) +{ + struct resource *mem; + struct device_node *np = pdev->dev.of_node; + struct uart_port *up; + struct clk *clk; + int irq; + + + if (np) + pdev->id = of_alias_get_id(np, "serial"); + + if (unlikely(pdev->id < 0 || pdev->id >= UART_NR_MAX)) { + dev_err(&pdev->dev, "does not support id %d\n", pdev->id); + return -ENXIO; + } + + sprd_port[pdev->id] = devm_kzalloc(&pdev->dev, + sizeof(*sprd_port[pdev->id]), GFP_KERNEL); + if (!sprd_port[pdev->id]) + return -ENOMEM; + + up = (struct uart_port *)sprd_port[pdev->id]; + up->dev = &pdev->dev; + up->line = pdev->id; + up->type = PORT_SPRD; + up->iotype = SERIAL_IO_PORT; + up->uartclk = SPRD_DEF_RATE; + up->fifosize = SPRD_FIFO_SIZE; + up->ops = &serial_sprd_ops; + up->flags = ASYNC_BOOT_AUTOCONF; + + clk = devm_clk_get(&pdev->dev, NULL); + if (!IS_ERR(clk)) + up->uartclk = clk_get_rate(clk); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!mem)) { + dev_err(&pdev->dev, "not provide mem resource\n"); + return -ENODEV; + } + up->mapbase = mem->start; + up->membase = ioremap(mem->start, resource_size(mem)); + + irq = platform_get_irq(pdev, 0); + if (unlikely(irq < 0)) { + dev_err(&pdev->dev, "not provide irq resource\n"); + return -ENODEV; + } + up->irq = irq; + + platform_set_drvdata(pdev, up); + + return uart_add_one_port(&sprd_uart_driver, up); +} + +static int sprd_remove(struct platform_device *dev) +{ + struct uart_port *up = platform_get_drvdata(dev); + + return uart_remove_one_port(&sprd_uart_driver, up); +} + +static int sprd_suspend(struct platform_device *dev, pm_message_t state) +{ + int id = dev->id; + struct uart_port *port = (struct uart_port *)sprd_port[id]; + struct reg_backup *reg_bak = &(sprd_port[id]->reg_bak); + + reg_bak->ien = serial_in(port, SPRD_IEN); + reg_bak->ctrl0 = serial_in(port, SPRD_LCR); + reg_bak->ctrl1 = serial_in(port, SPRD_CTL1); + reg_bak->ctrl2 = serial_in(port, SPRD_CTL2); + reg_bak->clkd0 = serial_in(port, SPRD_CLKD0); + reg_bak->clkd1 = serial_in(port, SPRD_CLKD1); + + return 0; +} + +static int sprd_resume(struct platform_device *dev) +{ + int id = dev->id; + struct uart_port *port = (struct uart_port *)sprd_port[id]; + struct reg_backup *reg_bak = &(sprd_port[id]->reg_bak); + + serial_out(port, SPRD_LCR, reg_bak->ctrl0); + serial_out(port, SPRD_CTL1, reg_bak->ctrl1); + serial_out(port, SPRD_CTL2, reg_bak->ctrl2); + serial_out(port, SPRD_CLKD0, reg_bak->clkd0); + serial_out(port, SPRD_CLKD1, reg_bak->clkd1); + serial_out(port, SPRD_IEN, reg_bak->ien); + + return 0; +} + +static const struct of_device_id serial_ids[] = { + {.compatible = "sprd,sc9836-uart",}, + {} +}; + +static struct platform_driver sprd_platform_driver = { + .probe = sprd_probe, + .remove = sprd_remove, + .suspend = sprd_suspend, + .resume = sprd_resume, + .driver = { + .name = "sprd_serial", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(serial_ids), + }, +}; + +static int __init sprd_serial_init(void) +{ + int ret = 0; + + ret = uart_register_driver(&sprd_uart_driver); + if (unlikely(ret != 0)) + return ret; + + ret = platform_driver_register(&sprd_platform_driver); + if (unlikely(ret != 0)) + uart_unregister_driver(&sprd_uart_driver); + + return ret; +} + +static void __exit sprd_serial_exit(void) +{ + platform_driver_unregister(&sprd_platform_driver); + uart_unregister_driver(&sprd_uart_driver); +} + +module_init(sprd_serial_init); +module_exit(sprd_serial_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Spreadtrum SoC serial driver series"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 16ad852..d9a8c88 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -247,4 +247,7 @@ /* MESON */ #define PORT_MESON 109 +/* SPRD SERIAL */ +#define PORT_SPRD 110 + #endif /* _UAPILINUX_SERIAL_CORE_H */ -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html