Use of_device_get_match_data() instead of open-coding it. Signed-off-by: Thierry Reding <treding@nvidia.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
		
			
				
	
	
		
			393 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			393 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Ingenic JZ47xx GPIO driver
 | |
|  *
 | |
|  * Copyright (c) 2017 Paul Cercueil <paul@crapouillou.net>
 | |
|  *
 | |
|  * License terms: GNU General Public License (GPL) version 2
 | |
|  */
 | |
| 
 | |
| #include <linux/gpio/driver.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/of_address.h>
 | |
| #include <linux/of_device.h>
 | |
| #include <linux/of_irq.h>
 | |
| #include <linux/pinctrl/consumer.h>
 | |
| #include <linux/regmap.h>
 | |
| 
 | |
| #define GPIO_PIN	0x00
 | |
| #define GPIO_MSK	0x20
 | |
| 
 | |
| #define JZ4740_GPIO_DATA	0x10
 | |
| #define JZ4740_GPIO_SELECT	0x50
 | |
| #define JZ4740_GPIO_DIR		0x60
 | |
| #define JZ4740_GPIO_TRIG	0x70
 | |
| #define JZ4740_GPIO_FLAG	0x80
 | |
| 
 | |
| #define JZ4770_GPIO_INT		0x10
 | |
| #define JZ4770_GPIO_PAT1	0x30
 | |
| #define JZ4770_GPIO_PAT0	0x40
 | |
| #define JZ4770_GPIO_FLAG	0x50
 | |
| 
 | |
| #define REG_SET(x) ((x) + 0x4)
 | |
| #define REG_CLEAR(x) ((x) + 0x8)
 | |
| 
 | |
| enum jz_version {
 | |
| 	ID_JZ4740,
 | |
| 	ID_JZ4770,
 | |
| 	ID_JZ4780,
 | |
| };
 | |
| 
 | |
| struct ingenic_gpio_chip {
 | |
| 	struct regmap *map;
 | |
| 	struct gpio_chip gc;
 | |
| 	struct irq_chip irq_chip;
 | |
| 	unsigned int irq, reg_base;
 | |
| 	enum jz_version version;
 | |
| };
 | |
| 
 | |
| static u32 gpio_ingenic_read_reg(struct ingenic_gpio_chip *jzgc, u8 reg)
 | |
| {
 | |
| 	unsigned int val;
 | |
| 
 | |
| 	regmap_read(jzgc->map, jzgc->reg_base + reg, &val);
 | |
| 
 | |
| 	return (u32) val;
 | |
| }
 | |
| 
 | |
| static void gpio_ingenic_set_bit(struct ingenic_gpio_chip *jzgc,
 | |
| 		u8 reg, u8 offset, bool set)
 | |
| {
 | |
| 	if (set)
 | |
| 		reg = REG_SET(reg);
 | |
| 	else
 | |
| 		reg = REG_CLEAR(reg);
 | |
| 
 | |
| 	regmap_write(jzgc->map, jzgc->reg_base + reg, BIT(offset));
 | |
| }
 | |
| 
 | |
| static inline bool gpio_get_value(struct ingenic_gpio_chip *jzgc, u8 offset)
 | |
| {
 | |
| 	unsigned int val = gpio_ingenic_read_reg(jzgc, GPIO_PIN);
 | |
| 
 | |
| 	return !!(val & BIT(offset));
 | |
| }
 | |
| 
 | |
| static void gpio_set_value(struct ingenic_gpio_chip *jzgc, u8 offset, int value)
 | |
| {
 | |
| 	if (jzgc->version >= ID_JZ4770)
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4770_GPIO_PAT0, offset, !!value);
 | |
| 	else
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4740_GPIO_DATA, offset, !!value);
 | |
| }
 | |
| 
 | |
| static void irq_set_type(struct ingenic_gpio_chip *jzgc,
 | |
| 		u8 offset, unsigned int type)
 | |
| {
 | |
| 	u8 reg1, reg2;
 | |
| 
 | |
| 	if (jzgc->version >= ID_JZ4770) {
 | |
| 		reg1 = JZ4770_GPIO_PAT1;
 | |
| 		reg2 = JZ4770_GPIO_PAT0;
 | |
| 	} else {
 | |
| 		reg1 = JZ4740_GPIO_TRIG;
 | |
| 		reg2 = JZ4740_GPIO_DIR;
 | |
| 	}
 | |
| 
 | |
| 	switch (type) {
 | |
| 	case IRQ_TYPE_EDGE_RISING:
 | |
| 		gpio_ingenic_set_bit(jzgc, reg2, offset, true);
 | |
| 		gpio_ingenic_set_bit(jzgc, reg1, offset, true);
 | |
| 		break;
 | |
| 	case IRQ_TYPE_EDGE_FALLING:
 | |
| 		gpio_ingenic_set_bit(jzgc, reg2, offset, false);
 | |
| 		gpio_ingenic_set_bit(jzgc, reg1, offset, true);
 | |
| 		break;
 | |
| 	case IRQ_TYPE_LEVEL_HIGH:
 | |
| 		gpio_ingenic_set_bit(jzgc, reg2, offset, true);
 | |
| 		gpio_ingenic_set_bit(jzgc, reg1, offset, false);
 | |
| 		break;
 | |
| 	case IRQ_TYPE_LEVEL_LOW:
 | |
| 	default:
 | |
| 		gpio_ingenic_set_bit(jzgc, reg2, offset, false);
 | |
| 		gpio_ingenic_set_bit(jzgc, reg1, offset, false);
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void ingenic_gpio_irq_mask(struct irq_data *irqd)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 
 | |
| 	gpio_ingenic_set_bit(jzgc, GPIO_MSK, irqd->hwirq, true);
 | |
| }
 | |
| 
 | |
| static void ingenic_gpio_irq_unmask(struct irq_data *irqd)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 
 | |
| 	gpio_ingenic_set_bit(jzgc, GPIO_MSK, irqd->hwirq, false);
 | |
| }
 | |
| 
 | |
| static void ingenic_gpio_irq_enable(struct irq_data *irqd)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 	int irq = irqd->hwirq;
 | |
| 
 | |
| 	if (jzgc->version >= ID_JZ4770)
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4770_GPIO_INT, irq, true);
 | |
| 	else
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4740_GPIO_SELECT, irq, true);
 | |
| 
 | |
| 	ingenic_gpio_irq_unmask(irqd);
 | |
| }
 | |
| 
 | |
| static void ingenic_gpio_irq_disable(struct irq_data *irqd)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 	int irq = irqd->hwirq;
 | |
| 
 | |
| 	ingenic_gpio_irq_mask(irqd);
 | |
| 
 | |
| 	if (jzgc->version >= ID_JZ4770)
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4770_GPIO_INT, irq, false);
 | |
| 	else
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4740_GPIO_SELECT, irq, false);
 | |
| }
 | |
| 
 | |
| static void ingenic_gpio_irq_ack(struct irq_data *irqd)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 	int irq = irqd->hwirq;
 | |
| 	bool high;
 | |
| 
 | |
| 	if (irqd_get_trigger_type(irqd) == IRQ_TYPE_EDGE_BOTH) {
 | |
| 		/*
 | |
| 		 * Switch to an interrupt for the opposite edge to the one that
 | |
| 		 * triggered the interrupt being ACKed.
 | |
| 		 */
 | |
| 		high = gpio_get_value(jzgc, irq);
 | |
| 		if (high)
 | |
| 			irq_set_type(jzgc, irq, IRQ_TYPE_EDGE_FALLING);
 | |
| 		else
 | |
| 			irq_set_type(jzgc, irq, IRQ_TYPE_EDGE_RISING);
 | |
| 	}
 | |
| 
 | |
| 	if (jzgc->version >= ID_JZ4770)
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4770_GPIO_FLAG, irq, false);
 | |
| 	else
 | |
| 		gpio_ingenic_set_bit(jzgc, JZ4740_GPIO_DATA, irq, true);
 | |
| }
 | |
| 
 | |
| static int ingenic_gpio_irq_set_type(struct irq_data *irqd, unsigned int type)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 
 | |
| 	switch (type) {
 | |
| 	case IRQ_TYPE_EDGE_BOTH:
 | |
| 	case IRQ_TYPE_EDGE_RISING:
 | |
| 	case IRQ_TYPE_EDGE_FALLING:
 | |
| 		irq_set_handler_locked(irqd, handle_edge_irq);
 | |
| 		break;
 | |
| 	case IRQ_TYPE_LEVEL_HIGH:
 | |
| 	case IRQ_TYPE_LEVEL_LOW:
 | |
| 		irq_set_handler_locked(irqd, handle_level_irq);
 | |
| 		break;
 | |
| 	default:
 | |
| 		irq_set_handler_locked(irqd, handle_bad_irq);
 | |
| 	}
 | |
| 
 | |
| 	if (type == IRQ_TYPE_EDGE_BOTH) {
 | |
| 		/*
 | |
| 		 * The hardware does not support interrupts on both edges. The
 | |
| 		 * best we can do is to set up a single-edge interrupt and then
 | |
| 		 * switch to the opposing edge when ACKing the interrupt.
 | |
| 		 */
 | |
| 		bool high = gpio_get_value(jzgc, irqd->hwirq);
 | |
| 
 | |
| 		type = high ? IRQ_TYPE_EDGE_FALLING : IRQ_TYPE_EDGE_RISING;
 | |
| 	}
 | |
| 
 | |
| 	irq_set_type(jzgc, irqd->hwirq, type);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ingenic_gpio_irq_set_wake(struct irq_data *irqd, unsigned int on)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_data_get_irq_chip_data(irqd);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 
 | |
| 	return irq_set_irq_wake(jzgc->irq, on);
 | |
| }
 | |
| 
 | |
| static void ingenic_gpio_irq_handler(struct irq_desc *desc)
 | |
| {
 | |
| 	struct gpio_chip *gc = irq_desc_get_handler_data(desc);
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 	struct irq_chip *irq_chip = irq_data_get_irq_chip(&desc->irq_data);
 | |
| 	unsigned long flag, i;
 | |
| 
 | |
| 	chained_irq_enter(irq_chip, desc);
 | |
| 
 | |
| 	if (jzgc->version >= ID_JZ4770)
 | |
| 		flag = gpio_ingenic_read_reg(jzgc, JZ4770_GPIO_FLAG);
 | |
| 	else
 | |
| 		flag = gpio_ingenic_read_reg(jzgc, JZ4740_GPIO_FLAG);
 | |
| 
 | |
| 	for_each_set_bit(i, &flag, 32)
 | |
| 		generic_handle_irq(irq_linear_revmap(gc->irq.domain, i));
 | |
| 	chained_irq_exit(irq_chip, desc);
 | |
| }
 | |
| 
 | |
| static void ingenic_gpio_set(struct gpio_chip *gc,
 | |
| 		unsigned int offset, int value)
 | |
| {
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 
 | |
| 	gpio_set_value(jzgc, offset, value);
 | |
| }
 | |
| 
 | |
| static int ingenic_gpio_get(struct gpio_chip *gc, unsigned int offset)
 | |
| {
 | |
| 	struct ingenic_gpio_chip *jzgc = gpiochip_get_data(gc);
 | |
| 
 | |
| 	return (int) gpio_get_value(jzgc, offset);
 | |
| }
 | |
| 
 | |
| static int ingenic_gpio_direction_input(struct gpio_chip *gc,
 | |
| 		unsigned int offset)
 | |
| {
 | |
| 	return pinctrl_gpio_direction_input(gc->base + offset);
 | |
| }
 | |
| 
 | |
| static int ingenic_gpio_direction_output(struct gpio_chip *gc,
 | |
| 		unsigned int offset, int value)
 | |
| {
 | |
| 	ingenic_gpio_set(gc, offset, value);
 | |
| 	return pinctrl_gpio_direction_output(gc->base + offset);
 | |
| }
 | |
| 
 | |
| static const struct of_device_id ingenic_gpio_of_match[] = {
 | |
| 	{ .compatible = "ingenic,jz4740-gpio", .data = (void *)ID_JZ4740 },
 | |
| 	{ .compatible = "ingenic,jz4770-gpio", .data = (void *)ID_JZ4770 },
 | |
| 	{ .compatible = "ingenic,jz4780-gpio", .data = (void *)ID_JZ4780 },
 | |
| 	{},
 | |
| };
 | |
| MODULE_DEVICE_TABLE(of, ingenic_gpio_of_match);
 | |
| 
 | |
| static int ingenic_gpio_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct device *dev = &pdev->dev;
 | |
| 	struct ingenic_gpio_chip *jzgc;
 | |
| 	u32 bank;
 | |
| 	int err;
 | |
| 
 | |
| 	jzgc = devm_kzalloc(dev, sizeof(*jzgc), GFP_KERNEL);
 | |
| 	if (!jzgc)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	jzgc->map = dev_get_drvdata(dev->parent);
 | |
| 	if (!jzgc->map) {
 | |
| 		dev_err(dev, "Cannot get parent regmap\n");
 | |
| 		return -ENXIO;
 | |
| 	}
 | |
| 
 | |
| 	err = of_property_read_u32(dev->of_node, "reg", &bank);
 | |
| 	if (err) {
 | |
| 		dev_err(dev, "Cannot read \"reg\" property: %i\n", err);
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	jzgc->reg_base = bank * 0x100;
 | |
| 
 | |
| 	jzgc->gc.label = devm_kasprintf(dev, GFP_KERNEL, "GPIO%c", 'A' + bank);
 | |
| 	if (!jzgc->gc.label)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	/* DO NOT EXPAND THIS: FOR BACKWARD GPIO NUMBERSPACE COMPATIBIBILITY
 | |
| 	 * ONLY: WORK TO TRANSITION CONSUMERS TO USE THE GPIO DESCRIPTOR API IN
 | |
| 	 * <linux/gpio/consumer.h> INSTEAD.
 | |
| 	 */
 | |
| 	jzgc->gc.base = bank * 32;
 | |
| 
 | |
| 	jzgc->gc.ngpio = 32;
 | |
| 	jzgc->gc.parent = dev;
 | |
| 	jzgc->gc.of_node = dev->of_node;
 | |
| 	jzgc->gc.owner = THIS_MODULE;
 | |
| 	jzgc->version = (enum jz_version)of_device_get_match_data(dev);
 | |
| 
 | |
| 	jzgc->gc.set = ingenic_gpio_set;
 | |
| 	jzgc->gc.get = ingenic_gpio_get;
 | |
| 	jzgc->gc.direction_input = ingenic_gpio_direction_input;
 | |
| 	jzgc->gc.direction_output = ingenic_gpio_direction_output;
 | |
| 
 | |
| 	if (of_property_read_bool(dev->of_node, "gpio-ranges")) {
 | |
| 		jzgc->gc.request = gpiochip_generic_request;
 | |
| 		jzgc->gc.free = gpiochip_generic_free;
 | |
| 	}
 | |
| 
 | |
| 	err = devm_gpiochip_add_data(dev, &jzgc->gc, jzgc);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	jzgc->irq = irq_of_parse_and_map(dev->of_node, 0);
 | |
| 	if (!jzgc->irq)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	jzgc->irq_chip.name = jzgc->gc.label;
 | |
| 	jzgc->irq_chip.irq_enable = ingenic_gpio_irq_enable;
 | |
| 	jzgc->irq_chip.irq_disable = ingenic_gpio_irq_disable;
 | |
| 	jzgc->irq_chip.irq_unmask = ingenic_gpio_irq_unmask;
 | |
| 	jzgc->irq_chip.irq_mask = ingenic_gpio_irq_mask;
 | |
| 	jzgc->irq_chip.irq_ack = ingenic_gpio_irq_ack;
 | |
| 	jzgc->irq_chip.irq_set_type = ingenic_gpio_irq_set_type;
 | |
| 	jzgc->irq_chip.irq_set_wake = ingenic_gpio_irq_set_wake;
 | |
| 	jzgc->irq_chip.flags = IRQCHIP_MASK_ON_SUSPEND;
 | |
| 
 | |
| 	err = gpiochip_irqchip_add(&jzgc->gc, &jzgc->irq_chip, 0,
 | |
| 			handle_level_irq, IRQ_TYPE_NONE);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	gpiochip_set_chained_irqchip(&jzgc->gc, &jzgc->irq_chip,
 | |
| 			jzgc->irq, ingenic_gpio_irq_handler);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ingenic_gpio_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct platform_driver ingenic_gpio_driver = {
 | |
| 	.driver = {
 | |
| 		.name = "gpio-ingenic",
 | |
| 		.of_match_table = of_match_ptr(ingenic_gpio_of_match),
 | |
| 	},
 | |
| 	.probe = ingenic_gpio_probe,
 | |
| 	.remove = ingenic_gpio_remove,
 | |
| };
 | |
| 
 | |
| static int __init ingenic_gpio_drv_register(void)
 | |
| {
 | |
| 	return platform_driver_register(&ingenic_gpio_driver);
 | |
| }
 | |
| subsys_initcall(ingenic_gpio_drv_register);
 | |
| 
 | |
| static void __exit ingenic_gpio_drv_unregister(void)
 | |
| {
 | |
| 	platform_driver_unregister(&ingenic_gpio_driver);
 | |
| }
 | |
| module_exit(ingenic_gpio_drv_unregister);
 | |
| 
 | |
| MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>");
 | |
| MODULE_DESCRIPTION("Ingenic JZ47xx GPIO driver");
 | |
| MODULE_LICENSE("GPL");
 |