From 19649d46684eb4f3e6f0a00daea7e5f0c2983001 Mon Sep 17 00:00:00 2001
From: Davide Cardillo <davide.cardillo@seco.com>
Date: Tue, 4 Jan 2022 12:35:41 +0100
Subject: [PATCH] [i.MX6][CPLD][driver/seco] Add driver v2.1 for SECO CPLD
 LPC/GPIO

Add LPC tty device: /dev/lpctty#
---
 drivers/tty/serial/Kconfig      |    9 +
 drivers/tty/serial/Makefile     |    1 +
 drivers/tty/serial/lpc_serial.c | 1085 +++++++++++++++++++++++++++++++
 3 files changed, 1095 insertions(+)
 create mode 100644 drivers/tty/serial/lpc_serial.c

diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 28f22e58639c6c..0d5dc71e1e811b 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1538,6 +1538,15 @@ config SERIAL_OWL_CONSOLE
 	  Say 'Y' here if you wish to use Actions Semiconductor S500/S900 UART
 	  as the system console.
 
+config SERIAL_LPC_SIO
+        tristate "SuperIO Serial port via LPC bus"
+        depends on OF
+        depends on SECO_CPLD_FW
+        depends on SERIAL_8250
+        help
+          The QuadMo747 supports the LPC Bridge to communicate with
+                 a superIO which has UART controller.
+
 config SERIAL_RDA
 	bool "RDA Micro serial port support"
 	depends on ARCH_RDA || COMPILE_TEST
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index caf167f0c10a62..bc26acd6eb72db 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -96,3 +96,4 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO)	+= serial_mctrl_gpio.o
 
 obj-$(CONFIG_SERIAL_KGDB_NMI) += kgdb_nmi.o
 obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
+obj-$(CONFIG_SERIAL_LPC_SIO) += lpc_serial.o
diff --git a/drivers/tty/serial/lpc_serial.c b/drivers/tty/serial/lpc_serial.c
new file mode 100644
index 00000000000000..7e25b7597c42f1
--- /dev/null
+++ b/drivers/tty/serial/lpc_serial.c
@@ -0,0 +1,1085 @@
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/sysrq.h>
+#include <linux/platform_device.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/of_irq.h>
+#include <linux/rational.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <asm/irq.h>
+#include <linux/sched/signal.h>
+#include <linux/workqueue.h>
+
+#include <linux/sio_lpc_serial.h>
+
+#define DRIVER_NAME   "lpc_serial"
+#define DEV_NAME      "ttylpc"
+
+#define UART_NR       2
+
+
+#define SIO_UART_INFO(fmt, arg...) printk(KERN_INFO "UART SIO LPC: " fmt "\n" , ## arg)
+#define SIO_UART_ERR(fmt, arg...)  printk(KERN_ERR "%s: " fmt "\n" , __func__ , ## arg)
+#define SIO_UART_DBG(fmt, arg...)  pr_debug("%s: " fmt "\n" , __func__ , ## arg)
+
+
+//static struct workqueue_struct *serial_wq;
+
+static unsigned char lpc_rx_chars (struct lpc_serial_port *lpc_port, unsigned char lsr);
+
+
+static struct of_device_id of_platform_serial_table[];
+
+
+static struct lpc_serial_port *lpc_ports[UART_NR];
+
+
+static void lpc_clear_fifos (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	if ( !lpc_port )
+		return;
+
+	lpc_port->serial_out (port, UART_FCR, UART_FCR_ENABLE_FIFO);
+	lpc_port->serial_out (port, UART_FCR, UART_FCR_ENABLE_FIFO |
+				UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);
+	lpc_port->serial_out (port, UART_FCR, 0);
+}
+
+
+static unsigned int lpc_tx_empty (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned long flags;
+	unsigned int lsr;
+
+	if ( !lpc_port )
+		return 0;
+
+	spin_lock_irqsave(&port->lock, flags);
+	lsr = lpc_port->serial_in (port, UART_LSR);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	return (lsr & (UART_LSR_TEMT | UART_LSR_THRE))
+				== (UART_LSR_TEMT | UART_LSR_THRE) ? TIOCSER_TEMT : 0;
+}
+
+
+static unsigned int get_modem_status (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned int status;
+
+	if ( !lpc_port )
+		return 0;
+
+	status = lpc_port->serial_in (port, UART_MSR);
+	status |= lpc_port->uart_reg[UART_MSR];
+
+	lpc_port->uart_reg[UART_MSR] = 0;
+
+	if ( status & UART_MSR_ANY_DELTA &&
+			lpc_port->uart_reg[UART_IER] &&
+			port->state != NULL )
+	{
+		if ( status & UART_MSR_TERI )
+			port->icount.rng++;
+		if ( status & UART_MSR_DDSR )
+			port->icount.dsr++;
+		if ( status & UART_MSR_DDCD )
+			uart_handle_dcd_change (port, status & UART_MSR_DCD);
+		if ( status & UART_MSR_DCTS )
+			uart_handle_cts_change (port, status & UART_MSR_CTS);
+
+		wake_up_interruptible (&port->state->port.delta_msr_wait);
+	}
+
+	return status;
+}
+
+
+static unsigned int lpc_get_mctrl (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned int status;
+	unsigned int ret = 0;
+
+	if ( !lpc_port )
+		return 0;
+
+	status = get_modem_status (port);
+
+	if ( status & UART_MSR_DCD )
+		ret |= TIOCM_CAR;
+	if ( status & UART_MSR_RI )
+		ret |= TIOCM_RNG;
+	if ( status & UART_MSR_DSR )
+		ret |= TIOCM_DSR;
+	if ( status & UART_MSR_CTS )
+		ret |= TIOCM_CTS;
+
+	return ret;
+}
+
+
+static void lpc_set_mctrl (struct uart_port *port, unsigned int mctrl) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned char mcr = 0;
+
+	if ( !lpc_port )
+		return;
+
+	if (mctrl & TIOCM_RTS && !lpc_port->is_rs485)
+		mcr |= UART_MCR_RTS;
+	if (mctrl & TIOCM_DTR)
+		mcr |= UART_MCR_DTR;
+	if (mctrl & TIOCM_LOOP)
+		mcr |= UART_MCR_LOOP;
+	if (mctrl & TIOCM_OUT2)
+		mcr |= UART_MCR_OUT2;
+
+	lpc_port->serial_out (&lpc_port->port, UART_MCR, mcr);
+}
+
+
+static void lpc_set_rts_status (struct lpc_serial_port *lpc_port, int val) {
+	struct uart_port *port = &lpc_port->port;
+	unsigned int status = lpc_port->serial_in (port, UART_MCR);
+	status = val ? status | UART_MCR_RTS : status & ~UART_MCR_RTS;
+	lpc_port->serial_out (port, UART_MCR, status);
+}
+
+
+static void lpc_set_silent_read (struct lpc_serial_port *lpc_port, int en) {
+	struct uart_port *port = &lpc_port->port;
+	unsigned int status = lpc_port->serial_in (port, UART_IER);
+	status = !en ? status | (UART_IER_RDI | UART_IER_RLSI | UART_IER_MSI) :
+		status & ~(UART_IER_RDI | UART_IER_RLSI | UART_IER_MSI);
+	lpc_port->serial_out (port, UART_IER, status);
+}
+
+
+#define BOUDRATE_REFERENCE  115200
+static inline int __boudrate_getDelay (struct lpc_serial_port *lpc_port) {
+	int boud = lpc_port->port.uartclk / (lpc_port->boudrate * 16);
+	if (boud > BOUDRATE_REFERENCE)
+		boud = BOUDRATE_REFERENCE;
+	return (8000000 / boud);
+}
+
+
+static inline int __boudrate_byte_time (struct lpc_serial_port *lpc_port) {
+	int boud = lpc_port->port.uartclk / (lpc_port->boudrate * 16);
+	if (boud > BOUDRATE_REFERENCE)
+		boud = BOUDRATE_REFERENCE;
+	return ((120758 - boud) / 15) + 1;
+}
+
+
+#define WAIT_SLAVE_EVENT_USEC  100000
+static int wait_uart_event (struct lpc_serial_port *lpc_port, int reg, unsigned char mask, int val)
+{
+	int ret = 0;
+	int reg_val;
+	ktime_t orig_time = ktime_get();
+
+	while (1) {
+		reg_val = lpc_port->serial_in (&lpc_port->port, reg);
+
+		if ( !!(reg_val & mask) == val )
+			break;
+
+		if ( unlikely(fatal_signal_pending(current)) ) {
+			ret = -EINTR;
+			break;
+		}
+
+		if (ktime_to_us(ktime_sub(ktime_get(), orig_time)) >= WAIT_SLAVE_EVENT_USEC) {
+			ret = -ETIMEDOUT;
+			break;
+		}
+
+		udelay (5);
+	}
+
+	return ret;
+}
+
+
+static void end_transmission (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned int rts_delay = 0;
+
+	wait_uart_event (lpc_port, UART_LSR, UART_LSR_TEMT, 1);
+
+	if ( lpc_port->is_rs485 ) {
+
+		if ( lpc_port->is_half_duplex ) {
+			lpc_set_silent_read (lpc_port, 0);
+		}
+
+		rts_delay = __boudrate_getDelay (lpc_port);
+		udelay (rts_delay);
+		lpc_set_rts_status (lpc_port, 0);
+		udelay (rts_delay);
+	}
+}
+
+
+static void lpc_stop_tx (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	if ( !lpc_port )
+		return;
+
+	if ( lpc_port->uart_reg[UART_IER] & UART_IER_THRI) {
+		lpc_port->uart_reg[UART_IER] &= ~UART_IER_THRI;
+		lpc_port->serial_out (port, UART_IER, lpc_port->uart_reg[UART_IER]);
+	}
+
+	lpc_port->tx_mode = 0;
+}
+
+
+static unsigned int  lpc_set_modem_status (struct lpc_serial_port *lpc_port) {
+	struct uart_port *port = &lpc_port->port;
+	unsigned int status = lpc_port->serial_in (port, UART_MSR);
+
+	status |= lpc_port->uart_reg[UART_MSR];
+	lpc_port->uart_reg[UART_MSR] = 0;
+
+	if (status & UART_MSR_ANY_DELTA &&
+			lpc_port->uart_reg[UART_IER] & UART_IER_MSI &&
+			port->state != NULL) {
+
+		if (status & UART_MSR_TERI)
+			port->icount.rng++;
+		if (status & UART_MSR_DDSR)
+			port->icount.dsr++;
+		if (status & UART_MSR_DDCD)
+		uart_handle_dcd_change(port, status & UART_MSR_DCD);
+		if (status & UART_MSR_DCTS)
+			uart_handle_cts_change(port, status & UART_MSR_CTS);
+
+		wake_up_interruptible(&port->state->port.delta_msr_wait);
+	}
+
+	return status;
+}
+
+
+static inline void transmit_buffer (struct lpc_serial_port *lpc_port) {
+	int payload_size, count, rts_delay = 0;
+	unsigned char lsr;
+	unsigned long flags;
+	struct uart_port *port = &lpc_port->port;
+	struct circ_buf *xmit = &lpc_port->port.state->xmit;
+	unsigned long time_byte = __boudrate_byte_time (lpc_port);
+	ktime_t orig_time;
+	unsigned long delta_t;
+
+	if ( lpc_port->is_rs485 ) {
+		rts_delay = __boudrate_getDelay (lpc_port);
+		lpc_set_rts_status (lpc_port, 1);
+
+		if ( lpc_port->is_half_duplex ) {
+			/*  unset the flag for the Data Ready IRQ  */
+			lpc_set_silent_read (lpc_port, 1);
+		}
+	}
+	udelay (rts_delay);
+	lpc_port->tx_mode = 1;
+
+	if ( lpc_port->payload_size )
+		payload_size = lpc_port->payload_size;
+	else
+		payload_size = 1024;	// virtual infinite size
+
+	spin_lock_irqsave (&lpc_port->port.lock, flags);
+
+	if (port->x_char) {
+		lpc_port->serial_out (port, UART_TX, port->x_char);
+		port->icount.tx++;
+		port->x_char = 0;
+		goto irq_en;
+	}
+
+	if ( uart_tx_stopped(port) || uart_circ_empty(xmit) ) {
+		spin_unlock_irqrestore (&lpc_port->port.lock, flags);
+		lpc_stop_tx (port);
+		return;
+	}
+
+	count = 0;
+	orig_time = ktime_get();
+	while ( !uart_circ_empty(xmit) && (++count <= payload_size)) {
+
+		lpc_port->serial_out (port, UART_TX, xmit->buf[xmit->tail]);
+		xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
+		port->icount.tx++;
+
+		if ( count % (port->fifosize / 4) == 0 ) {
+			delta_t = ktime_to_us(ktime_sub(ktime_get(), orig_time));
+			if ( time_byte > delta_t )
+				udelay (time_byte - delta_t);
+			orig_time = ktime_get();
+			lsr = (unsigned char)lpc_port->serial_in (&lpc_port->port, UART_LSR);
+			lsr = lpc_rx_chars (lpc_port, lsr);
+
+		}
+	}
+
+	if ( lpc_port->is_half_duplex ) {
+		delta_t = ktime_to_us(ktime_sub(ktime_get(), orig_time));
+		if ( time_byte > delta_t )
+			udelay (time_byte - delta_t);
+		/*  flush RX buffer  */
+		lsr = (unsigned char)lpc_port->serial_in (&lpc_port->port, UART_LSR);
+		lsr = lpc_rx_chars (lpc_port, lsr);
+	}
+
+	end_transmission (port);
+
+	if ( uart_circ_chars_pending(xmit) < WAKEUP_CHARS ) {
+		uart_write_wakeup (port);
+	}
+
+	wait_uart_event (lpc_port, UART_LSR, UART_LSR_TEMT, 1);
+	if ( uart_circ_empty(xmit) ) {
+		lpc_stop_tx (port );
+		spin_unlock_irqrestore (&lpc_port->port.lock, flags);
+	} else {
+		spin_unlock_irqrestore (&lpc_port->port.lock, flags);
+		tasklet_schedule (&lpc_port->tasklet);
+	}
+
+	return;
+irq_en:
+
+	end_transmission (port);
+
+	lpc_port->tx_mode = 0;
+
+	spin_unlock_irqrestore (&lpc_port->port.lock, flags);
+}
+
+
+static void work_tx (unsigned long data) {
+	struct lpc_serial_port *lpc_port = (struct lpc_serial_port *)data;
+	transmit_buffer (lpc_port);
+}
+
+
+static void lpc_start_tx (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	if ( !lpc_port )
+		return;
+
+	if ( !(lpc_port->uart_reg[UART_IER] & UART_IER_THRI) ) {
+		lpc_port->uart_reg[UART_IER] |= UART_IER_THRI;
+		lpc_port->serial_out (port, UART_IER, lpc_port->uart_reg[UART_IER]);
+
+		// this action will rise an UART interrupt
+	}
+}
+
+
+static void lpc_stop_rx (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	lpc_port->uart_reg[UART_IER] &= ~UART_IER_RLSI;
+	lpc_port->port.read_status_mask &= ~UART_LSR_DR;
+	lpc_port->serial_out (port, UART_IER, lpc_port->uart_reg[UART_IER]);
+}
+
+
+static void lpc_enable_ms (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	lpc_port->uart_reg[UART_IER] |= UART_IER_MSI;
+	lpc_port->serial_out (port, UART_IER, lpc_port->uart_reg[UART_IER]);
+}
+
+
+static void lpc_break_ctl (struct uart_port *port, int break_state) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned long flags;
+
+	if ( !lpc_port )
+		return;
+
+	spin_lock_irqsave (&port->lock, flags);
+	if (break_state == -1)
+		lpc_port->uart_reg[UART_LCR] |= UART_LCR_SBC;
+	else
+		lpc_port->uart_reg[UART_LCR] &= ~UART_LCR_SBC;
+
+	lpc_port->serial_out (port, UART_LCR, lpc_port->uart_reg[UART_LCR]);
+	spin_unlock_irqrestore (&port->lock, flags);
+}
+
+
+static unsigned char lpc_rx_chars (struct lpc_serial_port *lpc_port, unsigned char lsr) {
+	unsigned char ch;
+	char flag;
+	int max_count = 256;
+	struct uart_port *port = &lpc_port->port;
+
+	if ( ! port )
+		return 0x00;
+
+	do {
+		if ( likely (lsr & UART_LSR_DR) )
+			ch = lpc_port->serial_in (port, UART_RX);
+		else if ( !lpc_port->tx_mode )
+			ch = 0x00;
+		else
+			return lsr;
+
+		flag = TTY_NORMAL;
+		port->icount.rx++;
+
+		lsr |= lpc_port->uart_reg[UART_LSR];
+		lpc_port->uart_reg[UART_LSR] = 0;
+
+		if ( !uart_handle_sysrq_char(port, ch) ) {
+			uart_insert_char (port, lsr, UART_LSR_OE, ch, flag);
+		}
+
+		lsr = (unsigned char)lpc_port->serial_in (&lpc_port->port, UART_LSR);
+	} while ( (lsr & (UART_LSR_DR | UART_LSR_BI)) && (max_count-- > 0) );
+
+	spin_unlock (&port->lock);
+	tty_flip_buffer_push (&port->state->port);
+	spin_lock (&port->lock);
+	return lsr;
+}
+
+
+#define INT_PENDING_MASK    0x01
+#define INT_CODE_MASK       0x0E
+#define INT_CODE_PRIOR_1    0x03      // UART Receive Status
+#define INT_CODE_PRIOR_2A   0x02      // RBR Data Ready
+#define INT_CODE_PRIOR_2B   0x06      // FIFO Data Timeout
+#define INT_CODE_PRIOR_3    0x01      // TBR Empty
+#define INT_CODE_PRIOR_4    0x00      // Handshake status
+
+
+static irqreturn_t lpc_serial_isr (int irq, void *dev_id) {
+	int *i = (int *)dev_id;
+	unsigned char int_pending, int_code, iir, lsr;
+	unsigned long flags;
+	int already_unlock = 0;
+
+	struct lpc_serial_port *lpc_port;
+
+	if ( *i >= UART_NR ) {
+		return IRQ_RETVAL(0);
+	}
+
+	lpc_port = lpc_ports[*i];
+	if ( !lpc_port )
+		return IRQ_RETVAL(0);
+
+	spin_lock_irqsave (&lpc_port->port.lock, flags);
+
+	iir = (unsigned char)lpc_port->serial_in (&lpc_port->port, UART_IIR);
+	lsr = (unsigned char)lpc_port->serial_in (&lpc_port->port, UART_LSR);
+
+	int_pending = iir & INT_PENDING_MASK;
+
+	if ( int_pending == 0 ) {	// there is an interrupt to manage
+
+		int_code = ( iir & INT_CODE_MASK ) >> 1;
+		switch ( int_code ) {
+
+			case INT_CODE_PRIOR_1:
+				// read LSR registr -> operation already performed
+				break;
+			case INT_CODE_PRIOR_2A:
+				lpc_port->rx_lsr = lsr;
+				lsr = lpc_rx_chars (lpc_port, lsr);
+				break;
+			case INT_CODE_PRIOR_2B:
+				lpc_port->rx_lsr = lsr;
+				lsr = lpc_rx_chars (lpc_port, lsr);
+				break;
+			case INT_CODE_PRIOR_3:
+				if ( lsr & UART_LSR_THRE ) {
+
+					lpc_set_modem_status (lpc_port);
+					already_unlock = 1;
+					lpc_port->tx_mode = 0;
+					spin_unlock_irqrestore (&lpc_port->port.lock, flags);
+					tasklet_schedule (&lpc_port->tasklet);
+				}
+				break;
+			case INT_CODE_PRIOR_4:
+				break;
+			default:
+				break;
+		}
+
+	}
+	if ( !already_unlock ) {
+		spin_unlock_irqrestore (&lpc_port->port.lock, flags);
+	}
+
+	return IRQ_HANDLED;
+}
+
+
+static int lpc_startup (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned long flags;
+	int retval;
+
+	if ( !lpc_port )
+		return -ENODEV;
+
+	lpc_port->uart_reg[UART_MCR] = 0;
+
+	lpc_clear_fifos (port);
+
+	/*  Clear the interrupt registers  */
+	lpc_port->serial_in (port, UART_LSR);
+	lpc_port->serial_in (port, UART_RX);
+	lpc_port->serial_in (port, UART_IIR);
+	lpc_port->serial_in (port, UART_MSR);
+
+	/*  UART init  */
+	lpc_port->serial_out (port, UART_LCR, UART_LCR_WLEN8);
+	
+
+	spin_lock_irqsave(&port->lock, flags);
+
+	if ( port->irq )
+		lpc_port->port.mctrl |= TIOCM_OUT2;
+
+	if ( lpc_port->is_rs485 ) {
+		lpc_port->port.mctrl &= ~TIOCM_RTS;
+	}
+
+	lpc_set_mctrl (port, port->mctrl);
+
+	spin_unlock_irqrestore (&port->lock, flags);
+
+	if ( port->irq ) {
+
+		spin_lock_irqsave (&port->lock, flags);
+
+		if ( port->irqflags & IRQF_SHARED )
+			disable_irq_nosync (port->irq);
+
+		retval = request_any_context_irq (lpc_port->port.irq, lpc_serial_isr, IRQ_TYPE_EDGE_FALLING,
+							"tty_lpc", &lpc_port->port.line);
+
+		if ( retval < 0 ) {
+			return -EINVAL;
+		}
+
+		spin_unlock_irqrestore (&port->lock, flags);
+
+	}
+	/*  do test  */
+
+
+	/*  Clear the interrupt registers  */
+	lpc_port->serial_in (port, UART_LSR);
+	lpc_port->serial_in (port, UART_RX);
+	lpc_port->serial_in (port, UART_IIR);
+	lpc_port->serial_in (port, UART_MSR);
+
+	lpc_port->uart_reg[UART_MSR] = 0;
+	lpc_port->uart_reg[UART_IER] = UART_IER_RLSI | UART_IER_RDI;
+	lpc_port->serial_out (port, UART_IER, lpc_port->uart_reg[UART_IER]);
+
+	lpc_port->uart_reg[UART_FCR] = UART_FCR | UART_FCR_R_TRIG_11 | UART_FCR_ENABLE_FIFO;
+	lpc_port->serial_out (port, UART_FCR, UART_FCR_R_TRIG_11 | UART_FCR_ENABLE_FIFO);
+
+	if ( lpc_port->is_rs485 ) {
+		lpc_set_rts_status (lpc_port, 0);
+	}
+
+	return 0;
+}
+
+
+static void lpc_shutdown (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+	unsigned long flags;
+
+	if ( !lpc_port )
+		return;
+
+	// free IRQ
+	free_irq (port->irq, &lpc_port->port.line);
+
+	lpc_port->uart_reg[UART_IIR] = 0;
+	lpc_port->serial_out (port, UART_IIR, lpc_port->uart_reg[UART_IIR]);
+
+	spin_lock_irqsave (&port->lock, flags);
+	port->mctrl &= ~TIOCM_OUT2;		// irq disable
+
+	if ( lpc_port->is_rs485 )
+		port->mctrl &= ~TIOCM_RTS;
+
+	lpc_set_mctrl (port, port->mctrl);
+	spin_unlock_irqrestore (&port->lock, flags);
+
+	lpc_port->serial_out (port, UART_LCR,
+			lpc_port->serial_in (port, UART_LCR) & ~UART_LCR_SBC);
+	lpc_clear_fifos (port);
+
+	// flush buffer
+	lpc_port->serial_in (port, UART_RX);
+}
+
+
+static void lpc_flush_buffer (struct uart_port *port) {}
+
+
+static void lpc_set_termios (struct uart_port *port, struct ktermios *termios, struct ktermios *old) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	unsigned char cval, fval;
+	unsigned int baud, quot;
+	unsigned long flags;
+
+	if ( !lpc_port )
+		return;
+
+	/*  data length  */
+	switch (termios->c_cflag & CSIZE) {
+		case CS5:
+			cval = UART_LCR_WLEN5;
+			break;
+		case CS6:
+			cval = UART_LCR_WLEN6;
+			break;
+		case CS7:
+			cval = UART_LCR_WLEN7;
+			break;
+		case CS8:
+			cval = UART_LCR_WLEN8;
+			break;
+		default:
+			cval = UART_LCR_WLEN8;
+	}
+
+	/*  stop bit
+	 *  if UART_LCR_WLEN5  -> 1.5 bit stop
+	 *  if UART_LCR_WLEN6, UART_LCR_WLEN7, UART_LCR_WLEN8 -> 2 bits stop
+	 */
+	if ( termios->c_cflag & CSTOPB ) {
+		cval |= UART_LCR_STOP;
+	}
+
+	/*  parity bit  */
+	if ( termios->c_cflag & PARENB ) {
+		cval |= UART_LCR_PARITY;
+	}
+	if ( !(termios->c_cflag & PARODD) ) {
+		cval |= UART_LCR_EPAR;
+	}
+
+
+	/*  boudrate  */
+	baud = uart_get_baud_rate (port, termios, old,
+			port->uartclk / 16 / 0xffff, port->uartclk / 16);
+	quot = uart_get_divisor (port, baud);
+
+
+	spin_lock_irqsave (&port->lock, flags);
+
+	/*  Update the per-port timeout.  */
+	uart_update_timeout (port, termios->c_cflag, baud);
+
+
+	/*   FIFO Setting */
+	fval = UART_FCR_ENABLE_FIFO;	                        // enable fifo
+	fval &= ~UART_FCR_DMA_SELECT;                           // disable DMA feature
+	fval &= ~UART_FCR_TRIGGER_MASK;
+    fval |= UART_FCR_TRIGGER_8;                             // set FIFO size to 8 bytes
+	fval |= (UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT);    // reset TX and RX FIFO counter
+
+
+	/*  Characteres to ignore  */
+	lpc_port->port.ignore_status_mask = 0;
+	if ( termios->c_iflag & IGNPAR )
+		lpc_port->port.ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	if ( termios->c_iflag & IGNBRK )
+		lpc_port->port.ignore_status_mask |= UART_LSR_BI;
+	if ( (termios->c_iflag & (IGNPAR | IGNBRK)) == (IGNPAR | IGNBRK) )
+		lpc_port->port.ignore_status_mask |= UART_LSR_OE;
+	if ( (termios->c_cflag & CREAD) == 0 )
+		lpc_port->port.ignore_status_mask |= UART_LSR_DR;
+
+
+	lpc_port->serial_out (&lpc_port->port, UART_LCR, cval | UART_LCR_DLAB);
+	lpc_port->serial_out (&lpc_port->port, UART_DLL, quot & 0xFF);
+	lpc_port->serial_out (&lpc_port->port, UART_DLM, (quot >> 8) & 0xFF);
+
+	lpc_port->serial_out (&lpc_port->port, UART_LCR, cval);    // restore bank
+
+	if ( fval & UART_FCR_ENABLE_FIFO ) {
+		/*  two steps setting */
+		lpc_port->serial_out (&lpc_port->port, UART_FCR, UART_FCR_ENABLE_FIFO);
+		lpc_port->serial_out (&lpc_port->port, UART_FCR, fval);
+	}
+
+	if ( lpc_port->is_rs485 )
+		port->mctrl &= ~TIOCM_RTS;
+
+	lpc_set_mctrl (port, port->mctrl);
+
+	lpc_port->boudrate = quot;
+	if ( tty_termios_baud_rate(termios) )
+		tty_termios_encode_baud_rate (termios, baud, baud);
+
+
+	spin_unlock_irqrestore (&port->lock, flags);
+}
+
+
+static const char *lpc_type (struct uart_port *port) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	if ( !lpc_port )
+		return NULL;
+
+	switch ( lpc_port->type ) {
+		case LPC_PORT_TYPE_W83627:
+			return "W83627";
+		case LPC_PORT_TYPE_XR28V382:
+			return "xr28v382";
+		default:
+			return NULL;
+	}
+}
+
+
+static void lpc_config_port (struct uart_port *port, int flags) {
+	struct lpc_serial_port *lpc_port =
+		container_of (port, struct lpc_serial_port, port);
+
+	if ( !lpc_port )
+		return;
+
+	port->type = PORT_LPC;
+}
+
+
+static int lpc_verify_port (struct uart_port *port, struct serial_struct *ser) {
+
+	if ( ser->irq >= nr_irqs || ser->irq < 0 )
+		return -EINVAL;
+	if ( ser->baud_base > 115200 )
+		return -EINVAL;
+	if ( ser->type < PORT_UNKNOWN )
+		return -EINVAL;
+
+	return 0;
+}
+
+
+static void lpc_console_write(struct console *co, const char *s, unsigned int count) {}
+
+
+static int __init lpc_console_setup(struct console *co, char *options) {
+	struct uart_port *port;
+	struct lpc_serial_port *lpc_port;
+	int baud = 9600;
+	int bits = 8;
+	int parity = 'n';
+	int flow = 'n';
+
+	if ( co->index == -1 || co->index > 2 ) {
+		co->index = 0;
+	}
+
+	lpc_port = lpc_ports[co->index];
+	if ( lpc_port == NULL )
+		return -ENODEV;
+
+	port = &lpc_port->port;
+
+	if ( options )
+		uart_parse_options (options, &baud, &parity, &bits, &flow);
+
+	return uart_set_options (port, co, baud, parity, bits, flow);
+}
+
+
+static struct uart_ops lpc_pops = {
+	.tx_empty       = lpc_tx_empty,
+	.set_mctrl      = lpc_set_mctrl,
+	.get_mctrl      = lpc_get_mctrl,
+	.stop_tx        = lpc_stop_tx,
+	.start_tx       = lpc_start_tx,
+	.stop_rx        = lpc_stop_rx,
+	.enable_ms      = lpc_enable_ms,
+	.break_ctl      = lpc_break_ctl,
+	.startup        = lpc_startup,
+	.shutdown       = lpc_shutdown,
+	.flush_buffer   = lpc_flush_buffer,
+	.set_termios    = lpc_set_termios,
+	.type           = lpc_type,
+	.config_port    = lpc_config_port,
+	.verify_port    = lpc_verify_port,
+};
+
+
+
+static struct uart_driver lpc_reg;
+static struct console lpc_console = {
+	.name       = DEV_NAME,
+	.write      = lpc_console_write,
+	.device     = uart_console_device,
+	.setup      = lpc_console_setup,
+	.flags      = CON_PRINTBUFFER,
+	.index      = -1,
+	.data       = &lpc_reg,
+};
+
+
+static struct uart_driver lpc_reg = {
+	.owner          = THIS_MODULE,
+	.driver_name    = DRIVER_NAME,
+	.dev_name       = DEV_NAME,
+	.major          = 210,
+	.minor          = 64,
+	.nr             = 2,
+	.cons           = &lpc_console,
+};
+
+
+static void setup_from_of (struct device_node *np, struct lpc_serial_port *lpc_port) {
+	u32 prop;
+
+	lpc_port->payload_size = 0;   // means infinite size
+
+	if ( of_property_read_u32(np, "device_id", &prop) == 0)
+		lpc_port->uart_ldev = prop;
+
+	if ( of_property_read_u32(np, "membase", &prop) == 0)
+		lpc_port->membase = prop;
+
+	if ( of_property_read_u32(np, "uart-clk", &prop) == 0) {
+		lpc_port->port.uartclk = prop;
+		lpc_port->uart_clk = prop;
+	}
+		
+	if ( of_property_read_u32(np, "reg-shift", &prop) == 0)
+		lpc_port->port.regshift = prop;
+
+	if ( of_property_read_u32(np, "fifo-size", &prop) == 0)
+		lpc_port->port.fifosize = prop;
+
+	if ( of_property_read_u32(np, "pl-size", &prop) == 0)
+		lpc_port->payload_size = prop;
+
+	lpc_port->is_rs485 = of_property_read_bool(np, "rs485");
+	if ( lpc_port->is_rs485 ) {
+		SIO_UART_INFO ("UART#%d used as RS485", lpc_port->uart_index);
+	}
+
+	lpc_port->is_half_duplex = of_property_read_bool(np, "half-duplex");
+	if ( lpc_port->is_half_duplex ) {
+		SIO_UART_INFO ("UART#%d used in half duplex mode", lpc_port->uart_index);
+	}
+
+	lpc_port->port.mapbase = (resource_size_t)lpc_port->membase;
+
+	lpc_port->port.irq = irq_of_parse_and_map (np, 0);
+}
+
+
+static int lpc_serial_probe (struct platform_device *pdev) {
+	const struct of_device_id *match;
+	struct device_node *np = pdev->dev.of_node;
+	struct lpc_serial_port *lpc_port;
+	struct serial_data *data;
+	int ret, err;
+
+
+	match = of_match_device(of_platform_serial_table, &pdev->dev);
+	if (!match)
+		return -EINVAL;
+
+	lpc_port = kzalloc (sizeof (struct lpc_serial_port), GFP_KERNEL);
+	if ( !lpc_port ) {
+		SIO_UART_ERR ("cannot allocate memory for structure data");
+		err = -ENOMEM;
+		goto err_data_allocate;
+	}
+
+	lpc_port->sio_ops = (struct sio_w83627_ops *)dev_get_platdata(&pdev->dev);
+	if ( !lpc_port->sio_ops ) {
+		SIO_UART_ERR ("cannot obtain ops structure data");
+		err = -EINVAL;
+		goto err_ops_data;
+	}
+	if ( !lpc_port->sio_ops->read || !lpc_port->sio_ops->write ||
+			!lpc_port->sio_ops->g_read || !lpc_port->sio_ops->g_write) {
+		SIO_UART_ERR ("write and read operation not assigned");
+   		err= -EINVAL;
+		goto err_ops_data;
+	}
+
+	data = (struct serial_data *)match->data;
+	if ( !data ) {
+		SIO_UART_ERR ("cannot obtain serial data");
+		err = -EINVAL;
+		goto err_serial_data;
+	}
+
+	lpc_port->port_type  = data->port_type;
+	lpc_port->uart_index = data->uart_index;
+	lpc_port->port.line  = data->uart_index;
+
+	setup_from_of (np, lpc_port);
+
+	platform_set_drvdata (pdev, lpc_port);
+
+	switch ( data->port_type ) {
+		case LPC_PORT_TYPE_W83627:
+			ret = lpc_w83627_platform_serial_setup (pdev, lpc_port);
+			break;
+		case LPC_PORT_TYPE_XR28V382:
+			ret = lpc_xr28v382_platform_serial_setup (pdev, lpc_port);
+			break;
+		default:
+			ret = -1;
+	}
+	if ( ret ) {
+		SIO_UART_ERR ("error while setting UART");
+		err = ret;
+		goto err_uart_setting;
+	}
+
+	lpc_port->port.dev    = &pdev->dev;
+	lpc_port->port.iotype = UPIO_LPC;
+	lpc_port->port.type   = PORT_LPC;
+	lpc_port->port.ops    = &lpc_pops;
+	lpc_port->port.flags  = UPF_BOOT_AUTOCONF;
+
+	ret = lpc_w83627_sys_serial_setup (pdev, lpc_port);
+	if ( ret ) {
+		SIO_UART_ERR ("cannot create sysfs interface");
+		err = ret;
+		goto err_sysfs;
+	}
+
+	lpc_ports[lpc_port->port.line] = lpc_port;
+	lpc_port->tx_mode = 0;
+
+	tasklet_init (&lpc_port->tasklet, work_tx, (unsigned long) lpc_port);
+
+	ret = uart_add_one_port (&lpc_reg, &lpc_port->port);
+
+	SIO_UART_INFO ("UART#%d LPC SuperIO driver probed!!!", lpc_port->uart_index);
+
+	return 0;
+err_sysfs:
+err_uart_setting:
+err_serial_data:
+err_ops_data:
+err_data_allocate:
+	return err;
+}
+
+
+static int lpc_serial_remove (struct platform_device *pdev) {
+	struct lpc_serial_port *lpc_port = (struct lpc_serial_port *)
+											platform_get_drvdata (pdev);
+
+	return uart_remove_one_port (&lpc_reg, &lpc_port->port);
+}
+
+
+static struct serial_data w83627dhg_A_16550a = { LPC_UART_TYPE_W83627_A, LPC_PORT_TYPE_W83627};
+static struct serial_data w83627dhg_B_16550a = { LPC_UART_TYPE_W83627_B, LPC_PORT_TYPE_W83627};
+static struct serial_data xr28v382_A_16550a = { LPC_UART_TYPE_XR28V382_A, LPC_PORT_TYPE_XR28V382};
+static struct serial_data xr28v382_B_16550a = { LPC_UART_TYPE_XR28V382_B, LPC_PORT_TYPE_XR28V382};
+
+
+static struct of_device_id of_platform_serial_table[] = {
+	{ .compatible = "nuvoton,w83627dhg_A_16550a", .data = &w83627dhg_A_16550a, },
+	{ .compatible = "nuvoton,w83627dhg_B_16550a", .data = &w83627dhg_B_16550a, },
+	{ .compatible = "MaxLinear,xr28v382_A_16550a", .data = &xr28v382_A_16550a, },
+	{ .compatible = "MaxLinear,xr28v382_B_16550a", .data = &xr28v382_B_16550a, },
+	{ /* end of list */ },
+};
+
+
+static struct platform_driver serial_lpc_driver = {
+	.probe		= lpc_serial_probe,
+	.remove		= lpc_serial_remove,
+	.suspend	= NULL,
+	.resume		= NULL,
+	.driver		= {
+		.name	        = "lpc_serial",
+		.owner	        = THIS_MODULE,
+		.of_match_table = of_platform_serial_table,
+	},
+};
+
+
+static int __init lpc_serial_init (void) {
+
+	int ret;
+
+	pr_info ("Serial: LPC driver");
+
+	ret = uart_register_driver (&lpc_reg);
+	if ( ret )
+		return ret;
+
+	ret = platform_driver_register (&serial_lpc_driver);
+	if ( ret != 0 )
+		uart_unregister_driver (&lpc_reg);
+
+	return ret;
+}
+
+
+static void __exit lpc_serial_exit (void) {
+	platform_driver_unregister (&serial_lpc_driver);
+	uart_unregister_driver (&lpc_reg);
+}
+
+
+module_init (lpc_serial_init);
+module_exit (lpc_serial_exit);
+
+
+MODULE_AUTHOR("Davide Cardillo, SECO srl");
+MODULE_DESCRIPTION("SuperIO LPC UART interface");
+MODULE_LICENSE("GPL");
-- 
GitLab