xref: /linux/drivers/gpio/gpio-tb10x.c (revision 7d099290486bd0a86d5e5ccf8e19dbdef95e5400)
145051539SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2c6ce2b6bSChristian Ruppert /* Abilis Systems MODULE DESCRIPTION
3c6ce2b6bSChristian Ruppert  *
4c6ce2b6bSChristian Ruppert  * Copyright (C) Abilis Systems 2013
5c6ce2b6bSChristian Ruppert  *
6c6ce2b6bSChristian Ruppert  * Authors: Sascha Leuenberger <sascha.leuenberger@abilis.com>
7c6ce2b6bSChristian Ruppert  *          Christian Ruppert <christian.ruppert@abilis.com>
8c6ce2b6bSChristian Ruppert  */
9c6ce2b6bSChristian Ruppert 
10c6ce2b6bSChristian Ruppert #include <linux/kernel.h>
11c6ce2b6bSChristian Ruppert #include <linux/module.h>
12c6ce2b6bSChristian Ruppert #include <linux/platform_device.h>
1332e49b9aSLinus Walleij #include <linux/gpio/driver.h>
14c6ce2b6bSChristian Ruppert #include <linux/slab.h>
15c6ce2b6bSChristian Ruppert #include <linux/irq.h>
16c6ce2b6bSChristian Ruppert #include <linux/irqdomain.h>
17c6ce2b6bSChristian Ruppert #include <linux/interrupt.h>
18c6ce2b6bSChristian Ruppert #include <linux/io.h>
19c6ce2b6bSChristian Ruppert #include <linux/of.h>
20c6ce2b6bSChristian Ruppert #include <linux/of_platform.h>
21c6ce2b6bSChristian Ruppert #include <linux/spinlock.h>
22c6ce2b6bSChristian Ruppert #include <linux/bitops.h>
23c6ce2b6bSChristian Ruppert #include <linux/pinctrl/consumer.h>
24c6ce2b6bSChristian Ruppert 
25c6ce2b6bSChristian Ruppert #define TB10X_GPIO_DIR_IN	(0x00000000)
26c6ce2b6bSChristian Ruppert #define TB10X_GPIO_DIR_OUT	(0x00000001)
27c6ce2b6bSChristian Ruppert #define OFFSET_TO_REG_DDR	(0x00)
28c6ce2b6bSChristian Ruppert #define OFFSET_TO_REG_DATA	(0x04)
29c6ce2b6bSChristian Ruppert #define OFFSET_TO_REG_INT_EN	(0x08)
30c6ce2b6bSChristian Ruppert #define OFFSET_TO_REG_CHANGE	(0x0C)
31c6ce2b6bSChristian Ruppert #define OFFSET_TO_REG_WRMASK	(0x10)
32c6ce2b6bSChristian Ruppert #define OFFSET_TO_REG_INT_TYPE	(0x14)
33c6ce2b6bSChristian Ruppert 
34c6ce2b6bSChristian Ruppert 
35c6ce2b6bSChristian Ruppert /**
36c6ce2b6bSChristian Ruppert  * @base: register base address
37c6ce2b6bSChristian Ruppert  * @domain: IRQ domain of GPIO generated interrupts managed by this controller
38c6ce2b6bSChristian Ruppert  * @irq: Interrupt line of parent interrupt controller
39c6ce2b6bSChristian Ruppert  * @gc: gpio_chip structure associated to this GPIO controller
40c6ce2b6bSChristian Ruppert  */
41c6ce2b6bSChristian Ruppert struct tb10x_gpio {
42c6ce2b6bSChristian Ruppert 	void __iomem *base;
43c6ce2b6bSChristian Ruppert 	struct irq_domain *domain;
44c6ce2b6bSChristian Ruppert 	int irq;
45c6ce2b6bSChristian Ruppert 	struct gpio_chip gc;
46c6ce2b6bSChristian Ruppert };
47c6ce2b6bSChristian Ruppert 
48c6ce2b6bSChristian Ruppert static inline u32 tb10x_reg_read(struct tb10x_gpio *gpio, unsigned int offs)
49c6ce2b6bSChristian Ruppert {
50c6ce2b6bSChristian Ruppert 	return ioread32(gpio->base + offs);
51c6ce2b6bSChristian Ruppert }
52c6ce2b6bSChristian Ruppert 
53c6ce2b6bSChristian Ruppert static inline void tb10x_reg_write(struct tb10x_gpio *gpio, unsigned int offs,
54c6ce2b6bSChristian Ruppert 				u32 val)
55c6ce2b6bSChristian Ruppert {
56c6ce2b6bSChristian Ruppert 	iowrite32(val, gpio->base + offs);
57c6ce2b6bSChristian Ruppert }
58c6ce2b6bSChristian Ruppert 
59c6ce2b6bSChristian Ruppert static inline void tb10x_set_bits(struct tb10x_gpio *gpio, unsigned int offs,
60c6ce2b6bSChristian Ruppert 				u32 mask, u32 val)
61c6ce2b6bSChristian Ruppert {
62c6ce2b6bSChristian Ruppert 	u32 r;
63c6ce2b6bSChristian Ruppert 	unsigned long flags;
64c6ce2b6bSChristian Ruppert 
653c938cc5SSchspa Shi 	raw_spin_lock_irqsave(&gpio->gc.bgpio_lock, flags);
66c6ce2b6bSChristian Ruppert 
67c6ce2b6bSChristian Ruppert 	r = tb10x_reg_read(gpio, offs);
68c6ce2b6bSChristian Ruppert 	r = (r & ~mask) | (val & mask);
69c6ce2b6bSChristian Ruppert 
70c6ce2b6bSChristian Ruppert 	tb10x_reg_write(gpio, offs, r);
71c6ce2b6bSChristian Ruppert 
723c938cc5SSchspa Shi 	raw_spin_unlock_irqrestore(&gpio->gc.bgpio_lock, flags);
73c6ce2b6bSChristian Ruppert }
74c6ce2b6bSChristian Ruppert 
75c6ce2b6bSChristian Ruppert static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
76c6ce2b6bSChristian Ruppert {
770ca8c5c4SLinus Walleij 	struct tb10x_gpio *tb10x_gpio = gpiochip_get_data(chip);
78c6ce2b6bSChristian Ruppert 
79c6ce2b6bSChristian Ruppert 	return irq_create_mapping(tb10x_gpio->domain, offset);
80c6ce2b6bSChristian Ruppert }
81c6ce2b6bSChristian Ruppert 
82c6ce2b6bSChristian Ruppert static int tb10x_gpio_irq_set_type(struct irq_data *data, unsigned int type)
83c6ce2b6bSChristian Ruppert {
84c6ce2b6bSChristian Ruppert 	if ((type & IRQF_TRIGGER_MASK) != IRQ_TYPE_EDGE_BOTH) {
85c6ce2b6bSChristian Ruppert 		pr_err("Only (both) edge triggered interrupts supported.\n");
86c6ce2b6bSChristian Ruppert 		return -EINVAL;
87c6ce2b6bSChristian Ruppert 	}
88c6ce2b6bSChristian Ruppert 
89c6ce2b6bSChristian Ruppert 	irqd_set_trigger_type(data, type);
90c6ce2b6bSChristian Ruppert 
91c6ce2b6bSChristian Ruppert 	return IRQ_SET_MASK_OK;
92c6ce2b6bSChristian Ruppert }
93c6ce2b6bSChristian Ruppert 
94c6ce2b6bSChristian Ruppert static irqreturn_t tb10x_gpio_irq_cascade(int irq, void *data)
95c6ce2b6bSChristian Ruppert {
96c6ce2b6bSChristian Ruppert 	struct tb10x_gpio *tb10x_gpio = data;
97c6ce2b6bSChristian Ruppert 	u32 r = tb10x_reg_read(tb10x_gpio, OFFSET_TO_REG_CHANGE);
98c6ce2b6bSChristian Ruppert 	u32 m = tb10x_reg_read(tb10x_gpio, OFFSET_TO_REG_INT_EN);
99c6ce2b6bSChristian Ruppert 	const unsigned long bits = r & m;
100c6ce2b6bSChristian Ruppert 	int i;
101c6ce2b6bSChristian Ruppert 
102c6ce2b6bSChristian Ruppert 	for_each_set_bit(i, &bits, 32)
103dbd1c54fSMarc Zyngier 		generic_handle_domain_irq(tb10x_gpio->domain, i);
104c6ce2b6bSChristian Ruppert 
105c6ce2b6bSChristian Ruppert 	return IRQ_HANDLED;
106c6ce2b6bSChristian Ruppert }
107c6ce2b6bSChristian Ruppert 
108c6ce2b6bSChristian Ruppert static int tb10x_gpio_probe(struct platform_device *pdev)
109c6ce2b6bSChristian Ruppert {
110c6ce2b6bSChristian Ruppert 	struct tb10x_gpio *tb10x_gpio;
111d28af35bSLinus Walleij 	struct device *dev = &pdev->dev;
112d28af35bSLinus Walleij 	struct device_node *np = dev->of_node;
113c6ce2b6bSChristian Ruppert 	int ret = -EBUSY;
114c6ce2b6bSChristian Ruppert 	u32 ngpio;
115c6ce2b6bSChristian Ruppert 
116d28af35bSLinus Walleij 	if (!np)
117c6ce2b6bSChristian Ruppert 		return -EINVAL;
118c6ce2b6bSChristian Ruppert 
119d28af35bSLinus Walleij 	if (of_property_read_u32(np, "abilis,ngpio", &ngpio))
120c6ce2b6bSChristian Ruppert 		return -EINVAL;
121c6ce2b6bSChristian Ruppert 
122d28af35bSLinus Walleij 	tb10x_gpio = devm_kzalloc(dev, sizeof(*tb10x_gpio), GFP_KERNEL);
123c6ce2b6bSChristian Ruppert 	if (tb10x_gpio == NULL)
124c6ce2b6bSChristian Ruppert 		return -ENOMEM;
125c6ce2b6bSChristian Ruppert 
1265b827ff5SEnrico Weigelt, metux IT consult 	tb10x_gpio->base = devm_platform_ioremap_resource(pdev, 0);
1279a4864c8SWei Yongjun 	if (IS_ERR(tb10x_gpio->base))
1289a4864c8SWei Yongjun 		return PTR_ERR(tb10x_gpio->base);
129c6ce2b6bSChristian Ruppert 
1307eb6ce2fSRob Herring 	tb10x_gpio->gc.label =
131d28af35bSLinus Walleij 		devm_kasprintf(dev, GFP_KERNEL, "%pOF", pdev->dev.of_node);
132a5ae5f5cSArvind Yadav 	if (!tb10x_gpio->gc.label)
133a5ae5f5cSArvind Yadav 		return -ENOMEM;
134a5ae5f5cSArvind Yadav 
1350d1e31abSLinus Walleij 	/*
1360d1e31abSLinus Walleij 	 * Initialize generic GPIO with one single register for reading and setting
1370d1e31abSLinus Walleij 	 * the lines, no special set or clear registers and a data direction register
1380d1e31abSLinus Walleij 	 * wher 1 means "output".
1390d1e31abSLinus Walleij 	 */
1400d1e31abSLinus Walleij 	ret = bgpio_init(&tb10x_gpio->gc, dev, 4,
1410d1e31abSLinus Walleij 			 tb10x_gpio->base + OFFSET_TO_REG_DATA,
1420d1e31abSLinus Walleij 			 NULL,
1430d1e31abSLinus Walleij 			 NULL,
1440d1e31abSLinus Walleij 			 tb10x_gpio->base + OFFSET_TO_REG_DDR,
1450d1e31abSLinus Walleij 			 NULL,
1460d1e31abSLinus Walleij 			 0);
1470d1e31abSLinus Walleij 	if (ret) {
1480d1e31abSLinus Walleij 		dev_err(dev, "unable to init generic GPIO\n");
1490d1e31abSLinus Walleij 		return ret;
1500d1e31abSLinus Walleij 	}
1510d1e31abSLinus Walleij 	tb10x_gpio->gc.base = -1;
1520d1e31abSLinus Walleij 	tb10x_gpio->gc.parent = dev;
153c6ce2b6bSChristian Ruppert 	tb10x_gpio->gc.owner = THIS_MODULE;
1540d1e31abSLinus Walleij 	/*
1550d1e31abSLinus Walleij 	 * ngpio is set by bgpio_init() but we override it, this .request()
1560d1e31abSLinus Walleij 	 * callback also overrides the one set up by generic GPIO.
1570d1e31abSLinus Walleij 	 */
1580d1e31abSLinus Walleij 	tb10x_gpio->gc.ngpio = ngpio;
159203f0daaSJonas Gorski 	tb10x_gpio->gc.request = gpiochip_generic_request;
160203f0daaSJonas Gorski 	tb10x_gpio->gc.free = gpiochip_generic_free;
161c6ce2b6bSChristian Ruppert 
1620d1e31abSLinus Walleij 	ret = devm_gpiochip_add_data(dev, &tb10x_gpio->gc, tb10x_gpio);
163c6ce2b6bSChristian Ruppert 	if (ret < 0) {
164d28af35bSLinus Walleij 		dev_err(dev, "Could not add gpiochip.\n");
165ad7b550aSLaxman Dewangan 		return ret;
166c6ce2b6bSChristian Ruppert 	}
167c6ce2b6bSChristian Ruppert 
168c6ce2b6bSChristian Ruppert 	platform_set_drvdata(pdev, tb10x_gpio);
169c6ce2b6bSChristian Ruppert 
1705b0ad5b2SRob Herring 	if (of_property_read_bool(np, "interrupt-controller")) {
171c6ce2b6bSChristian Ruppert 		struct irq_chip_generic *gc;
172c6ce2b6bSChristian Ruppert 
173c6ce2b6bSChristian Ruppert 		ret = platform_get_irq(pdev, 0);
17415bddb7dSStephen Boyd 		if (ret < 0)
175ad7b550aSLaxman Dewangan 			return ret;
176c6ce2b6bSChristian Ruppert 
177c6ce2b6bSChristian Ruppert 		tb10x_gpio->gc.to_irq	= tb10x_gpio_to_irq;
178c6ce2b6bSChristian Ruppert 		tb10x_gpio->irq		= ret;
179c6ce2b6bSChristian Ruppert 
180d28af35bSLinus Walleij 		ret = devm_request_irq(dev, ret, tb10x_gpio_irq_cascade,
181c6ce2b6bSChristian Ruppert 				IRQF_TRIGGER_NONE | IRQF_SHARED,
182d28af35bSLinus Walleij 				dev_name(dev), tb10x_gpio);
183c6ce2b6bSChristian Ruppert 		if (ret != 0)
184ad7b550aSLaxman Dewangan 			return ret;
185c6ce2b6bSChristian Ruppert 
186d28af35bSLinus Walleij 		tb10x_gpio->domain = irq_domain_add_linear(np,
187c6ce2b6bSChristian Ruppert 						tb10x_gpio->gc.ngpio,
188c6ce2b6bSChristian Ruppert 						&irq_generic_chip_ops, NULL);
189c6ce2b6bSChristian Ruppert 		if (!tb10x_gpio->domain) {
190ad7b550aSLaxman Dewangan 			return -ENOMEM;
191c6ce2b6bSChristian Ruppert 		}
192c6ce2b6bSChristian Ruppert 
193c6ce2b6bSChristian Ruppert 		ret = irq_alloc_domain_generic_chips(tb10x_gpio->domain,
194c6ce2b6bSChristian Ruppert 				tb10x_gpio->gc.ngpio, 1, tb10x_gpio->gc.label,
195c6ce2b6bSChristian Ruppert 				handle_edge_irq, IRQ_NOREQUEST, IRQ_NOPROBE,
196c6ce2b6bSChristian Ruppert 				IRQ_GC_INIT_MASK_CACHE);
197c6ce2b6bSChristian Ruppert 		if (ret)
198ad7b550aSLaxman Dewangan 			return ret;
199c6ce2b6bSChristian Ruppert 
200c6ce2b6bSChristian Ruppert 		gc = tb10x_gpio->domain->gc->gc[0];
201c6ce2b6bSChristian Ruppert 		gc->reg_base                         = tb10x_gpio->base;
202c6ce2b6bSChristian Ruppert 		gc->chip_types[0].type               = IRQ_TYPE_EDGE_BOTH;
203c6ce2b6bSChristian Ruppert 		gc->chip_types[0].chip.irq_ack       = irq_gc_ack_set_bit;
204c6ce2b6bSChristian Ruppert 		gc->chip_types[0].chip.irq_mask      = irq_gc_mask_clr_bit;
205c6ce2b6bSChristian Ruppert 		gc->chip_types[0].chip.irq_unmask    = irq_gc_mask_set_bit;
206c6ce2b6bSChristian Ruppert 		gc->chip_types[0].chip.irq_set_type  = tb10x_gpio_irq_set_type;
207c6ce2b6bSChristian Ruppert 		gc->chip_types[0].regs.ack           = OFFSET_TO_REG_CHANGE;
208c6ce2b6bSChristian Ruppert 		gc->chip_types[0].regs.mask          = OFFSET_TO_REG_INT_EN;
209c6ce2b6bSChristian Ruppert 	}
210c6ce2b6bSChristian Ruppert 
211c6ce2b6bSChristian Ruppert 	return 0;
212c6ce2b6bSChristian Ruppert }
213c6ce2b6bSChristian Ruppert 
214*7d099290SUwe Kleine-König static void tb10x_gpio_remove(struct platform_device *pdev)
215c6ce2b6bSChristian Ruppert {
216c6ce2b6bSChristian Ruppert 	struct tb10x_gpio *tb10x_gpio = platform_get_drvdata(pdev);
217c6ce2b6bSChristian Ruppert 
218c6ce2b6bSChristian Ruppert 	if (tb10x_gpio->gc.to_irq) {
219c6ce2b6bSChristian Ruppert 		irq_remove_generic_chip(tb10x_gpio->domain->gc->gc[0],
220c6ce2b6bSChristian Ruppert 					BIT(tb10x_gpio->gc.ngpio) - 1, 0, 0);
221c6ce2b6bSChristian Ruppert 		kfree(tb10x_gpio->domain->gc);
222c6ce2b6bSChristian Ruppert 		irq_domain_remove(tb10x_gpio->domain);
223c6ce2b6bSChristian Ruppert 	}
224c6ce2b6bSChristian Ruppert }
225c6ce2b6bSChristian Ruppert 
226c6ce2b6bSChristian Ruppert static const struct of_device_id tb10x_gpio_dt_ids[] = {
227c6ce2b6bSChristian Ruppert 	{ .compatible = "abilis,tb10x-gpio" },
228c6ce2b6bSChristian Ruppert 	{ }
229c6ce2b6bSChristian Ruppert };
230c6ce2b6bSChristian Ruppert MODULE_DEVICE_TABLE(of, tb10x_gpio_dt_ids);
231c6ce2b6bSChristian Ruppert 
232c6ce2b6bSChristian Ruppert static struct platform_driver tb10x_gpio_driver = {
233c6ce2b6bSChristian Ruppert 	.probe		= tb10x_gpio_probe,
234*7d099290SUwe Kleine-König 	.remove_new	= tb10x_gpio_remove,
235c6ce2b6bSChristian Ruppert 	.driver = {
236c6ce2b6bSChristian Ruppert 		.name	= "tb10x-gpio",
237b10b45c0SSachin Kamat 		.of_match_table = tb10x_gpio_dt_ids,
238c6ce2b6bSChristian Ruppert 	}
239c6ce2b6bSChristian Ruppert };
240c6ce2b6bSChristian Ruppert 
24185aa3915SWei Yongjun module_platform_driver(tb10x_gpio_driver);
242c6ce2b6bSChristian Ruppert MODULE_LICENSE("GPL");
243c6ce2b6bSChristian Ruppert MODULE_DESCRIPTION("tb10x gpio.");
244