xref: /openbmc/linux/sound/soc/samsung/smdk_spdif.c (revision 54525552)
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");
222