| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  * sl28cpld GPIO driver | 
 |  * | 
 |  * Copyright 2020 Michael Walle <michael@walle.cc> | 
 |  */ | 
 |  | 
 | #include <linux/device.h> | 
 | #include <linux/gpio/driver.h> | 
 | #include <linux/gpio/regmap.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/mod_devicetable.h> | 
 | #include <linux/module.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, | 
 | }; | 
 |  | 
 | 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 platform_device *pdev, | 
 | 				  unsigned int base, | 
 | 				  struct gpio_regmap_config *config) | 
 | { | 
 | 	struct regmap_irq_chip_data *irq_data; | 
 | 	struct regmap_irq_chip *irq_chip; | 
 | 	struct device *dev = &pdev->dev; | 
 | 	int irq, ret; | 
 |  | 
 | 	if (!device_property_read_bool(dev, "interrupt-controller")) | 
 | 		return 0; | 
 |  | 
 | 	irq = platform_get_irq(pdev, 0); | 
 | 	if (irq < 0) | 
 | 		return irq; | 
 |  | 
 | 	irq_chip = devm_kzalloc(dev, sizeof(*irq_chip), GFP_KERNEL); | 
 | 	if (!irq_chip) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	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; | 
 |  | 
 | 	ret = devm_regmap_add_irq_chip_fwnode(dev, dev_fwnode(dev), | 
 | 					      config->regmap, irq, | 
 | 					      IRQF_SHARED | IRQF_ONESHOT, | 
 | 					      0, irq_chip, &irq_data); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	config->irq_domain = regmap_irq_get_domain(irq_data); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int sl28cpld_gpio_probe(struct platform_device *pdev) | 
 | { | 
 | 	struct gpio_regmap_config config = {0}; | 
 | 	enum sl28cpld_gpio_type type; | 
 | 	struct regmap *regmap; | 
 | 	u32 base; | 
 | 	int ret; | 
 |  | 
 | 	if (!pdev->dev.parent) | 
 | 		return -ENODEV; | 
 |  | 
 | 	type = (uintptr_t)device_get_match_data(&pdev->dev); | 
 | 	if (!type) | 
 | 		return -ENODEV; | 
 |  | 
 | 	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); | 
 |  | 
 | 		/* This type supports interrupts */ | 
 | 		ret = sl28cpld_gpio_irq_init(pdev, base, &config); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 		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; | 
 | 	} | 
 |  | 
 | 	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 struct platform_driver sl28cpld_gpio_driver = { | 
 | 	.probe = sl28cpld_gpio_probe, | 
 | 	.driver = { | 
 | 		.name = "sl28cpld-gpio", | 
 | 		.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"); |