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 | * |
6 | * This driver supports the following ACCES devices: PCIe-IDIO-24, |
7 | * PCIe-IDI-24, PCIe-IDO-24, and PCIe-IDIO-12. |
8 | */ |
9 | #include <linux/bits.h> |
10 | #include <linux/device.h> |
11 | #include <linux/err.h> |
12 | #include <linux/gpio/regmap.h> |
13 | #include <linux/irq.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/pci.h> |
17 | #include <linux/regmap.h> |
18 | #include <linux/spinlock.h> |
19 | #include <linux/types.h> |
20 | |
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) |
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 | }; |
86 | |
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 */ |
144 | }; |
145 | |
146 | /** |
147 | * struct idio_24_gpio - GPIO device private data structure |
148 | * @map: regmap for the device |
149 | * @lock: synchronization lock to prevent I/O race conditions |
150 | * @irq_type: type configuration for IRQs |
151 | */ |
152 | struct idio_24_gpio { |
153 | struct regmap *map; |
154 | raw_spinlock_t lock; |
155 | u8 irq_type; |
156 | }; |
157 | |
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) |
160 | { |
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; |
165 | |
166 | raw_spin_lock(&idio24gpio->lock); |
167 | |
168 | /* if all are masked, then disable interrupts, else set to type */ |
169 | type = (mask_buf == mask_buf_def) ? ~type_mask : idio24gpio->irq_type; |
170 | |
171 | ret = regmap_update_bits(map: idio24gpio->map, IDIO_24_COS_ENABLE, mask: type_mask, val: type); |
172 | |
173 | raw_spin_unlock(&idio24gpio->lock); |
174 | |
175 | return ret; |
176 | } |
177 | |
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) |
181 | { |
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; |
203 | } |
204 | |
205 | raw_spin_lock(&idio24gpio->lock); |
206 | |
207 | /* replace old bitmap with new bitmap */ |
208 | idio24gpio->irq_type = (idio24gpio->irq_type & ~mask) | (new & mask); |
209 | |
210 | ret = regmap_read(map: idio24gpio->map, IDIO_24_COS_ENABLE, val: &cos_enable); |
211 | if (ret) |
212 | goto exit_unlock; |
213 | |
214 | /* if COS is currently enabled then update the edge type */ |
215 | if (cos_enable & mask) { |
216 | ret = regmap_update_bits(map: idio24gpio->map, IDIO_24_COS_ENABLE, mask, |
217 | val: idio24gpio->irq_type); |
218 | if (ret) |
219 | goto exit_unlock; |
220 | } |
221 | |
222 | exit_unlock: |
223 | raw_spin_unlock(&idio24gpio->lock); |
224 | |
225 | return ret; |
226 | } |
227 | |
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) |
231 | { |
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; |
237 | |
238 | switch (base) { |
239 | case IDIO_24_OUT_BASE: |
240 | *mask = BIT(offset % IDIO_24_NGPIO_PER_REG); |
241 | |
242 | /* FET Outputs */ |
243 | if (offset < 24) { |
244 | *reg = IDIO_24_OUT_BASE + out_stride; |
245 | return 0; |
246 | } |
247 | |
248 | /* Isolated Inputs */ |
249 | if (offset < 48) { |
250 | *reg = IDIO_24_IN_BASE + in_stride; |
251 | return 0; |
252 | } |
253 | |
254 | err = regmap_read(map, IDIO_24_CONTROL_REG, val: &ctrl_reg); |
255 | if (err) |
256 | return err; |
257 | |
258 | /* TTL/CMOS Outputs */ |
259 | if (ctrl_reg & CONTROL_REG_OUT_MODE) { |
260 | *reg = IDIO_24_TTLCMOS_OUT_REG; |
261 | return 0; |
262 | } |
263 | |
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 */ |
277 | return -EINVAL; |
278 | } |
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; |
297 | const size_t pci_plx_bar_index = 1; |
298 | const size_t pci_bar_index = 2; |
299 | const char *const name = pci_name(pdev); |
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; |
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 | |
313 | err = pcim_iomap_regions(pdev, BIT(pci_plx_bar_index) | BIT(pci_bar_index), name); |
314 | if (err) { |
315 | dev_err(dev, "Unable to map PCI I/O addresses (%d)\n" , err); |
316 | return err; |
317 | } |
318 | |
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(ptr: intcsr_map)) |
324 | return dev_err_probe(dev, err: PTR_ERR(ptr: intcsr_map), |
325 | fmt: "Unable to initialize PEX8311 register map\n" ); |
326 | |
327 | idio24gpio = devm_kzalloc(dev, size: 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(ptr: idio24gpio->map)) |
333 | return dev_err_probe(dev, err: PTR_ERR(ptr: idio24gpio->map), |
334 | fmt: "Unable to initialize register map\n" ); |
335 | |
336 | raw_spin_lock_init(&idio24gpio->lock); |
337 | |
338 | /* Initialize all IRQ type configuration to IRQ_TYPE_EDGE_BOTH */ |
339 | idio24gpio->irq_type = GENMASK(7, 0); |
340 | |
341 | chip = devm_kzalloc(dev, size: 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 | |
356 | /* Software board reset */ |
357 | err = regmap_write(map: idio24gpio->map, IDIO_24_SOFT_RESET, val: 0); |
358 | if (err) |
359 | return err; |
360 | /* |
361 | * enable PLX PEX8311 internal PCI wire interrupt and local interrupt |
362 | * input |
363 | */ |
364 | err = regmap_update_bits(map: intcsr_map, reg: 0x0, IDIO_24_ENABLE_IRQ, IDIO_24_ENABLE_IRQ); |
365 | if (err) |
366 | return err; |
367 | |
368 | err = devm_regmap_add_irq_chip(dev, map: idio24gpio->map, irq: pdev->irq, irq_flags: 0, irq_base: 0, chip, data: &chip_data); |
369 | if (err) |
370 | return dev_err_probe(dev, err, fmt: "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(data: 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(ptr: devm_gpio_regmap_register(dev, config: &gpio_config)); |
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" ); |
405 | |