unplugged-kernel/drivers/misc/mediatek/power_throttling/mtk_battery_oc_throttling.c

447 lines
12 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
* Author: Samuel Hsieh <samuel.hsieh@mediatek.com>
*/
#include <linux/interrupt.h>
#include <linux/mfd/mt6357/registers.h>
#include <linux/mfd/mt6359/registers.h>
#include <linux/mfd/mt6397/core.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/device.h>
#include "mtk_battery_oc_throttling.h"
/* Customize the setting in pmic mt635x.dtsi */
#define DEF_BAT_OC_THD_H 5800
#define DEF_BAT_OC_THD_L 6300
#define UNIT_TRANS_10 (10)
#define CURRENT_CONVERT_RATIO 95
#define OCCB_MAX_NUM 16
/* Get R_FG_VALUE/CAR_TUNE_VALUE from gauge dts node */
#define MT6357_DEFAULT_RFG (100)
#define MT6357_UNIT_FGCURRENT (314331)
#define MT6358_DEFAULT_RFG (100)
#define MT6358_UNIT_FGCURRENT (381470)
#define MT6359_DEFAULT_RFG (50)
#define MT6359_UNIT_FGCURRENT (610352)
struct reg_t {
unsigned int addr;
unsigned int mask;
};
struct battery_oc_regs_t {
struct reg_t fg_cur_hth;
struct reg_t fg_cur_lth;
};
struct battery_oc_regs_t mt6357_battery_oc_regs = {
.fg_cur_hth = {MT6357_FGADC_CUR_CON2, 0xFFFF},
.fg_cur_lth = {MT6357_FGADC_CUR_CON1, 0xFFFF},
};
struct battery_oc_regs_t mt6359_battery_oc_regs = {
.fg_cur_hth = {MT6359_FGADC_CUR_CON2, 0xFFFF},
.fg_cur_lth = {MT6359_FGADC_CUR_CON1, 0xFFFF},
};
struct battery_oc_priv {
struct regmap *regmap;
int oc_level;
unsigned int oc_thd_h;
unsigned int oc_thd_l;
int fg_cur_h_irq;
int fg_cur_l_irq;
int r_fg_value;
int default_rfg;
int car_tune_value;
int unit_fg_cur;
const struct battery_oc_regs_t *regs;
};
static int g_battery_oc_stop;
struct battery_oc_callback_table {
void (*occb)(enum BATTERY_OC_LEVEL_TAG);
};
static struct battery_oc_callback_table occb_tb[OCCB_MAX_NUM] = { {0} };
void register_battery_oc_notify(battery_oc_callback oc_cb,
enum BATTERY_OC_PRIO_TAG prio_val)
{
if (prio_val >= OCCB_MAX_NUM || prio_val < 0) {
pr_info("[%s] prio_val=%d, out of boundary\n",
__func__, prio_val);
return;
}
occb_tb[prio_val].occb = oc_cb;
pr_info("[%s] prio_val=%d\n", __func__, prio_val);
}
EXPORT_SYMBOL(register_battery_oc_notify);
static void exec_battery_oc_callback(enum BATTERY_OC_LEVEL_TAG battery_oc_level)
{
int i;
if (g_battery_oc_stop == 1) {
pr_info("[%s] g_battery_oc_stop=%d\n"
, __func__, g_battery_oc_stop);
} else {
for (i = 0; i < OCCB_MAX_NUM; i++) {
if (occb_tb[i].occb) {
occb_tb[i].occb(battery_oc_level);
pr_info("[%s] prio_val=%d,battery_oc_level=%d\n",
__func__, i, battery_oc_level);
}
}
}
}
/*****************************************************************************
* battery OC protect UT
******************************************************************************/
static ssize_t battery_oc_protect_ut_show(
struct device *pdev, struct device_attribute *attr,
char *buf)
{
struct battery_oc_priv *priv = dev_get_drvdata(pdev);
pr_debug("[%s] g_battery_oc_level=%d\n",
__func__, priv->oc_level);
return sprintf(buf, "%u\n", priv->oc_level);
}
static ssize_t battery_oc_protect_ut_store(
struct device *pdev, struct device_attribute *attr,
const char *buf, size_t size)
{
int ret = 0;
char *pvalue = NULL;
unsigned int val = 0;
pr_info("[%s]\n", __func__);
if (buf != NULL && size != 0) {
pr_info("[%s] buf is %s and size is %zu\n",
__func__, buf, size);
pvalue = (char *)buf;
ret = kstrtou32(pvalue, 16, (unsigned int *)&val);
if (val <= 1) {
pr_info("[%s] your input is %d\n", __func__, val);
exec_battery_oc_callback(val);
} else {
pr_info("[%s] wrong number (%d)\n", __func__, val);
}
}
return size;
}
static DEVICE_ATTR_RW(battery_oc_protect_ut);
/*****************************************************************************
* battery OC protect stop
******************************************************************************/
static ssize_t battery_oc_protect_stop_show(
struct device *pdev, struct device_attribute *attr,
char *buf)
{
pr_debug("[%s] g_battery_oc_stop=%d\n",
__func__, g_battery_oc_stop);
return sprintf(buf, "%u\n", g_battery_oc_stop);
}
static ssize_t battery_oc_protect_stop_store(
struct device *pdev, struct device_attribute *attr,
const char *buf, size_t size)
{
int ret = 0;
char *pvalue = NULL;
unsigned int val = 0;
pr_info("[%s]\n", __func__);
if (buf != NULL && size != 0) {
pr_info("[%s] buf is %s and size is %zu\n",
__func__, buf, size);
pvalue = (char *)buf;
ret = kstrtou32(pvalue, 16, (unsigned int *)&val);
if ((val != 0) && (val != 1))
val = 0;
g_battery_oc_stop = val;
pr_info("[%s] g_battery_oc_stop=%d\n",
__func__, g_battery_oc_stop);
}
return size;
}
static DEVICE_ATTR_RW(battery_oc_protect_stop);
/*****************************************************************************
* battery OC protect level
******************************************************************************/
static ssize_t battery_oc_protect_level_show(
struct device *pdev, struct device_attribute *attr,
char *buf)
{
struct battery_oc_priv *priv = dev_get_drvdata(pdev);
pr_info("[%s] g_battery_oc_level=%d\n",
__func__, priv->oc_level);
return sprintf(buf, "%u\n", priv->oc_level);
}
static ssize_t battery_oc_protect_level_store(
struct device *pdev, struct device_attribute *attr,
const char *buf, size_t size)
{
struct battery_oc_priv *priv = dev_get_drvdata(pdev);
pr_info("[%s] g_battery_oc_level = %d\n",
__func__, priv->oc_level);
return size;
}
static DEVICE_ATTR_RW(battery_oc_protect_level);
/*
* 65535 - (I_mA * 1000 * r_fg_value / DEFAULT_RFG * 1000000 / car_tune_value
* / UNIT_FGCURRENT * CURRENT_CONVERT_RATIO / 100)
*/
static unsigned int to_fg_code(struct battery_oc_priv *priv, u64 cur_mA)
{
cur_mA = div_u64(cur_mA * 1000 * priv->r_fg_value, priv->default_rfg);
cur_mA = div_u64(cur_mA * 1000000, priv->car_tune_value);
cur_mA = div_u64(cur_mA, priv->unit_fg_cur);
cur_mA = div_u64(cur_mA * CURRENT_CONVERT_RATIO, 100);
/* 2's complement */
return (0xFFFF - cur_mA);
}
static irqreturn_t fg_cur_h_int_handler(int irq, void *data)
{
struct battery_oc_priv *priv = data;
priv->oc_level = BATTERY_OC_LEVEL_0;
exec_battery_oc_callback(priv->oc_level);
disable_irq_nosync(priv->fg_cur_h_irq);
enable_irq(priv->fg_cur_l_irq);
return IRQ_HANDLED;
}
static irqreturn_t fg_cur_l_int_handler(int irq, void *data)
{
struct battery_oc_priv *priv = data;
priv->oc_level = BATTERY_OC_LEVEL_1;
exec_battery_oc_callback(priv->oc_level);
disable_irq_nosync(priv->fg_cur_l_irq);
enable_irq(priv->fg_cur_h_irq);
return IRQ_HANDLED;
}
static int battery_oc_parse_dt(struct platform_device *pdev)
{
struct mt6397_chip *pmic = dev_get_drvdata(pdev->dev.parent);
struct battery_oc_priv *priv = dev_get_drvdata(&pdev->dev);
struct device_node *np;
int ret = 0;
/* Get R_FG_VALUE/CAR_TUNE_VALUE from gauge dts node */
np = of_find_node_by_name(pdev->dev.parent->of_node, "mtk_gauge");
if (!np) {
dev_notice(&pdev->dev, "get mtk_gauge node fail\n");
return -EINVAL;
}
ret = of_property_read_u32(np, "R_FG_VALUE", &priv->r_fg_value);
if (ret) {
dev_notice(&pdev->dev, "get R_FG_VALUE fail\n");
return -EINVAL;
}
priv->r_fg_value *= UNIT_TRANS_10;
ret = of_property_read_u32(np, "CAR_TUNE_VALUE", &priv->car_tune_value);
if (ret) {
dev_notice(&pdev->dev, "get CAR_TUNE_VALUE fail\n");
return -EINVAL;
}
priv->car_tune_value *= UNIT_TRANS_10;
/* Get oc_thd_h/oc_thd_l value from dts node */
np = of_find_node_by_name(pdev->dev.parent->of_node,
"mtk_battery_oc_throttling");
if (!np) {
dev_notice(&pdev->dev, "get mtk battery oc node fail\n");
return -EINVAL;
}
ret = of_property_read_u32(np, "oc-thd-h", &priv->oc_thd_h);
if (ret)
priv->oc_thd_h = DEF_BAT_OC_THD_H;
ret = of_property_read_u32(np, "oc-thd-l", &priv->oc_thd_l);
if (ret)
priv->oc_thd_l = DEF_BAT_OC_THD_L;
/* Get DEFAULT_RFG/UNIT_FGCURRENT from pre-defined MACRO */
switch (pmic->chip_id) {
case MT6357_CHIP_ID:
priv->default_rfg = MT6357_DEFAULT_RFG;
priv->unit_fg_cur = MT6357_UNIT_FGCURRENT;
break;
case MT6359_CHIP_ID:
priv->default_rfg = MT6359_DEFAULT_RFG;
priv->unit_fg_cur = MT6359_UNIT_FGCURRENT;
break;
default:
dev_info(&pdev->dev, "unsupported chip: 0x%x\n", pmic->chip_id);
return -EINVAL;
}
dev_info(&pdev->dev, "r_fg=%d car_tune=%d DEFAULT_RFG=%d UNIT_FGCURRENT=%d\n"
, priv->r_fg_value, priv->car_tune_value
, priv->default_rfg, priv->unit_fg_cur);
return 0;
}
static int battery_oc_throttling_probe(struct platform_device *pdev)
{
int ret;
struct battery_oc_priv *priv;
struct mt6397_chip *chip;
chip = dev_get_drvdata(pdev->dev.parent);
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
dev_set_drvdata(&pdev->dev, priv);
priv->regmap = chip->regmap;
priv->regs = of_device_get_match_data(&pdev->dev);
/* set Maximum threshold to avoid irq being triggered at init */
regmap_update_bits(priv->regmap, priv->regs->fg_cur_hth.addr,
priv->regs->fg_cur_hth.mask, 0x7FFF);
regmap_update_bits(priv->regmap, priv->regs->fg_cur_lth.addr,
priv->regs->fg_cur_lth.mask, 0x8000);
priv->fg_cur_h_irq = platform_get_irq_byname(pdev, "fg_cur_h");
if (priv->fg_cur_h_irq < 0) {
dev_notice(&pdev->dev, "failed to get fg_cur_h irq, ret=%d\n",
priv->fg_cur_h_irq);
return priv->fg_cur_h_irq;
}
priv->fg_cur_l_irq = platform_get_irq_byname(pdev, "fg_cur_l");
if (priv->fg_cur_l_irq < 0) {
dev_notice(&pdev->dev, "failed to get fg_cur_l irq, ret=%d\n",
priv->fg_cur_l_irq);
return priv->fg_cur_l_irq;
}
ret = devm_request_threaded_irq(&pdev->dev, priv->fg_cur_h_irq, NULL,
fg_cur_h_int_handler, IRQF_TRIGGER_NONE,
"fg_cur_h", priv);
if (ret < 0)
dev_notice(&pdev->dev, "request fg_cur_h irq fail\n");
ret = devm_request_threaded_irq(&pdev->dev, priv->fg_cur_l_irq, NULL,
fg_cur_l_int_handler, IRQF_TRIGGER_NONE,
"fg_cur_l", priv);
if (ret < 0)
dev_notice(&pdev->dev, "request fg_cur_l irq fail\n");
disable_irq_nosync(priv->fg_cur_h_irq);
ret = battery_oc_parse_dt(pdev);
if (ret < 0) {
dev_notice(&pdev->dev, "bat_oc parse dt fail, ret=%d\n", ret);
return ret;
}
regmap_update_bits(priv->regmap, priv->regs->fg_cur_hth.addr,
priv->regs->fg_cur_hth.mask,
to_fg_code(priv, priv->oc_thd_h));
regmap_update_bits(priv->regmap, priv->regs->fg_cur_lth.addr,
priv->regs->fg_cur_lth.mask,
to_fg_code(priv, priv->oc_thd_l));
dev_info(&pdev->dev, "%dmA(0x%x), %dmA(0x%x) Done\n",
priv->oc_thd_h, to_fg_code(priv, priv->oc_thd_h),
priv->oc_thd_l, to_fg_code(priv, priv->oc_thd_l));
ret = device_create_file(&(pdev->dev),
&dev_attr_battery_oc_protect_ut);
ret |= device_create_file(&(pdev->dev),
&dev_attr_battery_oc_protect_stop);
ret |= device_create_file(&(pdev->dev),
&dev_attr_battery_oc_protect_level);
if (ret)
dev_notice(&pdev->dev, "create file error ret=%d\n", ret);
return ret;
}
static int __maybe_unused battery_oc_throttling_suspend(struct device *d)
{
struct battery_oc_priv *priv = dev_get_drvdata(d);
if (priv->oc_level == BATTERY_OC_LEVEL_0)
disable_irq_nosync(priv->fg_cur_l_irq);
else
disable_irq_nosync(priv->fg_cur_h_irq);
return 0;
}
static int __maybe_unused battery_oc_throttling_resume(struct device *d)
{
struct battery_oc_priv *priv = dev_get_drvdata(d);
if (priv->oc_level == BATTERY_OC_LEVEL_0)
enable_irq(priv->fg_cur_l_irq);
else
enable_irq(priv->fg_cur_h_irq);
return 0;
}
static SIMPLE_DEV_PM_OPS(battery_oc_throttling_pm_ops,
battery_oc_throttling_suspend,
battery_oc_throttling_resume);
static const struct of_device_id battery_oc_throttling_of_match[] = {
{
.compatible = "mediatek,mt6357-battery_oc_throttling",
.data = &mt6357_battery_oc_regs,
}, {
.compatible = "mediatek,mt6359-battery_oc_throttling",
.data = &mt6359_battery_oc_regs,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, battery_oc_throttling_of_match);
static struct platform_driver battery_oc_throttling_driver = {
.driver = {
.name = "mtk_battery_oc_throttling",
.of_match_table = battery_oc_throttling_of_match,
.pm = &battery_oc_throttling_pm_ops,
},
.probe = battery_oc_throttling_probe,
};
module_platform_driver(battery_oc_throttling_driver);
MODULE_AUTHOR("Jeter Chen");
MODULE_DESCRIPTION("MTK battery over current throttling driver");
MODULE_LICENSE("GPL");