summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/mfd/ab8500-core.c6
-rw-r--r--drivers/power/Kconfig7
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/ab8500_btemp.c67
-rw-r--r--drivers/power/ab8500_charger.c617
-rw-r--r--drivers/power/ab8500_fg.c51
-rw-r--r--drivers/power/abx500_chargalg.c43
-rw-r--r--drivers/power/pm2301_charger.c1088
-rw-r--r--drivers/power/pm2301_charger.h513
-rw-r--r--include/linux/mfd/abx500.h3
-rw-r--r--include/linux/mfd/abx500/ab8500-bm.h22
-rw-r--r--include/linux/mfd/abx500/ab8500.h19
-rw-r--r--include/linux/mfd/abx500/ux500_chargalg.h5
-rw-r--r--include/linux/pm2301_charger.h61
14 files changed, 2306 insertions, 197 deletions
diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c
index 30b92652fce9..4c4aa197f307 100644
--- a/drivers/mfd/ab8500-core.c
+++ b/drivers/mfd/ab8500-core.c
@@ -749,6 +749,12 @@ static struct resource ab8500_charger_resources[] = {
.end = AB8500_INT_CH_WD_EXP,
.flags = IORESOURCE_IRQ,
},
+ {
+ .name = "VBUS_CH_DROP_END",
+ .start = AB8500_INT_VBUS_CH_DROP_END,
+ .end = AB8500_INT_VBUS_CH_DROP_END,
+ .flags = IORESOURCE_IRQ,
+ },
};
static struct resource ab8500_btemp_resources[] = {
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 1ae51551c9ff..1e4719790a94 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -352,6 +352,13 @@ config BATTERY_GOLDFISH
Say Y to enable support for the battery and AC power in the
Goldfish emulator.
+config CHARGER_PM2301
+ bool "PM2301 Battery Charger Driver"
+ depends on AB8500_BM
+ help
+ Say Y to include support for PM2301 charger driver.
+ Depends on AB8500 battery management core.
+
source "drivers/power/reset/Kconfig"
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index a9f5c06ad41a..3f66436af45c 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_CHARGER_PM2301) += pm2301_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c
index f3afebd4fac4..07689064996e 100644
--- a/drivers/power/ab8500_btemp.c
+++ b/drivers/power/ab8500_btemp.c
@@ -39,6 +39,9 @@
#define BTEMP_BATCTRL_CURR_SRC_7UA 7
#define BTEMP_BATCTRL_CURR_SRC_20UA 20
+#define BTEMP_BATCTRL_CURR_SRC_16UA 16
+#define BTEMP_BATCTRL_CURR_SRC_18UA 18
+
#define to_ab8500_btemp_device_info(x) container_of((x), \
struct ab8500_btemp, btemp_psy);
@@ -212,10 +215,18 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
/* Only do this for batteries with internal NTC */
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
- if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
- curr = BAT_CTRL_7U_ENA;
- else
- curr = BAT_CTRL_20U_ENA;
+
+ if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA)
+ curr = BAT_CTRL_16U_ENA;
+ else
+ curr = BAT_CTRL_18U_ENA;
+ } else {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
+ curr = BAT_CTRL_7U_ENA;
+ else
+ curr = BAT_CTRL_20U_ENA;
+ }
dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source);
@@ -246,11 +257,22 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
} else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
dev_dbg(di->dev, "Disable BATCTRL curr source\n");
- /* Write 0 to the curr bits */
- ret = abx500_mask_and_set_register_interruptible(di->dev,
- AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
- BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
- ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA,
+ ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA));
+ } else {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+ ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ }
+
if (ret) {
dev_err(di->dev, "%s failed disabling current source\n",
__func__);
@@ -292,11 +314,20 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
* if we got an error above
*/
disable_curr_source:
- /* Write 0 to the curr bits */
- ret = abx500_mask_and_set_register_interruptible(di->dev,
+ if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA,
+ ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA));
+ } else {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ }
+
if (ret) {
dev_err(di->dev, "%s failed disabling current source\n",
__func__);
@@ -510,8 +541,11 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
{
int res;
u8 i;
+ if (is_ab9540(di->parent) || is_ab8505(di->parent))
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
+ else
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
- di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
di->bm->batt_id = BATTERY_UNKNOWN;
res = ab8500_btemp_get_batctrl_res(di);
@@ -549,8 +583,13 @@ static int ab8500_btemp_id(struct ab8500_btemp *di)
*/
if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
di->bm->batt_id == 1) {
- dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
- di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
+ if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ dev_dbg(di->dev, "Set BATCTRL current source to 16uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
+ } else {
+ dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
+ }
}
return di->bm->batt_id;
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;
diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c
index cd03549b6227..25dae4c4b0ef 100644
--- a/drivers/power/ab8500_fg.c
+++ b/drivers/power/ab8500_fg.c
@@ -43,7 +43,7 @@
#define NBR_AVG_SAMPLES 20
-#define LOW_BAT_CHECK_INTERVAL (2 * HZ)
+#define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */
#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */
#define BATT_OK_MIN 2360 /* mV */
@@ -169,6 +169,7 @@ struct inst_curr_result_list {
* @recovery_cnt: Counter for recovery mode
* @high_curr_cnt: Counter for high current mode
* @init_cnt: Counter for init mode
+ * @low_bat_cnt Counter for number of consecutive low battery measures
* @nbr_cceoc_irq_cnt Counter for number of CCEOC irqs received since enabled
* @recovery_needed: Indicate if recovery is needed
* @high_curr_mode: Indicate if we're in high current mode
@@ -210,6 +211,7 @@ struct ab8500_fg {
int recovery_cnt;
int high_curr_cnt;
int init_cnt;
+ int low_bat_cnt;
int nbr_cceoc_irq_cnt;
bool recovery_needed;
bool high_curr_mode;
@@ -1639,7 +1641,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
if (di->recovery_needed) {
ab8500_fg_discharge_state_to(di,
- AB8500_FG_DISCHARGE_RECOVERY);
+ AB8500_FG_DISCHARGE_INIT_RECOVERY);
queue_delayed_work(di->fg_wq,
&di->fg_periodic_work, 0);
@@ -1879,25 +1881,29 @@ static void ab8500_fg_low_bat_work(struct work_struct *work)
/* Check if LOW_BAT still fulfilled */
if (vbat < di->bm->fg_params->lowbat_threshold) {
- di->flags.low_bat = true;
- dev_warn(di->dev, "Battery voltage still LOW\n");
-
- /*
- * We need to re-schedule this check to be able to detect
- * if the voltage increases again during charging
- */
- queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
- round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ /* Is it time to shut down? */
+ if (di->low_bat_cnt < 1) {
+ di->flags.low_bat = true;
+ dev_warn(di->dev, "Shut down pending...\n");
+ } else {
+ /*
+ * Else we need to re-schedule this check to be able to detect
+ * if the voltage increases again during charging or
+ * due to decreasing load.
+ */
+ di->low_bat_cnt--;
+ dev_warn(di->dev, "Battery voltage still LOW\n");
+ queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+ round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ }
} else {
- di->flags.low_bat = false;
+ di->flags.low_bat_delay = false;
+ di->low_bat_cnt = 10;
dev_warn(di->dev, "Battery voltage OK again\n");
}
/* This is needed to dispatch LOW_BAT */
ab8500_fg_check_capacity_limits(di, false);
-
- /* Set this flag to check if LOW_BAT IRQ still occurs */
- di->flags.low_bat_delay = false;
}
/**
@@ -2056,6 +2062,7 @@ static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di)
{
struct ab8500_fg *di = _di;
+ /* Initiate handling in ab8500_fg_low_bat_work() if not already initiated. */
if (!di->flags.low_bat_delay) {
dev_warn(di->dev, "Battery voltage is below LOW threshold\n");
di->flags.low_bat_delay = true;
@@ -2243,7 +2250,8 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
case POWER_SUPPLY_PROP_TECHNOLOGY:
switch (ext->type) {
case POWER_SUPPLY_TYPE_BATTERY:
- if (!di->flags.batt_id_received) {
+ if (!di->flags.batt_id_received &&
+ di->bm->batt_id != BATTERY_UNKNOWN) {
const struct abx500_battery_type *b;
b = &(di->bm->bat_type[di->bm->batt_id]);
@@ -2563,6 +2571,11 @@ static int ab8500_fg_suspend(struct platform_device *pdev,
struct ab8500_fg *di = platform_get_drvdata(pdev);
flush_delayed_work(&di->fg_periodic_work);
+ flush_work(&di->fg_work);
+ flush_work(&di->fg_acc_cur_work);
+ flush_delayed_work(&di->fg_reinit_work);
+ flush_delayed_work(&di->fg_low_bat_work);
+ flush_delayed_work(&di->fg_check_hw_failure_work);
/*
* If the FG is enabled we will disable it before going to suspend
@@ -2698,6 +2711,12 @@ static int ab8500_fg_probe(struct platform_device *pdev)
INIT_DEFERRABLE_WORK(&di->fg_check_hw_failure_work,
ab8500_fg_check_hw_failure_work);
+ /* Reset battery low voltage flag */
+ di->flags.low_bat = false;
+
+ /* Initialize low battery counter */
+ di->low_bat_cnt = 10;
+
/* Initialize OVV, and other registers */
ret = ab8500_fg_init_hw_registers(di);
if (ret) {
diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c
index 78b623572b52..f043c0851a76 100644
--- a/drivers/power/abx500_chargalg.c
+++ b/drivers/power/abx500_chargalg.c
@@ -445,8 +445,18 @@ static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)
{
/* Check if charger exists and kick watchdog if charging */
if (di->ac_chg && di->ac_chg->ops.kick_wd &&
- di->chg_info.online_chg & AC_CHG)
+ di->chg_info.online_chg & AC_CHG) {
+ /*
+ * If AB charger watchdog expired, pm2xxx charging
+ * gets disabled. To be safe, kick both AB charger watchdog
+ * and pm2xxx watchdog.
+ */
+ if (di->ac_chg->external &&
+ di->usb_chg && di->usb_chg->ops.kick_wd)
+ di->usb_chg->ops.kick_wd(di->usb_chg);
+
return di->ac_chg->ops.kick_wd(di->ac_chg);
+ }
else if (di->usb_chg && di->usb_chg->ops.kick_wd &&
di->chg_info.online_chg & USB_CHG)
return di->usb_chg->ops.kick_wd(di->usb_chg);
@@ -603,6 +613,8 @@ static void abx500_chargalg_hold_charging(struct abx500_chargalg *di)
static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
int vset, int iset)
{
+ bool start_chargalg_wd = true;
+
switch (di->chg_info.charger_type) {
case AC_CHG:
dev_dbg(di->dev,
@@ -620,8 +632,12 @@ static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
default:
dev_err(di->dev, "Unknown charger to charge from\n");
+ start_chargalg_wd = false;
break;
}
+
+ if (start_chargalg_wd && !delayed_work_pending(&di->chargalg_wd_work))
+ queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0);
}
/**
@@ -1622,6 +1638,9 @@ static int abx500_chargalg_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_HEALTH_COLD;
else
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ } else if (di->charge_state == STATE_SAFETY_TIMER_EXPIRED ||
+ di->charge_state == STATE_SAFETY_TIMER_EXPIRED_INIT) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
} else {
val->intval = POWER_SUPPLY_HEALTH_GOOD;
}
@@ -1635,6 +1654,25 @@ static int abx500_chargalg_get_property(struct power_supply *psy,
/* Exposure to the sysfs interface */
/**
+ * abx500_chargalg_sysfs_show() - sysfs show operations
+ * @kobj: pointer to the struct kobject
+ * @attr: pointer to the struct attribute
+ * @buf: buffer that holds the parameter to send to userspace
+ *
+ * Returns a buffer to be displayed in user space
+ */
+static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct abx500_chargalg *di = container_of(kobj,
+ struct abx500_chargalg, chargalg_kobject);
+
+ return sprintf(buf, "%d\n",
+ di->susp_status.ac_suspended &&
+ di->susp_status.usb_suspended);
+}
+
+/**
* abx500_chargalg_sysfs_charger() - sysfs store operations
* @kobj: pointer to the struct kobject
* @attr: pointer to the struct attribute
@@ -1702,7 +1740,7 @@ static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
static struct attribute abx500_chargalg_en_charger = \
{
.name = "chargalg",
- .mode = S_IWUGO,
+ .mode = S_IRUGO | S_IWUSR,
};
static struct attribute *abx500_chargalg_chg[] = {
@@ -1711,6 +1749,7 @@ static struct attribute *abx500_chargalg_chg[] = {
};
static const struct sysfs_ops abx500_chargalg_sysfs_ops = {
+ .show = abx500_chargalg_sysfs_show,
.store = abx500_chargalg_sysfs_charger,
};
diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c
new file mode 100644
index 000000000000..ed48d75bb786
--- /dev/null
+++ b/drivers/power/pm2301_charger.c
@@ -0,0 +1,1088 @@
+/*
+ * Copyright 2012 ST Ericsson.
+ *
+ * Power supply driver for ST Ericsson pm2xxx_charger charger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/pm2301_charger.h>
+#include <linux/gpio.h>
+
+#include "pm2301_charger.h"
+
+#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \
+ struct pm2xxx_charger, ac_chg)
+
+static int pm2xxx_interrupt_registers[] = {
+ PM2XXX_REG_INT1,
+ PM2XXX_REG_INT2,
+ PM2XXX_REG_INT3,
+ PM2XXX_REG_INT4,
+ PM2XXX_REG_INT5,
+ PM2XXX_REG_INT6,
+};
+
+static enum power_supply_property pm2xxx_charger_ac_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+};
+
+static int pm2xxx_charger_voltage_map[] = {
+ 3500,
+ 3525,
+ 3550,
+ 3575,
+ 3600,
+ 3625,
+ 3650,
+ 3675,
+ 3700,
+ 3725,
+ 3750,
+ 3775,
+ 3800,
+ 3825,
+ 3850,
+ 3875,
+ 3900,
+ 3925,
+ 3950,
+ 3975,
+ 4000,
+ 4025,
+ 4050,
+ 4075,
+ 4100,
+ 4125,
+ 4150,
+ 4175,
+ 4200,
+ 4225,
+ 4250,
+ 4275,
+ 4300,
+};
+
+static int pm2xxx_charger_current_map[] = {
+ 200,
+ 200,
+ 400,
+ 600,
+ 800,
+ 1000,
+ 1200,
+ 1400,
+ 1600,
+ 1800,
+ 2000,
+ 2200,
+ 2400,
+ 2600,
+ 2800,
+ 3000,
+};
+
+static const struct i2c_device_id pm2xxx_ident[] = {
+ { "pm2301", 0 },
+ { }
+};
+
+static void set_lpn_pin(struct pm2xxx_charger *pm2)
+{
+ if (pm2->ac.charger_connected)
+ return;
+ gpio_set_value(pm2->lpn_pin, 1);
+
+ return;
+}
+
+static void clear_lpn_pin(struct pm2xxx_charger *pm2)
+{
+ if (pm2->ac.charger_connected)
+ return;
+ gpio_set_value(pm2->lpn_pin, 0);
+
+ return;
+}
+
+static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val)
+{
+ int ret;
+ /*
+ * When AC adaptor is unplugged, the host
+ * must put LPN high to be able to
+ * communicate by I2C with PM2301
+ * and receive I2C "acknowledge" from PM2301.
+ */
+ mutex_lock(&pm2->lock);
+ set_lpn_pin(pm2);
+
+ ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
+ 1, val);
+ if (ret < 0)
+ dev_err(pm2->dev, "Error reading register at 0x%x\n", reg);
+ else
+ ret = 0;
+ clear_lpn_pin(pm2);
+ mutex_unlock(&pm2->lock);
+
+ return ret;
+}
+
+static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val)
+{
+ int ret;
+ /*
+ * When AC adaptor is unplugged, the host
+ * must put LPN high to be able to
+ * communicate by I2C with PM2301
+ * and receive I2C "acknowledge" from PM2301.
+ */
+ mutex_lock(&pm2->lock);
+ set_lpn_pin(pm2);
+
+ ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
+ 1, &val);
+ if (ret < 0)
+ dev_err(pm2->dev, "Error writing register at 0x%x\n", reg);
+ else
+ ret = 0;
+ clear_lpn_pin(pm2);
+ mutex_unlock(&pm2->lock);
+
+ return ret;
+}
+
+static int pm2xxx_charging_enable_mngt(struct pm2xxx_charger *pm2)
+{
+ int ret;
+
+ /* Enable charging */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2,
+ (PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA));
+
+ return ret;
+}
+
+static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2)
+{
+ int ret;
+
+ /* Disable charging */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2,
+ (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS));
+
+ return ret;
+}
+
+static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+
+int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ int ret = 0;
+
+ pm2->failure_input_ovv++;
+ if (pm2->failure_input_ovv < 4) {
+ ret = pm2xxx_charging_enable_mngt(pm2);
+ goto out;
+ } else {
+ pm2->failure_input_ovv = 0;
+ dev_err(pm2->dev, "Overvoltage detected\n");
+ pm2->flags.ovv = true;
+ power_supply_changed(&pm2->ac_chg.psy);
+ }
+
+out:
+ return ret;
+}
+
+static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_dbg(pm2->dev , "20 minutes watchdog occured\n");
+
+ pm2->ac.wd_expired = true;
+ power_supply_changed(&pm2->ac_chg.psy);
+
+ return 0;
+}
+
+static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ switch (val) {
+ case PM2XXX_INT1_ITVBATLOWR:
+ dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n");
+ break;
+
+ case PM2XXX_INT1_ITVBATLOWF:
+ dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n");
+ break;
+
+ default:
+ dev_err(pm2->dev, "Unknown VBAT level\n");
+ }
+
+ return 0;
+}
+
+static int pm2xxx_charger_bat_disc_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_dbg(pm2->dev, "battery disconnected\n");
+
+ return 0;
+}
+
+static int pm2xxx_charger_detection(struct pm2xxx_charger *pm2, u8 *val)
+{
+ int ret;
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT2, val);
+
+ if (ret < 0) {
+ dev_err(pm2->dev, "Charger detection failed\n");
+ goto out;
+ }
+
+ *val &= (PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG);
+
+out:
+ return ret;
+}
+
+static int pm2xxx_charger_itv_pwr_plug_mngt(struct pm2xxx_charger *pm2, int val)
+{
+
+ int ret;
+ u8 read_val;
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if the main charger is
+ * connected by reading the interrupt source register.
+ */
+ ret = pm2xxx_charger_detection(pm2, &read_val);
+
+ if ((ret == 0) && read_val) {
+ pm2->ac.charger_connected = 1;
+ pm2->ac_conn = true;
+ queue_work(pm2->charger_wq, &pm2->ac_work);
+ }
+
+
+ return ret;
+}
+
+static int pm2xxx_charger_itv_pwr_unplug_mngt(struct pm2xxx_charger *pm2,
+ int val)
+{
+ pm2->ac.charger_connected = 0;
+ queue_work(pm2->charger_wq, &pm2->ac_work);
+
+ return 0;
+}
+
+static int pm2_int_reg0(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF)) {
+ ret = pm2xxx_charger_vbat_lsig_mngt(pm2, val &
+ (PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF));
+ }
+
+ if (val & PM2XXX_INT1_ITVBATDISCONNECT) {
+ ret = pm2xxx_charger_bat_disc_mngt(pm2,
+ PM2XXX_INT1_ITVBATDISCONNECT);
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg1(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)) {
+ dev_dbg(pm2->dev , "Main charger plugged\n");
+ ret = pm2xxx_charger_itv_pwr_plug_mngt(pm2, val &
+ (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG));
+ }
+
+ if (val &
+ (PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG)) {
+ dev_dbg(pm2->dev , "Main charger unplugged\n");
+ ret = pm2xxx_charger_itv_pwr_unplug_mngt(pm2, val &
+ (PM2XXX_INT2_ITVPWR1UNPLUG |
+ PM2XXX_INT2_ITVPWR2UNPLUG));
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg2(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT3_ITAUTOTIMEOUTWD)
+ ret = pm2xxx_charger_wd_exp_mngt(pm2, val);
+
+ if (val & (PM2XXX_INT3_ITCHPRECHARGEWD |
+ PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD)) {
+ dev_dbg(pm2->dev,
+ "Watchdog occured for precharge, CC and CV charge\n");
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg3(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT4_ITCHARGINGON)) {
+ dev_dbg(pm2->dev ,
+ "chargind operation has started\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITVRESUME)) {
+ dev_dbg(pm2->dev,
+ "battery discharged down to VResume threshold\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITBATTFULL)) {
+ dev_dbg(pm2->dev , "battery fully detected\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITCVPHASE)) {
+ dev_dbg(pm2->dev, "CV phase enter with 0.5C charging\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)) {
+ pm2->failure_case = VPWR_OVV;
+ ret = pm2xxx_charger_ovv_mngt(pm2, val &
+ (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV));
+ dev_dbg(pm2->dev, "VPWR/VSYSTEM overvoltage detected\n");
+ }
+
+ if (val & (PM2XXX_INT4_S_ITBATTEMPCOLD |
+ PM2XXX_INT4_S_ITBATTEMPHOT)) {
+ ret = pm2xxx_charger_batt_therm_mngt(pm2, val &
+ (PM2XXX_INT4_S_ITBATTEMPCOLD |
+ PM2XXX_INT4_S_ITBATTEMPHOT));
+ dev_dbg(pm2->dev, "BTEMP is too Low/High\n");
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg4(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT5_ITVSYSTEMOVV) {
+ pm2->failure_case = VSYSTEM_OVV;
+ ret = pm2xxx_charger_ovv_mngt(pm2, val &
+ PM2XXX_INT5_ITVSYSTEMOVV);
+ dev_dbg(pm2->dev, "VSYSTEM overvoltage detected\n");
+ }
+
+ if (val & (PM2XXX_INT5_ITTHERMALWARNINGFALL |
+ PM2XXX_INT5_ITTHERMALWARNINGRISE |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)) {
+ dev_dbg(pm2->dev, "BTEMP die temperature is too Low/High\n");
+ ret = pm2xxx_charger_die_therm_mngt(pm2, val &
+ (PM2XXX_INT5_ITTHERMALWARNINGFALL |
+ PM2XXX_INT5_ITTHERMALWARNINGRISE |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE));
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg5(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+
+ if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) {
+ dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n");
+ }
+
+ if (val & (PM2XXX_INT6_ITVPWR2VALIDRISE |
+ PM2XXX_INT6_ITVPWR1VALIDRISE |
+ PM2XXX_INT6_ITVPWR2VALIDFALL |
+ PM2XXX_INT6_ITVPWR1VALIDFALL)) {
+ dev_dbg(pm2->dev, "Falling/Rising edge on WPWR1/2\n");
+ }
+
+ return ret;
+}
+
+static irqreturn_t pm2xxx_irq_int(int irq, void *data)
+{
+ struct pm2xxx_charger *pm2 = data;
+ struct pm2xxx_interrupts *interrupt = pm2->pm2_int;
+ int i;
+
+ for (i = 0; i < PM2XXX_NUM_INT_REG; i++) {
+ pm2xxx_reg_read(pm2,
+ pm2xxx_interrupt_registers[i],
+ &(interrupt->reg[i]));
+
+ if (interrupt->reg[i] > 0)
+ interrupt->handler[i](pm2, interrupt->reg[i]);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int pm2xxx_charger_get_ac_cv(struct pm2xxx_charger *pm2)
+{
+ int ret = 0;
+ u8 val;
+
+ if (pm2->ac.charger_connected && pm2->ac.charger_online) {
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto out;
+ }
+
+ if (val & PM2XXX_INT4_S_ITCVPHASE)
+ ret = PM2XXX_CONST_VOLT;
+ else
+ ret = PM2XXX_CONST_CURR;
+ }
+out:
+ return ret;
+}
+
+static int pm2xxx_current_to_regval(int curr)
+{
+ int i;
+
+ if (curr < pm2xxx_charger_current_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_current_map); i++) {
+ if (curr < pm2xxx_charger_current_map[i])
+ return (i - 1);
+ }
+
+ i = ARRAY_SIZE(pm2xxx_charger_current_map) - 1;
+ if (curr == pm2xxx_charger_current_map[i])
+ return i;
+ else
+ return -EINVAL;
+}
+
+static int pm2xxx_voltage_to_regval(int curr)
+{
+ int i;
+
+ if (curr < pm2xxx_charger_voltage_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_voltage_map); i++) {
+ if (curr < pm2xxx_charger_voltage_map[i])
+ return i - 1;
+ }
+
+ i = ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1;
+ if (curr == pm2xxx_charger_voltage_map[i])
+ return i;
+ else
+ return -EINVAL;
+}
+
+static int pm2xxx_charger_update_charger_current(struct ux500_charger *charger,
+ int ich_out)
+{
+ int ret;
+ int curr_index;
+ struct pm2xxx_charger *pm2;
+ u8 val;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+ pm2 = to_pm2xxx_charger_ac_device_info(charger);
+ else
+ return -ENXIO;
+
+ curr_index = pm2xxx_current_to_regval(ich_out);
+ if (curr_index < 0) {
+ dev_err(pm2->dev,
+ "Charger current too high, charging not started\n");
+ return -ENXIO;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val);
+ if (ret >= 0) {
+ val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK;
+ val |= curr_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val);
+ if (ret < 0) {
+ dev_err(pm2->dev,
+ "%s write failed\n", __func__);
+ }
+ }
+ else
+ dev_err(pm2->dev, "%s read failed\n", __func__);
+
+ return ret;
+}
+
+static int pm2xxx_charger_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pm2xxx_charger *pm2;
+
+ pm2 = to_pm2xxx_charger_ac_device_info(psy_to_ux500_charger(psy));
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (pm2->flags.mainextchnotok)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (pm2->ac.wd_expired)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (pm2->flags.main_thermal_prot)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = pm2->ac.charger_online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pm2->ac.charger_connected;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ pm2->ac.cv_active = pm2xxx_charger_get_ac_cv(pm2);
+ val->intval = pm2->ac.cv_active;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pm2xxx_charging_init(struct pm2xxx_charger *pm2)
+{
+ int ret = 0;
+
+ /* enable CC and CV watchdog */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG3,
+ (PM2XXX_CH_WD_CV_PHASE_60MIN | PM2XXX_CH_WD_CC_PHASE_60MIN));
+ if( ret < 0)
+ return ret;
+
+ /* enable precharge watchdog */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG4,
+ PM2XXX_CH_WD_PRECH_PHASE_60MIN);
+
+ /* Disable auto timeout */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG5,
+ PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN);
+
+ /*
+ * EOC current level = 100mA
+ * Precharge current level = 100mA
+ * CC current level = 1000mA
+ */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6,
+ (PM2XXX_DIR_CH_CC_CURRENT_1000MA |
+ PM2XXX_CH_PRECH_CURRENT_100MA |
+ PM2XXX_CH_EOC_CURRENT_100MA));
+
+ /*
+ * recharge threshold = 3.8V
+ * Precharge to CC threshold = 2.9V
+ */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG7,
+ (PM2XXX_CH_PRECH_VOL_2_9 | PM2XXX_CH_VRESUME_VOL_3_8));
+
+ /* float voltage charger level = 4.2V */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8,
+ PM2XXX_CH_VOLT_4_2);
+
+ /* Voltage drop between VBAT and VSYS in HW charging = 300mV */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG9,
+ (PM2XXX_CH_150MV_DROP_300MV | PM2XXX_CHARCHING_INFO_DIS |
+ PM2XXX_CH_CC_REDUCED_CURRENT_IDENT |
+ PM2XXX_CH_CC_MODEDROP_DIS));
+
+ /* Input charger level of over voltage = 10V */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR2,
+ PM2XXX_VPWR2_OVV_10);
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR1,
+ PM2XXX_VPWR1_OVV_10);
+
+ /* Input charger drop */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR2,
+ (PM2XXX_VPWR2_HW_OPT_DIS | PM2XXX_VPWR2_VALID_DIS |
+ PM2XXX_VPWR2_DROP_DIS));
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR1,
+ (PM2XXX_VPWR1_HW_OPT_DIS | PM2XXX_VPWR1_VALID_DIS |
+ PM2XXX_VPWR1_DROP_DIS));
+
+ /* Disable battery low monitoring */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG,
+ PM2XXX_VBAT_LOW_MONITORING_ENA);
+
+ /* Disable LED */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG,
+ PM2XXX_LED_SELECT_DIS);
+
+ return ret;
+}
+
+static int pm2xxx_charger_ac_en(struct ux500_charger *charger,
+ int enable, int vset, int iset)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+ u8 val;
+
+ struct pm2xxx_charger *pm2 = to_pm2xxx_charger_ac_device_info(charger);
+
+ if (enable) {
+ if (!pm2->ac.charger_connected) {
+ dev_dbg(pm2->dev, "AC charger not connected\n");
+ return -ENXIO;
+ }
+
+ dev_dbg(pm2->dev, "Enable AC: %dmV %dmA\n", vset, iset);
+ if (!pm2->vddadc_en_ac) {
+ regulator_enable(pm2->regu);
+ pm2->vddadc_en_ac = true;
+ }
+
+ ret = pm2xxx_charging_init(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s charging init failed\n",
+ __func__);
+ goto error_occured;
+ }
+
+ volt_index = pm2xxx_voltage_to_regval(vset);
+ curr_index = pm2xxx_current_to_regval(iset);
+
+ if (volt_index < 0 || curr_index < 0) {
+ dev_err(pm2->dev,
+ "Charger voltage or current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG8, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto error_occured;
+ }
+ val &= ~PM2XXX_CH_VOLT_MASK;
+ val |= volt_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ goto error_occured;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto error_occured;
+ }
+ val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK;
+ val |= curr_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ goto error_occured;
+ }
+
+ if (!pm2->bat->enable_overshoot) {
+ ret = pm2xxx_reg_read(pm2, PM2XXX_LED_CTRL_REG, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n",
+ __func__);
+ goto error_occured;
+ }
+ val |= PM2XXX_ANTI_OVERSHOOT_EN;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n",
+ __func__);
+ goto error_occured;
+ }
+ }
+
+ ret = pm2xxx_charging_enable_mngt(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "Failed to enable"
+ "pm2xxx ac charger\n");
+ goto error_occured;
+ }
+
+ pm2->ac.charger_online = 1;
+ } else {
+ pm2->ac.charger_online = 0;
+ pm2->ac.wd_expired = false;
+
+ /* Disable regulator if enabled */
+ if (pm2->vddadc_en_ac) {
+ regulator_disable(pm2->regu);
+ pm2->vddadc_en_ac = false;
+ }
+
+ ret = pm2xxx_charging_disable_mngt(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "failed to disable"
+ "pm2xxx ac charger\n");
+ goto error_occured;
+ }
+
+ dev_dbg(pm2->dev, "PM2301: " "Disabled AC charging\n");
+ }
+ power_supply_changed(&pm2->ac_chg.psy);
+
+error_occured:
+ return ret;
+}
+
+static int pm2xxx_charger_watchdog_kick(struct ux500_charger *charger)
+{
+ int ret;
+ struct pm2xxx_charger *pm2;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+ pm2 = to_pm2xxx_charger_ac_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_WD_KICK, WD_TIMER);
+ if (ret)
+ dev_err(pm2->dev, "Failed to kick WD!\n");
+
+ return ret;
+}
+
+static void pm2xxx_charger_ac_work(struct work_struct *work)
+{
+ struct pm2xxx_charger *pm2 = container_of(work,
+ struct pm2xxx_charger, ac_work);
+
+
+ power_supply_changed(&pm2->ac_chg.psy);
+ sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present");
+};
+
+static void pm2xxx_charger_check_main_thermal_prot_work(
+ struct work_struct *work)
+{
+};
+
+static struct pm2xxx_interrupts pm2xxx_int = {
+ .handler[0] = pm2_int_reg0,
+ .handler[1] = pm2_int_reg1,
+ .handler[2] = pm2_int_reg2,
+ .handler[3] = pm2_int_reg3,
+ .handler[4] = pm2_int_reg4,
+ .handler[5] = pm2_int_reg5,
+};
+
+static struct pm2xxx_irq pm2xxx_charger_irq[] = {
+ {"PM2XXX_IRQ_INT", pm2xxx_irq_int},
+};
+
+static int pm2xxx_wall_charger_resume(struct i2c_client *i2c_client)
+{
+ return 0;
+}
+
+static int pm2xxx_wall_charger_suspend(struct i2c_client *i2c_client,
+ pm_message_t state)
+{
+ return 0;
+}
+
+static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data;
+ struct pm2xxx_charger *pm2;
+ int ret = 0;
+ u8 val;
+
+ pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL);
+ if (!pm2) {
+ dev_err(pm2->dev, "pm2xxx_charger allocation failed\n");
+ return -ENOMEM;
+ }
+
+ /* get parent data */
+ pm2->dev = &i2c_client->dev;
+ pm2->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+ pm2->pm2_int = &pm2xxx_int;
+
+ /* get charger spcific platform data */
+ if (!pl_data->wall_charger) {
+ dev_err(pm2->dev, "no charger platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ pm2->pdata = pl_data->wall_charger;
+
+ /* get battery specific platform data */
+ if (!pl_data->battery) {
+ dev_err(pm2->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ pm2->bat = pl_data->battery;
+
+ /*get lpn GPIO from platform data*/
+ if (!pm2->pdata->lpn_gpio) {
+ dev_err(pm2->dev, "no lpn gpio data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+ pm2->lpn_pin = pm2->pdata->lpn_gpio;
+
+ if (!i2c_check_functionality(i2c_client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_WORD_DATA)) {
+ ret = -ENODEV;
+ dev_info(pm2->dev, "pm2301 i2c_check_functionality failed\n");
+ goto free_device_info;
+ }
+
+ pm2->config.pm2xxx_i2c = i2c_client;
+ pm2->config.pm2xxx_id = (struct i2c_device_id *) id;
+ i2c_set_clientdata(i2c_client, pm2);
+
+ /* AC supply */
+ /* power_supply base class */
+ pm2->ac_chg.psy.name = pm2->pdata->label;
+ pm2->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS;
+ pm2->ac_chg.psy.properties = pm2xxx_charger_ac_props;
+ pm2->ac_chg.psy.num_properties = ARRAY_SIZE(pm2xxx_charger_ac_props);
+ pm2->ac_chg.psy.get_property = pm2xxx_charger_ac_get_property;
+ pm2->ac_chg.psy.supplied_to = pm2->pdata->supplied_to;
+ pm2->ac_chg.psy.num_supplicants = pm2->pdata->num_supplicants;
+ /* pm2xxx_charger sub-class */
+ pm2->ac_chg.ops.enable = &pm2xxx_charger_ac_en;
+ pm2->ac_chg.ops.kick_wd = &pm2xxx_charger_watchdog_kick;
+ pm2->ac_chg.ops.update_curr = &pm2xxx_charger_update_charger_current;
+ pm2->ac_chg.max_out_volt = pm2xxx_charger_voltage_map[
+ ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1];
+ pm2->ac_chg.max_out_curr = pm2xxx_charger_current_map[
+ ARRAY_SIZE(pm2xxx_charger_current_map) - 1];
+ pm2->ac_chg.wdt_refresh = WD_KICK_INTERVAL;
+ pm2->ac_chg.enabled = true;
+ pm2->ac_chg.external = true;
+
+ /* Create a work queue for the charger */
+ pm2->charger_wq =
+ create_singlethread_workqueue("pm2xxx_charger_wq");
+ if (pm2->charger_wq == NULL) {
+ dev_err(pm2->dev, "failed to create work queue\n");
+ goto free_device_info;
+ }
+
+ /* Init work for charger detection */
+ INIT_WORK(&pm2->ac_work, pm2xxx_charger_ac_work);
+
+ /* Init work for checking HW status */
+ INIT_WORK(&pm2->check_main_thermal_prot_work,
+ pm2xxx_charger_check_main_thermal_prot_work);
+
+ /*
+ * VDD ADC supply needs to be enabled from this driver when there
+ * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
+ * interrupts during charging
+ */
+ pm2->regu = regulator_get(pm2->dev, "vddadc");
+ if (IS_ERR(pm2->regu)) {
+ ret = PTR_ERR(pm2->regu);
+ dev_err(pm2->dev, "failed to get vddadc regulator\n");
+ goto free_charger_wq;
+ }
+
+ /* Register AC charger class */
+ ret = power_supply_register(pm2->dev, &pm2->ac_chg.psy);
+ if (ret) {
+ dev_err(pm2->dev, "failed to register AC charger\n");
+ goto free_regulator;
+ }
+
+ /* Register interrupts */
+ ret = request_threaded_irq(pm2->pdata->irq_number, NULL,
+ pm2xxx_charger_irq[0].isr,
+ pm2->pdata->irq_type,
+ pm2xxx_charger_irq[0].name, pm2);
+
+ if (ret != 0) {
+ dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n",
+ pm2xxx_charger_irq[0].name, pm2->pdata->irq_number, ret);
+ goto unregister_pm2xxx_charger;
+ }
+
+ /*Initialize lock*/
+ mutex_init(&pm2->lock);
+
+ /*
+ * Charger detection mechanism requires pulling up the LPN pin
+ * while i2c communication if Charger is not connected
+ * LPN pin of PM2301 is GPIO60 of AB9540
+ */
+ ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio");
+ if (ret < 0) {
+ dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n");
+ goto unregister_pm2xxx_charger;
+ }
+ ret = gpio_direction_output(pm2->lpn_pin, 0);
+ if (ret < 0) {
+ dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n");
+ goto free_gpio;
+ }
+
+ ret = pm2xxx_charger_detection(pm2, &val);
+
+ if ((ret == 0) && val) {
+ pm2->ac.charger_connected = 1;
+ pm2->ac_conn = true;
+ power_supply_changed(&pm2->ac_chg.psy);
+ sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present");
+ }
+
+ return 0;
+
+free_gpio:
+ gpio_free(pm2->lpn_pin);
+unregister_pm2xxx_charger:
+ /* unregister power supply */
+ power_supply_unregister(&pm2->ac_chg.psy);
+free_regulator:
+ /* disable the regulator */
+ regulator_put(pm2->regu);
+free_charger_wq:
+ destroy_workqueue(pm2->charger_wq);
+free_device_info:
+ kfree(pm2);
+ return ret;
+}
+
+static int __devexit pm2xxx_wall_charger_remove(struct i2c_client *i2c_client)
+{
+ struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client);
+
+ /* Disable AC charging */
+ pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0);
+
+ /* Disable interrupts */
+ free_irq(pm2->pdata->irq_number, pm2);
+
+ /* Delete the work queue */
+ destroy_workqueue(pm2->charger_wq);
+
+ flush_scheduled_work();
+
+ /* disable the regulator */
+ regulator_put(pm2->regu);
+
+ power_supply_unregister(&pm2->ac_chg.psy);
+
+ /*Free GPIO60*/
+ gpio_free(pm2->lpn_pin);
+
+ kfree(pm2);
+
+ return 0;
+}
+
+static const struct i2c_device_id pm2xxx_id[] = {
+ { "pm2301", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, pm2xxx_id);
+
+static struct i2c_driver pm2xxx_charger_driver = {
+ .probe = pm2xxx_wall_charger_probe,
+ .remove = __devexit_p(pm2xxx_wall_charger_remove),
+ .suspend = pm2xxx_wall_charger_suspend,
+ .resume = pm2xxx_wall_charger_resume,
+ .driver = {
+ .name = "pm2xxx-wall_charger",
+ .owner = THIS_MODULE,
+ },
+ .id_table = pm2xxx_id,
+};
+
+static int __init pm2xxx_charger_init(void)
+{
+ return i2c_add_driver(&pm2xxx_charger_driver);
+}
+
+static void __exit pm2xxx_charger_exit(void)
+{
+ i2c_del_driver(&pm2xxx_charger_driver);
+}
+
+subsys_initcall_sync(pm2xxx_charger_init);
+module_exit(pm2xxx_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay");
+MODULE_ALIAS("platform:pm2xxx-charger");
+MODULE_DESCRIPTION("PM2xxx charger management driver");
+
diff --git a/drivers/power/pm2301_charger.h b/drivers/power/pm2301_charger.h
new file mode 100644
index 000000000000..e6319cdbc94f
--- /dev/null
+++ b/drivers/power/pm2301_charger.h
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * PM2301 power supply interface
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef PM2301_CHARGER_H
+#define PM2301_CHARGER_H
+
+#define MAIN_WDOG_ENA 0x01
+#define MAIN_WDOG_KICK 0x02
+#define MAIN_WDOG_DIS 0x00
+#define CHARG_WD_KICK 0x01
+#define MAIN_CH_ENA 0x01
+#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02
+#define MAIN_CH_DET 0x01
+#define MAIN_CH_CV_ON 0x04
+#define OTP_ENABLE_WD 0x01
+
+#define MAIN_CH_INPUT_CURR_SHIFT 4
+
+#define LED_INDICATOR_PWM_ENA 0x01
+#define LED_INDICATOR_PWM_DIS 0x00
+#define LED_IND_CUR_5MA 0x04
+#define LED_INDICATOR_PWM_DUTY_252_256 0xBF
+
+/* HW failure constants */
+#define MAIN_CH_TH_PROT 0x02
+#define MAIN_CH_NOK 0x01
+
+/* Watchdog timeout constant */
+#define WD_TIMER 0x30 /* 4min */
+#define WD_KICK_INTERVAL (30 * HZ)
+
+#define PM2XXX_NUM_INT_REG 0x6
+
+/* Constant voltage/current */
+#define PM2XXX_CONST_CURR 0x0
+#define PM2XXX_CONST_VOLT 0x1
+
+/* Lowest charger voltage is 3.39V -> 0x4E */
+#define LOW_VOLT_REG 0x4E
+
+#define PM2XXX_BATT_CTRL_REG1 0x00
+#define PM2XXX_BATT_CTRL_REG2 0x01
+#define PM2XXX_BATT_CTRL_REG3 0x02
+#define PM2XXX_BATT_CTRL_REG4 0x03
+#define PM2XXX_BATT_CTRL_REG5 0x04
+#define PM2XXX_BATT_CTRL_REG6 0x05
+#define PM2XXX_BATT_CTRL_REG7 0x06
+#define PM2XXX_BATT_CTRL_REG8 0x07
+#define PM2XXX_NTC_CTRL_REG1 0x08
+#define PM2XXX_NTC_CTRL_REG2 0x09
+#define PM2XXX_BATT_CTRL_REG9 0x0A
+#define PM2XXX_BATT_STAT_REG1 0x0B
+#define PM2XXX_INP_VOLT_VPWR2 0x11
+#define PM2XXX_INP_DROP_VPWR2 0x13
+#define PM2XXX_INP_VOLT_VPWR1 0x15
+#define PM2XXX_INP_DROP_VPWR1 0x17
+#define PM2XXX_INP_MODE_VPWR 0x18
+#define PM2XXX_BATT_WD_KICK 0x70
+#define PM2XXX_DEV_VER_STAT 0x0C
+#define PM2XXX_THERM_WARN_CTRL_REG 0x20
+#define PM2XXX_BATT_DISC_REG 0x21
+#define PM2XXX_BATT_LOW_LEV_COMP_REG 0x22
+#define PM2XXX_BATT_LOW_LEV_VAL_REG 0x23
+#define PM2XXX_I2C_PAD_CTRL_REG 0x24
+#define PM2XXX_SW_CTRL_REG 0x26
+#define PM2XXX_LED_CTRL_REG 0x28
+
+#define PM2XXX_REG_INT1 0x40
+#define PM2XXX_MASK_REG_INT1 0x50
+#define PM2XXX_SRCE_REG_INT1 0x60
+#define PM2XXX_REG_INT2 0x41
+#define PM2XXX_MASK_REG_INT2 0x51
+#define PM2XXX_SRCE_REG_INT2 0x61
+#define PM2XXX_REG_INT3 0x42
+#define PM2XXX_MASK_REG_INT3 0x52
+#define PM2XXX_SRCE_REG_INT3 0x62
+#define PM2XXX_REG_INT4 0x43
+#define PM2XXX_MASK_REG_INT4 0x53
+#define PM2XXX_SRCE_REG_INT4 0x63
+#define PM2XXX_REG_INT5 0x44
+#define PM2XXX_MASK_REG_INT5 0x54
+#define PM2XXX_SRCE_REG_INT5 0x64
+#define PM2XXX_REG_INT6 0x45
+#define PM2XXX_MASK_REG_INT6 0x55
+#define PM2XXX_SRCE_REG_INT6 0x65
+
+#define VPWR_OVV 0x0
+#define VSYSTEM_OVV 0x1
+
+/* control Reg 1 */
+#define PM2XXX_CH_RESUME_EN 0x1
+#define PM2XXX_CH_RESUME_DIS 0x0
+
+/* control Reg 2 */
+#define PM2XXX_CH_AUTO_RESUME_EN 0X2
+#define PM2XXX_CH_AUTO_RESUME_DIS 0X0
+#define PM2XXX_CHARGER_ENA 0x4
+#define PM2XXX_CHARGER_DIS 0x0
+
+/* control Reg 3 */
+#define PM2XXX_CH_WD_CC_PHASE_OFF 0x0
+#define PM2XXX_CH_WD_CC_PHASE_5MIN 0x1
+#define PM2XXX_CH_WD_CC_PHASE_10MIN 0x2
+#define PM2XXX_CH_WD_CC_PHASE_30MIN 0x3
+#define PM2XXX_CH_WD_CC_PHASE_60MIN 0x4
+#define PM2XXX_CH_WD_CC_PHASE_120MIN 0x5
+#define PM2XXX_CH_WD_CC_PHASE_240MIN 0x6
+#define PM2XXX_CH_WD_CC_PHASE_360MIN 0x7
+
+#define PM2XXX_CH_WD_CV_PHASE_OFF (0x0<<3)
+#define PM2XXX_CH_WD_CV_PHASE_5MIN (0x1<<3)
+#define PM2XXX_CH_WD_CV_PHASE_10MIN (0x2<<3)
+#define PM2XXX_CH_WD_CV_PHASE_30MIN (0x3<<3)
+#define PM2XXX_CH_WD_CV_PHASE_60MIN (0x4<<3)
+#define PM2XXX_CH_WD_CV_PHASE_120MIN (0x5<<3)
+#define PM2XXX_CH_WD_CV_PHASE_240MIN (0x6<<3)
+#define PM2XXX_CH_WD_CV_PHASE_360MIN (0x7<<3)
+
+/* control Reg 4 */
+#define PM2XXX_CH_WD_PRECH_PHASE_OFF 0x0
+#define PM2XXX_CH_WD_PRECH_PHASE_1MIN 0x1
+#define PM2XXX_CH_WD_PRECH_PHASE_5MIN 0x2
+#define PM2XXX_CH_WD_PRECH_PHASE_10MIN 0x3
+#define PM2XXX_CH_WD_PRECH_PHASE_30MIN 0x4
+#define PM2XXX_CH_WD_PRECH_PHASE_60MIN 0x5
+#define PM2XXX_CH_WD_PRECH_PHASE_120MIN 0x6
+#define PM2XXX_CH_WD_PRECH_PHASE_240MIN 0x7
+
+/* control Reg 5 */
+#define PM2XXX_CH_WD_AUTO_TIMEOUT_NONE 0x0
+#define PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN 0x1
+
+/* control Reg 6 */
+#define PM2XXX_DIR_CH_CC_CURRENT_MASK 0x0F
+#define PM2XXX_DIR_CH_CC_CURRENT_200MA 0x0
+#define PM2XXX_DIR_CH_CC_CURRENT_400MA 0x2
+#define PM2XXX_DIR_CH_CC_CURRENT_600MA 0x3
+#define PM2XXX_DIR_CH_CC_CURRENT_800MA 0x4
+#define PM2XXX_DIR_CH_CC_CURRENT_1000MA 0x5
+#define PM2XXX_DIR_CH_CC_CURRENT_1200MA 0x6
+#define PM2XXX_DIR_CH_CC_CURRENT_1400MA 0x7
+#define PM2XXX_DIR_CH_CC_CURRENT_1600MA 0x8
+#define PM2XXX_DIR_CH_CC_CURRENT_1800MA 0x9
+#define PM2XXX_DIR_CH_CC_CURRENT_2000MA 0xA
+#define PM2XXX_DIR_CH_CC_CURRENT_2200MA 0xB
+#define PM2XXX_DIR_CH_CC_CURRENT_2400MA 0xC
+#define PM2XXX_DIR_CH_CC_CURRENT_2600MA 0xD
+#define PM2XXX_DIR_CH_CC_CURRENT_2800MA 0xE
+#define PM2XXX_DIR_CH_CC_CURRENT_3000MA 0xF
+
+#define PM2XXX_CH_PRECH_CURRENT_MASK 0x30
+#define PM2XXX_CH_PRECH_CURRENT_25MA (0x0<<4)
+#define PM2XXX_CH_PRECH_CURRENT_50MA (0x1<<4)
+#define PM2XXX_CH_PRECH_CURRENT_75MA (0x2<<4)
+#define PM2XXX_CH_PRECH_CURRENT_100MA (0x3<<4)
+
+#define PM2XXX_CH_EOC_CURRENT_MASK 0xC0
+#define PM2XXX_CH_EOC_CURRENT_100MA (0x0<<6)
+#define PM2XXX_CH_EOC_CURRENT_150MA (0x1<<6)
+#define PM2XXX_CH_EOC_CURRENT_300MA (0x2<<6)
+#define PM2XXX_CH_EOC_CURRENT_400MA (0x3<<6)
+
+/* control Reg 7 */
+#define PM2XXX_CH_PRECH_VOL_2_5 0x0
+#define PM2XXX_CH_PRECH_VOL_2_7 0x1
+#define PM2XXX_CH_PRECH_VOL_2_9 0x2
+#define PM2XXX_CH_PRECH_VOL_3_1 0x3
+
+#define PM2XXX_CH_VRESUME_VOL_3_2 (0x0<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_4 (0x1<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_6 (0x2<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_8 (0x3<<2)
+
+/* control Reg 8 */
+#define PM2XXX_CH_VOLT_MASK 0x3F
+#define PM2XXX_CH_VOLT_3_5 0x0
+#define PM2XXX_CH_VOLT_3_5225 0x1
+#define PM2XXX_CH_VOLT_3_6 0x4
+#define PM2XXX_CH_VOLT_3_7 0x8
+#define PM2XXX_CH_VOLT_4_0 0x14
+#define PM2XXX_CH_VOLT_4_175 0x1B
+#define PM2XXX_CH_VOLT_4_2 0x1C
+#define PM2XXX_CH_VOLT_4_275 0x1F
+#define PM2XXX_CH_VOLT_4_3 0x20
+
+/*NTC control register 1*/
+#define PM2XXX_BTEMP_HIGH_TH_45 0x0
+#define PM2XXX_BTEMP_HIGH_TH_50 0x1
+#define PM2XXX_BTEMP_HIGH_TH_55 0x2
+#define PM2XXX_BTEMP_HIGH_TH_60 0x3
+#define PM2XXX_BTEMP_HIGH_TH_65 0x4
+
+#define PM2XXX_BTEMP_LOW_TH_N5 (0x0<<3)
+#define PM2XXX_BTEMP_LOW_TH_0 (0x1<<3)
+#define PM2XXX_BTEMP_LOW_TH_5 (0x2<<3)
+#define PM2XXX_BTEMP_LOW_TH_10 (0x3<<3)
+
+/*NTC control register 2*/
+#define PM2XXX_NTC_BETA_COEFF_3477 0x0
+#define PM2XXX_NTC_BETA_COEFF_3964 0x1
+
+#define PM2XXX_NTC_RES_10K (0x0<<2)
+#define PM2XXX_NTC_RES_47K (0x1<<2)
+#define PM2XXX_NTC_RES_100K (0x2<<2)
+#define PM2XXX_NTC_RES_NO_NTC (0x3<<2)
+
+/* control Reg 9 */
+#define PM2XXX_CH_CC_MODEDROP_EN 1
+#define PM2XXX_CH_CC_MODEDROP_DIS 0
+
+#define PM2XXX_CH_CC_REDUCED_CURRENT_100MA (0x0<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_200MA (0x1<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_400MA (0x2<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_IDENT (0x3<<1)
+
+#define PM2XXX_CHARCHING_INFO_DIS (0<<3)
+#define PM2XXX_CHARCHING_INFO_EN (1<<3)
+
+#define PM2XXX_CH_150MV_DROP_300MV (0<<4)
+#define PM2XXX_CH_150MV_DROP_150MV (1<<4)
+
+
+/* charger status register */
+#define PM2XXX_CHG_STATUS_OFF 0x0
+#define PM2XXX_CHG_STATUS_ON 0x1
+#define PM2XXX_CHG_STATUS_FULL 0x2
+#define PM2XXX_CHG_STATUS_ERR 0x3
+#define PM2XXX_CHG_STATUS_WAIT 0x4
+#define PM2XXX_CHG_STATUS_NOBAT 0x5
+
+/* Input charger voltage VPWR2 */
+#define PM2XXX_VPWR2_OVV_6_0 0x0
+#define PM2XXX_VPWR2_OVV_6_3 0x1
+#define PM2XXX_VPWR2_OVV_10 0x2
+#define PM2XXX_VPWR2_OVV_NONE 0x3
+
+/* Input charger drop VPWR2 */
+#define PM2XXX_VPWR2_HW_OPT_EN (0x1<<4)
+#define PM2XXX_VPWR2_HW_OPT_DIS (0x0<<4)
+
+#define PM2XXX_VPWR2_VALID_EN (0x1<<3)
+#define PM2XXX_VPWR2_VALID_DIS (0x0<<3)
+
+#define PM2XXX_VPWR2_DROP_EN (0x1<<2)
+#define PM2XXX_VPWR2_DROP_DIS (0x0<<2)
+
+/* Input charger voltage VPWR1 */
+#define PM2XXX_VPWR1_OVV_6_0 0x0
+#define PM2XXX_VPWR1_OVV_6_3 0x1
+#define PM2XXX_VPWR1_OVV_10 0x2
+#define PM2XXX_VPWR1_OVV_NONE 0x3
+
+/* Input charger drop VPWR1 */
+#define PM2XXX_VPWR1_HW_OPT_EN (0x1<<4)
+#define PM2XXX_VPWR1_HW_OPT_DIS (0x0<<4)
+
+#define PM2XXX_VPWR1_VALID_EN (0x1<<3)
+#define PM2XXX_VPWR1_VALID_DIS (0x0<<3)
+
+#define PM2XXX_VPWR1_DROP_EN (0x1<<2)
+#define PM2XXX_VPWR1_DROP_DIS (0x0<<2)
+
+/* Battery low level comparator control register */
+#define PM2XXX_VBAT_LOW_MONITORING_DIS 0x0
+#define PM2XXX_VBAT_LOW_MONITORING_ENA 0x1
+
+/* Battery low level value control register */
+#define PM2XXX_VBAT_LOW_LEVEL_2_3 0x0
+#define PM2XXX_VBAT_LOW_LEVEL_2_4 0x1
+#define PM2XXX_VBAT_LOW_LEVEL_2_5 0x2
+#define PM2XXX_VBAT_LOW_LEVEL_2_6 0x3
+#define PM2XXX_VBAT_LOW_LEVEL_2_7 0x4
+#define PM2XXX_VBAT_LOW_LEVEL_2_8 0x5
+#define PM2XXX_VBAT_LOW_LEVEL_2_9 0x6
+#define PM2XXX_VBAT_LOW_LEVEL_3_0 0x7
+#define PM2XXX_VBAT_LOW_LEVEL_3_1 0x8
+#define PM2XXX_VBAT_LOW_LEVEL_3_2 0x9
+#define PM2XXX_VBAT_LOW_LEVEL_3_3 0xA
+#define PM2XXX_VBAT_LOW_LEVEL_3_4 0xB
+#define PM2XXX_VBAT_LOW_LEVEL_3_5 0xC
+#define PM2XXX_VBAT_LOW_LEVEL_3_6 0xD
+#define PM2XXX_VBAT_LOW_LEVEL_3_7 0xE
+#define PM2XXX_VBAT_LOW_LEVEL_3_8 0xF
+#define PM2XXX_VBAT_LOW_LEVEL_3_9 0x10
+#define PM2XXX_VBAT_LOW_LEVEL_4_0 0x11
+#define PM2XXX_VBAT_LOW_LEVEL_4_1 0x12
+#define PM2XXX_VBAT_LOW_LEVEL_4_2 0x13
+
+/* SW CTRL */
+#define PM2XXX_SWCTRL_HW 0x0
+#define PM2XXX_SWCTRL_SW 0x1
+
+
+/* LED Driver Control */
+#define PM2XXX_LED_CURRENT_MASK 0x0C
+#define PM2XXX_LED_CURRENT_2_5MA (0X0<<2)
+#define PM2XXX_LED_CURRENT_1MA (0X1<<2)
+#define PM2XXX_LED_CURRENT_5MA (0X2<<2)
+#define PM2XXX_LED_CURRENT_10MA (0X3<<2)
+
+#define PM2XXX_LED_SELECT_MASK 0x02
+#define PM2XXX_LED_SELECT_EN (0X0<<1)
+#define PM2XXX_LED_SELECT_DIS (0X1<<1)
+
+#define PM2XXX_ANTI_OVERSHOOT_MASK 0x01
+#define PM2XXX_ANTI_OVERSHOOT_DIS 0X0
+#define PM2XXX_ANTI_OVERSHOOT_EN 0X1
+
+enum pm2xxx_reg_int1 {
+ PM2XXX_INT1_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_mask_reg_int1 {
+ PM2XXX_INT1_M_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_M_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_M_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_source_reg_int1 {
+ PM2XXX_INT1_S_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_S_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_S_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_reg_int2 {
+ PM2XXX_INT2_ITVPWR2PLUG = 0x01,
+ PM2XXX_INT2_ITVPWR2UNPLUG = 0x02,
+ PM2XXX_INT2_ITVPWR1PLUG = 0x04,
+ PM2XXX_INT2_ITVPWR1UNPLUG = 0x08,
+};
+
+enum pm2xxx_mask_reg_int2 {
+ PM2XXX_INT2_M_ITVPWR2PLUG = 0x01,
+ PM2XXX_INT2_M_ITVPWR2UNPLUG = 0x02,
+ PM2XXX_INT2_M_ITVPWR1PLUG = 0x04,
+ PM2XXX_INT2_M_ITVPWR1UNPLUG = 0x08,
+};
+
+enum pm2xxx_source_reg_int2 {
+ PM2XXX_INT2_S_ITVPWR2PLUG = 0x03,
+ PM2XXX_INT2_S_ITVPWR1PLUG = 0x0c,
+};
+
+enum pm2xxx_reg_int3 {
+ PM2XXX_INT3_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_ITCHCCWD = 0x02,
+ PM2XXX_INT3_ITCHCVWD = 0x04,
+ PM2XXX_INT3_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_mask_reg_int3 {
+ PM2XXX_INT3_M_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_M_ITCHCCWD = 0x02,
+ PM2XXX_INT3_M_ITCHCVWD = 0x04,
+ PM2XXX_INT3_M_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_source_reg_int3 {
+ PM2XXX_INT3_S_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_S_ITCHCCWD = 0x02,
+ PM2XXX_INT3_S_ITCHCVWD = 0x04,
+ PM2XXX_INT3_S_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_reg_int4 {
+ PM2XXX_INT4_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_ITVRESUME = 0x20,
+ PM2XXX_INT4_ITBATTFULL = 0x40,
+ PM2XXX_INT4_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_mask_reg_int4 {
+ PM2XXX_INT4_M_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_M_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_M_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_M_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_M_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_M_ITVRESUME = 0x20,
+ PM2XXX_INT4_M_ITBATTFULL = 0x40,
+ PM2XXX_INT4_M_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_source_reg_int4 {
+ PM2XXX_INT4_S_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_S_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_S_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_S_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_S_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_S_ITVRESUME = 0x20,
+ PM2XXX_INT4_S_ITBATTFULL = 0x40,
+ PM2XXX_INT4_S_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_reg_int5 {
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_mask_reg_int5 {
+ PM2XXX_INT5_M_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_M_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_M_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_M_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_M_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_source_reg_int5 {
+ PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_S_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_S_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_S_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_reg_int6 {
+ PM2XXX_INT6_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_ITVPWR1VALIDFALL = 0x20,
+};
+
+enum pm2xxx_mask_reg_int6 {
+ PM2XXX_INT6_M_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_M_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_M_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_M_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_M_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_M_ITVPWR1VALIDFALL = 0x20,
+};
+
+enum pm2xxx_source_reg_int6 {
+ PM2XXX_INT6_S_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_S_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_S_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_S_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_S_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_S_ITVPWR1VALIDFALL = 0x20,
+};
+
+struct pm2xxx_charger_info {
+ int charger_connected;
+ int charger_online;
+ int cv_active;
+ bool wd_expired;
+};
+
+struct pm2xxx_charger_event_flags {
+ bool mainextchnotok;
+ bool main_thermal_prot;
+ bool ovv;
+ bool chgwdexp;
+};
+
+struct pm2xxx_interrupts {
+ u8 reg[PM2XXX_NUM_INT_REG];
+ int (*handler[PM2XXX_NUM_INT_REG])(void *, int);
+};
+
+struct pm2xxx_config {
+ struct i2c_client *pm2xxx_i2c;
+ struct i2c_device_id *pm2xxx_id;
+};
+
+struct pm2xxx_irq {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct pm2xxx_charger {
+ struct device *dev;
+ u8 chip_id;
+ bool vddadc_en_ac;
+ struct pm2xxx_config config;
+ bool ac_conn;
+ unsigned int gpio_irq;
+ int vbat;
+ int old_vbat;
+ int failure_case;
+ int failure_input_ovv;
+ unsigned int lpn_pin;
+ struct pm2xxx_interrupts *pm2_int;
+ struct ab8500_gpadc *gpadc;
+ struct regulator *regu;
+ struct pm2xxx_bm_data *bat;
+ struct mutex lock;
+ struct ab8500 *parent;
+ struct pm2xxx_charger_info ac;
+ struct pm2xxx_charger_platform_data *pdata;
+ struct workqueue_struct *charger_wq;
+ struct delayed_work check_vbat_work;
+ struct work_struct ac_work;
+ struct work_struct check_main_thermal_prot_work;
+ struct ux500_charger ac_chg;
+ struct pm2xxx_charger_event_flags flags;
+};
+
+#endif /* PM2301_CHARGER_H */
diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h
index 0e6e90badfca..1beaa056f195 100644
--- a/include/linux/mfd/abx500.h
+++ b/include/linux/mfd/abx500.h
@@ -254,6 +254,9 @@ struct abx500_bm_data {
int usb_safety_tmr_h;
int bkup_bat_v;
int bkup_bat_i;
+ bool autopower_cfg;
+ bool ac_enabled;
+ bool usb_enabled;
bool no_maintenance;
bool capacity_scaling;
bool chg_unknown_bat;
diff --git a/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h
index e2a1e6d84f72..345bc159f978 100644
--- a/include/linux/mfd/abx500/ab8500-bm.h
+++ b/include/linux/mfd/abx500/ab8500-bm.h
@@ -226,6 +226,8 @@
/* BatCtrl Current Source Constants */
#define BAT_CTRL_7U_ENA 0x01
#define BAT_CTRL_20U_ENA 0x02
+#define BAT_CTRL_18U_ENA 0x01
+#define BAT_CTRL_16U_ENA 0x02
#define BAT_CTRL_CMP_ENA 0x04
#define FORCE_BAT_CTRL_CMP_HIGH 0x08
#define BAT_CTRL_PULL_UP_ENA 0x10
@@ -402,26 +404,6 @@ struct ab8500_bm_data {
const struct ab8500_fg_parameters *fg_params;
};
-struct ab8500_charger_platform_data {
- char **supplied_to;
- size_t num_supplicants;
- bool autopower_cfg;
-};
-
-struct ab8500_btemp_platform_data {
- char **supplied_to;
- size_t num_supplicants;
-};
-
-struct ab8500_fg_platform_data {
- char **supplied_to;
- size_t num_supplicants;
-};
-
-struct ab8500_chargalg_platform_data {
- char **supplied_to;
- size_t num_supplicants;
-};
struct ab8500_btemp;
struct ab8500_gpadc;
struct ab8500_fg;
diff --git a/include/linux/mfd/abx500/ab8500.h b/include/linux/mfd/abx500/ab8500.h
index 9dd9b99099df..b9a6a847ff61 100644
--- a/include/linux/mfd/abx500/ab8500.h
+++ b/include/linux/mfd/abx500/ab8500.h
@@ -346,4 +346,23 @@ static inline int is_ab8500_2p0(struct ab8500 *ab)
return (is_ab8500(ab) && (ab->chip_id == AB8500_CUT2P0));
}
+static inline int is_ab8505_1p0_or_earlier(struct ab8500 *ab)
+{
+ return (is_ab8505(ab) && (ab->chip_id <= AB8500_CUT1P0));
+}
+
+static inline int is_ab8505_2p0(struct ab8500 *ab)
+{
+ return (is_ab8505(ab) && (ab->chip_id == AB8500_CUT2P0));
+}
+
+static inline int is_ab9540_1p0_or_earlier(struct ab8500 *ab)
+{
+ return (is_ab9540(ab) && (ab->chip_id <= AB8500_CUT1P0));
+}
+
+static inline int is_ab9540_2p0(struct ab8500 *ab)
+{
+ return (is_ab9540(ab) && (ab->chip_id == AB8500_CUT2P0));
+}
#endif /* MFD_AB8500_H */
diff --git a/include/linux/mfd/abx500/ux500_chargalg.h b/include/linux/mfd/abx500/ux500_chargalg.h
index 9b07725750c9..d43ac0f35526 100644
--- a/include/linux/mfd/abx500/ux500_chargalg.h
+++ b/include/linux/mfd/abx500/ux500_chargalg.h
@@ -27,12 +27,17 @@ struct ux500_charger_ops {
* @ops ux500 charger operations
* @max_out_volt maximum output charger voltage in mV
* @max_out_curr maximum output charger current in mA
+ * @enabled indicates if this charger is used or not
+ * @external external charger unit (pm2xxx)
*/
struct ux500_charger {
struct power_supply psy;
struct ux500_charger_ops ops;
int max_out_volt;
int max_out_curr;
+ int wdt_refresh;
+ bool enabled;
+ bool external;
};
#endif
diff --git a/include/linux/pm2301_charger.h b/include/linux/pm2301_charger.h
new file mode 100644
index 000000000000..fc3f026922ae
--- /dev/null
+++ b/include/linux/pm2301_charger.h
@@ -0,0 +1,61 @@
+/*
+ * PM2301 charger driver.
+ *
+ * Copyright (C) 2012 ST Ericsson Corporation
+ *
+ * Contact: Olivier LAUNAY (olivier.launay@stericsson.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#ifndef __LINUX_PM2301_H
+#define __LINUX_PM2301_H
+
+/**
+ * struct pm2xxx_bm_charger_parameters - Charger specific parameters
+ * @ac_volt_max: maximum allowed AC charger voltage in mV
+ * @ac_curr_max: maximum allowed AC charger current in mA
+ */
+struct pm2xxx_bm_charger_parameters {
+ int ac_volt_max;
+ int ac_curr_max;
+};
+
+/**
+ * struct pm2xxx_bm_data - pm2xxx battery management data
+ * @enable_overshoot flag to enable VBAT overshoot control
+ * @chg_params charger parameters
+ */
+struct pm2xxx_bm_data {
+ bool enable_overshoot;
+ const struct pm2xxx_bm_charger_parameters *chg_params;
+};
+
+struct pm2xxx_charger_platform_data {
+ char **supplied_to;
+ size_t num_supplicants;
+ int i2c_bus;
+ const char *label;
+ int irq_number;
+ unsigned int lpn_gpio;
+ int irq_type;
+};
+
+struct pm2xxx_platform_data {
+ struct pm2xxx_charger_platform_data *wall_charger;
+ struct pm2xxx_bm_data *battery;
+};
+
+#endif /* __LINUX_PM2301_H */