[RFC PATCH] 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 is loaded at runtime as firmware with the name
dln2.aml, it looks for an ACPI device entry with _HID set to
"DLN20000" and makes it the ACPI companion for DLN2 USB sub-drivers.

It is sort of a hack due to the "../acpi/internal.h" and
"../usb/core/usb.h" includes and perhaps something more generic would
be more appropriate. Any suggestions to the right direction are kindly
appreciated.

Signed-off-by: Octavian Purdila <octavian.purdila@xxxxxxxxx>
---
 Documentation/acpi/dln2-acpi.txt |  48 ++++++++++++++++++
 drivers/mfd/Kconfig              |  11 +++++
 drivers/mfd/Makefile             |   1 +
 drivers/mfd/dln2-acpi.c          | 103 +++++++++++++++++++++++++++++++++++++++
 drivers/mfd/dln2.c               |   6 +--
 drivers/mfd/dln2.h               |   9 ++++
 6 files changed, 173 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/acpi/dln2-acpi.txt
 create mode 100644 drivers/mfd/dln2-acpi.c
 create mode 100644 drivers/mfd/dln2.h

diff --git a/Documentation/acpi/dln2-acpi.txt b/Documentation/acpi/dln2-acpi.txt
new file mode 100644
index 0000000..c099241
--- /dev/null
+++ b/Documentation/acpi/dln2-acpi.txt
@@ -0,0 +1,48 @@
+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 using such devices connect to the DLN2 bridge to their full extend
+(e.g. interrupt mode), if CONFIG_MFD_DLN2_ACPI option has been compiled in the
+kernel, the user can define a custom ACPI table that will be dynamically loaded
+at boot time from firmware paths. The ACPI table filename must be dln2.aml and
+it must contain a root device with _HID set "DLN20000".
+
+Here is a example of how the ACPI table should look like:
+
+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)
+		       }
+		}
+	}
+}
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 2e6b731..b810195 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -205,6 +205,17 @@ config MFD_DLN2
 	  etc. must be enabled in order to use the functionality of
 	  the device.
 
+config MFD_DLN2_ACPI
+	bool "Diolan DLN2 ACPI support"
+	depends on MFD_DLN2 && ACPI
+	default n
+	help
+	  Say yes here to add ACPI support to DLN2 which allows loading a custom
+	  ACPI table to describe devices between the DLN2 I2C or SPI bridge as
+	  well as GPIO support for those devices. See
+	  Documentation/acpi/dln2-acpi.txt for more information.
+
+
 config MFD_MC13XXX
 	tristate
 	depends on (SPI_MASTER || I2C)
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 53467e2..dbe1f3f 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -176,6 +176,7 @@ obj-$(CONFIG_MFD_IPAQ_MICRO)	+= ipaq-micro.o
 obj-$(CONFIG_MFD_MENF21BMC)	+= menf21bmc.o
 obj-$(CONFIG_MFD_HI6421_PMIC)	+= hi6421-pmic-core.o
 obj-$(CONFIG_MFD_DLN2)		+= dln2.o
+obj-$(CONFIG_MFD_DLN2_ACPI)	+= dln2-acpi.o
 
 intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
 obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
diff --git a/drivers/mfd/dln2-acpi.c b/drivers/mfd/dln2-acpi.c
new file mode 100644
index 0000000..8c99769
--- /dev/null
+++ b/drivers/mfd/dln2-acpi.c
@@ -0,0 +1,103 @@
+#define pr_fmt(fmt) "dln2-acpi: " fmt
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/acpi.h>
+#include <linux/firmware.h>
+#include "../acpi/internal.h"
+#include "../usb/core/usb.h"
+#include "dln2.h"
+
+static acpi_handle dln2_acpi_handle = NULL;
+static const struct firmware *dln2_acpi;
+
+static bool dln2_acpi_bus_match(struct device *dev)
+{
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	u16 idVendor, idProduct;
+	int i;
+
+	if (is_usb_endpoint(dev) || !dev->parent ||
+	    !is_usb_interface(dev->parent))
+		return false;
+
+	intf = to_usb_interface(dev->parent);
+	udev = interface_to_usbdev(intf);
+	idVendor = le16_to_cpu(udev->descriptor.idVendor);
+	idProduct = le16_to_cpu(udev->descriptor.idProduct);
+
+	for (i = 0; i < ARRAY_SIZE(dln2_table); i++)
+		if (idVendor == dln2_table[i].idVendor &&
+		    idProduct == dln2_table[i].idProduct)
+			return true;
+
+	return false;
+}
+
+static struct acpi_device *dln2_acpi_find_companion(struct device *dev)
+{
+	struct acpi_device *adev;
+	int ret;
+
+	ret = acpi_bus_get_device(dln2_acpi_handle, &adev);
+	if (ret) {
+		pr_err("failed to get device: %d\n", ret);
+		return NULL;
+	}
+
+	return adev;
+}
+
+static struct acpi_bus_type dln2_acpi_bus = {
+	.name = "DNL2",
+	.match = dln2_acpi_bus_match,
+	.find_companion = dln2_acpi_find_companion,
+};
+
+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 int dln2_acpi_init(void)
+{
+	int ret;
+
+	request_firmware_direct(&dln2_acpi, "dln2.aml", NULL);
+	if (dln2_acpi) {
+		struct acpi_table_header *acpi_table = (void *)dln2_acpi->data;
+
+		ret = acpi_load_table(acpi_table);
+		if (ret) {
+			pr_err("invalid ACPI table\n");
+			release_firmware(dln2_acpi);
+		}
+	}
+
+	acpi_get_devices("DLN20000", dln2_find_acpi_handle, NULL, &dln2_acpi_handle);
+	if (!dln2_acpi_handle)
+		return -ENODEV;
+
+	if (dln2_acpi)
+		acpi_bus_scan(dln2_acpi_handle);
+
+	return register_acpi_bus_type(&dln2_acpi_bus);
+}
+
+static void dln2_acpi_exit(void)
+{
+	release_firmware(dln2_acpi);
+	unregister_acpi_bus_type(&dln2_acpi_bus);
+}
+
+module_init(dln2_acpi_init);
+module_exit(dln2_acpi_exit);
diff --git a/drivers/mfd/dln2.c b/drivers/mfd/dln2.c
index 08c403c..62120e7 100644
--- a/drivers/mfd/dln2.c
+++ b/drivers/mfd/dln2.c
@@ -23,6 +23,7 @@
 #include <linux/mfd/core.h>
 #include <linux/mfd/dln2.h>
 #include <linux/rculist.h>
+#include "dln2.h"
 
 struct dln2_header {
 	__le16 size;
@@ -768,11 +769,6 @@ out_cleanup:
 	return ret;
 }
 
-static const struct usb_device_id dln2_table[] = {
-	{ USB_DEVICE(0xa257, 0x2013) },
-	{ }
-};
-
 MODULE_DEVICE_TABLE(usb, dln2_table);
 
 static int dln2_suspend(struct usb_interface *iface, pm_message_t message)
diff --git a/drivers/mfd/dln2.h b/drivers/mfd/dln2.h
new file mode 100644
index 0000000..1ed4272
--- /dev/null
+++ b/drivers/mfd/dln2.h
@@ -0,0 +1,9 @@
+#ifndef _MFD_DLN2_H
+#define _MFD_DLN2_H
+
+static const struct usb_device_id dln2_table[] = {
+	{ USB_DEVICE(0xa257, 0x2013) },
+	{ }
+};
+
+#endif /* _MFD_DLN2_H */
-- 
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