1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Copyright (C) 2017 Samsung Electronics Co., Ltd. |
4 | |
5 | #include <linux/clk.h> |
6 | #include <linux/clk-provider.h> |
7 | #include <linux/of.h> |
8 | #include <linux/module.h> |
9 | #include <sound/soc.h> |
10 | #include <sound/pcm_params.h> |
11 | #include "i2s.h" |
12 | #include "i2s-regs.h" |
13 | |
14 | struct odroid_priv { |
15 | struct snd_soc_card card; |
16 | struct clk *clk_i2s_bus; |
17 | struct clk *sclk_i2s; |
18 | |
19 | /* Spinlock protecting fields below */ |
20 | spinlock_t lock; |
21 | unsigned int be_sample_rate; |
22 | bool be_active; |
23 | }; |
24 | |
25 | static int odroid_card_fe_startup(struct snd_pcm_substream *substream) |
26 | { |
27 | struct snd_pcm_runtime *runtime = substream->runtime; |
28 | |
29 | snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, val: 2); |
30 | |
31 | return 0; |
32 | } |
33 | |
34 | static int odroid_card_fe_hw_params(struct snd_pcm_substream *substream, |
35 | struct snd_pcm_hw_params *params) |
36 | { |
37 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
38 | struct odroid_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
39 | unsigned long flags; |
40 | int ret = 0; |
41 | |
42 | spin_lock_irqsave(&priv->lock, flags); |
43 | if (priv->be_active && priv->be_sample_rate != params_rate(p: params)) |
44 | ret = -EINVAL; |
45 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
46 | |
47 | return ret; |
48 | } |
49 | |
50 | static const struct snd_soc_ops odroid_card_fe_ops = { |
51 | .startup = odroid_card_fe_startup, |
52 | .hw_params = odroid_card_fe_hw_params, |
53 | }; |
54 | |
55 | static int odroid_card_be_hw_params(struct snd_pcm_substream *substream, |
56 | struct snd_pcm_hw_params *params) |
57 | { |
58 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
59 | struct odroid_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
60 | unsigned int pll_freq, rclk_freq, rfs; |
61 | unsigned long flags; |
62 | int ret; |
63 | |
64 | switch (params_rate(p: params)) { |
65 | case 64000: |
66 | pll_freq = 196608001U; |
67 | rfs = 384; |
68 | break; |
69 | case 44100: |
70 | case 88200: |
71 | pll_freq = 180633609U; |
72 | rfs = 512; |
73 | break; |
74 | case 32000: |
75 | case 48000: |
76 | case 96000: |
77 | pll_freq = 196608001U; |
78 | rfs = 512; |
79 | break; |
80 | default: |
81 | return -EINVAL; |
82 | } |
83 | |
84 | ret = clk_set_rate(clk: priv->clk_i2s_bus, rate: pll_freq / 2 + 1); |
85 | if (ret < 0) |
86 | return ret; |
87 | |
88 | /* |
89 | * We add 2 to the rclk_freq value in order to avoid too low clock |
90 | * frequency values due to the EPLL output frequency not being exact |
91 | * multiple of the audio sampling rate. |
92 | */ |
93 | rclk_freq = params_rate(p: params) * rfs + 2; |
94 | |
95 | ret = clk_set_rate(clk: priv->sclk_i2s, rate: rclk_freq); |
96 | if (ret < 0) |
97 | return ret; |
98 | |
99 | if (rtd->dai_link->num_codecs > 1) { |
100 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 1); |
101 | |
102 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: rclk_freq, |
103 | SND_SOC_CLOCK_IN); |
104 | if (ret < 0) |
105 | return ret; |
106 | } |
107 | |
108 | spin_lock_irqsave(&priv->lock, flags); |
109 | priv->be_sample_rate = params_rate(p: params); |
110 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
111 | |
112 | return 0; |
113 | } |
114 | |
115 | static int odroid_card_be_trigger(struct snd_pcm_substream *substream, int cmd) |
116 | { |
117 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
118 | struct odroid_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
119 | unsigned long flags; |
120 | |
121 | spin_lock_irqsave(&priv->lock, flags); |
122 | |
123 | switch (cmd) { |
124 | case SNDRV_PCM_TRIGGER_START: |
125 | case SNDRV_PCM_TRIGGER_RESUME: |
126 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
127 | priv->be_active = true; |
128 | break; |
129 | |
130 | case SNDRV_PCM_TRIGGER_STOP: |
131 | case SNDRV_PCM_TRIGGER_SUSPEND: |
132 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
133 | priv->be_active = false; |
134 | break; |
135 | } |
136 | |
137 | spin_unlock_irqrestore(lock: &priv->lock, flags); |
138 | |
139 | return 0; |
140 | } |
141 | |
142 | static const struct snd_soc_ops odroid_card_be_ops = { |
143 | .hw_params = odroid_card_be_hw_params, |
144 | .trigger = odroid_card_be_trigger, |
145 | }; |
146 | |
147 | /* DAPM routes for backward compatibility with old DTS */ |
148 | static const struct snd_soc_dapm_route odroid_dapm_routes[] = { |
149 | { "I2S Playback" , NULL, "Mixer DAI TX" }, |
150 | { "HiFi Playback" , NULL, "Mixer DAI TX" }, |
151 | }; |
152 | |
153 | SND_SOC_DAILINK_DEFS(primary, |
154 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
155 | DAILINK_COMP_ARRAY(COMP_DUMMY()), |
156 | DAILINK_COMP_ARRAY(COMP_PLATFORM("3830000.i2s" ))); |
157 | |
158 | SND_SOC_DAILINK_DEFS(mixer, |
159 | DAILINK_COMP_ARRAY(COMP_DUMMY()), |
160 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
161 | |
162 | SND_SOC_DAILINK_DEFS(secondary, |
163 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
164 | DAILINK_COMP_ARRAY(COMP_DUMMY()), |
165 | DAILINK_COMP_ARRAY(COMP_PLATFORM("3830000.i2s-sec" ))); |
166 | |
167 | static struct snd_soc_dai_link odroid_card_dais[] = { |
168 | { |
169 | /* Primary FE <-> BE link */ |
170 | .ops = &odroid_card_fe_ops, |
171 | .name = "Primary" , |
172 | .stream_name = "Primary" , |
173 | .dynamic = 1, |
174 | .dpcm_playback = 1, |
175 | SND_SOC_DAILINK_REG(primary), |
176 | }, { |
177 | /* BE <-> CODECs link */ |
178 | .name = "I2S Mixer" , |
179 | .ops = &odroid_card_be_ops, |
180 | .no_pcm = 1, |
181 | .dpcm_playback = 1, |
182 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
183 | SND_SOC_DAIFMT_CBS_CFS, |
184 | SND_SOC_DAILINK_REG(mixer), |
185 | }, { |
186 | /* Secondary FE <-> BE link */ |
187 | .playback_only = 1, |
188 | .ops = &odroid_card_fe_ops, |
189 | .name = "Secondary" , |
190 | .stream_name = "Secondary" , |
191 | .dynamic = 1, |
192 | .dpcm_playback = 1, |
193 | SND_SOC_DAILINK_REG(secondary), |
194 | } |
195 | }; |
196 | |
197 | static int odroid_audio_probe(struct platform_device *pdev) |
198 | { |
199 | struct device *dev = &pdev->dev; |
200 | struct device_node *cpu_dai = NULL; |
201 | struct device_node *cpu, *codec; |
202 | struct odroid_priv *priv; |
203 | struct snd_soc_card *card; |
204 | struct snd_soc_dai_link *link, *codec_link; |
205 | int num_pcms, ret, i; |
206 | |
207 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
208 | if (!priv) |
209 | return -ENOMEM; |
210 | |
211 | card = &priv->card; |
212 | card->dev = dev; |
213 | |
214 | card->owner = THIS_MODULE; |
215 | card->fully_routed = true; |
216 | |
217 | spin_lock_init(&priv->lock); |
218 | snd_soc_card_set_drvdata(card, data: priv); |
219 | |
220 | ret = snd_soc_of_parse_card_name(card, propname: "model" ); |
221 | if (ret < 0) |
222 | return ret; |
223 | |
224 | if (of_property_present(np: dev->of_node, propname: "samsung,audio-widgets" )) { |
225 | ret = snd_soc_of_parse_audio_simple_widgets(card, |
226 | propname: "samsung,audio-widgets" ); |
227 | if (ret < 0) |
228 | return ret; |
229 | } |
230 | |
231 | ret = 0; |
232 | if (of_property_present(np: dev->of_node, propname: "audio-routing" )) |
233 | ret = snd_soc_of_parse_audio_routing(card, propname: "audio-routing" ); |
234 | else if (of_property_present(np: dev->of_node, propname: "samsung,audio-routing" )) |
235 | ret = snd_soc_of_parse_audio_routing(card, propname: "samsung,audio-routing" ); |
236 | if (ret < 0) |
237 | return ret; |
238 | |
239 | card->dai_link = odroid_card_dais; |
240 | card->num_links = ARRAY_SIZE(odroid_card_dais); |
241 | |
242 | cpu = of_get_child_by_name(node: dev->of_node, name: "cpu" ); |
243 | codec = of_get_child_by_name(node: dev->of_node, name: "codec" ); |
244 | link = card->dai_link; |
245 | codec_link = &card->dai_link[1]; |
246 | |
247 | /* |
248 | * For backwards compatibility create the secondary CPU DAI link only |
249 | * if there are 2 CPU DAI entries in the cpu sound-dai property in DT. |
250 | * Also add required DAPM routes not available in old DTS. |
251 | */ |
252 | num_pcms = of_count_phandle_with_args(np: cpu, list_name: "sound-dai" , |
253 | cells_name: "#sound-dai-cells" ); |
254 | if (num_pcms == 1) { |
255 | card->dapm_routes = odroid_dapm_routes; |
256 | card->num_dapm_routes = ARRAY_SIZE(odroid_dapm_routes); |
257 | card->num_links--; |
258 | } |
259 | |
260 | for (i = 0; i < num_pcms; i++, link += 2) { |
261 | ret = snd_soc_of_get_dai_name(of_node: cpu, dai_name: &link->cpus->dai_name, index: i); |
262 | if (ret < 0) |
263 | break; |
264 | } |
265 | if (ret == 0) { |
266 | cpu_dai = of_parse_phandle(np: cpu, phandle_name: "sound-dai" , index: 0); |
267 | if (!cpu_dai) |
268 | ret = -EINVAL; |
269 | } |
270 | |
271 | of_node_put(node: cpu); |
272 | if (ret < 0) |
273 | goto err_put_node; |
274 | |
275 | ret = snd_soc_of_get_dai_link_codecs(dev, of_node: codec, dai_link: codec_link); |
276 | if (ret < 0) |
277 | goto err_put_cpu_dai; |
278 | |
279 | /* Set capture capability only for boards with the MAX98090 CODEC */ |
280 | if (codec_link->num_codecs > 1) { |
281 | card->dai_link[0].dpcm_capture = 1; |
282 | card->dai_link[1].dpcm_capture = 1; |
283 | } |
284 | |
285 | priv->sclk_i2s = of_clk_get_by_name(np: cpu_dai, name: "i2s_opclk1" ); |
286 | if (IS_ERR(ptr: priv->sclk_i2s)) { |
287 | ret = PTR_ERR(ptr: priv->sclk_i2s); |
288 | goto err_put_cpu_dai; |
289 | } |
290 | |
291 | priv->clk_i2s_bus = of_clk_get_by_name(np: cpu_dai, name: "iis" ); |
292 | if (IS_ERR(ptr: priv->clk_i2s_bus)) { |
293 | ret = PTR_ERR(ptr: priv->clk_i2s_bus); |
294 | goto err_put_sclk; |
295 | } |
296 | |
297 | ret = devm_snd_soc_register_card(dev, card); |
298 | if (ret < 0) { |
299 | dev_err_probe(dev, err: ret, fmt: "snd_soc_register_card() failed\n" ); |
300 | goto err_put_clk_i2s; |
301 | } |
302 | |
303 | of_node_put(node: cpu_dai); |
304 | of_node_put(node: codec); |
305 | return 0; |
306 | |
307 | err_put_clk_i2s: |
308 | clk_put(clk: priv->clk_i2s_bus); |
309 | err_put_sclk: |
310 | clk_put(clk: priv->sclk_i2s); |
311 | err_put_cpu_dai: |
312 | of_node_put(node: cpu_dai); |
313 | snd_soc_of_put_dai_link_codecs(dai_link: codec_link); |
314 | err_put_node: |
315 | of_node_put(node: codec); |
316 | return ret; |
317 | } |
318 | |
319 | static void odroid_audio_remove(struct platform_device *pdev) |
320 | { |
321 | struct odroid_priv *priv = platform_get_drvdata(pdev); |
322 | |
323 | snd_soc_of_put_dai_link_codecs(dai_link: &priv->card.dai_link[1]); |
324 | clk_put(clk: priv->sclk_i2s); |
325 | clk_put(clk: priv->clk_i2s_bus); |
326 | } |
327 | |
328 | static const struct of_device_id odroid_audio_of_match[] = { |
329 | { .compatible = "hardkernel,odroid-xu3-audio" }, |
330 | { .compatible = "hardkernel,odroid-xu4-audio" }, |
331 | { .compatible = "samsung,odroid-xu3-audio" }, |
332 | { .compatible = "samsung,odroid-xu4-audio" }, |
333 | { }, |
334 | }; |
335 | MODULE_DEVICE_TABLE(of, odroid_audio_of_match); |
336 | |
337 | static struct platform_driver odroid_audio_driver = { |
338 | .driver = { |
339 | .name = "odroid-audio" , |
340 | .of_match_table = odroid_audio_of_match, |
341 | .pm = &snd_soc_pm_ops, |
342 | }, |
343 | .probe = odroid_audio_probe, |
344 | .remove_new = odroid_audio_remove, |
345 | }; |
346 | module_platform_driver(odroid_audio_driver); |
347 | |
348 | MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>" ); |
349 | MODULE_DESCRIPTION("Odroid XU3/XU4 audio support" ); |
350 | MODULE_LICENSE("GPL v2" ); |
351 | |