power: pm8921-charger: Handle cxo buffers off
The cxo buffers could be turned off while the main cpu(s) are running. This
can cause few issues with the charging driver.
The pmic generates sleep_b signal when all the buffers derived from the
cxo clocks are turned off. This sleep_b signal is fed to all the h/w
subsystems within the pmic.
The charger h/w block uses this for mainly two reasons
1. Turn off the VREF_BATT_THERM signal. This signal drives the battery
thermistor across a resistor divider circuit. With this signal off the
battery temperature is reported too cold. Update the driver to force this
signal on IOW make it not dependent on sleep_b. Remove this forcing when
the system suspends.
2. Run from a slower clock (sleep clk) and as a result freeze charger
register accesses. Force the charger to run from internal cxo clock while
not suspended. This guarantees charger registers are always accessible to
the cpu(s). While suspending, switch back to hw controlled clock sourcing
mode.
While in hw controlled clock sourcing mode,
the charger system switches between cxo and sleep clk depending on
the sleep_b signal. If sleep_b deactivates within six clock
cycles of the sleep clock, the charger doesn't correctly register
an exit from the sleep state and it stops functioning.
To fix this lockup , apply a "kickstart" procedure while resuming. This
procedure involves a series of steps to switch the charger's clock source
between sleep clk and cxo clock with appropriate delays, before finally
switching to cxo clock.
Change-Id: Icda8eb932df0ee7c270d35e2163be7cb8ddd43c3
Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c
index 22d53e9..c0c398b 100644
--- a/drivers/power/pm8921-charger.c
+++ b/drivers/power/pm8921-charger.c
@@ -2541,12 +2541,98 @@
return -EINVAL;
}
+static void pm8921_chg_force_19p2mhz_clk(struct pm8921_chg_chip *chip)
+{
+ int err;
+ u8 temp;
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD3;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD5;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ udelay(183);
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD0;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+ udelay(32);
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD3;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+}
+
+static void pm8921_chg_set_hw_clk_switching(struct pm8921_chg_chip *chip)
+{
+ int err;
+ u8 temp;
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD0;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+}
+
#define ENUM_TIMER_STOP_BIT BIT(1)
#define BOOT_DONE_BIT BIT(6)
#define CHG_BATFET_ON_BIT BIT(3)
#define CHG_VCP_EN BIT(0)
#define CHG_BAT_TEMP_DIS_BIT BIT(2)
#define SAFE_CURRENT_MA 1500
+#define VREF_BATT_THERM_FORCE_ON BIT(7)
static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip)
{
int rc;
@@ -2736,6 +2822,13 @@
/* Disable EOC FSM processing */
pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x91);
+ pm8921_chg_force_19p2mhz_clk(chip);
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON,
+ VREF_BATT_THERM_FORCE_ON);
+ if (rc)
+ pr_err("Failed to Force Vref therm rc=%d\n", rc);
+
rc = pm_chg_charge_dis(chip, charging_disabled);
if (rc) {
pr_err("Failed to disable CHG_CHARGE_DIS bit rc=%d\n", rc);
@@ -2918,6 +3011,32 @@
}
}
+static int pm8921_charger_suspend_noirq(struct device *dev)
+{
+ int rc;
+ struct pm8921_chg_chip *chip = dev_get_drvdata(dev);
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, 0);
+ if (rc)
+ pr_err("Failed to Force Vref therm off rc=%d\n", rc);
+ pm8921_chg_set_hw_clk_switching(chip);
+ return 0;
+}
+
+static int pm8921_charger_resume_noirq(struct device *dev)
+{
+ int rc;
+ struct pm8921_chg_chip *chip = dev_get_drvdata(dev);
+
+ pm8921_chg_force_19p2mhz_clk(chip);
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON,
+ VREF_BATT_THERM_FORCE_ON);
+ if (rc)
+ pr_err("Failed to Force Vref therm on rc=%d\n", rc);
+ return 0;
+}
+
static int pm8921_charger_resume(struct device *dev)
{
int rc;
@@ -2956,6 +3075,7 @@
pm8921_chg_enable_irq(chip, LOOP_CHANGE_IRQ);
enable_irq_wake(chip->pmic_chg_irq[LOOP_CHANGE_IRQ]);
}
+
return 0;
}
static int __devinit pm8921_charger_probe(struct platform_device *pdev)
@@ -3116,6 +3236,8 @@
}
static const struct dev_pm_ops pm8921_pm_ops = {
.suspend = pm8921_charger_suspend,
+ .suspend_noirq = pm8921_charger_suspend_noirq,
+ .resume_noirq = pm8921_charger_resume_noirq,
.resume = pm8921_charger_resume,
};
static struct platform_driver pm8921_charger_driver = {