// SPDX-License-Identifier: GPL-2.0 /* Copyright 2011 Broadcom Corporation. All rights reserved. */ #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 bcm2835_audio_set_chip_ctls(struct bcm2835_chip *chip) { int i, err = 0; /* change ctls for all substreams */ for (i = 0; i < MAX_SUBSTREAMS; i++) { if (chip->alsa_stream[i]) { err = bcm2835_audio_set_ctls(chip->alsa_stream[i]); if (err < 0) break; } } return err; } static int snd_bcm2835_ctl_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { 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; } return 0; } static int snd_bcm2835_ctl_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct bcm2835_chip *chip = snd_kcontrol_chip(kcontrol); mutex_lock(&chip->audio_mutex); if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) ucontrol->value.integer.value[0] = 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 val, *valp; int changed = 0; if (kcontrol->private_value == PCM_PLAYBACK_VOLUME) valp = &chip->volume; else if (kcontrol->private_value == PCM_PLAYBACK_MUTE) valp = &chip->mute; else if (kcontrol->private_value == PCM_PLAYBACK_DEVICE) valp = &chip->dest; else return -EINVAL; val = ucontrol->value.integer.value[0]; mutex_lock(&chip->audio_mutex); if (val != *valp) { *valp = val; changed = 1; if (bcm2835_audio_set_chip_ctls(chip)) dev_err(chip->card->dev, "Failed to set ALSA controls..\n"); } mutex_unlock(&chip->audio_mutex); return changed; } static DECLARE_TLV_DB_SCALE(snd_bcm2835_db_scale, CTRL_VOL_MIN, 1, 1); static const struct snd_kcontrol_new snd_bcm2835_ctl[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Volume", .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, .tlv = {.p = snd_bcm2835_db_scale} }, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "PCM Playback Switch", .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, }, }; 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; mutex_lock(&chip->audio_mutex); 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; mutex_lock(&chip->audio_mutex); 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 const 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, }, }; static int create_ctls(struct bcm2835_chip *chip, size_t size, const struct snd_kcontrol_new *kctls) { int i, err; for (i = 0; i < size; i++) { err = snd_ctl_add(chip->card, snd_ctl_new1(&kctls[i], chip)); if (err < 0) return err; } return 0; } int snd_bcm2835_new_headphones_ctl(struct bcm2835_chip *chip) { strscpy(chip->card->mixername, "Broadcom Mixer", sizeof(chip->card->mixername)); return create_ctls(chip, ARRAY_SIZE(snd_bcm2835_ctl), snd_bcm2835_ctl); } int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip) { int err; strscpy(chip->card->mixername, "Broadcom Mixer", sizeof(chip->card->mixername)); err = create_ctls(chip, ARRAY_SIZE(snd_bcm2835_ctl), snd_bcm2835_ctl); if (err < 0) return err; return create_ctls(chip, ARRAY_SIZE(snd_bcm2835_spdif), snd_bcm2835_spdif); }