Merge tag 'gpio-v4.13-1' of git://git.kernel.org/pub/scm/linux/kernel/git/linusw...
[sfrench/cifs-2.6.git] / drivers / tty / serial / 8250 / 8250_exar.c
index a309bcffffcd37509c88dc4b305e8b6d82701d99..b5c98e5bf52402c2febb0b4d64d54dd19c739446 100644 (file)
@@ -9,10 +9,13 @@
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License.
  */
+#include <linux/acpi.h>
+#include <linux/dmi.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/pci.h>
+#include <linux/property.h>
 #include <linux/serial_core.h>
 #include <linux/serial_reg.h>
 #include <linux/slab.h>
 #define UART_EXAR_MPIOSEL_15_8 0x99    /* MPIOSEL[15:8] */
 #define UART_EXAR_MPIOOD_15_8  0x9a    /* MPIOOD[15:8] */
 
+#define UART_EXAR_RS485_DLY(x) ((x) << 4)
+
+/*
+ * IOT2040 MPIO wiring semantics:
+ *
+ * MPIO                Port    Function
+ * ----                ----    --------
+ * 0           2       Mode bit 0
+ * 1           2       Mode bit 1
+ * 2           2       Terminate bus
+ * 3           -       <reserved>
+ * 4           3       Mode bit 0
+ * 5           3       Mode bit 1
+ * 6           3       Terminate bus
+ * 7           -       <reserved>
+ * 8           2       Enable
+ * 9           3       Enable
+ * 10          -       Red LED
+ * 11..15      -       <unused>
+ */
+
+/* IOT2040 MPIOs 0..7 */
+#define IOT2040_UART_MODE_RS232                0x01
+#define IOT2040_UART_MODE_RS485                0x02
+#define IOT2040_UART_MODE_RS422                0x03
+#define IOT2040_UART_TERMINATE_BUS     0x04
+
+#define IOT2040_UART1_MASK             0x0f
+#define IOT2040_UART2_SHIFT            4
+
+#define IOT2040_UARTS_DEFAULT_MODE     0x11    /* both RS232 */
+#define IOT2040_UARTS_GPIO_LO_MODE     0x88    /* reserved pins as input */
+
+/* IOT2040 MPIOs 8..15 */
+#define IOT2040_UARTS_ENABLE           0x03
+#define IOT2040_UARTS_GPIO_HI_MODE     0xF8    /* enable & LED as outputs */
+
 struct exar8250;
 
+struct exar8250_platform {
+       int (*rs485_config)(struct uart_port *, struct serial_rs485 *);
+       int (*register_gpio)(struct pci_dev *, struct uart_8250_port *);
+};
+
 /**
  * struct exar8250_board - board information
  * @num_ports: number of serial ports
@@ -194,7 +239,8 @@ static void setup_gpio(struct pci_dev *pcidev, u8 __iomem *p)
 }
 
 static void *
-xr17v35x_register_gpio(struct pci_dev *pcidev)
+__xr17v35x_register_gpio(struct pci_dev *pcidev,
+                        const struct property_entry *properties)
 {
        struct platform_device *pdev;
 
@@ -202,8 +248,11 @@ xr17v35x_register_gpio(struct pci_dev *pcidev)
        if (!pdev)
                return NULL;
 
-       platform_set_drvdata(pdev, pcidev);
-       if (platform_device_add(pdev) < 0) {
+       pdev->dev.parent = &pcidev->dev;
+       ACPI_COMPANION_SET(&pdev->dev, ACPI_COMPANION(&pcidev->dev));
+
+       if (platform_device_add_properties(pdev, properties) < 0 ||
+           platform_device_add(pdev) < 0) {
                platform_device_put(pdev);
                return NULL;
        }
@@ -211,17 +260,131 @@ xr17v35x_register_gpio(struct pci_dev *pcidev)
        return pdev;
 }
 
+static const struct property_entry exar_gpio_properties[] = {
+       PROPERTY_ENTRY_U32("linux,first-pin", 0),
+       PROPERTY_ENTRY_U32("ngpios", 16),
+       { }
+};
+
+static int xr17v35x_register_gpio(struct pci_dev *pcidev,
+                                 struct uart_8250_port *port)
+{
+       if (pcidev->vendor == PCI_VENDOR_ID_EXAR)
+               port->port.private_data =
+                       __xr17v35x_register_gpio(pcidev, exar_gpio_properties);
+
+       return 0;
+}
+
+static const struct exar8250_platform exar8250_default_platform = {
+       .register_gpio = xr17v35x_register_gpio,
+};
+
+static int iot2040_rs485_config(struct uart_port *port,
+                               struct serial_rs485 *rs485)
+{
+       bool is_rs485 = !!(rs485->flags & SER_RS485_ENABLED);
+       u8 __iomem *p = port->membase;
+       u8 mask = IOT2040_UART1_MASK;
+       u8 mode, value;
+
+       if (is_rs485) {
+               if (rs485->flags & SER_RS485_RX_DURING_TX)
+                       mode = IOT2040_UART_MODE_RS422;
+               else
+                       mode = IOT2040_UART_MODE_RS485;
+
+               if (rs485->flags & SER_RS485_TERMINATE_BUS)
+                       mode |= IOT2040_UART_TERMINATE_BUS;
+       } else {
+               mode = IOT2040_UART_MODE_RS232;
+       }
+
+       if (port->line == 3) {
+               mask <<= IOT2040_UART2_SHIFT;
+               mode <<= IOT2040_UART2_SHIFT;
+       }
+
+       value = readb(p + UART_EXAR_MPIOLVL_7_0);
+       value &= ~mask;
+       value |= mode;
+       writeb(value, p + UART_EXAR_MPIOLVL_7_0);
+
+       value = readb(p + UART_EXAR_FCTR);
+       if (is_rs485)
+               value |= UART_FCTR_EXAR_485;
+       else
+               value &= ~UART_FCTR_EXAR_485;
+       writeb(value, p + UART_EXAR_FCTR);
+
+       if (is_rs485)
+               writeb(UART_EXAR_RS485_DLY(4), p + UART_MSR);
+
+       port->rs485 = *rs485;
+
+       return 0;
+}
+
+static const struct property_entry iot2040_gpio_properties[] = {
+       PROPERTY_ENTRY_U32("linux,first-pin", 10),
+       PROPERTY_ENTRY_U32("ngpios", 1),
+       { }
+};
+
+static int iot2040_register_gpio(struct pci_dev *pcidev,
+                             struct uart_8250_port *port)
+{
+       u8 __iomem *p = port->port.membase;
+
+       writeb(IOT2040_UARTS_DEFAULT_MODE, p + UART_EXAR_MPIOLVL_7_0);
+       writeb(IOT2040_UARTS_GPIO_LO_MODE, p + UART_EXAR_MPIOSEL_7_0);
+       writeb(IOT2040_UARTS_ENABLE, p + UART_EXAR_MPIOLVL_15_8);
+       writeb(IOT2040_UARTS_GPIO_HI_MODE, p + UART_EXAR_MPIOSEL_15_8);
+
+       port->port.private_data =
+               __xr17v35x_register_gpio(pcidev, iot2040_gpio_properties);
+
+       return 0;
+}
+
+static const struct exar8250_platform iot2040_platform = {
+       .rs485_config = iot2040_rs485_config,
+       .register_gpio = iot2040_register_gpio,
+};
+
+static const struct dmi_system_id exar_platforms[] = {
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_BOARD_NAME, "SIMATIC IOT2000"),
+                       DMI_EXACT_MATCH(DMI_BOARD_ASSET_TAG,
+                                       "6ES7647-0AA00-1YA2"),
+               },
+               .driver_data = (void *)&iot2040_platform,
+       },
+       {}
+};
+
 static int
 pci_xr17v35x_setup(struct exar8250 *priv, struct pci_dev *pcidev,
                   struct uart_8250_port *port, int idx)
 {
        const struct exar8250_board *board = priv->board;
+       const struct exar8250_platform *platform;
+       const struct dmi_system_id *dmi_match;
        unsigned int offset = idx * 0x400;
        unsigned int baud = 7812500;
        u8 __iomem *p;
        int ret;
 
+       dmi_match = dmi_first_match(exar_platforms);
+       if (dmi_match)
+               platform = dmi_match->driver_data;
+       else
+               platform = &exar8250_default_platform;
+
        port->port.uartclk = baud * 16;
+       port->port.rs485_config = platform->rs485_config;
+
        /*
         * Setup the uart clock for the devices on expansion slot to
         * half the clock speed of the main chip (which is 125MHz)
@@ -244,10 +407,10 @@ pci_xr17v35x_setup(struct exar8250 *priv, struct pci_dev *pcidev,
                /* Setup Multipurpose Input/Output pins. */
                setup_gpio(pcidev, p);
 
-               port->port.private_data = xr17v35x_register_gpio(pcidev);
+               ret = platform->register_gpio(pcidev, port);
        }
 
-       return 0;
+       return ret;
 }
 
 static void pci_xr17v35x_exit(struct pci_dev *pcidev)