diff options
author | Anton Vorontsov <anton@enomsg.org> | 2013-02-02 19:25:57 -0800 |
---|---|---|
committer | Anton Vorontsov <anton@enomsg.org> | 2013-02-02 19:29:13 -0800 |
commit | 58a1c154d449aae97a8ecad67ddbfd4024ac8446 (patch) | |
tree | 4d09553a369094149646e3018aa908b9e1294dba /drivers/power | |
parent | eeb0751c99522a4d1bbcc7b6bc1460cd07d07488 (diff) | |
parent | 34c11a709e928090cf34ecd706f7d3170f4e5026 (diff) |
Merge branch 'tb-power-2' of git://git.linaro.org/people/ljones/linux-3.0-ux500
Pull a huge ab8500/pm2301 pile of changes from Lee Jones. Lee did an
awesome job cleaning this stuff up and thus brought ab8500 Stericsson's
development tree much closer to the mainline. Even more changes to come,
though.
Conflicts:
drivers/power/Kconfig
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 7 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/ab8500_btemp.c | 67 | ||||
-rw-r--r-- | drivers/power/ab8500_charger.c | 617 | ||||
-rw-r--r-- | drivers/power/ab8500_fg.c | 51 | ||||
-rw-r--r-- | drivers/power/abx500_chargalg.c | 43 | ||||
-rw-r--r-- | drivers/power/pm2301_charger.c | 1088 | ||||
-rw-r--r-- | drivers/power/pm2301_charger.h | 513 |
8 files changed, 2210 insertions, 177 deletions
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, ®); - 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, ®_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, ®_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, ®_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 */ |