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