LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
* [PATCH 0/7] ASoC: pcm512x: Clock master modes
@ 2015-01-14 11:52 Peter Rosin
  2015-01-14 11:52 ` [PATCH 1/7] ALSA: pcm: Add snd_interval_ranges() and snd_pcm_hw_constraint_ranges() Peter Rosin
                   ` (6 more replies)
  0 siblings, 7 replies; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel
  Cc: Peter Rosin, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai, linux-kernel

From: Peter Rosin <peda@axentia.se>

Hi!

This series implements BCLK master modes for the pcm512x driver. It has
only been tested with the pcm5142 chip, but they are from the same family
and should be compatible. I have mainly used the spec for the newer
pcm5242 chip (also from the same family) as it fills in a lot of blanks
in the pcm512x/pcm514x specs.

The code has also seen most of its testing in a 3.10 environment, so
there might be some forward-porting warts. But it is able to play sound
in 3.18 as well, and most of the changes have little to do with anything
but the clocking in the chip itself.

Cheers,
Peter

Peter Rosin (7):
  ALSA: pcm: Add snd_interval_ranges() and
    snd_pcm_hw_constraint_ranges()
  ASoC: pcm512x: Fix spelling of register field names.
  ASoC: pcm512x: Change register default to match actual content after
    reset
  ASoC: pcm512x: Support mastering BCLK/LRCLK without using the PLL
  ASoC: pcm512x: Support mastering BCLK/LRCLK using the PLL
  ASoC: pcm512x: Avoid the PLL for the DAC clock, if possible
  ASoC: pcm512x: Support SND_SOC_DAIFMT_CBM_CFS

 .../devicetree/bindings/sound/pcm512x.txt          |   25 +-
 include/sound/pcm.h                                |   12 +
 sound/core/pcm_lib.c                               |   85 ++
 sound/soc/codecs/pcm512x.c                         |  955 +++++++++++++++++++-
 sound/soc/codecs/pcm512x.h                         |  109 ++-
 5 files changed, 1161 insertions(+), 25 deletions(-)

-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/7] ALSA: pcm: Add snd_interval_ranges() and snd_pcm_hw_constraint_ranges()
  2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
@ 2015-01-14 11:52 ` Peter Rosin
  2015-01-26 22:10   ` [alsa-devel] " Lars-Peter Clausen
  2015-01-14 11:52 ` [PATCH 2/7] ASoC: pcm512x: Fix spelling of register field names Peter Rosin
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel
  Cc: Peter Rosin, Jaroslav Kysela, Takashi Iwai, Mark Brown, linux-kernel

From: Peter Rosin <peda@axentia.se>

Add helper functions to allow drivers to specify several disjoint
ranges for a variable. In particular, there is a codec (PCM512x) that
has a hole in its supported range of rates, due to PLL and divider
restrictions.

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 include/sound/pcm.h  |   12 +++++++
 sound/core/pcm_lib.c |   85 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 97 insertions(+)

diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index b429b73e875e..95d1c20fa659 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -275,6 +275,12 @@ struct snd_pcm_hw_constraint_list {
 	unsigned int mask;
 };
 
+struct snd_pcm_hw_constraint_ranges {
+	unsigned int count;
+	const struct snd_interval *ranges;
+	unsigned int mask;
+};
+
 struct snd_pcm_hwptr_log;
 
 struct snd_pcm_runtime {
@@ -910,6 +916,8 @@ void snd_interval_mulkdiv(const struct snd_interval *a, unsigned int k,
 			  const struct snd_interval *b, struct snd_interval *c);
 int snd_interval_list(struct snd_interval *i, unsigned int count,
 		      const unsigned int *list, unsigned int mask);
+int snd_interval_ranges(struct snd_interval *i, unsigned int count,
+			const struct snd_interval *list, unsigned int mask);
 int snd_interval_ratnum(struct snd_interval *i,
 			unsigned int rats_count, struct snd_ratnum *rats,
 			unsigned int *nump, unsigned int *denp);
@@ -934,6 +942,10 @@ int snd_pcm_hw_constraint_list(struct snd_pcm_runtime *runtime,
 			       unsigned int cond,
 			       snd_pcm_hw_param_t var,
 			       const struct snd_pcm_hw_constraint_list *l);
+int snd_pcm_hw_constraint_ranges(struct snd_pcm_runtime *runtime,
+				 unsigned int cond,
+				 snd_pcm_hw_param_t var,
+				 const struct snd_pcm_hw_constraint_ranges *r);
 int snd_pcm_hw_constraint_ratnums(struct snd_pcm_runtime *runtime, 
 				  unsigned int cond,
 				  snd_pcm_hw_param_t var,
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index ec9e7866177f..446c00bd908b 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -1015,6 +1015,60 @@ int snd_interval_list(struct snd_interval *i, unsigned int count,
 
 EXPORT_SYMBOL(snd_interval_list);
 
+/**
+ * snd_interval_ranges - refine the interval value from the list of ranges
+ * @i: the interval value to refine
+ * @count: the number of elements in the list of ranges
+ * @ranges: the ranges list
+ * @mask: the bit-mask to evaluate
+ *
+ * Refines the interval value from the list of ranges.
+ * When mask is non-zero, only the elements corresponding to bit 1 are
+ * evaluated.
+ *
+ * Return: Positive if the value is changed, zero if it's not changed, or a
+ * negative error code.
+ */
+int snd_interval_ranges(struct snd_interval *i, unsigned int count,
+			const struct snd_interval *ranges, unsigned int mask)
+{
+	unsigned int k;
+	struct snd_interval range_union;
+	struct snd_interval range;
+
+	if (!count) {
+		snd_interval_none(i);
+		return -EINVAL;
+	}
+	snd_interval_any(&range_union);
+	range_union.min = UINT_MAX;
+	range_union.max = 0;
+	for (k = 0; k < count; k++) {
+		if (mask && !(mask & (1 << k)))
+			continue;
+		snd_interval_copy(&range, &ranges[k]);
+		if (snd_interval_refine(&range, i) < 0)
+			continue;
+		if (snd_interval_empty(&range))
+			continue;
+
+		if (range.min < range_union.min) {
+			range_union.min = range.min;
+			range_union.openmin = 1;
+		}
+		if (range.min == range_union.min && !range.openmin)
+			range_union.openmin = 0;
+		if (range.max > range_union.max) {
+			range_union.max = range.max;
+			range_union.openmax = 1;
+		}
+		if (range.max == range_union.max && !range.openmax)
+			range_union.openmax = 0;
+	}
+	return snd_interval_refine(i, &range_union);
+}
+EXPORT_SYMBOL(snd_interval_ranges);
+
 static int snd_interval_step(struct snd_interval *i, unsigned int step)
 {
 	unsigned int n;
@@ -1221,6 +1275,37 @@ int snd_pcm_hw_constraint_list(struct snd_pcm_runtime *runtime,
 
 EXPORT_SYMBOL(snd_pcm_hw_constraint_list);
 
+static int snd_pcm_hw_rule_ranges(struct snd_pcm_hw_params *params,
+				  struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hw_constraint_ranges *r = rule->private;
+	return snd_interval_ranges(hw_param_interval(params, rule->var),
+				   r->count, r->ranges, r->mask);
+}
+
+
+/**
+ * snd_pcm_hw_constraint_ranges - apply list of range constraints to a parameter
+ * @runtime: PCM runtime instance
+ * @cond: condition bits
+ * @var: hw_params variable to apply the list of range constraints
+ * @r: ranges
+ *
+ * Apply the list of range constraints to an interval parameter.
+ *
+ * Return: Zero if successful, or a negative error code on failure.
+ */
+int snd_pcm_hw_constraint_ranges(struct snd_pcm_runtime *runtime,
+				 unsigned int cond,
+				 snd_pcm_hw_param_t var,
+				 const struct snd_pcm_hw_constraint_ranges *r)
+{
+	return snd_pcm_hw_rule_add(runtime, cond, var,
+				   snd_pcm_hw_rule_ranges, (void *)r,
+				   var, -1);
+}
+EXPORT_SYMBOL(snd_pcm_hw_constraint_ranges);
+
 static int snd_pcm_hw_rule_ratnums(struct snd_pcm_hw_params *params,
 				   struct snd_pcm_hw_rule *rule)
 {
-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 2/7] ASoC: pcm512x: Fix spelling of register field names.
  2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
  2015-01-14 11:52 ` [PATCH 1/7] ALSA: pcm: Add snd_interval_ranges() and snd_pcm_hw_constraint_ranges() Peter Rosin
@ 2015-01-14 11:52 ` Peter Rosin
  2015-01-14 11:52 ` [PATCH 3/7] ASoC: pcm512x: Change register default to match actual content after reset Peter Rosin
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel
  Cc: Peter Rosin, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai, linux-kernel

From: Peter Rosin <peda@axentia.se>

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 sound/soc/codecs/pcm512x.c |    2 +-
 sound/soc/codecs/pcm512x.h |    6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index 30c673cdc12e..355a8543c8b1 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -277,7 +277,7 @@ SOC_ENUM("Auto Mute Time Right", pcm512x_autom_r),
 SOC_SINGLE("Auto Mute Mono Switch", PCM512x_DIGITAL_MUTE_3,
 	   PCM512x_ACTL_SHIFT, 1, 0),
 SOC_DOUBLE("Auto Mute Switch", PCM512x_DIGITAL_MUTE_3, PCM512x_AMLE_SHIFT,
-	   PCM512x_AMLR_SHIFT, 1, 0),
+	   PCM512x_AMRE_SHIFT, 1, 0),
 
 SOC_ENUM("Volume Ramp Down Rate", pcm512x_vndf),
 SOC_ENUM("Volume Ramp Down Step", pcm512x_vnds),
diff --git a/sound/soc/codecs/pcm512x.h b/sound/soc/codecs/pcm512x.h
index 6ee76aaca09a..28b3dfd302bc 100644
--- a/sound/soc/codecs/pcm512x.h
+++ b/sound/soc/codecs/pcm512x.h
@@ -108,8 +108,8 @@
 #define PCM512x_RQML_SHIFT 4
 
 /* Page 0, Register 4 - PLL */
-#define PCM512x_PLCE       (1 << 0)
-#define PCM512x_RLCE_SHIFT 0
+#define PCM512x_PLLE       (1 << 0)
+#define PCM512x_PLLE_SHIFT 0
 #define PCM512x_PLCK       (1 << 4)
 #define PCM512x_PLCK_SHIFT 4
 
@@ -152,7 +152,7 @@
 /* Page 0, Register 65 - Digital mute enables */
 #define PCM512x_ACTL_SHIFT 2
 #define PCM512x_AMLE_SHIFT 1
-#define PCM512x_AMLR_SHIFT 0
+#define PCM512x_AMRE_SHIFT 0
 
 /* Page 1, Register 2 - analog volume control */
 #define PCM512x_RAGN_SHIFT 0
-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 3/7] ASoC: pcm512x: Change register default to match actual content after reset
  2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
  2015-01-14 11:52 ` [PATCH 1/7] ALSA: pcm: Add snd_interval_ranges() and snd_pcm_hw_constraint_ranges() Peter Rosin
  2015-01-14 11:52 ` [PATCH 2/7] ASoC: pcm512x: Fix spelling of register field names Peter Rosin
@ 2015-01-14 11:52 ` Peter Rosin
  2015-01-14 11:52 ` [PATCH 4/7] ASoC: pcm512x: Support mastering BCLK/LRCLK without using the PLL Peter Rosin
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel
  Cc: Peter Rosin, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai, linux-kernel

From: Peter Rosin <peda@axentia.se>

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 sound/soc/codecs/pcm512x.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index 355a8543c8b1..78dc3306d2f2 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -78,7 +78,7 @@ static const struct reg_default pcm512x_reg_defaults[] = {
 	{ PCM512x_DIGITAL_VOLUME_2,  0x30 },
 	{ PCM512x_DIGITAL_VOLUME_3,  0x30 },
 	{ PCM512x_DIGITAL_MUTE_1,    0x22 },
-	{ PCM512x_DIGITAL_MUTE_2,    0x00 },
+	{ PCM512x_DIGITAL_MUTE_2,    0x02 },
 	{ PCM512x_DIGITAL_MUTE_3,    0x07 },
 	{ PCM512x_OUTPUT_AMPLITUDE,  0x00 },
 	{ PCM512x_ANALOG_GAIN_CTRL,  0x00 },
-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 4/7] ASoC: pcm512x: Support mastering BCLK/LRCLK without using the PLL
  2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
                   ` (2 preceding siblings ...)
  2015-01-14 11:52 ` [PATCH 3/7] ASoC: pcm512x: Change register default to match actual content after reset Peter Rosin
@ 2015-01-14 11:52 ` Peter Rosin
  2015-01-14 11:52 ` [PATCH 5/7] ASoC: pcm512x: Support mastering BCLK/LRCLK " Peter Rosin
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel
  Cc: Peter Rosin, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai, linux-kernel

From: Peter Rosin <peda@axentia.se>

Use register field names from the seemingly compatible PCM5242 datasheet,
as the PCM512x and PCM514x datasheets are severly lacking.

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 sound/soc/codecs/pcm512x.c |  452 ++++++++++++++++++++++++++++++++++++++++++--
 sound/soc/codecs/pcm512x.h |   57 +++++-
 2 files changed, 492 insertions(+), 17 deletions(-)

diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index 78dc3306d2f2..70a88c7d2b1d 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -23,6 +23,7 @@
 #include <linux/regulator/consumer.h>
 #include <sound/soc.h>
 #include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
 #include <sound/tlv.h>
 
 #include "pcm512x.h"
@@ -39,6 +40,7 @@ struct pcm512x_priv {
 	struct clk *sclk;
 	struct regulator_bulk_data supplies[PCM512x_NUM_SUPPLIES];
 	struct notifier_block supply_nb[PCM512x_NUM_SUPPLIES];
+	int fmt;
 };
 
 /*
@@ -69,6 +71,7 @@ static const struct reg_default pcm512x_reg_defaults[] = {
 	{ PCM512x_MUTE,              0x00 },
 	{ PCM512x_DSP,               0x00 },
 	{ PCM512x_PLL_REF,           0x00 },
+	{ PCM512x_DAC_REF,           0x00 },
 	{ PCM512x_DAC_ROUTING,       0x11 },
 	{ PCM512x_DSP_PROGRAM,       0x01 },
 	{ PCM512x_CLKDET,            0x00 },
@@ -87,6 +90,18 @@ static const struct reg_default pcm512x_reg_defaults[] = {
 	{ PCM512x_ANALOG_GAIN_BOOST, 0x00 },
 	{ PCM512x_VCOM_CTRL_1,       0x00 },
 	{ PCM512x_VCOM_CTRL_2,       0x01 },
+	{ PCM512x_BCLK_LRCLK_CFG,    0x00 },
+	{ PCM512x_MASTER_MODE,       0x7c },
+	{ PCM512x_SYNCHRONIZE,       0x10 },
+	{ PCM512x_DSP_CLKDIV,        0x00 },
+	{ PCM512x_DAC_CLKDIV,        0x00 },
+	{ PCM512x_NCP_CLKDIV,        0x00 },
+	{ PCM512x_OSR_CLKDIV,        0x00 },
+	{ PCM512x_MASTER_CLKDIV_1,   0x00 },
+	{ PCM512x_MASTER_CLKDIV_2,   0x00 },
+	{ PCM512x_FS_SPEED_MODE,     0x00 },
+	{ PCM512x_IDAC_1,            0x01 },
+	{ PCM512x_IDAC_2,            0x00 },
 };
 
 static bool pcm512x_readable(struct device *dev, unsigned int reg)
@@ -103,6 +118,8 @@ static bool pcm512x_readable(struct device *dev, unsigned int reg)
 	case PCM512x_DSP_GPIO_INPUT:
 	case PCM512x_MASTER_MODE:
 	case PCM512x_PLL_REF:
+	case PCM512x_DAC_REF:
+	case PCM512x_SYNCHRONIZE:
 	case PCM512x_PLL_COEFF_0:
 	case PCM512x_PLL_COEFF_1:
 	case PCM512x_PLL_COEFF_2:
@@ -303,6 +320,105 @@ static const struct snd_soc_dapm_route pcm512x_dapm_routes[] = {
 	{ "OUTR", NULL, "DACR" },
 };
 
+static const u32 pcm512x_dai_rates[] = {
+	8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000,
+	88200, 96000, 176400, 192000, 384000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_slave = {
+	.count = ARRAY_SIZE(pcm512x_dai_rates),
+	.list  = pcm512x_dai_rates,
+};
+
+static int pcm512x_params_to_frame_size(struct snd_pcm_hw_params *params)
+{
+	int sample_size;
+
+	sample_size = snd_pcm_format_physical_width(params_format(params));
+	if (sample_size < 0)
+		return sample_size;
+
+	return snd_soc_calc_frame_size(sample_size, params_channels(params), 1);
+}
+
+static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream,
+				      struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+	struct device *dev = dai->dev;
+	struct snd_pcm_hw_constraint_ratnums *constraints_no_pll;
+	struct snd_ratnum *rats_no_pll;
+
+	if (IS_ERR(pcm512x->sclk)) {
+		dev_err(dev, "Need SCLK for master mode: %ld\n",
+			PTR_ERR(pcm512x->sclk));
+		return PTR_ERR(pcm512x->sclk);
+	}
+
+	constraints_no_pll = devm_kzalloc(dev, sizeof(*constraints_no_pll),
+					  GFP_KERNEL);
+	if (!constraints_no_pll)
+		return -ENOMEM;
+	constraints_no_pll->nrats = 1;
+	rats_no_pll = devm_kzalloc(dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+	constraints_no_pll->rats = rats_no_pll;
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	return snd_pcm_hw_constraint_ratnums(substream->runtime, 0,
+					     SNDRV_PCM_HW_PARAM_RATE,
+					     constraints_no_pll);
+}
+
+static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream,
+				     struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+	struct device *dev = dai->dev;
+	struct regmap *regmap = pcm512x->regmap;
+
+	if (IS_ERR(pcm512x->sclk)) {
+		dev_info(dev, "No SCLK, using BCLK: %ld\n",
+			 PTR_ERR(pcm512x->sclk));
+
+		/* Disable reporting of missing SCLK as an error */
+		regmap_update_bits(regmap, PCM512x_ERROR_DETECT,
+				   PCM512x_IDCH, PCM512x_IDCH);
+
+		/* Switch PLL input to BCLK */
+		regmap_update_bits(regmap, PCM512x_PLL_REF,
+				   PCM512x_SREF, PCM512x_SREF_BCK);
+	}
+
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+					  SNDRV_PCM_HW_PARAM_RATE,
+					  &constraints_slave);
+}
+
+static int pcm512x_dai_startup(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+	switch (pcm512x->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		return pcm512x_dai_startup_master(substream, dai);
+
+	case SND_SOC_DAIFMT_CBS_CFS:
+		return pcm512x_dai_startup_slave(substream, dai);
+
+	default:
+		return -EINVAL;
+	}
+}
+
 static int pcm512x_set_bias_level(struct snd_soc_codec *codec,
 				  enum snd_soc_bias_level level)
 {
@@ -340,17 +456,333 @@ static int pcm512x_set_bias_level(struct snd_soc_codec *codec,
 	return 0;
 }
 
+static int pcm512x_set_dividers(struct snd_soc_dai *dai,
+				struct snd_pcm_hw_params *params)
+{
+	struct device *dev = dai->dev;
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+	unsigned long sck_rate;
+	unsigned long mck_rate;
+	unsigned long bclk_rate;
+	unsigned long sample_rate;
+	unsigned long osr_rate;
+	int bclk_div;
+	int lrclk_div;
+	int dsp_div;
+	int dac_div;
+	unsigned long dac_rate;
+	int ncp_div;
+	int osr_div;
+	unsigned long dac_mul;
+	unsigned long sck_mul;
+	int ret;
+	int idac;
+	int fssp;
+
+	lrclk_div = pcm512x_params_to_frame_size(params);
+	if (lrclk_div == 0) {
+		dev_err(dev, "No LRCLK?\n");
+		return -EINVAL;
+	}
+
+	sck_rate = clk_get_rate(pcm512x->sclk);
+	bclk_div = params->rate_den * 64 / lrclk_div;
+	bclk_rate = DIV_ROUND_CLOSEST(sck_rate, bclk_div);
+
+	mck_rate = sck_rate;
+
+	if (bclk_div > 128) {
+		dev_err(dev, "Failed to find BCLK divider\n");
+		return -EINVAL;
+	}
+
+	/* the actual rate */
+	sample_rate = sck_rate / bclk_div / lrclk_div;
+	osr_rate = 16 * sample_rate;
+
+	/* run DSP no faster than 50 MHz */
+	dsp_div = mck_rate > 50000000 ? 2 : 1;
+
+	/* run DAC no faster than 6144000 Hz */
+	dac_mul = 6144000 / osr_rate;
+	sck_mul = sck_rate / osr_rate;
+	for (; dac_mul; dac_mul--) {
+		if (!(sck_mul % dac_mul))
+			break;
+	}
+	if (!dac_mul) {
+		dev_err(dev, "Failed to find DAC rate\n");
+		return -EINVAL;
+	}
+
+	dac_rate = dac_mul * osr_rate;
+	dev_dbg(dev, "dac_rate %lu sample_rate %lu\n", dac_rate, sample_rate);
+
+	dac_div = DIV_ROUND_CLOSEST(sck_rate, dac_rate);
+	if (dac_div > 128) {
+		dev_err(dev, "Failed to find DAC divider\n");
+		return -EINVAL;
+	}
+
+	ncp_div = DIV_ROUND_CLOSEST(sck_rate / dac_div, 1536000);
+	if (ncp_div > 128 || sck_rate / dac_div / ncp_div > 2048000) {
+		/* run NCP no faster than 2048000 Hz, but why? */
+		ncp_div = DIV_ROUND_UP(sck_rate / dac_div, 2048000);
+		if (ncp_div > 128) {
+			dev_err(dev, "Failed to find NCP divider\n");
+			return -EINVAL;
+		}
+	}
+
+	osr_div = DIV_ROUND_CLOSEST(dac_rate, osr_rate);
+	if (osr_div > 128) {
+		dev_err(dev, "Failed to find OSR divider\n");
+		return -EINVAL;
+	}
+
+	idac = mck_rate / (dsp_div * sample_rate);
+
+	ret = regmap_write(pcm512x->regmap, PCM512x_DSP_CLKDIV, dsp_div - 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write DSP divider: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(pcm512x->regmap, PCM512x_DAC_CLKDIV, dac_div - 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write DAC divider: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(pcm512x->regmap, PCM512x_NCP_CLKDIV, ncp_div - 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write NCP divider: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(pcm512x->regmap, PCM512x_OSR_CLKDIV, osr_div - 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write OSR divider: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(pcm512x->regmap,
+			   PCM512x_MASTER_CLKDIV_1, bclk_div - 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write BCLK divider: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(pcm512x->regmap,
+			   PCM512x_MASTER_CLKDIV_2, lrclk_div - 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write LRCLK divider: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(pcm512x->regmap, PCM512x_IDAC_1, idac >> 8);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write IDAC msb divider: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_write(pcm512x->regmap, PCM512x_IDAC_2, idac & 0xff);
+	if (ret != 0) {
+		dev_err(dev, "Failed to write IDAC lsb divider: %d\n", ret);
+		return ret;
+	}
+
+	if (sample_rate <= 48000)
+		fssp = PCM512x_FSSP_48KHZ;
+	else if (sample_rate <= 96000)
+		fssp = PCM512x_FSSP_96KHZ;
+	else if (sample_rate <= 192000)
+		fssp = PCM512x_FSSP_192KHZ;
+	else
+		fssp = PCM512x_FSSP_384KHZ;
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_FS_SPEED_MODE,
+				 PCM512x_FSSP, fssp);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set fs speed: %d\n", ret);
+		return ret;
+	}
+
+	dev_dbg(codec->dev, "DSP divider %d\n", dsp_div);
+	dev_dbg(codec->dev, "DAC divider %d\n", dac_div);
+	dev_dbg(codec->dev, "NCP divider %d\n", ncp_div);
+	dev_dbg(codec->dev, "OSR divider %d\n", osr_div);
+	dev_dbg(codec->dev, "BCK divider %d\n", bclk_div);
+	dev_dbg(codec->dev, "LRCK divider %d\n", lrclk_div);
+	dev_dbg(codec->dev, "IDAC %d\n", idac);
+	dev_dbg(codec->dev, "1<<FSSP %d\n", 1 << fssp);
+
+	return 0;
+}
+
+static int pcm512x_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+	int alen;
+	int ret;
+
+	dev_dbg(codec->dev, "hw_params %u Hz, %u channels\n",
+		params_rate(params),
+		params_channels(params));
+
+	switch (snd_pcm_format_width(params_format(params))) {
+	case 16:
+		alen = PCM512x_ALEN_16;
+		break;
+	case 20:
+		alen = PCM512x_ALEN_20;
+		break;
+	case 24:
+		alen = PCM512x_ALEN_24;
+		break;
+	case 32:
+		alen = PCM512x_ALEN_32;
+		break;
+	default:
+		dev_err(codec->dev, "Bad frame size: %d\n",
+			snd_pcm_format_width(params_format(params)));
+		return -EINVAL;
+	}
+
+	switch (pcm512x->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		ret = regmap_update_bits(pcm512x->regmap,
+					 PCM512x_BCLK_LRCLK_CFG,
+					 PCM512x_BCKP
+					 | PCM512x_BCKO | PCM512x_LRKO,
+					 0);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to enable slave mode: %d\n", ret);
+			return ret;
+		}
+
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT,
+					 PCM512x_DCAS, 0);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to enable clock divider autoset: %d\n",
+				ret);
+			return ret;
+		}
+		return 0;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_I2S_1,
+				 PCM512x_ALEN, alen);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set frame size: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT,
+				 PCM512x_IDFS | PCM512x_IDBK
+				 | PCM512x_IDSK | PCM512x_IDCH
+				 | PCM512x_IDCM | PCM512x_DCAS
+				 | PCM512x_IPLK,
+				 PCM512x_IDFS | PCM512x_IDBK
+				 | PCM512x_IDSK | PCM512x_IDCH
+				 | PCM512x_DCAS | PCM512x_IPLK);
+	if (ret != 0) {
+		dev_err(codec->dev,
+			"Failed to ignore auto-clock failures: %d\n",
+			ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_EN,
+				 PCM512x_PLLE, 0);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to disable pll: %d\n", ret);
+		return ret;
+	}
+
+	ret = pcm512x_set_dividers(dai, params);
+	if (ret != 0)
+		return ret;
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_DAC_REF,
+				 PCM512x_SDAC, PCM512x_SDAC_SCK);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to set sck as dacref: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_BCLK_LRCLK_CFG,
+				 PCM512x_BCKP | PCM512x_BCKO | PCM512x_LRKO,
+				 PCM512x_BCKO | PCM512x_LRKO);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable clock output: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_MASTER_MODE,
+				 PCM512x_RLRK | PCM512x_RBCK,
+				 PCM512x_RLRK | PCM512x_RBCK);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to enable master mode: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_SYNCHRONIZE,
+				 PCM512x_RQSY, PCM512x_RQSY_HALT);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to halt clocks: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(pcm512x->regmap, PCM512x_SYNCHRONIZE,
+				 PCM512x_RQSY, PCM512x_RQSY_RESUME);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to resume clocks: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int pcm512x_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+
+	pcm512x->fmt = fmt;
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops pcm512x_dai_ops = {
+	.startup = pcm512x_dai_startup,
+	.hw_params = pcm512x_hw_params,
+	.set_fmt = pcm512x_set_fmt,
+};
+
 static struct snd_soc_dai_driver pcm512x_dai = {
 	.name = "pcm512x-hifi",
 	.playback = {
 		.stream_name = "Playback",
 		.channels_min = 2,
 		.channels_max = 2,
-		.rates = SNDRV_PCM_RATE_8000_192000,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 8000,
+		.rate_max = 384000,
 		.formats = SNDRV_PCM_FMTBIT_S16_LE |
 			   SNDRV_PCM_FMTBIT_S24_LE |
 			   SNDRV_PCM_FMTBIT_S32_LE
 	},
+	.ops = &pcm512x_dai_ops,
 };
 
 static struct snd_soc_codec_driver pcm512x_codec_driver = {
@@ -448,21 +880,9 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap)
 	}
 
 	pcm512x->sclk = devm_clk_get(dev, NULL);
-	if (IS_ERR(pcm512x->sclk)) {
-		if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER)
-			return -EPROBE_DEFER;
-
-		dev_info(dev, "No SCLK, using BCLK: %ld\n",
-			 PTR_ERR(pcm512x->sclk));
-
-		/* Disable reporting of missing SCLK as an error */
-		regmap_update_bits(regmap, PCM512x_ERROR_DETECT,
-				   PCM512x_IDCH, PCM512x_IDCH);
-
-		/* Switch PLL input to BCLK */
-		regmap_update_bits(regmap, PCM512x_PLL_REF,
-				   PCM512x_SREF, PCM512x_SREF);
-	} else {
+	if (PTR_ERR(pcm512x->sclk) == -EPROBE_DEFER)
+		return -EPROBE_DEFER;
+	if (!IS_ERR(pcm512x->sclk)) {
 		ret = clk_prepare_enable(pcm512x->sclk);
 		if (ret != 0) {
 			dev_err(dev, "Failed to enable SCLK: %d\n", ret);
diff --git a/sound/soc/codecs/pcm512x.h b/sound/soc/codecs/pcm512x.h
index 28b3dfd302bc..fa538d5aabf2 100644
--- a/sound/soc/codecs/pcm512x.h
+++ b/sound/soc/codecs/pcm512x.h
@@ -37,6 +37,8 @@
 #define PCM512x_DSP_GPIO_INPUT    (PCM512x_PAGE_BASE(0) +  10)
 #define PCM512x_MASTER_MODE       (PCM512x_PAGE_BASE(0) +  12)
 #define PCM512x_PLL_REF           (PCM512x_PAGE_BASE(0) +  13)
+#define PCM512x_DAC_REF           (PCM512x_PAGE_BASE(0) +  14)
+#define PCM512x_SYNCHRONIZE       (PCM512x_PAGE_BASE(0) +  19)
 #define PCM512x_PLL_COEFF_0       (PCM512x_PAGE_BASE(0) +  20)
 #define PCM512x_PLL_COEFF_1       (PCM512x_PAGE_BASE(0) +  21)
 #define PCM512x_PLL_COEFF_2       (PCM512x_PAGE_BASE(0) +  22)
@@ -119,8 +121,47 @@
 #define PCM512x_DEMP       (1 << 4)
 #define PCM512x_DEMP_SHIFT 4
 
+/* Page 0, Register 9 - BCK, LRCLK configuration */
+#define PCM512x_LRKO       (1 << 0)
+#define PCM512x_LRKO_SHIFT 0
+#define PCM512x_BCKO       (1 << 4)
+#define PCM512x_BCKO_SHIFT 4
+#define PCM512x_BCKP       (1 << 5)
+#define PCM512x_BCKP_SHIFT 5
+
+/* Page 0, Register 12 - Master mode BCK, LRCLK reset */
+#define PCM512x_RLRK       (1 << 0)
+#define PCM512x_RLRK_SHIFT 0
+#define PCM512x_RBCK       (1 << 1)
+#define PCM512x_RBCK_SHIFT 1
+
 /* Page 0, Register 13 - PLL reference */
-#define PCM512x_SREF (1 << 4)
+#define PCM512x_SREF        (7 << 4)
+#define PCM512x_SREF_SHIFT  4
+#define PCM512x_SREF_SCK    (0 << 4)
+#define PCM512x_SREF_BCK    (1 << 4)
+#define PCM512x_SREF_GPIO   (3 << 4)
+
+/* Page 0, Register 14 - DAC reference */
+#define PCM512x_SDAC        (7 << 4)
+#define PCM512x_SDAC_SHIFT  4
+#define PCM512x_SDAC_MCK    (0 << 4)
+#define PCM512x_SDAC_PLL    (1 << 4)
+#define PCM512x_SDAC_SCK    (3 << 4)
+#define PCM512x_SDAC_BCK    (4 << 4)
+
+/* Page 0, Register 19 - synchronize */
+#define PCM512x_RQSY        (1 << 0)
+#define PCM512x_RQSY_RESUME (0 << 0)
+#define PCM512x_RQSY_HALT   (1 << 0)
+
+/* Page 0, Register 34 - fs speed mode */
+#define PCM512x_FSSP        (3 << 0)
+#define PCM512x_FSSP_SHIFT  0
+#define PCM512x_FSSP_48KHZ  (0 << 0)
+#define PCM512x_FSSP_96KHZ  (1 << 0)
+#define PCM512x_FSSP_192KHZ (2 << 0)
+#define PCM512x_FSSP_384KHZ (3 << 0)
 
 /* Page 0, Register 37 - Error detection */
 #define PCM512x_IPLK (1 << 0)
@@ -131,6 +172,20 @@
 #define PCM512x_IDBK (1 << 5)
 #define PCM512x_IDFS (1 << 6)
 
+/* Page 0, Register 40 - I2S configuration */
+#define PCM512x_ALEN       (3 << 0)
+#define PCM512x_ALEN_SHIFT 0
+#define PCM512x_ALEN_16    (0 << 0)
+#define PCM512x_ALEN_20    (1 << 0)
+#define PCM512x_ALEN_24    (2 << 0)
+#define PCM512x_ALEN_32    (3 << 0)
+#define PCM512x_AFMT       (3 << 4)
+#define PCM512x_AFMT_SHIFT 4
+#define PCM512x_AFMT_I2S   (0 << 4)
+#define PCM512x_AFMT_DSP   (1 << 4)
+#define PCM512x_AFMT_RTJ   (2 << 4)
+#define PCM512x_AFMT_LTJ   (3 << 4)
+
 /* Page 0, Register 42 - DAC routing */
 #define PCM512x_AUPR_SHIFT 0
 #define PCM512x_AUPL_SHIFT 4
-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 5/7] ASoC: pcm512x: Support mastering BCLK/LRCLK using the PLL
  2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
                   ` (3 preceding siblings ...)
  2015-01-14 11:52 ` [PATCH 4/7] ASoC: pcm512x: Support mastering BCLK/LRCLK without using the PLL Peter Rosin
@ 2015-01-14 11:52 ` Peter Rosin
  2015-01-14 11:52 ` [PATCH 6/7] ASoC: pcm512x: Avoid the PLL for the DAC clock, if possible Peter Rosin
  2015-01-14 11:52 ` [PATCH 7/7] ASoC: pcm512x: Support SND_SOC_DAIFMT_CBM_CFS Peter Rosin
  6 siblings, 0 replies; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel, devicetree
  Cc: Peter Rosin, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai, linux-kernel

From: Peter Rosin <peda@axentia.se>

Using the PLL in master mode requires using an external connection
between one of the GPIO pins (configured as PLL/4 output) and the
SCK pin. It also requires the external clock to be fed to some other
GPIO pin instead of the SCK pin.

This is described for the PCM5122 chip in the answers to the forum post
"PCM5122 DAC as I2S master troubles with PLL mode" at the TI E2E
community pages (1). The clocking functionality is also much better
described in the datasheet for the chip PCM5242, which seems to be
register compatible with PCM512x and PCM514x (which both have severely
lacking datasheets).

(1) http://e2e.ti.com/support/data_converters/audio_converters/f/64/t/267830

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 .../devicetree/bindings/sound/pcm512x.txt          |   25 +-
 sound/soc/codecs/pcm512x.c                         |  469 +++++++++++++++++++-
 sound/soc/codecs/pcm512x.h                         |   44 +-
 3 files changed, 512 insertions(+), 26 deletions(-)

diff --git a/Documentation/devicetree/bindings/sound/pcm512x.txt b/Documentation/devicetree/bindings/sound/pcm512x.txt
index 98e0d34915e8..3aae3b41bd8e 100644
--- a/Documentation/devicetree/bindings/sound/pcm512x.txt
+++ b/Documentation/devicetree/bindings/sound/pcm512x.txt
@@ -17,9 +17,16 @@ Required properties:
 Optional properties:
 
   - clocks : A clock specifier for the clock connected as SCLK.  If this
-    is absent the device will be configured to clock from BCLK.
+    is absent the device will be configured to clock from BCLK.  If pll-in
+    and pll-out are specified in addition to a clock, the device is
+    configured to accept clock input on a specified gpio pin.
 
-Example:
+  - pll-in, pll-out : gpio pins used to connect the pll using <1>
+    through <6>.  The device will be configured for clock input on the
+    given pll-in pin and PLL output on the given pll-out pin.  An
+    external connection from the pll-out pin to the SCLK pin is assumed.
+
+Examples:
 
 	pcm5122: pcm5122@4c {
 		compatible = "ti,pcm5122";
@@ -29,3 +36,17 @@ Example:
 		DVDD-supply = <&reg_1v8>;
 		CPVDD-supply = <&reg_3v3>;
 	};
+
+
+	pcm5142: pcm5142@4c {
+		compatible = "ti,pcm5142";
+		reg = <0x4c>;
+
+		AVDD-supply = <&reg_3v3_analog>;
+		DVDD-supply = <&reg_1v8>;
+		CPVDD-supply = <&reg_3v3>;
+
+		clocks = <&sck>;
+		pll-in = <3>;
+		pll-out = <6>;
+	};
diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index 70a88c7d2b1d..df8948b4e848 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -21,6 +21,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
+#include <linux/gcd.h>
 #include <sound/soc.h>
 #include <sound/soc-dapm.h>
 #include <sound/pcm_params.h>
@@ -28,6 +29,11 @@
 
 #include "pcm512x.h"
 
+#define DIV_ROUND_DOWN_ULL(ll, d) \
+	({ unsigned long long _tmp = (ll); do_div(_tmp, d); _tmp; })
+#define DIV_ROUND_CLOSEST_ULL(ll, d) \
+	({ unsigned long long _tmp = (ll)+(d)/2; do_div(_tmp, d); _tmp; })
+
 #define PCM512x_NUM_SUPPLIES 3
 static const char * const pcm512x_supply_names[PCM512x_NUM_SUPPLIES] = {
 	"AVDD",
@@ -41,6 +47,13 @@ struct pcm512x_priv {
 	struct regulator_bulk_data supplies[PCM512x_NUM_SUPPLIES];
 	struct notifier_block supply_nb[PCM512x_NUM_SUPPLIES];
 	int fmt;
+	int pll_in;
+	int pll_out;
+	int pll_r;
+	int pll_j;
+	int pll_d;
+	int pll_p;
+	unsigned long real_pll;
 };
 
 /*
@@ -92,7 +105,13 @@ static const struct reg_default pcm512x_reg_defaults[] = {
 	{ PCM512x_VCOM_CTRL_2,       0x01 },
 	{ PCM512x_BCLK_LRCLK_CFG,    0x00 },
 	{ PCM512x_MASTER_MODE,       0x7c },
+	{ PCM512x_GPIO_PLLIN,        0x00 },
 	{ PCM512x_SYNCHRONIZE,       0x10 },
+	{ PCM512x_PLL_COEFF_0,       0x00 },
+	{ PCM512x_PLL_COEFF_1,       0x00 },
+	{ PCM512x_PLL_COEFF_2,       0x00 },
+	{ PCM512x_PLL_COEFF_3,       0x00 },
+	{ PCM512x_PLL_COEFF_4,       0x00 },
 	{ PCM512x_DSP_CLKDIV,        0x00 },
 	{ PCM512x_DAC_CLKDIV,        0x00 },
 	{ PCM512x_NCP_CLKDIV,        0x00 },
@@ -119,6 +138,7 @@ static bool pcm512x_readable(struct device *dev, unsigned int reg)
 	case PCM512x_MASTER_MODE:
 	case PCM512x_PLL_REF:
 	case PCM512x_DAC_REF:
+	case PCM512x_GPIO_PLLIN:
 	case PCM512x_SYNCHRONIZE:
 	case PCM512x_PLL_COEFF_0:
 	case PCM512x_PLL_COEFF_1:
@@ -160,6 +180,7 @@ static bool pcm512x_readable(struct device *dev, unsigned int reg)
 	case PCM512x_RATE_DET_2:
 	case PCM512x_RATE_DET_3:
 	case PCM512x_RATE_DET_4:
+	case PCM512x_CLOCK_STATUS:
 	case PCM512x_ANALOG_MUTE_DET:
 	case PCM512x_GPIN:
 	case PCM512x_DIGITAL_MUTE_DET:
@@ -171,6 +192,8 @@ static bool pcm512x_readable(struct device *dev, unsigned int reg)
 	case PCM512x_VCOM_CTRL_1:
 	case PCM512x_VCOM_CTRL_2:
 	case PCM512x_CRAM_CTRL:
+	case PCM512x_FLEX_A:
+	case PCM512x_FLEX_B:
 		return true;
 	default:
 		/* There are 256 raw register addresses */
@@ -187,6 +210,7 @@ static bool pcm512x_volatile(struct device *dev, unsigned int reg)
 	case PCM512x_RATE_DET_2:
 	case PCM512x_RATE_DET_3:
 	case PCM512x_RATE_DET_4:
+	case PCM512x_CLOCK_STATUS:
 	case PCM512x_ANALOG_MUTE_DET:
 	case PCM512x_GPIN:
 	case PCM512x_DIGITAL_MUTE_DET:
@@ -330,6 +354,21 @@ static const struct snd_pcm_hw_constraint_list constraints_slave = {
 	.list  = pcm512x_dai_rates,
 };
 
+static const struct snd_interval pcm512x_dai_ranges_64bpf[] = {
+	{
+		.min = 8000,
+		.max = 195312,
+	}, {
+		.min = 250000,
+		.max = 390625,
+	},
+};
+
+static struct snd_pcm_hw_constraint_ranges constraints_64bpf = {
+	.count  = ARRAY_SIZE(pcm512x_dai_ranges_64bpf),
+	.ranges = pcm512x_dai_ranges_64bpf,
+};
+
 static int pcm512x_params_to_frame_size(struct snd_pcm_hw_params *params)
 {
 	int sample_size;
@@ -341,6 +380,34 @@ static int pcm512x_params_to_frame_size(struct snd_pcm_hw_params *params)
 	return snd_soc_calc_frame_size(sample_size, params_channels(params), 1);
 }
 
+static int pcm512x_params_to_bclk(struct snd_pcm_hw_params *params)
+{
+	int frame_size;
+
+	frame_size = pcm512x_params_to_frame_size(params);
+	if (frame_size < 0)
+		return frame_size;
+
+	return frame_size * params_rate(params);
+}
+
+static int pcm512x_hw_rule_rate(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_pcm_hw_constraint_ranges *r = rule->private;
+	int frame_size;
+
+	frame_size = pcm512x_params_to_frame_size(params);
+	if (frame_size < 0)
+		return frame_size;
+
+	if (frame_size != 64)
+		return 0;
+
+	return snd_interval_ranges(hw_param_interval(params, rule->var),
+				   r->count, r->ranges, r->mask);
+}
+
 static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream,
 				      struct snd_soc_dai *dai)
 {
@@ -356,6 +423,14 @@ static int pcm512x_dai_startup_master(struct snd_pcm_substream *substream,
 		return PTR_ERR(pcm512x->sclk);
 	}
 
+	if (pcm512x->pll_out)
+		return snd_pcm_hw_rule_add(substream->runtime, 0,
+					   SNDRV_PCM_HW_PARAM_RATE,
+					   pcm512x_hw_rule_rate,
+					   (void *)&constraints_64bpf,
+					   SNDRV_PCM_HW_PARAM_FRAME_BITS,
+					   SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+
 	constraints_no_pll = devm_kzalloc(dev, sizeof(*constraints_no_pll),
 					  GFP_KERNEL);
 	if (!constraints_no_pll)
@@ -456,12 +531,164 @@ static int pcm512x_set_bias_level(struct snd_soc_codec *codec,
 	return 0;
 }
 
+static unsigned long pcm512x_find_sck(struct snd_soc_dai *dai,
+				      unsigned long bclk_rate)
+{
+	struct device *dev = dai->dev;
+	unsigned long sck_rate;
+	int pow2;
+
+	/* 64 MHz <= pll_rate <= 100 MHz, VREF mode */
+	/* 16 MHz <= sck_rate <=  25 MHz, VREF mode */
+
+	/* select sck_rate as a multiple of bclk_rate but still with
+	 * as many factors of 2 as possible, as that makes it easier
+	 * to find a fast DAC rate
+	 */
+	pow2 = 1 << fls((25000000 - 16000000) / bclk_rate);
+	for (; pow2; pow2 >>= 1) {
+		sck_rate = rounddown(25000000, bclk_rate * pow2);
+		if (sck_rate >= 16000000)
+			break;
+	}
+	if (!pow2) {
+		dev_err(dev, "Impossible to generate a suitable SCK\n");
+		return 0;
+	}
+
+	dev_dbg(dev, "sck_rate %lu\n", sck_rate);
+	return sck_rate;
+}
+
+/* pll_rate = pllin_rate * R * J.D / P
+ * 1 <= R <= 16
+ * 1 <= J <= 63
+ * 0 <= D <= 9999
+ * 1 <= P <= 15
+ * 64 MHz <= pll_rate <= 100 MHz
+ * if D == 0
+ *     1 MHz <= pllin_rate / P <= 20 MHz
+ * else if D > 0
+ *     6.667 MHz <= pllin_rate / P <= 20 MHz
+ *     4 <= J <= 11
+ *     R = 1
+ */
+static int pcm512x_find_pll_coeff(struct snd_soc_dai *dai,
+				  unsigned long pllin_rate,
+				  unsigned long pll_rate)
+{
+	struct device *dev = dai->dev;
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+	unsigned long common;
+	int R, J, D, P;
+	unsigned long K; /* 10000 * J.D */
+	unsigned long num;
+	unsigned long den;
+
+	common = gcd(pll_rate, pllin_rate);
+	dev_dbg(dev, "pll %lu pllin %lu common %lu\n",
+		pll_rate, pllin_rate, common);
+	num = pll_rate / common;
+	den = pllin_rate / common;
+
+	/* pllin_rate / P (or here, den) cannot be greater than 20 MHz */
+	if (pllin_rate / den > 20000000 && num < 8) {
+		num *= 20000000 / (pllin_rate / den);
+		den *= 20000000 / (pllin_rate / den);
+	}
+	dev_dbg(dev, "num / den = %lu / %lu\n", num, den);
+
+	P = den;
+	if (den <= 15 && num <= 16 * 63
+	    && 1000000 <= pllin_rate / P && pllin_rate / P <= 20000000) {
+		/* Try the case with D = 0 */
+		D = 0;
+		/* factor 'num' into J and R, such that R <= 16 and J <= 63 */
+		for (R = 16; R; R--) {
+			if (num % R)
+				continue;
+			J = num / R;
+			if (J == 0 || J > 63)
+				continue;
+
+			dev_dbg(dev, "R * J / P = %d * %d / %d\n", R, J, P);
+			pcm512x->real_pll = pll_rate;
+			goto done;
+		}
+		/* no luck */
+	}
+
+	R = 1;
+
+	if (num > 0xffffffffUL / 10000)
+		goto fallback;
+
+	/* Try to find an exact pll_rate using the D > 0 case */
+	common = gcd(10000 * num, den);
+	num = 10000 * num / common;
+	den /= common;
+	dev_dbg(dev, "num %lu den %lu common %lu\n", num, den, common);
+
+	for (P = den; P <= 15; P++) {
+		if (pllin_rate / P < 6667000 || 200000000 < pllin_rate / P)
+			continue;
+		if (num * P % den)
+			continue;
+		K = num * P / den;
+		/* J == 12 is ok if D == 0 */
+		if (K < 40000 || K > 120000)
+			continue;
+
+		J = K / 10000;
+		D = K % 10000;
+		dev_dbg(dev, "J.D / P = %d.%04d / %d\n", J, D, P);
+		pcm512x->real_pll = pll_rate;
+		goto done;
+	}
+
+	/* Fall back to an approximate pll_rate */
+
+fallback:
+	/* find smallest possible P */
+	P = DIV_ROUND_UP(pllin_rate, 20000000);
+	if (!P)
+		P = 1;
+	else if (P > 15) {
+		dev_err(dev, "Need a slower clock as pll-input\n");
+		return -EINVAL;
+	}
+	if (pllin_rate / P < 6667000) {
+		dev_err(dev, "Need a faster clock as pll-input\n");
+		return -EINVAL;
+	}
+	K = DIV_ROUND_CLOSEST_ULL(10000ULL * pll_rate * P, pllin_rate);
+	if (K < 40000)
+		K = 40000;
+	/* J == 12 is ok if D == 0 */
+	if (K > 120000)
+		K = 120000;
+	J = K / 10000;
+	D = K % 10000;
+	dev_dbg(dev, "J.D / P ~ %d.%04d / %d\n", J, D, P);
+	pcm512x->real_pll = DIV_ROUND_DOWN_ULL((u64)K * pllin_rate, 10000 * P);
+
+done:
+	pcm512x->pll_r = R;
+	pcm512x->pll_j = J;
+	pcm512x->pll_d = D;
+	pcm512x->pll_p = P;
+	return 0;
+}
+
 static int pcm512x_set_dividers(struct snd_soc_dai *dai,
 				struct snd_pcm_hw_params *params)
 {
 	struct device *dev = dai->dev;
 	struct snd_soc_codec *codec = dai->codec;
 	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+	unsigned long pllin_rate = 0;
+	unsigned long pll_rate;
 	unsigned long sck_rate;
 	unsigned long mck_rate;
 	unsigned long bclk_rate;
@@ -486,11 +713,74 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
 		return -EINVAL;
 	}
 
-	sck_rate = clk_get_rate(pcm512x->sclk);
-	bclk_div = params->rate_den * 64 / lrclk_div;
-	bclk_rate = DIV_ROUND_CLOSEST(sck_rate, bclk_div);
+	if (!pcm512x->pll_out) {
+		sck_rate = clk_get_rate(pcm512x->sclk);
+		bclk_div = params->rate_den * 64 / lrclk_div;
+		bclk_rate = DIV_ROUND_CLOSEST(sck_rate, bclk_div);
+
+		mck_rate = sck_rate;
+	} else {
+		ret = pcm512x_params_to_bclk(params);
+		if (ret < 0) {
+			dev_err(dev, "Failed to find suitable BCLK: %d\n", ret);
+			return ret;
+		}
+		if (ret == 0) {
+			dev_err(dev, "No BCLK?\n");
+			return -EINVAL;
+		}
+		bclk_rate = ret;
+
+		pllin_rate = clk_get_rate(pcm512x->sclk);
+
+		sck_rate = pcm512x_find_sck(dai, bclk_rate);
+		if (!sck_rate)
+			return -EINVAL;
+		pll_rate = 4 * sck_rate;
+
+		ret = pcm512x_find_pll_coeff(dai, pllin_rate, pll_rate);
+		if (ret != 0)
+			return ret;
+
+		ret = regmap_write(pcm512x->regmap,
+				   PCM512x_PLL_COEFF_0, pcm512x->pll_p - 1);
+		if (ret != 0) {
+			dev_err(dev, "Failed to write PLL P: %d\n", ret);
+			return ret;
+		}
+
+		ret = regmap_write(pcm512x->regmap,
+				   PCM512x_PLL_COEFF_1, pcm512x->pll_j);
+		if (ret != 0) {
+			dev_err(dev, "Failed to write PLL J: %d\n", ret);
+			return ret;
+		}
+
+		ret = regmap_write(pcm512x->regmap,
+				   PCM512x_PLL_COEFF_2, pcm512x->pll_d >> 8);
+		if (ret != 0) {
+			dev_err(dev, "Failed to write PLL D msb: %d\n", ret);
+			return ret;
+		}
+
+		ret = regmap_write(pcm512x->regmap,
+				   PCM512x_PLL_COEFF_3, pcm512x->pll_d & 0xff);
+		if (ret != 0) {
+			dev_err(dev, "Failed to write PLL D lsb: %d\n", ret);
+			return ret;
+		}
+
+		ret = regmap_write(pcm512x->regmap,
+				   PCM512x_PLL_COEFF_4, pcm512x->pll_r - 1);
+		if (ret != 0) {
+			dev_err(dev, "Failed to write PLL R: %d\n", ret);
+			return ret;
+		}
+
+		mck_rate = pcm512x->real_pll;
 
-	mck_rate = sck_rate;
+		bclk_div = DIV_ROUND_CLOSEST(sck_rate, bclk_rate);
+	}
 
 	if (bclk_div > 128) {
 		dev_err(dev, "Failed to find BCLK divider\n");
@@ -627,6 +917,7 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 	struct snd_soc_codec *codec = dai->codec;
 	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
 	int alen;
+	int gpio;
 	int ret;
 
 	dev_dbg(codec->dev, "hw_params %u Hz, %u channels\n",
@@ -687,26 +978,55 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 		return ret;
 	}
 
-	ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT,
-				 PCM512x_IDFS | PCM512x_IDBK
-				 | PCM512x_IDSK | PCM512x_IDCH
-				 | PCM512x_IDCM | PCM512x_DCAS
-				 | PCM512x_IPLK,
-				 PCM512x_IDFS | PCM512x_IDBK
-				 | PCM512x_IDSK | PCM512x_IDCH
-				 | PCM512x_DCAS | PCM512x_IPLK);
-	if (ret != 0) {
-		dev_err(codec->dev,
-			"Failed to ignore auto-clock failures: %d\n",
-			ret);
-		return ret;
-	}
+	if (pcm512x->pll_out) {
+		ret = regmap_write(pcm512x->regmap, PCM512x_FLEX_A, 0x11);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to set FLEX_A: %d\n", ret);
+			return ret;
+		}
 
-	ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_EN,
-				 PCM512x_PLLE, 0);
-	if (ret != 0) {
-		dev_err(codec->dev, "Failed to disable pll: %d\n", ret);
-		return ret;
+		ret = regmap_write(pcm512x->regmap, PCM512x_FLEX_B, 0xff);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to set FLEX_B: %d\n", ret);
+			return ret;
+		}
+
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT,
+					 PCM512x_IDFS | PCM512x_IDBK
+					 | PCM512x_IDSK | PCM512x_IDCH
+					 | PCM512x_IDCM | PCM512x_DCAS
+					 | PCM512x_IPLK,
+					 PCM512x_IDFS | PCM512x_IDBK
+					 | PCM512x_IDSK | PCM512x_IDCH
+					 | PCM512x_DCAS);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to ignore auto-clock failures: %d\n",
+				ret);
+			return ret;
+		}
+	} else {
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_ERROR_DETECT,
+					 PCM512x_IDFS | PCM512x_IDBK
+					 | PCM512x_IDSK | PCM512x_IDCH
+					 | PCM512x_IDCM | PCM512x_DCAS
+					 | PCM512x_IPLK,
+					 PCM512x_IDFS | PCM512x_IDBK
+					 | PCM512x_IDSK | PCM512x_IDCH
+					 | PCM512x_DCAS | PCM512x_IPLK);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to ignore auto-clock failures: %d\n",
+				ret);
+			return ret;
+		}
+
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_EN,
+					 PCM512x_PLLE, 0);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to disable pll: %d\n", ret);
+			return ret;
+		}
 	}
 
 	ret = pcm512x_set_dividers(dai, params);
@@ -720,6 +1040,33 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 		return ret;
 	}
 
+	if (pcm512x->pll_out) {
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_REF,
+					 PCM512x_SREF, PCM512x_SREF_GPIO);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to set gpio as pllref: %d\n", ret);
+			return ret;
+		}
+
+		gpio = PCM512x_GREF_GPIO1 + pcm512x->pll_in - 1;
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_PLLIN,
+					 PCM512x_GREF, gpio);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to set gpio %d as pllin: %d\n",
+				pcm512x->pll_in, ret);
+			return ret;
+		}
+
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_EN,
+					 PCM512x_PLLE, PCM512x_PLLE);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to enable pll: %d\n", ret);
+			return ret;
+		}
+	}
+
 	ret = regmap_update_bits(pcm512x->regmap, PCM512x_BCLK_LRCLK_CFG,
 				 PCM512x_BCKP | PCM512x_BCKO | PCM512x_LRKO,
 				 PCM512x_BCKO | PCM512x_LRKO);
@@ -736,6 +1083,45 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 		return ret;
 	}
 
+	if (pcm512x->pll_out) {
+		gpio = PCM512x_G1OE << (pcm512x->pll_out - 1);
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_EN,
+					 gpio, gpio);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to enable gpio %d: %d\n",
+				pcm512x->pll_out, ret);
+			return ret;
+		}
+
+		gpio = PCM512x_GPIO_OUTPUT_1 + pcm512x->pll_out - 1;
+		ret = regmap_update_bits(pcm512x->regmap, gpio,
+					 PCM512x_GxSL, PCM512x_GxSL_PLLCK);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to output pll on %d: %d\n",
+				ret, pcm512x->pll_out);
+			return ret;
+		}
+
+		gpio = PCM512x_G1OE << (4 - 1);
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_EN,
+					 gpio, gpio);
+		if (ret != 0) {
+			dev_err(codec->dev, "Failed to enable gpio %d: %d\n",
+				4, ret);
+			return ret;
+		}
+
+		gpio = PCM512x_GPIO_OUTPUT_1 + 4 - 1;
+		ret = regmap_update_bits(pcm512x->regmap, gpio,
+					 PCM512x_GxSL, PCM512x_GxSL_PLLLK);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to output pll lock on %d: %d\n",
+				ret, 4);
+			return ret;
+		}
+	}
+
 	ret = regmap_update_bits(pcm512x->regmap, PCM512x_SYNCHRONIZE,
 				 PCM512x_RQSY, PCM512x_RQSY_HALT);
 	if (ret != 0) {
@@ -826,6 +1212,7 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap)
 {
 	struct pcm512x_priv *pcm512x;
 	int i, ret;
+	u32 val;
 
 	pcm512x = devm_kzalloc(dev, sizeof(struct pcm512x_priv), GFP_KERNEL);
 	if (!pcm512x)
@@ -903,6 +1290,42 @@ int pcm512x_probe(struct device *dev, struct regmap *regmap)
 	pm_runtime_enable(dev);
 	pm_runtime_idle(dev);
 
+#ifdef CONFIG_OF
+	if (dev->of_node) {
+		const struct device_node *np = dev->of_node;
+
+		if (of_property_read_u32(np, "pll-in", &val) >= 0) {
+			if (val > 6) {
+				dev_err(dev, "Invalid pll-in\n");
+				ret = -EINVAL;
+				goto err_clk;
+			}
+			pcm512x->pll_in = val;
+		}
+
+		if (of_property_read_u32(np, "pll-out", &val) >= 0) {
+			if (val > 6) {
+				dev_err(dev, "Invalid pll-out\n");
+				ret = -EINVAL;
+				goto err_clk;
+			}
+			pcm512x->pll_out = val;
+		}
+
+		if (!pcm512x->pll_in != !pcm512x->pll_out) {
+			dev_err(dev,
+				"Error: both pll-in and pll-out, or none\n");
+			ret = -EINVAL;
+			goto err_clk;
+		}
+		if (pcm512x->pll_in && pcm512x->pll_in == pcm512x->pll_out) {
+			dev_err(dev, "Error: pll-in == pll-out\n");
+			ret = -EINVAL;
+			goto err_clk;
+		}
+	}
+#endif
+
 	ret = snd_soc_register_codec(dev, &pcm512x_codec_driver,
 				    &pcm512x_dai, 1);
 	if (ret != 0) {
diff --git a/sound/soc/codecs/pcm512x.h b/sound/soc/codecs/pcm512x.h
index fa538d5aabf2..eba5adc2cdb1 100644
--- a/sound/soc/codecs/pcm512x.h
+++ b/sound/soc/codecs/pcm512x.h
@@ -38,6 +38,7 @@
 #define PCM512x_MASTER_MODE       (PCM512x_PAGE_BASE(0) +  12)
 #define PCM512x_PLL_REF           (PCM512x_PAGE_BASE(0) +  13)
 #define PCM512x_DAC_REF           (PCM512x_PAGE_BASE(0) +  14)
+#define PCM512x_GPIO_PLLIN        (PCM512x_PAGE_BASE(0) +  18)
 #define PCM512x_SYNCHRONIZE       (PCM512x_PAGE_BASE(0) +  19)
 #define PCM512x_PLL_COEFF_0       (PCM512x_PAGE_BASE(0) +  20)
 #define PCM512x_PLL_COEFF_1       (PCM512x_PAGE_BASE(0) +  21)
@@ -79,6 +80,7 @@
 #define PCM512x_RATE_DET_2        (PCM512x_PAGE_BASE(0) +  92)
 #define PCM512x_RATE_DET_3        (PCM512x_PAGE_BASE(0) +  93)
 #define PCM512x_RATE_DET_4        (PCM512x_PAGE_BASE(0) +  94)
+#define PCM512x_CLOCK_STATUS      (PCM512x_PAGE_BASE(0) +  95)
 #define PCM512x_ANALOG_MUTE_DET   (PCM512x_PAGE_BASE(0) + 108)
 #define PCM512x_GPIN              (PCM512x_PAGE_BASE(0) + 119)
 #define PCM512x_DIGITAL_MUTE_DET  (PCM512x_PAGE_BASE(0) + 120)
@@ -93,7 +95,10 @@
 
 #define PCM512x_CRAM_CTRL         (PCM512x_PAGE_BASE(44) +  1)
 
-#define PCM512x_MAX_REGISTER      (PCM512x_PAGE_BASE(44) +  1)
+#define PCM512x_FLEX_A            (PCM512x_PAGE_BASE(253) + 63)
+#define PCM512x_FLEX_B            (PCM512x_PAGE_BASE(253) + 64)
+
+#define PCM512x_MAX_REGISTER      (PCM512x_PAGE_BASE(253) + 64)
 
 /* Page 0, Register 1 - reset */
 #define PCM512x_RSTR (1 << 0)
@@ -121,6 +126,14 @@
 #define PCM512x_DEMP       (1 << 4)
 #define PCM512x_DEMP_SHIFT 4
 
+/* Page 0, Register 8 - GPIO output enable */
+#define PCM512x_G1OE       (1 << 0)
+#define PCM512x_G2OE       (1 << 1)
+#define PCM512x_G3OE       (1 << 2)
+#define PCM512x_G4OE       (1 << 3)
+#define PCM512x_G5OE       (1 << 4)
+#define PCM512x_G6OE       (1 << 5)
+
 /* Page 0, Register 9 - BCK, LRCLK configuration */
 #define PCM512x_LRKO       (1 << 0)
 #define PCM512x_LRKO_SHIFT 0
@@ -150,6 +163,16 @@
 #define PCM512x_SDAC_SCK    (3 << 4)
 #define PCM512x_SDAC_BCK    (4 << 4)
 
+/* Page 0, Register 18 - GPIO source for PLL */
+#define PCM512x_GREF        (7 << 0)
+#define PCM512x_GREF_SHIFT  0
+#define PCM512x_GREF_GPIO1  (0 << 0)
+#define PCM512x_GREF_GPIO2  (1 << 0)
+#define PCM512x_GREF_GPIO3  (2 << 0)
+#define PCM512x_GREF_GPIO4  (3 << 0)
+#define PCM512x_GREF_GPIO5  (4 << 0)
+#define PCM512x_GREF_GPIO6  (5 << 0)
+
 /* Page 0, Register 19 - synchronize */
 #define PCM512x_RQSY        (1 << 0)
 #define PCM512x_RQSY_RESUME (0 << 0)
@@ -209,6 +232,25 @@
 #define PCM512x_AMLE_SHIFT 1
 #define PCM512x_AMRE_SHIFT 0
 
+/* Page 0, Register 80-85, GPIO output selection */
+#define PCM512x_GxSL       (31 << 0)
+#define PCM512x_GxSL_SHIFT 0
+#define PCM512x_GxSL_OFF   (0 << 0)
+#define PCM512x_GxSL_DSP   (1 << 0)
+#define PCM512x_GxSL_REG   (2 << 0)
+#define PCM512x_GxSL_AMUTB (3 << 0)
+#define PCM512x_GxSL_AMUTL (4 << 0)
+#define PCM512x_GxSL_AMUTR (5 << 0)
+#define PCM512x_GxSL_CLKI  (6 << 0)
+#define PCM512x_GxSL_SDOUT (7 << 0)
+#define PCM512x_GxSL_ANMUL (8 << 0)
+#define PCM512x_GxSL_ANMUR (9 << 0)
+#define PCM512x_GxSL_PLLLK (10 << 0)
+#define PCM512x_GxSL_CPCLK (11 << 0)
+#define PCM512x_GxSL_UV0_7 (14 << 0)
+#define PCM512x_GxSL_UV0_3 (15 << 0)
+#define PCM512x_GxSL_PLLCK (16 << 0)
+
 /* Page 1, Register 2 - analog volume control */
 #define PCM512x_RAGN_SHIFT 0
 #define PCM512x_LAGN_SHIFT 4
-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 6/7] ASoC: pcm512x: Avoid the PLL for the DAC clock, if possible
  2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
                   ` (4 preceding siblings ...)
  2015-01-14 11:52 ` [PATCH 5/7] ASoC: pcm512x: Support mastering BCLK/LRCLK " Peter Rosin
@ 2015-01-14 11:52 ` Peter Rosin
  2015-01-14 11:52 ` [PATCH 7/7] ASoC: pcm512x: Support SND_SOC_DAIFMT_CBM_CFS Peter Rosin
  6 siblings, 0 replies; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel
  Cc: Peter Rosin, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai, linux-kernel

From: Peter Rosin <peda@axentia.se>

The PLL introduces jitter, which in turn introduces noice if used
to clock the DAC. Thus, avoid the PLL output, and use the PLL input
to drive the DAC clock, if possible.

This is described for the PCM5142/PCM5242 chips in the answers to the
forum post "PCM5142/PCM5242 DAC clock source" at the TI E2E community
pages (1).

(1) http://e2e.ti.com/support/data_converters/audio_converters/f/64/t/389994

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 sound/soc/codecs/pcm512x.c |  119 ++++++++++++++++++++++++++++++++++----------
 sound/soc/codecs/pcm512x.h |    4 +-
 2 files changed, 96 insertions(+), 27 deletions(-)

diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index df8948b4e848..2a50c58a2fb1 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -105,6 +105,7 @@ static const struct reg_default pcm512x_reg_defaults[] = {
 	{ PCM512x_VCOM_CTRL_2,       0x01 },
 	{ PCM512x_BCLK_LRCLK_CFG,    0x00 },
 	{ PCM512x_MASTER_MODE,       0x7c },
+	{ PCM512x_GPIO_DACIN,        0x00 },
 	{ PCM512x_GPIO_PLLIN,        0x00 },
 	{ PCM512x_SYNCHRONIZE,       0x10 },
 	{ PCM512x_PLL_COEFF_0,       0x00 },
@@ -138,6 +139,7 @@ static bool pcm512x_readable(struct device *dev, unsigned int reg)
 	case PCM512x_MASTER_MODE:
 	case PCM512x_PLL_REF:
 	case PCM512x_DAC_REF:
+	case PCM512x_GPIO_DACIN:
 	case PCM512x_GPIO_PLLIN:
 	case PCM512x_SYNCHRONIZE:
 	case PCM512x_PLL_COEFF_0:
@@ -681,6 +683,37 @@ done:
 	return 0;
 }
 
+static unsigned long pcm512x_pllin_dac_rate(struct snd_soc_dai *dai,
+					    unsigned long osr_rate,
+					    unsigned long pllin_rate)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
+	unsigned long dac_rate;
+
+	if (!pcm512x->pll_out)
+		return 0; /* no PLL to bypass, force SCK as DAC input */
+
+	if (pllin_rate % osr_rate)
+		return 0; /* futile, quit early */
+
+	/* run DAC no faster than 6144000 Hz */
+	for (dac_rate = rounddown(6144000, osr_rate);
+	     dac_rate;
+	     dac_rate -= osr_rate) {
+
+		if (pllin_rate / dac_rate > 128)
+			return 0; /* DAC divider would be too big */
+
+		if (!(pllin_rate % dac_rate))
+			return dac_rate;
+
+		dac_rate -= osr_rate;
+	}
+
+	return 0;
+}
+
 static int pcm512x_set_dividers(struct snd_soc_dai *dai,
 				struct snd_pcm_hw_params *params)
 {
@@ -694,6 +727,7 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
 	unsigned long bclk_rate;
 	unsigned long sample_rate;
 	unsigned long osr_rate;
+	unsigned long dacsrc_rate;
 	int bclk_div;
 	int lrclk_div;
 	int dsp_div;
@@ -701,11 +735,10 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
 	unsigned long dac_rate;
 	int ncp_div;
 	int osr_div;
-	unsigned long dac_mul;
-	unsigned long sck_mul;
 	int ret;
 	int idac;
 	int fssp;
+	int gpio;
 
 	lrclk_div = pcm512x_params_to_frame_size(params);
 	if (lrclk_div == 0) {
@@ -794,31 +827,72 @@ static int pcm512x_set_dividers(struct snd_soc_dai *dai,
 	/* run DSP no faster than 50 MHz */
 	dsp_div = mck_rate > 50000000 ? 2 : 1;
 
-	/* run DAC no faster than 6144000 Hz */
-	dac_mul = 6144000 / osr_rate;
-	sck_mul = sck_rate / osr_rate;
-	for (; dac_mul; dac_mul--) {
-		if (!(sck_mul % dac_mul))
-			break;
-	}
-	if (!dac_mul) {
-		dev_err(dev, "Failed to find DAC rate\n");
-		return -EINVAL;
-	}
+	dac_rate = pcm512x_pllin_dac_rate(dai, osr_rate, pllin_rate);
+	if (dac_rate) {
+		/* the desired clock rate is "compatible" with the pll input
+		 * clock, so use that clock as dac input instead of the pll
+		 * output clock since the pll will introduce jitter and thus
+		 * noise.
+		 */
+		dev_dbg(dev, "using pll input as dac input\n");
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_DAC_REF,
+					 PCM512x_SDAC, PCM512x_SDAC_GPIO);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to set gpio as dacref: %d\n", ret);
+			return ret;
+		}
 
-	dac_rate = dac_mul * osr_rate;
-	dev_dbg(dev, "dac_rate %lu sample_rate %lu\n", dac_rate, sample_rate);
+		gpio = PCM512x_GREF_GPIO1 + pcm512x->pll_in - 1;
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_GPIO_DACIN,
+					 PCM512x_GREF, gpio);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to set gpio %d as dacin: %d\n",
+				pcm512x->pll_in, ret);
+			return ret;
+		}
+
+		dacsrc_rate = pllin_rate;
+	} else {
+		/* run DAC no faster than 6144000 Hz */
+		unsigned long dac_mul = 6144000 / osr_rate;
+		unsigned long sck_mul = sck_rate / osr_rate;
+
+		for (; dac_mul; dac_mul--) {
+			if (!(sck_mul % dac_mul))
+				break;
+		}
+		if (!dac_mul) {
+			dev_err(dev, "Failed to find DAC rate\n");
+			return -EINVAL;
+		}
 
-	dac_div = DIV_ROUND_CLOSEST(sck_rate, dac_rate);
+		dac_rate = dac_mul * osr_rate;
+		dev_dbg(dev, "dac_rate %lu sample_rate %lu\n",
+			dac_rate, sample_rate);
+
+		ret = regmap_update_bits(pcm512x->regmap, PCM512x_DAC_REF,
+					 PCM512x_SDAC, PCM512x_SDAC_SCK);
+		if (ret != 0) {
+			dev_err(codec->dev,
+				"Failed to set sck as dacref: %d\n", ret);
+			return ret;
+		}
+
+		dacsrc_rate = sck_rate;
+	}
+
+	dac_div = DIV_ROUND_CLOSEST(dacsrc_rate, dac_rate);
 	if (dac_div > 128) {
 		dev_err(dev, "Failed to find DAC divider\n");
 		return -EINVAL;
 	}
 
-	ncp_div = DIV_ROUND_CLOSEST(sck_rate / dac_div, 1536000);
-	if (ncp_div > 128 || sck_rate / dac_div / ncp_div > 2048000) {
+	ncp_div = DIV_ROUND_CLOSEST(dacsrc_rate / dac_div, 1536000);
+	if (ncp_div > 128 || dacsrc_rate / dac_div / ncp_div > 2048000) {
 		/* run NCP no faster than 2048000 Hz, but why? */
-		ncp_div = DIV_ROUND_UP(sck_rate / dac_div, 2048000);
+		ncp_div = DIV_ROUND_UP(dacsrc_rate / dac_div, 2048000);
 		if (ncp_div > 128) {
 			dev_err(dev, "Failed to find NCP divider\n");
 			return -EINVAL;
@@ -1033,13 +1107,6 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 	if (ret != 0)
 		return ret;
 
-	ret = regmap_update_bits(pcm512x->regmap, PCM512x_DAC_REF,
-				 PCM512x_SDAC, PCM512x_SDAC_SCK);
-	if (ret != 0) {
-		dev_err(codec->dev, "Failed to set sck as dacref: %d\n", ret);
-		return ret;
-	}
-
 	if (pcm512x->pll_out) {
 		ret = regmap_update_bits(pcm512x->regmap, PCM512x_PLL_REF,
 					 PCM512x_SREF, PCM512x_SREF_GPIO);
diff --git a/sound/soc/codecs/pcm512x.h b/sound/soc/codecs/pcm512x.h
index eba5adc2cdb1..b7c310207223 100644
--- a/sound/soc/codecs/pcm512x.h
+++ b/sound/soc/codecs/pcm512x.h
@@ -38,6 +38,7 @@
 #define PCM512x_MASTER_MODE       (PCM512x_PAGE_BASE(0) +  12)
 #define PCM512x_PLL_REF           (PCM512x_PAGE_BASE(0) +  13)
 #define PCM512x_DAC_REF           (PCM512x_PAGE_BASE(0) +  14)
+#define PCM512x_GPIO_DACIN        (PCM512x_PAGE_BASE(0) +  16)
 #define PCM512x_GPIO_PLLIN        (PCM512x_PAGE_BASE(0) +  18)
 #define PCM512x_SYNCHRONIZE       (PCM512x_PAGE_BASE(0) +  19)
 #define PCM512x_PLL_COEFF_0       (PCM512x_PAGE_BASE(0) +  20)
@@ -162,8 +163,9 @@
 #define PCM512x_SDAC_PLL    (1 << 4)
 #define PCM512x_SDAC_SCK    (3 << 4)
 #define PCM512x_SDAC_BCK    (4 << 4)
+#define PCM512x_SDAC_GPIO   (5 << 4)
 
-/* Page 0, Register 18 - GPIO source for PLL */
+/* Page 0, Register 16, 18 - GPIO source for DAC, PLL */
 #define PCM512x_GREF        (7 << 0)
 #define PCM512x_GREF_SHIFT  0
 #define PCM512x_GREF_GPIO1  (0 << 0)
-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 7/7] ASoC: pcm512x: Support SND_SOC_DAIFMT_CBM_CFS
  2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
                   ` (5 preceding siblings ...)
  2015-01-14 11:52 ` [PATCH 6/7] ASoC: pcm512x: Avoid the PLL for the DAC clock, if possible Peter Rosin
@ 2015-01-14 11:52 ` Peter Rosin
  6 siblings, 0 replies; 9+ messages in thread
From: Peter Rosin @ 2015-01-14 11:52 UTC (permalink / raw)
  To: alsa-devel
  Cc: Peter Rosin, Liam Girdwood, Mark Brown, Jaroslav Kysela,
	Takashi Iwai, linux-kernel

From: Peter Rosin <peda@axentia.se>

Signed-off-by: Peter Rosin <peda@axentia.se>
---
 sound/soc/codecs/pcm512x.c |   13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index 2a50c58a2fb1..511d98b3afa4 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -486,6 +486,7 @@ static int pcm512x_dai_startup(struct snd_pcm_substream *substream,
 
 	switch (pcm512x->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 	case SND_SOC_DAIFMT_CBM_CFM:
+	case SND_SOC_DAIFMT_CBM_CFS:
 		return pcm512x_dai_startup_master(substream, dai);
 
 	case SND_SOC_DAIFMT_CBS_CFS:
@@ -992,6 +993,8 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 	struct pcm512x_priv *pcm512x = snd_soc_codec_get_drvdata(codec);
 	int alen;
 	int gpio;
+	int clock_output;
+	int master_mode;
 	int ret;
 
 	dev_dbg(codec->dev, "hw_params %u Hz, %u channels\n",
@@ -1040,6 +1043,12 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 		}
 		return 0;
 	case SND_SOC_DAIFMT_CBM_CFM:
+		clock_output = PCM512x_BCKO | PCM512x_LRKO;
+		master_mode = PCM512x_RLRK | PCM512x_RBCK;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFS:
+		clock_output = PCM512x_BCKO;
+		master_mode = PCM512x_RBCK;
 		break;
 	default:
 		return -EINVAL;
@@ -1136,7 +1145,7 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 
 	ret = regmap_update_bits(pcm512x->regmap, PCM512x_BCLK_LRCLK_CFG,
 				 PCM512x_BCKP | PCM512x_BCKO | PCM512x_LRKO,
-				 PCM512x_BCKO | PCM512x_LRKO);
+				 clock_output);
 	if (ret != 0) {
 		dev_err(codec->dev, "Failed to enable clock output: %d\n", ret);
 		return ret;
@@ -1144,7 +1153,7 @@ static int pcm512x_hw_params(struct snd_pcm_substream *substream,
 
 	ret = regmap_update_bits(pcm512x->regmap, PCM512x_MASTER_MODE,
 				 PCM512x_RLRK | PCM512x_RBCK,
-				 PCM512x_RLRK | PCM512x_RBCK);
+				 master_mode);
 	if (ret != 0) {
 		dev_err(codec->dev, "Failed to enable master mode: %d\n", ret);
 		return ret;
-- 
1.7.10.4


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [alsa-devel] [PATCH 1/7] ALSA: pcm: Add snd_interval_ranges() and snd_pcm_hw_constraint_ranges()
  2015-01-14 11:52 ` [PATCH 1/7] ALSA: pcm: Add snd_interval_ranges() and snd_pcm_hw_constraint_ranges() Peter Rosin
@ 2015-01-26 22:10   ` Lars-Peter Clausen
  0 siblings, 0 replies; 9+ messages in thread
From: Lars-Peter Clausen @ 2015-01-26 22:10 UTC (permalink / raw)
  To: Peter Rosin, alsa-devel
  Cc: Takashi Iwai, linux-kernel, Mark Brown, Peter Rosin

On 01/14/2015 12:52 PM, Peter Rosin wrote:
> From: Peter Rosin <peda@axentia.se>
>
> Add helper functions to allow drivers to specify several disjoint
> ranges for a variable. In particular, there is a codec (PCM512x) that
> has a hole in its supported range of rates, due to PLL and divider
> restrictions.

Might be worth mentioning that this is the extension of 
snd_pcm_hw_constraint_list() from points to ranges. Otherwise looks good.

Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2015-01-26 22:10 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-01-14 11:52 [PATCH 0/7] ASoC: pcm512x: Clock master modes Peter Rosin
2015-01-14 11:52 ` [PATCH 1/7] ALSA: pcm: Add snd_interval_ranges() and snd_pcm_hw_constraint_ranges() Peter Rosin
2015-01-26 22:10   ` [alsa-devel] " Lars-Peter Clausen
2015-01-14 11:52 ` [PATCH 2/7] ASoC: pcm512x: Fix spelling of register field names Peter Rosin
2015-01-14 11:52 ` [PATCH 3/7] ASoC: pcm512x: Change register default to match actual content after reset Peter Rosin
2015-01-14 11:52 ` [PATCH 4/7] ASoC: pcm512x: Support mastering BCLK/LRCLK without using the PLL Peter Rosin
2015-01-14 11:52 ` [PATCH 5/7] ASoC: pcm512x: Support mastering BCLK/LRCLK " Peter Rosin
2015-01-14 11:52 ` [PATCH 6/7] ASoC: pcm512x: Avoid the PLL for the DAC clock, if possible Peter Rosin
2015-01-14 11:52 ` [PATCH 7/7] ASoC: pcm512x: Support SND_SOC_DAIFMT_CBM_CFS Peter Rosin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).