wifi: wfx: allow SDIO device to wake up the host
authorJérôme Pouiller <jerome.pouiller@silabs.com>
Tue, 4 Mar 2025 15:32:23 +0000 (16:32 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 7 Mar 2025 08:24:22 +0000 (09:24 +0100)
If the device is connected with SDIO bus, it can wake up the host using
either the SDIO controller (that requires MMC_PM_WAKE_SDIO_IRQ
capability) or the out-of-bound IRQ. This patch allows both.

Obviously, the SDIO controller must be able to keep the SDIO device
powered on.

Signed-off-by: Jérôme Pouiller <jerome.pouiller@silabs.com>
Link: https://patch.msgid.link/20250304153224.39083-5-jerome.pouiller@silabs.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/silabs/wfx/bus_sdio.c

index f290eecde773fbab9b840805fbb8684c754a9f34..27c5d7f534a4d109d46b9a56e0ad1733cbef08ed 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/of_irq.h>
 #include <linux/irq.h>
 #include <linux/align.h>
+#include <linux/pm.h>
 
 #include "bus.h"
 #include "wfx.h"
@@ -191,9 +192,48 @@ static const struct of_device_id wfx_sdio_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, wfx_sdio_of_match);
 
+static int wfx_sdio_suspend(struct device *dev)
+{
+       struct sdio_func *func = dev_to_sdio_func(dev);
+       struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
+       int ret;
+
+       if (!device_may_wakeup(dev))
+               return 0;
+
+       flush_work(&bus->core->hif.bh);
+       /* Either "wakeup-source" attribute or out-of-band IRQ is required for
+        * WoWLAN
+        */
+       if (bus->of_irq) {
+               ret = enable_irq_wake(bus->of_irq);
+               if (ret)
+                       return ret;
+       } else {
+               ret = sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
+               if (ret)
+                       return ret;
+       }
+       return sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+}
+
+static int wfx_sdio_resume(struct device *dev)
+{
+       struct sdio_func *func = dev_to_sdio_func(dev);
+       struct wfx_sdio_priv *bus = sdio_get_drvdata(func);
+
+       if (!device_may_wakeup(dev))
+               return 0;
+       if (bus->of_irq)
+               return disable_irq_wake(bus->of_irq);
+       else
+               return 0;
+}
+
 static int wfx_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
 {
        const struct wfx_platform_data *pdata = of_device_get_match_data(&func->dev);
+       mmc_pm_flag_t pm_flag = sdio_get_host_pm_caps(func);
        struct device_node *np = func->dev.of_node;
        struct wfx_sdio_priv *bus;
        int ret;
@@ -235,6 +275,9 @@ static int wfx_sdio_probe(struct sdio_func *func, const struct sdio_device_id *i
        if (ret)
                goto sdio_release;
 
+       if (pm_flag & MMC_PM_KEEP_POWER)
+               device_set_wakeup_capable(&func->dev, true);
+
        return 0;
 
 sdio_release:
@@ -261,6 +304,8 @@ static const struct sdio_device_id wfx_sdio_ids[] = {
 };
 MODULE_DEVICE_TABLE(sdio, wfx_sdio_ids);
 
+static DEFINE_SIMPLE_DEV_PM_OPS(wfx_sdio_pm_ops, wfx_sdio_suspend, wfx_sdio_resume);
+
 struct sdio_driver wfx_sdio_driver = {
        .name = "wfx-sdio",
        .id_table = wfx_sdio_ids,
@@ -268,5 +313,6 @@ struct sdio_driver wfx_sdio_driver = {
        .remove = wfx_sdio_remove,
        .drv = {
                .of_match_table = wfx_sdio_of_match,
+               .pm = &wfx_sdio_pm_ops,
        }
 };