blob: 04c7b753e8d1624e71f515908b038fc2614a5673 [file] [log] [blame]
/*
* Samsung Exynos SoC series NPU driver
*
* Copyright (c) 2017 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/pm_qos.h>
#include <linux/pm_opp.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include "npu-vs4l.h"
#include "npu-device.h"
#include "npu-memory.h"
#include "npu-system.h"
#include "npu-qos.h"
#ifdef CONFIG_ARCH_EXYNOS
#if defined(CONFIG_SOC_EXYNOS9820)
#define NPU_PM_QOS_NPU (PM_QOS_NPU_THROUGHPUT)
#define NPU_PM_QOS_DEFAULT_FREQ (666000)
#define NPU_PM_NPU_MIN (222000)
#define NPU_PM_NPU_MAX (933000)
#define NPU_PM_QOS_MIF (PM_QOS_BUS_THROUGHPUT)
#define NPU_PM_MIF_MIN (421000)
#define NPU_PM_MIF_REQ (1352000)
#define NPU_PM_MIF_MAX (2093000)
#define NPU_PM_QOS_INT (PM_QOS_DEVICE_THROUGHPUT)
#define NPU_PM_INT_MIN (100000)
#define NPU_PM_INT_MAX (534000)
#define NPU_PM_CPU_CL0 (PM_QOS_CLUSTER0_FREQ_MIN)
#define NPU_PM_CPU_CL0_MIN (442000)
#define NPU_PM_CPU_CL0_MAX (1742000)
#define NPU_PM_CPU_CL1 (PM_QOS_CLUSTER1_FREQ_MIN)
#define NPU_PM_CPU_CL1_MIN (507000)
#define NPU_PM_CPU_CL1_MAX (1898000)
#define NPU_PM_CPU_CL2 (PM_QOS_CLUSTER2_FREQ_MIN)
#define NPU_PM_CPU_CL2_MIN (520000)
#define NPU_PM_CPU_CL2_MAX (2470000)
#define NPU_PM_CPU_ONLINE_MIN 0
#define NPU_PM_CPU_ONLINE_MAX 6
#else
#undef NPU_PM_QOS_NPU
#define NPU_PM_NPU_MIN (167000)
#endif
#endif
static struct npu_qos_setting *qos_setting;
static LIST_HEAD(qos_list);
static int npu_sysfs_print(char *buf, size_t buf_sz, const char *fmt, ...)
{
int strlen;
va_list ap;
va_start(ap, fmt);
strlen = vsnprintf(buf, buf_sz, fmt, ap);
va_end(ap);
return strlen;
}
#define NPU_SYSFS_PRINT(buf, buf_size, tmp_buf, tmp_size, format, ...) \
do { \
tmp_size = npu_sysfs_print(tmp_buf, sizeof(tmp_buf), \
format, ## __VA_ARGS__); \
if ((buf_size + tmp_size + 1) > PAGE_SIZE) \
goto exit; /* no space */ \
strncat(buf, tmp_buf, tmp_size); \
buf_size += tmp_size; \
} while (0)
static ssize_t npu_sysfs_show_qos(struct device *dev,
struct device_attribute *dev_attr, char *buf)
{
struct device *dvfs_dev;
struct dev_pm_opp *opp;
ssize_t buf_size = 0;
char tmp_buf[256];
int tmp_size;
unsigned long freq = 0;
buf[0] = 0;
if (!qos_setting) {
dev_warn(dev, "No qos_setting in device.\n");
goto exit;
}
dvfs_dev = &qos_setting->dvfs_npu_dev->dev;
NPU_SYSFS_PRINT(buf, buf_size, tmp_buf, tmp_size,
"# DVFS table:\n ");
do {
opp = dev_pm_opp_find_freq_ceil(dvfs_dev, &freq);
if (IS_ERR(opp))
break;
NPU_SYSFS_PRINT(buf, buf_size, tmp_buf, tmp_size,
"%lu%s%s ", freq,
(freq == qos_setting->current_freq) ?
"(*)" : "",
(freq == qos_setting->request_freq) ?
"(R)" : "");
freq++;
dev_pm_opp_put(opp);
} while (1);
NPU_SYSFS_PRINT(buf, buf_size, tmp_buf, tmp_size,
"\n\n# default: %d KHz / request: %d KHz / current: %d KHz\n\n",
qos_setting->default_freq,
qos_setting->request_freq, qos_setting->current_freq);
exit:
return buf_size;
}
static ssize_t npu_sysfs_set_qos(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct device *dvfs_dev;
struct dev_pm_opp *opp;
unsigned long freq = 0;
bool hit = false;
int value, err;
err = kstrtoint(buf, 10, &value);
if (err) {
dev_err(dev, "only decimal value allowed (input: %s)\n", buf);
return count;
}
if (!qos_setting->dvfs_npu_dev) {
dev_warn(dev, "npu drivers does not connect with dvfs table\n");
return count;
}
dvfs_dev = &qos_setting->dvfs_npu_dev->dev;
do {
opp = dev_pm_opp_find_freq_ceil(dvfs_dev, &freq);
if (IS_ERR(opp))
break;
if (freq == (int)value) {
hit = true;
dev_pm_opp_put(opp);
break;
}
dev_pm_opp_put(opp);
freq++;
} while (1);
if (hit)
qos_setting->request_freq = (s32)value;
else
dev_warn(dev, "fail to set qos_rate (req %d KHz) and"
"(current %d KHz)\n", value, qos_setting->current_freq);
return count;
}
static DEVICE_ATTR(qos_rate, 0644, npu_sysfs_show_qos, npu_sysfs_set_qos);
static struct attribute *npu_qos_attributes[] = {
&dev_attr_qos_rate.attr,
NULL,
};
static struct attribute_group npu_attr_group = {
.attrs = npu_qos_attributes,
};
int npu_qos_probe(struct npu_system *system)
{
struct device *dev = &system->pdev->dev;
struct device_node *of_node = dev->of_node;
struct device_node *child_node;
qos_setting = &(system->qos_setting);
mutex_init(&qos_setting->npu_qos_lock);
/* parse dts and fill data */
child_node = of_parse_phandle(of_node, "dvfs-dev", 0);
qos_setting->dvfs_npu_dev = of_find_device_by_node(child_node);
if (!qos_setting->dvfs_npu_dev)
dev_warn(dev, "fail to get npu dvfs_dev\n");
if (of_property_read_s32(of_node, "npu_default_qos_rate",
&qos_setting->default_freq))
qos_setting->default_freq = NPU_PM_NPU_MIN;
qos_setting->request_freq = qos_setting->default_freq;
/* qos add request(default_freq) */
#ifdef NPU_PM_QOS_NPU
pm_qos_add_request(&qos_setting->npu_qos_req_npu, NPU_PM_QOS_NPU, 0);
pm_qos_add_request(&qos_setting->npu_qos_req_mif, NPU_PM_QOS_MIF, 0);
pm_qos_add_request(&qos_setting->npu_qos_req_int, NPU_PM_QOS_INT, 0);
pm_qos_add_request(&qos_setting->npu_qos_req_cpu_cl0, NPU_PM_CPU_CL0, 0);
pm_qos_add_request(&qos_setting->npu_qos_req_cpu_cl1, NPU_PM_CPU_CL1, 0);
pm_qos_add_request(&qos_setting->npu_qos_req_cpu_cl2, NPU_PM_CPU_CL2, 0);
qos_setting->current_freq = qos_setting->default_freq;
#else
qos_setting->default_freq = NPU_PM_NPU_MIN;
qos_setting->current_freq = NPU_PM_NPU_MIN;
#endif
qos_setting->req_cl0_freq = 0;
qos_setting->req_cl1_freq = 0;
qos_setting->req_cl2_freq = 0;
return sysfs_create_group(&dev->kobj, &npu_attr_group);
}
int npu_qos_release(struct npu_system *system)
{
struct device *dev = &system->pdev->dev;
sysfs_remove_group(&dev->kobj, &npu_attr_group);
return 0;
}
int npu_qos_start(struct npu_system *system)
{
int cur_value;
BUG_ON(!system);
mutex_lock(&qos_setting->npu_qos_lock);
if (qos_setting->current_freq != qos_setting->request_freq)
qos_setting->current_freq = qos_setting->request_freq;
#ifdef NPU_PM_QOS_NPU
if (qos_setting->req_npu_freq <= qos_setting->current_freq)
pm_qos_update_request(&qos_setting->npu_qos_req_npu, qos_setting->current_freq);
#endif
mutex_unlock(&qos_setting->npu_qos_lock);
cur_value = (s32)pm_qos_read_req_value(qos_setting->npu_qos_req_npu.pm_qos_class,
&qos_setting->npu_qos_req_npu);
npu_info("%s() npu_qos_rate(%d)\n", __func__, cur_value);
qos_setting->req_cl0_freq = 0;
qos_setting->req_cl1_freq = 0;
qos_setting->req_cl2_freq = 0;
return 0;
}
int npu_qos_stop(struct npu_system *system)
{
struct list_head *pos, *q;
struct npu_session_qos_req *qr;
int cur_value;
BUG_ON(!system);
mutex_lock(&qos_setting->npu_qos_lock);
list_for_each_safe(pos, q, &qos_list) {
qr = list_entry(pos, struct npu_session_qos_req, list);
list_del(pos);
if (qr)
kfree(qr);
}
list_del_init(&qos_list);
#ifdef NPU_PM_QOS_NPU
pm_qos_update_request(&qos_setting->npu_qos_req_npu, NPU_PM_NPU_MIN);
pm_qos_update_request(&qos_setting->npu_qos_req_mif, 0);
pm_qos_update_request(&qos_setting->npu_qos_req_int, 0);
pm_qos_update_request(&qos_setting->npu_qos_req_cpu_cl0, 0);
pm_qos_update_request(&qos_setting->npu_qos_req_cpu_cl1, 0);
pm_qos_update_request(&qos_setting->npu_qos_req_cpu_cl2, 0);
#endif
qos_setting->req_npu_freq = 0;
mutex_unlock(&qos_setting->npu_qos_lock);
cur_value = (s32)pm_qos_read_req_value(qos_setting->npu_qos_req_npu.pm_qos_class,
&qos_setting->npu_qos_req_npu);
npu_info("%s() npu_qos_rate(%d)\n", __func__, cur_value);
return 0;
}
s32 __update_freq_from_showcase(__u32 nCategory)
{
s32 nValue = 0;
struct list_head *pos, *q;
struct npu_session_qos_req *qr;
list_for_each_safe(pos, q, &qos_list) {
qr = list_entry(pos, struct npu_session_qos_req, list);
if (qr->eCategory == nCategory) {
nValue = qr->req_freq > nValue ? qr->req_freq : nValue;
npu_dbg("[U%u]Candidate Freq. category : %u freq : %d\n",
qr->sessionUID, nCategory, nValue);
}
}
return nValue;
}
int __req_param_qos(int uid, __u32 nCategory, struct pm_qos_request *req, s32 new_value)
{
int ret = 0;
s32 cur_value, rec_value;
struct list_head *pos, *q;
struct npu_session_qos_req *qr;
//Check that same uid, and category whether already registered.
list_for_each_safe(pos, q, &qos_list) {
qr = list_entry(pos, struct npu_session_qos_req, list);
if ((qr->sessionUID == uid) && (qr->eCategory == nCategory)) {
cur_value = qr->req_freq;
npu_dbg("[U%u]Change Req Freq. category : %u, from freq : %d to %d\n",
uid, nCategory, cur_value, new_value);
list_del(pos);
qr->sessionUID = uid;
qr->req_freq = new_value;
qr->eCategory = nCategory;
list_add_tail(&qr->list, &qos_list);
rec_value = __update_freq_from_showcase(nCategory);
if (new_value > rec_value) {
pm_qos_update_request(req, new_value);
npu_dbg("[U%u]Changed Freq. category : %u, from freq : %d to %d\n",
uid, nCategory, cur_value, new_value);
} else {
pm_qos_update_request(req, rec_value);
npu_dbg("[U%u]Recovered Freq. category : %u, from freq : %d to %d\n",
uid, nCategory, cur_value, rec_value);
}
return ret;
}
}
//No Same uid, and category. Add new item
qr = kmalloc(sizeof(struct npu_session_qos_req), GFP_KERNEL);
if (!qr) {
npu_err("memory alloc fail.\n");
return -ENOMEM;
}
qr->sessionUID = uid;
qr->req_freq = new_value;
qr->eCategory = nCategory;
list_add_tail(&qr->list, &qos_list);
//If new_value is lager than current value, update the freq
cur_value = (s32)pm_qos_read_req_value(req->pm_qos_class, req);
npu_dbg("[U%u]New Freq. category : %u freq : %u\n",
qr->sessionUID, qr->eCategory, qr->req_freq);
if (cur_value < new_value) {
npu_dbg("[U%u]Update Freq. category : %u freq : %u\n",
qr->sessionUID, qr->eCategory, qr->req_freq);
pm_qos_update_request(req, new_value);
}
return ret;
}
npu_s_param_ret npu_qos_param_handler(struct npu_session *sess, struct vs4l_param *param, int *retval)
{
BUG_ON(!sess);
BUG_ON(!param);
mutex_lock(&qos_setting->npu_qos_lock);
switch (param->target) {
case NPU_S_PARAM_QOS_NPU:
qos_setting->req_npu_freq = param->offset;
//Set minimum safe clock as default to avoid NDONE
if (qos_setting->req_npu_freq < qos_setting->default_freq)
qos_setting->req_npu_freq = qos_setting->default_freq;
__req_param_qos(sess->uid, param->target, &qos_setting->npu_qos_req_npu, qos_setting->req_npu_freq);
mutex_unlock(&qos_setting->npu_qos_lock);
return S_PARAM_HANDLED;
case NPU_S_PARAM_QOS_MIF:
qos_setting->req_mif_freq = param->offset;
__req_param_qos(sess->uid, param->target, &qos_setting->npu_qos_req_mif, qos_setting->req_mif_freq);
mutex_unlock(&qos_setting->npu_qos_lock);
return S_PARAM_HANDLED;
case NPU_S_PARAM_QOS_INT:
qos_setting->req_int_freq = param->offset;
__req_param_qos(sess->uid, param->target, &qos_setting->npu_qos_req_int, qos_setting->req_int_freq);
mutex_unlock(&qos_setting->npu_qos_lock);
return S_PARAM_HANDLED;
case NPU_S_PARAM_QOS_CL0:
qos_setting->req_cl0_freq = param->offset;
__req_param_qos(sess->uid, param->target, &qos_setting->npu_qos_req_cpu_cl0, qos_setting->req_cl0_freq);
mutex_unlock(&qos_setting->npu_qos_lock);
return S_PARAM_HANDLED;
case NPU_S_PARAM_QOS_CL1:
qos_setting->req_cl1_freq = param->offset;
__req_param_qos(sess->uid, param->target, &qos_setting->npu_qos_req_cpu_cl1, qos_setting->req_cl1_freq);
mutex_unlock(&qos_setting->npu_qos_lock);
return S_PARAM_HANDLED;
case NPU_S_PARAM_QOS_CL2:
qos_setting->req_cl2_freq = param->offset;
__req_param_qos(sess->uid, param->target, &qos_setting->npu_qos_req_cpu_cl2, qos_setting->req_cl2_freq);
mutex_unlock(&qos_setting->npu_qos_lock);
return S_PARAM_HANDLED;
case NPU_S_PARAM_CPU_AFF:
case NPU_S_PARAM_QOS_RST:
default:
mutex_unlock(&qos_setting->npu_qos_lock);
return S_PARAM_NOMB;
}
}