Skip to content

Commit a57409a

Browse files
IntegralPilotjannau
authored andcommitted
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 93f4f24 commit a57409a

File tree

1 file changed

+125
-33
lines changed

1 file changed

+125
-33
lines changed

drivers/power/supply/macsmc-power.c

Lines changed: 125 additions & 33 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
*/
323350

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;
@@ -853,8 +917,9 @@ static int macsmc_power_probe(struct platform_device *pdev)
853917
struct power_supply_config psy_cfg = {};
854918
struct macsmc_power *power;
855919
bool flag;
856-
u32 val;
920+
u8 val8;
857921
u16 vu16;
922+
u32 val32;
858923
int ret;
859924

860925
power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
@@ -876,10 +941,37 @@ static int macsmc_power_probe(struct platform_device *pdev)
876941
apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1);
877942
apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1);
878943

944+
if (apple_smc_read_u32(power->smc, SMC_KEY(CHTE), &val32) >= 0)
945+
power->has_chte = true;
946+
947+
if (apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val8) >= 0)
948+
power->has_ch0c = true;
949+
950+
if (apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val8) >= 0)
951+
power->has_ch0i = true;
952+
879953
/* Turn off the "optimized battery charging" flags, in case macOS left them on */
954+
if (power->has_chte)
955+
apple_smc_write_u32(power->smc, SMC_KEY(CHTE), 0);
956+
else if (power->has_ch0c)
957+
apple_smc_write_u8(power->smc, SMC_KEY(CH0C), 0);
958+
959+
if (power->has_ch0i)
960+
apple_smc_write_u8(power->smc, SMC_KEY(CH0I), 0);
961+
880962
apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
881963
apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
882964

965+
power->batt_desc.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO);
966+
967+
/* Newer firmwares do not have force discharge, so check if it's supported */
968+
if (power->has_ch0i)
969+
power->batt_desc.charge_behaviours |= BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE);
970+
971+
/* Older firmware uses CH0C, and newer firmware uses CHTE, so check if at least one is present*/
972+
if (power->has_chte || power->has_ch0c)
973+
power->batt_desc.charge_behaviours |= BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE);
974+
883975
/*
884976
* Prefer CHWA as the SMC firmware from iBoot-10151.1.1 is not compatible with
885977
* this CHLS usage.
@@ -897,7 +989,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
897989
power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
898990

899991
/* Doing one read of this flag enables critical shutdown notifications */
900-
apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val);
992+
apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32);
901993

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

0 commit comments

Comments
 (0)