ASoC: rockchip: i2s_tdm: Add support for clk compensation

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

e.g:

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

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

/# amixer -- cset numid=3 -10
numid=3,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=3 10
numid=3,iface=PCM,name='PCM Clk Compensation In PPM'
  ; type=INTEGER,access=rw------,values=1,min=-1000,max=1000,step=1
  : values=10

Change-Id: Id8620ef942e7be20eb6ca502cc198ad97da813f1
Signed-off-by: Sugar Zhang <sugar.zhang@rock-chips.com>
This commit is contained in:
Sugar Zhang
2020-11-30 21:42:24 +08:00
committed by Tao Huang
parent 8d9702cd43
commit 9bc2eb6c70

View File

@@ -18,6 +18,7 @@
#include <linux/of_address.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clk/rockchip.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
@@ -32,6 +33,8 @@
#define DEFAULT_MCLK_FS 256
#define CH_GRP_MAX 4 /* The max channel 8 / 2 */
#define MULTIPLEX_CH_MAX 10
#define CLK_PPM_MIN (-1000)
#define CLK_PPM_MAX (1000)
struct txrx_config {
u32 addr;
@@ -82,10 +85,15 @@ struct rk_i2s_tdm_dev {
bool tdm_mode;
unsigned int mclk_rx_freq;
unsigned int mclk_tx_freq;
unsigned int mclk_root0_freq;
unsigned int mclk_root1_freq;
unsigned int mclk_root0_initial_freq;
unsigned int mclk_root1_initial_freq;
unsigned int bclk_fs;
unsigned int clk_trcm;
unsigned int i2s_sdis[CH_GRP_MAX];
unsigned int i2s_sdos[CH_GRP_MAX];
int clk_ppm;
int tx_reset_id;
int rx_reset_id;
atomic_t refcount;
@@ -658,13 +666,39 @@ static void rockchip_i2s_tdm_xfer_resume(struct snd_pcm_substream *substream,
I2S_XFER_RXS_START);
}
static int rockchip_i2s_tdm_clk_set_rate(struct rk_i2s_tdm_dev *i2s_tdm,
struct clk *clk, unsigned long rate)
{
unsigned long rate_target;
int delta, ret;
ret = rockchip_pll_clk_compensation(clk, i2s_tdm->clk_ppm);
if (!ret)
return ret;
delta = (i2s_tdm->clk_ppm < 0) ? -1 : 1;
delta *= (int)div64_u64((uint64_t)rate * (uint64_t)abs(i2s_tdm->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_i2s_tdm_calibrate_mclk(struct rk_i2s_tdm_dev *i2s_tdm,
struct snd_pcm_substream *substream,
unsigned int lrck_freq)
{
struct clk *mclk_root;
struct clk *mclk_parent;
unsigned int mclk_root_freq;
unsigned int mclk_root_initial_freq;
unsigned int mclk_parent_freq;
unsigned int div;
int ret;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
@@ -682,6 +716,8 @@ static int rockchip_i2s_tdm_calibrate_mclk(struct rk_i2s_tdm_dev *i2s_tdm,
case 96000:
case 192000:
mclk_root = i2s_tdm->mclk_root0;
mclk_root_freq = i2s_tdm->mclk_root0_freq;
mclk_root_initial_freq = i2s_tdm->mclk_root0_initial_freq;
mclk_parent_freq = DEFAULT_MCLK_FS * 192000;
break;
case 11025:
@@ -690,6 +726,8 @@ static int rockchip_i2s_tdm_calibrate_mclk(struct rk_i2s_tdm_dev *i2s_tdm,
case 88200:
case 176400:
mclk_root = i2s_tdm->mclk_root1;
mclk_root_freq = i2s_tdm->mclk_root1_freq;
mclk_root_initial_freq = i2s_tdm->mclk_root1_initial_freq;
mclk_parent_freq = DEFAULT_MCLK_FS * 176400;
break;
default:
@@ -702,6 +740,21 @@ static int rockchip_i2s_tdm_calibrate_mclk(struct rk_i2s_tdm_dev *i2s_tdm,
if (ret)
goto out;
if (mclk_root_freq % mclk_parent_freq) {
div = DIV_ROUND_CLOSEST(mclk_root_initial_freq, mclk_parent_freq);
if (!div)
return -EINVAL;
mclk_root_freq = mclk_parent_freq * round_up(div, 2);
i2s_tdm->clk_ppm = 0;
ret = clk_set_rate(mclk_root, mclk_root_freq);
if (ret)
goto out;
i2s_tdm->mclk_root0_freq = clk_get_rate(i2s_tdm->mclk_root0);
i2s_tdm->mclk_root1_freq = clk_get_rate(i2s_tdm->mclk_root1);
}
ret = clk_set_rate(mclk_parent, mclk_parent_freq);
if (ret)
goto out;
@@ -1037,6 +1090,64 @@ static int rockchip_i2s_tdm_set_sysclk(struct snd_soc_dai *cpu_dai, int stream,
return 0;
}
static int rockchip_i2s_tdm_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_i2s_tdm_clk_compensation_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai);
ucontrol->value.integer.value[0] = i2s_tdm->clk_ppm;
return 0;
}
static int rockchip_i2s_tdm_clk_compensation_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol);
struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai);
int ret = 0;
if ((ucontrol->value.integer.value[0] < CLK_PPM_MIN) ||
(ucontrol->value.integer.value[0] > CLK_PPM_MAX))
return -EINVAL;
i2s_tdm->clk_ppm = ucontrol->value.integer.value[0];
ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, i2s_tdm->mclk_root0,
i2s_tdm->mclk_root0_freq);
if (ret)
return ret;
if (clk_is_match(i2s_tdm->mclk_root0, i2s_tdm->mclk_root1))
return 0;
ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, i2s_tdm->mclk_root1,
i2s_tdm->mclk_root1_freq);
return ret;
}
static struct snd_kcontrol_new rockchip_i2s_tdm_compensation_control = {
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
.name = "PCM Clk Compensation In PPM",
.info = rockchip_i2s_tdm_clk_compensation_info,
.get = rockchip_i2s_tdm_clk_compensation_get,
.put = rockchip_i2s_tdm_clk_compensation_put,
};
static int rockchip_i2s_tdm_dai_probe(struct snd_soc_dai *dai)
{
struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai);
@@ -1044,6 +1155,9 @@ static int rockchip_i2s_tdm_dai_probe(struct snd_soc_dai *dai)
dai->capture_dma_data = &i2s_tdm->capture_dma_data;
dai->playback_dma_data = &i2s_tdm->playback_dma_data;
if (i2s_tdm->mclk_calibrate)
snd_soc_add_dai_controls(dai, &rockchip_i2s_tdm_compensation_control, 1);
return 0;
}
@@ -1590,6 +1704,11 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev)
i2s_tdm->mclk_root1 = devm_clk_get(&pdev->dev, "mclk_root1");
if (IS_ERR(i2s_tdm->mclk_root1))
return PTR_ERR(i2s_tdm->mclk_root1);
i2s_tdm->mclk_root0_initial_freq = clk_get_rate(i2s_tdm->mclk_root0);
i2s_tdm->mclk_root1_initial_freq = clk_get_rate(i2s_tdm->mclk_root1);
i2s_tdm->mclk_root0_freq = i2s_tdm->mclk_root0_initial_freq;
i2s_tdm->mclk_root1_freq = i2s_tdm->mclk_root1_initial_freq;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);