18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * I2C multi-instantiate driver, pseudo driver to instantiate multiple 48c2ecf20Sopenharmony_ci * i2c-clients from a single fwnode. 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright 2018 Hans de Goede <hdegoede@redhat.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/acpi.h> 108c2ecf20Sopenharmony_ci#include <linux/bits.h> 118c2ecf20Sopenharmony_ci#include <linux/i2c.h> 128c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 138c2ecf20Sopenharmony_ci#include <linux/kernel.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 168c2ecf20Sopenharmony_ci#include <linux/types.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define IRQ_RESOURCE_TYPE GENMASK(1, 0) 198c2ecf20Sopenharmony_ci#define IRQ_RESOURCE_NONE 0 208c2ecf20Sopenharmony_ci#define IRQ_RESOURCE_GPIO 1 218c2ecf20Sopenharmony_ci#define IRQ_RESOURCE_APIC 2 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistruct i2c_inst_data { 248c2ecf20Sopenharmony_ci const char *type; 258c2ecf20Sopenharmony_ci unsigned int flags; 268c2ecf20Sopenharmony_ci int irq_idx; 278c2ecf20Sopenharmony_ci}; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistruct i2c_multi_inst_data { 308c2ecf20Sopenharmony_ci int num_clients; 318c2ecf20Sopenharmony_ci struct i2c_client *clients[]; 328c2ecf20Sopenharmony_ci}; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic int i2c_multi_inst_count(struct acpi_resource *ares, void *data) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci struct acpi_resource_i2c_serialbus *sb; 378c2ecf20Sopenharmony_ci int *count = data; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci if (i2c_acpi_get_i2c_resource(ares, &sb)) 408c2ecf20Sopenharmony_ci *count = *count + 1; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci return 1; 438c2ecf20Sopenharmony_ci} 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic int i2c_multi_inst_count_resources(struct acpi_device *adev) 468c2ecf20Sopenharmony_ci{ 478c2ecf20Sopenharmony_ci LIST_HEAD(r); 488c2ecf20Sopenharmony_ci int count = 0; 498c2ecf20Sopenharmony_ci int ret; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci ret = acpi_dev_get_resources(adev, &r, i2c_multi_inst_count, &count); 528c2ecf20Sopenharmony_ci if (ret < 0) 538c2ecf20Sopenharmony_ci return ret; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci acpi_dev_free_resource_list(&r); 568c2ecf20Sopenharmony_ci return count; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic int i2c_multi_inst_probe(struct platform_device *pdev) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci struct i2c_multi_inst_data *multi; 628c2ecf20Sopenharmony_ci const struct acpi_device_id *match; 638c2ecf20Sopenharmony_ci const struct i2c_inst_data *inst_data; 648c2ecf20Sopenharmony_ci struct i2c_board_info board_info = {}; 658c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 668c2ecf20Sopenharmony_ci struct acpi_device *adev; 678c2ecf20Sopenharmony_ci char name[32]; 688c2ecf20Sopenharmony_ci int i, ret; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci match = acpi_match_device(dev->driver->acpi_match_table, dev); 718c2ecf20Sopenharmony_ci if (!match) { 728c2ecf20Sopenharmony_ci dev_err(dev, "Error ACPI match data is missing\n"); 738c2ecf20Sopenharmony_ci return -ENODEV; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci inst_data = (const struct i2c_inst_data *)match->driver_data; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci adev = ACPI_COMPANION(dev); 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci /* Count number of clients to instantiate */ 808c2ecf20Sopenharmony_ci ret = i2c_multi_inst_count_resources(adev); 818c2ecf20Sopenharmony_ci if (ret < 0) 828c2ecf20Sopenharmony_ci return ret; 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci multi = devm_kmalloc(dev, struct_size(multi, clients, ret), GFP_KERNEL); 858c2ecf20Sopenharmony_ci if (!multi) 868c2ecf20Sopenharmony_ci return -ENOMEM; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci multi->num_clients = ret; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci for (i = 0; i < multi->num_clients && inst_data[i].type; i++) { 918c2ecf20Sopenharmony_ci memset(&board_info, 0, sizeof(board_info)); 928c2ecf20Sopenharmony_ci strlcpy(board_info.type, inst_data[i].type, I2C_NAME_SIZE); 938c2ecf20Sopenharmony_ci snprintf(name, sizeof(name), "%s-%s.%d", dev_name(dev), 948c2ecf20Sopenharmony_ci inst_data[i].type, i); 958c2ecf20Sopenharmony_ci board_info.dev_name = name; 968c2ecf20Sopenharmony_ci switch (inst_data[i].flags & IRQ_RESOURCE_TYPE) { 978c2ecf20Sopenharmony_ci case IRQ_RESOURCE_GPIO: 988c2ecf20Sopenharmony_ci ret = acpi_dev_gpio_irq_get(adev, inst_data[i].irq_idx); 998c2ecf20Sopenharmony_ci if (ret < 0) { 1008c2ecf20Sopenharmony_ci dev_err(dev, "Error requesting irq at index %d: %d\n", 1018c2ecf20Sopenharmony_ci inst_data[i].irq_idx, ret); 1028c2ecf20Sopenharmony_ci goto error; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci board_info.irq = ret; 1058c2ecf20Sopenharmony_ci break; 1068c2ecf20Sopenharmony_ci case IRQ_RESOURCE_APIC: 1078c2ecf20Sopenharmony_ci ret = platform_get_irq(pdev, inst_data[i].irq_idx); 1088c2ecf20Sopenharmony_ci if (ret < 0) { 1098c2ecf20Sopenharmony_ci dev_dbg(dev, "Error requesting irq at index %d: %d\n", 1108c2ecf20Sopenharmony_ci inst_data[i].irq_idx, ret); 1118c2ecf20Sopenharmony_ci goto error; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci board_info.irq = ret; 1148c2ecf20Sopenharmony_ci break; 1158c2ecf20Sopenharmony_ci default: 1168c2ecf20Sopenharmony_ci board_info.irq = 0; 1178c2ecf20Sopenharmony_ci break; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info); 1208c2ecf20Sopenharmony_ci if (IS_ERR(multi->clients[i])) { 1218c2ecf20Sopenharmony_ci ret = PTR_ERR(multi->clients[i]); 1228c2ecf20Sopenharmony_ci if (ret != -EPROBE_DEFER) 1238c2ecf20Sopenharmony_ci dev_err(dev, "Error creating i2c-client, idx %d\n", i); 1248c2ecf20Sopenharmony_ci goto error; 1258c2ecf20Sopenharmony_ci } 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci if (i < multi->num_clients) { 1288c2ecf20Sopenharmony_ci dev_err(dev, "Error finding driver, idx %d\n", i); 1298c2ecf20Sopenharmony_ci ret = -ENODEV; 1308c2ecf20Sopenharmony_ci goto error; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, multi); 1348c2ecf20Sopenharmony_ci return 0; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cierror: 1378c2ecf20Sopenharmony_ci while (--i >= 0) 1388c2ecf20Sopenharmony_ci i2c_unregister_device(multi->clients[i]); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci return ret; 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_cistatic int i2c_multi_inst_remove(struct platform_device *pdev) 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci struct i2c_multi_inst_data *multi = platform_get_drvdata(pdev); 1468c2ecf20Sopenharmony_ci int i; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci for (i = 0; i < multi->num_clients; i++) 1498c2ecf20Sopenharmony_ci i2c_unregister_device(multi->clients[i]); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return 0; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic const struct i2c_inst_data bsg1160_data[] = { 1558c2ecf20Sopenharmony_ci { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, 1568c2ecf20Sopenharmony_ci { "bmc150_magn" }, 1578c2ecf20Sopenharmony_ci { "bmg160" }, 1588c2ecf20Sopenharmony_ci {} 1598c2ecf20Sopenharmony_ci}; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic const struct i2c_inst_data bsg2150_data[] = { 1628c2ecf20Sopenharmony_ci { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, 1638c2ecf20Sopenharmony_ci { "bmc150_magn" }, 1648c2ecf20Sopenharmony_ci /* The resources describe a 3th client, but it is not really there. */ 1658c2ecf20Sopenharmony_ci { "bsg2150_dummy_dev" }, 1668c2ecf20Sopenharmony_ci {} 1678c2ecf20Sopenharmony_ci}; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci/* 1708c2ecf20Sopenharmony_ci * Device with _HID INT3515 (TI PD controllers) has some unresolved interrupt 1718c2ecf20Sopenharmony_ci * issues. The most common problem seen is interrupt flood. 1728c2ecf20Sopenharmony_ci * 1738c2ecf20Sopenharmony_ci * There are at least two known causes. Firstly, on some boards, the 1748c2ecf20Sopenharmony_ci * I2CSerialBus resource index does not match the Interrupt resource, i.e. they 1758c2ecf20Sopenharmony_ci * are not one-to-one mapped like in the array below. Secondly, on some boards 1768c2ecf20Sopenharmony_ci * the IRQ line from the PD controller is not actually connected at all. But the 1778c2ecf20Sopenharmony_ci * interrupt flood is also seen on some boards where those are not a problem, so 1788c2ecf20Sopenharmony_ci * there are some other problems as well. 1798c2ecf20Sopenharmony_ci * 1808c2ecf20Sopenharmony_ci * Because of the issues with the interrupt, the device is disabled for now. If 1818c2ecf20Sopenharmony_ci * you wish to debug the issues, uncomment the below, and add an entry for the 1828c2ecf20Sopenharmony_ci * INT3515 device to the i2c_multi_instance_ids table. 1838c2ecf20Sopenharmony_ci * 1848c2ecf20Sopenharmony_ci * static const struct i2c_inst_data int3515_data[] = { 1858c2ecf20Sopenharmony_ci * { "tps6598x", IRQ_RESOURCE_APIC, 0 }, 1868c2ecf20Sopenharmony_ci * { "tps6598x", IRQ_RESOURCE_APIC, 1 }, 1878c2ecf20Sopenharmony_ci * { "tps6598x", IRQ_RESOURCE_APIC, 2 }, 1888c2ecf20Sopenharmony_ci * { "tps6598x", IRQ_RESOURCE_APIC, 3 }, 1898c2ecf20Sopenharmony_ci * { } 1908c2ecf20Sopenharmony_ci * }; 1918c2ecf20Sopenharmony_ci */ 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci/* 1948c2ecf20Sopenharmony_ci * Note new device-ids must also be added to i2c_multi_instantiate_ids in 1958c2ecf20Sopenharmony_ci * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). 1968c2ecf20Sopenharmony_ci */ 1978c2ecf20Sopenharmony_cistatic const struct acpi_device_id i2c_multi_inst_acpi_ids[] = { 1988c2ecf20Sopenharmony_ci { "BSG1160", (unsigned long)bsg1160_data }, 1998c2ecf20Sopenharmony_ci { "BSG2150", (unsigned long)bsg2150_data }, 2008c2ecf20Sopenharmony_ci { } 2018c2ecf20Sopenharmony_ci}; 2028c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistatic struct platform_driver i2c_multi_inst_driver = { 2058c2ecf20Sopenharmony_ci .driver = { 2068c2ecf20Sopenharmony_ci .name = "I2C multi instantiate pseudo device driver", 2078c2ecf20Sopenharmony_ci .acpi_match_table = ACPI_PTR(i2c_multi_inst_acpi_ids), 2088c2ecf20Sopenharmony_ci }, 2098c2ecf20Sopenharmony_ci .probe = i2c_multi_inst_probe, 2108c2ecf20Sopenharmony_ci .remove = i2c_multi_inst_remove, 2118c2ecf20Sopenharmony_ci}; 2128c2ecf20Sopenharmony_cimodule_platform_driver(i2c_multi_inst_driver); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("I2C multi instantiate pseudo device driver"); 2158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 2168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 217