Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 | // SPDX-License-Identifier: GPL-2.0+ /* * Serial multi-instantiate driver, pseudo driver to instantiate multiple * client devices from a single fwnode. * * Copyright 2018 Hans de Goede <hdegoede@redhat.com> */ #include <linux/acpi.h> #include <linux/bits.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/spi/spi.h> #include <linux/types.h> #define IRQ_RESOURCE_TYPE GENMASK(1, 0) #define IRQ_RESOURCE_NONE 0 #define IRQ_RESOURCE_GPIO 1 #define IRQ_RESOURCE_APIC 2 enum smi_bus_type { SMI_I2C, SMI_SPI, SMI_AUTO_DETECT, }; struct smi_instance { const char *type; unsigned int flags; int irq_idx; }; struct smi_node { enum smi_bus_type bus_type; struct smi_instance instances[]; }; struct smi { int i2c_num; int spi_num; struct i2c_client **i2c_devs; struct spi_device **spi_devs; }; static int smi_get_irq(struct platform_device *pdev, struct acpi_device *adev, const struct smi_instance *inst) { int ret; switch (inst->flags & IRQ_RESOURCE_TYPE) { case IRQ_RESOURCE_GPIO: ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx); break; case IRQ_RESOURCE_APIC: ret = platform_get_irq(pdev, inst->irq_idx); break; default: return 0; } if (ret < 0) return dev_err_probe(&pdev->dev, ret, "Error requesting irq at index %d\n", inst->irq_idx); return ret; } static void smi_devs_unregister(struct smi *smi) { while (smi->i2c_num--) i2c_unregister_device(smi->i2c_devs[smi->i2c_num]); while (smi->spi_num--) spi_unregister_device(smi->spi_devs[smi->spi_num]); } /** * smi_spi_probe - Instantiate multiple SPI devices from inst array * @pdev: Platform device * @smi: Internal struct for Serial multi instantiate driver * @inst_array: Array of instances to probe * * Returns the number of SPI devices instantiate, Zero if none is found or a negative error code. */ static int smi_spi_probe(struct platform_device *pdev, struct smi *smi, const struct smi_instance *inst_array) { struct device *dev = &pdev->dev; struct acpi_device *adev = ACPI_COMPANION(dev); struct spi_controller *ctlr; struct spi_device *spi_dev; char name[50]; int i, ret, count; ret = acpi_spi_count_resources(adev); if (ret < 0) return ret; if (!ret) return -ENOENT; count = ret; smi->spi_devs = devm_kcalloc(dev, count, sizeof(*smi->spi_devs), GFP_KERNEL); if (!smi->spi_devs) return -ENOMEM; for (i = 0; i < count && inst_array[i].type; i++) { spi_dev = acpi_spi_device_alloc(NULL, adev, i); if (IS_ERR(spi_dev)) { ret = dev_err_probe(dev, PTR_ERR(spi_dev), "failed to allocate SPI device %s from ACPI\n", dev_name(&adev->dev)); goto error; } ctlr = spi_dev->controller; strscpy(spi_dev->modalias, inst_array[i].type, sizeof(spi_dev->modalias)); ret = smi_get_irq(pdev, adev, &inst_array[i]); if (ret < 0) { spi_dev_put(spi_dev); goto error; } spi_dev->irq = ret; snprintf(name, sizeof(name), "%s-%s-%s.%d", dev_name(&ctlr->dev), dev_name(dev), inst_array[i].type, i); spi_dev->dev.init_name = name; ret = spi_add_device(spi_dev); if (ret) { dev_err_probe(&ctlr->dev, ret, "failed to add SPI device %s from ACPI\n", dev_name(&adev->dev)); spi_dev_put(spi_dev); goto error; } dev_dbg(dev, "SPI device %s using chip select %u", name, spi_dev->chip_select); smi->spi_devs[i] = spi_dev; smi->spi_num++; } if (smi->spi_num < count) { dev_dbg(dev, "Error finding driver, idx %d\n", i); ret = -ENODEV; goto error; } dev_info(dev, "Instantiated %d SPI devices.\n", smi->spi_num); return 0; error: smi_devs_unregister(smi); return ret; } /** * smi_i2c_probe - Instantiate multiple I2C devices from inst array * @pdev: Platform device * @smi: Internal struct for Serial multi instantiate driver * @inst_array: Array of instances to probe * * Returns the number of I2C devices instantiate, Zero if none is found or a negative error code. */ static int smi_i2c_probe(struct platform_device *pdev, struct smi *smi, const struct smi_instance *inst_array) { struct i2c_board_info board_info = {}; struct device *dev = &pdev->dev; struct acpi_device *adev = ACPI_COMPANION(dev); char name[32]; int i, ret, count; ret = i2c_acpi_client_count(adev); if (ret < 0) return ret; if (!ret) return -ENOENT; count = ret; smi->i2c_devs = devm_kcalloc(dev, count, sizeof(*smi->i2c_devs), GFP_KERNEL); if (!smi->i2c_devs) return -ENOMEM; for (i = 0; i < count && inst_array[i].type; i++) { memset(&board_info, 0, sizeof(board_info)); strscpy(board_info.type, inst_array[i].type, I2C_NAME_SIZE); snprintf(name, sizeof(name), "%s-%s.%d", dev_name(dev), inst_array[i].type, i); board_info.dev_name = name; ret = smi_get_irq(pdev, adev, &inst_array[i]); if (ret < 0) goto error; board_info.irq = ret; smi->i2c_devs[i] = i2c_acpi_new_device(dev, i, &board_info); if (IS_ERR(smi->i2c_devs[i])) { ret = dev_err_probe(dev, PTR_ERR(smi->i2c_devs[i]), "Error creating i2c-client, idx %d\n", i); goto error; } smi->i2c_num++; } if (smi->i2c_num < count) { dev_dbg(dev, "Error finding driver, idx %d\n", i); ret = -ENODEV; goto error; } dev_info(dev, "Instantiated %d I2C devices.\n", smi->i2c_num); return 0; error: smi_devs_unregister(smi); return ret; } static int smi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct smi_node *node; struct smi *smi; int ret; node = device_get_match_data(dev); if (!node) { dev_dbg(dev, "Error ACPI match data is missing\n"); return -ENODEV; } smi = devm_kzalloc(dev, sizeof(*smi), GFP_KERNEL); if (!smi) return -ENOMEM; platform_set_drvdata(pdev, smi); switch (node->bus_type) { case SMI_I2C: return smi_i2c_probe(pdev, smi, node->instances); case SMI_SPI: return smi_spi_probe(pdev, smi, node->instances); case SMI_AUTO_DETECT: /* * For backwards-compatibility with the existing nodes I2C * is checked first and if such entries are found ONLY I2C * devices are created. Since some existing nodes that were * already handled by this driver could also contain unrelated * SpiSerialBus nodes that were previously ignored, and this * preserves that behavior. */ ret = smi_i2c_probe(pdev, smi, node->instances); if (ret != -ENOENT) return ret; return smi_spi_probe(pdev, smi, node->instances); default: return -EINVAL; } } static int smi_remove(struct platform_device *pdev) { struct smi *smi = platform_get_drvdata(pdev); smi_devs_unregister(smi); return 0; } static const struct smi_node bsg1160_data = { .instances = { { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, { "bmc150_magn" }, { "bmg160" }, {} }, .bus_type = SMI_I2C, }; static const struct smi_node bsg2150_data = { .instances = { { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, { "bmc150_magn" }, /* The resources describe a 3th client, but it is not really there. */ { "bsg2150_dummy_dev" }, {} }, .bus_type = SMI_I2C, }; static const struct smi_node int3515_data = { .instances = { { "tps6598x", IRQ_RESOURCE_APIC, 0 }, { "tps6598x", IRQ_RESOURCE_APIC, 1 }, { "tps6598x", IRQ_RESOURCE_APIC, 2 }, { "tps6598x", IRQ_RESOURCE_APIC, 3 }, {} }, .bus_type = SMI_I2C, }; static const struct smi_node cs35l41_hda = { .instances = { { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, {} }, .bus_type = SMI_AUTO_DETECT, }; /* * Note new device-ids must also be added to ignore_serial_bus_ids in * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). */ static const struct acpi_device_id smi_acpi_ids[] = { { "BSG1160", (unsigned long)&bsg1160_data }, { "BSG2150", (unsigned long)&bsg2150_data }, { "CSC3551", (unsigned long)&cs35l41_hda }, { "INT3515", (unsigned long)&int3515_data }, /* Non-conforming _HID for Cirrus Logic already released */ { "CLSA0100", (unsigned long)&cs35l41_hda }, { "CLSA0101", (unsigned long)&cs35l41_hda }, { } }; MODULE_DEVICE_TABLE(acpi, smi_acpi_ids); static struct platform_driver smi_driver = { .driver = { .name = "Serial bus multi instantiate pseudo device driver", .acpi_match_table = smi_acpi_ids, }, .probe = smi_probe, .remove = smi_remove, }; module_platform_driver(smi_driver); MODULE_DESCRIPTION("Serial multi instantiate pseudo device driver"); MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); MODULE_LICENSE("GPL"); |