1448 lines
34 KiB
C
1448 lines
34 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2019 MediaTek Inc.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
*
|
||
|
* Filename:
|
||
|
* ---------
|
||
|
* mtk_pe2.c
|
||
|
*
|
||
|
* Project:
|
||
|
* --------
|
||
|
* Android_Software
|
||
|
*
|
||
|
* Description:
|
||
|
* ------------
|
||
|
* This Module defines functions of Battery charging
|
||
|
*
|
||
|
* Author:
|
||
|
* -------
|
||
|
* Wy Chuang
|
||
|
*
|
||
|
*/
|
||
|
#include <linux/init.h> /* For init/exit macros */
|
||
|
#include <linux/module.h> /* For MODULE_ marcros */
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/kdev_t.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/wait.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/poll.h>
|
||
|
#include <linux/power_supply.h>
|
||
|
#include <linux/pm_wakeup.h>
|
||
|
#include <linux/time.h>
|
||
|
#include <linux/mutex.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/proc_fs.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/seq_file.h>
|
||
|
#include <linux/scatterlist.h>
|
||
|
#include <linux/suspend.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_irq.h>
|
||
|
#include <linux/of_address.h>
|
||
|
#include <linux/reboot.h>
|
||
|
|
||
|
#include "mtk_pe2.h"
|
||
|
#include "mtk_charger_algorithm_class.h"
|
||
|
|
||
|
|
||
|
static int pe2_dbg_level = PE2_DEBUG_LEVEL;
|
||
|
|
||
|
int pe2_get_debug_level(void)
|
||
|
{
|
||
|
return pe2_dbg_level;
|
||
|
}
|
||
|
|
||
|
static int pe2_plugout_reset(struct chg_alg_device *alg)
|
||
|
{
|
||
|
int ret = 0, cnt = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
|
||
|
pe2_dbg("%s\n", __func__);
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
switch (pe2->state) {
|
||
|
case PE2_HW_UNINIT:
|
||
|
case PE2_HW_FAIL:
|
||
|
case PE2_HW_READY:
|
||
|
break;
|
||
|
case PE2_TA_NOT_SUPPORT:
|
||
|
pe2->state = PE2_HW_READY;
|
||
|
break;
|
||
|
case PE2_RUN:
|
||
|
case PE2_TUNING:
|
||
|
case PE2_POSTCC:
|
||
|
/* set flag to end PE2 thread asap */
|
||
|
mutex_lock(&pe2->cable_out_lock);
|
||
|
pe2->is_cable_out_occur = true;
|
||
|
mutex_unlock(&pe2->cable_out_lock);
|
||
|
|
||
|
while (mutex_trylock(&pe2->access_lock) == 0) {
|
||
|
pe2_err("%s:pe2 is running state:%d cnt:%d\n",
|
||
|
__func__, pe2->state,
|
||
|
cnt);
|
||
|
cnt++;
|
||
|
msleep(100);
|
||
|
}
|
||
|
|
||
|
mutex_lock(&pe2->cable_out_lock);
|
||
|
pe2->is_cable_out_occur = false;
|
||
|
mutex_unlock(&pe2->cable_out_lock);
|
||
|
|
||
|
pe2->idx = -1;
|
||
|
pe2->vbus = 5000000; /* mV */
|
||
|
|
||
|
/* Enable OVP */
|
||
|
ret = pe2_hal_enable_vbus_ovp(alg, true);
|
||
|
if (ret < 0)
|
||
|
pe2_err("%s:enable vbus ovp fail, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
/* Set MIVR for vbus 5V */
|
||
|
ret = pe2_hal_set_mivr(alg, CHG1,
|
||
|
pe2->min_charger_voltage); /* uV */
|
||
|
if (ret < 0)
|
||
|
pe2_err("%s:set mivr fail, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
if (alg->config == DUAL_CHARGERS_IN_SERIES) {
|
||
|
pe2_hal_enable_charger(alg, CHG2, false);
|
||
|
pe2_hal_charger_enable_chip(alg, CHG2, false);
|
||
|
}
|
||
|
|
||
|
pe2_dbg("%s: OK\n", __func__);
|
||
|
pe2->state = PE2_HW_READY;
|
||
|
mutex_unlock(&pe2->access_lock);
|
||
|
break;
|
||
|
default:
|
||
|
pe2_dbg("%s: error state:%d\n", __func__,
|
||
|
pe2->state);
|
||
|
break;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int pe2_reset_ta_vchr(struct chg_alg_device *alg)
|
||
|
{
|
||
|
int ret, chr_volt = 0, ret_value = -1;
|
||
|
u32 retry_cnt = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int chg_cnt, i, is_chip_enabled;
|
||
|
|
||
|
pe2_dbg("%s: starts\n", __func__);
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
ret = pe2_hal_set_mivr(alg, 0, pe2->min_charger_voltage);
|
||
|
if (ret < 0)
|
||
|
pe2_err("%s:set mivr fail, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
/* Reset TA's charging voltage */
|
||
|
do {
|
||
|
chg_cnt = pe2_hal_get_charger_cnt(alg);
|
||
|
if (chg_cnt > 1 && alg->config == DUAL_CHARGERS_IN_SERIES) {
|
||
|
for (i = CHG2; i < CHG_MAX; i++) {
|
||
|
is_chip_enabled =
|
||
|
pe2_hal_is_chip_enable(alg, i);
|
||
|
if (is_chip_enabled)
|
||
|
pe2_hal_enable_charger(alg, i, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pe2_hal_reset_ta(alg, CHG1);
|
||
|
if (ret < 0)
|
||
|
pe2_err("%s:reset TA fail, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
msleep(250);
|
||
|
|
||
|
/* Check charger's voltage */
|
||
|
chr_volt = pe2_hal_get_vbus(alg);
|
||
|
pe2_dbg("%s: ori_vbus:%d vbus:%d\n", __func__,
|
||
|
pe2->ta_vchr_org, chr_volt);
|
||
|
if (abs(chr_volt - pe2->ta_vchr_org) <= 1000000) {
|
||
|
pe2->vbus = chr_volt;
|
||
|
pe2->idx = -1;
|
||
|
ret_value = 0;
|
||
|
break;
|
||
|
}
|
||
|
retry_cnt++;
|
||
|
} while (retry_cnt < 3);
|
||
|
|
||
|
if (ret_value != 0) {
|
||
|
pe2_err("%s: failed, ret = %d\n", __func__, ret);
|
||
|
ret = pe2_hal_set_mivr(alg, CHG1, pe2->vbus - 500000);
|
||
|
if (ret < 0)
|
||
|
pe2_err("%s:set mivr fail, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
return -EHAL;
|
||
|
}
|
||
|
/* Measure VBAT */
|
||
|
pe2->vbat_orig = pe2_hal_get_vbus(alg);
|
||
|
pe2_dbg("%s: OK\n", __func__);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void pe2_check_cable_impedance(struct chg_alg_device *alg)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
int vchr1, vchr2, cable_imp;
|
||
|
unsigned int aicr_value;
|
||
|
bool mivr_state = false;
|
||
|
struct timespec ptime[2], diff;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int input_current;
|
||
|
|
||
|
|
||
|
pe2_dbg("%s: starts type:%d\n", __func__,
|
||
|
alg->config);
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
if (alg->config == SINGLE_CHARGER)
|
||
|
input_current = pe2->sc_input_current;
|
||
|
else if (alg->config == DUAL_CHARGERS_IN_SERIES)
|
||
|
input_current = pe2->dcs_input_current;
|
||
|
else
|
||
|
input_current = pe2->sc_input_current;
|
||
|
|
||
|
if (pe2->vbat_orig > pe2->vbat_cable_imp_threshold) {
|
||
|
pe2_info("VBAT > %dmV, directly set aicr to %dmA\n",
|
||
|
pe2->vbat_cable_imp_threshold / 1000,
|
||
|
input_current / 1000);
|
||
|
pe2->aicr_cable_imp = input_current;
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
/* Disable cable drop compensation */
|
||
|
pe2_hal_enable_cable_drop_comp(alg, false);
|
||
|
|
||
|
get_monotonic_boottime(&ptime[0]);
|
||
|
|
||
|
/* Set ichg = 2500mA, set MIVR */
|
||
|
pe2_hal_set_charging_current(alg, CHG1, 2500000);
|
||
|
mdelay(240);
|
||
|
pe2_hal_set_mivr(alg, CHG1, pe2->min_charger_voltage);
|
||
|
|
||
|
get_monotonic_boottime(&ptime[1]);
|
||
|
diff = timespec_sub(ptime[1], ptime[0]);
|
||
|
|
||
|
aicr_value = 800000;
|
||
|
pe2_hal_set_input_current(alg, CHG1, aicr_value);
|
||
|
|
||
|
/* To wait for soft-start */
|
||
|
msleep(150);
|
||
|
|
||
|
ret = pe2_hal_get_mivr_state(alg, CHG1, &mivr_state);
|
||
|
if (ret != -ENOTSUPP && mivr_state) {
|
||
|
pe2->aicr_cable_imp = 1000000;
|
||
|
goto end;
|
||
|
}
|
||
|
|
||
|
vchr1 = pe2_hal_get_vbus(alg);
|
||
|
|
||
|
aicr_value = 500000;
|
||
|
pe2_hal_set_input_current(alg, CHG1, aicr_value);
|
||
|
msleep(20);
|
||
|
|
||
|
vchr2 = pe2_hal_get_vbus(alg);
|
||
|
|
||
|
/*
|
||
|
* Calculate cable impedance (|V1 - V2|) / (|I2 - I1|)
|
||
|
* m_ohm = (mv * 10 * 1000) / (mA * 10)
|
||
|
* m_ohm = (uV * 10) / (mA * 10)
|
||
|
*/
|
||
|
cable_imp = (abs(vchr1 - vchr2) * 10) / (7400 - 4625);
|
||
|
|
||
|
pe2_info("%s: cable_imp:%d mohm, vchr1:%d, vchr2:%d, time:%ld\n",
|
||
|
__func__, cable_imp, vchr1 / 1000, vchr2 / 1000,
|
||
|
diff.tv_nsec);
|
||
|
|
||
|
/* Recover cable drop compensation */
|
||
|
aicr_value = 100000;
|
||
|
pe2_hal_set_input_current(alg, CHG1, aicr_value);
|
||
|
msleep(250);
|
||
|
|
||
|
if (cable_imp < pe2->cable_imp_threshold) {
|
||
|
pe2->aicr_cable_imp = input_current;
|
||
|
pe2_info("Normal cable\n");
|
||
|
} else {
|
||
|
pe2->aicr_cable_imp = 1000000; /* uA */
|
||
|
pe2_info("Bad cable\n");
|
||
|
}
|
||
|
pe2_hal_set_input_current(alg, CHG1, pe2->aicr_cable_imp);
|
||
|
|
||
|
pe2_info("%s: set aicr:%dmA, vbat:%dmV, mivr_state:%d\n",
|
||
|
__func__, pe2->aicr_cable_imp / 1000,
|
||
|
pe2->vbat_orig / 1000, mivr_state);
|
||
|
return;
|
||
|
|
||
|
end:
|
||
|
pe2_info("%s not started: set aicr:%dmA, vbat:%dmV, mivr_state:%d\n",
|
||
|
__func__, pe2->aicr_cable_imp / 1000,
|
||
|
pe2->vbat_orig / 1000, mivr_state);
|
||
|
}
|
||
|
|
||
|
static int _pe20_set_ta_vchr(struct chg_alg_device *alg, u32 chr_volt)
|
||
|
{
|
||
|
int ret = 0, ret_value = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int chg_cnt, i;
|
||
|
bool is_chip_enabled;
|
||
|
|
||
|
pe2_dbg("%s: starts\n", __func__);
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
/* Not to set chr volt if cable is plugged out */
|
||
|
if (pe2->is_cable_out_occur) {
|
||
|
pe2_err("%s: failed, cable out\n", __func__);
|
||
|
return -ECABLEOUT;
|
||
|
}
|
||
|
|
||
|
chg_cnt = pe2_hal_get_charger_cnt(alg);
|
||
|
if (chg_cnt > 1 && alg->config == DUAL_CHARGERS_IN_SERIES) {
|
||
|
for (i = CHG2; i < CHG_MAX; i++) {
|
||
|
is_chip_enabled = pe2_hal_is_chip_enable(alg, i);
|
||
|
if (is_chip_enabled) {
|
||
|
ret = pe2_hal_enable_charger(alg, i, false);
|
||
|
if (ret < 0) {
|
||
|
pe2_err("%s:enable chip fail,idx:%d ret:%d\n",
|
||
|
__func__, i, ret);
|
||
|
ret_value = -EHAL;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
ret = pe2_hal_send_ta20_current_pattern(alg, chr_volt);
|
||
|
if (ret < 0) {
|
||
|
pe2_err("%s: pattern failed, ret = %d\n", __func__, ret);
|
||
|
ret_value = -EHAL;
|
||
|
}
|
||
|
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
static int pe20_set_ta_vchr(struct chg_alg_device *alg, u32 chr_volt)
|
||
|
{
|
||
|
int ret = 0, ret_value = 0;
|
||
|
int vchr_before, vchr_after, vchr_delta;
|
||
|
const u32 sw_retry_cnt_max = 3;
|
||
|
const u32 retry_cnt_max = 5;
|
||
|
u32 sw_retry_cnt = 0, retry_cnt = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
|
||
|
pe2_dbg("%s: starts, target:%d\n", __func__, chr_volt / 1000);
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
do {
|
||
|
vchr_before = pe2_hal_get_vbus(alg);
|
||
|
ret = _pe20_set_ta_vchr(alg, chr_volt);
|
||
|
vchr_after = pe2_hal_get_vbus(alg);
|
||
|
|
||
|
vchr_delta = abs(vchr_after - chr_volt);
|
||
|
|
||
|
/*
|
||
|
* It is successful if VBUS difference to target is
|
||
|
* less than 500mV.
|
||
|
*/
|
||
|
pe2_err("ret:%d delta:%d %d %d\n",
|
||
|
ret, vchr_delta,
|
||
|
vchr_after, chr_volt);
|
||
|
if (vchr_delta < 500000 && ret == 0) {
|
||
|
pe2_dbg("%s: OK, vchr = (%d, %d), vchr_target = %dmV\n",
|
||
|
__func__, vchr_before / 1000, vchr_after / 1000,
|
||
|
chr_volt / 1000);
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
if (ret == 0 || sw_retry_cnt >= sw_retry_cnt_max)
|
||
|
retry_cnt++;
|
||
|
else
|
||
|
sw_retry_cnt++;
|
||
|
|
||
|
ret = pe2_hal_set_mivr(alg, CHG1, pe2->min_charger_voltage);
|
||
|
if (ret < 0)
|
||
|
pe2_err("%s:set mivr fail, ret:%d\n",
|
||
|
__func__, ret);
|
||
|
|
||
|
pe2_dbg("%s: retry_cnt = (%d, %d), vchr = (%d, %d), vchr_target = %dmV\n",
|
||
|
__func__, sw_retry_cnt, retry_cnt, vchr_before / 1000,
|
||
|
vchr_after / 1000, chr_volt / 1000);
|
||
|
pe2_dbg("%s: %d %d %d %d\n", __func__,
|
||
|
pe2->is_cable_out_occur,
|
||
|
pe2_hal_get_charger_type(alg),
|
||
|
retry_cnt,
|
||
|
retry_cnt_max);
|
||
|
|
||
|
} while (!pe2->is_cable_out_occur &&
|
||
|
(pe2_hal_get_charger_type(alg) !=
|
||
|
POWER_SUPPLY_TYPE_USB_DCP) &&
|
||
|
(retry_cnt < retry_cnt_max));
|
||
|
|
||
|
if (pe2->is_cable_out_occur)
|
||
|
ret_value = -ECABLEOUT;
|
||
|
else
|
||
|
ret_value = -EHAL;
|
||
|
|
||
|
pe2_dbg("%s: failed, vchr_org = %dmV, vchr_after = %dmV, target_vchr = %dmV\n",
|
||
|
__func__, pe2->ta_vchr_org / 1000, vchr_after / 1000,
|
||
|
chr_volt / 1000);
|
||
|
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int pe20_detect_ta(struct chg_alg_device *alg)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
|
||
|
pe2_dbg("%s: starts\n", __func__);
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
pe2->ta_vchr_org = pe2_hal_get_vbus(alg);
|
||
|
|
||
|
/* Disable OVP */
|
||
|
ret = pe2_hal_enable_vbus_ovp(alg, false);
|
||
|
if (ret < 0)
|
||
|
goto err;
|
||
|
if (abs(pe2->ta_vchr_org - 8500000) > 500000)
|
||
|
ret = pe20_set_ta_vchr(alg, 8500000);
|
||
|
else
|
||
|
ret = pe20_set_ta_vchr(alg, 6500000);
|
||
|
|
||
|
pe2_dbg("%s: ret:%d\n", __func__, ret);
|
||
|
|
||
|
return ret;
|
||
|
err:
|
||
|
pe2_hal_enable_vbus_ovp(alg, true);
|
||
|
pe2_err("%s: failed, ret = %d\n", __func__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int __pe2_check_charger(struct chg_alg_device *alg)
|
||
|
{
|
||
|
int ret = 0, ret_value = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int uisoc;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
uisoc = pe2_hal_get_uisoc(alg);
|
||
|
|
||
|
pe2_dbg("%s uisoc:%d s:%d end:%d type:%d", __func__,
|
||
|
uisoc,
|
||
|
pe2->ta_start_battery_soc,
|
||
|
pe2->ta_stop_battery_soc,
|
||
|
pe2_hal_get_charger_type(alg));
|
||
|
|
||
|
if (pe2_hal_get_charger_type(alg) !=
|
||
|
POWER_SUPPLY_TYPE_USB_DCP) {
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
if (pe2->is_cable_out_occur)
|
||
|
goto out;
|
||
|
|
||
|
if (uisoc < pe2->ta_start_battery_soc ||
|
||
|
uisoc >= pe2->ta_stop_battery_soc) {
|
||
|
ret_value = ALG_TA_CHECKING;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
ret = pe2_reset_ta_vchr(alg);
|
||
|
if (ret != 0)
|
||
|
goto out;
|
||
|
|
||
|
if (pe2->is_cable_out_occur)
|
||
|
goto out;
|
||
|
|
||
|
pe2_check_cable_impedance(alg);
|
||
|
|
||
|
if (pe2->is_cable_out_occur)
|
||
|
goto out;
|
||
|
|
||
|
ret = pe20_detect_ta(alg);
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
|
||
|
pe2_dbg("%s: OK, state = %d\n",
|
||
|
__func__, pe2->state);
|
||
|
|
||
|
return ret;
|
||
|
out:
|
||
|
|
||
|
if (ret_value == 0)
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
pe2_dbg("%s:SOC:(%d,%d,%d),state:%d,chr_type:%d,ret:%d,plugout:%d\n",
|
||
|
__func__,
|
||
|
pe2_hal_get_uisoc(alg),
|
||
|
pe2->ta_start_battery_soc,
|
||
|
pe2->ta_stop_battery_soc,
|
||
|
pe2->state,
|
||
|
pe2_hal_get_charger_type(alg),
|
||
|
ret,
|
||
|
pe2->is_cable_out_occur);
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
static int pe2_leave(struct chg_alg_device *alg)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
|
||
|
pe2_dbg("%s: starts\n", __func__);
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
ret = pe2_reset_ta_vchr(alg);
|
||
|
if (ret != 0) {
|
||
|
pe2_err("%s: failed, state = %d, ret = %d\n",
|
||
|
__func__, pe2->state, ret);
|
||
|
}
|
||
|
|
||
|
ret = pe2_hal_enable_vbus_ovp(alg, true);
|
||
|
if (ret != 0) {
|
||
|
pe2_err("%s: enable vbus ovp fai,ret:%d\n",
|
||
|
__func__, ret);
|
||
|
}
|
||
|
|
||
|
pe2_hal_set_mivr(alg, CHG1, pe2->min_charger_voltage);
|
||
|
if (ret != 0) {
|
||
|
pe2_err("%s:set mivr fail,ret:%d\n",
|
||
|
__func__, ret);
|
||
|
}
|
||
|
|
||
|
pe2_dbg("%s: OK\n", __func__);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int _pe2_init_algo(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int ret, cnt;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
mutex_lock(&pe2->access_lock);
|
||
|
if (pe2_hal_init_hardware(alg) != 0) {
|
||
|
pe2->state = PE2_HW_FAIL;
|
||
|
pe2_err("%s:init hw fail\n", __func__);
|
||
|
} else
|
||
|
pe2->state = PE2_HW_READY;
|
||
|
ret = pe2_hal_set_efficiency_table(pe2->alg);
|
||
|
if (ret != 0)
|
||
|
pe2_err("%s: use default table, %d\n", __func__, ret);
|
||
|
|
||
|
if (alg->config == DUAL_CHARGERS_IN_PARALLEL) {
|
||
|
pe2_err("%s does not support DUAL_CHARGERS_IN_PARALLEL\n",
|
||
|
__func__);
|
||
|
alg->config = SINGLE_CHARGER;
|
||
|
} else if (alg->config == DUAL_CHARGERS_IN_SERIES) {
|
||
|
cnt = pe2_hal_get_charger_cnt(alg);
|
||
|
if (cnt == 2)
|
||
|
alg->config = DUAL_CHARGERS_IN_SERIES;
|
||
|
else
|
||
|
alg->config = SINGLE_CHARGER;
|
||
|
} else
|
||
|
alg->config = SINGLE_CHARGER;
|
||
|
mutex_unlock(&pe2->access_lock);
|
||
|
pe2_dbg("%s config:%d\n", __func__, alg->config);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static char *pe2_state_to_str(int state)
|
||
|
{
|
||
|
switch (state) {
|
||
|
case PE2_HW_UNINIT:
|
||
|
return "PE2_HW_UNINIT";
|
||
|
case PE2_HW_FAIL:
|
||
|
return "PE2_HW_FAIL";
|
||
|
case PE2_HW_READY:
|
||
|
return "PE2_HW_READY";
|
||
|
case PE2_TA_NOT_SUPPORT:
|
||
|
return "PE2_TA_NOT_SUPPORT";
|
||
|
case PE2_RUN:
|
||
|
return "PE2_RUN";
|
||
|
case PE2_TUNING:
|
||
|
return "PE2_TUNING";
|
||
|
case PE2_POSTCC:
|
||
|
return "PE2_POSTCC";
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
pe2_err("%s unknown state:%d\n", __func__
|
||
|
, state);
|
||
|
return "PE2_UNKNOWN";
|
||
|
}
|
||
|
|
||
|
static int _pe2_is_algo_ready(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int ret_value, uisoc;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
mutex_lock(&pe2->access_lock);
|
||
|
__pm_stay_awake(pe2->suspend_lock);
|
||
|
pe2_dbg("%s state:%s\n", __func__,
|
||
|
pe2_state_to_str(pe2->state));
|
||
|
|
||
|
switch (pe2->state) {
|
||
|
case PE2_HW_UNINIT:
|
||
|
case PE2_HW_FAIL:
|
||
|
ret_value = ALG_INIT_FAIL;
|
||
|
break;
|
||
|
case PE2_HW_READY:
|
||
|
uisoc = pe2_hal_get_uisoc(alg);
|
||
|
if (pe2_hal_get_charger_type(alg) !=
|
||
|
POWER_SUPPLY_TYPE_USB_DCP) {
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
} else if (uisoc < pe2->ta_start_battery_soc ||
|
||
|
uisoc >= pe2->ta_stop_battery_soc ||
|
||
|
pe2->charging_current_limit1 != -1 ||
|
||
|
pe2->charging_current_limit2 != -1) {
|
||
|
ret_value = ALG_NOT_READY;
|
||
|
} else {
|
||
|
ret_value = ALG_READY;
|
||
|
}
|
||
|
break;
|
||
|
case PE2_TA_NOT_SUPPORT:
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
break;
|
||
|
case PE2_RUN:
|
||
|
case PE2_TUNING:
|
||
|
case PE2_POSTCC:
|
||
|
ret_value = ALG_RUNNING;
|
||
|
break;
|
||
|
default:
|
||
|
ret_value = ALG_INIT_FAIL;
|
||
|
break;
|
||
|
}
|
||
|
__pm_relax(pe2->suspend_lock);
|
||
|
mutex_unlock(&pe2->access_lock);
|
||
|
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
static int pe2_sc_set_charger(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int ichg1_min = -1, aicr1_min = -1;
|
||
|
int ret;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
if (pe2->input_current_limit1 == 0 ||
|
||
|
pe2->charging_current_limit1 == 0) {
|
||
|
pr_notice("input/charging current is 0, end PE2\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
mutex_lock(&pe2->data_lock);
|
||
|
if (pe2->charging_current_limit1 != -1) {
|
||
|
if (pe2->charging_current_limit1 <
|
||
|
pe2->sc_charger_current)
|
||
|
pe2->charging_current1 =
|
||
|
pe2->charging_current_limit1;
|
||
|
ret = pe2_hal_get_min_charging_current(alg, CHG1, &ichg1_min);
|
||
|
if (ret != -ENOTSUPP &&
|
||
|
pe2->charging_current_limit1 < ichg1_min)
|
||
|
pe2->charging_current1 = 0;
|
||
|
} else
|
||
|
pe2->charging_current1 = pe2->sc_charger_current;
|
||
|
|
||
|
if (pe2->input_current_limit1 != -1 &&
|
||
|
pe2->input_current_limit1 <
|
||
|
pe2->sc_input_current) {
|
||
|
pe2->input_current1 = pe2->input_current_limit1;
|
||
|
ret = pe2_hal_get_min_input_current(alg, CHG1, &aicr1_min);
|
||
|
if (ret != -ENOTSUPP &&
|
||
|
pe2->input_current_limit1 < aicr1_min)
|
||
|
pe2->input_current1 = 0;
|
||
|
} else
|
||
|
pe2->input_current1 = pe2->sc_input_current;
|
||
|
mutex_unlock(&pe2->data_lock);
|
||
|
|
||
|
|
||
|
if (pe2->input_current1 == 0 ||
|
||
|
pe2->charging_current1 == 0) {
|
||
|
pe2_err("current is zero %d %d\n",
|
||
|
pe2->input_current1,
|
||
|
pe2->charging_current1);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
pe2_hal_set_charging_current(alg,
|
||
|
CHG1, pe2->charging_current1);
|
||
|
pe2_hal_set_input_current(alg,
|
||
|
CHG1, pe2->input_current1);
|
||
|
pe2_hal_set_cv(alg,
|
||
|
CHG1, pe2->cv);
|
||
|
|
||
|
pe2_dbg("%s m:%d s:%d cv:%d chg1:%d,%d min:%d:%d\n", __func__,
|
||
|
alg->config,
|
||
|
pe2->state,
|
||
|
pe2->cv,
|
||
|
pe2->input_current1,
|
||
|
pe2->charging_current1,
|
||
|
ichg1_min,
|
||
|
aicr1_min);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int pe2_dcs_set_charger(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
//bool chg1_enable = true;
|
||
|
bool chg2_enable = true;
|
||
|
bool chg2_chip_enabled = false;
|
||
|
int ret;
|
||
|
int ichg1_min = -1, ichg2_min = -1;
|
||
|
int aicr1_min = -1;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
if (pe2->input_current_limit1 == 0 ||
|
||
|
pe2->charging_current_limit1 == 0 ||
|
||
|
pe2->charging_current_limit2 == 0) {
|
||
|
pr_notice("input/charging current is 0, end PE2\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
mutex_lock(&pe2->data_lock);
|
||
|
if (pe2->input_current_limit1 != -1 &&
|
||
|
pe2->input_current_limit1 <
|
||
|
pe2->dcs_input_current) {
|
||
|
pe2->input_current1 = pe2->input_current_limit1;
|
||
|
ret = pe2_hal_get_min_input_current(alg, CHG1, &aicr1_min);
|
||
|
if (ret != -ENOTSUPP &&
|
||
|
pe2->input_current_limit1 < aicr1_min)
|
||
|
pe2->input_current1 = 0;
|
||
|
} else
|
||
|
pe2->input_current1 = pe2->dcs_input_current;
|
||
|
|
||
|
if (pe2->charging_current_limit1 != -1 &&
|
||
|
pe2->charging_current_limit1 <
|
||
|
pe2->dcs_chg1_charger_current) {
|
||
|
pe2->charging_current1 = pe2->charging_current_limit1;
|
||
|
ret = pe2_hal_get_min_charging_current(alg, CHG1, &ichg1_min);
|
||
|
if (ret != -ENOTSUPP &&
|
||
|
pe2->charging_current_limit1 < ichg1_min)
|
||
|
pe2->charging_current1 = 0;
|
||
|
} else
|
||
|
pe2->charging_current1 = pe2->dcs_chg1_charger_current;
|
||
|
|
||
|
if (pe2->state == PE2_RUN)
|
||
|
pe2->charging_current2 = pe2->dcs_chg2_charger_current;
|
||
|
|
||
|
if (pe2->charging_current_limit2 != -1 &&
|
||
|
pe2->charging_current_limit2 <
|
||
|
pe2->charging_current2) {
|
||
|
pe2->charging_current2 = pe2->charging_current_limit2;
|
||
|
ret = pe2_hal_get_min_charging_current(alg, CHG2, &ichg1_min);
|
||
|
if (ret != -ENOTSUPP &&
|
||
|
pe2->charging_current_limit2 < ichg1_min)
|
||
|
pe2->charging_current2 = 0;
|
||
|
}
|
||
|
mutex_unlock(&pe2->data_lock);
|
||
|
|
||
|
if (pe2->input_current1 == 0 ||
|
||
|
pe2->charging_current1 == 0 ||
|
||
|
pe2->charging_current2 == 0) {
|
||
|
pe2_err("current is zero %d %d %d\n",
|
||
|
pe2->input_current1,
|
||
|
pe2->charging_current1,
|
||
|
pe2->charging_current2);
|
||
|
pe2_hal_enable_charger(alg, CHG2, false);
|
||
|
pe2_hal_charger_enable_chip(alg, CHG2, false);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
chg2_chip_enabled = pe2_hal_is_chip_enable(alg, CHG2);
|
||
|
pe2_err("chg2_en:%d %d %d\n",
|
||
|
chg2_enable, chg2_chip_enabled, pe2->state);
|
||
|
if (pe2->state == PE2_RUN) {
|
||
|
if (!chg2_chip_enabled)
|
||
|
pe2_hal_charger_enable_chip(alg, CHG2, true);
|
||
|
pe2_hal_enable_charger(alg, CHG2, true);
|
||
|
pe2_hal_set_input_current(alg,
|
||
|
CHG2, pe2->charging_current2);
|
||
|
pe2_hal_set_charging_current(alg,
|
||
|
CHG2, pe2->charging_current2);
|
||
|
|
||
|
pe2_hal_set_eoc_current(alg, CHG1,
|
||
|
pe2->dual_polling_ieoc);
|
||
|
pe2_hal_enable_termination(alg, CHG1, false);
|
||
|
pe2_hal_safety_check(alg, pe2->dual_polling_ieoc);
|
||
|
} else if (pe2->state == PE2_TUNING) {
|
||
|
if (!chg2_chip_enabled)
|
||
|
pe2_hal_charger_enable_chip(alg, CHG2, true);
|
||
|
pe2_hal_enable_charger(alg, CHG2, true);
|
||
|
pe2_hal_set_eoc_current(alg, CHG1, pe2->dual_polling_ieoc);
|
||
|
pe2_hal_enable_termination(alg, CHG1, false);
|
||
|
pe2_hal_safety_check(alg, pe2->dual_polling_ieoc);
|
||
|
} else if (pe2->state == PE2_POSTCC) {
|
||
|
pe2_hal_set_eoc_current(alg, CHG1, 150000);
|
||
|
pe2_hal_enable_termination(alg, CHG1, true);
|
||
|
} else {
|
||
|
pe2_err("%s state error!", __func__);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
pe2_hal_set_charging_current(alg,
|
||
|
CHG1, pe2->charging_current1);
|
||
|
pe2_hal_set_input_current(alg,
|
||
|
CHG1, pe2->input_current1);
|
||
|
pe2_hal_set_cv(alg,
|
||
|
CHG1, pe2->cv);
|
||
|
|
||
|
pe2_dbg("%s m:%d s:%d cv:%d chg1:%d,%d chg2:%d,%d chg2en:%d min:%d,%d,%d\n",
|
||
|
__func__,
|
||
|
alg->config,
|
||
|
pe2->state,
|
||
|
pe2->cv,
|
||
|
pe2->input_current1,
|
||
|
pe2->charging_current1,
|
||
|
pe2->input_current2,
|
||
|
pe2->charging_current2,
|
||
|
chg2_enable,
|
||
|
ichg1_min,
|
||
|
ichg2_min,
|
||
|
aicr1_min);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __pe2_run(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int i;
|
||
|
int vbat, vbus, ichg;
|
||
|
int pre_vbus, pre_idx;
|
||
|
int tune = 0, pes = 0; /* For log, to know the state of PE+20 */
|
||
|
u32 size;
|
||
|
int ret = 0, ret_value = 0, vchr, uisoc;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
if (pe2->is_cable_out_occur) {
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
vbat = pe2_hal_get_vbat(alg);
|
||
|
vbus = pe2_hal_get_vbus(alg);
|
||
|
ichg = pe2_hal_get_ibat(alg);
|
||
|
|
||
|
pre_vbus = pe2->vbus;
|
||
|
pre_idx = pe2->idx;
|
||
|
|
||
|
/* PE+ leaves unexpectedly */
|
||
|
vchr = pe2_hal_get_vbus(alg);
|
||
|
if (abs(vchr - pe2->ta_vchr_org) < 1000000) {
|
||
|
pe2_err("%s: PE+20 leave unexpectedly, recheck TA %d %d\n",
|
||
|
__func__,
|
||
|
vchr,
|
||
|
pe2->ta_vchr_org);
|
||
|
ret = pe2_leave(alg);
|
||
|
pe2->ta_vchr_org = vchr;
|
||
|
ret_value = ALG_TA_CHECKING;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
ichg = pe2_hal_get_ibat(alg);
|
||
|
/* Check SOC & Ichg */
|
||
|
uisoc = pe2_hal_get_uisoc(alg);
|
||
|
if (uisoc > pe2->ta_stop_battery_soc &&
|
||
|
ichg > 0 && ichg < pe2->pe20_ichg_level_threshold) {
|
||
|
ret = pe2_leave(alg);
|
||
|
pe2_err("%s: OK, SOC = (%d,%d), stop PE+20\n", __func__,
|
||
|
uisoc, pe2->ta_stop_battery_soc);
|
||
|
ret_value = ALG_DONE;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
size = ARRAY_SIZE(pe2->profile);
|
||
|
for (i = 0; i < size; i++) {
|
||
|
tune = 0;
|
||
|
|
||
|
/* Exceed this level, check next level */
|
||
|
if (vbat > (pe2->profile[i].vbat + 100000))
|
||
|
continue;
|
||
|
|
||
|
/* If vbat is still 30mV larger than the lower level
|
||
|
* Do not down grade
|
||
|
*/
|
||
|
if (i < pe2->idx && vbat > (pe2->profile[i].vbat + 30000))
|
||
|
continue;
|
||
|
|
||
|
if (pe2->vbus != pe2->profile[i].vchr)
|
||
|
tune = 1;
|
||
|
|
||
|
pe2->vbus = pe2->profile[i].vchr;
|
||
|
pe2->idx = i;
|
||
|
|
||
|
if (abs(vbus - pe2->vbus) >= 1000000)
|
||
|
tune = 2;
|
||
|
|
||
|
if (tune != 0) {
|
||
|
ret = pe20_set_ta_vchr(alg, pe2->vbus);
|
||
|
if (ret == 0)
|
||
|
pe2_hal_set_mivr(alg, CHG1, pe2->vbus - 500000);
|
||
|
else {
|
||
|
pe2_leave(alg);
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
}
|
||
|
} else
|
||
|
pe2_hal_set_mivr(alg, CHG1, pe2->vbus - 500000);
|
||
|
break;
|
||
|
}
|
||
|
pes = 2;
|
||
|
|
||
|
if (alg->config == DUAL_CHARGERS_IN_SERIES) {
|
||
|
if (pe2_dcs_set_charger(alg) != 0) {
|
||
|
ret = pe2_leave(alg);
|
||
|
ret_value = ALG_DONE;
|
||
|
goto out;
|
||
|
}
|
||
|
} else {
|
||
|
if (pe2_sc_set_charger(alg) != 0) {
|
||
|
ret = pe2_leave(alg);
|
||
|
ret_value = ALG_DONE;
|
||
|
goto out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pe2_dbg("%s cv:%d chg1:%d,%d chg2:%d,%d\n", __func__,
|
||
|
pe2->cv,
|
||
|
pe2->input_current1,
|
||
|
pe2->charging_current1,
|
||
|
pe2->input_current2,
|
||
|
pe2->charging_current2);
|
||
|
|
||
|
out:
|
||
|
|
||
|
pe2_dbg("%s: vbus = (%d, %d), idx = (%d, %d), I = %d plugout=%d\n",
|
||
|
__func__, pre_vbus / 1000, pe2->vbus / 1000, pre_idx,
|
||
|
pe2->idx, ichg / 1000, pe2->is_cable_out_occur);
|
||
|
|
||
|
pe2_dbg("%s: SOC = %d, state = %s, tune = %d, pes = %d, vbat = %d, ret = %d:%d\n",
|
||
|
__func__, pe2_hal_get_uisoc(alg),
|
||
|
pe2_state_to_str(pe2->state),
|
||
|
tune, pes,
|
||
|
vbat / 1000, ret, ret_value);
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
static int _pe2_start_algo(struct chg_alg_device *alg)
|
||
|
{
|
||
|
int ret = 0, ret_value = 0;
|
||
|
struct mtk_pe20 *pe2;
|
||
|
bool again;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
mutex_lock(&pe2->access_lock);
|
||
|
__pm_stay_awake(pe2->suspend_lock);
|
||
|
|
||
|
do {
|
||
|
pe2_info("%s state:%d %s %d\n", __func__,
|
||
|
pe2->state,
|
||
|
pe2_state_to_str(pe2->state),
|
||
|
again);
|
||
|
|
||
|
again = false;
|
||
|
switch (pe2->state) {
|
||
|
case PE2_HW_UNINIT:
|
||
|
case PE2_HW_FAIL:
|
||
|
ret_value = ALG_INIT_FAIL;
|
||
|
break;
|
||
|
case PE2_HW_READY:
|
||
|
ret = __pe2_check_charger(alg);
|
||
|
if (ret == 0) {
|
||
|
pe2->state = PE2_RUN;
|
||
|
ret_value = ALG_READY;
|
||
|
again = true;
|
||
|
} else if (ret == ALG_TA_CHECKING)
|
||
|
ret_value = ALG_TA_CHECKING;
|
||
|
else if (pe2->charging_current_limit1 != -1 ||
|
||
|
pe2->charging_current_limit2 != -1)
|
||
|
ret_value = ALG_NOT_READY;
|
||
|
else {
|
||
|
pe2->state = PE2_TA_NOT_SUPPORT;
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
}
|
||
|
break;
|
||
|
case PE2_TA_NOT_SUPPORT:
|
||
|
ret_value = ALG_TA_NOT_SUPPORT;
|
||
|
break;
|
||
|
case PE2_RUN:
|
||
|
case PE2_TUNING:
|
||
|
case PE2_POSTCC:
|
||
|
ret = __pe2_run(alg);
|
||
|
if (ret == ALG_TA_NOT_SUPPORT)
|
||
|
pe2->state = PE2_TA_NOT_SUPPORT;
|
||
|
else if (ret == ALG_TA_CHECKING) {
|
||
|
pe2->state = PE2_RUN;
|
||
|
again = true;
|
||
|
} else if (ret == ALG_DONE)
|
||
|
pe2->state = PE2_HW_READY;
|
||
|
ret_value = ret;
|
||
|
break;
|
||
|
default:
|
||
|
pe2_err("PE2 unknown state:%d\n", pe2->state);
|
||
|
ret_value = ALG_INIT_FAIL;
|
||
|
break;
|
||
|
}
|
||
|
} while (again == true);
|
||
|
__pm_relax(pe2->suspend_lock);
|
||
|
mutex_unlock(&pe2->access_lock);
|
||
|
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool _pe2_is_algo_running(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
|
||
|
pe2_dbg("%s\n", __func__);
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
if (pe2->state == PE2_RUN ||
|
||
|
pe2->state == PE2_TUNING ||
|
||
|
pe2->state == PE2_POSTCC)
|
||
|
return true;
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static int _pe2_stop_algo(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
|
||
|
pe2_dbg("%s %d\n", __func__, pe2->state);
|
||
|
if (pe2->state == PE2_RUN ||
|
||
|
pe2->state == PE2_TUNING ||
|
||
|
pe2->state == PE2_POSTCC) {
|
||
|
pe2_reset_ta_vchr(alg);
|
||
|
pe2->state = PE2_HW_READY;
|
||
|
if (alg->config == DUAL_CHARGERS_IN_SERIES) {
|
||
|
pe2_hal_enable_charger(alg, CHG2, false);
|
||
|
pe2_hal_charger_enable_chip(alg,
|
||
|
CHG2, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int pe2_full_event(struct chg_alg_device *alg)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int ret;
|
||
|
bool chg_en, chg2_enabled = false;
|
||
|
int ichg2, ichg2_min;
|
||
|
int ret_value = 0;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
switch (pe2->state) {
|
||
|
case PE2_HW_UNINIT:
|
||
|
case PE2_HW_FAIL:
|
||
|
case PE2_HW_READY:
|
||
|
case PE2_TA_NOT_SUPPORT:
|
||
|
break;
|
||
|
case PE2_RUN:
|
||
|
case PE2_TUNING:
|
||
|
case PE2_POSTCC:
|
||
|
if (alg->config == DUAL_CHARGERS_IN_SERIES) {
|
||
|
pe2_hal_is_charger_enable(alg, CHG2, &chg_en);
|
||
|
chg2_enabled = pe2_hal_is_chip_enable(alg, CHG2);
|
||
|
|
||
|
if (!chg_en || !chg2_enabled) {
|
||
|
/* notify eoc , fix me */
|
||
|
pe2->state = PE2_HW_READY;
|
||
|
pe2_err("charging done:%d %d\n",
|
||
|
__func__, chg_en, chg2_enabled);
|
||
|
if (alg->is_polling_mode == false)
|
||
|
ret_value = 1;
|
||
|
} else {
|
||
|
pe2_hal_get_charging_current(
|
||
|
alg, CHG2, &ichg2);
|
||
|
ret = pe2_hal_get_min_charging_current(
|
||
|
alg, CHG2, &ichg2_min);
|
||
|
if (ret == -ENOTSUPP)
|
||
|
ichg2_min = 100000;
|
||
|
|
||
|
pe2_err("ichg2:%d, ichg2_min:%d state:%d\n",
|
||
|
ichg2, ichg2_min, pe2->state);
|
||
|
if (ichg2 - 500000 <= ichg2_min) {
|
||
|
pe2->state = PE2_POSTCC;
|
||
|
pe2_hal_enable_charger(alg,
|
||
|
CHG2, false);
|
||
|
pe2_hal_set_eoc_current(alg,
|
||
|
CHG1, 150000);
|
||
|
pe2_hal_enable_termination(alg,
|
||
|
CHG1, true);
|
||
|
} else {
|
||
|
pe2->state = PE2_TUNING;
|
||
|
mutex_lock(&pe2->data_lock);
|
||
|
if (pe2->charging_current2 >= 500000)
|
||
|
pe2->charging_current2 = ichg2
|
||
|
- 500000;
|
||
|
pe2_hal_set_charging_current(alg,
|
||
|
CHG2, pe2->charging_current2);
|
||
|
mutex_unlock(&pe2->data_lock);
|
||
|
}
|
||
|
ret_value = 1;
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
if (pe2->state == PE2_RUN) {
|
||
|
pe2_err("%s evt full\n", __func__);
|
||
|
pe2_leave(alg);
|
||
|
pe2->state = PE2_HW_READY;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
pe2_dbg("%s: error state:%d\n", __func__,
|
||
|
pe2->state);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
static int _pe2_notifier_call(struct chg_alg_device *alg,
|
||
|
struct chg_alg_notify *notify)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
int ret_value = 0;
|
||
|
|
||
|
pe2 = dev_get_drvdata(&alg->dev);
|
||
|
pe2_err("%s evt:%d state:%s\n", __func__, notify->evt,
|
||
|
pe2_state_to_str(pe2->state));
|
||
|
|
||
|
switch (notify->evt) {
|
||
|
case EVT_PLUG_OUT:
|
||
|
ret_value = pe2_plugout_reset(alg);
|
||
|
break;
|
||
|
case EVT_FULL:
|
||
|
ret_value = pe2_full_event(alg);
|
||
|
break;
|
||
|
default:
|
||
|
ret_value = -EINVAL;
|
||
|
}
|
||
|
return ret_value;
|
||
|
}
|
||
|
|
||
|
static void mtk_pe2_parse_dt(struct mtk_pe20 *pe2,
|
||
|
struct device *dev)
|
||
|
{
|
||
|
struct device_node *np = dev->of_node;
|
||
|
u32 val;
|
||
|
|
||
|
/* PE 2.0 */
|
||
|
if (of_property_read_u32(np, "pe20_ichg_level_threshold", &val) >= 0)
|
||
|
pe2->pe20_ichg_level_threshold = val;
|
||
|
else {
|
||
|
pr_notice("use default PE20_ICHG_LEAVE_THRESHOLD:%d\n",
|
||
|
PE20_ICHG_LEAVE_THRESHOLD);
|
||
|
pe2->pe20_ichg_level_threshold =
|
||
|
PE20_ICHG_LEAVE_THRESHOLD;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "ta_start_battery_soc", &val) >= 0)
|
||
|
pe2->ta_start_battery_soc = val;
|
||
|
else {
|
||
|
pr_notice("use default TA_START_BATTERY_SOC:%d\n",
|
||
|
TA_START_BATTERY_SOC);
|
||
|
pe2->ta_start_battery_soc = TA_START_BATTERY_SOC;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "ta_stop_battery_soc", &val) >= 0)
|
||
|
pe2->ta_stop_battery_soc = val;
|
||
|
else {
|
||
|
pr_notice("use default TA_STOP_BATTERY_SOC:%d\n",
|
||
|
TA_STOP_BATTERY_SOC);
|
||
|
pe2->ta_stop_battery_soc = TA_STOP_BATTERY_SOC;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "min_charger_voltage", &val) >= 0)
|
||
|
pe2->min_charger_voltage = val;
|
||
|
else {
|
||
|
pr_notice("use default V_CHARGER_MIN:%d\n", PE20_V_CHARGER_MIN);
|
||
|
pe2->min_charger_voltage = PE20_V_CHARGER_MIN;
|
||
|
}
|
||
|
|
||
|
/* cable measurement impedance */
|
||
|
if (of_property_read_u32(np, "cable_imp_threshold", &val) >= 0)
|
||
|
pe2->cable_imp_threshold = val;
|
||
|
else {
|
||
|
pr_notice("use default CABLE_IMP_THRESHOLD:%d\n",
|
||
|
PE2_CABLE_IMP_THRESHOLD);
|
||
|
pe2->cable_imp_threshold = PE2_CABLE_IMP_THRESHOLD;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "vbat_cable_imp_threshold", &val) >= 0)
|
||
|
pe2->vbat_cable_imp_threshold = val;
|
||
|
else {
|
||
|
pr_notice("use default VBAT_CABLE_IMP_THRESHOLD:%d\n",
|
||
|
PE2_VBAT_CABLE_IMP_THRESHOLD);
|
||
|
pe2->vbat_cable_imp_threshold = PE2_VBAT_CABLE_IMP_THRESHOLD;
|
||
|
}
|
||
|
|
||
|
/* single charger */
|
||
|
if (of_property_read_u32(np, "sc_input_current", &val) >= 0)
|
||
|
pe2->sc_input_current = val;
|
||
|
else {
|
||
|
pr_notice("use default SC_INPUT_CURRENT:%d\n",
|
||
|
SC_INPUT_CURRENT);
|
||
|
pe2->sc_input_current = SC_INPUT_CURRENT;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "sc_charger_current", &val) >= 0)
|
||
|
pe2->sc_charger_current = val;
|
||
|
else {
|
||
|
pr_notice("use default SC_CHARGING_CURRENT:%d\n",
|
||
|
SC_CHARGING_CURRENT);
|
||
|
pe2->sc_charger_current = SC_CHARGING_CURRENT;
|
||
|
}
|
||
|
|
||
|
/* dual charger in series */
|
||
|
if (of_property_read_u32(np, "dcs_input_current", &val) >= 0)
|
||
|
pe2->dcs_input_current = val;
|
||
|
else {
|
||
|
pr_notice("use default DCS_INPUT_CURRENT:%d\n",
|
||
|
DCS_INPUT_CURRENT);
|
||
|
pe2->dcs_input_current = DCS_INPUT_CURRENT;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "dcs_chg1_charger_current", &val) >= 0)
|
||
|
pe2->dcs_chg1_charger_current = val;
|
||
|
else {
|
||
|
pr_notice("use default DCS_CHG1_CHARGER_CURRENT:%d\n",
|
||
|
DCS_CHG1_CHARGER_CURRENT);
|
||
|
pe2->dcs_chg1_charger_current = DCS_CHG1_CHARGER_CURRENT;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "dcs_chg2_charger_current", &val) >= 0)
|
||
|
pe2->dcs_chg2_charger_current = val;
|
||
|
else {
|
||
|
pr_notice("use default DCS_CHG2_CHARGER_CURRENT:%d\n",
|
||
|
SC_CHARGING_CURRENT);
|
||
|
pe2->dcs_chg2_charger_current = DCS_CHG2_CHARGER_CURRENT;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "slave_mivr_diff", &val) >= 0)
|
||
|
pe2->pe2_slave_mivr_diff = val;
|
||
|
else {
|
||
|
pr_notice("use default slave_mivr_diff:%d\n",
|
||
|
PE2_SLAVE_MIVR_DIFF);
|
||
|
pe2->pe2_slave_mivr_diff = PE2_SLAVE_MIVR_DIFF;
|
||
|
}
|
||
|
|
||
|
if (of_property_read_u32(np, "dual_polling_ieoc", &val) >= 0)
|
||
|
pe2->dual_polling_ieoc = val;
|
||
|
else {
|
||
|
pr_notice("use default dual_polling_ieoc :%d\n", 750000);
|
||
|
pe2->dual_polling_ieoc = 750000;
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
int _pe2_get_prop(struct chg_alg_device *alg,
|
||
|
enum chg_alg_props s, int *value)
|
||
|
{
|
||
|
|
||
|
pr_notice("%s\n", __func__);
|
||
|
if (s == ALG_MAX_VBUS)
|
||
|
*value = 10000;
|
||
|
else
|
||
|
pr_notice("%s does not support prop:%d\n", __func__, s);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int _pe2_set_prop(struct chg_alg_device *alg,
|
||
|
enum chg_alg_props s, int value)
|
||
|
{
|
||
|
pr_notice("%s %d %d\n", __func__, s, value);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int _pe2_set_setting(struct chg_alg_device *alg_dev,
|
||
|
struct chg_limit_setting *setting)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2;
|
||
|
|
||
|
pe2_dbg("%s cv:%d icl:%d,%d cc:%d,%d\n",
|
||
|
__func__,
|
||
|
setting->cv,
|
||
|
setting->input_current_limit1,
|
||
|
setting->input_current_limit2,
|
||
|
setting->charging_current_limit1,
|
||
|
setting->charging_current_limit2);
|
||
|
pe2 = dev_get_drvdata(&alg_dev->dev);
|
||
|
|
||
|
mutex_lock(&pe2->access_lock);
|
||
|
__pm_stay_awake(pe2->suspend_lock);
|
||
|
pe2->cv = setting->cv;
|
||
|
pe2->input_current_limit1 = setting->input_current_limit1;
|
||
|
pe2->charging_current_limit1 = setting->charging_current_limit1;
|
||
|
pe2->input_current_limit2 = setting->input_current_limit2;
|
||
|
pe2->charging_current_limit2 = setting->charging_current_limit2;
|
||
|
__pm_relax(pe2->suspend_lock);
|
||
|
mutex_unlock(&pe2->access_lock);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct chg_alg_ops pe2_alg_ops = {
|
||
|
.init_algo = _pe2_init_algo,
|
||
|
.is_algo_ready = _pe2_is_algo_ready,
|
||
|
.start_algo = _pe2_start_algo,
|
||
|
.is_algo_running = _pe2_is_algo_running,
|
||
|
.stop_algo = _pe2_stop_algo,
|
||
|
.notifier_call = _pe2_notifier_call,
|
||
|
.get_prop = _pe2_get_prop,
|
||
|
.set_prop = _pe2_set_prop,
|
||
|
.set_current_limit = _pe2_set_setting,
|
||
|
};
|
||
|
|
||
|
static int mtk_pe2_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct mtk_pe20 *pe2 = NULL;
|
||
|
|
||
|
pr_notice("%s: starts\n", __func__);
|
||
|
|
||
|
pe2 = devm_kzalloc(&pdev->dev, sizeof(*pe2), GFP_KERNEL);
|
||
|
if (!pe2)
|
||
|
return -ENOMEM;
|
||
|
platform_set_drvdata(pdev, pe2);
|
||
|
pe2->pdev = pdev;
|
||
|
|
||
|
pe2->suspend_lock =
|
||
|
wakeup_source_register(NULL, "PE+20 suspend wakelock");
|
||
|
|
||
|
mutex_init(&pe2->access_lock);
|
||
|
mutex_init(&pe2->cable_out_lock);
|
||
|
mutex_init(&pe2->data_lock);
|
||
|
|
||
|
pe2->ta_vchr_org = 5000000;
|
||
|
pe2->idx = -1;
|
||
|
pe2->vbus = 5000000;
|
||
|
pe2->state = PE2_HW_UNINIT;
|
||
|
mtk_pe2_parse_dt(pe2, &pdev->dev);
|
||
|
pe2->bat_psy = devm_power_supply_get_by_phandle(&pdev->dev, "gauge");
|
||
|
|
||
|
if (IS_ERR_OR_NULL(pe2->bat_psy))
|
||
|
pe2_err("%s: devm power fail to get bat_psy\n", __func__);
|
||
|
|
||
|
pe2->profile[0].vbat = 3400000;
|
||
|
pe2->profile[1].vbat = 3500000;
|
||
|
pe2->profile[2].vbat = 3600000;
|
||
|
pe2->profile[3].vbat = 3700000;
|
||
|
pe2->profile[4].vbat = 3800000;
|
||
|
pe2->profile[5].vbat = 3900000;
|
||
|
pe2->profile[6].vbat = 4000000;
|
||
|
pe2->profile[7].vbat = 4100000;
|
||
|
pe2->profile[8].vbat = 4200000;
|
||
|
pe2->profile[9].vbat = 4300000;
|
||
|
|
||
|
/*
|
||
|
pe2->profile[0].vchr = 8000000;
|
||
|
pe2->profile[1].vchr = 8500000;
|
||
|
pe2->profile[2].vchr = 8500000;
|
||
|
pe2->profile[3].vchr = 9000000;
|
||
|
pe2->profile[4].vchr = 9000000;
|
||
|
pe2->profile[5].vchr = 9000000;
|
||
|
pe2->profile[6].vchr = 9500000;
|
||
|
pe2->profile[7].vchr = 9500000;
|
||
|
pe2->profile[8].vchr = 10000000;
|
||
|
pe2->profile[9].vchr = 10000000;
|
||
|
*/
|
||
|
pe2->profile[0].vchr = 8000000;
|
||
|
pe2->profile[1].vchr = 8000000;
|
||
|
pe2->profile[2].vchr = 8000000;
|
||
|
pe2->profile[3].vchr = 8500000;
|
||
|
pe2->profile[4].vchr = 8500000;
|
||
|
pe2->profile[5].vchr = 8500000;
|
||
|
pe2->profile[6].vchr = 9000000;
|
||
|
pe2->profile[7].vchr = 9000000;
|
||
|
pe2->profile[8].vchr = 9500000;
|
||
|
pe2->profile[9].vchr = 9500000;
|
||
|
|
||
|
pe2->alg = chg_alg_device_register("pe2", &pdev->dev,
|
||
|
pe2, &pe2_alg_ops, NULL);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int mtk_pe2_remove(struct platform_device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void mtk_pe2_shutdown(struct platform_device *dev)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id mtk_pe2_of_match[] = {
|
||
|
{.compatible = "mediatek,charger,pe2",},
|
||
|
{},
|
||
|
};
|
||
|
|
||
|
MODULE_DEVICE_TABLE(of, mtk_pe2_of_match);
|
||
|
|
||
|
struct platform_device pe2_device = {
|
||
|
.name = "pe2",
|
||
|
.id = -1,
|
||
|
};
|
||
|
|
||
|
static struct platform_driver pe2_driver = {
|
||
|
.probe = mtk_pe2_probe,
|
||
|
.remove = mtk_pe2_remove,
|
||
|
.shutdown = mtk_pe2_shutdown,
|
||
|
.driver = {
|
||
|
.name = "pe2",
|
||
|
.of_match_table = mtk_pe2_of_match,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static int __init mtk_pe2_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&pe2_driver);
|
||
|
}
|
||
|
late_initcall(mtk_pe2_init);
|
||
|
|
||
|
static void __exit mtk_pe2_exit(void)
|
||
|
{
|
||
|
platform_driver_unregister(&pe2_driver);
|
||
|
}
|
||
|
module_exit(mtk_pe2_exit);
|
||
|
|
||
|
|
||
|
MODULE_AUTHOR("wy.chuang <wy.chuang@mediatek.com>");
|
||
|
MODULE_DESCRIPTION("MTK Pump Express 2 algorithm Driver");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
|