@@ -1215,6 +1215,17 @@ config GPIO_RC5T583
This driver provides the support for driving/reading the gpio pins
of RC5T583 device through standard gpio library.
+config GPIO_SL28CPLD
+ tristate "Kontron sl28 GPIO"
+ depends on MFD_SL28CPLD
+ select GPIO_REGMAP
+ select GPIOLIB_IRQCHIP
+ help
+ This enables support for the GPIOs found on the Kontron sl28 CPLD.
+
+ This driver can also be built as a module. If so, the module will be
+ called gpio-sl28cpld.
+
config GPIO_STMPE
bool "STMPE GPIOs"
depends on MFD_STMPE
@@ -130,6 +130,7 @@ obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o
obj-$(CONFIG_GPIO_SCH) += gpio-sch.o
obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o
obj-$(CONFIG_GPIO_SIOX) += gpio-siox.o
+obj-$(CONFIG_GPIO_SL28CPLD) += gpio-sl28cpld.o
obj-$(CONFIG_GPIO_SODAVILLE) += gpio-sodaville.o
obj-$(CONFIG_GPIO_SPEAR_SPICS) += gpio-spear-spics.o
obj-$(CONFIG_GPIO_SPRD) += gpio-sprd.o
new file mode 100644
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * sl28cpld GPIO driver.
+ *
+ * Copyright 2019 Michael Walle <michael@walle.cc>
+ */
+
+#include <linux/device.h>
+#include <linux/gpio/regmap.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* GPIO flavor */
+#define GPIO_REG_DIR 0x00
+#define GPIO_REG_OUT 0x01
+#define GPIO_REG_IN 0x02
+#define GPIO_REG_IE 0x03
+#define GPIO_REG_IP 0x04
+
+/* input-only flavor */
+#define GPI_REG_IN 0x00
+
+/* output-only flavor */
+#define GPO_REG_OUT 0x00
+
+enum sl28cpld_gpio_type {
+ SL28CPLD_GPIO = 1,
+ SL28CPLD_GPI,
+ SL28CPLD_GPO,
+};
+
+struct sl28cpld_gpio {
+ struct regmap_irq_chip irq_chip;
+ struct regmap_irq_chip_data *irq_data;
+};
+
+static const struct regmap_irq sl28cpld_gpio_irqs[] = {
+ REGMAP_IRQ_REG_LINE(0, 8),
+ REGMAP_IRQ_REG_LINE(1, 8),
+ REGMAP_IRQ_REG_LINE(2, 8),
+ REGMAP_IRQ_REG_LINE(3, 8),
+ REGMAP_IRQ_REG_LINE(4, 8),
+ REGMAP_IRQ_REG_LINE(5, 8),
+ REGMAP_IRQ_REG_LINE(6, 8),
+ REGMAP_IRQ_REG_LINE(7, 8),
+};
+
+static int sl28cpld_gpio_irq_init(struct device *dev,
+ struct sl28cpld_gpio *gpio,
+ struct regmap *regmap, unsigned int base,
+ int irq)
+{
+ struct regmap_irq_chip *irq_chip = &gpio->irq_chip;
+
+ irq_chip->name = "sl28cpld-gpio-irq",
+ irq_chip->irqs = sl28cpld_gpio_irqs;
+ irq_chip->num_irqs = ARRAY_SIZE(sl28cpld_gpio_irqs);
+ irq_chip->num_regs = 1;
+ irq_chip->status_base = base + GPIO_REG_IP;
+ irq_chip->mask_base = base + GPIO_REG_IE;
+ irq_chip->mask_invert = true,
+ irq_chip->ack_base = base + GPIO_REG_IP;
+
+ return devm_regmap_add_irq_chip_np(dev, dev_of_node(dev), regmap,
+ irq, IRQF_SHARED | IRQF_ONESHOT, 0,
+ irq_chip, &gpio->irq_data);
+}
+
+static int sl28cpld_gpio_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *dev_id;
+ struct gpio_regmap_config config = {0};
+ enum sl28cpld_gpio_type type;
+ struct sl28cpld_gpio *gpio;
+ bool irq_support = false;
+ struct regmap *regmap;
+ int irq, ret;
+ u32 base;
+
+ if (!pdev->dev.parent)
+ return -ENODEV;
+
+ dev_id = platform_get_device_id(pdev);
+ if (dev_id)
+ type = dev_id->driver_data;
+ else
+ type = (uintptr_t)of_device_get_match_data(&pdev->dev);
+ if (!type)
+ return -ENODEV;
+
+ gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
+ if (!gpio)
+ return -ENOMEM;
+
+ ret = device_property_read_u32(&pdev->dev, "reg", &base);
+ if (ret)
+ return -EINVAL;
+
+ regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ config.regmap = regmap;
+ config.parent = &pdev->dev;
+ config.ngpio = 8;
+
+ switch (type) {
+ case SL28CPLD_GPIO:
+ config.reg_dat_base = base + GPIO_REG_IN;
+ config.reg_set_base = base + GPIO_REG_OUT;
+ /* reg_dir_out_base might be zero */
+ config.reg_dir_out_base = GPIO_REGMAP_ADDR(base +
+ GPIO_REG_DIR);
+ irq_support = true;
+ break;
+ case SL28CPLD_GPO:
+ config.reg_set_base = base + GPO_REG_OUT;
+ break;
+ case SL28CPLD_GPI:
+ config.reg_dat_base = base + GPI_REG_IN;
+ break;
+ default:
+ dev_err(&pdev->dev, "unknown type %d\n", type);
+ return -ENODEV;
+ }
+
+ if (irq_support &&
+ device_property_read_bool(&pdev->dev, "interrupt-controller")) {
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = sl28cpld_gpio_irq_init(&pdev->dev, gpio, regmap,
+ base, irq);
+ if (ret)
+ return ret;
+
+ config.irq_domain = regmap_irq_get_domain(gpio->irq_data);
+ }
+
+ return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(&pdev->dev, &config));
+}
+
+static const struct of_device_id sl28cpld_gpio_of_match[] = {
+ { .compatible = "kontron,sl28cpld-gpio",
+ .data = (void *)SL28CPLD_GPIO },
+ { .compatible = "kontron,sl28cpld-gpi",
+ .data = (void *)SL28CPLD_GPI },
+ { .compatible = "kontron,sl28cpld-gpo",
+ .data = (void *)SL28CPLD_GPO },
+ {},
+};
+MODULE_DEVICE_TABLE(of, sl28cpld_gpio_of_match);
+
+static const struct platform_device_id sl28cpld_gpio_id_table[] = {
+ { "sl28cpld-gpio", SL28CPLD_GPIO },
+ { "sl28cpld-gpi", SL28CPLD_GPI },
+ { "sl28cpld-gpo", SL28CPLD_GPO },
+ {}
+};
+MODULE_DEVICE_TABLE(platform, sl28cpld_gpio_id_table);
+
+static struct platform_driver sl28cpld_gpio_driver = {
+ .probe = sl28cpld_gpio_probe,
+ .id_table = sl28cpld_gpio_id_table,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = sl28cpld_gpio_of_match,
+ },
+};
+module_platform_driver(sl28cpld_gpio_driver);
+
+MODULE_DESCRIPTION("sl28cpld GPIO Driver");
+MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
+MODULE_LICENSE("GPL");
Add support for the GPIO controller of the sl28 board management controller. This driver is part of a multi-function device. A controller has 8 lines. There are three different flavors: full-featured GPIO with interrupt support, input-only and output-only. Signed-off-by: Michael Walle <michael@walle.cc> --- drivers/gpio/Kconfig | 11 +++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-sl28cpld.c | 180 +++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 drivers/gpio/gpio-sl28cpld.c