diff mbox

[PATCHv2] ASoC: Add pcm9211 driver

Message ID 20170524103547.9252-1-julian@jusst.de (mailing list archive)
State New, archived
Headers show

Commit Message

Julian Scheel May 24, 2017, 10:35 a.m. UTC
This adds a driver for the TI PCM9211 digital audio interface
transceiver. The driver currently only handles the receiver aspect of
the chip. Some extra device-tree fields are introduced to allow
configuration of pin functions directly out of this driver.

Signed-off-by: Julian Scheel <julian@jusst.de>

---
Changes in v2:
- Fix checkpatch errors (long lines, * association)
- Fix typos in device-tree documentation
- Fix group-function dt. Mask was applied on unshifted data
- Fix error check for int0 gpio lookup
- Fix ADC rate list (92->96kHz)
- Add support for AUXOUT as second DAI

 .../devicetree/bindings/sound/pcm9211.txt          |  119 ++
 MAINTAINERS                                        |    6 +
 include/dt-bindings/sound/pcm9211.h                |   55 +
 sound/soc/codecs/Kconfig                           |   10 +
 sound/soc/codecs/Makefile                          |    4 +
 sound/soc/codecs/pcm9211-i2c.c                     |   65 +
 sound/soc/codecs/pcm9211.c                         | 1446 ++++++++++++++++++++
 sound/soc/codecs/pcm9211.h                         |  206 +++
 8 files changed, 1911 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/pcm9211.txt
 create mode 100644 include/dt-bindings/sound/pcm9211.h
 create mode 100644 sound/soc/codecs/pcm9211-i2c.c
 create mode 100644 sound/soc/codecs/pcm9211.c
 create mode 100644 sound/soc/codecs/pcm9211.h

Comments

Rob Herring May 31, 2017, 5:16 p.m. UTC | #1
On Wed, May 24, 2017 at 12:35:47PM +0200, Julian Scheel wrote:
> This adds a driver for the TI PCM9211 digital audio interface
> transceiver. The driver currently only handles the receiver aspect of
> the chip. Some extra device-tree fields are introduced to allow
> configuration of pin functions directly out of this driver.
> 
> Signed-off-by: Julian Scheel <julian@jusst.de>
> 
> ---
> Changes in v2:
> - Fix checkpatch errors (long lines, * association)
> - Fix typos in device-tree documentation
> - Fix group-function dt. Mask was applied on unshifted data
> - Fix error check for int0 gpio lookup
> - Fix ADC rate list (92->96kHz)
> - Add support for AUXOUT as second DAI
> 
>  .../devicetree/bindings/sound/pcm9211.txt          |  119 ++

Matching the compatible for filename is preferred, so "ti,pcm9211.txt".

>  MAINTAINERS                                        |    6 +
>  include/dt-bindings/sound/pcm9211.h                |   55 +
>  sound/soc/codecs/Kconfig                           |   10 +
>  sound/soc/codecs/Makefile                          |    4 +
>  sound/soc/codecs/pcm9211-i2c.c                     |   65 +
>  sound/soc/codecs/pcm9211.c                         | 1446 ++++++++++++++++++++
>  sound/soc/codecs/pcm9211.h                         |  206 +++
>  8 files changed, 1911 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/sound/pcm9211.txt
>  create mode 100644 include/dt-bindings/sound/pcm9211.h
>  create mode 100644 sound/soc/codecs/pcm9211-i2c.c
>  create mode 100644 sound/soc/codecs/pcm9211.c
>  create mode 100644 sound/soc/codecs/pcm9211.h
> 
> diff --git a/Documentation/devicetree/bindings/sound/pcm9211.txt b/Documentation/devicetree/bindings/sound/pcm9211.txt
> new file mode 100644
> index 000000000000..df3974368125
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/pcm9211.txt
> @@ -0,0 +1,119 @@
> +PCM9211 audio CODEC

TI PCM9211...

> +
> +This device supports both I2C and SPI (configured with pin strapping
> +on the board). The driver is currently implementing i2c only.

What the driver supports is not relevant to the binding.

> +
> +Required properties:
> +
> +  - compatible : "ti,pcm9211"
> +
> +  - reg : the I2C address of the device for I2C, the chip select
> +          number for SPI.
> +
> +  - VCCAD-supply, VCC-supply, VDDRX-supply and AVDD-supply :
> +    power supplies for the device, as covered in
> +    bindings/regulator/regulator.txt
> +
> +Optional properties:
> +
> +  - clocks : A clock specifier for the clock connected to XTI. While the
> +    device could work without it, this driver currently relies on it being
> +    available
> +
> +  - clock-names : Must specify "xti" to match the requested clock.
> +
> +  - reset-gpios : GPIO wired to the reset pin, if not supplied a soft-reset is
> +    used as fallback
> +
> +  - int0-gpios : GPIO connected to interrupt0 output of the PCM9211. The
> +    line can be configured to any of MPO0,1 and MPIOA,B,C0-4 and error/int0.
> +    If this is specified it is used as an interrupt to notify alsa controls

alsa is a Linux detail not relevant to the binding.

> +    about changes of the incoming sampling rate on DIR (spdif) as well as NPCM
> +    status.

This should use interrupts property instead.

> +
> +  - ti,group-function: An array with size 3 specifying function for pingroups
> +    A, B and C. Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,mpio-a-flags-gpio: An array with size 4 specifying flags out or gpio
> +    mode per pin, when function DIR_FLAGS_GPIO is selected for group a.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,mpio-b-flags-gpio: An array with size 4 specifying flags out or gpio
> +    mode per pin, when function DIR_FLAGS_GPIO is selected for group b.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,mpio-c-flags-gpio: An array with size 4 specifying flags out or gpio
> +    mode per pin, when function DIR_FLAGS_GPIO is selected for group c.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,mpio-a-flag: An array with size 4 specifying flag assigned per pin,
> +    when function DIR_FLAGS_GPIO is selected for group a and pin is set to
> +    flags mode.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,mpio-b-flag: An array with size 4 specifying flag assigned per pin,
> +    when function DIR_FLAGS_GPIO is selected for group b and pin is set to
> +    flags mode.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,mpio-c-flag: An array with size 4 specifying flag assigned per pin,
> +    when function DIR_FLAGS_GPIO is selected for group c and pin is set to
> +    flags mode.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,mpo-function: An array with size 2 specifying flag assigned per mpo
> +    pin.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,int0-function: Selects error/int0 pin function.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h
> +
> +  - ti,int1-function: Selects error/int1 pin function.
> +    Possible values are defined in dt-bindings/sound/pcm9211.h

This all looks like your own custom pinmuxing properties. We already 
have a common binding for that.

> +
> +Examples:
> +
> +	pcm9211: pcm9211@43 {
> +		compatible = "ti,pcm9211";
> +		reg = <0x43>;
> +
> +		VCCAD-supply = <&reg_5v0_analog>;
> +		VCC-supply = <&reg_3v3_pll_analog>;
> +		VDDRX-supply = <&reg_3v3>;
> +		DVDD-supply = <&reg_3v3>;
> +
> +		clocks = <&xti_clk>;
> +		clock-names = "xti";
> +	};
> +
> +
> +	pcm9211: pcm9211@43 {

audio-codec@...

> +		compatible = "ti,pcm9211";
> +		reg = <0x43>;
> +
> +		VCCAD-supply = <&reg_5v0_analog>;
> +		VCC-supply = <&reg_3v3_pll_analog>;
> +		VDDRX-supply = <&reg_3v3>;
> +		DVDD-supply = <&reg_3v3>;
> +
> +		clocks = <&xti_clk>;
> +		clock-names = "xti";
> +
> +		reset-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>;
> +		int-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>;
> +
> +		ti,group-function = /bits/ 8
> +			<PCM9211_MPIO_A_GROUP_BIPHASE_INPUT,
> +			PCM9211_MPIO_B_GROUP_DIR_FLAGS_GPIO,
> +			PCM9211_MPIO_C_GROUP_AUXIN1>;
> +		ti,mpio-b-flags-gpio = /bits/ 8
> +			<PCM9211_MPIO_DIR_FLAGS,
> +			PCM9211_MPIO_DIR_FLAGS,
> +			PCM9211_MPIO_DIR_FLAGS,
> +			PCM9211_MPIO_DIR_FLAGS>;
> +		ti,mpio-b-flag = /bits/ 8
> +			<PCM9211_MPIO_FLAG_INT0,
> +			PCM9211_MPIO_FLAG_CLKST,
> +			PCM9211_MPIO_FLAG_CLKST,
> +			PCM9211_MPIO_FLAG_CLKST>;
> +	};
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/sound/pcm9211.txt b/Documentation/devicetree/bindings/sound/pcm9211.txt
new file mode 100644
index 000000000000..df3974368125
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/pcm9211.txt
@@ -0,0 +1,119 @@ 
+PCM9211 audio CODEC
+
+This device supports both I2C and SPI (configured with pin strapping
+on the board). The driver is currently implementing i2c only.
+
+Required properties:
+
+  - compatible : "ti,pcm9211"
+
+  - reg : the I2C address of the device for I2C, the chip select
+          number for SPI.
+
+  - VCCAD-supply, VCC-supply, VDDRX-supply and AVDD-supply :
+    power supplies for the device, as covered in
+    bindings/regulator/regulator.txt
+
+Optional properties:
+
+  - clocks : A clock specifier for the clock connected to XTI. While the
+    device could work without it, this driver currently relies on it being
+    available
+
+  - clock-names : Must specify "xti" to match the requested clock.
+
+  - reset-gpios : GPIO wired to the reset pin, if not supplied a soft-reset is
+    used as fallback
+
+  - int0-gpios : GPIO connected to interrupt0 output of the PCM9211. The
+    line can be configured to any of MPO0,1 and MPIOA,B,C0-4 and error/int0.
+    If this is specified it is used as an interrupt to notify alsa controls
+    about changes of the incoming sampling rate on DIR (spdif) as well as NPCM
+    status.
+
+  - ti,group-function: An array with size 3 specifying function for pingroups
+    A, B and C. Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,mpio-a-flags-gpio: An array with size 4 specifying flags out or gpio
+    mode per pin, when function DIR_FLAGS_GPIO is selected for group a.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,mpio-b-flags-gpio: An array with size 4 specifying flags out or gpio
+    mode per pin, when function DIR_FLAGS_GPIO is selected for group b.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,mpio-c-flags-gpio: An array with size 4 specifying flags out or gpio
+    mode per pin, when function DIR_FLAGS_GPIO is selected for group c.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,mpio-a-flag: An array with size 4 specifying flag assigned per pin,
+    when function DIR_FLAGS_GPIO is selected for group a and pin is set to
+    flags mode.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,mpio-b-flag: An array with size 4 specifying flag assigned per pin,
+    when function DIR_FLAGS_GPIO is selected for group b and pin is set to
+    flags mode.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,mpio-c-flag: An array with size 4 specifying flag assigned per pin,
+    when function DIR_FLAGS_GPIO is selected for group c and pin is set to
+    flags mode.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,mpo-function: An array with size 2 specifying flag assigned per mpo
+    pin.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,int0-function: Selects error/int0 pin function.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+  - ti,int1-function: Selects error/int1 pin function.
+    Possible values are defined in dt-bindings/sound/pcm9211.h
+
+Examples:
+
+	pcm9211: pcm9211@43 {
+		compatible = "ti,pcm9211";
+		reg = <0x43>;
+
+		VCCAD-supply = <&reg_5v0_analog>;
+		VCC-supply = <&reg_3v3_pll_analog>;
+		VDDRX-supply = <&reg_3v3>;
+		DVDD-supply = <&reg_3v3>;
+
+		clocks = <&xti_clk>;
+		clock-names = "xti";
+	};
+
+
+	pcm9211: pcm9211@43 {
+		compatible = "ti,pcm9211";
+		reg = <0x43>;
+
+		VCCAD-supply = <&reg_5v0_analog>;
+		VCC-supply = <&reg_3v3_pll_analog>;
+		VDDRX-supply = <&reg_3v3>;
+		DVDD-supply = <&reg_3v3>;
+
+		clocks = <&xti_clk>;
+		clock-names = "xti";
+
+		reset-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>;
+		int-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>;
+
+		ti,group-function = /bits/ 8
+			<PCM9211_MPIO_A_GROUP_BIPHASE_INPUT,
+			PCM9211_MPIO_B_GROUP_DIR_FLAGS_GPIO,
+			PCM9211_MPIO_C_GROUP_AUXIN1>;
+		ti,mpio-b-flags-gpio = /bits/ 8
+			<PCM9211_MPIO_DIR_FLAGS,
+			PCM9211_MPIO_DIR_FLAGS,
+			PCM9211_MPIO_DIR_FLAGS,
+			PCM9211_MPIO_DIR_FLAGS>;
+		ti,mpio-b-flag = /bits/ 8
+			<PCM9211_MPIO_FLAG_INT0,
+			PCM9211_MPIO_FLAG_CLKST,
+			PCM9211_MPIO_FLAG_CLKST,
+			PCM9211_MPIO_FLAG_CLKST>;
+	};
diff --git a/MAINTAINERS b/MAINTAINERS
index c2ffc96ccf4c..1f3b2e98dd9d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9985,6 +9985,12 @@  S:	Supported
 F:	Documentation/devicetree/bindings/pci/pci-thunder-*
 F:	drivers/pci/host/pci-thunder-*
 
+PCM9211 AUDIO CODEC DRIVER
+M:	Julian Scheel <julian@jusst.de>
+L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
+S:	Maintained
+F:	sound/soc/codecs/pcm9211*
+
 PCMCIA SUBSYSTEM
 P:	Linux PCMCIA Team
 L:	linux-pcmcia@lists.infradead.org
diff --git a/include/dt-bindings/sound/pcm9211.h b/include/dt-bindings/sound/pcm9211.h
new file mode 100644
index 000000000000..6d1367f21d25
--- /dev/null
+++ b/include/dt-bindings/sound/pcm9211.h
@@ -0,0 +1,55 @@ 
+#ifndef __DT_PCM9211_H
+#define __DT_PCM9211_H
+
+/* INT0/ERROR select */
+#define PCM9211_ERROR_INT0_ERROR			0
+#define PCM9211_ERROR_INT0_INT0				1
+
+/* INT1/NPCM select */
+#define PCM9211_NPCM_INT1_NPCM				0
+#define PCM9211_NPCM_INT1_INT1				1
+
+/* MPIO group functions */
+#define PCM9211_MPIO_A_GROUP_BIPHASE_INPUT		0
+#define PCM9211_MPIO_A_GROUP_CLKST_VOUT_XMCK_INT0	1
+#define PCM9211_MPIO_A_GROUP_SEC_BCK_LRCK_XMCKO_INT0	2
+#define PCM9211_MPIO_A_GROUP_DIR_FLAGS_GPIO		3
+
+#define PCM9211_MPIO_B_GROUP_AUXIN2			0
+#define PCM9211_MPIO_B_GROUP_AUXOUT			1
+#define PCM9211_MPIO_B_GROUP_SAMPLING_FREQ_RES		2
+#define PCM9211_MPIO_B_GROUP_DIR_FLAGS_GPIO		3
+#define PCM9211_MPIO_B_GROUP_DIR_BCUV_BFRAME_VUC	4
+#define PCM9211_MPIO_B_GROUP_EXT_ADC_IN			5
+#define PCM9211_MPIO_B_GROUP_TEST_MODE			7
+
+#define PCM9211_MPIO_C_GROUP_AUXIN1			0
+#define PCM9211_MPIO_C_GROUP_ADC_CLOCK_DATA		1
+#define PCM9211_MPIO_C_GROUP_SAMPLING_FREQ_RES		2
+#define PCM9211_MPIO_C_GROUP_DIR_FLAGS_GPIO		3
+#define PCM9211_MPIO_C_GROUP_DIR_BCUV_BFRAME_VUC	4
+#define PCM9211_MPIO_C_GROUP_DIT_CLOCK_DATA		5
+
+/* MPIO flags/gpio select */
+#define PCM9211_MPIO_DIR_FLAGS				0
+#define PCM9211_MPIO_GPIO				1
+
+/* MPIO flags */
+#define PCM9211_MPIO_FLAG_CLKST				0
+#define PCM9211_MPIO_FLAG_EMPH				1
+#define PCM9211_MPIO_FLAG_BPSYNC			2
+#define PCM9211_MPIO_FLAG_DTSCD				3
+#define PCM9211_MPIO_FLAG_PARITY			4
+#define PCM9211_MPIO_FLAG_LOCK				5
+#define PCM9211_MPIO_FLAG_VOUT				6
+#define PCM9211_MPIO_FLAG_UOUT				7
+#define PCM9211_MPIO_FLAG_COUT				8
+#define PCM9211_MPIO_FLAG_BFRAME			9
+#define PCM9211_MPIO_FLAG_FSOUT0			10
+#define PCM9211_MPIO_FLAG_FSOUT1			11
+#define PCM9211_MPIO_FLAG_FSOUT2			12
+#define PCM9211_MPIO_FLAG_FSOUT3			13
+#define PCM9211_MPIO_FLAG_INT0				14
+#define PCM9211_MPIO_FLAG_INT1				15
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 883ed4c8a551..d7cf85ba020f 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -113,6 +113,7 @@  config SND_SOC_ALL_CODECS
 	select SND_SOC_PCM5102A
 	select SND_SOC_PCM512x_I2C if I2C
 	select SND_SOC_PCM512x_SPI if SPI_MASTER
+	select SND_SOC_PCM9211_I2C if I2C
 	select SND_SOC_RT286 if I2C
 	select SND_SOC_RT298 if I2C
 	select SND_SOC_RT5514 if I2C
@@ -684,6 +685,15 @@  config SND_SOC_PCM512x_SPI
 	select SND_SOC_PCM512x
 	select REGMAP_SPI
 
+config SND_SOC_PCM9211
+	tristate
+
+config SND_SOC_PCM9211_I2C
+	tristate "Texas Instruments PCM9211 CODEC - I2C"
+	depends on I2C
+	select SND_SOC_PCM9211
+	select REGMAP_I2C
+
 config SND_SOC_RL6231
 	tristate
 	default y if SND_SOC_RT5514=y
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 28a63fdaf982..a8eecee5fcfc 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -110,6 +110,8 @@  snd-soc-pcm5102a-objs := pcm5102a.o
 snd-soc-pcm512x-objs := pcm512x.o
 snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o
 snd-soc-pcm512x-spi-objs := pcm512x-spi.o
+snd-soc-pcm9211-objs := pcm9211.o
+snd-soc-pcm9211-i2c-objs := pcm9211-i2c.o
 snd-soc-rl6231-objs := rl6231.o
 snd-soc-rl6347a-objs := rl6347a.o
 snd-soc-rt286-objs := rt286.o
@@ -344,6 +346,8 @@  obj-$(CONFIG_SND_SOC_PCM5102A)	+= snd-soc-pcm5102a.o
 obj-$(CONFIG_SND_SOC_PCM512x)	+= snd-soc-pcm512x.o
 obj-$(CONFIG_SND_SOC_PCM512x_I2C)	+= snd-soc-pcm512x-i2c.o
 obj-$(CONFIG_SND_SOC_PCM512x_SPI)	+= snd-soc-pcm512x-spi.o
+obj-$(CONFIG_SND_SOC_PCM9211)	+= snd-soc-pcm9211.o
+obj-$(CONFIG_SND_SOC_PCM9211_I2C)	+= snd-soc-pcm9211-i2c.o
 obj-$(CONFIG_SND_SOC_RL6231)	+= snd-soc-rl6231.o
 obj-$(CONFIG_SND_SOC_RL6347A)	+= snd-soc-rl6347a.o
 obj-$(CONFIG_SND_SOC_RT286)	+= snd-soc-rt286.o
diff --git a/sound/soc/codecs/pcm9211-i2c.c b/sound/soc/codecs/pcm9211-i2c.c
new file mode 100644
index 000000000000..e0678b280f11
--- /dev/null
+++ b/sound/soc/codecs/pcm9211-i2c.c
@@ -0,0 +1,65 @@ 
+/*
+ * PCM9211 codec i2c driver
+ *
+ * Copyright (C) 2017 jusst technologies GmbH / jusst.engineering
+ *
+ * Author: Julian Scheel <julian@jusst.de>
+ *
+ * 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/i2c.h>
+#include <linux/module.h>
+
+#include <sound/soc.h>
+
+#include "pcm9211.h"
+
+static int pcm9211_i2c_probe(struct i2c_client *i2c,
+		const struct i2c_device_id *id)
+{
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_i2c(i2c, &pcm9211_regmap);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	return pcm9211_probe(&i2c->dev, regmap);
+}
+
+static int pcm9211_i2c_remove(struct i2c_client *i2c)
+{
+	pcm9211_remove(&i2c->dev);
+
+	return 0;
+}
+
+static const struct i2c_device_id pcm9211_i2c_id[] = {
+	{ "pcm9211", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, pcm9211_i2c_id);
+
+static const struct of_device_id pcm9211_of_match[] = {
+	{ .compatible = "ti,pcm9211", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pcm9211_of_match);
+
+static struct i2c_driver pcm9211_i2c_driver = {
+	.probe = pcm9211_i2c_probe,
+	.remove = pcm9211_i2c_remove,
+	.id_table = pcm9211_i2c_id,
+	.driver = {
+		.name = "pcm9211",
+		.of_match_table = pcm9211_of_match,
+		.pm = &pcm9211_pm_ops,
+	},
+};
+module_i2c_driver(pcm9211_i2c_driver);
+
+MODULE_DESCRIPTION("PCM9211 I2C codec driver");
+MODULE_AUTHOR("Julian Scheel <julian@jusst.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/pcm9211.c b/sound/soc/codecs/pcm9211.c
new file mode 100644
index 000000000000..9ccf3a09b24c
--- /dev/null
+++ b/sound/soc/codecs/pcm9211.c
@@ -0,0 +1,1446 @@ 
+/*
+ * PCM9211 codec driver
+ *
+ * Copyright (C) 2017 jusst technologies GmbH / jusst.engineering
+ *
+ * Author; Julian Scheel <julian@jusst.de>
+ *
+ * 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/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "pcm9211.h"
+
+#define PCM9211_MAX_SYSCLK 24576000
+#define PCM9211_DAI_MAIN 0
+#define PCM9211_DAI_AUX 1
+
+#define PCM9211_SUPPLIES 4
+static const char *const pcm9211_supply_names[PCM9211_SUPPLIES] = {
+	"VCCAD",
+	"VCC",
+	"VDDRX",
+	"DVDD",
+};
+
+struct pcm9211_priv {
+	struct regulator_bulk_data supplies[PCM9211_SUPPLIES];
+	struct snd_pcm_hw_constraint_list rate_constraints;
+	struct delayed_work npcm_clear_work;
+	struct snd_kcontrol *preamble_ctl;
+	struct snd_kcontrol *npcm_ctl;
+	struct snd_kcontrol *rate_ctl;
+	struct snd_kcontrol *dts_ctl;
+	struct snd_soc_codec *codec;
+	struct gpio_desc *reset;
+	struct gpio_desc *int0;
+	struct regmap *regmap;
+	struct device *dev;
+	struct clk *xti;
+
+	unsigned int dai_format;
+	unsigned int dir_rate;
+	unsigned int adc_rate;
+	unsigned long sysclk;
+	u8 burst_preamble[4];
+	u8 npcm_state;
+};
+
+static const struct regmap_range pcm9211_reg_rd_range[] = {
+	regmap_reg_range(PCM9211_ERR_OUT, PCM9211_PD_BUF1),
+	regmap_reg_range(PCM9211_SYS_RESET, PCM9211_SYS_RESET),
+	regmap_reg_range(PCM9211_ADC_CTRL1, PCM9211_ADC_CTRL1),
+	regmap_reg_range(PCM9211_ADC_L_CH_ATT, PCM9211_ADC_CTRL3),
+	regmap_reg_range(PCM9211_DIR_STATUS1, PCM9211_DIT_STATUS6),
+	regmap_reg_range(PCM9211_MAIN_AUX_MUTE, PCM9211_MPIO_C_DATA_IN),
+};
+
+static const struct regmap_access_table pcm9211_reg_rd_table = {
+	.yes_ranges = pcm9211_reg_rd_range,
+	.n_yes_ranges = ARRAY_SIZE(pcm9211_reg_rd_range),
+};
+
+static const struct regmap_range pcm9211_reg_wr_range[] = {
+	regmap_reg_range(PCM9211_ERR_OUT, PCM9211_INT1_CAUSE),
+	regmap_reg_range(PCM9211_INT_POLARITY, PCM9211_FS_CALC_TARGET),
+	regmap_reg_range(PCM9211_SYS_RESET, PCM9211_SYS_RESET),
+	regmap_reg_range(PCM9211_ADC_CTRL1, PCM9211_ADC_CTRL1),
+	regmap_reg_range(PCM9211_ADC_L_CH_ATT, PCM9211_ADC_CTRL3),
+	regmap_reg_range(PCM9211_DIT_CTRL1, PCM9211_DIT_STATUS6),
+	regmap_reg_range(PCM9211_MAIN_AUX_MUTE, PCM9211_MPIO_C_DATA_OUT),
+};
+
+static const struct regmap_access_table pcm9211_reg_wr_table = {
+	.yes_ranges = pcm9211_reg_wr_range,
+	.n_yes_ranges = ARRAY_SIZE(pcm9211_reg_wr_range),
+};
+
+static const struct regmap_range pcm9211_reg_volatile_range[] = {
+	regmap_reg_range(PCM9211_INT0_OUT, PCM9211_INT1_OUT),
+	regmap_reg_range(PCM9211_BIPHASE_INFO, PCM9211_PD_BUF1),
+	regmap_reg_range(PCM9211_DIR_STATUS1, PCM9211_DIR_STATUS6),
+};
+
+static const struct regmap_access_table pcm9211_reg_volatile_table = {
+	.yes_ranges = pcm9211_reg_volatile_range,
+	.n_yes_ranges = ARRAY_SIZE(pcm9211_reg_volatile_range),
+};
+
+static const struct reg_default pcm9211_reg_defaults[] = {
+	{ PCM9211_ERR_OUT, 0x00 },
+	{ PCM9211_DIR_INITIAL1, 0x00 },
+	{ PCM9211_DIR_INITIAL2, 0x01 },
+	{ PCM9211_DIR_INITIAL3, 0x04 },
+	{ PCM9211_OSC_CTRL, 0x00 },
+	{ PCM9211_ERR_CAUSE, 0x01 },
+	{ PCM9211_AUTO_SEL_CAUSE, 0x01 },
+	{ PCM9211_DIR_FS_RANGE, 0x00 },
+	{ PCM9211_NON_PCM_DEF, 0x03 },
+	{ PCM9211_DTS_CD_LD, 0x0c },
+	{ PCM9211_INT0_CAUSE, 0xff },
+	{ PCM9211_INT1_CAUSE, 0xff },
+	{ PCM9211_INT0_OUT, 0x00 },
+	{ PCM9211_INT1_OUT, 0x00 },
+	{ PCM9211_INT_POLARITY, 0x00 },
+	{ PCM9211_DIR_OUT_FMT, 0x04 },
+	{ PCM9211_DIR_RSCLK_RATIO, 0x02 },
+	{ PCM9211_XTI_SCLK_FREQ, 0x1a },
+	{ PCM9211_DIR_SOURCE_BIT2, 0x22 },
+	{ PCM9211_XTI_SOURCE_BIT2, 0x22 },
+	{ PCM9211_DIR_INP_BIPHASE, 0xc2 },
+	{ PCM9211_RECOUT0_BIPHASE, 0x02 },
+	{ PCM9211_RECOUT1_BIPHASE, 0x02 },
+	{ PCM9211_FS_CALC_TARGET, 0x00 },
+	{ PCM9211_FS_CALC_RESULT, 0x08 },
+	{ PCM9211_BIPHASE_INFO, 0x08 },
+	{ PCM9211_PC_BUF0, 0x01 },
+	{ PCM9211_PC_BUF1, 0x00 },
+	{ PCM9211_PD_BUF0, 0x20 },
+	{ PCM9211_PD_BUF1, 0x57 },
+	{ PCM9211_SYS_RESET, 0x40 },
+	{ PCM9211_ADC_CTRL1, 0x02 },
+	{ PCM9211_ADC_L_CH_ATT, 0xd7 },
+	{ PCM9211_ADC_R_CH_ATT, 0xd7 },
+	{ PCM9211_ADC_CTRL2, 0x00 },
+	{ PCM9211_ADC_CTRL3, 0x00 },
+	{ PCM9211_DIR_STATUS1, 0x04 },
+	{ PCM9211_DIR_STATUS2, 0x00 },
+	{ PCM9211_DIR_STATUS3, 0x00 },
+	{ PCM9211_DIR_STATUS4, 0x00 },
+	{ PCM9211_DIR_STATUS5, 0x00 },
+	{ PCM9211_DIR_STATUS6, 0x00 },
+	{ PCM9211_DIT_CTRL1, 0x44 },
+	{ PCM9211_DIT_CTRL2, 0x10 },
+	{ PCM9211_DIT_CTRL3, 0x00 },
+	{ PCM9211_DIT_STATUS1, 0x00 },
+	{ PCM9211_DIT_STATUS2, 0x00 },
+	{ PCM9211_DIT_STATUS3, 0x00 },
+	{ PCM9211_DIT_STATUS4, 0x00 },
+	{ PCM9211_DIT_STATUS5, 0x00 },
+	{ PCM9211_DIT_STATUS6, 0x00 },
+	{ PCM9211_MAIN_AUX_MUTE, 0x00 },
+	{ PCM9211_MAIN_OUT_SOURCE, 0x00 },
+	{ PCM9211_AUX_OUT_SOURCE, 0x00 },
+	{ PCM9211_MPIO_B_MAIN_HIZ, 0x00 },
+	{ PCM9211_MPIO_C_MPIO_A_HIZ, 0x0f },
+	{ PCM9211_MPIO_GROUP, 0x40 },
+	{ PCM9211_MPIO_A_FLAGS, 0x00 },
+	{ PCM9211_MPIO_B_MPIO_C_FLAGS, 0x00 },
+	{ PCM9211_MPIO_A1_A0_OUT_FLAG, 0x00 },
+	{ PCM9211_MPIO_A3_A2_OUT_FLAG, 0x00 },
+	{ PCM9211_MPIO_B1_B0_OUT_FLAG, 0x00 },
+	{ PCM9211_MPIO_B3_B2_OUT_FLAG, 0x00 },
+	{ PCM9211_MPIO_C1_C0_OUT_FLAG, 0x00 },
+	{ PCM9211_MPIO_C3_C2_OUT_FLAG, 0x00 },
+	{ PCM9211_MPO_1_0_FUNC, 0x3d },
+	{ PCM9211_MPIO_A_B_DIR, 0x00 },
+	{ PCM9211_MPIO_C_DIR, 0x00 },
+	{ PCM9211_MPIO_A_B_DATA_OUT, 0x00 },
+	{ PCM9211_MPIO_C_DATA_OUT, 0x00 },
+	{ PCM9211_MPIO_A_B_DATA_IN, 0x00 },
+	{ PCM9211_MPIO_C_DATA_IN, 0x02 },
+};
+
+const struct regmap_config pcm9211_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = PCM9211_MPIO_C_DATA_IN,
+	.wr_table = &pcm9211_reg_wr_table,
+	.rd_table = &pcm9211_reg_rd_table,
+	.volatile_table = &pcm9211_reg_volatile_table,
+	.reg_defaults = pcm9211_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(pcm9211_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(pcm9211_regmap);
+
+static const u32 adc_rates[] = { 48000, 96000 };
+static const struct snd_pcm_hw_constraint_list adc_rate_constraints = {
+	.count = ARRAY_SIZE(adc_rates),
+	.list = adc_rates,
+};
+
+static const int biphase_rates[] = {
+	0, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100,
+	48000, 64000, 88200, 96000, 128000, 176400, 192000
+};
+
+static const unsigned int pcm9211_sck_ratios[] = { 1, 2, 4, 8 };
+static const unsigned int pcm9211_bck_ratios[] = { 2, 4, 8, 16 };
+static const unsigned int pcm9211_lrck_ratios[] = { 128, 256, 512, 1024 };
+#define PCM9211_NUM_SCK_RATIOS ARRAY_SIZE(pcm9211_sck_ratios)
+#define PCM9211_NUM_BCK_RATIOS ARRAY_SIZE(pcm9211_bck_ratios)
+#define PCM9211_NUM_LRCK_RATIOS ARRAY_SIZE(pcm9211_lrck_ratios)
+
+static int pcm9211_get_output_port(struct device *dev, int dai_id)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+	unsigned int port;
+	unsigned int val;
+	int reg = 0;
+	int ret;
+
+	switch (dai_id) {
+	case PCM9211_DAI_MAIN:
+		reg = PCM9211_MAIN_OUT_SOURCE;
+		break;
+	case PCM9211_DAI_AUX:
+		reg = PCM9211_AUX_OUT_SOURCE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = regmap_read(priv->regmap, reg, &val);
+	if (ret) {
+		dev_err(dev, "Failed to read selected source: %d\n", ret);
+		return ret;
+	}
+
+	port = (val & PCM9211_MOPSRC_MASK) >> PCM9211_MOPSRC_SHIFT;
+	if (port == PCM9211_MOSRC_AUTO) {
+		ret = regmap_read(priv->regmap, PCM9211_BIPHASE_INFO, &val);
+		if (ret) {
+			dev_err(dev, "Failed to read biphase information: %d\n",
+					ret);
+			return ret;
+		}
+
+		/* Assumes that Sampling Frequency Status calculation
+		 * corresponds with DIR Lock, which seems to to be exposed to
+		 * any register directly
+		 */
+		if ((val & PCM9211_BIPHASE_SFSST_MASK) == 0)
+			port = PCM9211_MOSRC_DIR;
+		else
+			port = PCM9211_MOSRC_ADC;
+	}
+
+	return port;
+}
+
+static int pcm9211_dir_rate(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(priv->regmap, PCM9211_BIPHASE_INFO, &val);
+	if (ret) {
+		dev_err(dev, "Failed to read biphase information: %d\n", ret);
+		return ret;
+	}
+
+	if ((val & PCM9211_BIPHASE_SFSST_MASK)) {
+		dev_dbg(dev, "Biphase Fs calculation not locked\n");
+		return 0;
+	}
+
+	return biphase_rates[(val & PCM9211_BIPHASE_SFSOUT_MASK)
+		>> PCM9211_BIPHASE_SFSOUT_SHIFT];
+}
+
+static int pcm9211_read_burst_preamble(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+	int ret;
+
+	ret = regmap_raw_read(priv->regmap, PCM9211_PC_BUF0,
+			priv->burst_preamble, 4);
+	if (ret) {
+		dev_err(dev, "Failed to read burst preamble: %d\n", ret);
+		return ret;
+	}
+
+	dev_dbg(dev, "Burst preamble: 0x%02x 0x%02x 0x%02x 0x%02x\n",
+			priv->burst_preamble[0], priv->burst_preamble[1],
+			priv->burst_preamble[2], priv->burst_preamble[3]);
+
+	return 0;
+}
+
+static int pcm9211_dir_rate_kctl_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 8000;
+	uinfo->value.integer.min = 96000;
+
+	return 0;
+}
+
+static int pcm9211_dir_rate_kctl(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcm9211_priv *priv = snd_soc_component_get_drvdata(component);
+	struct device *dev = priv->dev;
+
+	/* If we have an interrupt connected dir_rate is up-to-date */
+	if (!priv->int0)
+		priv->dir_rate = pcm9211_dir_rate(dev);
+
+	ucontrol->value.integer.value[0] = priv->dir_rate;
+
+	return 0;
+}
+
+#define pcm9211_dir_npcm_kctl_info snd_ctl_boolean_mono_info
+#define pcm9211_dir_npcm_kctl pcm9211_int0_kctl
+
+#define pcm9211_dir_dtscd_kctl_info snd_ctl_boolean_mono_info
+#define pcm9211_dir_dtscd_kctl pcm9211_int0_kctl
+
+static int pcm9211_int0_kctl(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcm9211_priv *priv = snd_soc_component_get_drvdata(component);
+	u8 mask = kcontrol->private_value & 0xff;
+	struct device *dev = priv->dev;
+	unsigned int cause;
+	int ret;
+
+	/* If interrupt line is not connected read the last interrupt state */
+	if (!priv->int0) {
+		ret = regmap_read(priv->regmap, PCM9211_INT0_OUT, &cause);
+		if (ret) {
+			dev_err(dev, "Failed to read int0 cause: %d\n", ret);
+			return IRQ_HANDLED;
+		}
+		priv->npcm_state = cause & (PCM9211_INT0_MNPCM0_MASK |
+				PCM9211_INT0_MDTSCD0_MASK);
+	}
+
+	ucontrol->value.integer.value[0] = (priv->npcm_state & mask) == mask;
+
+	return 0;
+}
+
+static int pcm9211_dir_preamble_kctl_info(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES;
+	uinfo->count = 4;
+
+	return 0;
+}
+
+static int pcm9211_dir_preamble_kctl(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct pcm9211_priv *priv = snd_soc_component_get_drvdata(component);
+	struct device *dev = priv->dev;
+
+	/* If we have an interrupt connected preamble is up-to-date */
+	if (!priv->int0)
+		priv->dir_rate = pcm9211_read_burst_preamble(dev);
+
+	memcpy(ucontrol->value.bytes.data, priv->burst_preamble, 4);
+
+	return 0;
+}
+
+static struct snd_kcontrol *pcm9211_get_ctl(struct device *dev,
+		const char *name)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+	struct snd_ctl_elem_id elem_id;
+
+	memset(&elem_id, 0, sizeof(elem_id));
+	elem_id.iface = SNDRV_CTL_ELEM_IFACE_PCM;
+	strcpy(elem_id.name, name);
+	return snd_ctl_find_id(priv->codec->component.card->snd_card, &elem_id);
+}
+
+static struct snd_kcontrol *pcm9211_get_rate_ctl(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+
+	if (!priv->rate_ctl)
+		priv->rate_ctl = pcm9211_get_ctl(dev, "DIR Sample Rate");
+
+	return priv->rate_ctl;
+}
+
+static struct snd_kcontrol *pcm9211_get_npcm_ctl(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+
+	if (!priv->npcm_ctl)
+		priv->npcm_ctl = pcm9211_get_ctl(dev, "DIR Non-PCM Bitstream");
+
+	return priv->npcm_ctl;
+}
+
+static struct snd_kcontrol *pcm9211_get_dtscd_ctl(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+
+	if (!priv->dts_ctl)
+		priv->dts_ctl = pcm9211_get_ctl(dev, "DIR DTS Bitstream");
+
+	return priv->dts_ctl;
+}
+
+static struct snd_kcontrol *pcm9211_get_burst_preamble_ctl(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+
+	if (!priv->preamble_ctl)
+		priv->preamble_ctl = pcm9211_get_ctl(dev, "DIR Burst Preamble");
+
+	return priv->preamble_ctl;
+}
+
+static irqreturn_t pcm9211_interrupt(int irq, void *data)
+{
+	struct pcm9211_priv *priv = data;
+	struct device *dev = priv->dev;
+
+	unsigned int cause;
+	int rate;
+	int ret;
+
+	ret = regmap_read(priv->regmap, PCM9211_INT0_OUT, &cause);
+	if (ret) {
+		dev_err(dev, "Failed to read int0 cause: %d\n", ret);
+		return IRQ_HANDLED;
+	}
+
+	if (cause & PCM9211_INT0_MFSCHG0_MASK) {
+		/* Interrupt is generated before the Fs calculation has
+		 * finished. Give it time to settle.
+		 */
+		usleep_range(15000, 16000);
+		rate = pcm9211_dir_rate(dev);
+
+		if (rate < 0) {
+			dev_err(dev, "Failed to retrieve DIR rate: %d\n", rate);
+			goto preamble;
+		}
+
+		if (rate == priv->dir_rate)
+			goto preamble;
+
+		priv->dir_rate = rate;
+		dev_dbg(dev, "DIR sampling rate changed to: %d\n", rate);
+
+		if (priv->codec == NULL || pcm9211_get_rate_ctl(dev) == NULL)
+			goto preamble;
+
+		snd_ctl_notify(priv->codec->component.card->snd_card,
+				SNDRV_CTL_EVENT_MASK_VALUE,
+				&pcm9211_get_rate_ctl(dev)->id);
+	}
+
+preamble:
+	if ((cause & PCM9211_INT0_MPCRNW0_MASK)) {
+		if (pcm9211_read_burst_preamble(dev) < 0)
+			goto npcm;
+
+		if (priv->codec == NULL ||
+				pcm9211_get_burst_preamble_ctl(dev) == NULL)
+			goto dts;
+
+		snd_ctl_notify(priv->codec->component.card->snd_card,
+				SNDRV_CTL_EVENT_MASK_VALUE,
+				&pcm9211_get_burst_preamble_ctl(dev)->id);
+	}
+
+npcm:
+	if ((cause & PCM9211_INT0_MNPCM0_MASK)) {
+		/* PCM9211 does not generate an interrupt for NPCM0 1->0
+		 * transition, but continuously generates interrupts as long as
+		 * NPCM0 is high, so use a timeout to clear
+		 */
+		cancel_delayed_work_sync(&priv->npcm_clear_work);
+		queue_delayed_work(system_wq, &priv->npcm_clear_work,
+				msecs_to_jiffies(100));
+
+
+		if ((cause & PCM9211_INT0_MNPCM0_MASK) !=
+				(priv->npcm_state & PCM9211_INT0_MNPCM0_MASK))
+			dev_dbg(dev, "NPCM status on interrupt: %d\n",
+					(cause & PCM9211_INT0_MNPCM0_MASK) ==
+					PCM9211_INT0_MNPCM0_MASK);
+
+		priv->npcm_state = (priv->npcm_state &
+				~PCM9211_INT0_MNPCM0_MASK) |
+			(cause & PCM9211_INT0_MNPCM0_MASK);
+
+		if (priv->codec == NULL || pcm9211_get_npcm_ctl(dev) == NULL)
+			goto dts;
+
+		snd_ctl_notify(priv->codec->component.card->snd_card,
+				SNDRV_CTL_EVENT_MASK_VALUE,
+				&pcm9211_get_npcm_ctl(dev)->id);
+	}
+
+dts:
+	if (cause & PCM9211_INT0_MDTSCD0_MASK) {
+		dev_dbg(dev, "DTSCD status on interrupt: %d\n",
+				(cause & PCM9211_INT0_MDTSCD0_MASK) ==
+				PCM9211_INT0_MDTSCD0_MASK);
+		priv->npcm_state |= PCM9211_INT0_MDTSCD0_MASK;
+
+		if (priv->codec == NULL || pcm9211_get_dtscd_ctl(dev) == NULL)
+			return IRQ_HANDLED;
+
+		snd_ctl_notify(priv->codec->component.card->snd_card,
+				SNDRV_CTL_EVENT_MASK_VALUE,
+				&pcm9211_get_dtscd_ctl(dev)->id);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int pcm9211_startup(struct snd_pcm_substream *substream,
+		struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec);
+	struct snd_soc_component *component = &codec->component;
+	struct device *dev = codec->dev;
+	struct snd_soc_dai *other_dai;
+	int port;
+
+	dev_dbg(dev, "Startup on dai %d\n", dai->id);
+	port = pcm9211_get_output_port(dev, dai->id);
+	if (port < 0) {
+		dev_err(dev, "Failed to read selected port: %d\n", port);
+		return port;
+	}
+
+	if (port == PCM9211_MOSRC_ADC) {
+		dev_dbg(dev, "ADC capture on dai %d\n", dai->id);
+		/* Check if other DAI uses ADC, if so limit available rates */
+		list_for_each_entry(other_dai, &component->dai_list, list) {
+			if (!other_dai->capture_active)
+				continue;
+
+			if (pcm9211_get_output_port(dev, other_dai->id) != port)
+				continue;
+
+			priv->rate_constraints.count = 1;
+			priv->rate_constraints.list = &priv->adc_rate;
+			priv->rate_constraints.mask = 0;
+
+			dev_dbg(dev, "Active ADC rate is %d Hz\n",
+					priv->adc_rate);
+
+			return snd_pcm_hw_constraint_list(substream->runtime,
+					0, SNDRV_PCM_HW_PARAM_RATE,
+					&priv->rate_constraints);
+		}
+
+		return snd_pcm_hw_constraint_list(substream->runtime,
+				0, SNDRV_PCM_HW_PARAM_RATE,
+				&adc_rate_constraints);
+	}
+
+	priv->dir_rate = pcm9211_dir_rate(dev);
+	priv->rate_constraints.count = 1;
+	priv->rate_constraints.list = &priv->dir_rate;
+	priv->rate_constraints.mask = 0;
+
+	dev_dbg(dev, "Detected biphase rate is %d Hz\n", priv->dir_rate);
+
+	return snd_pcm_hw_constraint_list(substream->runtime,
+			0, SNDRV_PCM_HW_PARAM_RATE, &priv->rate_constraints);
+}
+
+static int pcm9211_set_dai_sysclk(struct snd_soc_dai *dai,
+		int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec);
+	struct device *dev = codec->dev;
+
+	int ret;
+
+	if (freq > PCM9211_MAX_SYSCLK) {
+		dev_err(dev, "System clock greater %d is not supported\n",
+				PCM9211_MAX_SYSCLK);
+		return -EINVAL;
+	}
+
+	ret = clk_set_rate(priv->xti, freq);
+	if (ret)
+		return ret;
+
+	priv->sysclk = freq;
+
+	return 0;
+}
+
+static int pcm9211_set_dai_fmt(struct snd_soc_dai *dai,
+		unsigned int format)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec);
+	struct device *dev = codec->dev;
+	u32 adfmt, dirfmt;
+	int ret;
+
+	if (priv->dai_format != 0 && priv->dai_format != format) {
+		dev_err(dev, "Can not use different dai formats for dai links.\n");
+		return -EINVAL;
+	}
+
+	/* Configure format for ADC and DIR block, if main output source is
+	 * set to AUTO the output port may switch between them at any time
+	 */
+	switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		adfmt = PCM9211_ADFMT_I2S;
+		dirfmt = PCM9211_DIR_FMT_I2S;
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		adfmt = PCM9211_ADFMT_RIGHT_J;
+		dirfmt = PCM9211_DIR_FMT_RIGHT_J;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		adfmt = PCM9211_ADFMT_LEFT_J;
+		dirfmt = PCM9211_DIR_FMT_LEFT_J;
+		break;
+	default:
+		dev_err(dev, "Unsupported DAI format\n");
+		return -EINVAL;
+	}
+
+	ret = regmap_update_bits(priv->regmap, PCM9211_ADC_CTRL2,
+			PCM9211_ADFMT_MASK, adfmt << PCM9211_ADFMT_SHIFT);
+	if (ret) {
+		dev_err(dev, "Failed to update ADC format: %d\n", ret);
+		return ret;
+	}
+
+	ret = regmap_update_bits(priv->regmap, PCM9211_DIR_OUT_FMT,
+			PCM9211_DIR_FMT_MASK, dirfmt << PCM9211_DIR_FMT_SHIFT);
+	if (ret) {
+		dev_err(dev, "Failed to update ADC format: %d\n", ret);
+		return ret;
+	}
+
+	priv->dai_format = format;
+
+	return 0;
+}
+
+static int pcm9211_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params,
+		struct snd_soc_dai *dai)
+{
+	struct snd_soc_codec *codec = dai->codec;
+	struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec);
+	struct device *dev = codec->dev;
+	unsigned int sclk = 1;
+	unsigned int rate;
+	unsigned int bck;
+	unsigned int ratio;
+	unsigned int port;
+	int ret;
+	int i;
+
+	rate = params_rate(params);
+	bck = rate * 64;
+
+	port = pcm9211_get_output_port(dev, dai->id);
+	if (port == PCM9211_MOSRC_ADC) {
+		switch (rate) {
+		case 48000:
+			sclk = 12288000;
+			break;
+		case 96000:
+			sclk = 24576000;
+			break;
+		default:
+			dev_err(dev, "Rate %d unsupported.\n", rate);
+			return -EINVAL;
+		}
+
+		/* Systemclock setup */
+		ratio = priv->sysclk / sclk;
+		for (i = 0; i < PCM9211_NUM_SCK_RATIOS; i++) {
+			if (pcm9211_sck_ratios[i] == ratio)
+				break;
+		}
+		if (i == PCM9211_NUM_SCK_RATIOS) {
+			dev_err(dev, "SCK divider %d is not supported\n",
+					ratio);
+			return -EINVAL;
+		}
+		ret = regmap_update_bits(priv->regmap, PCM9211_XTI_SCLK_FREQ,
+				PCM9211_XTI_XSCK_MASK,
+				i << PCM9211_XTI_XSCK_SHIFT);
+
+		if (ret) {
+			dev_err(dev, "Failed to configure SCK divider: %d\n",
+					ret);
+			return ret;
+		}
+
+		/* Bitclock setup */
+		ratio = priv->sysclk / bck;
+		for (i = 0; i < PCM9211_NUM_BCK_RATIOS; i++) {
+			if (pcm9211_bck_ratios[i] == ratio)
+				break;
+		}
+		if (i == PCM9211_NUM_BCK_RATIOS) {
+			dev_err(dev, "BCK divider %d is not supported\n",
+					ratio);
+			return -EINVAL;
+		}
+		ret = regmap_update_bits(priv->regmap, PCM9211_XTI_SCLK_FREQ,
+				PCM9211_XTI_BCK_MASK,
+				i << PCM9211_XTI_BCK_SHIFT);
+		if (ret) {
+			dev_err(dev, "Failed to configure BCK divider: %d\n",
+					ret);
+			return ret;
+		}
+
+		/* Frameclock setup */
+		ratio = priv->sysclk / rate;
+		for (i = 0; i < PCM9211_NUM_LRCK_RATIOS; i++) {
+			if (pcm9211_lrck_ratios[i] == ratio)
+				break;
+		}
+		if (i == PCM9211_NUM_LRCK_RATIOS) {
+			dev_err(dev, "LRCK divider %d is not supported\n",
+					ratio);
+			return -EINVAL;
+		}
+		ret = regmap_update_bits(priv->regmap, PCM9211_XTI_SCLK_FREQ,
+				PCM9211_XTI_LRCK_MASK,
+				i << PCM9211_XTI_LRCK_SHIFT);
+		if (ret) {
+			dev_err(dev, "Failed to configure LRCK divider: %d\n",
+					ret);
+			return ret;
+		}
+
+		priv->adc_rate = rate;
+	}
+
+	return 0;
+}
+
+static int pcm9211_reset(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+	int ret;
+
+	/* Use reset gpio if available, otherwise soft-reset */
+	if (priv->reset) {
+		gpiod_set_value_cansleep(priv->reset, 0);
+		usleep_range(500, 1000);
+		gpiod_set_value_cansleep(priv->reset, 1);
+	} else {
+		ret = regmap_update_bits(priv->regmap, PCM9211_SYS_RESET,
+				PCM9211_SYS_RESET_MRST, 0);
+		if (ret) {
+			dev_err(dev, "Could not reset device: %d\n", ret);
+			return ret;
+		}
+		usleep_range(10000, 15000);
+	}
+
+	regcache_mark_dirty(priv->regmap);
+
+	return 0;
+}
+
+static int pcm9211_write_pinconfig(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+	u8 values[4];
+	int val;
+	int ret;
+	int i;
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,group-function",
+			values, 3);
+	if (!ret) {
+		val = (values[0] << PCM9211_MPASEL_SHIFT &
+				PCM9211_MPASEL_MASK) |
+			(values[1] << PCM9211_MPBSEL_SHIFT &
+				 PCM9211_MPBSEL_MASK) |
+			(values[2] << PCM9211_MPCSEL_SHIFT &
+				PCM9211_MPCSEL_MASK);
+		ret = regmap_write(priv->regmap, PCM9211_MPIO_GROUP, val);
+		if (ret) {
+			dev_err(dev, "Failed to write mpio group functions: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,mpio-a-flags-gpio",
+			values, 4);
+	if (!ret) {
+		/* Write MPIO A flags/gpio selection */
+		for (i = 0, val = 0; i < 4; i++)
+			val |= (values[i] << PCM9211_MPAxSEL_SHIFT(i)) &
+				PCM9211_MPAxSEL_MASK(i);
+
+		ret = regmap_update_bits(priv->regmap, PCM9211_MPIO_A_FLAGS,
+				PCM9211_MPAxSEL_MASK(0) |
+				PCM9211_MPAxSEL_MASK(1) |
+				PCM9211_MPAxSEL_MASK(2), val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_a flags: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,mpio-b-flags-gpio",
+			values, 4);
+	if (!ret) {
+		/* Write MPIO B flags/gpio selection */
+		for (i = 0, val = 0; i < 4; i++)
+			val |= (values[i] << PCM9211_MPBxSEL_SHIFT(i)) &
+				PCM9211_MPBxSEL_MASK(i);
+
+		ret = regmap_update_bits(priv->regmap,
+				PCM9211_MPIO_B_MPIO_C_FLAGS,
+				PCM9211_MPBxSEL_MASK(0) |
+				PCM9211_MPBxSEL_MASK(1) |
+				PCM9211_MPBxSEL_MASK(2), val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_a flags: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,mpio-c-flags-gpio",
+			values, 4);
+	if (!ret) {
+		/* Write MPIO B flags/gpio selection */
+		for (i = 0, val = 0; i < 4; i++)
+			val |= (values[i] << PCM9211_MPCxSEL_SHIFT(i)) &
+				PCM9211_MPCxSEL_MASK(i);
+
+		ret = regmap_update_bits(priv->regmap,
+				PCM9211_MPIO_B_MPIO_C_FLAGS,
+				PCM9211_MPCxSEL_MASK(0) |
+				PCM9211_MPCxSEL_MASK(1) |
+				PCM9211_MPCxSEL_MASK(2), val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_a flags: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,mpio-a-flag",
+			values, 4);
+	if (!ret) {
+		/* Write MPIO A flag selection */
+		for (i = 0, val = 0; i < 2; i++)
+			val |= (values[i] <<
+					PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) &
+				PCM9211_MPIO_ABCx_FLAG_MASK(i);
+		ret = regmap_write(priv->regmap, PCM9211_MPIO_A1_A0_OUT_FLAG,
+				val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_a1/0 flags: %d\n",
+					ret);
+			return ret;
+		}
+
+		for (i = 2, val = 0; i < 4; i++)
+			val |= (values[i] <<
+					PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) &
+				PCM9211_MPIO_ABCx_FLAG_MASK(i);
+		ret = regmap_write(priv->regmap, PCM9211_MPIO_A3_A2_OUT_FLAG,
+				val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_a3/2 flags: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,mpio-b-flag",
+			values, 4);
+	if (!ret) {
+		/* Write MPIO B flag selection */
+		for (i = 0, val = 0; i < 2; i++)
+			val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) &
+				PCM9211_MPIO_ABCx_FLAG_MASK(i);
+		ret = regmap_write(priv->regmap, PCM9211_MPIO_B1_B0_OUT_FLAG,
+				val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_b1/0 flags: %d\n",
+					ret);
+			return ret;
+		}
+
+		for (i = 2, val = 0; i < 4; i++)
+			val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) &
+				PCM9211_MPIO_ABCx_FLAG_MASK(i);
+		ret = regmap_write(priv->regmap, PCM9211_MPIO_B3_B2_OUT_FLAG,
+				val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_b3/2 flags: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,mpio-c-flag",
+				  values, 4);
+	if (!ret) {
+		/* Write MPIO C flag selection */
+		for (i = 0, val = 0; i < 2; i++)
+			val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) &
+				PCM9211_MPIO_ABCx_FLAG_MASK(i);
+		ret = regmap_write(priv->regmap, PCM9211_MPIO_C1_C0_OUT_FLAG,
+				val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_c1/0 flags: %d\n",
+					ret);
+			return ret;
+		}
+
+		for (i = 2, val = 0; i < 4; i++)
+			val |= (values[i] << PCM9211_MPIO_ABCx_FLAG_SHIFT(i)) &
+				PCM9211_MPIO_ABCx_FLAG_MASK(i);
+		ret = regmap_write(priv->regmap, PCM9211_MPIO_C3_C2_OUT_FLAG,
+				val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpio_c3/2 flags: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8_array(dev->of_node, "ti,mpo-function",
+			values, 2);
+	if (!ret) {
+		/* Write MPO function selection */
+		for (i = 0, val = 0; i < 2; i++)
+			val |= (values[i] << PCM9211_MPOxOUT_SHIFT(i)) &
+				PCM9211_MPOxOUT_MASK(i);
+		ret = regmap_write(priv->regmap, PCM9211_MPO_1_0_FUNC, val);
+		if (ret) {
+			dev_err(dev, "Failed to update mpo function selection: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8(dev->of_node, "ti,int0-function", values);
+	if (!ret) {
+		val = values[0] ? PCM9211_ERROR_INT0_MASK : 0;
+		ret = regmap_update_bits(priv->regmap, PCM9211_ERR_OUT,
+				PCM9211_ERROR_INT0_MASK, val);
+		if (ret) {
+			dev_err(dev, "Failed to update int0 function selection: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = of_property_read_u8(dev->of_node, "ti,int1-function", values);
+	if (!ret) {
+		val = values[0] ? PCM9211_NPCM_INT1_MASK : 0;
+		ret = regmap_update_bits(priv->regmap, PCM9211_ERR_OUT,
+				PCM9211_NPCM_INT1_MASK, val);
+		if (ret) {
+			dev_err(dev, "Failed to update int1 function selection: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void pcm9211_npcm_clear_work(struct work_struct *work)
+{
+	struct pcm9211_priv *priv = container_of(work, struct pcm9211_priv,
+			npcm_clear_work.work);
+	u8 old_state = priv->npcm_state;
+	struct device *dev = priv->dev;
+
+	/* Clear NPCM & DTSCD, as DTSCD is only valid as long as NPCM is */
+	priv->npcm_state &= ~(PCM9211_INT0_MNPCM0_MASK |
+			PCM9211_INT0_MDTSCD0_MASK);
+
+	dev_dbg(dev, "Clear NPCM flag after timeout\n");
+
+	if (priv->codec == NULL || pcm9211_get_dtscd_ctl(dev) == NULL ||
+			pcm9211_get_npcm_ctl(dev) == NULL)
+		return;
+
+	if (old_state & PCM9211_INT0_MNPCM0_MASK)
+		snd_ctl_notify(priv->codec->component.card->snd_card,
+				SNDRV_CTL_EVENT_MASK_VALUE,
+				&pcm9211_get_npcm_ctl(dev)->id);
+
+	if (old_state & PCM9211_INT0_MDTSCD0_MASK)
+		snd_ctl_notify(priv->codec->component.card->snd_card,
+				SNDRV_CTL_EVENT_MASK_VALUE,
+				&pcm9211_get_dtscd_ctl(dev)->id);
+}
+
+static int pcm9211_soc_probe(struct snd_soc_codec *codec)
+{
+	struct pcm9211_priv *priv = snd_soc_codec_get_drvdata(codec);
+
+	priv->codec = codec;
+
+	return 0;
+}
+
+/* Simple Controls */
+static const DECLARE_TLV_DB_SCALE(pcm9211_adc_tlv, -10050, 50, 1);
+static const char *const pcm9211_main_outputs[] = { "AUTO", "DIR", "ADC",
+	"AUXIN0", "AUXIN1", "AUXIN2" };
+static const struct soc_enum pcm9211_main_sclk_enum =
+	SOC_ENUM_SINGLE(PCM9211_MAIN_OUT_SOURCE, 4, 6, pcm9211_main_outputs);
+static const struct soc_enum pcm9211_aux_sclk_enum =
+	SOC_ENUM_SINGLE(PCM9211_AUX_OUT_SOURCE, 4, 5, pcm9211_main_outputs);
+
+static const struct snd_kcontrol_new pcm9211_snd_controls[] = {
+	SOC_DOUBLE_R_RANGE_TLV("ADC Attenuation",
+			PCM9211_ADC_L_CH_ATT,
+			PCM9211_ADC_R_CH_ATT,
+			0, 14, 255, 0, pcm9211_adc_tlv),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "DIR Sample Rate",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = pcm9211_dir_rate_kctl_info,
+		.get = pcm9211_dir_rate_kctl,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "DIR Non-PCM Bitstream",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = pcm9211_dir_npcm_kctl_info,
+		.get = pcm9211_dir_npcm_kctl,
+		.private_value = PCM9211_INT0_MNPCM0_MASK,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "DIR DTS Bitstream",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = pcm9211_dir_dtscd_kctl_info,
+		.get = pcm9211_dir_dtscd_kctl,
+		.private_value = PCM9211_INT0_MDTSCD0_MASK,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
+		.name = "DIR Burst Preamble",
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.info = pcm9211_dir_preamble_kctl_info,
+		.get = pcm9211_dir_preamble_kctl,
+	},
+	SOC_ENUM("MAIN SCLK Output Select", pcm9211_main_sclk_enum),
+	SOC_ENUM("AUX SCLK Output Select", pcm9211_aux_sclk_enum),
+};
+
+/* DAPM Controls */
+static const char *const pcm9211_dir_inputs[] = { "RXIN0", "RXIN1", "RXIN2",
+	"RXIN3", "RXIN4", "RXIN5", "RXIN6", "RXIN7" };
+static const struct soc_enum pcm9211_dir_mux_enum =
+	SOC_ENUM_SINGLE(PCM9211_DIR_INP_BIPHASE, 0, 8, pcm9211_dir_inputs);
+static const struct snd_kcontrol_new pcm9211_dir_mux_control =
+	SOC_DAPM_ENUM("DIR Input Select", pcm9211_dir_mux_enum);
+
+static const struct soc_enum pcm9211_main_out_enum =
+	SOC_ENUM_SINGLE(PCM9211_MAIN_OUT_SOURCE, 0, 6, pcm9211_main_outputs);
+static const struct snd_kcontrol_new pcm9211_main_out_control =
+	SOC_DAPM_ENUM("MAIN Output Select", pcm9211_main_out_enum);
+
+static const struct soc_enum pcm9211_aux_out_enum =
+	SOC_ENUM_SINGLE(PCM9211_AUX_OUT_SOURCE, 0, 5, pcm9211_main_outputs);
+static const struct snd_kcontrol_new pcm9211_aux_out_control =
+	SOC_DAPM_ENUM("AUX Output Select", pcm9211_aux_out_enum);
+
+/* DAPM widgets */
+static const struct snd_soc_dapm_widget pcm9211_dapm_widgets[] = {
+	/* Inputs */
+	SND_SOC_DAPM_INPUT("RXIN0"),
+	SND_SOC_DAPM_INPUT("RXIN1"),
+	SND_SOC_DAPM_INPUT("RXIN2"),
+	SND_SOC_DAPM_INPUT("RXIN3"),
+	SND_SOC_DAPM_INPUT("RXIN4"),
+	SND_SOC_DAPM_INPUT("RXIN5"),
+	SND_SOC_DAPM_INPUT("RXIN6"),
+	SND_SOC_DAPM_INPUT("RXIN7"),
+	SND_SOC_DAPM_INPUT("VINL"),
+	SND_SOC_DAPM_INPUT("VINR"),
+
+	SND_SOC_DAPM_ADC("ADC", NULL, PCM9211_SYS_RESET,
+			PCM9211_SYS_RESET_ADDIS_SHIFT, 1),
+
+	/* Processing */
+	SND_SOC_DAPM_AIF_IN("DIR", NULL, 0, PCM9211_SYS_RESET,
+			PCM9211_SYS_RESET_RXDIS_SHIFT, 1),
+	SND_SOC_DAPM_MIXER("AUTO", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	/* Internal routing */
+	SND_SOC_DAPM_MUX("DIR Input Mux", SND_SOC_NOPM, 0, 0,
+			&pcm9211_dir_mux_control),
+	SND_SOC_DAPM_MUX("MAIN Output Mux", SND_SOC_NOPM, 0, 0,
+			&pcm9211_main_out_control),
+	SND_SOC_DAPM_MUX("AUX Output Mux", SND_SOC_NOPM, 0, 0,
+			&pcm9211_aux_out_control),
+
+	/* Outputs */
+	SND_SOC_DAPM_OUTPUT("MAIN"),
+	SND_SOC_DAPM_OUTPUT("AUX"),
+};
+
+/* DAPM Routing */
+static const struct snd_soc_dapm_route pcm9211_dapm_routes[] = {
+	{ "DIR Input Mux", "RXIN0", "RXIN0" },
+	{ "DIR Input Mux", "RXIN1", "RXIN1" },
+	{ "DIR Input Mux", "RXIN2", "RXIN2" },
+	{ "DIR Input Mux", "RXIN3", "RXIN3" },
+	{ "DIR Input Mux", "RXIN4", "RXIN4" },
+	{ "DIR Input Mux", "RXIN5", "RXIN5" },
+	{ "DIR Input Mux", "RXIN6", "RXIN6" },
+	{ "DIR Input Mux", "RXIN7", "RXIN7" },
+
+	{ "ADC", NULL, "VINL" },
+	{ "ADC", NULL, "VINR" },
+
+	{ "DIR", NULL, "DIR Input Mux" },
+	{ "AUTO", NULL, "DIR" },
+	{ "AUTO", NULL, "ADC" },
+
+	{ "MAIN Output Mux", "DIR", "DIR" },
+	{ "MAIN Output Mux", "ADC", "ADC" },
+	{ "MAIN Output Mux", "AUTO", "AUTO" },
+
+	{ "AUX Output Mux", "DIR", "DIR" },
+	{ "AUX Output Mux", "ADC", "ADC" },
+	{ "AUX Output Mux", "AUTO", "AUTO" },
+
+	{ "MAIN", NULL, "MAIN Output Mux" },
+	{ "AUX", NULL, "AUX Output Mux" },
+
+	{ "MAIN Capture", NULL, "MAIN" },
+	{ "AUX Capture", NULL, "AUX" },
+};
+
+static struct snd_soc_dai_ops pcm9211_dai_ops = {
+	.startup = pcm9211_startup,
+	.hw_params = pcm9211_hw_params,
+	.set_sysclk = pcm9211_set_dai_sysclk,
+	.set_fmt = pcm9211_set_dai_fmt,
+};
+
+/* BCLK is always 64 * FS == 32 bit/channel */
+#define PCM9211_FORMATS SNDRV_PCM_FMTBIT_S32_LE
+struct snd_soc_dai_driver pcm9211_dai[] = {
+	{
+		.name = "pcm9211-main-hifi",
+		.id = PCM9211_DAI_MAIN,
+		.capture = {
+			.stream_name = "MAIN Capture",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_192000,
+			.formats = PCM9211_FORMATS,
+		},
+		.ops = &pcm9211_dai_ops,
+	},
+	{
+		.name = "pcm9211-aux-hifi",
+		.id = PCM9211_DAI_AUX,
+		.capture = {
+			.stream_name = "AUX Capture",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_8000_192000,
+			.formats = PCM9211_FORMATS,
+		},
+		.ops = &pcm9211_dai_ops,
+	},
+};
+
+static const struct snd_soc_codec_driver pcm9211_driver = {
+	.probe = pcm9211_soc_probe,
+	.component_driver = {
+		.controls = pcm9211_snd_controls,
+		.num_controls = ARRAY_SIZE(pcm9211_snd_controls),
+		.dapm_widgets = pcm9211_dapm_widgets,
+		.num_dapm_widgets = ARRAY_SIZE(pcm9211_dapm_widgets),
+		.dapm_routes = pcm9211_dapm_routes,
+		.num_dapm_routes = ARRAY_SIZE(pcm9211_dapm_routes),
+	},
+};
+
+int pcm9211_probe(struct device *dev, struct regmap *regmap)
+{
+	struct pcm9211_priv *priv;
+	unsigned int cause;
+	int ret;
+	int i;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (priv == NULL)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, priv);
+	priv->dev = dev;
+	priv->regmap = regmap;
+
+	priv->xti = devm_clk_get(dev, "xti");
+	if (IS_ERR(priv->xti)) {
+		ret = PTR_ERR(priv->xti);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "Failed to get clock 'xti': %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(priv->xti);
+	if (ret) {
+		dev_err(dev, "Failed to enable xti clock: %d\n", ret);
+		return ret;
+	}
+
+	priv->sysclk = clk_get_rate(priv->xti);
+	if (priv->sysclk > PCM9211_MAX_SYSCLK) {
+		dev_err(dev, "xti clock rate (%lu) exceeds supported max %u\n",
+				priv->sysclk, PCM9211_MAX_SYSCLK);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(priv->supplies); i++)
+		priv->supplies[i].supply = pcm9211_supply_names[i];
+
+	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(priv->supplies),
+			priv->supplies);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "Failed to get supplies: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies),
+			priv->supplies);
+	if (ret) {
+		dev_err(dev, "Failed to enable supplies: %d\n", ret);
+		return ret;
+	}
+
+	priv->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(priv->reset)) {
+		ret = PTR_ERR(priv->reset);
+		dev_err(dev, "Failed to get reset gpio: %d\n", ret);
+		return ret;
+	}
+
+	pcm9211_reset(dev);
+
+	priv->int0 = devm_gpiod_get_optional(dev, "int0", GPIOD_IN);
+	if (IS_ERR(priv->int0)) {
+		ret = PTR_ERR(priv->int0);
+		dev_err(dev, "Failed to get int0 gpio: %d\n", ret);
+		return ret;
+	}
+
+	if (priv->int0) {
+		int irq = gpiod_to_irq(priv->int0);
+
+		if (irq < 0) {
+			dev_err(dev, "Configured 'int0' gpio cannot be used as IRQ: %d\n",
+					irq);
+			return irq;
+		}
+
+		INIT_DELAYED_WORK(&priv->npcm_clear_work,
+				pcm9211_npcm_clear_work);
+		ret = devm_request_threaded_irq(dev, irq, NULL,
+				pcm9211_interrupt,
+				IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+				"pcm9211", priv);
+		if (ret) {
+			dev_err(dev, "Failed to request irq: %d\n", ret);
+			return ret;
+		}
+
+		/* Set interrupt to use positive polarity */
+		ret = regmap_update_bits(priv->regmap, PCM9211_INT_POLARITY,
+				PCM9211_INT0_POLARITY_POS_MASK,
+				PCM9211_INT0_POLARITY_POS_MASK);
+		if (ret) {
+			dev_err(dev, "Failed to configure int0 polaroty: %d\n",
+					ret);
+			return ret;
+		}
+	}
+
+	ret = pcm9211_write_pinconfig(dev);
+	if (ret)
+		return ret;
+
+	/* Unmap NPCM, DTS, Burst Preamble and Fs change interrupt */
+	ret = regmap_update_bits(priv->regmap, PCM9211_INT0_CAUSE,
+			PCM9211_INT0_MNPCM0_MASK | PCM9211_INT0_MDTSCD0_MASK |
+			PCM9211_INT0_MPCRNW0_MASK | PCM9211_INT0_MFSCHG0_MASK,
+			0);
+	if (ret) {
+		dev_err(dev, "Failed to unmask interrupt causes: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable DTSCD detection */
+	ret = regmap_update_bits(priv->regmap, PCM9211_NON_PCM_DEF,
+			PCM9211_NON_PCM_DTS_CD_DET_MASK,
+			PCM9211_NON_PCM_DTS_CD_DET_MASK);
+	if (ret) {
+		dev_err(dev, "Failed to enable DTSCD detection: %d\n", ret);
+		return ret;
+	}
+
+	/* Read initial sampling rate and npcm state */
+	priv->dir_rate = pcm9211_dir_rate(dev);
+	ret = regmap_read(priv->regmap, PCM9211_INT0_OUT, &cause);
+	if (ret) {
+		dev_err(dev, "Failed to read int0 cause: %d\n", ret);
+		return IRQ_HANDLED;
+	}
+	priv->npcm_state = cause;
+
+	ret = snd_soc_register_codec(dev, &pcm9211_driver, pcm9211_dai,
+			ARRAY_SIZE(pcm9211_dai));
+	if (ret) {
+		dev_err(dev, "Failed to register codec: %d\n", ret);
+		return ret;
+	}
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pcm9211_probe);
+
+void pcm9211_remove(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+
+	snd_soc_unregister_codec(dev);
+	pm_runtime_disable(dev);
+	regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies);
+	clk_disable_unprepare(priv->xti);
+}
+EXPORT_SYMBOL_GPL(pcm9211_remove);
+
+#ifdef CONFIG_PM
+static int pcm9211_runtime_resume(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+	int ret;
+
+	ret = clk_prepare_enable(priv->xti);
+	if (ret) {
+		dev_err(dev, "Failed to enable xti clock: %d\n", ret);
+		return ret;
+	}
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies);
+	if (ret) {
+		dev_err(dev, "Failed to enable supplies: %d\n", ret);
+		goto err_reg;
+	}
+
+	ret = pcm9211_reset(dev);
+	if (ret) {
+		dev_err(dev, "Failed to reset device: %d\n", ret);
+		goto err;
+	}
+
+	regcache_cache_only(priv->regmap, false);
+	regcache_mark_dirty(priv->regmap);
+
+	ret = regcache_sync(priv->regmap);
+	if (ret) {
+		dev_err(dev, "Failed to sync regmap: %d\n", ret);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies);
+err_reg:
+	clk_disable_unprepare(priv->xti);
+
+	return ret;
+}
+
+static int pcm9211_runtime_suspend(struct device *dev)
+{
+	struct pcm9211_priv *priv = dev_get_drvdata(dev);
+
+	regcache_cache_only(priv->regmap, true);
+	regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies);
+	clk_disable_unprepare(priv->xti);
+
+	return 0;
+}
+#endif
+
+const struct dev_pm_ops pcm9211_pm_ops = {
+	SET_RUNTIME_PM_OPS(pcm9211_runtime_suspend, pcm9211_runtime_resume,
+			NULL)
+};
+EXPORT_SYMBOL_GPL(pcm9211_pm_ops);
+
+MODULE_DESCRIPTION("PCM9211 codec driver");
+MODULE_AUTHOR("Julian Scheel <julian@jusst.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/pcm9211.h b/sound/soc/codecs/pcm9211.h
new file mode 100644
index 000000000000..3ad9e9e9419a
--- /dev/null
+++ b/sound/soc/codecs/pcm9211.h
@@ -0,0 +1,206 @@ 
+/*
+ * PCM9211 codec driver header
+ *
+ * Copyright (C) 2017 jusst technologies GmbH / jusst.engineering
+ *
+ * Author: Julian Scheel <julian@jusst.de>
+ *
+ * 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.
+ */
+
+#ifndef __PCM9211_H_
+#define __PCM9211_H_
+
+extern const struct dev_pm_ops pcm9211_pm_ops;
+extern const struct regmap_config pcm9211_regmap;
+
+extern int pcm9211_probe(struct device *dev, struct regmap *regmap);
+extern void pcm9211_remove(struct device *dev);
+
+/* Register definitions */
+#define PCM9211_ERR_OUT			0x20
+#define PCM9211_DIR_INITIAL1		0x21
+#define PCM9211_DIR_INITIAL2		0x22
+#define PCM9211_DIR_INITIAL3		0x23
+#define PCM9211_OSC_CTRL		0x24
+#define PCM9211_ERR_CAUSE		0x25
+#define PCM9211_AUTO_SEL_CAUSE		0x26
+#define PCM9211_DIR_FS_RANGE		0x27
+#define PCM9211_NON_PCM_DEF		0x28
+#define PCM9211_DTS_CD_LD		0x29
+#define PCM9211_INT0_CAUSE		0x2a
+#define PCM9211_INT1_CAUSE		0x2b
+#define PCM9211_INT0_OUT		0x2c
+#define PCM9211_INT1_OUT		0x2d
+#define PCM9211_INT_POLARITY		0x2e
+#define PCM9211_DIR_OUT_FMT		0x2f
+#define PCM9211_DIR_RSCLK_RATIO		0x30
+#define PCM9211_XTI_SCLK_FREQ		0x31
+#define PCM9211_DIR_SOURCE_BIT2		0x32
+#define PCM9211_XTI_SOURCE_BIT2		0x33
+#define PCM9211_DIR_INP_BIPHASE		0x34
+#define PCM9211_RECOUT0_BIPHASE		0x35
+#define PCM9211_RECOUT1_BIPHASE		0x36
+#define PCM9211_FS_CALC_TARGET		0x37
+#define PCM9211_FS_CALC_RESULT		0x38
+#define PCM9211_BIPHASE_INFO		0x39
+#define PCM9211_PC_BUF0			0x3a
+#define PCM9211_PC_BUF1			0x3b
+#define PCM9211_PD_BUF0			0x3c
+#define PCM9211_PD_BUF1			0x3d
+
+#define PCM9211_SYS_RESET		0x40
+
+#define PCM9211_ADC_CTRL1		0x42
+
+#define PCM9211_ADC_L_CH_ATT		0x46
+#define PCM9211_ADC_R_CH_ATT		0x47
+#define PCM9211_ADC_CTRL2		0x48
+#define PCM9211_ADC_CTRL3		0x49
+
+#define PCM9211_DIR_STATUS1		0x5a
+#define PCM9211_DIR_STATUS2		0x5b
+#define PCM9211_DIR_STATUS3		0x5c
+#define PCM9211_DIR_STATUS4		0x5d
+#define PCM9211_DIR_STATUS5		0x5e
+#define PCM9211_DIR_STATUS6		0x5f
+#define PCM9211_DIT_CTRL1		0x60
+#define PCM9211_DIT_CTRL2		0x61
+#define PCM9211_DIT_CTRL3		0x62
+#define PCM9211_DIT_STATUS1		0x63
+#define PCM9211_DIT_STATUS2		0x64
+#define PCM9211_DIT_STATUS3		0x65
+#define PCM9211_DIT_STATUS4		0x66
+#define PCM9211_DIT_STATUS5		0x67
+#define PCM9211_DIT_STATUS6		0x68
+
+#define PCM9211_MAIN_AUX_MUTE		0x6a
+#define PCM9211_MAIN_OUT_SOURCE		0x6b
+#define PCM9211_AUX_OUT_SOURCE		0x6c
+#define PCM9211_MPIO_B_MAIN_HIZ		0x6d
+#define PCM9211_MPIO_C_MPIO_A_HIZ	0x6e
+#define PCM9211_MPIO_GROUP		0x6f
+#define PCM9211_MPIO_A_FLAGS		0x70
+#define PCM9211_MPIO_B_MPIO_C_FLAGS	0x71
+#define PCM9211_MPIO_A1_A0_OUT_FLAG	0x72
+#define PCM9211_MPIO_A3_A2_OUT_FLAG	0x73
+#define PCM9211_MPIO_B1_B0_OUT_FLAG	0x74
+#define PCM9211_MPIO_B3_B2_OUT_FLAG	0x75
+#define PCM9211_MPIO_C1_C0_OUT_FLAG	0x76
+#define PCM9211_MPIO_C3_C2_OUT_FLAG	0x77
+#define PCM9211_MPO_1_0_FUNC		0x78
+#define PCM9211_MPIO_A_B_DIR		0x79
+#define PCM9211_MPIO_C_DIR		0x7a
+#define PCM9211_MPIO_A_B_DATA_OUT	0x7b
+#define PCM9211_MPIO_C_DATA_OUT		0x7c
+#define PCM9211_MPIO_A_B_DATA_IN	0x7d
+#define PCM9211_MPIO_C_DATA_IN		0x7e
+
+
+/* Register field values (only used ones) */
+
+/* PCM9211_ERR_OUT */
+#define PCM9211_ERROR_INT0_MASK		BIT(2)
+#define PCM9211_NPCM_INT1_MASK		BIT(0)
+
+/* PCM9211_NON_PCM_DEF */
+#define PCM9211_NON_PCM_DTS_CD_DET_MASK	BIT(2)
+
+/* PCM9211_INT0_CAUSE */
+#define PCM9211_INT0_MNPCM0_MASK	BIT(6)
+#define PCM9211_INT0_MDTSCD0_MASK	BIT(4)
+#define PCM9211_INT0_MPCRNW0_MASK	BIT(2)
+#define PCM9211_INT0_MFSCHG0_MASK	BIT(1)
+
+/* PCM9211_INT_POLARITY	*/
+#define PCM9211_INT0_POLARITY_POS_MASK	BIT(2)
+
+/* PCM9211_DIR_OUT_FMT */
+#define PCM9211_DIR_FMT_MASK		0x7
+#define PCM9211_DIR_FMT_SHIFT		0
+
+#define PCM9211_DIR_FMT_I2S		4
+#define PCM9211_DIR_FMT_LEFT_J		5
+#define PCM9211_DIR_FMT_RIGHT_J		0
+
+/* PCM9211_XTI_SCLK_FREQ */
+#define PCM9211_XTI_XSCK_SHIFT		4
+#define PCM9211_XTI_XSCK_MASK		(0x3 << PCM9211_XTI_XSCK_SHIFT)
+#define PCM9211_XTI_BCK_SHIFT		2
+#define PCM9211_XTI_BCK_MASK		(0x3 << PCM9211_XTI_BCK_SHIFT)
+#define PCM9211_XTI_LRCK_SHIFT		0
+#define PCM9211_XTI_LRCK_MASK		(0x3 << PCM9211_XTI_LRCK_SHIFT)
+
+
+/* PCM9211_BIPHASE_INFO */
+#define PCM9211_BIPHASE_SFSST_SHIFT	7
+#define PCM9211_BIPHASE_SFSST_MASK	BIT(PCM9211_BIPHASE_SFSST_SHIFT)
+#define PCM9211_BIPHASE_SFSOUT_SHIFT	0
+#define PCM9211_BIPHASE_SFSOUT_MASK	(0xf << PCM9211_BIPHASE_SFSOUT_SHIFT)
+
+
+/* PCM9211_SYS_RESET */
+#define PCM9211_SYS_RESET_MRST		BIT(7)
+#define PCM9211_SYS_RESET_RXDIS_SHIFT	4
+#define PCM9211_SYS_RESET_ADDIS_SHIFT	5
+
+
+/* PCM9211_ADC_CTRL2 */
+#define PCM9211_ADFMT_MASK		0x3
+#define PCM9211_ADFMT_SHIFT		0
+
+#define PCM9211_ADFMT_I2S		0
+#define PCM9211_ADFMT_LEFT_J		1
+#define PCM9211_ADFMT_RIGHT_J		2
+
+
+/* PCM9211_MAIN_OUT_SOURCE */
+#define PCM9211_MOSSRC_SHIFT		4
+#define PCM9211_MOPSRC_SHIFT		0
+#define PCM9211_MOSRC_MASK		0x7
+#define PCM9211_MOSSRC_MASK		\
+	(PCM9211_MOSRC_MASK << PCM9211_MOSSRC_SHIFT)
+#define PCM9211_MOPSRC_MASK		\
+	(PCM9211_MOSRC_MASK << PCM9211_MOPSRC_SHIFT)
+
+#define PCM9211_MOSRC_AUTO		0
+#define PCM9211_MOSRC_DIR		1
+#define PCM9211_MOSRC_ADC		2
+#define PCM9211_MOSRC_AUXIN0		3
+#define PCM9211_MOSRC_AUXIN1		4
+#define PCM9211_MOSRC_AUXIN2		5
+
+
+/* PCM9211_MPIO_GROUP */
+#define PCM9211_MPASEL_SHIFT		6
+#define PCM9211_MPASEL_MASK		(0x2 << PCM9211_MPASEL_SHIFT)
+#define PCM9211_MPBSEL_SHIFT		3
+#define PCM9211_MPBSEL_MASK		(0x7 << PCM9211_MPBSEL_SHIFT)
+#define PCM9211_MPCSEL_SHIFT		0
+#define PCM9211_MPCSEL_MASK		(0x7 << PCM9211_MPCSEL_SHIFT)
+
+
+/* PCM9211_MPIO_A_FLAGS */
+#define PCM9211_MPAxSEL_SHIFT(x)	(x)
+#define PCM9211_MPAxSEL_MASK(x)		BIT(PCM9211_MPAxSEL_SHIFT(x))
+
+
+/* PCM9211_MPIO_B_MPIO_C_FLAGS */
+#define PCM9211_MPBxSEL_SHIFT(x)	((x) + 4)
+#define PCM9211_MPBxSEL_MASK(x)		BIT(PCM9211_MPBxSEL_SHIFT(x))
+#define PCM9211_MPCxSEL_SHIFT(x)	(x)
+#define PCM9211_MPCxSEL_MASK(x)		BIT(PCM9211_MPCxSEL_SHIFT(x))
+
+
+/* PCM9211_MPIO_A1_A0_OUT_FLAG..PCM9211_MPIO_C3_C2_OUT_FLAG */
+#define PCM9211_MPIO_ABCx_FLAG_SHIFT(x)	(((x) % 2) * 4)
+#define PCM9211_MPIO_ABCx_FLAG_MASK(x)	(0xf << PCM9211_MPIO_ABCx_FLAG_SHIFT(x))
+
+
+/* PCM9211_MPO_1_0_FUNC */
+#define PCM9211_MPOxOUT_SHIFT(x)	((x) * 4)
+#define PCM9211_MPOxOUT_MASK(x)		(0xf << PCM9211_MPOxOUT_SHIFT(x))
+
+#endif