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");