diff options
author | Ray Jui <rjui@broadcom.com> | 2015-03-09 16:45:00 -0400 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2015-03-17 21:02:19 -0400 |
commit | b64333ce769cbcc6a4dbd0325d9b1da95c6929ff (patch) | |
tree | 1ef96363aa5a6f3083996372c5d6c97cf89c9db8 | |
parent | 2dffad825f0bbba16590cfaebb1ec3672b6919d3 (diff) |
pinctrl: cygnus: add gpio/pinconf driver
This adds the initial support of the Broadcom Cygnus GPIO/PINCONF driver
that supports all 3 GPIO controllers on Cygnus including the ASIU GPIO
controller, the chipCommonG GPIO controller, and the always-on GPIO
controller. Basic PINCONF configurations such as bias pull up/down, and
drive strength are also supported in this driver.
Pins from the ASIU GPIO controller can be individually muxed to GPIO
function, through interaction with the Cygnus IOMUX controller
Signed-off-by: Ray Jui <rjui@broadcom.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
Tested-by: Dmitry Torokhov <dtor@chromium.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-rw-r--r-- | drivers/pinctrl/bcm/Kconfig | 22 | ||||
-rw-r--r-- | drivers/pinctrl/bcm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c | 907 |
3 files changed, 930 insertions, 0 deletions
diff --git a/drivers/pinctrl/bcm/Kconfig b/drivers/pinctrl/bcm/Kconfig index eb1320152a87..cd11d4d9ad58 100644 --- a/drivers/pinctrl/bcm/Kconfig +++ b/drivers/pinctrl/bcm/Kconfig | |||
@@ -20,6 +20,28 @@ config PINCTRL_BCM2835 | |||
20 | select PINMUX | 20 | select PINMUX |
21 | select PINCONF | 21 | select PINCONF |
22 | 22 | ||
23 | config PINCTRL_CYGNUS_GPIO | ||
24 | bool "Broadcom Cygnus GPIO (with PINCONF) driver" | ||
25 | depends on OF_GPIO && ARCH_BCM_CYGNUS | ||
26 | select GPIOLIB_IRQCHIP | ||
27 | select PINCONF | ||
28 | select GENERIC_PINCONF | ||
29 | default ARCH_BCM_CYGNUS | ||
30 | help | ||
31 | Say yes here to enable the Broadcom Cygnus GPIO driver. | ||
32 | |||
33 | The Broadcom Cygnus SoC has 3 GPIO controllers including the ASIU | ||
34 | GPIO controller (ASIU), the chipCommonG GPIO controller (CCM), and | ||
35 | the always-ON GPIO controller (CRMU/AON). All 3 GPIO controllers are | ||
36 | supported by this driver. | ||
37 | |||
38 | All 3 Cygnus GPIO controllers support basic PINCONF functions such | ||
39 | as bias pull up, pull down, and drive strength configurations, when | ||
40 | these pins are muxed to GPIO. | ||
41 | |||
42 | Pins from the ASIU GPIO can be individually muxed to GPIO function, | ||
43 | through interaction with the Cygnus IOMUX controller. | ||
44 | |||
23 | config PINCTRL_CYGNUS_MUX | 45 | config PINCTRL_CYGNUS_MUX |
24 | bool "Broadcom Cygnus IOMUX driver" | 46 | bool "Broadcom Cygnus IOMUX driver" |
25 | depends on (ARCH_BCM_CYGNUS || COMPILE_TEST) | 47 | depends on (ARCH_BCM_CYGNUS || COMPILE_TEST) |
diff --git a/drivers/pinctrl/bcm/Makefile b/drivers/pinctrl/bcm/Makefile index bb6beb66d962..2b2f70ee804c 100644 --- a/drivers/pinctrl/bcm/Makefile +++ b/drivers/pinctrl/bcm/Makefile | |||
@@ -2,4 +2,5 @@ | |||
2 | 2 | ||
3 | obj-$(CONFIG_PINCTRL_BCM281XX) += pinctrl-bcm281xx.o | 3 | obj-$(CONFIG_PINCTRL_BCM281XX) += pinctrl-bcm281xx.o |
4 | obj-$(CONFIG_PINCTRL_BCM2835) += pinctrl-bcm2835.o | 4 | obj-$(CONFIG_PINCTRL_BCM2835) += pinctrl-bcm2835.o |
5 | obj-$(CONFIG_PINCTRL_CYGNUS_GPIO) += pinctrl-cygnus-gpio.o | ||
5 | obj-$(CONFIG_PINCTRL_CYGNUS_MUX) += pinctrl-cygnus-mux.o | 6 | obj-$(CONFIG_PINCTRL_CYGNUS_MUX) += pinctrl-cygnus-mux.o |
diff --git a/drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c b/drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c new file mode 100644 index 000000000000..4ad5c1a996e3 --- /dev/null +++ b/drivers/pinctrl/bcm/pinctrl-cygnus-gpio.c | |||
@@ -0,0 +1,907 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2015 Broadcom Corporation | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation version 2. | ||
7 | * | ||
8 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | ||
9 | * kind, whether express or implied; without even the implied warranty | ||
10 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | * | ||
13 | * This file contains the Broadcom Cygnus GPIO driver that supports 3 | ||
14 | * GPIO controllers on Cygnus including the ASIU GPIO controller, the | ||
15 | * chipCommonG GPIO controller, and the always-on GPIO controller. Basic | ||
16 | * PINCONF such as bias pull up/down, and drive strength are also supported | ||
17 | * in this driver. | ||
18 | * | ||
19 | * Pins from the ASIU GPIO can be individually muxed to GPIO function, | ||
20 | * through the interaction with the Cygnus IOMUX controller | ||
21 | */ | ||
22 | |||
23 | #include <linux/kernel.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/interrupt.h> | ||
26 | #include <linux/io.h> | ||
27 | #include <linux/gpio.h> | ||
28 | #include <linux/ioport.h> | ||
29 | #include <linux/of_device.h> | ||
30 | #include <linux/of_irq.h> | ||
31 | #include <linux/pinctrl/pinctrl.h> | ||
32 | #include <linux/pinctrl/pinmux.h> | ||
33 | #include <linux/pinctrl/pinconf.h> | ||
34 | #include <linux/pinctrl/pinconf-generic.h> | ||
35 | |||
36 | #include "../pinctrl-utils.h" | ||
37 | |||
38 | #define CYGNUS_GPIO_DATA_IN_OFFSET 0x00 | ||
39 | #define CYGNUS_GPIO_DATA_OUT_OFFSET 0x04 | ||
40 | #define CYGNUS_GPIO_OUT_EN_OFFSET 0x08 | ||
41 | #define CYGNUS_GPIO_IN_TYPE_OFFSET 0x0c | ||
42 | #define CYGNUS_GPIO_INT_DE_OFFSET 0x10 | ||
43 | #define CYGNUS_GPIO_INT_EDGE_OFFSET 0x14 | ||
44 | #define CYGNUS_GPIO_INT_MSK_OFFSET 0x18 | ||
45 | #define CYGNUS_GPIO_INT_STAT_OFFSET 0x1c | ||
46 | #define CYGNUS_GPIO_INT_MSTAT_OFFSET 0x20 | ||
47 | #define CYGNUS_GPIO_INT_CLR_OFFSET 0x24 | ||
48 | #define CYGNUS_GPIO_PAD_RES_OFFSET 0x34 | ||
49 | #define CYGNUS_GPIO_RES_EN_OFFSET 0x38 | ||
50 | |||
51 | /* drive strength control for ASIU GPIO */ | ||
52 | #define CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET 0x58 | ||
53 | |||
54 | /* drive strength control for CCM/CRMU (AON) GPIO */ | ||
55 | #define CYGNUS_GPIO_DRV0_CTRL_OFFSET 0x00 | ||
56 | |||
57 | #define GPIO_BANK_SIZE 0x200 | ||
58 | #define NGPIOS_PER_BANK 32 | ||
59 | #define GPIO_BANK(pin) ((pin) / NGPIOS_PER_BANK) | ||
60 | |||
61 | #define CYGNUS_GPIO_REG(pin, reg) (GPIO_BANK(pin) * GPIO_BANK_SIZE + (reg)) | ||
62 | #define CYGNUS_GPIO_SHIFT(pin) ((pin) % NGPIOS_PER_BANK) | ||
63 | |||
64 | #define GPIO_DRV_STRENGTH_BIT_SHIFT 20 | ||
65 | #define GPIO_DRV_STRENGTH_BITS 3 | ||
66 | #define GPIO_DRV_STRENGTH_BIT_MASK ((1 << GPIO_DRV_STRENGTH_BITS) - 1) | ||
67 | |||
68 | /* | ||
69 | * Cygnus GPIO core | ||
70 | * | ||
71 | * @dev: pointer to device | ||
72 | * @base: I/O register base for Cygnus GPIO controller | ||
73 | * @io_ctrl: I/O register base for certain type of Cygnus GPIO controller that | ||
74 | * has the PINCONF support implemented outside of the GPIO block | ||
75 | * @lock: lock to protect access to I/O registers | ||
76 | * @gc: GPIO chip | ||
77 | * @num_banks: number of GPIO banks, each bank supports up to 32 GPIOs | ||
78 | * @pinmux_is_supported: flag to indicate this GPIO controller contains pins | ||
79 | * that can be individually muxed to GPIO | ||
80 | * @pctl: pointer to pinctrl_dev | ||
81 | * @pctldesc: pinctrl descriptor | ||
82 | */ | ||
83 | struct cygnus_gpio { | ||
84 | struct device *dev; | ||
85 | |||
86 | void __iomem *base; | ||
87 | void __iomem *io_ctrl; | ||
88 | |||
89 | spinlock_t lock; | ||
90 | |||
91 | struct gpio_chip gc; | ||
92 | unsigned num_banks; | ||
93 | |||
94 | bool pinmux_is_supported; | ||
95 | |||
96 | struct pinctrl_dev *pctl; | ||
97 | struct pinctrl_desc pctldesc; | ||
98 | }; | ||
99 | |||
100 | static inline struct cygnus_gpio *to_cygnus_gpio(struct gpio_chip *gc) | ||
101 | { | ||
102 | return container_of(gc, struct cygnus_gpio, gc); | ||
103 | } | ||
104 | |||
105 | /* | ||
106 | * Mapping from PINCONF pins to GPIO pins is 1-to-1 | ||
107 | */ | ||
108 | static inline unsigned cygnus_pin_to_gpio(unsigned pin) | ||
109 | { | ||
110 | return pin; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * cygnus_set_bit - set or clear one bit (corresponding to the GPIO pin) in a | ||
115 | * Cygnus GPIO register | ||
116 | * | ||
117 | * @cygnus_gpio: Cygnus GPIO device | ||
118 | * @reg: register offset | ||
119 | * @gpio: GPIO pin | ||
120 | * @set: set or clear | ||
121 | */ | ||
122 | static inline void cygnus_set_bit(struct cygnus_gpio *chip, unsigned int reg, | ||
123 | unsigned gpio, bool set) | ||
124 | { | ||
125 | unsigned int offset = CYGNUS_GPIO_REG(gpio, reg); | ||
126 | unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); | ||
127 | u32 val; | ||
128 | |||
129 | val = readl(chip->base + offset); | ||
130 | if (set) | ||
131 | val |= BIT(shift); | ||
132 | else | ||
133 | val &= ~BIT(shift); | ||
134 | writel(val, chip->base + offset); | ||
135 | } | ||
136 | |||
137 | static inline bool cygnus_get_bit(struct cygnus_gpio *chip, unsigned int reg, | ||
138 | unsigned gpio) | ||
139 | { | ||
140 | unsigned int offset = CYGNUS_GPIO_REG(gpio, reg); | ||
141 | unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); | ||
142 | |||
143 | return !!(readl(chip->base + offset) & BIT(shift)); | ||
144 | } | ||
145 | |||
146 | static void cygnus_gpio_irq_handler(unsigned int irq, struct irq_desc *desc) | ||
147 | { | ||
148 | struct gpio_chip *gc = irq_desc_get_handler_data(desc); | ||
149 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
150 | struct irq_chip *irq_chip = irq_desc_get_chip(desc); | ||
151 | int i, bit; | ||
152 | |||
153 | chained_irq_enter(irq_chip, desc); | ||
154 | |||
155 | /* go through the entire GPIO banks and handle all interrupts */ | ||
156 | for (i = 0; i < chip->num_banks; i++) { | ||
157 | unsigned long val = readl(chip->base + (i * GPIO_BANK_SIZE) + | ||
158 | CYGNUS_GPIO_INT_MSTAT_OFFSET); | ||
159 | |||
160 | for_each_set_bit(bit, &val, NGPIOS_PER_BANK) { | ||
161 | unsigned pin = NGPIOS_PER_BANK * i + bit; | ||
162 | int child_irq = irq_find_mapping(gc->irqdomain, pin); | ||
163 | |||
164 | /* | ||
165 | * Clear the interrupt before invoking the | ||
166 | * handler, so we do not leave any window | ||
167 | */ | ||
168 | writel(BIT(bit), chip->base + (i * GPIO_BANK_SIZE) + | ||
169 | CYGNUS_GPIO_INT_CLR_OFFSET); | ||
170 | |||
171 | generic_handle_irq(child_irq); | ||
172 | } | ||
173 | } | ||
174 | |||
175 | chained_irq_exit(irq_chip, desc); | ||
176 | } | ||
177 | |||
178 | |||
179 | static void cygnus_gpio_irq_ack(struct irq_data *d) | ||
180 | { | ||
181 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | ||
182 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
183 | unsigned gpio = d->hwirq; | ||
184 | unsigned int offset = CYGNUS_GPIO_REG(gpio, | ||
185 | CYGNUS_GPIO_INT_CLR_OFFSET); | ||
186 | unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); | ||
187 | u32 val = BIT(shift); | ||
188 | |||
189 | writel(val, chip->base + offset); | ||
190 | } | ||
191 | |||
192 | /** | ||
193 | * cygnus_gpio_irq_set_mask - mask/unmask a GPIO interrupt | ||
194 | * | ||
195 | * @d: IRQ chip data | ||
196 | * @unmask: mask/unmask GPIO interrupt | ||
197 | */ | ||
198 | static void cygnus_gpio_irq_set_mask(struct irq_data *d, bool unmask) | ||
199 | { | ||
200 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | ||
201 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
202 | unsigned gpio = d->hwirq; | ||
203 | |||
204 | cygnus_set_bit(chip, CYGNUS_GPIO_INT_MSK_OFFSET, gpio, unmask); | ||
205 | } | ||
206 | |||
207 | static void cygnus_gpio_irq_mask(struct irq_data *d) | ||
208 | { | ||
209 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | ||
210 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
211 | unsigned long flags; | ||
212 | |||
213 | spin_lock_irqsave(&chip->lock, flags); | ||
214 | cygnus_gpio_irq_set_mask(d, false); | ||
215 | spin_unlock_irqrestore(&chip->lock, flags); | ||
216 | } | ||
217 | |||
218 | static void cygnus_gpio_irq_unmask(struct irq_data *d) | ||
219 | { | ||
220 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | ||
221 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
222 | unsigned long flags; | ||
223 | |||
224 | spin_lock_irqsave(&chip->lock, flags); | ||
225 | cygnus_gpio_irq_set_mask(d, true); | ||
226 | spin_unlock_irqrestore(&chip->lock, flags); | ||
227 | } | ||
228 | |||
229 | static int cygnus_gpio_irq_set_type(struct irq_data *d, unsigned int type) | ||
230 | { | ||
231 | struct gpio_chip *gc = irq_data_get_irq_chip_data(d); | ||
232 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
233 | unsigned gpio = d->hwirq; | ||
234 | bool level_triggered = false; | ||
235 | bool dual_edge = false; | ||
236 | bool rising_or_high = false; | ||
237 | unsigned long flags; | ||
238 | |||
239 | switch (type & IRQ_TYPE_SENSE_MASK) { | ||
240 | case IRQ_TYPE_EDGE_RISING: | ||
241 | rising_or_high = true; | ||
242 | break; | ||
243 | |||
244 | case IRQ_TYPE_EDGE_FALLING: | ||
245 | break; | ||
246 | |||
247 | case IRQ_TYPE_EDGE_BOTH: | ||
248 | dual_edge = true; | ||
249 | break; | ||
250 | |||
251 | case IRQ_TYPE_LEVEL_HIGH: | ||
252 | level_triggered = true; | ||
253 | rising_or_high = true; | ||
254 | break; | ||
255 | |||
256 | case IRQ_TYPE_LEVEL_LOW: | ||
257 | level_triggered = true; | ||
258 | break; | ||
259 | |||
260 | default: | ||
261 | dev_err(chip->dev, "invalid GPIO IRQ type 0x%x\n", | ||
262 | type); | ||
263 | return -EINVAL; | ||
264 | } | ||
265 | |||
266 | spin_lock_irqsave(&chip->lock, flags); | ||
267 | cygnus_set_bit(chip, CYGNUS_GPIO_IN_TYPE_OFFSET, gpio, | ||
268 | level_triggered); | ||
269 | cygnus_set_bit(chip, CYGNUS_GPIO_INT_DE_OFFSET, gpio, dual_edge); | ||
270 | cygnus_set_bit(chip, CYGNUS_GPIO_INT_EDGE_OFFSET, gpio, | ||
271 | rising_or_high); | ||
272 | spin_unlock_irqrestore(&chip->lock, flags); | ||
273 | |||
274 | dev_dbg(chip->dev, | ||
275 | "gpio:%u level_triggered:%d dual_edge:%d rising_or_high:%d\n", | ||
276 | gpio, level_triggered, dual_edge, rising_or_high); | ||
277 | |||
278 | return 0; | ||
279 | } | ||
280 | |||
281 | static struct irq_chip cygnus_gpio_irq_chip = { | ||
282 | .name = "bcm-cygnus-gpio", | ||
283 | .irq_ack = cygnus_gpio_irq_ack, | ||
284 | .irq_mask = cygnus_gpio_irq_mask, | ||
285 | .irq_unmask = cygnus_gpio_irq_unmask, | ||
286 | .irq_set_type = cygnus_gpio_irq_set_type, | ||
287 | }; | ||
288 | |||
289 | /* | ||
290 | * Request the Cygnus IOMUX pinmux controller to mux individual pins to GPIO | ||
291 | */ | ||
292 | static int cygnus_gpio_request(struct gpio_chip *gc, unsigned offset) | ||
293 | { | ||
294 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
295 | unsigned gpio = gc->base + offset; | ||
296 | |||
297 | /* not all Cygnus GPIO pins can be muxed individually */ | ||
298 | if (!chip->pinmux_is_supported) | ||
299 | return 0; | ||
300 | |||
301 | return pinctrl_request_gpio(gpio); | ||
302 | } | ||
303 | |||
304 | static void cygnus_gpio_free(struct gpio_chip *gc, unsigned offset) | ||
305 | { | ||
306 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
307 | unsigned gpio = gc->base + offset; | ||
308 | |||
309 | if (!chip->pinmux_is_supported) | ||
310 | return; | ||
311 | |||
312 | pinctrl_free_gpio(gpio); | ||
313 | } | ||
314 | |||
315 | static int cygnus_gpio_direction_input(struct gpio_chip *gc, unsigned gpio) | ||
316 | { | ||
317 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
318 | unsigned long flags; | ||
319 | |||
320 | spin_lock_irqsave(&chip->lock, flags); | ||
321 | cygnus_set_bit(chip, CYGNUS_GPIO_OUT_EN_OFFSET, gpio, false); | ||
322 | spin_unlock_irqrestore(&chip->lock, flags); | ||
323 | |||
324 | dev_dbg(chip->dev, "gpio:%u set input\n", gpio); | ||
325 | |||
326 | return 0; | ||
327 | } | ||
328 | |||
329 | static int cygnus_gpio_direction_output(struct gpio_chip *gc, unsigned gpio, | ||
330 | int val) | ||
331 | { | ||
332 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
333 | unsigned long flags; | ||
334 | |||
335 | spin_lock_irqsave(&chip->lock, flags); | ||
336 | cygnus_set_bit(chip, CYGNUS_GPIO_OUT_EN_OFFSET, gpio, true); | ||
337 | cygnus_set_bit(chip, CYGNUS_GPIO_DATA_OUT_OFFSET, gpio, !!(val)); | ||
338 | spin_unlock_irqrestore(&chip->lock, flags); | ||
339 | |||
340 | dev_dbg(chip->dev, "gpio:%u set output, value:%d\n", gpio, val); | ||
341 | |||
342 | return 0; | ||
343 | } | ||
344 | |||
345 | static void cygnus_gpio_set(struct gpio_chip *gc, unsigned gpio, int val) | ||
346 | { | ||
347 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
348 | unsigned long flags; | ||
349 | |||
350 | spin_lock_irqsave(&chip->lock, flags); | ||
351 | cygnus_set_bit(chip, CYGNUS_GPIO_DATA_OUT_OFFSET, gpio, !!(val)); | ||
352 | spin_unlock_irqrestore(&chip->lock, flags); | ||
353 | |||
354 | dev_dbg(chip->dev, "gpio:%u set, value:%d\n", gpio, val); | ||
355 | } | ||
356 | |||
357 | static int cygnus_gpio_get(struct gpio_chip *gc, unsigned gpio) | ||
358 | { | ||
359 | struct cygnus_gpio *chip = to_cygnus_gpio(gc); | ||
360 | unsigned int offset = CYGNUS_GPIO_REG(gpio, | ||
361 | CYGNUS_GPIO_DATA_IN_OFFSET); | ||
362 | unsigned int shift = CYGNUS_GPIO_SHIFT(gpio); | ||
363 | |||
364 | return !!(readl(chip->base + offset) & BIT(shift)); | ||
365 | } | ||
366 | |||
367 | static int cygnus_get_groups_count(struct pinctrl_dev *pctldev) | ||
368 | { | ||
369 | return 1; | ||
370 | } | ||
371 | |||
372 | /* | ||
373 | * Only one group: "gpio_grp", since this local pinctrl device only performs | ||
374 | * GPIO specific PINCONF configurations | ||
375 | */ | ||
376 | static const char *cygnus_get_group_name(struct pinctrl_dev *pctldev, | ||
377 | unsigned selector) | ||
378 | { | ||
379 | return "gpio_grp"; | ||
380 | } | ||
381 | |||
382 | static const struct pinctrl_ops cygnus_pctrl_ops = { | ||
383 | .get_groups_count = cygnus_get_groups_count, | ||
384 | .get_group_name = cygnus_get_group_name, | ||
385 | .dt_node_to_map = pinconf_generic_dt_node_to_map_pin, | ||
386 | .dt_free_map = pinctrl_utils_dt_free_map, | ||
387 | }; | ||
388 | |||
389 | static int cygnus_gpio_set_pull(struct cygnus_gpio *chip, unsigned gpio, | ||
390 | bool disable, bool pull_up) | ||
391 | { | ||
392 | unsigned long flags; | ||
393 | |||
394 | spin_lock_irqsave(&chip->lock, flags); | ||
395 | |||
396 | if (disable) { | ||
397 | cygnus_set_bit(chip, CYGNUS_GPIO_RES_EN_OFFSET, gpio, false); | ||
398 | } else { | ||
399 | cygnus_set_bit(chip, CYGNUS_GPIO_PAD_RES_OFFSET, gpio, | ||
400 | pull_up); | ||
401 | cygnus_set_bit(chip, CYGNUS_GPIO_RES_EN_OFFSET, gpio, true); | ||
402 | } | ||
403 | |||
404 | spin_unlock_irqrestore(&chip->lock, flags); | ||
405 | |||
406 | dev_dbg(chip->dev, "gpio:%u set pullup:%d\n", gpio, pull_up); | ||
407 | |||
408 | return 0; | ||
409 | } | ||
410 | |||
411 | static void cygnus_gpio_get_pull(struct cygnus_gpio *chip, unsigned gpio, | ||
412 | bool *disable, bool *pull_up) | ||
413 | { | ||
414 | unsigned long flags; | ||
415 | |||
416 | spin_lock_irqsave(&chip->lock, flags); | ||
417 | *disable = !cygnus_get_bit(chip, CYGNUS_GPIO_RES_EN_OFFSET, gpio); | ||
418 | *pull_up = cygnus_get_bit(chip, CYGNUS_GPIO_PAD_RES_OFFSET, gpio); | ||
419 | spin_unlock_irqrestore(&chip->lock, flags); | ||
420 | } | ||
421 | |||
422 | static int cygnus_gpio_set_strength(struct cygnus_gpio *chip, unsigned gpio, | ||
423 | unsigned strength) | ||
424 | { | ||
425 | void __iomem *base; | ||
426 | unsigned int i, offset, shift; | ||
427 | u32 val; | ||
428 | unsigned long flags; | ||
429 | |||
430 | /* make sure drive strength is supported */ | ||
431 | if (strength < 2 || strength > 16 || (strength % 2)) | ||
432 | return -ENOTSUPP; | ||
433 | |||
434 | if (chip->io_ctrl) { | ||
435 | base = chip->io_ctrl; | ||
436 | offset = CYGNUS_GPIO_DRV0_CTRL_OFFSET; | ||
437 | } else { | ||
438 | base = chip->base; | ||
439 | offset = CYGNUS_GPIO_REG(gpio, | ||
440 | CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET); | ||
441 | } | ||
442 | |||
443 | shift = CYGNUS_GPIO_SHIFT(gpio); | ||
444 | |||
445 | dev_dbg(chip->dev, "gpio:%u set drive strength:%d mA\n", gpio, | ||
446 | strength); | ||
447 | |||
448 | spin_lock_irqsave(&chip->lock, flags); | ||
449 | strength = (strength / 2) - 1; | ||
450 | for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) { | ||
451 | val = readl(base + offset); | ||
452 | val &= ~BIT(shift); | ||
453 | val |= ((strength >> i) & 0x1) << shift; | ||
454 | writel(val, base + offset); | ||
455 | offset += 4; | ||
456 | } | ||
457 | spin_unlock_irqrestore(&chip->lock, flags); | ||
458 | |||
459 | return 0; | ||
460 | } | ||
461 | |||
462 | static int cygnus_gpio_get_strength(struct cygnus_gpio *chip, unsigned gpio, | ||
463 | u16 *strength) | ||
464 | { | ||
465 | void __iomem *base; | ||
466 | unsigned int i, offset, shift; | ||
467 | u32 val; | ||
468 | unsigned long flags; | ||
469 | |||
470 | if (chip->io_ctrl) { | ||
471 | base = chip->io_ctrl; | ||
472 | offset = CYGNUS_GPIO_DRV0_CTRL_OFFSET; | ||
473 | } else { | ||
474 | base = chip->base; | ||
475 | offset = CYGNUS_GPIO_REG(gpio, | ||
476 | CYGNUS_GPIO_ASIU_DRV0_CTRL_OFFSET); | ||
477 | } | ||
478 | |||
479 | shift = CYGNUS_GPIO_SHIFT(gpio); | ||
480 | |||
481 | spin_lock_irqsave(&chip->lock, flags); | ||
482 | *strength = 0; | ||
483 | for (i = 0; i < GPIO_DRV_STRENGTH_BITS; i++) { | ||
484 | val = readl(base + offset) & BIT(shift); | ||
485 | val >>= shift; | ||
486 | *strength += (val << i); | ||
487 | offset += 4; | ||
488 | } | ||
489 | |||
490 | /* convert to mA */ | ||
491 | *strength = (*strength + 1) * 2; | ||
492 | spin_unlock_irqrestore(&chip->lock, flags); | ||
493 | |||
494 | return 0; | ||
495 | } | ||
496 | |||
497 | static int cygnus_pin_config_get(struct pinctrl_dev *pctldev, unsigned pin, | ||
498 | unsigned long *config) | ||
499 | { | ||
500 | struct cygnus_gpio *chip = pinctrl_dev_get_drvdata(pctldev); | ||
501 | enum pin_config_param param = pinconf_to_config_param(*config); | ||
502 | unsigned gpio = cygnus_pin_to_gpio(pin); | ||
503 | u16 arg; | ||
504 | bool disable, pull_up; | ||
505 | int ret; | ||
506 | |||
507 | switch (param) { | ||
508 | case PIN_CONFIG_BIAS_DISABLE: | ||
509 | cygnus_gpio_get_pull(chip, gpio, &disable, &pull_up); | ||
510 | if (disable) | ||
511 | return 0; | ||
512 | else | ||
513 | return -EINVAL; | ||
514 | |||
515 | case PIN_CONFIG_BIAS_PULL_UP: | ||
516 | cygnus_gpio_get_pull(chip, gpio, &disable, &pull_up); | ||
517 | if (!disable && pull_up) | ||
518 | return 0; | ||
519 | else | ||
520 | return -EINVAL; | ||
521 | |||
522 | case PIN_CONFIG_BIAS_PULL_DOWN: | ||
523 | cygnus_gpio_get_pull(chip, gpio, &disable, &pull_up); | ||
524 | if (!disable && !pull_up) | ||
525 | return 0; | ||
526 | else | ||
527 | return -EINVAL; | ||
528 | |||
529 | case PIN_CONFIG_DRIVE_STRENGTH: | ||
530 | ret = cygnus_gpio_get_strength(chip, gpio, &arg); | ||
531 | if (ret) | ||
532 | return ret; | ||
533 | else | ||
534 | *config = pinconf_to_config_packed(param, arg); | ||
535 | |||
536 | return 0; | ||
537 | |||
538 | default: | ||
539 | return -ENOTSUPP; | ||
540 | } | ||
541 | |||
542 | return -ENOTSUPP; | ||
543 | } | ||
544 | |||
545 | static int cygnus_pin_config_set(struct pinctrl_dev *pctldev, unsigned pin, | ||
546 | unsigned long *configs, unsigned num_configs) | ||
547 | { | ||
548 | struct cygnus_gpio *chip = pinctrl_dev_get_drvdata(pctldev); | ||
549 | enum pin_config_param param; | ||
550 | u16 arg; | ||
551 | unsigned i, gpio = cygnus_pin_to_gpio(pin); | ||
552 | int ret = -ENOTSUPP; | ||
553 | |||
554 | for (i = 0; i < num_configs; i++) { | ||
555 | param = pinconf_to_config_param(configs[i]); | ||
556 | arg = pinconf_to_config_argument(configs[i]); | ||
557 | |||
558 | switch (param) { | ||
559 | case PIN_CONFIG_BIAS_DISABLE: | ||
560 | ret = cygnus_gpio_set_pull(chip, gpio, true, false); | ||
561 | if (ret < 0) | ||
562 | goto out; | ||
563 | break; | ||
564 | |||
565 | case PIN_CONFIG_BIAS_PULL_UP: | ||
566 | ret = cygnus_gpio_set_pull(chip, gpio, false, true); | ||
567 | if (ret < 0) | ||
568 | goto out; | ||
569 | break; | ||
570 | |||
571 | case PIN_CONFIG_BIAS_PULL_DOWN: | ||
572 | ret = cygnus_gpio_set_pull(chip, gpio, false, false); | ||
573 | if (ret < 0) | ||
574 | goto out; | ||
575 | break; | ||
576 | |||
577 | case PIN_CONFIG_DRIVE_STRENGTH: | ||
578 | ret = cygnus_gpio_set_strength(chip, gpio, arg); | ||
579 | if (ret < 0) | ||
580 | goto out; | ||
581 | break; | ||
582 | |||
583 | default: | ||
584 | dev_err(chip->dev, "invalid configuration\n"); | ||
585 | return -ENOTSUPP; | ||
586 | } | ||
587 | } /* for each config */ | ||
588 | |||
589 | out: | ||
590 | return ret; | ||
591 | } | ||
592 | |||
593 | static const struct pinconf_ops cygnus_pconf_ops = { | ||
594 | .is_generic = true, | ||
595 | .pin_config_get = cygnus_pin_config_get, | ||
596 | .pin_config_set = cygnus_pin_config_set, | ||
597 | }; | ||
598 | |||
599 | /* | ||
600 | * Map a GPIO in the local gpio_chip pin space to a pin in the Cygnus IOMUX | ||
601 | * pinctrl pin space | ||
602 | */ | ||
603 | struct cygnus_gpio_pin_range { | ||
604 | unsigned offset; | ||
605 | unsigned pin_base; | ||
606 | unsigned num_pins; | ||
607 | }; | ||
608 | |||
609 | #define CYGNUS_PINRANGE(o, p, n) { .offset = o, .pin_base = p, .num_pins = n } | ||
610 | |||
611 | /* | ||
612 | * Pin mapping table for mapping local GPIO pins to Cygnus IOMUX pinctrl pins | ||
613 | */ | ||
614 | static const struct cygnus_gpio_pin_range cygnus_gpio_pintable[] = { | ||
615 | CYGNUS_PINRANGE(0, 42, 1), | ||
616 | CYGNUS_PINRANGE(1, 44, 3), | ||
617 | CYGNUS_PINRANGE(4, 48, 1), | ||
618 | CYGNUS_PINRANGE(5, 50, 3), | ||
619 | CYGNUS_PINRANGE(8, 126, 1), | ||
620 | CYGNUS_PINRANGE(9, 155, 1), | ||
621 | CYGNUS_PINRANGE(10, 152, 1), | ||
622 | CYGNUS_PINRANGE(11, 154, 1), | ||
623 | CYGNUS_PINRANGE(12, 153, 1), | ||
624 | CYGNUS_PINRANGE(13, 127, 3), | ||
625 | CYGNUS_PINRANGE(16, 140, 1), | ||
626 | CYGNUS_PINRANGE(17, 145, 7), | ||
627 | CYGNUS_PINRANGE(24, 130, 10), | ||
628 | CYGNUS_PINRANGE(34, 141, 4), | ||
629 | CYGNUS_PINRANGE(38, 54, 1), | ||
630 | CYGNUS_PINRANGE(39, 56, 3), | ||
631 | CYGNUS_PINRANGE(42, 60, 3), | ||
632 | CYGNUS_PINRANGE(45, 64, 3), | ||
633 | CYGNUS_PINRANGE(48, 68, 2), | ||
634 | CYGNUS_PINRANGE(50, 84, 6), | ||
635 | CYGNUS_PINRANGE(56, 94, 6), | ||
636 | CYGNUS_PINRANGE(62, 72, 1), | ||
637 | CYGNUS_PINRANGE(63, 70, 1), | ||
638 | CYGNUS_PINRANGE(64, 80, 1), | ||
639 | CYGNUS_PINRANGE(65, 74, 3), | ||
640 | CYGNUS_PINRANGE(68, 78, 1), | ||
641 | CYGNUS_PINRANGE(69, 82, 1), | ||
642 | CYGNUS_PINRANGE(70, 156, 17), | ||
643 | CYGNUS_PINRANGE(87, 104, 12), | ||
644 | CYGNUS_PINRANGE(99, 102, 2), | ||
645 | CYGNUS_PINRANGE(101, 90, 4), | ||
646 | CYGNUS_PINRANGE(105, 116, 10), | ||
647 | CYGNUS_PINRANGE(123, 11, 1), | ||
648 | CYGNUS_PINRANGE(124, 38, 4), | ||
649 | CYGNUS_PINRANGE(128, 43, 1), | ||
650 | CYGNUS_PINRANGE(129, 47, 1), | ||
651 | CYGNUS_PINRANGE(130, 49, 1), | ||
652 | CYGNUS_PINRANGE(131, 53, 1), | ||
653 | CYGNUS_PINRANGE(132, 55, 1), | ||
654 | CYGNUS_PINRANGE(133, 59, 1), | ||
655 | CYGNUS_PINRANGE(134, 63, 1), | ||
656 | CYGNUS_PINRANGE(135, 67, 1), | ||
657 | CYGNUS_PINRANGE(136, 71, 1), | ||
658 | CYGNUS_PINRANGE(137, 73, 1), | ||
659 | CYGNUS_PINRANGE(138, 77, 1), | ||
660 | CYGNUS_PINRANGE(139, 79, 1), | ||
661 | CYGNUS_PINRANGE(140, 81, 1), | ||
662 | CYGNUS_PINRANGE(141, 83, 1), | ||
663 | CYGNUS_PINRANGE(142, 10, 1) | ||
664 | }; | ||
665 | |||
666 | /* | ||
667 | * The Cygnus IOMUX controller mainly supports group based mux configuration, | ||
668 | * but certain pins can be muxed to GPIO individually. Only the ASIU GPIO | ||
669 | * controller can support this, so it's an optional configuration | ||
670 | * | ||
671 | * Return -ENODEV means no support and that's fine | ||
672 | */ | ||
673 | static int cygnus_gpio_pinmux_add_range(struct cygnus_gpio *chip) | ||
674 | { | ||
675 | struct device_node *node = chip->dev->of_node; | ||
676 | struct device_node *pinmux_node; | ||
677 | struct platform_device *pinmux_pdev; | ||
678 | struct gpio_chip *gc = &chip->gc; | ||
679 | int i, ret = 0; | ||
680 | |||
681 | /* parse DT to find the phandle to the pinmux controller */ | ||
682 | pinmux_node = of_parse_phandle(node, "pinmux", 0); | ||
683 | if (!pinmux_node) | ||
684 | return -ENODEV; | ||
685 | |||
686 | pinmux_pdev = of_find_device_by_node(pinmux_node); | ||
687 | /* no longer need the pinmux node */ | ||
688 | of_node_put(pinmux_node); | ||
689 | if (!pinmux_pdev) { | ||
690 | dev_err(chip->dev, "failed to get pinmux device\n"); | ||
691 | return -EINVAL; | ||
692 | } | ||
693 | |||
694 | /* now need to create the mapping between local GPIO and PINMUX pins */ | ||
695 | for (i = 0; i < ARRAY_SIZE(cygnus_gpio_pintable); i++) { | ||
696 | ret = gpiochip_add_pin_range(gc, dev_name(&pinmux_pdev->dev), | ||
697 | cygnus_gpio_pintable[i].offset, | ||
698 | cygnus_gpio_pintable[i].pin_base, | ||
699 | cygnus_gpio_pintable[i].num_pins); | ||
700 | if (ret) { | ||
701 | dev_err(chip->dev, "unable to add GPIO pin range\n"); | ||
702 | goto err_put_device; | ||
703 | } | ||
704 | } | ||
705 | |||
706 | chip->pinmux_is_supported = true; | ||
707 | |||
708 | /* no need for pinmux_pdev device reference anymore */ | ||
709 | put_device(&pinmux_pdev->dev); | ||
710 | return 0; | ||
711 | |||
712 | err_put_device: | ||
713 | put_device(&pinmux_pdev->dev); | ||
714 | gpiochip_remove_pin_ranges(gc); | ||
715 | return ret; | ||
716 | } | ||
717 | |||
718 | /* | ||
719 | * Cygnus GPIO controller supports some PINCONF related configurations such as | ||
720 | * pull up, pull down, and drive strength, when the pin is configured to GPIO | ||
721 | * | ||
722 | * Here a local pinctrl device is created with simple 1-to-1 pin mapping to the | ||
723 | * local GPIO pins | ||
724 | */ | ||
725 | static int cygnus_gpio_register_pinconf(struct cygnus_gpio *chip) | ||
726 | { | ||
727 | struct pinctrl_desc *pctldesc = &chip->pctldesc; | ||
728 | struct pinctrl_pin_desc *pins; | ||
729 | struct gpio_chip *gc = &chip->gc; | ||
730 | int i; | ||
731 | |||
732 | pins = devm_kcalloc(chip->dev, gc->ngpio, sizeof(*pins), GFP_KERNEL); | ||
733 | if (!pins) | ||
734 | return -ENOMEM; | ||
735 | |||
736 | for (i = 0; i < gc->ngpio; i++) { | ||
737 | pins[i].number = i; | ||
738 | pins[i].name = devm_kasprintf(chip->dev, GFP_KERNEL, | ||
739 | "gpio-%d", i); | ||
740 | if (!pins[i].name) | ||
741 | return -ENOMEM; | ||
742 | } | ||
743 | |||
744 | pctldesc->name = dev_name(chip->dev); | ||
745 | pctldesc->pctlops = &cygnus_pctrl_ops; | ||
746 | pctldesc->pins = pins; | ||
747 | pctldesc->npins = gc->ngpio; | ||
748 | pctldesc->confops = &cygnus_pconf_ops; | ||
749 | |||
750 | chip->pctl = pinctrl_register(pctldesc, chip->dev, chip); | ||
751 | if (!chip->pctl) { | ||
752 | dev_err(chip->dev, "unable to register pinctrl device\n"); | ||
753 | return -EINVAL; | ||
754 | } | ||
755 | |||
756 | return 0; | ||
757 | } | ||
758 | |||
759 | static void cygnus_gpio_unregister_pinconf(struct cygnus_gpio *chip) | ||
760 | { | ||
761 | if (chip->pctl) | ||
762 | pinctrl_unregister(chip->pctl); | ||
763 | } | ||
764 | |||
765 | struct cygnus_gpio_data { | ||
766 | unsigned num_gpios; | ||
767 | }; | ||
768 | |||
769 | static const struct cygnus_gpio_data cygnus_cmm_gpio_data = { | ||
770 | .num_gpios = 24, | ||
771 | }; | ||
772 | |||
773 | static const struct cygnus_gpio_data cygnus_asiu_gpio_data = { | ||
774 | .num_gpios = 146, | ||
775 | }; | ||
776 | |||
777 | static const struct cygnus_gpio_data cygnus_crmu_gpio_data = { | ||
778 | .num_gpios = 6, | ||
779 | }; | ||
780 | |||
781 | static const struct of_device_id cygnus_gpio_of_match[] = { | ||
782 | { | ||
783 | .compatible = "brcm,cygnus-ccm-gpio", | ||
784 | .data = &cygnus_cmm_gpio_data, | ||
785 | }, | ||
786 | { | ||
787 | .compatible = "brcm,cygnus-asiu-gpio", | ||
788 | .data = &cygnus_asiu_gpio_data, | ||
789 | }, | ||
790 | { | ||
791 | .compatible = "brcm,cygnus-crmu-gpio", | ||
792 | .data = &cygnus_crmu_gpio_data, | ||
793 | } | ||
794 | }; | ||
795 | |||
796 | static int cygnus_gpio_probe(struct platform_device *pdev) | ||
797 | { | ||
798 | struct device *dev = &pdev->dev; | ||
799 | struct resource *res; | ||
800 | struct cygnus_gpio *chip; | ||
801 | struct gpio_chip *gc; | ||
802 | u32 ngpios; | ||
803 | int irq, ret; | ||
804 | const struct of_device_id *match; | ||
805 | const struct cygnus_gpio_data *gpio_data; | ||
806 | |||
807 | match = of_match_device(cygnus_gpio_of_match, dev); | ||
808 | if (!match) | ||
809 | return -ENODEV; | ||
810 | gpio_data = match->data; | ||
811 | ngpios = gpio_data->num_gpios; | ||
812 | |||
813 | chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); | ||
814 | if (!chip) | ||
815 | return -ENOMEM; | ||
816 | |||
817 | chip->dev = dev; | ||
818 | platform_set_drvdata(pdev, chip); | ||
819 | |||
820 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
821 | chip->base = devm_ioremap_resource(dev, res); | ||
822 | if (IS_ERR(chip->base)) { | ||
823 | dev_err(dev, "unable to map I/O memory\n"); | ||
824 | return PTR_ERR(chip->base); | ||
825 | } | ||
826 | |||
827 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1); | ||
828 | if (res) { | ||
829 | chip->io_ctrl = devm_ioremap_resource(dev, res); | ||
830 | if (IS_ERR(chip->io_ctrl)) { | ||
831 | dev_err(dev, "unable to map I/O memory\n"); | ||
832 | return PTR_ERR(chip->io_ctrl); | ||
833 | } | ||
834 | } | ||
835 | |||
836 | spin_lock_init(&chip->lock); | ||
837 | |||
838 | gc = &chip->gc; | ||
839 | gc->base = -1; | ||
840 | gc->ngpio = ngpios; | ||
841 | chip->num_banks = (ngpios + NGPIOS_PER_BANK - 1) / NGPIOS_PER_BANK; | ||
842 | gc->label = dev_name(dev); | ||
843 | gc->dev = dev; | ||
844 | gc->of_node = dev->of_node; | ||
845 | gc->request = cygnus_gpio_request; | ||
846 | gc->free = cygnus_gpio_free; | ||
847 | gc->direction_input = cygnus_gpio_direction_input; | ||
848 | gc->direction_output = cygnus_gpio_direction_output; | ||
849 | gc->set = cygnus_gpio_set; | ||
850 | gc->get = cygnus_gpio_get; | ||
851 | |||
852 | ret = gpiochip_add(gc); | ||
853 | if (ret < 0) { | ||
854 | dev_err(dev, "unable to add GPIO chip\n"); | ||
855 | return ret; | ||
856 | } | ||
857 | |||
858 | ret = cygnus_gpio_pinmux_add_range(chip); | ||
859 | if (ret && ret != -ENODEV) { | ||
860 | dev_err(dev, "unable to add GPIO pin range\n"); | ||
861 | goto err_rm_gpiochip; | ||
862 | } | ||
863 | |||
864 | ret = cygnus_gpio_register_pinconf(chip); | ||
865 | if (ret) { | ||
866 | dev_err(dev, "unable to register pinconf\n"); | ||
867 | goto err_rm_gpiochip; | ||
868 | } | ||
869 | |||
870 | /* optional GPIO interrupt support */ | ||
871 | irq = platform_get_irq(pdev, 0); | ||
872 | if (irq) { | ||
873 | ret = gpiochip_irqchip_add(gc, &cygnus_gpio_irq_chip, 0, | ||
874 | handle_simple_irq, IRQ_TYPE_NONE); | ||
875 | if (ret) { | ||
876 | dev_err(dev, "no GPIO irqchip\n"); | ||
877 | goto err_unregister_pinconf; | ||
878 | } | ||
879 | |||
880 | gpiochip_set_chained_irqchip(gc, &cygnus_gpio_irq_chip, irq, | ||
881 | cygnus_gpio_irq_handler); | ||
882 | } | ||
883 | |||
884 | return 0; | ||
885 | |||
886 | err_unregister_pinconf: | ||
887 | cygnus_gpio_unregister_pinconf(chip); | ||
888 | |||
889 | err_rm_gpiochip: | ||
890 | gpiochip_remove(gc); | ||
891 | |||
892 | return ret; | ||
893 | } | ||
894 | |||
895 | static struct platform_driver cygnus_gpio_driver = { | ||
896 | .driver = { | ||
897 | .name = "cygnus-gpio", | ||
898 | .of_match_table = cygnus_gpio_of_match, | ||
899 | }, | ||
900 | .probe = cygnus_gpio_probe, | ||
901 | }; | ||
902 | |||
903 | static int __init cygnus_gpio_init(void) | ||
904 | { | ||
905 | return platform_driver_probe(&cygnus_gpio_driver, cygnus_gpio_probe); | ||
906 | } | ||
907 | arch_initcall_sync(cygnus_gpio_init); | ||