mfd: tqmx86: IO controller with I2C, Wachdog and GPIO
authorAndrew Lunn <andrew@lunn.ch>
Fri, 8 Feb 2019 23:06:22 +0000 (00:06 +0100)
committerLee Jones <lee.jones@linaro.org>
Mon, 18 Feb 2019 09:06:57 +0000 (09:06 +0000)
The QMX86 is a PLD present on some TQ-Systems ComExpress modules. It
provides 1 or 2 I2C bus masters, 8 GPIOs and a watchdog timer. Add an
MFD which will instantiate the individual drivers.

Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/tqmx86.c [new file with mode: 0644]

index f38f874..18ee803 100644 (file)
@@ -1677,6 +1677,14 @@ config MFD_TC6393XB
        help
          Support for Toshiba Mobile IO Controller TC6393XB
 
+config MFD_TQMX86
+       tristate "TQ-Systems IO controller TQMX86"
+       select MFD_CORE
+       help
+         Say yes here to enable support for various functions of the
+         TQ-Systems IO controller and watchdog device, found on their
+         ComExpress CPU modules.
+
 config MFD_VX855
        tristate "VIA VX855/VX875 integrated south bridge"
        depends on PCI
index a406fd3..61a078a 100644 (file)
@@ -36,6 +36,7 @@ obj-$(CONFIG_MFD_TC3589X)     += tc3589x.o
 obj-$(CONFIG_MFD_T7L66XB)      += t7l66xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6387XB)     += tc6387xb.o tmio_core.o
 obj-$(CONFIG_MFD_TC6393XB)     += tc6393xb.o tmio_core.o
+obj-$(CONFIG_MFD_TQMX86)       += tqmx86.o
 
 obj-$(CONFIG_MFD_LOCHNAGAR)    += lochnagar-i2c.o
 
diff --git a/drivers/mfd/tqmx86.c b/drivers/mfd/tqmx86.c
new file mode 100644 (file)
index 0000000..22d2f02
--- /dev/null
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * TQ-Systems PLD MFD core driver, based on vendor driver by
+ * Vadim V.Vlasov <vvlasov@dev.rtsoft.ru>
+ *
+ * Copyright (c) 2015 TQ-Systems GmbH
+ * Copyright (c) 2019 Andrew Lunn <andrew@lunn.ch>
+ */
+
+#include <linux/delay.h>
+#include <linux/dmi.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/platform_data/i2c-ocores.h>
+#include <linux/platform_device.h>
+
+#define TQMX86_IOBASE  0x160
+#define TQMX86_IOSIZE  0x3f
+#define TQMX86_IOBASE_I2C      0x1a0
+#define TQMX86_IOSIZE_I2C      0xa
+#define TQMX86_IOBASE_WATCHDOG 0x18b
+#define TQMX86_IOSIZE_WATCHDOG 0x2
+#define TQMX86_IOBASE_GPIO     0x18d
+#define TQMX86_IOSIZE_GPIO     0x4
+
+#define TQMX86_REG_BOARD_ID    0x20
+#define TQMX86_REG_BOARD_ID_E38M       1
+#define TQMX86_REG_BOARD_ID_50UC       2
+#define TQMX86_REG_BOARD_ID_E38C       3
+#define TQMX86_REG_BOARD_ID_60EB       4
+#define TQMX86_REG_BOARD_ID_E39M       5
+#define TQMX86_REG_BOARD_ID_E39C       6
+#define TQMX86_REG_BOARD_ID_E39x       7
+#define TQMX86_REG_BOARD_ID_70EB       8
+#define TQMX86_REG_BOARD_ID_80UC       9
+#define TQMX86_REG_BOARD_ID_90UC       10
+#define TQMX86_REG_BOARD_REV   0x21
+#define TQMX86_REG_IO_EXT_INT  0x26
+#define TQMX86_REG_IO_EXT_INT_NONE             0
+#define TQMX86_REG_IO_EXT_INT_7                        1
+#define TQMX86_REG_IO_EXT_INT_9                        2
+#define TQMX86_REG_IO_EXT_INT_12               3
+#define TQMX86_REG_IO_EXT_INT_MASK             0x3
+#define TQMX86_REG_IO_EXT_INT_GPIO_SHIFT       4
+
+#define TQMX86_REG_I2C_DETECT  0x47
+#define TQMX86_REG_I2C_DETECT_SOFT             0xa5
+#define TQMX86_REG_I2C_INT_EN  0x49
+
+static uint gpio_irq;
+module_param(gpio_irq, uint, 0);
+MODULE_PARM_DESC(gpio_irq, "GPIO IRQ number (7, 9, 12)");
+
+static const struct resource tqmx_i2c_soft_resources[] = {
+       DEFINE_RES_IO(TQMX86_IOBASE_I2C, TQMX86_IOSIZE_I2C),
+};
+
+static const struct resource tqmx_watchdog_resources[] = {
+       DEFINE_RES_IO(TQMX86_IOBASE_WATCHDOG, TQMX86_IOSIZE_WATCHDOG),
+};
+
+/*
+ * The IRQ resource must be first, since it is updated with the
+ * configured IRQ in the probe function.
+ */
+static struct resource tqmx_gpio_resources[] = {
+       DEFINE_RES_IRQ(0),
+       DEFINE_RES_IO(TQMX86_IOBASE_GPIO, TQMX86_IOSIZE_GPIO),
+};
+
+static struct i2c_board_info tqmx86_i2c_devices[] = {
+       {
+               /* 4K EEPROM at 0x50 */
+               I2C_BOARD_INFO("24c32", 0x50),
+       },
+};
+
+static struct ocores_i2c_platform_data ocores_platfom_data = {
+       .num_devices = ARRAY_SIZE(tqmx86_i2c_devices),
+       .devices = tqmx86_i2c_devices,
+};
+
+static const struct mfd_cell tqmx86_i2c_soft_dev[] = {
+       {
+               .name = "ocores-i2c",
+               .platform_data = &ocores_platfom_data,
+               .pdata_size = sizeof(ocores_platfom_data),
+               .resources = tqmx_i2c_soft_resources,
+               .num_resources = ARRAY_SIZE(tqmx_i2c_soft_resources),
+       },
+};
+
+static const struct mfd_cell tqmx86_devs[] = {
+       {
+               .name = "tqmx86-wdt",
+               .resources = tqmx_watchdog_resources,
+               .num_resources = ARRAY_SIZE(tqmx_watchdog_resources),
+               .ignore_resource_conflicts = true,
+       },
+       {
+               .name = "tqmx86-gpio",
+               .resources = tqmx_gpio_resources,
+               .num_resources = ARRAY_SIZE(tqmx_gpio_resources),
+               .ignore_resource_conflicts = true,
+       },
+};
+
+static const char *tqmx86_board_id_to_name(u8 board_id)
+{
+       switch (board_id) {
+       case TQMX86_REG_BOARD_ID_E38M:
+               return "TQMxE38M";
+       case TQMX86_REG_BOARD_ID_50UC:
+               return "TQMx50UC";
+       case TQMX86_REG_BOARD_ID_E38C:
+               return "TQMxE38C";
+       case TQMX86_REG_BOARD_ID_60EB:
+               return "TQMx60EB";
+       case TQMX86_REG_BOARD_ID_E39M:
+               return "TQMxE39M";
+       case TQMX86_REG_BOARD_ID_E39C:
+               return "TQMxE39C";
+       case TQMX86_REG_BOARD_ID_E39x:
+               return "TQMxE39x";
+       case TQMX86_REG_BOARD_ID_70EB:
+               return "TQMx70EB";
+       case TQMX86_REG_BOARD_ID_80UC:
+               return "TQMx80UC";
+       case TQMX86_REG_BOARD_ID_90UC:
+               return "TQMx90UC";
+       default:
+               return "Unknown";
+       }
+}
+
+static int tqmx86_board_id_to_clk_rate(u8 board_id)
+{
+       switch (board_id) {
+       case TQMX86_REG_BOARD_ID_50UC:
+       case TQMX86_REG_BOARD_ID_60EB:
+       case TQMX86_REG_BOARD_ID_70EB:
+       case TQMX86_REG_BOARD_ID_80UC:
+       case TQMX86_REG_BOARD_ID_90UC:
+               return 24000;
+       case TQMX86_REG_BOARD_ID_E39M:
+       case TQMX86_REG_BOARD_ID_E39C:
+       case TQMX86_REG_BOARD_ID_E39x:
+               return 25000;
+       case TQMX86_REG_BOARD_ID_E38M:
+       case TQMX86_REG_BOARD_ID_E38C:
+               return 33000;
+       default:
+               return 0;
+       }
+}
+
+static int tqmx86_probe(struct platform_device *pdev)
+{
+       u8 board_id, rev, i2c_det, i2c_ien, io_ext_int_val;
+       struct device *dev = &pdev->dev;
+       u8 gpio_irq_cfg, readback;
+       const char *board_name;
+       void __iomem *io_base;
+       int err;
+
+       switch (gpio_irq) {
+       case 0:
+               gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_NONE;
+               break;
+       case 7:
+               gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_7;
+               break;
+       case 9:
+               gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_9;
+               break;
+       case 12:
+               gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_12;
+               break;
+       default:
+               pr_err("tqmx86: Invalid GPIO IRQ (%d)\n", gpio_irq);
+               return -EINVAL;
+       }
+
+       io_base = devm_ioport_map(dev, TQMX86_IOBASE, TQMX86_IOSIZE);
+       if (!io_base)
+               return -ENOMEM;
+
+       board_id = ioread8(io_base + TQMX86_REG_BOARD_ID);
+       board_name = tqmx86_board_id_to_name(board_id);
+       rev = ioread8(io_base + TQMX86_REG_BOARD_REV);
+
+       dev_info(dev,
+                "Found %s - Board ID %d, PCB Revision %d, PLD Revision %d\n",
+                board_name, board_id, rev >> 4, rev & 0xf);
+
+       i2c_det = ioread8(io_base + TQMX86_REG_I2C_DETECT);
+       i2c_ien = ioread8(io_base + TQMX86_REG_I2C_INT_EN);
+
+       if (gpio_irq_cfg) {
+               io_ext_int_val =
+                       gpio_irq_cfg << TQMX86_REG_IO_EXT_INT_GPIO_SHIFT;
+               iowrite8(io_ext_int_val, io_base + TQMX86_REG_IO_EXT_INT);
+               readback = ioread8(io_base + TQMX86_REG_IO_EXT_INT);
+               if (readback != io_ext_int_val) {
+                       dev_warn(dev, "GPIO interrupts not supported.\n");
+                       return -EINVAL;
+               }
+
+               /* Assumes the IRQ resource is first. */
+               tqmx_gpio_resources[0].start = gpio_irq;
+       }
+
+       ocores_platfom_data.clock_khz = tqmx86_board_id_to_clk_rate(board_id);
+
+       if (i2c_det == TQMX86_REG_I2C_DETECT_SOFT) {
+               err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+                                          tqmx86_i2c_soft_dev,
+                                          ARRAY_SIZE(tqmx86_i2c_soft_dev),
+                                          NULL, 0, NULL);
+               if (err)
+                       return err;
+       }
+
+       return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
+                                   tqmx86_devs,
+                                   ARRAY_SIZE(tqmx86_devs),
+                                   NULL, 0, NULL);
+}
+
+static int tqmx86_create_platform_device(const struct dmi_system_id *id)
+{
+       struct platform_device *pdev;
+       int err;
+
+       pdev = platform_device_alloc("tqmx86", -1);
+       if (!pdev)
+               return -ENOMEM;
+
+       err = platform_device_add(pdev);
+       if (err)
+               platform_device_put(pdev);
+
+       return err;
+}
+
+static const struct dmi_system_id tqmx86_dmi_table[] __initconst = {
+       {
+               .ident = "TQMX86",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "TQ-Group"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "TQMx"),
+               },
+               .callback = tqmx86_create_platform_device,
+       },
+       {}
+};
+MODULE_DEVICE_TABLE(dmi, tqmx86_dmi_table);
+
+static struct platform_driver tqmx86_driver = {
+       .driver         = {
+               .name   = "tqmx86",
+       },
+       .probe          = tqmx86_probe,
+};
+
+static int __init tqmx86_init(void)
+{
+       if (!dmi_check_system(tqmx86_dmi_table))
+               return -ENODEV;
+
+       return platform_driver_register(&tqmx86_driver);
+}
+
+module_init(tqmx86_init);
+
+MODULE_DESCRIPTION("TQx86 PLD Core Driver");
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:tqmx86");