kernel-brax3-ubuntu-touch/drivers/misc/mediatek/power_throttling/pmic_lvsys_notify.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

485 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2022 MediaTek Inc.
*/
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/linear_range.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "pmic_lvsys_notify.h"
#define LVSYS_DBG 0
#define EVENT_LVSYS_F 0
#define EVENT_LVSYS_R BIT(15)
#define MT6363_RG_LVSYS_INT_EN 0xa18
#define MT6363_RG_LVSYS_INT_VTHL 0xa8b
#define MT6363_RG_LVSYS_INT_VTHH 0xa8c
struct pmic_lvsys_info {
u32 lvsys_int_en_reg;
u32 lvsys_int_en_mask;
u32 lvsys_int_fdb_sel_mask;
u32 lvsys_int_rdb_sel_mask;
u32 lvsys_int_vthl_reg;
u32 lvsys_int_vthh_reg;
const struct linear_range vthl_range;
const struct linear_range vthh_range;
};
static const struct pmic_lvsys_info mt6363_lvsys_info = {
.lvsys_int_en_reg = MT6363_RG_LVSYS_INT_EN,
.lvsys_int_en_mask = 0x1,
.lvsys_int_fdb_sel_mask = 0x6,
.lvsys_int_rdb_sel_mask = 0x18,
.lvsys_int_vthl_reg = MT6363_RG_LVSYS_INT_VTHL,
.lvsys_int_vthh_reg = MT6363_RG_LVSYS_INT_VTHH,
.vthl_range = {
.min = 2500,
.min_sel = 0,
.max_sel = 9,
.step = 100,
},
.vthh_range = {
.min = 2600,
.min_sel = 0,
.max_sel = 9,
.step = 100,
},
};
struct pmic_lvsys_notify {
struct device *dev;
struct regmap *regmap;
struct mutex lock;
const struct pmic_lvsys_info *info;
unsigned int *thd_volts_l;
unsigned int *thd_volts_h;
unsigned int *cur_lv_ptr;
unsigned int *cur_hv_ptr;
int thd_volts_l_size;
int thd_volts_h_size;
};
static BLOCKING_NOTIFIER_HEAD(lvsys_notifier_list);
/**
* lvsys_register_notifier - register a lvsys notifier
* @nb: notifier block to callback on events
*
* Return: 0 on success, negative error code on failure.
*/
int lvsys_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&lvsys_notifier_list, nb);
}
EXPORT_SYMBOL(lvsys_register_notifier);
/**
* lvsys_unregister_notifier - unregister a lvsys notifier
* @nb: notifier block to callback on events
*
* Return: 0 on success, negative error code on failure.
*/
int lvsys_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&lvsys_notifier_list, nb);
}
EXPORT_SYMBOL(lvsys_unregister_notifier);
struct vio18_ctrl_t {
struct notifier_block nb;
struct regmap *main_regmap;
struct regmap *second_regmap;
unsigned int main_switch;
unsigned int second_switch;
};
static int vio18_switch_handler(struct notifier_block *nb, unsigned long event, void *v)
{
int ret;
struct vio18_ctrl_t *vio18_ctrl = container_of(nb, struct vio18_ctrl_t, nb);
if (event == LVSYS_F_3400) {
ret = regmap_write(vio18_ctrl->main_regmap, vio18_ctrl->main_switch, 0);
if (ret)
pr_notice("Failed to set main vio18_switch, ret=%d\n", ret);
ret = regmap_write(vio18_ctrl->second_regmap, vio18_ctrl->second_switch, 0);
if (ret)
pr_notice("Failed to set second vio18_switch, ret=%d\n", ret);
} else if (event == LVSYS_R_3500) {
ret = regmap_write(vio18_ctrl->main_regmap, vio18_ctrl->main_switch, 1);
if (ret)
pr_notice("Failed to set main vio18_switch, ret=%d\n", ret);
ret = regmap_write(vio18_ctrl->second_regmap, vio18_ctrl->second_switch, 1);
if (ret)
pr_notice("Failed to set second vio18_switch, ret=%d\n", ret);
}
return 0;
}
static struct regmap *vio18_switch_get_regmap(const char *name)
{
struct device_node *np;
struct platform_device *pdev;
np = of_find_node_by_name(NULL, name);
if (!np)
return NULL;
pdev = of_find_device_by_node(np->child);
if (!pdev)
return NULL;
return dev_get_regmap(pdev->dev.parent, NULL);
}
static int vio18_switch_init(struct device *dev, struct regmap *main_regmap)
{
int ret = 0;
unsigned int val_arr[2] = {0};
struct vio18_ctrl_t *vio18_ctrl;
vio18_ctrl = devm_kzalloc(dev, sizeof(*vio18_ctrl), GFP_KERNEL);
if (!vio18_ctrl)
return -ENOMEM;
vio18_ctrl->main_regmap = main_regmap;
vio18_ctrl->second_regmap = vio18_switch_get_regmap("second_pmic");
if (!vio18_ctrl->second_regmap)
return -EINVAL;
ret = of_property_read_u32_array(dev->of_node, "vio18-switch-reg", val_arr, 2);
if (ret)
return -EINVAL;
vio18_ctrl->main_switch = val_arr[0];
vio18_ctrl->second_switch = val_arr[1];
vio18_ctrl->nb.notifier_call = vio18_switch_handler;
return lvsys_register_notifier(&vio18_ctrl->nb);
}
static void enable_lvsys_int(struct pmic_lvsys_notify *lvsys_notify, bool en)
{
int ret;
unsigned int val;
const struct pmic_lvsys_info *info = lvsys_notify->info;
val = en ? info->lvsys_int_en_mask : 0;
ret = regmap_update_bits(lvsys_notify->regmap, info->lvsys_int_en_reg,
info->lvsys_int_en_mask, val);
if (ret)
dev_notice(lvsys_notify->dev, "Failed to enable LVSYS_INT, ret=%d\n", ret);
}
static int lvsys_vth_get_selector_high(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 int lvsys_vth_get_selector_low(const struct linear_range *r,
unsigned int val, unsigned int *selector)
{
if (r->min > val)
return -EINVAL;
if ((r->min + (r->max_sel - r->min_sel) * r->step) < val) {
*selector = r->max_sel;
return 0;
}
if (r->step == 0)
*selector = r->min_sel;
else
*selector = (val - r->min) / r->step + r->min_sel;
return 0;
}
static void update_lvsys_vth(struct pmic_lvsys_notify *lvsys_notify)
{
int ret;
unsigned int *last_lv = lvsys_notify->thd_volts_l + lvsys_notify->thd_volts_l_size - 1;
unsigned int *first_hv = lvsys_notify->thd_volts_h;
unsigned int vth_sel = 0;
const struct pmic_lvsys_info *info = lvsys_notify->info;
if (lvsys_notify->cur_lv_ptr && lvsys_notify->cur_lv_ptr <= last_lv) {
ret = lvsys_vth_get_selector_high(&info->vthl_range, *(lvsys_notify->cur_lv_ptr),
&vth_sel);
if (ret)
dev_notice(lvsys_notify->dev, "Failed to get selector high(%d)\n", ret);
#if LVSYS_DBG
dev_info(lvsys_notify->dev, "set INT_VTHL=%d(%d)\n",
vth_sel, *(lvsys_notify->cur_lv_ptr));
#endif
ret = regmap_write(lvsys_notify->regmap, info->lvsys_int_vthl_reg, vth_sel);
if (ret)
dev_notice(lvsys_notify->dev, "Failed to set INT_VTHL=%d, ret=%d\n",
vth_sel, ret);
} else
lvsys_notify->cur_lv_ptr = NULL;
if (lvsys_notify->cur_hv_ptr && lvsys_notify->cur_hv_ptr >= first_hv) {
ret = lvsys_vth_get_selector_low(&info->vthh_range, *(lvsys_notify->cur_hv_ptr),
&vth_sel);
if (ret)
dev_notice(lvsys_notify->dev, "Failed to get selector low(%d)\n", ret);
#if LVSYS_DBG
dev_info(lvsys_notify->dev, "set INT_VTHH=%d(%d)\n",
vth_sel, *(lvsys_notify->cur_hv_ptr));
#endif
ret = regmap_write(lvsys_notify->regmap, info->lvsys_int_vthh_reg, vth_sel);
if (ret)
dev_notice(lvsys_notify->dev, "Failed to set INT_VTHH=%d, ret=%d\n",
vth_sel, ret);
} else
lvsys_notify->cur_hv_ptr = NULL;
}
static unsigned int *get_next_lv_ptr(struct pmic_lvsys_notify *lvsys_notify)
{
int thd_volts_size = lvsys_notify->thd_volts_l_size;
unsigned int *next_lv_ptr = lvsys_notify->thd_volts_l;
for (; next_lv_ptr < lvsys_notify->thd_volts_l + thd_volts_size; next_lv_ptr++) {
if (*next_lv_ptr < *(lvsys_notify->cur_hv_ptr))
break;
}
if (next_lv_ptr == lvsys_notify->thd_volts_l + thd_volts_size)
return NULL; /* not found */
return next_lv_ptr;
}
static unsigned int *get_next_hv_ptr(struct pmic_lvsys_notify *lvsys_notify)
{
int thd_volts_size = lvsys_notify->thd_volts_h_size;
unsigned int *next_hv_ptr = lvsys_notify->thd_volts_h + thd_volts_size - 1;
for (; next_hv_ptr >= lvsys_notify->thd_volts_h; next_hv_ptr--) {
if (*next_hv_ptr > *(lvsys_notify->cur_lv_ptr))
break;
}
if (next_hv_ptr == lvsys_notify->thd_volts_h - 1)
return NULL; /* not found */
return next_hv_ptr;
}
static irqreturn_t lvsys_f_int_handler(int irq, void *data)
{
struct pmic_lvsys_notify *lvsys_notify = (struct pmic_lvsys_notify *)data;
unsigned int event;
if (!lvsys_notify)
return IRQ_HANDLED;
mutex_lock(&lvsys_notify->lock);
if (!lvsys_notify->cur_lv_ptr) {
mutex_unlock(&lvsys_notify->lock);
return IRQ_HANDLED;
}
event = EVENT_LVSYS_F | *(lvsys_notify->cur_lv_ptr);
dev_notice(lvsys_notify->dev, "event: falling %d\n", *(lvsys_notify->cur_lv_ptr));
blocking_notifier_call_chain(&lvsys_notifier_list, event, NULL);
lvsys_notify->cur_hv_ptr = get_next_hv_ptr(lvsys_notify);
lvsys_notify->cur_lv_ptr++;
update_lvsys_vth(lvsys_notify);
mutex_unlock(&lvsys_notify->lock);
return IRQ_HANDLED;
}
static irqreturn_t lvsys_r_int_handler(int irq, void *data)
{
struct pmic_lvsys_notify *lvsys_notify = (struct pmic_lvsys_notify *)data;
unsigned int event;
if (!lvsys_notify)
return IRQ_HANDLED;
mutex_lock(&lvsys_notify->lock);
if (!lvsys_notify->cur_hv_ptr) {
mutex_unlock(&lvsys_notify->lock);
return IRQ_HANDLED;
}
event = EVENT_LVSYS_R | *(lvsys_notify->cur_hv_ptr);
dev_notice(lvsys_notify->dev, "event: rising %d\n", *(lvsys_notify->cur_hv_ptr));
blocking_notifier_call_chain(&lvsys_notifier_list, event, NULL);
lvsys_notify->cur_lv_ptr = get_next_lv_ptr(lvsys_notify);
lvsys_notify->cur_hv_ptr--;
update_lvsys_vth(lvsys_notify);
mutex_unlock(&lvsys_notify->lock);
return IRQ_HANDLED;
}
static int pmic_lvsys_parse_dt(struct pmic_lvsys_notify *lvsys_notify, struct device_node *np)
{
int ret;
unsigned int deb_sel = 0;
const struct pmic_lvsys_info *info = lvsys_notify->info;
lvsys_notify->thd_volts_l_size =
of_property_count_elems_of_size(np, "thd-volts-l", sizeof(u32));
if (lvsys_notify->thd_volts_l_size <= 0)
return -EINVAL;
lvsys_notify->thd_volts_l = devm_kmalloc_array(lvsys_notify->dev,
lvsys_notify->thd_volts_l_size,
sizeof(u32), GFP_KERNEL);
if (!lvsys_notify->thd_volts_l)
return -ENOMEM;
ret = of_property_read_u32_array(np, "thd-volts-l", lvsys_notify->thd_volts_l,
lvsys_notify->thd_volts_l_size);
lvsys_notify->thd_volts_h_size =
of_property_count_elems_of_size(np, "thd-volts-h", sizeof(u32));
if (lvsys_notify->thd_volts_h_size <= 0)
return -EINVAL;
lvsys_notify->thd_volts_h = devm_kmalloc_array(lvsys_notify->dev,
lvsys_notify->thd_volts_h_size,
sizeof(u32), GFP_KERNEL);
if (!lvsys_notify->thd_volts_h)
return -ENOMEM;
ret |= of_property_read_u32_array(np, "thd-volts-h", lvsys_notify->thd_volts_h,
lvsys_notify->thd_volts_h_size);
if (ret)
return ret;
lvsys_notify->cur_lv_ptr = lvsys_notify->thd_volts_l;
lvsys_notify->cur_hv_ptr = get_next_hv_ptr(lvsys_notify);
update_lvsys_vth(lvsys_notify);
ret = of_property_read_u32(np, "lv-deb-sel", &deb_sel);
if (ret)
deb_sel = 0;
else
deb_sel <<= __ffs(info->lvsys_int_fdb_sel_mask);
ret = regmap_update_bits(lvsys_notify->regmap, info->lvsys_int_en_reg,
info->lvsys_int_fdb_sel_mask, deb_sel);
if (ret)
dev_notice(lvsys_notify->dev, "Failed to set LVSYS_INT_FDB_SEL, ret=%d\n", ret);
ret = of_property_read_u32(np, "hv-deb-sel", &deb_sel);
if (ret)
deb_sel = 0;
else
deb_sel <<= __ffs(info->lvsys_int_rdb_sel_mask);
ret = regmap_update_bits(lvsys_notify->regmap, info->lvsys_int_en_reg,
info->lvsys_int_rdb_sel_mask, deb_sel);
if (ret)
dev_notice(lvsys_notify->dev, "Failed to set LVSYS_INT_RDB_SEL, ret=%d\n", ret);
return ret;
}
static int pmic_lvsys_notify_probe(struct platform_device *pdev)
{
int ret, irq;
struct device_node *np = pdev->dev.of_node;
struct pmic_lvsys_notify *lvsys_notify;
const struct pmic_lvsys_info *info;
lvsys_notify = devm_kzalloc(&pdev->dev, sizeof(*lvsys_notify), GFP_KERNEL);
if (!lvsys_notify)
return -ENOMEM;
info = of_device_get_match_data(&pdev->dev);
if (!info) {
dev_notice(&pdev->dev, "no platform data\n");
return -EINVAL;
}
lvsys_notify->dev = &pdev->dev;
lvsys_notify->info = info;
lvsys_notify->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!lvsys_notify->regmap) {
dev_notice(&pdev->dev, "failed to get regmap\n");
return -ENODEV;
}
mutex_init(&lvsys_notify->lock);
pmic_lvsys_parse_dt(lvsys_notify, np);
irq = platform_get_irq_byname_optional(pdev, "LVSYS_F");
if (irq < 0) {
dev_notice(&pdev->dev, "failed to get LVSYS_F irq, ret=%d\n", irq);
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, lvsys_f_int_handler, IRQF_ONESHOT,
"LVSYS_F", lvsys_notify);
if (ret < 0) {
dev_notice(&pdev->dev, "failed to request LVSYS_F irq, ret=%d\n", ret);
return ret;
}
irq = platform_get_irq_byname_optional(pdev, "LVSYS_R");
if (irq < 0) {
dev_notice(&pdev->dev, "failed to get LVSYS_R irq, ret=%d\n", irq);
return irq;
}
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, lvsys_r_int_handler, IRQF_ONESHOT,
"LVSYS_R", lvsys_notify);
if (ret < 0) {
dev_notice(&pdev->dev, "failed to request LVSYS_R irq, ret=%d\n", ret);
return ret;
}
/* RG_LVSYS_INT_EN = 0x1 */
enable_lvsys_int(lvsys_notify, true);
ret = vio18_switch_init(&pdev->dev, lvsys_notify->regmap);
if (ret)
dev_notice(&pdev->dev, "vio18_switch_init failed, ret=%d\n", ret);
return 0;
}
static const struct of_device_id pmic_lvsys_notify_of_match[] = {
{
.compatible = "mediatek,mt6363-lvsys-notify",
.data = &mt6363_lvsys_info,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(of, pmic_lvsys_notify_of_match);
static struct platform_driver pmic_lvsys_notify_driver = {
.driver = {
.name = "pmic_lvsys_notify",
.of_match_table = pmic_lvsys_notify_of_match,
},
.probe = pmic_lvsys_notify_probe,
};
module_platform_driver(pmic_lvsys_notify_driver);
MODULE_AUTHOR("Jeter Chen <Jeter.Chen@mediatek.com>");
MODULE_DESCRIPTION("MTK pmic lvsys notify driver");
MODULE_LICENSE("GPL");