/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2021 MediaTek Inc. */ #include "mtk_pep20_intf.h" #include #include #include #include #include #include #if !defined(TA_AC_CHARGING_CURRENT) #include #endif #ifdef CONFIG_MTK_PUMP_EXPRESS_PLUS_20_SUPPORT static struct mutex pep20_access_lock; static struct mutex pep20_pmic_sync_lock; static struct wake_lock pep20_suspend_lock; static int pep20_ta_vchr_org = 5000; /* mA */ static int pep20_idx = -1; static int pep20_vbus = 5000; /* mA */ static bool pep20_to_check_chr_type = true; static bool pep20_is_cable_out_occur; /* Plug out happened while detecting PE+20 */ static bool pep20_is_connect; static bool pep20_is_enabled = true; static struct pep20_profile_t pep20_profile[] = { {3400, VBAT3400_VBUS}, {3500, VBAT3500_VBUS}, {3600, VBAT3600_VBUS}, {3700, VBAT3700_VBUS}, {3800, VBAT3800_VBUS}, {3900, VBAT3900_VBUS}, {4000, VBAT4000_VBUS}, {4100, VBAT4100_VBUS}, {4200, VBAT4200_VBUS}, {4300, VBAT4300_VBUS}, }; static int pep20_enable_hw_vbus_ovp(bool enable) { int ret = 0; u32 data; data = (enable ? 1 : 0); /* Compatible with charging_hw_bq25896.c */ ret = battery_charging_control(CHARGING_CMD_SET_VBUS_OVP_EN, &data); if (ret < 0) battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); return ret; } /* Enable/Disable HW & SW VBUS OVP */ static int pep20_enable_vbus_ovp(bool enable) { int ret = 0; u32 sw_ovp = (enable ? V_CHARGER_MAX : 15000); /* Enable/Disable HW(PMIC) OVP */ ret = pep20_enable_hw_vbus_ovp(enable); if (ret < 0) { battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); return ret; } /* Enable/Disable SW OVP status */ batt_cust_data.v_charger_max = sw_ovp; return ret; } static int pep20_set_mivr(u32 mivr) { int ret = 0; ret = battery_charging_control(CHARGING_CMD_SET_VINDPM, &mivr); if (ret < 0) battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); return ret; } static int pep20_leave(void) { int ret = 0; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); ret = mtk_pep20_reset_ta_vchr(); if (ret < 0 || pep20_is_connect) { battery_log(BAT_LOG_CRTI, "%s: failed, is_connect = %d, ret = %d\n", __func__, pep20_is_connect, ret); return ret; } battery_log(BAT_LOG_CRTI, "%s: OK\n", __func__); return ret; } static int pep20_check_leave_status(void) { int ret = 0; u32 ichg = 0, vchr = 0; kal_bool current_sign; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); /* PE+ leaves unexpectedly */ vchr = battery_meter_get_charger_voltage(); if (abs(vchr - pep20_ta_vchr_org) < 1000) { battery_log(BAT_LOG_CRTI, "%s: PE+20 leave unexpectedly, recheck TA\n", __func__); pep20_to_check_chr_type = true; ret = pep20_leave(); if (ret < 0 || pep20_is_connect) goto _err; return ret; } ichg = battery_meter_get_battery_current(); /* 0.1 mA */ ichg /= 10; /* mA */ current_sign = battery_meter_get_battery_current_sign(); /* Check SOC & Ichg */ if (BMT_status.SOC > batt_cust_data.ta_stop_battery_soc && current_sign && ichg < PEP20_ICHG_LEAVE_THRESHOLD) { ret = pep20_leave(); if (ret < 0 || pep20_is_connect) goto _err; battery_log(BAT_LOG_CRTI, "%s: OK, SOC = (%d,%d), Ichg = %dmA, stop PE+20\n", __func__, BMT_status.SOC, batt_cust_data.ta_stop_battery_soc, ichg); } return ret; _err: battery_log(BAT_LOG_CRTI, "%s: failed, is_connect = %d, ret = %d\n", __func__, pep20_is_connect, ret); return ret; } static int __pep20_set_ta_vchr(u32 chr_volt) { int ret = 0; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); /* Not to set chr volt if cable is plugged out */ if (pep20_is_cable_out_occur) { battery_log(BAT_LOG_CRTI, "%s: failed, cable out\n", __func__); return -EIO; } ret = battery_charging_control(CHARGING_CMD_SET_TA20_CURRENT_PATTERN, &chr_volt); if (ret < 0) { battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); return ret; } battery_log(BAT_LOG_CRTI, "%s: OK\n", __func__); return ret; } static int pep20_set_ta_vchr(u32 chr_volt) { int ret = 0; int vchr_before, vchr_after, vchr_delta; const u32 sw_retry_cnt_max = 3; const u32 retry_cnt_max = 5; u32 sw_retry_cnt = 0, retry_cnt = 0; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); do { vchr_before = battery_meter_get_charger_voltage(); ret = __pep20_set_ta_vchr(chr_volt); vchr_after = battery_meter_get_charger_voltage(); vchr_delta = abs(vchr_after - chr_volt); /* It is successful * if difference to target is less than 500mA */ if (vchr_delta < 500 && ret == 0) { battery_log( BAT_LOG_CRTI, "%s: OK, vchr = (%d, %d), vchr_target = %d\n", __func__, vchr_before, vchr_after, chr_volt); return ret; } if (ret == 0 || sw_retry_cnt >= sw_retry_cnt_max) retry_cnt++; else { msleep(2000); sw_retry_cnt++; } battery_log( BAT_LOG_CRTI, "%s: retry_cnt = (%d, %d), vchr = (%d, %d), vchr_target = %d\n", __func__, sw_retry_cnt, retry_cnt, vchr_before, vchr_after, chr_volt); } while (!pep20_is_cable_out_occur && BMT_status.charger_exist == KAL_TRUE && retry_cnt < retry_cnt_max && mtk_chr_is_hv_charging_enable()); ret = -EIO; battery_log( BAT_LOG_CRTI, "%s: failed, vchr_org = %d, vchr_after = %d, target_vchr = %d\n", __func__, pep20_ta_vchr_org, vchr_after, chr_volt); return ret; } static int pep20_detect_ta(void) { int ret; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); pep20_ta_vchr_org = battery_meter_get_charger_voltage(); /* Disable OVP */ ret = pep20_enable_vbus_ovp(false); if (ret < 0) goto _err; if (abs(pep20_ta_vchr_org - 8500) > 500) ret = pep20_set_ta_vchr(8500); else ret = pep20_set_ta_vchr(6500); if (ret < 0) { pep20_to_check_chr_type = false; goto _err; } pep20_is_connect = true; mt_charger_enable_DP_voltage(1); battery_log(BAT_LOG_CRTI, "%s: OK\n", __func__); return ret; _err: pep20_is_connect = false; pep20_enable_vbus_ovp(true); battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); return ret; } int pep20_plugout_reset(void) { int ret = 0; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); pep20_to_check_chr_type = true; ret = mtk_pep20_reset_ta_vchr(); if (ret < 0) goto _err; mt_charger_enable_DP_voltage(0); mtk_pep20_set_is_cable_out_occur(false); battery_log(BAT_LOG_CRTI, "%s: OK\n", __func__); return ret; _err: battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); return ret; } static int pep20_init_ta(void) { int ret = 0; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); ret = battery_charging_control(CHARGING_CMD_INIT, NULL); if (ret < 0) { battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); return ret; } battery_log(BAT_LOG_CRTI, "%s: OK\n", __func__); return ret; } int mtk_pep20_set_charging_current(CHR_CURRENT_ENUM *ichg, CHR_CURRENT_ENUM *aicr) { int ret = 0; if (!pep20_is_connect) return -ENOTSUPP; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); *aicr = CHARGE_CURRENT_3200_00_MA; *ichg = TA_AC_CHARGING_CURRENT; battery_log(BAT_LOG_CRTI, "%s: OK, ichg = %dmA, AICR = %dmA\n", __func__, *ichg / 100, *aicr / 100); return ret; } int mtk_pep20_init(void) { wake_lock_init(&pep20_suspend_lock, WAKE_LOCK_SUSPEND, "PE+20 TA charger suspend wakelock"); mutex_init(&pep20_access_lock); mutex_init(&pep20_pmic_sync_lock); battery_charging_control(CHARGING_CMD_SET_PEP20_EFFICIENCY_TABLE, pep20_profile); return 0; } int mtk_pep20_reset_ta_vchr(void) { int ret = 0, chr_volt = 0; u32 retry_cnt = 0; battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); /* Reset TA's charging voltage */ do { ret = battery_charging_control(CHARGING_CMD_SET_TA20_RESET, NULL); msleep(250); /* Check charger's voltage */ chr_volt = battery_meter_get_charger_voltage(); if (abs(chr_volt - pep20_ta_vchr_org) <= 1000) { pep20_vbus = chr_volt; pep20_idx = -1; pep20_is_connect = false; break; } retry_cnt++; } while (retry_cnt < 3); if (pep20_is_connect) { battery_log(BAT_LOG_CRTI, "%s: failed, ret = %d\n", __func__, ret); /* * SET_TA20_RESET success but chr_volt does not reset to 5V * set ret = -EIO to represent the case */ if (ret == 0) ret = -EIO; return ret; } pep20_enable_vbus_ovp(true); pep20_set_mivr(4500); battery_log(BAT_LOG_CRTI, "%s: OK\n", __func__); return ret; } int mtk_pep20_check_charger(void) { int ret = 0; if (!mtk_chr_is_hv_charging_enable()) { pr_info("%s: hv charging is disabled\n", __func__); if (pep20_is_connect) { pep20_leave(); pep20_to_check_chr_type = true; } return ret; } if (!pep20_is_enabled) { battery_log(BAT_LOG_CRTI, "%s: stop, PE+20 is disabled\n", __func__); return ret; } mutex_lock(&pep20_access_lock); wake_lock(&pep20_suspend_lock); battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); if (!BMT_status.charger_exist || pep20_is_cable_out_occur) pep20_plugout_reset(); /* * Not to check charger type or * Not standard charger or * SOC is not in range */ if (!pep20_to_check_chr_type || BMT_status.charger_type != STANDARD_CHARGER || BMT_status.SOC < batt_cust_data.ta_start_battery_soc || BMT_status.SOC >= batt_cust_data.ta_stop_battery_soc) goto _out; ret = pep20_init_ta(); if (ret < 0) goto _out; ret = mtk_pep20_reset_ta_vchr(); if (ret < 0) goto _out; ret = pep20_detect_ta(); if (ret < 0) goto _out; pep20_to_check_chr_type = false; battery_log(BAT_LOG_CRTI, "%s: OK, to_check_chr_type = %d\n", __func__, pep20_to_check_chr_type); wake_unlock(&pep20_suspend_lock); mutex_unlock(&pep20_access_lock); return ret; _out: battery_log( BAT_LOG_CRTI, "%s: stop, SOC = (%d, %d, %d), to_check_chr_type = %d, chr_type = %d, ret = %d\n", __func__, BMT_status.SOC, batt_cust_data.ta_start_battery_soc, batt_cust_data.ta_stop_battery_soc, pep20_to_check_chr_type, BMT_status.charger_type, ret); wake_unlock(&pep20_suspend_lock); mutex_unlock(&pep20_access_lock); return ret; } int mtk_pep20_start_algorithm(void) { int ret = 0; int i; int vbat, vbus, ichg; int pre_vbus, pre_idx; int tune = 0, pes = 0; /* For log, to know the state of PE+20 */ kal_bool current_sign; u32 size; if (!mtk_chr_is_hv_charging_enable()) { pr_info("%s: hv charging is disabled\n", __func__); if (pep20_is_connect) { pep20_leave(); pep20_to_check_chr_type = true; } return ret; } if (!pep20_is_enabled) { battery_log(BAT_LOG_CRTI, "%s: stop, PE+20 is disabled\n", __func__); return ret; } mutex_lock(&pep20_access_lock); wake_lock(&pep20_suspend_lock); battery_log(BAT_LOG_FULL, "%s: starts\n", __func__); if (!BMT_status.charger_exist || pep20_is_cable_out_occur) pep20_plugout_reset(); if (!pep20_is_connect) { ret = -EIO; battery_log(BAT_LOG_CRTI, "%s: stop, PE+20 is not connected\n", __func__); wake_unlock(&pep20_suspend_lock); mutex_unlock(&pep20_access_lock); return ret; } vbat = battery_meter_get_battery_voltage(KAL_FALSE); vbus = battery_meter_get_charger_voltage(); ichg = battery_meter_get_battery_current(); current_sign = battery_meter_get_battery_current_sign(); pre_vbus = pep20_vbus; pre_idx = pep20_idx; ret = pep20_check_leave_status(); if (!pep20_is_connect || ret < 0) { pes = 1; goto _out; } size = ARRAY_SIZE(pep20_profile); for (i = 0; i < size; i++) { tune = 0; /* Exceed this level, check next level */ if (vbat > (pep20_profile[i].vbat + 100)) continue; /* If vbat is still 30mV larger than the lower level * Do not down grade */ if (i < pep20_idx && vbat > (pep20_profile[i].vbat + 30)) continue; if (pep20_vbus != pep20_profile[i].vchr) tune = 1; pep20_vbus = pep20_profile[i].vchr; pep20_idx = i; if (abs(vbus - pep20_vbus) >= 1000) tune = 2; if (tune != 0) { ret = pep20_set_ta_vchr(pep20_vbus); if (ret < 0) pep20_leave(); else pep20_set_mivr(pep20_vbus - 1000); } break; } pes = 2; _out: battery_log(BAT_LOG_CRTI, "%s: vbus = (%d, %d), idx = (%d, %d), I = (%d, %d)\n", __func__, pre_vbus, pep20_vbus, pre_idx, pep20_idx, (int)current_sign, (int)ichg / 10); battery_log( BAT_LOG_CRTI, "%s: SOC = %d, is_connect = %d, tune = %d, pes = %d, vbat = %d, ret = %d\n", __func__, BMT_status.SOC, pep20_is_connect, tune, pes, vbat, ret); wake_unlock(&pep20_suspend_lock); mutex_unlock(&pep20_access_lock); return ret; } void mtk_pep20_set_to_check_chr_type(bool check) { mutex_lock(&pep20_access_lock); wake_lock(&pep20_suspend_lock); battery_log(BAT_LOG_CRTI, "%s: check = %d\n", __func__, check); pep20_to_check_chr_type = check; wake_unlock(&pep20_suspend_lock); mutex_unlock(&pep20_access_lock); } void mtk_pep20_set_is_enable(bool enable) { mutex_lock(&pep20_access_lock); wake_lock(&pep20_suspend_lock); battery_log(BAT_LOG_CRTI, "%s: enable = %d\n", __func__, enable); pep20_is_enabled = enable; wake_unlock(&pep20_suspend_lock); mutex_unlock(&pep20_access_lock); } void mtk_pep20_set_is_cable_out_occur(bool out) { battery_log(BAT_LOG_CRTI, "%s: out = %d\n", __func__, out); mutex_lock(&pep20_pmic_sync_lock); pep20_is_cable_out_occur = out; mutex_unlock(&pep20_pmic_sync_lock); } bool mtk_pep20_get_to_check_chr_type(void) { return pep20_to_check_chr_type; } bool mtk_pep20_get_is_connect(void) { /* * Cable out is occurred, * but not execute plugout_reset yet */ if (pep20_is_cable_out_occur) return false; return pep20_is_connect; } bool mtk_pep20_get_is_enable(void) { return pep20_is_enabled; } #endif