162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Serial multi-instantiate driver, pseudo driver to instantiate multiple 462306a36Sopenharmony_ci * client devices from a single fwnode. 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright 2018 Hans de Goede <hdegoede@redhat.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/acpi.h> 1062306a36Sopenharmony_ci#include <linux/bits.h> 1162306a36Sopenharmony_ci#include <linux/i2c.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/kernel.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/property.h> 1762306a36Sopenharmony_ci#include <linux/spi/spi.h> 1862306a36Sopenharmony_ci#include <linux/types.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define IRQ_RESOURCE_TYPE GENMASK(1, 0) 2162306a36Sopenharmony_ci#define IRQ_RESOURCE_NONE 0 2262306a36Sopenharmony_ci#define IRQ_RESOURCE_GPIO 1 2362306a36Sopenharmony_ci#define IRQ_RESOURCE_APIC 2 2462306a36Sopenharmony_ci#define IRQ_RESOURCE_AUTO 3 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cienum smi_bus_type { 2762306a36Sopenharmony_ci SMI_I2C, 2862306a36Sopenharmony_ci SMI_SPI, 2962306a36Sopenharmony_ci SMI_AUTO_DETECT, 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistruct smi_instance { 3362306a36Sopenharmony_ci const char *type; 3462306a36Sopenharmony_ci unsigned int flags; 3562306a36Sopenharmony_ci int irq_idx; 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct smi_node { 3962306a36Sopenharmony_ci enum smi_bus_type bus_type; 4062306a36Sopenharmony_ci struct smi_instance instances[]; 4162306a36Sopenharmony_ci}; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistruct smi { 4462306a36Sopenharmony_ci int i2c_num; 4562306a36Sopenharmony_ci int spi_num; 4662306a36Sopenharmony_ci struct i2c_client **i2c_devs; 4762306a36Sopenharmony_ci struct spi_device **spi_devs; 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic int smi_get_irq(struct platform_device *pdev, struct acpi_device *adev, 5162306a36Sopenharmony_ci const struct smi_instance *inst) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci int ret; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci switch (inst->flags & IRQ_RESOURCE_TYPE) { 5662306a36Sopenharmony_ci case IRQ_RESOURCE_AUTO: 5762306a36Sopenharmony_ci ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx); 5862306a36Sopenharmony_ci if (ret > 0) { 5962306a36Sopenharmony_ci dev_dbg(&pdev->dev, "Using gpio irq\n"); 6062306a36Sopenharmony_ci break; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci ret = platform_get_irq(pdev, inst->irq_idx); 6362306a36Sopenharmony_ci if (ret > 0) { 6462306a36Sopenharmony_ci dev_dbg(&pdev->dev, "Using platform irq\n"); 6562306a36Sopenharmony_ci break; 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci break; 6862306a36Sopenharmony_ci case IRQ_RESOURCE_GPIO: 6962306a36Sopenharmony_ci ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx); 7062306a36Sopenharmony_ci break; 7162306a36Sopenharmony_ci case IRQ_RESOURCE_APIC: 7262306a36Sopenharmony_ci ret = platform_get_irq(pdev, inst->irq_idx); 7362306a36Sopenharmony_ci break; 7462306a36Sopenharmony_ci default: 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci if (ret < 0) 7862306a36Sopenharmony_ci return dev_err_probe(&pdev->dev, ret, "Error requesting irq at index %d\n", 7962306a36Sopenharmony_ci inst->irq_idx); 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return ret; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void smi_devs_unregister(struct smi *smi) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci while (smi->i2c_num--) 8762306a36Sopenharmony_ci i2c_unregister_device(smi->i2c_devs[smi->i2c_num]); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci while (smi->spi_num--) 9062306a36Sopenharmony_ci spi_unregister_device(smi->spi_devs[smi->spi_num]); 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/** 9462306a36Sopenharmony_ci * smi_spi_probe - Instantiate multiple SPI devices from inst array 9562306a36Sopenharmony_ci * @pdev: Platform device 9662306a36Sopenharmony_ci * @smi: Internal struct for Serial multi instantiate driver 9762306a36Sopenharmony_ci * @inst_array: Array of instances to probe 9862306a36Sopenharmony_ci * 9962306a36Sopenharmony_ci * Returns the number of SPI devices instantiate, Zero if none is found or a negative error code. 10062306a36Sopenharmony_ci */ 10162306a36Sopenharmony_cistatic int smi_spi_probe(struct platform_device *pdev, struct smi *smi, 10262306a36Sopenharmony_ci const struct smi_instance *inst_array) 10362306a36Sopenharmony_ci{ 10462306a36Sopenharmony_ci struct device *dev = &pdev->dev; 10562306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(dev); 10662306a36Sopenharmony_ci struct spi_controller *ctlr; 10762306a36Sopenharmony_ci struct spi_device *spi_dev; 10862306a36Sopenharmony_ci char name[50]; 10962306a36Sopenharmony_ci int i, ret, count; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci ret = acpi_spi_count_resources(adev); 11262306a36Sopenharmony_ci if (ret < 0) 11362306a36Sopenharmony_ci return ret; 11462306a36Sopenharmony_ci if (!ret) 11562306a36Sopenharmony_ci return -ENOENT; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci count = ret; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci smi->spi_devs = devm_kcalloc(dev, count, sizeof(*smi->spi_devs), GFP_KERNEL); 12062306a36Sopenharmony_ci if (!smi->spi_devs) 12162306a36Sopenharmony_ci return -ENOMEM; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci for (i = 0; i < count && inst_array[i].type; i++) { 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci spi_dev = acpi_spi_device_alloc(NULL, adev, i); 12662306a36Sopenharmony_ci if (IS_ERR(spi_dev)) { 12762306a36Sopenharmony_ci ret = dev_err_probe(dev, PTR_ERR(spi_dev), "failed to allocate SPI device %s from ACPI\n", 12862306a36Sopenharmony_ci dev_name(&adev->dev)); 12962306a36Sopenharmony_ci goto error; 13062306a36Sopenharmony_ci } 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci ctlr = spi_dev->controller; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci strscpy(spi_dev->modalias, inst_array[i].type, sizeof(spi_dev->modalias)); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci ret = smi_get_irq(pdev, adev, &inst_array[i]); 13762306a36Sopenharmony_ci if (ret < 0) { 13862306a36Sopenharmony_ci spi_dev_put(spi_dev); 13962306a36Sopenharmony_ci goto error; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci spi_dev->irq = ret; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci snprintf(name, sizeof(name), "%s-%s-%s.%d", dev_name(&ctlr->dev), dev_name(dev), 14462306a36Sopenharmony_ci inst_array[i].type, i); 14562306a36Sopenharmony_ci spi_dev->dev.init_name = name; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci ret = spi_add_device(spi_dev); 14862306a36Sopenharmony_ci if (ret) { 14962306a36Sopenharmony_ci dev_err_probe(&ctlr->dev, ret, "failed to add SPI device %s from ACPI\n", 15062306a36Sopenharmony_ci dev_name(&adev->dev)); 15162306a36Sopenharmony_ci spi_dev_put(spi_dev); 15262306a36Sopenharmony_ci goto error; 15362306a36Sopenharmony_ci } 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci dev_dbg(dev, "SPI device %s using chip select %u", name, 15662306a36Sopenharmony_ci spi_get_chipselect(spi_dev, 0)); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci smi->spi_devs[i] = spi_dev; 15962306a36Sopenharmony_ci smi->spi_num++; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if (smi->spi_num < count) { 16362306a36Sopenharmony_ci dev_dbg(dev, "Error finding driver, idx %d\n", i); 16462306a36Sopenharmony_ci ret = -ENODEV; 16562306a36Sopenharmony_ci goto error; 16662306a36Sopenharmony_ci } 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci dev_info(dev, "Instantiated %d SPI devices.\n", smi->spi_num); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci return 0; 17162306a36Sopenharmony_cierror: 17262306a36Sopenharmony_ci smi_devs_unregister(smi); 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci return ret; 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci/** 17862306a36Sopenharmony_ci * smi_i2c_probe - Instantiate multiple I2C devices from inst array 17962306a36Sopenharmony_ci * @pdev: Platform device 18062306a36Sopenharmony_ci * @smi: Internal struct for Serial multi instantiate driver 18162306a36Sopenharmony_ci * @inst_array: Array of instances to probe 18262306a36Sopenharmony_ci * 18362306a36Sopenharmony_ci * Returns the number of I2C devices instantiate, Zero if none is found or a negative error code. 18462306a36Sopenharmony_ci */ 18562306a36Sopenharmony_cistatic int smi_i2c_probe(struct platform_device *pdev, struct smi *smi, 18662306a36Sopenharmony_ci const struct smi_instance *inst_array) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci struct i2c_board_info board_info = {}; 18962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 19062306a36Sopenharmony_ci struct acpi_device *adev = ACPI_COMPANION(dev); 19162306a36Sopenharmony_ci char name[32]; 19262306a36Sopenharmony_ci int i, ret, count; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci ret = i2c_acpi_client_count(adev); 19562306a36Sopenharmony_ci if (ret < 0) 19662306a36Sopenharmony_ci return ret; 19762306a36Sopenharmony_ci if (!ret) 19862306a36Sopenharmony_ci return -ENOENT; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci count = ret; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci smi->i2c_devs = devm_kcalloc(dev, count, sizeof(*smi->i2c_devs), GFP_KERNEL); 20362306a36Sopenharmony_ci if (!smi->i2c_devs) 20462306a36Sopenharmony_ci return -ENOMEM; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci for (i = 0; i < count && inst_array[i].type; i++) { 20762306a36Sopenharmony_ci memset(&board_info, 0, sizeof(board_info)); 20862306a36Sopenharmony_ci strscpy(board_info.type, inst_array[i].type, I2C_NAME_SIZE); 20962306a36Sopenharmony_ci snprintf(name, sizeof(name), "%s-%s.%d", dev_name(dev), inst_array[i].type, i); 21062306a36Sopenharmony_ci board_info.dev_name = name; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci ret = smi_get_irq(pdev, adev, &inst_array[i]); 21362306a36Sopenharmony_ci if (ret < 0) 21462306a36Sopenharmony_ci goto error; 21562306a36Sopenharmony_ci board_info.irq = ret; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci smi->i2c_devs[i] = i2c_acpi_new_device(dev, i, &board_info); 21862306a36Sopenharmony_ci if (IS_ERR(smi->i2c_devs[i])) { 21962306a36Sopenharmony_ci ret = dev_err_probe(dev, PTR_ERR(smi->i2c_devs[i]), 22062306a36Sopenharmony_ci "Error creating i2c-client, idx %d\n", i); 22162306a36Sopenharmony_ci goto error; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci smi->i2c_num++; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci if (smi->i2c_num < count) { 22662306a36Sopenharmony_ci dev_dbg(dev, "Error finding driver, idx %d\n", i); 22762306a36Sopenharmony_ci ret = -ENODEV; 22862306a36Sopenharmony_ci goto error; 22962306a36Sopenharmony_ci } 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci dev_info(dev, "Instantiated %d I2C devices.\n", smi->i2c_num); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci return 0; 23462306a36Sopenharmony_cierror: 23562306a36Sopenharmony_ci smi_devs_unregister(smi); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci return ret; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cistatic int smi_probe(struct platform_device *pdev) 24162306a36Sopenharmony_ci{ 24262306a36Sopenharmony_ci struct device *dev = &pdev->dev; 24362306a36Sopenharmony_ci const struct smi_node *node; 24462306a36Sopenharmony_ci struct smi *smi; 24562306a36Sopenharmony_ci int ret; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci node = device_get_match_data(dev); 24862306a36Sopenharmony_ci if (!node) { 24962306a36Sopenharmony_ci dev_dbg(dev, "Error ACPI match data is missing\n"); 25062306a36Sopenharmony_ci return -ENODEV; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci smi = devm_kzalloc(dev, sizeof(*smi), GFP_KERNEL); 25462306a36Sopenharmony_ci if (!smi) 25562306a36Sopenharmony_ci return -ENOMEM; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci platform_set_drvdata(pdev, smi); 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci switch (node->bus_type) { 26062306a36Sopenharmony_ci case SMI_I2C: 26162306a36Sopenharmony_ci return smi_i2c_probe(pdev, smi, node->instances); 26262306a36Sopenharmony_ci case SMI_SPI: 26362306a36Sopenharmony_ci return smi_spi_probe(pdev, smi, node->instances); 26462306a36Sopenharmony_ci case SMI_AUTO_DETECT: 26562306a36Sopenharmony_ci /* 26662306a36Sopenharmony_ci * For backwards-compatibility with the existing nodes I2C 26762306a36Sopenharmony_ci * is checked first and if such entries are found ONLY I2C 26862306a36Sopenharmony_ci * devices are created. Since some existing nodes that were 26962306a36Sopenharmony_ci * already handled by this driver could also contain unrelated 27062306a36Sopenharmony_ci * SpiSerialBus nodes that were previously ignored, and this 27162306a36Sopenharmony_ci * preserves that behavior. 27262306a36Sopenharmony_ci */ 27362306a36Sopenharmony_ci ret = smi_i2c_probe(pdev, smi, node->instances); 27462306a36Sopenharmony_ci if (ret != -ENOENT) 27562306a36Sopenharmony_ci return ret; 27662306a36Sopenharmony_ci return smi_spi_probe(pdev, smi, node->instances); 27762306a36Sopenharmony_ci default: 27862306a36Sopenharmony_ci return -EINVAL; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic void smi_remove(struct platform_device *pdev) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci struct smi *smi = platform_get_drvdata(pdev); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci smi_devs_unregister(smi); 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistatic const struct smi_node bsg1160_data = { 29062306a36Sopenharmony_ci .instances = { 29162306a36Sopenharmony_ci { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, 29262306a36Sopenharmony_ci { "bmc150_magn" }, 29362306a36Sopenharmony_ci { "bmg160" }, 29462306a36Sopenharmony_ci {} 29562306a36Sopenharmony_ci }, 29662306a36Sopenharmony_ci .bus_type = SMI_I2C, 29762306a36Sopenharmony_ci}; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_cistatic const struct smi_node bsg2150_data = { 30062306a36Sopenharmony_ci .instances = { 30162306a36Sopenharmony_ci { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, 30262306a36Sopenharmony_ci { "bmc150_magn" }, 30362306a36Sopenharmony_ci /* The resources describe a 3th client, but it is not really there. */ 30462306a36Sopenharmony_ci { "bsg2150_dummy_dev" }, 30562306a36Sopenharmony_ci {} 30662306a36Sopenharmony_ci }, 30762306a36Sopenharmony_ci .bus_type = SMI_I2C, 30862306a36Sopenharmony_ci}; 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic const struct smi_node int3515_data = { 31162306a36Sopenharmony_ci .instances = { 31262306a36Sopenharmony_ci { "tps6598x", IRQ_RESOURCE_APIC, 0 }, 31362306a36Sopenharmony_ci { "tps6598x", IRQ_RESOURCE_APIC, 1 }, 31462306a36Sopenharmony_ci { "tps6598x", IRQ_RESOURCE_APIC, 2 }, 31562306a36Sopenharmony_ci { "tps6598x", IRQ_RESOURCE_APIC, 3 }, 31662306a36Sopenharmony_ci {} 31762306a36Sopenharmony_ci }, 31862306a36Sopenharmony_ci .bus_type = SMI_I2C, 31962306a36Sopenharmony_ci}; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic const struct smi_node cs35l41_hda = { 32262306a36Sopenharmony_ci .instances = { 32362306a36Sopenharmony_ci { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 }, 32462306a36Sopenharmony_ci { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 }, 32562306a36Sopenharmony_ci { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 }, 32662306a36Sopenharmony_ci { "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 }, 32762306a36Sopenharmony_ci {} 32862306a36Sopenharmony_ci }, 32962306a36Sopenharmony_ci .bus_type = SMI_AUTO_DETECT, 33062306a36Sopenharmony_ci}; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic const struct smi_node cs35l56_hda = { 33362306a36Sopenharmony_ci .instances = { 33462306a36Sopenharmony_ci { "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 }, 33562306a36Sopenharmony_ci { "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 }, 33662306a36Sopenharmony_ci { "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 }, 33762306a36Sopenharmony_ci { "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 }, 33862306a36Sopenharmony_ci /* a 5th entry is an alias address, not a real device */ 33962306a36Sopenharmony_ci { "cs35l56-hda_dummy_dev" }, 34062306a36Sopenharmony_ci {} 34162306a36Sopenharmony_ci }, 34262306a36Sopenharmony_ci .bus_type = SMI_AUTO_DETECT, 34362306a36Sopenharmony_ci}; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci/* 34662306a36Sopenharmony_ci * Note new device-ids must also be added to ignore_serial_bus_ids in 34762306a36Sopenharmony_ci * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). 34862306a36Sopenharmony_ci */ 34962306a36Sopenharmony_cistatic const struct acpi_device_id smi_acpi_ids[] = { 35062306a36Sopenharmony_ci { "BSG1160", (unsigned long)&bsg1160_data }, 35162306a36Sopenharmony_ci { "BSG2150", (unsigned long)&bsg2150_data }, 35262306a36Sopenharmony_ci { "CSC3551", (unsigned long)&cs35l41_hda }, 35362306a36Sopenharmony_ci { "CSC3556", (unsigned long)&cs35l56_hda }, 35462306a36Sopenharmony_ci { "INT3515", (unsigned long)&int3515_data }, 35562306a36Sopenharmony_ci /* Non-conforming _HID for Cirrus Logic already released */ 35662306a36Sopenharmony_ci { "CLSA0100", (unsigned long)&cs35l41_hda }, 35762306a36Sopenharmony_ci { "CLSA0101", (unsigned long)&cs35l41_hda }, 35862306a36Sopenharmony_ci { } 35962306a36Sopenharmony_ci}; 36062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, smi_acpi_ids); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_cistatic struct platform_driver smi_driver = { 36362306a36Sopenharmony_ci .driver = { 36462306a36Sopenharmony_ci .name = "Serial bus multi instantiate pseudo device driver", 36562306a36Sopenharmony_ci .acpi_match_table = smi_acpi_ids, 36662306a36Sopenharmony_ci }, 36762306a36Sopenharmony_ci .probe = smi_probe, 36862306a36Sopenharmony_ci .remove_new = smi_remove, 36962306a36Sopenharmony_ci}; 37062306a36Sopenharmony_cimodule_platform_driver(smi_driver); 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ciMODULE_DESCRIPTION("Serial multi instantiate pseudo device driver"); 37362306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 37462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 375