563 lines
13 KiB
C
563 lines
13 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* Copyright (c) 2021 MediaTek Inc.
|
|
*/
|
|
|
|
#ifndef _DEA_MODIFY_
|
|
#include <linux/errno.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/reboot.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/alarmtimer.h>
|
|
#include <linux/suspend.h>
|
|
|
|
#include <mt-plat/v1/charger_type.h>
|
|
#include <mt-plat/v1/mtk_battery.h>
|
|
#include <mach/mtk_battery_property.h>
|
|
#else
|
|
#include <string.h>
|
|
#include "simulator_kernel.h"
|
|
#include "mtk_battery_property.h"
|
|
#endif
|
|
#include <mtk_gauge_time_service.h>
|
|
#include "mtk_battery_internal.h"
|
|
|
|
|
|
struct shutdown_condition {
|
|
bool is_overheat;
|
|
bool is_soc_zero_percent;
|
|
bool is_uisoc_one_percent;
|
|
bool is_under_shutdown_voltage;
|
|
bool is_dlpt_shutdown;
|
|
};
|
|
|
|
struct shutdown_controller {
|
|
struct alarm kthread_fgtimer;
|
|
bool timeout;
|
|
bool overheat;
|
|
wait_queue_head_t wait_que;
|
|
struct shutdown_condition shutdown_status;
|
|
struct timespec pre_time[SHUTDOWN_FACTOR_MAX];
|
|
int avgvbat;
|
|
bool lowbatteryshutdown;
|
|
int batdata[AVGVBAT_ARRAY_SIZE];
|
|
int batidx;
|
|
struct mutex lock;
|
|
struct notifier_block psy_nb;
|
|
};
|
|
|
|
static struct shutdown_controller sdc;
|
|
|
|
static int g_vbat_lt;
|
|
static int g_vbat_lt_lv1;
|
|
static int shutdown_cond_flag;
|
|
static int fix_coverity;
|
|
static bool b_power_misc_init;
|
|
|
|
static void wake_up_power_misc(struct shutdown_controller *sdd)
|
|
{
|
|
sdd->timeout = true;
|
|
wake_up(&sdd->wait_que);
|
|
}
|
|
|
|
static void wake_up_overheat(struct shutdown_controller *sdd)
|
|
{
|
|
sdd->overheat = true;
|
|
wake_up(&sdd->wait_que);
|
|
}
|
|
|
|
void set_shutdown_vbat_lt(int vbat_lt, int vbat_lt_lv1)
|
|
{
|
|
g_vbat_lt = vbat_lt;
|
|
g_vbat_lt_lv1 = vbat_lt_lv1;
|
|
}
|
|
|
|
int get_shutdown_cond(void)
|
|
{
|
|
int ret = 0;
|
|
int vbat = battery_get_bat_voltage();
|
|
|
|
if (sdc.shutdown_status.is_soc_zero_percent)
|
|
ret |= 1;
|
|
if (sdc.shutdown_status.is_uisoc_one_percent)
|
|
ret |= 1;
|
|
if (sdc.lowbatteryshutdown)
|
|
ret |= 1;
|
|
bm_err("%s ret:%d %d %d %d vbat:%d\n",
|
|
__func__,
|
|
ret, sdc.shutdown_status.is_soc_zero_percent,
|
|
sdc.shutdown_status.is_uisoc_one_percent,
|
|
sdc.lowbatteryshutdown, vbat);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void set_shutdown_cond_flag(int val)
|
|
{
|
|
shutdown_cond_flag = val;
|
|
}
|
|
|
|
int get_shutdown_cond_flag(void)
|
|
{
|
|
return shutdown_cond_flag;
|
|
}
|
|
|
|
int disable_shutdown_cond(int shutdown_cond)
|
|
{
|
|
int now_current;
|
|
int now_is_charging = 0;
|
|
int now_is_kpoc;
|
|
|
|
now_current = battery_get_bat_current();
|
|
now_is_kpoc = is_kernel_power_off_charging();
|
|
|
|
if (mt_get_charger_type() != CHARGER_UNKNOWN)
|
|
now_is_charging = 1;
|
|
|
|
bm_err("%s %d, is kpoc %d curr %d is_charging %d flag:%d lb:%d\n",
|
|
__func__,
|
|
shutdown_cond, now_is_kpoc, now_current, now_is_charging,
|
|
shutdown_cond_flag, battery_get_bat_voltage());
|
|
|
|
switch (shutdown_cond) {
|
|
#ifdef SHUTDOWN_CONDITION_LOW_BAT_VOLT
|
|
case LOW_BAT_VOLT:
|
|
sdc.shutdown_status.is_under_shutdown_voltage = false;
|
|
sdc.lowbatteryshutdown = false;
|
|
bm_err("disable LOW_BAT_VOLT avgvbat %d ,threshold:%d %d %d\n",
|
|
sdc.avgvbat,
|
|
BAT_VOLTAGE_HIGH_BOUND, g_vbat_lt, g_vbat_lt_lv1);
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int set_shutdown_cond(int shutdown_cond)
|
|
{
|
|
int now_current;
|
|
int now_is_charging = 0;
|
|
int now_is_kpoc;
|
|
int vbat;
|
|
struct shutdown_condition *sds;
|
|
int enable_lbat_shutdown;
|
|
|
|
#ifdef SHUTDOWN_CONDITION_LOW_BAT_VOLT
|
|
enable_lbat_shutdown = 1;
|
|
#else
|
|
enable_lbat_shutdown = 0;
|
|
#endif
|
|
|
|
now_current = battery_get_bat_current();
|
|
now_is_kpoc = is_kernel_power_off_charging();
|
|
vbat = battery_get_bat_voltage();
|
|
sds = &sdc.shutdown_status;
|
|
|
|
if (now_current >= 0)
|
|
now_is_charging = 1;
|
|
|
|
bm_err("%s %d %d kpoc %d curr %d is_charging %d flag:%d lb:%d\n",
|
|
__func__,
|
|
shutdown_cond, enable_lbat_shutdown,
|
|
now_is_kpoc, now_current, now_is_charging,
|
|
shutdown_cond_flag, vbat);
|
|
|
|
if (shutdown_cond_flag == 1)
|
|
return 0;
|
|
|
|
if (shutdown_cond_flag == 2 && shutdown_cond != LOW_BAT_VOLT)
|
|
return 0;
|
|
|
|
if (shutdown_cond_flag == 3 && shutdown_cond != DLPT_SHUTDOWN)
|
|
return 0;
|
|
|
|
switch (shutdown_cond) {
|
|
case OVERHEAT:
|
|
mutex_lock(&sdc.lock);
|
|
sdc.shutdown_status.is_overheat = true;
|
|
mutex_unlock(&sdc.lock);
|
|
bm_err("[%s]OVERHEAT shutdown!\n", __func__);
|
|
mutex_lock(&system_transition_mutex);
|
|
kernel_power_off();
|
|
mutex_unlock(&system_transition_mutex);
|
|
break;
|
|
case SOC_ZERO_PERCENT:
|
|
if (sdc.shutdown_status.is_soc_zero_percent != true) {
|
|
mutex_lock(&sdc.lock);
|
|
if (now_is_kpoc != 1) {
|
|
if (now_is_charging != 1) {
|
|
sds->is_soc_zero_percent =
|
|
true;
|
|
get_monotonic_boottime(
|
|
&sdc.pre_time[
|
|
SOC_ZERO_PERCENT]);
|
|
bm_err("[%s]soc_zero_percent shutdown\n",
|
|
__func__);
|
|
notify_fg_shutdown();
|
|
}
|
|
}
|
|
mutex_unlock(&sdc.lock);
|
|
}
|
|
break;
|
|
case UISOC_ONE_PERCENT:
|
|
if (sdc.shutdown_status.is_uisoc_one_percent != true) {
|
|
mutex_lock(&sdc.lock);
|
|
if (now_is_kpoc != 1) {
|
|
if (now_is_charging != 1) {
|
|
sds->is_uisoc_one_percent =
|
|
true;
|
|
get_monotonic_boottime(
|
|
&sdc.pre_time[UISOC_ONE_PERCENT]);
|
|
bm_err("[%s]uisoc 1 percent shutdown\n",
|
|
__func__);
|
|
notify_fg_shutdown();
|
|
}
|
|
}
|
|
mutex_unlock(&sdc.lock);
|
|
}
|
|
break;
|
|
#ifdef SHUTDOWN_CONDITION_LOW_BAT_VOLT
|
|
case LOW_BAT_VOLT:
|
|
if (sdc.shutdown_status.is_under_shutdown_voltage != true) {
|
|
int i;
|
|
|
|
mutex_lock(&sdc.lock);
|
|
if (now_is_kpoc != 1) {
|
|
sds->is_under_shutdown_voltage = true;
|
|
for (i = 0; i < AVGVBAT_ARRAY_SIZE; i++)
|
|
sdc.batdata[i] =
|
|
VBAT2_DET_VOLTAGE1 / 10;
|
|
sdc.batidx = 0;
|
|
}
|
|
bm_err("LOW_BAT_VOLT:vbat %d %d",
|
|
vbat, VBAT2_DET_VOLTAGE1 / 10);
|
|
mutex_unlock(&sdc.lock);
|
|
}
|
|
break;
|
|
#endif
|
|
case DLPT_SHUTDOWN:
|
|
if (sdc.shutdown_status.is_dlpt_shutdown != true) {
|
|
sdc.shutdown_status.is_dlpt_shutdown = true;
|
|
get_monotonic_boottime(&sdc.pre_time[DLPT_SHUTDOWN]);
|
|
notify_fg_dlpt_sd();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (b_power_misc_init == true)
|
|
wake_up_power_misc(&sdc);
|
|
else
|
|
bm_err("[%s]init:%d\n", __func__, b_power_misc_init);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int next_waketime(int polling)
|
|
{
|
|
if (polling <= 0)
|
|
return 0;
|
|
else
|
|
return 10;
|
|
}
|
|
|
|
static int shutdown_event_handler(struct shutdown_controller *sdd)
|
|
{
|
|
struct timespec now, duraction;
|
|
int polling = 0;
|
|
static int ui_zero_time_flag;
|
|
static int down_to_low_bat;
|
|
int now_current = 0;
|
|
int current_ui_soc = battery_get_uisoc();
|
|
int current_soc = battery_get_soc();
|
|
int vbat = battery_get_bat_voltage();
|
|
int tmp = 25;
|
|
|
|
now.tv_sec = 0;
|
|
now.tv_nsec = 0;
|
|
duraction.tv_sec = 0;
|
|
duraction.tv_nsec = 0;
|
|
|
|
get_monotonic_boottime(&now);
|
|
|
|
bm_err("%s:soc_zero:%d,ui 1percent:%d,dlpt_shut:%d,under_shutdown_volt:%d\n",
|
|
__func__,
|
|
sdd->shutdown_status.is_soc_zero_percent,
|
|
sdd->shutdown_status.is_uisoc_one_percent,
|
|
sdd->shutdown_status.is_dlpt_shutdown,
|
|
sdd->shutdown_status.is_under_shutdown_voltage);
|
|
|
|
|
|
if (sdd->shutdown_status.is_soc_zero_percent) {
|
|
if (current_ui_soc == 0) {
|
|
duraction = timespec_sub(
|
|
now, sdd->pre_time[SOC_ZERO_PERCENT]);
|
|
polling++;
|
|
if (duraction.tv_sec >= SHUTDOWN_TIME) {
|
|
bm_err("soc zero shutdown\n");
|
|
mutex_lock(&system_transition_mutex);
|
|
kernel_power_off();
|
|
mutex_unlock(&system_transition_mutex);
|
|
return next_waketime(polling);
|
|
|
|
}
|
|
} else if (current_soc > 0) {
|
|
sdd->shutdown_status.is_soc_zero_percent = false;
|
|
} else {
|
|
/* ui_soc is not zero, check it after 10s */
|
|
polling++;
|
|
}
|
|
}
|
|
|
|
if (sdd->shutdown_status.is_uisoc_one_percent) {
|
|
now_current = battery_get_bat_current();
|
|
|
|
if (current_ui_soc == 0) {
|
|
duraction =
|
|
timespec_sub(
|
|
now, sdd->pre_time[UISOC_ONE_PERCENT]);
|
|
polling++;
|
|
if (duraction.tv_sec >= SHUTDOWN_TIME) {
|
|
bm_err("uisoc one percent shutdown\n");
|
|
mutex_lock(&system_transition_mutex);
|
|
kernel_power_off();
|
|
mutex_unlock(&system_transition_mutex);
|
|
return next_waketime(polling);
|
|
}
|
|
} else if (now_current > 0 && current_soc > 0) {
|
|
polling = 0;
|
|
sdd->shutdown_status.is_uisoc_one_percent = 0;
|
|
bm_err("disable uisoc_one_percent shutdown cur:%d soc:%d\n",
|
|
now_current, current_soc);
|
|
return next_waketime(polling);
|
|
} else {
|
|
/* ui_soc is not zero, check it after 10s */
|
|
polling++;
|
|
}
|
|
}
|
|
|
|
if (sdd->shutdown_status.is_dlpt_shutdown) {
|
|
duraction = timespec_sub(now, sdd->pre_time[DLPT_SHUTDOWN]);
|
|
polling++;
|
|
if (duraction.tv_sec >= SHUTDOWN_TIME) {
|
|
bm_err("dlpt shutdown\n");
|
|
mutex_lock(&system_transition_mutex);
|
|
kernel_power_off();
|
|
mutex_unlock(&system_transition_mutex);
|
|
return next_waketime(polling);
|
|
}
|
|
}
|
|
|
|
if (sdd->shutdown_status.is_under_shutdown_voltage) {
|
|
|
|
int vbatcnt = 0, i;
|
|
|
|
sdd->batdata[sdd->batidx] = vbat;
|
|
|
|
for (i = 0; i < AVGVBAT_ARRAY_SIZE; i++)
|
|
vbatcnt += sdd->batdata[i];
|
|
sdd->avgvbat = vbatcnt / AVGVBAT_ARRAY_SIZE;
|
|
|
|
tmp = battery_get_bat_temperature();
|
|
|
|
bm_err("lbatcheck vbat:%d avgvbat:%d %d,%d tmp:%d,bound:%d,th:%d %d,en:%d\n",
|
|
vbat,
|
|
sdd->avgvbat,
|
|
g_vbat_lt,
|
|
g_vbat_lt_lv1,
|
|
tmp,
|
|
BAT_VOLTAGE_LOW_BOUND,
|
|
LOW_TEMP_THRESHOLD,
|
|
LOW_TMP_BAT_VOLTAGE_LOW_BOUND,
|
|
LOW_TEMP_DISABLE_LOW_BAT_SHUTDOWN);
|
|
|
|
if (sdd->avgvbat < BAT_VOLTAGE_LOW_BOUND) {
|
|
/* avg vbat less than 3.4v */
|
|
sdd->lowbatteryshutdown = true;
|
|
polling++;
|
|
|
|
if (down_to_low_bat == 0) {
|
|
if (IS_ENABLED(
|
|
LOW_TEMP_DISABLE_LOW_BAT_SHUTDOWN)) {
|
|
if (tmp >= LOW_TEMP_THRESHOLD) {
|
|
down_to_low_bat = 1;
|
|
bm_err("normal tmp, battery voltage is low shutdown\n");
|
|
notify_fg_shutdown();
|
|
} else if (sdd->avgvbat <=
|
|
LOW_TMP_BAT_VOLTAGE_LOW_BOUND) {
|
|
down_to_low_bat = 1;
|
|
bm_err("cold tmp, battery voltage is low shutdown\n");
|
|
notify_fg_shutdown();
|
|
} else
|
|
bm_err("low temp disable low battery sd\n");
|
|
} else {
|
|
down_to_low_bat = 1;
|
|
bm_err("[%s]avg vbat is low to shutdown\n",
|
|
__func__);
|
|
notify_fg_shutdown();
|
|
}
|
|
}
|
|
|
|
if ((current_ui_soc == 0) && (ui_zero_time_flag == 0)) {
|
|
get_monotonic_boottime(
|
|
&sdc.pre_time[LOW_BAT_VOLT]);
|
|
ui_zero_time_flag = 1;
|
|
}
|
|
|
|
if (current_ui_soc == 0) {
|
|
duraction = timespec_sub(
|
|
now, sdd->pre_time[LOW_BAT_VOLT]);
|
|
if (duraction.tv_sec >= SHUTDOWN_TIME) {
|
|
bm_err("low bat shutdown, over %d second\n",
|
|
SHUTDOWN_TIME);
|
|
mutex_lock(&system_transition_mutex);
|
|
kernel_power_off();
|
|
mutex_unlock(&system_transition_mutex);
|
|
return next_waketime(polling);
|
|
}
|
|
}
|
|
} else {
|
|
/* greater than 3.4v, clear status */
|
|
down_to_low_bat = 0;
|
|
ui_zero_time_flag = 0;
|
|
sdd->pre_time[LOW_BAT_VOLT].tv_sec = 0;
|
|
sdd->lowbatteryshutdown = false;
|
|
polling++;
|
|
}
|
|
|
|
polling++;
|
|
bm_err("[%s][UT] V %d ui_soc %d dur %d [%d:%d:%d:%d] batdata[%d] %d\n",
|
|
__func__,
|
|
sdd->avgvbat, current_ui_soc,
|
|
(int)duraction.tv_sec,
|
|
down_to_low_bat, ui_zero_time_flag,
|
|
(int)sdd->pre_time[LOW_BAT_VOLT].tv_sec,
|
|
sdd->lowbatteryshutdown,
|
|
sdd->batidx, sdd->batdata[sdd->batidx]);
|
|
|
|
sdd->batidx++;
|
|
if (sdd->batidx >= AVGVBAT_ARRAY_SIZE)
|
|
sdd->batidx = 0;
|
|
}
|
|
|
|
bm_err(
|
|
"%s %d avgvbat:%d sec:%d lowst:%d\n",
|
|
__func__,
|
|
polling, sdd->avgvbat,
|
|
(int)duraction.tv_sec, sdd->lowbatteryshutdown);
|
|
|
|
return next_waketime(polling);
|
|
|
|
}
|
|
|
|
static enum alarmtimer_restart power_misc_kthread_fgtimer_func(
|
|
struct alarm *alarm, ktime_t now)
|
|
{
|
|
struct shutdown_controller *info =
|
|
container_of(
|
|
alarm, struct shutdown_controller, kthread_fgtimer);
|
|
|
|
wake_up_power_misc(info);
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
|
|
void power_misc_handler(void *arg)
|
|
{
|
|
struct shutdown_controller *sdd = arg;
|
|
struct timespec time, time_now, end_time;
|
|
ktime_t ktime;
|
|
int secs = 0;
|
|
|
|
secs = shutdown_event_handler(sdd);
|
|
if (secs != 0 && is_fg_disabled() == false) {
|
|
get_monotonic_boottime(&time_now);
|
|
time.tv_sec = secs;
|
|
time.tv_nsec = 0;
|
|
end_time = timespec_add(time_now, time);
|
|
ktime = ktime_set(end_time.tv_sec, end_time.tv_nsec);
|
|
|
|
alarm_start(&sdd->kthread_fgtimer, ktime);
|
|
bm_err("%s:set new alarm timer:%ds\n",
|
|
__func__, secs);
|
|
}
|
|
|
|
}
|
|
|
|
static int power_misc_routine_thread(void *arg)
|
|
{
|
|
struct shutdown_controller *sdd = arg;
|
|
int ret = 0;
|
|
|
|
while (1) {
|
|
ret = wait_event_interruptible(sdd->wait_que, (sdd->timeout == true)
|
|
|| (sdd->overheat == true));
|
|
if (sdd->timeout == true) {
|
|
sdd->timeout = false;
|
|
power_misc_handler(arg);
|
|
}
|
|
if (sdd->overheat == true) {
|
|
sdd->overheat = false;
|
|
bm_err("%s battery overheat~ power off\n",
|
|
__func__);
|
|
mutex_lock(&system_transition_mutex);
|
|
kernel_power_off();
|
|
mutex_unlock(&system_transition_mutex);
|
|
fix_coverity = 1;
|
|
return 1;
|
|
}
|
|
if (fix_coverity == 1)
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mtk_power_misc_psy_event(
|
|
struct notifier_block *nb, unsigned long event, void *v)
|
|
{
|
|
struct power_supply *psy = v;
|
|
union power_supply_propval val;
|
|
int ret;
|
|
int tmp = 0;
|
|
|
|
if (strcmp(psy->desc->name, "battery") == 0) {
|
|
ret = psy->desc->get_property(
|
|
psy, POWER_SUPPLY_PROP_TEMP, &val);
|
|
if (!ret) {
|
|
tmp = val.intval / 10;
|
|
if (tmp >= BATTERY_SHUTDOWN_TEMPERATURE) {
|
|
bm_err(
|
|
"battery temperature >= %d,shutdown",
|
|
tmp);
|
|
|
|
wake_up_overheat(&sdc);
|
|
}
|
|
}
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
void mtk_power_misc_init(struct platform_device *pdev)
|
|
{
|
|
mutex_init(&sdc.lock);
|
|
alarm_init(&sdc.kthread_fgtimer, ALARM_BOOTTIME,
|
|
power_misc_kthread_fgtimer_func);
|
|
init_waitqueue_head(&sdc.wait_que);
|
|
|
|
kthread_run(power_misc_routine_thread, &sdc, "power_misc_thread");
|
|
|
|
sdc.psy_nb.notifier_call = mtk_power_misc_psy_event;
|
|
power_supply_reg_notifier(&sdc.psy_nb);
|
|
b_power_misc_init = true;
|
|
bm_err("%s INIT done, init:%d\n", __func__, b_power_misc_init);
|
|
}
|
|
|