diff options
author | NeilBrown <neil@brown.name> | 2015-07-29 20:11:24 -0400 |
---|---|---|
committer | Sebastian Reichel <sre@kernel.org> | 2015-08-04 23:21:32 -0400 |
commit | 7f4a633d21331155ee06c5ee44749ed35a3a3cc5 (patch) | |
tree | 6ea5515851e6e238c9d381df5eb5fd73ab4bf4bd /drivers/power | |
parent | 22d4c33f7335ddf6deda26630c78650e133bfe72 (diff) |
twl4030_charger: add software controlled linear charging mode.
Add a 'continuous' option for usb charging which enables
the "linear" charging mode of the twl4030.
Linear charging does a good job with not-so-reliable power sources.
Auto mode does not work well as it switches off when voltage drops
momentarily. Care must be taken not to over-charge.
It was used with a bike hub dynamo since a year or so. In that case
there are automatically charging stops when the cyclist needs a break.
Original-by: Andreas Kemnade <andreas@kemnade.info>
Signed-off-by: NeilBrown <neil@brown.name>
Signed-off-by: Sebastian Reichel <sre@kernel.org>
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/twl4030_charger.c | 55 |
1 files changed, 50 insertions, 5 deletions
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 6fa928ed3128..de5430deaf23 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c | |||
@@ -24,6 +24,8 @@ | |||
24 | #include <linux/usb/otg.h> | 24 | #include <linux/usb/otg.h> |
25 | #include <linux/i2c/twl4030-madc.h> | 25 | #include <linux/i2c/twl4030-madc.h> |
26 | 26 | ||
27 | #define TWL4030_BCIMDEN 0x00 | ||
28 | #define TWL4030_BCIMDKEY 0x01 | ||
27 | #define TWL4030_BCIMSTATEC 0x02 | 29 | #define TWL4030_BCIMSTATEC 0x02 |
28 | #define TWL4030_BCIICHG 0x08 | 30 | #define TWL4030_BCIICHG 0x08 |
29 | #define TWL4030_BCIVAC 0x0a | 31 | #define TWL4030_BCIVAC 0x0a |
@@ -35,13 +37,16 @@ | |||
35 | #define TWL4030_BCIIREF1 0x27 | 37 | #define TWL4030_BCIIREF1 0x27 |
36 | #define TWL4030_BCIIREF2 0x28 | 38 | #define TWL4030_BCIIREF2 0x28 |
37 | #define TWL4030_BCIMFKEY 0x11 | 39 | #define TWL4030_BCIMFKEY 0x11 |
40 | #define TWL4030_BCIMFEN3 0x14 | ||
38 | #define TWL4030_BCIMFTH8 0x1d | 41 | #define TWL4030_BCIMFTH8 0x1d |
39 | #define TWL4030_BCIMFTH9 0x1e | 42 | #define TWL4030_BCIMFTH9 0x1e |
43 | #define TWL4030_BCIWDKEY 0x21 | ||
40 | 44 | ||
41 | #define TWL4030_BCIMFSTS1 0x01 | 45 | #define TWL4030_BCIMFSTS1 0x01 |
42 | 46 | ||
43 | #define TWL4030_BCIAUTOWEN BIT(5) | 47 | #define TWL4030_BCIAUTOWEN BIT(5) |
44 | #define TWL4030_CONFIG_DONE BIT(4) | 48 | #define TWL4030_CONFIG_DONE BIT(4) |
49 | #define TWL4030_CVENAC BIT(2) | ||
45 | #define TWL4030_BCIAUTOUSB BIT(1) | 50 | #define TWL4030_BCIAUTOUSB BIT(1) |
46 | #define TWL4030_BCIAUTOAC BIT(0) | 51 | #define TWL4030_BCIAUTOAC BIT(0) |
47 | #define TWL4030_CGAIN BIT(5) | 52 | #define TWL4030_CGAIN BIT(5) |
@@ -112,12 +117,13 @@ struct twl4030_bci { | |||
112 | int usb_mode; /* charging mode requested */ | 117 | int usb_mode; /* charging mode requested */ |
113 | #define CHARGE_OFF 0 | 118 | #define CHARGE_OFF 0 |
114 | #define CHARGE_AUTO 1 | 119 | #define CHARGE_AUTO 1 |
120 | #define CHARGE_LINEAR 2 | ||
115 | 121 | ||
116 | unsigned long event; | 122 | unsigned long event; |
117 | }; | 123 | }; |
118 | 124 | ||
119 | /* strings for 'usb_mode' values */ | 125 | /* strings for 'usb_mode' values */ |
120 | static char *modes[] = { "off", "auto" }; | 126 | static char *modes[] = { "off", "auto", "continuous" }; |
121 | 127 | ||
122 | /* | 128 | /* |
123 | * clear and set bits on an given register on a given module | 129 | * clear and set bits on an given register on a given module |
@@ -404,16 +410,42 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) | |||
404 | bci->usb_enabled = 1; | 410 | bci->usb_enabled = 1; |
405 | } | 411 | } |
406 | 412 | ||
407 | /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ | 413 | if (bci->usb_mode == CHARGE_AUTO) |
408 | ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); | 414 | /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ |
409 | if (ret < 0) | 415 | ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); |
410 | return ret; | ||
411 | 416 | ||
412 | /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ | 417 | /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ |
413 | ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0, | 418 | ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0, |
414 | TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); | 419 | TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); |
420 | if (bci->usb_mode == CHARGE_LINEAR) { | ||
421 | twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC|TWL4030_CVENAC, 0); | ||
422 | /* Watch dog key: WOVF acknowledge */ | ||
423 | ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x33, | ||
424 | TWL4030_BCIWDKEY); | ||
425 | /* 0x24 + EKEY6: off mode */ | ||
426 | ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, | ||
427 | TWL4030_BCIMDKEY); | ||
428 | /* EKEY2: Linear charge: USB path */ | ||
429 | ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x26, | ||
430 | TWL4030_BCIMDKEY); | ||
431 | /* WDKEY5: stop watchdog count */ | ||
432 | ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf3, | ||
433 | TWL4030_BCIWDKEY); | ||
434 | /* enable MFEN3 access */ | ||
435 | ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x9c, | ||
436 | TWL4030_BCIMFKEY); | ||
437 | /* ICHGEOCEN - end-of-charge monitor (current < 80mA) | ||
438 | * (charging continues) | ||
439 | * ICHGLOWEN - current level monitor (charge continues) | ||
440 | * don't monitor over-current or heat save | ||
441 | */ | ||
442 | ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf0, | ||
443 | TWL4030_BCIMFEN3); | ||
444 | } | ||
415 | } else { | 445 | } else { |
416 | ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); | 446 | ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); |
447 | ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a, | ||
448 | TWL4030_BCIMDKEY); | ||
417 | if (bci->usb_enabled) { | 449 | if (bci->usb_enabled) { |
418 | pm_runtime_mark_last_busy(bci->transceiver->dev); | 450 | pm_runtime_mark_last_busy(bci->transceiver->dev); |
419 | pm_runtime_put_autosuspend(bci->transceiver->dev); | 451 | pm_runtime_put_autosuspend(bci->transceiver->dev); |
@@ -652,6 +684,8 @@ twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr, | |||
652 | mode = 0; | 684 | mode = 0; |
653 | else if (sysfs_streq(buf, modes[1])) | 685 | else if (sysfs_streq(buf, modes[1])) |
654 | mode = 1; | 686 | mode = 1; |
687 | else if (sysfs_streq(buf, modes[2])) | ||
688 | mode = 2; | ||
655 | else | 689 | else |
656 | return -EINVAL; | 690 | return -EINVAL; |
657 | twl4030_charger_enable_usb(bci, false); | 691 | twl4030_charger_enable_usb(bci, false); |
@@ -750,6 +784,17 @@ static int twl4030_bci_get_property(struct power_supply *psy, | |||
750 | is_charging = state & TWL4030_MSTATEC_USB; | 784 | is_charging = state & TWL4030_MSTATEC_USB; |
751 | else | 785 | else |
752 | is_charging = state & TWL4030_MSTATEC_AC; | 786 | is_charging = state & TWL4030_MSTATEC_AC; |
787 | if (!is_charging) { | ||
788 | u8 s; | ||
789 | twl4030_bci_read(TWL4030_BCIMDEN, &s); | ||
790 | if (psy->desc->type == POWER_SUPPLY_TYPE_USB) | ||
791 | is_charging = s & 1; | ||
792 | else | ||
793 | is_charging = s & 2; | ||
794 | if (is_charging) | ||
795 | /* A little white lie */ | ||
796 | state = TWL4030_MSTATEC_QUICK1; | ||
797 | } | ||
753 | 798 | ||
754 | switch (psp) { | 799 | switch (psp) { |
755 | case POWER_SUPPLY_PROP_STATUS: | 800 | case POWER_SUPPLY_PROP_STATUS: |