sam9g20_wm8731.c 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. /*
  2. * sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based
  3. * ATMEL AT91SAM9G20ek board.
  4. *
  5. * Copyright (C) 2005 SAN People
  6. * Copyright (C) 2008 Atmel
  7. *
  8. * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com>
  9. *
  10. * Based on ati_b1_wm8731.c by:
  11. * Frank Mandarino <fmandarino@endrelia.com>
  12. * Copyright 2006 Endrelia Technologies Inc.
  13. * Based on corgi.c by:
  14. * Copyright 2005 Wolfson Microelectronics PLC.
  15. * Copyright 2005 Openedhand Ltd.
  16. *
  17. * This program is free software; you can redistribute it and/or modify
  18. * it under the terms of the GNU General Public License as published by
  19. * the Free Software Foundation; either version 2 of the License, or
  20. * (at your option) any later version.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU General Public License for more details.
  26. *
  27. * You should have received a copy of the GNU General Public License
  28. * along with this program; if not, write to the Free Software
  29. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  30. */
  31. #include <linux/module.h>
  32. #include <linux/moduleparam.h>
  33. #include <linux/kernel.h>
  34. #include <linux/clk.h>
  35. #include <linux/timer.h>
  36. #include <linux/interrupt.h>
  37. #include <linux/platform_device.h>
  38. #include <linux/i2c.h>
  39. #include <linux/pinctrl/consumer.h>
  40. #include <linux/atmel-ssc.h>
  41. #include <sound/core.h>
  42. #include <sound/pcm.h>
  43. #include <sound/pcm_params.h>
  44. #include <sound/soc.h>
  45. #include <asm/mach-types.h>
  46. #include <mach/hardware.h>
  47. #include <mach/gpio.h>
  48. #include "../codecs/wm8731.h"
  49. #include "atmel-pcm.h"
  50. #include "atmel_ssc_dai.h"
  51. #define MCLK_RATE 12000000
  52. /*
  53. * As shipped the board does not have inputs. However, it is relatively
  54. * straightforward to modify the board to hook them up so support is left
  55. * in the driver.
  56. */
  57. #undef ENABLE_MIC_INPUT
  58. static struct clk *mclk;
  59. static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream,
  60. struct snd_pcm_hw_params *params)
  61. {
  62. struct snd_soc_pcm_runtime *rtd = substream->private_data;
  63. struct snd_soc_dai *codec_dai = rtd->codec_dai;
  64. struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
  65. int ret;
  66. /* set codec DAI configuration */
  67. ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
  68. SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
  69. if (ret < 0)
  70. return ret;
  71. /* set cpu DAI configuration */
  72. ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
  73. SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
  74. if (ret < 0)
  75. return ret;
  76. return 0;
  77. }
  78. static struct snd_soc_ops at91sam9g20ek_ops = {
  79. .hw_params = at91sam9g20ek_hw_params,
  80. };
  81. static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
  82. struct snd_soc_dapm_context *dapm,
  83. enum snd_soc_bias_level level)
  84. {
  85. static int mclk_on;
  86. int ret = 0;
  87. switch (level) {
  88. case SND_SOC_BIAS_ON:
  89. case SND_SOC_BIAS_PREPARE:
  90. if (!mclk_on)
  91. ret = clk_enable(mclk);
  92. if (ret == 0)
  93. mclk_on = 1;
  94. break;
  95. case SND_SOC_BIAS_OFF:
  96. case SND_SOC_BIAS_STANDBY:
  97. if (mclk_on)
  98. clk_disable(mclk);
  99. mclk_on = 0;
  100. break;
  101. }
  102. return ret;
  103. }
  104. static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
  105. SND_SOC_DAPM_MIC("Int Mic", NULL),
  106. SND_SOC_DAPM_SPK("Ext Spk", NULL),
  107. };
  108. static const struct snd_soc_dapm_route intercon[] = {
  109. /* speaker connected to LHPOUT */
  110. {"Ext Spk", NULL, "LHPOUT"},
  111. /* mic is connected to Mic Jack, with WM8731 Mic Bias */
  112. {"MICIN", NULL, "Mic Bias"},
  113. {"Mic Bias", NULL, "Int Mic"},
  114. };
  115. /*
  116. * Logic for a wm8731 as connected on a at91sam9g20ek board.
  117. */
  118. static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd)
  119. {
  120. struct snd_soc_codec *codec = rtd->codec;
  121. struct snd_soc_dai *codec_dai = rtd->codec_dai;
  122. struct snd_soc_dapm_context *dapm = &codec->dapm;
  123. int ret;
  124. printk(KERN_DEBUG
  125. "at91sam9g20ek_wm8731 "
  126. ": at91sam9g20ek_wm8731_init() called\n");
  127. ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK,
  128. MCLK_RATE, SND_SOC_CLOCK_IN);
  129. if (ret < 0) {
  130. printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret);
  131. return ret;
  132. }
  133. /* Add specific widgets */
  134. snd_soc_dapm_new_controls(dapm, at91sam9g20ek_dapm_widgets,
  135. ARRAY_SIZE(at91sam9g20ek_dapm_widgets));
  136. /* Set up specific audio path interconnects */
  137. snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
  138. /* not connected */
  139. snd_soc_dapm_nc_pin(dapm, "RLINEIN");
  140. snd_soc_dapm_nc_pin(dapm, "LLINEIN");
  141. #ifdef ENABLE_MIC_INPUT
  142. snd_soc_dapm_enable_pin(dapm, "Int Mic");
  143. #else
  144. snd_soc_dapm_nc_pin(dapm, "Int Mic");
  145. #endif
  146. /* always connected */
  147. snd_soc_dapm_enable_pin(dapm, "Ext Spk");
  148. return 0;
  149. }
  150. static struct snd_soc_dai_link at91sam9g20ek_dai = {
  151. .name = "WM8731",
  152. .stream_name = "WM8731 PCM",
  153. .cpu_dai_name = "at91rm9200_ssc.0",
  154. .codec_dai_name = "wm8731-hifi",
  155. .init = at91sam9g20ek_wm8731_init,
  156. .platform_name = "at91rm9200_ssc.0",
  157. .codec_name = "wm8731.0-001b",
  158. .ops = &at91sam9g20ek_ops,
  159. };
  160. static struct snd_soc_card snd_soc_at91sam9g20ek = {
  161. .name = "AT91SAMG20-EK",
  162. .owner = THIS_MODULE,
  163. .dai_link = &at91sam9g20ek_dai,
  164. .num_links = 1,
  165. .set_bias_level = at91sam9g20ek_set_bias_level,
  166. };
  167. static int at91sam9g20ek_audio_probe(struct platform_device *pdev)
  168. {
  169. struct device_node *np = pdev->dev.of_node;
  170. struct device_node *codec_np, *cpu_np;
  171. struct clk *pllb;
  172. struct snd_soc_card *card = &snd_soc_at91sam9g20ek;
  173. struct pinctrl *pinctrl;
  174. int ret;
  175. pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
  176. if (IS_ERR(pinctrl)) {
  177. dev_err(&pdev->dev, "Failed to request pinctrl for mck\n");
  178. return PTR_ERR(pinctrl);
  179. }
  180. if (!np) {
  181. if (!(machine_is_at91sam9g20ek() ||
  182. machine_is_at91sam9g20ek_2mmc()))
  183. return -ENODEV;
  184. }
  185. ret = atmel_ssc_set_audio(0);
  186. if (ret) {
  187. dev_err(&pdev->dev, "ssc channel is not valid\n");
  188. return -EINVAL;
  189. }
  190. /*
  191. * Codec MCLK is supplied by PCK0 - set it up.
  192. */
  193. mclk = clk_get(NULL, "pck0");
  194. if (IS_ERR(mclk)) {
  195. printk(KERN_ERR "ASoC: Failed to get MCLK\n");
  196. ret = PTR_ERR(mclk);
  197. goto err;
  198. }
  199. pllb = clk_get(NULL, "pllb");
  200. if (IS_ERR(pllb)) {
  201. printk(KERN_ERR "ASoC: Failed to get PLLB\n");
  202. ret = PTR_ERR(pllb);
  203. goto err_mclk;
  204. }
  205. ret = clk_set_parent(mclk, pllb);
  206. clk_put(pllb);
  207. if (ret != 0) {
  208. printk(KERN_ERR "ASoC: Failed to set MCLK parent\n");
  209. goto err_mclk;
  210. }
  211. clk_set_rate(mclk, MCLK_RATE);
  212. card->dev = &pdev->dev;
  213. /* Parse device node info */
  214. if (np) {
  215. ret = snd_soc_of_parse_card_name(card, "atmel,model");
  216. if (ret)
  217. goto err;
  218. ret = snd_soc_of_parse_audio_routing(card,
  219. "atmel,audio-routing");
  220. if (ret)
  221. goto err;
  222. /* Parse codec info */
  223. at91sam9g20ek_dai.codec_name = NULL;
  224. codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
  225. if (!codec_np) {
  226. dev_err(&pdev->dev, "codec info missing\n");
  227. return -EINVAL;
  228. }
  229. at91sam9g20ek_dai.codec_of_node = codec_np;
  230. /* Parse dai and platform info */
  231. at91sam9g20ek_dai.cpu_dai_name = NULL;
  232. at91sam9g20ek_dai.platform_name = NULL;
  233. cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
  234. if (!cpu_np) {
  235. dev_err(&pdev->dev, "dai and pcm info missing\n");
  236. return -EINVAL;
  237. }
  238. at91sam9g20ek_dai.cpu_of_node = cpu_np;
  239. at91sam9g20ek_dai.platform_of_node = cpu_np;
  240. of_node_put(codec_np);
  241. of_node_put(cpu_np);
  242. }
  243. ret = snd_soc_register_card(card);
  244. if (ret) {
  245. printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n");
  246. }
  247. return ret;
  248. err_mclk:
  249. clk_put(mclk);
  250. mclk = NULL;
  251. err:
  252. atmel_ssc_put_audio(0);
  253. return ret;
  254. }
  255. static int at91sam9g20ek_audio_remove(struct platform_device *pdev)
  256. {
  257. struct snd_soc_card *card = platform_get_drvdata(pdev);
  258. clk_disable(mclk);
  259. mclk = NULL;
  260. snd_soc_unregister_card(card);
  261. atmel_ssc_put_audio(0);
  262. return 0;
  263. }
  264. #ifdef CONFIG_OF
  265. static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = {
  266. { .compatible = "atmel,at91sam9g20ek-wm8731-audio", },
  267. { }
  268. };
  269. MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids);
  270. #endif
  271. static struct platform_driver at91sam9g20ek_audio_driver = {
  272. .driver = {
  273. .name = "at91sam9g20ek-audio",
  274. .owner = THIS_MODULE,
  275. .of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids),
  276. },
  277. .probe = at91sam9g20ek_audio_probe,
  278. .remove = at91sam9g20ek_audio_remove,
  279. };
  280. module_platform_driver(at91sam9g20ek_audio_driver);
  281. /* Module information */
  282. MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>");
  283. MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731");
  284. MODULE_ALIAS("platform:at91sam9g20ek-audio");
  285. MODULE_LICENSE("GPL");