aboutsummaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorJohan Palsson <johan.palsson@stericsson.com>2010-12-17 11:18:47 +0100
committerJonas ABERG <jonas.aberg@stericsson.com>2010-12-20 10:37:14 +0100
commit23fdfe19438ed9d37df5201216e3573e8ad67be1 (patch)
tree7cbc1c7df8d76d0dcf0947307c4439c862ed5fa6 /drivers/power
parentb4e3d1a1ff6cbfa5901c60dbfe9694a55880d5d7 (diff)
[ANDROID] ab8500_bm: Always kick watchdog to avoid power off in ABB rev1.1
There is a bug in rev1.1 of AB8500 which means that we have to continously kick the charger watchdog every 60 seconds or else it will power off. This error only occurs when you connect and then disconnect the DCIO charger. The solution is to wake up periodically on the FG IRQ and kick the charger watchdog. This is only done for ABB rev1.1 and when the DCIO charger has been enabled. We also take a wakelock when a DCIO charger is connected to be able to control algorithm execution. This is a battery safety issue. Also small changes is introduced to allow USB charging with ABB rev 1.1. ST-Ericsson ID: ER280739 Change-Id: I9fa63299be874c7b1c34144e8bfabc64adb5da00 Signed-off-by: Johan Palsson <johan.palsson@stericsson.com> Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/11208 Reviewed-by: QATOOLS Reviewed-by: Jonas ABERG <jonas.aberg@stericsson.com>
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/ab8500_bm.c253
1 files changed, 185 insertions, 68 deletions
diff --git a/drivers/power/ab8500_bm.c b/drivers/power/ab8500_bm.c
index 44ec2549302..65eff3d024a 100644
--- a/drivers/power/ab8500_bm.c
+++ b/drivers/power/ab8500_bm.c
@@ -35,6 +35,7 @@
#include <mach/stm_musb.h>
#include <linux/mfd/ab8500/ab8500-bm.h>
#include <linux/mfd/ab8500/ab8500-gpadc.h>
+#include <linux/wakelock.h>
/*
* Charger / status register offfsets
@@ -76,9 +77,19 @@
*/
#define AB8500_OTP_CONF_15 0x0E
+/* Watchdog timeout constant */
+#define WD_TIMER 0x30 /* 4min */
+
/* Watchdog kick interval */
#define CHG_WD_INTERVAL (30*HZ)
+/* Number of FG samples to use for kicking the WD */
+#define CHG_WD_INTERVAL_FG (60*4) /* 60s */
+
+/* Charging algorithm interval */
+#define CHG_ALG_INTERVAL_SHORT (3*HZ)
+#define CHG_ALG_INTERVAL_LONG (120*HZ)
+
/* End-of-charge criteria counter */
#define EOC_COND_CNT 20
@@ -325,6 +336,7 @@ struct ab8500_avg_cap {
* struct ab8500_bm - ab8500 EM device information
* @dev: Pointer to the structure device
* @cid Chip ID of the ABB ASIC
+ * @ac_conn This will be true when the AC charger has been plugged
* @bat_id; Battery index to select identified battery type
* @voltage: Battery voltage in mV
* @temp_C: Battery temperature in Celcius
@@ -390,6 +402,7 @@ struct ab8500_bm {
struct device *dev;
u8 cid;
u8 bat_id;
+ bool ac_conn;
int voltage;
int temp_C;
int inst_current;
@@ -431,6 +444,7 @@ struct ab8500_bm {
struct workqueue_struct *ab8500_bm_wd_kick_wq;
struct completion ab8500_bm_usb_completed;
struct kobject ab8500_bm_kobject;
+ struct wake_lock ab8500_charger_wakelock;
spinlock_t ab8500_bm_lock;
};
@@ -1346,10 +1360,14 @@ static void ab8500_bm_ac_work(struct work_struct *work)
if (ret < 0)
return;
- if (ret & AC_PW_CONN)
+ if (ret & AC_PW_CONN) {
di->state_info.connected_charger_status |= AC_PW_CONN;
- else
+ wake_lock(&di->ab8500_charger_wakelock);
+ di->ac_conn = true;
+ } else {
di->state_info.connected_charger_status &= ~AC_PW_CONN;
+ wake_unlock(&di->ab8500_charger_wakelock);
+ }
queue_work(di->ab8500_bm_wq, &di->ab8500_bm_instant_monitor_work);
}
@@ -1399,6 +1417,20 @@ static irqreturn_t ab8500_bm_cc_convend_handler (int irq, void *_di)
{
struct ab8500_bm *di = _di;
+ switch (di->cid) {
+ case AB8500_CUT1P0:
+ case AB8500_CUT1P1:
+ if (di->ac_conn) {
+ cancel_delayed_work(&di->ab8500_bm_watchdog_work);
+ queue_delayed_work(di->ab8500_bm_wd_kick_wq,
+ &di->ab8500_bm_watchdog_work, 0);
+ }
+ break;
+
+ default:
+ break;
+ }
+
queue_work(di->ab8500_bm_wq, &di->ab8500_bm_avg_cur_monitor_work);
return IRQ_HANDLED;
@@ -1523,22 +1555,25 @@ static irqreturn_t ab8500_bm_vbusovv_handler(int irq, void *_di)
{
struct ab8500_bm *di = _di;
- dev_warn(di->dev, "%s VBUS overvoltage detected\n", __func__);
/* For protection sw is also disabling charging */
switch (di->cid) {
case AB8500_CUT1P0:
case AB8500_CUT1P1:
/*
* On cut 1.x we get false VBUS OVV interrupts
- * when a USB cable is insterted that is stopping
+ * when a USB cable is inserted that is stopping
* the charging
*/
+ dev_dbg(di->dev,
+ "%s VBUS OVV detected, ignore for ABB cut 1.*\n",
+ __func__);
/* TODO: Handle this in a better way */
- di->event_flags.vbus_ovv = true;
+ di->event_flags.vbus_ovv = false;
break;
case AB8500_CUT2P0:
+ dev_warn(di->dev, "%s VBUS overvoltage detected\n", __func__);
di->event_flags.vbus_ovv = true;
break;
}
@@ -1696,13 +1731,13 @@ static irqreturn_t ab8500_bm_mainchthprotr_handler(int irq, void *_di)
}
/**
- * ab8500_bm_usbchargernotokf_handler() - usb charger unplug detected
+ * ab8500_bm_usbchargernotokr_handler() - usb charger not ok detected
* @irq: interrupt number
* @_di: void pointer that has to address of ab8500_bm
*
* Returns IRQ status(IRQ_HANDLED)
*/
-static irqreturn_t ab8500_bm_usbchargernotokf_handler(int irq, void *_di)
+static irqreturn_t ab8500_bm_usbchargernotokr_handler(int irq, void *_di)
{
struct ab8500_bm *di = _di;
@@ -1728,7 +1763,7 @@ static struct ab8500_bm_interrupts ab8500_bm_irq[] = {
{"USB_CHARGE_DET_DONE", ab8500_bm_usbchgdetdone_handler},
{"USB_CH_TH_PROT_R", ab8500_bm_usbchthprotr_handler},
{"MAIN_CH_TH_PROT_R", ab8500_bm_mainchthprotr_handler},
- {"USB_CHARGER_NOT_OKF", ab8500_bm_usbchargernotokf_handler},
+ {"USB_CHARGER_NOT_OKR", ab8500_bm_usbchargernotokr_handler},
{"NCONV_ACCU", ab8500_bm_cc_convend_handler},
};
@@ -2358,10 +2393,10 @@ static void ab8500_bm_battery_work(struct work_struct *work)
if ((di->charge_status == POWER_SUPPLY_STATUS_CHARGING) ||
di->maintenance_chg)
queue_delayed_work(di->ab8500_bm_wq,
- &di->ab8500_bm_monitor_work, HZ);
+ &di->ab8500_bm_monitor_work, CHG_ALG_INTERVAL_SHORT);
else
queue_delayed_work(di->ab8500_bm_wq,
- &di->ab8500_bm_monitor_work, 20 * HZ);
+ &di->ab8500_bm_monitor_work, CHG_ALG_INTERVAL_LONG);
}
/**
@@ -2380,6 +2415,7 @@ static void ab8500_bm_watchdog_kick_work(struct work_struct *work)
struct ab8500_bm, ab8500_bm_watchdog_work.work);
/* Kickoff main watchdog */
+ dev_dbg(di->dev, "Watchdog kick!\n");
ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
if (ret) {
@@ -2599,6 +2635,7 @@ static int ab8500_bm_ac_en_w_v_i(struct ab8500_bm *di,
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
}
+ udelay(100);
ret = abx500_set_register_interruptible(di->dev,
AB8500_SYS_CTRL2_BLOCK,
AB8500_MAIN_WDOG_CTRL_REG,
@@ -2607,6 +2644,7 @@ static int ab8500_bm_ac_en_w_v_i(struct ab8500_bm *di,
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
}
+ udelay(100);
/* Disable main watchdog */
ret = abx500_set_register_interruptible(di->dev,
AB8500_SYS_CTRL2_BLOCK,
@@ -2768,21 +2806,26 @@ static int ab8500_bm_usb_en_w_v_i(struct ab8500_bm *di,
return ret;
}
/* Enable main watchdog */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA);
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
}
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ udelay(100);
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
AB8500_MAIN_WDOG_CTRL_REG,
(MAIN_WDOG_ENA | MAIN_WDOG_KICK));
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
}
+ udelay(100);
/* Disable main watchdog */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS);
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
@@ -2804,46 +2847,13 @@ static int ab8500_bm_usb_en_w_v_i(struct ab8500_bm *di,
*/
} else {
/* Disable USB charging */
-
- switch (di->cid) {
- case AB8500_CUT1P0:
- case AB8500_CUT1P1:
- /*
- * We can't turn off charging completely
- * due to a bug in AB8500 cut1.
- * If we do, charging will not start again.
- * That is why we set the lowest voltage
- * and current possible
- */
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5);
- if (ret) {
- dev_err(di->dev,
- "%s write failed\n", __func__);
- return ret;
- }
-
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1);
- if (ret) {
- dev_err(di->dev,
- "%s write failed\n", __func__);
- return ret;
- }
- break;
-
- case AB8500_CUT2P0:
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_USBCH_CTRL1_REG, USB_CH_DIS);
- if (ret) {
- dev_err(di->dev,
- "%s write failed\n", __func__);
- return ret;
- }
- break;
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL1_REG, USB_CH_DIS);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
}
/* If successful power off charging LED indication */
@@ -3665,15 +3675,51 @@ static int ab8500_bm_resume(struct platform_device *pdev)
struct ab8500_bm *di = platform_get_drvdata(pdev);
int ret;
- queue_delayed_work(di->ab8500_bm_wq, &di->ab8500_bm_monitor_work, 0);
+ switch (di->cid) {
+ case AB8500_CUT1P0:
+ case AB8500_CUT1P1:
+ if (di->ac_conn) {
+ /*
+ * The CC is already on, so leave this
+ * We have to kick the watchdog in case we resume
+ * before the CC conversion IRQ is triggered
+ */
+ dev_dbg(di->dev, "Watchdog kick in resume!\n");
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CHARG_WD_CTRL,
+ CHARG_WD_KICK);
- /* Start the CC */
- ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
- AB8500_RTC_CC_CONF_REG, (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
- if (ret) {
- dev_err(di->dev, "resume fuel gauge failed\n");
- return ret;
+ } else {
+ /* Start the CC */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG,
+ (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+ if (ret) {
+ dev_err(di->dev, "resume fuel gauge failed\n");
+ return ret;
+ }
+
+ }
+ break;
+ default:
+ /* Start the CC */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG,
+ (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+ if (ret) {
+ dev_err(di->dev, "resume fuel gauge failed\n");
+ return ret;
+ }
+ break;
}
+
+ queue_delayed_work(di->ab8500_bm_wd_kick_wq,
+ &di->ab8500_bm_watchdog_work, 0);
+ queue_delayed_work(di->ab8500_bm_wq, &di->ab8500_bm_monitor_work, 0);
+
return 0;
}
@@ -3682,15 +3728,70 @@ static int ab8500_bm_suspend(struct platform_device *pdev, pm_message_t state)
struct ab8500_bm *di = platform_get_drvdata(pdev);
int ret;
- di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
cancel_delayed_work(&di->ab8500_bm_monitor_work);
+ cancel_delayed_work(&di->ab8500_bm_watchdog_work);
- /* Stop the CC */
- ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
- AB8500_RTC_CC_CONF_REG, 0x0);
- if (ret) {
- dev_err(di->dev, "suspend fuel gauge failed\n");
- return ret;
+ switch (di->cid) {
+ case AB8500_CUT1P0:
+ case AB8500_CUT1P1:
+ /*
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. In suspend no
+ * timers are active, that is why we use the FG IRQ to wake us
+ * up periodically to kick the watchdog
+ */
+
+ if (di->ac_conn) {
+ di->samples = CHG_WD_INTERVAL_FG;
+ /* Stop the CC */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0x00);
+ if (ret) {
+ dev_err(di->dev, "cc disable failed\n");
+ return ret;
+ }
+ /* Program the samples */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU,
+ di->samples);
+ if (ret) {
+ dev_err(di->dev, "cc write sample failed\n");
+ return ret;
+ }
+ /* Start the CC */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG,
+ (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+ if (ret) {
+ dev_err(di->dev, "cc enable failed\n");
+ return ret;
+ }
+ } else {
+ /* Stop the CC */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0x0);
+ if (ret) {
+ dev_err(di->dev, "suspend fuel gauge failed\n");
+ return ret;
+ }
+ }
+ break;
+
+ default:
+ /* Stop the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0x0);
+ if (ret) {
+ dev_err(di->dev, "suspend fuel gauge failed\n");
+ return ret;
+ }
+
+ break;
}
return 0;
}
@@ -3792,6 +3893,11 @@ static int __devinit ab8500_bm_probe(struct platform_device *pdev)
}
ab8500_bm_di->pdata = plat->battery;
+ /* Wakelock for preventing suspend when a charger is connected */
+ wake_lock_init(&ab8500_bm_di->ab8500_charger_wakelock,
+ WAKE_LOCK_SUSPEND, "ab8500-charger");
+
+
ab8500_bm_di->maintenance_chg = false;
ab8500_bm_di->suspension_status.suspended_change = true;
ab8500_bm_di->suspension_status.ac_suspended = false;
@@ -4028,6 +4134,14 @@ static int __devinit ab8500_bm_probe(struct platform_device *pdev)
goto free_usb;
}
+ /* Set watchdog timeout */
+ ret = abx500_set_register_interruptible(ab8500_bm_di->dev,
+ AB8500_CHARGER, AB8500_CH_WD_TIMER_REG, WD_TIMER);
+ if (ret) {
+ dev_err(ab8500_bm_di->dev, "%s write failed\n", __func__);
+ goto free_usb;
+ }
+
/* Low Battery Voltage = 3.1v */
ret = abx500_set_register_interruptible(ab8500_bm_di->dev,
AB8500_SYS_CTRL2_BLOCK,
@@ -4086,8 +4200,11 @@ static int __devinit ab8500_bm_probe(struct platform_device *pdev)
/* Identify the connected charger types during startup */
charger_status = ab8500_bm_connected_chargers(ab8500_bm_di);
- if (charger_status & AC_PW_CONN)
+ if (charger_status & AC_PW_CONN) {
ab8500_bm_di->state_info.connected_charger_status = AC_PW_CONN;
+ wake_lock(&ab8500_bm_di->ab8500_charger_wakelock);
+ ab8500_bm_di->ac_conn = true;
+ }
if (charger_status & USB_PW_CONN) {
ab8500_bm_di->usb_plug_unplug = true;