|
@@ -48,6 +48,9 @@
|
|
#define WM8960_DISOP 0x40
|
|
#define WM8960_DISOP 0x40
|
|
#define WM8960_DRES_MASK 0x30
|
|
#define WM8960_DRES_MASK 0x30
|
|
|
|
|
|
|
|
+static bool is_pll_freq_available(unsigned int source, unsigned int target);
|
|
|
|
+static int wm8960_set_pll(struct snd_soc_codec *codec,
|
|
|
|
+ unsigned int freq_in, unsigned int freq_out);
|
|
/*
|
|
/*
|
|
* wm8960 register cache
|
|
* wm8960 register cache
|
|
* We can't read the WM8960 register space when we are
|
|
* We can't read the WM8960 register space when we are
|
|
@@ -126,9 +129,12 @@ struct wm8960_priv {
|
|
struct snd_soc_dapm_widget *rout1;
|
|
struct snd_soc_dapm_widget *rout1;
|
|
struct snd_soc_dapm_widget *out3;
|
|
struct snd_soc_dapm_widget *out3;
|
|
bool deemph;
|
|
bool deemph;
|
|
- int playback_fs;
|
|
|
|
|
|
+ int lrclk;
|
|
int bclk;
|
|
int bclk;
|
|
int sysclk;
|
|
int sysclk;
|
|
|
|
+ int clk_id;
|
|
|
|
+ int freq_in;
|
|
|
|
+ bool is_stream_in_use[2];
|
|
struct wm8960_data pdata;
|
|
struct wm8960_data pdata;
|
|
};
|
|
};
|
|
|
|
|
|
@@ -164,8 +170,8 @@ static int wm8960_set_deemph(struct snd_soc_codec *codec)
|
|
if (wm8960->deemph) {
|
|
if (wm8960->deemph) {
|
|
best = 1;
|
|
best = 1;
|
|
for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
|
|
for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) {
|
|
- if (abs(deemph_settings[i] - wm8960->playback_fs) <
|
|
|
|
- abs(deemph_settings[best] - wm8960->playback_fs))
|
|
|
|
|
|
+ if (abs(deemph_settings[i] - wm8960->lrclk) <
|
|
|
|
+ abs(deemph_settings[best] - wm8960->lrclk))
|
|
best = i;
|
|
best = i;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -565,6 +571,9 @@ static struct {
|
|
{ 8000, 5 },
|
|
{ 8000, 5 },
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+/* -1 for reserved value */
|
|
|
|
+static const int sysclk_divs[] = { 1, -1, 2, -1 };
|
|
|
|
+
|
|
/* Multiply 256 for internal 256 div */
|
|
/* Multiply 256 for internal 256 div */
|
|
static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 };
|
|
static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 };
|
|
|
|
|
|
@@ -574,61 +583,110 @@ static const int bclk_divs[] = {
|
|
120, 160, 220, 240, 320, 320, 320
|
|
120, 160, 220, 240, 320, 320, 320
|
|
};
|
|
};
|
|
|
|
|
|
-static void wm8960_configure_clocking(struct snd_soc_codec *codec,
|
|
|
|
- bool tx, int lrclk)
|
|
|
|
|
|
+static int wm8960_configure_clocking(struct snd_soc_codec *codec)
|
|
{
|
|
{
|
|
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
+ int sysclk, bclk, lrclk, freq_out, freq_in;
|
|
u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
|
|
u16 iface1 = snd_soc_read(codec, WM8960_IFACE1);
|
|
- u16 iface2 = snd_soc_read(codec, WM8960_IFACE2);
|
|
|
|
- u32 sysclk;
|
|
|
|
- int i, j;
|
|
|
|
|
|
+ int i, j, k;
|
|
|
|
|
|
if (!(iface1 & (1<<6))) {
|
|
if (!(iface1 & (1<<6))) {
|
|
dev_dbg(codec->dev,
|
|
dev_dbg(codec->dev,
|
|
"Codec is slave mode, no need to configure clock\n");
|
|
"Codec is slave mode, no need to configure clock\n");
|
|
- return;
|
|
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) {
|
|
|
|
+ dev_err(codec->dev, "No MCLK configured\n");
|
|
|
|
+ return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
- if (!wm8960->sysclk) {
|
|
|
|
- dev_dbg(codec->dev, "No SYSCLK configured\n");
|
|
|
|
- return;
|
|
|
|
|
|
+ freq_in = wm8960->freq_in;
|
|
|
|
+ bclk = wm8960->bclk;
|
|
|
|
+ lrclk = wm8960->lrclk;
|
|
|
|
+ /*
|
|
|
|
+ * If it's sysclk auto mode, check if the MCLK can provide sysclk or
|
|
|
|
+ * not. If MCLK can provide sysclk, using MCLK to provide sysclk
|
|
|
|
+ * directly. Otherwise, auto select a available pll out frequency
|
|
|
|
+ * and set PLL.
|
|
|
|
+ */
|
|
|
|
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO) {
|
|
|
|
+ /* disable the PLL and using MCLK to provide sysclk */
|
|
|
|
+ wm8960_set_pll(codec, 0, 0);
|
|
|
|
+ freq_out = freq_in;
|
|
|
|
+ } else if (wm8960->sysclk) {
|
|
|
|
+ freq_out = wm8960->sysclk;
|
|
|
|
+ } else {
|
|
|
|
+ dev_err(codec->dev, "No SYSCLK configured\n");
|
|
|
|
+ return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
- if (!wm8960->bclk || !lrclk) {
|
|
|
|
- dev_dbg(codec->dev, "No audio clocks configured\n");
|
|
|
|
- return;
|
|
|
|
|
|
+ /* check if the sysclk frequency is available. */
|
|
|
|
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
|
|
|
|
+ if (sysclk_divs[i] == -1)
|
|
|
|
+ continue;
|
|
|
|
+ sysclk = freq_out / sysclk_divs[i];
|
|
|
|
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
|
|
|
|
+ if (sysclk == dac_divs[j] * lrclk) {
|
|
|
|
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k)
|
|
|
|
+ if (sysclk == bclk * bclk_divs[k] / 10)
|
|
|
|
+ break;
|
|
|
|
+ if (k != ARRAY_SIZE(bclk_divs))
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ if (j != ARRAY_SIZE(dac_divs))
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
|
|
|
|
- for (i = 0; i < ARRAY_SIZE(dac_divs); ++i) {
|
|
|
|
- if (wm8960->sysclk == lrclk * dac_divs[i]) {
|
|
|
|
- for (j = 0; j < ARRAY_SIZE(bclk_divs); ++j) {
|
|
|
|
- sysclk = wm8960->bclk * bclk_divs[j] / 10;
|
|
|
|
- if (wm8960->sysclk == sysclk)
|
|
|
|
|
|
+ if (i != ARRAY_SIZE(sysclk_divs)) {
|
|
|
|
+ goto configure_clock;
|
|
|
|
+ } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) {
|
|
|
|
+ dev_err(codec->dev, "failed to configure clock\n");
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ }
|
|
|
|
+ /* get a available pll out frequency and set pll */
|
|
|
|
+ for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) {
|
|
|
|
+ if (sysclk_divs[i] == -1)
|
|
|
|
+ continue;
|
|
|
|
+ for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) {
|
|
|
|
+ sysclk = lrclk * dac_divs[j];
|
|
|
|
+ freq_out = sysclk * sysclk_divs[i];
|
|
|
|
+
|
|
|
|
+ for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) {
|
|
|
|
+ if (sysclk == bclk * bclk_divs[k] / 10 &&
|
|
|
|
+ is_pll_freq_available(freq_in, freq_out)) {
|
|
|
|
+ wm8960_set_pll(codec,
|
|
|
|
+ freq_in, freq_out);
|
|
break;
|
|
break;
|
|
|
|
+ } else {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- if(j != ARRAY_SIZE(bclk_divs))
|
|
|
|
|
|
+ if (k != ARRAY_SIZE(bclk_divs))
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+ if (j != ARRAY_SIZE(dac_divs))
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
|
|
|
|
- if (i == ARRAY_SIZE(dac_divs)) {
|
|
|
|
- dev_err(codec->dev, "Unsupported sysclk %d\n", wm8960->sysclk);
|
|
|
|
- return;
|
|
|
|
|
|
+ if (i == ARRAY_SIZE(sysclk_divs)) {
|
|
|
|
+ dev_err(codec->dev, "failed to configure clock\n");
|
|
|
|
+ return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
- /*
|
|
|
|
- * configure frame clock. If ADCLRC configure as GPIO pin, DACLRC
|
|
|
|
- * pin is used as a frame clock for ADCs and DACs.
|
|
|
|
- */
|
|
|
|
- if (iface2 & (1<<6))
|
|
|
|
- snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, i << 3);
|
|
|
|
- else if (tx)
|
|
|
|
- snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, i << 3);
|
|
|
|
- else if (!tx)
|
|
|
|
- snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, i << 6);
|
|
|
|
|
|
+configure_clock:
|
|
|
|
+ /* configure sysclk clock */
|
|
|
|
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 3 << 1, i << 1);
|
|
|
|
+
|
|
|
|
+ /* configure frame clock */
|
|
|
|
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 3, j << 3);
|
|
|
|
+ snd_soc_update_bits(codec, WM8960_CLOCK1, 0x7 << 6, j << 6);
|
|
|
|
|
|
/* configure bit clock */
|
|
/* configure bit clock */
|
|
- snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, j);
|
|
|
|
|
|
+ snd_soc_update_bits(codec, WM8960_CLOCK2, 0xf, k);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int wm8960_hw_params(struct snd_pcm_substream *substream,
|
|
static int wm8960_hw_params(struct snd_pcm_substream *substream,
|
|
@@ -667,9 +725,9 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ wm8960->lrclk = params_rate(params);
|
|
/* Update filters for the new rate */
|
|
/* Update filters for the new rate */
|
|
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
- wm8960->playback_fs = params_rate(params);
|
|
|
|
|
|
+ if (tx) {
|
|
wm8960_set_deemph(codec);
|
|
wm8960_set_deemph(codec);
|
|
} else {
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
|
|
for (i = 0; i < ARRAY_SIZE(alc_rates); i++)
|
|
@@ -682,7 +740,23 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream,
|
|
/* set iface */
|
|
/* set iface */
|
|
snd_soc_write(codec, WM8960_IFACE1, iface);
|
|
snd_soc_write(codec, WM8960_IFACE1, iface);
|
|
|
|
|
|
- wm8960_configure_clocking(codec, tx, params_rate(params));
|
|
|
|
|
|
+ wm8960->is_stream_in_use[tx] = true;
|
|
|
|
+
|
|
|
|
+ if (snd_soc_codec_get_bias_level(codec) == SND_SOC_BIAS_ON &&
|
|
|
|
+ !wm8960->is_stream_in_use[!tx])
|
|
|
|
+ return wm8960_configure_clocking(codec);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int wm8960_hw_free(struct snd_pcm_substream *substream,
|
|
|
|
+ struct snd_soc_dai *dai)
|
|
|
|
+{
|
|
|
|
+ struct snd_soc_codec *codec = dai->codec;
|
|
|
|
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
+
|
|
|
|
+ wm8960->is_stream_in_use[tx] = false;
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
@@ -702,6 +776,7 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
|
|
enum snd_soc_bias_level level)
|
|
enum snd_soc_bias_level level)
|
|
{
|
|
{
|
|
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
+ u16 pm2 = snd_soc_read(codec, WM8960_POWER2);
|
|
int ret;
|
|
int ret;
|
|
|
|
|
|
switch (level) {
|
|
switch (level) {
|
|
@@ -721,11 +796,22 @@ static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ ret = wm8960_configure_clocking(codec);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
/* Set VMID to 2x50k */
|
|
/* Set VMID to 2x50k */
|
|
snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x80);
|
|
snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x80);
|
|
break;
|
|
break;
|
|
|
|
|
|
case SND_SOC_BIAS_ON:
|
|
case SND_SOC_BIAS_ON:
|
|
|
|
+ /*
|
|
|
|
+ * If it's sysclk auto mode, and the pll is enabled,
|
|
|
|
+ * disable the pll
|
|
|
|
+ */
|
|
|
|
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
|
|
|
|
+ wm8960_set_pll(codec, 0, 0);
|
|
|
|
+
|
|
if (!IS_ERR(wm8960->mclk))
|
|
if (!IS_ERR(wm8960->mclk))
|
|
clk_disable_unprepare(wm8960->mclk);
|
|
clk_disable_unprepare(wm8960->mclk);
|
|
break;
|
|
break;
|
|
@@ -780,6 +866,7 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
|
|
enum snd_soc_bias_level level)
|
|
enum snd_soc_bias_level level)
|
|
{
|
|
{
|
|
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
+ u16 pm2 = snd_soc_read(codec, WM8960_POWER2);
|
|
int reg, ret;
|
|
int reg, ret;
|
|
|
|
|
|
switch (level) {
|
|
switch (level) {
|
|
@@ -831,9 +918,21 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec,
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ ret = wm8960_configure_clocking(codec);
|
|
|
|
+ if (ret)
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
break;
|
|
break;
|
|
|
|
|
|
case SND_SOC_BIAS_ON:
|
|
case SND_SOC_BIAS_ON:
|
|
|
|
+ /*
|
|
|
|
+ * If it's sysclk auto mode, and the pll is enabled,
|
|
|
|
+ * disable the pll
|
|
|
|
+ */
|
|
|
|
+ if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1))
|
|
|
|
+ wm8960_set_pll(codec, 0, 0);
|
|
|
|
+
|
|
if (!IS_ERR(wm8960->mclk))
|
|
if (!IS_ERR(wm8960->mclk))
|
|
clk_disable_unprepare(wm8960->mclk);
|
|
clk_disable_unprepare(wm8960->mclk);
|
|
|
|
|
|
@@ -892,6 +991,28 @@ struct _pll_div {
|
|
u32 k:24;
|
|
u32 k:24;
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+static bool is_pll_freq_available(unsigned int source, unsigned int target)
|
|
|
|
+{
|
|
|
|
+ unsigned int Ndiv;
|
|
|
|
+
|
|
|
|
+ if (source == 0 || target == 0)
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ /* Scale up target to PLL operating frequency */
|
|
|
|
+ target *= 4;
|
|
|
|
+ Ndiv = target / source;
|
|
|
|
+
|
|
|
|
+ if (Ndiv < 6) {
|
|
|
|
+ source >>= 1;
|
|
|
|
+ Ndiv = target / source;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ((Ndiv < 6) || (Ndiv > 12))
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
/* The size in bits of the pll divide multiplied by 10
|
|
/* The size in bits of the pll divide multiplied by 10
|
|
* to allow rounding later */
|
|
* to allow rounding later */
|
|
#define FIXED_PLL_SIZE ((1 << 24) * 10)
|
|
#define FIXED_PLL_SIZE ((1 << 24) * 10)
|
|
@@ -943,10 +1064,9 @@ static int pll_factors(unsigned int source, unsigned int target,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
|
|
|
|
- int source, unsigned int freq_in, unsigned int freq_out)
|
|
|
|
|
|
+static int wm8960_set_pll(struct snd_soc_codec *codec,
|
|
|
|
+ unsigned int freq_in, unsigned int freq_out)
|
|
{
|
|
{
|
|
- struct snd_soc_codec *codec = codec_dai->codec;
|
|
|
|
u16 reg;
|
|
u16 reg;
|
|
static struct _pll_div pll_div;
|
|
static struct _pll_div pll_div;
|
|
int ret;
|
|
int ret;
|
|
@@ -986,6 +1106,20 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
|
|
|
|
+ int source, unsigned int freq_in, unsigned int freq_out)
|
|
|
|
+{
|
|
|
|
+ struct snd_soc_codec *codec = codec_dai->codec;
|
|
|
|
+ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
+
|
|
|
|
+ wm8960->freq_in = freq_in;
|
|
|
|
+
|
|
|
|
+ if (pll_id == WM8960_SYSCLK_AUTO)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ return wm8960_set_pll(codec, freq_in, freq_out);
|
|
|
|
+}
|
|
|
|
+
|
|
static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|
static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
|
|
int div_id, int div)
|
|
int div_id, int div)
|
|
{
|
|
{
|
|
@@ -1043,11 +1177,14 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
snd_soc_update_bits(codec, WM8960_CLOCK1,
|
|
snd_soc_update_bits(codec, WM8960_CLOCK1,
|
|
0x1, WM8960_SYSCLK_PLL);
|
|
0x1, WM8960_SYSCLK_PLL);
|
|
break;
|
|
break;
|
|
|
|
+ case WM8960_SYSCLK_AUTO:
|
|
|
|
+ break;
|
|
default:
|
|
default:
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
wm8960->sysclk = freq;
|
|
wm8960->sysclk = freq;
|
|
|
|
+ wm8960->clk_id = clk_id;
|
|
|
|
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
@@ -1060,6 +1197,7 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
|
|
|
|
static const struct snd_soc_dai_ops wm8960_dai_ops = {
|
|
static const struct snd_soc_dai_ops wm8960_dai_ops = {
|
|
.hw_params = wm8960_hw_params,
|
|
.hw_params = wm8960_hw_params,
|
|
|
|
+ .hw_free = wm8960_hw_free,
|
|
.digital_mute = wm8960_mute,
|
|
.digital_mute = wm8960_mute,
|
|
.set_fmt = wm8960_set_dai_fmt,
|
|
.set_fmt = wm8960_set_dai_fmt,
|
|
.set_clkdiv = wm8960_set_dai_clkdiv,
|
|
.set_clkdiv = wm8960_set_dai_clkdiv,
|