1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // ASoC machine driver for Snow boards |
4 | |
5 | #include <linux/clk.h> |
6 | #include <linux/module.h> |
7 | #include <linux/platform_device.h> |
8 | #include <linux/of.h> |
9 | #include <sound/pcm_params.h> |
10 | #include <sound/soc.h> |
11 | |
12 | #include "i2s.h" |
13 | |
14 | #define FIN_PLL_RATE 24000000 |
15 | |
16 | SND_SOC_DAILINK_DEFS(links, |
17 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
18 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
19 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
20 | |
21 | struct snow_priv { |
22 | struct snd_soc_dai_link dai_link; |
23 | struct clk *clk_i2s_bus; |
24 | }; |
25 | |
26 | static int snow_card_hw_params(struct snd_pcm_substream *substream, |
27 | struct snd_pcm_hw_params *params) |
28 | { |
29 | static const unsigned int pll_rate[] = { |
30 | 73728000U, 67737602U, 49152000U, 45158401U, 32768001U |
31 | }; |
32 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
33 | struct snow_priv *priv = snd_soc_card_get_drvdata(card: rtd->card); |
34 | int bfs, psr, rfs, bitwidth; |
35 | unsigned long int rclk; |
36 | long int freq = -EINVAL; |
37 | int ret, i; |
38 | |
39 | bitwidth = snd_pcm_format_width(format: params_format(p: params)); |
40 | if (bitwidth < 0) { |
41 | dev_err(rtd->card->dev, "Invalid bit-width: %d\n" , bitwidth); |
42 | return bitwidth; |
43 | } |
44 | |
45 | if (bitwidth != 16 && bitwidth != 24) { |
46 | dev_err(rtd->card->dev, "Unsupported bit-width: %d\n" , bitwidth); |
47 | return -EINVAL; |
48 | } |
49 | |
50 | bfs = 2 * bitwidth; |
51 | |
52 | switch (params_rate(p: params)) { |
53 | case 16000: |
54 | case 22050: |
55 | case 24000: |
56 | case 32000: |
57 | case 44100: |
58 | case 48000: |
59 | case 88200: |
60 | case 96000: |
61 | rfs = 8 * bfs; |
62 | break; |
63 | case 64000: |
64 | rfs = 384; |
65 | break; |
66 | case 8000: |
67 | case 11025: |
68 | case 12000: |
69 | rfs = 16 * bfs; |
70 | break; |
71 | default: |
72 | return -EINVAL; |
73 | } |
74 | |
75 | rclk = params_rate(p: params) * rfs; |
76 | |
77 | for (psr = 8; psr > 0; psr /= 2) { |
78 | for (i = 0; i < ARRAY_SIZE(pll_rate); i++) { |
79 | if ((pll_rate[i] - rclk * psr) <= 2) { |
80 | freq = pll_rate[i]; |
81 | break; |
82 | } |
83 | } |
84 | } |
85 | if (freq < 0) { |
86 | dev_err(rtd->card->dev, "Unsupported RCLK rate: %lu\n" , rclk); |
87 | return -EINVAL; |
88 | } |
89 | |
90 | ret = clk_set_rate(clk: priv->clk_i2s_bus, rate: freq); |
91 | if (ret < 0) { |
92 | dev_err(rtd->card->dev, "I2S bus clock rate set failed\n" ); |
93 | return ret; |
94 | } |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static const struct snd_soc_ops snow_card_ops = { |
100 | .hw_params = snow_card_hw_params, |
101 | }; |
102 | |
103 | static int snow_late_probe(struct snd_soc_card *card) |
104 | { |
105 | struct snd_soc_pcm_runtime *rtd; |
106 | struct snd_soc_dai *codec_dai; |
107 | |
108 | rtd = snd_soc_get_pcm_runtime(card, dai_link: &card->dai_link[0]); |
109 | |
110 | /* In the multi-codec case codec_dais 0 is MAX98095 and 1 is HDMI. */ |
111 | codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
112 | |
113 | /* Set the MCLK rate for the codec */ |
114 | return snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, |
115 | FIN_PLL_RATE, SND_SOC_CLOCK_IN); |
116 | } |
117 | |
118 | static struct snd_soc_card snow_snd = { |
119 | .name = "Snow-I2S" , |
120 | .owner = THIS_MODULE, |
121 | .late_probe = snow_late_probe, |
122 | }; |
123 | |
124 | static int snow_probe(struct platform_device *pdev) |
125 | { |
126 | struct device *dev = &pdev->dev; |
127 | struct snd_soc_card *card = &snow_snd; |
128 | struct device_node *cpu, *codec; |
129 | struct snd_soc_dai_link *link; |
130 | struct snow_priv *priv; |
131 | int ret; |
132 | |
133 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
134 | if (!priv) |
135 | return -ENOMEM; |
136 | |
137 | link = &priv->dai_link; |
138 | |
139 | link->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
140 | SND_SOC_DAIFMT_CBS_CFS; |
141 | |
142 | link->name = "Primary" ; |
143 | link->stream_name = link->name; |
144 | |
145 | link->cpus = links_cpus; |
146 | link->num_cpus = ARRAY_SIZE(links_cpus); |
147 | link->codecs = links_codecs; |
148 | link->num_codecs = ARRAY_SIZE(links_codecs); |
149 | link->platforms = links_platforms; |
150 | link->num_platforms = ARRAY_SIZE(links_platforms); |
151 | |
152 | card->dai_link = link; |
153 | card->num_links = 1; |
154 | card->dev = dev; |
155 | |
156 | /* Try new DT bindings with HDMI support first. */ |
157 | cpu = of_get_child_by_name(node: dev->of_node, name: "cpu" ); |
158 | |
159 | if (cpu) { |
160 | link->ops = &snow_card_ops; |
161 | |
162 | link->cpus->of_node = of_parse_phandle(np: cpu, phandle_name: "sound-dai" , index: 0); |
163 | of_node_put(node: cpu); |
164 | |
165 | if (!link->cpus->of_node) { |
166 | dev_err(dev, "Failed parsing cpu/sound-dai property\n" ); |
167 | return -EINVAL; |
168 | } |
169 | |
170 | codec = of_get_child_by_name(node: dev->of_node, name: "codec" ); |
171 | ret = snd_soc_of_get_dai_link_codecs(dev, of_node: codec, dai_link: link); |
172 | of_node_put(node: codec); |
173 | |
174 | if (ret < 0) { |
175 | of_node_put(node: link->cpus->of_node); |
176 | dev_err(dev, "Failed parsing codec node\n" ); |
177 | return ret; |
178 | } |
179 | |
180 | priv->clk_i2s_bus = of_clk_get_by_name(np: link->cpus->of_node, |
181 | name: "i2s_opclk0" ); |
182 | if (IS_ERR(ptr: priv->clk_i2s_bus)) { |
183 | snd_soc_of_put_dai_link_codecs(dai_link: link); |
184 | of_node_put(node: link->cpus->of_node); |
185 | return PTR_ERR(ptr: priv->clk_i2s_bus); |
186 | } |
187 | } else { |
188 | link->codecs->dai_name = "HiFi" ; |
189 | |
190 | link->cpus->of_node = of_parse_phandle(np: dev->of_node, |
191 | phandle_name: "samsung,i2s-controller" , index: 0); |
192 | if (!link->cpus->of_node) { |
193 | dev_err(dev, "i2s-controller property parse error\n" ); |
194 | return -EINVAL; |
195 | } |
196 | |
197 | link->codecs->of_node = of_parse_phandle(np: dev->of_node, |
198 | phandle_name: "samsung,audio-codec" , index: 0); |
199 | if (!link->codecs->of_node) { |
200 | of_node_put(node: link->cpus->of_node); |
201 | dev_err(dev, "audio-codec property parse error\n" ); |
202 | return -EINVAL; |
203 | } |
204 | } |
205 | |
206 | link->platforms->of_node = link->cpus->of_node; |
207 | |
208 | /* Update card-name if provided through DT, else use default name */ |
209 | snd_soc_of_parse_card_name(card, propname: "samsung,model" ); |
210 | |
211 | snd_soc_card_set_drvdata(card, data: priv); |
212 | |
213 | ret = devm_snd_soc_register_card(dev, card); |
214 | if (ret) |
215 | return dev_err_probe(dev: &pdev->dev, err: ret, |
216 | fmt: "snd_soc_register_card failed\n" ); |
217 | |
218 | return 0; |
219 | } |
220 | |
221 | static void snow_remove(struct platform_device *pdev) |
222 | { |
223 | struct snow_priv *priv = platform_get_drvdata(pdev); |
224 | struct snd_soc_dai_link *link = &priv->dai_link; |
225 | |
226 | of_node_put(node: link->cpus->of_node); |
227 | of_node_put(node: link->codecs->of_node); |
228 | snd_soc_of_put_dai_link_codecs(dai_link: link); |
229 | |
230 | clk_put(clk: priv->clk_i2s_bus); |
231 | } |
232 | |
233 | static const struct of_device_id snow_of_match[] = { |
234 | { .compatible = "google,snow-audio-max98090" , }, |
235 | { .compatible = "google,snow-audio-max98091" , }, |
236 | { .compatible = "google,snow-audio-max98095" , }, |
237 | {}, |
238 | }; |
239 | MODULE_DEVICE_TABLE(of, snow_of_match); |
240 | |
241 | static struct platform_driver snow_driver = { |
242 | .driver = { |
243 | .name = "snow-audio" , |
244 | .pm = &snd_soc_pm_ops, |
245 | .of_match_table = snow_of_match, |
246 | }, |
247 | .probe = snow_probe, |
248 | .remove_new = snow_remove, |
249 | }; |
250 | |
251 | module_platform_driver(snow_driver); |
252 | |
253 | MODULE_DESCRIPTION("ALSA SoC Audio machine driver for Snow" ); |
254 | MODULE_LICENSE("GPL" ); |
255 | |