From 9705ecc5c1f8f34f756164a711b4cc61110c0283 Mon Sep 17 00:00:00 2001 From: Balaji Rao Date: Tue, 27 Jan 2009 19:23:12 +0530 Subject: pcf50633_charger: Enable periodic charging restart The battery charger state machine switches into charging mode when the battery voltage falls below 96% of a battery float voltage. But the voltage drop in Li-ion batteries is marginal(1~2 %) till about 80% of its capacity - which means, after a BATFULL, charging won't be restarted until 80%. This work_struct function restarts charging at regular intervals to make sure the battery doesn't discharge too much. Signed-off-by: Balaji Rao Cc: Andy Green Signed-off-by: Anton Vorontsov --- drivers/power/pcf50633-charger.c | 73 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c index 41aec2acbb91..1fe1e851a8dd 100644 --- a/drivers/power/pcf50633-charger.c +++ b/drivers/power/pcf50633-charger.c @@ -36,6 +36,8 @@ struct pcf50633_mbc { struct power_supply usb; struct power_supply adapter; + + struct delayed_work charging_restart_work; }; int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) @@ -43,6 +45,8 @@ int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); int ret = 0; u8 bits; + int charging_start = 1; + u8 mbcs2, chgmod; if (ma >= 1000) bits = PCF50633_MBCC7_USB_1000mA; @@ -50,8 +54,10 @@ int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) bits = PCF50633_MBCC7_USB_500mA; else if (ma >= 100) bits = PCF50633_MBCC7_USB_100mA; - else + else { bits = PCF50633_MBCC7_USB_SUSPEND; + charging_start = 0; + } ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7, PCF50633_MBCC7_USB_MASK, bits); @@ -60,6 +66,22 @@ int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma) else dev_info(pcf->dev, "usb curlim to %d mA\n", ma); + /* Manual charging start */ + mbcs2 = pcf50633_reg_read(pcf, PCF50633_REG_MBCS2); + chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); + + /* If chgmod == BATFULL, setting chgena has no effect. + * We need to set resume instead. + */ + if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) + pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA); + else + pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_RESUME, PCF50633_MBCC1_RESUME); + + mbc->usb_active = charging_start; + power_supply_changed(&mbc->usb); return ret; @@ -160,10 +182,44 @@ static struct attribute_group mbc_attr_group = { .attrs = pcf50633_mbc_sysfs_entries, }; +/* MBC state machine switches into charging mode when the battery voltage + * falls below 96% of a battery float voltage. But the voltage drop in Li-ion + * batteries is marginal(1~2 %) till about 80% of its capacity - which means, + * after a BATFULL, charging won't be restarted until 80%. + * + * This work_struct function restarts charging at regular intervals to make + * sure we don't discharge too much + */ + +static void pcf50633_mbc_charging_restart(struct work_struct *work) +{ + struct pcf50633_mbc *mbc; + u8 mbcs2, chgmod; + + mbc = container_of(work, struct pcf50633_mbc, + charging_restart_work.work); + + mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2); + chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK); + + if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL) + return; + + /* Restart charging */ + pcf50633_reg_set_bit_mask(mbc->pcf, PCF50633_REG_MBCC1, + PCF50633_MBCC1_RESUME, PCF50633_MBCC1_RESUME); + mbc->usb_active = 1; + power_supply_changed(&mbc->usb); + + dev_info(mbc->pcf->dev, "Charging restarted\n"); +} + static void pcf50633_mbc_irq_handler(int irq, void *data) { struct pcf50633_mbc *mbc = data; + int chg_restart_interval = + mbc->pcf->pdata->charging_restart_interval; /* USB */ if (irq == PCF50633_IRQ_USBINS) { @@ -172,6 +228,7 @@ pcf50633_mbc_irq_handler(int irq, void *data) mbc->usb_online = 0; mbc->usb_active = 0; pcf50633_mbc_usb_curlim_set(mbc->pcf, 0); + cancel_delayed_work_sync(&mbc->charging_restart_work); } /* Adapter */ @@ -186,7 +243,14 @@ pcf50633_mbc_irq_handler(int irq, void *data) if (irq == PCF50633_IRQ_BATFULL) { mbc->usb_active = 0; mbc->adapter_active = 0; - } + + if (chg_restart_interval > 0) + schedule_delayed_work(&mbc->charging_restart_work, + chg_restart_interval); + } else if (irq == PCF50633_IRQ_USBLIMON) + mbc->usb_active = 0; + else if (irq == PCF50633_IRQ_USBLIMOFF) + mbc->usb_active = 1; power_supply_changed(&mbc->usb); power_supply_changed(&mbc->adapter); @@ -303,6 +367,9 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) return ret; } + INIT_DELAYED_WORK(&mbc->charging_restart_work, + pcf50633_mbc_charging_restart); + ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group); if (ret) dev_err(mbc->pcf->dev, "failed to create sysfs entries\n"); @@ -328,6 +395,8 @@ static int __devexit pcf50633_mbc_remove(struct platform_device *pdev) power_supply_unregister(&mbc->usb); power_supply_unregister(&mbc->adapter); + cancel_delayed_work_sync(&mbc->charging_restart_work); + kfree(mbc); return 0; -- cgit v1.2.3-58-ga151 From cc52a29e6245acd9032fcfa0ffcab4cc612de986 Mon Sep 17 00:00:00 2001 From: Balaji Rao Date: Tue, 27 Jan 2009 19:22:55 +0530 Subject: pcf50633_charger: Remove unused mbc_set_status function The 'pcf50633_mbc_set_status' function is unused, so remove it. Signed-off-by: Balaji Rao Cc: Andy Green Signed-off-by: Anton Vorontsov --- drivers/power/pcf50633-charger.c | 15 --------------- include/linux/mfd/pcf50633/mbc.h | 1 - 2 files changed, 16 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c index 1fe1e851a8dd..e8b278f71781 100644 --- a/drivers/power/pcf50633-charger.c +++ b/drivers/power/pcf50633-charger.c @@ -106,21 +106,6 @@ int pcf50633_mbc_get_status(struct pcf50633 *pcf) } EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status); -void pcf50633_mbc_set_status(struct pcf50633 *pcf, int what, int status) -{ - struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev); - - if (what & PCF50633_MBC_USB_ONLINE) - mbc->usb_online = !!status; - if (what & PCF50633_MBC_USB_ACTIVE) - mbc->usb_active = !!status; - if (what & PCF50633_MBC_ADAPTER_ONLINE) - mbc->adapter_online = !!status; - if (what & PCF50633_MBC_ADAPTER_ACTIVE) - mbc->adapter_active = !!status; -} -EXPORT_SYMBOL_GPL(pcf50633_mbc_set_status); - static ssize_t show_chgmode(struct device *dev, struct device_attribute *attr, char *buf) { diff --git a/include/linux/mfd/pcf50633/mbc.h b/include/linux/mfd/pcf50633/mbc.h index 6e17619b773a..4119579acf2c 100644 --- a/include/linux/mfd/pcf50633/mbc.h +++ b/include/linux/mfd/pcf50633/mbc.h @@ -128,7 +128,6 @@ enum pcf50633_reg_mbcs3 { int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma); int pcf50633_mbc_get_status(struct pcf50633 *); -void pcf50633_mbc_set_status(struct pcf50633 *, int what, int status); #endif -- cgit v1.2.3-58-ga151 From 5bf2b994bfe11bfe86231050897b2d881ca544d9 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Sun, 18 Jan 2009 17:40:27 +0100 Subject: pda_power: Add optional OTG transceiver and voltage regulator support This patch allows machines to use an OTG transceiver driver instead of supplying a custom is_usb_online callback to check USB power. Also, in the case that the OTG transceiver handles charger control when connected to USB, a regulator named "ac_draw" can be supplied instead of the custom set_charge callback to control the charger when connected to AC. The check for (transceiver->state == OTG_STATE_B_PERIPHERAL) in otg_is_usb_online is probably too simple, I'm just using this with a peripheral only device and gpio_vbus + bq24022. I'm not sure which other OTG states can supply power. Signed-off-by: Philipp Zabel Signed-off-by: Anton Vorontsov --- drivers/power/pda_power.c | 89 ++++++++++++++++++++++++++++++++++++++++------- include/linux/pda_power.h | 2 ++ 2 files changed, 79 insertions(+), 12 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c index b56a704409d2..a232de6a5703 100644 --- a/drivers/power/pda_power.c +++ b/drivers/power/pda_power.c @@ -12,11 +12,14 @@ #include #include +#include #include #include #include +#include #include #include +#include static inline unsigned int get_irq_flags(struct resource *res) { @@ -35,6 +38,11 @@ static struct timer_list supply_timer; static struct timer_list polling_timer; static int polling; +#ifdef CONFIG_USB_OTG_UTILS +static struct otg_transceiver *transceiver; +#endif +static struct regulator *ac_draw; + enum { PDA_PSY_OFFLINE = 0, PDA_PSY_ONLINE = 1, @@ -104,18 +112,35 @@ static void update_status(void) static void update_charger(void) { - if (!pdata->set_charge) - return; - - if (new_ac_status > 0) { - dev_dbg(dev, "charger on (AC)\n"); - pdata->set_charge(PDA_POWER_CHARGE_AC); - } else if (new_usb_status > 0) { - dev_dbg(dev, "charger on (USB)\n"); - pdata->set_charge(PDA_POWER_CHARGE_USB); - } else { - dev_dbg(dev, "charger off\n"); - pdata->set_charge(0); + static int regulator_enabled; + int max_uA = pdata->ac_max_uA; + + if (pdata->set_charge) { + if (new_ac_status > 0) { + dev_dbg(dev, "charger on (AC)\n"); + pdata->set_charge(PDA_POWER_CHARGE_AC); + } else if (new_usb_status > 0) { + dev_dbg(dev, "charger on (USB)\n"); + pdata->set_charge(PDA_POWER_CHARGE_USB); + } else { + dev_dbg(dev, "charger off\n"); + pdata->set_charge(0); + } + } else if (ac_draw) { + if (new_ac_status > 0) { + regulator_set_current_limit(ac_draw, max_uA, max_uA); + if (!regulator_enabled) { + dev_dbg(dev, "charger on (AC)\n"); + regulator_enable(ac_draw); + regulator_enabled = 1; + } + } else { + if (regulator_enabled) { + dev_dbg(dev, "charger off\n"); + regulator_disable(ac_draw); + regulator_enabled = 0; + } + } } } @@ -194,6 +219,13 @@ static void polling_timer_func(unsigned long unused) jiffies + msecs_to_jiffies(pdata->polling_interval)); } +#ifdef CONFIG_USB_OTG_UTILS +static int otg_is_usb_online(void) +{ + return (transceiver->state == OTG_STATE_B_PERIPHERAL); +} +#endif + static int pda_power_probe(struct platform_device *pdev) { int ret = 0; @@ -227,6 +259,9 @@ static int pda_power_probe(struct platform_device *pdev) if (!pdata->polling_interval) pdata->polling_interval = 2000; + if (!pdata->ac_max_uA) + pdata->ac_max_uA = 500000; + setup_timer(&charger_timer, charger_timer_func, 0); setup_timer(&supply_timer, supply_timer_func, 0); @@ -240,6 +275,13 @@ static int pda_power_probe(struct platform_device *pdev) pda_psy_usb.num_supplicants = pdata->num_supplicants; } + ac_draw = regulator_get(dev, "ac_draw"); + if (IS_ERR(ac_draw)) { + dev_dbg(dev, "couldn't get ac_draw regulator\n"); + ac_draw = NULL; + ret = PTR_ERR(ac_draw); + } + if (pdata->is_ac_online) { ret = power_supply_register(&pdev->dev, &pda_psy_ac); if (ret) { @@ -261,6 +303,13 @@ static int pda_power_probe(struct platform_device *pdev) } } +#ifdef CONFIG_USB_OTG_UTILS + transceiver = otg_get_transceiver(); + if (transceiver && !pdata->is_usb_online) { + pdata->is_usb_online = otg_is_usb_online; + } +#endif + if (pdata->is_usb_online) { ret = power_supply_register(&pdev->dev, &pda_psy_usb); if (ret) { @@ -300,10 +349,18 @@ usb_irq_failed: usb_supply_failed: if (pdata->is_ac_online && ac_irq) free_irq(ac_irq->start, &pda_psy_ac); +#ifdef CONFIG_USB_OTG_UTILS + if (transceiver) + otg_put_transceiver(transceiver); +#endif ac_irq_failed: if (pdata->is_ac_online) power_supply_unregister(&pda_psy_ac); ac_supply_failed: + if (ac_draw) { + regulator_put(ac_draw); + ac_draw = NULL; + } if (pdata->exit) pdata->exit(dev); init_failed: @@ -327,6 +384,14 @@ static int pda_power_remove(struct platform_device *pdev) power_supply_unregister(&pda_psy_usb); if (pdata->is_ac_online) power_supply_unregister(&pda_psy_ac); +#ifdef CONFIG_USB_OTG_UTILS + if (transceiver) + otg_put_transceiver(transceiver); +#endif + if (ac_draw) { + regulator_put(ac_draw); + ac_draw = NULL; + } if (pdata->exit) pdata->exit(dev); diff --git a/include/linux/pda_power.h b/include/linux/pda_power.h index cb7d10f30763..d4cf7a2ceb3e 100644 --- a/include/linux/pda_power.h +++ b/include/linux/pda_power.h @@ -31,6 +31,8 @@ struct pda_power_pdata { unsigned int wait_for_status; /* msecs, default is 500 */ unsigned int wait_for_charger; /* msecs, default is 500 */ unsigned int polling_interval; /* msecs, default is 2000 */ + + unsigned long ac_max_uA; /* current to draw when on AC */ }; #endif /* __PDA_POWER_H__ */ -- cgit v1.2.3-58-ga151