diff options
Diffstat (limited to 'drivers/gpio/gpio-tb10x.c')
-rw-r--r-- | drivers/gpio/gpio-tb10x.c | 328 |
1 files changed, 328 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-tb10x.c b/drivers/gpio/gpio-tb10x.c new file mode 100644 index 000000000000..0502b9a041a5 --- /dev/null +++ b/drivers/gpio/gpio-tb10x.c | |||
@@ -0,0 +1,328 @@ | |||
1 | /* Abilis Systems MODULE DESCRIPTION | ||
2 | * | ||
3 | * Copyright (C) Abilis Systems 2013 | ||
4 | * | ||
5 | * Authors: Sascha Leuenberger <sascha.leuenberger@abilis.com> | ||
6 | * Christian Ruppert <christian.ruppert@abilis.com> | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify | ||
9 | * it under the terms of the GNU General Public License version 2 as | ||
10 | * published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
20 | */ | ||
21 | |||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/module.h> | ||
24 | #include <linux/platform_device.h> | ||
25 | #include <linux/gpio.h> | ||
26 | #include <linux/slab.h> | ||
27 | #include <linux/irq.h> | ||
28 | #include <linux/irqdomain.h> | ||
29 | #include <linux/interrupt.h> | ||
30 | #include <linux/io.h> | ||
31 | #include <linux/of.h> | ||
32 | #include <linux/of_platform.h> | ||
33 | #include <linux/of_gpio.h> | ||
34 | #include <linux/spinlock.h> | ||
35 | #include <linux/bitops.h> | ||
36 | #include <linux/pinctrl/consumer.h> | ||
37 | |||
38 | #define TB10X_GPIO_DIR_IN (0x00000000) | ||
39 | #define TB10X_GPIO_DIR_OUT (0x00000001) | ||
40 | #define OFFSET_TO_REG_DDR (0x00) | ||
41 | #define OFFSET_TO_REG_DATA (0x04) | ||
42 | #define OFFSET_TO_REG_INT_EN (0x08) | ||
43 | #define OFFSET_TO_REG_CHANGE (0x0C) | ||
44 | #define OFFSET_TO_REG_WRMASK (0x10) | ||
45 | #define OFFSET_TO_REG_INT_TYPE (0x14) | ||
46 | |||
47 | |||
48 | /** | ||
49 | * @spinlock: used for atomic read/modify/write of registers | ||
50 | * @base: register base address | ||
51 | * @domain: IRQ domain of GPIO generated interrupts managed by this controller | ||
52 | * @irq: Interrupt line of parent interrupt controller | ||
53 | * @gc: gpio_chip structure associated to this GPIO controller | ||
54 | */ | ||
55 | struct tb10x_gpio { | ||
56 | spinlock_t spinlock; | ||
57 | void __iomem *base; | ||
58 | struct irq_domain *domain; | ||
59 | int irq; | ||
60 | struct gpio_chip gc; | ||
61 | }; | ||
62 | |||
63 | static inline u32 tb10x_reg_read(struct tb10x_gpio *gpio, unsigned int offs) | ||
64 | { | ||
65 | return ioread32(gpio->base + offs); | ||
66 | } | ||
67 | |||
68 | static inline void tb10x_reg_write(struct tb10x_gpio *gpio, unsigned int offs, | ||
69 | u32 val) | ||
70 | { | ||
71 | iowrite32(val, gpio->base + offs); | ||
72 | } | ||
73 | |||
74 | static inline void tb10x_set_bits(struct tb10x_gpio *gpio, unsigned int offs, | ||
75 | u32 mask, u32 val) | ||
76 | { | ||
77 | u32 r; | ||
78 | unsigned long flags; | ||
79 | |||
80 | spin_lock_irqsave(&gpio->spinlock, flags); | ||
81 | |||
82 | r = tb10x_reg_read(gpio, offs); | ||
83 | r = (r & ~mask) | (val & mask); | ||
84 | |||
85 | tb10x_reg_write(gpio, offs, r); | ||
86 | |||
87 | spin_unlock_irqrestore(&gpio->spinlock, flags); | ||
88 | } | ||
89 | |||
90 | static inline struct tb10x_gpio *to_tb10x_gpio(struct gpio_chip *chip) | ||
91 | { | ||
92 | return container_of(chip, struct tb10x_gpio, gc); | ||
93 | } | ||
94 | |||
95 | static int tb10x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) | ||
96 | { | ||
97 | struct tb10x_gpio *tb10x_gpio = to_tb10x_gpio(chip); | ||
98 | int mask = BIT(offset); | ||
99 | int val = TB10X_GPIO_DIR_IN << offset; | ||
100 | |||
101 | tb10x_set_bits(tb10x_gpio, OFFSET_TO_REG_DDR, mask, val); | ||
102 | |||
103 | return 0; | ||
104 | } | ||
105 | |||
106 | static int tb10x_gpio_get(struct gpio_chip *chip, unsigned offset) | ||
107 | { | ||
108 | struct tb10x_gpio *tb10x_gpio = to_tb10x_gpio(chip); | ||
109 | int val; | ||
110 | |||
111 | val = tb10x_reg_read(tb10x_gpio, OFFSET_TO_REG_DATA); | ||
112 | |||
113 | if (val & BIT(offset)) | ||
114 | return 1; | ||
115 | else | ||
116 | return 0; | ||
117 | } | ||
118 | |||
119 | static void tb10x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) | ||
120 | { | ||
121 | struct tb10x_gpio *tb10x_gpio = to_tb10x_gpio(chip); | ||
122 | int mask = BIT(offset); | ||
123 | int val = value << offset; | ||
124 | |||
125 | tb10x_set_bits(tb10x_gpio, OFFSET_TO_REG_DATA, mask, val); | ||
126 | } | ||
127 | |||
128 | static int tb10x_gpio_direction_out(struct gpio_chip *chip, | ||
129 | unsigned offset, int value) | ||
130 | { | ||
131 | struct tb10x_gpio *tb10x_gpio = to_tb10x_gpio(chip); | ||
132 | int mask = BIT(offset); | ||
133 | int val = TB10X_GPIO_DIR_OUT << offset; | ||
134 | |||
135 | tb10x_set_bits(tb10x_gpio, OFFSET_TO_REG_DDR, mask, val); | ||
136 | |||
137 | return 0; | ||
138 | } | ||
139 | |||
140 | static int tb10x_gpio_request(struct gpio_chip *chip, unsigned offset) | ||
141 | { | ||
142 | return pinctrl_request_gpio(chip->base + offset); | ||
143 | } | ||
144 | |||
145 | static void tb10x_gpio_free(struct gpio_chip *chip, unsigned offset) | ||
146 | { | ||
147 | pinctrl_free_gpio(chip->base + offset); | ||
148 | } | ||
149 | |||
150 | static int tb10x_gpio_to_irq(struct gpio_chip *chip, unsigned offset) | ||
151 | { | ||
152 | struct tb10x_gpio *tb10x_gpio = to_tb10x_gpio(chip); | ||
153 | |||
154 | return irq_create_mapping(tb10x_gpio->domain, offset); | ||
155 | } | ||
156 | |||
157 | static int tb10x_gpio_irq_set_type(struct irq_data *data, unsigned int type) | ||
158 | { | ||
159 | if ((type & IRQF_TRIGGER_MASK) != IRQ_TYPE_EDGE_BOTH) { | ||
160 | pr_err("Only (both) edge triggered interrupts supported.\n"); | ||
161 | return -EINVAL; | ||
162 | } | ||
163 | |||
164 | irqd_set_trigger_type(data, type); | ||
165 | |||
166 | return IRQ_SET_MASK_OK; | ||
167 | } | ||
168 | |||
169 | static irqreturn_t tb10x_gpio_irq_cascade(int irq, void *data) | ||
170 | { | ||
171 | struct tb10x_gpio *tb10x_gpio = data; | ||
172 | u32 r = tb10x_reg_read(tb10x_gpio, OFFSET_TO_REG_CHANGE); | ||
173 | u32 m = tb10x_reg_read(tb10x_gpio, OFFSET_TO_REG_INT_EN); | ||
174 | const unsigned long bits = r & m; | ||
175 | int i; | ||
176 | |||
177 | for_each_set_bit(i, &bits, 32) | ||
178 | generic_handle_irq(irq_find_mapping(tb10x_gpio->domain, i)); | ||
179 | |||
180 | return IRQ_HANDLED; | ||
181 | } | ||
182 | |||
183 | static int tb10x_gpio_probe(struct platform_device *pdev) | ||
184 | { | ||
185 | struct tb10x_gpio *tb10x_gpio; | ||
186 | struct resource *mem; | ||
187 | struct device_node *dn = pdev->dev.of_node; | ||
188 | int ret = -EBUSY; | ||
189 | u32 ngpio; | ||
190 | |||
191 | if (!dn) | ||
192 | return -EINVAL; | ||
193 | |||
194 | if (of_property_read_u32(dn, "abilis,ngpio", &ngpio)) | ||
195 | return -EINVAL; | ||
196 | |||
197 | mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
198 | if (!mem) { | ||
199 | dev_err(&pdev->dev, "No memory resource defined.\n"); | ||
200 | return -EINVAL; | ||
201 | } | ||
202 | |||
203 | tb10x_gpio = devm_kzalloc(&pdev->dev, sizeof(*tb10x_gpio), GFP_KERNEL); | ||
204 | if (tb10x_gpio == NULL) | ||
205 | return -ENOMEM; | ||
206 | |||
207 | spin_lock_init(&tb10x_gpio->spinlock); | ||
208 | |||
209 | tb10x_gpio->base = devm_ioremap_resource(&pdev->dev, mem); | ||
210 | if (IS_ERR(tb10x_gpio->base)) | ||
211 | return PTR_ERR(tb10x_gpio->base); | ||
212 | |||
213 | tb10x_gpio->gc.label = of_node_full_name(dn); | ||
214 | tb10x_gpio->gc.dev = &pdev->dev; | ||
215 | tb10x_gpio->gc.owner = THIS_MODULE; | ||
216 | tb10x_gpio->gc.direction_input = tb10x_gpio_direction_in; | ||
217 | tb10x_gpio->gc.get = tb10x_gpio_get; | ||
218 | tb10x_gpio->gc.direction_output = tb10x_gpio_direction_out; | ||
219 | tb10x_gpio->gc.set = tb10x_gpio_set; | ||
220 | tb10x_gpio->gc.request = tb10x_gpio_request; | ||
221 | tb10x_gpio->gc.free = tb10x_gpio_free; | ||
222 | tb10x_gpio->gc.base = -1; | ||
223 | tb10x_gpio->gc.ngpio = ngpio; | ||
224 | tb10x_gpio->gc.can_sleep = 0; | ||
225 | |||
226 | |||
227 | ret = gpiochip_add(&tb10x_gpio->gc); | ||
228 | if (ret < 0) { | ||
229 | dev_err(&pdev->dev, "Could not add gpiochip.\n"); | ||
230 | goto fail_gpiochip_registration; | ||
231 | } | ||
232 | |||
233 | platform_set_drvdata(pdev, tb10x_gpio); | ||
234 | |||
235 | if (of_find_property(dn, "interrupt-controller", NULL)) { | ||
236 | struct irq_chip_generic *gc; | ||
237 | |||
238 | ret = platform_get_irq(pdev, 0); | ||
239 | if (ret < 0) { | ||
240 | dev_err(&pdev->dev, "No interrupt specified.\n"); | ||
241 | goto fail_get_irq; | ||
242 | } | ||
243 | |||
244 | tb10x_gpio->gc.to_irq = tb10x_gpio_to_irq; | ||
245 | tb10x_gpio->irq = ret; | ||
246 | |||
247 | ret = devm_request_irq(&pdev->dev, ret, tb10x_gpio_irq_cascade, | ||
248 | IRQF_TRIGGER_NONE | IRQF_SHARED, | ||
249 | dev_name(&pdev->dev), tb10x_gpio); | ||
250 | if (ret != 0) | ||
251 | goto fail_request_irq; | ||
252 | |||
253 | tb10x_gpio->domain = irq_domain_add_linear(dn, | ||
254 | tb10x_gpio->gc.ngpio, | ||
255 | &irq_generic_chip_ops, NULL); | ||
256 | if (!tb10x_gpio->domain) { | ||
257 | ret = -ENOMEM; | ||
258 | goto fail_irq_domain; | ||
259 | } | ||
260 | |||
261 | ret = irq_alloc_domain_generic_chips(tb10x_gpio->domain, | ||
262 | tb10x_gpio->gc.ngpio, 1, tb10x_gpio->gc.label, | ||
263 | handle_edge_irq, IRQ_NOREQUEST, IRQ_NOPROBE, | ||
264 | IRQ_GC_INIT_MASK_CACHE); | ||
265 | if (ret) | ||
266 | goto fail_irq_domain; | ||
267 | |||
268 | gc = tb10x_gpio->domain->gc->gc[0]; | ||
269 | gc->reg_base = tb10x_gpio->base; | ||
270 | gc->chip_types[0].type = IRQ_TYPE_EDGE_BOTH; | ||
271 | gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; | ||
272 | gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; | ||
273 | gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; | ||
274 | gc->chip_types[0].chip.irq_set_type = tb10x_gpio_irq_set_type; | ||
275 | gc->chip_types[0].regs.ack = OFFSET_TO_REG_CHANGE; | ||
276 | gc->chip_types[0].regs.mask = OFFSET_TO_REG_INT_EN; | ||
277 | } | ||
278 | |||
279 | return 0; | ||
280 | |||
281 | fail_irq_domain: | ||
282 | fail_request_irq: | ||
283 | fail_get_irq: | ||
284 | gpiochip_remove(&tb10x_gpio->gc); | ||
285 | fail_gpiochip_registration: | ||
286 | fail_ioremap: | ||
287 | return ret; | ||
288 | } | ||
289 | |||
290 | static int __exit tb10x_gpio_remove(struct platform_device *pdev) | ||
291 | { | ||
292 | struct tb10x_gpio *tb10x_gpio = platform_get_drvdata(pdev); | ||
293 | int ret; | ||
294 | |||
295 | if (tb10x_gpio->gc.to_irq) { | ||
296 | irq_remove_generic_chip(tb10x_gpio->domain->gc->gc[0], | ||
297 | BIT(tb10x_gpio->gc.ngpio) - 1, 0, 0); | ||
298 | kfree(tb10x_gpio->domain->gc); | ||
299 | irq_domain_remove(tb10x_gpio->domain); | ||
300 | free_irq(tb10x_gpio->irq, tb10x_gpio); | ||
301 | } | ||
302 | ret = gpiochip_remove(&tb10x_gpio->gc); | ||
303 | if (ret) | ||
304 | return ret; | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static const struct of_device_id tb10x_gpio_dt_ids[] = { | ||
310 | { .compatible = "abilis,tb10x-gpio" }, | ||
311 | { } | ||
312 | }; | ||
313 | MODULE_DEVICE_TABLE(of, tb10x_gpio_dt_ids); | ||
314 | |||
315 | static struct platform_driver tb10x_gpio_driver = { | ||
316 | .probe = tb10x_gpio_probe, | ||
317 | .remove = tb10x_gpio_remove, | ||
318 | .driver = { | ||
319 | .name = "tb10x-gpio", | ||
320 | .of_match_table = of_match_ptr(tb10x_gpio_dt_ids), | ||
321 | .owner = THIS_MODULE, | ||
322 | } | ||
323 | }; | ||
324 | |||
325 | module_platform_driver(tb10x_gpio_driver); | ||
326 | MODULE_LICENSE("GPL"); | ||
327 | MODULE_DESCRIPTION("tb10x gpio."); | ||
328 | MODULE_VERSION("0.0.1"); | ||