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 | 11 ++ drivers/mfd/Makefile | 2 + drivers/mfd/f81504-core.c | 331 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/f81504.h | 52 +++++++ 4 files changed, 396 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 9ca66de..40503a4 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -310,6 +310,17 @@ 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 y + help + This driver generate F81504/508/512 UART & GPIO platform + device. It should enable CONFIG_GPIO_F81504 to get GPIOLIB + support and CONFIG_8250_F81504 to get serial ports support. + Please bulit-in kernel if you need 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 0f230a6..f7382b3 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -21,6 +21,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..9059171 --- /dev/null +++ b/drivers/mfd/f81504-core.c @@ -0,0 +1,331 @@ +/* + * 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_DRIVER_NAME "f81504_core" +#define F81504_DEV_DESC "Fintek F81504/508/512 PCIE-to-UART core" +#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 int f81504_port_init(struct pci_dev *dev) +{ + size_t i, j; + u32 max_port, iobase, gpio_addr; + u32 bar_data[3]; + u16 tmp; + u8 config_base, gpio_en, f0h_data, f3h_data; + bool is_gpio; + struct f81504_pci_private *priv = pci_get_drvdata(dev); + + /* Init GPIO IO Address */ + pci_read_config_dword(dev, 0x18, &gpio_addr); + gpio_addr &= 0xffffffe0; + pci_write_config_byte(dev, F81504_GPIO_IO_LSB_REG, gpio_addr & 0xff); + pci_write_config_byte(dev, 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) { + /* Reinit from resume(), read the previous value from priv */ + gpio_en = priv->gpio_en; + + /* re-save GPIO IO addr for called by resume() */ + priv->gpio_ioaddr = gpio_addr; + } else { + /* Driver first init */ + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, &f0h_data); + pci_read_config_byte(dev, F81504_GPIO_MODE_REG, &f3h_data); + + /* find the max set of GPIOs */ + gpio_en = f0h_data | ~f3h_data; + } + + switch (dev->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 = dev->device & 0xff; + break; + case FINTEK_F81512: /* 12 ports */ + max_port = 12; + break; + default: + return -EINVAL; + } + + /* rewrite GPIO Mode setting */ + pci_write_config_byte(dev, F81504_GPIO_ENABLE_REG, gpio_en & 0x3f); + pci_write_config_byte(dev, F81504_GPIO_MODE_REG, ~gpio_en & 0x3f); + + /* 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 bit for newer step IC */ + pci_read_config_word(dev, F81504_IRQSEL_REG, &tmp); + tmp |= BIT(8); + pci_write_config_word(dev, 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; + is_gpio = false; + + /* find every port to check is multi-function port? */ + for (j = 0; j < ARRAY_SIZE(fintek_gpio_mapping); ++j) { + if (fintek_gpio_mapping[j] != i || !(gpio_en & BIT(j))) + continue; + + /* + * This port is multi-function and enabled as gpio + * mode. So we'll not configure it as serial port. + */ + is_gpio = true; + break; + } + + /* + * If the serial port is setting to gpio mode, don't init it. + * Disable the serial port for user-space application to + * control. + */ + if (is_gpio) { + /* Disable current serial port */ + pci_write_config_byte(dev, 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(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); + + /* + * Force init to RS232 / Share Mode, recovery previous mode + * will done in F81504 8250 platform driver resume() + */ + pci_write_config_byte(dev, config_base + 0x07, 0x01); + } + + return 0; +} + +static int f81504_prepage_serial_port(struct pci_dev *dev, int max_port) +{ + size_t i; + int status; + u8 tmp; + u16 iobase; + struct resource resources = DEFINE_RES_IO(0, 0); + struct mfd_cell f81504_serial_cell = { + .name = F81504_SERIAL_NAME, + .num_resources = 1, + }; + + for (i = 0; i < max_port; ++i) { + /* Check UART is enabled */ + pci_read_config_byte(dev, F81504_UART_START_ADDR + i * + F81504_UART_OFFSET, &tmp); + if (!tmp) + continue; + + /* Get UART IO Address */ + pci_read_config_word(dev, 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.pdata_size = sizeof(i); + f81504_serial_cell.platform_data = &i; + + status = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, + &f81504_serial_cell, 1, NULL, dev->irq, + NULL); + if (status) { + dev_warn(&dev->dev, "%s: add device failed: %d\n", + __func__, status); + return status; + } + } + + return 0; +} + +static int f81504_prepage_gpiolib(struct pci_dev *dev) +{ + size_t i; + int status; + struct f81504_pci_private *priv = pci_get_drvdata(dev); + struct mfd_cell f81504_gpio_cell = { + .name = F81504_GPIO_NAME, + }; + + for (i = 0; i < ARRAY_SIZE(fintek_gpio_mapping); ++i) { + if (!(priv->gpio_en & BIT(i))) + continue; + + f81504_gpio_cell.pdata_size = sizeof(i); + f81504_gpio_cell.platform_data = &i; + + status = mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO, + &f81504_gpio_cell, 1, NULL, dev->irq, + NULL); + if (status) { + dev_warn(&dev->dev, "%s: add device failed: %d\n", + __func__, status); + return status; + } + } + + return 0; +} + +static int f81504_probe(struct pci_dev *dev, const struct pci_device_id + *dev_id) +{ + int status; + 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; + + /* Save the GPIO_ENABLE_REG after f81504_port_init() for future use */ + pci_read_config_byte(dev, F81504_GPIO_ENABLE_REG, &priv->gpio_en); + + /* Save GPIO IO Addr to private data */ + pci_read_config_byte(dev, F81504_GPIO_IO_MSB_REG, &tmp); + priv->gpio_ioaddr = tmp << 8; + pci_read_config_byte(dev, F81504_GPIO_IO_LSB_REG, &tmp); + priv->gpio_ioaddr |= tmp; + + pci_set_drvdata(dev, priv); + + /* Generate UART Ports */ + status = f81504_prepage_serial_port(dev, dev_id->driver_data); + if (status) + goto failed; + + /* Generate GPIO Sets */ + status = f81504_prepage_gpiolib(dev); + if (status) + goto failed; + + return 0; + +failed: + mfd_remove_devices(&dev->dev); + pci_disable_device(dev); + return status; +} + +static void f81504_remove(struct pci_dev *dev) +{ + mfd_remove_devices(&dev->dev); + pci_disable_device(dev); +} + +#ifdef CONFIG_PM_SLEEP +static int f81504_suspend(struct device *dev) +{ + return 0; +} + +static int f81504_resume(struct device *dev) +{ + int status; + struct pci_dev *pdev = to_pci_dev(dev); + + status = pci_enable_device(pdev); + if (status) + return 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_DRIVER_NAME, + .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(F81504_DEV_DESC); +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..13bd0ae --- /dev/null +++ b/include/linux/mfd/f81504.h @@ -0,0 +1,52 @@ +#ifndef __F81504_H__ +#define __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 -- To unsubscribe from this list: send the line "unsubscribe linux-gpio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html