brintos

brintos / linux-shallow public Read only

0
0
Text · 10.1 KiB · 7c04cc9 Raw
419 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Serial multi-instantiate driver, pseudo driver to instantiate multiple4 * client devices 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/property.h>17#include <linux/spi/spi.h>18#include <linux/types.h>19 20#define IRQ_RESOURCE_TYPE	GENMASK(1, 0)21#define IRQ_RESOURCE_NONE	022#define IRQ_RESOURCE_GPIO	123#define IRQ_RESOURCE_APIC	224#define IRQ_RESOURCE_AUTO   325 26enum smi_bus_type {27	SMI_I2C,28	SMI_SPI,29	SMI_AUTO_DETECT,30};31 32struct smi_instance {33	const char *type;34	unsigned int flags;35	int irq_idx;36};37 38struct smi_node {39	enum smi_bus_type bus_type;40	struct smi_instance instances[];41};42 43struct smi {44	int i2c_num;45	int spi_num;46	struct i2c_client **i2c_devs;47	struct spi_device **spi_devs;48};49 50static int smi_get_irq(struct platform_device *pdev, struct acpi_device *adev,51		       const struct smi_instance *inst)52{53	int ret;54 55	switch (inst->flags & IRQ_RESOURCE_TYPE) {56	case IRQ_RESOURCE_AUTO:57		ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx);58		if (ret > 0) {59			dev_dbg(&pdev->dev, "Using gpio irq\n");60			break;61		}62		ret = platform_get_irq(pdev, inst->irq_idx);63		if (ret > 0) {64			dev_dbg(&pdev->dev, "Using platform irq\n");65			break;66		}67		break;68	case IRQ_RESOURCE_GPIO:69		ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx);70		break;71	case IRQ_RESOURCE_APIC:72		ret = platform_get_irq(pdev, inst->irq_idx);73		break;74	default:75		return 0;76	}77	if (ret < 0)78		return dev_err_probe(&pdev->dev, ret, "Error requesting irq at index %d\n",79				     inst->irq_idx);80 81	return ret;82}83 84static void smi_devs_unregister(struct smi *smi)85{86#if IS_REACHABLE(CONFIG_I2C)87	while (smi->i2c_num--)88		i2c_unregister_device(smi->i2c_devs[smi->i2c_num]);89#endif90 91	if (IS_REACHABLE(CONFIG_SPI)) {92		while (smi->spi_num--)93			spi_unregister_device(smi->spi_devs[smi->spi_num]);94	}95}96 97/**98 * smi_spi_probe - Instantiate multiple SPI devices from inst array99 * @pdev:	Platform device100 * @smi:	Internal struct for Serial multi instantiate driver101 * @inst_array:	Array of instances to probe102 *103 * Returns the number of SPI devices instantiate, Zero if none is found or a negative error code.104 */105static int smi_spi_probe(struct platform_device *pdev, struct smi *smi,106			 const struct smi_instance *inst_array)107{108	struct device *dev = &pdev->dev;109	struct acpi_device *adev = ACPI_COMPANION(dev);110	struct spi_controller *ctlr;111	struct spi_device *spi_dev;112	char name[50];113	int i, ret, count;114 115	ret = acpi_spi_count_resources(adev);116	if (ret < 0)117		return ret;118	if (!ret)119		return -ENOENT;120 121	count = ret;122 123	smi->spi_devs = devm_kcalloc(dev, count, sizeof(*smi->spi_devs), GFP_KERNEL);124	if (!smi->spi_devs)125		return -ENOMEM;126 127	for (i = 0; i < count && inst_array[i].type; i++) {128 129		spi_dev = acpi_spi_device_alloc(NULL, adev, i);130		if (IS_ERR(spi_dev)) {131			ret = dev_err_probe(dev, PTR_ERR(spi_dev), "failed to allocate SPI device %s from ACPI\n",132					    dev_name(&adev->dev));133			goto error;134		}135 136		ctlr = spi_dev->controller;137 138		strscpy(spi_dev->modalias, inst_array[i].type);139 140		ret = smi_get_irq(pdev, adev, &inst_array[i]);141		if (ret < 0) {142			spi_dev_put(spi_dev);143			goto error;144		}145		spi_dev->irq = ret;146 147		snprintf(name, sizeof(name), "%s-%s-%s.%d", dev_name(&ctlr->dev), dev_name(dev),148			 inst_array[i].type, i);149		spi_dev->dev.init_name = name;150 151		ret = spi_add_device(spi_dev);152		if (ret) {153			dev_err_probe(&ctlr->dev, ret, "failed to add SPI device %s from ACPI\n",154				      dev_name(&adev->dev));155			spi_dev_put(spi_dev);156			goto error;157		}158 159		dev_dbg(dev, "SPI device %s using chip select %u", name,160			spi_get_chipselect(spi_dev, 0));161 162		smi->spi_devs[i] = spi_dev;163		smi->spi_num++;164	}165 166	if (smi->spi_num < count) {167		dev_dbg(dev, "Error finding driver, idx %d\n", i);168		ret = -ENODEV;169		goto error;170	}171 172	dev_info(dev, "Instantiated %d SPI devices.\n", smi->spi_num);173 174	return 0;175error:176	smi_devs_unregister(smi);177 178	return ret;179}180 181/**182 * smi_i2c_probe - Instantiate multiple I2C devices from inst array183 * @pdev:	Platform device184 * @smi:	Internal struct for Serial multi instantiate driver185 * @inst_array:	Array of instances to probe186 *187 * Returns the number of I2C devices instantiate, Zero if none is found or a negative error code.188 */189static int smi_i2c_probe(struct platform_device *pdev, struct smi *smi,190			 const struct smi_instance *inst_array)191{192	struct i2c_board_info board_info = {};193	struct device *dev = &pdev->dev;194	struct acpi_device *adev = ACPI_COMPANION(dev);195	char name[32];196	int i, ret, count;197 198	ret = i2c_acpi_client_count(adev);199	if (ret < 0)200		return ret;201	if (!ret)202		return -ENOENT;203 204	count = ret;205 206	smi->i2c_devs = devm_kcalloc(dev, count, sizeof(*smi->i2c_devs), GFP_KERNEL);207	if (!smi->i2c_devs)208		return -ENOMEM;209 210	for (i = 0; i < count && inst_array[i].type; i++) {211		memset(&board_info, 0, sizeof(board_info));212		strscpy(board_info.type, inst_array[i].type);213		snprintf(name, sizeof(name), "%s-%s.%d", dev_name(dev), inst_array[i].type, i);214		board_info.dev_name = name;215 216		ret = smi_get_irq(pdev, adev, &inst_array[i]);217		if (ret < 0)218			goto error;219		board_info.irq = ret;220 221		smi->i2c_devs[i] = i2c_acpi_new_device(dev, i, &board_info);222		if (IS_ERR(smi->i2c_devs[i])) {223			ret = dev_err_probe(dev, PTR_ERR(smi->i2c_devs[i]),224					    "Error creating i2c-client, idx %d\n", i);225			goto error;226		}227		smi->i2c_num++;228	}229	if (smi->i2c_num < count) {230		dev_dbg(dev, "Error finding driver, idx %d\n", i);231		ret = -ENODEV;232		goto error;233	}234 235	dev_info(dev, "Instantiated %d I2C devices.\n", smi->i2c_num);236 237	return 0;238error:239	smi_devs_unregister(smi);240 241	return ret;242}243 244static int smi_probe(struct platform_device *pdev)245{246	struct device *dev = &pdev->dev;247	const struct smi_node *node;248	struct smi *smi;249	int ret;250 251	node = device_get_match_data(dev);252	if (!node) {253		dev_dbg(dev, "Error ACPI match data is missing\n");254		return -ENODEV;255	}256 257	smi = devm_kzalloc(dev, sizeof(*smi), GFP_KERNEL);258	if (!smi)259		return -ENOMEM;260 261	platform_set_drvdata(pdev, smi);262 263	switch (node->bus_type) {264	case SMI_I2C:265		if (IS_REACHABLE(CONFIG_I2C))266			return smi_i2c_probe(pdev, smi, node->instances);267 268		return -ENODEV;269	case SMI_SPI:270		if (IS_REACHABLE(CONFIG_SPI))271			return smi_spi_probe(pdev, smi, node->instances);272 273		return -ENODEV;274	case SMI_AUTO_DETECT:275		/*276		 * For backwards-compatibility with the existing nodes I2C277		 * is checked first and if such entries are found ONLY I2C278		 * devices are created. Since some existing nodes that were279		 * already handled by this driver could also contain unrelated280		 * SpiSerialBus nodes that were previously ignored, and this281		 * preserves that behavior.282		 */283		if (IS_REACHABLE(CONFIG_I2C)) {284			ret = smi_i2c_probe(pdev, smi, node->instances);285			if (ret != -ENOENT)286				return ret;287		}288 289		if (IS_REACHABLE(CONFIG_SPI))290			return smi_spi_probe(pdev, smi, node->instances);291 292		return -ENODEV;293	default:294		return -EINVAL;295	}296}297 298static void smi_remove(struct platform_device *pdev)299{300	struct smi *smi = platform_get_drvdata(pdev);301 302	smi_devs_unregister(smi);303}304 305static const struct smi_node bsg1160_data = {306	.instances = {307		{ "bmc150_accel", IRQ_RESOURCE_GPIO, 0 },308		{ "bmc150_magn" },309		{ "bmg160" },310		{}311	},312	.bus_type = SMI_I2C,313};314 315static const struct smi_node bsg2150_data = {316	.instances = {317		{ "bmc150_accel", IRQ_RESOURCE_GPIO, 0 },318		{ "bmc150_magn" },319		/* The resources describe a 3th client, but it is not really there. */320		{ "bsg2150_dummy_dev" },321		{}322	},323	.bus_type = SMI_I2C,324};325 326static const struct smi_node int3515_data = {327	.instances = {328		{ "tps6598x", IRQ_RESOURCE_APIC, 0 },329		{ "tps6598x", IRQ_RESOURCE_APIC, 1 },330		{ "tps6598x", IRQ_RESOURCE_APIC, 2 },331		{ "tps6598x", IRQ_RESOURCE_APIC, 3 },332		{}333	},334	.bus_type = SMI_I2C,335};336 337static const struct smi_node cs35l41_hda = {338	.instances = {339		{ "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },340		{ "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },341		{ "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },342		{ "cs35l41-hda", IRQ_RESOURCE_AUTO, 0 },343		{}344	},345	.bus_type = SMI_AUTO_DETECT,346};347 348static const struct smi_node cs35l54_hda = {349	.instances = {350		{ "cs35l54-hda", IRQ_RESOURCE_AUTO, 0 },351		{ "cs35l54-hda", IRQ_RESOURCE_AUTO, 0 },352		{ "cs35l54-hda", IRQ_RESOURCE_AUTO, 0 },353		{ "cs35l54-hda", IRQ_RESOURCE_AUTO, 0 },354		/* a 5th entry is an alias address, not a real device */355		{ "cs35l54-hda_dummy_dev" },356		{}357	},358	.bus_type = SMI_AUTO_DETECT,359};360 361static const struct smi_node cs35l56_hda = {362	.instances = {363		{ "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 },364		{ "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 },365		{ "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 },366		{ "cs35l56-hda", IRQ_RESOURCE_AUTO, 0 },367		/* a 5th entry is an alias address, not a real device */368		{ "cs35l56-hda_dummy_dev" },369		{}370	},371	.bus_type = SMI_AUTO_DETECT,372};373 374static const struct smi_node cs35l57_hda = {375	.instances = {376		{ "cs35l57-hda", IRQ_RESOURCE_AUTO, 0 },377		{ "cs35l57-hda", IRQ_RESOURCE_AUTO, 0 },378		{ "cs35l57-hda", IRQ_RESOURCE_AUTO, 0 },379		{ "cs35l57-hda", IRQ_RESOURCE_AUTO, 0 },380		/* a 5th entry is an alias address, not a real device */381		{ "cs35l57-hda_dummy_dev" },382		{}383	},384	.bus_type = SMI_AUTO_DETECT,385};386 387/*388 * Note new device-ids must also be added to ignore_serial_bus_ids in389 * drivers/acpi/scan.c: acpi_device_enumeration_by_parent().390 */391static const struct acpi_device_id smi_acpi_ids[] = {392	{ "BSG1160", (unsigned long)&bsg1160_data },393	{ "BSG2150", (unsigned long)&bsg2150_data },394	{ "CSC3551", (unsigned long)&cs35l41_hda },395	{ "CSC3554", (unsigned long)&cs35l54_hda },396	{ "CSC3556", (unsigned long)&cs35l56_hda },397	{ "CSC3557", (unsigned long)&cs35l57_hda },398	{ "INT3515", (unsigned long)&int3515_data },399	/* Non-conforming _HID for Cirrus Logic already released */400	{ "CLSA0100", (unsigned long)&cs35l41_hda },401	{ "CLSA0101", (unsigned long)&cs35l41_hda },402	{ }403};404MODULE_DEVICE_TABLE(acpi, smi_acpi_ids);405 406static struct platform_driver smi_driver = {407	.driver	= {408		.name = "Serial bus multi instantiate pseudo device driver",409		.acpi_match_table = smi_acpi_ids,410	},411	.probe = smi_probe,412	.remove_new = smi_remove,413};414module_platform_driver(smi_driver);415 416MODULE_DESCRIPTION("Serial multi instantiate pseudo device driver");417MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");418MODULE_LICENSE("GPL");419