Skip to content

Commit dc0d9f4

Browse files
committed
snd-bcm2835: Add support for spdif/hdmi passthrough
This adds a dedicated subdevice which can be used for passthrough of non-audio formats (ie encoded a52) through the hdmi audio link. In addition to this driver extension an appropriate card config is required to make alsa-lib support the AES parameters for this device.
1 parent 2a8d45e commit dc0d9f4

File tree

4 files changed

+250
-19
lines changed

4 files changed

+250
-19
lines changed

sound/arm/bcm2835-ctl.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <sound/rawmidi.h>
3131
#include <sound/initval.h>
3232
#include <sound/tlv.h>
33+
#include <sound/asoundef.h>
3334

3435
#include "bcm2835.h"
3536

@@ -183,6 +184,122 @@ static struct snd_kcontrol_new snd_bcm2835_ctl[] __devinitdata = {
183184
},
184185
};
185186

187+
static int snd_bcm2835_spdif_default_info(struct snd_kcontrol *kcontrol,
188+
struct snd_ctl_elem_info *uinfo)
189+
{
190+
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
191+
uinfo->count = 1;
192+
return 0;
193+
}
194+
195+
static int snd_bcm2835_spdif_default_get(struct snd_kcontrol *kcontrol,
196+
struct snd_ctl_elem_value *ucontrol)
197+
{
198+
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
199+
int i;
200+
201+
for (i = 0; i < 4; i++)
202+
ucontrol->value.iec958.status[i] =
203+
(chip->spdif_status >> (i * 8)) && 0xff;
204+
205+
return 0;
206+
}
207+
208+
static int snd_bcm2835_spdif_default_put(struct snd_kcontrol *kcontrol,
209+
struct snd_ctl_elem_value *ucontrol)
210+
{
211+
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
212+
unsigned int val = 0;
213+
int i, change;
214+
215+
for (i = 0; i < 4; i++)
216+
val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8);
217+
218+
change = val != chip->spdif_status;
219+
chip->spdif_status = val;
220+
221+
return change;
222+
}
223+
224+
static int snd_bcm2835_spdif_mask_info(struct snd_kcontrol *kcontrol,
225+
struct snd_ctl_elem_info *uinfo)
226+
{
227+
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
228+
uinfo->count = 1;
229+
return 0;
230+
}
231+
232+
static int snd_bcm2835_spdif_mask_get(struct snd_kcontrol *kcontrol,
233+
struct snd_ctl_elem_value *ucontrol)
234+
{
235+
/* bcm2835 supports only consumer mode and sets all other format flags
236+
* automatically. So the only thing left is signalling non-audio
237+
* content */
238+
ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO;
239+
return 0;
240+
}
241+
242+
static int snd_bcm2835_spdif_stream_info(struct snd_kcontrol *kcontrol,
243+
struct snd_ctl_elem_info *uinfo)
244+
{
245+
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
246+
uinfo->count = 1;
247+
return 0;
248+
}
249+
250+
static int snd_bcm2835_spdif_stream_get(struct snd_kcontrol *kcontrol,
251+
struct snd_ctl_elem_value *ucontrol)
252+
{
253+
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
254+
int i;
255+
256+
for (i = 0; i < 4; i++)
257+
ucontrol->value.iec958.status[i] =
258+
(chip->spdif_status >> (i * 8)) & 0xff;
259+
return 0;
260+
}
261+
262+
static int snd_bcm2835_spdif_stream_put(struct snd_kcontrol *kcontrol,
263+
struct snd_ctl_elem_value *ucontrol)
264+
{
265+
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol);
266+
unsigned int val = 0;
267+
int i, change;
268+
269+
for (i = 0; i < 4; i++)
270+
val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8);
271+
change = val != chip->spdif_status;
272+
chip->spdif_status = val;
273+
274+
return change;
275+
}
276+
277+
static struct snd_kcontrol_new snd_bcm2835_spdif[] __devinitdata = {
278+
{
279+
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
280+
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
281+
.info = snd_bcm2835_spdif_default_info,
282+
.get = snd_bcm2835_spdif_default_get,
283+
.put = snd_bcm2835_spdif_default_put
284+
},
285+
{
286+
.access = SNDRV_CTL_ELEM_ACCESS_READ,
287+
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
288+
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
289+
.info = snd_bcm2835_spdif_mask_info,
290+
.get = snd_bcm2835_spdif_mask_get,
291+
},
292+
{
293+
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
294+
SNDRV_CTL_ELEM_ACCESS_INACTIVE,
295+
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
296+
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
297+
.info = snd_bcm2835_spdif_stream_info,
298+
.get = snd_bcm2835_spdif_stream_get,
299+
.put = snd_bcm2835_spdif_stream_put,
300+
},
301+
};
302+
186303
int __devinit snd_bcm2835_new_ctl(bcm2835_chip_t * chip)
187304
{
188305
int err;
@@ -196,5 +313,11 @@ int __devinit snd_bcm2835_new_ctl(bcm2835_chip_t * chip)
196313
if (err < 0)
197314
return err;
198315
}
316+
for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_spdif); idx++) {
317+
err = snd_ctl_add(chip->card,
318+
snd_ctl_new1(&snd_bcm2835_spdif[idx], chip));
319+
if (err < 0)
320+
return err;
321+
}
199322
return 0;
200323
}

sound/arm/bcm2835-pcm.c

Lines changed: 110 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include <linux/interrupt.h>
1616
#include <linux/slab.h>
1717

18+
#include <sound/asoundef.h>
19+
1820
#include "bcm2835.h"
1921

2022
/* hardware definition */
@@ -34,6 +36,23 @@ static struct snd_pcm_hardware snd_bcm2835_playback_hw = {
3436
.periods_max = 128,
3537
};
3638

39+
static struct snd_pcm_hardware snd_bcm2835_playback_spdif_hw = {
40+
.info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
41+
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),
42+
.formats = SNDRV_PCM_FMTBIT_S16_LE,
43+
.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 |
44+
SNDRV_PCM_RATE_48000,
45+
.rate_min = 44100,
46+
.rate_max = 48000,
47+
.channels_min = 2,
48+
.channels_max = 2,
49+
.buffer_bytes_max = 128 * 1024,
50+
.period_bytes_min = 1 * 1024,
51+
.period_bytes_max = 128 * 1024,
52+
.periods_min = 1,
53+
.periods_max = 128,
54+
};
55+
3756
static void snd_bcm2835_playback_free(struct snd_pcm_runtime *runtime)
3857
{
3958
audio_info("Freeing up alsa stream here ..\n");
@@ -89,7 +108,8 @@ static irqreturn_t bcm2835_playback_fifo_irq(int irq, void *dev_id)
89108
}
90109

91110
/* open callback */
92-
static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
111+
static int snd_bcm2835_playback_open_generic(
112+
struct snd_pcm_substream *substream, int spdif)
93113
{
94114
bcm2835_chip_t *chip = snd_pcm_substream_chip(substream);
95115
struct snd_pcm_runtime *runtime = substream->runtime;
@@ -102,6 +122,11 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
102122
audio_info("Alsa open (%d)\n", substream->number);
103123
idx = substream->number;
104124

125+
if (spdif && chip->opened != 0)
126+
return -EBUSY;
127+
else if (!spdif && (chip->opened & (1 << idx)))
128+
return -EBUSY;
129+
105130
if (idx > MAX_SUBSTREAMS) {
106131
audio_error
107132
("substream(%d) device doesn't exist max(%d) substreams allowed\n",
@@ -139,7 +164,13 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
139164

140165
runtime->private_data = alsa_stream;
141166
runtime->private_free = snd_bcm2835_playback_free;
142-
runtime->hw = snd_bcm2835_playback_hw;
167+
if (spdif) {
168+
runtime->hw = snd_bcm2835_playback_spdif_hw;
169+
} else {
170+
/* clear spdif status, as we are not in spdif mode */
171+
chip->spdif_status = 0;
172+
runtime->hw = snd_bcm2835_playback_hw;
173+
}
143174
/* minimum 16 bytes alignment (for vchiq bulk transfers) */
144175
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
145176
16);
@@ -150,6 +181,7 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
150181
return err;
151182
}
152183

184+
chip->opened |= (1 << idx);
153185
alsa_stream->open = 1;
154186
alsa_stream->draining = 1;
155187

@@ -159,13 +191,24 @@ static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
159191
return err;
160192
}
161193

194+
static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
195+
{
196+
return snd_bcm2835_playback_open_generic(substream, 0);
197+
}
198+
199+
static int snd_bcm2835_playback_spdif_open(struct snd_pcm_substream *substream)
200+
{
201+
return snd_bcm2835_playback_open_generic(substream, 1);
202+
}
203+
162204
/* close callback */
163205
static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
164206
{
165207
/* the hardware-specific codes will be here */
166208

167209
struct snd_pcm_runtime *runtime = substream->runtime;
168210
bcm2835_alsa_stream_t *alsa_stream = runtime->private_data;
211+
bcm2835_chip_t *chip = snd_pcm_substream_chip(substream);
169212

170213
audio_info(" .. IN\n");
171214
audio_info("Alsa close\n");
@@ -196,6 +239,8 @@ static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
196239
* runtime->private_free callback we registered in *_open above
197240
*/
198241

242+
chip->opened &= ~(1 << substream->number);
243+
199244
audio_info(" .. OUT\n");
200245

201246
return 0;
@@ -205,10 +250,9 @@ static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
205250
static int snd_bcm2835_pcm_hw_params(struct snd_pcm_substream *substream,
206251
struct snd_pcm_hw_params *params)
207252
{
208-
int err;
209253
struct snd_pcm_runtime *runtime = substream->runtime;
210-
bcm2835_alsa_stream_t *alsa_stream =
211-
(bcm2835_alsa_stream_t *) runtime->private_data;
254+
bcm2835_alsa_stream_t *alsa_stream = runtime->private_data;
255+
int err;
212256

213257
audio_info(" .. IN\n");
214258

@@ -219,19 +263,9 @@ static int snd_bcm2835_pcm_hw_params(struct snd_pcm_substream *substream,
219263
return err;
220264
}
221265

222-
err = bcm2835_audio_set_params(alsa_stream, params_channels(params),
223-
params_rate(params),
224-
snd_pcm_format_width(params_format
225-
(params)));
226-
if (err < 0) {
227-
audio_error(" error setting hw params\n");
228-
}
229-
230-
bcm2835_audio_setup(alsa_stream);
231-
232-
/* in preparation of the stream, set the controls (volume level) of the stream */
233-
bcm2835_audio_set_ctls(alsa_stream->chip);
234-
266+
alsa_stream->channels = params_channels(params);
267+
alsa_stream->params_rate = params_rate(params);
268+
alsa_stream->pcm_format_width = snd_pcm_format_width(params_format (params));
235269
audio_info(" .. OUT\n");
236270

237271
return err;
@@ -247,11 +281,35 @@ static int snd_bcm2835_pcm_hw_free(struct snd_pcm_substream *substream)
247281
/* prepare callback */
248282
static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
249283
{
284+
bcm2835_chip_t *chip = snd_pcm_substream_chip(substream);
250285
struct snd_pcm_runtime *runtime = substream->runtime;
251286
bcm2835_alsa_stream_t *alsa_stream = runtime->private_data;
287+
int channels;
288+
int err;
252289

253290
audio_info(" .. IN\n");
254291

292+
/* notify the vchiq that it should enter spdif passthrough mode by
293+
* setting channels=0 (see
294+
* https://github.com/raspberrypi/linux/issues/528) */
295+
if (chip->spdif_status & IEC958_AES0_NONAUDIO)
296+
channels = 0;
297+
else
298+
channels = alsa_stream->channels;
299+
300+
err = bcm2835_audio_set_params(alsa_stream, channels,
301+
alsa_stream->params_rate,
302+
alsa_stream->pcm_format_width);
303+
if (err < 0) {
304+
audio_error(" error setting hw params\n");
305+
}
306+
307+
bcm2835_audio_setup(alsa_stream);
308+
309+
/* in preparation of the stream, set the controls (volume level) of the stream */
310+
bcm2835_audio_set_ctls(alsa_stream->chip);
311+
312+
255313
memset(&alsa_stream->pcm_indirect, 0, sizeof(alsa_stream->pcm_indirect));
256314

257315
alsa_stream->pcm_indirect.hw_buffer_size =
@@ -392,6 +450,18 @@ static struct snd_pcm_ops snd_bcm2835_playback_ops = {
392450
.ack = snd_bcm2835_pcm_ack,
393451
};
394452

453+
static struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
454+
.open = snd_bcm2835_playback_spdif_open,
455+
.close = snd_bcm2835_playback_close,
456+
.ioctl = snd_bcm2835_pcm_lib_ioctl,
457+
.hw_params = snd_bcm2835_pcm_hw_params,
458+
.hw_free = snd_bcm2835_pcm_hw_free,
459+
.prepare = snd_bcm2835_pcm_prepare,
460+
.trigger = snd_bcm2835_pcm_trigger,
461+
.pointer = snd_bcm2835_pcm_pointer,
462+
.ack = snd_bcm2835_pcm_ack,
463+
};
464+
395465
/* create a pcm device */
396466
int __devinit snd_bcm2835_new_pcm(bcm2835_chip_t * chip)
397467
{
@@ -424,3 +494,25 @@ int __devinit snd_bcm2835_new_pcm(bcm2835_chip_t * chip)
424494

425495
return 0;
426496
}
497+
498+
int __devinit snd_bcm2835_new_spdif_pcm(bcm2835_chip_t * chip)
499+
{
500+
struct snd_pcm *pcm;
501+
int err;
502+
503+
err = snd_pcm_new(chip->card, "bcm2835 ALSA", 1, 1, 0, &pcm);
504+
if (err < 0)
505+
return err;
506+
507+
pcm->private_data = chip;
508+
strcpy(pcm->name, "bcm2835 IEC958/HDMI");
509+
chip->pcm_spdif = pcm;
510+
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
511+
&snd_bcm2835_playback_spdif_ops);
512+
513+
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
514+
snd_dma_continuous_data (GFP_KERNEL),
515+
64 * 1024, 64 * 1024);
516+
517+
return 0;
518+
}

0 commit comments

Comments
 (0)