diff options
Diffstat (limited to 'drivers/tty/serial/serial_sprd_dt.c')
| -rw-r--r-- | drivers/tty/serial/serial_sprd_dt.c | 1055 |
1 files changed, 1055 insertions, 0 deletions
diff --git a/drivers/tty/serial/serial_sprd_dt.c b/drivers/tty/serial/serial_sprd_dt.c new file mode 100644 index 00000000..8c9bdc1a --- /dev/null +++ b/drivers/tty/serial/serial_sprd_dt.c @@ -0,0 +1,1055 @@ +/* + * 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/kernel.h> +#include <linux/module.h> +#include <linux/tty.h> +#include <linux/ioport.h> +#include <linux/console.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/tty_flip.h> +#include <linux/serial_core.h> +#include <linux/serial.h> +#include <linux/termios.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <linux/err.h> +#include <linux/slab.h> +#ifndef CONFIG_64BIT +#include <soc/sprd/hardware.h> +#include <soc/sprd/serial_sprd.h> +#endif +#include <linux/clk.h> +#include <linux/wakelock.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#define IRQ_WAKEUP 0 + +#define UART_NR_MAX CONFIG_SERIAL_SPRD_UART_NR +#define SP_TTY_NAME "ttyS" +#define SP_TTY_MINOR_START 64 +#define SP_TTY_MAJOR TTY_MAJOR + +#define UART_CLK 48000000 + +/*offset*/ +#define ARM_UART_TXD 0x0000 +#define ARM_UART_RXD 0x0004 +#define ARM_UART_STS0 0x0008 +#define ARM_UART_STS1 0x000C +#define ARM_UART_IEN 0x0010 +#define ARM_UART_ICLR 0x0014 +#define ARM_UART_CTL0 0x0018 +#define ARM_UART_CTL1 0x001C +#define ARM_UART_CTL2 0x0020 +#define ARM_UART_CLKD0 0x0024 +#define ARM_UART_CLKD1 0x0028 +#define ARM_UART_STS2 0x002C +/*UART IRQ num*/ + +/*UART FIFO watermark*/ +#define SP_TX_FIFO 0x40 +#define SP_RX_FIFO 0x40 +/*UART IEN*/ +#define UART_IEN_RX_FIFO_FULL (0x1<<0) +#define UART_IEN_TX_FIFO_EMPTY (0x1<<1) +#define UART_IEN_BREAK_DETECT (0x1<<7) +#define UART_IEN_TIMEOUT (0x1<<13) + +/*data length*/ +#define UART_DATA_BIT (0x3<<2) +#define UART_DATA_5BIT (0x0<<2) +#define UART_DATA_6BIT (0x1<<2) +#define UART_DATA_7BIT (0x2<<2) +#define UART_DATA_8BIT (0x3<<2) +/*stop bit*/ +#define UART_STOP_1BIT (0x1<<4) +#define UART_STOP_2BIT (0x3<<4) +/*parity*/ +#define UART_PARITY 0x3 +#define UART_PARITY_EN 0x2 +#define UART_EVEN_PAR 0x0 +#define UART_ODD_PAR 0x1 +/*line status */ +#define UART_LSR_OE (0x1<<4) +#define UART_LSR_FE (0x1<<3) +#define UART_LSR_PE (0x1<<2) +#define UART_LSR_BI (0x1<<7) +#define UART_LSR_DR (0x1<<8) +/*flow control */ +#define RX_HW_FLOW_CTL_THRESHOLD 0x40 +#define RX_HW_FLOW_CTL_EN (0x1<<7) +#define TX_HW_FLOW_CTL_EN (0x1<<8) +/*status indicator*/ +#define UART_STS_RX_FIFO_FULL (0x1<<0) +#define UART_STS_TX_FIFO_EMPTY (0x1<<1) +#define UART_STS_BREAK_DETECT (0x1<<7) +#define UART_STS_TIMEOUT (0x1<<13) +/*baud rate*/ +#define BAUD_1200_48M 0x9C40 +#define BAUD_2400_48M 0x4E20 +#define BAUD_4800_48M 0x2710 +#define BAUD_9600_48M 0x1388 +#define BAUD_19200_48M 0x09C4 +#define BAUD_38400_48M 0x04E2 +#define BAUD_57600_48M 0x0314 +#define BAUD_115200_48M 0x01A0 +#define BAUD_230400_48M 0x00D0 +#define BAUD_460800_48M 0x0068 +#define BAUD_921600_48M 0x0034 +#define BAUD_1000000_48M 0x0030 +#define BAUD_1152000_48M 0x0029 +#define BAUD_1500000_48M 0x0020 +#define BAUD_2000000_48M 0x0018 +#define BAUD_2500000_48M 0x0013 +#define BAUD_3000000_48M 0x0010 + +#define UART_DUMP_REG 1 +#define UART_TX_DATA_INFO 2 +#define UART_TX_DEBUG_FUNC_CLOSE 3 + +struct uart_sprd_debug +{ + int debug_function_flag; //open uart debug function +}; +#ifndef CONFIG_64BIT +static struct wake_lock uart_rx_lock; // UART0 RX IRQ +static bool is_uart_rx_wakeup; +static struct serial_data plat_data; +#endif + +static inline unsigned int serial_in(struct uart_port *port, int offset) +{ + return __raw_readl(port->membase + offset); +} + +static inline void serial_out(struct uart_port *port, int offset, int value) +{ + __raw_writel(value, port->membase + offset); +} + +static unsigned int serial_sprd_tx_empty(struct uart_port *port) +{ + if (serial_in(port, ARM_UART_STS1) & 0xff00) + return 0; + else + return 1; +} + +static unsigned int serial_sprd_get_mctrl(struct uart_port *port) +{ + return TIOCM_DSR | TIOCM_CTS; +} + +static void serial_sprd_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ +} + +static void serial_sprd_stop_tx(struct uart_port *port) +{ + unsigned int ien, iclr; + + iclr = serial_in(port, ARM_UART_ICLR); + ien = serial_in(port, ARM_UART_IEN); + + iclr |= UART_IEN_TX_FIFO_EMPTY; + ien &= ~UART_IEN_TX_FIFO_EMPTY; + + serial_out(port, ARM_UART_ICLR, iclr); + serial_out(port, ARM_UART_IEN, ien); +} + +static void serial_sprd_start_tx(struct uart_port *port) +{ + unsigned int ien; + + ien = serial_in(port, ARM_UART_IEN); + if (!(ien & UART_IEN_TX_FIFO_EMPTY)) { + ien |= UART_IEN_TX_FIFO_EMPTY; + serial_out(port, ARM_UART_IEN, ien); + } +} + +static void serial_sprd_stop_rx(struct uart_port *port) +{ + unsigned int ien, iclr; + + iclr = serial_in(port, ARM_UART_ICLR); + ien = serial_in(port, ARM_UART_IEN); + + ien &= ~(UART_IEN_RX_FIFO_FULL | UART_IEN_BREAK_DETECT); + iclr |= UART_IEN_RX_FIFO_FULL | UART_IEN_BREAK_DETECT; + + serial_out(port, ARM_UART_IEN, ien); + serial_out(port, ARM_UART_ICLR, iclr); +} + +static void serial_sprd_enable_ms(struct uart_port *port) +{ +} + +static void serial_sprd_break_ctl(struct uart_port *port, int break_state) +{ +} + +static inline void serial_sprd_rx_chars(int irq, void *dev_id) +{ + struct uart_port *port = (struct uart_port *)dev_id; + struct tty_port *tty = &port->state->port; + struct uart_sprd_debug *temp_data = (struct uart_sprd_debug *)(port->private_data); + unsigned int status, ch, flag, lsr, max_count = 2048; + int mm = 0; + status = serial_in(port, ARM_UART_STS1); + lsr = serial_in(port, ARM_UART_STS0); + if (temp_data->debug_function_flag) + printk("sprd tty%d RX[]; ", port->line); + while ((status & 0x00ff) && max_count--) { + ch = serial_in(port, ARM_UART_RXD); + flag = TTY_NORMAL; + port->icount.rx++; + + if (unlikely(lsr & + (UART_LSR_BI | UART_LSR_PE | UART_LSR_FE | + UART_LSR_OE))) { + /* + *for statistics only + */ + if (lsr & UART_LSR_BI) { + lsr &= ~(UART_LSR_FE | UART_LSR_PE); + port->icount.brk++; + /* + *we do the SysRQ and SAK checking here because otherwise the + *break may get masked by ignore_status_mask or read_status_mask + */ + if (uart_handle_break(port)) + goto ignore_char; + } else if (lsr & UART_LSR_PE) + port->icount.parity++; + else if (lsr & UART_LSR_FE) + port->icount.frame++; + if (lsr & UART_LSR_OE) + port->icount.overrun++; + /* + *mask off conditions which should be ignored + */ + lsr &= port->read_status_mask; + if (lsr & UART_LSR_BI) + flag = TTY_BREAK; + else if (lsr & UART_LSR_PE) + flag = TTY_PARITY; + else if (lsr & UART_LSR_FE) + flag = TTY_FRAME; + } + if (uart_handle_sysrq_char(port, ch)) + goto ignore_char; + + uart_insert_char(port, lsr, UART_LSR_OE, ch, flag); + if (temp_data->debug_function_flag) { + if (mm++ < 10) + printk("%X_ ", ch); + } +ignore_char: + status = serial_in(port, ARM_UART_STS1); + lsr = serial_in(port, ARM_UART_STS0); + } + //tty->low_latency = 1; + if (temp_data->debug_function_flag) { + printk("\n "); + printk("UART%d , STS0:0X%08X, Uart_src_clk =%d, BAUD_REG= %x,RX_DATA = %d\n", port->line, + serial_in(port, ARM_UART_STS0), port->uartclk,serial_in(port,ARM_UART_CLKD0), (2048-max_count)); + } + tty_flip_buffer_push(tty); +} + +static inline void serial_sprd_tx_chars(int irq, void *dev_id) +{ + struct uart_port *port = dev_id; + struct circ_buf *xmit = &port->state->xmit; + struct uart_sprd_debug *temp_data = (struct uart_sprd_debug *)(port->private_data); + int count; + + if (port->x_char) { + serial_out(port, ARM_UART_TXD, port->x_char); + port->icount.tx++; + port->x_char = 0; + return; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) { + serial_sprd_stop_tx(port); + return; + } + count = SP_TX_FIFO; + + if (temp_data->debug_function_flag) { + int i; + printk("sprd tty%d TX[]; ", port->line); + for(i=0; i < 10; i++) { + printk("%X_ ", xmit->buf[xmit->tail+i]); + } + printk("\n "); + } + + do { + serial_out(port, ARM_UART_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 (temp_data->debug_function_flag) { + printk("UART%d , STS0:0X%08X, Uart_src_clk =%d, BAUD_REG= %x\n,Tx_data_count = %d \n", + port->line, serial_in(port, ARM_UART_STS0), port->uartclk, + serial_in(port, ARM_UART_CLKD0), (count ? (SP_TX_FIFO - count+1) : SP_TX_FIFO)); + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) { + uart_write_wakeup(port); + } + if (uart_circ_empty(xmit)) { + serial_sprd_stop_tx(port); + } + +} + +/* + *this handles the interrupt from one port + */ +static irqreturn_t serial_sprd_interrupt_chars(int irq, void *dev_id) +{ + struct uart_port *port = (struct uart_port *)dev_id; + u32 int_status; + + int_status = serial_in(port, ARM_UART_STS2); + + serial_out(port, ARM_UART_ICLR, 0xffffffff); + + if (int_status & + (UART_STS_RX_FIFO_FULL | UART_STS_BREAK_DETECT | + UART_STS_TIMEOUT)) { + serial_sprd_rx_chars(irq, port); + } + if (int_status & UART_STS_TX_FIFO_EMPTY) { + serial_sprd_tx_chars(irq, port); + } + + return IRQ_HANDLED; +} + +#ifndef CONFIG_64BIT +#define SPRD_EICINT_BASE (SPRD_EIC_BASE+0x80) +/* + *this handles the interrupt from rx0 wakeup + */ +static irqreturn_t wakeup_rx_interrupt(int irq, void *dev_id) +{ + u32 val; + + //SIC polarity 1 + val = __raw_readl(SPRD_EICINT_BASE + 0x10); + if ((val & BIT(0)) == BIT(0)) + val &= ~BIT(0); + else + val |= BIT(0); + __raw_writel(val, SPRD_EICINT_BASE + 0x10); + + //clear interrupt + val = __raw_readl(SPRD_EICINT_BASE + 0x0C); + val |= BIT(0); + __raw_writel(val, SPRD_EICINT_BASE + 0x0C); + + // set wakeup symbol + is_uart_rx_wakeup = true; + + return IRQ_HANDLED; +} +#endif + +/* FIXME: this pin config should be just defined int general pin mux table */ +static void serial_sprd_pin_config(void) +{ +#if (!defined CONFIG_ARCH_SCX35) && (!defined CONFIG_64BIT) + value = __raw_readl(SPRD_GREG_BASE + 0x08); + value |= 0x07 << 20; + __raw_writel(value, SPRD_GREG_BASE + 0x08); +#endif +} + +static int serial_sprd_startup(struct uart_port *port) +{ + int ret = 0; + unsigned int ien, ctrl1; + int rx_count = 130; + int tx_count = 130; + + /* FIXME: don't know who change u0cts pin in 88 */ + serial_sprd_pin_config(); + + /* set fifo water mark,tx_int_mark=8,rx_int_mark=1 */ +#if 0 /* ? */ + serial_out(port, ARM_UART_CTL2, 0x801); +#endif + + serial_out(port, ARM_UART_CTL2, ((SP_TX_FIFO << 8) | SP_RX_FIFO)); + /* clear rx fifo */ + while (serial_in(port, ARM_UART_STS1) & 0x00ff) { + serial_in(port, ARM_UART_RXD); + if(!(rx_count)){ + printk("serial_sprd_startup rx\n"); + return -EIO; + } + rx_count --; + } + /* clear tx fifo */ + while (serial_in(port, ARM_UART_STS1) & 0xff00){ + if(!(tx_count)){ + printk("serial_sprd_startup tx\n"); + return -EIO; + } + tx_count --; + } + /* clear interrupt */ + serial_out(port, ARM_UART_IEN, 0x00); + serial_out(port, ARM_UART_ICLR, 0xffffffff); + /* allocate irq */ + ret = + request_irq(port->irq, serial_sprd_interrupt_chars, IRQF_DISABLED, + "serial", port); + if (ret) { + printk(KERN_ERR "fail to request serial irq %d\n", port->irq); + free_irq(port->irq, port); + } + +#ifndef CONFIG_64BIT + if(BT_RX_WAKE_UP == plat_data.wakeup_type) { + int ret2 = 0; + + if (!port->line) { + ret2 = + request_irq(IRQ_WAKEUP, wakeup_rx_interrupt, + IRQF_SHARED, "wakeup_rx", port); + if (ret2) { + printk("fail to request wakeup irq\n"); + free_irq(IRQ_WAKEUP, NULL); + } + } + } +#endif + + ctrl1 = serial_in(port, ARM_UART_CTL1); + ctrl1 |= 0x3e00 | SP_RX_FIFO; + serial_out(port, ARM_UART_CTL1, ctrl1); + + /* enable interrupt */ + ien = serial_in(port, ARM_UART_IEN); + ien |= + UART_IEN_RX_FIFO_FULL | UART_IEN_TX_FIFO_EMPTY | + UART_IEN_BREAK_DETECT | UART_IEN_TIMEOUT; + serial_out(port, ARM_UART_IEN, ien); + + return 0; +} + +static void serial_sprd_shutdown(struct uart_port *port) +{ + serial_out(port, ARM_UART_IEN, 0x0); + serial_out(port, ARM_UART_ICLR, 0xffffffff); + free_irq(port->irq, port); +} + +static void serial_sprd_set_termios(struct uart_port *port, + struct ktermios *termios, + struct ktermios *old) +{ + unsigned int baud, quot; + unsigned long flags; + unsigned int lcr, fc; + /* ask the core to calculate the divisor for us */ + baud = uart_get_baud_rate(port, termios, old, 1200, 3000000); + +#ifdef CONFIG_SPRD_2331 + printk("marlin sprd_dt serial_sprd_set_termios baud %d\n", baud); + if(baud == 3000000) + baud = 3250000; // add this case, becaulse UART support 325000 baud rate +#endif + + quot = (unsigned int)((port->uartclk + baud / 2) / baud); + + /* set data length */ + lcr = serial_in(port, ARM_UART_CTL0); + lcr &= ~UART_DATA_BIT; + switch (termios->c_cflag & CSIZE) { + case CS5: + lcr |= UART_DATA_5BIT; + break; + case CS6: + lcr |= UART_DATA_6BIT; + break; + case CS7: + lcr |= UART_DATA_7BIT; + break; + default: + case CS8: + lcr |= UART_DATA_8BIT; + break; + } + /* calculate stop bits */ + lcr &= ~(UART_STOP_1BIT | UART_STOP_2BIT); + if (termios->c_cflag & CSTOPB) + lcr |= UART_STOP_2BIT; + else + lcr |= UART_STOP_1BIT; + /* calculate parity */ + lcr &= ~UART_PARITY; + if (termios->c_cflag & PARENB) { + lcr |= UART_PARITY_EN; + if (termios->c_cflag & PARODD) + lcr |= UART_ODD_PAR; + else + lcr |= UART_EVEN_PAR; + } + spin_lock_irqsave(&port->lock, flags); + /* change the port state. */ + /* update the per-port timeout */ + uart_update_timeout(port, termios->c_cflag, baud); + + port->read_status_mask = UART_LSR_OE; + if (termios->c_iflag & INPCK) + port->read_status_mask |= UART_LSR_FE | UART_LSR_PE; + if (termios->c_iflag & (BRKINT | PARMRK)) + port->read_status_mask |= UART_LSR_BI; + /* characters to ignore */ + port->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE; + if (termios->c_iflag & IGNBRK) { + port->ignore_status_mask |= UART_LSR_BI; + /* if we ignore parity and break indicators,ignore overruns too */ + if (termios->c_iflag & IGNPAR) + port->ignore_status_mask |= UART_LSR_OE; + } + /* ignore all characters if CREAD is not set */ +#if 0 /* ? */ + if ((termios->c_cflag & CREAD) == 0) + port->ignore_status_mask |= UART_LSR_DR; +#endif + /* flow control */ + fc = serial_in(port, ARM_UART_CTL1); + fc &= + ~(RX_HW_FLOW_CTL_THRESHOLD | RX_HW_FLOW_CTL_EN | TX_HW_FLOW_CTL_EN); + if (termios->c_cflag & CRTSCTS) { + fc |= RX_HW_FLOW_CTL_THRESHOLD; + fc |= RX_HW_FLOW_CTL_EN; + fc |= TX_HW_FLOW_CTL_EN; + } + /* clock divider bit0~bit15 */ + serial_out(port, ARM_UART_CLKD0, quot & 0xffff); + /* clock divider bit16~bit20 */ + serial_out(port, ARM_UART_CLKD1, (quot & 0x1f0000) >> 16); + serial_out(port, ARM_UART_CTL0, lcr); + fc |= 0x3e00 | SP_RX_FIFO; + serial_out(port, ARM_UART_CTL1, fc); + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *serial_sprd_type(struct uart_port *port) +{ + return "SPX"; +} + +static void serial_sprd_release_port(struct uart_port *port) +{ +} + +static int serial_sprd_request_port(struct uart_port *port) +{ + return 0; +} + +static void serial_sprd_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE && serial_sprd_request_port(port) == 0) + port->type = PORT_SPRD; +} + +static int serial_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 int serial_sprd_dump_reg(struct uart_port *port) +{ + printk("UART%d STS0 0X0008: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_STS0)); + printk("UART%d STS1 0X000C: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_STS1)); + printk("UART%d IEN 0X0010: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_IEN)); + printk("UART%d CTL0 0X0018: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_CTL0)); + printk("UART%d CTL1 0X001C: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_CTL1)); + printk("UART%d CTL2 0X0020: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_CTL2)); + printk("UART%d CLKD 0x0024: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_CLKD0)); + printk("UART%d STS2 0x002C: 0X%08X\n\n", port->line, serial_in(port, ARM_UART_STS2)); + printk("UART%d CLK SOURCE: %d \n", port->line, port->uartclk ); +} + +static ssize_t serial_sprd_config_reg_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uart_port *port = dev_get_drvdata(dev); + unsigned int pinmap_uart_conf = 0; + unsigned int uartclk_source = 0; + unsigned int uart_reg_clk = 0; + + uartclk_source = port->uartclk; + uart_reg_clk = serial_in(port, ARM_UART_CLKD0); + + return snprintf(buf, PAGE_SIZE, "uart%d config info:\nclk source: %d\nuart %d baundrate config:0x%x\n", + port->line,uartclk_source,port->line,uart_reg_clk); +} + +static ssize_t serial_sprd_config_reg_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct uart_port *port = dev_get_drvdata(dev); + struct uart_sprd_debug *temp_data; + int user_flag = 0; + + temp_data = (struct uart_sprd_debug *)(port->private_data); + if (!sscanf(buf, "%d", &user_flag)) + return 0; + if(user_flag == UART_DUMP_REG) + serial_sprd_dump_reg(port); + else if (user_flag == UART_TX_DATA_INFO) { + temp_data->debug_function_flag = 1; + } + else if (user_flag == UART_TX_DEBUG_FUNC_CLOSE) { + temp_data->debug_function_flag = 0; + } + + return count; +} +static DEVICE_ATTR(uart_conf, S_IRWXU | S_IRWXG, serial_sprd_config_reg_show, + serial_sprd_config_reg_store); + +static struct uart_ops serial_sprd_ops = { + .tx_empty = serial_sprd_tx_empty, + .get_mctrl = serial_sprd_get_mctrl, + .set_mctrl = serial_sprd_set_mctrl, + .stop_tx = serial_sprd_stop_tx, + .start_tx = serial_sprd_start_tx, + .stop_rx = serial_sprd_stop_rx, + .enable_ms = serial_sprd_enable_ms, + .break_ctl = serial_sprd_break_ctl, + .startup = serial_sprd_startup, + .shutdown = serial_sprd_shutdown, + .set_termios = serial_sprd_set_termios, + .type = serial_sprd_type, + .release_port = serial_sprd_release_port, + .request_port = serial_sprd_request_port, + .config_port = serial_sprd_config_port, + .verify_port = serial_sprd_verify_port, +}; +static struct uart_port *serial_sprd_ports[UART_NR_MAX] = { 0 }; + +static struct { + uint32_t ien; + uint32_t ctrl0; + uint32_t ctrl1; + uint32_t ctrl2; + uint32_t clkd0; + uint32_t clkd1; + uint32_t dspwait; +} uart_bak[UART_NR_MAX]; + +static int clk_startup(struct platform_device *pdev) +{ +#ifndef CONFIG_64BIT + struct clk *clk; + struct clk *clk_parent; + char clk_name[10]; + int ret; + int clksrc; + struct serial_data plat_local_data; + + sprintf(clk_name, "clk_uart%d", pdev->id); + clk = clk_get(NULL, clk_name); + if (IS_ERR(clk)) { + printk("clock[%s]: failed to get clock by clk_get()!\n", + clk_name); + return -1; + } + + plat_local_data = *(struct serial_data *)(pdev->dev.platform_data); + clksrc = plat_local_data.clk; + + if (clksrc == 48000000) { + clk_parent = clk_get(NULL, "clk_48m"); + } else { + clk_parent = clk_get(NULL, "ext_26m"); + } + if (IS_ERR(clk_parent)) { + printk("clock[%s]: failed to get parent [%s] by clk_get()!\n", + clk_name, "clk_48m"); + return -1; + } + + ret = clk_set_parent(clk, clk_parent); + if (ret) { + printk("clock[%s]: clk_set_parent() failed!\n", clk_name); + } + ret = clk_prepare_enable(clk); + if (ret) { + printk("clock[%s]: clk_enable() failed!\n", clk_name); + } +#endif + return 0; +} + +static int serial_sprd_setup_port(struct platform_device *pdev, + struct resource *mem, struct resource *irq) +{ +#ifndef CONFIG_64BIT + struct serial_data plat_local_data; + struct device_node *np = pdev->dev.of_node; +#endif + struct uart_port *up; + struct uart_sprd_debug * private_data ; + up = kzalloc(sizeof(*up), GFP_KERNEL); + if (up == NULL) { + return -ENOMEM; + } + + up->dev = &(pdev->dev); + up->line = pdev->id; + up->type = PORT_SPRD; + up->iotype = SERIAL_IO_PORT; + up->membase = ioremap(mem->start, 0x100); + up->mapbase = (phys_addr_t)up->membase; + +#ifndef CONFIG_64BIT + plat_local_data = *(struct serial_data *)(pdev->dev.platform_data); + up->uartclk = plat_local_data.clk; +#else + up->uartclk = 26 * 1000 * 1000; +#endif + + up->irq = irq->start; + up->fifosize = 128; + up->ops = &serial_sprd_ops; + up->flags = ASYNC_BOOT_AUTOCONF; + + private_data = kmalloc(sizeof(struct uart_sprd_debug), GFP_KERNEL); + if(!private_data) + return -ENOMEM; + private_data->debug_function_flag = 0; + up->private_data = private_data; + serial_sprd_ports[pdev->id] = up; + + clk_startup(pdev); + return 0; +} + +#ifdef CONFIG_SERIAL_SPRD_UART_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, ARM_UART_STS1); + if (--tmout == 0) + break; + udelay(1); + } while (status & 0xff00); +} + +static void serial_sprd_console_putchar(struct uart_port *port, int ch) +{ + wait_for_xmitr(port); + serial_out(port, ARM_UART_TXD, ch); +} + +static void serial_sprd_console_write(struct console *co, const char *s, + unsigned int count) +{ + struct uart_port *port = serial_sprd_ports[co->index]; + int ien; + int locked = 1; + if (oops_in_progress) + locked = spin_trylock(&port->lock); + else + spin_lock(&port->lock); + /*firstly,save the IEN register and disable the interrupts */ + ien = serial_in(port, ARM_UART_IEN); + serial_out(port, ARM_UART_IEN, 0x0); + + uart_console_write(port, s, count, serial_sprd_console_putchar); + /*finally,wait for TXD FIFO to become empty and restore the IEN register */ + wait_for_xmitr(port); + serial_out(port, ARM_UART_IEN, ien); + if (locked) + spin_unlock(&port->lock); +} + +static int __init serial_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 = serial_sprd_ports[co->index]; + if (port == NULL) { + printk(KERN_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 serial_sprd_reg; +static struct console serial_sprd_console = { + .name = "ttyS", + .write = serial_sprd_console_write, + .device = uart_console_device, + .setup = serial_sprd_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &serial_sprd_reg, +}; + +#define SPRD_CONSOLE &serial_sprd_console + +#else /* !CONFIG_SERIAL_SPRD_UART_CONSOLE */ +#define SPRD_CONSOLE NULL +#endif + +static struct uart_driver serial_sprd_reg = { + .owner = THIS_MODULE, + .driver_name = "serial_sprd", + .dev_name = SP_TTY_NAME, + .major = SP_TTY_MAJOR, + .minor = SP_TTY_MINOR_START, + .nr = UART_NR_MAX, + .cons = SPRD_CONSOLE, +}; + +static int serial_sprd_probe(struct platform_device *pdev) +{ + int ret; + struct resource *mem, *irq; + struct device_node *np = pdev->dev.of_node; + + 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; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!mem)) { + dev_err(&pdev->dev, "not provide mem resource\n"); + return -ENODEV; + } + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (unlikely(!irq)) { + dev_err(&pdev->dev, "not provide irq resource\n"); + return -ENODEV; + } + if (np) { +#ifndef CONFIG_64BIT + struct serial_data *plat_local_data; + char *local_string; + plat_local_data = + kmalloc(sizeof(struct serial_data), GFP_KERNEL); + if (!plat_local_data) { + dev_err(&pdev->dev, + "fail to malloc memory for platform_data\n"); + return -ENOMEM; + } + ret = + of_property_read_u32(np, "sprdclk", &plat_local_data->clk); + if (ret) { + dev_err(&pdev->dev, "fail to get sprdclk\n"); + plat_local_data->clk = 26000000; + } + ret = + of_property_read_string(np, "sprdwaketype", &local_string); + if (ret) { + dev_err(&pdev->dev, "fail to get sprdwaketype\n"); + plat_local_data->wakeup_type = BT_RTS_HIGH_WHEN_SLEEP; + } else { + if (!strcmp(local_string, "BT_RTS_HIGH_WHEN_SLEEP")) + plat_local_data->wakeup_type = + BT_RTS_HIGH_WHEN_SLEEP; + else if (!strcmp(local_string, "BT_RX_WAKE_UP")) + plat_local_data->wakeup_type = BT_RX_WAKE_UP; + else if (!strcmp(local_string, "BT_NO_WAKE_UP")) + plat_local_data->wakeup_type = BT_RX_WAKE_UP; + } + pdev->dev.platform_data = (void *)plat_local_data; +#endif + } + + ret = serial_sprd_setup_port(pdev, mem, irq); + if (unlikely(ret != 0)) { + dev_err(&pdev->dev, "setup port failed\n"); + return ret; + } + ret = uart_add_one_port(&serial_sprd_reg, serial_sprd_ports[pdev->id]); + if (likely(ret == 0)) { + platform_set_drvdata(pdev, serial_sprd_ports[pdev->id]); + } + ret = device_create_file(&(pdev->dev), &dev_attr_uart_conf); +#ifndef CONFIG_64BIT + if (!((void *)(pdev->dev.platform_data))) { + dev_err(&pdev->dev, "serial driver get platform data failed\n"); + return -ENODEV; + } + plat_data = *(struct serial_data *)(pdev->dev.platform_data); + printk("bt host wake up type is %d, clk is %d \n", + plat_data.wakeup_type, plat_data.clk); + if (BT_RX_WAKE_UP == plat_data.wakeup_type) { + wake_lock_init(&uart_rx_lock, WAKE_LOCK_SUSPEND, + "uart_rx_lock"); + } +#endif + + return ret; +} + +struct uart_port * serial_get_uart_port(int uart_index) +{ + int i = 0; + for(i= 0;i < UART_NR_MAX;i++){ + if(serial_sprd_ports[i]->line == uart_index){ + return serial_sprd_ports[uart_index]; + } + } + return NULL; +} +EXPORT_SYMBOL(serial_get_uart_port); + + +static int serial_sprd_remove(struct platform_device *dev) +{ + struct uart_port *up = platform_get_drvdata(dev); + struct device_node *np = dev->dev.of_node; + + if (np) { + kfree(dev->dev.platform_data); + } + platform_set_drvdata(dev, NULL); + if (up) { + uart_remove_one_port(&serial_sprd_reg, up); + kfree(up); + serial_sprd_ports[dev->id] = NULL; + } + return 0; +} + +static int serial_sprd_suspend(struct platform_device *dev, pm_message_t state) +{ + /* TODO */ + int id = dev->id; + struct uart_port *port; + + port = serial_sprd_ports[id]; +#ifndef CONFIG_64BIT + if(BT_RX_WAKE_UP == plat_data.wakeup_type){ + is_uart_rx_wakeup = false; + } else { + pr_debug("BT host wake up feature has not been supported\n"); + } +#endif + uart_suspend_port(&serial_sprd_reg, port); + return 0; +} + +static int serial_sprd_resume(struct platform_device *dev) +{ + /* TODO */ + int id = dev->id; + struct uart_port *port = serial_sprd_ports[id]; +#ifndef CONFIG_64BIT + if(BT_RX_WAKE_UP == plat_data.wakeup_type){ + if(is_uart_rx_wakeup) { + is_uart_rx_wakeup = false; + wake_lock_timeout(&uart_rx_lock, HZ / 5); // 0.2s + } + } else { + pr_debug("BT host wake up feature has not been supported\n"); + } +#endif + uart_resume_port(&serial_sprd_reg, port); + return 0; +} + +static const struct of_device_id serial_ids[] = { + {.compatible = "sprd,serial",}, + {} +}; + +static struct platform_driver serial_sprd_driver = { + .probe = serial_sprd_probe, + .remove = serial_sprd_remove, + .suspend = serial_sprd_suspend, + .resume = serial_sprd_resume, + .driver = { + .name = "serial_sprd", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(serial_ids), + }, +}; + +static int __init serial_sprd_init(void) +{ + int ret = 0; + + ret = uart_register_driver(&serial_sprd_reg); + if (unlikely(ret != 0)) + return ret; + + ret = platform_driver_register(&serial_sprd_driver); + if (unlikely(ret != 0)) + uart_unregister_driver(&serial_sprd_reg); + + return ret; +} + +static void __exit serial_sprd_exit(void) +{ + platform_driver_unregister(&serial_sprd_driver); + uart_unregister_driver(&serial_sprd_reg); +} + +module_init(serial_sprd_init); +module_exit(serial_sprd_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("sprd serial driver $Revision:1.0$"); |
