Messages in this thread Patch in this message | | | From | Andrew Lunn <> | Subject | [PATCH v3] mfd: tqmx86: IO controller with i2c, wachdog and gpio | Date | Sat, 12 Jan 2019 00:56:05 +0100 |
| |
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> --- v2:
Drop setting i2c bus speed, which removes the build dependencies on the i2c ocores patches. This can be added back later.
v3 --
Fix indentation of help Fix SPDX to match MODULE_LICENSE Remove warranty text Add my Copyright Sort include files Add _REG_ to register defines Add #defines for all resources Replace magic numbers with #defines Rename pld_clock to pld_clock_rate Remove wrapper around ioread8()/iowrite8() Scatter const keyword in a few places Remove enum for cells Implement lookup for board in tq_board_if Rename ioeic to io_ext_int_val to make is more readable Handle optional calls in a different way Better group code and structures Kill all the sysfs attributes Use devm_mfd_add_devices() Don't exist silently for unknown board ID
Not addressed, waiting for answers: MODULE_PARM for GPIO interrupts Not using regmap, intel IO not supported by regmap Setting GPIO irq on resource structure Global tqmx86_pdev Jumping through hoops --- drivers/mfd/Kconfig | 8 ++ drivers/mfd/Makefile | 1 + drivers/mfd/tqmx86.c | 303 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 312 insertions(+) create mode 100644 drivers/mfd/tqmx86.c
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 8c5dfdce4326..87cfb0afaf84 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1676,6 +1676,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 diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 12980a4ad460..7f4790662988 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -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_ARIZONA) += arizona-core.o obj-$(CONFIG_MFD_ARIZONA) += arizona-irq.o diff --git a/drivers/mfd/tqmx86.c b/drivers/mfd/tqmx86.c new file mode 100644 index 000000000000..144c176cf2bc --- /dev/null +++ b/drivers/mfd/tqmx86.c @@ -0,0 +1,303 @@ +// 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_REV 0x21 +#define TQMX86_REG_IO_EXT_INT 0x26 +#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 + +#define I2C_KIND_SOFT 1 /* Ocores I2C soft controller */ +#define I2C_KIND_HARD 2 /* Machxo2 I2C hard controller */ + +/** + * struct tqmx86_device_data - Internal representation of the PLD device + * @io_base: Pointer to the IO memory + * @pld_clock_rate: PLD clock frequency + * @dev: Pointer to kernel device structure + * @i2c_type: Hard of soft I2C hardware macro + */ +struct tqmx86_device_ddata { + void __iomem *io_base; + u32 pld_clock_rate; + u32 i2c_type; +}; + +/** + * struct tqmx86_platform_data - PLD hardware configuration structure + * @ioresource: IO addresses of the PLD + */ +struct tqmx86_platform_ddata { + struct resource *ioresource; +}; + +static uint gpio_irq; +module_param(gpio_irq, uint, 0); +MODULE_PARM_DESC(gpio_irq, "GPIO IRQ number (7, 9, 12)"); + +static const u8 i2c_irq_ctl[] = { + [7] = 1, + [9] = 2, + [12] = 3 +}; + +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), +}; + +static struct resource tqmx_gpio_resources[] = { + DEFINE_RES_IO(TQMX86_IOBASE_GPIO, TQMX86_IOSIZE_GPIO), + DEFINE_RES_IRQ(0) +}; + +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_ddata = { + .num_devices = ARRAY_SIZE(tqmx86_i2c_devices), + .devices = tqmx86_i2c_devices, +}; + +static const struct mfd_cell tqmx86_devs[] = { + { + .name = "ocores-i2c", + .platform_data = &ocores_platfom_ddata, + .pdata_size = sizeof(ocores_platfom_ddata), + .resources = tqmx_i2c_soft_resources, + .num_resources = ARRAY_SIZE(tqmx_i2c_soft_resources), + }, + { + .name = "tqmx86-wdt", + .resources = tqmx_watchdog_resources, + .num_resources = 1, + .ignore_resource_conflicts = 1, + }, + { + .name = "tqmx86-gpio", + .resources = tqmx_gpio_resources, + .num_resources = ARRAY_SIZE(tqmx_gpio_resources), + .ignore_resource_conflicts = 1, + }, +}; + +static const struct tq_board_info { + u8 board_id; + char *name; + u32 pld_clock_rate; +} tq_board_info[] = { + { 0, "", 0 }, + { 1, "TQMxE38M", 33000 }, + { 2, "TQMx50UC", 24000 }, + { 3, "TQMxE38C", 33000 }, + { 4, "TQMx60EB", 24000 }, + { 5, "TQMxE39M", 25000 }, + { 6, "TQMxE39C", 25000 }, + { 7, "TQMxE39x", 25000 }, + { 8, "TQMx70EB", 24000 }, + { 9, "TQMx80UC", 24000 }, + {10, "TQMx90UC", 24000 } +}; + +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; + struct tqmx86_device_ddata *pld; + struct resource *ioport; + int i; + + pld = devm_kzalloc(dev, sizeof(*pld), GFP_KERNEL); + if (!pld) + return -ENOMEM; + + ioport = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!ioport) + return -EINVAL; + + pld->io_base = devm_ioport_map(dev, ioport->start, + resource_size(ioport)); + if (!pld->io_base) + return -ENOMEM; + + platform_set_drvdata(pdev, pld); + + board_id = ioread8(pld->io_base + TQMX86_REG_BOARD_ID); + for (i = 0; i < ARRAY_SIZE(tq_board_info); i++) + if (tq_board_info[i].board_id == board_id) + break; + + if (i == ARRAY_SIZE(tq_board_info)) { + dev_info(&pdev->dev, + "Board ID %d not supported by this driver\n", + board_id); + return -ENODEV; + } + + rev = ioread8(pld->io_base + TQMX86_REG_BOARD_REV); + + dev_info(&pdev->dev, + "Found %s - Board ID %d, PCB Revision %d, PLD Revision %d\n", + tq_board_info[i].name, board_id, rev >> 4, rev & 0xf); + + pld->pld_clock_rate = tq_board_info[i].pld_clock_rate; + + i2c_det = ioread8(pld->io_base + TQMX86_REG_I2C_DETECT); + i2c_ien = ioread8(pld->io_base + TQMX86_REG_I2C_INT_EN); + + if (i2c_det == TQMX86_REG_I2C_DETECT_SOFT) + pld->i2c_type = I2C_KIND_SOFT; + else + pld->i2c_type = I2C_KIND_HARD; + + io_ext_int_val = + (i2c_irq_ctl[gpio_irq] & TQMX86_REG_IO_EXT_INT_MASK) << + TQMX86_REG_IO_EXT_INT_GPIO_SHIFT; + + if (io_ext_int_val) { + iowrite8(io_ext_int_val, pld->io_base + TQMX86_REG_IO_EXT_INT); + if (ioread8(pld->io_base + TQMX86_REG_IO_EXT_INT) != + io_ext_int_val) { + dev_warn(&pdev->dev, + "gpio interrupts not supported.\n"); + gpio_irq = 0; + } + } + + ocores_platfom_ddata.clock_khz = pld->pld_clock_rate; + tqmx_gpio_resources[1].start = gpio_irq; + + if (pld->i2c_type == I2C_KIND_SOFT) + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + tqmx86_devs, + ARRAY_SIZE(tqmx86_devs), + NULL, 0, NULL); + + /* Skip the soft I2C cell */ + return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, + &tqmx86_devs[1], + ARRAY_SIZE(tqmx86_devs) - 1, + NULL, 0, NULL); +} + +static struct resource tqmx86_ioresource[] = { + DEFINE_RES_IO(TQMX86_IOBASE, TQMX86_IOSIZE), +}; + +static const struct tqmx86_platform_ddata tqmx86_platform_ddata_generic = { + .ioresource = &tqmx86_ioresource[0], +}; + +static struct platform_device *tqmx86_pdev; + +static int tqmx86_create_platform_device(const struct dmi_system_id *id) +{ + struct tqmx86_platform_ddata *pdata = id->driver_data; + int ret; + + tqmx86_pdev = platform_device_alloc("tqmx86", -1); + if (!tqmx86_pdev) + return -ENOMEM; + + ret = platform_device_add_data(tqmx86_pdev, pdata, sizeof(*pdata)); + if (ret) + goto err; + + ret = platform_device_add_resources(tqmx86_pdev, pdata->ioresource, 1); + if (ret) + goto err; + + ret = platform_device_add(tqmx86_pdev); + if (ret) + goto err; + + return 0; +err: + platform_device_put(tqmx86_pdev); + return ret; +} + +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"), + }, + .driver_data = (void *)&tqmx86_platform_ddata_generic, + .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 (gpio_irq != 0 && gpio_irq != 7 && + gpio_irq != 9 && gpio_irq != 12) { + pr_warn("tqmx86: Invalid GPIO IRQ (%d)\n", gpio_irq); + gpio_irq = 0; + } + + if (!dmi_check_system(tqmx86_dmi_table)) + return -ENODEV; + + return platform_driver_register(&tqmx86_driver); +} + +static void __exit tqmx86_exit(void) +{ + if (tqmx86_pdev) + platform_device_unregister(tqmx86_pdev); + + platform_driver_unregister(&tqmx86_driver); +} + +module_init(tqmx86_init); +module_exit(tqmx86_exit); + +MODULE_DESCRIPTION("TQx86 PLD Core Driver"); +MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:tqmx86"); -- 2.20.1
| |