Commit | Line | Data |
---|---|---|
c94d4ed0 ML |
1 | /* |
2 | * Copyright (C) 2017, Topic Embedded Products | |
3 | * Driver for LTC3651 charger IC. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation; either version 2 of the License, or (at your | |
8 | * option) any later version. | |
9 | */ | |
10 | ||
11 | #include <linux/device.h> | |
12 | #include <linux/gpio/consumer.h> | |
13 | #include <linux/init.h> | |
14 | #include <linux/interrupt.h> | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/platform_device.h> | |
18 | #include <linux/power_supply.h> | |
19 | #include <linux/slab.h> | |
20 | #include <linux/of.h> | |
21 | ||
22 | struct ltc3651_charger { | |
23 | struct power_supply *charger; | |
24 | struct power_supply_desc charger_desc; | |
25 | struct gpio_desc *acpr_gpio; | |
26 | struct gpio_desc *fault_gpio; | |
27 | struct gpio_desc *chrg_gpio; | |
28 | }; | |
29 | ||
30 | static irqreturn_t ltc3651_charger_irq(int irq, void *devid) | |
31 | { | |
32 | struct power_supply *charger = devid; | |
33 | ||
34 | power_supply_changed(charger); | |
35 | ||
36 | return IRQ_HANDLED; | |
37 | } | |
38 | ||
39 | static inline struct ltc3651_charger *psy_to_ltc3651_charger( | |
40 | struct power_supply *psy) | |
41 | { | |
42 | return power_supply_get_drvdata(psy); | |
43 | } | |
44 | ||
45 | static int ltc3651_charger_get_property(struct power_supply *psy, | |
46 | enum power_supply_property psp, union power_supply_propval *val) | |
47 | { | |
48 | struct ltc3651_charger *ltc3651_charger = psy_to_ltc3651_charger(psy); | |
49 | ||
50 | switch (psp) { | |
51 | case POWER_SUPPLY_PROP_STATUS: | |
52 | if (!ltc3651_charger->chrg_gpio) { | |
53 | val->intval = POWER_SUPPLY_STATUS_UNKNOWN; | |
54 | break; | |
55 | } | |
56 | if (gpiod_get_value(ltc3651_charger->chrg_gpio)) | |
57 | val->intval = POWER_SUPPLY_STATUS_CHARGING; | |
58 | else | |
59 | val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; | |
60 | break; | |
61 | case POWER_SUPPLY_PROP_ONLINE: | |
62 | val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio); | |
63 | break; | |
64 | case POWER_SUPPLY_PROP_HEALTH: | |
65 | if (!ltc3651_charger->fault_gpio) { | |
66 | val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; | |
67 | break; | |
68 | } | |
69 | if (!gpiod_get_value(ltc3651_charger->fault_gpio)) { | |
70 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | |
71 | break; | |
72 | } | |
73 | /* | |
74 | * If the fault pin is active, the chrg pin explains the type | |
75 | * of failure. | |
76 | */ | |
77 | if (!ltc3651_charger->chrg_gpio) { | |
78 | val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; | |
79 | break; | |
80 | } | |
81 | val->intval = gpiod_get_value(ltc3651_charger->chrg_gpio) ? | |
82 | POWER_SUPPLY_HEALTH_OVERHEAT : | |
83 | POWER_SUPPLY_HEALTH_DEAD; | |
84 | break; | |
85 | default: | |
86 | return -EINVAL; | |
87 | } | |
88 | ||
89 | return 0; | |
90 | } | |
91 | ||
92 | static enum power_supply_property ltc3651_charger_properties[] = { | |
93 | POWER_SUPPLY_PROP_STATUS, | |
94 | POWER_SUPPLY_PROP_ONLINE, | |
95 | POWER_SUPPLY_PROP_HEALTH, | |
96 | }; | |
97 | ||
98 | static int ltc3651_charger_probe(struct platform_device *pdev) | |
99 | { | |
100 | struct power_supply_config psy_cfg = {}; | |
101 | struct ltc3651_charger *ltc3651_charger; | |
102 | struct power_supply_desc *charger_desc; | |
103 | int ret; | |
104 | ||
105 | ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger), | |
106 | GFP_KERNEL); | |
107 | if (!ltc3651_charger) | |
108 | return -ENOMEM; | |
109 | ||
110 | ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev, | |
111 | "lltc,acpr", GPIOD_IN); | |
112 | if (IS_ERR(ltc3651_charger->acpr_gpio)) { | |
e1285417 | 113 | ret = PTR_ERR(ltc3651_charger->acpr_gpio); |
c94d4ed0 ML |
114 | dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret); |
115 | return ret; | |
116 | } | |
117 | ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev, | |
118 | "lltc,fault", GPIOD_IN); | |
119 | if (IS_ERR(ltc3651_charger->fault_gpio)) { | |
e1285417 | 120 | ret = PTR_ERR(ltc3651_charger->fault_gpio); |
c94d4ed0 ML |
121 | dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret); |
122 | return ret; | |
123 | } | |
124 | ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev, | |
125 | "lltc,chrg", GPIOD_IN); | |
126 | if (IS_ERR(ltc3651_charger->chrg_gpio)) { | |
e1285417 | 127 | ret = PTR_ERR(ltc3651_charger->chrg_gpio); |
c94d4ed0 ML |
128 | dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret); |
129 | return ret; | |
130 | } | |
131 | ||
132 | charger_desc = <c3651_charger->charger_desc; | |
133 | charger_desc->name = pdev->dev.of_node->name; | |
134 | charger_desc->type = POWER_SUPPLY_TYPE_MAINS; | |
135 | charger_desc->properties = ltc3651_charger_properties; | |
136 | charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties); | |
137 | charger_desc->get_property = ltc3651_charger_get_property; | |
138 | psy_cfg.of_node = pdev->dev.of_node; | |
139 | psy_cfg.drv_data = ltc3651_charger; | |
140 | ||
141 | ltc3651_charger->charger = devm_power_supply_register(&pdev->dev, | |
142 | charger_desc, &psy_cfg); | |
143 | if (IS_ERR(ltc3651_charger->charger)) { | |
144 | ret = PTR_ERR(ltc3651_charger->charger); | |
145 | dev_err(&pdev->dev, "Failed to register power supply: %d\n", | |
146 | ret); | |
147 | return ret; | |
148 | } | |
149 | ||
150 | /* | |
151 | * Acquire IRQs for the GPIO pins if possible. If the system does not | |
152 | * support IRQs on these pins, userspace will have to poll the sysfs | |
153 | * files manually. | |
154 | */ | |
155 | if (ltc3651_charger->acpr_gpio) { | |
156 | ret = gpiod_to_irq(ltc3651_charger->acpr_gpio); | |
157 | if (ret >= 0) | |
158 | ret = devm_request_any_context_irq(&pdev->dev, ret, | |
159 | ltc3651_charger_irq, | |
160 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, | |
161 | dev_name(&pdev->dev), ltc3651_charger->charger); | |
162 | if (ret < 0) | |
163 | dev_warn(&pdev->dev, "Failed to request acpr irq\n"); | |
164 | } | |
165 | if (ltc3651_charger->fault_gpio) { | |
166 | ret = gpiod_to_irq(ltc3651_charger->fault_gpio); | |
167 | if (ret >= 0) | |
168 | ret = devm_request_any_context_irq(&pdev->dev, ret, | |
169 | ltc3651_charger_irq, | |
170 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, | |
171 | dev_name(&pdev->dev), ltc3651_charger->charger); | |
172 | if (ret < 0) | |
173 | dev_warn(&pdev->dev, "Failed to request fault irq\n"); | |
174 | } | |
175 | if (ltc3651_charger->chrg_gpio) { | |
176 | ret = gpiod_to_irq(ltc3651_charger->chrg_gpio); | |
177 | if (ret >= 0) | |
178 | ret = devm_request_any_context_irq(&pdev->dev, ret, | |
179 | ltc3651_charger_irq, | |
180 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, | |
181 | dev_name(&pdev->dev), ltc3651_charger->charger); | |
182 | if (ret < 0) | |
183 | dev_warn(&pdev->dev, "Failed to request chrg irq\n"); | |
184 | } | |
185 | ||
186 | platform_set_drvdata(pdev, ltc3651_charger); | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | static const struct of_device_id ltc3651_charger_match[] = { | |
192 | { .compatible = "lltc,ltc3651-charger" }, | |
193 | { } | |
194 | }; | |
195 | MODULE_DEVICE_TABLE(of, ltc3651_charger_match); | |
196 | ||
197 | static struct platform_driver ltc3651_charger_driver = { | |
198 | .probe = ltc3651_charger_probe, | |
199 | .driver = { | |
200 | .name = "ltc3651-charger", | |
201 | .of_match_table = ltc3651_charger_match, | |
202 | }, | |
203 | }; | |
204 | ||
205 | module_platform_driver(ltc3651_charger_driver); | |
206 | ||
207 | MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>"); | |
208 | MODULE_DESCRIPTION("Driver for LTC3651 charger"); | |
209 | MODULE_LICENSE("GPL"); | |
210 | MODULE_ALIAS("platform:ltc3651-charger"); |