1
0
mirror of https://github.com/physwizz/a155-U-u1.git synced 2024-11-19 13:27:49 +00:00
a155-U-u1/kernel-5.10/drivers/battery/charger/sm5461_charger/sm5461_direct_charger.c
2024-03-11 06:53:12 +11:00

2233 lines
68 KiB
C

/*
* sm5461_direct_charger.c - Direct charging module on the SM ICs
*
* Copyright (C) 2023 SiliconMitus Co.Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/power_supply.h>
#if IS_ENABLED(CONFIG_BATTERY_SAMSUNG)
#include "../../common/sec_charging_common.h"
#include "../../common/sec_direct_charger.h"
#endif
#include "sm5461_direct_charger.h"
/**
* Internal support functions for Direct-charging
* - used sharing PD3.0
*/
static u32 pps_v(u32 vol)
{
if ((vol%PPS_V_STEP) >= (PPS_V_STEP / 2))
vol += PPS_V_STEP;
return (vol / PPS_V_STEP) * PPS_V_STEP;
}
static u32 pps_c(u32 cur)
{
if ((cur % PPS_C_STEP) >= (PPS_C_STEP / 2))
cur += PPS_C_STEP;
return (cur / PPS_C_STEP) * PPS_C_STEP;
}
static int report_dc_state(struct sm_dc_info *sm_dc)
{
union power_supply_propval val = {0,};
static int prev_val = SEC_DIRECT_CHG_MODE_DIRECT_OFF;
switch (sm_dc->state) {
case SM_DC_CHG_OFF:
case SM_DC_ERR:
val.intval = SEC_DIRECT_CHG_MODE_DIRECT_OFF;
break;
case SM_DC_EOC:
val.intval = SEC_DIRECT_CHG_MODE_DIRECT_DONE;
break;
case SM_DC_CHECK_VBAT:
val.intval = SEC_DIRECT_CHG_MODE_DIRECT_CHECK_VBAT;
break;
case SM_DC_PRESET:
val.intval = SEC_DIRECT_CHG_MODE_DIRECT_PRESET;
break;
case SM_DC_PRE_CC:
case SM_DC_UPDAT_BAT:
val.intval = SEC_DIRECT_CHG_MODE_DIRECT_ON_ADJUST;
break;
case SM_DC_CC:
case SM_DC_CV:
case SM_DC_CV_FPDO:
val.intval = SEC_DIRECT_CHG_MODE_DIRECT_ON;
break;
case SM_DC_CV_MAN:
val.intval = SEC_DIRECT_CHG_MODE_DIRECT_BYPASS;
break;
default:
return -EINVAL;
}
if (prev_val != val.intval) {
psy_do_property(sm_dc->config.sec_dc_name, set,
POWER_SUPPLY_EXT_PROP_DIRECT_CHARGER_MODE, val);
}
return 0;
}
static int request_state_work(struct sm_dc_info *sm_dc, u8 state, u32 delay)
{
int ret = 0;
mutex_lock(&sm_dc->st_lock);
switch (state) {
case SM_DC_CHECK_VBAT:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->check_vbat_work,
msecs_to_jiffies(delay));
break;
case SM_DC_PRESET:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->preset_dc_work,
msecs_to_jiffies(delay));
break;
case SM_DC_PRE_CC:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->pre_cc_work,
msecs_to_jiffies(delay));
break;
case SM_DC_CC:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->cc_work,
msecs_to_jiffies(delay));
break;
case SM_DC_CV:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->cv_work,
msecs_to_jiffies(delay));
break;
case SM_DC_CV_MAN:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->cv_man_work,
msecs_to_jiffies(delay));
break;
case SM_DC_CV_FPDO:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->cv_fpdo_work,
msecs_to_jiffies(delay));
break;
case SM_DC_UPDAT_BAT:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->update_bat_work,
msecs_to_jiffies(delay));
break;
case SM_DC_ERR:
queue_delayed_work(sm_dc->dc_wqueue, &sm_dc->error_work,
msecs_to_jiffies(delay));
break;
default:
pr_err("%s %s: invalid state(%d)\n", sm_dc->name, __func__, state);
ret = -EINVAL;
break;
}
mutex_unlock(&sm_dc->st_lock);
return ret;
}
static int update_work_state(struct sm_dc_info *sm_dc, u8 state)
{
int ret = 0;
if (sm_dc->state == SM_DC_CHG_OFF) {
pr_err("%s %s: detected chg_off, terminate work\n", sm_dc->name, __func__);
return -EBUSY;
}
pr_info("%s %s: sm_dc->state=%d, state=%d, update(%d,%d,%d)\n",
sm_dc->name, __func__, sm_dc->state, state, sm_dc->req_update_vbat,
sm_dc->req_update_ibus, sm_dc->req_update_ibat);
if (sm_dc->state > SM_DC_PRESET && state > SM_DC_PRESET) { /* going on charging-cycle */
if (sm_dc->req_update_vbat || sm_dc->req_update_ibus || sm_dc->req_update_ibat) {
pr_info("%s %s: changed chg param, request: update_bat\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_UPDAT_BAT, DELAY_NONE);
ret = -EINVAL;
}
}
if (sm_dc->state != state) {
mutex_lock(&sm_dc->st_lock);
sm_dc->state = state;
mutex_unlock(&sm_dc->st_lock);
report_dc_state(sm_dc);
}
return ret;
}
static int send_power_source_msg(struct sm_dc_info *sm_dc)
{
int ret;
if (sm_dc->state < SM_DC_CHECK_VBAT)
return -EINVAL;
if (sm_dc->ta.v > sm_dc->ta.v_max || sm_dc->ta.c > sm_dc->ta.c_max) {
pr_err("%s %s: ERROR: out of bounce v=%dmV(max=%dmV) c=%dmA(max=%dmA)\n",
sm_dc->name, __func__, sm_dc->ta.v, sm_dc->ta.v_max,
sm_dc->ta.c, sm_dc->ta.c_max);
sm_dc->err = SM_DC_ERR_SEND_PD_MSG;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return -EINVAL;
}
pr_info("%s %s: [send PWR_MSG] pdo=%d, v=%dmV(max=%dmV) c=%dmA(max=%dmA)\n",
sm_dc->name, __func__, sm_dc->ta.pdo_pos, sm_dc->ta.v,
sm_dc->ta.v_max, sm_dc->ta.c, sm_dc->ta.c_max);
ret = sm_dc->ops->send_power_source_msg(sm_dc->i2c, &sm_dc->ta);
if (ret < 0) {
pr_err("%s %s: fail to send msg(ret=%d)\n", sm_dc->name, __func__, ret);
sm_dc->err = SM_DC_ERR_SEND_PD_MSG;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
}
return ret;
}
static int setup_direct_charging_work_config(struct sm_dc_info *sm_dc)
{
sm_dc->wq.pps_cl = 0;
sm_dc->wq.c_down = 0;
sm_dc->wq.c_up = 0;
sm_dc->wq.v_down = 0;
sm_dc->wq.v_up = 0;
sm_dc->wq.prev_adc_ibus = 0;
sm_dc->wq.prev_adc_vbus = 0;
sm_dc->wq.cc_limit = 0;
sm_dc->wq.cv_cnt = 0;
sm_dc->wq.cv_gl = sm_dc->target_vbat;
sm_dc->wq.ci_gl = MIN(sm_dc->ta.c_max, ((sm_dc->target_ibus * 100) / 100));
sm_dc->wq.cc_gl = sm_dc->wq.ci_gl * 2;
sm_dc->wq.retry_cnt = 0;
sm_dc->wq.cc_cnt = 0;
sm_dc->wq.pps_vcm = 0;
pr_info("%s %s: CV_GL=%dmV, CI_GL=%dmA, CC_GL=%dmA\n", sm_dc->name, __func__,
sm_dc->wq.cv_gl, sm_dc->wq.ci_gl, sm_dc->wq.cc_gl);
sm_dc->ops->set_charging_config(sm_dc->i2c, sm_dc->wq.cv_gl, sm_dc->wq.ci_gl, sm_dc->wq.cc_gl);
if (sm_dc->i2c_sub)
sm_dc->ops->set_charging_config(sm_dc->i2c_sub, sm_dc->wq.cv_gl, sm_dc->wq.ci_gl, sm_dc->wq.cc_gl);
return 0;
}
static int check_error_state(struct sm_dc_info *sm_dc, u8 retry_state)
{
int adc_vbat, adc_ibus;
u32 sub_err;
if (sm_dc->state == SM_DC_ERR) {
pr_err("%s %s: already occurred error (err=0x%x)\n", sm_dc->name, __func__, sm_dc->err);
return -EINVAL;
}
sm_dc->err = sm_dc->ops->get_dc_error_status(sm_dc->i2c);
if (sm_dc->err == SM_DC_ERR_RETRY) {
pr_err("%s %s: error status retry, wait 2sec\n", sm_dc->name, __func__);
request_state_work(sm_dc, retry_state, DELAY_RETRY);
return -EAGAIN;
} else if (sm_dc->err == SM_DC_ERR_VBATREG || sm_dc->err == SM_DC_ERR_IBUSREG) {
return sm_dc->err;
} else if (sm_dc->err > SM_DC_ERR_NONE) {
pr_err("%s %s: error status:0x%x\n", sm_dc->name, __func__, sm_dc->err);
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return -EPERM;
}
if (sm_dc->i2c_sub) {
sub_err = sm_dc->ops->get_dc_error_status(sm_dc->i2c_sub);
if (sub_err == SM_DC_ERR_IBUSREG) {
sm_dc->err = sub_err;
return sm_dc->err;
}
if (sm_dc->ops->get_charging_enable(sm_dc->i2c_sub) == 0x0) {
adc_ibus = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_IBUS);
if (adc_ibus > SM_DC_DUAL_STOP_IBUS) {
sm_dc->err = SM_DC_ERR_FAIL_ADJUST;
pr_err("%s %s: error status:0x%x\n", sm_dc->name, __func__, sm_dc->err);
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return -ERANGE;
}
}
}
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
if (adc_vbat <= sm_dc->config.dc_min_vbat) {
pr_err("%s %s: abnormal adc_vbat(%d)\n", sm_dc->name, __func__, adc_vbat);
sm_dc->err = SM_DC_ERR_INVAL_VBAT;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return -ERANGE;
}
return 0;
}
static int get_adc_values(struct sm_dc_info *sm_dc, const char *str, int *vbus, int *ibus, int *vout,
int *vbat, int *ibat, int *them, int *dietemp)
{
int adc_vbus, adc_ibus, adc_vout, adc_vbat, adc_ibat, adc_them, adc_dietemp;
int adc_vbus2, adc_ibus2, adc_vout2, adc_vbat2, adc_ibat2, adc_them2, adc_dietemp2;
adc_vbus = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBUS);
adc_ibus = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_IBUS);
adc_vout = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VOUT);
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
adc_ibat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_IBAT);
adc_them = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_THEM);
adc_dietemp = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_DIETEMP);
pr_info("%s %s:vbus:%d:ibus:%d:vout:%d:vbat:%d:ibat:%d:them:%d:dietemp:%d\n",
sm_dc->name, str, adc_vbus, adc_ibus, adc_vout,
adc_vbat, adc_ibat, adc_them, adc_dietemp);
if (sm_dc->i2c_sub) {
if (sm_dc->ops->get_charging_enable(sm_dc->i2c_sub)) {
adc_vbus2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_VBUS);
adc_ibus2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_IBUS);
adc_vout2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_VOUT);
adc_vbat2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_VBAT);
adc_ibat2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_IBAT);
adc_them2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_THEM);
adc_dietemp2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_DIETEMP);
adc_ibus += adc_ibus2;
pr_info("%s %s(s):vbus:%d:ibus:%d:ibus_t:%d:vout:%d:vbat:%d:ibat:%d:them:%d:dietemp:%d\n",
sm_dc->name, str, adc_vbus2, adc_ibus2, adc_ibus, adc_vout2,
adc_vbat2, adc_ibat2, adc_them2, adc_dietemp2);
}
}
if (vbus)
*vbus = adc_vbus;
if (ibus)
*ibus = adc_ibus;
if (vout)
*vout = adc_vout;
if (vbat)
*vbat = adc_vbat;
if (ibat)
*ibat = adc_ibat;
if (them)
*them = adc_them;
if (dietemp)
*dietemp = adc_dietemp;
return 0;
}
static int terminate_charging_work(struct sm_dc_info *sm_dc)
{
flush_workqueue(sm_dc->dc_wqueue);
cancel_delayed_work_sync(&sm_dc->check_vbat_work);
cancel_delayed_work_sync(&sm_dc->preset_dc_work);
cancel_delayed_work_sync(&sm_dc->pre_cc_work);
cancel_delayed_work_sync(&sm_dc->cc_work);
cancel_delayed_work_sync(&sm_dc->cv_work);
cancel_delayed_work_sync(&sm_dc->cv_man_work);
cancel_delayed_work_sync(&sm_dc->cv_fpdo_work);
cancel_delayed_work_sync(&sm_dc->update_bat_work);
cancel_delayed_work_sync(&sm_dc->error_work);
sm_dc->ops->set_charging_enable(sm_dc->i2c, 0);
if (sm_dc->i2c_sub)
sm_dc->ops->set_charging_enable(sm_dc->i2c_sub, 0);
return 0;
}
/**
* PD3.0 PPS Direct-charging work functions
*/
static inline u32 _calc_pps_v_init_offset(struct sm_dc_info *sm_dc)
{
u32 offset;
offset = (sm_dc->ta.c * sm_dc->config.r_ttl) / 1000000;
offset += 200; /* add to extra initial offset */
pr_info("%s %s: pps_c=%dmA, v_init_offset=%dmV\n", sm_dc->name, __func__, sm_dc->ta.c, offset);
return offset;
}
static inline int _adjust_pps_v(struct sm_dc_info *sm_dc, int pps_v_original)
{
int adc_vbus;
adc_vbus = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBUS);
msleep(DELAY_PPS_UPDATE);
pr_info("%s %s: adc_vbus=%dmV, pps_v_original=%dmV\n",
sm_dc->name, __func__, adc_vbus, pps_v_original);
sm_dc->wq.v_offset = 0;
return 0;
}
static inline int _pd_pre_cc_check_limitation(struct sm_dc_info *sm_dc, int adc_ibus, int adc_vbus)
{
u32 calc_pps_v, calc_reg_v;
int ret = 0;
if (adc_ibus == sm_dc->wq.prev_adc_ibus && adc_vbus == sm_dc->wq.prev_adc_vbus) {
pr_info("%s %s: adc didn't update yet\n", sm_dc->name, __func__);
/* if continuos adc didn't update yet */
return 0;
}
if ((adc_vbus < sm_dc->wq.prev_adc_vbus + (PPS_V_STEP/2)) &&
(adc_ibus < sm_dc->wq.prev_adc_ibus + (PPS_C_STEP)) &&
(sm_dc->wq.v_down == 0 && sm_dc->wq.pps_cl == 0)) {
ret = 1;
} else if (sm_dc->wq.ci_gl == sm_dc->config.ta_min_current) { /* Case: didn't reduce PPS_C than TA_MIN_C */
ret = 1;
}
#if defined(CONFIG_SEC_FACTORY)
if (sm_dc->wq.v_down == 0 && sm_dc->wq.pps_cl == 0)
ret = 1;
#endif
if (ret) {
calc_reg_v = (sm_dc->wq.ci_gl * sm_dc->config.r_ttl) / 1000000;
if (sm_dc->ta.v_max < SM_DC_BYPASS_TA_MAX_VOL) {
calc_pps_v = (sm_dc->target_vbat) + calc_reg_v + sm_dc->wq.v_offset;
calc_pps_v = MIN(calc_pps_v, sm_dc->ta.v_max);
} else {
calc_pps_v = (sm_dc->target_vbat * 2) + calc_reg_v + sm_dc->wq.v_offset;
if ((pps_v(calc_pps_v) * sm_dc->wq.ci_gl) > sm_dc->ta.p_max) {
pr_info("%s %s: calc_pps_v(%dmV) will be reduced\n", sm_dc->name, __func__, calc_pps_v);
calc_pps_v = ((sm_dc->ta.p_max / sm_dc->wq.ci_gl) < PPS_V_STEP) ?
0 : ((sm_dc->ta.p_max / sm_dc->wq.ci_gl) - PPS_V_STEP);
}
}
sm_dc->ta.v = pps_v(MIN(calc_pps_v, sm_dc->config.dc_vbus_ovp_th - 350));
pr_info("%s %s: R_TTL=%d, calc_reg_v=%dmV, calc_pps_v=%dmV\n",
sm_dc->name, __func__, sm_dc->config.r_ttl, calc_reg_v, calc_pps_v);
sm_dc->ta.c = sm_dc->ta.c;
sm_dc->wq.pps_cl = 1;
}
return ret;
}
static inline int _try_to_adjust_cc_up(struct sm_dc_info *sm_dc)
{
sm_dc->wq.cc_cnt += 1;
if ((sm_dc->wq.cc_cnt % 2) && (sm_dc->ta.c <= sm_dc->wq.ci_gl + (PPS_C_STEP * 4))
&& (sm_dc->ta.c != sm_dc->ta.c_max)) {
if ((sm_dc->ta.v * (sm_dc->ta.c + PPS_C_STEP) <= (sm_dc->ta.p_max / 100) * 107)) {
sm_dc->ta.c += PPS_C_STEP;
if (sm_dc->ta.c > sm_dc->ta.c_max)
sm_dc->ta.c = sm_dc->ta.c_max;
}
} else {
/* TA P_MAX + 7% */
if ((sm_dc->ta.v + (PPS_V_STEP * 2)) * sm_dc->ta.c <= (sm_dc->ta.p_max / 100) * 107) {
sm_dc->ta.v += PPS_V_STEP * 2;
if (sm_dc->ta.v > MIN(sm_dc->ta.v_max, sm_dc->config.dc_vbus_ovp_th - 350))
sm_dc->ta.v = pps_v(MIN(sm_dc->ta.v_max, sm_dc->config.dc_vbus_ovp_th - 350));
} else {
pr_info("%s %s: PPS-TA has been reached limitation(v=%dmV, c=%dmA)\n",
sm_dc->name, __func__, sm_dc->ta.v, sm_dc->ta.c);
sm_dc->wq.cc_limit = 1;
return -EINVAL;
}
}
return 0;
}
static inline void _try_to_adjust_cc_down(struct sm_dc_info *sm_dc)
{
sm_dc->wq.cc_cnt += 1;
if ((sm_dc->wq.cc_cnt % 2) && (sm_dc->ta.c >= sm_dc->wq.ci_gl - (PPS_C_STEP * 4))) {
if (sm_dc->ta.c - PPS_C_STEP >= sm_dc->config.ta_min_current)
sm_dc->ta.c -= PPS_C_STEP;
} else {
if (sm_dc->ta.v > sm_dc->config.ta_min_voltage)
sm_dc->ta.v -= PPS_V_STEP;
}
}
static void pd_check_vbat_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, check_vbat_work.work);
struct sm_dc_power_source_info ta;
union power_supply_propval val;
int adc_vbat;
int ret;
ret = update_work_state(sm_dc, SM_DC_CHECK_VBAT);
if (ret < 0)
return;
ret = psy_do_property(sm_dc->config.sec_dc_name, get,
POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val);
if (ret < 0) {
pr_err("%s %s: is not ready to work (wait 1sec)\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CHECK_VBAT, DELAY_ADC_UPDATE);
return;
}
ret = sm_dc->ops->get_apdo_max_power(sm_dc->i2c, &ta);
if (ret < 0) {
if (sm_dc->ta.retry_cnt < 3) {
pr_err("%s %s: get_apdo_max_power, RETRY=%d\n",
sm_dc->name, __func__, sm_dc->ta.retry_cnt);
sm_dc->ta.retry_cnt++;
request_state_work(sm_dc, SM_DC_CHECK_VBAT, DELAY_PPS_UPDATE);
} else {
pr_err("%s %s: fail to get APDO(ret=%d)\n", sm_dc->name, __func__, ret);
sm_dc->err = SM_DC_ERR_SEND_PD_MSG;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
}
return;
}
if (sm_dc->i2c_sub) {
ret = sm_dc->ops->get_apdo_max_power(sm_dc->i2c_sub, &ta);
if (ret < 0) {
pr_err("%s %s: fail to get APDO(ret=%d)\n", sm_dc->name, __func__, ret);
return;
}
}
val.intval = 0;
if (val.intval == 0) { /* already disabled switching charger */
pr_info("%s %s: [request] check_vbat -> preset\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRESET, DELAY_NONE);
psy_do_property(sm_dc->config.sec_dc_name, set,
POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val);
} else {
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
pr_info("%s %s: adc:vbat=%dmV\n", sm_dc->name, __func__, adc_vbat);
if (adc_vbat > sm_dc->config.dc_min_vbat) {
pr_info("%s %s: set_prop - disable sw_chg\n", sm_dc->name, __func__);
val.intval = 0;
psy_do_property(sm_dc->config.sec_dc_name, set,
POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val);
}
pr_err("%s %s: sw_chg not disabled yet (wait 1sec)\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CHECK_VBAT, DELAY_ADC_UPDATE);
}
}
static void pd_preset_dc_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, preset_dc_work.work);
int adc_vbat, ret, delay = DELAY_PPS_UPDATE;
ret = update_work_state(sm_dc, SM_DC_PRESET);
if (ret < 0)
return;
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
if (adc_vbat > sm_dc->target_vbat + PPS_V_STEP) { /* Debounce ADC accuracy */
pr_err("%s %s: adc_vbat(%dmV) > target_v, can't start dc\n", sm_dc->name, __func__, adc_vbat);
sm_dc->err = SM_DC_ERR_INVAL_VBAT;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return;
}
pr_info("%s %s: adc_vbat=%dmV, ta_min_v=%dmV, v_max=%dmA, c_max=%dmA, target_ibus=%dmA\n",
sm_dc->name, __func__, adc_vbat, sm_dc->config.ta_min_voltage, sm_dc->ta.v_max,
sm_dc->ta.c_max, sm_dc->target_ibus);
sm_dc->ta.c = MIN(sm_dc->ta.c_max, ((sm_dc->target_ibus * 50) / 100));
sm_dc->ta.c = pps_c(MAX(sm_dc->ta.c, sm_dc->config.ta_min_current));
if (sm_dc->ta.v_max < SM_DC_BYPASS_TA_MAX_VOL) {
sm_dc->ta.v = adc_vbat + _calc_pps_v_init_offset(sm_dc);
sm_dc->ta.v = pps_v(MAX(sm_dc->ta.v, sm_dc->config.ta_min_voltage / 2));
} else {
sm_dc->ta.v = (2 * adc_vbat) + _calc_pps_v_init_offset(sm_dc);
sm_dc->ta.v = pps_v(MAX(sm_dc->ta.v, sm_dc->config.ta_min_voltage));
}
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
msleep(DELAY_PPS_UPDATE);
if (sm_dc->ops->get_charging_enable(sm_dc->i2c) == 0x0)
_adjust_pps_v(sm_dc, sm_dc->ta.v);
if (sm_dc->target_ibus < sm_dc->wq.ci_gl)
delay = DELAY_ADC_UPDATE;
setup_direct_charging_work_config(sm_dc);
if (sm_dc->i2c_sub)
sm_dc->ops->set_charging_enable(sm_dc->i2c_sub, 1);
sm_dc->ops->set_charging_enable(sm_dc->i2c, 1);
pr_info("%s %s: enable Direct-charging\n", sm_dc->name, __func__);
/* Pre-update PRE_CC state. for check to charging initial error case */
ret = update_work_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
pr_info("%s %s: [request] preset -> pre_cc\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRE_CC, delay);
}
static void pd_pre_cc_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, pre_cc_work.work);
int ret, adc_ibus, adc_vbus, adc_vbat;
int delay_time = DELAY_PPS_UPDATE;
u8 loop_status = LOOP_INACTIVE;
pr_info("%s %s: (CI_GL=%dmA)\n", sm_dc->name, __func__, sm_dc->wq.ci_gl);
ret = check_error_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
if (ret == SM_DC_ERR_VBATREG)
loop_status = LOOP_VBATREG;
else if (ret == SM_DC_ERR_IBUSREG)
loop_status = LOOP_IBUSREG;
ret = update_work_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
get_adc_values(sm_dc, "[adc-values]:pre_cc_work", &adc_vbus, &adc_ibus, NULL,
&adc_vbat, NULL, NULL, NULL);
switch (loop_status) {
case LOOP_VBATREG:
sm_dc->wq.cv_cnt = 0;
pr_info("%s %s: [request] pre-cc -> cv\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CV, DELAY_ADC_UPDATE);
return;
case LOOP_IBUSREG:
if (sm_dc->config.need_to_sw_ocp && sm_dc->wq.v_down == 1) {
pr_info("%s %s: call check_sw_ocp\n", sm_dc->name, __func__);
ret = sm_dc->ops->check_sw_ocp(sm_dc->i2c);
if (ret < 0)
return;
}
sm_dc->wq.c_offset = 0;
if (sm_dc->ta.v - (PPS_V_STEP * 2) >= sm_dc->config.ta_min_voltage) {
sm_dc->ta.v -= PPS_V_STEP * 2;
sm_dc->wq.v_down = 1;
sm_dc->wq.v_up = 0;
} else {
sm_dc->ta.v = sm_dc->config.ta_min_voltage;
pr_info("%s %s: can't use less then ta_min_voltage\n", sm_dc->name, __func__);
pr_info("%s %s: [request] pre_cc -> cc\n", sm_dc->name, __func__);
sm_dc->wq.v_down = 0;
sm_dc->wq.pps_vcm = 1;
sm_dc->wq.pps_cl = 0;
sm_dc->wq.cc_limit = 0;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
return;
}
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_PRE_CC, DELAY_PPS_UPDATE);
return;
}
_pd_pre_cc_check_limitation(sm_dc, adc_ibus, adc_vbus);
if (adc_ibus > sm_dc->wq.ci_gl) {
sm_dc->wq.cc_limit = 0;
if (sm_dc->wq.ci_gl > sm_dc->ta.c)
sm_dc->wq.c_offset = sm_dc->wq.ci_gl - sm_dc->ta.c;
else
sm_dc->wq.c_offset = 0;
if (!sm_dc->wq.pps_cl)
sm_dc->wq.pps_vcm = 1;
pr_info("%s %s: [request] pre_cc -> cc (c_offset=%d, pps_cl=%d)\n", sm_dc->name,
__func__, sm_dc->wq.c_offset, sm_dc->wq.pps_cl);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
return;
}
if ((sm_dc->wq.pps_cl) &&
((sm_dc->ta.v * (sm_dc->ta.c + PPS_C_STEP) > sm_dc->ta.p_max) ||
((sm_dc->ta.c + PPS_C_STEP) > sm_dc->ta.c_max) ||
(sm_dc->ta.c > sm_dc->wq.ci_gl + PRE_CC_ST_IBUS_OFFSET))) {
sm_dc->wq.c_offset = 0;
sm_dc->wq.cc_limit = 0;
pr_info("%s %s: [request] pre_cc -> cc\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
return;
}
if (sm_dc->wq.pps_cl) {
if ((adc_ibus < sm_dc->wq.ci_gl - (PPS_C_STEP * 6)) &&
(sm_dc->ta.c < ((sm_dc->wq.ci_gl * 85)/100)))
sm_dc->ta.c += (PPS_C_STEP * 3);
else
sm_dc->ta.c += PPS_C_STEP;
if (sm_dc->ta.c > sm_dc->ta.c_max)
sm_dc->ta.c = sm_dc->ta.c_max;
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->wq.c_up = 1;
sm_dc->wq.c_down = 0;
} else {
sm_dc->ta.v += PPS_V_STEP;
if (sm_dc->ta.v > MIN(sm_dc->ta.v_max, sm_dc->config.dc_vbus_ovp_th - 350)) {
pr_info("%s %s: can't increase voltage(v:%d, v_max:%d)\n",
sm_dc->name, __func__, sm_dc->ta.v, sm_dc->ta.v_max);
sm_dc->ta.v = pps_v(MIN(sm_dc->ta.v_max, sm_dc->config.dc_vbus_ovp_th - 350));
sm_dc->wq.pps_cl = 1;
}
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->wq.v_up = 1;
sm_dc->wq.v_down = 0;
}
sm_dc->wq.prev_adc_vbus = adc_vbus;
sm_dc->wq.prev_adc_ibus = adc_ibus;
request_state_work(sm_dc, SM_DC_PRE_CC, delay_time);
}
static void pd_cc_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, cc_work.work);
int ret, adc_ibus, adc_vbus, adc_vbat;
u8 loop_status = LOOP_INACTIVE;
#if IS_ENABLED(CONFIG_DUAL_BATTERY)
union power_supply_propval value = {0,};
#endif
pr_info("%s %s\n", sm_dc->name, __func__);
ret = check_error_state(sm_dc, SM_DC_CC);
if (ret < 0)
return;
if (ret == SM_DC_ERR_VBATREG)
loop_status = LOOP_VBATREG;
else if (ret == SM_DC_ERR_IBUSREG)
loop_status = LOOP_IBUSREG;
ret = update_work_state(sm_dc, SM_DC_CC);
if (ret < 0)
return;
get_adc_values(sm_dc, "[adc-values]:cc_work", &adc_vbus, &adc_ibus, NULL,
&adc_vbat, NULL, NULL, NULL);
#if IS_ENABLED(CONFIG_DUAL_BATTERY)
psy_do_property("battery", get,
POWER_SUPPLY_EXT_PROP_DIRECT_VBAT_CHECK, value);
if (value.intval) {
pr_info("%s: CC MODE will be done by vcell\n", __func__);
loop_status = LOOP_VBATREG;
}
#endif
switch (loop_status) {
case LOOP_VBATREG:
sm_dc->wq.cv_cnt = 0;
pr_info("%s %s: [request] cc -> cv\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CV, DELAY_ADC_UPDATE);
return;
case LOOP_IBUSREG:
if (sm_dc->config.need_to_sw_ocp) {
pr_info("%s %s: call check_sw_ocp\n", sm_dc->name, __func__);
ret = sm_dc->ops->check_sw_ocp(sm_dc->i2c);
if (ret < 0)
return;
}
break;
}
/* CC_STEP_DOWN */
if (sm_dc->wq.ci_gl < adc_ibus) {
_try_to_adjust_cc_down(sm_dc);
if (sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
return;
}
if (adc_ibus >= sm_dc->wq.ci_gl - CC_ST_IBUS_OFFSET || sm_dc->wq.cc_limit) {
if (sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
request_state_work(sm_dc, SM_DC_CC, DELAY_CHG_LOOP);
return;
}
/* CC_STEP_UP */
ret = _try_to_adjust_cc_up(sm_dc);
if (ret < 0) {
if (sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
request_state_work(sm_dc, SM_DC_CC, DELAY_CHG_LOOP);
} else {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
}
}
static void pd_cv_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, cv_work.work);
int ret, adc_ibus, adc_vbus, adc_vbat, delay = DELAY_CHG_LOOP;
u8 loop_status = LOOP_INACTIVE;
#if IS_ENABLED(CONFIG_DUAL_BATTERY)
union power_supply_propval value = {0,};
#endif
pr_info("%s %s\n", sm_dc->name, __func__);
ret = check_error_state(sm_dc, SM_DC_CV);
if (ret < 0)
return;
if (ret == SM_DC_ERR_VBATREG)
loop_status = LOOP_VBATREG;
else if (ret == SM_DC_ERR_IBUSREG)
loop_status = LOOP_IBUSREG;
ret = update_work_state(sm_dc, SM_DC_CV);
if (ret < 0)
return;
get_adc_values(sm_dc, "[adc-values]:cv_work", &adc_vbus, &adc_ibus, NULL,
&adc_vbat, NULL, NULL, NULL);
if ((sm_dc->wq.cv_cnt == 0) && (loop_status & (LOOP_VBATREG | LOOP_IBUSREG)))
sm_dc->wq.cv_cnt = 1;
else if ((sm_dc->wq.cv_cnt == 1) && (loop_status == LOOP_INACTIVE))
sm_dc->wq.cv_cnt = 2;
switch (loop_status) {
case LOOP_VBATREG:
case LOOP_IBUSREG:
if (sm_dc->wq.cv_cnt == 1)
/* fast decrease PPS_V during on the first vbatreg loop */
sm_dc->ta.v -= PPS_V_STEP * 2;
else
sm_dc->ta.v -= PPS_V_STEP;
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
delay = DELAY_PPS_UPDATE;
break;
case LOOP_THEMREG:
if (sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
delay = DELAY_PPS_UPDATE;
break;
case LOOP_IBATREG:
/* un-used IBATREG*/
loop_status = LOOP_INACTIVE;
break;
}
/* occurred abnormal CV status */
if (adc_vbat < sm_dc->target_vbat - 100) {
pr_info("%s %s: adnormal cv, [request] cv -> pre_cc\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_ONESHOT);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_ONESHOT);
request_state_work(sm_dc, SM_DC_PRE_CC, DELAY_ADC_UPDATE);
return;
}
#if IS_ENABLED(CONFIG_DUAL_BATTERY)
psy_do_property("battery", get,
POWER_SUPPLY_EXT_PROP_DIRECT_VBAT_CHECK, value);
if (value.intval) {
pr_info("%s %s: CV MODE will be done by vcell\n", sm_dc->name, __func__);
schedule_delayed_work(&sm_dc->done_event_work, msecs_to_jiffies(50));
}
#endif
/* Support to "POWER_SUPPLY_EXT_PROP_DIRECT_DONE" used ADC_IBUS */
if (sm_dc->config.topoff_current > 0) {
if ((sm_dc->target_vbat == sm_dc->config.chg_float_voltage) &&
(adc_ibus < sm_dc->config.topoff_current)) {
pr_info("%s %s: dc done!!\n", sm_dc->name, __func__);
schedule_delayed_work(&sm_dc->done_event_work, msecs_to_jiffies(50));
}
}
/* case IBUS_T < 1A than sub DC done */
if (adc_ibus < CV_ST_SUB_DC_OFF_IBUS) {
if (sm_dc->i2c_sub) {
if (sm_dc->ops->get_charging_enable(sm_dc->i2c_sub)) {
sm_dc->ops->set_charging_enable(sm_dc->i2c_sub, 0);
pr_info("%s %s: sub dc done!!\n", sm_dc->name, __func__);
}
}
}
if (loop_status == LOOP_INACTIVE && sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
request_state_work(sm_dc, SM_DC_CV, delay);
}
static void pd_cv_man_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, cv_man_work.work);
int ret, adc_ibus, adc_vbus, adc_vbat, delay = DELAY_ADC_UPDATE;
u8 loop_status = LOOP_INACTIVE;
pr_info("%s %s\n", sm_dc->name, __func__);
ret = check_error_state(sm_dc, SM_DC_CV_MAN);
if (ret < 0)
return;
if (ret == SM_DC_ERR_VBATREG)
loop_status = LOOP_VBATREG;
else if (ret == SM_DC_ERR_IBUSREG)
loop_status = LOOP_IBUSREG;
get_adc_values(sm_dc, "[adc-values]:cv_man_work", &adc_vbus, &adc_ibus, NULL,
&adc_vbat, NULL, NULL, NULL);
switch (loop_status) {
case LOOP_VBATREG:
case LOOP_IBUSREG:
if (sm_dc->ta.v > (2 * adc_vbat) + (PPS_V_STEP * 2))
sm_dc->ta.v -= PPS_V_STEP;
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
delay = DELAY_PPS_UPDATE;
break;
}
if (loop_status == LOOP_INACTIVE && sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
request_state_work(sm_dc, SM_DC_CV_MAN, delay);
}
static void pd_cv_fpdo_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, cv_fpdo_work.work);
int ret, adc_ibus, adc_vbus, adc_vbat, delay = DELAY_ADC_UPDATE;
union power_supply_propval val = {0,};
int mainvbat = 0;
#if IS_ENABLED(CONFIG_DUAL_BATTERY)
int subvbat = 0;
#endif
pr_info("%s %s\n", sm_dc->name, __func__);
ret = check_error_state(sm_dc, SM_DC_CV_FPDO);
if (ret < 0)
return;
get_adc_values(sm_dc, "[adc-values]:cv_fpdo_work", &adc_vbus, &adc_ibus, NULL,
&adc_vbat, NULL, NULL, NULL);
#if IS_ENABLED(CONFIG_DUAL_BATTERY)
psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_MAIN, val);
mainvbat = val.intval * 1000;
psy_do_property("battery", get, POWER_SUPPLY_EXT_PROP_VOLTAGE_PACK_SUB, val);
subvbat = val.intval * 1000;
pr_info("%s %s: topoff=%d/%dmA, mainvbat=%d/%dmV, subvbat=%d/%dmV\n",
sm_dc->name, __func__, adc_ibus * 1000, sm_dc->config.fpdo_topoff,
mainvbat, sm_dc->config.fpdo_mainvbat_reg,
subvbat, sm_dc->config.fpdo_subvbat_reg);
if (mainvbat >= sm_dc->config.fpdo_mainvbat_reg ||
subvbat >= sm_dc->config.fpdo_subvbat_reg ||
adc_ibus * 1000 < sm_dc->config.fpdo_topoff) {
pr_info("%s %s: fpdo dc done!!\n", sm_dc->name, __func__);
schedule_delayed_work(&sm_dc->done_event_work, msecs_to_jiffies(50));
}
#else
psy_do_property("battery", get, POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
mainvbat = val.intval * 1000;
pr_info("%s %s: topoff=%d/%d(mA), vnow=%d/%d(mV)\n",
sm_dc->name, __func__, adc_ibus * 1000, sm_dc->config.fpdo_topoff,
mainvbat, sm_dc->config.fpdo_vnow_reg);
if (mainvbat >= sm_dc->config.fpdo_vnow_reg ||
adc_ibus * 1000 < sm_dc->config.fpdo_topoff) {
pr_info("%s %s: fpdo dc done!!\n", sm_dc->name, __func__);
schedule_delayed_work(&sm_dc->done_event_work, msecs_to_jiffies(50));
}
#endif /* CONFIG_DUAL_BATTERY */
request_state_work(sm_dc, SM_DC_CV_FPDO, delay);
}
static void pd_update_bat_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, update_bat_work.work);
int index, ret, cnt;
bool need_to_preset = 1;
pr_info("%s %s\n", sm_dc->name, __func__);
ret = check_error_state(sm_dc, SM_DC_UPDAT_BAT);
if (ret < 0)
return;
/* waiting for step change event */
for (cnt = 0; cnt < 1; ++cnt) {
if (sm_dc->req_update_vbat && sm_dc->req_update_ibus)
break;
pr_info("%s %s: wait 1sec for step changed\n", sm_dc->name, __func__);
msleep(1000);
}
mutex_lock(&sm_dc->st_lock);
index = (sm_dc->req_update_vbat << 2) | (sm_dc->req_update_ibus << 1) | sm_dc->req_update_ibat;
sm_dc->req_update_vbat = 0;
sm_dc->req_update_ibus = 0;
sm_dc->req_update_ibat = 0;
mutex_unlock(&sm_dc->st_lock);
ret = update_work_state(sm_dc, SM_DC_UPDAT_BAT);
if (ret < 0)
return;
if (index & (0x1 << 2))
pr_info("%s %s: vbat changed (%dmV)\n", sm_dc->name, __func__, sm_dc->target_vbat);
if (index & (0x1 << 1))
pr_info("%s %s: ibus changed (%dmA)\n", sm_dc->name, __func__, sm_dc->target_ibus);
if (index & 0x1)
pr_info("%s %s: ibat changed (%dmA)\n", sm_dc->name, __func__, sm_dc->target_ibat);
/* check step change event */
if ((index & (0x1 << 2)) && (index & (0x1 << 1))) {
if ((sm_dc->target_vbat > sm_dc->wq.cv_gl) && (sm_dc->target_ibus < sm_dc->wq.ci_gl))
need_to_preset = 0;
}
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_ONESHOT);
if (need_to_preset) {
pr_info("%s %s: [request] update_bat -> preset\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRESET, DELAY_NONE);
} else {
setup_direct_charging_work_config(sm_dc);
sm_dc->ta.c = pps_c(sm_dc->wq.ci_gl - 200 - sm_dc->wq.c_offset);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
pr_info("%s %s: [request] update_bat -> pre_cc\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRE_CC, DELAY_ADC_UPDATE);
}
}
static void pd_error_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, error_work.work);
int ret;
pr_info("%s %s: err=0x%x\n", sm_dc->name, __func__, sm_dc->err);
ret = update_work_state(sm_dc, SM_DC_ERR);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_OFF);
sm_dc->ops->set_charging_enable(sm_dc->i2c, 0);
}
static void sec_done_event_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, done_event_work.work);
union power_supply_propval value = {0, };
pr_info("%s called\n", __func__);
value.intval = 1;
psy_do_property(sm_dc->config.sec_dc_name, set, POWER_SUPPLY_EXT_PROP_DIRECT_DONE, value);
}
/**
* Dual battery Direct-charging work functions
*/
static inline u32 _x2bat_calc_v_init_offset(struct sm_dc_info *sm_dc)
{
u32 offset;
offset = (((sm_dc->wq.ci_gl * 50) / 100) * sm_dc->config.r_ttl) / 1000000;
pr_info("%s %s: ci_gl=%dmA, v_init_offset=%d\n", sm_dc->name,
__func__, sm_dc->wq.ci_gl, offset);
return offset;
}
static inline void _x2bat_try_to_adjust_cc_down(struct sm_dc_info *sm_dc)
{
if (sm_dc->ta.v >= sm_dc->config.ta_min_voltage) {
sm_dc->ta.v -= PPS_V_STEP;
} else {
sm_dc->ta.v = sm_dc->config.ta_min_voltage;
pr_info("%s %s: can't use less then ta_min_voltage\n", sm_dc->name, __func__);
}
}
static inline void _x2bat_try_to_adjust_cc_up(struct sm_dc_info *sm_dc)
{
if (sm_dc->ta.v >=
MIN(((sm_dc->ta.p_max * 100) / sm_dc->ta.c) / 100, sm_dc->config.dc_vbus_ovp_th - 350)) {
pr_info("%s %s: can't increase voltage(v:%d, v_max:%d)\n",
sm_dc->name, __func__, sm_dc->ta.v, sm_dc->ta.v_max);
} else {
sm_dc->ta.v += PPS_V_STEP;
}
}
static int _x2bat_set_ibusreg_m_down(struct sm_dc_info *sm_dc)
{
sm_dc->wq.ci_gl_m = MIN(sm_dc->ta.c_max, ((sm_dc->wq.ci_gl_m * 100) / 100) - 100);
sm_dc->wq.ci_gl = MIN(sm_dc->ta.c_max, sm_dc->wq.ci_gl_m + sm_dc->wq.ci_gl_s);
sm_dc->wq.cc_gl = sm_dc->wq.ci_gl * 2;
pr_info("%s %s: CV_GL=%dmV, CI_GL=%dmA, CI_GL_M=%dmA, CI_GL_S=%dmA, CC_GL=%dmA\n", sm_dc->name,
__func__, sm_dc->wq.cv_gl, sm_dc->wq.ci_gl, sm_dc->wq.ci_gl_m, sm_dc->wq.ci_gl_s, sm_dc->wq.cc_gl);
sm_dc->ops->set_charging_config(sm_dc->i2c, sm_dc->wq.cv_gl, sm_dc->wq.ci_gl_m, sm_dc->wq.cc_gl);
return 0;
}
static int x2bat_get_adc_values(struct sm_dc_info *sm_dc, const char *str, int *vbus, int *ibus,
int *ibus_m, int *ibus_s, int *vout, int *vbat_m, int *vbat_s, int *ibat, int *them, int *dietemp)
{
int adc_vbus = 0, adc_ibus = 0, adc_vout = 0, adc_vbat = 0, adc_ibat = 0, adc_them = 0, adc_dietemp = 0, adc_ibus_t = 0;
int adc_vbus2 = 0, adc_ibus2 = 0, adc_vout2 = 0, adc_vbat2 = 0, adc_ibat2 = 0, adc_them2 = 0, adc_dietemp2 = 0;
adc_vbus = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBUS);
adc_ibus = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_IBUS);
adc_vout = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VOUT);
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
adc_ibat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_IBAT);
adc_them = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_THEM);
adc_dietemp = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_DIETEMP);
pr_info("%s %s:vbus:%d:ibus:%d:vout:%d:vbat:%d:ibat:%d:them:%d:dietemp:%d\n",
sm_dc->name, str, adc_vbus, adc_ibus, adc_vout,
adc_vbat, adc_ibat, adc_them, adc_dietemp);
if (sm_dc->i2c_sub) {
if (sm_dc->ops->get_charging_enable(sm_dc->i2c_sub)) {
adc_vbus2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_VBUS);
adc_ibus2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_IBUS);
adc_vout2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_VOUT);
adc_vbat2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_VBAT);
adc_ibat2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_IBAT);
adc_them2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_THEM);
adc_dietemp2 = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_DIETEMP);
adc_ibus_t = adc_ibus + adc_ibus2;
pr_info("%s %s(s):vbus:%d:ibus:%d:ibus_t:%d:vout:%d:vbat:%d:ibat:%d:them:%d:dietemp:%d\n",
sm_dc->name, str, adc_vbus2, adc_ibus2, adc_ibus_t, adc_vout2,
adc_vbat2, adc_ibat2, adc_them2, adc_dietemp2);
} else {
adc_ibus_t = adc_ibus;
}
} else {
adc_ibus_t = adc_ibus;
}
if (vbus)
*vbus = adc_vbus;
if (ibus)
*ibus = adc_ibus_t;
if (ibus_m)
*ibus_m = adc_ibus;
if (ibus_s)
*ibus_s = adc_ibus2;
if (vout)
*vout = adc_vout;
if (vbat_m)
*vbat_m = adc_vbat;
if (vbat_s)
*vbat_s = adc_vbat2;
if (ibat)
*ibat = adc_ibat;
if (them)
*them = adc_them;
if (dietemp)
*dietemp = adc_dietemp;
return 0;
}
static int x2bat_setup_direct_charging_work_config(struct sm_dc_info *sm_dc)
{
u32 target_ibus_sub = 0;
if (sm_dc->i2c_sub)
target_ibus_sub = sm_dc->ops->get_target_ibus(sm_dc->i2c_sub);
sm_dc->wq.cv_cnt = 0;
sm_dc->wq.cv_gl = sm_dc->target_vbat;
sm_dc->wq.ci_gl_s = MIN(sm_dc->ta.c_max, ((target_ibus_sub * 100) / 100));
sm_dc->wq.ci_gl_m = MIN(sm_dc->ta.c_max, ((sm_dc->target_ibus * 100) / 100));
sm_dc->wq.ci_gl = MIN(sm_dc->ta.c_max, sm_dc->target_ibus + target_ibus_sub);
sm_dc->wq.cc_gl = sm_dc->wq.ci_gl * 2;
sm_dc->wq.retry_cnt = 0;
sm_dc->wq.cc_cnt = 0;
sm_dc->wq.topoff_m = 0;
sm_dc->wq.topoff_s = 0;
pr_info("%s %s: CV_GL=%dmV, CI_GL=%dmA, CI_GL_M=%dmA, CI_GL_S=%dmA, CC_GL=%dmA\n", sm_dc->name,
__func__, sm_dc->wq.cv_gl, sm_dc->wq.ci_gl, sm_dc->wq.ci_gl_m, sm_dc->wq.ci_gl_s, sm_dc->wq.cc_gl);
sm_dc->ops->set_charging_config(sm_dc->i2c, sm_dc->wq.cv_gl, sm_dc->wq.ci_gl_m, sm_dc->wq.cc_gl);
if (sm_dc->i2c_sub)
sm_dc->ops->set_charging_config(sm_dc->i2c_sub, sm_dc->wq.cv_gl, sm_dc->wq.ci_gl_s, sm_dc->wq.cc_gl);
return 0;
}
static int x2bat_check_error_state(struct sm_dc_info *sm_dc, u8 retry_state)
{
int adc_vbat, adc_ibus, chgon_m, chgon_s;
u32 sub_err;
if (sm_dc->state == SM_DC_ERR) {
pr_err("%s %s: already occurred error (err=0x%x)\n", sm_dc->name, __func__, sm_dc->err);
return -EINVAL;
}
sm_dc->err = sm_dc->ops->get_dc_error_status(sm_dc->i2c);
if (sm_dc->err == SM_DC_ERR_RETRY) {
pr_err("%s %s: error status retry, wait 2sec\n", sm_dc->name, __func__);
request_state_work(sm_dc, retry_state, DELAY_RETRY);
return -EAGAIN;
} else if (sm_dc->err == SM_DC_ERR_VBATREG) {
sm_dc->err = SM_DC_ERR_VBATREG;
} else if (sm_dc->err == SM_DC_ERR_IBUSREG) {
sm_dc->err = SM_DC_ERR_IBUSREG;
} else if (sm_dc->err > SM_DC_ERR_NONE) {
pr_err("%s %s: error status:0x%x\n", sm_dc->name, __func__, sm_dc->err);
// request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
// return -EPERM;
}
if (sm_dc->i2c_sub) {
chgon_m = sm_dc->ops->get_charging_enable(sm_dc->i2c);
chgon_s = sm_dc->ops->get_charging_enable(sm_dc->i2c_sub);
if (chgon_s == 0 && chgon_m == 0) {
sm_dc->err = SM_DC_ERR_FAIL_ADJUST;
} else if (chgon_s == 0x0) { // CHG_S == 0 && IBUS_M >= 1500 than DC err
adc_ibus = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_IBUS);
if (adc_ibus > SM_DC_DUAL_STOP_IBUS)
sm_dc->err = SM_DC_ERR_FAIL_ADJUST;
} else if (chgon_m == 0x0) { // CHG_M == 0 && IBUS_S >= 1500 than DC err
adc_ibus = sm_dc->ops->get_adc_value(sm_dc->i2c_sub, SM_DC_ADC_IBUS);
if (adc_ibus > SM_DC_DUAL_STOP_IBUS)
sm_dc->err = SM_DC_ERR_FAIL_ADJUST;
}
if (sm_dc->err == SM_DC_ERR_FAIL_ADJUST) {
pr_err("%s %s: error status:0x%x\n", sm_dc->name, __func__, sm_dc->err);
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return -ERANGE;
}
sub_err = sm_dc->ops->get_dc_error_status(sm_dc->i2c_sub);
if (sub_err == SM_DC_ERR_VBATREG) {
sm_dc->err = SM_DC_ERR_VBATREG_S;
return sm_dc->err;
} else if (sm_dc->err == SM_DC_ERR_VBATREG) {
sm_dc->err = SM_DC_ERR_VBATREG;
return sm_dc->err;
} else if (sm_dc->err == SM_DC_ERR_IBUSREG && sub_err == SM_DC_ERR_IBUSREG) {
sm_dc->err = SM_DC_ERR_IBUSREG;
return sm_dc->err;
} else if (sm_dc->err == SM_DC_ERR_IBUSREG) {
sm_dc->err = SM_DC_ERR_IBUSREG_M;
return sm_dc->err;
} else if (sm_dc->err > SM_DC_ERR_NONE) {
pr_err("%s %s: error status:0x%x\n", sm_dc->name, __func__, sm_dc->err);
}
} else {
if (sm_dc->err == SM_DC_ERR_IBUSREG || sm_dc->err == SM_DC_ERR_VBATREG)
return sm_dc->err;
}
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
if (adc_vbat <= sm_dc->config.dc_min_vbat) {
pr_err("%s %s: abnormal adc_vbat(%d)\n", sm_dc->name, __func__, adc_vbat);
sm_dc->err = SM_DC_ERR_INVAL_VBAT;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return -ERANGE;
}
return 0;
}
static void x2bat_check_vbat_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, check_vbat_work.work);
struct sm_dc_power_source_info ta;
union power_supply_propval val;
int adc_vbat;
int ret;
ret = update_work_state(sm_dc, SM_DC_CHECK_VBAT);
if (ret < 0)
return;
ret = psy_do_property(sm_dc->config.sec_dc_name, get,
POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val);
if (ret < 0) {
pr_err("%s %s: is not ready to work (wait 1sec)\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CHECK_VBAT, DELAY_ADC_UPDATE);
return;
}
ret = sm_dc->ops->get_apdo_max_power(sm_dc->i2c, &ta);
if (ret < 0) {
if (sm_dc->ta.retry_cnt < 3) {
pr_err("%s %s: get_apdo_max_power, RETRY=%d\n",
sm_dc->name, __func__, sm_dc->ta.retry_cnt);
sm_dc->ta.retry_cnt++;
request_state_work(sm_dc, SM_DC_CHECK_VBAT, DELAY_PPS_UPDATE);
} else {
pr_err("%s %s: fail to get APDO(ret=%d)\n", sm_dc->name, __func__, ret);
sm_dc->err = SM_DC_ERR_SEND_PD_MSG;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
}
return;
}
if (sm_dc->i2c_sub) {
ret = sm_dc->ops->get_apdo_max_power(sm_dc->i2c_sub, &ta);
if (ret < 0) {
pr_err("%s %s: fail to get APDO(ret=%d)\n", sm_dc->name, __func__, ret);
return;
}
}
val.intval = 0;
if (val.intval == 0) { /* already disabled switching charger */
pr_info("%s %s: [request] check_vbat -> preset\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRESET, DELAY_NONE);
psy_do_property(sm_dc->config.sec_dc_name, set,
POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val);
} else {
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
pr_info("%s %s: adc:vbat=%dmV\n", sm_dc->name, __func__, adc_vbat);
if (adc_vbat > sm_dc->config.dc_min_vbat) {
pr_info("%s %s: set_prop - disable sw_chg\n", sm_dc->name, __func__);
val.intval = 0;
psy_do_property(sm_dc->config.sec_dc_name, set,
POWER_SUPPLY_EXT_PROP_CHARGING_ENABLED_DC, val);
}
pr_err("%s %s: sw_chg not disabled yet (wait 1sec)\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CHECK_VBAT, DELAY_ADC_UPDATE);
}
}
static void x2bat_preset_dc_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, preset_dc_work.work);
u32 target_ibus_sub = 0;
int adc_vbat, ret, delay = DELAY_PPS_UPDATE;
ret = update_work_state(sm_dc, SM_DC_PRESET);
if (ret < 0)
return;
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
if (adc_vbat > sm_dc->target_vbat + PPS_V_STEP) { /* Debounce ADC accuracy */
pr_err("%s %s: adc_vbat(%dmV) > target_v, can't start dc\n", sm_dc->name, __func__, adc_vbat);
sm_dc->err = SM_DC_ERR_INVAL_VBAT;
request_state_work(sm_dc, SM_DC_ERR, DELAY_NONE);
return;
}
if (sm_dc->i2c_sub)
target_ibus_sub = sm_dc->ops->get_target_ibus(sm_dc->i2c_sub);
pr_info("%s %s: adc_vbat=%dmV, ta_min_v=%dmV, v_max=%dmA, c_max=%dmA, target_ibus=%dmA\n",
sm_dc->name, __func__, adc_vbat, sm_dc->config.ta_min_voltage, sm_dc->ta.v_max,
sm_dc->ta.c_max, sm_dc->target_ibus + target_ibus_sub);
sm_dc->wq.ci_gl = MIN(sm_dc->ta.c_max, sm_dc->target_ibus + target_ibus_sub);
sm_dc->ta.c = pps_c(MAX(MIN(sm_dc->ta.c_max, sm_dc->wq.ci_gl + 200),
sm_dc->config.ta_min_current));
if (sm_dc->ta.v_max < SM_DC_BYPASS_TA_MAX_VOL) {
sm_dc->ta.v = adc_vbat + _x2bat_calc_v_init_offset(sm_dc);
sm_dc->ta.v = pps_v(MAX(sm_dc->ta.v, sm_dc->config.ta_min_voltage / 2));
} else {
sm_dc->ta.v = (2 * adc_vbat) + _x2bat_calc_v_init_offset(sm_dc);
sm_dc->ta.v = pps_v(MAX(sm_dc->ta.v, sm_dc->config.ta_min_voltage));
}
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
msleep(DELAY_PPS_UPDATE);
if (sm_dc->ops->get_charging_enable(sm_dc->i2c) == 0x0)
_adjust_pps_v(sm_dc, sm_dc->ta.v);
if (sm_dc->target_ibus < sm_dc->wq.ci_gl)
delay = DELAY_ADC_UPDATE;
x2bat_setup_direct_charging_work_config(sm_dc);
if (sm_dc->i2c_sub)
sm_dc->ops->set_charging_enable(sm_dc->i2c_sub, 1);
sm_dc->ops->set_charging_enable(sm_dc->i2c, 1);
pr_info("%s %s: enable Direct-charging\n", sm_dc->name, __func__);
/* Pre-update PRE_CC state. for check to charging initial error case */
ret = update_work_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
pr_info("%s %s: [request] preset -> pre_cc\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRE_CC, delay);
}
static void x2bat_pre_cc_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, pre_cc_work.work);
int ret, adc_ibus, adc_ibus_s, adc_vbus, adc_vbat;
u8 loop_status = LOOP_INACTIVE;
pr_info("%s %s: (CI_GL=%dmA)\n", sm_dc->name, __func__, sm_dc->wq.ci_gl);
ret = x2bat_check_error_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
if (ret == SM_DC_ERR_VBATREG)
loop_status = LOOP_VBATREG;
else if (ret == SM_DC_ERR_VBATREG_S)
loop_status = LOOP_VBATREG_S;
else if (ret == SM_DC_ERR_IBUSREG)
loop_status = LOOP_IBUSREG;
else if (ret == SM_DC_ERR_IBUSREG_M)
loop_status = LOOP_IBUSREG_M;
ret = update_work_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
x2bat_get_adc_values(sm_dc, "[adc-values]:pre_cc_work", &adc_vbus, &adc_ibus, NULL,
&adc_ibus_s, NULL, &adc_vbat, NULL, NULL, NULL, NULL);
switch (loop_status) {
case LOOP_VBATREG:
case LOOP_VBATREG_S:
sm_dc->wq.cv_cnt = 0;
sm_dc->wq.topoff_m = 0;
sm_dc->wq.topoff_s = 0;
pr_info("%s %s: [request] pre-cc -> cv\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CV, DELAY_ADC_UPDATE);
return;
case LOOP_IBUSREG:
if (sm_dc->config.need_to_sw_ocp && sm_dc->wq.c_down == 1) {
pr_info("%s %s: call check_sw_ocp\n", sm_dc->name, __func__);
ret = sm_dc->ops->check_sw_ocp(sm_dc->i2c);
if (ret < 0)
return;
}
_x2bat_try_to_adjust_cc_down(sm_dc);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_PRE_CC, DELAY_PPS_UPDATE);
return;
case LOOP_IBUSREG_M:
if (adc_ibus_s > sm_dc->wq.ci_gl_s - PPS_C_STEP) {
pr_info("%s %s: [request] pre_cc -> cc\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
return;
}
break;
}
if (sm_dc->ta.v >=
MIN(((sm_dc->ta.p_max * 100) / sm_dc->ta.c) / 100, sm_dc->config.dc_vbus_ovp_th - 350)) {
pr_info("%s %s: [request] pre_cc -> cc\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
}
if (adc_ibus_s < sm_dc->wq.ci_gl_s - 500)
sm_dc->ta.v += PPS_V_STEP * 2;
else
sm_dc->ta.v += PPS_V_STEP;
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_PRE_CC, DELAY_PPS_UPDATE);
}
static void x2bat_cc_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, pre_cc_work.work);
int ret, adc_ibus, adc_ibus_s, adc_vbus, adc_vbat;
u8 loop_status = LOOP_INACTIVE;
pr_info("%s %s: (CI_GL=%dmA)\n", sm_dc->name, __func__, sm_dc->wq.ci_gl);
ret = x2bat_check_error_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
if (ret == SM_DC_ERR_VBATREG)
loop_status = LOOP_VBATREG;
else if (ret == SM_DC_ERR_VBATREG_S)
loop_status = LOOP_VBATREG_S;
else if (ret == SM_DC_ERR_IBUSREG)
loop_status = LOOP_IBUSREG;
else if (ret == SM_DC_ERR_IBUSREG_M)
loop_status = LOOP_IBUSREG_M;
ret = update_work_state(sm_dc, SM_DC_PRE_CC);
if (ret < 0)
return;
x2bat_get_adc_values(sm_dc, "[adc-values]:cc_work", &adc_vbus, &adc_ibus, NULL,
&adc_ibus_s, NULL, &adc_vbat, NULL, NULL, NULL, NULL);
switch (loop_status) {
case LOOP_VBATREG:
case LOOP_VBATREG_S:
sm_dc->wq.cv_cnt = 0;
sm_dc->wq.topoff_m = 0;
sm_dc->wq.topoff_s = 0;
pr_info("%s %s: [request] pre-cc -> cv\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CV, DELAY_ADC_UPDATE);
return;
case LOOP_IBUSREG:
if (sm_dc->config.need_to_sw_ocp && sm_dc->wq.v_down == 1) {
pr_info("%s %s: call check_sw_ocp\n", sm_dc->name, __func__);
ret = sm_dc->ops->check_sw_ocp(sm_dc->i2c);
if (ret < 0)
return;
}
_x2bat_try_to_adjust_cc_down(sm_dc);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
return;
case LOOP_IBUSREG_M:
if (adc_ibus_s < sm_dc->wq.ci_gl_s + (CC_ST_IBUS_OFFSET) &&
adc_ibus_s > sm_dc->wq.ci_gl_s - CC_ST_IBUS_OFFSET) {
if (sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_CHG_LOOP);
return;
} else if (adc_ibus_s > sm_dc->wq.ci_gl_s + (CC_ST_IBUS_OFFSET / 2)) {
_x2bat_try_to_adjust_cc_down(sm_dc);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
}
break;
}
_x2bat_try_to_adjust_cc_up(sm_dc);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CC, DELAY_ADC_UPDATE);
}
static void x2bat_cv_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, cv_work.work);
int ret, adc_ibus, adc_vbus, adc_ibus_s, adc_vbat_m, adc_vbat_s;
u8 loop_status = LOOP_INACTIVE;
pr_info("%s %s\n", sm_dc->name, __func__);
ret = x2bat_check_error_state(sm_dc, SM_DC_CV);
if (ret < 0)
return;
if (ret == SM_DC_ERR_VBATREG)
loop_status = LOOP_VBATREG;
else if (ret == SM_DC_ERR_VBATREG_S)
loop_status = LOOP_VBATREG_S;
else if (ret == SM_DC_ERR_IBUSREG)
loop_status = LOOP_IBUSREG;
else if (ret == SM_DC_ERR_IBUSREG_M)
loop_status = LOOP_IBUSREG_M;
ret = update_work_state(sm_dc, SM_DC_CV);
if (ret < 0)
return;
x2bat_get_adc_values(sm_dc, "[adc-values]:cv_work", &adc_vbus, &adc_ibus, NULL,
&adc_ibus_s, NULL, &adc_vbat_m, &adc_vbat_s, NULL, NULL, NULL);
/* main off */
if (((sm_dc->wq.ci_gl_m - 100) / 100) * 100 <= SM_DC_CI_OFFSET_X2BAT) {
sm_dc->wq.topoff_m = 1;
sm_dc->ops->set_charging_enable(sm_dc->i2c, 0);
pr_info("%s %s: main dc done!!\n", sm_dc->name, __func__);
}
/* sub DC off */
if (sm_dc->config.topoff_current > 0) {
if ((sm_dc->target_vbat == sm_dc->config.chg_float_voltage) &&
(adc_ibus_s < sm_dc->config.topoff_current)) {
sm_dc->wq.topoff_s = 1;
if (sm_dc->i2c_sub) {
if (sm_dc->ops->get_charging_enable(sm_dc->i2c_sub)) {
sm_dc->ops->set_charging_enable(sm_dc->i2c_sub, 0);
pr_info("%s %s: sub dc done!!\n", sm_dc->name, __func__);
}
}
}
}
/* Support to "POWER_SUPPLY_EXT_PROP_DIRECT_DONE" used TOPOFF flag */
if (sm_dc->wq.topoff_m && sm_dc->wq.topoff_s) {
pr_info("%s : dc done!!\n", __func__);
schedule_delayed_work(&sm_dc->done_event_work, msecs_to_jiffies(50));
}
if (!sm_dc->wq.topoff_s) {
if (loop_status == LOOP_VBATREG_S) {
_x2bat_try_to_adjust_cc_down(sm_dc);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CV, DELAY_ADC_UPDATE);
return;
}
}
if (!sm_dc->wq.topoff_m) {
if (loop_status == LOOP_VBATREG) {
_x2bat_set_ibusreg_m_down(sm_dc); // ci_gl = ci_gl - 100mA
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_CONTINUOUS);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_CONTINUOUS);
request_state_work(sm_dc, SM_DC_CV, DELAY_ADC_UPDATE);
}
}
/* occurred abnormal CV status */
if (loop_status == LOOP_IBUSREG ||
adc_vbat_m < sm_dc->wq.ci_gl_m - 100 || adc_vbat_s < sm_dc->wq.ci_gl_s - 100) {
pr_info("%s %s: adnormal cv, [request] cv -> pre_cc\n", sm_dc->name, __func__);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_ONESHOT);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_ONESHOT);
request_state_work(sm_dc, SM_DC_PRE_CC, DELAY_ADC_UPDATE);
return;
}
if (loop_status == LOOP_INACTIVE && sm_dc->config.support_pd_remain) {
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
}
request_state_work(sm_dc, SM_DC_CV, DELAY_CHG_LOOP);
}
static void x2bat_update_bat_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, update_bat_work.work);
int index, ret, cnt;
bool need_to_preset = 1;
pr_info("%s %s\n", sm_dc->name, __func__);
ret = check_error_state(sm_dc, SM_DC_UPDAT_BAT);
if (ret < 0)
return;
/* waiting for step change event */
for (cnt = 0; cnt < 1; ++cnt) {
if (sm_dc->req_update_vbat && sm_dc->req_update_ibus)
break;
pr_info("%s %s: wait 1sec for step changed\n", sm_dc->name, __func__);
msleep(DELAY_PPS_UPDATE);
}
mutex_lock(&sm_dc->st_lock);
index = (sm_dc->req_update_vbat << 2) | (sm_dc->req_update_ibus << 1) | sm_dc->req_update_ibat;
sm_dc->req_update_vbat = 0;
sm_dc->req_update_ibus = 0;
sm_dc->req_update_ibat = 0;
mutex_unlock(&sm_dc->st_lock);
ret = update_work_state(sm_dc, SM_DC_UPDAT_BAT);
if (ret < 0)
return;
if (index & (0x1 << 2))
pr_info("%s %s: vbat changed (%dmV)\n", sm_dc->name, __func__, sm_dc->target_vbat);
if (index & (0x1 << 1))
pr_info("%s %s: ibus changed (%dmA)\n", sm_dc->name, __func__, sm_dc->target_ibus);
if (index & 0x1)
pr_info("%s %s: ibat changed (%dmA)\n", sm_dc->name, __func__, sm_dc->target_ibat);
/* check step change event */
if ((index & (0x1 << 2)) && (index & (0x1 << 1))) {
if ((sm_dc->target_vbat > sm_dc->wq.cv_gl) && (sm_dc->target_ibus < sm_dc->wq.ci_gl))
need_to_preset = 0;
}
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_ONESHOT);
if (need_to_preset) {
pr_info("%s %s: [request] update_bat -> preset\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRESET, DELAY_NONE);
} else {
x2bat_setup_direct_charging_work_config(sm_dc);
sm_dc->ta.c = pps_c(MAX(MIN(sm_dc->ta.c_max, sm_dc->wq.ci_gl + 200),
sm_dc->config.ta_min_current));
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return;
pr_info("%s %s: [request] update_bat -> pre_cc\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_PRE_CC, DELAY_ADC_UPDATE);
}
}
static void x2bat_error_work(struct work_struct *work)
{
struct sm_dc_info *sm_dc = container_of(work, struct sm_dc_info, error_work.work);
int ret;
pr_info("%s %s: err=0x%x\n", sm_dc->name, __func__, sm_dc->err);
ret = update_work_state(sm_dc, SM_DC_ERR);
if (ret < 0)
return;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_OFF);
sm_dc->ops->set_charging_enable(sm_dc->i2c, 0);
}
/**
* SM Direct-charging module management APIs
*/
struct sm_dc_info *sm_dc_create_pd_instance(const char *name, struct i2c_client *i2c)
{
struct sm_dc_info *sm_dc;
int ret;
sm_dc = kzalloc(sizeof(struct sm_dc_info), GFP_KERNEL);
if (!sm_dc)
return ERR_PTR(-ENOMEM);
sm_dc->name = name;
sm_dc->i2c = i2c;
sm_dc->i2c_sub = NULL;
mutex_init(&sm_dc->st_lock);
/* create work queue */
sm_dc->state = SM_DC_CHG_OFF;
sm_dc->dc_wqueue = create_singlethread_workqueue(name);
if (!sm_dc->dc_wqueue) {
pr_err("%s %s: fail to crearte workqueue\n", name, __func__);
ret = -ENOMEM;
goto err_kmem;
}
INIT_DELAYED_WORK(&sm_dc->check_vbat_work, pd_check_vbat_work);
INIT_DELAYED_WORK(&sm_dc->preset_dc_work, pd_preset_dc_work);
INIT_DELAYED_WORK(&sm_dc->pre_cc_work, pd_pre_cc_work);
INIT_DELAYED_WORK(&sm_dc->cc_work, pd_cc_work);
INIT_DELAYED_WORK(&sm_dc->cv_work, pd_cv_work);
INIT_DELAYED_WORK(&sm_dc->cv_man_work, pd_cv_man_work);
INIT_DELAYED_WORK(&sm_dc->cv_fpdo_work, pd_cv_fpdo_work);
INIT_DELAYED_WORK(&sm_dc->update_bat_work, pd_update_bat_work);
INIT_DELAYED_WORK(&sm_dc->error_work, pd_error_work);
/* for SEC_BATTERY done event process */
INIT_DELAYED_WORK(&sm_dc->done_event_work, sec_done_event_work);
pr_info("%s %s: done.\n", name, __func__);
return sm_dc;
err_kmem:
mutex_destroy(&sm_dc->st_lock);
kfree(sm_dc);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(sm_dc_create_pd_instance);
struct sm_dc_info *sm_dc_create_x2bat_instance(const char *name, struct i2c_client *i2c)
{
struct sm_dc_info *sm_dc;
int ret;
sm_dc = kzalloc(sizeof(struct sm_dc_info), GFP_KERNEL);
if (!sm_dc)
return ERR_PTR(-ENOMEM);
sm_dc->name = name;
sm_dc->i2c = i2c;
sm_dc->i2c_sub = NULL;
mutex_init(&sm_dc->st_lock);
/* create work queue */
sm_dc->state = SM_DC_CHG_OFF;
sm_dc->dc_wqueue = create_singlethread_workqueue(name);
if (!sm_dc->dc_wqueue) {
pr_err("%s %s: fail to crearte workqueue\n", name, __func__);
ret = -ENOMEM;
goto err_kmem;
}
INIT_DELAYED_WORK(&sm_dc->check_vbat_work, x2bat_check_vbat_work);
INIT_DELAYED_WORK(&sm_dc->preset_dc_work, x2bat_preset_dc_work);
INIT_DELAYED_WORK(&sm_dc->pre_cc_work, x2bat_pre_cc_work);
INIT_DELAYED_WORK(&sm_dc->cc_work, x2bat_cc_work);
INIT_DELAYED_WORK(&sm_dc->cv_work, x2bat_cv_work);
INIT_DELAYED_WORK(&sm_dc->update_bat_work, x2bat_update_bat_work);
INIT_DELAYED_WORK(&sm_dc->error_work, x2bat_error_work);
INIT_DELAYED_WORK(&sm_dc->done_event_work, sec_done_event_work);
pr_info("%s %s: done.\n", name, __func__);
return sm_dc;
err_kmem:
mutex_destroy(&sm_dc->st_lock);
kfree(sm_dc);
return ERR_PTR(ret);
}
EXPORT_SYMBOL(sm_dc_create_x2bat_instance);
int sm_dc_verify_configuration(struct sm_dc_info *sm_dc)
{
if (sm_dc == NULL)
return -EINVAL;
if (sm_dc->ops == NULL)
return -EINVAL;
return 0;
}
EXPORT_SYMBOL(sm_dc_verify_configuration);
void sm_dc_destroy_instance(struct sm_dc_info *sm_dc)
{
if (sm_dc != NULL) {
destroy_workqueue(sm_dc->dc_wqueue);
mutex_destroy(&sm_dc->st_lock);
kfree(sm_dc);
}
}
EXPORT_SYMBOL(sm_dc_destroy_instance);
int sm_dc_report_error_status(struct sm_dc_info *sm_dc, u32 err)
{
terminate_charging_work(sm_dc);
sm_dc->state = SM_DC_ERR;
sm_dc->err = err;
report_dc_state(sm_dc);
return 0;
}
EXPORT_SYMBOL(sm_dc_report_error_status);
int sm_dc_report_interrupt_event(struct sm_dc_info *sm_dc, u32 interrupt)
{
if ((sm_dc->state == SM_DC_CC) && (interrupt == SM_DC_INT_VBATREG)) {
if (delayed_work_pending(&sm_dc->cc_work)) {
cancel_delayed_work(&sm_dc->cc_work);
pr_info("%s %s: cancel CC_work, direct request work\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CC, DELAY_NONE);
}
}
return 0;
}
EXPORT_SYMBOL(sm_dc_report_interrupt_event);
int sm_dc_get_current_state(struct sm_dc_info *sm_dc)
{
return sm_dc->state;
}
EXPORT_SYMBOL(sm_dc_get_current_state);
int sm_dc_start_charging(struct sm_dc_info *sm_dc)
{
if (sm_dc->state >= SM_DC_CHECK_VBAT) {
pr_err("%s %s: already work on dc (state=%d)\n", sm_dc->name, __func__, sm_dc->state);
return -EBUSY;
}
sm_dc->ta.pdo_pos = 0; /* set '0' else return error */
sm_dc->ta.v_max = 10000; /* request voltage level */
sm_dc->ta.c_max = 0;
sm_dc->ta.p_max = 0;
sm_dc->ta.retry_cnt = 0;
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_ONESHOT);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_ONESHOT);
mutex_lock(&sm_dc->st_lock);
sm_dc->state = SM_DC_CHECK_VBAT; /* Pre-update chg.state */
mutex_unlock(&sm_dc->st_lock);
request_state_work(sm_dc, SM_DC_CHECK_VBAT, DELAY_PPS_UPDATE);
pr_info("%s %s: done\n", sm_dc->name, __func__);
return 0;
}
EXPORT_SYMBOL(sm_dc_start_charging);
int sm_dc_stop_charging(struct sm_dc_info *sm_dc)
{
mutex_lock(&sm_dc->st_lock);
sm_dc->state = SM_DC_CHG_OFF;
mutex_unlock(&sm_dc->st_lock);
terminate_charging_work(sm_dc);
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_OFF);
if (sm_dc->i2c_sub)
sm_dc->ops->set_adc_mode(sm_dc->i2c_sub, SM_DC_ADC_MODE_OFF);
report_dc_state(sm_dc);
return 0;
}
EXPORT_SYMBOL(sm_dc_stop_charging);
int sm_dc_start_manual_charging(struct sm_dc_info *sm_dc)
{
struct sm_dc_power_source_info ta;
int ret = 0;
int adc_vbat, cnt;
sm_dc->ta.pdo_pos = 0;
sm_dc->ta.v_max = 10000;
sm_dc->ta.c_max = 0;
sm_dc->ta.p_max = 0;
for (cnt = 0; cnt < 3; ++cnt) {
ret = sm_dc->ops->get_apdo_max_power(sm_dc->i2c, &ta);
if (ret < 0)
pr_err("%s %s: fail to get APDO(ret=%d)\n", sm_dc->name, __func__, ret);
else
break;
msleep(DELAY_PPS_UPDATE);
}
if (ret < 0)
return ret;
mutex_lock(&sm_dc->st_lock);
sm_dc->state = SM_DC_CV_MAN; /* direct change the chg.state */
mutex_unlock(&sm_dc->st_lock);
for (cnt = 0; cnt < 3; ++cnt) {
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_ONESHOT);
msleep(DELAY_PPS_UPDATE);
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
if (adc_vbat < sm_dc->config.dc_min_vbat)
pr_err("%s %s: adc_vbat=%dmV, RETRY=%d\n", sm_dc->name, __func__, adc_vbat, cnt);
else
break;
if (cnt == 2) {
pr_err("%s %s: adc_vbat(%dmV) less then dc_min_vbat(%dmV)\n",
sm_dc->name, __func__, adc_vbat, sm_dc->config.dc_min_vbat);
ret = -EINVAL;
}
}
if (ret < 0) {
mutex_lock(&sm_dc->st_lock);
sm_dc->state = SM_DC_ERR;
mutex_unlock(&sm_dc->st_lock);
return ret;
}
sm_dc->target_vbat = pps_v(adc_vbat + (PPS_V_STEP * 10)); /* VBAT_ADC + 200mV */
sm_dc->target_ibus = SM_DC_MANUAL_TA_MAX_CUR;
sm_dc->ta.c = pps_c(MIN(sm_dc->ta.c_max, sm_dc->target_ibus));
sm_dc->ta.v = pps_v((2 * adc_vbat) + (PPS_V_STEP * 4)); /* VBAT_ADC + 80mV */
pr_info("%s %s: adc_vbat=%dmV, ta_min_v=%dmV, v_max=%dmV, c_max=%dmA, target_ibus=%dmA, target_vbat=%dmV\n",
sm_dc->name, __func__, adc_vbat, sm_dc->config.ta_min_voltage, sm_dc->ta.v_max,
sm_dc->ta.c_max, sm_dc->target_ibus, sm_dc->target_vbat);
setup_direct_charging_work_config(sm_dc);
ret = send_power_source_msg(sm_dc);
if (ret < 0) {
mutex_lock(&sm_dc->st_lock);
sm_dc->state = SM_DC_ERR;
mutex_unlock(&sm_dc->st_lock);
return ret;
}
sm_dc->ops->set_charging_enable(sm_dc->i2c, 1);
pr_info("%s %s: enable Direct-charging\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CV_MAN, DELAY_PPS_UPDATE);
pr_info("%s %s: done\n", sm_dc->name, __func__);
return 0;
}
EXPORT_SYMBOL(sm_dc_start_manual_charging);
int sm_dc_start_fpdo_charging(struct sm_dc_info *sm_dc)
{
int ret = 0;
int adc_vbat, cnt;
if (sm_dc->state >= SM_DC_CHECK_VBAT) {
pr_err("%s %s: already work on dc (state=%d)\n", sm_dc->name, __func__, sm_dc->state);
return -EBUSY;
}
sm_dc->ta.pdo_pos = 2; /* Set PDO object position to 9V FPDO */
sm_dc->ta.v_max = 9000; /* Set TA voltage to 9V */
sm_dc->ta.c_max = sm_dc->config.fpdo_chg_curr;
sm_dc->ta.p_max = sm_dc->ta.v_max / 1000 * sm_dc->ta.c_max;
sm_dc->ta.v = sm_dc->ta.v_max;
sm_dc->ta.c = sm_dc->ta.c_max;
sm_dc->target_ibus = MAX(sm_dc->config.fpdo_chg_curr, sm_dc->target_ibus);
sm_dc->target_vbat = 4800; /* Set VBATREG to 4800mV */
sm_dc->ta.retry_cnt = 0;
pr_info("%s %s: set FPDO DC: v_max=%dmV, c_max=%dmA, p_max=%dmW, target_ibus=%d\n", sm_dc->name,
__func__, sm_dc->ta.v_max, sm_dc->ta.c_max, sm_dc->ta.p_max, sm_dc->target_ibus);
mutex_lock(&sm_dc->st_lock);
sm_dc->state = SM_DC_CV_FPDO; /* direct change the chg.state */
mutex_unlock(&sm_dc->st_lock);
for (cnt = 0; cnt < 3; ++cnt) {
sm_dc->ops->set_adc_mode(sm_dc->i2c, SM_DC_ADC_MODE_ONESHOT);
msleep(DELAY_PPS_UPDATE);
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
if (adc_vbat < sm_dc->config.dc_min_vbat)
pr_err("%s %s: adc_vbat=%dmV, RETRY=%d\n", sm_dc->name, __func__, adc_vbat, cnt);
else
break;
if (cnt == 2) {
pr_err("%s %s: adc_vbat(%dmV) less then dc_min_vbat(%dmV)\n",
sm_dc->name, __func__, adc_vbat, sm_dc->config.dc_min_vbat);
ret = -EINVAL;
}
}
if (ret < 0) {
mutex_lock(&sm_dc->st_lock);
sm_dc->state = SM_DC_ERR;
mutex_unlock(&sm_dc->st_lock);
return ret;
}
setup_direct_charging_work_config(sm_dc);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return ret;
sm_dc->ops->set_charging_enable(sm_dc->i2c, 1);
pr_info("%s %s: enable Direct-charging\n", sm_dc->name, __func__);
request_state_work(sm_dc, SM_DC_CV_FPDO, DELAY_PPS_UPDATE);
pr_info("%s %s: done\n", sm_dc->name, __func__);
return 0;
}
EXPORT_SYMBOL(sm_dc_start_fpdo_charging);
/* Set TA voltage for bypass mode */
int sm_dc_set_ta_volt_by_soc(struct sm_dc_info *sm_dc, int delta_soc)
{
int ret = 0;
unsigned int prev_ta_vol = sm_dc->ta.v;
if (delta_soc < 0) { // increase soc (soc_now - ref_soc)
sm_dc->ta.v += PPS_V_STEP;
} else if (delta_soc > 0) { // decrease soc (soc_now - ref_soc)
sm_dc->ta.v -= PPS_V_STEP;
} else {
pr_info("%s: abnormal delta_soc=%d\n", __func__, delta_soc);
return -1;
}
pr_info("%s: delta_soc=%d, prev_ta_vol=%d, ta_vol=%d, ta_cur=%d\n",
__func__, delta_soc, prev_ta_vol, sm_dc->ta.v, sm_dc->ta.c);
ret = send_power_source_msg(sm_dc);
if (ret < 0)
return ret;
request_state_work(sm_dc, SM_DC_CV_MAN, DELAY_PPS_UPDATE);
return ret;
}
EXPORT_SYMBOL(sm_dc_set_ta_volt_by_soc);
int sm_dc_set_target_vbat(struct sm_dc_info *sm_dc, u32 target_vbat)
{
int ret = 0;
int adc_vbat;
pr_info("%s %s: [%dmV] to [%dmV]\n", sm_dc->name, __func__, sm_dc->target_vbat, target_vbat);
sm_dc->target_vbat = target_vbat;
if (sm_dc->state > SM_DC_CHECK_VBAT) {
adc_vbat = sm_dc->ops->get_adc_value(sm_dc->i2c, SM_DC_ADC_VBAT);
if (sm_dc->target_vbat > adc_vbat - PPS_V_STEP) {
mutex_lock(&sm_dc->st_lock);
sm_dc->req_update_vbat = 1;
mutex_unlock(&sm_dc->st_lock);
pr_info("%s %s: request VBAT update on DC work\n", sm_dc->name, __func__);
} else {
pr_err("%s %s: target_vbat(%dmV) less then adc_vbat(%dmV)\n", sm_dc->name, __func__,
sm_dc->target_vbat, adc_vbat);
ret = -EINVAL;
}
}
return ret;
}
EXPORT_SYMBOL(sm_dc_set_target_vbat);
int sm_dc_set_target_ibus(struct sm_dc_info *sm_dc, u32 target_ibus)
{
pr_info("%s %s: [%dmA] to [%dmA]\n", sm_dc->name, __func__, sm_dc->target_ibus, target_ibus);
sm_dc->target_ibus = target_ibus;
if (sm_dc->state > SM_DC_CHECK_VBAT) {
mutex_lock(&sm_dc->st_lock);
sm_dc->req_update_ibus = 1;
mutex_unlock(&sm_dc->st_lock);
pr_info("%s %s: request IBUS update on DC work\n", sm_dc->name, __func__);
}
return 0;
}
EXPORT_SYMBOL(sm_dc_set_target_ibus);
int sm_dc_set_target_ibat(struct sm_dc_info *sm_dc, u32 target_ibat)
{
/* if need to it, we need to improve direct-charging module for cc_loop */
return 0;
}
EXPORT_SYMBOL(sm_dc_set_target_ibat);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SiliconMitus <hwangjoo.jang@SiliconMitus.com>");
MODULE_DESCRIPTION("Direct-charger module for SM ICs");