diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index f51b28d1b94d..350100781c04 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -42,6 +42,15 @@ config SND_SOC_APQ8016_SBC
 	  APQ8016 SOC-based systems.
 	  Say Y if you want to use audio devices on MI2S.
 
+config SND_SOC_APQ8039
+	tristate "SoC Audio support for APQ8039 based platforms"
+	depends on SND_SOC_QCOM
+	select SND_SOC_LPASS_APQ8016
+	help
+          Support for Qualcomm Technologies LPASS audio block in
+          APQ8039 SOC-based systems.
+          Say Y if you want to use audio devices on MI2S.
+
 config SND_SOC_QCOM_COMMON
 	tristate
 
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
index 41b2c7a23a4d..cc67e240744f 100644
--- a/sound/soc/qcom/Makefile
+++ b/sound/soc/qcom/Makefile
@@ -13,12 +13,14 @@ obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
 # Machine
 snd-soc-storm-objs := storm.o
 snd-soc-apq8016-sbc-objs := apq8016_sbc.o
+snd-soc-apq8039-objs := apq8039.o
 snd-soc-apq8096-objs := apq8096.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_APQ8039) += snd-soc-apq8039.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/apq8039.c b/sound/soc/qcom/apq8039.c
new file mode 100644
index 000000000000..0c413cf215dd
--- /dev/null
+++ b/sound/soc/qcom/apq8039.c
@@ -0,0 +1,1126 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2020, Linaro Limited
+
+// #define DEBUG
+#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 <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <uapi/linux/input-event-codes.h>
+#include <dt-bindings/sound/apq8016-lpass.h>
+
+struct ctl_set_node {
+	struct list_head node;
+	char *name;
+	char *value;
+};
+
+struct state_input_node {
+	struct list_head node;
+	const char *name;
+	int state_input;
+};
+
+struct apq8039_data {
+	void __iomem *mic_iomux;
+	void __iomem *spkr_iomux;
+	struct snd_soc_jack jack;
+	bool jack_setup;
+	int headphone_det;
+	int microphone_det;
+	struct list_head ctl_list;
+	struct list_head state_input_list;
+	int num_functions;
+	struct device_node **function_nodes;
+	const char **function_names;
+	struct soc_enum func_enum[1];
+	struct snd_kcontrol_new func_control[1];
+	struct device_node *function_node;
+	struct device_node *cooked_function_node;
+	struct device_node *init_node;
+	struct device_node *shutdown_node;
+	char *control_name;
+	struct snd_soc_dai_link dai_link[];	/* dynamically allocated */
+};
+
+static struct snd_kcontrol *
+snd_soc_card_ctl_find(struct snd_soc_card *card,
+		      const char *name, const char *suffix)
+{
+	struct snd_ctl_elem_id sid;
+
+	memset(&sid, 0, sizeof(sid));
+	if (suffix)
+		snprintf(sid.name, sizeof(sid.name), "%s %s", name, suffix);
+	else
+		snprintf(sid.name, sizeof(sid.name), "%s", name);
+	sid.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+	return snd_ctl_find_id(card->snd_card, &sid);
+}
+
+static int
+snd_soc_card_ctl_getset(struct snd_soc_card *card,
+			const char *name,
+			char *orig, size_t origsz, const char *value)
+{
+	struct snd_kcontrol *ctl;
+	struct snd_ctl_elem_info *uinfo = NULL;
+	struct snd_ctl_elem_value *uctl = NULL;
+	unsigned int i, count;
+	int ret;
+
+	/* get the control */
+	ctl = snd_soc_card_ctl_find(card, name, NULL);
+	if (!ctl)
+		return -ENOENT;
+
+	/* allocate info and value */
+	uinfo = kzalloc(sizeof(*uinfo), GFP_KERNEL);
+	uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
+	if (!uinfo || !uctl) {
+		ret = -ENOMEM;
+		goto out_free;
+	}
+
+	ret = ctl->info(ctl, uinfo);
+	if (ret)
+		goto out_free;
+	if (uinfo->count != 1) {
+		ret = -ENOTSUPP;
+		goto out_free;
+	}
+
+	ret = ctl->get(ctl, uctl);
+	if (ret)
+		goto out_free;
+
+	ret = 0;
+
+	switch (uinfo->type) {
+
+	case SNDRV_CTL_ELEM_TYPE_NONE:
+		break;
+
+	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
+		if (orig && origsz > 0)
+			snprintf(orig, origsz, "%s",
+					uctl->value.integer.value[0] ?
+						"true" : "false");
+
+		if (value) {
+			bool bval;
+
+			ret = kstrtobool(value, &bval);
+			if (ret)
+				goto out_free;
+			uctl->value.integer.value[0] = !!bval;
+
+		}
+		break;
+
+	case SNDRV_CTL_ELEM_TYPE_INTEGER:
+		if (orig && origsz > 0)
+			snprintf(orig, origsz, "%ld",
+					uctl->value.integer.value[0]);
+
+		if (value) {
+			ret = kstrtol(value, 10,
+					&uctl->value.integer.value[0]);
+			if (ret)
+				goto out_free;
+		}
+		break;
+
+	case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
+		count = uinfo->value.enumerated.items;
+
+		if (value) { /* set */
+			for (i = 0; i < count; i++) {
+				uinfo->value.enumerated.item = i;
+				ret = ctl->info(ctl, uinfo);
+				if (ret)
+					goto out_free;
+
+				if (!strcmp(value,
+						uinfo->value.enumerated.name))
+					break;
+			}
+
+			/* setting without finding the enum */
+			if (i >= count) {
+				ret = -EINVAL;
+				goto out_free;
+			}
+			uctl->value.enumerated.item[0] = i;
+		} else { /* get */
+			uinfo->value.enumerated.item =
+				uctl->value.enumerated.item[0];
+			ret = ctl->info(ctl, uinfo);
+			if (ret)
+				goto out_free;
+
+			if (orig && origsz) {
+				if (origsz <
+					strlen(uinfo->value.enumerated.name)
+					+ 1) {
+					ret = -ENOSPC;
+					goto out_free;
+				}
+				strncpy(orig, uinfo->value.enumerated.name,
+						origsz);
+			}
+		}
+		break;
+
+	case SNDRV_CTL_ELEM_TYPE_INTEGER64:
+		if (orig && origsz > 0)
+			snprintf(orig, origsz, "%lld",
+					uctl->value.integer64.value[0]);
+
+		if (value) {
+			ret = kstrtoll(value, 10,
+					&uctl->value.integer64.value[0]);
+			if (ret)
+				goto out_free;
+		}
+		break;
+
+	case SNDRV_CTL_ELEM_TYPE_BYTES:
+	case SNDRV_CTL_ELEM_TYPE_IEC958:
+		ret = -ENOTSUPP;
+		goto out_free;
+	default:
+		ret = -EINVAL;
+		goto out_free;
+	}
+
+	if (value) {
+		ret = ctl->put(ctl, uctl);
+		if (ret < 0)
+			goto out_free;
+
+		/* if it changed, report change to user space */
+		if (ret > 0)
+			snd_ctl_notify(card->snd_card,
+					SNDRV_CTL_EVENT_MASK_VALUE,
+					&uctl->id);
+	}
+
+out_free:
+	kfree(uctl);
+	kfree(uinfo);
+
+	if (ret < 0)
+		dev_err(card->dev, "%s: %s operation failed for \"%s\"",
+				__func__, value ? "set" : "get", name);
+
+	return ret;
+}
+
+static int apq8039_ctl_set(struct snd_soc_card *card,
+			      const char *name, const char *value)
+{
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	struct ctl_set_node *n;
+	char *nvalue;
+
+	list_for_each_entry(n, &pdata->ctl_list, node) {
+		/* if it exists, just update with new value */
+		if (!strcmp(name, n->name)) {
+			nvalue = devm_kstrdup(card->dev, value, GFP_KERNEL);
+			if (!nvalue)
+				return -ENOMEM;
+			devm_kfree(card->dev, n->value);
+			n->value = nvalue;
+			dev_dbg(card->dev, "REP \"%s\" \"%s\"\r\n",
+					n->name, n->value);
+			return 0;
+		}
+	}
+	n = devm_kzalloc(card->dev, sizeof(*n), GFP_KERNEL);
+	if (!n)
+		return -ENOMEM;
+	n->name = devm_kstrdup(card->dev, name, GFP_KERNEL);
+	n->value = devm_kstrdup(card->dev, value, GFP_KERNEL);
+	if (!n->name || !n->value) {
+		/* n is not NULL and kfree can handle NULLs */
+		devm_kfree(card->dev, n->name);
+		devm_kfree(card->dev, n->value);
+		devm_kfree(card->dev, n);
+		return -ENOMEM;
+	}
+	list_add_tail(&n->node, &pdata->ctl_list);
+
+	dev_dbg(card->dev, "NEW \"%s\" \"%s\"\r\n", n->name, n->value);
+
+	return 0;
+}
+
+static int apq8039_ctl_commit(struct snd_soc_card *card)
+{
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	struct ctl_set_node *n, *nn;
+	int res, failed_res;
+	bool modified;
+
+	modified = false;
+	failed_res = 0;
+	list_for_each_entry_safe(n, nn, &pdata->ctl_list, node) {
+
+		list_del(&n->node);
+
+		dev_dbg(card->dev, "CMT \"%s\" \"%s\"\r\n", n->name, n->value);
+		res = snd_soc_card_ctl_getset(card, n->name, NULL, 0, n->value);
+		if (res < 0 && !failed_res)
+			failed_res = res;
+		if (res > 0 && !modified)
+			modified = true;
+
+		devm_kfree(card->dev, n->name);
+		devm_kfree(card->dev, n->value);
+		devm_kfree(card->dev, n);
+	}
+
+	if (failed_res)
+		return failed_res;
+
+	return modified ? 1 : 0;
+}
+
+static bool apq8039_function_is_cooked(struct snd_soc_card *card,
+					  struct device_node *funcnp)
+{
+	int res;
+
+	res = of_property_count_strings(funcnp, "state-inputs");
+	return res >= 1;
+}
+
+static int apq8039_state_input_get(struct snd_soc_card *card,
+				      const char *name)
+{
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	struct state_input_node *n;
+
+	list_for_each_entry(n, &pdata->state_input_list, node) {
+		if (!strcmp(n->name, name))
+			return n->state_input;
+	}
+
+	/* does not exist */
+	return -ENOENT;
+}
+
+static int apq8039_state_get(struct snd_soc_card *card,
+				struct device_node *funcnp)
+{
+	int res = -EINVAL, i, state, count;
+	u32 bits, def, bitpos;
+	const char *state_input;
+
+	res = of_property_count_strings(funcnp, "state-inputs");
+	if (res <= 0) {
+		/* if there's no events property this is a fixed function */
+		res = 0;
+		goto out;
+	}
+	count = res;
+
+	state = 0;
+	bitpos = 0;
+	for (i = 0; i < count; i++) {
+
+		res = of_property_read_string_index(funcnp, "state-inputs",
+				i, &state_input);
+		if (res < 0) {
+			dev_err(card->dev,
+					"Failed to read \"%s\" #%d of \"%s\"\n",
+					"state-inputs", i, funcnp->name);
+			goto out;
+		}
+
+		res = of_property_read_u32_index(funcnp, "state-input-bits",
+					i, &bits);
+		if (res < 0) {
+			dev_err(card->dev,
+					"Failed to read \"%s\" #%d of \"%s\"\n",
+					"state-input-bits", i, funcnp->name);
+			goto out;
+		}
+
+		res = of_property_read_u32_index(funcnp,
+				"state-input-defaults", i, &def);
+		if (res < 0) {
+			dev_err(card->dev,
+					"Failed to read \"%s\" #%d of \"%s\"\n",
+					"state-input-defaults", i,
+					funcnp->name);
+			goto out;
+		}
+
+		if (bitpos + bits >= 32) {
+			dev_err(card->dev,
+					"Too many events for bitstate \"%s\"\n",
+					funcnp->name);
+			res = -EINVAL;
+			goto out;
+		}
+
+		res = apq8039_state_input_get(card, state_input);
+		if (res < 0)
+			res = (int)def;
+
+		dev_dbg(card->dev, "@%s state_input \"%s\" -> %d\n",
+				__func__, state_input, res);
+
+		state |= (res & ((1 << bits) - 1)) << bitpos;
+
+		bitpos += bits;
+	}
+
+	/* return the state */
+	res = state;
+
+	dev_dbg(card->dev, "@%s state 0x%x\n", __func__, state);
+
+out:
+	return res;
+}
+
+static struct device_node *
+apq8039_get_cooked_function(struct snd_soc_card *card,
+			       struct device_node *funcnp)
+{
+	struct device_node *cooked_funcnp = NULL;
+	int res, i, count, state;
+	struct of_phandle_args ofargs;
+
+	/* if this is not a cooked function, return self (with a ref) */
+	if (!apq8039_function_is_cooked(card, funcnp))
+		return of_node_get(funcnp);
+
+	/* get synthetic state from state inputs */
+	state = apq8039_state_get(card, funcnp);
+
+	/* must exist and be a multiple of 3 */
+	res = of_property_count_elems_of_size(funcnp, "state-map",
+						sizeof(u32));
+	if (res <= 0 || (res % 3)) {
+		dev_err(card->dev, "%s %s property of \"%s\" node\n",
+				res < 0 ? "missing" : "invalid",
+				"state-map", funcnp->name);
+		goto out;
+	}
+	count = res / 3;
+
+	memset(&ofargs, 0, sizeof(ofargs));
+
+	/* look for a match for the state in the map */
+	for (i = 0; i < count; i++) {
+		res = of_parse_phandle_with_fixed_args(funcnp, "state-map",
+							2, i, &ofargs);
+		if (res < 0) {
+			dev_err(card->dev,
+					"failed to parse %s prop \"%s\" #%d\n",
+					"state-map", funcnp->name, i);
+			goto out;
+		}
+		/* match */
+		if (((uint32_t)state & ofargs.args[0]) == ofargs.args[1])
+			break;
+		of_node_put(ofargs.np);
+	}
+	if (i >= count) {
+		dev_err(card->dev,
+				"failed to match state %d %s prop of \"%s\"\n",
+				state, "state-map", funcnp->name);
+		goto out;
+	}
+
+	dev_dbg(card->dev, "@%s function \"%s\" state 0x%x -> \"%s\"\n",
+			__func__, funcnp->name, state, ofargs.np->name);
+	cooked_funcnp = ofargs.np;
+
+out:
+	return cooked_funcnp;
+}
+
+static int apq8039_apply_function_sequence(struct snd_soc_card *card,
+					      struct device_node *funcnp,
+					      const char *kind)
+{
+	int res = -EINVAL, i, count;
+	const char *ctl, *val;
+
+	/* if the property does not exist, it's no problem */
+	if (!of_get_property(funcnp, kind, NULL)) {
+		res = 0;
+		goto out;
+	}
+
+	/* number of strings (must be pairs) */
+	res = of_property_count_strings(funcnp, kind);
+	if (res < 0 || (res % 2) != 0) {
+		dev_err(card->dev, "Illegal string list prop \"%s\"\n",
+				kind);
+		goto out;
+	}
+
+	for (i = 0, count = res; i < count; i += 2) {
+
+		res = of_property_read_string_index(funcnp, kind, i, &ctl);
+		if (res < 0) {
+			dev_err(card->dev,
+					"Bad string list prop ctl \"%s\" @%d\n",
+					kind, i);
+			goto out;
+		}
+
+		res = of_property_read_string_index(funcnp, kind, i + 1, &val);
+		if (res < 0) {
+			dev_err(card->dev,
+					"Bad string list prop val \"%s\" @%d\n",
+					kind, i + 1);
+			goto out;
+		}
+
+		res = apq8039_ctl_set(card, ctl, val);
+		if (res < 0) {
+			dev_err(card->dev,
+					"ctl_set() failed at \"%s\" = \"%s\"\n",
+					ctl, val);
+			goto out;
+		}
+	}
+
+	res = 0;
+
+out:
+	return res;
+}
+
+static void apq8039_select_function(struct snd_soc_card *card,
+				       struct device_node *new_funcnp)
+{
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	struct device_node *old_funcnp, *old_cooked_funcnp, *new_cooked_funcnp;
+
+	if (!new_funcnp)
+		return;
+
+	/* get final old function */
+	old_funcnp = pdata->function_node;
+	old_cooked_funcnp = pdata->cooked_function_node;
+
+	new_cooked_funcnp = apq8039_get_cooked_function(card, new_funcnp);
+
+	dev_dbg(card->dev, "function_node \"%s\" -> \"%s\"\r\n",
+			old_funcnp ? old_funcnp->name : "NULL",
+			new_funcnp->name);
+	dev_dbg(card->dev, "cooked function_node \"%s\" -> \"%s\"\r\n",
+			old_cooked_funcnp ? old_cooked_funcnp->name : "NULL",
+			new_cooked_funcnp->name);
+
+	/* same? don't bother */
+	if (old_cooked_funcnp == new_cooked_funcnp)
+		return;
+
+	if (pdata->cooked_function_node)
+		apq8039_apply_function_sequence(card,
+				pdata->cooked_function_node, "disable");
+
+	of_node_put(pdata->function_node);
+	of_node_put(pdata->cooked_function_node);
+
+	pdata->function_node = of_node_get(new_funcnp);
+	pdata->cooked_function_node = new_cooked_funcnp;
+
+	apq8039_apply_function_sequence(card,
+			pdata->cooked_function_node, "enable");
+
+	apq8039_ctl_commit(card);
+}
+
+static int apq8039_state_input_set(struct snd_soc_card *card,
+				      const char *name, int value)
+{
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	struct state_input_node *n;
+
+	list_for_each_entry(n, &pdata->state_input_list, node) {
+		/* if it exists, just update with new value */
+		if (!strcmp(name, n->name)) {
+			/* same state */
+			if (value == n->state_input)
+				return 0;
+			/* return 1 on update, 0, on no change */
+			n->state_input = value;
+			dev_dbg(card->dev, "update state-input \"%s\" %d\r\n",
+					n->name, n->state_input);
+			return 1;
+		}
+	}
+	n = devm_kzalloc(card->dev, sizeof(*n), GFP_KERNEL);
+	if (!n)
+		return -ENOMEM;
+	n->name = devm_kstrdup(card->dev, name, GFP_KERNEL);
+	if (!n->name) {
+		devm_kfree(card->dev, n);
+		return -ENOMEM;
+	}
+	n->state_input = value;
+	list_add_tail(&n->node, &pdata->state_input_list);
+
+	dev_dbg(card->dev, "new state-input \"%s\" %d\r\n",
+			n->name, n->state_input);
+
+	return 1;
+}
+
+static int apq8039_get_function(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol);
+static int apq8039_set_function(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol);
+
+static int apq8039_function_parse_of(struct snd_soc_card *card,
+		struct apq8039_data *pdata)
+{
+	static const char *root_name = "function-definition";
+	struct device_node *rootnp, *np;
+	int res = -EINVAL, i = 0, j, count;
+	const char *str;
+
+	if (!card || !card->dev || !card->dev->of_node)
+		return -EINVAL;
+
+	rootnp = of_get_child_by_name(card->dev->of_node, root_name);
+	if (!rootnp) {
+		dev_err(card->dev, "No \"%s\" child node\n", root_name);
+		return -ENOENT;
+	}
+
+	res = of_property_count_elems_of_size(rootnp, "function-list",
+						sizeof(u32));
+	if (res <= 0) {
+		dev_err(card->dev, "%s %s property of \"%s\" node\n",
+				res < 0 ? "missing" : "invalid",
+				root_name, "function-list");
+		goto err_out;
+	}
+	count = res;
+
+	pdata->num_functions = count;
+	pdata->function_nodes = devm_kzalloc(card->dev,
+					sizeof(*pdata->function_nodes) * count,
+					GFP_KERNEL);
+	if (!pdata->function_nodes) {
+		dev_err(card->dev, "out of memory allocating function_nodes\n");
+		res = -ENOMEM;
+		goto err_out;
+	}
+
+	pdata->function_names = devm_kzalloc(card->dev,
+					sizeof(*pdata->function_names) *
+						pdata->num_functions,
+					GFP_KERNEL);
+	if (!pdata->function_names) {
+		dev_err(card->dev, "out of memory allocating function_names\n");
+		res = -ENOMEM;
+		goto err_out;
+	}
+
+
+	for (i = 0; i < pdata->num_functions; i++) {
+		np = of_parse_phandle(rootnp, "function-list", i);
+		if (!np) {
+			dev_err(card->dev,
+					"failed to parse \"%s\" phandle #%d\n",
+					"function-list", i);
+			res = -ENOENT;
+			goto err_out;
+		}
+		pdata->function_nodes[i] = np;
+	}
+
+	/* get the default, init, shutdown state(s) */
+	for (j = 0; j < 3; j++) {
+		np = of_parse_phandle(rootnp, "system-list", j);
+
+		/* init and shutdown are optional */
+		if (j > 0 && !np)
+			break;
+
+		if (!np) {
+			dev_err(card->dev,
+					"failed to parse \"%s\" phandle #%d\n",
+					"system-list", j);
+			res = -ENOENT;
+			goto err_out;
+		}
+		/* and verify it's one of the ones we declared */
+		for (i = 0; i < pdata->num_functions; i++) {
+			if (np == pdata->function_nodes[i])
+				break;
+		}
+		if (i >= pdata->num_functions) {
+			dev_err(card->dev,
+					"\"%s\" is not part of \"%s\"\n",
+					"system-list", "function-list");
+			res = -EINVAL;
+			of_node_put(np);
+			goto err_out;
+		}
+		switch (j) {
+		case 0:
+			pdata->function_node = np;
+			break;
+		case 1:
+			pdata->init_node = np;
+			break;
+		case 2:
+			pdata->shutdown_node = np;
+			break;
+		}
+	}
+
+	/* collect the function names */
+	for (i = 0; i < pdata->num_functions; i++)
+		pdata->function_names[i] = pdata->function_nodes[i]->name;
+
+	res = of_property_read_string(rootnp, "mixer-control", &str);
+	if (res) {
+		dev_err(card->dev, "failed to read string prop \"%s\"\n",
+			"mixer-control");
+		goto err_out;
+	}
+	pdata->control_name = devm_kstrdup(card->dev, str, GFP_KERNEL);
+	if (!pdata->control_name) {
+		res = -ENOMEM;
+		goto err_out;
+	}
+
+	pdata->func_enum[0] = (const struct soc_enum)
+				SOC_ENUM_SINGLE_EXT(pdata->num_functions,
+						    pdata->function_names);
+	pdata->func_control[0] = (const struct snd_kcontrol_new)
+				SOC_ENUM_EXT(pdata->control_name,
+					     pdata->func_enum[0],
+					     apq8039_get_function,
+					     apq8039_set_function);
+
+	/* note! cooked_function_node must be set */
+
+	of_node_put(rootnp);
+	return 0;
+
+err_out:
+	if (pdata->function_nodes) {
+		for (i = pdata->num_functions - 1; i >= 0; i--)
+			of_node_put(pdata->function_nodes[i]);
+		devm_kfree(card->dev, pdata->function_nodes);
+	}
+	pdata->function_nodes = NULL;
+	if (pdata->function_names)
+		devm_kfree(card->dev, pdata->function_names);
+	pdata->function_names = NULL;
+
+	of_node_put(pdata->function_node);
+	of_node_put(pdata->cooked_function_node);
+	of_node_put(pdata->init_node);
+	of_node_put(pdata->shutdown_node);
+
+	of_node_put(rootnp);
+	return res;
+}
+
+static int apq8039_jack_event(struct notifier_block *nb,
+		unsigned long event, void *data)
+{
+	struct snd_soc_jack *jack = (struct snd_soc_jack *)data;
+	struct snd_soc_card *card = jack->card;
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	int headphone, microphone;
+	bool modified;
+
+	headphone = (event & SND_JACK_HEADPHONE)  == SND_JACK_HEADPHONE;
+	microphone = (event & SND_JACK_MICROPHONE) == SND_JACK_MICROPHONE;
+
+	apq8039_state_input_set(card, "JACK_HEADPHONE", headphone);
+	apq8039_state_input_set(card, "JACK_MICROPHONE", microphone);
+
+	modified = false;
+	if (pdata->headphone_det != headphone) {
+		pdata->headphone_det = headphone;
+		modified = true;
+	}
+	if (pdata->microphone_det != microphone) {
+		pdata->microphone_det = headphone;
+		modified = true;
+	}
+
+	if (!modified)
+		return 0;
+
+	apq8039_select_function(card, pdata->function_node);
+
+	return 0;
+}
+
+static struct notifier_block apq8039_jack_nb = {
+	.notifier_call = apq8039_jack_event,
+};
+
+#define MIC_CTRL_TER_WS_SLAVE_SEL	BIT(21)
+#define MIC_CTRL_QUA_WS_SLAVE_SEL_10	BIT(17)
+#define MIC_CTRL_TLMM_SCLK_EN		BIT(1)
+#define	SPKR_CTL_PRI_WS_SLAVE_SEL_11	(BIT(17) | BIT(16))
+#define DEFAULT_MCLK_RATE		9600000
+
+static int apq8039_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_dai *codec_dai;
+	struct snd_soc_component *component;
+	struct snd_soc_card *card = rtd->card;
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	int i, rval;
+
+	switch (cpu_dai->id) {
+	case MI2S_PRIMARY:
+		writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11,
+			pdata->spkr_iomux);
+		break;
+
+	case MI2S_QUATERNARY:
+		writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 |
+			MIC_CTRL_TLMM_SCLK_EN,
+			pdata->mic_iomux);
+		break;
+	case MI2S_TERTIARY:
+		writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL |
+			MIC_CTRL_TLMM_SCLK_EN,
+			pdata->mic_iomux);
+
+		break;
+
+	default:
+		dev_err(card->dev, "unsupported cpu dai configuration\n");
+		return -EINVAL;
+
+	}
+
+	if (!pdata->jack_setup) {
+		struct snd_jack *jack;
+
+		rval = snd_soc_card_jack_new(card, "Headset Jack",
+					     SND_JACK_HEADSET |
+					     SND_JACK_HEADPHONE |
+					     SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+					     SND_JACK_BTN_2 | SND_JACK_BTN_3 |
+					     SND_JACK_BTN_4,
+					     &pdata->jack, NULL, 0);
+
+		if (rval < 0) {
+			dev_err(card->dev, "Unable to add Headphone Jack\n");
+			return rval;
+		}
+
+		jack = pdata->jack.jack;
+
+		snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+		snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+		snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+		snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+		pdata->jack_setup = true;
+
+		snd_soc_jack_notifier_register(&pdata->jack,
+						&apq8039_jack_nb);
+	}
+
+	for_each_rtd_codec_dais(rtd, i, codec_dai) {
+
+		component = codec_dai->component;
+		/* Set default mclk for internal codec */
+		rval = snd_soc_component_set_sysclk(component, 0, 0,
+					DEFAULT_MCLK_RATE, SND_SOC_CLOCK_IN);
+		if (rval != 0 && rval != -ENOTSUPP) {
+			dev_warn(card->dev, "Failed to set mclk: %d\n", rval);
+			return rval;
+		}
+		rval = snd_soc_component_set_jack(component,
+						&pdata->jack, NULL);
+		if (rval != 0 && rval != -ENOTSUPP) {
+			dev_warn(card->dev, "Failed to set jack: %d\n", rval);
+			return rval;
+		}
+	}
+
+	return 0;
+}
+
+static struct apq8039_data *apq8039_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 apq8039_data *data;
+	struct snd_soc_dai_link_component *dlc;
+	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);
+	}
+
+	/* count the number of dai links */
+	num_links = 0;
+	for_each_child_of_node(node, np) {
+		cpu = of_get_child_by_name(np, "cpu");
+		codec = of_get_child_by_name(np, "codec");
+
+		/* it's a dai link only if both cpu & codec nodes exist */
+		num_links += cpu && codec;
+
+		/* handles NULLs just fine */
+		of_node_put(cpu);
+		of_node_put(codec);
+	}
+
+	/* Allocate the private data and the DAI link array */
+	data = devm_kzalloc(dev,
+			    struct_size(data, dai_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) {
+			/* not an error, just not a dai node */
+			of_node_put(cpu);
+			of_node_put(codec);
+			continue;
+		}
+
+		dlc = devm_kzalloc(dev, 2 * sizeof(*dlc), GFP_KERNEL);
+		if (!dlc)
+			return ERR_PTR(-ENOMEM);
+
+		link->cpus	= &dlc[0];
+		link->platforms	= &dlc[1];
+
+		link->num_cpus		= 1;
+		link->num_platforms	= 1;
+
+		link->cpus->of_node = of_parse_phandle(cpu, "sound-dai", 0);
+		if (!link->cpus->of_node) {
+			dev_err(card->dev,
+					"error getting cpu phandle (%s)\n",
+					of_node_full_name(cpu));
+			ret = -EINVAL;
+			goto error;
+		}
+
+		ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name);
+		if (ret) {
+			dev_err(card->dev,
+					"error getting cpu dai name (%s)\n",
+					of_node_full_name(cpu));
+			goto error;
+		}
+
+		ret = snd_soc_of_get_dai_link_codecs(dev, codec, link);
+		if (ret < 0) {
+			dev_err(card->dev,
+					"error getting codec dai name (%s)\n",
+					of_node_full_name(codec));
+			goto error;
+		}
+
+		link->platforms->of_node = link->cpus->of_node;
+		ret = of_property_read_string(np, "link-name", &link->name);
+		if (ret) {
+			dev_err(card->dev, "error getting dai_link name\n");
+			goto error;
+		}
+
+		link->stream_name = link->name;
+		link->init = apq8039_dai_init;
+		link++;
+
+		of_node_put(cpu);
+		of_node_put(codec);
+	}
+	cpu = codec = NULL;
+
+	ret = apq8039_function_parse_of(card, data);
+	if (ret != 0)
+		goto error;
+
+	return data;
+
+ error:
+	of_node_put(np);
+	of_node_put(cpu);
+	of_node_put(codec);
+	return ERR_PTR(ret);
+}
+
+static int apq8039_get_function(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	int i;
+
+	for (i = 0; i < pdata->num_functions; i++) {
+		if (pdata->function_node == pdata->function_nodes[i]) {
+			ucontrol->value.enumerated.item[0] = i;
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static int apq8039_set_function(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	unsigned int target;
+
+	if (ucontrol->value.enumerated.item[0] >= pdata->num_functions)
+		return -EINVAL;
+
+	target = ucontrol->value.enumerated.item[0];
+	apq8039_select_function(card, pdata->function_nodes[target]);
+
+	return 0;
+}
+
+static int apq8039_platform_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct snd_soc_card *card;
+	struct apq8039_data *data;
+	struct resource *res;
+	int ret;
+
+	card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+	if (!card)
+		return -ENOMEM;
+
+	card->dev = dev;
+
+	data = apq8039_parse_of(card);
+	if (IS_ERR(data)) {
+		dev_err(&pdev->dev, "Error resolving dai links: %ld\n",
+			PTR_ERR(data));
+		return PTR_ERR(data);
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux");
+	data->mic_iomux = devm_ioremap_resource(dev, res);
+	if (IS_ERR(data->mic_iomux))
+		return PTR_ERR(data->mic_iomux);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux");
+	data->spkr_iomux = devm_ioremap_resource(dev, res);
+	if (IS_ERR(data->spkr_iomux))
+		return PTR_ERR(data->spkr_iomux);
+
+	data->headphone_det = -1;
+	data->microphone_det = -1;
+
+	INIT_LIST_HEAD(&data->ctl_list);
+	INIT_LIST_HEAD(&data->state_input_list);
+
+	card->controls = data->func_control;
+	card->num_controls = ARRAY_SIZE(data->func_control);
+
+	snd_soc_card_set_drvdata(card, data);
+
+	ret = devm_snd_soc_register_card(&pdev->dev, card);
+	if (ret)
+		return ret;
+
+	/* initialize first */
+	if (data->init_node)
+		apq8039_select_function(card, data->init_node);
+
+	/* select now */
+	if (data->function_node != data->init_node)
+		apq8039_select_function(card, data->function_node);
+
+	return 0;
+}
+
+static int apq8039_platform_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(&pdev->dev);
+	struct apq8039_data *pdata = snd_soc_card_get_drvdata(card);
+	int i;
+
+	/* the lists are using managed memory, no need to free them */
+	if (pdata) {
+
+		if (pdata->shutdown_node)
+			apq8039_select_function(card, pdata->shutdown_node);
+
+		of_node_put(pdata->function_node);
+		of_node_put(pdata->cooked_function_node);
+		of_node_put(pdata->init_node);
+		of_node_put(pdata->shutdown_node);
+		if (pdata->function_nodes) {
+			for (i = 0; i < pdata->num_functions; i++)
+				of_node_put(pdata->function_nodes[i]);
+		}
+	}
+
+	snd_soc_unregister_card(card);
+
+	return 0;
+}
+
+static const struct of_device_id apq8039_device_id[]  = {
+	{ .compatible = "qcom,apq8039-sndcard" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, apq8039_device_id);
+
+static struct platform_driver apq8039_platform_driver = {
+	.driver = {
+		.name = "qcom-apq8039",
+		.of_match_table = of_match_ptr(apq8039_device_id),
+	},
+	.probe = apq8039_platform_probe,
+	.remove = apq8039_platform_remove,
+};
+module_platform_driver(apq8039_platform_driver);
+
+MODULE_AUTHOR("Pantelis Antoniou <pantelis.antoniou@linaro.org");
+MODULE_DESCRIPTION("APQ8039 ASoC Machine Driver");
+MODULE_LICENSE("GPL v2");
