blob: 4355febc045695f376f78db4e565e65f33ec1835 [file] [log] [blame]
/*
* sec_argos.c
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd
* http://www.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.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/pm_qos.h>
#include <linux/reboot.h>
#include <linux/of.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/cpumask.h>
#include <linux/interrupt.h>
#include <linux/sec_argos.h>
#include <linux/ologk.h>
#if defined(CONFIG_SCHED_EMS)
#include <linux/ems.h>
static struct gb_qos_request gb_req = {
.name = "argos_global_boost",
};
#endif
#define ARGOS_NAME "argos"
#define TYPE_SHIFT 4
#define TYPE_MASK_BIT ((1 << TYPE_SHIFT) - 1)
static DEFINE_SPINLOCK(argos_irq_lock);
static DEFINE_SPINLOCK(argos_task_lock);
enum {
THRESHOLD,
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
BIG_MIN_FREQ,
BIG_MAX_FREQ,
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
MID_MIN_FREQ,
MID_MAX_FREQ,
#endif
LIT_MIN_FREQ,
LIT_MAX_FREQ,
MIF_FREQ,
INT_FREQ,
TASK_AFFINITY_EN,
IRQ_AFFINITY_EN,
HMP_BOOST_EN,
ITEM_MAX,
};
struct boost_table {
unsigned int items[ITEM_MAX];
};
struct argos_task_affinity {
struct task_struct *p;
struct cpumask *affinity_cpu_mask;
struct cpumask *default_cpu_mask;
struct list_head entry;
};
struct argos_irq_affinity {
unsigned int irq;
struct cpumask *affinity_cpu_mask;
struct cpumask *default_cpu_mask;
struct list_head entry;
};
struct argos_pm_qos {
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
struct pm_qos_request big_min_qos_req;
struct pm_qos_request big_max_qos_req;
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
struct pm_qos_request mid_min_qos_req;
struct pm_qos_request mid_max_qos_req;
#endif
struct pm_qos_request lit_min_qos_req;
struct pm_qos_request lit_max_qos_req;
struct pm_qos_request mif_qos_req;
struct pm_qos_request int_qos_req;
struct pm_qos_request hotplug_min_qos_req;
};
struct argos {
const char *desc;
struct platform_device *pdev;
struct boost_table *tables;
int ntables;
int prev_level;
struct argos_pm_qos *qos;
struct list_head task_affinity_list;
bool task_hotplug_disable;
struct list_head irq_affinity_list;
bool irq_hotplug_disable;
bool hmpboost_enable;
bool slowdown;
bool argos_block;
struct blocking_notifier_head argos_notifier;
/* protect prev_level, qos, task/irq_hotplug_disable, hmpboost_enable */
struct mutex level_mutex;
};
struct argos_platform_data {
struct argos *devices;
int ndevice;
struct notifier_block pm_qos_nfb;
};
static struct argos_platform_data *argos_pdata;
static inline void UPDATE_PM_QOS(struct pm_qos_request *req, int class_id, int arg)
{
if (arg) {
if (pm_qos_request_active(req))
pm_qos_update_request(req, arg);
else
pm_qos_add_request(req, class_id, arg);
}
}
static inline void REMOVE_PM_QOS(struct pm_qos_request *req)
{
if (pm_qos_request_active(req))
pm_qos_remove_request(req);
}
static int argos_find_index(const char *label)
{
int i;
int dev_num = -1;
if (!argos_pdata) {
pr_err("%s argos not initialized\n", __func__);
return -1;
}
for (i = 0; i < argos_pdata->ndevice; i++)
if (strcmp(argos_pdata->devices[i].desc, label) == 0)
dev_num = i;
return dev_num;
}
int sec_argos_register_notifier(struct notifier_block *n, char *label)
{
struct blocking_notifier_head *cnotifier;
int dev_num;
dev_num = argos_find_index(label);
if (dev_num < 0) {
pr_err("%s: No match found for label: %d", __func__, dev_num);
return -ENODEV;
}
cnotifier = &argos_pdata->devices[dev_num].argos_notifier;
if (!cnotifier) {
pr_err("%s argos notifier not found(dev_num:%d)\n", __func__, dev_num);
return -ENXIO;
}
pr_info("%s: %pf(dev_num:%d)\n", __func__, n->notifier_call, dev_num);
return blocking_notifier_chain_register(cnotifier, n);
}
EXPORT_SYMBOL(sec_argos_register_notifier);
int sec_argos_unregister_notifier(struct notifier_block *n, char *label)
{
struct blocking_notifier_head *cnotifier;
int dev_num;
dev_num = argos_find_index(label);
if (dev_num < 0) {
pr_err("%s: No match found for label: %d", __func__, dev_num);
return -ENODEV;
}
cnotifier = &argos_pdata->devices[dev_num].argos_notifier;
if (!cnotifier) {
pr_err("%s argos notifier not found(dev_num:%d)\n", __func__, dev_num);
return -ENXIO;
}
pr_info("%s: %pf(dev_num:%d)\n", __func__, n->notifier_call, dev_num);
return blocking_notifier_chain_unregister(cnotifier, n);
}
EXPORT_SYMBOL(sec_argos_unregister_notifier);
static int argos_task_affinity_setup(struct task_struct *p, int dev_num,
struct cpumask *affinity_cpu_mask,
struct cpumask *default_cpu_mask)
{
struct argos_task_affinity *this;
struct list_head *head;
if (!argos_pdata) {
pr_err("%s argos not initialized\n", __func__);
return -ENXIO;
}
if (dev_num < 0 || dev_num >= argos_pdata->ndevice) {
pr_err("%s dev_num:%d should be dev_num:0 ~ %d in boundary\n",
__func__, dev_num, argos_pdata->ndevice - 1);
return -EINVAL;
}
head = &argos_pdata->devices[dev_num].task_affinity_list;
this = kzalloc(sizeof(*this), GFP_ATOMIC);
if (!this)
return -ENOMEM;
this->p = p;
this->affinity_cpu_mask = affinity_cpu_mask;
this->default_cpu_mask = default_cpu_mask;
spin_lock(&argos_task_lock);
list_add(&this->entry, head);
spin_unlock(&argos_task_lock);
return 0;
}
int argos_task_affinity_setup_label(struct task_struct *p, const char *label,
struct cpumask *affinity_cpu_mask,
struct cpumask *default_cpu_mask)
{
int dev_num;
dev_num = argos_find_index(label);
return argos_task_affinity_setup(p, dev_num, affinity_cpu_mask,
default_cpu_mask);
}
static int argos_irq_affinity_setup(unsigned int irq, int dev_num,
struct cpumask *affinity_cpu_mask,
struct cpumask *default_cpu_mask)
{
struct argos_irq_affinity *this;
struct list_head *head;
if (!argos_pdata) {
pr_err("%s argos not initialized\n", __func__);
return -ENXIO;
}
if (dev_num < 0 || dev_num >= argos_pdata->ndevice) {
pr_err("%s dev_num:%d should be dev_num:0 ~ %d in boundary\n",
__func__, dev_num, argos_pdata->ndevice - 1);
return -EINVAL;
}
head = &argos_pdata->devices[dev_num].irq_affinity_list;
this = kzalloc(sizeof(*this), GFP_ATOMIC);
if (!this)
return -ENOMEM;
this->irq = irq;
this->affinity_cpu_mask = affinity_cpu_mask;
this->default_cpu_mask = default_cpu_mask;
spin_lock(&argos_irq_lock);
list_add(&this->entry, head);
spin_unlock(&argos_irq_lock);
return 0;
}
int argos_irq_affinity_setup_label(unsigned int irq, const char *label,
struct cpumask *affinity_cpu_mask,
struct cpumask *default_cpu_mask)
{
int dev_num;
dev_num = argos_find_index(label);
return argos_irq_affinity_setup(irq, dev_num, affinity_cpu_mask,
default_cpu_mask);
}
int argos_task_affinity_apply(int dev_num, bool enable)
{
struct argos_task_affinity *this;
struct list_head *head;
int result = 0;
struct cpumask *mask;
bool *hotplug_disable;
struct pm_qos_request *hotplug_min_qos_req;
head = &argos_pdata->devices[dev_num].task_affinity_list;
hotplug_disable = &argos_pdata->devices[dev_num].task_hotplug_disable;
hotplug_min_qos_req =
&argos_pdata->devices[dev_num].qos->hotplug_min_qos_req;
if (list_empty(head)) {
pr_debug("%s: task_affinity_list is empty\n", __func__);
return result;
}
list_for_each_entry(this, head, entry) {
if (enable) {
if (!*hotplug_disable) {
UPDATE_PM_QOS(hotplug_min_qos_req,
PM_QOS_CPU_ONLINE_MIN, num_possible_cpus());
*hotplug_disable = true;
}
mask = this->affinity_cpu_mask;
} else {
if (*hotplug_disable) {
REMOVE_PM_QOS(hotplug_min_qos_req);
*hotplug_disable = false;
}
mask = this->default_cpu_mask;
}
result = set_cpus_allowed_ptr(this->p, mask);
pr_info("%s: %s affinity %s to cpu_mask:0x%X\n",
__func__, this->p->comm,
(enable ? "enable" : "disable"),
(int)*mask->bits);
}
return result;
}
int argos_irq_affinity_apply(int dev_num, bool enable)
{
struct argos_irq_affinity *this;
struct list_head *head;
int result = 0;
struct cpumask *mask;
bool *hotplug_disable;
struct pm_qos_request *hotplug_min_qos_req;
head = &argos_pdata->devices[dev_num].irq_affinity_list;
hotplug_disable = &argos_pdata->devices[dev_num].irq_hotplug_disable;
hotplug_min_qos_req =
&argos_pdata->devices[dev_num].qos->hotplug_min_qos_req;
if (list_empty(head)) {
pr_debug("%s: irq_affinity_list is empty\n", __func__);
return result;
}
list_for_each_entry(this, head, entry) {
if (enable) {
if (!*hotplug_disable) {
UPDATE_PM_QOS(hotplug_min_qos_req,
PM_QOS_CPU_ONLINE_MIN, num_possible_cpus());
*hotplug_disable = true;
}
mask = this->affinity_cpu_mask;
} else {
if (*hotplug_disable) {
REMOVE_PM_QOS(hotplug_min_qos_req);
*hotplug_disable = false;
}
mask = this->default_cpu_mask;
}
result = irq_set_affinity(this->irq, mask);
pr_info("%s: irq%d affinity %s to cpu_mask:0x%X\n",
__func__, this->irq, (enable ? "enable" : "disable"),
(int)*mask->bits);
}
return result;
}
int argos_hmpboost_apply(int dev_num, bool enable)
{
bool *hmpboost_enable;
hmpboost_enable = &argos_pdata->devices[dev_num].hmpboost_enable;
if (enable) {
/* disable -> enable */
if (!*hmpboost_enable) {
#if defined(CONFIG_SCHED_EMS)
/* set global boost */
gb_qos_update_request(&gb_req, 100);
#endif
*hmpboost_enable = true;
pr_info("%s: hmp boost enable [%d]\n", __func__, dev_num);
}
} else {
/* enable -> disable */
if (*hmpboost_enable) {
#if defined(CONFIG_SCHED_EMS)
/* unset global boost */
gb_qos_update_request(&gb_req, 0);
#endif
*hmpboost_enable = false;
pr_info("%s: hmp boost disable [%d]\n", __func__, dev_num);
}
}
return 0;
}
static void argos_freq_unlock(int type)
{
struct argos_pm_qos *qos = argos_pdata->devices[type].qos;
const char *cname;
cname = argos_pdata->devices[type].desc;
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
REMOVE_PM_QOS(&qos->big_min_qos_req);
REMOVE_PM_QOS(&qos->big_max_qos_req);
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
REMOVE_PM_QOS(&qos->mid_min_qos_req);
REMOVE_PM_QOS(&qos->mid_max_qos_req);
#endif
REMOVE_PM_QOS(&qos->lit_min_qos_req);
REMOVE_PM_QOS(&qos->lit_max_qos_req);
REMOVE_PM_QOS(&qos->mif_qos_req);
REMOVE_PM_QOS(&qos->int_qos_req);
pr_info("%s name:%s\n", __func__, cname);
}
static void argos_freq_lock(int type, int level)
{
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
unsigned int big_min_freq, big_max_freq;
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
unsigned int mid_min_freq, mid_max_freq;
#endif
unsigned int lit_min_freq, lit_max_freq;
unsigned int mif_freq, int_freq;
struct boost_table *t = &argos_pdata->devices[type].tables[level];
struct argos_pm_qos *qos = argos_pdata->devices[type].qos;
const char *cname;
cname = argos_pdata->devices[type].desc;
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
big_min_freq = t->items[BIG_MIN_FREQ];
big_max_freq = t->items[BIG_MAX_FREQ];
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
mid_min_freq = t->items[MID_MIN_FREQ];
mid_max_freq = t->items[MID_MAX_FREQ];
#endif
lit_min_freq = t->items[LIT_MIN_FREQ];
lit_max_freq = t->items[LIT_MAX_FREQ];
mif_freq = t->items[MIF_FREQ];
int_freq = t->items[INT_FREQ];
#if (CONFIG_ARGOS_CLUSTER_NUM == 3)
if (big_min_freq)
UPDATE_PM_QOS(&qos->big_min_qos_req,
PM_QOS_CLUSTER2_FREQ_MIN, big_min_freq);
else
REMOVE_PM_QOS(&qos->big_min_qos_req);
if (big_max_freq)
UPDATE_PM_QOS(&qos->big_max_qos_req,
PM_QOS_CLUSTER2_FREQ_MAX, big_max_freq);
else
REMOVE_PM_QOS(&qos->big_max_qos_req);
if (mid_min_freq)
UPDATE_PM_QOS(&qos->mid_min_qos_req,
PM_QOS_CLUSTER1_FREQ_MIN, mid_min_freq);
else
REMOVE_PM_QOS(&qos->mid_min_qos_req);
if (mid_max_freq)
UPDATE_PM_QOS(&qos->mid_max_qos_req,
PM_QOS_CLUSTER1_FREQ_MAX, mid_max_freq);
else
REMOVE_PM_QOS(&qos->mid_max_qos_req);
#elif (CONFIG_ARGOS_CLUSTER_NUM == 2)
if (big_min_freq)
UPDATE_PM_QOS(&qos->big_min_qos_req,
PM_QOS_CLUSTER1_FREQ_MIN, big_min_freq);
else
REMOVE_PM_QOS(&qos->big_min_qos_req);
if (big_max_freq)
UPDATE_PM_QOS(&qos->big_max_qos_req,
PM_QOS_CLUSTER1_FREQ_MAX, big_max_freq);
else
REMOVE_PM_QOS(&qos->big_max_qos_req);
#endif
if (lit_min_freq)
UPDATE_PM_QOS(&qos->lit_min_qos_req,
PM_QOS_CLUSTER0_FREQ_MIN, lit_min_freq);
else
REMOVE_PM_QOS(&qos->lit_min_qos_req);
if (lit_max_freq)
UPDATE_PM_QOS(&qos->lit_max_qos_req,
PM_QOS_CLUSTER0_FREQ_MAX, lit_max_freq);
else
REMOVE_PM_QOS(&qos->lit_max_qos_req);
if (mif_freq)
UPDATE_PM_QOS(&qos->mif_qos_req,
PM_QOS_BUS_THROUGHPUT, mif_freq);
else
REMOVE_PM_QOS(&qos->mif_qos_req);
if (int_freq)
UPDATE_PM_QOS(&qos->int_qos_req,
PM_QOS_DEVICE_THROUGHPUT, int_freq);
else
REMOVE_PM_QOS(&qos->int_qos_req);
pr_info("%s name:%s, "
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
"BIG_MIN=%d, BIG_MAX=%d, "
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
"MID_MIN=%d, MID_MAX=%d, "
#endif
"LIT_MIN=%d, LIT_MAX=%d, MIF=%d, INT=%d\n",
__func__, cname,
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
big_min_freq, big_max_freq,
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
mid_min_freq, mid_max_freq,
#endif
lit_min_freq, lit_max_freq, mif_freq, int_freq);
}
void argos_block_enable(char *req_name, bool set)
{
int dev_num;
struct argos *cnode;
dev_num = argos_find_index(req_name);
if (dev_num < 0) {
pr_err("%s: No match found for label: %s", __func__, req_name);
return;
}
cnode = &argos_pdata->devices[dev_num];
if (set) {
cnode->argos_block = true;
mutex_lock(&cnode->level_mutex);
argos_freq_unlock(dev_num);
argos_task_affinity_apply(dev_num, 0);
argos_irq_affinity_apply(dev_num, 0);
argos_hmpboost_apply(dev_num, 0);
cnode->prev_level = -1;
mutex_unlock(&cnode->level_mutex);
} else {
cnode->argos_block = false;
}
pr_info("%s req_name:%s block:%d\n",
__func__, req_name, cnode->argos_block);
}
static int argos_cpuidle_reboot_notifier(struct notifier_block *this,
unsigned long event, void *_cmd)
{
switch (event) {
case SYSTEM_POWER_OFF:
case SYS_RESTART:
pr_info("%s called\n", __func__);
pm_qos_remove_notifier(PM_QOS_NETWORK_THROUGHPUT,
&argos_pdata->pm_qos_nfb);
break;
}
return NOTIFY_OK;
}
static struct notifier_block argos_cpuidle_reboot_nb = {
.notifier_call = argos_cpuidle_reboot_notifier,
};
static int argos_pm_qos_notify(struct notifier_block *nfb,
unsigned long speedtype, void *arg)
{
int type, level, prev_level, change_level;
unsigned long speed;
bool argos_blocked;
struct argos *cnode;
type = (speedtype & TYPE_MASK_BIT) - 1;
if (type < 0 || type > argos_pdata->ndevice) {
pr_err("There is no type for devices type[%d], ndevice[%d]\n",
type, argos_pdata->ndevice);
return NOTIFY_BAD;
}
speed = speedtype >> TYPE_SHIFT;
cnode = &argos_pdata->devices[type];
prev_level = cnode->prev_level;
pr_debug("%s name:%s, speed:%ldMbps\n", __func__, cnode->desc, speed);
if(speed >= 300) {
perflog(PERFLOG_ARGOS, "name:%s, speed:%ldMbps", cnode->desc, speed);
}
argos_blocked = cnode->argos_block;
/* Find proper level */
for (level = 0; level < cnode->ntables; level++)
if (speed < cnode->tables[level].items[THRESHOLD])
break;
/* decrease 1 level to match proper table */
level--;
if (!argos_blocked) {
if (level != prev_level) {
if (mutex_trylock(&cnode->level_mutex) == 0) {
/*
* If the mutex is already locked, it means this argos
* is being blocked or is handling another change.
* We don't need to wait.
*/
pr_warn("%s: skip name:%s, speed:%ldMbps, prev level:%d, request level:%d\n",
__func__, cnode->desc, speed, prev_level, level);
goto out;
}
pr_info("%s: name:%s, speed:%ldMbps, prev level:%d, request level:%d\n",
__func__, cnode->desc, speed, prev_level, level);
change_level = level;
if (level == -1) {
if (cnode->argos_notifier.head) {
pr_debug("%s: Call argos notifier(%s lev:%d)\n",
__func__, cnode->desc, level);
blocking_notifier_call_chain(&cnode->argos_notifier,
speed, NULL);
}
argos_freq_unlock(type);
argos_task_affinity_apply(type, 0);
argos_irq_affinity_apply(type, 0);
argos_hmpboost_apply(type, 0);
} else {
unsigned int enable_flag;
struct boost_table *plevel;
if (cnode->slowdown) {
if (prev_level - level == 1) {
pr_info("%s: skip! apply slowdown scheme. prev level:%d, request level:%d\n",
__func__, prev_level, level);
mutex_unlock(&cnode->level_mutex);
goto out;
} else if (prev_level - level > 1) {
change_level = level + 1;
pr_info("%s: slowdown! request level:%d, change level:%d\n",
__func__, level, change_level);
}
}
plevel = &argos_pdata->devices[type].tables[change_level];
argos_freq_lock(type, change_level);
enable_flag = plevel->items[TASK_AFFINITY_EN];
argos_task_affinity_apply(type, enable_flag);
enable_flag = plevel->items[IRQ_AFFINITY_EN];
argos_irq_affinity_apply(type, enable_flag);
enable_flag = plevel->items[HMP_BOOST_EN];
argos_hmpboost_apply(type, enable_flag);
if (cnode->argos_notifier.head) {
pr_debug("%s: Call argos notifier(%s lev:%d)\n",
__func__, cnode->desc, change_level);
blocking_notifier_call_chain(&cnode->argos_notifier,
speed, NULL);
}
}
cnode->prev_level = change_level;
mutex_unlock(&cnode->level_mutex);
} else {
pr_debug("%s:same level (%d) is requested", __func__, level);
}
}
out:
return NOTIFY_OK;
}
#ifdef CONFIG_OF
static int load_table_items(struct device_node *np, struct boost_table *t)
{
int ret = 0;
int len = 0;
const char *status;
ret = of_property_read_u32(np, "threshold", &t->items[THRESHOLD]);
if (ret) {
pr_err("Failed to get speed property\n");
return -EINVAL;
}
#if (CONFIG_ARGOS_CLUSTER_NUM > 1)
ret = of_property_read_u32(np, "big_min", &t->items[BIG_MIN_FREQ]);
/* If not exist, set to default 0 */
if (ret == -EINVAL) {
t->items[BIG_MIN_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get big_min\n");
return ret;
}
ret = of_property_read_u32(np, "big_max", &t->items[BIG_MAX_FREQ]);
/* If not exist, set to default 0 */
if (ret == -EINVAL) {
t->items[BIG_MAX_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get big_max\n");
return ret;
}
#endif
#if (CONFIG_ARGOS_CLUSTER_NUM > 2)
ret = of_property_read_u32(np, "mid_min", &t->items[MID_MIN_FREQ]);
/* If not exist, set to default 0 */
if (ret == -EINVAL) {
t->items[MID_MIN_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get mid_min\n");
return ret;
}
ret = of_property_read_u32(np, "mid_max", &t->items[MID_MAX_FREQ]);
/* If not exist, set to default 0 */
if (ret == -EINVAL) {
t->items[MID_MAX_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get mid_max\n");
return ret;
}
#endif
ret = of_property_read_u32(np, "lit_min", &t->items[LIT_MIN_FREQ]);
/* If not exist, set to default 0 */
if (ret == -EINVAL) {
t->items[LIT_MIN_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get lit_min\n");
return ret;
}
ret = of_property_read_u32(np, "lit_max", &t->items[LIT_MAX_FREQ]);
/* If not exist, set to default 0 */
if (ret == -EINVAL) {
t->items[LIT_MAX_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get lit_max\n");
return ret;
}
ret = of_property_read_u32(np, "mif", &t->items[MIF_FREQ]);
if (ret == -EINVAL) {
t->items[MIF_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get mif\n");
return ret;
}
ret = of_property_read_u32(np, "int", &t->items[INT_FREQ]);
if (ret == -EINVAL) {
t->items[INT_FREQ] = 0;
} else if (ret) {
pr_err("Failed to get int\n");
return ret;
}
status = of_get_property(np, "task_affinity", &len);
if (status && len > 0 && !strcmp(status, "enable"))
t->items[TASK_AFFINITY_EN] = 1;
else
t->items[TASK_AFFINITY_EN] = 0;
status = of_get_property(np, "irq_affinity", &len);
if (status && len > 0 && !strcmp(status, "enable"))
t->items[HMP_BOOST_EN] = 1;
else
t->items[HMP_BOOST_EN] = 0;
status = of_get_property(np, "hmp_boost", &len);
if (status && len > 0 && !strcmp(status, "enable"))
t->items[IRQ_AFFINITY_EN] = 1;
else
t->items[IRQ_AFFINITY_EN] = 0;
return 0;
}
static int argos_set_device_node(struct device *dev, struct device_node *np, struct argos *node)
{
struct device_node *np_table, *np_level;
int ret = 0;
if (of_get_property(np, "net_boost,slowdown", NULL))
node->slowdown = true;
else
node->slowdown = false;
node->desc = of_get_property(np, "net_boost,label", NULL);
node->qos = devm_kzalloc(dev, sizeof(struct argos_pm_qos), GFP_KERNEL);
if (!node->qos)
return -ENOMEM;
np_table = of_get_child_by_name(np, "net_boost,table");
if (!of_device_is_available(np_table)) {
return -EINVAL;
}
/* Allocation for freq and time table */
node->tables = devm_kzalloc(dev, sizeof(struct boost_table) * of_get_child_count(np_table), GFP_KERNEL);
if (!node->tables)
return -ENOMEM;
/* Get and add frequency and time table */
for_each_child_of_node(np_table, np_level) {
if ((ret = load_table_items(np_level, &node->tables[node->ntables])) != 0)
return ret;
node->ntables++;
}
INIT_LIST_HEAD(&node->task_affinity_list);
INIT_LIST_HEAD(&node->irq_affinity_list);
node->task_hotplug_disable = false;
node->irq_hotplug_disable = false;
node->hmpboost_enable = false;
node->argos_block = false;
node->prev_level = -1;
mutex_init(&node->level_mutex);
BLOCKING_INIT_NOTIFIER_HEAD(&node->argos_notifier);
return 0;
}
static int argos_parse_dt(struct device *dev)
{
struct argos_platform_data *pdata = dev->platform_data;
struct argos *device_node;
struct device_node *root_np, *device_np;
int device_count = 0;
int retval = 0;
root_np = dev->of_node;
pdata->ndevice = of_get_child_count(root_np);
if (!pdata->ndevice) {
dev_err(dev, "Failed to get child count\n");
return -ENODEV;
}
pdata->devices = devm_kzalloc(dev, sizeof(struct argos) * pdata->ndevice, GFP_KERNEL);
if (!pdata->devices)
return -ENOMEM;
for_each_child_of_node(root_np, device_np) {
device_node = &pdata->devices[device_count];
if ((retval = argos_set_device_node(dev, device_np, device_node)) != 0)
goto err_out;
device_count++;
}
return 0;
err_out:
return retval;
}
#endif
static int argos_probe(struct platform_device *pdev)
{
int ret = 0;
struct argos_platform_data *pdata;
pr_info("%s: Start probe\n", __func__);
if (pdev->dev.of_node) {
pdata = devm_kzalloc(&pdev->dev,
sizeof(struct argos_platform_data),
GFP_KERNEL);
if (!pdata) {
dev_err(&pdev->dev, "Failed to allocate platform data\n");
return -ENOMEM;
}
pdev->dev.platform_data = pdata;
ret = argos_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->ndevice || !pdata->devices) {
dev_err(&pdev->dev, "There are no devices\n");
return -EINVAL;
}
pdata->pm_qos_nfb.notifier_call = argos_pm_qos_notify;
pm_qos_add_notifier(PM_QOS_NETWORK_THROUGHPUT, &pdata->pm_qos_nfb);
register_reboot_notifier(&argos_cpuidle_reboot_nb);
argos_pdata = pdata;
platform_set_drvdata(pdev, pdata);
return 0;
}
static int argos_remove(struct platform_device *pdev)
{
struct argos_platform_data *pdata = platform_get_drvdata(pdev);
if (!pdata || !argos_pdata)
return 0;
pm_qos_remove_notifier(PM_QOS_NETWORK_THROUGHPUT, &pdata->pm_qos_nfb);
unregister_reboot_notifier(&argos_cpuidle_reboot_nb);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id argos_dt_ids[] = {
{ .compatible = "samsung,argos"},
{ }
};
#endif
static struct platform_driver argos_driver = {
.driver = {
.name = ARGOS_NAME,
.owner = THIS_MODULE,
#ifdef CONFIG_OF
.of_match_table = of_match_ptr(argos_dt_ids),
#endif
},
.probe = argos_probe,
.remove = argos_remove
};
static int __init argos_init(void)
{
return platform_driver_register(&argos_driver);
}
static void __exit argos_exit(void)
{
return platform_driver_unregister(&argos_driver);
}
subsys_initcall(argos_init);
module_exit(argos_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SAMSUNG Electronics");
MODULE_DESCRIPTION("ARGOS DEVICE");