diff --git a/sound/soc/rockchip/rockchip_pdm.c b/sound/soc/rockchip/rockchip_pdm.c index 52446103b1c9..608e4558c551 100644 --- a/sound/soc/rockchip/rockchip_pdm.c +++ b/sound/soc/rockchip/rockchip_pdm.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -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);