[alsa-devel] [PATCH v5 1/2] ASoC: samsung: Add DT bindings documentation for TM2 sound subsystem
This patch adds DT binding documentation for Exnos5433 based TM2 and TM2E boards sound subsystem.
Signed-off-by: Sylwester Nawrocki s.nawrocki@samsung.com Acked-by: Rob Herring robh@kernel.org --- Changes since v4: - indentation changes.
Changes since v2: - none.
Changes since initial version: - dropped clocks, clock-names properties, instead properties from the CODEC node will be used, - property renames: 'samsung,model' -> 'model', 'samsung,i2s-controller' -> 'i2s-controller', 'samsung,speaker-amplifier' -> 'audio-amplifier', - added 'audio-codec' property. --- .../bindings/sound/samsung,tm2-audio.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt
diff --git a/Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt b/Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt new file mode 100644 index 0000000..94442e5 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt @@ -0,0 +1,38 @@ +Samsung Exynos5433 TM2(E) audio complex with WM5110 codec + +Required properties: + + - compatible : "samsung,tm2-audio" + - model : the user-visible name of this sound complex + - audio-codec : the phandle of the wm5110 audio codec node, + as described in ../mfd/arizona.txt + - i2s-controller : the phandle of the I2S controller + - audio-amplifier : the phandle of the MAX98504 amplifier + - samsung,audio-routing : a list of the connections between audio components; + each entry is a pair of strings, the first being the + connection's sink, the second being the connection's + source; valid names for sources and sinks are the + WM5110's and MAX98504's pins and the jacks on the + board: HP, SPK, Main Mic, Sub Mic, Third Mic, + Headset Mic + - mic-bias-gpios : GPIO pin that enables the Main Mic bias regulator + + +Example: + +sound { + compatible = "samsung,tm2-audio"; + audio-codec = <&wm5110>; + i2s-controller = <&i2s0>; + audio-amplifier = <&max98504>; + mic-bias-gpios = <&gpr3 2 0>; + model = "wm5110"; + samsung,audio-routing = + "HP", "HPOUT1L", + "HP", "HPOUT1R", + "SPK", "SPKOUT", + "SPKOUT", "HPOUT2L", + "SPKOUT", "HPOUT2R", + "Main Mic", "MICBIAS2", + "IN1R", "Main Mic"; +}; -- 1.9.1
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@samsung.com [k.kozlowski: rebased on 4.1] Signed-off-by: Krzysztof Kozlowski k.kozlowski@samsung.com [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@samsung.com ---
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@samsung.com + * Sylwester Nawrocki s.nawrocki@samsung.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. + */ + +#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@samsung.com"); +MODULE_DESCRIPTION("ALSA SoC Exynos TM2 Audio Support"); +MODULE_LICENSE("GPL v2"); -- 1.9.1
On Tue, Aug 09, 2016 at 04:21:54PM +0200, Sylwester Nawrocki wrote:
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)) {
So, all this of_clk_get_by_name() stuff is messy and is going to need to be duplicated for any cards using this CODEC which isn't excellent. It would be a lot nicer if we just did some regular clk_get() calls in the MFD and then provided an interface for other drivers to get at those clocks (or just had the CODEC driver DTRT internally).
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@samsung.com
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@samsung.com [k.kozlowski: rebased on 4.1] Signed-off-by: Krzysztof Kozlowski k.kozlowski@samsung.com [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@samsung.com
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@samsung.com
Sylwester Nawrocki <s.nawrocki@samsung.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.
- */
+#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@samsung.com"); +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@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Tue, Aug 09, 2016 at 04:21:54PM +0200, 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@samsung.com [k.kozlowski: rebased on 4.1] Signed-off-by: Krzysztof Kozlowski k.kozlowski@samsung.com [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@samsung.com
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).
<snip>
+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,
+};
This bit still looks problematic to me, although admittedly I not sure exactly why you don't seem to be hitting any issues with it. Before you were effectively calling things from .suspend, now its .suspend_late. As we are now later in the suspend process that means its even more likely the SPI will be unavailable when we try to talk to the CODEC. You need to run before the SPI has started suspending so you can still communicate with the CODEC, I still think prepare/complete are likely to be the best options for this, unless there are some complications there I have missed.
Aside from that and the clock stuff we are already discussing everything else looks totally fine to me.
Thanks, Charles
On 08/29/2016 05:02 PM, Charles Keepax wrote:
On Tue, Aug 09, 2016 at 04:21:54PM +0200, Sylwester Nawrocki wrote:
<snip> > +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, > +};
This bit still looks problematic to me, although admittedly I not sure exactly why you don't seem to be hitting any issues with it. Before you were effectively calling things from .suspend, now its .suspend_late. As we are now later in the suspend process that means its even more likely the SPI will be unavailable when we try to talk to the CODEC. You need to run before the SPI has started suspending so you can still communicate with the CODEC, I still think prepare/complete are likely to be the best options for this, unless there are some complications there I have missed.
Aside from that and the clock stuff we are already discussing everything else looks totally fine to me.
Thanks for your review. First of all I'm testing system suspend related changes on 4.1 kernel as there is no full support for the exynos5433 SoC in mainline yet. I will use prepare/complete as with late_suspend/early_resume power sequences are wrong as you point out. I still need to debug why there is no errors reported when the SPI controller is suspended while there are supposed to be done SPI bus transfers.
It seems I missed the main point of making that pm_ops change, I focused on the MCLK clock presence while the main issue was communication with the codec over SPI bus.
I added some debug prints and below are logs when using late_suspend/early_resume and prepare/complete. In both cases tm2_stop_sysclk() returns 0.
I think suspend/resume sequences are still correct when snd_soc_pm_ops are used thanks to order of linking drivers into kernel image and adding device to dpm_list.
-------------------------------8<----------------------------------- suspend_late/early_resume
# dmesg | grep "resume|suspend|arizona"
[ 42.169691] Suspending console(s) (use no_console_suspend to debug)
[ 42.211115] tm2-audio sound: snd_soc_suspend [ 42.213991] s3c64xx-spi 14d30000.spi: s3c64xx_spi_suspend
[ 42.215097] PM: suspend of devices complete after 40.028 msecs [ 42.229764] tm2-audio sound: tm2_suspend_late: 0
[ 42.231496] PM: late suspend of devices complete after 6.362 msecs [ 42.237318] PM: noirq suspend of devices complete after 5.804 msecs [ 42.819161] PM: noirq resume of devices complete after 62.500 msecs
[ 43.063552] arizona spi1.0: FLL1: Timed out waiting for lock [ 43.063572] tm2-audio sound: tm2_resume_early: 0 [ 43.066830] PM: early resume of devices complete after 247.302 msecs
[ 43.078789] s3c64xx-spi 14d30000.spi: s3c64xx_spi_resume [ 43.080192] tm2-audio sound: snd_soc_resume [ 43.080212] tm2-audio sound: ASoC: starting resume work
[ 43.319459] PM: resume of devices complete after 241.698 msecs -------------------------------8<-----------------------------------
-------------------------------8<----------------------------------- prepare/complete
# dmesg -c | grep "prepare|complete|suspend|resume|arizona"
[ 172.398552] Suspending console(s) (use no_console_suspend to debug) [ 172.399431] tm2-audio sound: tm2_prepare: 0 [ 172.441073] tm2-audio sound: snd_soc_suspend [ 172.443653] s3c64xx-spi 14d30000.spi: s3c64xx_spi_suspend
[ 172.444766] PM: suspend of devices complete after 42.202 msecs [ 172.459500] PM: late suspend of devices complete after 6.407 msecs [ 172.465277] PM: noirq suspend of devices complete after 5.758 msecs [ 173.039052] PM: noirq resume of devices complete after 62.396 msecs [ 173.044578] PM: early resume of devices complete after 5.152 msecs
[ 173.057042] s3c64xx-spi 14d30000.spi: s3c64xx_spi_resume [ 173.058423] tm2-audio sound: snd_soc_resume [ 173.058442] tm2-audio sound: ASoC: starting resume work
[ 173.299428] PM: resume of devices complete after 243.393 msecs [ 173.329555] arizona spi1.0: Spurious HPDET IRQ [ 173.329704] arizona spi1.0: Mixer dropped sample [ 173.329758] tm2-audio sound: tm2_complete: 0 -------------------------------8<-----------------------------------
On 08/30/2016 04:57 PM, Sylwester Nawrocki wrote:
I will use prepare/complete as with late_suspend/early_resume power sequences are wrong as you point out. I still need to debug why there is no errors reported when the SPI controller is suspended while there are supposed to be done SPI bus transfers.
As it turns out the reason there is no any errors with late_suspend/ early_resume is that the CODEC sets its regmap's "cache only" only flag in its suspend() callback and then clears it in resume(). So any SPI transfers which might happen when SPI controller is suspended are prevented this way.
On Fri, Sep 02, 2016 at 12:49:51PM +0200, Sylwester Nawrocki wrote:
On 08/30/2016 04:57 PM, Sylwester Nawrocki wrote:
I will use prepare/complete as with late_suspend/early_resume power sequences are wrong as you point out. I still need to debug why there is no errors reported when the SPI controller is suspended while there are supposed to be done SPI bus transfers.
As it turns out the reason there is no any errors with late_suspend/ early_resume is that the CODEC sets its regmap's "cache only" only flag in its suspend() callback and then clears it in resume(). So any SPI transfers which might happen when SPI controller is suspended are prevented this way.
Ah of course yeah should have realised that, but yeah you still end up with the writes not actually reaching the hardware.
Thanks, Charles
The patch
ASoC: samsung: Add DT bindings documentation for TM2 sound subsystem
has been applied to the asoc tree at
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 48a760279ba31c59b8cfb14c5be9ed1c8e191038 Mon Sep 17 00:00:00 2001
From: Sylwester Nawrocki s.nawrocki@samsung.com Date: Wed, 2 Nov 2016 17:02:37 +0100 Subject: [PATCH] ASoC: samsung: Add DT bindings documentation for TM2 sound subsystem
This patch adds DT binding documentation for Exnos5433 based TM2 and TM2E boards sound subsystem.
Signed-off-by: Sylwester Nawrocki s.nawrocki@samsung.com Acked-by: Rob Herring robh@kernel.org Signed-off-by: Mark Brown broonie@kernel.org --- .../bindings/sound/samsung,tm2-audio.txt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt
diff --git a/Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt b/Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt new file mode 100644 index 000000000000..94442e5673b3 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/samsung,tm2-audio.txt @@ -0,0 +1,38 @@ +Samsung Exynos5433 TM2(E) audio complex with WM5110 codec + +Required properties: + + - compatible : "samsung,tm2-audio" + - model : the user-visible name of this sound complex + - audio-codec : the phandle of the wm5110 audio codec node, + as described in ../mfd/arizona.txt + - i2s-controller : the phandle of the I2S controller + - audio-amplifier : the phandle of the MAX98504 amplifier + - samsung,audio-routing : a list of the connections between audio components; + each entry is a pair of strings, the first being the + connection's sink, the second being the connection's + source; valid names for sources and sinks are the + WM5110's and MAX98504's pins and the jacks on the + board: HP, SPK, Main Mic, Sub Mic, Third Mic, + Headset Mic + - mic-bias-gpios : GPIO pin that enables the Main Mic bias regulator + + +Example: + +sound { + compatible = "samsung,tm2-audio"; + audio-codec = <&wm5110>; + i2s-controller = <&i2s0>; + audio-amplifier = <&max98504>; + mic-bias-gpios = <&gpr3 2 0>; + model = "wm5110"; + samsung,audio-routing = + "HP", "HPOUT1L", + "HP", "HPOUT1R", + "SPK", "SPKOUT", + "SPKOUT", "HPOUT2L", + "SPKOUT", "HPOUT2R", + "Main Mic", "MICBIAS2", + "IN1R", "Main Mic"; +};
participants (4)
-
Chanwoo Choi
-
Charles Keepax
-
Mark Brown
-
Sylwester Nawrocki