1
0
Files
kernel-49/drivers/tty/serial/sprd_serial.c
Greg Kroah-Hartman f61f3b1c19 Merge 4.9.194 into android-4.9
Changes in 4.9.194
	bridge/mdb: remove wrong use of NLM_F_MULTI
	cdc_ether: fix rndis support for Mediatek based smartphones
	ipv6: Fix the link time qualifier of 'ping_v6_proc_exit_net()'
	isdn/capi: check message length in capi_write()
	net: Fix null de-reference of device refcount
	net: gso: Fix skb_segment splat when splitting gso_size mangled skb having linear-headed frag_list
	sch_hhf: ensure quantum and hhf_non_hh_weight are non-zero
	sctp: Fix the link time qualifier of 'sctp_ctrlsock_exit()'
	sctp: use transport pf_retrans in sctp_do_8_2_transport_strike
	tcp: fix tcp_ecn_withdraw_cwr() to clear TCP_ECN_QUEUE_CWR
	tipc: add NULL pointer check before calling kfree_rcu
	tun: fix use-after-free when register netdev failed
	Revert "MIPS: SiByte: Enable swiotlb for SWARM, LittleSur and BigSur"
	gpio: fix line flag validation in linehandle_create
	gpio: fix line flag validation in lineevent_create
	Btrfs: fix assertion failure during fsync and use of stale transaction
	genirq: Prevent NULL pointer dereference in resend_irqs()
	KVM: s390: Do not leak kernel stack data in the KVM_S390_INTERRUPT ioctl
	KVM: x86: work around leak of uninitialized stack contents
	KVM: nVMX: handle page fault in vmread
	MIPS: VDSO: Prevent use of smp_processor_id()
	MIPS: VDSO: Use same -m%-float cflag as the kernel proper
	clk: rockchip: Don't yell about bad mmc phases when getting
	mtd: rawnand: mtk: Fix wrongly assigned OOB buffer pointer issue
	driver core: Fix use-after-free and double free on glue directory
	crypto: talitos - check AES key size
	crypto: talitos - fix CTR alg blocksize
	crypto: talitos - check data blocksize in ablkcipher.
	crypto: talitos - fix ECB algs ivsize
	crypto: talitos - Do not modify req->cryptlen on decryption.
	crypto: talitos - HMAC SNOOP NO AFEU mode requires SW icv checking.
	drm/mediatek: mtk_drm_drv.c: Add of_node_put() before goto
	nvmem: Use the same permissions for eeprom as for nvmem
	x86/build: Add -Wnoaddress-of-packed-member to REALMODE_CFLAGS, to silence GCC9 build warning
	USB: usbcore: Fix slab-out-of-bounds bug during device reset
	media: tm6000: double free if usb disconnect while streaming
	powerpc/mm/radix: Use the right page size for vmemmap mapping
	x86/boot: Add missing bootparam that breaks boot on some platforms
	xen-netfront: do not assume sk_buff_head list is empty in error handling
	KVM: coalesced_mmio: add bounds checking
	serial: sprd: correct the wrong sequence of arguments
	tty/serial: atmel: reschedule TX after RX was started
	mwifiex: Fix three heap overflow at parsing element in cfg80211_ap_settings
	ARM: OMAP2+: Fix missing SYSC_HAS_RESET_STATUS for dra7 epwmss
	s390/bpf: fix lcgr instruction encoding
	ARM: OMAP2+: Fix omap4 errata warning on other SoCs
	s390/bpf: use 32-bit index for tail calls
	NFSv4: Fix return values for nfs4_file_open()
	NFS: Fix initialisation of I/O result struct in nfs_pgio_rpcsetup
	Kconfig: Fix the reference to the IDT77105 Phy driver in the description of ATM_NICSTAR_USE_IDT77105
	qed: Add cleanup in qed_slowpath_start()
	ARM: 8874/1: mm: only adjust sections of valid mm structures
	batman-adv: Only read OGM2 tvlv_len after buffer len check
	r8152: Set memory to all 0xFFs on failed reg reads
	x86/apic: Fix arch_dynirq_lower_bound() bug for DT enabled machines
	netfilter: nf_conntrack_ftp: Fix debug output
	NFSv2: Fix eof handling
	NFSv2: Fix write regression
	cifs: set domainName when a domain-key is used in multiuser
	cifs: Use kzfree() to zero out the password
	ARM: 8901/1: add a criteria for pfn_valid of arm
	sky2: Disable MSI on yet another ASUS boards (P6Xxxx)
	perf/x86/intel: Restrict period on Nehalem
	perf/x86/amd/ibs: Fix sample bias for dispatched micro-ops
	tools/power turbostat: fix buffer overrun
	net: seeq: Fix the function used to release some memory in an error handling path
	dmaengine: ti: dma-crossbar: Fix a memory leak bug
	dmaengine: ti: omap-dma: Add cleanup in omap_dma_probe()
	x86/uaccess: Don't leak the AC flags into __get_user() argument evaluation
	keys: Fix missing null pointer check in request_key_auth_describe()
	iommu/amd: Fix race in increase_address_space()
	floppy: fix usercopy direction
	media: technisat-usb2: break out of loop at end of buffer
	ARC: export "abort" for modules
	net_sched: let qdisc_put() accept NULL pointer
	Linux 4.9.194

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
2019-09-22 17:06:19 +03:00

801 lines
18 KiB
C

/*
* Copyright (C) 2012-2015 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.
*/
#if defined(CONFIG_SERIAL_SPRD_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ)
#define SUPPORT_SYSRQ
#endif
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
/* device name */
#define UART_NR_MAX 8
#define SPRD_TTY_NAME "ttyS"
#define SPRD_FIFO_SIZE 128
#define SPRD_DEF_RATE 26000000
#define SPRD_BAUD_IO_LIMIT 3000000
#define SPRD_TIMEOUT 256000
/* 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
#define SPRD_ICLR_TIMEOUT BIT(13)
/* 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)
#define RX_TOUT_THLD_DEF 0x3E00
#define RX_HFC_THLD_DEF 0x40
/* 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 {
u32 ien;
u32 ctrl0;
u32 ctrl1;
u32 ctrl2;
u32 clkd0;
u32 clkd1;
u32 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];
static int sprd_ports_num;
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 int handle_lsr_errors(struct uart_port *port,
unsigned int *flag,
unsigned int *lsr)
{
int ret = 0;
/* statistics */
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(struct uart_port *port)
{
struct tty_port *tty = &port->state->port;
unsigned int ch, flag, lsr, max_count = SPRD_TIMEOUT;
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 (lsr & (SPRD_LSR_BI | SPRD_LSR_PE |
SPRD_LSR_FE | SPRD_LSR_OE))
if (handle_lsr_errors(port, &flag, &lsr))
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(struct uart_port *port)
{
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 = dev_id;
unsigned int ims;
spin_lock(&port->lock);
ims = serial_in(port, SPRD_IMSR);
if (!ims) {
spin_unlock(&port->lock);
return IRQ_NONE;
}
if (ims & SPRD_IMSR_TIMEOUT)
serial_out(port, SPRD_ICLR, SPRD_ICLR_TIMEOUT);
if (ims & (SPRD_IMSR_RX_FIFO_FULL |
SPRD_IMSR_BREAK_DETECT | SPRD_IMSR_TIMEOUT))
sprd_rx(port);
if (ims & SPRD_IMSR_TX_FIFO_EMPTY)
sprd_tx(port);
spin_unlock(&port->lock);
return IRQ_HANDLED;
}
static int sprd_startup(struct uart_port *port)
{
int ret = 0;
unsigned int ien, fc;
unsigned int timeout;
struct sprd_uart_port *sp;
unsigned long flags;
serial_out(port, SPRD_CTL2, ((THLD_TX_EMPTY << 8) | THLD_RX_FULL));
/* clear rx fifo */
timeout = SPRD_TIMEOUT;
while (timeout-- && serial_in(port, SPRD_STS1) & 0x00ff)
serial_in(port, SPRD_RXD);
/* clear tx fifo */
timeout = SPRD_TIMEOUT;
while (timeout-- && serial_in(port, SPRD_STS1) & 0xff00)
cpu_relax();
/* clear interrupt */
serial_out(port, SPRD_IEN, 0);
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, ret=%d\n",
port->irq, ret);
return ret;
}
fc = serial_in(port, SPRD_CTL1);
fc |= RX_TOUT_THLD_DEF | RX_HFC_THLD_DEF;
serial_out(port, SPRD_CTL1, fc);
/* enable interrupt */
spin_lock_irqsave(&port->lock, flags);
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_irqrestore(&port->lock, flags);
return 0;
}
static void sprd_shutdown(struct uart_port *port)
{
serial_out(port, SPRD_IEN, 0);
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 = 0, fc;
unsigned long flags;
/* ask the core to calculate the divisor for us */
baud = uart_get_baud_rate(port, termios, old, 0, SPRD_BAUD_IO_LIMIT);
quot = (unsigned int)((port->uartclk + baud / 2) / baud);
/* set data length */
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;
termios->c_cflag &= ~CMSPAR; /* no support mark/space */
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;
}
spin_lock_irqsave(&port->lock, flags);
/* 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 & (IGNBRK | 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 |= RX_TOUT_THLD_DEF | RX_HFC_THLD_DEF;
serial_out(port, SPRD_CTL1, fc);
spin_unlock_irqrestore(&port->lock, flags);
/* Don't rewrite B0 */
if (tty_termios_baud_rate(termios))
tty_termios_encode_baud_rate(termios, baud, baud);
}
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 (ser->type != PORT_SPRD)
return -EINVAL;
if (port->irq != ser->irq)
return -EINVAL;
if (port->iotype != ser->io_type)
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 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 = &sprd_port[co->index]->port;
int locked = 1;
unsigned long flags;
if (port->sysrq)
locked = 0;
else if (oops_in_progress)
locked = spin_trylock_irqsave(&port->lock, flags);
else
spin_lock_irqsave(&port->lock, flags);
uart_console_write(port, s, count, sprd_console_putchar);
/* wait for transmitter to become empty */
wait_for_xmitr(port);
if (locked)
spin_unlock_irqrestore(&port->lock, flags);
}
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 (co->index >= UART_NR_MAX || co->index < 0)
co->index = 0;
port = &sprd_port[co->index]->port;
if (port == NULL) {
pr_info("serial 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)
{
unsigned int timeout = SPRD_TIMEOUT;
while (timeout-- &&
!(readl(port->membase + SPRD_LSR) & SPRD_LSR_TX_OVER))
cpu_relax();
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;
}
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 = 0,
.minor = 0,
.nr = UART_NR_MAX,
.cons = SPRD_CONSOLE,
};
static int sprd_probe_dt_alias(int index, struct device *dev)
{
struct device_node *np;
int ret = index;
if (!IS_ENABLED(CONFIG_OF))
return ret;
np = dev->of_node;
if (!np)
return ret;
ret = of_alias_get_id(np, "serial");
if (ret < 0)
ret = index;
else if (ret >= ARRAY_SIZE(sprd_port) || sprd_port[ret] != NULL) {
dev_warn(dev, "requested serial port %d not available.\n", ret);
ret = index;
}
return ret;
}
static int sprd_remove(struct platform_device *dev)
{
struct sprd_uart_port *sup = platform_get_drvdata(dev);
if (sup) {
uart_remove_one_port(&sprd_uart_driver, &sup->port);
sprd_port[sup->port.line] = NULL;
sprd_ports_num--;
}
if (!sprd_ports_num)
uart_unregister_driver(&sprd_uart_driver);
return 0;
}
static int sprd_probe(struct platform_device *pdev)
{
struct resource *res;
struct uart_port *up;
struct clk *clk;
int irq;
int index;
int ret;
for (index = 0; index < ARRAY_SIZE(sprd_port); index++)
if (sprd_port[index] == NULL)
break;
if (index == ARRAY_SIZE(sprd_port))
return -EBUSY;
index = sprd_probe_dt_alias(index, &pdev->dev);
sprd_port[index] = devm_kzalloc(&pdev->dev,
sizeof(*sprd_port[index]), GFP_KERNEL);
if (!sprd_port[index])
return -ENOMEM;
up = &sprd_port[index]->port;
up->dev = &pdev->dev;
up->line = index;
up->type = PORT_SPRD;
up->iotype = UPIO_MEM;
up->uartclk = SPRD_DEF_RATE;
up->fifosize = SPRD_FIFO_SIZE;
up->ops = &serial_sprd_ops;
up->flags = UPF_BOOT_AUTOCONF;
clk = devm_clk_get(&pdev->dev, NULL);
if (!IS_ERR_OR_NULL(clk))
up->uartclk = clk_get_rate(clk);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "not provide mem resource\n");
return -ENODEV;
}
up->mapbase = res->start;
up->membase = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(up->membase))
return PTR_ERR(up->membase);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "not provide irq resource\n");
return -ENODEV;
}
up->irq = irq;
if (!sprd_ports_num) {
ret = uart_register_driver(&sprd_uart_driver);
if (ret < 0) {
pr_err("Failed to register SPRD-UART driver\n");
return ret;
}
}
sprd_ports_num++;
ret = uart_add_one_port(&sprd_uart_driver, up);
if (ret) {
sprd_port[index] = NULL;
sprd_remove(pdev);
}
platform_set_drvdata(pdev, up);
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int sprd_suspend(struct device *dev)
{
struct sprd_uart_port *sup = dev_get_drvdata(dev);
uart_suspend_port(&sprd_uart_driver, &sup->port);
return 0;
}
static int sprd_resume(struct device *dev)
{
struct sprd_uart_port *sup = dev_get_drvdata(dev);
uart_resume_port(&sprd_uart_driver, &sup->port);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(sprd_pm_ops, sprd_suspend, sprd_resume);
static const struct of_device_id serial_ids[] = {
{.compatible = "sprd,sc9836-uart",},
{}
};
MODULE_DEVICE_TABLE(of, serial_ids);
static struct platform_driver sprd_platform_driver = {
.probe = sprd_probe,
.remove = sprd_remove,
.driver = {
.name = "sprd_serial",
.of_match_table = of_match_ptr(serial_ids),
.pm = &sprd_pm_ops,
},
};
module_platform_driver(sprd_platform_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Spreadtrum SoC serial driver series");