1/*
2 * Modifications by Christian Pellegrin <chripell@evolware.org>
3 *
4 * s3c24xx_uda134x.c  --  S3C24XX_UDA134X ALSA SoC Audio board driver
5 *
6 * Copyright 2007 Dension Audio Systems Ltd.
7 * Author: Zoltan Devai
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 */
13
14#include <linux/clk.h>
15#include <linux/gpio.h>
16
17#include <sound/soc.h>
18#include <sound/s3c24xx_uda134x.h>
19
20#include <plat/regs-iis.h>
21
22#include "s3c24xx-i2s.h"
23
24/* #define ENFORCE_RATES 1 */
25/*
26  Unfortunately the S3C24XX in master mode has a limited capacity of
27  generating the clock for the codec. If you define this only rates
28  that are really available will be enforced. But be careful, most
29  user level application just want the usual sampling frequencies (8,
30  11.025, 22.050, 44.1 kHz) and anyway resampling is a costly
31  operation for embedded systems. So if you aren't very lucky or your
32  hardware engineer wasn't very forward-looking it's better to leave
33  this undefined. If you do so an approximate value for the requested
34  sampling rate in the range -/+ 5% will be chosen. If this in not
35  possible an error will be returned.
36*/
37
38static struct clk *xtal;
39static struct clk *pclk;
40/* this is need because we don't have a place where to keep the
41 * pointers to the clocks in each substream. We get the clocks only
42 * when we are actually using them so we don't block stuff like
43 * frequency change or oscillator power-off */
44static int clk_users;
45static DEFINE_MUTEX(clk_lock);
46
47static unsigned int rates[33 * 2];
48#ifdef ENFORCE_RATES
49static struct snd_pcm_hw_constraint_list hw_constraints_rates = {
50	.count	= ARRAY_SIZE(rates),
51	.list	= rates,
52	.mask	= 0,
53};
54#endif
55
56static struct platform_device *s3c24xx_uda134x_snd_device;
57
58static int s3c24xx_uda134x_startup(struct snd_pcm_substream *substream)
59{
60	int ret = 0;
61#ifdef ENFORCE_RATES
62	struct snd_pcm_runtime *runtime = substream->runtime;
63#endif
64
65	mutex_lock(&clk_lock);
66	pr_debug("%s %d\n", __func__, clk_users);
67	if (clk_users == 0) {
68		xtal = clk_get(&s3c24xx_uda134x_snd_device->dev, "xtal");
69		if (!xtal) {
70			printk(KERN_ERR "%s cannot get xtal\n", __func__);
71			ret = -EBUSY;
72		} else {
73			pclk = clk_get(&s3c24xx_uda134x_snd_device->dev,
74				       "pclk");
75			if (!pclk) {
76				printk(KERN_ERR "%s cannot get pclk\n",
77				       __func__);
78				clk_put(xtal);
79				ret = -EBUSY;
80			}
81		}
82		if (!ret) {
83			int i, j;
84
85			for (i = 0; i < 2; i++) {
86				int fs = i ? 256 : 384;
87
88				rates[i*33] = clk_get_rate(xtal) / fs;
89				for (j = 1; j < 33; j++)
90					rates[i*33 + j] = clk_get_rate(pclk) /
91						(j * fs);
92			}
93		}
94	}
95	clk_users += 1;
96	mutex_unlock(&clk_lock);
97	if (!ret) {
98#ifdef ENFORCE_RATES
99		ret = snd_pcm_hw_constraint_list(runtime, 0,
100						 SNDRV_PCM_HW_PARAM_RATE,
101						 &hw_constraints_rates);
102		if (ret < 0)
103			printk(KERN_ERR "%s cannot set constraints\n",
104			       __func__);
105#endif
106	}
107	return ret;
108}
109
110static void s3c24xx_uda134x_shutdown(struct snd_pcm_substream *substream)
111{
112	mutex_lock(&clk_lock);
113	pr_debug("%s %d\n", __func__, clk_users);
114	clk_users -= 1;
115	if (clk_users == 0) {
116		clk_put(xtal);
117		xtal = NULL;
118		clk_put(pclk);
119		pclk = NULL;
120	}
121	mutex_unlock(&clk_lock);
122}
123
124static int s3c24xx_uda134x_hw_params(struct snd_pcm_substream *substream,
125					struct snd_pcm_hw_params *params)
126{
127	struct snd_soc_pcm_runtime *rtd = substream->private_data;
128	struct snd_soc_dai *codec_dai = rtd->codec_dai;
129	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
130	unsigned int clk = 0;
131	int ret = 0;
132	int clk_source, fs_mode;
133	unsigned long rate = params_rate(params);
134	long err, cerr;
135	unsigned int div;
136	int i, bi;
137
138	err = 999999;
139	bi = 0;
140	for (i = 0; i < 2*33; i++) {
141		cerr = rates[i] - rate;
142		if (cerr < 0)
143			cerr = -cerr;
144		if (cerr < err) {
145			err = cerr;
146			bi = i;
147		}
148	}
149	if (bi / 33 == 1)
150		fs_mode = S3C2410_IISMOD_256FS;
151	else
152		fs_mode = S3C2410_IISMOD_384FS;
153	if (bi % 33 == 0) {
154		clk_source = S3C24XX_CLKSRC_MPLL;
155		div = 1;
156	} else {
157		clk_source = S3C24XX_CLKSRC_PCLK;
158		div = bi % 33;
159	}
160	pr_debug("%s desired rate %lu, %d\n", __func__, rate, bi);
161
162	clk = (fs_mode == S3C2410_IISMOD_384FS ? 384 : 256) * rate;
163	pr_debug("%s will use: %s %s %d sysclk %d err %ld\n", __func__,
164		 fs_mode == S3C2410_IISMOD_384FS ? "384FS" : "256FS",
165		 clk_source == S3C24XX_CLKSRC_MPLL ? "MPLLin" : "PCLK",
166		 div, clk, err);
167
168	if ((err * 100 / rate) > 5) {
169		printk(KERN_ERR "S3C24XX_UDA134X: effective frequency "
170		       "too different from desired (%ld%%)\n",
171		       err * 100 / rate);
172		return -EINVAL;
173	}
174
175	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
176			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
177	if (ret < 0)
178		return ret;
179
180	ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
181			SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
182	if (ret < 0)
183		return ret;
184
185	ret = snd_soc_dai_set_sysclk(cpu_dai, clk_source , clk,
186			SND_SOC_CLOCK_IN);
187	if (ret < 0)
188		return ret;
189
190	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, fs_mode);
191	if (ret < 0)
192		return ret;
193
194	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_BCLK,
195			S3C2410_IISMOD_32FS);
196	if (ret < 0)
197		return ret;
198
199	ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER,
200			S3C24XX_PRESCALE(div, div));
201	if (ret < 0)
202		return ret;
203
204	/* set the codec system clock for DAC and ADC */
205	ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk,
206			SND_SOC_CLOCK_OUT);
207	if (ret < 0)
208		return ret;
209
210	return 0;
211}
212
213static struct snd_soc_ops s3c24xx_uda134x_ops = {
214	.startup = s3c24xx_uda134x_startup,
215	.shutdown = s3c24xx_uda134x_shutdown,
216	.hw_params = s3c24xx_uda134x_hw_params,
217};
218
219static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {
220	.name = "UDA134X",
221	.stream_name = "UDA134X",
222	.codec_name = "uda134x-codec",
223	.codec_dai_name = "uda134x-hifi",
224	.cpu_dai_name = "s3c24xx-iis",
225	.ops = &s3c24xx_uda134x_ops,
226	.platform_name	= "samsung-audio",
227};
228
229static struct snd_soc_card snd_soc_s3c24xx_uda134x = {
230	.name = "S3C24XX_UDA134X",
231	.dai_link = &s3c24xx_uda134x_dai_link,
232	.num_links = 1,
233};
234
235static struct s3c24xx_uda134x_platform_data *s3c24xx_uda134x_l3_pins;
236
237static void setdat(int v)
238{
239	gpio_set_value(s3c24xx_uda134x_l3_pins->l3_data, v > 0);
240}
241
242static void setclk(int v)
243{
244	gpio_set_value(s3c24xx_uda134x_l3_pins->l3_clk, v > 0);
245}
246
247static void setmode(int v)
248{
249	gpio_set_value(s3c24xx_uda134x_l3_pins->l3_mode, v > 0);
250}
251
252/* FIXME - This must be codec platform data but in which board file ?? */
253static struct uda134x_platform_data s3c24xx_uda134x = {
254	.l3 = {
255		.setdat = setdat,
256		.setclk = setclk,
257		.setmode = setmode,
258		.data_hold = 1,
259		.data_setup = 1,
260		.clock_high = 1,
261		.mode_hold = 1,
262		.mode = 1,
263		.mode_setup = 1,
264	},
265};
266
267static int s3c24xx_uda134x_setup_pin(int pin, char *fun)
268{
269	if (gpio_request(pin, "s3c24xx_uda134x") < 0) {
270		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
271		       "l3 %s pin already in use", fun);
272		return -EBUSY;
273	}
274	gpio_direction_output(pin, 0);
275	return 0;
276}
277
278static int s3c24xx_uda134x_probe(struct platform_device *pdev)
279{
280	int ret;
281
282	printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n");
283
284	s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;
285	if (s3c24xx_uda134x_l3_pins == NULL) {
286		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
287		       "unable to find platform data\n");
288		return -ENODEV;
289	}
290	s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;
291	s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;
292
293	if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,
294				      "data") < 0)
295		return -EBUSY;
296	if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,
297				      "clk") < 0) {
298		gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
299		return -EBUSY;
300	}
301	if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,
302				      "mode") < 0) {
303		gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
304		gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
305		return -EBUSY;
306	}
307
308	s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);
309	if (!s3c24xx_uda134x_snd_device) {
310		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "
311		       "Unable to register\n");
312		return -ENOMEM;
313	}
314
315	platform_set_drvdata(s3c24xx_uda134x_snd_device,
316			     &snd_soc_s3c24xx_uda134x);
317	platform_device_add_data(s3c24xx_uda134x_snd_device, &s3c24xx_uda134x, sizeof(s3c24xx_uda134x));
318	ret = platform_device_add(s3c24xx_uda134x_snd_device);
319	if (ret) {
320		printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n");
321		platform_device_put(s3c24xx_uda134x_snd_device);
322	}
323
324	return ret;
325}
326
327static int s3c24xx_uda134x_remove(struct platform_device *pdev)
328{
329	platform_device_unregister(s3c24xx_uda134x_snd_device);
330	gpio_free(s3c24xx_uda134x_l3_pins->l3_data);
331	gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);
332	gpio_free(s3c24xx_uda134x_l3_pins->l3_mode);
333	return 0;
334}
335
336static struct platform_driver s3c24xx_uda134x_driver = {
337	.probe  = s3c24xx_uda134x_probe,
338	.remove = s3c24xx_uda134x_remove,
339	.driver = {
340		.name = "s3c24xx_uda134x",
341		.owner = THIS_MODULE,
342	},
343};
344
345static int __init s3c24xx_uda134x_init(void)
346{
347	return platform_driver_register(&s3c24xx_uda134x_driver);
348}
349
350static void __exit s3c24xx_uda134x_exit(void)
351{
352	platform_driver_unregister(&s3c24xx_uda134x_driver);
353}
354
355
356module_init(s3c24xx_uda134x_init);
357module_exit(s3c24xx_uda134x_exit);
358
359MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
360MODULE_DESCRIPTION("S3C24XX_UDA134X ALSA SoC audio driver");
361MODULE_LICENSE("GPL");
362