2 * power_supply_extcon: Power supply detection through extcon.
4 * Copyright (c) 2012-2014, NVIDIA CORPORATION. All rights reserved.
5 * Laxman Dewangan <ldewangan@nvidia.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include <linux/delay.h>
23 #include <linux/err.h>
24 #include <linux/err.h>
25 #include <linux/module.h>
27 #include <linux/of_device.h>
28 #include <linux/platform_device.h>
29 #include <linux/power_supply.h>
30 #include <linux/power/power_supply_extcon.h>
31 #include <linux/slab.h>
32 #include <linux/extcon.h>
33 #include <linux/spinlock.h>
35 #define CHARGER_TYPE_DETECTION_DEFAULT_DEBOUNCE_TIME_MS 500
37 struct power_supply_cables;
39 struct power_supply_extcon {
41 struct power_supply ac;
42 struct power_supply usb;
45 bool default_ac_connected;
46 struct power_supply_extcon_plat_data *pdata;
48 struct power_supply_cables *psy_cables;
52 struct power_supply_cables {
54 const char *dt_cable_name;
55 const char *print_str;
58 struct power_supply_extcon *psy_extcon;
59 struct notifier_block nb;
60 struct extcon_specific_cable_nb ec_cable_nb;
61 struct extcon_cable *ec_cable;
64 static struct power_supply_cables psy_cables[] = {
67 .dt_cable_name = "usb-charger",
68 .print_str = "USB charger",
73 .dt_cable_name = "ta-charger",
74 .print_str = "USB TA",
79 .dt_cable_name = "qc2-charger",
80 .print_str = "USB QC2-charger",
85 .dt_cable_name = "maxim-charger",
86 .print_str = "USB Maxim-charger",
90 .name = "Fast-charger",
91 .dt_cable_name = "fast-charger",
92 .print_str = "USB Fast-charger",
96 .name = "Slow-charger",
97 .dt_cable_name = "slow-charger",
98 .print_str = "USB Slow-charger",
102 .name = "Charge-downstream",
103 .dt_cable_name = "downstream-charger",
104 .print_str = "USB charger downstream",
108 .name = "Apple 500mA-charger",
109 .dt_cable_name = "apple-500ma",
110 .print_str = "USB Apple 500mA-charger",
114 .name = "Apple 1A-charger",
115 .dt_cable_name = "apple-1a",
116 .print_str = "USB Apple 1A charger",
120 .name = "Apple 2A-charger",
121 .dt_cable_name = "apple-2a",
122 .print_str = "USB Apple 2A charger",
126 .name = "ACA NV-Charger",
127 .dt_cable_name = "ACA NV-Charger",
128 .print_str = "USB ACA NV-Charger",
133 .dt_cable_name = "ACA RID-B",
134 .print_str = "USB ACA RID-B Charger",
139 .dt_cable_name = "ACA RID-C",
140 .print_str = "USB ACA RID-C Charger",
145 .dt_cable_name = "y-cable",
146 .print_str = "Y cable",
151 .dt_cable_name = "ACA RID-A",
152 .print_str = "ACA RID-A cable",
157 static enum power_supply_property power_supply_extcon_props[] = {
158 POWER_SUPPLY_PROP_ONLINE,
159 POWER_SUPPLY_PROP_CHARGER_TYPE,
162 static bool psy_get_cable_state(struct power_supply_extcon *psy_extcon,
163 const char *cable_name)
169 for (i = 0; i < psy_extcon->max_psy_cables; ++i) {
170 if (!strncmp(psy_extcon->psy_cables[i].name,
171 cable_name, CABLE_NAME_MAX)) {
180 if (!psy_extcon->psy_cables[i].ec_cable)
183 ret = extcon_get_cable_state_(psy_extcon->psy_cables[i].ec_cable->edev,
184 psy_extcon->psy_cables[i].ec_cable->cable_index);
190 static int power_supply_extcon_get_property(struct power_supply *psy,
191 enum power_supply_property psp, union power_supply_propval *val)
197 struct power_supply_extcon *psy_extcon;
198 struct power_supply_cables *psy_cable;
200 if (psy->type == POWER_SUPPLY_TYPE_MAINS) {
201 psy_extcon = container_of(psy, struct power_supply_extcon, ac);
202 online = psy_extcon->ac_online;
203 if(!online && psy_extcon->default_ac_connected)
205 } else if (psy->type == POWER_SUPPLY_TYPE_USB) {
206 psy_extcon = container_of(psy, struct power_supply_extcon, usb);
207 online = psy_extcon->usb_online;
213 case POWER_SUPPLY_PROP_ONLINE:
214 val->intval = online;
216 case POWER_SUPPLY_PROP_CHARGER_TYPE:
217 val->strval = "no cable";
218 for (i = 0; i < psy_extcon->max_psy_cables; ++i) {
219 psy_cable = &psy_extcon->psy_cables[i];
220 if (!psy_cable->ec_cable)
223 state = psy_get_cable_state(psy_extcon, psy_cable->name);
225 val->strval = psy_cable->name;
236 static int power_supply_extcon_attach_cable(
237 struct power_supply_extcon *psy_extcon)
239 struct power_supply_cables *psy_cable;
243 psy_extcon->usb_online = 0;
244 psy_extcon->ac_online = 0;
246 for (i = 0; i < psy_extcon->max_psy_cables; ++i) {
247 psy_cable = &psy_extcon->psy_cables[i];
248 if (!psy_cable->ec_cable)
251 state = psy_get_cable_state(psy_extcon, psy_cable->name);
253 dev_info(psy_extcon->dev, "%s cable detected\n",
254 psy_cable->print_str);
255 psy_extcon->ac_online = psy_cable->ac_online;
256 psy_extcon->usb_online = psy_cable->usb_online;
262 dev_info(psy_extcon->dev, "No cable detected\n");
264 power_supply_changed(&psy_extcon->usb);
265 power_supply_changed(&psy_extcon->ac);
269 static int psy_extcon_extcon_notifier(struct notifier_block *self,
270 unsigned long event, void *ptr)
272 struct power_supply_cables *cable = container_of(self,
273 struct power_supply_cables, nb);
274 struct power_supply_extcon *psy_extcon = cable->psy_extcon;
276 spin_lock(&psy_extcon->lock);
278 dev_info(psy_extcon->dev, "Charging cable removed\n");
279 psy_extcon->ac_online = 0;
280 psy_extcon->usb_online = 0;
281 } else if (event == 1) {
282 dev_info(psy_extcon->dev, "%s cable detected\n",
284 psy_extcon->ac_online = cable->ac_online;
285 psy_extcon->usb_online = cable->usb_online;
288 power_supply_changed(&psy_extcon->usb);
289 power_supply_changed(&psy_extcon->ac);
290 spin_unlock(&psy_extcon->lock);
295 static struct power_supply_extcon_plat_data *psy_extcon_get_dt_pdata(
296 struct platform_device *pdev)
298 struct device_node *np = pdev->dev.of_node;
299 struct power_supply_extcon_plat_data *pdata;
303 pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
305 return ERR_PTR(-ENOMEM);
307 ret = of_property_read_string(np, "power-supply,extcon-dev", &pstr);
309 pdata->extcon_name = pstr;
311 ret = of_property_read_string(np, "power-supply,y-cable-extcon-dev",
314 pdata->y_cable_extcon_name = pstr;
316 pdata->default_ac_connected = false;
317 pdata->default_ac_connected = of_property_read_bool(np,
318 "power-supply,default-ac-cable-connected");
323 static int psy_extcon_probe(struct platform_device *pdev)
327 struct power_supply_extcon *psy_extcon;
328 struct power_supply_extcon_plat_data *pdata = pdev->dev.platform_data;
330 if (!pdata && pdev->dev.of_node) {
331 pdata = psy_extcon_get_dt_pdata(pdev);
333 ret = PTR_ERR(pdata);
339 dev_err(&pdev->dev, "No platform data, exiting..\n");
343 psy_extcon = devm_kzalloc(&pdev->dev, sizeof(*psy_extcon), GFP_KERNEL);
345 dev_err(&pdev->dev, "failed to allocate memory status\n");
349 psy_extcon->dev = &pdev->dev;
350 dev_set_drvdata(&pdev->dev, psy_extcon);
351 spin_lock_init(&psy_extcon->lock);
353 dev_info(psy_extcon->dev, "Extcon name %s\n", pdata->extcon_name);
355 psy_extcon->ac.name = "ac";
356 psy_extcon->ac.type = POWER_SUPPLY_TYPE_MAINS;
357 psy_extcon->ac.get_property = power_supply_extcon_get_property;
358 psy_extcon->ac.properties = power_supply_extcon_props;
359 psy_extcon->ac.num_properties = ARRAY_SIZE(power_supply_extcon_props);
360 ret = power_supply_register(psy_extcon->dev, &psy_extcon->ac);
362 dev_err(psy_extcon->dev, "failed: power supply register\n");
366 psy_extcon->usb = psy_extcon->ac;
367 psy_extcon->usb.name = "usb";
368 psy_extcon->usb.type = POWER_SUPPLY_TYPE_USB;
369 ret = power_supply_register(psy_extcon->dev, &psy_extcon->usb);
371 dev_err(psy_extcon->dev, "failed: power supply register\n");
374 psy_extcon->default_ac_connected = pdata->default_ac_connected;
376 psy_extcon->psy_cables = psy_cables;
377 psy_extcon->max_psy_cables = ARRAY_SIZE(psy_cables);
378 for (j = 0; j < psy_extcon->max_psy_cables; j++) {
379 struct power_supply_cables *psy_cable = &psy_cables[j];
380 const char *ext_name;
382 psy_cable->psy_extcon = psy_extcon;
383 psy_cable->nb.notifier_call = psy_extcon_extcon_notifier;
385 psy_cable->ec_cable = extcon_get_extcon_cable(psy_extcon->dev,
386 psy_cable->dt_cable_name);
387 if (!IS_ERR(psy_cable->ec_cable))
390 psy_cable->ec_cable = NULL;
391 ext_name = pdata->extcon_name;
392 if (!strcmp(psy_cable->name, "Y-cable") ||
393 !strcmp(psy_cable->name, "ACA RID-A"))
394 ext_name = pdata->y_cable_extcon_name;
396 dev_info(psy_extcon->dev, "No extname for cable %s\n",
401 psy_cable->ec_cable = extcon_get_extcon_cable_by_extcon_name(
402 ext_name, psy_cable->name);
403 if (IS_ERR(psy_cable->ec_cable)) {
404 dev_err(psy_extcon->dev,
405 "Cable %s not found on ext_name %s\n",
406 psy_cable->name, ext_name);
407 psy_cable->ec_cable = NULL;
412 ret = extcon_register_cable_interest(&psy_cable->ec_cable_nb,
413 psy_cable->ec_cable, &psy_cable->nb);
415 extcon_put_extcon_cable(psy_cable->ec_cable);
416 psy_cable->ec_cable = NULL;
417 dev_err(psy_extcon->dev,
418 "Cable %s registration failed: %d\n",
419 psy_cable->name, ret);
423 spin_lock(&psy_extcon->lock);
424 power_supply_extcon_attach_cable(psy_extcon);
425 spin_unlock(&psy_extcon->lock);
427 dev_info(&pdev->dev, "%s() get success\n", __func__);
430 power_supply_unregister(&psy_extcon->usb);
432 power_supply_unregister(&psy_extcon->ac);
436 static int psy_extcon_remove(struct platform_device *pdev)
438 struct power_supply_extcon *psy_extcon = platform_get_drvdata(pdev);
440 power_supply_unregister(&psy_extcon->ac);
441 power_supply_unregister(&psy_extcon->usb);
445 static struct of_device_id power_supply_extcon_of_match[] = {
446 { .compatible = "power-supply-extcon", },
449 MODULE_DEVICE_TABLE(of, power_supply_extcon_of_match);
451 static struct platform_driver power_supply_extcon_driver = {
453 .name = "power-supply-extcon",
454 .owner = THIS_MODULE,
455 .of_match_table = power_supply_extcon_of_match,
457 .probe = psy_extcon_probe,
458 .remove = psy_extcon_remove,
461 static int __init psy_extcon_init(void)
463 return platform_driver_register(&power_supply_extcon_driver);
466 static void __exit psy_extcon_exit(void)
468 platform_driver_unregister(&power_supply_extcon_driver);
471 late_initcall(psy_extcon_init);
472 module_exit(psy_extcon_exit);
474 MODULE_DESCRIPTION("Power supply detection through extcon driver");
475 MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
476 MODULE_LICENSE("GPL v2");