| /***************************************************************************** |
| * Copyright 2001 - 2008 Broadcom Corporation. All rights reserved. |
| * |
| * Unless you and Broadcom execute a separate written software license |
| * agreement governing use of this software, this software is licensed to you |
| * under the terms of the GNU General Public License version 2, available at |
| * http://www.gnu.org/licenses/old-license/gpl-2.0.html (the "GPL"). |
| * |
| * Notwithstanding the above, under no circumstances may you combine this |
| * software in any way with any other Broadcom software provided under a |
| * license other than the GPL, without Broadcom's express prior written |
| * consent. |
| * |
| *****************************************************************************/ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/bug.h> |
| #include <linux/device.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/workqueue.h> |
| #include <linux/fs.h> |
| #include <linux/miscdevice.h> |
| #include <linux/uaccess.h> |
| #include <linux/poll.h> |
| #include <linux/power_supply.h> |
| #include <linux/time.h> |
| #include <linux/sort.h> |
| #include <linux/wakelock.h> |
| #include <linux/broadcom/wd-tapper.h> |
| |
| #include <linux/mfd/bcmpmu.h> |
| |
| #define BCMPMU_PRINT_ERROR (1U << 0) |
| #define BCMPMU_PRINT_INIT (1U << 1) |
| #define BCMPMU_PRINT_FLOW (1U << 2) |
| #define BCMPMU_PRINT_DATA (1U << 3) |
| #define BCMPMU_PRINT_REPORT (1U << 4) |
| |
| static int debug_mask = BCMPMU_PRINT_ERROR | |
| BCMPMU_PRINT_INIT; |
| |
| /* static int debug_mask = 0xFF; */ |
| |
| #define POLL_SAMPLES 8 |
| #define CAP_POLL_SAMPLES 4 |
| #define POLLRATE_TRANSITION 5000 |
| #define POLLRATE_CHRG 5000 |
| #define POLLRATE_CHRG_MAINT 60000 |
| #define POLLRATE_LOWBAT 5000 |
| #define POLLRATE_HIGHBAT 60000 |
| #define POLLRATE_POLL 50 |
| #define POLLRATE_POLL_INIT 20 |
| #define POLLRATE_IDLE_INIT 160 |
| #define POLLRATE_IDLE 500 |
| #define POLLRATE_RETRY 500 |
| #define POLLRATE_ADC 20 |
| #define POLLRATE_CUTOFF 2000 |
| |
| #define LOWBAT_LVL 3550 |
| #define FULLBAT_LVL 4150 |
| #define MAX_EOC_CNT 3 |
| #define AVG_LENGTH 3 |
| #define MAX_VFLOAT 4200 |
| #define ADC_RETRY_MAX 10 |
| |
| #define MIN_BATT_VOLT 2300 |
| #define MAX_BATT_OV 100 |
| #define MAX_BATT_CURR 3000 |
| #define MAX_TEMP_DELTA 200 |
| #define HICAL_FACT 50 |
| #define LOCAL_FACT 80 |
| |
| #define FG_CIC_2HZ 0x00 |
| #define FG_CIC_4HZ 0x01 |
| #define FG_CIC_8HZ 0x02 |
| #define FG_CIC_16HZ 0x03 |
| |
| #define pr_em(debug_level, args...) \ |
| do { \ |
| if (debug_mask & BCMPMU_PRINT_##debug_level) { \ |
| pr_info(args); \ |
| } \ |
| } while (0) |
| |
| enum { |
| CHRG_STATE_CHRG, |
| CHRG_STATE_IDLE, |
| CHRG_STATE_MAINT, |
| }; |
| |
| enum { |
| CHRG_ZONE_QC, |
| CHRG_ZONE_LL, |
| CHRG_ZONE_L, |
| CHRG_ZONE_N, |
| CHRG_ZONE_H, |
| CHRG_ZONE_HH, |
| CHRG_ZONE_OUT, |
| }; |
| |
| enum { |
| MODE_CHRG, |
| MODE_HIGHBAT, |
| MODE_LOWBAT, |
| MODE_TRANSITION, |
| MODE_POLL, |
| MODE_IDLE, |
| MODE_CHRG_MAINT, |
| MODE_RETRY, |
| MODE_ADC, |
| MODE_CUTOFF, |
| }; |
| |
| enum { |
| CAL_MODE_NONE, |
| CAL_MODE_LOWBAT, |
| CAL_MODE_HIGHBAT, |
| CAL_MODE_FORCE, |
| CAL_MODE_CUTOFF, |
| CAL_MODE_TEMP, |
| }; |
| |
| static struct bcmpmu_charge_zone chrg_zone[] = { |
| /* This table is default data, the reak data is from board file*/ |
| {.tl = 253, .th = 333, .v = 3000, .fc = 10, .qc = 100},/* Zone QC */ |
| {.tl = 253, .th = 272, .v = 4100, .fc = 50, .qc = 0},/* Zone LL */ |
| {.tl = 273, .th = 282, .v = 4200, .fc = 50, .qc = 0},/* Zone L */ |
| {.tl = 283, .th = 318, .v = 4200, .fc = 100, .qc = 0},/* Zone N */ |
| {.tl = 319, .th = 323, .v = 4200, .fc = 50, .qc = 0},/* Zone H */ |
| {.tl = 324, .th = 333, .v = 4100, .fc = 50, .qc = 0},/* Zone HH */ |
| {.tl = 253, .th = 333, .v = 0, .fc = 0, .qc = 0},/* Zone OUT */ |
| }; |
| |
| static struct bcmpmu_voltcap_map batt_voltcap_map[] = { |
| /* This table is default data, the real data is from board file*/ |
| /* volt capacity*/ |
| {4160, 100}, |
| {4130, 95}, |
| {4085, 90}, |
| {4040, 85}, |
| {3986, 80}, |
| {3948, 75}, |
| {3914, 70}, |
| {3877, 65}, |
| {3842, 60}, |
| {3815, 55}, |
| {3794, 50}, |
| {3776, 45}, |
| {3761, 40}, |
| {3751, 35}, |
| {3742, 30}, |
| {3724, 25}, |
| {3684, 20}, |
| {3659, 15}, |
| {3612, 10}, |
| {3565, 8}, |
| {3507, 6}, |
| {3430, 4}, |
| {3340, 2}, |
| {3236, 0}, |
| }; |
| |
| struct volt_cutoff_lvl { |
| int volt; |
| int cap; |
| int state; |
| }; |
| |
| static struct volt_cutoff_lvl cutoff_cal_map[] = { |
| {3480, 2, 0}, |
| {3440, 1, 0}, |
| {3400, 0, 0}, |
| }; |
| |
| struct eoc_curr_map { |
| int curr; |
| int cap; |
| int state; |
| }; |
| |
| static struct eoc_curr_map eoc_cal_map[] = { |
| {290, 90, 0}, |
| {270, 91, 0}, |
| {250, 92, 0}, |
| {228, 93, 0}, |
| {208, 94, 0}, |
| {185, 95, 0}, |
| {165, 96, 0}, |
| {145, 97, 0}, |
| {125, 98, 0}, |
| {105, 99, 0}, |
| {85, 100, 0}, |
| {0, 100, 0}, |
| }; |
| |
| struct bcmpmu_em { |
| struct bcmpmu *bcmpmu; |
| wait_queue_head_t wait; |
| struct delayed_work work; |
| struct mutex lock; |
| struct notifier_block nb; |
| struct bcmpmu_charge_zone *zone; |
| struct bcmpmu_voltcap_map *bvcap; |
| int bvcap_len; |
| int charge_1c_rate; |
| int eoc; |
| int eoc_state; |
| int support_hw_eoc; |
| int eoc_count; |
| int eoc_cap; |
| int eoc_cal_index; |
| int eoc_factor; |
| int eoc_factor_max; |
| int esr; |
| int cutoff_volt; |
| int cutoff_count; |
| int cutoff_count_max; |
| int cutoff_delta; |
| int cutoff_chk_cnt; |
| int cutoff_cal_index; |
| int cutoff_update_cnt; |
| int capacity_cutoff; |
| s64 fg_capacity_full; |
| int support_fg; |
| int support_chrg_maint; |
| s64 fg_capacity; |
| int fg_cap_cal; |
| int fg_force_cal; |
| int fg_lowbatt_cal; |
| int cal_mode; |
| int last_cal_mode; |
| int cap_delta; |
| int cap_init; |
| int mode; |
| int pollrate; |
| int chrgr_curr; |
| enum bcmpmu_chrgr_type_t chrgr_type; |
| int support_ext_chrgr; |
| int ext_chrgr; |
| int charge_state; |
| int charge_zone; |
| int resume_chrg; |
| int chrg_resume_lvl; |
| int vfloat; |
| int icc_fc; |
| int icc_qc; |
| int force_update; |
| int transition; |
| int batt_volt; |
| int batt_curr; |
| int batt_temp; |
| unsigned char batt_status; |
| unsigned char batt_health; |
| unsigned char batt_capacity_lvl; |
| unsigned char batt_present; |
| unsigned char batt_capacity; |
| unsigned long time; |
| int fg_support_tc; |
| int fg_tc_dn_zone; |
| int fg_tc_up_zone; |
| int fg_zone; |
| int fg_temp_fact; |
| int fg_comp_mode; |
| int fg_guard; |
| int fg_pending_zone; |
| int fg_zone_settle_tm; |
| struct bcmpmu_fg_zone *fg_zone_ptr; |
| int fg_dbg_temp; |
| unsigned long fg_zone_tm; |
| int fg_poll_hbat; |
| int fg_poll_lbat; |
| int fg_lbat_lvl; |
| int fg_fbat_lvl; |
| int fg_low_cal_lvl; |
| int piggyback_chrg; |
| void (*pb_notify) (enum bcmpmu_event_t event, int data); |
| int batt_temp_in_celsius; |
| int retry_cnt; |
| int max_vfloat; |
| int non_pse_charging; |
| int sys_impedence; |
| int avg_oc_volt; |
| int avg_volt; |
| int avg_curr; |
| int avg_temp; |
| int oc_cap; |
| int low_cal_factor; |
| int high_cal_factor; |
| int adc_retry; |
| int init_poll; |
| }; |
| static struct bcmpmu_em *bcmpmu_em; |
| |
| static unsigned int em_poll(struct file *file, poll_table *wait); |
| static int em_open(struct inode *inode, struct file *file); |
| static int em_release(struct inode *inode, struct file *file); |
| |
| static const struct file_operations em_fops = { |
| .owner = THIS_MODULE, |
| .open = em_open, |
| .release = em_release, |
| .poll = em_poll, |
| }; |
| |
| static struct miscdevice bcmpmu_em_device = { |
| MISC_DYNAMIC_MINOR, "bcmpmu_em", &em_fops |
| }; |
| |
| static int em_open(struct inode *inode, struct file *file) |
| { |
| file->private_data = bcmpmu_em; |
| return 0; |
| } |
| |
| static int em_release(struct inode *inode, struct file *file) |
| { |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static unsigned int em_poll(struct file *file, poll_table *wait) |
| { |
| /* To handle inerrupts and other events */ |
| return 0; |
| } |
| |
| static int save_fg_delta(struct bcmpmu *bcmpmu, int data) |
| { |
| int ret = 0; |
| if ((data > 50) || |
| (data < -50)) { |
| pr_em(ERROR, "%s, fg delta abnormal: %d\n", |
| __func__, data); |
| return -EFAULT; |
| } else |
| ret = bcmpmu->write_dev(bcmpmu, |
| PMU_REG_FG_DELTA, |
| data, |
| bcmpmu->regmap[PMU_REG_FG_DELTA].mask); |
| pr_em(FLOW, "%s, fg delta write: ret=%d, data=0x%X\n", |
| __func__, ret, data); |
| return ret; |
| } |
| |
| static int get_fg_delta(struct bcmpmu *bcmpmu, int *delta) |
| { |
| int ret = 0; |
| int data; |
| signed char temp; |
| ret = bcmpmu->read_dev(bcmpmu, |
| PMU_REG_FG_DELTA, |
| &data, |
| bcmpmu->regmap[PMU_REG_FG_DELTA].mask); |
| if (ret != 0) { |
| pr_em(ERROR, "%s failed to read fg delta.\n", __func__); |
| temp = 0; |
| } else { |
| temp = (signed char)data; |
| if ((temp > 50) || |
| (temp < -50)) { |
| pr_em(ERROR, "%s, fg delta abnormal: %d\n", |
| __func__, temp); |
| temp = 0; |
| } |
| } |
| *delta = (int)temp; |
| pr_em(FLOW, "%s, fg delta read: %d, 0x%X\n", __func__, *delta, data); |
| return ret; |
| } |
| |
| static void update_fg_delta(struct bcmpmu_em *pem) |
| { |
| int cap = 100 + pem->cap_delta; |
| |
| pem->fg_capacity_full = pem->fg_capacity_full * cap; |
| pem->fg_capacity_full = div_s64(pem->fg_capacity_full, 100); |
| pr_em(FLOW, "%s, delta=%d, fg_capacity_full = %lld\n", |
| __func__, pem->cap_delta, pem->fg_capacity_full); |
| } |
| |
| static int save_fg_cap(struct bcmpmu *bcmpmu, int data) |
| { |
| int ret = 0; |
| ret = bcmpmu->write_dev(bcmpmu, |
| PMU_REG_FG_CAP, |
| data, |
| bcmpmu->regmap[PMU_REG_FG_CAP].mask); |
| pr_em(FLOW, "%s, fg cap write: ret=%d, data=0x%X\n", |
| __func__, ret, data); |
| return ret; |
| } |
| |
| static int get_fg_cap(struct bcmpmu *bcmpmu, int *cap) |
| { |
| int ret = 0; |
| int data; |
| unsigned char temp; |
| ret = bcmpmu->read_dev(bcmpmu, |
| PMU_REG_FG_CAP, |
| &data, |
| bcmpmu->regmap[PMU_REG_FG_CAP].mask); |
| if (ret != 0) { |
| pr_em(ERROR, "%s failed to read fg cap.\n", __func__); |
| temp = 0; |
| } else { |
| temp = (signed char)data; |
| if (temp > 100) { |
| pr_em(ERROR, "%s, fg cap abnormal: %d\n", |
| __func__, temp); |
| temp = 0; |
| } |
| } |
| *cap = (int)temp; |
| pr_em(FLOW, "%s, fg cap read: %d, 0x%X\n", __func__, *cap, data); |
| return ret; |
| } |
| |
| static int fg_offset_cal(struct bcmpmu *bcmpmu) |
| { |
| int ret; |
| ret = bcmpmu->write_dev(bcmpmu, |
| PMU_REG_FG_CAL, |
| 1 << bcmpmu->regmap[PMU_REG_FG_CAL].shift, |
| bcmpmu->regmap[PMU_REG_FG_CAL].mask); |
| if (ret != 0) |
| pr_em(ERROR, "%s failed to write device.\n", __func__); |
| return ret; |
| } |
| |
| static int fg_set_default_rate(struct bcmpmu *bcmpmu) |
| { |
| int ret = -1; |
| int retry_cnt = 0; |
| while ((ret != 0) && (retry_cnt < 20)) { |
| ret = bcmpmu->write_dev(bcmpmu, |
| PMU_REG_FG_CIC, |
| FG_CIC_2HZ, |
| bcmpmu->regmap[PMU_REG_FG_CIC].mask); |
| if (ret != 0) |
| pr_em(ERROR, "%s, failed set fg clock, retry=%d\n", |
| __func__, retry_cnt); |
| retry_cnt++; |
| } |
| return ret; |
| } |
| |
| static int get_fg_zone(struct bcmpmu_em *pem, int temp) |
| { |
| int zone = FG_TMP_ZONE_MAX; |
| int i; |
| struct bcmpmu_fg_zone *pzone; |
| |
| if (pem->fg_support_tc == 0) |
| return FG_TMP_ZONE_MAX; |
| if (pem->fg_zone_ptr == NULL) { |
| pr_em(FLOW, "%s, FG zone info not defined\n", __func__); |
| return FG_TMP_ZONE_MAX; |
| } |
| for (i = FG_TMP_ZONE_MIN; i <= FG_TMP_ZONE_MAX; i++) { |
| pzone = pem->fg_zone_ptr + i; |
| if (temp <= pzone->temp) { |
| zone = i; |
| break; |
| } |
| } |
| pr_em(FLOW, "%s, fg zone =%d\n", __func__, zone); |
| return zone; |
| } |
| |
| static int update_fg_zone(struct bcmpmu_em *pem, int temp) |
| { |
| int zone = FG_TMP_ZONE_MAX; |
| unsigned long time = get_seconds(); |
| |
| if (pem->fg_support_tc == 0) |
| return FG_TMP_ZONE_MAX; |
| if (pem->fg_zone_ptr == NULL) { |
| pr_em(FLOW, "%s, FG zone info not defined\n", __func__); |
| return FG_TMP_ZONE_MAX; |
| } |
| |
| zone = get_fg_zone(pem, temp); |
| if (zone != pem->fg_zone) { |
| if (zone != pem->fg_pending_zone) { |
| pem->fg_pending_zone = zone; |
| zone = pem->fg_zone; |
| pem->fg_zone_tm = time; |
| } else if ((time - pem->fg_zone_tm) < pem->fg_zone_settle_tm) |
| zone = pem->fg_zone; |
| else |
| zone = pem->fg_pending_zone; |
| } |
| if ((zone < pem->fg_zone) && |
| (zone > pem->fg_tc_dn_zone)) |
| zone = pem->fg_zone; |
| else if ((zone > pem->fg_zone) && |
| (zone >= pem->fg_tc_up_zone)) |
| zone = pem->fg_tc_up_zone; |
| pr_em(FLOW, "%s, zn=%d, pdzn=%d, tmp=%d, ztmp=%d, tm=%ld\n", |
| __func__, zone, pem->fg_pending_zone, temp, |
| pem->fg_zone_ptr[zone].temp, time - pem->fg_zone_tm); |
| return zone; |
| } |
| |
| static struct bcmpmu_voltcap_map *get_fg_vcmap(struct bcmpmu_em *pem, int *len) |
| { |
| struct bcmpmu_voltcap_map *map; |
| if (pem->fg_support_tc == 0) { |
| *len = pem->bvcap_len; |
| map = pem->bvcap; |
| } else if (pem->fg_zone_ptr == NULL) { |
| pr_em(FLOW, "%s, FG zone info not defined\n", __func__); |
| *len = pem->bvcap_len; |
| map = pem->bvcap; |
| } else { |
| if (pem->fg_zone_ptr[pem->fg_zone].reset != 0) { |
| *len = pem->fg_zone_ptr[pem->fg_zone].maplen; |
| map = pem->fg_zone_ptr[pem->fg_zone].vcmap; |
| } else { |
| if ((pem->fg_zone >= FG_TMP_ZONE_MAX) || |
| (pem->fg_zone <= FG_TMP_ZONE_MIN)) { |
| *len = pem->fg_zone_ptr[pem->fg_zone].maplen; |
| map = pem->fg_zone_ptr[pem->fg_zone].vcmap; |
| } else if (pem->batt_temp >= |
| pem->fg_zone_ptr[pem->fg_zone].temp) { |
| *len = pem->fg_zone_ptr |
| [pem->fg_zone + 1].maplen; |
| map = pem->fg_zone_ptr[pem->fg_zone + 1].vcmap; |
| } else { |
| *len = pem->fg_zone_ptr |
| [pem->fg_zone - 1].maplen; |
| map = pem->fg_zone_ptr[pem->fg_zone - 1].vcmap; |
| } |
| } |
| } |
| return map; |
| } |
| |
| static int get_fg_guard(struct bcmpmu_em *pem) |
| { |
| int guard; |
| if (pem->fg_support_tc == 0) |
| guard = 30; |
| else if (pem->fg_zone_ptr == NULL) { |
| pr_em(FLOW, "%s, FG zone info not defined\n", __func__); |
| guard = 30; |
| } else |
| guard = pem->fg_zone_ptr[pem->fg_zone].guardband; |
| pr_em(FLOW, "%s, FG guard band =%d\n", __func__, guard); |
| return guard; |
| } |
| |
| static int get_fg_esr(struct bcmpmu_em *pem) |
| { |
| int esr; |
| int slope = 100; |
| int offset = 0; |
| if (pem->fg_support_tc == 0) |
| esr = pem->esr; |
| else if (pem->fg_zone_ptr == NULL) { |
| pr_em(FLOW, "%s, FG zone info not defined\n", __func__); |
| esr = pem->esr; |
| } else { |
| if (pem->batt_volt < |
| pem->fg_zone_ptr[pem->fg_zone].esr_vl_lvl) { |
| slope = pem->fg_zone_ptr[pem->fg_zone].esr_vl_slope; |
| offset = pem->fg_zone_ptr[pem->fg_zone].esr_vl_offset; |
| esr = (pem->batt_volt * slope)/1000; |
| esr += offset; |
| } else if (pem->batt_volt < |
| pem->fg_zone_ptr[pem->fg_zone].esr_vm_lvl) { |
| slope = pem->fg_zone_ptr[pem->fg_zone].esr_vm_slope; |
| offset = pem->fg_zone_ptr[pem->fg_zone].esr_vm_offset; |
| esr = (pem->batt_volt * slope)/1000; |
| esr += offset; |
| } else if (pem->batt_volt < |
| pem->fg_zone_ptr[pem->fg_zone].esr_vh_lvl) { |
| slope = pem->fg_zone_ptr[pem->fg_zone].esr_vh_slope; |
| offset = pem->fg_zone_ptr[pem->fg_zone].esr_vh_offset; |
| esr = (pem->batt_volt * slope)/1000; |
| esr += offset; |
| } else { |
| slope = pem->fg_zone_ptr[pem->fg_zone].esr_vf_slope; |
| offset = pem->fg_zone_ptr[pem->fg_zone].esr_vf_offset; |
| esr = (pem->batt_volt * slope)/1000; |
| esr += offset; |
| } |
| } |
| pr_em(FLOW, "%s, fg temp esr=%d, slope=%d, offset=%d\n", |
| __func__, esr, slope, offset); |
| esr = esr + pem->sys_impedence; |
| return esr; |
| } |
| |
| static int get_fg_temp_factor(struct bcmpmu_em *pem, int temp) |
| { |
| int factor; |
| int dtemp; |
| int dzone; |
| int dfactor; |
| |
| if (pem->fg_support_tc == 0) |
| factor = 1000; |
| else if (pem->fg_zone_ptr == NULL) { |
| pr_em(FLOW, "%s, FG zone info not defined\n", __func__); |
| factor = 1000; |
| } else { |
| if (pem->fg_zone >= FG_TMP_ZONE_MAX) |
| factor = pem->fg_zone_ptr[FG_TMP_ZONE_MAX].fct; |
| else if (pem->fg_zone <= FG_TMP_ZONE_MIN) { |
| if (temp >= pem->fg_zone_ptr[FG_TMP_ZONE_MIN + 1].temp) |
| factor = pem->fg_zone_ptr |
| [FG_TMP_ZONE_MIN + 1].fct; |
| else if (temp <= pem->fg_zone_ptr[FG_TMP_ZONE_MIN].temp) |
| factor = pem->fg_zone_ptr[FG_TMP_ZONE_MIN].fct; |
| else { |
| dfactor = (pem->fg_zone_ptr |
| [FG_TMP_ZONE_MIN + 1].fct - |
| pem->fg_zone_ptr[FG_TMP_ZONE_MIN].fct); |
| dzone = pem->fg_zone_ptr |
| [FG_TMP_ZONE_MIN + 1].temp - |
| pem->fg_zone_ptr[FG_TMP_ZONE_MIN].temp; |
| dtemp = temp - pem->fg_zone_ptr |
| [FG_TMP_ZONE_MIN].temp; |
| factor = (dtemp * dfactor)/dzone + |
| pem->fg_zone_ptr[FG_TMP_ZONE_MIN].fct; |
| } |
| } else if (temp >= pem->fg_zone_ptr[pem->fg_zone + 1].temp) |
| factor = pem->fg_zone_ptr[pem->fg_zone + 1].fct; |
| else if (temp <= pem->fg_zone_ptr[pem->fg_zone - 1].temp) |
| factor = pem->fg_zone_ptr[pem->fg_zone - 1].fct; |
| else if (temp > pem->fg_zone_ptr[pem->fg_zone].temp) { |
| dfactor = pem->fg_zone_ptr[pem->fg_zone + 1].fct - |
| pem->fg_zone_ptr[pem->fg_zone].fct; |
| dzone = pem->fg_zone_ptr[pem->fg_zone+1].temp - |
| pem->fg_zone_ptr[pem->fg_zone].temp; |
| dtemp = temp - pem->fg_zone_ptr[pem->fg_zone].temp; |
| factor = (dtemp * dfactor)/dzone + |
| pem->fg_zone_ptr[pem->fg_zone].fct; |
| } else if (temp < pem->fg_zone_ptr[pem->fg_zone].temp) { |
| dfactor = pem->fg_zone_ptr[pem->fg_zone].fct - |
| pem->fg_zone_ptr[pem->fg_zone - 1].fct; |
| dzone = pem->fg_zone_ptr[pem->fg_zone].temp - |
| pem->fg_zone_ptr[pem->fg_zone - 1].temp; |
| dtemp = temp - pem->fg_zone_ptr[pem->fg_zone].temp; |
| factor = (dtemp * dfactor)/dzone + |
| pem->fg_zone_ptr[pem->fg_zone].fct; |
| } else |
| factor = pem->fg_zone_ptr[pem->fg_zone].fct; |
| pr_em(FLOW, "%s, fg temp factor=%d\n", __func__, factor); |
| } |
| return factor; |
| } |
| |
| static void update_fg_capacity(struct bcmpmu_em *pem, int capacity) |
| { |
| pem->fg_capacity = pem->fg_capacity_full * capacity; |
| pem->fg_capacity = div_s64(pem->fg_capacity, 100); |
| pr_em(FLOW, "%s, capacity=%d\n", __func__, capacity); |
| } |
| |
| #ifdef CONFIG_MFD_BCMPMU_DBG |
| static ssize_t |
| dbgmsk_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| return sprintf(buf, "debug_mask is %x\n", debug_mask); |
| } |
| |
| static ssize_t |
| dbgmsk_set(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| unsigned long val = simple_strtoul(buf, NULL, 0); |
| if (val > 0xFF || val == 0) |
| return -EINVAL; |
| debug_mask = val; |
| return count; |
| } |
| |
| static ssize_t |
| pollrate_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "poll rate is %x\n", pem->pollrate/1000); |
| } |
| |
| static ssize_t |
| pollrate_set(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| unsigned long val = simple_strtoul(buf, NULL, 0); |
| if (val > 0xFF || val == 0) |
| return -EINVAL; |
| pem->pollrate = val * 1000; |
| cancel_delayed_work_sync(&pem->work); |
| schedule_delayed_work(&pem->work, 0); |
| return count; |
| } |
| |
| static ssize_t |
| fgcal_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "fg_cap_cal is %x\n", pem->fg_cap_cal); |
| } |
| |
| static ssize_t |
| fgcal_set(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| unsigned long val = simple_strtoul(buf, NULL, 0); |
| if (val > 1) |
| return -EINVAL; |
| pem->fg_cap_cal = val; |
| return count; |
| } |
| |
| static ssize_t |
| fgdelta_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| int data; |
| int ret; |
| ret = get_fg_delta(bcmpmu, &data); |
| return sprintf(buf, "fgdelta is 0x%X\n", data); |
| } |
| |
| static ssize_t |
| fgdelta_set(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| unsigned long val = simple_strtoul(buf, NULL, 0); |
| int ret; |
| signed char data; |
| if (val > 255) |
| return -EINVAL; |
| data = (signed char)val; |
| ret = save_fg_delta(bcmpmu, data); |
| return count; |
| } |
| |
| static ssize_t fg_tcstatus_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| ssize_t count; |
| count = sprintf(buf, "Fuel temp compensation status\n"); |
| count += sprintf(buf + count, "fg_support_tc=%d\n", |
| pem->fg_support_tc); |
| count += sprintf(buf + count, "fg_tc_dn_zone=%d\n", |
| pem->fg_tc_dn_zone); |
| count += sprintf(buf + count, "fg_tc_up_zone=%d\n", |
| pem->fg_tc_up_zone); |
| count += sprintf(buf + count, "fg_zone_settle_tm=%d\n", |
| pem->fg_zone_settle_tm); |
| count += sprintf(buf + count, "fg_zone=%d\n", |
| pem->fg_zone); |
| count += sprintf(buf + count, "fg_pending_zone=%d\n", |
| pem->fg_pending_zone); |
| return count; |
| } |
| |
| static ssize_t fg_tczone_info_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| ssize_t count; |
| struct bcmpmu_fg_zone zone; |
| if (pem->fg_zone_ptr == NULL) |
| return sprintf(buf, "fg_zone_ptr not defined\n"); |
| zone = pem->fg_zone_ptr[pem->fg_zone]; |
| |
| count = sprintf(buf, "Fuel temp compensation zone info\n"); |
| count += sprintf(buf + count, "zone=%d\n", pem->fg_zone); |
| |
| count += sprintf(buf + count, "temp=%d\n", zone.temp); |
| count += sprintf(buf + count, "reset=%d\n", zone.reset); |
| count += sprintf(buf + count, "guardband=%d\n", zone.guardband); |
| count += sprintf(buf + count, "factor=%d\n", zone.fct); |
| |
| count += sprintf(buf + count, "esr_vl_lvl=%d\n", zone.esr_vl_lvl); |
| count += sprintf(buf + count, "esr_vm_lvl=%d\n", zone.esr_vm_lvl); |
| count += sprintf(buf + count, "esr_vh_lvl=%d\n", zone.esr_vh_lvl); |
| |
| count += sprintf(buf + count, "esr_vl=%d\n", zone.esr_vl); |
| count += sprintf(buf + count, "esr_vl_slope=%d\n", zone.esr_vl_slope); |
| count += sprintf(buf + count, "esr_vl_offset=%d\n", zone.esr_vl_offset); |
| |
| count += sprintf(buf + count, "esr_vm=%d\n", zone.esr_vl); |
| count += sprintf(buf + count, "esr_vm_slope=%d\n", zone.esr_vl_slope); |
| count += sprintf(buf + count, "esr_vm_offset=%d\n", zone.esr_vl_offset); |
| |
| count += sprintf(buf + count, "esr_vh=%d\n", zone.esr_vl); |
| count += sprintf(buf + count, "esr_vh_slope=%d\n", zone.esr_vl_slope); |
| count += sprintf(buf + count, "esr_vh_offset=%d\n", zone.esr_vl_offset); |
| |
| count += sprintf(buf + count, "esr_vf=%d\n", zone.esr_vf); |
| count += sprintf(buf + count, "esr_vf_slope=%d\n", zone.esr_vf_slope); |
| count += sprintf(buf + count, "esr_vf_offset=%d\n", zone.esr_vf_offset); |
| |
| return count; |
| } |
| |
| static ssize_t fg_tczone_map_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| int i; |
| ssize_t count; |
| struct bcmpmu_fg_zone *zone; |
| |
| if (pem->fg_zone_ptr == NULL) |
| return sprintf(buf, "fg_zone_ptr not defined\n"); |
| zone = pem->fg_zone_ptr + pem->fg_zone; |
| |
| count = sprintf(buf, "Fuel temp compensation volt factor table\n"); |
| count += sprintf(buf+count, "map addr=%p, len=%d\n", |
| zone->vcmap, |
| zone->maplen); |
| for (i = 0; i < zone->maplen; i++) |
| count += sprintf(buf+count, "volt=%d, percent=%d\n", |
| zone->vcmap[i].volt, |
| zone->vcmap[i].cap); |
| return count; |
| } |
| |
| static ssize_t fg_room_map_show(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| int i; |
| ssize_t count; |
| count = sprintf(buf, "FG room temp volt map table\n"); |
| count += sprintf(buf+count, "map addr=%p, len=%d\n", |
| pem->bvcap, |
| pem->bvcap_len); |
| for (i = 0; i < pem->bvcap_len; i++) |
| count += sprintf(buf+count, "volt=%d, capacity=%d\n", |
| pem->bvcap[i].volt, |
| pem->bvcap[i].cap); |
| return count; |
| } |
| |
| static ssize_t |
| fg_temp_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "fg_dbg_temp=%d\n", pem->fg_dbg_temp); |
| } |
| |
| static ssize_t |
| fg_temp_set(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| int val, code; |
| sscanf(buf, "%x %d", &code, &val); |
| if (code != 0xFEED) |
| return -EINVAL; |
| if ((val > 100) || (val < -40)) |
| return -EINVAL; |
| pem->fg_dbg_temp = val; |
| return count; |
| } |
| |
| static ssize_t |
| fg_bat_cap_set(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| int val, code; |
| sscanf(buf, "%x %d", &code, &val); |
| if (code != 0xFEED) |
| return -EINVAL; |
| if ((val > 100) || (val < 0)) |
| return -EINVAL; |
| pem->batt_capacity = val; |
| update_fg_capacity(pem, val); |
| return count; |
| } |
| |
| static ssize_t |
| avg_curr_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "%d\n", pem->avg_curr); |
| } |
| |
| static ssize_t |
| avg_volt_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "%d\n", pem->avg_volt); |
| } |
| |
| static ssize_t |
| avg_oc_volt_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "%d\n", pem->avg_oc_volt); |
| } |
| |
| static ssize_t |
| esr_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "%d\n", pem->esr); |
| } |
| |
| static ssize_t |
| oc_cap_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "%d\n", pem->oc_cap); |
| } |
| |
| static ssize_t |
| low_cal_factor_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "%d\n", pem->low_cal_factor); |
| } |
| |
| static ssize_t |
| fg_cap_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct bcmpmu *bcmpmu = dev->platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return sprintf(buf, "%lld\n", pem->fg_capacity); |
| } |
| |
| static DEVICE_ATTR(dbgmsk, 0644, dbgmsk_show, dbgmsk_set); |
| static DEVICE_ATTR(pollrate, 0644, pollrate_show, pollrate_set); |
| static DEVICE_ATTR(fgcal, 0644, fgcal_show, fgcal_set); |
| static DEVICE_ATTR(fgdelta, 0644, fgdelta_show, fgdelta_set); |
| static DEVICE_ATTR(fg_dbg_temp, 0644, fg_temp_show, fg_temp_set); |
| static DEVICE_ATTR(fg_tcstatus, 0644, fg_tcstatus_show, NULL); |
| static DEVICE_ATTR(fg_tczone_info, 0644, fg_tczone_info_show, NULL); |
| static DEVICE_ATTR(fg_tczone_map, 0644, fg_tczone_map_show, NULL); |
| static DEVICE_ATTR(fg_room_map, 0644, fg_room_map_show, NULL); |
| static DEVICE_ATTR(fg_bat_cap, 0644, NULL, fg_bat_cap_set); |
| static DEVICE_ATTR(avg_curr, 0644, avg_curr_show, NULL); |
| static DEVICE_ATTR(avg_volt, 0644, avg_volt_show, NULL); |
| static DEVICE_ATTR(avg_oc_volt, 0644, avg_oc_volt_show, NULL); |
| static DEVICE_ATTR(esr, 0644, esr_show, NULL); |
| static DEVICE_ATTR(oc_cap, 0644, oc_cap_show, NULL); |
| static DEVICE_ATTR(low_cal_factor, 0644, low_cal_factor_show, NULL); |
| static DEVICE_ATTR(fg_cap, 0644, fg_cap_show, NULL); |
| static struct attribute *bcmpmu_em_attrs[] = { |
| &dev_attr_dbgmsk.attr, |
| &dev_attr_pollrate.attr, |
| &dev_attr_fgcal.attr, |
| &dev_attr_fgdelta.attr, |
| &dev_attr_fg_dbg_temp.attr, |
| &dev_attr_fg_tcstatus.attr, |
| &dev_attr_fg_tczone_info.attr, |
| &dev_attr_fg_tczone_map.attr, |
| &dev_attr_fg_room_map.attr, |
| &dev_attr_fg_bat_cap.attr, |
| &dev_attr_avg_curr.attr, |
| &dev_attr_avg_volt.attr, |
| &dev_attr_avg_oc_volt.attr, |
| &dev_attr_esr.attr, |
| &dev_attr_oc_cap.attr, |
| &dev_attr_low_cal_factor.attr, |
| &dev_attr_fg_cap.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group bcmpmu_em_attr_group = { |
| .attrs = bcmpmu_em_attrs, |
| }; |
| #endif |
| |
| static int cmp(const void *a, const void *b) |
| { |
| const int *ia = a; |
| const int *ib = b; |
| if (*ia < *ib) |
| return -1; |
| if (*ia > *ib) |
| return 1; |
| return 0; |
| } |
| |
| static int average(int *val, int n, int m) |
| { |
| int i; |
| int acc = 0; |
| for (i = m; (i < n - m); i++) |
| acc += val[i]; |
| return acc / (n - (m * 2)); |
| } |
| |
| static int is_charger_present(struct bcmpmu_em *pem) |
| { |
| if ((pem->chrgr_type == PMU_CHRGR_TYPE_NONE) && |
| (pem->ext_chrgr == 0)) |
| return 0; |
| else |
| return 1; |
| } |
| |
| static int em_batt_get_capacity(struct bcmpmu_em *pem, int pvolt, int curr) |
| { |
| int i = 0; |
| int cap = pem->bvcap[i].cap; |
| int index; |
| int volt = pvolt; |
| int len; |
| struct bcmpmu_voltcap_map *vcmap = get_fg_vcmap(pem, &len); |
| |
| volt = pvolt - (pem->esr * curr)/1000; |
| |
| if (volt >= vcmap[0].volt) |
| cap = vcmap[0].cap; |
| else if (volt <= vcmap[len - 1].volt) |
| cap = vcmap[len - 1].cap; |
| else { |
| for (i = 0; i < len - 1; i++) { |
| if ((volt <= vcmap[i].volt) && |
| (volt > vcmap[i+1].volt)) { |
| index = ((vcmap[i].volt - volt) * 1000)/ |
| (vcmap[i].volt - vcmap[i+1].volt); |
| cap = vcmap[i].cap + |
| ((vcmap[i+1].cap - vcmap[i].cap) |
| * index)/1000; |
| break; |
| } |
| } |
| } |
| pr_em(FLOW, "%s, cpcty=%d, pvlt=%d, crr=%d, vlt=%d, imp=%d\n", |
| __func__, cap, pvolt, curr, volt, pem->esr); |
| return cap; |
| } |
| |
| static int update_adc_readings(struct bcmpmu_em *pem) |
| { |
| struct bcmpmu_adc_req req; |
| int volt = pem->batt_volt; |
| int temp = pem->batt_temp; |
| int ret; |
| static int first_time; |
| |
| req.sig = PMU_ADC_NTC; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| ret = pem->bcmpmu->adc_req(pem->bcmpmu, &req); |
| if ((ret == 0) && (req.raw != 0)) |
| temp = req.cnv; |
| |
| req.sig = PMU_ADC_VMBATT; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| ret |= pem->bcmpmu->adc_req(pem->bcmpmu, &req); |
| if (ret == 0) |
| volt = req.cnv; |
| |
| if ((ret == 0) && |
| (first_time == 0)) { |
| pem->batt_volt = volt; |
| pem->batt_temp = temp; |
| pem->avg_volt = pem->batt_volt; |
| pem->avg_temp = pem->batt_temp; |
| first_time = 1; |
| } else { |
| if ((volt > MIN_BATT_VOLT) && |
| (volt < (pem->max_vfloat + MAX_BATT_OV))) |
| pem->batt_volt = volt; |
| if (abs(temp - pem->avg_temp) < MAX_TEMP_DELTA) |
| pem->batt_temp = temp; |
| pem->avg_volt = (pem->avg_volt * (AVG_LENGTH - 1))/AVG_LENGTH + |
| pem->batt_volt / AVG_LENGTH; |
| pem->avg_temp = (pem->avg_temp * (AVG_LENGTH - 1))/AVG_LENGTH + |
| pem->batt_temp / AVG_LENGTH; |
| } |
| |
| return ret; |
| } |
| |
| static void pre_eoc_check(struct bcmpmu_em *pem) |
| { |
| int index = -1; |
| int i; |
| int capacity = pem->batt_capacity; |
| |
| pr_em(FLOW, "%s, capacity=%d, curr=%d\n", |
| __func__, pem->batt_capacity, pem->batt_curr); |
| |
| if ((pem->batt_capacity < 90) || |
| (pem->batt_volt < pem->fg_fbat_lvl) || |
| (pem->batt_curr > 300) || |
| (pem->batt_curr < 0)) { |
| pem->eoc_factor = 0; |
| return; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(eoc_cal_map) - 1; i++) { |
| if ((pem->batt_curr <= eoc_cal_map[i].curr) && |
| (pem->batt_curr > eoc_cal_map[i+1].curr)) { |
| index = i; |
| break; |
| } |
| } |
| if (index >= 0) |
| capacity = eoc_cal_map[index].cap; |
| else |
| capacity = pem->batt_capacity; |
| |
| if (capacity == pem->batt_capacity) |
| pem->eoc_factor = 0; |
| else if (pem->batt_capacity != 100) |
| pem->eoc_factor = |
| ((capacity - pem->batt_capacity) * 100) / |
| (pem->batt_capacity - 100); |
| if (pem->eoc_factor > pem->eoc_factor_max) |
| pem->eoc_factor = pem->eoc_factor_max; |
| else if (pem->eoc_factor < -pem->eoc_factor_max) |
| pem->eoc_factor = -pem->eoc_factor_max; |
| |
| pr_em(FLOW, "%s, capacity=%d, index=%d, factor=%d\n", |
| __func__, capacity, index, pem->eoc_factor); |
| return; |
| } |
| |
| static int update_eoc(struct bcmpmu_em *pem) |
| { |
| int capacity = 0; |
| |
| if (!is_charger_present(pem)) { |
| pem->eoc_count = 0; |
| return capacity; |
| } |
| |
| pre_eoc_check(pem); |
| |
| if (pem->piggyback_chrg) { |
| if (pem->support_hw_eoc) { |
| if ((pem->eoc_state == 1) && |
| (pem->charge_state != |
| CHRG_STATE_MAINT)) |
| capacity = 100; |
| pr_em(FLOW, "%s, pb-hw, eoc_st=%d\n", |
| __func__, pem->eoc_state); |
| } else if ((pem->batt_volt > pem->fg_fbat_lvl) && |
| (pem->batt_curr < pem->eoc) && |
| (pem->charge_state != CHRG_STATE_MAINT)) { |
| pem->eoc_count++; |
| if (pem->eoc_count > MAX_EOC_CNT) { |
| capacity = 100; |
| pem->eoc_count = 0; |
| } |
| pr_em(FLOW, "%s, pb-sw, eoc_cnt=%d\n", |
| __func__, pem->eoc_count); |
| } else { |
| pem->eoc_count = 0; |
| } |
| |
| pr_info("%s, eoc_count=%d batt_volt=%d batt_curr=%d\n", |
| __func__, |
| pem->eoc_count, pem->batt_volt, pem->batt_curr); |
| |
| |
| } else if (pem->support_hw_eoc) { |
| if ((pem->eoc_state == 1) && |
| (pem->charge_state != CHRG_STATE_MAINT)) { |
| capacity = 100; |
| } |
| pr_em(FLOW, "%s, brcm-hw, eoc_st=%d\n", |
| __func__, pem->eoc_state); |
| } else if ((pem->batt_volt > pem->fg_fbat_lvl) && |
| (pem->batt_curr < pem->eoc) && |
| (pem->charge_state != CHRG_STATE_MAINT)) { |
| pem->eoc_count++; |
| if (pem->eoc_count > MAX_EOC_CNT) { |
| capacity = 100; |
| pem->eoc_count = 0; |
| } |
| pr_em(FLOW, "%s, brcm-sw, eoc_cnt=%d\n", |
| __func__, pem->eoc_count); |
| } else |
| pem->eoc_count = 0; |
| |
| if ((capacity == 100) && |
| (pem->batt_capacity < 100)) { |
| capacity = pem->batt_capacity + 1; |
| update_fg_capacity(pem, capacity); |
| pr_em(FLOW, "%s, n_cap=%d, b_cap=%d\n", |
| __func__, capacity, pem->batt_capacity); |
| } |
| if (capacity == 100) { |
| if (pem->fg_comp_mode == 0) |
| pem->fg_cap_cal = 1; |
| pem->fg_capacity = pem->fg_capacity_full; |
| pem->charge_state = CHRG_STATE_MAINT; |
| if (pem->piggyback_chrg == 0) |
| pem->bcmpmu->chrgr_usb_en(pem->bcmpmu, 0); |
| } |
| |
| pr_em(FLOW, "%s, eoc_st=%d, eoc_cnt=%d\n", |
| __func__, pem->eoc_state, pem->eoc_count); |
| pem->eoc_cap = capacity; |
| return capacity; |
| } |
| |
| |
| |
| static void clear_cal_state(struct bcmpmu_em *pem) |
| { |
| int i; |
| for (i = 0; i < ARRAY_SIZE(cutoff_cal_map); i++) |
| cutoff_cal_map[i].state = 0; |
| pem->cutoff_delta = 0; |
| pem->capacity_cutoff = -1; |
| |
| for (i = 0; i < ARRAY_SIZE(eoc_cal_map); i++) |
| eoc_cal_map[i].state = 0; |
| pem->eoc_count = 0; |
| pem->high_cal_factor = 0; |
| pem->low_cal_factor = 0; |
| pem->eoc_factor = 0; |
| } |
| |
| static int pre_cutoff_check(struct bcmpmu_em *pem) |
| { |
| int index = -1; |
| int i; |
| int capacity = -1; |
| |
| if (pem->batt_volt > pem->fg_fbat_lvl) { |
| pem->cutoff_chk_cnt = 0; |
| return capacity; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(cutoff_cal_map); i++) { |
| if ((cutoff_cal_map[i].state == 0) && |
| (pem->batt_volt <= cutoff_cal_map[i].volt)) { |
| index = i; |
| break; |
| } |
| } |
| if ((index >= 0) && |
| (pem->cutoff_cal_index == index)) |
| pem->cutoff_chk_cnt++; |
| else |
| pem->cutoff_chk_cnt = 0; |
| if (pem->cutoff_chk_cnt > pem->cutoff_count_max) { |
| pem->cutoff_chk_cnt = 0; |
| capacity = cutoff_cal_map[i].cap; |
| cutoff_cal_map[i].state = 1; |
| } |
| pem->cutoff_cal_index = index; |
| |
| pr_em(FLOW, "%s, capacity=%d, cnt=%d, index=%d\n", __func__, |
| capacity, pem->cutoff_chk_cnt, index); |
| |
| return capacity; |
| } |
| |
| |
| static int update_batt_capacity(struct bcmpmu_em *pem, int *cap) |
| { |
| struct bcmpmu_adc_req req; |
| int capacity = 0; |
| int capacity_v = 0; |
| int fg_result = 0; |
| int ret; |
| int calibration = 0; |
| int volt = pem->batt_volt; |
| int zone = pem->fg_zone; |
| s64 capacity64; |
| int factor; |
| int fg_result_adjusted; |
| static int first_time; |
| int cutoff_cap; |
| |
| if (pem->fg_dbg_temp != 0) |
| pem->batt_temp = pem->fg_dbg_temp; |
| pem->fg_zone = update_fg_zone(pem, pem->batt_temp); |
| if (pem->fg_temp_fact >= 1000) |
| pem->fg_comp_mode = 0; |
| pem->fg_temp_fact = |
| get_fg_temp_factor(pem, pem->batt_temp); |
| if (pem->fg_temp_fact < 1000) |
| pem->fg_comp_mode = 1; |
| pem->fg_guard = get_fg_guard(pem); |
| pem->esr = get_fg_esr(pem); |
| |
| if (pem->fg_zone_ptr) { |
| if ((pem->fg_zone != zone) && |
| (pem->fg_zone_ptr[pem->fg_zone].reset != 0)) { |
| pem->fg_cap_cal = 0; |
| pem->cal_mode = CAL_MODE_TEMP, |
| calibration = 1; |
| pr_em(FLOW, "%s, reset by fg zone\n", __func__); |
| goto err; |
| }; |
| }; |
| |
| if (pem->support_fg == 0) { |
| capacity = em_batt_get_capacity(pem, |
| pem->batt_volt, pem->batt_curr); |
| } else { |
| req.sig = PMU_ADC_FG_CURRSMPL; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| ret = pem->bcmpmu->adc_req(pem->bcmpmu, &req); |
| if (ret == 0) { |
| if (req.cnv > MAX_BATT_CURR) { |
| pr_em(ERROR, "%s, extrem batt curr %d\n", |
| __func__, req.cnv); |
| capacity = pem->batt_capacity; |
| calibration = 0; |
| goto err; |
| } |
| pem->batt_curr = req.cnv; |
| } |
| ret = pem->bcmpmu->fg_acc_mas(pem->bcmpmu, &fg_result); |
| if (ret != 0) { |
| pr_em(ERROR, "%s, fg data invalid\n", __func__); |
| fg_result = 0; |
| capacity = pem->batt_capacity; |
| calibration = 0; |
| goto err; |
| } |
| |
| volt = pem->batt_volt - (pem->esr * pem->batt_curr)/1000; |
| if (first_time == 0) { |
| pem->avg_oc_volt = volt; |
| pem->avg_curr = pem->batt_curr; |
| first_time = 1; |
| } else { |
| pem->avg_curr = (pem->avg_curr * |
| (AVG_LENGTH - 1))/AVG_LENGTH + |
| pem->batt_curr / AVG_LENGTH; |
| pem->avg_oc_volt = (pem->avg_oc_volt * |
| (AVG_LENGTH - 1))/AVG_LENGTH + |
| volt / AVG_LENGTH; |
| } |
| |
| capacity_v = em_batt_get_capacity(pem, |
| pem->batt_volt, pem->batt_curr); |
| pem->oc_cap = capacity_v; |
| |
| if (pem->high_cal_factor != 0) |
| factor = pem->high_cal_factor; |
| else |
| factor = pem->low_cal_factor; |
| |
| if (pem->charge_state == CHRG_STATE_CHRG) |
| factor = pem->eoc_factor; |
| pr_em(FLOW, "%s, eoc_factor=%d\n", |
| __func__, pem->eoc_factor); |
| |
| fg_result_adjusted = fg_result - (fg_result * factor / 100); |
| pem->fg_capacity += fg_result_adjusted; |
| pr_em(FLOW, "%s, fg=%d, fg_adj=%d, fact=%d, hi=%d, lo=%d\n", |
| __func__, fg_result, fg_result_adjusted, factor, |
| pem->high_cal_factor, pem->low_cal_factor); |
| |
| if (pem->fg_capacity >= pem->fg_capacity_full) |
| pem->fg_capacity = pem->fg_capacity_full; |
| if (pem->fg_capacity <= 0) |
| pem->fg_capacity = 0; |
| |
| capacity64 = pem->fg_capacity * 100 + pem->fg_capacity_full / 2; |
| capacity = div64_s64(capacity64, pem->fg_capacity_full); |
| |
| if ((is_charger_present(pem)) && |
| (pem->batt_capacity != 100) && |
| (capacity >= 100)) |
| capacity--; |
| |
| if ((!is_charger_present(pem)) && (capacity <= 1)) |
| capacity = 1; |
| |
| if (pem->fg_force_cal != 0) { |
| pem->fg_cap_cal = 0; |
| pem->cal_mode = CAL_MODE_FORCE, |
| calibration = 1; |
| } else if ((!is_charger_present(pem)) && |
| (pem->fg_lowbatt_cal != 0) && |
| (pem->fg_comp_mode == 0) && |
| (volt < pem->fg_low_cal_lvl)) { |
| pem->cal_mode = CAL_MODE_LOWBAT, |
| calibration = 1; |
| } else if ((pem->mode != MODE_TRANSITION) && |
| (pem->mode != MODE_CHRG) && |
| (pem->high_cal_factor == 0) && |
| (abs(capacity - capacity_v) > pem->fg_guard)) { |
| pem->fg_cap_cal = 0; |
| pem->cal_mode = CAL_MODE_HIGHBAT, |
| calibration = 1; |
| } else if ((pem->high_cal_factor != 0) && |
| (abs(capacity - capacity_v) < 10)) |
| pem->high_cal_factor = 0; |
| else if ((pem->low_cal_factor != 0) && |
| (abs(capacity - capacity_v) <= 1)) |
| pem->low_cal_factor = 0; |
| |
| if (calibration == 0) { |
| pem->retry_cnt = 0; |
| cutoff_cap = pre_cutoff_check(pem); |
| if (cutoff_cap >= 0) |
| pem->capacity_cutoff = cutoff_cap; |
| if (pem->capacity_cutoff >= 0) |
| pem->cutoff_delta = |
| capacity - pem->capacity_cutoff; |
| if ((pem->capacity_cutoff >= 0) && |
| (capacity > pem->capacity_cutoff) && |
| (pem->cutoff_delta > 0)) { |
| pem->cutoff_update_cnt++; |
| if (pem->cutoff_update_cnt > 3) { |
| capacity--; |
| pem->cutoff_delta--; |
| update_fg_capacity(pem, capacity); |
| pem->cutoff_update_cnt = 0; |
| } |
| } else { |
| pem->cutoff_delta = 0; |
| pem->capacity_cutoff = -1; |
| } |
| pr_em(FLOW, "%s, cutoff check, delta=%d, cnt=%d\n", |
| __func__, pem->cutoff_delta, |
| pem->cutoff_update_cnt); |
| } |
| } |
| |
| if (update_eoc(pem) > 0) |
| capacity = pem->eoc_cap; |
| |
| if ((is_charger_present(pem)) && |
| (pem->batt_curr >= 0) && |
| (capacity < pem->batt_capacity)) |
| capacity = pem->batt_capacity; |
| |
| if (calibration == 0) |
| *cap = (capacity * pem->fg_temp_fact)/1000; |
| else |
| *cap = capacity; |
| err: |
| pr_em(FLOW, "%s, facc=%d, fcp=%lld, cp=%d, vcp=%d, cl=%d, clm=%d\n", |
| __func__, fg_result, pem->fg_capacity, capacity, |
| capacity_v, calibration, pem->cal_mode); |
| |
| if (!is_charger_present(pem)) { |
| if (pem->batt_volt > pem->fg_lbat_lvl) |
| pem->mode = MODE_HIGHBAT; |
| else if (pem->capacity_cutoff == 0) |
| pem->mode = MODE_CUTOFF; |
| else |
| pem->mode = MODE_LOWBAT; |
| } else { |
| if (pem->charge_state == CHRG_STATE_MAINT) |
| pem->mode = MODE_CHRG_MAINT; |
| else |
| pem->mode = MODE_CHRG; |
| } |
| |
| return calibration; |
| } |
| |
| static unsigned char em_batt_get_capacity_lvl(struct bcmpmu_em *pem, |
| int capacity) |
| { int capacity_lvl; |
| if (capacity < 5) |
| capacity_lvl = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; |
| else if (capacity < 15) |
| capacity_lvl = POWER_SUPPLY_CAPACITY_LEVEL_LOW; |
| else if (capacity < 75) |
| capacity_lvl = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; |
| else if (capacity < 95) |
| capacity_lvl = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; |
| else |
| capacity_lvl = POWER_SUPPLY_CAPACITY_LEVEL_FULL; |
| return capacity_lvl; |
| } |
| |
| static void update_charge_zone(struct bcmpmu_em *pem) |
| { |
| unsigned char zone = pem->charge_zone; |
| |
| if (pem->piggyback_chrg) |
| return; |
| if (pem->non_pse_charging) |
| return; |
| |
| switch (zone) { |
| case CHRG_ZONE_QC: |
| if (pem->batt_volt > pem->zone[pem->charge_zone].v) |
| pem->charge_zone = CHRG_ZONE_N; |
| break; |
| case CHRG_ZONE_OUT: |
| if (pem->batt_volt < pem->zone[CHRG_ZONE_QC].v) |
| pem->charge_zone = CHRG_ZONE_QC; |
| else if ((pem->batt_temp < pem->zone[CHRG_ZONE_N].th) && |
| (pem->batt_temp > pem->zone[CHRG_ZONE_N].tl)) |
| pem->charge_zone = CHRG_ZONE_N; |
| break; |
| default: |
| if ((pem->batt_temp > pem->zone[pem->charge_zone].th) && |
| (pem->charge_zone < CHRG_ZONE_HH)) |
| pem->charge_zone++; |
| else if ((pem->batt_temp < pem->zone[pem->charge_zone].tl) && |
| (pem->charge_zone > CHRG_ZONE_LL)) |
| pem->charge_zone--; |
| break; |
| } |
| |
| if ((zone != pem->charge_zone) || |
| (pem->force_update != 0)) { |
| pem->icc_qc = min(pem->zone[pem->charge_zone].qc, |
| pem->chrgr_curr); |
| pem->bcmpmu->set_icc_qc(pem->bcmpmu, pem->icc_qc); |
| pem->vfloat = min(pem->zone[pem->charge_zone].v, 4200); |
| pem->bcmpmu->set_vfloat(pem->bcmpmu, pem->vfloat); |
| pem->icc_fc = (pem->zone[pem->charge_zone].fc * |
| pem->charge_1c_rate)/100; |
| pem->icc_fc = min(pem->icc_fc, pem->chrgr_curr); |
| pem->bcmpmu->set_icc_fc(pem->bcmpmu, pem->icc_fc); |
| pem->force_update = 0; |
| } |
| }; |
| |
| static int get_update_rate(struct bcmpmu_em *pem) |
| { |
| int rate = POLLRATE_HIGHBAT; |
| |
| switch (pem->mode) { |
| case MODE_TRANSITION: |
| rate = POLLRATE_TRANSITION; |
| break; |
| case MODE_CHRG: |
| rate = POLLRATE_CHRG; |
| break; |
| case MODE_CHRG_MAINT: |
| rate = POLLRATE_CHRG_MAINT; |
| break; |
| case MODE_CUTOFF: |
| rate = POLLRATE_CUTOFF; |
| break; |
| case MODE_LOWBAT: |
| rate = pem->fg_poll_lbat; |
| break; |
| case MODE_HIGHBAT: |
| if (pem->pollrate < POLLRATE_HIGHBAT) |
| rate = pem->pollrate; |
| else |
| rate = pem->fg_poll_hbat; |
| break; |
| case MODE_POLL: |
| if (pem->init_poll == 1) |
| rate = POLLRATE_POLL_INIT; |
| else |
| rate = POLLRATE_POLL; |
| break; |
| case MODE_IDLE: |
| if (pem->init_poll == 1) |
| rate = POLLRATE_IDLE_INIT; |
| else |
| rate = POLLRATE_IDLE; |
| break; |
| case MODE_RETRY: |
| rate = POLLRATE_RETRY; |
| break; |
| case MODE_ADC: |
| rate = POLLRATE_ADC; |
| break; |
| default: |
| break; |
| } |
| #ifdef CONFIG_WD_TAPPER |
| if (pem->mode == MODE_CUTOFF) |
| wd_tapper_set_timeout(POLLRATE_CUTOFF/1000); |
| else if (pem->mode == MODE_LOWBAT) |
| wd_tapper_set_timeout(POLLRATE_LOWBAT/1000); |
| else |
| wd_tapper_set_timeout(120); |
| #endif |
| |
| printk(KERN_INFO "%s: rate = %d\n", __func__, rate); |
| |
| return rate; |
| } |
| |
| static void em_reset(struct bcmpmu *bcmpmu) |
| { |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| |
| pem->fg_force_cal = 1; |
| |
| printk(KERN_INFO "%s: called.\n", __func__); |
| |
| |
| cancel_delayed_work_sync(&pem->work); |
| |
| schedule_delayed_work(&pem->work, 0); |
| } |
| |
| static int em_reset_status(struct bcmpmu *bcmpmu) |
| { |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| printk(KERN_INFO "%s: called.\n", __func__); |
| return pem->fg_force_cal; |
| } |
| |
| static int bcmpmu_get_capacity(struct bcmpmu *bcmpmu) |
| { |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| return pem->batt_capacity; |
| } |
| |
| static void charging_algorithm(struct bcmpmu_em *pem) |
| { |
| int charge_zone; |
| struct bcmpmu *bcmpmu = pem->bcmpmu; |
| |
| if (pem->piggyback_chrg) |
| return; |
| |
| if (is_charger_present(pem)) { |
| if (pem->charge_state == CHRG_STATE_MAINT) { |
| if (pem->support_hw_eoc) { |
| if (pem->batt_volt < pem->chrg_resume_lvl) { |
| pem->eoc_state = 0; |
| bcmpmu->chrgr_usb_en(bcmpmu, 1); |
| pem->charge_state = CHRG_STATE_CHRG; |
| } |
| } else if ((pem->batt_volt < pem->chrg_resume_lvl) || |
| (pem->resume_chrg == 1)) { |
| pem->resume_chrg = 0; |
| bcmpmu->chrgr_usb_en(bcmpmu, 1); |
| pem->charge_state = CHRG_STATE_CHRG; |
| } |
| } else if (pem->charge_state != CHRG_STATE_CHRG) { |
| if (bcmpmu->get_env_bit_status(bcmpmu, |
| PMU_ENV_USB_VALID) == true) |
| pem->charge_state = CHRG_STATE_CHRG; |
| else { |
| if (pem->chrgr_curr != 0) { |
| bcmpmu->chrgr_usb_en(bcmpmu, 1); |
| pr_em(FLOW, |
| "%s, charger not ready yet.\n", |
| __func__); |
| } |
| } |
| } |
| |
| if (pem->chrgr_curr == 0) { |
| pem->bcmpmu->chrgr_usb_en(pem->bcmpmu, 0); |
| pem->charge_state = CHRG_STATE_IDLE; |
| pr_em(FLOW, |
| "%s, chrgr_curr is 0 and stop charging.\n", |
| __func__); |
| } /* Revisit, this is a temporary fix */ |
| charge_zone = -1; |
| while (charge_zone != pem->charge_zone) { |
| charge_zone = pem->charge_zone; |
| update_charge_zone(pem); |
| } |
| } else if (pem->charge_state != CHRG_STATE_IDLE) { |
| bcmpmu->chrgr_usb_en(bcmpmu, 0); |
| pem->charge_state = CHRG_STATE_IDLE; |
| pem->charge_zone = CHRG_ZONE_OUT; |
| } |
| |
| if (!is_charger_present(pem)) |
| return; |
| pr_em(FLOW, "%s, cst=%d, cty=%d, crr=%d, czn=%d, fc=%d, qc=%d, vf=%d\n", |
| __func__, pem->charge_state, pem->chrgr_type, |
| pem->chrgr_curr, pem->charge_zone, |
| pem->icc_fc, pem->icc_qc, pem->vfloat); |
| } |
| |
| static void update_power_supply(struct bcmpmu_em *pem, int capacity) |
| { |
| struct power_supply *ps; |
| union power_supply_propval propval; |
| struct bcmpmu *bcmpmu = pem->bcmpmu; |
| int psy_changed = 0; |
| |
| if (pem->piggyback_chrg) { |
| if (pem->pb_notify) |
| pem->pb_notify(BCMPMU_CHRGR_EVENT_CAPACITY, capacity); |
| pr_em(FLOW, "%s, batt_status = %d\n", |
| __func__, pem->batt_status); |
| if (is_charger_present(pem)) { |
| if ((capacity == 100) && |
| (pem->eoc_cap == 100) && |
| (!pem->support_hw_eoc)) { |
| if (pem->batt_status == |
| POWER_SUPPLY_STATUS_CHARGING) { |
| pem->batt_status = |
| POWER_SUPPLY_STATUS_FULL; |
| pr_em(FLOW, "%s, transition to full\n", |
| __func__); |
| if (pem->pb_notify) |
| pem->pb_notify( |
| BCMPMU_CHRGR_EVENT_EOC, 0); |
| } |
| } |
| } |
| return; |
| } |
| |
| ps = power_supply_get_by_name("battery"); |
| if (!ps) { |
| pr_em(ERROR, "%s, No battery power supply\n", __func__); |
| return; |
| } |
| |
| if (!is_charger_present(pem)) { |
| if (pem->chrgr_curr != 0) { |
| bcmpmu->set_icc_fc(bcmpmu, 0); |
| pem->chrgr_curr = 0; |
| bcmpmu->chrgr_usb_en(bcmpmu, 0); |
| } |
| if (pem->batt_status != POWER_SUPPLY_STATUS_DISCHARGING) { |
| propval.intval = POWER_SUPPLY_STATUS_DISCHARGING; |
| ps->set_property(ps, |
| POWER_SUPPLY_PROP_STATUS, &propval); |
| pem->batt_status = POWER_SUPPLY_STATUS_DISCHARGING; |
| pr_em(FLOW, "%s, transition to discharging\n", |
| __func__); |
| psy_changed = 1; |
| } |
| } else { |
| if (capacity == 100) { |
| if (pem->batt_status != POWER_SUPPLY_STATUS_FULL) { |
| propval.intval = POWER_SUPPLY_STATUS_FULL; |
| ps->set_property(ps, |
| POWER_SUPPLY_PROP_STATUS, &propval); |
| pem->batt_status = POWER_SUPPLY_STATUS_FULL; |
| pr_em(FLOW, "%s, transition to fully charged\n", |
| __func__); |
| psy_changed = 1; |
| } |
| } else { |
| if (pem->batt_status != POWER_SUPPLY_STATUS_CHARGING) { |
| propval.intval = POWER_SUPPLY_STATUS_CHARGING; |
| ps->set_property(ps, |
| POWER_SUPPLY_PROP_STATUS, &propval); |
| pem->batt_status = POWER_SUPPLY_STATUS_CHARGING; |
| pr_em(FLOW, "%s, transition to charging\n", |
| __func__); |
| psy_changed = 1; |
| } |
| } |
| } |
| propval.intval = pem->batt_volt; |
| ps->set_property(ps, POWER_SUPPLY_PROP_VOLTAGE_NOW, &propval); |
| |
| propval.intval = capacity; |
| ps->set_property(ps, POWER_SUPPLY_PROP_CAPACITY, &propval); |
| if (pem->batt_capacity != capacity) |
| psy_changed = 1; |
| |
| propval.intval = em_batt_get_capacity_lvl(pem, capacity); |
| ps->set_property(ps, POWER_SUPPLY_PROP_CAPACITY_LEVEL, &propval); |
| if (pem->batt_capacity_lvl != propval.intval) |
| psy_changed = 1; |
| pem->batt_capacity_lvl = propval.intval; |
| |
| /* tbd for battery detection */ |
| propval.intval = 1; |
| ps->set_property(ps, POWER_SUPPLY_PROP_PRESENT, &propval); |
| if (pem->batt_present != propval.intval) |
| psy_changed = 1; |
| pem->batt_present = propval.intval; |
| |
| if (pem->batt_temp_in_celsius) |
| propval.intval = pem->batt_temp; |
| else |
| propval.intval = (pem->batt_temp - 273) * 10; |
| ps->set_property(ps, POWER_SUPPLY_PROP_TEMP, &propval); |
| |
| if (psy_changed) |
| power_supply_changed(ps); |
| } |
| |
| static void em_algorithm(struct work_struct *work) |
| { |
| struct bcmpmu_em *pem = |
| container_of(work, struct bcmpmu_em, work.work); |
| struct bcmpmu *bcmpmu = pem->bcmpmu; |
| struct bcmpmu_adc_req req; |
| int capacity = pem->batt_capacity; |
| int calibration; |
| int fg_result; |
| |
| static int first_run; |
| static int poll_count; |
| static int cap_poll_count; |
| static int vbatt_poll[POLL_SAMPLES]; |
| static int cap_poll[CAP_POLL_SAMPLES]; |
| int ret; |
| int retry_cnt = 0; |
| |
| if (first_run == 0) { |
| bcmpmu->fg_enable(bcmpmu, 1); |
| bcmpmu->fg_reset(bcmpmu); |
| |
| ret = bcmpmu->read_dev(bcmpmu, |
| PMU_REG_FG_CIC, |
| &fg_result, |
| bcmpmu->regmap[PMU_REG_FG_CIC].mask); |
| pr_em(INIT, "%s, Init CIC=0x%X\n", __func__, fg_result); |
| |
| req.sig = PMU_ADC_VMBATT; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| ret = -1; |
| retry_cnt = 0; |
| while ((ret != 0) && (retry_cnt < 20)) { |
| ret = bcmpmu->adc_req(bcmpmu, &req); |
| if (ret == 0) |
| pem->batt_volt = req.cnv; |
| else { |
| pr_em(INIT, "%s, failed adc vmbat, retry=%d\n", |
| __func__, retry_cnt); |
| retry_cnt++; |
| } |
| } |
| if (ret != 0) |
| pem->batt_volt = 3800; |
| |
| get_fg_delta(pem->bcmpmu, &pem->cap_delta); |
| update_fg_delta(pem); |
| get_fg_cap(pem->bcmpmu, &pem->cap_init); |
| |
| req.sig = PMU_ADC_NTC; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| ret = -1; |
| retry_cnt = 0; |
| while ((ret != 0) && (retry_cnt < 20)) { |
| ret = bcmpmu->adc_req(bcmpmu, &req); |
| if (ret == 0) |
| pem->batt_temp = req.cnv; |
| else { |
| pr_em(INIT, "%s, failed adc ntc, retry=%d\n", |
| __func__, retry_cnt); |
| retry_cnt++; |
| } |
| } |
| if (ret != 0) |
| pem->batt_temp = 200; |
| pem->fg_zone = get_fg_zone(pem, pem->batt_temp); |
| pem->fg_temp_fact = get_fg_temp_factor(pem, pem->batt_temp); |
| pem->fg_guard = get_fg_guard(pem); |
| pem->esr = get_fg_esr(pem); |
| |
| pr_em(INIT, "%s, first fg delta =%d, cap init =%d\n", __func__, |
| pem->cap_delta, pem->cap_init); |
| |
| pr_em(INIT, "%s, Init volt=%d, temp=%d\n", __func__, |
| pem->batt_volt, pem->batt_temp); |
| |
| poll_count = POLL_SAMPLES; |
| pem->mode = MODE_POLL; |
| pem->init_poll = 1; |
| cap_poll_count = CAP_POLL_SAMPLES; |
| |
| first_run = 1; |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } |
| |
| if (poll_count > 0) { |
| req.sig = PMU_ADC_VMBATT; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| bcmpmu->adc_req(bcmpmu, &req); |
| vbatt_poll[poll_count - 1] = req.cnv; |
| |
| pem->mode = MODE_POLL; |
| poll_count--; |
| if (pem->cal_mode == CAL_MODE_CUTOFF) |
| if (req.cnv <= pem->cutoff_volt) |
| pem->cutoff_count++; |
| pr_em(FLOW, "%s, first_run=%d, pollcnt=%d, fgbat=%d\n", |
| __func__, first_run, poll_count, req.cnv); |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } |
| if (pem->mode == MODE_POLL) { |
| ret = pem->bcmpmu->fg_acc_mas(pem->bcmpmu, &fg_result); |
| if (ret != 0) { |
| pem->mode = MODE_IDLE; |
| poll_count = POLL_SAMPLES; |
| pr_em(FLOW, |
| "%s, acc_mas failed, restart poll.\n", __func__); |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } |
| req.sig = PMU_ADC_FG_CURRSMPL; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| ret = bcmpmu->adc_req(bcmpmu, &req); |
| if (ret != 0) { |
| pem->mode = MODE_IDLE; |
| poll_count = POLL_SAMPLES; |
| pr_em(FLOW, |
| "%s, currsmpl failed, restart poll.\n", __func__); |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } |
| pem->batt_curr = req.cnv; |
| |
| sort(vbatt_poll, POLL_SAMPLES, sizeof(int), &cmp, NULL); |
| pem->batt_volt = average(vbatt_poll, POLL_SAMPLES, 2); |
| |
| capacity = em_batt_get_capacity(pem, |
| pem->batt_volt, pem->batt_curr); |
| |
| if (cap_poll_count > 0) { |
| cap_poll[cap_poll_count - 1] = capacity; |
| cap_poll_count--; |
| pr_em(INIT, "%s, Init capacity=%d, count=%d\n", |
| __func__, capacity, cap_poll_count); |
| if (cap_poll_count > 0) { |
| pem->mode = MODE_IDLE; |
| poll_count = POLL_SAMPLES; |
| pr_em(FLOW, "%s, restart capacity poll.\n", |
| __func__); |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| }; |
| sort(cap_poll, |
| CAP_POLL_SAMPLES, sizeof(int), &cmp, NULL); |
| capacity = average(cap_poll, CAP_POLL_SAMPLES, 1); |
| pr_em(INIT, |
| "%s, Avg Init capacity=%d\n", __func__, capacity); |
| } |
| |
| if (pem->init_poll == 1) { |
| if ((pem->cap_init > 0) && |
| (pem->cap_init <= 100) && |
| (abs(capacity - pem->cap_init) < 30)) { |
| capacity = pem->cap_init; |
| pr_em(FLOW, "%s, Init cal, use saved cap\n", |
| __func__); |
| } |
| update_fg_capacity(pem, capacity); |
| pem->batt_capacity = capacity; |
| update_power_supply(pem, capacity); |
| if (capacity >= 30) |
| pem->fg_lowbatt_cal = 1; |
| fg_set_default_rate(bcmpmu); |
| fg_offset_cal(bcmpmu); |
| } else { |
| if (pem->cal_mode == CAL_MODE_CUTOFF) { |
| pr_em(FLOW, "%s, Cutoff cal, count = %d\n", |
| __func__, pem->cutoff_count); |
| if (pem->cutoff_count >= pem->cutoff_count_max) |
| capacity = 0; |
| pem->cutoff_count = 0; |
| pem->transition = 1; |
| } else if (pem->cal_mode == CAL_MODE_FORCE) { |
| pem->fg_force_cal = 0; |
| pr_em(FLOW, "%s, Force cal\n", __func__); |
| } else if (pem->cal_mode == CAL_MODE_LOWBAT) { |
| if (pem->fg_cap_cal == 1) { |
| pem->cap_delta += |
| capacity - pem->batt_capacity; |
| ret = save_fg_delta(pem->bcmpmu, |
| pem->cap_delta); |
| if (ret != 0) |
| pem->cap_delta = 0; |
| update_fg_delta(pem); |
| pem->fg_cap_cal = 0; |
| } |
| pem->fg_lowbatt_cal = 0; |
| pr_em(FLOW, "%s, Low Volt cal.\n", __func__); |
| if (pem->batt_capacity != 0) |
| pem->low_cal_factor = |
| (capacity - pem->batt_capacity) |
| * 100 / pem->batt_capacity; |
| else |
| pem->low_cal_factor = 0; |
| if (pem->low_cal_factor > LOCAL_FACT) |
| pem->low_cal_factor = LOCAL_FACT; |
| else if (pem->low_cal_factor < -LOCAL_FACT) |
| pem->low_cal_factor = -LOCAL_FACT; |
| } else if (pem->cal_mode == CAL_MODE_TEMP) |
| pr_em(FLOW, "%s, Temp cal.\n", __func__); |
| else { |
| pr_em(FLOW, "%s, High Volt cal.\n", __func__); |
| if (pem->batt_capacity != 0) |
| pem->high_cal_factor = |
| (capacity - pem->batt_capacity) |
| * 100 / pem->batt_capacity; |
| else |
| pem->high_cal_factor = 0; |
| if (pem->high_cal_factor > HICAL_FACT) |
| pem->high_cal_factor = HICAL_FACT; |
| else if (pem->high_cal_factor < -HICAL_FACT) |
| pem->high_cal_factor = -HICAL_FACT; |
| } |
| if (pem->cal_mode != CAL_MODE_HIGHBAT) |
| pem->high_cal_factor = 0; |
| if (pem->cal_mode != CAL_MODE_LOWBAT) |
| pem->low_cal_factor = 0; |
| } |
| if ((pem->cal_mode != CAL_MODE_HIGHBAT) && |
| (pem->cal_mode != CAL_MODE_LOWBAT)) { |
| update_fg_capacity(pem, capacity); |
| pr_em(FLOW, "%s, map, v=%d, c=%d, capo=%d, capn=%d\n", |
| __func__, pem->batt_volt, pem->batt_curr, |
| pem->batt_capacity, capacity); |
| pem->batt_capacity = capacity; |
| } |
| pem->cal_mode = CAL_MODE_NONE; |
| pem->mode = MODE_IDLE; |
| poll_count = 0; |
| pem->init_poll = 0; |
| pr_em(FLOW, "%s, poll run complete.\n", __func__); |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } |
| |
| ret = update_adc_readings(pem); |
| if (ret != 0) { |
| pem->adc_retry++; |
| if (pem->adc_retry < ADC_RETRY_MAX) { |
| pem->mode = MODE_ADC; |
| pem->adc_retry++; |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| pr_em(FLOW, "%s, retry adc request\n", __func__); |
| return; |
| } else { |
| pem->adc_retry = 0; |
| pr_em(ERROR, "%s, adc request failed retry\n", |
| __func__); |
| } |
| } else |
| pem->adc_retry = 0; |
| charging_algorithm(pem); |
| |
| if (pem->transition != 0) { |
| pem->mode = MODE_TRANSITION; |
| calibration = 0; |
| capacity = pem->batt_capacity; |
| pem->transition = 0; |
| } else |
| calibration = update_batt_capacity(pem, &capacity); |
| |
| if (calibration != 0) { |
| if ((pem->cal_mode == CAL_MODE_LOWBAT) || |
| (pem->cal_mode == CAL_MODE_HIGHBAT)) { |
| if (pem->last_cal_mode == pem->cal_mode) |
| pem->retry_cnt++; |
| else |
| pem->retry_cnt = 0; |
| pr_em(FLOW, "%s, calm=%d, lst_calm=%d, retry=%d\n", |
| __func__, pem->cal_mode, |
| pem->last_cal_mode, pem->retry_cnt); |
| pem->last_cal_mode = pem->cal_mode; |
| if (pem->retry_cnt >= 3) { |
| pem->mode = MODE_POLL; |
| poll_count = POLL_SAMPLES; |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } else { |
| pem->mode = MODE_RETRY; |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } |
| } else { |
| pem->retry_cnt = 0; |
| pem->mode = MODE_POLL; |
| poll_count = POLL_SAMPLES; |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return; |
| } |
| } else |
| pem->retry_cnt = 0; |
| |
| |
| if ((!is_charger_present(pem)) && |
| (pem->fg_comp_mode == 0) && |
| (capacity > pem->batt_capacity)) |
| capacity = pem->batt_capacity; |
| |
| update_power_supply(pem, capacity); |
| |
| if (pem->batt_capacity != capacity) { |
| pem->batt_capacity = capacity; |
| save_fg_cap(pem->bcmpmu, capacity); |
| } |
| pr_em(REPORT, "EM: capacity=%d, volt=%d, curr=%d, temp=%d\n", |
| capacity, pem->batt_volt, pem->batt_curr, pem->batt_temp); |
| |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| |
| pr_em(REPORT, "%s, mode=%d, cstate=%d, update rate=%d\n", |
| __func__, pem->mode, |
| pem->charge_state, get_update_rate(pem)); |
| |
| pr_em(FLOW, "%s, mode=%d, cstate=%d, update rate=%d\n", |
| __func__, pem->mode, |
| pem->charge_state, get_update_rate(pem)); |
| |
| pem->time = get_seconds(); |
| } |
| |
| static int em_event_handler(struct notifier_block *nb, |
| unsigned long event, void *para) |
| { |
| struct bcmpmu_em *pem = container_of(nb, struct bcmpmu_em, nb); |
| int capacity_v; |
| struct bcmpmu_adc_req req; |
| int data = 0; |
| |
| switch (event) { |
| case BCMPMU_CHRGR_EVENT_CHGR_DETECTION: |
| pem->chrgr_type = *(enum bcmpmu_chrgr_type_t *)para; |
| |
| if (is_charger_present(pem)) { |
| blocking_notifier_call_chain( |
| &pem->bcmpmu-> |
| event[BCMPMU_JIG_EVENT_UART].notifiers, |
| BCMPMU_JIG_EVENT_UART, NULL); |
| } else { |
| blocking_notifier_call_chain( |
| &pem->bcmpmu-> |
| event[BCMPMU_USB_EVENT_ID_CHANGE].notifiers, |
| BCMPMU_USB_EVENT_ID_CHANGE, NULL); |
| } |
| |
| if (is_charger_present(pem)) |
| pem->fg_cap_cal = 0; |
| else if (pem->batt_capacity >= 30) |
| pem->fg_lowbatt_cal = 1; |
| pem->force_update = 1; |
| pem->transition = 1; |
| clear_cal_state(pem); |
| pr_em(FLOW, "%s, chrgr type=%d\n", __func__, pem->chrgr_type); |
| break; |
| |
| case BCMPMU_CHRGR_EVENT_CHRG_CURR_LMT: |
| pem->chrgr_curr = *(int *)para; |
| if (pem->chrgr_curr < bcmpmu_min_supported_curr()) |
| pem->chrgr_curr = 0; |
| pem->force_update = 1; |
| pem->eoc_count = 0; |
| pr_em(FLOW, "%s, chrgr curr=%d\n", __func__, pem->chrgr_curr); |
| break; |
| |
| case BCMPMU_FG_EVENT_FGC: |
| req.sig = PMU_ADC_FG_VMBATT; |
| req.tm = PMU_ADC_TM_HK; |
| req.flags = PMU_ADC_RAW_AND_UNIT; |
| pem->bcmpmu->adc_req(pem->bcmpmu, &req); |
| |
| capacity_v = em_batt_get_capacity(pem, req.cnv, 0); |
| if ((pem->batt_capacity > (capacity_v + 10)) || |
| ((pem->batt_capacity + 10) < (capacity_v))) { |
| pem->batt_capacity = capacity_v; |
| update_fg_capacity(pem, capacity_v); |
| pem->fg_cap_cal = 0; |
| } |
| pr_em(FLOW, "%s, fgc event\n", __func__); |
| break; |
| |
| case BCMPMU_CHRGR_EVENT_CHRG_RESUME_VBUS: |
| if (is_charger_present(pem)) |
| pem->resume_chrg = 1; |
| pem->eoc_count = 0; |
| pr_em(FLOW, "%s, resume vbus event\n", __func__); |
| break; |
| |
| case BCMPMU_CHRGR_EVENT_EOC: |
| if (pem->charge_state == CHRG_STATE_CHRG) |
| pem->eoc_state = 1; |
| pr_em(FLOW, "%s, eoc event, eoc_state=%d\n", |
| __func__, pem->eoc_state); |
| break; |
| |
| case BCMPMU_CHRGR_EVENT_CHRG_STATUS: |
| if (para) { |
| data = *(int *)para; |
| if (data == POWER_SUPPLY_STATUS_CHARGING) { |
| pem->charge_state = CHRG_STATE_CHRG; |
| pem->batt_status = data; |
| pem->eoc_state = 0; |
| } else if (data == POWER_SUPPLY_STATUS_DISCHARGING) { |
| if (!is_charger_present(pem)) { |
| pem->charge_state = CHRG_STATE_IDLE; |
| pem->batt_status = data; |
| } |
| } |
| |
| } else |
| pr_em(ERROR, "%s, no data avail.\n", __func__); |
| clear_cal_state(pem); |
| pr_em(FLOW, "%s, chrg sts event, data=%d, state=%d\n", |
| __func__, data, pem->charge_state); |
| break; |
| default: |
| break; |
| } |
| if (pem->mode == MODE_POLL) |
| return 0; |
| cancel_delayed_work_sync(&pem->work); |
| schedule_delayed_work(&pem->work, 0); |
| return 0; |
| } |
| |
| static int __devinit bcmpmu_em_probe(struct platform_device *pdev) |
| { |
| int ret = 0; |
| |
| struct bcmpmu *bcmpmu = pdev->dev.platform_data; |
| struct bcmpmu_em *pem; |
| struct bcmpmu_platform_data *pdata = bcmpmu->pdata; |
| |
| printk(KERN_INFO "%s: called.\n", __func__); |
| |
| pem = kzalloc(sizeof(struct bcmpmu_em), GFP_KERNEL); |
| if (!pem) { |
| printk(KERN_ERR "%s: failed to alloc mem.\n", __func__); |
| return -ENOMEM; |
| } |
| init_waitqueue_head(&pem->wait); |
| mutex_init(&pem->lock); |
| |
| pem->bcmpmu = bcmpmu; |
| bcmpmu->fg_get_capacity = bcmpmu_get_capacity; |
| bcmpmu->eminfo = pem; |
| bcmpmu_em = pem; |
| pem->chrgr_curr = 0; |
| pem->chrgr_type = PMU_CHRGR_TYPE_NONE; |
| pem->charge_state = CHRG_STATE_IDLE; |
| |
| pem->batt_status = POWER_SUPPLY_STATUS_UNKNOWN; |
| pem->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| pem->batt_capacity_lvl = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; |
| pem->batt_present = 1; |
| pem->batt_capacity = 50; |
| pem->fg_cap_cal = 0; |
| pem->fg_force_cal = 0; |
| pem->fg_lowbatt_cal = 0; |
| pem->cal_mode = CAL_MODE_NONE; |
| pem->pollrate = POLLRATE_HIGHBAT; |
| pem->support_fg = pdata->support_fg; |
| #ifdef CONFIG_CHARGER_BCMPMU_SPA |
| pem->piggyback_chrg = pdata->piggyback_chrg; |
| pem->pb_notify = pdata->piggyback_notify; |
| #endif |
| if (pdata->fg_capacity_full) |
| pem->fg_capacity_full = pdata->fg_capacity_full; |
| else |
| pem->fg_capacity_full = 1000*3600; |
| pem->fg_capacity = pem->fg_capacity_full; |
| |
| if (pdata->batt_voltcap_map) { |
| pem->bvcap = pdata->batt_voltcap_map; |
| pem->bvcap_len = pdata->batt_voltcap_map_len; |
| } else { |
| pem->bvcap = &batt_voltcap_map[0]; |
| pem->bvcap_len = ARRAY_SIZE(batt_voltcap_map); |
| } |
| if (pdata->chrg_zone_map) |
| pem->zone = pdata->chrg_zone_map; |
| else |
| pem->zone = &chrg_zone[0]; |
| if (pdata->chrg_1c_rate) |
| pem->charge_1c_rate = pdata->chrg_1c_rate; |
| else |
| pem->charge_1c_rate = 1000; |
| if (pdata->chrg_eoc) |
| pem->eoc = pdata->chrg_eoc; |
| else |
| pem->eoc = 50; |
| pem->support_hw_eoc = pdata->support_hw_eoc; |
| if (pdata->batt_impedence) |
| pem->esr = pdata->batt_impedence; |
| else |
| pem->esr = 250; |
| if (pdata->sys_impedence) |
| pem->sys_impedence = pdata->sys_impedence; |
| else |
| pem->sys_impedence = 15; |
| if (pdata->cutoff_volt) |
| pem->cutoff_volt = pdata->cutoff_volt; |
| else |
| pem->cutoff_volt = 3300; |
| |
| if (pdata->cutoff_count_max) |
| pem->cutoff_count_max = pdata->cutoff_count_max; |
| else |
| pem->cutoff_count_max = 3; |
| pem->eoc_factor_max = 50; |
| |
| if (pdata->support_chrg_maint) |
| pem->support_chrg_maint = pdata->support_chrg_maint; |
| if (pdata->chrg_resume_lvl) |
| pem->chrg_resume_lvl = pdata->chrg_resume_lvl; |
| else |
| pem->chrg_resume_lvl = 4000; |
| |
| if (pdata->fg_support_tc) |
| pem->fg_support_tc = pdata->fg_support_tc; |
| else |
| pem->fg_support_tc = 0; |
| if (pdata->fg_zone_info) |
| pem->fg_zone_ptr = pdata->fg_zone_info; |
| else |
| pem->fg_zone_ptr = NULL; |
| |
| if (pdata->fg_zone_settle_tm) |
| pem->fg_zone_settle_tm = pdata->fg_zone_settle_tm; |
| else |
| pem->fg_zone_settle_tm = 120; |
| if (pdata->fg_tc_dn_lvl) |
| pem->fg_tc_dn_zone = get_fg_zone(pem, pdata->fg_tc_dn_lvl); |
| else |
| pem->fg_tc_dn_zone = FG_TMP_ZONE_p5; |
| |
| if (pdata->fg_tc_up_lvl) |
| pem->fg_tc_up_zone = get_fg_zone(pem, pdata->fg_tc_up_lvl); |
| else |
| pem->fg_tc_up_zone = FG_TMP_ZONE_p10; |
| |
| if (pdata->fg_poll_hbat) |
| pem->fg_poll_hbat = pdata->fg_poll_hbat; |
| else |
| pem->fg_poll_hbat = POLLRATE_HIGHBAT; |
| if (pdata->fg_poll_lbat) |
| pem->fg_poll_lbat = pdata->fg_poll_lbat; |
| else |
| pem->fg_poll_lbat = POLLRATE_LOWBAT; |
| if (pdata->fg_lbat_lvl) |
| pem->fg_lbat_lvl = pdata->fg_lbat_lvl; |
| else |
| pem->fg_lbat_lvl = LOWBAT_LVL; |
| if (pdata->fg_low_cal_lvl) |
| pem->fg_low_cal_lvl = pdata->fg_low_cal_lvl; |
| else |
| pem->fg_low_cal_lvl = LOWBAT_LVL; |
| if (pdata->fg_fbat_lvl) |
| pem->fg_fbat_lvl = pdata->fg_fbat_lvl; |
| else |
| pem->fg_fbat_lvl = FULLBAT_LVL; |
| pem->batt_temp_in_celsius = pdata->batt_temp_in_celsius; |
| |
| pem->non_pse_charging = pdata->non_pse_charging; |
| if (pdata->max_vfloat) |
| pem->max_vfloat = pdata->max_vfloat; |
| else |
| pem->max_vfloat = MAX_VFLOAT; |
| pr_em(INIT, "%s, Battery Model = %s\n", __func__, pdata->batt_model); |
| |
| pem->charge_zone = CHRG_ZONE_QC; |
| pem->fg_zone = FG_TMP_ZONE_MAX; |
| pem->fg_pending_zone = FG_TMP_ZONE_MAX; |
| pem->fg_dbg_temp = 0; |
| pem->capacity_cutoff = -1; |
| |
| INIT_DELAYED_WORK(&pem->work, em_algorithm); |
| misc_register(&bcmpmu_em_device); |
| |
| pem->nb.notifier_call = em_event_handler; |
| ret = bcmpmu_add_notifier(BCMPMU_CHRGR_EVENT_CHGR_DETECTION, &pem->nb); |
| if (ret) { |
| pr_em(INIT, "%s, failed on chrgr det notifier, err=%d\n", |
| __func__, ret); |
| goto err; |
| } |
| ret = bcmpmu_add_notifier(BCMPMU_CHRGR_EVENT_CHRG_CURR_LMT, &pem->nb); |
| if (ret) { |
| pr_em(INIT, "%s, failed on chrgr curr lmt notifier, err=%d\n", |
| __func__, ret); |
| goto err; |
| } |
| ret = bcmpmu_add_notifier(BCMPMU_FG_EVENT_FGC, &pem->nb); |
| if (ret) { |
| pr_em(INIT, "%s, failed on fgc notifier, err=%d\n", |
| __func__, ret); |
| goto err; |
| } |
| ret = bcmpmu_add_notifier(BCMPMU_CHRGR_EVENT_CHRG_RESUME_VBUS, |
| &pem->nb); |
| if (ret) { |
| pr_em(INIT, "%s, failed on resume vbus notifier, err=%d\n", |
| __func__, ret); |
| goto err; |
| } |
| ret = bcmpmu_add_notifier(BCMPMU_CHRGR_EVENT_EOC, &pem->nb); |
| if (ret) { |
| pr_em(INIT, "%s, failed on eoc notifier, err=%d\n", |
| __func__, ret); |
| goto err; |
| } |
| ret = bcmpmu_add_notifier(BCMPMU_CHRGR_EVENT_CHRG_STATUS, &pem->nb); |
| if (ret) { |
| pr_em(INIT, "%s, failed on chrg status notifier, err=%d\n", |
| __func__, ret); |
| goto err; |
| } |
| pem->chrgr_type = bcmpmu->usb_accy_data.chrgr_type; |
| pem->chrgr_curr = bcmpmu->usb_accy_data.max_curr_chrgr; |
| if (pem->chrgr_curr < bcmpmu_min_supported_curr()) |
| pem->chrgr_curr = 0; |
| bcmpmu->em_reset = em_reset; |
| bcmpmu->em_reset_status = em_reset_status; |
| |
| clear_cal_state(pem); |
| |
| #ifdef CONFIG_MFD_BCMPMU_DBG |
| ret = sysfs_create_group(&pdev->dev.kobj, &bcmpmu_em_attr_group); |
| #endif |
| if (pdata->tch_timer_dis) { |
| if ((bcmpmu->regmap[PMU_REG_TCH_TIMER].addr != 0) && |
| (bcmpmu->regmap[PMU_REG_TCH_TIMER].mask != 0)) { |
| pr_em(INIT, "%s: Disable TCH timer\n", __func__); |
| bcmpmu->write_dev(bcmpmu, PMU_REG_TCH_TIMER, |
| bcmpmu->regmap[PMU_REG_TCH_TIMER].mask, |
| bcmpmu->regmap[PMU_REG_TCH_TIMER].mask); |
| } |
| } |
| schedule_delayed_work(&pem->work, msecs_to_jiffies(500)); |
| return 0; |
| |
| err: |
| #ifdef CONFIG_MFD_BCMPMU_DBG |
| sysfs_remove_group(&pdev->dev.kobj, &bcmpmu_em_attr_group); |
| #endif |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHGR_DETECTION, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHRG_CURR_LMT, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_FG_EVENT_FGC, &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHRG_RESUME_VBUS, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_EOC, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHRG_STATUS, |
| &pem->nb); |
| kfree(pem); |
| return ret; |
| } |
| |
| static int __devexit bcmpmu_em_remove(struct platform_device *pdev) |
| { |
| struct bcmpmu *bcmpmu = pdev->dev.platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| int ret; |
| |
| misc_deregister(&bcmpmu_em_device); |
| |
| #ifdef CONFIG_MFD_BCMPMU_DBG |
| sysfs_remove_group(&pdev->dev.kobj, &bcmpmu_em_attr_group); |
| #endif |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHGR_DETECTION, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHRG_CURR_LMT, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_FG_EVENT_FGC, &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHRG_RESUME_VBUS, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_EOC, |
| &pem->nb); |
| ret = bcmpmu_remove_notifier(BCMPMU_CHRGR_EVENT_CHRG_STATUS, |
| &pem->nb); |
| kfree(pem); |
| |
| return 0; |
| } |
| |
| static int bcmpmu_em_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct bcmpmu *bcmpmu = pdev->dev.platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| |
| cancel_delayed_work_sync(&pem->work); |
| |
| return 0; |
| } |
| |
| static int bcmpmu_em_resume(struct platform_device *pdev) |
| { |
| struct bcmpmu *bcmpmu = pdev->dev.platform_data; |
| struct bcmpmu_em *pem = bcmpmu->eminfo; |
| unsigned long time; |
| time = get_seconds(); |
| |
| if ((time - pem->time) * 1000 > get_update_rate(pem)) |
| schedule_delayed_work(&pem->work, 0); |
| else |
| schedule_delayed_work(&pem->work, |
| msecs_to_jiffies(get_update_rate(pem))); |
| return 0; |
| } |
| |
| static struct platform_driver bcmpmu_em_driver = { |
| .driver = { |
| .name = "bcmpmu_em", |
| }, |
| .probe = bcmpmu_em_probe, |
| .remove = __devexit_p(bcmpmu_em_remove), |
| .suspend = bcmpmu_em_suspend, |
| .resume = bcmpmu_em_resume, |
| }; |
| |
| static int __init bcmpmu_em_init(void) |
| { |
| return platform_driver_register(&bcmpmu_em_driver); |
| } |
| module_init(bcmpmu_em_init); |
| |
| static void __exit bcmpmu_em_exit(void) |
| { |
| platform_driver_unregister(&bcmpmu_em_driver); |
| } |
| module_exit(bcmpmu_em_exit); |
| |
| MODULE_DESCRIPTION("BCM PMIC Battery Charging and Gauging"); |
| MODULE_LICENSE("GPL"); |