summaryrefslogtreecommitdiffstats
path: root/drivers/power/ab8500_charger.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/ab8500_charger.c')
-rw-r--r--drivers/power/ab8500_charger.c222
1 files changed, 158 insertions, 64 deletions
diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c
index 3be9c0ee3fc5..e5755f0ba831 100644
--- a/drivers/power/ab8500_charger.c
+++ b/drivers/power/ab8500_charger.c
@@ -79,6 +79,9 @@
/* Lowest charger voltage is 3.39V -> 0x4E */
#define LOW_VOLT_REG 0x4E
+/* Step up/down delay in us */
+#define STEP_UDELAY 1000
+
/* UsbLineStatus register - usb types */
enum ab8500_charger_link_status {
USB_STAT_NOT_CONFIGURED,
@@ -186,7 +189,7 @@ struct ab8500_charger_usb_state {
* @autopower_cfg platform specific power config support for "pwron after pwrloss"
* @parent: Pointer to the struct ab8500
* @gpadc: Pointer to the struct gpadc
- * @bat: Pointer to the abx500_bm platform data
+ * @bm: Platform specific battery management information
* @flags: Structure for information about events triggered
* @usb_state: Structure for usb stack information
* @ac_chg: AC charger power supply
@@ -223,7 +226,7 @@ struct ab8500_charger {
bool autopower_cfg;
struct ab8500 *parent;
struct ab8500_gpadc *gpadc;
- struct abx500_bm_data *bat;
+ struct abx500_bm_data *bm;
struct ab8500_charger_event_flags flags;
struct ab8500_charger_usb_state usb_state;
struct ux500_charger ac_chg;
@@ -936,6 +939,88 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
}
/**
+ * ab8500_charger_set_current() - set charger current
+ * @di: pointer to the ab8500_charger structure
+ * @ich: charger current, in mA
+ * @reg: select what charger register to set
+ *
+ * Set charger current.
+ * There is no state machine in the AB to step up/down the charger
+ * current to avoid dips and spikes on MAIN, VBUS and VBAT when
+ * charging is started. Instead we need to implement
+ * this charger current step-up/down here.
+ * Returns error code in case of failure else 0(on success)
+ */
+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;
+ u8 reg_value;
+
+ switch (reg) {
+ case AB8500_MCH_IPT_CURLVL_REG:
+ shift_value = MAIN_CH_INPUT_CURR_SHIFT;
+ curr_index = ab8500_current_to_regval(ich);
+ break;
+ case AB8500_USBCH_IPT_CRNTLVL_REG:
+ shift_value = VBUS_IN_CURR_LIM_SHIFT;
+ curr_index = ab8500_vbus_in_curr_to_regval(ich);
+ break;
+ case AB8500_CH_OPT_CRNTLVL_REG:
+ shift_value = 0;
+ curr_index = ab8500_current_to_regval(ich);
+ break;
+ default:
+ dev_err(di->dev, "%s current register not valid\n", __func__);
+ return -ENXIO;
+ }
+
+ 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;
+ }
+ prev_curr_index = (reg_value >> shift_value);
+
+ /* only update current if it's been changed */
+ if (prev_curr_index == curr_index)
+ return 0;
+
+ dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n",
+ __func__, ich, reg);
+
+ if (prev_curr_index > curr_index) {
+ for (i = prev_curr_index - 1; i >= curr_index; i--) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8) i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+ }
+ } else {
+ for (i = prev_curr_index + 1; i <= curr_index; i++) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8) i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+ }
+ }
+ return ret;
+}
+
+/**
* ab8500_charger_set_vbus_in_curr() - set VBUS input current limit
* @di: pointer to the ab8500_charger structure
* @ich_in: charger input current limit
@@ -946,12 +1031,10 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
int ich_in)
{
- int ret;
- int input_curr_index;
int min_value;
/* We should always use to lowest current limit */
- min_value = min(di->bat->chg_params->usb_curr_max, ich_in);
+ min_value = min(di->bm->chg_params->usb_curr_max, ich_in);
switch (min_value) {
case 100:
@@ -966,19 +1049,38 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
break;
}
- input_curr_index = ab8500_vbus_in_curr_to_regval(min_value);
- if (input_curr_index < 0) {
- dev_err(di->dev, "VBUS input current limit too high\n");
- return -ENXIO;
- }
+ return ab8500_charger_set_current(di, min_value,
+ AB8500_USBCH_IPT_CRNTLVL_REG);
+}
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_USBCH_IPT_CRNTLVL_REG,
- input_curr_index << VBUS_IN_CURR_LIM_SHIFT);
- if (ret)
- dev_err(di->dev, "%s write failed\n", __func__);
+/**
+ * ab8500_charger_set_main_in_curr() - set main charger input current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_in: input charger current, in mA
+ *
+ * Set main charger input current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_main_in_curr(struct ab8500_charger *di,
+ int ich_in)
+{
+ return ab8500_charger_set_current(di, ich_in,
+ AB8500_MCH_IPT_CURLVL_REG);
+}
- return ret;
+/**
+ * ab8500_charger_set_output_curr() - set charger output current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_out: output charger current, in mA
+ *
+ * Set charger output current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_output_curr(struct ab8500_charger *di,
+ int ich_out)
+{
+ return ab8500_charger_set_current(di, ich_out,
+ AB8500_CH_OPT_CRNTLVL_REG);
}
/**
@@ -1074,7 +1176,7 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
volt_index = ab8500_voltage_to_regval(vset);
curr_index = ab8500_current_to_regval(iset);
input_curr_index = ab8500_current_to_regval(
- di->bat->chg_params->ac_curr_max);
+ di->bm->chg_params->ac_curr_max);
if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) {
dev_err(di->dev,
"Charger voltage or current too high, "
@@ -1090,23 +1192,24 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
return ret;
}
/* MainChInputCurr: current that can be drawn from the charger*/
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_MCH_IPT_CURLVL_REG,
- input_curr_index << MAIN_CH_INPUT_CURR_SHIFT);
+ ret = ab8500_charger_set_main_in_curr(di,
+ di->bm->chg_params->ac_curr_max);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s Failed to set MainChInputCurr\n",
+ __func__);
return ret;
}
/* ChOutputCurentLevel: protected output current */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ ret = ab8500_charger_set_output_curr(di, iset);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
/* Check if VBAT overshoot control should be enabled */
- if (!di->bat->enable_overshoot)
+ if (!di->bm->enable_overshoot)
overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N;
/* Enable Main Charger */
@@ -1158,12 +1261,11 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
return ret;
}
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1);
+ ret = ab8500_charger_set_output_curr(di, 0);
if (ret) {
- dev_err(di->dev,
- "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
} else {
@@ -1266,14 +1368,15 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
return ret;
}
/* ChOutputCurentLevel: protected output current */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ ret = ab8500_charger_set_output_curr(di, ich_out);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
/* Check if VBAT overshoot control should be enabled */
- if (!di->bat->enable_overshoot)
+ if (!di->bm->enable_overshoot)
overshoot = USB_CHG_NO_OVERSHOOT_ENA_N;
/* Enable USB Charger */
@@ -1366,7 +1469,6 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
int ich_out)
{
int ret;
- int curr_index;
struct ab8500_charger *di;
if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
@@ -1376,18 +1478,11 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
else
return -ENXIO;
- curr_index = ab8500_current_to_regval(ich_out);
- if (curr_index < 0) {
- dev_err(di->dev,
- "Charger current too high, "
- "charging not started\n");
- return -ENXIO;
- }
-
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ ret = ab8500_charger_set_output_curr(di, ich_out);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
@@ -2359,8 +2454,8 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
ret = abx500_set_register_interruptible(di->dev,
AB8500_RTC,
AB8500_RTC_BACKUP_CHG_REG,
- di->bat->bkup_bat_v |
- di->bat->bkup_bat_i);
+ di->bm->bkup_bat_v |
+ di->bm->bkup_bat_i);
if (ret) {
dev_err(di->dev, "failed to setup backup battery charging\n");
goto out;
@@ -2541,6 +2636,7 @@ static char *supply_interface[] = {
static int ab8500_charger_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
struct ab8500_charger *di;
int irq, i, charger_status, ret = 0;
@@ -2549,24 +2645,22 @@ static int ab8500_charger_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "%s no mem for ab8500_charger\n", __func__);
return -ENOMEM;
}
- di->bat = pdev->mfd_cell->platform_data;
- if (!di->bat) {
- if (np) {
- ret = bmdevs_of_probe(&pdev->dev, np, &di->bat);
- if (ret) {
- dev_err(&pdev->dev,
- "failed to get battery information\n");
- return ret;
- }
- di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
- } else {
- dev_err(&pdev->dev, "missing dt node for ab8500_charger\n");
- return -EINVAL;
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
}
- } else {
- dev_info(&pdev->dev, "falling back to legacy platform data\n");
+ di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
+ } else
di->autopower_cfg = false;
- }
/* get parent data */
di->dev = &pdev->dev;
OpenPOWER on IntegriCloud