/***************************************************************************** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(struct bcm2835_chip *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; }