zylonite.c 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * zylonite.c -- SoC audio for Zylonite
  4. *
  5. * Copyright 2008 Wolfson Microelectronics PLC.
  6. * Author: Mark Brown <[email protected]>
  7. */
  8. #include <linux/module.h>
  9. #include <linux/moduleparam.h>
  10. #include <linux/device.h>
  11. #include <linux/clk.h>
  12. #include <linux/i2c.h>
  13. #include <sound/core.h>
  14. #include <sound/pcm.h>
  15. #include <sound/pcm_params.h>
  16. #include <sound/soc.h>
  17. #include "../codecs/wm9713.h"
  18. #include "pxa-ssp.h"
  19. /*
  20. * There is a physical switch SW15 on the board which changes the MCLK
  21. * for the WM9713 between the standard AC97 master clock and the
  22. * output of the CLK_POUT signal from the PXA.
  23. */
  24. static int clk_pout;
  25. module_param(clk_pout, int, 0);
  26. MODULE_PARM_DESC(clk_pout, "Use CLK_POUT as WM9713 MCLK (SW15 on board).");
  27. static struct clk *pout;
  28. static struct snd_soc_card zylonite;
  29. static const struct snd_soc_dapm_widget zylonite_dapm_widgets[] = {
  30. SND_SOC_DAPM_HP("Headphone", NULL),
  31. SND_SOC_DAPM_MIC("Headset Microphone", NULL),
  32. SND_SOC_DAPM_MIC("Handset Microphone", NULL),
  33. SND_SOC_DAPM_SPK("Multiactor", NULL),
  34. SND_SOC_DAPM_SPK("Headset Earpiece", NULL),
  35. };
  36. /* Currently supported audio map */
  37. static const struct snd_soc_dapm_route audio_map[] = {
  38. /* Headphone output connected to HPL/HPR */
  39. { "Headphone", NULL, "HPL" },
  40. { "Headphone", NULL, "HPR" },
  41. /* On-board earpiece */
  42. { "Headset Earpiece", NULL, "OUT3" },
  43. /* Headphone mic */
  44. { "MIC2A", NULL, "Mic Bias" },
  45. { "Mic Bias", NULL, "Headset Microphone" },
  46. /* On-board mic */
  47. { "MIC1", NULL, "Mic Bias" },
  48. { "Mic Bias", NULL, "Handset Microphone" },
  49. /* Multiactor differentially connected over SPKL/SPKR */
  50. { "Multiactor", NULL, "SPKL" },
  51. { "Multiactor", NULL, "SPKR" },
  52. };
  53. static int zylonite_wm9713_init(struct snd_soc_pcm_runtime *rtd)
  54. {
  55. if (clk_pout)
  56. snd_soc_dai_set_pll(asoc_rtd_to_codec(rtd, 0), 0, 0,
  57. clk_get_rate(pout), 0);
  58. return 0;
  59. }
  60. static int zylonite_voice_hw_params(struct snd_pcm_substream *substream,
  61. struct snd_pcm_hw_params *params)
  62. {
  63. struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
  64. struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
  65. struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
  66. unsigned int wm9713_div = 0;
  67. int ret = 0;
  68. int rate = params_rate(params);
  69. /* Only support ratios that we can generate neatly from the AC97
  70. * based master clock - in particular, this excludes 44.1kHz.
  71. * In most applications the voice DAC will be used for telephony
  72. * data so multiples of 8kHz will be the common case.
  73. */
  74. switch (rate) {
  75. case 8000:
  76. wm9713_div = 12;
  77. break;
  78. case 16000:
  79. wm9713_div = 6;
  80. break;
  81. case 48000:
  82. wm9713_div = 2;
  83. break;
  84. default:
  85. /* Don't support OSS emulation */
  86. return -EINVAL;
  87. }
  88. ret = snd_soc_dai_set_sysclk(cpu_dai, PXA_SSP_CLK_AUDIO, 0, 1);
  89. if (ret < 0)
  90. return ret;
  91. if (clk_pout)
  92. ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_PLL_DIV,
  93. WM9713_PCMDIV(wm9713_div));
  94. else
  95. ret = snd_soc_dai_set_clkdiv(codec_dai, WM9713_PCMCLK_DIV,
  96. WM9713_PCMDIV(wm9713_div));
  97. if (ret < 0)
  98. return ret;
  99. return 0;
  100. }
  101. static const struct snd_soc_ops zylonite_voice_ops = {
  102. .hw_params = zylonite_voice_hw_params,
  103. };
  104. SND_SOC_DAILINK_DEFS(ac97,
  105. DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97")),
  106. DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-hifi")),
  107. DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
  108. SND_SOC_DAILINK_DEFS(ac97_aux,
  109. DAILINK_COMP_ARRAY(COMP_CPU("pxa2xx-ac97-aux")),
  110. DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-aux")),
  111. DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
  112. SND_SOC_DAILINK_DEFS(voice,
  113. DAILINK_COMP_ARRAY(COMP_CPU("pxa-ssp-dai.2")),
  114. DAILINK_COMP_ARRAY(COMP_CODEC("wm9713-codec", "wm9713-voice")),
  115. DAILINK_COMP_ARRAY(COMP_PLATFORM("pxa-pcm-audio")));
  116. static struct snd_soc_dai_link zylonite_dai[] = {
  117. {
  118. .name = "AC97",
  119. .stream_name = "AC97 HiFi",
  120. .init = zylonite_wm9713_init,
  121. SND_SOC_DAILINK_REG(ac97),
  122. },
  123. {
  124. .name = "AC97 Aux",
  125. .stream_name = "AC97 Aux",
  126. SND_SOC_DAILINK_REG(ac97_aux),
  127. },
  128. {
  129. .name = "WM9713 Voice",
  130. .stream_name = "WM9713 Voice",
  131. .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
  132. SND_SOC_DAIFMT_CBS_CFS,
  133. .ops = &zylonite_voice_ops,
  134. SND_SOC_DAILINK_REG(voice),
  135. },
  136. };
  137. static int zylonite_probe(struct snd_soc_card *card)
  138. {
  139. int ret;
  140. if (clk_pout) {
  141. pout = clk_get(NULL, "CLK_POUT");
  142. if (IS_ERR(pout)) {
  143. dev_err(card->dev, "Unable to obtain CLK_POUT: %ld\n",
  144. PTR_ERR(pout));
  145. return PTR_ERR(pout);
  146. }
  147. ret = clk_enable(pout);
  148. if (ret != 0) {
  149. dev_err(card->dev, "Unable to enable CLK_POUT: %d\n",
  150. ret);
  151. clk_put(pout);
  152. return ret;
  153. }
  154. dev_dbg(card->dev, "MCLK enabled at %luHz\n",
  155. clk_get_rate(pout));
  156. }
  157. return 0;
  158. }
  159. static int zylonite_remove(struct snd_soc_card *card)
  160. {
  161. if (clk_pout) {
  162. clk_disable(pout);
  163. clk_put(pout);
  164. }
  165. return 0;
  166. }
  167. static int zylonite_suspend_post(struct snd_soc_card *card)
  168. {
  169. if (clk_pout)
  170. clk_disable(pout);
  171. return 0;
  172. }
  173. static int zylonite_resume_pre(struct snd_soc_card *card)
  174. {
  175. int ret = 0;
  176. if (clk_pout) {
  177. ret = clk_enable(pout);
  178. if (ret != 0)
  179. dev_err(card->dev, "Unable to enable CLK_POUT: %d\n",
  180. ret);
  181. }
  182. return ret;
  183. }
  184. static struct snd_soc_card zylonite = {
  185. .name = "Zylonite",
  186. .owner = THIS_MODULE,
  187. .probe = &zylonite_probe,
  188. .remove = &zylonite_remove,
  189. .suspend_post = &zylonite_suspend_post,
  190. .resume_pre = &zylonite_resume_pre,
  191. .dai_link = zylonite_dai,
  192. .num_links = ARRAY_SIZE(zylonite_dai),
  193. .dapm_widgets = zylonite_dapm_widgets,
  194. .num_dapm_widgets = ARRAY_SIZE(zylonite_dapm_widgets),
  195. .dapm_routes = audio_map,
  196. .num_dapm_routes = ARRAY_SIZE(audio_map),
  197. };
  198. static struct platform_device *zylonite_snd_ac97_device;
  199. static int __init zylonite_init(void)
  200. {
  201. int ret;
  202. zylonite_snd_ac97_device = platform_device_alloc("soc-audio", -1);
  203. if (!zylonite_snd_ac97_device)
  204. return -ENOMEM;
  205. platform_set_drvdata(zylonite_snd_ac97_device, &zylonite);
  206. ret = platform_device_add(zylonite_snd_ac97_device);
  207. if (ret != 0)
  208. platform_device_put(zylonite_snd_ac97_device);
  209. return ret;
  210. }
  211. static void __exit zylonite_exit(void)
  212. {
  213. platform_device_unregister(zylonite_snd_ac97_device);
  214. }
  215. module_init(zylonite_init);
  216. module_exit(zylonite_exit);
  217. MODULE_AUTHOR("Mark Brown <[email protected]>");
  218. MODULE_DESCRIPTION("ALSA SoC WM9713 Zylonite");
  219. MODULE_LICENSE("GPL");