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/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> 17 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 22 23struct i2c_inst_data { 24 const char *type; 25 unsigned int flags; 26 int irq_idx; 27}; 28 29struct i2c_multi_inst_data { 30 int num_clients; 31 struct i2c_client *clients[]; 32}; 33 34static int i2c_multi_inst_count(struct acpi_resource *ares, void *data) 35{ 36 struct acpi_resource_i2c_serialbus *sb; 37 int *count = data; 38 39 if (i2c_acpi_get_i2c_resource(ares, &sb)) 40 *count = *count + 1; 41 42 return 1; 43} 44 45static int i2c_multi_inst_count_resources(struct acpi_device *adev) 46{ 47 LIST_HEAD(r); 48 int count = 0; 49 int ret; 50 51 ret = acpi_dev_get_resources(adev, &r, i2c_multi_inst_count, &count); 52 if (ret < 0) 53 return ret; 54 55 acpi_dev_free_resource_list(&r); 56 return count; 57} 58 59static int i2c_multi_inst_probe(struct platform_device *pdev) 60{ 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; 67 char name[32]; 68 int i, ret; 69 70 match = acpi_match_device(dev->driver->acpi_match_table, dev); 71 if (!match) { 72 dev_err(dev, "Error ACPI match data is missing\n"); 73 return -ENODEV; 74 } 75 inst_data = (const struct i2c_inst_data *)match->driver_data; 76 77 adev = ACPI_COMPANION(dev); 78 79 /* Count number of clients to instantiate */ 80 ret = i2c_multi_inst_count_resources(adev); 81 if (ret < 0) 82 return ret; 83 84 multi = devm_kmalloc(dev, struct_size(multi, clients, ret), GFP_KERNEL); 85 if (!multi) 86 return -ENOMEM; 87 88 multi->num_clients = ret; 89 90 for (i = 0; i < multi->num_clients && inst_data[i].type; i++) { 91 memset(&board_info, 0, sizeof(board_info)); 92 strlcpy(board_info.type, inst_data[i].type, I2C_NAME_SIZE); 93 snprintf(name, sizeof(name), "%s-%s.%d", dev_name(dev), 94 inst_data[i].type, i); 95 board_info.dev_name = name; 96 switch (inst_data[i].flags & IRQ_RESOURCE_TYPE) { 97 case IRQ_RESOURCE_GPIO: 98 ret = acpi_dev_gpio_irq_get(adev, inst_data[i].irq_idx); 99 if (ret < 0) { 100 dev_err(dev, "Error requesting irq at index %d: %d\n", 101 inst_data[i].irq_idx, ret); 102 goto error; 103 } 104 board_info.irq = ret; 105 break; 106 case IRQ_RESOURCE_APIC: 107 ret = platform_get_irq(pdev, inst_data[i].irq_idx); 108 if (ret < 0) { 109 dev_dbg(dev, "Error requesting irq at index %d: %d\n", 110 inst_data[i].irq_idx, ret); 111 goto error; 112 } 113 board_info.irq = ret; 114 break; 115 default: 116 board_info.irq = 0; 117 break; 118 } 119 multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info); 120 if (IS_ERR(multi->clients[i])) { 121 ret = PTR_ERR(multi->clients[i]); 122 if (ret != -EPROBE_DEFER) 123 dev_err(dev, "Error creating i2c-client, idx %d\n", i); 124 goto error; 125 } 126 } 127 if (i < multi->num_clients) { 128 dev_err(dev, "Error finding driver, idx %d\n", i); 129 ret = -ENODEV; 130 goto error; 131 } 132 133 platform_set_drvdata(pdev, multi); 134 return 0; 135 136error: 137 while (--i >= 0) 138 i2c_unregister_device(multi->clients[i]); 139 140 return ret; 141} 142 143static int i2c_multi_inst_remove(struct platform_device *pdev) 144{ 145 struct i2c_multi_inst_data *multi = platform_get_drvdata(pdev); 146 int i; 147 148 for (i = 0; i < multi->num_clients; i++) 149 i2c_unregister_device(multi->clients[i]); 150 151 return 0; 152} 153 154static const struct i2c_inst_data bsg1160_data[] = { 155 { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, 156 { "bmc150_magn" }, 157 { "bmg160" }, 158 {} 159}; 160 161static const struct i2c_inst_data bsg2150_data[] = { 162 { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, 163 { "bmc150_magn" }, 164 /* The resources describe a 3th client, but it is not really there. */ 165 { "bsg2150_dummy_dev" }, 166 {} 167}; 168 169/* 170 * Device with _HID INT3515 (TI PD controllers) has some unresolved interrupt 171 * issues. The most common problem seen is interrupt flood. 172 * 173 * There are at least two known causes. Firstly, on some boards, the 174 * I2CSerialBus resource index does not match the Interrupt resource, i.e. they 175 * are not one-to-one mapped like in the array below. Secondly, on some boards 176 * the IRQ line from the PD controller is not actually connected at all. But the 177 * interrupt flood is also seen on some boards where those are not a problem, so 178 * there are some other problems as well. 179 * 180 * Because of the issues with the interrupt, the device is disabled for now. If 181 * you wish to debug the issues, uncomment the below, and add an entry for the 182 * INT3515 device to the i2c_multi_instance_ids table. 183 * 184 * static const struct i2c_inst_data int3515_data[] = { 185 * { "tps6598x", IRQ_RESOURCE_APIC, 0 }, 186 * { "tps6598x", IRQ_RESOURCE_APIC, 1 }, 187 * { "tps6598x", IRQ_RESOURCE_APIC, 2 }, 188 * { "tps6598x", IRQ_RESOURCE_APIC, 3 }, 189 * { } 190 * }; 191 */ 192 193/* 194 * Note new device-ids must also be added to i2c_multi_instantiate_ids in 195 * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). 196 */ 197static const struct acpi_device_id i2c_multi_inst_acpi_ids[] = { 198 { "BSG1160", (unsigned long)bsg1160_data }, 199 { "BSG2150", (unsigned long)bsg2150_data }, 200 { } 201}; 202MODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids); 203 204static struct platform_driver i2c_multi_inst_driver = { 205 .driver = { 206 .name = "I2C multi instantiate pseudo device driver", 207 .acpi_match_table = ACPI_PTR(i2c_multi_inst_acpi_ids), 208 }, 209 .probe = i2c_multi_inst_probe, 210 .remove = i2c_multi_inst_remove, 211}; 212module_platform_driver(i2c_multi_inst_driver); 213 214MODULE_DESCRIPTION("I2C multi instantiate pseudo device driver"); 215MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 216MODULE_LICENSE("GPL"); 217