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.purd...@intel.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to