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> |
1b06d64f | 12 | #include <linux/ioport.h> |
2f7e845f | 13 | #include <linux/irq.h> |
4c23db0f | 14 | #include <linux/isa.h> |
1b06d64f WBG |
15 | #include <linux/kernel.h> |
16 | #include <linux/module.h> | |
17 | #include <linux/moduleparam.h> | |
2f7e845f | 18 | #include <linux/regmap.h> |
71b7b397 WBG |
19 | #include <linux/types.h> |
20 | ||
21 | #include "gpio-i8255.h" | |
22 | ||
23 | MODULE_IMPORT_NS(I8255); | |
1b06d64f | 24 | |
4c23db0f WBG |
25 | #define DIO48E_EXTENT 16 |
26 | #define MAX_NUM_DIO48E max_num_isa_dev(DIO48E_EXTENT) | |
27 | ||
28 | static unsigned int base[MAX_NUM_DIO48E]; | |
29 | static unsigned int num_dio48e; | |
d759f906 | 30 | module_param_hw_array(base, uint, ioport, &num_dio48e, 0); |
4c23db0f WBG |
31 | MODULE_PARM_DESC(base, "ACCES 104-DIO-48E base addresses"); |
32 | ||
33 | static unsigned int irq[MAX_NUM_DIO48E]; | |
443ad0f7 WBG |
34 | static unsigned int num_irq; |
35 | module_param_hw_array(irq, uint, irq, &num_irq, 0); | |
4c23db0f | 36 | MODULE_PARM_DESC(irq, "ACCES 104-DIO-48E interrupt line numbers"); |
1b06d64f | 37 | |
2f7e845f WBG |
38 | #define DIO48E_ENABLE_INTERRUPT 0xB |
39 | #define DIO48E_DISABLE_INTERRUPT DIO48E_ENABLE_INTERRUPT | |
40 | #define DIO48E_CLEAR_INTERRUPT 0xF | |
41 | ||
71b7b397 WBG |
42 | #define DIO48E_NUM_PPI 2 |
43 | ||
2f7e845f WBG |
44 | static const struct regmap_range dio48e_wr_ranges[] = { |
45 | regmap_reg_range(0x0, 0x9), regmap_reg_range(0xB, 0xB), | |
46 | regmap_reg_range(0xD, 0xD), regmap_reg_range(0xF, 0xF), | |
47 | }; | |
48 | static const struct regmap_range dio48e_rd_ranges[] = { | |
49 | regmap_reg_range(0x0, 0x2), regmap_reg_range(0x4, 0x6), | |
50 | regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD), | |
51 | regmap_reg_range(0xF, 0xF), | |
52 | }; | |
53 | static const struct regmap_range dio48e_volatile_ranges[] = { | |
54 | i8255_volatile_regmap_range(0x0), i8255_volatile_regmap_range(0x4), | |
55 | regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD), | |
56 | regmap_reg_range(0xF, 0xF), | |
57 | }; | |
58 | static const struct regmap_range dio48e_precious_ranges[] = { | |
59 | regmap_reg_range(0xB, 0xB), regmap_reg_range(0xD, 0xD), | |
60 | regmap_reg_range(0xF, 0xF), | |
61 | }; | |
62 | static const struct regmap_access_table dio48e_wr_table = { | |
63 | .yes_ranges = dio48e_wr_ranges, | |
64 | .n_yes_ranges = ARRAY_SIZE(dio48e_wr_ranges), | |
65 | }; | |
66 | static const struct regmap_access_table dio48e_rd_table = { | |
67 | .yes_ranges = dio48e_rd_ranges, | |
68 | .n_yes_ranges = ARRAY_SIZE(dio48e_rd_ranges), | |
69 | }; | |
70 | static const struct regmap_access_table dio48e_volatile_table = { | |
71 | .yes_ranges = dio48e_volatile_ranges, | |
72 | .n_yes_ranges = ARRAY_SIZE(dio48e_volatile_ranges), | |
73 | }; | |
74 | static const struct regmap_access_table dio48e_precious_table = { | |
75 | .yes_ranges = dio48e_precious_ranges, | |
76 | .n_yes_ranges = ARRAY_SIZE(dio48e_precious_ranges), | |
77 | }; | |
78 | static const struct regmap_config dio48e_regmap_config = { | |
79 | .reg_bits = 8, | |
80 | .reg_stride = 1, | |
81 | .val_bits = 8, | |
82 | .io_port = true, | |
83 | .max_register = 0xF, | |
84 | .wr_table = &dio48e_wr_table, | |
85 | .rd_table = &dio48e_rd_table, | |
86 | .volatile_table = &dio48e_volatile_table, | |
87 | .precious_table = &dio48e_precious_table, | |
88 | .cache_type = REGCACHE_FLAT, | |
89 | }; | |
1b06d64f | 90 | |
2f7e845f WBG |
91 | /* only bit 3 on each respective Port C supports interrupts */ |
92 | #define DIO48E_REGMAP_IRQ(_ppi) \ | |
93 | [19 + (_ppi) * 24] = { \ | |
94 | .mask = BIT(_ppi), \ | |
95 | .type = { .types_supported = IRQ_TYPE_EDGE_RISING }, \ | |
1b06d64f WBG |
96 | } |
97 | ||
2f7e845f WBG |
98 | static const struct regmap_irq dio48e_regmap_irqs[] = { |
99 | DIO48E_REGMAP_IRQ(0), DIO48E_REGMAP_IRQ(1), | |
1b06d64f WBG |
100 | }; |
101 | ||
2f7e845f WBG |
102 | static int dio48e_handle_mask_sync(struct regmap *const map, const int index, |
103 | const unsigned int mask_buf_def, | |
104 | const unsigned int mask_buf, | |
105 | void *const irq_drv_data) | |
1b06d64f | 106 | { |
2f7e845f WBG |
107 | unsigned int *const irq_mask = irq_drv_data; |
108 | const unsigned int prev_mask = *irq_mask; | |
109 | const unsigned int all_masked = GENMASK(1, 0); | |
110 | int err; | |
111 | unsigned int val; | |
1b06d64f | 112 | |
2f7e845f WBG |
113 | /* exit early if no change since the previous mask */ |
114 | if (mask_buf == prev_mask) | |
115 | return 0; | |
1b06d64f | 116 | |
2f7e845f WBG |
117 | /* remember the current mask for the next mask sync */ |
118 | *irq_mask = mask_buf; | |
1b06d64f | 119 | |
2f7e845f WBG |
120 | /* if all previously masked, enable interrupts when unmasking */ |
121 | if (prev_mask == all_masked) { | |
122 | err = regmap_write(map, DIO48E_CLEAR_INTERRUPT, 0x00); | |
123 | if (err) | |
124 | return err; | |
125 | return regmap_write(map, DIO48E_ENABLE_INTERRUPT, 0x00); | |
126 | } | |
1b06d64f | 127 | |
2f7e845f WBG |
128 | /* if all are currently masked, disable interrupts */ |
129 | if (mask_buf == all_masked) | |
130 | return regmap_read(map, DIO48E_DISABLE_INTERRUPT, &val); | |
1b06d64f | 131 | |
2f7e845f | 132 | return 0; |
1b06d64f WBG |
133 | } |
134 | ||
7bdba73e WBG |
135 | #define DIO48E_NGPIO 48 |
136 | static const char *dio48e_names[DIO48E_NGPIO] = { | |
137 | "PPI Group 0 Port A 0", "PPI Group 0 Port A 1", "PPI Group 0 Port A 2", | |
138 | "PPI Group 0 Port A 3", "PPI Group 0 Port A 4", "PPI Group 0 Port A 5", | |
139 | "PPI Group 0 Port A 6", "PPI Group 0 Port A 7", "PPI Group 0 Port B 0", | |
140 | "PPI Group 0 Port B 1", "PPI Group 0 Port B 2", "PPI Group 0 Port B 3", | |
141 | "PPI Group 0 Port B 4", "PPI Group 0 Port B 5", "PPI Group 0 Port B 6", | |
142 | "PPI Group 0 Port B 7", "PPI Group 0 Port C 0", "PPI Group 0 Port C 1", | |
143 | "PPI Group 0 Port C 2", "PPI Group 0 Port C 3", "PPI Group 0 Port C 4", | |
144 | "PPI Group 0 Port C 5", "PPI Group 0 Port C 6", "PPI Group 0 Port C 7", | |
145 | "PPI Group 1 Port A 0", "PPI Group 1 Port A 1", "PPI Group 1 Port A 2", | |
146 | "PPI Group 1 Port A 3", "PPI Group 1 Port A 4", "PPI Group 1 Port A 5", | |
147 | "PPI Group 1 Port A 6", "PPI Group 1 Port A 7", "PPI Group 1 Port B 0", | |
148 | "PPI Group 1 Port B 1", "PPI Group 1 Port B 2", "PPI Group 1 Port B 3", | |
149 | "PPI Group 1 Port B 4", "PPI Group 1 Port B 5", "PPI Group 1 Port B 6", | |
150 | "PPI Group 1 Port B 7", "PPI Group 1 Port C 0", "PPI Group 1 Port C 1", | |
151 | "PPI Group 1 Port C 2", "PPI Group 1 Port C 3", "PPI Group 1 Port C 4", | |
152 | "PPI Group 1 Port C 5", "PPI Group 1 Port C 6", "PPI Group 1 Port C 7" | |
153 | }; | |
154 | ||
2f7e845f | 155 | static int dio48e_irq_init_hw(struct regmap *const map) |
2fa1d392 | 156 | { |
2f7e845f | 157 | unsigned int val; |
2fa1d392 LW |
158 | |
159 | /* Disable IRQ by default */ | |
2f7e845f | 160 | return regmap_read(map, DIO48E_DISABLE_INTERRUPT, &val); |
2fa1d392 LW |
161 | } |
162 | ||
4c23db0f | 163 | static int dio48e_probe(struct device *dev, unsigned int id) |
1b06d64f | 164 | { |
1b06d64f | 165 | const char *const name = dev_name(dev); |
0b424340 | 166 | struct i8255_regmap_config config = {}; |
2f7e845f WBG |
167 | void __iomem *regs; |
168 | struct regmap *map; | |
1b06d64f | 169 | int err; |
2f7e845f WBG |
170 | struct regmap_irq_chip *chip; |
171 | unsigned int irq_mask; | |
172 | struct regmap_irq_chip_data *chip_data; | |
1b06d64f | 173 | |
4c23db0f | 174 | if (!devm_request_region(dev, base[id], DIO48E_EXTENT, name)) { |
aa6c3602 | 175 | dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", |
4c23db0f | 176 | base[id], base[id] + DIO48E_EXTENT); |
aa6c3602 | 177 | return -EBUSY; |
1b06d64f WBG |
178 | } |
179 | ||
2f7e845f WBG |
180 | regs = devm_ioport_map(dev, base[id], DIO48E_EXTENT); |
181 | if (!regs) | |
182 | return -ENOMEM; | |
2f7e845f WBG |
183 | |
184 | map = devm_regmap_init_mmio(dev, regs, &dio48e_regmap_config); | |
185 | if (IS_ERR(map)) | |
186 | return dev_err_probe(dev, PTR_ERR(map), | |
187 | "Unable to initialize register map\n"); | |
188 | ||
189 | chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); | |
190 | if (!chip) | |
191 | return -ENOMEM; | |
192 | ||
193 | chip->irq_drv_data = devm_kzalloc(dev, sizeof(irq_mask), GFP_KERNEL); | |
194 | if (!chip->irq_drv_data) | |
e993e236 WBG |
195 | return -ENOMEM; |
196 | ||
2f7e845f WBG |
197 | chip->name = name; |
198 | /* No IRQ status register so use CLEAR_INTERRUPT register instead */ | |
199 | chip->status_base = DIO48E_CLEAR_INTERRUPT; | |
200 | chip->mask_base = DIO48E_ENABLE_INTERRUPT; | |
201 | chip->ack_base = DIO48E_CLEAR_INTERRUPT; | |
202 | /* CLEAR_INTERRUPT doubles as status register so we need it cleared */ | |
203 | chip->clear_ack = true; | |
204 | chip->status_invert = true; | |
205 | chip->num_regs = 1; | |
206 | chip->irqs = dio48e_regmap_irqs; | |
207 | chip->num_irqs = ARRAY_SIZE(dio48e_regmap_irqs); | |
208 | chip->handle_mask_sync = dio48e_handle_mask_sync; | |
209 | ||
210 | /* Initialize to prevent spurious interrupts before we're ready */ | |
211 | err = dio48e_irq_init_hw(map); | |
212 | if (err) | |
213 | return err; | |
214 | ||
215 | err = devm_regmap_add_irq_chip(dev, map, irq[id], 0, 0, chip, &chip_data); | |
216 | if (err) | |
217 | return dev_err_probe(dev, err, "IRQ registration failed\n"); | |
218 | ||
0b424340 WBG |
219 | config.parent = dev; |
220 | config.map = map; | |
221 | config.num_ppi = DIO48E_NUM_PPI; | |
222 | config.names = dio48e_names; | |
223 | config.domain = regmap_irq_get_domain(chip_data); | |
1b06d64f | 224 | |
0b424340 | 225 | return devm_i8255_regmap_register(dev, &config); |
1b06d64f WBG |
226 | } |
227 | ||
4c23db0f WBG |
228 | static struct isa_driver dio48e_driver = { |
229 | .probe = dio48e_probe, | |
1b06d64f WBG |
230 | .driver = { |
231 | .name = "104-dio-48e" | |
232 | }, | |
1b06d64f | 233 | }; |
443ad0f7 | 234 | module_isa_driver_with_irq(dio48e_driver, num_dio48e, num_irq); |
1b06d64f WBG |
235 | |
236 | MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); | |
237 | MODULE_DESCRIPTION("ACCES 104-DIO-48E GPIO driver"); | |
22aeddb5 | 238 | MODULE_LICENSE("GPL v2"); |