| // 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 bool algo_waiver_test; |
| module_param(algo_waiver_test, bool, 0644); |
| |
| 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 */ |
| |
| pe2_hal_vbat_mon_en(alg, CHG1, false); |
| pe2->old_cv = 0; |
| pe2->stop_6pin_re_en = 0; |
| |
| /* 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); |
| |
| pe2_hal_vbat_mon_en(alg, CHG1, false); |
| pe2->old_cv = 0; |
| pe2->stop_6pin_re_en = 0; |
| |
| 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; |
| ktime_t ptime[2], ktime_diff; |
| struct timespec64 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); |
| |
| ptime[0] = ktime_get_boottime(); |
| |
| /* 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); |
| |
| ptime[1] = ktime_get_boottime(); |
| ktime_diff = ktime_sub(ptime[1], ptime[0]); |
| diff = ktime_to_timespec64(ktime_diff); |
| |
| 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 != -EOPNOTSUPP && 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 && |
| pe2->ref_vbat > pe2->vbat_threshold) || |
| 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 ref_vbat:%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, |
| pe2->ref_vbat); |
| 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, log_level; |
| |
| 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; |
| |
| pe2_hal_vbat_mon_en(alg, CHG1, false); |
| pe2->old_cv = 0; |
| pe2->stop_6pin_re_en = 0; |
| |
| 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; |
| |
| log_level = pe2_hal_get_log_level(alg); |
| pr_notice("%s: log_level=%d", __func__, log_level); |
| if (log_level > 0) |
| pe2_dbg_level = log_level; |
| |
| 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); |
| |
| if (algo_waiver_test) { |
| ret_value = ALG_WAIVER; |
| goto skip; |
| } |
| 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 (pe2->charging_current_limit1 != -1 || |
| pe2->charging_current_limit2 != -1) { |
| ret_value = ALG_NOT_READY; |
| } else if ((uisoc < pe2->ta_start_battery_soc && |
| pe2->ref_vbat > pe2->vbat_threshold) || |
| uisoc >= pe2->ta_stop_battery_soc) { |
| ret_value = ALG_WAIVER; |
| } 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; |
| } |
| skip: |
| __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 != -EOPNOTSUPP && |
| 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 != -EOPNOTSUPP && |
| 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); |
| |
| if (pe2->old_cv == 0 || (pe2->old_cv != pe2->cv) || pe2->pe2_6pin_en == 0) { |
| pe2_hal_vbat_mon_en(alg, CHG1, false); |
| pe2_hal_set_cv(alg, CHG1, pe2->cv); |
| if (pe2->pe2_6pin_en && pe2->stop_6pin_re_en != 1) |
| pe2_hal_vbat_mon_en(alg, CHG1, true); |
| |
| pe2_dbg("%s old_cv=%d, new_cv=%d, pe2_6pin_en=%d\n", __func__, |
| pe2->old_cv, pe2->cv, pe2->pe2_6pin_en); |
| |
| pe2->old_cv = pe2->cv; |
| } else { |
| if (pe2->pe2_6pin_en && pe2->stop_6pin_re_en != 1) { |
| pe2->stop_6pin_re_en = 1; |
| pe2_hal_vbat_mon_en(alg, CHG1, true); |
| } |
| } |
| |
| pe2_dbg("%s m:%d s:%d cv:%d chg1:%d,%d min:%d:%d, 6pin_en:%d, 6pin_re_en=%d\n", __func__, |
| alg->config, |
| pe2->state, |
| pe2->cv, |
| pe2->input_current1, |
| pe2->charging_current1, |
| ichg1_min, |
| aicr1_min, |
| pe2->pe2_6pin_en, |
| pe2->stop_6pin_re_en); |
| |
| 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 != -EOPNOTSUPP && |
| 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 != -EOPNOTSUPP && |
| 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 != -EOPNOTSUPP && |
| 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_HW_READY; |
| 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 == -EOPNOTSUPP) |
| 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: |
| pe2->stop_6pin_re_en = 0; |
| ret_value = pe2_plugout_reset(alg); |
| break; |
| case EVT_FULL: |
| pe2->stop_6pin_re_en = 1; |
| ret_value = pe2_full_event(alg); |
| break; |
| case EVT_BATPRO_DONE: |
| pe2->pe2_6pin_en = 0; |
| ret_value = 0; |
| 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; |
| } |
| |
| if (of_property_read_u32(np, "vbat_threshold", &val) >= 0) |
| pe2->vbat_threshold = val; |
| else { |
| pr_notice("turn off vbat_threshold checking:%d\n", |
| DISABLE_VBAT_THRESHOLD); |
| pe2->vbat_threshold = DISABLE_VBAT_THRESHOLD; |
| } |
| |
| } |
| |
| 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) |
| { |
| struct mtk_pe20 *pe2; |
| |
| pr_notice("%s %d %d\n", __func__, s, value); |
| |
| pe2 = dev_get_drvdata(&alg->dev); |
| |
| switch (s) { |
| case ALG_LOG_LEVEL: |
| pe2_dbg_level = value; |
| break; |
| case ALG_REF_VBAT: |
| pe2->ref_vbat = value; |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| int _pe2_set_setting(struct chg_alg_device *alg_dev, |
| struct chg_limit_setting *setting) |
| { |
| struct mtk_pe20 *pe2; |
| |
| pe2 = dev_get_drvdata(&alg_dev->dev); |
| |
| pe2_dbg("%s cv:%d icl:%d,%d cc:%d,%d, 6pin_en:%d\n", |
| __func__, |
| setting->cv, |
| setting->input_current_limit1, |
| setting->input_current_limit2, |
| setting->charging_current_limit1, |
| setting->charging_current_limit2, |
| setting->vbat_mon_en); |
| |
| mutex_lock(&pe2->access_lock); |
| __pm_stay_awake(pe2->suspend_lock); |
| pe2->cv = setting->cv; |
| pe2->pe2_6pin_en = setting->vbat_mon_en; |
| 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); |
| } |
| module_init(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"); |