• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-R7000-V1.0.7.12_1.2.5/components/opensource/linux/linux-2.6.36/sound/soc/s3c24xx/
1/* sound/soc/s3c24xx/s3c-pcm.c
2 *
3 * ALSA SoC Audio Layer - S3C PCM-Controller driver
4 *
5 * Copyright (c) 2009 Samsung Electronics Co. Ltd
6 * Author: Jaswinder Singh <jassi.brar@samsung.com>
7 * based upon I2S drivers by Ben Dooks.
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/init.h>
15#include <linux/module.h>
16#include <linux/device.h>
17#include <linux/delay.h>
18#include <linux/clk.h>
19#include <linux/kernel.h>
20#include <linux/gpio.h>
21#include <linux/io.h>
22
23#include <sound/core.h>
24#include <sound/pcm.h>
25#include <sound/pcm_params.h>
26#include <sound/initval.h>
27#include <sound/soc.h>
28
29#include <plat/audio.h>
30#include <plat/dma.h>
31
32#include "s3c-dma.h"
33#include "s3c-pcm.h"
34
35static struct s3c2410_dma_client s3c_pcm_dma_client_out = {
36	.name		= "PCM Stereo out"
37};
38
39static struct s3c2410_dma_client s3c_pcm_dma_client_in = {
40	.name		= "PCM Stereo in"
41};
42
43static struct s3c_dma_params s3c_pcm_stereo_out[] = {
44	[0] = {
45		.client		= &s3c_pcm_dma_client_out,
46		.dma_size	= 4,
47	},
48	[1] = {
49		.client		= &s3c_pcm_dma_client_out,
50		.dma_size	= 4,
51	},
52};
53
54static struct s3c_dma_params s3c_pcm_stereo_in[] = {
55	[0] = {
56		.client		= &s3c_pcm_dma_client_in,
57		.dma_size	= 4,
58	},
59	[1] = {
60		.client		= &s3c_pcm_dma_client_in,
61		.dma_size	= 4,
62	},
63};
64
65static struct s3c_pcm_info s3c_pcm[2];
66
67static inline struct s3c_pcm_info *to_info(struct snd_soc_dai *cpu_dai)
68{
69	return cpu_dai->private_data;
70}
71
72static void s3c_pcm_snd_txctrl(struct s3c_pcm_info *pcm, int on)
73{
74	void __iomem *regs = pcm->regs;
75	u32 ctl, clkctl;
76
77	clkctl = readl(regs + S3C_PCM_CLKCTL);
78	ctl = readl(regs + S3C_PCM_CTL);
79	ctl &= ~(S3C_PCM_CTL_TXDIPSTICK_MASK
80			 << S3C_PCM_CTL_TXDIPSTICK_SHIFT);
81
82	if (on) {
83		ctl |= S3C_PCM_CTL_TXDMA_EN;
84		ctl |= S3C_PCM_CTL_TXFIFO_EN;
85		ctl |= S3C_PCM_CTL_ENABLE;
86		ctl |= (0x20<<S3C_PCM_CTL_TXDIPSTICK_SHIFT);
87		clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
88	} else {
89		ctl &= ~S3C_PCM_CTL_TXDMA_EN;
90		ctl &= ~S3C_PCM_CTL_TXFIFO_EN;
91
92		if (!(ctl & S3C_PCM_CTL_RXFIFO_EN)) {
93			ctl &= ~S3C_PCM_CTL_ENABLE;
94			if (!pcm->idleclk)
95				clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
96		}
97	}
98
99	writel(clkctl, regs + S3C_PCM_CLKCTL);
100	writel(ctl, regs + S3C_PCM_CTL);
101}
102
103static void s3c_pcm_snd_rxctrl(struct s3c_pcm_info *pcm, int on)
104{
105	void __iomem *regs = pcm->regs;
106	u32 ctl, clkctl;
107
108	ctl = readl(regs + S3C_PCM_CTL);
109	clkctl = readl(regs + S3C_PCM_CLKCTL);
110
111	if (on) {
112		ctl |= S3C_PCM_CTL_RXDMA_EN;
113		ctl |= S3C_PCM_CTL_RXFIFO_EN;
114		ctl |= S3C_PCM_CTL_ENABLE;
115		clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
116	} else {
117		ctl &= ~S3C_PCM_CTL_RXDMA_EN;
118		ctl &= ~S3C_PCM_CTL_RXFIFO_EN;
119
120		if (!(ctl & S3C_PCM_CTL_TXFIFO_EN)) {
121			ctl &= ~S3C_PCM_CTL_ENABLE;
122			if (!pcm->idleclk)
123				clkctl |= S3C_PCM_CLKCTL_SERCLK_EN;
124		}
125	}
126
127	writel(clkctl, regs + S3C_PCM_CLKCTL);
128	writel(ctl, regs + S3C_PCM_CTL);
129}
130
131static int s3c_pcm_trigger(struct snd_pcm_substream *substream, int cmd,
132			       struct snd_soc_dai *dai)
133{
134	struct snd_soc_pcm_runtime *rtd = substream->private_data;
135	struct s3c_pcm_info *pcm = to_info(rtd->dai->cpu_dai);
136	unsigned long flags;
137
138	dev_dbg(pcm->dev, "Entered %s\n", __func__);
139
140	switch (cmd) {
141	case SNDRV_PCM_TRIGGER_START:
142	case SNDRV_PCM_TRIGGER_RESUME:
143	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
144		spin_lock_irqsave(&pcm->lock, flags);
145
146		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
147			s3c_pcm_snd_rxctrl(pcm, 1);
148		else
149			s3c_pcm_snd_txctrl(pcm, 1);
150
151		spin_unlock_irqrestore(&pcm->lock, flags);
152		break;
153
154	case SNDRV_PCM_TRIGGER_STOP:
155	case SNDRV_PCM_TRIGGER_SUSPEND:
156	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
157		spin_lock_irqsave(&pcm->lock, flags);
158
159		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
160			s3c_pcm_snd_rxctrl(pcm, 0);
161		else
162			s3c_pcm_snd_txctrl(pcm, 0);
163
164		spin_unlock_irqrestore(&pcm->lock, flags);
165		break;
166
167	default:
168		return -EINVAL;
169	}
170
171	return 0;
172}
173
174static int s3c_pcm_hw_params(struct snd_pcm_substream *substream,
175				 struct snd_pcm_hw_params *params,
176				 struct snd_soc_dai *socdai)
177{
178	struct snd_soc_pcm_runtime *rtd = substream->private_data;
179	struct snd_soc_dai_link *dai = rtd->dai;
180	struct s3c_pcm_info *pcm = to_info(dai->cpu_dai);
181	struct s3c_dma_params *dma_data;
182	void __iomem *regs = pcm->regs;
183	struct clk *clk;
184	int sclk_div, sync_div;
185	unsigned long flags;
186	u32 clkctl;
187
188	dev_dbg(pcm->dev, "Entered %s\n", __func__);
189
190	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
191		dma_data = pcm->dma_playback;
192	else
193		dma_data = pcm->dma_capture;
194
195	snd_soc_dai_set_dma_data(dai->cpu_dai, substream, dma_data);
196
197	/* Strictly check for sample size */
198	switch (params_format(params)) {
199	case SNDRV_PCM_FORMAT_S16_LE:
200		break;
201	default:
202		return -EINVAL;
203	}
204
205	spin_lock_irqsave(&pcm->lock, flags);
206
207	/* Get hold of the PCMSOURCE_CLK */
208	clkctl = readl(regs + S3C_PCM_CLKCTL);
209	if (clkctl & S3C_PCM_CLKCTL_SERCLKSEL_PCLK)
210		clk = pcm->pclk;
211	else
212		clk = pcm->cclk;
213
214	/* Set the SCLK divider */
215	sclk_div = clk_get_rate(clk) / pcm->sclk_per_fs /
216					params_rate(params) / 2 - 1;
217
218	clkctl &= ~(S3C_PCM_CLKCTL_SCLKDIV_MASK
219			<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
220	clkctl |= ((sclk_div & S3C_PCM_CLKCTL_SCLKDIV_MASK)
221			<< S3C_PCM_CLKCTL_SCLKDIV_SHIFT);
222
223	/* Set the SYNC divider */
224	sync_div = pcm->sclk_per_fs - 1;
225
226	clkctl &= ~(S3C_PCM_CLKCTL_SYNCDIV_MASK
227				<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
228	clkctl |= ((sync_div & S3C_PCM_CLKCTL_SYNCDIV_MASK)
229				<< S3C_PCM_CLKCTL_SYNCDIV_SHIFT);
230
231	writel(clkctl, regs + S3C_PCM_CLKCTL);
232
233	spin_unlock_irqrestore(&pcm->lock, flags);
234
235	dev_dbg(pcm->dev, "PCMSOURCE_CLK-%lu SCLK=%ufs SCLK_DIV=%d SYNC_DIV=%d\n",
236				clk_get_rate(clk), pcm->sclk_per_fs,
237				sclk_div, sync_div);
238
239	return 0;
240}
241
242static int s3c_pcm_set_fmt(struct snd_soc_dai *cpu_dai,
243			       unsigned int fmt)
244{
245	struct s3c_pcm_info *pcm = to_info(cpu_dai);
246	void __iomem *regs = pcm->regs;
247	unsigned long flags;
248	int ret = 0;
249	u32 ctl;
250
251	dev_dbg(pcm->dev, "Entered %s\n", __func__);
252
253	spin_lock_irqsave(&pcm->lock, flags);
254
255	ctl = readl(regs + S3C_PCM_CTL);
256
257	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
258	case SND_SOC_DAIFMT_NB_NF:
259		/* Nothing to do, NB_NF by default */
260		break;
261	default:
262		dev_err(pcm->dev, "Unsupported clock inversion!\n");
263		ret = -EINVAL;
264		goto exit;
265	}
266
267	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
268	case SND_SOC_DAIFMT_CBS_CFS:
269		/* Nothing to do, Master by default */
270		break;
271	default:
272		dev_err(pcm->dev, "Unsupported master/slave format!\n");
273		ret = -EINVAL;
274		goto exit;
275	}
276
277	switch (fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
278	case SND_SOC_DAIFMT_CONT:
279		pcm->idleclk = 1;
280		break;
281	case SND_SOC_DAIFMT_GATED:
282		pcm->idleclk = 0;
283		break;
284	default:
285		dev_err(pcm->dev, "Invalid Clock gating request!\n");
286		ret = -EINVAL;
287		goto exit;
288	}
289
290	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
291	case SND_SOC_DAIFMT_DSP_A:
292		ctl |= S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
293		ctl |= S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
294		break;
295	case SND_SOC_DAIFMT_DSP_B:
296		ctl &= ~S3C_PCM_CTL_TXMSB_AFTER_FSYNC;
297		ctl &= ~S3C_PCM_CTL_RXMSB_AFTER_FSYNC;
298		break;
299	default:
300		dev_err(pcm->dev, "Unsupported data format!\n");
301		ret = -EINVAL;
302		goto exit;
303	}
304
305	writel(ctl, regs + S3C_PCM_CTL);
306
307exit:
308	spin_unlock_irqrestore(&pcm->lock, flags);
309
310	return ret;
311}
312
313static int s3c_pcm_set_clkdiv(struct snd_soc_dai *cpu_dai,
314						int div_id, int div)
315{
316	struct s3c_pcm_info *pcm = to_info(cpu_dai);
317
318	switch (div_id) {
319	case S3C_PCM_SCLK_PER_FS:
320		pcm->sclk_per_fs = div;
321		break;
322
323	default:
324		return -EINVAL;
325	}
326
327	return 0;
328}
329
330static int s3c_pcm_set_sysclk(struct snd_soc_dai *cpu_dai,
331				  int clk_id, unsigned int freq, int dir)
332{
333	struct s3c_pcm_info *pcm = to_info(cpu_dai);
334	void __iomem *regs = pcm->regs;
335	u32 clkctl = readl(regs + S3C_PCM_CLKCTL);
336
337	switch (clk_id) {
338	case S3C_PCM_CLKSRC_PCLK:
339		clkctl |= S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
340		break;
341
342	case S3C_PCM_CLKSRC_MUX:
343		clkctl &= ~S3C_PCM_CLKCTL_SERCLKSEL_PCLK;
344
345		if (clk_get_rate(pcm->cclk) != freq)
346			clk_set_rate(pcm->cclk, freq);
347
348		break;
349
350	default:
351		return -EINVAL;
352	}
353
354	writel(clkctl, regs + S3C_PCM_CLKCTL);
355
356	return 0;
357}
358
359static struct snd_soc_dai_ops s3c_pcm_dai_ops = {
360	.set_sysclk	= s3c_pcm_set_sysclk,
361	.set_clkdiv	= s3c_pcm_set_clkdiv,
362	.trigger	= s3c_pcm_trigger,
363	.hw_params	= s3c_pcm_hw_params,
364	.set_fmt	= s3c_pcm_set_fmt,
365};
366
367#define S3C_PCM_RATES  SNDRV_PCM_RATE_8000_96000
368
369#define S3C_PCM_DECLARE(n)			\
370{								\
371	.name		 = "samsung-pcm",			\
372	.id		 = (n),				\
373	.symmetric_rates = 1,					\
374	.ops = &s3c_pcm_dai_ops,				\
375	.playback = {						\
376		.channels_min	= 2,				\
377		.channels_max	= 2,				\
378		.rates		= S3C_PCM_RATES,		\
379		.formats	= SNDRV_PCM_FMTBIT_S16_LE,	\
380	},							\
381	.capture = {						\
382		.channels_min	= 2,				\
383		.channels_max	= 2,				\
384		.rates		= S3C_PCM_RATES,		\
385		.formats	= SNDRV_PCM_FMTBIT_S16_LE,	\
386	},							\
387}
388
389struct snd_soc_dai s3c_pcm_dai[] = {
390	S3C_PCM_DECLARE(0),
391	S3C_PCM_DECLARE(1),
392};
393EXPORT_SYMBOL_GPL(s3c_pcm_dai);
394
395static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev)
396{
397	struct s3c_pcm_info *pcm;
398	struct snd_soc_dai *dai;
399	struct resource *mem_res, *dmatx_res, *dmarx_res;
400	struct s3c_audio_pdata *pcm_pdata;
401	int ret;
402
403	/* Check for valid device index */
404	if ((pdev->id < 0) || pdev->id >= ARRAY_SIZE(s3c_pcm)) {
405		dev_err(&pdev->dev, "id %d out of range\n", pdev->id);
406		return -EINVAL;
407	}
408
409	pcm_pdata = pdev->dev.platform_data;
410
411	/* Check for availability of necessary resource */
412	dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
413	if (!dmatx_res) {
414		dev_err(&pdev->dev, "Unable to get PCM-TX dma resource\n");
415		return -ENXIO;
416	}
417
418	dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
419	if (!dmarx_res) {
420		dev_err(&pdev->dev, "Unable to get PCM-RX dma resource\n");
421		return -ENXIO;
422	}
423
424	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
425	if (!mem_res) {
426		dev_err(&pdev->dev, "Unable to get register resource\n");
427		return -ENXIO;
428	}
429
430	if (pcm_pdata && pcm_pdata->cfg_gpio && pcm_pdata->cfg_gpio(pdev)) {
431		dev_err(&pdev->dev, "Unable to configure gpio\n");
432		return -EINVAL;
433	}
434
435	pcm = &s3c_pcm[pdev->id];
436	pcm->dev = &pdev->dev;
437
438	spin_lock_init(&pcm->lock);
439
440	dai = &s3c_pcm_dai[pdev->id];
441	dai->dev = &pdev->dev;
442
443	/* Default is 128fs */
444	pcm->sclk_per_fs = 128;
445
446	pcm->cclk = clk_get(&pdev->dev, "audio-bus");
447	if (IS_ERR(pcm->cclk)) {
448		dev_err(&pdev->dev, "failed to get audio-bus\n");
449		ret = PTR_ERR(pcm->cclk);
450		goto err1;
451	}
452	clk_enable(pcm->cclk);
453
454	/* record our pcm structure for later use in the callbacks */
455	dai->private_data = pcm;
456
457	if (!request_mem_region(mem_res->start,
458				resource_size(mem_res), "samsung-pcm")) {
459		dev_err(&pdev->dev, "Unable to request register region\n");
460		ret = -EBUSY;
461		goto err2;
462	}
463
464	pcm->regs = ioremap(mem_res->start, 0x100);
465	if (pcm->regs == NULL) {
466		dev_err(&pdev->dev, "cannot ioremap registers\n");
467		ret = -ENXIO;
468		goto err3;
469	}
470
471	pcm->pclk = clk_get(&pdev->dev, "pcm");
472	if (IS_ERR(pcm->pclk)) {
473		dev_err(&pdev->dev, "failed to get pcm_clock\n");
474		ret = -ENOENT;
475		goto err4;
476	}
477	clk_enable(pcm->pclk);
478
479	ret = snd_soc_register_dai(dai);
480	if (ret != 0) {
481		dev_err(&pdev->dev, "failed to get pcm_clock\n");
482		goto err5;
483	}
484
485	s3c_pcm_stereo_in[pdev->id].dma_addr = mem_res->start
486							+ S3C_PCM_RXFIFO;
487	s3c_pcm_stereo_out[pdev->id].dma_addr = mem_res->start
488							+ S3C_PCM_TXFIFO;
489
490	s3c_pcm_stereo_in[pdev->id].channel = dmarx_res->start;
491	s3c_pcm_stereo_out[pdev->id].channel = dmatx_res->start;
492
493	pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id];
494	pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id];
495
496	return 0;
497
498err5:
499	clk_disable(pcm->pclk);
500	clk_put(pcm->pclk);
501err4:
502	iounmap(pcm->regs);
503err3:
504	release_mem_region(mem_res->start, resource_size(mem_res));
505err2:
506	clk_disable(pcm->cclk);
507	clk_put(pcm->cclk);
508err1:
509	return ret;
510}
511
512static __devexit int s3c_pcm_dev_remove(struct platform_device *pdev)
513{
514	struct s3c_pcm_info *pcm = &s3c_pcm[pdev->id];
515	struct resource *mem_res;
516
517	iounmap(pcm->regs);
518
519	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
520	release_mem_region(mem_res->start, resource_size(mem_res));
521
522	clk_disable(pcm->cclk);
523	clk_disable(pcm->pclk);
524	clk_put(pcm->pclk);
525	clk_put(pcm->cclk);
526
527	return 0;
528}
529
530static struct platform_driver s3c_pcm_driver = {
531	.probe  = s3c_pcm_dev_probe,
532	.remove = s3c_pcm_dev_remove,
533	.driver = {
534		.name = "samsung-pcm",
535		.owner = THIS_MODULE,
536	},
537};
538
539static int __init s3c_pcm_init(void)
540{
541	return platform_driver_register(&s3c_pcm_driver);
542}
543module_init(s3c_pcm_init);
544
545static void __exit s3c_pcm_exit(void)
546{
547	platform_driver_unregister(&s3c_pcm_driver);
548}
549module_exit(s3c_pcm_exit);
550
551/* Module information */
552MODULE_AUTHOR("Jaswinder Singh, <jassi.brar@samsung.com>");
553MODULE_DESCRIPTION("S3C PCM Controller Driver");
554MODULE_LICENSE("GPL");
555