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