This patch adds support to load a custom ACPI table that describes devices connected via the DLN2 USB to I2C/SPI/GPIO bridge. The ACPI table can be loaded either externally (from QEMU or with CONFIG_ACPI_CUSTOM_DSDT) or it can be loaded as firmware file with the name dln2.aml. The driver looks for an ACPI device entry with _HID set to "DLN20000" and makes it the ACPI companion for DLN2 USB sub-drivers. Signed-off-by: Octavian Purdila <octavian.purdila@xxxxxxxxx> --- Documentation/acpi/dln2-acpi.txt | 62 ++++++++++++++++++ drivers/mfd/dln2.c | 134 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 Documentation/acpi/dln2-acpi.txt diff --git a/Documentation/acpi/dln2-acpi.txt b/Documentation/acpi/dln2-acpi.txt new file mode 100644 index 0000000..d76605f --- /dev/null +++ b/Documentation/acpi/dln2-acpi.txt @@ -0,0 +1,62 @@ +Diolan DLN2 custom APCI table + +The Diolan DLN2 is an USB to I2C/SPI/GPIO bridge and as such it can be used to +connect to various I2C or SPI devices. Because these busses lack an enumeration +protocol, the driver obtains various information about the device (such as I2C +address and GPIO pins) from either ACPI or device tree. + +To allow enumerating devices and their properties via ACPI, the Diolan +driver looks for an ACPI tree with the root _HID set to "DLN20000". If +it finds such an ACPI object it will set the ACPI companion to the +DLN2 MFD driver and from their it will be propagated to all its +sub-devices (I2C, GPIO, SPI). + +The user can either load the custom DSDT table with three methods: + +1. Via QEMU (see -acpitable) + +2. Via the CONFIG_ACPI_CUSTOM_DSDT kernel config option (see +Documentation/acpi/dsdt-override.txt) + +3. By placing the custom DSDT in the firmware paths in a file name +dln2.aml. + +Here is an example ACPI table that enumerates a BMC150 accelerometer +and defines its I2C address and GPIO pin used as an interrupt source: + +DefinitionBlock ("ssdt.aml", "SSDT", 1, "INTEL ", "CpuDptf", 0x00000003) +{ + Device (DLN0) + { + Name (_ADR, Zero) + Name (_HID, "DLN2000") + + Device (STAC) + { + Name (_ADR, Zero) + Name (_HID, "BMC150A") + Name (_CID, "INTACCL") + Name (_UID, One) + + Method (_CRS, 0, Serialized) + { + Name (RBUF, ResourceTemplate () + { + I2cSerialBus (0x0010, ControllerInitiated, 0x00061A80, + AddressingMode7Bit, "\\DLN0", + 0x00, ResourceConsumer, ,) + + GpioInt (Level, ActiveHigh, Exclusive, PullDown, 0x0000, + "\\DLN0", 0x00, ResourceConsumer, , ) + { // Pin list + 0 + } + }) + Return (RBUF) + } + } + } +} + +The resources defined in the devices under the DLN0 are those +supported by the I2C, GPIO and SPI sub-systems. diff --git a/drivers/mfd/dln2.c b/drivers/mfd/dln2.c index f9c4a0b..93f6d1d 100644 --- a/drivers/mfd/dln2.c +++ b/drivers/mfd/dln2.c @@ -23,6 +23,8 @@ #include <linux/mfd/core.h> #include <linux/mfd/dln2.h> #include <linux/rculist.h> +#include <linux/acpi.h> +#include <linux/firmware.h> struct dln2_header { __le16 size; @@ -714,6 +716,134 @@ static void dln2_stop(struct dln2_dev *dln2) dln2_stop_rx_urbs(dln2); } + +#if IS_ENABLED(CONFIG_ACPI) + +static struct dln2_acpi_info { + const struct firmware *fw; + acpi_owner_id table_id; + struct acpi_device *dev; + int users; +} dln2_acpi_info; + +static DEFINE_MUTEX(dln2_acpi_lock); + +static acpi_status dln2_find_acpi_handle(acpi_handle handle, u32 level, + void *ctxt, void **retv) +{ + acpi_handle *dln2_handle = (acpi_handle *)retv; + + *dln2_handle = handle; + + return AE_CTRL_TERMINATE; +} + +static void dln2_probe_acpi(struct dln2_dev *dln2) +{ + struct device *dev = &dln2->interface->dev; + struct dln2_acpi_info *ai = &dln2_acpi_info; + acpi_handle h = NULL; + int ret; + bool fw_loaded = false; + + mutex_lock(&dln2_acpi_lock); + + if (ai->dev) + goto out_success; + + /* + * Look for the DLN2000 HID in case the ACPI table was loaded + * externally (e.g. from qemu). + */ + acpi_get_devices("DLN20000", dln2_find_acpi_handle, NULL, &h); + if (!h) { + /* Try to load the ACPI table via a firmware file */ + ret = request_firmware(&ai->fw, "dln2.aml", NULL); + if (ret) + goto out_unlock; + + ret = acpi_load_table((void *)ai->fw->data); + if (ret) { + dev_err(dev, "invalid ACPI table\n"); + goto out_release_fw; + } + + acpi_get_devices("DLN20000", dln2_find_acpi_handle, NULL, &h); + if (!h) { + dev_err(dev, "not a DLN2 ACPI table\n"); + goto out_leak_fw; + } + + ret = acpi_get_id(h, &ai->table_id); + if (ret) { + dev_err(dev, "acpi_get_id failed: %d\n", ret); + goto out_leak_fw; + } + + ret = acpi_bus_scan(h); + if (ret) { + dev_err(dev, "acpi_bus_scan failed: %d\n", ret); + goto out_leak_fw; + } + + fw_loaded = true; + } + + ret = acpi_bus_get_device(h, &ai->dev); + if (ret) { + dev_err(dev, "failed to get ACPI device: %d\n", ret); + if (fw_loaded) { + acpi_unload_table_id(ai->table_id); + goto out_leak_fw; + } + goto out_unlock; + } + +out_success: + ACPI_COMPANION_SET(dev, ai->dev); + ai->users++; + mutex_unlock(&dln2_acpi_lock); + return; + +out_release_fw: + release_firmware(ai->fw); +out_leak_fw: + /* + * Once a table is loaded we can't release the firmware anymore because + * acpi_unload_table does not actually unload the table but keeps it in + * memory to speed up subsequent loads. + */ + ai->fw = NULL; +out_unlock: + mutex_unlock(&dln2_acpi_lock); +} + +static void dln2_disconnect_acpi(struct dln2_dev *dln2) +{ + struct dln2_acpi_info *ai = &dln2_acpi_info; + + mutex_lock(&dln2_acpi_lock); + if (--ai->users == 0 && ai->fw) { + acpi_scan_lock_acquire(); + acpi_bus_trim(ai->dev); + acpi_scan_lock_release(); + acpi_unload_table_id(ai->table_id); + ai->dev = NULL; + /* we can't release firmware see comment in dln2_probe_acpi */ + ai->fw = NULL; + } + mutex_unlock(&dln2_acpi_lock); +} +#else +static void dln2_probe_acpi(struct dln2_dev *dln2) +{ +} + +static void dln2_disconnect_acpi(struct dln2_dev *dln2) +{ +} +#endif + static void dln2_disconnect(struct usb_interface *interface) { struct dln2_dev *dln2 = usb_get_intfdata(interface); @@ -722,6 +852,8 @@ static void dln2_disconnect(struct usb_interface *interface) mfd_remove_devices(&interface->dev); + dln2_disconnect_acpi(dln2); + dln2_free(dln2); } @@ -774,6 +906,8 @@ static int dln2_probe(struct usb_interface *interface, goto out_stop_rx; } + dln2_probe_acpi(dln2); + ret = mfd_add_hotplug_devices(dev, dln2_devs, ARRAY_SIZE(dln2_devs)); if (ret != 0) { dev_err(dev, "failed to add mfd devices to core\n"); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-gpio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html