1113 lines
24 KiB
C
Executable file
1113 lines
24 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Samsung Exynos SoC series dsp driver
|
|
*
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com/
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "dsp-log.h"
|
|
#include "dsp-hw-common-system.h"
|
|
#include "dsp-hw-common-pm.h"
|
|
|
|
#if IS_ENABLED(CONFIG_ARM_FREQ_QOS_TRACER)
|
|
#include <soc/samsung/freq-qos-tracer.h>
|
|
#else
|
|
#define freq_qos_tracer_add_request(a, b, c, d)
|
|
#define freq_qos_tracer_remove_request(a)
|
|
#endif
|
|
|
|
#define DSP_PM_TOKEN_WAIT_COUNT (500)
|
|
|
|
#ifdef ENABLE_DSP_VELOCE
|
|
static int pm_for_veloce;
|
|
#endif
|
|
|
|
int dsp_hw_common_pm_devfreq_active(struct dsp_pm *pm)
|
|
{
|
|
dsp_check();
|
|
#ifndef ENABLE_DSP_VELOCE
|
|
return pm_runtime_active(pm->dev);
|
|
#else
|
|
return pm_for_veloce;
|
|
#endif
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_check_valid(struct dsp_pm_devfreq *devfreq,
|
|
int val)
|
|
{
|
|
dsp_check();
|
|
if (val < 0 || val > devfreq->min_qos) {
|
|
dsp_err("devfreq[%s] value(%d) is invalid(L0 ~ L%u)\n",
|
|
devfreq->name, val, devfreq->min_qos);
|
|
return -EINVAL;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_update_devfreq_raw(struct dsp_pm_devfreq *devfreq,
|
|
int val)
|
|
{
|
|
int ret, req_val;
|
|
|
|
dsp_enter();
|
|
if (val == DSP_GOVERNOR_DEFAULT)
|
|
req_val = devfreq->min_qos;
|
|
else
|
|
req_val = val;
|
|
|
|
ret = __dsp_hw_common_pm_check_valid(devfreq, req_val);
|
|
if (ret)
|
|
goto p_err;
|
|
|
|
#ifndef ENABLE_DSP_VELOCE
|
|
if (devfreq->current_qos != req_val) {
|
|
if (devfreq->use_freq_qos) {
|
|
#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE
|
|
freq_qos_update_request(&devfreq->freq_qos_req,
|
|
devfreq->table[req_val]);
|
|
#endif
|
|
} else {
|
|
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
|
exynos_pm_qos_update_request(&devfreq->req,
|
|
devfreq->table[req_val]);
|
|
#else
|
|
pm_qos_update_request(&devfreq->req,
|
|
devfreq->table[req_val]);
|
|
#endif
|
|
}
|
|
dsp_dbg("devfreq[%s] is changed from L%d to L%d\n",
|
|
devfreq->name, devfreq->current_qos, req_val);
|
|
devfreq->current_qos = req_val;
|
|
} else {
|
|
dsp_dbg("devfreq[%s] is already running L%d\n",
|
|
devfreq->name, req_val);
|
|
}
|
|
#else
|
|
dsp_dbg("devfreq[%s] is not supported\n", devfreq->name);
|
|
#endif
|
|
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_devfreq_get_token(struct dsp_pm_devfreq *devfreq,
|
|
bool async)
|
|
{
|
|
int repeat = DSP_PM_TOKEN_WAIT_COUNT;
|
|
|
|
dsp_enter();
|
|
while (repeat) {
|
|
spin_lock(&devfreq->slock);
|
|
if (devfreq->async_status < 0) {
|
|
devfreq->async_status = async;
|
|
spin_unlock(&devfreq->slock);
|
|
dsp_dbg("get devfreq[%s/%d] token(wait count:%d/%d)\n",
|
|
devfreq->name, async, repeat,
|
|
DSP_PM_TOKEN_WAIT_COUNT);
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
spin_unlock(&devfreq->slock);
|
|
udelay(10);
|
|
repeat--;
|
|
}
|
|
|
|
dsp_err("other asynchronous work is not done [%s]\n", devfreq->name);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_devfreq_put_token(struct dsp_pm_devfreq *devfreq)
|
|
{
|
|
dsp_enter();
|
|
spin_lock(&devfreq->slock);
|
|
if (devfreq->async_status < 0)
|
|
dsp_warn("devfreq token is invalid(%s/%d)\n",
|
|
devfreq->name, devfreq->async_status);
|
|
|
|
devfreq->async_status = -1;
|
|
dsp_dbg("put devfreq[%s] token\n", devfreq->name);
|
|
spin_unlock(&devfreq->slock);
|
|
dsp_leave();
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_update_devfreq(struct dsp_pm_devfreq *devfreq,
|
|
int val)
|
|
{
|
|
int ret;
|
|
|
|
dsp_enter();
|
|
ret = __dsp_hw_common_pm_update_devfreq_raw(devfreq, val);
|
|
if (ret)
|
|
goto p_err;
|
|
|
|
if (devfreq->pm->update_devfreq_handler)
|
|
devfreq->pm->update_devfreq_handler(devfreq->pm,
|
|
devfreq->id, val);
|
|
|
|
devfreq->pm->ops->update_freq_info(devfreq->pm, devfreq->id);
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
static void dsp_hw_common_devfreq_async(struct kthread_work *work)
|
|
{
|
|
struct dsp_pm_devfreq *devfreq;
|
|
|
|
dsp_enter();
|
|
devfreq = container_of(work, struct dsp_pm_devfreq, work);
|
|
|
|
__dsp_hw_common_pm_update_devfreq(devfreq, devfreq->async_qos);
|
|
devfreq->async_qos = -1;
|
|
__dsp_hw_common_pm_devfreq_put_token(devfreq);
|
|
devfreq->pm->sys->clk.ops->dump(&devfreq->pm->sys->clk);
|
|
dsp_leave();
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_update_devfreq_nolock(struct dsp_pm *pm, int id,
|
|
int val, bool async)
|
|
{
|
|
int ret;
|
|
|
|
dsp_enter();
|
|
if (!pm->ops->devfreq_active(pm)) {
|
|
ret = -EINVAL;
|
|
dsp_warn("dsp is not running\n");
|
|
goto p_err;
|
|
}
|
|
|
|
if (id < 0 || id >= pm->devfreq_count) {
|
|
ret = -EINVAL;
|
|
dsp_err("devfreq id(%d) for dsp is invalid\n", id);
|
|
goto p_err;
|
|
}
|
|
|
|
ret = __dsp_hw_common_pm_devfreq_get_token(&pm->devfreq[id], async);
|
|
if (ret)
|
|
goto p_err;
|
|
|
|
if (async) {
|
|
pm->devfreq[id].async_qos = val;
|
|
kthread_queue_work(&pm->async_worker, &pm->devfreq[id].work);
|
|
} else {
|
|
ret = __dsp_hw_common_pm_update_devfreq(&pm->devfreq[id], val);
|
|
__dsp_hw_common_pm_devfreq_put_token(&pm->devfreq[id]);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_devfreq_nolock(struct dsp_pm *pm, int id, int val)
|
|
{
|
|
dsp_check();
|
|
return __dsp_hw_common_pm_update_devfreq_nolock(pm, id, val, false);
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_devfreq_nolock_async(struct dsp_pm *pm, int id,
|
|
int val)
|
|
{
|
|
dsp_check();
|
|
return __dsp_hw_common_pm_update_devfreq_nolock(pm, id, val, true);
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_extra_devfreq(struct dsp_pm *pm, int id, int val)
|
|
{
|
|
int ret;
|
|
|
|
dsp_enter();
|
|
if (id < 0 || id >= pm->extra_devfreq_count) {
|
|
ret = -EINVAL;
|
|
dsp_err("extra devfreq id(%d) is invalid\n", id);
|
|
goto p_err;
|
|
}
|
|
|
|
mutex_lock(&pm->lock);
|
|
ret = __dsp_hw_common_pm_update_devfreq_raw(&pm->extra_devfreq[id],
|
|
val);
|
|
if (ret)
|
|
goto p_err_update;
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
p_err_update:
|
|
mutex_unlock(&pm->lock);
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_set_force_qos(struct dsp_pm *pm, int id, int val)
|
|
{
|
|
int ret;
|
|
|
|
dsp_enter();
|
|
if (id < 0 || id >= pm->devfreq_count) {
|
|
ret = -EINVAL;
|
|
dsp_err("devfreq id(%d) for dsp is invalid\n", id);
|
|
goto p_err;
|
|
}
|
|
|
|
if (val >= 0) {
|
|
ret = __dsp_hw_common_pm_check_valid(&pm->devfreq[id], val);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
pm->devfreq[id].force_qos = val;
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_static_reset(struct dsp_pm_devfreq *devfreq)
|
|
{
|
|
dsp_enter();
|
|
devfreq->static_qos = devfreq->min_qos;
|
|
devfreq->static_total_count = 0;
|
|
memset(devfreq->static_count, 0x0, DSP_DEVFREQ_RESERVED_NUM << 2);
|
|
dsp_leave();
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_del_static(struct dsp_pm_devfreq *devfreq,
|
|
int val)
|
|
{
|
|
int ret, idx;
|
|
|
|
dsp_enter();
|
|
if (!devfreq->static_count[val]) {
|
|
ret = -EINVAL;
|
|
dsp_warn("static count is unstable([%s][L%d]%u)\n",
|
|
devfreq->name, val, devfreq->static_count[val]);
|
|
goto p_err;
|
|
} else {
|
|
devfreq->static_count[val]--;
|
|
if (devfreq->static_total_count) {
|
|
devfreq->static_total_count--;
|
|
} else {
|
|
ret = -EINVAL;
|
|
dsp_warn("static total count is unstable([%s]%u)\n",
|
|
devfreq->name,
|
|
devfreq->static_total_count);
|
|
goto p_err;
|
|
}
|
|
}
|
|
|
|
if ((val == devfreq->static_qos) && (!devfreq->static_count[val])) {
|
|
for (idx = val + 1; idx <= devfreq->min_qos; ++idx) {
|
|
if (idx == devfreq->min_qos) {
|
|
devfreq->static_qos = idx;
|
|
break;
|
|
}
|
|
if (devfreq->static_count[idx]) {
|
|
devfreq->static_qos = idx;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_dvfs_enable(struct dsp_pm *pm, int val)
|
|
{
|
|
int ret, run, idx;
|
|
struct dsp_pm_devfreq *devfreq;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
if (!pm->dvfs_disable_count) {
|
|
ret = -EINVAL;
|
|
dsp_warn("dvfs disable count is unstable(%u)\n",
|
|
pm->dvfs_disable_count);
|
|
goto p_err;
|
|
}
|
|
|
|
if (val == DSP_GOVERNOR_DEFAULT) {
|
|
dsp_info("DVFS enabled forcibly\n");
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_static_reset(&pm->devfreq[idx]);
|
|
|
|
pm->dvfs = true;
|
|
pm->dvfs_disable_count = 0;
|
|
} else {
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
ret = __dsp_hw_common_pm_check_valid(&pm->devfreq[idx],
|
|
val);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_del_static(&pm->devfreq[idx], val);
|
|
|
|
if (!(--pm->dvfs_disable_count)) {
|
|
pm->dvfs = true;
|
|
dsp_info("DVFS enabled\n");
|
|
}
|
|
}
|
|
|
|
if (!pm->ops->devfreq_active(pm)) {
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
devfreq = &pm->devfreq[idx];
|
|
|
|
if (devfreq->static_total_count) {
|
|
if (devfreq->force_qos < 0)
|
|
run = devfreq->static_qos;
|
|
else
|
|
run = devfreq->force_qos;
|
|
} else {
|
|
run = devfreq->dynamic_qos;
|
|
}
|
|
|
|
pm->ops->update_devfreq_nolock(pm, idx, run);
|
|
}
|
|
pm->sys->clk.ops->dump(&pm->sys->clk);
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
mutex_unlock(&pm->lock);
|
|
return ret;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_add_static(struct dsp_pm_devfreq *devfreq,
|
|
int val)
|
|
{
|
|
dsp_enter();
|
|
devfreq->static_count[val]++;
|
|
devfreq->static_total_count++;
|
|
|
|
if (devfreq->static_total_count == 1)
|
|
devfreq->static_qos = val;
|
|
else if (val < devfreq->static_qos)
|
|
devfreq->static_qos = val;
|
|
|
|
dsp_leave();
|
|
}
|
|
|
|
int dsp_hw_common_pm_dvfs_disable(struct dsp_pm *pm, int val)
|
|
{
|
|
int ret, run, idx;
|
|
struct dsp_pm_devfreq *devfreq;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
ret = __dsp_hw_common_pm_check_valid(&pm->devfreq[idx], val);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
pm->dvfs = false;
|
|
if (!pm->dvfs_disable_count)
|
|
dsp_info("DVFS disabled\n");
|
|
pm->dvfs_disable_count++;
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_add_static(&pm->devfreq[idx], val);
|
|
|
|
if (!pm->ops->devfreq_active(pm)) {
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
devfreq = &pm->devfreq[idx];
|
|
|
|
if (devfreq->force_qos < 0)
|
|
run = devfreq->static_qos;
|
|
else
|
|
run = devfreq->force_qos;
|
|
|
|
pm->ops->update_devfreq_nolock(pm, idx, run);
|
|
}
|
|
pm->sys->clk.ops->dump(&pm->sys->clk);
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
mutex_unlock(&pm->lock);
|
|
return ret;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_add_dynamic(struct dsp_pm_devfreq *devfreq,
|
|
int val)
|
|
{
|
|
dsp_enter();
|
|
devfreq->dynamic_count[val]++;
|
|
devfreq->dynamic_total_count++;
|
|
|
|
if (devfreq->dynamic_total_count == 1)
|
|
devfreq->dynamic_qos = val;
|
|
else if (val < devfreq->dynamic_qos)
|
|
devfreq->dynamic_qos = val;
|
|
|
|
dsp_leave();
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_devfreq_busy(struct dsp_pm *pm, int val)
|
|
{
|
|
int ret, run, idx;
|
|
struct dsp_pm_devfreq *devfreq;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
ret = __dsp_hw_common_pm_check_valid(&pm->devfreq[idx], val);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_add_dynamic(&pm->devfreq[idx], val);
|
|
|
|
if (!pm->dvfs) {
|
|
dsp_dbg("DVFS was disabled(busy)\n");
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
if (!pm->ops->devfreq_active(pm)) {
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
devfreq = &pm->devfreq[idx];
|
|
|
|
if (devfreq->force_qos < 0)
|
|
run = devfreq->dynamic_qos;
|
|
else
|
|
run = devfreq->force_qos;
|
|
|
|
pm->ops->update_devfreq_nolock(pm, idx, run);
|
|
}
|
|
|
|
dsp_dbg("DVFS busy\n");
|
|
pm->sys->clk.ops->dump(&pm->sys->clk);
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
mutex_unlock(&pm->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_del_dynamic(struct dsp_pm_devfreq *devfreq,
|
|
int val)
|
|
{
|
|
int ret, idx;
|
|
|
|
dsp_enter();
|
|
if (!devfreq->dynamic_count[val]) {
|
|
ret = -EINVAL;
|
|
dsp_warn("dynamic count is unstable([%s][L%d]%u)\n",
|
|
devfreq->name, val,
|
|
devfreq->dynamic_count[val]);
|
|
goto p_err;
|
|
} else {
|
|
devfreq->dynamic_count[val]--;
|
|
if (devfreq->dynamic_total_count) {
|
|
devfreq->dynamic_total_count--;
|
|
} else {
|
|
ret = -EINVAL;
|
|
dsp_warn("dynamic total count is unstable([%s]%u)\n",
|
|
devfreq->name,
|
|
devfreq->dynamic_total_count);
|
|
goto p_err;
|
|
}
|
|
}
|
|
|
|
if ((val == devfreq->dynamic_qos) && (!devfreq->dynamic_count[val])) {
|
|
for (idx = val + 1; idx <= devfreq->min_qos; ++idx) {
|
|
if (idx == devfreq->min_qos) {
|
|
devfreq->dynamic_qos = idx;
|
|
break;
|
|
}
|
|
if (devfreq->dynamic_count[idx]) {
|
|
devfreq->dynamic_qos = idx;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_devfreq_idle(struct dsp_pm *pm, int val)
|
|
{
|
|
int ret, idx;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
ret = __dsp_hw_common_pm_check_valid(&pm->devfreq[idx], val);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_del_dynamic(&pm->devfreq[idx], val);
|
|
|
|
if (!pm->dvfs) {
|
|
dsp_dbg("DVFS was disabled(idle)\n");
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
if (!pm->ops->devfreq_active(pm)) {
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
pm->ops->update_devfreq_nolock_async(pm, idx,
|
|
pm->devfreq[idx].dynamic_qos);
|
|
|
|
dsp_dbg("DVFS idle\n");
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
mutex_unlock(&pm->lock);
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_devfreq_boot(struct dsp_pm *pm)
|
|
{
|
|
int idx, boot;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
if (!pm->dvfs) {
|
|
dsp_dbg("DVFS was disabled(boot)\n");
|
|
pm->sys->clk.ops->dump(&pm->sys->clk);
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
if (pm->devfreq[idx].force_qos < 0)
|
|
boot = pm->devfreq[idx].boot_qos;
|
|
else
|
|
boot = pm->devfreq[idx].force_qos;
|
|
|
|
pm->ops->update_devfreq_nolock(pm, idx, boot);
|
|
}
|
|
|
|
dsp_dbg("DVFS boot\n");
|
|
pm->sys->clk.ops->dump(&pm->sys->clk);
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_devfreq_max(struct dsp_pm *pm)
|
|
{
|
|
int idx;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
if (!pm->dvfs) {
|
|
dsp_dbg("DVFS was disabled(max)\n");
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
pm->ops->update_devfreq_nolock(pm, idx, 0);
|
|
|
|
dsp_dbg("DVFS max\n");
|
|
pm->sys->clk.ops->dump(&pm->sys->clk);
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
int dsp_hw_common_pm_update_devfreq_min(struct dsp_pm *pm)
|
|
{
|
|
int idx;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
if (!pm->dvfs) {
|
|
dsp_dbg("DVFS was disabled(min)\n");
|
|
mutex_unlock(&pm->lock);
|
|
return 0;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
pm->ops->update_devfreq_nolock(pm, idx,
|
|
pm->devfreq[idx].min_qos);
|
|
|
|
dsp_dbg("DVFS min\n");
|
|
pm->sys->clk.ops->dump(&pm->sys->clk);
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_set_boot_qos(struct dsp_pm *pm, int id, int val)
|
|
{
|
|
int ret;
|
|
|
|
dsp_enter();
|
|
if (id < 0 || id >= pm->devfreq_count) {
|
|
ret = -EINVAL;
|
|
dsp_err("devfreq id(%d) is invalid\n", id);
|
|
goto p_err;
|
|
}
|
|
|
|
ret = __dsp_hw_common_pm_check_valid(&pm->devfreq[id], val);
|
|
if (ret)
|
|
goto p_err;
|
|
|
|
pm->devfreq[id].boot_qos = val;
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_set_boot_qos(struct dsp_pm *pm, int val)
|
|
{
|
|
int ret, idx;
|
|
|
|
dsp_enter();
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
ret = __dsp_hw_common_pm_set_boot_qos(pm, idx, val);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_boost_enable(struct dsp_pm *pm)
|
|
{
|
|
int ret;
|
|
struct dsp_pm_devfreq *devfreq;
|
|
int idx;
|
|
|
|
dsp_enter();
|
|
devfreq = pm->extra_devfreq;
|
|
|
|
mutex_lock(&pm->lock);
|
|
for (idx = 0; idx < pm->extra_devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_update_devfreq_raw(&devfreq[idx],
|
|
DSP_PM_QOS_L0);
|
|
mutex_unlock(&pm->lock);
|
|
|
|
ret = pm->ops->dvfs_disable(pm, DSP_PM_QOS_L0);
|
|
if (ret)
|
|
goto p_err_dvfs;
|
|
|
|
dsp_info("boost mode of pm is enabled\n");
|
|
dsp_leave();
|
|
return 0;
|
|
p_err_dvfs:
|
|
mutex_lock(&pm->lock);
|
|
for (idx = pm->extra_devfreq_count - 1; idx >= 0; --idx)
|
|
__dsp_hw_common_pm_update_devfreq_raw(&devfreq[idx],
|
|
devfreq[idx].min_qos);
|
|
mutex_unlock(&pm->lock);
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_boost_disable(struct dsp_pm *pm)
|
|
{
|
|
struct dsp_pm_devfreq *devfreq;
|
|
int idx;
|
|
|
|
dsp_enter();
|
|
pm->ops->dvfs_enable(pm, DSP_PM_QOS_L0);
|
|
|
|
devfreq = pm->extra_devfreq;
|
|
mutex_lock(&pm->lock);
|
|
for (idx = pm->extra_devfreq_count - 1; idx >= 0; --idx)
|
|
__dsp_hw_common_pm_update_devfreq_raw(&devfreq[idx],
|
|
devfreq[idx].min_qos);
|
|
mutex_unlock(&pm->lock);
|
|
|
|
dsp_info("boost mode of pm is disabled\n");
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_enable(struct dsp_pm_devfreq *devfreq, bool dvfs)
|
|
{
|
|
int init_qos;
|
|
|
|
dsp_enter();
|
|
if (devfreq->force_qos < 0) {
|
|
if (!dvfs) {
|
|
init_qos = devfreq->static_qos;
|
|
dsp_info("devfreq[%s] is enabled(L%d)(static)\n",
|
|
devfreq->name, init_qos);
|
|
} else {
|
|
init_qos = devfreq->min_qos;
|
|
dsp_info("devfreq[%s] is enabled(L%d)(dynamic)\n",
|
|
devfreq->name, init_qos);
|
|
}
|
|
} else {
|
|
init_qos = devfreq->force_qos;
|
|
dsp_info("devfreq[%s] is enabled(L%d)(force)\n",
|
|
devfreq->name, init_qos);
|
|
}
|
|
|
|
#ifndef ENABLE_DSP_VELOCE
|
|
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
|
exynos_pm_qos_add_request(&devfreq->req, devfreq->class_id,
|
|
devfreq->table[init_qos]);
|
|
#else
|
|
pm_qos_add_request(&devfreq->req, devfreq->class_id,
|
|
devfreq->table[init_qos]);
|
|
#endif
|
|
#endif
|
|
devfreq->current_qos = init_qos;
|
|
devfreq->pm->ops->update_freq_info(devfreq->pm, devfreq->id);
|
|
dsp_leave();
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_extra_enable(struct dsp_pm_devfreq *devfreq)
|
|
{
|
|
dsp_enter();
|
|
#ifndef ENABLE_DSP_VELOCE
|
|
if (devfreq->use_freq_qos) {
|
|
devfreq->policy = cpufreq_cpu_get(devfreq->class_id);
|
|
if (!devfreq->policy) {
|
|
dsp_err("Failed to get policy[%s/%d]\n",
|
|
devfreq->name, devfreq->class_id);
|
|
return;
|
|
}
|
|
|
|
#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE
|
|
freq_qos_tracer_add_request(&devfreq->policy->constraints,
|
|
&devfreq->freq_qos_req, FREQ_QOS_MIN,
|
|
devfreq->table[devfreq->min_qos]);
|
|
#endif
|
|
} else {
|
|
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
|
exynos_pm_qos_add_request(&devfreq->req, devfreq->class_id,
|
|
devfreq->table[devfreq->min_qos]);
|
|
#else
|
|
pm_qos_add_request(&devfreq->req, devfreq->class_id,
|
|
devfreq->table[devfreq->min_qos]);
|
|
#endif
|
|
}
|
|
#endif
|
|
devfreq->current_qos = devfreq->min_qos;
|
|
dsp_dbg("devfreq[%s] is enabled(L%d)\n",
|
|
devfreq->name, devfreq->current_qos);
|
|
dsp_leave();
|
|
}
|
|
|
|
int dsp_hw_common_pm_enable(struct dsp_pm *pm)
|
|
{
|
|
int idx;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_enable(&pm->devfreq[idx], pm->dvfs);
|
|
|
|
for (idx = 0; idx < pm->extra_devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_extra_enable(&pm->extra_devfreq[idx]);
|
|
|
|
#ifdef ENABLE_DSP_VELOCE
|
|
pm_for_veloce = 1;
|
|
#endif
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_disable(struct dsp_pm_devfreq *devfreq)
|
|
{
|
|
dsp_enter();
|
|
kthread_flush_work(&devfreq->work);
|
|
#ifndef ENABLE_DSP_VELOCE
|
|
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
|
exynos_pm_qos_remove_request(&devfreq->req);
|
|
#else
|
|
pm_qos_remove_request(&devfreq->req);
|
|
#endif
|
|
#endif
|
|
dsp_info("devfreq[%s] is disabled\n", devfreq->name);
|
|
dsp_leave();
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_extra_disable(struct dsp_pm_devfreq *devfreq)
|
|
{
|
|
dsp_enter();
|
|
#ifndef ENABLE_DSP_VELOCE
|
|
if (devfreq->use_freq_qos) {
|
|
#if KERNEL_VERSION(5, 4, 0) <= LINUX_VERSION_CODE
|
|
freq_qos_tracer_remove_request(&devfreq->freq_qos_req);
|
|
#endif
|
|
devfreq->policy = NULL;
|
|
} else {
|
|
#if IS_ENABLED(CONFIG_EXYNOS_PM_QOS)
|
|
exynos_pm_qos_remove_request(&devfreq->req);
|
|
#else
|
|
pm_qos_remove_request(&devfreq->req);
|
|
#endif
|
|
}
|
|
#endif
|
|
dsp_dbg("devfreq[%s] is disabled\n", devfreq->name);
|
|
dsp_leave();
|
|
}
|
|
|
|
int dsp_hw_common_pm_disable(struct dsp_pm *pm)
|
|
{
|
|
int idx;
|
|
|
|
dsp_enter();
|
|
mutex_lock(&pm->lock);
|
|
#ifdef ENABLE_DSP_VELOCE
|
|
pm_for_veloce = 0;
|
|
#endif
|
|
|
|
for (idx = pm->extra_devfreq_count - 1; idx >= 0; --idx)
|
|
__dsp_hw_common_pm_extra_disable(&pm->extra_devfreq[idx]);
|
|
|
|
for (idx = pm->devfreq_count - 1; idx >= 0; --idx)
|
|
__dsp_hw_common_pm_disable(&pm->devfreq[idx]);
|
|
|
|
mutex_unlock(&pm->lock);
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_check_class_id(struct dsp_pm_devfreq *devfreq)
|
|
{
|
|
dsp_enter();
|
|
if (devfreq->use_freq_qos) {
|
|
dsp_dbg("devfreq[%s] uses freq_qos(%d)\n",
|
|
devfreq->name, devfreq->class_id);
|
|
} else {
|
|
#ifndef ENABLE_DSP_VELOCE
|
|
if (!devfreq->class_id) {
|
|
dsp_err("devfreq[%s] class_id must not be zero(%d)\n",
|
|
devfreq->name, devfreq->class_id);
|
|
return -EINVAL;
|
|
}
|
|
#endif
|
|
dsp_dbg("devfreq[%s] class_id is %d\n", devfreq->name,
|
|
devfreq->class_id);
|
|
}
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
int dsp_hw_common_pm_open(struct dsp_pm *pm)
|
|
{
|
|
int idx, ret;
|
|
|
|
dsp_enter();
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
ret = __dsp_hw_common_pm_check_class_id(&pm->devfreq[idx]);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->extra_devfreq_count; ++idx) {
|
|
ret = __dsp_hw_common_pm_check_class_id(
|
|
&pm->extra_devfreq[idx]);
|
|
if (ret)
|
|
goto p_err;
|
|
}
|
|
|
|
dsp_leave();
|
|
return 0;
|
|
p_err:
|
|
return ret;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_init(struct dsp_pm_devfreq *devfreq)
|
|
{
|
|
dsp_enter();
|
|
devfreq->static_qos = devfreq->min_qos;
|
|
devfreq->static_total_count = 0;
|
|
memset(devfreq->static_count, 0x0, DSP_DEVFREQ_RESERVED_NUM << 2);
|
|
devfreq->dynamic_qos = devfreq->min_qos;
|
|
devfreq->dynamic_total_count = 0;
|
|
memset(devfreq->dynamic_count, 0x0, DSP_DEVFREQ_RESERVED_NUM << 2);
|
|
devfreq->force_qos = -1;
|
|
|
|
devfreq->async_status = -1;
|
|
devfreq->async_qos = -1;
|
|
dsp_leave();
|
|
}
|
|
|
|
int dsp_hw_common_pm_close(struct dsp_pm *pm)
|
|
{
|
|
int idx;
|
|
|
|
dsp_enter();
|
|
if (!pm->dvfs_lock) {
|
|
dsp_info("DVFS is reinitialized\n");
|
|
pm->dvfs = true;
|
|
pm->dvfs_disable_count = 0;
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx)
|
|
__dsp_hw_common_pm_init(&pm->devfreq[idx]);
|
|
}
|
|
dsp_leave();
|
|
return 0;
|
|
}
|
|
|
|
static int __dsp_hw_common_pm_worker_init(struct dsp_pm *pm)
|
|
{
|
|
int ret;
|
|
struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
|
|
|
|
kthread_init_worker(&pm->async_worker);
|
|
|
|
pm->async_task = kthread_run(kthread_worker_fn, &pm->async_worker,
|
|
"dsp_pm_worker");
|
|
if (IS_ERR(pm->async_task)) {
|
|
ret = PTR_ERR(pm->async_task);
|
|
dsp_err("kthread of pm is not running(%d)\n", ret);
|
|
goto p_err_kthread;
|
|
}
|
|
|
|
ret = sched_setscheduler_nocheck(pm->async_task, SCHED_FIFO, ¶m);
|
|
if (ret) {
|
|
dsp_err("Failed to set scheduler of pm worker(%d)\n", ret);
|
|
goto p_err_sched;
|
|
}
|
|
|
|
return ret;
|
|
p_err_sched:
|
|
kthread_stop(pm->async_task);
|
|
p_err_kthread:
|
|
return ret;
|
|
}
|
|
|
|
int dsp_hw_common_pm_probe(struct dsp_pm *pm, void *sys, pm_handler_t handler)
|
|
{
|
|
int ret;
|
|
struct dsp_pm_devfreq *devfreq;
|
|
unsigned int idx;
|
|
|
|
dsp_enter();
|
|
pm->sys = sys;
|
|
pm->dev = pm->sys->dev;
|
|
|
|
pm->devfreq = kcalloc(pm->devfreq_count, sizeof(*devfreq), GFP_KERNEL);
|
|
if (!pm->devfreq) {
|
|
ret = -ENOMEM;
|
|
dsp_err("Failed to alloc pm_devfreq\n");
|
|
goto p_err_devfreq;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->devfreq_count; ++idx) {
|
|
devfreq = &pm->devfreq[idx];
|
|
devfreq->pm = pm;
|
|
|
|
devfreq->id = idx;
|
|
devfreq->force_qos = -1;
|
|
|
|
kthread_init_work(&devfreq->work, dsp_hw_common_devfreq_async);
|
|
spin_lock_init(&devfreq->slock);
|
|
devfreq->async_status = -1;
|
|
devfreq->async_qos = -1;
|
|
}
|
|
|
|
pm->extra_devfreq = kzalloc(sizeof(*devfreq) * pm->extra_devfreq_count,
|
|
GFP_KERNEL);
|
|
if (!pm->extra_devfreq) {
|
|
ret = -ENOMEM;
|
|
dsp_err("Failed to alloc extra_devfreq\n");
|
|
goto p_err_extra_devfreq;
|
|
}
|
|
|
|
for (idx = 0; idx < pm->extra_devfreq_count; ++idx) {
|
|
devfreq = &pm->extra_devfreq[idx];
|
|
devfreq->pm = pm;
|
|
|
|
devfreq->id = idx;
|
|
}
|
|
|
|
pm_runtime_enable(pm->dev);
|
|
mutex_init(&pm->lock);
|
|
pm->dvfs = true;
|
|
pm->dvfs_lock = false;
|
|
|
|
ret = __dsp_hw_common_pm_worker_init(pm);
|
|
if (ret)
|
|
goto p_err_worker;
|
|
|
|
if (handler)
|
|
pm->update_devfreq_handler = handler;
|
|
|
|
dsp_leave();
|
|
return 0;
|
|
p_err_worker:
|
|
kfree(pm->extra_devfreq);
|
|
p_err_extra_devfreq:
|
|
kfree(pm->devfreq);
|
|
p_err_devfreq:
|
|
return ret;
|
|
}
|
|
|
|
static void __dsp_hw_common_pm_worker_deinit(struct dsp_pm *pm)
|
|
{
|
|
dsp_enter();
|
|
kthread_stop(pm->async_task);
|
|
dsp_leave();
|
|
}
|
|
|
|
void dsp_hw_common_pm_remove(struct dsp_pm *pm)
|
|
{
|
|
dsp_enter();
|
|
__dsp_hw_common_pm_worker_deinit(pm);
|
|
mutex_destroy(&pm->lock);
|
|
pm_runtime_disable(pm->dev);
|
|
kfree(pm->extra_devfreq);
|
|
kfree(pm->devfreq);
|
|
dsp_leave();
|
|
}
|
|
|
|
int dsp_hw_common_pm_set_ops(struct dsp_pm *pm, const void *ops)
|
|
{
|
|
dsp_enter();
|
|
pm->ops = ops;
|
|
dsp_leave();
|
|
return 0;
|
|
}
|