mmc: core: Add infrastructure for undervoltage handling

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>
This commit is contained in:
Oleksij Rempel
2025-08-22 12:08:07 +02:00
committed by Ulf Hansson
parent 6fb942b85a
commit 3202d6ed93
6 changed files with 130 additions and 0 deletions
+12
View 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)) {
+23
View 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.
+5
View 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)
{
+2
View 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.
+77
View 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
+11
View 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 */