Skip to content
Snippets Groups Projects
gn412x-gpio.c 15.8 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, *file;
	int err;

	dir = debugfs_create_dir(dev_name(gn412x->gpiochip.dev), NULL);
	if (IS_ERR_OR_NULL(dir)) {
		err = PTR_ERR(dir);
		dev_warn(gn412x->gpiochip.dev,
			"Cannot create debugfs directory \"%s\" (%d)\n",
			dev_name(gn412x->gpiochip.dev), err);
		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;
	file = debugfs_create_regset32(GN412X_DBG_REG_NAME, 0200,
				       dir, &gn412x->dbg_reg32);
	if (IS_ERR_OR_NULL(file)) {
		err = PTR_ERR(file);
		dev_warn(gn412x->gpiochip.dev,
			 "Cannot create debugfs file \"%s\" (%d)\n",
			 GN412X_DBG_REG_NAME, err);
		goto err_reg32;
	}

	gn412x->dbg_dir = dir;
	gn412x->dbg_reg = file;
	return 0;

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 GPIO interrupts
 * @gn412x gn412x device
 *
 * Return: 0 on success, otherwise a negative error number
 */
static void gn412x_gpio_int_cfg_enable(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
 *
 * Return: 0 on success, otherwise a negative error number
 */
static void gn412x_gpio_int_cfg_disable(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)
{
	int val;

	val = gn412x_gpio_reg_read(chip, GNGPIO_BYPASS_MODE, offset);
	if (val) {
		dev_err(chip->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);

	/*
	 * detect errors:
	 * - level and edge together cannot work
	 */
	if ((flow_type & IRQ_TYPE_LEVEL_MASK) &&
	    (flow_type & IRQ_TYPE_EDGE_BOTH)) {
		dev_err(gc->dev,
			"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
}

static struct irq_chip gn412x_gpio_irq_chip = {
	.name = "GN412X-GPIO",
	.irq_startup = gn412x_gpio_irq_startup,
	.irq_disable = gn412x_gpio_irq_disable,
	.irq_mask = gn412x_gpio_irq_mask,
	.irq_unmask = gn412x_gpio_irq_unmask,
	.irq_set_type = gn412x_gpio_irq_set_type,
	.irq_ack = gn412x_gpio_irq_ack,
};

/**
 * 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;

	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) {
		cascade_irq = irq_find_mapping(gc->irqdomain, i);
		dev_dbg(gc->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(gn412x);

	return ret;
}


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);

	/*
	 * 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).
	 */
	gn412x_gpio_int_cfg_disable(gn412x);
static void gn412x_gpio_irq_set_nested_thread(struct gn412x_gpio_dev *gn412x,
					      unsigned int gpio, bool nest)
{
	int irq;

Federico Vaga's avatar
Federico Vaga committed
	irq = irq_find_mapping(gn412x->gpiochip.irqdomain, gpio);
	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 = devm_kzalloc(&pdev->dev, 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 = devm_ioremap(&pdev->dev, 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->gpiochip.dev = &pdev->dev;
	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;

	err = gpiochip_add(&gn412x->gpiochip);
	if (err)
	gn412x_gpio_int_cfg_disable(gn412x);
	gn412x_iowrite32(gn412x, 0xFFFF, GNGPIO_INT_MASK_SET);
	err = gpiochip_irqchip_add(&gn412x->gpiochip,
				   &gn412x_gpio_irq_chip,
				   0, handle_simple_irq,
				   IRQ_TYPE_NONE);
	if (err)
		goto err_add_irq;

	gn412x_gpio_irq_set_nested_thread_all(gn412x, true);

	err = request_threaded_irq(platform_get_irq(pdev, 0),
				   gn412x_gpio_irq_handler_h,
				   gn412x_gpio_irq_handler_t,
				   IRQF_SHARED,
				   dev_name(gn412x->gpiochip.dev),
				   gn412x);
	if (err) {
		dev_err(gn412x->gpiochip.dev, "Can't request IRQ %d (%d)\n",
			platform_get_irq(pdev, 0), err);
	gn412x_gpio_int_cfg_enable(gn412x);

	gn412x_dbg_init(gn412x);

	return 0;

err_req:
	gn412x_gpio_irq_set_nested_thread_all(gn412x, false);
err_add_irq:
	gpiochip_remove(&gn412x->gpiochip);
err_add:
	devm_iounmap(&pdev->dev, gn412x->mem);
err_map:
err_res_mem:
	devm_kfree(&pdev->dev, 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(gn412x);
	free_irq(platform_get_irq(pdev, 0), gn412x);
	gn412x_gpio_irq_set_nested_thread_all(gn412x, false);
	gpiochip_remove(&gn412x->gpiochip);
	dev_dbg(&pdev->dev, "%s\n", __func__);

static const struct platform_device_id gn412x_gpio_id[] = {
	{
		.name = "gn412x-gpio",
	},
	{ .name = "" }, /* 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);