1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * I2C multi-instantiate driver, pseudo driver to instantiate multiple
4 * i2c-clients from a single fwnode.
5 *
6 * Copyright 2018 Hans de Goede <hdegoede@redhat.com>
7 */
8
9 #include <linux/acpi.h>
10 #include <linux/i2c.h>
11 #include <linux/interrupt.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/platform_device.h>
15
16 struct i2c_inst_data {
17 const char *type;
18 int gpio_irq_idx;
19 };
20
21 struct i2c_multi_inst_data {
22 int num_clients;
23 struct i2c_client *clients[0];
24 };
25
i2c_multi_inst_probe(struct platform_device * pdev)26 static int i2c_multi_inst_probe(struct platform_device *pdev)
27 {
28 struct i2c_multi_inst_data *multi;
29 const struct acpi_device_id *match;
30 const struct i2c_inst_data *inst_data;
31 struct i2c_board_info board_info = {};
32 struct device *dev = &pdev->dev;
33 struct acpi_device *adev;
34 char name[32];
35 int i, ret;
36
37 match = acpi_match_device(dev->driver->acpi_match_table, dev);
38 if (!match) {
39 dev_err(dev, "Error ACPI match data is missing\n");
40 return -ENODEV;
41 }
42 inst_data = (const struct i2c_inst_data *)match->driver_data;
43
44 adev = ACPI_COMPANION(dev);
45
46 /* Count number of clients to instantiate */
47 for (i = 0; inst_data[i].type; i++) {}
48
49 multi = devm_kmalloc(dev,
50 offsetof(struct i2c_multi_inst_data, clients[i]),
51 GFP_KERNEL);
52 if (!multi)
53 return -ENOMEM;
54
55 multi->num_clients = i;
56
57 for (i = 0; i < multi->num_clients; i++) {
58 memset(&board_info, 0, sizeof(board_info));
59 strlcpy(board_info.type, inst_data[i].type, I2C_NAME_SIZE);
60 snprintf(name, sizeof(name), "%s-%s", match->id,
61 inst_data[i].type);
62 board_info.dev_name = name;
63 board_info.irq = 0;
64 if (inst_data[i].gpio_irq_idx != -1) {
65 ret = acpi_dev_gpio_irq_get(adev,
66 inst_data[i].gpio_irq_idx);
67 if (ret < 0) {
68 dev_err(dev, "Error requesting irq at index %d: %d\n",
69 inst_data[i].gpio_irq_idx, ret);
70 goto error;
71 }
72 board_info.irq = ret;
73 }
74 multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info);
75 if (!multi->clients[i]) {
76 dev_err(dev, "Error creating i2c-client, idx %d\n", i);
77 ret = -ENODEV;
78 goto error;
79 }
80 }
81
82 platform_set_drvdata(pdev, multi);
83 return 0;
84
85 error:
86 while (--i >= 0)
87 i2c_unregister_device(multi->clients[i]);
88
89 return ret;
90 }
91
i2c_multi_inst_remove(struct platform_device * pdev)92 static int i2c_multi_inst_remove(struct platform_device *pdev)
93 {
94 struct i2c_multi_inst_data *multi = platform_get_drvdata(pdev);
95 int i;
96
97 for (i = 0; i < multi->num_clients; i++)
98 i2c_unregister_device(multi->clients[i]);
99
100 return 0;
101 }
102
103 static const struct i2c_inst_data bsg1160_data[] = {
104 { "bmc150_accel", 0 },
105 { "bmc150_magn", -1 },
106 { "bmg160", -1 },
107 {}
108 };
109
110 /*
111 * Note new device-ids must also be added to i2c_multi_instantiate_ids in
112 * drivers/acpi/scan.c: acpi_device_enumeration_by_parent().
113 */
114 static const struct acpi_device_id i2c_multi_inst_acpi_ids[] = {
115 { "BSG1160", (unsigned long)bsg1160_data },
116 { }
117 };
118 MODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids);
119
120 static struct platform_driver i2c_multi_inst_driver = {
121 .driver = {
122 .name = "I2C multi instantiate pseudo device driver",
123 .acpi_match_table = ACPI_PTR(i2c_multi_inst_acpi_ids),
124 },
125 .probe = i2c_multi_inst_probe,
126 .remove = i2c_multi_inst_remove,
127 };
128 module_platform_driver(i2c_multi_inst_driver);
129
130 MODULE_DESCRIPTION("I2C multi instantiate pseudo device driver");
131 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
132 MODULE_LICENSE("GPL");
133