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
tb10x_reg_read(struct tb10x_gpio * gpio,unsigned int offs)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
tb10x_reg_write(struct tb10x_gpio * gpio,unsigned int offs,u32 val)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
tb10x_set_bits(struct tb10x_gpio * gpio,unsigned int offs,u32 mask,u32 val)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
tb10x_gpio_to_irq(struct gpio_chip * chip,unsigned offset)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
tb10x_gpio_irq_set_type(struct irq_data * data,unsigned int type)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
tb10x_gpio_irq_cascade(int irq,void * data)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
tb10x_gpio_probe(struct platform_device * pdev)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)
198b547b5e5SChristophe JAILLET goto err_remove_domain;
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;
212b547b5e5SChristophe JAILLET
213b547b5e5SChristophe JAILLET err_remove_domain:
214b547b5e5SChristophe JAILLET irq_domain_remove(tb10x_gpio->domain);
215b547b5e5SChristophe JAILLET return ret;
216c6ce2b6bSChristian Ruppert }
217c6ce2b6bSChristian Ruppert
tb10x_gpio_remove(struct platform_device * pdev)218*7d099290SUwe Kleine-König static void tb10x_gpio_remove(struct platform_device *pdev)
219c6ce2b6bSChristian Ruppert {
220c6ce2b6bSChristian Ruppert struct tb10x_gpio *tb10x_gpio = platform_get_drvdata(pdev);
221c6ce2b6bSChristian Ruppert
222c6ce2b6bSChristian Ruppert if (tb10x_gpio->gc.to_irq) {
223c6ce2b6bSChristian Ruppert irq_remove_generic_chip(tb10x_gpio->domain->gc->gc[0],
224c6ce2b6bSChristian Ruppert BIT(tb10x_gpio->gc.ngpio) - 1, 0, 0);
225c6ce2b6bSChristian Ruppert kfree(tb10x_gpio->domain->gc);
226c6ce2b6bSChristian Ruppert irq_domain_remove(tb10x_gpio->domain);
227c6ce2b6bSChristian Ruppert }
228c6ce2b6bSChristian Ruppert }
229c6ce2b6bSChristian Ruppert
230c6ce2b6bSChristian Ruppert static const struct of_device_id tb10x_gpio_dt_ids[] = {
231c6ce2b6bSChristian Ruppert { .compatible = "abilis,tb10x-gpio" },
232c6ce2b6bSChristian Ruppert { }
233c6ce2b6bSChristian Ruppert };
234c6ce2b6bSChristian Ruppert MODULE_DEVICE_TABLE(of, tb10x_gpio_dt_ids);
235c6ce2b6bSChristian Ruppert
236c6ce2b6bSChristian Ruppert static struct platform_driver tb10x_gpio_driver = {
237c6ce2b6bSChristian Ruppert .probe = tb10x_gpio_probe,
238*7d099290SUwe Kleine-König .remove_new = tb10x_gpio_remove,
239c6ce2b6bSChristian Ruppert .driver = {
240c6ce2b6bSChristian Ruppert .name = "tb10x-gpio",
241b10b45c0SSachin Kamat .of_match_table = tb10x_gpio_dt_ids,
242c6ce2b6bSChristian Ruppert }
243c6ce2b6bSChristian Ruppert };
244c6ce2b6bSChristian Ruppert
24585aa3915SWei Yongjun module_platform_driver(tb10x_gpio_driver);
246c6ce2b6bSChristian Ruppert MODULE_LICENSE("GPL");
247c6ce2b6bSChristian Ruppert MODULE_DESCRIPTION("tb10x gpio.");
248