Skip to content

Commit 49a85dd

Browse files
committed
power: supply: macsmc: support charge_behaviour on newer SMC firmware
Newer Apple SMC firmware (found on M3 devices and updated M1/M2) has removed the legacy `CH0C` (Inhibit Charge) and `CH0I` (Force Discharge) keys. Reading these missing keys results in -EIO (-5) errors, causing the `charge_behaviour` sysfs property to fail completely. This patch adds support for the new `CHTE` key used for charge inhibition on these devices. For now, it seems that `auto` and `inhibit-charge` are the only possible behaviours to set using this new key, however further macOS tracing may reveal additional behaviour states in future. Changes: 1. Detects the presence of `CHTE`, `CH0C`, and `CH0I` during probe. 2. Only exposes `force_discharge` capability if `CH0I` is actually present. 3. Implements read/write support for `CHTE` using raw byte buffers (this is to avoid endianness issues with the kernel's u32 helpers) Fully backwards compatible with both old and new firmwares. Tested on M3 with new firmware. Signed-off-by: Michael Reeves <[email protected]>
1 parent fe39760 commit 49a85dd

File tree

1 file changed

+126
-35
lines changed

1 file changed

+126
-35
lines changed

drivers/power/supply/macsmc-power.c

Lines changed: 126 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@ struct macsmc_power {
3939
char model_name[MAX_STRING_LENGTH];
4040
char serial_number[MAX_STRING_LENGTH];
4141
char mfg_date[MAX_STRING_LENGTH];
42+
4243
bool has_chwa;
4344
bool has_chls;
45+
bool has_ch0i;
46+
bool has_ch0c;
47+
bool has_chte;
48+
4449
u8 num_cells;
4550
int nominal_voltage_mv;
4651

@@ -57,8 +62,8 @@ struct macsmc_power {
5762
static int macsmc_log_power_set(const char *val, const struct kernel_param *kp);
5863

5964
static const struct kernel_param_ops macsmc_log_power_ops = {
60-
.set = macsmc_log_power_set,
61-
.get = param_get_bool,
65+
.set = macsmc_log_power_set,
66+
.get = param_get_bool,
6267
};
6368

6469
static bool log_power = false;
@@ -242,6 +247,7 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
242247
*/
243248
if (power->has_chls) {
244249
u16 vu16;
250+
245251
ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16);
246252
if (ret == sizeof(vu16) && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD)
247253
charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET;
@@ -253,6 +259,7 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
253259

254260
if (charge_limit > 0) {
255261
u8 buic = 0;
262+
256263
if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
257264
buic >= charge_limit)
258265
limited = true;
@@ -291,55 +298,112 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
291298
static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power)
292299
{
293300
int ret;
294-
u8 val;
301+
u8 val8;
302+
u8 chte_buf[4];
303+
304+
if (power->has_ch0i) {
305+
/* CH0I returns a bitmask like the low byte of CH0R */
306+
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8);
307+
if (ret)
308+
return ret;
309+
if (val8 & CH0R_NOAC_CH0I)
310+
return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
311+
}
295312

296-
/* CH0I returns a bitmask like the low byte of CH0R */
297-
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val);
298-
if (ret)
299-
return ret;
300-
if (val & CH0R_NOAC_CH0I)
301-
return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
313+
/* Prefer CHTE available in newer firmwares */
314+
if (power->has_chte) {
315+
ret = apple_smc_read(power->smc, SMC_KEY(CHTE), chte_buf, 4);
316+
if (ret < 0)
317+
return ret;
318+
319+
if (chte_buf[0] == 0x01)
320+
return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
321+
322+
} else if (power->has_ch0c) {
323+
/* CH0C returns a bitmask containing CH0B/CH0C flags */
324+
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8);
325+
if (ret)
326+
return ret;
327+
if (val8 & CH0X_CH0C)
328+
return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
329+
}
302330

303-
/* CH0C returns a bitmask containing CH0B/CH0C flags */
304-
ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val);
305-
if (ret)
306-
return ret;
307-
if (val & CH0X_CH0C)
308-
return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
309-
else
310-
return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
331+
return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
311332
}
312333

313334
static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val)
314335
{
315-
u8 ch0i, ch0c;
316336
int ret;
317337

318338
/*
319-
* CH0I/CH0C are "hard" controls that will allow the battery to run down to 0.
320-
* CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%;
321-
* we don't expose these yet.
339+
* apple_smc_write_u32 does weird things with endianess,
340+
* so we write raw bytes to ensure correctness of CHTE
322341
*/
342+
u8 chte_inhibit[4] = {0x01, 0x00, 0x00, 0x00};
343+
u8 chte_auto[4] = {0x00, 0x00, 0x00, 0x00};
323344

324345
switch (val) {
325346
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
326-
ch0i = ch0c = 0;
347+
/*
348+
* CH0I/CH0C/CHTE are "hard" controls that will allow the battery to run down to 0.
349+
* CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%;
350+
* we don't expose these yet.
351+
*/
352+
if (power->has_ch0i) {
353+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
354+
if (ret)
355+
return ret;
356+
}
357+
358+
if (power->has_chte) {
359+
ret = apple_smc_write(power->smc, SMC_KEY(CHTE), chte_auto, 4);
360+
if (ret)
361+
return ret;
362+
} else if (power->has_ch0c) {
363+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
364+
if (ret)
365+
return ret;
366+
}
327367
break;
368+
328369
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
329-
ch0i = 0;
330-
ch0c = 1;
370+
if (power->has_ch0i) {
371+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
372+
if (ret)
373+
return ret;
374+
}
375+
376+
/* Prefer CHTE available in newer firmwares */
377+
if (power->has_chte)
378+
return apple_smc_write(power->smc, SMC_KEY(CHTE), chte_inhibit, 4);
379+
else if (power->has_ch0c)
380+
return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 1);
381+
else
382+
return -EINVAL;
331383
break;
384+
332385
case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
333-
ch0i = 1;
334-
ch0c = 0;
335-
break;
386+
if (!power->has_ch0i)
387+
return -EINVAL;
388+
389+
/* Prefer CHTE available in newer firmwares */
390+
if (power->has_chte) {
391+
ret = apple_smc_write(power->smc, SMC_KEY(CHTE), chte_auto, 4);
392+
if (ret)
393+
return ret;
394+
} else if (power->has_ch0c) {
395+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
396+
if (ret)
397+
return ret;
398+
}
399+
400+
return apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 1);
401+
336402
default:
337403
return -EINVAL;
338404
}
339-
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), ch0i);
340-
if (ret)
341-
return ret;
342-
return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), ch0c);
405+
406+
return 0;
343407
}
344408

345409
static int macsmc_battery_get_date(const char *s, int *out)
@@ -539,8 +603,7 @@ static int macsmc_battery_get_property(struct power_supply *psy,
539603
val->intval = vu16 & 0xff;
540604
if (val->intval < CHLS_MIN_END_THRESHOLD || val->intval >= 100)
541605
val->intval = 100;
542-
}
543-
else if (power->has_chwa) {
606+
} else if (power->has_chwa) {
544607
flag = false;
545608
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag);
546609
val->intval = flag ? CHWA_FIXED_END_THRESHOLD : 100;
@@ -838,8 +901,9 @@ static int macsmc_power_probe(struct platform_device *pdev)
838901
struct power_supply_config psy_cfg = {};
839902
struct macsmc_power *power;
840903
bool flag;
841-
u32 val;
904+
u8 val8;
842905
u16 vu16;
906+
u32 val32;
843907
int ret;
844908

845909
power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
@@ -861,10 +925,37 @@ static int macsmc_power_probe(struct platform_device *pdev)
861925
apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1);
862926
apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1);
863927

928+
if (apple_smc_read_u32(power->smc, SMC_KEY(CHTE), &val32) >= 0)
929+
power->has_chte = true;
930+
931+
if (apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8) >= 0)
932+
power->has_ch0c = true;
933+
934+
if (apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8) >= 0)
935+
power->has_ch0i = true;
936+
864937
/* Turn off the "optimized battery charging" flags, in case macOS left them on */
938+
if (power->has_chte)
939+
apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 0);
940+
else if (power->has_ch0c)
941+
apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
942+
943+
if (power->has_ch0i)
944+
apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
945+
865946
apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
866947
apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
867948

949+
power->batt_desc.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
950+
951+
/* Newer firmwares do not have force discharge, so check if it's supported */
952+
if (power->has_ch0i)
953+
power->batt_desc.charge_behaviours |= BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
954+
955+
/* Older firmware uses CH0C, and newer firmware uses CHTE, so check if at least one is present*/
956+
if (power->has_chte || power->has_ch0c)
957+
power->batt_desc.charge_behaviours |= BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
958+
868959
/*
869960
* Prefer CHWA as the SMC firmware from iBoot-10151.1.1 is not compatible with
870961
* this CHLS usage.
@@ -882,7 +973,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
882973
power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
883974

884975
/* Doing one read of this flag enables critical shutdown notifications */
885-
apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val);
976+
apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32);
886977

887978
psy_cfg.drv_data = power;
888979
power->batt = devm_power_supply_register(&pdev->dev, &power->batt_desc, &psy_cfg);

0 commit comments

Comments
 (0)