2 * File: sound/soc/blackfin/bf5xx-tdm.c
3 * Author: Barry Song <Barry.Song@analog.com>
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
12 * Copyright 2009 Analog Devices Inc.
14 * Bugs: Enter bugs at http://blackfin.uclinux.org/
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.
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.
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
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>
42 #include <asm/portmux.h>
43 #include <linux/mutex.h>
44 #include <linux/gpio.h>
46 #include "bf5xx-sport.h"
47 #include "bf5xx-tdm.h"
49 static struct bf5xx_tdm_port bf5xx_tdm
;
50 static int sport_num
= CONFIG_SND_BF5XX_SPORT_NUM
;
52 static struct sport_param sport_params
[2] = {
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
,
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
,
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.
75 #ifdef CONFIG_BF527_SPORT0_PORTG
76 #define LOCAL_SPORT0_TFS (0)
78 #define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
81 static 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} };
86 static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai
*cpu_dai
,
91 /* interface format:support TDM,slave mode */
92 switch (fmt
& SND_SOC_DAIFMT_FORMAT_MASK
) {
93 case SND_SOC_DAIFMT_DSP_A
:
96 printk(KERN_ERR
"%s: Unknown DAI format type\n", __func__
);
101 switch (fmt
& SND_SOC_DAIFMT_MASTER_MASK
) {
102 case SND_SOC_DAIFMT_CBM_CFM
:
104 case SND_SOC_DAIFMT_CBS_CFS
:
105 case SND_SOC_DAIFMT_CBM_CFS
:
106 case SND_SOC_DAIFMT_CBS_CFM
:
110 printk(KERN_ERR
"%s: Unknown DAI master type\n", __func__
);
118 static int bf5xx_tdm_hw_params(struct snd_pcm_substream
*substream
,
119 struct snd_pcm_hw_params
*params
,
120 struct snd_soc_dai
*dai
)
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;
132 /* at present, we only support 32bit transfer */
134 pr_err("not supported PCM format yet\n");
139 if (!bf5xx_tdm
.configured
) {
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
146 * CPU DAI:slave mode.
148 ret
= sport_config_rx(sport_handle
, bf5xx_tdm
.rcr1
,
149 bf5xx_tdm
.rcr2
, 0, 0);
151 pr_err("SPORT is busy!\n");
155 ret
= sport_config_tx(sport_handle
, bf5xx_tdm
.tcr1
,
156 bf5xx_tdm
.tcr2
, 0, 0);
158 pr_err("SPORT is busy!\n");
162 bf5xx_tdm
.configured
= 1;
168 static void bf5xx_tdm_shutdown(struct snd_pcm_substream
*substream
,
169 struct snd_soc_dai
*dai
)
171 /* No active stream, SPORT is allowed to be configured again. */
173 bf5xx_tdm
.configured
= 0;
176 static 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
)
182 unsigned int tx_mapped
= 0, rx_mapped
= 0;
184 if ((tx_num
> BFIN_TDM_DAI_MAX_SLOTS
) ||
185 (rx_num
> BFIN_TDM_DAI_MAX_SLOTS
))
188 for (i
= 0; i
< tx_num
; 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
;
197 for (i
= 0; i
< rx_num
; 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
;
211 static int bf5xx_tdm_suspend(struct snd_soc_dai
*dai
)
213 struct sport_device
*sport
=
214 (struct sport_device
*)dai
->private_data
;
218 if (dai
->capture
.active
)
219 sport_rx_stop(sport
);
220 if (dai
->playback
.active
)
221 sport_tx_stop(sport
);
225 static int bf5xx_tdm_resume(struct snd_soc_dai
*dai
)
228 struct sport_device
*sport
=
229 (struct sport_device
*)dai
->private_data
;
234 ret
= sport_set_multichannel(sport
, 8, 0xFF, 1);
236 pr_err("SPORT is busy!\n");
240 ret
= sport_config_rx(sport
, IRFS
, 0x1F, 0, 0);
242 pr_err("SPORT is busy!\n");
246 ret
= sport_config_tx(sport
, ITFS
, 0x1F, 0, 0);
248 pr_err("SPORT is busy!\n");
256 #define bf5xx_tdm_suspend NULL
257 #define bf5xx_tdm_resume NULL
260 static struct snd_soc_dai_ops bf5xx_tdm_dai_ops
= {
261 .hw_params
= bf5xx_tdm_hw_params
,
262 .set_fmt
= bf5xx_tdm_set_dai_fmt
,
263 .shutdown
= bf5xx_tdm_shutdown
,
264 .set_channel_map
= bf5xx_tdm_set_channel_map
,
267 struct snd_soc_dai bf5xx_tdm_dai
= {
270 .suspend
= bf5xx_tdm_suspend
,
271 .resume
= bf5xx_tdm_resume
,
275 .rates
= SNDRV_PCM_RATE_48000
,
276 .formats
= SNDRV_PCM_FMTBIT_S32_LE
,},
280 .rates
= SNDRV_PCM_RATE_48000
,
281 .formats
= SNDRV_PCM_FMTBIT_S32_LE
,},
282 .ops
= &bf5xx_tdm_dai_ops
,
284 EXPORT_SYMBOL_GPL(bf5xx_tdm_dai
);
286 static int __devinit
bfin_tdm_probe(struct platform_device
*pdev
)
290 if (peripheral_request_list(&sport_req
[sport_num
][0], "soc-audio")) {
291 pr_err("Requesting Peripherals failed\n");
295 /* request DMA for SPORT */
296 sport_handle
= sport_init(&sport_params
[sport_num
], 4, \
297 8 * sizeof(u32
), NULL
);
299 peripheral_free_list(&sport_req
[sport_num
][0]);
303 /* SPORT works in TDM mode */
304 ret
= sport_set_multichannel(sport_handle
, 8, 0xFF, 1);
306 pr_err("SPORT is busy!\n");
308 goto sport_config_err
;
311 ret
= sport_config_rx(sport_handle
, IRFS
, 0x1F, 0, 0);
313 pr_err("SPORT is busy!\n");
315 goto sport_config_err
;
318 ret
= sport_config_tx(sport_handle
, ITFS
, 0x1F, 0, 0);
320 pr_err("SPORT is busy!\n");
322 goto sport_config_err
;
325 ret
= snd_soc_register_dai(&bf5xx_tdm_dai
);
327 pr_err("Failed to register DAI: %d\n", ret
);
328 goto sport_config_err
;
331 sport_handle
->private_data
= &bf5xx_tdm
;
335 peripheral_free_list(&sport_req
[sport_num
][0]);
339 static int __devexit
bfin_tdm_remove(struct platform_device
*pdev
)
341 peripheral_free_list(&sport_req
[sport_num
][0]);
342 snd_soc_unregister_dai(&bf5xx_tdm_dai
);
347 static struct platform_driver bfin_tdm_driver
= {
348 .probe
= bfin_tdm_probe
,
349 .remove
= __devexit_p(bfin_tdm_remove
),
352 .owner
= THIS_MODULE
,
356 static int __init
bfin_tdm_init(void)
358 return platform_driver_register(&bfin_tdm_driver
);
360 module_init(bfin_tdm_init
);
362 static void __exit
bfin_tdm_exit(void)
364 platform_driver_unregister(&bfin_tdm_driver
);
366 module_exit(bfin_tdm_exit
);
368 /* Module information */
369 MODULE_AUTHOR("Barry Song");
370 MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
371 MODULE_LICENSE("GPL");