1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Power supply driver for ChromeOS EC based Peripheral Device Charger. |
4 | * |
5 | * Copyright 2020 Google LLC. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/notifier.h> |
10 | #include <linux/platform_data/cros_ec_commands.h> |
11 | #include <linux/platform_data/cros_ec_proto.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/power_supply.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/stringify.h> |
16 | #include <linux/types.h> |
17 | #include <asm/unaligned.h> |
18 | |
19 | #define DRV_NAME "cros-ec-pchg" |
20 | #define PCHG_DIR_PREFIX "peripheral" |
21 | #define PCHG_DIR_NAME PCHG_DIR_PREFIX "%d" |
22 | #define PCHG_DIR_NAME_LENGTH \ |
23 | sizeof(PCHG_DIR_PREFIX __stringify(EC_PCHG_MAX_PORTS)) |
24 | #define PCHG_CACHE_UPDATE_DELAY msecs_to_jiffies(500) |
25 | |
26 | struct port_data { |
27 | int port_number; |
28 | char name[PCHG_DIR_NAME_LENGTH]; |
29 | struct power_supply *psy; |
30 | struct power_supply_desc psy_desc; |
31 | int psy_status; |
32 | int battery_percentage; |
33 | int charge_type; |
34 | struct charger_data *charger; |
35 | unsigned long last_update; |
36 | }; |
37 | |
38 | struct charger_data { |
39 | struct device *dev; |
40 | struct cros_ec_dev *ec_dev; |
41 | struct cros_ec_device *ec_device; |
42 | int num_registered_psy; |
43 | struct port_data *ports[EC_PCHG_MAX_PORTS]; |
44 | struct notifier_block notifier; |
45 | }; |
46 | |
47 | static enum power_supply_property cros_pchg_props[] = { |
48 | POWER_SUPPLY_PROP_STATUS, |
49 | POWER_SUPPLY_PROP_CHARGE_TYPE, |
50 | POWER_SUPPLY_PROP_CAPACITY, |
51 | POWER_SUPPLY_PROP_SCOPE, |
52 | }; |
53 | |
54 | static int cros_pchg_ec_command(const struct charger_data *charger, |
55 | unsigned int version, |
56 | unsigned int command, |
57 | const void *outdata, |
58 | unsigned int outsize, |
59 | void *indata, |
60 | unsigned int insize) |
61 | { |
62 | struct cros_ec_dev *ec_dev = charger->ec_dev; |
63 | struct cros_ec_command *msg; |
64 | int ret; |
65 | |
66 | msg = kzalloc(struct_size(msg, data, max(outsize, insize)), GFP_KERNEL); |
67 | if (!msg) |
68 | return -ENOMEM; |
69 | |
70 | msg->version = version; |
71 | msg->command = ec_dev->cmd_offset + command; |
72 | msg->outsize = outsize; |
73 | msg->insize = insize; |
74 | |
75 | if (outsize) |
76 | memcpy(msg->data, outdata, outsize); |
77 | |
78 | ret = cros_ec_cmd_xfer_status(ec_dev: charger->ec_device, msg); |
79 | if (ret >= 0 && insize) |
80 | memcpy(indata, msg->data, insize); |
81 | |
82 | kfree(objp: msg); |
83 | return ret; |
84 | } |
85 | |
86 | static const unsigned int pchg_cmd_version = 1; |
87 | |
88 | static bool cros_pchg_cmd_ver_check(const struct charger_data *charger) |
89 | { |
90 | struct ec_params_get_cmd_versions_v1 req; |
91 | struct ec_response_get_cmd_versions rsp; |
92 | int ret; |
93 | |
94 | req.cmd = EC_CMD_PCHG; |
95 | ret = cros_pchg_ec_command(charger, version: 1, EC_CMD_GET_CMD_VERSIONS, |
96 | outdata: &req, outsize: sizeof(req), indata: &rsp, insize: sizeof(rsp)); |
97 | if (ret < 0) { |
98 | dev_warn(charger->dev, |
99 | "Unable to get versions of EC_CMD_PCHG (err:%d)\n" , |
100 | ret); |
101 | return false; |
102 | } |
103 | |
104 | return !!(rsp.version_mask & BIT(pchg_cmd_version)); |
105 | } |
106 | |
107 | static int cros_pchg_port_count(const struct charger_data *charger) |
108 | { |
109 | struct ec_response_pchg_count rsp; |
110 | int ret; |
111 | |
112 | ret = cros_pchg_ec_command(charger, version: 0, EC_CMD_PCHG_COUNT, |
113 | NULL, outsize: 0, indata: &rsp, insize: sizeof(rsp)); |
114 | if (ret < 0) { |
115 | dev_warn(charger->dev, |
116 | "Unable to get number or ports (err:%d)\n" , ret); |
117 | return ret; |
118 | } |
119 | |
120 | return rsp.port_count; |
121 | } |
122 | |
123 | static int cros_pchg_get_status(struct port_data *port) |
124 | { |
125 | struct charger_data *charger = port->charger; |
126 | struct ec_params_pchg req; |
127 | struct ec_response_pchg rsp; |
128 | struct device *dev = charger->dev; |
129 | int old_status = port->psy_status; |
130 | int old_percentage = port->battery_percentage; |
131 | int ret; |
132 | |
133 | req.port = port->port_number; |
134 | ret = cros_pchg_ec_command(charger, version: pchg_cmd_version, EC_CMD_PCHG, |
135 | outdata: &req, outsize: sizeof(req), indata: &rsp, insize: sizeof(rsp)); |
136 | if (ret < 0) { |
137 | dev_err(dev, "Unable to get port.%d status (err:%d)\n" , |
138 | port->port_number, ret); |
139 | return ret; |
140 | } |
141 | |
142 | switch (rsp.state) { |
143 | case PCHG_STATE_RESET: |
144 | case PCHG_STATE_INITIALIZED: |
145 | case PCHG_STATE_ENABLED: |
146 | default: |
147 | port->psy_status = POWER_SUPPLY_STATUS_UNKNOWN; |
148 | port->charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE; |
149 | break; |
150 | case PCHG_STATE_DETECTED: |
151 | port->psy_status = POWER_SUPPLY_STATUS_CHARGING; |
152 | port->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
153 | break; |
154 | case PCHG_STATE_CHARGING: |
155 | port->psy_status = POWER_SUPPLY_STATUS_CHARGING; |
156 | port->charge_type = POWER_SUPPLY_CHARGE_TYPE_STANDARD; |
157 | break; |
158 | case PCHG_STATE_FULL: |
159 | port->psy_status = POWER_SUPPLY_STATUS_FULL; |
160 | port->charge_type = POWER_SUPPLY_CHARGE_TYPE_NONE; |
161 | break; |
162 | } |
163 | |
164 | port->battery_percentage = rsp.battery_percentage; |
165 | |
166 | if (port->psy_status != old_status || |
167 | port->battery_percentage != old_percentage) |
168 | power_supply_changed(psy: port->psy); |
169 | |
170 | dev_dbg(dev, |
171 | "Port %d: state=%d battery=%d%%\n" , |
172 | port->port_number, rsp.state, rsp.battery_percentage); |
173 | |
174 | return 0; |
175 | } |
176 | |
177 | static int cros_pchg_get_port_status(struct port_data *port, bool ratelimit) |
178 | { |
179 | int ret; |
180 | |
181 | if (ratelimit && |
182 | time_is_after_jiffies(port->last_update + PCHG_CACHE_UPDATE_DELAY)) |
183 | return 0; |
184 | |
185 | ret = cros_pchg_get_status(port); |
186 | if (ret < 0) |
187 | return ret; |
188 | |
189 | port->last_update = jiffies; |
190 | |
191 | return ret; |
192 | } |
193 | |
194 | static int cros_pchg_get_prop(struct power_supply *psy, |
195 | enum power_supply_property psp, |
196 | union power_supply_propval *val) |
197 | { |
198 | struct port_data *port = power_supply_get_drvdata(psy); |
199 | |
200 | switch (psp) { |
201 | case POWER_SUPPLY_PROP_STATUS: |
202 | case POWER_SUPPLY_PROP_CAPACITY: |
203 | case POWER_SUPPLY_PROP_CHARGE_TYPE: |
204 | cros_pchg_get_port_status(port, ratelimit: true); |
205 | break; |
206 | default: |
207 | break; |
208 | } |
209 | |
210 | switch (psp) { |
211 | case POWER_SUPPLY_PROP_STATUS: |
212 | val->intval = port->psy_status; |
213 | break; |
214 | case POWER_SUPPLY_PROP_CAPACITY: |
215 | val->intval = port->battery_percentage; |
216 | break; |
217 | case POWER_SUPPLY_PROP_CHARGE_TYPE: |
218 | val->intval = port->charge_type; |
219 | break; |
220 | case POWER_SUPPLY_PROP_SCOPE: |
221 | val->intval = POWER_SUPPLY_SCOPE_DEVICE; |
222 | break; |
223 | default: |
224 | return -EINVAL; |
225 | } |
226 | |
227 | return 0; |
228 | } |
229 | |
230 | static int cros_pchg_event(const struct charger_data *charger) |
231 | { |
232 | int i; |
233 | |
234 | for (i = 0; i < charger->num_registered_psy; i++) |
235 | cros_pchg_get_port_status(port: charger->ports[i], ratelimit: false); |
236 | |
237 | return NOTIFY_OK; |
238 | } |
239 | |
240 | static int cros_ec_notify(struct notifier_block *nb, |
241 | unsigned long queued_during_suspend, |
242 | void *data) |
243 | { |
244 | struct cros_ec_device *ec_dev = data; |
245 | struct charger_data *charger = |
246 | container_of(nb, struct charger_data, notifier); |
247 | u32 host_event; |
248 | |
249 | if (ec_dev->event_data.event_type != EC_MKBP_EVENT_PCHG || |
250 | ec_dev->event_size != sizeof(host_event)) |
251 | return NOTIFY_DONE; |
252 | |
253 | host_event = get_unaligned_le32(p: &ec_dev->event_data.data.host_event); |
254 | |
255 | if (!(host_event & EC_MKBP_PCHG_DEVICE_EVENT)) |
256 | return NOTIFY_DONE; |
257 | |
258 | return cros_pchg_event(charger); |
259 | } |
260 | |
261 | static int cros_pchg_probe(struct platform_device *pdev) |
262 | { |
263 | struct device *dev = &pdev->dev; |
264 | struct cros_ec_dev *ec_dev = dev_get_drvdata(dev: dev->parent); |
265 | struct cros_ec_device *ec_device = ec_dev->ec_dev; |
266 | struct power_supply_desc *psy_desc; |
267 | struct charger_data *charger; |
268 | struct power_supply *psy; |
269 | struct port_data *port; |
270 | struct notifier_block *nb; |
271 | int num_ports; |
272 | int ret; |
273 | int i; |
274 | |
275 | charger = devm_kzalloc(dev, size: sizeof(*charger), GFP_KERNEL); |
276 | if (!charger) |
277 | return -ENOMEM; |
278 | |
279 | charger->dev = dev; |
280 | charger->ec_dev = ec_dev; |
281 | charger->ec_device = ec_device; |
282 | |
283 | platform_set_drvdata(pdev, data: charger); |
284 | |
285 | ret = cros_pchg_port_count(charger); |
286 | if (ret <= 0) { |
287 | /* |
288 | * This feature is enabled by the EC and the kernel driver is |
289 | * included by default for CrOS devices. Don't need to be loud |
290 | * since this error can be normal. |
291 | */ |
292 | dev_info(dev, "No peripheral charge ports (err:%d)\n" , ret); |
293 | return -ENODEV; |
294 | } |
295 | |
296 | if (!cros_pchg_cmd_ver_check(charger)) { |
297 | dev_err(dev, "EC_CMD_PCHG version %d isn't available.\n" , |
298 | pchg_cmd_version); |
299 | return -EOPNOTSUPP; |
300 | } |
301 | |
302 | num_ports = ret; |
303 | if (num_ports > EC_PCHG_MAX_PORTS) { |
304 | dev_err(dev, "Too many peripheral charge ports (%d)\n" , |
305 | num_ports); |
306 | return -ENOBUFS; |
307 | } |
308 | |
309 | dev_info(dev, "%d peripheral charge ports found\n" , num_ports); |
310 | |
311 | for (i = 0; i < num_ports; i++) { |
312 | struct power_supply_config psy_cfg = {}; |
313 | |
314 | port = devm_kzalloc(dev, size: sizeof(*port), GFP_KERNEL); |
315 | if (!port) |
316 | return -ENOMEM; |
317 | |
318 | port->charger = charger; |
319 | port->port_number = i; |
320 | snprintf(buf: port->name, size: sizeof(port->name), PCHG_DIR_NAME, i); |
321 | |
322 | psy_desc = &port->psy_desc; |
323 | psy_desc->name = port->name; |
324 | psy_desc->type = POWER_SUPPLY_TYPE_BATTERY; |
325 | psy_desc->get_property = cros_pchg_get_prop; |
326 | psy_desc->external_power_changed = NULL; |
327 | psy_desc->properties = cros_pchg_props; |
328 | psy_desc->num_properties = ARRAY_SIZE(cros_pchg_props); |
329 | psy_cfg.drv_data = port; |
330 | |
331 | psy = devm_power_supply_register(parent: dev, desc: psy_desc, cfg: &psy_cfg); |
332 | if (IS_ERR(ptr: psy)) |
333 | return dev_err_probe(dev, err: PTR_ERR(ptr: psy), |
334 | fmt: "Failed to register power supply\n" ); |
335 | port->psy = psy; |
336 | |
337 | charger->ports[charger->num_registered_psy++] = port; |
338 | } |
339 | |
340 | if (!charger->num_registered_psy) |
341 | return -ENODEV; |
342 | |
343 | nb = &charger->notifier; |
344 | nb->notifier_call = cros_ec_notify; |
345 | ret = blocking_notifier_chain_register(nh: &ec_dev->ec_dev->event_notifier, |
346 | nb); |
347 | if (ret < 0) |
348 | dev_err(dev, "Failed to register notifier (err:%d)\n" , ret); |
349 | |
350 | return 0; |
351 | } |
352 | |
353 | #ifdef CONFIG_PM_SLEEP |
354 | static int __maybe_unused cros_pchg_resume(struct device *dev) |
355 | { |
356 | struct charger_data *charger = dev_get_drvdata(dev); |
357 | |
358 | /* |
359 | * Sync all ports on resume in case reports from EC are lost during |
360 | * the last suspend. |
361 | */ |
362 | cros_pchg_event(charger); |
363 | |
364 | return 0; |
365 | } |
366 | #endif |
367 | |
368 | static SIMPLE_DEV_PM_OPS(cros_pchg_pm_ops, NULL, cros_pchg_resume); |
369 | |
370 | static struct platform_driver cros_pchg_driver = { |
371 | .driver = { |
372 | .name = DRV_NAME, |
373 | .pm = &cros_pchg_pm_ops, |
374 | }, |
375 | .probe = cros_pchg_probe |
376 | }; |
377 | |
378 | module_platform_driver(cros_pchg_driver); |
379 | |
380 | MODULE_LICENSE("GPL" ); |
381 | MODULE_DESCRIPTION("ChromeOS EC peripheral device charger" ); |
382 | MODULE_ALIAS("platform:" DRV_NAME); |
383 | |