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/property.h>
17 #include <linux/types.h>
19 #define IRQ_RESOURCE_TYPE GENMASK(1, 0)
20 #define IRQ_RESOURCE_NONE 0
21 #define IRQ_RESOURCE_GPIO 1
22 #define IRQ_RESOURCE_APIC 2
24 struct i2c_inst_data
{
30 struct i2c_multi_inst_data
{
32 struct i2c_client
*clients
[];
35 static int i2c_multi_inst_count(struct acpi_resource
*ares
, void *data
)
37 struct acpi_resource_i2c_serialbus
*sb
;
40 if (i2c_acpi_get_i2c_resource(ares
, &sb
))
46 static int i2c_multi_inst_count_resources(struct acpi_device
*adev
)
52 ret
= acpi_dev_get_resources(adev
, &r
, i2c_multi_inst_count
, &count
);
56 acpi_dev_free_resource_list(&r
);
60 static int i2c_multi_inst_probe(struct platform_device
*pdev
)
62 struct i2c_multi_inst_data
*multi
;
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 inst_data
= device_get_match_data(dev
);
72 dev_err(dev
, "Error ACPI match data is missing\n");
76 adev
= ACPI_COMPANION(dev
);
78 /* Count number of clients to instantiate */
79 ret
= i2c_multi_inst_count_resources(adev
);
83 multi
= devm_kmalloc(dev
, struct_size(multi
, clients
, ret
), GFP_KERNEL
);
87 multi
->num_clients
= ret
;
89 for (i
= 0; i
< multi
->num_clients
&& inst_data
[i
].type
; i
++) {
90 memset(&board_info
, 0, sizeof(board_info
));
91 strlcpy(board_info
.type
, inst_data
[i
].type
, I2C_NAME_SIZE
);
92 snprintf(name
, sizeof(name
), "%s-%s.%d", dev_name(dev
),
93 inst_data
[i
].type
, i
);
94 board_info
.dev_name
= name
;
95 switch (inst_data
[i
].flags
& IRQ_RESOURCE_TYPE
) {
96 case IRQ_RESOURCE_GPIO
:
97 ret
= acpi_dev_gpio_irq_get(adev
, inst_data
[i
].irq_idx
);
99 dev_err(dev
, "Error requesting irq at index %d: %d\n",
100 inst_data
[i
].irq_idx
, ret
);
103 board_info
.irq
= ret
;
105 case IRQ_RESOURCE_APIC
:
106 ret
= platform_get_irq(pdev
, inst_data
[i
].irq_idx
);
108 dev_dbg(dev
, "Error requesting irq at index %d: %d\n",
109 inst_data
[i
].irq_idx
, ret
);
112 board_info
.irq
= ret
;
118 multi
->clients
[i
] = i2c_acpi_new_device(dev
, i
, &board_info
);
119 if (IS_ERR(multi
->clients
[i
])) {
120 ret
= dev_err_probe(dev
, PTR_ERR(multi
->clients
[i
]),
121 "Error creating i2c-client, idx %d\n", i
);
125 if (i
< multi
->num_clients
) {
126 dev_err(dev
, "Error finding driver, idx %d\n", i
);
131 platform_set_drvdata(pdev
, multi
);
136 i2c_unregister_device(multi
->clients
[i
]);
141 static int i2c_multi_inst_remove(struct platform_device
*pdev
)
143 struct i2c_multi_inst_data
*multi
= platform_get_drvdata(pdev
);
146 for (i
= 0; i
< multi
->num_clients
; i
++)
147 i2c_unregister_device(multi
->clients
[i
]);
152 static const struct i2c_inst_data bsg1160_data
[] = {
153 { "bmc150_accel", IRQ_RESOURCE_GPIO
, 0 },
159 static const struct i2c_inst_data bsg2150_data
[] = {
160 { "bmc150_accel", IRQ_RESOURCE_GPIO
, 0 },
162 /* The resources describe a 3th client, but it is not really there. */
163 { "bsg2150_dummy_dev" },
167 static const struct i2c_inst_data int3515_data
[] = {
168 { "tps6598x", IRQ_RESOURCE_APIC
, 0 },
169 { "tps6598x", IRQ_RESOURCE_APIC
, 1 },
170 { "tps6598x", IRQ_RESOURCE_APIC
, 2 },
171 { "tps6598x", IRQ_RESOURCE_APIC
, 3 },
176 * Note new device-ids must also be added to i2c_multi_instantiate_ids in
177 * drivers/acpi/scan.c: acpi_device_enumeration_by_parent().
179 static const struct acpi_device_id i2c_multi_inst_acpi_ids
[] = {
180 { "BSG1160", (unsigned long)bsg1160_data
},
181 { "BSG2150", (unsigned long)bsg2150_data
},
182 { "INT3515", (unsigned long)int3515_data
},
185 MODULE_DEVICE_TABLE(acpi
, i2c_multi_inst_acpi_ids
);
187 static struct platform_driver i2c_multi_inst_driver
= {
189 .name
= "I2C multi instantiate pseudo device driver",
190 .acpi_match_table
= i2c_multi_inst_acpi_ids
,
192 .probe
= i2c_multi_inst_probe
,
193 .remove
= i2c_multi_inst_remove
,
195 module_platform_driver(i2c_multi_inst_driver
);
197 MODULE_DESCRIPTION("I2C multi instantiate pseudo device driver");
198 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
199 MODULE_LICENSE("GPL");