Skip to content

Commit c59a890

Browse files
marcanjannau
authored andcommitted
power: supply: macsmc_power: Add critical level shutdown & misc events
Signed-off-by: Hector Martin <marcan@marcan.st>
1 parent c83571b commit c59a890

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

drivers/power/supply/macsmc_power.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
#include <linux/mfd/core.h>
1212
#include <linux/mfd/macsmc.h>
1313
#include <linux/power_supply.h>
14+
#include <linux/reboot.h>
15+
#include <linux/delay.h>
16+
#include <linux/workqueue.h>
1417

1518
#define MAX_STRING_LENGTH 256
1619

@@ -26,6 +29,9 @@ struct macsmc_power {
2629
struct power_supply *ac;
2730

2831
struct notifier_block nb;
32+
33+
struct work_struct critical_work;
34+
bool shutdown_started;
2935
};
3036

3137
#define CHNC_BATTERY_FULL BIT(0)
@@ -46,6 +52,9 @@ struct macsmc_power {
4652
#define CH0X_CH0C BIT(0)
4753
#define CH0X_CH0B BIT(1)
4854

55+
#define ACSt_CAN_BOOT_AP BIT(2)
56+
#define ACSt_CAN_BOOT_IBOOT BIT(1)
57+
4958
static int macsmc_battery_get_status(struct macsmc_power *power)
5059
{
5160
u64 nocharge_flags;
@@ -187,6 +196,34 @@ static int macsmc_battery_get_date(const char *s, int *out)
187196
return 0;
188197
}
189198

199+
static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
200+
{
201+
u32 val;
202+
int ret;
203+
204+
/* Check for emergency shutdown condition */
205+
if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val)
206+
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
207+
208+
/* Check AC status for whether we could boot in this state */
209+
if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) {
210+
if (!(val & ACSt_CAN_BOOT_IBOOT))
211+
return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
212+
213+
if (!(val & ACSt_CAN_BOOT_AP))
214+
return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
215+
}
216+
217+
/* Check battery full flag */
218+
ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC));
219+
if (ret > 0)
220+
return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
221+
else if (ret == 0)
222+
return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
223+
else
224+
return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
225+
}
226+
190227
static int macsmc_battery_get_property(struct power_supply *psy,
191228
enum power_supply_property psp,
192229
union power_supply_propval *val)
@@ -224,6 +261,10 @@ static int macsmc_battery_get_property(struct power_supply *psy,
224261
ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
225262
val->intval = vu8;
226263
break;
264+
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
265+
val->intval = macsmc_battery_get_capacity_level(power);
266+
ret = val->intval < 0 ? val->intval : 0;
267+
break;
227268
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
228269
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
229270
val->intval = vu16 * 1000;
@@ -343,6 +384,7 @@ static enum power_supply_property macsmc_battery_props[] = {
343384
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
344385
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
345386
POWER_SUPPLY_PROP_CAPACITY,
387+
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
346388
POWER_SUPPLY_PROP_VOLTAGE_NOW,
347389
POWER_SUPPLY_PROP_CURRENT_NOW,
348390
POWER_SUPPLY_PROP_POWER_NOW,
@@ -424,6 +466,59 @@ static const struct power_supply_desc macsmc_ac_desc = {
424466
.num_properties = ARRAY_SIZE(macsmc_ac_props),
425467
};
426468

469+
static void macsmc_power_critical_work(struct work_struct *wrk) {
470+
struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work);
471+
int ret;
472+
u32 bcf0;
473+
u16 bitv, b0av;
474+
475+
/*
476+
* Check if the battery voltage is below the design voltage. If it is,
477+
* we have a few seconds until the machine dies. Explicitly shut down,
478+
* which at least gets the NVMe controller to flush its cache.
479+
*/
480+
if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 &&
481+
apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 &&
482+
b0av < bitv) {
483+
dev_crit(power->dev, "Emergency notification: Battery is critical\n");
484+
if (kernel_can_power_off())
485+
kernel_power_off();
486+
else /* Missing macsmc-reboot driver? In this state, this will not boot anyway. */
487+
kernel_restart("Battery is critical");
488+
}
489+
490+
/* This spams once per second, so make sure we only trigger shutdown once. */
491+
if (power->shutdown_started)
492+
return;
493+
494+
/* Check for battery empty condition */
495+
ret = apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0);
496+
if (ret < 0) {
497+
dev_err(power->dev,
498+
"Emergency notification: Failed to read battery status\n");
499+
} else if (bcf0 == 0) {
500+
dev_warn(power->dev, "Emergency notification: Battery status is OK?\n");
501+
return;
502+
} else {
503+
dev_warn(power->dev, "Emergency notification: Battery is empty\n");
504+
}
505+
506+
power->shutdown_started = true;
507+
508+
/*
509+
* Attempt to trigger an orderly shutdown. At this point, we should have a few
510+
* minutes of reserve capacity left, enough to do a clean shutdown.
511+
*/
512+
dev_warn(power->dev, "Shutting down in 10 seconds\n");
513+
ssleep(10);
514+
515+
/*
516+
* Don't force it; if this stalls or fails, the last-resort check above will
517+
* trigger a hard shutdown when shutdown is truly imminent.
518+
*/
519+
orderly_poweroff(false);
520+
}
521+
427522
static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
428523
{
429524
struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
@@ -435,6 +530,28 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo
435530
power_supply_changed(power->batt);
436531
power_supply_changed(power->ac);
437532

533+
return NOTIFY_OK;
534+
} else if (event == 0x71020000) {
535+
schedule_work(&power->critical_work);
536+
537+
return NOTIFY_OK;
538+
} else if ((event & 0xffff0000) == 0x71060000) {
539+
u8 changed_port = event >> 8;
540+
u8 cur_port;
541+
542+
/* Port charging state change? */
543+
if (apple_smc_read_u8(power->smc, SMC_KEY(AC-W), &cur_port) >= 0) {
544+
dev_info(power->dev, "Port %d state change (charge port: %d)\n",
545+
changed_port + 1, cur_port);
546+
}
547+
548+
power_supply_changed(power->batt);
549+
power_supply_changed(power->ac);
550+
551+
return NOTIFY_OK;
552+
} else if ((event & 0xff000000) == 0x71000000) {
553+
dev_info(power->dev, "Unknown charger event 0x%lx\n", event);
554+
438555
return NOTIFY_OK;
439556
}
440557

@@ -446,6 +563,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
446563
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
447564
struct power_supply_config psy_cfg = {};
448565
struct macsmc_power *power;
566+
u32 val;
449567
int ret;
450568

451569
power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
@@ -469,6 +587,9 @@ static int macsmc_power_probe(struct platform_device *pdev)
469587
apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
470588
apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
471589

590+
/* Doing one read of this flag enables critical shutdown notifications */
591+
apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val);
592+
472593
psy_cfg.drv_data = power;
473594
power->batt = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
474595
if (IS_ERR(power->batt)) {
@@ -487,6 +608,8 @@ static int macsmc_power_probe(struct platform_device *pdev)
487608
power->nb.notifier_call = macsmc_power_event;
488609
apple_smc_register_notifier(power->smc, &power->nb);
489610

611+
INIT_WORK(&power->critical_work, macsmc_power_critical_work);
612+
490613
return 0;
491614
}
492615

0 commit comments

Comments
 (0)