Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mfd: tqmx86: IO controller with I2C, Wachdog and GPIO

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>

authored by

Andrew Lunn and committed by
Lee Jones
2f17dd34 16b7a09b

+290
+8
drivers/mfd/Kconfig
··· 1677 1677 help 1678 1678 Support for Toshiba Mobile IO Controller TC6393XB 1679 1679 1680 + config MFD_TQMX86 1681 + tristate "TQ-Systems IO controller TQMX86" 1682 + select MFD_CORE 1683 + help 1684 + Say yes here to enable support for various functions of the 1685 + TQ-Systems IO controller and watchdog device, found on their 1686 + ComExpress CPU modules. 1687 + 1680 1688 config MFD_VX855 1681 1689 tristate "VIA VX855/VX875 integrated south bridge" 1682 1690 depends on PCI
+1
drivers/mfd/Makefile
··· 36 36 obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o 37 37 obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o 38 38 obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o 39 + obj-$(CONFIG_MFD_TQMX86) += tqmx86.o 39 40 40 41 obj-$(CONFIG_MFD_LOCHNAGAR) += lochnagar-i2c.o 41 42
+281
drivers/mfd/tqmx86.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + /* 3 + * TQ-Systems PLD MFD core driver, based on vendor driver by 4 + * Vadim V.Vlasov <vvlasov@dev.rtsoft.ru> 5 + * 6 + * Copyright (c) 2015 TQ-Systems GmbH 7 + * Copyright (c) 2019 Andrew Lunn <andrew@lunn.ch> 8 + */ 9 + 10 + #include <linux/delay.h> 11 + #include <linux/dmi.h> 12 + #include <linux/i2c.h> 13 + #include <linux/io.h> 14 + #include <linux/mfd/core.h> 15 + #include <linux/module.h> 16 + #include <linux/platform_data/i2c-ocores.h> 17 + #include <linux/platform_device.h> 18 + 19 + #define TQMX86_IOBASE 0x160 20 + #define TQMX86_IOSIZE 0x3f 21 + #define TQMX86_IOBASE_I2C 0x1a0 22 + #define TQMX86_IOSIZE_I2C 0xa 23 + #define TQMX86_IOBASE_WATCHDOG 0x18b 24 + #define TQMX86_IOSIZE_WATCHDOG 0x2 25 + #define TQMX86_IOBASE_GPIO 0x18d 26 + #define TQMX86_IOSIZE_GPIO 0x4 27 + 28 + #define TQMX86_REG_BOARD_ID 0x20 29 + #define TQMX86_REG_BOARD_ID_E38M 1 30 + #define TQMX86_REG_BOARD_ID_50UC 2 31 + #define TQMX86_REG_BOARD_ID_E38C 3 32 + #define TQMX86_REG_BOARD_ID_60EB 4 33 + #define TQMX86_REG_BOARD_ID_E39M 5 34 + #define TQMX86_REG_BOARD_ID_E39C 6 35 + #define TQMX86_REG_BOARD_ID_E39x 7 36 + #define TQMX86_REG_BOARD_ID_70EB 8 37 + #define TQMX86_REG_BOARD_ID_80UC 9 38 + #define TQMX86_REG_BOARD_ID_90UC 10 39 + #define TQMX86_REG_BOARD_REV 0x21 40 + #define TQMX86_REG_IO_EXT_INT 0x26 41 + #define TQMX86_REG_IO_EXT_INT_NONE 0 42 + #define TQMX86_REG_IO_EXT_INT_7 1 43 + #define TQMX86_REG_IO_EXT_INT_9 2 44 + #define TQMX86_REG_IO_EXT_INT_12 3 45 + #define TQMX86_REG_IO_EXT_INT_MASK 0x3 46 + #define TQMX86_REG_IO_EXT_INT_GPIO_SHIFT 4 47 + 48 + #define TQMX86_REG_I2C_DETECT 0x47 49 + #define TQMX86_REG_I2C_DETECT_SOFT 0xa5 50 + #define TQMX86_REG_I2C_INT_EN 0x49 51 + 52 + static uint gpio_irq; 53 + module_param(gpio_irq, uint, 0); 54 + MODULE_PARM_DESC(gpio_irq, "GPIO IRQ number (7, 9, 12)"); 55 + 56 + static const struct resource tqmx_i2c_soft_resources[] = { 57 + DEFINE_RES_IO(TQMX86_IOBASE_I2C, TQMX86_IOSIZE_I2C), 58 + }; 59 + 60 + static const struct resource tqmx_watchdog_resources[] = { 61 + DEFINE_RES_IO(TQMX86_IOBASE_WATCHDOG, TQMX86_IOSIZE_WATCHDOG), 62 + }; 63 + 64 + /* 65 + * The IRQ resource must be first, since it is updated with the 66 + * configured IRQ in the probe function. 67 + */ 68 + static struct resource tqmx_gpio_resources[] = { 69 + DEFINE_RES_IRQ(0), 70 + DEFINE_RES_IO(TQMX86_IOBASE_GPIO, TQMX86_IOSIZE_GPIO), 71 + }; 72 + 73 + static struct i2c_board_info tqmx86_i2c_devices[] = { 74 + { 75 + /* 4K EEPROM at 0x50 */ 76 + I2C_BOARD_INFO("24c32", 0x50), 77 + }, 78 + }; 79 + 80 + static struct ocores_i2c_platform_data ocores_platfom_data = { 81 + .num_devices = ARRAY_SIZE(tqmx86_i2c_devices), 82 + .devices = tqmx86_i2c_devices, 83 + }; 84 + 85 + static const struct mfd_cell tqmx86_i2c_soft_dev[] = { 86 + { 87 + .name = "ocores-i2c", 88 + .platform_data = &ocores_platfom_data, 89 + .pdata_size = sizeof(ocores_platfom_data), 90 + .resources = tqmx_i2c_soft_resources, 91 + .num_resources = ARRAY_SIZE(tqmx_i2c_soft_resources), 92 + }, 93 + }; 94 + 95 + static const struct mfd_cell tqmx86_devs[] = { 96 + { 97 + .name = "tqmx86-wdt", 98 + .resources = tqmx_watchdog_resources, 99 + .num_resources = ARRAY_SIZE(tqmx_watchdog_resources), 100 + .ignore_resource_conflicts = true, 101 + }, 102 + { 103 + .name = "tqmx86-gpio", 104 + .resources = tqmx_gpio_resources, 105 + .num_resources = ARRAY_SIZE(tqmx_gpio_resources), 106 + .ignore_resource_conflicts = true, 107 + }, 108 + }; 109 + 110 + static const char *tqmx86_board_id_to_name(u8 board_id) 111 + { 112 + switch (board_id) { 113 + case TQMX86_REG_BOARD_ID_E38M: 114 + return "TQMxE38M"; 115 + case TQMX86_REG_BOARD_ID_50UC: 116 + return "TQMx50UC"; 117 + case TQMX86_REG_BOARD_ID_E38C: 118 + return "TQMxE38C"; 119 + case TQMX86_REG_BOARD_ID_60EB: 120 + return "TQMx60EB"; 121 + case TQMX86_REG_BOARD_ID_E39M: 122 + return "TQMxE39M"; 123 + case TQMX86_REG_BOARD_ID_E39C: 124 + return "TQMxE39C"; 125 + case TQMX86_REG_BOARD_ID_E39x: 126 + return "TQMxE39x"; 127 + case TQMX86_REG_BOARD_ID_70EB: 128 + return "TQMx70EB"; 129 + case TQMX86_REG_BOARD_ID_80UC: 130 + return "TQMx80UC"; 131 + case TQMX86_REG_BOARD_ID_90UC: 132 + return "TQMx90UC"; 133 + default: 134 + return "Unknown"; 135 + } 136 + } 137 + 138 + static int tqmx86_board_id_to_clk_rate(u8 board_id) 139 + { 140 + switch (board_id) { 141 + case TQMX86_REG_BOARD_ID_50UC: 142 + case TQMX86_REG_BOARD_ID_60EB: 143 + case TQMX86_REG_BOARD_ID_70EB: 144 + case TQMX86_REG_BOARD_ID_80UC: 145 + case TQMX86_REG_BOARD_ID_90UC: 146 + return 24000; 147 + case TQMX86_REG_BOARD_ID_E39M: 148 + case TQMX86_REG_BOARD_ID_E39C: 149 + case TQMX86_REG_BOARD_ID_E39x: 150 + return 25000; 151 + case TQMX86_REG_BOARD_ID_E38M: 152 + case TQMX86_REG_BOARD_ID_E38C: 153 + return 33000; 154 + default: 155 + return 0; 156 + } 157 + } 158 + 159 + static int tqmx86_probe(struct platform_device *pdev) 160 + { 161 + u8 board_id, rev, i2c_det, i2c_ien, io_ext_int_val; 162 + struct device *dev = &pdev->dev; 163 + u8 gpio_irq_cfg, readback; 164 + const char *board_name; 165 + void __iomem *io_base; 166 + int err; 167 + 168 + switch (gpio_irq) { 169 + case 0: 170 + gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_NONE; 171 + break; 172 + case 7: 173 + gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_7; 174 + break; 175 + case 9: 176 + gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_9; 177 + break; 178 + case 12: 179 + gpio_irq_cfg = TQMX86_REG_IO_EXT_INT_12; 180 + break; 181 + default: 182 + pr_err("tqmx86: Invalid GPIO IRQ (%d)\n", gpio_irq); 183 + return -EINVAL; 184 + } 185 + 186 + io_base = devm_ioport_map(dev, TQMX86_IOBASE, TQMX86_IOSIZE); 187 + if (!io_base) 188 + return -ENOMEM; 189 + 190 + board_id = ioread8(io_base + TQMX86_REG_BOARD_ID); 191 + board_name = tqmx86_board_id_to_name(board_id); 192 + rev = ioread8(io_base + TQMX86_REG_BOARD_REV); 193 + 194 + dev_info(dev, 195 + "Found %s - Board ID %d, PCB Revision %d, PLD Revision %d\n", 196 + board_name, board_id, rev >> 4, rev & 0xf); 197 + 198 + i2c_det = ioread8(io_base + TQMX86_REG_I2C_DETECT); 199 + i2c_ien = ioread8(io_base + TQMX86_REG_I2C_INT_EN); 200 + 201 + if (gpio_irq_cfg) { 202 + io_ext_int_val = 203 + gpio_irq_cfg << TQMX86_REG_IO_EXT_INT_GPIO_SHIFT; 204 + iowrite8(io_ext_int_val, io_base + TQMX86_REG_IO_EXT_INT); 205 + readback = ioread8(io_base + TQMX86_REG_IO_EXT_INT); 206 + if (readback != io_ext_int_val) { 207 + dev_warn(dev, "GPIO interrupts not supported.\n"); 208 + return -EINVAL; 209 + } 210 + 211 + /* Assumes the IRQ resource is first. */ 212 + tqmx_gpio_resources[0].start = gpio_irq; 213 + } 214 + 215 + ocores_platfom_data.clock_khz = tqmx86_board_id_to_clk_rate(board_id); 216 + 217 + if (i2c_det == TQMX86_REG_I2C_DETECT_SOFT) { 218 + err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, 219 + tqmx86_i2c_soft_dev, 220 + ARRAY_SIZE(tqmx86_i2c_soft_dev), 221 + NULL, 0, NULL); 222 + if (err) 223 + return err; 224 + } 225 + 226 + return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, 227 + tqmx86_devs, 228 + ARRAY_SIZE(tqmx86_devs), 229 + NULL, 0, NULL); 230 + } 231 + 232 + static int tqmx86_create_platform_device(const struct dmi_system_id *id) 233 + { 234 + struct platform_device *pdev; 235 + int err; 236 + 237 + pdev = platform_device_alloc("tqmx86", -1); 238 + if (!pdev) 239 + return -ENOMEM; 240 + 241 + err = platform_device_add(pdev); 242 + if (err) 243 + platform_device_put(pdev); 244 + 245 + return err; 246 + } 247 + 248 + static const struct dmi_system_id tqmx86_dmi_table[] __initconst = { 249 + { 250 + .ident = "TQMX86", 251 + .matches = { 252 + DMI_MATCH(DMI_SYS_VENDOR, "TQ-Group"), 253 + DMI_MATCH(DMI_PRODUCT_NAME, "TQMx"), 254 + }, 255 + .callback = tqmx86_create_platform_device, 256 + }, 257 + {} 258 + }; 259 + MODULE_DEVICE_TABLE(dmi, tqmx86_dmi_table); 260 + 261 + static struct platform_driver tqmx86_driver = { 262 + .driver = { 263 + .name = "tqmx86", 264 + }, 265 + .probe = tqmx86_probe, 266 + }; 267 + 268 + static int __init tqmx86_init(void) 269 + { 270 + if (!dmi_check_system(tqmx86_dmi_table)) 271 + return -ENODEV; 272 + 273 + return platform_driver_register(&tqmx86_driver); 274 + } 275 + 276 + module_init(tqmx86_init); 277 + 278 + MODULE_DESCRIPTION("TQx86 PLD Core Driver"); 279 + MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); 280 + MODULE_LICENSE("GPL"); 281 + MODULE_ALIAS("platform:tqmx86");