// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ /* * * Filename: * --------- * mtk_pulse_charger.c * * Project: * -------- * Android_Software * * Description: * ------------ * This Module defines functions of Battery charging * * Author: * ------- * Wy Chuang * */ #include /* For init/exit macros */ #include /* For MODULE_ marcros */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mtk_charger.h" #define MAX_TOPOFF_CHARGING_TIME (3 * 60 * 60) /* 3 hours */ #define RECHARGE_OFFSET 150000 /* uV */ #define TOPOFF_VOLTAGE 4200000 /* uV */ #define CHG_FULL_CURRENT 150000 /* uA */ struct pcharger_data { int state; bool disable_charging; unsigned int total_charging_time; unsigned int cc_charging_time; unsigned int topoff_charging_time; unsigned int full_charging_time; struct timespec topoff_begin_time; struct timespec charging_begin_time; int recharge_offset; /* uv */ int topoff_voltage; /* uv */ int chg_full_current; /* uA */ }; enum pcharger_state_enum { CHR_CC, CHR_TOPOFF, CHR_BATFULL, CHR_ERROR }; static int _uA_to_mA(int uA) { if (uA == -1) return -1; else return uA / 1000; } static void pchr_select_cv(struct mtk_charger *info) { u32 constant_voltage; if (info->enable_sw_jeita) if (info->sw_jeita.cv != 0) { info->setting.cv = info->sw_jeita.cv; return; } constant_voltage = info->data.battery_cv; info->setting.cv = constant_voltage; charger_dev_set_constant_voltage(info->chg1_dev, info->setting.cv); } static bool pchr_select_charging_current_limit(struct mtk_charger *info, struct chg_limit_setting *setting) { struct charger_data *pdata; bool is_basic = false; u32 ichg1_min = 0, aicr1_min = 0; int ret; pchr_select_cv(info); pdata = &info->chg_data[CHG1_SETTING]; if (info->usb_unlimited) { pdata->input_current_limit = info->data.ac_charger_input_current; pdata->charging_current_limit = info->data.ac_charger_current; is_basic = true; goto done; } if ((info->bootmode == 1) || (info->bootmode == 5)) { pdata->input_current_limit = 200000; /* 200mA */ is_basic = true; goto done; } if (info->atm_enabled == true && (info->chr_type == POWER_SUPPLY_TYPE_USB || info->chr_type == POWER_SUPPLY_TYPE_USB_CDP) ) { pdata->input_current_limit = 100000; /* 100mA */ is_basic = true; goto done; } if (info->chr_type == POWER_SUPPLY_TYPE_USB) { pdata->input_current_limit = info->data.usb_charger_current; /* it can be larger */ pdata->charging_current_limit = info->data.usb_charger_current; is_basic = true; } else if (info->chr_type == POWER_SUPPLY_TYPE_USB_DCP) { pdata->input_current_limit = info->data.ac_charger_input_current; pdata->charging_current_limit = info->data.ac_charger_current; } else if (info->chr_type == POWER_SUPPLY_TYPE_USB_CDP) { pdata->input_current_limit = info->data.charging_host_charger_current; pdata->charging_current_limit = info->data.charging_host_charger_current; is_basic = true; } if (info->enable_sw_jeita) { if (IS_ENABLED(CONFIG_USBIF_COMPLIANCE) && info->chr_type == POWER_SUPPLY_TYPE_USB) chr_debug("USBIF & STAND_HOST skip current check\n"); else { if (info->sw_jeita.sm == TEMP_T0_TO_T1) { pdata->input_current_limit = 500000; pdata->charging_current_limit = 350000; } } } if (pdata->thermal_charging_current_limit != -1) { if (pdata->thermal_charging_current_limit < pdata->charging_current_limit) { pdata->charging_current_limit = pdata->thermal_charging_current_limit; info->setting.charging_current_limit1 = pdata->thermal_charging_current_limit; } } else info->setting.charging_current_limit1 = -1; if (pdata->thermal_input_current_limit != -1) { if (pdata->thermal_input_current_limit < pdata->input_current_limit) { pdata->input_current_limit = pdata->thermal_input_current_limit; info->setting.input_current_limit1 = pdata->thermal_input_current_limit; } } else info->setting.input_current_limit1 = -1; if (info->pd_type == MTK_PD_CONNECT_PE_READY_SNK || info->pd_type == MTK_PD_CONNECT_PE_READY_SNK_PD30 || info->pd_type == MTK_PD_CONNECT_PE_READY_SNK_APDO) is_basic = false; done: ret = charger_dev_get_min_charging_current(info->chg1_dev, &ichg1_min); if (ret != -ENOTSUPP && pdata->charging_current_limit < ichg1_min) pdata->charging_current_limit = 0; ret = charger_dev_get_min_input_current(info->chg1_dev, &aicr1_min); if (ret != -ENOTSUPP && pdata->input_current_limit < aicr1_min) pdata->input_current_limit = 0; chr_err("thermal:%d %d setting:%d %d type:%d:%d usb_unlimited:%d usbif:%d usbsm:%d aicl:%d atm:%d bm:%d b:%d\n", _uA_to_mA(pdata->thermal_input_current_limit), _uA_to_mA(pdata->thermal_charging_current_limit), _uA_to_mA(pdata->input_current_limit), _uA_to_mA(pdata->charging_current_limit), info->chr_type, info->pd_type, info->usb_unlimited, IS_ENABLED(CONFIG_USBIF_COMPLIANCE), info->usb_state, pdata->input_current_limit_by_aicl, info->atm_enabled, info->bootmode, is_basic); return is_basic; } static void linear_chg_turn_on_charging(struct mtk_charger *info) { struct pcharger_data *algo_data = info->algo.algo_data; bool charging_enable = true; struct charger_data *pdata; pdata = &info->chg_data[CHG1_SETTING]; if (algo_data->state == CHR_ERROR) { charging_enable = false; chr_err("[charger]Charger Error, turn OFF charging !\n"); #ifdef SUPPORT_BOOTMODE } else if ((get_boot_mode() == META_BOOT) || ((get_boot_mode() == ADVMETA_BOOT))) { charging_enable = false; chr_err("[charger]In meta or advanced meta mode, disable charging\n"); #endif } else { pchr_select_charging_current_limit(info, &info->setting); if (info->chg_data[CHG1_SETTING].charging_current_limit == 0) { charging_enable = false; chr_err("[charger]charging current is set 0mA, turn off charging\n"); } charger_dev_set_input_current(info->chg1_dev, pdata->input_current_limit); charger_dev_set_charging_current(info->chg1_dev, pdata->charging_current_limit); charger_dev_set_constant_voltage(info->chg1_dev, info->setting.cv); } charger_dev_enable(info->chg1_dev, charging_enable); } static int mtk_linear_chr_cc(struct mtk_charger *info) { struct timespec time_now, charging_time; u32 vbat; struct pcharger_data *algo_data; algo_data = info->algo.algo_data; pr_notice("%s time:%d %d %d %d\n", __func__, algo_data->total_charging_time, algo_data->cc_charging_time, algo_data->topoff_charging_time, algo_data->full_charging_time); get_monotonic_boottime(&time_now); charging_time = timespec_sub(time_now, algo_data->charging_begin_time); algo_data->cc_charging_time = charging_time.tv_sec; algo_data->topoff_charging_time = 0; algo_data->total_charging_time = charging_time.tv_sec; /* discharge for 1 second and charge for 9 seconds */ charger_dev_enable(info->chg1_dev, false); msleep(1000); vbat = get_battery_voltage(info) * 1000; /* uV */ if (vbat > algo_data->topoff_voltage) { algo_data->state = CHR_TOPOFF; get_monotonic_boottime(&algo_data->topoff_begin_time); pr_notice("%s: enter TOPOFF mode on vbat = %d uV\n", __func__, vbat); } linear_chg_turn_on_charging(info); return 0; } static bool charging_full_check(struct mtk_charger *info) { struct pcharger_data *algo_data = info->algo.algo_data; static u32 full_check_count; bool chg_full_status = false; int chg_current = get_battery_current(info) * 1000; /* uA */ if (chg_current > algo_data->chg_full_current) full_check_count = 0; else { full_check_count++; if (full_check_count >= 6) { full_check_count = 0; chg_full_status = true; } } return chg_full_status; } static int mtk_linear_chr_topoff(struct mtk_charger *info) { struct pcharger_data *algo_data = info->algo.algo_data; struct timespec time_now, charging_time, topoff_time; pr_notice("%s time:%d %d %d %d\n", __func__, algo_data->total_charging_time, algo_data->cc_charging_time, algo_data->topoff_charging_time, algo_data->full_charging_time); get_monotonic_boottime(&time_now); charging_time = timespec_sub(time_now, algo_data->charging_begin_time); topoff_time = timespec_sub(time_now, algo_data->topoff_begin_time); algo_data->cc_charging_time = 0; algo_data->topoff_charging_time = topoff_time.tv_sec; algo_data->total_charging_time = charging_time.tv_sec; linear_chg_turn_on_charging(info); if (algo_data->topoff_charging_time >= MAX_TOPOFF_CHARGING_TIME || charging_full_check(info) == true) { algo_data->state = CHR_BATFULL; /* Disable charging */ charger_dev_enable(info->chg1_dev, false); pr_notice("%s: disable charging\n", __func__); } return 0; } static int mtk_linear_chr_full(struct mtk_charger *info) { struct pcharger_data *algo_data = info->algo.algo_data; u32 vbat; bool is_recharging = false; algo_data->total_charging_time = 0; algo_data->cc_charging_time = 0; algo_data->topoff_charging_time = 0; pr_notice("%s time:%d %d %d %d\n", __func__, algo_data->total_charging_time, algo_data->cc_charging_time, algo_data->topoff_charging_time, algo_data->full_charging_time); /* * If CV is set to lower value by JEITA, * Reset CV to normal value if temperture is in normal zone */ pchr_select_cv(info); info->polling_interval = CHARGING_FULL_INTERVAL; vbat = get_battery_voltage(info) * 1000; /* uV */ if (info->enable_sw_jeita && info->sw_jeita.cv != 0) { if (vbat < (info->sw_jeita.cv - algo_data->recharge_offset)) is_recharging = true; } else { if (vbat < (info->data.battery_cv - algo_data->recharge_offset)) is_recharging = true; } if (is_recharging) { algo_data->state = CHR_CC; get_monotonic_boottime(&algo_data->charging_begin_time); pr_notice("battery recharging on vbat = %d uV\n", vbat); info->polling_interval = CHARGING_INTERVAL; } return 0; } static void pchr_disable_all_charging(struct mtk_charger *info) { charger_dev_enable(info->chg1_dev, false); } static int mtk_linear_chr_err(struct mtk_charger *info) { struct pcharger_data *algo_data = info->algo.algo_data; pr_notice("%s time:%d %d %d %d\n", __func__, algo_data->total_charging_time, algo_data->cc_charging_time, algo_data->topoff_charging_time, algo_data->full_charging_time); if (info->enable_sw_jeita) { if ((info->sw_jeita.sm == TEMP_BELOW_T0) || (info->sw_jeita.sm == TEMP_ABOVE_T4)) info->sw_jeita.error_recovery_flag = false; if ((info->sw_jeita.error_recovery_flag == false) && (info->sw_jeita.sm != TEMP_BELOW_T0) && (info->sw_jeita.sm != TEMP_ABOVE_T4)) { info->sw_jeita.error_recovery_flag = true; algo_data->state = CHR_CC; get_monotonic_boottime(&algo_data->charging_begin_time); } } algo_data->total_charging_time = 0; algo_data->cc_charging_time = 0; algo_data->topoff_charging_time = 0; pchr_disable_all_charging(info); return 0; } static int pchr_do_algorithm(struct mtk_charger *info) { struct charger_data *pdata; bool is_basic = true; int ret; struct pcharger_data *algo_data = info->algo.algo_data; charger_dev_kick_wdt(info->chg1_dev); pdata = &info->chg_data[CHG1_SETTING]; chr_err("%s: %d\n", __func__, algo_data->state); switch (algo_data->state) { case CHR_CC: ret = mtk_linear_chr_cc(info); break; case CHR_TOPOFF: ret = mtk_linear_chr_topoff(info); break; case CHR_BATFULL: ret = mtk_linear_chr_full(info); break; case CHR_ERROR: ret = mtk_linear_chr_err(info); break; } if (is_basic == true) { charger_dev_set_input_current(info->chg1_dev, pdata->input_current_limit); charger_dev_set_charging_current(info->chg1_dev, pdata->charging_current_limit); charger_dev_set_constant_voltage(info->chg1_dev, info->setting.cv); } charger_dev_dump_registers(info->chg1_dev); return 0; } static void mtk_pulse_charger_parse_dt(struct mtk_charger *info, struct device *dev) { struct device_node *np = dev->of_node; u32 val = 0; struct pcharger_data *algo_data; algo_data = info->algo.algo_data; if (of_property_read_u32(np, "recharge_offset", &val) >= 0) { algo_data->recharge_offset = val; } else { chr_err("use default RECHARGE_OFFSET: %d\n", RECHARGE_OFFSET); algo_data->recharge_offset = RECHARGE_OFFSET; } if (of_property_read_u32(np, "topoff_voltage", &val) >= 0) { algo_data->topoff_voltage = val; } else { chr_err("use default TOPOFF_VOLTAGE: %d\n", TOPOFF_VOLTAGE); algo_data->topoff_voltage = TOPOFF_VOLTAGE; } if (of_property_read_u32(np, "chg_full_current", &val) >= 0) { algo_data->chg_full_current = val; } else { chr_err("use default CHG_FULL_CURRENT: %d\n", CHG_FULL_CURRENT); algo_data->chg_full_current = CHG_FULL_CURRENT; } } static int mtk_linear_charging_do_charging(struct mtk_charger *info, bool en) { struct pcharger_data *algo_data = info->algo.algo_data; pr_info("%s en:%d %s\n", __func__, en, info->algorithm_name); if (en) { algo_data->disable_charging = false; algo_data->state = CHR_CC; get_monotonic_boottime(&algo_data->charging_begin_time); } else { algo_data->disable_charging = true; algo_data->state = CHR_ERROR; pchr_disable_all_charging(info); } return 0; } int mtk_pulse_charger_init(struct mtk_charger *info) { static struct pcharger_data pdata; pr_notice("%s\n", __func__); info->algo.do_algorithm = pchr_do_algorithm; info->algo.enable_charging = mtk_linear_charging_do_charging; //info->algo.do_event = charger_dev_event; info->algo.algo_data = &pdata; mtk_pulse_charger_parse_dt(info, &info->pdev->dev); pr_notice("%s end\n", __func__); return 0; }