Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: atmel: add wm8904 based audio machine driver

Add wm8904 based audio machine driver for Atmel EK board

The following ek board based on it
- at91sam9n12ek
- sama5d3xek (d31, d33, d34, d35)

Signed-off-by: Bo Shen <voice.shen@atmel.com>
Signed-off-by: Mark Brown <broonie@linaro.org>

authored by

Bo Shen and committed by
Mark Brown
52f19b14 95e0e07e

+321
+55
Documentation/devicetree/bindings/sound/atmel-wm8904.txt
··· 1 + Atmel ASoC driver with wm8904 audio codec complex 2 + 3 + Required properties: 4 + - compatible: "atmel,asoc-wm8904" 5 + - atmel,model: The user-visible name of this sound complex. 6 + - atmel,audio-routing: A list of the connections between audio components. 7 + Each entry is a pair of strings, the first being the connection's sink, 8 + the second being the connection's source. Valid names for sources and 9 + sinks are the WM8904's pins, and the jacks on the board: 10 + 11 + WM8904 pins: 12 + 13 + * IN1L 14 + * IN1R 15 + * IN2L 16 + * IN2R 17 + * IN3L 18 + * IN3R 19 + * HPOUTL 20 + * HPOUTR 21 + * LINEOUTL 22 + * LINEOUTR 23 + * MICBIAS 24 + 25 + Board connectors: 26 + 27 + * Headphone Jack 28 + * Line In Jack 29 + * Mic 30 + 31 + - atmel,ssc-controller: The phandle of the SSC controller 32 + - atmel,audio-codec: The phandle of the WM8904 audio codec 33 + 34 + Optional properties: 35 + - pinctrl-names, pinctrl-0: Please refer to pinctrl-bindings.txt 36 + 37 + Example: 38 + sound { 39 + compatible = "atmel,asoc-wm8904"; 40 + pinctrl-names = "default"; 41 + pinctrl-0 = <&pinctrl_pck0_as_mck>; 42 + 43 + atmel,model = "wm8904 @ AT91SAM9N12EK"; 44 + 45 + atmel,audio-routing = 46 + "Headphone Jack", "HPOUTL", 47 + "Headphone Jack", "HPOUTR", 48 + "IN2L", "Line In Jack", 49 + "IN2R", "Line In Jack", 50 + "Mic", "MICBIAS", 51 + "IN1L", "Mic"; 52 + 53 + atmel,ssc-controller = <&ssc0>; 54 + atmel,audio-codec = <&wm8904>; 55 + };
+10
sound/soc/atmel/Kconfig
··· 33 33 Say Y if you want to add support for SoC audio on WM8731-based 34 34 AT91sam9g20 evaluation board. 35 35 36 + config SND_ATMEL_SOC_WM8904 37 + tristate "Atmel ASoC driver for boards using WM8904 codec" 38 + depends on ARCH_AT91 && ATMEL_SSC && SND_ATMEL_SOC 39 + select SND_ATMEL_SOC_SSC 40 + select SND_ATMEL_SOC_DMA 41 + select SND_SOC_WM8904 42 + help 43 + Say Y if you want to add support for Atmel ASoC driver for boards using 44 + WM8904 codec. 45 + 36 46 config SND_AT91_SOC_AFEB9260 37 47 tristate "SoC Audio support for AFEB9260 board" 38 48 depends on ARCH_AT91 && ATMEL_SSC && ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC
+2
sound/soc/atmel/Makefile
··· 11 11 12 12 # AT91 Machine Support 13 13 snd-soc-sam9g20-wm8731-objs := sam9g20_wm8731.o 14 + snd-atmel-soc-wm8904-objs := atmel_wm8904.o 14 15 15 16 obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o 17 + obj-$(CONFIG_SND_ATMEL_SOC_WM8904) += snd-atmel-soc-wm8904.o 16 18 obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o
+254
sound/soc/atmel/atmel_wm8904.c
··· 1 + /* 2 + * atmel_wm8904 - Atmel ASoC driver for boards with WM8904 codec. 3 + * 4 + * Copyright (C) 2012 Atmel 5 + * 6 + * Author: Bo Shen <voice.shen@atmel.com> 7 + * 8 + * GPLv2 or later 9 + */ 10 + 11 + #include <linux/clk.h> 12 + #include <linux/module.h> 13 + #include <linux/of.h> 14 + #include <linux/of_device.h> 15 + #include <linux/pinctrl/consumer.h> 16 + 17 + #include <sound/soc.h> 18 + 19 + #include "../codecs/wm8904.h" 20 + #include "atmel_ssc_dai.h" 21 + 22 + #define MCLK_RATE 32768 23 + 24 + static struct clk *mclk; 25 + 26 + static const struct snd_soc_dapm_widget atmel_asoc_wm8904_dapm_widgets[] = { 27 + SND_SOC_DAPM_HP("Headphone Jack", NULL), 28 + SND_SOC_DAPM_MIC("Mic", NULL), 29 + SND_SOC_DAPM_LINE("Line In Jack", NULL), 30 + }; 31 + 32 + static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream, 33 + struct snd_pcm_hw_params *params) 34 + { 35 + struct snd_soc_pcm_runtime *rtd = substream->private_data; 36 + struct snd_soc_dai *codec_dai = rtd->codec_dai; 37 + int ret; 38 + 39 + ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, 40 + 32768, params_rate(params) * 256); 41 + if (ret < 0) { 42 + pr_err("%s - failed to set wm8904 codec PLL.", __func__); 43 + return ret; 44 + } 45 + 46 + /* 47 + * As here wm8904 use FLL output as its system clock 48 + * so calling set_sysclk won't care freq parameter 49 + * then we pass 0 50 + */ 51 + ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL, 52 + 0, SND_SOC_CLOCK_IN); 53 + if (ret < 0) { 54 + pr_err("%s -failed to set wm8904 SYSCLK\n", __func__); 55 + return ret; 56 + } 57 + 58 + return 0; 59 + } 60 + 61 + static struct snd_soc_ops atmel_asoc_wm8904_ops = { 62 + .hw_params = atmel_asoc_wm8904_hw_params, 63 + }; 64 + 65 + static int atmel_set_bias_level(struct snd_soc_card *card, 66 + struct snd_soc_dapm_context *dapm, 67 + enum snd_soc_bias_level level) 68 + { 69 + if (dapm->bias_level == SND_SOC_BIAS_STANDBY) { 70 + switch (level) { 71 + case SND_SOC_BIAS_PREPARE: 72 + clk_prepare_enable(mclk); 73 + break; 74 + case SND_SOC_BIAS_OFF: 75 + clk_disable_unprepare(mclk); 76 + break; 77 + default: 78 + break; 79 + } 80 + } 81 + 82 + return 0; 83 + }; 84 + 85 + static struct snd_soc_dai_link atmel_asoc_wm8904_dailink = { 86 + .name = "WM8904", 87 + .stream_name = "WM8904 PCM", 88 + .codec_dai_name = "wm8904-hifi", 89 + .dai_fmt = SND_SOC_DAIFMT_I2S 90 + | SND_SOC_DAIFMT_NB_NF 91 + | SND_SOC_DAIFMT_CBM_CFM, 92 + .ops = &atmel_asoc_wm8904_ops, 93 + }; 94 + 95 + static struct snd_soc_card atmel_asoc_wm8904_card = { 96 + .name = "atmel_asoc_wm8904", 97 + .owner = THIS_MODULE, 98 + .set_bias_level = atmel_set_bias_level, 99 + .dai_link = &atmel_asoc_wm8904_dailink, 100 + .num_links = 1, 101 + .dapm_widgets = atmel_asoc_wm8904_dapm_widgets, 102 + .num_dapm_widgets = ARRAY_SIZE(atmel_asoc_wm8904_dapm_widgets), 103 + .fully_routed = true, 104 + }; 105 + 106 + static int atmel_asoc_wm8904_dt_init(struct platform_device *pdev) 107 + { 108 + struct device_node *np = pdev->dev.of_node; 109 + struct device_node *codec_np, *cpu_np; 110 + struct snd_soc_card *card = &atmel_asoc_wm8904_card; 111 + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 112 + int ret; 113 + 114 + if (!np) { 115 + dev_err(&pdev->dev, "only device tree supported\n"); 116 + return -EINVAL; 117 + } 118 + 119 + ret = snd_soc_of_parse_card_name(card, "atmel,model"); 120 + if (ret) { 121 + dev_err(&pdev->dev, "failed to parse card name\n"); 122 + return ret; 123 + } 124 + 125 + ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing"); 126 + if (ret) { 127 + dev_err(&pdev->dev, "failed to parse audio routing\n"); 128 + return ret; 129 + } 130 + 131 + cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0); 132 + if (!cpu_np) { 133 + dev_err(&pdev->dev, "failed to get dai and pcm info\n"); 134 + ret = -EINVAL; 135 + return ret; 136 + } 137 + dailink->cpu_of_node = cpu_np; 138 + dailink->platform_of_node = cpu_np; 139 + of_node_put(cpu_np); 140 + 141 + codec_np = of_parse_phandle(np, "atmel,audio-codec", 0); 142 + if (!codec_np) { 143 + dev_err(&pdev->dev, "failed to get codec info\n"); 144 + ret = -EINVAL; 145 + return ret; 146 + } 147 + dailink->codec_of_node = codec_np; 148 + of_node_put(codec_np); 149 + 150 + return 0; 151 + } 152 + 153 + static int atmel_asoc_wm8904_probe(struct platform_device *pdev) 154 + { 155 + struct snd_soc_card *card = &atmel_asoc_wm8904_card; 156 + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 157 + struct clk *clk_src; 158 + struct pinctrl *pinctrl; 159 + int id, ret; 160 + 161 + pinctrl = devm_pinctrl_get_select_default(&pdev->dev); 162 + if (IS_ERR(pinctrl)) { 163 + dev_err(&pdev->dev, "failed to request pinctrl\n"); 164 + return PTR_ERR(pinctrl); 165 + } 166 + 167 + card->dev = &pdev->dev; 168 + ret = atmel_asoc_wm8904_dt_init(pdev); 169 + if (ret) { 170 + dev_err(&pdev->dev, "failed to init dt info\n"); 171 + return ret; 172 + } 173 + 174 + id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc"); 175 + ret = atmel_ssc_set_audio(id); 176 + if (ret != 0) { 177 + dev_err(&pdev->dev, "failed to set SSC %d for audio\n", id); 178 + return ret; 179 + } 180 + 181 + mclk = clk_get(NULL, "pck0"); 182 + if (IS_ERR(mclk)) { 183 + dev_err(&pdev->dev, "failed to get pck0\n"); 184 + ret = PTR_ERR(mclk); 185 + goto err_set_audio; 186 + } 187 + 188 + clk_src = clk_get(NULL, "clk32k"); 189 + if (IS_ERR(clk_src)) { 190 + dev_err(&pdev->dev, "failed to get clk32k\n"); 191 + ret = PTR_ERR(clk_src); 192 + goto err_set_audio; 193 + } 194 + 195 + ret = clk_set_parent(mclk, clk_src); 196 + clk_put(clk_src); 197 + if (ret != 0) { 198 + dev_err(&pdev->dev, "failed to set MCLK parent\n"); 199 + goto err_set_audio; 200 + } 201 + 202 + dev_info(&pdev->dev, "setting pck0 to %dHz\n", MCLK_RATE); 203 + clk_set_rate(mclk, MCLK_RATE); 204 + 205 + ret = snd_soc_register_card(card); 206 + if (ret) { 207 + dev_err(&pdev->dev, "snd_soc_register_card failed\n"); 208 + goto err_set_audio; 209 + } 210 + 211 + return 0; 212 + 213 + err_set_audio: 214 + atmel_ssc_put_audio(id); 215 + return ret; 216 + } 217 + 218 + static int atmel_asoc_wm8904_remove(struct platform_device *pdev) 219 + { 220 + struct snd_soc_card *card = platform_get_drvdata(pdev); 221 + struct snd_soc_dai_link *dailink = &atmel_asoc_wm8904_dailink; 222 + int id; 223 + 224 + id = of_alias_get_id((struct device_node *)dailink->cpu_of_node, "ssc"); 225 + 226 + snd_soc_unregister_card(card); 227 + atmel_ssc_put_audio(id); 228 + 229 + return 0; 230 + } 231 + 232 + #ifdef CONFIG_OF 233 + static const struct of_device_id atmel_asoc_wm8904_dt_ids[] = { 234 + { .compatible = "atmel,asoc-wm8904", }, 235 + { } 236 + }; 237 + #endif 238 + 239 + static struct platform_driver atmel_asoc_wm8904_driver = { 240 + .driver = { 241 + .name = "atmel-wm8904-audio", 242 + .owner = THIS_MODULE, 243 + .of_match_table = of_match_ptr(atmel_asoc_wm8904_dt_ids), 244 + }, 245 + .probe = atmel_asoc_wm8904_probe, 246 + .remove = atmel_asoc_wm8904_remove, 247 + }; 248 + 249 + module_platform_driver(atmel_asoc_wm8904_driver); 250 + 251 + /* Module information */ 252 + MODULE_AUTHOR("Bo Shen <voice.shen@atmel.com>"); 253 + MODULE_DESCRIPTION("ALSA SoC machine driver for Atmel EK with WM8904 codec"); 254 + MODULE_LICENSE("GPL");