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 int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai
*cpu_dai
,
54 /* interface format:support TDM,slave mode */
55 switch (fmt
& SND_SOC_DAIFMT_FORMAT_MASK
) {
56 case SND_SOC_DAIFMT_DSP_A
:
59 printk(KERN_ERR
"%s: Unknown DAI format type\n", __func__
);
64 switch (fmt
& SND_SOC_DAIFMT_MASTER_MASK
) {
65 case SND_SOC_DAIFMT_CBM_CFM
:
67 case SND_SOC_DAIFMT_CBS_CFS
:
68 case SND_SOC_DAIFMT_CBM_CFS
:
69 case SND_SOC_DAIFMT_CBS_CFM
:
73 printk(KERN_ERR
"%s: Unknown DAI master type\n", __func__
);
81 static int bf5xx_tdm_hw_params(struct snd_pcm_substream
*substream
,
82 struct snd_pcm_hw_params
*params
,
83 struct snd_soc_dai
*dai
)
85 struct sport_device
*sport_handle
= snd_soc_dai_get_drvdata(dai
);
86 struct bf5xx_tdm_port
*bf5xx_tdm
= sport_handle
->private_data
;
89 bf5xx_tdm
->tcr2
&= ~0x1f;
90 bf5xx_tdm
->rcr2
&= ~0x1f;
91 switch (params_format(params
)) {
92 case SNDRV_PCM_FORMAT_S32_LE
:
93 bf5xx_tdm
->tcr2
|= 31;
94 bf5xx_tdm
->rcr2
|= 31;
95 sport_handle
->wdsize
= 4;
97 /* at present, we only support 32bit transfer */
99 pr_err("not supported PCM format yet\n");
104 if (!bf5xx_tdm
->configured
) {
106 * TX and RX are not independent,they are enabled at the
107 * same time, even if only one side is running. So, we
108 * need to configure both of them at the time when the first
111 * CPU DAI:slave mode.
113 ret
= sport_config_rx(sport_handle
, bf5xx_tdm
->rcr1
,
114 bf5xx_tdm
->rcr2
, 0, 0);
116 pr_err("SPORT is busy!\n");
120 ret
= sport_config_tx(sport_handle
, bf5xx_tdm
->tcr1
,
121 bf5xx_tdm
->tcr2
, 0, 0);
123 pr_err("SPORT is busy!\n");
127 bf5xx_tdm
->configured
= 1;
133 static void bf5xx_tdm_shutdown(struct snd_pcm_substream
*substream
,
134 struct snd_soc_dai
*dai
)
136 struct sport_device
*sport_handle
= snd_soc_dai_get_drvdata(dai
);
137 struct bf5xx_tdm_port
*bf5xx_tdm
= sport_handle
->private_data
;
139 /* No active stream, SPORT is allowed to be configured again. */
141 bf5xx_tdm
->configured
= 0;
144 static int bf5xx_tdm_set_channel_map(struct snd_soc_dai
*dai
,
145 unsigned int tx_num
, unsigned int *tx_slot
,
146 unsigned int rx_num
, unsigned int *rx_slot
)
148 struct sport_device
*sport_handle
= snd_soc_dai_get_drvdata(dai
);
149 struct bf5xx_tdm_port
*bf5xx_tdm
= sport_handle
->private_data
;
152 unsigned int tx_mapped
= 0, rx_mapped
= 0;
154 if ((tx_num
> BFIN_TDM_DAI_MAX_SLOTS
) ||
155 (rx_num
> BFIN_TDM_DAI_MAX_SLOTS
))
158 for (i
= 0; i
< tx_num
; i
++) {
160 if ((slot
< BFIN_TDM_DAI_MAX_SLOTS
) &&
161 (!(tx_mapped
& (1 << slot
)))) {
162 bf5xx_tdm
->tx_map
[i
] = slot
;
163 tx_mapped
|= 1 << slot
;
167 for (i
= 0; i
< rx_num
; i
++) {
169 if ((slot
< BFIN_TDM_DAI_MAX_SLOTS
) &&
170 (!(rx_mapped
& (1 << slot
)))) {
171 bf5xx_tdm
->rx_map
[i
] = slot
;
172 rx_mapped
|= 1 << slot
;
181 static int bf5xx_tdm_suspend(struct snd_soc_dai
*dai
)
183 struct sport_device
*sport
= snd_soc_dai_get_drvdata(dai
);
185 if (dai
->playback_active
)
186 sport_tx_stop(sport
);
187 if (dai
->capture_active
)
188 sport_rx_stop(sport
);
190 /* isolate sync/clock pins from codec while sports resume */
191 peripheral_free_list(sport
->pin_req
);
196 static int bf5xx_tdm_resume(struct snd_soc_dai
*dai
)
199 struct sport_device
*sport
= snd_soc_dai_get_drvdata(dai
);
201 ret
= sport_set_multichannel(sport
, 8, 0xFF, 1);
203 pr_err("SPORT is busy!\n");
207 ret
= sport_config_rx(sport
, 0, 0x1F, 0, 0);
209 pr_err("SPORT is busy!\n");
213 ret
= sport_config_tx(sport
, 0, 0x1F, 0, 0);
215 pr_err("SPORT is busy!\n");
219 peripheral_request_list(sport
->pin_req
, "soc-audio");
225 #define bf5xx_tdm_suspend NULL
226 #define bf5xx_tdm_resume NULL
229 static struct snd_soc_dai_ops bf5xx_tdm_dai_ops
= {
230 .hw_params
= bf5xx_tdm_hw_params
,
231 .set_fmt
= bf5xx_tdm_set_dai_fmt
,
232 .shutdown
= bf5xx_tdm_shutdown
,
233 .set_channel_map
= bf5xx_tdm_set_channel_map
,
236 static struct snd_soc_dai_driver bf5xx_tdm_dai
= {
237 .suspend
= bf5xx_tdm_suspend
,
238 .resume
= bf5xx_tdm_resume
,
242 .rates
= SNDRV_PCM_RATE_48000
,
243 .formats
= SNDRV_PCM_FMTBIT_S32_LE
,},
247 .rates
= SNDRV_PCM_RATE_48000
,
248 .formats
= SNDRV_PCM_FMTBIT_S32_LE
,},
249 .ops
= &bf5xx_tdm_dai_ops
,
252 static int __devinit
bfin_tdm_probe(struct platform_device
*pdev
)
254 struct sport_device
*sport_handle
;
257 /* configure SPORT for TDM */
258 sport_handle
= sport_init(pdev
, 4, 8 * sizeof(u32
),
259 sizeof(struct bf5xx_tdm_port
));
263 /* SPORT works in TDM mode */
264 ret
= sport_set_multichannel(sport_handle
, 8, 0xFF, 1);
266 pr_err("SPORT is busy!\n");
268 goto sport_config_err
;
271 ret
= sport_config_rx(sport_handle
, 0, 0x1F, 0, 0);
273 pr_err("SPORT is busy!\n");
275 goto sport_config_err
;
278 ret
= sport_config_tx(sport_handle
, 0, 0x1F, 0, 0);
280 pr_err("SPORT is busy!\n");
282 goto sport_config_err
;
285 ret
= snd_soc_register_dai(&pdev
->dev
, &bf5xx_tdm_dai
);
287 pr_err("Failed to register DAI: %d\n", ret
);
288 goto sport_config_err
;
294 sport_done(sport_handle
);
298 static int __devexit
bfin_tdm_remove(struct platform_device
*pdev
)
300 struct sport_device
*sport_handle
= platform_get_drvdata(pdev
);
302 snd_soc_unregister_dai(&pdev
->dev
);
303 sport_done(sport_handle
);
308 static struct platform_driver bfin_tdm_driver
= {
309 .probe
= bfin_tdm_probe
,
310 .remove
= __devexit_p(bfin_tdm_remove
),
313 .owner
= THIS_MODULE
,
317 static int __init
bfin_tdm_init(void)
319 return platform_driver_register(&bfin_tdm_driver
);
321 module_init(bfin_tdm_init
);
323 static void __exit
bfin_tdm_exit(void)
325 platform_driver_unregister(&bfin_tdm_driver
);
327 module_exit(bfin_tdm_exit
);
329 /* Module information */
330 MODULE_AUTHOR("Barry Song");
331 MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
332 MODULE_LICENSE("GPL");