Dear all, I tested this patch with TM2 dt patches[1] based on v4.8-rc2. The playback is well working. [1] https://lkml.org/lkml/2016/8/16/61 : [PATCH 0/7] arm64: dts: Add the dts file for Exynos5433 and TM/TM2E board Tested-by: Chanwoo Choi <cw00.choi@xxxxxxxxxxx> Best Regards, Chanwoo Choi On 2016년 08월 09일 23:21, Sylwester Nawrocki wrote: > This patch adds the sound machine driver for TM2 and TM2E board. > Speaker and headphone playback, Main Mic capture, Bluetooth, > Voice call and external accessory are supported. > > Signed-off-by: Inha Song <ideal.song@xxxxxxxxxxx> > [k.kozlowski: rebased on 4.1] > Signed-off-by: Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx> > [s.nawrocki: rebased to 4.7, adjustment to the ASoC core changes, > removed unused ops and direct calls to the max98504 function, > added parsing of "audio-amplifier" and "audio-codec" > properties, added TDM API calls, switched to gpiod API] > Signed-off-by: Sylwester Nawrocki <s.nawrocki@xxxxxxxxxxx> > --- > > Changes since v4 (addressing review comments from Charles): > - changed the order of WM5110_FLL{1,2}, WM5110_FLL{1,2}_REFCLK setting, > - ARIZONA_CLK_SYSCLK, ARIZONA_CLK_ASYNCCLK setting moved to late_probe, > - added tm2_aif2_hw_free callback for disabling FLL2, > - removed unneded card->dapm.bias_level assignment in tm2_mic_bias callback, > - suspend_late, resume_early dev_pm_ops used instead of suspend_post, > resume_pre struct snd_soc_card callbacks. > > Changes since v3: > - removed SND_SOC_SAMSUNG_AUDSS from Kconfig. > > Changes since v2: > - added missing Kconfig dependencies. > > Changes since initial version: > - added PDM Tx channels setup through TDM API > - adaptation to renamed 'samsung,model', 'samsung,i2s-controller', > 'samsung,speaker-amplifier' properties, > - removed some dev_dbg() calls, > - cleaned up mic-bias GPIO handling and switched to gpiod API, > - added parsing of 'audio-codec' property, > - initialized codec_of_node of dai_link instead of codec_name, > - switched to using clock, clock-names properties from the wm5110 > codec node, > - fixed error paths in probe() (of_node reference counting). > --- > sound/soc/samsung/Kconfig | 9 + > sound/soc/samsung/Makefile | 2 + > sound/soc/samsung/tm2_wm5110.c | 604 +++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 615 insertions(+) > create mode 100644 sound/soc/samsung/tm2_wm5110.c > > diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig > index 7b722b0..1bed8a5 100644 > --- a/sound/soc/samsung/Kconfig > +++ b/sound/soc/samsung/Kconfig > @@ -229,3 +229,12 @@ config SND_SOC_ARNDALE_RT5631_ALC5631 > depends on SND_SOC_SAMSUNG && I2C > select SND_SAMSUNG_I2S > select SND_SOC_RT5631 > + > +config SND_SOC_SAMSUNG_TM2_WM5110 > + tristate "SoC I2S Audio support for WM5110 on TM2 board" > + depends on SND_SOC_SAMSUNG && MFD_ARIZONA && I2C && SPI_MASTER > + select SND_SOC_MAX98504 > + select SND_SOC_WM5110 > + select SND_SAMSUNG_I2S > + help > + Say Y if you want to add support for SoC audio on the TM2 board. > diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile > index 5d03f5c..4444b9f 100644 > --- a/sound/soc/samsung/Makefile > +++ b/sound/soc/samsung/Makefile > @@ -44,6 +44,7 @@ snd-soc-lowland-objs := lowland.o > snd-soc-littlemill-objs := littlemill.o > snd-soc-bells-objs := bells.o > snd-soc-arndale-rt5631-objs := arndale_rt5631.o > +snd-soc-tm2-wm5110-objs := tm2_wm5110.o > > obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o > obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o > @@ -69,3 +70,4 @@ obj-$(CONFIG_SND_SOC_LOWLAND) += snd-soc-lowland.o > obj-$(CONFIG_SND_SOC_LITTLEMILL) += snd-soc-littlemill.o > obj-$(CONFIG_SND_SOC_BELLS) += snd-soc-bells.o > obj-$(CONFIG_SND_SOC_ARNDALE_RT5631_ALC5631) += snd-soc-arndale-rt5631.o > +obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o > diff --git a/sound/soc/samsung/tm2_wm5110.c b/sound/soc/samsung/tm2_wm5110.c > new file mode 100644 > index 0000000..16c48fb > --- /dev/null > +++ b/sound/soc/samsung/tm2_wm5110.c > @@ -0,0 +1,604 @@ > +/* > + * Copyright (C) 2015 - 2016 Samsung Electronics Co., Ltd. > + * > + * Authors: Inha Song <ideal.song@xxxxxxxxxxx> > + * Sylwester Nawrocki <s.nawrocki@xxxxxxxxxxx> > + * > + * 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. > + */ > + > +#include <linux/clk.h> > +#include <linux/gpio.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <sound/pcm_params.h> > +#include <sound/soc.h> > + > +#include "i2s.h" > +#include "../codecs/wm5110.h" > + > +#define TM2_DAI_AIF1 0 > +#define TM2_DAI_AIF2 1 > + > +struct tm2_machine_priv { > + struct snd_soc_codec *codec; > + struct clk *codec_mclk1; > + struct clk *codec_mclk2; > + > + unsigned int sysclk_rate; > + > + struct gpio_desc *gpio_mic_bias; > +}; > + > +static int tm2_start_sysclk(struct snd_soc_card *card) > +{ > + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); > + struct snd_soc_codec *codec = priv->codec; > + unsigned long mclk_rate = clk_get_rate(priv->codec_mclk1); > + int ret; > + > + ret = clk_prepare_enable(priv->codec_mclk1); > + if (ret < 0) { > + dev_err(card->dev, "Failed to enable mclk: %d\n", ret); > + return ret; > + } > + > + ret = snd_soc_codec_set_pll(codec, WM5110_FLL1_REFCLK, > + ARIZONA_FLL_SRC_MCLK1, > + mclk_rate, > + priv->sysclk_rate); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to set FLL1 source: %d\n", ret); > + return ret; > + } > + > + ret = snd_soc_codec_set_pll(codec, WM5110_FLL1, > + ARIZONA_FLL_SRC_MCLK1, > + mclk_rate, > + priv->sysclk_rate); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to start FLL1: %d\n", ret); > + return ret; > + } > + > + ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK, > + ARIZONA_CLK_SRC_FLL1, > + priv->sysclk_rate, > + SND_SOC_CLOCK_IN); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to set SYSCLK source: %d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static int tm2_stop_sysclk(struct snd_soc_card *card) > +{ > + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); > + struct snd_soc_codec *codec = priv->codec; > + int ret; > + > + ret = snd_soc_codec_set_pll(codec, WM5110_FLL1, 0, 0, 0); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to stop FLL1: %d\n", ret); > + return ret; > + } > + > + ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK, > + ARIZONA_CLK_SRC_FLL1, 0, 0); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to stop SYSCLK: %d\n", ret); > + return ret; > + } > + > + clk_disable_unprepare(priv->codec_mclk1); > + > + return 0; > +} > + > +static int tm2_aif1_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_codec *codec = rtd->codec; > + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(rtd->card); > + > + switch (params_rate(params)) { > + case 4000: > + case 8000: > + case 12000: > + case 16000: > + case 24000: > + case 32000: > + case 48000: > + case 96000: > + case 192000: > + /* Highest possible SYSCLK frequency: 147.456MHz */ > + priv->sysclk_rate = 147456000U; > + break; > + case 11025: > + case 22050: > + case 44100: > + case 88200: > + case 176400: > + /* Highest possible SYSCLK frequency: 135.4752 MHz */ > + priv->sysclk_rate = 135475200U; > + break; > + default: > + dev_err(codec->dev, "Not supported sample rate: %d\n", > + params_rate(params)); > + return -EINVAL; > + } > + > + return tm2_start_sysclk(rtd->card); > +} > + > +static struct snd_soc_ops tm2_aif1_ops = { > + .hw_params = tm2_aif1_hw_params, > +}; > + > +static int tm2_aif2_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_codec *codec = rtd->codec; > + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(rtd->card); > + unsigned long mclk_rate = clk_get_rate(priv->codec_mclk1); > + unsigned int asyncclk_rate; > + int ret; > + > + switch (params_rate(params)) { > + case 8000: > + case 12000: > + case 16000: > + /* Highest possible ASYNCCLK frequency: 49.152MHz */ > + asyncclk_rate = 49152000U; > + break; > + case 11025: > + /* Highest possible ASYNCCLK frequency: 45.1584 MHz */ > + asyncclk_rate = 45158400U; > + break; > + default: > + dev_err(codec->dev, "Not supported sample rate: %d\n", > + params_rate(params)); > + return -EINVAL; > + } > + > + ret = snd_soc_codec_set_pll(codec, WM5110_FLL2_REFCLK, > + ARIZONA_FLL_SRC_MCLK1, > + mclk_rate, > + asyncclk_rate); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to set FLL2 source: %d\n", ret); > + return ret; > + } > + > + ret = snd_soc_codec_set_pll(codec, WM5110_FLL2, > + ARIZONA_FLL_SRC_MCLK1, > + mclk_rate, > + asyncclk_rate); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to start FLL2: %d\n", ret); > + return ret; > + } > + > + ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK, > + ARIZONA_CLK_SRC_FLL2, > + asyncclk_rate, > + SND_SOC_CLOCK_IN); > + if (ret < 0) { > + dev_err(codec->dev, "Failed to set ASYNCCLK source: %d\n", ret); > + return ret; > + } > + > + return 0; > +} > + > +static int tm2_aif2_hw_free(struct snd_pcm_substream *substream) > +{ > + struct snd_soc_pcm_runtime *rtd = substream->private_data; > + struct snd_soc_codec *codec = rtd->codec; > + int ret; > + > + /* disable FLL2 */ > + ret = snd_soc_codec_set_pll(codec, WM5110_FLL2, ARIZONA_FLL_SRC_MCLK1, > + 0, 0); > + if (ret < 0) > + dev_err(codec->dev, "Failed to stop FLL2: %d\n", ret); > + > + return ret; > +} > + > +static struct snd_soc_ops tm2_aif2_ops = { > + .hw_params = tm2_aif2_hw_params, > + .hw_free = tm2_aif2_hw_free, > +}; > + > +static int tm2_mic_bias(struct snd_soc_dapm_widget *w, > + struct snd_kcontrol *kcontrol, int event) > +{ > + struct snd_soc_card *card = w->dapm->card; > + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); > + > + switch (event) { > + case SND_SOC_DAPM_PRE_PMU: > + gpiod_set_value_cansleep(priv->gpio_mic_bias, 1); > + break; > + case SND_SOC_DAPM_POST_PMD: > + gpiod_set_value_cansleep(priv->gpio_mic_bias, 0); > + break; > + } > + > + return 0; > +} > + > +static int tm2_set_bias_level(struct snd_soc_card *card, > + struct snd_soc_dapm_context *dapm, > + enum snd_soc_bias_level level) > +{ > + struct snd_soc_pcm_runtime *rtd; > + > + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[0].name); > + > + if (dapm->dev != rtd->codec_dai->dev) > + return 0; > + > + switch (level) { > + case SND_SOC_BIAS_STANDBY: > + if (card->dapm.bias_level == SND_SOC_BIAS_OFF) > + tm2_start_sysclk(card); > + break; > + case SND_SOC_BIAS_OFF: > + tm2_stop_sysclk(card); > + break; > + default: > + break; > + } > + > + return 0; > +} > + > +static struct snd_soc_aux_dev tm2_speaker_amp_dev; > + > +static int tm2_late_probe(struct snd_soc_card *card) > +{ > + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); > + struct snd_soc_dai_link_component dlc = { 0 }; > + unsigned int ch_map[] = { 0, 1 }; > + struct snd_soc_dai *amp_pdm_dai; > + struct snd_soc_pcm_runtime *rtd; > + struct snd_soc_dai *aif1_dai; > + struct snd_soc_dai *aif2_dai; > + int ret; > + > + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[TM2_DAI_AIF1].name); > + aif1_dai = rtd->codec_dai; > + priv->codec = rtd->codec; > + > + /* 32 kHz must be enabled for jack detection */ > + if (!IS_ERR(priv->codec_mclk2)) > + clk_prepare_enable(priv->codec_mclk2); > + > + ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0); > + if (ret < 0) { > + dev_err(aif1_dai->dev, "Failed to set SYSCLK: %d\n", ret); > + return ret; > + } > + > + rtd = snd_soc_get_pcm_runtime(card, card->dai_link[TM2_DAI_AIF2].name); > + aif2_dai = rtd->codec_dai; > + > + ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0); > + if (ret < 0) { > + dev_err(aif2_dai->dev, "Failed to set ASYNCCLK: %d\n", ret); > + return ret; > + } > + > + dlc.of_node = tm2_speaker_amp_dev.codec_of_node; > + amp_pdm_dai = snd_soc_find_dai(&dlc); > + if (!amp_pdm_dai) > + return -ENODEV; > + > + /* Set the MAX98504 V/I sense PDM Tx DAI channel mapping */ > + ret = snd_soc_dai_set_channel_map(amp_pdm_dai, ARRAY_SIZE(ch_map), > + ch_map, 0, NULL); > + if (ret < 0) > + return ret; > + > + ret = snd_soc_dai_set_tdm_slot(amp_pdm_dai, 0x3, 0x0, 2, 16); > + if (ret < 0) > + return ret; > + > + return 0; > +} > + > +static const struct snd_kcontrol_new tm2_controls[] = { > + SOC_DAPM_PIN_SWITCH("HP"), > + SOC_DAPM_PIN_SWITCH("SPK"), > + SOC_DAPM_PIN_SWITCH("RCV"), > + SOC_DAPM_PIN_SWITCH("VPS"), > + SOC_DAPM_PIN_SWITCH("HDMI"), > + > + SOC_DAPM_PIN_SWITCH("Main Mic"), > + SOC_DAPM_PIN_SWITCH("Sub Mic"), > + SOC_DAPM_PIN_SWITCH("Third Mic"), > + > + SOC_DAPM_PIN_SWITCH("Headset Mic"), > +}; > + > +const struct snd_soc_dapm_widget tm2_dapm_widgets[] = { > + SND_SOC_DAPM_HP("HP", NULL), > + SND_SOC_DAPM_SPK("SPK", NULL), > + SND_SOC_DAPM_SPK("RCV", NULL), > + SND_SOC_DAPM_LINE("VPS", NULL), > + SND_SOC_DAPM_LINE("HDMI", NULL), > + > + SND_SOC_DAPM_MIC("Main Mic", tm2_mic_bias), > + SND_SOC_DAPM_MIC("Sub Mic", NULL), > + SND_SOC_DAPM_MIC("Third Mic", NULL), > + > + SND_SOC_DAPM_MIC("Headset Mic", NULL), > +}; > + > +static const struct snd_soc_component_driver tm2_component = { > + .name = "tm2-audio", > +}; > + > +static struct snd_soc_dai_driver tm2_ext_dai[] = { > + { > + .name = "Voice call", > + .playback = { > + .channels_min = 1, > + .channels_max = 4, > + .rate_min = 8000, > + .rate_max = 48000, > + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | > + SNDRV_PCM_RATE_48000), > + .formats = SNDRV_PCM_FMTBIT_S16_LE, > + }, > + .capture = { > + .channels_min = 1, > + .channels_max = 4, > + .rate_min = 8000, > + .rate_max = 48000, > + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | > + SNDRV_PCM_RATE_48000), > + .formats = SNDRV_PCM_FMTBIT_S16_LE, > + }, > + }, > + { > + .name = "Bluetooth", > + .playback = { > + .channels_min = 1, > + .channels_max = 4, > + .rate_min = 8000, > + .rate_max = 16000, > + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), > + .formats = SNDRV_PCM_FMTBIT_S16_LE, > + }, > + .capture = { > + .channels_min = 1, > + .channels_max = 2, > + .rate_min = 8000, > + .rate_max = 16000, > + .rates = (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000), > + .formats = SNDRV_PCM_FMTBIT_S16_LE, > + }, > + }, > +}; > + > +static struct snd_soc_dai_link tm2_dai_links[] = { > + { > + .name = "WM5110 AIF1", > + .stream_name = "HiFi Primary", > + .codec_dai_name = "wm5110-aif1", > + .ops = &tm2_aif1_ops, > + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | > + SND_SOC_DAIFMT_CBM_CFM, > + }, { > + .name = "WM5110 Voice", > + .stream_name = "Voice call", > + .codec_dai_name = "wm5110-aif2", > + .ops = &tm2_aif2_ops, > + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | > + SND_SOC_DAIFMT_CBM_CFM, > + .ignore_suspend = 1, > + }, { > + .name = "WM5110 BT", > + .stream_name = "Bluetooth", > + .codec_dai_name = "wm5110-aif3", > + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | > + SND_SOC_DAIFMT_CBM_CFM, > + .ignore_suspend = 1, > + } > +}; > + > +static struct snd_soc_card tm2_card = { > + .owner = THIS_MODULE, > + > + .dai_link = tm2_dai_links, > + .num_links = ARRAY_SIZE(tm2_dai_links), > + .controls = tm2_controls, > + .num_controls = ARRAY_SIZE(tm2_controls), > + .dapm_widgets = tm2_dapm_widgets, > + .num_dapm_widgets = ARRAY_SIZE(tm2_dapm_widgets), > + .aux_dev = &tm2_speaker_amp_dev, > + .num_aux_devs = 1, > + > + .late_probe = tm2_late_probe, > + .set_bias_level = tm2_set_bias_level, > +}; > + > +static int tm2_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct snd_soc_card *card = &tm2_card; > + struct tm2_machine_priv *priv; > + struct device_node *cpu_dai_node, *codec_dai_node; > + int ret, i; > + > + if (!dev->of_node) { > + dev_err(dev, "DT node is missing\n"); > + return -ENODEV; > + } > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + snd_soc_card_set_drvdata(card, priv); > + card->dev = dev; > + > + priv->gpio_mic_bias = devm_gpiod_get(dev, "mic-bias", > + GPIOF_OUT_INIT_LOW); > + if (IS_ERR(priv->gpio_mic_bias)) { > + dev_err(dev, "Failed to get mic bias gpio\n"); > + return PTR_ERR(priv->gpio_mic_bias); > + } > + > + ret = snd_soc_of_parse_card_name(card, "model"); > + if (ret < 0) { > + dev_err(dev, "Card name is not specified\n"); > + return ret; > + } > + > + ret = snd_soc_of_parse_audio_routing(card, "samsung,audio-routing"); > + if (ret < 0) { > + dev_err(dev, "Audio routing is not specified or invalid\n"); > + return ret; > + } > + > + card->aux_dev[0].codec_of_node = of_parse_phandle(dev->of_node, > + "audio-amplifier", 0); > + if (!card->aux_dev[0].codec_of_node) { > + dev_err(dev, "audio-amplifier property invalid or missing\n"); > + return -EINVAL; > + } > + > + cpu_dai_node = of_parse_phandle(dev->of_node, "i2s-controller", 0); > + if (!cpu_dai_node) { > + dev_err(dev, "i2s-controllers property invalid or missing\n"); > + ret = -EINVAL; > + goto err_put_amp; > + } > + > + codec_dai_node = of_parse_phandle(dev->of_node, "audio-codec", 0); > + if (!codec_dai_node) { > + dev_err(dev, "audio-codec property invalid or missing\n"); > + ret = -EINVAL; > + goto err_put_cpu_dai; > + } > + > + for (i = 0; i < card->num_links; i++) { > + card->dai_link[i].cpu_dai_name = NULL; > + card->dai_link[i].cpu_name = NULL; > + card->dai_link[i].platform_name = NULL; > + card->dai_link[i].codec_of_node = codec_dai_node; > + card->dai_link[i].cpu_of_node = cpu_dai_node; > + card->dai_link[i].platform_of_node = cpu_dai_node; > + } > + > + priv->codec_mclk1 = of_clk_get_by_name(codec_dai_node, "mclk1"); > + if (IS_ERR(priv->codec_mclk1)) { > + dev_err(dev, "Failed to get mclk1 clock\n"); > + ret = PTR_ERR(priv->codec_mclk1); > + goto err_put_codec_dai; > + } > + > + /* mclk2 is optional */ > + priv->codec_mclk2 = of_clk_get_by_name(codec_dai_node, "mclk2"); > + if (IS_ERR(priv->codec_mclk2)) > + dev_info(dev, "Not using mclk2 clock\n"); > + > + ret = devm_snd_soc_register_component(dev, &tm2_component, > + tm2_ext_dai, ARRAY_SIZE(tm2_ext_dai)); > + if (ret < 0) { > + dev_err(dev, "Failed to register component: %d\n", ret); > + goto err_put_mclk; > + } > + > + ret = devm_snd_soc_register_card(dev, card); > + if (ret < 0) { > + dev_err(dev, "Failed to register card: %d\n", ret); > + goto err_put_mclk; > + } > + > + return 0; > + > +err_put_mclk: > + clk_put(priv->codec_mclk1); > + if (!IS_ERR(priv->codec_mclk2)) > + clk_put(priv->codec_mclk2); > +err_put_codec_dai: > + of_node_put(codec_dai_node); > +err_put_cpu_dai: > + of_node_put(cpu_dai_node); > +err_put_amp: > + of_node_put(card->aux_dev[0].codec_of_node); > + return ret; > +} > + > +static int tm2_remove(struct platform_device *pdev) > +{ > + struct snd_soc_card *card = &tm2_card; > + struct tm2_machine_priv *priv = snd_soc_card_get_drvdata(card); > + > + clk_put(priv->codec_mclk1); > + if (!IS_ERR(priv->codec_mclk2)) > + clk_put(priv->codec_mclk2); > + > + of_node_put(card->dai_link[0].codec_of_node); > + of_node_put(card->dai_link[0].cpu_of_node); > + of_node_put(card->aux_dev[0].codec_of_node); > + > + return 0; > +} > + > +static int tm2_suspend_late(struct device *dev) > +{ > + struct snd_soc_card *card = dev_get_drvdata(dev); > + > + return tm2_stop_sysclk(card); > +} > + > +static int tm2_resume_early(struct device *dev) > +{ > + struct snd_soc_card *card = dev_get_drvdata(dev); > + > + return tm2_start_sysclk(card); > +} > + > +const struct dev_pm_ops tm2_pm_ops = { > + .suspend = snd_soc_suspend, > + .suspend_late = tm2_suspend_late, > + .resume = snd_soc_resume, > + .resume_early = tm2_resume_early, > + .freeze = snd_soc_suspend, > + .thaw = snd_soc_resume, > + .poweroff = snd_soc_poweroff, > + .restore = snd_soc_resume, > +}; > + > +static const struct of_device_id tm2_of_match[] = { > + { .compatible = "samsung,tm2-audio" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, tm2_of_match); > + > +static struct platform_driver tm2_driver = { > + .driver = { > + .name = "tm2-audio", > + .pm = &tm2_pm_ops, > + .of_match_table = tm2_of_match, > + }, > + .probe = tm2_probe, > + .remove = tm2_remove, > +}; > +module_platform_driver(tm2_driver); > + > +MODULE_AUTHOR("Inha Song <ideal.song@xxxxxxxxxxx>"); > +MODULE_DESCRIPTION("ALSA SoC Exynos TM2 Audio Support"); > +MODULE_LICENSE("GPL v2"); > -- > 1.9.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html > > > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html