Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 30530791 authored by Wilson Ding's avatar Wilson Ding Committed by Greg Kroah-Hartman
Browse files

serial: mvebu-uart: initial support for Armada-3700 serial port



Armada-3700's uart is a simple serial port, which doesn't
support. Configuring the modem control lines. The uart port has a 32
bytes Tx FIFO and a 64 bytes Rx FIFO

The uart driver implements the uart core operations. It also support the
system (early) console based on Armada-3700's serial port.

Known Issue:

The uart driver currently doesn't support clock programming, which means
the baud-rate stays with the default value configured by the bootloader
at boot time

[gregory.clement@free-electrons.com: Rewrite many part which are too long
to enumerate]

Signed-off-by: default avatarWilson Ding <dingwei@marvell.com>
Signed-off-by: default avatarNadav Haklai <nadavh@marvell.com>
Signed-off-by: default avatarGregory CLEMENT <gregory.clement@free-electrons.com>
Acked-by: default avatarRob Herring <robh@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 82a3f87f
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
* Marvell UART : Non standard UART used in some of Marvell EBU SoCs (e.g., Armada-3700)

Required properties:
- compatible: "marvell,armada-3700-uart"
- reg: offset and length of the register set for the device.
- interrupts: device interrupt

Example:
	serial@12000 {
		compatible = "marvell,armada-3700-uart";
		reg = <0x12000 0x400>;
		interrupts = <43>;
	};
+6 −0
Original line number Diff line number Diff line
@@ -1058,6 +1058,12 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
			A valid base address must be provided, and the serial
			port must already be setup and configured.

		armada3700_uart,<addr>
			Start an early, polled-mode console on the
			Armada 3700 serial port at the specified
			address. The serial port must already be setup
			and configured. Options are not yet supported.

	earlyprintk=	[X86,SH,BLACKFIN,ARM,M68k]
			earlyprintk=vga
			earlyprintk=efi
+22 −0
Original line number Diff line number Diff line
@@ -1602,6 +1602,28 @@ config SERIAL_STM32_CONSOLE
	depends on SERIAL_STM32=y
	select SERIAL_CORE_CONSOLE

config SERIAL_MVEBU_UART
	bool "Marvell EBU serial port support"
	select SERIAL_CORE
	help
	  This driver is for Marvell EBU SoC's UART. If you have a machine
	  based on the Armada-3700 SoC and wish to use the on-board serial
	  port,
	  say 'Y' here.
	  Otherwise, say 'N'.

config SERIAL_MVEBU_CONSOLE
	bool "Console on Marvell EBU serial port"
	depends on SERIAL_MVEBU_UART
	select SERIAL_CORE_CONSOLE
	select SERIAL_EARLYCON
	default y
	help
	  Say 'Y' here if you wish to use Armada-3700 UART as the system console.
	  (the system console is the device which receives all kernel messages
	  and warnings and which allows logins in single user mode)
	  Otherwise, say 'N'.

endmenu

config SERIAL_MCTRL_GPIO
+1 −0
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ obj-$(CONFIG_SERIAL_CONEXANT_DIGICOLOR) += digicolor-usart.o
obj-$(CONFIG_SERIAL_MEN_Z135)	+= men_z135_uart.o
obj-$(CONFIG_SERIAL_SPRD) += sprd_serial.o
obj-$(CONFIG_SERIAL_STM32)	+= stm32-usart.o
obj-$(CONFIG_SERIAL_MVEBU_UART)	+= mvebu-uart.o

# GPIOLIB helpers for modem control lines
obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
+650 −0
Original line number Diff line number Diff line
/*
* ***************************************************************************
* Copyright (C) 2015 Marvell International Ltd.
* ***************************************************************************
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 2 of the License, or any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
* ***************************************************************************
*/

#include <linux/clk.h>
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/slab.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>

/* Register Map */
#define UART_RBR		0x00
#define  RBR_BRK_DET		BIT(15)
#define  RBR_FRM_ERR_DET	BIT(14)
#define  RBR_PAR_ERR_DET	BIT(13)
#define  RBR_OVR_ERR_DET	BIT(12)

#define UART_TSH		0x04

#define UART_CTRL		0x08
#define  CTRL_SOFT_RST		BIT(31)
#define  CTRL_TXFIFO_RST	BIT(15)
#define  CTRL_RXFIFO_RST	BIT(14)
#define  CTRL_ST_MIRR_EN	BIT(13)
#define  CTRL_LPBK_EN		BIT(12)
#define  CTRL_SND_BRK_SEQ	BIT(11)
#define  CTRL_PAR_EN		BIT(10)
#define  CTRL_TWO_STOP		BIT(9)
#define  CTRL_TX_HFL_INT	BIT(8)
#define  CTRL_RX_HFL_INT	BIT(7)
#define  CTRL_TX_EMP_INT	BIT(6)
#define  CTRL_TX_RDY_INT	BIT(5)
#define  CTRL_RX_RDY_INT	BIT(4)
#define  CTRL_BRK_DET_INT	BIT(3)
#define  CTRL_FRM_ERR_INT	BIT(2)
#define  CTRL_PAR_ERR_INT	BIT(1)
#define  CTRL_OVR_ERR_INT	BIT(0)
#define  CTRL_RX_INT			(CTRL_RX_RDY_INT | CTRL_BRK_DET_INT |\
	CTRL_FRM_ERR_INT | CTRL_PAR_ERR_INT | CTRL_OVR_ERR_INT)

#define UART_STAT		0x0c
#define  STAT_TX_FIFO_EMP	BIT(13)
#define  STAT_RX_FIFO_EMP	BIT(12)
#define  STAT_TX_FIFO_FUL	BIT(11)
#define  STAT_TX_FIFO_HFL	BIT(10)
#define  STAT_RX_TOGL		BIT(9)
#define  STAT_RX_FIFO_FUL	BIT(8)
#define  STAT_RX_FIFO_HFL	BIT(7)
#define  STAT_TX_EMP		BIT(6)
#define  STAT_TX_RDY		BIT(5)
#define  STAT_RX_RDY		BIT(4)
#define  STAT_BRK_DET		BIT(3)
#define  STAT_FRM_ERR		BIT(2)
#define  STAT_PAR_ERR		BIT(1)
#define  STAT_OVR_ERR		BIT(0)
#define  STAT_BRK_ERR		(STAT_BRK_DET | STAT_FRM_ERR | STAT_FRM_ERR\
				 | STAT_PAR_ERR | STAT_OVR_ERR)

#define UART_BRDV		0x10

#define MVEBU_NR_UARTS		1

#define MVEBU_UART_TYPE		"mvebu-uart"

static struct uart_port mvebu_uart_ports[MVEBU_NR_UARTS];

struct mvebu_uart_data {
	struct uart_port *port;
	struct clk       *clk;
};

/* Core UART Driver Operations */
static unsigned int mvebu_uart_tx_empty(struct uart_port *port)
{
	unsigned long flags;
	unsigned int st;

	spin_lock_irqsave(&port->lock, flags);
	st = readl(port->membase + UART_STAT);
	spin_unlock_irqrestore(&port->lock, flags);

	return (st & STAT_TX_FIFO_EMP) ? TIOCSER_TEMT : 0;
}

static unsigned int mvebu_uart_get_mctrl(struct uart_port *port)
{
	return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR;
}

static void mvebu_uart_set_mctrl(struct uart_port *port,
				 unsigned int mctrl)
{
/*
 * Even if we do not support configuring the modem control lines, this
 * function must be proided to the serial core
 */
}

static void mvebu_uart_stop_tx(struct uart_port *port)
{
	unsigned int ctl = readl(port->membase + UART_CTRL);

	ctl &= ~CTRL_TX_RDY_INT;
	writel(ctl, port->membase + UART_CTRL);
}

static void mvebu_uart_start_tx(struct uart_port *port)
{
	unsigned int ctl = readl(port->membase + UART_CTRL);

	ctl |= CTRL_TX_RDY_INT;
	writel(ctl, port->membase + UART_CTRL);
}

static void mvebu_uart_stop_rx(struct uart_port *port)
{
	unsigned int ctl = readl(port->membase + UART_CTRL);

	ctl &= ~CTRL_RX_INT;
	writel(ctl, port->membase + UART_CTRL);
}

static void mvebu_uart_break_ctl(struct uart_port *port, int brk)
{
	unsigned int ctl;
	unsigned long flags;

	spin_lock_irqsave(&port->lock, flags);
	ctl = readl(port->membase + UART_CTRL);
	if (brk == -1)
		ctl |= CTRL_SND_BRK_SEQ;
	else
		ctl &= ~CTRL_SND_BRK_SEQ;
	writel(ctl, port->membase + UART_CTRL);
	spin_unlock_irqrestore(&port->lock, flags);
}

static void mvebu_uart_rx_chars(struct uart_port *port, unsigned int status)
{
	struct tty_port *tport = &port->state->port;
	unsigned char ch = 0;
	char flag = 0;

	do {
		if (status & STAT_RX_RDY) {
			ch = readl(port->membase + UART_RBR);
			ch &= 0xff;
			flag = TTY_NORMAL;
			port->icount.rx++;

			if (status & STAT_PAR_ERR)
				port->icount.parity++;
		}

		if (status & STAT_BRK_DET) {
			port->icount.brk++;
			status &= ~(STAT_FRM_ERR | STAT_PAR_ERR);
			if (uart_handle_break(port))
				goto ignore_char;
		}

		if (status & STAT_OVR_ERR)
			port->icount.overrun++;

		if (status & STAT_FRM_ERR)
			port->icount.frame++;

		if (uart_handle_sysrq_char(port, ch))
			goto ignore_char;

		if (status & port->ignore_status_mask & STAT_PAR_ERR)
			status &= ~STAT_RX_RDY;

		status &= port->read_status_mask;

		if (status & STAT_PAR_ERR)
			flag = TTY_PARITY;

		status &= ~port->ignore_status_mask;

		if (status & STAT_RX_RDY)
			tty_insert_flip_char(tport, ch, flag);

		if (status & STAT_BRK_DET)
			tty_insert_flip_char(tport, 0, TTY_BREAK);

		if (status & STAT_FRM_ERR)
			tty_insert_flip_char(tport, 0, TTY_FRAME);

		if (status & STAT_OVR_ERR)
			tty_insert_flip_char(tport, 0, TTY_OVERRUN);

ignore_char:
		status = readl(port->membase + UART_STAT);
	} while (status & (STAT_RX_RDY | STAT_BRK_DET));

	tty_flip_buffer_push(tport);
}

static void mvebu_uart_tx_chars(struct uart_port *port, unsigned int status)
{
	struct circ_buf *xmit = &port->state->xmit;
	unsigned int count;
	unsigned int st;

	if (port->x_char) {
		writel(port->x_char, port->membase + UART_TSH);
		port->icount.tx++;
		port->x_char = 0;
		return;
	}

	if (uart_circ_empty(xmit) || uart_tx_stopped(port)) {
		mvebu_uart_stop_tx(port);
		return;
	}

	for (count = 0; count < port->fifosize; count++) {
		writel(xmit->buf[xmit->tail], port->membase + UART_TSH);
		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
		port->icount.tx++;

		if (uart_circ_empty(xmit))
			break;

		st = readl(port->membase + UART_STAT);
		if (st & STAT_TX_FIFO_FUL)
			break;
	}

	if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
		uart_write_wakeup(port);

	if (uart_circ_empty(xmit))
		mvebu_uart_stop_tx(port);
}

static irqreturn_t mvebu_uart_isr(int irq, void *dev_id)
{
	struct uart_port *port = (struct uart_port *)dev_id;
	unsigned int st = readl(port->membase + UART_STAT);

	if (st & (STAT_RX_RDY | STAT_OVR_ERR | STAT_FRM_ERR | STAT_BRK_DET))
		mvebu_uart_rx_chars(port, st);

	if (st & STAT_TX_RDY)
		mvebu_uart_tx_chars(port, st);

	return IRQ_HANDLED;
}

static int mvebu_uart_startup(struct uart_port *port)
{
	int ret;

	writel(CTRL_TXFIFO_RST | CTRL_RXFIFO_RST,
	       port->membase + UART_CTRL);
	udelay(1);
	writel(CTRL_RX_INT, port->membase + UART_CTRL);

	ret = request_irq(port->irq, mvebu_uart_isr, port->irqflags, "serial",
			  port);
	if (ret) {
		dev_err(port->dev, "failed to request irq\n");
		return ret;
	}

	return 0;
}

static void mvebu_uart_shutdown(struct uart_port *port)
{
	writel(0, port->membase + UART_CTRL);
}

static void mvebu_uart_set_termios(struct uart_port *port,
				   struct ktermios *termios,
				   struct ktermios *old)
{
	unsigned long flags;
	unsigned int baud;

	spin_lock_irqsave(&port->lock, flags);

	port->read_status_mask = STAT_RX_RDY | STAT_OVR_ERR |
		STAT_TX_RDY | STAT_TX_FIFO_FUL;

	if (termios->c_iflag & INPCK)
		port->read_status_mask |= STAT_FRM_ERR | STAT_PAR_ERR;

	port->ignore_status_mask = 0;
	if (termios->c_iflag & IGNPAR)
		port->ignore_status_mask |=
			STAT_FRM_ERR | STAT_PAR_ERR | STAT_OVR_ERR;

	if ((termios->c_cflag & CREAD) == 0)
		port->ignore_status_mask |= STAT_RX_RDY | STAT_BRK_ERR;

	if (old)
		tty_termios_copy_hw(termios, old);

	baud = uart_get_baud_rate(port, termios, old, 0, 460800);
	uart_update_timeout(port, termios->c_cflag, baud);

	spin_unlock_irqrestore(&port->lock, flags);
}

static const char *mvebu_uart_type(struct uart_port *port)
{
	return MVEBU_UART_TYPE;
}

static void mvebu_uart_release_port(struct uart_port *port)
{
	/* Nothing to do here */
}

static int mvebu_uart_request_port(struct uart_port *port)
{
	return 0;
}

#ifdef CONFIG_CONSOLE_POLL
static int mvebu_uart_get_poll_char(struct uart_port *port)
{
	unsigned int st = readl(port->membase + UART_STAT);

	if (!(st & STAT_RX_RDY))
		return NO_POLL_CHAR;

	return readl(port->membase + UART_RBR);
}

static void mvebu_uart_put_poll_char(struct uart_port *port, unsigned char c)
{
	unsigned int st;

	for (;;) {
		st = readl(port->membase + UART_STAT);

		if (!(st & STAT_TX_FIFO_FUL))
			break;

		udelay(1);
	}

	writel(c, port->membase + UART_TSH);
}
#endif

static const struct uart_ops mvebu_uart_ops = {
	.tx_empty	= mvebu_uart_tx_empty,
	.set_mctrl	= mvebu_uart_set_mctrl,
	.get_mctrl	= mvebu_uart_get_mctrl,
	.stop_tx	= mvebu_uart_stop_tx,
	.start_tx	= mvebu_uart_start_tx,
	.stop_rx	= mvebu_uart_stop_rx,
	.break_ctl	= mvebu_uart_break_ctl,
	.startup	= mvebu_uart_startup,
	.shutdown	= mvebu_uart_shutdown,
	.set_termios	= mvebu_uart_set_termios,
	.type		= mvebu_uart_type,
	.release_port	= mvebu_uart_release_port,
	.request_port	= mvebu_uart_request_port,
#ifdef CONFIG_CONSOLE_POLL
	.poll_get_char	= mvebu_uart_get_poll_char,
	.poll_put_char	= mvebu_uart_put_poll_char,
#endif
};

/* Console Driver Operations  */

#ifdef CONFIG_SERIAL_MVEBU_CONSOLE
/* Early Console */
static void mvebu_uart_putc(struct uart_port *port, int c)
{
	unsigned int st;

	for (;;) {
		st = readl(port->membase + UART_STAT);
		if (!(st & STAT_TX_FIFO_FUL))
			break;
	}

	writel(c, port->membase + UART_TSH);

	for (;;) {
		st = readl(port->membase + UART_STAT);
		if (st & STAT_TX_FIFO_EMP)
			break;
	}
}

static void mvebu_uart_putc_early_write(struct console *con,
					const char *s,
					unsigned n)
{
	struct earlycon_device *dev = con->data;

	uart_console_write(&dev->port, s, n, mvebu_uart_putc);
}

static int __init
mvebu_uart_early_console_setup(struct earlycon_device *device,
			       const char *opt)
{
	if (!device->port.membase)
		return -ENODEV;

	device->con->write = mvebu_uart_putc_early_write;

	return 0;
}

EARLYCON_DECLARE(ar3700_uart, mvebu_uart_early_console_setup);
OF_EARLYCON_DECLARE(ar3700_uart, "marvell,armada-3700-uart",
		    mvebu_uart_early_console_setup);

static void wait_for_xmitr(struct uart_port *port)
{
	u32 val;

	readl_poll_timeout_atomic(port->membase + UART_STAT, val,
				  (val & STAT_TX_EMP), 1, 10000);
}

static void mvebu_uart_console_putchar(struct uart_port *port, int ch)
{
	wait_for_xmitr(port);
	writel(ch, port->membase + UART_TSH);
}

static void mvebu_uart_console_write(struct console *co, const char *s,
				     unsigned int count)
{
	struct uart_port *port = &mvebu_uart_ports[co->index];
	unsigned long flags;
	unsigned int ier;
	int locked = 1;

	if (oops_in_progress)
		locked = spin_trylock_irqsave(&port->lock, flags);
	else
		spin_lock_irqsave(&port->lock, flags);

	ier = readl(port->membase + UART_CTRL) &
		(CTRL_RX_INT | CTRL_TX_RDY_INT);
	writel(0, port->membase + UART_CTRL);

	uart_console_write(port, s, count, mvebu_uart_console_putchar);

	wait_for_xmitr(port);

	if (ier)
		writel(ier, port->membase + UART_CTRL);

	if (locked)
		spin_unlock_irqrestore(&port->lock, flags);
}

static int mvebu_uart_console_setup(struct console *co, char *options)
{
	struct uart_port *port;
	int baud = 9600;
	int bits = 8;
	int parity = 'n';
	int flow = 'n';

	if (co->index < 0 || co->index >= MVEBU_NR_UARTS)
		return -EINVAL;

	port = &mvebu_uart_ports[co->index];

	if (!port->mapbase || !port->membase) {
		pr_debug("console on ttyMV%i not present\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 mvebu_uart_driver;

static struct console mvebu_uart_console = {
	.name	= "ttyMV",
	.write	= mvebu_uart_console_write,
	.device	= uart_console_device,
	.setup	= mvebu_uart_console_setup,
	.flags	= CON_PRINTBUFFER,
	.index	= -1,
	.data	= &mvebu_uart_driver,
};

static int __init mvebu_uart_console_init(void)
{
	register_console(&mvebu_uart_console);
	return 0;
}

console_initcall(mvebu_uart_console_init);


#endif /* CONFIG_SERIAL_MVEBU_CONSOLE */

static struct uart_driver mvebu_uart_driver = {
	.owner			= THIS_MODULE,
	.driver_name		= "mvebu_serial",
	.dev_name		= "ttyMV",
	.nr			= MVEBU_NR_UARTS,
#ifdef CONFIG_SERIAL_MVEBU_CONSOLE
	.cons			= &mvebu_uart_console,
#endif
};

static int mvebu_uart_probe(struct platform_device *pdev)
{
	struct resource *reg = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	struct resource *irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	struct uart_port *port;
	struct mvebu_uart_data *data;
	int ret;

	if (!reg || !irq) {
		dev_err(&pdev->dev, "no registers/irq defined\n");
		return -EINVAL;
	}

	port = &mvebu_uart_ports[0];

	spin_lock_init(&port->lock);

	port->dev        = &pdev->dev;
	port->type       = PORT_MVEBU;
	port->ops        = &mvebu_uart_ops;
	port->regshift   = 0;

	port->fifosize   = 32;
	port->iotype     = UPIO_MEM32;
	port->flags      = UPF_FIXED_PORT;
	port->line       = 0; /* single port: force line number to  0 */

	port->irq        = irq->start;
	port->irqflags   = 0;
	port->mapbase    = reg->start;

	port->membase = devm_ioremap_resource(&pdev->dev, reg);
	if (IS_ERR(port->membase))
		return -PTR_ERR(port->membase);

	data = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_uart_data),
			    GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	data->port = port;

	port->private_data = data;
	platform_set_drvdata(pdev, data);

	ret = uart_add_one_port(&mvebu_uart_driver, port);
	if (ret)
		return ret;
	return 0;
}

static int mvebu_uart_remove(struct platform_device *pdev)
{
	struct mvebu_uart_data *data = platform_get_drvdata(pdev);

	uart_remove_one_port(&mvebu_uart_driver, data->port);
	data->port->private_data = NULL;
	data->port->mapbase      = 0;
	return 0;
}

/* Match table for of_platform binding */
static const struct of_device_id mvebu_uart_of_match[] = {
	{ .compatible = "marvell,armada-3700-uart", },
	{}
};
MODULE_DEVICE_TABLE(of, mvebu_uart_of_match);

static struct platform_driver mvebu_uart_platform_driver = {
	.probe	= mvebu_uart_probe,
	.remove	= mvebu_uart_remove,
	.driver	= {
		.owner	= THIS_MODULE,
		.name  = "mvebu-uart",
		.of_match_table = of_match_ptr(mvebu_uart_of_match),
	},
};

static int __init mvebu_uart_init(void)
{
	int ret;

	ret = uart_register_driver(&mvebu_uart_driver);
	if (ret)
		return ret;

	ret = platform_driver_register(&mvebu_uart_platform_driver);
	if (ret)
		uart_unregister_driver(&mvebu_uart_driver);

	return ret;
}

static void __exit mvebu_uart_exit(void)
{
	platform_driver_unregister(&mvebu_uart_platform_driver);
	uart_unregister_driver(&mvebu_uart_driver);
}

arch_initcall(mvebu_uart_init);
module_exit(mvebu_uart_exit);

MODULE_AUTHOR("Wilson Ding <dingwei@marvell.com>");
MODULE_DESCRIPTION("Marvell Armada-3700 Serial Driver");
MODULE_LICENSE("GPL");
Loading