1093 lines
34 KiB
C
1093 lines
34 KiB
C
#define pr_fmt(x) KBUILD_MODNAME ": " x "\n"
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/device.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/param.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/of.h>
|
|
#include <linux/timekeeping.h>
|
|
#include <linux/types.h>
|
|
#include <linux/string.h>
|
|
#include <asm/irq.h>
|
|
#include <linux/cdev.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/freezer.h>
|
|
#include <linux/err.h>
|
|
#include <linux/mfd/axp2101.h>
|
|
#include "axp803_charger.h"
|
|
|
|
struct axp803_bat_power {
|
|
char *name;
|
|
struct device *dev;
|
|
struct axp_config_info dts_info;
|
|
struct regmap *regmap;
|
|
struct power_supply *bat_supply;
|
|
struct delayed_work bat_supply_mon;
|
|
};
|
|
|
|
static bool charger_debug;
|
|
|
|
static int axp803_get_bat_health(struct axp803_bat_power *bat_power)
|
|
{
|
|
unsigned int reg_value;
|
|
int ret = 0;
|
|
|
|
ret = regmap_read(bat_power->regmap, AXP803_MODE_CHGSTATUS, ®_value);
|
|
if (reg_value & AXP803_FAULT_LOG_BATINACT)
|
|
return POWER_SUPPLY_HEALTH_DEAD;
|
|
else if (reg_value & AXP803_FAULT_LOG_OVER_TEMP)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
else if (reg_value & AXP803_FAULT_LOG_COLD)
|
|
return POWER_SUPPLY_HEALTH_COLD;
|
|
else
|
|
return POWER_SUPPLY_HEALTH_GOOD;
|
|
}
|
|
|
|
static inline int axp803_vbat_to_mV(unsigned int reg)
|
|
{
|
|
return ((int)(((reg >> 8) << 4) | (reg & 0x000F))) * 1100 / 1000;
|
|
}
|
|
|
|
static int axp803_get_vbat(struct axp803_bat_power *bat_power)
|
|
{
|
|
unsigned char temp_val[2];
|
|
unsigned int res;
|
|
int ret = 0;
|
|
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_VBATH_RES, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
res = (temp_val[0] << 8) | temp_val[1];
|
|
|
|
return axp803_vbat_to_mV(res);
|
|
}
|
|
|
|
static inline int axp803_icharge_to_mA(unsigned int reg)
|
|
{
|
|
return (int)(((reg >> 8) << 4) | (reg & 0x000F));
|
|
}
|
|
|
|
static int axp803_get_ibat(struct axp803_bat_power *bat_power)
|
|
{
|
|
unsigned char tmp[2];
|
|
unsigned int res;
|
|
|
|
regmap_bulk_read(bat_power->regmap, AXP803_IBATH_REG, tmp, 2);
|
|
res = (tmp[0] << 8) | tmp[1];
|
|
|
|
return axp803_icharge_to_mA(res);
|
|
}
|
|
|
|
static int axp803_get_disibat(struct axp803_bat_power *bat_power)
|
|
{
|
|
unsigned char tmp[2];
|
|
unsigned int dis_res;
|
|
|
|
regmap_bulk_read(bat_power->regmap, AXP803_DISIBATH_REG, tmp, 2);
|
|
dis_res = (tmp[0] << 8) | tmp[1];
|
|
|
|
return axp803_icharge_to_mA(dis_res);
|
|
}
|
|
|
|
static int axp803_set_bat_chg_cur(struct axp803_bat_power *bat_power, int cur)
|
|
{
|
|
uint8_t tmp = 0;
|
|
struct regmap *map = bat_power->regmap;
|
|
|
|
if (cur == 0)
|
|
regmap_update_bits(map, AXP803_CHARGE1, 0x80, 0x00);
|
|
else
|
|
regmap_update_bits(map, AXP803_CHARGE1, 0x80, 0x80);
|
|
|
|
if (cur >= 200 && cur <= 2800) {
|
|
tmp = (cur - 200) / 200;
|
|
regmap_update_bits(map, AXP803_CHARGE1, 0x0f, tmp);
|
|
} else if (cur < 200) {
|
|
regmap_update_bits(map, AXP803_CHARGE1, 0x0f, 0x00);
|
|
} else {
|
|
regmap_update_bits(map, AXP803_CHARGE1, 0x0f, 0x0d);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp803_get_rest_cap(struct axp803_bat_power *bat_power)
|
|
{
|
|
unsigned char temp_val[2];
|
|
unsigned int reg_value;
|
|
int batt_max_cap, coulumb_counter;
|
|
int rest_vol = 0;
|
|
int ocv_vol = 0;
|
|
int rdc = 0;
|
|
int ret = 0;
|
|
|
|
ret = regmap_read(bat_power->regmap, AXP803_CAP, ®_value);
|
|
if (ret)
|
|
return ret;
|
|
if (reg_value & 0x80)
|
|
rest_vol = (int)(reg_value & 0x7F);
|
|
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_COUCNT0, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
coulumb_counter = (((temp_val[0] & 0x7f) << 8) + temp_val[1])
|
|
* 1456 / 1000;
|
|
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_BATCAP0, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
batt_max_cap = (((temp_val[0] & 0x7f) << 8) + temp_val[1])
|
|
* 1456 / 1000;
|
|
|
|
if (charger_debug) {
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_OCVBATH_RES, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
ocv_vol = ((temp_val[0] << 4) | (temp_val[1] & 0xF)) * 1100 / 1000;
|
|
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_RDC0, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
rdc = (((temp_val[0] & 0x1f) << 8) + temp_val[1]) * 10742 / 10000;
|
|
|
|
pr_debug("calc_info: ocv_vol:%d rdc:%d coulumb_counter:%d batt_max_cap:%d\n",
|
|
ocv_vol, rdc, coulumb_counter, batt_max_cap);
|
|
}
|
|
|
|
return rest_vol;
|
|
}
|
|
|
|
static inline int axp_vts_to_mV(u16 reg)
|
|
{
|
|
return ((int)(((reg >> 8) << 4) | (reg & 0x000F))) * 800 / 1000;
|
|
}
|
|
|
|
static inline int axp_vts_to_temp(int data,
|
|
const struct axp_config_info *axp_config)
|
|
{
|
|
int temp;
|
|
|
|
if (data < 80 || !axp_config->pmu_bat_temp_enable)
|
|
return 30;
|
|
else if (data < axp_config->pmu_bat_temp_para16)
|
|
return 80;
|
|
else if (data <= axp_config->pmu_bat_temp_para15) {
|
|
temp = 70 + (axp_config->pmu_bat_temp_para15-data)*10/
|
|
(axp_config->pmu_bat_temp_para15-axp_config->pmu_bat_temp_para16);
|
|
} else if (data <= axp_config->pmu_bat_temp_para14) {
|
|
temp = 60 + (axp_config->pmu_bat_temp_para14-data)*10/
|
|
(axp_config->pmu_bat_temp_para14-axp_config->pmu_bat_temp_para15);
|
|
} else if (data <= axp_config->pmu_bat_temp_para13) {
|
|
temp = 55 + (axp_config->pmu_bat_temp_para13-data)*5/
|
|
(axp_config->pmu_bat_temp_para13-axp_config->pmu_bat_temp_para14);
|
|
} else if (data <= axp_config->pmu_bat_temp_para12) {
|
|
temp = 50 + (axp_config->pmu_bat_temp_para12-data)*5/
|
|
(axp_config->pmu_bat_temp_para12-axp_config->pmu_bat_temp_para13);
|
|
} else if (data <= axp_config->pmu_bat_temp_para11) {
|
|
temp = 45 + (axp_config->pmu_bat_temp_para11-data)*5/
|
|
(axp_config->pmu_bat_temp_para11-axp_config->pmu_bat_temp_para12);
|
|
} else if (data <= axp_config->pmu_bat_temp_para10) {
|
|
temp = 40 + (axp_config->pmu_bat_temp_para10-data)*5/
|
|
(axp_config->pmu_bat_temp_para10-axp_config->pmu_bat_temp_para11);
|
|
} else if (data <= axp_config->pmu_bat_temp_para9) {
|
|
temp = 30 + (axp_config->pmu_bat_temp_para9-data)*10/
|
|
(axp_config->pmu_bat_temp_para9-axp_config->pmu_bat_temp_para10);
|
|
} else if (data <= axp_config->pmu_bat_temp_para8) {
|
|
temp = 20 + (axp_config->pmu_bat_temp_para8-data)*10/
|
|
(axp_config->pmu_bat_temp_para8-axp_config->pmu_bat_temp_para9);
|
|
} else if (data <= axp_config->pmu_bat_temp_para7) {
|
|
temp = 10 + (axp_config->pmu_bat_temp_para7-data)*10/
|
|
(axp_config->pmu_bat_temp_para7-axp_config->pmu_bat_temp_para8);
|
|
} else if (data <= axp_config->pmu_bat_temp_para6) {
|
|
temp = 5 + (axp_config->pmu_bat_temp_para6-data)*5/
|
|
(axp_config->pmu_bat_temp_para6-axp_config->pmu_bat_temp_para7);
|
|
} else if (data <= axp_config->pmu_bat_temp_para5) {
|
|
temp = 0 + (axp_config->pmu_bat_temp_para5-data)*5/
|
|
(axp_config->pmu_bat_temp_para5-axp_config->pmu_bat_temp_para6);
|
|
} else if (data <= axp_config->pmu_bat_temp_para4) {
|
|
temp = -5 + (axp_config->pmu_bat_temp_para4-data)*5/
|
|
(axp_config->pmu_bat_temp_para4-axp_config->pmu_bat_temp_para5);
|
|
} else if (data <= axp_config->pmu_bat_temp_para3) {
|
|
temp = -10 + (axp_config->pmu_bat_temp_para3-data)*5/
|
|
(axp_config->pmu_bat_temp_para3-axp_config->pmu_bat_temp_para4);
|
|
} else if (data <= axp_config->pmu_bat_temp_para2) {
|
|
temp = -15 + (axp_config->pmu_bat_temp_para2-data)*5/
|
|
(axp_config->pmu_bat_temp_para2-axp_config->pmu_bat_temp_para3);
|
|
} else if (data <= axp_config->pmu_bat_temp_para1) {
|
|
temp = -25 + (axp_config->pmu_bat_temp_para1-data)*10/
|
|
(axp_config->pmu_bat_temp_para1-axp_config->pmu_bat_temp_para2);
|
|
} else
|
|
temp = -25;
|
|
return temp;
|
|
}
|
|
|
|
static int axp803_get_bat_temp(struct axp803_bat_power *bat_power)
|
|
{
|
|
unsigned char temp_val[2];
|
|
unsigned short ts_res;
|
|
int bat_temp_mv, bat_temp;
|
|
int ret = 0;
|
|
|
|
struct axp_config_info *axp_config = &bat_power->dts_info;
|
|
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_VTS_RES, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ts_res = ((unsigned short) temp_val[0] << 8) | temp_val[1];
|
|
bat_temp_mv = axp_vts_to_mV(ts_res);
|
|
bat_temp = axp_vts_to_temp(bat_temp_mv, axp_config);
|
|
|
|
pr_debug("bat_temp: %d\n", bat_temp);
|
|
|
|
return bat_temp;
|
|
}
|
|
|
|
static int axp803_bat_get_max_voltage(struct axp803_bat_power *bat_power)
|
|
{
|
|
int ret, reg;
|
|
int val = 0;
|
|
|
|
ret = regmap_read(bat_power->regmap, AXP803_CHARGE1, ®);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (reg & AXP803_CHRG_CTRL1_TGT_VOLT) {
|
|
case AXP803_CHRG_CTRL1_TGT_4_1V:
|
|
val = 4100000;
|
|
break;
|
|
case AXP803_CHRG_CTRL1_TGT_4_15V:
|
|
val = 4150000;
|
|
break;
|
|
case AXP803_CHRG_CTRL1_TGT_4_2V:
|
|
val = 4200000;
|
|
break;
|
|
case AXP803_CHRG_CTRL1_TGT_4_35V:
|
|
val = 4350000;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int axp803_get_bat_status(struct power_supply *psy,
|
|
union power_supply_propval *val)
|
|
{
|
|
bool bat_det, bat_charging;
|
|
bool ac_valid, vbus_valid;
|
|
unsigned int rest_vol;
|
|
unsigned int reg_value;
|
|
int ret;
|
|
|
|
struct axp803_bat_power *bat_power = power_supply_get_drvdata(psy);
|
|
|
|
ret = regmap_read(bat_power->regmap, AXP803_MODE_CHGSTATUS, ®_value);
|
|
if (ret)
|
|
return ret;
|
|
bat_det = !!(reg_value & AXP803_CHGSTATUS_BAT_PST_VALID) &&
|
|
!!(reg_value & AXP803_CHGSTATUS_BAT_PRESENT);
|
|
bat_charging = !!(reg_value & AXP803_CHGSTATUS_BAT_CHARGING);
|
|
|
|
ret = regmap_read(bat_power->regmap, AXP803_STATUS, ®_value);
|
|
if (ret)
|
|
return ret;
|
|
ac_valid = !!(reg_value & AXP803_STATUS_AC_USED);
|
|
vbus_valid = !!(reg_value & AXP803_STATUS_VBUS_USED);
|
|
|
|
rest_vol = axp803_get_rest_cap(bat_power);
|
|
|
|
if (bat_det) {
|
|
if (ac_valid || vbus_valid) {
|
|
if (rest_vol == 100)
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
else if (bat_charging)
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
else
|
|
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
} else {
|
|
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
}
|
|
} else {
|
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_property axp803_bat_props[] = {
|
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
POWER_SUPPLY_PROP_CHARGE_COUNTER,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_CHARGE_FULL,
|
|
POWER_SUPPLY_PROP_CAPACITY,
|
|
POWER_SUPPLY_PROP_TEMP,
|
|
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
|
|
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
|
|
POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN,
|
|
POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX,
|
|
};
|
|
|
|
static int axp803_bat_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
int ret = 0;
|
|
unsigned int reg_value;
|
|
unsigned char temp_val[2];
|
|
struct axp803_bat_power *bat_power = power_supply_get_drvdata(psy);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
val->strval = psy->desc->name;
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
ret = axp803_get_bat_status(psy, val);
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = axp803_get_bat_health(bat_power);
|
|
break;
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_COUNTER:
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_COUCNT0, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
val->intval = (((temp_val[0] & 0x7f) << 8) + temp_val[1]) * 1456;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
|
val->intval = axp803_bat_get_max_voltage(bat_power);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
ret = regmap_read(bat_power->regmap, AXP803_VOFF_SET, ®_value);
|
|
if (ret)
|
|
return ret;
|
|
val->intval = 2600000 + 100000 * (reg_value & AXP803_V_OFF_MASK);
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
ret = regmap_read(bat_power->regmap, AXP803_STATUS, ®_value);
|
|
if (ret)
|
|
return ret;
|
|
val->intval = !(reg_value & AXP803_STATUS_BAT_CUR_DIRCT);
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
ret = regmap_read(bat_power->regmap, AXP803_MODE_CHGSTATUS, ®_value);
|
|
if (ret)
|
|
return ret;
|
|
val->intval = !!(reg_value & AXP803_CHGSTATUS_BAT_PST_VALID) &&
|
|
!!(reg_value & AXP803_CHGSTATUS_BAT_PRESENT);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
val->intval = axp803_get_vbat(bat_power) * 1000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
val->intval = (axp803_get_ibat(bat_power)
|
|
- axp803_get_disibat(bat_power)) * 1000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_FULL:
|
|
ret = regmap_bulk_read(bat_power->regmap, AXP803_BATCAP0, temp_val, 2);
|
|
if (ret < 0)
|
|
return ret;
|
|
val->intval = (((temp_val[0] & 0x7f) << 8) + temp_val[1]) * 1456;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
val->intval = axp803_get_rest_cap(bat_power);
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
val->intval = axp803_get_bat_temp(bat_power) * 10;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
|
|
ret = bat_power->dts_info.pmu_bat_shutdown_ltf;
|
|
val->intval = axp_vts_to_temp(ret, &bat_power->dts_info) * 10;
|
|
if (!bat_power->dts_info.pmu_bat_temp_enable)
|
|
val->intval = -200000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
|
|
ret = bat_power->dts_info.pmu_bat_shutdown_htf;
|
|
val->intval = axp_vts_to_temp(ret, &bat_power->dts_info) * 10;
|
|
if (!bat_power->dts_info.pmu_bat_temp_enable)
|
|
val->intval = 200000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN:
|
|
ret = bat_power->dts_info.pmu_bat_charge_ltf;
|
|
val->intval = axp_vts_to_temp(ret, &bat_power->dts_info) * 10;
|
|
if (!bat_power->dts_info.pmu_bat_temp_enable)
|
|
val->intval = -200000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX:
|
|
ret = bat_power->dts_info.pmu_bat_charge_htf;
|
|
val->intval = axp_vts_to_temp(ret, &bat_power->dts_info) * 10;
|
|
if (!bat_power->dts_info.pmu_bat_temp_enable)
|
|
val->intval = 200000;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static const struct power_supply_desc axp803_bat_desc = {
|
|
.name = "axp803-battery",
|
|
.type = POWER_SUPPLY_TYPE_BATTERY,
|
|
.get_property = axp803_bat_get_property,
|
|
.properties = axp803_bat_props,
|
|
.num_properties = ARRAY_SIZE(axp803_bat_props),
|
|
};
|
|
|
|
static int axp803_bat_power_init(struct axp803_bat_power *bat_power)
|
|
{
|
|
unsigned char ocv_cap[32];
|
|
unsigned int val;
|
|
int cur_coulomb_counter, rdc;
|
|
int i;
|
|
int update_min_times[8] = {30, 60, 120, 164, 0, 5, 10, 20};
|
|
int ocv_cou_adjust_time[4] = {60, 120, 15, 30};
|
|
|
|
struct axp_config_info *axp_config = &bat_power->dts_info;
|
|
struct regmap *map = bat_power->regmap;
|
|
|
|
if (axp_config->pmu_init_chgend_rate == 10)
|
|
val = 0;
|
|
else
|
|
val = 1;
|
|
val <<= 4;
|
|
regmap_update_bits(map, AXP803_CHARGE1, 0x10, val);
|
|
|
|
if (axp_config->pmu_init_chg_pretime < 40)
|
|
axp_config->pmu_init_chg_pretime = 40;
|
|
|
|
if (axp_config->pmu_init_chg_csttime < 360)
|
|
axp_config->pmu_init_chg_csttime = 360;
|
|
|
|
val = ((((axp_config->pmu_init_chg_pretime - 40) / 10) << 6)
|
|
| ((axp_config->pmu_init_chg_csttime - 360) / 120));
|
|
regmap_update_bits(map, AXP803_CHARGE2, 0xc2, val);
|
|
|
|
/* adc set */
|
|
val = AXP803_ADC_BATVOL_ENABLE | AXP803_ADC_BATCUR_ENABLE;
|
|
if (axp_config->pmu_bat_temp_enable != 0)
|
|
val = val | AXP803_ADC_TSVOL_ENABLE;
|
|
regmap_update_bits(map, AXP803_ADC_EN,
|
|
AXP803_ADC_BATVOL_ENABLE
|
|
| AXP803_ADC_BATCUR_ENABLE
|
|
| AXP803_ADC_TSVOL_ENABLE,
|
|
val);
|
|
|
|
regmap_read(map, AXP803_ADC_SPEED_SET, &val);
|
|
switch (axp_config->pmu_init_adc_freq / 100) {
|
|
case 1:
|
|
val &= ~(0x3 << 4);
|
|
break;
|
|
case 2:
|
|
val &= ~(0x3 << 4);
|
|
val |= 0x1 << 4;
|
|
break;
|
|
case 4:
|
|
val &= ~(0x3 << 4);
|
|
val |= 0x2 << 4;
|
|
break;
|
|
case 8:
|
|
val |= 0x3 << 4;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (axp_config->pmu_bat_temp_enable != 0)
|
|
val &= (~(0x1 << 2));
|
|
regmap_write(map, AXP803_ADC_SPEED_SET, val);
|
|
|
|
/* bat para */
|
|
regmap_write(map, AXP803_WARNING_LEVEL,
|
|
((axp_config->pmu_battery_warning_level1 - 5) << 4)
|
|
+ axp_config->pmu_battery_warning_level2);
|
|
|
|
/* set target voltage */
|
|
if (axp_config->pmu_init_chgvol < 4150) {
|
|
val = 0;
|
|
} else if (axp_config->pmu_init_chgvol < 4200) {
|
|
val = 1;
|
|
} else if (axp_config->pmu_init_chgvol < 4350) {
|
|
val = 2;
|
|
} else {
|
|
val = 3;
|
|
}
|
|
val <<= 5;
|
|
regmap_update_bits(map, AXP803_CHARGE1, 0x60, val);
|
|
|
|
ocv_cap[0] = axp_config->pmu_bat_para1;
|
|
ocv_cap[1] = axp_config->pmu_bat_para2;
|
|
ocv_cap[2] = axp_config->pmu_bat_para3;
|
|
ocv_cap[3] = axp_config->pmu_bat_para4;
|
|
ocv_cap[4] = axp_config->pmu_bat_para5;
|
|
ocv_cap[5] = axp_config->pmu_bat_para6;
|
|
ocv_cap[6] = axp_config->pmu_bat_para7;
|
|
ocv_cap[7] = axp_config->pmu_bat_para8;
|
|
ocv_cap[8] = axp_config->pmu_bat_para9;
|
|
ocv_cap[9] = axp_config->pmu_bat_para10;
|
|
ocv_cap[10] = axp_config->pmu_bat_para11;
|
|
ocv_cap[11] = axp_config->pmu_bat_para12;
|
|
ocv_cap[12] = axp_config->pmu_bat_para13;
|
|
ocv_cap[13] = axp_config->pmu_bat_para14;
|
|
ocv_cap[14] = axp_config->pmu_bat_para15;
|
|
ocv_cap[15] = axp_config->pmu_bat_para16;
|
|
ocv_cap[16] = axp_config->pmu_bat_para17;
|
|
ocv_cap[17] = axp_config->pmu_bat_para18;
|
|
ocv_cap[18] = axp_config->pmu_bat_para19;
|
|
ocv_cap[19] = axp_config->pmu_bat_para20;
|
|
ocv_cap[20] = axp_config->pmu_bat_para21;
|
|
ocv_cap[21] = axp_config->pmu_bat_para22;
|
|
ocv_cap[22] = axp_config->pmu_bat_para23;
|
|
ocv_cap[23] = axp_config->pmu_bat_para24;
|
|
ocv_cap[24] = axp_config->pmu_bat_para25;
|
|
ocv_cap[25] = axp_config->pmu_bat_para26;
|
|
ocv_cap[26] = axp_config->pmu_bat_para27;
|
|
ocv_cap[27] = axp_config->pmu_bat_para28;
|
|
ocv_cap[28] = axp_config->pmu_bat_para29;
|
|
ocv_cap[29] = axp_config->pmu_bat_para30;
|
|
ocv_cap[30] = axp_config->pmu_bat_para31;
|
|
ocv_cap[31] = axp_config->pmu_bat_para32;
|
|
regmap_bulk_write(map, AXP803_OCVCAP, ocv_cap, 32);
|
|
|
|
/* Init CHGLED function */
|
|
if (axp_config->pmu_chgled_func)
|
|
regmap_update_bits(map, AXP803_OFF_CTL, 0x08, 0x08); /* by charger */
|
|
else
|
|
regmap_update_bits(map, AXP803_OFF_CTL, 0x08, 0x00); /* drive MOTO */
|
|
|
|
/* set CHGLED Indication Type */
|
|
if (axp_config->pmu_chgled_type)
|
|
regmap_update_bits(map, AXP803_CHARGE2, 0x10, 0x10); /* Type B */
|
|
else
|
|
regmap_update_bits(map, AXP803_CHARGE2, 0x10, 0x00); /* Type A */
|
|
|
|
/* Init battery capacity correct function */
|
|
if (axp_config->pmu_batt_cap_correct)
|
|
regmap_update_bits(map, AXP803_COULOMB_CTL, 0x20, 0x20);
|
|
else
|
|
regmap_update_bits(map, AXP803_COULOMB_CTL, 0x20, 0x00);
|
|
|
|
/* Init battery regulator enable or not when charge finish */
|
|
if (axp_config->pmu_chg_end_on_en)
|
|
regmap_update_bits(map, AXP803_CHARGE2, 0x20, 0x20);
|
|
else
|
|
regmap_update_bits(map, AXP803_CHARGE2, 0x20, 0x00);
|
|
|
|
if (axp_config->pmu_batdeten)
|
|
regmap_update_bits(map, AXP803_OFF_CTL, 0x40, 0x40);
|
|
else
|
|
regmap_update_bits(map, AXP803_OFF_CTL, 0x40, 0x00);
|
|
|
|
/* RDC initial */
|
|
regmap_read(map, AXP803_RDC0, &val);
|
|
if ((axp_config->pmu_battery_rdc) && (!(val & 0x40))) {
|
|
rdc = (axp_config->pmu_battery_rdc * 10000 + 5371) / 10742;
|
|
regmap_write(map, AXP803_RDC0, ((rdc >> 8) & 0x1F)|0x80);
|
|
regmap_write(map, AXP803_RDC1, rdc & 0x00FF);
|
|
}
|
|
|
|
regmap_read(map, AXP803_BATCAP0, &val);
|
|
if ((axp_config->pmu_battery_cap) && (!(val & 0x80))) {
|
|
cur_coulomb_counter = axp_config->pmu_battery_cap
|
|
* 1000 / 1456;
|
|
regmap_write(map, AXP803_BATCAP0, ((cur_coulomb_counter >> 8) | 0x80));
|
|
regmap_write(map, AXP803_BATCAP1, cur_coulomb_counter & 0x00FF);
|
|
} else if (!axp_config->pmu_battery_cap) {
|
|
regmap_write(map, AXP803_BATCAP0, 0x00);
|
|
regmap_write(map, AXP803_BATCAP1, 0x00);
|
|
}
|
|
|
|
/*
|
|
* As datasheet decripted:
|
|
* TS_VOL = reg_value * 16 * 10K * 80ua
|
|
*/
|
|
if (axp_config->pmu_bat_temp_enable == 1) {
|
|
regmap_write(map, AXP803_VLTF_CHARGE,
|
|
axp_config->pmu_bat_charge_ltf * 10 / 128);
|
|
regmap_write(map, AXP803_VHTF_CHARGE,
|
|
axp_config->pmu_bat_charge_htf * 10 / 128);
|
|
regmap_write(map, AXP803_VLTF_WORK,
|
|
axp_config->pmu_bat_shutdown_ltf * 10 / 128);
|
|
regmap_write(map, AXP803_VHTF_WORK,
|
|
axp_config->pmu_bat_shutdown_htf * 10 / 128);
|
|
}
|
|
|
|
if (axp_config->pmu_ocv_en == 0) {
|
|
pr_warn("axp803 ocv must be enabled\n");
|
|
axp_config->pmu_ocv_en = 1;
|
|
}
|
|
if (axp_config->pmu_init_bc_en == 1) {
|
|
regmap_update_bits(map, AXP803_BC_CTL, 0x01, 0x01);
|
|
} else {
|
|
regmap_update_bits(map, AXP803_BC_CTL, 0x01, 0x00);
|
|
}
|
|
|
|
if (axp_config->pmu_cou_en == 1) {
|
|
/* use ocv and cou */
|
|
regmap_update_bits(map, AXP803_COULOMB_CTL, 0x80, 0x80);
|
|
regmap_update_bits(map, AXP803_COULOMB_CTL, 0x40, 0x40);
|
|
} else if (axp_config->pmu_cou_en == 0) {
|
|
/* only use ocv */
|
|
regmap_update_bits(map, AXP803_COULOMB_CTL, 0x80, 0x80);
|
|
regmap_update_bits(map, AXP803_COULOMB_CTL, 0x40, 0x00);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(update_min_times); i++) {
|
|
if (update_min_times[i] == axp_config->pmu_update_min_time)
|
|
break;
|
|
}
|
|
regmap_update_bits(map, AXP803_ADJUST_PARA, 0x07, i);
|
|
for (i = 0; i < ARRAY_SIZE(ocv_cou_adjust_time); i++) {
|
|
if (ocv_cou_adjust_time[i] == axp_config->pmu_ocv_cou_adjust_time)
|
|
break;
|
|
}
|
|
i <<= 6;
|
|
regmap_update_bits(map, AXP803_ADJUST_PARA1, 0xc0, i);
|
|
|
|
axp803_set_bat_chg_cur(bat_power, axp_config->pmu_runtime_chgcur);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t axp803_bat_power_irq(int irq, void *data)
|
|
{
|
|
struct axp803_bat_power *bat_power = data;
|
|
|
|
power_supply_changed(bat_power->bat_supply);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
enum axp803_bat_power_virqs {
|
|
AXP803_VIRQ_CHAST,
|
|
AXP803_VIRQ_CHAOV,
|
|
AXP803_VIRQ_BATIN,
|
|
AXP803_VIRQ_BATRE,
|
|
AXP803_VIRQ_BATINWORK,
|
|
AXP803_VIRQ_BATOVWORK,
|
|
AXP803_VIRQ_BATINCHG,
|
|
AXP803_VIRQ_BATOVCHG,
|
|
AXP803_VIRQ_LOWN2,
|
|
AXP803_VIRQ_LOWN1,
|
|
|
|
AXP803_BAT_VIRQ_MAX_VIRQ,
|
|
};
|
|
|
|
static struct axp_interrupts axp803_bat_irq[] = {
|
|
[AXP803_VIRQ_CHAST] = {"charging", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_CHAOV] = {"charge over", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_BATIN] = {"bat in", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_BATRE] = {"bat out", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_BATINWORK] = {"bat untemp work", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_BATOVWORK] = {"bat ovtemp work", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_BATINCHG] = {"bat untemp chg", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_BATOVCHG] = {"bat ovtemp chg", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_LOWN2] = {"low warning2", axp803_bat_power_irq},
|
|
[AXP803_VIRQ_LOWN1] = {"low warning1", axp803_bat_power_irq},
|
|
};
|
|
|
|
static void axp803_bat_power_monitor(struct work_struct *work)
|
|
{
|
|
struct axp803_bat_power *bat_power =
|
|
container_of(work, typeof(*bat_power), bat_supply_mon.work);
|
|
|
|
power_supply_changed(bat_power->bat_supply);
|
|
|
|
/* debug info */
|
|
if (unlikely(axp_debug_mask & AXP_CHG)) {
|
|
|
|
unsigned char temp_val[2];
|
|
unsigned int reg_value;
|
|
int vbat, ibat, rest_pct, ocv_pct, col_pct;
|
|
int coulumb_counter, batt_max_cap, ocv_vol, rdc;
|
|
|
|
vbat = axp803_get_vbat(bat_power);
|
|
|
|
ibat = axp803_get_ibat(bat_power) - axp803_get_disibat(bat_power);
|
|
|
|
rest_pct = axp803_get_rest_cap(bat_power);
|
|
|
|
regmap_read(bat_power->regmap, AXP803_OCV_PERCENT, ®_value);
|
|
if (reg_value & 0x80)
|
|
ocv_pct = (int)(reg_value & 0x7F);
|
|
else
|
|
ocv_pct = 0;
|
|
|
|
regmap_read(bat_power->regmap, AXP803_COU_PERCENT, ®_value);
|
|
if (reg_value & 0x80)
|
|
col_pct = (int)(reg_value & 0x7F);
|
|
else
|
|
col_pct = 0;
|
|
|
|
regmap_bulk_read(bat_power->regmap, AXP803_COUCNT0, temp_val, 2);
|
|
coulumb_counter = (((temp_val[0] & 0x7f) << 8) + temp_val[1])
|
|
* 1456 / 1000;
|
|
|
|
regmap_bulk_read(bat_power->regmap, AXP803_BATCAP0, temp_val, 2);
|
|
batt_max_cap = (((temp_val[0] & 0x7f) << 8) + temp_val[1])
|
|
* 1456 / 1000;
|
|
|
|
regmap_bulk_read(bat_power->regmap, AXP803_OCVBATH_RES, temp_val, 2);
|
|
ocv_vol = ((temp_val[0] << 4) | (temp_val[1] & 0xF)) * 1100 / 1000;
|
|
|
|
regmap_bulk_read(bat_power->regmap, AXP803_RDC0, temp_val, 2);
|
|
rdc = (((temp_val[0] & 0x1f) << 8) + temp_val[1]) * 10742 / 10000;
|
|
|
|
printk("vbat:%d, ibat:%d, rest_pct:%d, ocv_pct:%d, col_pct:%d\n",
|
|
vbat, ibat, rest_pct, ocv_pct, col_pct);
|
|
|
|
printk("coulumb_counter:%d, batt_max_cap:%d, ocv_vol:%d, rdc:%d\n",
|
|
coulumb_counter, batt_max_cap, ocv_vol, rdc);
|
|
}
|
|
|
|
schedule_delayed_work(&bat_power->bat_supply_mon, msecs_to_jiffies(10 * 1000));
|
|
}
|
|
|
|
static int axp803_bat_power_dt_parse(struct axp803_bat_power *bat_power)
|
|
{
|
|
struct axp_config_info *axp_config = &bat_power->dts_info;
|
|
struct device_node *node = bat_power->dev->of_node;
|
|
|
|
if (!of_device_is_available(node)) {
|
|
pr_err("%s: failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
AXP_OF_PROP_READ(pmu_battery_rdc, BATRDC);
|
|
AXP_OF_PROP_READ(pmu_battery_cap, 4000);
|
|
AXP_OF_PROP_READ(pmu_batdeten, 1);
|
|
AXP_OF_PROP_READ(pmu_chg_ic_temp, 0);
|
|
AXP_OF_PROP_READ(pmu_runtime_chgcur, INTCHGCUR / 1000);
|
|
AXP_OF_PROP_READ(pmu_suspend_chgcur, 1200);
|
|
AXP_OF_PROP_READ(pmu_shutdown_chgcur, 1200);
|
|
AXP_OF_PROP_READ(pmu_init_chgvol, INTCHGVOL / 1000);
|
|
AXP_OF_PROP_READ(pmu_init_chgend_rate, INTCHGENDRATE);
|
|
AXP_OF_PROP_READ(pmu_init_chg_enabled, 1);
|
|
AXP_OF_PROP_READ(pmu_init_bc_en, 0);
|
|
AXP_OF_PROP_READ(pmu_init_adc_freq, INTADCFREQ);
|
|
AXP_OF_PROP_READ(pmu_init_adcts_freq, INTADCFREQC);
|
|
AXP_OF_PROP_READ(pmu_init_chg_pretime, INTCHGPRETIME);
|
|
AXP_OF_PROP_READ(pmu_init_chg_csttime, INTCHGCSTTIME);
|
|
AXP_OF_PROP_READ(pmu_batt_cap_correct, 1);
|
|
AXP_OF_PROP_READ(pmu_chg_end_on_en, 0);
|
|
AXP_OF_PROP_READ(ocv_coulumb_100, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_para1, OCVREG0);
|
|
AXP_OF_PROP_READ(pmu_bat_para2, OCVREG1);
|
|
AXP_OF_PROP_READ(pmu_bat_para3, OCVREG2);
|
|
AXP_OF_PROP_READ(pmu_bat_para4, OCVREG3);
|
|
AXP_OF_PROP_READ(pmu_bat_para5, OCVREG4);
|
|
AXP_OF_PROP_READ(pmu_bat_para6, OCVREG5);
|
|
AXP_OF_PROP_READ(pmu_bat_para7, OCVREG6);
|
|
AXP_OF_PROP_READ(pmu_bat_para8, OCVREG7);
|
|
AXP_OF_PROP_READ(pmu_bat_para9, OCVREG8);
|
|
AXP_OF_PROP_READ(pmu_bat_para10, OCVREG9);
|
|
AXP_OF_PROP_READ(pmu_bat_para11, OCVREGA);
|
|
AXP_OF_PROP_READ(pmu_bat_para12, OCVREGB);
|
|
AXP_OF_PROP_READ(pmu_bat_para13, OCVREGC);
|
|
AXP_OF_PROP_READ(pmu_bat_para14, OCVREGD);
|
|
AXP_OF_PROP_READ(pmu_bat_para15, OCVREGE);
|
|
AXP_OF_PROP_READ(pmu_bat_para16, OCVREGF);
|
|
AXP_OF_PROP_READ(pmu_bat_para17, OCVREG10);
|
|
AXP_OF_PROP_READ(pmu_bat_para18, OCVREG11);
|
|
AXP_OF_PROP_READ(pmu_bat_para19, OCVREG12);
|
|
AXP_OF_PROP_READ(pmu_bat_para20, OCVREG13);
|
|
AXP_OF_PROP_READ(pmu_bat_para21, OCVREG14);
|
|
AXP_OF_PROP_READ(pmu_bat_para22, OCVREG15);
|
|
AXP_OF_PROP_READ(pmu_bat_para23, OCVREG16);
|
|
AXP_OF_PROP_READ(pmu_bat_para24, OCVREG17);
|
|
AXP_OF_PROP_READ(pmu_bat_para25, OCVREG18);
|
|
AXP_OF_PROP_READ(pmu_bat_para26, OCVREG19);
|
|
AXP_OF_PROP_READ(pmu_bat_para27, OCVREG1A);
|
|
AXP_OF_PROP_READ(pmu_bat_para28, OCVREG1B);
|
|
AXP_OF_PROP_READ(pmu_bat_para29, OCVREG1C);
|
|
AXP_OF_PROP_READ(pmu_bat_para30, OCVREG1D);
|
|
AXP_OF_PROP_READ(pmu_bat_para31, OCVREG1E);
|
|
AXP_OF_PROP_READ(pmu_bat_para32, OCVREG1F);
|
|
AXP_OF_PROP_READ(pmu_pwroff_vol, 3300);
|
|
AXP_OF_PROP_READ(pmu_pwron_vol, 2900);
|
|
AXP_OF_PROP_READ(pmu_battery_warning_level1, 15);
|
|
AXP_OF_PROP_READ(pmu_battery_warning_level2, 0);
|
|
AXP_OF_PROP_READ(pmu_restvol_adjust_time, 30);
|
|
AXP_OF_PROP_READ(pmu_ocv_cou_adjust_time, 60);
|
|
AXP_OF_PROP_READ(pmu_chgled_func, 0);
|
|
AXP_OF_PROP_READ(pmu_chgled_type, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_enable, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_charge_ltf, 0xA5);
|
|
AXP_OF_PROP_READ(pmu_bat_charge_htf, 0x1F);
|
|
AXP_OF_PROP_READ(pmu_bat_shutdown_ltf, 0xFC);
|
|
AXP_OF_PROP_READ(pmu_bat_shutdown_htf, 0x16);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para1, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para2, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para3, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para4, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para5, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para6, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para7, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para8, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para9, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para10, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para11, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para12, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para13, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para14, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para15, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_temp_para16, 0);
|
|
AXP_OF_PROP_READ(pmu_bat_unused, 0);
|
|
AXP_OF_PROP_READ(power_start, 0);
|
|
AXP_OF_PROP_READ(pmu_ocv_en, 1);
|
|
AXP_OF_PROP_READ(pmu_cou_en, 1);
|
|
AXP_OF_PROP_READ(pmu_update_min_time, UPDATEMINTIME);
|
|
|
|
axp_config->wakeup_bat_in =
|
|
of_property_read_bool(node, "wakeup_bat_in");
|
|
axp_config->wakeup_bat_out =
|
|
of_property_read_bool(node, "wakeup_bat_out");
|
|
axp_config->wakeup_bat_charging =
|
|
of_property_read_bool(node, "wakeup_bat_charging");
|
|
axp_config->wakeup_bat_charge_over =
|
|
of_property_read_bool(node, "wakeup_bat_charge_over");
|
|
axp_config->wakeup_low_warning1 =
|
|
of_property_read_bool(node, "wakeup_low_warning1");
|
|
axp_config->wakeup_low_warning2 =
|
|
of_property_read_bool(node, "wakeup_low_warning2");
|
|
axp_config->wakeup_bat_untemp_work =
|
|
of_property_read_bool(node, "wakeup_bat_untemp_work");
|
|
axp_config->wakeup_bat_ovtemp_work =
|
|
of_property_read_bool(node, "wakeup_bat_ovtemp_work");
|
|
axp_config->wakeup_untemp_chg =
|
|
of_property_read_bool(node, "wakeup_bat_untemp_chg");
|
|
axp_config->wakeup_ovtemp_chg =
|
|
of_property_read_bool(node, "wakeup_bat_ovtemp_chg");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp803_bat_power_probe(struct platform_device *pdev)
|
|
{
|
|
struct axp20x_dev *axp_dev = dev_get_drvdata(pdev->dev.parent);
|
|
struct power_supply_config psy_cfg = {};
|
|
struct axp803_bat_power *bat_power;
|
|
struct device_node *node = pdev->dev.of_node;
|
|
int i, irq;
|
|
int ret = 0;
|
|
|
|
if (!of_device_is_available(node)) {
|
|
pr_err("axp803-battery device is not configed\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
|
|
if (!axp_dev->irq) {
|
|
pr_err("can not register axp803 bat without irq\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
bat_power = devm_kzalloc(&pdev->dev, sizeof(*bat_power), GFP_KERNEL);
|
|
if (!bat_power) {
|
|
pr_err("axp803 bat power alloc failed\n");
|
|
ret = -ENOMEM;
|
|
return ret;
|
|
}
|
|
|
|
bat_power->name = "axp803-bat-power";
|
|
bat_power->dev = &pdev->dev;
|
|
bat_power->regmap = axp_dev->regmap;
|
|
|
|
platform_set_drvdata(pdev, bat_power);
|
|
|
|
ret = axp803_bat_power_dt_parse(bat_power);
|
|
if (ret) {
|
|
pr_err("%s parse device tree err\n", __func__);
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
ret = axp803_bat_power_init(bat_power);
|
|
if (ret < 0) {
|
|
pr_err("axp210x init bat fail!\n");
|
|
ret = -ENODEV;
|
|
return ret;
|
|
}
|
|
|
|
psy_cfg.of_node = pdev->dev.of_node;
|
|
psy_cfg.drv_data = bat_power;
|
|
|
|
bat_power->bat_supply = devm_power_supply_register(bat_power->dev,
|
|
&axp803_bat_desc, &psy_cfg);
|
|
|
|
if (IS_ERR(bat_power->bat_supply)) {
|
|
pr_err("axp803 failed to register bat power\n");
|
|
ret = PTR_ERR(bat_power->bat_supply);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(axp803_bat_irq); i++) {
|
|
irq = platform_get_irq_byname(pdev, axp803_bat_irq[i].name);
|
|
if (irq < 0) {
|
|
dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
|
|
axp803_bat_irq[i].name, irq);
|
|
continue;
|
|
}
|
|
irq = regmap_irq_get_virq(axp_dev->regmap_irqc, irq);
|
|
ret = devm_request_any_context_irq(&pdev->dev, irq,
|
|
axp803_bat_irq[i].isr, 0,
|
|
axp803_bat_irq[i].name, bat_power);
|
|
if (ret < 0)
|
|
dev_warn(&pdev->dev, "Error requesting %s IRQ %d: %d\n",
|
|
axp803_bat_irq[i].name, irq, ret);
|
|
|
|
dev_dbg(&pdev->dev, "Requested %s IRQ %d: %d\n",
|
|
axp803_bat_irq[i].name, irq, ret);
|
|
|
|
/* we use this variable to suspend irq */
|
|
axp803_bat_irq[i].irq = irq;
|
|
}
|
|
|
|
|
|
INIT_DELAYED_WORK(&bat_power->bat_supply_mon, axp803_bat_power_monitor);
|
|
schedule_delayed_work(&bat_power->bat_supply_mon, msecs_to_jiffies(10 * 1000));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp803_bat_power_remove(struct platform_device *pdev)
|
|
{
|
|
struct axp803_bat_power *bat_power = platform_get_drvdata(pdev);
|
|
|
|
if (bat_power->bat_supply)
|
|
power_supply_unregister(bat_power->bat_supply);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void axp_irq_set(unsigned int irq, bool enable)
|
|
{
|
|
if (enable)
|
|
enable_irq(irq);
|
|
else
|
|
disable_irq(irq);
|
|
}
|
|
|
|
static void axp803_bat_virq_dts_set(struct axp803_bat_power *bat_power, bool enable)
|
|
{
|
|
struct axp_config_info *dts_info = &bat_power->dts_info;
|
|
|
|
if (!dts_info->wakeup_bat_in)
|
|
axp_irq_set(axp803_bat_irq[AXP803_VIRQ_BATIN].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_bat_out)
|
|
axp_irq_set(axp803_bat_irq[AXP803_VIRQ_BATRE].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_bat_charging)
|
|
axp_irq_set(axp803_bat_irq[AXP803_VIRQ_CHAST].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_bat_charge_over)
|
|
axp_irq_set(axp803_bat_irq[AXP803_VIRQ_CHAOV].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_low_warning1)
|
|
axp_irq_set(axp803_bat_irq[AXP803_VIRQ_LOWN1].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_low_warning2)
|
|
axp_irq_set(axp803_bat_irq[AXP803_VIRQ_LOWN2].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_bat_untemp_work)
|
|
axp_irq_set(
|
|
axp803_bat_irq[AXP803_VIRQ_BATINWORK].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_bat_ovtemp_work)
|
|
axp_irq_set(
|
|
axp803_bat_irq[AXP803_VIRQ_BATOVWORK].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_untemp_chg)
|
|
axp_irq_set(
|
|
axp803_bat_irq[AXP803_VIRQ_BATINCHG].irq,
|
|
enable);
|
|
if (!dts_info->wakeup_ovtemp_chg)
|
|
axp_irq_set(
|
|
axp803_bat_irq[AXP803_VIRQ_BATOVCHG].irq,
|
|
enable);
|
|
}
|
|
|
|
static void axp803_bat_power_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct axp803_bat_power *bat_power = platform_get_drvdata(pdev);
|
|
|
|
cancel_delayed_work_sync(&bat_power->bat_supply_mon);
|
|
|
|
axp803_set_bat_chg_cur(bat_power, bat_power->dts_info.pmu_shutdown_chgcur);
|
|
}
|
|
|
|
|
|
static int axp803_bat_power_suspend(struct platform_device *pdev, pm_message_t state)
|
|
{
|
|
struct axp803_bat_power *bat_power = platform_get_drvdata(pdev);
|
|
|
|
cancel_delayed_work_sync(&bat_power->bat_supply_mon);
|
|
axp803_set_bat_chg_cur(bat_power, bat_power->dts_info.pmu_suspend_chgcur);
|
|
|
|
axp803_bat_virq_dts_set(bat_power, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp803_bat_power_resume(struct platform_device *pdev)
|
|
{
|
|
struct axp803_bat_power *bat_power = platform_get_drvdata(pdev);
|
|
|
|
schedule_delayed_work(&bat_power->bat_supply_mon, 0);
|
|
axp803_set_bat_chg_cur(bat_power, bat_power->dts_info.pmu_runtime_chgcur);
|
|
|
|
axp803_bat_virq_dts_set(bat_power, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id axp803_bat_power_match[] = {
|
|
{
|
|
.compatible = "x-powers,axp803-battery-power-supply",
|
|
.data = (void *)AXP803_ID,
|
|
}, { /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, axp803_bat_power_match);
|
|
|
|
static struct platform_driver axp803_bat_power_driver = {
|
|
.driver = {
|
|
.name = "axp803-battery-power-supply",
|
|
.of_match_table = axp803_bat_power_match,
|
|
},
|
|
.probe = axp803_bat_power_probe,
|
|
.remove = axp803_bat_power_remove,
|
|
.shutdown = axp803_bat_power_shutdown,
|
|
.suspend = axp803_bat_power_suspend,
|
|
.resume = axp803_bat_power_resume,
|
|
};
|
|
|
|
module_platform_driver(axp803_bat_power_driver);
|
|
|
|
MODULE_AUTHOR("wangxiaoliang <wangxiaoliang@x-powers.com>");
|
|
MODULE_DESCRIPTION("axp803 bat power driver");
|
|
MODULE_LICENSE("GPL");
|