#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 = {
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));
}