2018-04-11 22:09:42 +07:00
|
|
|
/*
|
|
|
|
* ADP5061 I2C Programmable Linear Battery Charger
|
|
|
|
*
|
|
|
|
* Copyright 2018 Analog Devices Inc.
|
|
|
|
*
|
|
|
|
* Licensed under the GPL-2.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/pm.h>
|
|
|
|
#include <linux/mod_devicetable.h>
|
|
|
|
#include <linux/power_supply.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/regmap.h>
|
|
|
|
|
|
|
|
/* ADP5061 registers definition */
|
|
|
|
#define ADP5061_ID 0x00
|
|
|
|
#define ADP5061_REV 0x01
|
|
|
|
#define ADP5061_VINX_SET 0x02
|
|
|
|
#define ADP5061_TERM_SET 0x03
|
|
|
|
#define ADP5061_CHG_CURR 0x04
|
|
|
|
#define ADP5061_VOLTAGE_TH 0x05
|
|
|
|
#define ADP5061_TIMER_SET 0x06
|
|
|
|
#define ADP5061_FUNC_SET_1 0x07
|
|
|
|
#define ADP5061_FUNC_SET_2 0x08
|
|
|
|
#define ADP5061_INT_EN 0x09
|
|
|
|
#define ADP5061_INT_ACT 0x0A
|
|
|
|
#define ADP5061_CHG_STATUS_1 0x0B
|
|
|
|
#define ADP5061_CHG_STATUS_2 0x0C
|
|
|
|
#define ADP5061_FAULT 0x0D
|
|
|
|
#define ADP5061_BATTERY_SHORT 0x10
|
|
|
|
#define ADP5061_IEND 0x11
|
|
|
|
|
|
|
|
/* ADP5061_VINX_SET */
|
|
|
|
#define ADP5061_VINX_SET_ILIM_MSK GENMASK(3, 0)
|
|
|
|
#define ADP5061_VINX_SET_ILIM_MODE(x) (((x) & 0x0F) << 0)
|
|
|
|
|
|
|
|
/* ADP5061_TERM_SET */
|
|
|
|
#define ADP5061_TERM_SET_VTRM_MSK GENMASK(7, 2)
|
|
|
|
#define ADP5061_TERM_SET_VTRM_MODE(x) (((x) & 0x3F) << 2)
|
|
|
|
#define ADP5061_TERM_SET_CHG_VLIM_MSK GENMASK(1, 0)
|
|
|
|
#define ADP5061_TERM_SET_CHG_VLIM_MODE(x) (((x) & 0x03) << 0)
|
|
|
|
|
|
|
|
/* ADP5061_CHG_CURR */
|
|
|
|
#define ADP5061_CHG_CURR_ICHG_MSK GENMASK(6, 2)
|
|
|
|
#define ADP5061_CHG_CURR_ICHG_MODE(x) (((x) & 0x1F) << 2)
|
|
|
|
#define ADP5061_CHG_CURR_ITRK_DEAD_MSK GENMASK(1, 0)
|
|
|
|
#define ADP5061_CHG_CURR_ITRK_DEAD_MODE(x) (((x) & 0x03) << 0)
|
|
|
|
|
|
|
|
/* ADP5061_VOLTAGE_TH */
|
|
|
|
#define ADP5061_VOLTAGE_TH_DIS_RCH_MSK BIT(7)
|
|
|
|
#define ADP5061_VOLTAGE_TH_DIS_RCH_MODE(x) (((x) & 0x01) << 7)
|
|
|
|
#define ADP5061_VOLTAGE_TH_VRCH_MSK GENMASK(6, 5)
|
|
|
|
#define ADP5061_VOLTAGE_TH_VRCH_MODE(x) (((x) & 0x03) << 5)
|
|
|
|
#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK GENMASK(4, 3)
|
|
|
|
#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(x) (((x) & 0x03) << 3)
|
|
|
|
#define ADP5061_VOLTAGE_TH_VWEAK_MSK GENMASK(2, 0)
|
|
|
|
#define ADP5061_VOLTAGE_TH_VWEAK_MODE(x) (((x) & 0x07) << 0)
|
|
|
|
|
|
|
|
/* ADP5061_CHG_STATUS_1 */
|
|
|
|
#define ADP5061_CHG_STATUS_1_VIN_OV(x) (((x) >> 7) & 0x1)
|
|
|
|
#define ADP5061_CHG_STATUS_1_VIN_OK(x) (((x) >> 6) & 0x1)
|
|
|
|
#define ADP5061_CHG_STATUS_1_VIN_ILIM(x) (((x) >> 5) & 0x1)
|
|
|
|
#define ADP5061_CHG_STATUS_1_THERM_LIM(x) (((x) >> 4) & 0x1)
|
|
|
|
#define ADP5061_CHG_STATUS_1_CHDONE(x) (((x) >> 3) & 0x1)
|
|
|
|
#define ADP5061_CHG_STATUS_1_CHG_STATUS(x) (((x) >> 0) & 0x7)
|
|
|
|
|
|
|
|
/* ADP5061_CHG_STATUS_2 */
|
|
|
|
#define ADP5061_CHG_STATUS_2_THR_STATUS(x) (((x) >> 5) & 0x7)
|
|
|
|
#define ADP5061_CHG_STATUS_2_RCH_LIM_INFO(x) (((x) >> 3) & 0x1)
|
|
|
|
#define ADP5061_CHG_STATUS_2_BAT_STATUS(x) (((x) >> 0) & 0x7)
|
|
|
|
|
|
|
|
/* ADP5061_IEND */
|
|
|
|
#define ADP5061_IEND_IEND_MSK GENMASK(7, 5)
|
|
|
|
#define ADP5061_IEND_IEND_MODE(x) (((x) & 0x07) << 5)
|
|
|
|
|
|
|
|
#define ADP5061_NO_BATTERY 0x01
|
|
|
|
#define ADP5061_ICHG_MAX 1300 // mA
|
|
|
|
|
|
|
|
enum adp5061_chg_status {
|
|
|
|
ADP5061_CHG_OFF,
|
|
|
|
ADP5061_CHG_TRICKLE,
|
|
|
|
ADP5061_CHG_FAST_CC,
|
|
|
|
ADP5061_CHG_FAST_CV,
|
|
|
|
ADP5061_CHG_COMPLETE,
|
|
|
|
ADP5061_CHG_LDO_MODE,
|
|
|
|
ADP5061_CHG_TIMER_EXP,
|
|
|
|
ADP5061_CHG_BAT_DET,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_chg_type[4] = {
|
|
|
|
[ADP5061_CHG_OFF] = POWER_SUPPLY_CHARGE_TYPE_NONE,
|
|
|
|
[ADP5061_CHG_TRICKLE] = POWER_SUPPLY_CHARGE_TYPE_TRICKLE,
|
|
|
|
[ADP5061_CHG_FAST_CC] = POWER_SUPPLY_CHARGE_TYPE_FAST,
|
|
|
|
[ADP5061_CHG_FAST_CV] = POWER_SUPPLY_CHARGE_TYPE_FAST,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_vweak_th[8] = {
|
|
|
|
2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_prechg_current[4] = {
|
|
|
|
5, 10, 20, 80,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_vmin[4] = {
|
|
|
|
2000, 2500, 2600, 2900,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_const_chg_vmax[4] = {
|
|
|
|
3200, 3400, 3700, 3800,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_const_ichg[24] = {
|
|
|
|
50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650,
|
|
|
|
700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1200, 1300,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_vmax[36] = {
|
|
|
|
3800, 3820, 3840, 3860, 3880, 3900, 3920, 3940, 3960, 3980,
|
|
|
|
4000, 4020, 4040, 4060, 4080, 4100, 4120, 4140, 4160, 4180,
|
|
|
|
4200, 4220, 4240, 4260, 4280, 4300, 4320, 4340, 4360, 4380,
|
|
|
|
4400, 4420, 4440, 4460, 4480, 4500,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_in_current_lim[16] = {
|
|
|
|
100, 150, 200, 250, 300, 400, 500, 600, 700,
|
|
|
|
800, 900, 1000, 1200, 1500, 1800, 2100,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int adp5061_iend[8] = {
|
|
|
|
12500, 32500, 52500, 72500, 92500, 117500, 142500, 170000,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct adp5061_state {
|
|
|
|
struct i2c_client *client;
|
|
|
|
struct regmap *regmap;
|
|
|
|
struct power_supply *psy;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int adp5061_get_array_index(const int *array, u8 size, int val)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 1; i < size; i++) {
|
|
|
|
if (val < array[i])
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return i-1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_status(struct adp5061_state *st,
|
|
|
|
u8 *status1, u8 *status2)
|
|
|
|
{
|
|
|
|
u8 buf[2];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* CHG_STATUS1 and CHG_STATUS2 are adjacent regs */
|
|
|
|
ret = regmap_bulk_read(st->regmap, ADP5061_CHG_STATUS_1,
|
|
|
|
&buf[0], 2);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
*status1 = buf[0];
|
|
|
|
*status2 = buf[1];
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_input_current_limit(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int mode, ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_VINX_SET, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
mode = ADP5061_VINX_SET_ILIM_MODE(regval);
|
|
|
|
val->intval = adp5061_in_current_lim[mode] * 1000;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_input_current_limit(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/* Convert from uA to mA */
|
|
|
|
val /= 1000;
|
|
|
|
index = adp5061_get_array_index(adp5061_in_current_lim,
|
|
|
|
ARRAY_SIZE(adp5061_in_current_lim),
|
|
|
|
val);
|
|
|
|
if (index < 0)
|
|
|
|
return index;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_VINX_SET,
|
|
|
|
ADP5061_VINX_SET_ILIM_MSK,
|
|
|
|
ADP5061_VINX_SET_ILIM_MODE(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_min_voltage(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/* Convert from uV to mV */
|
|
|
|
val /= 1000;
|
|
|
|
index = adp5061_get_array_index(adp5061_vmin,
|
|
|
|
ARRAY_SIZE(adp5061_vmin),
|
|
|
|
val);
|
|
|
|
if (index < 0)
|
|
|
|
return index;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH,
|
|
|
|
ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK,
|
|
|
|
ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_min_voltage(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
regval = ((regval & ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK) >> 3);
|
|
|
|
val->intval = adp5061_vmin[regval] * 1000;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_chg_volt_lim(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int mode, ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
mode = ADP5061_TERM_SET_CHG_VLIM_MODE(regval);
|
|
|
|
val->intval = adp5061_const_chg_vmax[mode] * 1000;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_max_voltage(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_TERM_SET, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
regval = ((regval & ADP5061_TERM_SET_VTRM_MSK) >> 2) - 0x0F;
|
2018-07-11 14:58:13 +07:00
|
|
|
if (regval >= ARRAY_SIZE(adp5061_vmax))
|
|
|
|
regval = ARRAY_SIZE(adp5061_vmax) - 1;
|
2018-04-11 22:09:42 +07:00
|
|
|
|
|
|
|
val->intval = adp5061_vmax[regval] * 1000;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_max_voltage(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
int vmax_index;
|
|
|
|
|
|
|
|
/* Convert from uV to mV */
|
|
|
|
val /= 1000;
|
|
|
|
if (val > 4500)
|
|
|
|
val = 4500;
|
|
|
|
|
|
|
|
vmax_index = adp5061_get_array_index(adp5061_vmax,
|
|
|
|
ARRAY_SIZE(adp5061_vmax), val);
|
|
|
|
if (vmax_index < 0)
|
|
|
|
return vmax_index;
|
|
|
|
|
|
|
|
vmax_index += 0x0F;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_TERM_SET,
|
|
|
|
ADP5061_TERM_SET_VTRM_MSK,
|
|
|
|
ADP5061_TERM_SET_VTRM_MODE(vmax_index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_const_chg_vmax(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/* Convert from uV to mV */
|
|
|
|
val /= 1000;
|
|
|
|
index = adp5061_get_array_index(adp5061_const_chg_vmax,
|
|
|
|
ARRAY_SIZE(adp5061_const_chg_vmax),
|
|
|
|
val);
|
|
|
|
if (index < 0)
|
|
|
|
return index;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_TERM_SET,
|
|
|
|
ADP5061_TERM_SET_CHG_VLIM_MSK,
|
|
|
|
ADP5061_TERM_SET_CHG_VLIM_MODE(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_const_chg_current(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/* Convert from uA to mA */
|
|
|
|
val /= 1000;
|
|
|
|
if (val > ADP5061_ICHG_MAX)
|
|
|
|
val = ADP5061_ICHG_MAX;
|
|
|
|
|
|
|
|
index = adp5061_get_array_index(adp5061_const_ichg,
|
|
|
|
ARRAY_SIZE(adp5061_const_ichg),
|
|
|
|
val);
|
|
|
|
if (index < 0)
|
|
|
|
return index;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_CHG_CURR,
|
|
|
|
ADP5061_CHG_CURR_ICHG_MSK,
|
|
|
|
ADP5061_CHG_CURR_ICHG_MODE(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_const_chg_current(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
regval = ((regval & ADP5061_CHG_CURR_ICHG_MSK) >> 2);
|
2018-07-11 14:58:13 +07:00
|
|
|
if (regval >= ARRAY_SIZE(adp5061_const_ichg))
|
|
|
|
regval = ARRAY_SIZE(adp5061_const_ichg) - 1;
|
2018-04-11 22:09:42 +07:00
|
|
|
|
|
|
|
val->intval = adp5061_const_ichg[regval] * 1000;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_prechg_current(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_CHG_CURR, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
regval &= ADP5061_CHG_CURR_ITRK_DEAD_MSK;
|
|
|
|
val->intval = adp5061_prechg_current[regval] * 1000;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_prechg_current(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/* Convert from uA to mA */
|
|
|
|
val /= 1000;
|
|
|
|
index = adp5061_get_array_index(adp5061_prechg_current,
|
|
|
|
ARRAY_SIZE(adp5061_prechg_current),
|
|
|
|
val);
|
|
|
|
if (index < 0)
|
|
|
|
return index;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_CHG_CURR,
|
|
|
|
ADP5061_CHG_CURR_ITRK_DEAD_MSK,
|
|
|
|
ADP5061_CHG_CURR_ITRK_DEAD_MODE(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_vweak_th(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
regval &= ADP5061_VOLTAGE_TH_VWEAK_MSK;
|
|
|
|
val->intval = adp5061_vweak_th[regval] * 1000;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_vweak_th(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
|
|
|
/* Convert from uV to mV */
|
|
|
|
val /= 1000;
|
|
|
|
index = adp5061_get_array_index(adp5061_vweak_th,
|
|
|
|
ARRAY_SIZE(adp5061_vweak_th),
|
|
|
|
val);
|
|
|
|
if (index < 0)
|
|
|
|
return index;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH,
|
|
|
|
ADP5061_VOLTAGE_TH_VWEAK_MSK,
|
|
|
|
ADP5061_VOLTAGE_TH_VWEAK_MODE(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_chg_type(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
u8 status1, status2;
|
|
|
|
int chg_type, ret;
|
|
|
|
|
|
|
|
ret = adp5061_get_status(st, &status1, &status2);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
chg_type = adp5061_chg_type[ADP5061_CHG_STATUS_1_CHG_STATUS(status1)];
|
|
|
|
if (chg_type > ADP5061_CHG_FAST_CV)
|
|
|
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
|
|
else
|
|
|
|
val->intval = chg_type;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_charger_status(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
u8 status1, status2;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = adp5061_get_status(st, &status1, &status2);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
switch (ADP5061_CHG_STATUS_1_CHG_STATUS(status1)) {
|
|
|
|
case ADP5061_CHG_OFF:
|
|
|
|
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
|
|
break;
|
|
|
|
case ADP5061_CHG_TRICKLE:
|
|
|
|
case ADP5061_CHG_FAST_CC:
|
|
|
|
case ADP5061_CHG_FAST_CV:
|
|
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
|
|
break;
|
|
|
|
case ADP5061_CHG_COMPLETE:
|
|
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
|
|
break;
|
|
|
|
case ADP5061_CHG_TIMER_EXP:
|
|
|
|
/* The battery must be discharging if there is a charge fault */
|
|
|
|
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_battery_status(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
u8 status1, status2;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = adp5061_get_status(st, &status1, &status2);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
switch (ADP5061_CHG_STATUS_2_BAT_STATUS(status2)) {
|
|
|
|
case 0x0: /* Battery monitor off */
|
|
|
|
case 0x1: /* No battery */
|
|
|
|
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
|
|
|
|
break;
|
|
|
|
case 0x2: /* VBAT < VTRK */
|
|
|
|
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
|
|
|
break;
|
|
|
|
case 0x3: /* VTRK < VBAT_SNS < VWEAK */
|
|
|
|
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
|
|
|
break;
|
|
|
|
case 0x4: /* VBAT_SNS > VWEAK */
|
|
|
|
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_termination_current(struct adp5061_state *st,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
unsigned int regval;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regmap_read(st->regmap, ADP5061_IEND, ®val);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
regval = (regval & ADP5061_IEND_IEND_MSK) >> 5;
|
|
|
|
val->intval = adp5061_iend[regval];
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_termination_current(struct adp5061_state *st, int val)
|
|
|
|
{
|
|
|
|
int index;
|
|
|
|
|
|
|
|
index = adp5061_get_array_index(adp5061_iend,
|
|
|
|
ARRAY_SIZE(adp5061_iend),
|
|
|
|
val);
|
|
|
|
if (index < 0)
|
|
|
|
return index;
|
|
|
|
|
|
|
|
return regmap_update_bits(st->regmap, ADP5061_IEND,
|
|
|
|
ADP5061_IEND_IEND_MSK,
|
|
|
|
ADP5061_IEND_IEND_MODE(index));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_get_property(struct power_supply *psy,
|
|
|
|
enum power_supply_property psp,
|
|
|
|
union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
struct adp5061_state *st = power_supply_get_drvdata(psy);
|
|
|
|
u8 status1, status2;
|
|
|
|
int mode, ret;
|
|
|
|
|
|
|
|
switch (psp) {
|
|
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
|
|
ret = adp5061_get_status(st, &status1, &status2);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
mode = ADP5061_CHG_STATUS_2_BAT_STATUS(status2);
|
|
|
|
if (mode == ADP5061_NO_BATTERY)
|
|
|
|
val->intval = 0;
|
|
|
|
else
|
|
|
|
val->intval = 1;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
|
|
return adp5061_get_chg_type(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
|
|
/* This property is used to indicate the input current
|
|
|
|
* limit into VINx (ILIM)
|
|
|
|
*/
|
|
|
|
return adp5061_get_input_current_limit(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
|
|
/* This property is used to indicate the termination
|
|
|
|
* voltage (VTRM)
|
|
|
|
*/
|
|
|
|
return adp5061_get_max_voltage(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
|
|
|
/*
|
|
|
|
* This property is used to indicate the trickle to fast
|
|
|
|
* charge threshold (VTRK_DEAD)
|
|
|
|
*/
|
|
|
|
return adp5061_get_min_voltage(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
|
|
/* This property is used to indicate the charging
|
|
|
|
* voltage limit (CHG_VLIM)
|
|
|
|
*/
|
|
|
|
return adp5061_get_chg_volt_lim(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
|
|
/*
|
|
|
|
* This property is used to indicate the value of the constant
|
|
|
|
* current charge (ICHG)
|
|
|
|
*/
|
|
|
|
return adp5061_get_const_chg_current(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
|
|
/*
|
|
|
|
* This property is used to indicate the value of the trickle
|
|
|
|
* and weak charge currents (ITRK_DEAD)
|
|
|
|
*/
|
|
|
|
return adp5061_get_prechg_current(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
|
|
|
|
/*
|
|
|
|
* This property is used to set the VWEAK threshold
|
|
|
|
* bellow this value, weak charge mode is entered
|
|
|
|
* above this value, fast chargerge mode is entered
|
|
|
|
*/
|
|
|
|
return adp5061_get_vweak_th(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
|
|
/*
|
|
|
|
* Indicate the charger status in relation to power
|
|
|
|
* supply status property
|
|
|
|
*/
|
|
|
|
return adp5061_get_charger_status(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
|
|
|
/*
|
|
|
|
* Indicate the battery status in relation to power
|
|
|
|
* supply capacity level property
|
|
|
|
*/
|
|
|
|
return adp5061_get_battery_status(st, val);
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
|
|
/* Indicate the values of the termination current */
|
|
|
|
return adp5061_get_termination_current(st, val);
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_set_property(struct power_supply *psy,
|
|
|
|
enum power_supply_property psp,
|
|
|
|
const union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
struct adp5061_state *st = power_supply_get_drvdata(psy);
|
|
|
|
|
|
|
|
switch (psp) {
|
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
|
|
return adp5061_set_input_current_limit(st, val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
|
|
return adp5061_set_max_voltage(st, val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
|
|
|
return adp5061_set_min_voltage(st, val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
|
|
return adp5061_set_const_chg_vmax(st, val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
|
|
return adp5061_set_const_chg_current(st, val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
|
|
return adp5061_set_prechg_current(st, val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
|
|
|
|
return adp5061_set_vweak_th(st, val->intval);
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
|
|
return adp5061_set_termination_current(st, val->intval);
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int adp5061_prop_writeable(struct power_supply *psy,
|
|
|
|
enum power_supply_property psp)
|
|
|
|
{
|
|
|
|
switch (psp) {
|
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
|
|
|
|
case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_AVG:
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
|
|
|
|
return 1;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum power_supply_property adp5061_props[] = {
|
|
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
|
|
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
|
|
POWER_SUPPLY_PROP_VOLTAGE_MIN,
|
|
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
|
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
|
|
|
|
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
|
|
|
|
POWER_SUPPLY_PROP_VOLTAGE_AVG,
|
|
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
|
|
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
|
|
|
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_config adp5061_regmap_config = {
|
|
|
|
.reg_bits = 8,
|
|
|
|
.val_bits = 8,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct power_supply_desc adp5061_desc = {
|
|
|
|
.name = "adp5061",
|
|
|
|
.type = POWER_SUPPLY_TYPE_USB,
|
|
|
|
.get_property = adp5061_get_property,
|
|
|
|
.set_property = adp5061_set_property,
|
|
|
|
.property_is_writeable = adp5061_prop_writeable,
|
|
|
|
.properties = adp5061_props,
|
|
|
|
.num_properties = ARRAY_SIZE(adp5061_props),
|
|
|
|
};
|
|
|
|
|
|
|
|
static int adp5061_probe(struct i2c_client *client,
|
|
|
|
const struct i2c_device_id *id)
|
|
|
|
{
|
|
|
|
struct power_supply_config psy_cfg = {};
|
|
|
|
struct adp5061_state *st;
|
|
|
|
|
|
|
|
st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
|
|
|
|
if (!st)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
st->client = client;
|
|
|
|
st->regmap = devm_regmap_init_i2c(client,
|
|
|
|
&adp5061_regmap_config);
|
|
|
|
if (IS_ERR(st->regmap)) {
|
|
|
|
dev_err(&client->dev, "Failed to initialize register map\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
i2c_set_clientdata(client, st);
|
|
|
|
psy_cfg.drv_data = st;
|
|
|
|
|
|
|
|
st->psy = devm_power_supply_register(&client->dev,
|
|
|
|
&adp5061_desc,
|
|
|
|
&psy_cfg);
|
|
|
|
|
|
|
|
if (IS_ERR(st->psy)) {
|
|
|
|
dev_err(&client->dev, "Failed to register power supply\n");
|
|
|
|
return PTR_ERR(st->psy);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct i2c_device_id adp5061_id[] = {
|
|
|
|
{ "adp5061", 0},
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, adp5061_id);
|
|
|
|
|
|
|
|
static struct i2c_driver adp5061_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = KBUILD_MODNAME,
|
|
|
|
},
|
|
|
|
.probe = adp5061_probe,
|
|
|
|
.id_table = adp5061_id,
|
|
|
|
};
|
|
|
|
module_i2c_driver(adp5061_driver);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("Analog Devices adp5061 battery charger driver");
|
|
|
|
MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
|
|
|
|
MODULE_LICENSE("GPL v2");
|