summaryrefslogtreecommitdiffstats
path: root/drivers/power/ab8500_charger.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/ab8500_charger.c')
-rw-r--r--drivers/power/ab8500_charger.c617
1 files changed, 472 insertions, 145 deletions
diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c
index d5a8bdadb49a..24b30b7ea5ca 100644
--- a/drivers/power/ab8500_charger.c
+++ b/drivers/power/ab8500_charger.c
@@ -55,6 +55,7 @@
#define MAIN_CH_INPUT_CURR_SHIFT 4
#define VBUS_IN_CURR_LIM_SHIFT 4
+#define AUTO_VBUS_IN_CURR_LIM_SHIFT 4
#define LED_INDICATOR_PWM_ENA 0x01
#define LED_INDICATOR_PWM_DIS 0x00
@@ -88,11 +89,14 @@
/* Step up/down delay in us */
#define STEP_UDELAY 1000
-/* Wait for enumeration before charging in ms */
-#define WAIT_FOR_USB_ENUMERATION 5 * 1000
-
#define CHARGER_STATUS_POLL 10 /* in ms */
+#define CHG_WD_INTERVAL (60 * HZ)
+
+#define AB8500_SW_CONTROL_FALLBACK 0x03
+/* Wait for enumeration before charing in us */
+#define WAIT_ACA_RID_ENUMERATION (5 * 1000)
+
/* UsbLineStatus register - usb types */
enum ab8500_charger_link_status {
USB_STAT_NOT_CONFIGURED,
@@ -181,12 +185,14 @@ struct ab8500_charger_event_flags {
bool usbchargernotok;
bool chgwdexp;
bool vbus_collapse;
+ bool vbus_drop_end;
};
struct ab8500_charger_usb_state {
- bool usb_changed;
int usb_current;
+ int usb_current_tmp;
enum ab8500_usb_state state;
+ enum ab8500_usb_state state_tmp;
spinlock_t usb_lock;
};
@@ -207,6 +213,11 @@ struct ab8500_charger_usb_state {
* @usb_device_is_unrecognised USB device is unrecognised by the hardware
* @autopower Indicate if we should have automatic pwron after pwrloss
* @autopower_cfg platform specific power config support for "pwron after pwrloss"
+ * @invalid_charger_detect_state State when forcing AB to use invalid charger
+ * @is_usb_host: Indicate if last detected USB type is host
+ * @is_aca_rid: Incicate if accessory is ACA type
+ * @current_stepping_sessions:
+ * Counter for current stepping sessions
* @parent: Pointer to the struct ab8500
* @gpadc: Pointer to the struct gpadc
* @bm: Platform specific battery management information
@@ -218,12 +229,13 @@ struct ab8500_charger_usb_state {
* @usb: Structure that holds the USB charger properties
* @regu: Pointer to the struct regulator
* @charger_wq: Work queue for the IRQs and checking HW state
+ * @usb_ipt_crnt_lock: Lock to protect VBUS input current setting from mutuals
+ * @pm_lock: Lock to prevent system to suspend
* @check_vbat_work Work for checking vbat threshold to adjust vbus current
* @check_hw_failure_work: Work for checking HW state
* @check_usbchgnotok_work: Work for checking USB charger not ok status
* @kick_wd_work: Work for kicking the charger watchdog in case
* of ABB rev 1.* due to the watchog logic bug
- * @attach_work: Work for checking the usb enumeration
* @ac_charger_attached_work: Work for checking if AC charger is still
* connected
* @usb_charger_attached_work: Work for checking if USB charger is still
@@ -232,6 +244,8 @@ struct ab8500_charger_usb_state {
* @detect_usb_type_work: Work for detecting the USB type connected
* @usb_link_status_work: Work for checking the new USB link status
* @usb_state_changed_work: Work for checking USB state
+ * @attach_work: Work for detecting USB type
+ * @vbus_drop_end_work: Work for detecting VBUS drop end
* @check_main_thermal_prot_work:
* Work for checking Main thermal status
* @check_usb_thermal_prot_work:
@@ -251,6 +265,10 @@ struct ab8500_charger {
bool usb_device_is_unrecognised;
bool autopower;
bool autopower_cfg;
+ int invalid_charger_detect_state;
+ bool is_usb_host;
+ int is_aca_rid;
+ atomic_t current_stepping_sessions;
struct ab8500 *parent;
struct ab8500_gpadc *gpadc;
struct abx500_bm_data *bm;
@@ -262,17 +280,19 @@ struct ab8500_charger {
struct ab8500_charger_info usb;
struct regulator *regu;
struct workqueue_struct *charger_wq;
+ struct mutex usb_ipt_crnt_lock;
struct delayed_work check_vbat_work;
struct delayed_work check_hw_failure_work;
struct delayed_work check_usbchgnotok_work;
struct delayed_work kick_wd_work;
+ struct delayed_work usb_state_changed_work;
struct delayed_work attach_work;
struct delayed_work ac_charger_attached_work;
struct delayed_work usb_charger_attached_work;
+ struct delayed_work vbus_drop_end_work;
struct work_struct ac_work;
struct work_struct detect_usb_type_work;
struct work_struct usb_link_status_work;
- struct work_struct usb_state_changed_work;
struct work_struct check_main_thermal_prot_work;
struct work_struct check_usb_thermal_prot_work;
struct usb_phy *usb_phy;
@@ -308,42 +328,58 @@ static enum power_supply_property ab8500_charger_usb_props[] = {
static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di,
bool fallback)
{
+ u8 val;
u8 reg;
+ u8 bank;
+ u8 bit;
int ret;
dev_dbg(di->dev, "SW Fallback: %d\n", fallback);
+ if (is_ab8500(di->parent)) {
+ bank = 0x15;
+ reg = 0x0;
+ bit = 3;
+ } else {
+ bank = AB8500_SYS_CTRL1_BLOCK;
+ reg = AB8500_SW_CONTROL_FALLBACK;
+ bit = 0;
+ }
+
/* read the register containing fallback bit */
- ret = abx500_get_register_interruptible(di->dev, 0x15, 0x00, &reg);
- if (ret) {
- dev_err(di->dev, "%d write failed\n", __LINE__);
+ ret = abx500_get_register_interruptible(di->dev, bank, reg, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%d read failed\n", __LINE__);
return;
}
- /* enable the OPT emulation registers */
- ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2);
- if (ret) {
- dev_err(di->dev, "%d write failed\n", __LINE__);
- return;
+ if (is_ab8500(di->parent)) {
+ /* enable the OPT emulation registers */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ goto disable_otp;
+ }
}
if (fallback)
- reg |= 0x8;
+ val |= (1 << bit);
else
- reg &= ~0x8;
+ val &= ~(1 << bit);
/* write back the changed fallback bit value to register */
- ret = abx500_set_register_interruptible(di->dev, 0x15, 0x00, reg);
+ ret = abx500_set_register_interruptible(di->dev, bank, reg, val);
if (ret) {
dev_err(di->dev, "%d write failed\n", __LINE__);
- return;
}
- /* disable the set OTP registers again */
- ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0);
- if (ret) {
- dev_err(di->dev, "%d write failed\n", __LINE__);
- return;
+disable_otp:
+ if (is_ab8500(di->parent)) {
+ /* disable the set OTP registers again */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ }
}
}
@@ -546,6 +582,7 @@ static int ab8500_charger_usb_cv(struct ab8500_charger *di)
/**
* ab8500_charger_detect_chargers() - Detect the connected chargers
* @di: pointer to the ab8500_charger structure
+ * @probe: if probe, don't delay and wait for HW
*
* Returns the type of charger connected.
* For USB it will not mean we can actually charge from it
@@ -559,7 +596,7 @@ static int ab8500_charger_usb_cv(struct ab8500_charger *di)
* USB_PW_CONN if the USB power supply is connected
* AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected
*/
-static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
+static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
{
int result = NO_PW_CONN;
int ret;
@@ -577,13 +614,25 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
result = AC_PW_CONN;
/* Check for USB charger */
+
+ if (!probe) {
+ /*
+ * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100
+ * when disconnecting ACA even though no
+ * charger was connected. Try waiting a little
+ * longer than the 100 ms of VBUS_DET_DBNC100...
+ */
+ msleep(110);
+ }
ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_CH_USBCH_STAT1_REG, &val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
-
+ dev_dbg(di->dev,
+ "%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__,
+ val);
if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100))
result |= USB_PW_CONN;
@@ -606,33 +655,47 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
di->usb_device_is_unrecognised = false;
+ /*
+ * Platform only supports USB 2.0.
+ * This means that charging current from USB source
+ * is maximum 500 mA. Every occurence of USB_STAT_*_HOST_*
+ * should set USB_CH_IP_CUR_LVL_0P5.
+ */
+
switch (link_status) {
case USB_STAT_STD_HOST_NC:
case USB_STAT_STD_HOST_C_NS:
case USB_STAT_STD_HOST_C_S:
dev_dbg(di->dev, "USB Type - Standard host is "
- "detected through USB driver\n");
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
+ "detected through USB driver\n");
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ di->is_usb_host = true;
+ di->is_aca_rid = 0;
break;
case USB_STAT_HOST_CHG_HS_CHIRP:
di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
- dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
- di->max_usb_in_curr);
+ di->is_usb_host = true;
+ di->is_aca_rid = 0;
break;
case USB_STAT_HOST_CHG_HS:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ di->is_usb_host = true;
+ di->is_aca_rid = 0;
+ break;
case USB_STAT_ACA_RID_C_HS:
di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9;
- dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
- di->max_usb_in_curr);
+ di->is_usb_host = false;
+ di->is_aca_rid = 0;
break;
case USB_STAT_ACA_RID_A:
/*
* Dedicated charger level minus maximum current accessory
- * can consume (300mA). Closest level is 1100mA
+ * can consume (900mA). Closest level is 500mA
*/
- di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1;
- dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
- di->max_usb_in_curr);
+ dev_dbg(di->dev, "USB_STAT_ACA_RID_A detected\n");
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ di->is_usb_host = false;
+ di->is_aca_rid = 1;
break;
case USB_STAT_ACA_RID_B:
/*
@@ -642,14 +705,24 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3;
dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
di->max_usb_in_curr);
+ di->is_usb_host = false;
+ di->is_aca_rid = 1;
break;
case USB_STAT_HOST_CHG_NM:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ di->is_usb_host = true;
+ di->is_aca_rid = 0;
+ break;
case USB_STAT_DEDICATED_CHG:
- case USB_STAT_ACA_RID_C_NM:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5;
+ di->is_usb_host = false;
+ di->is_aca_rid = 0;
+ break;
case USB_STAT_ACA_RID_C_HS_CHIRP:
+ case USB_STAT_ACA_RID_C_NM:
di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5;
- dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
- di->max_usb_in_curr);
+ di->is_usb_host = false;
+ di->is_aca_rid = 1;
break;
case USB_STAT_NOT_CONFIGURED:
if (di->vbus_detected) {
@@ -659,7 +732,6 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
break;
}
case USB_STAT_HM_IDGND:
- case USB_STAT_NOT_VALID_LINK:
dev_err(di->dev, "USB Type - Charging not allowed\n");
di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
ret = -ENXIO;
@@ -688,6 +760,9 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
di->max_usb_in_curr);
+ case USB_STAT_NOT_VALID_LINK:
+ dev_err(di->dev, "USB Type invalid - try charging anyway\n");
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
break;
default:
@@ -764,6 +839,8 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
ret = abx500_get_register_interruptible(di->dev,
AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG,
&val);
+ dev_dbg(di->dev, "%s AB8500_IT_SOURCE21_REG %x\n",
+ __func__, val);
if (ret < 0) {
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
@@ -779,6 +856,8 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
dev_err(di->dev, "%s ab8500 read failed\n", __func__);
return ret;
}
+ dev_dbg(di->dev, "%s AB8500_USB_LINE_STAT_REG %x\n", __func__,
+ val);
/*
* Until the IT source register is read the UsbLineStatus
* register is not updated, hence doing the same
@@ -1038,69 +1117,125 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
static int ab8500_charger_set_current(struct ab8500_charger *di,
int ich, int reg)
{
- int ret, i;
- int curr_index, prev_curr_index, shift_value;
+ int ret = 0;
+ int auto_curr_index, curr_index, prev_curr_index, shift_value, i;
u8 reg_value;
+ u32 step_udelay;
+ bool no_stepping = false;
+
+ atomic_inc(&di->current_stepping_sessions);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ reg, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s read failed\n", __func__);
+ goto exit_set_current;
+ }
switch (reg) {
case AB8500_MCH_IPT_CURLVL_REG:
shift_value = MAIN_CH_INPUT_CURR_SHIFT;
+ prev_curr_index = (reg_value >> shift_value);
curr_index = ab8500_current_to_regval(ich);
+ step_udelay = STEP_UDELAY;
+ if (!di->ac.charger_connected)
+ no_stepping = true;
break;
case AB8500_USBCH_IPT_CRNTLVL_REG:
shift_value = VBUS_IN_CURR_LIM_SHIFT;
+ prev_curr_index = (reg_value >> shift_value);
curr_index = ab8500_vbus_in_curr_to_regval(ich);
+ step_udelay = STEP_UDELAY * 100;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s read failed\n", __func__);
+ goto exit_set_current;
+ }
+ auto_curr_index =
+ reg_value >> AUTO_VBUS_IN_CURR_LIM_SHIFT;
+
+ dev_dbg(di->dev, "%s Auto VBUS curr is %d mA\n",
+ __func__,
+ ab8500_charger_vbus_in_curr_map[auto_curr_index]);
+
+ prev_curr_index = min(prev_curr_index, auto_curr_index);
+
+ if (!di->usb.charger_connected)
+ no_stepping = true;
break;
case AB8500_CH_OPT_CRNTLVL_REG:
shift_value = 0;
+ prev_curr_index = (reg_value >> shift_value);
curr_index = ab8500_current_to_regval(ich);
+ step_udelay = STEP_UDELAY;
+ if (curr_index && (curr_index - prev_curr_index) > 1)
+ step_udelay *= 100;
+
+ if (!di->usb.charger_connected && !di->ac.charger_connected)
+ no_stepping = true;
+
break;
default:
dev_err(di->dev, "%s current register not valid\n", __func__);
- return -ENXIO;
+ ret = -ENXIO;
+ goto exit_set_current;
}
if (curr_index < 0) {
dev_err(di->dev, "requested current limit out-of-range\n");
- return -ENXIO;
- }
-
- ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
- reg, &reg_value);
- if (ret < 0) {
- dev_err(di->dev, "%s read failed\n", __func__);
- return ret;
+ ret = -ENXIO;
+ goto exit_set_current;
}
- prev_curr_index = (reg_value >> shift_value);
/* only update current if it's been changed */
- if (prev_curr_index == curr_index)
- return 0;
+ if (prev_curr_index == curr_index) {
+ dev_dbg(di->dev, "%s current not changed for reg: 0x%02x\n",
+ __func__, reg);
+ ret = 0;
+ goto exit_set_current;
+ }
dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n",
__func__, ich, reg);
- if (prev_curr_index > curr_index) {
+ if (no_stepping) {
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ reg, (u8)curr_index << shift_value);
+ if (ret)
+ dev_err(di->dev, "%s write failed\n", __func__);
+ } else if (prev_curr_index > curr_index) {
for (i = prev_curr_index - 1; i >= curr_index; i--) {
+ dev_dbg(di->dev, "curr change_1 to: %x for 0x%02x\n",
+ (u8) i << shift_value, reg);
ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER, reg, (u8) i << shift_value);
+ AB8500_CHARGER, reg, (u8)i << shift_value);
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
- return ret;
+ goto exit_set_current;
}
- usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+ if (i != curr_index)
+ usleep_range(step_udelay, step_udelay * 2);
}
} else {
for (i = prev_curr_index + 1; i <= curr_index; i++) {
+ dev_dbg(di->dev, "curr change_2 to: %x for 0x%02x\n",
+ (u8)i << shift_value, reg);
ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER, reg, (u8) i << shift_value);
+ AB8500_CHARGER, reg, (u8)i << shift_value);
if (ret) {
dev_err(di->dev, "%s write failed\n", __func__);
- return ret;
+ goto exit_set_current;
}
- usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+ if (i != curr_index)
+ usleep_range(step_udelay, step_udelay * 2);
}
}
+
+exit_set_current:
+ atomic_dec(&di->current_stepping_sessions);
+
return ret;
}
@@ -1116,6 +1251,7 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
int ich_in)
{
int min_value;
+ int ret;
/* We should always use to lowest current limit */
min_value = min(di->bm->chg_params->usb_curr_max, ich_in);
@@ -1133,8 +1269,14 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
break;
}
- return ab8500_charger_set_current(di, min_value,
+ dev_info(di->dev, "VBUS input current limit set to %d mA\n", min_value);
+
+ mutex_lock(&di->usb_ipt_crnt_lock);
+ ret = ab8500_charger_set_current(di, min_value,
AB8500_USBCH_IPT_CRNTLVL_REG);
+ mutex_unlock(&di->usb_ipt_crnt_lock);
+
+ return ret;
}
/**
@@ -1445,25 +1587,13 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
dev_err(di->dev, "%s write failed\n", __func__);
return ret;
}
- /* USBChInputCurr: current that can be drawn from the usb */
- ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
- if (ret) {
- dev_err(di->dev, "setting USBChInputCurr failed\n");
- return ret;
- }
- /* ChOutputCurentLevel: protected output current */
- ret = ab8500_charger_set_output_curr(di, ich_out);
- if (ret) {
- dev_err(di->dev, "%s "
- "Failed to set ChOutputCurentLevel\n",
- __func__);
- return ret;
- }
/* Check if VBAT overshoot control should be enabled */
if (!di->bm->enable_overshoot)
overshoot = USB_CHG_NO_OVERSHOOT_ENA_N;
/* Enable USB Charger */
+ dev_dbg(di->dev,
+ "Enabling USB with write to AB8500_USBCH_CTRL1_REG\n");
ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot);
if (ret) {
@@ -1476,11 +1606,29 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
if (ret < 0)
dev_err(di->dev, "failed to enable LED\n");
+ di->usb.charger_online = 1;
+
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+ if (ret) {
+ dev_err(di->dev, "setting USBChInputCurr failed\n");
+ return ret;
+ }
+
+ /* ChOutputCurentLevel: protected output current */
+ ret = ab8500_charger_set_output_curr(di, ich_out);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
+
queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ);
- di->usb.charger_online = 1;
} else {
/* Disable USB charging */
+ dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
ret = abx500_set_register_interruptible(di->dev,
AB8500_CHARGER,
AB8500_USBCH_CTRL1_REG, 0);
@@ -1493,7 +1641,21 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
ret = ab8500_charger_led_en(di, false);
if (ret < 0)
dev_err(di->dev, "failed to disable LED\n");
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab8500_charger_set_vbus_in_curr(di, 0);
+ if (ret) {
+ dev_err(di->dev, "setting USBChInputCurr failed\n");
+ return ret;
+ }
+ /* ChOutputCurentLevel: protected output current */
+ ret = ab8500_charger_set_output_curr(di, 0);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to reset ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
di->usb.charger_online = 0;
di->usb.wd_expired = false;
@@ -1776,7 +1938,7 @@ static void ab8500_charger_ac_work(struct work_struct *work)
* synchronously, we have the check if the main charger is
* connected by reading the status register
*/
- ret = ab8500_charger_detect_chargers(di);
+ ret = ab8500_charger_detect_chargers(di, false);
if (ret < 0)
return;
@@ -1887,16 +2049,18 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
* synchronously, we have the check if is
* connected by reading the status register
*/
- ret = ab8500_charger_detect_chargers(di);
+ ret = ab8500_charger_detect_chargers(di, false);
if (ret < 0)
return;
if (!(ret & USB_PW_CONN)) {
- di->vbus_detected = 0;
+ dev_dbg(di->dev, "%s di->vbus_detected = false\n", __func__);
+ di->vbus_detected = false;
ab8500_charger_set_usb_connected(di, false);
ab8500_power_supply_changed(di, &di->usb_chg.psy);
} else {
- di->vbus_detected = 1;
+ dev_dbg(di->dev, "%s di->vbus_detected = true\n", __func__);
+ di->vbus_detected = true;
if (is_ab8500_1p1_or_earlier(di->parent)) {
ret = ab8500_charger_detect_usb_type(di);
@@ -1906,7 +2070,8 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
&di->usb_chg.psy);
}
} else {
- /* For ABB cut2.0 and onwards we have an IRQ,
+ /*
+ * For ABB cut2.0 and onwards we have an IRQ,
* USB_LINK_STATUS that will be triggered when the USB
* link status changes. The exception is USB connected
* during startup. Then we don't get a
@@ -1927,7 +2092,7 @@ static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
}
/**
- * ab8500_charger_usb_link_attach_work() - delayd work to detect USB type
+ * ab8500_charger_usb_link_attach_work() - work to detect USB type
* @work: pointer to the work_struct structure
*
* Detect the type of USB plugged
@@ -1957,7 +2122,9 @@ static void ab8500_charger_usb_link_attach_work(struct work_struct *work)
*/
static void ab8500_charger_usb_link_status_work(struct work_struct *work)
{
+ int detected_chargers;
int ret;
+ u8 val;
struct ab8500_charger *di = container_of(work,
struct ab8500_charger, usb_link_status_work);
@@ -1967,37 +2134,95 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work)
* synchronously, we have the check if is
* connected by reading the status register
*/
- ret = ab8500_charger_detect_chargers(di);
- if (ret < 0)
+ detected_chargers = ab8500_charger_detect_chargers(di, false);
+ if (detected_chargers < 0)
return;
- if (!(ret & USB_PW_CONN)) {
- di->vbus_detected = 0;
+ /*
+ * Some chargers that breaks the USB spec is
+ * identified as invalid by AB8500 and it refuse
+ * to start the charging process. but by jumping
+ * thru a few hoops it can be forced to start.
+ */
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ if (ret >= 0)
+ dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val);
+ else
+ dev_dbg(di->dev, "Error reading USB link status\n");
+
+ if (detected_chargers & USB_PW_CONN) {
+ if (((val & AB8500_USB_LINK_STATUS) >> 3) == USB_STAT_NOT_VALID_LINK &&
+ di->invalid_charger_detect_state == 0) {
+ dev_dbg(di->dev, "Invalid charger detected, state= 0\n");
+ /*Enable charger*/
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_USBCH_CTRL1_REG, 0x01, 0x01);
+ /*Enable charger detection*/
+ abx500_mask_and_set_register_interruptible(di->dev, AB8500_USB,
+ AB8500_MCH_IPT_CURLVL_REG, 0x01, 0x01);
+ di->invalid_charger_detect_state = 1;
+ /*exit and wait for new link status interrupt.*/
+ return;
+
+ }
+ if (di->invalid_charger_detect_state == 1) {
+ dev_dbg(di->dev, "Invalid charger detected, state= 1\n");
+ /*Stop charger detection*/
+ abx500_mask_and_set_register_interruptible(di->dev, AB8500_USB,
+ AB8500_MCH_IPT_CURLVL_REG, 0x01, 0x00);
+ /*Check link status*/
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ dev_dbg(di->dev, "USB link status= 0x%02x\n",
+ (val & AB8500_USB_LINK_STATUS) >> 3);
+ di->invalid_charger_detect_state = 2;
+ }
+ } else {
+ di->invalid_charger_detect_state = 0;
+ }
+
+ if (!(detected_chargers & USB_PW_CONN)) {
+ di->vbus_detected = false;
ab8500_charger_set_usb_connected(di, false);
ab8500_power_supply_changed(di, &di->usb_chg.psy);
return;
}
- di->vbus_detected = 1;
+ dev_dbg(di->dev,"%s di->vbus_detected = true\n",__func__);
+ di->vbus_detected = true;
ret = ab8500_charger_read_usb_type(di);
- if (!ret) {
- if (di->usb_device_is_unrecognised) {
- dev_dbg(di->dev,
- "Potential Legacy Charger device. "
- "Delay work for %d msec for USB enum "
- "to finish",
- WAIT_FOR_USB_ENUMERATION);
- queue_delayed_work(di->charger_wq,
- &di->attach_work,
- msecs_to_jiffies(WAIT_FOR_USB_ENUMERATION));
- } else {
- queue_delayed_work(di->charger_wq,
- &di->attach_work, 0);
+ if (ret) {
+ if (ret == -ENXIO) {
+ /* No valid charger type detected */
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
}
- } else if (ret == -ENXIO) {
- /* No valid charger type detected */
- ab8500_charger_set_usb_connected(di, false);
- ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ return;
+ }
+
+ if (di->usb_device_is_unrecognised) {
+ dev_dbg(di->dev,
+ "Potential Legacy Charger device. "
+ "Delay work for %d msec for USB enum "
+ "to finish",
+ WAIT_ACA_RID_ENUMERATION);
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION));
+ } else if (di->is_aca_rid == 1) {
+ /* Only wait once */
+ di->is_aca_rid++;
+ dev_dbg(di->dev,
+ "%s Wait %d msec for USB enum to finish",
+ __func__, WAIT_ACA_RID_ENUMERATION);
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION));
+ } else {
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ 0);
}
}
@@ -2007,24 +2232,20 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
unsigned long flags;
struct ab8500_charger *di = container_of(work,
- struct ab8500_charger, usb_state_changed_work);
+ struct ab8500_charger, usb_state_changed_work.work);
- if (!di->vbus_detected)
+ if (!di->vbus_detected) {
+ dev_dbg(di->dev,
+ "%s !di->vbus_detected\n",
+ __func__);
return;
+ }
spin_lock_irqsave(&di->usb_state.usb_lock, flags);
- di->usb_state.usb_changed = false;
+ di->usb_state.state = di->usb_state.state_tmp;
+ di->usb_state.usb_current = di->usb_state.usb_current_tmp;
spin_unlock_irqrestore(&di->usb_state.usb_lock, flags);
- /*
- * wait for some time until you get updates from the usb stack
- * and negotiations are completed
- */
- msleep(250);
-
- if (di->usb_state.usb_changed)
- return;
-
dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n",
__func__, di->usb_state.state, di->usb_state.usb_current);
@@ -2266,6 +2487,21 @@ static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di)
return IRQ_HANDLED;
}
+static void ab8500_charger_vbus_drop_end_work(struct work_struct *work)
+{
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, vbus_drop_end_work.work);
+
+ di->flags.vbus_drop_end = false;
+
+ /* Reset the drop counter */
+ abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CHARGER_CTRL, 0x01);
+
+ if (di->usb.charger_connected)
+ ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+}
+
/**
* ab8500_charger_vbusdetf_handler() - VBUS falling detected
* @irq: interrupt number
@@ -2277,6 +2513,7 @@ static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di)
{
struct ab8500_charger *di = _di;
+ di->vbus_detected = false;
dev_dbg(di->dev, "VBUS falling detected\n");
queue_work(di->charger_wq, &di->detect_usb_type_work);
@@ -2296,6 +2533,7 @@ static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di)
di->vbus_detected = true;
dev_dbg(di->dev, "VBUS rising detected\n");
+
queue_work(di->charger_wq, &di->detect_usb_type_work);
return IRQ_HANDLED;
@@ -2404,6 +2642,25 @@ static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di)
}
/**
+ * ab8500_charger_vbuschdropend_handler() - VBUS drop removed
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbuschdropend_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS charger drop ended\n");
+ di->flags.vbus_drop_end = true;
+ queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work,
+ round_jiffies(30 * HZ));
+
+ return IRQ_HANDLED;
+}
+
+/**
* ab8500_charger_vbusovv_handler() - VBUS overvoltage detected
* @irq: interrupt number
* @_di: pointer to the ab8500_charger structure
@@ -2601,13 +2858,23 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
}
}
- /* VBUS OVV set to 6.3V and enable automatic current limitiation */
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_USBCH_CTRL2_REG,
- VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA);
+ if (is_ab9540_2p0(di->parent) || is_ab8505_2p0(di->parent))
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL2_REG,
+ VBUS_AUTO_IN_CURR_LIM_ENA,
+ VBUS_AUTO_IN_CURR_LIM_ENA);
+ else
+ /*
+ * VBUS OVV set to 6.3V and enable automatic current limitation
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL2_REG,
+ VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA);
if (ret) {
- dev_err(di->dev, "failed to set VBUS OVV\n");
+ dev_err(di->dev,
+ "failed to set automatic current limitation\n");
goto out;
}
@@ -2663,6 +2930,20 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
goto out;
}
+ /* Set charger watchdog timeout */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_WD_TIMER_REG, WD_TIMER);
+ if (ret) {
+ dev_err(di->dev, "failed to set charger watchdog timeout\n");
+ goto out;
+ }
+
+ ret = ab8500_charger_led_en(di, false);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to disable LED\n");
+ goto out;
+ }
+
/* Backup battery voltage and current */
ret = abx500_set_register_interruptible(di->dev,
AB8500_RTC,
@@ -2702,6 +2983,7 @@ static struct ab8500_charger_interrupts ab8500_charger_irq[] = {
{"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler},
{"VBUS_OVV", ab8500_charger_vbusovv_handler},
{"CH_WD_EXP", ab8500_charger_chwdexp_handler},
+ {"VBUS_CH_DROP_END", ab8500_charger_vbuschdropend_handler},
};
static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
@@ -2712,6 +2994,9 @@ static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
enum ab8500_usb_state bm_usb_state;
unsigned mA = *((unsigned *)power);
+ if (!di)
+ return NOTIFY_DONE;
+
if (event != USB_EVENT_VBUS) {
dev_dbg(di->dev, "not a standard host, returning\n");
return NOTIFY_DONE;
@@ -2735,13 +3020,15 @@ static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
__func__, bm_usb_state, mA);
spin_lock(&di->usb_state.usb_lock);
- di->usb_state.usb_changed = true;
+ di->usb_state.state_tmp = bm_usb_state;
+ di->usb_state.usb_current_tmp = mA;
spin_unlock(&di->usb_state.usb_lock);
- di->usb_state.state = bm_usb_state;
- di->usb_state.usb_current = mA;
-
- queue_work(di->charger_wq, &di->usb_state_changed_work);
+ /*
+ * wait for some time until you get updates from the usb stack
+ * and negotiations are completed
+ */
+ queue_delayed_work(di->charger_wq, &di->usb_state_changed_work, HZ/2);
return NOTIFY_OK;
}
@@ -2781,6 +3068,9 @@ static int ab8500_charger_resume(struct platform_device *pdev)
&di->check_hw_failure_work, 0);
}
+ if (di->flags.vbus_drop_end)
+ queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, 0);
+
return 0;
}
@@ -2793,6 +3083,23 @@ static int ab8500_charger_suspend(struct platform_device *pdev,
if (delayed_work_pending(&di->check_hw_failure_work))
cancel_delayed_work(&di->check_hw_failure_work);
+ if (delayed_work_pending(&di->vbus_drop_end_work))
+ cancel_delayed_work(&di->vbus_drop_end_work);
+
+ flush_delayed_work(&di->attach_work);
+ flush_delayed_work(&di->usb_charger_attached_work);
+ flush_delayed_work(&di->ac_charger_attached_work);
+ flush_delayed_work(&di->check_usbchgnotok_work);
+ flush_delayed_work(&di->check_vbat_work);
+ flush_delayed_work(&di->kick_wd_work);
+
+ flush_work(&di->usb_link_status_work);
+ flush_work(&di->ac_work);
+ flush_work(&di->detect_usb_type_work);
+
+ if (atomic_read(&di->current_stepping_sessions))
+ return -EAGAIN;
+
return 0;
}
#else
@@ -2830,8 +3137,12 @@ static int ab8500_charger_remove(struct platform_device *pdev)
destroy_workqueue(di->charger_wq);
flush_scheduled_work();
- power_supply_unregister(&di->usb_chg.psy);
- power_supply_unregister(&di->ac_chg.psy);
+ if(di->usb_chg.enabled)
+ power_supply_unregister(&di->usb_chg.psy);
+#if !defined(CONFIG_CHARGER_PM2301)
+ if(di->ac_chg.enabled)
+ power_supply_unregister(&di->ac_chg.psy);
+#endif
platform_set_drvdata(pdev, NULL);
return 0;
@@ -2879,8 +3190,10 @@ static int ab8500_charger_probe(struct platform_device *pdev)
/* initialize lock */
spin_lock_init(&di->usb_state.usb_lock);
+ mutex_init(&di->usb_ipt_crnt_lock);
di->autopower = false;
+ di->invalid_charger_detect_state = 0;
/* AC supply */
/* power_supply base class */
@@ -2899,6 +3212,9 @@ static int ab8500_charger_probe(struct platform_device *pdev)
ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
di->ac_chg.max_out_curr = ab8500_charger_current_map[
ARRAY_SIZE(ab8500_charger_current_map) - 1];
+ di->ac_chg.wdt_refresh = CHG_WD_INTERVAL;
+ di->ac_chg.enabled = di->bm->ac_enabled;
+ di->ac_chg.external = false;
/* USB supply */
/* power_supply base class */
@@ -2917,7 +3233,9 @@ static int ab8500_charger_probe(struct platform_device *pdev)
ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
di->usb_chg.max_out_curr = ab8500_charger_current_map[
ARRAY_SIZE(ab8500_charger_current_map) - 1];
-
+ di->usb_chg.wdt_refresh = CHG_WD_INTERVAL;
+ di->usb_chg.enabled = di->bm->usb_enabled;
+ di->usb_chg.external = false;
/* Create a work queue for the charger */
di->charger_wq =
@@ -2958,6 +3276,12 @@ static int ab8500_charger_probe(struct platform_device *pdev)
INIT_DELAYED_WORK(&di->attach_work,
ab8500_charger_usb_link_attach_work);
+ INIT_DELAYED_WORK(&di->usb_state_changed_work,
+ ab8500_charger_usb_state_changed_work);
+
+ INIT_DELAYED_WORK(&di->vbus_drop_end_work,
+ ab8500_charger_vbus_drop_end_work);
+
/* Init work for charger detection */
INIT_WORK(&di->usb_link_status_work,
ab8500_charger_usb_link_status_work);
@@ -2965,9 +3289,6 @@ static int ab8500_charger_probe(struct platform_device *pdev)
INIT_WORK(&di->detect_usb_type_work,
ab8500_charger_detect_usb_type_work);
- INIT_WORK(&di->usb_state_changed_work,
- ab8500_charger_usb_state_changed_work);
-
/* Init work for checking HW status */
INIT_WORK(&di->check_main_thermal_prot_work,
ab8500_charger_check_main_thermal_prot_work);
@@ -2995,17 +3316,21 @@ static int ab8500_charger_probe(struct platform_device *pdev)
}
/* Register AC charger class */
- ret = power_supply_register(di->dev, &di->ac_chg.psy);
- if (ret) {
- dev_err(di->dev, "failed to register AC charger\n");
- goto free_charger_wq;
+ if(di->ac_chg.enabled) {
+ ret = power_supply_register(di->dev, &di->ac_chg.psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register AC charger\n");
+ goto free_charger_wq;
+ }
}
/* Register USB charger class */
- ret = power_supply_register(di->dev, &di->usb_chg.psy);
- if (ret) {
- dev_err(di->dev, "failed to register USB charger\n");
- goto free_ac;
+ if(di->usb_chg.enabled) {
+ ret = power_supply_register(di->dev, &di->usb_chg.psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register USB charger\n");
+ goto free_ac;
+ }
}
di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2);
@@ -3022,7 +3347,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
}
/* Identify the connected charger types during startup */
- charger_status = ab8500_charger_detect_chargers(di);
+ charger_status = ab8500_charger_detect_chargers(di, true);
if (charger_status & AC_PW_CONN) {
di->ac.charger_connected = 1;
di->ac_conn = true;
@@ -3057,7 +3382,7 @@ static int ab8500_charger_probe(struct platform_device *pdev)
mutex_lock(&di->charger_attached_mutex);
- ch_stat = ab8500_charger_detect_chargers(di);
+ ch_stat = ab8500_charger_detect_chargers(di, false);
if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) {
queue_delayed_work(di->charger_wq,
@@ -3085,9 +3410,11 @@ free_irq:
put_usb_phy:
usb_put_phy(di->usb_phy);
free_usb:
- power_supply_unregister(&di->usb_chg.psy);
+ if(di->usb_chg.enabled)
+ power_supply_unregister(&di->usb_chg.psy);
free_ac:
- power_supply_unregister(&di->ac_chg.psy);
+ if(di->ac_chg.enabled)
+ power_supply_unregister(&di->ac_chg.psy);
free_charger_wq:
destroy_workqueue(di->charger_wq);
return ret;
OpenPOWER on IntegriCloud