aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/blackfin
diff options
context:
space:
mode:
authorBarry Song <21cnbao@gmail.com>2009-07-27 06:06:39 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2009-07-28 17:15:27 -0400
commit01e2ab207ca5a8edac622ab112b581d41b5eb36e (patch)
treef12ef7f14cee59b081fa3bc9ab77876e128fc6ae /sound/soc/blackfin
parentb84eab08a67913581515a1184f1deedf1d54dc5d (diff)
ASoC: blackfin I2S(TDM mode) CPU DAI driver
The I2S DAI driver for blackfin SPORT, but works in TDM mode. I2S is not a special case of TDM with only left and right two slots for SPORT interface. I2S coordinates with TDM in SPORT, but not a part of TDM. TDM require different hardware configuration with I2S, not only different slot number. One is "Stereo Serial Operation" mode of SPORT, the other one is "Multichannel Operation" mode. They are incompatible at the same time. Hardware and DMA description and data transfer flow are much different for I2S and TDM. Merging them as a whole will be very ugly and difficult to maintain. So we don't define a new DAI type, but give two DAI instances for standard I2S and TDM, both in I2S-family DAI type. The TDM instance still uses the I2S-family DAI type. Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/blackfin')
-rw-r--r--sound/soc/blackfin/Kconfig15
-rw-r--r--sound/soc/blackfin/Makefile4
-rw-r--r--sound/soc/blackfin/bf5xx-tdm-pcm.c330
-rw-r--r--sound/soc/blackfin/bf5xx-tdm-pcm.h21
-rw-r--r--sound/soc/blackfin/bf5xx-tdm.c343
-rw-r--r--sound/soc/blackfin/bf5xx-tdm.h14
6 files changed, 726 insertions, 1 deletions
diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig
index 811596f4c092..2ab6d2fa87a0 100644
--- a/sound/soc/blackfin/Kconfig
+++ b/sound/soc/blackfin/Kconfig
@@ -7,6 +7,15 @@ config SND_BF5XX_I2S
7 mode (supports single stereo In/Out). 7 mode (supports single stereo In/Out).
8 You will also need to select the audio interfaces to support below. 8 You will also need to select the audio interfaces to support below.
9 9
10config SND_BF5XX_TDM
11 tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip"
12 depends on (BLACKFIN && SND_SOC)
13 help
14 Say Y or M if you want to add support for codecs attached to
15 the Blackfin SPORT (synchronous serial ports) interface in TDM
16 mode.
17 You will also need to select the audio interfaces to support below.
18
10config SND_BF5XX_SOC_SSM2602 19config SND_BF5XX_SOC_SSM2602
11 tristate "SoC SSM2602 Audio support for BF52x ezkit" 20 tristate "SoC SSM2602 Audio support for BF52x ezkit"
12 depends on SND_BF5XX_I2S 21 depends on SND_BF5XX_I2S
@@ -69,6 +78,10 @@ config SND_BF5XX_SOC_I2S
69 tristate 78 tristate
70 select SND_BF5XX_SOC_SPORT 79 select SND_BF5XX_SOC_SPORT
71 80
81config SND_BF5XX_SOC_TDM
82 tristate
83 select SND_BF5XX_SOC_SPORT
84
72config SND_BF5XX_SOC_AC97 85config SND_BF5XX_SOC_AC97
73 tristate 86 tristate
74 select AC97_BUS 87 select AC97_BUS
@@ -85,7 +98,7 @@ config SND_BF5XX_SOC_AD1980
85 98
86config SND_BF5XX_SPORT_NUM 99config SND_BF5XX_SPORT_NUM
87 int "Set a SPORT for Sound chip" 100 int "Set a SPORT for Sound chip"
88 depends on (SND_BF5XX_I2S || SND_BF5XX_AC97) 101 depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM)
89 range 0 3 if BF54x 102 range 0 3 if BF54x
90 range 0 1 if !BF54x 103 range 0 1 if !BF54x
91 default 0 104 default 0
diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile
index 97bb37a6359c..f86192623dd8 100644
--- a/sound/soc/blackfin/Makefile
+++ b/sound/soc/blackfin/Makefile
@@ -1,15 +1,19 @@
1# Blackfin Platform Support 1# Blackfin Platform Support
2snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o 2snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o
3snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o 3snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o
4snd-bf5xx-tdm-objs := bf5xx-tdm-pcm.o
4snd-soc-bf5xx-sport-objs := bf5xx-sport.o 5snd-soc-bf5xx-sport-objs := bf5xx-sport.o
5snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o 6snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o
6snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o 7snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o
8snd-soc-bf5xx-tdm-objs := bf5xx-tdm.o
7 9
8obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o 10obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o
9obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o 11obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o
12obj-$(CONFIG_SND_BF5XX_TDM) += snd-bf5xx-tdm.o
10obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o 13obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o
11obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o 14obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o
12obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o 15obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o
16obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o
13 17
14# Blackfin Machine Support 18# Blackfin Machine Support
15snd-ad1980-objs := bf5xx-ad1980.o 19snd-ad1980-objs := bf5xx-ad1980.o
diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c
new file mode 100644
index 000000000000..af7fa8d7ff0d
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c
@@ -0,0 +1,330 @@
1/*
2 * File: sound/soc/blackfin/bf5xx-tdm-pcm.c
3 * Author: Barry Song <Barry.Song@analog.com>
4 *
5 * Created: Tue June 06 2009
6 * Description: DMA driver for tdm codec
7 *
8 * Modified:
9 * Copyright 2009 Analog Devices Inc.
10 *
11 * Bugs: Enter bugs at http://blackfin.uclinux.org/
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, see the file COPYING, or write
25 * to the Free Software Foundation, Inc.,
26 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27 */
28
29#include <linux/module.h>
30#include <linux/init.h>
31#include <linux/platform_device.h>
32#include <linux/slab.h>
33#include <linux/dma-mapping.h>
34
35#include <sound/core.h>
36#include <sound/pcm.h>
37#include <sound/pcm_params.h>
38#include <sound/soc.h>
39
40#include <asm/dma.h>
41
42#include "bf5xx-tdm-pcm.h"
43#include "bf5xx-tdm.h"
44#include "bf5xx-sport.h"
45
46#define PCM_BUFFER_MAX 0x10000
47#define FRAGMENT_SIZE_MIN (4*1024)
48#define FRAGMENTS_MIN 2
49#define FRAGMENTS_MAX 32
50
51static void bf5xx_dma_irq(void *data)
52{
53 struct snd_pcm_substream *pcm = data;
54 snd_pcm_period_elapsed(pcm);
55}
56
57static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
58 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
59 SNDRV_PCM_INFO_RESUME),
60 .formats = SNDRV_PCM_FMTBIT_S32_LE,
61 .rates = SNDRV_PCM_RATE_48000,
62 .channels_min = 2,
63 .channels_max = 8,
64 .buffer_bytes_max = PCM_BUFFER_MAX,
65 .period_bytes_min = FRAGMENT_SIZE_MIN,
66 .period_bytes_max = PCM_BUFFER_MAX/2,
67 .periods_min = FRAGMENTS_MIN,
68 .periods_max = FRAGMENTS_MAX,
69};
70
71static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
72 struct snd_pcm_hw_params *params)
73{
74 size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
75 snd_pcm_lib_malloc_pages(substream, size * 4);
76
77 return 0;
78}
79
80static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
81{
82 snd_pcm_lib_free_pages(substream);
83
84 return 0;
85}
86
87static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
88{
89 struct snd_pcm_runtime *runtime = substream->runtime;
90 struct sport_device *sport = runtime->private_data;
91 int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size);
92
93 fragsize_bytes /= runtime->channels;
94 /* inflate the fragsize to match the dma width of SPORT */
95 fragsize_bytes *= 8;
96
97 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
98 sport_set_tx_callback(sport, bf5xx_dma_irq, substream);
99 sport_config_tx_dma(sport, runtime->dma_area,
100 runtime->periods, fragsize_bytes);
101 } else {
102 sport_set_rx_callback(sport, bf5xx_dma_irq, substream);
103 sport_config_rx_dma(sport, runtime->dma_area,
104 runtime->periods, fragsize_bytes);
105 }
106
107 return 0;
108}
109
110static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
111{
112 struct snd_pcm_runtime *runtime = substream->runtime;
113 struct sport_device *sport = runtime->private_data;
114 int ret = 0;
115
116 switch (cmd) {
117 case SNDRV_PCM_TRIGGER_START:
118 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
119 sport_tx_start(sport);
120 else
121 sport_rx_start(sport);
122 break;
123 case SNDRV_PCM_TRIGGER_STOP:
124 case SNDRV_PCM_TRIGGER_SUSPEND:
125 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
126 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
127 sport_tx_stop(sport);
128 else
129 sport_rx_stop(sport);
130 break;
131 default:
132 ret = -EINVAL;
133 }
134
135 return ret;
136}
137
138static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
139{
140 struct snd_pcm_runtime *runtime = substream->runtime;
141 struct sport_device *sport = runtime->private_data;
142 unsigned int diff;
143 snd_pcm_uframes_t frames;
144
145 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
146 diff = sport_curr_offset_tx(sport);
147 frames = diff / (8*4); /* 32 bytes per frame */
148 } else {
149 diff = sport_curr_offset_rx(sport);
150 frames = diff / (8*4);
151 }
152 return frames;
153}
154
155static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
156{
157 struct snd_pcm_runtime *runtime = substream->runtime;
158 int ret = 0;
159
160 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
161
162 ret = snd_pcm_hw_constraint_integer(runtime,
163 SNDRV_PCM_HW_PARAM_PERIODS);
164 if (ret < 0)
165 goto out;
166
167 if (sport_handle != NULL)
168 runtime->private_data = sport_handle;
169 else {
170 pr_err("sport_handle is NULL\n");
171 ret = -ENODEV;
172 }
173out:
174 return ret;
175}
176
177static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
178 snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
179{
180 unsigned int *src;
181 unsigned int *dst;
182 int i;
183
184 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
185 src = buf;
186 dst = (unsigned int *)substream->runtime->dma_area;
187
188 dst += pos * 8;
189 while (count--) {
190 for (i = 0; i < substream->runtime->channels; i++)
191 *(dst + i) = *src++;
192 dst += 8;
193 }
194 } else {
195 src = (unsigned int *)substream->runtime->dma_area;
196 dst = buf;
197
198 src += pos * 8;
199 while (count--) {
200 for (i = 0; i < substream->runtime->channels; i++)
201 *dst++ = *(src+i);
202 src += 8;
203 }
204 }
205
206 return 0;
207}
208
209static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
210 int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
211{
212 unsigned char *buf = substream->runtime->dma_area;
213 buf += pos * 8 * 4;
214 memset(buf, '\0', count * 8 * 4);
215
216 return 0;
217}
218
219
220struct snd_pcm_ops bf5xx_pcm_tdm_ops = {
221 .open = bf5xx_pcm_open,
222 .ioctl = snd_pcm_lib_ioctl,
223 .hw_params = bf5xx_pcm_hw_params,
224 .hw_free = bf5xx_pcm_hw_free,
225 .prepare = bf5xx_pcm_prepare,
226 .trigger = bf5xx_pcm_trigger,
227 .pointer = bf5xx_pcm_pointer,
228 .copy = bf5xx_pcm_copy,
229 .silence = bf5xx_pcm_silence,
230};
231
232static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
233{
234 struct snd_pcm_substream *substream = pcm->streams[stream].substream;
235 struct snd_dma_buffer *buf = &substream->dma_buffer;
236 size_t size = bf5xx_pcm_hardware.buffer_bytes_max;
237
238 buf->dev.type = SNDRV_DMA_TYPE_DEV;
239 buf->dev.dev = pcm->card->dev;
240 buf->private_data = NULL;
241 buf->area = dma_alloc_coherent(pcm->card->dev, size * 4,
242 &buf->addr, GFP_KERNEL);
243 if (!buf->area) {
244 pr_err("Failed to allocate dma memory \
245 Please increase uncached DMA memory region\n");
246 return -ENOMEM;
247 }
248 buf->bytes = size;
249
250 if (stream == SNDRV_PCM_STREAM_PLAYBACK)
251 sport_handle->tx_buf = buf->area;
252 else
253 sport_handle->rx_buf = buf->area;
254
255 return 0;
256}
257
258static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm)
259{
260 struct snd_pcm_substream *substream;
261 struct snd_dma_buffer *buf;
262 int stream;
263
264 for (stream = 0; stream < 2; stream++) {
265 substream = pcm->streams[stream].substream;
266 if (!substream)
267 continue;
268
269 buf = &substream->dma_buffer;
270 if (!buf->area)
271 continue;
272 dma_free_coherent(NULL, buf->bytes, buf->area, 0);
273 buf->area = NULL;
274 }
275 if (sport_handle)
276 sport_done(sport_handle);
277}
278
279static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK;
280
281static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai,
282 struct snd_pcm *pcm)
283{
284 int ret = 0;
285
286 if (!card->dev->dma_mask)
287 card->dev->dma_mask = &bf5xx_pcm_dmamask;
288 if (!card->dev->coherent_dma_mask)
289 card->dev->coherent_dma_mask = DMA_32BIT_MASK;
290
291 if (dai->playback.channels_min) {
292 ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
293 SNDRV_PCM_STREAM_PLAYBACK);
294 if (ret)
295 goto out;
296 }
297
298 if (dai->capture.channels_min) {
299 ret = bf5xx_pcm_preallocate_dma_buffer(pcm,
300 SNDRV_PCM_STREAM_CAPTURE);
301 if (ret)
302 goto out;
303 }
304out:
305 return ret;
306}
307
308struct snd_soc_platform bf5xx_tdm_soc_platform = {
309 .name = "bf5xx-audio",
310 .pcm_ops = &bf5xx_pcm_tdm_ops,
311 .pcm_new = bf5xx_pcm_tdm_new,
312 .pcm_free = bf5xx_pcm_free_dma_buffers,
313};
314EXPORT_SYMBOL_GPL(bf5xx_tdm_soc_platform);
315
316static int __init bfin_pcm_tdm_init(void)
317{
318 return snd_soc_register_platform(&bf5xx_tdm_soc_platform);
319}
320module_init(bfin_pcm_tdm_init);
321
322static void __exit bfin_pcm_tdm_exit(void)
323{
324 snd_soc_unregister_platform(&bf5xx_tdm_soc_platform);
325}
326module_exit(bfin_pcm_tdm_exit);
327
328MODULE_AUTHOR("Barry Song");
329MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module");
330MODULE_LICENSE("GPL");
diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.h b/sound/soc/blackfin/bf5xx-tdm-pcm.h
new file mode 100644
index 000000000000..9e8473d1c389
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm-pcm.h
@@ -0,0 +1,21 @@
1/*
2 * linux/sound/arm/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin
3 *
4 * Copyright 2009 Analog Device Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 */
10
11#ifndef _BF5XX_TDM_PCM_H
12#define _BF5XX_TDM_PCM_H
13
14struct bf5xx_pcm_dma_params {
15 char *name; /* stream identifier */
16};
17
18/* platform data */
19extern struct snd_soc_platform bf5xx_tdm_soc_platform;
20
21#endif
diff --git a/sound/soc/blackfin/bf5xx-tdm.c b/sound/soc/blackfin/bf5xx-tdm.c
new file mode 100644
index 000000000000..3096badf09a5
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm.c
@@ -0,0 +1,343 @@
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
49struct bf5xx_tdm_port {
50 u16 tcr1;
51 u16 rcr1;
52 u16 tcr2;
53 u16 rcr2;
54 int configured;
55};
56
57static struct bf5xx_tdm_port bf5xx_tdm;
58static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
59
60static struct sport_param sport_params[2] = {
61 {
62 .dma_rx_chan = CH_SPORT0_RX,
63 .dma_tx_chan = CH_SPORT0_TX,
64 .err_irq = IRQ_SPORT0_ERROR,
65 .regs = (struct sport_register *)SPORT0_TCR1,
66 },
67 {
68 .dma_rx_chan = CH_SPORT1_RX,
69 .dma_tx_chan = CH_SPORT1_TX,
70 .err_irq = IRQ_SPORT1_ERROR,
71 .regs = (struct sport_register *)SPORT1_TCR1,
72 }
73};
74
75/*
76 * Setting the TFS pin selector for SPORT 0 based on whether the selected
77 * port id F or G. If the port is F then no conflict should exist for the
78 * TFS. When Port G is selected and EMAC then there is a conflict between
79 * the PHY interrupt line and TFS. Current settings prevent the conflict
80 * by ignoring the TFS pin when Port G is selected. This allows both
81 * ssm2602 using Port G and EMAC concurrently.
82 */
83#ifdef CONFIG_BF527_SPORT0_PORTF
84#define LOCAL_SPORT0_TFS (P_SPORT0_TFS)
85#else
86#define LOCAL_SPORT0_TFS (0)
87#endif
88
89static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS,
90 P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0},
91 {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI,
92 P_SPORT1_RSCLK, P_SPORT1_TFS, 0} };
93
94static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai,
95 unsigned int fmt)
96{
97 int ret = 0;
98
99 /* interface format:support TDM,slave mode */
100 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
101 case SND_SOC_DAIFMT_DSP_A:
102 break;
103 default:
104 printk(KERN_ERR "%s: Unknown DAI format type\n", __func__);
105 ret = -EINVAL;
106 break;
107 }
108
109 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
110 case SND_SOC_DAIFMT_CBM_CFM:
111 break;
112 case SND_SOC_DAIFMT_CBS_CFS:
113 case SND_SOC_DAIFMT_CBM_CFS:
114 case SND_SOC_DAIFMT_CBS_CFM:
115 ret = -EINVAL;
116 break;
117 default:
118 printk(KERN_ERR "%s: Unknown DAI master type\n", __func__);
119 ret = -EINVAL;
120 break;
121 }
122
123 return ret;
124}
125
126static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream,
127 struct snd_pcm_hw_params *params,
128 struct snd_soc_dai *dai)
129{
130 int ret = 0;
131
132 bf5xx_tdm.tcr2 &= ~0x1f;
133 bf5xx_tdm.rcr2 &= ~0x1f;
134 switch (params_format(params)) {
135 case SNDRV_PCM_FORMAT_S32_LE:
136 bf5xx_tdm.tcr2 |= 31;
137 bf5xx_tdm.rcr2 |= 31;
138 sport_handle->wdsize = 4;
139 break;
140 /* at present, we only support 32bit transfer */
141 default:
142 pr_err("not supported PCM format yet\n");
143 return -EINVAL;
144 break;
145 }
146
147 if (!bf5xx_tdm.configured) {
148 /*
149 * TX and RX are not independent,they are enabled at the
150 * same time, even if only one side is running. So, we
151 * need to configure both of them at the time when the first
152 * stream is opened.
153 *
154 * CPU DAI:slave mode.
155 */
156 ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1,
157 bf5xx_tdm.rcr2, 0, 0);
158 if (ret) {
159 pr_err("SPORT is busy!\n");
160 return -EBUSY;
161 }
162
163 ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1,
164 bf5xx_tdm.tcr2, 0, 0);
165 if (ret) {
166 pr_err("SPORT is busy!\n");
167 return -EBUSY;
168 }
169
170 bf5xx_tdm.configured = 1;
171 }
172
173 return 0;
174}
175
176static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream,
177 struct snd_soc_dai *dai)
178{
179 /* No active stream, SPORT is allowed to be configured again. */
180 if (!dai->active)
181 bf5xx_tdm.configured = 0;
182}
183
184#ifdef CONFIG_PM
185static int bf5xx_tdm_suspend(struct snd_soc_dai *dai)
186{
187 struct sport_device *sport =
188 (struct sport_device *)dai->private_data;
189
190 if (!dai->active)
191 return 0;
192 if (dai->capture.active)
193 sport_rx_stop(sport);
194 if (dai->playback.active)
195 sport_tx_stop(sport);
196 return 0;
197}
198
199static int bf5xx_tdm_resume(struct snd_soc_dai *dai)
200{
201 int ret;
202 struct sport_device *sport =
203 (struct sport_device *)dai->private_data;
204
205 if (!dai->active)
206 return 0;
207
208 ret = sport_set_multichannel(sport, 8, 0xFF, 1);
209 if (ret) {
210 pr_err("SPORT is busy!\n");
211 ret = -EBUSY;
212 }
213
214 ret = sport_config_rx(sport, IRFS, 0x1F, 0, 0);
215 if (ret) {
216 pr_err("SPORT is busy!\n");
217 ret = -EBUSY;
218 }
219
220 ret = sport_config_tx(sport, ITFS, 0x1F, 0, 0);
221 if (ret) {
222 pr_err("SPORT is busy!\n");
223 ret = -EBUSY;
224 }
225
226 return 0;
227}
228
229#else
230#define bf5xx_tdm_suspend NULL
231#define bf5xx_tdm_resume NULL
232#endif
233
234static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = {
235 .hw_params = bf5xx_tdm_hw_params,
236 .set_fmt = bf5xx_tdm_set_dai_fmt,
237 .shutdown = bf5xx_tdm_shutdown,
238};
239
240struct snd_soc_dai bf5xx_tdm_dai = {
241 .name = "bf5xx-tdm",
242 .id = 0,
243 .suspend = bf5xx_tdm_suspend,
244 .resume = bf5xx_tdm_resume,
245 .playback = {
246 .channels_min = 2,
247 .channels_max = 8,
248 .rates = SNDRV_PCM_RATE_48000,
249 .formats = SNDRV_PCM_FMTBIT_S32_LE,},
250 .capture = {
251 .channels_min = 2,
252 .channels_max = 8,
253 .rates = SNDRV_PCM_RATE_48000,
254 .formats = SNDRV_PCM_FMTBIT_S32_LE,},
255 .ops = &bf5xx_tdm_dai_ops,
256};
257EXPORT_SYMBOL_GPL(bf5xx_tdm_dai);
258
259static int __devinit bfin_tdm_probe(struct platform_device *pdev)
260{
261 int ret = 0;
262
263 if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
264 pr_err("Requesting Peripherals failed\n");
265 return -EFAULT;
266 }
267
268 /* request DMA for SPORT */
269 sport_handle = sport_init(&sport_params[sport_num], 4, \
270 8 * sizeof(u32), NULL);
271 if (!sport_handle) {
272 peripheral_free_list(&sport_req[sport_num][0]);
273 return -ENODEV;
274 }
275
276 /* SPORT works in TDM mode */
277 ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1);
278 if (ret) {
279 pr_err("SPORT is busy!\n");
280 ret = -EBUSY;
281 goto sport_config_err;
282 }
283
284 ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0);
285 if (ret) {
286 pr_err("SPORT is busy!\n");
287 ret = -EBUSY;
288 goto sport_config_err;
289 }
290
291 ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0);
292 if (ret) {
293 pr_err("SPORT is busy!\n");
294 ret = -EBUSY;
295 goto sport_config_err;
296 }
297
298 ret = snd_soc_register_dai(&bf5xx_tdm_dai);
299 if (ret) {
300 pr_err("Failed to register DAI: %d\n", ret);
301 goto sport_config_err;
302 }
303 return 0;
304
305sport_config_err:
306 peripheral_free_list(&sport_req[sport_num][0]);
307 return ret;
308}
309
310static int __devexit bfin_tdm_remove(struct platform_device *pdev)
311{
312 peripheral_free_list(&sport_req[sport_num][0]);
313 snd_soc_unregister_dai(&bf5xx_tdm_dai);
314
315 return 0;
316}
317
318static struct platform_driver bfin_tdm_driver = {
319 .probe = bfin_tdm_probe,
320 .remove = __devexit_p(bfin_tdm_remove),
321 .driver = {
322 .name = "bfin-tdm",
323 .owner = THIS_MODULE,
324 },
325};
326
327static int __init bfin_tdm_init(void)
328{
329 return platform_driver_register(&bfin_tdm_driver);
330}
331module_init(bfin_tdm_init);
332
333static void __exit bfin_tdm_exit(void)
334{
335 platform_driver_unregister(&bfin_tdm_driver);
336}
337module_exit(bfin_tdm_exit);
338
339/* Module information */
340MODULE_AUTHOR("Barry Song");
341MODULE_DESCRIPTION("TDM driver for ADI Blackfin");
342MODULE_LICENSE("GPL");
343
diff --git a/sound/soc/blackfin/bf5xx-tdm.h b/sound/soc/blackfin/bf5xx-tdm.h
new file mode 100644
index 000000000000..e5157cd81c5e
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-tdm.h
@@ -0,0 +1,14 @@
1/*
2 * linux/sound/arm/bf5xx-i2s.h
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#ifndef _BF5XX_TDM_H
10#define _BF5XX_TDM_H
11
12extern struct snd_soc_dai bf5xx_tdm_dai;
13
14#endif