serial driver support for Phytium desktop and embedded CPUs

serial driver function fix patch. Support Phytium desktop and embedded processors, such as D2000.

Reviewed-by:JianChao Sheng <shengjianchao@phytium.com.cn>
(cherry picked from commit 92e0f87830)
Signed-off-by: Alex Shi <alexsshi@tencent.com>
This commit is contained in:
JianChao Sheng 2023-04-18 04:03:51 -04:00 committed by Jianping Liu
parent 2f350b2dc6
commit 6039b7b2dc
4 changed files with 937 additions and 0 deletions

View File

@ -75,6 +75,17 @@ config SERIAL_AMBA_PL011_CONSOLE
your boot loader (lilo or loadlin) about how to pass options to the
kernel at boot time.)
config SERIAL_PHYTIUM_PCI
tristate "Phytium PCI serial port support"
depends on PCI
select SERIAL_CORE
help
This driver supports the Phytium UART controller on PCI/PCIe adapters.
If you want to compile this driver into the kernel, say Y here. To
compile this driver as a module, choose M here.
If unsure, say N.
config SERIAL_EARLYCON_ARM_SEMIHOST
bool "Early console using ARM semihosting"
depends on ARM64 || ARM

View File

@ -25,6 +25,7 @@ obj-$(CONFIG_SERIAL_8250) += 8250/
obj-$(CONFIG_SERIAL_AMBA_PL010) += amba-pl010.o
obj-$(CONFIG_SERIAL_AMBA_PL011) += amba-pl011.o
obj-$(CONFIG_SERIAL_PHYTIUM_PCI) += phytium-uart.o
obj-$(CONFIG_SERIAL_CLPS711X) += clps711x.o
obj-$(CONFIG_SERIAL_PXA_NON8250) += pxa.o
obj-$(CONFIG_SERIAL_PNX8XXX) += pnx8xxx_uart.o

View File

@ -0,0 +1,922 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Driver for Phytium PCI UART controller
*
* Copyright 2021 Phytium Ltd.
*/
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#define DRV_NAME "phytium_uart"
#define REG_DR 0x00
#define REG_FR 0x18
#define REG_IBRD 0x24
#define REG_FBRD 0x28
#define REG_LCRH_RX 0x2c
#define REG_LCRH_TX 0x2c
#define REG_CR 0x30
#define REG_IFLS 0x34
#define REG_IMSC 0x38
#define REG_RIS 0x3c
#define REG_MIS 0x40
#define REG_ICR 0x44
#define REG_DR_OE (1 << 11)
#define REG_DR_BE (1 << 10)
#define REG_DR_PE (1 << 9)
#define REG_DR_FE (1 << 8)
#define REG_LCRH_SPS 0x80
#define REG_LCRH_WLEN_8 0x60
#define REG_LCRH_WLEN_7 0x40
#define REG_LCRH_WLEN_6 0x20
#define REG_LCRH_WLEN_5 0x00
#define REG_LCRH_FEN 0x10
#define REG_LCRH_STP2 0x08
#define REG_LCRH_EPS 0x04
#define REG_LCRH_PEN 0x02
#define REG_LCRH_BRK 0x01
#define REG_FR_RI 0x100
#define REG_FR_TXFE 0x080
#define REG_FR_RXFF 0x040
#define REG_FR_TXFF 0x020
#define REG_FR_RXFE 0x010
#define REG_FR_BUSY 0x008
#define REG_FR_DCD 0x004
#define REG_FR_DSR 0x002
#define REG_FR_CTS 0x001
#define REG_FR_TMSK (REG_FR_TXFF + REG_FR_BUSY)
#define REG_CR_CTSEN 0x8000 /* CTS hardware flow control */
#define REG_CR_RTSEN 0x4000 /* RTS hardware flow control */
#define REG_CR_OUT2 0x2000 /* OUT2 */
#define REG_CR_OUT1 0x1000 /* OUT1 */
#define REG_CR_RTS 0x0800 /* RTS */
#define REG_CR_DTR 0x0400 /* DTR */
#define REG_CR_RXE 0x0200 /* receive enable */
#define REG_CR_TXE 0x0100 /* transmit enable */
#define REG_CR_LBE 0x0080 /* loopback enable */
#define REG_CR_RTIE 0x0040
#define REG_CR_TIE 0x0020
#define REG_CR_RIE 0x0010
#define REG_CR_MSIE 0x0008
#define REG_CR_IIRLP 0x0004 /* SIR low power mode */
#define REG_CR_SIREN 0x0002 /* SIR enable */
#define REG_CR_UARTEN 0x0001 /* UART enable */
#define REG_IFLS_RX1_8 (0 << 3)
#define REG_IFLS_RX2_8 (1 << 3)
#define REG_IFLS_RX4_8 (2 << 3)
#define REG_IFLS_RX6_8 (3 << 3)
#define REG_IFLS_RX7_8 (4 << 3)
#define REG_IFLS_TX1_8 (0 << 0)
#define REG_IFLS_TX2_8 (1 << 0)
#define REG_IFLS_TX4_8 (2 << 0)
#define REG_IFLS_TX6_8 (3 << 0)
#define REG_IMSC_OEIM (1 << 10) /* overrun error interrupt mask */
#define REG_IMSC_BEIM (1 << 9) /* break error interrupt mask */
#define REG_IMSC_PEIM (1 << 8) /* parity error interrupt mask */
#define REG_IMSC_FEIM (1 << 7) /* framing error interrupt mask */
#define REG_IMSC_RTIM (1 << 6) /* receive timeout interrupt mask */
#define REG_IMSC_TXIM (1 << 5) /* transmit interrupt mask */
#define REG_IMSC_RXIM (1 << 4) /* receive interrupt mask */
#define REG_IMSC_DSRMIM (1 << 3) /* DSR interrupt mask */
#define REG_IMSC_DCDMIM (1 << 2) /* DCD interrupt mask */
#define REG_IMSC_CTSMIM (1 << 1) /* CTS interrupt mask */
#define REG_IMSC_RIMIM (1 << 0) /* RI interrupt mask */
#define REG_ICR_OEIS (1 << 10) /* overrun error interrupt status */
#define REG_ICR_BEIS (1 << 9) /* break error interrupt status */
#define REG_ICR_PEIS (1 << 8) /* parity error interrupt status */
#define REG_ICR_FEIS (1 << 7) /* framing error interrupt status */
#define REG_ICR_RTIS (1 << 6) /* receive timeout interrupt status */
#define REG_ICR_TXIS (1 << 5) /* transmit interrupt status */
#define REG_ICR_RXIS (1 << 4) /* receive interrupt status */
#define REG_ICR_DSRMIS (1 << 3) /* DSR interrupt status */
#define REG_ICR_DCDMIS (1 << 2) /* DCD interrupt status */
#define REG_ICR_CTSMIS (1 << 1) /* CTS interrupt status */
#define REG_ICR_RIMIS (1 << 0) /* RI interrupt status */
#define UART_NR 12
#define UART_DR_ERROR (REG_DR_OE|REG_DR_BE|REG_DR_PE|REG_DR_FE)
#define UART_DUMMY_DR_RX (1 << 16)
#define DEFAULT_UARTCLK 48000000 /* 48 MHz */
/*
* We wrap our port structure around the generic uart_port.
*/
struct phytium_uart_port {
struct uart_port port;
unsigned int im; /* interrupt mask */
unsigned int old_status;
unsigned int old_cr; /* state during shutdown */
char type[12];
};
static unsigned int phytium_uart_read(const struct phytium_uart_port *pup,
unsigned int reg)
{
void __iomem *addr = pup->port.membase + reg;
return readl_relaxed(addr);
}
static void phytium_uart_write(unsigned int val, const struct phytium_uart_port *pup,
unsigned int reg)
{
void __iomem *addr = pup->port.membase + reg;
writel_relaxed(val, addr);
}
static int phytium_fifo_to_tty(struct phytium_uart_port *pup)
{
u16 status;
unsigned int ch, flag, fifotaken;
for (fifotaken = 0; fifotaken < 256; fifotaken++) {
status = phytium_uart_read(pup, REG_FR);
if (status & REG_FR_RXFE)
break;
/* Take chars from the FIFO and update status */
ch = phytium_uart_read(pup, REG_DR) | UART_DUMMY_DR_RX;
flag = TTY_NORMAL;
pup->port.icount.rx++;
if (unlikely(ch & UART_DR_ERROR)) {
if (ch & REG_DR_BE) {
ch &= ~(REG_DR_FE | REG_DR_PE);
pup->port.icount.brk++;
if (uart_handle_break(&pup->port))
continue;
} else if (ch & REG_DR_PE)
pup->port.icount.parity++;
else if (ch & REG_DR_FE)
pup->port.icount.frame++;
if (ch & REG_DR_OE)
pup->port.icount.overrun++;
ch &= pup->port.read_status_mask;
if (ch & REG_DR_BE)
flag = TTY_BREAK;
else if (ch & REG_DR_PE)
flag = TTY_PARITY;
else if (ch & REG_DR_FE)
flag = TTY_FRAME;
}
if (uart_handle_sysrq_char(&pup->port, ch & 255))
continue;
uart_insert_char(&pup->port, ch, REG_DR_OE, ch, flag);
}
return fifotaken;
}
static void phytium_rx_chars(struct phytium_uart_port *pup)
__releases(&pup->port.lock)
__acquires(&pup->port.lock)
{
phytium_fifo_to_tty(pup);
spin_unlock(&pup->port.lock);
tty_flip_buffer_push(&pup->port.state->port);
spin_lock(&pup->port.lock);
}
static void phytium_stop_tx(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
pup->im &= ~REG_IMSC_TXIM;
phytium_uart_write(pup->im, pup, REG_IMSC);
}
static bool phytium_tx_char(struct phytium_uart_port *pup, unsigned char c,
bool from_irq)
{
if (unlikely(!from_irq) &&
phytium_uart_read(pup, REG_FR) & REG_FR_TXFF)
return false; /* unable to transmit character */
phytium_uart_write(c, pup, REG_DR);
pup->port.icount.tx++;
return true;
}
static bool phytium_tx_chars(struct phytium_uart_port *pup, bool from_irq)
{
struct circ_buf *xmit = &pup->port.state->xmit;
int count = pup->port.fifosize >> 1;
if (pup->port.x_char) {
if (!phytium_tx_char(pup, pup->port.x_char, from_irq))
return true;
pup->port.x_char = 0;
--count;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(&pup->port)) {
phytium_stop_tx(&pup->port);
return false;
}
do {
if (likely(from_irq) && count-- == 0)
break;
if (!phytium_tx_char(pup, xmit->buf[xmit->tail], from_irq))
break;
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
} while (!uart_circ_empty(xmit));
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_write_wakeup(&pup->port);
if (uart_circ_empty(xmit)) {
phytium_stop_tx(&pup->port);
return false;
}
return true;
}
static void phytium_modem_status(struct phytium_uart_port *pup)
{
unsigned int status, delta;
status = phytium_uart_read(pup, REG_FR) & (REG_FR_DCD|REG_FR_DSR|REG_FR_CTS);
delta = status ^ pup->old_status;
pup->old_status = status;
if (!delta)
return;
if (delta & REG_FR_DCD)
uart_handle_dcd_change(&pup->port, status & REG_FR_DCD);
if (delta & REG_FR_DSR)
pup->port.icount.dsr++;
if (delta & REG_FR_CTS)
uart_handle_cts_change(&pup->port, status & REG_FR_CTS);
wake_up_interruptible(&pup->port.state->port.delta_msr_wait);
}
static irqreturn_t phytium_uart_interrupt(int irq, void *dev_id)
{
struct phytium_uart_port *pup = dev_id;
unsigned long flags;
unsigned int status, pass_counter = 256;
int handled = 0;
spin_lock_irqsave(&pup->port.lock, flags);
status = phytium_uart_read(pup, REG_RIS) & pup->im;
if (status) {
do {
phytium_uart_write(status & ~(REG_ICR_TXIS|REG_ICR_RTIS|REG_ICR_RXIS),
pup, REG_ICR);
if (status & (REG_ICR_RTIS|REG_ICR_RXIS))
phytium_rx_chars(pup);
if (status & (REG_ICR_DSRMIS|REG_ICR_DCDMIS|
REG_ICR_CTSMIS|REG_ICR_RIMIS))
phytium_modem_status(pup);
if (status & REG_ICR_TXIS)
phytium_tx_chars(pup, true);
if (pass_counter-- == 0)
break;
status = phytium_uart_read(pup, REG_RIS) & pup->im;
} while (status != 0);
handled = 1;
}
spin_unlock_irqrestore(&pup->port.lock, flags);
return IRQ_RETVAL(handled);
}
static unsigned int phytium_tx_empty(struct uart_port *port)
{
unsigned int status;
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
status = phytium_uart_read(pup, REG_FR) & (REG_FR_BUSY | REG_FR_TXFF);
return status ? 0 : TIOCSER_TEMT;
}
static void phytium_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
unsigned int cr;
cr = phytium_uart_read(pup, REG_CR);
#define TIOCMBIT(tiocmbit, uartbit) \
do { \
if (mctrl & tiocmbit) \
cr |= uartbit; \
else \
cr &= ~uartbit; \
} while (0)
TIOCMBIT(TIOCM_RTS, REG_CR_RTS);
TIOCMBIT(TIOCM_DTR, REG_CR_DTR);
TIOCMBIT(TIOCM_OUT1, REG_CR_OUT1);
TIOCMBIT(TIOCM_OUT2, REG_CR_OUT2);
TIOCMBIT(TIOCM_LOOP, REG_CR_LBE);
if (port->status & UPSTAT_AUTORTS) {
/* We need to disable auto-RTS if we want to turn RTS off */
TIOCMBIT(TIOCM_RTS, REG_CR_RTSEN);
}
#undef TIOCMBIT
phytium_uart_write(cr, pup, REG_CR);
}
static unsigned int phytium_get_mctrl(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
unsigned int cr = 0;
unsigned int status = phytium_uart_read(pup, REG_FR);
#define TIOCMBIT(uartbit, tiocmbit) \
do { \
if (status & uartbit) \
cr |= tiocmbit; \
} while (0)
TIOCMBIT(REG_FR_DCD, TIOCM_CAR);
TIOCMBIT(REG_FR_DSR, TIOCM_DSR);
TIOCMBIT(REG_FR_CTS, TIOCM_CTS);
TIOCMBIT(REG_FR_RI, TIOCM_RNG);
#undef TIOCMBIT
return cr;
}
static void phytium_start_tx(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
if (phytium_tx_chars(pup, false)) {
pup->im |= REG_IMSC_TXIM;
phytium_uart_write(pup->im, pup, REG_IMSC);
}
}
static void phytium_stop_rx(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
pup->im &= ~(REG_IMSC_RXIM|REG_IMSC_RTIM|REG_IMSC_FEIM|
REG_IMSC_PEIM|REG_IMSC_BEIM|REG_IMSC_OEIM);
phytium_uart_write(pup->im, pup, REG_IMSC);
}
static void phytium_enable_ms(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
pup->im |= REG_IMSC_RIMIM|REG_IMSC_CTSMIM|REG_IMSC_DCDMIM|REG_IMSC_DSRMIM;
phytium_uart_write(pup->im, pup, REG_IMSC);
}
static void phytium_break_ctl(struct uart_port *port, int break_state)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
unsigned long flags;
unsigned int lcr_h;
spin_lock_irqsave(&pup->port.lock, flags);
lcr_h = phytium_uart_read(pup, REG_LCRH_TX);
if (break_state == -1)
lcr_h |= REG_LCRH_BRK;
else
lcr_h &= ~REG_LCRH_BRK;
phytium_uart_write(lcr_h, pup, REG_LCRH_TX);
spin_unlock_irqrestore(&pup->port.lock, flags);
}
static int phytium_hwinit(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
/* XXX: more configurable setup method in future */
pup->port.uartclk = DEFAULT_UARTCLK;
/* Clear pending error and receive interrupts */
phytium_uart_write(REG_ICR_OEIS | REG_ICR_BEIS | REG_ICR_PEIS |
REG_ICR_FEIS | REG_ICR_RTIS | REG_ICR_RXIS,
pup, REG_ICR);
/*
* Save interrupts enable mask, and enable RX interrupts in case if
* the interrupt is used for NMI entry.
*/
pup->im = phytium_uart_read(pup, REG_IMSC);
phytium_uart_write(REG_IMSC_RTIM | REG_IMSC_RXIM, pup, REG_IMSC);
return 0;
}
static int phytium_uart_allocate_irq(struct phytium_uart_port *pup)
{
phytium_uart_write(pup->im, pup, REG_IMSC);
return request_irq(pup->port.irq, phytium_uart_interrupt, IRQF_SHARED, DRV_NAME, pup);
}
static void phytium_enable_interrtups(struct phytium_uart_port *pup)
{
unsigned int i;
spin_lock_irq(&pup->port.lock);
/* Clear out any spuriously appearing RX interrupts */
phytium_uart_write(REG_ICR_RTIS | REG_ICR_RXIS, pup, REG_ICR);
/*
* RXIS is asserted only when the RX FIFO transitions from below
* to above the trigger threshold. If the RX FIFO is already
* full to the threashold this can't happen and RXIS will now be
* stuck off. Drain the RX FIFO explicitly to fix this:
*/
for (i = 0; i < pup->port.fifosize * 2; i++) {
if (phytium_uart_read(pup, REG_FR) & REG_FR_RXFE)
break;
phytium_uart_read(pup, REG_DR);
}
pup->im = REG_IMSC_RTIM | REG_IMSC_RXIM;
phytium_uart_write(pup->im, pup, REG_IMSC);
spin_unlock_irq(&pup->port.lock);
}
static int phytium_startup(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
unsigned int cr;
int ret = 0;
ret = phytium_hwinit(port);
if (ret)
goto out;
ret = phytium_uart_allocate_irq(pup);
if (ret)
goto out;
phytium_uart_write(REG_IFLS_RX4_8|REG_IFLS_TX4_8, pup, REG_IFLS);
spin_lock_irq(&pup->port.lock);
/* restore RTS and DTR */
cr = pup->old_cr & (REG_CR_RTS | REG_CR_DTR);
cr |= REG_CR_UARTEN | REG_CR_RXE | REG_CR_TXE;
phytium_uart_write(cr, pup, REG_CR);
spin_unlock_irq(&pup->port.lock);
/* initialise the old status of the modem signals */
pup->old_status = phytium_uart_read(pup, REG_FR) & (REG_FR_DCD|REG_FR_DSR|REG_FR_CTS);
phytium_enable_interrtups(pup);
out:
return ret;
}
static void phytium_shutdown_channel(struct phytium_uart_port *pup,
unsigned int lcrh)
{
unsigned long val;
val = phytium_uart_read(pup, lcrh);
val &= ~(REG_LCRH_BRK | REG_LCRH_FEN);
phytium_uart_write(val, pup, lcrh);
}
static void phytium_disable_uart(struct phytium_uart_port *pup)
{
unsigned int cr;
pup->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
spin_lock_irq(&pup->port.lock);
cr = phytium_uart_read(pup, REG_CR);
pup->old_cr = cr;
cr &= REG_CR_RTS | REG_CR_DTR;
cr |= REG_CR_UARTEN | REG_CR_TXE;
phytium_uart_write(cr, pup, REG_CR);
spin_unlock_irq(&pup->port.lock);
/*
* disable break condition and fifos
*/
phytium_shutdown_channel(pup, REG_LCRH_RX);
}
static void phytium_disable_interrupts(struct phytium_uart_port *pup)
{
spin_lock_irq(&pup->port.lock);
/* mask all interrupts and clear all pending ones */
pup->im = 0;
phytium_uart_write(pup->im, pup, REG_IMSC);
phytium_uart_write(0xffff, pup, REG_ICR);
spin_unlock_irq(&pup->port.lock);
}
static void phytium_shutdown(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
phytium_disable_interrupts(pup);
free_irq(pup->port.irq, pup);
phytium_disable_uart(pup);
if (pup->port.ops->flush_buffer)
pup->port.ops->flush_buffer(port);
}
static void
phytium_setup_status_masks(struct uart_port *port, struct ktermios *termios)
{
port->read_status_mask = REG_DR_OE | 255;
if (termios->c_iflag & INPCK)
port->read_status_mask |= REG_DR_FE | REG_DR_PE;
if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
port->read_status_mask |= REG_DR_BE;
/*
* Characters to ignore
*/
port->ignore_status_mask = 0;
if (termios->c_iflag & IGNPAR)
port->ignore_status_mask |= REG_DR_FE | REG_DR_PE;
if (termios->c_iflag & IGNBRK) {
port->ignore_status_mask |= REG_DR_BE;
/*
* If we're ignoring parity and break indicators,
* ignore overruns too (for real raw support).
*/
if (termios->c_iflag & IGNPAR)
port->ignore_status_mask |= REG_DR_OE;
}
/*
* Ignore all characters if CREAD is not set.
*/
if ((termios->c_cflag & CREAD) == 0)
port->ignore_status_mask |= UART_DUMMY_DR_RX;
}
static void
phytium_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
unsigned int lcr_h, old_cr;
unsigned long flags;
unsigned int baud, quot;
/* Ask the core to calculate the divisor for us. */
baud = uart_get_baud_rate(port, termios, old, 0, port->uartclk/16);
if (baud > port->uartclk/16)
quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud);
else
quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud);
switch (termios->c_cflag & CSIZE) {
case CS5:
lcr_h = REG_LCRH_WLEN_5;
break;
case CS6:
lcr_h = REG_LCRH_WLEN_6;
break;
case CS7:
lcr_h = REG_LCRH_WLEN_7;
break;
default: /* CS8 */
lcr_h = REG_LCRH_WLEN_8;
break;
}
if (termios->c_cflag & CSTOPB)
lcr_h |= REG_LCRH_STP2;
if (termios->c_cflag & PARENB) {
lcr_h |= REG_LCRH_PEN;
if (!(termios->c_cflag & PARODD))
lcr_h |= REG_LCRH_EPS;
if (termios->c_cflag & CMSPAR)
lcr_h |= REG_LCRH_SPS;
}
if (pup->port.fifosize > 1)
lcr_h |= REG_LCRH_FEN;
spin_lock_irqsave(&port->lock, flags);
/*
* Update the per-port timeout.
*/
uart_update_timeout(port, termios->c_cflag, baud);
phytium_setup_status_masks(port, termios);
if (UART_ENABLE_MS(port, termios->c_cflag))
phytium_enable_ms(port);
/* first, disable everything */
old_cr = phytium_uart_read(pup, REG_CR);
phytium_uart_write(0, pup, REG_CR);
if (termios->c_cflag & CRTSCTS) {
if (old_cr & REG_CR_RTS)
old_cr |= REG_CR_RTSEN;
old_cr |= REG_CR_CTSEN;
port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS;
} else {
old_cr &= ~(REG_CR_CTSEN | REG_CR_RTSEN);
port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
}
/* Set baud rate */
phytium_uart_write(quot & 0x3f, pup, REG_FBRD);
phytium_uart_write(quot >> 6, pup, REG_IBRD);
phytium_uart_write(lcr_h, pup, REG_LCRH_RX);
phytium_uart_write(old_cr, pup, REG_CR);
spin_unlock_irqrestore(&port->lock, flags);
}
static const char *phytium_type(struct uart_port *port)
{
struct phytium_uart_port *pup =
container_of(port, struct phytium_uart_port, port);
return pup->port.type == PORT_PHYTIUM ? pup->type : NULL;
}
static void phytium_release_port(struct uart_port *port)
{
/* Nothing to release ... */
}
static int phytium_request_port(struct uart_port *port)
{
/* UARTs always present */
return 0;
}
static void phytium_config_port(struct uart_port *port, int flags)
{
if (flags & UART_CONFIG_TYPE) {
port->type = PORT_PHYTIUM;
phytium_request_port(port);
}
}
static int phytium_verify_port(struct uart_port *port, struct serial_struct *ser)
{
int ret = 0;
if (ser->type != PORT_UNKNOWN && ser->type != PORT_PHYTIUM)
ret = -EINVAL;
if (ser->irq < 0 || ser->irq >= nr_irqs)
ret = -EINVAL;
if (ser->baud_base < 9600)
ret = -EINVAL;
return ret;
}
static const struct uart_ops phytium_uart_ops = {
.tx_empty = phytium_tx_empty,
.set_mctrl = phytium_set_mctrl,
.get_mctrl = phytium_get_mctrl,
.stop_tx = phytium_stop_tx,
.start_tx = phytium_start_tx,
.stop_rx = phytium_stop_rx,
.enable_ms = phytium_enable_ms,
.break_ctl = phytium_break_ctl,
.startup = phytium_startup,
.shutdown = phytium_shutdown,
.set_termios = phytium_set_termios,
.type = phytium_type,
.release_port = phytium_release_port,
.request_port = phytium_request_port,
.config_port = phytium_config_port,
.verify_port = phytium_verify_port,
};
static struct phytium_uart_port *uart_ports[UART_NR];
static struct uart_driver phytium_uart = {
.owner = THIS_MODULE,
.driver_name = DRV_NAME,
.dev_name = "ttyS",
.nr = UART_NR,
};
void phytium_unregister_port(struct phytium_uart_port *pup)
{
int i;
bool busy = false;
for (i = 0; i < ARRAY_SIZE(uart_ports); i++) {
if (uart_ports[i] == pup)
uart_ports[i] = NULL;
else if (uart_ports[i])
busy = true;
}
if (!busy)
uart_unregister_driver(&phytium_uart);
}
static int phytium_find_free_port(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(uart_ports); i++)
if (uart_ports[i] == NULL)
return i;
return -EBUSY;
}
static int phytium_register_port(struct phytium_uart_port *pup)
{
int rc;
/* Ensure interrupts from this UART are masked and cleared */
phytium_uart_write(0, pup, REG_IMSC);
phytium_uart_write(0xffff, pup, REG_ICR);
if (!phytium_uart.state) {
rc = uart_register_driver(&phytium_uart);
if (rc < 0) {
dev_err(pup->port.dev,
"Failed to register Phytium PCI UART driver\n");
return rc;
}
}
rc = uart_add_one_port(&phytium_uart, &pup->port);
if (rc)
phytium_unregister_port(pup);
return rc;
}
static int phytium_uart_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct phytium_uart_port *pup;
int portnr, rc;
portnr = phytium_find_free_port();
if (portnr < 0)
return portnr;
pup = devm_kzalloc(&pdev->dev, sizeof(struct phytium_uart_port),
GFP_KERNEL);
if (!pup)
return -ENOMEM;
rc = pcim_enable_device(pdev);
if (rc)
return rc;
rc = pcim_iomap_regions_request_all(pdev, 0x01, pci_name(pdev));
if (rc)
return rc;
pup->port.iotype = UPIO_MEM32;
pup->port.irq = pdev->irq;
pup->port.mapbase = pci_resource_start(pdev, 0);
pup->port.membase = pcim_iomap_table(pdev)[0];
pup->port.ops = &phytium_uart_ops;
pup->port.dev = &pdev->dev;
pup->port.fifosize = 32;
pup->port.flags = UPF_BOOT_AUTOCONF;
pup->port.line = portnr;
uart_ports[portnr] = pup;
pup->old_cr = 0;
snprintf(pup->type, sizeof(pup->type), "pci-uart");
pci_set_drvdata(pdev, pup);
return phytium_register_port(pup);
}
static void phytium_uart_remove(struct pci_dev *pdev)
{
struct phytium_uart_port *pup = pci_get_drvdata(pdev);
uart_remove_one_port(&phytium_uart, &pup->port);
phytium_unregister_port(pup);
}
#ifdef CONFIG_PM_SLEEP
static int phytium_uart_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct phytium_uart_port *pup = pci_get_drvdata(pdev);
if (pup)
uart_suspend_port(&phytium_uart, &pup->port);
return 0;
}
static int phytium_uart_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct phytium_uart_port *pup = pci_get_drvdata(pdev);
if (pup)
uart_resume_port(&phytium_uart, &pup->port);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(phytium_dev_pm_ops, phytium_uart_suspend, phytium_uart_resume);
static const struct pci_device_id pci_ids[] = {
{ PCI_VDEVICE(PHYTIUM, 0xdc2e) },
{ 0 }
};
MODULE_DEVICE_TABLE(pci, pci_ids);
static struct pci_driver phytium_uart_pci_driver = {
.name = DRV_NAME,
.probe = phytium_uart_probe,
.remove = phytium_uart_remove,
.driver = {
.pm = &phytium_dev_pm_ops,
},
.id_table = pci_ids,
};
static int __init phytium_uart_init(void)
{
pr_info("Serial: Phytium PCI UART driver\n");
return pci_register_driver(&phytium_uart_pci_driver);
}
static void __exit phytium_uart_exit(void)
{
pci_unregister_driver(&phytium_uart_pci_driver);
}
module_init(phytium_uart_init);
module_exit(phytium_uart_exit);
MODULE_AUTHOR("Chen Baozi <chenbaozi@phytium.com.cn>");
MODULE_DESCRIPTION("Phytium PCI serial port driver");
MODULE_LICENSE("GPL");

View File

@ -293,4 +293,7 @@
/* Freescale Linflex UART */
#define PORT_LINFLEXUART 122
/* Phytium PCI UART */
#define PORT_PHYTIUM 118
#endif /* _UAPILINUX_SERIAL_CORE_H */