18bb3bfe03002042252fdb428e8ecc0829e13fa6
[sfrench/cifs-2.6.git] / sound / soc / samsung / odroid.c
1 /*
2  * Copyright (C) 2017 Samsung Electronics Co., Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  */
8
9 #include <linux/clk.h>
10 #include <linux/clk-provider.h>
11 #include <linux/of.h>
12 #include <linux/of_device.h>
13 #include <linux/module.h>
14 #include <sound/soc.h>
15 #include <sound/pcm_params.h>
16 #include "i2s.h"
17 #include "i2s-regs.h"
18
19 struct odroid_priv {
20         struct snd_soc_card card;
21         struct clk *clk_i2s_bus;
22         struct clk *sclk_i2s;
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, 2);
30
31         return 0;
32 }
33
34 static const struct snd_soc_ops odroid_card_fe_ops = {
35         .startup = odroid_card_fe_startup,
36 };
37
38 static int odroid_card_be_hw_params(struct snd_pcm_substream *substream,
39                                       struct snd_pcm_hw_params *params)
40 {
41         struct snd_soc_pcm_runtime *rtd = substream->private_data;
42         struct odroid_priv *priv = snd_soc_card_get_drvdata(rtd->card);
43         unsigned int pll_freq, rclk_freq, rfs;
44         int ret;
45
46         switch (params_rate(params)) {
47         case 64000:
48                 pll_freq = 196608001U;
49                 rfs = 384;
50                 break;
51         case 44100:
52         case 88200:
53                 pll_freq = 180633609U;
54                 rfs = 512;
55                 break;
56         case 32000:
57         case 48000:
58         case 96000:
59                 pll_freq = 196608001U;
60                 rfs = 512;
61                 break;
62         default:
63                 return -EINVAL;
64         }
65
66         ret = clk_set_rate(priv->clk_i2s_bus, pll_freq / 2 + 1);
67         if (ret < 0)
68                 return ret;
69
70         /*
71          *  We add 1 to the rclk_freq value in order to avoid too low clock
72          *  frequency values due to the EPLL output frequency not being exact
73          *  multiple of the audio sampling rate.
74          */
75         rclk_freq = params_rate(params) * rfs + 1;
76
77         ret = clk_set_rate(priv->sclk_i2s, rclk_freq);
78         if (ret < 0)
79                 return ret;
80
81         if (rtd->num_codecs > 1) {
82                 struct snd_soc_dai *codec_dai = rtd->codec_dais[1];
83
84                 ret = snd_soc_dai_set_sysclk(codec_dai, 0, rclk_freq,
85                                              SND_SOC_CLOCK_IN);
86                 if (ret < 0)
87                         return ret;
88         }
89
90         return 0;
91 }
92
93 static const struct snd_soc_ops odroid_card_be_ops = {
94         .hw_params = odroid_card_be_hw_params,
95 };
96
97 static struct snd_soc_dai_link odroid_card_dais[] = {
98         {
99                 /* Primary FE <-> BE link */
100                 .codec_name = "snd-soc-dummy",
101                 .codec_dai_name = "snd-soc-dummy-dai",
102                 .ops = &odroid_card_fe_ops,
103                 .name = "Primary",
104                 .stream_name = "Primary",
105                 .platform_name = "3830000.i2s",
106                 .dynamic = 1,
107                 .dpcm_playback = 1,
108         }, {
109                 /* BE <-> CODECs link */
110                 .name = "I2S Mixer",
111                 .cpu_name = "snd-soc-dummy",
112                 .cpu_dai_name = "snd-soc-dummy-dai",
113                 .platform_name = "snd-soc-dummy",
114                 .ops = &odroid_card_be_ops,
115                 .no_pcm = 1,
116                 .dpcm_playback = 1,
117                 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
118                                 SND_SOC_DAIFMT_CBS_CFS,
119         }, {
120                 /* Secondary FE <-> BE link */
121                 .playback_only = 1,
122                 .codec_name = "snd-soc-dummy",
123                 .codec_dai_name = "snd-soc-dummy-dai",
124                 .ops = &odroid_card_fe_ops,
125                 .name = "Secondary",
126                 .stream_name = "Secondary",
127                 .platform_name = "samsung-i2s-sec",
128                 .dynamic = 1,
129                 .dpcm_playback = 1,
130         }
131 };
132
133 static int odroid_audio_probe(struct platform_device *pdev)
134 {
135         struct device *dev = &pdev->dev;
136         struct device_node *cpu, *cpu_dai, *codec;
137         struct odroid_priv *priv;
138         struct snd_soc_card *card;
139         struct snd_soc_dai_link *link, *codec_link;
140         int num_pcms, ret, i;
141         struct of_phandle_args args = {};
142
143         priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
144         if (!priv)
145                 return -ENOMEM;
146
147         card = &priv->card;
148         card->dev = dev;
149
150         card->owner = THIS_MODULE;
151         card->fully_routed = true;
152
153         snd_soc_card_set_drvdata(card, priv);
154
155         ret = snd_soc_of_parse_card_name(card, "model");
156         if (ret < 0)
157                 return ret;
158
159         if (of_property_read_bool(dev->of_node, "samsung,audio-widgets")) {
160                 ret = snd_soc_of_parse_audio_simple_widgets(card,
161                                                 "samsung,audio-widgets");
162                 if (ret < 0)
163                         return ret;
164         }
165
166         if (of_property_read_bool(dev->of_node, "samsung,audio-routing")) {
167                 ret = snd_soc_of_parse_audio_routing(card,
168                                                 "samsung,audio-routing");
169                 if (ret < 0)
170                         return ret;
171         }
172
173         card->dai_link = odroid_card_dais;
174         card->num_links = ARRAY_SIZE(odroid_card_dais);
175
176         cpu = of_get_child_by_name(dev->of_node, "cpu");
177         codec = of_get_child_by_name(dev->of_node, "codec");
178         link = card->dai_link;
179         codec_link = &card->dai_link[1];
180
181         /*
182          * For backwards compatibility create the secondary CPU DAI link only
183          * if there are 2 CPU DAI entries in the cpu sound-dai property in DT.
184          */
185         num_pcms = of_count_phandle_with_args(cpu, "sound-dai",
186                                               "#sound-dai-cells");
187         if (num_pcms == 1)
188                 card->num_links--;
189
190         for (i = 0; i < num_pcms; i++, link += 2) {
191                 ret = of_parse_phandle_with_args(cpu, "sound-dai",
192                                                  "#sound-dai-cells", i, &args);
193                 if (ret < 0)
194                         return ret;
195
196                 if (!args.np) {
197                         dev_err(dev, "sound-dai property parse error: %d\n", ret);
198                         return -EINVAL;
199                 }
200
201                 ret = snd_soc_get_dai_name(&args, &link->cpu_dai_name);
202                 of_node_put(args.np);
203
204                 if (ret < 0)
205                         return ret;
206         }
207
208         cpu_dai = of_parse_phandle(cpu, "sound-dai", 0);
209         of_node_put(cpu);
210         of_node_put(codec);
211
212         ret = snd_soc_of_get_dai_link_codecs(dev, codec, codec_link);
213         if (ret < 0)
214                 goto err_put_codec_n;
215
216         /* Set capture capability only for boards with the MAX98090 CODEC */
217         if (codec_link->num_codecs > 1) {
218                 card->dai_link[0].dpcm_capture = 1;
219                 card->dai_link[1].dpcm_capture = 1;
220         }
221
222         priv->sclk_i2s = of_clk_get_by_name(cpu_dai, "i2s_opclk1");
223         if (IS_ERR(priv->sclk_i2s)) {
224                 ret = PTR_ERR(priv->sclk_i2s);
225                 goto err_put_codec_n;
226         }
227
228         priv->clk_i2s_bus = of_clk_get_by_name(cpu_dai, "iis");
229         if (IS_ERR(priv->clk_i2s_bus)) {
230                 ret = PTR_ERR(priv->clk_i2s_bus);
231                 goto err_put_sclk;
232         }
233         of_node_put(cpu_dai);
234
235         ret = devm_snd_soc_register_card(dev, card);
236         if (ret < 0) {
237                 dev_err(dev, "snd_soc_register_card() failed: %d\n", ret);
238                 goto err_put_clk_i2s;
239         }
240
241         return 0;
242
243 err_put_clk_i2s:
244         clk_put(priv->clk_i2s_bus);
245 err_put_sclk:
246         clk_put(priv->sclk_i2s);
247 err_put_codec_n:
248         snd_soc_of_put_dai_link_codecs(codec_link);
249         return ret;
250 }
251
252 static int odroid_audio_remove(struct platform_device *pdev)
253 {
254         struct odroid_priv *priv = platform_get_drvdata(pdev);
255
256         snd_soc_of_put_dai_link_codecs(&priv->card.dai_link[1]);
257         clk_put(priv->sclk_i2s);
258         clk_put(priv->clk_i2s_bus);
259
260         return 0;
261 }
262
263 static const struct of_device_id odroid_audio_of_match[] = {
264         { .compatible   = "hardkernel,odroid-xu3-audio" },
265         { .compatible   = "hardkernel,odroid-xu4-audio" },
266         { .compatible   = "samsung,odroid-xu3-audio" },
267         { .compatible   = "samsung,odroid-xu4-audio" },
268         { },
269 };
270 MODULE_DEVICE_TABLE(of, odroid_audio_of_match);
271
272 static struct platform_driver odroid_audio_driver = {
273         .driver = {
274                 .name           = "odroid-audio",
275                 .of_match_table = odroid_audio_of_match,
276                 .pm             = &snd_soc_pm_ops,
277         },
278         .probe  = odroid_audio_probe,
279         .remove = odroid_audio_remove,
280 };
281 module_platform_driver(odroid_audio_driver);
282
283 MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
284 MODULE_DESCRIPTION("Odroid XU3/XU4 audio support");
285 MODULE_LICENSE("GPL v2");