kernel-brax3-ubuntu-touch/drivers/misc/mediatek/power_throttling/mtk_dynamic_loading_throttling.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

688 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/device.h>
#include <linux/iio/consumer.h>
#include <linux/kthread.h>
#include <linux/linear_range.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/mfd/mt6359p/registers.h>
#include <linux/mfd/mt6363/registers.h>
#include <linux/mfd/mt6377/registers.h>
#include <linux/mfd/mt6397/core.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_wakeup.h>
#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/sort.h>
#include <linux/suspend.h>
#include "mtk_low_battery_throttling.h"
#include "mtk_dynamic_loading_throttling.h"
#define POWER_UVLO_VOLT_LEVEL 2600
#define IMAX_MAX_VALUE 5500
#define DLPT_NOTIFY_FAST_UISOC 30
#define DLPT_VOLT_MIN 3100
static int isThreeLevel;
struct reg_t {
unsigned int addr;
unsigned int mask;
unsigned int shift;
};
struct dlpt_regs_t {
struct reg_t rgs_chrdet;
struct reg_t uvlo_reg;
struct reg_t vbb_uvlo_reg;
const struct linear_range uvlo_range;
};
struct tag_bootmode {
u32 size;
u32 tag;
u32 bootmode;
u32 boottype;
};
struct dlpt_regs_t mt6359p_dlpt_regs = {
.rgs_chrdet = {
MT6359P_RGS_CHRDET_ADDR,
MT6359P_RGS_CHRDET_MASK << MT6359P_RGS_CHRDET_SHIFT,
MT6359P_RGS_CHRDET_SHIFT
},
.uvlo_reg = {
MT6359P_RG_UVLO_VTHL_ADDR,
MT6359P_RG_UVLO_VTHL_MASK << MT6359P_RG_UVLO_VTHL_SHIFT,
MT6359P_RG_UVLO_VTHL_SHIFT
},
.uvlo_range = {
.min = 2500,
.min_sel = 0,
.max_sel = 8,
.step = 50,
},
};
struct dlpt_regs_t mt6363_dlpt_regs = {
.rgs_chrdet = {
MT6363_CHRDET_DEB_ADDR,
MT6363_CHRDET_DEB_MASK << MT6363_CHRDET_DEB_SHIFT,
MT6363_CHRDET_DEB_SHIFT
},
.uvlo_reg = {
MT6363_RG_VSYS_UVLO_VTHL_ADDR,
MT6363_RG_VSYS_UVLO_VTHL_MASK << MT6363_RG_VSYS_UVLO_VTHL_SHIFT,
MT6363_RG_VSYS_UVLO_VTHL_SHIFT
},
.vbb_uvlo_reg = {
MT6363_RG_VBB_UVLO_VTHL_ADDR,
MT6363_RG_VBB_UVLO_VTHL_MASK << MT6363_RG_VBB_UVLO_VTHL_SHIFT,
MT6363_RG_VBB_UVLO_VTHL_SHIFT
},
.uvlo_range = {
.min = 2000,
.min_sel = 0,
.max_sel = 9,
.step = 100,
},
};
struct dlpt_regs_t mt6377_dlpt_regs = {
.rgs_chrdet = {
MT6377_CHRDET_DEB_ADDR,
MT6377_CHRDET_DEB_MASK << MT6377_CHRDET_DEB_SHIFT,
MT6377_CHRDET_DEB_SHIFT
},
.uvlo_reg = {
MT6377_RG_VSYS_UVLO_VTHL_ADDR,
MT6377_RG_VSYS_UVLO_VTHL_MASK << MT6377_RG_VSYS_UVLO_VTHL_SHIFT,
MT6377_RG_VSYS_UVLO_VTHL_SHIFT
},
.uvlo_range = {
.min = 2500,
.min_sel = 0,
.max_sel = 8,
.step = 50,
},
};
struct dlpt_priv {
struct regmap *regmap;
enum LOW_BATTERY_LEVEL_TAG lbat_level;
const struct dlpt_regs_t *regs;
/* dlpt notify */
struct mutex notify_lock;
struct wakeup_source *notify_ws;
struct timer_list notify_timer;
struct wait_queue_head notify_waiter;
struct task_struct *notify_thread;
bool notify_flag;
/* Imix */
int imix;
int imix_r;
/* others */
int is_power_path_supported;
int is_isense_supported;
struct iio_channel *chan_ptim;
struct iio_channel *chan_imix_r;
struct iio_channel *chan_zcv;
bool suspend_flag;
struct tag_bootmode *tag;
};
struct dlpt_callback_table {
void (*dlptcb)(int value);
};
static struct dlpt_priv dlpt = {
.notify_lock = __MUTEX_INITIALIZER(dlpt.notify_lock),
.notify_waiter = __WAIT_QUEUE_HEAD_INITIALIZER(dlpt.notify_waiter),
.suspend_flag = false,
};
#define DLPTCB_MAX_NUM 16
static struct dlpt_callback_table dlptcb_tb[DLPTCB_MAX_NUM] = { {0} };
/*
* Get ZCV/imix_r Auxadc function
*/
static void update_dlpt_imix_r(void)
{
int ret = 0;
if (!PTR_ERR_OR_ZERO(dlpt.chan_imix_r)) {
ret = iio_read_channel_raw(dlpt.chan_imix_r, &dlpt.imix_r);
if (ret < 0) {
pr_notice("[%s] iio_read_channel_raw error\n", __func__);
return;
}
}
pr_info("[dlpt] imix_r=%d\n", dlpt.imix_r);
}
static int dlpt_adc_chan_init(struct platform_device *pdev)
{
int ret = 0;
dlpt.chan_ptim = devm_iio_channel_get(&pdev->dev, "pmic_ptim");
ret = PTR_ERR_OR_ZERO(dlpt.chan_ptim);
if (ret) {
if (ret != -EPROBE_DEFER)
pr_notice("%s ptim fail, ret=%d\n", __func__, ret);
return ret;
}
dlpt.chan_imix_r = devm_iio_channel_get(&pdev->dev, "pmic_imix_r");
ret = PTR_ERR_OR_ZERO(dlpt.chan_imix_r);
if (ret) {
if (ret != -EPROBE_DEFER)
pr_notice("%s imix_r fail, ret=%d\n", __func__, ret);
return ret;
}
update_dlpt_imix_r();
/* direct point to BATADC or ISENSE phandle in DTS */
if (dlpt.is_isense_supported && dlpt.is_power_path_supported)
dlpt.chan_zcv = devm_iio_channel_get(&pdev->dev, "pmic_isense");
else
dlpt.chan_zcv = devm_iio_channel_get(&pdev->dev, "pmic_batadc");
ret = PTR_ERR_OR_ZERO(dlpt.chan_zcv);
if (ret) {
if (ret != -EPROBE_DEFER)
pr_notice("%s pmic_zcv fail, ret=%d\n", __func__, ret);
return ret;
}
return ret;
}
/*
* DLPT notify function
*/
void register_dlpt_notify(dlpt_callback dlpt_cb,
enum DLPT_PRIO_TAG prio_val)
{
if (prio_val >= DLPTCB_MAX_NUM) {
pr_notice("[%s] prio_val=%d, out of boundary\n",
__func__, prio_val);
return;
}
dlptcb_tb[prio_val].dlptcb = dlpt_cb;
pr_info("[%s] prio_val=%d\n", __func__, prio_val);
if (dlpt.imix != 0) {
pr_notice("[%s] happen\n", __func__);
if (dlpt_cb != NULL)
dlpt_cb(dlpt.imix);
}
}
EXPORT_SYMBOL(register_dlpt_notify);
static void exec_dlpt_callback(int dlpt_val)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(dlptcb_tb); i++) {
if (dlptcb_tb[i].dlptcb)
dlptcb_tb[i].dlptcb(dlpt_val);
}
pr_debug("[%s] dlpt imix_val=%d\n", __func__, dlpt_val);
}
static int dlpt_get_rgs_chrdet(void)
{
int ret = 0;
unsigned int regval = 0;
ret = regmap_read(dlpt.regmap, dlpt.regs->rgs_chrdet.addr, &regval);
if (ret != 0) {
pr_info("%s Failed to get chrdet status\n", __func__);
return ret;
}
if (regval & dlpt.regs->rgs_chrdet.mask)
ret = 1;
return ret;
}
static int dlpt_check_power_off(void)
{
int ret = 0;
static int dlpt_power_off_cnt;
enum LOW_BATTERY_LEVEL_TAG dlpt_power_off_lv = LOW_BATTERY_LEVEL_2;
if (isThreeLevel)
dlpt_power_off_lv = LOW_BATTERY_LEVEL_3;
if (dlpt.lbat_level == dlpt_power_off_lv && dlpt.tag->bootmode != 8) {
if (dlpt_power_off_cnt == 0)
ret = 0; /* 1st time get VBAT < 3.1V, record it */
else
ret = 1; /* 2nd time get VBAT < 3.1V */
dlpt_power_off_cnt++;
pr_info("[%s] %d ret:%d\n", __func__, dlpt_power_off_cnt, ret);
} else
dlpt_power_off_cnt = 0;
/* TODO: do we need to get system_transition_mutex */
if (dlpt_power_off_cnt >= 4)
kernel_restart("DLPT reboot system");
return ret;
}
#if IS_ENABLED(CONFIG_MTK_LOW_BATTERY_POWER_THROTTLING)
static void dlpt_low_battery_cb(enum LOW_BATTERY_LEVEL_TAG level)
{
dlpt.lbat_level = level;
}
#endif
static struct power_supply *get_mtk_gauge_psy(void)
{
static struct power_supply *psy;
union power_supply_propval prop;
int ret;
if (!psy) {
psy = power_supply_get_by_name("mtk-gauge");
if (!psy) {
pr_info("%s psy is not rdy\n", __func__);
return NULL;
}
}
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_PRESENT,
&prop);
if (!ret && prop.intval == 0)
return psy; /* gauge enabled */
return NULL;
}
static void dlpt_set_shutdown_condition(void)
{
struct power_supply *psy;
union power_supply_propval prop;
int ret;
psy = get_mtk_gauge_psy();
/* gauge disabled */
if (!psy)
return;
prop.intval = 1;
ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_ENERGY_EMPTY,
&prop);
if (ret)
pr_info("%s fail\n", __func__);
}
static void dlpt_update_imix(int imix)
{
struct power_supply *psy;
union power_supply_propval prop;
int ret;
psy = get_mtk_gauge_psy();
/* gauge disabled */
if (!psy)
return;
prop.intval = imix;
ret = power_supply_set_property(psy, POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
&prop);
if (ret)
pr_info("%s fail\n", __func__);
}
static int dlpt_get_uisoc(void)
{
struct power_supply *psy;
union power_supply_propval prop;
int ret;
psy = power_supply_get_by_name("battery");
if (!psy)
return -ENODEV;
ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY,
&prop);
if (ret || prop.intval < 0)
return -EINVAL;
else
return prop.intval;
}
static int cmpint(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
static int get_dlpt_imix_charging(void)
{
int zcv_val = 0;
int vsys_min_1_val = DLPT_VOLT_MIN;
int imix = 0;
int ret = 0;
ret = iio_read_channel_processed(dlpt.chan_zcv, &zcv_val);
if (ret < 0) {
pr_notice("[%s] iio_read_channel_processed error\n",
__func__);
return 0;
}
imix = (zcv_val - vsys_min_1_val) * 1000 / dlpt.imix_r * 9 / 10;
pr_debug("[%s] %d %d %d %d\n", __func__,
imix, zcv_val, vsys_min_1_val, dlpt.imix_r);
return imix;
}
#define IMP_VOLT_TENFOLD (10)
static int get_dlpt_imix(void)
{
int volt[5], curr[5], volt_avg = 0, curr_avg = 0;
int vbat = 0, ibat = 0, imix = 0;
int lbatInt1 = DLPT_VOLT_MIN * IMP_VOLT_TENFOLD;
int i = 0, ret = 0;
for (i = 0; i < 5; i++) {
ret = iio_read_channel_attribute(dlpt.chan_ptim, &vbat, &ibat,
IIO_CHAN_INFO_PROCESSED);
if (ret < 0) {
pr_notice("[%s] iio_read_channel_processed error\n",
__func__);
return 0;
}
volt[i] = vbat * IMP_VOLT_TENFOLD;
curr[i] = ibat;
}
sort(volt, 5, sizeof(int), cmpint, NULL);
sort(curr, 5, sizeof(int), cmpint, NULL);
volt_avg = volt[1] + volt[2] + volt[3];
curr_avg = curr[1] + curr[2] + curr[3];
volt_avg = volt_avg / 3;
curr_avg = curr_avg / 3;
imix = (curr_avg + (volt_avg - lbatInt1) * 1000 / dlpt.imix_r) / 10;
pr_info("[%s] %d,%d,%d,%d\n"
, __func__, volt_avg, curr_avg, dlpt.imix_r, imix);
if (imix < 0) {
pr_notice("[dlpt] imix= %d < 0\n", imix);
return dlpt.imix;
}
return imix;
}
static int dlpt_notify_handler(void *unused)
{
unsigned long dlpt_notify_interval;
int pre_ui_soc = 0;
int cur_ui_soc = 0;
pre_ui_soc = dlpt_get_uisoc();
cur_ui_soc = pre_ui_soc;
do {
if (pre_ui_soc > DLPT_NOTIFY_FAST_UISOC)
dlpt_notify_interval = HZ * 20;
else
dlpt_notify_interval = HZ * 10;
wait_event_interruptible(dlpt.notify_waiter,
(dlpt.notify_flag == true));
__pm_stay_awake(dlpt.notify_ws);
mutex_lock(&dlpt.notify_lock);
if (dlpt.suspend_flag)
goto bypass;
cur_ui_soc = dlpt_get_uisoc();
if (dlpt.imix_r == 0)
pr_info("[DLPT] imix_r==0, skip\n");
else if (!get_mtk_gauge_psy())
pr_info("[DLPT] gauge disabled, skip\n");
else {
if (dlpt_get_rgs_chrdet())
dlpt.imix = get_dlpt_imix_charging();
else
dlpt.imix = get_dlpt_imix();
if (dlpt.imix > IMAX_MAX_VALUE)
dlpt.imix = IMAX_MAX_VALUE;
dlpt_update_imix(dlpt.imix);
exec_dlpt_callback(dlpt.imix);
pr_info("[DLPT_final] %d,%d,%d,%d\n"
, dlpt.imix, pre_ui_soc
, cur_ui_soc, IMAX_MAX_VALUE);
}
pre_ui_soc = cur_ui_soc;
if (cur_ui_soc == 1) {
if (dlpt_check_power_off()) {
/* notify battery driver to power off by SOC=0 */
dlpt_set_shutdown_condition();
pr_info("[DLPT] notify battery SOC=0 to power off.\n");
}
}
bypass:
dlpt.notify_flag = false;
mutex_unlock(&dlpt.notify_lock);
__pm_relax(dlpt.notify_ws);
mod_timer(&dlpt.notify_timer, jiffies + dlpt_notify_interval);
} while (!kthread_should_stop());
return 0;
}
static void dlpt_timer_func(struct timer_list *t)
{
dlpt.notify_flag = true;
wake_up_interruptible(&dlpt.notify_waiter);
}
static void dlpt_notify_init(void)
{
int ret = 0;
unsigned long dlpt_notify_interval;
dlpt_notify_interval = HZ * 30;
timer_setup(&dlpt.notify_timer, dlpt_timer_func, TIMER_DEFERRABLE);
mod_timer(&dlpt.notify_timer, jiffies + dlpt_notify_interval);
dlpt.notify_ws = wakeup_source_register(NULL,
"dlpt_notify_ws wakelock");
if (!dlpt.notify_ws)
pr_notice("dlpt_notify_ws wakeup source fail\n");
dlpt.notify_thread = kthread_run(dlpt_notify_handler, 0,
"dlpt_notify_thread");
if (IS_ERR(dlpt.notify_thread))
pr_notice("Failed to create dlpt_notify_thread\n");
#if IS_ENABLED(CONFIG_MTK_LOW_BATTERY_POWER_THROTTLING)
ret = register_low_battery_notify(&dlpt_low_battery_cb,
LOW_BATTERY_PRIO_DLPT);
if (ret == 3)
isThreeLevel = 1;
#endif
}
static int linear_range_get_selector(const struct linear_range *r,
unsigned int val, unsigned int *selector)
{
if ((r->min + (r->max_sel - r->min_sel) * r->step) < val)
return -EINVAL;
if (r->min > val) {
*selector = r->min_sel;
return 0;
}
if (r->step == 0)
*selector = r->max_sel;
else
*selector = DIV_ROUND_UP(val - r->min, r->step) + r->min_sel;
return 0;
}
static void pmic_uvlo_init(int uvlo_level, int vbb_uvlo_level)
{
int ret, val = 0;
ret = linear_range_get_selector(&dlpt.regs->uvlo_range, uvlo_level, &val);
if (!ret) {
regmap_update_bits(dlpt.regmap, dlpt.regs->uvlo_reg.addr,
dlpt.regs->uvlo_reg.mask,
val << dlpt.regs->uvlo_reg.shift);
pr_info("[dlpt] UVLO_VOLT_LEVEL = %d, RG_UVLO_VTHL = 0x%x\n",
uvlo_level, val);
} else
pr_notice("[dlpt] Invalid uvlo_level (%d)\n", uvlo_level);
if (vbb_uvlo_level) {
ret = linear_range_get_selector(&dlpt.regs->uvlo_range, vbb_uvlo_level, &val);
if (!ret) {
regmap_update_bits(dlpt.regmap, dlpt.regs->vbb_uvlo_reg.addr,
dlpt.regs->vbb_uvlo_reg.mask,
val << dlpt.regs->vbb_uvlo_reg.shift);
pr_info("[dlpt] VBB_UVLO_VOLT_LEVEL = %d, RG_VBB_UVLO_VTHL = 0x%x\n",
vbb_uvlo_level, val);
} else
pr_notice("[dlpt] Invalid vbb_uvlo_level (%d)\n", vbb_uvlo_level);
}
}
static void dlpt_parse_dt(struct platform_device *pdev)
{
struct device_node *np;
int uvlo_level = 0, vbb_uvlo_level;
int ret;
/* get dlpt device node */
np = pdev->dev.of_node;
if (!np)
dev_notice(&pdev->dev, "get dlpt node fail\n");
else {
/* get isense support */
dlpt.is_isense_supported =
of_property_read_bool(np, "isense_support");
/* get uvlo-level */
ret = of_property_read_u32(np, "uvlo-level", &uvlo_level);
if (ret)
uvlo_level = POWER_UVLO_VOLT_LEVEL;
/* get vbb-uvlo-level */
ret = of_property_read_u32(np, "vbb-uvlo-level", &vbb_uvlo_level);
if (ret)
vbb_uvlo_level = 0;
pmic_uvlo_init(uvlo_level, vbb_uvlo_level);
/* get power_path_support */
np = of_parse_phandle(pdev->dev.of_node, "mediatek,charger", 0);
if (!np)
dev_notice(&pdev->dev, "get charger node fail\n");
else
dlpt.is_power_path_supported =
of_property_read_bool(np, "power_path_support");
np = of_parse_phandle(pdev->dev.of_node, "bootmode", 0);
if (!np)
dev_notice(&pdev->dev, "get bootmode fail\n");
else {
dlpt.tag = (struct tag_bootmode *)of_get_property(np, "atag,boot", NULL);
if (!dlpt.tag)
dev_notice(&pdev->dev, "failed to get atag,boot\n");
}
}
dev_notice(&pdev->dev, "power_path_support:%d isense_support:%d bootmode:0x%x\n"
, dlpt.is_power_path_supported, dlpt.is_isense_supported, dlpt.tag->bootmode);
}
static int dlpt_probe(struct platform_device *pdev)
{
struct mt6397_chip *chip;
int ret;
dlpt.regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!dlpt.regmap) {
chip = dev_get_drvdata(pdev->dev.parent);
if (!chip || !chip->regmap) {
dev_notice(&pdev->dev, "%s: invalid regmap.\n", __func__);
return -ENODEV;
}
dlpt.regmap = chip->regmap;
}
dlpt.regs = of_device_get_match_data(&pdev->dev);
dlpt_parse_dt(pdev);
ret = dlpt_adc_chan_init(pdev);
if (ret)
return ret;
dlpt_notify_init();
return 0;
}
static int __maybe_unused dlpt_suspend(struct device *d)
{
if (!mutex_trylock(&dlpt.notify_lock))
return -EAGAIN;
dlpt.suspend_flag = true;
mutex_unlock(&dlpt.notify_lock);
return 0;
}
static int __maybe_unused dlpt_resume(struct device *d)
{
mutex_lock(&dlpt.notify_lock);
dlpt.suspend_flag = false;
mutex_unlock(&dlpt.notify_lock);
update_dlpt_imix_r();
wake_up_interruptible(&dlpt.notify_waiter);
return 0;
}
static SIMPLE_DEV_PM_OPS(dlpt_pm_ops,
dlpt_suspend,
dlpt_resume);
static const struct of_device_id dynamic_loading_throttling_of_match[] = {
{
.compatible = "mediatek,mt6359p-dynamic_loading_throttling",
.data = &mt6359p_dlpt_regs,
}, {
.compatible = "mediatek,mt6363-dynamic_loading_throttling",
.data = &mt6363_dlpt_regs,
}, {
.compatible = "mediatek,mt6377-dynamic_loading_throttling",
.data = &mt6377_dlpt_regs,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, dynamic_loading_throttling_of_match);
static struct platform_driver dynamic_loading_throttling_driver = {
.driver = {
.name = "mtk_dynamic_loading_throttling",
.of_match_table = dynamic_loading_throttling_of_match,
.pm = &dlpt_pm_ops,
},
.probe = dlpt_probe,
};
module_platform_driver(dynamic_loading_throttling_driver);
MODULE_AUTHOR("Wen Su <Wen.Su@mediatek.com>");
MODULE_DESCRIPTION("MTK dynamic loading throttling driver");
MODULE_LICENSE("GPL");