-
Notifications
You must be signed in to change notification settings - Fork 5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: popcornmix <[email protected]> alsa: add mmap support and some cleanups to bcm2835 ALSA driver 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. snd-bcm2708: Add mutex, improve logging Fix for ALSA driver crash Avoids an issue when closing and opening vchiq where a message can arrive before service handle has been written alsa: reduce severity of expected warning message snd-bcm2708: Fix dmesg spam for non-error case alsa: Ensure mutexes are released through error paths alsa: Make interrupted close paths quieter BCM270x: Add onboard sound device to Device Tree Add Device Tree support to alsa driver. Add device to Device Tree. Don't add platform devices when booting in DT mode. Signed-off-by: Noralf Trønnes <[email protected]> bcm2835: access controls under the audio mutex I don't think the ALSA framework provides any kind of automatic synchronization within the control callbacks. We most likely need to ensure this manually, so add locking around all access to shared mutable data. In particular, bcm2835_audio_set_ctls() should probably always be called under our own audio lock.
- Loading branch information
1 parent
ae5d6e2
commit 3d286c4
Showing
8 changed files
with
2,620 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,350 @@ | ||
/***************************************************************************** | ||
* Copyright 2011 Broadcom Corporation. All rights reserved. | ||
* | ||
* Unless you and Broadcom execute a separate written software license | ||
* agreement governing use of this software, this software is licensed to you | ||
* under the terms of the GNU General Public License version 2, available at | ||
* http://www.broadcom.com/licenses/GPLv2.php (the "GPL"). | ||
* | ||
* Notwithstanding the above, under no circumstances may you combine this | ||
* software in any way with any other Broadcom software provided under a | ||
* license other than the GPL, without Broadcom's express prior written | ||
* consent. | ||
*****************************************************************************/ | ||
|
||
#include <linux/platform_device.h> | ||
#include <linux/init.h> | ||
#include <linux/io.h> | ||
#include <linux/jiffies.h> | ||
#include <linux/slab.h> | ||
#include <linux/time.h> | ||
#include <linux/wait.h> | ||
#include <linux/delay.h> | ||
#include <linux/moduleparam.h> | ||
#include <linux/sched.h> | ||
|
||
#include <sound/core.h> | ||
#include <sound/control.h> | ||
#include <sound/pcm.h> | ||
#include <sound/pcm_params.h> | ||
#include <sound/rawmidi.h> | ||
#include <sound/initval.h> | ||
#include <sound/tlv.h> | ||
#include <sound/asoundef.h> | ||
|
||
#include "bcm2835.h" | ||
|
||
/* volume maximum and minimum in terms of 0.01dB */ | ||
#define CTRL_VOL_MAX 400 | ||
#define CTRL_VOL_MIN -10239 /* originally -10240 */ | ||
|
||
|
||
static int snd_bcm2835_ctl_info(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_info *uinfo) | ||
{ | ||
audio_info(" ... IN\n"); | ||
if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) { | ||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
uinfo->count = 1; | ||
uinfo->value.integer.min = CTRL_VOL_MIN; | ||
uinfo->value.integer.max = CTRL_VOL_MAX; /* 2303 */ | ||
} else if (kcontrol->private_value == PCM_PLAYBACK_MUTE) { | ||
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; | ||
uinfo->count = 1; | ||
uinfo->value.integer.min = 0; | ||
uinfo->value.integer.max = 1; | ||
} else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE) { | ||
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
uinfo->count = 1; | ||
uinfo->value.integer.min = 0; | ||
uinfo->value.integer.max = AUDIO_DEST_MAX-1; | ||
} | ||
audio_info(" ... OUT\n"); | ||
return 0; | ||
} | ||
|
||
/* toggles mute on or off depending on the value of nmute, and returns | ||
* 1 if the mute value was changed, otherwise 0 | ||
*/ | ||
static int toggle_mute(struct bcm2835_chip *chip, int nmute) | ||
{ | ||
/* if settings are ok, just return 0 */ | ||
if(chip->mute == nmute) | ||
return 0; | ||
|
||
/* if the sound is muted then we need to unmute */ | ||
if(chip->mute == CTRL_VOL_MUTE) | ||
{ | ||
chip->volume = chip->old_volume; /* copy the old volume back */ | ||
audio_info("Unmuting, old_volume = %d, volume = %d ...\n", chip->old_volume, chip->volume); | ||
} | ||
else /* otherwise we mute */ | ||
{ | ||
chip->old_volume = chip->volume; | ||
chip->volume = 26214; /* set volume to minimum level AKA mute */ | ||
audio_info("Muting, old_volume = %d, volume = %d ...\n", chip->old_volume, chip->volume); | ||
} | ||
|
||
chip->mute = nmute; | ||
return 1; | ||
} | ||
|
||
static int snd_bcm2835_ctl_get(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_value *ucontrol) | ||
{ | ||
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); | ||
|
||
if (mutex_lock_interruptible(&chip->audio_mutex)) | ||
return -EINTR; | ||
|
||
BUG_ON(!chip && !(chip->avail_substreams & AVAIL_SUBSTREAMS_MASK)); | ||
|
||
if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) | ||
ucontrol->value.integer.value[0] = chip2alsa(chip->volume); | ||
else if (kcontrol->private_value == PCM_PLAYBACK_MUTE) | ||
ucontrol->value.integer.value[0] = chip->mute; | ||
else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE) | ||
ucontrol->value.integer.value[0] = chip->dest; | ||
|
||
mutex_unlock(&chip->audio_mutex); | ||
return 0; | ||
} | ||
|
||
static int snd_bcm2835_ctl_put(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_value *ucontrol) | ||
{ | ||
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); | ||
int changed = 0; | ||
|
||
if (mutex_lock_interruptible(&chip->audio_mutex)) | ||
return -EINTR; | ||
|
||
if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) { | ||
audio_info("Volume change attempted.. volume = %d new_volume = %d\n", chip->volume, (int)ucontrol->value.integer.value[0]); | ||
if (chip->mute == CTRL_VOL_MUTE) { | ||
/* changed = toggle_mute(chip, CTRL_VOL_UNMUTE); */ | ||
changed = 1; /* should return 0 to signify no change but the mixer takes this as the opposite sign (no idea why) */ | ||
goto unlock; | ||
} | ||
if (changed | ||
|| (ucontrol->value.integer.value[0] != chip2alsa(chip->volume))) { | ||
|
||
chip->volume = alsa2chip(ucontrol->value.integer.value[0]); | ||
changed = 1; | ||
} | ||
|
||
} else if (kcontrol->private_value == PCM_PLAYBACK_MUTE) { | ||
/* Now implemented */ | ||
audio_info(" Mute attempted\n"); | ||
changed = toggle_mute(chip, ucontrol->value.integer.value[0]); | ||
|
||
} else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE) { | ||
if (ucontrol->value.integer.value[0] != chip->dest) { | ||
chip->dest = ucontrol->value.integer.value[0]; | ||
changed = 1; | ||
} | ||
} | ||
|
||
if (changed) { | ||
if (bcm2835_audio_set_ctls(chip)) | ||
printk(KERN_ERR "Failed to set ALSA controls..\n"); | ||
} | ||
|
||
unlock: | ||
mutex_unlock(&chip->audio_mutex); | ||
return changed; | ||
} | ||
|
||
static DECLARE_TLV_DB_SCALE(snd_bcm2835_db_scale, CTRL_VOL_MIN, 1, 1); | ||
|
||
static struct snd_kcontrol_new snd_bcm2835_ctl[] = { | ||
{ | ||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
.name = "PCM Playback Volume", | ||
.index = 0, | ||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ, | ||
.private_value = PCM_PLAYBACK_VOLUME, | ||
.info = snd_bcm2835_ctl_info, | ||
.get = snd_bcm2835_ctl_get, | ||
.put = snd_bcm2835_ctl_put, | ||
.count = 1, | ||
.tlv = {.p = snd_bcm2835_db_scale} | ||
}, | ||
{ | ||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
.name = "PCM Playback Switch", | ||
.index = 0, | ||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||
.private_value = PCM_PLAYBACK_MUTE, | ||
.info = snd_bcm2835_ctl_info, | ||
.get = snd_bcm2835_ctl_get, | ||
.put = snd_bcm2835_ctl_put, | ||
.count = 1, | ||
}, | ||
{ | ||
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
.name = "PCM Playback Route", | ||
.index = 0, | ||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||
.private_value = PCM_PLAYBACK_DEVICE, | ||
.info = snd_bcm2835_ctl_info, | ||
.get = snd_bcm2835_ctl_get, | ||
.put = snd_bcm2835_ctl_put, | ||
.count = 1, | ||
}, | ||
}; | ||
|
||
static int snd_bcm2835_spdif_default_info(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_info *uinfo) | ||
{ | ||
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; | ||
uinfo->count = 1; | ||
return 0; | ||
} | ||
|
||
static int snd_bcm2835_spdif_default_get(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_value *ucontrol) | ||
{ | ||
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); | ||
int i; | ||
|
||
if (mutex_lock_interruptible(&chip->audio_mutex)) | ||
return -EINTR; | ||
|
||
for (i = 0; i < 4; i++) | ||
ucontrol->value.iec958.status[i] = | ||
(chip->spdif_status >> (i * 8)) && 0xff; | ||
|
||
mutex_unlock(&chip->audio_mutex); | ||
return 0; | ||
} | ||
|
||
static int snd_bcm2835_spdif_default_put(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_value *ucontrol) | ||
{ | ||
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); | ||
unsigned int val = 0; | ||
int i, change; | ||
|
||
if (mutex_lock_interruptible(&chip->audio_mutex)) | ||
return -EINTR; | ||
|
||
for (i = 0; i < 4; i++) | ||
val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8); | ||
|
||
change = val != chip->spdif_status; | ||
chip->spdif_status = val; | ||
|
||
mutex_unlock(&chip->audio_mutex); | ||
return change; | ||
} | ||
|
||
static int snd_bcm2835_spdif_mask_info(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_info *uinfo) | ||
{ | ||
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; | ||
uinfo->count = 1; | ||
return 0; | ||
} | ||
|
||
static int snd_bcm2835_spdif_mask_get(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_value *ucontrol) | ||
{ | ||
/* bcm2835 supports only consumer mode and sets all other format flags | ||
* automatically. So the only thing left is signalling non-audio | ||
* content */ | ||
ucontrol->value.iec958.status[0] = IEC958_AES0_NONAUDIO; | ||
return 0; | ||
} | ||
|
||
static int snd_bcm2835_spdif_stream_info(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_info *uinfo) | ||
{ | ||
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; | ||
uinfo->count = 1; | ||
return 0; | ||
} | ||
|
||
static int snd_bcm2835_spdif_stream_get(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_value *ucontrol) | ||
{ | ||
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); | ||
int i; | ||
|
||
if (mutex_lock_interruptible(&chip->audio_mutex)) | ||
return -EINTR; | ||
|
||
for (i = 0; i < 4; i++) | ||
ucontrol->value.iec958.status[i] = | ||
(chip->spdif_status >> (i * 8)) & 0xff; | ||
|
||
mutex_unlock(&chip->audio_mutex); | ||
return 0; | ||
} | ||
|
||
static int snd_bcm2835_spdif_stream_put(struct snd_kcontrol *kcontrol, | ||
struct snd_ctl_elem_value *ucontrol) | ||
{ | ||
struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); | ||
unsigned int val = 0; | ||
int i, change; | ||
|
||
if (mutex_lock_interruptible(&chip->audio_mutex)) | ||
return -EINTR; | ||
|
||
for (i = 0; i < 4; i++) | ||
val |= (unsigned int)ucontrol->value.iec958.status[i] << (i * 8); | ||
change = val != chip->spdif_status; | ||
chip->spdif_status = val; | ||
|
||
mutex_unlock(&chip->audio_mutex); | ||
return change; | ||
} | ||
|
||
static struct snd_kcontrol_new snd_bcm2835_spdif[] = { | ||
{ | ||
.iface = SNDRV_CTL_ELEM_IFACE_PCM, | ||
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), | ||
.info = snd_bcm2835_spdif_default_info, | ||
.get = snd_bcm2835_spdif_default_get, | ||
.put = snd_bcm2835_spdif_default_put | ||
}, | ||
{ | ||
.access = SNDRV_CTL_ELEM_ACCESS_READ, | ||
.iface = SNDRV_CTL_ELEM_IFACE_PCM, | ||
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), | ||
.info = snd_bcm2835_spdif_mask_info, | ||
.get = snd_bcm2835_spdif_mask_get, | ||
}, | ||
{ | ||
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | | ||
SNDRV_CTL_ELEM_ACCESS_INACTIVE, | ||
.iface = SNDRV_CTL_ELEM_IFACE_PCM, | ||
.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), | ||
.info = snd_bcm2835_spdif_stream_info, | ||
.get = snd_bcm2835_spdif_stream_get, | ||
.put = snd_bcm2835_spdif_stream_put, | ||
}, | ||
}; | ||
|
||
int snd_bcm2835_new_ctl(bcm2835_chip_t * chip) | ||
{ | ||
int err; | ||
unsigned int idx; | ||
|
||
strcpy(chip->card->mixername, "Broadcom Mixer"); | ||
for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_ctl); idx++) { | ||
err = | ||
snd_ctl_add(chip->card, | ||
snd_ctl_new1(&snd_bcm2835_ctl[idx], chip)); | ||
if (err < 0) | ||
return err; | ||
} | ||
for (idx = 0; idx < ARRAY_SIZE(snd_bcm2835_spdif); idx++) { | ||
err = snd_ctl_add(chip->card, | ||
snd_ctl_new1(&snd_bcm2835_spdif[idx], chip)); | ||
if (err < 0) | ||
return err; | ||
} | ||
return 0; | ||
} |
Oops, something went wrong.