summaryrefslogtreecommitdiff
path: root/drivers/tty/serial/vt8500_serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/serial/vt8500_serial.c')
-rw-r--r--drivers/tty/serial/vt8500_serial.c136
1 files changed, 116 insertions, 20 deletions
diff --git a/drivers/tty/serial/vt8500_serial.c b/drivers/tty/serial/vt8500_serial.c
index 15ad6fcda88b..b2bc9e8ba048 100644
--- a/drivers/tty/serial/vt8500_serial.c
+++ b/drivers/tty/serial/vt8500_serial.c
@@ -33,8 +33,8 @@
#include <linux/serial.h>
#include <linux/slab.h>
#include <linux/clk.h>
-#include <linux/platform_device.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/err.h>
/*
@@ -78,13 +78,40 @@
#define RX_FIFO_INTS (RXFAF | RXFF | RXOVER | PER | FER | RXTOUT)
#define TX_FIFO_INTS (TXFAE | TXFE | TXUDR)
+/*
+ * Line control bits
+ */
+
+#define VT8500_TXEN (1 << 0) /* Enable transmit logic */
+#define VT8500_RXEN (1 << 1) /* Enable receive logic */
+#define VT8500_CS8 (1 << 2) /* 8-bit data length (vs. 7-bit) */
+#define VT8500_CSTOPB (1 << 3) /* 2 stop bits (vs. 1) */
+#define VT8500_PARENB (1 << 4) /* Enable parity */
+#define VT8500_PARODD (1 << 5) /* Odd parity (vs. even) */
+#define VT8500_RTS (1 << 6) /* Ready to send */
+#define VT8500_LOOPBK (1 << 7) /* Enable internal loopback */
+#define VT8500_DMA (1 << 8) /* Enable DMA mode (needs FIFO) */
+#define VT8500_BREAK (1 << 9) /* Initiate break signal */
+#define VT8500_PSLVERR (1 << 10) /* APB error upon empty RX FIFO read */
+#define VT8500_SWRTSCTS (1 << 11) /* Software-controlled RTS/CTS */
+
+/*
+ * Capability flags (driver-internal)
+ */
+
+#define VT8500_HAS_SWRTSCTS_SWITCH (1 << 1)
+
+#define VT8500_RECOMMENDED_CLK 12000000
+#define VT8500_OVERSAMPLING_DIVISOR 13
#define VT8500_MAX_PORTS 6
struct vt8500_port {
struct uart_port uart;
char name[16];
struct clk *clk;
+ unsigned int clk_predivisor;
unsigned int ier;
+ unsigned int vt8500_uart_flags;
};
/*
@@ -267,31 +294,45 @@ static unsigned int vt8500_get_mctrl(struct uart_port *port)
static void vt8500_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
+ unsigned int lcr = vt8500_read(port, VT8500_URLCR);
+
+ if (mctrl & TIOCM_RTS)
+ lcr |= VT8500_RTS;
+ else
+ lcr &= ~VT8500_RTS;
+
+ vt8500_write(port, lcr, VT8500_URLCR);
}
static void vt8500_break_ctl(struct uart_port *port, int break_ctl)
{
if (break_ctl)
- vt8500_write(port, vt8500_read(port, VT8500_URLCR) | (1 << 9),
+ vt8500_write(port,
+ vt8500_read(port, VT8500_URLCR) | VT8500_BREAK,
VT8500_URLCR);
}
static int vt8500_set_baud_rate(struct uart_port *port, unsigned int baud)
{
+ struct vt8500_port *vt8500_port =
+ container_of(port, struct vt8500_port, uart);
unsigned long div;
unsigned int loops = 1000;
- div = vt8500_read(port, VT8500_URDIV) & ~(0x3ff);
+ div = ((vt8500_port->clk_predivisor - 1) & 0xf) << 16;
+ div |= (uart_get_divisor(port, baud) - 1) & 0x3ff;
- if (unlikely((baud < 900) || (baud > 921600)))
- div |= 7;
- else
- div |= (921600 / baud) - 1;
+ /* Effective baud rate */
+ baud = port->uartclk / 16 / ((div & 0x3ff) + 1);
while ((vt8500_read(port, VT8500_URUSR) & (1 << 5)) && --loops)
cpu_relax();
+
vt8500_write(port, div, VT8500_URDIV);
+ /* Break signal timing depends on baud rate, update accordingly */
+ vt8500_write(port, mult_frac(baud, 4096, 1000000), VT8500_URBKR);
+
return baud;
}
@@ -347,31 +388,35 @@ static void vt8500_set_termios(struct uart_port *port,
/* calculate parity */
lcr = vt8500_read(&vt8500_port->uart, VT8500_URLCR);
- lcr &= ~((1 << 5) | (1 << 4));
+ lcr &= ~(VT8500_PARENB | VT8500_PARODD);
if (termios->c_cflag & PARENB) {
- lcr |= (1 << 4);
+ lcr |= VT8500_PARENB;
termios->c_cflag &= ~CMSPAR;
if (termios->c_cflag & PARODD)
- lcr |= (1 << 5);
+ lcr |= VT8500_PARODD;
}
/* calculate bits per char */
- lcr &= ~(1 << 2);
+ lcr &= ~VT8500_CS8;
switch (termios->c_cflag & CSIZE) {
case CS7:
break;
case CS8:
default:
- lcr |= (1 << 2);
+ lcr |= VT8500_CS8;
termios->c_cflag &= ~CSIZE;
termios->c_cflag |= CS8;
break;
}
/* calculate stop bits */
- lcr &= ~(1 << 3);
+ lcr &= ~VT8500_CSTOPB;
if (termios->c_cflag & CSTOPB)
- lcr |= (1 << 3);
+ lcr |= VT8500_CSTOPB;
+
+ lcr &= ~VT8500_SWRTSCTS;
+ if (vt8500_port->vt8500_uart_flags & VT8500_HAS_SWRTSCTS_SWITCH)
+ lcr |= VT8500_SWRTSCTS;
/* set parity, bits per char, and stop bit */
vt8500_write(&vt8500_port->uart, lcr, VT8500_URLCR);
@@ -521,6 +566,33 @@ static struct console vt8500_console = {
#define VT8500_CONSOLE NULL
#endif
+#ifdef CONFIG_CONSOLE_POLL
+static int vt8500_get_poll_char(struct uart_port *port)
+{
+ unsigned int status = vt8500_read(port, VT8500_URFIDX);
+
+ if (!(status & 0x1f00))
+ return NO_POLL_CHAR;
+
+ return vt8500_read(port, VT8500_RXFIFO) & 0xff;
+}
+
+static void vt8500_put_poll_char(struct uart_port *port, unsigned char c)
+{
+ unsigned int status, tmout = 10000;
+
+ do {
+ status = vt8500_read(port, VT8500_URFIDX);
+
+ if (--tmout == 0)
+ break;
+ udelay(1);
+ } while (status & 0x10);
+
+ vt8500_write(port, c, VT8500_TXFIFO);
+}
+#endif
+
static struct uart_ops vt8500_uart_pops = {
.tx_empty = vt8500_tx_empty,
.set_mctrl = vt8500_set_mctrl,
@@ -538,6 +610,10 @@ static struct uart_ops vt8500_uart_pops = {
.request_port = vt8500_request_port,
.config_port = vt8500_config_port,
.verify_port = vt8500_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_get_char = vt8500_get_poll_char,
+ .poll_put_char = vt8500_put_poll_char,
+#endif
};
static struct uart_driver vt8500_uart_driver = {
@@ -548,14 +624,31 @@ static struct uart_driver vt8500_uart_driver = {
.cons = VT8500_CONSOLE,
};
+static unsigned int vt8500_flags; /* none required so far */
+static unsigned int wm8880_flags = VT8500_HAS_SWRTSCTS_SWITCH;
+
+static const struct of_device_id wmt_dt_ids[] = {
+ { .compatible = "via,vt8500-uart", .data = &vt8500_flags},
+ { .compatible = "wm,wm8880-uart", .data = &wm8880_flags},
+ {}
+};
+
static int vt8500_serial_probe(struct platform_device *pdev)
{
struct vt8500_port *vt8500_port;
struct resource *mmres, *irqres;
struct device_node *np = pdev->dev.of_node;
+ const struct of_device_id *match;
+ const unsigned int *flags;
int ret;
int port;
+ match = of_match_device(wmt_dt_ids, &pdev->dev);
+ if (!match)
+ return -EINVAL;
+
+ flags = match->data;
+
mmres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
irqres = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!mmres || !irqres)
@@ -605,6 +698,11 @@ static int vt8500_serial_probe(struct platform_device *pdev)
return ret;
}
+ vt8500_port->vt8500_uart_flags = *flags;
+ vt8500_port->clk_predivisor = DIV_ROUND_CLOSEST(
+ clk_get_rate(vt8500_port->clk),
+ VT8500_RECOMMENDED_CLK
+ );
vt8500_port->uart.type = PORT_VT8500;
vt8500_port->uart.iotype = UPIO_MEM;
vt8500_port->uart.mapbase = mmres->start;
@@ -615,7 +713,10 @@ static int vt8500_serial_probe(struct platform_device *pdev)
vt8500_port->uart.dev = &pdev->dev;
vt8500_port->uart.flags = UPF_IOREMAP | UPF_BOOT_AUTOCONF;
- vt8500_port->uart.uartclk = clk_get_rate(vt8500_port->clk);
+ /* Serial core uses the magic "16" everywhere - adjust for it */
+ vt8500_port->uart.uartclk = 16 * clk_get_rate(vt8500_port->clk) /
+ vt8500_port->clk_predivisor /
+ VT8500_OVERSAMPLING_DIVISOR;
snprintf(vt8500_port->name, sizeof(vt8500_port->name),
"VT8500 UART%d", pdev->id);
@@ -639,11 +740,6 @@ static int vt8500_serial_remove(struct platform_device *pdev)
return 0;
}
-static const struct of_device_id wmt_dt_ids[] = {
- { .compatible = "via,vt8500-uart", },
- {}
-};
-
static struct platform_driver vt8500_platform_driver = {
.probe = vt8500_serial_probe,
.remove = vt8500_serial_remove,