aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/blackfin
diff options
context:
space:
mode:
authorLars-Peter Clausen <lars@metafoo.de>2013-05-28 13:22:14 -0400
committerMark Brown <broonie@opensource.wolfsonmicro.com>2013-05-30 07:33:40 -0400
commit8b5e2e396b589119bcc9c6a382a999e0202bae18 (patch)
treebc5057b3426563cace2ae075bfc2b5fa5ba37c11 /sound/soc/blackfin
parenta3935a29f68c261d31b41c896f95c9333b615abf (diff)
ASoC: blackfin: bf5xx-i2s: Add support for TDM mode
The bf5xx-i2s{,-pcm} and bf5xx-tdm{-pcm} drivers are nearly identical. Both are for the same hardware each supporting a slight different subset of the hardware. The bf5xx-i2s driver supports 2 channel I2S mode while the bf5xx-tdm driver supports TDM mode and channel remapping. This patch adds support for TDM mode and channel remapping to the bf5xx-i2s driver so that we'll eventually be able to retire the bf5xx-tdm driver. Unfortunately the hardware is fixed to using 8 channels in TDM mode. The bf5xx-tdm driver jumps through a few hoops to make it work well with other channel counts as well: * Don't support mmap * Translate between internal frame size (which is always 8 * sample_size) and ALSA frame size (which depends on the channel count) * Have special copy and silence callbacks which are aware of the mismatch between internal and ALSA frame size * Reduce the maximum buffer size to ensure that there is enough headroom for dummy data. The bf5xx-i2s driver is going to use the same mechanisms when being used int TDM mode. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'sound/soc/blackfin')
-rw-r--r--sound/soc/blackfin/bf5xx-i2s-pcm.c118
-rw-r--r--sound/soc/blackfin/bf5xx-i2s-pcm.h17
-rw-r--r--sound/soc/blackfin/bf5xx-i2s.c105
3 files changed, 229 insertions, 11 deletions
diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.c b/sound/soc/blackfin/bf5xx-i2s-pcm.c
index 9931a18c962e..9cb4a80df98e 100644
--- a/sound/soc/blackfin/bf5xx-i2s-pcm.c
+++ b/sound/soc/blackfin/bf5xx-i2s-pcm.c
@@ -40,6 +40,7 @@
40#include <asm/dma.h> 40#include <asm/dma.h>
41 41
42#include "bf5xx-sport.h" 42#include "bf5xx-sport.h"
43#include "bf5xx-i2s-pcm.h"
43 44
44static void bf5xx_dma_irq(void *data) 45static void bf5xx_dma_irq(void *data)
45{ 46{
@@ -49,7 +50,6 @@ static void bf5xx_dma_irq(void *data)
49 50
50static const struct snd_pcm_hardware bf5xx_pcm_hardware = { 51static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
51 .info = SNDRV_PCM_INFO_INTERLEAVED | 52 .info = SNDRV_PCM_INFO_INTERLEAVED |
52 SNDRV_PCM_INFO_MMAP |
53 SNDRV_PCM_INFO_MMAP_VALID | 53 SNDRV_PCM_INFO_MMAP_VALID |
54 SNDRV_PCM_INFO_BLOCK_TRANSFER, 54 SNDRV_PCM_INFO_BLOCK_TRANSFER,
55 .formats = SNDRV_PCM_FMTBIT_S16_LE | 55 .formats = SNDRV_PCM_FMTBIT_S16_LE |
@@ -66,7 +66,16 @@ static const struct snd_pcm_hardware bf5xx_pcm_hardware = {
66static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, 66static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream,
67 struct snd_pcm_hw_params *params) 67 struct snd_pcm_hw_params *params)
68{ 68{
69 return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); 69 struct snd_soc_pcm_runtime *rtd = substream->private_data;
70 unsigned int buffer_size = params_buffer_bytes(params);
71 struct bf5xx_i2s_pcm_data *dma_data;
72
73 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
74
75 if (dma_data->tdm_mode)
76 buffer_size = buffer_size / params_channels(params) * 8;
77
78 return snd_pcm_lib_malloc_pages(substream, buffer_size);
70} 79}
71 80
72static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) 81static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
@@ -78,9 +87,16 @@ static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream)
78 87
79static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) 88static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream)
80{ 89{
90 struct snd_soc_pcm_runtime *rtd = substream->private_data;
81 struct snd_pcm_runtime *runtime = substream->runtime; 91 struct snd_pcm_runtime *runtime = substream->runtime;
82 struct sport_device *sport = runtime->private_data; 92 struct sport_device *sport = runtime->private_data;
83 int period_bytes = frames_to_bytes(runtime, runtime->period_size); 93 int period_bytes = frames_to_bytes(runtime, runtime->period_size);
94 struct bf5xx_i2s_pcm_data *dma_data;
95
96 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
97
98 if (dma_data->tdm_mode)
99 period_bytes = period_bytes / runtime->channels * 8;
84 100
85 pr_debug("%s enter\n", __func__); 101 pr_debug("%s enter\n", __func__);
86 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 102 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
@@ -127,10 +143,15 @@ static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
127 143
128static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) 144static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
129{ 145{
146 struct snd_soc_pcm_runtime *rtd = substream->private_data;
130 struct snd_pcm_runtime *runtime = substream->runtime; 147 struct snd_pcm_runtime *runtime = substream->runtime;
131 struct sport_device *sport = runtime->private_data; 148 struct sport_device *sport = runtime->private_data;
132 unsigned int diff; 149 unsigned int diff;
133 snd_pcm_uframes_t frames; 150 snd_pcm_uframes_t frames;
151 struct bf5xx_i2s_pcm_data *dma_data;
152
153 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
154
134 pr_debug("%s enter\n", __func__); 155 pr_debug("%s enter\n", __func__);
135 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 156 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
136 diff = sport_curr_offset_tx(sport); 157 diff = sport_curr_offset_tx(sport);
@@ -147,6 +168,8 @@ static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream)
147 diff = 0; 168 diff = 0;
148 169
149 frames = bytes_to_frames(substream->runtime, diff); 170 frames = bytes_to_frames(substream->runtime, diff);
171 if (dma_data->tdm_mode)
172 frames = frames * runtime->channels / 8;
150 173
151 return frames; 174 return frames;
152} 175}
@@ -158,11 +181,18 @@ static int bf5xx_pcm_open(struct snd_pcm_substream *substream)
158 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai); 181 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(cpu_dai);
159 struct snd_pcm_runtime *runtime = substream->runtime; 182 struct snd_pcm_runtime *runtime = substream->runtime;
160 struct snd_dma_buffer *buf = &substream->dma_buffer; 183 struct snd_dma_buffer *buf = &substream->dma_buffer;
184 struct bf5xx_i2s_pcm_data *dma_data;
161 int ret; 185 int ret;
162 186
187 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
188
163 pr_debug("%s enter\n", __func__); 189 pr_debug("%s enter\n", __func__);
164 190
165 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); 191 snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware);
192 if (dma_data->tdm_mode)
193 runtime->hw.buffer_bytes_max /= 4;
194 else
195 runtime->hw.info |= SNDRV_PCM_INFO_MMAP;
166 196
167 ret = snd_pcm_hw_constraint_integer(runtime, 197 ret = snd_pcm_hw_constraint_integer(runtime,
168 SNDRV_PCM_HW_PARAM_PERIODS); 198 SNDRV_PCM_HW_PARAM_PERIODS);
@@ -198,6 +228,88 @@ static int bf5xx_pcm_mmap(struct snd_pcm_substream *substream,
198 return 0 ; 228 return 0 ;
199} 229}
200 230
231static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel,
232 snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count)
233{
234 struct snd_soc_pcm_runtime *rtd = substream->private_data;
235 struct snd_pcm_runtime *runtime = substream->runtime;
236 unsigned int sample_size = runtime->sample_bits / 8;
237 struct bf5xx_i2s_pcm_data *dma_data;
238 unsigned int i;
239 void *src, *dst;
240
241 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
242
243 if (dma_data->tdm_mode) {
244 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
245 src = buf;
246 dst = runtime->dma_area;
247 dst += pos * sample_size * 8;
248
249 while (count--) {
250 for (i = 0; i < runtime->channels; i++) {
251 memcpy(dst + dma_data->map[i] *
252 sample_size, src, sample_size);
253 src += sample_size;
254 }
255 dst += 8 * sample_size;
256 }
257 } else {
258 src = runtime->dma_area;
259 src += pos * sample_size * 8;
260 dst = buf;
261
262 while (count--) {
263 for (i = 0; i < runtime->channels; i++) {
264 memcpy(dst, src + dma_data->map[i] *
265 sample_size, sample_size);
266 dst += sample_size;
267 }
268 src += 8 * sample_size;
269 }
270 }
271 } else {
272 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
273 src = buf;
274 dst = runtime->dma_area;
275 dst += frames_to_bytes(runtime, pos);
276 } else {
277 src = runtime->dma_area;
278 src += frames_to_bytes(runtime, pos);
279 dst = buf;
280 }
281
282 memcpy(dst, src, frames_to_bytes(runtime, count));
283 }
284
285 return 0;
286}
287
288static int bf5xx_pcm_silence(struct snd_pcm_substream *substream,
289 int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count)
290{
291 struct snd_soc_pcm_runtime *rtd = substream->private_data;
292 struct snd_pcm_runtime *runtime = substream->runtime;
293 unsigned int sample_size = runtime->sample_bits / 8;
294 void *buf = runtime->dma_area;
295 struct bf5xx_i2s_pcm_data *dma_data;
296 unsigned int offset, size;
297
298 dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
299
300 if (dma_data->tdm_mode) {
301 offset = pos * 8 * sample_size;
302 size = count * 8 * sample_size;
303 } else {
304 offset = frames_to_bytes(runtime, pos);
305 size = frames_to_bytes(runtime, count);
306 }
307
308 snd_pcm_format_set_silence(runtime->format, buf + offset, size);
309
310 return 0;
311}
312
201static struct snd_pcm_ops bf5xx_pcm_i2s_ops = { 313static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
202 .open = bf5xx_pcm_open, 314 .open = bf5xx_pcm_open,
203 .ioctl = snd_pcm_lib_ioctl, 315 .ioctl = snd_pcm_lib_ioctl,
@@ -207,6 +319,8 @@ static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
207 .trigger = bf5xx_pcm_trigger, 319 .trigger = bf5xx_pcm_trigger,
208 .pointer = bf5xx_pcm_pointer, 320 .pointer = bf5xx_pcm_pointer,
209 .mmap = bf5xx_pcm_mmap, 321 .mmap = bf5xx_pcm_mmap,
322 .copy = bf5xx_pcm_copy,
323 .silence = bf5xx_pcm_silence,
210}; 324};
211 325
212static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); 326static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32);
diff --git a/sound/soc/blackfin/bf5xx-i2s-pcm.h b/sound/soc/blackfin/bf5xx-i2s-pcm.h
new file mode 100644
index 000000000000..1f0435249f88
--- /dev/null
+++ b/sound/soc/blackfin/bf5xx-i2s-pcm.h
@@ -0,0 +1,17 @@
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License version 2 as
4 * published by the Free Software Foundation.
5 */
6
7#ifndef _BF5XX_TDM_PCM_H
8#define _BF5XX_TDM_PCM_H
9
10#define BFIN_TDM_DAI_MAX_SLOTS 8
11
12struct bf5xx_i2s_pcm_data {
13 unsigned int map[BFIN_TDM_DAI_MAX_SLOTS];
14 bool tdm_mode;
15};
16
17#endif
diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c
index 78411f266f60..9a174fc47d39 100644
--- a/sound/soc/blackfin/bf5xx-i2s.c
+++ b/sound/soc/blackfin/bf5xx-i2s.c
@@ -42,6 +42,7 @@
42#include <linux/gpio.h> 42#include <linux/gpio.h>
43 43
44#include "bf5xx-sport.h" 44#include "bf5xx-sport.h"
45#include "bf5xx-i2s-pcm.h"
45 46
46struct bf5xx_i2s_port { 47struct bf5xx_i2s_port {
47 u16 tcr1; 48 u16 tcr1;
@@ -49,6 +50,13 @@ struct bf5xx_i2s_port {
49 u16 tcr2; 50 u16 tcr2;
50 u16 rcr2; 51 u16 rcr2;
51 int configured; 52 int configured;
53
54 unsigned int slots;
55 unsigned int tx_mask;
56 unsigned int rx_mask;
57
58 struct bf5xx_i2s_pcm_data tx_dma_data;
59 struct bf5xx_i2s_pcm_data rx_dma_data;
52}; 60};
53 61
54static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, 62static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
@@ -170,6 +178,64 @@ static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream,
170 bf5xx_i2s->configured = 0; 178 bf5xx_i2s->configured = 0;
171} 179}
172 180
181static int bf5xx_i2s_set_channel_map(struct snd_soc_dai *dai,
182 unsigned int tx_num, unsigned int *tx_slot,
183 unsigned int rx_num, unsigned int *rx_slot)
184{
185 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
186 struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
187 unsigned int tx_mapped = 0, rx_mapped = 0;
188 unsigned int slot;
189 int i;
190
191 if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) ||
192 (rx_num > BFIN_TDM_DAI_MAX_SLOTS))
193 return -EINVAL;
194
195 for (i = 0; i < tx_num; i++) {
196 slot = tx_slot[i];
197 if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
198 (!(tx_mapped & (1 << slot)))) {
199 bf5xx_i2s->tx_dma_data.map[i] = slot;
200 tx_mapped |= 1 << slot;
201 } else
202 return -EINVAL;
203 }
204 for (i = 0; i < rx_num; i++) {
205 slot = rx_slot[i];
206 if ((slot < BFIN_TDM_DAI_MAX_SLOTS) &&
207 (!(rx_mapped & (1 << slot)))) {
208 bf5xx_i2s->rx_dma_data.map[i] = slot;
209 rx_mapped |= 1 << slot;
210 } else
211 return -EINVAL;
212 }
213
214 return 0;
215}
216
217static int bf5xx_i2s_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
218 unsigned int rx_mask, int slots, int width)
219{
220 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
221 struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
222
223 if (slots % 8 != 0 || slots > 8)
224 return -EINVAL;
225
226 if (width != 32)
227 return -EINVAL;
228
229 bf5xx_i2s->slots = slots;
230 bf5xx_i2s->tx_mask = tx_mask;
231 bf5xx_i2s->rx_mask = rx_mask;
232
233 bf5xx_i2s->tx_dma_data.tdm_mode = slots != 0;
234 bf5xx_i2s->rx_dma_data.tdm_mode = slots != 0;
235
236 return sport_set_multichannel(sport_handle, slots, tx_mask, rx_mask, 0);
237}
238
173#ifdef CONFIG_PM 239#ifdef CONFIG_PM
174static int bf5xx_i2s_suspend(struct snd_soc_dai *dai) 240static int bf5xx_i2s_suspend(struct snd_soc_dai *dai)
175{ 241{
@@ -206,7 +272,8 @@ static int bf5xx_i2s_resume(struct snd_soc_dai *dai)
206 return -EBUSY; 272 return -EBUSY;
207 } 273 }
208 274
209 return 0; 275 return sport_set_multichannel(sport_handle, bf5xx_i2s->slots,
276 bf5xx_i2s->tx_mask, bf5xx_i2s->rx_mask, 0);
210} 277}
211 278
212#else 279#else
@@ -214,6 +281,23 @@ static int bf5xx_i2s_resume(struct snd_soc_dai *dai)
214#define bf5xx_i2s_resume NULL 281#define bf5xx_i2s_resume NULL
215#endif 282#endif
216 283
284static int bf5xx_i2s_dai_probe(struct snd_soc_dai *dai)
285{
286 struct sport_device *sport_handle = snd_soc_dai_get_drvdata(dai);
287 struct bf5xx_i2s_port *bf5xx_i2s = sport_handle->private_data;
288 unsigned int i;
289
290 for (i = 0; i < BFIN_TDM_DAI_MAX_SLOTS; i++) {
291 bf5xx_i2s->tx_dma_data.map[i] = i;
292 bf5xx_i2s->rx_dma_data.map[i] = i;
293 }
294
295 dai->playback_dma_data = &bf5xx_i2s->tx_dma_data;
296 dai->capture_dma_data = &bf5xx_i2s->rx_dma_data;
297
298 return 0;
299}
300
217#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ 301#define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
218 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \ 302 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
219 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \ 303 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
@@ -226,22 +310,25 @@ static int bf5xx_i2s_resume(struct snd_soc_dai *dai)
226 SNDRV_PCM_FMTBIT_S32_LE) 310 SNDRV_PCM_FMTBIT_S32_LE)
227 311
228static const struct snd_soc_dai_ops bf5xx_i2s_dai_ops = { 312static const struct snd_soc_dai_ops bf5xx_i2s_dai_ops = {
229 .shutdown = bf5xx_i2s_shutdown, 313 .shutdown = bf5xx_i2s_shutdown,
230 .hw_params = bf5xx_i2s_hw_params, 314 .hw_params = bf5xx_i2s_hw_params,
231 .set_fmt = bf5xx_i2s_set_dai_fmt, 315 .set_fmt = bf5xx_i2s_set_dai_fmt,
316 .set_tdm_slot = bf5xx_i2s_set_tdm_slot,
317 .set_channel_map = bf5xx_i2s_set_channel_map,
232}; 318};
233 319
234static struct snd_soc_dai_driver bf5xx_i2s_dai = { 320static struct snd_soc_dai_driver bf5xx_i2s_dai = {
321 .probe = bf5xx_i2s_dai_probe,
235 .suspend = bf5xx_i2s_suspend, 322 .suspend = bf5xx_i2s_suspend,
236 .resume = bf5xx_i2s_resume, 323 .resume = bf5xx_i2s_resume,
237 .playback = { 324 .playback = {
238 .channels_min = 1, 325 .channels_min = 2,
239 .channels_max = 2, 326 .channels_max = 8,
240 .rates = BF5XX_I2S_RATES, 327 .rates = BF5XX_I2S_RATES,
241 .formats = BF5XX_I2S_FORMATS,}, 328 .formats = BF5XX_I2S_FORMATS,},
242 .capture = { 329 .capture = {
243 .channels_min = 1, 330 .channels_min = 2,
244 .channels_max = 2, 331 .channels_max = 8,
245 .rates = BF5XX_I2S_RATES, 332 .rates = BF5XX_I2S_RATES,
246 .formats = BF5XX_I2S_FORMATS,}, 333 .formats = BF5XX_I2S_FORMATS,},
247 .ops = &bf5xx_i2s_dai_ops, 334 .ops = &bf5xx_i2s_dai_ops,
@@ -257,7 +344,7 @@ static int bf5xx_i2s_probe(struct platform_device *pdev)
257 int ret; 344 int ret;
258 345
259 /* configure SPORT for I2S */ 346 /* configure SPORT for I2S */
260 sport_handle = sport_init(pdev, 4, 2 * sizeof(u32), 347 sport_handle = sport_init(pdev, 4, 8 * sizeof(u32),
261 sizeof(struct bf5xx_i2s_port)); 348 sizeof(struct bf5xx_i2s_port));
262 if (!sport_handle) 349 if (!sport_handle)
263 return -ENODEV; 350 return -ENODEV;