Merge tag 'docs-5.9-2' of git://git.lwn.net/linux
[linux-2.6-microblaze.git] / sound / soc / samsung / smdk_spdif.c
1 // SPDX-License-Identifier: GPL-2.0+
2 //
3 // smdk_spdif.c - S/PDIF audio for SMDK
4 //
5 // Copyright (C) 2010 Samsung Electronics Co., Ltd.
6
7 #include <linux/clk.h>
8 #include <linux/module.h>
9
10 #include <sound/soc.h>
11
12 #include "spdif.h"
13
14 /* Audio clock settings are belonged to board specific part. Every
15  * board can set audio source clock setting which is matched with H/W
16  * like this function-'set_audio_clock_heirachy'.
17  */
18 static int set_audio_clock_heirachy(struct platform_device *pdev)
19 {
20         struct clk *fout_epll, *mout_epll, *sclk_audio0, *sclk_spdif;
21         int ret = 0;
22
23         fout_epll = clk_get(NULL, "fout_epll");
24         if (IS_ERR(fout_epll)) {
25                 printk(KERN_WARNING "%s: Cannot find fout_epll.\n",
26                                 __func__);
27                 return -EINVAL;
28         }
29
30         mout_epll = clk_get(NULL, "mout_epll");
31         if (IS_ERR(mout_epll)) {
32                 printk(KERN_WARNING "%s: Cannot find mout_epll.\n",
33                                 __func__);
34                 ret = -EINVAL;
35                 goto out1;
36         }
37
38         sclk_audio0 = clk_get(&pdev->dev, "sclk_audio");
39         if (IS_ERR(sclk_audio0)) {
40                 printk(KERN_WARNING "%s: Cannot find sclk_audio.\n",
41                                 __func__);
42                 ret = -EINVAL;
43                 goto out2;
44         }
45
46         sclk_spdif = clk_get(NULL, "sclk_spdif");
47         if (IS_ERR(sclk_spdif)) {
48                 printk(KERN_WARNING "%s: Cannot find sclk_spdif.\n",
49                                 __func__);
50                 ret = -EINVAL;
51                 goto out3;
52         }
53
54         /* Set audio clock hierarchy for S/PDIF */
55         clk_set_parent(mout_epll, fout_epll);
56         clk_set_parent(sclk_audio0, mout_epll);
57         clk_set_parent(sclk_spdif, sclk_audio0);
58
59         clk_put(sclk_spdif);
60 out3:
61         clk_put(sclk_audio0);
62 out2:
63         clk_put(mout_epll);
64 out1:
65         clk_put(fout_epll);
66
67         return ret;
68 }
69
70 /* We should haved to set clock directly on this part because of clock
71  * scheme of Samsudng SoCs did not support to set rates from abstrct
72  * clock of it's hierarchy.
73  */
74 static int set_audio_clock_rate(unsigned long epll_rate,
75                                 unsigned long audio_rate)
76 {
77         struct clk *fout_epll, *sclk_spdif;
78
79         fout_epll = clk_get(NULL, "fout_epll");
80         if (IS_ERR(fout_epll)) {
81                 printk(KERN_ERR "%s: failed to get fout_epll\n", __func__);
82                 return -ENOENT;
83         }
84
85         clk_set_rate(fout_epll, epll_rate);
86         clk_put(fout_epll);
87
88         sclk_spdif = clk_get(NULL, "sclk_spdif");
89         if (IS_ERR(sclk_spdif)) {
90                 printk(KERN_ERR "%s: failed to get sclk_spdif\n", __func__);
91                 return -ENOENT;
92         }
93
94         clk_set_rate(sclk_spdif, audio_rate);
95         clk_put(sclk_spdif);
96
97         return 0;
98 }
99
100 static int smdk_hw_params(struct snd_pcm_substream *substream,
101                 struct snd_pcm_hw_params *params)
102 {
103         struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
104         struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
105         unsigned long pll_out, rclk_rate;
106         int ret, ratio;
107
108         switch (params_rate(params)) {
109         case 44100:
110                 pll_out = 45158400;
111                 break;
112         case 32000:
113         case 48000:
114         case 96000:
115                 pll_out = 49152000;
116                 break;
117         default:
118                 return -EINVAL;
119         }
120
121         /* Setting ratio to 512fs helps to use S/PDIF with HDMI without
122          * modify S/PDIF ASoC machine driver.
123          */
124         ratio = 512;
125         rclk_rate = params_rate(params) * ratio;
126
127         /* Set audio source clock rates */
128         ret = set_audio_clock_rate(pll_out, rclk_rate);
129         if (ret < 0)
130                 return ret;
131
132         /* Set S/PDIF uses internal source clock */
133         ret = snd_soc_dai_set_sysclk(cpu_dai, SND_SOC_SPDIF_INT_MCLK,
134                                         rclk_rate, SND_SOC_CLOCK_IN);
135         if (ret < 0)
136                 return ret;
137
138         return ret;
139 }
140
141 static const struct snd_soc_ops smdk_spdif_ops = {
142         .hw_params = smdk_hw_params,
143 };
144
145 SND_SOC_DAILINK_DEFS(spdif,
146         DAILINK_COMP_ARRAY(COMP_CPU("samsung-spdif")),
147         DAILINK_COMP_ARRAY(COMP_CODEC("spdif-dit", "dit-hifi")),
148         DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-spdif")));
149
150 static struct snd_soc_dai_link smdk_dai = {
151         .name = "S/PDIF",
152         .stream_name = "S/PDIF PCM Playback",
153         .ops = &smdk_spdif_ops,
154         SND_SOC_DAILINK_REG(spdif),
155 };
156
157 static struct snd_soc_card smdk = {
158         .name = "SMDK-S/PDIF",
159         .owner = THIS_MODULE,
160         .dai_link = &smdk_dai,
161         .num_links = 1,
162 };
163
164 static struct platform_device *smdk_snd_spdif_dit_device;
165 static struct platform_device *smdk_snd_spdif_device;
166
167 static int __init smdk_init(void)
168 {
169         int ret;
170
171         smdk_snd_spdif_dit_device = platform_device_alloc("spdif-dit", -1);
172         if (!smdk_snd_spdif_dit_device)
173                 return -ENOMEM;
174
175         ret = platform_device_add(smdk_snd_spdif_dit_device);
176         if (ret)
177                 goto err1;
178
179         smdk_snd_spdif_device = platform_device_alloc("soc-audio", -1);
180         if (!smdk_snd_spdif_device) {
181                 ret = -ENOMEM;
182                 goto err2;
183         }
184
185         platform_set_drvdata(smdk_snd_spdif_device, &smdk);
186
187         ret = platform_device_add(smdk_snd_spdif_device);
188         if (ret)
189                 goto err3;
190
191         /* Set audio clock hierarchy manually */
192         ret = set_audio_clock_heirachy(smdk_snd_spdif_device);
193         if (ret)
194                 goto err4;
195
196         return 0;
197 err4:
198         platform_device_del(smdk_snd_spdif_device);
199 err3:
200         platform_device_put(smdk_snd_spdif_device);
201 err2:
202         platform_device_del(smdk_snd_spdif_dit_device);
203 err1:
204         platform_device_put(smdk_snd_spdif_dit_device);
205         return ret;
206 }
207
208 static void __exit smdk_exit(void)
209 {
210         platform_device_unregister(smdk_snd_spdif_device);
211         platform_device_unregister(smdk_snd_spdif_dit_device);
212 }
213
214 module_init(smdk_init);
215 module_exit(smdk_exit);
216
217 MODULE_AUTHOR("Seungwhan Youn, <sw.youn@samsung.com>");
218 MODULE_DESCRIPTION("ALSA SoC SMDK+S/PDIF");
219 MODULE_LICENSE("GPL");