Skip to content

Commit 4d782bf

Browse files
marcanjgouly
andcommitted
power: supply: macsmc_power: Driver for Apple SMC power/battery stats
This driver implements support for battery stats on top of the macsmc framework, to support Apple M1 Mac machines. Co-authored-by: Joey Gouly <[email protected]> Signed-off-by: Hector Martin <[email protected]>
1 parent 205e985 commit 4d782bf

3 files changed

Lines changed: 267 additions & 0 deletions

File tree

drivers/power/supply/Kconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,4 +934,11 @@ config BATTERY_UG3105
934934
device is off or suspended, the functionality of this driver is
935935
limited to reporting capacity only.
936936

937+
config CHARGER_MACSMC
938+
tristate "Apple SMC Charger / Battery support"
939+
depends on APPLE_SMC
940+
help
941+
Say Y here to enable support for the charger and battery controls on
942+
Apple SMC controllers, as used on Apple Silicon Macs.
943+
937944
endif # POWER_SUPPLY

drivers/power/supply/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,4 @@ obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
109109
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
110110
obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
111111
obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o
112+
obj-$(CONFIG_CHARGER_MACSMC) += macsmc_power.o
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
// SPDX-License-Identifier: GPL-2.0-only OR MIT
2+
/*
3+
* Apple SMC Power/Battery Management
4+
* Copyright The Asahi Linux Contributors
5+
*/
6+
7+
#include <linux/module.h>
8+
#include <linux/of.h>
9+
#include <linux/platform_device.h>
10+
#include <linux/mfd/core.h>
11+
#include <linux/mfd/macsmc.h>
12+
#include <linux/power_supply.h>
13+
14+
#define MAX_STRING_LENGTH 256
15+
16+
struct macsmc_power {
17+
struct device *dev;
18+
struct apple_smc *smc;
19+
struct power_supply *psy;
20+
char model_name[MAX_STRING_LENGTH];
21+
char serial_number[MAX_STRING_LENGTH];
22+
23+
struct notifier_block nb;
24+
};
25+
26+
static int macsmc_battery_get_status(struct macsmc_power *power)
27+
{
28+
u8 val;
29+
int ret;
30+
31+
ret = apple_smc_read_u8(power->smc, SMC_KEY(BSFC), &val);
32+
if (ret)
33+
return ret;
34+
if (val == 1)
35+
return POWER_SUPPLY_STATUS_FULL;
36+
37+
ret = apple_smc_read_u8(power->smc, SMC_KEY(CHSC), &val);
38+
if (ret)
39+
return ret;
40+
if (val == 1)
41+
return POWER_SUPPLY_STATUS_CHARGING;
42+
43+
ret = apple_smc_read_u8(power->smc, SMC_KEY(CHCC), &val);
44+
if (ret)
45+
return ret;
46+
if (val == 0)
47+
return POWER_SUPPLY_STATUS_DISCHARGING;
48+
49+
ret = apple_smc_read_u8(power->smc, SMC_KEY(CHCE), &val);
50+
if (ret)
51+
return ret;
52+
if (val == 0)
53+
return POWER_SUPPLY_STATUS_DISCHARGING;
54+
else
55+
return POWER_SUPPLY_STATUS_NOT_CHARGING;
56+
57+
58+
}
59+
60+
static int macsmc_battery_get_property(struct power_supply *psy,
61+
enum power_supply_property psp,
62+
union power_supply_propval *val)
63+
{
64+
struct macsmc_power *power = power_supply_get_drvdata(psy);
65+
int ret = 0;
66+
u16 vu16;
67+
u32 vu32;
68+
s16 vs16;
69+
s32 vs32;
70+
s64 vs64;
71+
72+
switch (psp) {
73+
case POWER_SUPPLY_PROP_STATUS:
74+
val->intval = macsmc_battery_get_status(power);
75+
ret = val->intval < 0 ? val->intval : 0;
76+
break;
77+
case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
78+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
79+
val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
80+
break;
81+
case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
82+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
83+
val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
84+
break;
85+
case POWER_SUPPLY_PROP_CAPACITY:
86+
ret = apple_smc_read_u16(power->smc, SMC_KEY(BRSC), &vu16);
87+
val->intval = vu16;
88+
break;
89+
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
90+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
91+
val->intval = vu16 * 1000;
92+
break;
93+
case POWER_SUPPLY_PROP_CURRENT_NOW:
94+
ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
95+
val->intval = vs16 * 1000;
96+
break;
97+
case POWER_SUPPLY_PROP_POWER_NOW:
98+
ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
99+
val->intval = vs32 * 1000;
100+
break;
101+
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
102+
ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
103+
val->intval = vu16 * 1000;
104+
break;
105+
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
106+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
107+
val->intval = vu16 * 1000;
108+
break;
109+
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
110+
ret = apple_smc_read_u32(power->smc, SMC_KEY(CSIL), &vu32);
111+
val->intval = vu32 * 1000;
112+
break;
113+
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
114+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
115+
val->intval = vu16 * 1000;
116+
break;
117+
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
118+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
119+
val->intval = vu16 * 1000;
120+
break;
121+
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
122+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
123+
val->intval = vu16 * 1000;
124+
break;
125+
case POWER_SUPPLY_PROP_CHARGE_FULL:
126+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
127+
val->intval = vu16 * 1000;
128+
break;
129+
case POWER_SUPPLY_PROP_CHARGE_NOW:
130+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
131+
val->intval = swab16(vu16) * 1000;
132+
break;
133+
case POWER_SUPPLY_PROP_TEMP:
134+
ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
135+
val->intval = vu16 - 2732;
136+
break;
137+
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
138+
ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
139+
val->intval = vs64;
140+
break;
141+
case POWER_SUPPLY_PROP_MODEL_NAME:
142+
val->strval = power->model_name;
143+
break;
144+
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
145+
val->strval = power->serial_number;
146+
break;
147+
default:
148+
return -EINVAL;
149+
}
150+
151+
return ret;
152+
}
153+
154+
static enum power_supply_property macsmc_battery_props[] = {
155+
POWER_SUPPLY_PROP_STATUS,
156+
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
157+
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
158+
POWER_SUPPLY_PROP_CAPACITY,
159+
POWER_SUPPLY_PROP_VOLTAGE_NOW,
160+
POWER_SUPPLY_PROP_CURRENT_NOW,
161+
POWER_SUPPLY_PROP_POWER_NOW,
162+
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
163+
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
164+
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
165+
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
166+
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
167+
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
168+
POWER_SUPPLY_PROP_CHARGE_FULL,
169+
POWER_SUPPLY_PROP_CHARGE_NOW,
170+
POWER_SUPPLY_PROP_TEMP,
171+
POWER_SUPPLY_PROP_CHARGE_COUNTER,
172+
POWER_SUPPLY_PROP_MODEL_NAME,
173+
POWER_SUPPLY_PROP_SERIAL_NUMBER,
174+
};
175+
176+
static const struct power_supply_desc macsmc_battery_desc = {
177+
.name = "macsmc-battery",
178+
.type = POWER_SUPPLY_TYPE_BATTERY,
179+
.get_property = macsmc_battery_get_property,
180+
.properties = macsmc_battery_props,
181+
.num_properties = ARRAY_SIZE(macsmc_battery_props),
182+
};
183+
184+
static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
185+
{
186+
struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
187+
188+
if ((event & 0xffffff00) == 0x71010100) {
189+
bool charging = (event & 0xff) != 0;
190+
191+
dev_info(power->dev, "Charging: %d\n", charging);
192+
power_supply_changed(power->psy);
193+
194+
return NOTIFY_OK;
195+
}
196+
197+
return NOTIFY_DONE;
198+
}
199+
200+
static int macsmc_power_probe(struct platform_device *pdev)
201+
{
202+
struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
203+
struct power_supply_config psy_cfg = {};
204+
struct macsmc_power *power;
205+
int ret;
206+
207+
power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
208+
if (!power)
209+
return -ENOMEM;
210+
211+
power->dev = &pdev->dev;
212+
power->smc = smc;
213+
dev_set_drvdata(&pdev->dev, power);
214+
215+
/* Ignore devices without a charger/battery */
216+
if (macsmc_battery_get_status(power) <= POWER_SUPPLY_STATUS_UNKNOWN)
217+
return -ENODEV;
218+
219+
/* Fetch string properties */
220+
apple_smc_read(smc, SMC_KEY(BMDN), power->model_name, sizeof(power->model_name) - 1);
221+
apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1);
222+
223+
psy_cfg.drv_data = power;
224+
power->psy = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
225+
if (IS_ERR(power->psy)) {
226+
dev_err(&pdev->dev, "Failed to register power supply\n");
227+
ret = PTR_ERR(power->psy);
228+
return ret;
229+
}
230+
231+
power->nb.notifier_call = macsmc_power_event;
232+
apple_smc_register_notifier(power->smc, &power->nb);
233+
234+
return 0;
235+
}
236+
237+
static int macsmc_power_remove(struct platform_device *pdev)
238+
{
239+
struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
240+
241+
apple_smc_unregister_notifier(power->smc, &power->nb);
242+
243+
return 0;
244+
}
245+
246+
static struct platform_driver macsmc_power_driver = {
247+
.driver = {
248+
.name = "macsmc-power",
249+
.owner = THIS_MODULE,
250+
},
251+
.probe = macsmc_power_probe,
252+
.remove = macsmc_power_remove,
253+
};
254+
module_platform_driver(macsmc_power_driver);
255+
256+
MODULE_LICENSE("Dual MIT/GPL");
257+
MODULE_DESCRIPTION("Apple SMC battery and power management driver");
258+
MODULE_AUTHOR("Hector Martin <[email protected]>");
259+
MODULE_ALIAS("platform:macsmc-power");

0 commit comments

Comments
 (0)