[stage 1] gpio: pcie-edu: Add irq support
authorNikita Shubin <nikita.shubin@maquefel.me>
Sun, 14 Apr 2024 12:36:19 +0000 (15:36 +0300)
committerNikita Shubin <nikita.shubin@maquefel.me>
Fri, 26 Apr 2024 09:32:11 +0000 (12:32 +0300)
Add irq processing and irq cascade capability.

Signed-off-by: Nikita Shubin <nikita.shubin@maquefel.me>
drivers/gpio/gpio-pcie-edu.c

index 8dc23e0458676acc110ab3c1bea89e1b0cec94f8..d0618e2a05a3b6c1049fb56bf42e6adcb61969ef 100644 (file)
@@ -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>
 #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));
 }