[alsa-devel] [RFC 2/2] ASoC: img: Add driver for Pistachio audio card

Damien Horsley Damien.Horsley at imgtec.com
Mon Jan 25 20:12:12 CET 2016


From: "Damien.Horsley" <Damien.Horsley at imgtec.com>

Add a driver for Pistachio audio card

Signed-off-by: Damien.Horsley <Damien.Horsley at imgtec.com>
---
 include/dt-bindings/sound/pistachio-audio.h |    7 +
 sound/soc/img/Kconfig                       |   10 +
 sound/soc/img/Makefile                      |    1 +
 sound/soc/img/pistachio.c                   | 2249 +++++++++++++++++++++++++++
 4 files changed, 2267 insertions(+)
 create mode 100644 include/dt-bindings/sound/pistachio-audio.h
 create mode 100644 sound/soc/img/pistachio.c

diff --git a/include/dt-bindings/sound/pistachio-audio.h b/include/dt-bindings/sound/pistachio-audio.h
new file mode 100644
index 0000000..ac34261
--- /dev/null
+++ b/include/dt-bindings/sound/pistachio-audio.h
@@ -0,0 +1,7 @@
+#ifndef __PISTACHIO_AUDIO_H
+#define __PISTACHIO_AUDIO_H
+
+#define PISTACHIO_MCLK_I2S		0
+#define PISTACHIO_MCLK_DAC		1
+
+#endif
diff --git a/sound/soc/img/Kconfig b/sound/soc/img/Kconfig
index 857a951..9482de3 100644
--- a/sound/soc/img/Kconfig
+++ b/sound/soc/img/Kconfig
@@ -50,3 +50,13 @@ config SND_SOC_IMG_PISTACHIO_INTERNAL_DAC
 	help
 	  Say Y or M if you want to add support for Pistachio internal DAC
 	  driver for Imagination Technologies Pistachio internal DAC device.
+
+config SND_SOC_IMG_PISTACHIO
+	tristate "Audio support for Pistachio SoC"
+	depends on SND_SOC_IMG
+	select SND_SOC_IMG_I2S_IN
+	select SND_SOC_IMG_I2S_OUT
+	select SND_SOC_IMG_PARALLEL_OUT
+	select SND_SOC_IMG_SPDIF_IN
+	select SND_SOC_IMG_SPDIF_OUT
+	select SND_SOC_IMG_PISTACHIO_INTERNAL_DAC
\ No newline at end of file
diff --git a/sound/soc/img/Makefile b/sound/soc/img/Makefile
index 0508c1c..e72024f 100644
--- a/sound/soc/img/Makefile
+++ b/sound/soc/img/Makefile
@@ -5,3 +5,4 @@ obj-$(CONFIG_SND_SOC_IMG_SPDIF_IN) += img-spdif-in.o
 obj-$(CONFIG_SND_SOC_IMG_SPDIF_OUT) += img-spdif-out.o
 
 obj-$(CONFIG_SND_SOC_IMG_PISTACHIO_INTERNAL_DAC) += pistachio-internal-dac.o
+obj-$(CONFIG_SND_SOC_IMG_PISTACHIO) += pistachio.o
\ No newline at end of file
diff --git a/sound/soc/img/pistachio.c b/sound/soc/img/pistachio.c
new file mode 100644
index 0000000..b8d387a
--- /dev/null
+++ b/sound/soc/img/pistachio.c
@@ -0,0 +1,2249 @@
+/*
+ * Pistachio audio card driver
+ *
+ * Copyright (C) 2015 Imagination Technologies Ltd.
+ *
+ * Author: Damien Horsley <Damien.Horsley at imgtec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ */
+
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <dt-bindings/sound/pistachio-audio.h>
+
+#define PISTACHIO_PLL_RATE_A		147456000
+#define PISTACHIO_PLL_RATE_B		135475200
+#define PISTACHIO_MAX_DIV		256
+#define PISTACHIO_MIN_MCLK_FREQ		(135475200 / 256)
+
+#define PISTACHIO_CLOCK_MASTER_EXT	-1
+#define PISTACHIO_CLOCK_MASTER_LOOPBACK	-2
+
+#define PISTACHIO_MAX_I2S_CODECS	12
+
+#define PISTACHIO_MAX_FS_RATES		20
+
+#define PISTACHIO_I2S_MCLK_MAX_FREQ	200000000
+#define PISTACHIO_DAC_MCLK_MAX_FREQ	200000000
+
+#define PISTACHIO_INTERNAL_DAC_PREFIX	"internal-dac"
+#define PISTACHIO_I2S_OUT_PREFIX	"i2s-out"
+#define PISTACHIO_I2S_IN_PREFIX		"i2s-in"
+
+#define PISTACHIO_I2S_MCLK_NAME		"i2s_mclk"
+#define PISTACHIO_DAC_MCLK_NAME		"dac_mclk"
+
+#define PISTACHIO_I2S_OUTPUT_NAME	"I2S OUTPUT"
+#define PISTACHIO_I2S_INPUT_NAME	"I2S INPUT"
+
+#define PISTACHIO_I2S_LOOPBACK_REG		0x88
+#define PISTACHIO_I2S_LOOPBACK_CLK_MASK		0x3
+
+#define PISTACHIO_I2S_LOOPBACK_CLK_NONE		0
+#define PISTACHIO_I2S_LOOPBACK_CLK_LOCAL	2
+
+#define PISTACHIO_MAX_DAPM_ROUTES		6
+
+struct pistachio_audio_output {
+	unsigned int active_rate;
+};
+
+struct pistachio_parallel_out {
+	struct pistachio_audio_output output;
+	struct snd_soc_dai_link_component component;
+};
+
+struct pistachio_mclk {
+	char *name;
+	struct clk *mclk;
+	unsigned int cur_rate;
+	unsigned int max_rate;
+};
+
+struct pistachio_i2s_mclk {
+	struct pistachio_mclk *mclk;
+	unsigned int *fs_rates;
+	unsigned int num_fs_rates;
+	unsigned int min_rate;
+	unsigned int max_rate;
+};
+
+struct pistachio_codec_i2s {
+	struct pistachio_mclk *mclk;
+	struct snd_soc_dai *dai;
+	unsigned int mclk_index;
+};
+
+struct pistachio_i2s {
+	struct pistachio_i2s_mclk mclk_a;
+	struct pistachio_i2s_mclk mclk_b;
+	struct pistachio_codec_i2s *codecs;
+	struct snd_soc_dai_link_component *components;
+	unsigned int num_codecs;
+};
+
+struct pistachio_i2s_out {
+	struct pistachio_i2s i2s;
+	struct pistachio_audio_output output;
+};
+
+struct pistachio_i2s_in {
+	struct pistachio_i2s i2s;
+	unsigned int active_rate;
+	unsigned int fmt;
+	int frame_master;
+	int bitclock_master;
+};
+
+struct pistachio_i2s_codec_info_s {
+	const char *prefix;
+	const char *dai_name;
+	struct device_node *np;
+	struct pistachio_mclk *mclk;
+	unsigned int mclk_index;
+};
+
+struct pistachio_i2s_codec_info {
+	unsigned int total_codecs;
+	unsigned int unique_codecs;
+	int bitclock_master_idx;
+	int frame_master_idx;
+	struct pistachio_i2s_codec_info_s codecs[PISTACHIO_MAX_I2S_CODECS];
+};
+
+struct pistachio_i2s_mclk_info {
+	unsigned int fs_rates[PISTACHIO_MAX_FS_RATES];
+	unsigned int num_fs_rates;
+	unsigned int min_rate;
+	unsigned int max_rate;
+};
+
+struct pistachio_card {
+	struct pistachio_audio_output *spdif_out;
+	struct pistachio_parallel_out *parallel_out;
+	struct pistachio_i2s_out *i2s_out;
+	struct pistachio_i2s_in *i2s_in;
+	bool spdif_in;
+	struct snd_soc_card card;
+	struct snd_soc_jack hp_jack;
+	struct snd_soc_jack_pin hp_jack_pin;
+	struct snd_soc_jack_gpio hp_jack_gpio;
+	unsigned int mute_gpio;
+	bool mute_gpio_inverted;
+	struct mutex rate_mutex;
+	struct clk *audio_pll;
+	unsigned int audio_pll_rate;
+	struct pistachio_mclk i2s_mclk;
+	struct pistachio_mclk dac_mclk;
+	struct regmap *periph_regs;
+	struct notifier_block i2s_clk_notifier;
+	struct snd_soc_dapm_route routes[PISTACHIO_MAX_DAPM_ROUTES];
+};
+
+static const struct snd_soc_dapm_widget pistachio_card_widgets[] = {
+	SND_SOC_DAPM_CLOCK_SUPPLY(PISTACHIO_I2S_MCLK_NAME),
+	SND_SOC_DAPM_CLOCK_SUPPLY(PISTACHIO_DAC_MCLK_NAME),
+	SND_SOC_DAPM_OUTPUT(PISTACHIO_I2S_OUTPUT_NAME),
+	SND_SOC_DAPM_INPUT(PISTACHIO_I2S_INPUT_NAME)
+};
+
+static int pistachio_card_set_sysclk_s(struct pistachio_codec_i2s *codec,
+				       unsigned int rate, struct device *dev)
+{
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(codec->dai, codec->mclk_index,
+				     rate, SND_SOC_CLOCK_IN);
+	if (ret)
+		dev_err(dev, "snd_soc_dai_set_sysclk failed: %d", ret);
+
+	return ret;
+}
+
+static int pistachio_card_set_sysclk(struct pistachio_i2s *i2s,
+				     struct pistachio_mclk *mclk,
+				     struct device *dev)
+{
+	int i, ret;
+	struct pistachio_codec_i2s *codec;
+	unsigned int rate = mclk->cur_rate;
+
+	for (i = 0; i < i2s->num_codecs; i++) {
+		codec = &i2s->codecs[i];
+		if (codec->mclk == mclk) {
+			ret = pistachio_card_set_sysclk_s(codec, rate, dev);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int pistachio_card_set_mclk_codecs(struct pistachio_card *pbc,
+					  struct pistachio_mclk *mclk)
+{
+	int ret;
+	struct device *dev = pbc->card.dev;
+
+	if (pbc->i2s_out) {
+		ret = pistachio_card_set_sysclk(&pbc->i2s_out->i2s, mclk, dev);
+		if (ret)
+			return ret;
+	}
+
+	if (pbc->i2s_in) {
+		ret = pistachio_card_set_sysclk(&pbc->i2s_in->i2s, mclk, dev);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static bool pistachio_card_mclk_active(struct pistachio_card *pbc,
+				       struct pistachio_mclk *mclk)
+{
+	if (pbc->i2s_out && pbc->i2s_out->output.active_rate) {
+		if (pbc->i2s_out->i2s.mclk_a.mclk == mclk)
+			return true;
+		if (pbc->i2s_out->i2s.mclk_b.mclk == mclk)
+			return true;
+	}
+
+	if (pbc->i2s_in && pbc->i2s_in->active_rate) {
+		if (pbc->i2s_in->i2s.mclk_a.mclk == mclk)
+			return true;
+		if (pbc->i2s_in->i2s.mclk_b.mclk == mclk)
+			return true;
+	}
+
+	return false;
+}
+
+static int pistachio_card_set_mclk(struct pistachio_card *pbc,
+				   struct pistachio_mclk *mclk,
+				   unsigned int rate)
+{
+	int ret;
+	unsigned int old_rate = mclk->cur_rate;
+	struct device *dev = pbc->card.dev;
+
+	if (pistachio_card_mclk_active(pbc, mclk)) {
+		dev_err(dev, "%s in use, cannot change rate\n", mclk->name);
+		return -EBUSY;
+	}
+
+	/*
+	 * Set cur_rate before the clk_set_rate call to stop the i2s
+	 * mclk rate change callback rejecting the change
+	 */
+	mclk->cur_rate = rate;
+	ret = clk_set_rate(mclk->mclk, rate);
+	if (ret) {
+		dev_err(dev, "clk_set_rate(%s, %u) failed: %d\n",
+			mclk->name, rate, ret);
+		mclk->cur_rate = old_rate;
+		return ret;
+	}
+
+	return pistachio_card_set_mclk_codecs(pbc, mclk);
+}
+
+static int pistachio_card_set_pll_rate(struct pistachio_card *pbc,
+				       unsigned int rate)
+{
+	int ret;
+	unsigned int old_i2s_rate;
+	struct device *dev = pbc->card.dev;
+
+	/*
+	 * If any configured streams are currently using a clock derived
+	 * from the audio pll, a pll rate change cannot take place
+	 */
+	if ((pbc->spdif_out && pbc->spdif_out->active_rate) ||
+	    (pbc->parallel_out && pbc->parallel_out->output.active_rate) ||
+	    (pbc->i2s_out && pbc->i2s_out->output.active_rate) ||
+	    (pbc->i2s_in && pbc->i2s_in->active_rate &&
+	    pbc->i2s_in->i2s.mclk_a.mclk)) {
+		dev_err(dev, "audio pll in use, cannot change rate\n");
+		return -EBUSY;
+	}
+
+	/*
+	 * Set cur_rate before the clk_set_rate call to stop the i2s
+	 * mclk rate change callback rejecting the change
+	 */
+	old_i2s_rate = pbc->i2s_mclk.cur_rate;
+	pbc->i2s_mclk.cur_rate = rate / (pbc->audio_pll_rate / old_i2s_rate);
+
+	ret = clk_set_rate(pbc->audio_pll, rate);
+	if (ret) {
+		dev_err(dev, "clk_set_rate(audio_pll, %u) failed: %d\n",
+			rate, ret);
+		pbc->i2s_mclk.cur_rate = old_i2s_rate;
+		return ret;
+	}
+
+	pbc->audio_pll_rate = rate;
+
+	pbc->dac_mclk.cur_rate = rate / (pbc->audio_pll_rate /
+					 pbc->dac_mclk.cur_rate);
+
+	ret = pistachio_card_set_mclk_codecs(pbc, &pbc->i2s_mclk);
+	if (ret)
+		return ret;
+
+	return pistachio_card_set_mclk_codecs(pbc, &pbc->dac_mclk);
+}
+
+static void pistachio_card_rate_err(struct pistachio_card *pbc,
+			struct pistachio_i2s_mclk *mclk_a,
+			struct pistachio_i2s_mclk *mclk_b,
+			unsigned int rate_a, unsigned int rate_b)
+{
+	char *dir_a, *dir_b;
+	struct device *dev = pbc->card.dev;
+
+	if (pbc->i2s_out && ((mclk_a == &pbc->i2s_out->i2s.mclk_a) ||
+			     (mclk_a == &pbc->i2s_out->i2s.mclk_b))) {
+		dir_a = "I2S out";
+	} else {
+		dir_a = "I2S in";
+	}
+
+	if (pbc->i2s_out && ((mclk_b == &pbc->i2s_out->i2s.mclk_a) ||
+			     (mclk_b == &pbc->i2s_out->i2s.mclk_b))) {
+		dir_b = "I2S out";
+	} else {
+		dir_b = "I2S in";
+	}
+
+	if (!mclk_b) {
+		dev_err(dev, "No valid rate for %s (%s sample rate %u)\n",
+			mclk_a->mclk->name, dir_a, rate_a);
+	} else {
+		dev_err(dev,
+			"No valid rate for %s (%s sample rate %u, %s sample rate %u)\n",
+			mclk_a->mclk->name, dir_a, rate_a, dir_b, rate_b);
+	}
+}
+
+static bool pistachio_card_mclk_ok(struct pistachio_i2s_mclk *mclk,
+				   unsigned int rate)
+{
+	int i;
+	unsigned int mclk_rate;
+
+	if (!mclk)
+		return true;
+
+	mclk_rate = mclk->mclk->cur_rate;
+
+	if ((mclk_rate < mclk->min_rate) || (mclk_rate > mclk->max_rate))
+		return false;
+
+	for (i = 0; i < mclk->num_fs_rates; i++)
+		if ((rate * mclk->fs_rates[i]) == mclk_rate)
+			return true;
+
+	return false;
+}
+
+static int pistachio_card_get_mclk_rate(struct pistachio_card *pbc,
+	struct pistachio_i2s_mclk *mclk_a, struct pistachio_i2s_mclk *mclk_b,
+	unsigned int rate_a, unsigned int rate_b, unsigned int *p_mclk_rate)
+{
+	int i, j;
+	unsigned int div, total_div, mclk_rate;
+
+	/*
+	 * If the current system clock rate is sufficient for the given
+	 * sample rates, do not change the rate.
+	 */
+	if (pistachio_card_mclk_ok(mclk_a, rate_a) &&
+	    pistachio_card_mclk_ok(mclk_b, rate_b)) {
+		*p_mclk_rate = mclk_a->mclk->cur_rate;
+		return 0;
+	}
+
+	/* Calculate total divide (internal divide and Nfs combined) */
+	total_div = pbc->audio_pll_rate / rate_a;
+
+	/* Attempt to find an mclk rate that satisfies the constraints */
+	for (i = 0; i < mclk_a->num_fs_rates; i++) {
+
+		div = total_div / mclk_a->fs_rates[i];
+
+		if (div > PISTACHIO_MAX_DIV)
+			continue;
+
+		mclk_rate = pbc->audio_pll_rate / div;
+
+		if ((mclk_rate < mclk_a->min_rate) ||
+		    (mclk_rate > mclk_a->max_rate) ||
+		    (mclk_rate > mclk_a->mclk->max_rate))
+			continue;
+
+		if ((rate_a * mclk_a->fs_rates[i] * div) !=
+		     pbc->audio_pll_rate)
+			continue;
+
+		if (!mclk_b)
+			break;
+
+		if ((mclk_rate < mclk_b->min_rate) ||
+		    (mclk_rate > mclk_b->max_rate))
+			continue;
+
+		for (j = 0; j < mclk_b->num_fs_rates; j++)
+			if ((rate_b * mclk_b->fs_rates[j] * div) ==
+			     pbc->audio_pll_rate)
+				break;
+
+		if (j != mclk_b->num_fs_rates)
+			break;
+	}
+
+	if (i == mclk_a->num_fs_rates) {
+		pistachio_card_rate_err(pbc, mclk_a, mclk_b, rate_a, rate_b);
+		return -EINVAL;
+	}
+
+	*p_mclk_rate = mclk_rate;
+
+	return 0;
+}
+
+static int pistachio_card_update_mclk(struct pistachio_card *pbc,
+	struct pistachio_i2s_mclk *mclk_a, struct pistachio_i2s_mclk *mclk_b,
+	unsigned int rate_a, unsigned int rate_b)
+{
+	struct pistachio_mclk *mclk = mclk_a->mclk;
+	unsigned int mclk_rate;
+	int ret;
+
+	ret = pistachio_card_get_mclk_rate(pbc, mclk_a, mclk_b, rate_a,
+					   rate_b, &mclk_rate);
+	if (ret)
+		return ret;
+
+	if (mclk->cur_rate != mclk_rate)
+		return pistachio_card_set_mclk(pbc, mclk, mclk_rate);
+
+	return 0;
+}
+
+static int pistachio_card_update_mclk_single(struct pistachio_card *pbc,
+		      struct pistachio_i2s_mclk *mclk, unsigned int rate)
+{
+	return pistachio_card_update_mclk(pbc, mclk, NULL, rate, 0);
+}
+
+static int pistachio_card_get_pll_rate(unsigned int rate, struct device *dev)
+{
+	switch (rate) {
+	case 8000:
+	case 16000:
+	case 32000:
+	case 48000:
+	case 64000:
+	case 96000:
+	case 192000:
+		return PISTACHIO_PLL_RATE_A;
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+		return PISTACHIO_PLL_RATE_B;
+	default:
+		dev_err(dev, "No suitable pll rate for sample rate %u\n", rate);
+		return -EINVAL;
+	}
+}
+
+static int _pistachio_card_change_rate(struct pistachio_card *pbc,
+				       unsigned int rate,
+				       struct pistachio_i2s *i2s)
+{
+	int ret;
+	unsigned int pll_rate;
+
+	ret = pistachio_card_get_pll_rate(rate, pbc->card.dev);
+	if (ret < 0)
+		return ret;
+
+	pll_rate = ret;
+
+	if (pbc->audio_pll_rate != pll_rate) {
+		ret = pistachio_card_set_pll_rate(pbc, pll_rate);
+		if (ret)
+			return ret;
+	}
+
+	/*
+	 * Nothing more to do if an mclk is not used. The individual
+	 * cpu-dai drivers will make the required clock changes
+	 */
+	if (!i2s)
+		return 0;
+
+	ret = pistachio_card_update_mclk_single(pbc, &i2s->mclk_a, rate);
+	if (ret)
+		return ret;
+
+	if (!i2s->mclk_b.mclk)
+		return 0;
+
+	return pistachio_card_update_mclk_single(pbc, &i2s->mclk_b, rate);
+}
+
+static int pistachio_card_change_rate(struct pistachio_card *pbc,
+			unsigned int rate, struct pistachio_i2s *i2s,
+			unsigned int *active_rate)
+{
+	int ret;
+
+	mutex_lock(&pbc->rate_mutex);
+	*active_rate = 0;
+	ret = _pistachio_card_change_rate(pbc, rate, i2s);
+	if (!ret)
+		*active_rate = rate;
+	mutex_unlock(&pbc->rate_mutex);
+
+	return ret;
+}
+
+static int pistachio_card_i2s_link_init(struct pistachio_i2s *i2s,
+					struct snd_soc_pcm_runtime *rtd,
+					struct device *dev)
+{
+	int ret, i;
+	unsigned long rate;
+	struct pistachio_codec_i2s *codec;
+
+	for (i = 0; i < i2s->num_codecs; i++) {
+		codec = &i2s->codecs[i];
+		codec->dai = rtd->codec_dais[i];
+		if (codec->mclk) {
+			rate = codec->mclk->cur_rate;
+			ret = pistachio_card_set_sysclk_s(codec, rate, dev);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int pistachio_card_i2s_out_link_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+	struct device *dev = pbc->card.dev;
+
+	return pistachio_card_i2s_link_init(&pbc->i2s_out->i2s, rtd, dev);
+}
+
+static int pistachio_card_i2s_in_link_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+	int ret, i;
+	unsigned int fmt;
+	struct device *dev = pbc->card.dev;
+	u32 val;
+
+	ret = pistachio_card_i2s_link_init(&pbc->i2s_in->i2s, rtd, dev);
+	if (ret)
+		return ret;
+
+	if (pbc->i2s_in->frame_master == PISTACHIO_CLOCK_MASTER_LOOPBACK)
+		val = PISTACHIO_I2S_LOOPBACK_CLK_LOCAL;
+	else
+		val = PISTACHIO_I2S_LOOPBACK_CLK_NONE;
+
+	ret = regmap_update_bits(pbc->periph_regs, PISTACHIO_I2S_LOOPBACK_REG,
+				 PISTACHIO_I2S_LOOPBACK_CLK_MASK, val);
+	if (ret) {
+		dev_err(pbc->card.dev, "regmap_update_bits failed: %d\n", ret);
+		return ret;
+	}
+
+	fmt = pbc->i2s_in->fmt | SND_SOC_DAIFMT_CBM_CFM;
+	ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt);
+	if (ret) {
+		dev_err(dev, "snd_soc_dai_set_fmt (cpu) failed: %d\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < pbc->i2s_in->i2s.num_codecs; i++) {
+		fmt = pbc->i2s_in->fmt;
+
+		if (i == pbc->i2s_in->frame_master)
+			if (i == pbc->i2s_in->bitclock_master)
+				fmt |= SND_SOC_DAIFMT_CBM_CFM;
+			else
+				fmt |= SND_SOC_DAIFMT_CBS_CFM;
+		else
+			if (i == pbc->i2s_in->bitclock_master)
+				fmt |= SND_SOC_DAIFMT_CBM_CFS;
+			else
+				fmt |= SND_SOC_DAIFMT_CBS_CFS;
+
+		ret = snd_soc_dai_set_fmt(rtd->codec_dais[i], fmt);
+		if (ret) {
+			dev_err(dev, "snd_soc_dai_set_fmt failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void pistachio_card_parallel_out_shutdown(struct snd_pcm_substream *st)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	pbc->parallel_out->output.active_rate = 0;
+}
+
+static int pistachio_card_parallel_out_hw_params(struct snd_pcm_substream *st,
+					      struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	return pistachio_card_change_rate(pbc, params_rate(params), NULL,
+				   &pbc->parallel_out->output.active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_parallel_out_ops = {
+	.shutdown = pistachio_card_parallel_out_shutdown,
+	.hw_params = pistachio_card_parallel_out_hw_params
+};
+
+static void pistachio_card_spdif_out_shutdown(struct snd_pcm_substream *st)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	pbc->spdif_out->active_rate = 0;
+}
+
+static int pistachio_card_spdif_out_hw_params(struct snd_pcm_substream *st,
+					      struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	return pistachio_card_change_rate(pbc, params_rate(params), NULL,
+					  &pbc->spdif_out->active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_spdif_out_ops = {
+	.shutdown = pistachio_card_spdif_out_shutdown,
+	.hw_params = pistachio_card_spdif_out_hw_params
+};
+
+static void pistachio_card_i2s_out_shutdown(struct snd_pcm_substream *st)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	pbc->i2s_out->output.active_rate = 0;
+}
+
+static int pistachio_card_i2s_out_hw_params(struct snd_pcm_substream *st,
+					    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	return pistachio_card_change_rate(pbc, params_rate(params),
+					  &pbc->i2s_out->i2s,
+					  &pbc->i2s_out->output.active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_i2s_out_ops = {
+	.shutdown = pistachio_card_i2s_out_shutdown,
+	.hw_params = pistachio_card_i2s_out_hw_params
+};
+
+static void pistachio_card_i2s_in_shutdown(struct snd_pcm_substream *st)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	pbc->i2s_in->active_rate = 0;
+}
+
+static int pistachio_card_i2s_in_hw_params(struct snd_pcm_substream *st,
+					   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = st->private_data;
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(rtd->card);
+
+	return pistachio_card_change_rate(pbc, params_rate(params),
+					  &pbc->i2s_in->i2s,
+					  &pbc->i2s_in->active_rate);
+}
+
+static struct snd_soc_ops pistachio_card_i2s_in_ops = {
+	.shutdown = pistachio_card_i2s_in_shutdown,
+	.hw_params = pistachio_card_i2s_in_hw_params
+};
+
+static int pistachio_card_parse_spdif_out(struct device_node *node,
+					  struct pistachio_card *pbc,
+					  struct snd_soc_dai_link *link)
+{
+	struct device_node *np;
+	struct device *dev = pbc->card.dev;
+
+	pbc->spdif_out = devm_kzalloc(pbc->card.dev, sizeof(*pbc->spdif_out),
+				      GFP_KERNEL);
+	if (!pbc->spdif_out)
+		return -ENOMEM;
+
+	link->name = link->stream_name = "pistachio-spdif-out";
+
+	np = of_parse_phandle(node, "cpu-dai", 0);
+	if (!np) {
+		dev_err(dev, "Failed to parse cpu-dai (%s)\n", node->name);
+		return -EINVAL;
+	}
+
+	link->cpu_of_node = np;
+	link->platform_of_node = np;
+	link->codec_dai_name = "snd-soc-dummy-dai";
+	link->codec_name = "snd-soc-dummy";
+	link->ops = &pistachio_card_spdif_out_ops;
+
+	return 0;
+}
+
+static int pistachio_card_parse_spdif_in(struct device_node *node,
+					 struct pistachio_card *pbc,
+					 struct snd_soc_dai_link *link)
+{
+	struct device_node *np;
+	struct device *dev = pbc->card.dev;
+
+	pbc->spdif_in = true;
+
+	link->name = link->stream_name = "pistachio-spdif-in";
+
+	np = of_parse_phandle(node, "cpu-dai", 0);
+	if (!np) {
+		dev_err(dev, "Failed to parse cpu-dai (%s)\n", node->name);
+		return -EINVAL;
+	}
+
+	link->cpu_of_node = np;
+	link->platform_of_node = np;
+	link->codec_dai_name = "snd-soc-dummy-dai";
+	link->codec_name = "snd-soc-dummy";
+
+	return 0;
+}
+
+static int pistachio_card_parse_parallel_out(struct device_node *node,
+					     struct pistachio_card *pbc,
+					     struct snd_soc_dai_link *link)
+{
+	struct device_node *np;
+	int ret;
+	struct device *dev = pbc->card.dev;
+
+	pbc->parallel_out = devm_kzalloc(pbc->card.dev,
+					 sizeof(*pbc->parallel_out),
+					 GFP_KERNEL);
+	if (!pbc->parallel_out)
+		return -ENOMEM;
+
+	link->name = link->stream_name = "pistachio-parallel-out";
+
+	np = of_parse_phandle(node, "cpu-dai", 0);
+	if (!np) {
+		dev_err(dev, "Failed to parse cpu-dai (%s)\n", node->name);
+		return -EINVAL;
+	}
+
+	link->cpu_of_node = np;
+	link->platform_of_node = np;
+	link->codecs = &pbc->parallel_out->component;
+
+	np = of_parse_phandle(node, "sound-dai", 0);
+	if (!np) {
+		dev_err(dev, "Failed to parse sound-dai (%s)\n", node->name);
+		return -EINVAL;
+	}
+	link->codecs[0].of_node = np;
+	link->num_codecs = 1;
+	ret = snd_soc_of_get_dai_name(node, &link->codecs[0].dai_name);
+	if (ret) {
+		dev_err(dev, "snd_soc_of_get_dai_name failed (%s): %d\n",
+			node->name, ret);
+		return ret;
+	}
+
+	link->ops = &pistachio_card_parallel_out_ops;
+
+	return 0;
+}
+
+static int pistachio_card_parse_i2s_mclk(struct pistachio_card *pbc,
+		struct device_node *np, struct pistachio_mclk *mclk,
+		struct pistachio_i2s_mclk_info *fs)
+{
+	int ret, i, j, k, num_fs_rates;
+	u32 min_fs_rate, max_fs_rate, max_rate;
+	u32 fs_rates[PISTACHIO_MAX_FS_RATES];
+	struct device *dev = pbc->card.dev;
+
+	ret = of_property_read_u32(np, "mclk-min-fs-freq", &min_fs_rate);
+	if (ret) {
+		dev_err(dev, "Failed to read mclk-min-fs-freq (%s): %d\n",
+			np->name, ret);
+		return ret;
+	}
+
+	ret = of_property_read_u32(np, "mclk-max-fs-freq", &max_fs_rate);
+	if (ret) {
+		dev_err(dev, "Failed to read mclk-max-fs-freq (%s): %d\n",
+			np->name, ret);
+		return ret;
+	}
+
+	ret = of_property_read_u32(np, "mclk-max-freq", &max_rate);
+	if (ret) {
+		dev_err(dev, "Failed to read mclk-max-freq (%s): %d\n",
+			np->name, ret);
+		return ret;
+	}
+
+	if ((max_fs_rate < PISTACHIO_MIN_MCLK_FREQ) ||
+	    (max_fs_rate > max_rate) ||
+	    (max_fs_rate < min_fs_rate)) {
+		dev_err(dev, "Invalid min/max rate (%s)\n", np->name);
+		return -EINVAL;
+	}
+
+	if (min_fs_rate > fs->min_rate)
+		fs->min_rate = min_fs_rate;
+
+	if (max_fs_rate < fs->max_rate)
+		fs->max_rate = max_fs_rate;
+
+	if (max_rate < mclk->max_rate)
+		mclk->max_rate = max_rate;
+
+	if (fs->min_rate > fs->max_rate) {
+		dev_err(dev, "No valid frequency range remaining for %s\n",
+			mclk->name);
+		return -EINVAL;
+	}
+
+	num_fs_rates = of_property_count_u32_elems(np, "mclk-fs");
+	if (num_fs_rates < 0) {
+		dev_err(dev, "of_property_count_u32_elems failed: %d (%s)\n",
+			num_fs_rates, np->name);
+		return num_fs_rates;
+	}
+
+	if (!num_fs_rates || (num_fs_rates > PISTACHIO_MAX_FS_RATES)) {
+		dev_err(dev, "Invalid fs-rates count: %d (%s)\n",
+			num_fs_rates, np->name);
+		return -EINVAL;
+	}
+
+	ret = of_property_read_u32_array(np, "mclk-fs", fs_rates,
+					 num_fs_rates);
+	if (ret) {
+		dev_err(dev, "of_property_read_u32_array failed: %d (%s)\n",
+			ret, np->name);
+		return ret;
+	}
+
+	/*
+	 * If this is the first fs-rates list for this combination
+	 * of {i2s direction, mclk}, this list defines the
+	 * current fs-rate list for this combination. Else, this list
+	 * subtracts any fs-rates that are not present in both lists
+	 */
+	if (!fs->num_fs_rates) {
+		/* Remove any duplicates while copying */
+		for (i = 0, k = 0; i < num_fs_rates; i++) {
+			for (j = 0; j < k; j++)
+				if (fs->fs_rates[j] == fs_rates[i])
+					break;
+			if (j == k)
+				fs->fs_rates[k++] = fs_rates[i];
+		}
+		fs->num_fs_rates = k;
+	} else {
+		for (j = 0; j < fs->num_fs_rates; j++) {
+			for (i = 0; i < num_fs_rates; i++)
+				if (fs->fs_rates[j] == fs_rates[i])
+					break;
+
+			if (i == num_fs_rates) {
+				for (k = j; k < (fs->num_fs_rates - 1); k++)
+					fs->fs_rates[k] = fs->fs_rates[k + 1];
+				fs->num_fs_rates--;
+
+				if (!fs->num_fs_rates) {
+					dev_err(dev, "No fs rates remaining for %s\n",
+						mclk->name);
+					return -EINVAL;
+				}
+
+				j--;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int pistachio_card_parse_i2s_codec(struct device_node *np,
+	struct device_node *subnode, int index, struct pistachio_card *pbc,
+	struct device_node *codec, struct pistachio_i2s_codec_info *codec_info,
+	struct pistachio_mclk *mclk)
+{
+	int i, ret;
+	struct pistachio_i2s_codec_info_s *info;
+	struct device *dev = pbc->card.dev;
+
+	if (codec_info->total_codecs == PISTACHIO_MAX_I2S_CODECS) {
+		dev_err(dev, "Too many codecs\n");
+		of_node_put(codec);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < codec_info->total_codecs; i++)
+		if (codec_info->codecs[i].np == codec)
+			break;
+
+	if (i == codec_info->total_codecs)
+		codec_info->unique_codecs++;
+
+	info = &codec_info->codecs[codec_info->total_codecs++];
+	info->np = codec;
+	info->prefix = subnode->name;
+	info->mclk = mclk;
+
+	ret = of_property_read_u32(subnode, "mclk-index", &info->mclk_index);
+	if (ret)
+		info->mclk_index = 0;
+
+	ret = snd_soc_of_get_dai_name(subnode, &info->dai_name);
+	if (ret) {
+		dev_err(dev, "snd_soc_of_get_dai_name failed: %d (%s)\n",
+			ret, subnode->name);
+		return ret;
+	}
+
+	if (of_property_read_bool(subnode, "frame-master")) {
+		if (codec_info->frame_master_idx != -1) {
+			dev_err(dev, "Multiple frame clock masters (%s)\n",
+				np->name);
+			return -EINVAL;
+		}
+		codec_info->frame_master_idx = index;
+	}
+
+	if (of_property_read_bool(subnode, "bitclock-master")) {
+		if (codec_info->bitclock_master_idx != -1) {
+			dev_err(dev, "Multiple bit clock masters (%s)\n",
+				np->name);
+			return -EINVAL;
+		}
+		codec_info->bitclock_master_idx = index;
+	}
+
+	return 0;
+}
+
+static int pistachio_card_parse_i2s_codecs(struct device_node *np,
+			struct pistachio_card *pbc,
+			struct pistachio_i2s_codec_info *codec_info,
+			struct pistachio_i2s_mclk_info *i2s_fs_info,
+			struct pistachio_i2s_mclk_info *dac_fs_info)
+{
+	int i, ret;
+	struct device_node *subnode, *codec;
+	u32 mclk_id;
+	struct pistachio_mclk *mclk;
+	struct pistachio_i2s_mclk_info *fs_info;
+	struct device *dev = pbc->card.dev;
+
+	i = 0;
+	for_each_child_of_node(np, subnode) {
+
+		ret = of_property_read_u32(subnode, "mclk", &mclk_id);
+		if (ret) {
+			mclk = NULL;
+		} else {
+			switch (mclk_id) {
+			case PISTACHIO_MCLK_I2S:
+				mclk = &pbc->i2s_mclk;
+				fs_info = i2s_fs_info;
+				break;
+			case PISTACHIO_MCLK_DAC:
+				mclk = &pbc->dac_mclk;
+				fs_info = dac_fs_info;
+				break;
+			default:
+				dev_err(dev, "Invalid mclk id: %u (%s)\n",
+					mclk_id, subnode->name);
+				ret = -EINVAL;
+				goto err_subnode;
+			}
+			ret = pistachio_card_parse_i2s_mclk(pbc, subnode, mclk,
+							    fs_info);
+			if (ret)
+				goto err_subnode;
+		}
+
+		codec = of_parse_phandle(subnode, "sound-dai", 0);
+		if (!codec)
+			continue;
+
+		ret = pistachio_card_parse_i2s_codec(np, subnode, i, pbc,
+						     codec, codec_info, mclk);
+		if (ret)
+			goto err_subnode;
+
+		i++;
+	}
+
+	return 0;
+
+err_subnode:
+	of_node_put(subnode);
+
+	return ret;
+}
+
+static int pistachio_card_mclk_copy(struct pistachio_mclk *mclk,
+	struct pistachio_i2s_mclk *mclk_i2s, struct pistachio_card *pbc,
+	struct pistachio_i2s_mclk_info *mclk_info)
+{
+	unsigned int size;
+
+	mclk_i2s->mclk = mclk;
+	mclk_i2s->num_fs_rates = mclk_info->num_fs_rates;
+
+	size = sizeof(*mclk_i2s->fs_rates) * mclk_i2s->num_fs_rates;
+	mclk_i2s->fs_rates = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+	if (!mclk_i2s->fs_rates)
+		return -ENOMEM;
+
+	memcpy(mclk_i2s->fs_rates, mclk_info->fs_rates, size);
+
+	mclk_i2s->min_rate = mclk_info->min_rate;
+	mclk_i2s->max_rate = mclk_info->max_rate;
+
+	return 0;
+}
+
+static int pistachio_card_parse_i2s_common(struct device_node *node,
+		struct pistachio_card *pbc, struct pistachio_i2s *i2s,
+		struct snd_soc_dai_link *link,
+		struct pistachio_i2s_codec_info *codec_info,
+		struct pistachio_i2s_mclk_info *i2s_mclk_info,
+		struct pistachio_i2s_mclk_info *dac_mclk_info)
+{
+	int ret, i;
+	unsigned int initial_codecs = codec_info->total_codecs, size;
+	struct pistachio_i2s_codec_info_s *codecs;
+	struct pistachio_i2s_mclk *mclk;
+
+	codecs = &codec_info->codecs[initial_codecs];
+
+	ret = pistachio_card_parse_i2s_codecs(node, pbc, codec_info,
+					      i2s_mclk_info, dac_mclk_info);
+	i2s->num_codecs = codec_info->total_codecs - initial_codecs;
+	if (ret)
+		goto err_codec_info;
+
+	mclk = &i2s->mclk_a;
+
+	if (i2s_mclk_info->num_fs_rates) {
+		ret = pistachio_card_mclk_copy(&pbc->i2s_mclk, mclk,
+					       pbc, i2s_mclk_info);
+		if (ret)
+			goto err_codec_info;
+
+		mclk = &i2s->mclk_b;
+	}
+
+	if (dac_mclk_info->num_fs_rates) {
+		ret = pistachio_card_mclk_copy(&pbc->dac_mclk, mclk,
+					       pbc, dac_mclk_info);
+		if (ret)
+			goto err_codec_info;
+	}
+
+	/* Use the dummy codec if there are no codec drivers in this link */
+	if (!i2s->num_codecs) {
+		link->codec_dai_name = "snd-soc-dummy-dai";
+		link->codec_name = "snd-soc-dummy";
+		return 0;
+	}
+
+	size = sizeof(*i2s->codecs) * i2s->num_codecs;
+	i2s->codecs = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+	if (!i2s->codecs) {
+		ret = -ENOMEM;
+		goto err_codec_info;
+	}
+
+	for (i = 0; i < i2s->num_codecs; i++) {
+		i2s->codecs[i].mclk = codecs[i].mclk;
+		i2s->codecs[i].mclk_index = codecs[i].mclk_index;
+	}
+
+	size = sizeof(*i2s->components) * i2s->num_codecs;
+	i2s->components = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+	if (!i2s->components) {
+		ret = -ENOMEM;
+		goto err_codec_info;
+	}
+
+	for (i = 0; i < i2s->num_codecs; i++) {
+		i2s->components[i].dai_name = codecs[i].dai_name;
+		i2s->components[i].of_node = codecs[i].np;
+	}
+
+	link->codecs = i2s->components;
+	link->num_codecs = i2s->num_codecs;
+
+	return 0;
+
+err_codec_info:
+	for (i = 0; i < i2s->num_codecs; i++)
+		of_node_put(codecs[i].np);
+
+	return ret;
+}
+
+static void pistachio_card_add_i2s_clk_route(struct pistachio_card *pbc,
+					     struct pistachio_i2s_mclk *mclk,
+					     char *cpu_dai_wname)
+{
+	struct snd_soc_dapm_route *route;
+
+	/*
+	 * Add a route connecting the clock supply widget to the i2s
+	 * Playback/Capture widget if the mclk is used in this path
+	 */
+	if (!mclk->mclk)
+		return;
+
+	route = &pbc->routes[pbc->card.num_dapm_routes++];
+	route->source = mclk->mclk->name;
+	route->sink = cpu_dai_wname;
+}
+
+static void pistachio_card_add_i2s_routes(struct pistachio_card *pbc,
+					  struct pistachio_i2s *i2s)
+{
+	char *cpu_dai_wname;
+	struct snd_soc_dapm_route *route;
+
+	route = &pbc->routes[pbc->card.num_dapm_routes++];
+
+	/*
+	 * dapm requires a full path (source to sink) for the clock supply
+	 * widgets to turn on/off as expected. Create routes linking the
+	 * i2s Playback/Capture widgets to Inputs/Outputs as required to
+	 * create these paths
+	 */
+	if (i2s == &pbc->i2s_out->i2s) {
+		cpu_dai_wname = PISTACHIO_I2S_OUT_PREFIX " Playback";
+		route->source = cpu_dai_wname;
+		route->sink = PISTACHIO_I2S_OUTPUT_NAME;
+	} else {
+		cpu_dai_wname = PISTACHIO_I2S_IN_PREFIX " Capture";
+		route->source = PISTACHIO_I2S_INPUT_NAME;
+		route->sink = cpu_dai_wname;
+	}
+
+	pistachio_card_add_i2s_clk_route(pbc, &i2s->mclk_a, cpu_dai_wname);
+
+	pistachio_card_add_i2s_clk_route(pbc, &i2s->mclk_b, cpu_dai_wname);
+}
+
+static int pistachio_card_parse_i2s_out(struct device_node *i2s_out_np,
+		struct pistachio_card *pbc, struct snd_soc_dai_link *link,
+		struct pistachio_i2s_codec_info *codec_info)
+{
+	struct device *dev = pbc->card.dev;
+	struct device_node *np;
+	unsigned int fmt;
+	int ret;
+	struct pistachio_i2s_mclk_info i2s_mclk_info;
+	struct pistachio_i2s_mclk_info dac_mclk_info;
+
+	pbc->i2s_out = devm_kzalloc(dev, sizeof(*pbc->i2s_out), GFP_KERNEL);
+	if (!pbc->i2s_out)
+		return -ENOMEM;
+
+	link->name = link->stream_name = "pistachio-i2s-out";
+
+	np = of_parse_phandle(i2s_out_np, "cpu-dai", 0);
+	if (!np) {
+		dev_err(dev, "Failed to parse cpu-dai (%s)", i2s_out_np->name);
+		return -EINVAL;
+	}
+
+	link->cpu_of_node = np;
+	link->platform_of_node = np;
+
+	fmt = snd_soc_of_parse_daifmt(i2s_out_np, NULL, NULL, NULL);
+	fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+	fmt |= SND_SOC_DAIFMT_CBS_CFS;
+	link->dai_fmt = fmt;
+
+	/*
+	 * Internal i2s out controller uses i2s_mclk and
+	 * accepts 256fs,384fs
+	 */
+	i2s_mclk_info.fs_rates[0] = 256;
+	i2s_mclk_info.fs_rates[1] = 384;
+	i2s_mclk_info.num_fs_rates = 2;
+	i2s_mclk_info.min_rate = 0;
+	i2s_mclk_info.max_rate = PISTACHIO_I2S_MCLK_MAX_FREQ;
+	dac_mclk_info.num_fs_rates = 0;
+	dac_mclk_info.min_rate = 0;
+	dac_mclk_info.max_rate = PISTACHIO_DAC_MCLK_MAX_FREQ;
+
+	codec_info->bitclock_master_idx = 0;
+	codec_info->frame_master_idx = 0;
+
+	ret = pistachio_card_parse_i2s_common(i2s_out_np, pbc,
+			&pbc->i2s_out->i2s, link, codec_info,
+			&i2s_mclk_info, &dac_mclk_info);
+	if (ret)
+		return ret;
+
+	pistachio_card_add_i2s_routes(pbc, &pbc->i2s_out->i2s);
+
+	link->init = pistachio_card_i2s_out_link_init;
+	link->ops = &pistachio_card_i2s_out_ops;
+
+	return 0;
+}
+
+static int pistachio_card_parse_i2s_in(struct device_node *i2s_in_np,
+		struct pistachio_card *pbc, struct snd_soc_dai_link *link,
+		bool i2s_loopback, struct pistachio_i2s_codec_info *codec_info)
+{
+	int ret;
+	struct device *dev = pbc->card.dev;
+	unsigned int fmt;
+	struct device_node *np;
+	struct pistachio_i2s_mclk_info i2s_mclk_info;
+	struct pistachio_i2s_mclk_info dac_mclk_info;
+
+	pbc->i2s_in = devm_kzalloc(dev, sizeof(*pbc->i2s_in), GFP_KERNEL);
+	if (!pbc->i2s_in)
+		return -ENOMEM;
+
+	link->name = link->stream_name = "pistachio-i2s-in";
+
+	np = of_parse_phandle(i2s_in_np, "cpu-dai", 0);
+	if (!np) {
+		dev_err(dev, "Failed to parse cpu-dai (%s)", i2s_in_np->name);
+		return -EINVAL;
+	}
+
+	link->cpu_of_node = np;
+	link->platform_of_node = np;
+
+	fmt = snd_soc_of_parse_daifmt(i2s_in_np, NULL, NULL, NULL);
+	fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+	pbc->i2s_in->fmt = fmt;
+
+	i2s_mclk_info.num_fs_rates = 0;
+	i2s_mclk_info.min_rate = 0;
+	i2s_mclk_info.max_rate = PISTACHIO_I2S_MCLK_MAX_FREQ;
+	dac_mclk_info.num_fs_rates = 0;
+	dac_mclk_info.min_rate = 0;
+	dac_mclk_info.max_rate = PISTACHIO_DAC_MCLK_MAX_FREQ;
+
+	codec_info->bitclock_master_idx = -1;
+	codec_info->frame_master_idx = -1;
+
+	ret = pistachio_card_parse_i2s_common(i2s_in_np, pbc,
+			&pbc->i2s_in->i2s, link, codec_info,
+			&i2s_mclk_info, &dac_mclk_info);
+	if (ret)
+		return ret;
+
+	if (i2s_loopback) {
+		pbc->i2s_in->frame_master = PISTACHIO_CLOCK_MASTER_LOOPBACK;
+		pbc->i2s_in->bitclock_master = PISTACHIO_CLOCK_MASTER_LOOPBACK;
+	} else if ((codec_info->bitclock_master_idx == -1) ||
+		   (codec_info->frame_master_idx == -1)) {
+		pbc->i2s_in->frame_master = PISTACHIO_CLOCK_MASTER_EXT;
+		pbc->i2s_in->bitclock_master = PISTACHIO_CLOCK_MASTER_EXT;
+	} else {
+		pbc->i2s_in->frame_master = codec_info->frame_master_idx;
+		pbc->i2s_in->bitclock_master = codec_info->bitclock_master_idx;
+	}
+
+	pistachio_card_add_i2s_routes(pbc, &pbc->i2s_in->i2s);
+
+	link->init = pistachio_card_i2s_in_link_init;
+
+	/*
+	 * If no mclks are used by i2s in, there is nothing for
+	 * the ops callbacks to do, so leave this as NULL
+	 */
+	if (pbc->i2s_in->i2s.mclk_a.mclk)
+		link->ops = &pistachio_card_i2s_in_ops;
+
+	return 0;
+}
+
+static int pistachio_card_prefixes(struct pistachio_card *pbc,
+	struct pistachio_i2s_codec_info *codec_info,
+	struct snd_soc_dai_link *i2s_out, struct snd_soc_dai_link *i2s_in,
+	struct snd_soc_dai_link *parallel_out)
+{
+	int i, j, n;
+	unsigned int size;
+	struct pistachio_i2s_codec_info_s *codecs;
+	struct snd_soc_codec_conf *conf, *c;
+
+	n = codec_info->unique_codecs;
+
+	if (parallel_out)
+		n++;
+
+	if (i2s_out)
+		n++;
+
+	if (i2s_in)
+		n++;
+
+	codecs = codec_info->codecs;
+
+	size = sizeof(*pbc->card.codec_conf) * n;
+	pbc->card.codec_conf = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+	if (!pbc->card.codec_conf)
+		return -ENOMEM;
+
+	conf = pbc->card.codec_conf;
+
+	/* Create prefixes for unique codecs only */
+	for (i = 0; i < codec_info->total_codecs; i++) {
+		for (j = 0; j < i; j++)
+			if (codecs[j].np == codecs[i].np)
+				break;
+		if (j == i) {
+			conf->of_node = codecs[i].np;
+			conf->name_prefix = codecs[i].prefix;
+			conf++;
+		}
+	}
+
+	if (i2s_out) {
+		conf->of_node = i2s_out->cpu_of_node;
+		conf->name_prefix = PISTACHIO_I2S_OUT_PREFIX;
+		conf++;
+	}
+
+	if (i2s_in) {
+		conf->of_node = i2s_in->cpu_of_node;
+		conf->name_prefix = PISTACHIO_I2S_IN_PREFIX;
+		conf++;
+	}
+
+	if (parallel_out) {
+		conf->of_node = parallel_out->codecs[0].of_node;
+		conf->name_prefix = PISTACHIO_INTERNAL_DAC_PREFIX;
+		conf++;
+	}
+
+	pbc->card.num_configs = n;
+
+	/* Check for prefix clashes */
+	for (i = 0; i < n; i++) {
+		conf = &pbc->card.codec_conf[i];
+		for (j = i + 1; j < n; j++) {
+			c = &pbc->card.codec_conf[j];
+			if (!strcasecmp(conf->name_prefix, c->name_prefix)) {
+				dev_err(pbc->card.dev, "Prefix clash: %s\n",
+					conf->name_prefix);
+				return -EINVAL;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int pistachio_card_parse_of(struct device_node *node,
+				   struct pistachio_card *pbc)
+{
+	int ret = 0;
+	unsigned int size, gpio;
+	struct device_node *spdif_out_np, *spdif_in_np, *parallel_out_np;
+	struct device_node *i2s_out_np, *i2s_in_np;
+	struct snd_soc_dai_link *link, *prl_out, *i2s_out, *i2s_in;
+	enum of_gpio_flags flags;
+	struct pistachio_i2s_codec_info codec_info;
+	bool i2s_loopback;
+	struct device *dev = pbc->card.dev;
+
+	pbc->periph_regs = syscon_regmap_lookup_by_phandle(node, "img,cr-periph");
+	if (IS_ERR(pbc->periph_regs)) {
+		dev_err(dev, "syscon_regmap_lookup_by_phandle failed: %ld\n",
+			PTR_ERR(pbc->periph_regs));
+		return PTR_ERR(pbc->periph_regs);
+	}
+
+	if (of_property_read_bool(node, "img,widgets")) {
+		ret = snd_soc_of_parse_audio_simple_widgets(&pbc->card, "img,widgets");
+		if (ret) {
+			dev_err(dev, "img,widgets parse failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (of_property_read_bool(node, "img,routing")) {
+		ret = snd_soc_of_parse_audio_routing(&pbc->card, "img,routing");
+		if (ret) {
+			dev_err(dev, "img,routing parse failed: %d\n", ret);
+			return ret;
+		}
+	}
+
+	spdif_out_np = of_get_child_by_name(node, "spdif-out");
+	if (spdif_out_np)
+		pbc->card.num_links++;
+
+	spdif_in_np = of_get_child_by_name(node, "spdif-in");
+	if (spdif_in_np)
+		pbc->card.num_links++;
+
+	parallel_out_np = of_get_child_by_name(node, "parallel-out");
+	if (parallel_out_np)
+		pbc->card.num_links++;
+
+	i2s_out_np = of_get_child_by_name(node, "i2s-out");
+	if (i2s_out_np)
+		pbc->card.num_links++;
+
+	i2s_in_np = of_get_child_by_name(node, "i2s-in");
+	if (i2s_in_np)
+		pbc->card.num_links++;
+
+	i2s_loopback = of_property_read_bool(node, "img,i2s-clk-loopback");
+	if (i2s_loopback && (!i2s_out_np || !i2s_in_np)) {
+		dev_err(dev, "img,i2s-clk-loopback specified when i2s-out/i2s-in are not present\n");
+		ret = -EINVAL;
+		goto end;
+	}
+
+	if (!pbc->card.num_links) {
+		dev_err(dev, "No dai links on card\n");
+		ret = -EINVAL;
+		goto end;
+	}
+
+	size = sizeof(*pbc->card.dai_link) * pbc->card.num_links;
+	pbc->card.dai_link = devm_kzalloc(pbc->card.dev, size, GFP_KERNEL);
+	if (!pbc->card.dai_link) {
+		ret = -ENOMEM;
+		goto end;
+	}
+
+	codec_info.total_codecs = 0;
+	codec_info.unique_codecs = 0;
+
+	link = pbc->card.dai_link;
+
+	if (spdif_out_np) {
+		ret = pistachio_card_parse_spdif_out(spdif_out_np, pbc, link);
+		if (ret)
+			goto end;
+		link++;
+	}
+
+	if (spdif_in_np) {
+		ret = pistachio_card_parse_spdif_in(spdif_in_np, pbc, link);
+		if (ret)
+			goto end;
+		link++;
+	}
+
+	if (parallel_out_np) {
+		ret = pistachio_card_parse_parallel_out(parallel_out_np, pbc,
+							link);
+		if (ret)
+			goto end;
+
+		prl_out = link++;
+	} else {
+		prl_out = NULL;
+	}
+
+	if (i2s_out_np) {
+		ret = pistachio_card_parse_i2s_out(i2s_out_np, pbc, link,
+						   &codec_info);
+		if (ret)
+			goto end;
+
+		i2s_out = link++;
+	} else {
+		i2s_out = NULL;
+	}
+
+	if (i2s_in_np) {
+		ret = pistachio_card_parse_i2s_in(i2s_in_np, pbc, link,
+						  i2s_loopback, &codec_info);
+		if (ret)
+			goto end;
+
+		i2s_in = link;
+	} else {
+		i2s_in = NULL;
+	}
+
+	ret = pistachio_card_prefixes(pbc, &codec_info, i2s_out,
+				      i2s_in, prl_out);
+	if (ret)
+		goto end;
+
+	gpio = of_get_named_gpio_flags(node, "img,hp-det-gpio", 0, &flags);
+	pbc->hp_jack_gpio.gpio = gpio;
+	pbc->hp_jack_gpio.invert = !!(flags & OF_GPIO_ACTIVE_LOW);
+	if (pbc->hp_jack_gpio.gpio == -EPROBE_DEFER) {
+		ret = -EPROBE_DEFER;
+		goto end;
+	}
+
+	gpio = of_get_named_gpio_flags(node, "img,mute-gpio", 0, &flags);
+	pbc->mute_gpio = gpio;
+	pbc->mute_gpio_inverted = !!(flags & OF_GPIO_ACTIVE_LOW);
+	if (pbc->mute_gpio == -EPROBE_DEFER)
+		ret = -EPROBE_DEFER;
+
+end:
+	if (spdif_out_np)
+		of_node_put(spdif_out_np);
+	if (spdif_in_np)
+		of_node_put(spdif_in_np);
+	if (parallel_out_np)
+		of_node_put(parallel_out_np);
+	if (i2s_out_np)
+		of_node_put(i2s_out_np);
+	if (i2s_in_np)
+		of_node_put(i2s_in_np);
+
+	return ret;
+}
+
+static void pistachio_card_unref(struct pistachio_card *pbc)
+{
+	int i, j;
+	struct snd_soc_dai_link *link;
+
+	link = pbc->card.dai_link;
+	if (!link)
+		return;
+
+	for (i = 0; i < pbc->card.num_links; i++, link++) {
+		if (link->cpu_of_node)
+			of_node_put(link->cpu_of_node);
+		for (j = 0; j < link->num_codecs; j++)
+			of_node_put(link->codecs[j].of_node);
+	}
+}
+
+static int pistachio_card_init_clk(struct device *dev, char *name,
+				   struct clk **pclk, unsigned int rate)
+{
+	struct clk *clk;
+	int ret;
+
+	clk = devm_clk_get(dev, name);
+	if (IS_ERR(clk)) {
+		ret = PTR_ERR(clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "devm_clk_get failed for %s: %d",
+				name, ret);
+		return ret;
+	}
+
+	ret = clk_set_rate(clk, rate);
+	if (ret) {
+		dev_err(dev, "clk_set_rate(%s, %u) failed: %d",
+			name, rate, ret);
+		return ret;
+	}
+
+	*pclk = clk;
+
+	return 0;
+}
+
+static int pistachio_card_get_mute(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+	int ret;
+
+	ret = gpio_get_value_cansleep(pbc->mute_gpio);
+	if (ret < 0)
+		return ret;
+
+	if (pbc->mute_gpio_inverted)
+		ucontrol->value.integer.value[0] = !ret;
+	else
+		ucontrol->value.integer.value[0] = !!ret;
+
+	return 0;
+}
+
+static int pistachio_card_set_mute(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+	int val;
+
+	if (pbc->mute_gpio_inverted)
+		val = !ucontrol->value.integer.value[0];
+	else
+		val = ucontrol->value.integer.value[0];
+
+	gpio_set_value_cansleep(pbc->mute_gpio, val);
+
+	return 0;
+}
+
+static int pistachio_card_info_sample_rates(struct snd_kcontrol *kcontrol,
+					    struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 192000;
+
+	return 0;
+}
+
+static int pistachio_card_set_sample_rates_mclk(struct pistachio_card *pbc,
+		    struct pistachio_mclk *mclk, unsigned int i2s_out_rate,
+		    unsigned int i2s_in_rate)
+{
+	struct pistachio_i2s_mclk *mclk_a, *mclk_b;
+	unsigned int rate_a, rate_b;
+
+	mclk_a = NULL;
+	mclk_b = NULL;
+	rate_a = i2s_out_rate;
+	rate_b = i2s_in_rate;
+
+	if (i2s_out_rate) {
+		if (pbc->i2s_out->i2s.mclk_a.mclk == mclk)
+			mclk_a = &pbc->i2s_out->i2s.mclk_a;
+		else if (pbc->i2s_out->i2s.mclk_b.mclk == mclk)
+			mclk_a = &pbc->i2s_out->i2s.mclk_b;
+	}
+	if (i2s_in_rate) {
+		if (pbc->i2s_in->i2s.mclk_a.mclk == mclk)
+			mclk_b = &pbc->i2s_in->i2s.mclk_a;
+		else if (pbc->i2s_in->i2s.mclk_b.mclk == mclk)
+			mclk_b = &pbc->i2s_in->i2s.mclk_b;
+	}
+
+	if (!mclk_a) {
+		mclk_a = mclk_b;
+		rate_a = rate_b;
+		mclk_b = NULL;
+	}
+
+	if (mclk_a)
+		return pistachio_card_update_mclk(pbc, mclk_a, mclk_b,
+						  rate_a, rate_b);
+
+	return 0;
+}
+
+static int pistachio_card_set_sample_rates(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+	int ret;
+	unsigned int pll_rate, i2s_out_rate, i2s_in_rate;
+	struct device *dev = pbc->card.dev;
+
+	i2s_out_rate = 0;
+	i2s_in_rate = 0;
+
+	if (pbc->i2s_out)
+		i2s_out_rate = ucontrol->value.integer.value[0];
+
+	if (pbc->i2s_in && pbc->i2s_in->i2s.mclk_a.mclk)
+		i2s_in_rate = ucontrol->value.integer.value[1];
+
+	if (!i2s_out_rate && !i2s_in_rate)
+		return 0;
+
+	pll_rate = 0;
+
+	if (i2s_out_rate) {
+		ret = pistachio_card_get_pll_rate(i2s_out_rate, dev);
+		if (ret < 0)
+			return ret;
+
+		pll_rate = ret;
+	}
+
+	if (i2s_in_rate) {
+		ret = pistachio_card_get_pll_rate(i2s_in_rate, dev);
+		if (ret < 0)
+			return ret;
+
+		if (pll_rate && (ret != pll_rate)) {
+			dev_err(dev, "Conflicting pll rate requirements\n");
+			return -EINVAL;
+		}
+
+		pll_rate = ret;
+	}
+
+	mutex_lock(&pbc->rate_mutex);
+
+	if (pbc->audio_pll_rate != pll_rate) {
+		ret = pistachio_card_set_pll_rate(pbc, pll_rate);
+		if (ret) {
+			mutex_unlock(&pbc->rate_mutex);
+			return ret;
+		}
+	}
+
+	ret = pistachio_card_set_sample_rates_mclk(pbc, &pbc->i2s_mclk,
+						   i2s_out_rate, i2s_in_rate);
+	if (ret) {
+		mutex_unlock(&pbc->rate_mutex);
+		return ret;
+	}
+
+	ret = pistachio_card_set_sample_rates_mclk(pbc, &pbc->dac_mclk,
+						   i2s_out_rate, i2s_in_rate);
+
+	mutex_unlock(&pbc->rate_mutex);
+
+	return ret;
+}
+
+static struct snd_kcontrol_new pistachio_controls[] = {
+	{
+		.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+		.iface = SNDRV_CTL_ELEM_IFACE_CARD,
+		.name = "I2S Rates",
+		.info = pistachio_card_info_sample_rates,
+		.put = pistachio_card_set_sample_rates
+	},
+};
+
+static int pistachio_card_i2s_clk_cb(struct notifier_block *nb,
+				     unsigned long event, void *data)
+{
+	struct clk_notifier_data *ndata = data;
+	struct pistachio_card *pbc;
+
+	pbc = container_of(nb, struct pistachio_card, i2s_clk_notifier);
+
+	switch (event) {
+	case PRE_RATE_CHANGE:
+		/* Allow changes made by the card driver only */
+		if (ndata->new_rate == pbc->i2s_mclk.cur_rate)
+			return NOTIFY_OK;
+		else
+			return NOTIFY_STOP;
+	case POST_RATE_CHANGE:
+	case ABORT_RATE_CHANGE:
+		return NOTIFY_OK;
+	default:
+		return NOTIFY_DONE;
+	}
+}
+
+#ifdef DEBUG
+
+static void pistachio_card_info_fmt(struct pistachio_card *pbc,
+				    unsigned int fmt)
+{
+	struct device *dev = pbc->card.dev;
+	char *text_a, *text_b;
+
+	if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S)
+		text_a = "I2S";
+	else
+		text_a = "Left Justified";
+	dev_dbg(dev, "    Format: %s\n", text_a);
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		text_a = "No";
+		text_b = "No";
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		text_a = "Yes";
+		text_b = "No";
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		text_a = "No";
+		text_b = "Yes";
+		break;
+	default:
+		text_a = "Yes";
+		text_b = "Yes";
+		break;
+	}
+	dev_dbg(dev, "    Frame Clock Inverted: %s\n", text_a);
+	dev_dbg(dev, "    Bit Clock Inverted: %s\n", text_b);
+
+	if ((fmt & SND_SOC_DAIFMT_CLOCK_MASK) == SND_SOC_DAIFMT_CONT)
+		text_a = "Yes";
+	else
+		text_a = "No";
+	dev_dbg(dev, "    Continuous Clock: %s\n", text_a);
+}
+
+static void pistachio_card_info_mclk(struct pistachio_card *pbc,
+				     struct pistachio_i2s_mclk *mclk)
+{
+	struct device *dev = pbc->card.dev;
+	int i;
+
+	dev_dbg(dev, "        Min FS Freq: %u\n", mclk->min_rate);
+	dev_dbg(dev, "        Max FS Freq: %u\n", mclk->max_rate);
+	dev_dbg(dev, "        FS Rates:\n");
+
+	for (i = 0; i < mclk->num_fs_rates; i++)
+		dev_dbg(dev, "            %u\n", mclk->fs_rates[i]);
+}
+
+static void pistachio_card_info_mclks(struct pistachio_card *pbc,
+				      struct pistachio_i2s *i2s)
+{
+	struct pistachio_i2s_mclk *i2s_mclk;
+	struct pistachio_i2s_mclk *dac_mclk;
+	struct device *dev = pbc->card.dev;
+
+	if (i2s->mclk_a.mclk == &pbc->i2s_mclk)
+		i2s_mclk = &i2s->mclk_a;
+	else if (pbc->i2s_in->i2s.mclk_b.mclk == &pbc->i2s_mclk)
+		i2s_mclk = &i2s->mclk_b;
+	else
+		i2s_mclk = NULL;
+
+	if (i2s_mclk) {
+		dev_dbg(dev, "    I2S MCLK\n");
+		pistachio_card_info_mclk(pbc, i2s_mclk);
+	} else {
+		dev_dbg(dev, "    I2S MCLK NOT USED\n");
+	}
+
+	dev_dbg(dev, "\n");
+
+	if (i2s->mclk_a.mclk == &pbc->dac_mclk)
+		dac_mclk = &i2s->mclk_a;
+	else if (i2s->mclk_b.mclk == &pbc->dac_mclk)
+		dac_mclk = &i2s->mclk_b;
+	else
+		dac_mclk = NULL;
+
+	if (dac_mclk) {
+		dev_dbg(dev, "    DAC MCLK\n");
+		pistachio_card_info_mclk(pbc, dac_mclk);
+	} else {
+		dev_dbg(dev, "    DAC MCLK NOT USED\n");
+	}
+}
+
+static void pistachio_card_info_i2s_out(struct pistachio_card *pbc,
+					struct snd_soc_dai_link *link)
+{
+	int i, j;
+	struct snd_soc_dai_link_component *components;
+	struct snd_soc_codec_conf *confs;
+	struct device *dev = pbc->card.dev;
+	char *text;
+
+	components = pbc->i2s_out->i2s.components;
+	confs = pbc->card.codec_conf;
+
+	dev_dbg(dev, "I2S OUT\n");
+	dev_dbg(dev, "\n");
+	if (pbc->i2s_in && (pbc->i2s_in->frame_master ==
+			    PISTACHIO_CLOCK_MASTER_LOOPBACK))
+		text = "(Dual Frame + Bit Clock Master)";
+	else
+		text = "(Frame + Bit Clock Master)";
+	dev_dbg(dev, "    CPU DAI\n");
+	dev_dbg(dev, "        i2s-out (%s) %s\n",
+		link->cpu_of_node->name, text);
+	dev_dbg(dev, "\n");
+	dev_dbg(dev, "    CODECS\n");
+
+	for (i = 0; i < pbc->i2s_out->i2s.num_codecs; i++) {
+		for (j = 0; j < pbc->card.num_configs; j++)
+			if (confs[j].of_node == components[i].of_node)
+				break;
+
+		dev_dbg(dev, "        %s (%s) (%s)\n", confs[j].name_prefix,
+			confs[j].of_node->name, components[i].dai_name);
+	}
+	dev_dbg(dev, "\n");
+
+	pistachio_card_info_mclks(pbc, &pbc->i2s_out->i2s);
+
+	dev_dbg(dev, "\n");
+
+	pistachio_card_info_fmt(pbc, link->dai_fmt);
+
+	dev_dbg(dev, "\n");
+}
+
+static void pistachio_card_info_i2s_in(struct pistachio_card *pbc,
+				       struct snd_soc_dai_link *link)
+{
+	int i, j;
+	struct snd_soc_dai_link_component *components;
+	struct snd_soc_codec_conf *confs;
+	char *text;
+	struct device *dev = pbc->card.dev;
+
+	components = pbc->i2s_in->i2s.components;
+	confs = pbc->card.codec_conf;
+
+	dev_dbg(dev, "I2S IN\n");
+	dev_dbg(dev, "\n");
+	dev_dbg(dev, "    CPU DAI\n");
+	dev_dbg(dev, "        i2s-in (%s)\n", link->cpu_of_node->name);
+	dev_dbg(dev, "\n");
+	dev_dbg(dev, "    CODECS\n");
+
+	for (i = 0; i < pbc->i2s_in->i2s.num_codecs; i++) {
+		for (j = 0; j < pbc->card.num_configs; j++)
+			if (confs[j].of_node == components[i].of_node)
+				break;
+
+		if (i == pbc->i2s_in->frame_master)
+			if (i == pbc->i2s_in->bitclock_master)
+				text = "(Frame + Bit Clock Master)";
+			else
+				text = "(Frame Master)";
+		else
+			if (i == pbc->i2s_in->bitclock_master)
+				text = "(Bitclock Master)";
+			else
+				text = "";
+
+		dev_dbg(dev, "        %s (%s) (%s) %s\n", confs[j].name_prefix,
+			confs[j].of_node->name, components[i].dai_name, text);
+	}
+	dev_dbg(dev, "\n");
+
+	pistachio_card_info_mclks(pbc, &pbc->i2s_in->i2s);
+
+	dev_dbg(dev, "\n");
+
+	pistachio_card_info_fmt(pbc, pbc->i2s_in->fmt);
+
+	dev_dbg(dev, "\n");
+}
+
+static void pistachio_card_info(struct pistachio_card *pbc)
+{
+	struct device *dev = pbc->card.dev;
+	struct snd_soc_codec_conf *conf;
+	struct snd_soc_dai_link *link;
+	char *text;
+
+	link = pbc->card.dai_link;
+
+	dev_dbg(dev, "\n");
+	dev_dbg(dev, "####################################################\n");
+	dev_dbg(dev, "\n");
+	dev_dbg(dev, "Pistachio Audio Card\n");
+	dev_dbg(dev, "\n");
+
+	if (pbc->spdif_out) {
+		dev_dbg(dev, "SPDIF OUT\n");
+		dev_dbg(dev, "\n");
+		dev_dbg(dev, "    CPU DAI\n");
+		dev_dbg(dev, "        spdif-out (%s)\n",
+			link->cpu_of_node->name);
+		dev_dbg(dev, "\n");
+		link++;
+	}
+	if (pbc->spdif_in) {
+		dev_dbg(dev, "SPDIF IN\n");
+		dev_dbg(dev, "\n");
+		dev_dbg(dev, "    CPU DAI\n");
+		dev_dbg(dev, "        spdif-in (%s)\n",
+			link->cpu_of_node->name);
+		dev_dbg(dev, "\n");
+		link++;
+	}
+	if (pbc->parallel_out) {
+		dev_dbg(dev, "PARALLEL OUT\n");
+		dev_dbg(dev, "\n");
+		dev_dbg(dev, "    CPU DAI\n");
+		dev_dbg(dev, "        parallel-out (%s)\n",
+			link->cpu_of_node->name);
+		dev_dbg(dev, "\n");
+		dev_dbg(dev, "    CODEC\n");
+		conf = &pbc->card.codec_conf[pbc->card.num_configs - 1];
+		dev_dbg(dev, "        %s (%s) (%s)\n", conf->name_prefix,
+			conf->of_node->name,
+			pbc->parallel_out->component.dai_name);
+		dev_dbg(dev, "\n");
+		link++;
+	}
+	if (pbc->i2s_out) {
+		pistachio_card_info_i2s_out(pbc, link);
+		link++;
+	}
+	if (pbc->i2s_in)
+		pistachio_card_info_i2s_in(pbc, link);
+
+	dev_dbg(dev, "I2S MCLK Max Freq: %u\n", pbc->i2s_mclk.max_rate);
+	dev_dbg(dev, "DAC MCLK Max Freq: %u\n", pbc->dac_mclk.max_rate);
+	dev_dbg(dev, "\n");
+
+	if (gpio_is_valid(pbc->mute_gpio)) {
+		if (pbc->mute_gpio_inverted)
+			text = "(Active Low)";
+		else
+			text = "(Active High)";
+		dev_dbg(dev, "Mute: GPIO %u %s\n", pbc->mute_gpio, text);
+	}
+	if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) {
+		if (pbc->hp_jack_gpio.invert)
+			text = "(Active Low)";
+		else
+			text = "(Active High)";
+		dev_dbg(dev, "Headphone-Detect: GPIO %u %s\n",
+				pbc->hp_jack_gpio.gpio, text);
+	}
+	dev_dbg(dev, "\n");
+	dev_dbg(dev, "####################################################\n");
+	dev_dbg(dev, "\n");
+}
+
+#endif
+
+static int pistachio_card_probe(struct platform_device *pdev)
+{
+	struct pistachio_card *pbc;
+	struct device_node *np = pdev->dev.of_node;
+	struct device *dev = &pdev->dev;
+	int ret;
+	unsigned long gpio_flags;
+	struct snd_kcontrol_new *control;
+
+	if (!np || !of_device_is_available(np))
+		return -EINVAL;
+
+	pbc = devm_kzalloc(dev, sizeof(*pbc), GFP_KERNEL);
+	if (!pbc)
+		return -ENOMEM;
+
+	snd_soc_card_set_drvdata(&pbc->card, pbc);
+
+	pbc->card.owner = THIS_MODULE;
+	pbc->card.dev = dev;
+	pbc->card.name = "pistachio-card";
+
+	pbc->i2s_mclk.name = PISTACHIO_I2S_MCLK_NAME;
+	pbc->i2s_mclk.max_rate = PISTACHIO_I2S_MCLK_MAX_FREQ;
+	pbc->dac_mclk.name = PISTACHIO_DAC_MCLK_NAME;
+	pbc->dac_mclk.max_rate = PISTACHIO_DAC_MCLK_MAX_FREQ;
+
+	mutex_init(&pbc->rate_mutex);
+
+	pbc->hp_jack_gpio.gpio = -ENOENT;
+	pbc->mute_gpio = -ENOENT;
+
+	pbc->card.dapm_widgets = pistachio_card_widgets;
+	pbc->card.num_dapm_widgets = ARRAY_SIZE(pistachio_card_widgets);
+
+	pbc->card.dapm_routes = pbc->routes;
+
+	ret = pistachio_card_parse_of(np, pbc);
+	if (ret)
+		goto err;
+
+	pbc->audio_pll_rate = PISTACHIO_PLL_RATE_B;
+	ret = pistachio_card_init_clk(dev, "audio_pll", &pbc->audio_pll,
+				      pbc->audio_pll_rate);
+	if (ret)
+		goto err;
+
+	pbc->i2s_mclk.cur_rate = PISTACHIO_MIN_MCLK_FREQ;
+	ret = pistachio_card_init_clk(dev, PISTACHIO_I2S_MCLK_NAME,
+				      &pbc->i2s_mclk.mclk,
+				      pbc->i2s_mclk.cur_rate);
+	if (ret)
+		goto err;
+
+	pbc->dac_mclk.cur_rate = PISTACHIO_MIN_MCLK_FREQ;
+	ret = pistachio_card_init_clk(dev, PISTACHIO_DAC_MCLK_NAME,
+				      &pbc->dac_mclk.mclk,
+				      pbc->dac_mclk.cur_rate);
+	if (ret)
+		goto err;
+
+	pbc->i2s_clk_notifier.notifier_call = pistachio_card_i2s_clk_cb;
+	ret = clk_notifier_register(pbc->i2s_mclk.mclk,
+				    &pbc->i2s_clk_notifier);
+	if (ret) {
+		dev_err(dev, "clk_notifier_register failed: %d", ret);
+		goto err;
+	}
+
+	ret = devm_snd_soc_register_card(dev, &pbc->card);
+	if (ret) {
+		dev_err(dev, "devm_snd_soc_register_card failed: %d", ret);
+		goto err_notifier;
+	}
+
+	ret = snd_soc_add_card_controls(&pbc->card, pistachio_controls,
+					ARRAY_SIZE(pistachio_controls));
+	if (ret) {
+		dev_err(dev, "snd_soc_add_card_controls failed: %d", ret);
+		goto err_notifier;
+	}
+
+	if (gpio_is_valid(pbc->hp_jack_gpio.gpio)) {
+		pbc->hp_jack_pin.pin = "Headphones";
+		pbc->hp_jack_pin.mask = SND_JACK_HEADPHONE;
+		pbc->hp_jack_gpio.name = "Headphone detection";
+		pbc->hp_jack_gpio.report = SND_JACK_HEADPHONE;
+		pbc->hp_jack_gpio.debounce_time = 150;
+		ret = snd_soc_card_jack_new(&pbc->card, "Headphones",
+					    SND_JACK_HEADPHONE, &pbc->hp_jack,
+					    &pbc->hp_jack_pin, 1);
+		if (ret) {
+			dev_err(dev, "snd_soc_card_jack_new failed: %d", ret);
+			goto err_notifier;
+		}
+		ret = snd_soc_jack_add_gpios(&pbc->hp_jack, 1,
+					     &pbc->hp_jack_gpio);
+		if (ret) {
+			dev_err(dev, "snd_soc_jack_add_gpios failed: %d", ret);
+			goto err_notifier;
+		}
+	}
+
+	if (gpio_is_valid(pbc->mute_gpio)) {
+		if (pbc->mute_gpio_inverted)
+			gpio_flags = GPIOF_OUT_INIT_HIGH;
+		else
+			gpio_flags = GPIOF_OUT_INIT_LOW;
+		ret = gpio_request_one(pbc->mute_gpio, gpio_flags, "Mute");
+		if (ret) {
+			dev_err(dev, "gpio_request_one failed: %d", ret);
+			goto err_jack;
+		}
+		control = devm_kzalloc(dev, sizeof(*control), GFP_KERNEL);
+		if (!control) {
+			ret = -ENOMEM;
+			goto err_mute;
+		}
+		control->access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+		control->iface = SNDRV_CTL_ELEM_IFACE_CARD;
+		control->name = "Mute Switch";
+		control->info = snd_ctl_boolean_mono_info;
+		control->get = pistachio_card_get_mute;
+		control->put = pistachio_card_set_mute;
+		ret = snd_soc_add_card_controls(&pbc->card, control, 1);
+		if (ret) {
+			dev_err(dev, "mute control add failed: %d", ret);
+			goto err_mute;
+		}
+	}
+
+#ifdef	DEBUG
+	pistachio_card_info(pbc);
+#endif
+
+	return 0;
+
+err_mute:
+	if (gpio_is_valid(pbc->mute_gpio))
+		gpio_free(pbc->mute_gpio);
+err_jack:
+	if (gpio_is_valid(pbc->hp_jack_gpio.gpio))
+		snd_soc_jack_free_gpios(&pbc->hp_jack, 1, &pbc->hp_jack_gpio);
+err_notifier:
+	clk_notifier_unregister(pbc->i2s_mclk.mclk, &pbc->i2s_clk_notifier);
+err:
+	pistachio_card_unref(pbc);
+
+	return ret;
+}
+
+static int pistachio_card_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	struct pistachio_card *pbc = snd_soc_card_get_drvdata(card);
+
+	if (gpio_is_valid(pbc->mute_gpio))
+		gpio_free(pbc->mute_gpio);
+	if (gpio_is_valid(pbc->hp_jack_gpio.gpio))
+		snd_soc_jack_free_gpios(&pbc->hp_jack, 1, &pbc->hp_jack_gpio);
+	clk_notifier_unregister(pbc->i2s_mclk.mclk, &pbc->i2s_clk_notifier);
+	pistachio_card_unref(pbc);
+
+	return 0;
+}
+
+static const struct of_device_id pistachio_card_of_match[] = {
+	{ .compatible = "img,pistachio-audio" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, pistachio_card_of_match);
+
+static struct platform_driver pistachio_card = {
+	.driver = {
+		.name = "pistachio-card",
+		.of_match_table = pistachio_card_of_match,
+	},
+	.probe = pistachio_card_probe,
+	.remove = pistachio_card_remove,
+};
+module_platform_driver(pistachio_card);
+
+MODULE_DESCRIPTION("Pistachio audio card driver");
+MODULE_AUTHOR("Damien Horsley <Damien.Horsley at imgtec.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.1.4



More information about the Alsa-devel mailing list