688 lines
16 KiB
C
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, ®val);
|
|
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");
|