kernel_samsung_a34x-permissive/drivers/misc/mediatek/connectivity/power_throttling/adapter.c

585 lines
15 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2019 MediaTek Inc.
*/
#include <linux/types.h>
#include <linux/string.h>
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sched/clock.h>
#if IS_ENABLED(CONFIG_COMPAT)
#include <linux/compat.h>
#endif
#include "conn_power_throttling.h"
#if IS_ENABLED(CONFIG_MTK_LOW_BATTERY_POWER_THROTTLING)
#include <mtk_low_battery_throttling.h>
#endif
/* termal related macro */
#if IS_ENABLED(CONFIG_MTK_LOW_BATTERY_POWER_THROTTLING)
//#define CONN_PWR_LOW_BATTERY_ENABLE
#endif
#define CONN_PWR_INVALID_TEMP (-888)
#define CONN_PWR_MAX_TEMP_HIGH 110
#if IS_ENABLED(CONFIG_CONN_PWR_DEBUG)
#define CONN_PWR_MAX_TEMP_LOW 0
#else
#define CONN_PWR_MAX_TEMP_LOW 60
#endif
/* device node related macro */
#define CONN_PWR_DEV_NUM 1
#define CONN_PWR_DRVIER_NAME "conn_pwr_drv"
#define CONN_PWR_DEVICE_NAME "conn_pwr_dev"
#define CONN_PWR_DEV_MAJOR 155
#define CONN_PWR_DEV_IOC_MAGIC 0xc2
#define CONN_PWR_IOCTL_SET_MAX_TEMP _IOW(CONN_PWR_DEV_IOC_MAGIC, 0, int)
#define CONN_PWR_IOCTL_GET_DRV_LEVEL _IOR(CONN_PWR_DEV_IOC_MAGIC, 1, int)
#define CONN_PWR_IOCTL_GET_PLAT_LEVEL _IOR(CONN_PWR_DEV_IOC_MAGIC, 2, int)
#define CONN_PWR_SWITCH_LEVEL_MIN_SEC 30
#define CONN_PWR_SWITCH_LEVEL_GPS_MIN_SEC 5
#define CONN_CUSTOMER_SET_LEVEL_SUCCESS 0
#define CONN_CUSTOMER_SET_LEVEL_FAILED -1
static struct conn_pwr_plat_info g_plat_info;
static CONN_PWR_EVENT_CB g_event_cb_tbl[CONN_PWR_DRV_MAX];
static int g_drv_status_tbl[CONN_PWR_DRV_MAX];
#ifdef CONN_PWR_LOW_BATTERY_ENABLE
static int g_low_battery_level = LOW_BATTERY_LEVEL_0;
#else
static int g_low_battery_level;
#endif
static int g_max_temp = CONN_PWR_MAX_TEMP_HIGH;
static int g_connsys_temp = CONN_PWR_INVALID_TEMP;
static unsigned int g_customer_level;
static unsigned long long g_radio_last_updated_time[CONN_PWR_DRV_MAX];
/* device node related */
static int gConnPwrMajor = CONN_PWR_DEV_MAJOR;
static struct class *pConnPwrClass;
static struct device *pConnPwrDev;
static struct cdev gConnPwrdev;
#if IS_ENABLED(CONFIG_CONN_PWR_DEBUG)
extern ssize_t conn_pwr_dev_write(struct file *filp, const char __user *buffer, size_t count,
loff_t *f_pos);
#endif
static long conn_pwr_dev_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
#if IS_ENABLED(CONFIG_COMPAT)
static long conn_pwr_dev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
#endif
const struct file_operations gConnPwrDevFops = {
#if IS_ENABLED(CONFIG_CONN_PWR_DEBUG)
.write = conn_pwr_dev_write,
#endif
.unlocked_ioctl = conn_pwr_dev_unlocked_ioctl,
#if IS_ENABLED(CONFIG_COMPAT)
.compat_ioctl = conn_pwr_dev_compat_ioctl,
#endif
};
void conn_pwr_get_local_time(unsigned long long *sec, unsigned long *usec)
{
if (sec != NULL && usec != NULL) {
*sec = local_clock();
*usec = do_div(*sec, 1000000000)/1000;
} else
pr_info("The input parameters error when get local time\n");
}
int conn_pwr_enable(int enable)
{
conn_pwr_core_enable(enable);
return 0;
}
EXPORT_SYMBOL(conn_pwr_enable);
int conn_pwr_send_msg(enum conn_pwr_drv_type drv, enum conn_pwr_msg_type msg, void *data)
{
struct conn_pwr_update_info info = {0};
if (drv < 0 || drv >= CONN_PWR_DRV_MAX || msg < 0 || msg > CONN_PWR_MSG_MAX) {
pr_info("%s, invalid parameter. drv (%d), msg(%d)", __func__, drv, msg);
return -1;
}
if (msg == CONN_PWR_MSG_TEMP_TOO_HIGH) {
if (data != NULL) {
g_connsys_temp = *((int *)data);
pr_info("%s drv:%d, msg: %d, temp: %d\n", __func__, drv, msg,
*((int *)data));
}
info.reason = CONN_PWR_ARB_TEMP_CHECK;
info.drv = drv;
conn_pwr_arbitrate(&info);
} else if (msg == CONN_PWR_MSG_TEMP_RECOVERY) {
if (data != NULL) {
g_connsys_temp = *((int *)data);
pr_info("%s drv:%d, msg: %d, temp: %d\n", __func__, drv, msg,
*((int *)data));
}
info.reason = CONN_PWR_ARB_TEMP_CHECK;
info.drv = drv;
conn_pwr_arbitrate(&info);
} else if (msg == CONN_PWR_MSG_GET_TEMP && data != NULL) {
struct conn_pwr_event_max_temp *d = (struct conn_pwr_event_max_temp *)data;
conn_pwr_get_thermal(d);
pr_info("%s drv:%d, msg: %d, max: %d, recovery: %d\n", __func__, drv, msg,
d->max_temp, d->recovery_temp);
}
if (data == NULL)
pr_info("%s drv:%d, msg: %d\n", __func__, drv, msg);
return 0;
}
EXPORT_SYMBOL(conn_pwr_send_msg);
int conn_pwr_set_max_temp(unsigned long arg)
{
struct conn_pwr_update_info info;
if (g_max_temp == arg || arg > CONN_PWR_MAX_TEMP_HIGH || arg < CONN_PWR_MAX_TEMP_LOW) {
pr_info("%s, max temp is not updated. old(%d), new(%lu)", __func__,
g_max_temp, arg);
return 0;
}
pr_info("%s, max temp is adjusted to %lu from %d\n", __func__, arg, g_max_temp);
g_max_temp = arg;
info.reason = CONN_PWR_ARB_THERMAL;
conn_pwr_arbitrate(&info);
return 0;
}
int conn_pwr_set_battery_level(int level)
{
struct conn_pwr_update_info info;
pr_info("%s level = %d\n", __func__, level);
#if IS_ENABLED(CONFIG_MTK_LOW_BATTERY_POWER_THROTTLING)
if (level < LOW_BATTERY_LEVEL_0 || level > LOW_BATTERY_LEVEL_2) {
pr_info("invalid level %d\n", level);
return -1;
}
#endif
g_low_battery_level = level;
info.reason = CONN_PWR_ARB_LOW_BATTERY;
conn_pwr_arbitrate(&info);
return 0;
}
static long conn_pwr_dev_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
pr_info("[%s] cmd (%d),arg (%ld)\n", __func__, cmd, arg);
switch (cmd) {
case CONN_PWR_IOCTL_SET_MAX_TEMP:
ret = conn_pwr_set_max_temp(arg);
break;
case CONN_PWR_IOCTL_GET_DRV_LEVEL:
conn_pwr_get_drv_level(arg, (enum conn_pwr_low_battery_level *)&ret);
break;
case CONN_PWR_IOCTL_GET_PLAT_LEVEL:
conn_pwr_get_platform_level(arg, (int *)&ret);
break;
default:
break;
}
return ret;
}
#if IS_ENABLED(CONFIG_COMPAT)
static long conn_pwr_dev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
pr_info("[%s] cmd (%d)\n", __func__, cmd);
return conn_pwr_dev_unlocked_ioctl(filp, cmd, arg);
}
#endif
#ifdef CONN_PWR_LOW_BATTERY_ENABLE
static void conn_pwr_low_battery_cb(enum LOW_BATTERY_LEVEL_TAG level)
{
conn_pwr_set_battery_level(level);
}
#endif
int conn_pwr_get_plat_level(enum conn_pwr_plat_type type, int *data)
{
if (data == NULL) {
pr_info("%s: data is NULL.\n", __func__);
return -1;
}
if (type == CONN_PWR_PLAT_LOW_BATTERY)
*data = g_low_battery_level;
else if (type == CONN_PWR_PLAT_THERMAL)
*data = g_max_temp;
else if (type == CONN_PWR_PLAT_CUSTOMER)
*data = g_customer_level;
else {
pr_info("type %d is out of range.\n", type);
return -2;
}
pr_info("%s ,type = %d, ret = %d\n", __func__, type, *data);
return 0;
}
int conn_pwr_set_customer_level(enum conn_pwr_drv_type type, enum conn_pwr_low_battery_level level)
{
struct conn_pwr_update_info info = {0};
int i;
int updated = 0;
unsigned long long sec;
unsigned long usec;
int ret = CONN_CUSTOMER_SET_LEVEL_SUCCESS;
unsigned int inactive_time = 0;
if (type < CONN_PRW_DRV_ALL || type >= CONN_PWR_DRV_MAX || level < CONN_PWR_THR_LV_0 ||
level >= CONN_PWR_LOW_BATTERY_MAX) {
pr_info("%s, invalid parameter, type = %d, level = %d\n", __func__, type, level);
return CONN_CUSTOMER_SET_LEVEL_FAILED;
}
conn_pwr_get_local_time(&sec, &usec);
if (type == CONN_PRW_DRV_ALL) {
for (i = 0; i < CONN_PWR_DRV_MAX; i++) {
if (i == CONN_PWR_DRV_GPS)
inactive_time = CONN_PWR_SWITCH_LEVEL_GPS_MIN_SEC;
else
inactive_time = CONN_PWR_SWITCH_LEVEL_MIN_SEC;
if (sec > (g_radio_last_updated_time[i] + inactive_time) ||
g_radio_last_updated_time[i] > sec) {
updated = 1;
} else {
updated = 0;
break;
}
}
if (updated) {
CONN_PWR_SET_CUSTOMER_POWER_LEVEL(g_customer_level, CONN_PWR_DRV_BT, level);
CONN_PWR_SET_CUSTOMER_POWER_LEVEL(g_customer_level, CONN_PWR_DRV_FM, level);
CONN_PWR_SET_CUSTOMER_POWER_LEVEL(g_customer_level, CONN_PWR_DRV_GPS,
level);
CONN_PWR_SET_CUSTOMER_POWER_LEVEL(g_customer_level, CONN_PWR_DRV_WIFI,
level);
g_radio_last_updated_time[CONN_PWR_DRV_BT] = sec;
g_radio_last_updated_time[CONN_PWR_DRV_FM] = sec;
g_radio_last_updated_time[CONN_PWR_DRV_GPS] = sec;
g_radio_last_updated_time[CONN_PWR_DRV_WIFI] = sec;
}
} else {
if (type == CONN_PWR_DRV_GPS)
inactive_time = CONN_PWR_SWITCH_LEVEL_GPS_MIN_SEC;
else
inactive_time = CONN_PWR_SWITCH_LEVEL_MIN_SEC;
if (sec > (g_radio_last_updated_time[type] + inactive_time) ||
g_radio_last_updated_time[type] > sec) {
g_radio_last_updated_time[type] = sec;
updated = 1;
}
if (updated)
CONN_PWR_SET_CUSTOMER_POWER_LEVEL(g_customer_level, type, level);
}
if (updated) {
info.reason = CONN_PWR_ARB_CUSTOMER;
info.drv = type;
conn_pwr_arbitrate(&info);
}
pr_info("%s, type = %d, level = %d, customer_level = %d, updated = %d\n", __func__,
type, level, g_customer_level, updated);
if (updated == 0) {
pr_info("%s, Set Level failed within %d, %llu (%llu, %llu, %llu, %llu)\n", __func__,
CONN_PWR_SWITCH_LEVEL_MIN_SEC, sec,
g_radio_last_updated_time[CONN_PWR_DRV_BT],
g_radio_last_updated_time[CONN_PWR_DRV_FM],
g_radio_last_updated_time[CONN_PWR_DRV_GPS],
g_radio_last_updated_time[CONN_PWR_DRV_WIFI]);
ret = CONN_CUSTOMER_SET_LEVEL_FAILED;
}
return ret;
}
EXPORT_SYMBOL(conn_pwr_set_customer_level);
int conn_pwr_get_chip_id(void)
{
return g_plat_info.chip_id;
}
int conn_pwr_get_adie_id(void)
{
return g_plat_info.adie_id;
}
int conn_pwr_get_temp(int *temp, int cached)
{
int ret = 0;
if ((g_connsys_temp == CONN_PWR_INVALID_TEMP || cached == 0) && g_plat_info.get_temp) {
ret = (*g_plat_info.get_temp)(temp);
if (ret == 0)
g_connsys_temp = *temp;
} else if (cached > 0 && g_connsys_temp != CONN_PWR_INVALID_TEMP) {
*temp = g_connsys_temp;
} else {
ret = -1;
pr_info("%s, get_temp is NULL/n", __func__);
}
pr_info("%s, ret = %d, temp = %d, cached = %d\n", __func__, ret, *temp, cached);
return ret;
}
int conn_pwr_notify_event(enum conn_pwr_drv_type drv, enum conn_pwr_event_type event, void *data)
{
int ret;
struct conn_pwr_event_max_temp *d;
if (drv < 0 || drv >= CONN_PWR_DRV_MAX || event < 0 || event >= CONN_PWR_EVENT_MAX) {
pr_info("drv %d or event %d is out of range.\n", drv, event);
return -1;
}
if (g_event_cb_tbl[drv] == NULL) {
pr_info("event cb is not registered.\n", drv);
return -2;
}
ret = (*g_event_cb_tbl[drv])(event, data);
if (event == CONN_PWR_EVENT_LEVEL)
pr_info("%s, drv = %d, level = %d, ret = %d\n", __func__, drv, *((int *)data), ret);
else if (event == CONN_PWR_EVENT_MAX_TEMP && data != NULL) {
d = (struct conn_pwr_event_max_temp *)data;
pr_info("%s, drv = %d, max_t = %d, rcv_t = %d, ret = %d\n", __func__,
drv, d->max_temp, d->recovery_temp, ret);
} else {
pr_info("invalid. event = %d, data = %d\n", event, drv);
return -3;
}
return ret;
}
int conn_pwr_get_drv_status(enum conn_pwr_drv_type type)
{
if (type < 0 || type >= CONN_PWR_DRV_MAX) {
pr_info("type %d is out of range.\n", type);
return -1;
}
return g_drv_status_tbl[type];
}
static int conn_pwr_set_drv_status(enum conn_pwr_drv_type type, enum conn_pwr_drv_status status)
{
struct conn_pwr_update_info info;
pr_info("%s, type = %d, status = %x\n", __func__, type, status);
if (type < 0 || type >= CONN_PWR_DRV_MAX) {
pr_info("type %d is out of range.\n", type);
return -1;
}
if (status != CONN_PWR_DRV_STATUS_OFF && status != CONN_PWR_DRV_STATUS_ON) {
pr_info("status %d is invalid.\n", status);
return -2;
}
if (g_drv_status_tbl[type] == status) {
pr_info("status is not changed.\n");
return 0;
}
g_drv_status_tbl[type] = status;
info.reason = CONN_PWR_ARB_SUBSYS_ON_OFF;
info.drv = type;
info.status = status;
conn_pwr_arbitrate(&info);
return 0;
}
int conn_pwr_drv_pre_on(enum conn_pwr_drv_type type, enum conn_pwr_low_battery_level *level)
{
int ret;
ret = conn_pwr_set_drv_status(type, CONN_PWR_DRV_STATUS_ON);
if (ret == 0)
ret = conn_pwr_get_drv_level(type, level);
pr_info("%s, ret = %d, type = %d, level = %d\n", __func__, ret, type, *level);
return ret;
}
EXPORT_SYMBOL(conn_pwr_drv_pre_on);
int conn_pwr_drv_post_off(enum conn_pwr_drv_type type)
{
pr_info("%s type = %d", __func__, type);
return conn_pwr_set_drv_status(type, CONN_PWR_DRV_STATUS_OFF);
}
EXPORT_SYMBOL(conn_pwr_drv_post_off);
int conn_pwr_register_event_cb(enum conn_pwr_drv_type type,
CONN_PWR_EVENT_CB cb)
{
pr_info("%s, type = %d, cb = %p\n", __func__, type, cb);
if (type < 0 || type >= CONN_PWR_DRV_MAX) {
pr_info("type %d is out of range.\n", type);
return -1;
}
g_event_cb_tbl[type] = cb;
return 0;
}
EXPORT_SYMBOL(conn_pwr_register_event_cb);
static int conn_pwr_dev_init(void)
{
dev_t dev_id = MKDEV(gConnPwrMajor, 0);
int ret = 0;
ret = register_chrdev_region(dev_id, CONN_PWR_DEV_NUM,
CONN_PWR_DRVIER_NAME);
if (ret) {
pr_info("fail to register chrdev.(%d)\n", ret);
return -1;
}
cdev_init(&gConnPwrdev, &gConnPwrDevFops);
gConnPwrdev.owner = THIS_MODULE;
ret = cdev_add(&gConnPwrdev, dev_id, CONN_PWR_DEV_NUM);
if (ret) {
pr_info("cdev_add() fails (%d)\n", ret);
goto err1;
}
pConnPwrClass = class_create(THIS_MODULE, CONN_PWR_DEVICE_NAME);
if (IS_ERR(pConnPwrClass)) {
pr_info("class create fail, error code(%ld)\n",
PTR_ERR(pConnPwrClass));
goto err2;
}
pConnPwrDev = device_create(pConnPwrClass, NULL, dev_id,
NULL, CONN_PWR_DEVICE_NAME);
if (IS_ERR(pConnPwrDev)) {
pr_info("device create fail, error code(%ld)\n",
PTR_ERR(pConnPwrDev));
goto err3;
}
return 0;
err3:
pr_info("[%s] err3", __func__);
if (pConnPwrClass) {
class_destroy(pConnPwrClass);
pConnPwrClass = NULL;
}
err2:
pr_info("[%s] err2", __func__);
cdev_del(&gConnPwrdev);
err1:
pr_info("[%s] err1", __func__);
unregister_chrdev_region(dev_id, CONN_PWR_DEV_NUM);
return -1;
}
static int conn_pwr_dev_deinit(void)
{
dev_t dev_id = MKDEV(gConnPwrMajor, 0);
if (pConnPwrDev) {
device_destroy(pConnPwrClass, dev_id);
pConnPwrDev = NULL;
}
if (pConnPwrClass) {
class_destroy(pConnPwrClass);
pConnPwrClass = NULL;
}
cdev_del(&gConnPwrdev);
unregister_chrdev_region(dev_id, CONN_PWR_DEV_NUM);
return 0;
}
int conn_pwr_init(struct conn_pwr_plat_info *data)
{
pr_info("%s\n", __func__);
if (data == NULL) {
pr_info("data is NULL\n");
return -1;
}
if (data->chip_id == 0 || data->get_temp == NULL) {
pr_info("%s, init data is invalid: chip_id = %d, get_temp = %p\n",
__func__, data->chip_id, data->get_temp);
return -2;
}
memcpy(&g_plat_info, data, sizeof(struct conn_pwr_plat_info));
memset(g_event_cb_tbl, 0, sizeof(g_event_cb_tbl));
memset(g_drv_status_tbl, 0, sizeof(g_drv_status_tbl));
conn_pwr_core_init();
#ifdef CONN_PWR_LOW_BATTERY_ENABLE
register_low_battery_notify(&conn_pwr_low_battery_cb, LOW_BATTERY_PRIO_WIFI);
#endif
conn_pwr_dev_init();
return 0;
}
EXPORT_SYMBOL(conn_pwr_init);
int conn_pwr_deinit(void)
{
pr_info("%s", __func__);
conn_pwr_dev_deinit();
return 0;
}
EXPORT_SYMBOL(conn_pwr_deinit);
int conn_pwr_resume(void)
{
conn_pwr_core_resume();
return 0;
}
EXPORT_SYMBOL(conn_pwr_resume);
int conn_pwr_suspend(void)
{
conn_pwr_core_suspend();
return 0;
}
EXPORT_SYMBOL(conn_pwr_suspend);