Skip to content

Commit aee3b28

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 <michael.reeves077@gmail.com>
1 parent fe39760 commit aee3b28

File tree

1 file changed

+126
-34
lines changed

1 file changed

+126
-34
lines changed

drivers/power/supply/macsmc-power.c

Lines changed: 126 additions & 34 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,113 @@ 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.
339+
* apple_smc_write_u32 does weird things with endianess,
340+
* so we write raw bytes to ensure correctness of CHTE
341+
*/
342+
u8 chte_inhibit[4] = {0x01, 0x00, 0x00, 0x00};
343+
u8 chte_auto[4] = {0x00, 0x00, 0x00, 0x00};
344+
345+
/*
346+
* CH0I/CH0C/CHTE are "hard" controls that will allow the battery to run down to 0.
320347
* CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%;
321348
* we don't expose these yet.
322349
*/
323-
350+
324351
switch (val) {
325352
case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
326-
ch0i = ch0c = 0;
353+
if (power->has_ch0i) {
354+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
355+
if (ret)
356+
return ret;
357+
}
358+
359+
if (power->has_chte) {
360+
ret = apple_smc_write(power->smc, SMC_KEY(CHTE), chte_auto, 4);
361+
if (ret)
362+
return ret;
363+
} else if (power->has_ch0c) {
364+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
365+
if (ret)
366+
return ret;
367+
}
327368
break;
369+
328370
case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
329-
ch0i = 0;
330-
ch0c = 1;
371+
if (power->has_ch0i) {
372+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
373+
if (ret)
374+
return ret;
375+
}
376+
377+
/* Prefer CHTE available in newer firmwares */
378+
if (power->has_chte)
379+
return apple_smc_write(power->smc, SMC_KEY(CHTE), chte_inhibit, 4);
380+
else if (power->has_ch0c)
381+
return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 1);
382+
else
383+
return -EINVAL;
331384
break;
385+
332386
case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
333-
ch0i = 1;
334-
ch0c = 0;
335-
break;
387+
if (!power->has_ch0i)
388+
return -EINVAL;
389+
390+
/* Prefer CHTE available in newer firmwares */
391+
if (power->has_chte) {
392+
ret = apple_smc_write(power->smc, SMC_KEY(CHTE), chte_auto, 4);
393+
if (ret)
394+
return ret;
395+
} else if (power->has_ch0c) {
396+
ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
397+
if (ret)
398+
return ret;
399+
}
400+
401+
return apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 1);
402+
336403
default:
337404
return -EINVAL;
338405
}
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);
406+
407+
return 0;
343408
}
344409

345410
static int macsmc_battery_get_date(const char *s, int *out)
@@ -539,8 +604,7 @@ static int macsmc_battery_get_property(struct power_supply *psy,
539604
val->intval = vu16 & 0xff;
540605
if (val->intval < CHLS_MIN_END_THRESHOLD || val->intval >= 100)
541606
val->intval = 100;
542-
}
543-
else if (power->has_chwa) {
607+
} else if (power->has_chwa) {
544608
flag = false;
545609
ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA), &flag);
546610
val->intval = flag ? CHWA_FIXED_END_THRESHOLD : 100;
@@ -838,8 +902,9 @@ static int macsmc_power_probe(struct platform_device *pdev)
838902
struct power_supply_config psy_cfg = {};
839903
struct macsmc_power *power;
840904
bool flag;
841-
u32 val;
905+
u8 val8;
842906
u16 vu16;
907+
u32 val32;
843908
int ret;
844909

845910
power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
@@ -861,10 +926,37 @@ static int macsmc_power_probe(struct platform_device *pdev)
861926
apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1);
862927
apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1);
863928

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

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

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

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

0 commit comments

Comments
 (0)