Skip to content
Snippets Groups Projects
gn412x-gpio.c 19.2 KiB
Newer Older
// SPDX-License-Identifier: GPL-2.0-or-later
/*
Federico Vaga's avatar
Federico Vaga committed
 * Copyright (C) 2019 CERN (www.cern.ch)
 * Author: Federico Vaga <federico.vaga@cern.ch>
 */
#include <linux/module.h>
#include <linux/gpio/driver.h>
#include <linux/debugfs.h>

#include "spec.h"
#include "gn412x.h"
#include "platform_data/gn412x-gpio.h"
/**
 * struct gn412x_gpio_dev GN412X device descriptor
 * @compl: for IRQ testing
 * @int_cfg_gpio: INT_CFG used for GPIO interrupts
 */
struct gn412x_gpio_dev {
	void __iomem *mem;
	struct gpio_chip gpiochip;

	struct completion	compl;
	struct gn412x_platform_data *pdata;

	struct dentry *dbg_dir;
#define GN412X_DBG_REG_NAME "regs"
	struct dentry *dbg_reg;
	struct debugfs_regset32 dbg_reg32;
};

static struct gn412x_platform_data gn412x_gpio_pdata_default = {
	.int_cfg = 0,
};

static inline struct gn412x_gpio_dev *to_gn412x_gpio_dev_gpio(struct gpio_chip *chip)
{
	return container_of(chip, struct gn412x_gpio_dev, gpiochip);
}
enum gn412x_gpio_versions {
	GN412X_VER = 0,
};

enum htvic_mem_resources {
	GN412X_MEM_BASE = 0,
};

#define REG32(_name, _offset) {.name = _name, .offset = _offset}
static const struct debugfs_reg32 gn412x_debugfs_reg32[] = {
	REG32("INT_CTRL", GNINT_CTRL),
	REG32("INT_STAT", GNINT_STAT),
	REG32("INT_CFG0", GNINT_CFG_0),
	REG32("INT_CFG1", GNINT_CFG_1),
	REG32("INT_CFG2", GNINT_CFG_2),
	REG32("INT_CFG3", GNINT_CFG_3),
	REG32("INT_CFG4", GNINT_CFG_4),
	REG32("INT_CFG5", GNINT_CFG_5),
	REG32("INT_CFG6", GNINT_CFG_6),
	REG32("INT_CFG7", GNINT_CFG_7),
	REG32("GPIO_BYPASS_MODE", GNGPIO_BYPASS_MODE),
	REG32("GPIO_DIRECTION_MODE", GNGPIO_DIRECTION_MODE),
	REG32("GPIO_OUTPUT_ENABLE", GNGPIO_OUTPUT_ENABLE),
	REG32("GPIO_OUTPUT_VALUE", GNGPIO_OUTPUT_VALUE),
	REG32("GPIO_INPUT_VALUE", GNGPIO_INPUT_VALUE),
	REG32("GPIO_INT_MASK", GNGPIO_INT_MASK),
	REG32("GPIO_INT_MASK_CLR", GNGPIO_INT_MASK_CLR),
	REG32("GPIO_INT_MASK_SET", GNGPIO_INT_MASK_SET),
	REG32("GPIO_INT_STATUS", GNGPIO_INT_STATUS),
	REG32("GPIO_INT_TYPE", GNGPIO_INT_TYPE),
	REG32("GPIO_INT_VALUE", GNGPIO_INT_VALUE),
	REG32("GPIO_INT_ON_ANY", GNGPIO_INT_ON_ANY),
};

static int gn412x_dbg_init(struct gn412x_gpio_dev *gn412x)
{
	struct dentry *dir;
#if KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE
	struct dentry *file;
#endif
#if KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
	struct device *dev = gn412x->gpiochip.dev;
#else
	struct device *dev = gn412x->gpiochip.parent;
#endif

	dir = debugfs_create_dir(dev_name(dev), NULL);
	if (IS_ERR_OR_NULL(dir)) {
		err = PTR_ERR(dir);
			"Cannot create debugfs directory \"%s\" (%d)\n",
		goto err_dir;
	}

	gn412x->dbg_reg32.regs = gn412x_debugfs_reg32;
	gn412x->dbg_reg32.nregs = ARRAY_SIZE(gn412x_debugfs_reg32);
	gn412x->dbg_reg32.base = gn412x->mem;
#if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE
	debugfs_create_regset32(GN412X_DBG_REG_NAME, 0200,
				       dir, &gn412x->dbg_reg32);
#else
	file = debugfs_create_regset32(GN412X_DBG_REG_NAME, 0200,
				       dir, &gn412x->dbg_reg32);
	if (IS_ERR_OR_NULL(file)) {
		err = PTR_ERR(file);
			 "Cannot create debugfs file \"%s\" (%d)\n",
			 GN412X_DBG_REG_NAME, err);
		goto err_reg32;
	}
	gn412x->dbg_reg = file;
#endif

	gn412x->dbg_dir = dir;
	return 0;

#if KERNEL_VERSION(5, 6, 0) > LINUX_VERSION_CODE
err_reg32:
	debugfs_remove_recursive(dir);
err_dir:
	return err;
}

static void gn412x_dbg_exit(struct gn412x_gpio_dev *gn412x)
{
	debugfs_remove_recursive(gn412x->dbg_dir);
}

Federico Vaga's avatar
Federico Vaga committed
static uint32_t gn412x_ioread32(struct gn412x_gpio_dev *gn412x,
				int reg)
{
	return ioread32(gn412x->mem + reg);
}

Federico Vaga's avatar
Federico Vaga committed
static void gn412x_iowrite32(struct gn412x_gpio_dev *gn412x,
			     uint32_t val, int reg)
{
	return iowrite32(val, gn412x->mem + reg);
}


static int gn412x_gpio_reg_read(struct gpio_chip *chip,
Federico Vaga's avatar
Federico Vaga committed
				int reg, unsigned int offset)
	struct gn412x_gpio_dev *gn412x = to_gn412x_gpio_dev_gpio(chip);
	return gn412x_ioread32(gn412x, reg) & BIT(offset);
}

static void gn412x_gpio_reg_write(struct gpio_chip *chip,
Federico Vaga's avatar
Federico Vaga committed
				  int reg, unsigned int offset, int value)
	struct gn412x_gpio_dev *gn412x = to_gn412x_gpio_dev_gpio(chip);
	uint32_t regval;

	regval = gn412x_ioread32(gn412x, reg);
	if (value)
		regval |= BIT(offset);
	else
		regval &= ~BIT(offset);
	gn412x_iowrite32(gn412x, regval, reg);
/**
 * Enable Internal Gennum error's interrupts
 * @gn412x gn412x device
 *
 * Return: 0 on success, otherwise a negative error number
 */
static void gn412x_gpio_int_cfg_enable_err(struct gn412x_gpio_dev *gn412x)
{
	uint32_t int_cfg;

	int_cfg = gn412x_ioread32(gn412x, GNINT_CFG(gn412x->pdata->int_cfg));
	int_cfg |= GNINT_STAT_ERR_ALL;
	gn412x_iowrite32(gn412x, int_cfg, GNINT_CFG(gn412x->pdata->int_cfg));
}

/**
 * disable Internal Gennum error's interrupts
 * @gn412x gn412x device
 *
 * Return: 0 on success, otherwise a negative error number
 */
static void gn412x_gpio_int_cfg_disable_err(struct gn412x_gpio_dev *gn412x)
{
	uint32_t int_cfg;

	int_cfg = gn412x_ioread32(gn412x, GNINT_CFG(gn412x->pdata->int_cfg));
	int_cfg &= ~GNINT_STAT_ERR_ALL;
	gn412x_iowrite32(gn412x, int_cfg, GNINT_CFG(gn412x->pdata->int_cfg));
}

/**
 * Enable GPIO interrupts
 * @gn412x gn412x device
 *
 * Return: 0 on success, otherwise a negative error number
 */
static void gn412x_gpio_int_cfg_enable_gpio(struct gn412x_gpio_dev *gn412x)
{
	uint32_t int_cfg;

	int_cfg = gn412x_ioread32(gn412x, GNINT_CFG(gn412x->pdata->int_cfg));
	int_cfg |= GNINT_STAT_GPIO;
	gn412x_iowrite32(gn412x, int_cfg, GNINT_CFG(gn412x->pdata->int_cfg));
}

/**
 * Disable GPIO interrupts from a single configuration space
 * @gn412x gn412x device
 */
static void gn412x_gpio_int_cfg_disable_gpio(struct gn412x_gpio_dev *gn412x)
{
	uint32_t int_cfg;

	int_cfg = gn412x_ioread32(gn412x, GNINT_CFG(gn412x->pdata->int_cfg));
	int_cfg &= ~GNINT_STAT_GPIO;
	gn412x_iowrite32(gn412x, int_cfg, GNINT_CFG(gn412x->pdata->int_cfg));
}

Federico Vaga's avatar
Federico Vaga committed
static int gn412x_gpio_request(struct gpio_chip *chip, unsigned int offset)
#if KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
	struct device *dev = chip->dev;
#else
	struct device *dev = chip->parent;
#endif

	val = gn412x_gpio_reg_read(chip, GNGPIO_BYPASS_MODE, offset);
	if (val) {
		dev_err(dev, "%s GPIO %d is in BYPASS mode\n",
			chip->label, offset);
		return -EBUSY;
	}

	return 0;
}

Federico Vaga's avatar
Federico Vaga committed
static void gn412x_gpio_free(struct gpio_chip *chip, unsigned int offset)
{
	/* set it as input to avoid to drive anything */
	gn412x_gpio_reg_write(chip, GNGPIO_DIRECTION_MODE, offset, 1);
}

static int gn412x_gpio_get_direction(struct gpio_chip *chip,
Federico Vaga's avatar
Federico Vaga committed
				     unsigned int offset)
	return !!gn412x_gpio_reg_read(chip, GNGPIO_DIRECTION_MODE, offset);
}

static int gn412x_gpio_direction_input(struct gpio_chip *chip,
Federico Vaga's avatar
Federico Vaga committed
				       unsigned int offset)
	gn412x_gpio_reg_write(chip, GNGPIO_DIRECTION_MODE, offset, 1);
	gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_ENABLE, offset, 0);

	return 0;
}

static int gn412x_gpio_direction_output(struct gpio_chip *chip,
Federico Vaga's avatar
Federico Vaga committed
					unsigned int offset, int value)
	gn412x_gpio_reg_write(chip, GNGPIO_DIRECTION_MODE, offset, 0);
	gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_ENABLE, offset, 1);
	gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_VALUE, offset, value);

	return 0;
}

static int gn412x_gpio_get(struct gpio_chip *chip,
Federico Vaga's avatar
Federico Vaga committed
			   unsigned int offset)
{
	return gn412x_gpio_reg_read(chip, GNGPIO_INPUT_VALUE, offset);
}

static void gn412x_gpio_set(struct gpio_chip *chip,
Federico Vaga's avatar
Federico Vaga committed
			    unsigned int offset, int value)
{
	gn412x_gpio_reg_write(chip, GNGPIO_OUTPUT_VALUE, offset, value);
}

/**
 * (disable)
 */
static void gn412x_gpio_irq_mask(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct gn412x_gpio_dev *gn412x = to_gn412x_gpio_dev_gpio(gc);

	gn412x_iowrite32(gn412x, BIT(d->hwirq), GNGPIO_INT_MASK_SET);
}


/**
 * (enable)
 */
static void gn412x_gpio_irq_unmask(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
	struct gn412x_gpio_dev *gn412x = to_gn412x_gpio_dev_gpio(gc);

	gn412x_iowrite32(gn412x, BIT(d->hwirq), GNGPIO_INT_MASK_CLR);
}


static int gn412x_gpio_irq_set_type(struct irq_data *d, unsigned int flow_type)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);

#if KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
	struct device *dev = gc->dev;
#else
	struct device *dev = gc->parent;
#endif

	/*
	 * detect errors:
	 * - level and edge together cannot work
	 */
	if ((flow_type & IRQ_TYPE_LEVEL_MASK) &&
	    (flow_type & IRQ_TYPE_EDGE_BOTH)) {
			"Impossible to set GPIO IRQ %ld to both LEVEL and EDGE (0x%x)\n",
			d->hwirq, flow_type);
		return -EINVAL;
	}

	/* Configure: level or edge (default)? */
	if (flow_type & IRQ_TYPE_LEVEL_MASK) {
		gn412x_gpio_reg_write(gc, GNGPIO_INT_TYPE, d->hwirq, 1);
	} else {
		gn412x_gpio_reg_write(gc, GNGPIO_INT_TYPE, d->hwirq, 0);

		/* if we want to trigger on any edge */
		if ((flow_type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH)
			gn412x_gpio_reg_write(gc, GNGPIO_INT_ON_ANY,
					      d->hwirq, 1);
	}


Federico Vaga's avatar
Federico Vaga committed
	/* Configure: level-low or falling-edge, level-high or raising-edge */
	if (flow_type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING))
		gn412x_gpio_reg_write(gc, GNGPIO_INT_VALUE, d->hwirq, 0);
	else
		gn412x_gpio_reg_write(gc, GNGPIO_INT_VALUE, d->hwirq, 1);

	return IRQ_SET_MASK_OK;
}


/**
 * A new IRQ interrupt has been requested
 * @d IRQ related data
 *
 * We need to set the GPIO line to be input and disable completely any
 * kind of output. We do not want any alternative function (bypass mode).
 */
static unsigned int gn412x_gpio_irq_startup(struct irq_data *d)
{
	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);

	gn412x_gpio_reg_write(gc, GNGPIO_BYPASS_MODE, d->hwirq, 0);
	gn412x_gpio_reg_write(gc, GNGPIO_DIRECTION_MODE, d->hwirq, 1);
	gn412x_gpio_reg_write(gc, GNGPIO_OUTPUT_ENABLE, d->hwirq, 0);

	gn412x_gpio_direction_input(gc, d->hwirq);

	/* FIXME in the original code we had this? What is it? */
	/* !!(gennum_readl(spec, GNGPIO_INPUT_VALUE) & bit); */

	gn412x_gpio_irq_unmask(d);

	return 0;
}


/**
 * It disables the GPIO interrupt by masking it
 */
static void gn412x_gpio_irq_disable(struct irq_data *d)
{
	gn412x_gpio_irq_mask(d);
}

static void gn412x_gpio_irq_ack(struct irq_data *d)
{
	/*
	 * based on HW design,there is no need to ack HW
	 * before handle current irq. But this routine is
	 * necessary for handle_edge_irq
}

/**
 * This will run in hard-IRQ context since we do not have much to do
 */
static irqreturn_t spec_irq_sw_handler(int irq, void *arg)
{
	struct gn412x_gpio_dev *gn412x = arg;

	/* Ack the interrupts */
	gn412x_ioread32(gn412x, GNINT_STAT);
	gn412x_iowrite32(gn412x, 0x0000, GNINT_STAT);

	complete(&gn412x->compl);

	return IRQ_HANDLED;
}

/**
 * Handle IRQ from the GPIO block
 */
static irqreturn_t gn412x_gpio_irq_handler_t(int irq, void *arg)
	struct gn412x_gpio_dev *gn412x = arg;
	struct gpio_chip *gc = &gn412x->gpiochip;
	unsigned int cascade_irq;
	uint32_t gpio_int_status, gpio_int_type;
	unsigned long loop;
	irqreturn_t ret = IRQ_NONE;
	int i;

#if KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
	struct device *dev = gc->dev;
#else
	struct device *dev = gc->parent;
#endif

	gpio_int_status = gn412x_ioread32(gn412x, GNGPIO_INT_STATUS);
	gpio_int_status &= ~gn412x_ioread32(gn412x, GNGPIO_INT_MASK);
	if (!gpio_int_status)
		goto out_enable_irq;

	loop = gpio_int_status;
	for_each_set_bit(i, &loop, GN4124_GPIO_MAX) {
#if KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE
		cascade_irq = irq_find_mapping(gc->irqdomain, i);
#else
		cascade_irq = irq_find_mapping(gc->irq.domain, i);
#endif
		dev_dbg(dev, "GPIO: %d, IRQ: %d\n", i, cascade_irq);
		/*
		 * Ok, now we execute the handler for the given IRQ. Please
Federico Vaga's avatar
Federico Vaga committed
		 * note that this is not the action requested by the device
		 * driver but it is the handler defined during the IRQ mapping
		 */
		handle_nested_irq(cascade_irq);
	}
	/*
	 * Level interrupts are cleared only when they are processed. This
	 * means that at this point (interrupts have been processed) the
	 * GPIO_INT_STATUS is still set because it is a sticky bit that got
	 * set again after our first read because we did not handle yet
	 * the IRQ. For this reason we have to clear it (defentivelly) by
	 * reading again the register ...
	 */
	gpio_int_status = gn412x_ioread32(gn412x, GNGPIO_INT_STATUS);
	/*
	 * ... but like this edge interrupts are lost. So write back
	 * in the register the status of all pending edge interrupts.
	 * (NOTE: not yet prooven that it works)
	 */
	gpio_int_type = gn412x_ioread32(gn412x, GNGPIO_INT_TYPE);
	gn412x_iowrite32(gn412x, (gpio_int_status & (~gpio_int_type)),
			 GNGPIO_INT_STATUS);
	ret = IRQ_HANDLED;

out_enable_irq:
	/* Re-enable the GPIO interrupts, we are done here */
	gn412x_gpio_int_cfg_enable_gpio(gn412x);
static bool gn412x_gpio_fpga_is_programmed(struct gn412x_gpio_dev *gn412x)
{
	bool done;

	done = gn412x_ioread32(gn412x, FCL_STATUS) & FCL_SPRI_DONE;

	return done;
}

static irqreturn_t gn412x_gpio_irq_handler_h(int irq, void *arg)
{
	struct gn412x_gpio_dev *gn412x = arg;
	uint32_t int_stat, int_cfg;

	int_cfg = gn412x_ioread32(gn412x, GNINT_CFG(gn412x->pdata->int_cfg));
	int_stat = gn412x_ioread32(gn412x, GNINT_STAT);
	if (unlikely(!(int_stat & int_cfg)))
		return IRQ_NONE;

	if (unlikely(int_stat & GNINT_STAT_SW_ALL)) /* only for testing */
		return spec_irq_sw_handler(irq, gn412x);

	if (unlikely(int_stat & GNINT_STAT_ERR_ALL)) {
		gn412x_iowrite32(gn412x, int_stat & GNINT_STAT_ERR_ALL,
						 GNINT_STAT);
		if (!gn412x_gpio_fpga_is_programmed(gn412x))
			return IRQ_NONE;  /* spurious interrupt if FPGA is not programmed */
#if KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
		dev_err(gn412x->gpiochip.dev, "Local bus error 0x%08x\n", int_stat);
#else
		dev_err(gn412x->gpiochip.parent, "Local bus error 0x%08x\n", int_stat);
#endif

	/*
	 * Do not listen to new interrupts while handling the current GPIOs.
	 * This may take a while since the chain behind each GPIO can be long.
	 * If the IRQ behind is level, we do not want this IRQ handeler to be
	 * called continuously. But on the other hand we do not want other
	 * devices sharing the same IRQ to wait for us; just to play safe,
	 * let's disable interrupts. Within the thread we will re-enable them
	 * when we are ready (like IRQF_ONESHOT).
	 *
	 * We keep the error interrupts enabled
	gn412x_gpio_int_cfg_disable_gpio(gn412x);
static void gn412x_gpio_irq_set_nested_thread(struct gn412x_gpio_dev *gn412x,
					      unsigned int gpio, bool nest)
{
	int irq;

#if KERNEL_VERSION(4, 14, 0) > LINUX_VERSION_CODE
Federico Vaga's avatar
Federico Vaga committed
	irq = irq_find_mapping(gn412x->gpiochip.irqdomain, gpio);
#else
	irq = irq_find_mapping(gn412x->gpiochip.irq.domain, gpio);
#endif
	irq_set_nested_thread(irq, nest);
}

static void gn412x_gpio_irq_set_nested_thread_all(struct gn412x_gpio_dev *gn412x,
						  bool nest)
{
	int i;

	for (i = 0; i < GN4124_GPIO_MAX; ++i)
		gn412x_gpio_irq_set_nested_thread(gn412x, i, nest);
}

static int gn412x_gpio_probe(struct platform_device *pdev)
	struct gn412x_gpio_dev *gn412x;
	struct resource *r;
	int err;
	gn412x = kzalloc(sizeof(*gn412x), GFP_KERNEL);
	if (!gn412x)
		return -ENOMEM;

	platform_set_drvdata(pdev, gn412x);

	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!r) {
		dev_err(&pdev->dev, "Missing memory resource\n");
		err = -EINVAL;
		goto err_res_mem;
	}
	gn412x->mem = ioremap(r->start, resource_size(r));
	if (!gn412x->mem) {
		err = -EADDRNOTAVAIL;
		goto err_map;
	}
	gn412x->pdata = dev_get_platdata(&pdev->dev);
	if (!gn412x->pdata) {
		dev_warn(&pdev->dev, "Missing platform data, use default\n");
		gn412x->pdata = &gn412x_gpio_pdata_default;
	}
	gn412x_iowrite32(gn412x, 0, GNGPIO_BYPASS_MODE);

	gn412x_gpio_int_cfg_disable_err(gn412x);
	gn412x_gpio_int_cfg_disable_gpio(gn412x);
	gn412x_iowrite32(gn412x, 0xFFFF, GNGPIO_INT_MASK_SET);

	gn412x->irqchip.name = "GN412X-GPIO",
	gn412x->irqchip.irq_startup = gn412x_gpio_irq_startup,
	gn412x->irqchip.irq_disable = gn412x_gpio_irq_disable,
	gn412x->irqchip.irq_mask = gn412x_gpio_irq_mask,
	gn412x->irqchip.irq_unmask = gn412x_gpio_irq_unmask,
	gn412x->irqchip.irq_set_type = gn412x_gpio_irq_set_type,
	gn412x->irqchip.irq_ack = gn412x_gpio_irq_ack,

#if KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
	gn412x->gpiochip.dev = &pdev->dev;
#else
	gn412x->gpiochip.parent = &pdev->dev;
#endif
	gn412x->gpiochip.label = "gn412x-gpio";
	gn412x->gpiochip.owner = THIS_MODULE;
	gn412x->gpiochip.request = gn412x_gpio_request;
	gn412x->gpiochip.free = gn412x_gpio_free;
	gn412x->gpiochip.get_direction = gn412x_gpio_get_direction;
	gn412x->gpiochip.direction_input = gn412x_gpio_direction_input;
	gn412x->gpiochip.direction_output = gn412x_gpio_direction_output;
	gn412x->gpiochip.get = gn412x_gpio_get;
	gn412x->gpiochip.set = gn412x_gpio_set;
	gn412x->gpiochip.base = -1;
	gn412x->gpiochip.ngpio = GN4124_GPIO_MAX;
	gn412x->gpiochip.can_sleep = 0;
#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE
	gn412x->gpiochip.irq.chip = &gn412x->irqchip;
	gn412x->gpiochip.irq.first = 0;
	gn412x->gpiochip.irq.handler = handle_simple_irq;
	gn412x->gpiochip.irq.default_type = IRQ_TYPE_NONE;
	gn412x->gpiochip.irq.threaded = true;
#endif
	err = gpiochip_add(&gn412x->gpiochip);
	if (err)
#if KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE
	err = gpiochip_irqchip_add(&gn412x->gpiochip,
				   0, handle_simple_irq,
				   IRQ_TYPE_NONE);
	if (err)
		goto err_add_irq;

	gn412x_gpio_irq_set_nested_thread_all(gn412x, true);
#if KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE
	err = request_threaded_irq(platform_get_irq(pdev, 0),
				   gn412x_gpio_irq_handler_h,
				   gn412x_gpio_irq_handler_t,
				   dev_name(gn412x->gpiochip.dev),
				   gn412x);
	if (err) {
		dev_err(gn412x->gpiochip.dev, "Can't request IRQ %d (%d)\n",
#else
	err = request_threaded_irq(platform_get_irq(pdev, 0),
				   gn412x_gpio_irq_handler_h,
				   gn412x_gpio_irq_handler_t,
				   IRQF_SHARED | IRQF_ONESHOT,
				   dev_name(gn412x->gpiochip.parent),
				   gn412x);
	if (err) {
		dev_err(gn412x->gpiochip.parent, "Can't request IRQ %d (%d)\n",
#endif
			platform_get_irq(pdev, 0), err);
	gn412x_gpio_int_cfg_enable_err(gn412x);
	gn412x_gpio_int_cfg_enable_gpio(gn412x);
	gn412x_dbg_init(gn412x);

	return 0;

err_req:
	gn412x_gpio_irq_set_nested_thread_all(gn412x, false);
#if KERNEL_VERSION(4, 15, 0) > LINUX_VERSION_CODE
err_add_irq:
	gpiochip_remove(&gn412x->gpiochip);
	iounmap(gn412x->mem);
err_map:
err_res_mem:
	kfree(gn412x);

static int gn412x_gpio_remove(struct platform_device *pdev)
	struct gn412x_gpio_dev *gn412x = platform_get_drvdata(pdev);
	gn412x_dbg_exit(gn412x);

	gn412x_gpio_int_cfg_disable_gpio(gn412x);
	gn412x_gpio_int_cfg_disable_err(gn412x);

	free_irq(platform_get_irq(pdev, 0), gn412x);
	gn412x_gpio_irq_set_nested_thread_all(gn412x, false);
	gpiochip_remove(&gn412x->gpiochip);
	iounmap(gn412x->mem);
	kfree(gn412x);
	dev_dbg(&pdev->dev, "%s\n", __func__);

static const struct platform_device_id gn412x_gpio_id[] = {
	{
		.name = "gn412x-gpio",
	},
	{}, /* last */
};

static struct platform_driver gn412x_gpio_platform_driver = {
	.driver = {
		.name = KBUILD_MODNAME,
	},
	.probe = gn412x_gpio_probe,
	.remove = gn412x_gpio_remove,
	.id_table = gn412x_gpio_id,
};

module_platform_driver(gn412x_gpio_platform_driver);

MODULE_AUTHOR("Federico Vaga <federico.vaga@cern.ch>");
MODULE_DESCRIPTION("GN412X GPIO driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(VERSION);
MODULE_DEVICE_TABLE(platform, gn412x_gpio_id);