bdw_rt286.c 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Sound card driver for Intel Broadwell Wildcat Point with Realtek 286
  4. *
  5. * Copyright (C) 2013, Intel Corporation
  6. */
  7. #include <linux/module.h>
  8. #include <linux/platform_device.h>
  9. #include <sound/core.h>
  10. #include <sound/jack.h>
  11. #include <sound/pcm.h>
  12. #include <sound/pcm_params.h>
  13. #include <sound/soc.h>
  14. #include <sound/soc-acpi.h>
  15. #include "../../codecs/rt286.h"
  16. static struct snd_soc_jack card_headset;
  17. static struct snd_soc_jack_pin card_headset_pins[] = {
  18. {
  19. .pin = "Mic Jack",
  20. .mask = SND_JACK_MICROPHONE,
  21. },
  22. {
  23. .pin = "Headphone Jack",
  24. .mask = SND_JACK_HEADPHONE,
  25. },
  26. };
  27. static const struct snd_kcontrol_new card_controls[] = {
  28. SOC_DAPM_PIN_SWITCH("Speaker"),
  29. SOC_DAPM_PIN_SWITCH("Headphone Jack"),
  30. };
  31. static const struct snd_soc_dapm_widget card_widgets[] = {
  32. SND_SOC_DAPM_HP("Headphone Jack", NULL),
  33. SND_SOC_DAPM_SPK("Speaker", NULL),
  34. SND_SOC_DAPM_MIC("Mic Jack", NULL),
  35. SND_SOC_DAPM_MIC("DMIC1", NULL),
  36. SND_SOC_DAPM_MIC("DMIC2", NULL),
  37. SND_SOC_DAPM_LINE("Line Jack", NULL),
  38. };
  39. static const struct snd_soc_dapm_route card_routes[] = {
  40. {"Speaker", NULL, "SPOR"},
  41. {"Speaker", NULL, "SPOL"},
  42. {"Headphone Jack", NULL, "HPO Pin"},
  43. {"MIC1", NULL, "Mic Jack"},
  44. {"LINE1", NULL, "Line Jack"},
  45. {"DMIC1 Pin", NULL, "DMIC1"},
  46. {"DMIC2 Pin", NULL, "DMIC2"},
  47. /* CODEC BE connections */
  48. {"SSP0 CODEC IN", NULL, "AIF1 Capture"},
  49. {"AIF1 Playback", NULL, "SSP0 CODEC OUT"},
  50. };
  51. static int codec_link_init(struct snd_soc_pcm_runtime *rtd)
  52. {
  53. struct snd_soc_component *codec = snd_soc_rtd_to_codec(rtd, 0)->component;
  54. int ret;
  55. ret = snd_soc_card_jack_new_pins(rtd->card, "Headset", SND_JACK_HEADSET | SND_JACK_BTN_0,
  56. &card_headset, card_headset_pins,
  57. ARRAY_SIZE(card_headset_pins));
  58. if (ret)
  59. return ret;
  60. return snd_soc_component_set_jack(codec, &card_headset, NULL);
  61. }
  62. static void codec_link_exit(struct snd_soc_pcm_runtime *rtd)
  63. {
  64. struct snd_soc_component *codec = snd_soc_rtd_to_codec(rtd, 0)->component;
  65. snd_soc_component_set_jack(codec, NULL, NULL);
  66. }
  67. static int codec_link_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
  68. struct snd_pcm_hw_params *params)
  69. {
  70. struct snd_interval *channels = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
  71. struct snd_interval *rate = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
  72. /* The ADSP will convert the FE rate to 48kHz, stereo. */
  73. rate->min = rate->max = 48000;
  74. channels->min = channels->max = 2;
  75. /* Set SSP0 to 16 bit. */
  76. params_set_format(params, SNDRV_PCM_FORMAT_S16_LE);
  77. return 0;
  78. }
  79. static int codec_link_hw_params(struct snd_pcm_substream *substream,
  80. struct snd_pcm_hw_params *params)
  81. {
  82. struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
  83. struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
  84. int ret;
  85. ret = snd_soc_dai_set_sysclk(codec_dai, RT286_SCLK_S_PLL, 24000000, SND_SOC_CLOCK_IN);
  86. if (ret < 0) {
  87. dev_err(rtd->dev, "set codec sysclk failed: %d\n", ret);
  88. return ret;
  89. }
  90. return ret;
  91. }
  92. static const struct snd_soc_ops codec_link_ops = {
  93. .hw_params = codec_link_hw_params,
  94. };
  95. SND_SOC_DAILINK_DEF(system, DAILINK_COMP_ARRAY(COMP_CPU("System Pin")));
  96. SND_SOC_DAILINK_DEF(offload0, DAILINK_COMP_ARRAY(COMP_CPU("Offload0 Pin")));
  97. SND_SOC_DAILINK_DEF(offload1, DAILINK_COMP_ARRAY(COMP_CPU("Offload1 Pin")));
  98. SND_SOC_DAILINK_DEF(loopback, DAILINK_COMP_ARRAY(COMP_CPU("Loopback Pin")));
  99. SND_SOC_DAILINK_DEF(dummy, DAILINK_COMP_ARRAY(COMP_DUMMY()));
  100. SND_SOC_DAILINK_DEF(platform, DAILINK_COMP_ARRAY(COMP_PLATFORM("haswell-pcm-audio")));
  101. SND_SOC_DAILINK_DEF(codec, DAILINK_COMP_ARRAY(COMP_CODEC("i2c-INT343A:00", "rt286-aif1")));
  102. SND_SOC_DAILINK_DEF(ssp0_port, DAILINK_COMP_ARRAY(COMP_CPU("ssp0-port")));
  103. static struct snd_soc_dai_link card_dai_links[] = {
  104. /* Front End DAI links */
  105. {
  106. .name = "System PCM",
  107. .stream_name = "System Playback/Capture",
  108. .nonatomic = 1,
  109. .dynamic = 1,
  110. .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
  111. .dpcm_playback = 1,
  112. .dpcm_capture = 1,
  113. SND_SOC_DAILINK_REG(system, dummy, platform),
  114. },
  115. {
  116. .name = "Offload0",
  117. .stream_name = "Offload0 Playback",
  118. .nonatomic = 1,
  119. .dynamic = 1,
  120. .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
  121. .dpcm_playback = 1,
  122. SND_SOC_DAILINK_REG(offload0, dummy, platform),
  123. },
  124. {
  125. .name = "Offload1",
  126. .stream_name = "Offload1 Playback",
  127. .nonatomic = 1,
  128. .dynamic = 1,
  129. .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
  130. .dpcm_playback = 1,
  131. SND_SOC_DAILINK_REG(offload1, dummy, platform),
  132. },
  133. {
  134. .name = "Loopback PCM",
  135. .stream_name = "Loopback",
  136. .nonatomic = 1,
  137. .dynamic = 1,
  138. .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
  139. .dpcm_capture = 1,
  140. SND_SOC_DAILINK_REG(loopback, dummy, platform),
  141. },
  142. /* Back End DAI links */
  143. {
  144. /* SSP0 - Codec */
  145. .name = "Codec",
  146. .id = 0,
  147. .nonatomic = 1,
  148. .no_pcm = 1,
  149. .init = codec_link_init,
  150. .exit = codec_link_exit,
  151. .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBC_CFC,
  152. .ignore_pmdown_time = 1,
  153. .be_hw_params_fixup = codec_link_hw_params_fixup,
  154. .ops = &codec_link_ops,
  155. .dpcm_playback = 1,
  156. .dpcm_capture = 1,
  157. SND_SOC_DAILINK_REG(ssp0_port, codec, platform),
  158. },
  159. };
  160. static int card_suspend_pre(struct snd_soc_card *card)
  161. {
  162. struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, "rt286-aif1");
  163. if (!codec_dai)
  164. return 0;
  165. return snd_soc_component_set_jack(codec_dai->component, NULL, NULL);
  166. }
  167. static int card_resume_post(struct snd_soc_card *card)
  168. {
  169. struct snd_soc_dai *codec_dai = snd_soc_card_get_codec_dai(card, "rt286-aif1");
  170. if (!codec_dai)
  171. return 0;
  172. return snd_soc_component_set_jack(codec_dai->component, &card_headset, NULL);
  173. }
  174. static struct snd_soc_card bdw_rt286_card = {
  175. .owner = THIS_MODULE,
  176. .suspend_pre = card_suspend_pre,
  177. .resume_post = card_resume_post,
  178. .dai_link = card_dai_links,
  179. .num_links = ARRAY_SIZE(card_dai_links),
  180. .controls = card_controls,
  181. .num_controls = ARRAY_SIZE(card_controls),
  182. .dapm_widgets = card_widgets,
  183. .num_dapm_widgets = ARRAY_SIZE(card_widgets),
  184. .dapm_routes = card_routes,
  185. .num_dapm_routes = ARRAY_SIZE(card_routes),
  186. .fully_routed = true,
  187. };
  188. /* Use space before codec name to simplify card ID, and simplify driver name. */
  189. #define SOF_CARD_NAME "bdw rt286" /* card name will be 'sof-bdw rt286' */
  190. #define SOF_DRIVER_NAME "SOF"
  191. #define CARD_NAME "broadwell-rt286"
  192. static int bdw_rt286_probe(struct platform_device *pdev)
  193. {
  194. struct snd_soc_acpi_mach *mach;
  195. struct device *dev = &pdev->dev;
  196. int ret;
  197. bdw_rt286_card.dev = dev;
  198. mach = dev_get_platdata(dev);
  199. ret = snd_soc_fixup_dai_links_platform_name(&bdw_rt286_card, mach->mach_params.platform);
  200. if (ret)
  201. return ret;
  202. if (snd_soc_acpi_sof_parent(dev)) {
  203. bdw_rt286_card.name = SOF_CARD_NAME;
  204. bdw_rt286_card.driver_name = SOF_DRIVER_NAME;
  205. } else {
  206. bdw_rt286_card.name = CARD_NAME;
  207. }
  208. return devm_snd_soc_register_card(dev, &bdw_rt286_card);
  209. }
  210. static struct platform_driver bdw_rt286_driver = {
  211. .probe = bdw_rt286_probe,
  212. .driver = {
  213. .name = "bdw_rt286",
  214. .pm = &snd_soc_pm_ops
  215. },
  216. };
  217. module_platform_driver(bdw_rt286_driver)
  218. MODULE_AUTHOR("Liam Girdwood, Xingchao Wang");
  219. MODULE_DESCRIPTION("Sound card driver for Intel Broadwell Wildcat Point with Realtek 286");
  220. MODULE_LICENSE("GPL");
  221. MODULE_ALIAS("platform:bdw_rt286");