mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
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:
committed by
Ulf Hansson
parent
6fb942b85a
commit
3202d6ed93
@@ -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)) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user