1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Serial multi-instantiate driver, pseudo driver to instantiate multiple |
4 | * client devices from a single fwnode. |
5 | * |
6 | * Copyright 2018 Hans de Goede <hdegoede@redhat.com> |
7 | */ |
8 | |
9 | #include <linux/acpi.h> |
10 | #include <linux/bits.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/property.h> |
17 | #include <linux/spi/spi.h> |
18 | #include <linux/types.h> |
19 | |
20 | #define IRQ_RESOURCE_TYPE GENMASK(1, 0) |
21 | #define IRQ_RESOURCE_NONE 0 |
22 | #define IRQ_RESOURCE_GPIO 1 |
23 | #define IRQ_RESOURCE_APIC 2 |
24 | #define IRQ_RESOURCE_AUTO 3 |
25 | |
26 | enum smi_bus_type { |
27 | SMI_I2C, |
28 | SMI_SPI, |
29 | SMI_AUTO_DETECT, |
30 | }; |
31 | |
32 | struct smi_instance { |
33 | const char *type; |
34 | unsigned int flags; |
35 | int irq_idx; |
36 | }; |
37 | |
38 | struct smi_node { |
39 | enum smi_bus_type bus_type; |
40 | struct smi_instance instances[]; |
41 | }; |
42 | |
43 | struct smi { |
44 | int i2c_num; |
45 | int spi_num; |
46 | struct i2c_client **i2c_devs; |
47 | struct spi_device **spi_devs; |
48 | }; |
49 | |
50 | static int smi_get_irq(struct platform_device *pdev, struct acpi_device *adev, |
51 | const struct smi_instance *inst) |
52 | { |
53 | int ret; |
54 | |
55 | switch (inst->flags & IRQ_RESOURCE_TYPE) { |
56 | case IRQ_RESOURCE_AUTO: |
57 | ret = acpi_dev_gpio_irq_get(adev, index: inst->irq_idx); |
58 | if (ret > 0) { |
59 | dev_dbg(&pdev->dev, "Using gpio irq\n" ); |
60 | break; |
61 | } |
62 | ret = platform_get_irq(pdev, inst->irq_idx); |
63 | if (ret > 0) { |
64 | dev_dbg(&pdev->dev, "Using platform irq\n" ); |
65 | break; |
66 | } |
67 | break; |
68 | case IRQ_RESOURCE_GPIO: |
69 | ret = acpi_dev_gpio_irq_get(adev, index: inst->irq_idx); |
70 | break; |
71 | case IRQ_RESOURCE_APIC: |
72 | ret = platform_get_irq(pdev, inst->irq_idx); |
73 | break; |
74 | default: |
75 | return 0; |
76 | } |
77 | if (ret < 0) |
78 | return dev_err_probe(dev: &pdev->dev, err: ret, fmt: "Error requesting irq at index %d\n" , |
79 | inst->irq_idx); |
80 | |
81 | return ret; |
82 | } |
83 | |
84 | static void smi_devs_unregister(struct smi *smi) |
85 | { |
86 | while (smi->i2c_num--) |
87 | i2c_unregister_device(client: smi->i2c_devs[smi->i2c_num]); |
88 | |
89 | while (smi->spi_num--) |
90 | spi_unregister_device(spi: smi->spi_devs[smi->spi_num]); |
91 | } |
92 | |
93 | /** |
94 | * smi_spi_probe - Instantiate multiple SPI devices from inst array |
95 | * @pdev: Platform device |
96 | * @smi: Internal struct for Serial multi instantiate driver |
97 | * @inst_array: Array of instances to probe |
98 | * |
99 | * Returns the number of SPI devices instantiate, Zero if none is found or a negative error code. |
100 | */ |
101 | static int smi_spi_probe(struct platform_device *pdev, struct smi *smi, |
102 | const struct smi_instance *inst_array) |
103 | { |
104 | struct device *dev = &pdev->dev; |
105 | struct acpi_device *adev = ACPI_COMPANION(dev); |
106 | struct spi_controller *ctlr; |
107 | struct spi_device *spi_dev; |
108 | char name[50]; |
109 | int i, ret, count; |
110 | |
111 | ret = acpi_spi_count_resources(adev); |
112 | if (ret < 0) |
113 | return ret; |
114 | if (!ret) |
115 | return -ENOENT; |
116 | |
117 | count = ret; |
118 | |
119 | smi->spi_devs = devm_kcalloc(dev, n: count, size: sizeof(*smi->spi_devs), GFP_KERNEL); |
120 | if (!smi->spi_devs) |
121 | return -ENOMEM; |
122 | |
123 | for (i = 0; i < count && inst_array[i].type; i++) { |
124 | |
125 | spi_dev = acpi_spi_device_alloc(NULL, adev, index: i); |
126 | if (IS_ERR(ptr: spi_dev)) { |
127 | ret = dev_err_probe(dev, err: PTR_ERR(ptr: spi_dev), fmt: "failed to allocate SPI device %s from ACPI\n" , |
128 | dev_name(dev: &adev->dev)); |
129 | goto error; |
130 | } |
131 | |
132 | ctlr = spi_dev->controller; |
133 | |
134 | strscpy(spi_dev->modalias, inst_array[i].type, sizeof(spi_dev->modalias)); |
135 | |
136 | ret = smi_get_irq(pdev, adev, inst: &inst_array[i]); |
137 | if (ret < 0) { |
138 | spi_dev_put(spi: spi_dev); |
139 | goto error; |
140 | } |
141 | spi_dev->irq = ret; |
142 | |
143 | snprintf(buf: name, size: sizeof(name), fmt: "%s-%s-%s.%d" , dev_name(dev: &ctlr->dev), dev_name(dev), |
144 | inst_array[i].type, i); |
145 | spi_dev->dev.init_name = name; |
146 | |
147 | ret = spi_add_device(spi: spi_dev); |
148 | if (ret) { |
149 | dev_err_probe(dev: &ctlr->dev, err: ret, fmt: "failed to add SPI device %s from ACPI\n" , |
150 | dev_name(dev: &adev->dev)); |
151 | spi_dev_put(spi: spi_dev); |
152 | goto error; |
153 | } |
154 | |
155 | dev_dbg(dev, "SPI device %s using chip select %u" , name, |
156 | spi_get_chipselect(spi_dev, 0)); |
157 | |
158 | smi->spi_devs[i] = spi_dev; |
159 | smi->spi_num++; |
160 | } |
161 | |
162 | if (smi->spi_num < count) { |
163 | dev_dbg(dev, "Error finding driver, idx %d\n" , i); |
164 | ret = -ENODEV; |
165 | goto error; |
166 | } |
167 | |
168 | dev_info(dev, "Instantiated %d SPI devices.\n" , smi->spi_num); |
169 | |
170 | return 0; |
171 | error: |
172 | smi_devs_unregister(smi); |
173 | |
174 | return ret; |
175 | } |
176 | |
177 | /** |
178 | * smi_i2c_probe - Instantiate multiple I2C devices from inst array |
179 | * @pdev: Platform device |
180 | * @smi: Internal struct for Serial multi instantiate driver |
181 | * @inst_array: Array of instances to probe |
182 | * |
183 | * Returns the number of I2C devices instantiate, Zero if none is found or a negative error code. |
184 | */ |
185 | static int smi_i2c_probe(struct platform_device *pdev, struct smi *smi, |
186 | const struct smi_instance *inst_array) |
187 | { |
188 | struct i2c_board_info board_info = {}; |
189 | struct device *dev = &pdev->dev; |
190 | struct acpi_device *adev = ACPI_COMPANION(dev); |
191 | char name[32]; |
192 | int i, ret, count; |
193 | |
194 | ret = i2c_acpi_client_count(adev); |
195 | if (ret < 0) |
196 | return ret; |
197 | if (!ret) |
198 | return -ENOENT; |
199 | |
200 | count = ret; |
201 | |
202 | smi->i2c_devs = devm_kcalloc(dev, n: count, size: sizeof(*smi->i2c_devs), GFP_KERNEL); |
203 | if (!smi->i2c_devs) |
204 | return -ENOMEM; |
205 | |
206 | for (i = 0; i < count && inst_array[i].type; i++) { |
207 | memset(&board_info, 0, sizeof(board_info)); |
208 | strscpy(board_info.type, inst_array[i].type, I2C_NAME_SIZE); |
209 | snprintf(buf: name, size: sizeof(name), fmt: "%s-%s.%d" , dev_name(dev), inst_array[i].type, i); |
210 | board_info.dev_name = name; |
211 | |
212 | ret = smi_get_irq(pdev, adev, inst: &inst_array[i]); |
213 | if (ret < 0) |
214 | goto error; |
215 | board_info.irq = ret; |
216 | |
217 | smi->i2c_devs[i] = i2c_acpi_new_device(dev, index: i, info: &board_info); |
218 | if (IS_ERR(ptr: smi->i2c_devs[i])) { |
219 | ret = dev_err_probe(dev, err: PTR_ERR(ptr: smi->i2c_devs[i]), |
220 | fmt: "Error creating i2c-client, idx %d\n" , i); |
221 | goto error; |
222 | } |
223 | smi->i2c_num++; |
224 | } |
225 | if (smi->i2c_num < count) { |
226 | dev_dbg(dev, "Error finding driver, idx %d\n" , i); |
227 | ret = -ENODEV; |
228 | goto error; |
229 | } |
230 | |
231 | dev_info(dev, "Instantiated %d I2C devices.\n" , smi->i2c_num); |
232 | |
233 | return 0; |
234 | error: |
235 | smi_devs_unregister(smi); |
236 | |
237 | return ret; |
238 | } |
239 | |
240 | static int smi_probe(struct platform_device *pdev) |
241 | { |
242 | struct device *dev = &pdev->dev; |
243 | const struct smi_node *node; |
244 | struct smi *smi; |
245 | int ret; |
246 | |
247 | node = device_get_match_data(dev); |
248 | if (!node) { |
249 | dev_dbg(dev, "Error ACPI match data is missing\n" ); |
250 | return -ENODEV; |
251 | } |
252 | |
253 | smi = devm_kzalloc(dev, size: sizeof(*smi), GFP_KERNEL); |
254 | if (!smi) |
255 | return -ENOMEM; |
256 | |
257 | platform_set_drvdata(pdev, data: smi); |
258 | |
259 | switch (node->bus_type) { |
260 | case SMI_I2C: |
261 | return smi_i2c_probe(pdev, smi, inst_array: node->instances); |
262 | case SMI_SPI: |
263 | return smi_spi_probe(pdev, smi, inst_array: node->instances); |
264 | case SMI_AUTO_DETECT: |
265 | /* |
266 | * For backwards-compatibility with the existing nodes I2C |
267 | * is checked first and if such entries are found ONLY I2C |
268 | * devices are created. Since some existing nodes that were |
269 | * already handled by this driver could also contain unrelated |
270 | * SpiSerialBus nodes that were previously ignored, and this |
271 | * preserves that behavior. |
272 | */ |
273 | ret = smi_i2c_probe(pdev, smi, inst_array: node->instances); |
274 | if (ret != -ENOENT) |
275 | return ret; |
276 | return smi_spi_probe(pdev, smi, inst_array: node->instances); |
277 | default: |
278 | return -EINVAL; |
279 | } |
280 | } |
281 | |
282 | static void smi_remove(struct platform_device *pdev) |
283 | { |
284 | struct smi *smi = platform_get_drvdata(pdev); |
285 | |
286 | smi_devs_unregister(smi); |
287 | } |
288 | |
289 | static const struct smi_node bsg1160_data = { |
290 | .instances = { |
291 | { "bmc150_accel" , IRQ_RESOURCE_GPIO, 0 }, |
292 | { "bmc150_magn" }, |
293 | { "bmg160" }, |
294 | {} |
295 | }, |
296 | .bus_type = SMI_I2C, |
297 | }; |
298 | |
299 | static const struct smi_node bsg2150_data = { |
300 | .instances = { |
301 | { "bmc150_accel" , IRQ_RESOURCE_GPIO, 0 }, |
302 | { "bmc150_magn" }, |
303 | /* The resources describe a 3th client, but it is not really there. */ |
304 | { "bsg2150_dummy_dev" }, |
305 | {} |
306 | }, |
307 | .bus_type = SMI_I2C, |
308 | }; |
309 | |
310 | static const struct smi_node int3515_data = { |
311 | .instances = { |
312 | { "tps6598x" , IRQ_RESOURCE_APIC, 0 }, |
313 | { "tps6598x" , IRQ_RESOURCE_APIC, 1 }, |
314 | { "tps6598x" , IRQ_RESOURCE_APIC, 2 }, |
315 | { "tps6598x" , IRQ_RESOURCE_APIC, 3 }, |
316 | {} |
317 | }, |
318 | .bus_type = SMI_I2C, |
319 | }; |
320 | |
321 | static const struct smi_node cs35l41_hda = { |
322 | .instances = { |
323 | { "cs35l41-hda" , IRQ_RESOURCE_AUTO, 0 }, |
324 | { "cs35l41-hda" , IRQ_RESOURCE_AUTO, 0 }, |
325 | { "cs35l41-hda" , IRQ_RESOURCE_AUTO, 0 }, |
326 | { "cs35l41-hda" , IRQ_RESOURCE_AUTO, 0 }, |
327 | {} |
328 | }, |
329 | .bus_type = SMI_AUTO_DETECT, |
330 | }; |
331 | |
332 | static const struct smi_node cs35l54_hda = { |
333 | .instances = { |
334 | { "cs35l54-hda" , IRQ_RESOURCE_AUTO, 0 }, |
335 | { "cs35l54-hda" , IRQ_RESOURCE_AUTO, 0 }, |
336 | { "cs35l54-hda" , IRQ_RESOURCE_AUTO, 0 }, |
337 | { "cs35l54-hda" , IRQ_RESOURCE_AUTO, 0 }, |
338 | /* a 5th entry is an alias address, not a real device */ |
339 | { "cs35l54-hda_dummy_dev" }, |
340 | {} |
341 | }, |
342 | .bus_type = SMI_AUTO_DETECT, |
343 | }; |
344 | |
345 | static const struct smi_node cs35l56_hda = { |
346 | .instances = { |
347 | { "cs35l56-hda" , IRQ_RESOURCE_AUTO, 0 }, |
348 | { "cs35l56-hda" , IRQ_RESOURCE_AUTO, 0 }, |
349 | { "cs35l56-hda" , IRQ_RESOURCE_AUTO, 0 }, |
350 | { "cs35l56-hda" , IRQ_RESOURCE_AUTO, 0 }, |
351 | /* a 5th entry is an alias address, not a real device */ |
352 | { "cs35l56-hda_dummy_dev" }, |
353 | {} |
354 | }, |
355 | .bus_type = SMI_AUTO_DETECT, |
356 | }; |
357 | |
358 | static const struct smi_node cs35l57_hda = { |
359 | .instances = { |
360 | { "cs35l57-hda" , IRQ_RESOURCE_AUTO, 0 }, |
361 | { "cs35l57-hda" , IRQ_RESOURCE_AUTO, 0 }, |
362 | { "cs35l57-hda" , IRQ_RESOURCE_AUTO, 0 }, |
363 | { "cs35l57-hda" , IRQ_RESOURCE_AUTO, 0 }, |
364 | /* a 5th entry is an alias address, not a real device */ |
365 | { "cs35l57-hda_dummy_dev" }, |
366 | {} |
367 | }, |
368 | .bus_type = SMI_AUTO_DETECT, |
369 | }; |
370 | |
371 | /* |
372 | * Note new device-ids must also be added to ignore_serial_bus_ids in |
373 | * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). |
374 | */ |
375 | static const struct acpi_device_id smi_acpi_ids[] = { |
376 | { "BSG1160" , (unsigned long)&bsg1160_data }, |
377 | { "BSG2150" , (unsigned long)&bsg2150_data }, |
378 | { "CSC3551" , (unsigned long)&cs35l41_hda }, |
379 | { "CSC3554" , (unsigned long)&cs35l54_hda }, |
380 | { "CSC3556" , (unsigned long)&cs35l56_hda }, |
381 | { "CSC3557" , (unsigned long)&cs35l57_hda }, |
382 | { "INT3515" , (unsigned long)&int3515_data }, |
383 | /* Non-conforming _HID for Cirrus Logic already released */ |
384 | { "CLSA0100" , (unsigned long)&cs35l41_hda }, |
385 | { "CLSA0101" , (unsigned long)&cs35l41_hda }, |
386 | { } |
387 | }; |
388 | MODULE_DEVICE_TABLE(acpi, smi_acpi_ids); |
389 | |
390 | static struct platform_driver smi_driver = { |
391 | .driver = { |
392 | .name = "Serial bus multi instantiate pseudo device driver" , |
393 | .acpi_match_table = smi_acpi_ids, |
394 | }, |
395 | .probe = smi_probe, |
396 | .remove_new = smi_remove, |
397 | }; |
398 | module_platform_driver(smi_driver); |
399 | |
400 | MODULE_DESCRIPTION("Serial multi instantiate pseudo device driver" ); |
401 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>" ); |
402 | MODULE_LICENSE("GPL" ); |
403 | |