Depending on the "Mio Mode" chosen, the master volume controls the widget volume/mute levels, providing userspace a unified vision of volume control (one control only).
The master volume is adapted to smartphones because : - it only handles 2 sub-controls (left and right) - the generic code is split apart from mio specific code - it abstracts to userland audiopath within the chip
Signed-off-by: Robert Jarzmik robert.jarzmik@free.fr --- sound/soc/pxa/Makefile | 2 +- sound/soc/pxa/mioa701_masterctrl.c | 243 ++++++++++++++++++++++++++++++++++++ sound/soc/pxa/mioa701_wm9713.c | 7 + 3 files changed, 251 insertions(+), 1 deletions(-) create mode 100644 sound/soc/pxa/mioa701_masterctrl.c
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 9a6d27c..f40949f 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -18,7 +18,7 @@ snd-soc-spitz-objs := spitz.o snd-soc-em-x270-objs := em-x270.o snd-soc-palm27x-objs := palm27x.o snd-soc-zylonite-objs := zylonite.o -snd-soc-mioa701-objs := mioa701_wm9713.o +snd-soc-mioa701-objs := mioa701_wm9713.o mioa701_masterctrl.o
obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o diff --git a/sound/soc/pxa/mioa701_masterctrl.c b/sound/soc/pxa/mioa701_masterctrl.c new file mode 100644 index 0000000..f95e1f7 --- /dev/null +++ b/sound/soc/pxa/mioa701_masterctrl.c @@ -0,0 +1,243 @@ +/* + * Handles the Mitac mioa701 Master Volume Control + * + * Copyright (C) 2008 Robert Jarzmik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include <linux/vmalloc.h> +#include <sound/core.h> +#include <sound/soc.h> + +#define MASTER_MAX_CTL 2 +#define MAST2CODEC(m) (((struct kctl_master_t *)m)->codec) +#define KCTL2MAST(k) ((struct kctl_master_t *) k->private_data) + +struct kctl_master_t { + unsigned int count; + snd_ctl_elem_type_t type; + char *names[MASTER_MAX_CTL]; + int nidx[MASTER_MAX_CTL]; + struct snd_soc_codec *codec; +}; + +#define MVOL2(name1, idx1, name2, idx2) \ +{ .count = 2, .names = { name1, name2 }, .nidx = { idx1, idx2 }, \ + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, \ +} +#define MMUTE2(name1, idx1, name2, idx2) \ +{ .count = 2, .names = { name1, name2 }, .nidx = { idx1, idx2 }, \ + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, \ +} +#define MVOL2_DEFAULT MVOL2(NULL, 0, NULL, 0) +#define MMUTE2_DEFAULT MMUTE2(NULL, 0, NULL, 0) + +static struct snd_kcontrol *kctrl_byname(struct snd_soc_codec *codec, char *n) +{ + struct snd_ctl_elem_id rid; + + memset(&rid, 0, sizeof(rid)); + rid.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strncpy(rid.name, n, sizeof(rid.name)); + return snd_ctl_find_id(codec->card, &rid); +} + +static int master_find(struct snd_soc_codec *codec, char *name, + int *max, struct snd_kcontrol **kctl) +{ + struct snd_ctl_elem_info info; + int rc; + + memset(&info, 0, sizeof(struct snd_ctl_elem_info)); + *kctl = NULL; + + if (name) + *kctl = kctrl_byname(codec, name); + + if (*kctl) { + (*kctl)->info(*kctl, &info); + *max = info.value.integer.max; + } + + rc = (*kctl != NULL); + return rc; +} + +static int master_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct kctl_master_t *master = KCTL2MAST(kcontrol); + + uinfo->type = master->type; + uinfo->count = master->count; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0; + if (master->type & SNDRV_CTL_ELEM_TYPE_BOOLEAN) + uinfo->value.integer.max = 1; + if (master->type & SNDRV_CTL_ELEM_TYPE_INTEGER) + uinfo->value.integer.max = 256; + return 0; +} + +static int master_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_kcontrol *kctl; + struct snd_ctl_elem_value uc; + struct kctl_master_t *master = KCTL2MAST(kcontrol); + struct snd_soc_codec *codec = MAST2CODEC(master); + int i, ind, max, old; + + for (i = 0; i < master->count; i++) { + max = old = 0; + ind = master->nidx[i]; + if (!master_find(codec, master->names[i], &max, &kctl)) + continue; + if (kctl->get(kctl, &uc) < 0) + continue; + old = uc.value.integer.value[ind]; + if (master->type == SNDRV_CTL_ELEM_TYPE_INTEGER) + old = old * 256 / (max+1); + ucontrol->value.integer.value[i] = old; + } + + return 0; +} + +static int master_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_kcontrol *kctl; + struct snd_ctl_elem_value uc; + struct kctl_master_t *master = KCTL2MAST(kcontrol); + struct snd_soc_codec *codec = MAST2CODEC(master); + + int i, ind, max, new; + + for (i = 0; i < master->count; i++) { + max = new = 0; + ind = master->nidx[i]; + if (!master_find(codec, master->names[i], &max, &kctl)) + continue; + new = ucontrol->value.integer.value[i]; + if (master->type == SNDRV_CTL_ELEM_TYPE_INTEGER) + new = new * (max+1) / 256; + + if (kctl->get(kctl, &uc) < 0) + continue; + + uc.value.integer.value[ind] = new; + if (kctl->put(kctl, &uc) < 0) + continue; + } + + return 0; +} + +/* + * Beware : masterconf must be of same type, and have same count as the old + * master + */ +void master_change(struct snd_kcontrol *k, struct kctl_master_t *masterconf) +{ + if (masterconf && k) + masterconf->codec = MAST2CODEC(k->private_data); + if (k) + k->private_data = masterconf; +} + +struct snd_kcontrol *master_init(struct snd_soc_codec *codec, char *mname, + struct kctl_master_t *master) +{ + struct snd_kcontrol_new *kctln = NULL; + struct snd_kcontrol *kctl = NULL; + int rc; + + kctln = kmalloc(sizeof(struct snd_kcontrol_new), GFP_KERNEL); + if (!kctln) + return NULL; + + memset(kctln, 0, sizeof(struct snd_kcontrol_new)); + kctln->iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kctln->name = mname; + kctln->info = master_info; + kctln->get = master_get; + kctln->put = master_put; + master->codec = codec; + + kctl = snd_soc_cnew(kctln, master, NULL); + + rc = snd_ctl_add(codec->card, kctl); + if (rc < 0) { + kfree(kctln); + kctl = NULL; + } + + return kctl; +} + +/* MIO Specific */ +static struct kctl_master_t miomastervol[] = { + MVOL2_DEFAULT, /* MIO_AUDIO_OFF */ + MVOL2("Headphone Playback Volume", 0, /* MIO_GSM_AUDIO_HANDSET */ + "Out3 Playback Volume", 0), + MVOL2("Headphone Playback Volume", 0, /* MIO_GSM_AUDIO_HEADSET */ + "Headphone Playback Volume", 1), + MVOL2("Speaker Playback Volume", 0, /* MIO_GSM_AUDIO_HANDSFREE */ + "Speaker Playback Volume", 1), + MVOL2_DEFAULT, /* MIO_GSM_AUDIO_BLUETOOTH */ + MVOL2("Speaker Playback Volume", 0, /* MIO_STEREO_TO_SPEAKER */ + "Speaker Playback Volume", 1), + MVOL2("Headphone Playback Volume", 0, /* MIO_STEREO_TO_HEADPHONES */ + "Headphone Playback Volume", 1), + MVOL2_DEFAULT, /* MIO_CAPTURE_HANDSET */ + MVOL2_DEFAULT, /* MIO_CAPTURE_HEADSET */ + MVOL2_DEFAULT, /* MIO_CAPTURE_BLUETOOTH */ +}; + +static struct kctl_master_t miomastermute[] = { + MMUTE2_DEFAULT, /* MIO_AUDIO_OFF */ + MMUTE2("Headphone Playback Switch", 0, /* MIO_GSM_AUDIO_HANDSET */ + "Out3 Playback Switch", 0), + MMUTE2("Headphone Playback Switch", 0, /* MIO_GSM_AUDIO_HEADSET */ + "Headphone Playback Switch", 1), + MMUTE2("Speaker Playback Switch", 0, /* MIO_GSM_AUDIO_HANDSFREE */ + "Speaker Playback Switch", 1), + MMUTE2_DEFAULT, /* MIO_GSM_AUDIO_BLUETOOTH */ + MMUTE2("Speaker Playback Switch", 0, /* MIO_STEREO_TO_SPEAKER */ + "Speaker Playback Switch", 1), + MMUTE2("Headphone Playback Switch", 0, /* MIO_STEREO_TO_HEADPHONES */ + "Headphone Playback Switch", 1), + MMUTE2_DEFAULT, /* MIO_CAPTURE_HANDSET */ + MMUTE2_DEFAULT, /* MIO_CAPTURE_HEADSET */ + MMUTE2_DEFAULT, /* MIO_CAPTURE_BLUETOOTH */ +}; + +static struct snd_kcontrol *miovol, *miomute; + +int mioa701_master_init(struct snd_soc_codec *codec) +{ + miovol = master_init(codec, "Mio Volume", &miomastervol[0]); + miomute = master_init(codec, "Mio Switch", &miomastermute[0]); + + return 0; +} + +void mioa701_master_change(int scenario) +{ + master_change(miovol, &miomastervol[scenario]); + master_change(miomute, &miomastermute[scenario]); +} diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c index dbf6799..0ffc0c1 100644 --- a/sound/soc/pxa/mioa701_wm9713.c +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -55,6 +55,8 @@
static int mio_scenario = MIO_AUDIO_OFF;
+extern int mioa701_master_init(struct snd_soc_codec *codec); +extern void mioa701_master_change(int scenario); static int phone_stream_start(struct snd_soc_codec *codec); static int phone_stream_stop(struct snd_soc_codec *codec);
@@ -320,6 +322,8 @@ static void switch_mio_mode(struct snd_soc_codec *codec, int new_scenario) default: break; } + + mioa701_master_change(mio_scenario); }
static int set_scenario(struct snd_kcontrol *kcontrol, @@ -463,6 +467,9 @@ static int mioa701_wm9713_init(struct snd_soc_codec *codec) /* initialize mioa701 codec pins */ set_scenario_endpoints(codec, mio_scenario);
+ /* Add mio Masters */ + mioa701_master_init(codec); + /* Prepare GPIO8 for rear speaker amplificator */ reg = codec->read(codec, AC97_GPIO_CFG); codec->write(codec, AC97_GPIO_CFG, reg | 0x0100);