diff options
Diffstat (limited to 'drivers/acpi/acpi_i2c.c')
| -rw-r--r-- | drivers/acpi/acpi_i2c.c | 103 | 
1 files changed, 103 insertions, 0 deletions
diff --git a/drivers/acpi/acpi_i2c.c b/drivers/acpi/acpi_i2c.c new file mode 100644 index 00000000000..82045e3f5ca --- /dev/null +++ b/drivers/acpi/acpi_i2c.c @@ -0,0 +1,103 @@ +/* + * ACPI I2C enumeration support + * + * Copyright (C) 2012, Intel Corporation + * Author: Mika Westerberg <mika.westerberg@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/i2c.h> +#include <linux/ioport.h> + +ACPI_MODULE_NAME("i2c"); + +static int acpi_i2c_add_resource(struct acpi_resource *ares, void *data) +{ +	struct i2c_board_info *info = data; + +	if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { +		struct acpi_resource_i2c_serialbus *sb; + +		sb = &ares->data.i2c_serial_bus; +		if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_I2C) { +			info->addr = sb->slave_address; +			if (sb->access_mode == ACPI_I2C_10BIT_MODE) +				info->flags |= I2C_CLIENT_TEN; +		} +	} else if (info->irq < 0) { +		struct resource r; + +		if (acpi_dev_resource_interrupt(ares, 0, &r)) +			info->irq = r.start; +	} + +	/* Tell the ACPI core to skip this resource */ +	return 1; +} + +static acpi_status acpi_i2c_add_device(acpi_handle handle, u32 level, +				       void *data, void **return_value) +{ +	struct i2c_adapter *adapter = data; +	struct list_head resource_list; +	struct i2c_board_info info; +	struct acpi_device *adev; +	int ret; + +	if (acpi_bus_get_device(handle, &adev)) +		return AE_OK; +	if (acpi_bus_get_status(adev) || !adev->status.present) +		return AE_OK; + +	memset(&info, 0, sizeof(info)); +	info.acpi_node.handle = handle; +	info.irq = -1; + +	INIT_LIST_HEAD(&resource_list); +	ret = acpi_dev_get_resources(adev, &resource_list, +				     acpi_i2c_add_resource, &info); +	acpi_dev_free_resource_list(&resource_list); + +	if (ret < 0 || !info.addr) +		return AE_OK; + +	strlcpy(info.type, dev_name(&adev->dev), sizeof(info.type)); +	if (!i2c_new_device(adapter, &info)) { +		dev_err(&adapter->dev, +			"failed to add I2C device %s from ACPI\n", +			dev_name(&adev->dev)); +	} + +	return AE_OK; +} + +/** + * acpi_i2c_register_devices - enumerate I2C slave devices behind adapter + * @adapter: pointer to adapter + * + * Enumerate all I2C slave devices behind this adapter by walking the ACPI + * namespace. When a device is found it will be added to the Linux device + * model and bound to the corresponding ACPI handle. + */ +void acpi_i2c_register_devices(struct i2c_adapter *adapter) +{ +	acpi_handle handle; +	acpi_status status; + +	handle = ACPI_HANDLE(&adapter->dev); +	if (!handle) +		return; + +	status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, +				     acpi_i2c_add_device, NULL, +				     adapter, NULL); +	if (ACPI_FAILURE(status)) +		dev_warn(&adapter->dev, "failed to enumerate I2C slaves\n"); +} +EXPORT_SYMBOL_GPL(acpi_i2c_register_devices);  |