[PATCH] mfd: Add ACPI support to Kontron PLD driver

From: Michael Brunner
Date: Wed Aug 12 2020 - 10:57:08 EST


Recent Kontron COMe modules identify the PLD device using the hardware
id KEM0001 in the ACPI table.
This patch adds support for probing the device using the HID and also
retrieving the resources.

As this is not available for all products, the DMI based detection still
needs to be around for older systems. It is executed if no matching ACPI
HID is found during registering the platform driver or no specific
device id is forced.
If a device is detected using ACPI and no resource information is
available, the default io resource is used.

Forcing a device id with the force_device_id parameter and therefore
manually generating a platform device takes precedence over ACPI during
probing.

Signed-off-by: Michael Brunner <michael.brunner@xxxxxxxxxxx>
---
drivers/mfd/kempld-core.c | 97 ++++++++++++++++++++++++++++++++++++---
1 file changed, 91 insertions(+), 6 deletions(-)

diff --git a/drivers/mfd/kempld-core.c b/drivers/mfd/kempld-core.c
index f48e21d8b97c..408cad1958d9 100644
--- a/drivers/mfd/kempld-core.c
+++ b/drivers/mfd/kempld-core.c
@@ -13,6 +13,7 @@
#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/delay.h>
+#include <linux/acpi.h>

#define MAX_ID_LEN 4
static char force_device_id[MAX_ID_LEN + 1] = "";
@@ -132,6 +133,7 @@ static const struct kempld_platform_data kempld_platform_data_generic = {
};

static struct platform_device *kempld_pdev;
+static bool kempld_acpi_mode;

static int kempld_create_platform_device(const struct dmi_system_id *id)
{
@@ -434,13 +436,87 @@ static int kempld_detect_device(struct kempld_device_data *pld)
return ret;
}

+#ifdef CONFIG_ACPI
+static const struct acpi_device_id kempld_acpi_table[] = {
+ { "KEM0001", (kernel_ulong_t)&kempld_platform_data_generic },
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, kempld_acpi_table);
+
+static int kempld_get_acpi_data(struct platform_device *pdev)
+{
+ struct list_head resource_list;
+ struct resource *resources;
+ struct resource_entry *rentry;
+ struct device *dev = &pdev->dev;
+ struct acpi_device *acpi_dev = ACPI_COMPANION(dev);
+ const struct kempld_platform_data *pdata;
+ int ret;
+ int count;
+
+ pdata = acpi_device_get_match_data(dev);
+ ret = platform_device_add_data(pdev, pdata,
+ sizeof(struct kempld_platform_data));
+ if (ret)
+ return ret;
+
+ INIT_LIST_HEAD(&resource_list);
+ ret = acpi_dev_get_resources(acpi_dev, &resource_list, NULL, NULL);
+ if (ret < 0)
+ goto out;
+
+ count = ret;
+
+ if (count > 0) {
+ resources = devm_kcalloc(&acpi_dev->dev, count,
+ sizeof(struct resource), GFP_KERNEL);
+ if (!resources) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ count = 0;
+ list_for_each_entry(rentry, &resource_list, node) {
+ memcpy(&resources[count], rentry->res,
+ sizeof(*resources));
+ count++;
+ }
+ ret = platform_device_add_resources(pdev, resources, count);
+ if (ret)
+ goto out;
+ } else
+ ret = platform_device_add_resources(pdev, pdata->ioresource, 1);
+
+out:
+ acpi_dev_free_resource_list(&resource_list);
+
+ return ret;
+}
+#else
+static int kempld_get_acpi_data(struct platform_device *pdev)
+{
+ return -ENODEV;
+}
+#endif /* CONFIG_ACPI */
+
static int kempld_probe(struct platform_device *pdev)
{
- const struct kempld_platform_data *pdata =
- dev_get_platdata(&pdev->dev);
+ const struct kempld_platform_data *pdata;
struct device *dev = &pdev->dev;
struct kempld_device_data *pld;
struct resource *ioport;
+ int ret;
+
+ if (kempld_pdev == NULL) {
+ ret = kempld_get_acpi_data(pdev);
+ if (ret < 0)
+ return ret;
+ kempld_acpi_mode = true;
+ } else if (kempld_pdev != pdev) {
+ dev_notice(dev, "platform device exists - not using ACPI\n");
+ return -ENODEV;
+ }
+ pdata = dev_get_platdata(dev);

pld = devm_kzalloc(dev, sizeof(*pld), GFP_KERNEL);
if (!pld)
@@ -482,6 +558,7 @@ static int kempld_remove(struct platform_device *pdev)
static struct platform_driver kempld_driver = {
.driver = {
.name = "kempld",
+ .acpi_match_table = ACPI_PTR(kempld_acpi_table),
},
.probe = kempld_probe,
.remove = kempld_remove,
@@ -800,6 +877,7 @@ MODULE_DEVICE_TABLE(dmi, kempld_dmi_table);
static int __init kempld_init(void)
{
const struct dmi_system_id *id;
+ int ret;

if (force_device_id[0]) {
for (id = kempld_dmi_table;
@@ -809,12 +887,19 @@ static int __init kempld_init(void)
break;
if (id->matches[0].slot == DMI_NONE)
return -ENODEV;
- } else {
- if (!dmi_check_system(kempld_dmi_table))
- return -ENODEV;
}

- return platform_driver_register(&kempld_driver);
+ ret = platform_driver_register(&kempld_driver);
+ if (ret)
+ return ret;
+
+ if (!kempld_pdev && !kempld_acpi_mode)
+ if (!dmi_check_system(kempld_dmi_table)) {
+ platform_driver_unregister(&kempld_driver);
+ return -ENODEV;
+ }
+
+ return 0;
}

static void __exit kempld_exit(void)
--
2.25.1