1
2
3
4
5
6
7
8
9
10#include <linux/clk.h>
11#include <linux/gpio.h>
12#include <linux/module.h>
13
14#include <sound/soc.h>
15#include <sound/s3c24xx_uda134x.h>
16
17#include "regs-iis.h"
18#include "s3c24xx-i2s.h"
19
20struct s3c24xx_uda134x {
21 struct clk *xtal;
22 struct clk *pclk;
23 struct mutex clk_lock;
24 int clk_users;
25};
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41static unsigned int rates[33 * 2];
42#ifdef ENFORCE_RATES
43static const struct snd_pcm_hw_constraint_list hw_constraints_rates = {
44 .count = ARRAY_SIZE(rates),
45 .list = rates,
46 .mask = 0,
47};
48#endif
49
50static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
51{
52 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
53 struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card);
54 struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
55 int ret = 0;
56
57 mutex_lock(&priv->clk_lock);
58
59 if (priv->clk_users == 0) {
60 priv->xtal = clk_get(rtd->dev, "xtal");
61 if (IS_ERR(priv->xtal)) {
62 dev_err(rtd->dev, "%s cannot get xtal\n", __func__);
63 ret = PTR_ERR(priv->xtal);
64 } else {
65 priv->pclk = clk_get(cpu_dai->dev, "iis");
66 if (IS_ERR(priv->pclk)) {
67 dev_err(rtd->dev, "%s cannot get pclk\n",
68 __func__);
69 clk_put(priv->xtal);
70 ret = PTR_ERR(priv->pclk);
71 }
72 }
73 if (!ret) {
74 int i, j;
75
76 for (i = 0; i < 2; i++) {
77 int fs = i ? 256 : 384;
78
79 rates[i*33] = clk_get_rate(priv->xtal) / fs;
80 for (j = 1; j < 33; j++)
81 rates[i*33 + j] = clk_get_rate(priv->pclk) /
82 (j * fs);
83 }
84 }
85 }
86 priv->clk_users += 1;
87 mutex_unlock(&priv->clk_lock);
88
89 if (!ret) {
90#ifdef ENFORCE_RATES
91 ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
92 SNDRV_PCM_HW_PARAM_RATE,
93 &hw_constraints_rates);
94 if (ret < 0)
95 dev_err(rtd->dev, "%s cannot set constraints\n",
96 __func__);
97#endif
98 }
99 return ret;
100}
101
102static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
103{
104 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
105 struct s3c24xx_uda134x *priv = snd_soc_card_get_drvdata(rtd->card);
106
107 mutex_lock(&priv->clk_lock);
108 priv->clk_users -= 1;
109 if (priv->clk_users == 0) {
110 clk_put(priv->xtal);
111 priv->xtal = NULL;
112 clk_put(priv->pclk);
113 priv->pclk = NULL;
114 }
115 mutex_unlock(&priv->clk_lock);
116}
117
118static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream,
119 struct snd_pcm_hw_params *params)
120{
121 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
122 struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
123 struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
124 unsigned int clk = 0;
125 int ret = 0;
126 int clk_source, fs_mode;
127 unsigned long rate = params_rate(params);
128 long err, cerr;
129 unsigned int div;
130 int i, bi;
131
132 err = 999999;
133 bi = 0;
134 for (i = 0; i < 2*33; i++) {
135 cerr = rates[i] - rate;
136 if (cerr < 0)
137 cerr = -cerr;
138 if (cerr < err) {
139 err = cerr;
140 bi = i;
141 }
142 }
143 if (bi / 33 == 1)
144 fs_mode = S3C2410_IISMOD_256FS;
145 else
146 fs_mode = S3C2410_IISMOD_384FS;
147 if (bi % 33 == 0) {
148 clk_source = S3C24XX_CLKSRC_MPLL;
149 div = 1;
150 } else {
151 clk_source = S3C24XX_CLKSRC_PCLK;
152 div = bi % 33;
153 }
154
155 dev_dbg(rtd->dev, "%s desired rate %lu, %d\n", __func__, rate, bi);
156
157 clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;
158
159 dev_dbg(rtd->dev, "%s will use: %s %s %d sysclk %d err %ld\n", __func__,
160 fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
161 clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
162 div, clk, err);
163
164 if ((err * 100 / rate) > 5) {
165 dev_err(rtd->dev, "effective frequency too different "
166 "from desired (%ld%%)\n", err * 100 / rate);
167 return -EINVAL;
168 }
169
170 ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
171 SND_SOC_CLOCK_IN);
172 if (ret < 0)
173 return ret;
174
175 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
176 if (ret < 0)
177 return ret;
178
179 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
180 S3C2410_IISMOD_32FS);
181 if (ret < 0)
182 return ret;
183
184 ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
185 S3C24XX_PRESCALE(div, div));
186 if (ret < 0)
187 return ret;
188
189
190 ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
191 SND_SOC_CLOCK_OUT);
192 if (ret < 0)
193 return ret;
194
195 return 0;
196}
197
198static const struct snd_soc_ops s3c24xx_uda134x_ops = {
199 .startup = s3c24xx_uda134x_startup,
200 .shutdown = s3c24xx_uda134x_shutdown,
201 .hw_params = s3c24xx_uda134x_hw_params,
202};
203
204SND_SOC_DAILINK_DEFS(uda134x,
205 DAILINK_COMP_ARRAY(COMP_CPU("s3c24xx-iis")),
206 DAILINK_COMP_ARRAY(COMP_CODEC("uda134x-codec", "uda134x-hifi")),
207 DAILINK_COMP_ARRAY(COMP_PLATFORM("s3c24xx-iis")));
208
209static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
210 .name = "UDA134X",
211 .stream_name = "UDA134X",
212 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
213 SND_SOC_DAIFMT_CBS_CFS,
214 .ops = &s3c24xx_uda134x_ops,
215 SND_SOC_DAILINK_REG(uda134x),
216};
217
218static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
219 .name = "S3C24XX_UDA134X",
220 .owner = THIS_MODULE,
221 .dai_link = &s3c24xx_uda134x_dai_link,
222 .num_links = 1,
223};
224
225static int s3c24xx_uda134x_probe(struct platform_device *pdev)
226{
227 struct snd_soc_card *card = &snd_soc_s3c24xx_uda134x;
228 struct s3c24xx_uda134x *priv;
229 int ret;
230
231 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
232 if (!priv)
233 return -ENOMEM;
234
235 mutex_init(&priv->clk_lock);
236
237 card->dev = &pdev->dev;
238 snd_soc_card_set_drvdata(card, priv);
239
240 ret = devm_snd_soc_register_card(&pdev->dev, card);
241 if (ret)
242 dev_err(&pdev->dev, "failed to register card: %d\n", ret);
243
244 return ret;
245}
246
247static struct platform_driver s3c24xx_uda134x_driver = {
248 .probe = s3c24xx_uda134x_probe,
249 .driver = {
250 .name = "s3c24xx_uda134x",
251 },
252};
253module_platform_driver(s3c24xx_uda134x_driver);
254
255MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
256MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
257MODULE_LICENSE("GPL");
258