stab
diff --git a/arch/arm/boot/dts/qcom-apq8060-dragonboard.dts b/arch/arm/boot/dts/qcom-apq8060-dragonboard.dts
index 4e6c50d..39e8b71 100644
--- a/arch/arm/boot/dts/qcom-apq8060-dragonboard.dts
+++ b/arch/arm/boot/dts/qcom-apq8060-dragonboard.dts
@@ -102,6 +102,84 @@
 		pinctrl-0 = <&dragon_cm3605_gpios>, <&dragon_cm3605_mpps>;
 	};
 
+	/*
+	 * The audio routing is board specific so we need to probe and manage
+	 * a specific sound card just for the APQ8060 with WM8903 board.
+	 * On my board, testpoint TP73 (WS) is connected to the WM8903 DACDAT
+	 * signal with a green soldered-on wire and R30 (MCLK) on the main
+	 * board.
+	 */
+	sound: sound {
+		compatible = "qcom,msm8660-wm8903-sndcard";
+		qcom,model = "Dragonboard APQ8060";
+
+		/*
+		 * IN3L and IN3R are used as mono input for headset/headphone
+		 * mic and handset mic respectively. Both use the MICBIAS
+		 * output to biasing the microphone.
+		 */
+		qcom,audio-routing =
+			"Headphone", "LINEOUTL",
+			"Headphone", "LINEOUTR",
+			"Headset Mic", "MICBIAS",
+			"Handset", "HPOUTL",
+			"Handset", "HPOUTR",
+			"Handset Mic", "MICBIAS",
+			"IN3L", "Headset Mic",
+			"IN3R", "Handset Mic";
+
+		/*
+		 * External speaker power amplifier GPIOs called
+		 * D0 (left) and D1 (right). Each PA is a
+		 * ON semiconductor NLMD5820.
+		 */
+		qcom,spkr-pa-left-gpios = <&tlmm 80 GPIO_ACTIVE_HIGH>;
+		qcom,spkr-pa-right-gpios = <&tlmm 81 GPIO_ACTIVE_HIGH>;
+
+		/*
+		 * On the schematic, GPIO 108 is confusingly named
+		 * "WLAN_RST_N" but apparently not routed for that function,
+		 * but used to enable a clock gate to the MCLK.
+		 */
+		qcom,mclk-gate-gpios = <&tlmm 108 GPIO_ACTIVE_HIGH>;
+
+		/*
+		 * The I2S MIC OSR CLK drives MCLK to the WM8903 codec
+		 * and the I2S SPKR bit clock drives the actual I2S to the
+		 * codec.
+		 */
+		clocks = <&lcc CODEC_I2S_MIC_OSR_CLK>,
+			 <&lcc CODEC_I2S_MIC_BIT_CLK>,
+			 <&lcc CODEC_I2S_SPKR_OSR_CLK>,
+			 <&lcc CODEC_I2S_SPKR_BIT_CLK>;
+		clock-names = "codec-i2s-mic-osr-clk",
+			      "codec-i2s-mic-bit-clk",
+			      "codec-i2s-spkr-osr-clk",
+			      "codec-i2s-spkr-bit-clk";
+
+		external-codec-playback-dai-link@0 {
+			link-name = "WM8903 codec playback";
+			cpu {
+				sound-dai = <&lpass CODEC_I2S_SPKR>;
+			};
+			codec {
+				sound-dai = <&wm8903>;
+			};
+		};
+
+		external-codec-record-dai-link@1 {
+			link-name = "WM8903 codec record";
+			cpu {
+				sound-dai = <&lpass CODEC_I2S_MIC>;
+			};
+			codec {
+				sound-dai = <&wm8903>;
+			};
+		};
+
+		/* FIXME: add FM radio on MI2S and Bluetooth on PCM */
+	};
+
 	soc {
 		pinctrl@800000 {
 			/* eMMMC pins, all 8 data lines connected */
@@ -260,6 +338,29 @@
 					bias-pull-up;
 				};
 			};
+
+			/*
+			 * Muxings for the Low-power Audio Subsystem
+			 * The default I2S output/input lines (dedicated pins)
+			 * are routed to the Wolfson codec.
+			 */
+			dragon_lpass_pins: lpass {
+				/* MI2S on these lines is routed to the WCN2243 FM radio I2S input */
+				mux0 {
+					pins = "gpio101", /* FM_I2S_WS (bit clock) */
+					       "gpio102", /* FM_I2S_SCK (word select) */
+					       "gpio107"; /* FM_I2S_SD (SD3, data) */
+					function = "mi2s";
+				};
+				/* The mono PCM channel on these lines is routed to the WCN2243 Bluetooth */
+				mux1 {
+					pins = "gpio111", /* BT_PCM_DOUT */
+					       "gpio112", /* BT_PCM_DIN */
+					       "gpio113", /* BT_PCM_SYNC */
+					       "gpio114"; /* BT_PCM_CLK */
+					function = "pcm";
+				};
+			};
 		};
 
 		qcom,ssbi@500000 {
@@ -479,6 +580,7 @@
 				wm8903: wm8903@1a {
 					/* This Woolfson Micro device has an unrouted interrupt line */
 					compatible = "wlf,wm8903";
+					#sound-dai-cells = <0>;
 					reg = <0x1a>;
 
 					AVDD-supply = <&pm8058_l16>;
@@ -956,5 +1058,13 @@
 				vqmmc-supply = <&dragon_vio_txb>;
 			};
 		};
+
+		/* Low-power Audio Subsystem (LPASS) I2S and PCM output */
+		lpass: lpass@28100000 {
+			status = "okay";
+			pinctrl-names = "default";
+			pinctrl-0 = <&dragon_lpass_pins>;
+			qcom,slave-channels = <CODEC_I2S_SPKR>;
+		};
 	};
 };
diff --git a/arch/arm/boot/dts/qcom-msm8660.dtsi b/arch/arm/boot/dts/qcom-msm8660.dtsi
index ec5cbc4..ee26f2b 100644
--- a/arch/arm/boot/dts/qcom-msm8660.dtsi
+++ b/arch/arm/boot/dts/qcom-msm8660.dtsi
@@ -4,6 +4,8 @@
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/interrupt-controller/arm-gic.h>
 #include <dt-bindings/clock/qcom,gcc-msm8660.h>
+#include <dt-bindings/clock/qcom,lcc-msm8660.h>
+#include <dt-bindings/sound/msm8x60-lpass.h>
 #include <dt-bindings/soc/qcom,gsbi.h>
 
 / {
@@ -575,6 +577,51 @@
 			compatible = "qcom,tcsr-msm8660", "syscon";
 			reg = <0x1a400000 0x100>;
 		};
-	};
 
+		/* Clock controller for the low-power audio subsystem */
+		lcc: clock-controller@28000000 {
+			compatible = "qcom,lcc-msm8660";
+			reg = <0x28000000 0x1000>;
+			#clock-cells = <1>;
+			#reset-cells = <1>;
+		};
+
+		/*
+		 * Low-power audio subsystem, this contains a bunch of things
+		 * including I2S channels and a QDSP6 V3 for autonomous
+		 * playback of audio when the rest of the system is sleeping.
+		 */
+		lpass@28100000 {
+			compatible = "qcom,lpass-cpu-msm8660";
+			status = "disabled";
+			#sound-dai-cells = <1>;
+			reg = <0x28100000 0x10000>;
+			reg-names = "lpass-lpaif";
+			interrupts = <GIC_SPI 85 IRQ_TYPE_EDGE_RISING>;
+			interrupt-names = "lpass-irq-lpaif";
+
+			clocks = <&lcc MI2S_OSR_CLK>,
+				 <&lcc MI2S_BIT_CLK>,
+				 <&lcc CODEC_I2S_MIC_OSR_CLK>,
+				 <&lcc CODEC_I2S_MIC_BIT_CLK>,
+				 <&lcc SPARE_I2S_MIC_OSR_CLK>,
+				 <&lcc SPARE_I2S_MIC_BIT_CLK>,
+				 <&lcc CODEC_I2S_SPKR_OSR_CLK>,
+				 <&lcc CODEC_I2S_SPKR_BIT_CLK>,
+				 <&lcc SPARE_I2S_SPKR_OSR_CLK>,
+				 <&lcc SPARE_I2S_SPKR_BIT_CLK>,
+				 <&lcc PCM_CLK>;
+			clock-names = "mi2s-osr-clk",
+				      "mi2s-bit-clk",
+				      "codec-i2s-mic-osr-clk",
+				      "codec-i2s-mic-bit-clk",
+				      "spare-i2s-mic-osr-clk",
+				      "spare-i2s-mic-bit-clk",
+				      "codec-i2s-spkr-osr-clk",
+				      "codec-i2s-spkr-bit-clk",
+				      "spare-i2s-spkr-osr-clk",
+				      "spare-i2s-spkr-bit-clk",
+				      "pcm-clk";
+		};
+	};
 };
diff --git a/drivers/clk/qcom/clk-rcg.c b/drivers/clk/qcom/clk-rcg.c
index 67ce7c1..3583090 100644
--- a/drivers/clk/qcom/clk-rcg.c
+++ b/drivers/clk/qcom/clk-rcg.c
@@ -54,12 +54,15 @@
 		goto err;
 	ns = ns_to_src(&rcg->s, ns);
 	for (i = 0; i < num_parents; i++)
-		if (ns == rcg->s.parent_map[i].cfg)
+		if (ns == rcg->s.parent_map[i].cfg) {
+			pr_info("%s: Clock %s has parent %d\n",
+				__func__, clk_hw_get_name(hw), i);
 			return i;
+		}
 
 err:
-	pr_debug("%s: Clock %s has invalid parent, using default.\n",
-		 __func__, clk_hw_get_name(hw));
+	pr_info("%s: Clock %s has invalid parent %d, using default.\n",
+		__func__, clk_hw_get_name(hw), ns);
 	return 0;
 }
 
@@ -90,12 +93,15 @@
 	ns = ns_to_src(s, ns);
 
 	for (i = 0; i < num_parents; i++)
-		if (ns == s->parent_map[i].cfg)
+		if (ns == s->parent_map[i].cfg) {
+			pr_info("%s: Clock %s has parent %d\n",
+				__func__, clk_hw_get_name(hw), i);
 			return i;
+		}
 
 err:
-	pr_debug("%s: Clock %s has invalid parent, using default.\n",
-		 __func__, clk_hw_get_name(hw));
+	pr_info("%s: Clock %s has invalid parent %d, using default.\n",
+		__func__, clk_hw_get_name(hw), ns);
 	return 0;
 }
 
diff --git a/include/dt-bindings/sound/msm8x60-lpass.h b/include/dt-bindings/sound/msm8x60-lpass.h
new file mode 100644
index 0000000..0996680
--- /dev/null
+++ b/include/dt-bindings/sound/msm8x60-lpass.h
@@ -0,0 +1,10 @@
+#ifndef __DT_MSM8660_LPASS_H
+#define __DT_MSM8660_LPASS_H
+
+#define MI2S		0
+#define CODEC_I2S_MIC	1
+#define SPARE_I2S_MIC	2
+#define CODEC_I2S_SPKR	3
+#define SPARE_I2S_SPKR	4
+
+#endif /* __DT_MSM8660_LPASS_H */
diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c
index 6cb3c15..92662b4 100644
--- a/sound/soc/codecs/wm8903.c
+++ b/sound/soc/codecs/wm8903.c
@@ -1234,14 +1234,18 @@
 
 	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
 	case SND_SOC_DAIFMT_CBS_CFS:
+		dev_info(codec->dev, "slave mode CBS_CFS\n");
 		break;
 	case SND_SOC_DAIFMT_CBS_CFM:
+		dev_info(codec->dev, "master mode LRC CLK CBS_CFM\n");
 		aif1 |= WM8903_LRCLK_DIR;
 		break;
 	case SND_SOC_DAIFMT_CBM_CFM:
+		dev_info(codec->dev, "master mode LRC CLK + BCLK CBS_CFM\n");
 		aif1 |= WM8903_LRCLK_DIR | WM8903_BCLK_DIR;
 		break;
 	case SND_SOC_DAIFMT_CBM_CFS:
+		dev_info(codec->dev, "master mode BCLK CBM_CFS\n");
 		aif1 |= WM8903_BCLK_DIR;
 		break;
 	default:
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index b1764af..e568f24 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -23,6 +23,12 @@
 	select SND_SOC_LPASS_CPU
 	select SND_SOC_LPASS_PLATFORM
 
+config SND_SOC_LPASS_MSM8X60
+	tristate
+	depends on HAS_DMA
+	select SND_SOC_LPASS_CPU
+	select SND_SOC_LPASS_PLATFORM
+
 config SND_SOC_STORM
 	tristate "ASoC I2S support for Storm boards"
 	depends on SND_SOC_QCOM
@@ -108,3 +114,12 @@
 	  To add support for audio on Qualcomm Technologies Inc.
 	  SDM845 SoC-based systems.
 	  Say Y if you want to use audio device on this SoCs.
+
+config SND_SOC_MSM8660_WM8903
+	tristate "SoC Audio support for MSM8660 platforms with WM8903 codec"
+	depends on SND_SOC_QCOM && HAS_DMA
+	select SND_SOC_LPASS_MSM8X60
+	help
+          Support for Qualcomm Technologies LPASS audio block in
+          MSM8660 SOC-based systems with the Wolfson Micro WM8903 codec.
+          Say Y if you want to use audio with the WM8903 on this platform.
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
index 41b2c7a..b441c66 100644
--- a/sound/soc/qcom/Makefile
+++ b/sound/soc/qcom/Makefile
@@ -4,21 +4,25 @@
 snd-soc-lpass-platform-objs := lpass-platform.o
 snd-soc-lpass-ipq806x-objs := lpass-ipq806x.o
 snd-soc-lpass-apq8016-objs := lpass-apq8016.o
+snd-soc-lpass-msm8x60-objs := lpass-msm8x60.o
 
 obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o
 obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o
 obj-$(CONFIG_SND_SOC_LPASS_IPQ806X) += snd-soc-lpass-ipq806x.o
 obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
+obj-$(CONFIG_SND_SOC_LPASS_MSM8X60) += snd-soc-lpass-msm8x60.o
 
 # Machine
 snd-soc-storm-objs := storm.o
 snd-soc-apq8016-sbc-objs := apq8016_sbc.o
 snd-soc-apq8096-objs := apq8096.o
+snd-soc-msm8660-wm8903-objs := msm8660_wm8903.o
 snd-soc-sdm845-objs := sdm845.o
 snd-soc-qcom-common-objs := common.o
 
 obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o
 obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o
+obj-$(CONFIG_SND_SOC_MSM8660_WM8903) += snd-soc-msm8660-wm8903.o
 obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o
 obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o
 obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o
diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c
index d17b84b..995a518 100644
--- a/sound/soc/qcom/lpass-cpu.c
+++ b/sound/soc/qcom/lpass-cpu.c
@@ -226,6 +226,8 @@
 			freq, ret);
 		return ret;
 	}
+	dev_info(dai->dev, "setting %s to %lu Hz I2S regval %08x\n",
+		 variant->dai_bit_clk_names[dai->driver->id], freq, regval);
 
 	return 0;
 }
diff --git a/sound/soc/qcom/lpass-msm8x60.c b/sound/soc/qcom/lpass-msm8x60.c
new file mode 100644
index 0000000..cc01ca0
--- /dev/null
+++ b/sound/soc/qcom/lpass-msm8x60.c
@@ -0,0 +1,246 @@
+/*
+ * lpass-msm8x60.c - ALSA SoC CPU DAI driver for MSM8X60 Low-power Audio
+ * Subsystem (LPASS)
+ *
+ * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+#include <dt-bindings/sound/msm8x60-lpass.h>
+#include "lpass-lpaif-reg.h"
+#include "lpass.h"
+
+static struct snd_soc_dai_driver msm8x60_lpass_cpu_dai_driver[] = {
+	[MI2S] =  {
+		.id = MI2S,
+		.name = "Mono I2S",
+		.playback = {
+			.stream_name	= "Mono I2S Playback",
+			.formats	= SNDRV_PCM_FMTBIT_S16 |
+						SNDRV_PCM_FMTBIT_S24 |
+						SNDRV_PCM_FMTBIT_S32,
+			.rates		= SNDRV_PCM_RATE_8000 |
+						SNDRV_PCM_RATE_16000 |
+						SNDRV_PCM_RATE_32000 |
+						SNDRV_PCM_RATE_48000 |
+						SNDRV_PCM_RATE_96000,
+			.rate_min	= 8000,
+			.rate_max	= 96000,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.capture = {
+			.stream_name	= "Mono I2S Capture",
+			.formats	= SNDRV_PCM_FMTBIT_S16 |
+						SNDRV_PCM_FMTBIT_S24 |
+						SNDRV_PCM_FMTBIT_S32,
+			.rates		= SNDRV_PCM_RATE_8000 |
+						SNDRV_PCM_RATE_16000 |
+						SNDRV_PCM_RATE_32000 |
+						SNDRV_PCM_RATE_48000 |
+						SNDRV_PCM_RATE_96000,
+			.rate_min	= 8000,
+			.rate_max	= 96000,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.probe	= &asoc_qcom_lpass_cpu_dai_probe,
+		.ops    = &asoc_qcom_lpass_cpu_dai_ops,
+	},
+	[CODEC_I2S_MIC] =  {
+		.id = CODEC_I2S_MIC,
+		.name = "Codec I2S Microphone",
+		.capture = {
+			.stream_name	= "Codec I2S Microphone",
+			.formats	= SNDRV_PCM_FMTBIT_S16 |
+						SNDRV_PCM_FMTBIT_S24 |
+						SNDRV_PCM_FMTBIT_S32,
+			.rates		= SNDRV_PCM_RATE_8000 |
+						SNDRV_PCM_RATE_16000 |
+						SNDRV_PCM_RATE_32000 |
+						SNDRV_PCM_RATE_48000 |
+						SNDRV_PCM_RATE_96000,
+			.rate_min	= 8000,
+			.rate_max	= 96000,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.probe	= &asoc_qcom_lpass_cpu_dai_probe,
+		.ops    = &asoc_qcom_lpass_cpu_dai_ops,
+	},
+	[SPARE_I2S_MIC] =  {
+		.id = SPARE_I2S_MIC,
+		.name = "Spare I2S Microphone",
+		.capture = {
+			.stream_name	= "Spare I2S Microphone",
+			.formats	= SNDRV_PCM_FMTBIT_S16 |
+						SNDRV_PCM_FMTBIT_S24 |
+						SNDRV_PCM_FMTBIT_S32,
+			.rates		= SNDRV_PCM_RATE_8000 |
+						SNDRV_PCM_RATE_16000 |
+						SNDRV_PCM_RATE_32000 |
+						SNDRV_PCM_RATE_48000 |
+						SNDRV_PCM_RATE_96000,
+			.rate_min	= 8000,
+			.rate_max	= 96000,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.probe	= &asoc_qcom_lpass_cpu_dai_probe,
+		.ops    = &asoc_qcom_lpass_cpu_dai_ops,
+	},
+	[CODEC_I2S_SPKR] =  {
+		.id = CODEC_I2S_SPKR,
+		.name = "Codec I2S Speaker",
+		.playback = {
+			/* FIXME: removed some formats and rates */
+			.stream_name	= "Codec I2S Speaker",
+			.formats	= SNDRV_PCM_FMTBIT_S16_LE,
+			.rates		= SNDRV_PCM_RATE_8000 |
+			SNDRV_PCM_RATE_16000 |
+			SNDRV_PCM_RATE_32000 |
+			SNDRV_PCM_RATE_48000,
+			.rate_min	= 8000,
+			.rate_max	= 48000,
+			.channels_min	= 1,
+			.channels_max	= 2,
+		},
+		.probe	= &asoc_qcom_lpass_cpu_dai_probe,
+		.ops    = &asoc_qcom_lpass_cpu_dai_ops,
+	},
+	[SPARE_I2S_SPKR] =  {
+		.id = SPARE_I2S_SPKR,
+		.name = "Spare I2S Speaker",
+		.playback = {
+			.stream_name	= "Spare I2S Speaker",
+			.formats	= SNDRV_PCM_FMTBIT_S16 |
+						SNDRV_PCM_FMTBIT_S24 |
+						SNDRV_PCM_FMTBIT_S32,
+			.rates		= SNDRV_PCM_RATE_8000 |
+						SNDRV_PCM_RATE_16000 |
+						SNDRV_PCM_RATE_32000 |
+						SNDRV_PCM_RATE_48000 |
+						SNDRV_PCM_RATE_96000,
+			.rate_min	= 8000,
+			.rate_max	= 96000,
+			.channels_min	= 1,
+			.channels_max	= 8,
+		},
+		.probe	= &asoc_qcom_lpass_cpu_dai_probe,
+		.ops    = &asoc_qcom_lpass_cpu_dai_ops,
+	},
+};
+
+static int msm8x60_lpass_alloc_dma_channel(struct lpass_data *drvdata,
+					   int direction)
+{
+	struct lpass_variant *v = drvdata->variant;
+	int chan = 0;
+
+	if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
+		chan = find_first_zero_bit(&drvdata->dma_ch_bit_map,
+					v->rdma_channels);
+
+		if (chan >= v->rdma_channels)
+			return -EBUSY;
+	} else {
+		chan = find_next_zero_bit(&drvdata->dma_ch_bit_map,
+					v->wrdma_channel_start +
+					v->wrdma_channels,
+					v->wrdma_channel_start);
+
+		if (chan >=  v->wrdma_channel_start + v->wrdma_channels)
+			return -EBUSY;
+	}
+
+	set_bit(chan, &drvdata->dma_ch_bit_map);
+
+	pr_info("Allocated LPASS DMA channel %d\n", chan);
+	return chan;
+}
+
+static int msm8x60_lpass_free_dma_channel(struct lpass_data *drvdata, int chan)
+{
+	clear_bit(chan, &drvdata->dma_ch_bit_map);
+	pr_info("Free:ed LPASS DMA channel %d\n", chan);
+	return 0;
+}
+
+static int msm8x60_lpass_init(struct platform_device *pdev)
+{
+	dev_info(&pdev->dev, "initialized MSM8x60 LPASS\n");
+
+	return 0;
+}
+
+static int msm8x60_lpass_exit(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct lpass_variant msm8x60_data = {
+	.i2sctrl_reg_base	= 0x0004, /* IPQ8064 has 0x0010, Hint at 0x0004 */
+	.i2sctrl_reg_stride	= 0x04,
+	.i2s_ports		= 5,
+	.irq_reg_base		= 0x3000,
+	.irq_reg_stride		= 0x1000,
+	.irq_ports		= 3,
+	.rdma_reg_base		= 0x6000,
+	.rdma_reg_stride	= 0x1000,
+	.rdma_channels		= 5,
+	/* .dmactl_audif_start	= 1, */ /* No idea */
+	.wrdma_reg_base		= 0xB000, /* 0x6000 + 5 * 0x1000 */
+	.wrdma_reg_stride	= 0x1000,
+	.wrdma_channel_start	= 5,
+	.wrdma_channels		= 5,
+	.dai_driver		= msm8x60_lpass_cpu_dai_driver,
+	.num_dai		= ARRAY_SIZE(msm8x60_lpass_cpu_dai_driver),
+	.dai_osr_clk_names	= (const char *[]) {
+				"mi2s-osr-clk",
+				"codec-i2s-mic-osr-clk",
+				"spare-i2s-mic-osr-clk",
+				"codec-i2s-spkr-osr-clk",
+				"spare-i2s-spkr-osr-clk",
+				},
+	.dai_bit_clk_names	= (const char *[]) {
+				"mi2s-bit-clk",
+				"codec-i2s-mic-bit-clk",
+				"spare-i2s-mic-bit-clk",
+				"codec-i2s-spkr-bit-clk",
+				"spare-i2s-spkr-bit-clk",
+				},
+	.init			= msm8x60_lpass_init,
+	.exit			= msm8x60_lpass_exit,
+	.alloc_dma_channel	= msm8x60_lpass_alloc_dma_channel,
+	.free_dma_channel	= msm8x60_lpass_free_dma_channel,
+};
+
+static const struct of_device_id msm8x60_lpass_cpu_device_id[] = {
+	{ .compatible = "qcom,lpass-cpu-msm8660", .data = &msm8x60_data },
+	{}
+};
+MODULE_DEVICE_TABLE(of, msm8x60_lpass_cpu_device_id);
+
+static struct platform_driver msm8x60_lpass_cpu_platform_driver = {
+	.driver	= {
+		.name		= "msm8x60-lpass-cpu",
+		.of_match_table	= of_match_ptr(msm8x60_lpass_cpu_device_id),
+	},
+	.probe	= asoc_qcom_lpass_cpu_platform_probe,
+	.remove	= asoc_qcom_lpass_cpu_platform_remove,
+};
+module_platform_driver(msm8x60_lpass_cpu_platform_driver);
+
+MODULE_DESCRIPTION("MSM8X60 LPASS CPU DAI Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c
index 028bce6..c1e41fc 100644
--- a/sound/soc/qcom/lpass-platform.c
+++ b/sound/soc/qcom/lpass-platform.c
@@ -220,6 +220,12 @@
 			ret);
 		return ret;
 	}
+	dev_info(soc_runtime->dev,
+		 "Write into DMACTL channel %d @%08x value %08x\n",
+		 ch,
+		 LPAIF_DMACTL_REG(v, ch, dir),
+		 regval);
+
 
 	return 0;
 }
@@ -240,6 +246,12 @@
 	if (ret)
 		dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n",
 			ret);
+	dev_info(soc_runtime->dev,
+		 "Write into DMACTL channel %d @%08x value %08x\n",
+		 pcm_data->dma_ch,
+		 LPAIF_DMACTL_REG(v, pcm_data->dma_ch, substream->stream),
+		 0);
+
 
 	return ret;
 }
@@ -265,6 +277,12 @@
 			ret);
 		return ret;
 	}
+	dev_info(soc_runtime->dev,
+		 "Write into DMABASE channel %d @%08x value %08x\n",
+		 ch,
+		 LPAIF_DMABASE_REG(v, ch, dir),
+		 runtime->dma_addr);
+
 
 	ret = regmap_write(drvdata->lpaif_map,
 			LPAIF_DMABUFF_REG(v, ch, dir),
@@ -274,6 +292,11 @@
 			ret);
 		return ret;
 	}
+	dev_info(soc_runtime->dev,
+		 "Write into DMABUFF channel %d @%08x value %08x\n",
+		 ch,
+		 LPAIF_DMABUFF_REG(v, ch, dir),
+		 (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1);
 
 	ret = regmap_write(drvdata->lpaif_map,
 			LPAIF_DMAPER_REG(v, ch, dir),
@@ -283,6 +306,11 @@
 			ret);
 		return ret;
 	}
+	dev_info(soc_runtime->dev,
+		 "Write into DMAPER channel %d @%08x value %08x\n",
+		 ch,
+		 LPAIF_DMAPER_REG(v, ch, dir),
+		 (snd_pcm_lib_period_bytes(substream) >> 2) - 1);
 
 	ret = regmap_update_bits(drvdata->lpaif_map,
 			LPAIF_DMACTL_REG(v, ch, dir),
@@ -292,6 +320,10 @@
 			ret);
 		return ret;
 	}
+	dev_info(soc_runtime->dev,
+		 "Enable DMA channel %d @%08x\n",
+		 ch,
+		 LPAIF_DMACTL_REG(v, ch, dir));
 
 	return 0;
 }
@@ -322,6 +354,11 @@
 				"error writing to irqclear reg: %d\n", ret);
 			return ret;
 		}
+		dev_info(soc_runtime->dev,
+			 "Clear IRQ channel %d @%08x val %08x\n",
+			 ch,
+			 LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST),
+			 LPAIF_IRQ_ALL(ch));
 
 		ret = regmap_update_bits(drvdata->lpaif_map,
 				LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST),
@@ -332,6 +369,11 @@
 				"error writing to irqen reg: %d\n", ret);
 			return ret;
 		}
+		dev_info(soc_runtime->dev,
+			 "Enable IRQ channel %d @%08x bits %08x\n",
+			 ch,
+			 LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST),
+			 LPAIF_IRQ_ALL(ch));
 
 		ret = regmap_update_bits(drvdata->lpaif_map,
 				LPAIF_DMACTL_REG(v, ch, dir),
@@ -342,6 +384,11 @@
 				"error writing to rdmactl reg: %d\n", ret);
 			return ret;
 		}
+		dev_info(soc_runtime->dev,
+			 "Enable DMA channel %d @%08x bits %08x\n",
+			 ch,
+			 LPAIF_DMACTL_REG(v, ch, dir),
+			 LPAIF_DMACTL_ENABLE_ON);
 		break;
 	case SNDRV_PCM_TRIGGER_STOP:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
@@ -355,6 +402,12 @@
 				"error writing to rdmactl reg: %d\n", ret);
 			return ret;
 		}
+		dev_info(soc_runtime->dev,
+			 "Disable DMA channel %d @%08x bits %08x\n",
+			 ch,
+			 LPAIF_DMACTL_REG(v, ch, dir),
+			 LPAIF_DMACTL_ENABLE_OFF);
+
 
 		ret = regmap_update_bits(drvdata->lpaif_map,
 				LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST),
@@ -364,6 +417,12 @@
 				"error writing to irqen reg: %d\n", ret);
 			return ret;
 		}
+		dev_info(soc_runtime->dev,
+			 "Disable IRQ DMA channel %d @%08x bits %08x\n",
+			 ch,
+			 LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST),
+			 0);
+
 		break;
 	}
 
@@ -435,6 +494,8 @@
 	irqreturn_t ret = IRQ_NONE;
 	int rv;
 
+	dev_info(soc_runtime->dev, "%s\n", __func__);
+
 	if (interrupts & LPAIF_IRQ_PER(chan)) {
 		rv = regmap_write(drvdata->lpaif_map,
 				LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST),
@@ -486,6 +547,8 @@
 	unsigned int irqs;
 	int rv, chan;
 
+	pr_info("%s\n", __func__);
+
 	rv = regmap_read(drvdata->lpaif_map,
 			LPAIF_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs);
 	if (rv) {
@@ -585,6 +648,9 @@
 		dev_err(&pdev->dev, "error writing to irqen reg: %d\n", ret);
 		return ret;
 	}
+	dev_info(&pdev->dev,
+		 "Disable all IRQ @%08x bits %08x\n",
+		 LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0);
 
 	ret = devm_request_irq(&pdev->dev, drvdata->lpaif_irq,
 			lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING,
diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h
index 2b086e8..46e80eb 100644
--- a/sound/soc/qcom/lpass.h
+++ b/sound/soc/qcom/lpass.h
@@ -55,6 +55,9 @@
 	/* used it for handling interrupt per dma channel */
 	struct snd_pcm_substream *substream[LPASS_MAX_DMA_CHANNELS];
 
+	/* Cached master/slave config from set_dai_fmt() */
+	unsigned int fmt;
+
 	/* 8016 specific */
 	struct clk *pcnoc_mport_clk;
 	struct clk *pcnoc_sway_clk;
diff --git a/sound/soc/qcom/msm8660_wm8903.c b/sound/soc/qcom/msm8660_wm8903.c
new file mode 100644
index 0000000..c9f2f8d
--- /dev/null
+++ b/sound/soc/qcom/msm8660_wm8903.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <dt-bindings/sound/msm8x60-lpass.h>
+
+/**
+ * struct msm8660_wm8903_data - state container for the sound card
+ * @spkr_pa_left: the speaker PA enable GPIO for the left channel
+ * @spkr_pa_right: the speaker PA enable GPIO for the right channel
+ * @mclk_gate: a GPIO gating the MCLK
+ * @wm8903_mclk: the CODEC I2S MIC OSR clk is connected to the WM8903
+ * MCLK, so this must be running for any of the I2S channels to the
+ * WM89803 to work
+ * @wm8903_mclk_bit: the CODEC I2S MIC bit clockk is connected to
+ * the WM8903 MCLK, so this must be running for any of the I2S
+ * channels to the WM89803 to work
+ * @dai_link: the digital audio interface link
+ */
+struct msm8660_wm8903_data {
+	struct gpio_desc *spkr_pa_left;
+	struct gpio_desc *spkr_pa_right;
+	struct gpio_desc *mclk_gate;
+	struct clk *wm8903_mclk;
+	struct clk *wm8903_mclk_bit;
+	struct clk *spkr_osr_clk;
+	struct clk *spkr_bit_clk;
+	struct snd_soc_dai_link dai_link[];	/* dynamically allocated */
+};
+
+#define MSM8660_SYSCLK_MULT 4
+
+static int msm8660_wm8903_hw_params(struct snd_pcm_substream *substream,
+				    struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct msm8660_wm8903_data *data = snd_soc_card_get_drvdata(card);
+	//snd_pcm_format_t format = params_format(params);
+	unsigned int rate = params_rate(params);
+	unsigned int codec_freq;
+	int bitwidth, ret;
+
+	pr_info("%s\n", __func__);
+	dev_info(card->dev, "rate: %d\n", rate);
+
+	/* WM8903 runs at LRC*256 */
+	codec_freq = rate * 256;
+	dev_info(card->dev, "request codec clock rate %d\n", codec_freq);
+
+	/* Tell the codec what clock it gets in */
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0,
+				     codec_freq, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(card->dev, "error setting codec clock to %u: %d\n",
+			codec_freq, ret);
+		return ret;
+	}
+
+	ret = clk_set_rate(data->wm8903_mclk, codec_freq);
+	if (ret) {
+		dev_err(card->dev, "could not set WM8903 MCLK\n");
+		return ret;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		// snd_soc_dai_digital_mute(rtd->codec_dai, 0);
+		/* Set codec as master driving LRC and BCLK */
+		ret = snd_soc_dai_set_fmt(rtd->codec_dai,
+					  SND_SOC_DAIFMT_CBM_CFM |
+					  SND_SOC_DAIFMT_I2S);
+		if (ret) {
+			dev_err(card->dev,
+				"could not set CODEC DAI sound format\n");
+			return ret;
+		}
+		/* Set CPU DAI as slave for LRC and BCLK */
+		ret = snd_soc_dai_set_fmt(rtd->cpu_dai,
+					  SND_SOC_DAIFMT_CBS_CFS);
+		if (ret) {
+			dev_err(card->dev,
+				"could not set CPU DAI sound format\n");
+			return ret;
+		}
+	} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		// clk_set_rate(mic_bit_clk, 0);
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_ops msm8660_wm8903_ops = {
+	.hw_params = msm8660_wm8903_hw_params,
+};
+
+static int msm8660_wm8903_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct snd_soc_card *card = rtd->card;
+	struct msm8660_wm8903_data *data = snd_soc_card_get_drvdata(card);
+	int rval = 0;
+	int ret;
+
+	switch (cpu_dai->id) {
+	case MI2S:
+		dev_info(card->dev, "DAI init MI2S\n");
+		break;
+	case CODEC_I2S_MIC:
+		dev_info(card->dev, "DAI init CODEC I2S MIC\n");
+		break;
+	case SPARE_I2S_MIC:
+		dev_info(card->dev, "DAI init SPARE I2S MIC (WM8903)\n");
+		break;
+	case CODEC_I2S_SPKR:
+		dev_info(card->dev, "DAI init CODEC I2S SPKR (WM8903 master, CPU I2S slave)\n");
+		/* Set codec as master driving LRC and BCLK */
+		ret = snd_soc_dai_set_fmt(rtd->codec_dai,
+					  SND_SOC_DAIFMT_CBM_CFM |
+					  SND_SOC_DAIFMT_I2S);
+		if (ret) {
+			dev_err(card->dev,
+				"could not set CODEC DAI sound format\n");
+			return ret;
+		}
+		/* Set CPU DAI as slave for LRC and BCLK */
+		ret = snd_soc_dai_set_fmt(rtd->cpu_dai,
+					  SND_SOC_DAIFMT_CBS_CFS);
+		if (ret) {
+			dev_err(card->dev,
+				"could not set CPU DAI sound format\n");
+			return ret;
+		}
+		break;
+	case SPARE_I2S_SPKR:
+		dev_info(card->dev, "DAI init SPARE I2S SPKR\n");
+		break;
+	default:
+		dev_err(card->dev, "unsupported cpu dai configuration\n");
+		rval = -EINVAL;
+		break;
+	}
+
+	return rval;
+}
+
+static struct msm8660_wm8903_data *
+msm8660_wm8903_parse_of(struct snd_soc_card *card)
+{
+	struct device *dev = card->dev;
+	struct snd_soc_dai_link *link;
+	struct device_node *np, *codec, *cpu, *node  = dev->of_node;
+	struct msm8660_wm8903_data *data;
+	int ret, num_links;
+
+	ret = snd_soc_of_parse_card_name(card, "qcom,model");
+	if (ret) {
+		dev_err(dev, "Error parsing card name: %d\n", ret);
+		return ERR_PTR(ret);
+	}
+
+	/* DAPM routes */
+	if (of_property_read_bool(node, "qcom,audio-routing")) {
+		ret = snd_soc_of_parse_audio_routing(card,
+					"qcom,audio-routing");
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	/* Populate links */
+	num_links = of_get_child_count(node);
+
+	/* Allocate the private data and the DAI link array */
+	data = devm_kzalloc(dev, sizeof(*data) + sizeof(*link) * num_links,
+			    GFP_KERNEL);
+	if (!data)
+		return ERR_PTR(-ENOMEM);
+
+	card->dai_link	= &data->dai_link[0];
+	card->num_links	= num_links;
+
+	link = data->dai_link;
+
+	for_each_child_of_node(node, np) {
+		cpu = of_get_child_by_name(np, "cpu");
+		codec = of_get_child_by_name(np, "codec");
+
+		if (!cpu || !codec) {
+			dev_err(dev, "Can't find cpu/codec DT node\n");
+			return ERR_PTR(-EINVAL);
+		}
+
+		link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0);
+		if (!link->cpu_of_node) {
+			dev_err(card->dev, "error getting cpu phandle\n");
+			return ERR_PTR(-EINVAL);
+		}
+
+		ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name);
+		if (ret) {
+			dev_err(card->dev, "error getting cpu dai name\n");
+			return ERR_PTR(ret);
+		}
+
+		ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
+		if (ret < 0) {
+			dev_err(card->dev, "error getting codec dai link\n");
+			return ERR_PTR(ret);
+		}
+
+		link->platform_of_node = link->cpu_of_node;
+		ret = of_property_read_string(np, "link-name", &link->name);
+		if (ret) {
+			dev_err(card->dev, "error getting codec DAI link name\n");
+			return ERR_PTR(ret);
+		}
+
+		link->stream_name = link->name;
+		link->init = msm8660_wm8903_dai_init;
+		link->ops = &msm8660_wm8903_ops;
+		dev_info(dev, "parsed link \"%s\"\n", link->name);
+		link++;
+	}
+
+	/* Obtain speaker PA GPIOs */
+	data->spkr_pa_left = devm_gpiod_get(dev, "qcom,spkr-pa-left",
+					    GPIOD_OUT_LOW);
+	if (IS_ERR(data->spkr_pa_left)) {
+		dev_err(dev, "could not obtain left speaker PA GPIO\n");
+		return ERR_CAST(data->spkr_pa_left);
+	}
+	data->spkr_pa_right = devm_gpiod_get(dev, "qcom,spkr-pa-right",
+					     GPIOD_OUT_LOW);
+	if (IS_ERR(data->spkr_pa_left)) {
+		dev_err(dev, "could not obtain left speaker PA GPIO\n");
+		return ERR_CAST(data->spkr_pa_left);
+	}
+
+	/*
+	 * FIXME: what on earth is this? My schematic does not have this
+	 * but the vendor tree drives this line high.
+	 */
+	data->mclk_gate = devm_gpiod_get(dev, "qcom,mclk-gate", GPIOD_OUT_LOW);
+	if (IS_ERR(data->mclk_gate)) {
+		dev_err(dev, "could not obtain MCLK gate GPIO\n");
+		return ERR_CAST(data->mclk_gate);
+	}
+	gpiod_set_value_cansleep(data->mclk_gate, 1);
+
+	/* Obtain CODEC I2S MIC clock */
+	data->wm8903_mclk = devm_clk_get(dev, "codec-i2s-mic-osr-clk");
+	if (IS_ERR(data->wm8903_mclk)) {
+		dev_err(dev, "could not get CODEC I2S MIC clock\n");
+		return ERR_CAST(data->wm8903_mclk);
+	}
+	clk_set_rate(data->wm8903_mclk, 48000 * 256);
+	ret = clk_prepare_enable(data->wm8903_mclk);
+	if (ret) {
+		dev_err(dev, "could not enable CODEC I2S MIC clock\n");
+		return ERR_PTR(ret);
+	}
+
+	/* Obtain CODEC I2S bit clock so we latch out onto MCLK */
+	data->wm8903_mclk_bit = devm_clk_get(dev, "codec-i2s-mic-bit-clk");
+	if (IS_ERR(data->wm8903_mclk_bit)) {
+		dev_err(dev, "could not get CODEC I2S MIC BIT clock\n");
+		return ERR_CAST(data->wm8903_mclk_bit);
+	}
+	ret = clk_prepare_enable(data->wm8903_mclk_bit);
+	if (ret) {
+		dev_err(dev, "could not enable CODEC I2S MIC BIT clock\n");
+		return ERR_PTR(ret);
+	}
+
+	/* FIXME: let runtime PM disable the CODEC I2S MIC clock */
+	data->spkr_osr_clk = devm_clk_get(dev, "codec-i2s-spkr-osr-clk");
+	if (IS_ERR(data->spkr_osr_clk)) {
+		dev_err(dev, "could not get CODEC I2S SPKR OSR clock\n");
+		return ERR_CAST(data->spkr_osr_clk);
+	}
+	clk_set_rate(data->spkr_osr_clk, 48000*256);
+	clk_set_rate(data->spkr_osr_clk, 0);
+	ret = clk_prepare_enable(data->spkr_osr_clk);
+	if (ret) {
+		dev_err(dev, "could not enable CODEC I2S SPKR OSR clock\n");
+		return ERR_PTR(ret);
+	}
+
+	data->spkr_bit_clk = devm_clk_get(dev, "codec-i2s-spkr-bit-clk");
+	if (IS_ERR(data->spkr_bit_clk)) {
+		dev_err(dev, "could not get CODEC I2S SPKR BIT clock\n");
+		return ERR_CAST(data->spkr_bit_clk);
+	}
+	ret = clk_prepare_enable(data->spkr_bit_clk);
+	if (ret) {
+		dev_err(dev, "could not enable CODEC I2S SPKR BIT clock\n");
+		return ERR_PTR(ret);
+	}
+
+	return data;
+}
+
+static int msm8660_spkramp_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	struct snd_soc_dapm_context *dapm = w->dapm;
+	struct snd_soc_card *card = dapm->card;
+	struct msm8660_wm8903_data *data = snd_soc_card_get_drvdata(card);
+
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		pr_info("enable SPKR GPIOs\n");
+		gpiod_set_value_cansleep(data->spkr_pa_left, 1);
+		gpiod_set_value_cansleep(data->spkr_pa_right, 1);
+	} else {
+		pr_info("disable SPKR GPIOs\n");
+		gpiod_set_value_cansleep(data->spkr_pa_left, 0);
+		gpiod_set_value_cansleep(data->spkr_pa_right, 0);
+	}
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget msm8660_wm8903_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Headphone", msm8660_spkramp_event),
+	SND_SOC_DAPM_SPK("Handset", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_MIC("Handset Mic", NULL),
+};
+
+static int msm8660_wm8903_platform_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct snd_soc_card *card;
+	struct msm8660_wm8903_data *data;
+
+	card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+	if (!card)
+		return -ENOMEM;
+
+	card->dev = dev;
+	card->dapm_widgets = msm8660_wm8903_dapm_widgets;
+	card->num_dapm_widgets = ARRAY_SIZE(msm8660_wm8903_dapm_widgets);
+	data = msm8660_wm8903_parse_of(card);
+	if (IS_ERR(data)) {
+		dev_err(&pdev->dev, "Error resolving DAI links: %ld\n",
+			PTR_ERR(data));
+		return PTR_ERR(data);
+	}
+
+	platform_set_drvdata(pdev, data);
+	snd_soc_card_set_drvdata(card, data);
+
+	return devm_snd_soc_register_card(&pdev->dev, card);
+}
+
+static const struct of_device_id msm8660_wm8903_device_id[]  = {
+	{ .compatible = "qcom,msm8660-wm8903-sndcard" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, msm8660_wm8903_device_id);
+
+static struct platform_driver msm8660_wm8903_platform_driver = {
+	.driver = {
+		.name = "qcom-msm8660-wm8903",
+		.of_match_table = of_match_ptr(msm8660_wm8903_device_id),
+	},
+	.probe = msm8660_wm8903_platform_probe,
+};
+module_platform_driver(msm8660_wm8903_platform_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("MSM8660 ASoC Machine Driver");
+MODULE_LICENSE("GPL v2");