diff mbox

[RFC,2/4] ASoC: Intel: Add merrifield machine driver

Message ID 1399312908-20744-3-git-send-email-vinod.koul@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Vinod Koul May 5, 2014, 6:01 p.m. UTC
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
---
 sound/soc/intel/Kconfig                  |   17 +
 sound/soc/intel/board/Makefile           |    4 +
 sound/soc/intel/board/merr_dpcm_wm8958.c |  619 ++++++++++++++++++++++++++++++
 3 files changed, 640 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/intel/board/Makefile
 create mode 100644 sound/soc/intel/board/merr_dpcm_wm8958.c

Comments

Girdwood, Liam R May 6, 2014, 3:17 p.m. UTC | #1
On Mon, 2014-05-05 at 23:31 +0530, Vinod Koul wrote:
> Signed-off-by: Vinod Koul <vinod.koul@intel.com>
> ---
>  sound/soc/intel/Kconfig                  |   17 +
>  sound/soc/intel/board/Makefile           |    4 +
>  sound/soc/intel/board/merr_dpcm_wm8958.c |  619 ++++++++++++++++++++++++++++++
>  3 files changed, 640 insertions(+), 0 deletions(-)
>  create mode 100644 sound/soc/intel/board/Makefile
>  create mode 100644 sound/soc/intel/board/merr_dpcm_wm8958.c
> 
> diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig
> index 3c81b38..422c32d 100644
> --- a/sound/soc/intel/Kconfig
> +++ b/sound/soc/intel/Kconfig
> @@ -49,3 +49,20 @@ config SND_SOC_INTEL_BYT_RT5640_MACH
>  	help
>  	  This adds audio driver for Intel Baytrail platform based boards
>  	  with the RT5640 audio codec.
> +
> +config SND_SOC_INTEL_MRFLD_WM8958_MACH
> +	tristate "SOC Machine Audio driver for Intel Merrifield MID platform"
> +	depends on X86
> +	select SND_SOC_WM8994
> +	select MFD_CORE
> +	select MFD_WM8994
> +	select REGULATOR_WM8994
> +	select SND_SST_MFLD_PLATFORM
> +	select SND_SST_MACHINE
> +	select SND_INTEL_SST
> +	default n
> +	help
> +	  This adds support for ASoC machine driver for Intel(R) MID Merrifield platform
> +          used as alsa device in audio substem in Intel(R) MID devices
> +          Say Y if you have such a device
> +          If unsure select "N".
> diff --git a/sound/soc/intel/board/Makefile b/sound/soc/intel/board/Makefile
> new file mode 100644
> index 0000000..fdfbecf
> --- /dev/null
> +++ b/sound/soc/intel/board/Makefile
> @@ -0,0 +1,4 @@
> +# Merrifield board
> +snd-merr-dpcm-wm8958-objs := merr_dpcm_wm8958.o
> +
> +obj-$(CONFIG_SND_SOC_INTEL_MRFLD_WM8958_MACH) += snd-merr-dpcm-wm8958.o
> diff --git a/sound/soc/intel/board/merr_dpcm_wm8958.c b/sound/soc/intel/board/merr_dpcm_wm8958.c
> new file mode 100644
> index 0000000..4c17746
> --- /dev/null
> +++ b/sound/soc/intel/board/merr_dpcm_wm8958.c
> @@ -0,0 +1,619 @@
> +/*
> + *  merr_dpcm_wm8958.c - ASoc DPCM Machine driver for Intel Merrfield MID platform
> + *
> + *  Copyright (C) 2013-14 Intel Corp
> + *  Author: Vinod Koul <vinod.koul@intel.com>
> + *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + *
> + *  This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation; version 2 of the License.
> + *
> + *  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.
> + *
> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + */
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/device.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/async.h>
> +#include <linux/delay.h>
> +#include <linux/gpio.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +#include <sound/soc.h>
> +#include <sound/jack.h>
> +#include <linux/input.h>
> +
> +#include <linux/mfd/wm8994/core.h>
> +#include <linux/mfd/wm8994/registers.h>
> +#include <linux/mfd/wm8994/pdata.h>
> +#include "../../codecs/wm8994.h"
> +#include "../platform-libs/controls_v2.h"
> +
> +/* Codec PLL output clk rate */
> +#define CODEC_SYSCLK_RATE			24576000
> +/* Input clock to codec at MCLK1 PIN */
> +#define CODEC_IN_MCLK1_RATE			19200000
> +/* Input clock to codec at MCLK2 PIN */
> +#define CODEC_IN_MCLK2_RATE			32768
> +/*  define to select between MCLK1 and MCLK2 input to codec as its clock */
> +#define CODEC_IN_MCLK1				1
> +#define CODEC_IN_MCLK2				2
> +
> +struct mrfld_8958_mc_private {
> +	struct snd_soc_jack jack;
> +	int jack_retry;
> +};
> +
> +
> +static inline struct snd_soc_codec *mrfld_8958_get_codec(struct snd_soc_card *card)
> +{
> +	bool found = false;
> +	struct snd_soc_codec *codec;
> +
> +	list_for_each_entry(codec, &card->codec_dev_list, card_list) {
> +		if (!strstr(codec->name, "wm8994-codec")) {
> +			pr_debug("codec was %s", codec->name);
> +			continue;
> +		} else {
> +			found = true;
> +			break;
> +		}
> +	}
> +	if (found == false) {
> +		pr_warn("%s: cant find codec", __func__);
> +		return NULL;
> +	}
> +	return codec;
> +}
> +
> +/* TODO: find better way of doing this */
> +static struct snd_soc_dai *find_codec_dai(struct snd_soc_card *card, const char *dai_name)
> +{
> +	int i;
> +	for (i = 0; i < card->num_rtd; i++) {
> +		if (!strcmp(card->rtd[i].codec_dai->name, dai_name))
> +			return card->rtd[i].codec_dai;
> +	}
> +	pr_err("%s: unable to find codec dai\n", __func__);
> +	/* this should never occur */
> +	WARN_ON(1);
> +	return NULL;
> +}
> +

It would be good if we could eventually make the above 2 calls generic
in core. Seem to be quite useful functions.

> +/* Function to switch the input clock for codec,  When audio is in
> + * progress input clock to codec will be through MCLK1 which is 19.2MHz
> + * while in off state input clock to codec will be through 32KHz through
> + * MCLK2
> + * card	: Sound card structure
> + * src	: Input clock source to codec
> + */
> +static int mrfld_8958_set_codec_clk(struct snd_soc_card *card, int src)
> +{
> +	struct snd_soc_dai *aif1_dai = find_codec_dai(card, "wm8994-aif1");
> +	int ret;
> +
> +	if (!aif1_dai)
> +		return -ENODEV;
> +
> +	switch (src) {
> +	case CODEC_IN_MCLK1:
> +		/* Turn ON the PLL to generate required sysclk rate
> +		 * from MCLK1 */
> +		ret = snd_soc_dai_set_pll(aif1_dai,
> +			WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
> +			CODEC_IN_MCLK1_RATE, CODEC_SYSCLK_RATE);
> +		if (ret < 0) {
> +			pr_err("Failed to start FLL: %d\n", ret);
> +			return ret;
> +		}
> +		/* Switch to MCLK1 input */
> +		ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1,
> +				CODEC_SYSCLK_RATE, SND_SOC_CLOCK_IN);
> +		if (ret < 0) {
> +			pr_err("Failed to set codec sysclk configuration %d\n",
> +				 ret);
> +			return ret;
> +		}
> +		break;
> +	case CODEC_IN_MCLK2:
> +		/* Switch to MCLK2 */
> +		ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
> +				32768, SND_SOC_CLOCK_IN);
> +		if (ret < 0) {
> +			pr_err("Failed to switch to MCLK2: %d", ret);
> +			return ret;
> +		}
> +		/* Turn off PLL for MCLK1 */
> +		ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
> +		if (ret < 0) {
> +			pr_err("Failed to stop the FLL: %d", ret);
> +			return ret;
> +		}
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +	return 0;
> +}
> +
> +static int mrfld_wm8958_set_clk_fmt(struct snd_soc_dai *codec_dai)
> +{
> +	unsigned int fmt;
> +	int ret = 0;
> +	struct snd_soc_card *card = codec_dai->card;
> +
> +	pr_err("setting snd_soc_dai_set_tdm_slot\n");
> +	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 4, SNDRV_PCM_FORMAT_S24_LE);
> +	if (ret < 0) {
> +		pr_err("can't set codec pcm format %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* WM8958 slave Mode */
> +	fmt =   SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
> +		| SND_SOC_DAIFMT_CBS_CFS;
> +	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
> +	if (ret < 0) {
> +		pr_err("can't set codec DAI configuration %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* FIXME: move this to SYS_CLOCK event handler when codec driver
> +	 * dependency is clean.
> +	 */
> +	/* Switch to 19.2MHz MCLK1 input clock for codec */
> +	ret = mrfld_8958_set_codec_clk(card, CODEC_IN_MCLK1);
> +
> +	return ret;
> +}
> +
> +static int mrfld_8958_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 *codec_dai = rtd->codec_dai;
> +
> +	if (!strcmp(codec_dai->name, "wm8994-aif1"))
> +		return mrfld_wm8958_set_clk_fmt(codec_dai);
> +	return 0;
> +}
> +
> +static int mrfld_wm8958_compr_set_params(struct snd_compr_stream *cstream)
> +{
> +	return 0;
> +}

Do we need to implement this if it's not used ?


---------------------------------------------------------------------
Intel Corporation (UK) Limited
Registered No. 1134945 (England)
Registered Office: Pipers Way, Swindon SN3 1RJ
VAT No: 860 2173 47

This e-mail and any attachments may contain confidential material for
the sole use of the intended recipient(s). Any review or distribution
by others is strictly prohibited. If you are not the intended
recipient, please contact the sender and delete all copies.
Lars-Peter Clausen May 6, 2014, 3:54 p.m. UTC | #2
On 05/05/2014 08:01 PM, Vinod Koul wrote:
[...]
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/device.h>
> +#include <linux/slab.h>
> +#include <linux/io.h>
> +#include <linux/async.h>
> +#include <linux/delay.h>
> +#include <linux/gpio.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +#include <sound/soc.h>
> +#include <sound/jack.h>
> +#include <linux/input.h>
> +
> +#include <linux/mfd/wm8994/core.h>
> +#include <linux/mfd/wm8994/registers.h>
> +#include <linux/mfd/wm8994/pdata.h>
> +#include "../../codecs/wm8994.h"
> +#include "../platform-libs/controls_v2.h"

I don't think that include exists in upstream.

> +
> +/* Codec PLL output clk rate */
> +#define CODEC_SYSCLK_RATE			24576000
> +/* Input clock to codec at MCLK1 PIN */
> +#define CODEC_IN_MCLK1_RATE			19200000
> +/* Input clock to codec at MCLK2 PIN */
> +#define CODEC_IN_MCLK2_RATE			32768
> +/*  define to select between MCLK1 and MCLK2 input to codec as its clock */
> +#define CODEC_IN_MCLK1				1
> +#define CODEC_IN_MCLK2				2
> +
> +struct mrfld_8958_mc_private {
> +	struct snd_soc_jack jack;
> +	int jack_retry;
> +};
> +
> +
> +static inline struct snd_soc_codec *mrfld_8958_get_codec(struct snd_soc_card *card)
> +{
> +	bool found = false;
> +	struct snd_soc_codec *codec;
> +
> +	list_for_each_entry(codec, &card->codec_dev_list, card_list) {
> +		if (!strstr(codec->name, "wm8994-codec")) {
> +			pr_debug("codec was %s", codec->name);
> +			continue;
> +		} else {
> +			found = true;
> +			break;
> +		}
> +	}
> +	if (found == false) {
> +		pr_warn("%s: cant find codec", __func__);
> +		return NULL;
> +	}
> +	return codec;
> +}
> +
> +/* TODO: find better way of doing this */

Yes!

> +static struct snd_soc_dai *find_codec_dai(struct snd_soc_card *card, const char *dai_name)
> +{
> +	int i;
> +	for (i = 0; i < card->num_rtd; i++) {
> +		if (!strcmp(card->rtd[i].codec_dai->name, dai_name))
> +			return card->rtd[i].codec_dai;
> +	}
> +	pr_err("%s: unable to find codec dai\n", __func__);
> +	/* this should never occur */
> +	WARN_ON(1);
> +	return NULL;
> +}

The proper way to do this is to implement the init callback for the dai 
link. There you get a pointer to the codec and the dai and everything else. 
If you need one for later store them in the private struct of the card driver.


> +
> +/* Function to switch the input clock for codec,  When audio is in
> + * progress input clock to codec will be through MCLK1 which is 19.2MHz
> + * while in off state input clock to codec will be through 32KHz through
> + * MCLK2
> + * card	: Sound card structure
> + * src	: Input clock source to codec

That's not proper kerneldoc

> + */
[...]
> +
> +static int mrfld_wm8958_set_clk_fmt(struct snd_soc_dai *codec_dai)
> +{
> +	unsigned int fmt;
> +	int ret = 0;
> +	struct snd_soc_card *card = codec_dai->card;
> +
> +	pr_err("setting snd_soc_dai_set_tdm_slot\n");
> +	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 4, SNDRV_PCM_FORMAT_S24_LE);

the slot width should be in number of bit clock cycles.

> +	if (ret < 0) {
> +		pr_err("can't set codec pcm format %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* WM8958 slave Mode */
> +	fmt =   SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
> +		| SND_SOC_DAIFMT_CBS_CFS;
> +	ret = snd_soc_dai_set_fmt(codec_dai, fmt);

Use the dai_fmt field of the dai_link to set this up.

> +	if (ret < 0) {
> +		pr_err("can't set codec DAI configuration %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* FIXME: move this to SYS_CLOCK event handler when codec driver
> +	 * dependency is clean.
> +	 */
> +	/* Switch to 19.2MHz MCLK1 input clock for codec */
> +	ret = mrfld_8958_set_codec_clk(card, CODEC_IN_MCLK1);
> +
> +	return ret;
> +}
[...]
> +static int mrfld_8958_init(struct snd_soc_pcm_runtime *runtime)
> +{
> +	int ret;
> +	unsigned int fmt;
> +	struct snd_soc_codec *codec;
> +	struct snd_soc_card *card = runtime->card;
> +	struct snd_soc_dai *aif1_dai = find_codec_dai(card, "wm8994-aif1");
> +	struct mrfld_8958_mc_private *ctx = snd_soc_card_get_drvdata(card);
> +
> +	if (!aif1_dai)
> +		return -ENODEV;
> +
> +	pr_debug("Entry %s\n", __func__);
> +
> +	ret = snd_soc_dai_set_tdm_slot(aif1_dai, 0, 0, 4, SNDRV_PCM_FORMAT_S24_LE);
> +	if (ret < 0) {
> +		pr_err("can't set codec pcm format %d\n", ret);
> +		return ret;
> +	}
> +
> +	/* WM8958 slave Mode */
> +	fmt =   SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
> +		| SND_SOC_DAIFMT_CBS_CFS;
> +	ret = snd_soc_dai_set_fmt(aif1_dai, fmt);

Same commens as above.

> +	if (ret < 0) {
> +		pr_err("can't set codec DAI configuration %d\n", ret);
> +		return ret;
> +	}
> +
> +	mrfld_8958_set_bias_level(card, &card->dapm, SND_SOC_BIAS_OFF);
> +	card->dapm.idle_bias_off = true;
> +
> +	/* these pins are not used in SB config so mark as nc
> +	 *
> +	 * LINEOUT1, 2
> +	 * IN1R
> +	 * DMICDAT2
> +	 */
> +	snd_soc_dapm_nc_pin(&card->dapm, "DMIC2DAT");
> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1P");
> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1N");
> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2P");
> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2N");
> +	snd_soc_dapm_nc_pin(&card->dapm, "IN1RN");
> +	snd_soc_dapm_nc_pin(&card->dapm, "IN1RP");

Set the fully_routed flag on the card and this will happen automatically.

> +
> +	/* Force enable VMID to avoid cold latency constraints */
> +	snd_soc_dapm_force_enable_pin(&card->dapm, "VMID");
> +	snd_soc_dapm_sync(&card->dapm);
> +
> +	codec = mrfld_8958_get_codec(card);
> +	if (!codec) {
> +		pr_err("%s: we didnt find the codec pointer!\n", __func__);
> +		return 0;
> +	}
> +
> +	ctx->jack_retry = 0;
> +	ret = snd_soc_jack_new(codec, "Intel MID Audio Jack",
> +			       SND_JACK_HEADSET | SND_JACK_HEADPHONE |
> +				SND_JACK_BTN_0 | SND_JACK_BTN_1,
> +				&ctx->jack);
> +	if (ret) {
> +		pr_err("jack creation failed\n");
> +		return ret;
> +	}
> +
> +	snd_jack_set_key(ctx->jack.jack, SND_JACK_BTN_1, KEY_MEDIA);
> +	snd_jack_set_key(ctx->jack.jack, SND_JACK_BTN_0, KEY_MEDIA);
> +
> +	wm8958_mic_detect(codec, &ctx->jack, NULL, NULL, NULL, NULL);
> +
> +	snd_soc_update_bits(codec, WM8994_AIF1_DAC1_FILTERS_1, WM8994_AIF1DAC1_MUTE, 0);
> +	snd_soc_update_bits(codec, WM8994_AIF1_DAC2_FILTERS_1, WM8994_AIF1DAC2_MUTE, 0);

The card driver should not be poking in the codec registers.

> +
> +	/* Micbias1 is always off, so for pm optimizations make sure the micbias1
> +	 * discharge bit is set to floating to avoid discharge in disable state
> +	 */
> +	snd_soc_update_bits(codec, WM8958_MICBIAS1, WM8958_MICB1_DISCH, 0);
> +
> +	return 0;
> +}
> +
[...]
> +
> +static int snd_mrfld_8958_mc_probe(struct platform_device *pdev)
> +{
> +	int ret_val = 0;
> +	struct mrfld_8958_mc_private *drv;
> +
> +	pr_debug("Entry %s\n", __func__);
> +
> +	drv = kzalloc(sizeof(*drv), GFP_ATOMIC);

GFP_KERNEL and maybe devm_kzalloc

> +	if (!drv) {
> +		pr_err("allocation failed\n");
> +		return -ENOMEM;
> +	}
> +
> +	/* register the soc card */
> +	snd_soc_card_mrfld.dev = &pdev->dev;
> +	snd_soc_card_set_drvdata(&snd_soc_card_mrfld, drv);
> +	ret_val = snd_soc_register_card(&snd_soc_card_mrfld);
> +	if (ret_val) {
> +		pr_err("snd_soc_register_card failed %d\n", ret_val);
> +		goto unalloc;
> +	}
> +	platform_set_drvdata(pdev, &snd_soc_card_mrfld);
> +	pr_info("%s successful\n", __func__);
> +	return ret_val;
> +
> +unalloc:
> +	kfree(drv);
> +	return ret_val;
> +}
[...]
> +
> +static int snd_mrfld_8958_driver_init(void)
> +{
> +	pr_info("Merrifield Machine Driver mrfld_wm8958 registerd\n");

That's just noise.

> +	return platform_driver_register(&snd_mrfld_8958_mc_driver);
> +}
> +
> +static void snd_mrfld_8958_driver_exit(void)
> +{
> +	pr_debug("In %s\n", __func__);
> +	platform_driver_unregister(&snd_mrfld_8958_mc_driver);
> +}
> +late_initcall(snd_mrfld_8958_driver_init);
> +module_exit(snd_mrfld_8958_driver_exit);

module_platform_driver().
Vinod Koul May 6, 2014, 4:49 p.m. UTC | #3
On Tue, May 06, 2014 at 04:17:19PM +0100, Liam Girdwood wrote:
> On Mon, 2014-05-05 at 23:31 +0530, Vinod Koul wrote:

> > +/* TODO: find better way of doing this */
> > +static struct snd_soc_dai *find_codec_dai(struct snd_soc_card *card, const char *dai_name)
> > +{
> > +	int i;
> > +	for (i = 0; i < card->num_rtd; i++) {
> > +		if (!strcmp(card->rtd[i].codec_dai->name, dai_name))
> > +			return card->rtd[i].codec_dai;
> > +	}
> > +	pr_err("%s: unable to find codec dai\n", __func__);
> > +	/* this should never occur */
> > +	WARN_ON(1);
> > +	return NULL;
> > +}
> > +
> 
> It would be good if we could eventually make the above 2 calls generic
> in core. Seem to be quite useful functions.
Yes I didnt do that as I wanted to check if we have a better method or not. With
multi-codec systems we dont have single codec for the card so finding this would
need lookup or else... Somehow I think we should have simpler solution, Mark??


> > +static int mrfld_wm8958_compr_set_params(struct snd_compr_stream *cstream)
> > +{
> > +	return 0;
> > +}
> 
> Do we need to implement this if it's not used ?
My bad, this was supposed to be removed but... :)
Vinod Koul May 6, 2014, 4:58 p.m. UTC | #4
On Tue, May 06, 2014 at 05:54:53PM +0200, Lars-Peter Clausen wrote:
> On 05/05/2014 08:01 PM, Vinod Koul wrote:
> [...]
> >+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> >+
> >+#include <linux/module.h>
> >+#include <linux/init.h>
> >+#include <linux/device.h>
> >+#include <linux/slab.h>
> >+#include <linux/io.h>
> >+#include <linux/async.h>
> >+#include <linux/delay.h>
> >+#include <linux/gpio.h>
> >+#include <sound/pcm.h>
> >+#include <sound/pcm_params.h>
> >+#include <sound/soc.h>
> >+#include <sound/jack.h>
> >+#include <linux/input.h>
> >+
> >+#include <linux/mfd/wm8994/core.h>
> >+#include <linux/mfd/wm8994/registers.h>
> >+#include <linux/mfd/wm8994/pdata.h>
> >+#include "../../codecs/wm8994.h"
> >+#include "../platform-libs/controls_v2.h"
> 
> I don't think that include exists in upstream.
which one ../platform-libs/controls_v2.h is part of this patch.

> 
> >+
> >+/* Codec PLL output clk rate */
> >+#define CODEC_SYSCLK_RATE			24576000
> >+/* Input clock to codec at MCLK1 PIN */
> >+#define CODEC_IN_MCLK1_RATE			19200000
> >+/* Input clock to codec at MCLK2 PIN */
> >+#define CODEC_IN_MCLK2_RATE			32768
> >+/*  define to select between MCLK1 and MCLK2 input to codec as its clock */
> >+#define CODEC_IN_MCLK1				1
> >+#define CODEC_IN_MCLK2				2
> >+
> >+struct mrfld_8958_mc_private {
> >+	struct snd_soc_jack jack;
> >+	int jack_retry;
> >+};
> >+
> >+
> >+static inline struct snd_soc_codec *mrfld_8958_get_codec(struct snd_soc_card *card)
> >+{
> >+	bool found = false;
> >+	struct snd_soc_codec *codec;
> >+
> >+	list_for_each_entry(codec, &card->codec_dev_list, card_list) {
> >+		if (!strstr(codec->name, "wm8994-codec")) {
> >+			pr_debug("codec was %s", codec->name);
> >+			continue;
> >+		} else {
> >+			found = true;
> >+			break;
> >+		}
> >+	}
> >+	if (found == false) {
> >+		pr_warn("%s: cant find codec", __func__);
> >+		return NULL;
> >+	}
> >+	return codec;
> >+}
> >+
> >+/* TODO: find better way of doing this */
> 
> Yes!
Am all ears :)

> 
> >+static struct snd_soc_dai *find_codec_dai(struct snd_soc_card *card, const char *dai_name)
> >+{
> >+	int i;
> >+	for (i = 0; i < card->num_rtd; i++) {
> >+		if (!strcmp(card->rtd[i].codec_dai->name, dai_name))
> >+			return card->rtd[i].codec_dai;
> >+	}
> >+	pr_err("%s: unable to find codec dai\n", __func__);
> >+	/* this should never occur */
> >+	WARN_ON(1);
> >+	return NULL;
> >+}
> 
> The proper way to do this is to implement the init callback for the
> dai link. There you get a pointer to the codec and the dai and
> everything else. If you need one for later store them in the private
> struct of the card driver.
again the driver would need to store the pointers to cards (we multi codec
systems) and multi dais. Somehow I dont feel this might be worth thr trouble.
Earlier we always had card->codec pointing to _one_ codec but with multi-codec
systems that is not the case, so we need ot lookup, but yes am not sure if above
is best way or something else..

> 
> 
> >+
> >+/* Function to switch the input clock for codec,  When audio is in
> >+ * progress input clock to codec will be through MCLK1 which is 19.2MHz
> >+ * while in off state input clock to codec will be through 32KHz through
> >+ * MCLK2
> >+ * card	: Sound card structure
> >+ * src	: Input clock source to codec
> 
> That's not proper kerneldoc
Ah that shouldnt have slipped, will fix.

> 
> >+ */
> [...]
> >+
> >+static int mrfld_wm8958_set_clk_fmt(struct snd_soc_dai *codec_dai)
> >+{
> >+	unsigned int fmt;
> >+	int ret = 0;
> >+	struct snd_soc_card *card = codec_dai->card;
> >+
> >+	pr_err("setting snd_soc_dai_set_tdm_slot\n");
> >+	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 4, SNDRV_PCM_FORMAT_S24_LE);
> 
> the slot width should be in number of bit clock cycles.
ok

> 
> >+	if (ret < 0) {
> >+		pr_err("can't set codec pcm format %d\n", ret);
> >+		return ret;
> >+	}
> >+
> >+	/* WM8958 slave Mode */
> >+	fmt =   SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
> >+		| SND_SOC_DAIFMT_CBS_CFS;
> >+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
> 
> Use the dai_fmt field of the dai_link to set this up.
ok

> 
> >+	if (ret < 0) {
> >+		pr_err("can't set codec DAI configuration %d\n", ret);
> >+		return ret;
> >+	}
> >+
> >+	/* FIXME: move this to SYS_CLOCK event handler when codec driver
> >+	 * dependency is clean.
> >+	 */
> >+	/* Switch to 19.2MHz MCLK1 input clock for codec */
> >+	ret = mrfld_8958_set_codec_clk(card, CODEC_IN_MCLK1);
> >+
> >+	return ret;
> >+}
> [...]
> >+static int mrfld_8958_init(struct snd_soc_pcm_runtime *runtime)
> >+{
> >+	int ret;
> >+	unsigned int fmt;
> >+	struct snd_soc_codec *codec;
> >+	struct snd_soc_card *card = runtime->card;
> >+	struct snd_soc_dai *aif1_dai = find_codec_dai(card, "wm8994-aif1");
> >+	struct mrfld_8958_mc_private *ctx = snd_soc_card_get_drvdata(card);
> >+
> >+	if (!aif1_dai)
> >+		return -ENODEV;
> >+
> >+	pr_debug("Entry %s\n", __func__);
> >+
> >+	ret = snd_soc_dai_set_tdm_slot(aif1_dai, 0, 0, 4, SNDRV_PCM_FORMAT_S24_LE);
> >+	if (ret < 0) {
> >+		pr_err("can't set codec pcm format %d\n", ret);
> >+		return ret;
> >+	}
> >+
> >+	/* WM8958 slave Mode */
> >+	fmt =   SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
> >+		| SND_SOC_DAIFMT_CBS_CFS;
> >+	ret = snd_soc_dai_set_fmt(aif1_dai, fmt);
> 
> Same commens as above.
> 
> >+	if (ret < 0) {
> >+		pr_err("can't set codec DAI configuration %d\n", ret);
> >+		return ret;
> >+	}
> >+
> >+	mrfld_8958_set_bias_level(card, &card->dapm, SND_SOC_BIAS_OFF);
> >+	card->dapm.idle_bias_off = true;
> >+
> >+	/* these pins are not used in SB config so mark as nc
> >+	 *
> >+	 * LINEOUT1, 2
> >+	 * IN1R
> >+	 * DMICDAT2
> >+	 */
> >+	snd_soc_dapm_nc_pin(&card->dapm, "DMIC2DAT");
> >+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1P");
> >+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1N");
> >+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2P");
> >+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2N");
> >+	snd_soc_dapm_nc_pin(&card->dapm, "IN1RN");
> >+	snd_soc_dapm_nc_pin(&card->dapm, "IN1RP");
> 
> Set the fully_routed flag on the card and this will happen automatically.
I havent looked thos changes up, will this mark as nc asll the unrouted pins?

> 
> >+
> >+	/* Force enable VMID to avoid cold latency constraints */
> >+	snd_soc_dapm_force_enable_pin(&card->dapm, "VMID");
> >+	snd_soc_dapm_sync(&card->dapm);
> >+
> >+	codec = mrfld_8958_get_codec(card);
> >+	if (!codec) {
> >+		pr_err("%s: we didnt find the codec pointer!\n", __func__);
> >+		return 0;
> >+	}
> >+
> >+	ctx->jack_retry = 0;
> >+	ret = snd_soc_jack_new(codec, "Intel MID Audio Jack",
> >+			       SND_JACK_HEADSET | SND_JACK_HEADPHONE |
> >+				SND_JACK_BTN_0 | SND_JACK_BTN_1,
> >+				&ctx->jack);
> >+	if (ret) {
> >+		pr_err("jack creation failed\n");
> >+		return ret;
> >+	}
> >+
> >+	snd_jack_set_key(ctx->jack.jack, SND_JACK_BTN_1, KEY_MEDIA);
> >+	snd_jack_set_key(ctx->jack.jack, SND_JACK_BTN_0, KEY_MEDIA);
> >+
> >+	wm8958_mic_detect(codec, &ctx->jack, NULL, NULL, NULL, NULL);
> >+
> >+	snd_soc_update_bits(codec, WM8994_AIF1_DAC1_FILTERS_1, WM8994_AIF1DAC1_MUTE, 0);
> >+	snd_soc_update_bits(codec, WM8994_AIF1_DAC2_FILTERS_1, WM8994_AIF1DAC2_MUTE, 0);
> 
> The card driver should not be poking in the codec registers.
ah yes with DPCM we dont need this

> 
> >+
> >+	/* Micbias1 is always off, so for pm optimizations make sure the micbias1
> >+	 * discharge bit is set to floating to avoid discharge in disable state
> >+	 */
> >+	snd_soc_update_bits(codec, WM8958_MICBIAS1, WM8958_MICB1_DISCH, 0);
> >+
> >+	return 0;
> >+}
> >+
> [...]
> >+
> >+static int snd_mrfld_8958_mc_probe(struct platform_device *pdev)
> >+{
> >+	int ret_val = 0;
> >+	struct mrfld_8958_mc_private *drv;
> >+
> >+	pr_debug("Entry %s\n", __func__);
> >+
> >+	drv = kzalloc(sizeof(*drv), GFP_ATOMIC);
> 
> GFP_KERNEL and maybe devm_kzalloc
devm_ yes
> 
> >+	if (!drv) {
> >+		pr_err("allocation failed\n");
> >+		return -ENOMEM;
> >+	}
> >+
> >+	/* register the soc card */
> >+	snd_soc_card_mrfld.dev = &pdev->dev;
> >+	snd_soc_card_set_drvdata(&snd_soc_card_mrfld, drv);
> >+	ret_val = snd_soc_register_card(&snd_soc_card_mrfld);
> >+	if (ret_val) {
> >+		pr_err("snd_soc_register_card failed %d\n", ret_val);
> >+		goto unalloc;
> >+	}
> >+	platform_set_drvdata(pdev, &snd_soc_card_mrfld);
> >+	pr_info("%s successful\n", __func__);
> >+	return ret_val;
> >+
> >+unalloc:
> >+	kfree(drv);
> >+	return ret_val;
> >+}
> [...]
> >+
> >+static int snd_mrfld_8958_driver_init(void)
> >+{
> >+	pr_info("Merrifield Machine Driver mrfld_wm8958 registerd\n");
> 
> That's just noise.
> 
> >+	return platform_driver_register(&snd_mrfld_8958_mc_driver);
> >+}
> >+
> >+static void snd_mrfld_8958_driver_exit(void)
> >+{
> >+	pr_debug("In %s\n", __func__);
> >+	platform_driver_unregister(&snd_mrfld_8958_mc_driver);
> >+}
> >+late_initcall(snd_mrfld_8958_driver_init);
> >+module_exit(snd_mrfld_8958_driver_exit);
> 
> module_platform_driver().
yes!
Lars-Peter Clausen May 6, 2014, 6:17 p.m. UTC | #5
On 05/06/2014 06:58 PM, Vinod Koul wrote:
> On Tue, May 06, 2014 at 05:54:53PM +0200, Lars-Peter Clausen wrote:
>> On 05/05/2014 08:01 PM, Vinod Koul wrote:
>> [...]
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/init.h>
>>> +#include <linux/device.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/io.h>
>>> +#include <linux/async.h>
>>> +#include <linux/delay.h>
>>> +#include <linux/gpio.h>
>>> +#include <sound/pcm.h>
>>> +#include <sound/pcm_params.h>
>>> +#include <sound/soc.h>
>>> +#include <sound/jack.h>
>>> +#include <linux/input.h>
>>> +
>>> +#include <linux/mfd/wm8994/core.h>
>>> +#include <linux/mfd/wm8994/registers.h>
>>> +#include <linux/mfd/wm8994/pdata.h>
>>> +#include "../../codecs/wm8994.h"
>>> +#include "../platform-libs/controls_v2.h"
>>
>> I don't think that include exists in upstream.
> which one ../platform-libs/controls_v2.h is part of this patch.

I just saw it's part of patch 4 of this series. But still, it should added 
before the user is added.

[..]
>>
>>> +static struct snd_soc_dai *find_codec_dai(struct snd_soc_card *card, const char *dai_name)
>>> +{
>>> +	int i;
>>> +	for (i = 0; i < card->num_rtd; i++) {
>>> +		if (!strcmp(card->rtd[i].codec_dai->name, dai_name))
>>> +			return card->rtd[i].codec_dai;
>>> +	}
>>> +	pr_err("%s: unable to find codec dai\n", __func__);
>>> +	/* this should never occur */
>>> +	WARN_ON(1);
>>> +	return NULL;
>>> +}
>>
>> The proper way to do this is to implement the init callback for the
>> dai link. There you get a pointer to the codec and the dai and
>> everything else. If you need one for later store them in the private
>> struct of the card driver.
> again the driver would need to store the pointers to cards (we multi codec
> systems) and multi dais. Somehow I dont feel this might be worth thr trouble.
> Earlier we always had card->codec pointing to _one_ codec but with multi-codec
> systems that is not the case, so we need ot lookup, but yes am not sure if above
> is best way or something else..

Another thing you could do is just use card->rtd[X].codec_dai. The rtds are 
in the same order as the DAI links so you know which rtd is for which DAI.

[...]
>>> +	snd_soc_dapm_nc_pin(&card->dapm, "DMIC2DAT");
>>> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1P");
>>> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1N");
>>> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2P");
>>> +	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2N");
>>> +	snd_soc_dapm_nc_pin(&card->dapm, "IN1RN");
>>> +	snd_soc_dapm_nc_pin(&card->dapm, "IN1RP");
>>
>> Set the fully_routed flag on the card and this will happen automatically.
> I havent looked thos changes up, will this mark as nc asll the unrouted pins?

It will mark any input, output and micbias pin that has no external 
connection as not connected. Which is typically what you want.

- Lars
Vinod Koul May 7, 2014, 4:51 a.m. UTC | #6
On Tue, May 06, 2014 at 08:17:18PM +0200, Lars-Peter Clausen wrote:
> On 05/06/2014 06:58 PM, Vinod Koul wrote:
> >On Tue, May 06, 2014 at 05:54:53PM +0200, Lars-Peter Clausen wrote:
> >>On 05/05/2014 08:01 PM, Vinod Koul wrote:
> >>[...]
> >>>+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> >>>+
> >>>+#include <linux/module.h>
> >>>+#include <linux/init.h>
> >>>+#include <linux/device.h>
> >>>+#include <linux/slab.h>
> >>>+#include <linux/io.h>
> >>>+#include <linux/async.h>
> >>>+#include <linux/delay.h>
> >>>+#include <linux/gpio.h>
> >>>+#include <sound/pcm.h>
> >>>+#include <sound/pcm_params.h>
> >>>+#include <sound/soc.h>
> >>>+#include <sound/jack.h>
> >>>+#include <linux/input.h>
> >>>+
> >>>+#include <linux/mfd/wm8994/core.h>
> >>>+#include <linux/mfd/wm8994/registers.h>
> >>>+#include <linux/mfd/wm8994/pdata.h>
> >>>+#include "../../codecs/wm8994.h"
> >>>+#include "../platform-libs/controls_v2.h"
> >>
> >>I don't think that include exists in upstream.
> >which one ../platform-libs/controls_v2.h is part of this patch.
> 
> I just saw it's part of patch 4 of this series. But still, it should
> added before the user is added.
Yes :)

And i dont use in below code so would remove for now...
Later additions to thsi would use these defines so would amke sense to add then.

> 
> [..]
> >>
> >>>+static struct snd_soc_dai *find_codec_dai(struct snd_soc_card *card, const char *dai_name)
> >>>+{
> >>>+	int i;
> >>>+	for (i = 0; i < card->num_rtd; i++) {
> >>>+		if (!strcmp(card->rtd[i].codec_dai->name, dai_name))
> >>>+			return card->rtd[i].codec_dai;
> >>>+	}
> >>>+	pr_err("%s: unable to find codec dai\n", __func__);
> >>>+	/* this should never occur */
> >>>+	WARN_ON(1);
> >>>+	return NULL;
> >>>+}
> >>
> >>The proper way to do this is to implement the init callback for the
> >>dai link. There you get a pointer to the codec and the dai and
> >>everything else. If you need one for later store them in the private
> >>struct of the card driver.
> >again the driver would need to store the pointers to cards (we multi codec
> >systems) and multi dais. Somehow I dont feel this might be worth thr trouble.
> >Earlier we always had card->codec pointing to _one_ codec but with multi-codec
> >systems that is not the case, so we need ot lookup, but yes am not sure if above
> >is best way or something else..
> 
> Another thing you could do is just use card->rtd[X].codec_dai. The
> rtds are in the same order as the DAI links so you know which rtd is
> for which DAI.
that should work too..
Mark, Any comments on these approaches before I modify the code?
diff mbox

Patch

diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig
index 3c81b38..422c32d 100644
--- a/sound/soc/intel/Kconfig
+++ b/sound/soc/intel/Kconfig
@@ -49,3 +49,20 @@  config SND_SOC_INTEL_BYT_RT5640_MACH
 	help
 	  This adds audio driver for Intel Baytrail platform based boards
 	  with the RT5640 audio codec.
+
+config SND_SOC_INTEL_MRFLD_WM8958_MACH
+	tristate "SOC Machine Audio driver for Intel Merrifield MID platform"
+	depends on X86
+	select SND_SOC_WM8994
+	select MFD_CORE
+	select MFD_WM8994
+	select REGULATOR_WM8994
+	select SND_SST_MFLD_PLATFORM
+	select SND_SST_MACHINE
+	select SND_INTEL_SST
+	default n
+	help
+	  This adds support for ASoC machine driver for Intel(R) MID Merrifield platform
+          used as alsa device in audio substem in Intel(R) MID devices
+          Say Y if you have such a device
+          If unsure select "N".
diff --git a/sound/soc/intel/board/Makefile b/sound/soc/intel/board/Makefile
new file mode 100644
index 0000000..fdfbecf
--- /dev/null
+++ b/sound/soc/intel/board/Makefile
@@ -0,0 +1,4 @@ 
+# Merrifield board
+snd-merr-dpcm-wm8958-objs := merr_dpcm_wm8958.o
+
+obj-$(CONFIG_SND_SOC_INTEL_MRFLD_WM8958_MACH) += snd-merr-dpcm-wm8958.o
diff --git a/sound/soc/intel/board/merr_dpcm_wm8958.c b/sound/soc/intel/board/merr_dpcm_wm8958.c
new file mode 100644
index 0000000..4c17746
--- /dev/null
+++ b/sound/soc/intel/board/merr_dpcm_wm8958.c
@@ -0,0 +1,619 @@ 
+/*
+ *  merr_dpcm_wm8958.c - ASoc DPCM Machine driver for Intel Merrfield MID platform
+ *
+ *  Copyright (C) 2013-14 Intel Corp
+ *  Author: Vinod Koul <vinod.koul@intel.com>
+ *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; version 2 of the License.
+ *
+ *  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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/async.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <linux/input.h>
+
+#include <linux/mfd/wm8994/core.h>
+#include <linux/mfd/wm8994/registers.h>
+#include <linux/mfd/wm8994/pdata.h>
+#include "../../codecs/wm8994.h"
+#include "../platform-libs/controls_v2.h"
+
+/* Codec PLL output clk rate */
+#define CODEC_SYSCLK_RATE			24576000
+/* Input clock to codec at MCLK1 PIN */
+#define CODEC_IN_MCLK1_RATE			19200000
+/* Input clock to codec at MCLK2 PIN */
+#define CODEC_IN_MCLK2_RATE			32768
+/*  define to select between MCLK1 and MCLK2 input to codec as its clock */
+#define CODEC_IN_MCLK1				1
+#define CODEC_IN_MCLK2				2
+
+struct mrfld_8958_mc_private {
+	struct snd_soc_jack jack;
+	int jack_retry;
+};
+
+
+static inline struct snd_soc_codec *mrfld_8958_get_codec(struct snd_soc_card *card)
+{
+	bool found = false;
+	struct snd_soc_codec *codec;
+
+	list_for_each_entry(codec, &card->codec_dev_list, card_list) {
+		if (!strstr(codec->name, "wm8994-codec")) {
+			pr_debug("codec was %s", codec->name);
+			continue;
+		} else {
+			found = true;
+			break;
+		}
+	}
+	if (found == false) {
+		pr_warn("%s: cant find codec", __func__);
+		return NULL;
+	}
+	return codec;
+}
+
+/* TODO: find better way of doing this */
+static struct snd_soc_dai *find_codec_dai(struct snd_soc_card *card, const char *dai_name)
+{
+	int i;
+	for (i = 0; i < card->num_rtd; i++) {
+		if (!strcmp(card->rtd[i].codec_dai->name, dai_name))
+			return card->rtd[i].codec_dai;
+	}
+	pr_err("%s: unable to find codec dai\n", __func__);
+	/* this should never occur */
+	WARN_ON(1);
+	return NULL;
+}
+
+/* Function to switch the input clock for codec,  When audio is in
+ * progress input clock to codec will be through MCLK1 which is 19.2MHz
+ * while in off state input clock to codec will be through 32KHz through
+ * MCLK2
+ * card	: Sound card structure
+ * src	: Input clock source to codec
+ */
+static int mrfld_8958_set_codec_clk(struct snd_soc_card *card, int src)
+{
+	struct snd_soc_dai *aif1_dai = find_codec_dai(card, "wm8994-aif1");
+	int ret;
+
+	if (!aif1_dai)
+		return -ENODEV;
+
+	switch (src) {
+	case CODEC_IN_MCLK1:
+		/* Turn ON the PLL to generate required sysclk rate
+		 * from MCLK1 */
+		ret = snd_soc_dai_set_pll(aif1_dai,
+			WM8994_FLL1, WM8994_FLL_SRC_MCLK1,
+			CODEC_IN_MCLK1_RATE, CODEC_SYSCLK_RATE);
+		if (ret < 0) {
+			pr_err("Failed to start FLL: %d\n", ret);
+			return ret;
+		}
+		/* Switch to MCLK1 input */
+		ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1,
+				CODEC_SYSCLK_RATE, SND_SOC_CLOCK_IN);
+		if (ret < 0) {
+			pr_err("Failed to set codec sysclk configuration %d\n",
+				 ret);
+			return ret;
+		}
+		break;
+	case CODEC_IN_MCLK2:
+		/* Switch to MCLK2 */
+		ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2,
+				32768, SND_SOC_CLOCK_IN);
+		if (ret < 0) {
+			pr_err("Failed to switch to MCLK2: %d", ret);
+			return ret;
+		}
+		/* Turn off PLL for MCLK1 */
+		ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0);
+		if (ret < 0) {
+			pr_err("Failed to stop the FLL: %d", ret);
+			return ret;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int mrfld_wm8958_set_clk_fmt(struct snd_soc_dai *codec_dai)
+{
+	unsigned int fmt;
+	int ret = 0;
+	struct snd_soc_card *card = codec_dai->card;
+
+	pr_err("setting snd_soc_dai_set_tdm_slot\n");
+	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 4, SNDRV_PCM_FORMAT_S24_LE);
+	if (ret < 0) {
+		pr_err("can't set codec pcm format %d\n", ret);
+		return ret;
+	}
+
+	/* WM8958 slave Mode */
+	fmt =   SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
+		| SND_SOC_DAIFMT_CBS_CFS;
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+	if (ret < 0) {
+		pr_err("can't set codec DAI configuration %d\n", ret);
+		return ret;
+	}
+
+	/* FIXME: move this to SYS_CLOCK event handler when codec driver
+	 * dependency is clean.
+	 */
+	/* Switch to 19.2MHz MCLK1 input clock for codec */
+	ret = mrfld_8958_set_codec_clk(card, CODEC_IN_MCLK1);
+
+	return ret;
+}
+
+static int mrfld_8958_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 *codec_dai = rtd->codec_dai;
+
+	if (!strcmp(codec_dai->name, "wm8994-aif1"))
+		return mrfld_wm8958_set_clk_fmt(codec_dai);
+	return 0;
+}
+
+static int mrfld_wm8958_compr_set_params(struct snd_compr_stream *cstream)
+{
+	return 0;
+}
+
+static const struct snd_soc_pcm_stream mrfld_wm8958_dai_params = {
+	.formats = SNDRV_PCM_FMTBIT_S24_LE,
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+};
+
+static int merr_codec_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);
+
+	pr_debug("Invoked %s for dailink %s\n", __func__, rtd->dai_link->name);
+
+	/* The DSP will covert the FE rate to 48k, stereo, 24bits */
+	rate->min = rate->max = 48000;
+	channels->min = channels->max = 2;
+
+	/* set SSP2 to 24-bit */
+	snd_mask_set(&params->masks[SNDRV_PCM_HW_PARAM_FORMAT -
+				    SNDRV_PCM_HW_PARAM_FIRST_MASK],
+				    SNDRV_PCM_FORMAT_S24_LE);
+	return 0;
+}
+
+static int mrfld_8958_set_bias_level(struct snd_soc_card *card,
+				struct snd_soc_dapm_context *dapm,
+				enum snd_soc_bias_level level)
+{
+	struct snd_soc_dai *aif1_dai = find_codec_dai(card, "wm8994-aif1");
+	int ret = 0;
+
+	if (!aif1_dai)
+		return -ENODEV;
+
+	if (dapm->dev != aif1_dai->dev)
+		return 0;
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (card->dapm.bias_level == SND_SOC_BIAS_STANDBY)
+
+			ret = mrfld_wm8958_set_clk_fmt(aif1_dai);
+		break;
+	default:
+		break;
+	}
+	pr_debug("%s card(%s)->bias_level %u\n", __func__, card->name,
+			card->dapm.bias_level);
+	return ret;
+}
+
+static int mrfld_8958_set_bias_level_post(struct snd_soc_card *card,
+		 struct snd_soc_dapm_context *dapm,
+		 enum snd_soc_bias_level level)
+{
+	struct snd_soc_dai *aif1_dai = find_codec_dai(card, "wm8994-aif1");
+	int ret = 0;
+
+	if (!aif1_dai)
+		return -ENODEV;
+
+	if (dapm->dev != aif1_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_STANDBY:
+		/* We are in stabdba down so */
+		/* Switch to 32KHz MCLK2 input clock for codec
+		 */
+		ret = mrfld_8958_set_codec_clk(card, CODEC_IN_MCLK2);
+		break;
+	default:
+		break;
+	}
+	card->dapm.bias_level = level;
+	pr_debug("%s card(%s)->bias_level %u\n", __func__, card->name,
+			card->dapm.bias_level);
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget widgets[] = {
+	SND_SOC_DAPM_HP("Headphones", NULL),
+	SND_SOC_DAPM_MIC("AMIC", NULL),
+	SND_SOC_DAPM_MIC("DMIC", NULL),
+};
+
+static const struct snd_soc_dapm_route map[] = {
+	{ "Headphones", NULL, "HPOUT1L" },
+	{ "Headphones", NULL, "HPOUT1R" },
+
+	/* saltbay uses 2 DMICs, other configs may use more so change below
+	 * accordingly
+	 */
+	{ "DMIC1DAT", NULL, "DMIC" },
+	{ "DMIC2DAT", NULL, "DMIC" },
+	/*{ "DMIC3DAT", NULL, "DMIC" },*/
+	/*{ "DMIC4DAT", NULL, "DMIC" },*/
+
+	/* MICBIAS2 is connected as Bias for AMIC so we link it
+	 * here. Also AMIC wires up to IN1LP pin.
+	 * DMIC is externally connected to 1.8V rail, so no link rqd.
+	 */
+	{ "AMIC", NULL, "MICBIAS2" },
+	{ "IN1LP", NULL, "AMIC" },
+
+	/* SWM map link the SWM outs to codec AIF */
+	{ "AIF1 Playback", NULL, "ssp2 Tx"},
+	{ "ssp2 Tx", NULL, "codec_out0"},
+	{ "ssp2 Tx", NULL, "codec_out1"},
+	{ "codec_in0", NULL, "ssp2 Rx" },
+	{ "codec_in1", NULL, "ssp2 Rx" },
+	{ "ssp2 Rx", NULL, "AIF1 Capture"},
+
+};
+
+static const struct wm8958_micd_rate micdet_rates[] = {
+	{ 32768,       true,  1, 4 },
+	{ 32768,       false, 1, 1 },
+	{ 44100 * 256, true,  7, 10 },
+	{ 44100 * 256, false, 7, 10 },
+};
+
+static int mrfld_8958_init(struct snd_soc_pcm_runtime *runtime)
+{
+	int ret;
+	unsigned int fmt;
+	struct snd_soc_codec *codec;
+	struct snd_soc_card *card = runtime->card;
+	struct snd_soc_dai *aif1_dai = find_codec_dai(card, "wm8994-aif1");
+	struct mrfld_8958_mc_private *ctx = snd_soc_card_get_drvdata(card);
+
+	if (!aif1_dai)
+		return -ENODEV;
+
+	pr_debug("Entry %s\n", __func__);
+
+	ret = snd_soc_dai_set_tdm_slot(aif1_dai, 0, 0, 4, SNDRV_PCM_FORMAT_S24_LE);
+	if (ret < 0) {
+		pr_err("can't set codec pcm format %d\n", ret);
+		return ret;
+	}
+
+	/* WM8958 slave Mode */
+	fmt =   SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF
+		| SND_SOC_DAIFMT_CBS_CFS;
+	ret = snd_soc_dai_set_fmt(aif1_dai, fmt);
+	if (ret < 0) {
+		pr_err("can't set codec DAI configuration %d\n", ret);
+		return ret;
+	}
+
+	mrfld_8958_set_bias_level(card, &card->dapm, SND_SOC_BIAS_OFF);
+	card->dapm.idle_bias_off = true;
+
+	/* these pins are not used in SB config so mark as nc
+	 *
+	 * LINEOUT1, 2
+	 * IN1R
+	 * DMICDAT2
+	 */
+	snd_soc_dapm_nc_pin(&card->dapm, "DMIC2DAT");
+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1P");
+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT1N");
+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2P");
+	snd_soc_dapm_nc_pin(&card->dapm, "LINEOUT2N");
+	snd_soc_dapm_nc_pin(&card->dapm, "IN1RN");
+	snd_soc_dapm_nc_pin(&card->dapm, "IN1RP");
+
+	/* Force enable VMID to avoid cold latency constraints */
+	snd_soc_dapm_force_enable_pin(&card->dapm, "VMID");
+	snd_soc_dapm_sync(&card->dapm);
+
+	codec = mrfld_8958_get_codec(card);
+	if (!codec) {
+		pr_err("%s: we didnt find the codec pointer!\n", __func__);
+		return 0;
+	}
+
+	ctx->jack_retry = 0;
+	ret = snd_soc_jack_new(codec, "Intel MID Audio Jack",
+			       SND_JACK_HEADSET | SND_JACK_HEADPHONE |
+				SND_JACK_BTN_0 | SND_JACK_BTN_1,
+				&ctx->jack);
+	if (ret) {
+		pr_err("jack creation failed\n");
+		return ret;
+	}
+
+	snd_jack_set_key(ctx->jack.jack, SND_JACK_BTN_1, KEY_MEDIA);
+	snd_jack_set_key(ctx->jack.jack, SND_JACK_BTN_0, KEY_MEDIA);
+
+	wm8958_mic_detect(codec, &ctx->jack, NULL, NULL, NULL, NULL);
+
+	snd_soc_update_bits(codec, WM8994_AIF1_DAC1_FILTERS_1, WM8994_AIF1DAC1_MUTE, 0);
+	snd_soc_update_bits(codec, WM8994_AIF1_DAC2_FILTERS_1, WM8994_AIF1DAC2_MUTE, 0);
+
+	/* Micbias1 is always off, so for pm optimizations make sure the micbias1
+	 * discharge bit is set to floating to avoid discharge in disable state
+	 */
+	snd_soc_update_bits(codec, WM8958_MICBIAS1, WM8958_MICB1_DISCH, 0);
+
+	return 0;
+}
+
+static unsigned int rates_48000[] = {
+	48000,
+};
+
+static struct snd_pcm_hw_constraint_list constraints_48000 = {
+	.count = ARRAY_SIZE(rates_48000),
+	.list  = rates_48000,
+};
+static int mrfld_8958_startup(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+			SNDRV_PCM_HW_PARAM_RATE,
+			&constraints_48000);
+}
+
+static struct snd_soc_ops mrfld_8958_ops = {
+	.startup = mrfld_8958_startup,
+};
+
+static struct snd_soc_ops mrfld_8958_be_ssp2_ops = {
+	.hw_params = mrfld_8958_hw_params,
+};
+static struct snd_soc_compr_ops mrfld_compr_ops = {
+	.set_params = mrfld_wm8958_compr_set_params,
+};
+
+struct snd_soc_dai_link mrfld_8958_msic_dailink[] = {
+	[MERR_DPCM_AUDIO] = {
+		.name = "Merrifield Audio Port",
+		.stream_name = "Saltbay Audio",
+		.cpu_dai_name = "Headset-cpu-dai",
+		.codec_name = "snd-soc-dummy",
+		.codec_dai_name = "snd-soc-dummy-dai",
+		.platform_name = "sst-platform",
+		.init = mrfld_8958_init,
+		.ignore_suspend = 1,
+		.dynamic = 1,
+		.ops = &mrfld_8958_ops,
+	},
+	[MERR_DPCM_COMPR] = {
+		.name = "Merrifield Compress Port",
+		.stream_name = "Saltbay Compress",
+		.platform_name = "sst-platform",
+		.cpu_dai_name = "Compress-cpu-dai",
+		.codec_name = "snd-soc-dummy",
+		.codec_dai_name = "snd-soc-dummy-dai",
+		.dynamic = 1,
+		.init = mrfld_8958_init,
+		.compr_ops = &mrfld_compr_ops,
+	},
+
+	/* back ends */
+	{
+		.name = "SSP2-Codec",
+		.be_id = 1,
+		.cpu_dai_name = "ssp2-port",
+		.platform_name = "sst-platform",
+		.no_pcm = 1,
+		.codec_dai_name = "wm8994-aif1",
+		.codec_name = "wm8994-codec",
+		.be_hw_params_fixup = merr_codec_fixup,
+		.ignore_suspend = 1,
+		.ops = &mrfld_8958_be_ssp2_ops,
+	},
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int snd_mrfld_8958_prepare(struct device *dev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(dev);
+	struct snd_soc_codec *codec;
+	struct snd_soc_dapm_context *dapm;
+
+	pr_debug("In %s\n", __func__);
+
+	codec = mrfld_8958_get_codec(card);
+	if (!codec) {
+		pr_err("%s: couldn't find the codec pointer!\n", __func__);
+		return -EAGAIN;
+	}
+
+	pr_debug("found codec %s\n", codec->name);
+	dapm = &codec->dapm;
+
+	snd_soc_dapm_disable_pin(dapm, "VMID");
+	snd_soc_dapm_sync(dapm);
+
+	snd_soc_suspend(dev);
+	return 0;
+}
+
+static void snd_mrfld_8958_complete(struct device *dev)
+{
+	struct snd_soc_card *card = dev_get_drvdata(dev);
+	struct snd_soc_codec *codec;
+	struct snd_soc_dapm_context *dapm;
+
+	pr_debug("In %s\n", __func__);
+
+	codec = mrfld_8958_get_codec(card);
+	if (!codec) {
+		pr_err("%s: couldn't find the codec pointer!\n", __func__);
+		return;
+	}
+
+	pr_debug("found codec %s\n", codec->name);
+	dapm = &codec->dapm;
+
+	snd_soc_dapm_force_enable_pin(dapm, "VMID");
+	snd_soc_dapm_sync(dapm);
+
+	snd_soc_resume(dev);
+	return;
+}
+
+static int snd_mrfld_8958_poweroff(struct device *dev)
+{
+	pr_debug("In %s\n", __func__);
+	snd_soc_poweroff(dev);
+	return 0;
+}
+#else
+#define snd_mrfld_8958_prepare NULL
+#define snd_mrfld_8958_complete NULL
+#define snd_mrfld_8958_poweroff NULL
+#endif
+
+/* SoC card */
+static struct snd_soc_card snd_soc_card_mrfld = {
+	.name = "wm8958-audio",
+	.dai_link = mrfld_8958_msic_dailink,
+	.num_links = ARRAY_SIZE(mrfld_8958_msic_dailink),
+	.set_bias_level = mrfld_8958_set_bias_level,
+	.set_bias_level_post = mrfld_8958_set_bias_level_post,
+	.dapm_widgets = widgets,
+	.num_dapm_widgets = ARRAY_SIZE(widgets),
+	.dapm_routes = map,
+	.num_dapm_routes = ARRAY_SIZE(map),
+};
+
+static int snd_mrfld_8958_mc_probe(struct platform_device *pdev)
+{
+	int ret_val = 0;
+	struct mrfld_8958_mc_private *drv;
+
+	pr_debug("Entry %s\n", __func__);
+
+	drv = kzalloc(sizeof(*drv), GFP_ATOMIC);
+	if (!drv) {
+		pr_err("allocation failed\n");
+		return -ENOMEM;
+	}
+
+	/* register the soc card */
+	snd_soc_card_mrfld.dev = &pdev->dev;
+	snd_soc_card_set_drvdata(&snd_soc_card_mrfld, drv);
+	ret_val = snd_soc_register_card(&snd_soc_card_mrfld);
+	if (ret_val) {
+		pr_err("snd_soc_register_card failed %d\n", ret_val);
+		goto unalloc;
+	}
+	platform_set_drvdata(pdev, &snd_soc_card_mrfld);
+	pr_info("%s successful\n", __func__);
+	return ret_val;
+
+unalloc:
+	kfree(drv);
+	return ret_val;
+}
+
+static int snd_mrfld_8958_mc_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *soc_card = platform_get_drvdata(pdev);
+	struct mrfld_8958_mc_private *drv = snd_soc_card_get_drvdata(soc_card);
+
+	pr_debug("In %s\n", __func__);
+	kfree(drv);
+	snd_soc_card_set_drvdata(soc_card, NULL);
+	snd_soc_unregister_card(soc_card);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+const struct dev_pm_ops snd_mrfld_8958_mc_pm_ops = {
+	.prepare = snd_mrfld_8958_prepare,
+	.complete = snd_mrfld_8958_complete,
+	.poweroff = snd_mrfld_8958_poweroff,
+};
+
+static struct platform_driver snd_mrfld_8958_mc_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "mrfld_wm8958",
+		.pm = &snd_mrfld_8958_mc_pm_ops,
+	},
+	.probe = snd_mrfld_8958_mc_probe,
+	.remove = snd_mrfld_8958_mc_remove,
+};
+
+static int snd_mrfld_8958_driver_init(void)
+{
+	pr_info("Merrifield Machine Driver mrfld_wm8958 registerd\n");
+	return platform_driver_register(&snd_mrfld_8958_mc_driver);
+}
+
+static void snd_mrfld_8958_driver_exit(void)
+{
+	pr_debug("In %s\n", __func__);
+	platform_driver_unregister(&snd_mrfld_8958_mc_driver);
+}
+late_initcall(snd_mrfld_8958_driver_init);
+module_exit(snd_mrfld_8958_driver_exit);
+
+MODULE_DESCRIPTION("ASoC Intel(R) Merrifield MID Machine driver");
+MODULE_AUTHOR("Vinod Koul <vinod.koul@linux.intel.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:mrfld_wm8958");