183 lines
4.0 KiB
C
183 lines
4.0 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Rockchip Utils API
|
|
*
|
|
* Copyright (c) 2023 Rockchip Electronics Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/module.h>
|
|
#include <linux/notifier.h>
|
|
#include <soc/rockchip/rockchip_dmc.h>
|
|
#include <soc/rockchip/rockchip-system-status.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/dmaengine_pcm.h>
|
|
#include "rockchip_utils.h"
|
|
|
|
#define DMC_STALL_TIME_US_DEFAULT 100
|
|
#define TIME_MARGIN_US 20
|
|
|
|
static DEFINE_MUTEX(list_mutex);
|
|
static LIST_HEAD(substream_ref_list);
|
|
|
|
struct substream_ref {
|
|
struct list_head node;
|
|
struct snd_pcm_substream *substream;
|
|
};
|
|
|
|
static int substream_ref_new(struct snd_pcm_substream *substream)
|
|
{
|
|
struct substream_ref *ref = NULL;
|
|
bool found = false;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&list_mutex);
|
|
list_for_each_entry(ref, &substream_ref_list, node) {
|
|
if (ref->substream == substream) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
ret = -EEXIST;
|
|
goto _err_unlock;
|
|
}
|
|
|
|
ref = kzalloc(sizeof(*ref), GFP_KERNEL);
|
|
if (!ref) {
|
|
ret = -ENOMEM;
|
|
goto _err_unlock;
|
|
}
|
|
|
|
ref->substream = substream;
|
|
|
|
list_add(&ref->node, &substream_ref_list);
|
|
|
|
_err_unlock:
|
|
mutex_unlock(&list_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool substream_ref_found(struct snd_pcm_substream *substream)
|
|
{
|
|
struct substream_ref *ref = NULL, *_ref = NULL;
|
|
bool found = false;
|
|
|
|
mutex_lock(&list_mutex);
|
|
list_for_each_entry_safe(ref, _ref, &substream_ref_list, node) {
|
|
if (ref->substream == substream) {
|
|
list_del(&ref->node);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&list_mutex);
|
|
|
|
if (found)
|
|
kfree(ref);
|
|
|
|
return found;
|
|
}
|
|
|
|
static bool fifo_bigger_than_stall(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai,
|
|
int fifo_word)
|
|
{
|
|
unsigned int rate = params_rate(params);
|
|
unsigned int channels = params_channels(params);
|
|
int width = params_physical_width(params);
|
|
int fifo_time, stall_time, data_word;
|
|
|
|
dev_dbg(dai->dev, "stream[%d]: %px, rate: %u, channels: %u, width: %d\n",
|
|
substream->stream, substream, rate, channels, width);
|
|
|
|
stall_time = rockchip_dmcfreq_get_stall_time_ns() / 1000;
|
|
if (!stall_time)
|
|
stall_time = DMC_STALL_TIME_US_DEFAULT;
|
|
|
|
stall_time += TIME_MARGIN_US;
|
|
|
|
data_word = rate * channels * width / 32;
|
|
|
|
if (!fifo_word || !data_word)
|
|
return true;
|
|
|
|
fifo_time = 1000000 * fifo_word / data_word;
|
|
|
|
dev_dbg(dai->dev, "data: %d, fifo: %d, fifo time: %d us, stall time: %d us\n",
|
|
data_word, fifo_word, fifo_time, stall_time);
|
|
|
|
return (fifo_time > stall_time);
|
|
}
|
|
|
|
void rockchip_utils_get_performance(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai,
|
|
int fifo_word)
|
|
{
|
|
might_sleep();
|
|
|
|
if (fifo_bigger_than_stall(substream, params, dai, fifo_word))
|
|
return;
|
|
|
|
if (substream_ref_new(substream))
|
|
return;
|
|
|
|
dev_dbg(dai->dev, "%s: stream[%d]: %px\n",
|
|
__func__, substream->stream, substream);
|
|
|
|
rockchip_set_system_status(SYS_STATUS_PERFORMANCE);
|
|
}
|
|
EXPORT_SYMBOL_GPL(rockchip_utils_get_performance);
|
|
|
|
void rockchip_utils_put_performance(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
might_sleep();
|
|
|
|
if (!substream_ref_found(substream))
|
|
return;
|
|
|
|
dev_dbg(dai->dev, "%s: stream[%d]: %px\n",
|
|
__func__, substream->stream, substream);
|
|
|
|
rockchip_clear_system_status(SYS_STATUS_PERFORMANCE);
|
|
}
|
|
EXPORT_SYMBOL_GPL(rockchip_utils_put_performance);
|
|
|
|
/*
|
|
* It's workaround for GKI.
|
|
*
|
|
* Can be replaced by API clk_gate_endisable directly
|
|
* once the symbol exported been merged.
|
|
*/
|
|
int rockchip_utils_clk_gate_endisable(struct device *dev, struct clk *clk, int enable)
|
|
{
|
|
struct clk_hw *hw;
|
|
struct clk_gate *gate;
|
|
unsigned int val;
|
|
|
|
hw = __clk_get_hw(clk);
|
|
if (!hw)
|
|
return -EINVAL;
|
|
|
|
gate = to_clk_gate(hw);
|
|
|
|
if (enable)
|
|
val = BIT(gate->bit_idx + 16);
|
|
else
|
|
val = BIT(gate->bit_idx + 16) | BIT(gate->bit_idx);
|
|
|
|
writel(val, gate->reg);
|
|
|
|
dev_dbg(dev, "%s: reg: %px, val: 0x%08x\n", __func__, gate->reg, val);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(rockchip_utils_clk_gate_endisable);
|
|
|
|
MODULE_LICENSE("GPL");
|