power: pm8921-charger: fix reverse boosting
The reverse boosting of PM8921 may cause the USB to be
deemed as present even though it has been unplugged. This
causes unplug detection to fail and also break charging.
Fix this by disabling input regulation (vin_min) as well
as opening the OVP fets to allow capacitors to discharge.
Disabling input regulation nullifies reverse boosting. The usbin
voltage then is stuck at vph_pwr minus some diode drop across
fets. The CHG_GONE_IRQ is indicative of this condition.
This voltage is further reduced by opening up the ovp fets.
Note that before disabling input regulation we reduce the current
drawn from the usb to 500mA. It is expected that at such low current
the charger will not drop its voltage while input regulation remains
disabled.
The condition for detecting reverse boosting remains the same - input
regulation is active and the battery is supplying current instead of
getting charged.
Parameters allowing this workaround to be fine tuned:
param_vin_disable_counter:
max number of times vin_min is disabled, each iteration disables
input regulation for 20 milliseconds. The loop is exited if a
successful usb removal is detected.
param_open_ovp_counter:
number of times OVP fet is forced opened in steps of 20 millisecond.
The loop exits if a valid usb removal is detected.
Change-Id: Ic9e9ca4ef8585f70700053153c825e4f0df645b3
Signed-off-by: David Keitel <dkeitel@codeaurora.org>
diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c
index aca724a..65e923e 100644
--- a/drivers/power/pm8921-charger.c
+++ b/drivers/power/pm8921-charger.c
@@ -256,9 +256,9 @@
int thermal_levels;
struct delayed_work update_heartbeat_work;
struct delayed_work eoc_work;
- struct delayed_work unplug_wrkarnd_restore_work;
+ struct work_struct unplug_ovp_fet_open_work;
struct delayed_work unplug_check_work;
- struct wake_lock unplug_wrkarnd_restore_wake_lock;
+ struct wake_lock unplug_ovp_fet_open_wake_lock;
struct wake_lock eoc_wake_lock;
enum pm8921_chg_cold_thr cold_thr;
enum pm8921_chg_hot_thr hot_thr;
@@ -841,6 +841,56 @@
temp);
}
+static void disable_input_voltage_regulation(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70);
+ if (rc) {
+ pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp);
+ if (rc) {
+ pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ /* set the input voltage disable bit and the write bit */
+ temp |= 0x81;
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc);
+ return;
+ }
+}
+
+static void enable_input_voltage_regulation(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70);
+ if (rc) {
+ pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp);
+ if (rc) {
+ pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ /* unset the input voltage disable bit */
+ temp &= 0xFE;
+ /* set the write bit */
+ temp |= 0x80;
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc);
+ return;
+ }
+}
+
static int64_t read_battery_id(struct pm8921_chg_chip *chip)
{
int rc;
@@ -1623,7 +1673,11 @@
schedule_delayed_work(&chip->unplug_check_work,
round_jiffies_relative(msecs_to_jiffies
(UNPLUG_CHECK_WAIT_PERIOD_MS)));
+ pm8921_chg_enable_irq(chip, CHG_GONE_IRQ);
+ } else {
+ pm8921_chg_disable_irq(chip, CHG_GONE_IRQ);
}
+ enable_input_voltage_regulation(chip);
bms_notify_check(chip);
}
@@ -1702,26 +1756,94 @@
wake_lock(&chip->eoc_wake_lock);
}
-#define WRITE_BANK_4 0xC0
-static void unplug_wrkarnd_restore_worker(struct work_struct *work)
+static void turn_off_usb_ovp_fet(struct pm8921_chg_chip *chip)
{
u8 temp;
int rc;
- struct delayed_work *dwork = to_delayed_work(work);
- struct pm8921_chg_chip *chip = container_of(dwork,
- struct pm8921_chg_chip,
- unplug_wrkarnd_restore_work);
- pr_debug("restoring vin_min to %d mV\n", chip->vin_min);
- rc = pm_chg_vinmin_set(the_chip, chip->vin_min);
-
- temp = WRITE_BANK_4 | 0xA;
- rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp);
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0x30);
if (rc) {
- pr_err("Error %d writing %d to addr %d\n", rc,
- temp, CHG_BUCK_CTRL_TEST3);
+ pr_err("Failed to write 0x30 to USB_OVP_TEST rc = %d\n", rc);
+ return;
}
- wake_unlock(&chip->unplug_wrkarnd_restore_wake_lock);
+ rc = pm8xxx_readb(chip->dev->parent, USB_OVP_TEST, &temp);
+ if (rc) {
+ pr_err("Failed to read from USB_OVP_TEST rc = %d\n", rc);
+ return;
+ }
+ /* set ovp fet disable bit and the write bit */
+ temp |= 0x81;
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x USB_OVP_TEST rc=%d\n", temp, rc);
+ return;
+ }
+}
+
+static void turn_on_usb_ovp_fet(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0x30);
+ if (rc) {
+ pr_err("Failed to write 0x30 to USB_OVP_TEST rc = %d\n", rc);
+ return;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, USB_OVP_TEST, &temp);
+ if (rc) {
+ pr_err("Failed to read from USB_OVP_TEST rc = %d\n", rc);
+ return;
+ }
+ /* unset ovp fet disable bit and set the write bit */
+ temp &= 0xFE;
+ temp |= 0x80;
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x to USB_OVP_TEST rc = %d\n",
+ temp, rc);
+ return;
+ }
+}
+
+static int param_open_ovp_counter = 10;
+module_param(param_open_ovp_counter, int, 0644);
+
+#define WRITE_BANK_4 0xC0
+#define USB_OVP_DEBOUNCE_TIME 0x06
+static void unplug_ovp_fet_open_worker(struct work_struct *work)
+{
+ struct pm8921_chg_chip *chip = container_of(work,
+ struct pm8921_chg_chip,
+ unplug_ovp_fet_open_work);
+ int chg_gone, usb_chg_plugged_in;
+ int count = 0;
+
+ while (count++ < param_open_ovp_counter) {
+ pm_chg_masked_write(chip, USB_OVP_CONTROL,
+ USB_OVP_DEBOUNCE_TIME, 0x0);
+ usleep(10);
+ usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
+ chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ);
+ pr_debug("OVP FET count = %d chg_gone=%d, usb_valid = %d\n",
+ count, chg_gone, usb_chg_plugged_in);
+
+ /* note usb_chg_plugged_in=0 => chg_gone=1 */
+ if (chg_gone == 1 && usb_chg_plugged_in == 1) {
+ pr_debug("since chg_gone = 1 dis ovp_fet for 20msec\n");
+ turn_off_usb_ovp_fet(chip);
+
+ msleep(20);
+
+ turn_on_usb_ovp_fet(chip);
+ } else {
+ pm_chg_masked_write(chip, USB_OVP_CONTROL,
+ USB_OVP_DEBOUNCE_TIME, 0x1);
+ pr_debug("Exit count=%d chg_gone=%d, usb_valid=%d\n",
+ count, chg_gone, usb_chg_plugged_in);
+ return;
+ }
+ }
}
static irqreturn_t usbin_valid_irq_handler(int irq, void *data)
{
@@ -1799,8 +1921,7 @@
static irqreturn_t vcp_irq_handler(int irq, void *data)
{
- pr_warning("VCP triggered BATDET forced on\n");
- pr_debug("state_changed_to=%d\n", pm_chg_get_fsm_state(data));
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
return IRQ_HANDLED;
}
@@ -1867,6 +1988,29 @@
return IRQ_HANDLED;
}
+static int param_vin_disable_counter = 5;
+module_param(param_vin_disable_counter, int, 0644);
+
+static void attempt_reverse_boost_fix(struct pm8921_chg_chip *chip,
+ int count, int usb_ma)
+{
+ pm8921_charger_vbus_draw(500);
+ pr_debug("count = %d iusb=500mA\n", count);
+ disable_input_voltage_regulation(chip);
+ pr_debug("count = %d disable_input_regulation\n", count);
+
+ msleep(20);
+
+ pr_debug("count = %d end sleep 20ms chg_gone=%d, usb_valid = %d\n",
+ count,
+ pm_chg_get_rt_status(chip, CHG_GONE_IRQ),
+ is_usb_chg_plugged_in(chip));
+ pr_debug("count = %d restoring input regulation and usb_ma = %d\n",
+ count, usb_ma);
+ enable_input_voltage_regulation(chip);
+ pm8921_charger_vbus_draw(usb_ma);
+}
+
#define VIN_ACTIVE_BIT BIT(0)
#define UNPLUG_WRKARND_RESTORE_WAIT_PERIOD_US 200
#define VIN_MIN_INCREASE_MV 100
@@ -1877,6 +2021,8 @@
struct pm8921_chg_chip, unplug_check_work);
u8 reg_loop;
int ibat, usb_chg_plugged_in;
+ int usb_ma;
+ int chg_gone = 0;
usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
if (!usb_chg_plugged_in) {
@@ -1888,8 +2034,25 @@
);
return;
}
+
+ pm_chg_iusbmax_get(chip, &usb_ma);
+ if (usb_ma == 500) {
+ pr_debug("Stopping Unplug Check Worker since USB == 500mA\n");
+ disable_input_voltage_regulation(chip);
+ return;
+ }
+
+ if (usb_ma <= 100) {
+ pr_debug(
+ "Unenumerated yet or suspended usb_ma = %d skipping\n",
+ usb_ma);
+ goto check_again_later;
+ }
+ if (pm8921_chg_is_enabled(chip, CHG_GONE_IRQ))
+ pr_debug("chg gone irq is enabled\n");
+
reg_loop = pm_chg_get_regulation_loop(chip);
- pr_debug("reg_loop=0x%x\n", reg_loop);
+ pr_debug("reg_loop=0x%x usb_ma = %d\n", reg_loop, usb_ma);
if (reg_loop & VIN_ACTIVE_BIT) {
ibat = get_prop_batt_current(chip);
@@ -1897,28 +2060,28 @@
pr_debug("ibat = %d fsm = %d reg_loop = 0x%x\n",
ibat, pm_chg_get_fsm_state(chip), reg_loop);
if (ibat > 0) {
- int err;
- u8 temp;
+ int count = 0;
- temp = WRITE_BANK_4 | 0xE;
- err = pm8xxx_writeb(chip->dev->parent,
- CHG_BUCK_CTRL_TEST3, temp);
- if (err) {
- pr_err("Error %d writing %d to addr %d\n", err,
- temp, CHG_BUCK_CTRL_TEST3);
+ while (count++ < param_vin_disable_counter
+ && usb_chg_plugged_in == 1) {
+ attempt_reverse_boost_fix(chip, count, usb_ma);
+ usb_chg_plugged_in
+ = is_usb_chg_plugged_in(chip);
}
-
- pm_chg_vinmin_set(chip,
- chip->vin_min + VIN_MIN_INCREASE_MV);
-
- wake_lock(&chip->unplug_wrkarnd_restore_wake_lock);
- schedule_delayed_work(
- &chip->unplug_wrkarnd_restore_work,
- round_jiffies_relative(usecs_to_jiffies
- (UNPLUG_WRKARND_RESTORE_WAIT_PERIOD_US)));
}
}
+ usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
+ chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ);
+
+ if (chg_gone == 1 && usb_chg_plugged_in == 1) {
+ /* run the worker directly */
+ pr_debug(" ver5 step: chg_gone=%d, usb_valid = %d\n",
+ chg_gone, usb_chg_plugged_in);
+ schedule_work(&chip->unplug_ovp_fet_open_work);
+ }
+
+check_again_later:
schedule_delayed_work(&chip->unplug_check_work,
round_jiffies_relative(msecs_to_jiffies
(UNPLUG_CHECK_WAIT_PERIOD_MS)));
@@ -2008,6 +2171,13 @@
static irqreturn_t chg_gone_irq_handler(int irq, void *data)
{
struct pm8921_chg_chip *chip = data;
+ int chg_gone, usb_chg_plugged_in;
+
+ usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
+ chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ);
+
+ pr_debug("chg_gone=%d, usb_valid = %d\n", chg_gone, usb_chg_plugged_in);
+ schedule_work(&chip->unplug_ovp_fet_open_work);
pr_debug("Chg gone fsm_state=%d\n", pm_chg_get_fsm_state(data));
power_supply_changed(&chip->batt_psy);
@@ -2540,6 +2710,7 @@
schedule_delayed_work(&chip->unplug_check_work,
round_jiffies_relative(msecs_to_jiffies
(UNPLUG_CHECK_WAIT_PERIOD_MS)));
+ pm8921_chg_enable_irq(chip, CHG_GONE_IRQ);
}
pm8921_chg_enable_irq(chip, DCIN_VALID_IRQ);
@@ -2621,7 +2792,8 @@
CHG_IRQ(CHGHOT_IRQ, IRQF_TRIGGER_RISING, chghot_irq_handler),
CHG_IRQ(BATTTEMP_COLD_IRQ, IRQF_TRIGGER_RISING,
batttemp_cold_irq_handler),
- CHG_IRQ(CHG_GONE_IRQ, IRQF_TRIGGER_RISING, chg_gone_irq_handler),
+ CHG_IRQ(CHG_GONE_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ chg_gone_irq_handler),
CHG_IRQ(BAT_TEMP_OK_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
bat_temp_ok_irq_handler),
CHG_IRQ(COARSE_DET_LOW_IRQ, IRQF_TRIGGER_RISING,
@@ -3320,11 +3492,11 @@
the_chip = chip;
wake_lock_init(&chip->eoc_wake_lock, WAKE_LOCK_SUSPEND, "pm8921_eoc");
- wake_lock_init(&chip->unplug_wrkarnd_restore_wake_lock,
+ wake_lock_init(&chip->unplug_ovp_fet_open_wake_lock,
WAKE_LOCK_SUSPEND, "pm8921_unplug_wrkarnd");
INIT_DELAYED_WORK(&chip->eoc_work, eoc_worker);
- INIT_DELAYED_WORK(&chip->unplug_wrkarnd_restore_work,
- unplug_wrkarnd_restore_worker);
+ INIT_WORK(&chip->unplug_ovp_fet_open_work,
+ unplug_ovp_fet_open_worker);
INIT_DELAYED_WORK(&chip->unplug_check_work, unplug_check_worker);
rc = request_irqs(chip, pdev);