Skip to content

Commit 5226027

Browse files
committed
power: supply: macsmc_power: Add critical level shutdown & misc events
Signed-off-by: Hector Martin <[email protected]>
1 parent 0cd0a3c commit 5226027

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;
@@ -188,6 +197,34 @@ static int macsmc_battery_get_date(const char *s, int *out)
188197
return 0;
189198
}
190199

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

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

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

@@ -447,6 +564,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
447564
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
448565
struct power_supply_config psy_cfg = {};
449566
struct macsmc_power *power;
567+
u32 val;
450568
int ret;
451569

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

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

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

0 commit comments

Comments
 (0)