File Index
Files and directories
Sign in
FILE

cros_charge-control.c

File preview with syntax highlighting.

linux-6.19.14/drivers/power/supply/cros_charge-control.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 *  ChromeOS EC driver for charge control
  4 *
  5 *  Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
  6 */
  7#include <acpi/battery.h>
  8#include <linux/container_of.h>
  9#include <linux/dmi.h>
 10#include <linux/lockdep.h>
 11#include <linux/mod_devicetable.h>
 12#include <linux/module.h>
 13#include <linux/mutex.h>
 14#include <linux/platform_data/cros_ec_commands.h>
 15#include <linux/platform_data/cros_ec_proto.h>
 16#include <linux/platform_device.h>
 17#include <linux/types.h>
 18
 19#define EC_CHARGE_CONTROL_BEHAVIOURS	(BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO)             | \
 20					 BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE)   | \
 21					 BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE))
 22
 23/*
 24 * Semantics of data *returned* from the EC API and Linux sysfs differ
 25 * slightly, also the v1 API can not return any data.
 26 * To match the expected sysfs API, data is never read back from the EC but
 27 * cached in the driver.
 28 *
 29 * Changes to the EC bypassing the driver will not be reflected in sysfs.
 30 * Any change to "charge_behaviour" will synchronize the EC with the driver state.
 31 */
 32
 33struct cros_chctl_priv {
 34	struct device *dev;
 35	struct cros_ec_device *cros_ec;
 36	struct acpi_battery_hook battery_hook;
 37	struct power_supply *hooked_battery;
 38	u8 cmd_version;
 39
 40	const struct power_supply_ext *psy_ext;
 41
 42	struct mutex lock; /* protects fields below and cros_ec */
 43	enum power_supply_charge_behaviour current_behaviour;
 44	u8 current_start_threshold, current_end_threshold;
 45};
 46
 47static int cros_chctl_send_charge_control_cmd(struct cros_ec_device *cros_ec,
 48					      u8 cmd_version, struct ec_params_charge_control *req)
 49{
 50	int ret;
 51	static const u8 outsizes[] = {
 52		[1] = offsetof(struct ec_params_charge_control, cmd),
 53		[2] = sizeof(struct ec_params_charge_control),
 54		[3] = sizeof(struct ec_params_charge_control),
 55	};
 56
 57	ret = cros_ec_cmd(cros_ec, cmd_version, EC_CMD_CHARGE_CONTROL, req,
 58			  outsizes[cmd_version], NULL, 0);
 59
 60	if (ret < 0)
 61		return ret;
 62
 63	return 0;
 64}
 65
 66static int cros_chctl_configure_ec(struct cros_chctl_priv *priv)
 67{
 68	struct ec_params_charge_control req = {};
 69
 70	lockdep_assert_held(&priv->lock);
 71
 72	req.cmd = EC_CHARGE_CONTROL_CMD_SET;
 73
 74	switch (priv->current_behaviour) {
 75	case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
 76		req.mode = CHARGE_CONTROL_NORMAL;
 77		break;
 78	case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
 79		req.mode = CHARGE_CONTROL_IDLE;
 80		break;
 81	case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
 82		req.mode = CHARGE_CONTROL_DISCHARGE;
 83		break;
 84	default:
 85		return -EINVAL;
 86	}
 87
 88	if (priv->current_behaviour == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO &&
 89	    !(priv->current_start_threshold == 0 && priv->current_end_threshold == 100)) {
 90		req.sustain_soc.lower = priv->current_start_threshold;
 91		req.sustain_soc.upper = priv->current_end_threshold;
 92	} else {
 93		/* Disable charging limits */
 94		req.sustain_soc.lower = -1;
 95		req.sustain_soc.upper = -1;
 96	}
 97
 98	return cros_chctl_send_charge_control_cmd(priv->cros_ec, priv->cmd_version, &req);
 99}
100
101static int cros_chctl_psy_ext_get_prop(struct power_supply *psy,
102				       const struct power_supply_ext *ext,
103				       void *data,
104				       enum power_supply_property psp,
105				       union power_supply_propval *val)
106{
107	struct cros_chctl_priv *priv = data;
108
109	switch (psp) {
110	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
111		val->intval = priv->current_start_threshold;
112		return 0;
113	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
114		val->intval = priv->current_end_threshold;
115		return 0;
116	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
117		val->intval = priv->current_behaviour;
118		return 0;
119	default:
120		return -EINVAL;
121	}
122}
123
124static int cros_chctl_psy_ext_set_threshold(struct cros_chctl_priv *priv,
125					    enum power_supply_property psp,
126					    int val)
127{
128	int ret;
129
130	if (val < 0 || val > 100)
131		return -EINVAL;
132
133	if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) {
134		/* Start threshold is not exposed, use fixed value */
135		if (priv->cmd_version == 2)
136			priv->current_start_threshold = val == 100 ? 0 : val;
137
138		if (val < priv->current_start_threshold)
139			return -EINVAL;
140		priv->current_end_threshold = val;
141	} else {
142		if (val > priv->current_end_threshold)
143			return -EINVAL;
144		priv->current_start_threshold = val;
145	}
146
147	if (priv->current_behaviour == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) {
148		ret = cros_chctl_configure_ec(priv);
149		if (ret < 0)
150			return ret;
151	}
152
153	return 0;
154}
155
156
157static int cros_chctl_psy_ext_set_prop(struct power_supply *psy,
158				       const struct power_supply_ext *ext,
159				       void *data,
160				       enum power_supply_property psp,
161				       const union power_supply_propval *val)
162{
163	struct cros_chctl_priv *priv = data;
164	int ret;
165
166	guard(mutex)(&priv->lock);
167
168	switch (psp) {
169	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
170	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
171		return cros_chctl_psy_ext_set_threshold(priv, psp, val->intval);
172	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
173		priv->current_behaviour = val->intval;
174		ret = cros_chctl_configure_ec(priv);
175		if (ret < 0)
176			return ret;
177		return 0;
178	default:
179		return -EINVAL;
180	}
181}
182
183static int cros_chctl_psy_prop_is_writeable(struct power_supply *psy,
184					    const struct power_supply_ext *ext,
185					    void *data,
186					    enum power_supply_property psp)
187{
188	return true;
189}
190
191#define DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(_name, ...)			\
192	static const enum power_supply_property _name ## _props[] = {		\
193		__VA_ARGS__,							\
194	};									\
195										\
196	static const struct power_supply_ext _name = {				\
197		.name			= "cros-charge-control",		\
198		.properties		= _name ## _props,			\
199		.num_properties		= ARRAY_SIZE(_name ## _props),		\
200		.charge_behaviours	= EC_CHARGE_CONTROL_BEHAVIOURS,		\
201		.get_property		= cros_chctl_psy_ext_get_prop,		\
202		.set_property		= cros_chctl_psy_ext_set_prop,		\
203		.property_is_writeable	= cros_chctl_psy_prop_is_writeable,	\
204	}
205
206DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v1,
207	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR
208);
209
210DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v2,
211	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
212	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD
213);
214
215DEFINE_CROS_CHCTL_POWER_SUPPLY_EXTENSION(cros_chctl_psy_ext_v3,
216	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
217	POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
218	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD
219);
220
221static int cros_chctl_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
222{
223	struct cros_chctl_priv *priv = container_of(hook, struct cros_chctl_priv, battery_hook);
224
225	if (priv->hooked_battery)
226		return 0;
227
228	priv->hooked_battery = battery;
229	return power_supply_register_extension(battery, priv->psy_ext, priv->dev, priv);
230}
231
232static int cros_chctl_remove_battery(struct power_supply *battery, struct acpi_battery_hook *hook)
233{
234	struct cros_chctl_priv *priv = container_of(hook, struct cros_chctl_priv, battery_hook);
235
236	if (priv->hooked_battery == battery) {
237		power_supply_unregister_extension(battery, priv->psy_ext);
238		priv->hooked_battery = NULL;
239	}
240
241	return 0;
242}
243
244static bool probe_with_fwk_charge_control;
245module_param(probe_with_fwk_charge_control, bool, 0644);
246MODULE_PARM_DESC(probe_with_fwk_charge_control,
247		 "Probe the driver in the presence of the custom Framework EC charge control");
248
249static int cros_chctl_fwk_charge_control_versions(struct cros_ec_device *cros_ec)
250{
251	if (!dmi_match(DMI_SYS_VENDOR, "Framework"))
252		return 0;
253
254	return cros_ec_get_cmd_versions(cros_ec, 0x3E03 /* FW_EC_CMD_CHARGE_LIMIT */);
255}
256
257static int cros_chctl_probe(struct platform_device *pdev)
258{
259	struct device *dev = &pdev->dev;
260	struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
261	struct cros_ec_device *cros_ec = ec_dev->ec_dev;
262	struct cros_chctl_priv *priv;
263	int ret;
264
265	ret = cros_chctl_fwk_charge_control_versions(cros_ec);
266	if (ret < 0)
267		return ret;
268	if (ret > 0 && !probe_with_fwk_charge_control) {
269		dev_info(dev, "Framework charge control detected, preventing load\n");
270		return -ENODEV;
271	}
272
273	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
274	if (!priv)
275		return -ENOMEM;
276
277	ret = devm_mutex_init(dev, &priv->lock);
278	if (ret)
279		return ret;
280
281	ret = cros_ec_get_cmd_versions(cros_ec, EC_CMD_CHARGE_CONTROL);
282	if (ret < 0)
283		return ret;
284	else if (ret & EC_VER_MASK(3))
285		priv->cmd_version = 3;
286	else if (ret & EC_VER_MASK(2))
287		priv->cmd_version = 2;
288	else if (ret & EC_VER_MASK(1))
289		priv->cmd_version = 1;
290	else
291		return -ENODEV;
292
293	dev_dbg(dev, "Command version: %u\n", (unsigned int)priv->cmd_version);
294
295	priv->dev = dev;
296	priv->cros_ec = cros_ec;
297
298	if (priv->cmd_version == 1)
299		priv->psy_ext = &cros_chctl_psy_ext_v1;
300	else if (priv->cmd_version == 2)
301		priv->psy_ext = &cros_chctl_psy_ext_v2;
302	else
303		priv->psy_ext = &cros_chctl_psy_ext_v3;
304
305	priv->battery_hook.name = dev_name(dev);
306	priv->battery_hook.add_battery = cros_chctl_add_battery;
307	priv->battery_hook.remove_battery = cros_chctl_remove_battery;
308
309	priv->current_behaviour = POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
310	priv->current_start_threshold = 0;
311	priv->current_end_threshold = 100;
312
313	/* Bring EC into well-known state */
314	scoped_guard(mutex, &priv->lock)
315		ret = cros_chctl_configure_ec(priv);
316	if (ret < 0)
317		return ret;
318
319	return devm_battery_hook_register(dev, &priv->battery_hook);
320}
321
322static const struct platform_device_id cros_chctl_id[] = {
323	{ "cros-charge-control", 0 },
324	{}
325};
326
327static struct platform_driver cros_chctl_driver = {
328	.driver.name	= "cros-charge-control",
329	.probe		= cros_chctl_probe,
330	.id_table	= cros_chctl_id,
331};
332module_platform_driver(cros_chctl_driver);
333
334MODULE_DEVICE_TABLE(platform, cros_chctl_id);
335MODULE_DESCRIPTION("ChromeOS EC charge control");
336MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net>");
337MODULE_LICENSE("GPL");
~/ Read-only file preview.