diff options
author | David Daney <david.daney@cavium.com> | 2017-08-17 20:53:35 -0400 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2017-08-22 08:28:22 -0400 |
commit | 5a2a30024d8c04cf638be05508e655b66f1c6103 (patch) | |
tree | c39600a7f52f310943859de8432b650590e6b2e7 | |
parent | b4c495f03ae3d332e3304eb62b4748b31f1170dd (diff) |
gpio: Add gpio driver support for ThunderX and OCTEON-TX
Cavium ThunderX and OCTEON-TX are arm64 based SoCs. Add driver for
the on-chip GPIO pins.
Signed-off-by: David Daney <david.daney@cavium.com>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
-rw-r--r-- | drivers/gpio/Kconfig | 9 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/gpio-thunderx.c | 639 |
3 files changed, 649 insertions, 0 deletions
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index fc9b75c9e3ba..3388d54ba114 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -450,6 +450,15 @@ config GPIO_TS4800 | |||
450 | help | 450 | help |
451 | This driver support TS-4800 FPGA GPIO controllers. | 451 | This driver support TS-4800 FPGA GPIO controllers. |
452 | 452 | ||
453 | config GPIO_THUNDERX | ||
454 | tristate "Cavium ThunderX/OCTEON-TX GPIO" | ||
455 | depends on ARCH_THUNDER || (64BIT && COMPILE_TEST) | ||
456 | depends on PCI_MSI && IRQ_DOMAIN_HIERARCHY | ||
457 | select IRQ_FASTEOI_HIERARCHY_HANDLERS | ||
458 | help | ||
459 | Say yes here to support the on-chip GPIO lines on the ThunderX | ||
460 | and OCTEON-TX families of SoCs. | ||
461 | |||
453 | config GPIO_TZ1090 | 462 | config GPIO_TZ1090 |
454 | bool "Toumaz Xenif TZ1090 GPIO support" | 463 | bool "Toumaz Xenif TZ1090 GPIO support" |
455 | depends on SOC_TZ1090 | 464 | depends on SOC_TZ1090 |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 37f2029218cb..aeb70e9de6f2 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -113,6 +113,7 @@ obj-$(CONFIG_GPIO_SYSCON) += gpio-syscon.o | |||
113 | obj-$(CONFIG_GPIO_TB10X) += gpio-tb10x.o | 113 | obj-$(CONFIG_GPIO_TB10X) += gpio-tb10x.o |
114 | obj-$(CONFIG_GPIO_TC3589X) += gpio-tc3589x.o | 114 | obj-$(CONFIG_GPIO_TC3589X) += gpio-tc3589x.o |
115 | obj-$(CONFIG_GPIO_TEGRA) += gpio-tegra.o | 115 | obj-$(CONFIG_GPIO_TEGRA) += gpio-tegra.o |
116 | obj-$(CONFIG_GPIO_THUNDERX) += gpio-thunderx.o | ||
116 | obj-$(CONFIG_GPIO_TIMBERDALE) += gpio-timberdale.o | 117 | obj-$(CONFIG_GPIO_TIMBERDALE) += gpio-timberdale.o |
117 | obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o | 118 | obj-$(CONFIG_GPIO_PALMAS) += gpio-palmas.o |
118 | obj-$(CONFIG_GPIO_TPIC2810) += gpio-tpic2810.o | 119 | obj-$(CONFIG_GPIO_TPIC2810) += gpio-tpic2810.o |
diff --git a/drivers/gpio/gpio-thunderx.c b/drivers/gpio/gpio-thunderx.c new file mode 100644 index 000000000000..57efb251f9c4 --- /dev/null +++ b/drivers/gpio/gpio-thunderx.c | |||
@@ -0,0 +1,639 @@ | |||
1 | /* | ||
2 | * This file is subject to the terms and conditions of the GNU General Public | ||
3 | * License. See the file "COPYING" in the main directory of this archive | ||
4 | * for more details. | ||
5 | * | ||
6 | * Copyright (C) 2016, 2017 Cavium Inc. | ||
7 | */ | ||
8 | |||
9 | #include <linux/bitops.h> | ||
10 | #include <linux/gpio/driver.h> | ||
11 | #include <linux/interrupt.h> | ||
12 | #include <linux/io.h> | ||
13 | #include <linux/irq.h> | ||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/pci.h> | ||
17 | #include <linux/spinlock.h> | ||
18 | |||
19 | |||
20 | #define GPIO_RX_DAT 0x0 | ||
21 | #define GPIO_TX_SET 0x8 | ||
22 | #define GPIO_TX_CLR 0x10 | ||
23 | #define GPIO_CONST 0x90 | ||
24 | #define GPIO_CONST_GPIOS_MASK 0xff | ||
25 | #define GPIO_BIT_CFG 0x400 | ||
26 | #define GPIO_BIT_CFG_TX_OE BIT(0) | ||
27 | #define GPIO_BIT_CFG_PIN_XOR BIT(1) | ||
28 | #define GPIO_BIT_CFG_INT_EN BIT(2) | ||
29 | #define GPIO_BIT_CFG_INT_TYPE BIT(3) | ||
30 | #define GPIO_BIT_CFG_FIL_MASK GENMASK(11, 4) | ||
31 | #define GPIO_BIT_CFG_FIL_CNT_SHIFT 4 | ||
32 | #define GPIO_BIT_CFG_FIL_SEL_SHIFT 8 | ||
33 | #define GPIO_BIT_CFG_TX_OD BIT(12) | ||
34 | #define GPIO_BIT_CFG_PIN_SEL_MASK GENMASK(25, 16) | ||
35 | #define GPIO_INTR 0x800 | ||
36 | #define GPIO_INTR_INTR BIT(0) | ||
37 | #define GPIO_INTR_INTR_W1S BIT(1) | ||
38 | #define GPIO_INTR_ENA_W1C BIT(2) | ||
39 | #define GPIO_INTR_ENA_W1S BIT(3) | ||
40 | #define GPIO_2ND_BANK 0x1400 | ||
41 | |||
42 | #define GLITCH_FILTER_400NS ((4u << GPIO_BIT_CFG_FIL_SEL_SHIFT) | \ | ||
43 | (9u << GPIO_BIT_CFG_FIL_CNT_SHIFT)) | ||
44 | |||
45 | struct thunderx_gpio; | ||
46 | |||
47 | struct thunderx_line { | ||
48 | struct thunderx_gpio *txgpio; | ||
49 | unsigned int line; | ||
50 | unsigned int fil_bits; | ||
51 | }; | ||
52 | |||
53 | struct thunderx_gpio { | ||
54 | struct gpio_chip chip; | ||
55 | u8 __iomem *register_base; | ||
56 | struct irq_domain *irqd; | ||
57 | struct msix_entry *msix_entries; /* per line MSI-X */ | ||
58 | struct thunderx_line *line_entries; /* per line irq info */ | ||
59 | raw_spinlock_t lock; | ||
60 | unsigned long invert_mask[2]; | ||
61 | unsigned long od_mask[2]; | ||
62 | int base_msi; | ||
63 | }; | ||
64 | |||
65 | static unsigned int bit_cfg_reg(unsigned int line) | ||
66 | { | ||
67 | return 8 * line + GPIO_BIT_CFG; | ||
68 | } | ||
69 | |||
70 | static unsigned int intr_reg(unsigned int line) | ||
71 | { | ||
72 | return 8 * line + GPIO_INTR; | ||
73 | } | ||
74 | |||
75 | static bool thunderx_gpio_is_gpio_nowarn(struct thunderx_gpio *txgpio, | ||
76 | unsigned int line) | ||
77 | { | ||
78 | u64 bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line)); | ||
79 | |||
80 | return (bit_cfg & GPIO_BIT_CFG_PIN_SEL_MASK) == 0; | ||
81 | } | ||
82 | |||
83 | /* | ||
84 | * Check (and WARN) that the pin is available for GPIO. We will not | ||
85 | * allow modification of the state of non-GPIO pins from this driver. | ||
86 | */ | ||
87 | static bool thunderx_gpio_is_gpio(struct thunderx_gpio *txgpio, | ||
88 | unsigned int line) | ||
89 | { | ||
90 | bool rv = thunderx_gpio_is_gpio_nowarn(txgpio, line); | ||
91 | |||
92 | WARN_RATELIMIT(!rv, "Pin %d not available for GPIO\n", line); | ||
93 | |||
94 | return rv; | ||
95 | } | ||
96 | |||
97 | static int thunderx_gpio_request(struct gpio_chip *chip, unsigned int line) | ||
98 | { | ||
99 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
100 | |||
101 | return thunderx_gpio_is_gpio(txgpio, line) ? 0 : -EIO; | ||
102 | } | ||
103 | |||
104 | static int thunderx_gpio_dir_in(struct gpio_chip *chip, unsigned int line) | ||
105 | { | ||
106 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
107 | |||
108 | if (!thunderx_gpio_is_gpio(txgpio, line)) | ||
109 | return -EIO; | ||
110 | |||
111 | raw_spin_lock(&txgpio->lock); | ||
112 | clear_bit(line, txgpio->invert_mask); | ||
113 | clear_bit(line, txgpio->od_mask); | ||
114 | writeq(txgpio->line_entries[line].fil_bits, | ||
115 | txgpio->register_base + bit_cfg_reg(line)); | ||
116 | raw_spin_unlock(&txgpio->lock); | ||
117 | return 0; | ||
118 | } | ||
119 | |||
120 | static void thunderx_gpio_set(struct gpio_chip *chip, unsigned int line, | ||
121 | int value) | ||
122 | { | ||
123 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
124 | int bank = line / 64; | ||
125 | int bank_bit = line % 64; | ||
126 | |||
127 | void __iomem *reg = txgpio->register_base + | ||
128 | (bank * GPIO_2ND_BANK) + (value ? GPIO_TX_SET : GPIO_TX_CLR); | ||
129 | |||
130 | writeq(BIT_ULL(bank_bit), reg); | ||
131 | } | ||
132 | |||
133 | static int thunderx_gpio_dir_out(struct gpio_chip *chip, unsigned int line, | ||
134 | int value) | ||
135 | { | ||
136 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
137 | u64 bit_cfg = txgpio->line_entries[line].fil_bits | GPIO_BIT_CFG_TX_OE; | ||
138 | |||
139 | if (!thunderx_gpio_is_gpio(txgpio, line)) | ||
140 | return -EIO; | ||
141 | |||
142 | raw_spin_lock(&txgpio->lock); | ||
143 | |||
144 | thunderx_gpio_set(chip, line, value); | ||
145 | |||
146 | if (test_bit(line, txgpio->invert_mask)) | ||
147 | bit_cfg |= GPIO_BIT_CFG_PIN_XOR; | ||
148 | |||
149 | if (test_bit(line, txgpio->od_mask)) | ||
150 | bit_cfg |= GPIO_BIT_CFG_TX_OD; | ||
151 | |||
152 | writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(line)); | ||
153 | |||
154 | raw_spin_unlock(&txgpio->lock); | ||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | static int thunderx_gpio_get_direction(struct gpio_chip *chip, unsigned int line) | ||
159 | { | ||
160 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
161 | u64 bit_cfg; | ||
162 | |||
163 | if (!thunderx_gpio_is_gpio_nowarn(txgpio, line)) | ||
164 | /* | ||
165 | * Say it is input for now to avoid WARNing on | ||
166 | * gpiochip_add_data(). We will WARN if someone | ||
167 | * requests it or tries to use it. | ||
168 | */ | ||
169 | return 1; | ||
170 | |||
171 | bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line)); | ||
172 | |||
173 | return !(bit_cfg & GPIO_BIT_CFG_TX_OE); | ||
174 | } | ||
175 | |||
176 | static int thunderx_gpio_set_config(struct gpio_chip *chip, | ||
177 | unsigned int line, | ||
178 | unsigned long cfg) | ||
179 | { | ||
180 | bool orig_invert, orig_od, orig_dat, new_invert, new_od; | ||
181 | u32 arg, sel; | ||
182 | u64 bit_cfg; | ||
183 | int bank = line / 64; | ||
184 | int bank_bit = line % 64; | ||
185 | int ret = -ENOTSUPP; | ||
186 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
187 | void __iomem *reg = txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_SET; | ||
188 | |||
189 | if (!thunderx_gpio_is_gpio(txgpio, line)) | ||
190 | return -EIO; | ||
191 | |||
192 | raw_spin_lock(&txgpio->lock); | ||
193 | orig_invert = test_bit(line, txgpio->invert_mask); | ||
194 | new_invert = orig_invert; | ||
195 | orig_od = test_bit(line, txgpio->od_mask); | ||
196 | new_od = orig_od; | ||
197 | orig_dat = ((readq(reg) >> bank_bit) & 1) ^ orig_invert; | ||
198 | bit_cfg = readq(txgpio->register_base + bit_cfg_reg(line)); | ||
199 | switch (pinconf_to_config_param(cfg)) { | ||
200 | case PIN_CONFIG_DRIVE_OPEN_DRAIN: | ||
201 | /* | ||
202 | * Weird, setting open-drain mode causes signal | ||
203 | * inversion. Note this so we can compensate in the | ||
204 | * dir_out function. | ||
205 | */ | ||
206 | set_bit(line, txgpio->invert_mask); | ||
207 | new_invert = true; | ||
208 | set_bit(line, txgpio->od_mask); | ||
209 | new_od = true; | ||
210 | ret = 0; | ||
211 | break; | ||
212 | case PIN_CONFIG_DRIVE_PUSH_PULL: | ||
213 | clear_bit(line, txgpio->invert_mask); | ||
214 | new_invert = false; | ||
215 | clear_bit(line, txgpio->od_mask); | ||
216 | new_od = false; | ||
217 | ret = 0; | ||
218 | break; | ||
219 | case PIN_CONFIG_INPUT_DEBOUNCE: | ||
220 | arg = pinconf_to_config_argument(cfg); | ||
221 | if (arg > 1228) { /* 15 * 2^15 * 2.5nS maximum */ | ||
222 | ret = -EINVAL; | ||
223 | break; | ||
224 | } | ||
225 | arg *= 400; /* scale to 2.5nS clocks. */ | ||
226 | sel = 0; | ||
227 | while (arg > 15) { | ||
228 | sel++; | ||
229 | arg++; /* always round up */ | ||
230 | arg >>= 1; | ||
231 | } | ||
232 | txgpio->line_entries[line].fil_bits = | ||
233 | (sel << GPIO_BIT_CFG_FIL_SEL_SHIFT) | | ||
234 | (arg << GPIO_BIT_CFG_FIL_CNT_SHIFT); | ||
235 | bit_cfg &= ~GPIO_BIT_CFG_FIL_MASK; | ||
236 | bit_cfg |= txgpio->line_entries[line].fil_bits; | ||
237 | writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(line)); | ||
238 | ret = 0; | ||
239 | break; | ||
240 | default: | ||
241 | break; | ||
242 | } | ||
243 | raw_spin_unlock(&txgpio->lock); | ||
244 | |||
245 | /* | ||
246 | * If currently output and OPEN_DRAIN changed, install the new | ||
247 | * settings | ||
248 | */ | ||
249 | if ((new_invert != orig_invert || new_od != orig_od) && | ||
250 | (bit_cfg & GPIO_BIT_CFG_TX_OE)) | ||
251 | ret = thunderx_gpio_dir_out(chip, line, orig_dat ^ new_invert); | ||
252 | |||
253 | return ret; | ||
254 | } | ||
255 | |||
256 | static int thunderx_gpio_get(struct gpio_chip *chip, unsigned int line) | ||
257 | { | ||
258 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
259 | int bank = line / 64; | ||
260 | int bank_bit = line % 64; | ||
261 | u64 read_bits = readq(txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_RX_DAT); | ||
262 | u64 masked_bits = read_bits & BIT_ULL(bank_bit); | ||
263 | |||
264 | if (test_bit(line, txgpio->invert_mask)) | ||
265 | return masked_bits == 0; | ||
266 | else | ||
267 | return masked_bits != 0; | ||
268 | } | ||
269 | |||
270 | static void thunderx_gpio_set_multiple(struct gpio_chip *chip, | ||
271 | unsigned long *mask, | ||
272 | unsigned long *bits) | ||
273 | { | ||
274 | int bank; | ||
275 | u64 set_bits, clear_bits; | ||
276 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
277 | |||
278 | for (bank = 0; bank <= chip->ngpio / 64; bank++) { | ||
279 | set_bits = bits[bank] & mask[bank]; | ||
280 | clear_bits = ~bits[bank] & mask[bank]; | ||
281 | writeq(set_bits, txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_SET); | ||
282 | writeq(clear_bits, txgpio->register_base + (bank * GPIO_2ND_BANK) + GPIO_TX_CLR); | ||
283 | } | ||
284 | } | ||
285 | |||
286 | static void thunderx_gpio_irq_ack(struct irq_data *data) | ||
287 | { | ||
288 | struct thunderx_line *txline = irq_data_get_irq_chip_data(data); | ||
289 | |||
290 | writeq(GPIO_INTR_INTR, | ||
291 | txline->txgpio->register_base + intr_reg(txline->line)); | ||
292 | } | ||
293 | |||
294 | static void thunderx_gpio_irq_mask(struct irq_data *data) | ||
295 | { | ||
296 | struct thunderx_line *txline = irq_data_get_irq_chip_data(data); | ||
297 | |||
298 | writeq(GPIO_INTR_ENA_W1C, | ||
299 | txline->txgpio->register_base + intr_reg(txline->line)); | ||
300 | } | ||
301 | |||
302 | static void thunderx_gpio_irq_mask_ack(struct irq_data *data) | ||
303 | { | ||
304 | struct thunderx_line *txline = irq_data_get_irq_chip_data(data); | ||
305 | |||
306 | writeq(GPIO_INTR_ENA_W1C | GPIO_INTR_INTR, | ||
307 | txline->txgpio->register_base + intr_reg(txline->line)); | ||
308 | } | ||
309 | |||
310 | static void thunderx_gpio_irq_unmask(struct irq_data *data) | ||
311 | { | ||
312 | struct thunderx_line *txline = irq_data_get_irq_chip_data(data); | ||
313 | |||
314 | writeq(GPIO_INTR_ENA_W1S, | ||
315 | txline->txgpio->register_base + intr_reg(txline->line)); | ||
316 | } | ||
317 | |||
318 | static int thunderx_gpio_irq_set_type(struct irq_data *data, | ||
319 | unsigned int flow_type) | ||
320 | { | ||
321 | struct thunderx_line *txline = irq_data_get_irq_chip_data(data); | ||
322 | struct thunderx_gpio *txgpio = txline->txgpio; | ||
323 | u64 bit_cfg; | ||
324 | |||
325 | irqd_set_trigger_type(data, flow_type); | ||
326 | |||
327 | bit_cfg = txline->fil_bits | GPIO_BIT_CFG_INT_EN; | ||
328 | |||
329 | if (flow_type & IRQ_TYPE_EDGE_BOTH) { | ||
330 | irq_set_handler_locked(data, handle_fasteoi_ack_irq); | ||
331 | bit_cfg |= GPIO_BIT_CFG_INT_TYPE; | ||
332 | } else { | ||
333 | irq_set_handler_locked(data, handle_fasteoi_mask_irq); | ||
334 | } | ||
335 | |||
336 | raw_spin_lock(&txgpio->lock); | ||
337 | if (flow_type & (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_LEVEL_LOW)) { | ||
338 | bit_cfg |= GPIO_BIT_CFG_PIN_XOR; | ||
339 | set_bit(txline->line, txgpio->invert_mask); | ||
340 | } else { | ||
341 | clear_bit(txline->line, txgpio->invert_mask); | ||
342 | } | ||
343 | clear_bit(txline->line, txgpio->od_mask); | ||
344 | writeq(bit_cfg, txgpio->register_base + bit_cfg_reg(txline->line)); | ||
345 | raw_spin_unlock(&txgpio->lock); | ||
346 | |||
347 | return IRQ_SET_MASK_OK; | ||
348 | } | ||
349 | |||
350 | static void thunderx_gpio_irq_enable(struct irq_data *data) | ||
351 | { | ||
352 | irq_chip_enable_parent(data); | ||
353 | thunderx_gpio_irq_unmask(data); | ||
354 | } | ||
355 | |||
356 | static void thunderx_gpio_irq_disable(struct irq_data *data) | ||
357 | { | ||
358 | thunderx_gpio_irq_mask(data); | ||
359 | irq_chip_disable_parent(data); | ||
360 | } | ||
361 | |||
362 | static int thunderx_gpio_irq_request_resources(struct irq_data *data) | ||
363 | { | ||
364 | struct thunderx_line *txline = irq_data_get_irq_chip_data(data); | ||
365 | struct thunderx_gpio *txgpio = txline->txgpio; | ||
366 | struct irq_data *parent_data = data->parent_data; | ||
367 | int r; | ||
368 | |||
369 | r = gpiochip_lock_as_irq(&txgpio->chip, txline->line); | ||
370 | if (r) | ||
371 | return r; | ||
372 | |||
373 | if (parent_data && parent_data->chip->irq_request_resources) { | ||
374 | r = parent_data->chip->irq_request_resources(parent_data); | ||
375 | if (r) | ||
376 | goto error; | ||
377 | } | ||
378 | |||
379 | return 0; | ||
380 | error: | ||
381 | gpiochip_unlock_as_irq(&txgpio->chip, txline->line); | ||
382 | return r; | ||
383 | } | ||
384 | |||
385 | static void thunderx_gpio_irq_release_resources(struct irq_data *data) | ||
386 | { | ||
387 | struct thunderx_line *txline = irq_data_get_irq_chip_data(data); | ||
388 | struct thunderx_gpio *txgpio = txline->txgpio; | ||
389 | struct irq_data *parent_data = data->parent_data; | ||
390 | |||
391 | if (parent_data && parent_data->chip->irq_release_resources) | ||
392 | parent_data->chip->irq_release_resources(parent_data); | ||
393 | |||
394 | gpiochip_unlock_as_irq(&txgpio->chip, txline->line); | ||
395 | } | ||
396 | |||
397 | /* | ||
398 | * Interrupts are chained from underlying MSI-X vectors. We have | ||
399 | * these irq_chip functions to be able to handle level triggering | ||
400 | * semantics and other acknowledgment tasks associated with the GPIO | ||
401 | * mechanism. | ||
402 | */ | ||
403 | static struct irq_chip thunderx_gpio_irq_chip = { | ||
404 | .name = "GPIO", | ||
405 | .irq_enable = thunderx_gpio_irq_enable, | ||
406 | .irq_disable = thunderx_gpio_irq_disable, | ||
407 | .irq_ack = thunderx_gpio_irq_ack, | ||
408 | .irq_mask = thunderx_gpio_irq_mask, | ||
409 | .irq_mask_ack = thunderx_gpio_irq_mask_ack, | ||
410 | .irq_unmask = thunderx_gpio_irq_unmask, | ||
411 | .irq_eoi = irq_chip_eoi_parent, | ||
412 | .irq_set_affinity = irq_chip_set_affinity_parent, | ||
413 | .irq_request_resources = thunderx_gpio_irq_request_resources, | ||
414 | .irq_release_resources = thunderx_gpio_irq_release_resources, | ||
415 | .irq_set_type = thunderx_gpio_irq_set_type, | ||
416 | |||
417 | .flags = IRQCHIP_SET_TYPE_MASKED | ||
418 | }; | ||
419 | |||
420 | static int thunderx_gpio_irq_map(struct irq_domain *d, unsigned int irq, | ||
421 | irq_hw_number_t hwirq) | ||
422 | { | ||
423 | struct thunderx_gpio *txgpio = d->host_data; | ||
424 | |||
425 | if (hwirq >= txgpio->chip.ngpio) | ||
426 | return -EINVAL; | ||
427 | if (!thunderx_gpio_is_gpio_nowarn(txgpio, hwirq)) | ||
428 | return -EPERM; | ||
429 | return 0; | ||
430 | } | ||
431 | |||
432 | static int thunderx_gpio_irq_translate(struct irq_domain *d, | ||
433 | struct irq_fwspec *fwspec, | ||
434 | irq_hw_number_t *hwirq, | ||
435 | unsigned int *type) | ||
436 | { | ||
437 | struct thunderx_gpio *txgpio = d->host_data; | ||
438 | |||
439 | if (WARN_ON(fwspec->param_count < 2)) | ||
440 | return -EINVAL; | ||
441 | if (fwspec->param[0] >= txgpio->chip.ngpio) | ||
442 | return -EINVAL; | ||
443 | *hwirq = fwspec->param[0]; | ||
444 | *type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK; | ||
445 | return 0; | ||
446 | } | ||
447 | |||
448 | static int thunderx_gpio_irq_alloc(struct irq_domain *d, unsigned int virq, | ||
449 | unsigned int nr_irqs, void *arg) | ||
450 | { | ||
451 | struct thunderx_line *txline = arg; | ||
452 | |||
453 | return irq_domain_set_hwirq_and_chip(d, virq, txline->line, | ||
454 | &thunderx_gpio_irq_chip, txline); | ||
455 | } | ||
456 | |||
457 | static const struct irq_domain_ops thunderx_gpio_irqd_ops = { | ||
458 | .map = thunderx_gpio_irq_map, | ||
459 | .alloc = thunderx_gpio_irq_alloc, | ||
460 | .translate = thunderx_gpio_irq_translate | ||
461 | }; | ||
462 | |||
463 | static int thunderx_gpio_to_irq(struct gpio_chip *chip, unsigned int offset) | ||
464 | { | ||
465 | struct thunderx_gpio *txgpio = gpiochip_get_data(chip); | ||
466 | |||
467 | return irq_find_mapping(txgpio->irqd, offset); | ||
468 | } | ||
469 | |||
470 | static int thunderx_gpio_probe(struct pci_dev *pdev, | ||
471 | const struct pci_device_id *id) | ||
472 | { | ||
473 | void __iomem * const *tbl; | ||
474 | struct device *dev = &pdev->dev; | ||
475 | struct thunderx_gpio *txgpio; | ||
476 | struct gpio_chip *chip; | ||
477 | int ngpio, i; | ||
478 | int err = 0; | ||
479 | |||
480 | txgpio = devm_kzalloc(dev, sizeof(*txgpio), GFP_KERNEL); | ||
481 | if (!txgpio) | ||
482 | return -ENOMEM; | ||
483 | |||
484 | raw_spin_lock_init(&txgpio->lock); | ||
485 | chip = &txgpio->chip; | ||
486 | |||
487 | pci_set_drvdata(pdev, txgpio); | ||
488 | |||
489 | err = pcim_enable_device(pdev); | ||
490 | if (err) { | ||
491 | dev_err(dev, "Failed to enable PCI device: err %d\n", err); | ||
492 | goto out; | ||
493 | } | ||
494 | |||
495 | err = pcim_iomap_regions(pdev, 1 << 0, KBUILD_MODNAME); | ||
496 | if (err) { | ||
497 | dev_err(dev, "Failed to iomap PCI device: err %d\n", err); | ||
498 | goto out; | ||
499 | } | ||
500 | |||
501 | tbl = pcim_iomap_table(pdev); | ||
502 | txgpio->register_base = tbl[0]; | ||
503 | if (!txgpio->register_base) { | ||
504 | dev_err(dev, "Cannot map PCI resource\n"); | ||
505 | err = -ENOMEM; | ||
506 | goto out; | ||
507 | } | ||
508 | |||
509 | if (pdev->subsystem_device == 0xa10a) { | ||
510 | /* CN88XX has no GPIO_CONST register*/ | ||
511 | ngpio = 50; | ||
512 | txgpio->base_msi = 48; | ||
513 | } else { | ||
514 | u64 c = readq(txgpio->register_base + GPIO_CONST); | ||
515 | |||
516 | ngpio = c & GPIO_CONST_GPIOS_MASK; | ||
517 | txgpio->base_msi = (c >> 8) & 0xff; | ||
518 | } | ||
519 | |||
520 | txgpio->msix_entries = devm_kzalloc(dev, | ||
521 | sizeof(struct msix_entry) * ngpio, | ||
522 | GFP_KERNEL); | ||
523 | if (!txgpio->msix_entries) { | ||
524 | err = -ENOMEM; | ||
525 | goto out; | ||
526 | } | ||
527 | |||
528 | txgpio->line_entries = devm_kzalloc(dev, | ||
529 | sizeof(struct thunderx_line) * ngpio, | ||
530 | GFP_KERNEL); | ||
531 | if (!txgpio->line_entries) { | ||
532 | err = -ENOMEM; | ||
533 | goto out; | ||
534 | } | ||
535 | |||
536 | for (i = 0; i < ngpio; i++) { | ||
537 | u64 bit_cfg = readq(txgpio->register_base + bit_cfg_reg(i)); | ||
538 | |||
539 | txgpio->msix_entries[i].entry = txgpio->base_msi + (2 * i); | ||
540 | txgpio->line_entries[i].line = i; | ||
541 | txgpio->line_entries[i].txgpio = txgpio; | ||
542 | /* | ||
543 | * If something has already programmed the pin, use | ||
544 | * the existing glitch filter settings, otherwise go | ||
545 | * to 400nS. | ||
546 | */ | ||
547 | txgpio->line_entries[i].fil_bits = bit_cfg ? | ||
548 | (bit_cfg & GPIO_BIT_CFG_FIL_MASK) : GLITCH_FILTER_400NS; | ||
549 | |||
550 | if ((bit_cfg & GPIO_BIT_CFG_TX_OE) && (bit_cfg & GPIO_BIT_CFG_TX_OD)) | ||
551 | set_bit(i, txgpio->od_mask); | ||
552 | if (bit_cfg & GPIO_BIT_CFG_PIN_XOR) | ||
553 | set_bit(i, txgpio->invert_mask); | ||
554 | } | ||
555 | |||
556 | |||
557 | /* Enable all MSI-X for interrupts on all possible lines. */ | ||
558 | err = pci_enable_msix_range(pdev, txgpio->msix_entries, ngpio, ngpio); | ||
559 | if (err < 0) | ||
560 | goto out; | ||
561 | |||
562 | /* | ||
563 | * Push GPIO specific irqdomain on hierarchy created as a side | ||
564 | * effect of the pci_enable_msix() | ||
565 | */ | ||
566 | txgpio->irqd = irq_domain_create_hierarchy(irq_get_irq_data(txgpio->msix_entries[0].vector)->domain, | ||
567 | 0, 0, of_node_to_fwnode(dev->of_node), | ||
568 | &thunderx_gpio_irqd_ops, txgpio); | ||
569 | if (!txgpio->irqd) | ||
570 | goto out; | ||
571 | |||
572 | /* Push on irq_data and the domain for each line. */ | ||
573 | for (i = 0; i < ngpio; i++) { | ||
574 | err = irq_domain_push_irq(txgpio->irqd, | ||
575 | txgpio->msix_entries[i].vector, | ||
576 | &txgpio->line_entries[i]); | ||
577 | if (err < 0) | ||
578 | dev_err(dev, "irq_domain_push_irq: %d\n", err); | ||
579 | } | ||
580 | |||
581 | chip->label = KBUILD_MODNAME; | ||
582 | chip->parent = dev; | ||
583 | chip->owner = THIS_MODULE; | ||
584 | chip->request = thunderx_gpio_request; | ||
585 | chip->base = -1; /* System allocated */ | ||
586 | chip->can_sleep = false; | ||
587 | chip->ngpio = ngpio; | ||
588 | chip->get_direction = thunderx_gpio_get_direction; | ||
589 | chip->direction_input = thunderx_gpio_dir_in; | ||
590 | chip->get = thunderx_gpio_get; | ||
591 | chip->direction_output = thunderx_gpio_dir_out; | ||
592 | chip->set = thunderx_gpio_set; | ||
593 | chip->set_multiple = thunderx_gpio_set_multiple; | ||
594 | chip->set_config = thunderx_gpio_set_config; | ||
595 | chip->to_irq = thunderx_gpio_to_irq; | ||
596 | err = devm_gpiochip_add_data(dev, chip, txgpio); | ||
597 | if (err) | ||
598 | goto out; | ||
599 | |||
600 | dev_info(dev, "ThunderX GPIO: %d lines with base %d.\n", | ||
601 | ngpio, chip->base); | ||
602 | return 0; | ||
603 | out: | ||
604 | pci_set_drvdata(pdev, NULL); | ||
605 | return err; | ||
606 | } | ||
607 | |||
608 | static void thunderx_gpio_remove(struct pci_dev *pdev) | ||
609 | { | ||
610 | int i; | ||
611 | struct thunderx_gpio *txgpio = pci_get_drvdata(pdev); | ||
612 | |||
613 | for (i = 0; i < txgpio->chip.ngpio; i++) | ||
614 | irq_domain_pop_irq(txgpio->irqd, | ||
615 | txgpio->msix_entries[i].vector); | ||
616 | |||
617 | irq_domain_remove(txgpio->irqd); | ||
618 | |||
619 | pci_set_drvdata(pdev, NULL); | ||
620 | } | ||
621 | |||
622 | static const struct pci_device_id thunderx_gpio_id_table[] = { | ||
623 | { PCI_DEVICE(PCI_VENDOR_ID_CAVIUM, 0xA00A) }, | ||
624 | { 0, } /* end of table */ | ||
625 | }; | ||
626 | |||
627 | MODULE_DEVICE_TABLE(pci, thunderx_gpio_id_table); | ||
628 | |||
629 | static struct pci_driver thunderx_gpio_driver = { | ||
630 | .name = KBUILD_MODNAME, | ||
631 | .id_table = thunderx_gpio_id_table, | ||
632 | .probe = thunderx_gpio_probe, | ||
633 | .remove = thunderx_gpio_remove, | ||
634 | }; | ||
635 | |||
636 | module_pci_driver(thunderx_gpio_driver); | ||
637 | |||
638 | MODULE_DESCRIPTION("Cavium Inc. ThunderX/OCTEON-TX GPIO Driver"); | ||
639 | MODULE_LICENSE("GPL"); | ||