Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
1b06d64f | 2 | /* |
4c23db0f | 3 | * GPIO driver for the ACCES 104-DIO-48E series |
1b06d64f WBG |
4 | * Copyright (C) 2016 William Breathitt Gray |
5 | * | |
4c23db0f WBG |
6 | * This driver supports the following ACCES devices: 104-DIO-48E and |
7 | * 104-DIO-24E. | |
1b06d64f | 8 | */ |
71b7b397 | 9 | #include <linux/bits.h> |
1b06d64f | 10 | #include <linux/device.h> |
2f7e845f | 11 | #include <linux/err.h> |
92f7a358 | 12 | #include <linux/i8254.h> |
1b06d64f | 13 | #include <linux/ioport.h> |
2f7e845f | 14 | #include <linux/irq.h> |
4c23db0f | 15 | #include <linux/isa.h> |
1b06d64f WBG |
16 | #include <linux/kernel.h> |
17 | #include <linux/module.h> | |
18 | #include <linux/moduleparam.h> | |
2f7e845f | 19 | #include <linux/regmap.h> |
92f7a358 | 20 | #include <linux/spinlock.h> |
71b7b397 WBG |
21 | #include <linux/types.h> |
22 | ||
23 | #include "gpio-i8255.h" | |
24 | ||
25 | MODULE_IMPORT_NS(I8255); | |
1b06d64f | 26 | |
4c23db0f WBG |
27 | #define DIO48E_EXTENT 16 |
28 | #define MAX_NUM_DIO48E max_num_isa_dev(DIO48E_EXTENT) | |
29 | ||
30 | static unsigned int base[MAX_NUM_DIO48E]; | |
31 | static unsigned int num_dio48e; | |
d759f906 | 32 | module_param_hw_array(base, uint, ioport, &num_dio48e, 0); |
4c23db0f WBG |
33 | MODULE_PARM_DESC(base, "ACCES 104-DIO-48E base addresses"); |
34 | ||
35 | static unsigned int irq[MAX_NUM_DIO48E]; | |
443ad0f7 WBG |
36 | static unsigned int num_irq; |
37 | module_param_hw_array(irq, uint, irq, &num_irq, 0); | |
4c23db0f | 38 | MODULE_PARM_DESC(irq, "ACCES 104-DIO-48E interrupt line numbers"); |
1b06d64f | 39 | |
2f7e845f WBG |
40 | #define DIO48E_ENABLE_INTERRUPT 0xB |
41 | #define DIO48E_DISABLE_INTERRUPT DIO48E_ENABLE_INTERRUPT | |
92f7a358 WBG |
42 | #define DIO48E_ENABLE_COUNTER_TIMER_ADDRESSING 0xD |
43 | #define DIO48E_DISABLE_COUNTER_TIMER_ADDRESSING DIO48E_ENABLE_COUNTER_TIMER_ADDRESSING | |
2f7e845f WBG |
44 | #define DIO48E_CLEAR_INTERRUPT 0xF |
45 | ||
71b7b397 WBG |
46 | #define DIO48E_NUM_PPI 2 |
47 | ||
2f7e845f WBG |
48 | static const struct regmap_range dio48e_wr_ranges[] = { |
49 | regmap_reg_range(0x0, 0x9), regmap_reg_range(0xB, 0xB), | |
50 | regmap_reg_range(0xD, 0xD), regmap_reg_range(0xF, 0xF), | |
51 | }; | |
52 | static const struct regmap_range dio48e_rd_ranges[] = { | |
53 | regmap_reg_range(0x0, 0x2), regmap_reg_range(0x4, 0x6), | |
54 | regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD), | |
55 | regmap_reg_range(0xF, 0xF), | |
56 | }; | |
57 | static const struct regmap_range dio48e_volatile_ranges[] = { | |
58 | i8255_volatile_regmap_range(0x0), i8255_volatile_regmap_range(0x4), | |
59 | regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD), | |
60 | regmap_reg_range(0xF, 0xF), | |
61 | }; | |
62 | static const struct regmap_range dio48e_precious_ranges[] = { | |
63 | regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD), | |
64 | regmap_reg_range(0xF, 0xF), | |
65 | }; | |
66 | static const struct regmap_access_table dio48e_wr_table = { | |
67 | .yes_ranges = dio48e_wr_ranges, | |
68 | .n_yes_ranges = ARRAY_SIZE(dio48e_wr_ranges), | |
69 | }; | |
70 | static const struct regmap_access_table dio48e_rd_table = { | |
71 | .yes_ranges = dio48e_rd_ranges, | |
72 | .n_yes_ranges = ARRAY_SIZE(dio48e_rd_ranges), | |
73 | }; | |
74 | static const struct regmap_access_table dio48e_volatile_table = { | |
75 | .yes_ranges = dio48e_volatile_ranges, | |
76 | .n_yes_ranges = ARRAY_SIZE(dio48e_volatile_ranges), | |
77 | }; | |
78 | static const struct regmap_access_table dio48e_precious_table = { | |
79 | .yes_ranges = dio48e_precious_ranges, | |
80 | .n_yes_ranges = ARRAY_SIZE(dio48e_precious_ranges), | |
81 | }; | |
92f7a358 WBG |
82 | |
83 | static const struct regmap_range pit_wr_ranges[] = { | |
84 | regmap_reg_range(0x0, 0x3), | |
85 | }; | |
86 | static const struct regmap_range pit_rd_ranges[] = { | |
87 | regmap_reg_range(0x0, 0x2), | |
88 | }; | |
89 | static const struct regmap_access_table pit_wr_table = { | |
90 | .yes_ranges = pit_wr_ranges, | |
91 | .n_yes_ranges = ARRAY_SIZE(pit_wr_ranges), | |
92 | }; | |
93 | static const struct regmap_access_table pit_rd_table = { | |
94 | .yes_ranges = pit_rd_ranges, | |
95 | .n_yes_ranges = ARRAY_SIZE(pit_rd_ranges), | |
2f7e845f | 96 | }; |
1b06d64f | 97 | |
2f7e845f WBG |
98 | /* only bit 3 on each respective Port C supports interrupts */ |
99 | #define DIO48E_REGMAP_IRQ(_ppi) \ | |
100 | [19 + (_ppi) * 24] = { \ | |
101 | .mask = BIT(_ppi), \ | |
102 | .type = { .types_supported = IRQ_TYPE_EDGE_RISING }, \ | |
1b06d64f WBG |
103 | } |
104 | ||
2f7e845f WBG |
105 | static const struct regmap_irq dio48e_regmap_irqs[] = { |
106 | DIO48E_REGMAP_IRQ(0), DIO48E_REGMAP_IRQ(1), | |
1b06d64f WBG |
107 | }; |
108 | ||
b5c506b1 WBG |
109 | /** |
110 | * struct dio48e_gpio - GPIO device private data structure | |
92f7a358 | 111 | * @lock: synchronization lock to prevent I/O race conditions |
b5c506b1 | 112 | * @map: Regmap for the device |
92f7a358 WBG |
113 | * @regs: virtual mapping for device registers |
114 | * @flags: IRQ flags saved during locking | |
b5c506b1 WBG |
115 | * @irq_mask: Current IRQ mask state on the device |
116 | */ | |
117 | struct dio48e_gpio { | |
92f7a358 | 118 | raw_spinlock_t lock; |
b5c506b1 | 119 | struct regmap *map; |
92f7a358 WBG |
120 | void __iomem *regs; |
121 | unsigned long flags; | |
b5c506b1 WBG |
122 | unsigned int irq_mask; |
123 | }; | |
124 | ||
92f7a358 WBG |
125 | static void dio48e_regmap_lock(void *lock_arg) __acquires(&dio48egpio->lock) |
126 | { | |
127 | struct dio48e_gpio *const dio48egpio = lock_arg; | |
128 | unsigned long flags; | |
129 | ||
130 | raw_spin_lock_irqsave(&dio48egpio->lock, flags); | |
131 | dio48egpio->flags = flags; | |
132 | } | |
133 | ||
134 | static void dio48e_regmap_unlock(void *lock_arg) __releases(&dio48egpio->lock) | |
135 | { | |
136 | struct dio48e_gpio *const dio48egpio = lock_arg; | |
137 | ||
138 | raw_spin_unlock_irqrestore(&dio48egpio->lock, dio48egpio->flags); | |
139 | } | |
140 | ||
141 | static void pit_regmap_lock(void *lock_arg) __acquires(&dio48egpio->lock) | |
142 | { | |
143 | struct dio48e_gpio *const dio48egpio = lock_arg; | |
144 | unsigned long flags; | |
145 | ||
146 | raw_spin_lock_irqsave(&dio48egpio->lock, flags); | |
147 | dio48egpio->flags = flags; | |
148 | ||
149 | iowrite8(0x00, dio48egpio->regs + DIO48E_ENABLE_COUNTER_TIMER_ADDRESSING); | |
150 | } | |
151 | ||
152 | static void pit_regmap_unlock(void *lock_arg) __releases(&dio48egpio->lock) | |
153 | { | |
154 | struct dio48e_gpio *const dio48egpio = lock_arg; | |
155 | ||
156 | ioread8(dio48egpio->regs + DIO48E_DISABLE_COUNTER_TIMER_ADDRESSING); | |
157 | ||
158 | raw_spin_unlock_irqrestore(&dio48egpio->lock, dio48egpio->flags); | |
159 | } | |
160 | ||
69da5aa9 | 161 | static int dio48e_handle_mask_sync(const int index, |
2f7e845f WBG |
162 | const unsigned int mask_buf_def, |
163 | const unsigned int mask_buf, | |
164 | void *const irq_drv_data) | |
1b06d64f | 165 | { |
b5c506b1 WBG |
166 | struct dio48e_gpio *const dio48egpio = irq_drv_data; |
167 | const unsigned int prev_mask = dio48egpio->irq_mask; | |
2f7e845f WBG |
168 | int err; |
169 | unsigned int val; | |
1b06d64f | 170 | |
2f7e845f WBG |
171 | /* exit early if no change since the previous mask */ |
172 | if (mask_buf == prev_mask) | |
173 | return 0; | |
1b06d64f | 174 | |
2f7e845f | 175 | /* remember the current mask for the next mask sync */ |
b5c506b1 | 176 | dio48egpio->irq_mask = mask_buf; |
1b06d64f | 177 | |
2f7e845f | 178 | /* if all previously masked, enable interrupts when unmasking */ |
b961b2aa | 179 | if (prev_mask == mask_buf_def) { |
b5c506b1 | 180 | err = regmap_write(dio48egpio->map, DIO48E_CLEAR_INTERRUPT, 0x00); |
2f7e845f WBG |
181 | if (err) |
182 | return err; | |
b5c506b1 | 183 | return regmap_write(dio48egpio->map, DIO48E_ENABLE_INTERRUPT, 0x00); |
2f7e845f | 184 | } |
1b06d64f | 185 | |
2f7e845f | 186 | /* if all are currently masked, disable interrupts */ |
b961b2aa | 187 | if (mask_buf == mask_buf_def) |
b5c506b1 | 188 | return regmap_read(dio48egpio->map, DIO48E_DISABLE_INTERRUPT, &val); |
1b06d64f | 189 | |
2f7e845f | 190 | return 0; |
1b06d64f WBG |
191 | } |
192 | ||
7bdba73e WBG |
193 | #define DIO48E_NGPIO 48 |
194 | static const char *dio48e_names[DIO48E_NGPIO] = { | |
195 | "PPI Group 0 Port A 0", "PPI Group 0 Port A 1", "PPI Group 0 Port A 2", | |
196 | "PPI Group 0 Port A 3", "PPI Group 0 Port A 4", "PPI Group 0 Port A 5", | |
197 | "PPI Group 0 Port A 6", "PPI Group 0 Port A 7", "PPI Group 0 Port B 0", | |
198 | "PPI Group 0 Port B 1", "PPI Group 0 Port B 2", "PPI Group 0 Port B 3", | |
199 | "PPI Group 0 Port B 4", "PPI Group 0 Port B 5", "PPI Group 0 Port B 6", | |
200 | "PPI Group 0 Port B 7", "PPI Group 0 Port C 0", "PPI Group 0 Port C 1", | |
201 | "PPI Group 0 Port C 2", "PPI Group 0 Port C 3", "PPI Group 0 Port C 4", | |
202 | "PPI Group 0 Port C 5", "PPI Group 0 Port C 6", "PPI Group 0 Port C 7", | |
203 | "PPI Group 1 Port A 0", "PPI Group 1 Port A 1", "PPI Group 1 Port A 2", | |
204 | "PPI Group 1 Port A 3", "PPI Group 1 Port A 4", "PPI Group 1 Port A 5", | |
205 | "PPI Group 1 Port A 6", "PPI Group 1 Port A 7", "PPI Group 1 Port B 0", | |
206 | "PPI Group 1 Port B 1", "PPI Group 1 Port B 2", "PPI Group 1 Port B 3", | |
207 | "PPI Group 1 Port B 4", "PPI Group 1 Port B 5", "PPI Group 1 Port B 6", | |
208 | "PPI Group 1 Port B 7", "PPI Group 1 Port C 0", "PPI Group 1 Port C 1", | |
209 | "PPI Group 1 Port C 2", "PPI Group 1 Port C 3", "PPI Group 1 Port C 4", | |
210 | "PPI Group 1 Port C 5", "PPI Group 1 Port C 6", "PPI Group 1 Port C 7" | |
211 | }; | |
212 | ||
2f7e845f | 213 | static int dio48e_irq_init_hw(struct regmap *const map) |
2fa1d392 | 214 | { |
2f7e845f | 215 | unsigned int val; |
2fa1d392 LW |
216 | |
217 | /* Disable IRQ by default */ | |
2f7e845f | 218 | return regmap_read(map, DIO48E_DISABLE_INTERRUPT, &val); |
2fa1d392 LW |
219 | } |
220 | ||
4c23db0f | 221 | static int dio48e_probe(struct device *dev, unsigned int id) |
1b06d64f | 222 | { |
1b06d64f | 223 | const char *const name = dev_name(dev); |
0b424340 | 224 | struct i8255_regmap_config config = {}; |
2f7e845f WBG |
225 | void __iomem *regs; |
226 | struct regmap *map; | |
92f7a358 WBG |
227 | struct regmap_config dio48e_regmap_config; |
228 | struct regmap_config pit_regmap_config; | |
229 | struct i8254_regmap_config pit_config; | |
1b06d64f | 230 | int err; |
2f7e845f | 231 | struct regmap_irq_chip *chip; |
b5c506b1 | 232 | struct dio48e_gpio *dio48egpio; |
2f7e845f | 233 | struct regmap_irq_chip_data *chip_data; |
1b06d64f | 234 | |
4c23db0f | 235 | if (!devm_request_region(dev, base[id], DIO48E_EXTENT, name)) { |
aa6c3602 | 236 | dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", |
4c23db0f | 237 | base[id], base[id] + DIO48E_EXTENT); |
aa6c3602 | 238 | return -EBUSY; |
1b06d64f WBG |
239 | } |
240 | ||
92f7a358 WBG |
241 | dio48egpio = devm_kzalloc(dev, sizeof(*dio48egpio), GFP_KERNEL); |
242 | if (!dio48egpio) | |
243 | return -ENOMEM; | |
244 | ||
2f7e845f WBG |
245 | regs = devm_ioport_map(dev, base[id], DIO48E_EXTENT); |
246 | if (!regs) | |
247 | return -ENOMEM; | |
2f7e845f | 248 | |
92f7a358 WBG |
249 | dio48egpio->regs = regs; |
250 | ||
251 | raw_spin_lock_init(&dio48egpio->lock); | |
252 | ||
253 | dio48e_regmap_config = (struct regmap_config) { | |
254 | .reg_bits = 8, | |
255 | .reg_stride = 1, | |
256 | .val_bits = 8, | |
257 | .lock = dio48e_regmap_lock, | |
258 | .unlock = dio48e_regmap_unlock, | |
259 | .lock_arg = dio48egpio, | |
260 | .io_port = true, | |
261 | .wr_table = &dio48e_wr_table, | |
262 | .rd_table = &dio48e_rd_table, | |
263 | .volatile_table = &dio48e_volatile_table, | |
264 | .precious_table = &dio48e_precious_table, | |
265 | .cache_type = REGCACHE_FLAT, | |
266 | }; | |
267 | ||
2f7e845f WBG |
268 | map = devm_regmap_init_mmio(dev, regs, &dio48e_regmap_config); |
269 | if (IS_ERR(map)) | |
270 | return dev_err_probe(dev, PTR_ERR(map), | |
271 | "Unable to initialize register map\n"); | |
272 | ||
b5c506b1 WBG |
273 | dio48egpio->map = map; |
274 | ||
92f7a358 WBG |
275 | pit_regmap_config = (struct regmap_config) { |
276 | .name = "i8254", | |
277 | .reg_bits = 8, | |
278 | .reg_stride = 1, | |
279 | .val_bits = 8, | |
280 | .lock = pit_regmap_lock, | |
281 | .unlock = pit_regmap_unlock, | |
282 | .lock_arg = dio48egpio, | |
283 | .io_port = true, | |
284 | .wr_table = &pit_wr_table, | |
285 | .rd_table = &pit_rd_table, | |
286 | }; | |
287 | ||
288 | pit_config.map = devm_regmap_init_mmio(dev, regs, &pit_regmap_config); | |
289 | if (IS_ERR(pit_config.map)) | |
290 | return dev_err_probe(dev, PTR_ERR(pit_config.map), | |
291 | "Unable to initialize i8254 register map\n"); | |
292 | ||
b5c506b1 WBG |
293 | chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); |
294 | if (!chip) | |
e993e236 WBG |
295 | return -ENOMEM; |
296 | ||
2f7e845f | 297 | chip->name = name; |
2f7e845f WBG |
298 | chip->mask_base = DIO48E_ENABLE_INTERRUPT; |
299 | chip->ack_base = DIO48E_CLEAR_INTERRUPT; | |
177b70c0 | 300 | chip->no_status = true; |
2f7e845f WBG |
301 | chip->num_regs = 1; |
302 | chip->irqs = dio48e_regmap_irqs; | |
303 | chip->num_irqs = ARRAY_SIZE(dio48e_regmap_irqs); | |
304 | chip->handle_mask_sync = dio48e_handle_mask_sync; | |
b5c506b1 | 305 | chip->irq_drv_data = dio48egpio; |
2f7e845f WBG |
306 | |
307 | /* Initialize to prevent spurious interrupts before we're ready */ | |
308 | err = dio48e_irq_init_hw(map); | |
309 | if (err) | |
310 | return err; | |
311 | ||
312 | err = devm_regmap_add_irq_chip(dev, map, irq[id], 0, 0, chip, &chip_data); | |
313 | if (err) | |
314 | return dev_err_probe(dev, err, "IRQ registration failed\n"); | |
315 | ||
92f7a358 WBG |
316 | pit_config.parent = dev; |
317 | ||
318 | err = devm_i8254_regmap_register(dev, &pit_config); | |
319 | if (err) | |
320 | return err; | |
321 | ||
0b424340 WBG |
322 | config.parent = dev; |
323 | config.map = map; | |
324 | config.num_ppi = DIO48E_NUM_PPI; | |
325 | config.names = dio48e_names; | |
326 | config.domain = regmap_irq_get_domain(chip_data); | |
1b06d64f | 327 | |
0b424340 | 328 | return devm_i8255_regmap_register(dev, &config); |
1b06d64f WBG |
329 | } |
330 | ||
4c23db0f WBG |
331 | static struct isa_driver dio48e_driver = { |
332 | .probe = dio48e_probe, | |
1b06d64f WBG |
333 | .driver = { |
334 | .name = "104-dio-48e" | |
335 | }, | |
1b06d64f | 336 | }; |
443ad0f7 | 337 | module_isa_driver_with_irq(dio48e_driver, num_dio48e, num_irq); |
1b06d64f WBG |
338 | |
339 | MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); | |
340 | MODULE_DESCRIPTION("ACCES 104-DIO-48E GPIO driver"); | |
22aeddb5 | 341 | MODULE_LICENSE("GPL v2"); |
92f7a358 | 342 | MODULE_IMPORT_NS(I8254); |