/* SPDX-License-Identifier: GPL-2.0 */ /* * Copyright (c) 2021 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #define PCA_DV2_ALGO_VERSION "2.0.2_G" #define MS_TO_NS(msec) ((msec) * 1000 * 1000) #define PRECISION_ENHANCE 5 /* Parameters */ #define DV2_VTA_INIT 5000 /* mV */ #define DV2_ITA_INIT 3000 /* mA */ #define DV2_TA_WDT_MIN 10000 /* ms */ #define DV2_VTA_GAP_MIN 200 /* mV */ #define DV2_VTA_VAR_MIN 103 /* % */ #define DV2_ITA_TRACKING_GAP 150 /* mA */ #define DV2_ITA_GAP_WINDOW_SIZE 50 #define DV2_DVCHG_VBUSALM_GAP 100 /* mV */ #define DV2_DVCHG_STARTUP_CONVERT_RATIO 210 /* % */ #define DV2_DVCHG_CHARGING_CONVERT_RATIO 202 /* % */ #define DV2_VBUSOVP_RATIO 110 #define DV2_IBUSOCP_RATIO 110 #define DV2_VBATOVP_RATIO 110 #define DV2_IBATOCP_RATIO 110 #define DV2_ITAOCP_RATIO 110 #define DV2_IBUSUCPF_RECHECK 250 /* mA */ #define DV2_VBUS_CALI_THRESHOLD 150 /* mV */ #define DV2_CV_LOWER_BOUND_GAP 50 /* mV */ #define DV2_ALGO_INIT_POLLING_INTERVAL 500 /* ms */ #define DV2_ALGO_INIT_RETRY_MAX 0 #define DV2_ALGO_MEASURE_R_RETRY_MAX 3 #define DV2_ALGO_MEASURE_R_AVG_TIMES 10 #define DV2_VSYS_UPPER_BOUND 4700 /* mV */ #define DV2_VSYS_UPPER_BOUND_GAP 40 /* mV */ #define DV2_HWERR_NOTIFY \ (BIT(PCA_NOTIEVT_VBUSOVP) | BIT(PCA_NOTIEVT_IBUSOCP) | \ BIT(PCA_NOTIEVT_VBATOVP) | BIT(PCA_NOTIEVT_IBATOCP) | \ BIT(PCA_NOTIEVT_VOUTOVP) | BIT(PCA_NOTIEVT_VDROVP) | \ BIT(PCA_NOTIEVT_IBUSUCP_FALL)) #define DV2_RESET_NOTIFY \ (BIT(PCA_NOTIEVT_DETACH) | BIT(PCA_NOTIEVT_HARDRESET)) enum dv2_algo_state { DV2_ALGO_INIT = 0, DV2_ALGO_MEASURE_R, DV2_ALGO_SS_SWCHG, DV2_ALGO_SS_DVCHG, DV2_ALGO_CC_CV, DV2_ALGO_STOP, DV2_ALGO_STATE_MAX, }; enum dv2_thermal_level { DV2_THERMAL_VERY_COLD = 0, DV2_THERMAL_COLD, DV2_THERMAL_VERY_COOL, DV2_THERMAL_COOL, DV2_THERMAL_NORMAL, DV2_THERMAL_WARM, DV2_THERMAL_VERY_WARM, DV2_THERMAL_HOT, DV2_THERMAL_VERY_HOT, DV2_THERMAL_MAX, }; enum dv2_rcable_level { DV2_RCABLE_NORMAL = 0, DV2_RCABLE_BAD1, DV2_RCABLE_BAD2, DV2_RCABLE_BAD3, DV2_RCABLE_MAX, }; enum dv2_dvchg_role { DV2_DVCHG_MASTER = 0, DV2_DVCHG_SLAVE, DV2_DVCHG_MAX, }; static const char *const __dv2_dvchg_role_name[DV2_DVCHG_MAX] = { "master", "slave", }; static const char *const __dv2_algo_state_name[DV2_ALGO_STATE_MAX] = { "INIT", "MEASURE_R", "SS_SWCHG", "SS_DVCHG", "CC_CV", "STOP", }; /* Setting from dtsi */ struct dv2_algo_desc { u32 polling_interval; /* polling interval */ u32 ta_cv_ss_repeat_tmin; /* min repeat time of ss for TA CV */ u32 vbat_cv; /* vbat constant voltage */ u32 start_soc_min; /* algo start bat low bound */ u32 start_soc_max; /* algo start bat upper bound */ u32 start_vbat_max; /* algo start bat upper bound */ u32 idvchg_term; /* terminated current */ u32 idvchg_step; /* input current step */ u32 ita_level[DV2_RCABLE_MAX]; /* input current */ u32 rcable_level[DV2_RCABLE_MAX]; /* cable impedance level */ u32 ita_level_dual[DV2_RCABLE_MAX]; /* input current */ u32 rcable_level_dual[DV2_RCABLE_MAX]; /* cable impedance level */ u32 idvchg_ss_init; /* SS state init input current */ u32 idvchg_ss_step; /* SS state input current step */ u32 idvchg_ss_step1; /* SS state input current step2 */ u32 idvchg_ss_step2; /* SS state input current step3 */ u32 idvchg_ss_step1_vbat; /* vbat threshold for ic_ss_step2 */ u32 idvchg_ss_step2_vbat; /* vbat threshold for ic_ss_step3 */ u32 ta_blanking; /* wait TA stable */ u32 swchg_aicr; /* CC state swchg input current */ u32 swchg_ichg; /* CC state swchg charging current */ u32 swchg_aicr_ss_init; /* SWCHG_SS state init input current */ u32 swchg_aicr_ss_step; /* SWCHG_SS state input current step */ u32 swchg_off_vbat; /* VBAT to turn off SWCHG */ u32 force_ta_cv_vbat; /* Force TA using CV mode */ u32 chg_time_max; /* max charging time */ int tta_level_def[DV2_THERMAL_MAX]; /* TA temp level */ int tta_curlmt[DV2_THERMAL_MAX]; /* TA temp current limit */ int tbat_level_def[DV2_THERMAL_MAX]; /* BAT temp level */ int tbat_curlmt[DV2_THERMAL_MAX]; /* BAT temp current limit */ int tdvchg_level_def[DV2_THERMAL_MAX]; /* DVCHG temp level */ int tdvchg_curlmt[DV2_THERMAL_MAX]; /* DVCHG temp current limit */ int tswchg_level_def[DV2_THERMAL_MAX]; /* SWCHG temp level */ int tswchg_curlmt[DV2_THERMAL_MAX]; /* SWCHG temp current limit */ u32 tta_recovery_area; u32 tbat_recovery_area; u32 tdvchg_recovery_area; u32 tswchg_recovery_area; u32 ifod_threshold; /* FOD current threshold */ u32 rsw_min; /* min rsw */ u32 ircmp_rbat; /* IR compensation's rbat */ u32 ircmp_vclamp; /* IR compensation's vclamp */ u32 vta_cap_min; /* min ta voltage capability */ u32 vta_cap_max; /* max ta voltage capability */ u32 ita_cap_min; /* min ta current capability */ const char **support_ta; /* supported ta name */ u32 support_ta_cnt; /* supported ta count */ bool allow_not_check_ta_status; /* allow not to check ta status */ }; /* Algorithm related information */ struct dv2_algo_data { /* PCA devices */ struct prop_chgalgo_device **pca_ta_pool; struct prop_chgalgo_device *pca_ta; struct prop_chgalgo_device *pca_swchg; struct prop_chgalgo_device *pca_dvchg[DV2_DVCHG_MAX]; struct prop_chgalgo_device *pca_hv_dvchg; /* Thread & Timer */ struct alarm timer; struct task_struct *task; struct mutex lock; struct mutex ext_lock; wait_queue_head_t wq; atomic_t wakeup_thread; atomic_t stop_thread; atomic_t stop_algo; /* Notify */ struct mutex notify_lock; u32 notify; /* Algorithm */ bool inited; bool ta_ready; bool run_once; bool is_swchg_en; bool is_dvchg_en[DV2_DVCHG_MAX]; bool ignore_ibusucpf; bool force_ta_cv; bool tried_dual_dvchg; bool suspect_ta_cc; struct prop_chgalgo_ta_auth_data ta_auth_data; u32 vta_setting; u32 ita_setting; u32 vta_measure; u32 ita_measure; u32 ita_gap_per_vstep; u32 ita_gaps[DV2_ITA_GAP_WINDOW_SIZE]; u32 ita_gap_window_idx; u32 ichg_setting; u32 aicr_setting; u32 aicr_lmt; u32 aicr_init_lmt; u32 idvchg_cc; u32 idvchg_ss_init; u32 idvchg_term; int vbus_cali; u32 r_sw; u32 r_cable; u32 r_bat; u32 r_total; u32 ita_lmt; u32 ita_pwr_lmt; u32 cv_lower_bound; u32 err_retry_cnt; u32 vbusovp; u32 zcv; u32 vbat_cv_no_ircmp; u32 vbat_cv; u32 vbat_ircmp; int vta_comp; bool is_vbat_over_cv; struct timespec stime; enum dv2_algo_state state; enum dv2_thermal_level tbat_level; enum dv2_thermal_level tta_level; enum dv2_thermal_level tdvchg_level; enum dv2_thermal_level tswchg_level; int thermal_throttling; int jeita_vbat_cv; }; /* * @reset_ta: set output voltage/current of TA to 5V/3A and disable * direct charge * @hardreset: send hardreset to port partner * Note: hardreset's priority is higher than reset_ta */ struct dv2_stop_info { bool hardreset_ta; bool reset_ta; }; struct dv2_algo_info { struct device *dev; struct prop_chgalgo_device *pca; struct dv2_algo_desc *desc; struct dv2_algo_data *data; }; /* If there's no property in dts, these values will be applied */ static struct dv2_algo_desc algo_desc_defval = { .polling_interval = 500, .ta_cv_ss_repeat_tmin = 25, .vbat_cv = 4350, .start_soc_min = 5, .start_soc_max = 80, .start_vbat_max = 4300, .idvchg_term = 500, .idvchg_step = 50, .ita_level = {3000, 2700, 2400, 2000}, .rcable_level = {250, 278, 313, 375}, .ita_level_dual = {4000, 3700, 3400, 3000}, .rcable_level_dual = {188, 203, 221, 250}, .idvchg_ss_init = 500, .idvchg_ss_step = 250, .idvchg_ss_step1 = 100, .idvchg_ss_step2 = 50, .idvchg_ss_step1_vbat = 4000, .idvchg_ss_step2_vbat = 4200, .ta_blanking = 500, .swchg_aicr = 0, .swchg_ichg = 0, .swchg_aicr_ss_init = 400, .swchg_aicr_ss_step = 200, .swchg_off_vbat = 4250, .force_ta_cv_vbat = 4250, .chg_time_max = 5400, .tta_level_def = {0, 0, 0, 0, 25, 40, 50, 60, 70}, .tta_curlmt = {0, 0, 0, 0, 0, 300, 600, 900, -1}, .tta_recovery_area = 3, .tbat_level_def = {0, 0, 0, 5, 25, 40, 50, 55, 60}, .tbat_curlmt = {-1, -1, -1, 300, 0, 300, 600, 900, -1}, .tbat_recovery_area = 3, .tdvchg_level_def = {0, 0, 0, 5, 25, 55, 60, 65, 70}, .tdvchg_curlmt = {-1, -1, -1, 300, 0, 300, 600, 900, -1}, .tdvchg_recovery_area = 3, .tswchg_level_def = {0, 0, 0, 5, 25, 55, 60, 65, 70}, .tswchg_curlmt = {-1, -1, -1, 200, 0, 200, 300, 400, -1}, .tswchg_recovery_area = 3, .ifod_threshold = 200, .rsw_min = 20, .ircmp_rbat = 40, .ircmp_vclamp = 0, .vta_cap_min = 6800, .vta_cap_max = 11000, .ita_cap_min = 1000, .allow_not_check_ta_status = true, }; static inline u32 precise_div(u64 dividend, u64 divisor) { u64 _val = div64_u64(dividend << PRECISION_ENHANCE, divisor); return (u32)((_val + (1 << (PRECISION_ENHANCE - 1))) >> PRECISION_ENHANCE); } static inline u32 percent(u32 val, u32 percent) { return precise_div((u64)val * percent, 100); } static inline u32 div1000(u32 val) { return precise_div(val, 1000); } /* * Send notification to all subscribers * This function is called by dv2 thread, "DO NOT" call dv2's API in the * callback function that you registered */ static int __dv2_send_notification(struct dv2_algo_info *info, unsigned long val, struct prop_chgalgo_notify *notify) { return srcu_notifier_call_chain(&info->pca->nh, val, notify); } /* Check if there is error notification coming from H/W */ static bool __dv2_is_hwerr_notified(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; bool err = false; u32 hwerr = DV2_HWERR_NOTIFY; mutex_lock(&data->notify_lock); if (data->ignore_ibusucpf) hwerr &= ~BIT(PCA_NOTIEVT_IBUSUCP_FALL); err = !!(data->notify & hwerr); if (err) PCA_ERR("H/W error(0x%08X)", hwerr); mutex_unlock(&data->notify_lock); return err; } /* * Get ADC value from divider charger * Note: ibus will sum up value from all enabled chargers * (master dvchg, slave dvchg and swchg) */ static int __dv2_stop(struct dv2_algo_info *info, struct dv2_stop_info *sinfo); static int __dv2_get_adc(struct dv2_algo_info *info, enum prop_chgalgo_adc_channel chan, int *val) { struct dv2_algo_data *data = info->data; int ret, i, ibus; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; if (!data->pca_dvchg[DV2_DVCHG_MASTER]) return -EINVAL; if (atomic_read(&data->stop_algo)) { PCA_INFO("stop algo\n"); goto stop; } *val = 0; if (chan == PCA_ADCCHAN_IBUS) { for (i = DV2_DVCHG_MASTER; i < DV2_DVCHG_MAX; i++) { if (!data->is_dvchg_en[i]) continue; ret = prop_chgalgo_get_adc(data->pca_dvchg[i], PCA_ADCCHAN_IBUS, &ibus, &ibus); if (ret < 0) { PCA_ERR("get dvchg ibus fail(%d)\n", ret); return ret; } *val += ibus; } if (data->is_swchg_en) { ret = prop_chgalgo_get_adc(data->pca_swchg, PCA_ADCCHAN_IBUS, &ibus, &ibus); if (ret < 0) { PCA_ERR("get swchg ibus fail(%d)\n", ret); return ret; } *val += ibus; } return 0; } return prop_chgalgo_get_adc(data->pca_dvchg[DV2_DVCHG_MASTER], chan, val, val); stop: __dv2_stop(info, &sinfo); return -EIO; } /* * Calculate VBUS for divider charger * If divider charger is charging, the VBUS only needs to be 2 times of VOUT. */ static inline u32 __dv2_vout2vbus(struct dv2_algo_info *info, u32 vout) { struct dv2_algo_data *data = info->data; u32 ratio = data->is_dvchg_en[DV2_DVCHG_MASTER] ? DV2_DVCHG_CHARGING_CONVERT_RATIO : DV2_DVCHG_STARTUP_CONVERT_RATIO; return percent(vout, ratio); } /* * Maximum DV2_VTA_VAR_MIN(%) variation (from PD's sepcification) * Keep vta_setting DV2_VTA_VAR_MIN(%) higher than vta_measure * and make sure it has minimum gap, DV2_VTA_GAP_MIN */ static inline u32 __dv2_vta_add_gap(struct dv2_algo_info *info, u32 vta) { return max(percent(vta, DV2_VTA_VAR_MIN), vta + DV2_VTA_GAP_MIN); } /* * Get output current and voltage measured by TA * and updates measured data */ static inline int __dv2_get_ta_cap(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; return prop_chgalgo_get_ta_measure_cap(data->pca_ta, &data->vta_measure, &data->ita_measure); } /* * Get output current and voltage measured by TA * and updates measured data * If ta does not support measure capability, dvchg's ADC is used instead */ static inline int __dv2_get_ta_cap_by_supportive(struct dv2_algo_info *info, int *vta, int *ita) { int ret; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; if (auth_data->support_meas_cap) { ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); return ret; } *vta = data->vta_measure; *ita = data->ita_measure; return 0; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, vta); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); return ret; } return __dv2_get_adc(info, PCA_ADCCHAN_IBUS, ita); } /* Calculate ibat from ita */ static inline u32 dv2_cal_ibat(struct dv2_algo_info *info, u32 ita) { struct dv2_algo_data *data = info->data; return 2 * (data->is_swchg_en ? (ita - data->aicr_setting) : ita); } /* * Calculate calibrated output voltage of TA by measured resistence * Firstly, calculate voltage needed by divider charger * Finally, calculate voltage outputing from TA * * @ita: expected output current of TA * @vta: calibrated output voltage of TA */ static int __dv2_get_cali_vta(struct dv2_algo_info *info, u32 ita, u32 *vta) { int ret, vbat; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 ibat, vbus, _vta, comp; ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); return ret; } ibat = dv2_cal_ibat(info, ita); vbus = __dv2_vout2vbus(info, vbat + div1000(ibat * data->r_sw)); *vta = vbus + (data->vbus_cali + data->vta_comp + div1000(ita * data->r_cable)); if (data->is_dvchg_en[DV2_DVCHG_MASTER]) { ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); return ret; } _vta = __dv2_vta_add_gap(info, data->vta_measure); if (_vta > *vta) { comp = _vta - *vta; data->vta_comp += comp; PCA_DBG("comp,add=(%d,%d)\n", data->vta_comp, comp); } *vta = max(*vta, _vta); } if (*vta >= auth_data->vcap_max) { PCA_INFO("cali vta %d over vcap max\n", *vta); *vta = auth_data->vcap_max; } return 0; } /* * Tracking vbus of divider charger using vbusovp alarm * If vbusovp alarm is triggered, algorithm needs to come up with a new vbus */ static int __dv2_set_vbus_tracking(struct dv2_algo_info *info) { int ret, vbus; struct dv2_algo_data *data = info->data; ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, &vbus); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); return ret; } return prop_chgalgo_set_vbusovp_alarm(data->pca_dvchg[DV2_DVCHG_MASTER], vbus + DV2_DVCHG_VBUSALM_GAP); } /* Calculate power limited ita according to TA's power limitation */ static u32 __dv2_get_ita_pwr_lmt_by_vta(struct dv2_algo_info *info, u32 vta) { struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 ita_pwr_lmt; if (!auth_data->pwr_lmt) return data->ita_lmt; ita_pwr_lmt = precise_div(auth_data->pdp * 1000000, vta); /* Round to nearest level */ if (auth_data->support_cc) { ita_pwr_lmt /= auth_data->ita_step; ita_pwr_lmt *= auth_data->ita_step; } return min(ita_pwr_lmt, data->ita_lmt); } static inline u32 __dv2_get_ita_tracking_max(u32 ita) { return min_t(u32, percent(ita, DV2_ITAOCP_RATIO), (ita + DV2_ITA_TRACKING_GAP)); } /* * Set output capability of TA in CC mode and update setting in data * * @vta: output voltage of TA, mV * @ita: output current of TA, mA */ static int __dv2_force_ta_cv(struct dv2_algo_info *info, struct dv2_stop_info *sinfo); static inline int __dv2_set_ta_cap_cc(struct dv2_algo_info *info, u32 vta, u32 ita) { int ret, vbat; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; bool set_opt_vta = true; bool is_ta_cc = false; u32 opt_vta; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; if (data->vta_setting == vta && data->ita_setting == ita && data->state != DV2_ALGO_INIT) return 0; while (true) { if (atomic_read(&data->stop_algo)) { PCA_INFO("stop algo\n"); goto stop; } /* Check TA's PDP */ data->ita_pwr_lmt = __dv2_get_ita_pwr_lmt_by_vta(info, vta); if (data->ita_pwr_lmt < ita) { PCA_INFO("ita(%d) > ita_pwr_lmt(%d)\n", ita, data->ita_pwr_lmt); ita = data->ita_pwr_lmt; } ret = prop_chgalgo_set_ta_cap(data->pca_ta, vta, ita); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); return ret; } msleep(desc->ta_blanking); if (!data->is_dvchg_en[DV2_DVCHG_MASTER]) break; ret = prop_chgalgo_is_ta_cc(data->pca_ta, &is_ta_cc); if (ret < 0) { PCA_ERR("get ta cc mode fail(%d)\n", ret); return ret; } ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); return ret; } PCA_DBG("vta(set,meas,comp),ita(set,meas)=(%d,%d,%d),(%d,%d)\n", vta, data->vta_measure, data->vta_comp, ita, data->ita_measure); if (is_ta_cc) { opt_vta = __dv2_vta_add_gap(info, data->vta_measure); if (vta > opt_vta && set_opt_vta) { data->vta_comp -= (vta - opt_vta); vta = opt_vta; set_opt_vta = false; continue; } break; } if (vta >= auth_data->vcap_max) { PCA_ERR("vta(%d) over capability(%d)\n", vta, auth_data->vcap_max); goto stop; } if (__dv2_is_hwerr_notified(info)) { PCA_ERR("H/W error notified\n"); goto stop; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); return ret; } if (vbat >= data->vbat_cv) { PCA_INFO("vbat(%d), decrease ita immediately\n", vbat); ita -= auth_data->ita_step; continue; } PCA_ERR("Not in cc mode\n"); if (data->ita_measure > __dv2_get_ita_tracking_max(ita)) { ret = __dv2_force_ta_cv(info, &sinfo); if (ret < 0) goto stop; return 0; } set_opt_vta = false; data->vta_comp += auth_data->vta_step; vta += auth_data->vta_step; vta = min_t(u32, vta, auth_data->vcap_max); } data->vta_setting = vta; data->ita_setting = ita; PCA_INFO("vta,ita = (%d,%d)\n", vta, ita); __dv2_set_vbus_tracking(info); return 0; stop: __dv2_stop(info, &sinfo); return -EIO; } /* * Set TA's output voltage & current by a given current and * calculated voltage */ static inline int __dv2_set_ta_cap_cc_by_cali_vta(struct dv2_algo_info *info, u32 ita) { int ret; u32 vta; ret = __dv2_get_cali_vta(info, ita, &vta); if (ret < 0) { PCA_ERR("get cali vta fail(%d)\n", ret); return ret; } return __dv2_set_ta_cap_cc(info, vta, ita); } static inline void __dv2_update_ita_gap(struct dv2_algo_info *info, u32 ita_gap) { int i; u32 val = 0, avg_cnt = DV2_ITA_GAP_WINDOW_SIZE; struct dv2_algo_data *data = info->data; if (ita_gap < data->ita_gap_per_vstep) return; data->ita_gap_window_idx = (data->ita_gap_window_idx + 1) % DV2_ITA_GAP_WINDOW_SIZE; data->ita_gaps[data->ita_gap_window_idx] = ita_gap; for (i = 0; i < DV2_ITA_GAP_WINDOW_SIZE; i++) { if (data->ita_gaps[i] == 0) avg_cnt--; else val += data->ita_gaps[i]; } data->ita_gap_per_vstep = avg_cnt != 0 ? precise_div(val, avg_cnt) : 0; } static inline int __dv2_set_ta_cap_cv(struct dv2_algo_info *info, u32 vta, u32 ita) { int ret, ita_meas_pre, ita_meas_post, vta_meas; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 vstep_cnt, ita_gap, vta_gap; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; if (data->vta_setting == vta && data->ita_setting == ita) return 0; while (true) { if (__dv2_is_hwerr_notified(info)) { PCA_ERR("H/W error notified\n"); goto stop; } if (atomic_read(&data->stop_algo)) { PCA_INFO("stop algo\n"); goto stop; } if (vta > auth_data->vcap_max) { PCA_ERR("vta(%d) over capability(%d)\n", vta, auth_data->vcap_max); goto stop; } if (ita < auth_data->ita_min) { PCA_INFO("ita(%d) under ita_min(%d)\n", ita, auth_data->ita_min); ita = auth_data->ita_min; } vta_gap = abs(data->vta_setting - vta); /* Get ta cap before setting */ ret = __dv2_get_ta_cap_by_supportive(info, &vta_meas, &ita_meas_pre); if (ret < 0) { PCA_ERR("get ta cap by supportive fail(%d)\n", ret); return ret; } /* Not to increase vta if it exceeds pwr_lmt */ data->ita_pwr_lmt = __dv2_get_ita_pwr_lmt_by_vta(info, vta); if (vta > data->vta_setting && (data->ita_pwr_lmt < ita_meas_pre + data->ita_gap_per_vstep)) { PCA_INFO("ita_meas(%d) + ita_gap(%d) > pwr_lmt(%d)\n", ita_meas_pre, data->ita_gap_per_vstep, data->ita_pwr_lmt); return 0; } /* Set ta cap */ ret = prop_chgalgo_set_ta_cap(data->pca_ta, vta, ita); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); return ret; } if (vta_gap > auth_data->vta_step || data->state != DV2_ALGO_SS_DVCHG) msleep(desc->ta_blanking); /* Get ta cap after setting */ ret = __dv2_get_ta_cap_by_supportive(info, &vta_meas, &ita_meas_post); if (ret < 0) { PCA_ERR("get ta cap by supportive fail(%d)\n", ret); return ret; } if (data->is_dvchg_en[DV2_DVCHG_MASTER] && (ita_meas_post > ita_meas_pre) && (vta > data->vta_setting)) { vstep_cnt = precise_div(max_t(u32, vta, vta_meas) - data->vta_setting, auth_data->vta_step); ita_gap = precise_div(ita_meas_post - ita_meas_pre, vstep_cnt); __dv2_update_ita_gap(info, ita_gap); PCA_INFO("ita gap(now,updated)=(%d,%d)\n", ita_gap, data->ita_gap_per_vstep); } data->vta_setting = vta; data->ita_setting = ita; if (ita_meas_post <= __dv2_get_ita_tracking_max(ita)) break; vta -= auth_data->vta_step; PCA_INFO("ita_meas %dmA over setting %dmA, keep tracking...\n", ita_meas_post, ita); } data->vta_measure = vta_meas; data->ita_measure = ita_meas_post; PCA_INFO("vta(set,meas):(%d,%d),ita(set,meas):(%d,%d)\n", data->vta_setting, data->vta_measure, data->ita_setting, data->ita_measure); return 0; stop: __dv2_stop(info, &sinfo); return -EIO; } static inline void __dv2_calculate_vbat_ircmp(struct dv2_algo_info *info) { int ret, ibat; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 ircmp; if (!data->is_dvchg_en[DV2_DVCHG_MASTER]) { data->vbat_ircmp = 0; return; } ret = __dv2_get_adc(info, PCA_ADCCHAN_IBAT, &ibat); if (ret < 0) { PCA_ERR("get ibat fail(%d)\n", ret); return; } ircmp = div1000(ibat * data->r_bat); /* * For safety, * if state is CC_CV, ircmp can only be smaller than previous one */ if (data->state == DV2_ALGO_CC_CV) ircmp = min(data->vbat_ircmp, ircmp); data->vbat_ircmp = min(desc->ircmp_vclamp, ircmp); PCA_INFO("vbat_ircmp(vclamp,ibat,rbat)=%d(%d,%d,%d)\n", data->vbat_ircmp, desc->ircmp_vclamp, ibat, data->r_bat); } static inline void __dv2_select_vbat_cv(struct dv2_algo_info *info) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 cv = data->vbat_cv; u32 cv_no_ircmp = desc->vbat_cv; mutex_lock(&data->ext_lock); if (data->jeita_vbat_cv > 0) cv_no_ircmp = min_t(u32, cv_no_ircmp, data->jeita_vbat_cv); if (cv_no_ircmp != data->vbat_cv_no_ircmp) data->vbat_cv_no_ircmp = cv_no_ircmp; cv = data->vbat_cv_no_ircmp + data->vbat_ircmp; if (cv == data->vbat_cv) goto out; /* VBATOVP ALARM */ ret = prop_chgalgo_set_vbatovp_alarm(data->pca_dvchg[DV2_DVCHG_MASTER], cv); if (ret < 0) { PCA_ERR("set vbatovp alarm fail(%d)\n", ret); goto out; } data->vbat_cv = cv; data->cv_lower_bound = data->vbat_cv - DV2_CV_LOWER_BOUND_GAP; out: PCA_INFO("vbat_cv(org,jeita,no_ircmp,low_bound)=%d(%d,%d,%d,%d)\n", data->vbat_cv, desc->vbat_cv, data->jeita_vbat_cv, data->vbat_cv_no_ircmp, data->cv_lower_bound); mutex_unlock(&data->ext_lock); } /* * Select current limit according to severial status * If switching charger is charging, add AICR setting to ita * For now, the following features are taken into consider * 1. Resistence * 2. Phone's thermal throttling * 3. TA's power limit * 4. TA's temperature * 5. Battery's temperature * 6. Divider charger's temperature */ static inline int __dv2_get_ita_lmt(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 ita = data->ita_lmt; mutex_lock(&data->ext_lock); if (data->thermal_throttling >= 0) ita = min_t(u32, ita, data->thermal_throttling); if (data->ita_pwr_lmt > 0) ita = min(ita, data->ita_pwr_lmt); if (data->tried_dual_dvchg) { ita = min(ita, data->ita_lmt - (2 * desc->tta_curlmt[data->tta_level])); ita = min(ita, data->ita_lmt - (2 * desc->tbat_curlmt[data->tbat_level])); ita = min(ita, data->ita_lmt - (2 * desc->tdvchg_curlmt[data->tdvchg_level])); } else { ita = min(ita, data->ita_lmt - desc->tta_curlmt[data->tta_level]); ita = min(ita, data->ita_lmt - desc->tbat_curlmt[data->tbat_level]); ita = min(ita, data->ita_lmt - desc->tdvchg_curlmt[data->tdvchg_level]); } PCA_INFO("ita(org,tta,tbat,tdvchg,prlmt,throt)=%d(%d,%d,%d,%d,%d,%d)\n", ita, data->ita_lmt, desc->tta_curlmt[data->tta_level], desc->tbat_curlmt[data->tbat_level], desc->tdvchg_curlmt[data->tdvchg_level], data->ita_pwr_lmt, data->thermal_throttling); mutex_unlock(&data->ext_lock); return ita; } static inline int __dv2_get_idvchg_lmt(struct dv2_algo_info *info) { u32 ita_lmt, idvchg_lmt; struct dv2_algo_data *data = info->data; ita_lmt = __dv2_get_ita_lmt(info); idvchg_lmt = min(data->idvchg_cc, ita_lmt); PCA_INFO("idvchg_lmt(ita_lmt,idvchg_cc)=%d(%d,%d)\n", idvchg_lmt, ita_lmt, data->idvchg_cc); return idvchg_lmt; } /* Calculate VBUSOV S/W level */ static u32 __dv2_get_dvchg_vbusovp(struct dv2_algo_info *info, u32 ita) { struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 vout, ibat; ibat = dv2_cal_ibat(info, ita); vout = desc->vbat_cv + div1000(ibat * data->r_sw); return min(percent(__dv2_vout2vbus(info, vout), DV2_VBUSOVP_RATIO), data->vbusovp); } /* Calculate IBUSOC S/W level */ static u32 __dv2_get_dvchg_ibusocp(struct dv2_algo_info *info, u32 ita) { struct dv2_algo_data *data = info->data; u32 ibus, ratio = DV2_IBUSOCP_RATIO; ibus = data->is_swchg_en ? (ita - data->aicr_setting) : ita; /* Add 10% for unbalance tolerance */ if (data->is_dvchg_en[DV2_DVCHG_SLAVE]) { ibus = precise_div(ibus, 2); ratio += 10; } return percent(ibus, ratio); } /* Calculate VBATOV S/W level */ static u32 __dv2_get_vbatovp(struct dv2_algo_info *info) { struct dv2_algo_desc *desc = info->desc; return percent(desc->vbat_cv + desc->ircmp_vclamp, DV2_VBATOVP_RATIO); } /* Calculate IBATOC S/W level */ static u32 __dv2_get_ibatocp(struct dv2_algo_info *info, u32 ita) { struct dv2_algo_data *data = info->data; u32 ibat; ibat = dv2_cal_ibat(info, ita); if (data->is_swchg_en) ibat += data->ichg_setting; return percent(ibat, DV2_IBATOCP_RATIO); } /* Calculate ITAOC S/W level */ static u32 __dv2_get_itaocp(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; return percent(data->ita_setting, DV2_ITAOCP_RATIO); } static int __dv2_set_dvchg_protection(struct dv2_algo_info *info, bool dual) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 vout, idvchg_lmt; u32 vbusovp, ibusocp, vbatovp, ibatocp; /* VBATOVP ALARM */ ret = prop_chgalgo_set_vbatovp_alarm(data->pca_dvchg[DV2_DVCHG_MASTER], desc->vbat_cv); if (ret < 0) { PCA_ERR("set vbatovp alarm fail(%d)\n", ret); return ret; } /* VBUSOVP */ vout = desc->vbat_cv + div1000(2 * data->idvchg_cc * data->r_sw); vbusovp = percent(__dv2_vout2vbus(info, vout), DV2_VBUSOVP_RATIO); vbusovp = min_t(u32, vbusovp, auth_data->vcap_max); ret = prop_chgalgo_set_vbusovp(data->pca_dvchg[DV2_DVCHG_MASTER], vbusovp); if (ret < 0) { PCA_ERR("set vbusovp fail(%d)\n", ret); return ret; } data->vbusovp = vbusovp; /* For TA CV mode, vbusovp alarm is not required */ if (!auth_data->support_cc) { ret = prop_chgalgo_set_vbusovp_alarm( data->pca_dvchg[DV2_DVCHG_MASTER], vbusovp); if (ret < 0) { PCA_ERR("set vbusovp alarm fail(%d)\n", ret); return ret; } } /* IBUSOCP */ idvchg_lmt = min_t(u32, data->idvchg_cc, auth_data->ita_max); ibusocp = percent(idvchg_lmt, DV2_IBUSOCP_RATIO); if (data->pca_dvchg[DV2_DVCHG_SLAVE] && dual) { /* Add 10% for unbalance tolerance */ ibusocp = percent(precise_div(idvchg_lmt, 2), DV2_IBUSOCP_RATIO + 10); ret = prop_chgalgo_set_ibusocp(data->pca_dvchg[DV2_DVCHG_SLAVE], ibusocp); if (ret < 0) { PCA_ERR("set slave ibusocp fail(%d)\n", ret); return ret; } } ret = prop_chgalgo_set_ibusocp(data->pca_dvchg[DV2_DVCHG_MASTER], ibusocp); if (ret < 0) { PCA_ERR("set ibusocp fail(%d)\n", ret); return ret; } /* VBATOVP */ vbatovp = percent(desc->vbat_cv + desc->ircmp_vclamp, DV2_VBATOVP_RATIO); ret = prop_chgalgo_set_vbatovp(data->pca_dvchg[DV2_DVCHG_MASTER], vbatovp); if (ret < 0) { PCA_ERR("set vbatovp fail(%d)\n", ret); return ret; } /* IBATOCP */ ibatocp = percent(2 * data->idvchg_cc + desc->swchg_ichg, DV2_IBATOCP_RATIO); ret = prop_chgalgo_set_ibatocp(data->pca_dvchg[DV2_DVCHG_MASTER], ibatocp); if (ret < 0) { PCA_ERR("set ibatocp fail(%d)\n", ret); return ret; } PCA_INFO("vbusovp,ibusocp,vbatovp,ibatocp = (%d,%d,%d,%d)\n", vbusovp, ibusocp, vbatovp, ibatocp); return 0; } static int __dv2_set_hv_dvchg_protection(struct dv2_algo_info *info, bool start) { int ret; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 ibusocp = start ? auth_data->ita_max : DV2_ITA_INIT; u32 vbusovp = start ? auth_data->vta_max : DV2_VTA_INIT; u32 vbatovp = start ? auth_data->vta_max : DV2_VTA_INIT; ret = prop_chgalgo_set_ibusocp(data->pca_hv_dvchg, ibusocp + 2000); if (ret < 0) { PCA_ERR("set hv dvchg ibusocp fail(%d)\n", ret); return ret; } ret = prop_chgalgo_set_vbusovp(data->pca_hv_dvchg, vbusovp + 2000); if (ret < 0) { PCA_ERR("set hv dvchg vbusovp fail(%d)\n", ret); return ret; } ret = prop_chgalgo_set_vbatovp(data->pca_hv_dvchg, vbatovp + 2000); if (ret < 0) { PCA_ERR("set hv dvchg voutovp fail(%d)\n", ret); return ret; } return 0; } /* * Enable/Disable divider charger * * @en: enable/disable */ static int __dv2_enable_dvchg_charging(struct dv2_algo_info *info, enum dv2_dvchg_role role, bool en) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; if (!data->pca_dvchg[role]) return -ENODEV; if (data->is_dvchg_en[role] == en) return 0; PCA_INFO("en[%s] = %d\n", __dv2_dvchg_role_name[role], en); ret = prop_chgalgo_enable_charging(data->pca_dvchg[role], en); if (ret < 0) { PCA_ERR("en chg fail(%d)\n", ret); return ret; } data->is_dvchg_en[role] = en; msleep(desc->ta_blanking); return 0; } /* * Set protection parameters, disable swchg and enable divider charger * * @en: enable/disable */ static int __dv2_set_dvchg_charging(struct dv2_algo_info *info, bool en) { int ret; struct dv2_algo_data *data = info->data; if (!data->pca_dvchg[DV2_DVCHG_MASTER]) return -ENODEV; PCA_INFO("en = %d\n", en); if (en) { ret = prop_chgalgo_enable_hz(data->pca_swchg, true); if (ret < 0) { PCA_ERR("set swchg hz fail(%d)\n", ret); return ret; } ret = __dv2_set_dvchg_protection(info, false); if (ret < 0) { PCA_ERR("set protection fail(%d)\n", ret); return ret; } } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_MASTER, en); if (ret < 0) return ret; if (!en) { ret = prop_chgalgo_enable_hz(data->pca_swchg, false); if (ret < 0) { PCA_ERR("disable swchg hz fail(%d)\n", ret); return ret; } } return 0; } /* * Enable charging of switching charger * For divide by two algorithm, according to swchg_ichg to decide enable or not * * @en: enable/disable */ static int __dv2_enable_swchg_charging(struct dv2_algo_info *info, bool en) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; PCA_INFO("en = %d\n", en); if (en) { ret = prop_chgalgo_enable_charging(data->pca_swchg, true); if (ret < 0) { PCA_ERR("en swchg fail(%d)\n", ret); return ret; } ret = prop_chgalgo_enable_hz(data->pca_swchg, false); if (ret < 0) { PCA_ERR("disable hz fail(%d)\n", ret); return ret; } } else { ret = prop_chgalgo_enable_hz(data->pca_swchg, true); if (ret < 0) { PCA_ERR("disable hz fail(%d)\n", ret); return ret; } ret = prop_chgalgo_enable_charging(data->pca_swchg, false); if (ret < 0) { PCA_ERR("en swchg fail(%d)\n", ret); return ret; } } data->is_swchg_en = en; ret = prop_chgalgo_set_vbatovp_alarm(data->pca_dvchg[DV2_DVCHG_MASTER], en ? desc->swchg_off_vbat : desc->vbat_cv); if (ret < 0) { PCA_ERR("set vbatovp alarm fail(%d)\n", ret); return ret; } return 0; } /* * Set AICR & ICHG of switching charger * * @aicr: setting of AICR * @ichg: setting of ICHG */ static int __dv2_set_swchg_cap(struct dv2_algo_info *info, u32 aicr) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 ichg, vbat, vbus; if (aicr == data->aicr_setting) goto set_ichg; ret = prop_chgalgo_set_aicr(data->pca_swchg, aicr); if (ret < 0) { PCA_ERR("set aicr fail(%d)\n", ret); return ret; } data->aicr_setting = aicr; set_ichg: ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); return ret; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, &vbus); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); return ret; } /* 90% charging efficiency */ ichg = precise_div(percent(vbus * aicr, 90), vbat); ichg = min(ichg, desc->swchg_ichg); if (ichg == data->ichg_setting) return 0; ret = prop_chgalgo_set_ichg(data->pca_swchg, ichg); if (ret < 0) { PCA_ERR("set_ichg fail(%d)\n", ret); return ret; } data->ichg_setting = ichg; PCA_INFO("AICR = %d, ICHG = %d\n", aicr, ichg); return 0; } /* * Enable TA by algo * * @en: enable/disable * @mV: requested output voltage * @mA: requested output current */ static int __dv2_enable_ta_charging(struct dv2_algo_info *info, bool en, int mV, int mA) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 wdt = max_t(u32, desc->polling_interval * 2, DV2_TA_WDT_MIN); PCA_INFO("en = %d\n", en); if (en) { ret = prop_chgalgo_set_ta_wdt(data->pca_ta, wdt); if (ret < 0) { PCA_ERR("set ta wdt fail(%d)\n", ret); return ret; } ret = prop_chgalgo_enable_ta_wdt(data->pca_ta, true); if (ret < 0) { PCA_ERR("en ta wdt fail(%d)\n", ret); return ret; } } ret = prop_chgalgo_enable_ta_charging(data->pca_ta, en, mV, mA); if (ret < 0) { PCA_ERR("en ta charging fail(%d)\n", ret); return ret; } if (!en) { ret = prop_chgalgo_enable_ta_wdt(data->pca_ta, false); if (ret < 0) PCA_ERR("disable ta wdt fail(%d)\n", ret); } data->vta_setting = mV; data->ita_setting = mA; return ret; } /* Stop dv2 charging and reset parameter */ static int __dv2_stop(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { struct dv2_algo_data *data = info->data; struct prop_chgalgo_notify notify = { .src = PCA_NOTISRC_ALGO, .evt = PCA_NOTIEVT_ALGO_STOP, }; if (data->state == DV2_ALGO_STOP) { /* * Always clear stop_algo, * in case it is called from dv2_stop_algo */ atomic_set(&data->stop_algo, 0); PCA_DBG("already stop\n"); return 0; } PCA_INFO("reset ta(%d), hardreset ta(%d)\n", sinfo->reset_ta, sinfo->hardreset_ta); data->state = DV2_ALGO_STOP; atomic_set(&data->stop_algo, 0); alarm_cancel(&data->timer); if (data->is_swchg_en) __dv2_enable_swchg_charging(info, false); __dv2_enable_dvchg_charging(info, DV2_DVCHG_SLAVE, false); __dv2_set_dvchg_charging(info, false); if (!(data->notify & DV2_RESET_NOTIFY)) { if (sinfo->hardreset_ta) prop_chgalgo_send_ta_hardreset(data->pca_ta); else if (sinfo->reset_ta) { prop_chgalgo_set_ta_cap(data->pca_ta, DV2_VTA_INIT, DV2_ITA_INIT); __dv2_enable_ta_charging(info, false, DV2_VTA_INIT, DV2_ITA_INIT); } } __dv2_set_hv_dvchg_protection(info, false); __dv2_send_notification(info, PCA_NOTIEVT_ALGO_STOP, ¬ify); return 0; } static inline void __dv2_init_algo_data(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 *rcable_level = desc->rcable_level; u32 *ita_level = desc->ita_level; data->ita_lmt = min_t(u32, ita_level[DV2_RCABLE_NORMAL], auth_data->ita_max); data->idvchg_ss_init = max_t(u32, data->idvchg_ss_init, auth_data->ita_min); data->idvchg_ss_init = min(data->idvchg_ss_init, data->ita_lmt); data->ita_pwr_lmt = 0; data->idvchg_cc = ita_level[DV2_RCABLE_NORMAL] - desc->swchg_aicr; data->idvchg_term = desc->idvchg_term; data->err_retry_cnt = 0; data->is_swchg_en = false; data->is_dvchg_en[DV2_DVCHG_MASTER] = false; data->is_dvchg_en[DV2_DVCHG_SLAVE] = false; data->suspect_ta_cc = false; data->aicr_setting = 0; data->ichg_setting = 0; data->vta_setting = DV2_VTA_INIT; data->ita_setting = DV2_ITA_INIT; data->ita_gap_per_vstep = 0; data->ita_gap_window_idx = 0; memset(data->ita_gaps, 0, sizeof(data->ita_gaps)); data->is_vbat_over_cv = false; data->ignore_ibusucpf = false; data->force_ta_cv = false; data->vbat_cv = desc->vbat_cv; data->vbat_cv_no_ircmp = desc->vbat_cv; data->cv_lower_bound = desc->vbat_cv - DV2_CV_LOWER_BOUND_GAP; data->vta_comp = 0; data->zcv = 0; data->r_bat = desc->ircmp_rbat; data->r_sw = desc->rsw_min; data->r_cable = rcable_level[DV2_RCABLE_NORMAL]; data->tbat_level = DV2_THERMAL_NORMAL; data->tta_level = DV2_THERMAL_NORMAL; data->tdvchg_level = DV2_THERMAL_NORMAL; data->tswchg_level = DV2_THERMAL_NORMAL; data->run_once = true; data->state = DV2_ALGO_INIT; mutex_lock(&data->notify_lock); data->notify = 0; mutex_unlock(&data->notify_lock); get_monotonic_boottime(&data->stime); } static int __dv2_earily_restart(struct dv2_algo_info *info) { int ret; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_SLAVE, false); if (ret < 0) { PCA_ERR("disable slave dvchg fail(%d)\n", ret); return ret; } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_MASTER, false); if (ret < 0) { PCA_ERR("disable master dvchg fail(%d)\n", ret); return ret; } if (auth_data->support_cc) { ret = __dv2_set_ta_cap_cc(info, DV2_VTA_INIT, DV2_ITA_INIT); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); return ret; } } ret = __dv2_enable_ta_charging(info, false, DV2_VTA_INIT, DV2_ITA_INIT); if (ret < 0) { PCA_ERR("disable ta charging fail(%d)\n", ret); return ret; } __dv2_init_algo_data(info); return 0; } /* * Start dv2 algo timer and run algo * It cannot start algo again if algo has been started once before * Run once flag will be reset after plugging out TA */ static inline int __dv2_start(struct dv2_algo_info *info) { int ret, ibus, vbat, vbus, ita, i; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; ktime_t ktime = ktime_set(0, MS_TO_NS(DV2_ALGO_INIT_POLLING_INTERVAL)); PCA_DBG("++\n"); if (data->run_once) { PCA_ERR("already run dv2 algo once\n"); return -EINVAL; } if (data->pca_hv_dvchg) { ret = prop_chgalgo_init_chip(data->pca_hv_dvchg); if (ret < 0) { PCA_ERR("init hv dvchg chip fail(%d)\n", ret); return ret; } ret = __dv2_set_hv_dvchg_protection(info, true); if (ret < 0) { PCA_ERR("set hv dvchg protection fail(%d)\n", ret); return ret; } } data->idvchg_ss_init = desc->idvchg_ss_init; ret = prop_chgalgo_set_aicr(data->pca_swchg, 3000); if (ret < 0) { PCA_ERR("set aicr fail(%d)\n", ret); goto start; } ret = prop_chgalgo_set_ichg(data->pca_swchg, 3000); if (ret < 0) { PCA_ERR("set ichg fail(%d)\n", ret); goto start; } ret = prop_chgalgo_enable_charging(data->pca_swchg, true); if (ret < 0) { PCA_ERR("en swchg fail(%d)\n", ret); goto start; } msleep(1000); ret = prop_chgalgo_get_adc(data->pca_swchg, PCA_ADCCHAN_VBUS, &vbus, &vbus); if (ret < 0) { PCA_ERR("get swchg vbus fail(%d)\n", ret); goto start; } ret = prop_chgalgo_get_adc(data->pca_swchg, PCA_ADCCHAN_IBUS, &ibus, &ibus); if (ret < 0) { PCA_ERR("get swchg ibus fail(%d)\n", ret); goto start; } ret = prop_chgalgo_get_adc(data->pca_swchg, PCA_ADCCHAN_VBAT, &vbat, &vbat); if (ret < 0) { PCA_ERR("get swchg vbat fail(%d)\n", ret); goto start; } ita = precise_div(percent(vbus * ibus, 90), 2 * vbat); if (ita < desc->idvchg_term) { PCA_ERR("estimated ita(%d) < idvchg_term(%d)\n", ita, desc->idvchg_term); return -EINVAL; } /* Update idvchg_ss_init */ if (ita >= auth_data->ita_min) { PCA_INFO("set idvchg_ss_init(%d)->(%d)\n", desc->idvchg_ss_init, ita); data->idvchg_ss_init = ita; } start: /* disable charger */ ret = prop_chgalgo_enable_charging(data->pca_swchg, false); if (ret < 0) { PCA_ERR("disable charger fail\n"); return ret; } msleep(1000); /* wait for battery to recovery */ /* Check DVCHG registers stat first */ for (i = DV2_DVCHG_MASTER; i < DV2_DVCHG_MAX; i++) { if (!data->pca_dvchg[i]) continue; ret = prop_chgalgo_init_chip(data->pca_dvchg[i]); if (ret < 0) { PCA_ERR("(%s) init chip fail(%d)\n", __dv2_dvchg_role_name[i], ret); return ret; } } /* Parameters that only reset by restarting from outside */ mutex_lock(&data->ext_lock); data->thermal_throttling = -1; data->jeita_vbat_cv = -1; mutex_unlock(&data->ext_lock); data->tried_dual_dvchg = false; __dv2_init_algo_data(info); alarm_start_relative(&data->timer, ktime); return 0; } /* =================================================================== */ /* DV2 Algo State Machine */ /* =================================================================== */ static int __dv2_algo_init_with_ta_cc(struct dv2_algo_info *info) { int ret, i, vbus, vbat; int ita_avg = 0, vta_avg = 0, vbus_avg = 0, vbat_avg = 0; const int avg_times = 10; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct dv2_stop_info sinfo = { .hardreset_ta = false, .reset_ta = true, }; PCA_DBG("++\n"); /* Change charging policy first */ ret = __dv2_enable_ta_charging(info, true, DV2_VTA_INIT, DV2_ITA_INIT); if (ret < 0) { PCA_ERR("enable ta charging fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } /* Check VBAT after disabling CHG_EN and before enabling HZ */ for (i = 0; i < avg_times; i++) { ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); goto err; } vbat_avg += vbat; } vbat_avg = precise_div(vbat_avg, avg_times); data->zcv = vbat_avg; if (vbat_avg > desc->start_vbat_max) { PCA_INFO("finish dv2, vbat(%d) > %d\n", vbat_avg, desc->start_vbat_max); goto out; } ret = prop_chgalgo_enable_hz(data->pca_swchg, true); if (ret < 0) { PCA_ERR("set swchg hz fail(%d)\n", ret); goto err; } msleep(500); /* Wait current stable */ /* Initial setting, no need to check ita_lmt */ ret = __dv2_set_ta_cap_cc_by_cali_vta(info, data->idvchg_ss_init); if (ret < 0) { PCA_ERR("set ta cap by algo fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } for (i = 0; i < avg_times; i++) { ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, &vbus); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); goto err; } ita_avg += data->ita_measure; vta_avg += data->vta_measure; vbus_avg += vbus; } ita_avg = precise_div(ita_avg, avg_times); vta_avg = precise_div(vta_avg, avg_times); vbus_avg = precise_div(vbus_avg, avg_times); /* vbus calibration: voltage difference between TA & device */ data->vbus_cali = vta_avg - vbus_avg; PCA_INFO("avg(ita,vta,vbus,vbat):(%d, %d, %d, %d), vbus cali:%d\n", ita_avg, vta_avg, vbus_avg, vbat_avg, data->vbus_cali); if (abs(data->vbus_cali) > DV2_VBUS_CALI_THRESHOLD) { PCA_ERR("vbus cali (%d) > (%d)\n", data->vbus_cali, DV2_VBUS_CALI_THRESHOLD); goto err; } if (ita_avg > desc->ifod_threshold) { PCA_ERR("foreign object detected, ita(%d) > (%d)\n", ita_avg, desc->ifod_threshold); goto err; } ret = __dv2_set_dvchg_charging(info, true); if (ret < 0) { PCA_ERR("en dvchg fail\n"); goto err; } ret = __dv2_set_ta_cap_cc_by_cali_vta(info, data->idvchg_ss_init); if (ret < 0) { PCA_ERR("set ta cap by algo fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } data->err_retry_cnt = 0; data->state = DV2_ALGO_MEASURE_R; return 0; err: if (data->err_retry_cnt < DV2_ALGO_INIT_RETRY_MAX) { data->err_retry_cnt++; return 0; } out: return __dv2_stop(info, &sinfo); } static int __dv2_algo_init_with_ta_cv(struct dv2_algo_info *info) { int ret, i, vbus, vbat, vout; int ita_avg = 0, vta_avg = 0, vbus_avg = 0, vbat_avg = 0; bool err; u32 vta; const int avg_times = 10; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); /* Change charging policy first */ ret = __dv2_enable_ta_charging(info, true, DV2_VTA_INIT, DV2_ITA_INIT); if (ret < 0) { PCA_ERR("enable ta charge fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } /* Check VBAT after disabling CHG_EN and before enabling HZ */ for (i = 0; i < avg_times; i++) { ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); goto err; } vbat_avg += vbat; } vbat_avg = precise_div(vbat_avg, avg_times); data->zcv = vbat_avg; PCA_INFO("avg(vbat):(%d)\n", vbat_avg); if (vbat_avg >= desc->start_vbat_max) { PCA_INFO("finish dv2, vbat(%d) > %d\n", vbat_avg, desc->start_vbat_max); goto out; } ret = prop_chgalgo_enable_hz(data->pca_swchg, true); if (ret < 0) { PCA_ERR("set swchg hz fail(%d)\n", ret); goto err; } msleep(500); /* Wait current stable */ ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, &vbus); if (ret < 0) { PCA_ERR("get vbus fail\n"); goto err; } ret = prop_chgalgo_sync_ta_volt(data->pca_ta, vbus); if (ret < 0 && ret != -ENOTSUPP) { PCA_ERR("sync ta setting fail\n"); goto err; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VOUT, &vout); if (ret < 0) { PCA_ERR("get vout fail\n"); goto err; } /* Adjust VBUS to make sure DVCHG can be turned on */ vta = __dv2_vout2vbus(info, vout); ret = __dv2_set_ta_cap_cv(info, vta, data->idvchg_ss_init); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); goto err; } while (true) { ret = prop_chgalgo_is_vbuslowerr( data->pca_dvchg[DV2_DVCHG_MASTER], &err); if (ret < 0) { PCA_ERR("get vbuslowerr fail(%d)\n", ret); goto err; } if (!err) break; vta = data->vta_setting + auth_data->vta_step; ret = __dv2_set_ta_cap_cv(info, vta, data->idvchg_ss_init); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); goto err; } } for (i = 0; i < avg_times; i++) { if (auth_data->support_meas_cap) { ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } ita_avg += data->ita_measure; vta_avg += data->vta_measure; ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, &vbus); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); goto err; } vbus_avg += vbus; } } if (auth_data->support_meas_cap) { ita_avg = precise_div(ita_avg, avg_times); vta_avg = precise_div(vta_avg, avg_times); vbus_avg = precise_div(vbus_avg, avg_times); } if (auth_data->support_meas_cap) { /* vbus calibration: voltage difference between TA & device */ data->vbus_cali = vta_avg - vbus_avg; PCA_INFO("avg(ita,vta,vbus):(%d,%d,%d), vbus_cali:%d\n", ita_avg, vta_avg, vbus_avg, data->vbus_cali); if (abs(data->vbus_cali) > DV2_VBUS_CALI_THRESHOLD) { PCA_ERR("vbus cali (%d) > (%d)\n", data->vbus_cali, DV2_VBUS_CALI_THRESHOLD); goto err; } if (ita_avg > desc->ifod_threshold) { PCA_ERR("foreign object detected, ita(%d) > (%d)\n", ita_avg, desc->ifod_threshold); goto err; } } ret = __dv2_set_dvchg_charging(info, true); if (ret < 0) { PCA_ERR("en dvchg fail\n"); goto err; } /* Get ita measure after enable dvchg */ ret = __dv2_get_ta_cap_by_supportive(info, &data->vta_measure, &data->ita_measure); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = auth_data->support_meas_cap; goto out; } data->err_retry_cnt = 0; data->state = DV2_ALGO_MEASURE_R; return 0; err: if (data->err_retry_cnt < DV2_ALGO_INIT_RETRY_MAX) { data->err_retry_cnt++; return 0; } out: return __dv2_stop(info, &sinfo); } /* * DV2 algorithm initial state * It does Foreign Object Detection(FOD) */ static int __dv2_algo_init(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; return auth_data->support_cc ? __dv2_algo_init_with_ta_cc(info) : __dv2_algo_init_with_ta_cv(info); } struct meas_r_info { u32 vbus; u32 ibus; u32 vbat; u32 ibat; u32 vout; u32 vta; u32 ita; u32 r_cable; u32 r_bat; u32 r_sw; }; static int __dv2_algo_get_r_info(struct dv2_algo_info *info, struct meas_r_info *r_info, struct dv2_stop_info *sinfo) { int ret; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; memset(r_info, 0, sizeof(struct meas_r_info)); if (auth_data->support_meas_cap) { ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo->hardreset_ta = true; return ret; } r_info->ita = data->ita_measure; r_info->vta = data->vta_measure; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, &r_info->vbus); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); return ret; } ret = __dv2_get_adc(info, PCA_ADCCHAN_IBUS, &r_info->ibus); if (ret < 0) { PCA_ERR("get ibus fail(%d)\n", ret); return ret; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VOUT, &r_info->vout); if (ret < 0) { PCA_ERR("get vout fail(%d)\n", ret); return ret; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &r_info->vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); return ret; } ret = __dv2_get_adc(info, PCA_ADCCHAN_IBAT, &r_info->ibat); if (ret < 0) { PCA_ERR("get ibat fail(%d)\n", ret); return ret; } PCA_DBG("vta:%d,ita:%d,vbus:%d,ibus:%d,vout:%d,vbat:%d,ibat:%d\n", r_info->vta, r_info->ita, r_info->vbus, r_info->ibus, r_info->vout, r_info->vbat, r_info->ibat); return 0; } static int __dv2_algo_cal_r_info_with_ta_cap(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, i; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct meas_r_info r_info, max_r_info, min_r_info; struct dv2_stop_info _sinfo = { .reset_ta = true, .hardreset_ta = false, }; memset(&max_r_info, 0, sizeof(struct meas_r_info)); memset(&min_r_info, 0, sizeof(struct meas_r_info)); data->r_bat = data->r_sw = data->r_cable = 0; for (i = 0; i < DV2_ALGO_MEASURE_R_AVG_TIMES + 2; i++) { if (atomic_read(&data->stop_algo)) { PCA_INFO("stop algo\n"); goto stop; } ret = __dv2_algo_get_r_info(info, &r_info, sinfo); if (ret < 0) { PCA_ERR("get r info fail(%d)\n", ret); return ret; } if (r_info.ibat == 0) { PCA_ERR("ibat == 0 fail\n"); return -EINVAL; } if (r_info.ita == 0) { PCA_ERR("ita == 0 fail\n"); sinfo->hardreset_ta = true; return -EINVAL; } if (r_info.ita < data->idvchg_term && r_info.vbat >= data->vbat_cv) { PCA_INFO("finish dv2 charging\n"); return -EINVAL; } /* Use absolute instead of relative calculation */ r_info.r_bat = precise_div(abs(r_info.vbat - data->zcv) * 1000, abs(r_info.ibat)); if (r_info.r_bat > desc->ircmp_rbat) r_info.r_bat = desc->ircmp_rbat; r_info.r_sw = precise_div(abs(r_info.vout - r_info.vbat) * 1000, abs(r_info.ibat)); if (r_info.r_sw < desc->rsw_min) r_info.r_sw = desc->rsw_min; r_info.r_cable = precise_div(abs(r_info.vta - data->vbus_cali - r_info.vbus) * 1000, abs(r_info.ita)); PCA_INFO("r_sw:%d, r_bat:%d, r_cable:%d\n", r_info.r_sw, r_info.r_bat, r_info.r_cable); if (i == 0) { memcpy(&max_r_info, &r_info, sizeof(struct meas_r_info)); memcpy(&min_r_info, &r_info, sizeof(struct meas_r_info)); } else { max_r_info.r_bat = max(max_r_info.r_bat, r_info.r_bat); max_r_info.r_sw = max(max_r_info.r_sw, r_info.r_sw); max_r_info.r_cable = max(max_r_info.r_cable, r_info.r_cable); min_r_info.r_bat = min(min_r_info.r_bat, r_info.r_bat); min_r_info.r_sw = min(min_r_info.r_sw, r_info.r_sw); min_r_info.r_cable = min(min_r_info.r_cable, r_info.r_cable); } data->r_bat += r_info.r_bat; data->r_sw += r_info.r_sw; data->r_cable += r_info.r_cable; } data->r_bat -= (max_r_info.r_bat + min_r_info.r_bat); data->r_sw -= (max_r_info.r_sw + min_r_info.r_sw); data->r_cable -= (max_r_info.r_cable + min_r_info.r_cable); data->r_bat = precise_div(data->r_bat, DV2_ALGO_MEASURE_R_AVG_TIMES); data->r_sw = precise_div(data->r_sw, DV2_ALGO_MEASURE_R_AVG_TIMES); data->r_cable = precise_div(data->r_cable, DV2_ALGO_MEASURE_R_AVG_TIMES); data->r_total = data->r_bat + data->r_sw + data->r_cable; return 0; stop: __dv2_stop(info, &_sinfo); return -EIO; } static int __dv2_select_ita_lmt_by_r(struct dv2_algo_info *info, bool dual) { struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 ita_lmt_by_r, ita_lmt; u32 *rcable_level = dual ? desc->rcable_level_dual : desc->rcable_level; u32 *ita_level = dual ? desc->ita_level_dual : desc->ita_level; if (!auth_data->support_meas_cap) { ita_lmt_by_r = ita_level[DV2_RCABLE_NORMAL]; goto out; } if (data->r_cable <= rcable_level[DV2_RCABLE_NORMAL]) ita_lmt_by_r = ita_level[DV2_RCABLE_NORMAL]; else if (data->r_cable <= rcable_level[DV2_RCABLE_BAD1]) ita_lmt_by_r = ita_level[DV2_RCABLE_BAD1]; else if (data->r_cable <= rcable_level[DV2_RCABLE_BAD2]) ita_lmt_by_r = ita_level[DV2_RCABLE_BAD2]; else if (data->r_cable <= rcable_level[DV2_RCABLE_BAD3]) ita_lmt_by_r = ita_level[DV2_RCABLE_BAD3]; else { PCA_ERR("r_cable(%d) too worse\n", data->r_cable); return -EINVAL; } out: PCA_INFO("ita limited by r = %d\n", ita_lmt_by_r); data->ita_lmt = min_t(u32, ita_lmt_by_r, auth_data->ita_max); data->ita_pwr_lmt = __dv2_get_ita_pwr_lmt_by_vta(info, data->vta_setting); ita_lmt = __dv2_get_ita_lmt(info); if (ita_lmt < data->idvchg_term) { PCA_ERR("ita_lmt(%d) < dvchg_term(%d)\n", ita_lmt, data->idvchg_term); return -EINVAL; } return 0; } static int __dv2_algo_measure_r_with_ta_cc(struct dv2_algo_info *info) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 ita, idvchg_lmt; u32 rcable_retry_level = (data->pca_dvchg[DV2_DVCHG_SLAVE] && !data->tried_dual_dvchg) ? desc->rcable_level_dual[DV2_RCABLE_NORMAL] : desc->rcable_level[DV2_RCABLE_NORMAL]; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); ret = __dv2_algo_cal_r_info_with_ta_cap(info, &sinfo); if (ret < 0) goto err; if (data->r_cable > rcable_retry_level && data->err_retry_cnt < DV2_ALGO_MEASURE_R_RETRY_MAX) { PCA_INFO("rcable(%d) is worse than normal\n", data->r_cable); goto err; } PCA_ERR("avg_r(sw,bat,cable):(%d,%d,%d), r_total:%d\n", data->r_sw, data->r_bat, data->r_cable, data->r_total); /* If haven't tried dual dvchg, try it once */ if (data->pca_dvchg[DV2_DVCHG_SLAVE] && !data->tried_dual_dvchg && !data->is_dvchg_en[DV2_DVCHG_SLAVE]) { PCA_INFO("try dual dvchg\n"); data->tried_dual_dvchg = true; data->idvchg_term = 2 * desc->idvchg_term; data->idvchg_cc = desc->ita_level_dual[DV2_RCABLE_NORMAL] - desc->swchg_aicr; ret = __dv2_select_ita_lmt_by_r(info, true); if (ret < 0) { PCA_ERR("select dual dvchg ita lmt fail(%d)\n", ret); goto single_dvchg_select_ita; } /* Turn on slave dvchg if idvchg_lmt >= 2 * idvchg_term */ idvchg_lmt = __dv2_get_idvchg_lmt(info); if (idvchg_lmt < data->idvchg_term) { PCA_ERR("idvchg_lmt(%d) < 2 * idvchg_term(%d)\n", idvchg_lmt, data->idvchg_term); goto single_dvchg_select_ita; } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_MASTER, false); if (ret < 0) { PCA_ERR("disable master dvchg fail(%d)\n", ret); goto single_dvchg_restart; } data->ignore_ibusucpf = true; ret = __dv2_set_dvchg_protection(info, true); if (ret < 0) { PCA_ERR("set dual dvchg protection fail(%d)\n", ret); goto single_dvchg_restart; } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_SLAVE, true); if (ret < 0) { PCA_ERR("en slave dvchg fail(%d)\n", ret); goto single_dvchg_restart; } ita = max(data->idvchg_term, data->ita_setting); ita = min(ita, idvchg_lmt); ret = __dv2_set_ta_cap_cc_by_cali_vta(info, ita); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto out; } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_MASTER, true); if (ret < 0) { PCA_ERR("en master dvchg fail(%d)\n", ret); goto single_dvchg_restart; } goto ss_dvchg; single_dvchg_restart: ret = __dv2_earily_restart(info); if (ret < 0) { PCA_ERR("earily restart fail(%d)\n", ret); goto out; } return 0; } single_dvchg_select_ita: data->idvchg_term = desc->idvchg_term; data->idvchg_cc = desc->ita_level[DV2_RCABLE_NORMAL] - desc->swchg_aicr; ret = __dv2_select_ita_lmt_by_r(info, false); if (ret < 0) { PCA_ERR("select dvchg ita lmt fail(%d)\n", ret); goto out; } ss_dvchg: data->err_retry_cnt = 0; data->state = DV2_ALGO_SS_DVCHG; return 0; err: if (data->err_retry_cnt < DV2_ALGO_MEASURE_R_RETRY_MAX) { data->err_retry_cnt++; return 0; } out: return __dv2_stop(info, &sinfo); } static int __dv2_algo_measure_r_with_ta_cv(struct dv2_algo_info *info) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 rcable_retry_level = (data->pca_dvchg[DV2_DVCHG_SLAVE] && !data->tried_dual_dvchg) ? desc->rcable_level_dual[DV2_RCABLE_NORMAL] : desc->rcable_level[DV2_RCABLE_NORMAL]; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); /* * Ignore measuring r, * treat as normal cable if meas_cap is not supported */ if (!auth_data->support_meas_cap) { PCA_INFO("ignore measuring resistance\n"); goto select_ita; } ret = __dv2_algo_cal_r_info_with_ta_cap(info, &sinfo); if (ret < 0) { PCA_ERR("get r info fail(%d)\n", ret); goto err; } if (data->r_cable > rcable_retry_level && data->err_retry_cnt < DV2_ALGO_MEASURE_R_RETRY_MAX) { PCA_INFO("rcable(%d) is worse than normal\n", data->r_cable); goto err; } PCA_ERR("avg_r(sw,bat,cable):(%d,%d,%d), r_total:%d\n", data->r_sw, data->r_bat, data->r_cable, data->r_total); select_ita: ret = __dv2_select_ita_lmt_by_r(info, false); if (ret < 0) { PCA_ERR("select dvchg ita lmt fail(%d)\n", ret); goto out; } data->err_retry_cnt = 0; data->state = DV2_ALGO_SS_DVCHG; return 0; err: if (data->err_retry_cnt < DV2_ALGO_MEASURE_R_RETRY_MAX) { data->err_retry_cnt++; return 0; } out: return __dv2_stop(info, &sinfo); } /* Measure resistance of cable/battery/sw and get corressponding ita limit */ static int __dv2_algo_measure_r(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; return (auth_data->support_cc && !data->force_ta_cv) ? __dv2_algo_measure_r_with_ta_cc(info) : __dv2_algo_measure_r_with_ta_cv(info); } static int __dv2_check_slave_dvchg_off(struct dv2_algo_info *info) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; data->idvchg_cc = desc->ita_level[DV2_RCABLE_NORMAL] - desc->swchg_aicr; data->idvchg_term = desc->idvchg_term; ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_SLAVE, false); if (ret < 0) { PCA_ERR("disable slave dvchg fail(%d)\n", ret); return ret; } ret = __dv2_select_ita_lmt_by_r(info, false); if (ret < 0) { PCA_ERR("select dvchg ita lmt fail(%d)\n", ret); return ret; } ret = __dv2_set_dvchg_protection(info, false); if (ret < 0) { PCA_ERR("dvchg protection fail(%d)\n", ret); return ret; } return 0; } static int __dv2_force_ta_cv(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret; u32 ita, vta; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; PCA_DBG("++\n"); ret = prop_chgalgo_set_vbusovp_alarm(data->pca_dvchg[DV2_DVCHG_MASTER], data->vbusovp); if (ret < 0) { PCA_ERR("set vbusovp alarm fail(%d)\n", ret); return ret; } ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail\n"); sinfo->hardreset_ta = true; return ret; } ita = min(data->ita_measure, data->ita_setting); vta = min_t(u32, data->vta_measure, auth_data->vcap_max); ret = __dv2_set_ta_cap_cv(info, vta, ita); if (ret < 0) { PCA_ERR("set ta cap fail\n"); return ret; } data->force_ta_cv = true; return 0; } static int __dv2_check_force_ta_cv(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret; u32 vbat; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); return ret; } if (desc->force_ta_cv_vbat != 0 && vbat >= desc->force_ta_cv_vbat && !data->is_swchg_en) { ret = __dv2_force_ta_cv(info, sinfo); if (ret < 0) { PCA_ERR("force ta cv fail(%d)\n", ret); return ret; } } return 0; } static int __dv2_algo_ss_dvchg_with_ta_cc(struct dv2_algo_info *info) { int ret, vbat; u32 ita, ita_lmt, idvchg_lmt; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); ret = __dv2_check_force_ta_cv(info, &sinfo); if (ret < 0) { PCA_ERR("check force ta cv fail(%d)\n", ret); goto err; } if (data->force_ta_cv) { PCA_INFO("force switching to ta cv mode\n"); return 0; } ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); goto err; } idvchg_lmt = __dv2_get_idvchg_lmt(info); if (idvchg_lmt < data->idvchg_term) { PCA_INFO("idvchg_lmt(%d) < idvchg_term(%d)\n", idvchg_lmt, data->idvchg_term); goto err; } /* VBAT reaches CV level */ if (vbat >= data->vbat_cv) { if (data->ita_measure < data->idvchg_term) { if (data->is_dvchg_en[DV2_DVCHG_SLAVE]) { ret = __dv2_check_slave_dvchg_off(info); if (ret < 0) { PCA_INFO("slave off fail(%d)\n", ret); goto err; } idvchg_lmt = __dv2_get_idvchg_lmt(info); goto cc_cv; } PCA_INFO("finish dv2 charging, vbat(%d), ita(%d)\n", vbat, data->ita_measure); goto err; } cc_cv: ita = min(data->ita_setting - desc->idvchg_ss_step, idvchg_lmt); data->state = DV2_ALGO_CC_CV; goto out_set_cap; } /* ITA reaches CC level */ if (data->ita_measure >= idvchg_lmt || data->ita_setting >= idvchg_lmt) { /* * Turn on switching charger only if divider charger * is charging by it's maximum setting current */ ita_lmt = __dv2_get_ita_lmt(info); if (vbat < desc->swchg_off_vbat && desc->swchg_aicr > 0 && desc->swchg_ichg > 0 && (ita_lmt > data->idvchg_cc + desc->swchg_aicr_ss_init)) { ret = __dv2_set_swchg_cap(info, desc->swchg_aicr_ss_init); if (ret < 0) { PCA_ERR("set swchg cap fail(%d)\n", ret); goto err; } ret = __dv2_enable_swchg_charging(info, true); if (ret < 0) { PCA_ERR("en swchg fail(%d)\n", ret); goto err; } ita = idvchg_lmt + desc->swchg_aicr_ss_init; data->aicr_init_lmt = ita_lmt - data->idvchg_cc; data->aicr_lmt = data->aicr_init_lmt; data->state = DV2_ALGO_SS_SWCHG; goto out_set_cap; } data->state = DV2_ALGO_CC_CV; ita = idvchg_lmt; goto out_set_cap; } /* Increase ita according to vbat level */ if (vbat < desc->idvchg_ss_step1_vbat) ita = data->ita_setting + desc->idvchg_ss_step; else if (vbat < desc->idvchg_ss_step2_vbat) ita = data->ita_setting + desc->idvchg_ss_step1; else ita = data->ita_setting + desc->idvchg_ss_step2; ita = min(ita, idvchg_lmt); out_set_cap: ret = __dv2_set_ta_cap_cc_by_cali_vta(info, ita); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } return 0; err: return __dv2_stop(info, &sinfo); } static int __dv2_algo_ss_dvchg_with_ta_cv(struct dv2_algo_info *info) { int ret, vbat; ktime_t start_time, end_time; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 idvchg_lmt, vta, ita, delta_time; u32 ita_gap_per_vstep = data->ita_gap_per_vstep > 0 ? data->ita_gap_per_vstep : auth_data->ita_gap_per_vstep; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; repeat: PCA_DBG("++\n"); vta = data->vta_setting; start_time = ktime_get(); ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); goto out; } ret = __dv2_get_ta_cap_by_supportive(info, &data->vta_measure, &data->ita_measure); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = auth_data->support_meas_cap; goto out; } /* Turn on slave dvchg if idvchg_lmt >= 2 * idvchg_term */ ita = data->idvchg_term * 2; if (data->pca_dvchg[DV2_DVCHG_SLAVE] && !data->tried_dual_dvchg && !data->is_dvchg_en[DV2_DVCHG_SLAVE] && (data->ita_measure >= ita)) { PCA_INFO("try dual dvchg\n"); data->tried_dual_dvchg = true; data->idvchg_term = 2 * desc->idvchg_term; data->idvchg_cc = desc->ita_level_dual[DV2_RCABLE_NORMAL] - desc->swchg_aicr; ret = __dv2_select_ita_lmt_by_r(info, true); if (ret < 0) { PCA_ERR("select dual dvchg ita lmt fail(%d)\n", ret); goto single_dvchg_select_ita; } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_MASTER, false); if (ret < 0) { PCA_ERR("disable master dvchg fail(%d)\n", ret); goto single_dvchg_restart; } data->ignore_ibusucpf = true; ret = __dv2_set_dvchg_protection(info, true); if (ret < 0) { PCA_ERR("set dual dvchg protection fail(%d)\n", ret); goto single_dvchg_restart; } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_SLAVE, true); if (ret < 0) { PCA_ERR("en slave dvchg fail(%d)\n", ret); goto single_dvchg_restart; } ret = __dv2_enable_dvchg_charging(info, DV2_DVCHG_MASTER, true); if (ret < 0) { PCA_ERR("en master dvchg fail(%d)\n", ret); goto single_dvchg_restart; } goto ss_dvchg; single_dvchg_restart: ret = __dv2_earily_restart(info); if (ret < 0) { PCA_ERR("earily restart fail(%d)\n", ret); goto out; } return 0; single_dvchg_select_ita: data->idvchg_term = desc->idvchg_term; data->idvchg_cc = desc->ita_level[DV2_RCABLE_NORMAL] - desc->swchg_aicr; ret = __dv2_select_ita_lmt_by_r(info, false); if (ret < 0) { PCA_ERR("select dvchg ita lmt fail(%d)\n", ret); goto out; } } ss_dvchg: ita = data->ita_setting; idvchg_lmt = __dv2_get_idvchg_lmt(info); if (idvchg_lmt < data->idvchg_term) { PCA_INFO("idvchg_lmt(%d) < idvchg_term(%d)\n", idvchg_lmt, data->idvchg_term); goto out; } /* VBAT reaches CV level */ if (vbat >= data->vbat_cv) { if (data->ita_measure < data->idvchg_term) { if (data->is_dvchg_en[DV2_DVCHG_SLAVE]) { ret = __dv2_check_slave_dvchg_off(info); if (ret < 0) { PCA_INFO("slave off fail(%d)\n", ret); goto out; } idvchg_lmt = __dv2_get_idvchg_lmt(info); goto cc_cv; } PCA_INFO("finish dv2 charging, vbat(%d), ita(%d)\n", vbat, data->ita_measure); goto out; } cc_cv: vta -= auth_data->vta_step; ita -= ita_gap_per_vstep; data->state = DV2_ALGO_CC_CV; goto out_set_cap; } /* IBUS reaches CC level */ if (data->ita_measure + ita_gap_per_vstep > idvchg_lmt || vta == auth_data->vcap_max) data->state = DV2_ALGO_CC_CV; else { vta += auth_data->vta_step; vta = min_t(u32, vta, auth_data->vcap_max); ita += ita_gap_per_vstep; ita = min(ita, idvchg_lmt); } out_set_cap: ret = __dv2_set_ta_cap_cv(info, vta, ita); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto out; } if (data->state == DV2_ALGO_SS_DVCHG) { end_time = ktime_get(); delta_time = ktime_ms_delta(end_time, start_time); PCA_DBG("delta time %dms\n", delta_time); if (delta_time < desc->ta_cv_ss_repeat_tmin) msleep(desc->ta_cv_ss_repeat_tmin - delta_time); goto repeat; } return 0; out: return __dv2_stop(info, &sinfo); } /* Soft start of divider charger */ static int __dv2_algo_ss_dvchg(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; return (auth_data->support_cc && !data->force_ta_cv) ? __dv2_algo_ss_dvchg_with_ta_cc(info) : __dv2_algo_ss_dvchg_with_ta_cv(info); } static int __dv2_check_swchg_off(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 aicr = data->aicr_setting, ita, vbat; ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); return ret; } if (!data->is_swchg_en || (vbat < desc->swchg_off_vbat && desc->tswchg_curlmt[data->tswchg_level] <= 0 && aicr <= data->aicr_lmt)) return 0; PCA_INFO("vbat(%d),swchg_off_vbat(%d),aicr_lmt(%d)\n", vbat, desc->swchg_off_vbat, data->aicr_lmt); /* Calculate AICR */ if (vbat >= desc->swchg_off_vbat) aicr -= desc->swchg_aicr_ss_step; aicr = min(aicr, data->aicr_lmt); /* Calculate ITA */ if (aicr >= desc->swchg_aicr_ss_init) ita = data->ita_setting - (data->aicr_setting - aicr); else ita = data->ita_setting - data->aicr_setting; ret = __dv2_set_ta_cap_cc_by_cali_vta(info, ita); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); sinfo->hardreset_ta = true; return ret; } return (aicr >= desc->swchg_aicr_ss_init) ? __dv2_set_swchg_cap(info, aicr) : __dv2_enable_swchg_charging(info, false); } static int __dv2_update_aicr_lmt(struct dv2_algo_info *info) { struct dv2_algo_desc *desc = info->desc; struct dv2_algo_data *data = info->data; u32 ita_lmt; if (!data->is_swchg_en) return 0; ita_lmt = __dv2_get_ita_lmt(info); /* Turn off swchg and use dvchg only */ if (ita_lmt < data->idvchg_cc + desc->swchg_aicr_ss_init) { data->aicr_lmt = 0; return -EINVAL; } data->aicr_lmt = min(data->aicr_lmt, ita_lmt - data->idvchg_cc); if (data->aicr_lmt < desc->swchg_aicr_ss_init) { data->aicr_lmt = 0; return -EINVAL; } return 0; } static int __dv2_algo_ss_swchg(struct dv2_algo_info *info) { int ret; struct dv2_algo_desc *desc = info->desc; struct dv2_algo_data *data = info->data; u32 ita = data->ita_setting, aicr = 0, vbat; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); if (__dv2_update_aicr_lmt(info) < 0) goto out; /* Set new AICR & TA cap */ aicr = min(data->aicr_setting + desc->swchg_aicr_ss_step, data->aicr_lmt); ita += (aicr - data->aicr_setting); ret = __dv2_set_swchg_cap(info, aicr); if (ret < 0) { PCA_ERR("set swchg cap fail(%d)\n", ret); goto err; } ret = __dv2_set_ta_cap_cc_by_cali_vta(info, ita); if (ret < 0) { PCA_ERR("set ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } out: ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); goto err; } ret = __dv2_check_swchg_off(info, &sinfo); if (ret < 0) { PCA_ERR("check swchg off fail(%d)\n", ret); goto err; } if (!data->is_swchg_en || vbat >= desc->swchg_off_vbat || aicr >= data->aicr_lmt) data->state = DV2_ALGO_CC_CV; return 0; err: return __dv2_stop(info, &sinfo); } static int __dv2_algo_cc_cv_with_ta_cc(struct dv2_algo_info *info) { int ret, vbat = 0; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 ita = data->ita_setting, ita_lmt; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); ret = __dv2_check_force_ta_cv(info, &sinfo); if (ret < 0) { PCA_ERR("check force ta cv fail(%d)\n", ret); goto err; } if (data->force_ta_cv) { PCA_INFO("force switching to ta cv mode\n"); return 0; } /* Check swchg */ __dv2_update_aicr_lmt(info); ret = __dv2_check_swchg_off(info, &sinfo); if (ret < 0) { PCA_ERR("check swchg off fail(%d)\n", ret); goto err; } ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) PCA_ERR("get vbat fail(%d)\n", ret); ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } if (data->ita_measure < data->idvchg_term) { if (data->is_dvchg_en[DV2_DVCHG_SLAVE]) { ret = __dv2_check_slave_dvchg_off(info); if (ret < 0) { PCA_INFO("slave off fail(%d)\n", ret); goto err; } goto cc_cv; } PCA_INFO("finish dv2 charging\n"); goto err; } cc_cv: ita_lmt = __dv2_get_ita_lmt(info); /* Consider AICR is decreased */ ita_lmt = min(ita_lmt, data->is_swchg_en ? (data->idvchg_cc + data->aicr_setting) : data->idvchg_cc); if (ita_lmt < data->idvchg_term) { PCA_INFO("ita_lmt(%d) < idvchg_term(%d)\n", ita_lmt, data->idvchg_term); goto err; } if (vbat >= data->vbat_cv) { ita = data->ita_setting - desc->idvchg_step; data->is_vbat_over_cv = true; } else if (vbat < desc->idvchg_ss_step1_vbat && ita < ita_lmt) { PCA_INFO("++ita(set,lmt,add)=(%d,%d,%d)\n", ita, ita_lmt, desc->idvchg_ss_step); ita = data->ita_setting + desc->idvchg_ss_step; } else if (vbat < desc->idvchg_ss_step2_vbat && ita < ita_lmt) { PCA_INFO("++ita(set,lmt,add)=(%d,%d,%d)\n", ita, ita_lmt, desc->idvchg_ss_step1); ita = data->ita_setting + desc->idvchg_ss_step1; } else if (!data->is_vbat_over_cv && vbat <= data->cv_lower_bound && ita < ita_lmt) { PCA_INFO("++ita(set,lmt,add)=(%d,%d,%d)\n", ita, ita_lmt, desc->idvchg_step); ita = data->ita_setting + desc->idvchg_step; } else if (data->is_vbat_over_cv) data->is_vbat_over_cv = false; ita = min(ita, ita_lmt); ret = __dv2_set_ta_cap_cc_by_cali_vta(info, ita); if (ret < 0) { PCA_ERR("set_ta_cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto err; } return 0; err: return __dv2_stop(info, &sinfo); } static int __dv2_algo_cc_cv_with_ta_cv(struct dv2_algo_info *info) { int ret, vbat, vsys = 0; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 idvchg_lmt, vta = data->vta_setting, ita = data->ita_setting; u32 ita_gap_per_vstep = data->ita_gap_per_vstep > 0 ? data->ita_gap_per_vstep : auth_data->ita_gap_per_vstep; u32 vta_measure, ita_measure, suspect_ta_cc = false; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); goto out; } ret = prop_chgalgo_get_adc(data->pca_swchg, PCA_ADCCHAN_VSYS, &vsys, &vsys); if (ret < 0) { PCA_ERR("get vsys fail(%d)\n", ret); goto out; } ret = __dv2_get_ta_cap_by_supportive(info, &data->vta_measure, &data->ita_measure); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = auth_data->support_meas_cap; goto out; } if (data->ita_measure <= data->idvchg_term) { if (data->is_dvchg_en[DV2_DVCHG_SLAVE]) { PCA_INFO("Turn off slave dvchg, ita_meas:%d, i_term:%d\n", data->ita_measure, data->idvchg_term); ret = __dv2_check_slave_dvchg_off(info); if (ret < 0) { PCA_INFO("slave off fail(%d)\n", ret); goto out; } goto cc_cv; } PCA_INFO("finish dv2 charging\n"); goto out; } cc_cv: idvchg_lmt = __dv2_get_idvchg_lmt(info); if (idvchg_lmt < data->idvchg_term) { PCA_INFO("idvchg_lmt(%d) < idvchg_term(%d)\n", idvchg_lmt, data->idvchg_term); goto out; } if (vbat >= data->vbat_cv) { PCA_INFO("--vbat >= vbat_cv, %d > %d\n", vbat, data->vbat_cv); vta -= auth_data->vta_step; ita -= ita_gap_per_vstep; data->is_vbat_over_cv = true; } else if (data->ita_measure > idvchg_lmt || vsys >= DV2_VSYS_UPPER_BOUND) { vta -= auth_data->vta_step; ita -= ita_gap_per_vstep; ita = max(ita, idvchg_lmt); PCA_INFO("--vta, ita(meas,lmt)=(%d,%d)\n", data->ita_measure, idvchg_lmt); } else if (!data->is_vbat_over_cv && vbat <= data->cv_lower_bound && data->ita_measure <= (idvchg_lmt - ita_gap_per_vstep) && vta < auth_data->vcap_max && !data->suspect_ta_cc && vsys < (DV2_VSYS_UPPER_BOUND - DV2_VSYS_UPPER_BOUND_GAP)) { vta += auth_data->vta_step; vta = min_t(u32, vta, auth_data->vcap_max); ita += ita_gap_per_vstep; ita = min(ita, idvchg_lmt); if (ita == data->ita_setting) suspect_ta_cc = true; PCA_INFO("++vta, ita(meas,lmt)=(%d,%d)\n", data->ita_measure, idvchg_lmt); } else if (data->is_vbat_over_cv) data->is_vbat_over_cv = false; ret = __dv2_set_ta_cap_cv(info, vta, ita); if (ret < 0) { PCA_ERR("set_ta_cap fail(%d)\n", ret); sinfo.hardreset_ta = true; goto out; } ret = __dv2_get_ta_cap_by_supportive(info, &vta_measure, &ita_measure); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); sinfo.hardreset_ta = auth_data->support_meas_cap; goto out; } data->suspect_ta_cc = (suspect_ta_cc && data->ita_measure == ita_measure); return 0; out: return __dv2_stop(info, &sinfo); } static int __dv2_algo_cc_cv(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; return (auth_data->support_cc && !data->force_ta_cv) ? __dv2_algo_cc_cv_with_ta_cc(info) : __dv2_algo_cc_cv_with_ta_cv(info); } /* * Check TA's status * Get status from TA and check temperature, OCP, OTP, and OVP, etc... * * return true if TA is normal and false if it is abnormal */ static bool __dv2_check_ta_status(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret; struct prop_chgalgo_ta_status status; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; if (!auth_data->support_status) return desc->allow_not_check_ta_status; ret = prop_chgalgo_get_ta_status(data->pca_ta, &status); if (ret < 0) { PCA_ERR("get ta status fail(%d)\n", ret); goto err; } PCA_INFO("temp = (%d,%d), (OVP,OCP,OTP) = (%d,%d,%d)\n", status.temp1, status.temp_level, status.ovp, status.ocp, status.otp); if (status.ocp || status.otp || status.ovp) goto err; return true; err: sinfo->hardreset_ta = true; return false; } static bool __dv2_check_dvchg_ibusocp(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, i, ibus, acc = 0; struct dv2_algo_data *data = info->data; u32 ibusocp; if (!data->is_dvchg_en[DV2_DVCHG_MASTER]) return true; ibusocp = __dv2_get_dvchg_ibusocp(info, data->ita_setting); for (i = DV2_DVCHG_MASTER; i < DV2_DVCHG_MAX; i++) { if (!data->is_dvchg_en[i]) continue; prop_chgalgo_get_adc_accuracy(data->pca_dvchg[i], PCA_ADCCHAN_IBUS, &acc, &acc); ret = prop_chgalgo_get_adc(data->pca_dvchg[i], PCA_ADCCHAN_IBUS, &ibus, &ibus); if (ret < 0) { PCA_ERR("get ibus fail(%d)\n", ret); return false; } PCA_INFO("(%s)ibus(%d+-%dmA), ibusocp(%dmA)\n", __dv2_dvchg_role_name[i], ibus, acc, ibusocp); if (ibus > acc) ibus -= acc; if (ibus > ibusocp) { PCA_ERR("(%s)ibus(%dmA) > ibusocp(%dmA)\n", __dv2_dvchg_role_name[i], ibus, ibusocp); return false; } } return true; } static bool __dv2_check_ta_ibusocp(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 itaocp; if (!auth_data->support_meas_cap) return true; ret = __dv2_get_ta_cap(info); if (ret < 0) { PCA_ERR("get ta cap fail(%d)\n", ret); goto err; } itaocp = __dv2_get_itaocp(info); PCA_INFO("ita(%dmA), itaocp(%dmA)\n", data->ita_measure, itaocp); if (data->ita_measure > itaocp) { PCA_ERR("ita(%dmA) > itaocp(%dmA)\n", data->ita_measure, itaocp); /* double confirm using dvchg */ if (!__dv2_check_dvchg_ibusocp(info, sinfo)) goto err; } return true; err: sinfo->hardreset_ta = true; return false; } /* * Check VBUS voltage of divider charger * return false if VBUS is over voltage otherwise return true */ static bool __dv2_check_dvchg_vbusovp(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, vbus, i; struct dv2_algo_data *data = info->data; u32 vbusovp; vbusovp = __dv2_get_dvchg_vbusovp(info, data->ita_setting); for (i = DV2_DVCHG_MASTER; i < DV2_DVCHG_MAX; i++) { if (!data->is_dvchg_en[i]) continue; ret = prop_chgalgo_get_adc(data->pca_dvchg[i], PCA_ADCCHAN_VBUS, &vbus, &vbus); if (ret < 0) { PCA_ERR("get vbus fail(%d)\n", ret); return false; } PCA_INFO("(%s)vbus(%dmV), vbusovp(%dmV)\n", __dv2_dvchg_role_name[i], vbus, vbusovp); if (vbus > vbusovp) { PCA_ERR("(%s)vbus(%dmV) > vbusovp(%dmV)\n", __dv2_dvchg_role_name[i], vbus, vbusovp); return false; } } return true; } static bool __dv2_check_vbatovp(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, vbat; u32 vbatovp; vbatovp = __dv2_get_vbatovp(info); ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) { PCA_ERR("get vbat fail(%d)\n", ret); return false; } PCA_INFO("vbat(%dmV), vbatovp(%dmV)\n", vbat, vbatovp); if (vbat > vbatovp) { PCA_ERR("vbat(%dmV) > vbatovp(%dmV)\n", vbat, vbatovp); return false; } return true; } static bool __dv2_check_ibatocp(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, ibat; struct dv2_algo_data *data = info->data; u32 ibatocp; if (!data->is_dvchg_en[DV2_DVCHG_MASTER]) return true; ibatocp = __dv2_get_ibatocp(info, data->ita_setting); ret = __dv2_get_adc(info, PCA_ADCCHAN_IBAT, &ibat); if (ret < 0) { PCA_ERR("get ibat fail(%d)\n", ret); return false; } PCA_INFO("ibat(%dmA), ibatocp(%dmA)\n", ibat, ibatocp); if (ibat > ibatocp) { PCA_ERR("ibat(%dmA) > ibatocp(%dmA)\n", ibat, ibatocp); return false; } return true; } struct dv2_thermal_data { const char *name; int temp; enum dv2_thermal_level *temp_level; int *temp_level_def; int *curlmt; int recovery_area; }; static bool __dv2_check_thermal_level(struct dv2_algo_info *info, struct dv2_thermal_data *tdata) { if (tdata->temp >= tdata->temp_level_def[DV2_THERMAL_VERY_HOT]) { if (tdata->curlmt[DV2_THERMAL_VERY_HOT] == 0) return true; PCA_ERR("%s(%d) is over max(%d)\n", tdata->name, tdata->temp, tdata->temp_level_def[DV2_THERMAL_VERY_HOT]); return false; } if (tdata->temp <= tdata->temp_level_def[DV2_THERMAL_VERY_COLD]) { if (tdata->curlmt[DV2_THERMAL_VERY_COLD] == 0) return true; PCA_ERR("%s(%d) is under min(%d)\n", tdata->name, tdata->temp, tdata->temp_level_def[DV2_THERMAL_VERY_COLD]); return false; } switch (*tdata->temp_level) { case DV2_THERMAL_COLD: if (tdata->temp >= (tdata->temp_level_def[DV2_THERMAL_COLD] + tdata->recovery_area)) *tdata->temp_level = DV2_THERMAL_VERY_COOL; break; case DV2_THERMAL_VERY_COOL: if (tdata->temp >= (tdata->temp_level_def[DV2_THERMAL_VERY_COOL] + tdata->recovery_area)) *tdata->temp_level = DV2_THERMAL_COOL; else if (tdata->temp <= tdata->temp_level_def[DV2_THERMAL_COLD] && tdata->curlmt[DV2_THERMAL_COLD] > 0) *tdata->temp_level = DV2_THERMAL_COLD; break; case DV2_THERMAL_COOL: if (tdata->temp >= (tdata->temp_level_def[DV2_THERMAL_COOL] + tdata->recovery_area)) *tdata->temp_level = DV2_THERMAL_NORMAL; else if (tdata->temp <= tdata->temp_level_def[DV2_THERMAL_VERY_COOL] && tdata->curlmt[DV2_THERMAL_VERY_COOL] > 0) *tdata->temp_level = DV2_THERMAL_VERY_COOL; break; case DV2_THERMAL_NORMAL: if (tdata->temp >= tdata->temp_level_def[DV2_THERMAL_WARM] && tdata->curlmt[DV2_THERMAL_WARM] > 0) *tdata->temp_level = DV2_THERMAL_WARM; else if (tdata->temp <= tdata->temp_level_def[DV2_THERMAL_COOL] && tdata->curlmt[DV2_THERMAL_COOL] > 0) *tdata->temp_level = DV2_THERMAL_COOL; break; case DV2_THERMAL_WARM: if (tdata->temp <= (tdata->temp_level_def[DV2_THERMAL_WARM] - tdata->recovery_area)) *tdata->temp_level = DV2_THERMAL_NORMAL; else if (tdata->temp >= tdata->temp_level_def[DV2_THERMAL_VERY_WARM] && tdata->curlmt[DV2_THERMAL_VERY_WARM] > 0) *tdata->temp_level = DV2_THERMAL_VERY_WARM; break; case DV2_THERMAL_VERY_WARM: if (tdata->temp <= (tdata->temp_level_def[DV2_THERMAL_VERY_WARM] - tdata->recovery_area)) *tdata->temp_level = DV2_THERMAL_WARM; else if (tdata->temp >= tdata->temp_level_def[DV2_THERMAL_HOT] && tdata->curlmt[DV2_THERMAL_HOT] > 0) *tdata->temp_level = DV2_THERMAL_HOT; break; case DV2_THERMAL_HOT: if (tdata->temp <= (tdata->temp_level_def[DV2_THERMAL_HOT] - tdata->recovery_area)) *tdata->temp_level = DV2_THERMAL_VERY_WARM; break; default: PCA_ERR("NO SUCH STATE\n"); return false; } PCA_INFO("%s(%d,%d)\n", tdata->name, tdata->temp, *tdata->temp_level); return true; } /* * Check and adjust battery's temperature level * return false if battery's temperature is over maximum or under minimum * otherwise return true */ static bool __dv2_check_tbat_level(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, tbat; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct dv2_thermal_data tdata = { .name = "tbat", .temp_level_def = desc->tbat_level_def, .curlmt = desc->tbat_curlmt, .temp_level = &data->tbat_level, .recovery_area = desc->tbat_recovery_area, }; ret = __dv2_get_adc(info, PCA_ADCCHAN_TBAT, &tbat); if (ret < 0) { PCA_ERR("get tbat fail(%d)\n", ret); return false; } tdata.temp = tbat; return __dv2_check_thermal_level(info, &tdata); } /* * Check and adjust TA's temperature level * return false if TA's temperature is over maximum * otherwise return true */ static bool __dv2_check_tta_level(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, tta; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; struct dv2_thermal_data tdata = { .name = "tta", .temp_level_def = desc->tta_level_def, .curlmt = desc->tta_curlmt, .temp_level = &data->tta_level, .recovery_area = desc->tta_recovery_area, }; if (!auth_data->support_status) return desc->allow_not_check_ta_status; ret = prop_chgalgo_get_ta_temperature(data->pca_ta, &tta); if (ret < 0) { PCA_ERR("get tta fail(%d)\n", ret); sinfo->hardreset_ta = true; return false; } tdata.temp = tta; return __dv2_check_thermal_level(info, &tdata); } /* * Check and adjust divider charger's temperature level * return false if divider charger's temperature is over maximum * otherwise return true */ static bool __dv2_check_tdvchg_level(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, i, tdvchg; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; char buf[14]; struct dv2_thermal_data tdata = { .temp_level_def = desc->tdvchg_level_def, .curlmt = desc->tdvchg_curlmt, .temp_level = &data->tdvchg_level, .recovery_area = desc->tdvchg_recovery_area, }; for (i = DV2_DVCHG_MASTER; i < DV2_DVCHG_MAX; i++) { if (!data->is_dvchg_en[i]) continue; ret = prop_chgalgo_get_adc(data->pca_dvchg[i], PCA_ADCCHAN_TCHG, &tdvchg, &tdvchg); if (ret < 0) { PCA_ERR("get tdvchg fail(%d)\n", ret); return false; } snprintf(buf, 8 + strlen(__dv2_dvchg_role_name[i]), "tdvchg_%s", __dv2_dvchg_role_name[i]); tdata.name = buf; tdata.temp = tdvchg; if (!__dv2_check_thermal_level(info, &tdata)) return false; } return true; } /* * Check and adjust switching charger's temperature level * return false if switching charger's temperature is over maximum * otherwise return true */ static bool __dv2_check_tswchg_level(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { int ret, tswchg; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct dv2_thermal_data tdata = { .name = "tswchg", .temp_level_def = desc->tswchg_level_def, .curlmt = desc->tswchg_curlmt, .temp_level = &data->tswchg_level, .recovery_area = desc->tswchg_recovery_area, }; if (!data->is_swchg_en) return true; ret = prop_chgalgo_get_adc(data->pca_swchg, PCA_ADCCHAN_TCHG, &tswchg, &tswchg); if (ret < 0) { PCA_ERR("get tswchg fail(%d)\n", ret); return false; } tdata.temp = tswchg; if (!__dv2_check_thermal_level(info, &tdata)) return false; data->aicr_lmt = min(data->aicr_lmt, data->aicr_init_lmt - desc->tswchg_curlmt[data->tswchg_level]); return true; } static bool (*__dv2_safety_check_fn[])(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) = { __dv2_check_ta_status, __dv2_check_ta_ibusocp, __dv2_check_dvchg_vbusovp, __dv2_check_dvchg_ibusocp, __dv2_check_vbatovp, __dv2_check_ibatocp, __dv2_check_tbat_level, __dv2_check_tta_level, __dv2_check_tdvchg_level, __dv2_check_tswchg_level, }; static bool __dv2_algo_safety_check(struct dv2_algo_info *info) { int i; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_DBG("++\n"); for (i = 0; i < ARRAY_SIZE(__dv2_safety_check_fn); i++) { if (!__dv2_safety_check_fn[i](info, &sinfo)) goto err; } return true; err: __dv2_stop(info, &sinfo); return false; } static bool __dv2_is_ta_rdy(struct dv2_algo_info *info) { int ret, i; struct dv2_algo_desc *desc = info->desc; struct dv2_algo_data *data = info->data; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; if (data->ta_ready) return true; auth_data->vcap_min = desc->vta_cap_min; auth_data->vcap_max = desc->vta_cap_max; auth_data->icap_min = desc->ita_cap_min; auth_data->pwr_lmt = false; auth_data->pdp = 0; for (i = 0; i < desc->support_ta_cnt; i++) { if (!data->pca_ta_pool[i]) continue; ret = prop_chgalgo_authenticate_ta(data->pca_ta_pool[i], auth_data); if (ret < 0) { PCA_DBG("%s authenticate fail(%d)\n", desc->support_ta[i], ret); continue; } data->pca_ta = data->pca_ta_pool[i]; PCA_INFO("%s,lmt(%d,%dW),step(%d,%d),cc=%d,cap=%d,status=%d\n", desc->support_ta[i], auth_data->pwr_lmt, auth_data->pdp, auth_data->vta_step, auth_data->ita_step, auth_data->support_cc, auth_data->support_meas_cap, auth_data->support_status); data->ta_ready = true; return true; } return false; } static inline void __dv2_wakeup_algo_thread(struct dv2_algo_data *data) { PCA_DBG("++\n"); atomic_set(&data->wakeup_thread, 1); wake_up_interruptible(&data->wq); } static enum alarmtimer_restart __dv2_algo_timer_cb(struct alarm *alarm, ktime_t now) { struct dv2_algo_data *data = container_of(alarm, struct dv2_algo_data, timer); PCA_DBG("++\n"); __dv2_wakeup_algo_thread(data); return ALARMTIMER_NORESTART; } /* * Check charging time of dv2 algorithm * return false if timeout otherwise return true */ static bool __dv2_algo_check_charging_time(struct dv2_algo_info *info) { struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct timespec etime, dtime; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; get_monotonic_boottime(&etime); dtime = timespec_sub(etime, data->stime); if (dtime.tv_sec >= desc->chg_time_max) { PCA_ERR("dv2 algo timeout(%d, %d)\n", (int)dtime.tv_sec, desc->chg_time_max); __dv2_stop(info, &sinfo); return false; } return true; } static inline int __dv2_plugout_reset(struct dv2_algo_info *info, struct dv2_stop_info *sinfo) { struct dv2_algo_data *data = info->data; PCA_DBG("++\n"); data->ta_ready = false; data->run_once = false; return __dv2_stop(info, sinfo); } static int __dv2_notify_hardreset_hdlr(struct dv2_algo_info *info) { struct dv2_stop_info sinfo = { .reset_ta = false, .hardreset_ta = false, }; PCA_INFO("++\n"); return __dv2_plugout_reset(info, &sinfo); } static int __dv2_notify_detach_hdlr(struct dv2_algo_info *info) { struct dv2_stop_info sinfo = { .reset_ta = false, .hardreset_ta = false, }; PCA_INFO("++\n"); return __dv2_plugout_reset(info, &sinfo); } static int __dv2_notify_hwerr_hdlr(struct dv2_algo_info *info) { struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; PCA_INFO("++\n"); return __dv2_stop(info, &sinfo); } static int __dv2_notify_ibusucpf_hdlr(struct dv2_algo_info *info) { int ret, ibus; struct dv2_algo_data *data = info->data; if (data->ignore_ibusucpf) { PCA_INFO("ignore ibusucpf\n"); data->ignore_ibusucpf = false; return 0; } if (!data->is_dvchg_en[DV2_DVCHG_MASTER]) { PCA_INFO("master dvchg is off\n"); return 0; } /* Last chance */ ret = prop_chgalgo_get_adc(data->pca_dvchg[DV2_DVCHG_MASTER], PCA_ADCCHAN_IBUS, &ibus, &ibus); if (ret < 0) { PCA_ERR("get dvchg ibus fail(%d)\n", ret); goto out; } if (ibus < DV2_IBUSUCPF_RECHECK) { PCA_ERR("ibus(%d) < recheck(%d)\n", ibus, DV2_IBUSUCPF_RECHECK); goto out; } PCA_INFO("recheck ibus and it is not ucp\n"); return 0; out: return __dv2_notify_hwerr_hdlr(info); } static int __dv2_notify_vbatovp_alarm_hdlr(struct dv2_algo_info *info) { int ret; struct dv2_algo_data *data = info->data; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; if (data->state == DV2_ALGO_STOP) return 0; PCA_INFO("++\n"); ret = prop_chgalgo_reset_vbatovp_alarm( data->pca_dvchg[DV2_DVCHG_MASTER]); if (ret < 0) { PCA_ERR("reset vbatovp alarm fail(%d)\n", ret); return __dv2_stop(info, &sinfo); } return 0; } static int __dv2_notify_vbusovp_alarm_hdlr(struct dv2_algo_info *info) { int ret; struct dv2_algo_data *data = info->data; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; if (data->state == DV2_ALGO_STOP) return 0; PCA_INFO("++\n"); ret = prop_chgalgo_reset_vbusovp_alarm( data->pca_dvchg[DV2_DVCHG_MASTER]); if (ret < 0) { PCA_ERR("reset vbusovp alarm fail(%d)\n", ret); return __dv2_stop(info, &sinfo); } return 0; } static int (*__dv2_notify_pre_hdlr[PCA_NOTIEVT_MAX])(struct dv2_algo_info *info) = { [PCA_NOTIEVT_DETACH] = __dv2_notify_detach_hdlr, [PCA_NOTIEVT_HARDRESET] = __dv2_notify_hardreset_hdlr, [PCA_NOTIEVT_VBUSOVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_IBUSOCP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_IBUSUCP_FALL] = __dv2_notify_ibusucpf_hdlr, [PCA_NOTIEVT_VBATOVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_IBATOCP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_VOUTOVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_VDROVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_VBATOVP_ALARM] = __dv2_notify_vbatovp_alarm_hdlr, }; static int (*__dv2_notify_post_hdlr[PCA_NOTIEVT_MAX])(struct dv2_algo_info *info) = { [PCA_NOTIEVT_DETACH] = __dv2_notify_detach_hdlr, [PCA_NOTIEVT_HARDRESET] = __dv2_notify_hardreset_hdlr, [PCA_NOTIEVT_VBUSOVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_IBUSOCP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_IBUSUCP_FALL] = __dv2_notify_ibusucpf_hdlr, [PCA_NOTIEVT_VBATOVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_IBATOCP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_VOUTOVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_VDROVP] = __dv2_notify_hwerr_hdlr, [PCA_NOTIEVT_VBATOVP_ALARM] = __dv2_notify_vbatovp_alarm_hdlr, [PCA_NOTIEVT_VBUSOVP_ALARM] = __dv2_notify_vbusovp_alarm_hdlr, }; static int __dv2_pre_handle_notify_evt(struct dv2_algo_info *info) { int i; struct dv2_algo_data *data = info->data; mutex_lock(&data->notify_lock); PCA_DBG("0x%08X\n", data->notify); for (i = 0; i < PCA_NOTIEVT_MAX; i++) { if ((data->notify & BIT(i)) && __dv2_notify_pre_hdlr[i]) { data->notify &= ~BIT(i); mutex_unlock(&data->notify_lock); __dv2_notify_pre_hdlr[i](info); mutex_lock(&data->notify_lock); } } mutex_unlock(&data->notify_lock); return 0; } static int __dv2_post_handle_notify_evt(struct dv2_algo_info *info) { int i; struct dv2_algo_data *data = info->data; mutex_lock(&data->notify_lock); PCA_DBG("0x%08X\n", data->notify); for (i = 0; i < PCA_NOTIEVT_MAX; i++) { if ((data->notify & BIT(i)) && __dv2_notify_post_hdlr[i]) { data->notify &= ~BIT(i); mutex_unlock(&data->notify_lock); __dv2_notify_post_hdlr[i](info); mutex_lock(&data->notify_lock); } } mutex_unlock(&data->notify_lock); return 0; } static int __dv2_dump_charging_info(struct dv2_algo_info *info) { int ret, i; int vbus, ibus[DV2_DVCHG_MAX] = {0}, ibus_swchg = 0, vbat, ibat; struct dv2_algo_data *data = info->data; /* vbus */ ret = __dv2_get_adc(info, PCA_ADCCHAN_VBUS, &vbus); if (ret < 0) PCA_ERR("get vbus fail\n"); /* ibus */ for (i = 0; i < DV2_DVCHG_MAX; i++) { if (!data->is_dvchg_en[i]) continue; ret = prop_chgalgo_get_adc(data->pca_dvchg[i], PCA_ADCCHAN_IBUS, &ibus[i], &ibus[i]); if (ret < 0) { PCA_ERR("get %s ibus fail\n", __dv2_dvchg_role_name[i]); continue; } } if (data->is_swchg_en) { ret = prop_chgalgo_get_adc(data->pca_swchg, PCA_ADCCHAN_IBUS, &ibus_swchg, &ibus_swchg); if (ret < 0) PCA_ERR("get swchg ibus fail\n"); } /* vbat */ ret = __dv2_get_adc(info, PCA_ADCCHAN_VBAT, &vbat); if (ret < 0) PCA_ERR("get vbat fail(%d)\n", ret); /* ibat */ ret = __dv2_get_adc(info, PCA_ADCCHAN_IBAT, &ibat); if (ret < 0) PCA_ERR("get ibat fail(%d)\n", ret); ret = __dv2_get_ta_cap_by_supportive(info, &data->vta_measure, &data->ita_measure); if (ret < 0) PCA_ERR("get ta measure cap fail(%d)\n", ret); PCA_INFO("vbus,ibus(master,slave,sw),vbat,ibat=%d,(%d,%d,%d),%d,%d\n", vbus, ibus[DV2_DVCHG_MASTER], ibus[DV2_DVCHG_SLAVE], ibus_swchg, vbat, ibat); PCA_INFO("vta,ita(set,meas)=(%d,%d),(%d,%d),force_cv=%d\n", data->vta_setting, data->vta_measure, data->ita_setting, data->ita_measure, data->force_ta_cv); return 0; } static int __dv2_algo_threadfn(void *param) { struct dv2_algo_info *info = param; struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; struct prop_chgalgo_ta_auth_data *auth_data = &data->ta_auth_data; u32 sec, ms, polling_interval; ktime_t ktime; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; while (!kthread_should_stop()) { wait_event_interruptible(data->wq, atomic_read(&data->wakeup_thread)); pm_stay_awake(info->dev); if (atomic_read(&data->stop_thread)) { pm_relax(info->dev); break; } atomic_set(&data->wakeup_thread, 0); mutex_lock(&data->lock); PCA_INFO("state = %s\n", __dv2_algo_state_name[data->state]); if (atomic_read(&data->stop_algo)) __dv2_stop(info, &sinfo); __dv2_pre_handle_notify_evt(info); if (data->state != DV2_ALGO_STOP) { __dv2_algo_check_charging_time(info); __dv2_calculate_vbat_ircmp(info); __dv2_select_vbat_cv(info); __dv2_dump_charging_info(info); } switch (data->state) { case DV2_ALGO_INIT: __dv2_algo_init(info); break; case DV2_ALGO_MEASURE_R: __dv2_algo_measure_r(info); break; case DV2_ALGO_SS_SWCHG: __dv2_algo_ss_swchg(info); break; case DV2_ALGO_SS_DVCHG: __dv2_algo_ss_dvchg(info); break; case DV2_ALGO_CC_CV: __dv2_algo_cc_cv(info); break; case DV2_ALGO_STOP: PCA_INFO("DV2 ALGO STOP\n"); break; default: PCA_ERR("NO SUCH STATE\n"); break; } __dv2_post_handle_notify_evt(info); if (data->state != DV2_ALGO_STOP) { if (!__dv2_algo_safety_check(info)) goto cont; __dv2_dump_charging_info(info); if (data->state == DV2_ALGO_CC_CV && auth_data->support_cc && !data->force_ta_cv) polling_interval = desc->polling_interval; else polling_interval = DV2_ALGO_INIT_POLLING_INTERVAL; sec = polling_interval / 1000; ms = polling_interval % 1000; ktime = ktime_set(sec, MS_TO_NS(ms)); alarm_start_relative(&data->timer, ktime); } cont: mutex_unlock(&data->lock); pm_relax(info->dev); } return 0; } /* =================================================================== */ /* DV2 Algo OPS */ /* =================================================================== */ static int dv2_init_algo(struct prop_chgalgo_device *pca) { int ret = 0, i; struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; bool has_ta = false; mutex_lock(&data->lock); PCA_DBG("++\n"); if (data->inited) { PCA_INFO("already inited\n"); goto out; } /* get ta & chg pca device */ data->pca_ta_pool = devm_kzalloc(info->dev, sizeof(struct prop_chgalgo_device *) * desc->support_ta_cnt, GFP_KERNEL); if (!data->pca_ta_pool) { ret = -ENOMEM; goto out; } for (i = 0; i < desc->support_ta_cnt; i++) { data->pca_ta_pool[i] = prop_chgalgo_dev_get_by_name(desc->support_ta[i]); if (data->pca_ta_pool[i]) { has_ta = true; continue; } PCA_ERR("no %s\n", desc->support_ta[i]); } if (!has_ta) { ret = -ENODEV; goto out; } data->pca_swchg = prop_chgalgo_dev_get_by_name("pca_chg_swchg"); if (!data->pca_swchg) { PCA_ERR("get pca_swchg fail\n"); ret = -ENODEV; goto out; } data->pca_dvchg[DV2_DVCHG_MASTER] = prop_chgalgo_dev_get_by_name("pca_chg_dvchg"); if (!data->pca_dvchg[DV2_DVCHG_MASTER]) { PCA_ERR("get pca_dvchg fail\n"); ret = -ENODEV; goto out; } data->pca_dvchg[DV2_DVCHG_SLAVE] = prop_chgalgo_dev_get_by_name("pca_chg_dvchg_slave"); if (!data->pca_dvchg[DV2_DVCHG_SLAVE]) PCA_ERR("get pca_dvchg_slave fail\n"); data->pca_hv_dvchg = prop_chgalgo_dev_get_by_name("pca_chg_hv_dvchg"); if (!data->pca_hv_dvchg) PCA_ERR("get pca_hv_dvchg fail\n"); data->inited = true; PCA_INFO("successfully\n"); out: mutex_unlock(&data->lock); return ret; } static bool dv2_is_algo_ready(struct prop_chgalgo_device *pca) { int ret; bool rdy = true; struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; struct dv2_algo_desc *desc = info->desc; u32 soc; mutex_lock(&data->lock); PCA_DBG("++\n"); if (!data->inited) { rdy = false; goto out; } PCA_DBG("run once(%d)\n", data->run_once); if (data->run_once) { if (!(data->notify & DV2_RESET_NOTIFY)) { rdy = false; goto out; } mutex_lock(&data->notify_lock); PCA_INFO("run once but detach/hardreset happened\n"); data->notify &= ~DV2_RESET_NOTIFY; data->run_once = false; data->ta_ready = false; mutex_unlock(&data->notify_lock); } ret = prop_chgalgo_get_soc(data->pca_swchg, &soc); if (ret < 0) { rdy = false; goto out; } if (soc < desc->start_soc_min || soc > desc->start_soc_max) { PCA_INFO("soc(%d) not in range(%d~%d)\n", soc, desc->start_soc_min, desc->start_soc_max); rdy = false; goto out; } if (!__dv2_is_ta_rdy(info)) rdy = false; out: mutex_unlock(&data->lock); return rdy; } static int dv2_start_algo(struct prop_chgalgo_device *pca) { int ret = 0; struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; mutex_lock(&data->lock); PCA_DBG("++\n"); if (!data->inited || !data->ta_ready) goto out; ret = __dv2_start(info); if (ret < 0) PCA_ERR("start dv2 algo fail\n"); out: mutex_unlock(&data->lock); return ret; } static bool dv2_is_algo_running(struct prop_chgalgo_device *pca) { struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; bool running = true; if (!mutex_trylock(&data->lock)) goto out; if (!data->inited) { running = false; goto out_unlock; } running = !(data->state == DV2_ALGO_STOP); PCA_DBG("running = %d\n", running); out_unlock: mutex_unlock(&data->lock); out: return running; } static int dv2_plugout_reset(struct prop_chgalgo_device *pca) { int ret = 0; struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; struct dv2_stop_info sinfo = { .reset_ta = false, .hardreset_ta = false, }; mutex_lock(&data->lock); PCA_DBG("++\n"); if (!data->inited) goto out; ret = __dv2_plugout_reset(info, &sinfo); out: mutex_unlock(&data->lock); return ret; } static int dv2_stop_algo(struct prop_chgalgo_device *pca, bool rerun) { int ret = 0; struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; struct dv2_stop_info sinfo = { .reset_ta = true, .hardreset_ta = false, }; atomic_set(&data->stop_algo, 1); mutex_lock(&data->lock); PCA_DBG("rerun %d\n", rerun); if (!data->inited) goto out; ret = __dv2_stop(info, &sinfo); if (rerun) data->run_once = false; out: mutex_unlock(&data->lock); return ret; } static int dv2_notifier_call(struct prop_chgalgo_device *pca, struct prop_chgalgo_notify *notify) { int ret = 0; struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; mutex_lock(&data->notify_lock); if (data->state == DV2_ALGO_STOP) { if ((notify->evt == PCA_NOTIEVT_DETACH || notify->evt == PCA_NOTIEVT_HARDRESET) && data->run_once) { PCA_INFO("detach/hardreset && run once after stop\n"); data->notify |= BIT(notify->evt); } goto out; } PCA_INFO("%d %s\n", notify->src, prop_chgalgo_notify_evt_tostring(notify->evt)); switch (notify->evt) { case PCA_NOTIEVT_DETACH: case PCA_NOTIEVT_HARDRESET: case PCA_NOTIEVT_VBUSOVP: case PCA_NOTIEVT_IBUSOCP: case PCA_NOTIEVT_IBUSUCP_FALL: case PCA_NOTIEVT_VBATOVP: case PCA_NOTIEVT_IBATOCP: case PCA_NOTIEVT_VOUTOVP: case PCA_NOTIEVT_VDROVP: case PCA_NOTIEVT_VBATOVP_ALARM: case PCA_NOTIEVT_VBUSOVP_ALARM: data->notify |= BIT(notify->evt); break; default: ret = -EINVAL; goto out; } __dv2_wakeup_algo_thread(data); out: mutex_unlock(&data->notify_lock); return ret; } static int dv2_thermal_throttling(struct prop_chgalgo_device *pca, int mA) { struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; PCA_INFO("%d\n", mA); mutex_lock(&data->ext_lock); if (data->thermal_throttling != mA) { data->thermal_throttling = mA; __dv2_wakeup_algo_thread(data); } mutex_unlock(&data->ext_lock); return 0; } static int dv2_set_jeita_vbat_cv(struct prop_chgalgo_device *pca, int mV) { struct dv2_algo_info *info = prop_chgalgo_get_drvdata(pca); struct dv2_algo_data *data = info->data; PCA_INFO("%d\n", mV); mutex_lock(&data->ext_lock); if (data->jeita_vbat_cv != mV) { data->jeita_vbat_cv = mV; __dv2_wakeup_algo_thread(data); } mutex_unlock(&data->ext_lock); return 0; } static struct prop_chgalgo_algo_ops pca_dv2_ops = { .init_algo = dv2_init_algo, .is_algo_ready = dv2_is_algo_ready, .start_algo = dv2_start_algo, .is_algo_running = dv2_is_algo_running, .plugout_reset = dv2_plugout_reset, .stop_algo = dv2_stop_algo, .notifier_call = dv2_notifier_call, .thermal_throttling = dv2_thermal_throttling, .set_jeita_vbat_cv = dv2_set_jeita_vbat_cv, }; static SIMPLE_PCA_ALGO_DESC(pca_algo_dv2, pca_dv2_ops); #define DV2_DT_VALPROP_ARR(name, sz) \ {#name, offsetof(struct dv2_algo_desc, name), sz} #define DV2_DT_VALPROP(name) \ DV2_DT_VALPROP_ARR(name, 1) struct dv2_dtprop { const char *name; size_t offset; size_t sz; }; static inline void dv2_parse_dt_u32(struct device_node *np, void *desc, const struct dv2_dtprop *props, int prop_cnt) { int i; for (i = 0; i < prop_cnt; i++) { if (unlikely(!props[i].name)) continue; of_property_read_u32(np, props[i].name, desc + props[i].offset); } } static inline void dv2_parse_dt_u32_arr(struct device_node *np, void *desc, const struct dv2_dtprop *props, int prop_cnt) { int i; for (i = 0; i < prop_cnt; i++) { if (unlikely(!props[i].name)) continue; of_property_read_u32_array(np, props[i].name, desc + props[i].offset, props[i].sz); } } static inline int __of_property_read_s32_array(const struct device_node *np, const char *propname, s32 *out_values, size_t sz) { return of_property_read_u32_array(np, propname, (u32 *)out_values, sz); } static inline void dv2_parse_dt_s32_arr(struct device_node *np, void *desc, const struct dv2_dtprop *props, int prop_cnt) { int i; for (i = 0; i < prop_cnt; i++) { if (unlikely(!props[i].name)) continue; __of_property_read_s32_array(np, props[i].name, desc + props[i].offset, props[i].sz); } } static const struct dv2_dtprop dv2_dtprops_u32[] = { DV2_DT_VALPROP(polling_interval), DV2_DT_VALPROP(ta_cv_ss_repeat_tmin), DV2_DT_VALPROP(vbat_cv), DV2_DT_VALPROP(start_soc_min), DV2_DT_VALPROP(start_soc_max), DV2_DT_VALPROP(start_vbat_max), DV2_DT_VALPROP(idvchg_term), DV2_DT_VALPROP(idvchg_step), DV2_DT_VALPROP(idvchg_ss_init), DV2_DT_VALPROP(idvchg_ss_step), DV2_DT_VALPROP(idvchg_ss_step1), DV2_DT_VALPROP(idvchg_ss_step2), DV2_DT_VALPROP(idvchg_ss_step1_vbat), DV2_DT_VALPROP(idvchg_ss_step2_vbat), DV2_DT_VALPROP(ta_blanking), DV2_DT_VALPROP(swchg_aicr), DV2_DT_VALPROP(swchg_ichg), DV2_DT_VALPROP(swchg_aicr_ss_init), DV2_DT_VALPROP(swchg_aicr_ss_step), DV2_DT_VALPROP(swchg_off_vbat), DV2_DT_VALPROP(force_ta_cv_vbat), DV2_DT_VALPROP(chg_time_max), DV2_DT_VALPROP(tta_recovery_area), DV2_DT_VALPROP(tbat_recovery_area), DV2_DT_VALPROP(tdvchg_recovery_area), DV2_DT_VALPROP(tswchg_recovery_area), DV2_DT_VALPROP(ifod_threshold), DV2_DT_VALPROP(rsw_min), DV2_DT_VALPROP(ircmp_rbat), DV2_DT_VALPROP(ircmp_vclamp), DV2_DT_VALPROP(vta_cap_min), DV2_DT_VALPROP(vta_cap_max), DV2_DT_VALPROP(ita_cap_min), }; static const struct dv2_dtprop dv2_dtprops_u32_array[] = { DV2_DT_VALPROP_ARR(ita_level, DV2_RCABLE_MAX), DV2_DT_VALPROP_ARR(rcable_level, DV2_RCABLE_MAX), DV2_DT_VALPROP_ARR(ita_level_dual, DV2_RCABLE_MAX), DV2_DT_VALPROP_ARR(rcable_level_dual, DV2_RCABLE_MAX), }; static const struct dv2_dtprop dv2_dtprops_s32_array[] = { DV2_DT_VALPROP_ARR(tta_level_def, DV2_THERMAL_MAX), DV2_DT_VALPROP_ARR(tta_curlmt, DV2_THERMAL_MAX), DV2_DT_VALPROP_ARR(tbat_level_def, DV2_THERMAL_MAX), DV2_DT_VALPROP_ARR(tbat_curlmt, DV2_THERMAL_MAX), DV2_DT_VALPROP_ARR(tdvchg_level_def, DV2_THERMAL_MAX), DV2_DT_VALPROP_ARR(tdvchg_curlmt, DV2_THERMAL_MAX), DV2_DT_VALPROP_ARR(tswchg_level_def, DV2_THERMAL_MAX), DV2_DT_VALPROP_ARR(tswchg_curlmt, DV2_THERMAL_MAX), }; static const char * const __dv2_dev_node_name[] = { "mtk_pe50", }; static int dv2_parse_dt(struct dv2_algo_info *info) { int i, ret; struct dv2_algo_desc *desc; struct device_node *np; desc = devm_kzalloc(info->dev, sizeof(*desc), GFP_KERNEL); if (!desc) return -ENOMEM; info->desc = desc; memcpy(desc, &algo_desc_defval, sizeof(*desc)); for (i = 0; i < ARRAY_SIZE(__dv2_dev_node_name); i++) { np = of_find_node_by_name(NULL, __dv2_dev_node_name[i]); if (np) { PCA_ERR("find node %s\n", __dv2_dev_node_name[i]); break; } } if (i == ARRAY_SIZE(__dv2_dev_node_name)) { PCA_ERR("no device node found\n"); return -EINVAL; } ret = of_property_count_strings(np, "support_ta"); if (ret < 0) return ret; desc->support_ta_cnt = ret; desc->support_ta = devm_kzalloc(info->dev, ret * sizeof(char *), GFP_KERNEL); if (!desc->support_ta) return -ENOMEM; for (i = 0; i < desc->support_ta_cnt; i++) { ret = of_property_read_string_index(np, "support_ta", i, &desc->support_ta[i]); if (ret < 0) return ret; PCA_INFO("support ta(%s)\n", desc->support_ta[i]); } desc->allow_not_check_ta_status = of_property_read_bool(np, "allow_not_check_ta_status"); dv2_parse_dt_u32(np, (void *)desc, dv2_dtprops_u32, ARRAY_SIZE(dv2_dtprops_u32)); dv2_parse_dt_u32_arr(np, (void *)desc, dv2_dtprops_u32_array, ARRAY_SIZE(dv2_dtprops_u32_array)); dv2_parse_dt_s32_arr(np, (void *)desc, dv2_dtprops_s32_array, ARRAY_SIZE(dv2_dtprops_s32_array)); if (desc->swchg_aicr == 0 || desc->swchg_ichg == 0) { desc->swchg_aicr = 0; desc->swchg_ichg = 0; } return 0; } static int dv2_algo_probe(struct platform_device *pdev) { int ret; struct dv2_algo_info *info; struct dv2_algo_data *data; dev_info(&pdev->dev, "%s(%s)\n", __func__, PCA_DV2_ALGO_VERSION); info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) return -ENOMEM; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; info->data = data; info->dev = &pdev->dev; platform_set_drvdata(pdev, info); ret = dv2_parse_dt(info); if (ret < 0) { dev_notice(info->dev, "%s parse dt fail(%d)\n", __func__, ret); return ret; } info->pca = prop_chgalgo_device_register(info->dev, &pca_algo_dv2_desc, info); if (IS_ERR_OR_NULL(info->pca)) { dev_notice(info->dev, "%s reg dv2 algo fail(%d)\n", __func__, ret); return PTR_ERR(info->pca); } /* init algo thread & timer */ mutex_init(&data->notify_lock); mutex_init(&data->lock); mutex_init(&data->ext_lock); init_waitqueue_head(&data->wq); atomic_set(&data->wakeup_thread, 0); atomic_set(&data->stop_thread, 0); data->state = DV2_ALGO_STOP; alarm_init(&data->timer, ALARM_REALTIME, __dv2_algo_timer_cb); data->task = kthread_run(__dv2_algo_threadfn, info, "dv2_algo_task"); if (IS_ERR(data->task)) { ret = PTR_ERR(data->task); dev_notice(info->dev, "%s run task fail(%d)\n", __func__, ret); goto err; } device_init_wakeup(info->dev, true); dev_info(info->dev, "%s successfully\n", __func__); return 0; err: mutex_destroy(&data->ext_lock); mutex_destroy(&data->lock); mutex_destroy(&data->notify_lock); prop_chgalgo_device_unregister(info->pca); return ret; } static int dv2_algo_remove(struct platform_device *pdev) { struct dv2_algo_info *info = platform_get_drvdata(pdev); struct dv2_algo_data *data; if (info) { data = info->data; atomic_set(&data->stop_thread, 1); __dv2_wakeup_algo_thread(data); kthread_stop(data->task); mutex_destroy(&data->ext_lock); mutex_destroy(&data->lock); mutex_destroy(&data->notify_lock); prop_chgalgo_device_unregister(info->pca); } return 0; } static int __maybe_unused dv2_algo_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct dv2_algo_info *info = platform_get_drvdata(pdev); struct dv2_algo_data *data = info->data; dev_info(dev, "%s\n", __func__); mutex_lock(&data->lock); return 0; } static int __maybe_unused dv2_algo_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct dv2_algo_info *info = platform_get_drvdata(pdev); struct dv2_algo_data *data = info->data; dev_info(dev, "%s\n", __func__); mutex_unlock(&data->lock); return 0; } static SIMPLE_DEV_PM_OPS(dv2_algo_pm_ops, dv2_algo_suspend, dv2_algo_resume); static struct platform_device dv2_algo_platdev = { .name = "pca_dv2_algo", .id = PLATFORM_DEVID_NONE, }; static struct platform_driver dv2_algo_platdrv = { .probe = dv2_algo_probe, .remove = dv2_algo_remove, .driver = { .name = "pca_dv2_algo", .owner = THIS_MODULE, .pm = &dv2_algo_pm_ops, }, }; static int __init dv2_algo_init(void) { platform_device_register(&dv2_algo_platdev); return platform_driver_register(&dv2_algo_platdrv); } static void __exit dv2_algo_exit(void) { platform_driver_unregister(&dv2_algo_platdrv); platform_device_unregister(&dv2_algo_platdev); } device_initcall_sync(dv2_algo_init); module_exit(dv2_algo_exit); MODULE_DESCRIPTION("Divide By Two Algorithm For PCA"); MODULE_AUTHOR("ShuFan Lee "); MODULE_VERSION(PCA_DV2_ALGO_VERSION); MODULE_LICENSE("GPL v2"); /* * 2.0.2 * (1) Calculate ZCV after disabling CHG_EN and before enabling HZ * (2) Always clear stop_algo flag, in case it is called from dv2_stop_algo by * other threads. * * 2.0.1 * (1) Add ita tracking mechanism for ta cv mode * (2) Use moving window to calculate ita_gap_per_vstep * (3) Always set protection of HV DV2 to 5V's setting in __dv2_stop * (4) Force charging flow switching to CV mode if CC loop of TA fails * * 2.0.0 * (1) Adapt to prop_chgalgo_class v2.0.0 * (2) Add hv dvchg control (bypass mode) * (3) Fix bug of __dv2_vta_add_gap * (4) Add precision div and percent * (5) Arrange include files by alphabet * (6) Use min/max instead of MIN/MAX * (7) Increase vta_comp when get cali vta if calculated vta is smaller * * 1.0.14 * (1) For TA CV mode, not to increase vta if ita reaches ita_lmt and * there is no difference in ita's measurement * * 1.0.13 * (1) Init DVCHG chip in __dv2_start * * 1.0.12 * (1) For TA CV mode, compatible with TA which only accepts request current * greater than a certain value * * 1.0.11 * (1) Optimize speed of soft start of TA CV mode * (2) Average ita_gap only if new one is larger * * 1.0.10 * (1) Add stop_algo flag to speed up the process * (2) For estimating power, enable charger after setting ichg/aicr * (3) Limit vta to auth_data->vcap_max for set_ta_cap_cv * (4) Stop algo without hardreset if hwerr received during set_ta_cap * * 1.0.9 * (1) Add S/W IR compensation * (2) Skip one run's chance to increase ita after vbat over cv * Always keep cv_lower_bound be 20mV lower than cv * * 1.0.8 * (1) Remove DV2_TA_ACCURACY_IMAX, if ita is over spec, use ibus of charger to * confirm the status * (2) Add allow_not_check_ta_status property in dtsi * (3) Add a rerun paramenter in stop_algo * If it is true, run_once will be cleared * (4) In charging flow with ta_cv, always use 500ms polling interval * (5) Add algo api for thermal throttling * (6) Change auth_data->support_status to auth_data->support_meas_cap * and auth_data->support_status now means status of ta but not cap * (7) Ignore measured resistance if measure cap is not supported by TA * (8) Try dual dvchg for both cc/cv mode * If error occurs, restart and charge with single dvchg * (9) Remove BIF support * (10) Move ita_gap_per_step to TA's authentication data, * and average ita_gap when there's a new one * (11) If detach/hardreset hannened after ALGO_STOP, * reset run once/ta_ready flag * (12) Update vbat_upper_bound to vbat_cv, remove vbus_upper_bound * (13) For TA CV mode, not to increase ITA to maximum limited value but * tracking it as close as possible * (14) Make sure ita is limited by idvchg_cc if aicr/swchg are both > 0 * (15) Use MIN(idvchg_cc, ita_max) to calculate dvchg's ibusocp * (16) Add algo api for jeita vbat cv * (17) Remove cv_readd_cnt used in cc_cv_with_ta_cc * * 1.0.7 * (1) Remove enable_power(true) in dv2_algo_init_with_ta_xx * (2) If curlmt == 0, ignore temperature checking * * 1.0.6 * (1) For ss_dvchg_with_ta_cv, fix idvchg_lmt not initialized, * if vbat > desc->bat_upper_bound * (2) Take DVCHG IBUS ADC's accuracy as reference while checking IBUSOCP * (3) For set_ta_cap_cc, if (ita_measure > ita_setting + DV2_TA_ACCURACY_IMAX), * this TA is classified as not supporting CC mode * (4) Make sure ITAOCP is DV2_TA_ACCURACY_IMAX greater than ita_setting * * 1.0.5 * (1) For measure_r, check ibat/ibus/ita are zero or not before calculating * (2) For TA CV mode, check if (ita + ita_gap_per_vstep > ita_lmt) before * adding vta * (3) Not to check ibusocp/ibatocp before enabling master dvchg * (4) For TA CC mode, directly set opt_vta to vta_measure + DV2_VTA_GAP_VMIN * (5) For dual DVCHG, set DV2_IBUSOCP_RATIO += 10 * (6) Show ibus of swchg in dump_charging_info * * 1.0.4 * (1) Modify flow of INIT, measure vbus_cali after rising vbus * (2) Modify flow of MEASURE_R, calculate R for DV2_ALGO_MEASURE_R_AVG_TIMES * and average after discarding max & min * (3) Handle IBUSUCP_FALL from divider charger * (4) Use HZ instead of power path in INIT * (5) Add flow for TA with CV mode only(with/without status support) * * 1.0.3 * (1) If power limited bit is specified by TA(x watts), the output current * limit is calculated as RoundDown(x/requested output voltage) to the * nearest 50 mA * (2) Modify set TA cap flow to minimize the gap between set and measure so * that TA is able to provide as much power as possible if it has power * limited * (3) Move TA's req istep & vstep to its authentication data * * 1.0.2 * (1) Add retry mechanism if rcable is worse than expected * (2) Use VBUSOVP_ALM to wakeup thread to adjust VBUS and not to set VBUS using * VBAT = CV directly * * 1.0.1 * (1) Add notifier call interface, move out TCPC notifier * (2) Set VTA using vbat_upper_bound after entering DV2_ALGO_CC_CV stage * (3) Use 500ms as initial polling interval and use polling_interval in desc * after entering DV2_ALGO_CC_CV stage * (4) Start to use divider charger's ADC * (5) Add enum of DV2_THERMAL_LEVEL and remove TTA/TBAT/TDVCHG_TEMP_LEVEL * (6) Move safety check functions to function pointer array * (7) Parse support ta count and name from dtsi * (8) Add Master/Slave dvchg charging mode * * 1.0.0 * Initial Release */