/* * sec_battery.c * Samsung Mobile Battery Driver * * Copyright (C) 2021 Samsung Electronics * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include "sec_battery.h" #include "sec_battery_sysfs.h" #include "sec_battery_dt.h" #include "sec_battery_ttf.h" #if defined(CONFIG_SEC_COMMON) #include #endif #if IS_ENABLED(CONFIG_SEC_ABC) #include #endif #if defined(CONFIG_ARCH_QCOM) && !(defined(CONFIG_ARCH_EXYNOS) || defined(CONFIG_ARCH_MEDIATEK)) #include #endif #include #if defined(CONFIG_SEC_KUNIT) #include #else #define __visible_for_testing static #endif #if defined(CONFIG_UML) #include "kunit_test/uml_dummy.h" #endif #include "battery_logger.h" #include "sb_tx.h" #include "sb_batt_dump.h" #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_LSI_IFPMIC) #include #endif static unsigned int __read_mostly lpcharge; module_param(lpcharge, uint, 0444); static int __read_mostly fg_reset; module_param(fg_reset, int, 0444); static int __read_mostly factory_mode; module_param(factory_mode, int, 0444); static unsigned int __read_mostly charging_mode; module_param(charging_mode, uint, 0444); static unsigned int __read_mostly pd_disable; module_param(pd_disable, uint, 0444); static char * __read_mostly sales_code; module_param(sales_code, charp, 0444); #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) static char __read_mostly *f_mode; module_param(f_mode, charp, 0444); #endif static const char *sec_voter_name[] = { FOREACH_VOTER(GENERATE_STRING) }; static enum power_supply_property sec_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_TECHNOLOGY, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_VOLTAGE_AVG, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_NOW, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_TEMP_AMBIENT, #if IS_ENABLED(CONFIG_FUELGAUGE_MAX77705) POWER_SUPPLY_PROP_POWER_NOW, POWER_SUPPLY_PROP_POWER_AVG, #endif POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, POWER_SUPPLY_PROP_CHARGE_COUNTER, }; static enum power_supply_property sec_power_props[] = { POWER_SUPPLY_PROP_ONLINE, }; static enum power_supply_property sec_wireless_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_PRESENT, }; static enum power_supply_property sec_ac_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_TEMP, }; static enum power_supply_property sec_otg_props[] = { POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_MAX, }; static char *supply_list[] = { "battery", }; const char *sb_get_ct_str(int ct) { switch (ct) { case SEC_BATTERY_CABLE_UNKNOWN: return "UNKNOWN"; case SEC_BATTERY_CABLE_NONE: return "NONE"; case SEC_BATTERY_CABLE_PREPARE_TA: return "PREPARE_TA"; case SEC_BATTERY_CABLE_TA: return "TA"; case SEC_BATTERY_CABLE_USB: return "USB"; case SEC_BATTERY_CABLE_USB_CDP: return "USB_CDP"; case SEC_BATTERY_CABLE_9V_TA: return "9V_TA"; case SEC_BATTERY_CABLE_9V_ERR: return "9V_ERR"; case SEC_BATTERY_CABLE_9V_UNKNOWN: return "9V_UNKNOWN"; case SEC_BATTERY_CABLE_12V_TA: return "12V_TA"; case SEC_BATTERY_CABLE_WIRELESS: return "WC"; case SEC_BATTERY_CABLE_HV_WIRELESS: return "HV_WC"; case SEC_BATTERY_CABLE_PMA_WIRELESS: return "PMA_WC"; case SEC_BATTERY_CABLE_WIRELESS_PACK: return "WC_PACK"; case SEC_BATTERY_CABLE_WIRELESS_HV_PACK: return "WC_HV_PACK"; case SEC_BATTERY_CABLE_WIRELESS_STAND: return "WC_STAND"; case SEC_BATTERY_CABLE_WIRELESS_HV_STAND: return "WC_HV_STAND"; case SEC_BATTERY_CABLE_QC20: return "QC20"; case SEC_BATTERY_CABLE_QC30: return "QC30"; case SEC_BATTERY_CABLE_PDIC: return "PDIC"; case SEC_BATTERY_CABLE_UARTOFF: return "UARTOFF"; case SEC_BATTERY_CABLE_OTG: return "OTG"; case SEC_BATTERY_CABLE_LAN_HUB: return "LAN_HUB"; case SEC_BATTERY_CABLE_POWER_SHARING: return "POWER_SHARING"; case SEC_BATTERY_CABLE_HMT_CONNECTED: return "HMT_CONNECTED"; case SEC_BATTERY_CABLE_HMT_CHARGE: return "HMT_CHARGE"; case SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT: return "HV_TA_CHG_LIMIT"; case SEC_BATTERY_CABLE_WIRELESS_VEHICLE: return "WC_VEHICLE"; case SEC_BATTERY_CABLE_WIRELESS_HV_VEHICLE: return "WC_HV_VEHICLE"; case SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV: return "WC_HV_PREPARE"; case SEC_BATTERY_CABLE_TIMEOUT: return "TIMEOUT"; case SEC_BATTERY_CABLE_SMART_OTG: return "SMART_OTG"; case SEC_BATTERY_CABLE_SMART_NOTG: return "SMART_NOTG"; case SEC_BATTERY_CABLE_WIRELESS_TX: return "WC_TX"; case SEC_BATTERY_CABLE_HV_WIRELESS_20: return "HV_WC_20"; case SEC_BATTERY_CABLE_HV_WIRELESS_20_LIMIT: return "HV_WC_20_LIMIT"; case SEC_BATTERY_CABLE_WIRELESS_FAKE: return "WC_FAKE"; case SEC_BATTERY_CABLE_PREPARE_WIRELESS_20: return "HV_WC_20_PREPARE"; case SEC_BATTERY_CABLE_PDIC_APDO: return "PDIC_APDO"; case SEC_BATTERY_CABLE_POGO: return "POGO"; case SEC_BATTERY_CABLE_POGO_9V: return "POGO_9V"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_get_ct_str); const char *sb_get_cm_str(int charging_mode) { switch (charging_mode) { case SEC_BATTERY_CHARGING_NONE: return "None"; case SEC_BATTERY_CHARGING_1ST: return "Normal"; case SEC_BATTERY_CHARGING_2ND: return "Additional"; case SEC_BATTERY_CHARGING_RECHARGING: return "Re-Charging"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_get_cm_str); const char *sb_get_bst_str(int status) { switch (status) { case POWER_SUPPLY_STATUS_UNKNOWN: return "Unknown"; case POWER_SUPPLY_STATUS_CHARGING: return "Charging"; case POWER_SUPPLY_STATUS_DISCHARGING: return "Discharging"; case POWER_SUPPLY_STATUS_NOT_CHARGING: return "Not-charging"; case POWER_SUPPLY_STATUS_FULL: return "Full"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_get_bst_str); const char *sb_get_hl_str(int health) { switch (health) { case POWER_SUPPLY_HEALTH_GOOD: return "Good"; case POWER_SUPPLY_HEALTH_OVERHEAT: return "Overheat"; case POWER_SUPPLY_HEALTH_DEAD: return "Dead"; case POWER_SUPPLY_HEALTH_OVERVOLTAGE: return "Over voltage"; case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: return "Unspecified failure"; case POWER_SUPPLY_HEALTH_COLD: return "Cold"; case POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE: return "Watchdog timer expire"; case POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE: return "Safety timer expire"; case POWER_SUPPLY_HEALTH_OVERCURRENT: return "Over current"; case POWER_SUPPLY_HEALTH_CALIBRATION_REQUIRED: return "Cal required"; case POWER_SUPPLY_HEALTH_WARM: return "Warm"; case POWER_SUPPLY_HEALTH_COOL: return "Cool"; case POWER_SUPPLY_HEALTH_HOT: return "Hot"; case POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE: return "UnderVoltage"; case POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT: return "OverheatLimit"; case POWER_SUPPLY_EXT_HEALTH_VSYS_OVP: return "VsysOVP"; case POWER_SUPPLY_EXT_HEALTH_VBAT_OVP: return "VbatOVP"; case POWER_SUPPLY_EXT_HEALTH_DC_ERR: return "DCErr"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_get_hl_str); const char *sb_get_tz_str(int tz) { switch (tz) { case BAT_THERMAL_NORMAL: return "Normal"; case BAT_THERMAL_COLD: return "COLD"; case BAT_THERMAL_COOL3: return "COOL3"; case BAT_THERMAL_COOL2: return "COOL2"; case BAT_THERMAL_COOL1: return "COOL1"; case BAT_THERMAL_WARM: return "WARM"; case BAT_THERMAL_OVERHEAT: return "OVERHEAT"; case BAT_THERMAL_OVERHEATLIMIT: return "OVERHEATLIM"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_get_tz_str); const char *sb_charge_mode_str(int charge_mode) { switch (charge_mode) { case SEC_BAT_CHG_MODE_BUCK_OFF: return "Buck-Off"; case SEC_BAT_CHG_MODE_CHARGING_OFF: return "Charging-Off"; case SEC_BAT_CHG_MODE_PASS_THROUGH: return "Pass-Through"; case SEC_BAT_CHG_MODE_CHARGING: return "Charging-On"; case SEC_BAT_CHG_MODE_OTG_ON: return "OTG-On"; case SEC_BAT_CHG_MODE_OTG_OFF: return "OTG-Off"; case SEC_BAT_CHG_MODE_UNO_ON: return "UNO-On"; case SEC_BAT_CHG_MODE_UNO_OFF: return "UNO-Off"; case SEC_BAT_CHG_MODE_UNO_ONLY: return "UNO-Only"; case SEC_BAT_CHG_MODE_NOT_SET: return "Not-Set"; case SEC_BAT_CHG_MODE_MAX: return "Max"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_charge_mode_str); const char *sb_rx_type_str(int type) { switch (type) { case NO_DEV: return "No Dev"; case OTHER_DEV: return "Other Dev"; case SS_GEAR: return "Gear"; case SS_PHONE: return "Phone"; case SS_BUDS: return "Buds"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_rx_type_str); const char *sb_vout_ctr_mode_str(int vout_mode) { switch (vout_mode) { case WIRELESS_VOUT_OFF: return "Set VOUT Off"; case WIRELESS_VOUT_NORMAL_VOLTAGE: return "Set VOUT NV"; case WIRELESS_VOUT_RESERVED: return "Set VOUT Rsv"; case WIRELESS_VOUT_HIGH_VOLTAGE: return "Set VOUT HV"; case WIRELESS_VOUT_CC_CV_VOUT: return "Set VOUT CV"; case WIRELESS_VOUT_CALL: return "Set VOUT Call"; case WIRELESS_VOUT_5V: return "Set VOUT 5V"; case WIRELESS_VOUT_9V: return "Set VOUT 9V"; case WIRELESS_VOUT_10V: return "Set VOUT 10V"; case WIRELESS_VOUT_11V: return "Set VOUT 11V"; case WIRELESS_VOUT_12V: return "Set VOUT 12V"; case WIRELESS_VOUT_12_5V: return "Set VOUT 12.5V"; case WIRELESS_VOUT_4_5V_STEP: return "Set VOUT 4.5V Step"; case WIRELESS_VOUT_5V_STEP: return "Set VOUT 5V Step"; case WIRELESS_VOUT_5_5V_STEP: return "Set VOUT 5.5V Step"; case WIRELESS_VOUT_9V_STEP: return "Set VOUT 9V Step"; case WIRELESS_VOUT_10V_STEP: return "Set VOUT 10V Step"; case WIRELESS_VOUT_OTG: return "Set VOUT OTG"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_vout_ctr_mode_str); const char *sb_rx_vout_str(int vout) { switch (vout) { case MFC_VOUT_4_5V: return "VOUT 4.5V"; case MFC_VOUT_5V: return "VOUT 5V"; case MFC_VOUT_5_5V: return "VOUT 5.5V"; case MFC_VOUT_6V: return "VOUT 6V"; case MFC_VOUT_7V: return "VOUT 7V"; case MFC_VOUT_8V: return "VOUT 8V"; case MFC_VOUT_9V: return "VOUT 9V"; case MFC_VOUT_10V: return "VOUT 10V"; case MFC_VOUT_11V: return "VOUT 11V"; case MFC_VOUT_12V: return "VOUT 12V"; case MFC_VOUT_12_5V: return "VOUT 12.5V"; case MFC_VOUT_OTG: return "VOUT OTG"; default: return "UNDEFINED"; } } EXPORT_SYMBOL(sb_rx_vout_str); __visible_for_testing int sec_bat_check_afc_input_current(struct sec_battery_info *battery); #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) __visible_for_testing void sec_bat_set_rp_current(struct sec_battery_info *battery, int cable_type); #endif unsigned int sec_bat_get_lpmode(void) { return lpcharge; } void sec_bat_set_lpmode(unsigned int value) { lpcharge = value; } EXPORT_SYMBOL_KUNIT(sec_bat_set_lpmode); int sec_bat_get_fgreset(void) { return fg_reset; } int sec_bat_get_facmode(void) { return factory_mode; } #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) void sec_bat_set_facmode(int value) { factory_mode = value; } #endif unsigned int sec_bat_get_chgmode(void) { return charging_mode; } EXPORT_SYMBOL_KUNIT(sec_bat_get_chgmode); void sec_bat_set_chgmode(unsigned int value) { charging_mode = value; } EXPORT_SYMBOL_KUNIT(sec_bat_set_chgmode); unsigned int sec_bat_get_dispd(void) { return pd_disable; } EXPORT_SYMBOL_KUNIT(sec_bat_get_dispd); void sec_bat_set_dispd(unsigned int value) { pd_disable = value; } EXPORT_SYMBOL_KUNIT(sec_bat_set_dispd); char *sec_bat_get_sales_code(void) {return sales_code; } #if !defined(CONFIG_SEC_FACTORY) #define SALE_CODE_STR_LEN 3 bool sales_code_is(char *str) { if (sec_bat_get_sales_code() == NULL) return false; pr_info("%s: %s\n", __func__, sec_bat_get_sales_code()); return !strncmp(sec_bat_get_sales_code(), str, SALE_CODE_STR_LEN + 1); } #endif static bool check_silent_type(int ct) { #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) if (ct == ATTACHED_DEV_RETRY_TIMEOUT_OPEN_MUIC || ct == ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC || ct == ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC) return true; #endif return false; } static bool check_afc_disabled_type(int ct) { #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) if (ct == ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC) return true; #endif return false; } #if IS_ENABLED(CONFIG_DUAL_BATTERY) __visible_for_testing void sec_bat_divide_limiter_current(struct sec_battery_info *battery, int limiter_current) { unsigned int main_current = 0, sub_current = 0, main_current_rate = 0, sub_current_rate = 0; union power_supply_propval value = {0, }; if (is_pd_apdo_wire_type(battery->cable_type) && battery->pd_list.now_isApdo) { if (limiter_current < battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].fast_charging_current) limiter_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].fast_charging_current; } if (battery->pdata->sub_fto && limiter_current >= battery->pdata->sub_fto_current_thresh) { value.intval = 3; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_EXT_PROP_CHG_MODE, value); } else if (battery->pdata->sub_fto && limiter_current < battery->pdata->sub_fto_current_thresh) { value.intval = 0; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_EXT_PROP_CHG_MODE, value); } if (limiter_current >= battery->pdata->zone3_limiter_current) { main_current_rate = battery->pdata->main_zone3_current_rate; sub_current_rate = battery->pdata->sub_zone3_current_rate; } else if (limiter_current >= battery->pdata->zone2_limiter_current) { main_current_rate = battery->pdata->main_zone2_current_rate; sub_current_rate = battery->pdata->sub_zone2_current_rate; } else if (limiter_current > battery->pdata->zone1_limiter_current) { main_current_rate = battery->pdata->main_zone1_current_rate; sub_current_rate = battery->pdata->sub_zone1_current_rate; } else { /* like discharge current 100mA */ main_current_rate = battery->pdata->min_main_limiter_current; sub_current_rate = battery->pdata->min_sub_limiter_current; } /* divide setting has ratio value(percent value, max 99%) and current value(ex 1500mA) */ main_current = (main_current_rate > 100) ? main_current_rate : ((main_current_rate * limiter_current) / 100); sub_current = (sub_current_rate > 100) ? sub_current_rate : ((sub_current_rate * limiter_current) / 100); pr_info("%s: (%d) -> main_current(%d), sub_current(%d)\n", __func__, limiter_current, main_current, sub_current); // debug if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) { if (battery->pdata->limiter_main_cool3_current) main_current = battery->pdata->limiter_main_cool3_current; if (battery->pdata->limiter_sub_cool3_current) sub_current = battery->pdata->limiter_sub_cool3_current; } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL2) { if (battery->pdata->limiter_main_cool2_current) main_current = battery->pdata->limiter_main_cool2_current; if (battery->pdata->limiter_sub_cool2_current) sub_current = battery->pdata->limiter_sub_cool2_current; } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING) { if (battery->pdata->limiter_main_warm_current) main_current = battery->pdata->limiter_main_warm_current; if (battery->pdata->limiter_sub_warm_current) sub_current = battery->pdata->limiter_sub_warm_current; } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL1) { if (battery->pdata->limiter_main_cool1_current) main_current = battery->pdata->limiter_main_cool1_current; if (battery->pdata->limiter_sub_cool1_current) sub_current = battery->pdata->limiter_sub_cool1_current; } /* calculate main battery current */ if (main_current > battery->pdata->max_main_limiter_current) main_current = battery->pdata->max_main_limiter_current; /* calculate sub battery current */ if (sub_current > battery->pdata->max_sub_limiter_current) sub_current = battery->pdata->max_sub_limiter_current; pr_info("%s: main_current(%d), sub_current(%d)\n", __func__, main_current, sub_current); battery->main_current = main_current; battery->sub_current = sub_current; } EXPORT_SYMBOL_KUNIT(sec_bat_divide_limiter_current); __visible_for_testing void sec_bat_set_limiter_current(struct sec_battery_info *battery) { union power_supply_propval value = {0, }; pr_info("%s: charge_m(%d), charge_s(%d)\n", __func__, battery->main_current, battery->sub_current); value.intval = battery->main_current; psy_do_property(battery->pdata->main_limiter_name, set, POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); value.intval = battery->sub_current; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT, value); } EXPORT_SYMBOL_KUNIT(sec_bat_set_limiter_current); #endif __visible_for_testing int set_charging_current(void *data, int v) { union power_supply_propval value = {0, }; struct sec_battery_info *battery = data; #if IS_ENABLED(CONFIG_DUAL_BATTERY) sec_bat_divide_limiter_current(battery, v); if (battery->charging_current < v) sec_bat_set_limiter_current(battery); #endif value.intval = v; if (is_wireless_type(battery->cable_type)) { if (battery->charging_current < v) { psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, value); } else { psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_CONSTANT_CHARGE_CURRENT_WRL, value); } } else { psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, value); } #if IS_ENABLED(CONFIG_DUAL_BATTERY) if (battery->charging_current > v) { if (!is_wireless_type(battery->cable_type)) sec_bat_set_limiter_current(battery); } #endif battery->charging_current = v; #if IS_ENABLED(CONFIG_DUAL_BATTERY) pr_info("%s: power(%d), input(%d), charge(%d), charge_m(%d), charge_s(%d)\n", __func__, battery->charge_power, battery->input_current, battery->charging_current, battery->main_current, battery->sub_current); #else pr_info("%s: power(%d), input(%d), charge(%d)\n", __func__, battery->charge_power, battery->input_current, battery->charging_current); #endif return v; } EXPORT_SYMBOL_KUNIT(set_charging_current); __visible_for_testing int set_input_current(void *data, int v) { union power_supply_propval value = {0, }; struct sec_battery_info *battery = data; battery->input_current = v; battery->charge_power = mW_by_mVmA(battery->input_voltage, v); if (battery->charge_power > battery->max_charge_power) battery->max_charge_power = battery->charge_power; value.intval = v; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); return v; } EXPORT_SYMBOL_KUNIT(set_input_current); __visible_for_testing int set_float_voltage(void *data, int voltage) { struct sec_battery_info *battery = data; union power_supply_propval value = {0, }; value.intval = voltage; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); return voltage; } EXPORT_SYMBOL_KUNIT(set_float_voltage); __visible_for_testing int set_dc_float_voltage(void *data, int voltage) { struct sec_battery_info *battery = data; union power_supply_propval value = {0, }; value.intval = voltage; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_DIRECT_CONSTANT_CHARGE_VOLTAGE, value); return voltage; } EXPORT_SYMBOL_KUNIT(set_dc_float_voltage); __visible_for_testing int set_topoff_current(void *data, int v) { struct sec_battery_info *battery = data; union power_supply_propval value = {0, }; bool do_chgen_vote = false; if (battery->charging_mode == SEC_BATTERY_CHARGING_2ND || battery->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGPSY || battery->pdata->full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) do_chgen_vote = true; if (do_chgen_vote) sec_vote(battery->chgen_vote, VOTER_TOPOFF_CHANGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); value.intval = v; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, value); if (do_chgen_vote) sec_vote(battery->chgen_vote, VOTER_TOPOFF_CHANGE, false, 0); battery->topoff_condition = v; return v; } EXPORT_SYMBOL_KUNIT(set_topoff_current); int get_chg_power_type(int ct, int ws, int pd_max_pw, int max_pw) { if (!is_wireless_type(ct)) { if (is_pd_wire_type(ct) && pd_max_pw >= HV_CHARGER_STATUS_STANDARD4) return SFC_45W; else if (is_pd_wire_type(ct) && pd_max_pw >= HV_CHARGER_STATUS_STANDARD3) return SFC_25W; else if (is_hv_wire_12v_type(ct) || max_pw >= HV_CHARGER_STATUS_STANDARD2) /* 20000mW */ return AFC_12V_OR_20W; else if (is_hv_wire_type(ct) || (is_pd_wire_type(ct) && pd_max_pw >= HV_CHARGER_STATUS_STANDARD1) || #if !defined(CONFIG_BC12_DEVICE) ws == SEC_BATTERY_CABLE_PREPARE_TA || #endif max_pw >= HV_CHARGER_STATUS_STANDARD1) /* 12000mW */ return AFC_9V_OR_15W; } return NORMAL_TA; } EXPORT_SYMBOL_KUNIT(get_chg_power_type); static void sec_bat_run_input_check_work(struct sec_battery_info *battery, int work_delay) { unsigned int ws_duration = 0; unsigned int offset = 3; /* 3 seconds */ pr_info("%s: for %s after %d msec\n", __func__, sb_get_ct_str(battery->cable_type), work_delay); cancel_delayed_work(&battery->input_check_work); ws_duration = offset + (work_delay / 1000); __pm_wakeup_event(battery->input_ws, jiffies_to_msecs(HZ * ws_duration)); queue_delayed_work(battery->monitor_wqueue, &battery->input_check_work, msecs_to_jiffies(work_delay)); } static void sec_bat_cancel_input_check_work(struct sec_battery_info *battery) { cancel_delayed_work(&battery->input_check_work); battery->input_check_cnt = 0; } static bool sec_bat_recheck_input_work(struct sec_battery_info *battery) { return !delayed_work_pending(&battery->input_check_work) && (battery->current_event & SEC_BAT_CURRENT_EVENT_SELECT_PDO); } __visible_for_testing int sec_bat_change_iv(void *data, int voltage) { struct sec_battery_info *battery = data; if ((is_hv_wire_type(battery->cable_type) || is_hv_wire_type(battery->wire_status)) && (battery->cable_type != SEC_BATTERY_CABLE_QC30)) { /* set current event */ sec_bat_check_afc_input_current(battery); #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) #if defined(CONFIG_MTK_CHARGER) && defined(CONFIG_AFC_CHARGER) afc_set_voltage(voltage); #else muic_afc_request_voltage(AFC_REQUEST_CHARGER, voltage/1000); #endif #if defined(CONFIG_BC12_DEVICE) battery->input_voltage = voltage; #endif #endif } else if (is_pd_wire_type(battery->cable_type) || (is_pd_wire_type(battery->wire_status) && is_slate_mode(battery))) { int curr_pdo = 0, pdo = 0, iv = 0, icl = 0; if (voltage == SEC_INPUT_VOLTAGE_APDO) { pr_info("%s : Doesn't control input voltage during Direct Charging\n", __func__); return voltage; } iv = voltage; if (sec_pd_get_pdo_power(&pdo, &iv, &iv, &icl) <= 0) { pr_err("%s: failed to get pdo\n", __func__); return -1; } pr_info("%s: target pdo = %d, iv = %d, icl = %d\n", __func__, pdo, iv, icl); if (sec_pd_get_current_pdo(&curr_pdo) < 0) { pr_err("%s: failed to get current pdo\n", __func__); return -1; } if (curr_pdo != pdo) { /* change input current before request new pdo if new pdo's input current is less than now */ sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SELECT_PDO, SEC_BAT_CURRENT_EVENT_SELECT_PDO); sec_vote(battery->input_vote, VOTER_SELECT_PDO, true, min(battery->sink_status.power_list[pdo].max_current, battery->sink_status.power_list[curr_pdo].max_current)); sec_bat_run_input_check_work(battery, 1000); #if !defined(CONFIG_BC12_DEVICE) battery->pdic_ps_rdy = false; #endif } if (sec_pd_is_apdo(pdo)) sec_pd_select_pps(pdo, voltage, icl); else sec_pd_select_pdo(pdo); } return voltage; } EXPORT_SYMBOL_KUNIT(sec_bat_change_iv); void sec_bat_set_misc_event(struct sec_battery_info *battery, unsigned int misc_event_val, unsigned int misc_event_mask) { unsigned int temp = battery->misc_event; mutex_lock(&battery->misclock); battery->misc_event &= ~misc_event_mask; battery->misc_event |= misc_event_val; pr_info("%s: misc event before(0x%x), after(0x%x)\n", __func__, temp, battery->misc_event); mutex_unlock(&battery->misclock); if (battery->prev_misc_event != battery->misc_event) { cancel_delayed_work(&battery->misc_event_work); __pm_stay_awake(battery->misc_event_ws); queue_delayed_work(battery->monitor_wqueue, &battery->misc_event_work, 0); } } EXPORT_SYMBOL(sec_bat_set_misc_event); void sec_bat_set_tx_event(struct sec_battery_info *battery, unsigned int tx_event_val, unsigned int tx_event_mask) { unsigned int temp = battery->tx_event; mutex_lock(&battery->txeventlock); battery->tx_event &= ~tx_event_mask; battery->tx_event |= tx_event_val; pr_info("@Tx_Mode %s: tx event before(0x%x), after(0x%x)\n", __func__, temp, battery->tx_event); if (temp != battery->tx_event) { /* Assure receiving tx_event to App for sleep case */ __pm_wakeup_event(battery->tx_event_ws, jiffies_to_msecs(HZ * 2)); power_supply_changed(battery->psy_bat); } mutex_unlock(&battery->txeventlock); } EXPORT_SYMBOL_KUNIT(sec_bat_set_tx_event); void sec_bat_set_current_event(struct sec_battery_info *battery, unsigned int current_event_val, unsigned int current_event_mask) { unsigned int temp = battery->current_event; mutex_lock(&battery->current_eventlock); battery->current_event &= ~current_event_mask; battery->current_event |= current_event_val; pr_info("%s: current event before(0x%x), after(0x%x)\n", __func__, temp, battery->current_event); mutex_unlock(&battery->current_eventlock); } EXPORT_SYMBOL(sec_bat_set_current_event); void sec_bat_set_temp_control_test(struct sec_battery_info *battery, bool temp_enable) { if (temp_enable) { if (battery->current_event & SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST) { pr_info("%s : BATT_TEMP_CONTROL_TEST already ENABLED\n", __func__); return; } pr_info("%s : BATT_TEMP_CONTROL_TEST ENABLE\n", __func__); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST, SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST); battery->pdata->usb_temp_check_type_backup = battery->pdata->usb_thm_info.check_type; battery->pdata->usb_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; battery->overheatlimit_threshold_backup = battery->overheatlimit_threshold; battery->overheatlimit_threshold = 990; battery->overheatlimit_recovery_backup = battery->overheatlimit_recovery; battery->overheatlimit_recovery = 980; } else { if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST)) { pr_info("%s : BATT_TEMP_CONTROL_TEST already END\n", __func__); return; } pr_info("%s : BATT_TEMP_CONTROL_TEST END\n", __func__); sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_TEMP_CTRL_TEST); battery->pdata->usb_thm_info.check_type = battery->pdata->usb_temp_check_type_backup; battery->overheatlimit_threshold = battery->overheatlimit_threshold_backup; battery->overheatlimit_recovery = battery->overheatlimit_recovery_backup; } } EXPORT_SYMBOL(sec_bat_set_temp_control_test); void sec_bat_change_default_current(struct sec_battery_info *battery, int cable_type, int input, int output) { #if defined(CONFIG_ENG_BATTERY_CONCEPT) if (!battery->test_max_current) #endif battery->pdata->charging_current[cable_type].input_current_limit = input; #if defined(CONFIG_ENG_BATTERY_CONCEPT) if (!battery->test_charge_current) #endif battery->pdata->charging_current[cable_type].fast_charging_current = output; pr_info("%s: cable_type: %d(%d,%d), input: %d, output: %d\n", __func__, cable_type, battery->cable_type, battery->wire_status, battery->pdata->charging_current[cable_type].input_current_limit, battery->pdata->charging_current[cable_type].fast_charging_current); } EXPORT_SYMBOL_KUNIT(sec_bat_change_default_current); #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) #if defined(CONFIG_SEC_FACTORY) static bool sec_bat_usb_factory_set_vote(struct sec_battery_info *battery, bool vote_en) { if (vote_en) { if ((battery->cable_type == SEC_BATTERY_CABLE_USB) && !(battery->batt_f_mode == IB_MODE) && !sec_bat_get_lpmode()) if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL_NONE) { sec_vote(battery->fcc_vote, VOTER_USB_FAC_100MA, true, 100); sec_vote(battery->input_vote, VOTER_USB_FAC_100MA, true, 100); dev_info(battery->dev, "%s: usb factory 100mA\n", __func__); return true; } } else { if ((battery->cable_type == SEC_BATTERY_CABLE_USB) && !(battery->batt_f_mode == IB_MODE) && !sec_bat_get_lpmode()) if (battery->sink_status.rp_currentlvl != RP_CURRENT_LEVEL_NONE) { sec_vote(battery->fcc_vote, VOTER_USB_FAC_100MA, false, 100); sec_vote(battery->input_vote, VOTER_USB_FAC_100MA, false, 100); dev_info(battery->dev, "%s: recover usb factory 100mA\n", __func__); } } return false; } #endif static void sec_bat_usb_factory_clear(struct sec_battery_info *battery) { union power_supply_propval val = {0, }; #if defined(CONFIG_SEC_FACTORY) if (battery->usb_factory_slate_mode || (battery->batt_f_mode == IB_MODE)) { if (is_slate_mode(battery)) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SLATE); sec_vote(battery->chgen_vote, VOTER_SLATE, false, 0); sec_vote(battery->chgen_vote, VOTER_SMART_SLATE, false, 0); sec_bat_set_mfc_on(battery, WPC_EN_SLATE); dev_info(battery->dev, "%s: disable slate mode\n", __func__); } battery->usb_factory_slate_mode = false; } #endif if (battery->batt_f_mode == IB_MODE) { val.intval = 0; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_IB_MODE, val); } else if (battery->batt_f_mode == OB_MODE) { val.intval = 0; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_OB_MODE_CABLE_REMOVED, val); } } #endif #if !defined(CONFIG_SEC_FACTORY) #if defined(CONFIG_SUPPORT_HV_CTRL) static int sec_bat_chk_siop_scenario_idx(struct sec_battery_info *battery, int siop_level); static bool sec_bat_chk_siop_skip_scenario(struct sec_battery_info *battery, int ct, int ws, int scenario_idx); __visible_for_testing bool sec_bat_change_vbus_condition(int ct, unsigned int evt) { if (!(is_hv_wire_type(ct) || is_pd_wire_type(ct)) || (ct == SEC_BATTERY_CABLE_QC30)) return false; if ((evt & SEC_BAT_CURRENT_EVENT_AFC) || (evt & SEC_BAT_CURRENT_EVENT_SELECT_PDO)) return false; return true; } EXPORT_SYMBOL_KUNIT(sec_bat_change_vbus_condition); __visible_for_testing bool sec_bat_change_vbus(struct sec_battery_info *battery, int ct, unsigned int evt, int siop_level) { int s_idx = -1; if (battery->pdata->chg_thm_info.check_type == SEC_BATTERY_TEMP_CHECK_NONE || battery->store_mode || ((battery->siop_level == 80) && is_wired_type(battery->cable_type))) return false; if (!sec_bat_change_vbus_condition(ct, evt)) return false; s_idx = sec_bat_chk_siop_scenario_idx(battery, battery->siop_level); if ((siop_level >= 100) || sec_bat_chk_siop_skip_scenario(battery, battery->cable_type, battery->wire_status, s_idx)) sec_vote(battery->iv_vote, VOTER_SIOP, false, 0); else { sec_vote(battery->iv_vote, VOTER_SIOP, true, SEC_INPUT_VOLTAGE_5V); pr_info("%s: vbus set 5V by level(%d), Cable(%s, %s, %d, %d)\n", __func__, battery->siop_level, sb_get_ct_str(ct), sb_get_ct_str(battery->wire_status), battery->muic_cable_type, battery->pd_usb_attached); return true; } return false; } EXPORT_SYMBOL_KUNIT(sec_bat_change_vbus); #else __visible_for_testing bool sec_bat_change_vbus(struct sec_battery_info *battery, int ct, unsigned int evt, int siop_level) { return false; } EXPORT_SYMBOL_KUNIT(sec_bat_change_vbus); #endif #endif __visible_for_testing int sec_bat_check_afc_input_current(struct sec_battery_info *battery) { int work_delay = 0; int input_current; sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_AFC, (SEC_BAT_CURRENT_EVENT_CHG_LIMIT | SEC_BAT_CURRENT_EVENT_AFC)); if (!is_wireless_type(battery->cable_type)) { input_current = battery->pdata->pre_afc_input_current; // 1000mA work_delay = battery->pdata->pre_afc_work_delay; } else { input_current = battery->pdata->pre_wc_afc_input_current; /* do not reduce this time, this is for noble pad */ work_delay = battery->pdata->pre_wc_afc_work_delay; } sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, true, input_current); if (!delayed_work_pending(&battery->input_check_work)) sec_bat_run_input_check_work(battery, work_delay); pr_info("%s: change input_current(%d), cable_type(%d)\n", __func__, input_current, battery->cable_type); return input_current; } EXPORT_SYMBOL_KUNIT(sec_bat_check_afc_input_current); __visible_for_testing void sec_bat_get_input_current_in_power_list(struct sec_battery_info *battery) { int pdo_num = battery->sink_status.current_pdo_num; int max_input_current = 0; if (is_pd_apdo_wire_type(battery->wire_status) && battery->pd_list.now_isApdo) pdo_num = 1; max_input_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC].input_current_limit = battery->sink_status.power_list[pdo_num].max_current; battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].input_current_limit = battery->sink_status.power_list[pdo_num].max_current; pr_info("%s:max_input_current : %dmA\n", __func__, max_input_current); sec_vote(battery->input_vote, VOTER_CABLE, true, max_input_current); } EXPORT_SYMBOL_KUNIT(sec_bat_get_input_current_in_power_list); __visible_for_testing void sec_bat_get_charging_current_in_power_list(struct sec_battery_info *battery) { int max_charging_current = 0, pd_power = 0; int pdo_num = battery->sink_status.current_pdo_num; if (is_pd_apdo_wire_type(battery->wire_status) && battery->pd_list.now_isApdo) pdo_num = 1; pd_power = mW_by_mVmA(battery->sink_status.power_list[pdo_num].max_voltage, battery->sink_status.power_list[pdo_num].max_current); /* We assume that output voltage to float voltage */ max_charging_current = mA_by_mWmV(pd_power, (battery->pdata->chg_float_voltage / battery->pdata->chg_float_voltage_conv)); max_charging_current = max_charging_current > battery->pdata->max_charging_current ? battery->pdata->max_charging_current : max_charging_current; battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC].fast_charging_current = max_charging_current; #if defined(CONFIG_STEP_CHARGING) if (battery->dchg_dc_in_swelling && (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING)) { pr_info("%s: max cc(%d) warm_cc(%d)\n", __func__, max_charging_current, battery->pdata->wire_warm_current); max_charging_current = max(max_charging_current, battery->pdata->wire_warm_current); } if (is_pd_apdo_wire_type(battery->wire_status) && !battery->pd_list.now_isApdo && battery->step_chg_status < 0) #else if (is_pd_apdo_wire_type(battery->wire_status) && !battery->pd_list.now_isApdo) #endif battery->pdata->charging_current[SEC_BATTERY_CABLE_PDIC_APDO].fast_charging_current = max_charging_current; battery->charge_power = pd_power; pr_info("%s:pd_charge_power : %dmW, max_charging_current : %dmA\n", __func__, battery->charge_power, max_charging_current); sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->charging_current[battery->wire_status].fast_charging_current); } EXPORT_SYMBOL_KUNIT(sec_bat_get_charging_current_in_power_list); #if !defined(CONFIG_SEC_FACTORY) void sec_bat_check_temp_ctrl_by_cable(struct sec_battery_info *battery) { int ct = battery->cable_type; int siop_lvl = battery->siop_level; bool is_apdo = false; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) is_apdo = (is_pd_apdo_wire_type(ct) && battery->pd_list.now_isApdo) ? 1 : 0; #endif if (battery->pdata->enable_mix_v2) sec_bat_check_mix_temp_v2(battery); else sec_bat_check_mix_temp(battery, ct, siop_lvl, is_apdo); if (is_apdo) { sec_bat_check_direct_chg_temp(battery, siop_lvl); } else if (is_wireless_type(ct)) { if (battery->pdata->enable_check_wpc_temp_v2) sec_bat_check_wpc_temp_v2(battery); else sec_bat_check_wpc_temp(battery, ct, siop_lvl); } else { if (!sec_bat_change_vbus(battery, ct, battery->current_event, siop_lvl)) { if (is_pd_wire_type(ct)) sec_bat_check_pdic_temp(battery, siop_lvl); else { sec_bat_check_afc_temp(battery, siop_lvl); } } else if (battery->chg_limit) { sec_vote(battery->fcc_vote, VOTER_CHG_TEMP, false, 0); sec_vote(battery->input_vote, VOTER_CHG_TEMP, false, 0); battery->chg_limit = false; } } if (!is_wireless_fake_type(ct)) sec_bat_check_lrp_temp(battery, ct, battery->wire_status, siop_lvl, battery->lcd_status); pr_info("%s: ct : %s\n", __func__, sb_get_ct_str(ct)); } #endif int sec_bat_set_charging_current(struct sec_battery_info *battery) { mutex_lock(&battery->iolock); if (!is_nocharge_type(battery->cable_type)) { #if !defined(CONFIG_SEC_FACTORY) sec_bat_check_temp_ctrl_by_cable(battery); #endif #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) /* Calculate wireless input current under the specific conditions (wpc_sleep_mode, chg_limit)*/ /* VOTER_WPC_CUR */ if (battery->wc_status != SEC_BATTERY_CABLE_NONE) { sec_bat_get_wireless_current(battery); } #endif #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (battery->dc_float_voltage_set) { int age_step = battery->pdata->age_step; int chg_step = battery->step_chg_status; pr_info("%s : step float voltage = %d\n", __func__, battery->pdata->dc_step_chg_val_vfloat[age_step][chg_step]); sec_vote(battery->dc_fv_vote, VOTER_DC_STEP_CHARGE, true, battery->pdata->dc_step_chg_val_vfloat[age_step][chg_step]); battery->dc_float_voltage_set = false; } #endif /* check topoff current */ if (battery->charging_mode == SEC_BATTERY_CHARGING_2ND && (battery->pdata->full_check_type_2nd == SEC_BATTERY_FULLCHARGED_CHGPSY || battery->pdata->full_check_type_2nd == SEC_BATTERY_FULLCHARGED_LIMITER)) { sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, true, battery->pdata->full_check_current_2nd); } }/* !is_nocharge_type(battery->cable_type) */ mutex_unlock(&battery->iolock); return 0; } EXPORT_SYMBOL(sec_bat_set_charging_current); int sec_bat_set_charge(void * data, int chg_mode) { struct sec_battery_info *battery = data; union power_supply_propval val = {0, }; struct timespec64 ts = {0, }; if (chg_mode == SEC_BAT_CHG_MODE_NOT_SET) { pr_info("%s: temp mode for decrease 5v\n", __func__); return chg_mode; } if ((battery->current_event & SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE) && (chg_mode == SEC_BAT_CHG_MODE_CHARGING)) { dev_info(battery->dev, "%s: charge disable by HMT\n", __func__); chg_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; } battery->charger_mode = chg_mode; pr_info("%s set %s mode\n", __func__, sb_charge_mode_str(chg_mode)); val.intval = battery->status; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_STATUS, val); ts = ktime_to_timespec64(ktime_get_boottime()); if (chg_mode == SEC_BAT_CHG_MODE_CHARGING) { /*Reset charging start time only in initial charging start */ if (battery->charging_start_time == 0) { if (ts.tv_sec < 1) ts.tv_sec = 1; battery->charging_start_time = ts.tv_sec; battery->charging_next_time = battery->pdata->charging_reset_time; } #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (is_pd_apdo_wire_type(battery->cable_type)) { sec_bat_reset_step_charging(battery); sec_bat_check_dc_step_charging(battery); } #endif } else { battery->charging_start_time = 0; battery->charging_passed_time = 0; battery->charging_next_time = 0; battery->charging_fullcharged_time = 0; battery->full_check_cnt = 0; #if defined(CONFIG_STEP_CHARGING) sec_bat_reset_step_charging(battery); #endif #if defined(CONFIG_BATTERY_CISD) battery->usb_overheat_check = false; battery->cisd.ab_vbat_check_count = 0; #endif } val.intval = chg_mode; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); val.intval = chg_mode; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); #if IS_ENABLED(CONFIG_DUAL_BATTERY) if ((battery->pdata->full_check_type_2nd == SEC_BATTERY_FULLCHARGED_FG_CURRENT) && (battery->charging_mode == SEC_BATTERY_CHARGING_NONE && battery->status == POWER_SUPPLY_STATUS_FULL)) { /* * Enable supplement mode for 2nd charging done, should cut off charger then limiter sequence, * this case only for full_check_type_2nd is * SEC_BATTERY_FULLCHARGED_FG_CURRENT not SEC_BATTERY_FULLCHARGED_LIMITER */ val.intval = 1; psy_do_property(battery->pdata->dual_battery_name, set, POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); } else if (!(battery->charging_mode == SEC_BATTERY_CHARGING_NONE && battery->status == POWER_SUPPLY_STATUS_FULL) && !(battery->charging_mode == SEC_BATTERY_CHARGING_NONE && battery->thermal_zone == BAT_THERMAL_WARM)) { /* * Limiter should disable supplement mode to do battery balancing properly in case of * charging, discharging and buck off. But needs to disable supplement mode except * 2nd full charge done and swelling charging. */ val.intval = 0; psy_do_property(battery->pdata->dual_battery_name, set, POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED, val); } #endif return chg_mode; } EXPORT_SYMBOL(sec_bat_set_charge); __visible_for_testing bool sec_bat_check_by_psy(struct sec_battery_info *battery) { char *psy_name = NULL; union power_supply_propval value = {0, }; bool ret = true; switch (battery->pdata->battery_check_type) { case SEC_BATTERY_CHECK_PMIC: psy_name = battery->pdata->pmic_name; break; case SEC_BATTERY_CHECK_FUELGAUGE: psy_name = battery->pdata->fuelgauge_name; break; case SEC_BATTERY_CHECK_CHARGER: psy_name = battery->pdata->charger_name; break; default: dev_err(battery->dev, "%s: Invalid Battery Check Type\n", __func__); ret = false; goto battery_check_error; break; } psy_do_property(psy_name, get, POWER_SUPPLY_PROP_PRESENT, value); ret = (bool)value.intval; battery_check_error: return ret; } EXPORT_SYMBOL_KUNIT(sec_bat_check_by_psy); #if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) static bool sec_bat_check_by_gpio(struct sec_battery_info *battery) { union power_supply_propval value = {0, }; bool ret = true; int main_det = -1, sub_det = -1; value.intval = SEC_DUAL_BATTERY_MAIN; psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET, value); main_det = value.intval; value.intval = SEC_DUAL_BATTERY_SUB; psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_EXT_PROP_DUAL_BAT_DET, value); sub_det = value.intval; ret = (bool)(main_det & sub_det); if (!ret) pr_info("%s : main det = %d, sub det = %d\n", __func__, main_det, sub_det); return ret; } #if !defined(CONFIG_SEC_FACTORY) static void sec_bat_powerpath_check(struct sec_battery_info *battery, int m_health, int s_health) { static int m_abnormal_cnt, s_abnormal_cnt; bool m_supplement_status = false, s_supplement_status = false; union power_supply_propval value = {0, }; /* do not check battery power path when limiter is not ok */ if (m_health != POWER_SUPPLY_HEALTH_GOOD || s_health != POWER_SUPPLY_HEALTH_GOOD) { pr_info("%s : do not check current status\n", __func__); return; } /* get main limiter's supplement status */ psy_do_property(battery->pdata->main_limiter_name, get, POWER_SUPPLY_EXT_PROP_SUPLLEMENT_MODE, value); m_supplement_status = value.intval; /* get sub limiter's supplement status */ psy_do_property(battery->pdata->sub_limiter_name, get, POWER_SUPPLY_EXT_PROP_SUPLLEMENT_MODE, value); s_supplement_status = value.intval; if ((battery->current_now_main == 0) && !m_supplement_status && !(battery->misc_event & BATT_MISC_EVENT_MAIN_POWERPATH)) { m_abnormal_cnt++; } else if (battery->current_now_main != 0) { if (battery->misc_event & BATT_MISC_EVENT_MAIN_POWERPATH) { pr_info("%s : main power path get back to normal\n", __func__); sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_MAIN_POWERPATH); } m_abnormal_cnt = 0; } if ((battery->current_now_sub == 0) && !s_supplement_status && !(battery->misc_event & BATT_MISC_EVENT_SUB_POWERPATH)) { s_abnormal_cnt++; } else if (battery->current_now_sub != 0) { if (battery->misc_event & BATT_MISC_EVENT_SUB_POWERPATH) { pr_info("%s : sub power path get back to normal\n", __func__); sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_SUB_POWERPATH); } s_abnormal_cnt = 0; } if (m_abnormal_cnt > 3) { pr_info("%s : main power path seems to have problem\n", __func__); sec_bat_set_misc_event(battery, BATT_MISC_EVENT_MAIN_POWERPATH, BATT_MISC_EVENT_MAIN_POWERPATH); m_abnormal_cnt = 0; #if IS_ENABLED(CONFIG_SEC_ABC) sec_abc_send_event("MODULE=battery@WARN=pp_open"); #endif } if (s_abnormal_cnt > 3) { pr_info("%s : sub power path seems to have problem\n", __func__); sec_bat_set_misc_event(battery, BATT_MISC_EVENT_SUB_POWERPATH, BATT_MISC_EVENT_SUB_POWERPATH); s_abnormal_cnt = 0; #if IS_ENABLED(CONFIG_SEC_ABC) sec_abc_send_event("MODULE=battery@WARN=pp_open"); #endif } } static void sec_bat_limiter_check(struct sec_battery_info *battery) { union power_supply_propval m_value = {0, }, s_value = {0, }; int main_enb, main_enb2, sub_enb; /* do not check limiter status when enb is not active status since it is certain test mode using enb pin */ main_enb = gpio_get_value(battery->pdata->main_bat_enb_gpio); main_enb2 = gpio_get_value(battery->pdata->main_bat_enb2_gpio); sub_enb = gpio_get_value(battery->pdata->sub_bat_enb_gpio); if (main_enb2 || sub_enb) { pr_info("%s : main_enb = %d, main_enb2 = %d, sub_enb = %d\n", __func__, main_enb, main_enb2, sub_enb); return; } /* check powermeter and vchg, vbat value of main limiter */ psy_do_property(battery->pdata->main_limiter_name, get, POWER_SUPPLY_PROP_HEALTH, m_value); /* check powermeter and vchg, vbat value of sub limiter */ psy_do_property(battery->pdata->sub_limiter_name, get, POWER_SUPPLY_PROP_HEALTH, s_value); if (is_nocharge_type(battery->cable_type)) sec_bat_powerpath_check(battery, m_value.intval, s_value.intval); /* do not check limiter status input curruent is not set fully */ if (is_nocharge_type(battery->cable_type) || is_wireless_fake_type(battery->cable_type) || battery->health != POWER_SUPPLY_HEALTH_GOOD || battery->status != POWER_SUPPLY_STATUS_CHARGING || battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE || battery->current_event & SEC_BAT_CURRENT_EVENT_AICL || battery->charge_power < 9000 || battery->siop_level < 100 || battery->lcd_status || battery->limiter_check) { return; } if (m_value.intval != POWER_SUPPLY_HEALTH_GOOD) { pr_info("%s : main limiter wa will work\n", __func__); #if defined(CONFIG_BATTERY_CISD) battery->cisd.event_data[EVENT_MAIN_BAT_ERR]++; #endif sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); m_value.intval = 400; psy_do_property(battery->pdata->main_limiter_name, set, POWER_SUPPLY_EXT_PROP_DISCHG_LIMIT_CURRENT, m_value); m_value.intval = 0; psy_do_property(battery->pdata->main_limiter_name, set, POWER_SUPPLY_EXT_PROP_DISCHG_MODE, m_value); /* deactivate main limiter */ gpio_direction_output(battery->pdata->main_bat_enb2_gpio, 1); msleep(100); /* activate main limiter */ gpio_direction_output(battery->pdata->main_bat_enb2_gpio, 0); msleep(100); /* needs to init limiter setting again */ m_value.intval = 1; psy_do_property(battery->pdata->main_limiter_name, set, POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, m_value); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING); battery->limiter_check = true; pr_info("%s : main limiter wa done\n", __func__); /* re-check powermeter and vchg, vbat value of main limiter */ psy_do_property(battery->pdata->main_limiter_name, get, POWER_SUPPLY_PROP_HEALTH, m_value); if (m_value.intval != POWER_SUPPLY_HEALTH_GOOD) { pr_info("%s : main limiter wa did not work\n", __func__); #if defined(CONFIG_BATTERY_CISD) battery->cisd.event_data[EVENT_BAT_WA_ERR]++; #endif } #if IS_ENABLED(CONFIG_SEC_ABC) sec_abc_send_event("MODULE=battery@WARN=lim_stuck"); #endif } if (s_value.intval != POWER_SUPPLY_HEALTH_GOOD) { pr_info("%s : sub limiter wa will work\n", __func__); #if defined(CONFIG_BATTERY_CISD) battery->cisd.event_data[EVENT_SUB_BAT_ERR]++; #endif sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); s_value.intval = 400; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_EXT_PROP_DISCHG_LIMIT_CURRENT, s_value); s_value.intval = 0; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_EXT_PROP_DISCHG_MODE, s_value); /* deactivate sub limiter */ gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 1); msleep(100); /* activate sub limiter */ gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 0); msleep(100); /* needs to init limiter setting again */ s_value.intval = 1; psy_do_property(battery->pdata->sub_limiter_name, set, POWER_SUPPLY_EXT_PROP_POWERMETER_ENABLE, s_value); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING); battery->limiter_check = true; pr_info("%s : sub limiter wa done\n", __func__); /* re-check powermeter and vchg, vbat value of sub limiter */ psy_do_property(battery->pdata->sub_limiter_name, get, POWER_SUPPLY_PROP_HEALTH, s_value); if (s_value.intval != POWER_SUPPLY_HEALTH_GOOD) { pr_info("%s : main limiter wa did not work\n", __func__); #if defined(CONFIG_BATTERY_CISD) battery->cisd.event_data[EVENT_BAT_WA_ERR]++; #endif } #if IS_ENABLED(CONFIG_SEC_ABC) sec_abc_send_event("MODULE=battery@WARN=lim_stuck"); #endif } } #endif #endif static bool sec_bat_check(struct sec_battery_info *battery) { bool ret = true; if (battery->factory_mode || battery->is_jig_on || sec_bat_get_facmode()) { dev_dbg(battery->dev, "%s: No need to check in factory mode\n", __func__); return ret; } if (battery->health != POWER_SUPPLY_HEALTH_GOOD && battery->health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { dev_dbg(battery->dev, "%s: No need to check\n", __func__); return ret; } switch (battery->pdata->battery_check_type) { case SEC_BATTERY_CHECK_ADC: if (is_nocharge_type(battery->cable_type)) ret = battery->present; else ret = sec_bat_check_vf_adc(battery); break; case SEC_BATTERY_CHECK_INT: case SEC_BATTERY_CHECK_CALLBACK: if (is_nocharge_type(battery->cable_type)) { ret = battery->present; } else { if (battery->pdata->check_battery_callback) ret = battery->pdata->check_battery_callback(); } break; case SEC_BATTERY_CHECK_PMIC: case SEC_BATTERY_CHECK_FUELGAUGE: case SEC_BATTERY_CHECK_CHARGER: ret = sec_bat_check_by_psy(battery); break; #if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) case SEC_BATTERY_CHECK_DUAL_BAT_GPIO: ret = sec_bat_check_by_gpio(battery); break; #endif case SEC_BATTERY_CHECK_NONE: dev_dbg(battery->dev, "%s: No Check\n", __func__); default: break; } return ret; } static void sec_bat_send_cs100(struct sec_battery_info *battery) { union power_supply_propval value = {0, }; if (is_wireless_fake_type(battery->cable_type)) { value.intval = POWER_SUPPLY_STATUS_FULL; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_STATUS, value); } } __visible_for_testing bool sec_bat_get_cable_type(struct sec_battery_info *battery, int cable_source_type) { bool ret = false; int cable_type = battery->cable_type; if (cable_source_type & SEC_BATTERY_CABLE_SOURCE_CALLBACK) { if (battery->pdata->check_cable_callback) cable_type = battery->pdata->check_cable_callback(); } if (cable_source_type & SEC_BATTERY_CABLE_SOURCE_ADC) { if (gpio_get_value_cansleep( battery->pdata->bat_gpio_ta_nconnected) ^ battery->pdata->bat_polarity_ta_nconnected) cable_type = SEC_BATTERY_CABLE_NONE; else cable_type = sec_bat_get_charger_type_adc(battery); } if (battery->cable_type == cable_type) { dev_dbg(battery->dev, "%s: No need to change cable status\n", __func__); } else { if (cable_type < SEC_BATTERY_CABLE_NONE || cable_type >= SEC_BATTERY_CABLE_MAX) { dev_err(battery->dev, "%s: Invalid cable type\n", __func__); } else { battery->cable_type = cable_type; if (battery->pdata->check_cable_result_callback) battery->pdata->check_cable_result_callback(battery->cable_type); ret = true; dev_dbg(battery->dev, "%s: Cable Changed (%d)\n", __func__, battery->cable_type); } } return ret; } EXPORT_SYMBOL_KUNIT(sec_bat_get_cable_type); void sec_bat_set_charging_status(struct sec_battery_info *battery, int status) { union power_supply_propval value = {0, }; #if defined(CONFIG_NO_BATTERY) status = POWER_SUPPLY_STATUS_DISCHARGING; #endif switch (status) { case POWER_SUPPLY_STATUS_CHARGING: if (battery->siop_level < 100 || battery->lcd_status || battery->wc_tx_enable) battery->stop_timer = true; break; case POWER_SUPPLY_STATUS_NOT_CHARGING: case POWER_SUPPLY_STATUS_DISCHARGING: if ((battery->status == POWER_SUPPLY_STATUS_FULL || (battery->capacity == 100 && !is_slate_mode(battery))) && !battery->store_mode) { pr_info("%s : Update fg scale to 101%%\n", __func__); value.intval = 100; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_CHARGE_FULL, value); /* To get SOC value (NOT raw SOC), need to reset value */ value.intval = 0; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CAPACITY, value); battery->capacity = value.intval; } battery->expired_time = battery->pdata->expired_time; battery->prev_safety_time = 0; break; case POWER_SUPPLY_STATUS_FULL: sec_bat_send_cs100(battery); break; default: break; } battery->status = status; } EXPORT_SYMBOL_KUNIT(sec_bat_set_charging_status); void sec_bat_set_health(struct sec_battery_info *battery, int health) { if (battery->health != health) { if (health == POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT) sec_bat_set_misc_event(battery, BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT, BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT); else sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_HEALTH_OVERHEATLIMIT); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) && !defined(CONFIG_SEC_FACTORY) if (is_wireless_fake_type(battery->cable_type)) { union power_supply_propval val = {0, }; if (!battery->wc_ept_timeout) { battery->wc_ept_timeout = true; __pm_stay_awake(battery->wc_ept_timeout_ws); /* 10 secs time out */ queue_delayed_work(battery->monitor_wqueue, &battery->wc_ept_timeout_work, msecs_to_jiffies(10000)); val.intval = health; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_HEALTH, val); } } #endif } battery->health = health; if (health != POWER_SUPPLY_HEALTH_GOOD) store_battery_log( "Health:%d%%,%dmV,%s,ct(%s,%s,%d,%d),%s", battery->capacity, battery->voltage_now, sb_get_bst_str(battery->status), sb_get_ct_str(battery->cable_type), sb_get_ct_str(battery->wire_status), battery->muic_cable_type, battery->pd_usb_attached, sb_get_hl_str(battery->health) ); } EXPORT_SYMBOL_KUNIT(sec_bat_set_health); static bool sec_bat_battery_cable_check(struct sec_battery_info *battery) { if (!sec_bat_check(battery)) { if (battery->check_count < battery->pdata->check_count) battery->check_count++; else { dev_err(battery->dev, "%s: Battery Disconnected\n", __func__); battery->present = false; sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_UNSPEC_FAILURE); if (battery->status != POWER_SUPPLY_STATUS_DISCHARGING) { sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); sec_vote(battery->chgen_vote, VOTER_BATTERY, true, SEC_BAT_CHG_MODE_BUCK_OFF); } if (battery->pdata->check_battery_result_callback) battery->pdata-> check_battery_result_callback(); return false; } } else battery->check_count = 0; battery->present = true; if (battery->health == POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) { sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING) { sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); sec_vote(battery->chgen_vote, VOTER_BATTERY, false, 0); } } dev_dbg(battery->dev, "%s: Battery Connected\n", __func__); if (battery->pdata->cable_check_type & SEC_BATTERY_CABLE_CHECK_POLLING) { if (sec_bat_get_cable_type(battery, battery->pdata->cable_source_type)) { __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); } } return true; } static int sec_bat_ovp_uvlo_by_psy(struct sec_battery_info *battery) { char *psy_name = NULL; union power_supply_propval value = {0, }; int ret = 0; value.intval = POWER_SUPPLY_HEALTH_GOOD; switch (battery->pdata->ovp_uvlo_check_type) { case SEC_BATTERY_OVP_UVLO_PMICPOLLING: psy_name = battery->pdata->pmic_name; break; case SEC_BATTERY_OVP_UVLO_CHGPOLLING: psy_name = battery->pdata->charger_name; break; default: dev_err(battery->dev, "%s: Invalid OVP/UVLO Check Type\n", __func__); goto ovp_uvlo_check_error; break; } ret = psy_do_property(psy_name, get, POWER_SUPPLY_PROP_HEALTH, value); ovp_uvlo_check_error: /* Need to not display HEALTH UNKNOWN if driver cannot be read */ return ((ret == 0) ? value.intval : POWER_SUPPLY_HEALTH_GOOD); } static bool sec_bat_ovp_uvlo_result(struct sec_battery_info *battery, int health) { if (health == POWER_SUPPLY_EXT_HEALTH_DC_ERR) { dev_info(battery->dev, "%s: DC err (%d)\n", __func__, health); battery->is_recharging = false; battery->health_check_count = DEFAULT_HEALTH_CHECK_COUNT; __pm_wakeup_event(battery->vbus_ws, jiffies_to_msecs(HZ * 10)); /* Enable charging anyway to check actual DC's health */ sec_vote(battery->chgen_vote, VOTER_DC_ERR, true, SEC_BAT_CHG_MODE_CHARGING_OFF); sec_vote(battery->chgen_vote, VOTER_DC_ERR, false, 0); } if (battery->health != health) { sec_bat_set_health(battery, health); switch (health) { case POWER_SUPPLY_HEALTH_GOOD: dev_info(battery->dev, "%s: Safe voltage\n", __func__); dev_info(battery->dev, "%s: is_recharging : %d\n", __func__, battery->is_recharging); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); battery->charging_mode = SEC_BATTERY_CHARGING_1ST; sec_vote(battery->chgen_vote, VOTER_VBUS_OVP, false, 0); battery->health_check_count = 0; break; case POWER_SUPPLY_HEALTH_OVERVOLTAGE: case POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE: dev_info(battery->dev, "%s: Unsafe voltage (%d)\n", __func__, health); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); sec_vote(battery->chgen_vote, VOTER_VBUS_OVP, true, SEC_BAT_CHG_MODE_CHARGING_OFF); battery->charging_mode = SEC_BATTERY_CHARGING_NONE; battery->is_recharging = false; battery->health_check_count = DEFAULT_HEALTH_CHECK_COUNT; #if defined(CONFIG_BATTERY_CISD) battery->cisd.data[CISD_DATA_UNSAFETY_VOLTAGE]++; battery->cisd.data[CISD_DATA_UNSAFE_VOLTAGE_PER_DAY]++; #endif /* * Take the wakelock during 10 seconds * when over-voltage status is detected */ __pm_wakeup_event(battery->vbus_ws, jiffies_to_msecs(HZ * 10)); break; case POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE: dev_info(battery->dev, "%s: watchdog timer expired (%d)\n", __func__, health); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); sec_vote(battery->chgen_vote, VOTER_WDT_EXPIRE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); battery->charging_mode = SEC_BATTERY_CHARGING_NONE; battery->is_recharging = false; battery->health_check_count = DEFAULT_HEALTH_CHECK_COUNT; /* * Take the wakelock during 10 seconds * when watchdog timer expired is detected */ __pm_wakeup_event(battery->vbus_ws, jiffies_to_msecs(HZ * 10)); break; } power_supply_changed(battery->psy_bat); if (health != POWER_SUPPLY_HEALTH_GOOD) return true; } return false; } static bool sec_bat_ovp_uvlo(struct sec_battery_info *battery) { int health = POWER_SUPPLY_HEALTH_GOOD; if (battery->wdt_kick_disable) { dev_dbg(battery->dev, "%s: No need to check in wdt test\n", __func__); return false; } else if ((battery->status == POWER_SUPPLY_STATUS_FULL) && (battery->charging_mode == SEC_BATTERY_CHARGING_NONE)) { dev_dbg(battery->dev, "%s: No need to check in Full status", __func__); return false; } if (battery->health != POWER_SUPPLY_HEALTH_GOOD && battery->health != POWER_SUPPLY_HEALTH_OVERVOLTAGE && battery->health != POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE && battery->health != POWER_SUPPLY_EXT_HEALTH_DC_ERR && battery->health != POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE) { dev_dbg(battery->dev, "%s: No need to check\n", __func__); return false; } health = battery->health; switch (battery->pdata->ovp_uvlo_check_type) { case SEC_BATTERY_OVP_UVLO_CALLBACK: if (battery->pdata->ovp_uvlo_callback) health = battery->pdata->ovp_uvlo_callback(); break; case SEC_BATTERY_OVP_UVLO_PMICPOLLING: case SEC_BATTERY_OVP_UVLO_CHGPOLLING: health = sec_bat_ovp_uvlo_by_psy(battery); break; case SEC_BATTERY_OVP_UVLO_PMICINT: case SEC_BATTERY_OVP_UVLO_CHGINT: /* nothing for interrupt check */ default: break; } if (battery->factory_mode) { dev_dbg(battery->dev, "%s: No need to check in factory mode\n", __func__); return false; } return sec_bat_ovp_uvlo_result(battery, health); } __visible_for_testing bool sec_bat_check_recharge(struct sec_battery_info *battery) { if (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING) return false; if ((battery->status == POWER_SUPPLY_STATUS_CHARGING) && (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_NOTIMEFULL) && (battery->charging_mode == SEC_BATTERY_CHARGING_NONE)) { dev_info(battery->dev, "%s: Re-charging by NOTIMEFULL (%d)\n", __func__, battery->capacity); goto check_recharge_check_count; } if (battery->status == POWER_SUPPLY_STATUS_FULL && battery->charging_mode == SEC_BATTERY_CHARGING_NONE) { int recharging_voltage = battery->pdata->recharge_condition_vcell; if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE) { if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) recharging_voltage = battery->pdata->swelling_low_cool3_rechg_voltage; else recharging_voltage = battery->pdata->swelling_low_rechg_voltage; } dev_info(battery->dev, "%s: recharging voltage(%d) %s\n", __func__, recharging_voltage, battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE ? "changed by low temp" : ""); if ((battery->pdata->recharge_condition_type & SEC_BATTERY_RECHARGE_CONDITION_SOC) && (battery->capacity <= battery->pdata->recharge_condition_soc)) { dev_info(battery->dev, "%s: Re-charging by SOC (%d)\n", __func__, battery->capacity); goto check_recharge_check_count; } if ((battery->pdata->recharge_condition_type & SEC_BATTERY_RECHARGE_CONDITION_AVGVCELL) && (battery->voltage_avg <= recharging_voltage)) { dev_info(battery->dev, "%s: Re-charging by average VCELL (%d)\n", __func__, battery->voltage_avg); goto check_recharge_check_count; } if ((battery->pdata->recharge_condition_type & SEC_BATTERY_RECHARGE_CONDITION_VCELL) && (battery->voltage_now <= recharging_voltage)) { dev_info(battery->dev, "%s: Re-charging by VCELL (%d)\n", __func__, battery->voltage_now); goto check_recharge_check_count; } #if IS_ENABLED(CONFIG_DUAL_BATTERY) if (battery->pdata->recharge_condition_type & SEC_BATTERY_RECHARGE_CONDITION_LIMITER) { int voltage = max(battery->voltage_pack_main, battery->voltage_pack_sub); if (voltage <= recharging_voltage) { dev_info(battery->dev, "%s: Re-charging by VPACK (%d)mV\n", __func__, voltage); goto check_recharge_check_count; } else if (abs(battery->voltage_pack_main - battery->voltage_pack_sub) > battery->pdata->force_recharge_margin) { dev_info(battery->dev, "%s: Force Re-charging by VPACK diff(%d, %d)mV\n", __func__, battery->voltage_pack_main, battery->voltage_pack_sub); goto check_recharge_check_count; } } #endif } battery->recharge_check_cnt = 0; return false; check_recharge_check_count: battery->expired_time = battery->pdata->recharging_expired_time; battery->prev_safety_time = 0; if (battery->recharge_check_cnt < battery->pdata->recharge_check_count) battery->recharge_check_cnt++; dev_dbg(battery->dev, "%s: recharge count = %d\n", __func__, battery->recharge_check_cnt); if (battery->recharge_check_cnt >= battery->pdata->recharge_check_count) return true; else return false; } EXPORT_SYMBOL_KUNIT(sec_bat_check_recharge); static bool sec_bat_voltage_check(struct sec_battery_info *battery) { union power_supply_propval value = {0, }; if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING || is_nocharge_type(battery->cable_type) || battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_FAKE) { dev_dbg(battery->dev, "%s: Charging Disabled\n", __func__); return true; } /* OVP/UVLO check */ if (sec_bat_ovp_uvlo(battery)) { if (battery->pdata->ovp_uvlo_result_callback) battery->pdata-> ovp_uvlo_result_callback(battery->health); return false; } if ((battery->status == POWER_SUPPLY_STATUS_FULL) && ((battery->charging_mode != SEC_BATTERY_CHARGING_NONE && battery->charger_mode == SEC_BAT_CHG_MODE_CHARGING) || (battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING))) { int voltage_now = battery->voltage_now; int voltage_ref = battery->pdata->recharge_condition_vcell - 50; #if defined(CONFIG_ENABLE_FULL_BY_SOC) int soc_ref = 98; #else int soc_ref = battery->pdata->full_condition_soc; #endif pr_info("%s: chg mode (%d), voltage_ref(%d), voltage_now(%d)\n", __func__, battery->charging_mode, voltage_ref, voltage_now); if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_MODE) { if (battery->current_event & SEC_BAT_CURRENT_EVENT_LOW_TEMP_SWELLING_COOL3) voltage_ref = battery->pdata->swelling_low_cool3_rechg_voltage - 50; else voltage_ref = battery->pdata->swelling_low_rechg_voltage - 50; } value.intval = 0; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CAPACITY, value); #if IS_ENABLED(CONFIG_DUAL_BATTERY) if (battery->pdata->recharge_condition_type & SEC_BATTERY_RECHARGE_CONDITION_LIMITER) { voltage_now = max(battery->voltage_pack_main, battery->voltage_pack_sub); } #endif if (value.intval < soc_ref && voltage_now < voltage_ref) { sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); battery->is_recharging = false; battery->charging_mode = SEC_BATTERY_CHARGING_1ST; sec_vote(battery->chgen_vote, VOTER_CABLE, true, SEC_BAT_CHG_MODE_CHARGING); sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); pr_info("%s: battery status full -> charging, RepSOC(%d)\n", __func__, value.intval); value.intval = POWER_SUPPLY_STATUS_CHARGING; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_STATUS, value); return false; } } /* Re-Charging check */ if (sec_bat_check_recharge(battery)) { if (battery->pdata->full_check_type != SEC_BATTERY_FULLCHARGED_NONE) battery->charging_mode = SEC_BATTERY_CHARGING_1ST; else battery->charging_mode = SEC_BATTERY_CHARGING_2ND; battery->is_recharging = true; #if defined(CONFIG_BATTERY_CISD) battery->cisd.data[CISD_DATA_RECHARGING_COUNT]++; battery->cisd.data[CISD_DATA_RECHARGING_COUNT_PER_DAY]++; #endif if (is_wireless_type(battery->cable_type)) { value.intval = WIRELESS_VOUT_CC_CV_VOUT; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION, value); value.intval = WIRELESS_VRECT_ADJ_OFF; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL, value); } sec_vote(battery->chgen_vote, VOTER_CABLE, true, SEC_BAT_CHG_MODE_CHARGING); sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); return false; } return true; } #if defined(CONFIG_BATTERY_AGE_FORECAST) __visible_for_testing bool sec_bat_set_aging_step(struct sec_battery_info *battery, int step) { union power_supply_propval value = {0, }; if (battery->pdata->num_age_step <= 0 || step < 0 || step >= battery->pdata->num_age_step) { pr_info("%s: [AGE] abnormal age step : %d/%d\n", __func__, step, battery->pdata->num_age_step-1); return false; } battery->pdata->age_step = step; /* float voltage */ battery->pdata->chg_float_voltage = battery->pdata->age_data[battery->pdata->age_step].float_voltage; sec_vote(battery->fv_vote, VOTER_AGING_STEP, true, battery->pdata->chg_float_voltage); /* full/recharge condition */ battery->pdata->recharge_condition_vcell = battery->pdata->age_data[battery->pdata->age_step].recharge_condition_vcell; battery->pdata->full_condition_soc = battery->pdata->age_data[battery->pdata->age_step].full_condition_soc; battery->pdata->full_condition_vcell = battery->pdata->age_data[battery->pdata->age_step].full_condition_vcell; #if IS_ENABLED(CONFIG_DUAL_BATTERY) value.intval = battery->pdata->age_step; psy_do_property(battery->pdata->dual_battery_name, set, POWER_SUPPLY_EXT_PROP_FULL_CONDITION, value); #endif #if defined(CONFIG_LSI_IFPMIC) value.intval = battery->pdata->age_step; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_EXT_PROP_UPDATE_BATTERY_DATA, value); #else value.intval = battery->pdata->full_condition_soc; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_CAPACITY_LEVEL, value); #endif #if defined(CONFIG_STEP_CHARGING) sec_bat_set_aging_info_step_charging(battery); #endif #if defined(CONFIG_MTK_CHARGER) if (battery->pdata->dynamic_cv_factor) { value.intval = (battery->pdata->chg_float_voltage) * 1000; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, value); } #endif dev_info(battery->dev, "%s: Step(%d/%d), Cycle(%d), float_v(%d), r_v(%d), f_s(%d), f_vl(%d)\n", __func__, battery->pdata->age_step, battery->pdata->num_age_step-1, battery->batt_cycle, battery->pdata->chg_float_voltage, battery->pdata->recharge_condition_vcell, battery->pdata->full_condition_soc, battery->pdata->full_condition_vcell); #if defined(CONFIG_BATTERY_AGE_FORECAST_B2B) { int i; bool bChanged = false; battery->pdata->max_charging_current = battery->pdata->age_data[battery->pdata->age_step].max_charging_current; for (i = 0; i < SEC_BATTERY_CABLE_MAX; i++) { if (battery->pdata->charging_current[i].fast_charging_current > battery->pdata->max_charging_current) { dev_info(battery->dev, "%s: cable(%d) charging current(%d->%d)\n", __func__, i, battery->pdata->charging_current[i].fast_charging_current, battery->pdata->max_charging_current); battery->pdata->charging_current[i].fast_charging_current = battery->pdata->max_charging_current; if (battery->cable_type == i) bChanged = true; } } if (bChanged) sec_bat_set_charging_current(battery); } #endif return true; } EXPORT_SYMBOL_KUNIT(sec_bat_set_aging_step); void sec_bat_aging_check(struct sec_battery_info *battery) { int prev_step = battery->pdata->age_step; int calc_step; bool ret = 0; static bool init; /* false */ if ((battery->pdata->num_age_step <= 0) || (battery->batt_cycle < 0)) return; if (battery->temperature < 50) { pr_info("%s: [AGE] skip (temperature:%d)\n", __func__, battery->temperature); return; } for (calc_step = battery->pdata->num_age_step - 1; calc_step >= 0; calc_step--) { if (battery->pdata->age_data[calc_step].cycle <= battery->batt_cycle) break; } if ((calc_step == prev_step) && init) return; init = true; ret = sec_bat_set_aging_step(battery, calc_step); dev_info(battery->dev, "%s: %s change step (%d->%d), Cycle(%d)\n", __func__, ret ? "Succeed in" : "Fail to", prev_step, battery->pdata->age_step, battery->batt_cycle); } EXPORT_SYMBOL(sec_bat_aging_check); void sec_bat_check_battery_health(struct sec_battery_info *battery) { static battery_health_condition default_table[3] = {{.cycle = 900, .asoc = 75}, {.cycle = 1200, .asoc = 65}, {.cycle = 1500, .asoc = 55}}; battery_health_condition *ptable = default_table; battery_health_condition state; int i, battery_health, size = BATTERY_HEALTH_MAX; if (battery->pdata->health_condition == NULL) { /* * If a new type is added to misc_battery_health, default table cannot verify the actual state except "bad". * If you want to modify to return the correct values for all states, * add a table that matches the state added to the dt file. */ pr_info("%s: does not set health_condition_table, use default table\n", __func__); size = 3; } else { ptable = battery->pdata->health_condition; } /* Checking Cycle and ASoC */ state.cycle = state.asoc = BATTERY_HEALTH_BAD; for (i = size - 1; i >= 0; i--) { if (ptable[i].cycle >= (battery->batt_cycle % 10000)) state.cycle = i + BATTERY_HEALTH_GOOD; if (ptable[i].asoc <= battery->batt_asoc) state.asoc = i + BATTERY_HEALTH_GOOD; } battery_health = max(state.cycle, state.asoc); pr_info("%s: update battery_health(%d), (%d - %d)\n", __func__, battery_health, state.cycle, state.asoc); /* Update battery health */ sec_bat_set_misc_event(battery, (battery_health << BATTERY_HEALTH_SHIFT), BATT_MISC_EVENT_BATTERY_HEALTH); } EXPORT_SYMBOL(sec_bat_check_battery_health); #endif __visible_for_testing bool sec_bat_check_fullcharged_condition(struct sec_battery_info *battery) { int full_check_type = SEC_BATTERY_FULLCHARGED_NONE; if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) full_check_type = battery->pdata->full_check_type; else full_check_type = battery->pdata->full_check_type_2nd; switch (full_check_type) { /* * If these is NOT full check type or NONE full check type, * it is full-charged */ case SEC_BATTERY_FULLCHARGED_CHGINT: case SEC_BATTERY_FULLCHARGED_TIME: case SEC_BATTERY_FULLCHARGED_NONE: return true; default: break; } #if defined(CONFIG_ENABLE_FULL_BY_SOC) if (battery->capacity >= 100 && !battery->is_recharging) { dev_info(battery->dev, "%s: enough SOC (%d%%), skip!\n", __func__, battery->capacity); return true; } #endif if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_SOC) { if (battery->capacity < battery->pdata->full_condition_soc) { dev_dbg(battery->dev, "%s: Not enough SOC (%d%%)\n", __func__, battery->capacity); return false; } } if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_VCELL) { int full_condition_vcell = battery->pdata->full_condition_vcell; if (battery->thermal_zone == BAT_THERMAL_WARM) /* high temp swelling full */ full_condition_vcell = battery->pdata->high_temp_float - 100; if (battery->voltage_now < full_condition_vcell) { dev_dbg(battery->dev, "%s: Not enough VCELL (%dmV)\n", __func__, battery->voltage_now); return false; } } if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_AVGVCELL) { if (battery->voltage_avg < battery->pdata->full_condition_avgvcell) { dev_dbg(battery->dev, "%s: Not enough AVGVCELL (%dmV)\n", __func__, battery->voltage_avg); return false; } } if (battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_OCV) { if (battery->voltage_ocv < battery->pdata->full_condition_ocv) { dev_dbg(battery->dev, "%s: Not enough OCV (%dmV)\n", __func__, battery->voltage_ocv); return false; } } return true; } EXPORT_SYMBOL_KUNIT(sec_bat_check_fullcharged_condition); __visible_for_testing int sec_bat_do_test_function(struct sec_battery_info *battery) { union power_supply_propval value = {0, }; if (battery->test_mode == 0) return battery->test_mode; pr_info("%s: Test Mode\n", __func__); switch (battery->test_mode) { case 1: if (battery->status == POWER_SUPPLY_STATUS_CHARGING) { sec_vote(battery->chgen_vote, VOTER_TEST_MODE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); } break; case 2: if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { sec_vote(battery->chgen_vote, VOTER_TEST_MODE, true, SEC_BAT_CHG_MODE_CHARGING); psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_PROP_STATUS, value); sec_bat_set_charging_status(battery, value.intval); } battery->test_mode = 0; break; case 3: // clear temp block sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); break; case 4: if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { sec_vote(battery->chgen_vote, VOTER_TEST_MODE, true, SEC_BAT_CHG_MODE_CHARGING); psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_PROP_STATUS, value); sec_bat_set_charging_status(battery, value.intval); } break; default: pr_info("%s: error test: unknown state\n", __func__); break; } return battery->test_mode; } EXPORT_SYMBOL_KUNIT(sec_bat_do_test_function); static bool sec_bat_time_management(struct sec_battery_info *battery) { struct timespec64 ts = {0, }; unsigned long charging_time; if (battery->charging_start_time == 0 || !battery->safety_timer_set) { dev_dbg(battery->dev, "%s: Charging Disabled\n", __func__); return true; } ts = ktime_to_timespec64(ktime_get_boottime()); if (ts.tv_sec >= battery->charging_start_time) { charging_time = ts.tv_sec - battery->charging_start_time; } else { charging_time = 0xFFFFFFFF - battery->charging_start_time + ts.tv_sec; } battery->charging_passed_time = charging_time; switch (battery->status) { case POWER_SUPPLY_STATUS_FULL: if (battery->expired_time == 0) { dev_info(battery->dev, "%s: Recharging Timer Expired\n", __func__); battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); battery->is_recharging = false; sec_vote(battery->chgen_vote, VOTER_TIME_EXPIRED, true, SEC_BAT_CHG_MODE_CHARGING_OFF); return false; } break; case POWER_SUPPLY_STATUS_CHARGING: if ((battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_NOTIMEFULL) && (battery->is_recharging && (battery->expired_time == 0))) { dev_info(battery->dev, "%s: Recharging Timer Expired\n", __func__); battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); battery->is_recharging = false; sec_vote(battery->chgen_vote, VOTER_TIME_EXPIRED, true, SEC_BAT_CHG_MODE_CHARGING_OFF); return false; } else if (!battery->is_recharging && (battery->expired_time == 0)) { dev_info(battery->dev, "%s: Charging Timer Expired\n", __func__); battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); #if defined(CONFIG_BATTERY_CISD) battery->cisd.data[CISD_DATA_SAFETY_TIMER]++; battery->cisd.data[CISD_DATA_SAFETY_TIMER_PER_DAY]++; #endif #if IS_ENABLED(CONFIG_SEC_ABC) sec_abc_send_event("MODULE=battery@WARN=safety_timer"); #endif sec_vote(battery->chgen_vote, VOTER_TIME_EXPIRED, true, SEC_BAT_CHG_MODE_CHARGING_OFF); return false; } break; default: dev_err(battery->dev, "%s: Undefine Battery Status\n", __func__); return true; } return true; } bool sec_bat_check_full(struct sec_battery_info *battery, int full_check_type) { union power_supply_propval value = {0, }; int current_adc = 0; bool ret = false; int err = 0; switch (full_check_type) { case SEC_BATTERY_FULLCHARGED_ADC: current_adc = sec_bat_get_adc_data(battery->dev, SEC_BAT_ADC_CHANNEL_FULL_CHECK, battery->pdata->adc_check_count); dev_dbg(battery->dev, "%s: Current ADC (%d)\n", __func__, current_adc); #if !defined(CONFIG_SEC_KUNIT) /* In Kunit test, battery->current_adc is changed by KUNIT TC */ if (current_adc < 0) break; battery->current_adc = current_adc; #endif if (battery->current_adc < battery->topoff_condition) { battery->full_check_cnt++; dev_dbg(battery->dev, "%s: Full Check ADC (%d)\n", __func__, battery->full_check_cnt); } else { battery->full_check_cnt = 0; } break; case SEC_BATTERY_FULLCHARGED_FG_CURRENT: if ((battery->current_now > 0 && battery->current_now < battery->topoff_condition) && (battery->current_avg > 0 && battery->current_avg < battery->topoff_condition)) { battery->full_check_cnt++; dev_dbg(battery->dev, "%s: Full Check Current (%d)\n", __func__, battery->full_check_cnt); } else { battery->full_check_cnt = 0; } break; case SEC_BATTERY_FULLCHARGED_TIME: if ((battery->charging_mode == SEC_BATTERY_CHARGING_2ND ? (battery->charging_passed_time - battery->charging_fullcharged_time) : battery->charging_passed_time) > battery->topoff_condition) { battery->full_check_cnt++; dev_dbg(battery->dev, "%s: Full Check Time (%d)\n", __func__, battery->full_check_cnt); } else { battery->full_check_cnt = 0; } break; case SEC_BATTERY_FULLCHARGED_SOC: if (battery->capacity <= battery->topoff_condition) { battery->full_check_cnt++; dev_dbg(battery->dev, "%s: Full Check SOC (%d)\n", __func__, battery->full_check_cnt); } else { battery->full_check_cnt = 0; } break; case SEC_BATTERY_FULLCHARGED_CHGGPIO: err = gpio_request( battery->pdata->chg_gpio_full_check, "GPIO_CHG_FULL"); if (err) { dev_err(battery->dev, "%s: Error in Request of GPIO\n", __func__); break; } if (!(gpio_get_value_cansleep( battery->pdata->chg_gpio_full_check) ^ !battery->pdata->chg_polarity_full_check)) { battery->full_check_cnt++; dev_dbg(battery->dev, "%s: Full Check GPIO (%d)\n", __func__, battery->full_check_cnt); } else { battery->full_check_cnt = 0; } gpio_free(battery->pdata->chg_gpio_full_check); break; case SEC_BATTERY_FULLCHARGED_CHGINT: case SEC_BATTERY_FULLCHARGED_CHGPSY: psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_PROP_STATUS, value); if (value.intval == POWER_SUPPLY_STATUS_FULL) { battery->full_check_cnt++; dev_info(battery->dev, "%s: Full Check Charger (%d)\n", __func__, battery->full_check_cnt); } else { battery->full_check_cnt = 0; } break; /* * If these is NOT full check type or NONE full check type, * it is full-charged */ case SEC_BATTERY_FULLCHARGED_NONE: battery->full_check_cnt = 0; ret = true; break; #if IS_ENABLED(CONFIG_DUAL_BATTERY) case SEC_BATTERY_FULLCHARGED_LIMITER: value.intval = 1; psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_PROP_STATUS, value); if (value.intval == POWER_SUPPLY_STATUS_FULL) { battery->full_check_cnt++; dev_info(battery->dev, "%s: Full Check Limiter (%d)\n", __func__, battery->full_check_cnt); } else { battery->full_check_cnt = 0; } break; #endif default: dev_err(battery->dev, "%s: Invalid Full Check\n", __func__); break; } #if defined(CONFIG_ENABLE_FULL_BY_SOC) if (battery->capacity >= 100 && battery->charging_mode == SEC_BATTERY_CHARGING_1ST && !battery->is_recharging) { battery->full_check_cnt = battery->pdata->full_check_count; dev_info(battery->dev, "%s: enough SOC to make FULL(%d%%)\n", __func__, battery->capacity); } #endif if (battery->full_check_cnt >= battery->pdata->full_check_count) { battery->full_check_cnt = 0; ret = true; } #if defined(CONFIG_BATTERY_CISD) if (ret && (battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE)) { battery->cisd.data[CISD_DATA_SWELLING_FULL_CNT]++; battery->cisd.data[CISD_DATA_SWELLING_FULL_CNT_PER_DAY]++; } #endif return ret; } EXPORT_SYMBOL_KUNIT(sec_bat_check_full); static void sec_bat_check_eoc_for_repcap(struct sec_battery_info *battery, int full_check_type) { union power_supply_propval value = {0, }; if (battery->pdata->soc_by_repcap_en && battery->eoc_d->eoc_check) { pr_info("%s: check eoc for repcap\n", __func__); switch (full_check_type) { case SEC_BATTERY_FULLCHARGED_FG_CURRENT: if ((battery->current_now > 0 && battery->current_now < battery->pdata->full_check_current_1st) && (battery->current_avg > 0 && battery->current_avg < battery->pdata->full_check_current_1st)) { battery->eoc_d->eoc_cnt++; pr_info("%s : full cnt = %d FG current full check cnt = %d\n", __func__, battery->pdata->full_check_count, battery->eoc_d->eoc_cnt); if (battery->eoc_d->eoc_cnt >= battery->pdata->full_check_count) { battery->eoc_d->eoc_check = false; value.intval = battery->capacity; /*update 1st EOC repcap value */ psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_EXT_PROP_STATUS_FULL_REPCAP, value); pr_info("%s:first EOC\n", __func__); } } else { pr_info("%s: not first EOC\n", __func__); battery->eoc_d->eoc_cnt = 0; // check by default value } break; default: dev_err(battery->dev, "%s: Invalid Full Check\n", __func__); break; } } } static void sec_bat_enable_eoc_check(struct sec_battery_info *battery) { if (battery->pdata->soc_by_repcap_en && battery->capacity < 100 && battery->charging_mode == SEC_BATTERY_CHARGING_1ST) { battery->eoc_d->eoc_check = true; // check for default value, check charging mode value pr_info("%s : eoc_check(%d) charging mode int(%d) , battery_capacity: %d\n", __func__, battery->eoc_d->eoc_check, battery->charging_mode, battery->capacity); } } bool sec_bat_check_fullcharged(struct sec_battery_info *battery) { int full_check_type = SEC_BATTERY_FULLCHARGED_NONE; sec_bat_enable_eoc_check(battery); if (!sec_bat_check_fullcharged_condition(battery)) return false; sec_bat_check_eoc_for_repcap(battery, battery->pdata->full_check_type); if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) full_check_type = battery->pdata->full_check_type; else full_check_type = battery->pdata->full_check_type_2nd; return sec_bat_check_full(battery, full_check_type); } __visible_for_testing void sec_bat_do_fullcharged(struct sec_battery_info *battery, bool force_fullcharged) { union power_supply_propval value = {0, }; /* * To let charger/fuel gauge know the full status, * set status before calling sec_bat_set_charge() */ #if defined(CONFIG_BATTERY_CISD) if (battery->status != POWER_SUPPLY_STATUS_FULL) { battery->cisd.data[CISD_DATA_FULL_COUNT]++; battery->cisd.data[CISD_DATA_FULL_COUNT_PER_DAY]++; } #endif sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_FULL); if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST && battery->pdata->full_check_type_2nd != SEC_BATTERY_FULLCHARGED_NONE && !force_fullcharged) { battery->charging_mode = SEC_BATTERY_CHARGING_2ND; battery->charging_fullcharged_time = battery->charging_passed_time; sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, true, battery->pdata->full_check_current_2nd); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING); pr_info("%s: 1st charging is done\n", __func__); } else { battery->charging_mode = SEC_BATTERY_CHARGING_NONE; battery->is_recharging = false; if (!battery->wdt_kick_disable) { pr_info("%s: wdt kick enable -> Charger Off, %d\n", __func__, battery->wdt_kick_disable); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, true, SEC_BAT_CHG_MODE_CHARGING_OFF); pr_info("%s: 2nd charging is done\n", __func__); if (is_wireless_type(battery->cable_type) && !force_fullcharged) { psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_WIRELESS_2ND_DONE, value); } } else { pr_info("%s: wdt kick disabled -> skip charger off, %d\n", __func__, battery->wdt_kick_disable); } #if defined(CONFIG_BATTERY_AGE_FORECAST) sec_bat_aging_check(battery); #endif /* this concept is only for power-off charging mode*/ if (!battery->store_mode && sec_bat_get_lpmode()) { /* vbus level : 9V --> 5V */ sec_vote(battery->iv_vote, VOTER_FULL_CHARGE, true, SEC_INPUT_VOLTAGE_5V); pr_info("%s: vbus is set 5V by 2nd full\n", __func__); } value.intval = POWER_SUPPLY_STATUS_FULL; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_STATUS, value); } /* * platform can NOT get information of battery * because wakeup time is too short to check uevent * To make sure that target is wakeup if full-charged, * activated wake lock in a few seconds */ if (battery->pdata->polling_type == SEC_BATTERY_MONITOR_ALARM) __pm_wakeup_event(battery->vbus_ws, jiffies_to_msecs(HZ * 10)); } EXPORT_SYMBOL_KUNIT(sec_bat_do_fullcharged); static bool sec_bat_fullcharged_check(struct sec_battery_info *battery) { if ((battery->charging_mode == SEC_BATTERY_CHARGING_NONE) || (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING)) { dev_dbg(battery->dev, "%s: No Need to Check Full-Charged\n", __func__); return true; } if (sec_bat_check_fullcharged(battery)) { union power_supply_propval value = {0, }; if (battery->capacity < 100) { /* update capacity max */ value.intval = battery->capacity; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_CHARGE_FULL, value); pr_info("%s : forced full-charged sequence for the capacity(%d)\n", __func__, battery->capacity); battery->full_check_cnt = battery->pdata->full_check_count; } else { sec_bat_do_fullcharged(battery, false); } } dev_info(battery->dev, "%s: Charging Mode : %s\n", __func__, battery->is_recharging ? sb_get_cm_str(SEC_BATTERY_CHARGING_RECHARGING) : sb_get_cm_str(battery->charging_mode)); return true; } int sec_bat_get_inbat_vol_ocv(struct sec_battery_info *battery) { sec_battery_platform_data_t *pdata = battery->pdata; union power_supply_propval value = {0, }; int j, k, ocv, jig_on = 0, ocv_data[10]; int ret = 0; switch (pdata->inbat_ocv_type) { case SEC_BATTERY_OCV_FG_SRC_CHANGE: pr_info("%s: FGSRC_SWITCHING_VBAT\n", __func__); value.intval = SEC_BAT_INBAT_FGSRC_SWITCHING_VBAT; psy_do_property(pdata->fgsrc_switch_name, set, POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING, value); for (j = 0; j < 10; j++) { msleep(175); value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); ocv_data[j] = value.intval; } ret = psy_do_property(pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_JIG_GPIO, value); if (value.intval < 0 || ret < 0) jig_on = 0; else if (value.intval == 0) jig_on = 1; if (battery->is_jig_on || sec_bat_get_facmode() || battery->factory_mode || jig_on) { pr_info("%s: FGSRC_SWITCHING_VSYS\n", __func__); value.intval = SEC_BAT_INBAT_FGSRC_SWITCHING_VSYS; psy_do_property(pdata->fgsrc_switch_name, set, POWER_SUPPLY_EXT_PROP_FGSRC_SWITCHING, value); } for (j = 1; j < 10; j++) { ocv = ocv_data[j]; k = j; while (k > 0 && ocv_data[k-1] > ocv) { ocv_data[k] = ocv_data[k-1]; k--; } ocv_data[k] = ocv; } for (j = 0; j < 10; j++) pr_info("%s: [%d] %d\n", __func__, j, ocv_data[j]); ocv = 0; for (j = 2; j < 8; j++) ocv += ocv_data[j]; ret = ocv / 6; #if IS_ENABLED(CONFIG_DUAL_BATTERY) /* just for debug */ value.intval = SEC_DUAL_BATTERY_MAIN; psy_do_property(pdata->dual_battery_name, get, POWER_SUPPLY_PROP_VOLTAGE_AVG, value); value.intval = SEC_DUAL_BATTERY_SUB; psy_do_property(pdata->dual_battery_name, get, POWER_SUPPLY_PROP_VOLTAGE_AVG, value); #endif break; case SEC_BATTERY_OCV_FG_NOSRC_CHANGE: value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); ret = value.intval; break; case SEC_BATTERY_OCV_ADC: /* run twice */ ret = (sec_bat_get_inbat_vol_by_adc(battery) + sec_bat_get_inbat_vol_by_adc(battery)) / 2; break; case SEC_BATTERY_OCV_VOLT_FROM_PMIC: psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_PMIC_BAT_VOLTAGE, value); ret = value.intval; break; case SEC_BATTERY_OCV_NONE: default: break; }; pr_info("%s: [%d] in-battery voltage ocv(%d)\n", __func__, pdata->inbat_ocv_type, ret); return ret; } #if !defined(CONFIG_SEC_FACTORY) static void sec_bat_calc_unknown_wpc_temp( struct sec_battery_info *battery, int *batt_temp, int wpc_temp, int usb_temp) { if ((battery->pdata->wpc_thm_info.source != SEC_BATTERY_THERMAL_SOURCE_NONE) && !is_wireless_fake_type(battery->cable_type)) { if (wpc_temp <= (-200)) { if (usb_temp >= 270) *batt_temp = (usb_temp + 60) > 900 ? 900 : (usb_temp + 60); else if (usb_temp <= 210) *batt_temp = (usb_temp - 50) < (-200) ? (-200) : (usb_temp - 50); else *batt_temp = (170 * usb_temp - 26100) / 60; pr_info("%s : Tbat(wpc_temp:%d) to (%d)\n", __func__, wpc_temp, *batt_temp); } } } int adjust_bat_temp(struct sec_battery_info *battery, int batt_temp, int sub_bat_temp) { int bat_t = 0, bat_t_1 = 0; /* bat_t = bat_v(t), bat_t_1 = bat_v(t_1) */ int lr_delta = battery->pdata->lr_delta; struct timespec64 ts = {0, }; ts = ktime_to_timespec64(ktime_get_boottime()); /* this temperature prediction formula has high accuracy at room temperature */ if (batt_temp <= 250 || sub_bat_temp <= 0) { battery->lrp_temp = 0x7FFF; battery->lr_start_time = 0; battery->lr_time_span = 0; return batt_temp; } battery->lr_time_span = ts.tv_sec - battery->lr_start_time; battery->lr_start_time = battery->lr_start_time + battery->lr_time_span; if (battery->lr_time_span > 60) battery->lr_time_span = 60; if (battery->lr_time_span < 1) battery->lr_time_span = 1; if (battery->lrp_temp == 0x7FFF) { /* init bat_t_1 as bat(0) */ bat_t_1 = (sub_bat_temp*battery->pdata->lr_param_init_sub_bat_thm + batt_temp*battery->pdata->lr_param_init_bat_thm)/100; } else bat_t_1 = battery->lr_bat_t_1; /* bat_v(t) as previous bat_v(t_1) */ /* * calculate bat_v(t) * bat_v(0) = bat_v * bat_v(t) = bat_v(t_1) + ((raw - bat_v(t_1))*0.016*time_span+0.5) * lrp_temp = A*bat_v(t) + B*bat */ bat_t = ((bat_t_1*1000) + ((batt_temp - bat_t_1)*lr_delta*battery->lr_time_span+battery->pdata->lr_round_off))/1000; battery->lrp_temp = (battery->pdata->lr_param_bat_thm*bat_t + battery->pdata->lr_param_sub_bat_thm*sub_bat_temp)/1000; if ((is_wireless_fake_type(battery->cable_type) || battery->misc_event & BATT_MISC_EVENT_WIRELESS_DET_LEVEL) && battery->lrp_temp >= 350) battery->lrp_temp = battery->lrp_temp - 10; pr_info("%s : LRP: %d, lrp_temp_raw: %d wpc_temp: %d lrp_bat_t_1: %d lrp_bat_v: %d time_span: %lds\n", __func__, battery->lrp_temp, batt_temp, sub_bat_temp, bat_t_1, bat_t, battery->lr_time_span); battery->lr_bat_t_1 = bat_t; return battery->lrp_temp; } EXPORT_SYMBOL(adjust_bat_temp); #endif int sec_bat_get_temperature(struct device *dev, struct sec_bat_thm_info *info, int old_val, char *chg_name, char *fg_name) { union power_supply_propval value = {0, }; int temp = old_val; int adc; if (info->check_type == SEC_BATTERY_TEMP_CHECK_FAKE) return FAKE_TEMP; if (info->test > -300 && info->test < 3000) { pr_info("%s : temperature test %d\n", __func__, info->test); return info->test; } /* get battery thm info */ switch (info->source) { case SEC_BATTERY_THERMAL_SOURCE_FG: psy_do_property(fg_name, get, POWER_SUPPLY_PROP_TEMP, value); temp = value.intval; break; case SEC_BATTERY_THERMAL_SOURCE_CALLBACK: pr_info("%s : need to implement\n", __func__); break; case SEC_BATTERY_THERMAL_SOURCE_ADC: adc = sec_bat_get_adc_data(dev, info->channel, info->check_count); if (adc >= 0) { info->adc = adc; sec_bat_convert_adc_to_val(adc, info->offset, info->adc_table, info->adc_table_size, &temp); } break; case SEC_BATTERY_THERMAL_SOURCE_CHG_ADC: if (!psy_do_property(chg_name, get, POWER_SUPPLY_PROP_TEMP, value)) { info->adc = value.intval; sec_bat_convert_adc_to_val(value.intval, info->offset, info->adc_table, info->adc_table_size, &temp); } break; case SEC_BATTERY_THERMAL_SOURCE_FG_ADC: if (!psy_do_property(fg_name, get, POWER_SUPPLY_PROP_TEMP, value)) { info->adc = value.intval; sec_bat_convert_adc_to_val(value.intval, info->offset, info->adc_table, info->adc_table_size, &temp); } break; default: break; } return temp; } EXPORT_SYMBOL(sec_bat_get_temperature); __visible_for_testing void sec_bat_get_temperature_info(struct sec_battery_info *battery) { union power_supply_propval value = {0, }; static bool shipmode_en = false; int batt_temp = battery->temperature; int usb_temp = battery->usb_temp; int chg_temp = battery->chg_temp; int dchg_temp = battery->dchg_temp; int wpc_temp = battery->wpc_temp; int sub_bat_temp = battery->sub_bat_temp; int blkt_temp = battery->blkt_temp; char *fg_name = battery->pdata->fuelgauge_name; char *chg_name = battery->pdata->charger_name; /* get battery thm info */ batt_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->bat_thm_info, batt_temp, chg_name, fg_name); /* get usb thm info */ usb_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->usb_thm_info, usb_temp, chg_name, fg_name); /* get chg thm info */ chg_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->chg_thm_info, chg_temp, chg_name, fg_name); /* get wpc thm info */ wpc_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->wpc_thm_info, wpc_temp, chg_name, fg_name); /* get sub_bat thm info */ sub_bat_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->sub_bat_thm_info, sub_bat_temp, chg_name, fg_name); #if !defined(CONFIG_SEC_FACTORY) if (battery->pdata->lr_enable) batt_temp = adjust_bat_temp(battery, batt_temp, sub_bat_temp); #endif /* get blkt thm info */ blkt_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->blk_thm_info, blkt_temp, chg_name, fg_name); #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (battery->pdata->dctp_by_cgtp) dchg_temp = battery->chg_temp; else if (is_pd_apdo_wire_type(battery->wire_status)) dchg_temp = sec_bat_get_temperature(battery->dev, &battery->pdata->dchg_thm_info, dchg_temp, chg_name, fg_name); #endif #if !defined(CONFIG_SEC_FACTORY) if (battery->pdata->sub_temp_control_source == TEMP_CONTROL_SOURCE_WPC_THM) sec_bat_calc_unknown_wpc_temp(battery, &sub_bat_temp, wpc_temp, usb_temp); else sec_bat_calc_unknown_wpc_temp(battery, &batt_temp, wpc_temp, usb_temp); #endif battery->temperature = batt_temp; battery->temper_amb = batt_temp; battery->usb_temp = usb_temp; battery->chg_temp = chg_temp; battery->dchg_temp = dchg_temp; battery->wpc_temp = wpc_temp; battery->sub_bat_temp = sub_bat_temp; battery->blkt_temp = blkt_temp; value.intval = battery->temperature; #if defined(CONFIG_SEC_FACTORY) if (battery->pdata->usb_thm_info.check_type && (battery->temperature <= (-200))) { value.intval = (battery->usb_temp <= (-200) ? battery->chg_temp : battery->usb_temp); } #endif psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_TEMP, value); psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_TEMP_AMBIENT, value); if (!battery->pdata->dis_auto_shipmode_temp_ctrl) { if (battery->temperature < 0 && !shipmode_en) { value.intval = 0; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_AUTO_SHIPMODE_CONTROL, value); shipmode_en = true; } else if (battery->temperature >= 50 && shipmode_en) { value.intval = 1; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_AUTO_SHIPMODE_CONTROL, value); shipmode_en = false; } } } EXPORT_SYMBOL_KUNIT(sec_bat_get_temperature_info); void sec_bat_get_battery_info(struct sec_battery_info *battery) { union power_supply_propval value = {0, }; char str[1024] = {0, }; value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); battery->voltage_now = value.intval; value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_AVG, value); battery->voltage_avg = value.intval; /* Do not call it to reduce time after cable_work, this function call FG full log */ if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL)) { value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_OCV, value); battery->voltage_ocv = value.intval; } #if IS_ENABLED(CONFIG_DUAL_BATTERY) /* get main pack voltage */ value.intval = SEC_DUAL_BATTERY_MAIN; psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_PROP_VOLTAGE_AVG, value); battery->voltage_pack_main = value.intval; /* get sub pack voltage */ value.intval = SEC_DUAL_BATTERY_SUB; psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_PROP_VOLTAGE_AVG, value); battery->voltage_pack_sub = value.intval; /* get main current */ value.intval = SEC_DUAL_BATTERY_MAIN; psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_PROP_CURRENT_NOW, value); battery->current_now_main = value.intval; /* get sub current */ value.intval = SEC_DUAL_BATTERY_SUB; psy_do_property(battery->pdata->dual_battery_name, get, POWER_SUPPLY_PROP_CURRENT_NOW, value); battery->current_now_sub = value.intval; #endif value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CURRENT_NOW, value); battery->current_now = value.intval; value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CURRENT_AVG, value); battery->current_avg = value.intval; #if IS_ENABLED(CONFIG_FUELGAUGE_MAX77705) value.intval = SEC_BATTERY_ISYS_AVG_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); battery->current_sys_avg = value.intval; value.intval = SEC_BATTERY_ISYS_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); battery->current_sys = value.intval; #endif /* input current limit in charger */ psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); battery->current_max = value.intval; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CHARGE_COUNTER, value); battery->charge_counter = value.intval; /* check abnormal status for wireless charging */ if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL) && (is_wireless_type(battery->cable_type) || battery->wc_tx_enable)) { value.intval = (battery->status == POWER_SUPPLY_STATUS_FULL) ? 100 : battery->capacity; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, value); } #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) value.intval = (battery->status == POWER_SUPPLY_STATUS_FULL) ? 100 : battery->capacity; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_CAPACITY, value); #endif sec_bat_get_temperature_info(battery); /* To get SOC value (NOT raw SOC), need to reset value */ value.intval = 0; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CAPACITY, value); /* if the battery status was full, and SOC wasn't 100% yet, then ignore FG SOC, and report (previous SOC +1)% */ battery->capacity = value.intval; /* voltage information */ #if IS_ENABLED(CONFIG_DUAL_BATTERY) sprintf(str, "%s:Vnow(%dmV),Vavg(%dmV),Vmp(%dmV),Vsp(%dmV),", __func__, battery->voltage_now, battery->voltage_avg, battery->voltage_pack_main, battery->voltage_pack_sub ); #else sprintf(str, "%s:Vnow(%dmV),Vavg(%dmV),", __func__, battery->voltage_now, battery->voltage_avg ); #endif /* current information */ #if IS_ENABLED(CONFIG_DUAL_BATTERY) sprintf(str + strlen(str), "Inow(%dmA),Iavg(%dmA),Isysavg(%dmA),Inow_m(%dmA),Inow_s(%dmA),Imax(%dmA),Ichg(%dmA),Ichg_m(%dmA),Ichg_s(%dmA),SOC(%d%%),", battery->current_now, battery->current_avg, battery->current_sys_avg, battery->current_now_main, battery->current_now_sub, battery->current_max, battery->charging_current, battery->main_current, battery->sub_current, battery->capacity ); #else sprintf(str + strlen(str), "Inow(%dmA),Iavg(%dmA),Isysavg(%dmA),Imax(%dmA),Ichg(%dmA),SOC(%d%%),", battery->current_now, battery->current_avg, battery->current_sys_avg, battery->current_max, battery->charging_current, battery->capacity ); #endif /* temperature information */ #if IS_ENABLED(CONFIG_DUAL_BATTERY) sprintf(str + strlen(str), "Tsub(%d),", battery->sub_bat_temp ); #endif sprintf(str + strlen(str), "Tbat(%d),Tusb(%d),Tchg(%d),Twpc(%d),Tblkt(%d),Tlrp(%d),", battery->temperature, battery->usb_temp, battery->chg_temp, battery->wpc_temp, battery->blkt_temp, battery->lrp ); #if IS_ENABLED(CONFIG_DIRECT_CHARGING) sprintf(str + strlen(str), "Tdchg(%d)\n", battery->dchg_temp ); #endif pr_info("%s", str); #if defined(CONFIG_ARCH_QCOM) && !(defined(CONFIG_ARCH_EXYNOS) || defined(CONFIG_ARCH_MEDIATEK)) if (!strcmp(battery->pdata->chip_vendor, "QCOM")) battery_last_dcvs(battery->capacity, battery->voltage_avg, battery->temperature, battery->current_avg); #endif #if IS_ENABLED(CONFIG_SEC_DEBUG_EXTRA_INFO) && !defined(CONFIG_ARCH_MTK_PROJECT) if (!strcmp(battery->pdata->chip_vendor, "LSI")) secdbg_exin_set_batt(battery->capacity, battery->voltage_avg, battery->temperature, battery->current_avg); #endif } EXPORT_SYMBOL(sec_bat_get_battery_info); __visible_for_testing void sec_bat_polling_work(struct work_struct *work) { struct sec_battery_info *battery = container_of( work, struct sec_battery_info, polling_work.work); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); dev_dbg(battery->dev, "%s: Activated\n", __func__); } EXPORT_SYMBOL_KUNIT(sec_bat_polling_work); static void sec_bat_program_alarm( struct sec_battery_info *battery, int seconds) { alarm_start(&battery->polling_alarm, ktime_add(battery->last_poll_time, ktime_set(seconds, 0))); } static unsigned int sec_bat_get_polling_time( struct sec_battery_info *battery) { if (battery->status == POWER_SUPPLY_STATUS_FULL) battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_CHARGING]; else battery->polling_time = battery->pdata->polling_time[battery->status]; battery->polling_short = true; switch (battery->status) { case POWER_SUPPLY_STATUS_CHARGING: if (battery->polling_in_sleep) battery->polling_short = false; break; case POWER_SUPPLY_STATUS_DISCHARGING: if (battery->polling_in_sleep) battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_SLEEP]; else battery->polling_time = battery->pdata->polling_time[battery->status]; if (!battery->wc_enable || (battery->d2d_auth == D2D_AUTH_SRC)) { battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_CHARGING]; pr_info("%s: wc_enable is false, or hp d2d, polling time is 30sec\n", __func__); } battery->polling_short = false; break; case POWER_SUPPLY_STATUS_FULL: if (battery->polling_in_sleep) { if (!(battery->pdata->full_condition_type & SEC_BATTERY_FULL_CONDITION_NOSLEEPINFULL) && battery->charging_mode == SEC_BATTERY_CHARGING_NONE) battery->polling_time = battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_SLEEP]; battery->polling_short = false; } else { if (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) battery->polling_short = false; } break; case POWER_SUPPLY_STATUS_NOT_CHARGING: if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE || (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) && (battery->health_check_count > 0)) { battery->health_check_count--; battery->polling_time = 1; battery->polling_short = false; } break; } #if defined(CONFIG_WIRELESS_TX_MODE) if (battery->wc_tx_enable) { battery->polling_time = 10; battery->polling_short = false; pr_info("%s: Tx mode enable polling time is 10sec\n", __func__); } #endif if (is_pd_apdo_wire_type(battery->cable_type) && (battery->pd_list.now_isApdo || battery->ta_alert_mode != OCP_NONE)) { battery->polling_time = 10; battery->polling_short = false; pr_info("%s: DC mode enable polling time is 10sec\n", __func__); } if (battery->polling_short) return battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_BASIC]; return battery->polling_time; } __visible_for_testing bool sec_bat_is_short_polling(struct sec_battery_info *battery) { /* * Change the full and short monitoring sequence * Originally, full monitoring was the last time of polling_count * But change full monitoring to first time * because temperature check is too late */ if (!battery->polling_short || battery->polling_count == 1) return false; else return true; } EXPORT_SYMBOL_KUNIT(sec_bat_is_short_polling); static void sec_bat_update_polling_count(struct sec_battery_info *battery) { /* * do NOT change polling count in sleep * even though it is short polling * to keep polling count along sleep/wakeup */ if (battery->polling_short && battery->polling_in_sleep) return; if (battery->polling_short && ((battery->polling_time / battery->pdata->polling_time[SEC_BATTERY_POLLING_TIME_BASIC]) > battery->polling_count)) battery->polling_count++; else battery->polling_count = 1; /* initial value = 1 */ } __visible_for_testing int sec_bat_set_polling(struct sec_battery_info *battery) { unsigned int polling_time_temp = 0; dev_dbg(battery->dev, "%s: Start\n", __func__); polling_time_temp = sec_bat_get_polling_time(battery); dev_dbg(battery->dev, "%s: Status:%s, Sleep:%s, Charging:%s, Short Poll:%s\n", __func__, sb_get_bst_str(battery->status), battery->polling_in_sleep ? "Yes" : "No", (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) ? "No" : "Yes", battery->polling_short ? "Yes" : "No"); dev_info(battery->dev, "%s: Polling time %d/%d sec.\n", __func__, battery->polling_short ? (polling_time_temp * battery->polling_count) : polling_time_temp, battery->polling_time); /* * To sync with log above, * change polling count after log is displayed * Do NOT update polling count in initial monitor */ if (!battery->pdata->monitor_initial_count) sec_bat_update_polling_count(battery); else dev_dbg(battery->dev, "%s: Initial monitor %d times left.\n", __func__, battery->pdata->monitor_initial_count); switch (battery->pdata->polling_type) { case SEC_BATTERY_MONITOR_WORKQUEUE: if (battery->pdata->monitor_initial_count) { battery->pdata->monitor_initial_count--; schedule_delayed_work(&battery->polling_work, HZ); } else schedule_delayed_work(&battery->polling_work, polling_time_temp * HZ); break; case SEC_BATTERY_MONITOR_ALARM: battery->last_poll_time = ktime_get_boottime(); if (battery->pdata->monitor_initial_count) { battery->pdata->monitor_initial_count--; sec_bat_program_alarm(battery, 1); } else sec_bat_program_alarm(battery, polling_time_temp); break; case SEC_BATTERY_MONITOR_TIMER: break; default: break; } dev_dbg(battery->dev, "%s: End\n", __func__); return polling_time_temp; } EXPORT_SYMBOL_KUNIT(sec_bat_set_polling); #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) extern bool get_usb_enumeration_state(void); #else static bool get_usb_enumeration_state(void) { return true; } #endif /* To display slow charging when usb charging 100MA*/ __visible_for_testing void sec_bat_check_slowcharging_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, slowcharging_work.work); if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT && battery->cable_type == SEC_BATTERY_CABLE_USB) { if (!get_usb_enumeration_state() && (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_100MA)) { battery->usb_slow_chg = true; battery->max_charge_power = mW_by_mVmA(battery->input_voltage, battery->current_max); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } } pr_info("%s:\n", __func__); } EXPORT_SYMBOL_KUNIT(sec_bat_check_slowcharging_work); static int sec_bat_chk_siop_scenario_idx(struct sec_battery_info *battery, int siop_level) { int scenario_idx = -1; int i = 0; if (battery->pdata->siop_scenarios_num > 0) { for (i = 0; i < battery->pdata->siop_scenarios_num; ++i) if (siop_level == battery->pdata->siop_table[i].level) { scenario_idx = i; pr_info("%s: siop level(%d), scenario_idx(%d)\n", __func__, siop_level, scenario_idx); break; } } return scenario_idx; } static bool sec_bat_chk_siop_skip_scenario(struct sec_battery_info *battery, int ct, int ws, int scenario_idx) { struct sec_siop_table *siop_table; int type = SIOP_CURR_TYPE_NV; if (scenario_idx < 0) return false; if (is_hv_wireless_type(ct)) type = SIOP_CURR_TYPE_WPC_HV; else if (is_nv_wireless_type(ct)) type = SIOP_CURR_TYPE_WPC_NV; else if (is_hv_wire_type(ct) && is_hv_wire_type(ws)) type = SIOP_CURR_TYPE_HV; else if (is_pd_apdo_wire_type(ct) && battery->pd_list.now_isApdo) type = SIOP_CURR_TYPE_APDO; else if (is_pd_wire_type(ct)) type = SIOP_CURR_TYPE_FPDO; if (type >= battery->pdata->siop_curr_type_num) return false; siop_table = &battery->pdata->siop_table[scenario_idx]; if ((siop_table->icl[type] == SIOP_SKIP) && (siop_table->fcc[type] == SIOP_SKIP)) { pr_info("%s: scenario_idx(%d), type(%d), siop_icl(%d), siop_fcc(%d)\n", __func__, scenario_idx, type, siop_table->icl[type], siop_table->fcc[type]); return true; } return false; } static int sec_bat_get_siop_scenario_icl(struct sec_battery_info *battery, int icl, int scenario_idx, int type) { int siop_icl = icl; if (scenario_idx < 0) return icl; if (type >= SIOP_CURR_TYPE_MAX || type >= battery->pdata->siop_curr_type_num) return icl; siop_icl = battery->pdata->siop_table[scenario_idx].icl[type]; if (siop_icl == SIOP_DEFAULT || siop_icl > icl || siop_icl <= 0 || siop_icl == SIOP_SKIP) return icl; pr_info("%s: scenario_idx(%d), type(%d), siop_icl(%d)\n", __func__, scenario_idx, type, siop_icl); return siop_icl; } static int sec_bat_get_siop_scenario_fcc(struct sec_battery_info *battery, int fcc, int scenario_idx, int type) { int siop_fcc = fcc; if (scenario_idx < 0) return fcc; if (type >= SIOP_CURR_TYPE_MAX || type >= battery->pdata->siop_curr_type_num) return fcc; siop_fcc = battery->pdata->siop_table[scenario_idx].fcc[type]; if (siop_fcc == SIOP_DEFAULT || siop_fcc > fcc || siop_fcc <= 0 || siop_fcc == SIOP_SKIP) return fcc; pr_info("%s: scenario_idx(%d), type(%d), siop_fcc(%d)\n", __func__, scenario_idx, type, siop_fcc); return siop_fcc; } static void sec_bat_siop_level_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, siop_level_work.work); sec_battery_platform_data_t *pdata = battery->pdata; int icl = INT_MAX; /* Input Current Limit */ int fcc = INT_MAX; /* Fast Charging Current */ int scenario_idx = -1; int skip_vote = 0; enum { SIOP_STEP1, /* siop level 70 */ SIOP_STEP2, /* siop level 10 */ SIOP_STEP3, /* siop level 0 */ }; int siop_step = (battery->siop_level == 0) ? SIOP_STEP3 : ((battery->siop_level == 10) ? SIOP_STEP2 : SIOP_STEP1); pr_info("%s : set current by siop level(%d), siop_step(%d)\n", __func__, battery->siop_level, siop_step + 1); scenario_idx = sec_bat_chk_siop_scenario_idx(battery, battery->siop_level); if (sec_bat_chk_siop_skip_scenario(battery, battery->cable_type, battery->wire_status, scenario_idx) || (battery->siop_level >= 80)) { #if defined(CONFIG_SUPPORT_HV_CTRL) sec_vote(battery->iv_vote, VOTER_SIOP, false, 0); #endif sec_vote(battery->fcc_vote, VOTER_SIOP, false, 0); sec_vote(battery->input_vote, VOTER_SIOP, false, 0); __pm_relax(battery->siop_level_ws); return; } if (is_hv_wireless_type(battery->cable_type)) { icl = pdata->siop_hv_wpc_icl; fcc = pdata->siop_hv_wpc_fcc[siop_step]; if (battery->pdata->siop_scenarios_num > 0) { icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_WPC_HV); fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_WPC_HV); } } else if (is_nv_wireless_type(battery->cable_type)) { icl = pdata->siop_wpc_icl; fcc = pdata->siop_wpc_fcc[siop_step]; if (battery->pdata->siop_scenarios_num > 0) { icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_WPC_NV); fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_WPC_NV); } } else if (is_hv_wire_12v_type(battery->cable_type) && is_hv_wire_type(battery->wire_status)) { icl = pdata->siop_hv_12v_icl; fcc = pdata->siop_hv_12v_fcc; if (battery->pdata->siop_scenarios_num > 0) { icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_HV); fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_HV); } } else if (is_hv_wire_type(battery->cable_type) && is_hv_wire_type(battery->wire_status)) { #if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) if (sec_bat_change_vbus(battery, battery->cable_type, battery->current_event, battery->siop_level)) { if (battery->misc_event & BATT_MISC_EVENT_HV_BY_AICL) { icl = pdata->siop_hv_icl; fcc = pdata->siop_hv_fcc; } else { icl = pdata->siop_icl; fcc = pdata->siop_fcc; } } else { skip_vote = 1; } #else icl = (siop_step == SIOP_STEP3) ? pdata->siop_hv_icl_2nd : pdata->siop_hv_icl; fcc = pdata->siop_hv_fcc; #endif if (battery->pdata->siop_scenarios_num > 0) { icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_HV); fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_HV); } } else if (is_pd_apdo_wire_type(battery->cable_type) && battery->pd_list.now_isApdo) { icl = pdata->siop_apdo_icl; fcc = pdata->siop_apdo_fcc; if (battery->pdata->siop_scenarios_num > 0) { icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_APDO); fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_APDO); } } else if (is_pd_wire_type(battery->cable_type)) { #if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) if (sec_bat_change_vbus(battery, battery->cable_type, battery->current_event, battery->siop_level)) { if (battery->misc_event & BATT_MISC_EVENT_HV_BY_AICL) { icl = mA_by_mWmV(pdata->power_value, SEC_INPUT_VOLTAGE_9V); fcc = pdata->siop_hv_fcc; } else { icl = mA_by_mWmV(pdata->power_value, SEC_INPUT_VOLTAGE_5V); fcc = pdata->siop_fcc; } } else { skip_vote = 1; } #else icl = mA_by_mWmV(pdata->power_value, battery->input_voltage); fcc = pdata->siop_fcc; #endif if (battery->pdata->siop_scenarios_num > 0) { icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_FPDO); fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_FPDO); } } else { #if defined(CONFIG_SUPPORT_HV_CTRL) if (battery->wire_status != SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) sec_vote(battery->iv_vote, VOTER_SIOP, false, 0); #endif icl = pdata->siop_icl; fcc = pdata->siop_fcc; if (battery->pdata->siop_scenarios_num > 0) { icl = sec_bat_get_siop_scenario_icl(battery, icl, scenario_idx, SIOP_CURR_TYPE_NV); fcc = sec_bat_get_siop_scenario_fcc(battery, fcc, scenario_idx, SIOP_CURR_TYPE_NV); } } if (!skip_vote) { pr_info("%s: icl(%d), fcc(%d)\n", __func__, icl, fcc); sec_vote(battery->input_vote, VOTER_SIOP, true, icl); sec_vote(battery->fcc_vote, VOTER_SIOP, true, fcc); } __pm_relax(battery->siop_level_ws); } #if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) __visible_for_testing void sec_bat_fw_init_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, fw_init_work.work); union power_supply_propval value = {0, }; #if defined(CONFIG_WIRELESS_IC_PARAM) psy_do_property(battery->pdata->wireless_charger_name, get, POWER_SUPPLY_EXT_PROP_WIRELESS_CHECK_FW_VER, value); if (value.intval) { pr_info("%s: wireless firmware is already updated.\n", __func__); return; } #endif /* auto mfc firmware update when only no otg, mst, wpc */ if (sec_bat_check_boost_mfc_condition(battery, SEC_WIRELESS_FW_UPDATE_AUTO_MODE) && battery->capacity > 30 && !sec_bat_get_lpmode()) { sec_bat_fw_update(battery, SEC_WIRELESS_FW_UPDATE_AUTO_MODE); } } EXPORT_SYMBOL_KUNIT(sec_bat_fw_init_work); #endif #if defined(CONFIG_UPDATE_BATTERY_DATA) static void sec_bat_update_data_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, batt_data_work.work); sec_battery_update_data(battery->data_path); __pm_relax(battery->batt_data_ws); } #endif static void sec_bat_misc_event_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, misc_event_work.work); int xor_misc_event = battery->prev_misc_event ^ battery->misc_event; if (xor_misc_event & BATT_MISC_EVENT_MUIC_ABNORMAL) { if (battery->misc_event & BATT_MISC_EVENT_MUIC_ABNORMAL) sec_vote(battery->chgen_vote, VOTER_MUIC_ABNORMAL, true, SEC_BAT_CHG_MODE_BUCK_OFF); else if (battery->prev_misc_event & BATT_MISC_EVENT_MUIC_ABNORMAL) sec_vote(battery->chgen_vote, VOTER_MUIC_ABNORMAL, false, 0); } pr_info("%s: change misc event(0x%x --> 0x%x)\n", __func__, battery->prev_misc_event, battery->misc_event); battery->prev_misc_event = battery->misc_event; __pm_relax(battery->misc_event_ws); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } static void sec_bat_calculate_safety_time(struct sec_battery_info *battery) { unsigned long long expired_time = battery->expired_time; struct timespec64 ts = {0, }; int curr = 0; int input_power = 0; int charging_power = mW_by_mVmA(battery->charging_current, (battery->pdata->chg_float_voltage / battery->pdata->chg_float_voltage_conv)); static int discharging_cnt = 0; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) int direct_chg_done = 0; union power_supply_propval value = {0,}; #endif #if IS_ENABLED(CONFIG_DIRECT_CHARGING) psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_DIRECT_DONE, value); direct_chg_done = value.intval; if (is_pd_apdo_wire_type(battery->cable_type) && !battery->chg_limit && !battery->lrp_limit && (battery->pd_list.now_isApdo || direct_chg_done)) input_power = battery->pd_max_charge_power; else input_power = mW_by_mVmA(battery->input_voltage, battery->current_max); #else input_power = mW_by_mVmA(battery->input_voltage, battery->current_max); #endif if (battery->current_avg < 0) { discharging_cnt++; } else { discharging_cnt = 0; } if (discharging_cnt >= 5) { battery->expired_time = battery->pdata->expired_time; battery->prev_safety_time = 0; pr_info("%s : SAFETY TIME RESET! DISCHARGING CNT(%d)\n", __func__, discharging_cnt); discharging_cnt = 0; return; } else if ((battery->lcd_status || battery->wc_tx_enable) && battery->stop_timer) { battery->prev_safety_time = 0; return; } ts = ktime_to_timespec64(ktime_get_boottime()); if (battery->prev_safety_time == 0) { battery->prev_safety_time = ts.tv_sec; } if (input_power > charging_power) { curr = battery->charging_current; } else { curr = mA_by_mWmV(input_power, (battery->pdata->chg_float_voltage / battery->pdata->chg_float_voltage_conv)); curr = (curr * 9) / 10; } if ((battery->lcd_status || battery->wc_tx_enable) && !battery->stop_timer) { battery->stop_timer = true; } else if (!(battery->lcd_status || battery->wc_tx_enable) && battery->stop_timer) { battery->stop_timer = false; } pr_info("%s : EXPIRED_TIME(%llu), IP(%d), CP(%d), CURR(%d), STANDARD(%d)\n", __func__, expired_time, input_power, charging_power, curr, battery->pdata->standard_curr); if (curr == 0) return; else if (curr > battery->pdata->standard_curr) curr = battery->pdata->standard_curr; expired_time = (expired_time * battery->pdata->standard_curr) / curr; pr_info("%s : CAL_EXPIRED_TIME(%llu) TIME NOW(%llu) TIME PREV(%ld)\n", __func__, expired_time, ts.tv_sec, battery->prev_safety_time); if (expired_time <= ((ts.tv_sec - battery->prev_safety_time) * 1000)) expired_time = 0; else expired_time -= ((ts.tv_sec - battery->prev_safety_time) * 1000); battery->cal_safety_time = expired_time; expired_time = (expired_time * curr) / battery->pdata->standard_curr; battery->expired_time = expired_time; battery->prev_safety_time = ts.tv_sec; pr_info("%s : REMAIN_TIME(%ld) CAL_REMAIN_TIME(%ld)\n", __func__, battery->expired_time, battery->cal_safety_time); } static bool sb_check_skip_monitor(struct sec_battery_info *battery) { static struct timespec64 old_ts = {0, }; struct timespec64 c_ts = {0, }; union power_supply_propval val = {0, }; c_ts = ktime_to_timespec64(ktime_get_boottime()); /* monitor once after wakeup */ if (battery->polling_in_sleep) { battery->polling_in_sleep = false; if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING && !battery->wc_tx_enable && (battery->d2d_auth != D2D_AUTH_SRC) && ((unsigned long)(c_ts.tv_sec - old_ts.tv_sec) < 10 * 60) ) { val.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_NOW, val); battery->voltage_now = val.intval; val.intval = 0; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CAPACITY, val); battery->capacity = val.intval; sec_bat_get_temperature_info(battery); #if defined(CONFIG_BATTERY_CISD) sec_bat_cisd_check(battery); #endif power_supply_changed(battery->psy_bat); pr_info("Skip monitor work(%llu, Vnow:%d(mV), SoC:%d(%%), Tbat:%d(0.1'C))\n", c_ts.tv_sec - old_ts.tv_sec, battery->voltage_now, battery->capacity, battery->temperature); return true; } } /* update last monitor time */ old_ts = c_ts; return false; } static void sec_bat_check_store_mode(struct sec_battery_info *battery) { if (sec_bat_get_facmode()) return; #if defined(CONFIG_SEC_FACTORY) if (!is_nocharge_type(battery->cable_type)) { #else if (!is_nocharge_type(battery->cable_type) && battery->store_mode) { #endif pr_info("%s: capacity(%d), status(%d), store_mode(%d)\n", __func__, battery->capacity, battery->status, battery->store_mode); #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) pr_info("%s: @battery->batt_f_mode: %s\n", __func__, BOOT_MODE_STRING[battery->batt_f_mode]); #endif /* * VOTER_STORE_MODE * Set limited max power when store mode is set and LDU * Limited max power should be set with over 5% capacity * since target could be turned off during boot up * display test requirement : do not decrease fcc in store mode condition */ if (!battery->display_test && battery->store_mode && battery->capacity >= 5) { sec_vote(battery->input_vote, VOTER_STORE_MODE, true, mA_by_mWmV(battery->pdata->store_mode_max_input_power, battery->input_voltage)); } if (battery->capacity >= battery->pdata->store_mode_charging_max) { int chg_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; /* to discharge the battery, off buck */ if (battery->capacity > battery->pdata->store_mode_charging_max || battery->pdata->store_mode_buckoff) chg_mode = SEC_BAT_CHG_MODE_BUCK_OFF; #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) if ((sec_bat_get_facmode() || battery->batt_f_mode != NO_MODE) && chg_mode == SEC_BAT_CHG_MODE_BUCK_OFF) #else if (sec_bat_get_facmode() && chg_mode == SEC_BAT_CHG_MODE_BUCK_OFF) #endif chg_mode = SEC_BAT_CHG_MODE_CHARGING_OFF; sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); sec_vote(battery->chgen_vote, VOTER_STORE_MODE, true, chg_mode); } if (battery->capacity <= battery->pdata->store_mode_charging_min && battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); sec_vote(battery->chgen_vote, VOTER_STORE_MODE, false, 0); } } } static void sec_bat_recov_full_capacity(struct sec_battery_info *battery) { sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_FULL_CAPACITY); if (battery->status == POWER_SUPPLY_STATUS_NOT_CHARGING && battery->health == POWER_SUPPLY_HEALTH_GOOD) { #if defined(CONFIG_ENABLE_FULL_BY_SOC) if (battery->capacity >= 100) sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_FULL); else #endif sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); } sec_vote(battery->chgen_vote, VOTER_FULL_CAPACITY, false, 0); } static void sec_bat_check_full_capacity(struct sec_battery_info *battery) { int rechg_capacity = battery->batt_full_capacity - 2; if (battery->batt_full_capacity >= 100 || battery->batt_full_capacity <= 0 || battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { if (battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY) { pr_info("%s: full_capacity(%d) status(%d)\n", __func__, battery->batt_full_capacity, battery->status); sec_bat_recov_full_capacity(battery); } return; } if (battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY) { if (battery->capacity <= rechg_capacity || battery->status == POWER_SUPPLY_STATUS_CHARGING) { pr_info("%s : start re-charging(%d, %d) status(%d)\n", __func__, battery->capacity, rechg_capacity, battery->status); sec_bat_recov_full_capacity(battery); } } else if (battery->capacity >= battery->batt_full_capacity) { pr_info("%s : stop charging(%d, %d)\n", __func__, battery->capacity, battery->batt_full_capacity); sec_bat_set_misc_event(battery, BATT_MISC_EVENT_FULL_CAPACITY, BATT_MISC_EVENT_FULL_CAPACITY); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); sec_vote(battery->chgen_vote, VOTER_FULL_CAPACITY, true, SEC_BAT_CHG_MODE_CHARGING_OFF); sec_bat_send_cs100(battery); } } #if IS_ENABLED(CONFIG_DIRECT_CHARGING) static void sec_bat_check_boottime(void *data, bool dctp_en) { struct sec_battery_info *battery = data; if (!dctp_en) return; if (battery->capacity > 10) { pr_info("%s: %d\n", __func__, battery->pdata->dctp_bootmode_en); battery->pdata->dctp_bootmode_en = false; } } static void sec_bat_d2d_check(struct sec_battery_info *battery, unsigned int capacity, int batt_t, int lrp_t) { union power_supply_propval value = {0, }; int auth = AUTH_LOW_PWR; if ((battery->pdata->d2d_check_type == SB_D2D_NONE) || (battery->d2d_auth != D2D_AUTH_SRC) || battery->vpdo_ocp) { return; } if (battery->pdata->d2d_check_type == SB_D2D_SRCSNK) { psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); battery->vpdo_src_boost = value.intval ? true : false; } if (battery->vpdo_src_boost) { value.intval = SEC_BATTERY_VIN_MA; psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_D2D_REVERSE_OCP, value); battery->vpdo_ocp = value.intval ? true : false; pr_info("%s : reverse ocp(%d)\n", __func__, value.intval); if (battery->vpdo_ocp) battery->hp_d2d = HP_D2D_OCP; } if ((batt_t >= 500) || (batt_t <= 0)) { battery->hp_d2d = HP_D2D_BATT_TMP; } else if (lrp_t > 370) { battery->hp_d2d = HP_D2D_LRP_TMP; } else if (battery->vpdo_ocp) { battery->hp_d2d = HP_D2D_OCP; } else if (capacity < 30) { battery->hp_d2d = HP_D2D_SOC; } else { if (battery->hp_d2d == HP_D2D_BATT_TMP) { if ((batt_t <= 480) && (batt_t >= 20)) { auth = AUTH_HIGH_PWR; battery->hp_d2d = HP_D2D_ON; } else battery->vpdo_auth_stat = auth; } else { if (battery->lcd_status) { battery->hp_d2d = HP_D2D_LCD; } else { auth = AUTH_HIGH_PWR; battery->hp_d2d = HP_D2D_ON; } } } pr_info("%s : auth %s, hp d2d (%d)\n", __func__, (auth == AUTH_LOW_PWR) ? "LOW PWR" : "HIGH PWR", battery->hp_d2d); if (battery->vpdo_auth_stat != auth) { sec_pd_vpdo_auth(auth, battery->pdata->d2d_check_type); pr_info("%s : vpdo auth changed\n", __func__); battery->vpdo_auth_stat = auth; } } #endif static void sb_monitor_preliminary_checks(struct sec_battery_info *battery) { sec_bat_get_battery_info(battery); #if IS_ENABLED(CONFIG_DIRECT_CHARGING) sec_bat_d2d_check(battery, battery->capacity, battery->temperature, battery->lrp); #endif #if defined(CONFIG_BATTERY_CISD) sec_bat_cisd_check(battery); #endif #if defined(CONFIG_STEP_CHARGING) sec_bat_check_step_charging(battery); #endif /* time to full check */ sec_bat_calc_time_to_full(battery); sec_bat_check_full_capacity(battery); #if defined(CONFIG_WIRELESS_TX_MODE) /* tx mode check */ sec_bat_check_tx_mode(battery); #endif } static bool sb_monitor_critical_checks(struct sec_battery_info *battery) { bool checks_pass = !sec_bat_do_test_function(battery) && /* 0. test mode */ sec_bat_battery_cable_check(battery) && /* 1. battery check */ sec_bat_voltage_check(battery); /* 2. voltage check */ return checks_pass; } static void sb_monitor_nonurgent_checks(struct sec_battery_info *battery) { /* 4. bat thm check */ sec_bat_thermal_check(battery); /* 5. full charging check */ if (!(battery->current_event & SEC_BAT_CURRENT_EVENT_HIGH_TEMP_SWELLING)) sec_bat_fullcharged_check(battery); /* 6. additional check */ if (battery->pdata->monitor_additional_check) battery->pdata->monitor_additional_check(); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) if (is_wireless_type(battery->cable_type) && !battery->wc_cv_mode && battery->charging_passed_time > 10) sec_bat_wc_cv_mode_check(battery); #endif #if IS_ENABLED(CONFIG_DIRECT_CHARGING) sec_bat_check_boottime(battery, battery->pdata->dctp_bootmode_en); #if defined(CONFIG_STEP_CHARGING) if (is_pd_apdo_wire_type(battery->cable_type)) sec_bat_check_dc_step_charging(battery); #endif #endif #if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) && !defined(CONFIG_SEC_FACTORY) sec_bat_limiter_check(battery); #endif } static bool sb_monitor_checks(struct sec_battery_info *battery) { bool update_battery_info = true; sb_monitor_preliminary_checks(battery); /* Preliminary Checks */ if (sb_monitor_critical_checks(battery)) { /* Do Critical Checks*/ /* CheckShort Polling & Monitor Count */ if (battery->pdata->monitor_initial_count || sec_bat_is_short_polling(battery)) update_battery_info = false; // Don't Update Battery Info else if (sec_bat_time_management(battery)) /* 3. time management */ sb_monitor_nonurgent_checks(battery); } return update_battery_info; } static void sb_monitor_update(struct sec_battery_info *battery, bool update_battery_info) { union power_supply_propval val = {0, }; // Update Battery Info if (update_battery_info) { /* clear HEATING_CONTROL*/ sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL); /* calculate safety time */ if (battery->charger_mode == SEC_BAT_CHG_MODE_CHARGING) sec_bat_calculate_safety_time(battery); /* set charging current */ sec_bat_set_charging_current(battery); if (sec_bat_recheck_input_work(battery)) sec_bat_run_input_check_work(battery, 0); } // Update Charger psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_MONITOR_WORK, val); #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (is_pd_apdo_wire_type(battery->cable_type) && (val.intval == LOW_VBAT_SET)) sec_vote_refresh(battery->fcc_vote); #endif // Update Fuelgauge psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_MONITOR_WORK, val); if (battery->pdata->wireless_charger_name) psy_do_property(battery->pdata->wireless_charger_name, get, POWER_SUPPLY_EXT_PROP_MONITOR_WORK, val); } static void sb_monitor_internal(struct sec_battery_info *battery) { bool update_battery_info = true; bool bypass_checks = false; #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) sec_bat_check_wc_available(battery); if (is_wireless_type(battery->cable_type) && !battery->wc_auth_retried && !sec_bat_get_lpmode()) sec_bat_check_wc_re_auth(battery); #endif #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) if ((battery->cable_type != SEC_BATTERY_CABLE_NONE) && (battery->batt_f_mode == OB_MODE)) { battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); battery->cable_type = SEC_BATTERY_CABLE_NONE; bypass_checks = true; // Skip Checks } #endif if (!bypass_checks) update_battery_info = sb_monitor_checks(battery); sb_monitor_update(battery, update_battery_info); } static void sb_monitor_print(struct sec_battery_info *battery, const char *funcname) { pr_info("%s: Status(%s), mode(%s), Health(%s), Cable(%s, %s, %d, %d), rp(%d), HV(%s), flash(%d) mst(%d)\n", funcname, sb_get_bst_str(battery->status), sb_get_cm_str(battery->charging_mode), sb_get_hl_str(battery->health), sb_get_ct_str(battery->cable_type), sb_get_ct_str(battery->wire_status), battery->muic_cable_type, battery->pd_usb_attached, battery->sink_status.rp_currentlvl, battery->hv_chg_name, battery->flash_state, battery->mst_en); pr_info("%s: lcd(%d), slate(%d), store(%d), siop_level(%d), sleep_mode(%d)" #if defined(CONFIG_BATTERY_AGE_FORECAST_DETACHABLE) ", Cycle(%dw)" #else ", Cycle(%d)" #endif "\n", funcname, battery->lcd_status, is_slate_mode(battery), battery->store_mode, battery->siop_level, battery->sleep_mode #if defined(CONFIG_BATTERY_AGE_FORECAST) , battery->batt_cycle #else , -1 #endif ); #if defined(CONFIG_WIRELESS_TX_MODE) if (battery->wc_tx_enable) { pr_info("@Tx_Mode %s: Rx(%s), WC_TX_VOUT(%dmV), UNO_IOUT(%d), MFC_IOUT(%d) AFC_DISABLE(%d)\n", funcname, sb_rx_type_str(battery->wc_rx_type), battery->wc_tx_vout, battery->tx_uno_iout, battery->tx_mfc_iout, battery->afc_disable); } #endif #if defined(CONFIG_ENG_BATTERY_CONCEPT) pr_info("%s: battery->stability_test(%d), battery->eng_not_full_status(%d)\n", funcname, battery->stability_test, battery->eng_not_full_status); #endif } static void sec_bat_monitor_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, monitor_work.work); dev_dbg(battery->dev, "%s: Start\n", __func__); if (!sb_check_skip_monitor(battery)) { sb_monitor_internal(battery); // Internal Function Which Performs the Checks sb_monitor_print(battery, __func__); // Print Monitor /* store mode & fac bin */ sec_bat_check_store_mode(battery); power_supply_changed(battery->psy_bat); } sec_bat_set_polling(battery); #if defined(CONFIG_WIRELESS_TX_MODE) if (battery->tx_switch_mode_change) sec_bat_run_wpc_tx_work(battery, 0); #endif if (battery->capacity <= 0 || battery->health_change) __pm_wakeup_event(battery->monitor_ws, jiffies_to_msecs(HZ * 5)); else __pm_relax(battery->monitor_ws); dev_dbg(battery->dev, "%s: End\n", __func__); } static enum alarmtimer_restart sec_bat_alarm(struct alarm *alarm, ktime_t now) { struct sec_battery_info *battery = container_of(alarm, struct sec_battery_info, polling_alarm); dev_dbg(battery->dev, "%s\n", __func__); /* * In wake up, monitor work will be queued in complete function * To avoid duplicated queuing of monitor work, * do NOT queue monitor work in wake up by polling alarm */ if (!battery->polling_in_sleep) { __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); dev_dbg(battery->dev, "%s: Activated\n", __func__); } return ALARMTIMER_NORESTART; } __visible_for_testing void sec_bat_check_input_voltage(struct sec_battery_info *battery, int cable_type) { unsigned int voltage = 0; int input_current = battery->pdata->charging_current[cable_type].input_current_limit; #if !defined(CONFIG_NO_BATTERY) if (battery->status == POWER_SUPPLY_STATUS_DISCHARGING) return; #endif if (is_pd_wire_type(cable_type)) { battery->max_charge_power = battery->pd_max_charge_power; return; } else if (is_hv_wire_12v_type(cable_type)) voltage = SEC_INPUT_VOLTAGE_12V; else if (is_hv_wire_9v_type(cable_type)) voltage = SEC_INPUT_VOLTAGE_9V; #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) else if (cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 || cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20) voltage = battery->wc20_vout; else if (is_hv_wireless_type(cable_type) || cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) voltage = SEC_INPUT_VOLTAGE_10V; else if (is_nv_wireless_type(cable_type)) voltage = SEC_INPUT_VOLTAGE_5_5V; #endif else voltage = SEC_INPUT_VOLTAGE_5V; battery->input_voltage = voltage; battery->charge_power = mW_by_mVmA(voltage, input_current); #if !defined(CONFIG_SEC_FACTORY) if (battery->charge_power > battery->max_charge_power) #endif battery->max_charge_power = battery->charge_power; pr_info("%s: input_voltage:%dmV, charge_power:%dmW, max_charge_power:%dmW)\n", __func__, battery->input_voltage, battery->charge_power, battery->max_charge_power); } EXPORT_SYMBOL_KUNIT(sec_bat_check_input_voltage); static void sec_bat_set_usb_configure(struct sec_battery_info *battery, int usb_status) { int cable_work_delay = 0; pr_info("%s: usb configured %d -> %d\n", __func__, battery->prev_usb_conf, usb_status); if (usb_status == USB_CURRENT_UNCONFIGURED) { sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_USB_100MA, SEC_BAT_CURRENT_EVENT_USB_STATE); if (battery->cable_type == SEC_BATTERY_CABLE_USB && !sec_bat_get_lpmode()) { sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); } } else if (usb_status == USB_CURRENT_HIGH_SPEED || usb_status == USB_CURRENT_SUPER_SPEED) { battery->usb_slow_chg = false; sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); if (usb_status == USB_CURRENT_HIGH_SPEED) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_STATE); if (battery->cable_type == SEC_BATTERY_CABLE_USB) { sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->default_usb_charging_current); sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->default_usb_input_current); } } else { sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_USB_SUPER, SEC_BAT_CURRENT_EVENT_USB_STATE); if (battery->cable_type == SEC_BATTERY_CABLE_USB) { sec_vote(battery->fcc_vote, VOTER_CABLE, true, USB_CURRENT_SUPER_SPEED); sec_vote(battery->input_vote, VOTER_CABLE, true, USB_CURRENT_SUPER_SPEED); } } if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL3) { if (battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE) { sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->default_charging_current); sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->default_input_current); } else { if (battery->store_mode) { sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->max_charging_current); sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->rp_current_rdu_rp3); } else { if (!(is_pd_wire_type(battery->wire_status) || is_pd_wire_type(battery->cable_type))) { sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->max_charging_current); sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->rp_current_rp3); } } } } else if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL2) { if (!(is_pd_wire_type(battery->wire_status) || is_pd_wire_type(battery->cable_type))) { sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->rp_current_rp2); sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->rp_current_rp2); } } } else if (usb_status == USB_CURRENT_SUSPENDED) { battery->usb_slow_chg = false; sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_USB_SUSPENDED, SEC_BAT_CURRENT_EVENT_USB_STATE); sec_vote(battery->chgen_vote, VOTER_SUSPEND, true, SEC_BAT_CHG_MODE_BUCK_OFF); if (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT) { sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); } cable_work_delay = 500; store_battery_log( "USB suspend:%d%%,%dmV,%s,ct(%d,%d,%d),cev(0x%x),mev(0x%x)", battery->capacity, battery->voltage_now, sb_get_bst_str(battery->status), battery->cable_type, battery->wire_status, battery->muic_cable_type, battery->current_event, battery->misc_event ); } else if (usb_status == USB_CURRENT_CLEAR) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_STATE); sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); } if (usb_status != USB_CURRENT_SUSPENDED) sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); battery->prev_usb_conf = usb_status; cancel_delayed_work(&battery->cable_work); __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, msecs_to_jiffies(cable_work_delay)); } #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) __visible_for_testing void sec_bat_ext_event_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, ext_event_work.work); sec_bat_ext_event_work_content(battery); } EXPORT_SYMBOL_KUNIT(sec_bat_ext_event_work); static void sec_bat_wpc_tx_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, wpc_tx_work.work); sec_bat_wpc_tx_work_content(battery); } __visible_for_testing void sec_bat_wpc_tx_en_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, wpc_tx_en_work.work); sec_bat_wpc_tx_en_work_content(battery); } EXPORT_SYMBOL_KUNIT(sec_bat_wpc_tx_en_work); __visible_for_testing void sec_bat_txpower_calc_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, wpc_txpower_calc_work.work); sec_bat_txpower_calc(battery); } EXPORT_SYMBOL_KUNIT(sec_bat_txpower_calc_work); __visible_for_testing void sec_bat_otg_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, otg_work.work); set_wireless_otg_input_current(battery); } EXPORT_SYMBOL_KUNIT(sec_bat_otg_work); static void sec_bat_wc20_current_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, wc20_current_work.work); sec_bat_set_wc20_current(battery, battery->wc20_info_idx); sec_bat_predict_wc20_time_to_full_current(battery, battery->wc20_info_idx); __pm_relax(battery->wc20_current_ws); } static void sec_bat_wc_ept_timeout_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, wc_ept_timeout_work.work); battery->wc_ept_timeout = false; __pm_relax(battery->wc_ept_timeout_ws); } #endif static void cw_check_pd_wire_type(struct sec_battery_info *battery) { if (is_pd_wire_type(battery->wire_status)) { int pdo_num = battery->sink_status.current_pdo_num; sec_bat_get_input_current_in_power_list(battery); sec_bat_get_charging_current_in_power_list(battery); #if defined(CONFIG_STEP_CHARGING) #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (!is_pd_apdo_wire_type(battery->cable_type)) { sec_bat_reset_step_charging(battery); } else if (is_pd_apdo_wire_type(battery->cable_type) && (battery->ta_alert_mode != OCP_NONE)) { battery->ta_alert_mode = OCP_WA_ACTIVE; sec_bat_reset_step_charging(battery); } #else sec_bat_reset_step_charging(battery); #endif #endif if (!battery->sink_status.power_list[pdo_num].comm_capable || !battery->sink_status.power_list[pdo_num].suspend) { pr_info("%s : clear suspend event pdo_num:%d, comm:%d, suspend:%d\n", __func__, pdo_num, battery->sink_status.power_list[pdo_num].comm_capable, battery->sink_status.power_list[pdo_num].suspend); sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_SUSPENDED); sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); } } } static bool cw_check_skip_condition(struct sec_battery_info *battery, int current_cable_type) { static bool first_run = true; if (first_run) { first_run = false; if (current_cable_type == SEC_BATTERY_CABLE_NONE) { dev_info(battery->dev, "%s: do not skip! first cable work\n", __func__); return false; } } if ((current_cable_type == battery->cable_type) && !is_slate_mode(battery) && !(battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUSPENDED)) { if (is_pd_wire_type(current_cable_type) && is_pd_wire_type(battery->cable_type)) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC | SEC_BAT_CURRENT_EVENT_AICL); sec_vote(battery->input_vote, VOTER_AICL, false, 0); sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); power_supply_changed(battery->psy_bat); } else if (battery->prev_usb_conf != USB_CURRENT_NONE) { dev_info(battery->dev, "%s: set usb charging current to %d mA\n", __func__, battery->prev_usb_conf); sec_bat_set_charging_current(battery); battery->prev_usb_conf = USB_CURRENT_NONE; } dev_info(battery->dev, "%s: Cable is NOT Changed(%d)\n", __func__, battery->cable_type); /* Do NOT activate cable work for NOT changed */ return true; } return false; } __visible_for_testing int cw_check_cable_switch(struct sec_battery_info *battery, int prev_ct, int cur_ct, bool afc_disabled) { if ((is_wired_type(prev_ct) && is_wireless_fake_type(cur_ct)) || (is_wireless_fake_type(prev_ct) && is_wired_type(cur_ct)) || afc_disabled) { battery->max_charge_power = 0; sec_bat_set_threshold(battery, cur_ct); } if (cur_ct == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) cur_ct = SEC_BATTERY_CABLE_9V_TA; if (!can_usb_suspend_type(cur_ct) && battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUSPENDED) { pr_info("%s: clear suspend event prev_cable_type:%s -> %s\n", __func__, sb_get_ct_str(prev_ct), sb_get_ct_str(cur_ct)); sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_USB_SUSPENDED); sec_vote(battery->chgen_vote, VOTER_SUSPEND, false, 0); sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); } return cur_ct; } #if defined(CONFIG_USE_POGO) static int cw_check_pogo(struct sec_battery_info *battery) { int pogo_current, wire_current; if (battery->pogo_status) { if (battery->wire_status != SEC_BATTERY_CABLE_NONE) { if (battery->pogo_9v) { pogo_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_POGO_9V].input_current_limit; pogo_current = pogo_current * SEC_INPUT_VOLTAGE_9V; } else { pogo_current = battery->pdata->charging_current[SEC_BATTERY_CABLE_POGO].input_current_limit; pogo_current = pogo_current * SEC_INPUT_VOLTAGE_5V; } if (battery->wire_status == SEC_BATTERY_CABLE_PDIC) { if (pogo_current < battery->pd_max_charge_power) return battery->wire_status; } else { wire_current = (battery->wire_status == SEC_BATTERY_CABLE_PREPARE_TA ? battery->pdata->charging_current[SEC_BATTERY_CABLE_TA].input_current_limit : battery->pdata->charging_current[battery->wire_status].input_current_limit); wire_current = wire_current * (is_hv_wire_type(battery->wire_status) ? (battery->wire_status == SEC_BATTERY_CABLE_12V_TA ? SEC_INPUT_VOLTAGE_12V : SEC_INPUT_VOLTAGE_9V) : SEC_INPUT_VOLTAGE_5V); pr_info("%s: pogo_cur(%d), wr_cur(%d), wire_cable_type(%d)\n", __func__, pogo_current, wire_current, battery->wire_status); if (pogo_current < wire_current) return battery->wire_status; else return (battery->pogo_9v) ? SEC_BATTERY_CABLE_POGO_9V : SEC_BATTERY_CABLE_POGO; } } return (battery->pogo_9v) ? SEC_BATTERY_CABLE_POGO_9V : SEC_BATTERY_CABLE_POGO; } return battery->wire_status; } #endif static void cw_set_psp_online2drivers(struct sec_battery_info *battery) { union power_supply_propval val = {0, }; if (battery->cable_type != SEC_BATTERY_CABLE_WIRELESS_FAKE) { if (is_slate_mode(battery)) val.intval = SEC_BATTERY_CABLE_NONE; else val.intval = battery->cable_type; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_ONLINE, val); val.intval = battery->cable_type; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_ONLINE, val); if (battery->wc_tx_enable) { val.intval = is_wired_type(battery->wire_status) ? 1 : 0; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_WIRELESS_WR_CONNECTED, val); } sec_vote_refresh(battery->chgen_vote); } } static void cw_check_wc_condition(struct sec_battery_info *battery, int prev_cable_type) { /* need to move to wireless set property */ battery->wpc_vout_level = WIRELESS_VOUT_10V; if (is_wireless_type(battery->cable_type)) { power_supply_changed(battery->psy_bat); } else if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_FAKE) { power_supply_changed(battery->psy_bat); } /* For wire + wireless case */ if (!is_wireless_type(prev_cable_type) && is_wireless_type(battery->cable_type)) { pr_info("%s: non-wl -> wl: prev_cable(%s) , current_cable(%s)\n", __func__, sb_get_ct_str(prev_cable_type), sb_get_ct_str(battery->cable_type)); battery->wc_cv_mode = false; battery->charging_passed_time = 0; } } static void chg_retention_time_check(struct sec_battery_info *battery) { struct timespec64 ts = {0, }; ts = ktime_to_timespec64(ktime_get_boottime()); pr_info("%s: end time = %lld\n", __func__, ts.tv_sec); if (ts.tv_sec <= battery->charging_retention_time || !battery->charging_retention_time) return; battery->charging_retention_time = ts.tv_sec - battery->charging_retention_time; /* get only minutes part */ battery->charging_retention_time /= 60; pr_info("%s: retention time = %ld mins\n", __func__, battery->charging_retention_time); #if defined(CONFIG_BATTERY_CISD) battery->cisd.data[CISD_DATA_TOTAL_CHG_RETENTION_TIME_PER_DAY] += battery->charging_retention_time; battery->cisd.data[CISD_DATA_CHG_RETENTION_TIME_PER_DAY] = max(battery->cisd.data[CISD_DATA_CHG_RETENTION_TIME_PER_DAY], (int)battery->charging_retention_time); #endif battery->charging_retention_time = 0; } static void chg_start_time_check(struct sec_battery_info *battery) { struct timespec64 ts = {0, }; ts = ktime_to_timespec64(ktime_get_boottime()); battery->charging_retention_time = ts.tv_sec; pr_info("%s: start time = %lu\n", __func__, battery->charging_retention_time); } static void cw_nocharge_type(struct sec_battery_info *battery) { int i; /* initialize all status */ battery->charging_mode = SEC_BATTERY_CHARGING_NONE; battery->is_recharging = false; #if defined(CONFIG_BATTERY_CISD) battery->cisd.ab_vbat_check_count = 0; battery->cisd.state &= ~CISD_STATE_OVER_VOLTAGE; battery->d2d_check = false; #endif #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) battery->wc20_power_class = 0; sec_bat_predict_wc20_time_to_full_current(battery, -1); battery->wc20_rx_power = 0; battery->wc20_vout = 0; #endif battery->input_voltage = 0; battery->charge_power = 0; battery->max_charge_power = 0; battery->pd_max_charge_power = 0; battery->pd_rated_power = 0; sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); battery->thermal_zone = BAT_THERMAL_NORMAL; battery->chg_limit = false; battery->lrp_limit = false; battery->lrp_step = LRP_NONE; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; #endif battery->mix_limit = false; battery->chg_limit_recovery_cable = SEC_BATTERY_CABLE_NONE; battery->wc_heating_start_time = 0; sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); battery->prev_usb_conf = USB_CURRENT_NONE; battery->ta_alert_mode = OCP_NONE; battery->prev_tx_phm_mode = false; battery->wpc_temp_v2_offset = 0; sec_bat_cancel_input_check_work(battery); sec_bat_change_default_current(battery, SEC_BATTERY_CABLE_USB, battery->pdata->default_usb_input_current, battery->pdata->default_usb_charging_current); sec_bat_change_default_current(battery, SEC_BATTERY_CABLE_TA, battery->pdata->default_input_current, battery->pdata->default_charging_current); sec_bat_change_default_current(battery, SEC_BATTERY_CABLE_HV_WIRELESS_20, battery->pdata->default_wc20_input_current, battery->pdata->default_wc20_charging_current); /* usb default current is 100mA before configured*/ sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_USB_100MA, (SEC_BAT_CURRENT_EVENT_CHARGE_DISABLE | SEC_BAT_CURRENT_EVENT_AFC | SEC_BAT_CURRENT_EVENT_VBAT_OVP | SEC_BAT_CURRENT_EVENT_VSYS_OVP | SEC_BAT_CURRENT_EVENT_CHG_LIMIT | SEC_BAT_CURRENT_EVENT_AICL | SEC_BAT_CURRENT_EVENT_SELECT_PDO | SEC_BAT_CURRENT_EVENT_WDT_EXPIRED | SEC_BAT_CURRENT_EVENT_25W_OCP | SEC_BAT_CURRENT_EVENT_DC_ERR | SEC_BAT_CURRENT_EVENT_USB_STATE | SEC_BAT_CURRENT_EVENT_SEND_UVDM)); sec_bat_set_misc_event(battery, 0, (BATT_MISC_EVENT_WIRELESS_MISALIGN | BATT_MISC_EVENT_HV_BY_AICL)); sec_bat_recov_full_capacity(battery); /* should call this after setting discharging */ /* slate_mode needs to be clear manually since smart switch does not disable slate_mode sometimes */ if (is_slate_mode(battery)) { int voter_status = SEC_BAT_CHG_MODE_CHARGING; if (get_sec_voter_status(battery->chgen_vote, VOTER_SMART_SLATE, &voter_status) < 0) pr_err("%s: INVALID VOTER ID\n", __func__); pr_info("%s: voter_status: %d\n", __func__, voter_status); // debug if (voter_status == SEC_BAT_CHG_MODE_BUCK_OFF) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SLATE); sec_bat_set_mfc_on(battery, WPC_EN_SLATE); dev_info(battery->dev, "%s: disable slate mode(smart switch) manually\n", __func__); } } chg_retention_time_check(battery); battery->wc_cv_mode = false; battery->is_sysovlo = false; battery->is_vbatovlo = false; battery->is_abnormal_temp = false; battery->auto_mode = false; #if !defined(CONFIG_SEC_FACTORY) if (sec_bat_get_lpmode()) battery->usb_thm_status = USB_THM_NORMAL; #endif for (i = 0; i < VOTER_MAX; i++) { if (i != VOTER_FLASH && i != VOTER_MST && i != VOTER_FW) sec_vote(battery->iv_vote, i, false, 0); if (i == VOTER_SIOP || i == VOTER_SLATE || i == VOTER_AGING_STEP || i == VOTER_WC_TX || i == VOTER_MUIC_ABNORMAL || i == VOTER_NO_BATTERY) continue; sec_vote(battery->topoff_vote, i, false, 0); if (i != VOTER_FW) sec_vote(battery->chgen_vote, i, false, 0); sec_vote(battery->input_vote, i, false, 0); sec_vote(battery->fcc_vote, i, false, 0); sec_vote(battery->fv_vote, i, false, 0); sec_vote(battery->dc_fv_vote, i, false, 0); } cancel_delayed_work(&battery->slowcharging_work); #if !defined(CONFIG_NO_BATTERY) /* Discharging has 100mA current unlike non LEGO model */ sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); #endif battery->usb_slow_chg = false; } static void cw_slate_mode(struct sec_battery_info *battery) { #if IS_ENABLED(CONFIG_DIRECT_CHARGING) union power_supply_propval val = {0, }; #endif int j = 0; /* Some charger ic's buck is enabled after vbus off, So disable buck again*/ sec_vote_refresh(battery->chgen_vote); battery->is_recharging = false; battery->cable_type = SEC_BATTERY_CABLE_NONE; battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); battery->is_sysovlo = false; battery->is_vbatovlo = false; battery->is_abnormal_temp = false; for (j = 0; j < VOTER_MAX; j++) { sec_vote(battery->iv_vote, j, false, 0); if (j == VOTER_SIOP || j == VOTER_SLATE || j == VOTER_SMART_SLATE || j == VOTER_AGING_STEP || j == VOTER_WC_TX) continue; sec_vote(battery->topoff_vote, j, false, 0); sec_vote(battery->chgen_vote, j, false, 0); sec_vote(battery->input_vote, j, false, 0); sec_vote(battery->fcc_vote, j, false, 0); sec_vote(battery->fv_vote, j, false, 0); } #if IS_ENABLED(CONFIG_DIRECT_CHARGING) /* No need val data */ psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_DC_INITIALIZE, val); #endif battery->thermal_zone = BAT_THERMAL_NORMAL; sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); } static void cw_usb_suspend(struct sec_battery_info *battery) { /* Some charger ic's buck is enabled after vbus off, So disable buck again*/ sec_vote_refresh(battery->chgen_vote); battery->is_recharging = false; battery->cable_type = SEC_BATTERY_CABLE_NONE; battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); battery->is_sysovlo = false; battery->is_vbatovlo = false; battery->is_abnormal_temp = false; battery->thermal_zone = BAT_THERMAL_NORMAL; sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); } static bool is_afc_evt_clear(int cable_type, int wire_status, int wc_status, unsigned int rp_currentlvl) { if ((cable_type == SEC_BATTERY_CABLE_TA && rp_currentlvl == RP_CURRENT_LEVEL_DEFAULT) || cable_type == SEC_BATTERY_CABLE_WIRELESS || cable_type == SEC_BATTERY_CABLE_PMA_WIRELESS || (is_hv_wire_type(cable_type) && (wc_status == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20 || wc_status == SEC_BATTERY_CABLE_HV_WIRELESS_20 || wire_status == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT))) return false; else return true; } static void cw_prev_cable_is_nocharge(struct sec_battery_info *battery) { #if defined(CONFIG_MTK_CHARGER) union power_supply_propval value = {0, }; #endif chg_start_time_check(battery); if ((battery->cable_type == SEC_BATTERY_CABLE_WIRELESS) && (battery->health > POWER_SUPPLY_HEALTH_GOOD)) { pr_info("%s: prev cable type was fake and health is not good\n", __func__); return; } #if defined(CONFIG_ARCH_MTK_PROJECT) if (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_100MA) { if ((battery->cable_type == SEC_BATTERY_CABLE_USB) && !lpcharge) { pr_info("%s: usb unconfigured\n", __func__); sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); } } #endif if (battery->pdata->full_check_type != SEC_BATTERY_FULLCHARGED_NONE) battery->charging_mode = SEC_BATTERY_CHARGING_1ST; else battery->charging_mode = SEC_BATTERY_CHARGING_2ND; #if defined(CONFIG_ENABLE_FULL_BY_SOC) if (battery->capacity >= 100) { sec_bat_do_fullcharged(battery, true); pr_info("%s: charging start at full, do not turn on charging\n", __func__); #if defined(CONFIG_MTK_CHARGER) /* * MTK check the AFC when charging-on. * Even though do not charging-on, need to check AFC still. */ value.intval = battery->cable_type; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_AFC_INIT, value); #endif } else if (!(battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY)) { sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); } #else if (!(battery->misc_event & BATT_MISC_EVENT_FULL_CAPACITY)) { sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, false, 0); sec_vote(battery->chgen_vote, VOTER_FULL_CHARGE, false, 0); } #endif sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); /* bat thm check to prevent charging current spike */ sec_bat_thermal_check(battery); sec_vote(battery->chgen_vote, VOTER_CABLE, true, SEC_BAT_CHG_MODE_CHARGING); if (battery->cable_type == SEC_BATTERY_CABLE_USB && !sec_bat_get_lpmode()) queue_delayed_work(battery->monitor_wqueue, &battery->slowcharging_work, msecs_to_jiffies(3000)); if (is_hv_wireless_type(battery->cable_type) && battery->sleep_mode) sec_vote(battery->input_vote, VOTER_SLEEP_MODE, true, battery->pdata->sleep_mode_limit_current); ttf_work_start(battery); } static int sec_bat_check_pd_iv(SEC_PD_SINK_STATUS *sink_status) { int i; int max_voltage = SEC_INPUT_VOLTAGE_5V; for (i = 1; i <= sink_status->available_pdo_num; i++) { if ((sink_status->power_list[i].pdo_type == APDO_TYPE) || !sink_status->power_list[i].accept) break; if (sink_status->power_list[i].max_voltage == SEC_INPUT_VOLTAGE_9V) { max_voltage = SEC_INPUT_VOLTAGE_9V; break; } } return max_voltage; } #if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) static bool sb_ct_has_9v(struct sec_battery_info *battery, int ct) { if (is_hv_wire_type(ct)) return true; else if (is_pd_wire_type(ct)) { if (sec_bat_check_pd_iv(&battery->sink_status) == SEC_INPUT_VOLTAGE_9V) return true; } return false; } #endif static void cw_set_iv(struct sec_battery_info *battery) { if (is_nocharge_type(battery->cable_type) || is_wireless_fake_type(battery->cable_type)) return; if (is_hv_wire_type(battery->cable_type)) sec_vote(battery->iv_vote, VOTER_CABLE, true, SEC_INPUT_VOLTAGE_9V); else if (is_pd_wire_type(battery->cable_type)) sec_vote(battery->iv_vote, VOTER_CABLE, true, sec_bat_check_pd_iv(&battery->sink_status)); #if defined(CONFIG_USE_POGO) else if (battery->cable_type == SEC_BATTERY_CABLE_POGO_9V) sec_vote(battery->iv_vote, VOTER_CABLE, true, SEC_INPUT_VOLTAGE_9V); #endif else sec_vote(battery->iv_vote, VOTER_CABLE, true, SEC_INPUT_VOLTAGE_5V); } __visible_for_testing void sec_bat_cable_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, cable_work.work); int current_cable_type = SEC_BATTERY_CABLE_NONE; unsigned int input_current; unsigned int charging_current; bool clear_afc_evt = false; int prev_cable_type = battery->cable_type; int monitor_work_delay = 0; dev_info(battery->dev, "%s: Start\n", __func__); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL, SEC_BAT_CURRENT_EVENT_SKIP_HEATING_CONTROL); #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) #if !defined(CONFIG_MTK_CHARGER) || defined(CONFIG_VIRTUAL_MUIC) /* * showing charging icon and noti(no sound, vi, haptic) only * if slow insertion is detected by MUIC */ sec_bat_set_misc_event(battery, (battery->muic_cable_type == ATTACHED_DEV_TIMEOUT_OPEN_MUIC ? BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE : 0), BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE); #endif #endif /* check wire type PD charger case */ cw_check_pd_wire_type(battery); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) current_cable_type = sec_bat_choose_cable_type(battery); #elif defined(CONFIG_USE_POGO) current_cable_type = cw_check_pogo(battery); #else current_cable_type = battery->wire_status; #endif /* check cable work skip condition */ if (cw_check_skip_condition(battery, current_cable_type)) goto end_of_cable_work; /* to clear this value when cable type switched without detach */ prev_cable_type = battery->cable_type; battery->cable_type = cw_check_cable_switch(battery, prev_cable_type, current_cable_type, check_afc_disabled_type(battery->muic_cable_type)); /* set online(cable type) */ cw_set_psp_online2drivers(battery); /* check wireless charging condition */ cw_check_wc_condition(battery, prev_cable_type); if (battery->pdata->check_cable_result_callback) battery->pdata->check_cable_result_callback(battery->cable_type); /* * platform can NOT get information of cable connection because wakeup time is too short to check uevent * To make sure that target is wakeup if cable is connected and disconnected, * activated wake lock in a few seconds */ __pm_wakeup_event(battery->vbus_ws, jiffies_to_msecs(HZ * 10)); if (is_nocharge_type(battery->cable_type) || ((battery->pdata->cable_check_type & SEC_BATTERY_CABLE_CHECK_NOINCOMPATIBLECHARGE) && battery->cable_type == SEC_BATTERY_CABLE_UNKNOWN)) { pr_info("%s: prev_cable_type(%d)\n", __func__, prev_cable_type); cw_nocharge_type(battery); #if IS_ENABLED(CONFIG_DUAL_BATTERY) battery->limiter_check = false; #endif } else if (is_slate_mode(battery)) { pr_info("%s: slate mode on\n", __func__); cw_slate_mode(battery); } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUSPENDED) { pr_info("%s: usb suspend\n", __func__); cw_usb_suspend(battery); battery->prev_usb_conf = USB_CURRENT_NONE; monitor_work_delay = 3000; goto run_monitor_work; } else if (is_nocharge_type(prev_cable_type) || prev_cable_type == SEC_BATTERY_CABLE_WIRELESS_FAKE) { pr_info("%s: c: %d, ov: %d, at: %d, cm: %d, tz: %d\n", __func__, battery->cable_type, battery->is_vbatovlo, battery->is_abnormal_temp, battery->charger_mode, battery->thermal_zone); clear_afc_evt = is_afc_evt_clear(battery->cable_type, battery->wire_status, battery->wc_status, battery->sink_status.rp_currentlvl); if (!clear_afc_evt) sec_bat_check_afc_input_current(battery); cw_prev_cable_is_nocharge(battery); sec_vote_refresh(battery->iv_vote); } else if (is_hv_wire_type(battery->cable_type) && (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC)) { clear_afc_evt = is_afc_evt_clear(battery->cable_type, battery->wire_status, battery->wc_status, battery->sink_status.rp_currentlvl); } #if defined(CONFIG_STEP_CHARGING) if (!is_hv_wire_type(battery->cable_type) && !is_pd_wire_type(battery->cable_type) && (battery->sink_status.rp_currentlvl != RP_CURRENT_LEVEL3)) sec_bat_reset_step_charging(battery); #endif /* set input voltage by cable type */ cw_set_iv(battery); #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && defined(CONFIG_SEC_FACTORY) sec_bat_usb_factory_set_vote(battery, true); #endif /* Check VOTER_SIOP to set up current based on cable_type */ __pm_stay_awake(battery->siop_level_ws); queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); if (battery->cable_type != SEC_BATTERY_CABLE_WIRELESS_FAKE) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AICL); sec_vote(battery->input_vote, VOTER_AICL, false, 0); sec_bat_check_input_voltage(battery, battery->cable_type); /* to init battery type current when wireless charging -> battery case */ sec_vote_refresh(battery->input_vote); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) set_wireless_otg_input_current(battery); #endif input_current = battery->pdata->charging_current[current_cable_type].input_current_limit; charging_current = battery->pdata->charging_current[current_cable_type].fast_charging_current; sec_vote(battery->fcc_vote, VOTER_CABLE, true, charging_current); sec_vote(battery->input_vote, VOTER_CABLE, true, input_current); } if ((!is_nocharge_type(battery->cable_type) && battery->cable_type != SEC_BATTERY_CABLE_USB) || sec_bat_get_lpmode()) { sec_vote(battery->fcc_vote, VOTER_USB_100MA, false, 0); sec_vote(battery->input_vote, VOTER_USB_100MA, false, 0); } if (clear_afc_evt) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); } store_battery_log( "CW:%d%%,%dmV,%s,ct(%s,%s,%d,%d),slate(%d),cev(0x%x),mev(0x%x)", battery->capacity, battery->voltage_now, sb_get_bst_str(battery->status), sb_get_ct_str(battery->cable_type), sb_get_ct_str(battery->wire_status), battery->muic_cable_type, battery->pd_usb_attached, is_slate_mode(battery), battery->current_event, battery->misc_event ); /* * polling time should be reset when cable is changed * polling_in_sleep should be reset also * before polling time is re-calculated * to prevent from counting 1 for events * right after cable is connected */ battery->polling_in_sleep = false; sec_bat_get_polling_time(battery); pr_info("%s: Status:%s, Sleep:%s, Charging:%s, Short Poll:%s\n", __func__, sb_get_bst_str(battery->status), battery->polling_in_sleep ? "Yes" : "No", (battery->charging_mode == SEC_BATTERY_CHARGING_NONE) ? "No" : "Yes", battery->polling_short ? "Yes" : "No"); pr_info("%s: Polling time is reset to %d sec.\n", __func__, battery->polling_time); battery->polling_count = 1; /* initial value = 1 */ run_monitor_work: __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, msecs_to_jiffies(monitor_work_delay)); end_of_cable_work: #if defined(CONFIG_MTK_CHARGER) && !defined(CONFIG_VIRTUAL_MUIC) if ((battery->bc12_cable != SEC_BATTERY_CABLE_TIMEOUT) && (battery->misc_event & BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE)) sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE); #endif __pm_relax(battery->cable_ws); dev_info(battery->dev, "%s: End\n", __func__); } EXPORT_SYMBOL_KUNIT(sec_bat_cable_work); #define MAX_INPUT_CHECK_COUNT 3 __visible_for_testing void sec_bat_input_check_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, input_check_work.work); union power_supply_propval value = {0, }; dev_info(battery->dev, "%s for %s start\n", __func__, sb_get_ct_str(battery->cable_type)); psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, value); battery->current_max = value.intval; if (battery->current_event & SEC_BAT_CURRENT_EVENT_AFC) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); #if !defined(CONFIG_SEC_FACTORY) sec_bat_change_vbus(battery, battery->cable_type, battery->current_event, battery->siop_level); #endif #if defined(CONFIG_BATTERY_CISD) if (battery->cable_type == SEC_BATTERY_CABLE_TA) battery->cisd.cable_data[CISD_CABLE_TA]++; #endif #if defined(CONFIG_WIRELESS_TX_MODE) if (battery->wc_tx_enable) sec_bat_run_wpc_tx_work(battery, 0); #endif } else if (battery->current_event & SEC_BAT_CURRENT_EVENT_SELECT_PDO) { int curr_pdo = 0, pdo = 0, iv = 0, icl = 0; iv = get_sec_vote_result(battery->iv_vote); if ((iv == SEC_INPUT_VOLTAGE_APDO) || (sec_pd_get_pdo_power(&pdo, &iv, &iv, &icl) <= 0) || (sec_pd_get_current_pdo(&curr_pdo) < 0) || (pdo == curr_pdo)) { pr_info("%s: clear select_pdo event(%d, %d, %d)\n", __func__, iv, pdo, curr_pdo); sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_SELECT_PDO); sec_vote(battery->input_vote, VOTER_SELECT_PDO, false, 0); battery->input_check_cnt = 0; /* Check VOTER_SIOP to set up current after select pdo. */ __pm_stay_awake(battery->siop_level_ws); queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); } else if ((++battery->input_check_cnt) % MAX_INPUT_CHECK_COUNT) { pr_info("%s: refresh(%d)!! pdo: %d, curr_pdo: %d\n", __func__, battery->input_check_cnt, pdo, curr_pdo); sec_vote_refresh(battery->iv_vote); return; } } else { dev_info(battery->dev, "%s: Nothing to do\n", __func__); } dev_info(battery->dev, "%s: End\n", __func__); } EXPORT_SYMBOL_KUNIT(sec_bat_input_check_work); #if defined(CONFIG_MTK_CHARGER) && !defined(CONFIG_VIRTUAL_MUIC) static void sec_bat_handle_bc12_connection(struct sec_battery_info *battery) { mutex_lock(&battery->bc12_notylock); battery->wire_status = battery->bc12_cable; dev_info(battery->dev, "%s: current_cable(%d), wc_status(%d), wire_status(%d)\n", __func__, battery->bc12_cable, battery->wc_status, battery->wire_status); if (is_hv_wire_type(battery->bc12_cable)) { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AICL); sec_vote(battery->input_vote, VOTER_AICL, false, 0); } if (battery->bc12_cable == SEC_BATTERY_CABLE_NONE) { #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) sec_bat_usb_factory_clear(battery); #endif #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; #endif #if defined(CONFIG_PDIC_NOTIFIER) battery->init_src_cap = false; battery->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; #endif } #if defined(CONFIG_PDIC_NOTIFIER) else if (battery->cable_type != battery->bc12_cable && battery->sink_status.rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && (battery->bc12_cable == SEC_BATTERY_CABLE_USB || battery->bc12_cable == SEC_BATTERY_CABLE_TA)) { sec_bat_set_rp_current(battery, battery->bc12_cable); } #endif else if ((battery->bc12_cable == SEC_BATTERY_CABLE_TIMEOUT) && (!battery->init_src_cap)) /* * showing charging icon and noti(no sound, vi, haptic) only * if slow insertion is detected by BC1.2 */ sec_bat_set_misc_event(battery, BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE, BATT_MISC_EVENT_TIMEOUT_OPEN_TYPE); /* cable is attached or detached * if battery->bc12_cable is minus value, * check cable by sec_bat_get_cable_type() * although SEC_BATTERY_CABLE_SOURCE_EXTERNAL is set * (0 is SEC_BATTERY_CABLE_UNKNOWN) */ if ((battery->bc12_cable >= 0) && (battery->bc12_cable < SEC_BATTERY_CABLE_MAX) && (battery->pdata->cable_source_type & SEC_BATTERY_CABLE_SOURCE_EXTERNAL)) { __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); } else { if (sec_bat_get_cable_type(battery, battery->pdata->cable_source_type)) { __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); } } mutex_unlock(&battery->bc12_notylock); } /* sec_bat_handle_bc12_connection */ #endif static int sec_bat_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); int current_cable_type = SEC_BATTERY_CABLE_NONE; int full_check_type = SEC_BATTERY_FULLCHARGED_NONE; union power_supply_propval value = {0, }; enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; dev_dbg(battery->dev, "%s: (%d,%d)\n", __func__, psp, val->intval); switch ((int)psp) { case POWER_SUPPLY_PROP_STATUS: if (battery->charging_mode == SEC_BATTERY_CHARGING_1ST) full_check_type = battery->pdata->full_check_type; else full_check_type = battery->pdata->full_check_type_2nd; if ((full_check_type == SEC_BATTERY_FULLCHARGED_CHGINT) && (val->intval == POWER_SUPPLY_STATUS_FULL)) sec_bat_do_fullcharged(battery, false); sec_bat_set_charging_status(battery, val->intval); break; case POWER_SUPPLY_PROP_HEALTH: if (battery->cable_type != SEC_BATTERY_CABLE_WIRELESS_FAKE) { __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, msecs_to_jiffies(100)); } break; case POWER_SUPPLY_PROP_ONLINE: current_cable_type = val->intval; if (current_cable_type < 0) { dev_info(battery->dev, "%s: ignore event(%d)\n", __func__, current_cable_type); } else { if (current_cable_type == SEC_BATTERY_CABLE_OTG) { battery->charging_mode = SEC_BATTERY_CHARGING_NONE; battery->is_recharging = false; sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); battery->cable_type = current_cable_type; __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); break; } else { battery->wire_status = current_cable_type; if (is_nocharge_type(battery->wire_status) && (battery->wc_status != SEC_BATTERY_CABLE_NONE)) current_cable_type = SEC_BATTERY_CABLE_WIRELESS; } #if defined(CONFIG_MTK_CHARGER) && !defined(CONFIG_VIRTUAL_MUIC) battery->bc12_cable = current_cable_type; /* Skip notify from BC1.2 if PDIC is attached already */ if ((is_pd_wire_type(battery->wire_status) || battery->init_src_cap) && (battery->bc12_cable != SEC_BATTERY_CABLE_NONE)) { if (lpcharge) break; else if (battery->usb_thm_status == USB_THM_NORMAL && !(battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) break; } sec_bat_handle_bc12_connection(battery); #endif } #if !defined(CONFIG_MTK_CHARGER) || defined(CONFIG_VIRTUAL_MUIC) dev_info(battery->dev, "%s: current_cable(%d), wc_status(%d), wire_status(%d)\n", __func__, current_cable_type, battery->wc_status, battery->wire_status); /* * cable is attached or detached * if current_cable_type is minus value, * check cable by sec_bat_get_cable_type() * although SEC_BATTERY_CABLE_SOURCE_EXTERNAL is set * (0 is SEC_BATTERY_CABLE_UNKNOWN) */ if ((current_cable_type >= 0) && (current_cable_type < SEC_BATTERY_CABLE_MAX) && (battery->pdata->cable_source_type & SEC_BATTERY_CABLE_SOURCE_EXTERNAL)) { __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work,0); } else { if (sec_bat_get_cable_type(battery, battery->pdata->cable_source_type)) { __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work,0); } } #endif break; case POWER_SUPPLY_PROP_CAPACITY: battery->capacity = val->intval; power_supply_changed(battery->psy_bat); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: break; case POWER_SUPPLY_PROP_CHARGE_TYPE: __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); break; case POWER_SUPPLY_PROP_PRESENT: battery->present = val->intval; __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); break; case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: break; #if defined(CONFIG_BATTERY_CISD) case POWER_SUPPLY_PROP_VOLTAGE_MIN: pr_info("%s: Valert was occurred! run monitor work for updating cisd data!\n", __func__); battery->cisd.data[CISD_DATA_VALERT_COUNT]++; battery->cisd.data[CISD_DATA_VALERT_COUNT_PER_DAY]++; __pm_stay_awake(battery->monitor_ws); queue_delayed_work_on(0, battery->monitor_wqueue, &battery->monitor_work, 0); break; #endif case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { case POWER_SUPPLY_EXT_PROP_AICL_CURRENT: battery->max_charge_power = battery->charge_power = mW_by_mVmA(battery->input_voltage, val->intval); /* Voter should be removed after all chagers is fixed */ sec_vote(battery->input_vote, VOTER_AICL, true, val->intval); #if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) if (battery->pdata->boosting_voltage_aicl) { sec_bat_set_misc_event(battery, BATT_MISC_EVENT_HV_BY_AICL, BATT_MISC_EVENT_HV_BY_AICL); if (sb_ct_has_9v(battery, battery->cable_type)) { sec_vote(battery->input_vote, VOTER_AICL, false, 0); sec_vote(battery->iv_vote, VOTER_AICL, true, SEC_INPUT_VOLTAGE_9V); /* Check siop level to set current */ __pm_stay_awake(battery->siop_level_ws); queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); } } #endif pr_info("%s: aicl : %dmA, %dmW)\n", __func__, val->intval, battery->charge_power); if (is_wired_type(battery->cable_type)) { sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_AICL, SEC_BAT_CURRENT_EVENT_AICL); store_battery_log( "AICL:%d%%,curr(%dmA),%dmV,%s,ct(%d,%d,%d,%d),cev(0x%x)", battery->capacity, val->intval, battery->voltage_now, sb_get_bst_str(battery->status), battery->cable_type, battery->wire_status, battery->muic_cable_type, battery->pd_usb_attached, battery->current_event ); } #if defined(CONFIG_BATTERY_CISD) battery->cisd.data[CISD_DATA_AICL_COUNT]++; battery->cisd.data[CISD_DATA_AICL_COUNT_PER_DAY]++; #endif break; case POWER_SUPPLY_EXT_PROP_SYSOVLO: if (battery->status != POWER_SUPPLY_STATUS_DISCHARGING) { pr_info("%s: Vsys is ovlo !!\n", __func__); battery->is_sysovlo = true; battery->is_recharging = false; battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_health(battery, POWER_SUPPLY_EXT_HEALTH_VSYS_OVP); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_VSYS_OVP, SEC_BAT_CURRENT_EVENT_VSYS_OVP); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); #if defined(CONFIG_BATTERY_CISD) battery->cisd.data[CISD_DATA_VSYS_OVP]++; battery->cisd.data[CISD_DATA_VSYS_OVP_PER_DAY]++; #endif #if IS_ENABLED(CONFIG_SEC_ABC) sec_abc_send_event("MODULE=battery@WARN=vsys_ovp"); #endif sec_vote(battery->chgen_vote, VOTER_SYSOVLO, true, SEC_BAT_CHG_MODE_CHARGING_OFF); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } break; case POWER_SUPPLY_EXT_PROP_VBAT_OVP: if (battery->status != POWER_SUPPLY_STATUS_DISCHARGING) { pr_info("%s: Vbat is ovlo !!\n", __func__); battery->is_vbatovlo = true; battery->is_recharging = false; battery->charging_mode = SEC_BATTERY_CHARGING_NONE; sec_bat_set_health(battery, POWER_SUPPLY_EXT_HEALTH_VBAT_OVP); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_VBAT_OVP, SEC_BAT_CURRENT_EVENT_VBAT_OVP); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_NOT_CHARGING); sec_vote(battery->chgen_vote, VOTER_VBAT_OVP, true, SEC_BAT_CHG_MODE_CHARGING_OFF); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } break; case POWER_SUPPLY_EXT_PROP_USB_CONFIGURE: #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && defined(CONFIG_SEC_FACTORY) pr_info("%s: usb configured %d\n", __func__, val->intval); if (sec_bat_usb_factory_set_vote(battery, true)) break; #endif if (val->intval == USB_CURRENT_CLEAR || val->intval != battery->prev_usb_conf) sec_bat_set_usb_configure(battery, val->intval); break; case POWER_SUPPLY_EXT_PROP_OVERHEAT_NOTIFY: pr_info("%s: POWER_SUPPLY_EXT_PROP_OVERHEAT_NOTIFY!\n", __func__); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); break; case POWER_SUPPLY_EXT_PROP_HV_DISABLE: #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) #if defined(CONFIG_PD_CHARGER_HV_DISABLE) pr_info("None PD wired charging mode is %s\n", (val->intval == CH_MODE_AFC_DISABLE_VAL ? "Disabled" : "Enabled")); if (val->intval == CH_MODE_AFC_DISABLE_VAL) { sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE); } else { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE); } #else pr_info("HV wired charging mode is %s\n", (val->intval == CH_MODE_AFC_DISABLE_VAL ? "Disabled" : "Enabled")); if (val->intval == CH_MODE_AFC_DISABLE_VAL) { sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_HV_DISABLE, SEC_BAT_CURRENT_EVENT_HV_DISABLE); } else { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_HV_DISABLE); } if (is_pd_wire_type(battery->cable_type)) { battery->update_pd_list = true; pr_info("%s: update pd list\n", __func__); #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (is_pd_apdo_wire_type(battery->cable_type)) psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); #endif sec_vote_refresh(battery->iv_vote); } #endif #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) if ((battery->cable_type == SEC_BATTERY_CABLE_TA) && (battery->sink_status.rp_currentlvl == RP_CURRENT_LEVEL3)) { sec_bat_set_rp_current(battery, battery->cable_type); sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->charging_current[battery->cable_type].fast_charging_current); sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->charging_current[battery->cable_type].input_current_limit); } #endif #endif break; case POWER_SUPPLY_EXT_PROP_WC_CONTROL: #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) pr_info("%s: Recover MFC IC (wc_enable: %d)\n", __func__, battery->wc_enable); mutex_lock(&battery->wclock); if (battery->wc_enable) { sec_bat_set_mfc_off(battery, WPC_EN_CHARGING, false); msleep(500); sec_bat_set_mfc_on(battery, WPC_EN_CHARGING); } mutex_unlock(&battery->wclock); #endif break; case POWER_SUPPLY_EXT_PROP_WDT_STATUS: if (val->intval) sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_WDT_EXPIRED, SEC_BAT_CURRENT_EVENT_WDT_EXPIRED); break; case POWER_SUPPLY_EXT_PROP_CURRENT_EVENT: if (!(battery->current_event & val->intval)) { pr_info("%s: set new current_event %d\n", __func__, val->intval); #if defined(CONFIG_BATTERY_CISD) if (val->intval == SEC_BAT_CURRENT_EVENT_DC_ERR) battery->cisd.event_data[EVENT_DC_ERR]++; #endif sec_bat_set_current_event(battery, val->intval, val->intval); } break; case POWER_SUPPLY_EXT_PROP_CURRENT_EVENT_CLEAR: pr_info("%s: new current_event clear %d\n", __func__, val->intval); sec_bat_set_current_event(battery, 0, val->intval); break; #if defined(CONFIG_WIRELESS_TX_MODE) case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_AVG_CURR: break; case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE: sec_wireless_set_tx_enable(battery, val->intval); break; #endif case POWER_SUPPLY_EXT_PROP_SRCCAP: pr_info("%s: set init_src_cap(%d->%d)", __func__, battery->init_src_cap, val->intval); if (val->intval == 0) sec_vote(battery->chgen_vote, VOTER_SRCCAP_TRANSIT, true, SEC_BAT_CHG_MODE_BUCK_OFF); else battery->init_src_cap = true; break; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) case POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT: if (battery->ta_alert_wa) { pr_info("@TA_ALERT: %s: TA OCP DETECT\n", __func__); #if defined(CONFIG_BATTERY_CISD) battery->cisd.event_data[EVENT_TA_OCP_DET]++; if (battery->ta_alert_mode == OCP_NONE) battery->cisd.event_data[EVENT_TA_OCP_ON]++; #endif battery->ta_alert_mode = OCP_DETECT; sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_25W_OCP, SEC_BAT_CURRENT_EVENT_25W_OCP); store_battery_log( "TA OCP:%d%%,%dmV,%s,ct(%d,%d,%d,%d),cev(0x%x),mev(0x%x)", battery->capacity, battery->voltage_now, sb_get_bst_str(battery->status), battery->cable_type, battery->wire_status, battery->muic_cable_type, battery->pd_usb_attached, battery->current_event, battery->misc_event ); } break; case POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM: if (is_pd_apdo_wire_type(battery->cable_type)) { char direct_charging_source_status[2] = {0, }; pr_info("@SEND_UVDM: Request Change Charging Source : %s\n", val->intval == 0 ? "Switch Charger" : "Direct Charger" ); direct_charging_source_status[0] = SEC_SEND_UVDM; direct_charging_source_status[1] = val->intval; sec_bat_set_current_event(battery, val->intval == 0 ? SEC_BAT_CURRENT_EVENT_SEND_UVDM : 0, SEC_BAT_CURRENT_EVENT_SEND_UVDM); value.strval = direct_charging_source_status; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, value); } break; case POWER_SUPPLY_EXT_PROP_DIRECT_FIXED_PDO: if (!is_slate_mode(battery)) { sec_vote(battery->iv_vote, VOTER_DC_MODE, true, val->intval); if (val->intval == SEC_INPUT_VOLTAGE_APDO) { sec_vote(battery->iv_vote, VOTER_AICL, false, 0); sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_HV_BY_AICL); } } break; #endif #if IS_ENABLED(CONFIG_DUAL_BATTERY) case POWER_SUPPLY_EXT_PROP_FASTCHG_LIMIT_CURRENT: if (is_wireless_type(battery->cable_type)) sec_bat_set_limiter_current(battery); break; #endif case POWER_SUPPLY_EXT_PROP_WPC_EN: sec_bat_set_current_event(battery, val->intval ? 0 : SEC_BAT_CURRENT_EVENT_WPC_EN, SEC_BAT_CURRENT_EVENT_WPC_EN); break; case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: value.intval = val->intval; pr_info("%s: WCIN-UNO %s\n", __func__, value.intval > 0 ? "on" : "off"); psy_do_property("otg", set, POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL, value); break; #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) case POWER_SUPPLY_EXT_PROP_BATT_F_MODE: battery->batt_f_mode = val->intval; if (battery->batt_f_mode == OB_MODE) { sec_bat_set_facmode(true); battery->factory_mode = true; } else if (((battery->cable_type == SEC_BATTERY_CABLE_NONE) && (battery->wire_status != SEC_BATTERY_CABLE_NONE)) && (battery->batt_f_mode == IB_MODE)) { battery->charging_mode = SEC_BATTERY_CHARGING_1ST; sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_CHARGING); battery->cable_type = battery->wire_status; sec_bat_set_facmode(false); battery->factory_mode = false; } else { sec_bat_set_facmode(false); battery->factory_mode = false; } value.intval = battery->batt_f_mode; psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_EXT_PROP_BATT_F_MODE, value); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); break; #endif case POWER_SUPPLY_EXT_PROP_INPUT_VOLTAGE_REGULATION: case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONTROL: break; case POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW: break; #if defined(CONFIG_UPDATE_BATTERY_DATA) case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: sec_bat_parse_dt(battery->dev, battery); break; #endif case POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE: battery->mfc_fw_update = val->intval; if (!battery->mfc_fw_update) { pr_info("%s: fw update done: (5V -> 9V).\n", __func__); sec_vote(battery->chgen_vote, VOTER_FW, false, 0); sec_vote(battery->iv_vote, VOTER_FW, false, 0); } break; case POWER_SUPPLY_EXT_PROP_THERMAL_ZONE: pr_info("%s : bat_thm_info.check_type set to %s\n", __func__, val->intval ? "NONE" : "TEMP"); if (val->intval) { battery->skip_swelling = true; /* restore thermal_zone to NORMAL */ battery->pdata->bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_NONE; battery->pdata->lrp_temp_check_type = SEC_BATTERY_TEMP_CHECK_NONE; sec_vote(battery->iv_vote, VOTER_LRP_TEMP, false, 0); sec_vote(battery->fcc_vote, VOTER_LRP_TEMP, false, 0); sec_vote(battery->input_vote, VOTER_LRP_TEMP, false, 0); battery->lrp_limit = false; battery->lrp_step = LRP_NONE; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; #endif } else { battery->skip_swelling = false; battery->pdata->bat_thm_info.check_type = SEC_BATTERY_TEMP_CHECK_TEMP; battery->pdata->lrp_temp_check_type = SEC_BATTERY_TEMP_CHECK_TEMP; /* Check VOTER_SIOP to set up current based on cable_type */ __pm_stay_awake(battery->siop_level_ws); queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); #if !defined(CONFIG_SEC_FACTORY) sec_bat_check_lrp_temp(battery, battery->cable_type, battery->wire_status, battery->siop_level, battery->lcd_status); #endif } break; case POWER_SUPPLY_EXT_PROP_TEMP_CHECK_TYPE: break; case POWER_SUPPLY_EXT_PROP_SUB_TEMP: break; case POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH: pr_info("%s : wpc vout strength(%d)\n", __func__, val->intval); sec_bat_set_misc_event(battery, ((val->intval <= 0) ? BATT_MISC_EVENT_WIRELESS_MISALIGN : 0), BATT_MISC_EVENT_WIRELESS_MISALIGN); break; case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: value.intval = val->intval; pr_info("%s: d2d reverse boost : %d\n", __func__, val->intval); if (val->intval) { #if defined(CONFIG_BATTERY_CISD) if (!battery->d2d_check) { battery->cisd.event_data[EVENT_D2D]++; battery->d2d_check = true; } #endif sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, true, SEC_BAT_CHG_MODE_BUCK_OFF); } psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); if (!val->intval) { #if defined(CONFIG_BATTERY_CISD) battery->d2d_check = false; #endif sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, false, 0); } break; case POWER_SUPPLY_EXT_PROP_FLASH_STATE: /* check only for MTK */ battery->flash_state = val->intval; if (val->intval) { pr_info("%s: Flash on: (9V -> 5V).\n", __func__); sec_vote(battery->iv_vote, VOTER_FLASH, true, SEC_INPUT_VOLTAGE_5V); } else { pr_info("%s: Flash off: (5V -> 9V).\n", __func__); sec_vote(battery->iv_vote, VOTER_FLASH, false, 0); } #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (is_pd_apdo_wire_type(battery->cable_type)) psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); #endif break; #if defined(CONFIG_MTK_CHARGER) case POWER_SUPPLY_EXT_PROP_MTK_FG_INIT: /* check only for MTK */ battery->mtk_fg_init = val->intval; pr_info("%s: mtk_fg_init (%d)\n", __func__, battery->mtk_fg_init); #if IS_ENABLED(CONFIG_DIRECT_CHARGING) if (is_pd_apdo_wire_type(battery->cable_type)) psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_REFRESH_CHARGING_SOURCE, value); #endif break; #endif case POWER_SUPPLY_EXT_PROP_USB_BOOTCOMPLETE: battery->usb_bootcomplete = val->intval; pr_info("%s: usb_bootcomplete (%d)\n", __func__, battery->usb_bootcomplete); break; case POWER_SUPPLY_EXT_PROP_MISC_EVENT: if (!(battery->misc_event & val->intval)) { pr_info("%s: set new misc_event %d\n", __func__, val->intval); sec_bat_set_misc_event(battery, val->intval, val->intval); } break; case POWER_SUPPLY_EXT_PROP_MISC_EVENT_CLEAR: pr_info("%s: new misc_event clear %d\n", __func__, val->intval); sec_bat_set_misc_event(battery, 0, val->intval); break; case POWER_SUPPLY_EXT_PROP_MST_EN: pr_info("%s: POWER_SUPPLY_EXT_PROP_MST_EN(%d)\n", __func__, val->intval); battery->mst_en = val->intval; if (val->intval) sec_vote(battery->iv_vote, VOTER_MST, true, SEC_INPUT_VOLTAGE_5V); else sec_vote(battery->iv_vote, VOTER_MST, false, 0); break; case POWER_SUPPLY_EXT_PROP_ABNORMAL_SRCCAP: store_battery_log( "ABNORMAL_SRCCAP:%d%%,%dmV,%s,PDO(0x%X)", battery->capacity, battery->voltage_now, sb_get_bst_str(battery->status), val->intval ); break; default: return -EINVAL; } break; default: return -EINVAL; } return 0; } static int sec_bat_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); union power_supply_propval value = {0, }; enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; switch ((int)psp) { case POWER_SUPPLY_PROP_STATUS: /* need to update for DPM UI */ if (battery->misc_event & BATT_MISC_EVENT_DIRECT_POWER_MODE) { val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; return 0; } if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) { val->intval = POWER_SUPPLY_STATUS_DISCHARGING; } else { if ((battery->pdata->cable_check_type & SEC_BATTERY_CABLE_CHECK_NOUSBCHARGE) && !sec_bat_get_lpmode()) { switch (battery->cable_type) { case SEC_BATTERY_CABLE_USB: case SEC_BATTERY_CABLE_USB_CDP: val->intval = POWER_SUPPLY_STATUS_DISCHARGING; return 0; } } #if defined(CONFIG_STORE_MODE) if (battery->store_mode && !sec_bat_get_lpmode() && !is_nocharge_type(battery->cable_type) && battery->status == POWER_SUPPLY_STATUS_DISCHARGING) { val->intval = POWER_SUPPLY_STATUS_CHARGING; } else #endif val->intval = battery->status; } break; case POWER_SUPPLY_PROP_CHARGE_TYPE: { unsigned int input_current = battery->pdata->charging_current[battery->cable_type].input_current_limit; if (is_nocharge_type(battery->cable_type)) { val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; } else if (is_hv_wire_type(battery->cable_type) || is_pd_wire_type(battery->cable_type) || is_wireless_type(battery->cable_type)) { val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; } else if (!battery->usb_bootcomplete && !lpcharge && battery->pdata->slowcharging_usb_bootcomplete) { val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; } else if (input_current <= SLOW_CHARGING_CURRENT_STANDARD || battery->usb_slow_chg) { val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; } else { psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_PROP_CHARGE_TYPE, value); if (value.intval == SEC_BATTERY_CABLE_UNKNOWN) /* * if error in CHARGE_TYPE of charger * set CHARGE_TYPE as NONE */ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; else val->intval = value.intval; } } break; case POWER_SUPPLY_PROP_HEALTH: if (sec_bat_get_lpmode() && (battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE || battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE || battery->health == POWER_SUPPLY_EXT_HEALTH_DC_ERR)) { val->intval = POWER_SUPPLY_HEALTH_GOOD; } else if (battery->health >= POWER_SUPPLY_EXT_HEALTH_MIN) { if (battery->health == POWER_SUPPLY_EXT_HEALTH_OVERHEATLIMIT) val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; else val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; } else { val->intval = battery->health; } break; case POWER_SUPPLY_PROP_PRESENT: val->intval = battery->present; /* BATT_MISC_EVENT_DIRECT_POWER_MODE event is activated only in the DPM application model. */ /* need to update for SSRM cooldown mode */ if (battery->misc_event & BATT_MISC_EVENT_DIRECT_POWER_MODE) val->intval = 0; break; case POWER_SUPPLY_PROP_ONLINE: /* SEC_BATTERY_CABLE_SILENT_TYPE, defines with PMS team for avoid charger connection sound */ if (check_silent_type(battery->muic_cable_type)) val->intval = SEC_BATTERY_CABLE_SILENT_TYPE; #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) else if (is_hv_wireless_type(battery->cable_type) || (battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_HV) || (battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20)) { if (sec_bat_hv_wc_normal_mode_check(battery)) val->intval = SEC_BATTERY_CABLE_WIRELESS; else val->intval = SEC_BATTERY_CABLE_HV_WIRELESS_ETX; } else if (battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_PACK || battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_STAND || battery->cable_type == SEC_BATTERY_CABLE_PMA_WIRELESS || battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_VEHICLE || battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_TX || battery->cable_type == SEC_BATTERY_CABLE_HV_WIRELESS_20 || battery->cable_type == SEC_BATTERY_CABLE_WIRELESS_FAKE) val->intval = SEC_BATTERY_CABLE_WIRELESS; #endif else val->intval = battery->cable_type; break; case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = battery->pdata->technology; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: #ifdef CONFIG_SEC_FACTORY value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); battery->voltage_now = value.intval; dev_err(battery->dev, "%s: voltage now(%d)\n", __func__, battery->voltage_now); #endif /* voltage value should be in uV */ val->intval = battery->voltage_now * 1000; break; case POWER_SUPPLY_PROP_VOLTAGE_AVG: #ifdef CONFIG_SEC_FACTORY value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_AVG, value); battery->voltage_avg = value.intval; dev_err(battery->dev, "%s: voltage avg(%d)\n", __func__, battery->voltage_avg); #endif /* voltage value should be in uV */ val->intval = battery->voltage_avg * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW: value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CURRENT_NOW, value); val->intval = value.intval; break; case POWER_SUPPLY_PROP_CURRENT_AVG: value.intval = SEC_BATTERY_CURRENT_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CURRENT_AVG, value); val->intval = value.intval; break; case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: case POWER_SUPPLY_PROP_CHARGE_FULL: val->intval = battery->pdata->battery_full_capacity * 1000; break; /* charging mode (differ from power supply) */ case POWER_SUPPLY_PROP_CHARGE_NOW: val->intval = battery->charging_mode; break; case POWER_SUPPLY_PROP_CAPACITY: if (battery->pdata->fake_capacity) { val->intval = 70; pr_info("%s : capacity(%d)\n", __func__, val->intval); } else { #if defined(CONFIG_ENG_BATTERY_CONCEPT) if (battery->status == POWER_SUPPLY_STATUS_FULL) { if (battery->eng_not_full_status) val->intval = battery->capacity; else val->intval = 100; } else { val->intval = battery->capacity; } #else if (battery->status == POWER_SUPPLY_STATUS_FULL) val->intval = 100; else val->intval = battery->capacity; #endif } break; case POWER_SUPPLY_PROP_TEMP: val->intval = battery->temperature; break; case POWER_SUPPLY_PROP_TEMP_AMBIENT: val->intval = battery->temper_amb; break; #if IS_ENABLED(CONFIG_FUELGAUGE_MAX77705) case POWER_SUPPLY_PROP_POWER_NOW: value.intval = SEC_BATTERY_ISYS_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); val->intval = value.intval; break; case POWER_SUPPLY_PROP_POWER_AVG: value.intval = SEC_BATTERY_ISYS_AVG_MA; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_MEASURE_SYS, value); val->intval = value.intval; break; #endif case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: val->intval = ttf_display(battery->capacity, battery->status, battery->thermal_zone, battery->ttf_d->timetofull); break; case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: if (battery->current_event & SEC_BAT_CURRENT_EVENT_SWELLING_MODE) val->intval = 1; else val->intval = 0; break; case POWER_SUPPLY_PROP_CHARGE_COUNTER: val->intval = battery->charge_counter; break; case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { case POWER_SUPPLY_EXT_PROP_CHARGE_POWER: val->intval = battery->charge_power; break; case POWER_SUPPLY_EXT_PROP_CURRENT_EVENT: val->intval = battery->current_event; break; #if defined(CONFIG_WIRELESS_TX_MODE) case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_AVG_CURR: val->intval = battery->tx_avg_curr; break; case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ENABLE: val->intval = battery->wc_tx_enable; break; #endif #if IS_ENABLED(CONFIG_DIRECT_CHARGING) case POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE: val->intval = battery->pd_list.now_isApdo; break; case POWER_SUPPLY_EXT_PROP_DIRECT_HAS_APDO: val->intval = battery->sink_status.has_apdo; break; case POWER_SUPPLY_EXT_PROP_PAD_VOLT_CTRL: if (battery->pdata->wpc_vout_ctrl_lcd_on) val->intval = battery->lcd_status; else val->intval = false; break; case POWER_SUPPLY_EXT_PROP_DIRECT_TA_ALERT: if (battery->ta_alert_wa) { val->intval = battery->ta_alert_mode; } else val->intval = OCP_NONE; break; case POWER_SUPPLY_EXT_PROP_DIRECT_SEND_UVDM: break; #if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_DIRECT_CHARGING) case POWER_SUPPLY_EXT_PROP_DIRECT_VBAT_CHECK: pr_info("%s : mc:%dmV, sc:%dmV\n", __func__, battery->voltage_pack_main, battery->voltage_pack_sub); if ((battery->voltage_pack_main >= battery->pdata->sc_vbat_thresh) || (battery->voltage_pack_sub >= battery->pdata->sc_vbat_thresh)) val->intval = 1; else val->intval = 0; break; #endif #endif case POWER_SUPPLY_EXT_PROP_HV_PDO: val->intval = battery->hv_pdo; break; case POWER_SUPPLY_EXT_PROP_CHARGE_UNO_CONTROL: break; case POWER_SUPPLY_EXT_PROP_CHARGE_COUNTER_SHADOW: val->intval = battery->wire_status; break; case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: break; case POWER_SUPPLY_EXT_PROP_HEALTH: val->intval = battery->health; break; case POWER_SUPPLY_EXT_PROP_MFC_FW_UPDATE: val->intval = battery->mfc_fw_update; break; case POWER_SUPPLY_EXT_PROP_THERMAL_ZONE: val->intval = battery->thermal_zone; break; case POWER_SUPPLY_EXT_PROP_TEMP_CHECK_TYPE: switch (val->intval) { case THM_INFO_BAT: val->intval = battery->pdata->bat_thm_info.check_type; break; case THM_INFO_USB: val->intval = battery->pdata->usb_thm_info.check_type; break; case THM_INFO_CHG: val->intval = battery->pdata->chg_thm_info.check_type; break; case THM_INFO_WPC: val->intval = battery->pdata->wpc_thm_info.check_type; break; case THM_INFO_SUB_BAT: val->intval = battery->pdata->sub_bat_thm_info.check_type; break; case THM_INFO_BLK: val->intval = battery->pdata->blk_thm_info.check_type; break; case THM_INFO_DCHG: val->intval = battery->pdata->dchg_thm_info.check_type; break; default: val->intval = SEC_BATTERY_TEMP_CHECK_NONE; break; } break; case POWER_SUPPLY_EXT_PROP_SUB_TEMP: val->intval = battery->sub_bat_temp; break; case POWER_SUPPLY_EXT_PROP_MIX_LIMIT: val->intval = battery->mix_limit; break; case POWER_SUPPLY_EXT_PROP_WPC_FREQ_STRENGTH: break; case POWER_SUPPLY_EXT_PROP_LCD_FLICKER: val->intval = (battery->lcd_status && battery->wpc_vout_ctrl_mode) ? 1 : 0; break; case POWER_SUPPLY_EXT_PROP_FLASH_STATE: /* check only for MTK */ val->intval = battery->flash_state; break; #if defined(CONFIG_MTK_CHARGER) case POWER_SUPPLY_EXT_PROP_MTK_FG_INIT: /* check only for MTK */ val->intval = battery->mtk_fg_init; break; #endif case POWER_SUPPLY_EXT_PROP_SRCCAP: val->intval = battery->init_src_cap; break; #if defined(CONFIG_MTK_CHARGER) case POWER_SUPPLY_EXT_PROP_RP_LEVEL: val->intval = battery->sink_status.rp_currentlvl; break; #endif case POWER_SUPPLY_EXT_PROP_LRP_CHG_SRC: val->intval = battery->lrp_chg_src; break; case POWER_SUPPLY_EXT_PROP_MISC_EVENT: val->intval = battery->misc_event; break; case POWER_SUPPLY_EXT_PROP_MST_EN: val->intval = battery->mst_en; break; case POWER_SUPPLY_EXT_PROP_DIRECT_DONE: #if IS_ENABLED(CONFIG_DIRECT_CHARGING) psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_DIRECT_DONE, value); val->intval = value.intval; #else val->intval = 0; #endif break; default: return -EINVAL; } break; default: return -EINVAL; } return 0; } static int sec_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); if (psp != POWER_SUPPLY_PROP_ONLINE) return -EINVAL; if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) { val->intval = 0; return 0; } /* Set enable=1 only if the USB charger is connected */ switch (battery->wire_status) { case SEC_BATTERY_CABLE_USB: case SEC_BATTERY_CABLE_USB_CDP: val->intval = 1; break; case SEC_BATTERY_CABLE_PDIC: val->intval = (battery->pd_usb_attached) ? 1:0; break; default: val->intval = 0; break; } if (is_slate_mode(battery)) val->intval = 0; return 0; } static int sec_ac_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; switch ((int)psp) { case POWER_SUPPLY_PROP_ONLINE: if ((battery->health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) || (battery->health == POWER_SUPPLY_EXT_HEALTH_UNDERVOLTAGE)) { val->intval = 0; return 0; } /* Set enable=1 only if the AC charger is connected */ switch (battery->cable_type) { case SEC_BATTERY_CABLE_TA: case SEC_BATTERY_CABLE_UARTOFF: case SEC_BATTERY_CABLE_LAN_HUB: case SEC_BATTERY_CABLE_UNKNOWN: case SEC_BATTERY_CABLE_PREPARE_TA: case SEC_BATTERY_CABLE_9V_ERR: case SEC_BATTERY_CABLE_9V_UNKNOWN: case SEC_BATTERY_CABLE_9V_TA: case SEC_BATTERY_CABLE_12V_TA: case SEC_BATTERY_CABLE_HMT_CONNECTED: case SEC_BATTERY_CABLE_HMT_CHARGE: case SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT: case SEC_BATTERY_CABLE_QC20: case SEC_BATTERY_CABLE_QC30: case SEC_BATTERY_CABLE_TIMEOUT: case SEC_BATTERY_CABLE_SMART_OTG: case SEC_BATTERY_CABLE_SMART_NOTG: case SEC_BATTERY_CABLE_POGO: case SEC_BATTERY_CABLE_POGO_9V: case SEC_BATTERY_CABLE_PDIC_APDO: val->intval = 1; break; case SEC_BATTERY_CABLE_PDIC: val->intval = (battery->pd_usb_attached) ? 0:1; break; default: val->intval = 0; break; } if (sec_bat_get_lpmode() && (battery->misc_event & BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE)) val->intval = 1; break; case POWER_SUPPLY_PROP_TEMP: val->intval = battery->chg_temp; break; case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { case POWER_SUPPLY_EXT_PROP_WATER_DETECT: if (battery->misc_event & (BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE | BATT_MISC_EVENT_WATER_HICCUP_TYPE)) { val->intval = 1; pr_info("%s: Water Detect\n", __func__); } else { val->intval = 0; } break; default: return -EINVAL; } break; default: return -EINVAL; } return 0; } static int sec_wireless_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; switch ((int)psp) { case POWER_SUPPLY_PROP_ONLINE: val->intval = is_wireless_fake_type(battery->cable_type) ? 1 : 0; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = (battery->pdata->wireless_charger_name) ? 1 : 0; break; case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { case POWER_SUPPLY_EXT_PROP_POWER_DESIGN: #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) if (battery->cable_type == SEC_BATTERY_CABLE_PREPARE_WIRELESS_20) val->intval = battery->wc20_power_class; else #endif val->intval = 0; break; case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR: val->intval = battery->tx_event; break; default: return -EINVAL; } break; default: return -EINVAL; } return 0; } static int sec_wireless_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); enum power_supply_ext_property ext_psp = (enum power_supply_ext_property) psp; switch ((int)psp) { case POWER_SUPPLY_PROP_ONLINE: #if defined(CONFIG_BATTERY_CISD) if (val->intval != SEC_BATTERY_CABLE_NONE && battery->wc_status == SEC_BATTERY_CABLE_NONE) { battery->cisd.data[CISD_DATA_WIRELESS_COUNT]++; battery->cisd.data[CISD_DATA_WIRELESS_COUNT_PER_DAY]++; } #endif pr_info("%s : wireless_type(%s)\n", __func__, sb_get_ct_str(val->intval)); /* Clear the FOD , AUTH State */ sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_WIRELESS_FOD); battery->wc_status = val->intval; if ((battery->ext_event & BATT_EXT_EVENT_CALL) && (battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_PACK || battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_HV_PACK || battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_TX)) { battery->wc_rx_phm_mode = true; } if (battery->wc_status == SEC_BATTERY_CABLE_NONE) { battery->wpc_vout_level = WIRELESS_VOUT_10V; battery->wpc_max_vout_level = WIRELESS_VOUT_12_5V; battery->auto_mode = false; sec_bat_set_misc_event(battery, 0, (BATT_MISC_EVENT_WIRELESS_DET_LEVEL | /* clear wpc_det level status */ BATT_MISC_EVENT_WIRELESS_AUTH_START | BATT_MISC_EVENT_WIRELESS_AUTH_RECVED | BATT_MISC_EVENT_WIRELESS_AUTH_FAIL | BATT_MISC_EVENT_WIRELESS_AUTH_PASS | BATT_MISC_EVENT_WC_JIG_PAD)); } else if (battery->wc_status != SEC_BATTERY_CABLE_WIRELESS_FAKE) { sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_DET_LEVEL, /* set wpc_det level status */ BATT_MISC_EVENT_WIRELESS_DET_LEVEL); if (battery->wc_status == SEC_BATTERY_CABLE_HV_WIRELESS_20) { sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_PASS, BATT_MISC_EVENT_WIRELESS_AUTH_PASS); #if defined(CONFIG_BATTERY_CISD) if (battery->wc_status == SEC_BATTERY_CABLE_HV_WIRELESS_20) battery->cisd.cable_data[CISD_CABLE_HV_WC_20]++; #endif } } __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); if (battery->wc_status == SEC_BATTERY_CABLE_NONE || battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_PACK || battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_HV_PACK || battery->wc_status == SEC_BATTERY_CABLE_WIRELESS_VEHICLE) { sec_bat_set_misc_event(battery, (battery->wc_status == SEC_BATTERY_CABLE_NONE ? 0 : BATT_MISC_EVENT_WIRELESS_BACKPACK_TYPE), BATT_MISC_EVENT_WIRELESS_BACKPACK_TYPE); } break; case POWER_SUPPLY_PROP_AUTHENTIC: #if defined(CONFIG_BATTERY_CISD) pr_info("%s : tx_type(0x%x)\n", __func__, val->intval); count_cisd_pad_data(&battery->cisd, val->intval); #endif break; case POWER_SUPPLY_PROP_CURRENT_MAX: sec_vote(battery->input_vote, VOTER_AICL, false, 0); pr_info("%s: reset aicl\n", __func__); break; case POWER_SUPPLY_EXT_PROP_MIN ... POWER_SUPPLY_EXT_PROP_MAX: switch (ext_psp) { #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) case POWER_SUPPLY_EXT_PROP_WIRELESS_ERR: if (is_wireless_type(battery->cable_type)) sec_bat_set_misc_event(battery, val->intval ? BATT_MISC_EVENT_WIRELESS_FOD : 0, BATT_MISC_EVENT_WIRELESS_FOD); break; case POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ERR: if (val->intval & BATT_TX_EVENT_WIRELESS_TX_MISALIGN) { sec_bat_handle_tx_misalign(battery, true); } else if (val->intval & BATT_TX_EVENT_WIRELESS_TX_OCP) { sec_bat_handle_tx_ocp(battery, true); } else if (val->intval & BATT_TX_EVENT_WIRELESS_TX_AC_MISSING) { if (battery->wc_tx_enable) sec_wireless_set_tx_enable(battery, false); battery->tx_retry_case |= SEC_BAT_TX_RETRY_AC_MISSING; /* clear tx all event */ sec_bat_set_tx_event(battery, 0, BATT_TX_EVENT_WIRELESS_ALL_MASK); sec_bat_set_tx_event(battery, BATT_TX_EVENT_WIRELESS_TX_RETRY, BATT_TX_EVENT_WIRELESS_TX_RETRY); } else { sec_wireless_set_tx_enable(battery, false); sec_bat_set_tx_event(battery, val->intval, val->intval); } break; case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_CONNECTED: sec_bat_set_tx_event(battery, val->intval ? BATT_TX_EVENT_WIRELESS_RX_CONNECT : 0, BATT_TX_EVENT_WIRELESS_RX_CONNECT); battery->wc_rx_connected = val->intval; battery->wc_tx_phm_mode = false; battery->prev_tx_phm_mode = false; if (!val->intval) { battery->wc_rx_type = NO_DEV; battery->tx_switch_mode = TX_SWITCH_MODE_OFF; battery->tx_switch_mode_change = false; battery->tx_switch_start_soc = 0; sec_bat_run_wpc_tx_work(battery, 0); } break; case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_TYPE: battery->wc_rx_type = val->intval; #if defined(CONFIG_BATTERY_CISD) if (battery->wc_rx_type) { if (battery->wc_rx_type == SS_BUDS) { battery->cisd.tx_data[SS_PHONE]--; } battery->cisd.tx_data[battery->wc_rx_type]++; } #endif pr_info("@Tx_Mode %s : RX_TYPE=%d\n", __func__, battery->wc_rx_type); sec_bat_run_wpc_tx_work(battery, 0); break; case POWER_SUPPLY_EXT_PROP_WIRELESS_AUTH_ADT_STATUS: if (val->intval == WIRELESS_AUTH_START) sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_START, BATT_MISC_EVENT_WIRELESS_AUTH_START); else if (val->intval == WIRELESS_AUTH_RECEIVED) sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_RECVED, BATT_MISC_EVENT_WIRELESS_AUTH_RECVED); else if (val->intval == WIRELESS_AUTH_SENT) /* need to be clear this value when data is sent */ sec_bat_set_misc_event(battery, 0, BATT_MISC_EVENT_WIRELESS_AUTH_START | BATT_MISC_EVENT_WIRELESS_AUTH_RECVED); else if (val->intval == WIRELESS_AUTH_FAIL) sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WIRELESS_AUTH_FAIL, BATT_MISC_EVENT_WIRELESS_AUTH_FAIL); break; case POWER_SUPPLY_EXT_PROP_CALL_EVENT: if (val->intval == 1) { pr_info("%s : PHM enabled\n", __func__); battery->wc_rx_phm_mode = true; } break; case POWER_SUPPLY_EXT_PROP_GEAR_PHM_EVENT: battery->wc_tx_phm_mode = val->intval; pr_info("@Tx_Mode %s : tx phm status(%d)\n", __func__, val->intval); sec_bat_run_wpc_tx_work(battery, 0); break; case POWER_SUPPLY_EXT_PROP_WIRELESS_RX_POWER: #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) pr_info("%s : rx power %d\n", __func__, val->intval); battery->wc20_rx_power = val->intval; battery->wc20_info_idx = get_wc20_info_idx(battery->pdata->wireless_power_info, battery->wc20_info_len, battery->wpc_max_vout_level, battery->wc20_rx_power); battery->wc20_vout = battery->pdata->wireless_power_info[battery->wc20_info_idx].vout; __pm_stay_awake(battery->wc20_current_ws); queue_delayed_work(battery->monitor_wqueue, &battery->wc20_current_work, msecs_to_jiffies(0)); #endif break; case POWER_SUPPLY_EXT_PROP_WIRELESS_MAX_VOUT: pr_info("%s : max vout %s\n", __func__, sb_vout_ctr_mode_str(val->intval)); battery->wpc_max_vout_level = val->intval; break; case POWER_SUPPLY_EXT_PROP_CHARGE_OTG_CONTROL: sec_wireless_otg_control(battery, val->intval); break; case POWER_SUPPLY_EXT_PROP_WIRELESS_ABNORMAL_PAD: sec_bat_set_misc_event(battery, val->intval ? BATT_MISC_EVENT_ABNORMAL_PAD : 0, BATT_MISC_EVENT_ABNORMAL_PAD); break; case POWER_SUPPLY_EXT_PROP_WIRELESS_JIG_PAD: if (sec_bat_get_lpmode()) { sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WC_JIG_PAD, BATT_MISC_EVENT_WC_JIG_PAD); } break; #endif default: return -EINVAL; } break; default: return -EINVAL; } return 0; } static void sec_wireless_test_print_sgf_data(const char *buf, int count) { char temp_buf[1024] = {0,}; int i, size = 0; snprintf(temp_buf, sizeof(temp_buf), "0x%x", buf[0]); size = sizeof(temp_buf) - strlen(temp_buf); for (i = 1; i < count; i++) { snprintf(temp_buf+strlen(temp_buf), size, " 0x%x", buf[i]); size = sizeof(temp_buf) - strlen(temp_buf); } pr_info("%s: %s\n", __func__, temp_buf); } static ssize_t sec_wireless_sgf_show_attr(struct device *dev, struct device_attribute *attr, char *buf) { /* struct power_supply *psy = dev_get_drvdata(dev); struct sec_battery_info *battery = power_supply_get_drvdata(psy); */ return -EINVAL; } static ssize_t sec_wireless_sgf_store_attr(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct power_supply *psy = dev_get_drvdata(dev); struct sec_battery_info *battery = power_supply_get_drvdata(psy); union power_supply_propval value = {0, }; if (count < 4) { pr_err("%s: invalid data\n", __func__); return -EINVAL; } psy_do_property(battery->pdata->wireless_charger_name, get, POWER_SUPPLY_EXT_PROP_WIRELESS_TX_ID, value); pr_info("%s!!!(cable_type = %d, tx_id = %d, count = %ld)\n", __func__, battery->cable_type, value.intval, count); sec_wireless_test_print_sgf_data(buf, (int)count); if (is_wireless_type(battery->cable_type) && value.intval == 0xEF) { value.strval = buf; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_WIRELESS_SGF, value); } return count; } static DEVICE_ATTR(sgf, 0664, sec_wireless_sgf_show_attr, sec_wireless_sgf_store_attr); static int sec_pogo_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { #if defined(CONFIG_USE_POGO) struct sec_battery_info *battery = power_supply_get_drvdata(psy); #endif if (psp != POWER_SUPPLY_PROP_ONLINE) return -EINVAL; val->intval = 0; #if defined(CONFIG_USE_POGO) val->intval = battery->pogo_status; pr_info("%s: POGO online : %d\n", __func__, val->intval); #endif return 0; } static int sec_pogo_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { #if defined(CONFIG_USE_POGO) struct sec_battery_info *battery = power_supply_get_drvdata(psy); #endif switch ((int)psp) { case POWER_SUPPLY_PROP_ONLINE: #if defined(CONFIG_USE_POGO) battery->pogo_status = (val->intval) ? 1 : 0; battery->pogo_9v = (val->intval > 1) ? true : false; __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); pr_info("%s: pogo_status : %d\n", __func__, battery->pogo_status); #endif break; default: return -EINVAL; } return 0; } static int sec_otg_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); union power_supply_propval value = {0,}; int ret = 0; value.intval = val->intval; ret = psy_do_property(battery->pdata->otg_name, get, psp, value); val->intval = value.intval; return ret; } static int sec_otg_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { struct sec_battery_info *battery = power_supply_get_drvdata(psy); union power_supply_propval value = {0,}; int ret = 0; value.intval = val->intval; #if defined(CONFIG_BATTERY_CISD) if (value.intval && !battery->otg_check) { battery->cisd.event_data[EVENT_OTG]++; battery->otg_check = true; } else if (!value.intval) battery->otg_check = false; #endif ret = psy_do_property(battery->pdata->otg_name, set, psp, value); return ret; } static void sec_otg_external_power_changed(struct power_supply *psy) { power_supply_changed(psy); } #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) || IS_ENABLED(CONFIG_MUIC_NOTIFIER) __visible_for_testing int sec_bat_cable_check(struct sec_battery_info *battery, muic_attached_dev_t attached_dev) { int current_cable_type = -1; union power_supply_propval val = {0, }; pr_info("[%s]ATTACHED(%d)\n", __func__, attached_dev); switch (attached_dev) { case ATTACHED_DEV_JIG_UART_OFF_MUIC: case ATTACHED_DEV_JIG_UART_ON_MUIC: battery->is_jig_on = true; #if defined(CONFIG_BATTERY_CISD) battery->skip_cisd = true; #endif current_cable_type = SEC_BATTERY_CABLE_NONE; break; case ATTACHED_DEV_SMARTDOCK_MUIC: case ATTACHED_DEV_DESKDOCK_MUIC: case ATTACHED_DEV_JIG_USB_ON_MUIC: case ATTACHED_DEV_JIG_USB_OFF_MUIC: current_cable_type = SEC_BATTERY_CABLE_NONE; break; case ATTACHED_DEV_UNDEFINED_CHARGING_MUIC: case ATTACHED_DEV_UNDEFINED_RANGE_MUIC: current_cable_type = SEC_BATTERY_CABLE_NONE; break; case ATTACHED_DEV_HICCUP_MUIC: current_cable_type = SEC_BATTERY_CABLE_NONE; break; case ATTACHED_DEV_OTG_MUIC: case ATTACHED_DEV_JIG_UART_OFF_VB_OTG_MUIC: case ATTACHED_DEV_HMT_MUIC: current_cable_type = SEC_BATTERY_CABLE_OTG; break; case ATTACHED_DEV_TIMEOUT_OPEN_MUIC: case ATTACHED_DEV_RETRY_TIMEOUT_OPEN_MUIC: current_cable_type = SEC_BATTERY_CABLE_TIMEOUT; break; case ATTACHED_DEV_USB_MUIC: case ATTACHED_DEV_SMARTDOCK_USB_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_USB_MUIC: current_cable_type = SEC_BATTERY_CABLE_USB; break; case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: #if defined(CONFIG_LSI_IFPMIC) current_cable_type = SEC_BATTERY_CABLE_NONE; break; #endif case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: case ATTACHED_DEV_JIG_UART_OFF_VB_FG_MUIC: current_cable_type = SEC_BATTERY_CABLE_UARTOFF; if (sec_bat_get_facmode()) current_cable_type = SEC_BATTERY_CABLE_NONE; break; case ATTACHED_DEV_RDU_TA_MUIC: battery->store_mode = true; __pm_stay_awake(battery->parse_mode_dt_ws); queue_delayed_work(battery->monitor_wqueue, &battery->parse_mode_dt_work, 0); current_cable_type = SEC_BATTERY_CABLE_TA; break; case ATTACHED_DEV_TA_MUIC: case ATTACHED_DEV_CARDOCK_MUIC: case ATTACHED_DEV_DESKDOCK_VB_MUIC: case ATTACHED_DEV_SMARTDOCK_TA_MUIC: case ATTACHED_DEV_UNOFFICIAL_TA_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_TA_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_ANY_MUIC: case ATTACHED_DEV_UNSUPPORTED_ID_VB_MUIC: case ATTACHED_DEV_AFC_CHARGER_DISABLED_MUIC: current_cable_type = SEC_BATTERY_CABLE_TA; break; case ATTACHED_DEV_AFC_CHARGER_5V_MUIC: case ATTACHED_DEV_RETRY_AFC_CHARGER_5V_MUIC: case ATTACHED_DEV_QC_CHARGER_5V_MUIC: case ATTACHED_DEV_AFC_CHARGER_5V_DUPLI_MUIC: current_cable_type = SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT; break; case ATTACHED_DEV_CDP_MUIC: case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC: current_cable_type = SEC_BATTERY_CABLE_USB_CDP; break; case ATTACHED_DEV_USB_LANHUB_MUIC: current_cable_type = SEC_BATTERY_CABLE_LAN_HUB; break; case ATTACHED_DEV_CHARGING_CABLE_MUIC: current_cable_type = SEC_BATTERY_CABLE_POWER_SHARING; break; case ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC: case ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC: current_cable_type = SEC_BATTERY_CABLE_PREPARE_TA; break; case ATTACHED_DEV_QC_CHARGER_9V_MUIC: current_cable_type = SEC_BATTERY_CABLE_9V_TA; #if defined(CONFIG_BATTERY_CISD) if ((battery->cable_type == SEC_BATTERY_CABLE_TA) || (battery->cable_type == SEC_BATTERY_CABLE_NONE)) battery->cisd.cable_data[CISD_CABLE_QC]++; #endif break; case ATTACHED_DEV_AFC_CHARGER_9V_MUIC: case ATTACHED_DEV_RETRY_AFC_CHARGER_9V_MUIC: case ATTACHED_DEV_AFC_CHARGER_9V_DUPLI_MUIC: current_cable_type = SEC_BATTERY_CABLE_9V_TA; #if defined(CONFIG_BATTERY_CISD) if ((battery->cable_type == SEC_BATTERY_CABLE_TA) || (battery->cable_type == SEC_BATTERY_CABLE_NONE)) battery->cisd.cable_data[CISD_CABLE_AFC]++; #endif break; #if defined(CONFIG_MUIC_HV_12V) case ATTACHED_DEV_AFC_CHARGER_12V_MUIC: case ATTACHED_DEV_AFC_CHARGER_12V_DUPLI_MUIC: current_cable_type = SEC_BATTERY_CABLE_12V_TA; break; #endif case ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC: case ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC: #if defined(CONFIG_BATTERY_CISD) battery->cisd.cable_data[CISD_CABLE_AFC_FAIL]++; #endif break; case ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC: #if defined(CONFIG_BATTERY_CISD) battery->cisd.cable_data[CISD_CABLE_QC_FAIL]++; #endif break; case ATTACHED_DEV_HV_ID_ERR_UNDEFINED_MUIC: case ATTACHED_DEV_HV_ID_ERR_UNSUPPORTED_MUIC: case ATTACHED_DEV_HV_ID_ERR_SUPPORTED_MUIC: current_cable_type = SEC_BATTERY_CABLE_9V_UNKNOWN; break; case ATTACHED_DEV_VZW_INCOMPATIBLE_MUIC: current_cable_type = SEC_BATTERY_CABLE_UNKNOWN; break; default: pr_err("%s: invalid type for charger:%d\n", __func__, attached_dev); break; } #if IS_ENABLED(CONFIG_DUAL_BATTERY) && IS_ENABLED(CONFIG_LIMITER_S2ASL01) && defined(CONFIG_SEC_FACTORY) if (!sec_bat_check_by_gpio(battery)) { if (attached_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC || attached_dev == ATTACHED_DEV_JIG_USB_ON_MUIC) { pr_info("%s No Main or Sub Battery, 301k or 523k with FACTORY\n", __func__); gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 1); } } else { if (attached_dev == ATTACHED_DEV_JIG_UART_OFF_MUIC || attached_dev == ATTACHED_DEV_JIG_UART_ON_MUIC || attached_dev == ATTACHED_DEV_JIG_USB_ON_MUIC) { pr_info("%s 301k or 523k or 619k with FACTORY\n", __func__); gpio_direction_output(battery->pdata->sub_bat_enb_gpio, 0); } } #endif if (battery->is_jig_on && !battery->pdata->support_fgsrc_change) psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, val); #if defined(CONFIG_LSI_IFPMIC) switch (attached_dev) { case ATTACHED_DEV_JIG_USB_ON_MUIC: case ATTACHED_DEV_JIG_UART_OFF_VB_MUIC: val.intval = attached_dev; if (!battery->factory_mode_boot_on) factory_mode = 1; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, val); pr_err("%s : FACTORY MODE TEST! (%d, %d)\n", __func__, val.intval, battery->factory_mode_boot_on); break; #if defined(CONFIG_CHARGER_S2MF301) case ATTACHED_DEV_JIG_USB_OFF_MUIC: val.intval = 0; if (!battery->factory_mode_boot_on) factory_mode = 0; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, val); pr_err("%s : FACTORY MODE TEST! (%d, %d)\n", __func__, val.intval, battery->factory_mode_boot_on); break; #endif #if defined(CONFIG_SIDO_OVP) case ATTACHED_DEV_JIG_UART_ON_MUIC: #endif case ATTACHED_DEV_JIG_UART_ON_VB_MUIC: val.intval = 0; if (!battery->factory_mode_boot_on) factory_mode = 0; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, val); pr_err("%s : FACTORY MODE TEST! (%d, %d)\n", __func__, val.intval, battery->factory_mode_boot_on); break; case ATTACHED_DEV_JIG_UART_OFF_MUIC: psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_ENABLE_HW_FACTORY_MODE, val); pr_err("%s : HW FACTORY MODE ENABLE TEST! (%d)\n", __func__, val.intval); break; default: break; } #endif return current_cable_type; } EXPORT_SYMBOL_KUNIT(sec_bat_cable_check); #endif #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) __visible_for_testing void sec_bat_set_rp_current(struct sec_battery_info *battery, int cable_type) { int icl = 0; int fcc = 0; #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) && defined(CONFIG_SEC_FACTORY) sec_bat_usb_factory_set_vote(battery, false); #endif switch (battery->sink_status.rp_currentlvl) { case RP_CURRENT_ABNORMAL: icl = battery->pdata->rp_current_abnormal_rp3; fcc = battery->pdata->rp_current_abnormal_rp3; break; case RP_CURRENT_LEVEL3: #if defined(CONFIG_PD_CHARGER_HV_DISABLE) if (battery->current_event & SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE) { #else if (battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE) { #endif icl = battery->pdata->default_input_current; fcc = battery->pdata->default_charging_current; } else { if (battery->store_mode) { icl = battery->pdata->rp_current_rdu_rp3; fcc = battery->pdata->max_charging_current; } else { icl = battery->pdata->rp_current_rp3; fcc = battery->pdata->max_charging_current; } } break; case RP_CURRENT_LEVEL2: icl = battery->pdata->rp_current_rp2; fcc = battery->pdata->rp_current_rp2; break; case RP_CURRENT_LEVEL_DEFAULT: if (cable_type == SEC_BATTERY_CABLE_USB) { if (battery->current_event & SEC_BAT_CURRENT_EVENT_USB_SUPER) { icl = USB_CURRENT_SUPER_SPEED; fcc = USB_CURRENT_SUPER_SPEED; } else { icl = battery->pdata->default_usb_input_current; fcc = battery->pdata->default_usb_charging_current; } } else if (cable_type == SEC_BATTERY_CABLE_TA) { icl = battery->pdata->default_input_current; fcc = battery->pdata->default_charging_current; } else { icl = battery->pdata->default_usb_input_current; fcc = battery->pdata->default_usb_charging_current; pr_err("%s: wrong cable type(%d)\n", __func__, cable_type); } break; default: icl = battery->pdata->default_usb_input_current; fcc = battery->pdata->default_usb_charging_current; pr_err("%s: undefined rp_currentlvl(%d)\n", __func__, battery->sink_status.rp_currentlvl); break; } sec_bat_change_default_current(battery, cable_type, icl, fcc); pr_info("%s:(%d)\n", __func__, battery->sink_status.rp_currentlvl); battery->max_charge_power = 0; sec_bat_check_input_voltage(battery, cable_type); sec_vote(battery->input_vote, VOTER_AICL, false, 0); } EXPORT_SYMBOL_KUNIT(sec_bat_set_rp_current); static int usb_typec_handle_id_attach(struct sec_battery_info *battery, PD_NOTI_ATTACH_TYPEDEF *pdata, int *cable_type, const char **cmd) { struct pdic_notifier_struct *pd_noti = pdata->pd; SEC_PD_SINK_STATUS *psink_status = NULL; if (pd_noti) psink_status = &pd_noti->sink_status; switch (pdata->attach) { case MUIC_NOTIFY_CMD_DETACH: case MUIC_NOTIFY_CMD_LOGICALLY_DETACH: *cmd = "DETACH"; battery->is_jig_on = false; battery->pd_usb_attached = false; *cable_type = SEC_BATTERY_CABLE_NONE; battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; battery->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) sec_bat_usb_factory_clear(battery); #endif break; case MUIC_NOTIFY_CMD_ATTACH: case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH: /* Skip notify from MUIC if PDIC is attached already */ if (is_pd_wire_type(battery->wire_status) || battery->init_src_cap) { if (sec_bat_get_lpmode() || (battery->usb_thm_status == USB_THM_NORMAL && !(battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE))) { return -1; /* skip usb_typec_handle_after_id() */ } } *cmd = "ATTACH"; battery->muic_cable_type = pdata->cable_type; *cable_type = sec_bat_cable_check(battery, battery->muic_cable_type); if (battery->cable_type != *cable_type && battery->sink_status.rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && (*cable_type == SEC_BATTERY_CABLE_USB || *cable_type == SEC_BATTERY_CABLE_TA)) { sec_bat_set_rp_current(battery, *cable_type); } else if (psink_status && pd_noti->event == PDIC_NOTIFY_EVENT_PDIC_ATTACH && psink_status->rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && (*cable_type == SEC_BATTERY_CABLE_USB || *cable_type == SEC_BATTERY_CABLE_TA)) { battery->sink_status.rp_currentlvl = psink_status->rp_currentlvl; sec_bat_set_rp_current(battery, *cable_type); } break; default: *cmd = "ERROR"; *cable_type = -1; battery->muic_cable_type = pdata->cable_type; break; } battery->pdic_attach = false; battery->pdic_ps_rdy = false; battery->init_src_cap = false; if (battery->muic_cable_type == ATTACHED_DEV_QC_CHARGER_9V_MUIC || battery->muic_cable_type == ATTACHED_DEV_QC_CHARGER_ERR_V_MUIC) battery->hv_chg_name = "QC"; else if (battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_9V_MUIC || battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_9V_DUPLI_MUIC || battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_ERR_V_MUIC || battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC) battery->hv_chg_name = "AFC"; #if defined(CONFIG_MUIC_HV_12V) else if (battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_MUIC || battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_DUPLI_MUIC) battery->hv_chg_name = "12V"; #endif else battery->hv_chg_name = "NONE"; dev_info(battery->dev, "%s: cable_type:%d, muic_cable_type:%d\n", __func__, *cable_type, battery->muic_cable_type); return 0; } static void sb_disable_reverse_boost(struct sec_battery_info *battery) { union power_supply_propval value = { 0, }; if (battery->pdata->d2d_check_type == SB_D2D_SNKONLY) return; psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); battery->vpdo_src_boost = value.intval ? true : false; if (battery->vpdo_src_boost) { /* turn off dc reverse boost */ value.intval = 0; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_D2D_REVERSE_VOLTAGE, value); } } static int usb_typec_handle_id_power_status(struct sec_battery_info *battery, PD_NOTI_POWER_STATUS_TYPEDEF *pdata, int *cable_type, const char **cmd) { int pdata_fpdo_max_power = battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE ? battery->pdata->nv_charge_power : battery->pdata->pd_charging_charge_power; int pdata_apdo_max_power = battery->current_event & SEC_BAT_CURRENT_EVENT_HV_DISABLE ? battery->pdata->nv_charge_power : battery->pdata->max_charging_charge_power; int max_power = 0, apdo_max_power = 0, fpdo_max_power = 0; int i = 0, current_pdo = 0, num_pd_list = 0; bool bPdIndexChanged = false, bPrintPDlog = true; union power_supply_propval value = {0, }; struct pdic_notifier_struct *pd_noti = pdata->pd; SEC_PD_SINK_STATUS *psink_status = NULL; if (pd_noti) psink_status = &pd_noti->sink_status; if (!psink_status) { dev_err(battery->dev, "%s: sink_status is NULL\n", __func__); return -1; /* skip usb_typec_handle_after_id() */ } #ifdef CONFIG_SEC_FACTORY dev_info(battery->dev, "%s: pd_event(%d)\n", __func__, pd_noti->event); #endif #if IS_ENABLED(CONFIG_HICCUP_CHARGER) if (pd_noti->event != PDIC_NOTIFY_EVENT_DETACH && pd_noti->event != PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC) { if (!sec_bat_get_lpmode() && (battery->usb_thm_status || (battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE))) { return 0; } } #endif switch (pd_noti->event) { case PDIC_NOTIFY_EVENT_DETACH: dev_info(battery->dev, "%s: skip pd operation - attach(%d)\n", __func__, pdata->attach); battery->pdic_attach = false; battery->pdic_ps_rdy = false; battery->init_src_cap = false; battery->hv_pdo = false; battery->pd_list.now_isApdo = false; return -1; /* usb_typec_handle_after_id() */ break; case PDIC_NOTIFY_EVENT_PDIC_ATTACH: battery->sink_status.rp_currentlvl = psink_status->rp_currentlvl; dev_info(battery->dev, "%s: battery->rp_currentlvl(%d)\n", __func__, battery->sink_status.rp_currentlvl); if (battery->wire_status == SEC_BATTERY_CABLE_USB || battery->wire_status == SEC_BATTERY_CABLE_TA) { *cable_type = battery->wire_status; battery->chg_limit = false; battery->lrp_limit = false; battery->lrp_step = LRP_NONE; sec_bat_set_rp_current(battery, *cable_type); return 0; } return -1; /* skip usb_typec_handle_after_id() */ break; case PDIC_NOTIFY_EVENT_PD_SOURCE: case PDIC_NOTIFY_EVENT_PD_SINK: break; case PDIC_NOTIFY_EVENT_PD_SINK_CAP: battery->update_pd_list = true; break; case PDIC_NOTIFY_EVENT_PD_PRSWAP_SNKTOSRC: *cmd = "PD_PRWAP"; dev_info(battery->dev, "%s: PRSWAP_SNKTOSRC(%d)\n", __func__, pdata->attach); *cable_type = SEC_BATTERY_CABLE_NONE; battery->pdic_attach = false; battery->pdic_ps_rdy = false; battery->init_src_cap = false; battery->hv_pdo = false; if (battery->pdata->d2d_check_type == SB_D2D_NONE) return 0; /* for 15w d2d snk to src prswap */ if (battery->pdata->d2d_check_type == SB_D2D_SNKONLY) { value.intval = 1; psy_do_property(battery->pdata->otg_name, set, POWER_SUPPLY_PROP_VOLTAGE_MAX, value); } if (battery->d2d_auth == D2D_AUTH_SNK) battery->d2d_auth = D2D_AUTH_SRC; return 0; case PDIC_NOTIFY_EVENT_PD_PRSWAP_SRCTOSNK: *cmd = "PD_PRWAP"; dev_info(battery->dev, "%s: PRSWAP_SRCTOSNK(%d)\n", __func__, pdata->attach); if (battery->d2d_auth == D2D_AUTH_SRC) { if (battery->pdata->d2d_check_type == SB_D2D_SRCSNK) { sb_disable_reverse_boost(battery); battery->vpdo_ocp = false; } battery->d2d_auth = D2D_AUTH_SNK; value.intval = 0; psy_do_property(battery->pdata->otg_name, set, POWER_SUPPLY_PROP_VOLTAGE_MAX, value); } battery->vpdo_auth_stat = AUTH_NONE; battery->hp_d2d = HP_D2D_NONE; sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, false, 0); return 0; default: break; } *cmd = "PD_ATTACH"; battery->init_src_cap = false; if (battery->update_pd_list) { pr_info("%s : update_pd_list(%d)\n", __func__, battery->update_pd_list); #if IS_ENABLED(CONFIG_DIRECT_CHARGING) #if defined(CONFIG_STEP_CHARGING) sec_bat_reset_step_charging(battery); #endif psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_DIRECT_CLEAR_ERR, value); #endif battery->pdic_attach = false; battery->update_pd_list = false; sec_vote_refresh(battery->iv_vote); } if (!battery->pdic_attach) { battery->sink_status = *psink_status; bPdIndexChanged = true; } else { dev_info(battery->dev, "%s: prev_pdo(%d), curr_pdo(%d)\n", __func__, battery->sink_status.current_pdo_num, psink_status->current_pdo_num); if (battery->sink_status.current_pdo_num != psink_status->current_pdo_num) bPdIndexChanged = true; battery->sink_status.selected_pdo_num = psink_status->selected_pdo_num; battery->sink_status.current_pdo_num = psink_status->current_pdo_num; battery->pdic_ps_rdy = true; sec_bat_get_input_current_in_power_list(battery); sec_bat_get_charging_current_in_power_list(battery); sec_vote(battery->chgen_vote, VOTER_SRCCAP_TRANSIT, false, 0); } current_pdo = battery->sink_status.current_pdo_num; if (battery->sink_status.power_list[current_pdo].max_voltage > SEC_INPUT_VOLTAGE_5V) battery->hv_pdo = true; else battery->hv_pdo = false; dev_info(battery->dev, "%s: battery->pdic_ps_rdy(%d), hv_pdo(%d)\n", __func__, battery->pdic_ps_rdy, battery->hv_pdo); if (battery->sink_status.has_apdo) { *cable_type = SEC_BATTERY_CABLE_PDIC_APDO; if (battery->sink_status.power_list[current_pdo].pdo_type == APDO_TYPE) { battery->hv_chg_name = "PDIC_APDO"; battery->pd_list.now_isApdo = true; } else { battery->hv_chg_name = "PDIC_FIXED"; battery->pd_list.now_isApdo = false; } if (battery->pdic_attach) bPrintPDlog = false; } else { *cable_type = SEC_BATTERY_CABLE_PDIC; if (battery->sink_status.power_list[current_pdo].pdo_type == VPDO_TYPE) { battery->hv_chg_name = "PDIC_VPDO"; if ((battery->pdata->d2d_check_type == SB_D2D_SRCSNK) && (battery->d2d_auth == D2D_AUTH_SNK)) { /* preset auth vpdo for pr swap case (snk to src) */ sec_pd_vpdo_auth(AUTH_HIGH_PWR, SB_D2D_SRCSNK); } } else battery->hv_chg_name = "PDIC_FIXED"; battery->pd_list.now_isApdo = false; } battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; battery->input_voltage = battery->sink_status.power_list[current_pdo].max_voltage; dev_info(battery->dev, "%s: available pdo : %d, current pdo : %d\n", __func__, battery->sink_status.available_pdo_num, current_pdo); for (i = 1; i <= battery->sink_status.available_pdo_num; i++) { bool isUpdated = false; int temp_power = 0; int pdo_type = battery->sink_status.power_list[i].pdo_type; int max_volt = battery->sink_status.power_list[i].max_voltage; int min_volt = battery->sink_status.power_list[i].min_voltage; int max_curr = battery->sink_status.power_list[i].max_current; bool isAccept = battery->sink_status.power_list[i].accept; if ((pdo_type == APDO_TYPE) && (sec_pd_get_apdo_prog_volt(pdo_type, max_volt) > battery->pdata->apdo_max_volt)) temp_power = battery->pdata->apdo_max_volt * max_curr; /* Protect to show sfc2.0 with 45w ta + 3a cable */ else temp_power = sec_pd_get_max_power(pdo_type, min_volt, max_volt, max_curr); if ((pdo_type != APDO_TYPE) && isAccept && (max_volt > battery->pdata->max_input_voltage)) { battery->sink_status.power_list[i].accept = false; isAccept = battery->sink_status.power_list[i].accept; } if (bPrintPDlog) pr_info("%s:%spower_list[%d,%s,%s], maxVol:%d, minVol:%d, maxCur:%d, power:%d\n", __func__, i == current_pdo ? "**" : " ", i, sec_pd_pdo_type_str(pdo_type), isAccept ? "O" : "X", max_volt, min_volt, max_curr, temp_power); if (!battery->pdic_attach && isAccept) { if (pdo_type == APDO_TYPE) { if (temp_power > apdo_max_power) { max_power = temp_power; apdo_max_power = max_power; } } else { if (temp_power > fpdo_max_power) { max_power = temp_power; fpdo_max_power = max_power; } } } if (!isAccept) continue; /* no change apdo */ if (pdo_type == APDO_TYPE) { num_pd_list++; continue; } if (temp_power > (pdata_fpdo_max_power * 1000)) { fpdo_max_power = pdata_fpdo_max_power * 1000; max_curr = mA_by_mWmV(pdata_fpdo_max_power, max_volt); isUpdated = true; } if (max_curr > battery->pdata->max_input_current) { max_curr = battery->pdata->max_input_current; isUpdated = true; } if (isUpdated) { battery->sink_status.power_list[i].max_current = max_curr; if (bPrintPDlog) pr_info("%s:->updated [%d,%s,%s], maxVol:%d, minVol:%d, maxCur:%d, power:%d\n", __func__, i, sec_pd_pdo_type_str(pdo_type), isAccept ? "O" : "X", battery->sink_status.power_list[i].max_voltage, battery->sink_status.power_list[i].min_voltage, battery->sink_status.power_list[i].max_current, sec_pd_get_max_power(pdo_type, min_volt, max_volt, max_curr)); } num_pd_list++; } if (!battery->pdic_attach) { if (battery->sink_status.has_apdo) battery->max_charge_power = (apdo_max_power / 1000) > pdata_apdo_max_power ? pdata_apdo_max_power : (apdo_max_power / 1000); else battery->max_charge_power = (fpdo_max_power / 1000) > pdata_fpdo_max_power ? pdata_fpdo_max_power : (fpdo_max_power / 1000); battery->pd_max_charge_power = battery->max_charge_power; pr_info("%s: pd_max_charge_power(%d,%d,%d) = %d\n", __func__, max_power, apdo_max_power, fpdo_max_power, battery->pd_max_charge_power * 1000); battery->pd_rated_power = max_power / 1000 / 1000; /* save as W */ #if defined(CONFIG_BATTERY_CISD) count_cisd_power_data(&battery->cisd, max_power / 1000); if (battery->cable_type == SEC_BATTERY_CABLE_NONE) { if (battery->pd_max_charge_power > HV_CHARGER_STATUS_STANDARD1) battery->cisd.cable_data[CISD_CABLE_PD_HIGH]++; else battery->cisd.cable_data[CISD_CABLE_PD]++; } #endif #if defined(CONFIG_SUPPORT_HV_CTRL) && !defined(CONFIG_SEC_FACTORY) if (battery->pdata->boosting_voltage_aicl && (battery->misc_event & BATT_MISC_EVENT_HV_BY_AICL)) { if (sb_ct_has_9v(battery, battery->cable_type)) { sec_vote(battery->input_vote, VOTER_AICL, false, 0); sec_vote(battery->iv_vote, VOTER_AICL, true, SEC_INPUT_VOLTAGE_9V); } else { sec_vote(battery->iv_vote, VOTER_AICL, false, 0); } } #endif battery->pd_list.max_pd_count = num_pd_list; pr_info("%s: total num_pd_list: %d\n", __func__, battery->pd_list.max_pd_count); if (battery->pd_list.max_pd_count <= 0 || battery->pd_list.max_pd_count > MAX_PDO_NUM) return -1; /* skip usb_typec_handle_after_id() */ } battery->pdic_attach = true; #if defined(CONFIG_BC12_DEVICE) battery->pdic_ps_rdy = true; #endif if (is_pd_wire_type(battery->cable_type) && bPdIndexChanged) { if ((battery->d2d_auth == D2D_AUTH_SNK) && (battery->sink_status.power_list[current_pdo].pdo_type == FPDO_TYPE)) { /* request vpdo for 15w d2d */ sec_vote(battery->iv_vote, VOTER_CABLE, true, sec_bat_check_pd_iv(&battery->sink_status)); } /* Check VOTER_SIOP to set up current based on chagned index */ __pm_stay_awake(battery->siop_level_ws); queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); #if !defined(CONFIG_SEC_FACTORY) sec_bat_check_lrp_temp(battery, battery->cable_type, battery->wire_status, battery->siop_level, battery->lcd_status); #endif } if (is_pd_apdo_wire_type(battery->wire_status) && !bPdIndexChanged && (battery->sink_status.power_list[current_pdo].pdo_type == APDO_TYPE)) { battery->wire_status = *cable_type; return -1; /* skip usb_typec_handle_after_id() */ } store_battery_log("CURR_PDO:PDO(%d/%d),Max_V(%d),Min_V(%d),Max_C(%d),PD_MCP(%d),PD_RP(%d),", battery->sink_status.current_pdo_num, battery->sink_status.available_pdo_num, battery->sink_status.power_list[current_pdo].max_voltage, battery->sink_status.power_list[current_pdo].min_voltage, battery->sink_status.power_list[current_pdo].max_current, battery->pd_max_charge_power, battery->pd_rated_power); return 0; } static int usb_typec_handle_id_usb(struct sec_battery_info *battery, PD_NOTI_ATTACH_TYPEDEF *pdata) { if (pdata->cable_type == PD_USB_TYPE) battery->pd_usb_attached = true; else if (pdata->cable_type == PD_NONE_TYPE) battery->pd_usb_attached = false; dev_info(battery->dev, "%s: PDIC_NOTIFY_ID_USB: %d\n", __func__, battery->pd_usb_attached); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); return -1; /* skip usb_typec_handle_after_id() */ } static void sb_auth_detach(struct sec_battery_info *battery) { union power_supply_propval value = { 0, }; battery->d2d_auth = D2D_AUTH_NONE; battery->vpdo_ocp = false; battery->vpdo_auth_stat = AUTH_NONE; battery->hp_d2d = HP_D2D_NONE; /* clear vpdo auth for snk to src(pr swap) with detach case */ sec_pd_vpdo_auth(AUTH_NONE, battery->pdata->d2d_check_type); sb_disable_reverse_boost(battery); /* set otg ocp limit 0.9A */ value.intval = 0; psy_do_property(battery->pdata->otg_name, set, POWER_SUPPLY_PROP_VOLTAGE_MAX, value); /* clear buck off vote */ sec_vote(battery->chgen_vote, VOTER_D2D_WIRE, false, 0); } static int usb_typec_handle_id_device_info(struct sec_battery_info *battery, PD_NOTI_DEVICE_INFO_TYPEDEF *pdata) { union power_supply_propval value = { 0, }; if ((pdata->vendor_id != AUTH_VENDOR_ID) || (pdata->product_id != AUTH_PRODUCT_ID) || (pdata->version <= 0)) { pr_info("%s : skip id_device_info\n", __func__); return -1; } if (battery->pdata->d2d_check_type != SB_D2D_NONE) { if (battery->pdata->d2d_check_type == SB_D2D_SNKONLY) { /* set otg ocp limit 1.5A */ value.intval = 1; psy_do_property(battery->pdata->otg_name, set, POWER_SUPPLY_PROP_VOLTAGE_MAX, value); } battery->d2d_auth = D2D_AUTH_SRC; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) sec_bat_d2d_check(battery, battery->capacity, battery->temperature, battery->lrp); #endif } else battery->d2d_auth = D2D_AUTH_SNK; pr_info("%s set d2d auth %s : (0x%04x, 0x%04x, %d)\n", __func__, ((battery->d2d_auth == D2D_AUTH_SRC) ? "src" : "snk"), pdata->vendor_id, pdata->product_id, pdata->version); return -1; } static int usb_typec_handle_id_svid_info(struct sec_battery_info *battery, PD_NOTI_SVID_INFO_TYPEDEF *pdata) { if (pdata->standard_vendor_id == AUTH_VENDOR_ID) battery->d2d_auth = D2D_AUTH_SNK; pr_info("%s set vpdo snk auth : %s (0x%04x)\n", __func__, (battery->d2d_auth == D2D_AUTH_SNK) ? "enable" : "disable", pdata->standard_vendor_id); return -1; } static int usb_typec_handle_id_clear_info(struct sec_battery_info *battery, PD_NOTI_CLEAR_INFO_TYPEDEF *pdata) { if ((pdata->clear_id == PDIC_NOTIFY_ID_DEVICE_INFO) || (pdata->clear_id == PDIC_NOTIFY_ID_SVID_INFO)) { sb_auth_detach(battery); } pr_info("%s clear vpdo auth : (%d)\n", __func__, pdata->clear_id); return -1; } static int usb_typec_handle_after_id(struct sec_battery_info *battery, int cable_type, const char *cmd) { #if defined(CONFIG_WIRELESS_TX_MODE) if (!is_hv_wire_type(cable_type) && !is_hv_pdo_wire_type(cable_type, battery->hv_pdo)) { sec_vote(battery->chgen_vote, VOTER_CHANGE_CHGMODE, false, 0); sec_vote(battery->iv_vote, VOTER_CHANGE_CHGMODE, false, 0); } #endif #if defined(CONFIG_PD_CHARGER_HV_DISABLE) && !defined(CONFIG_SEC_FACTORY) if (check_afc_disabled_type(battery->muic_cable_type)) { pr_info("%s set SEC_BAT_CURRENT_EVENT_AFC_DISABLE\n", __func__); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_AFC_DISABLE, SEC_BAT_CURRENT_EVENT_AFC_DISABLE); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } else { sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC_DISABLE); } #endif sec_bat_set_misc_event(battery, (battery->muic_cable_type == ATTACHED_DEV_UNDEFINED_CHARGING_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0) | (battery->muic_cable_type == ATTACHED_DEV_UNDEFINED_RANGE_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0), BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE); if (battery->muic_cable_type == ATTACHED_DEV_HICCUP_MUIC) { if (battery->usb_thm_status || (battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { pr_info("%s: Hiccup Set because of USB Temp\n", __func__); sec_bat_set_misc_event(battery, BATT_MISC_EVENT_TEMP_HICCUP_TYPE, BATT_MISC_EVENT_TEMP_HICCUP_TYPE); battery->usb_thm_status = USB_THM_NORMAL; } else { pr_info("%s: Hiccup Set because of Water detect\n", __func__); sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WATER_HICCUP_TYPE, BATT_MISC_EVENT_WATER_HICCUP_TYPE); } battery->hiccup_status = 1; } else { battery->hiccup_status = 0; if (battery->hiccup_clear) { sec_bat_set_misc_event(battery, 0, (BATT_MISC_EVENT_WATER_HICCUP_TYPE | BATT_MISC_EVENT_TEMP_HICCUP_TYPE)); battery->hiccup_clear = false; pr_info("%s : Hiccup event clear! hiccup clear bit set (%d)\n", __func__, battery->hiccup_clear); } else if (battery->misc_event & (BATT_MISC_EVENT_WATER_HICCUP_TYPE | BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } } if (cable_type < 0 || cable_type > SEC_BATTERY_CABLE_MAX) { dev_info(battery->dev, "%s: ignore event(%d)\n", __func__, battery->muic_cable_type); return -1; /* skip usb_typec_handle_after_id() */ } else if ((cable_type == SEC_BATTERY_CABLE_UNKNOWN) && (battery->status != POWER_SUPPLY_STATUS_DISCHARGING)) { battery->cable_type = cable_type; __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); dev_info(battery->dev, "%s: UNKNOWN cable plugin\n", __func__); return -1; /* skip usb_typec_handle_after_id() */ } battery->wire_status = cable_type; #if defined(CONFIG_WIRELESS_TX_MODE) if (battery->wc_tx_enable && battery->uno_en) { int work_delay = 0; if (battery->wire_status == SEC_BATTERY_CABLE_NONE) { battery->buck_cntl_by_tx = true; sec_vote(battery->chgen_vote, VOTER_WC_TX, true, SEC_BAT_CHG_MODE_BUCK_OFF); if (battery->tx_switch_mode != TX_SWITCH_GEAR_PPS) { battery->tx_switch_mode = TX_SWITCH_MODE_OFF; battery->tx_switch_mode_change = false; } battery->tx_switch_start_soc = 0; } if (battery->pdata->tx_5v_disable && battery->wire_status == SEC_BATTERY_CABLE_TA) work_delay = battery->pdata->pre_afc_work_delay + 500; //add delay more afc check if (battery->tx_switch_mode != TX_SWITCH_GEAR_PPS) sec_bat_run_wpc_tx_work(battery, work_delay); } #endif cancel_delayed_work(&battery->cable_work); __pm_relax(battery->cable_ws); if (cable_type == SEC_BATTERY_CABLE_HV_TA_CHG_LIMIT) { /* set current event */ sec_bat_cancel_input_check_work(battery); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_CHG_LIMIT, (SEC_BAT_CURRENT_EVENT_CHG_LIMIT | SEC_BAT_CURRENT_EVENT_AFC)); sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); /* Check VOTER_SIOP to set up current based on cable_type */ __pm_stay_awake(battery->siop_level_ws); queue_delayed_work(battery->monitor_wqueue, &battery->siop_level_work, 0); __pm_stay_awake(battery->monitor_ws); battery->polling_count = 1; /* initial value = 1 */ queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } else if ((battery->wire_status == battery->cable_type) && (((battery->wire_status == SEC_BATTERY_CABLE_USB || battery->wire_status == SEC_BATTERY_CABLE_TA) && battery->sink_status.rp_currentlvl >= RP_CURRENT_LEVEL_DEFAULT && !(battery->current_event & SEC_BAT_CURRENT_EVENT_AFC)) || is_hv_wire_type(battery->wire_status))) { sec_bat_cancel_input_check_work(battery); sec_bat_set_current_event(battery, 0, SEC_BAT_CURRENT_EVENT_AFC); sec_vote(battery->input_vote, VOTER_CABLE, true, battery->pdata->charging_current[cable_type].input_current_limit); sec_vote(battery->fcc_vote, VOTER_CABLE, true, battery->pdata->charging_current[cable_type].fast_charging_current); sec_vote(battery->input_vote, VOTER_VBUS_CHANGE, false, 0); __pm_stay_awake(battery->monitor_ws); battery->polling_count = 1; /* initial value = 1 */ queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } else if (cable_type == SEC_BATTERY_CABLE_PREPARE_TA) { sec_bat_check_afc_input_current(battery); } else { __pm_stay_awake(battery->cable_ws); if (battery->ta_alert_wa && battery->ta_alert_mode != OCP_NONE) { if (!strcmp(cmd, "DETACH")) { queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, msecs_to_jiffies(3000)); } else { queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); } } else { queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); } } return 0; } static int usb_typec_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { const char *cmd = "NONE"; struct sec_battery_info *battery = container_of(nb, struct sec_battery_info, usb_typec_nb); int cable_type = SEC_BATTERY_CABLE_NONE; PD_NOTI_TYPEDEF *pdata = (PD_NOTI_TYPEDEF *)data; struct pdic_notifier_struct *pd_noti = pdata->pd; SEC_PD_SINK_STATUS *psink_status = NULL; int ret_handle = 0; dev_info(battery->dev, "%s: action:%ld src:%d, dest:%d, id:%d, sub1:%d, sub2:%d, sub3:%d\n", __func__, action, pdata->src, pdata->dest, pdata->id, pdata->sub1, pdata->sub2, pdata->sub3); if ((pdata->dest != PDIC_NOTIFY_DEV_BATT) && (pdata->dest != PDIC_NOTIFY_DEV_ALL)) { dev_info(battery->dev, "%s: skip handler dest(%d)\n", __func__, pdata->dest); return 0; } if (!pd_noti) { dev_info(battery->dev, "%s: pd_noti(pdata->pd) is NULL\n", __func__); } else { psink_status = &pd_noti->sink_status; if (!battery->psink_status) { battery->psink_status = psink_status; sec_pd_init_data(battery->psink_status); #if defined(CONFIG_BATTERY_CISD) sec_pd_register_chg_info_cb(count_cisd_pd_data); #endif } } mutex_lock(&battery->typec_notylock); switch (pdata->id) { case PDIC_NOTIFY_ID_WATER: case PDIC_NOTIFY_ID_ATTACH: ret_handle = usb_typec_handle_id_attach(battery, (PD_NOTI_ATTACH_TYPEDEF *)pdata, &cable_type, &cmd); break; case PDIC_NOTIFY_ID_POWER_STATUS: ret_handle = usb_typec_handle_id_power_status(battery, (PD_NOTI_POWER_STATUS_TYPEDEF *)pdata, &cable_type, &cmd); break; case PDIC_NOTIFY_ID_USB: ret_handle = usb_typec_handle_id_usb(battery, (PD_NOTI_ATTACH_TYPEDEF *)pdata); break; case PDIC_NOTIFY_ID_DEVICE_INFO: ret_handle = usb_typec_handle_id_device_info(battery, (PD_NOTI_DEVICE_INFO_TYPEDEF *)pdata); break; case PDIC_NOTIFY_ID_SVID_INFO: ret_handle = usb_typec_handle_id_svid_info(battery, (PD_NOTI_SVID_INFO_TYPEDEF *)pdata); break; case PDIC_NOTIFY_ID_CLEAR_INFO: ret_handle = usb_typec_handle_id_clear_info(battery, (PD_NOTI_CLEAR_INFO_TYPEDEF *)pdata); break; default: cmd = "ERROR"; cable_type = -1; battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; battery->hv_chg_name = "NONE"; break; } if (ret_handle < 0) goto skip_handle_after_id; usb_typec_handle_after_id(battery, cable_type, cmd); skip_handle_after_id: dev_info(battery->dev, "%s: CMD[%s], CABLE_TYPE[%d]\n", __func__, cmd, cable_type); mutex_unlock(&battery->typec_notylock); return 0; } #else #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) static int batt_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { const char *cmd; int cable_type = SEC_BATTERY_CABLE_NONE; struct sec_battery_info *battery = container_of(nb, struct sec_battery_info, batt_nb); union power_supply_propval value = {0, }; #if IS_ENABLED(CONFIG_PDIC_NOTIFIER) || IS_ENABLED(CONFIG_CCIC_NOTIFIER) PD_NOTI_ATTACH_TYPEDEF *p_noti = (PD_NOTI_ATTACH_TYPEDEF *)data; muic_attached_dev_t attached_dev = p_noti->cable_type; #else muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data; #endif mutex_lock(&battery->batt_handlelock); switch (action) { case MUIC_NOTIFY_CMD_DETACH: case MUIC_NOTIFY_CMD_LOGICALLY_DETACH: cmd = "DETACH"; battery->is_jig_on = false; cable_type = SEC_BATTERY_CABLE_NONE; battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; break; case MUIC_NOTIFY_CMD_ATTACH: case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH: cmd = "ATTACH"; cable_type = sec_bat_cable_check(battery, attached_dev); battery->muic_cable_type = attached_dev; break; default: cmd = "ERROR"; cable_type = -1; battery->muic_cable_type = ATTACHED_DEV_NONE_MUIC; break; } sec_bat_set_misc_event(battery, #if !defined(CONFIG_ENG_BATTERY_CONCEPT) && !defined(CONFIG_SEC_FACTORY) (battery->muic_cable_type == ATTACHED_DEV_JIG_UART_ON_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0) | (battery->muic_cable_type == ATTACHED_DEV_JIG_USB_ON_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0) | #endif (battery->muic_cable_type == ATTACHED_DEV_UNDEFINED_RANGE_MUIC ? BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE : 0), BATT_MISC_EVENT_UNDEFINED_RANGE_TYPE); if (battery->muic_cable_type == ATTACHED_DEV_HICCUP_MUIC) { if (battery->usb_thm_status || (battery->misc_event & BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { pr_info("%s: Hiccup Set because of USB Temp\n", __func__); sec_bat_set_misc_event(battery, BATT_MISC_EVENT_TEMP_HICCUP_TYPE, BATT_MISC_EVENT_TEMP_HICCUP_TYPE); battery->usb_thm_status = USB_THM_NORMAL; } else { pr_info("%s: Hiccup Set because of Water detect\n", __func__); sec_bat_set_misc_event(battery, BATT_MISC_EVENT_WATER_HICCUP_TYPE, BATT_MISC_EVENT_WATER_HICCUP_TYPE); } battery->hiccup_status = 1; } else { battery->hiccup_status = 0; if (battery->misc_event & (BATT_MISC_EVENT_WATER_HICCUP_TYPE | BATT_MISC_EVENT_TEMP_HICCUP_TYPE)) { __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } } /* If PD cable is already attached, return this function */ if (battery->pdic_attach) { dev_info(battery->dev, "%s: ignore event pdic attached(%d)\n", __func__, battery->pdic_attach); mutex_unlock(&battery->batt_handlelock); return 0; } if (attached_dev == ATTACHED_DEV_MHL_MUIC) { mutex_unlock(&battery->batt_handlelock); return 0; } if (cable_type < 0) { dev_info(battery->dev, "%s: ignore event(%d)\n", __func__, cable_type); } else if ((cable_type == SEC_BATTERY_CABLE_UNKNOWN) && (battery->status != POWER_SUPPLY_STATUS_DISCHARGING)) { battery->cable_type = cable_type; __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); dev_info(battery->dev, "%s: UNKNOWN cable plugin\n", __func__); mutex_unlock(&battery->batt_handlelock); return 0; } else { battery->wire_status = cable_type; if (is_nocharge_type(battery->wire_status) && (battery->wc_status != SEC_BATTERY_CABLE_NONE)) cable_type = SEC_BATTERY_CABLE_WIRELESS; } dev_info(battery->dev, "%s: current_cable(%d), wc_status(%d), wire_status(%d)\n", __func__, cable_type, battery->wc_status, battery->wire_status); mutex_unlock(&battery->batt_handlelock); if (attached_dev == ATTACHED_DEV_USB_LANHUB_MUIC) { if (!strcmp(cmd, "ATTACH")) { value.intval = true; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL, value); dev_info(battery->dev, "%s: Powered OTG cable attached\n", __func__); } else { value.intval = false; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_CHARGE_POWERED_OTG_CONTROL, value); dev_info(battery->dev, "%s: Powered OTG cable detached\n", __func__); } } if (!strcmp(cmd, "ATTACH")) { if ((battery->muic_cable_type >= ATTACHED_DEV_QC_CHARGER_PREPARE_MUIC) && (battery->muic_cable_type <= ATTACHED_DEV_QC_CHARGER_9V_MUIC)) { battery->hv_chg_name = "QC"; } else if ((battery->muic_cable_type >= ATTACHED_DEV_AFC_CHARGER_PREPARE_MUIC) && (battery->muic_cable_type <= ATTACHED_DEV_AFC_CHARGER_ERR_V_DUPLI_MUIC)) { battery->hv_chg_name = "AFC"; #if defined(CONFIG_MUIC_HV_12V) } else if (battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_MUIC || battery->muic_cable_type == ATTACHED_DEV_AFC_CHARGER_12V_DUPLI_MUIC) { battery->hv_chg_name = "12V"; #endif } else battery->hv_chg_name = "NONE"; } else { battery->hv_chg_name = "NONE"; } pr_info("%s : HV_CHARGER_NAME(%s)\n", __func__, battery->hv_chg_name); if ((cable_type >= 0) && cable_type <= SEC_BATTERY_CABLE_MAX) { if (cable_type == SEC_BATTERY_CABLE_NONE) { __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } else if (cable_type != battery->cable_type) { __pm_stay_awake(battery->cable_ws); queue_delayed_work(battery->monitor_wqueue, &battery->cable_work, 0); } else { dev_info(battery->dev, "%s: Cable is Not Changed(%d)\n", __func__, battery->cable_type); } } pr_info("%s: CMD=%s, attached_dev=%d\n", __func__, cmd, attached_dev); return 0; } #endif /* CONFIG_MUIC_NOTIFIER */ #endif #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_LSI_IFPMIC) static int vbus_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { vbus_status_t vbus_status = *(vbus_status_t *)data; struct sec_battery_info *battery = container_of(nb, struct sec_battery_info, vbus_nb); mutex_lock(&battery->batt_handlelock); pr_debug("battery: %s: action=%d, vbus_status=%s\n", __func__, (int)action, vbus_status == STATUS_VBUS_HIGH ? "HIGH" : "LOW"); if (battery->cable_type == SEC_BATTERY_CABLE_PDIC) { sec_vote_refresh(battery->input_vote); sec_vote_refresh(battery->fcc_vote); sec_vote_refresh(battery->chgen_vote); } mutex_unlock(&battery->batt_handlelock); return 0; } #endif __visible_for_testing void sec_bat_parse_param_value(struct sec_battery_info *battery) { #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) union power_supply_propval value = {0, }; #endif int chg_mode = 0; int pd_hv_disable = 0; #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) int afc_mode = 0; #endif chg_mode = sec_bat_get_chgmode() & 0x000000FF; pr_info("%s: charging_mode: 0x%x (charging_night_mode:0x%x)\n", __func__, sec_bat_get_chgmode(), chg_mode); pd_hv_disable = sec_bat_get_dispd() & 0x000000FF; pr_info("%s: pd_disable: 0x%x (pd_hv_disable:0x%x)\n", __func__, sec_bat_get_dispd(), pd_hv_disable); #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) afc_mode = get_afc_mode(); pr_info("%s: afc_mode: 0x%x\n", __func__, afc_mode); #endif #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) battery->charging_night_mode = chg_mode; /* Check High Voltage charging option for wireless charging */ /* '1' means disabling High Voltage charging */ if (battery->charging_night_mode == '1') /* 0x31 */ battery->sleep_mode = true; else battery->sleep_mode = false; value.intval = battery->sleep_mode; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_EXT_PROP_SLEEP_MODE, value); #endif #if defined(CONFIG_PD_CHARGER_HV_DISABLE) if (pd_hv_disable == '1') { /* 0x31 */ battery->pd_disable = true; pr_info("PD wired charging mode is disabled\n"); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_HV_DISABLE, SEC_BAT_CURRENT_EVENT_HV_DISABLE); } #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) /* Check High Voltage charging option for wired charging */ if (afc_mode == CH_MODE_AFC_DISABLE_VAL) { pr_info("None PD wired charging mode is disabled\n"); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE, SEC_BAT_CURRENT_EVENT_NOPD_HV_DISABLE); } #endif #else #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) /* Check High Voltage charging option for wired charging */ if (afc_mode == CH_MODE_AFC_DISABLE_VAL) { pr_info("HV wired charging mode is disabled\n"); sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_HV_DISABLE, SEC_BAT_CURRENT_EVENT_HV_DISABLE); } #endif #endif #if IS_ENABLED(CONFIG_USB_FACTORY_MODE) pr_info("%s: f_mode: %s\n", __func__, f_mode); if (!f_mode) { battery->batt_f_mode = NO_MODE; } else if ((strncmp(f_mode, "OB", 2) == 0) || (strncmp(f_mode, "DL", 2) == 0)) { /* Set factory mode variables in OB mode */ sec_bat_set_facmode(true); battery->factory_mode = true; battery->batt_f_mode = OB_MODE; } else if (strncmp(f_mode, "IB", 2) == 0) { battery->batt_f_mode = IB_MODE; } else { battery->batt_f_mode = NO_MODE; } pr_info("[BAT] %s: f_mode: %s\n", __func__, BOOT_MODE_STRING[battery->batt_f_mode]); battery->usb_factory_init = false; #if defined(CONFIG_SEC_FACTORY) battery->usb_factory_slate_mode = false; #endif #endif battery->factory_mode_boot_on = sec_bat_get_facmode(); } EXPORT_SYMBOL_KUNIT(sec_bat_parse_param_value); static void sec_bat_afc_init_work(struct work_struct *work) { int ret = 0; #if defined(CONFIG_AFC_CHARGER_MODE) ret = muic_hv_charger_init(); #endif pr_info("%s, ret(%d)\n", __func__, ret); } static void sec_batt_dev_init_work(struct work_struct *work) { struct sec_battery_info *battery = container_of(work, struct sec_battery_info, dev_init_work.work); union power_supply_propval value = {0, }; #if defined(CONFIG_STORE_MODE) && !defined(CONFIG_SEC_FACTORY) && IS_ENABLED(CONFIG_DIRECT_CHARGING) char direct_charging_source_status[2] = {0, }; #endif pr_info("%s: Start\n", __func__); sec_bat_set_charging_status(battery, POWER_SUPPLY_STATUS_DISCHARGING); sec_bat_set_health(battery, POWER_SUPPLY_HEALTH_GOOD); battery->capacity = 50; sec_chg_check_modprobe(); /* updates temperatures on boot */ sec_bat_get_temperature_info(battery); /* initialize battery voltage*/ value.intval = SEC_BATTERY_VOLTAGE_MV; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_VOLTAGE_NOW, value); battery->voltage_now = value.intval; /* initialize battery level*/ value.intval = 0; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_PROP_CAPACITY, value); battery->capacity = value.intval; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) sec_bat_check_boottime(battery, battery->pdata->dctp_bootmode_en); #endif sec_bat_parse_param_value(battery); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) #if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) queue_delayed_work(battery->monitor_wqueue, &battery->fw_init_work, msecs_to_jiffies(2000)); #endif psy_do_property(battery->pdata->wireless_charger_name, get, POWER_SUPPLY_EXT_PROP_WPC_EN, value); sec_bat_set_current_event(battery, value.intval ? SEC_BAT_CURRENT_EVENT_WPC_EN : 0, SEC_BAT_CURRENT_EVENT_WPC_EN); /* * notify wireless charger driver when sec_battery probe is done, * if wireless charging is possible, POWER_SUPPLY_PROP_ONLINE of wireless property will be called. */ value.intval = 0; psy_do_property(battery->pdata->wireless_charger_name, set, POWER_SUPPLY_PROP_CHARGE_TYPE, value); #endif #if defined(CONFIG_USE_POGO) /* * notify pogo charger driver when sec_battery probe is done, * if pogo charging is possible, POWER_SUPPLY_PROP_ONLINE of pogo property will be called. */ value.intval = 0; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_CHARGE_TYPE, value); #endif #if defined(CONFIG_STORE_MODE) && !defined(CONFIG_SEC_FACTORY) #if IS_ENABLED(CONFIG_DIRECT_CHARGING) direct_charging_source_status[0] = SEC_STORE_MODE; direct_charging_source_status[1] = SEC_CHARGING_SOURCE_SWITCHING; value.strval = direct_charging_source_status; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_EXT_PROP_CHANGE_CHARGING_SOURCE, value); #endif #endif register_batterylog_proc(); #if IS_ENABLED(CONFIG_USB_TYPEC_MANAGER_NOTIFIER) battery->sink_status.rp_currentlvl = RP_CURRENT_LEVEL_NONE; manager_notifier_register(&battery->usb_typec_nb, usb_typec_handle_notification, MANAGER_NOTIFY_PDIC_BATTERY); #else #if IS_ENABLED(CONFIG_MUIC_NOTIFIER) muic_notifier_register(&battery->batt_nb, batt_handle_notification, MUIC_NOTIFY_DEV_CHARGER); #endif #endif value.intval = true; psy_do_property(battery->pdata->charger_name, set, POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, value); #if defined(CONFIG_SEC_COMMON) /* make fg_reset true again for actual normal booting after recovery kernel is done */ if (sec_bat_get_fgreset() && seccmn_recv_is_boot_recovery()) { pr_info("%s: fg_reset(%d) boot_recov(%d)\n", __func__, sec_bat_get_fgreset(), seccmn_recv_is_boot_recovery()); psy_do_property(battery->pdata->fuelgauge_name, set, POWER_SUPPLY_PROP_ENERGY_NOW, value); pr_info("%s: make fg_reset true again for actual normal booting\n", __func__); } #endif #if defined(CONFIG_NO_BATTERY) sec_vote(battery->chgen_vote, VOTER_NO_BATTERY, true, SEC_BAT_CHG_MODE_CHARGING_OFF); #else sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_USB_100MA, SEC_BAT_CURRENT_EVENT_USB_100MA); sec_vote(battery->input_vote, VOTER_USB_100MA, true, 100); sec_vote(battery->fcc_vote, VOTER_USB_100MA, true, 100); sec_vote(battery->topoff_vote, VOTER_FULL_CHARGE, true, battery->pdata->full_check_current_1st); sec_vote(battery->fv_vote, VOTER_FULL_CHARGE, true, battery->pdata->chg_float_voltage); #endif #if defined(CONFIG_BC12_DEVICE) #if defined(CONFIG_SEC_FACTORY) value.intval = 0; psy_do_property(battery->pdata->fuelgauge_name, get, POWER_SUPPLY_EXT_PROP_CHECK_INIT, value); battery->vbat_adc_open = (value.intval == 1) ? true : false; #endif value.intval = 0; psy_do_property(battery->pdata->charger_name, get, POWER_SUPPLY_EXT_PROP_CHECK_INIT, value); #endif #if IS_ENABLED(CONFIG_VBUS_NOTIFIER) && IS_ENABLED(CONFIG_LSI_IFPMIC) if (battery->pdata->support_vpdo) vbus_notifier_register(&battery->vbus_nb, vbus_handle_notification, VBUS_NOTIFY_DEV_BATTERY); #endif queue_delayed_work(battery->monitor_wqueue, &battery->afc_init_work, 0); if ((battery->wire_status == SEC_BATTERY_CABLE_NONE) || (battery->wire_status == SEC_BATTERY_CABLE_PREPARE_TA)) { __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); } pr_info("%s: End\n", __func__); __pm_relax(battery->dev_init_ws); } static int sb_handle_notification(struct notifier_block *nb, unsigned long action, void *data) { struct sec_battery_info *battery = container_of(nb, struct sec_battery_info, sb_nb); sb_data *sbd = data; int ret = 0; switch (action) { case SB_NOTIFY_DEV_PROBE: ret = sb_sysfs_create_attrs(&battery->psy_bat->dev); pr_info("%s: create sysfs node (name = %s, count = %d)\n", __func__, cast_sb_data_ptr(const char, sbd), ret); break; case SB_NOTIFY_DEV_LIST: { struct sbn_dev_list *tmp_list = cast_sb_data_ptr(struct sbn_dev_list, sbd); ret = sb_sysfs_create_attrs(&battery->psy_bat->dev); pr_info("%s: create sysfs node (dev_count = %d, sysfs_count = %d)\n", __func__, tmp_list->count, ret); } break; case SB_NOTIFY_EVENT_MISC: { struct sbn_bit_event *misc = cast_sb_data_ptr(struct sbn_bit_event, sbd); sec_bat_set_misc_event(battery, misc->value, misc->mask); } break; case SB_NOTIFY_DEV_SHUTDOWN: break; default: return -EINVAL; } return 0; } static const struct power_supply_desc battery_power_supply_desc = { .name = "battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = sec_battery_props, .num_properties = ARRAY_SIZE(sec_battery_props), .get_property = sec_bat_get_property, .set_property = sec_bat_set_property, }; static const struct power_supply_desc usb_power_supply_desc = { .name = "usb", .type = POWER_SUPPLY_TYPE_USB, .properties = sec_power_props, .num_properties = ARRAY_SIZE(sec_power_props), .get_property = sec_usb_get_property, }; static const struct power_supply_desc ac_power_supply_desc = { .name = "ac", .type = POWER_SUPPLY_TYPE_MAINS, .properties = sec_ac_props, .num_properties = ARRAY_SIZE(sec_ac_props), .get_property = sec_ac_get_property, }; static const struct power_supply_desc wireless_power_supply_desc = { .name = "wireless", .type = POWER_SUPPLY_TYPE_WIRELESS, .properties = sec_wireless_props, .num_properties = ARRAY_SIZE(sec_wireless_props), .get_property = sec_wireless_get_property, .set_property = sec_wireless_set_property, }; static const struct power_supply_desc pogo_power_supply_desc = { .name = "pogo", .type = POWER_SUPPLY_TYPE_UNKNOWN, .properties = sec_power_props, .num_properties = ARRAY_SIZE(sec_power_props), .get_property = sec_pogo_get_property, .set_property = sec_pogo_set_property, }; static const struct power_supply_desc otg_power_supply_desc = { .name = "otg", .type = POWER_SUPPLY_TYPE_UNKNOWN, .properties = sec_otg_props, .num_properties = ARRAY_SIZE(sec_otg_props), .get_property = sec_otg_get_property, .set_property = sec_otg_set_property, .external_power_changed = sec_otg_external_power_changed, }; static int sec_battery_probe(struct platform_device *pdev) { sec_battery_platform_data_t *pdata = NULL; struct sec_battery_info *battery; struct power_supply_config battery_cfg = {}; int ret = 0; dev_info(&pdev->dev, "%s: SEC Battery Driver Loading\n", __func__); battery = kzalloc(sizeof(*battery), GFP_KERNEL); if (!battery) return -ENOMEM; if (pdev->dev.of_node) { pdata = devm_kzalloc(&pdev->dev, sizeof(sec_battery_platform_data_t), GFP_KERNEL); if (!pdata) { dev_err(&pdev->dev, "Failed to allocate memory\n"); ret = -ENOMEM; goto err_bat_free; } battery->pdata = pdata; if (sec_bat_parse_dt(&pdev->dev, battery)) { dev_err(&pdev->dev, "%s: Failed to get battery dt\n", __func__); ret = -EINVAL; goto err_bat_free; } } else { pdata = dev_get_platdata(&pdev->dev); battery->pdata = pdata; } if (battery->pdata->soc_by_repcap_en) { battery->eoc_d = kzalloc(sizeof(struct sec_eoc_info), GFP_KERNEL); if (!battery->eoc_d) { ret = -EINVAL; goto err_pdata_free; } } platform_set_drvdata(pdev, battery); battery->dev = &pdev->dev; mutex_init(&battery->iolock); mutex_init(&battery->misclock); mutex_init(&battery->txeventlock); mutex_init(&battery->batt_handlelock); mutex_init(&battery->current_eventlock); mutex_init(&battery->typec_notylock); #if defined(CONFIG_MTK_CHARGER) && !defined(CONFIG_VIRTUAL_MUIC) mutex_init(&battery->bc12_notylock); #endif mutex_init(&battery->wclock); mutex_init(&battery->voutlock); dev_dbg(battery->dev, "%s: ADC init\n", __func__); adc_init(pdev, battery); battery->monitor_ws = wakeup_source_register(&pdev->dev, "sec-battery-monitor"); battery->cable_ws = wakeup_source_register(&pdev->dev, "sec-battery-cable"); battery->vbus_ws = wakeup_source_register(&pdev->dev, "sec-battery-vbus"); battery->input_ws = wakeup_source_register(&pdev->dev, "sec-battery-input"); battery->siop_level_ws = wakeup_source_register(&pdev->dev, "sec-battery-siop_level"); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) battery->ext_event_ws = wakeup_source_register(&pdev->dev, "sec-battery-ext_event"); battery->wpc_tx_ws = wakeup_source_register(&pdev->dev, "sec-battery-wcp-tx"); battery->wpc_tx_en_ws = wakeup_source_register(&pdev->dev, "sec-battery-wpc_tx_en"); battery->tx_event_ws = wakeup_source_register(&pdev->dev, "sec-battery-tx-event"); battery->wc20_current_ws = wakeup_source_register(&pdev->dev, "sec-battery-wc20-current"); battery->wc_ept_timeout_ws = wakeup_source_register(&pdev->dev, "sec-battery-ept-timeout"); #endif #if defined(CONFIG_UPDATE_BATTERY_DATA) battery->batt_data_ws = wakeup_source_register(&pdev->dev, "sec-battery-update-data"); #endif battery->misc_event_ws = wakeup_source_register(&pdev->dev, "sec-battery-misc-event"); battery->parse_mode_dt_ws = wakeup_source_register(&pdev->dev, "sec-battery-parse_mode_dt"); battery->dev_init_ws = wakeup_source_register(&pdev->dev, "sec-battery-dev-init"); /* create work queue */ battery->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); if (!battery->monitor_wqueue) { dev_err(battery->dev, "%s: Fail to Create Workqueue\n", __func__); goto err_irq; } INIT_DELAYED_WORK(&battery->monitor_work, sec_bat_monitor_work); INIT_DELAYED_WORK(&battery->cable_work, sec_bat_cable_work); INIT_DELAYED_WORK(&battery->slowcharging_work, sec_bat_check_slowcharging_work); INIT_DELAYED_WORK(&battery->input_check_work, sec_bat_input_check_work); INIT_DELAYED_WORK(&battery->siop_level_work, sec_bat_siop_level_work); #if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) INIT_DELAYED_WORK(&battery->fw_init_work, sec_bat_fw_init_work); #endif #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) INIT_DELAYED_WORK(&battery->wpc_tx_work, sec_bat_wpc_tx_work); INIT_DELAYED_WORK(&battery->wpc_tx_en_work, sec_bat_wpc_tx_en_work); INIT_DELAYED_WORK(&battery->wpc_txpower_calc_work, sec_bat_txpower_calc_work); INIT_DELAYED_WORK(&battery->ext_event_work, sec_bat_ext_event_work); INIT_DELAYED_WORK(&battery->otg_work, sec_bat_otg_work); INIT_DELAYED_WORK(&battery->wc20_current_work, sec_bat_wc20_current_work); INIT_DELAYED_WORK(&battery->wc_ept_timeout_work, sec_bat_wc_ept_timeout_work); #endif #if defined(CONFIG_UPDATE_BATTERY_DATA) INIT_DELAYED_WORK(&battery->batt_data_work, sec_bat_update_data_work); #endif INIT_DELAYED_WORK(&battery->misc_event_work, sec_bat_misc_event_work); INIT_DELAYED_WORK(&battery->parse_mode_dt_work, sec_bat_parse_mode_dt_work); INIT_DELAYED_WORK(&battery->dev_init_work, sec_batt_dev_init_work); INIT_DELAYED_WORK(&battery->afc_init_work, sec_bat_afc_init_work); battery->fcc_vote = sec_vote_init("FCC", SEC_VOTE_MIN, VOTER_MAX, 500, sec_voter_name, set_charging_current, battery); battery->input_vote = sec_vote_init("ICL", SEC_VOTE_MIN, VOTER_MAX, 500, sec_voter_name, set_input_current, battery); battery->fv_vote = sec_vote_init("FV", SEC_VOTE_MIN, VOTER_MAX, battery->pdata->chg_float_voltage, sec_voter_name, set_float_voltage, battery); battery->dc_fv_vote = sec_vote_init("DCFV", SEC_VOTE_MIN, VOTER_MAX, battery->pdata->chg_float_voltage, sec_voter_name, set_dc_float_voltage, battery); battery->chgen_vote = sec_vote_init("CHGEN", SEC_VOTE_MIN, VOTER_MAX, SEC_BAT_CHG_MODE_CHARGING_OFF, sec_voter_name, sec_bat_set_charge, battery); battery->topoff_vote = sec_vote_init("TOPOFF", SEC_VOTE_MIN, VOTER_MAX, battery->pdata->full_check_current_1st, sec_voter_name, set_topoff_current, battery); battery->iv_vote = sec_vote_init("IV", SEC_VOTE_MIN, VOTER_MAX, SEC_INPUT_VOLTAGE_5V, sec_voter_name, sec_bat_change_iv, battery); /* set vote priority */ change_sec_voter_pri(battery->iv_vote, VOTER_MUIC_ABNORMAL, VOTE_PRI_10); change_sec_voter_pri(battery->iv_vote, VOTER_MST, VOTE_PRI_10); change_sec_voter_pri(battery->iv_vote, VOTER_CHANGE_CHGMODE, VOTE_PRI_9); change_sec_voter_pri(battery->iv_vote, VOTER_WC_TX, VOTE_PRI_8); change_sec_voter_pri(battery->iv_vote, VOTER_AICL, VOTE_PRI_1); change_sec_voter_pri(battery->chgen_vote, VOTER_NO_BATTERY, VOTE_PRI_10); change_sec_voter_pri(battery->chgen_vote, VOTER_CHANGE_CHGMODE, VOTE_PRI_9); change_sec_voter_pri(battery->fv_vote, VOTER_SWELLING, VOTE_PRI_10); change_sec_voter_pri(battery->dc_fv_vote, VOTER_SWELLING, VOTE_PRI_10); switch (pdata->polling_type) { case SEC_BATTERY_MONITOR_WORKQUEUE: INIT_DELAYED_WORK(&battery->polling_work, sec_bat_polling_work); break; case SEC_BATTERY_MONITOR_ALARM: battery->last_poll_time = ktime_get_boottime(); alarm_init(&battery->polling_alarm, ALARM_BOOTTIME, sec_bat_alarm); break; default: break; } battery->pogo_status = 0; battery->pogo_9v = false; battery->ta_alert_mode = OCP_NONE; battery->present = true; battery->is_jig_on = false; battery->wdt_kick_disable = 0; battery->polling_count = 1; /* initial value = 1 */ battery->polling_time = pdata->polling_time[ SEC_BATTERY_POLLING_TIME_DISCHARGING]; battery->polling_in_sleep = false; battery->polling_short = false; battery->check_count = 0; battery->check_adc_count = 0; battery->check_adc_value = 0; battery->input_current = 0; battery->charging_current = 0; #if IS_ENABLED(CONFIG_DUAL_BATTERY) battery->main_current = 0; battery->sub_current = 0; battery->limiter_check = false; #endif battery->topoff_condition = 0; battery->wpc_vout_level = WIRELESS_VOUT_10V; battery->wpc_max_vout_level = WIRELESS_VOUT_12_5V; battery->charging_retention_time = 0; battery->charging_start_time = 0; battery->charging_passed_time = 0; battery->wc_heating_start_time = 0; battery->wc_heating_passed_time = 0; battery->charging_next_time = 0; battery->charging_fullcharged_time = 0; battery->siop_level = 100; battery->wc_enable = true; battery->wc_enable_cnt = 0; battery->wc_enable_cnt_value = 3; battery->stability_test = 0; battery->eng_not_full_status = 0; #if defined(CONFIG_ENG_BATTERY_CONCEPT) #if defined(CONFIG_STEP_CHARGING) battery->test_step_condition = 0x7FFF; #endif battery->test_max_current = false; battery->test_charge_current = false; #endif battery->wc_status = SEC_BATTERY_CABLE_NONE; battery->wc_cv_mode = false; battery->wire_status = SEC_BATTERY_CABLE_NONE; #if defined(CONFIG_MTK_CHARGER) && !defined(CONFIG_VIRTUAL_MUIC) battery->bc12_cable = SEC_BATTERY_CABLE_NONE; #endif battery->wc_rx_phm_mode = false; battery->wc_tx_phm_mode = false; battery->wc_tx_enable = false; battery->uno_en = false; battery->afc_disable = false; battery->pd_disable = false; battery->buck_cntl_by_tx = false; battery->wc_tx_vout = WC_TX_VOUT_5000MV; battery->wc_rx_type = NO_DEV; battery->tx_mfc_iout = 0; battery->tx_uno_iout = 0; battery->wc_need_ldo_on = false; battery->tx_minduty = battery->pdata->tx_minduty_default; battery->chg_limit = false; battery->lrp_limit = false; battery->lrp_step = LRP_NONE; battery->mix_limit = false; battery->usb_temp = 0; battery->dchg_temp = 0; battery->blkt_temp = 0; battery->lrp = 0; battery->lrp_test = 0; #if IS_ENABLED(CONFIG_DIRECT_CHARGING) battery->lrp_chg_src = SEC_CHARGING_SOURCE_DIRECT; #endif battery->skip_swelling = false; battery->wpc_temp_v2_offset = 0; battery->bat_thm_count = 0; battery->adc_init_count = 0; battery->led_cover = 0; battery->hiccup_status = 0; battery->hiccup_clear = false; battery->ext_event = BATT_EXT_EVENT_NONE; battery->tx_retry_case = SEC_BAT_TX_RETRY_NONE; battery->tx_misalign_cnt = 0; battery->tx_ocp_cnt = 0; battery->auto_mode = false; battery->update_pd_list = false; battery->charging_mode = SEC_BATTERY_CHARGING_NONE; battery->is_recharging = false; battery->cable_type = SEC_BATTERY_CABLE_NONE; battery->test_mode = 0; battery->factory_mode = false; battery->display_test = false; battery->store_mode = false; battery->prev_usb_conf = USB_CURRENT_NONE; battery->is_hc_usb = false; battery->is_sysovlo = false; battery->is_vbatovlo = false; battery->is_abnormal_temp = false; battery->hv_pdo = false; battery->safety_timer_set = true; battery->stop_timer = false; battery->prev_safety_time = 0; battery->lcd_status = true; battery->wc_auth_retried = false; #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) battery->wc_ept_timeout = false; battery->wc20_power_class = 0; battery->wc20_vout = 0; battery->wc20_rx_power = 0; battery->wc20_info_idx = 0; #endif battery->thermal_zone = BAT_THERMAL_NORMAL; sec_bat_set_threshold(battery, battery->cable_type); #if defined(CONFIG_BATTERY_CISD) battery->usb_overheat_check = false; battery->skip_cisd = false; #endif #if defined(CONFIG_BATTERY_AGE_FORECAST) battery->batt_cycle = -1; battery->pdata->age_step = 0; #endif battery->batt_asoc = 100; battery->health_change = false; battery->usb_thm_status = USB_THM_NORMAL; battery->batt_full_capacity = 0; battery->lrp_temp = 0x7FFF; battery->lr_start_time = 0; battery->lr_time_span = 0; battery->usb_slow_chg = false; battery->usb_bootcomplete = false; #if defined(CONFIG_MTK_CHARGER) battery->mtk_fg_init = false; #endif battery->d2d_auth = D2D_AUTH_NONE; battery->vpdo_src_boost = false; battery->vpdo_ocp = false; battery->vpdo_auth_stat = AUTH_NONE; battery->hp_d2d = HP_D2D_NONE; if (battery->pdata->soc_by_repcap_en) { battery->eoc_d->eoc_check = false; battery->eoc_d->eoc_cnt = 0; } ttf_init(battery); #if defined(CONFIG_BATTERY_CISD) sec_battery_cisd_init(battery); #endif #if defined(CONFIG_STORE_MODE) && !defined(CONFIG_SEC_FACTORY) battery->store_mode = true; sec_bat_parse_mode_dt(battery); #endif #if defined(CONFIG_WIRELESS_AUTH) sec_bat_misc_init(battery); #endif #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) if ((battery->pdata->charging_current[SEC_BATTERY_CABLE_WIRELESS].input_current_limit <= battery->pdata->wpc_input_limit_current) || (battery->pdata->charging_current[SEC_BATTERY_CABLE_WIRELESS].input_current_limit <= battery->pdata->wpc_lcd_on_input_limit_current)) battery->nv_wc_temp_ctrl_skip = true; else battery->nv_wc_temp_ctrl_skip = false; #endif if (battery->pdata->charger_name == NULL) battery->pdata->charger_name = "sec-charger"; if (battery->pdata->fuelgauge_name == NULL) battery->pdata->fuelgauge_name = "sec-fuelgauge"; if (battery->pdata->check_battery_callback) battery->present = battery->pdata->check_battery_callback(); if (sec_bat_get_fgreset()) sec_bat_set_current_event(battery, SEC_BAT_CURRENT_EVENT_FG_RESET, SEC_BAT_CURRENT_EVENT_FG_RESET); battery_cfg.drv_data = battery; /* init power supplier framework */ battery->psy_usb = power_supply_register(&pdev->dev, &usb_power_supply_desc, &battery_cfg); if (IS_ERR(battery->psy_usb)) { ret = PTR_ERR(battery->psy_usb); dev_err(battery->dev, "%s: Failed to Register psy_usb(%d)\n", __func__, ret); goto err_workqueue; } battery->psy_usb->supplied_to = supply_list; battery->psy_usb->num_supplicants = ARRAY_SIZE(supply_list); battery->psy_ac = power_supply_register(&pdev->dev, &ac_power_supply_desc, &battery_cfg); if (IS_ERR(battery->psy_ac)) { ret = PTR_ERR(battery->psy_ac); dev_err(battery->dev, "%s: Failed to Register psy_ac(%d)\n", __func__, ret); goto err_supply_unreg_usb; } battery->psy_ac->supplied_to = supply_list; battery->psy_ac->num_supplicants = ARRAY_SIZE(supply_list); battery->psy_bat = power_supply_register(&pdev->dev, &battery_power_supply_desc, &battery_cfg); if (IS_ERR(battery->psy_bat)) { ret = PTR_ERR(battery->psy_bat); dev_err(battery->dev, "%s: Failed to Register psy_bat(%d)\n", __func__, ret); goto err_supply_unreg_ac; } battery->psy_pogo = power_supply_register(&pdev->dev, &pogo_power_supply_desc, &battery_cfg); if (IS_ERR(battery->psy_pogo)) { ret = PTR_ERR(battery->psy_pogo); dev_err(battery->dev, "%s: Failed to Register psy_pogo(%d)\n", __func__, ret); goto err_supply_unreg_bat; } battery->psy_wireless = power_supply_register(&pdev->dev, &wireless_power_supply_desc, &battery_cfg); if (IS_ERR(battery->psy_wireless)) { ret = PTR_ERR(battery->psy_wireless); dev_err(battery->dev, "%s: Failed to Register psy_wireless(%d)\n", __func__, ret); goto err_supply_unreg_pogo; } battery->psy_wireless->supplied_to = supply_list; battery->psy_wireless->num_supplicants = ARRAY_SIZE(supply_list); battery->psy_otg = power_supply_register(&pdev->dev, &otg_power_supply_desc, &battery_cfg); if (IS_ERR(battery->psy_otg)) { ret = PTR_ERR(battery->psy_otg); dev_err(battery->dev, "%s: Failed to Register psy_otg(%d)\n", __func__, ret); goto err_supply_unreg_wireless; } if (device_create_file(&battery->psy_wireless->dev, &dev_attr_sgf)) dev_err(battery->dev, "%s: failed to create sgf attr\n", __func__); ret = sec_bat_create_attrs(&battery->psy_bat->dev); if (ret) { dev_err(battery->dev, "%s : Failed to sec_bat_create_attrs\n", __func__); goto err_req_irq; } ret = sec_pogo_create_attrs(&battery->psy_pogo->dev); if (ret) { dev_err(battery->dev, "%s : Failed to sec_pogo_create_attrs\n", __func__); goto err_req_irq; } ret = sec_otg_create_attrs(&battery->psy_otg->dev); if (ret) { dev_err(battery->dev, "%s : Failed to sec_otg_create_attrs\n", __func__); goto err_req_irq; } ret = sb_notify_register(&battery->sb_nb, sb_handle_notification, "battery", SB_DEV_BATTERY); dev_info(battery->dev, "%s: sb_notify_register (%s)\n", __func__, (ret) ? "fail" : "success"); sb_ca_init(battery->dev); sb_bd_init(); __pm_stay_awake(battery->dev_init_ws); queue_delayed_work(battery->monitor_wqueue, &battery->dev_init_work, 0); pr_info("%s: SEC Battery Driver Loaded\n", __func__); return 0; err_req_irq: power_supply_unregister(battery->psy_otg); err_supply_unreg_wireless: power_supply_unregister(battery->psy_wireless); err_supply_unreg_pogo: power_supply_unregister(battery->psy_pogo); err_supply_unreg_bat: power_supply_unregister(battery->psy_bat); err_supply_unreg_ac: power_supply_unregister(battery->psy_ac); err_supply_unreg_usb: power_supply_unregister(battery->psy_usb); err_workqueue: destroy_workqueue(battery->monitor_wqueue); err_irq: wakeup_source_unregister(battery->monitor_ws); wakeup_source_unregister(battery->cable_ws); wakeup_source_unregister(battery->vbus_ws); wakeup_source_unregister(battery->input_ws); wakeup_source_unregister(battery->siop_level_ws); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) wakeup_source_unregister(battery->ext_event_ws); wakeup_source_unregister(battery->wpc_tx_ws); wakeup_source_unregister(battery->wpc_tx_en_ws); wakeup_source_unregister(battery->tx_event_ws); wakeup_source_unregister(battery->wc20_current_ws); wakeup_source_unregister(battery->wc_ept_timeout_ws); #endif #if defined(CONFIG_UPDATE_BATTERY_DATA) wakeup_source_unregister(battery->batt_data_ws); #endif wakeup_source_unregister(battery->misc_event_ws); wakeup_source_unregister(battery->parse_mode_dt_ws); wakeup_source_unregister(battery->dev_init_ws); mutex_destroy(&battery->iolock); mutex_destroy(&battery->misclock); mutex_destroy(&battery->txeventlock); mutex_destroy(&battery->batt_handlelock); mutex_destroy(&battery->current_eventlock); mutex_destroy(&battery->typec_notylock); #if defined(CONFIG_MTK_CHARGER) && !defined(CONFIG_VIRTUAL_MUIC) mutex_destroy(&battery->bc12_notylock); #endif mutex_destroy(&battery->wclock); mutex_destroy(&battery->voutlock); err_pdata_free: if (battery->pdata->soc_by_repcap_en) kfree(pdata); err_bat_free: kfree(battery); return ret; } static int sec_battery_remove(struct platform_device *pdev) { struct sec_battery_info *battery = platform_get_drvdata(pdev); pr_info("%s: ++\n", __func__); switch (battery->pdata->polling_type) { case SEC_BATTERY_MONITOR_WORKQUEUE: cancel_delayed_work(&battery->polling_work); break; case SEC_BATTERY_MONITOR_ALARM: alarm_cancel(&battery->polling_alarm); break; default: break; } unregister_batterylog_proc(); flush_workqueue(battery->monitor_wqueue); destroy_workqueue(battery->monitor_wqueue); wakeup_source_unregister(battery->monitor_ws); wakeup_source_unregister(battery->cable_ws); wakeup_source_unregister(battery->vbus_ws); wakeup_source_unregister(battery->input_ws); wakeup_source_unregister(battery->siop_level_ws); wakeup_source_unregister(battery->misc_event_ws); #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) wakeup_source_unregister(battery->ext_event_ws); wakeup_source_unregister(battery->wpc_tx_ws); wakeup_source_unregister(battery->wpc_tx_en_ws); wakeup_source_unregister(battery->tx_event_ws); wakeup_source_unregister(battery->wc20_current_ws); wakeup_source_unregister(battery->wc_ept_timeout_ws); #endif #if defined(CONFIG_UPDATE_BATTERY_DATA) wakeup_source_unregister(battery->batt_data_ws); #endif wakeup_source_unregister(battery->parse_mode_dt_ws); wakeup_source_unregister(battery->dev_init_ws); mutex_destroy(&battery->iolock); mutex_destroy(&battery->misclock); mutex_destroy(&battery->txeventlock); mutex_destroy(&battery->batt_handlelock); mutex_destroy(&battery->current_eventlock); mutex_destroy(&battery->typec_notylock); #if defined(CONFIG_MTK_CHARGER) && !defined(CONFIG_VIRTUAL_MUIC) mutex_destroy(&battery->bc12_notylock); #endif mutex_destroy(&battery->wclock); mutex_destroy(&battery->voutlock); adc_exit(battery); power_supply_unregister(battery->psy_otg); power_supply_unregister(battery->psy_wireless); power_supply_unregister(battery->psy_pogo); power_supply_unregister(battery->psy_ac); power_supply_unregister(battery->psy_usb); power_supply_unregister(battery->psy_bat); kfree(battery); pr_info("%s: --\n", __func__); return 0; } static int sec_battery_prepare(struct device *dev) { struct sec_battery_info *battery = dev_get_drvdata(dev); dev_info(battery->dev, "%s: Start\n", __func__); switch (battery->pdata->polling_type) { case SEC_BATTERY_MONITOR_WORKQUEUE: cancel_delayed_work(&battery->polling_work); break; case SEC_BATTERY_MONITOR_ALARM: alarm_cancel(&battery->polling_alarm); break; default: break; } /* monitor_ws should be unlocked before cancel monitor_work */ __pm_relax(battery->monitor_ws); cancel_delayed_work_sync(&battery->monitor_work); battery->polling_in_sleep = true; sec_bat_set_polling(battery); /* * cancel work for polling * that is set in sec_bat_set_polling() * no need for polling in sleep */ if (battery->pdata->polling_type == SEC_BATTERY_MONITOR_WORKQUEUE) cancel_delayed_work(&battery->polling_work); dev_info(battery->dev, "%s: End\n", __func__); return 0; } static int sec_battery_suspend(struct device *dev) { return 0; } static int sec_battery_resume(struct device *dev) { return 0; } static void sec_battery_complete(struct device *dev) { struct sec_battery_info *battery = dev_get_drvdata(dev); dev_info(battery->dev, "%s: Start\n", __func__); /* cancel current alarm and reset after monitor work */ if (battery->pdata->polling_type == SEC_BATTERY_MONITOR_ALARM) alarm_cancel(&battery->polling_alarm); __pm_stay_awake(battery->monitor_ws); queue_delayed_work(battery->monitor_wqueue, &battery->monitor_work, 0); dev_info(battery->dev, "%s: End\n", __func__); return; } static void sec_battery_shutdown(struct platform_device *pdev) { struct sec_battery_info *battery = platform_get_drvdata(pdev); pr_info("%s: ++\n", __func__); switch (battery->pdata->polling_type) { case SEC_BATTERY_MONITOR_WORKQUEUE: cancel_delayed_work(&battery->polling_work); break; case SEC_BATTERY_MONITOR_ALARM: alarm_cancel(&battery->polling_alarm); break; default: break; } cancel_delayed_work(&battery->monitor_work); cancel_delayed_work(&battery->cable_work); cancel_delayed_work(&battery->slowcharging_work); cancel_delayed_work(&battery->input_check_work); cancel_delayed_work(&battery->siop_level_work); #if defined(CONFIG_WIRELESS_FIRMWARE_UPDATE) cancel_delayed_work(&battery->fw_init_work); #endif #if IS_ENABLED(CONFIG_WIRELESS_CHARGING) cancel_delayed_work(&battery->wpc_tx_work); cancel_delayed_work(&battery->wpc_tx_en_work); cancel_delayed_work(&battery->wpc_txpower_calc_work); cancel_delayed_work(&battery->ext_event_work); cancel_delayed_work(&battery->otg_work); cancel_delayed_work(&battery->wc20_current_work); cancel_delayed_work(&battery->wc_ept_timeout_work); #endif #if defined(CONFIG_UPDATE_BATTERY_DATA) cancel_delayed_work(&battery->batt_data_work); #endif cancel_delayed_work(&battery->misc_event_work); cancel_delayed_work(&battery->parse_mode_dt_work); cancel_delayed_work(&battery->dev_init_work); cancel_delayed_work(&battery->afc_init_work); pr_info("%s: --\n", __func__); } #ifdef CONFIG_OF static struct of_device_id sec_battery_dt_ids[] = { { .compatible = "samsung,sec-battery" }, { } }; MODULE_DEVICE_TABLE(of, sec_battery_dt_ids); #endif /* CONFIG_OF */ static const struct dev_pm_ops sec_battery_pm_ops = { .prepare = sec_battery_prepare, .suspend = sec_battery_suspend, .resume = sec_battery_resume, .complete = sec_battery_complete, }; static struct platform_driver sec_battery_driver = { .driver = { .name = "sec-battery", .owner = THIS_MODULE, .pm = &sec_battery_pm_ops, #ifdef CONFIG_OF .of_match_table = sec_battery_dt_ids, #endif }, .probe = sec_battery_probe, .remove = sec_battery_remove, .shutdown = sec_battery_shutdown, }; static int __init sec_battery_init(void) { sec_chg_init_gdev(); return platform_driver_register(&sec_battery_driver); } static void __exit sec_battery_exit(void) { platform_driver_unregister(&sec_battery_driver); } late_initcall(sec_battery_init); module_exit(sec_battery_exit); MODULE_DESCRIPTION("Samsung Battery Driver"); MODULE_AUTHOR("Samsung Electronics"); MODULE_LICENSE("GPL");