aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power
diff options
context:
space:
mode:
authorDoug Anderson <dianders@chromium.org>2014-06-20 17:42:03 -0400
committerSebastian Reichel <sre@kernel.org>2014-07-18 17:40:23 -0400
commit193dcced049ceacc0cce1a512ad8a1e1a26eedad (patch)
tree706d206f021b5c751a7c452dfdfce5bd617a7b8d /drivers/power
parent661468b4e2a91516c7ac75bf466fed58ccd74b27 (diff)
charger: tps65090: Allow charger module to be used when no irq
On the ARM Chromebook tps65090 has two masters: the AP (the main processor running linux) and the EC (the embedded controller). The AP is allowed to mess with FETs but the EC is in charge of charge control. The tps65090 interupt line is routed to both the AP and the EC, which can cause quite a headache. Having two people adjusting masks and acking interrupts is a recipe for disaster. In the shipping kernel we had a hack to have the AP pay attention to the IRQ but not to ack it. It also wasn't supposed to configure the IRQ in any way. That hack allowed us to detect when the device was charging without messing with the EC's state. The current tps65090 infrastructure makes the above difficult, and it was a bit of a hack to begin with. Rather than uglify the driver to support it, just extend the driver's existing notion of "no irq" to the charger. This makes the charger code poll every 2 seconds for AC detect, which is sufficient. For proper functioning, requires (mfd: tps65090: Don't tell child devices we have an IRQ if we don't). If we don't have that patch we'll simply fail to probe on devices without an interrupt (just like we did before this patch). Signed-off-by: Doug Anderson <dianders@chromium.org> Reviewed-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk> Tested-by: Javier Martinez Canillas <javier.martinez@collabora.co.uk> [sre@kernel.org: Use -ENXIO instead of NO_IRQ for missing interrupt, since NO_IRQ is not available on all architectures.] Signed-off-by: Sebastian Reichel <sre@kernel.org>
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/tps65090-charger.c76
1 files changed, 59 insertions, 17 deletions
diff --git a/drivers/power/tps65090-charger.c b/drivers/power/tps65090-charger.c
index 1685f63b9e5d..3e8ba97c8169 100644
--- a/drivers/power/tps65090-charger.c
+++ b/drivers/power/tps65090-charger.c
@@ -17,9 +17,11 @@
17 */ 17 */
18#include <linux/delay.h> 18#include <linux/delay.h>
19#include <linux/err.h> 19#include <linux/err.h>
20#include <linux/freezer.h>
20#include <linux/init.h> 21#include <linux/init.h>
21#include <linux/interrupt.h> 22#include <linux/interrupt.h>
22#include <linux/kernel.h> 23#include <linux/kernel.h>
24#include <linux/kthread.h>
23#include <linux/module.h> 25#include <linux/module.h>
24#include <linux/of_device.h> 26#include <linux/of_device.h>
25#include <linux/platform_device.h> 27#include <linux/platform_device.h>
@@ -32,11 +34,15 @@
32#define TPS65090_VACG BIT(1) 34#define TPS65090_VACG BIT(1)
33#define TPS65090_NOITERM BIT(5) 35#define TPS65090_NOITERM BIT(5)
34 36
37#define POLL_INTERVAL (HZ * 2) /* Used when no irq */
38
35struct tps65090_charger { 39struct tps65090_charger {
36 struct device *dev; 40 struct device *dev;
37 int ac_online; 41 int ac_online;
38 int prev_ac_online; 42 int prev_ac_online;
39 int irq; 43 int irq;
44 struct task_struct *poll_task;
45 bool passive_mode;
40 struct power_supply ac; 46 struct power_supply ac;
41 struct tps65090_platform_data *pdata; 47 struct tps65090_platform_data *pdata;
42}; 48};
@@ -49,6 +55,9 @@ static int tps65090_low_chrg_current(struct tps65090_charger *charger)
49{ 55{
50 int ret; 56 int ret;
51 57
58 if (charger->passive_mode)
59 return 0;
60
52 ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5, 61 ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5,
53 TPS65090_NOITERM); 62 TPS65090_NOITERM);
54 if (ret < 0) { 63 if (ret < 0) {
@@ -64,6 +73,9 @@ static int tps65090_enable_charging(struct tps65090_charger *charger)
64 int ret; 73 int ret;
65 uint8_t ctrl0 = 0; 74 uint8_t ctrl0 = 0;
66 75
76 if (charger->passive_mode)
77 return 0;
78
67 ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0, 79 ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0,
68 &ctrl0); 80 &ctrl0);
69 if (ret < 0) { 81 if (ret < 0) {
@@ -87,6 +99,9 @@ static int tps65090_config_charger(struct tps65090_charger *charger)
87 uint8_t intrmask = 0; 99 uint8_t intrmask = 0;
88 int ret; 100 int ret;
89 101
102 if (charger->passive_mode)
103 return 0;
104
90 if (charger->pdata->enable_low_current_chrg) { 105 if (charger->pdata->enable_low_current_chrg) {
91 ret = tps65090_low_chrg_current(charger); 106 ret = tps65090_low_chrg_current(charger);
92 if (ret < 0) { 107 if (ret < 0) {
@@ -164,10 +179,14 @@ static irqreturn_t tps65090_charger_isr(int irq, void *dev_id)
164 } 179 }
165 180
166 /* Clear interrupts. */ 181 /* Clear interrupts. */
167 ret = tps65090_write(charger->dev->parent, TPS65090_REG_INTR_STS, 0x00); 182 if (!charger->passive_mode) {
168 if (ret < 0) { 183 ret = tps65090_write(charger->dev->parent,
169 dev_err(charger->dev, "%s(): Error in writing reg 0x%x\n", 184 TPS65090_REG_INTR_STS, 0x00);
185 if (ret < 0) {
186 dev_err(charger->dev,
187 "%s(): Error in writing reg 0x%x\n",
170 __func__, TPS65090_REG_INTR_STS); 188 __func__, TPS65090_REG_INTR_STS);
189 }
171 } 190 }
172 191
173 if (charger->prev_ac_online != charger->ac_online) 192 if (charger->prev_ac_online != charger->ac_online)
@@ -198,6 +217,18 @@ static struct tps65090_platform_data *
198 217
199} 218}
200 219
220static int tps65090_charger_poll_task(void *data)
221{
222 set_freezable();
223
224 while (!kthread_should_stop()) {
225 schedule_timeout_interruptible(POLL_INTERVAL);
226 try_to_freeze();
227 tps65090_charger_isr(-1, data);
228 }
229 return 0;
230}
231
201static int tps65090_charger_probe(struct platform_device *pdev) 232static int tps65090_charger_probe(struct platform_device *pdev)
202{ 233{
203 struct tps65090_charger *cdata; 234 struct tps65090_charger *cdata;
@@ -244,22 +275,10 @@ static int tps65090_charger_probe(struct platform_device *pdev)
244 } 275 }
245 276
246 irq = platform_get_irq(pdev, 0); 277 irq = platform_get_irq(pdev, 0);
247 if (irq <= 0) { 278 if (irq < 0)
248 dev_warn(&pdev->dev, "Unable to get charger irq = %d\n", irq); 279 irq = -ENXIO;
249 ret = irq;
250 goto fail_unregister_supply;
251 }
252
253 cdata->irq = irq; 280 cdata->irq = irq;
254 281
255 ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
256 tps65090_charger_isr, 0, "tps65090-charger", cdata);
257 if (ret) {
258 dev_err(cdata->dev, "Unable to register irq %d err %d\n", irq,
259 ret);
260 goto fail_unregister_supply;
261 }
262
263 ret = tps65090_config_charger(cdata); 282 ret = tps65090_config_charger(cdata);
264 if (ret < 0) { 283 if (ret < 0) {
265 dev_err(&pdev->dev, "charger config failed, err %d\n", ret); 284 dev_err(&pdev->dev, "charger config failed, err %d\n", ret);
@@ -285,6 +304,27 @@ static int tps65090_charger_probe(struct platform_device *pdev)
285 power_supply_changed(&cdata->ac); 304 power_supply_changed(&cdata->ac);
286 } 305 }
287 306
307 if (irq != -ENXIO) {
308 ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
309 tps65090_charger_isr, 0, "tps65090-charger", cdata);
310 if (ret) {
311 dev_err(cdata->dev,
312 "Unable to register irq %d err %d\n", irq,
313 ret);
314 goto fail_unregister_supply;
315 }
316 } else {
317 cdata->poll_task = kthread_run(tps65090_charger_poll_task,
318 cdata, "ktps65090charger");
319 cdata->passive_mode = true;
320 if (IS_ERR(cdata->poll_task)) {
321 ret = PTR_ERR(cdata->poll_task);
322 dev_err(cdata->dev,
323 "Unable to run kthread err %d\n", ret);
324 goto fail_unregister_supply;
325 }
326 }
327
288 return 0; 328 return 0;
289 329
290fail_unregister_supply: 330fail_unregister_supply:
@@ -297,6 +337,8 @@ static int tps65090_charger_remove(struct platform_device *pdev)
297{ 337{
298 struct tps65090_charger *cdata = platform_get_drvdata(pdev); 338 struct tps65090_charger *cdata = platform_get_drvdata(pdev);
299 339
340 if (cdata->irq == -ENXIO)
341 kthread_stop(cdata->poll_task);
300 power_supply_unregister(&cdata->ac); 342 power_supply_unregister(&cdata->ac);
301 343
302 return 0; 344 return 0;