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