lochnagar-sc.c 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // SPDX-License-Identifier: GPL-2.0
  2. //
  3. // Lochnagar sound card driver
  4. //
  5. // Copyright (c) 2017-2019 Cirrus Logic, Inc. and
  6. // Cirrus Logic International Semiconductor Ltd.
  7. //
  8. // Author: Charles Keepax <ckeepax@opensource.cirrus.com>
  9. // Piotr Stankiewicz <piotrs@opensource.cirrus.com>
  10. #include <linux/clk.h>
  11. #include <linux/module.h>
  12. #include <sound/soc.h>
  13. #include <linux/mfd/lochnagar.h>
  14. #include <linux/mfd/lochnagar1_regs.h>
  15. #include <linux/mfd/lochnagar2_regs.h>
  16. struct lochnagar_sc_priv {
  17. struct clk *mclk;
  18. };
  19. static const struct snd_soc_dapm_widget lochnagar_sc_widgets[] = {
  20. SND_SOC_DAPM_LINE("Line Jack", NULL),
  21. SND_SOC_DAPM_LINE("USB Audio", NULL),
  22. };
  23. static const struct snd_soc_dapm_route lochnagar_sc_routes[] = {
  24. { "Line Jack", NULL, "AIF1 Playback" },
  25. { "AIF1 Capture", NULL, "Line Jack" },
  26. { "USB Audio", NULL, "USB1 Playback" },
  27. { "USB Audio", NULL, "USB2 Playback" },
  28. { "USB1 Capture", NULL, "USB Audio" },
  29. { "USB2 Capture", NULL, "USB Audio" },
  30. };
  31. static const unsigned int lochnagar_sc_chan_vals[] = {
  32. 4, 8,
  33. };
  34. static const struct snd_pcm_hw_constraint_list lochnagar_sc_chan_constraint = {
  35. .count = ARRAY_SIZE(lochnagar_sc_chan_vals),
  36. .list = lochnagar_sc_chan_vals,
  37. };
  38. static const unsigned int lochnagar_sc_rate_vals[] = {
  39. 8000, 16000, 24000, 32000, 48000, 96000, 192000,
  40. 22050, 44100, 88200, 176400,
  41. };
  42. static const struct snd_pcm_hw_constraint_list lochnagar_sc_rate_constraint = {
  43. .count = ARRAY_SIZE(lochnagar_sc_rate_vals),
  44. .list = lochnagar_sc_rate_vals,
  45. };
  46. static int lochnagar_sc_hw_rule_rate(struct snd_pcm_hw_params *params,
  47. struct snd_pcm_hw_rule *rule)
  48. {
  49. struct snd_interval range = {
  50. .min = 8000,
  51. .max = 24576000 / hw_param_interval(params, rule->deps[0])->max,
  52. };
  53. return snd_interval_refine(hw_param_interval(params, rule->var),
  54. &range);
  55. }
  56. static int lochnagar_sc_startup(struct snd_pcm_substream *substream,
  57. struct snd_soc_dai *dai)
  58. {
  59. struct snd_soc_component *comp = dai->component;
  60. struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp);
  61. int ret;
  62. ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
  63. SNDRV_PCM_HW_PARAM_RATE,
  64. &lochnagar_sc_rate_constraint);
  65. if (ret)
  66. return ret;
  67. return snd_pcm_hw_rule_add(substream->runtime, 0,
  68. SNDRV_PCM_HW_PARAM_RATE,
  69. lochnagar_sc_hw_rule_rate, priv,
  70. SNDRV_PCM_HW_PARAM_FRAME_BITS, -1);
  71. }
  72. static int lochnagar_sc_line_startup(struct snd_pcm_substream *substream,
  73. struct snd_soc_dai *dai)
  74. {
  75. struct snd_soc_component *comp = dai->component;
  76. struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp);
  77. int ret;
  78. ret = clk_prepare_enable(priv->mclk);
  79. if (ret < 0) {
  80. dev_err(dai->dev, "Failed to enable MCLK: %d\n", ret);
  81. return ret;
  82. }
  83. ret = lochnagar_sc_startup(substream, dai);
  84. if (ret)
  85. return ret;
  86. return snd_pcm_hw_constraint_list(substream->runtime, 0,
  87. SNDRV_PCM_HW_PARAM_CHANNELS,
  88. &lochnagar_sc_chan_constraint);
  89. }
  90. static void lochnagar_sc_line_shutdown(struct snd_pcm_substream *substream,
  91. struct snd_soc_dai *dai)
  92. {
  93. struct snd_soc_component *comp = dai->component;
  94. struct lochnagar_sc_priv *priv = snd_soc_component_get_drvdata(comp);
  95. clk_disable_unprepare(priv->mclk);
  96. }
  97. static int lochnagar_sc_check_fmt(struct snd_soc_dai *dai, unsigned int fmt,
  98. unsigned int tar)
  99. {
  100. tar |= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF;
  101. if ((fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) != tar)
  102. return -EINVAL;
  103. return 0;
  104. }
  105. static int lochnagar_sc_set_line_fmt(struct snd_soc_dai *dai, unsigned int fmt)
  106. {
  107. return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBS_CFS);
  108. }
  109. static int lochnagar_sc_set_usb_fmt(struct snd_soc_dai *dai, unsigned int fmt)
  110. {
  111. return lochnagar_sc_check_fmt(dai, fmt, SND_SOC_DAIFMT_CBM_CFM);
  112. }
  113. static const struct snd_soc_dai_ops lochnagar_sc_line_ops = {
  114. .startup = lochnagar_sc_line_startup,
  115. .shutdown = lochnagar_sc_line_shutdown,
  116. .set_fmt = lochnagar_sc_set_line_fmt,
  117. };
  118. static const struct snd_soc_dai_ops lochnagar_sc_usb_ops = {
  119. .startup = lochnagar_sc_startup,
  120. .set_fmt = lochnagar_sc_set_usb_fmt,
  121. };
  122. static struct snd_soc_dai_driver lochnagar_sc_dai[] = {
  123. {
  124. .name = "lochnagar-line",
  125. .playback = {
  126. .stream_name = "AIF1 Playback",
  127. .channels_min = 4,
  128. .channels_max = 8,
  129. .rates = SNDRV_PCM_RATE_KNOT,
  130. .formats = SNDRV_PCM_FMTBIT_S32_LE,
  131. },
  132. .capture = {
  133. .stream_name = "AIF1 Capture",
  134. .channels_min = 4,
  135. .channels_max = 8,
  136. .rates = SNDRV_PCM_RATE_KNOT,
  137. .formats = SNDRV_PCM_FMTBIT_S32_LE,
  138. },
  139. .ops = &lochnagar_sc_line_ops,
  140. .symmetric_rates = true,
  141. .symmetric_samplebits = true,
  142. },
  143. {
  144. .name = "lochnagar-usb1",
  145. .playback = {
  146. .stream_name = "USB1 Playback",
  147. .channels_min = 1,
  148. .channels_max = 8,
  149. .rates = SNDRV_PCM_RATE_KNOT,
  150. .formats = SNDRV_PCM_FMTBIT_S32_LE,
  151. },
  152. .capture = {
  153. .stream_name = "USB1 Capture",
  154. .channels_min = 1,
  155. .channels_max = 8,
  156. .rates = SNDRV_PCM_RATE_KNOT,
  157. .formats = SNDRV_PCM_FMTBIT_S32_LE,
  158. },
  159. .ops = &lochnagar_sc_usb_ops,
  160. .symmetric_rates = true,
  161. .symmetric_samplebits = true,
  162. },
  163. {
  164. .name = "lochnagar-usb2",
  165. .playback = {
  166. .stream_name = "USB2 Playback",
  167. .channels_min = 1,
  168. .channels_max = 8,
  169. .rates = SNDRV_PCM_RATE_KNOT,
  170. .formats = SNDRV_PCM_FMTBIT_S32_LE,
  171. },
  172. .capture = {
  173. .stream_name = "USB2 Capture",
  174. .channels_min = 1,
  175. .channels_max = 8,
  176. .rates = SNDRV_PCM_RATE_KNOT,
  177. .formats = SNDRV_PCM_FMTBIT_S32_LE,
  178. },
  179. .ops = &lochnagar_sc_usb_ops,
  180. .symmetric_rates = true,
  181. .symmetric_samplebits = true,
  182. },
  183. };
  184. static const struct snd_soc_component_driver lochnagar_sc_driver = {
  185. .non_legacy_dai_naming = 1,
  186. .dapm_widgets = lochnagar_sc_widgets,
  187. .num_dapm_widgets = ARRAY_SIZE(lochnagar_sc_widgets),
  188. .dapm_routes = lochnagar_sc_routes,
  189. .num_dapm_routes = ARRAY_SIZE(lochnagar_sc_routes),
  190. };
  191. static int lochnagar_sc_probe(struct platform_device *pdev)
  192. {
  193. struct lochnagar_sc_priv *priv;
  194. int ret;
  195. priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
  196. if (!priv)
  197. return -ENOMEM;
  198. priv->mclk = devm_clk_get(&pdev->dev, "mclk");
  199. if (IS_ERR(priv->mclk)) {
  200. ret = PTR_ERR(priv->mclk);
  201. dev_err(&pdev->dev, "Failed to get MCLK: %d\n", ret);
  202. return ret;
  203. }
  204. platform_set_drvdata(pdev, priv);
  205. return devm_snd_soc_register_component(&pdev->dev,
  206. &lochnagar_sc_driver,
  207. lochnagar_sc_dai,
  208. ARRAY_SIZE(lochnagar_sc_dai));
  209. }
  210. static const struct of_device_id lochnagar_of_match[] = {
  211. { .compatible = "cirrus,lochnagar2-soundcard" },
  212. {}
  213. };
  214. MODULE_DEVICE_TABLE(of, lochnagar_of_match);
  215. static struct platform_driver lochnagar_sc_codec_driver = {
  216. .driver = {
  217. .name = "lochnagar-soundcard",
  218. .of_match_table = of_match_ptr(lochnagar_of_match),
  219. },
  220. .probe = lochnagar_sc_probe,
  221. };
  222. module_platform_driver(lochnagar_sc_codec_driver);
  223. MODULE_DESCRIPTION("ASoC Lochnagar Sound Card Driver");
  224. MODULE_AUTHOR("Piotr Stankiewicz <piotrs@opensource.cirrus.com>");
  225. MODULE_LICENSE("GPL v2");
  226. MODULE_ALIAS("platform:lochnagar-soundcard");