All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC 0/2] ASoC: img: Add Pistachio card support
@ 2016-01-25 19:12 Damien Horsley
  2016-01-25 19:12 ` [RFC 1/2] ASoC: img: Add binding document for Pistachio audio card Damien Horsley
  2016-01-25 19:12 ` [RFC 2/2] ASoC: img: Add driver " Damien Horsley
  0 siblings, 2 replies; 3+ messages in thread
From: Damien Horsley @ 2016-01-25 19:12 UTC (permalink / raw)
  To: alsa-devel
  Cc: Mark Rutland, devicetree, Pawel Moll, Ian Campbell, linux-kernel,
	Mark Brown, Takashi Iwai, Liam Girdwood, Rob Herring, Kumar Gala,
	Damien.Horsley

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

This patch set provides an initial implementation of a card driver suitable
for all boards that use the Pistachio SoC

The Pistachio SoC contains the following audio components:
Controllers - SPDIF out, SPDIF in, parallel out, I2S out, I2S in
Codecs - Internal DAC (connected to parallel out)
Clocks - Audio PLL, dividers, gates, codec system clocks

There are a number of boards that use the Pistachio SoC. The following
can vary from board to board:
- Which audio interfaces are usable on the board
- The I2S codecs used (including multicodec paths on some boards)
- The use of the two system clocks by the I2S codecs
- Other external audio components present on the board
- I2S clock loopback
- I2S formats
- Sample rates

For some boards, a single system clock is used by components on both the
I2S out and I2S in paths. This creates two dependacy issues that require
further review as follows:

- The information required to control the system clock rate(s)
in response to a sample rate change cannot be specified as a single
frequency, or as a single fs-ratio. Instead, the Pistachio card driver must
store sufficient information to derive acceptable system clock rate(s)
for any usable combination of {I2S out sample rate, I2S in sample rate}

In this revision, the information is specified as a per-codec list of fs-rates,
accompanied by min/max frequencies. This specification is sufficient for the
codecs used so far, but this likely needs to change to support a greater range
of codecs.

- In order for the Pistachio card driver to establish usable system clock
rate(s) for a given combination of {I2S out sample rate, I2S in sample rate},
the card driver must be made aware of both of the sample rate requirements in
advance of either of the streams starting, as system clock rates cannot
typically be changed when a codec is actively playing/recording.

For this revision of the Pistachio card driver, a control has been used
to specify these rates in advance. Calls to hw_params that require a system
clock to change rate when another stream is already using the clock fail
with -EBUSY.

Damien.Horsley (2):
  ASoC: img: Add binding document for Pistachio audio card
  ASoC: img: Add driver for Pistachio audio card

 .../bindings/sound/img,pistachio-audio.txt         |  308 +++
 include/dt-bindings/sound/pistachio-audio.h        |    7 +
 sound/soc/img/Kconfig                              |   10 +
 sound/soc/img/Makefile                             |    1 +
 sound/soc/img/pistachio.c                          | 2249 ++++++++++++++++++++
 5 files changed, 2575 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/img,pistachio-audio.txt
 create mode 100644 include/dt-bindings/sound/pistachio-audio.h
 create mode 100644 sound/soc/img/pistachio.c

-- 
2.1.4

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

* [RFC 1/2] ASoC: img: Add binding document for Pistachio audio card
  2016-01-25 19:12 [RFC 0/2] ASoC: img: Add Pistachio card support Damien Horsley
@ 2016-01-25 19:12 ` Damien Horsley
  2016-01-25 19:12 ` [RFC 2/2] ASoC: img: Add driver " Damien Horsley
  1 sibling, 0 replies; 3+ messages in thread
From: Damien Horsley @ 2016-01-25 19:12 UTC (permalink / raw)
  To: alsa-devel
  Cc: Mark Rutland, devicetree, Pawel Moll, Ian Campbell, linux-kernel,
	Mark Brown, Takashi Iwai, Liam Girdwood, Rob Herring, Kumar Gala,
	Damien.Horsley

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

Add a binding document for Pistachio audio card

Signed-off-by: Damien.Horsley <Damien.Horsley@imgtec.com>
---
 .../bindings/sound/img,pistachio-audio.txt         | 308 +++++++++++++++++++++
 1 file changed, 308 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/img,pistachio-audio.txt

diff --git a/Documentation/devicetree/bindings/sound/img,pistachio-audio.txt b/Documentation/devicetree/bindings/sound/img,pistachio-audio.txt
new file mode 100644
index 0000000..ab5bf7f
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/img,pistachio-audio.txt
@@ -0,0 +1,308 @@
+Imagination Technologies Pistachio Audio Card Driver
+
+Required properties:
+
+  - compatible : Compatible list, must contain "img,pistachio-audio"
+
+  - clocks : Contains an entry for each entry in clock-names
+
+  - clock-names : Includes the following entries:
+        "audio_pll"  The audio PLL
+        "i2s_mclk"   The i2s reference clock
+                     Also connected to i2s_out_0_mclk output
+        "dac_mclk"   Dac reference clock. Connected to i2s_dac_clk output
+
+  - img,cr-periph : phandle of the peripheral control syscon
+                    node which contains the i2s loopback control registers
+
+Optional properties:
+
+  - img,widgets : Please refer to widgets.txt
+
+  - img,routing : A list of the connections between audio components
+
+  - img,mute-gpio : phandle of the mute gpio
+
+  - img,hp-det-gpio : phandle of the headphone detect gpio
+
+  - img,i2s-clk-loopback : When present, the frame and bit clock signals for
+			   the i2s out and i2s in controllers will be connected
+			   internally
+
+Optional subnodes:
+
+  - spdif-out : Contains spdif out information
+
+  - spdif-in : Contains spdif in information
+
+  - parallel-out : Contains parallel out information
+
+  - i2s-out : Contains i2s out information
+
+  - i2s-in : Contains i2s in information
+
+Required spdif-out, spdif-in subnode properties:
+
+  - cpu-dai : phandle of spdif cpu dai
+
+Required parallel-out subnode properties:
+
+  - cpu-dai : phandle of parallel out cpu dai
+
+  - sound-dai : phandle of internal dac
+
+Required i2s-out, i2s-in subnode properties:
+
+  - cpu-dai : phandle of i2s cpu dai
+
+  - format : i2s format. "i2s" and "left_j" are supported by
+	     the Pistachio iteration of the i2s controllers
+
+Optional i2s-out, i2s-in subnode properties:
+
+  - bitclock-inversion : i2s bit clock inversion
+
+  - frame-inversion : i2s frame clock inversion
+
+  - continuous-clock : i2s bit and frame clock always active
+
+Optional i2s-out, i2s-in subnodes:
+
+  - <codec-name> : Contains codec information. <codec-name> will be used as
+		   the prefix for the codec. This name must be unique for
+		   each individual codec (unique codec device node), and
+		   cannot be equal to "internal-dac", "i2s-out" or "i2s-in".
+		   The name should be short to avoid control name truncation
+
+Optional <codec-name> subnode properties:
+
+  - mclk : Contains the mclk (master clock) used by the DAC/ADC. If no mclk
+	   is required, or the mclk is provided externally, with no software
+	   intervention required to compenstate for differing sample rates,
+	   this can be omitted.
+	   Valid identifiers (dt-bindings/sound/pistachio-audio.h):
+
+	       PISTACHIO_MCLK_I2S       mclk is provided by the i2s_out_0_mclk
+					output from Pistachio SoC. This clock
+					is shared with the internal i2s out
+					controller
+
+	       PISTACHIO_MCLK_DAC	mclk is provided by the i2s_dac_clk
+					output from Pistachio SoC
+
+  - sound-dai : phandle of the codec. If the codec does not accept/require
+		software configuration, this can be omitted
+
+  - mclk-index : Index of the mclk, used for snd_soc_dai_set_sysclk call.
+		 0 is used if this property is omitted
+
+  - frame-master : Indicates this codec is the frame clock master
+
+  - bitclock-master : Indicates this codec is the bit clock master
+
+  frame-master and bitclock-master cannot exist in more than one of the codec
+  subnodes
+
+  frame-master and bitclock-master cannot be used within i2s out codec subnodes
+  as the Pistachio iteration of the i2s out controller accepts master mode
+  only
+
+  frame-master and bitclock-master can be omitted if img,i2s-clk-loopback is
+  used, or if frame/bit clock generation does not require software intervention
+  (eg a codec operating in hardware-mode)
+
+Required <codec-name> subnode properties if mclk property is present:
+
+  - mclk-fs : Contains the set of fs ratios the DAC/ADC accepts (Nfs for
+	      sample rate r specifies the master clock input to the DAC is
+	      N times r). It is assumed this set of ratios is applicable to
+	      any given sample rate where this does not lead to a violation
+	      of the minimum/maximum frequencies specified by mclk-min-fs-freq
+	      and mclk-max-fs-freq respectively
+
+  - mclk-min-fs-freq : Contains the minimum frequency the DAC/ADC accepts for
+		       the mclk-fs list above
+
+  - mclk-max-fs-freq : Contains the maximum frequency the DAC/ADC accepts for
+		       the mclk-fs list above
+
+  - mclk-max-freq : Contains the maximum frequency for the DAC/ADC. Must be
+		    greater than or equal to mclk-max-fs-freq
+
+
+Example 1:
+
+All audio components present on board. 2x pcm3168a codecs provide 3 i2s in
+and out channels each. dac_mclk provides the single master clock to both
+codecs. The Pistachio i2s out controller is the frame + bit clock master for
+the DAC path. The second pcm3168a codec is the frame + bit clock master for
+the ADC path. GPIO 1 in bank 5 is used as a mute control (active low). GPIO 3
+in bank 5 is used for headphone detect control (active high)
+
+pistachio_audio_card {
+	compatible = "img,pistachio-audio";
+
+	clocks = <&clk_core CLK_AUDIO_PLL>,
+		 <&clk_core CLK_I2S>,
+		 <&clk_core CLK_AUDIO>;
+	clock-names = "audio_pll", "i2s_mclk", "dac_mclk";
+
+	img,cr-periph = <&cr_periph>;
+
+	img,mute-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
+	img,hp-det-gpio = <&gpio5 3 GPIO_ACTIVE_HIGH>;
+
+	img,widgets = "Headphone", "Headphones",
+		      "Speaker", "Speakers";
+
+	img,routing = "Headphones", "internal-dac AOUTL",
+		      "Headphones", "internal-dac AOUTR",
+		      "Speakers", "internal-dac AOUTL",
+		      "Speakers", "internal-dac AOUTR";
+
+	spdif-out {
+		cpu-dai = <&spdif_out>;
+	};
+
+	spdif-in {
+		cpu-dai = <&spdif_in>;
+	};
+
+	parallel-out {
+		cpu-dai = <&parallel_out>;
+		sound-dai = <&internal_dac>;
+	};
+
+	i2s-out {
+		cpu-dai = <&i2s_out>;
+		format = "i2s";
+
+		pcm3168a-1 {
+			mclk = <PISTACHIO_MCLK_DAC>;
+			mclk-fs = <128 192 256 384 512 768>;
+			mclk-min-fs-freq = <2048000>;
+			mclk-max-fs-freq = <36864000>;
+			mclk-max-freq = <36864000>;
+			sound-dai = <&pcm3168a_1 0>;
+		};
+
+		pcm3168a-2 {
+			mclk = <PISTACHIO_MCLK_DAC>;
+			mclk-fs = <128 192 256 384 512 768>;
+			mclk-min-fs-freq = <2048000>;
+			mclk-max-fs-freq = <36864000>;
+			mclk-max-freq = <36864000>;
+			sound-dai = <&pcm3168a_2 0>;
+		};
+	};
+
+	i2s-in {
+		cpu-dai = <&i2s_in>;
+		format = "i2s";
+
+		pcm3168a-1 {
+			mclk = <PISTACHIO_MCLK_DAC>;
+			mclk-fs = <256 384 512 768>;
+			mclk-min-fs-freq = <2048000>;
+			mclk-max-fs-freq = <36864000>;
+			mclk-max-freq = <36864000>;
+			sound-dai = <&pcm3168a_1 1>;
+		};
+
+		pcm3168a-2 {
+			mclk = <PISTACHIO_MCLK_DAC>;
+			mclk-fs = <256 384 512 768>;
+			mclk-min-fs-freq = <2048000>;
+			mclk-max-fs-freq = <36864000>;
+			mclk-max-freq = <36864000>;
+			sound-dai = <&pcm3168a_2 1>;
+			frame-master;
+			bitclock-master;
+		};
+	};
+};
+
+Example 2:
+
+i2s out, i2s in present on board. pcm5101 codec provides 1 i2s out channel.
+pcm1802 codec provides 1 i2s in channel. dac_mclk provides master clock to
+pcm1802. i2s_mclk provides master clock to pcm5101. Both codecs set to operate
+in hardware mode. The Pistachio i2s out controller is the frame + bit clock
+master for the DAC path. The pcm1802 codec is the frame + bit clock master for
+the ADC path
+
+pistachio_audio_card {
+	compatible = "img,pistachio-audio";
+
+	clocks = <&clk_core CLK_AUDIO_PLL>,
+		 <&clk_core CLK_I2S>,
+		 <&clk_core CLK_AUDIO>;
+	clock-names = "audio_pll", "i2s_mclk", "dac_mclk";
+
+	img,cr-periph = <&cr_periph>;
+
+	pinctrl-names = "default";
+	pinctrl-0 = <&dac_clk_pin>, <&i2s_mclk_pin>;
+
+	i2s-out {
+		cpu-dai = <&i2s_out>;
+		format = "i2s";
+
+		pcm5101 {
+			mclk = <PISTACHIO_MCLK_I2S>;
+			mclk-fs = <128 192 256 384 512 768 1024 1152 1536 2048 3072>;
+			mclk-min-fs-freq = <1024000>;
+			mclk-max-fs-freq = <49152000>;
+			mclk-max-freq = <49152000>;
+		};
+	};
+
+	i2s-in {
+		cpu-dai = <&i2s_in>;
+		format = "i2s";
+
+		pcm1802 {
+			mclk = <PISTACHIO_MCLK_DAC>;
+			mclk-fs = <256>;
+			mclk-min-fs-freq = <8192000>;
+			mclk-max-fs-freq = <49152000>;
+			mclk-max-freq = <49152000>;
+		};
+	};
+};
+
+Example 3:
+
+i2s out, i2s in present on board. dac provides 1 i2s out channel.
+microphone array provides 6 i2s in channels. No master clocks are required.
+The Pistachio i2s out controller is the frame + bit clock master
+for the DAC and microphone array paths.
+
+pistachio_audio_card {
+	compatible = "img,pistachio-audio";
+
+	clocks = <&clk_core CLK_AUDIO_PLL>,
+		 <&clk_core CLK_I2S>,
+		 <&clk_core CLK_AUDIO>;
+	clock-names = "audio_pll", "i2s_mclk", "dac_mclk";
+
+	img,cr-periph = <&cr_periph>;
+
+	img,i2s-clk-loopback;
+
+	i2s-out {
+		cpu-dai = <&i2s_out>;
+		format = "i2s";
+		bitclock-inversion;
+
+		dac {
+			sound-dai = <&simple-dac>;
+		};
+	};
+
+	i2s-in {
+		cpu-dai = <&i2s_in>;
+		format = "left_j";
+		frame-inversion;
+	};
+};
-- 
2.1.4

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

* [RFC 2/2] ASoC: img: Add driver for Pistachio audio card
  2016-01-25 19:12 [RFC 0/2] ASoC: img: Add Pistachio card support Damien Horsley
  2016-01-25 19:12 ` [RFC 1/2] ASoC: img: Add binding document for Pistachio audio card Damien Horsley
@ 2016-01-25 19:12 ` Damien Horsley
  1 sibling, 0 replies; 3+ messages in thread
From: Damien Horsley @ 2016-01-25 19:12 UTC (permalink / raw)
  To: alsa-devel
  Cc: Mark Rutland, devicetree, Pawel Moll, Ian Campbell, linux-kernel,
	Mark Brown, Takashi Iwai, Liam Girdwood, Rob Herring, Kumar Gala,
	Damien.Horsley

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

Add a driver for Pistachio audio card

Signed-off-by: Damien.Horsley <Damien.Horsley@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@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@imgtec.com>");
+MODULE_LICENSE("GPL v2");
-- 
2.1.4

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

end of thread, other threads:[~2016-01-25 19:12 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-01-25 19:12 [RFC 0/2] ASoC: img: Add Pistachio card support Damien Horsley
2016-01-25 19:12 ` [RFC 1/2] ASoC: img: Add binding document for Pistachio audio card Damien Horsley
2016-01-25 19:12 ` [RFC 2/2] ASoC: img: Add driver " Damien Horsley

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.