kernel-brax3-ubuntu-touch/drivers/power/supply/mtk_pe5.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

4220 lines
112 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/alarmtimer.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include "mtk_charger_algorithm_class.h"
#include "mtk_pe5.h"
static int log_level = PE50_INFO_LEVEL;
module_param(log_level, int, 0644);
static bool algo_waiver_test;
module_param(algo_waiver_test, bool, 0644);
int pe50_get_log_level(void)
{
return log_level;
}
#define MS_TO_NS(msec) ((msec) * (NSEC_PER_MSEC))
/* Parameters */
#define PE50_VTA_INIT 5000 /* mV */
#define PE50_ITA_INIT 3000 /* mA */
#define PE50_TA_WDT_MIN 10000 /* ms */
#define PE50_VTA_GAP_MIN 200 /* mV */
#define PE50_VTA_VAR_MIN 103 /* % */
#define PE50_ITA_TRACKING_GAP 150 /* mA */
#define PE50_DVCHG_VBUSALM_GAP 100 /* mV */
#define PE50_DVCHG_STARTUP_CONVERT_RATIO 210 /* % */
#define PE50_DVCHG_CHARGING_CONVERT_RATIO 202 /* % */
#define PE50_VBUSOVP_RATIO 110
#define PE50_IBUSOCP_RATIO 110
#define PE50_VBATOVP_RATIO 110
#define PE50_IBATOCP_RATIO 110
#define PE50_ITAOCP_RATIO 110
#define PE50_IBUSUCPF_RECHECK 250 /* mA */
#define PE50_VBUS_CALI_THRESHOLD 150 /* mV */
#define PE50_CV_LOWER_BOUND_GAP 50 /* mV */
#define PE50_INIT_POLLING_INTERVAL 500 /* ms */
#define PE50_INIT_RETRY_MAX 0
#define PE50_MEASURE_R_RETRY_MAX 3
#define PE50_MEASURE_R_AVG_TIMES 10
#define PE50_VSYS_UPPER_BOUND 4700 /* mV */
#define PE50_VSYS_UPPER_BOUND_GAP 40 /* mV */
#define PE50_HWERR_NOTIFY \
(BIT(EVT_VBUSOVP) | BIT(EVT_IBUSOCP) | BIT(EVT_VBATOVP) | \
BIT(EVT_IBATOCP) | BIT(EVT_VOUTOVP) | BIT(EVT_VDROVP) | \
BIT(EVT_IBUSUCP_FALL))
#define PE50_RESET_NOTIFY \
(BIT(EVT_DETACH) | BIT(EVT_HARDRESET))
static const char *const pe50_dvchg_role_name[PE50_DVCHG_MAX] = {
"master", "slave",
};
static const char *const pe50_algo_state_name[PE50_ALGO_STATE_MAX] = {
"INIT", "MEASURE_R", "SS_SWCHG", "SS_DVCHG", "CC_CV", "STOP",
};
/* If there's no property in dts, these values will be applied */
static const struct pe50_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,
};
/*
* @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 pe50_stop_info {
bool hardreset_ta;
bool reset_ta;
};
static inline enum chg_idx to_chgidx(enum pe50_dvchg_role role)
{
if (role == PE50_DVCHG_MASTER)
return DVCHG1;
return DVCHG2;
}
/* Check if there is error notification coming from H/W */
static bool pe50_is_hwerr_notified(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
bool err = false;
u32 hwerr = PE50_HWERR_NOTIFY;
mutex_lock(&data->notify_lock);
if (data->ignore_ibusucpf)
hwerr &= ~BIT(EVT_IBUSUCP_FALL);
err = !!(data->notify & hwerr);
if (err)
PE50_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 pe50_stop(struct pe50_algo_info *info, struct pe50_stop_info *sinfo);
static int pe50_get_adc(struct pe50_algo_info *info, enum pe50_adc_channel chan,
int *val)
{
struct pe50_algo_data *data = info->data;
int ret, i, ibus;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
if (atomic_read(&data->stop_algo)) {
PE50_INFO("stop algo\n");
goto stop;
}
*val = 0;
if (chan == PE50_ADCCHAN_IBUS) {
for (i = PE50_DVCHG_MASTER; i < PE50_DVCHG_MAX; i++) {
if (!data->is_dvchg_en[i])
continue;
ret = pe50_hal_get_adc(info->alg, to_chgidx(i),
PE50_ADCCHAN_IBUS, &ibus);
if (ret < 0) {
PE50_ERR("get dvchg ibus fail(%d)\n", ret);
return ret;
}
*val += ibus;
}
if (data->is_swchg_en) {
ret = pe50_hal_get_adc(info->alg, CHG1,
PE50_ADCCHAN_IBUS, &ibus);
if (ret < 0) {
PE50_ERR("get swchg ibus fail(%d)\n", ret);
return ret;
}
*val += ibus;
}
return 0;
}
return pe50_hal_get_adc(info->alg, DVCHG1, chan, val);
stop:
pe50_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 pe50_vout2vbus(struct pe50_algo_info *info, u32 vout)
{
struct pe50_algo_data *data = info->data;
u32 ratio = data->is_dvchg_en[PE50_DVCHG_MASTER] ?
PE50_DVCHG_CHARGING_CONVERT_RATIO :
PE50_DVCHG_STARTUP_CONVERT_RATIO;
return percent(vout, ratio);
}
/*
* Maximum PE50_VTA_VAR_MIN(%) variation (from PD's sepcification)
* Keep vta_setting PE50_VTA_VAR_MIN(%) higher than vta_measure
* and make sure it has minimum gap, PE50_VTA_GAP_MIN
*/
static inline u32 pe50_vta_add_gap(struct pe50_algo_info *info, u32 vta)
{
return max(percent(vta, PE50_VTA_VAR_MIN), vta + PE50_VTA_GAP_MIN);
}
/*
* Get output current and voltage measured by TA
* and updates measured data
*/
static inline int pe50_get_ta_cap(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
return pe50_hal_get_ta_output(info->alg, &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 pe50_get_ta_cap_by_supportive(struct pe50_algo_info *info,
int *vta, int *ita)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
if (auth_data->support_meas_cap) {
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_ERR("get ta cap fail(%d)\n", ret);
return ret;
}
*vta = data->vta_measure;
*ita = data->ita_measure;
return 0;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VBUS, vta);
if (ret < 0) {
PE50_ERR("get vbus fail(%d)\n", ret);
return ret;
}
return pe50_get_adc(info, PE50_ADCCHAN_IBUS, ita);
}
/* Calculate ibat from ita */
static inline u32 pe50_cal_ibat(struct pe50_algo_info *info, u32 ita)
{
struct pe50_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 pe50_get_cali_vta(struct pe50_algo_info *info, u32 ita, u32 *vta)
{
int ret, vbat;
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
u32 ibat, vbus, _vta, comp;
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
return ret;
}
ibat = pe50_cal_ibat(info, ita);
vbus = pe50_vout2vbus(info, vbat + div1000(ibat * data->r_sw));
*vta = vbus + (data->vbus_cali + data->vta_comp +
div1000(ita * data->r_cable_by_swchg));
if (data->is_dvchg_en[PE50_DVCHG_MASTER]) {
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_ERR("get ta cap fail(%d)\n", ret);
return ret;
}
_vta = pe50_vta_add_gap(info, data->vta_measure);
if (_vta > *vta) {
comp = _vta - *vta;
data->vta_comp += comp;
PE50_DBG("comp,add=(%d,%d)\n", data->vta_comp, comp);
}
*vta = max(*vta, _vta);
}
if (*vta >= auth_data->vcap_max)
*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 pe50_set_vbus_tracking(struct pe50_algo_info *info)
{
int ret, vbus;
ret = pe50_get_adc(info, PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0) {
PE50_ERR("get vbus fail(%d)\n", ret);
return ret;
}
return pe50_hal_set_vbusovp_alarm(info->alg, DVCHG1,
vbus + PE50_DVCHG_VBUSALM_GAP);
}
/* Calculate power limited ita according to TA's power limitation */
static u32 pe50_get_ita_pwr_lmt_by_vta(struct pe50_algo_info *info, u32 vta)
{
struct pe50_algo_data *data = info->data;
struct pe50_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 pe50_get_ita_tracking_max(u32 ita)
{
return min(percent(ita, PE50_ITAOCP_RATIO),
(u32)(ita + PE50_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
pe50_force_ta_cv(struct pe50_algo_info *info, struct pe50_stop_info *sinfo);
static inline int pe50_set_ta_cap_cc(struct pe50_algo_info *info, u32 vta,
u32 ita)
{
int ret, vbat;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
bool set_opt_vta = true;
bool is_ta_cc = false;
u32 opt_vta;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
if (data->vta_setting == vta && data->ita_setting == ita &&
data->state != PE50_ALGO_INIT)
return 0;
while (true) {
if (atomic_read(&data->stop_algo)) {
PE50_INFO("stop algo\n");
goto stop;
}
/* Check TA's PDP */
data->ita_pwr_lmt = pe50_get_ita_pwr_lmt_by_vta(info, vta);
if (data->ita_pwr_lmt < ita) {
PE50_INFO("ita(%d) > ita_pwr_lmt(%d)\n", ita,
data->ita_pwr_lmt);
ita = data->ita_pwr_lmt;
}
ret = pe50_hal_set_ta_cap(info->alg, vta, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
return ret;
}
msleep(desc->ta_blanking);
if (!data->is_dvchg_en[PE50_DVCHG_MASTER])
break;
ret = pe50_hal_is_ta_cc(info->alg, &is_ta_cc);
if (ret < 0) {
PE50_ERR("get ta cc mode fail(%d)\n", ret);
return ret;
}
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_ERR("get ta cap fail(%d)\n", ret);
return ret;
}
PE50_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 = pe50_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) {
PE50_ERR("vta(%d) over capability(%d)\n", vta,
auth_data->vcap_max);
goto stop;
}
if (pe50_is_hwerr_notified(info)) {
PE50_ERR("H/W error notified\n");
goto stop;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
return ret;
}
if (vbat >= data->vbat_cv) {
PE50_INFO("vbat(%d), decrease ita immediately\n", vbat);
ita -= auth_data->ita_step;
continue;
}
PE50_ERR("Not in cc mode\n");
if (data->ita_measure > pe50_get_ita_tracking_max(data->ita_setting)) {
ret = pe50_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(vta, (u32)auth_data->vcap_max);
}
data->vta_setting = vta;
data->ita_setting = ita;
PE50_INFO("vta,ita = (%d,%d)\n", vta, ita);
pe50_set_vbus_tracking(info);
return 0;
stop:
pe50_stop(info, &sinfo);
return -EIO;
}
/*
* Set TA's output voltage & current by a given current and
* calculated voltage
*/
static inline int pe50_set_ta_cap_cc_by_cali_vta(struct pe50_algo_info *info,
u32 ita)
{
int ret;
u32 vta;
ret = pe50_get_cali_vta(info, ita, &vta);
if (ret < 0) {
PE50_ERR("get cali vta fail(%d)\n", ret);
return ret;
}
return pe50_set_ta_cap_cc(info, vta, ita);
}
static inline void pe50_update_ita_gap(struct pe50_algo_info *info, u32 ita_gap)
{
int i;
u32 val = 0, avg_cnt = PE50_ITA_GAP_WINDOW_SIZE;
struct pe50_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) %
PE50_ITA_GAP_WINDOW_SIZE;
data->ita_gaps[data->ita_gap_window_idx] = ita_gap;
for (i = 0; i < PE50_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 pe50_set_ta_cap_cv(struct pe50_algo_info *info, u32 vta,
u32 ita)
{
int ret, ita_meas_pre, ita_meas_post, vta_meas;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
u32 vstep_cnt, ita_gap, vta_gap;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
if (data->vta_setting == vta && data->ita_setting == ita)
return 0;
while (true) {
if (pe50_is_hwerr_notified(info)) {
PE50_ERR("H/W error notified\n");
goto stop;
}
if (atomic_read(&data->stop_algo)) {
PE50_INFO("stop algo\n");
goto stop;
}
if (vta > auth_data->vcap_max) {
PE50_ERR("vta(%d) over capability(%d)\n", vta,
auth_data->vcap_max);
goto stop;
}
if (ita < auth_data->ita_min) {
PE50_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 = pe50_get_ta_cap_by_supportive(info, &vta_meas,
&ita_meas_pre);
if (ret < 0) {
PE50_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 = pe50_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)) {
PE50_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 = pe50_hal_set_ta_cap(info->alg, vta, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
return ret;
}
if (vta_gap > auth_data->vta_step ||
data->state != PE50_ALGO_SS_DVCHG)
msleep(desc->ta_blanking);
/* Get ta cap after setting */
ret = pe50_get_ta_cap_by_supportive(info, &vta_meas,
&ita_meas_post);
if (ret < 0) {
PE50_ERR("get ta cap by supportive fail(%d)\n", ret);
return ret;
}
if (data->is_dvchg_en[PE50_DVCHG_MASTER] &&
(ita_meas_post > ita_meas_pre) &&
(vta > data->vta_setting)) {
vstep_cnt = precise_div(max(vta, (u32)vta_meas) -
data->vta_setting,
auth_data->vta_step);
ita_gap = precise_div(ita_meas_post - ita_meas_pre,
vstep_cnt);
pe50_update_ita_gap(info, ita_gap);
PE50_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 <= pe50_get_ita_tracking_max(ita))
break;
vta -= auth_data->vta_step;
PE50_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;
PE50_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:
pe50_stop(info, &sinfo);
return -EIO;
}
static inline void pe50_calculate_vbat_ircmp(struct pe50_algo_info *info)
{
int ret, ibat;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 ircmp;
if (!data->is_dvchg_en[PE50_DVCHG_MASTER]) {
data->vbat_ircmp = 0;
return;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_IBAT, &ibat);
if (ret < 0) {
PE50_ERR("get ibat fail(%d)\n", ret);
return;
}
ircmp = max(div1000(ibat * data->r_bat), desc->ircmp_vclamp);
/*
* For safety,
* if state is CC_CV, ircmp can only be smaller than previous one
*/
if (data->state == PE50_ALGO_CC_CV)
ircmp = min(data->vbat_ircmp, ircmp);
data->vbat_ircmp = min(desc->ircmp_vclamp, ircmp);
PE50_INFO("vbat_ircmp(vclamp,ibat,rbat)=%d(%d,%d,%d)\n",
data->vbat_ircmp, desc->ircmp_vclamp, ibat, data->r_bat);
}
static inline void pe50_select_vbat_cv(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 cv = data->vbat_cv;
u32 cv_no_ircmp = desc->vbat_cv;
mutex_lock(&data->ext_lock);
if (data->cv_limit > 0)
cv_no_ircmp = min(cv_no_ircmp, (u32)data->cv_limit);
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 = pe50_hal_set_vbatovp_alarm(info->alg, DVCHG1, cv);
if (ret < 0) {
PE50_ERR("set vbatovp alarm fail(%d)\n", ret);
goto out;
}
data->vbat_cv = cv;
data->cv_lower_bound = data->vbat_cv - PE50_CV_LOWER_BOUND_GAP;
out:
PE50_INFO("vbat_cv(org,limit,no_ircmp,low_bound)=%d(%d,%d,%d,%d)\n",
data->vbat_cv, desc->vbat_cv, data->cv_limit,
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 pe50_get_ita_lmt(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 ita = data->ita_lmt;
mutex_lock(&data->ext_lock);
if (data->input_current_limit >= 0)
ita = min(ita, (u32)data->input_current_limit);
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]);
}
PE50_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->input_current_limit);
mutex_unlock(&data->ext_lock);
return ita;
}
static inline int pe50_get_idvchg_lmt(struct pe50_algo_info *info)
{
u32 ita_lmt, idvchg_lmt;
struct pe50_algo_data *data = info->data;
ita_lmt = pe50_get_ita_lmt(info);
idvchg_lmt = min(data->idvchg_cc, ita_lmt);
PE50_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 pe50_get_dvchg_vbusovp(struct pe50_algo_info *info, u32 ita)
{
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 vout, ibat;
ibat = pe50_cal_ibat(info, ita);
vout = desc->vbat_cv + div1000(ibat * data->r_sw);
return min(percent(pe50_vout2vbus(info, vout), PE50_VBUSOVP_RATIO),
data->vbusovp);
}
/* Calculate IBUSOC S/W level */
static u32 pe50_get_dvchg_ibusocp(struct pe50_algo_info *info, u32 ita)
{
struct pe50_algo_data *data = info->data;
u32 ibus, ratio = PE50_IBUSOCP_RATIO;
ibus = data->is_swchg_en ? (ita - data->aicr_setting) : ita;
/* Add 10% for unbalance tolerance */
if (data->is_dvchg_en[PE50_DVCHG_SLAVE]) {
ibus = precise_div(ibus, 2);
ratio += 10;
}
return percent(ibus, ratio);
}
/* Calculate VBATOV S/W level */
static u32 pe50_get_vbatovp(struct pe50_algo_info *info)
{
struct pe50_algo_desc *desc = info->desc;
return percent(desc->vbat_cv + desc->ircmp_vclamp, PE50_VBATOVP_RATIO);
}
/* Calculate IBATOC S/W level */
static u32 pe50_get_ibatocp(struct pe50_algo_info *info, u32 ita)
{
struct pe50_algo_data *data = info->data;
u32 ibat;
ibat = pe50_cal_ibat(info, ita);
if (data->is_swchg_en)
ibat += data->ichg_setting;
return percent(ibat, PE50_IBATOCP_RATIO);
}
/* Calculate ITAOC S/W level */
static u32 pe50_get_itaocp(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
return percent(data->ita_setting, PE50_ITAOCP_RATIO);
}
static int pe50_set_dvchg_protection(struct pe50_algo_info *info, bool dual)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
u32 vout, idvchg_lmt;
u32 vbusovp, ibusocp, vbatovp, ibatocp;
/* VBATOVP ALARM */
ret = pe50_hal_set_vbatovp_alarm(info->alg, DVCHG1, desc->vbat_cv);
if (ret < 0) {
PE50_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(pe50_vout2vbus(info, vout), PE50_VBUSOVP_RATIO);
vbusovp = min(vbusovp, (u32)auth_data->vcap_max);
ret = pe50_hal_set_vbusovp(info->alg, DVCHG1, vbusovp);
if (ret < 0) {
PE50_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 = pe50_hal_set_vbusovp_alarm(info->alg, DVCHG1, vbusovp);
if (ret < 0) {
PE50_ERR("set vbusovp alarm fail(%d)\n", ret);
return ret;
}
}
/* IBUSOCP */
idvchg_lmt = min(data->idvchg_cc, (u32)auth_data->ita_max);
ibusocp = percent(idvchg_lmt, PE50_IBUSOCP_RATIO);
if (data->is_dvchg_exist[PE50_DVCHG_SLAVE] && dual) {
/* Add 10% for unbalance tolerance */
ibusocp = percent(precise_div(idvchg_lmt, 2),
PE50_IBUSOCP_RATIO + 10);
ret = pe50_hal_set_ibusocp(info->alg, DVCHG2, ibusocp);
if (ret < 0) {
PE50_ERR("set slave ibusocp fail(%d)\n", ret);
return ret;
}
}
ret = pe50_hal_set_ibusocp(info->alg, DVCHG1, ibusocp);
if (ret < 0) {
PE50_ERR("set ibusocp fail(%d)\n", ret);
return ret;
}
/* VBATOVP */
vbatovp = percent(desc->vbat_cv + desc->ircmp_vclamp,
PE50_VBATOVP_RATIO);
ret = pe50_hal_set_vbatovp(info->alg, DVCHG1, vbatovp);
if (ret < 0) {
PE50_ERR("set vbatovp fail(%d)\n", ret);
return ret;
}
/* IBATOCP */
ibatocp = percent(2 * data->idvchg_cc + desc->swchg_ichg,
PE50_IBATOCP_RATIO);
ret = pe50_hal_set_ibatocp(info->alg, DVCHG1, ibatocp);
if (ret < 0) {
PE50_ERR("set ibatocp fail(%d)\n", ret);
return ret;
}
PE50_INFO("vbusovp,ibusocp,vbatovp,ibatocp = (%d,%d,%d,%d)\n",
vbusovp, ibusocp, vbatovp, ibatocp);
return 0;
}
/*
* Enable/Disable divider charger
*
* @en: enable/disable
*/
static int pe50_enable_dvchg_charging(struct pe50_algo_info *info,
enum pe50_dvchg_role role, bool en)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
if (!data->is_dvchg_exist[role])
return -ENODEV;
if (data->is_dvchg_en[role] == en)
return 0;
PE50_INFO("en[%s] = %d\n", pe50_dvchg_role_name[role], en);
ret = pe50_hal_enable_charging(info->alg, to_chgidx(role), en);
if (ret < 0) {
PE50_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 pe50_set_dvchg_charging(struct pe50_algo_info *info, bool en)
{
int ret;
struct pe50_algo_data *data = info->data;
if (!data->is_dvchg_exist[PE50_DVCHG_MASTER])
return -ENODEV;
PE50_INFO("en = %d\n", en);
if (en) {
ret = pe50_hal_enable_hz(info->alg, CHG1, true);
if (ret < 0) {
PE50_ERR("set swchg hz fail(%d)\n", ret);
return ret;
}
ret = pe50_set_dvchg_protection(info, false);
if (ret < 0) {
PE50_ERR("set protection fail(%d)\n", ret);
return ret;
}
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_MASTER, en);
if (ret < 0)
return ret;
if (!en) {
ret = pe50_hal_enable_hz(info->alg, CHG1, false);
if (ret < 0) {
PE50_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 pe50_enable_swchg_charging(struct pe50_algo_info *info, bool en)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
PE50_INFO("en = %d\n", en);
if (en) {
ret = pe50_hal_enable_charging(info->alg, CHG1, true);
if (ret < 0) {
PE50_ERR("en swchg fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_enable_hz(info->alg, CHG1, false);
if (ret < 0) {
PE50_ERR("disable hz fail(%d)\n", ret);
return ret;
}
} else {
ret = pe50_hal_enable_hz(info->alg, CHG1, true);
if (ret < 0) {
PE50_ERR("disable hz fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_enable_charging(info->alg, CHG1, false);
if (ret < 0) {
PE50_ERR("en swchg fail(%d)\n", ret);
return ret;
}
}
data->is_swchg_en = en;
ret = pe50_hal_set_vbatovp_alarm(info->alg, DVCHG1,
en ? desc->swchg_off_vbat : desc->vbat_cv);
if (ret < 0) {
PE50_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 pe50_set_swchg_cap(struct pe50_algo_info *info, u32 aicr)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 ichg, vbat, vbus;
if (aicr == data->aicr_setting)
goto set_ichg;
ret = pe50_hal_set_aicr(info->alg, CHG1, aicr);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
data->aicr_setting = aicr;
set_ichg:
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
return ret;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0) {
PE50_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 = pe50_hal_set_ichg(info->alg, CHG1, ichg);
if (ret < 0) {
PE50_ERR("set_ichg fail(%d)\n", ret);
return ret;
}
data->ichg_setting = ichg;
PE50_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 pe50_enable_ta_charging(struct pe50_algo_info *info, bool en, int mV,
int mA)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 wdt = max(desc->polling_interval * 2, (u32)PE50_TA_WDT_MIN);
PE50_INFO("en = %d\n", en);
if (en) {
ret = pe50_hal_set_ta_wdt(info->alg, wdt);
if (ret < 0) {
PE50_ERR("set ta wdt fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_enable_ta_wdt(info->alg, true);
if (ret < 0) {
PE50_ERR("en ta wdt fail(%d)\n", ret);
return ret;
}
}
ret = pe50_hal_enable_ta_charging(info->alg, en, mV, mA);
if (ret < 0) {
PE50_ERR("en ta charging fail(%d)\n", ret);
return ret;
}
if (!en) {
ret = pe50_hal_enable_ta_wdt(info->alg, false);
if (ret < 0)
PE50_ERR("disable ta wdt fail(%d)\n", ret);
}
data->vta_setting = mV;
data->ita_setting = mA;
return ret;
}
static int pe50_send_notification(struct pe50_algo_info *info,
unsigned long val,
struct chg_alg_notify *notify)
{
return srcu_notifier_call_chain(&info->alg->evt_nh, val, notify);
}
/* Stop PE5.0 charging and reset parameter */
static int pe50_stop(struct pe50_algo_info *info, struct pe50_stop_info *sinfo)
{
struct pe50_algo_data *data = info->data;
struct chg_alg_notify notify = {
.evt = EVT_ALGO_STOP,
};
if (data->state == PE50_ALGO_STOP) {
/*
* Always clear stop_algo,
* in case it is called from pe50_stop_algo
*/
atomic_set(&data->stop_algo, 0);
PE50_DBG("already stop\n");
return 0;
}
PE50_INFO("reset ta(%d), hardreset ta(%d)\n", sinfo->reset_ta,
sinfo->hardreset_ta);
data->state = PE50_ALGO_STOP;
atomic_set(&data->stop_algo, 0);
alarm_cancel(&data->timer);
if (data->is_swchg_en)
pe50_enable_swchg_charging(info, false);
pe50_enable_dvchg_charging(info, PE50_DVCHG_SLAVE, false);
pe50_set_dvchg_charging(info, false);
if (!(data->notify & PE50_RESET_NOTIFY)) {
if (sinfo->hardreset_ta)
pe50_hal_send_ta_hardreset(info->alg);
else if (sinfo->reset_ta) {
pe50_hal_set_ta_cap(info->alg, PE50_VTA_INIT,
PE50_ITA_INIT);
pe50_enable_ta_charging(info, false, PE50_VTA_INIT,
PE50_ITA_INIT);
}
}
pe50_hal_enable_sw_vbusovp(info->alg, true);
pe50_send_notification(info, EVT_ALGO_STOP, &notify);
return 0;
}
static inline void pe50_init_algo_data(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_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(ita_level[PE50_RCABLE_NORMAL],
(u32)auth_data->ita_max);
data->idvchg_ss_init = max(data->idvchg_ss_init,
(u32)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[PE50_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[PE50_DVCHG_MASTER] = false;
data->is_dvchg_en[PE50_DVCHG_SLAVE] = false;
data->suspect_ta_cc = false;
data->aicr_setting = 0;
data->ichg_setting = 0;
data->vta_setting = PE50_VTA_INIT;
data->ita_setting = PE50_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 - PE50_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[PE50_RCABLE_NORMAL];
data->r_cable_by_swchg = rcable_level[PE50_RCABLE_NORMAL];
data->tbat_level = PE50_THERMAL_NORMAL;
data->tta_level = PE50_THERMAL_NORMAL;
data->tdvchg_level = PE50_THERMAL_NORMAL;
data->tswchg_level = PE50_THERMAL_NORMAL;
data->run_once = true;
data->state = PE50_ALGO_INIT;
mutex_lock(&data->notify_lock);
data->notify = 0;
mutex_unlock(&data->notify_lock);
data->stime = ktime_get_boottime();
}
static int pe50_earily_restart(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_SLAVE, false);
if (ret < 0) {
PE50_ERR("disable slave dvchg fail(%d)\n", ret);
return ret;
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_MASTER, false);
if (ret < 0) {
PE50_ERR("disable master dvchg fail(%d)\n", ret);
return ret;
}
if (auth_data->support_cc) {
ret = pe50_set_ta_cap_cc(info, PE50_VTA_INIT, PE50_ITA_INIT);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
return ret;
}
}
ret = pe50_enable_ta_charging(info, false, PE50_VTA_INIT,
PE50_ITA_INIT);
if (ret < 0) {
PE50_ERR("disable ta charging fail(%d)\n", ret);
return ret;
}
pe50_init_algo_data(info);
return 0;
}
/*
* Start pe50 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 pe50_start(struct pe50_algo_info *info)
{
int ret, ibus, vbat, vbus, ita, i;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
ktime_t ktime = ktime_set(0, MS_TO_NS(PE50_INIT_POLLING_INTERVAL));
PE50_DBG("++\n");
if (data->run_once) {
PE50_ERR("already run PE5.0 once\n");
return -EINVAL;
}
data->idvchg_ss_init = desc->idvchg_ss_init;
ret = pe50_hal_set_aicr(info->alg, CHG1, 3000);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
goto start;
}
ret = pe50_hal_set_ichg(info->alg, CHG1, 3000);
if (ret < 0) {
PE50_ERR("set ichg fail(%d)\n", ret);
goto start;
}
ret = pe50_hal_enable_charging(info->alg, CHG1, true);
if (ret < 0) {
PE50_ERR("en swchg fail(%d)\n", ret);
goto start;
}
msleep(1000);
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0) {
PE50_ERR("get swchg vbus fail(%d)\n", ret);
goto start;
}
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_IBUS, &ibus);
if (ret < 0) {
PE50_ERR("get swchg ibus fail(%d)\n", ret);
goto start;
}
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get swchg vbat fail(%d)\n", ret);
goto start;
}
ita = precise_div(percent(vbus * ibus, 90), 2 * vbat);
if (ita < desc->idvchg_term) {
PE50_ERR("estimated ita(%d) < idvchg_term(%d)\n", ita,
desc->idvchg_term);
return -EINVAL;
}
/* Update idvchg_ss_init */
if (ita >= auth_data->ita_min) {
PE50_INFO("set idvchg_ss_init(%d)->(%d)\n",
desc->idvchg_ss_init, ita);
data->idvchg_ss_init = ita;
}
start:
/* disable charger */
ret = pe50_hal_enable_charging(info->alg, CHG1, false);
if (ret < 0) {
PE50_ERR("disable charger fail\n");
return ret;
}
msleep(1000); /* wait for battery to recovery */
/* Check DVCHG registers stat first */
for (i = PE50_DVCHG_MASTER; i < PE50_DVCHG_MAX; i++) {
if (!data->is_dvchg_exist[i])
continue;
ret = pe50_hal_init_chip(info->alg, to_chgidx(i));
if (ret < 0) {
PE50_ERR("(%s) init chip fail(%d)\n",
pe50_dvchg_role_name[i], ret);
return ret;
}
}
/* Parameters that only reset by restarting from outside */
mutex_lock(&data->ext_lock);
data->input_current_limit = -1;
data->cv_limit = -1;
mutex_unlock(&data->ext_lock);
data->tried_dual_dvchg = false;
pe50_init_algo_data(info);
alarm_start_relative(&data->timer, ktime);
return 0;
}
/* =================================================================== */
/* PE5.0 Algo State Machine */
/* =================================================================== */
static int pe50_calculate_rcable_by_swchg(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
int vbus1 = 0, vbus2 = 0, vbus_max = 0, vbus_min = 0;
int ibus1 = 0, ibus2 = 0, ibus_max = 0, ibus_min = 0;
int ret = 0, aicr = 0, ichg = 0, i = 0;
int val_vbus = 0, val_ibus = 0;
ret = pe50_hal_get_aicr(info->alg, CHG1, &aicr);
if (ret < 0) {
PE50_ERR("get aicr fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_get_ichg(info->alg, CHG1, &ichg);
if (ret < 0) {
PE50_ERR("get ichg fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_set_aicr(info->alg, CHG1, 300);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_set_ichg(info->alg, CHG1, 3000);
if (ret < 0) {
PE50_ERR("set ichg fail(%d)\n", ret);
return ret;
}
pe50_hal_enable_charging(info->alg, CHG1, true);
for (i = 0; i < PE50_MEASURE_R_AVG_TIMES + 2; i++) {
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_VBUS, &val_vbus);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_IBUS, &val_ibus);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
if (i == 0) {
vbus_max = vbus_min = val_vbus;
ibus_max = ibus_min = val_ibus;
} else {
vbus_max = max(vbus_max, val_vbus);
ibus_max = max(ibus_max, val_ibus);
vbus_min = min(vbus_min, val_vbus);
ibus_min = min(ibus_min, val_ibus);
}
vbus1 += val_vbus;
ibus1 += val_ibus;
PE50_ERR("vbus=%d ibus=%d vbus(max,min)=(%d,%d) ibus(max,min)=(%d,%d) vbus1=%d ibus1=%d",
val_vbus, val_ibus, vbus_max, vbus_min, ibus_max, ibus_min, vbus1, ibus1);
}
vbus1 -= (vbus_min + vbus_max);
vbus1 = precise_div(vbus1, PE50_MEASURE_R_AVG_TIMES);
ibus1 -= (ibus_min + ibus_max);
ibus1 = precise_div(ibus1, PE50_MEASURE_R_AVG_TIMES);
ret = pe50_hal_set_aicr(info->alg, CHG1, 400);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
for (i = 0; i < PE50_MEASURE_R_AVG_TIMES + 2; i++) {
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_VBUS, &val_vbus);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_IBUS, &val_ibus);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
if (i == 0) {
vbus_max = vbus_min = val_vbus;
ibus_max = ibus_min = val_ibus;
} else {
vbus_max = max(vbus_max, val_vbus);
ibus_max = max(ibus_max, val_ibus);
vbus_min = min(vbus_min, val_vbus);
ibus_min = min(ibus_min, val_ibus);
}
vbus2 += val_vbus;
ibus2 += val_ibus;
PE50_ERR("vbus=%d ibus=%d vbus(max,min)=(%d,%d) ibus(max,min)=(%d,%d) vbus2=%d ibus2=%d",
val_vbus, val_ibus, vbus_max, vbus_min, ibus_max, ibus_min, vbus2, ibus2);
}
vbus2 -= (vbus_min + vbus_max);
vbus2 = precise_div(vbus2, PE50_MEASURE_R_AVG_TIMES);
ibus2 -= (ibus_min + ibus_max);
ibus2 = precise_div(ibus2, PE50_MEASURE_R_AVG_TIMES);
data->r_cable_by_swchg = precise_div(abs(vbus2 - vbus1) * 1000,
abs(ibus2 - ibus1));
pe50_hal_enable_charging(info->alg, CHG1, false);
ret = pe50_hal_set_aicr(info->alg, CHG1, aicr);
if (ret < 0) {
PE50_ERR("set aicr fail(%d)\n", ret);
return ret;
}
ret = pe50_hal_set_ichg(info->alg, CHG1, ichg);
if (ret < 0) {
PE50_ERR("set ichg fail(%d)\n", ret);
return ret;
}
return 0;
}
static int pe50_algo_init_with_ta_cc(struct pe50_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 pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_stop_info sinfo = {
.hardreset_ta = false,
.reset_ta = true,
};
PE50_DBG("++\n");
/* Change charging policy first */
ret = pe50_enable_ta_charging(info, true, PE50_VTA_INIT, PE50_ITA_INIT);
if (ret < 0) {
PE50_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 = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_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) {
PE50_INFO("finish PE5.0, vbat(%d) > %d\n", vbat_avg,
desc->start_vbat_max);
goto out;
}
ret = pe50_set_ta_cap_cv(info, 8000, 1000);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
goto err;
}
ret = pe50_calculate_rcable_by_swchg(info);
if (ret < 0) {
PE50_ERR("calculate rcable by swchg fail(%d)\n", ret);
}
ret = pe50_hal_enable_hz(info->alg, CHG1, true);
if (ret < 0) {
PE50_ERR("set swchg hz fail(%d)\n", ret);
goto err;
}
msleep(500); /* Wait current stable */
/* Initial setting, no need to check ita_lmt */
ret = pe50_set_ta_cap_cc_by_cali_vta(info, data->idvchg_ss_init);
if (ret < 0) {
PE50_ERR("set ta cap by algo fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto err;
}
for (i = 0; i < avg_times; i++) {
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_ERR("get ta cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto err;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0) {
PE50_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;
PE50_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) > PE50_VBUS_CALI_THRESHOLD) {
PE50_ERR("vbus cali (%d) > (%d)\n", data->vbus_cali,
PE50_VBUS_CALI_THRESHOLD);
goto err;
}
if (ita_avg > desc->ifod_threshold) {
PE50_ERR("foreign object detected, ita(%d) > (%d)\n",
ita_avg, desc->ifod_threshold);
goto err;
}
ret = pe50_set_dvchg_charging(info, true);
if (ret < 0) {
PE50_ERR("en dvchg fail\n");
goto err;
}
ret = pe50_set_ta_cap_cc_by_cali_vta(info, data->idvchg_ss_init);
if (ret < 0) {
PE50_ERR("set ta cap by algo fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto err;
}
data->err_retry_cnt = 0;
data->state = PE50_ALGO_MEASURE_R;
return 0;
err:
if (data->err_retry_cnt < PE50_INIT_RETRY_MAX) {
data->err_retry_cnt++;
return 0;
}
out:
return pe50_stop(info, &sinfo);
}
static int pe50_algo_init_with_ta_cv(struct pe50_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 pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
/* Change charging policy first */
ret = pe50_enable_ta_charging(info, true, PE50_VTA_INIT, PE50_ITA_INIT);
if (ret < 0) {
PE50_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 = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbus fail(%d)\n", ret);
goto err;
}
vbat_avg += vbat;
}
vbat_avg = precise_div(vbat_avg, avg_times);
data->zcv = vbat_avg;
PE50_INFO("avg(vbat):(%d)\n", vbat_avg);
if (vbat_avg >= desc->start_vbat_max) {
PE50_INFO("finish PE5.0, vbat(%d) > %d\n", vbat_avg,
desc->start_vbat_max);
goto out;
}
ret = pe50_set_ta_cap_cv(info, 8000, 1000);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
goto err;
}
ret = pe50_calculate_rcable_by_swchg(info);
if (ret < 0) {
PE50_ERR("calculate rcable by swchg fail(%d)\n", ret);
}
ret = pe50_hal_enable_hz(info->alg, CHG1, true);
if (ret < 0) {
PE50_ERR("set swchg hz fail(%d)\n", ret);
goto err;
}
msleep(500); /* Wait current stable */
ret = pe50_get_adc(info, PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0) {
PE50_ERR("get vbus fail(%d)\n", ret);
goto err;
}
ret = pe50_hal_sync_ta_volt(info->alg, vbus);
if (ret < 0 && ret != -EOPNOTSUPP) {
PE50_ERR("sync ta setting fail(%d)\n", ret);
goto err;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VOUT, &vout);
if (ret < 0) {
PE50_ERR("get vout fail(%d)\n", ret);
goto err;
}
/* Adjust VBUS to make sure DVCHG can be turned on */
vta = pe50_vout2vbus(info, vout);
ret = pe50_set_ta_cap_cv(info, vta, data->idvchg_ss_init);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
goto err;
}
while (true) {
ret = pe50_hal_is_vbuslowerr(info->alg, DVCHG1, &err);
if (ret < 0) {
PE50_ERR("get vbuslowerr fail(%d)\n", ret);
goto err;
}
if (!err)
break;
vta = data->vta_setting + auth_data->vta_step;
ret = pe50_set_ta_cap_cv(info, vta, data->idvchg_ss_init);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
goto err;
}
}
for (i = 0; i < avg_times; i++) {
if (auth_data->support_meas_cap) {
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_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 = pe50_get_adc(info, PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0) {
PE50_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;
PE50_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) > PE50_VBUS_CALI_THRESHOLD) {
PE50_ERR("vbus cali (%d) > (%d)\n", data->vbus_cali,
PE50_VBUS_CALI_THRESHOLD);
goto err;
}
if (ita_avg > desc->ifod_threshold) {
PE50_ERR("foreign object detected, ita(%d) > (%d)\n",
ita_avg, desc->ifod_threshold);
goto err;
}
}
ret = pe50_set_dvchg_charging(info, true);
if (ret < 0) {
PE50_ERR("en dvchg fail\n");
goto err;
}
/* Get ita measure after enable dvchg */
ret = pe50_get_ta_cap_by_supportive(info, &data->vta_measure,
&data->ita_measure);
if (ret < 0) {
PE50_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 = PE50_ALGO_MEASURE_R;
return 0;
err:
if (data->err_retry_cnt < PE50_INIT_RETRY_MAX) {
data->err_retry_cnt++;
return 0;
}
out:
return pe50_stop(info, &sinfo);
}
/*
* PE5.0 algorithm initial state
* It does Foreign Object Detection(FOD)
*/
static int pe50_algo_init(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
return auth_data->support_cc ? pe50_algo_init_with_ta_cc(info) :
pe50_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 pe50_algo_get_r_info(struct pe50_algo_info *info,
struct meas_r_info *r_info,
struct pe50_stop_info *sinfo)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_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 = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_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 = pe50_get_adc(info, PE50_ADCCHAN_VBUS, &r_info->vbus);
if (ret < 0) {
PE50_ERR("get vbus fail(%d)\n", ret);
return ret;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_IBUS, &r_info->ibus);
if (ret < 0) {
PE50_ERR("get ibus fail(%d)\n", ret);
return ret;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VOUT, &r_info->vout);
if (ret < 0) {
PE50_ERR("get vout fail(%d)\n", ret);
return ret;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &r_info->vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
return ret;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_IBAT, &r_info->ibat);
if (ret < 0) {
PE50_ERR("get ibat fail(%d)\n", ret);
return ret;
}
PE50_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 pe50_algo_cal_r_info_with_ta_cap(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, i;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct meas_r_info r_info, max_r_info, min_r_info;
struct pe50_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 < PE50_MEASURE_R_AVG_TIMES + 2; i++) {
if (atomic_read(&data->stop_algo)) {
PE50_INFO("stop algo\n");
goto stop;
}
ret = pe50_algo_get_r_info(info, &r_info, sinfo);
if (ret < 0) {
PE50_ERR("get r info fail(%d)\n", ret);
return ret;
}
if (r_info.ibat == 0) {
PE50_ERR("ibat == 0 fail\n");
return -EINVAL;
}
if (r_info.ita == 0) {
PE50_ERR("ita == 0 fail\n");
sinfo->hardreset_ta = true;
return -EINVAL;
}
if (r_info.ita < data->idvchg_term &&
r_info.vbat >= data->vbat_cv) {
PE50_INFO("finish PE5.0 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));
PE50_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, PE50_MEASURE_R_AVG_TIMES);
data->r_sw = precise_div(data->r_sw, PE50_MEASURE_R_AVG_TIMES);
data->r_cable = precise_div(data->r_cable,
PE50_MEASURE_R_AVG_TIMES);
data->r_total = data->r_bat + data->r_sw + data->r_cable;
return 0;
stop:
pe50_stop(info, &_sinfo);
return -EIO;
}
static int pe50_select_ita_lmt_by_r(struct pe50_algo_info *info, bool dual)
{
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_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[PE50_RCABLE_NORMAL];
goto out;
}
if (data->r_cable_by_swchg <= rcable_level[PE50_RCABLE_NORMAL])
ita_lmt_by_r = ita_level[PE50_RCABLE_NORMAL];
else if (data->r_cable_by_swchg <= rcable_level[PE50_RCABLE_BAD1])
ita_lmt_by_r = ita_level[PE50_RCABLE_BAD1];
else if (data->r_cable_by_swchg <= rcable_level[PE50_RCABLE_BAD2])
ita_lmt_by_r = ita_level[PE50_RCABLE_BAD2];
else if (data->r_cable_by_swchg <= rcable_level[PE50_RCABLE_BAD3])
ita_lmt_by_r = ita_level[PE50_RCABLE_BAD3];
else {
PE50_ERR("r_cable_by_swchg(%d) too worse\n", data->r_cable_by_swchg);
PE50_ERR("r_cable(%d) too worse\n", data->r_cable);
return -EINVAL;
}
PE50_ERR("r_cable_by_swchg: %d\n", data->r_cable_by_swchg);
PE50_ERR("r_cable: %d\n", data->r_cable);
out:
PE50_INFO("ita limited by r = %d\n", ita_lmt_by_r);
data->ita_lmt = min(ita_lmt_by_r, (u32)auth_data->ita_max);
data->ita_pwr_lmt = pe50_get_ita_pwr_lmt_by_vta(info,
data->vta_setting);
ita_lmt = pe50_get_ita_lmt(info);
if (ita_lmt < data->idvchg_term) {
PE50_ERR("ita_lmt(%d) < dvchg_term(%d)\n", ita_lmt,
data->idvchg_term);
return -EINVAL;
}
return 0;
}
static int pe50_algo_measure_r_with_ta_cc(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 ita, idvchg_lmt;
u32 rcable_retry_level = (data->is_dvchg_exist[PE50_DVCHG_SLAVE] &&
!data->tried_dual_dvchg) ?
desc->rcable_level_dual[PE50_RCABLE_NORMAL] :
desc->rcable_level[PE50_RCABLE_NORMAL];
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
ret = pe50_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 < PE50_MEASURE_R_RETRY_MAX) {
PE50_INFO("rcable(%d) is worse than normal\n", data->r_cable);
goto err;
}
PE50_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->is_dvchg_exist[PE50_DVCHG_SLAVE] && !data->tried_dual_dvchg &&
!data->is_dvchg_en[PE50_DVCHG_SLAVE]) {
PE50_INFO("try dual dvchg\n");
data->tried_dual_dvchg = true;
data->idvchg_term = 2 * desc->idvchg_term;
data->idvchg_cc = desc->ita_level_dual[PE50_RCABLE_NORMAL] -
desc->swchg_aicr;
ret = pe50_select_ita_lmt_by_r(info, true);
if (ret < 0) {
PE50_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 = pe50_get_idvchg_lmt(info);
if (idvchg_lmt < data->idvchg_term) {
PE50_ERR("idvchg_lmt(%d) < 2 * idvchg_term(%d)\n",
idvchg_lmt, data->idvchg_term);
goto single_dvchg_select_ita;
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_MASTER,
false);
if (ret < 0) {
PE50_ERR("disable master dvchg fail(%d)\n", ret);
goto single_dvchg_restart;
}
data->ignore_ibusucpf = true;
ret = pe50_set_dvchg_protection(info, true);
if (ret < 0) {
PE50_ERR("set dual dvchg protection fail(%d)\n", ret);
goto single_dvchg_restart;
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_SLAVE, true);
if (ret < 0) {
PE50_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 = pe50_set_ta_cap_cc_by_cali_vta(info, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto out;
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_MASTER, true);
if (ret < 0) {
PE50_ERR("en master dvchg fail(%d)\n", ret);
goto single_dvchg_restart;
}
goto ss_dvchg;
single_dvchg_restart:
ret = pe50_earily_restart(info);
if (ret < 0) {
PE50_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[PE50_RCABLE_NORMAL] -
desc->swchg_aicr;
ret = pe50_select_ita_lmt_by_r(info, false);
if (ret < 0) {
PE50_ERR("select dvchg ita lmt fail(%d)\n", ret);
goto out;
}
ss_dvchg:
data->err_retry_cnt = 0;
data->state = PE50_ALGO_SS_DVCHG;
return 0;
err:
if (data->err_retry_cnt < PE50_MEASURE_R_RETRY_MAX) {
data->err_retry_cnt++;
return 0;
}
out:
return pe50_stop(info, &sinfo);
}
static int pe50_algo_measure_r_with_ta_cv(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
u32 rcable_retry_level = (data->is_dvchg_exist[PE50_DVCHG_SLAVE] &&
!data->tried_dual_dvchg) ?
desc->rcable_level_dual[PE50_RCABLE_NORMAL] :
desc->rcable_level[PE50_RCABLE_NORMAL];
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
/*
* Ignore measuring r,
* treat as normal cable if meas_cap is not supported
*/
if (!auth_data->support_meas_cap) {
PE50_INFO("ignore measuring resistance\n");
goto select_ita;
}
ret = pe50_algo_cal_r_info_with_ta_cap(info, &sinfo);
if (ret < 0) {
PE50_ERR("get r info fail(%d)\n", ret);
goto err;
}
if (data->r_cable > rcable_retry_level &&
data->err_retry_cnt < PE50_MEASURE_R_RETRY_MAX) {
PE50_INFO("rcable(%d) is worse than normal\n", data->r_cable);
goto err;
}
PE50_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 = pe50_select_ita_lmt_by_r(info, false);
if (ret < 0) {
PE50_ERR("select dvchg ita lmt fail(%d)\n", ret);
goto out;
}
data->err_retry_cnt = 0;
data->state = PE50_ALGO_SS_DVCHG;
return 0;
err:
if (data->err_retry_cnt < PE50_MEASURE_R_RETRY_MAX) {
data->err_retry_cnt++;
return 0;
}
out:
return pe50_stop(info, &sinfo);
}
/* Measure resistance of cable/battery/sw and get corressponding ita limit */
static int pe50_algo_measure_r(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
return (auth_data->support_cc && !data->force_ta_cv) ?
pe50_algo_measure_r_with_ta_cc(info) :
pe50_algo_measure_r_with_ta_cv(info);
}
static int pe50_check_slave_dvchg_off(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
data->idvchg_cc = desc->ita_level[PE50_RCABLE_NORMAL] -
desc->swchg_aicr;
data->idvchg_term = desc->idvchg_term;
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_SLAVE, false);
if (ret < 0) {
PE50_ERR("disable slave dvchg fail(%d)\n", ret);
return ret;
}
ret = pe50_select_ita_lmt_by_r(info, false);
if (ret < 0) {
PE50_ERR("select dvchg ita lmt fail(%d)\n", ret);
return ret;
}
ret = pe50_set_dvchg_protection(info, false);
if (ret < 0) {
PE50_ERR("dvchg protection fail(%d)\n", ret);
return ret;
}
return 0;
}
static int pe50_force_ta_cv(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret;
u32 ita, vta;
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
PE50_DBG("++\n");
ret = pe50_hal_set_vbusovp_alarm(info->alg, DVCHG1, data->vbusovp);
if (ret < 0) {
PE50_ERR("set vbusovp alarm fail(%d)\n", ret);
return ret;
}
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_ERR("get ta cap fail\n");
sinfo->hardreset_ta = true;
return ret;
}
ita = min(data->ita_measure, data->ita_setting);
vta = min(data->vta_measure, (u32)auth_data->vcap_max);
ret = pe50_set_ta_cap_cv(info, vta, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail\n");
return ret;
}
data->force_ta_cv = true;
return 0;
}
static int pe50_check_force_ta_cv(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret;
u32 vbat;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_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 = pe50_force_ta_cv(info, sinfo);
if (ret < 0) {
PE50_ERR("force ta cv fail(%d)\n", ret);
return ret;
}
}
return 0;
}
static int pe50_algo_ss_dvchg_with_ta_cc(struct pe50_algo_info *info)
{
int ret, vbat;
u32 ita, ita_lmt, idvchg_lmt;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
ret = pe50_check_force_ta_cv(info, &sinfo);
if (ret < 0) {
PE50_ERR("check force ta cv fail(%d)\n", ret);
goto err;
}
if (data->force_ta_cv) {
PE50_INFO("force switching to ta cv mode\n");
return 0;
}
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_ERR("get ta cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto err;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
goto err;
}
idvchg_lmt = pe50_get_idvchg_lmt(info);
if (idvchg_lmt < data->idvchg_term) {
PE50_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[PE50_DVCHG_SLAVE]) {
ret = pe50_check_slave_dvchg_off(info);
if (ret < 0) {
PE50_INFO("slave off fail(%d)\n", ret);
goto err;
}
idvchg_lmt = pe50_get_idvchg_lmt(info);
goto cc_cv;
}
PE50_INFO("finish PE5.0 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 = PE50_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 = pe50_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 = pe50_set_swchg_cap(info,
desc->swchg_aicr_ss_init);
if (ret < 0) {
PE50_ERR("set swchg cap fail(%d)\n", ret);
goto err;
}
ret = pe50_enable_swchg_charging(info, true);
if (ret < 0) {
PE50_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 = PE50_ALGO_SS_SWCHG;
goto out_set_cap;
}
data->state = PE50_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 = pe50_set_ta_cap_cc_by_cali_vta(info, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto err;
}
return 0;
err:
return pe50_stop(info, &sinfo);
}
static int pe50_algo_ss_dvchg_with_ta_cv(struct pe50_algo_info *info)
{
int ret, vbat;
ktime_t start_time, end_time;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_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 pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
repeat:
PE50_DBG("++\n");
vta = data->vta_setting;
start_time = ktime_get();
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
goto out;
}
ret = pe50_get_ta_cap_by_supportive(info, &data->vta_measure,
&data->ita_measure);
if (ret < 0) {
PE50_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->is_dvchg_exist[PE50_DVCHG_SLAVE] && !data->tried_dual_dvchg &&
!data->is_dvchg_en[PE50_DVCHG_SLAVE] &&
(data->ita_measure >= ita)) {
PE50_INFO("try dual dvchg\n");
data->tried_dual_dvchg = true;
data->idvchg_term = 2 * desc->idvchg_term;
data->idvchg_cc = desc->ita_level_dual[PE50_RCABLE_NORMAL] -
desc->swchg_aicr;
ret = pe50_select_ita_lmt_by_r(info, true);
if (ret < 0) {
PE50_ERR("select dual dvchg ita lmt fail(%d)\n", ret);
goto single_dvchg_select_ita;
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_MASTER,
false);
if (ret < 0) {
PE50_ERR("disable master dvchg fail(%d)\n", ret);
goto single_dvchg_restart;
}
data->ignore_ibusucpf = true;
ret = pe50_set_dvchg_protection(info, true);
if (ret < 0) {
PE50_ERR("set dual dvchg protection fail(%d)\n", ret);
goto single_dvchg_restart;
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_SLAVE, true);
if (ret < 0) {
PE50_ERR("en slave dvchg fail(%d)\n", ret);
goto single_dvchg_restart;
}
ret = pe50_enable_dvchg_charging(info, PE50_DVCHG_MASTER, true);
if (ret < 0) {
PE50_ERR("en master dvchg fail(%d)\n", ret);
goto single_dvchg_restart;
}
goto ss_dvchg;
single_dvchg_restart:
ret = pe50_earily_restart(info);
if (ret < 0) {
PE50_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[PE50_RCABLE_NORMAL] -
desc->swchg_aicr;
ret = pe50_select_ita_lmt_by_r(info, false);
if (ret < 0) {
PE50_ERR("select dvchg ita lmt fail(%d)\n", ret);
goto out;
}
}
ss_dvchg:
ita = data->ita_setting;
idvchg_lmt = pe50_get_idvchg_lmt(info);
if (idvchg_lmt < data->idvchg_term) {
PE50_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[PE50_DVCHG_SLAVE]) {
ret = pe50_check_slave_dvchg_off(info);
if (ret < 0) {
PE50_INFO("slave off fail(%d)\n", ret);
goto out;
}
idvchg_lmt = pe50_get_idvchg_lmt(info);
goto cc_cv;
}
PE50_INFO("finish PE5.0 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 = PE50_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 = PE50_ALGO_CC_CV;
else {
vta += auth_data->vta_step;
vta = min(vta, (u32)auth_data->vcap_max);
ita += ita_gap_per_vstep;
ita = min(ita, idvchg_lmt);
}
out_set_cap:
ret = pe50_set_ta_cap_cv(info, vta, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto out;
}
if (data->state == PE50_ALGO_SS_DVCHG) {
end_time = ktime_get();
delta_time = ktime_ms_delta(end_time, start_time);
PE50_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 pe50_stop(info, &sinfo);
}
/* Soft start of divider charger */
static int pe50_algo_ss_dvchg(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
return (auth_data->support_cc && !data->force_ta_cv) ?
pe50_algo_ss_dvchg_with_ta_cc(info) :
pe50_algo_ss_dvchg_with_ta_cv(info);
}
static int pe50_check_swchg_off(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 aicr = data->aicr_setting, ita, vbat;
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_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;
PE50_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 = pe50_set_ta_cap_cc_by_cali_vta(info, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
sinfo->hardreset_ta = true;
return ret;
}
return (aicr >= desc->swchg_aicr_ss_init) ?
pe50_set_swchg_cap(info, aicr) :
pe50_enable_swchg_charging(info, false);
}
static int pe50_update_aicr_lmt(struct pe50_algo_info *info)
{
struct pe50_algo_desc *desc = info->desc;
struct pe50_algo_data *data = info->data;
u32 ita_lmt;
if (!data->is_swchg_en)
return 0;
ita_lmt = pe50_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 pe50_algo_ss_swchg(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_desc *desc = info->desc;
struct pe50_algo_data *data = info->data;
u32 ita = data->ita_setting, aicr = 0, vbat;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
if (pe50_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 = pe50_set_swchg_cap(info, aicr);
if (ret < 0) {
PE50_ERR("set swchg cap fail(%d)\n", ret);
goto err;
}
ret = pe50_set_ta_cap_cc_by_cali_vta(info, ita);
if (ret < 0) {
PE50_ERR("set ta cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto err;
}
out:
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
goto err;
}
ret = pe50_check_swchg_off(info, &sinfo);
if (ret < 0) {
PE50_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 = PE50_ALGO_CC_CV;
return 0;
err:
return pe50_stop(info, &sinfo);
}
static int pe50_algo_cc_cv_with_ta_cc(struct pe50_algo_info *info)
{
int ret, vbat = 0;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
u32 ita = data->ita_setting, ita_lmt;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
ret = pe50_check_force_ta_cv(info, &sinfo);
if (ret < 0) {
PE50_ERR("check force ta cv fail(%d)\n", ret);
goto err;
}
if (data->force_ta_cv) {
PE50_INFO("force switching to ta cv mode\n");
return 0;
}
/* Check swchg */
pe50_update_aicr_lmt(info);
ret = pe50_check_swchg_off(info, &sinfo);
if (ret < 0) {
PE50_ERR("check swchg off fail(%d)\n", ret);
goto err;
}
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0)
PE50_ERR("get vbat fail(%d)\n", ret);
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_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[PE50_DVCHG_SLAVE]) {
ret = pe50_check_slave_dvchg_off(info);
if (ret < 0) {
PE50_INFO("slave off fail(%d)\n", ret);
goto err;
}
goto cc_cv;
}
PE50_INFO("finish PE5.0 charging\n");
goto err;
}
cc_cv:
ita_lmt = pe50_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) {
PE50_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) {
PE50_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) {
PE50_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) {
PE50_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 = pe50_set_ta_cap_cc_by_cali_vta(info, ita);
if (ret < 0) {
PE50_ERR("set_ta_cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto err;
}
return 0;
err:
return pe50_stop(info, &sinfo);
}
static int pe50_algo_cc_cv_with_ta_cv(struct pe50_algo_info *info)
{
int ret, vbat, vsys = 0;
struct pe50_algo_data *data = info->data;
struct pe50_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 pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
goto out;
}
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_VSYS,
&vsys);
if (ret < 0) {
PE50_ERR("get vsys fail(%d)\n", ret);
goto out;
}
ret = pe50_get_ta_cap_by_supportive(info, &data->vta_measure,
&data->ita_measure);
if (ret < 0) {
PE50_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[PE50_DVCHG_SLAVE]) {
ret = pe50_check_slave_dvchg_off(info);
if (ret < 0) {
PE50_INFO("slave off fail(%d)\n", ret);
goto out;
}
goto cc_cv;
}
PE50_INFO("finish PE5.0 charging\n");
goto out;
}
cc_cv:
idvchg_lmt = pe50_get_idvchg_lmt(info);
if (idvchg_lmt < data->idvchg_term) {
PE50_INFO("idvchg_lmt(%d) < idvchg_term(%d)\n", idvchg_lmt,
data->idvchg_term);
goto out;
}
if (vbat >= data->vbat_cv) {
PE50_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 >= PE50_VSYS_UPPER_BOUND) {
vta -= auth_data->vta_step;
ita -= ita_gap_per_vstep;
ita = max(ita, idvchg_lmt);
PE50_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 < (PE50_VSYS_UPPER_BOUND - PE50_VSYS_UPPER_BOUND_GAP)) {
vta += auth_data->vta_step;
vta = min(vta, (u32)auth_data->vcap_max);
ita += ita_gap_per_vstep;
ita = min(ita, idvchg_lmt);
if (ita == data->ita_setting)
suspect_ta_cc = true;
PE50_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 = pe50_set_ta_cap_cv(info, vta, ita);
if (ret < 0) {
PE50_ERR("set_ta_cap fail(%d)\n", ret);
sinfo.hardreset_ta = true;
goto out;
}
ret = pe50_get_ta_cap_by_supportive(info, &vta_measure, &ita_measure);
if (ret < 0) {
PE50_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 pe50_stop(info, &sinfo);
}
static int pe50_algo_cc_cv(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
return (auth_data->support_cc && !data->force_ta_cv) ?
pe50_algo_cc_cv_with_ta_cc(info) :
pe50_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 pe50_check_ta_status(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret;
struct pe50_ta_status status;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
if (!auth_data->support_status)
return desc->allow_not_check_ta_status;
ret = pe50_hal_get_ta_status(info->alg, &status);
if (ret < 0) {
PE50_ERR("get ta status fail(%d)\n", ret);
goto err;
}
PE50_INFO("temp = %d, (OVP,OCP,OTP) = (%d,%d,%d)\n",
status.temperature, 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 pe50_check_dvchg_ibusocp(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, i, ibus, acc = 0;
struct pe50_algo_data *data = info->data;
u32 ibusocp;
if (!data->is_dvchg_en[PE50_DVCHG_MASTER])
return true;
ibusocp = pe50_get_dvchg_ibusocp(info, data->ita_setting);
for (i = PE50_DVCHG_MASTER; i < PE50_DVCHG_MAX; i++) {
if (!data->is_dvchg_en[i])
continue;
pe50_hal_get_adc_accuracy(info->alg, to_chgidx(i),
PE50_ADCCHAN_IBUS, &acc);
ret = pe50_hal_get_adc(info->alg, to_chgidx(i),
PE50_ADCCHAN_IBUS, &ibus);
if (ret < 0) {
PE50_ERR("get ibus fail(%d)\n", ret);
return false;
}
PE50_INFO("(%s)ibus(%d+-%dmA), ibusocp(%dmA)\n",
pe50_dvchg_role_name[i], ibus, acc, ibusocp);
if (ibus > acc)
ibus -= acc;
if (ibus > ibusocp) {
PE50_ERR("(%s)ibus(%dmA) > ibusocp(%dmA)\n",
pe50_dvchg_role_name[i], ibus, ibusocp);
return false;
}
}
return true;
}
static bool pe50_check_ta_ibusocp(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
u32 itaocp;
if (!auth_data->support_meas_cap)
return true;
ret = pe50_get_ta_cap(info);
if (ret < 0) {
PE50_ERR("get ta cap fail(%d)\n", ret);
goto err;
}
itaocp = pe50_get_itaocp(info);
PE50_INFO("ita(%dmA), itaocp(%dmA)\n", data->ita_measure, itaocp);
if (data->ita_measure > itaocp) {
PE50_ERR("ita(%dmA) > itaocp(%dmA)\n", data->ita_measure,
itaocp);
/* double confirm using dvchg */
if (!pe50_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 pe50_check_dvchg_vbusovp(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, vbus, i;
struct pe50_algo_data *data = info->data;
u32 vbusovp;
vbusovp = pe50_get_dvchg_vbusovp(info, data->ita_setting);
for (i = PE50_DVCHG_MASTER; i < PE50_DVCHG_MAX; i++) {
if (!data->is_dvchg_en[i])
continue;
ret = pe50_hal_get_adc(info->alg, to_chgidx(i),
PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0) {
PE50_ERR("get vbus fail(%d)\n", ret);
return false;
}
PE50_INFO("(%s)vbus(%dmV), vbusovp(%dmV)\n",
pe50_dvchg_role_name[i], vbus, vbusovp);
if (vbus > vbusovp) {
PE50_ERR("(%s)vbus(%dmV) > vbusovp(%dmV)\n",
pe50_dvchg_role_name[i], vbus, vbusovp);
return false;
}
}
return true;
}
static bool pe50_check_vbatovp(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, vbat;
u32 vbatovp;
vbatovp = pe50_get_vbatovp(info);
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0) {
PE50_ERR("get vbat fail(%d)\n", ret);
return false;
}
PE50_INFO("vbat(%dmV), vbatovp(%dmV)\n", vbat, vbatovp);
if (vbat > vbatovp) {
PE50_ERR("vbat(%dmV) > vbatovp(%dmV)\n", vbat, vbatovp);
return false;
}
return true;
}
static bool pe50_check_ibatocp(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, ibat;
struct pe50_algo_data *data = info->data;
u32 ibatocp;
if (!data->is_dvchg_en[PE50_DVCHG_MASTER])
return true;
ibatocp = pe50_get_ibatocp(info, data->ita_setting);
ret = pe50_get_adc(info, PE50_ADCCHAN_IBAT, &ibat);
if (ret < 0) {
PE50_ERR("get ibat fail(%d)\n", ret);
return false;
}
PE50_INFO("ibat(%dmA), ibatocp(%dmA)\n", ibat, ibatocp);
if (ibat > ibatocp) {
PE50_ERR("ibat(%dmA) > ibatocp(%dmA)\n", ibat, ibatocp);
return false;
}
return true;
}
struct pe50_thermal_data {
const char *name;
int temp;
enum pe50_thermal_level *temp_level;
int *temp_level_def;
int *curlmt;
int recovery_area;
};
static bool pe50_check_thermal_level(struct pe50_algo_info *info,
struct pe50_thermal_data *tdata)
{
if (tdata->temp >= tdata->temp_level_def[PE50_THERMAL_VERY_HOT]) {
if (tdata->curlmt[PE50_THERMAL_VERY_HOT] == 0)
return true;
PE50_ERR("%s(%d) is over max(%d)\n", tdata->name, tdata->temp,
tdata->temp_level_def[PE50_THERMAL_VERY_HOT]);
return false;
}
if (tdata->temp <= tdata->temp_level_def[PE50_THERMAL_VERY_COLD]) {
if (tdata->curlmt[PE50_THERMAL_VERY_COLD] == 0)
return true;
PE50_ERR("%s(%d) is under min(%d)\n", tdata->name, tdata->temp,
tdata->temp_level_def[PE50_THERMAL_VERY_COLD]);
return false;
}
switch (*tdata->temp_level) {
case PE50_THERMAL_COLD:
if (tdata->temp >= (tdata->temp_level_def[PE50_THERMAL_COLD] +
tdata->recovery_area))
*tdata->temp_level = PE50_THERMAL_VERY_COOL;
break;
case PE50_THERMAL_VERY_COOL:
if (tdata->temp >=
(tdata->temp_level_def[PE50_THERMAL_VERY_COOL] +
tdata->recovery_area))
*tdata->temp_level = PE50_THERMAL_COOL;
else if (tdata->temp <=
tdata->temp_level_def[PE50_THERMAL_COLD] &&
tdata->curlmt[PE50_THERMAL_COLD] > 0)
*tdata->temp_level = PE50_THERMAL_COLD;
break;
case PE50_THERMAL_COOL:
if (tdata->temp >= (tdata->temp_level_def[PE50_THERMAL_COOL] +
tdata->recovery_area))
*tdata->temp_level = PE50_THERMAL_NORMAL;
else if (tdata->temp <=
tdata->temp_level_def[PE50_THERMAL_VERY_COOL] &&
tdata->curlmt[PE50_THERMAL_VERY_COOL] > 0)
*tdata->temp_level = PE50_THERMAL_VERY_COOL;
break;
case PE50_THERMAL_NORMAL:
if (tdata->temp >= tdata->temp_level_def[PE50_THERMAL_WARM] &&
tdata->curlmt[PE50_THERMAL_WARM] > 0)
*tdata->temp_level = PE50_THERMAL_WARM;
else if (tdata->temp <=
tdata->temp_level_def[PE50_THERMAL_COOL] &&
tdata->curlmt[PE50_THERMAL_COOL] > 0)
*tdata->temp_level = PE50_THERMAL_COOL;
break;
case PE50_THERMAL_WARM:
if (tdata->temp <= (tdata->temp_level_def[PE50_THERMAL_WARM] -
tdata->recovery_area))
*tdata->temp_level = PE50_THERMAL_NORMAL;
else if (tdata->temp >=
tdata->temp_level_def[PE50_THERMAL_VERY_WARM] &&
tdata->curlmt[PE50_THERMAL_VERY_WARM] > 0)
*tdata->temp_level = PE50_THERMAL_VERY_WARM;
break;
case PE50_THERMAL_VERY_WARM:
if (tdata->temp <=
(tdata->temp_level_def[PE50_THERMAL_VERY_WARM] -
tdata->recovery_area))
*tdata->temp_level = PE50_THERMAL_WARM;
else if (tdata->temp >=
tdata->temp_level_def[PE50_THERMAL_HOT] &&
tdata->curlmt[PE50_THERMAL_HOT] > 0)
*tdata->temp_level = PE50_THERMAL_HOT;
break;
case PE50_THERMAL_HOT:
if (tdata->temp <= (tdata->temp_level_def[PE50_THERMAL_HOT] -
tdata->recovery_area))
*tdata->temp_level = PE50_THERMAL_VERY_WARM;
break;
default:
PE50_ERR("NO SUCH STATE\n");
return false;
}
PE50_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 pe50_check_tbat_level(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, tbat;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_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 = pe50_get_adc(info, PE50_ADCCHAN_TBAT, &tbat);
if (ret < 0) {
PE50_ERR("get tbat fail(%d)\n", ret);
return false;
}
tdata.temp = tbat;
return pe50_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 pe50_check_tta_level(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
struct pe50_ta_status status;
struct pe50_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 = pe50_hal_get_ta_status(info->alg, &status);
if (ret < 0) {
PE50_ERR("get tta fail(%d)\n", ret);
sinfo->hardreset_ta = true;
return false;
}
tdata.temp = status.temperature;
return pe50_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 pe50_check_tdvchg_level(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, i, tdvchg;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
char buf[14];
struct pe50_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 = PE50_DVCHG_MASTER; i < PE50_DVCHG_MAX; i++) {
if (!data->is_dvchg_en[i])
continue;
ret = pe50_hal_get_adc(info->alg, to_chgidx(i),
PE50_ADCCHAN_TCHG, &tdvchg);
if (ret < 0) {
PE50_ERR("get tdvchg fail(%d)\n", ret);
return false;
}
snprintf(buf, 8 + strlen(pe50_dvchg_role_name[i]), "tdvchg_%s",
pe50_dvchg_role_name[i]);
tdata.name = buf;
tdata.temp = tdvchg;
if (!pe50_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 pe50_check_tswchg_level(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
int ret, tswchg;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_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 = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_TCHG, &tswchg);
if (ret < 0) {
PE50_ERR("get tswchg fail(%d)\n", ret);
return false;
}
tdata.temp = tswchg;
if (!pe50_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
(*pe50_safety_check_fn[])(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo) = {
pe50_check_ta_status,
pe50_check_ta_ibusocp,
pe50_check_dvchg_vbusovp,
pe50_check_dvchg_ibusocp,
pe50_check_vbatovp,
pe50_check_ibatocp,
pe50_check_tbat_level,
pe50_check_tta_level,
pe50_check_tdvchg_level,
pe50_check_tswchg_level,
};
static bool pe50_algo_safety_check(struct pe50_algo_info *info)
{
int i;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_DBG("++\n");
for (i = 0; i < ARRAY_SIZE(pe50_safety_check_fn); i++) {
if (!pe50_safety_check_fn[i](info, &sinfo))
goto err;
}
return true;
err:
pe50_stop(info, &sinfo);
return false;
}
static bool pe50_is_ta_rdy(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_desc *desc = info->desc;
struct pe50_algo_data *data = info->data;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
if (!data->ta_ready) {
auth_data->vcap_min = desc->vta_cap_min;
auth_data->vcap_max = desc->vta_cap_max;
auth_data->icap_min = desc->ita_cap_min;
ret = pe50_hal_authenticate_ta(info->alg, auth_data);
if (ret < 0)
return false;
data->ta_ready = true;
}
return true;
}
static inline void pe50_wakeup_algo_thread(struct pe50_algo_data *data)
{
PE50_DBG("++\n");
atomic_set(&data->wakeup_thread, 1);
wake_up_interruptible(&data->wq);
}
static enum alarmtimer_restart
pe50_algo_timer_cb(struct alarm *alarm, ktime_t now)
{
struct pe50_algo_data *data =
container_of(alarm, struct pe50_algo_data, timer);
PE50_DBG("++\n");
pe50_wakeup_algo_thread(data);
return ALARMTIMER_NORESTART;
}
/*
* Check charging time of pe5.0 algorithm
* return false if timeout otherwise return true
*/
static bool pe50_algo_check_charging_time(struct pe50_algo_info *info)
{
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
ktime_t etime, time_diff;
struct timespec64 dtime;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
etime = ktime_get_boottime();
time_diff = ktime_sub(etime, data->stime);
dtime = ktime_to_timespec64(time_diff);
if (dtime.tv_sec >= desc->chg_time_max) {
PE50_ERR("PE5.0 algo timeout(%d, %d)\n", (int)dtime.tv_sec,
desc->chg_time_max);
pe50_stop(info, &sinfo);
return false;
}
return true;
}
static inline int __pe50_plugout_reset(struct pe50_algo_info *info,
struct pe50_stop_info *sinfo)
{
struct pe50_algo_data *data = info->data;
PE50_DBG("++\n");
data->ta_ready = false;
data->run_once = false;
return pe50_stop(info, sinfo);
}
static int pe50_notify_hardreset_hdlr(struct pe50_algo_info *info)
{
struct pe50_stop_info sinfo = {
.reset_ta = false,
.hardreset_ta = false,
};
PE50_INFO("++\n");
return __pe50_plugout_reset(info, &sinfo);
}
static int pe50_notify_detach_hdlr(struct pe50_algo_info *info)
{
struct pe50_stop_info sinfo = {
.reset_ta = false,
.hardreset_ta = false,
};
PE50_INFO("++\n");
return __pe50_plugout_reset(info, &sinfo);
}
static int pe50_notify_hwerr_hdlr(struct pe50_algo_info *info)
{
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
PE50_INFO("++\n");
return pe50_stop(info, &sinfo);
}
static int pe50_notify_ibusucpf_hdlr(struct pe50_algo_info *info)
{
int ret, ibus;
struct pe50_algo_data *data = info->data;
if (data->ignore_ibusucpf) {
PE50_INFO("ignore ibusucpf\n");
data->ignore_ibusucpf = false;
return 0;
}
if (!data->is_dvchg_en[PE50_DVCHG_MASTER]) {
PE50_INFO("master dvchg is off\n");
return 0;
}
/* Last chance */
ret = pe50_hal_get_adc(info->alg, DVCHG1, PE50_ADCCHAN_IBUS, &ibus);
if (ret < 0) {
PE50_ERR("get dvchg ibus fail(%d)\n", ret);
goto out;
}
if (ibus < PE50_IBUSUCPF_RECHECK) {
PE50_ERR("ibus(%d) < recheck(%d)\n", ibus,
PE50_IBUSUCPF_RECHECK);
goto out;
}
PE50_INFO("recheck ibus and it is not ucp\n");
return 0;
out:
return pe50_notify_hwerr_hdlr(info);
}
static int pe50_notify_vbatovp_alarm_hdlr(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
if (data->state == PE50_ALGO_STOP)
return 0;
PE50_INFO("++\n");
ret = pe50_hal_reset_vbatovp_alarm(info->alg, DVCHG1);
if (ret < 0) {
PE50_ERR("reset vbatovp alarm fail(%d)\n", ret);
return pe50_stop(info, &sinfo);
}
return 0;
}
static int pe50_notify_vbusovp_alarm_hdlr(struct pe50_algo_info *info)
{
int ret;
struct pe50_algo_data *data = info->data;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
if (data->state == PE50_ALGO_STOP)
return 0;
PE50_INFO("++\n");
ret = pe50_hal_reset_vbusovp_alarm(info->alg, DVCHG1);
if (ret < 0) {
PE50_ERR("reset vbusovp alarm fail(%d)\n", ret);
return pe50_stop(info, &sinfo);
}
return 0;
}
static int
(*pe50_notify_pre_hdlr[EVT_MAX])(struct pe50_algo_info *info) = {
[EVT_DETACH] = pe50_notify_detach_hdlr,
[EVT_HARDRESET] = pe50_notify_hardreset_hdlr,
[EVT_VBUSOVP] = pe50_notify_hwerr_hdlr,
[EVT_IBUSOCP] = pe50_notify_hwerr_hdlr,
[EVT_IBUSUCP_FALL] = pe50_notify_ibusucpf_hdlr,
[EVT_VBATOVP] = pe50_notify_hwerr_hdlr,
[EVT_IBATOCP] = pe50_notify_hwerr_hdlr,
[EVT_VOUTOVP] = pe50_notify_hwerr_hdlr,
[EVT_VDROVP] = pe50_notify_hwerr_hdlr,
[EVT_VBATOVP_ALARM] = pe50_notify_vbatovp_alarm_hdlr,
};
static int
(*pe50_notify_post_hdlr[EVT_MAX])(struct pe50_algo_info *info) = {
[EVT_DETACH] = pe50_notify_detach_hdlr,
[EVT_HARDRESET] = pe50_notify_hardreset_hdlr,
[EVT_VBUSOVP] = pe50_notify_hwerr_hdlr,
[EVT_IBUSOCP] = pe50_notify_hwerr_hdlr,
[EVT_IBUSUCP_FALL] = pe50_notify_ibusucpf_hdlr,
[EVT_VBATOVP] = pe50_notify_hwerr_hdlr,
[EVT_IBATOCP] = pe50_notify_hwerr_hdlr,
[EVT_VOUTOVP] = pe50_notify_hwerr_hdlr,
[EVT_VDROVP] = pe50_notify_hwerr_hdlr,
[EVT_VBATOVP_ALARM] = pe50_notify_vbatovp_alarm_hdlr,
[EVT_VBUSOVP_ALARM] = pe50_notify_vbusovp_alarm_hdlr,
};
static int pe50_pre_handle_notify_evt(struct pe50_algo_info *info)
{
int i;
struct pe50_algo_data *data = info->data;
mutex_lock(&data->notify_lock);
PE50_DBG("0x%08X\n", data->notify);
for (i = 0; i < EVT_MAX; i++) {
if ((data->notify & BIT(i)) && pe50_notify_pre_hdlr[i]) {
data->notify &= ~BIT(i);
mutex_unlock(&data->notify_lock);
pe50_notify_pre_hdlr[i](info);
mutex_lock(&data->notify_lock);
}
}
mutex_unlock(&data->notify_lock);
return 0;
}
static int pe50_post_handle_notify_evt(struct pe50_algo_info *info)
{
int i;
struct pe50_algo_data *data = info->data;
mutex_lock(&data->notify_lock);
PE50_DBG("0x%08X\n", data->notify);
for (i = 0; i < EVT_MAX; i++) {
if ((data->notify & BIT(i)) && pe50_notify_post_hdlr[i]) {
data->notify &= ~BIT(i);
mutex_unlock(&data->notify_lock);
pe50_notify_post_hdlr[i](info);
mutex_lock(&data->notify_lock);
}
}
mutex_unlock(&data->notify_lock);
return 0;
}
static int pe50_dump_charging_info(struct pe50_algo_info *info)
{
int ret, i;
int vbus, ibus[PE50_DVCHG_MAX] = {0}, ibus_swchg = 0, vbat, ibat, vout[PE50_DVCHG_MAX] = {0};
int ibus_total, vsys, tbat;
u32 soc;
struct pe50_algo_data *data = info->data;
/* vbus */
ret = pe50_get_adc(info, PE50_ADCCHAN_VBUS, &vbus);
if (ret < 0)
PE50_ERR("get vbus fail(%d)\n", ret);
/* ibus */
for (i = 0; i < PE50_DVCHG_MAX; i++) {
if (!data->is_dvchg_en[i])
continue;
ret = pe50_hal_get_adc(info->alg, to_chgidx(i),
PE50_ADCCHAN_IBUS, &ibus[i]);
if (ret < 0) {
PE50_ERR("get %s ibus fail\n", pe50_dvchg_role_name[i]);
continue;
}
}
ibus_total = ibus[PE50_DVCHG_MASTER] + ibus[PE50_DVCHG_SLAVE];
if (data->is_swchg_en) {
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_IBUS,
&ibus_swchg);
if (ret < 0)
PE50_ERR("get swchg ibus fail\n");
}
/* vbat */
ret = pe50_get_adc(info, PE50_ADCCHAN_VBAT, &vbat);
if (ret < 0)
PE50_ERR("get vbat fail\n");
/* ibat */
ret = pe50_get_adc(info, PE50_ADCCHAN_IBAT, &ibat);
if (ret < 0)
PE50_ERR("get ibat fail\n");
ret = pe50_get_ta_cap_by_supportive(info, &data->vta_measure,
&data->ita_measure);
if (ret < 0)
PE50_ERR("get ta measure cap fail(%d)\n", ret);
/* vout */
for (i = PE50_DVCHG_MASTER; i < PE50_DVCHG_MAX; i++) {
if (!data->is_dvchg_en[i])
continue;
ret = pe50_hal_get_adc(info->alg, to_chgidx(i), PE50_ADCCHAN_VOUT,
&vout[i]);
if (ret < 0) {
PE50_ERR("get %s ibus fail\n", pe50_dvchg_role_name[i]);
continue;
}
}
ret = pe50_hal_get_adc(info->alg, CHG1, PE50_ADCCHAN_VSYS,
&vsys);
if (ret < 0) {
PE50_ERR("get vsys from swchg fail\n");
}
ret = pe50_get_adc(info, PE50_ADCCHAN_TBAT, &tbat);
ret = pe50_hal_get_soc(info->alg, &soc);
if (ret < 0) {
PE50_ERR("get soc fail\n");
}
PE50_INFO("vbus,ibus(master,slave,sw),vbat,ibat=%d,(%d,%d,%d),%d,%d\n",
vbus, ibus[PE50_DVCHG_MASTER], ibus[PE50_DVCHG_SLAVE],
ibus_swchg, vbat, ibat);
PE50_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);
PE50_INFO("vout(master,slave)=(%d,%d)\n",
vout[PE50_DVCHG_MASTER], vout[PE50_DVCHG_SLAVE]);
PE50_INFO("[PE5] %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
vbus, ibus[PE50_DVCHG_MASTER], ibus[PE50_DVCHG_SLAVE], ibus_total, vbat, ibat,
tbat, vsys, soc,
vout[PE50_DVCHG_MASTER], vout[PE50_DVCHG_SLAVE]);
return 0;
}
static int pe50_algo_threadfn(void *param)
{
struct pe50_algo_info *info = param;
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
struct pe50_ta_auth_data *auth_data = &data->ta_auth_data;
u32 sec, ms, polling_interval;
ktime_t ktime;
struct pe50_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);
PE50_INFO("state = %s\n", pe50_algo_state_name[data->state]);
if (atomic_read(&data->stop_algo))
pe50_stop(info, &sinfo);
pe50_pre_handle_notify_evt(info);
if (data->state != PE50_ALGO_STOP) {
pe50_algo_check_charging_time(info);
pe50_calculate_vbat_ircmp(info);
pe50_select_vbat_cv(info);
pe50_dump_charging_info(info);
}
switch (data->state) {
case PE50_ALGO_INIT:
pe50_algo_init(info);
break;
case PE50_ALGO_MEASURE_R:
pe50_algo_measure_r(info);
break;
case PE50_ALGO_SS_SWCHG:
pe50_algo_ss_swchg(info);
break;
case PE50_ALGO_SS_DVCHG:
pe50_algo_ss_dvchg(info);
break;
case PE50_ALGO_CC_CV:
pe50_algo_cc_cv(info);
break;
case PE50_ALGO_STOP:
PE50_INFO("PE5.0 STOP\n");
break;
default:
PE50_ERR("NO SUCH STATE\n");
break;
}
pe50_post_handle_notify_evt(info);
if (data->state != PE50_ALGO_STOP) {
if (!pe50_algo_safety_check(info))
goto cont;
pe50_dump_charging_info(info);
if (data->state == PE50_ALGO_CC_CV &&
auth_data->support_cc && !data->force_ta_cv)
polling_interval = desc->polling_interval;
else
polling_interval =
PE50_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;
}
/* =================================================================== */
/* PE5.0 Algo OPS */
/* =================================================================== */
static int pe50_init_algo(struct chg_alg_device *alg)
{
int ret = 0;
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
mutex_lock(&data->lock);
PE50_DBG("++\n");
if (data->inited) {
PE50_INFO("already inited\n");
goto out;
}
if (pe50_hal_init_hardware(info->alg, desc->support_ta,
desc->support_ta_cnt)) {
PE50_ERR("init hw fail\n", __func__);
goto out;
}
data->inited = true;
PE50_INFO("successfully\n");
out:
mutex_unlock(&data->lock);
return ret;
}
static bool pe50_is_algo_running(struct chg_alg_device *alg)
{
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
bool running = true;
mutex_lock(&data->lock);
if (!data->inited) {
running = false;
goto out_unlock;
}
running = !(data->state == PE50_ALGO_STOP);
PE50_DBG("running = %d\n", running);
out_unlock:
mutex_unlock(&data->lock);
return running;
}
static int pe50_is_algo_ready(struct chg_alg_device *alg)
{
int ret;
u32 soc;
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
struct pe50_algo_desc *desc = info->desc;
if (algo_waiver_test)
return ALG_WAIVER;
if (pe50_is_algo_running(info->alg))
return ALG_RUNNING;
mutex_lock(&data->lock);
PE50_DBG("++\n");
if (!data->inited) {
ret = ALG_INIT_FAIL;
goto out;
}
PE50_DBG("run once(%d)\n", data->run_once);
if (data->run_once) {
if (!(data->notify & PE50_RESET_NOTIFY)) {
ret = ALG_NOT_READY;
goto out;
}
mutex_lock(&data->notify_lock);
PE50_INFO("run once but detach/hardreset happened\n");
data->notify &= ~PE50_RESET_NOTIFY;
data->run_once = false;
data->ta_ready = false;
mutex_unlock(&data->notify_lock);
}
ret = pe50_hal_get_soc(info->alg, &soc);
if (ret < 0) {
PE50_ERR("get SOC fail(%d)\n", ret);
ret = ALG_INIT_FAIL;
goto out;
}
if (soc < desc->start_soc_min || soc > desc->start_soc_max) {
if (soc > 0) {
PE50_INFO("soc(%d) not in range(%d~%d)\n", soc,
desc->start_soc_min, desc->start_soc_max);
ret = ALG_WAIVER;
goto out;
}
if (soc == -1 && data->ref_vbat > data->vbat_threshold) {
PE50_INFO("soc(%d) not in range(%d~%d)\n", soc,
desc->start_soc_min, desc->start_soc_max);
ret = ALG_WAIVER;
goto out;
}
}
if (!pe50_is_ta_rdy(info)) {
ret = pe50_hal_is_pd_adapter_ready(alg);
goto out;
}
ret = ALG_READY;
out:
mutex_unlock(&data->lock);
return ret;
}
static int pe50_start_algo(struct chg_alg_device *alg)
{
int ret = 0;
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
if (pe50_is_algo_running(alg))
return ALG_RUNNING;
mutex_lock(&data->lock);
PE50_DBG("++\n");
if (!data->inited || !data->ta_ready) {
ret = ALG_INIT_FAIL;
goto out;
}
pe50_hal_enable_sw_vbusovp(alg, false);
ret = pe50_start(info);
if (ret < 0) {
pe50_hal_enable_sw_vbusovp(alg, true);
PE50_ERR("start PE5.0 algo fail\n");
ret = ALG_INIT_FAIL;
}
out:
mutex_unlock(&data->lock);
return ret;
}
static int pe50_plugout_reset(struct chg_alg_device *alg)
{
int ret = 0;
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
struct pe50_stop_info sinfo = {
.reset_ta = false,
.hardreset_ta = false,
};
mutex_lock(&data->lock);
PE50_DBG("++\n");
if (!data->inited)
goto out;
ret = __pe50_plugout_reset(info, &sinfo);
out:
mutex_unlock(&data->lock);
return ret;
}
static int pe50_stop_algo(struct chg_alg_device *alg)
{
int ret = 0;
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
struct pe50_stop_info sinfo = {
.reset_ta = true,
.hardreset_ta = false,
};
atomic_set(&data->stop_algo, 1);
mutex_lock(&data->lock);
if (!data->inited)
goto out;
ret = pe50_stop(info, &sinfo);
out:
mutex_unlock(&data->lock);
return ret;
}
static int pe50_notifier_call(struct chg_alg_device *alg,
struct chg_alg_notify *notify)
{
int ret = 0;
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
mutex_lock(&data->notify_lock);
if (data->state == PE50_ALGO_STOP) {
if ((notify->evt == EVT_DETACH ||
notify->evt == EVT_HARDRESET) && data->run_once) {
PE50_INFO("detach/hardreset && run once after stop\n");
data->notify |= BIT(notify->evt);
}
goto out;
}
PE50_INFO("%s\n", chg_alg_notify_evt_tostring(notify->evt));
switch (notify->evt) {
case EVT_DETACH:
case EVT_HARDRESET:
case EVT_VBUSOVP:
case EVT_IBUSOCP:
case EVT_IBUSUCP_FALL:
case EVT_VBATOVP:
case EVT_IBATOCP:
case EVT_VOUTOVP:
case EVT_VDROVP:
case EVT_VBATOVP_ALARM:
case EVT_VBUSOVP_ALARM:
data->notify |= BIT(notify->evt);
break;
default:
ret = -EINVAL;
goto out;
}
pe50_wakeup_algo_thread(data);
out:
mutex_unlock(&data->notify_lock);
return ret;
}
static int pe50_set_current_limit(struct chg_alg_device *alg,
struct chg_limit_setting *setting)
{
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
int cv = micro_to_milli(setting->cv);
int ic = micro_to_milli(setting->input_current_limit_dvchg1);
mutex_lock(&data->ext_lock);
if (data->cv_limit != cv || data->input_current_limit != ic) {
data->cv_limit = cv;
data->input_current_limit = ic;
PE50_INFO("ic = %d, cv = %d\n", ic, cv);
pe50_wakeup_algo_thread(data);
}
mutex_unlock(&data->ext_lock);
return 0;
}
int pe50_set_prop(struct chg_alg_device *alg,
enum chg_alg_props s, int value)
{
struct pe50_algo_info *info = chg_alg_dev_get_drvdata(alg);
struct pe50_algo_data *data = info->data;
pr_notice("%s %d %d\n", __func__, s, value);
switch (s) {
case ALG_LOG_LEVEL:
log_level = value;
break;
case ALG_REF_VBAT:
data->ref_vbat = value;
break;
default:
break;
}
return 0;
}
static struct chg_alg_ops pe50_ops = {
.init_algo = pe50_init_algo,
.is_algo_ready = pe50_is_algo_ready,
.start_algo = pe50_start_algo,
.is_algo_running = pe50_is_algo_running,
.plugout_reset = pe50_plugout_reset,
.stop_algo = pe50_stop_algo,
.notifier_call = pe50_notifier_call,
.set_current_limit = pe50_set_current_limit,
.set_prop = pe50_set_prop,
};
#define PE50_DT_VALPROP_ARR(name, sz) \
{#name, offsetof(struct pe50_algo_desc, name), sz}
#define PE50_DT_VALPROP(name) \
PE50_DT_VALPROP_ARR(name, 1)
struct pe50_dtprop {
const char *name;
size_t offset;
size_t sz;
};
static inline void pe50_parse_dt_u32(struct device_node *np, void *desc,
const struct pe50_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 pe50_parse_dt_u32_arr(struct device_node *np, void *desc,
const struct pe50_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 pe50_parse_dt_s32_arr(struct device_node *np, void *desc,
const struct pe50_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 pe50_dtprop pe50_dtprops_u32[] = {
PE50_DT_VALPROP(polling_interval),
PE50_DT_VALPROP(ta_cv_ss_repeat_tmin),
PE50_DT_VALPROP(vbat_cv),
PE50_DT_VALPROP(start_soc_min),
PE50_DT_VALPROP(start_soc_max),
PE50_DT_VALPROP(start_vbat_max),
PE50_DT_VALPROP(idvchg_term),
PE50_DT_VALPROP(idvchg_step),
PE50_DT_VALPROP(idvchg_ss_init),
PE50_DT_VALPROP(idvchg_ss_step),
PE50_DT_VALPROP(idvchg_ss_step1),
PE50_DT_VALPROP(idvchg_ss_step2),
PE50_DT_VALPROP(idvchg_ss_step1_vbat),
PE50_DT_VALPROP(idvchg_ss_step2_vbat),
PE50_DT_VALPROP(ta_blanking),
PE50_DT_VALPROP(swchg_aicr),
PE50_DT_VALPROP(swchg_ichg),
PE50_DT_VALPROP(swchg_aicr_ss_init),
PE50_DT_VALPROP(swchg_aicr_ss_step),
PE50_DT_VALPROP(swchg_off_vbat),
PE50_DT_VALPROP(force_ta_cv_vbat),
PE50_DT_VALPROP(chg_time_max),
PE50_DT_VALPROP(tta_recovery_area),
PE50_DT_VALPROP(tbat_recovery_area),
PE50_DT_VALPROP(tdvchg_recovery_area),
PE50_DT_VALPROP(tswchg_recovery_area),
PE50_DT_VALPROP(ifod_threshold),
PE50_DT_VALPROP(rsw_min),
PE50_DT_VALPROP(ircmp_rbat),
PE50_DT_VALPROP(ircmp_vclamp),
PE50_DT_VALPROP(vta_cap_min),
PE50_DT_VALPROP(vta_cap_max),
PE50_DT_VALPROP(ita_cap_min),
};
static const struct pe50_dtprop pe50_dtprops_u32_array[] = {
PE50_DT_VALPROP_ARR(ita_level, PE50_RCABLE_MAX),
PE50_DT_VALPROP_ARR(rcable_level, PE50_RCABLE_MAX),
PE50_DT_VALPROP_ARR(ita_level_dual, PE50_RCABLE_MAX),
PE50_DT_VALPROP_ARR(rcable_level_dual, PE50_RCABLE_MAX),
};
static const struct pe50_dtprop pe50_dtprops_s32_array[] = {
PE50_DT_VALPROP_ARR(tta_level_def, PE50_THERMAL_MAX),
PE50_DT_VALPROP_ARR(tta_curlmt, PE50_THERMAL_MAX),
PE50_DT_VALPROP_ARR(tbat_level_def, PE50_THERMAL_MAX),
PE50_DT_VALPROP_ARR(tbat_curlmt, PE50_THERMAL_MAX),
PE50_DT_VALPROP_ARR(tdvchg_level_def, PE50_THERMAL_MAX),
PE50_DT_VALPROP_ARR(tdvchg_curlmt, PE50_THERMAL_MAX),
PE50_DT_VALPROP_ARR(tswchg_level_def, PE50_THERMAL_MAX),
PE50_DT_VALPROP_ARR(tswchg_curlmt, PE50_THERMAL_MAX),
};
static int pe50_parse_dt(struct pe50_algo_info *info)
{
int i, ret;
struct pe50_algo_desc *desc;
struct pe50_algo_data *data;
struct device_node *np = info->dev->of_node;
u32 val;
desc = devm_kzalloc(info->dev, sizeof(*desc), GFP_KERNEL);
if (!desc)
return -ENOMEM;
info->desc = desc;
data = info->data;
memcpy(desc, &algo_desc_defval, sizeof(*desc));
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;
PE50_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");
pe50_parse_dt_u32(np, (void *)desc, pe50_dtprops_u32,
ARRAY_SIZE(pe50_dtprops_u32));
pe50_parse_dt_u32_arr(np, (void *)desc, pe50_dtprops_u32_array,
ARRAY_SIZE(pe50_dtprops_u32_array));
pe50_parse_dt_s32_arr(np, (void *)desc, pe50_dtprops_s32_array,
ARRAY_SIZE(pe50_dtprops_s32_array));
if (desc->swchg_aicr == 0 || desc->swchg_ichg == 0) {
desc->swchg_aicr = 0;
desc->swchg_ichg = 0;
}
if (of_property_read_u32(np, "vbat_threshold", &val) >= 0)
data->vbat_threshold = val;
else {
pr_notice("turn off vbat_threshold checking:%d\n",
DISABLE_VBAT_THRESHOLD);
data->vbat_threshold = DISABLE_VBAT_THRESHOLD;
}
return 0;
}
static int pe50_probe(struct platform_device *pdev)
{
int ret;
struct pe50_algo_info *info;
struct pe50_algo_data *data;
dev_info(&pdev->dev, "%s\n", __func__);
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 = pe50_parse_dt(info);
if (ret < 0) {
PE50_ERR("%s parse dt fail(%d)\n", __func__, ret);
return ret;
}
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 = PE50_ALGO_STOP;
alarm_init(&data->timer, ALARM_REALTIME, pe50_algo_timer_cb);
data->task = kthread_run(pe50_algo_threadfn, info, "pe50_algo_task");
if (IS_ERR(data->task)) {
ret = PTR_ERR(data->task);
PE50_ERR("%s run task fail(%d)\n", __func__, ret);
goto err;
}
device_init_wakeup(info->dev, true);
info->alg = chg_alg_device_register("pe5", info->dev, info, &pe50_ops,
NULL);
if (IS_ERR_OR_NULL(info->alg)) {
PE50_ERR("%s reg pe5 algo fail(%d)\n", __func__, ret);
ret = PTR_ERR(info->alg);
goto err;
}
chg_alg_dev_set_drvdata(info->alg, info);
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);
chg_alg_device_unregister(info->alg);
return ret;
}
static int pe50_remove(struct platform_device *pdev)
{
struct pe50_algo_info *info = platform_get_drvdata(pdev);
struct pe50_algo_data *data;
if (info) {
data = info->data;
atomic_set(&data->stop_thread, 1);
pe50_wakeup_algo_thread(data);
kthread_stop(data->task);
mutex_destroy(&data->ext_lock);
mutex_destroy(&data->lock);
mutex_destroy(&data->notify_lock);
chg_alg_device_unregister(info->alg);
}
return 0;
}
static int __maybe_unused pe50_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct pe50_algo_info *info = platform_get_drvdata(pdev);
struct pe50_algo_data *data = info->data;
dev_info(dev, "%s\n", __func__);
mutex_lock(&data->lock);
return 0;
}
static int __maybe_unused pe50_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct pe50_algo_info *info = platform_get_drvdata(pdev);
struct pe50_algo_data *data = info->data;
dev_info(dev, "%s\n", __func__);
mutex_unlock(&data->lock);
return 0;
}
static SIMPLE_DEV_PM_OPS(pe50_pm_ops, pe50_suspend, pe50_resume);
static const struct of_device_id mtk_pe50_of_match[] = {
{ .compatible = "mediatek,charger,pe5", },
{},
};
MODULE_DEVICE_TABLE(of, mtk_pe50_of_match);
static struct platform_driver pe50_platdrv = {
.probe = pe50_probe,
.remove = pe50_remove,
.driver = {
.name = "pe5",
.owner = THIS_MODULE,
.pm = &pe50_pm_ops,
.of_match_table = mtk_pe50_of_match,
},
};
static int __init pe50_init(void)
{
return platform_driver_register(&pe50_platdrv);
}
static void __exit pe50_exit(void)
{
platform_driver_unregister(&pe50_platdrv);
}
late_initcall(pe50_init);
module_exit(pe50_exit);
MODULE_AUTHOR("ShuFanLee <shufan_lee@richtek.com>");
MODULE_DESCRIPTION("MTK Pump Express 5 Algorithm");
MODULE_LICENSE("GPL");