1 // SPDX-License-Identifier: GPL-2.0+
3 * I2C multi-instantiate driver, pseudo driver to instantiate multiple
4 * i2c-clients from a single fwnode.
6 * Copyright 2018 Hans de Goede <hdegoede@redhat.com>
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/types.h>
18 #define IRQ_RESOURCE_TYPE GENMASK(1, 0)
19 #define IRQ_RESOURCE_NONE 0
20 #define IRQ_RESOURCE_GPIO 1
21 #define IRQ_RESOURCE_APIC 2
23 struct i2c_inst_data
{
29 struct i2c_multi_inst_data
{
31 struct i2c_client
*clients
[0];
34 static int i2c_multi_inst_count(struct acpi_resource
*ares
, void *data
)
36 struct acpi_resource_i2c_serialbus
*sb
;
39 if (i2c_acpi_get_i2c_resource(ares
, &sb
))
45 static int i2c_multi_inst_count_resources(struct acpi_device
*adev
)
51 ret
= acpi_dev_get_resources(adev
, &r
, i2c_multi_inst_count
, &count
);
55 acpi_dev_free_resource_list(&r
);
59 static int i2c_multi_inst_probe(struct platform_device
*pdev
)
61 struct i2c_multi_inst_data
*multi
;
62 const struct acpi_device_id
*match
;
63 const struct i2c_inst_data
*inst_data
;
64 struct i2c_board_info board_info
= {};
65 struct device
*dev
= &pdev
->dev
;
66 struct acpi_device
*adev
;
70 match
= acpi_match_device(dev
->driver
->acpi_match_table
, dev
);
72 dev_err(dev
, "Error ACPI match data is missing\n");
75 inst_data
= (const struct i2c_inst_data
*)match
->driver_data
;
77 adev
= ACPI_COMPANION(dev
);
79 /* Count number of clients to instantiate */
80 ret
= i2c_multi_inst_count_resources(adev
);
84 multi
= devm_kmalloc(dev
,
85 offsetof(struct i2c_multi_inst_data
, clients
[ret
]),
90 multi
->num_clients
= ret
;
92 for (i
= 0; i
< multi
->num_clients
&& inst_data
[i
].type
; i
++) {
93 memset(&board_info
, 0, sizeof(board_info
));
94 strlcpy(board_info
.type
, inst_data
[i
].type
, I2C_NAME_SIZE
);
95 snprintf(name
, sizeof(name
), "%s-%s.%d", match
->id
,
96 inst_data
[i
].type
, i
);
97 board_info
.dev_name
= name
;
98 switch (inst_data
[i
].flags
& IRQ_RESOURCE_TYPE
) {
99 case IRQ_RESOURCE_GPIO
:
100 ret
= acpi_dev_gpio_irq_get(adev
, inst_data
[i
].irq_idx
);
102 dev_err(dev
, "Error requesting irq at index %d: %d\n",
103 inst_data
[i
].irq_idx
, ret
);
106 board_info
.irq
= ret
;
108 case IRQ_RESOURCE_APIC
:
109 ret
= platform_get_irq(pdev
, inst_data
[i
].irq_idx
);
111 dev_dbg(dev
, "Error requesting irq at index %d: %d\n",
112 inst_data
[i
].irq_idx
, ret
);
114 board_info
.irq
= ret
;
120 multi
->clients
[i
] = i2c_acpi_new_device(dev
, i
, &board_info
);
121 if (IS_ERR(multi
->clients
[i
])) {
122 ret
= PTR_ERR(multi
->clients
[i
]);
123 if (ret
!= -EPROBE_DEFER
)
124 dev_err(dev
, "Error creating i2c-client, idx %d\n", i
);
128 if (i
< multi
->num_clients
) {
129 dev_err(dev
, "Error finding driver, idx %d\n", i
);
134 platform_set_drvdata(pdev
, multi
);
139 i2c_unregister_device(multi
->clients
[i
]);
144 static int i2c_multi_inst_remove(struct platform_device
*pdev
)
146 struct i2c_multi_inst_data
*multi
= platform_get_drvdata(pdev
);
149 for (i
= 0; i
< multi
->num_clients
; i
++)
150 i2c_unregister_device(multi
->clients
[i
]);
155 static const struct i2c_inst_data bsg1160_data
[] = {
156 { "bmc150_accel", IRQ_RESOURCE_GPIO
, 0 },
162 static const struct i2c_inst_data bsg2150_data
[] = {
163 { "bmc150_accel", IRQ_RESOURCE_GPIO
, 0 },
165 /* The resources describe a 3th client, but it is not really there. */
166 { "bsg2150_dummy_dev" },
170 static const struct i2c_inst_data int3515_data
[] = {
171 { "tps6598x", IRQ_RESOURCE_APIC
, 0 },
172 { "tps6598x", IRQ_RESOURCE_APIC
, 1 },
173 { "tps6598x", IRQ_RESOURCE_APIC
, 2 },
174 { "tps6598x", IRQ_RESOURCE_APIC
, 3 },
179 * Note new device-ids must also be added to i2c_multi_instantiate_ids in
180 * drivers/acpi/scan.c: acpi_device_enumeration_by_parent().
182 static const struct acpi_device_id i2c_multi_inst_acpi_ids
[] = {
183 { "BSG1160", (unsigned long)bsg1160_data
},
184 { "BSG2150", (unsigned long)bsg2150_data
},
185 { "INT3515", (unsigned long)int3515_data
},
188 MODULE_DEVICE_TABLE(acpi
, i2c_multi_inst_acpi_ids
);
190 static struct platform_driver i2c_multi_inst_driver
= {
192 .name
= "I2C multi instantiate pseudo device driver",
193 .acpi_match_table
= ACPI_PTR(i2c_multi_inst_acpi_ids
),
195 .probe
= i2c_multi_inst_probe
,
196 .remove
= i2c_multi_inst_remove
,
198 module_platform_driver(i2c_multi_inst_driver
);
200 MODULE_DESCRIPTION("I2C multi instantiate pseudo device driver");
201 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
202 MODULE_LICENSE("GPL");