Commit | Line | Data |
---|---|---|
cf0936b0 HM |
1 | /* |
2 | * Broadcom specific AMBA | |
3 | * GPIO driver | |
4 | * | |
5 | * Copyright 2011, Broadcom Corporation | |
6 | * Copyright 2012, Hauke Mehrtens <hauke@hauke-m.de> | |
7 | * | |
8 | * Licensed under the GNU/GPL. See COPYING for details. | |
9 | */ | |
10 | ||
11 | #include <linux/gpio.h> | |
2997609e RM |
12 | #include <linux/irq.h> |
13 | #include <linux/interrupt.h> | |
14 | #include <linux/irqdomain.h> | |
cf0936b0 HM |
15 | #include <linux/export.h> |
16 | #include <linux/bcma/bcma.h> | |
17 | ||
18 | #include "bcma_private.h" | |
19 | ||
057fcd42 RM |
20 | #define BCMA_GPIO_MAX_PINS 32 |
21 | ||
cf0936b0 HM |
22 | static inline struct bcma_drv_cc *bcma_gpio_get_cc(struct gpio_chip *chip) |
23 | { | |
24 | return container_of(chip, struct bcma_drv_cc, gpio); | |
25 | } | |
26 | ||
27 | static int bcma_gpio_get_value(struct gpio_chip *chip, unsigned gpio) | |
28 | { | |
29 | struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); | |
30 | ||
31 | return !!bcma_chipco_gpio_in(cc, 1 << gpio); | |
32 | } | |
33 | ||
34 | static void bcma_gpio_set_value(struct gpio_chip *chip, unsigned gpio, | |
35 | int value) | |
36 | { | |
37 | struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); | |
38 | ||
39 | bcma_chipco_gpio_out(cc, 1 << gpio, value ? 1 << gpio : 0); | |
40 | } | |
41 | ||
42 | static int bcma_gpio_direction_input(struct gpio_chip *chip, unsigned gpio) | |
43 | { | |
44 | struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); | |
45 | ||
46 | bcma_chipco_gpio_outen(cc, 1 << gpio, 0); | |
47 | return 0; | |
48 | } | |
49 | ||
50 | static int bcma_gpio_direction_output(struct gpio_chip *chip, unsigned gpio, | |
51 | int value) | |
52 | { | |
53 | struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); | |
54 | ||
55 | bcma_chipco_gpio_outen(cc, 1 << gpio, 1 << gpio); | |
56 | bcma_chipco_gpio_out(cc, 1 << gpio, value ? 1 << gpio : 0); | |
57 | return 0; | |
58 | } | |
59 | ||
60 | static int bcma_gpio_request(struct gpio_chip *chip, unsigned gpio) | |
61 | { | |
62 | struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); | |
63 | ||
64 | bcma_chipco_gpio_control(cc, 1 << gpio, 0); | |
65 | /* clear pulldown */ | |
66 | bcma_chipco_gpio_pulldown(cc, 1 << gpio, 0); | |
67 | /* Set pullup */ | |
68 | bcma_chipco_gpio_pullup(cc, 1 << gpio, 1 << gpio); | |
69 | ||
70 | return 0; | |
71 | } | |
72 | ||
73 | static void bcma_gpio_free(struct gpio_chip *chip, unsigned gpio) | |
74 | { | |
75 | struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); | |
76 | ||
77 | /* clear pullup */ | |
78 | bcma_chipco_gpio_pullup(cc, 1 << gpio, 0); | |
79 | } | |
80 | ||
0cbfc065 | 81 | #if IS_BUILTIN(CONFIG_BCM47XX) || IS_BUILTIN(CONFIG_ARCH_BCM_5301X) |
8f1ca268 HM |
82 | static int bcma_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) |
83 | { | |
84 | struct bcma_drv_cc *cc = bcma_gpio_get_cc(chip); | |
85 | ||
86 | if (cc->core->bus->hosttype == BCMA_HOSTTYPE_SOC) | |
2997609e | 87 | return irq_find_mapping(cc->irq_domain, gpio); |
8f1ca268 HM |
88 | else |
89 | return -EINVAL; | |
90 | } | |
91 | ||
2997609e RM |
92 | static void bcma_gpio_irq_unmask(struct irq_data *d) |
93 | { | |
94 | struct bcma_drv_cc *cc = irq_data_get_irq_chip_data(d); | |
95 | int gpio = irqd_to_hwirq(d); | |
978e55d2 | 96 | u32 val = bcma_chipco_gpio_in(cc, BIT(gpio)); |
2997609e | 97 | |
978e55d2 | 98 | bcma_chipco_gpio_polarity(cc, BIT(gpio), val); |
2997609e RM |
99 | bcma_chipco_gpio_intmask(cc, BIT(gpio), BIT(gpio)); |
100 | } | |
101 | ||
102 | static void bcma_gpio_irq_mask(struct irq_data *d) | |
103 | { | |
104 | struct bcma_drv_cc *cc = irq_data_get_irq_chip_data(d); | |
105 | int gpio = irqd_to_hwirq(d); | |
106 | ||
107 | bcma_chipco_gpio_intmask(cc, BIT(gpio), 0); | |
108 | } | |
109 | ||
110 | static struct irq_chip bcma_gpio_irq_chip = { | |
111 | .name = "BCMA-GPIO", | |
112 | .irq_mask = bcma_gpio_irq_mask, | |
113 | .irq_unmask = bcma_gpio_irq_unmask, | |
114 | }; | |
115 | ||
116 | static irqreturn_t bcma_gpio_irq_handler(int irq, void *dev_id) | |
117 | { | |
118 | struct bcma_drv_cc *cc = dev_id; | |
119 | u32 val = bcma_cc_read32(cc, BCMA_CC_GPIOIN); | |
120 | u32 mask = bcma_cc_read32(cc, BCMA_CC_GPIOIRQ); | |
121 | u32 pol = bcma_cc_read32(cc, BCMA_CC_GPIOPOL); | |
d5ab2adb | 122 | unsigned long irqs = (val ^ pol) & mask; |
2997609e RM |
123 | int gpio; |
124 | ||
125 | if (!irqs) | |
126 | return IRQ_NONE; | |
127 | ||
d5ab2adb | 128 | for_each_set_bit(gpio, &irqs, cc->gpio.ngpio) |
2997609e RM |
129 | generic_handle_irq(bcma_gpio_to_irq(&cc->gpio, gpio)); |
130 | bcma_chipco_gpio_polarity(cc, irqs, val & irqs); | |
131 | ||
132 | return IRQ_HANDLED; | |
133 | } | |
134 | ||
135 | static int bcma_gpio_irq_domain_init(struct bcma_drv_cc *cc) | |
136 | { | |
137 | struct gpio_chip *chip = &cc->gpio; | |
138 | int gpio, hwirq, err; | |
139 | ||
140 | if (cc->core->bus->hosttype != BCMA_HOSTTYPE_SOC) | |
141 | return 0; | |
142 | ||
143 | cc->irq_domain = irq_domain_add_linear(NULL, chip->ngpio, | |
144 | &irq_domain_simple_ops, cc); | |
145 | if (!cc->irq_domain) { | |
146 | err = -ENODEV; | |
147 | goto err_irq_domain; | |
148 | } | |
149 | for (gpio = 0; gpio < chip->ngpio; gpio++) { | |
150 | int irq = irq_create_mapping(cc->irq_domain, gpio); | |
151 | ||
152 | irq_set_chip_data(irq, cc); | |
153 | irq_set_chip_and_handler(irq, &bcma_gpio_irq_chip, | |
154 | handle_simple_irq); | |
155 | } | |
156 | ||
85eb92e8 | 157 | hwirq = bcma_core_irq(cc->core, 0); |
2997609e RM |
158 | err = request_irq(hwirq, bcma_gpio_irq_handler, IRQF_SHARED, "gpio", |
159 | cc); | |
160 | if (err) | |
161 | goto err_req_irq; | |
162 | ||
978e55d2 | 163 | bcma_chipco_gpio_intmask(cc, ~0, 0); |
2997609e RM |
164 | bcma_cc_set32(cc, BCMA_CC_IRQMASK, BCMA_CC_IRQ_GPIO); |
165 | ||
166 | return 0; | |
167 | ||
168 | err_req_irq: | |
169 | for (gpio = 0; gpio < chip->ngpio; gpio++) { | |
170 | int irq = irq_find_mapping(cc->irq_domain, gpio); | |
171 | ||
172 | irq_dispose_mapping(irq); | |
173 | } | |
174 | irq_domain_remove(cc->irq_domain); | |
175 | err_irq_domain: | |
176 | return err; | |
177 | } | |
178 | ||
179 | static void bcma_gpio_irq_domain_exit(struct bcma_drv_cc *cc) | |
180 | { | |
181 | struct gpio_chip *chip = &cc->gpio; | |
182 | int gpio; | |
183 | ||
184 | if (cc->core->bus->hosttype != BCMA_HOSTTYPE_SOC) | |
185 | return; | |
186 | ||
187 | bcma_cc_mask32(cc, BCMA_CC_IRQMASK, ~BCMA_CC_IRQ_GPIO); | |
85eb92e8 | 188 | free_irq(bcma_core_irq(cc->core, 0), cc); |
2997609e RM |
189 | for (gpio = 0; gpio < chip->ngpio; gpio++) { |
190 | int irq = irq_find_mapping(cc->irq_domain, gpio); | |
191 | ||
192 | irq_dispose_mapping(irq); | |
193 | } | |
194 | irq_domain_remove(cc->irq_domain); | |
195 | } | |
196 | #else | |
197 | static int bcma_gpio_irq_domain_init(struct bcma_drv_cc *cc) | |
198 | { | |
199 | return 0; | |
200 | } | |
201 | ||
202 | static void bcma_gpio_irq_domain_exit(struct bcma_drv_cc *cc) | |
203 | { | |
204 | } | |
205 | #endif | |
206 | ||
cf0936b0 HM |
207 | int bcma_gpio_init(struct bcma_drv_cc *cc) |
208 | { | |
057fcd42 | 209 | struct bcma_bus *bus = cc->core->bus; |
cf0936b0 | 210 | struct gpio_chip *chip = &cc->gpio; |
2997609e | 211 | int err; |
cf0936b0 HM |
212 | |
213 | chip->label = "bcma_gpio"; | |
214 | chip->owner = THIS_MODULE; | |
215 | chip->request = bcma_gpio_request; | |
216 | chip->free = bcma_gpio_free; | |
217 | chip->get = bcma_gpio_get_value; | |
218 | chip->set = bcma_gpio_set_value; | |
219 | chip->direction_input = bcma_gpio_direction_input; | |
220 | chip->direction_output = bcma_gpio_direction_output; | |
0cbfc065 | 221 | #if IS_BUILTIN(CONFIG_BCM47XX) || IS_BUILTIN(CONFIG_ARCH_BCM_5301X) |
8f1ca268 | 222 | chip->to_irq = bcma_gpio_to_irq; |
a0196d11 RM |
223 | #endif |
224 | #if IS_BUILTIN(CONFIG_OF) | |
225 | if (cc->core->bus->hosttype == BCMA_HOSTTYPE_SOC) | |
226 | chip->of_node = cc->core->dev.of_node; | |
2997609e | 227 | #endif |
057fcd42 | 228 | switch (bus->chipinfo.id) { |
0f8ca014 | 229 | case BCMA_CHIP_ID_BCM5357: |
f0db59e1 | 230 | case BCMA_CHIP_ID_BCM53572: |
0f8ca014 RM |
231 | chip->ngpio = 32; |
232 | break; | |
233 | default: | |
234 | chip->ngpio = 16; | |
235 | } | |
236 | ||
057fcd42 | 237 | /* |
2d57b712 FF |
238 | * Register SoC GPIO devices with absolute GPIO pin base. |
239 | * On MIPS, we don't have Device Tree and we can't use relative (per chip) | |
240 | * GPIO numbers. | |
241 | * On some ARM devices, user space may want to access some system GPIO | |
242 | * pins directly, which is easier to do with a predictable GPIO base. | |
057fcd42 | 243 | */ |
2d57b712 FF |
244 | if (IS_BUILTIN(CONFIG_BCM47XX) || |
245 | cc->core->bus->hosttype == BCMA_HOSTTYPE_SOC) | |
246 | chip->base = bus->num * BCMA_GPIO_MAX_PINS; | |
247 | else | |
248 | chip->base = -1; | |
cf0936b0 | 249 | |
2997609e RM |
250 | err = bcma_gpio_irq_domain_init(cc); |
251 | if (err) | |
252 | return err; | |
253 | ||
254 | err = gpiochip_add(chip); | |
255 | if (err) { | |
256 | bcma_gpio_irq_domain_exit(cc); | |
257 | return err; | |
258 | } | |
259 | ||
260 | return 0; | |
cf0936b0 | 261 | } |
c50ae947 HM |
262 | |
263 | int bcma_gpio_unregister(struct bcma_drv_cc *cc) | |
264 | { | |
2997609e | 265 | bcma_gpio_irq_domain_exit(cc); |
88d5e520 | 266 | gpiochip_remove(&cc->gpio); |
267 | return 0; | |
c50ae947 | 268 | } |