lineage_kernel_xcoverpro/drivers/samsung/misc/sec_nad_balancer.c

623 lines
15 KiB
C
Executable File

/* sec_nad_balancer.c
*
* NAD Balancer Driver
*
* Copyright (C) 2017 Samsung Electronics
*
* Hyeokseon Yu <hyeokseon.yu@samsung.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/sec_nad_balancer.h>
#include <linux/sec_ext.h>
#define NAD_PRINT(format, ...) pr_info("[NAD_BALANCER] " format, ##__VA_ARGS__)
#define DEBUG_NAD_BALANCER
#if defined(CONFIG_SEC_FACTORY)
#ifdef CONFIG_OF
static int parse_qos_data(struct device *dev,
struct nad_balancer_platform_data *pdata,
struct device_node *np)
{
struct device_node *cnp;
struct nad_balancer_pm_qos *cqos;
int ncount = 0;
int i;
u32 freq_item;
for_each_child_of_node(np, cnp) {
cqos = &pdata->qos_items[ncount];
cqos->desc = of_get_property(cnp, "qos,label", NULL);
if (of_property_read_u32(cnp, "qos,delay_time", &cqos->delay_time)) {
dev_err(dev, "Failed to get delay time: node not exist\n");
return -EINVAL;
}
if (of_property_read_u32(cnp, "qos,table_size", &cqos->table_size)) {
dev_err(dev, "Failed to get table size: node not exist\n");
return -EINVAL;
}
if (!cqos->tables) {
cqos->tables = devm_kzalloc(dev, sizeof(struct freq_table) * cqos->table_size,
GFP_KERNEL);
if (!cqos->tables) {
dev_err(dev, "Failed to allocate memory of freq_table\n");
return -ENOMEM;
}
}
for (i = 0; i < cqos->table_size; i++) {
if (of_property_read_u32_index(cnp, "qos,table", i, &freq_item))
return -EINVAL;
cqos->tables[i].freq = freq_item;
}
ncount++;
}
return 0;
}
static int parse_sleep_data(struct device *dev,
struct nad_balancer_platform_data *pdata,
struct device_node *np)
{
struct nad_balancer_sleep_info *csleep;
csleep = pdata->sleep_items;
if (of_property_read_u32(np, "sleep,suspend_threshold", &csleep->suspend_threshold)) {
dev_err(dev, "Failed to get suspend time: node not exist\n");
return -EINVAL;
}
if (of_property_read_u32(np, "sleep,resume_threshold", &csleep->resume_threshold)) {
dev_err(dev, "Failed to get resume time: node not exist\n");
return -EINVAL;
}
return 0;
}
static int nad_balancer_parse_dt(struct device *dev)
{
struct nad_balancer_platform_data *pdata = dev->platform_data;
struct device_node *np;
struct device_node *qos_np;
struct device_node *sleep_np;
np = dev->of_node;
if (of_property_read_u32(np, "nad_balancer,timeout", &pdata->timeout)) {
dev_err(dev, "error to read timeout value\n");
return -EINVAL;
}
pdata->nItem = of_get_child_count(np);
if (!pdata->nItem) {
dev_err(dev, "There are no items\n");
return -ENODEV;
}
qos_np = of_find_node_by_name(np, "qos");
pdata->nQos = of_get_child_count(qos_np);
pdata->qos_items = devm_kzalloc(dev,
sizeof(struct nad_balancer_pm_qos) * pdata->nQos, GFP_KERNEL);
if (!pdata->qos_items)
return -ENOMEM;
sleep_np = of_find_node_by_name(np, "sleep");
pdata->nSleep = of_get_child_count(sleep_np);
pdata->sleep_items = devm_kzalloc(dev,
sizeof(struct nad_balancer_sleep_info), GFP_KERNEL);
if (!pdata->sleep_items)
return -ENOMEM;
if (qos_np)
parse_qos_data(dev, pdata, qos_np);
if (sleep_np)
parse_sleep_data(dev, pdata, sleep_np);
return 0;
}
#endif
void report_sleep_info(struct device *dev, pm_message_t state,
unsigned long long usec)
{
struct nad_balancer_info *pinfo;
struct nad_balancer_sleep_info *psleep;
if (!sleep_test_enable)
return;
pinfo = dev_get_drvdata(sec_nad_balancer);
psleep = pinfo->pdata->sleep_items;
switch (state.event) {
case PM_EVENT_SUSPEND:
if ((usec / USEC_PER_MSEC) > psleep->suspend_threshold) {
panic("NAD_BALANCER: over suspend time! dev:%s, %lld.%03lld msecs\n"
,dev_name(dev), usec / USEC_PER_MSEC, usec % USEC_PER_MSEC);
}
break;
case PM_EVENT_RESUME:
if ((usec / USEC_PER_MSEC) > psleep->resume_threshold) {
panic("NAD_BALANCER: over resume time! dev:%s, %lld.%03lld msecs\n"
, dev_name(dev), usec / USEC_PER_MSEC, usec % USEC_PER_MSEC);
}
break;
default:
break;
}
}
static bool sec_nad_balancer_enabled(void)
{
return nad_balancer_enable;
}
static void sec_nad_balancer_enable(void)
{
mutex_lock(&nad_balancer_lock);
nad_balancer_enable = true;
mutex_unlock(&nad_balancer_lock);
}
static void sec_nad_balancer_disable(void)
{
mutex_lock(&nad_balancer_lock);
nad_balancer_enable = false;
mutex_unlock(&nad_balancer_lock);
}
static void sec_nad_balancer_timeout_work(struct work_struct *work)
{
NAD_PRINT("exprired nad qos work.\n");
sec_nad_balancer_disable();
}
static void update_temperature(struct nad_balancer_pm_qos *pqos, int type)
{
#if !defined(CONFIG_SEC_BOOTSTAT)
return;
#endif
int temp[MAX_TMU_COUNT];
int i;
if (type == UPDATE_FIRST_TEMPERATURE) {
sec_bootstat_get_thermal(pqos->temperature);
NAD_PRINT("first temperature ");
for (i = 0; i < MAX_TMU_COUNT; i++)
pr_info("[%d]", pqos->temperature[i]);
pr_info("\n");
} else if (type == UPDATE_CONTI_TEMPERATURE) {
sec_bootstat_get_thermal(temp);
for (i = 0; i < MAX_TMU_COUNT; i++) {
if (pqos->temperature[i] != max(pqos->temperature[i], temp[i])) {
NAD_PRINT("update temperature[%d] prev[%d] new [%d]\n",
i, pqos->temperature[i], temp[i]);
pqos->temperature[i] = temp[i];
}
}
} else if (type == UPDATE_FINAL_TEMPERATURE) {
sec_bootstat_get_thermal(temp);
for (i = 0; i < MAX_TMU_COUNT; i++) {
if (pqos->temperature[i] != max(pqos->temperature[i], temp[i])) {
NAD_PRINT("update temperature[%d] prev[%d] new [%d]\n",
i, pqos->temperature[i], temp[i]);
pqos->temperature[i] = temp[i];
}
}
/* print out final temperature */
NAD_PRINT("final temperature [MNGS][APOLLO][GPU][ISP] = ");
for (i = 0; i < MAX_TMU_COUNT; i++)
pr_info("[%d]", pqos->temperature[i]);
pr_info("\n");
}
}
static int on_run(void *data)
{
struct nad_balancer_pm_qos *pqos = data;
int delay = pqos->delay_time;
int policy = pqos->policy;
int idx = 0;
int req_lit = 0;
int req_big = 0;
int req_mif = 0;
int temp_check_period = 0;
NAD_PRINT("%s start.\n", pqos->desc);
while (!kthread_should_stop()) {
if (!sec_nad_balancer_enabled()) {
NAD_PRINT("%s no more working nad balancer qos thread.\n", pqos->desc);
if (!strncmp(pqos->desc, "LIT", 3) && req_lit == 1)
REMOVE_PM_QOS(&pqos->lit_qos);
if (!strncmp(pqos->desc, "BIG", 3) && req_big == 1)
REMOVE_PM_QOS(&pqos->big_qos);
if (!strncmp(pqos->desc, "MIF", 3) && req_mif == 1)
REMOVE_PM_QOS(&pqos->mif_qos);
/* update final temperature */
if (!strncmp(pqos->desc, "LIT", 3))
update_temperature(pqos, UPDATE_FINAL_TEMPERATURE);
break;
}
idx = prandom_u32() % pqos->table_size;
/* limit qos cl0 min throughput */
if (!strncmp(pqos->desc, "LIT", 3)) {
if (req_lit == 0) {
UPDATE_PM_QOS(&pqos->lit_qos, policy ?
PM_QOS_CLUSTER0_FREQ_MAX : PM_QOS_CLUSTER0_FREQ_MIN,
pqos->tables[idx].freq);
req_lit = 1;
} else {
REMOVE_PM_QOS(&pqos->lit_qos);
req_lit = 0;
}
temp_check_period++;
if (temp_check_period % 50 == 0)
update_temperature(pqos, UPDATE_CONTI_TEMPERATURE);
}
/* limit qos cl1 max throughput */
if (!strncmp(pqos->desc, "BIG", 3)) {
if (req_big == 0) {
UPDATE_PM_QOS(&pqos->big_qos, policy ?
PM_QOS_CLUSTER1_FREQ_MAX : PM_QOS_CLUSTER1_FREQ_MIN,
pqos->tables[idx].freq);
req_big = 1;
} else {
REMOVE_PM_QOS(&pqos->big_qos);
req_big = 0;
}
}
/* limit bus max throughput */
if (!strncmp(pqos->desc, "MIF", 3)) {
if (req_mif == 0) {
UPDATE_PM_QOS(&pqos->mif_qos, policy ?
PM_QOS_BUS_THROUGHPUT_MAX : PM_QOS_BUS_THROUGHPUT,
pqos->tables[idx].freq);
req_mif = 1;
} else {
REMOVE_PM_QOS(&pqos->mif_qos);
req_mif = 0;
}
}
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout_interruptible(msecs_to_jiffies(delay));
set_current_state(TASK_RUNNING);
}
NAD_PRINT("%s thread done.\n", pqos->desc);
return 0;
}
static int get_policy_num(char *str)
{
if (!strncmp(str, "max", 3))
return NAD_MAX_FREQ;
else
return NAD_MIN_FREQ;
}
static ssize_t store_nad_balancer(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct nad_balancer_info *info = dev_get_drvdata(dev);
struct nad_balancer_pm_qos *pqos;
char cmd_temp[NAD_BUFF_SIZE];
char nad_cmd[BALANCER_CMD][NAD_BUFF_SIZE];
char *nad_ptr, *string;
int i, j, idx = 0, ret = -1, expire_time;
unsigned int len = 0;
/* Copy buf to nad cmd */
len = (unsigned int)min(count, sizeof(cmd_temp) - 1);
strncpy(cmd_temp, buf, len);
cmd_temp[len] = '\0';
string = cmd_temp;
/* Parse AT CMD */
while (idx < BALANCER_CMD) {
nad_ptr = strsep(&string, ",");
if (nad_ptr == NULL || strlen(nad_ptr) >= NAD_BUFF_SIZE) {
NAD_PRINT(" %s: invalid input\n",__func__);
return -EINVAL;
}
strcpy(nad_cmd[idx++], nad_ptr);
}
/* Get thread expire time */
ret = sscanf(nad_cmd[1], "%d\n", &expire_time);
if (ret != 1)
return -EINVAL;
#if defined(DEBUG_NAD_BALANCER)
NAD_PRINT("cmd : %s, expire_time : %d\n", nad_cmd[0], expire_time);
idx = 0;
while (idx < BALANCER_CMD) {
NAD_PRINT("cmd[%d] : %s\n", idx, nad_cmd[idx]);
idx++;
}
for (i = 0; i < info->pdata->nQos; i++) {
pqos = &info->pdata->qos_items[i];
NAD_PRINT("%s freq table\n", pqos->desc);
for(j = 0; j < pqos->table_size; j++) {
NAD_PRINT("%d\n", pqos->tables[j].freq);
}
}
#endif
sleep_test_enable = true;
if (!strncmp(buf, "start", 5)) {
/* Enable qos thread */
if (sec_nad_balancer_enabled()) {
NAD_PRINT("already running skip start nad balancer!\n");
return count;
}
sec_nad_balancer_enable();
for (i = 0; i < info->pdata->nQos; i++) {
pqos = &info->pdata->qos_items[i];
if (i == 0)
update_temperature(pqos, UPDATE_FIRST_TEMPERATURE);
/* set qos policy */
info->pdata->qos_items[i].policy =
get_policy_num(nad_cmd[i + 2]);
#if defined(DEBUG_NAD_BALANCER)
NAD_PRINT("%s thread use %s qos policy\n",
info->pdata->qos_items[i].desc, nad_cmd[i + 2]);
#endif
info->pdata->qos_items[i].thread = kthread_run(on_run, pqos, "nad_balancer_qos_thread");
if (IS_ERR_OR_NULL(info->pdata->qos_items[i].thread)) {
NAD_PRINT("Failed in creation of thread.\n");
return count;
}
}
/* Trigger delayed workqueue */
schedule_delayed_work(&info->sec_nad_balancer_work, HZ * expire_time);
}
return count;
}
static ssize_t show_nad_balancer(struct device *dev,
struct device_attribute *attr,
char *buf)
{
NAD_PRINT("%s\n", __func__);
sec_nad_balancer_disable();
sleep_test_enable = false;
return sprintf(buf, "OK\n");
}
static DEVICE_ATTR(balancer, 0644, show_nad_balancer, store_nad_balancer);
static ssize_t store_nad_status(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
char *envp[2] = {"NAD_BALANCER_UEVENT", NULL};
NAD_PRINT("%s\n", __func__);
kobject_uevent_env(&sec_nad_balancer->kobj, KOBJ_CHANGE, envp);
return count;
}
static ssize_t show_nad_status(struct device *dev,
struct device_attribute *attr,
char *buf)
{
NAD_PRINT("%s\n", __func__);
return sprintf(buf, "OK\n");
}
static DEVICE_ATTR(status, 0644, show_nad_status, store_nad_status);
static ssize_t store_nad_timeout(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
NAD_PRINT("%s\n", __func__);
return count;
}
static ssize_t show_nad_timeout(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct nad_balancer_info *pinfo = dev_get_drvdata(dev);
int timeout_val = pinfo->pdata->timeout;
NAD_PRINT("%s: timeout val (%d)\n", __func__, timeout_val);
return sprintf(buf, "%d\n", timeout_val);
}
static DEVICE_ATTR(timeout, 0644, show_nad_timeout, store_nad_timeout);
static const struct dev_pm_ops sec_nad_balancer_pm = {
.prepare = sec_nad_balancer_prepare,
.resume = sec_nad_balancer_resume,
};
#ifdef CONFIG_OF
static const struct of_device_id sec_nad_balancer_dt_match[] = {
{ .compatible = "samsung,sec_nad_balancer" },
{ }
};
#endif
static struct platform_driver sec_nad_balancer_driver = {
.probe = sec_nad_balancer_probe,
.remove = sec_nad_balancer_remove,
.driver = {
.name = "sec_nad_balancer",
.owner = THIS_MODULE,
#if defined(CONFIG_PM)
.pm = &sec_nad_balancer_pm,
#endif
#if CONFIG_OF
.of_match_table = of_match_ptr(sec_nad_balancer_dt_match),
#endif
},
};
static int sec_nad_balancer_prepare(struct device *dev)
{
return 0;
}
static int sec_nad_balancer_resume(struct device *dev)
{
return 0;
}
static int sec_nad_balancer_remove(struct platform_device *pdev)
{
return 0;
}
static int sec_nad_balancer_probe(struct platform_device *pdev)
{
struct nad_balancer_platform_data *pdata;
struct nad_balancer_info *pinfo;
int ret = 0;
NAD_PRINT("%s\n", __func__);
if (pdev->dev.of_node) {
pdata = devm_kzalloc(&pdev->dev,
sizeof(struct nad_balancer_platform_data), GFP_KERNEL);
if (!pdata) {
dev_err(&pdev->dev, "Failed to allocate platform data\n");
return -ENOMEM;
}
pdev->dev.platform_data = pdata;
ret = nad_balancer_parse_dt(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Failed to parse dt data\n");
return ret;
}
pr_info("%s: parse dt done\n", __func__);
} else {
pdata = pdev->dev.platform_data;
}
if (!pdata) {
dev_err(&pdev->dev, "There are no platform data\n");
return -EINVAL;
}
if (!pdata->nItem) {
dev_err(&pdev->dev, "There are no devices\n");
return -EINVAL;
}
pinfo = devm_kzalloc(&pdev->dev, sizeof(struct nad_balancer_info), GFP_KERNEL);
if (!pinfo)
return -ENOMEM;
pinfo->dev = sec_device_create(pinfo, "sec_nad_balancer");
if (IS_ERR(sec_nad_balancer)) {
pr_err("%s Failed to create device(sec_nad_balancer)!\n", __func__);
ret = -ENODEV;
goto out;
}
ret = device_create_file(pinfo->dev, &dev_attr_balancer);
if (ret) {
pr_err("%s: Failed to create device file\n", __func__);
goto err_create_nad_balancer_sysfs;
}
ret = device_create_file(pinfo->dev, &dev_attr_status);
if (ret) {
pr_err("%s: Failed to create device file\n", __func__);
goto err_create_nad_balancer_sysfs;
}
ret = device_create_file(pinfo->dev, &dev_attr_timeout);
if (ret) {
pr_err("%s: Failed to create device file\n", __func__);
goto err_create_nad_balancer_sysfs;
}
INIT_DELAYED_WORK(&pinfo->sec_nad_balancer_work,
sec_nad_balancer_timeout_work);
pinfo->pdata = pdata;
sec_nad_balancer = pinfo->dev;
platform_set_drvdata(pdev, pinfo);
return ret;
err_create_nad_balancer_sysfs:
sec_device_destroy(sec_nad_balancer->devt);
out:
if (!pinfo)
devm_kfree(&pdev->dev, pinfo);
if (!pdata)
devm_kfree(&pdev->dev, pdata);
return ret;
}
#endif
static int __init sec_nad_balancer_init(void)
{
#if defined(CONFIG_SEC_FACTORY)
NAD_PRINT("%s\n", __func__);
return platform_driver_register(&sec_nad_balancer_driver);
#else
NAD_PRINT("Not support NAD balancer.\n");
return 0;
#endif
}
static void __exit sec_nad_balancer_exit(void)
{
#if defined(CONFIG_SEC_FACTORY)
NAD_PRINT("%s\n", __func__);
return platform_driver_unregister(&sec_nad_balancer_driver);
#endif
}
module_init(sec_nad_balancer_init);
module_exit(sec_nad_balancer_exit);
MODULE_DESCRIPTION("Samsung NAD balancer Driver");
MODULE_AUTHOR("Samsung Electronics");
MODULE_LICENSE("GPL");