[PATCH V3 1/4] mfd: f81504-core: Add Fintek F81504/508/512 PCIE-to-UART/GPIO core support

From: Peter Hung
Date: Tue Feb 16 2016 - 01:56:40 EST


The Fintek F81504/508/512 had implemented the basic serial port function in
8250_pci.c. We try to implement high baudrate & GPIOLIB with a spilt file
8250_f81504.c, but it seems too complex to add GPIOLIB.

Alan & Andy recommend us to rewrite and spilt our driver with MFD
architecture.
https://lkml.org/lkml/2016/1/19/288

This driver is core driver for F81504/508/512, it'll handle the generation
of UART/GPIO platform device and initialize PCIE configuration space when
probe()/resume().

IC function list:
F81504: Max 2x8 GPIOs and max 4 serial ports
port2/3 are multi-function
F81508: Max 6x8 GPIOs and max 8 serial ports
port2/3 are multi-function, port8/9/10/11 are gpio only
F81512: Max 6x8 GPIOs and max 12 serial ports
port2/3/8/9/10/11 are multi-function

H/W provider could changes the PCI configure space F0/F3h
values in EEPROM or ASL code to change mode.

F0h bit0~5: Enable GPIO0~5
bit6~7: Reserve

F3h bit0~5: Multi-Functional Flag (0:GPIO/1:UART)
bit0: UART2 pin out for UART2 / GPIO0
bit1: UART3 pin out for UART3 / GPIO1
bit2: UART8 pin out for UART8 / GPIO2
bit3: UART9 pin out for UART9 / GPIO3
bit4: UART10 pin out for UART10 / GPIO4
bit5: UART11 pin out for UART11 / GPIO5
bit6~7: Reserve

Suggested-by: One Thousand Gnomes <gnomes@xxxxxxxxxxxxxxxxxxx>
Suggested-by: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx>
Signed-off-by: Peter Hung <hpeter+linux_kernel@xxxxxxxxx>
---
drivers/mfd/Kconfig | 12 ++
drivers/mfd/Makefile | 2 +
drivers/mfd/f81504-core.c | 336 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/mfd/f81504.h | 52 +++++++
4 files changed, 402 insertions(+)
create mode 100644 drivers/mfd/f81504-core.c
create mode 100644 include/linux/mfd/f81504.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index aa21dc5..775761f 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -345,6 +345,18 @@ config HTC_I2CPLD
This device provides input and output GPIOs through an I2C
interface to one or more sub-chips.

+config MFD_FINTEK_F81504_CORE
+ tristate "Fintek F81504/508/512 PCIE-to-UART/GPIO MFD support"
+ depends on PCI
+ select MFD_CORE
+ default SERIAL_8250
+ help
+ This driver provides the F81504/508/512 UART & GPIO platform
+ devices. You should enable CONFIG_GPIO_F81504 to get GPIOLIB
+ support and CONFIG_8250_F81504 to get serial port support.
+ This driver needs to be built into the kernel to use early
+ console support.
+
config MFD_INTEL_QUARK_I2C_GPIO
tristate "Intel Quark MFD I2C GPIO"
depends on PCI
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 5eaa6465d..8e581ad 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -22,6 +22,8 @@ obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o
obj-$(CONFIG_HTC_PASIC3) += htc-pasic3.o
obj-$(CONFIG_HTC_I2CPLD) += htc-i2cpld.o

+obj-$(CONFIG_MFD_FINTEK_F81504_CORE) += f81504-core.o
+
obj-$(CONFIG_MFD_DAVINCI_VOICECODEC) += davinci_voicecodec.o
obj-$(CONFIG_MFD_DM355EVM_MSP) += dm355evm_msp.o
obj-$(CONFIG_MFD_TI_AM335X_TSCADC) += ti_am335x_tscadc.o
diff --git a/drivers/mfd/f81504-core.c b/drivers/mfd/f81504-core.c
new file mode 100644
index 0000000..12a5f7f
--- /dev/null
+++ b/drivers/mfd/f81504-core.c
@@ -0,0 +1,336 @@
+/*
+ * Core operations for Fintek F81504/508/512 PCIE-to-UART/GPIO device
+ */
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/f81504.h>
+
+#define F81504_IO_REGION 8
+
+const u8 fintek_gpio_mapping[F81504_MAX_GPIO_CNT] = { 2, 3, 8, 9, 10, 11 };
+EXPORT_SYMBOL(fintek_gpio_mapping);
+
+static bool f81504_is_gpio(unsigned int idx, u8 gpio_en)
+{
+ unsigned int i;
+
+ /* find every port to check is multi-function port? */
+ for (i = 0; i < ARRAY_SIZE(fintek_gpio_mapping); i++) {
+ if (fintek_gpio_mapping[i] != idx || !(gpio_en & BIT(i)))
+ continue;
+
+ /*
+ * This port is multi-function and enabled as gpio
+ * mode. So we'll not configure it as serial port.
+ */
+ return true;
+ }
+
+ return false;
+}
+
+static int f81504_port_init(struct pci_dev *pdev)
+{
+ struct f81504_pci_private *priv = pci_get_drvdata(pdev);
+ unsigned int i;
+ u32 gpio_addr;
+ u8 gpio_en, f0h_data, f3h_data;
+ u32 max_port, iobase;
+ u32 bar_data[3];
+ u16 tmp;
+ u8 config_base;
+
+ /* Init GPIO IO Address */
+ gpio_addr = pci_resource_start(pdev, 2);
+
+ /*
+ * Write GPIO IO Address LSB/MSB to corresponding register. Due to we
+ * can't write once with pci_write_config_word() on x86 platform, we'll
+ * write it with pci_write_config_byte().
+ */
+ pci_write_config_byte(pdev, F81504_GPIO_IO_LSB_REG, gpio_addr & 0xff);
+ pci_write_config_byte(pdev, F81504_GPIO_IO_MSB_REG, (gpio_addr >> 8) &
+ 0xff);
+
+ /*
+ * The PCI board is multi-function, some serial port can converts to
+ * GPIO function. Customers could changes the F0/F3h values in EEPROM
+ *
+ * F0h bit0~5: Enable GPIO0~5
+ * bit6~7: Reserve
+ *
+ * F3h bit0~5: Multi-Functional Flag (0:GPIO/1:UART)
+ * bit0: UART2 pin out for UART2 / GPIO0
+ * bit1: UART3 pin out for UART3 / GPIO1
+ * bit2: UART8 pin out for UART8 / GPIO2
+ * bit3: UART9 pin out for UART9 / GPIO3
+ * bit4: UART10 pin out for UART10 / GPIO4
+ * bit5: UART11 pin out for UART11 / GPIO5
+ * bit6~7: Reserve
+ */
+ if (priv) {
+ /* Re-save GPIO IO address for called by resume() */
+ priv->gpio_ioaddr = gpio_addr;
+
+ /* Reinit from resume(), read the previous value from priv */
+ gpio_en = priv->gpio_en;
+ } else {
+ /* Driver first init */
+ pci_read_config_byte(pdev, F81504_GPIO_ENABLE_REG, &f0h_data);
+ pci_read_config_byte(pdev, F81504_GPIO_MODE_REG, &f3h_data);
+
+ /* Find the max set of GPIOs */
+ gpio_en = f0h_data | ~f3h_data;
+ }
+
+ /*
+ * We'll determinate the max count of serial port by IC Product ID.
+ * Product ID list: F81504 pid = 0x1104
+ * F81508 pid = 0x1108
+ * F81512 pid = 0x1112
+ *
+ * In F81504/508, we can use pid & 0xff to get max serial port count,
+ * but F81512 can't use this. So we should direct set F81512 to 12.
+ */
+ switch (pdev->device) {
+ case FINTEK_F81504: /* 4 ports */
+ /* F81504 max 2 sets of GPIO, others are max 6 sets */
+ gpio_en &= 0x03;
+ case FINTEK_F81508: /* 8 ports */
+ max_port = pdev->device & 0xff;
+ break;
+ case FINTEK_F81512: /* 12 ports */
+ max_port = 12;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Rewrite GPIO Mode setting */
+ pci_write_config_byte(pdev, F81504_GPIO_ENABLE_REG, gpio_en & 0x3f);
+ pci_write_config_byte(pdev, F81504_GPIO_MODE_REG, ~gpio_en & 0x3f);
+
+ /* Get the UART IO address dispatch from the BIOS */
+ for (i = 0; i < 3; i++)
+ bar_data[i] = pci_resource_start(pdev, 3 + i);
+
+ /* Compatible bit for newer step IC */
+ pci_read_config_word(pdev, F81504_IRQSEL_REG, &tmp);
+ tmp |= BIT(8);
+ pci_write_config_word(pdev, F81504_IRQSEL_REG, tmp);
+
+ for (i = 0; i < max_port; i++) {
+ /* UART0 configuration offset start from 0x40 */
+ config_base = F81504_UART_START_ADDR + F81504_UART_OFFSET * i;
+
+ /*
+ * If the serial port is setting to gpio mode, don't init it.
+ * Disable the serial port for user-space application to
+ * control.
+ */
+ if (f81504_is_gpio(i, gpio_en)) {
+ /* Disable current serial port */
+ pci_write_config_byte(pdev, config_base + 0x00, 0x00);
+ continue;
+ }
+
+ /* Calculate Real IO Port */
+ iobase = (bar_data[i / 4] & 0xffffffe0) + (i % 4) * 8;
+
+ /* Enable UART I/O port */
+ pci_write_config_byte(pdev, config_base + 0x00, 0x01);
+
+ /* Select 128-byte FIFO and 8x FIFO threshold */
+ pci_write_config_byte(pdev, config_base + 0x01, 0x33);
+
+ /* Write UART IO address */
+ pci_write_config_word(pdev, config_base + 0x04, iobase);
+
+ pci_write_config_byte(pdev, config_base + 0x06, pdev->irq);
+
+ /*
+ * Force init to RS232 / Share Mode, recovery previous mode
+ * will done in F81504 8250 platform driver resume().
+ */
+ pci_write_config_byte(pdev, config_base + 0x07, 0x01);
+ }
+
+ return 0;
+}
+
+static int f81504_prepage_serial_port(struct pci_dev *pdev, int max_port)
+{
+ struct resource resources = DEFINE_RES_IO(0, 0);
+ struct mfd_cell f81504_serial_cell = {
+ .name = F81504_SERIAL_NAME,
+ .num_resources = 1,
+ .pdata_size = sizeof(unsigned int),
+ };
+ unsigned int i;
+ u8 tmp;
+ u16 iobase;
+ int status;
+
+ for (i = 0; i < max_port; i++) {
+ /* Check UART is enabled */
+ pci_read_config_byte(pdev, F81504_UART_START_ADDR + i *
+ F81504_UART_OFFSET, &tmp);
+ if (!tmp)
+ continue;
+
+ /* Get UART IO Address */
+ pci_read_config_word(pdev, F81504_UART_START_ADDR + i *
+ F81504_UART_OFFSET + 4, &iobase);
+
+ resources.start = iobase;
+ resources.end = iobase + F81504_IO_REGION - 1;
+
+ f81504_serial_cell.resources = &resources;
+ f81504_serial_cell.platform_data = &i;
+
+ status = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO,
+ &f81504_serial_cell, 1, NULL,
+ pdev->irq, NULL);
+ if (status) {
+ dev_warn(&pdev->dev, "%s: add device failed: %d\n",
+ __func__, status);
+ return status;
+ }
+ }
+
+ return 0;
+}
+
+static int f81504_prepage_gpiolib(struct pci_dev *pdev)
+{
+ struct f81504_pci_private *priv = pci_get_drvdata(pdev);
+ struct mfd_cell f81504_gpio_cell = {
+ .name = F81504_GPIO_NAME,
+ .pdata_size = sizeof(unsigned int),
+ };
+ unsigned int i;
+ int status;
+
+ for (i = 0; i < ARRAY_SIZE(fintek_gpio_mapping); i++) {
+ if (!(priv->gpio_en & BIT(i)))
+ continue;
+
+ f81504_gpio_cell.platform_data = &i;
+ status = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO,
+ &f81504_gpio_cell, 1, NULL, pdev->irq,
+ NULL);
+ if (status) {
+ dev_warn(&pdev->dev, "%s: add device failed: %d\n",
+ __func__, status);
+ return status;
+ }
+ }
+
+ return 0;
+}
+
+static int f81504_probe(struct pci_dev *pdev, const struct pci_device_id
+ *dev_id)
+{
+ struct f81504_pci_private *priv;
+ u8 tmp;
+ int status;
+
+ status = pcim_enable_device(pdev);
+ if (status)
+ return status;
+
+ /* Init PCI Configuration Space */
+ status = f81504_port_init(pdev);
+ if (status)
+ return status;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(struct f81504_pci_private),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* Save the GPIO_ENABLE_REG after f81504_port_init() for future use */
+ pci_read_config_byte(pdev, F81504_GPIO_ENABLE_REG, &priv->gpio_en);
+
+ /*
+ * Save GPIO IO Addr to private data. Due to we can't read once with
+ * pci_read_config_word() on x86 platform, we'll read it with
+ * pci_read_config_byte().
+ */
+ pci_read_config_byte(pdev, F81504_GPIO_IO_MSB_REG, &tmp);
+ priv->gpio_ioaddr = tmp << 8;
+ pci_read_config_byte(pdev, F81504_GPIO_IO_LSB_REG, &tmp);
+ priv->gpio_ioaddr |= tmp;
+
+ pci_set_drvdata(pdev, priv);
+
+ /* Generate UART Ports */
+ status = f81504_prepage_serial_port(pdev, dev_id->driver_data);
+ if (status)
+ return status;
+
+ /* Generate GPIO Sets */
+ status = f81504_prepage_gpiolib(pdev);
+ if (status)
+ return status;
+
+ return 0;
+}
+
+static void f81504_remove(struct pci_dev *pdev)
+{
+ mfd_remove_devices(&pdev->dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int f81504_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int f81504_resume(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int status;
+
+ /* Re-init PCI Configuration Space */
+ status = f81504_port_init(pdev);
+ if (status)
+ return status;
+
+ return 0;
+}
+#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},
+ {}
+};
+
+static SIMPLE_DEV_PM_OPS(f81504_pm_ops, f81504_suspend, f81504_resume);
+
+static struct pci_driver f81504_driver = {
+ .name = "f81504_core",
+ .probe = f81504_probe,
+ .remove = f81504_remove,
+ .driver = {
+ .pm = &f81504_pm_ops,
+ .owner = THIS_MODULE,
+ },
+ .id_table = f81504_dev_table,
+};
+
+module_pci_driver(f81504_driver);
+
+MODULE_DEVICE_TABLE(pci, f81504_dev_table);
+MODULE_DESCRIPTION("Fintek F81504/508/512 PCIE-to-UART core");
+MODULE_AUTHOR("Peter Hong <Peter_Hong@xxxxxxxxxxxxx>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/f81504.h b/include/linux/mfd/f81504.h
new file mode 100644
index 0000000..820d4e0
--- /dev/null
+++ b/include/linux/mfd/f81504.h
@@ -0,0 +1,52 @@
+#ifndef __MFD_F81504_H__
+#define __MFD_F81504_H__
+
+#define FINTEK_VID 0x1c29
+#define FINTEK_F81504 0x1104
+#define FINTEK_F81508 0x1108
+#define FINTEK_F81512 0x1112
+
+#define FINTEK_MAX_PORT 12
+#define FINTEK_GPIO_NAME_LEN 32
+#define FINTEK_GPIO_DISPLAY "GPIO"
+
+#define F81504_UART_START_ADDR 0x40
+#define F81504_UART_MODE_OFFSET 0x07
+#define F81504_UART_OFFSET 0x08
+
+/* RTS will control by MCR if this bit is 0 */
+#define F81504_RTS_CONTROL_BY_HW BIT(4)
+/* only worked with FINTEK_RTS_CONTROL_BY_HW on */
+#define F81504_RTS_INVERT BIT(5)
+
+#define F81504_CLOCK_RATE_MASK 0xc0
+#define F81504_CLKSEL_1DOT846_MHZ 0x00
+#define F81504_CLKSEL_18DOT46_MHZ 0x40
+#define F81504_CLKSEL_24_MHZ 0x80
+#define F81504_CLKSEL_14DOT77_MHZ 0xc0
+
+#define F81504_IRQSEL_REG 0xb8
+
+#define F81504_GPIO_ENABLE_REG 0xf0
+#define F81504_GPIO_IO_LSB_REG 0xf1
+#define F81504_GPIO_IO_MSB_REG 0xf2
+#define F81504_GPIO_MODE_REG 0xf3
+
+#define F81504_GPIO_START_ADDR 0xf8
+#define F81504_GPIO_OUT_EN_OFFSET 0x00
+#define F81504_GPIO_DRIVE_EN_OFFSET 0x01
+#define F81504_GPIO_SET_OFFSET 0x08
+
+#define F81504_GPIO_NAME "f81504_gpio"
+#define F81504_SERIAL_NAME "f81504_serial"
+#define F81504_MAX_GPIO_CNT 6
+
+extern const u8 fintek_gpio_mapping[F81504_MAX_GPIO_CNT];
+
+struct f81504_pci_private {
+ int line[FINTEK_MAX_PORT];
+ u8 gpio_en;
+ u16 gpio_ioaddr;
+ u32 uart_count, gpio_count;
+};
+#endif
--
1.9.1