[PATCH 3/4] mfd: dln2: add support for ACPI

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux