2252 lines
61 KiB
C
2252 lines
61 KiB
C
/*
|
|
* bd7181x-power.c
|
|
* @file ROHM BD71815/BD71817 Charger driver
|
|
*
|
|
* Copyright 2014 Embest Technology Co. Ltd. Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
/*#define DEBUG*/
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/mfd/bd7181x.h>
|
|
#include <linux/delay.h>
|
|
|
|
#define JITTER_DEFAULT 10000 /* hope 3s is enough */
|
|
#define JITTER_REPORT_CAP 10000 /* 10 seconds */
|
|
#define CALIB_CURRENT_A2A3 0xCE9E
|
|
#define BD7181X_BATTERY_CAP_MAH 1200
|
|
#define BD7181X_BATTERY_CAP mAh_A10s(BD7181X_BATTERY_CAP_MAH)
|
|
#define MAX_VOLTAGE ocv_table[0]
|
|
#define MIN_VOLTAGE 3200000
|
|
#define THR_VOLTAGE 4100000
|
|
#define MAX_CURRENT 1500000 /* uA */
|
|
#define AC_NAME "bd7181x_ac"
|
|
#define BAT_NAME "bd7181x_bat"
|
|
#define BD7181X_BATTERY_FULL 100
|
|
|
|
#define BY_BAT_VOLT 0
|
|
#define BY_VBATLOAD_REG 1
|
|
#define INIT_COULOMB BY_VBATLOAD_REG
|
|
|
|
#define CALIB_CURRENT_A2A3 0xCE9E
|
|
|
|
//VBAT Low voltage detection Threshold
|
|
#define VBAT_LOW_TH 0x00D4 // 0x00D4*16mV = 212*0.016 = 3.392v
|
|
|
|
|
|
#define RS_30mOHM /* This is for 30mOhm sense resistance */
|
|
|
|
#ifdef RS_30mOHM
|
|
#define A10s_mAh(s) ((s) * 1000 / (360 * 3))
|
|
#define mAh_A10s(m) ((m) * (360 * 3) / 1000)
|
|
#else
|
|
#define A10s_mAh(s) ((s) * 1000 / 360)
|
|
#define mAh_A10s(m) ((m) * 360 / 1000)
|
|
#endif
|
|
|
|
#define THR_RELAX_CURRENT 5 /* mA */
|
|
#define THR_RELAX_TIME ((60 * 60) - 10) /* sec. */
|
|
|
|
#define BD7181X_DGRD_CYC_CAP 15 /* 1 micro Ah unit */
|
|
|
|
#define BD7181X_DGRD_TEMP_M 25 /* 1 degrees C unit */
|
|
#define BD7181X_DGRD_TEMP_L 5 /* 1 degrees C unit */
|
|
#define BD7181X_DGRD_TEMP_CAP_H (0) /* 1 micro Ah unit */
|
|
#define BD7181X_DGRD_TEMP_CAP_M (0) /* 1 micro Ah unit */
|
|
#define BD7181X_DGRD_TEMP_CAP_L (0) /* 1 micro Ah unit */
|
|
|
|
#define CANCEL_ADJ_COULOMB_SOC_H_1 700 /* unit 0.1% */
|
|
#define CANCEL_ADJ_COULOMB_SOC_L_1 550 /* unit 0.1% */
|
|
#define CANCEL_ADJ_COULOMB_SOC_H_2 350 /* unit 0.1% */
|
|
#define CANCEL_ADJ_COULOMB_SOC_L_2 0 /* unit 0.1% */
|
|
|
|
#define FORCE_ADJ_COULOMB_TEMP_H 35 /* 1 degrees C unit */
|
|
#define FORCE_ADJ_COULOMB_TEMP_L 15 /* 1 degrees C unit */
|
|
|
|
unsigned int battery_cycle;
|
|
|
|
|
|
static int ocv_table[] = {
|
|
4200000,
|
|
4160000,
|
|
4111638,
|
|
4081966,
|
|
4016618,
|
|
3974931,
|
|
3953040,
|
|
3925422,
|
|
3896285,
|
|
3859529,
|
|
3835339,
|
|
3815914,
|
|
3803244,
|
|
3793558,
|
|
3783886,
|
|
3780000,
|
|
3769083,
|
|
3749738,
|
|
3725590,
|
|
3705524,
|
|
3646077,
|
|
3400000,
|
|
2682635
|
|
}; /* unit 1 micro V */
|
|
|
|
static int soc_table[] = {
|
|
1000,
|
|
1000,
|
|
950,
|
|
900,
|
|
850,
|
|
800,
|
|
750,
|
|
700,
|
|
650,
|
|
600,
|
|
550,
|
|
500,
|
|
450,
|
|
400,
|
|
350,
|
|
300,
|
|
250,
|
|
200,
|
|
150,
|
|
100,
|
|
50,
|
|
0,
|
|
-50
|
|
/* unit 0.1% */
|
|
};
|
|
|
|
|
|
/** @brief power deivce */
|
|
struct bd7181x_power {
|
|
struct device *dev;
|
|
struct bd7181x *mfd; /**< parent for access register */
|
|
struct power_supply *ac; /**< alternating current power */
|
|
struct power_supply_desc ac_desc;
|
|
struct power_supply_config ac_cfg;
|
|
struct power_supply *bat; /**< battery power */
|
|
struct power_supply_desc bat_desc;
|
|
struct power_supply_config bat_cfg;
|
|
struct delayed_work bd_work; /**< delayed work for timed work */
|
|
|
|
int reg_index; /**< register address saved for sysfs */
|
|
|
|
int vbus_status; /**< last vbus status */
|
|
int charge_status; /**< last charge status */
|
|
int bat_status; /**< last bat status */
|
|
|
|
int hw_ocv1; /**< HW ocv1 */
|
|
int hw_ocv2; /**< HW ocv2 */
|
|
int bat_online; /**< battery connect */
|
|
int charger_online; /**< charger connect */
|
|
int vcell; /**< battery voltage */
|
|
int vsys; /**< system voltage */
|
|
int vcell_min; /**< minimum battery voltage */
|
|
int vsys_min; /**< minimum system voltage */
|
|
int rpt_status; /**< battery status report */
|
|
int prev_rpt_status; /**< previous battery status report */
|
|
int bat_health; /**< battery health */
|
|
int designed_cap; /**< battery designed capacity */
|
|
int full_cap; /**< battery capacity */
|
|
int curr; /**< battery current from DS-ADC */
|
|
int curr_sar; /**< battery current from VM_IBAT */
|
|
int temp; /**< battery tempature */
|
|
u32 coulomb_cnt; /**< Coulomb Counter */
|
|
int state_machine; /**< initial-procedure state machine */
|
|
|
|
u32 soc_org; /**< State Of Charge using designed capacity without by load */
|
|
u32 soc_norm; /**< State Of Charge using full capacity without by load */
|
|
u32 soc; /**< State Of Charge using full capacity with by load */
|
|
u32 clamp_soc; /**< Clamped State Of Charge using full capacity with by load */
|
|
|
|
int relax_time; /**< Relax Time */
|
|
|
|
u32 cycle; /**< Charging and Discharging cycle number */
|
|
volatile int calib_current; /**< calibration current */
|
|
};
|
|
|
|
|
|
#define CALIB_NORM 0
|
|
#define CALIB_START 1
|
|
#define CALIB_GO 2
|
|
|
|
enum {
|
|
STAT_POWER_ON,
|
|
STAT_INITIALIZED,
|
|
};
|
|
|
|
static int bd7181x_calc_soc_org(struct bd7181x_power* pwr);
|
|
|
|
/** @brief read a register group once
|
|
* @param mfd bd7181x device
|
|
* @param reg register address of lower register
|
|
* @return register value
|
|
*/
|
|
#ifdef __BD7181X_REGMAP_H__
|
|
static u16 bd7181x_reg_read16(struct bd7181x* mfd, int reg) {
|
|
u16 v;
|
|
|
|
v = (u16)bd7181x_reg_read(mfd, reg) << 8;
|
|
v |= (u16)bd7181x_reg_read(mfd, reg + 1) << 0;
|
|
return v;
|
|
}
|
|
#else
|
|
static u16 bd7181x_reg_read16(struct bd7181x* mfd, int reg) {
|
|
union {
|
|
u16 long_type;
|
|
char chars[2];
|
|
} u;
|
|
int r;
|
|
|
|
r = regmap_bulk_read(mfd->regmap, reg, u.chars, sizeof u.chars);
|
|
if (r) {
|
|
return -1;
|
|
}
|
|
return be16_to_cpu(u.long_type);
|
|
}
|
|
#endif
|
|
|
|
/** @brief write a register group once
|
|
* @param mfd bd7181x device
|
|
* @param reg register address of lower register
|
|
* @param val value to write
|
|
* @retval 0 success
|
|
* @retval -1 fail
|
|
*/
|
|
static int bd7181x_reg_write16(struct bd7181x *mfd, int reg, u16 val) {
|
|
union {
|
|
u16 long_type;
|
|
char chars[2];
|
|
} u;
|
|
int r;
|
|
|
|
u.long_type = cpu_to_be16(val);
|
|
// printk("write16 0x%.4X 0x%.4X\n", val, u.long_type);
|
|
#ifdef __BD7181X_REGMAP_H__
|
|
r = mfd->write(mfd, reg, sizeof u.chars, u.chars);
|
|
#else
|
|
r = regmap_bulk_write(mfd->regmap, reg, u.chars, sizeof u.chars);
|
|
#endif
|
|
if (r) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** @brief read quad register once
|
|
* @param mfd bd7181x device
|
|
* @param reg register address of lower register
|
|
* @return register value
|
|
*/
|
|
static int bd7181x_reg_read32(struct bd7181x *mfd, int reg) {
|
|
union {
|
|
u32 long_type;
|
|
char chars[4];
|
|
} u;
|
|
int r;
|
|
|
|
#ifdef __BD7181X_REGMAP_H__
|
|
r = mfd->read(mfd, reg, sizeof u.chars, u.chars);
|
|
#else
|
|
r = regmap_bulk_read(mfd->regmap, reg, u.chars, sizeof u.chars);
|
|
#endif
|
|
if (r) {
|
|
return -1;
|
|
}
|
|
return be32_to_cpu(u.long_type);
|
|
}
|
|
|
|
#if 0
|
|
/** @brief write quad register once
|
|
* @param mfd bd7181x device
|
|
* @param reg register address of lower register
|
|
* @param val value to write
|
|
* @retval 0 success
|
|
* @retval -1 fail
|
|
*/
|
|
static int bd7181x_reg_write32(struct bd7181x *mfd, int reg, unsigned val) {
|
|
union {
|
|
u32 long_type;
|
|
char chars[4];
|
|
} u;
|
|
int r;
|
|
|
|
u.long_type = cpu_to_be32(val);
|
|
r = regmap_bulk_write(mfd->regmap, reg, u.chars, sizeof u.chars);
|
|
if (r) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if INIT_COULOMB == BY_VBATLOAD_REG
|
|
/** @brief get initial battery voltage and current
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_get_init_bat_stat(struct bd7181x_power *pwr)
|
|
{
|
|
struct bd7181x *mfd = pwr->mfd;
|
|
int vcell;
|
|
|
|
vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_OCV_PRE_U) * 1000;
|
|
dev_dbg(pwr->dev, "VM_OCV_PRE = %d\n", vcell);
|
|
pwr->hw_ocv1 = vcell;
|
|
|
|
vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_OCV_PST_U) * 1000;
|
|
dev_dbg(pwr->dev, "VM_OCV_PST = %d\n", vcell);
|
|
pwr->hw_ocv2 = vcell;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/** @brief get battery average voltage and current
|
|
* @param pwr power device
|
|
* @param vcell pointer to return back voltage in unit uV.
|
|
* @param curr pointer to return back current in unit uA.
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_get_vbat_curr(struct bd7181x_power *pwr, int *vcell, int *curr) {
|
|
struct bd7181x* mfd = pwr->mfd;
|
|
int tmp_vcell, tmp_curr;
|
|
|
|
tmp_vcell = 0;
|
|
tmp_curr = 0;
|
|
|
|
tmp_vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VBAT_U);
|
|
tmp_curr = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_IBAT_U);
|
|
if (tmp_curr & IBAT_SA_DIR_Discharging) {
|
|
tmp_curr = -(tmp_curr & ~IBAT_SA_DIR_Discharging);
|
|
}
|
|
|
|
*vcell = tmp_vcell * 1000;
|
|
#ifdef RS_30mOHM
|
|
*curr = tmp_curr * 1000 / 3;
|
|
#else
|
|
*curr = tmp_curr * 1000;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get battery current from DS-ADC
|
|
* @param pwr power device
|
|
* @return current in unit uA
|
|
*/
|
|
static int bd7181x_get_current_ds_adc(struct bd7181x_power *pwr) {
|
|
int r;
|
|
|
|
r = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CC_CURCD_U);
|
|
if (r < 0) {
|
|
return 0;
|
|
}
|
|
if (r & CURDIR_Discharging) {
|
|
r = -(r & ~CURDIR_Discharging);
|
|
}
|
|
#ifdef RS_30mOHM
|
|
return r * 1000 / 3;
|
|
#else
|
|
return r * 1000;
|
|
#endif
|
|
}
|
|
|
|
/** @brief get system average voltage
|
|
* @param pwr power device
|
|
* @param vcell pointer to return back voltage in unit uV.
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_get_vsys(struct bd7181x_power *pwr, int *vsys) {
|
|
struct bd7181x* mfd = pwr->mfd;
|
|
int tmp_vsys;
|
|
|
|
tmp_vsys = 0;
|
|
|
|
tmp_vsys = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VSYS_U);
|
|
|
|
*vsys = tmp_vsys * 1000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get battery minimum average voltage
|
|
* @param pwr power device
|
|
* @param vcell pointer to return back voltage in unit uV.
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_get_vbat_min(struct bd7181x_power *pwr, int *vcell) {
|
|
struct bd7181x* mfd = pwr->mfd;
|
|
int tmp_vcell;
|
|
|
|
tmp_vcell = 0;
|
|
|
|
tmp_vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VBAT_MIN_U);
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_VM_SA_MINMAX_CLR, VBAT_SA_MIN_CLR);
|
|
|
|
*vcell = tmp_vcell * 1000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get system minimum average voltage
|
|
* @param pwr power device
|
|
* @param vcell pointer to return back voltage in unit uV.
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_get_vsys_min(struct bd7181x_power *pwr, int *vcell) {
|
|
struct bd7181x* mfd = pwr->mfd;
|
|
int tmp_vcell;
|
|
|
|
tmp_vcell = 0;
|
|
|
|
tmp_vcell = bd7181x_reg_read16(mfd, BD7181X_REG_VM_SA_VSYS_MIN_U);
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_VM_SA_MINMAX_CLR, VSYS_SA_MIN_CLR);
|
|
|
|
*vcell = tmp_vcell * 1000;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get battery capacity
|
|
* @param ocv open circuit voltage
|
|
* @return capcity in unit 0.1 percent
|
|
*/
|
|
static int bd7181x_voltage_to_capacity(int ocv) {
|
|
int i = 0;
|
|
int soc;
|
|
|
|
if (ocv > ocv_table[0]) {
|
|
soc = soc_table[0];
|
|
} else {
|
|
i = 0;
|
|
while (soc_table[i] != -50) {
|
|
if ((ocv <= ocv_table[i]) && (ocv > ocv_table[i+1])) {
|
|
soc = (soc_table[i] - soc_table[i+1]) * (ocv - ocv_table[i+1]) / (ocv_table[i] - ocv_table[i+1]);
|
|
soc += soc_table[i+1];
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
if (soc_table[i] == -50)
|
|
soc = soc_table[i];
|
|
}
|
|
return soc;
|
|
}
|
|
|
|
/** @brief get battery temperature
|
|
* @param pwr power device
|
|
* @return temperature in unit deg.Celsius
|
|
*/
|
|
static int bd7181x_get_temp(struct bd7181x_power *pwr) {
|
|
struct bd7181x* mfd = pwr->mfd;
|
|
int t;
|
|
|
|
t = 200 - (int)bd7181x_reg_read(mfd, BD7181X_REG_VM_BTMP);
|
|
|
|
// battery temperature error
|
|
t = (t > 200)? 200: t;
|
|
|
|
return t;
|
|
}
|
|
|
|
static int bd7181x_reset_coulomb_count(struct bd7181x_power* pwr);
|
|
|
|
/** @brief get battery charge status
|
|
* @param pwr power device
|
|
* @return temperature in unit deg.Celsius
|
|
*/
|
|
static int bd7181x_charge_status(struct bd7181x_power *pwr)
|
|
{
|
|
u8 state;
|
|
int ret = 1;
|
|
|
|
state = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE);
|
|
dev_dbg(pwr->dev, "%s(): CHG_STATE %d\n", __func__, state);
|
|
|
|
switch (state) {
|
|
case 0x00:
|
|
ret = 0;
|
|
pwr->rpt_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
break;
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x03:
|
|
case 0x0E:
|
|
pwr->rpt_status = POWER_SUPPLY_STATUS_CHARGING;
|
|
pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
break;
|
|
case 0x0F:
|
|
ret = 0;
|
|
pwr->rpt_status = POWER_SUPPLY_STATUS_FULL;
|
|
pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
break;
|
|
case 0x10:
|
|
case 0x11:
|
|
case 0x12:
|
|
case 0x13:
|
|
case 0x14:
|
|
case 0x20:
|
|
case 0x21:
|
|
case 0x22:
|
|
case 0x23:
|
|
case 0x24:
|
|
ret = 0;
|
|
pwr->rpt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
pwr->bat_health = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
break;
|
|
case 0x30:
|
|
case 0x31:
|
|
case 0x32:
|
|
case 0x40:
|
|
ret = 0;
|
|
pwr->rpt_status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
pwr->bat_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
break;
|
|
case 0x7f:
|
|
default:
|
|
ret = 0;
|
|
pwr->rpt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
pwr->bat_health = POWER_SUPPLY_HEALTH_DEAD;
|
|
break;
|
|
}
|
|
|
|
bd7181x_reset_coulomb_count(pwr);
|
|
|
|
pwr->prev_rpt_status = pwr->rpt_status;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if INIT_COULOMB == BY_BAT_VOLT
|
|
static int bd7181x_calib_voltage(struct bd7181x_power* pwr, int* ocv) {
|
|
int r, curr, volt;
|
|
|
|
bd7181x_get_vbat_curr(pwr, &volt, &curr);
|
|
|
|
r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE);
|
|
if (r >= 0 && curr > 0) {
|
|
// voltage increment caused by battery inner resistor
|
|
if (r == 3) volt -= 100 * 1000;
|
|
else if (r == 2) volt -= 50 * 1000;
|
|
}
|
|
*ocv = volt;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/** @brief set initial coulomb counter value from battery voltage
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int calibration_coulomb_counter(struct bd7181x_power* pwr)
|
|
{
|
|
u32 bcap;
|
|
int soc, ocv;
|
|
|
|
#if INIT_COULOMB == BY_VBATLOAD_REG
|
|
/* Get init OCV by HW */
|
|
bd7181x_get_init_bat_stat(pwr);
|
|
|
|
ocv = (pwr->hw_ocv1 >= pwr->hw_ocv2)? pwr->hw_ocv1: pwr->hw_ocv2;
|
|
dev_dbg(pwr->dev, "ocv %d\n", ocv);
|
|
#elif INIT_COULOMB == BY_BAT_VOLT
|
|
bd7181x_calib_voltage(pwr, &ocv);
|
|
#endif
|
|
|
|
/* Get init soc from ocv/soc table */
|
|
soc = bd7181x_voltage_to_capacity(ocv);
|
|
dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc);
|
|
if (soc < 0)
|
|
soc = 0;
|
|
bcap = pwr->designed_cap * soc / 1000;
|
|
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0);
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((bcap + pwr->designed_cap / 200) & 0x1FFFUL));
|
|
|
|
pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL;
|
|
dev_dbg(pwr->dev, "%s() CC_CCNTD = %d\n", __func__, pwr->coulomb_cnt);
|
|
|
|
/* Start canceling offset of the DS ADC. This needs 1 second at least */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCCALIB);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief adjust coulomb counter values at relaxed state
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_adjust_coulomb_count(struct bd7181x_power* pwr)
|
|
{
|
|
u32 relaxed_coulomb_cnt;
|
|
|
|
relaxed_coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_REX_CCNTD_3) & 0x1FFFFFFFUL;
|
|
dev_dbg(pwr->dev, "%s(): relaxed_coulomb_cnt = 0x%x\n", __func__, relaxed_coulomb_cnt);
|
|
if (relaxed_coulomb_cnt != 0) {
|
|
u32 bcap;
|
|
int soc, ocv;
|
|
int diff_coulomb_cnt;
|
|
|
|
/* Get OCV at relaxed state by HW */
|
|
ocv = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_REX_SA_VBAT_U) * 1000;
|
|
dev_dbg(pwr->dev, "ocv %d\n", ocv);
|
|
|
|
/* Clear Relaxed Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_REX_CTRL_1, REX_CLR);
|
|
|
|
diff_coulomb_cnt = relaxed_coulomb_cnt - (bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL);
|
|
diff_coulomb_cnt = diff_coulomb_cnt >> 16;
|
|
dev_dbg(pwr->dev, "diff_coulomb_cnt = %d\n", diff_coulomb_cnt);
|
|
|
|
/* Get soc at relaxed state from ocv/soc table */
|
|
soc = bd7181x_voltage_to_capacity(ocv);
|
|
dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc);
|
|
if (soc < 0)
|
|
soc = 0;
|
|
|
|
if ((soc > CANCEL_ADJ_COULOMB_SOC_H_1) || ((soc < CANCEL_ADJ_COULOMB_SOC_L_1) && (soc > CANCEL_ADJ_COULOMB_SOC_H_2)) || (soc < CANCEL_ADJ_COULOMB_SOC_L_2) ||
|
|
((pwr->temp <= FORCE_ADJ_COULOMB_TEMP_H) && (pwr->temp >= FORCE_ADJ_COULOMB_TEMP_L))) {
|
|
bcap = pwr->designed_cap * soc / 1000;
|
|
|
|
/* Stop Coulomb Counter */
|
|
bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0);
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((bcap + pwr->designed_cap / 200) & 0x1FFFUL) + diff_coulomb_cnt);
|
|
|
|
pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL;
|
|
dev_dbg(pwr->dev, "Adjust Coulomb Counter at Relaxed State\n");
|
|
dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt);
|
|
|
|
/* Start Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
/* If the following commented out code is enabled, the SOC is not clamped at the relax time. */
|
|
/* Reset SOCs */
|
|
/* bd7181x_calc_soc_org(pwr); */
|
|
/* pwr->soc_norm = pwr->soc_org; */
|
|
/* pwr->soc = pwr->soc_norm; */
|
|
/* pwr->clamp_soc = pwr->soc; */
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief reset coulomb counter values at full charged state
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_reset_coulomb_count(struct bd7181x_power* pwr)
|
|
{
|
|
u32 full_charged_coulomb_cnt;
|
|
|
|
full_charged_coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_FULL_CCNTD_3) & 0x1FFFFFFFUL;
|
|
dev_dbg(pwr->dev, "%s(): full_charged_coulomb_cnt=0x%x\n", __func__, full_charged_coulomb_cnt);
|
|
if (full_charged_coulomb_cnt != 0) {
|
|
int diff_coulomb_cnt;
|
|
|
|
/* Clear Full Charged Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_FULL_CTRL, FULL_CLR);
|
|
|
|
diff_coulomb_cnt = full_charged_coulomb_cnt - (bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL);
|
|
diff_coulomb_cnt = diff_coulomb_cnt >> 16;
|
|
if (diff_coulomb_cnt > 0) {
|
|
diff_coulomb_cnt = 0;
|
|
}
|
|
dev_dbg(pwr->dev, "diff_coulomb_cnt = %d\n", diff_coulomb_cnt);
|
|
|
|
/* Stop Coulomb Counter */
|
|
bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0);
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((pwr->designed_cap + pwr->designed_cap / 200) & 0x1FFFUL) + diff_coulomb_cnt);
|
|
|
|
pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL;
|
|
dev_dbg(pwr->dev, "Reset Coulomb Counter at POWER_SUPPLY_STATUS_FULL\n");
|
|
dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt);
|
|
|
|
/* Start Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get battery parameters, such as voltages, currents, temperatures.
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_get_voltage_current(struct bd7181x_power* pwr)
|
|
{
|
|
|
|
/* Read detailed vcell and current */
|
|
bd7181x_get_vbat_curr(pwr, &pwr->vcell, &pwr->curr_sar);
|
|
dev_dbg(pwr->dev, "VM_VBAT = %d\n", pwr->vcell);
|
|
dev_dbg(pwr->dev, "VM_IBAT = %d\n", pwr->curr_sar);
|
|
|
|
pwr->curr = bd7181x_get_current_ds_adc(pwr);
|
|
dev_dbg(pwr->dev, "CC_CURCD = %d\n", pwr->curr);
|
|
|
|
/* Read detailed vsys */
|
|
bd7181x_get_vsys(pwr, &pwr->vsys);
|
|
dev_dbg(pwr->dev, "VM_VSYS = %d\n", pwr->vsys);
|
|
|
|
/* Read detailed vbat_min */
|
|
bd7181x_get_vbat_min(pwr, &pwr->vcell_min);
|
|
dev_dbg(pwr->dev, "VM_VBAT_MIN = %d\n", pwr->vcell_min);
|
|
|
|
/* Read detailed vsys_min */
|
|
bd7181x_get_vsys_min(pwr, &pwr->vsys_min);
|
|
dev_dbg(pwr->dev, "VM_VSYS_MIN = %d\n", pwr->vsys_min);
|
|
|
|
/* Get tempature */
|
|
pwr->temp = bd7181x_get_temp(pwr);
|
|
// dev_dbg(pwr->dev, "Temperature %d degrees C\n", pwr->temp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief adjust coulomb counter values at relaxed state by SW
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_adjust_coulomb_count_sw(struct bd7181x_power* pwr)
|
|
{
|
|
int tmp_curr_mA;
|
|
|
|
tmp_curr_mA = pwr->curr / 1000;
|
|
if ((tmp_curr_mA * tmp_curr_mA) <= (THR_RELAX_CURRENT * THR_RELAX_CURRENT)) { /* No load */
|
|
pwr->relax_time += (JITTER_DEFAULT / 1000);
|
|
}
|
|
else {
|
|
pwr->relax_time = 0;
|
|
}
|
|
dev_dbg(pwr->dev, "%s(): pwr->relax_time = 0x%x\n", __func__, pwr->relax_time);
|
|
if (pwr->relax_time >= THR_RELAX_TIME) { /* Battery is relaxed. */
|
|
u32 bcap;
|
|
int soc, ocv;
|
|
|
|
pwr->relax_time = 0;
|
|
|
|
/* Get OCV */
|
|
ocv = pwr->vcell;
|
|
|
|
/* Get soc at relaxed state from ocv/soc table */
|
|
soc = bd7181x_voltage_to_capacity(ocv);
|
|
dev_dbg(pwr->dev, "soc %d[0.1%%]\n", soc);
|
|
if (soc < 0)
|
|
soc = 0;
|
|
|
|
if ((soc > CANCEL_ADJ_COULOMB_SOC_H_1) || ((soc < CANCEL_ADJ_COULOMB_SOC_L_1) && (soc > CANCEL_ADJ_COULOMB_SOC_H_2)) || (soc < CANCEL_ADJ_COULOMB_SOC_L_2) ||
|
|
((pwr->temp <= FORCE_ADJ_COULOMB_TEMP_H) && (pwr->temp >= FORCE_ADJ_COULOMB_TEMP_L))) {
|
|
bcap = pwr->designed_cap * soc / 1000;
|
|
|
|
/* Stop Coulomb Counter */
|
|
bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0);
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((bcap + pwr->designed_cap / 200) & 0x1FFFUL));
|
|
|
|
pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL;
|
|
dev_dbg(pwr->dev, "Adjust Coulomb Counter by SW at Relaxed State\n");
|
|
dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt);
|
|
|
|
/* Start Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
/* If the following commented out code is enabled, the SOC is not clamped at the relax time. */
|
|
/* Reset SOCs */
|
|
/* bd7181x_calc_soc_org(pwr); */
|
|
/* pwr->soc_norm = pwr->soc_org; */
|
|
/* pwr->soc = pwr->soc_norm; */
|
|
/* pwr->clamp_soc = pwr->soc; */
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get coulomb counter values
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_coulomb_count(struct bd7181x_power* pwr)
|
|
{
|
|
dev_dbg(pwr->dev, "%s(): pwr->state_machine = 0x%x\n", __func__, pwr->state_machine);
|
|
if (pwr->state_machine == STAT_POWER_ON) {
|
|
pwr->state_machine = STAT_INITIALIZED;
|
|
/* Start Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
} else if (pwr->state_machine == STAT_INITIALIZED) {
|
|
pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL;
|
|
// dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** @brief calc cycle
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_update_cycle(struct bd7181x_power* pwr)
|
|
{
|
|
int charged_coulomb_cnt;
|
|
|
|
charged_coulomb_cnt = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CCNTD_CHG_3);
|
|
dev_dbg(pwr->dev, "%s(): charged_coulomb_cnt = 0x%x\n", __func__, charged_coulomb_cnt);
|
|
if (charged_coulomb_cnt >= pwr->designed_cap) {
|
|
pwr->cycle++;
|
|
dev_dbg(pwr->dev, "Update cycle = %d\n", pwr->cycle);
|
|
battery_cycle = pwr->cycle;
|
|
charged_coulomb_cnt -= pwr->designed_cap;
|
|
/* Stop Coulomb Counter */
|
|
bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CCNTD_CHG_3, charged_coulomb_cnt);
|
|
|
|
/* Start Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** @brief calc full capacity value by Cycle and Temperature
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_calc_full_cap(struct bd7181x_power* pwr)
|
|
{
|
|
u32 designed_cap_uAh;
|
|
u32 full_cap_uAh;
|
|
|
|
/* Calculate full capacity by cycle */
|
|
designed_cap_uAh = A10s_mAh(pwr->designed_cap) * 1000;
|
|
full_cap_uAh = designed_cap_uAh - BD7181X_DGRD_CYC_CAP * pwr->cycle;
|
|
pwr->full_cap = mAh_A10s(full_cap_uAh / 1000);
|
|
dev_dbg(pwr->dev, "Calculate full capacity by cycle\n");
|
|
dev_dbg(pwr->dev, "%s() pwr->full_cap = %d\n", __func__, pwr->full_cap);
|
|
|
|
/* Calculate full capacity by temperature */
|
|
dev_dbg(pwr->dev, "Temperature = %d\n", pwr->temp);
|
|
if (pwr->temp >= BD7181X_DGRD_TEMP_M) {
|
|
full_cap_uAh += (pwr->temp - BD7181X_DGRD_TEMP_M) * BD7181X_DGRD_TEMP_CAP_H;
|
|
pwr->full_cap = mAh_A10s(full_cap_uAh / 1000);
|
|
}
|
|
else if (pwr->temp >= BD7181X_DGRD_TEMP_L) {
|
|
full_cap_uAh += (pwr->temp - BD7181X_DGRD_TEMP_M) * BD7181X_DGRD_TEMP_CAP_M;
|
|
pwr->full_cap = mAh_A10s(full_cap_uAh / 1000);
|
|
}
|
|
else {
|
|
full_cap_uAh += (BD7181X_DGRD_TEMP_L - BD7181X_DGRD_TEMP_M) * BD7181X_DGRD_TEMP_CAP_M;
|
|
full_cap_uAh += (pwr->temp - BD7181X_DGRD_TEMP_L) * BD7181X_DGRD_TEMP_CAP_L;
|
|
pwr->full_cap = mAh_A10s(full_cap_uAh / 1000);
|
|
}
|
|
dev_dbg(pwr->dev, "Calculate full capacity by cycle and temperature\n");
|
|
dev_dbg(pwr->dev, "%s() pwr->full_cap = %d\n", __func__, pwr->full_cap);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief calculate SOC values by designed capacity
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_calc_soc_org(struct bd7181x_power* pwr)
|
|
{
|
|
pwr->soc_org = (pwr->coulomb_cnt >> 16) * 100 / pwr->designed_cap;
|
|
if (pwr->soc_org > 100) {
|
|
pwr->soc_org = 100;
|
|
/* Stop Coulomb Counter */
|
|
bd7181x_clear_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_1, 0);
|
|
bd7181x_reg_write16(pwr->mfd, BD7181X_REG_CC_CCNTD_3, ((pwr->designed_cap + pwr->designed_cap / 200) & 0x1FFFUL));
|
|
|
|
pwr->coulomb_cnt = bd7181x_reg_read32(pwr->mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL;
|
|
dev_dbg(pwr->dev, "Limit Coulomb Counter\n");
|
|
dev_dbg(pwr->dev, "CC_CCNTD = %d\n", pwr->coulomb_cnt);
|
|
|
|
/* Start Coulomb Counter */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
}
|
|
dev_dbg(pwr->dev, "%s(): pwr->soc_org = %d\n", __func__, pwr->soc_org);
|
|
return 0;
|
|
}
|
|
|
|
/** @brief calculate SOC values by full capacity
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_calc_soc_norm(struct bd7181x_power* pwr)
|
|
{
|
|
int lost_cap;
|
|
int mod_coulomb_cnt;
|
|
|
|
lost_cap = pwr->designed_cap - pwr->full_cap;
|
|
dev_dbg(pwr->dev, "%s() lost_cap = %d\n", __func__, lost_cap);
|
|
mod_coulomb_cnt = (pwr->coulomb_cnt >> 16) - lost_cap;
|
|
if ((mod_coulomb_cnt > 0) && (pwr->full_cap > 0)) {
|
|
pwr->soc_norm = mod_coulomb_cnt * 100 / pwr->full_cap;
|
|
}
|
|
else {
|
|
pwr->soc_norm = 0;
|
|
}
|
|
if (pwr->soc_norm > 100) {
|
|
pwr->soc_norm = 100;
|
|
}
|
|
dev_dbg(pwr->dev, "%s() pwr->soc_norm = %d\n", __func__, pwr->soc_norm);
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get OCV value by SOC
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
int bd7181x_get_ocv(struct bd7181x_power* pwr, int dsoc) {
|
|
int i = 0;
|
|
int ocv = 0;
|
|
|
|
if (dsoc > soc_table[0]) {
|
|
ocv = MAX_VOLTAGE;
|
|
}
|
|
else if (dsoc == 0) {
|
|
ocv = ocv_table[21];
|
|
}
|
|
else {
|
|
i = 0;
|
|
while (i < 22) {
|
|
if ((dsoc <= soc_table[i]) && (dsoc > soc_table[i+1])) {
|
|
ocv = (ocv_table[i] - ocv_table[i+1]) * (dsoc - soc_table[i+1]) / (soc_table[i] - soc_table[i+1]) + ocv_table[i+1];
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
if (i == 22)
|
|
ocv = ocv_table[22];
|
|
}
|
|
dev_dbg(pwr->dev, "%s() ocv = %d\n", __func__, ocv);
|
|
return ocv;
|
|
}
|
|
|
|
/** @brief calculate SOC value by full_capacity and load
|
|
* @param pwr power device
|
|
* @return OCV
|
|
*/
|
|
static int bd7181x_calc_soc(struct bd7181x_power* pwr) {
|
|
int ocv_table_load[23];
|
|
|
|
pwr->soc = pwr->soc_norm;
|
|
|
|
switch (pwr->rpt_status) { /* Adjust for 0% between THR_VOLTAGE and MIN_VOLTAGE */
|
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
|
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
|
if (pwr->vsys_min <= THR_VOLTAGE) {
|
|
int i;
|
|
int ocv;
|
|
int lost_cap;
|
|
int mod_coulomb_cnt;
|
|
int dsoc;
|
|
|
|
lost_cap = pwr->designed_cap - pwr->full_cap;
|
|
mod_coulomb_cnt = (pwr->coulomb_cnt >> 16) - lost_cap;
|
|
dsoc = mod_coulomb_cnt * 1000 / pwr->full_cap;
|
|
dev_dbg(pwr->dev, "%s() dsoc = %d\n", __func__, dsoc);
|
|
ocv = bd7181x_get_ocv(pwr, dsoc);
|
|
for (i = 1; i < 23; i++) {
|
|
ocv_table_load[i] = ocv_table[i] - (ocv - pwr->vsys_min);
|
|
if (ocv_table_load[i] <= MIN_VOLTAGE) {
|
|
dev_dbg(pwr->dev, "%s() ocv_table_load[%d] = %d\n", __func__, i, ocv_table_load[i]);
|
|
break;
|
|
}
|
|
}
|
|
if (i < 23) {
|
|
int j;
|
|
int dv = (ocv_table_load[i-1] - ocv_table_load[i]) / 5;
|
|
int lost_cap2;
|
|
int mod_coulomb_cnt2, mod_full_cap;
|
|
for (j = 1; j < 5; j++){
|
|
if ((ocv_table_load[i] + dv * j) > MIN_VOLTAGE) {
|
|
break;
|
|
}
|
|
}
|
|
lost_cap2 = ((21 - i) * 5 + (j - 1)) * pwr->full_cap / 100;
|
|
dev_dbg(pwr->dev, "%s() lost_cap2 = %d\n", __func__, lost_cap2);
|
|
mod_coulomb_cnt2 = mod_coulomb_cnt - lost_cap2;
|
|
mod_full_cap = pwr->full_cap - lost_cap2;
|
|
if ((mod_coulomb_cnt2 > 0) && (mod_full_cap > 0)) {
|
|
pwr->soc = mod_coulomb_cnt2 * 100 / mod_full_cap;
|
|
}
|
|
else {
|
|
pwr->soc = 0;
|
|
}
|
|
dev_dbg(pwr->dev, "%s() pwr->soc(by load) = %d\n", __func__, pwr->soc);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (pwr->rpt_status) {/* Adjust for 0% and 100% */
|
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
|
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
|
if (pwr->vsys_min <= MIN_VOLTAGE) {
|
|
pwr->soc = 0;
|
|
}
|
|
else {
|
|
if (pwr->soc == 0) {
|
|
pwr->soc = 1;
|
|
}
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_STATUS_CHARGING:
|
|
if (pwr->soc == 100) {
|
|
pwr->soc = 99;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
dev_dbg(pwr->dev, "%s() pwr->soc = %d\n", __func__, pwr->soc);
|
|
return 0;
|
|
}
|
|
|
|
/** @brief calculate Clamped SOC value by full_capacity and load
|
|
* @param pwr power device
|
|
* @return OCV
|
|
*/
|
|
static int bd7181x_calc_soc_clamp(struct bd7181x_power* pwr) {
|
|
switch (pwr->rpt_status) {/* Adjust for 0% and 100% */
|
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
|
case POWER_SUPPLY_STATUS_NOT_CHARGING:
|
|
if (pwr->soc <= pwr->clamp_soc) {
|
|
pwr->clamp_soc = pwr->soc;
|
|
}
|
|
break;
|
|
default:
|
|
pwr->clamp_soc = pwr->soc;
|
|
break;
|
|
}
|
|
dev_dbg(pwr->dev, "%s() pwr->clamp_soc = %d\n", __func__, pwr->clamp_soc);
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get battery and DC online status
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_get_online(struct bd7181x_power* pwr)
|
|
{
|
|
int r;
|
|
|
|
#if 0
|
|
#define TS_THRESHOLD_VOLT 0xD9
|
|
r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_VM_VTH);
|
|
pwr->bat_online = (r > TS_THRESHOLD_VOLT);
|
|
#endif
|
|
#if 0
|
|
r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT);
|
|
if (r >= 0 && (r & BAT_DET_DONE)) {
|
|
pwr->bat_online = (r & BAT_DET) != 0;
|
|
}
|
|
#endif
|
|
#if 1
|
|
#define BAT_OPEN 0x7
|
|
r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_TEMP);
|
|
pwr->bat_online = (r != BAT_OPEN);
|
|
#endif
|
|
r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_DCIN_STAT);
|
|
if (r >= 0) {
|
|
pwr->charger_online = (r & VBUS_DET) != 0;
|
|
}
|
|
dev_dbg(pwr->dev, "%s(): pwr->bat_online = %d, pwr->charger_online = %d\n", __func__, pwr->bat_online, pwr->charger_online);
|
|
return 0;
|
|
}
|
|
|
|
/** @brief init bd7181x sub module charger
|
|
* @param pwr power device
|
|
* @return 0
|
|
*/
|
|
static int bd7181x_init_hardware(struct bd7181x_power *pwr)
|
|
{
|
|
struct bd7181x *mfd = pwr->mfd;
|
|
int r;
|
|
|
|
r = bd7181x_reg_write(mfd, BD7181X_REG_DCIN_CLPS, 0x36);
|
|
|
|
#define XSTB 0x02
|
|
r = bd7181x_reg_read(mfd, BD7181X_REG_CONF);
|
|
|
|
#if 0
|
|
for (i = 0; i < 300; i++) {
|
|
r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT);
|
|
if (r >= 0 && (r & BAT_DET_DONE)) {
|
|
break;
|
|
}
|
|
msleep(5);
|
|
}
|
|
#endif
|
|
if ((r & XSTB) == 0x00) {
|
|
//if (r & BAT_DET) {
|
|
/* Init HW, when the battery is inserted. */
|
|
|
|
bd7181x_reg_write(mfd, BD7181X_REG_CONF, r | XSTB);
|
|
|
|
#define TEST_SEQ_00 0x00
|
|
#define TEST_SEQ_01 0x76
|
|
#define TEST_SEQ_02 0x66
|
|
#define TEST_SEQ_03 0x56
|
|
#if 0
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_01);
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_02);
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_03);
|
|
bd7181x_reg_write16(pwr->mfd, 0xA2, CALIB_CURRENT_A2A3);
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00);
|
|
#endif
|
|
|
|
/* Stop Coulomb Counter */
|
|
bd7181x_clear_bits(mfd, BD7181X_REG_CC_CTRL, CCNTENB);
|
|
|
|
/* Set Coulomb Counter Reset bit*/
|
|
bd7181x_set_bits(mfd, BD7181X_REG_CC_CTRL, CCNTRST);
|
|
|
|
/* Clear Coulomb Counter Reset bit*/
|
|
bd7181x_clear_bits(mfd, BD7181X_REG_CC_CTRL, CCNTRST);
|
|
|
|
/* Set default Battery Capacity */
|
|
pwr->designed_cap = BD7181X_BATTERY_CAP;
|
|
pwr->full_cap = BD7181X_BATTERY_CAP;
|
|
|
|
/* Set initial Coulomb Counter by HW OCV */
|
|
calibration_coulomb_counter(pwr);
|
|
|
|
/* WDT_FST auto set */
|
|
bd7181x_set_bits(mfd, BD7181X_REG_CHG_SET1, WDT_AUTO);
|
|
|
|
/* VBAT Low voltage detection Setting, added by John Zhang*/
|
|
bd7181x_reg_write16(mfd, BD7181X_REG_ALM_VBAT_TH_U, VBAT_LOW_TH);
|
|
|
|
/* Mask Relax decision by PMU STATE */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_REX_CTRL_1, REX_PMU_STATE_MASK);
|
|
|
|
/* Set Battery Capacity Monitor threshold1 as 90% */
|
|
bd7181x_reg_write16(mfd, BD7181X_REG_CC_BATCAP1_TH_U, (BD7181X_BATTERY_CAP * 9 / 10));
|
|
dev_dbg(pwr->dev, "BD7181X_REG_CC_BATCAP1_TH = %d\n", (BD7181X_BATTERY_CAP * 9 / 10));
|
|
|
|
/* Enable LED ON when charging */
|
|
bd7181x_set_bits(pwr->mfd, BD7181X_REG_LED_CTRL, CHGDONE_LED_EN);
|
|
|
|
pwr->state_machine = STAT_POWER_ON;
|
|
} else {
|
|
pwr->designed_cap = BD7181X_BATTERY_CAP;
|
|
pwr->full_cap = BD7181X_BATTERY_CAP; // bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CC_BATCAP_U);
|
|
pwr->state_machine = STAT_INITIALIZED; // STAT_INITIALIZED
|
|
}
|
|
|
|
pwr->temp = bd7181x_get_temp(pwr);
|
|
dev_dbg(pwr->dev, "Temperature = %d\n", pwr->temp);
|
|
bd7181x_adjust_coulomb_count(pwr);
|
|
bd7181x_reset_coulomb_count(pwr);
|
|
pwr->coulomb_cnt = bd7181x_reg_read32(mfd, BD7181X_REG_CC_CCNTD_3) & 0x1FFFFFFFUL;
|
|
bd7181x_calc_soc_org(pwr);
|
|
pwr->soc_norm = pwr->soc_org;
|
|
pwr->soc = pwr->soc_norm;
|
|
pwr->clamp_soc = pwr->soc;
|
|
dev_dbg(pwr->dev, "%s() CC_CCNTD = %d\n", __func__, pwr->coulomb_cnt);
|
|
dev_dbg(pwr->dev, "%s() pwr->soc = %d\n", __func__, pwr->soc);
|
|
dev_dbg(pwr->dev, "%s() pwr->clamp_soc = %d\n", __func__, pwr->clamp_soc);
|
|
|
|
pwr->cycle = battery_cycle;
|
|
pwr->curr = 0;
|
|
pwr->curr_sar = 0;
|
|
pwr->relax_time = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**@brief timed work function called by system
|
|
* read battery capacity,
|
|
* sense change of charge status, etc.
|
|
* @param work work struct
|
|
* @return void
|
|
*/
|
|
|
|
static void bd_work_callback(struct work_struct *work)
|
|
{
|
|
struct bd7181x_power *pwr;
|
|
struct delayed_work *delayed_work;
|
|
int status, changed = 0;
|
|
static int cap_counter = 0;
|
|
|
|
delayed_work = container_of(work, struct delayed_work, work);
|
|
pwr = container_of(delayed_work, struct bd7181x_power, bd_work);
|
|
|
|
dev_dbg(pwr->dev, "%s(): in\n", __func__);
|
|
status = bd7181x_reg_read(pwr->mfd, BD7181X_REG_DCIN_STAT);
|
|
if (status != pwr->vbus_status) {
|
|
//printk("DCIN_STAT CHANGED from 0x%X to 0x%X\n", pwr->vbus_status, status);
|
|
pwr->vbus_status = status;
|
|
changed = 1;
|
|
}
|
|
|
|
status = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT);
|
|
status &= ~BAT_DET_DONE;
|
|
if (status != pwr->bat_status) {
|
|
dev_dbg(pwr->dev, "BAT_STAT CHANGED from 0x%X to 0x%X\n", pwr->bat_status, status);
|
|
pwr->bat_status = status;
|
|
changed = 1;
|
|
}
|
|
|
|
status = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE);
|
|
if (status != pwr->charge_status) {
|
|
dev_dbg(pwr->dev, "CHG_STATE CHANGED from 0x%X to 0x%X\n", pwr->charge_status, status);
|
|
pwr->charge_status = status;
|
|
//changed = 1;
|
|
}
|
|
|
|
bd7181x_get_voltage_current(pwr);
|
|
bd7181x_adjust_coulomb_count(pwr);
|
|
bd7181x_reset_coulomb_count(pwr);
|
|
bd7181x_adjust_coulomb_count_sw(pwr);
|
|
bd7181x_coulomb_count(pwr);
|
|
bd7181x_update_cycle(pwr);
|
|
bd7181x_calc_full_cap(pwr);
|
|
bd7181x_calc_soc_org(pwr);
|
|
bd7181x_calc_soc_norm(pwr);
|
|
bd7181x_calc_soc(pwr);
|
|
bd7181x_calc_soc_clamp(pwr);
|
|
bd7181x_get_online(pwr);
|
|
bd7181x_charge_status(pwr);
|
|
|
|
if (changed || cap_counter++ > JITTER_REPORT_CAP / JITTER_DEFAULT) {
|
|
power_supply_changed(pwr->ac);
|
|
power_supply_changed(pwr->bat);
|
|
cap_counter = 0;
|
|
}
|
|
|
|
if (pwr->calib_current == CALIB_NORM) {
|
|
schedule_delayed_work(&pwr->bd_work, msecs_to_jiffies(JITTER_DEFAULT));
|
|
} else if (pwr->calib_current == CALIB_START) {
|
|
pwr->calib_current = CALIB_GO;
|
|
}
|
|
}
|
|
|
|
/**@brief bd7181x power interrupt
|
|
* @param irq system irq
|
|
* @param pwrsys bd7181x power device of system
|
|
* @retval IRQ_HANDLED success
|
|
* @retval IRQ_NONE error
|
|
*/
|
|
static irqreturn_t bd7181x_power_interrupt(int irq, void *pwrsys)
|
|
{
|
|
struct device *dev = pwrsys;
|
|
struct bd7181x *mfd = dev_get_drvdata(dev->parent);
|
|
// struct bd7181x_power *pwr = dev_get_drvdata(dev);
|
|
int reg, r;
|
|
|
|
dev_info(mfd->dev, "bd7181x_power_interrupt() in.\n");
|
|
|
|
reg = bd7181x_reg_read(mfd, BD7181X_REG_INT_STAT_03);
|
|
if (reg < 0)
|
|
return IRQ_NONE;
|
|
|
|
dev_info(mfd->dev, "INT_STAT_03 = 0x%.2X\n", reg);
|
|
if(reg & POWERON_PRESS)
|
|
{
|
|
kobject_uevent(&(mfd->dev->kobj), KOBJ_ONLINE);
|
|
dev_info(mfd->dev, "POWERON_PRESS\n");
|
|
}
|
|
if(reg & POWERON_SHORT)
|
|
{
|
|
kobject_uevent(&(mfd->dev->kobj), KOBJ_OFFLINE);
|
|
dev_info(mfd->dev, "POWERON_SHORT\n");
|
|
}
|
|
if(reg & POWERON_MID)
|
|
{
|
|
kobject_uevent(&(mfd->dev->kobj), KOBJ_OFFLINE);
|
|
dev_info(mfd->dev, "POWERON_MID\n");
|
|
}
|
|
if(reg & POWERON_LONG)
|
|
{
|
|
kobject_uevent(&(mfd->dev->kobj), KOBJ_OFFLINE);
|
|
dev_info(mfd->dev, "POWERON_LONG\n");
|
|
}
|
|
|
|
r = bd7181x_reg_write(mfd, BD7181X_REG_INT_STAT_03, reg);
|
|
if (r)
|
|
return IRQ_NONE;
|
|
|
|
if (reg & DCIN_MON_DET) {
|
|
dev_info(mfd->dev, "\n~~~DCIN removed\n");
|
|
} else if (reg & DCIN_MON_RES) {
|
|
dev_info(mfd->dev, "\n~~~DCIN inserted\n");
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**@brief bd7181x vbat low voltage detection interrupt
|
|
* @param irq system irq
|
|
* @param pwrsys bd7181x power device of system
|
|
* @retval IRQ_HANDLED success
|
|
* @retval IRQ_NONE error
|
|
* added by John Zhang at 2015-07-22
|
|
*/
|
|
static irqreturn_t bd7181x_vbat_interrupt(int irq, void *pwrsys)
|
|
{
|
|
struct device *dev = pwrsys;
|
|
struct bd7181x *mfd = dev_get_drvdata(dev->parent);
|
|
// struct bd7181x_power *pwr = dev_get_drvdata(dev);
|
|
int reg, r;
|
|
|
|
dev_info(mfd->dev, "bd7181x_vbat_interrupt() in.\n");
|
|
|
|
reg = bd7181x_reg_read(mfd, BD7181X_REG_INT_STAT_08);
|
|
if (reg < 0)
|
|
return IRQ_NONE;
|
|
|
|
dev_info(mfd->dev, "INT_STAT_08 = 0x%.2X\n", reg);
|
|
|
|
r = bd7181x_reg_write(mfd, BD7181X_REG_INT_STAT_08, reg);
|
|
if (r)
|
|
return IRQ_NONE;
|
|
|
|
if (reg & VBAT_MON_DET) {
|
|
dev_info(mfd->dev, "\n~~~ VBAT LOW Detected ... \n");
|
|
|
|
} else if (reg & VBAT_MON_RES) {
|
|
dev_info(mfd->dev, "\n~~~ VBAT LOW Resumed ... \n");
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
/**@brief bd7181x int_stat_11 detection interrupt
|
|
* @param irq system irq
|
|
* @param pwrsys bd7181x power device of system
|
|
* @retval IRQ_HANDLED success
|
|
* @retval IRQ_NONE error
|
|
* added 2015-12-26
|
|
*/
|
|
static irqreturn_t bd7181x_int_11_interrupt(int irq, void *pwrsys)
|
|
{
|
|
struct device *dev = pwrsys;
|
|
struct bd7181x *mfd = dev_get_drvdata(dev->parent);
|
|
// struct bd7181x_power *pwr = dev_get_drvdata(dev);
|
|
int reg, r;
|
|
|
|
dev_info(mfd->dev, "bd7181x_int_11_interrupt() in.\n");
|
|
|
|
reg = bd7181x_reg_read(mfd, BD7181X_REG_INT_STAT_11);
|
|
if (reg < 0)
|
|
return IRQ_NONE;
|
|
|
|
dev_info(mfd->dev, "INT_STAT_11 = 0x%.2X\n", reg);
|
|
|
|
r = bd7181x_reg_write(mfd, BD7181X_REG_INT_STAT_11, reg);
|
|
if (r) {
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
if (reg & INT_STAT_11_VF_DET) {
|
|
dev_info(mfd->dev, "\n~~~ VF Detected ... \n");
|
|
} else if (reg & INT_STAT_11_VF_RES) {
|
|
dev_info(mfd->dev, "\n~~~ VF Resumed ... \n");
|
|
} else if (reg & INT_STAT_11_VF125_DET) {
|
|
dev_info(mfd->dev, "\n~~~ VF125 Detected ... \n");
|
|
} else if (reg & INT_STAT_11_VF125_RES) {
|
|
dev_info(mfd->dev, "\n~~~ VF125 Resumed ... \n");
|
|
} else if (reg & INT_STAT_11_OVTMP_DET) {
|
|
dev_info(mfd->dev, "\n~~~ Overtemp Detected ... \n");
|
|
} else if (reg & INT_STAT_11_OVTMP_RES) {
|
|
dev_info(mfd->dev, "\n~~~ Overtemp Detected ... \n");
|
|
} else if (reg & INT_STAT_11_LOTMP_DET) {
|
|
dev_info(mfd->dev, "\n~~~ Lowtemp Detected ... \n");
|
|
} else if (reg & INT_STAT_11_LOTMP_RES) {
|
|
dev_info(mfd->dev, "\n~~~ Lowtemp Detected ... \n");
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
/** @brief get property of power supply ac
|
|
* @param psy power supply deivce
|
|
* @param psp property to get
|
|
* @param val property value to return
|
|
* @retval 0 success
|
|
* @retval negative fail
|
|
*/
|
|
static int bd7181x_charger_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp, union power_supply_propval *val)
|
|
{
|
|
struct bd7181x_power *pwr = dev_get_drvdata(psy->dev.parent);
|
|
u32 vot;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = pwr->charger_online;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
vot = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_VM_DCIN_U);
|
|
val->intval = 5000 * vot; // 5 milli volt steps
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief get property of power supply bat
|
|
* @param psy power supply deivce
|
|
* @param psp property to get
|
|
* @param val property value to return
|
|
* @retval 0 success
|
|
* @retval negative fail
|
|
*/
|
|
|
|
static int bd7181x_battery_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp, union power_supply_propval *val)
|
|
{
|
|
struct bd7181x_power *pwr = dev_get_drvdata(psy->dev.parent);
|
|
// u32 cap, vot, r;
|
|
// u8 ret;
|
|
|
|
switch (psp) {
|
|
/*
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
r = bd7181x_reg_read(pwr->mfd, BD7181X_REG_CHG_STATE);
|
|
// printk("CHG_STATE = 0x%.2X\n", r);
|
|
switch(r) {
|
|
case CHG_STATE_SUSPEND:
|
|
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
break;
|
|
case CHG_STATE_TRICKLE_CHARGE:
|
|
case CHG_STATE_PRE_CHARGE:
|
|
case CHG_STATE_FAST_CHARGE:
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
break;
|
|
case CHG_STATE_TOP_OFF:
|
|
case CHG_STATE_DONE:
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
ret = bd7181x_reg_read(pwr->mfd, BD7181X_REG_BAT_STAT);
|
|
if (ret & DBAT_DET)
|
|
val->intval = POWER_SUPPLY_HEALTH_DEAD;
|
|
else if (ret & VBAT_OV)
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
else
|
|
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
cap = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_CC_BATCAP_U);
|
|
// printk("CC_BATCAP = 0x%.4X\n", cap);
|
|
val->intval = cap * 100 / 0x1FFF;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
vot = bd7181x_reg_read16(pwr->mfd, BD7181X_REG_VM_VBAT_U) * 1000;
|
|
val->intval = vot;
|
|
break;
|
|
*/
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
val->intval = pwr->rpt_status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = pwr->bat_health;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
if (pwr->rpt_status == POWER_SUPPLY_STATUS_CHARGING)
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
else
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = pwr->bat_online;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
val->intval = pwr->vcell;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
val->intval = pwr->clamp_soc;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_NOW:
|
|
{
|
|
u32 t;
|
|
|
|
t = pwr->coulomb_cnt >> 16;
|
|
t = A10s_mAh(t);
|
|
if (t > A10s_mAh(pwr->designed_cap)) t = A10s_mAh(pwr->designed_cap);
|
|
val->intval = t * 1000; /* uA to report */
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = pwr->bat_online;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
|
|
val->intval = BD7181X_BATTERY_FULL * A10s_mAh(pwr->designed_cap) * 10;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_FULL:
|
|
val->intval = BD7181X_BATTERY_FULL * A10s_mAh(pwr->full_cap) * 10;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
val->intval = pwr->curr_sar;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_AVG:
|
|
val->intval = pwr->curr;
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
val->intval = pwr->temp * 10; /* 0.1 degrees C unit */
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
val->intval = MAX_VOLTAGE;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
|
val->intval = MIN_VOLTAGE;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
val->intval = MAX_CURRENT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** @brief ac properties */
|
|
static enum power_supply_property bd7181x_charger_props[] = {
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
};
|
|
|
|
/** @brief bat properies */
|
|
static enum power_supply_property bd7181x_battery_props[] = {
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_CAPACITY,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
POWER_SUPPLY_PROP_CHARGE_NOW,
|
|
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
|
|
POWER_SUPPLY_PROP_CHARGE_FULL,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_CURRENT_AVG,
|
|
POWER_SUPPLY_PROP_TEMP,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MIN,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
};
|
|
|
|
/** @brief directly set raw value to chip register, format: 'register value' */
|
|
static ssize_t bd7181x_sysfs_set_registers(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct bd7181x_power *pwr = container_of(&psy, struct bd7181x_power, bat);
|
|
ssize_t ret = 0;
|
|
unsigned int reg;
|
|
unsigned int val;
|
|
|
|
ret = sscanf(buf, "%x %x", ®, &val);
|
|
if (ret < 1) {
|
|
pwr->reg_index = -1;
|
|
dev_err(pwr->dev, "registers set: <reg> <value>\n");
|
|
return count;
|
|
}
|
|
|
|
if (ret == 1 && reg <= BD7181X_MAX_REGISTER) {
|
|
pwr->reg_index = reg;
|
|
dev_info(pwr->dev, "registers set: reg=0x%x\n", reg);
|
|
return count;
|
|
}
|
|
|
|
if (reg > BD7181X_MAX_REGISTER || val > 255)
|
|
return -EINVAL;
|
|
|
|
dev_info(pwr->dev, "registers set: reg=0x%x, val=0x%x\n", reg, val);
|
|
ret = bd7181x_reg_write(pwr->mfd, reg, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
return count;
|
|
}
|
|
|
|
/** @brief print value of chip register, format: 'register=value' */
|
|
static ssize_t bd7181x_sysfs_print_reg(struct bd7181x_power *pwr,
|
|
u8 reg,
|
|
char *buf)
|
|
{
|
|
int ret = bd7181x_reg_read(pwr->mfd, reg);
|
|
|
|
if (ret < 0)
|
|
return sprintf(buf, "%#.2x=error %d\n", reg, ret);
|
|
return sprintf(buf, "[0x%.2X] = %.2X\n", reg, ret);
|
|
}
|
|
|
|
/** @brief show all raw values of chip register, format per line: 'register=value' */
|
|
static ssize_t bd7181x_sysfs_show_registers(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct bd7181x_power *pwr = container_of(&psy, struct bd7181x_power, bat);
|
|
ssize_t ret = 0;
|
|
int i;
|
|
|
|
dev_info(pwr->dev, "register: index[%d]\n", pwr->reg_index);
|
|
if (pwr->reg_index >= 0) {
|
|
ret += bd7181x_sysfs_print_reg(pwr, pwr->reg_index, buf + ret);
|
|
} else {
|
|
for (i = 0; i <= BD7181X_MAX_REGISTER; i++) {
|
|
ret += bd7181x_sysfs_print_reg(pwr, i, buf + ret);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO,
|
|
bd7181x_sysfs_show_registers, bd7181x_sysfs_set_registers);
|
|
|
|
static int first_offset(struct bd7181x_power *pwr)
|
|
{
|
|
unsigned char ra2, ra3, ra6, ra7;
|
|
unsigned char ra2_temp;
|
|
struct bd7181x *mfd = pwr->mfd;
|
|
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_01);
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_02);
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_03);
|
|
|
|
|
|
ra2 = bd7181x_reg_read(mfd, 0xA2); // I want to know initial A2 & A3.
|
|
ra3 = bd7181x_reg_read(mfd, 0xA3); // I want to know initial A2 & A3.
|
|
ra6 = bd7181x_reg_read(mfd, 0xA6);
|
|
ra7 = bd7181x_reg_read(mfd, 0xA7);
|
|
|
|
bd7181x_reg_write(mfd, 0xA2, 0x00);
|
|
bd7181x_reg_write(mfd, 0xA3, 0x00);
|
|
|
|
dev_info(pwr->dev, "TEST[A2] = 0x%.2X\n", ra2);
|
|
dev_info(pwr->dev, "TEST[A3] = 0x%.2X\n", ra3);
|
|
dev_info(pwr->dev, "TEST[A6] = 0x%.2X\n", ra6);
|
|
dev_info(pwr->dev, "TEST[A7] = 0x%.2X\n", ra7);
|
|
|
|
//-------------- First Step -------------------
|
|
dev_info(pwr->dev, "Frist Step begginning \n");
|
|
|
|
// delay some time , Make a state of IBAT=0mA
|
|
// mdelay(1000 * 10);
|
|
|
|
ra2_temp = ra2;
|
|
|
|
if (ra7 != 0) {
|
|
//if 0<0xA7<20 decrease the Test register 0xA2[7:3] until 0xA7 becomes 0x00.
|
|
if ((ra7 > 0) && (ra7 < 20)) {
|
|
do {
|
|
ra2 = bd7181x_reg_read(mfd, 0xA2);
|
|
ra2_temp = ra2 >> 3;
|
|
ra2_temp -= 1;
|
|
ra2_temp <<= 3;
|
|
bd7181x_reg_write(mfd, 0xA2, ra2_temp);
|
|
dev_info(pwr->dev, "TEST[A2] = 0x%.2X\n", ra2_temp);
|
|
|
|
ra7 = bd7181x_reg_read(mfd, 0xA7);
|
|
dev_info(pwr->dev, "TEST[A7] = 0x%.2X\n", ra7);
|
|
mdelay(1000); // 1sec?
|
|
} while (ra7);
|
|
|
|
dev_info(pwr->dev, "A7 becomes 0 . \n");
|
|
|
|
} // end if((ra7 > 0)&&(ra7 < 20))
|
|
else if ((ra7 > 0xDF) && (ra7 < 0xFF))
|
|
//if DF<0xA7<FF increase the Test register 0xA2[7:3] until 0xA7 becomes 0x00.
|
|
{
|
|
do {
|
|
ra2 = bd7181x_reg_read(mfd, 0xA2);
|
|
ra2_temp = ra2 >> 3;
|
|
ra2_temp += 1;
|
|
ra2_temp <<= 3;
|
|
|
|
bd7181x_reg_write(mfd, 0xA2, ra2_temp);
|
|
dev_info(pwr->dev, "TEST[A2] = 0x%.2X\n", ra2_temp);
|
|
|
|
ra7 = bd7181x_reg_read(mfd, 0xA7);
|
|
dev_info(pwr->dev, "TEST[A7] = 0x%.2X\n", ra7);
|
|
mdelay(1000); // 1sec?
|
|
} while (ra7);
|
|
|
|
dev_info(pwr->dev, "A7 becomes 0 . \n");
|
|
}
|
|
}
|
|
|
|
// please use "ra2_temp" at step2.
|
|
return ra2_temp;
|
|
}
|
|
|
|
static int second_step(struct bd7181x_power *pwr, u8 ra2_temp)
|
|
{
|
|
u16 ra6, ra7;
|
|
u8 aft_ra2, aft_ra3;
|
|
u8 r79, r7a;
|
|
unsigned int LNRDSA_FUSE;
|
|
long ADC_SIGN;
|
|
long DSADGAIN1_INI;
|
|
struct bd7181x *mfd = pwr->mfd;
|
|
|
|
//-------------- Second Step -------------------
|
|
dev_info(pwr->dev, "Second Step begginning \n");
|
|
|
|
// need to change boad setting ( input 1A tio 10mohm)
|
|
// delay some time , Make a state of IBAT=1000mA
|
|
// mdelay(1000 * 10);
|
|
|
|
// rough adjust
|
|
dev_info(pwr->dev, "ra2_temp = 0x%.2X\n", ra2_temp);
|
|
|
|
ra6 = bd7181x_reg_read(mfd, 0xA6);
|
|
ra7 = bd7181x_reg_read(mfd, 0xA7);
|
|
ra6 <<= 8;
|
|
ra6 |= ra7; // [0xA6 0xA7]
|
|
dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6);
|
|
|
|
bd7181x_reg_write(mfd, 0xA2, ra2_temp); // this value from step1
|
|
bd7181x_reg_write(mfd, 0xA3, 0x00);
|
|
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00);
|
|
|
|
r79 = bd7181x_reg_read(mfd, 0x79);
|
|
r7a = bd7181x_reg_read(mfd, 0x7A);
|
|
|
|
ADC_SIGN = r79 >> 7;
|
|
ADC_SIGN = 1 - (2 * ADC_SIGN);
|
|
DSADGAIN1_INI = r79 << 8;
|
|
DSADGAIN1_INI = DSADGAIN1_INI + r7a;
|
|
DSADGAIN1_INI = DSADGAIN1_INI & 0x7FFF;
|
|
DSADGAIN1_INI = DSADGAIN1_INI * ADC_SIGN; // unit 0.001
|
|
|
|
// unit 0.000001
|
|
DSADGAIN1_INI *= 1000;
|
|
{
|
|
if (DSADGAIN1_INI > 1000001) {
|
|
DSADGAIN1_INI = 2048000000UL - (DSADGAIN1_INI - 1000000) * 8187;
|
|
} else if (DSADGAIN1_INI < 999999) {
|
|
DSADGAIN1_INI = -(DSADGAIN1_INI - 1000000) * 8187;
|
|
} else {
|
|
DSADGAIN1_INI = 0;
|
|
}
|
|
}
|
|
|
|
LNRDSA_FUSE = (int) DSADGAIN1_INI / 1000000;
|
|
|
|
dev_info(pwr->dev, "LNRDSA_FUSE = 0x%.8X\n", LNRDSA_FUSE);
|
|
|
|
aft_ra2 = (LNRDSA_FUSE >> 8) & 255;
|
|
aft_ra3 = (LNRDSA_FUSE) & 255;
|
|
|
|
aft_ra2 = aft_ra2 + ra2_temp;
|
|
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_01);
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_02);
|
|
bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_03);
|
|
|
|
bd7181x_reg_write(mfd, 0xA2, aft_ra2);
|
|
bd7181x_reg_write(mfd, 0xA3, aft_ra3);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int third_step(struct bd7181x_power *pwr, unsigned thr) {
|
|
u16 ra2_a3, ra6, ra7;
|
|
u8 ra2, ra3;
|
|
u8 aft_ra2, aft_ra3;
|
|
struct bd7181x *mfd = pwr->mfd;
|
|
|
|
// fine adjust
|
|
ra2 = bd7181x_reg_read(mfd, 0xA2); //
|
|
ra3 = bd7181x_reg_read(mfd, 0xA3); //
|
|
|
|
ra6 = bd7181x_reg_read(mfd, 0xA6);
|
|
ra7 = bd7181x_reg_read(mfd, 0xA7);
|
|
ra6 <<= 8;
|
|
ra6 |= ra7; // [0xA6 0xA7]
|
|
dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6);
|
|
|
|
|
|
if (ra6 > thr) {
|
|
do {
|
|
ra2_a3 = bd7181x_reg_read(mfd, 0xA2);
|
|
ra2_a3 <<= 8;
|
|
ra3 = bd7181x_reg_read(mfd, 0xA3);
|
|
ra2_a3 |= ra3;
|
|
//ra2_a3 >>= 3; // ? 0xA3[7:3] , or 0xA3[7:0]
|
|
|
|
ra2_a3 -= 1;
|
|
//ra2_a3 <<= 3;
|
|
ra3 = ra2_a3;
|
|
bd7181x_reg_write(mfd, 0xA3, ra3);
|
|
|
|
ra2_a3 >>= 8;
|
|
ra2 = ra2_a3;
|
|
bd7181x_reg_write(mfd, 0xA2, ra2);
|
|
|
|
dev_info(pwr->dev, "TEST[A2] = 0x%.2X , TEST[A3] = 0x%.2X \n", ra2, ra3);
|
|
|
|
mdelay(1000); // 1sec?
|
|
|
|
ra6 = bd7181x_reg_read(mfd, 0xA6);
|
|
ra7 = bd7181x_reg_read(mfd, 0xA7);
|
|
ra6 <<= 8;
|
|
ra6 |= ra7; // [0xA6 0xA7]
|
|
dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6);
|
|
} while (ra6 > thr);
|
|
} else if (ra6 < thr) {
|
|
do {
|
|
ra2_a3 = bd7181x_reg_read(mfd, 0xA2);
|
|
ra2_a3 <<= 8;
|
|
ra3 = bd7181x_reg_read(mfd, 0xA3);
|
|
ra2_a3 |= ra3;
|
|
//ra2_a3 >>= 3; // ? 0xA3[7:3] , or 0xA3[7:0]
|
|
|
|
ra2_a3 += 1;
|
|
//ra2_a3 <<= 3;
|
|
ra3 = ra2_a3;
|
|
bd7181x_reg_write(mfd, 0xA3, ra3);
|
|
|
|
ra2_a3 >>= 8;
|
|
ra2 = ra2_a3;
|
|
bd7181x_reg_write(mfd, 0xA2, ra2);
|
|
|
|
dev_info(pwr->dev, "TEST[A2] = 0x%.2X , TEST[A3] = 0x%.2X \n", ra2, ra3);
|
|
|
|
mdelay(1000); // 1sec?
|
|
|
|
ra6 = bd7181x_reg_read(mfd, 0xA6);
|
|
ra7 = bd7181x_reg_read(mfd, 0xA7);
|
|
ra6 <<= 8;
|
|
ra6 |= ra7; // [0xA6 0xA7]
|
|
dev_info(pwr->dev, "TEST[A6,A7] = 0x%.4X\n", ra6);
|
|
|
|
} while (ra6 < thr);
|
|
}
|
|
|
|
dev_info(pwr->dev, "[0xA6 0xA7] becomes [0x%.4X] . \n", thr);
|
|
dev_info(pwr->dev, " Calibation finished ... \n\n");
|
|
|
|
aft_ra2 = bd7181x_reg_read(mfd, 0xA2); //
|
|
aft_ra3 = bd7181x_reg_read(mfd, 0xA3); // I want to know initial A2 & A3.
|
|
|
|
dev_info(pwr->dev, "TEST[A2,A3] = 0x%.2X%.2X\n", aft_ra2, aft_ra3);
|
|
|
|
// bd7181x_reg_write(mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t bd7181x_sysfs_set_calibrate(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf,
|
|
size_t count)
|
|
{
|
|
struct power_supply *psy = dev_get_drvdata(dev);
|
|
struct bd7181x_power *pwr = container_of(&psy, struct bd7181x_power, bat);
|
|
ssize_t ret = 0;
|
|
unsigned int val, mA;
|
|
static u8 rA2;
|
|
|
|
ret = sscanf(buf, "%d %d", &val, &mA);
|
|
if (ret < 1) {
|
|
dev_err(pwr->dev, "error: write a integer string");
|
|
return count;
|
|
}
|
|
|
|
if (val == 1) {
|
|
pwr->calib_current = CALIB_START;
|
|
while (pwr->calib_current != CALIB_GO) {
|
|
msleep(500);
|
|
}
|
|
rA2 = first_offset(pwr);
|
|
}
|
|
if (val == 2) {
|
|
second_step(pwr, rA2);
|
|
}
|
|
if (val == 3) {
|
|
if (ret <= 1) {
|
|
dev_err(pwr->dev, "error: Fine adjust need a mA argument!");
|
|
} else {
|
|
unsigned int ra6_thr;
|
|
|
|
ra6_thr = mA * 0xFFFF / 20000;
|
|
dev_info(pwr->dev, "Fine adjust at %d mA, ra6 threshold %d(0x%X)\n", mA, ra6_thr, ra6_thr);
|
|
third_step(pwr, ra6_thr);
|
|
}
|
|
}
|
|
if (val == 4) {
|
|
bd7181x_reg_write(pwr->mfd, BD7181X_REG_TEST_MODE, TEST_SEQ_00);
|
|
pwr->calib_current = CALIB_NORM;
|
|
schedule_delayed_work(&pwr->bd_work, msecs_to_jiffies(0));
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t bd7181x_sysfs_show_calibrate(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
// struct power_supply *psy = dev_get_drvdata(dev);
|
|
// struct bd7181x_power *pwr = container_of(psy, struct bd7181x_power, bat);
|
|
ssize_t ret = 0;
|
|
|
|
ret = 0;
|
|
ret += sprintf(buf + ret, "write string value\n"
|
|
"\t1 0 mA for step one\n"
|
|
"\t2 1000 mA for rough adjust\n"
|
|
"\t3 <mA> for fine adjust\n"
|
|
"\t4 exit current calibration\n");
|
|
return ret;
|
|
}
|
|
|
|
static DEVICE_ATTR(calibrate, S_IWUSR | S_IRUGO,
|
|
bd7181x_sysfs_show_calibrate, bd7181x_sysfs_set_calibrate);
|
|
|
|
static struct attribute *bd7181x_sysfs_attributes[] = {
|
|
/*
|
|
* TODO: some (appropriate) of these attrs should be switched to
|
|
* use pwr supply class props.
|
|
*/
|
|
&dev_attr_registers.attr,
|
|
&dev_attr_calibrate.attr,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group bd7181x_sysfs_attr_group = {
|
|
.attrs = bd7181x_sysfs_attributes,
|
|
};
|
|
|
|
/** @brief powers supplied by bd7181x_ac */
|
|
static char *bd7181x_ac_supplied_to[] = {
|
|
BAT_NAME,
|
|
};
|
|
|
|
/** @brief probe pwr device
|
|
* @param pdev platform deivce of bd7181x_power
|
|
* @retval 0 success
|
|
* @retval negative fail
|
|
*/
|
|
static int __init bd7181x_power_probe(struct platform_device *pdev)
|
|
{
|
|
struct bd7181x *bd7181x = dev_get_drvdata(pdev->dev.parent);
|
|
struct bd7181x_power *pwr;
|
|
int irq, ret, reg;
|
|
|
|
pwr = kzalloc(sizeof(*pwr), GFP_KERNEL);
|
|
if (pwr == NULL)
|
|
return -ENOMEM;
|
|
|
|
pwr->dev = &pdev->dev;
|
|
pwr->mfd = bd7181x;
|
|
|
|
platform_set_drvdata(pdev, pwr);
|
|
|
|
if (battery_cycle <= 0) {
|
|
battery_cycle = 0;
|
|
}
|
|
dev_err(pwr->dev, "battery_cycle = %d\n", battery_cycle);
|
|
|
|
/* If the product often power up/down and the power down time is long, the Coulomb Counter may have a drift. */
|
|
/* If so, it may be better accuracy to enable Coulomb Counter using following commented out code */
|
|
/* for counting Coulomb when the product is power up(including sleep). */
|
|
/* The condition */
|
|
/* (1) Product often power up and down, the power down time is long and there is no power consumed in power down time. */
|
|
/* (2) Kernel must call this routin at power up time. */
|
|
/* (3) Kernel must call this routin at charging time. */
|
|
/* (4) Must use this code with "Stop Coulomb Counter" code in bd7181x_power_remove() function */
|
|
/* Start Coulomb Counter */
|
|
/* bd7181x_set_bits(pwr->mfd, BD7181x_REG_CC_CTRL, CCNTENB); */
|
|
|
|
bd7181x_init_hardware(pwr);
|
|
|
|
pwr->bat_desc.name = BAT_NAME;
|
|
pwr->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
pwr->bat_desc.properties = bd7181x_battery_props;
|
|
pwr->bat_desc.num_properties = ARRAY_SIZE(bd7181x_battery_props);
|
|
pwr->bat_desc.get_property = bd7181x_battery_get_property;
|
|
|
|
pwr->bat = power_supply_register(&pdev->dev, &pwr->bat_desc, &pwr->bat_cfg);
|
|
if (IS_ERR(pwr->bat)) {
|
|
dev_err(&pdev->dev, "Failed to register battery, %ld\n", PTR_ERR(pwr->bat));
|
|
ret = PTR_ERR(pwr->bat);
|
|
pwr->bat = NULL;
|
|
goto fail_register_bat;
|
|
}
|
|
|
|
pwr->ac_desc.name = AC_NAME;
|
|
pwr->ac_desc.type = POWER_SUPPLY_TYPE_MAINS;
|
|
pwr->ac_desc.properties = bd7181x_charger_props;
|
|
pwr->ac_cfg.supplied_to = bd7181x_ac_supplied_to;
|
|
pwr->ac_cfg.num_supplicants = ARRAY_SIZE(bd7181x_ac_supplied_to);
|
|
pwr->ac_desc.num_properties = ARRAY_SIZE(bd7181x_charger_props);
|
|
pwr->ac_desc.get_property = bd7181x_charger_get_property;
|
|
|
|
pwr->ac = power_supply_register(&pdev->dev, &pwr->ac_desc, &pwr->ac_cfg);
|
|
if (IS_ERR(pwr->ac)) {
|
|
dev_err(&pdev->dev, "Failed to register ac, %ld\n", PTR_ERR(pwr->ac));
|
|
ret = PTR_ERR(pwr->ac);
|
|
pwr->ac = NULL;
|
|
goto fail_register_ac;
|
|
}
|
|
|
|
/*Add DC_IN Inserted and Remove ISR */
|
|
irq = platform_get_irq(pdev, 0); // get irq number
|
|
#ifdef __BD7181X_REGMAP_H__
|
|
irq += bd7181x->irq_base;
|
|
#endif
|
|
if (irq <= 0) {
|
|
dev_warn(&pdev->dev, "platform irq error # %d\n", irq);
|
|
return -ENXIO;
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
|
bd7181x_power_interrupt, IRQF_TRIGGER_LOW | IRQF_EARLY_RESUME,
|
|
dev_name(&pdev->dev), &pdev->dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "IRQ %d is not free.\n", irq);
|
|
}
|
|
|
|
/*add VBAT Low Voltage detection, John Zhang*/
|
|
irq = platform_get_irq(pdev, 1);
|
|
#ifdef __BD7181X_REGMAP_H__
|
|
irq += bd7181x->irq_base;
|
|
#endif
|
|
if (irq <= 0) {
|
|
dev_warn(&pdev->dev, "platform irq error # %d\n", irq);
|
|
return -ENXIO;
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
|
bd7181x_vbat_interrupt, IRQF_TRIGGER_LOW | IRQF_EARLY_RESUME,
|
|
dev_name(&pdev->dev), &pdev->dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "IRQ %d is not free.\n", irq);
|
|
}
|
|
|
|
/* add INT_STAT_11 */
|
|
irq = platform_get_irq(pdev, 2);
|
|
#ifdef __BD7181X_REGMAP_H__
|
|
irq += bd7181x->irq_base;
|
|
#endif
|
|
if (irq <= 0) {
|
|
dev_warn(&pdev->dev, "platform irq error # %d\n", irq);
|
|
return -ENXIO;
|
|
}
|
|
|
|
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
|
|
bd7181x_int_11_interrupt, IRQF_TRIGGER_LOW | IRQF_EARLY_RESUME,
|
|
dev_name(&pdev->dev), &pdev->dev);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "IRQ %d is not free.\n", irq);
|
|
}
|
|
|
|
/* Enable INT_11 */
|
|
ret = bd7181x_reg_write(bd7181x, BD7181X_REG_INT_EN_11, 0xFF);
|
|
if (ret < 0) {
|
|
dev_warn(&pdev->dev, "Write BD7181X_REG_INT_EN_11 failed\n");
|
|
}
|
|
reg = bd7181x_reg_read(bd7181x, BD7181X_REG_INT_EN_11);
|
|
if (reg < 0) {
|
|
dev_warn(&pdev->dev, "Read BD7181X_REG_INT_EN_11 failed\n");
|
|
}
|
|
dev_info(&pdev->dev, "BD7181X_REG_INT_EN_11=0x%x\n", reg);
|
|
|
|
|
|
ret = sysfs_create_group(&pwr->bat->dev.kobj, &bd7181x_sysfs_attr_group);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "failed to register sysfs interface\n");
|
|
}
|
|
|
|
pwr->reg_index = -1;
|
|
|
|
INIT_DELAYED_WORK(&pwr->bd_work, bd_work_callback);
|
|
|
|
/* Schedule timer to check current status */
|
|
pwr->calib_current = CALIB_NORM;
|
|
schedule_delayed_work(&pwr->bd_work, msecs_to_jiffies(0));
|
|
|
|
return 0;
|
|
|
|
//error_exit:
|
|
power_supply_unregister(pwr->ac);
|
|
fail_register_ac:
|
|
power_supply_unregister(pwr->bat);
|
|
fail_register_bat:
|
|
platform_set_drvdata(pdev, NULL);
|
|
kfree(pwr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/** @brief remove pwr device
|
|
* @param pdev platform deivce of bd7181x_power
|
|
* @return 0
|
|
*/
|
|
|
|
static int __exit bd7181x_power_remove(struct platform_device *pdev)
|
|
{
|
|
struct bd7181x_power *pwr = platform_get_drvdata(pdev);
|
|
|
|
/* If the product often power up/down and the power down time is long, the Coulomb Counter may have a drift. */
|
|
/* If so, it may be better accuracy to disable Coulomb Counter using following commented out code */
|
|
/* for stopping counting Coulomb when the product is power down(without sleep). */
|
|
/* The condition */
|
|
/* (1) Product often power up and down, the power down time is long and there is no power consumed in power down time. */
|
|
/* (2) Kernel must call this routin at power down time. */
|
|
/* (3) Must use this code with "Start Coulomb Counter" code in bd7181x_power_probe() function */
|
|
/* Stop Coulomb Counter */
|
|
/* bd7181x_clear_bits(pwr->mfd, BD7181x_REG_CC_CTRL, CCNTENB); */
|
|
|
|
sysfs_remove_group(&pwr->bat->dev.kobj, &bd7181x_sysfs_attr_group);
|
|
|
|
cancel_delayed_work(&pwr->bd_work);
|
|
|
|
power_supply_unregister(pwr->bat);
|
|
power_supply_unregister(pwr->ac);
|
|
platform_set_drvdata(pdev, NULL);
|
|
kfree(pwr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver bd7181x_power_driver = {
|
|
.driver = {
|
|
.name = "bd7181x-power",
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.remove = __exit_p(bd7181x_power_remove),
|
|
};
|
|
|
|
/** @brief module initialize function */
|
|
static int __init bd7181x_power_init(void)
|
|
{
|
|
return platform_driver_probe(&bd7181x_power_driver, bd7181x_power_probe);
|
|
}
|
|
|
|
module_init(bd7181x_power_init);
|
|
|
|
/** @brief module deinitialize function */
|
|
static void __exit bd7181x_power_exit(void)
|
|
{
|
|
platform_driver_unregister(&bd7181x_power_driver);
|
|
}
|
|
|
|
module_exit(bd7181x_power_exit);
|
|
|
|
module_param(battery_cycle, uint, S_IWUSR | S_IRUGO);
|
|
MODULE_PARM_DESC(battery_parameters, "battery_cycle:battery charge/discharge cycles");
|
|
|
|
MODULE_AUTHOR("Tony Luo <luofc@embest-tech.com>");
|
|
MODULE_AUTHOR("Peter Yang <yanglsh@embest-tech.com>");
|
|
MODULE_DESCRIPTION("BD71815/BD71817 Battery Charger Power driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
/*-------------------------------------------------------*/
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#define PROCFS_NAME "bd7181x_rev"
|
|
#define BD7181X_REV "BD7181x Driver: Rev008\n"
|
|
|
|
#define BD7181X_BUF_SIZE 1024
|
|
static char procfs_buffer[BD7181X_BUF_SIZE];
|
|
/**
|
|
* This function is called then the /proc file is read
|
|
*
|
|
*/
|
|
static int onetime = 0;
|
|
static ssize_t bd7181x_proc_read (struct file *file, char __user *buffer, size_t count, loff_t *data)
|
|
{
|
|
int ret = 0, error = 0;
|
|
if(onetime==0) {
|
|
onetime = 1;
|
|
memset( procfs_buffer, 0, BD7181X_BUF_SIZE);
|
|
sprintf(procfs_buffer, "%s", BD7181X_REV);
|
|
ret = strlen(procfs_buffer);
|
|
error = raw_copy_to_user(buffer, procfs_buffer, strlen(procfs_buffer));
|
|
} else {
|
|
//Clear for next time
|
|
onetime = 0;
|
|
}
|
|
return (error!=0)?0:ret;
|
|
}
|
|
|
|
#if 0
|
|
int bd7181x_debug_mask = 0;
|
|
static ssize_t bd7181x_proc_write (struct file *file, const char __user *buffer, size_t count, loff_t *data)
|
|
{
|
|
sscanf(buffer, "0x%x", &bd7181x_debug_mask);
|
|
printk("BD7181x: bd7181x_debug_mask=0x%08x\n", bd7181x_debug_mask);
|
|
return count;
|
|
}
|
|
#endif
|
|
|
|
static const struct file_operations bd7181x_proc_fops = {
|
|
.owner = THIS_MODULE,
|
|
.read = bd7181x_proc_read,
|
|
//.write = bd7181x_proc_write,
|
|
};
|
|
|
|
/**
|
|
*This function is called when the module is loaded
|
|
*
|
|
*/
|
|
int bd7181x_revision_init(void)
|
|
{
|
|
struct proc_dir_entry *bd7181x_proc_entry;
|
|
|
|
/* create the /proc/bd7181x_rev */
|
|
bd7181x_proc_entry = proc_create(PROCFS_NAME, 0644, NULL, &bd7181x_proc_fops);
|
|
if (bd7181x_proc_entry == NULL) {
|
|
printk("Error: Could not initialize /proc/%s\n", PROCFS_NAME);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
module_init(bd7181x_revision_init);
|
|
/*-------------------------------------------------------*/
|