]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
mmc: core: Add infrastructure for undervoltage handling
authorOleksij Rempel <o.rempel@pengutronix.de>
Thu, 21 Aug 2025 13:07:50 +0000 (15:07 +0200)
committerUlf Hansson <ulf.hansson@linaro.org>
Fri, 22 Aug 2025 10:08:07 +0000 (12:08 +0200)
Implement the core infrastructure to allow MMC bus types to handle
REGULATOR_EVENT_UNDER_VOLTAGE events from power regulators. This is
primarily aimed at allowing devices like eMMC to perform an emergency
shutdown to prevent data corruption when a power failure is imminent.

This patch introduces:
- A new 'handle_undervoltage' function pointer to 'struct mmc_bus_ops'.
  Bus drivers (e.g., for eMMC) can implement this to define their
  emergency procedures.
- A workqueue ('uv_work') in 'struct mmc_supply' to handle the event
  asynchronously in a high-priority context.
- A new function 'mmc_handle_undervoltage()' which is called from the
  workqueue. It stops the host queue to prevent races with card removal,
  checks for the bus op, and invokes the handler.
- Functions to register and unregister the regulator notifier, intended
  to be called by bus drivers like 'mmc_attach_mmc' when a compatible
  card is detected.

The notifier is only registered for the main vmmc supply, as
undervoltage handling for vqmmc or vqmmc2 is not required at this
time.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
Link: https://lore.kernel.org/r/20250821130751.2089587-2-o.rempel@pengutronix.de
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
drivers/mmc/core/bus.c
drivers/mmc/core/core.c
drivers/mmc/core/core.h
drivers/mmc/core/host.c
drivers/mmc/core/regulator.c
include/linux/mmc/host.h

index 1cf64e0952fbe2ae18506da6b87beef71bf39162..ec4f3462bf80924bd3d73d9aab331fd549c31448 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <linux/mmc/card.h>
 #include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
 
 #include "core.h"
 #include "card.h"
@@ -383,6 +384,14 @@ int mmc_add_card(struct mmc_card *card)
 
        mmc_card_set_present(card);
 
+       /*
+        * Register for undervoltage notification if the card supports
+        * power-off notification, enabling emergency shutdowns.
+        */
+       if (mmc_card_mmc(card) &&
+           card->ext_csd.power_off_notification == EXT_CSD_POWER_ON)
+               mmc_regulator_register_undervoltage_notifier(card->host);
+
        return 0;
 }
 
@@ -394,6 +403,9 @@ void mmc_remove_card(struct mmc_card *card)
 {
        struct mmc_host *host = card->host;
 
+       if (mmc_card_present(card))
+               mmc_regulator_unregister_undervoltage_notifier(host);
+
        mmc_remove_card_debugfs(card);
 
        if (mmc_card_present(card)) {
index 88fd231fee1d150b22baebddb7ad47472c24fb32..860378bea557b306e7a931a94302060e96528f49 100644 (file)
@@ -1398,6 +1398,29 @@ void mmc_power_cycle(struct mmc_host *host, u32 ocr)
        mmc_power_up(host, ocr);
 }
 
+/**
+ * mmc_handle_undervoltage - Handle an undervoltage event on the MMC bus
+ * @host: The MMC host that detected the undervoltage condition
+ *
+ * This function is called when an undervoltage event is detected on one of
+ * the MMC regulators.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int mmc_handle_undervoltage(struct mmc_host *host)
+{
+       /* Stop the host to prevent races with card removal */
+       __mmc_stop_host(host);
+
+       if (!host->bus_ops || !host->bus_ops->handle_undervoltage)
+               return 0;
+
+       dev_warn(mmc_dev(host), "%s: Undervoltage detected, initiating emergency stop\n",
+                mmc_hostname(host));
+
+       return host->bus_ops->handle_undervoltage(host);
+}
+
 /*
  * Assign a mmc bus handler to a host. Only one bus handler may control a
  * host at any given time.
index 73f5d3d8c77d5da53795fe09beb384f134d6a886..a028b48be1644793a885aad02e3d739dd2417ee8 100644 (file)
@@ -31,6 +31,7 @@ struct mmc_bus_ops {
        int (*sw_reset)(struct mmc_host *);
        bool (*cache_enabled)(struct mmc_host *);
        int (*flush_cache)(struct mmc_host *);
+       int (*handle_undervoltage)(struct mmc_host *host);
 };
 
 void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
@@ -59,6 +60,10 @@ void mmc_power_off(struct mmc_host *host);
 void mmc_power_cycle(struct mmc_host *host, u32 ocr);
 void mmc_set_initial_state(struct mmc_host *host);
 u32 mmc_vddrange_to_ocrmask(int vdd_min, int vdd_max);
+int mmc_handle_undervoltage(struct mmc_host *host);
+void mmc_regulator_register_undervoltage_notifier(struct mmc_host *host);
+void mmc_regulator_unregister_undervoltage_notifier(struct mmc_host *host);
+void mmc_undervoltage_workfn(struct work_struct *work);
 
 static inline void mmc_delay(unsigned int ms)
 {
index f14671ea571628be81c705f64520e1f01882e5be..5f0ec23aeff553a98837e5ad3a96b35742ace10c 100644 (file)
@@ -564,6 +564,8 @@ struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
        INIT_WORK(&host->sdio_irq_work, sdio_irq_work);
        timer_setup(&host->retune_timer, mmc_retune_timer, 0);
 
+       INIT_WORK(&host->supply.uv_work, mmc_undervoltage_workfn);
+
        /*
         * By default, hosts do not support SGIO or large requests.
         * They have to set these according to their abilities.
index 3dae2e9b7978134db3bd2affb425677d1d7b34e7..a85179f1a4de6de04f559a6ff0a6af84a5ee12a5 100644 (file)
@@ -7,6 +7,7 @@
 #include <linux/err.h>
 #include <linux/log2.h>
 #include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
 
 #include <linux/mmc/host.h>
 
@@ -262,6 +263,82 @@ static inline int mmc_regulator_get_ocrmask(struct regulator *supply)
 
 #endif /* CONFIG_REGULATOR */
 
+/* To be called from a high-priority workqueue */
+void mmc_undervoltage_workfn(struct work_struct *work)
+{
+       struct mmc_supply *supply;
+       struct mmc_host *host;
+
+       supply = container_of(work, struct mmc_supply, uv_work);
+       host = container_of(supply, struct mmc_host, supply);
+
+       mmc_handle_undervoltage(host);
+}
+
+static int mmc_handle_regulator_event(struct notifier_block *nb,
+                                     unsigned long event, void *data)
+{
+       struct mmc_supply *supply = container_of(nb, struct mmc_supply,
+                                                vmmc_nb);
+       struct mmc_host *host = container_of(supply, struct mmc_host, supply);
+       unsigned long flags;
+
+       switch (event) {
+       case REGULATOR_EVENT_UNDER_VOLTAGE:
+               spin_lock_irqsave(&host->lock, flags);
+               if (host->undervoltage) {
+                       spin_unlock_irqrestore(&host->lock, flags);
+                       return NOTIFY_OK;
+               }
+
+               host->undervoltage = true;
+               spin_unlock_irqrestore(&host->lock, flags);
+
+               queue_work(system_highpri_wq, &host->supply.uv_work);
+               break;
+       default:
+               return NOTIFY_DONE;
+       }
+
+       return NOTIFY_OK;
+}
+
+/**
+ * mmc_regulator_register_undervoltage_notifier - Register for undervoltage
+ *                                               events
+ * @host: MMC host
+ *
+ * To be called by a bus driver when a card supporting graceful shutdown
+ * is attached.
+ */
+void mmc_regulator_register_undervoltage_notifier(struct mmc_host *host)
+{
+       int ret;
+
+       if (IS_ERR_OR_NULL(host->supply.vmmc))
+               return;
+
+       host->supply.vmmc_nb.notifier_call = mmc_handle_regulator_event;
+       ret = regulator_register_notifier(host->supply.vmmc,
+                                         &host->supply.vmmc_nb);
+       if (ret)
+               dev_warn(mmc_dev(host), "Failed to register vmmc notifier: %d\n", ret);
+}
+
+/**
+ * mmc_regulator_unregister_undervoltage_notifier - Unregister undervoltage
+ *                                                 notifier
+ * @host: MMC host
+ */
+void mmc_regulator_unregister_undervoltage_notifier(struct mmc_host *host)
+{
+       if (IS_ERR_OR_NULL(host->supply.vmmc))
+               return;
+
+       regulator_unregister_notifier(host->supply.vmmc, &host->supply.vmmc_nb);
+       cancel_work_sync(&host->supply.uv_work);
+}
+
 /**
  * mmc_regulator_get_supply - try to get VMMC and VQMMC regulators for a host
  * @mmc: the host to regulate
index 5ed5d203de23c1cd503061e0ef6a8bfd7253c8af..e0d935a4ac1d5a2d5eab037c9f40b8fffb6f2e30 100644 (file)
@@ -337,11 +337,15 @@ struct mmc_slot {
 
 struct regulator;
 struct mmc_pwrseq;
+struct notifier_block;
 
 struct mmc_supply {
        struct regulator *vmmc;         /* Card power supply */
        struct regulator *vqmmc;        /* Optional Vccq supply */
        struct regulator *vqmmc2;       /* Optional supply for phy */
+
+       struct notifier_block vmmc_nb;  /* Notifier for vmmc */
+       struct work_struct uv_work;     /* Undervoltage work */
 };
 
 struct mmc_ctx {
@@ -494,6 +498,13 @@ struct mmc_host {
        unsigned int            can_dma_map_merge:1; /* merging can be used */
        unsigned int            vqmmc_enabled:1; /* vqmmc regulator is enabled */
 
+       /*
+        * Indicates if an undervoltage event has already been handled.
+        * This prevents repeated regulator notifiers from triggering
+        * multiple REGULATOR_EVENT_UNDER_VOLTAGE events.
+        */
+       unsigned int            undervoltage:1;  /* Undervoltage state */
+
        int                     rescan_disable; /* disable card detection */
        int                     rescan_entered; /* used with nonremovable devices */