From acf57c86caa0fbc79e29cf64508b05dee75fecb2 Mon Sep 17 00:00:00 2001 From: David Keitel Date: Wed, 7 Mar 2012 18:48:50 -0800 Subject: [PATCH] power: pm8921-charger: add adaptive current limiting Adaptive current limiting is a method which tries to maximize the available usb current by slowly stepping up the allowed maximum usb current and verifying that the connected wall-charger is collapsing. If the USB driver determines that 1500 mA are available the starting current on usb is 500 mA slowly stepping up unless * VIN_MIN is reached (charger is reaching its capacity) * USBIN goes low for less than 50 ms (charger collapsed) If one of the two conditions apply then reduce the target maximum current to the current maximum usb current and stop trying to stepping up. If the conditions occur again decrease both the target and current usb maximum setting. CRs-Fixed: 339022 Change-Id: Icdb46e6346b6b871d81bbacbffe70831dd62d93c Signed-off-by: David Keitel --- drivers/power/pm8921-charger.c | 189 +++++++++++++++++++++++++-------- 1 file changed, 143 insertions(+), 46 deletions(-) diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c index 0d158906483..f6e7e939e7a 100644 --- a/drivers/power/pm8921-charger.c +++ b/drivers/power/pm8921-charger.c @@ -258,13 +258,21 @@ struct pm8921_chg_chip { struct delayed_work eoc_work; struct work_struct unplug_ovp_fet_open_work; struct delayed_work unplug_check_work; + struct delayed_work vin_collapse_check_work; struct wake_lock eoc_wake_lock; enum pm8921_chg_cold_thr cold_thr; enum pm8921_chg_hot_thr hot_thr; int rconn_mohm; }; -static int usb_max_current; +/* user space parameter to limit usb current */ +static unsigned int usb_max_current; +/* + * usb_target_ma is used for wall charger + * adaptive input current limiting only. Use + * pm_iusbmax_get() to get current maximum usb current setting. + */ +static int usb_target_ma; static int charging_disabled; static int thermal_mitigation; @@ -649,18 +657,17 @@ static int pm_chg_iterm_get(struct pm8921_chg_chip *chip, int *chg_current) struct usb_ma_limit_entry { int usb_ma; - u8 chg_iusb_value; }; static struct usb_ma_limit_entry usb_ma_table[] = { - {100, 0}, - {500, 1}, - {700, 2}, - {850, 3}, - {900, 4}, - {1100, 5}, - {1300, 6}, - {1500, 7}, + {100}, + {500}, + {700}, + {850}, + {900}, + {1100}, + {1300}, + {1500}, }; #define PM8921_CHG_IUSB_MASK 0x1C @@ -682,7 +689,7 @@ static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int reg_val) static int pm_chg_iusbmax_get(struct pm8921_chg_chip *chip, int *mA) { u8 temp; - int i, rc; + int rc; *mA = 0; rc = pm8xxx_readb(chip->dev->parent, PBL_ACCESS2, &temp); @@ -692,10 +699,7 @@ static int pm_chg_iusbmax_get(struct pm8921_chg_chip *chip, int *mA) } temp &= PM8921_CHG_IUSB_MASK; temp = temp >> 2; - for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) { - if (usb_ma_table[i].chg_iusb_value == temp) - *mA = usb_ma_table[i].usb_ma; - } + *mA = usb_ma_table[temp].usb_ma; return rc; } @@ -1071,8 +1075,6 @@ static int pm_power_get_property_mains(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { - int current_max; - /* Check if called before init */ if (!the_chip) return -EINVAL; @@ -1101,8 +1103,7 @@ static int pm_power_get_property_mains(struct power_supply *psy, } /* USB with max current greater than 500 mA connected */ - pm_chg_iusbmax_get(the_chip, ¤t_max); - if (current_max > USB_WALL_THRESHOLD_MA) + if (usb_target_ma > USB_WALL_THRESHOLD_MA) val->intval = is_usb_chg_plugged_in(the_chip); return 0; @@ -1421,19 +1422,11 @@ static void __pm8921_charger_vbus_draw(unsigned int mA) { int i, rc; - if (usb_max_current && mA > usb_max_current) { - pr_warn("restricting usb current to %d instead of %d\n", - usb_max_current, mA); - mA = usb_max_current; - } - if (mA > 0 && mA <= 2) { usb_chg_current = 0; - rc = pm_chg_iusbmax_set(the_chip, - usb_ma_table[0].chg_iusb_value); + rc = pm_chg_iusbmax_set(the_chip, 0); if (rc) { - pr_err("unable to set iusb to %d rc = %d\n", - usb_ma_table[0].chg_iusb_value, rc); + pr_err("unable to set iusb to %d rc = %d\n", 0, rc); } rc = pm_chg_usb_suspend_enable(the_chip, 1); if (rc) @@ -1448,11 +1441,9 @@ static void __pm8921_charger_vbus_draw(unsigned int mA) } if (i < 0) i = 0; - rc = pm_chg_iusbmax_set(the_chip, - usb_ma_table[i].chg_iusb_value); + rc = pm_chg_iusbmax_set(the_chip, i); if (rc) { - pr_err("unable to set iusb to %d rc = %d\n", - usb_ma_table[i].chg_iusb_value, rc); + pr_err("unable to set iusb to %d rc = %d\n", i, rc); } } } @@ -1463,15 +1454,30 @@ void pm8921_charger_vbus_draw(unsigned int mA) unsigned long flags; pr_debug("Enter charge=%d\n", mA); + + if (usb_max_current && mA > usb_max_current) { + pr_warn("restricting usb current to %d instead of %d\n", + usb_max_current, mA); + mA = usb_max_current; + } + if (usb_target_ma == 0 && mA > USB_WALL_THRESHOLD_MA) + usb_target_ma = mA; + spin_lock_irqsave(&vbus_lock, flags); if (the_chip) { - __pm8921_charger_vbus_draw(mA); + if (mA > USB_WALL_THRESHOLD_MA) + __pm8921_charger_vbus_draw(USB_WALL_THRESHOLD_MA); + else + __pm8921_charger_vbus_draw(mA); } else { /* * called before pmic initialized, * save this value and use it at probe */ - usb_chg_current = mA; + if (mA > USB_WALL_THRESHOLD_MA) + usb_chg_current = USB_WALL_THRESHOLD_MA; + else + usb_chg_current = mA; } spin_unlock_irqrestore(&vbus_lock, flags); } @@ -1713,6 +1719,8 @@ static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip) (UNPLUG_CHECK_WAIT_PERIOD_MS))); pm8921_chg_enable_irq(chip, CHG_GONE_IRQ); } else { + /* USB unplugged reset target current */ + usb_target_ma = 0; pm8921_chg_disable_irq(chip, CHG_GONE_IRQ); } enable_input_voltage_regulation(chip); @@ -1883,9 +1891,73 @@ static void unplug_ovp_fet_open_worker(struct work_struct *work) } } } + +static int find_usb_ma_value(int value) +{ + int i; + + for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) { + if (value >= usb_ma_table[i].usb_ma) + break; + } + + return i; +} + +static void decrease_usb_ma_value(int *value) +{ + int i; + + if (value) { + i = find_usb_ma_value(*value); + if (i > 0) + i--; + *value = usb_ma_table[i].usb_ma; + } +} + +static void increase_usb_ma_value(int *value) +{ + int i; + + if (value) { + i = find_usb_ma_value(*value); + + if (i < (ARRAY_SIZE(usb_ma_table) - 1)) + i++; + *value = usb_ma_table[i].usb_ma; + } +} + +static void vin_collapse_check_worker(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct pm8921_chg_chip *chip = container_of(dwork, + struct pm8921_chg_chip, vin_collapse_check_work); + + /* AICL only for wall-chargers */ + if (is_usb_chg_plugged_in(chip) && + usb_target_ma > USB_WALL_THRESHOLD_MA) { + /* decrease usb_target_ma */ + decrease_usb_ma_value(&usb_target_ma); + /* reset here, increase in unplug_check_worker */ + __pm8921_charger_vbus_draw(USB_WALL_THRESHOLD_MA); + pr_debug("usb_now=%d, usb_target = %d\n", + USB_WALL_THRESHOLD_MA, usb_target_ma); + } else { + handle_usb_insertion_removal(chip); + } +} + +#define VIN_MIN_COLLAPSE_CHECK_MS 50 static irqreturn_t usbin_valid_irq_handler(int irq, void *data) { - handle_usb_insertion_removal(data); + if (usb_target_ma) + schedule_delayed_work(&the_chip->vin_collapse_check_work, + round_jiffies_relative(msecs_to_jiffies + (VIN_MIN_COLLAPSE_CHECK_MS))); + else + handle_usb_insertion_removal(data); return IRQ_HANDLED; } @@ -2032,7 +2104,7 @@ 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); + __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); @@ -2046,7 +2118,7 @@ static void attempt_reverse_boost_fix(struct pm8921_chg_chip *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); + __pm8921_charger_vbus_draw(usb_ma); } #define VIN_ACTIVE_BIT BIT(0) @@ -2058,10 +2130,10 @@ static void unplug_check_worker(struct work_struct *work) struct pm8921_chg_chip *chip = container_of(dwork, struct pm8921_chg_chip, unplug_check_work); u8 reg_loop; - int ibat, usb_chg_plugged_in; - int usb_ma; + int ibat, usb_chg_plugged_in, usb_ma; int chg_gone = 0; + reg_loop = 0; usb_chg_plugged_in = is_usb_chg_plugged_in(chip); if (!usb_chg_plugged_in) { pr_debug("Stopping Unplug Check Worker since USB is removed" @@ -2074,7 +2146,7 @@ static void unplug_check_worker(struct work_struct *work) } pm_chg_iusbmax_get(chip, &usb_ma); - if (usb_ma == 500) { + if (usb_ma == 500 && !usb_target_ma) { pr_debug("Stopping Unplug Check Worker since USB == 500mA\n"); disable_input_voltage_regulation(chip); return; @@ -2092,6 +2164,18 @@ static void unplug_check_worker(struct work_struct *work) reg_loop = pm_chg_get_regulation_loop(chip); pr_debug("reg_loop=0x%x usb_ma = %d\n", reg_loop, usb_ma); + if (reg_loop & VIN_ACTIVE_BIT) { + decrease_usb_ma_value(&usb_ma); + usb_target_ma = usb_ma; + /* end AICL here */ + __pm8921_charger_vbus_draw(usb_ma); + pr_debug("usb_now=%d, usb_target = %d\n", + usb_ma, usb_target_ma); + } + + reg_loop = pm_chg_get_regulation_loop(chip); + 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); @@ -2119,7 +2203,19 @@ static void unplug_check_worker(struct work_struct *work) schedule_work(&chip->unplug_ovp_fet_open_work); } + if (!(reg_loop & VIN_ACTIVE_BIT)) { + /* only increase iusb_max if vin loop not active */ + if (usb_ma < usb_target_ma) { + increase_usb_ma_value(&usb_ma); + __pm8921_charger_vbus_draw(usb_ma); + pr_debug("usb_now=%d, usb_target = %d\n", + usb_ma, usb_target_ma); + } else { + usb_target_ma = usb_ma; + } + } check_again_later: + /* schedule to check again later */ schedule_delayed_work(&chip->unplug_check_work, round_jiffies_relative(msecs_to_jiffies (UNPLUG_CHECK_WAIT_PERIOD_MS))); @@ -2809,8 +2905,8 @@ static int set_usb_max_current(const char *val, struct kernel_param *kp) } return -EINVAL; } -module_param_call(usb_max_current, set_usb_max_current, param_get_uint, - &usb_max_current, 0644); +module_param_call(usb_max_current, set_usb_max_current, + param_get_uint, &usb_max_current, 0644); static void free_irqs(struct pm8921_chg_chip *chip) { @@ -3132,10 +3228,9 @@ static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) } /* init with the lowest USB current */ - rc = pm_chg_iusbmax_set(chip, usb_ma_table[0].chg_iusb_value); + rc = pm_chg_iusbmax_set(chip, 0); if (rc) { - pr_err("Failed to set usb max to %d rc=%d\n", - usb_ma_table[0].chg_iusb_value, rc); + pr_err("Failed to set usb max to %d rc=%d\n", 0, rc); return rc; } @@ -3628,6 +3723,8 @@ static int __devinit pm8921_charger_probe(struct platform_device *pdev) wake_lock_init(&chip->eoc_wake_lock, WAKE_LOCK_SUSPEND, "pm8921_eoc"); INIT_DELAYED_WORK(&chip->eoc_work, eoc_worker); + INIT_DELAYED_WORK(&chip->vin_collapse_check_work, + vin_collapse_check_worker); INIT_WORK(&chip->unplug_ovp_fet_open_work, unplug_ovp_fet_open_worker); INIT_DELAYED_WORK(&chip->unplug_check_work, unplug_check_worker);