Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Doug Anderson and committed by
Sebastian Reichel
193dcced 661468b4

+59 -17
+59 -17
drivers/power/tps65090-charger.c
··· 17 17 */ 18 18 #include <linux/delay.h> 19 19 #include <linux/err.h> 20 + #include <linux/freezer.h> 20 21 #include <linux/init.h> 21 22 #include <linux/interrupt.h> 22 23 #include <linux/kernel.h> 24 + #include <linux/kthread.h> 23 25 #include <linux/module.h> 24 26 #include <linux/of_device.h> 25 27 #include <linux/platform_device.h> ··· 34 32 #define TPS65090_VACG BIT(1) 35 33 #define TPS65090_NOITERM BIT(5) 36 34 35 + #define POLL_INTERVAL (HZ * 2) /* Used when no irq */ 36 + 37 37 struct tps65090_charger { 38 38 struct device *dev; 39 39 int ac_online; 40 40 int prev_ac_online; 41 41 int irq; 42 + struct task_struct *poll_task; 43 + bool passive_mode; 42 44 struct power_supply ac; 43 45 struct tps65090_platform_data *pdata; 44 46 }; ··· 54 48 static int tps65090_low_chrg_current(struct tps65090_charger *charger) 55 49 { 56 50 int ret; 51 + 52 + if (charger->passive_mode) 53 + return 0; 57 54 58 55 ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5, 59 56 TPS65090_NOITERM); ··· 72 63 { 73 64 int ret; 74 65 uint8_t ctrl0 = 0; 66 + 67 + if (charger->passive_mode) 68 + return 0; 75 69 76 70 ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0, 77 71 &ctrl0); ··· 98 86 { 99 87 uint8_t intrmask = 0; 100 88 int ret; 89 + 90 + if (charger->passive_mode) 91 + return 0; 101 92 102 93 if (charger->pdata->enable_low_current_chrg) { 103 94 ret = tps65090_low_chrg_current(charger); ··· 179 164 } 180 165 181 166 /* Clear interrupts. */ 182 - ret = tps65090_write(charger->dev->parent, TPS65090_REG_INTR_STS, 0x00); 183 - if (ret < 0) { 184 - dev_err(charger->dev, "%s(): Error in writing reg 0x%x\n", 167 + if (!charger->passive_mode) { 168 + ret = tps65090_write(charger->dev->parent, 169 + TPS65090_REG_INTR_STS, 0x00); 170 + if (ret < 0) { 171 + dev_err(charger->dev, 172 + "%s(): Error in writing reg 0x%x\n", 185 173 __func__, TPS65090_REG_INTR_STS); 174 + } 186 175 } 187 176 188 177 if (charger->prev_ac_online != charger->ac_online) ··· 215 196 216 197 return pdata; 217 198 199 + } 200 + 201 + static int tps65090_charger_poll_task(void *data) 202 + { 203 + set_freezable(); 204 + 205 + while (!kthread_should_stop()) { 206 + schedule_timeout_interruptible(POLL_INTERVAL); 207 + try_to_freeze(); 208 + tps65090_charger_isr(-1, data); 209 + } 210 + return 0; 218 211 } 219 212 220 213 static int tps65090_charger_probe(struct platform_device *pdev) ··· 275 244 } 276 245 277 246 irq = platform_get_irq(pdev, 0); 278 - if (irq <= 0) { 279 - dev_warn(&pdev->dev, "Unable to get charger irq = %d\n", irq); 280 - ret = irq; 281 - goto fail_unregister_supply; 282 - } 283 - 247 + if (irq < 0) 248 + irq = -ENXIO; 284 249 cdata->irq = irq; 285 - 286 - ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, 287 - tps65090_charger_isr, 0, "tps65090-charger", cdata); 288 - if (ret) { 289 - dev_err(cdata->dev, "Unable to register irq %d err %d\n", irq, 290 - ret); 291 - goto fail_unregister_supply; 292 - } 293 250 294 251 ret = tps65090_config_charger(cdata); 295 252 if (ret < 0) { ··· 304 285 power_supply_changed(&cdata->ac); 305 286 } 306 287 288 + if (irq != -ENXIO) { 289 + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, 290 + tps65090_charger_isr, 0, "tps65090-charger", cdata); 291 + if (ret) { 292 + dev_err(cdata->dev, 293 + "Unable to register irq %d err %d\n", irq, 294 + ret); 295 + goto fail_unregister_supply; 296 + } 297 + } else { 298 + cdata->poll_task = kthread_run(tps65090_charger_poll_task, 299 + cdata, "ktps65090charger"); 300 + cdata->passive_mode = true; 301 + if (IS_ERR(cdata->poll_task)) { 302 + ret = PTR_ERR(cdata->poll_task); 303 + dev_err(cdata->dev, 304 + "Unable to run kthread err %d\n", ret); 305 + goto fail_unregister_supply; 306 + } 307 + } 308 + 307 309 return 0; 308 310 309 311 fail_unregister_supply: ··· 337 297 { 338 298 struct tps65090_charger *cdata = platform_get_drvdata(pdev); 339 299 300 + if (cdata->irq == -ENXIO) 301 + kthread_stop(cdata->poll_task); 340 302 power_supply_unregister(&cdata->ac); 341 303 342 304 return 0;