All of lore.kernel.org
 help / color / mirror / Atom feed
From: Peter Hung <hpeter@gmail.com>
To: gregkh@linuxfoundation.org, jslaby@suse.com
Cc: andriy.shevchenko@linux.intel.com,
	heikki.krogerus@linux.intel.com, peter@hurleysoftware.com,
	soeren.grunewald@desy.de, udknight@gmail.com,
	adam.lee@canonical.com, arnd@arndb.de,
	yamada.masahiro@socionext.com, mans@mansr.com,
	scottwood@freescale.com, paul.burton@imgtec.com,
	paul.gortmaker@windriver.com, matthias.bgg@gmail.com,
	manabian@gmail.com, peter.ujfalusi@ti.com,
	linux-kernel@vger.kernel.org, linux-serial@vger.kernel.org,
	peter_hong@fintek.com.tw,
	Peter Hung <hpeter+linux_kernel@gmail.com>
Subject: [PATCH 2/3] 8250_fintek_pci: Add Fintek PCIE UART driver
Date: Tue, 19 Jan 2016 10:41:05 +0800	[thread overview]
Message-ID: <1453171266-15874-3-git-send-email-hpeter+linux_kernel@gmail.com> (raw)
In-Reply-To: <1453171266-15874-1-git-send-email-hpeter+linux_kernel@gmail.com>

Add Fintek F81504/508/512 PCIE-to-UART driver 8250_fintek_pci.c

Our device driver had already merge into 8250_pci.c, but it's only
implemented basic UART function. This patch will add new file
8250_fintek_pci.c to implements serial ports and high baudrate
functions.

Signed-off-by: Peter Hung <hpeter+linux_kernel@gmail.com>
---
 drivers/tty/serial/8250/8250_fintek_pci.c | 417 ++++++++++++++++++++++++++++++
 drivers/tty/serial/8250/Kconfig           |   9 +
 drivers/tty/serial/8250/Makefile          |   1 +
 3 files changed, 427 insertions(+)
 create mode 100644 drivers/tty/serial/8250/8250_fintek_pci.c

diff --git a/drivers/tty/serial/8250/8250_fintek_pci.c b/drivers/tty/serial/8250/8250_fintek_pci.c
new file mode 100644
index 0000000..5d9ea01a
--- /dev/null
+++ b/drivers/tty/serial/8250/8250_fintek_pci.c
@@ -0,0 +1,417 @@
+/*
+ *  Base port operations for Fintek F81504/508/512 PCI-to-UARTs 16550A-type
+ *  serial ports
+ */
+#include <linux/pci.h>
+#include <linux/serial_8250.h>
+#include <linux/module.h>
+#include "8250.h"
+
+#define FINTEK_VID		0x1c29
+#define FINTEK_F81504		0x1104
+#define FINTEK_F81508		0x1108
+#define FINTEK_F81512		0x1112
+
+#define FINTEK_MAX_PORT		12
+#define DRIVER_NAME		"f81504_serial"
+#define DEV_DESC		"Fintek F81504/508/512 PCIE-to-UART"
+
+#define UART_START_ADDR		0x40
+#define UART_MODE_OFFSET	0x07
+#define UART_OFFSET		0x08
+
+/* RTS will control by MCR if this bit is 0 */
+#define RTS_CONTROL_BY_HW	BIT(4)
+/* only worked with FINTEK_RTS_CONTROL_BY_HW on */
+#define RTS_INVERT		BIT(5)
+
+#define CLOCK_RATE_MASK		0xc0
+#define CLKSEL_1DOT846_MHZ	0x00
+#define CLKSEL_18DOT46_MHZ	0x40
+#define CLKSEL_24_MHZ		0x80
+#define CLKSEL_14DOT77_MHZ	0xc0
+
+#define IRQSEL_REG		0xb8
+
+static u32 baudrate_table[] = { 1500000, 1152000, 921600 };
+static u8 clock_table[] = { CLKSEL_24_MHZ, CLKSEL_18DOT46_MHZ,
+		CLKSEL_14DOT77_MHZ };
+
+struct f81504_pci_private {
+	int line[FINTEK_MAX_PORT];
+	u32 uart_count;
+};
+
+/* We should do proper H/W transceiver setting before change to RS485 mode */
+static int f81504_rs485_config(struct uart_port *port,
+			       struct serial_rs485 *rs485)
+{
+	u8 setting;
+	u8 *index = (u8 *)port->private_data;
+	struct pci_dev *pci_dev = container_of(port->dev, struct pci_dev, dev);
+
+	pci_read_config_byte(pci_dev, UART_START_ADDR + UART_OFFSET * *index +
+			UART_MODE_OFFSET, &setting);
+
+	if (!rs485)
+		rs485 = &port->rs485;
+	else if (rs485->flags & SER_RS485_ENABLED)
+		memset(rs485->padding, 0, sizeof(rs485->padding));
+	else
+		memset(rs485, 0, sizeof(*rs485));
+
+	/* F81504/508/512 not support RTS delay before or after send */
+	rs485->flags &= SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND;
+
+	if (rs485->flags & SER_RS485_ENABLED) {
+		/* Enable RTS H/W control mode */
+		setting |= RTS_CONTROL_BY_HW;
+
+		if (rs485->flags & SER_RS485_RTS_ON_SEND) {
+			/* RTS driving high on TX */
+			setting &= ~RTS_INVERT;
+		} else {
+			/* RTS driving low on TX */
+			setting |= RTS_INVERT;
+		}
+
+		rs485->delay_rts_after_send = 0;
+		rs485->delay_rts_before_send = 0;
+	} else {
+		/* Disable RTS H/W control mode */
+		setting &= ~(RTS_CONTROL_BY_HW | RTS_INVERT);
+	}
+
+	pci_write_config_byte(pci_dev, UART_START_ADDR + UART_OFFSET * *index +
+			UART_MODE_OFFSET, setting);
+
+	if (rs485 != &port->rs485)
+		port->rs485 = *rs485;
+
+	return 0;
+}
+
+static int f81504_check_baudrate(u32 baud, int *idx)
+{
+	size_t index;
+	u32 quot, rem;
+
+	for (index = 0; index < ARRAY_SIZE(baudrate_table); ++index) {
+		/* Clock source must largeer than desire baudrate */
+		if (baud > baudrate_table[index])
+			continue;
+
+		quot = DIV_ROUND_CLOSEST(baudrate_table[index], baud);
+		/* find divisible clock source */
+		rem = baudrate_table[index] % baud;
+
+		if (quot && !rem) {
+			if (idx)
+				*idx = index;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static void f81504_set_termios(struct uart_port *port,
+		struct ktermios *termios, struct ktermios *old)
+{
+	struct pci_dev *dev = container_of(port->dev, struct pci_dev, dev);
+	unsigned int baud = tty_termios_baud_rate(termios);
+	u8 tmp, *offset = (u8 *)port->private_data;
+	int i;
+
+	do {
+		/* read current clock source (masked with CLOCK_RATE_MASK) */
+		pci_read_config_byte(dev, UART_START_ADDR + *offset *
+				UART_OFFSET, &tmp);
+
+		if (baud <= 115200) {
+			/*
+			 * direct use 1.8432MHz when baudrate smaller then or
+			 * equal 115200bps
+			 */
+			port->uartclk = 115200 * 16;
+			pci_write_config_byte(dev, UART_START_ADDR + *offset *
+					UART_OFFSET, tmp & ~CLOCK_RATE_MASK);
+			break;
+		}
+
+		if (!f81504_check_baudrate(baud, &i)) {
+			/* had optimize value */
+			port->uartclk = baudrate_table[i] * 16;
+			tmp = (tmp & ~CLOCK_RATE_MASK) | clock_table[i];
+			pci_write_config_byte(dev, UART_START_ADDR + *offset *
+					UART_OFFSET, tmp);
+			break;
+		}
+
+		if (old && !f81504_check_baudrate(tty_termios_baud_rate(old),
+				NULL)) {
+			/*
+			 * If it can't found suitable clock source but had old
+			 * accpetable baudrate, we'll use it
+			 */
+			baud = tty_termios_baud_rate(old);
+		} else {
+			/*
+			 * If it can't found suitable clock source and not old
+			 * config, we'll direct set 115200bps for future use
+			 */
+			baud = 115200;
+		}
+
+		if (tty_termios_baud_rate(termios))
+			tty_termios_encode_baud_rate(termios, baud, baud);
+	} while (1);
+
+	serial8250_do_set_termios(port, termios, old);
+}
+
+static int f81504_register_port(struct pci_dev *dev, unsigned long address,
+				int idx)
+{
+	struct uart_8250_port port;
+	u8 *data;
+
+	memset(&port, 0, sizeof(port));
+
+	data = devm_kzalloc(&dev->dev, sizeof(u8), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	*data = idx;
+	port.port.iotype = UPIO_PORT;
+	port.port.mapbase = 0;
+	port.port.membase = NULL;
+	port.port.regshift = 0;
+	port.port.irq = dev->irq;
+	port.port.flags = UPF_SKIP_TEST | UPF_FIXED_TYPE | UPF_BOOT_AUTOCONF |
+			UPF_SHARE_IRQ;
+	port.port.uartclk = 115200 * 16;
+	port.port.dev = &dev->dev;
+	port.port.iobase = address;
+	port.port.type = PORT_16550A;
+	port.port.fifosize = 128;
+	port.tx_loadsz = 32;
+	port.port.private_data = data;	/* save current idx */
+	port.port.set_termios = f81504_set_termios;
+	port.port.rs485_config = f81504_rs485_config;
+
+	return serial8250_register_8250_port(&port);
+}
+
+static int f81504_port_init(struct pci_dev *dev)
+{
+	size_t i;
+	u32 max_port, iobase;
+	u32 bar_data[3];
+	u16 tmp;
+	u8 config_base;
+	struct f81504_pci_private *priv = pci_get_drvdata(dev);
+	struct uart_8250_port *port;
+
+	switch (dev->device) {
+	case FINTEK_F81504: /* 4 ports */
+	case FINTEK_F81508: /* 8 ports */
+		max_port = dev->device & 0xff;
+		break;
+	case FINTEK_F81512: /* 12 ports */
+		max_port = 12;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Get the UART IO address dispatch from the BIOS */
+	pci_read_config_dword(dev, 0x24, &bar_data[0]);
+	pci_read_config_dword(dev, 0x20, &bar_data[1]);
+	pci_read_config_dword(dev, 0x1c, &bar_data[2]);
+
+	/* Compatible for newer step IC */
+	pci_read_config_word(dev, IRQSEL_REG, &tmp);
+	tmp |= BIT(8);
+	pci_write_config_word(dev, IRQSEL_REG, tmp);
+
+	for (i = 0; i < max_port; ++i) {
+		/* UART0 configuration offset start from 0x40 */
+		config_base = UART_START_ADDR + UART_OFFSET * i;
+
+		/* Calculate Real IO Port */
+		iobase = (bar_data[i / 4] & 0xffffffe0) + (i % 4) * 8;
+
+		/* Enable UART I/O port */
+		pci_write_config_byte(dev, config_base + 0x00, 0x01);
+
+		/* Select 128-byte FIFO and 8x FIFO threshold */
+		pci_write_config_byte(dev, config_base + 0x01, 0x33);
+
+		/* LSB UART */
+		pci_write_config_byte(dev, config_base + 0x04,
+				(u8)(iobase & 0xff));
+
+		/* MSB UART */
+		pci_write_config_byte(dev, config_base + 0x05,
+				(u8)((iobase & 0xff00) >> 8));
+
+		pci_write_config_byte(dev, config_base + 0x06, dev->irq);
+
+		/* First init. force init to RS232 Mode */
+		pci_write_config_byte(dev, config_base + 0x07, 0x01);
+	}
+
+	if (!priv)
+		return 0;
+
+	/* re-apply RS232/485 mode when f81504_resume() */
+	for (i = 0; i < priv->uart_count; ++i) {
+		port = serial8250_get_port(priv->line[i]);
+		f81504_rs485_config(&port->port, NULL);
+	}
+
+	return 0;
+}
+
+static int f81504_probe(struct pci_dev *dev, const struct pci_device_id
+				*dev_id)
+{
+	int status;
+	size_t i;
+	u16 iobase;
+	u8 tmp;
+	struct f81504_pci_private *priv;
+
+	status = pci_enable_device(dev);
+	if (status)
+		return status;
+
+	/* Init PCI Configuration Space */
+	status = f81504_port_init(dev);
+	if (status)
+		return status;
+
+	priv = devm_kzalloc(&dev->dev, sizeof(struct f81504_pci_private),
+				GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	pci_set_drvdata(dev, priv);
+
+	/* Generate UART Ports */
+	for (i = 0; i < dev_id->driver_data; ++i) {
+		/* Check UART is enabled */
+		pci_read_config_byte(dev, UART_START_ADDR + i * UART_OFFSET,
+					&tmp);
+		if (!tmp)
+			continue;
+
+		/* Get UART IO Address */
+		pci_read_config_word(dev, UART_START_ADDR + i * UART_OFFSET +
+					4, &iobase);
+
+		/* Register to serial port */
+		priv->line[priv->uart_count] = f81504_register_port(dev,
+								iobase, i);
+		++priv->uart_count;
+	}
+
+	return 0;
+}
+
+static void f81504_remove(struct pci_dev *dev)
+{
+	struct f81504_pci_private *priv = pci_get_drvdata(dev);
+	size_t i;
+
+	for (i = 0; i < priv->uart_count; ++i) {
+		if (priv->line[i] < 0)
+			continue;
+
+		serial8250_unregister_port(priv->line[i]);
+	}
+
+	pci_disable_device(dev);
+}
+
+#ifdef CONFIG_PM
+static int f81504_suspend(struct pci_dev *dev, pm_message_t state)
+{
+	size_t i;
+	int status;
+	struct f81504_pci_private *priv = pci_get_drvdata(dev);
+
+	status = pci_save_state(dev);
+	if (status)
+		return status;
+
+	status = pci_set_power_state(dev, pci_choose_state(dev, state));
+	if (status)
+		return status;
+
+	for (i = 0; i < priv->uart_count; ++i) {
+		if (priv->line[i] < 0)
+			continue;
+
+		serial8250_suspend_port(priv->line[i]);
+	}
+
+	return 0;
+}
+
+static int f81504_resume(struct pci_dev *dev)
+{
+	size_t i;
+	int status;
+	struct f81504_pci_private *priv = pci_get_drvdata(dev);
+
+	status = pci_set_power_state(dev, PCI_D0);
+	if (status)
+		return status;
+
+	pci_restore_state(dev);
+
+	/* Init PCI Configuration Space */
+	status = f81504_port_init(dev);
+	if (status)
+		return status;
+
+	for (i = 0; i < priv->uart_count; ++i) {
+		if (priv->line[i] < 0)
+			continue;
+
+		serial8250_resume_port(priv->line[i]);
+	}
+
+	return 0;
+}
+#else
+#define f81504_suspend NULL
+#define f81504_resume NULL
+#endif
+
+static const struct pci_device_id f81504_dev_table[] = {
+	/* Fintek PCI serial cards */
+	{PCI_DEVICE(FINTEK_VID, FINTEK_F81504), .driver_data = 4},
+	{PCI_DEVICE(FINTEK_VID, FINTEK_F81508), .driver_data = 8},
+	{PCI_DEVICE(FINTEK_VID, FINTEK_F81512), .driver_data = 12},
+	{}
+};
+
+MODULE_DEVICE_TABLE(pci, f81504_dev_table);
+
+static struct pci_driver f81504_driver = {
+	.name = DRIVER_NAME,
+	.probe = f81504_probe,
+	.remove = f81504_remove,
+	.suspend = f81504_suspend,
+	.resume = f81504_resume,
+	.id_table = f81504_dev_table,
+};
+
+module_pci_driver(f81504_driver);
+
+MODULE_DESCRIPTION(DEV_DESC);
+MODULE_AUTHOR("Peter Hong <Peter_Hong@fintek.com.tw>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/8250/Kconfig b/drivers/tty/serial/8250/Kconfig
index b03cb517..0533a6b 100644
--- a/drivers/tty/serial/8250/Kconfig
+++ b/drivers/tty/serial/8250/Kconfig
@@ -116,6 +116,15 @@ config SERIAL_8250_PCI
 	  Note that serial ports on NetMos 9835 Multi-I/O cards are handled
 	  by the parport_serial driver, enabled with CONFIG_PARPORT_SERIAL.
 
+config SERIAL_8250_FINTEK_PCI
+        tristate "Fintek F81504/508/512 PCI-E to Multi Serial Ports support"
+        depends on SERIAL_8250 && PCI
+        default m
+        help
+          This driver works for Fintek F81504/508/512 PCIE-to-UART/GPIO
+          multi-function device. We'll use GPIOLIB interface to control
+          GPIOs if your card vendor reserve some pin as gpio.
+
 config SERIAL_8250_HP300
 	tristate
 	depends on SERIAL_8250 && HP300
diff --git a/drivers/tty/serial/8250/Makefile b/drivers/tty/serial/8250/Makefile
index b9b9bca..83ed91f 100644
--- a/drivers/tty/serial/8250/Makefile
+++ b/drivers/tty/serial/8250/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_SERIAL_8250)		+= 8250.o 8250_base.o
 8250_base-$(CONFIG_SERIAL_8250_DMA)	+= 8250_dma.o
 obj-$(CONFIG_SERIAL_8250_GSC)		+= 8250_gsc.o
 obj-$(CONFIG_SERIAL_8250_PCI)		+= 8250_pci.o
+obj-$(CONFIG_SERIAL_8250_FINTEK_PCI)	+= 8250_fintek_pci.o
 obj-$(CONFIG_SERIAL_8250_HP300)		+= 8250_hp300.o
 obj-$(CONFIG_SERIAL_8250_CS)		+= serial_cs.o
 obj-$(CONFIG_SERIAL_8250_ACORN)		+= 8250_acorn.o
-- 
1.9.1

  parent reply	other threads:[~2016-01-19  2:41 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-01-19  2:41 [PATCH 0/3] 8250: Split Fintek PCIE to UART to independent file Peter Hung
2016-01-19  2:41 ` [PATCH 1/3] serial: 8250_pci: Remove Fintek PCIE UART driver Peter Hung
2016-01-19  2:41 ` Peter Hung [this message]
2016-01-19  2:41 ` [PATCH 3/3] 8250_fintek_pci: Add GPIOLIB support Peter Hung
2016-01-19  3:56 ` [PATCH 0/3] 8250: Split Fintek PCIE to UART to independent file Paul Gortmaker
2016-01-19  3:56   ` Paul Gortmaker
2016-01-19  8:45   ` Peter Hung
2016-01-19  9:33     ` Andy Shevchenko
2016-01-19 12:33     ` One Thousand Gnomes
2016-01-19 13:21       ` Andy Shevchenko
2016-01-20  2:59         ` Peter Hung
2016-01-20  6:22           ` Sudip Mukherjee
2016-01-20  8:24             ` Peter Hung
2016-01-22 10:53               ` Sudip Mukherjee
2016-01-22 13:44                 ` Andy Shevchenko
2016-01-22 13:44                   ` Andy Shevchenko
2016-01-29 17:38                   ` Sudip Mukherjee
2016-01-29 17:38                     ` Sudip Mukherjee
2016-01-29 18:35                     ` Andy Shevchenko
2016-01-29 18:35                       ` Andy Shevchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1453171266-15874-3-git-send-email-hpeter+linux_kernel@gmail.com \
    --to=hpeter@gmail.com \
    --cc=adam.lee@canonical.com \
    --cc=andriy.shevchenko@linux.intel.com \
    --cc=arnd@arndb.de \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=hpeter+linux_kernel@gmail.com \
    --cc=jslaby@suse.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=manabian@gmail.com \
    --cc=mans@mansr.com \
    --cc=matthias.bgg@gmail.com \
    --cc=paul.burton@imgtec.com \
    --cc=paul.gortmaker@windriver.com \
    --cc=peter.ujfalusi@ti.com \
    --cc=peter@hurleysoftware.com \
    --cc=peter_hong@fintek.com.tw \
    --cc=scottwood@freescale.com \
    --cc=soeren.grunewald@desy.de \
    --cc=udknight@gmail.com \
    --cc=yamada.masahiro@socionext.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.