ASoC: rockchip: pdm: Add support for clk compensation

This patch introduces a method to handle clk drift and compensation.

e.g:

/# amixer contents
numid=1,iface=PCM,name='PCM Clk Compensation In PPM'
; type=INTEGER,access=rw------,values=1,min=-1000,max=1000,step=1
: values=0

/# arecord -D hw:0,0 --period-size=1024 --buffer-size=4096 -r
16000 -c 2 -f s16_le /dev/zero &

/# amixer -- cset numid=1 -10
numid=1,iface=PCM,name='PCM Clk Compensation In PPM'
; type=INTEGER,access=rw------,values=1,min=-1000,max=1000,step=1
: values=-10

/# amixer -- cset numid=1 10
numid=1,iface=PCM,name='PCM Clk Compensation In PPM'
; type=INTEGER,access=rw------,values=1,min=-1000,max=1000,step=1
: values=10

Change-Id: Ibc70ce8ccec6dddb082161fa544d066c706c37bc
Signed-off-by: Sugar Zhang <sugar.zhang@rock-chips.com>
This commit is contained in:
Sugar Zhang
2021-01-11 17:43:05 +08:00
committed by Tao Huang
parent ef3def055b
commit 70838da2b1

View File

@@ -16,6 +16,7 @@
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/clk/rockchip.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
@@ -30,6 +31,8 @@
#define PDM_DMA_BURST_SIZE (8) /* size * width: 8*4 = 32 bytes */
#define PDM_SIGNOFF_CLK_RATE (100000000)
#define PDM_PATH_MAX (4)
#define CLK_PPM_MIN (-1000)
#define CLK_PPM_MAX (1000)
enum rk_pdm_version {
RK_PDM_RK3229,
@@ -40,11 +43,16 @@ enum rk_pdm_version {
struct rk_pdm_dev {
struct device *dev;
struct clk *clk;
struct clk *clk_root;
struct clk *hclk;
struct regmap *regmap;
struct snd_dmaengine_dai_dma_data capture_dma_data;
struct reset_control *reset;
enum rk_pdm_version version;
unsigned int clk_root_rate;
unsigned int clk_root_initial_rate;
int clk_ppm;
bool clk_calibrate;
};
struct rk_pdm_clkref {
@@ -100,6 +108,11 @@ static unsigned int get_pdm_clk(struct rk_pdm_dev *pdm, unsigned int sr,
div = sr / clkref[i].sr;
if ((div & (div - 1)) == 0) {
*clk_out = clkref[i].clk_out;
if (pdm->clk_calibrate) {
clk = clkref[i].clk;
*clk_src = clk;
break;
}
rate = clk_round_rate(pdm->clk, clkref[i].clk);
if (rate != clkref[i].clk)
continue;
@@ -202,29 +215,44 @@ static void rockchip_pdm_rxctrl(struct rk_pdm_dev *pdm, int on)
}
}
static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
static int rockchip_pdm_set_samplerate(struct rk_pdm_dev *pdm,
unsigned int samplerate)
{
struct rk_pdm_dev *pdm = to_info(dai);
unsigned int val = 0;
unsigned int clk_rate, clk_div, samplerate;
unsigned int clk_rate, clk_div;
unsigned int clk_src, clk_out = 0;
unsigned int val = 0, div = 0, rate;
unsigned long m, n;
bool change;
int ret;
bool change;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return 0;
samplerate = params_rate(params);
clk_rate = get_pdm_clk(pdm, samplerate, &clk_src, &clk_out);
if (!clk_rate)
return -EINVAL;
if (pdm->clk_calibrate) {
ret = clk_set_parent(pdm->clk, pdm->clk_root);
if (ret)
return ret;
rate = pdm->clk_root_rate;
if (rate % clk_src) {
div = DIV_ROUND_CLOSEST(pdm->clk_root_initial_rate, clk_src);
if (!div)
return -EINVAL;
rate = clk_src * round_up(div, 2);
pdm->clk_ppm = 0;
ret = clk_set_rate(pdm->clk_root, rate);
if (ret)
return ret;
pdm->clk_root_rate = clk_get_rate(pdm->clk_root);
}
}
ret = clk_set_rate(pdm->clk, clk_src);
if (ret)
return -EINVAL;
return ret;
if (pdm->version == RK_PDM_RK3308 ||
pdm->version == RK_PDM_RV1126) {
@@ -267,6 +295,24 @@ static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream,
regmap_update_bits(pdm->regmap, PDM_CLK_CTRL, PDM_DS_RATIO_MSK, val);
}
return 0;
}
static int rockchip_pdm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct rk_pdm_dev *pdm = to_info(dai);
unsigned int val = 0;
int ret;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
return 0;
ret = rockchip_pdm_set_samplerate(pdm, params_rate(params));
if (ret)
return ret;
regmap_update_bits(pdm->regmap, PDM_HPF_CTRL,
PDM_HPF_CF_MSK, PDM_HPF_60HZ);
regmap_update_bits(pdm->regmap, PDM_HPF_CTRL,
@@ -378,12 +424,84 @@ static int rockchip_pdm_trigger(struct snd_pcm_substream *substream, int cmd,
return ret;
}
static int rockchip_pdm_clk_set_rate(struct rk_pdm_dev *pdm,
struct clk *clk, unsigned long rate)
{
unsigned long rate_target;
int delta, ret;
ret = rockchip_pll_clk_compensation(clk, pdm->clk_ppm);
if (!ret)
return ret;
delta = (pdm->clk_ppm < 0) ? -1 : 1;
delta *= (int)div64_u64((uint64_t)rate * (uint64_t)abs(pdm->clk_ppm) + 500000, 1000000);
rate_target = rate + delta;
if (!rate_target)
return -EINVAL;
ret = clk_set_rate(clk, rate_target);
return ret;
}
static int rockchip_pdm_clk_compensation_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = CLK_PPM_MIN;
uinfo->value.integer.max = CLK_PPM_MAX;
uinfo->value.integer.step = 1;
return 0;
}
static int rockchip_pdm_clk_compensation_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct rk_pdm_dev *pdm = snd_soc_dai_get_drvdata(dai);
ucontrol->value.integer.value[0] = pdm->clk_ppm;
return 0;
}
static int rockchip_pdm_clk_compensation_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct rk_pdm_dev *pdm = snd_soc_dai_get_drvdata(dai);
if ((ucontrol->value.integer.value[0] < CLK_PPM_MIN) ||
(ucontrol->value.integer.value[0] > CLK_PPM_MAX))
return -EINVAL;
pdm->clk_ppm = ucontrol->value.integer.value[0];
return rockchip_pdm_clk_set_rate(pdm, pdm->clk_root, pdm->clk_root_rate);
}
static struct snd_kcontrol_new rockchip_pdm_compensation_control = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Clk Compensation In PPM",
.info = rockchip_pdm_clk_compensation_info,
.get = rockchip_pdm_clk_compensation_get,
.put = rockchip_pdm_clk_compensation_put,
};
static int rockchip_pdm_dai_probe(struct snd_soc_dai *dai)
{
struct rk_pdm_dev *pdm = to_info(dai);
dai->capture_dma_data = &pdm->capture_dma_data;
if (pdm->clk_calibrate)
snd_soc_add_dai_controls(dai, &rockchip_pdm_compensation_control, 1);
return 0;
}
@@ -616,6 +734,17 @@ static int rockchip_pdm_probe(struct platform_device *pdev)
pdm->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, pdm);
pdm->clk_calibrate =
of_property_read_bool(node, "rockchip,mclk-calibrate");
if (pdm->clk_calibrate) {
pdm->clk_root = devm_clk_get(&pdev->dev, "pdm_clk_root");
if (IS_ERR(pdm->clk_root))
return PTR_ERR(pdm->clk_root);
pdm->clk_root_initial_rate = clk_get_rate(pdm->clk_root);
pdm->clk_root_rate = pdm->clk_root_initial_rate;
}
pdm->clk = devm_clk_get(&pdev->dev, "pdm_clk");
if (IS_ERR(pdm->clk))
return PTR_ERR(pdm->clk);