1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * ChromeOS EC driver for charge control
5 * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
7 #include <acpi/battery.h>
8 #include <linux/container_of.h>
10 #include <linux/mod_devicetable.h>
11 #include <linux/module.h>
12 #include <linux/platform_data/cros_ec_commands.h>
13 #include <linux/platform_data/cros_ec_proto.h>
14 #include <linux/platform_device.h>
15 #include <linux/types.h>
17 #define EC_CHARGE_CONTROL_BEHAVIOURS (BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | \
18 BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | \
19 BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
21 enum CROS_CHCTL_ATTR
{
22 CROS_CHCTL_ATTR_START_THRESHOLD
,
23 CROS_CHCTL_ATTR_END_THRESHOLD
,
24 CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR
,
25 _CROS_CHCTL_ATTR_COUNT
29 * Semantics of data *returned* from the EC API and Linux sysfs differ
30 * slightly, also the v1 API can not return any data.
31 * To match the expected sysfs API, data is never read back from the EC but
32 * cached in the driver.
34 * Changes to the EC bypassing the driver will not be reflected in sysfs.
35 * Any change to "charge_behaviour" will synchronize the EC with the driver state.
38 struct cros_chctl_priv
{
39 struct cros_ec_device
*cros_ec
;
40 struct acpi_battery_hook battery_hook
;
41 struct power_supply
*hooked_battery
;
44 /* The callbacks need to access this priv structure.
45 * As neither the struct device nor power_supply are under the drivers
46 * control, embed the attributes within priv to use with container_of().
48 struct device_attribute device_attrs
[_CROS_CHCTL_ATTR_COUNT
];
49 struct attribute
*attributes
[_CROS_CHCTL_ATTR_COUNT
];
50 struct attribute_group group
;
52 enum power_supply_charge_behaviour current_behaviour
;
53 u8 current_start_threshold
, current_end_threshold
;
56 static int cros_chctl_send_charge_control_cmd(struct cros_ec_device
*cros_ec
,
57 u8 cmd_version
, struct ec_params_charge_control
*req
)
59 static const u8 outsizes
[] = {
60 [1] = offsetof(struct ec_params_charge_control
, cmd
),
61 [2] = sizeof(struct ec_params_charge_control
),
62 [3] = sizeof(struct ec_params_charge_control
),
66 struct cros_ec_command msg
;
68 struct ec_params_charge_control req
;
69 struct ec_response_charge_control resp
;
73 .command
= EC_CMD_CHARGE_CONTROL
,
74 .version
= cmd_version
,
76 .outsize
= outsizes
[cmd_version
],
81 return cros_ec_cmd_xfer_status(cros_ec
, &buf
.msg
);
84 static int cros_chctl_configure_ec(struct cros_chctl_priv
*priv
)
86 struct ec_params_charge_control req
= {};
88 req
.cmd
= EC_CHARGE_CONTROL_CMD_SET
;
90 switch (priv
->current_behaviour
) {
91 case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO
:
92 req
.mode
= CHARGE_CONTROL_NORMAL
;
94 case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE
:
95 req
.mode
= CHARGE_CONTROL_IDLE
;
97 case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE
:
98 req
.mode
= CHARGE_CONTROL_DISCHARGE
;
104 if (priv
->current_behaviour
== POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO
&&
105 !(priv
->current_start_threshold
== 0 && priv
->current_end_threshold
== 100)) {
106 req
.sustain_soc
.lower
= priv
->current_start_threshold
;
107 req
.sustain_soc
.upper
= priv
->current_end_threshold
;
109 /* Disable charging limits */
110 req
.sustain_soc
.lower
= -1;
111 req
.sustain_soc
.upper
= -1;
114 return cros_chctl_send_charge_control_cmd(priv
->cros_ec
, priv
->cmd_version
, &req
);
117 static struct cros_chctl_priv
*cros_chctl_attr_to_priv(struct attribute
*attr
,
118 enum CROS_CHCTL_ATTR idx
)
120 struct device_attribute
*dev_attr
= container_of(attr
, struct device_attribute
, attr
);
122 return container_of(dev_attr
, struct cros_chctl_priv
, device_attrs
[idx
]);
125 static ssize_t
cros_chctl_store_threshold(struct device
*dev
, struct cros_chctl_priv
*priv
,
126 int is_end_threshold
, const char *buf
, size_t count
)
130 ret
= kstrtoint(buf
, 10, &val
);
133 if (val
< 0 || val
> 100)
136 if (is_end_threshold
) {
137 if (val
<= priv
->current_start_threshold
)
139 priv
->current_end_threshold
= val
;
141 if (val
>= priv
->current_end_threshold
)
143 priv
->current_start_threshold
= val
;
146 if (priv
->current_behaviour
== POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO
) {
147 ret
= cros_chctl_configure_ec(priv
);
155 static ssize_t
charge_control_start_threshold_show(struct device
*dev
,
156 struct device_attribute
*attr
,
159 struct cros_chctl_priv
*priv
= cros_chctl_attr_to_priv(&attr
->attr
,
160 CROS_CHCTL_ATTR_START_THRESHOLD
);
162 return sysfs_emit(buf
, "%u\n", (unsigned int)priv
->current_start_threshold
);
165 static ssize_t
charge_control_start_threshold_store(struct device
*dev
,
166 struct device_attribute
*attr
,
167 const char *buf
, size_t count
)
169 struct cros_chctl_priv
*priv
= cros_chctl_attr_to_priv(&attr
->attr
,
170 CROS_CHCTL_ATTR_START_THRESHOLD
);
172 return cros_chctl_store_threshold(dev
, priv
, 0, buf
, count
);
175 static ssize_t
charge_control_end_threshold_show(struct device
*dev
, struct device_attribute
*attr
,
178 struct cros_chctl_priv
*priv
= cros_chctl_attr_to_priv(&attr
->attr
,
179 CROS_CHCTL_ATTR_END_THRESHOLD
);
181 return sysfs_emit(buf
, "%u\n", (unsigned int)priv
->current_end_threshold
);
184 static ssize_t
charge_control_end_threshold_store(struct device
*dev
, struct device_attribute
*attr
,
185 const char *buf
, size_t count
)
187 struct cros_chctl_priv
*priv
= cros_chctl_attr_to_priv(&attr
->attr
,
188 CROS_CHCTL_ATTR_END_THRESHOLD
);
190 return cros_chctl_store_threshold(dev
, priv
, 1, buf
, count
);
193 static ssize_t
charge_behaviour_show(struct device
*dev
, struct device_attribute
*attr
, char *buf
)
195 struct cros_chctl_priv
*priv
= cros_chctl_attr_to_priv(&attr
->attr
,
196 CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR
);
198 return power_supply_charge_behaviour_show(dev
, EC_CHARGE_CONTROL_BEHAVIOURS
,
199 priv
->current_behaviour
, buf
);
202 static ssize_t
charge_behaviour_store(struct device
*dev
, struct device_attribute
*attr
,
203 const char *buf
, size_t count
)
205 struct cros_chctl_priv
*priv
= cros_chctl_attr_to_priv(&attr
->attr
,
206 CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR
);
209 ret
= power_supply_charge_behaviour_parse(EC_CHARGE_CONTROL_BEHAVIOURS
, buf
);
213 priv
->current_behaviour
= ret
;
215 ret
= cros_chctl_configure_ec(priv
);
222 static umode_t
cros_chtl_attr_is_visible(struct kobject
*kobj
, struct attribute
*attr
, int n
)
224 struct cros_chctl_priv
*priv
= cros_chctl_attr_to_priv(attr
, n
);
226 if (priv
->cmd_version
< 2) {
227 if (n
== CROS_CHCTL_ATTR_START_THRESHOLD
)
229 if (n
== CROS_CHCTL_ATTR_END_THRESHOLD
)
236 static int cros_chctl_add_battery(struct power_supply
*battery
, struct acpi_battery_hook
*hook
)
238 struct cros_chctl_priv
*priv
= container_of(hook
, struct cros_chctl_priv
, battery_hook
);
240 if (priv
->hooked_battery
)
243 priv
->hooked_battery
= battery
;
244 return device_add_group(&battery
->dev
, &priv
->group
);
247 static int cros_chctl_remove_battery(struct power_supply
*battery
, struct acpi_battery_hook
*hook
)
249 struct cros_chctl_priv
*priv
= container_of(hook
, struct cros_chctl_priv
, battery_hook
);
251 if (priv
->hooked_battery
== battery
) {
252 device_remove_group(&battery
->dev
, &priv
->group
);
253 priv
->hooked_battery
= NULL
;
259 static bool probe_with_fwk_charge_control
;
260 module_param(probe_with_fwk_charge_control
, bool, 0644);
261 MODULE_PARM_DESC(probe_with_fwk_charge_control
,
262 "Probe the driver in the presence of the custom Framework EC charge control");
264 static int cros_chctl_fwk_charge_control_versions(struct cros_ec_device
*cros_ec
)
266 if (!dmi_match(DMI_SYS_VENDOR
, "Framework"))
269 return cros_ec_get_cmd_versions(cros_ec
, 0x3E03 /* FW_EC_CMD_CHARGE_LIMIT */);
272 static int cros_chctl_probe(struct platform_device
*pdev
)
274 struct device
*dev
= &pdev
->dev
;
275 struct cros_ec_dev
*ec_dev
= dev_get_drvdata(dev
->parent
);
276 struct cros_ec_device
*cros_ec
= ec_dev
->ec_dev
;
277 struct cros_chctl_priv
*priv
;
281 ret
= cros_chctl_fwk_charge_control_versions(cros_ec
);
284 if (ret
> 0 && !probe_with_fwk_charge_control
) {
285 dev_info(dev
, "Framework charge control detected, preventing load\n");
289 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
293 ret
= cros_ec_get_cmd_versions(cros_ec
, EC_CMD_CHARGE_CONTROL
);
296 else if (ret
& EC_VER_MASK(3))
297 priv
->cmd_version
= 3;
298 else if (ret
& EC_VER_MASK(2))
299 priv
->cmd_version
= 2;
300 else if (ret
& EC_VER_MASK(1))
301 priv
->cmd_version
= 1;
305 dev_dbg(dev
, "Command version: %u\n", (unsigned int)priv
->cmd_version
);
307 priv
->cros_ec
= cros_ec
;
308 priv
->device_attrs
[CROS_CHCTL_ATTR_START_THRESHOLD
] =
309 (struct device_attribute
)__ATTR_RW(charge_control_start_threshold
);
310 priv
->device_attrs
[CROS_CHCTL_ATTR_END_THRESHOLD
] =
311 (struct device_attribute
)__ATTR_RW(charge_control_end_threshold
);
312 priv
->device_attrs
[CROS_CHCTL_ATTR_CHARGE_BEHAVIOUR
] =
313 (struct device_attribute
)__ATTR_RW(charge_behaviour
);
314 for (i
= 0; i
< _CROS_CHCTL_ATTR_COUNT
; i
++) {
315 sysfs_attr_init(&priv
->device_attrs
[i
].attr
);
316 priv
->attributes
[i
] = &priv
->device_attrs
[i
].attr
;
318 priv
->group
.is_visible
= cros_chtl_attr_is_visible
;
319 priv
->group
.attrs
= priv
->attributes
;
321 priv
->battery_hook
.name
= dev_name(dev
);
322 priv
->battery_hook
.add_battery
= cros_chctl_add_battery
;
323 priv
->battery_hook
.remove_battery
= cros_chctl_remove_battery
;
325 priv
->current_behaviour
= POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO
;
326 priv
->current_start_threshold
= 0;
327 priv
->current_end_threshold
= 100;
329 /* Bring EC into well-known state */
330 ret
= cros_chctl_configure_ec(priv
);
334 return devm_battery_hook_register(dev
, &priv
->battery_hook
);
337 static const struct platform_device_id cros_chctl_id
[] = {
338 { "cros-charge-control", 0 },
342 static struct platform_driver cros_chctl_driver
= {
343 .driver
.name
= "cros-charge-control",
344 .probe
= cros_chctl_probe
,
345 .id_table
= cros_chctl_id
,
347 module_platform_driver(cros_chctl_driver
);
349 MODULE_DEVICE_TABLE(platform
, cros_chctl_id
);
350 MODULE_DESCRIPTION("ChromeOS EC charge control");
351 MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net>");
352 MODULE_LICENSE("GPL");