Commit | Line | Data |
---|---|---|
58556204 WBG |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * GPIO driver for the ACCES PCIe-IDIO-24 family | |
4 | * Copyright (C) 2018 William Breathitt Gray | |
5 | * | |
58556204 WBG |
6 | * This driver supports the following ACCES devices: PCIe-IDIO-24, |
7 | * PCIe-IDI-24, PCIe-IDO-24, and PCIe-IDIO-12. | |
8 | */ | |
1a200a39 | 9 | #include <linux/bits.h> |
58556204 | 10 | #include <linux/device.h> |
1a200a39 WBG |
11 | #include <linux/err.h> |
12 | #include <linux/gpio/regmap.h> | |
13 | #include <linux/irq.h> | |
58556204 WBG |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> | |
16 | #include <linux/pci.h> | |
1a200a39 | 17 | #include <linux/regmap.h> |
58556204 WBG |
18 | #include <linux/spinlock.h> |
19 | #include <linux/types.h> | |
20 | ||
10a2f11d AT |
21 | /* |
22 | * PLX PEX8311 PCI LCS_INTCSR Interrupt Control/Status | |
23 | * | |
24 | * Bit: Description | |
25 | * 0: Enable Interrupt Sources (Bit 0) | |
26 | * 1: Enable Interrupt Sources (Bit 1) | |
27 | * 2: Generate Internal PCI Bus Internal SERR# Interrupt | |
28 | * 3: Mailbox Interrupt Enable | |
29 | * 4: Power Management Interrupt Enable | |
30 | * 5: Power Management Interrupt | |
31 | * 6: Slave Read Local Data Parity Check Error Enable | |
32 | * 7: Slave Read Local Data Parity Check Error Status | |
33 | * 8: Internal PCI Wire Interrupt Enable | |
34 | * 9: PCI Express Doorbell Interrupt Enable | |
35 | * 10: PCI Abort Interrupt Enable | |
36 | * 11: Local Interrupt Input Enable | |
37 | * 12: Retry Abort Enable | |
38 | * 13: PCI Express Doorbell Interrupt Active | |
39 | * 14: PCI Abort Interrupt Active | |
40 | * 15: Local Interrupt Input Active | |
41 | * 16: Local Interrupt Output Enable | |
42 | * 17: Local Doorbell Interrupt Enable | |
43 | * 18: DMA Channel 0 Interrupt Enable | |
44 | * 19: DMA Channel 1 Interrupt Enable | |
45 | * 20: Local Doorbell Interrupt Active | |
46 | * 21: DMA Channel 0 Interrupt Active | |
47 | * 22: DMA Channel 1 Interrupt Active | |
48 | * 23: Built-In Self-Test (BIST) Interrupt Active | |
49 | * 24: Direct Master was the Bus Master during a Master or Target Abort | |
50 | * 25: DMA Channel 0 was the Bus Master during a Master or Target Abort | |
51 | * 26: DMA Channel 1 was the Bus Master during a Master or Target Abort | |
52 | * 27: Target Abort after internal 256 consecutive Master Retrys | |
53 | * 28: PCI Bus wrote data to LCS_MBOX0 | |
54 | * 29: PCI Bus wrote data to LCS_MBOX1 | |
55 | * 30: PCI Bus wrote data to LCS_MBOX2 | |
56 | * 31: PCI Bus wrote data to LCS_MBOX3 | |
57 | */ | |
58 | #define PLX_PEX8311_PCI_LCS_INTCSR 0x68 | |
59 | #define INTCSR_INTERNAL_PCI_WIRE BIT(8) | |
60 | #define INTCSR_LOCAL_INPUT BIT(11) | |
1a200a39 WBG |
61 | #define IDIO_24_ENABLE_IRQ (INTCSR_INTERNAL_PCI_WIRE | INTCSR_LOCAL_INPUT) |
62 | ||
63 | #define IDIO_24_OUT_BASE 0x0 | |
64 | #define IDIO_24_TTLCMOS_OUT_REG 0x3 | |
65 | #define IDIO_24_IN_BASE 0x4 | |
66 | #define IDIO_24_TTLCMOS_IN_REG 0x7 | |
67 | #define IDIO_24_COS_STATUS_BASE 0x8 | |
68 | #define IDIO_24_CONTROL_REG 0xC | |
69 | #define IDIO_24_COS_ENABLE 0xE | |
70 | #define IDIO_24_SOFT_RESET 0xF | |
71 | ||
72 | #define CONTROL_REG_OUT_MODE BIT(1) | |
73 | ||
74 | #define COS_ENABLE_RISING BIT(1) | |
75 | #define COS_ENABLE_FALLING BIT(4) | |
76 | #define COS_ENABLE_BOTH (COS_ENABLE_RISING | COS_ENABLE_FALLING) | |
77 | ||
78 | static const struct regmap_config pex8311_intcsr_regmap_config = { | |
79 | .name = "pex8311_intcsr", | |
80 | .reg_bits = 32, | |
81 | .reg_stride = 1, | |
82 | .reg_base = PLX_PEX8311_PCI_LCS_INTCSR, | |
83 | .val_bits = 32, | |
84 | .io_port = true, | |
85 | }; | |
10a2f11d | 86 | |
1a200a39 WBG |
87 | static const struct regmap_range idio_24_wr_ranges[] = { |
88 | regmap_reg_range(0x0, 0x3), regmap_reg_range(0x8, 0xC), | |
89 | regmap_reg_range(0xE, 0xF), | |
90 | }; | |
91 | static const struct regmap_range idio_24_rd_ranges[] = { | |
92 | regmap_reg_range(0x0, 0xC), regmap_reg_range(0xE, 0xF), | |
93 | }; | |
94 | static const struct regmap_range idio_24_volatile_ranges[] = { | |
95 | regmap_reg_range(0x4, 0xB), regmap_reg_range(0xF, 0xF), | |
96 | }; | |
97 | static const struct regmap_access_table idio_24_wr_table = { | |
98 | .yes_ranges = idio_24_wr_ranges, | |
99 | .n_yes_ranges = ARRAY_SIZE(idio_24_wr_ranges), | |
100 | }; | |
101 | static const struct regmap_access_table idio_24_rd_table = { | |
102 | .yes_ranges = idio_24_rd_ranges, | |
103 | .n_yes_ranges = ARRAY_SIZE(idio_24_rd_ranges), | |
104 | }; | |
105 | static const struct regmap_access_table idio_24_volatile_table = { | |
106 | .yes_ranges = idio_24_volatile_ranges, | |
107 | .n_yes_ranges = ARRAY_SIZE(idio_24_volatile_ranges), | |
108 | }; | |
109 | ||
110 | static const struct regmap_config idio_24_regmap_config = { | |
111 | .reg_bits = 8, | |
112 | .reg_stride = 1, | |
113 | .val_bits = 8, | |
114 | .io_port = true, | |
115 | .wr_table = &idio_24_wr_table, | |
116 | .rd_table = &idio_24_rd_table, | |
117 | .volatile_table = &idio_24_volatile_table, | |
118 | .cache_type = REGCACHE_FLAT, | |
119 | .use_raw_spinlock = true, | |
120 | }; | |
121 | ||
122 | #define IDIO_24_NGPIO_PER_REG 8 | |
123 | #define IDIO_24_REGMAP_IRQ(_id) \ | |
124 | [24 + _id] = { \ | |
125 | .reg_offset = (_id) / IDIO_24_NGPIO_PER_REG, \ | |
126 | .mask = BIT((_id) % IDIO_24_NGPIO_PER_REG), \ | |
127 | .type = { .types_supported = IRQ_TYPE_EDGE_BOTH }, \ | |
128 | } | |
129 | #define IDIO_24_IIN_IRQ(_id) IDIO_24_REGMAP_IRQ(_id) | |
130 | #define IDIO_24_TTL_IRQ(_id) IDIO_24_REGMAP_IRQ(24 + _id) | |
131 | ||
132 | static const struct regmap_irq idio_24_regmap_irqs[] = { | |
133 | IDIO_24_IIN_IRQ(0), IDIO_24_IIN_IRQ(1), IDIO_24_IIN_IRQ(2), /* IIN 0-2 */ | |
134 | IDIO_24_IIN_IRQ(3), IDIO_24_IIN_IRQ(4), IDIO_24_IIN_IRQ(5), /* IIN 3-5 */ | |
135 | IDIO_24_IIN_IRQ(6), IDIO_24_IIN_IRQ(7), IDIO_24_IIN_IRQ(8), /* IIN 6-8 */ | |
136 | IDIO_24_IIN_IRQ(9), IDIO_24_IIN_IRQ(10), IDIO_24_IIN_IRQ(11), /* IIN 9-11 */ | |
137 | IDIO_24_IIN_IRQ(12), IDIO_24_IIN_IRQ(13), IDIO_24_IIN_IRQ(14), /* IIN 12-14 */ | |
138 | IDIO_24_IIN_IRQ(15), IDIO_24_IIN_IRQ(16), IDIO_24_IIN_IRQ(17), /* IIN 15-17 */ | |
139 | IDIO_24_IIN_IRQ(18), IDIO_24_IIN_IRQ(19), IDIO_24_IIN_IRQ(20), /* IIN 18-20 */ | |
140 | IDIO_24_IIN_IRQ(21), IDIO_24_IIN_IRQ(22), IDIO_24_IIN_IRQ(23), /* IIN 21-23 */ | |
141 | IDIO_24_TTL_IRQ(0), IDIO_24_TTL_IRQ(1), IDIO_24_TTL_IRQ(2), /* TTL 0-2 */ | |
142 | IDIO_24_TTL_IRQ(3), IDIO_24_TTL_IRQ(4), IDIO_24_TTL_IRQ(5), /* TTL 3-5 */ | |
143 | IDIO_24_TTL_IRQ(6), IDIO_24_TTL_IRQ(7), /* TTL 6-7 */ | |
58556204 WBG |
144 | }; |
145 | ||
146 | /** | |
147 | * struct idio_24_gpio - GPIO device private data structure | |
1a200a39 | 148 | * @map: regmap for the device |
58556204 | 149 | * @lock: synchronization lock to prevent I/O race conditions |
1a200a39 | 150 | * @irq_type: type configuration for IRQs |
58556204 WBG |
151 | */ |
152 | struct idio_24_gpio { | |
1a200a39 | 153 | struct regmap *map; |
58556204 | 154 | raw_spinlock_t lock; |
1a200a39 | 155 | u8 irq_type; |
58556204 WBG |
156 | }; |
157 | ||
1a200a39 WBG |
158 | static int idio_24_handle_mask_sync(const int index, const unsigned int mask_buf_def, |
159 | const unsigned int mask_buf, void *const irq_drv_data) | |
58556204 | 160 | { |
1a200a39 WBG |
161 | const unsigned int type_mask = COS_ENABLE_BOTH << index; |
162 | struct idio_24_gpio *const idio24gpio = irq_drv_data; | |
163 | u8 type; | |
164 | int ret; | |
58556204 | 165 | |
1a200a39 | 166 | raw_spin_lock(&idio24gpio->lock); |
58556204 | 167 | |
1a200a39 WBG |
168 | /* if all are masked, then disable interrupts, else set to type */ |
169 | type = (mask_buf == mask_buf_def) ? ~type_mask : idio24gpio->irq_type; | |
58556204 | 170 | |
1a200a39 | 171 | ret = regmap_update_bits(idio24gpio->map, IDIO_24_COS_ENABLE, type_mask, type); |
58556204 | 172 | |
1a200a39 | 173 | raw_spin_unlock(&idio24gpio->lock); |
ca370815 | 174 | |
1a200a39 | 175 | return ret; |
ca370815 WBG |
176 | } |
177 | ||
1a200a39 WBG |
178 | static int idio_24_set_type_config(unsigned int **const buf, const unsigned int type, |
179 | const struct regmap_irq *const irq_data, const int idx, | |
180 | void *const irq_drv_data) | |
58556204 | 181 | { |
1a200a39 WBG |
182 | const unsigned int offset = irq_data->reg_offset; |
183 | const unsigned int rising = COS_ENABLE_RISING << offset; | |
184 | const unsigned int falling = COS_ENABLE_FALLING << offset; | |
185 | const unsigned int mask = COS_ENABLE_BOTH << offset; | |
186 | struct idio_24_gpio *const idio24gpio = irq_drv_data; | |
187 | unsigned int new; | |
188 | unsigned int cos_enable; | |
189 | int ret; | |
190 | ||
191 | switch (type) { | |
192 | case IRQ_TYPE_EDGE_RISING: | |
193 | new = rising; | |
194 | break; | |
195 | case IRQ_TYPE_EDGE_FALLING: | |
196 | new = falling; | |
197 | break; | |
198 | case IRQ_TYPE_EDGE_BOTH: | |
199 | new = mask; | |
200 | break; | |
201 | default: | |
202 | return -EINVAL; | |
c586aa8f | 203 | } |
58556204 | 204 | |
1a200a39 | 205 | raw_spin_lock(&idio24gpio->lock); |
58556204 | 206 | |
1a200a39 WBG |
207 | /* replace old bitmap with new bitmap */ |
208 | idio24gpio->irq_type = (idio24gpio->irq_type & ~mask) | (new & mask); | |
58556204 | 209 | |
1a200a39 WBG |
210 | ret = regmap_read(idio24gpio->map, IDIO_24_COS_ENABLE, &cos_enable); |
211 | if (ret) | |
212 | goto exit_unlock; | |
58556204 | 213 | |
1a200a39 WBG |
214 | /* if COS is currently enabled then update the edge type */ |
215 | if (cos_enable & mask) { | |
216 | ret = regmap_update_bits(idio24gpio->map, IDIO_24_COS_ENABLE, mask, | |
217 | idio24gpio->irq_type); | |
218 | if (ret) | |
219 | goto exit_unlock; | |
58556204 WBG |
220 | } |
221 | ||
1a200a39 WBG |
222 | exit_unlock: |
223 | raw_spin_unlock(&idio24gpio->lock); | |
33886f92 | 224 | |
1a200a39 | 225 | return ret; |
58556204 WBG |
226 | } |
227 | ||
1a200a39 WBG |
228 | static int idio_24_reg_mask_xlate(struct gpio_regmap *const gpio, const unsigned int base, |
229 | const unsigned int offset, unsigned int *const reg, | |
230 | unsigned int *const mask) | |
58556204 | 231 | { |
1a200a39 WBG |
232 | const unsigned int out_stride = offset / IDIO_24_NGPIO_PER_REG; |
233 | const unsigned int in_stride = (offset - 24) / IDIO_24_NGPIO_PER_REG; | |
234 | struct regmap *const map = gpio_regmap_get_drvdata(gpio); | |
235 | int err; | |
236 | unsigned int ctrl_reg; | |
58556204 | 237 | |
1a200a39 WBG |
238 | switch (base) { |
239 | case IDIO_24_OUT_BASE: | |
240 | *mask = BIT(offset % IDIO_24_NGPIO_PER_REG); | |
58556204 | 241 | |
1a200a39 WBG |
242 | /* FET Outputs */ |
243 | if (offset < 24) { | |
244 | *reg = IDIO_24_OUT_BASE + out_stride; | |
245 | return 0; | |
246 | } | |
58556204 | 247 | |
1a200a39 WBG |
248 | /* Isolated Inputs */ |
249 | if (offset < 48) { | |
250 | *reg = IDIO_24_IN_BASE + in_stride; | |
251 | return 0; | |
252 | } | |
58556204 | 253 | |
1a200a39 WBG |
254 | err = regmap_read(map, IDIO_24_CONTROL_REG, &ctrl_reg); |
255 | if (err) | |
256 | return err; | |
58556204 | 257 | |
1a200a39 WBG |
258 | /* TTL/CMOS Outputs */ |
259 | if (ctrl_reg & CONTROL_REG_OUT_MODE) { | |
260 | *reg = IDIO_24_TTLCMOS_OUT_REG; | |
261 | return 0; | |
262 | } | |
58556204 | 263 | |
1a200a39 WBG |
264 | /* TTL/CMOS Inputs */ |
265 | *reg = IDIO_24_TTLCMOS_IN_REG; | |
266 | return 0; | |
267 | case IDIO_24_CONTROL_REG: | |
268 | /* We can only set direction for TTL/CMOS lines */ | |
269 | if (offset < 48) | |
270 | return -EOPNOTSUPP; | |
271 | ||
272 | *reg = IDIO_24_CONTROL_REG; | |
273 | *mask = CONTROL_REG_OUT_MODE; | |
274 | return 0; | |
275 | default: | |
276 | /* Should never reach this path */ | |
58556204 | 277 | return -EINVAL; |
1a200a39 | 278 | } |
58556204 WBG |
279 | } |
280 | ||
281 | #define IDIO_24_NGPIO 56 | |
282 | static const char *idio_24_names[IDIO_24_NGPIO] = { | |
283 | "OUT0", "OUT1", "OUT2", "OUT3", "OUT4", "OUT5", "OUT6", "OUT7", | |
284 | "OUT8", "OUT9", "OUT10", "OUT11", "OUT12", "OUT13", "OUT14", "OUT15", | |
285 | "OUT16", "OUT17", "OUT18", "OUT19", "OUT20", "OUT21", "OUT22", "OUT23", | |
286 | "IIN0", "IIN1", "IIN2", "IIN3", "IIN4", "IIN5", "IIN6", "IIN7", | |
287 | "IIN8", "IIN9", "IIN10", "IIN11", "IIN12", "IIN13", "IIN14", "IIN15", | |
288 | "IIN16", "IIN17", "IIN18", "IIN19", "IIN20", "IIN21", "IIN22", "IIN23", | |
289 | "TTL0", "TTL1", "TTL2", "TTL3", "TTL4", "TTL5", "TTL6", "TTL7" | |
290 | }; | |
291 | ||
292 | static int idio_24_probe(struct pci_dev *pdev, const struct pci_device_id *id) | |
293 | { | |
294 | struct device *const dev = &pdev->dev; | |
295 | struct idio_24_gpio *idio24gpio; | |
296 | int err; | |
10a2f11d | 297 | const size_t pci_plx_bar_index = 1; |
58556204 WBG |
298 | const size_t pci_bar_index = 2; |
299 | const char *const name = pci_name(pdev); | |
1a200a39 WBG |
300 | struct gpio_regmap_config gpio_config = {}; |
301 | void __iomem *pex8311_regs; | |
302 | void __iomem *idio_24_regs; | |
303 | struct regmap *intcsr_map; | |
304 | struct regmap_irq_chip *chip; | |
305 | struct regmap_irq_chip_data *chip_data; | |
58556204 WBG |
306 | |
307 | err = pcim_enable_device(pdev); | |
308 | if (err) { | |
309 | dev_err(dev, "Failed to enable PCI device (%d)\n", err); | |
310 | return err; | |
311 | } | |
312 | ||
10a2f11d | 313 | err = pcim_iomap_regions(pdev, BIT(pci_plx_bar_index) | BIT(pci_bar_index), name); |
58556204 WBG |
314 | if (err) { |
315 | dev_err(dev, "Unable to map PCI I/O addresses (%d)\n", err); | |
316 | return err; | |
317 | } | |
318 | ||
1a200a39 WBG |
319 | pex8311_regs = pcim_iomap_table(pdev)[pci_plx_bar_index]; |
320 | idio_24_regs = pcim_iomap_table(pdev)[pci_bar_index]; | |
321 | ||
322 | intcsr_map = devm_regmap_init_mmio(dev, pex8311_regs, &pex8311_intcsr_regmap_config); | |
323 | if (IS_ERR(intcsr_map)) | |
324 | return dev_err_probe(dev, PTR_ERR(intcsr_map), | |
325 | "Unable to initialize PEX8311 register map\n"); | |
326 | ||
327 | idio24gpio = devm_kzalloc(dev, sizeof(*idio24gpio), GFP_KERNEL); | |
328 | if (!idio24gpio) | |
329 | return -ENOMEM; | |
330 | ||
331 | idio24gpio->map = devm_regmap_init_mmio(dev, idio_24_regs, &idio_24_regmap_config); | |
332 | if (IS_ERR(idio24gpio->map)) | |
333 | return dev_err_probe(dev, PTR_ERR(idio24gpio->map), | |
334 | "Unable to initialize register map\n"); | |
866e863e | 335 | |
58556204 WBG |
336 | raw_spin_lock_init(&idio24gpio->lock); |
337 | ||
1a200a39 WBG |
338 | /* Initialize all IRQ type configuration to IRQ_TYPE_EDGE_BOTH */ |
339 | idio24gpio->irq_type = GENMASK(7, 0); | |
340 | ||
341 | chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); | |
342 | if (!chip) | |
343 | return -ENOMEM; | |
344 | ||
345 | chip->name = name; | |
346 | chip->status_base = IDIO_24_COS_STATUS_BASE; | |
347 | chip->mask_base = IDIO_24_COS_ENABLE; | |
348 | chip->ack_base = IDIO_24_COS_STATUS_BASE; | |
349 | chip->num_regs = 4; | |
350 | chip->irqs = idio_24_regmap_irqs; | |
351 | chip->num_irqs = ARRAY_SIZE(idio_24_regmap_irqs); | |
352 | chip->handle_mask_sync = idio_24_handle_mask_sync; | |
353 | chip->set_type_config = idio_24_set_type_config; | |
354 | chip->irq_drv_data = idio24gpio; | |
355 | ||
58556204 | 356 | /* Software board reset */ |
1a200a39 WBG |
357 | err = regmap_write(idio24gpio->map, IDIO_24_SOFT_RESET, 0); |
358 | if (err) | |
359 | return err; | |
10a2f11d AT |
360 | /* |
361 | * enable PLX PEX8311 internal PCI wire interrupt and local interrupt | |
362 | * input | |
363 | */ | |
1a200a39 WBG |
364 | err = regmap_update_bits(intcsr_map, 0x0, IDIO_24_ENABLE_IRQ, IDIO_24_ENABLE_IRQ); |
365 | if (err) | |
58556204 | 366 | return err; |
58556204 | 367 | |
1a200a39 WBG |
368 | err = devm_regmap_add_irq_chip(dev, idio24gpio->map, pdev->irq, 0, 0, chip, &chip_data); |
369 | if (err) | |
370 | return dev_err_probe(dev, err, "IRQ registration failed\n"); | |
371 | ||
372 | gpio_config.parent = dev; | |
373 | gpio_config.regmap = idio24gpio->map; | |
374 | gpio_config.ngpio = IDIO_24_NGPIO; | |
375 | gpio_config.names = idio_24_names; | |
376 | gpio_config.reg_dat_base = GPIO_REGMAP_ADDR(IDIO_24_OUT_BASE); | |
377 | gpio_config.reg_set_base = GPIO_REGMAP_ADDR(IDIO_24_OUT_BASE); | |
378 | gpio_config.reg_dir_out_base = GPIO_REGMAP_ADDR(IDIO_24_CONTROL_REG); | |
379 | gpio_config.ngpio_per_reg = IDIO_24_NGPIO_PER_REG; | |
380 | gpio_config.irq_domain = regmap_irq_get_domain(chip_data); | |
381 | gpio_config.reg_mask_xlate = idio_24_reg_mask_xlate; | |
382 | gpio_config.drvdata = idio24gpio->map; | |
383 | ||
384 | return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config)); | |
58556204 WBG |
385 | } |
386 | ||
387 | static const struct pci_device_id idio_24_pci_dev_id[] = { | |
388 | { PCI_DEVICE(0x494F, 0x0FD0) }, { PCI_DEVICE(0x494F, 0x0BD0) }, | |
389 | { PCI_DEVICE(0x494F, 0x07D0) }, { PCI_DEVICE(0x494F, 0x0FC0) }, | |
390 | { 0 } | |
391 | }; | |
392 | MODULE_DEVICE_TABLE(pci, idio_24_pci_dev_id); | |
393 | ||
394 | static struct pci_driver idio_24_driver = { | |
395 | .name = "pcie-idio-24", | |
396 | .id_table = idio_24_pci_dev_id, | |
397 | .probe = idio_24_probe | |
398 | }; | |
399 | ||
400 | module_pci_driver(idio_24_driver); | |
401 | ||
402 | MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); | |
403 | MODULE_DESCRIPTION("ACCES PCIe-IDIO-24 GPIO driver"); | |
404 | MODULE_LICENSE("GPL v2"); |