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