/* * Copyright (C) 2012 Spreadtrum Communications Inc. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "sprd_battery.h" #define SPRDBAT__DEBUG #ifdef SPRDBAT__DEBUG #define SPRDBAT_DEBUG(format, arg...) pr_info("sprdbat: " format, ## arg) #else #define SPRDBAT_DEBUG(format, arg...) #endif #define SPRDBAT_CV_TRIGGER_CURRENT 7/10 enum sprdbat_event { SPRDBAT_ADP_PLUGIN_E, SPRDBAT_ADP_PLUGOUT_E, SPRDBAT_OVI_STOP_E, SPRDBAT_OVI_RESTART_E, SPRDBAT_OTP_COLD_STOP_E, SPRDBAT_OTP_OVERHEAT_STOP_E, SPRDBAT_OTP_COLD_RESTART_E, SPRDBAT_OTP_OVERHEAT_RESTART_E, SPRDBAT_CHG_FULL_E, SPRDBAT_RECHARGE_E, SPRDBAT_CHG_TIMEOUT_E, SPRDBAT_CHG_TIMEOUT_RESTART_E, SPRDBAT_CHG_UNSPEC_E, }; static struct sprdbat_drivier_data *sprdbat_data; struct delayed_work sprdbat_charge_work; static uint32_t sprdbat_cv_irq_dis = 1; static uint32_t sprdbat_average_cnt; static unsigned long sprdbat_update_capacity_time; static unsigned long sprdbat_last_query_time; static uint32_t sprdbat_trickle_chg; static uint32_t sprdbat_start_chg; static uint32_t poweron_capacity; static struct notifier_block sprdbat_notifier; static uint32_t sprdbat_cccv_cal_from_chip = 0; static uint32_t chg_phy_dis_flag; //only be used on CONFIG_SPRD_NOFGUCURRENT_CHG /* < add by lgd: when calling charging, change the chgr_current to 400mA */ static uint32_t charging_call_flag; static uint32_t is_charging; /* add by lgd: when calling charging, change the chgr_current to 400mA > */ extern struct sprdbat_auxadc_cal adc_cal; static void sprdbat_change_module_state(uint32_t event); static int sprdbat_stop_charge(void); #ifdef SPRDBAT_TWO_CHARGE_CHANNEL static int sprdbat_stop_charge_ext(void); static int sprdbat_stop_chg_process_ext(void); static int sprdbat_start_chg_process_ext(void); static int plugin_callback_ext(int usb_cable, void *data); static int plugout_callback_ext(int usb_cable, void *data); #endif static unsigned long adjust_chg_flag = 0; #define TEMP_BUFF_CNT 5 static int current_buff[TEMP_BUFF_CNT] = {0,0,0,0,0}; static int sprdbat_get_current_from_buff(int bat_cur) { static int pointer = 0; int i = 0, delta = 0; SPRDBAT_DEBUG("sprdbat_store_and_adjust_current bat_cur=%d\n",bat_cur); if(pointer >= TEMP_BUFF_CNT) pointer = 0; current_buff[pointer++] = bat_cur; for(i = 0; ibat_info.bat_current_avg; int state_cur = 500 ; int i =1,delta = 0; if((state < 0) ||(sprdbat_data->bat_info.adp_type != ADP_TYPE_DCP)) return; if(state == 0){ state_cur = sprdbat_data->pdata->adp_dcp_cur; } SPRDBAT_DEBUG("sprdbat_chgcurrent_adjust enter chgcur=%d,avg_cur=%d,state_cur = %d\n", chg_cur, avg_cur, state_cur); if( state_cur > sprdbat_data->pdata->adp_dcp_cur){ state_cur = sprdbat_data->pdata->adp_dcp_cur; } delta = state_cur -chg_cur + avg_cur; if(delta > 0){ SPRDBAT_DEBUG("delta > 0 sprdchg_set_chg_cur %d \n",state_cur); sprdchg_set_chg_cur(state_cur); }else{ for(i = 1; i <= 20; i++){ delta += 50; if(delta > 0){ SPRDBAT_DEBUG("i=%d,deta > 0 break\n",i); break; } } state_cur += 50 * i; if( state_cur > sprdbat_data->pdata->adp_dcp_cur){ state_cur = sprdbat_data->pdata->adp_dcp_cur; } SPRDBAT_DEBUG("state_cur =%d,set cur\n",state_cur); sprdchg_set_chg_cur(state_cur); } sprdbat_clear_current_buff(); } static void sprdbat_auto_switch_cur(void) { int avg_cur = sprdbat_data->bat_info.bat_current_avg; int chg_cur = sprdchg_get_chg_cur(); SPRDBAT_DEBUG("enter sprdbat_auto_switch_cur avg_cur=%d,chg_cur=%d\n", avg_cur, chg_cur); if(sprdbat_data->bat_info.adp_type != ADP_TYPE_DCP){ return ; } if((avg_cur < 0) && (chg_cur < sprdbat_data->pdata->adp_dcp_cur)){ SPRDBAT_DEBUG("sprdbat_auto_increase_cur need ++chg_cur %d =>%d\n", chg_cur,chg_cur + 50); chg_cur += 50; sprdchg_set_chg_cur(chg_cur); sprdbat_clear_current_buff(); } if((adjust_chg_flag > 0) && (avg_cur > 100) && (chg_cur > 500)){ SPRDBAT_DEBUG("sprdbat_auto_switch_cur need --chg_cur %d => 500\n", chg_cur); sprdbat_chgcurrent_adjust(1); sprdbat_clear_current_buff(); } } #if 0 static int chg_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { *state = 4; printk(KERN_NOTICE " ------------------get_max_state \n"); return 0; } static int chg_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { *state = adjust_chg_flag; printk(KERN_NOTICE " ---------------get_cur_state=%d \n",*state); return 0; } static int chg_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { if(state > 0){ SPRDBAT_DEBUG("chg_set_cur_state state>0 =>%d ,adjust_chg_flag=%d\n", state,adjust_chg_flag); adjust_chg_flag = state; }else if(state ==0){ SPRDBAT_DEBUG("chg_set_cur_state state==0 =%d \n",state); adjust_chg_flag = 0; }else{ return 0; } sprdbat_chgcurrent_adjust(state); return 0; } static struct thermal_cooling_device_ops const chg_cooling_ops = { .get_max_state = chg_get_max_state, .get_cur_state = chg_get_cur_state, .set_cur_state = chg_set_cur_state, }; #endif int sprdbat_interpolate(int x, int n, struct sprdbat_table_data *tab) { int index; int y; if (x >= tab[0].x) y = tab[0].y; else if (x <= tab[n - 1].x) y = tab[n - 1].y; else { /* find interval */ for (index = 1; index < n; index++) if (x > tab[index].x) break; /* interpolate */ y = (tab[index - 1].y - tab[index].y) * (x - tab[index].x) * 2 / (tab[index - 1].x - tab[index].x); y = (y + 1) / 2; y += tab[index].y; } return y; } uint32_t sprd_get_vbat_voltage(void) { return sprdbat_read_vbat_vol(); } static int sprdbat_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sprdbat_drivier_data *data = container_of(psy, struct sprdbat_drivier_data, ac); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: if (likely(psy->type == POWER_SUPPLY_TYPE_MAINS)) { val->intval = data->bat_info.ac_online ? 1 : 0; } else { ret = -EINVAL; } break; default: ret = -EINVAL; break; } return ret; } static int sprdbat_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sprdbat_drivier_data *data = container_of(psy, struct sprdbat_drivier_data, usb); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = data->bat_info.usb_online ? 1 : 0; break; default: ret = -EINVAL; break; } return ret; } static int sprdbat_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sprdbat_drivier_data *data = container_of(psy, struct sprdbat_drivier_data, battery); int ret = 0; switch (psp) { case POWER_SUPPLY_PROP_STATUS: val->intval = data->bat_info.module_state; break; case POWER_SUPPLY_PROP_HEALTH: val->intval = data->bat_info.bat_health; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = data->bat_info.capacity; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = data->bat_info.vbat_vol * 1000; break; case POWER_SUPPLY_PROP_TEMP: val->intval = data->bat_info.cur_temp; break; default: ret = -EINVAL; break; } return ret; } static enum power_supply_property sprdbat_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_TEMP, }; static enum power_supply_property sprdbat_ac_props[] = { POWER_SUPPLY_PROP_ONLINE, }; static enum power_supply_property sprdbat_usb_props[] = { POWER_SUPPLY_PROP_ONLINE, }; static ssize_t sprdbat_store_caliberate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); static ssize_t sprdbat_show_caliberate(struct device *dev, struct device_attribute *attr, char *buf); #define SPRDBAT_CALIBERATE_ATTR(_name) \ { \ .attr = { .name = #_name, .mode = S_IRUGO | S_IWUSR | S_IWGRP, }, \ .show = sprdbat_show_caliberate, \ .store = sprdbat_store_caliberate, \ } #define SPRDBAT_CALIBERATE_ATTR_RO(_name) \ { \ .attr = { .name = #_name, .mode = S_IRUGO, }, \ .show = sprdbat_show_caliberate, \ } #define SPRDBAT_CALIBERATE_ATTR_WO(_name) \ { \ .attr = { .name = #_name, .mode = S_IWUSR | S_IWGRP, }, \ .store = sprdbat_store_caliberate, \ } /* < add by lgd: when calling charging, change the chgr_current to 400mA */ #define SPRDBAT_CALIBERATE_ATTR_RW(_name) \ { \ .attr = { .name = #_name, .mode = S_IWUGO | S_IRUGO, }, \ .show = sprdbat_show_caliberate, \ .store = sprdbat_store_caliberate, \ } /* add by lgd: when calling charging, change the chgr_current to 400mA > */ static struct device_attribute sprd_caliberate[] = { SPRDBAT_CALIBERATE_ATTR_RO(real_time_voltage), SPRDBAT_CALIBERATE_ATTR_WO(stop_charge), SPRDBAT_CALIBERATE_ATTR_RO(real_time_current), SPRDBAT_CALIBERATE_ATTR_WO(battery_0), SPRDBAT_CALIBERATE_ATTR_WO(battery_1), SPRDBAT_CALIBERATE_ATTR(hw_switch_point), SPRDBAT_CALIBERATE_ATTR_RO(charger_voltage), SPRDBAT_CALIBERATE_ATTR_RO(real_time_vbat_adc), SPRDBAT_CALIBERATE_ATTR_WO(save_capacity), SPRDBAT_CALIBERATE_ATTR_RO(temp_adc), SPRDBAT_CALIBERATE_ATTR_RW(charging_call), /* add by lgd: when calling charging, change the chgr_current to 400mA */ #ifdef CHG_CUR_ADJUST SPRDBAT_CALIBERATE_ATTR(chg_cool_state), #endif }; enum sprdbat_attribute { BATTERY_VOLTAGE = 0, STOP_CHARGE, BATTERY_NOW_CURRENT, BATTERY_0, BATTERY_1, HW_SWITCH_POINT, CHARGER_VOLTAGE, BATTERY_ADC, SAVE_CAPACITY, TEMP_ADC, CHARGING_CALL, /* add by lgd: when calling charging, change the chgr_current to 400mA */ CHG_COOL_STATE }; /* < add by lgd: when calling charging, change the chgr_current to 400mA */ static int sprdbat_change_chgring_cur(uint32_t set_value) { // struct timespec cur_time; if(1 == set_value) { sprdbat_data->bat_info.chg_current_type = 400; } else { if (sprdbat_data->bat_info.adp_type == ADP_TYPE_CDP) { sprdbat_data->bat_info.chg_current_type = sprdbat_data->pdata->adp_cdp_cur; } else if (sprdbat_data->bat_info.adp_type == ADP_TYPE_DCP) { sprdbat_data->bat_info.chg_current_type = sprdbat_data->pdata->adp_dcp_cur; } else { sprdbat_data->bat_info.chg_current_type = sprdbat_data->pdata->adp_sdp_cur; } } sprdchg_set_eoc_level(0); sprdchg_set_chg_cur(sprdbat_data->bat_info.chg_current_type); sprdchg_set_cccvpoint(sprdbat_data->bat_info.cccv_point); // get_monotonic_boottime(&cur_time); // sprdbat_data->bat_info.chg_start_time = cur_time.tv_sec; sprdchg_start_charge(); // sprdbat_average_cnt = 0; SPRDBAT_DEBUG ("OHH,sprdbat_chg_chgr_cur: health:%d,chg_start_time:%ld,chg_current_type:%d\n", sprdbat_data->bat_info.bat_health, sprdbat_data->bat_info.chg_start_time, sprdbat_data->bat_info.chg_current_type); // sprdbat_trickle_chg = 0; // sprdbat_start_chg = 1; // chg_phy_dis_flag = 0; return 0; } /* add by lgd: when calling charging, change the chgr_current to 400mA > */ static ssize_t sprdbat_store_caliberate(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { unsigned long set_value; unsigned long irq_flag = 0; const ptrdiff_t off = attr - sprd_caliberate; set_value = simple_strtoul(buf, NULL, 10); pr_info("battery calibrate value %lu %lu\n", off, set_value); mutex_lock(&sprdbat_data->lock); switch (off) { case STOP_CHARGE: if (0 == set_value) { sprdbat_change_module_state(SPRDBAT_ADP_PLUGIN_E); } else { sprdbat_change_module_state(SPRDBAT_ADP_PLUGOUT_E); #ifdef SPRDBAT_TWO_CHARGE_CHANNEL sprdbat_data->bat_info.ac_online = 0; sprdbat_data->bat_info.usb_online = 0; sprdchg_stop_charge(); sprdchg_stop_charge_ext(); #endif } break; case BATTERY_0: adc_cal.p0_vol = set_value & 0xffff; //only for debug adc_cal.p0_adc = (set_value >> 16) & 0xffff; break; case BATTERY_1: adc_cal.p1_vol = set_value & 0xffff; adc_cal.p1_adc = (set_value >> 16) & 0xffff; adc_cal.cal_type = SPRDBAT_AUXADC_CAL_NV; break; case HW_SWITCH_POINT: if (!sprdbat_cccv_cal_from_chip) { local_irq_save(irq_flag); sprdbat_data->bat_info.cccv_point = set_value; sprdchg_set_cccvpoint(sprdbat_data->bat_info. cccv_point); if ((!sprdbat_start_chg) && sprdbat_cv_irq_dis) { sprdbat_cv_irq_dis = 0; sprdbat_trickle_chg = 0; enable_irq(sprdbat_data->irq_chg_cv_state); } local_irq_restore(irq_flag); } break; case SAVE_CAPACITY: { int temp = set_value - poweron_capacity; pr_info("battery temp:%d\n", temp); if (abs(temp) > sprdbat_data->pdata->cap_valid_range_poweron || 0 == set_value) { pr_info("battery poweron capacity:%lu,%d\n", set_value, poweron_capacity); sprdbat_data->bat_info.capacity = poweron_capacity; } else { pr_info("battery old capacity:%lu,%d\n", set_value, poweron_capacity); sprdbat_data->bat_info.capacity = set_value; } power_supply_changed(&sprdbat_data->battery); } break; case CHG_COOL_STATE: if(set_value > 0){ SPRDBAT_DEBUG("chg_set_cur_state state>0 =>%d ,adjust_chg_flag=%d\n", set_value,adjust_chg_flag); adjust_chg_flag = set_value; }else if(set_value ==0){ SPRDBAT_DEBUG("chg_set_cur_state state==0 =%d \n",set_value); adjust_chg_flag = 0; }else{ break; } sprdbat_chgcurrent_adjust(set_value); break; /* < add by lgd: when calling charging, change the chgr_current to 400mA */ case CHARGING_CALL: if(1 == set_value) { if (1 == is_charging) { SPRDBAT_DEBUG("OHH,CHARGING_CALL: 1, charger current set to 400mA\n"); sprdbat_change_chgring_cur(set_value); charging_call_flag = set_value; } else { SPRDBAT_DEBUG("OHH,CHARGING_CALL: 1, but not in charging mode\n"); } } else if (0 == set_value) { if (1 == is_charging) { SPRDBAT_DEBUG("OHH,CHARGING_CALL: 0, charger current set to normal\n"); sprdbat_change_chgring_cur(set_value); charging_call_flag = set_value; } else { SPRDBAT_DEBUG("OHH,CHARGING_CALL: 0, but not in charging mode\n"); } } else { SPRDBAT_DEBUG("OHH,CHARGING_CALL: %lu, write error\n", set_value); break; } /* add by lgd: when calling charging, change the chgr_current to 400mA > */ default: count = -EINVAL; break; } mutex_unlock(&sprdbat_data->lock); return count; } static ssize_t sprdbat_show_caliberate(struct device *dev, struct device_attribute *attr, char *buf) { int i = 0; const ptrdiff_t off = attr - sprd_caliberate; int adc_value; int voltage; uint32_t now_current; int chg_cur = sprdchg_get_chg_cur(); switch (off) { case BATTERY_VOLTAGE: voltage = sprdchg_read_vbat_vol(); i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", voltage); break; case BATTERY_NOW_CURRENT: if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_CHARGING) { now_current = sprdchg_read_chg_current(); i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", now_current); } else { i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "discharging"); } break; case HW_SWITCH_POINT: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", sprdbat_data->bat_info.cccv_point); break; case CHARGER_VOLTAGE: if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_CHARGING) { voltage = sprdchg_read_vchg_vol(); i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", voltage); } else { i += scnprintf(buf + i, PAGE_SIZE - i, "%s\n", "discharging"); } break; case BATTERY_ADC: adc_value = sci_adc_get_value(ADC_CHANNEL_VBAT, false); if (adc_value < 0) adc_value = 0; i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", adc_value); break; case TEMP_ADC: adc_value = sprdbat_read_temp_adc(); if (adc_value < 0) adc_value = 0; i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", adc_value); break; /* < add by lgd: when calling charging, change the chgr_current to 400mA */ case CHARGING_CALL: i += scnprintf(buf + i, PAGE_SIZE - i, "%d\n", charging_call_flag); break; /* add by lgd: when calling charging, change the chgr_current to 400mA > */ case CHG_COOL_STATE: adc_value = adjust_chg_flag; i += scnprintf(buf + i, PAGE_SIZE - i, "%d,%d\n", adc_value,chg_cur); break; default: i = -EINVAL; break; } return i; } static int sprdbat_creat_caliberate_attr(struct device *dev) { int i, rc; for (i = 0; i < ARRAY_SIZE(sprd_caliberate); i++) { rc = device_create_file(dev, &sprd_caliberate[i]); if (rc) goto sprd_attrs_failed; } goto sprd_attrs_succeed; sprd_attrs_failed: while (i--) device_remove_file(dev, &sprd_caliberate[i]); sprd_attrs_succeed: return rc; } static int sprdbat_remove_caliberate_attr(struct device *dev) { int i; for (i = 0; i < ARRAY_SIZE(sprd_caliberate); i++) { device_remove_file(dev, &sprd_caliberate[i]); } return 0; } static void sprdbat_info_init(struct sprdbat_drivier_data *data) { struct timespec cur_time; data->bat_info.adp_type = ADP_TYPE_UNKNOW; if (data->gpio_vbat_detect > 0) { if (gpio_get_value(sprdbat_data->gpio_vbat_detect)) { SPRDBAT_DEBUG("vbat good!!!\n"); data->bat_info.bat_health = POWER_SUPPLY_HEALTH_GOOD; data->bat_info.chg_stop_flags = SPRDBAT_CHG_END_NONE_BIT; } else { SPRDBAT_DEBUG("vbat unspec!!!\n"); data->stop_charge(); data->bat_info.bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; data->bat_info.chg_stop_flags |= SPRDBAT_CHG_END_UNSPEC; } } else { SPRDBAT_DEBUG("vbat no detected!!!\n"); data->bat_info.bat_health = POWER_SUPPLY_HEALTH_GOOD; data->bat_info.chg_stop_flags = SPRDBAT_CHG_END_NONE_BIT; } data->bat_info.module_state = POWER_SUPPLY_STATUS_DISCHARGING; data->bat_info.chg_start_time = 0; get_monotonic_boottime(&cur_time); sprdbat_update_capacity_time = cur_time.tv_sec; sprdbat_last_query_time = cur_time.tv_sec; data->bat_info.capacity = sprdfgu_poweron_capacity(); //~0; poweron_capacity = sprdfgu_poweron_capacity(); data->bat_info.soc = sprdfgu_read_soc(); data->bat_info.vbat_vol = sprdbat_read_vbat_vol(); data->bat_info.vbat_ocv = sprdfgu_read_vbat_ocv(); data->bat_info.cur_temp = sprdbat_read_temp(); data->bat_info.bat_current = sprdfgu_read_batcurrent(); data->bat_info.chging_current = 0; data->bat_info.chg_current_type = sprdbat_data->pdata->adp_sdp_cur; { #ifdef CONFIG_OTP_SPRD uint32_t cv_point; extern int sci_efuse_cccv_cal_get(unsigned int *p_cal_data); if (sci_efuse_cccv_cal_get(&cv_point)) { SPRDBAT_DEBUG("cccv_point efuse:%d\n", cv_point); data->bat_info.cccv_point = sprdchg_tune_endvol_cccv(data->pdata-> chg_end_vol_pure, cv_point); SPRDBAT_DEBUG ("cccv_point sprdchg_tune_endvol_cccv:%d\n", data->bat_info.cccv_point); sprdbat_cccv_cal_from_chip = 1; } else { data->bat_info.cccv_point = data->pdata->cccv_default; SPRDBAT_DEBUG("cccv_point default\n"); #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG BUG_ON(1); //if do NOT have isense resistor ,cccv must cal by ATE #endif } #endif } return; } static int sprdbat_start_charge(void) { struct timespec cur_time; if (sprdbat_data->bat_info.adp_type == ADP_TYPE_CDP) { sprdbat_data->bat_info.chg_current_type = sprdbat_data->pdata->adp_cdp_cur; } else if (sprdbat_data->bat_info.adp_type == ADP_TYPE_DCP) { sprdbat_data->bat_info.chg_current_type = sprdbat_data->pdata->adp_dcp_cur; } else { sprdbat_data->bat_info.chg_current_type = sprdbat_data->pdata->adp_sdp_cur; } is_charging = 1; /* add by lgd: when calling charging, change the chgr_current to 400mA */ sprdchg_set_eoc_level(0); sprdchg_set_chg_cur(sprdbat_data->bat_info.chg_current_type); sprdchg_set_cccvpoint(sprdbat_data->bat_info.cccv_point); get_monotonic_boottime(&cur_time); sprdbat_data->bat_info.chg_start_time = cur_time.tv_sec; sprdchg_start_charge(); sprdbat_average_cnt = 0; SPRDBAT_DEBUG ("sprdbat_start_charge health:%d,chg_start_time:%ld,chg_current_type:%d\n", sprdbat_data->bat_info.bat_health, sprdbat_data->bat_info.chg_start_time, sprdbat_data->bat_info.chg_current_type); sprdbat_trickle_chg = 0; sprdbat_start_chg = 1; chg_phy_dis_flag = 0; return 0; } static int sprdbat_stop_charge(void) { unsigned long irq_flag = 0; sprdbat_data->bat_info.chg_start_time = 0; /* < add by lgd: when calling charging, change the chgr_current to 400mA */ is_charging = 0; charging_call_flag = 0; /* add by lgd: when calling charging, change the chgr_current to 400mA > */ sprdchg_set_eoc_level(0); sprdchg_stop_charge(); local_irq_save(irq_flag); if (!sprdbat_cv_irq_dis) { disable_irq_nosync(sprdbat_data->irq_chg_cv_state); sprdbat_cv_irq_dis = 1; } local_irq_restore(irq_flag); chg_phy_dis_flag = 1; SPRDBAT_DEBUG("sprdbat_stop_charge\n"); #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG schedule_delayed_work(&sprdbat_data->battery_work, sprdbat_data->pdata->bat_polling_time * HZ); #endif return 0; } static inline void _sprdbat_clear_stopflags(uint32_t flag_msk) { sprdbat_data->bat_info.chg_stop_flags &= ~flag_msk; SPRDBAT_DEBUG("_sprdbat_clear_stopflags flag_msk:0x%x\n", flag_msk); if (sprdbat_data->bat_info.chg_stop_flags == SPRDBAT_CHG_END_NONE_BIT) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_GOOD; if (flag_msk != SPRDBAT_CHG_END_FULL_BIT && flag_msk != SPRDBAT_CHG_END_TIMEOUT_BIT) { sprdbat_data->bat_info.module_state = POWER_SUPPLY_STATUS_CHARGING; } //sprdbat_start_charge(); sprdbat_data->start_charge(); } else if (sprdbat_data->bat_info. chg_stop_flags & SPRDBAT_CHG_END_UNSPEC) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; } else if (sprdbat_data->bat_info. chg_stop_flags & SPRDBAT_CHG_END_OTP_OVERHEAT_BIT) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; } else if (sprdbat_data->bat_info. chg_stop_flags & SPRDBAT_CHG_END_OTP_COLD_BIT) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_COLD; } else if (sprdbat_data->bat_info. chg_stop_flags & SPRDBAT_CHG_END_OVP_BIT) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; } else if (sprdbat_data->bat_info. chg_stop_flags & (SPRDBAT_CHG_END_TIMEOUT_BIT | SPRDBAT_CHG_END_FULL_BIT)) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_GOOD; } else { BUG_ON(1); } } static inline void _sprdbat_set_stopflags(uint32_t flag) { SPRDBAT_DEBUG("_sprdbat_set_stopflags flags:0x%x\n", flag); if (sprdbat_data->bat_info.chg_stop_flags == SPRDBAT_CHG_END_NONE_BIT) { //sprdbat_stop_charge(); sprdbat_data->stop_charge(); } sprdbat_data->bat_info.chg_stop_flags |= flag; if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_CHARGING) { if ((flag == SPRDBAT_CHG_END_FULL_BIT)) { sprdbat_data->bat_info.module_state = POWER_SUPPLY_STATUS_FULL; } else if (flag == SPRDBAT_CHG_END_TIMEOUT_BIT) { if (sprdbat_data->pdata->chgtimeout_show_full) { sprdbat_data->bat_info.module_state = POWER_SUPPLY_STATUS_FULL; } } else { sprdbat_data->bat_info.module_state = POWER_SUPPLY_STATUS_NOT_CHARGING; } } } static void sprdbat_change_module_state(uint32_t event) { SPRDBAT_DEBUG("sprdbat_change_module_state event :0x%x\n", event); switch (event) { case SPRDBAT_ADP_PLUGIN_E: sprdbat_data->bat_info.chg_this_timeout = sprdbat_data->pdata->chg_timeout; _sprdbat_clear_stopflags(~SPRDBAT_CHG_END_UNSPEC); if (sprdbat_data-> bat_info.chg_stop_flags & SPRDBAT_CHG_END_UNSPEC) { sprdbat_data->bat_info.module_state = POWER_SUPPLY_STATUS_NOT_CHARGING; } queue_delayed_work(sprdbat_data->monitor_wqueue, sprdbat_data->charge_work, 2 * HZ); break; case SPRDBAT_ADP_PLUGOUT_E: if (sprdbat_data-> bat_info.chg_stop_flags & SPRDBAT_CHG_END_UNSPEC) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; } else { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_GOOD; } sprdbat_data->bat_info.chg_stop_flags &= SPRDBAT_CHG_END_UNSPEC; sprdbat_data->bat_info.module_state = POWER_SUPPLY_STATUS_DISCHARGING; //sprdbat_stop_charge(); sprdbat_data->stop_charge(); break; case SPRDBAT_OVI_STOP_E: if (sprdbat_data->bat_info.bat_health == POWER_SUPPLY_HEALTH_GOOD) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; } _sprdbat_set_stopflags(SPRDBAT_CHG_END_OVP_BIT); break; case SPRDBAT_OVI_RESTART_E: _sprdbat_clear_stopflags(SPRDBAT_CHG_END_OVP_BIT); break; case SPRDBAT_OTP_COLD_STOP_E: if (sprdbat_data->bat_info.bat_health == POWER_SUPPLY_HEALTH_GOOD) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_COLD; } _sprdbat_set_stopflags(SPRDBAT_CHG_END_OTP_COLD_BIT); break; case SPRDBAT_OTP_OVERHEAT_STOP_E: if (sprdbat_data->bat_info.bat_health == POWER_SUPPLY_HEALTH_GOOD) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_OVERHEAT; } _sprdbat_set_stopflags(SPRDBAT_CHG_END_OTP_OVERHEAT_BIT); break; case SPRDBAT_OTP_COLD_RESTART_E: _sprdbat_clear_stopflags(SPRDBAT_CHG_END_OTP_COLD_BIT); break; case SPRDBAT_OTP_OVERHEAT_RESTART_E: _sprdbat_clear_stopflags(SPRDBAT_CHG_END_OTP_OVERHEAT_BIT); break; case SPRDBAT_CHG_FULL_E: sprdbat_data->bat_info.chg_this_timeout = sprdbat_data->pdata->chg_rechg_timeout; _sprdbat_set_stopflags(SPRDBAT_CHG_END_FULL_BIT); break; case SPRDBAT_RECHARGE_E: _sprdbat_clear_stopflags(SPRDBAT_CHG_END_FULL_BIT); break; case SPRDBAT_CHG_TIMEOUT_E: sprdbat_data->bat_info.chg_this_timeout = sprdbat_data->pdata->chg_rechg_timeout; _sprdbat_set_stopflags(SPRDBAT_CHG_END_TIMEOUT_BIT); break; case SPRDBAT_CHG_TIMEOUT_RESTART_E: _sprdbat_clear_stopflags(SPRDBAT_CHG_END_TIMEOUT_BIT); break; case SPRDBAT_CHG_UNSPEC_E: if (sprdbat_data->bat_info.bat_health == POWER_SUPPLY_HEALTH_GOOD) { sprdbat_data->bat_info.bat_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; } _sprdbat_set_stopflags(SPRDBAT_CHG_END_UNSPEC); break; default: BUG_ON(1); break; } power_supply_changed(&sprdbat_data->battery); } static int plugin_callback(int usb_cable, void *data) { SPRDBAT_DEBUG("charger plug in interrupt happen\n"); mutex_lock(&sprdbat_data->lock); #ifndef SPRDBAT_TWO_CHARGE_CHANNEL wake_lock_timeout(&(sprdbat_data->charger_plug_out_lock), SPRDBAT_PLUG_WAKELOCK_TIME_SEC * HZ); sprdbat_data->bat_info.adp_type = sprdchg_charger_is_adapter(); if (sprdbat_data->bat_info.adp_type == ADP_TYPE_DCP) { sprdbat_data->bat_info.ac_online = 1; } else { sprdbat_data->bat_info.usb_online = 1; } sprdbat_adp_plug_nodify(1); #else if (sprdbat_data->bat_info.usb_online) { sprdbat_stop_chg_process_ext(); } else { sprdbat_adp_plug_nodify(1); } sprdbat_data->bat_info.ac_online = 1; sprdbat_data->bat_info.adp_type = ADP_TYPE_DCP; sprdbat_data->start_charge = sprdbat_start_charge; sprdbat_data->stop_charge = sprdbat_stop_charge; sprdbat_data->charge_work = &sprdbat_charge_work; #endif sprdbat_change_module_state(SPRDBAT_ADP_PLUGIN_E); irq_set_irq_type(sprdbat_data->irq_vchg_ovi, IRQ_TYPE_LEVEL_HIGH); enable_irq(sprdbat_data->irq_vchg_ovi); sprdchg_timer_enable(sprdbat_data->pdata->chg_polling_time); mutex_unlock(&sprdbat_data->lock); SPRDBAT_DEBUG("plugin_callback:sprdbat_data->bat_info.adp_type:%d\n", sprdbat_data->bat_info.adp_type); if (sprdbat_data->bat_info.adp_type == ADP_TYPE_DCP) power_supply_changed(&sprdbat_data->ac); else power_supply_changed(&sprdbat_data->usb); SPRDBAT_DEBUG("plugin_callback: end...\n"); return 0; } static int plugout_callback(int usb_cable, void *data) { uint32_t adp_type = sprdbat_data->bat_info.adp_type; SPRDBAT_DEBUG("charger plug out interrupt happen\n"); mutex_lock(&sprdbat_data->lock); disable_irq_nosync(sprdbat_data->irq_vchg_ovi); sprdchg_timer_disable(); sprdbat_change_module_state(SPRDBAT_ADP_PLUGOUT_E); #ifndef SPRDBAT_TWO_CHARGE_CHANNEL wake_lock_timeout(&(sprdbat_data->charger_plug_out_lock), SPRDBAT_PLUG_WAKELOCK_TIME_SEC * HZ); sprdbat_adp_plug_nodify(0); sprdbat_data->bat_info.adp_type = ADP_TYPE_UNKNOW; sprdbat_data->bat_info.ac_online = 0; sprdbat_data->bat_info.usb_online = 0; #else sprdbat_data->bat_info.ac_online = 0; if (sprdbat_data->bat_info.usb_online) { sprdbat_start_chg_process_ext(); } else { sprdbat_adp_plug_nodify(0); } #endif mutex_unlock(&sprdbat_data->lock); if (adp_type == ADP_TYPE_DCP) power_supply_changed(&sprdbat_data->ac); else power_supply_changed(&sprdbat_data->usb); return 0; } #ifndef SPRDBAT_TWO_CHARGE_CHANNEL static struct usb_hotplug_callback power_cb = { .plugin = plugin_callback, .plugout = plugout_callback, .data = NULL, }; #else static struct usb_hotplug_callback power_cb = { .plugin = plugin_callback_ext, .plugout = plugout_callback_ext, .data = NULL, }; #endif static char *supply_list[] = { "battery", }; static char *battery_supply_list[] = { "audio-ldo", "sprdfgu", }; static int sprdbat_timer_handler(void *data) { sprdbat_data->bat_info.vbat_vol = sprdbat_read_vbat_vol(); sprdbat_data->bat_info.vbat_ocv = sprdfgu_read_vbat_ocv(); sprdbat_data->bat_info.bat_current = sprdfgu_read_batcurrent(); #if 0 SPRDBAT_DEBUG ("sprdbat_timer_handler---vbat_vol %d,ocv:%d,bat_current:%d\n", sprdbat_data->bat_info.vbat_vol, sprdbat_data->bat_info.vbat_ocv, sprdbat_data->bat_info.bat_current); #endif queue_delayed_work(sprdbat_data->monitor_wqueue, sprdbat_data->charge_work, 0); return 0; } static __used irqreturn_t sprdbat_vchg_ovi_irq(int irq, void *dev_id) { int value; value = gpio_get_value(sprdbat_data->gpio_vchg_ovi); if (value) { SPRDBAT_DEBUG("charger ovi high\n"); irq_set_irq_type(sprdbat_data->irq_vchg_ovi, IRQ_TYPE_LEVEL_LOW); } else { SPRDBAT_DEBUG("charger ovi low\n"); irq_set_irq_type(sprdbat_data->irq_vchg_ovi, IRQ_TYPE_LEVEL_HIGH); } queue_work(sprdbat_data->monitor_wqueue, &sprdbat_data->ovi_irq_work); return IRQ_HANDLED; } static int sprdbat_adjust_cccvpoint(uint32_t vbat_now) { uint32_t cv; if (sprdbat_cccv_cal_from_chip) { return 0; } if (vbat_now <= sprdbat_data->pdata->chg_end_vol_pure) { cv = ((sprdbat_data->pdata->chg_end_vol_pure - vbat_now) * 10) / ONE_CCCV_STEP_VOL + 1; cv += sprdchg_get_cccvpoint(); SPRDBAT_DEBUG("sprdbat_adjust_cccvpoint turn high cv:0x%x\n", cv); //BUG_ON(cv > SPRDBAT_CCCV_MAX); if (cv > SPRDBAT_CCCV_MAX) { cv = SPRDBAT_CCCV_MAX; } sprdbat_data->bat_info.cccv_point = cv; sprdchg_set_cccvpoint(cv); } else { cv = sprdchg_get_cccvpoint(); //BUG_ON(cv < (SPRDBAT_CCCV_MIN + 2)); if (cv < (SPRDBAT_CCCV_MIN + 1)) { cv = SPRDBAT_CCCV_MIN + 1; } cv -= 1; sprdbat_data->bat_info.cccv_point = cv; SPRDBAT_DEBUG("sprdbat_adjust_cccvpoint turn low cv:0x%x\n", cv); sprdchg_set_cccvpoint(cv); } return 0; } #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG static irqreturn_t sprdbat_chg_cv_irq(int irq, void *dev_id) { if (sprdchg_get_eoc_level() != sprdbat_data->pdata->chg_eoc_level) { sprdchg_set_eoc_level(sprdbat_data->pdata->chg_eoc_level); SPRDBAT_DEBUG("sprdbat_chg_cv_irq reset eoc level\n"); return IRQ_HANDLED; } if (!sprdbat_cv_irq_dis) { disable_irq_nosync(sprdbat_data->irq_chg_cv_state); sprdbat_cv_irq_dis = 1; } sprdbat_data->bat_info.vbat_vol = sprdbat_read_vbat_vol(); SPRDBAT_DEBUG("sprdbat_chg_cv_irq vol %d\n", sprdbat_data->bat_info.vbat_vol); SPRDBAT_DEBUG("sprdbat_chg_cv_irq voltage full ,trickle chargeing\n"); sprdbat_trickle_chg = 1; return IRQ_HANDLED; } #else static irqreturn_t sprdbat_chg_cv_irq(int irq, void *dev_id) { int value; value = gpio_get_value(sprdbat_data->gpio_chg_cv_state); SPRDBAT_DEBUG("sprdbat_chg_cv_irq:%d\n", value); if (!sprdbat_cv_irq_dis) { disable_irq_nosync(sprdbat_data->irq_chg_cv_state); sprdbat_cv_irq_dis = 1; } sprdbat_data->bat_info.vbat_vol = sprdbat_read_vbat_vol(); SPRDBAT_DEBUG("sprdbat_chg_cv_irq vol %d\n", sprdbat_data->bat_info.vbat_vol); if (sprdbat_data->bat_info.vbat_vol >= sprdbat_data->pdata->chg_end_vol_pure) { SPRDBAT_DEBUG ("sprdbat_chg_cv_irq voltage full ,trickle chargeing\n"); sprdbat_trickle_chg = 1; } else { sprdbat_average_cnt = 0; sprdbat_adjust_cccvpoint(sprdbat_data->bat_info.vbat_vol); schedule_delayed_work(&sprdbat_data->cv_irq_work, (HZ * 1)); } return IRQ_HANDLED; } #endif static void sprdbat_cv_irq_works(struct work_struct *work) { mutex_lock(&sprdbat_data->lock); if ((sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_DISCHARGING) || (sprdbat_data->bat_info.chg_stop_flags != SPRDBAT_CHG_END_NONE_BIT)) { mutex_unlock(&sprdbat_data->lock); //fixed SPRDBAT_DEBUG("sprdbat_cv_irq_works return \n"); return; } else { SPRDBAT_DEBUG("sprdbat_cv_irq_works adjust cccv\n"); if (sprdbat_cv_irq_dis) { SPRDBAT_DEBUG("sprdbat_cv_irq_works enable_irq\n"); sprdbat_cv_irq_dis = 0; enable_irq(sprdbat_data->irq_chg_cv_state); } } mutex_unlock(&sprdbat_data->lock); } static void sprdbat_ovi_irq_works(struct work_struct *work) { int value; value = gpio_get_value(sprdbat_data->gpio_vchg_ovi); sprdbat_data->bat_info.vchg_vol = sprdchg_read_vchg_vol(); SPRDBAT_DEBUG("vchg_vol:%d,gpio:%d\n", sprdbat_data->bat_info.vchg_vol, value); mutex_lock(&sprdbat_data->lock); if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_DISCHARGING) { mutex_unlock(&sprdbat_data->lock); return; } #ifdef SPRDBAT_TWO_CHARGE_CHANNEL if (!sprdbat_data->bat_info.ac_online) { mutex_unlock(&sprdbat_data->lock); return; } #endif if (value) { if (! (SPRDBAT_CHG_END_OVP_BIT & sprdbat_data->bat_info. chg_stop_flags)) { SPRDBAT_DEBUG("SPRDBAT_OVI_STOP_E\n"); sprdbat_change_module_state(SPRDBAT_OVI_STOP_E); } } else { if ((SPRDBAT_CHG_END_OVP_BIT & sprdbat_data->bat_info. chg_stop_flags)) { SPRDBAT_DEBUG("SPRDBAT_OVI_RESTART_E\n"); sprdbat_change_module_state(SPRDBAT_OVI_RESTART_E); } } mutex_unlock(&sprdbat_data->lock); } static __used irqreturn_t sprdbat_vbat_detect_irq(int irq, void *dev_id) { sprdbat_data->stop_charge(); disable_irq_nosync(sprdbat_data->irq_vbat_detect); SPRDBAT_DEBUG("battery remove!!!!\n"); queue_work(sprdbat_data->monitor_wqueue, &sprdbat_data->vbat_detect_irq_work); return IRQ_HANDLED; } static void sprdbat_vbat_detect_irq_works(struct work_struct *work) { int value; value = gpio_get_value(sprdbat_data->gpio_vbat_detect); SPRDBAT_DEBUG("bat_detect value:%d\n", value); mutex_lock(&sprdbat_data->lock); sprdbat_change_module_state(SPRDBAT_CHG_UNSPEC_E); mutex_unlock(&sprdbat_data->lock); } static void sprdbat_chg_print_log(void) { struct timespec cur_time; get_monotonic_boottime(&cur_time); //SPRDBAT_DEBUG("adp_type:%d\n", sprdbat_data->bat_info.adp_type); SPRDBAT_DEBUG ("chg_log:time:%ld,health:%d,state:%d,stopflags0x:%x,chg_s_time:%ld,temp:%d\n", cur_time.tv_sec, sprdbat_data->bat_info.bat_health, sprdbat_data->bat_info.module_state, sprdbat_data->bat_info.chg_stop_flags, sprdbat_data->bat_info.chg_start_time, sprdbat_data->bat_info.cur_temp); //SPRDBAT_DEBUG("capacity:%d\n", sprdbat_data->bat_info.capacity); //SPRDBAT_DEBUG("soc:%d\n", sprdbat_data->bat_info.soc); //SPRDBAT_DEBUG("vbat_vol:%d\n", sprdbat_data->bat_info.vbat_vol); //SPRDBAT_DEBUG("vbat_ocv:%d\n", sprdbat_data->bat_info.vbat_ocv); //SPRDBAT_DEBUG("cur_temp:%d\n", sprdbat_data->bat_info.cur_temp); //SPRDBAT_DEBUG("bat_current:%d\n", sprdbat_data->bat_info.bat_current); //SPRDBAT_DEBUG("chging_current:%d\n", // sprdbat_data->bat_info.chging_current); SPRDBAT_DEBUG ("chg_log:chgcur_type:%d,cccv:%d,vchg:%d,cvstate:%d,cccv_cal:%d\n", sprdbat_data->bat_info.chg_current_type, sprdbat_data->bat_info.cccv_point, sprdchg_read_vchg_vol(), gpio_get_value(sprdbat_data->gpio_chg_cv_state), sprdbat_cccv_cal_from_chip); //SPRDBAT_DEBUG("aux vbat vol:%d\n", sprdchg_read_vbat_vol()); } static void sprdbat_print_battery_log(void) { struct timespec cur_time; get_monotonic_boottime(&cur_time); SPRDBAT_DEBUG ("bat_log:time:%ld,vbat:%d,ocv:%d,current:%d,cap:%d,state:%d,auxbat:%d,temp:%d\n", cur_time.tv_sec, sprdbat_data->bat_info.vbat_vol, sprdbat_data->bat_info.vbat_ocv, sprdbat_data->bat_info.bat_current, sprdbat_data->bat_info.capacity, sprdbat_data->bat_info.module_state, sprdchg_read_vbat_vol(), sprdbat_data->bat_info.cur_temp); } #define SPRDBAT_SHUTDOWN_OFSSET 50 static void sprdbat_update_capacty(void) { uint32_t fgu_capacity = sprdfgu_read_capacity(); uint32_t flush_time = 0; uint32_t period_time = 0; struct timespec cur_time; if (sprdbat_data->bat_info.capacity == ~0) { return; } get_monotonic_boottime(&cur_time); flush_time = cur_time.tv_sec - sprdbat_update_capacity_time; period_time = cur_time.tv_sec - sprdbat_last_query_time; sprdbat_last_query_time = cur_time.tv_sec; SPRDBAT_DEBUG("fgu_cap: = %d,flush: = %d,period:=%d\n", fgu_capacity, flush_time, period_time); switch (sprdbat_data->bat_info.module_state) { case POWER_SUPPLY_STATUS_CHARGING: if (fgu_capacity < sprdbat_data->bat_info.capacity) { if (sprdfgu_read_batcurrent() >= 0) { pr_info("avoid vol jumping\n"); fgu_capacity = sprdbat_data->bat_info.capacity; } else { if (period_time < sprdbat_data->pdata->cap_one_per_time) { fgu_capacity = sprdbat_data->bat_info.capacity - 1; SPRDBAT_DEBUG ("cap decrease when charging fgu_capacity: = %d\n", fgu_capacity); } if ((sprdbat_data->bat_info.capacity - fgu_capacity) >= (flush_time / sprdbat_data->pdata->cap_one_per_time)) { fgu_capacity = sprdbat_data->bat_info.capacity - flush_time / sprdbat_data-> pdata->cap_one_per_time; } } } else if (fgu_capacity > sprdbat_data->bat_info.capacity) { if (period_time < sprdbat_data->pdata->cap_one_per_time) { fgu_capacity = sprdbat_data->bat_info.capacity + 1; SPRDBAT_DEBUG ("avoid jumping! fgu_cap: = %d\n", fgu_capacity); } if ((fgu_capacity - sprdbat_data->bat_info.capacity) >= (flush_time / sprdbat_data->pdata->cap_one_per_time)) { fgu_capacity = sprdbat_data->bat_info.capacity + flush_time / sprdbat_data->pdata->cap_one_per_time; } } /*when soc=100 and adp plugin occur, keep on 100, */ if (100 == sprdbat_data->bat_info.capacity) { sprdbat_update_capacity_time = cur_time.tv_sec; fgu_capacity = 100; } else { if (fgu_capacity >= 100) { fgu_capacity = 99; } } if (sprdbat_data->bat_info.vbat_vol <= (sprdbat_data->pdata->soft_vbat_uvlo - SPRDBAT_SHUTDOWN_OFSSET)) { fgu_capacity = 0; SPRDBAT_DEBUG("soft uvlo, shutdown by kernel.. vol:%d", sprdbat_data->bat_info.vbat_vol); orderly_poweroff(true); } /* the false of setting cap to 100 */ if((sprdbat_data->bat_info.vbat_ocv >= sprdbat_data->pdata->chg_false_full_vol || sprdbat_data->bat_info.bat_current <= sprdbat_data->pdata->chg_end_cur) && 99 == fgu_capacity) { fgu_capacity = 100; SPRDBAT_DEBUG("%s():OHH, set false cap 100; bat_ocv=%d false_full_vol=%d" " bat_cur=%d chg_end_cur = %d", __func__, sprdbat_data->bat_info.vbat_ocv, sprdbat_data->pdata->chg_false_full_vol, sprdbat_data->bat_info.bat_current, sprdbat_data->pdata->chg_end_cur); } /* the false of setting cap to 100 */ break; case POWER_SUPPLY_STATUS_NOT_CHARGING: case POWER_SUPPLY_STATUS_DISCHARGING: if (fgu_capacity >= sprdbat_data->bat_info.capacity) { fgu_capacity = sprdbat_data->bat_info.capacity; } else { if (period_time < sprdbat_data->pdata->cap_one_per_time) { fgu_capacity = sprdbat_data->bat_info.capacity - 1; SPRDBAT_DEBUG ("avoid jumping! fgu_capacity: = %d\n", fgu_capacity); } if ((sprdbat_data->bat_info.capacity - fgu_capacity) >= (flush_time / sprdbat_data->pdata->cap_one_per_time)) { fgu_capacity = sprdbat_data->bat_info.capacity - flush_time / sprdbat_data->pdata->cap_one_per_time; } } break; case POWER_SUPPLY_STATUS_FULL: sprdbat_update_capacity_time = cur_time.tv_sec; if (fgu_capacity != 100) { fgu_capacity = 100; } if (sprdbat_data->bat_info.vbat_vol <= (sprdbat_data->pdata->soft_vbat_uvlo - SPRDBAT_SHUTDOWN_OFSSET)) { fgu_capacity = 0; SPRDBAT_DEBUG("soft uvlo, shutdown by kernel when status is full.. vol:%d", sprdbat_data->bat_info.vbat_vol); orderly_poweroff(true); } break; default: BUG_ON(1); break; } if (sprdbat_data->bat_info.vbat_vol <= sprdbat_data->pdata->soft_vbat_uvlo) { fgu_capacity = 0; SPRDBAT_DEBUG("soft uvlo, vbat very low,level..0.. vol:%d", sprdbat_data->bat_info.vbat_vol); } if (fgu_capacity != sprdbat_data->bat_info.capacity) { sprdbat_data->bat_info.capacity = fgu_capacity; sprdbat_update_capacity_time = cur_time.tv_sec; sprdfgu_record_cap(sprdbat_data->bat_info.capacity); power_supply_changed(&sprdbat_data->battery); } } static void sprdbat_battery_works(struct work_struct *work) { SPRDBAT_DEBUG("sprdbat_battery_works\n"); mutex_lock(&sprdbat_data->lock); #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG if (sprdbat_data->bat_info.module_state != POWER_SUPPLY_STATUS_DISCHARGING && sprdbat_data->bat_info.chg_stop_flags == SPRDBAT_CHG_END_NONE_BIT) { mutex_unlock(&sprdbat_data->lock); SPRDBAT_DEBUG("sprdbat_battery_works return\n"); return; } #endif sprdbat_data->bat_info.vbat_vol = sprdbat_read_vbat_vol(); sprdbat_data->bat_info.cur_temp = sprdbat_read_temp(); sprdbat_data->bat_info.bat_current = sprdfgu_read_batcurrent(); sprdbat_data->bat_info.vbat_ocv = sprdfgu_read_vbat_ocv(); sprdbat_data->bat_info.bat_current_avg = sprdbat_get_current_from_buff(sprdbat_data->bat_info.bat_current); sprdbat_update_capacty(); mutex_unlock(&sprdbat_data->lock); sprdbat_print_battery_log(); #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_DISCHARGING || sprdbat_data->bat_info.chg_stop_flags != SPRDBAT_CHG_END_NONE_BIT) { schedule_delayed_work(&sprdbat_data->battery_work, sprdbat_data->pdata->bat_polling_time * HZ); } #else if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_CHARGING) { schedule_delayed_work(&sprdbat_data->battery_work, sprdbat_data->pdata-> bat_polling_time_fast * HZ); } else { schedule_delayed_work(&sprdbat_data->battery_work, sprdbat_data->pdata->bat_polling_time * HZ); } #endif } static void sprdbat_battery_sleep_works(struct work_struct *work) { SPRDBAT_DEBUG("sprdbat_battery_sleep_works\n"); if (schedule_delayed_work(&sprdbat_data->battery_work, 0) == 0) { cancel_delayed_work_sync(&sprdbat_data->battery_work); schedule_delayed_work(&sprdbat_data->battery_work, 0); } } static uint32_t temp_trigger_cnt; static void sprdbat_temp_monitor(void) { sprdbat_data->bat_info.cur_temp = sprdbat_read_temp(); if (sprdbat_data->bat_info. chg_stop_flags & SPRDBAT_CHG_END_OTP_COLD_BIT) { if (sprdbat_data->bat_info.cur_temp > sprdbat_data->pdata->otp_low_restart) sprdbat_change_module_state(SPRDBAT_OTP_COLD_RESTART_E); SPRDBAT_DEBUG("otp_low_restart:%d\n", sprdbat_data->pdata->otp_low_restart); } else if (sprdbat_data->bat_info. chg_stop_flags & SPRDBAT_CHG_END_OTP_OVERHEAT_BIT) { if (sprdbat_data->bat_info.cur_temp < sprdbat_data->pdata->otp_high_restart) sprdbat_change_module_state (SPRDBAT_OTP_OVERHEAT_RESTART_E); SPRDBAT_DEBUG("otp_high_restart:%d\n", sprdbat_data->pdata->otp_high_restart); } else { if (sprdbat_data->bat_info.cur_temp < sprdbat_data->pdata->otp_low_stop || sprdbat_data->bat_info.cur_temp > sprdbat_data->pdata->otp_high_stop) { SPRDBAT_DEBUG("otp_low_stop:%d,otp_high_stop:%d\n", sprdbat_data->pdata->otp_low_stop, sprdbat_data->pdata->otp_high_stop); temp_trigger_cnt++; if (temp_trigger_cnt > SPRDBAT_TEMP_TRIGGER_TIMES) { if (sprdbat_data->bat_info.cur_temp < sprdbat_data->pdata->otp_low_stop) { sprdbat_change_module_state (SPRDBAT_OTP_COLD_STOP_E); } else { sprdbat_change_module_state (SPRDBAT_OTP_OVERHEAT_STOP_E); } //sprdchg_timer_enable(sprdbat_data->pdata-> // chg_polling_time); temp_trigger_cnt = 0; } else { //sprdchg_timer_enable(sprdbat_data->pdata-> // chg_polling_time_fast); } } else { if (temp_trigger_cnt) { //sprdchg_timer_enable(sprdbat_data->pdata-> // chg_polling_time); temp_trigger_cnt = 0; } } } } static int sprdbat_is_chg_timeout(void) { struct timespec cur_time; get_monotonic_boottime(&cur_time); if (cur_time.tv_sec - sprdbat_data->bat_info.chg_start_time > sprdbat_data->bat_info.chg_this_timeout) return 1; else return 0; } #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG static inline sprdbat_polling_chg_state(void) { unsigned long irq_flag = 0; if (gpio_get_value(sprdbat_data->gpio_chg_cv_state)) { struct timespec cur_time; uint32_t chging_keep_time; SPRDBAT_DEBUG("first cccv eoc%d!\n", sprdchg_get_eoc_level()); get_monotonic_boottime(&cur_time); chging_keep_time = cur_time.tv_sec - sprdbat_data->bat_info.chg_start_time; if (chging_keep_time <= sprdbat_data->bat_info.chg_this_timeout) { if (sprdbat_data->pdata->chg_cv_timeout < sprdbat_data->bat_info.chg_this_timeout - chging_keep_time) { sprdbat_data->bat_info.chg_this_timeout = sprdbat_data->pdata->chg_cv_timeout; } else { sprdbat_data->bat_info.chg_this_timeout = sprdbat_data->bat_info.chg_this_timeout - chging_keep_time; } sprdbat_data->bat_info.chg_start_time = cur_time.tv_sec; } #if 1 sprdchg_set_eoc_level(sprdbat_data->pdata->chg_eoc_level); mdelay(5); SPRDBAT_DEBUG("after reset eoc%d, cv:%d!\n", sprdchg_get_eoc_level(), gpio_get_value(sprdbat_data->gpio_chg_cv_state)); { struct irq_desc *desc = irq_to_desc(sprdbat_data->irq_chg_cv_state); struct irq_data *idata = irq_desc_get_irq_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); if (chip->irq_ack) { chip->irq_ack(idata); } } local_irq_save(irq_flag); if (sprdbat_cv_irq_dis) { sprdbat_cv_irq_dis = 0; SPRDBAT_DEBUG("open cv irq--first cccv\n"); irq_set_irq_type(sprdbat_data->irq_chg_cv_state, IRQ_TYPE_LEVEL_HIGH); enable_irq(sprdbat_data->irq_chg_cv_state); } local_irq_restore(irq_flag); #endif } SPRDBAT_DEBUG("no fgu current charing... %d\n", gpio_get_value(sprdbat_data->gpio_chg_cv_state)); if (sprdbat_trickle_chg) { sprdbat_trickle_chg = 0; SPRDBAT_DEBUG("SPRDBAT_CHG_FULL_E\n"); sprdbat_change_module_state(SPRDBAT_CHG_FULL_E); } } #endif static void sprdbat_charge_works(struct work_struct *work) { uint32_t cur; unsigned long irq_flag = 0; SPRDBAT_DEBUG("sprdbat_charge_works-start\n"); mutex_lock(&sprdbat_data->lock); if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_DISCHARGING) { mutex_unlock(&sprdbat_data->lock); return; } #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG if ((sprdbat_data->bat_info.chg_stop_flags == SPRDBAT_CHG_END_NONE_BIT) && chg_phy_dis_flag && (!temp_trigger_cnt)) { sprdbat_update_capacty(); sprdbat_print_battery_log(); sprdchg_timer_enable(sprdbat_data->pdata->chg_polling_time); sprdchg_start_charge(); chg_phy_dis_flag = 0; SPRDBAT_DEBUG("update capacty-charging\n"); mutex_unlock(&sprdbat_data->lock); return; } #endif if (sprdbat_start_chg) { sprdbat_data->bat_info.vbat_vol = sprdbat_read_vbat_vol(); sprdbat_data->bat_info.vbat_ocv = sprdfgu_read_vbat_ocv(); sprdbat_data->bat_info.bat_current = sprdfgu_read_batcurrent(); SPRDBAT_DEBUG ("sprdbat_charge_works---vbat_vol %d,ocv:%d,bat_current:%d\n", sprdbat_data->bat_info.vbat_vol, sprdbat_data->bat_info.vbat_ocv, sprdbat_data->bat_info.bat_current); if (!sprdbat_cccv_cal_from_chip) { local_irq_save(irq_flag); if (sprdbat_cv_irq_dis) { sprdbat_cv_irq_dis = 0; enable_irq(sprdbat_data->irq_chg_cv_state); } local_irq_restore(irq_flag); } sprdbat_start_chg = 0; } sprdbat_temp_monitor(); if (sprdbat_data->bat_info.chg_stop_flags & SPRDBAT_CHG_END_TIMEOUT_BIT) { SPRDBAT_DEBUG("SPRDBAT_CHG_TIMEOUT_RESTART_E\n"); sprdbat_change_module_state(SPRDBAT_CHG_TIMEOUT_RESTART_E); } if (sprdbat_data->bat_info.chg_stop_flags == SPRDBAT_CHG_END_NONE_BIT) { #ifdef CHG_CUR_ADJUST sprdbat_auto_switch_cur(); #endif if (sprdbat_is_chg_timeout()) { SPRDBAT_DEBUG("chg timeout\n"); if (sprdbat_data->bat_info.vbat_ocv > sprdbat_data->pdata->rechg_vol) { sprdbat_change_module_state(SPRDBAT_CHG_FULL_E); } else { sprdbat_data->bat_info.chg_this_timeout = sprdbat_data->pdata->chg_rechg_timeout; sprdbat_change_module_state (SPRDBAT_CHG_TIMEOUT_E); } } #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG sprdbat_polling_chg_state(); #else if (sprdbat_cccv_cal_from_chip) { SPRDBAT_DEBUG("chg_end_vol_l:0x%x\n",sprdbat_data->pdata->chg_end_vol_l); if ((sprdbat_data->bat_info.vbat_vol > sprdbat_data->pdata->chg_end_vol_l || sprdchg_get_cccvstate()) && (sprdbat_data->bat_info.bat_current < sprdbat_data->pdata->chg_end_cur)) { sprdbat_trickle_chg = 0; SPRDBAT_DEBUG("SPRDBAT_CHG_FULL_E\n"); SPRDBAT_DEBUG("%s_1(): OHH, Cap will up to 100," "chg_end_cur = %d bat_current = %d " "vbat_vol = %d chg_end_vol_l = %d, cccvstate = %d\n", __func__, sprdbat_data->pdata->chg_end_cur, sprdbat_data->bat_info.bat_current, sprdbat_data->bat_info.vbat_vol, sprdbat_data->pdata->chg_end_vol_l, sprdchg_get_cccvstate()); sprdbat_change_module_state(SPRDBAT_CHG_FULL_E); } } else { if ((sprdbat_data->bat_info.vbat_vol > sprdbat_data->pdata->chg_end_vol_l || sprdbat_trickle_chg) && sprdbat_data->bat_info.bat_current < sprdbat_data->pdata->chg_end_cur) { sprdbat_trickle_chg = 0; SPRDBAT_DEBUG("SPRDBAT_CHG_FULL_E\n"); SPRDBAT_DEBUG("%s_2(): OHH, Cap will up to 100, " "chg_end_cur = %d bat_current = %d " "vbat_vol = %d chg_end_vol_l = %d\n", __func__, sprdbat_data->pdata->chg_end_cur, sprdbat_data->bat_info.bat_current, sprdbat_data->bat_info.vbat_vol, sprdbat_data->pdata->chg_end_vol_l); sprdbat_change_module_state(SPRDBAT_CHG_FULL_E); } } #endif //cccv point is high if (sprdbat_data->bat_info.vbat_vol > sprdbat_data->pdata->chg_end_vol_h) { SPRDBAT_DEBUG("cccv point is high\n"); sprdbat_adjust_cccvpoint(sprdbat_data-> bat_info.vbat_vol); } /*the purpose of this code is the same as cv irq handler, if cv irq is very accurately, this code can be deleted */ //cccv point is low cur = sprdchg_read_chg_current(); sprdchg_put_chgcur(cur); sprdbat_data->bat_info.chging_current = sprdchg_get_chgcur_ave(); SPRDBAT_DEBUG ("chg_cur: %d,chg_ave_current:%d\n", cur, sprdbat_data->bat_info.chging_current); sprdbat_average_cnt++; if (sprdbat_average_cnt == SPRDBAT_AVERAGE_COUNT) { uint32_t triggre_current = sprdbat_data->bat_info.chg_current_type * SPRDBAT_CV_TRIGGER_CURRENT; sprdbat_average_cnt = 0; if (sprdbat_data->bat_info.vbat_vol < sprdbat_data->pdata->chg_end_vol_pure && sprdbat_data->bat_info.chging_current < triggre_current) { SPRDBAT_DEBUG ("turn high cccv point vol:%d,chg_cur:%d,trigger_cur:%d", sprdbat_data->bat_info.vbat_vol, sprdbat_data->bat_info.chging_current, triggre_current); sprdbat_adjust_cccvpoint(sprdbat_data->bat_info. vbat_vol); } } } if (sprdbat_data->bat_info.chg_stop_flags & SPRDBAT_CHG_END_FULL_BIT) { if (sprdbat_data->bat_info.vbat_ocv < sprdbat_data->pdata->rechg_vol) { SPRDBAT_DEBUG("SPRDBAT_RECHARGE_E\n"); sprdbat_change_module_state(SPRDBAT_RECHARGE_E); } } #ifdef CONFIG_SPRD_NOFGUCURRENT_CHG if ((sprdbat_data->bat_info.chg_stop_flags == SPRDBAT_CHG_END_NONE_BIT)) { sprdchg_stop_charge(); sprdchg_timer_enable(sprdbat_data->pdata-> chg_polling_time_fast); chg_phy_dis_flag = 1; } else { sprdchg_timer_enable(sprdbat_data->pdata->chg_polling_time); } #endif mutex_unlock(&sprdbat_data->lock); sprdbat_chg_print_log(); } static int sprdbat_fgu_event(struct notifier_block *this, unsigned long event, void *ptr) { SPRDBAT_DEBUG("sprdbat_fgu_event---vol:%d\n", sprdbat_read_vbat_vol()); mutex_lock(&sprdbat_data->lock); sprdbat_adjust_cccvpoint(sprdbat_read_vbat_vol()); mutex_unlock(&sprdbat_data->lock); return 0; } //external charge ic code #ifdef SPRDBAT_TWO_CHARGE_CHANNEL static struct delayed_work sprdbat_charge_work_ext; static struct work_struct sprdbat_chg_detect_work; static uint32_t sprdbat_chg_online; static void sprdbat_chg_detect_works(struct work_struct *work) { if (sprdbat_chg_online) { SPRDBAT_DEBUG ("sprdbat_chg_detect_works plug in,vbus pull up\n"); plugin_callback(0, 0); } else { SPRDBAT_DEBUG ("sprdbat_chg_detect_works plug out,vbus pull down\n"); plugout_callback(0, 0); } } static irqreturn_t sprdbat_chg_detect_int(int irq, void *dev_id) { wake_lock_timeout(&(sprdbat_data->charger_plug_out_lock), SPRDBAT_PLUG_WAKELOCK_TIME_SEC * HZ); sprdbat_chg_online = gpio_get_value(sprdbat_data->gpio_charger_detect); SPRDBAT_DEBUG("sprdbat_chg_detect_int------sprdbat_chg_online:%d\n", sprdbat_chg_online); irq_set_irq_type(irq, sprdbat_chg_online ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH); queue_work(sprdbat_data->monitor_wqueue, &sprdbat_chg_detect_work); return IRQ_HANDLED; } static void sprdbat_charge_works_ext(struct work_struct *work) { SPRDBAT_DEBUG("sprdbat_charge_works_ext----------start\n"); sprdchg_chg_monitor_cb_ext(NULL); mutex_lock(&sprdbat_data->lock); if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_DISCHARGING) { mutex_unlock(&sprdbat_data->lock); return; } sprdbat_data->bat_info.vbat_vol = sprdbat_read_vbat_vol(); sprdbat_data->bat_info.vbat_ocv = sprdfgu_read_vbat_ocv(); sprdbat_data->bat_info.bat_current = sprdfgu_read_batcurrent(); SPRDBAT_DEBUG("sprdbat_charge_works----------vbat_vol %d,ocv:%d\n", sprdbat_data->bat_info.vbat_vol, sprdbat_data->bat_info.vbat_ocv); SPRDBAT_DEBUG("sprdbat_charge_works----------bat_current %d\n", sprdbat_data->bat_info.bat_current); sprdbat_temp_monitor(); if (sprdbat_data->bat_info.chg_stop_flags == SPRDBAT_CHG_END_NONE_BIT) { if (sprdbat_is_chg_timeout()) { SPRDBAT_DEBUG("chg timeout\n"); if (sprdbat_data->bat_info.vbat_ocv > sprdbat_data->pdata->rechg_vol) { sprdbat_change_module_state(SPRDBAT_CHG_FULL_E); } else { sprdbat_data->bat_info.chg_this_timeout = sprdbat_data->pdata->chg_rechg_timeout; sprdbat_change_module_state (SPRDBAT_CHG_TIMEOUT_E); } } if (sprdchg_is_chg_done_ext()) { SPRDBAT_DEBUG("SPRDBAT_CHG_FULL_E\n"); sprdbat_change_module_state(SPRDBAT_CHG_FULL_E); } } if (sprdbat_data->bat_info.chg_stop_flags & SPRDBAT_CHG_END_TIMEOUT_BIT) { SPRDBAT_DEBUG("SPRDBAT_CHG_TIMEOUT_RESTART_E\n"); sprdbat_change_module_state(SPRDBAT_CHG_TIMEOUT_RESTART_E); } if (sprdbat_data->bat_info.chg_stop_flags & SPRDBAT_CHG_END_FULL_BIT) { if (sprdbat_data->bat_info.vbat_ocv < sprdbat_data->pdata->rechg_vol) { SPRDBAT_DEBUG("SPRDBAT_RECHARGE_E\n"); sprdbat_change_module_state(SPRDBAT_RECHARGE_E); } } mutex_unlock(&sprdbat_data->lock); sprdbat_chg_print_log(); SPRDBAT_DEBUG("sprdbat_charge_works_ext----------end\n"); } void sprdbat_charge_event_ext(uint32_t event) { mutex_lock(&sprdbat_data->lock); if (sprdbat_data->bat_info.module_state == POWER_SUPPLY_STATUS_DISCHARGING || sprdbat_data->bat_info.ac_online) { mutex_unlock(&sprdbat_data->lock); return; } switch (event) { case SPRDBAT_CHG_EVENT_EXT_OVI: if (! (SPRDBAT_CHG_END_OVP_BIT & sprdbat_data->bat_info. chg_stop_flags)) { SPRDBAT_DEBUG("SPRDBAT_OVI_STOP_E\n"); sprdbat_change_module_state(SPRDBAT_OVI_STOP_E); } break; case SPRDBAT_CHG_EVENT_EXT_OVI_RESTART: if ((SPRDBAT_CHG_END_OVP_BIT & sprdbat_data->bat_info. chg_stop_flags)) { SPRDBAT_DEBUG("SPRDBAT_OVI_RESTART_E\n"); sprdbat_change_module_state(SPRDBAT_OVI_RESTART_E); } break; default: break; } mutex_unlock(&sprdbat_data->lock); } static int sprdbat_start_charge_ext(void) { struct timespec cur_time; get_monotonic_boottime(&cur_time); sprdbat_data->bat_info.chg_start_time = cur_time.tv_sec; sprdchg_start_charge_ext(); SPRDBAT_DEBUG ("sprdbat_start_charge bat_health:%d,chg_start_time:%ld,chg_current_type:%d\n", sprdbat_data->bat_info.bat_health, sprdbat_data->bat_info.chg_start_time, sprdbat_data->bat_info.chg_current_type); return 0; } static int sprdbat_stop_charge_ext(void) { sprdbat_data->bat_info.chg_start_time = 0; sprdchg_stop_charge_ext(); SPRDBAT_DEBUG("sprdbat_stop_charge\n"); return 0; } static int sprdbat_start_chg_process_ext(void) { sprdbat_data->bat_info.adp_type = ADP_TYPE_SDP; sprdbat_data->start_charge = sprdbat_start_charge_ext; sprdbat_data->stop_charge = sprdbat_stop_charge_ext; sprdbat_data->charge_work = &sprdbat_charge_work_ext; sprdchg_open_ovi_fun_ext(); sprdchg_timer_enable(sprdbat_data->pdata->chg_polling_time); sprdbat_change_module_state(SPRDBAT_ADP_PLUGIN_E); return 0; } static int sprdbat_stop_chg_process_ext(void) { sprdbat_data->bat_info.adp_type = ADP_TYPE_UNKNOW; sprdchg_close_ovi_fun_ext(); sprdchg_timer_disable(); sprdbat_change_module_state(SPRDBAT_ADP_PLUGOUT_E); return 0; } static int plugin_callback_ext(int usb_cable, void *data) { SPRDBAT_DEBUG("charger plugin_callback_ext happen\n"); wake_lock_timeout(&(sprdbat_data->charger_plug_out_lock), SPRDBAT_PLUG_WAKELOCK_TIME_SEC * HZ); mutex_lock(&sprdbat_data->lock); sprdbat_data->bat_info.usb_online = 1; if (!sprdbat_data->bat_info.ac_online) { sprdbat_adp_plug_nodify(1); sprdbat_start_chg_process_ext(); } mutex_unlock(&sprdbat_data->lock); power_supply_changed(&sprdbat_data->ac); power_supply_changed(&sprdbat_data->usb); SPRDBAT_DEBUG("plugin_callback_ext: end...\n"); return 0; } static int plugout_callback_ext(int usb_cable, void *data) { sprdbat_data->bat_info.usb_online = 0; SPRDBAT_DEBUG("charger plugout_callback_ext happen\n"); wake_lock_timeout(&(sprdbat_data->charger_plug_out_lock), SPRDBAT_PLUG_WAKELOCK_TIME_SEC * HZ); mutex_lock(&sprdbat_data->lock); if (!sprdbat_data->bat_info.ac_online) { sprdbat_adp_plug_nodify(0); sprdbat_stop_chg_process_ext(); } mutex_unlock(&sprdbat_data->lock); power_supply_changed(&sprdbat_data->ac); power_supply_changed(&sprdbat_data->usb); return 0; } #endif // static void print_pdata(struct sprd_battery_platform_data *pdata) { #define PDATA_LOG(format, arg...) printk("sprdbat pdata: " format, ## arg) int i; PDATA_LOG("chg_end_vol_h:%d\n", pdata->chg_end_vol_h); PDATA_LOG("chg_end_vol_l:%d\n", pdata->chg_end_vol_l); PDATA_LOG("chg_end_vol_pure:%d\n", pdata->chg_end_vol_pure); PDATA_LOG("chg_bat_safety_vol:%d\n", pdata->chg_bat_safety_vol); PDATA_LOG("rechg_vol:%d\n", pdata->rechg_vol); PDATA_LOG("adp_cdp_cur:%d\n", pdata->adp_cdp_cur); PDATA_LOG("adp_dcp_cur:%d\n", pdata->adp_dcp_cur); PDATA_LOG("adp_sdp_cur:%d\n", pdata->adp_sdp_cur); PDATA_LOG("ovp_stop:%d\n", pdata->ovp_stop); PDATA_LOG("ovp_restart:%d\n", pdata->ovp_restart); PDATA_LOG("chg_timeout:%d\n", pdata->chg_timeout); PDATA_LOG("chgtimeout_show_full:%d\n", pdata->chgtimeout_show_full); PDATA_LOG("chg_rechg_timeout:%d\n", pdata->chg_rechg_timeout); PDATA_LOG("chg_cv_timeout:%d\n", pdata->chg_cv_timeout); PDATA_LOG("chg_eoc_level:%d\n", pdata->chg_eoc_level); PDATA_LOG("cccv_default:%d\n", pdata->cccv_default); PDATA_LOG("chg_end_cur:%d\n", pdata->chg_end_cur); PDATA_LOG("otp_high_stop:%d\n", pdata->otp_high_stop); PDATA_LOG("otp_high_restart:%d\n", pdata->otp_high_restart); PDATA_LOG("otp_low_stop:%d\n", pdata->otp_low_stop); PDATA_LOG("otp_low_restart:%d\n", pdata->otp_low_restart); PDATA_LOG("chg_polling_time:%d\n", pdata->chg_polling_time); PDATA_LOG("chg_polling_time_fast:%d\n", pdata->chg_polling_time_fast); PDATA_LOG("bat_polling_time:%d\n", pdata->bat_polling_time); PDATA_LOG("bat_polling_time_fast:%d\n", pdata->bat_polling_time_fast); PDATA_LOG("bat_polling_time_sleep:%d\n", pdata->bat_polling_time_sleep); PDATA_LOG("cap_one_per_time:%d\n", pdata->cap_one_per_time); PDATA_LOG("cap_one_per_time_fast:%d\n", pdata->cap_one_per_time_fast); PDATA_LOG("cap_valid_range_poweron:%d\n", pdata->cap_valid_range_poweron); PDATA_LOG("temp_support:%d\n", pdata->temp_support); PDATA_LOG("temp_adc_ch:%d\n", pdata->temp_adc_ch); PDATA_LOG("temp_adc_scale:%d\n", pdata->temp_adc_scale); PDATA_LOG("temp_adc_sample_cnt:%d\n", pdata->temp_adc_sample_cnt); PDATA_LOG("temp_table_mode:%d\n", pdata->temp_table_mode); PDATA_LOG("temp_comp_res:%d\n", pdata->temp_comp_res); PDATA_LOG("temp_tab_size:%d\n", pdata->temp_tab_size); PDATA_LOG("gpio_vchg_detect:%d\n", pdata->gpio_vchg_detect); PDATA_LOG("gpio_cv_state:%d\n", pdata->gpio_cv_state); PDATA_LOG("gpio_vchg_ovi:%d\n", pdata->gpio_vchg_ovi); PDATA_LOG("irq_chg_timer:%d\n", pdata->irq_chg_timer); PDATA_LOG("irq_fgu:%d\n", pdata->irq_fgu); PDATA_LOG("chg_reg_base:%d\n", pdata->chg_reg_base); PDATA_LOG("fgu_reg_base:%d\n", pdata->fgu_reg_base); PDATA_LOG("fgu_mode:%d\n", pdata->fgu_mode); PDATA_LOG("alm_soc:%d\n", pdata->alm_soc); PDATA_LOG("alm_vol:%d\n", pdata->alm_vol); PDATA_LOG("soft_vbat_uvlo:%d\n", pdata->soft_vbat_uvlo); PDATA_LOG("soft_vbat_ovp:%d\n", pdata->soft_vbat_ovp); PDATA_LOG("rint:%d\n", pdata->rint); PDATA_LOG("cnom:%d\n", pdata->cnom); PDATA_LOG("rsense_real:%d\n", pdata->rsense_real); PDATA_LOG("rsense_spec:%d\n", pdata->rsense_spec); PDATA_LOG("relax_current:%d\n", pdata->relax_current); PDATA_LOG("fgu_cal_ajust:%d\n", pdata->fgu_cal_ajust); PDATA_LOG("qmax_update_period:%d\n", pdata->qmax_update_period); PDATA_LOG("ocv_tab_size:%d\n", pdata->ocv_tab_size); for (i = 0; i < pdata->ocv_tab_size; i++) { PDATA_LOG("ocv_tab i=%d x:%d,y:%d\n", i, pdata->ocv_tab[i].x, pdata->ocv_tab[i].y); } for (i = 0; i < pdata->temp_tab_size; i++) { PDATA_LOG("temp_tab_size i=%d x:%d,y:%d\n", i, pdata->temp_tab[i].x, pdata->temp_tab[i].y); } for (i = 0; i < pdata->cnom_temp_tab_size; i++) { PDATA_LOG("cnom_temp_tab i=%d x:%d,y:%d\n", i, pdata->cnom_temp_tab[i].x, pdata->cnom_temp_tab[i].y); } for (i = 0; i < pdata->rint_temp_tab_size; i++) { PDATA_LOG("rint_temp_tab i=%d x:%d,y:%d\n", i, pdata->rint_temp_tab[i].x, pdata->rint_temp_tab[i].y); } } #ifdef CONFIG_OF static struct sprd_battery_platform_data *sprdbat_parse_dt(struct platform_device *pdev) { struct sprd_battery_platform_data *pdata = NULL; struct device_node *np = pdev->dev.of_node; struct device_node *temp_np = NULL; unsigned int irq_num; int ret, i, temp,len; pdata = devm_kzalloc(&pdev->dev, sizeof(struct sprd_battery_platform_data), GFP_KERNEL); if (!pdata) { return NULL; } pdata->gpio_vchg_detect = (uint32_t) of_get_named_gpio(np, "gpios", 0); pdata->gpio_cv_state = (uint32_t) of_get_named_gpio(np, "gpios", 1); pdata->gpio_vchg_ovi = (uint32_t) of_get_named_gpio(np, "gpios", 2); temp = (uint32_t) of_get_named_gpio(np, "gpios", 3); if (gpio_is_valid(temp)) { pdata->gpio_vbat_detect = (uint32_t) temp; printk("sprdbat:gpio_vbat_detect support =%d\n", pdata->gpio_vbat_detect); } else { pdata->gpio_vbat_detect = 0; printk("sprdbat:gpio_vbat_detect do Not support =%d\n", pdata->gpio_vbat_detect); } temp_np = of_get_child_by_name(np, "sprd_chg"); if (!temp_np) { goto err_parse_dt; } irq_num = irq_of_parse_and_map(temp_np, 0); pdata->irq_chg_timer = irq_num; printk("sprd_chg dts irq_num =%s, %d\n", temp_np->name, irq_num); temp_np = of_get_child_by_name(np, "sprd_fgu"); if (!temp_np) { goto err_parse_dt; } irq_num = irq_of_parse_and_map(temp_np, 0); pdata->irq_fgu = irq_num; printk("sprd_fgu dts irq_num = %d\n", irq_num); ret = of_property_read_u32(np, "chg-end-vol-h", &pdata->chg_end_vol_h); ret = of_property_read_u32(np, "chg-end-vol-l", &pdata->chg_end_vol_l); ret = of_property_read_u32(np, "chg-false-full-vol", &pdata->chg_false_full_vol); ret = of_property_read_u32(np, "chg-end-vol-pure", &pdata->chg_end_vol_pure); ret = of_property_read_u32(np, "chg-bat-safety-vol", &pdata->chg_bat_safety_vol); ret = of_property_read_u32(np, "rechg-vol", &pdata->rechg_vol); ret = of_property_read_u32(np, "adp-cdp-cur", &pdata->adp_cdp_cur); ret = of_property_read_u32(np, "adp-dcp-cur", &pdata->adp_dcp_cur); ret = of_property_read_u32(np, "adp-sdp-cur", &pdata->adp_sdp_cur); ret = of_property_read_u32(np, "ovp-stop", &pdata->ovp_stop); ret = of_property_read_u32(np, "ovp-restart", &pdata->ovp_restart); ret = of_property_read_u32(np, "chg-timeout", &pdata->chg_timeout); ret = of_property_read_u32(np, "chgtimeout-show-full", &pdata->chgtimeout_show_full); ret = of_property_read_u32(np, "chg-rechg-timeout", &pdata->chg_rechg_timeout); ret = of_property_read_u32(np, "chg-cv-timeout", &pdata->chg_cv_timeout); ret = of_property_read_u32(np, "chg-eoc-level", &pdata->chg_eoc_level); ret = of_property_read_u32(np, "cccv-default", &pdata->cccv_default); ret = of_property_read_u32(np, "chg-end-cur", (u32 *) (&pdata->chg_end_cur)); ret = of_property_read_u32(np, "otp-high-stop", (u32 *) (&temp)); pdata->otp_high_stop = temp - 1000; ret = of_property_read_u32(np, "otp-high-restart", (u32 *) (&temp)); pdata->otp_high_restart = temp - 1000; ret = of_property_read_u32(np, "otp-low-stop", (u32 *) (&temp)); pdata->otp_low_stop = temp - 1000; ret = of_property_read_u32(np, "otp-low-restart", (u32 *) (&temp)); pdata->otp_low_restart = temp - 1000; ret = of_property_read_u32(np, "chg-polling-time", &pdata->chg_polling_time); ret = of_property_read_u32(np, "chg-polling-time-fast", &pdata->chg_polling_time_fast); ret = of_property_read_u32(np, "bat-polling-time", &pdata->bat_polling_time); ret = of_property_read_u32(np, "bat-polling-time-fast", &pdata->bat_polling_time_fast); ret = of_property_read_u32(np, "cap-one-per-time", &pdata->cap_one_per_time); ret = of_property_read_u32(np, "cap-valid-range-poweron", (u32 *) (&pdata->cap_valid_range_poweron)); ret = of_property_read_u32(np, "temp-support", (u32 *) (&pdata->temp_support)); ret = of_property_read_u32(np, "temp-adc-ch", (u32 *) (&pdata->temp_adc_ch)); ret = of_property_read_u32(np, "temp-adc-scale", (u32 *) (&pdata->temp_adc_scale)); ret = of_property_read_u32(np, "temp-adc-sample-cnt", (u32 *) (&pdata->temp_adc_sample_cnt)); ret = of_property_read_u32(np, "temp-table-mode", (u32 *) (&pdata->temp_table_mode)); ret = of_property_read_u32(np, "temp-comp-res", (u32 *) (&pdata->temp_comp_res)); ret = of_property_read_u32(np, "fgu-mode", &pdata->fgu_mode); ret = of_property_read_u32(np, "alm-soc", &pdata->alm_soc); ret = of_property_read_u32(np, "alm-vol", &pdata->alm_vol); ret = of_property_read_u32(np, "soft-vbat-uvlo", &pdata->soft_vbat_uvlo); ret = of_property_read_u32(np, "rint", (u32 *) (&pdata->rint)); ret = of_property_read_u32(np, "cnom", (u32 *) (&pdata->cnom)); ret = of_property_read_u32(np, "rsense-real", (u32 *) (&pdata->rsense_real)); ret = of_property_read_u32(np, "rsense-spec", (u32 *) (&pdata->rsense_spec)); ret = of_property_read_u32(np, "relax-current", &pdata->relax_current); ret = of_property_read_u32(np, "fgu-cal-ajust", (u32 *) (&pdata->fgu_cal_ajust)); ret = of_property_read_u32(np, "ocv-tab-size", (u32 *) (&pdata->ocv_tab_size)); ret = of_property_read_u32(np, "temp-tab-size", (u32 *) (&pdata->temp_tab_size)); pdata->temp_tab = kzalloc(sizeof(struct sprdbat_table_data) * pdata->temp_tab_size, GFP_KERNEL); for (i = 0; i < pdata->temp_tab_size; i++) { ret = of_property_read_u32_index(np, "temp-tab-val", i, &pdata->temp_tab[i].x); ret = of_property_read_u32_index(np, "temp-tab-temp", i, &temp); pdata->temp_tab[i].y = temp - 1000; } pdata->ocv_tab = kzalloc(sizeof(struct sprdbat_table_data) * pdata->ocv_tab_size, GFP_KERNEL); for (i = 0; i < pdata->ocv_tab_size; i++) { ret = of_property_read_u32_index(np, "ocv-tab-vol", i, (u32 *) (&pdata->ocv_tab[i]. x)); ret = of_property_read_u32_index(np, "ocv-tab-cap", i, (u32 *) (&pdata->ocv_tab[i].y)); } of_get_property(np, "cnom-temp-tab", &len); len /= sizeof(u32); pdata->cnom_temp_tab_size = len >> 1; pdata->cnom_temp_tab = kzalloc(sizeof(struct sprdbat_table_data) * pdata->cnom_temp_tab_size, GFP_KERNEL); for (i = 0; i < len; i++) { of_property_read_u32_index(np, "cnom-temp-tab", i, &temp); if(i&0x1) { pdata->cnom_temp_tab[i >> 1].y = temp; } else { pdata->cnom_temp_tab[i >> 1].x = temp - 1000; } } of_get_property(np, "rint-temp-tab", &len); len /= sizeof(u32); pdata->rint_temp_tab_size = len >> 1; pdata->rint_temp_tab = kzalloc(sizeof(struct sprdbat_table_data) * pdata->rint_temp_tab_size, GFP_KERNEL); for (i = 0; i < len; i++) { of_property_read_u32_index(np, "rint-temp-tab", i, &temp); if(i&0x1) { pdata->rint_temp_tab[i >> 1].y = temp; } else { pdata->rint_temp_tab[i >> 1].x = temp - 1000; } } return pdata; err_parse_dt: dev_err(&pdev->dev, "Parsing device tree data error.\n"); return NULL; } #else static struct sprd_battery_platform_data *sprdbat_parse_dt(struct platform_device *pdev) { return NULL; } #endif int __weak usb_register_hotplug_callback(struct usb_hotplug_callback *p){} static int sprdbat_probe(struct platform_device *pdev) { int ret = -ENODEV; struct sprdbat_drivier_data *data; #ifdef SPRDBAT_TWO_CHARGE_CHANNEL struct resource *res = NULL; #endif struct device_node *np = pdev->dev.of_node; SPRDBAT_DEBUG("sprdbat_probe\n"); #ifdef CONFIG_OF if (!np) { dev_err(&pdev->dev, "device node not found\n"); return -EINVAL; } #endif data = kzalloc(sizeof(*data), GFP_KERNEL); if (data == NULL) { ret = -ENOMEM; goto err_data_alloc_failed; } if (np) { data->pdata = sprdbat_parse_dt(pdev); } else { data->pdata = dev_get_platdata(&pdev->dev); } data->dev = &pdev->dev; platform_set_drvdata(pdev, data); sprdbat_data = data; print_pdata(sprdbat_data->pdata); data->battery.properties = sprdbat_battery_props; data->battery.num_properties = ARRAY_SIZE(sprdbat_battery_props); data->battery.get_property = sprdbat_battery_get_property; data->battery.name = "battery"; data->battery.type = POWER_SUPPLY_TYPE_BATTERY; data->battery.supplied_to = battery_supply_list; data->battery.num_supplicants = ARRAY_SIZE(battery_supply_list); data->ac.properties = sprdbat_ac_props; data->ac.num_properties = ARRAY_SIZE(sprdbat_ac_props); data->ac.get_property = sprdbat_ac_get_property; data->ac.name = "ac"; data->ac.type = POWER_SUPPLY_TYPE_MAINS; data->ac.supplied_to = supply_list; data->ac.num_supplicants = ARRAY_SIZE(supply_list); data->usb.properties = sprdbat_usb_props; data->usb.num_properties = ARRAY_SIZE(sprdbat_usb_props); data->usb.get_property = sprdbat_usb_get_property; data->usb.name = "usb"; data->usb.type = POWER_SUPPLY_TYPE_USB; data->usb.supplied_to = supply_list; data->usb.num_supplicants = ARRAY_SIZE(supply_list); data->start_charge = sprdbat_start_charge; data->stop_charge = sprdbat_stop_charge; ret = power_supply_register(&pdev->dev, &data->usb); if (ret) goto err_usb_failed; ret = power_supply_register(&pdev->dev, &data->ac); if (ret) goto err_ac_failed; ret = power_supply_register(&pdev->dev, &data->battery); if (ret) goto err_battery_failed; sprdbat_creat_caliberate_attr(data->battery.dev); data->gpio_chg_cv_state = data->pdata->gpio_cv_state; ret = gpio_request(data->gpio_chg_cv_state, "chg_cv_state"); if (ret) { dev_err(&pdev->dev, "failed to request gpio: %d\n", ret); goto err_io_cv_request; } gpio_direction_input(data->gpio_chg_cv_state); data->irq_chg_cv_state = gpio_to_irq(data->gpio_chg_cv_state); irq_set_status_flags(data->irq_chg_cv_state, IRQ_NOAUTOEN); //irq_set_irq_type(data->irq_chg_cv_state, IRQ_TYPE_LEVEL_HIGH); ret = request_irq(data->irq_chg_cv_state, sprdbat_chg_cv_irq, IRQF_NO_SUSPEND, "sprdbat_chg_cv_state", data); if (ret) goto err_request_irq_cv_failed; data->gpio_vchg_ovi = data->pdata->gpio_vchg_ovi; ret = gpio_request(data->gpio_vchg_ovi, "vchg_ovi"); if (ret) { dev_err(&pdev->dev, "failed to request gpio: %d\n", ret); goto err_io_ovi_request; } gpio_direction_input(data->gpio_vchg_ovi); data->irq_vchg_ovi = gpio_to_irq(data->gpio_vchg_ovi); irq_set_status_flags(data->irq_vchg_ovi, IRQ_NOAUTOEN); ret = request_irq(data->irq_vchg_ovi, sprdbat_vchg_ovi_irq, IRQF_NO_SUSPEND, "sprdbat_vchg_ovi", data); if (ret) goto err_request_irq_ovi_failed; data->gpio_vbat_detect = data->pdata->gpio_vbat_detect; if (data->gpio_vbat_detect > 0) { gpio_request(data->gpio_vbat_detect, "vbat_detect"); gpio_direction_input(data->gpio_vbat_detect); data->irq_vbat_detect = gpio_to_irq(data->gpio_vbat_detect); //set_irq_flags(data->irq_vbat_detect, // IRQF_VALID | IRQF_NOAUTOEN); irq_set_status_flags(data->irq_vbat_detect, IRQ_NOAUTOEN); ret = request_irq(data->irq_vbat_detect, sprdbat_vbat_detect_irq, IRQ_TYPE_LEVEL_LOW | IRQF_NO_SUSPEND, "sprdbat_vbat_detect", data); if (ret) dev_err(&pdev->dev, "failed to use vbat gpio: %d\n", ret); } mutex_init(&data->lock); wake_lock_init(&(data->charger_plug_out_lock), WAKE_LOCK_SUSPEND, "charger_plug_out_lock"); INIT_DELAYED_WORK(&data->cv_irq_work, sprdbat_cv_irq_works); INIT_DELAYED_WORK(&data->battery_work, sprdbat_battery_works); INIT_DELAYED_WORK(&data->battery_sleep_work, sprdbat_battery_sleep_works); INIT_WORK(&data->ovi_irq_work, sprdbat_ovi_irq_works); INIT_WORK(&data->vbat_detect_irq_work, sprdbat_vbat_detect_irq_works); INIT_DELAYED_WORK(&sprdbat_charge_work, sprdbat_charge_works); data->charge_work = &sprdbat_charge_work; data->monitor_wqueue = create_singlethread_workqueue("sprdbat_monitor"); sprdchg_timer_init(sprdbat_timer_handler, data); sprdchg_set_chg_ovp(data->pdata->ovp_stop); sprdchg_init(data->pdata); sprdfgu_init(data->pdata); #ifdef CONFIG_LEDS_TRIGGERS data->charging_led.name = "sprdbat_charging_led"; data->charging_led.default_trigger = "battery-charging"; data->charging_led.brightness_set = sprdchg_led_brightness_set; led_classdev_register(&pdev->dev, &data->charging_led); #endif #ifdef SPRDBAT_TWO_CHARGE_CHANNEL sprdchg_charge_init_ext(pdev); INIT_DELAYED_WORK(&sprdbat_charge_work_ext, sprdbat_charge_works_ext); #ifndef CONFIG_OF res = platform_get_resource(pdev, IORESOURCE_IO, 0); data->gpio_charger_detect = res->start; #else data->gpio_charger_detect = of_get_named_gpio(np, "gpios", 0); #endif gpio_request(data->gpio_charger_detect, "sprd_charger_detect"); gpio_direction_input(data->gpio_charger_detect); data->irq_charger_detect = gpio_to_irq(data->gpio_charger_detect); ret = request_irq(data->irq_charger_detect, sprdbat_chg_detect_int, IRQF_SHARED | IRQF_TRIGGER_HIGH, "sprdbat_charger_detect", data); INIT_WORK(&sprdbat_chg_detect_work, sprdbat_chg_detect_works); #endif sprdbat_info_init(data); if (data->gpio_vbat_detect > 0) { enable_irq(sprdbat_data->irq_vbat_detect); } sprdbat_notifier.notifier_call = sprdbat_fgu_event; sprdfgu_register_notifier(&sprdbat_notifier); usb_register_hotplug_callback(&power_cb); schedule_delayed_work(&data->battery_work, sprdbat_data->pdata->bat_polling_time * HZ); #ifdef CHG_CUR_ADJUST #if 0 thermal_cooling_device_register("charge", 0, &chg_cooling_ops); #endif #endif SPRDBAT_DEBUG("sprdbat_probe----------end\n"); return 0; err_request_irq_ovi_failed: gpio_free(data->gpio_vchg_ovi); err_io_ovi_request: free_irq(data->irq_chg_cv_state, data); err_request_irq_cv_failed: gpio_free(data->gpio_chg_cv_state); err_io_cv_request: power_supply_unregister(&data->battery); err_battery_failed: power_supply_unregister(&data->ac); err_ac_failed: power_supply_unregister(&data->usb); err_usb_failed: kfree(data); err_data_alloc_failed: sprdbat_data = NULL; return ret; } static int sprdbat_remove(struct platform_device *pdev) { struct sprdbat_drivier_data *data = platform_get_drvdata(pdev); sprdbat_remove_caliberate_attr(data->battery.dev); power_supply_unregister(&data->battery); power_supply_unregister(&data->ac); power_supply_unregister(&data->usb); free_irq(data->irq_vchg_ovi, data); free_irq(data->irq_chg_cv_state, data); gpio_free(data->gpio_vchg_ovi); gpio_free(data->gpio_chg_cv_state); kfree(data); sprdbat_data = NULL; return 0; } static int sprdbat_resume(struct platform_device *pdev) { schedule_delayed_work(&sprdbat_data->battery_sleep_work, 0); sprdfgu_pm_op(0); return 0; } static int sprdbat_suspend(struct platform_device *pdev, pm_message_t state) { sprdfgu_pm_op(1); return 0; } #ifdef CONFIG_OF static const struct of_device_id battery_of_match[] = { {.compatible = "sprd,sprd-battery",}, {} }; #endif static struct platform_driver sprdbat_driver = { .probe = sprdbat_probe, .remove = sprdbat_remove, .suspend = sprdbat_suspend, .resume = sprdbat_resume, .driver = { .name = "sprd-battery", #ifdef CONFIG_OF .of_match_table = of_match_ptr(battery_of_match), #endif } }; static int __init sprd_battery_init(void) { return platform_driver_register(&sprdbat_driver); } static void __exit sprd_battery_exit(void) { platform_driver_unregister(&sprdbat_driver); } module_init(sprd_battery_init); module_exit(sprd_battery_exit); MODULE_AUTHOR("Mingwei.Zhang@spreadtrum.com"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Battery and charger driver for SC2713");