995 lines
24 KiB
C
Executable File
995 lines
24 KiB
C
Executable File
/*
|
|
* 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");
|