diff mbox

[v3,3/5] ASoC: qcom: add sdm845 sound card support

Message ID 1530870195-13576-4-git-send-email-rohitkr@codeaurora.org (mailing list archive)
State New, archived
Headers show

Commit Message

Rohit Kumar July 6, 2018, 9:43 a.m. UTC
This patch adds sdm845 audio machine driver support.

Signed-off-by: Rohit kumar <rohitkr@codeaurora.org>
---
 sound/soc/qcom/Kconfig  |  10 ++
 sound/soc/qcom/Makefile |   2 +
 sound/soc/qcom/sdm845.c | 390 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 402 insertions(+)
 create mode 100644 sound/soc/qcom/sdm845.c

Comments

Vinod Koul July 9, 2018, 7:48 a.m. UTC | #1
On 06-07-18, 15:13, Rohit kumar wrote:

> +static void sdm845_init_supplies(struct device *dev)
> +{
> +	struct snd_soc_card *card = dev_get_drvdata(dev);
> +	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
> +
> +	data->vdd_supply = regulator_get(dev, "cdc-vdd");
> +	if (IS_ERR(data->vdd_supply)) {
> +		dev_err(dev, "Unable to get regulator supplies\n");
> +		data->vdd_supply = NULL;
> +		return;
> +	}
> +
> +	if (regulator_enable(data->vdd_supply))
> +		dev_err(dev, "Unable to enable vdd supply\n");
> +}
> +
> +static void sdm845_deinit_supplies(struct device *dev)
> +{
> +	struct snd_soc_card *card = dev_get_drvdata(dev);
> +	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
> +
> +	if (!data->vdd_supply)
> +		return;
> +
> +	regulator_disable(data->vdd_supply);
> +	regulator_put(data->vdd_supply);
> +}

these two can be made generic, cant we make these common when we have
supplies present?

> +static int sdm845_bind(struct device *dev)
> +{
> +	struct snd_soc_card *card;
> +	struct sdm845_snd_data *data;
> +	int ret;
> +
> +	card = kzalloc(sizeof(*card), GFP_KERNEL);
> +	if (!card)
> +		return -ENOMEM;
> +
> +	/* Allocate the private data */
> +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	ret = component_bind_all(dev, card);
> +	if (ret) {
> +		dev_err(dev, "Audio components bind failed: %d\n", ret);
> +		goto bind_fail;
> +	}
> +
> +	dev_set_drvdata(dev, card);
> +	card->dev = dev;
> +	ret = qcom_snd_parse_of(card);
> +	if (ret) {
> +		dev_err(dev, "Error parsing OF data\n");
> +		goto parse_dt_fail;
> +	}
> +
> +	data->card = card;
> +	snd_soc_card_set_drvdata(card, data);
> +
> +	sdm845_add_be_ops(card);
> +
> +	sdm845_init_supplies(dev);
> +
> +	ret = snd_soc_register_card(card);
> +	if (ret) {
> +		dev_err(dev, "Sound card registration failed\n");
> +		goto register_card_fail;
> +	}
> +	return ret;
> +
> +register_card_fail:
> +	sdm845_deinit_supplies(dev);
> +	kfree(card->dai_link);
> +parse_dt_fail:
> +	component_unbind_all(dev, card);
> +bind_fail:
> +	kfree(data);
> +	kfree(card);
> +	return ret;
> +}

I would make a case for this to be moved into common too :)
Srinivas Kandagatla July 9, 2018, 9:06 a.m. UTC | #2
Minor Nits...

On 06/07/18 10:43, Rohit kumar wrote:
> +struct sdm845_snd_data {
> +	struct snd_soc_card *card;
> +	struct regulator *vdd_supply;
> +	uint32_t pri_mi2s_clk_count;
> +	uint32_t quat_tdm_clk_count;
> +};
> +
> +
Unnecessary extra line..
> +static unsigned int tdm_slot_offset[8] = {0, 4, 8, 12, 16, 20, 24, 28};
> +
> 
> +static struct platform_driver sdm845_snd_driver = {
> +	.probe = sdm845_snd_platform_probe,
> +	.remove = sdm845_snd_platform_remove,
> +	.driver = {
> +		.name = "msm-snd-sdm845",
> +		.pm = &sdm845_pm_ops,
> +		.owner = THIS_MODULE,
setting owner is not required here!

> +		.of_match_table = sdm845_snd_device_id,
> +	},
> +};
Rohit Kumar July 9, 2018, 10:46 a.m. UTC | #3
Thanks Vinod for reviewing.


On 7/9/2018 1:18 PM, Vinod wrote:
> On 06-07-18, 15:13, Rohit kumar wrote:
>
>> +static void sdm845_init_supplies(struct device *dev)
>> +{
>> +	struct snd_soc_card *card = dev_get_drvdata(dev);
>> +	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
>> +
>> +	data->vdd_supply = regulator_get(dev, "cdc-vdd");
>> +	if (IS_ERR(data->vdd_supply)) {
>> +		dev_err(dev, "Unable to get regulator supplies\n");
>> +		data->vdd_supply = NULL;
>> +		return;
>> +	}
>> +
>> +	if (regulator_enable(data->vdd_supply))
>> +		dev_err(dev, "Unable to enable vdd supply\n");
>> +}
>> +
>> +static void sdm845_deinit_supplies(struct device *dev)
>> +{
>> +	struct snd_soc_card *card = dev_get_drvdata(dev);
>> +	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
>> +
>> +	if (!data->vdd_supply)
>> +		return;
>> +
>> +	regulator_disable(data->vdd_supply);
>> +	regulator_put(data->vdd_supply);
>> +}
> these two can be made generic, cant we make these common when we have
> supplies present?

Actually we  need to move it to codec driver as suggested by Rob in v2 
patchset.
I will remove this in next spin.

>> +static int sdm845_bind(struct device *dev)
>> +{
>> +	struct snd_soc_card *card;
>> +	struct sdm845_snd_data *data;
>> +	int ret;
>> +
>> +	card = kzalloc(sizeof(*card), GFP_KERNEL);
>> +	if (!card)
>> +		return -ENOMEM;
>> +
>> +	/* Allocate the private data */
>> +	data = kzalloc(sizeof(*data), GFP_KERNEL);
>> +	if (!data)
>> +		return -ENOMEM;
>> +
>> +	ret = component_bind_all(dev, card);
>> +	if (ret) {
>> +		dev_err(dev, "Audio components bind failed: %d\n", ret);
>> +		goto bind_fail;
>> +	}
>> +
>> +	dev_set_drvdata(dev, card);
>> +	card->dev = dev;
>> +	ret = qcom_snd_parse_of(card);
>> +	if (ret) {
>> +		dev_err(dev, "Error parsing OF data\n");
>> +		goto parse_dt_fail;
>> +	}
>> +
>> +	data->card = card;
>> +	snd_soc_card_set_drvdata(card, data);
>> +
>> +	sdm845_add_be_ops(card);
>> +
>> +	sdm845_init_supplies(dev);
>> +
>> +	ret = snd_soc_register_card(card);
>> +	if (ret) {
>> +		dev_err(dev, "Sound card registration failed\n");
>> +		goto register_card_fail;
>> +	}
>> +	return ret;
>> +
>> +register_card_fail:
>> +	sdm845_deinit_supplies(dev);
>> +	kfree(card->dai_link);
>> +parse_dt_fail:
>> +	component_unbind_all(dev, card);
>> +bind_fail:
>> +	kfree(data);
>> +	kfree(card);
>> +	return ret;
>> +}
> I would make a case for this to be moved into common too :)

There are few platform specific APIs and structs here like struct 
sdm845_snd_data,
sdm845_add_be_ops() which needs to be initialized and assigned before 
soundcard
registration. Moving this complete API to common will restrict it. 
Please suggest.
Mark Brown July 9, 2018, 11:14 a.m. UTC | #4
On Fri, Jul 06, 2018 at 03:13:13PM +0530, Rohit kumar wrote:

> @@ -0,0 +1,390 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018, The Linux Foundation. All rights reserved.
> + */

Please make the entire comment block a C++ comment, it makes it look
more intentional.

> +static const struct component_master_ops sdm845_ops = {
> +	.bind = sdm845_bind,
> +	.unbind = sdm845_unbind,
> +};

Why is this using the component stuff rather than the normal support for
finding the components of audio cards?
Rohit Kumar July 9, 2018, 12:01 p.m. UTC | #5
Thanks Mark for reviewing.


On 7/9/2018 4:44 PM, Mark Brown wrote:
> On Fri, Jul 06, 2018 at 03:13:13PM +0530, Rohit kumar wrote:
>
>> @@ -0,0 +1,390 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (c) 2018, The Linux Foundation. All rights reserved.
>> + */
> Please make the entire comment block a C++ comment, it makes it look
> more intentional.

Sure, will make this change in next patchset.
>> +static const struct component_master_ops sdm845_ops = {
>> +	.bind = sdm845_bind,
>> +	.unbind = sdm845_unbind,
>> +};
> Why is this using the component stuff rather than the normal support for
> finding the components of audio cards?
>
QCOM soundcard is dependent on platform and cpu dais which gets 
registered only when ADSP is up.
This is the design being followed in 
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/sound/soc/qcom/apq8096.c?h=v4.18-rc4#n132
We too have the same dependency.
>
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

Thanks,
Rohit
Mark Brown July 9, 2018, 12:03 p.m. UTC | #6
On Mon, Jul 09, 2018 at 05:31:04PM +0530, Rohit Kumar wrote:
> On 7/9/2018 4:44 PM, Mark Brown wrote:
> > On Fri, Jul 06, 2018 at 03:13:13PM +0530, Rohit kumar wrote:

> > > +static const struct component_master_ops sdm845_ops = {
> > > +	.bind = sdm845_bind,
> > > +	.unbind = sdm845_unbind,
> > > +};

> > Why is this using the component stuff rather than the normal support for
> > finding the components of audio cards?

> QCOM soundcard is dependent on platform and cpu dais which gets registered
> only when ADSP is up.

This doesn't answer the question...

> This is the design being followed in https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/sound/soc/qcom/apq8096.c?h=v4.18-rc4#n132

Oh dear, missed that :(  This is also broken and should be fixed.
Srinivas Kandagatla July 9, 2018, 12:34 p.m. UTC | #7
On 09/07/18 12:14, Mark Brown wrote:
>> +static const struct component_master_ops sdm845_ops = {
>> +	.bind = sdm845_bind,
>> +	.unbind = sdm845_unbind,
>> +};
> Why is this using the component stuff rather than the normal support for
> finding the components of audio cards?
Could you elaborate this please?
Do you mean something like snd_soc_lookup_component()?  Or in general 
audio card binding during startup.

AFAIU, The issue with that mechanism or EPROBEDEFER is that it works 
only for first time.. for the second time(restart usecase) there are no 
hooks like bind/unbind.

The reason why we chose to use component framework is because of bind 
and unbind functionality. Am more than happy to rework on this if there 
is already a alternative mechanism in ASoC which can provide this.

The design we are aiming at is:
1> audio card to deregister when any of the DSP audio services go DOWN.
2> re-register audio card only when all the DSP audio services are UP.

Usecase is something like DSP start-stop or restart due to fatal errors.

thanks,
srini
Mark Brown July 9, 2018, 12:41 p.m. UTC | #8
On Mon, Jul 09, 2018 at 01:34:42PM +0100, Srinivas Kandagatla wrote:
> On 09/07/18 12:14, Mark Brown wrote:
> > > +static const struct component_master_ops sdm845_ops = {
> > > +	.bind = sdm845_bind,
> > > +	.unbind = sdm845_unbind,
> > > +};

> > Why is this using the component stuff rather than the normal support for
> > finding the components of audio cards?

> Could you elaborate this please?
> Do you mean something like snd_soc_lookup_component()?  Or in general audio
> card binding during startup.

Just the normal audio card binding during startup.

> AFAIU, The issue with that mechanism or EPROBEDEFER is that it works only
> for first time.. for the second time(restart usecase) there are no hooks
> like bind/unbind.

This is not the case, the card will be unbound at the ASoC level when
any of the components are removed and then probed again when they
reappear.

> The reason why we chose to use component framework is because of bind and
> unbind functionality. Am more than happy to rework on this if there is
> already a alternative mechanism in ASoC which can provide this.

The component framework is a generalization of what ASoC does.
Srinivas Kandagatla July 9, 2018, 12:47 p.m. UTC | #9
On 09/07/18 13:41, Mark Brown wrote:
> This is not the case, the card will be unbound at the ASoC level when
> any of the components are removed and then probed again when they
> reappear.
> 
I will give that a try now!

--srini
Srinivas Kandagatla July 9, 2018, 2:02 p.m. UTC | #10
On 09/07/18 13:41, Mark Brown wrote:
>> AFAIU, The issue with that mechanism or EPROBEDEFER is that it works only
>> for first time.. for the second time(restart usecase) there are no hooks
>> like bind/unbind.
> This is not the case, the card will be unbound at the ASoC level when
> any of the components are removed and then probed again when they
> reappear.
> 
I did try this and It works only for first time! May be am missing 
something!

snd_soc_component_del_unlocked() unregisters the sound card totally. so 
for the second time (After DSP stop) there is no registered sound card 
in place.. Am not sure how this is supposed to work?

The reason I think it works for the first time is because of EPROBEDEFER 
from the machine driver.

Here are the steps I do with DSP:

Step1: Start DSP, can see sound card after all the services are ON.
Step2: Stop DSP, there is no audio card.
Step3: Start DSP, I can see all the components in debugfs but not the 
sound card.

This is what I do w.r.t code:
 From machine driver in snd_soc_register_card() in probe()
 From Audio services driver snd_soc_register_component() in probe() and
snd_soc_unregister_component() in remove()

When DSP is stopped the audio services disappear and 
snd_soc_unregister_component() is invoked.


thanks,
srini
Vinod Koul July 9, 2018, 2:45 p.m. UTC | #11
On 09-07-18, 16:16, Rohit Kumar wrote:

> > > +static int sdm845_bind(struct device *dev)
> > > +{
> > > +	struct snd_soc_card *card;
> > > +	struct sdm845_snd_data *data;
> > > +	int ret;
> > > +
> > > +	card = kzalloc(sizeof(*card), GFP_KERNEL);
> > > +	if (!card)
> > > +		return -ENOMEM;
> > > +
> > > +	/* Allocate the private data */
> > > +	data = kzalloc(sizeof(*data), GFP_KERNEL);
> > > +	if (!data)
> > > +		return -ENOMEM;
> > > +
> > > +	ret = component_bind_all(dev, card);
> > > +	if (ret) {
> > > +		dev_err(dev, "Audio components bind failed: %d\n", ret);
> > > +		goto bind_fail;
> > > +	}
> > > +
> > > +	dev_set_drvdata(dev, card);
> > > +	card->dev = dev;
> > > +	ret = qcom_snd_parse_of(card);
> > > +	if (ret) {
> > > +		dev_err(dev, "Error parsing OF data\n");
> > > +		goto parse_dt_fail;
> > > +	}
> > > +
> > > +	data->card = card;
> > > +	snd_soc_card_set_drvdata(card, data);
> > > +
> > > +	sdm845_add_be_ops(card);
> > > +
> > > +	sdm845_init_supplies(dev);
> > > +
> > > +	ret = snd_soc_register_card(card);
> > > +	if (ret) {
> > > +		dev_err(dev, "Sound card registration failed\n");
> > > +		goto register_card_fail;
> > > +	}
> > > +	return ret;
> > > +
> > > +register_card_fail:
> > > +	sdm845_deinit_supplies(dev);
> > > +	kfree(card->dai_link);
> > > +parse_dt_fail:
> > > +	component_unbind_all(dev, card);
> > > +bind_fail:
> > > +	kfree(data);
> > > +	kfree(card);
> > > +	return ret;
> > > +}
> > I would make a case for this to be moved into common too :)
> 
> There are few platform specific APIs and structs here like struct
> sdm845_snd_data,
> sdm845_add_be_ops() which needs to be initialized and assigned before
> soundcard
> registration. Moving this complete API to common will restrict it. Please
> suggest.

Yes indeed, they can be split and done outside while the common stuff
use a 'core' object and use that to initialize. If you need to do some
driver step, you can invoke a callback.
Mark Brown July 9, 2018, 4:33 p.m. UTC | #12
On Mon, Jul 09, 2018 at 03:02:11PM +0100, Srinivas Kandagatla wrote:
> On 09/07/18 13:41, Mark Brown wrote:
> > > AFAIU, The issue with that mechanism or EPROBEDEFER is that it works only

> > This is not the case, the card will be unbound at the ASoC level when
> > any of the components are removed and then probed again when they
> > reappear.

> I did try this and It works only for first time! May be am missing
> something!

> snd_soc_component_del_unlocked() unregisters the sound card totally. so for
> the second time (After DSP stop) there is no registered sound card in
> place.. Am not sure how this is supposed to work?

> The reason I think it works for the first time is because of EPROBEDEFER
> from the machine driver.

Ugh, right - we ripped out that code because there's no sensible use
case for it so now we don't keep the cards on a list.  The expectation
is that if someone is going around removing bits of the card they can
probably figure out that they should be removing the card first.

In any case the place to implement this is in the core, there's nothing
special about your cards here.  Either the core should be using the
component framework or the card list should be resurrected and we open
code it.  This isn't something that's unique to your device.
Srinivas Kandagatla July 10, 2018, 10:59 a.m. UTC | #13
On 09/07/18 17:33, Mark Brown wrote:
> On Mon, Jul 09, 2018 at 03:02:11PM +0100, Srinivas Kandagatla wrote:
>> On 09/07/18 13:41, Mark Brown wrote:
>>>> AFAIU, The issue with that mechanism or EPROBEDEFER is that it works only
> 
>>> This is not the case, the card will be unbound at the ASoC level when
>>> any of the components are removed and then probed again when they
>>> reappear.
> 
>> I did try this and It works only for first time! May be am missing
>> something!
> 
>> snd_soc_component_del_unlocked() unregisters the sound card totally. so for
>> the second time (After DSP stop) there is no registered sound card in
>> place.. Am not sure how this is supposed to work?
> 
>> The reason I think it works for the first time is because of EPROBEDEFER
>> from the machine driver.
> 
> Ugh, right - we ripped out that code because there's no sensible use
> case for it so now we don't keep the cards on a list.  The expectation
> is that if someone is going around removing bits of the card they can
> probably figure out that they should be removing the card first.
> 
> In any case the place to implement this is in the core, there's nothing
> special about your cards here.  Either the core should be using the
> component framework or the card list should be resurrected and we open
> code it.  This isn't something that's unique to your device.
I totally agree with you, this functionality belongs to core!

I will explore both options and see how it goes.

thanks,
srini
>
diff mbox

Patch

diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index 0e364b4..cf55f0a 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -93,3 +93,13 @@  config SND_SOC_MSM8996
 
 config SND_SOC_QCOM_COMMON
 	tristate
+
+config SND_SOC_SDM845
+	tristate "SoC Machine driver for SDM845 boards"
+	select SND_SOC_QDSP6
+	select SND_SOC_QCOM_COMMON
+	default n
+	help
+	  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.
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
index 1bcdbee..c9699fb 100644
--- a/sound/soc/qcom/Makefile
+++ b/sound/soc/qcom/Makefile
@@ -14,10 +14,12 @@  obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
 snd-soc-storm-objs := storm.o
 snd-soc-apq8016-sbc-objs := apq8016_sbc.o
 snd-soc-apq8096-objs := apq8096.o
+snd-soc-sdm845-objs := sdm845.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_MSM8996) += snd-soc-apq8096.o
+obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o
 
 obj-$(CONFIG_SND_SOC_QCOM_COMMON) += common.o
 #DSP lib
diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c
new file mode 100644
index 0000000..9ee3558
--- /dev/null
+++ b/sound/soc/qcom/sdm845.c
@@ -0,0 +1,390 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/atomic.h>
+#include <linux/of_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/soc/qcom/apr.h>
+#include "common.h"
+#include "qdsp6/q6afe.h"
+
+#define DEFAULT_SAMPLE_RATE_48K		48000
+#define DEFAULT_MCLK_RATE		24576000
+#define DEFAULT_BCLK_RATE		1536000
+
+struct sdm845_snd_data {
+	struct snd_soc_card *card;
+	struct regulator *vdd_supply;
+	uint32_t pri_mi2s_clk_count;
+	uint32_t quat_tdm_clk_count;
+};
+
+
+static unsigned int tdm_slot_offset[8] = {0, 4, 8, 12, 16, 20, 24, 28};
+
+static int sdm845_tdm_snd_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_dai *cpu_dai = rtd->cpu_dai;
+	int ret = 0;
+	int channels, slot_width;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S32_LE:
+	case SNDRV_PCM_FORMAT_S24_LE:
+	case SNDRV_PCM_FORMAT_S16_LE:
+		slot_width = 32;
+		break;
+	default:
+		dev_err(rtd->dev, "%s: invalid param format 0x%x\n",
+				__func__, params_format(params));
+		return -EINVAL;
+	}
+
+	channels = params_channels(params);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3,
+				channels, slot_width);
+		if (ret < 0) {
+			dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n",
+					__func__, ret);
+			goto end;
+		}
+
+		ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL,
+				channels, tdm_slot_offset);
+		if (ret < 0) {
+			dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n",
+					__func__, ret);
+			goto end;
+		}
+	} else {
+		ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0,
+				channels, slot_width);
+		if (ret < 0) {
+			dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n",
+					__func__, ret);
+			goto end;
+		}
+
+		ret = snd_soc_dai_set_channel_map(cpu_dai, channels,
+				tdm_slot_offset, 0, NULL);
+		if (ret < 0) {
+			dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n",
+					__func__, ret);
+			goto end;
+		}
+	}
+end:
+	return ret;
+}
+
+static int sdm845_snd_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_dai *cpu_dai = rtd->cpu_dai;
+	int ret = 0;
+
+	switch (cpu_dai->id) {
+	case QUATERNARY_TDM_RX_0:
+	case QUATERNARY_TDM_TX_0:
+		ret = sdm845_tdm_snd_hw_params(substream, params);
+		break;
+	default:
+		pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
+		break;
+	}
+	return ret;
+}
+
+static int sdm845_snd_startup(struct snd_pcm_substream *substream)
+{
+	unsigned int fmt = SND_SOC_DAIFMT_CBS_CFS;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	switch (cpu_dai->id) {
+	case PRIMARY_MI2S_RX:
+	case PRIMARY_MI2S_TX:
+		if (++(data->pri_mi2s_clk_count) == 1) {
+			snd_soc_dai_set_sysclk(cpu_dai,
+				Q6AFE_LPASS_CLK_ID_MCLK_1,
+				DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+			snd_soc_dai_set_sysclk(cpu_dai,
+				Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
+				DEFAULT_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+		}
+		snd_soc_dai_set_fmt(cpu_dai, fmt);
+		break;
+
+	case QUATERNARY_TDM_RX_0:
+	case QUATERNARY_TDM_TX_0:
+		if (++(data->quat_tdm_clk_count) == 1) {
+			snd_soc_dai_set_sysclk(cpu_dai,
+				Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT,
+				DEFAULT_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+		}
+		break;
+
+	default:
+		pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
+		break;
+	}
+	return 0;
+}
+
+static void  sdm845_snd_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	switch (cpu_dai->id) {
+	case PRIMARY_MI2S_RX:
+	case PRIMARY_MI2S_TX:
+		if (--(data->pri_mi2s_clk_count) == 0) {
+			snd_soc_dai_set_sysclk(cpu_dai,
+				Q6AFE_LPASS_CLK_ID_MCLK_1,
+				0, SNDRV_PCM_STREAM_PLAYBACK);
+			snd_soc_dai_set_sysclk(cpu_dai,
+				Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT,
+				0, SNDRV_PCM_STREAM_PLAYBACK);
+		};
+		break;
+
+	case QUATERNARY_TDM_RX_0:
+	case QUATERNARY_TDM_TX_0:
+		if (--(data->quat_tdm_clk_count) == 0) {
+			snd_soc_dai_set_sysclk(cpu_dai,
+				Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT,
+				0, SNDRV_PCM_STREAM_PLAYBACK);
+		}
+		break;
+
+	default:
+		pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id);
+		break;
+	}
+}
+
+static struct snd_soc_ops sdm845_be_ops = {
+	.hw_params = sdm845_snd_hw_params,
+	.startup = sdm845_snd_startup,
+	.shutdown = sdm845_snd_shutdown,
+};
+
+static int sdm845_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
+				struct snd_pcm_hw_params *params)
+{
+	struct snd_interval *rate = hw_param_interval(params,
+					SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval *channels = hw_param_interval(params,
+					SNDRV_PCM_HW_PARAM_CHANNELS);
+
+	rate->min = rate->max = DEFAULT_SAMPLE_RATE_48K;
+	channels->min = channels->max = 2;
+
+	return 0;
+}
+
+static void sdm845_init_supplies(struct device *dev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(dev);
+	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+
+	data->vdd_supply = regulator_get(dev, "cdc-vdd");
+	if (IS_ERR(data->vdd_supply)) {
+		dev_err(dev, "Unable to get regulator supplies\n");
+		data->vdd_supply = NULL;
+		return;
+	}
+
+	if (regulator_enable(data->vdd_supply))
+		dev_err(dev, "Unable to enable vdd supply\n");
+}
+
+static void sdm845_deinit_supplies(struct device *dev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(dev);
+	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+
+	if (!data->vdd_supply)
+		return;
+
+	regulator_disable(data->vdd_supply);
+	regulator_put(data->vdd_supply);
+}
+
+static void sdm845_add_be_ops(struct snd_soc_card *card)
+{
+	struct snd_soc_dai_link *link = card->dai_link;
+	int i, num_links = card->num_links;
+
+	for (i = 0; i < num_links; i++) {
+		if (link->no_pcm == 1) {
+			link->ops = &sdm845_be_ops;
+			link->be_hw_params_fixup = sdm845_be_hw_params_fixup;
+		}
+		link++;
+	}
+}
+
+static int sdm845_bind(struct device *dev)
+{
+	struct snd_soc_card *card;
+	struct sdm845_snd_data *data;
+	int ret;
+
+	card = kzalloc(sizeof(*card), GFP_KERNEL);
+	if (!card)
+		return -ENOMEM;
+
+	/* Allocate the private data */
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	ret = component_bind_all(dev, card);
+	if (ret) {
+		dev_err(dev, "Audio components bind failed: %d\n", ret);
+		goto bind_fail;
+	}
+
+	dev_set_drvdata(dev, card);
+	card->dev = dev;
+	ret = qcom_snd_parse_of(card);
+	if (ret) {
+		dev_err(dev, "Error parsing OF data\n");
+		goto parse_dt_fail;
+	}
+
+	data->card = card;
+	snd_soc_card_set_drvdata(card, data);
+
+	sdm845_add_be_ops(card);
+
+	sdm845_init_supplies(dev);
+
+	ret = snd_soc_register_card(card);
+	if (ret) {
+		dev_err(dev, "Sound card registration failed\n");
+		goto register_card_fail;
+	}
+	return ret;
+
+register_card_fail:
+	sdm845_deinit_supplies(dev);
+	kfree(card->dai_link);
+parse_dt_fail:
+	component_unbind_all(dev, card);
+bind_fail:
+	kfree(data);
+	kfree(card);
+	return ret;
+}
+
+static void sdm845_unbind(struct device *dev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(dev);
+	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+
+	if (data->vdd_supply)
+		regulator_put(data->vdd_supply);
+	component_unbind_all(dev, card);
+	snd_soc_unregister_card(card);
+	kfree(card->dai_link);
+	kfree(data);
+	kfree(card);
+}
+
+static const struct component_master_ops sdm845_ops = {
+	.bind = sdm845_bind,
+	.unbind = sdm845_unbind,
+};
+
+static int sdm845_runtime_resume(struct device *dev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(dev);
+	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+
+	if (!data->vdd_supply) {
+		dev_dbg(dev, "no supplies defined\n");
+		return 0;
+	}
+
+	if (regulator_enable(data->vdd_supply))
+		dev_err(dev, "Enable regulator supply failed\n");
+
+	return 0;
+}
+
+static int sdm845_runtime_suspend(struct device *dev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(dev);
+	struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card);
+
+	if (!data->vdd_supply) {
+		dev_dbg(dev, "no supplies defined\n");
+		return 0;
+	}
+
+	if (regulator_disable(data->vdd_supply))
+		dev_err(dev, "Disable regulator supply failed\n");
+
+	return 0;
+}
+
+static const struct dev_pm_ops sdm845_pm_ops = {
+	SET_RUNTIME_PM_OPS(sdm845_runtime_suspend,
+			sdm845_runtime_resume, NULL)
+};
+
+static int sdm845_snd_platform_probe(struct platform_device *pdev)
+{
+	struct component_match *match = NULL;
+	int ret;
+
+	ret = qcom_snd_add_components(&pdev->dev, &match);
+	if (ret)
+		return ret;
+
+	return component_master_add_with_match(&pdev->dev, &sdm845_ops, match);
+}
+
+static int sdm845_snd_platform_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &sdm845_ops);
+	return 0;
+}
+
+static const struct of_device_id sdm845_snd_device_id[]  = {
+	{ .compatible = "qcom,sdm845-sndcard" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, sdm845_snd_device_id);
+
+static struct platform_driver sdm845_snd_driver = {
+	.probe = sdm845_snd_platform_probe,
+	.remove = sdm845_snd_platform_remove,
+	.driver = {
+		.name = "msm-snd-sdm845",
+		.pm = &sdm845_pm_ops,
+		.owner = THIS_MODULE,
+		.of_match_table = sdm845_snd_device_id,
+	},
+};
+module_platform_driver(sdm845_snd_driver);
+
+MODULE_DESCRIPTION("sdm845 ASoC Machine Driver");
+MODULE_LICENSE("GPL v2");