diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index c54c67e27483..0efa44f50ab3 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -44,6 +44,12 @@ config APM_POWER Say Y here to enable support APM status emulation using battery class devices. +config BD7181X_POWER + tristate "ROHM BD71815/BD71817 Charger for Battery and Adapter Power" + depends on MFD_BD7181X + help + Say Y to enable support for the BD71815/BD71817 charger. + config GENERIC_ADC_BATTERY tristate "Generic battery support using IIO" depends on IIO diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index a90f1ab5e4e8..1bebd05aa90e 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_BD7181X_POWER) += bd7181x-power.o obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o obj-$(CONFIG_MAX8925_POWER) += max8925_power.o obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o diff --git a/drivers/power/supply/bd7181x-power.c b/drivers/power/supply/bd7181x-power.c new file mode 100644 index 000000000000..180a0aae8fa6 --- /dev/null +++ b/drivers/power/supply/bd7181x-power.c @@ -0,0 +1,2251 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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: \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> 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 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 "); +MODULE_AUTHOR("Peter Yang "); +MODULE_DESCRIPTION("BD71815/BD71817 Battery Charger Power driver"); +MODULE_LICENSE("GPL"); + + +/*-------------------------------------------------------*/ +#include +#include +#include +#include + +#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); +/*-------------------------------------------------------*/