| /* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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 <linux/delay.h> |
| #include <linux/fb.h> |
| #include <linux/gpio.h> |
| #include <linux/module.h> |
| #include <linux/notifier.h> |
| #include <linux/of.h> |
| #include <linux/of_batterydata.h> |
| #include <linux/of_gpio.h> |
| #include <linux/platform_device.h> |
| #include <linux/power_supply.h> |
| #include <linux/qpnp/qpnp-adc.h> |
| #include <linux/reboot.h> |
| #include <linux/rtc.h> |
| #include <linux/wakelock.h> |
| #include <linux/workqueue.h> |
| |
| #define HTC_BATT_NAME "htc_battery" |
| |
| /* |
| * Protects access to the following static data: |
| * |
| * htc_batt_info |
| * htc_batt_timer |
| * g_htc_battery_probe_done |
| */ |
| DEFINE_MUTEX(htc_battery_lock); |
| |
| static int full_level_dis_chg = 100; |
| module_param_named( |
| full_level_dis_chg, full_level_dis_chg, int, S_IRUSR | S_IWUSR |
| ); |
| |
| #define BATT_LOG(x...) pr_info("[BATT] " x) |
| |
| #define BATT_DEBUG(x...) do { \ |
| if (g_flag_enable_batt_debug_log) \ |
| pr_info("[BATT] " x); \ |
| else \ |
| pr_debug("[BATT] " x); \ |
| } while (0) |
| |
| #define BATT_ERR(x...) do { \ |
| struct timespec _ts; \ |
| struct rtc_time _tm; \ |
| getnstimeofday(&_ts); \ |
| rtc_time_to_tm(_ts.tv_sec, &_tm); \ |
| pr_err("[BATT] err:" x); \ |
| pr_err("[BATT] at %lld (%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", \ |
| ktime_to_ns(ktime_get()), \ |
| _tm.tm_year + 1900, _tm.tm_mon + 1, _tm.tm_mday, \ |
| _tm.tm_hour, _tm.tm_min, _tm.tm_sec, _ts.tv_nsec); \ |
| } while (0) |
| |
| #define BATT_EMBEDDED(x...) do { \ |
| struct timespec _ts; \ |
| struct rtc_time _tm; \ |
| getnstimeofday(&_ts); \ |
| rtc_time_to_tm(_ts.tv_sec, &_tm); \ |
| pr_info("[BATT] at %lld (%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", \ |
| ktime_to_ns(ktime_get()), \ |
| _tm.tm_year + 1900, _tm.tm_mon + 1, _tm.tm_mday, \ |
| _tm.tm_hour, _tm.tm_min, _tm.tm_sec, _ts.tv_nsec); \ |
| pr_info("[BATT] :" x); \ |
| } while (0) |
| |
| struct battery_info_reply { |
| u32 batt_vol; |
| u32 batt_id; |
| s32 batt_temp; |
| s32 batt_current; |
| u32 charging_source; |
| u32 level; |
| u32 level_raw; |
| u32 full_level; |
| u32 status; |
| u32 chg_src; |
| u32 chg_en; |
| u32 chg_batt_en; |
| u32 full_level_dis_batt_chg; |
| u32 overload; |
| u32 over_vchg; |
| u32 health; |
| bool is_full; |
| }; |
| |
| struct battery_info_previous { |
| s32 batt_temp; |
| u32 charging_source; |
| u32 level; |
| u32 level_raw; |
| }; |
| |
| struct htc_thermal_stage { |
| int next_temp; |
| int recover_temp; |
| }; |
| |
| struct htc_battery_info { |
| struct battery_info_reply rep; |
| struct battery_info_previous prev; |
| struct htc_charger *icharger; |
| struct power_supply *batt_psy; |
| struct power_supply *bms_psy; |
| struct power_supply *usb_psy; |
| int critical_low_voltage_mv; |
| int smooth_chg_full_delay_min; |
| int decreased_batt_level_check; |
| int batt_full_voltage_mv; |
| int batt_full_current_ma; |
| int overload_curr_thr_ma; |
| struct wake_lock charger_exist_lock; |
| struct work_struct batt_update_work; |
| int state; |
| int vbus; |
| int batt_fcc_ma; |
| int batt_capacity_mah; |
| int batt_thermal_limit_vol; |
| int v_elvdd_dis_en; |
| struct notifier_block fb_notif; |
| struct notifier_block htc_batt_cb; |
| }; |
| |
| struct htc_battery_timer { |
| unsigned long batt_system_jiffies; |
| unsigned long batt_suspend_ms; |
| unsigned long total_time_ms; /* since last do batt_work */ |
| struct work_struct batt_work; |
| struct timer_list batt_timer; |
| struct workqueue_struct *batt_wq; |
| unsigned int time_out; |
| }; |
| |
| static struct htc_battery_info htc_batt_info; |
| static struct htc_battery_timer htc_batt_timer; |
| |
| enum { |
| BATT_ID_1 = 1, |
| BATT_ID_2, |
| BATT_ID_UNKNOWN = 255, |
| }; |
| |
| #define BATT_SUSPEND_CHECK_TIME (3600) |
| #define BATT_SUSPEND_HIGHFREQ_CHECK_TIME (300) |
| #define BATT_TIMER_CHECK_TIME (360) |
| #define BATT_TIMER_UPDATE_TIME (60) |
| #define CHECH_TIME_TOLERANCE_MS (1000) |
| |
| /* disable pwrsrc reason */ |
| #define HTC_BATT_PWRSRC_DIS_BIT_MFG BIT(0) |
| #define HTC_BATT_PWRSRC_DIS_BIT_API BIT(1) |
| #define HTC_BATT_PWRSRC_DIS_BIT_USB_OVERHEAT BIT(2) |
| #define HTC_BATT_PWRSRC_DIS_BIT_FTM BIT(3) |
| static int g_pwrsrc_dis_reason; |
| |
| /* disable charging reason */ |
| #define HTC_BATT_CHG_DIS_BIT_EOC BIT(0) |
| #define HTC_BATT_CHG_DIS_BIT_ID BIT(1) |
| #define HTC_BATT_CHG_DIS_BIT_TMP BIT(2) |
| #define HTC_BATT_CHG_DIS_BIT_OVP BIT(3) |
| #define HTC_BATT_CHG_DIS_BIT_TMR BIT(4) |
| #define HTC_BATT_CHG_DIS_BIT_MFG BIT(5) |
| #define HTC_BATT_CHG_DIS_BIT_USR_TMR BIT(6) |
| #define HTC_BATT_CHG_DIS_BIT_STOP_SWOLLEN BIT(7) |
| #define HTC_BATT_CHG_DIS_BIT_USB_OVERHEAT BIT(8) |
| #define HTC_BATT_CHG_DIS_BIT_FTM BIT(9) |
| static int g_chg_dis_reason; |
| |
| /* for htc_batt_info.state */ |
| #define STATE_SCREEN_OFF (1) |
| |
| /* Set true when all battery need file probe done */ |
| static bool g_htc_battery_probe_done; |
| |
| /* Enable batterydebug log*/ |
| static bool g_flag_enable_batt_debug_log; |
| |
| static int gs_prev_charging_enabled; |
| |
| /* reference from power_supply.h power_supply_type */ |
| const char *g_chr_src[] = { |
| "NONE", "BATTERY", "UPS", "MAINS", "USB", "AC(USB_DCP)", |
| "USB_CDP", "USB_ACA", "USB_HVDCP", "USB_HVDCP_3", "USB_PD", |
| "WIRELESS", "BMS", "USB_PARALLEL", "WIPOWER", "TYPEC", "UFP", "DFP"}; |
| |
| /* accesses htc_batt_timer, needs htc_battery_lock */ |
| static void batt_set_check_timer(u32 seconds) |
| { |
| pr_debug("[BATT] %s(%u sec)\n", __func__, seconds); |
| mod_timer(&htc_batt_timer.batt_timer, |
| jiffies + msecs_to_jiffies(seconds * 1000)); |
| } |
| |
| static int get_property(struct power_supply *psy, |
| enum power_supply_property prop) |
| { |
| union power_supply_propval ret = {0, }; |
| int rc = 0; |
| |
| if (!g_htc_battery_probe_done) |
| return -EINVAL; |
| |
| if (psy) { |
| rc = power_supply_get_property(psy, prop, &ret); |
| if (rc) { |
| pr_err("[BATT] failed to retrieve value(%d) rc=%d\n", |
| prop, rc); |
| return -EINVAL; |
| } |
| } else { |
| pr_err("[BATT] psy is null.\n"); |
| return -EINVAL; |
| } |
| |
| return ret.intval; |
| } |
| |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| static int set_batt_psy_property(enum power_supply_property prop, int value) |
| { |
| union power_supply_propval ret = {0, }; |
| int rc = -1; |
| |
| if (htc_batt_info.batt_psy) { |
| BATT_EMBEDDED("%s value(%d) prop(%d)", |
| __func__, value, prop); |
| ret.intval = value; |
| rc = power_supply_set_property(htc_batt_info.batt_psy, |
| prop, &ret); |
| } |
| |
| return rc; |
| } |
| |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| static void batt_update_info_from_charger(void) |
| { |
| htc_batt_info.prev.batt_temp = htc_batt_info.rep.batt_temp; |
| |
| htc_batt_info.rep.batt_current = |
| get_property(htc_batt_info.bms_psy, |
| POWER_SUPPLY_PROP_CURRENT_NOW); |
| |
| htc_batt_info.rep.batt_vol = |
| get_property(htc_batt_info.bms_psy, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW) / 1000; |
| |
| htc_batt_info.rep.batt_temp = |
| get_property(htc_batt_info.bms_psy, POWER_SUPPLY_PROP_TEMP); |
| |
| htc_batt_info.vbus = |
| get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW); |
| |
| htc_batt_info.rep.status = |
| get_property(htc_batt_info.batt_psy, POWER_SUPPLY_PROP_STATUS); |
| |
| htc_batt_info.rep.health = |
| get_property(htc_batt_info.batt_psy, POWER_SUPPLY_PROP_HEALTH); |
| } |
| |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| static void batt_update_info_from_gauge(void) |
| { |
| htc_batt_info.rep.level = get_property(htc_batt_info.batt_psy, |
| POWER_SUPPLY_PROP_CAPACITY); |
| } |
| |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| static int is_bounding_fully_charged_level(void) |
| { |
| static int s_pingpong = 1; |
| int is_batt_chg_off_by_bounding = 0; |
| int upperbd = htc_batt_info.rep.full_level; |
| int current_level = htc_batt_info.rep.level; |
| /* Default 5% range */ |
| int lowerbd = upperbd - 5; |
| |
| if ((htc_batt_info.rep.full_level > 0) && |
| (htc_batt_info.rep.full_level < 100)) { |
| if (lowerbd < 0) |
| lowerbd = 0; |
| |
| if (s_pingpong == 1 && upperbd <= current_level) { |
| BATT_LOG( |
| "%s: lowerbd=%d, upperbd=%d, current=%d, pingpong:1->0 turn off\n", |
| __func__, lowerbd, upperbd, current_level); |
| is_batt_chg_off_by_bounding = 1; |
| s_pingpong = 0; |
| } else if (s_pingpong == 0 && lowerbd < current_level) { |
| BATT_LOG( |
| "%s: lowerbd=%d, upperbd=%d, current=%d, toward 0, turn off\n", |
| __func__, lowerbd, upperbd, current_level); |
| is_batt_chg_off_by_bounding = 1; |
| } else if (s_pingpong == 0 && current_level <= lowerbd) { |
| BATT_LOG( |
| "%s: lowerbd=%d, upperbd=%d, current=%d, pingpong:0->1 turn on\n", |
| __func__, lowerbd, upperbd, current_level); |
| s_pingpong = 1; |
| } else { |
| BATT_LOG( |
| "%s: lowerbd=%d, upperbd=%d, current=%d, toward %d, turn on\n", |
| __func__, lowerbd, upperbd, current_level, |
| s_pingpong); |
| } |
| } |
| |
| return is_batt_chg_off_by_bounding; |
| } |
| |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| void update_htc_chg_src(void) |
| { |
| /* In bootable/offmode_charging/offmode_charging.c |
| * The charging_source type is set as below, |
| CHARGER_BATTERY = 0, |
| CHARGER_USB, |
| CHARGER_AC, |
| CHARGER_9VAC, |
| CHARGER_WIRELESS, |
| CHARGER_MHL_AC, |
| CHARGER_DETECTING, |
| CHARGER_UNKNOWN_USB, |
| CHARGER_PQM_FASTCHARGE, |
| |
| */ |
| int chg_src = 0; |
| |
| switch (htc_batt_info.rep.charging_source) { |
| case POWER_SUPPLY_TYPE_UNKNOWN: |
| case POWER_SUPPLY_TYPE_BATTERY: |
| chg_src = 0; |
| break; |
| case POWER_SUPPLY_TYPE_USB: |
| case POWER_SUPPLY_TYPE_USB_CDP: |
| chg_src = 1; /* USB */ |
| break; |
| case POWER_SUPPLY_TYPE_USB_DCP: |
| case POWER_SUPPLY_TYPE_USB_ACA: |
| case POWER_SUPPLY_TYPE_USB_HVDCP: |
| case POWER_SUPPLY_TYPE_USB_HVDCP_3: |
| case POWER_SUPPLY_TYPE_USB_PD: |
| case POWER_SUPPLY_TYPE_TYPEC: |
| chg_src = 2; /* AC */ |
| break; |
| case POWER_SUPPLY_TYPE_WIRELESS: |
| chg_src = 4; /* WIRELESS */ |
| break; |
| default: |
| chg_src = 9; |
| break; |
| } |
| htc_batt_info.rep.chg_src = chg_src; |
| } |
| |
| #define HYST_MV 200 |
| #define MAX_IDX 4 |
| #define HEALTH_LEVELS 6 |
| static int ibat_map_ac[MAX_IDX][HEALTH_LEVELS] = { |
| /* IBAT_IDX = < VOLTAGE-LIMITED, DISPLAY-ON > */ |
| /* #0 (00) */ { 30, 50, 100, 80, 50, 50}, |
| /* #1 (01) */ { 30, 50, 100, 50, 1000, 1000}, |
| /* #2 (10) */ { 15, 30, 50, 50, 0, 0}, |
| /* #3 (11) */ { 15, 30, 50, 50, 0, 0}, |
| }; |
| |
| static struct htc_thermal_stage thermal_stage[] = { |
| { -INT_MAX, 120}, |
| { 100, 220}, |
| { 420, 200}, /* Good */ |
| { 450, 400}, |
| { 480, 430}, |
| { INT_MAX, 460} |
| }; |
| |
| static int thermal_limit_vol[] = { |
| 4200, 4200, 4200, 4200, 4300, 4100 }; |
| |
| enum { |
| HEALTH_COOL2 = 0, |
| HEALTH_COOL1, |
| HEALTH_GOOD, |
| HEALTH_WARM1, |
| HEALTH_WARM2, |
| HEALTH_WARM3, |
| }; |
| |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| static int update_ibat_setting(void) |
| { |
| static int batt_thermal = HEALTH_GOOD; |
| static bool is_vol_limited; |
| int idx = 0; |
| bool is_screen_on = true; |
| int batt_temp = htc_batt_info.rep.batt_temp; |
| int batt_vol = htc_batt_info.rep.batt_vol; |
| int chg_type = htc_batt_info.rep.charging_source; |
| int ibat_ma = 0; |
| |
| /* Step 1: Update Health status*/ |
| while (1) { |
| if (batt_thermal >= HEALTH_GOOD) { /* normal & warm */ |
| if (batt_temp >= thermal_stage[batt_thermal].next_temp) |
| batt_thermal++; |
| else if (batt_temp <= |
| thermal_stage[batt_thermal].recover_temp) |
| batt_thermal--; |
| else |
| break; |
| } else { /* cool */ |
| if (batt_temp <= thermal_stage[batt_thermal].next_temp) |
| batt_thermal--; |
| else if (batt_temp >= |
| thermal_stage[batt_thermal].recover_temp) |
| batt_thermal++; |
| else |
| break; |
| } |
| } |
| /* Step 2: Update Voltage status */ |
| if (is_vol_limited || batt_vol > thermal_limit_vol[batt_thermal]) |
| is_vol_limited = true; |
| if (!is_vol_limited || |
| batt_vol < (thermal_limit_vol[batt_thermal] - HYST_MV)) |
| is_vol_limited = false; |
| |
| /* Step 3: Apply Screen ON configuration */ |
| is_screen_on = !(htc_batt_info.state & STATE_SCREEN_OFF); |
| |
| /* Step 4: Get mapping index */ |
| idx = ((2 * (is_vol_limited ? 1 : 0)) + |
| (1 * (is_screen_on ? 1 : 0))); |
| |
| switch (chg_type) { |
| case POWER_SUPPLY_TYPE_USB_HVDCP_3: |
| case POWER_SUPPLY_TYPE_USB_HVDCP: |
| case POWER_SUPPLY_TYPE_USB_DCP: |
| case POWER_SUPPLY_TYPE_USB_PD: |
| if (ibat_map_ac[idx][batt_thermal] <= 100) |
| ibat_ma = htc_batt_info.batt_fcc_ma * |
| ibat_map_ac[idx][batt_thermal] / 100; |
| else |
| ibat_ma = ibat_map_ac[idx][batt_thermal]; |
| break; |
| default: |
| ibat_ma = htc_batt_info.batt_fcc_ma; |
| break; |
| } |
| |
| ibat_ma = (ibat_ma / 25) * 25; |
| |
| if (ibat_ma * 1000 != |
| get_property( |
| htc_batt_info.batt_psy, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX)) { |
| BATT_LOG( |
| "%s: thermal=%d,temp=%d,fcc=%d,is_vol_limited(>%dmV)=%d,is_screen_on=%d,idx=%d,ibat_ma=%d.\n", |
| __func__, batt_thermal, batt_temp, |
| htc_batt_info.batt_fcc_ma, |
| thermal_limit_vol[batt_thermal], is_vol_limited, |
| is_screen_on, idx, ibat_ma); |
| } |
| |
| return ibat_ma * 1000; |
| } |
| |
| /* |
| * Accesses htc_batt_timer and g_htc_battery_probe_done. |
| * |
| * Caller must hold htc_battery_lock. |
| */ |
| int htc_batt_schedule_batt_info_update(void) |
| { |
| if (!g_htc_battery_probe_done) |
| return 1; |
| |
| if (!work_pending(&htc_batt_timer.batt_work)) |
| queue_work(htc_batt_timer.batt_wq, &htc_batt_timer.batt_work); |
| return 0; |
| } |
| |
| static int fb_notifier_callback(struct notifier_block *self, |
| unsigned long event, void *data) |
| { |
| struct fb_event *evdata = data; |
| int *blank; |
| |
| if (evdata && evdata->data && event == FB_EVENT_BLANK) { |
| blank = evdata->data; |
| |
| switch (*blank) { |
| case FB_BLANK_UNBLANK: |
| htc_batt_info.state &= ~STATE_SCREEN_OFF; |
| BATT_LOG("%s-> display is On", __func__); |
| htc_batt_schedule_batt_info_update(); |
| break; |
| case FB_BLANK_POWERDOWN: |
| case FB_BLANK_HSYNC_SUSPEND: |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_NORMAL: |
| htc_batt_info.state |= STATE_SCREEN_OFF; |
| BATT_LOG("%s-> display is Off", __func__); |
| htc_batt_schedule_batt_info_update(); |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void batt_worker(struct work_struct *work) |
| { |
| static int s_first = 1; |
| static int s_prev_pwrsrc_enabled = 1; |
| static int s_chg_present; |
| int pwrsrc_enabled = s_prev_pwrsrc_enabled; |
| int charging_enabled = gs_prev_charging_enabled; |
| int src = 0, online = 0, chg_present = 0, ex_otg = 0; |
| int ibat = 0; |
| int ibat_new = 0; |
| unsigned long time_since_last_update_ms; |
| unsigned long cur_jiffies; |
| |
| mutex_lock(&htc_battery_lock); |
| |
| /* STEP 1: print out and reset total_time since last update */ |
| cur_jiffies = jiffies; |
| time_since_last_update_ms = htc_batt_timer.total_time_ms + |
| ((cur_jiffies - htc_batt_timer.batt_system_jiffies) * |
| MSEC_PER_SEC / HZ); |
| BATT_DEBUG("%s: total_time since last batt update = %lu ms.\n", |
| __func__, time_since_last_update_ms); |
| htc_batt_timer.total_time_ms = 0; /* reset total time */ |
| htc_batt_timer.batt_system_jiffies = cur_jiffies; |
| |
| /* STEP 2: setup next batt uptate timer (can put in the last step)*/ |
| del_timer_sync(&htc_batt_timer.batt_timer); |
| batt_set_check_timer(htc_batt_timer.time_out); |
| |
| /* STEP 3: update charging_source */ |
| htc_batt_info.prev.charging_source = htc_batt_info.rep.charging_source; |
| online = get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_ONLINE); |
| if (online) |
| htc_batt_info.rep.charging_source = |
| get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_TYPE); |
| else |
| htc_batt_info.rep.charging_source = POWER_SUPPLY_TYPE_UNKNOWN; |
| |
| /* STEP 4: fresh battery information from gauge/charger */ |
| batt_update_info_from_gauge(); |
| batt_update_info_from_charger(); |
| update_htc_chg_src(); |
| |
| if (htc_batt_info.rep.charging_source > POWER_SUPPLY_TYPE_UNKNOWN && |
| htc_batt_info.rep.status != POWER_SUPPLY_STATUS_FULL) |
| wake_lock(&htc_batt_info.charger_exist_lock); |
| else |
| wake_unlock(&htc_batt_info.charger_exist_lock); |
| |
| /* STEP 5: set the charger contorl depends on current status |
| * if charging source exist, determine charging_enable |
| */ |
| chg_present = get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_PRESENT); |
| ex_otg = get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_USE_EXTERNAL_VBUS_OUTPUT); |
| if (((int)htc_batt_info.rep.charging_source > |
| POWER_SUPPLY_TYPE_BATTERY) || |
| (chg_present && !ex_otg)) { |
| htc_batt_info.rep.full_level = full_level_dis_chg; |
| if (is_bounding_fully_charged_level()) |
| g_pwrsrc_dis_reason |= HTC_BATT_PWRSRC_DIS_BIT_MFG; |
| else |
| g_pwrsrc_dis_reason &= ~HTC_BATT_PWRSRC_DIS_BIT_MFG; |
| /* STEP 5.1 determin charging_eanbled for charger control */ |
| if (g_chg_dis_reason) |
| charging_enabled = 0; |
| else |
| charging_enabled = 1; |
| |
| /* STEP 5.2 determin pwrsrc_eanbled for charger control */ |
| if (g_pwrsrc_dis_reason) |
| pwrsrc_enabled = 0; |
| else |
| pwrsrc_enabled = 1; |
| |
| /* STEP 5.3 check ibat setting */ |
| ibat = get_property( |
| htc_batt_info.batt_psy, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX); |
| ibat_new = update_ibat_setting(); |
| |
| if (ibat != ibat_new) { |
| BATT_EMBEDDED("set ibat(%d)", ibat_new); |
| set_batt_psy_property( |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, |
| ibat_new); |
| } |
| |
| if (s_chg_present != chg_present) { |
| BATT_EMBEDDED( |
| "set pwrsrc_enable(%d) when plug-in", |
| pwrsrc_enabled); |
| set_batt_psy_property( |
| POWER_SUPPLY_PROP_INPUT_SUSPEND, |
| !pwrsrc_enabled); |
| } else if (pwrsrc_enabled != s_prev_pwrsrc_enabled) { |
| BATT_EMBEDDED("set pwrsrc_enable(%d)", pwrsrc_enabled); |
| set_batt_psy_property( |
| POWER_SUPPLY_PROP_INPUT_SUSPEND, |
| !pwrsrc_enabled); |
| } |
| } else { |
| if ((htc_batt_info.prev.charging_source != |
| htc_batt_info.rep.charging_source) || |
| s_first) { |
| charging_enabled = 0; |
| pwrsrc_enabled = 0; |
| } |
| } |
| |
| gs_prev_charging_enabled = charging_enabled; |
| s_prev_pwrsrc_enabled = pwrsrc_enabled; |
| s_chg_present = chg_present; |
| s_first = 0; |
| |
| if (g_pwrsrc_dis_reason || g_chg_dis_reason) { |
| htc_batt_info.rep.chg_en = 0; |
| } else { |
| src = htc_batt_info.rep.charging_source; |
| if (src == POWER_SUPPLY_TYPE_UNKNOWN) |
| htc_batt_info.rep.chg_en = 0; |
| else if (src == POWER_SUPPLY_TYPE_USB || |
| src == POWER_SUPPLY_TYPE_USB_CDP) |
| htc_batt_info.rep.chg_en = 1; |
| else |
| htc_batt_info.rep.chg_en = 2; |
| } |
| |
| BATT_EMBEDDED("ID=%d, level=%d, vol=%dmV, temp=%d, current(mA)=%d\n", |
| htc_batt_info.rep.batt_id, |
| htc_batt_info.rep.level, |
| htc_batt_info.rep.batt_vol, |
| htc_batt_info.rep.batt_temp, |
| (htc_batt_info.rep.batt_current / 1000)); |
| BATT_LOG(" chg_name=%s, chg_src=%d, chg_en=%d, health=%d\n", |
| g_chr_src[htc_batt_info.rep.charging_source], |
| htc_batt_info.rep.chg_src, |
| htc_batt_info.rep.chg_en, |
| htc_batt_info.rep.health); |
| BATT_LOG(" vbus(mV)=%d, MAX_IUSB(mA)=%d, MAX_PD_IUSB(mA)=%d\n", |
| (htc_batt_info.vbus / 1000), |
| get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_CURRENT_MAX) / 1000, |
| get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_PD_CURRENT_MAX) / 1000); |
| BATT_LOG(" MAX_IBAT(mA)=%d, iusb_now(mA)=%d, AICL=%d\n", |
| get_property( |
| htc_batt_info.batt_psy, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX) / 1000, |
| get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_INPUT_CURRENT_NOW) / 1000, |
| get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED) / 1000); |
| BATT_LOG(" batt_id(Kohms)=%d, status=%d, pwrsrc_dis_reason=%d\n", |
| get_property(htc_batt_info.bms_psy, |
| POWER_SUPPLY_PROP_RESISTANCE_ID) / 1000, |
| htc_batt_info.rep.status, |
| g_pwrsrc_dis_reason); |
| BATT_LOG(" chg_dis_reason=%d, batt_state=%d, charger_temp=%d\n", |
| g_chg_dis_reason, |
| htc_batt_info.state, |
| htc_batt_info.rep.chg_src ? |
| get_property(htc_batt_info.batt_psy, |
| POWER_SUPPLY_PROP_CHARGER_TEMP) : -1); |
| BATT_LOG(" CC_uAh=%d\n", |
| get_property(htc_batt_info.bms_psy, |
| POWER_SUPPLY_PROP_CHARGE_NOW_RAW) |
| ); |
| |
| /* WA for display flickering */ |
| if (htc_batt_info.v_elvdd_dis_en > 0 && |
| gpio_is_valid(htc_batt_info.v_elvdd_dis_en)) { |
| if ((int)htc_batt_info.rep.charging_source > |
| POWER_SUPPLY_TYPE_BATTERY) { |
| if (htc_batt_info.rep.level == 100) |
| gpio_direction_output( |
| htc_batt_info.v_elvdd_dis_en, 1); |
| else |
| gpio_direction_output( |
| htc_batt_info.v_elvdd_dis_en, 0); |
| } else { |
| gpio_direction_output(htc_batt_info.v_elvdd_dis_en, 0); |
| } |
| } |
| BATT_LOG(" v_elvdd_dis_en=%d\n", |
| gpio_get_value(htc_batt_info.v_elvdd_dis_en)); |
| |
| mutex_unlock(&htc_battery_lock); |
| } |
| |
| static void htc_battery_update_work(struct work_struct *work) |
| { |
| int intval = 0, present = 0, latest_chg_src = 0; |
| bool info_update = false; |
| |
| mutex_lock(&htc_battery_lock); |
| |
| /* POWER_SUPPLY_PROP_TYPE */ |
| present = get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_PRESENT); |
| if (present >= 0) { |
| if (present) |
| latest_chg_src = get_property(htc_batt_info.usb_psy, |
| POWER_SUPPLY_PROP_TYPE); |
| else |
| latest_chg_src = POWER_SUPPLY_TYPE_UNKNOWN; |
| if (htc_batt_info.rep.charging_source != latest_chg_src) |
| info_update = true; |
| } |
| |
| /* POWER_SUPPLY_PROP_STATUS */ |
| intval = get_property(htc_batt_info.batt_psy, POWER_SUPPLY_PROP_STATUS); |
| if ((intval >= 0) && (htc_batt_info.rep.status != intval)) |
| info_update = true; |
| |
| /* POWER_SUPPLY_PROP_CAPACITY */ |
| intval = get_property(htc_batt_info.batt_psy, |
| POWER_SUPPLY_PROP_CAPACITY); |
| if ((intval >= 0) && (htc_batt_info.rep.level != intval)) |
| info_update = true; |
| |
| /* POWER_SUPPLY_PROP_HEALTH */ |
| intval = get_property(htc_batt_info.batt_psy, POWER_SUPPLY_PROP_HEALTH); |
| if ((intval >= 0) && (htc_batt_info.rep.health != intval)) |
| info_update = true; |
| |
| /* Update batt_worker */ |
| if (info_update) |
| htc_batt_schedule_batt_info_update(); |
| |
| mutex_unlock(&htc_battery_lock); |
| } |
| |
| static int htc_notifier_batt_callback(struct notifier_block *nb, |
| unsigned long ev, void *v) |
| { |
| struct power_supply *psy = v; |
| |
| if (ev != PSY_EVENT_PROP_CHANGED) |
| return NOTIFY_OK; |
| |
| if (!strcmp(psy->desc->name, "battery") || |
| !strcmp(psy->desc->name, "usb")) { |
| if (work_pending(&htc_batt_info.batt_update_work)) |
| return NOTIFY_OK; |
| schedule_work(&htc_batt_info.batt_update_work); |
| } |
| |
| return NOTIFY_OK; |
| } |
| |
| #define WALLEYE_BATT_ID_1 "walleye 1" |
| #define WALLEYE_BATT_ID_2 "walleye 2" |
| #define MUSKIE_BATT_ID_1 "muskie 1" |
| #define MUSKIE_BATT_ID_2 "muskie 2" |
| #define LOADING_BATT_TYPE "Loading Battery" |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| static int htc_battery_probe_process(void) |
| { |
| union power_supply_propval ret = {0, }; |
| int rc = 0, id_kohms = 0; |
| struct device_node *node; |
| struct device_node *profile_node; |
| |
| htc_batt_info.batt_psy = power_supply_get_by_name("battery"); |
| if (!htc_batt_info.batt_psy) |
| return -EPROBE_DEFER; |
| htc_batt_info.bms_psy = power_supply_get_by_name("bms"); |
| if (!htc_batt_info.bms_psy) |
| return -EPROBE_DEFER; |
| htc_batt_info.usb_psy = power_supply_get_by_name("usb"); |
| if (!htc_batt_info.usb_psy) |
| return -EPROBE_DEFER; |
| |
| rc = power_supply_get_property(htc_batt_info.bms_psy, |
| POWER_SUPPLY_PROP_BATTERY_TYPE, &ret); |
| if (rc < 0) { |
| BATT_ERR("Unable to read battery-type rc=%d\n", rc); |
| htc_batt_info.rep.batt_id = BATT_ID_UNKNOWN; |
| } else { |
| if (!strncmp(ret.strval, |
| LOADING_BATT_TYPE, sizeof(LOADING_BATT_TYPE))) |
| return -EPROBE_DEFER; |
| else if ((!strncmp(ret.strval, |
| WALLEYE_BATT_ID_1, |
| sizeof(WALLEYE_BATT_ID_1))) || |
| (!strncmp(ret.strval, |
| MUSKIE_BATT_ID_1, sizeof(MUSKIE_BATT_ID_1)))) |
| htc_batt_info.rep.batt_id = BATT_ID_1; |
| else if ((!strncmp(ret.strval, |
| WALLEYE_BATT_ID_2, |
| sizeof(WALLEYE_BATT_ID_2))) || |
| (!strncmp(ret.strval, |
| MUSKIE_BATT_ID_2, sizeof(MUSKIE_BATT_ID_2)))) |
| htc_batt_info.rep.batt_id = BATT_ID_2; |
| else |
| htc_batt_info.rep.batt_id = BATT_ID_UNKNOWN; |
| } |
| |
| id_kohms = get_property( |
| htc_batt_info.bms_psy, |
| POWER_SUPPLY_PROP_RESISTANCE_ID) / 1000; |
| node = of_find_node_by_name(NULL, "qcom,battery-data"); |
| if (!node) { |
| BATT_LOG("%s: No batterydata available\n", __func__); |
| } else { |
| profile_node = of_batterydata_get_best_profile( |
| node, id_kohms, NULL); |
| if (!profile_node) { |
| BATT_LOG( |
| "%s: couldn't find profile handle\n", |
| __func__); |
| } else { |
| rc = of_property_read_u32( |
| profile_node, |
| "htc,fastchg-current-ma", |
| &htc_batt_info.batt_fcc_ma); |
| if (rc < 0) { |
| BATT_LOG( |
| "%s: error reading htc,fastchg-current-ma. %d\n", |
| __func__, rc); |
| htc_batt_info.batt_fcc_ma = 2600; |
| } |
| /* read battery design capacity from DT, unit is mAh */ |
| rc = of_property_read_u32( |
| profile_node, |
| "qcom,nom-batt-capacity-mah", |
| &htc_batt_info.batt_capacity_mah); |
| if (rc < 0) { |
| BATT_LOG( |
| "%s: error reading qcom,nom-batt-capacity-mah. %d\n", |
| __func__, rc); |
| htc_batt_info.batt_capacity_mah = 2600; |
| } |
| /* |
| * read battery thermal limit threshold voltage from DT, |
| * unit is mV |
| */ |
| rc = of_property_read_u32( |
| profile_node, |
| "qcom,batt-thermal-limit-vol", |
| &htc_batt_info.batt_thermal_limit_vol); |
| if (rc < 0) { |
| BATT_LOG( |
| "%s: error reading qcom,batt-thermal-limit-vol. %d\n", |
| __func__, rc); |
| htc_batt_info.batt_thermal_limit_vol = 4200; |
| } |
| } |
| } |
| BATT_LOG("%s: catch name %s, set batt id=%d, fcc_ma=%d, capacity=%d\n", |
| __func__, ret.strval, htc_batt_info.rep.batt_id, |
| htc_batt_info.batt_fcc_ma, htc_batt_info.batt_capacity_mah); |
| |
| /* WA for display flickering */ |
| node = of_find_node_by_name(NULL, "htc,battery-node"); |
| if (!node) { |
| BATT_LOG("%s: No htc,battery-node available\n", __func__); |
| } else { |
| htc_batt_info.v_elvdd_dis_en = |
| of_get_named_gpio(node, "htc,v-elvdd-dis-en", 0); |
| if (!gpio_is_valid(htc_batt_info.v_elvdd_dis_en)) { |
| BATT_LOG( |
| "%s: error to reading htc,v-elvdd-dis-en.\n", |
| __func__); |
| htc_batt_info.v_elvdd_dis_en = 0; |
| } else { |
| BATT_LOG("%s: htc,v-elvdd-dis-en = %d\n", |
| __func__, htc_batt_info.v_elvdd_dis_en); |
| rc = gpio_request(htc_batt_info.v_elvdd_dis_en, |
| "V_ELVDD_DIS_EN"); |
| if (rc < 0) { |
| BATT_LOG( |
| "%s: fail to request V_ELVDD_DIS_EN, rc=%dn", |
| __func__, rc); |
| } else { |
| gpio_direction_output( |
| htc_batt_info.v_elvdd_dis_en, 0); |
| } |
| } |
| } |
| |
| BATT_LOG("Probe process done.\n"); |
| |
| return 0; |
| } |
| |
| /* |
| * htc_batt_timer.batt_timer can only be started after htc_batt_timer.batt_wq |
| * is initialized and g_htc_battery_probe_done is set to true, so the timer |
| * callback is properly synchronized with code that acquires htc_battery_lock. |
| * |
| * This timer callback *cannot* acquire htc_battery_lock because otherwise it |
| * will deadlock with the del_timer_sync() in batt_worker(). |
| */ |
| static void batt_regular_timer_handler(unsigned long data) |
| { |
| htc_batt_schedule_batt_info_update(); |
| } |
| |
| /* accesses htc_batt_info, needs htc_battery_lock */ |
| static int htc_battery_fb_register(void) |
| { |
| int rc = 0; |
| |
| BATT_LOG("%s in", __func__); |
| htc_batt_info.fb_notif.notifier_call = fb_notifier_callback; |
| rc = fb_register_client(&htc_batt_info.fb_notif); |
| if (rc < 0) { |
| BATT_ERR("[warning]:Unable to register fb_notifier: %d\n", rc); |
| return rc; |
| } |
| return 0; |
| } |
| |
| static int htc_battery_probe(struct platform_device *pdev) |
| { |
| int rc = 0; |
| |
| mutex_lock(&htc_battery_lock); |
| |
| rc = htc_battery_probe_process(); |
| if (rc < 0) |
| goto err_unlock; |
| |
| INIT_WORK(&htc_batt_timer.batt_work, batt_worker); |
| INIT_WORK(&htc_batt_info.batt_update_work, htc_battery_update_work); |
| setup_timer(&htc_batt_timer.batt_timer, batt_regular_timer_handler, 0); |
| htc_batt_timer.batt_wq = create_singlethread_workqueue("batt_timer"); |
| if (!htc_batt_timer.batt_wq) { |
| BATT_LOG("%s: create_singlethread_workqueue failed.\n", |
| __func__); |
| goto err_unlock; |
| } |
| |
| htc_batt_timer.time_out = BATT_TIMER_UPDATE_TIME; |
| |
| htc_batt_info.htc_batt_cb.notifier_call = htc_notifier_batt_callback; |
| rc = power_supply_reg_notifier(&htc_batt_info.htc_batt_cb); |
| if (rc < 0) { |
| BATT_LOG("%s: power_supply_reg_notifier failed.\n", __func__); |
| goto err_destroy_workqueue; |
| } |
| |
| rc = htc_battery_fb_register(); |
| if (rc < 0) { |
| BATT_LOG("%s: htc_battery_fb_register failed.\n", __func__); |
| goto err_power_supply_unreg_notifier; |
| } |
| |
| g_htc_battery_probe_done = true; |
| batt_set_check_timer(htc_batt_timer.time_out); |
| goto err_unlock; |
| |
| err_power_supply_unreg_notifier: |
| power_supply_unreg_notifier(&htc_batt_info.htc_batt_cb); |
| err_destroy_workqueue: |
| destroy_workqueue(htc_batt_timer.batt_wq); |
| err_unlock: |
| mutex_unlock(&htc_battery_lock); |
| return rc; |
| } |
| |
| static struct platform_device htc_battery_pdev = { |
| .name = HTC_BATT_NAME, |
| .id = -1, |
| }; |
| |
| static struct platform_driver htc_battery_driver = { |
| .probe = htc_battery_probe, |
| .driver = { |
| .name = HTC_BATT_NAME, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init htc_battery_init(void) |
| { |
| int ret; |
| |
| wake_lock_init(&htc_batt_info.charger_exist_lock, |
| WAKE_LOCK_SUSPEND, "charger_exist_lock"); |
| |
| /* init battery parameters. */ |
| htc_batt_info.rep.batt_vol = 4000; |
| htc_batt_info.rep.batt_id = 1; |
| htc_batt_info.rep.batt_temp = 280; |
| htc_batt_info.rep.batt_current = 0; |
| htc_batt_info.rep.charging_source = POWER_SUPPLY_TYPE_UNKNOWN; |
| htc_batt_info.rep.level = 33; |
| htc_batt_info.rep.level_raw = 33; |
| htc_batt_info.rep.full_level = 100; |
| htc_batt_info.rep.status = POWER_SUPPLY_STATUS_UNKNOWN; |
| htc_batt_info.rep.full_level_dis_batt_chg = 100; |
| htc_batt_info.rep.overload = 0; |
| htc_batt_info.rep.over_vchg = 0; |
| htc_batt_info.rep.is_full = false; |
| htc_batt_info.rep.health = POWER_SUPPLY_HEALTH_UNKNOWN; |
| htc_batt_info.smooth_chg_full_delay_min = 1; |
| htc_batt_info.decreased_batt_level_check = 1; |
| htc_batt_info.critical_low_voltage_mv = 3200; |
| htc_batt_info.batt_full_voltage_mv = 4350; |
| htc_batt_info.batt_full_current_ma = 300; |
| htc_batt_info.overload_curr_thr_ma = 0; |
| htc_batt_info.batt_fcc_ma = 3000; |
| htc_batt_info.batt_capacity_mah = 3000; |
| htc_batt_info.batt_thermal_limit_vol = 4200; |
| htc_batt_info.vbus = 0; |
| |
| ret = platform_device_register(&htc_battery_pdev); |
| if (ret < 0) { |
| BATT_LOG("%s: device registration failed.\n", __func__); |
| return ret; |
| } |
| |
| ret = platform_driver_register(&htc_battery_driver); |
| if (ret < 0) { |
| BATT_LOG("%s: driver registration failed.\n", __func__); |
| return ret; |
| } |
| |
| BATT_LOG("%s done.\n", __func__); |
| |
| return 0; |
| } |
| |
| static void __exit htc_battery_exit(void) |
| { |
| platform_device_unregister(&htc_battery_pdev); |
| platform_driver_unregister(&htc_battery_driver); |
| BATT_LOG("%s done.\n", __func__); |
| } |
| |
| module_init(htc_battery_init); |
| module_exit(htc_battery_exit); |
| MODULE_LICENSE("GPL"); |