• 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/blackfin/
1/*
2 * File:         sound/soc/blackfin/bf5xx-tdm.c
3 * Author:       Barry Song <Barry.Song@analog.com>
4 *
5 * Created:      Thurs June 04 2009
6 * Description:  Blackfin I2S(TDM) CPU DAI driver
7 *              Even though TDM mode can be as part of I2S DAI, but there
8 *              are so much difference in configuration and data flow,
9 *              it's very ugly to integrate I2S and TDM into a module
10 *
11 * Modified:
12 *               Copyright 2009 Analog Devices Inc.
13 *
14 * Bugs:         Enter bugs at http://blackfin.uclinux.org/
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, see the file COPYING, or write
28 * to the Free Software Foundation, Inc.,
29 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
30 */
31
32#include <linux/init.h>
33#include <linux/module.h>
34#include <linux/device.h>
35#include <sound/core.h>
36#include <sound/pcm.h>
37#include <sound/pcm_params.h>
38#include <sound/initval.h>
39#include <sound/soc.h>
40
41#include <asm/irq.h>
42#include <asm/portmux.h>
43#include <linux/mutex.h>
44#include <linux/gpio.h>
45
46#include "bf5xx-sport.h"
47#include "bf5xx-tdm.h"
48
49static struct bf5xx_tdm_port bf5xx_tdm;
50static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
51
52static struct sport_param sport_params[2] = {
53	{
54		.dma_rx_chan    = CH_SPORT0_RX,
55		.dma_tx_chan    = CH_SPORT0_TX,
56		.err_irq        = IRQ_SPORT0_ERROR,
57		.regs           = (struct sport_register *)SPORT0_TCR1,
58	},
59	{
60		.dma_rx_chan    = CH_SPORT1_RX,
61		.dma_tx_chan    = CH_SPORT1_TX,
62		.err_irq        = IRQ_SPORT1_ERROR,
63		.regs           = (struct sport_register *)SPORT1_TCR1,
64	}
65};
66
67/*
68 * Setting the TFS pin selector for SPORT 0 based on whether the selected
69 * port id F or G. If the port is F then no conflict should exist for the
70 * TFS. When Port G is selected and EMAC then there is a conflict between
71 * the PHY interrupt line and TFS.  Current settings prevent the conflict
72 * by ignoring the TFS pin when Port G is selected. This allows both
73 * codecs and EMAC using Port G concurrently.
74 */
75#ifdef CONFIG_BF527_SPORT0_PORTG
76#define LOCAL_SPORT0_TFS (0)
77#else
78#define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
79#endif
80
81static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
82	P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0},
83	   {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI,
84		   P_SPORT1_RSCLK, P_SPORT1_TFS, 0} };
85
86static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai,
87	unsigned int fmt)
88{
89	int ret = 0;
90
91	/* interface format:support TDM,slave mode */
92	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
93	case SND_SOC_DAIFMT_DSP_A:
94		break;
95	default:
96		printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
97		ret = -EINVAL;
98		break;
99	}
100
101	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
102	case SND_SOC_DAIFMT_CBM_CFM:
103		break;
104	case SND_SOC_DAIFMT_CBS_CFS:
105	case SND_SOC_DAIFMT_CBM_CFS:
106	case SND_SOC_DAIFMT_CBS_CFM:
107		ret = -EINVAL;
108		break;
109	default:
110		printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
111		ret = -EINVAL;
112		break;
113	}
114
115	return ret;
116}
117
118static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream,
119	struct snd_pcm_hw_params *params,
120	struct snd_soc_dai *dai)
121{
122	int ret = 0;
123
124	bf5xx_tdm.tcr2 &= ~0x1f;
125	bf5xx_tdm.rcr2 &= ~0x1f;
126	switch (params_format(params)) {
127	case SNDRV_PCM_FORMAT_S32_LE:
128		bf5xx_tdm.tcr2 |= 31;
129		bf5xx_tdm.rcr2 |= 31;
130		sport_handle->wdsize = 4;
131		break;
132		/* at present, we only support 32bit transfer */
133	default:
134		pr_err("not supported PCM format yet\n");
135		return -EINVAL;
136		break;
137	}
138
139	if (!bf5xx_tdm.configured) {
140		/*
141		 * TX and RX are not independent,they are enabled at the
142		 * same time, even if only one side is running. So, we
143		 * need to configure both of them at the time when the first
144		 * stream is opened.
145		 *
146		 * CPU DAI:slave mode.
147		 */
148		ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1,
149			bf5xx_tdm.rcr2, 0, 0);
150		if (ret) {
151			pr_err("SPORT is busy!\n");
152			return -EBUSY;
153		}
154
155		ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1,
156			bf5xx_tdm.tcr2, 0, 0);
157		if (ret) {
158			pr_err("SPORT is busy!\n");
159			return -EBUSY;
160		}
161
162		bf5xx_tdm.configured = 1;
163	}
164
165	return 0;
166}
167
168static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream,
169	struct snd_soc_dai *dai)
170{
171	/* No active stream, SPORT is allowed to be configured again. */
172	if (!dai->active)
173		bf5xx_tdm.configured = 0;
174}
175
176static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai,
177		unsigned int tx_num, unsigned int *tx_slot,
178		unsigned int rx_num, unsigned int *rx_slot)
179{
180	int i;
181	unsigned int slot;
182	unsigned int tx_mapped = 0, rx_mapped = 0;
183
184	if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) ||
185			(rx_num > BFIN_TDM_DAI_MAX_SLOTS))
186		return -EINVAL;
187
188	for (i = 0; i < tx_num; i++) {
189		slot = tx_slot[i];
190		if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
191				(!(tx_mapped & (1 << slot)))) {
192			bf5xx_tdm.tx_map[i] = slot;
193			tx_mapped |= 1 << slot;
194		} else
195			return -EINVAL;
196	}
197	for (i = 0; i < rx_num; i++) {
198		slot = rx_slot[i];
199		if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
200				(!(rx_mapped & (1 << slot)))) {
201			bf5xx_tdm.rx_map[i] = slot;
202			rx_mapped |= 1 << slot;
203		} else
204			return -EINVAL;
205	}
206
207	return 0;
208}
209
210#ifdef CONFIG_PM
211static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
212{
213	struct sport_device *sport = snd_soc_dai_get_drvdata(dai);
214
215	if (!dai->active)
216		return 0;
217	if (dai->capture.active)
218		sport_rx_stop(sport);
219	if (dai->playback.active)
220		sport_tx_stop(sport);
221	return 0;
222}
223
224static int bf5xx_tdm_resume(struct snd_soc_dai *dai)
225{
226	int ret;
227	struct sport_device *sport = dai->private_data;
228
229	if (!dai->active)
230		return 0;
231
232	ret = sport_set_multichannel(sport, 8, 0xFF, 1);
233	if (ret) {
234		pr_err("SPORT is busy!\n");
235		ret = -EBUSY;
236	}
237
238	ret = sport_config_rx(sport, IRFS, 0x1F, 0, 0);
239	if (ret) {
240		pr_err("SPORT is busy!\n");
241		ret = -EBUSY;
242	}
243
244	ret = sport_config_tx(sport, ITFS, 0x1F, 0, 0);
245	if (ret) {
246		pr_err("SPORT is busy!\n");
247		ret = -EBUSY;
248	}
249
250	return 0;
251}
252
253#else
254#define bf5xx_tdm_suspend      NULL
255#define bf5xx_tdm_resume       NULL
256#endif
257
258static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = {
259	.hw_params      = bf5xx_tdm_hw_params,
260	.set_fmt        = bf5xx_tdm_set_dai_fmt,
261	.shutdown       = bf5xx_tdm_shutdown,
262	.set_channel_map   = bf5xx_tdm_set_channel_map,
263};
264
265struct snd_soc_dai bf5xx_tdm_dai = {
266	.name = "bf5xx-tdm",
267	.id = 0,
268	.suspend = bf5xx_tdm_suspend,
269	.resume = bf5xx_tdm_resume,
270	.playback = {
271		.channels_min = 2,
272		.channels_max = 8,
273		.rates = SNDRV_PCM_RATE_48000,
274		.formats = SNDRV_PCM_FMTBIT_S32_LE,},
275	.capture = {
276		.channels_min = 2,
277		.channels_max = 8,
278		.rates = SNDRV_PCM_RATE_48000,
279		.formats = SNDRV_PCM_FMTBIT_S32_LE,},
280	.ops = &bf5xx_tdm_dai_ops,
281};
282EXPORT_SYMBOL_GPL(bf5xx_tdm_dai);
283
284static int __devinit bfin_tdm_probe(struct platform_device *pdev)
285{
286	int ret = 0;
287
288	if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
289		pr_err("Requesting Peripherals failed\n");
290		return -EFAULT;
291	}
292
293	/* request DMA for SPORT */
294	sport_handle = sport_init(&sport_params[sport_num], 4, \
295		8 * sizeof(u32), NULL);
296	if (!sport_handle) {
297		peripheral_free_list(&sport_req[sport_num][0]);
298		return -ENODEV;
299	}
300
301	/* SPORT works in TDM mode */
302	ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1);
303	if (ret) {
304		pr_err("SPORT is busy!\n");
305		ret = -EBUSY;
306		goto sport_config_err;
307	}
308
309	ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0);
310	if (ret) {
311		pr_err("SPORT is busy!\n");
312		ret = -EBUSY;
313		goto sport_config_err;
314	}
315
316	ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0);
317	if (ret) {
318		pr_err("SPORT is busy!\n");
319		ret = -EBUSY;
320		goto sport_config_err;
321	}
322
323	ret = snd_soc_register_dai(&bf5xx_tdm_dai);
324	if (ret) {
325		pr_err("Failed to register DAI: %d\n", ret);
326		goto sport_config_err;
327	}
328
329	sport_handle->private_data = &bf5xx_tdm;
330	return 0;
331
332sport_config_err:
333	peripheral_free_list(&sport_req[sport_num][0]);
334	return ret;
335}
336
337static int __devexit bfin_tdm_remove(struct platform_device *pdev)
338{
339	peripheral_free_list(&sport_req[sport_num][0]);
340	snd_soc_unregister_dai(&bf5xx_tdm_dai);
341
342	return 0;
343}
344
345static struct platform_driver bfin_tdm_driver = {
346	.probe  = bfin_tdm_probe,
347	.remove = __devexit_p(bfin_tdm_remove),
348	.driver = {
349		.name   = "bfin-tdm",
350		.owner  = THIS_MODULE,
351	},
352};
353
354static int __init bfin_tdm_init(void)
355{
356	return platform_driver_register(&bfin_tdm_driver);
357}
358module_init(bfin_tdm_init);
359
360static void __exit bfin_tdm_exit(void)
361{
362	platform_driver_unregister(&bfin_tdm_driver);
363}
364module_exit(bfin_tdm_exit);
365
366/* Module information */
367MODULE_AUTHOR("Barry Song");
368MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
369MODULE_LICENSE("GPL");
370