From 195a2c7f6cf1266e940f21144f995615aaa1b9df Mon Sep 17 00:00:00 2001
From: Nikita Shubin <nikita.shubin@maquefel.me>
Date: Sun, 14 Apr 2024 15:36:19 +0300
Subject: [PATCH] [stage 1] gpio: pcie-edu: Add irq support

Add irq processing and irq cascade capability.

Signed-off-by: Nikita Shubin <nikita.shubin@maquefel.me>
---
 drivers/gpio/gpio-pcie-edu.c | 173 +++++++++++++++++++++++++++++++++--
 1 file changed, 163 insertions(+), 10 deletions(-)

diff --git a/drivers/gpio/gpio-pcie-edu.c b/drivers/gpio/gpio-pcie-edu.c
index 8dc23e0458676..d0618e2a05a3b 100644
--- a/drivers/gpio/gpio-pcie-edu.c
+++ b/drivers/gpio/gpio-pcie-edu.c
@@ -7,6 +7,7 @@
 #include <linux/bits.h>
 #include <linux/device.h>
 #include <linux/err.h>
+#include <linux/gpio/driver.h>
 #include <linux/gpio/regmap.h>
 #include <linux/irq.h>
 #include <linux/kernel.h>
@@ -16,15 +17,137 @@
 #include <linux/spinlock.h>
 #include <linux/types.h>
 
+#define GPIO_EDU_СFG		0x0
+#define GPIO_EDU_СFG_OUTPUT	BIT(0)
+#define GPIO_EDU_ISTATUS	0x1
+#define GPIO_EDU_EOI		0x2
+#define GPIO_EDU_IEN		0x3
+#define GPIO_EDU_RISEEN		0x4
+#define GPIO_EDU_FALLEN		0x5
+
 #define GPIO_EDU_DATA		0x0
 #define GPIO_EDU_SET		0x1
 #define GPIO_EDU_CLEAR		0x2
 #define GPIO_EDU_DIROUT		0x3
 
 #define GPIO_EDU_PIN_STRIDE	0x10
+#define GPIO_EDU_PIN_CNT	8
+
+#define GPIO_EDU_MSIX_VECTORS_CNT	1
 
 struct gpio_edu {
-	struct regmap *map;
+	struct device		*dev;
+	struct regmap		*map;
+	struct irq_chip		*irq_chip;
+	struct irq_domain	*irq_domain;
+	unsigned long           ien;
+        unsigned long           fallen;
+        unsigned long           riseen;
+};
+
+static int gpio_edu_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq)
+{
+	struct gpio_edu *edu = d->host_data;
+	struct device *dev = edu->dev;
+
+	dev_dbg(dev, "irq: %u hwirq=%lu\n", irq, hwirq);
+
+	irq_set_chip_data(irq, edu);
+	irq_set_chip_and_handler(irq, edu->irq_chip, handle_bad_irq);
+	irq_set_noprobe(irq);
+
+	return 0;
+}
+
+static void gpio_edu_irq_unmap(struct irq_domain *d, unsigned int irq)
+{
+	struct gpio_edu *edu = d->host_data;
+	struct device *dev = edu->dev;
+
+	dev_dbg(dev, "irq: %u\n", irq);
+}
+
+static const struct irq_domain_ops gpio_edu_domain_ops = {
+	.map	= gpio_edu_irq_map,
+	.unmap	= gpio_edu_irq_unmap,
+	.xlate	= irq_domain_xlate_twocell,
+};
+
+static irqreturn_t gpio_edu_isr(int virq, void *data)
+{
+	struct gpio_edu *edu = data;
+	unsigned int stat;
+	int offset;
+
+	regmap_read(edu->map, GPIO_EDU_ISTATUS, &stat);
+
+	for_each_set_bit(offset, (unsigned long *)&stat, 8) {
+		generic_handle_domain_irq(edu->irq_domain, offset);
+		regmap_write(edu->map, GPIO_EDU_EOI, stat);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int gpio_edu_setup_irqs(struct pci_dev *pdev, struct gpio_edu *edu)
+{
+	int rc, irq_base;
+
+	rc = devm_request_irq(&pdev->dev, pci_irq_vector(pdev, 0),
+			      gpio_edu_isr, 0,
+			      "gpio_edu_vec_isr", edu);
+	if (rc)
+		return rc;
+
+	irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, 8, 0);
+	edu->irq_domain = irq_domain_add_linear(NULL, 8, &gpio_edu_domain_ops, edu);
+	irq_domain_associate_many(edu->irq_domain, irq_base, 0, 8);
+
+	return 0;
+}
+
+static int gpio_edu_irq_type(struct irq_data *d, unsigned int type)
+{
+	struct gpio_edu *edu = irq_data_get_irq_chip_data(d);
+	irq_hw_number_t hwirq = irqd_to_hwirq(d);
+	irq_flow_handler_t handler = handle_bad_irq;
+
+	dev_dbg(edu->dev, "type: %d\n", type);
+
+	clear_bit(hwirq, &edu->fallen);
+	clear_bit(hwirq, &edu->riseen);
+
+	switch (type) {
+	case IRQ_TYPE_EDGE_BOTH:
+		set_bit(hwirq, &edu->riseen);
+		set_bit(hwirq, &edu->fallen);
+		handler = handle_simple_irq;
+		break;
+	case IRQ_TYPE_EDGE_RISING:
+		set_bit(hwirq, &edu->riseen);
+		handler = handle_simple_irq;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		set_bit(hwirq, &edu->fallen);
+		handler = handle_simple_irq;
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+	case IRQ_TYPE_LEVEL_LOW:
+	default:
+		return -EINVAL;
+	}
+
+	regmap_write(edu->map, GPIO_EDU_RISEEN, edu->riseen);
+	regmap_write(edu->map, GPIO_EDU_FALLEN, edu->fallen);
+
+	irq_set_handler_locked(d, handler);
+
+	return 0;
+}
+
+static struct irq_chip edu_irq_chip = {
+	.name			= "edu-gpio-eic",
+	.irq_set_type		= gpio_edu_irq_type,
 };
 
 static const struct regmap_config gpio_edu_regmap_config = {
@@ -49,39 +172,69 @@ static int gpio_edu_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 	struct gpio_regmap_config gpio_config = {};
 	const char *const name = pci_name(pdev);
 	struct device *const dev = &pdev->dev;
-	void __iomem *gpio_edu_regs;
 	struct gpio_edu *gpioedu;
+	unsigned int cfg;
+	void __iomem *regs;
 	int err;
 
 	err = pcim_enable_device(pdev);
 	if (err)
 		return dev_err_probe(dev, err, "Failed to enable PCI device");
 
-	err = pcim_iomap_regions(pdev, BIT(0), name);
+	err = pcim_iomap_regions_request_all(pdev, BIT(0), name);
 	if (err)
-		return dev_err_probe(dev, err, "Unable to map PCI I/O addresses");
+		return dev_err_probe(dev, err, "Failed to request resources\n");
 
-	gpio_edu_regs = pcim_iomap_table(pdev)[0];
+	regs = pcim_iomap_table(pdev)[0];
 
 	gpioedu = devm_kzalloc(dev, sizeof(*gpioedu), GFP_KERNEL);
 	if (!gpioedu)
 		return -ENOMEM;
 
-	gpioedu->map = devm_regmap_init_mmio(dev, gpio_edu_regs, &gpio_edu_regmap_config);
+	gpioedu->dev = dev;
+	gpioedu->map = devm_regmap_init_mmio(dev, regs, &gpio_edu_regmap_config);
 	if (IS_ERR(gpioedu->map))
 		return dev_err_probe(dev, PTR_ERR(gpioedu->map),
 				     "Unable to initialize register map\n");
 
+	err = pci_alloc_irq_vectors(pdev,
+				    GPIO_EDU_MSIX_VECTORS_CNT,
+				    GPIO_EDU_MSIX_VECTORS_CNT,
+				    PCI_IRQ_MSIX);
+	if (err < 0)
+		return dev_err_probe(dev, err,
+				     "Unable to allocate irqs\n");
+
+	gpioedu->irq_chip = &edu_irq_chip;
+	err = gpio_edu_setup_irqs(pdev, gpioedu);
+	if (err)
+		return dev_err_probe(dev, err,
+				     "Failed to setup irqs");
+
+	dev_dbg(dev, "enabled: %d vectors\n", pci_msix_vec_count(pdev));
+
+	pci_set_master(pdev);
+
+	regmap_read(gpioedu->map, GPIO_EDU_СFG, &cfg);
+
 	gpio_config.parent = dev;
 	gpio_config.regmap = gpioedu->map;
 	gpio_config.ngpio  = 8;
-	gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(GPIO_EDU_DATA);
-	gpio_config.reg_set_base = GPIO_REGMAP_ADDR(GPIO_EDU_SET);
-	gpio_config.reg_clr_base = GPIO_REGMAP_ADDR(GPIO_EDU_CLEAR);
-	gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(GPIO_EDU_DIROUT);
+	if (cfg & GPIO_EDU_СFG_OUTPUT) {
+		dev_dbg(dev, "configured as output only: %x\n", cfg);
+		/* if you only have @reg_set_base set, then it is output-only */
+		gpio_config.reg_set_base = GPIO_REGMAP_ADDR(GPIO_EDU_SET);
+		gpio_config.reg_clr_base = GPIO_REGMAP_ADDR(GPIO_EDU_CLEAR);
+	} else {
+		dev_dbg(dev, "configured as input only: %x\n", cfg);
+		/* if you only have @reg_dat_base set, then it is input-only */
+		gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(GPIO_EDU_DATA);
+	}
+
 	gpio_config.reg_mask_xlate = gpio_edu_reg_mask_xlate;
 	gpio_config.ngpio_per_reg = 1;
 	gpio_config.drvdata = gpioedu->map;
+	gpio_config.irq_domain = gpioedu->irq_domain;
 
 	return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
 }
-- 
2.30.2