[alsa-devel] [PATCH] Add support for Mitac Mio A701 smartphone
This patch serie aims at integration of the Mitac Mio A701 smartphone's sound system into alsa sound system.
It should be noted that this serie applies upon asoc-v2-dev tree, commit id e02f9491e1e6d9720e0caffdb8e5edc259f41946, and should not hit alsa tree before 2.6.27, because A701 official support will be included at that version through arm tree (perhaps even 2.6.28, depending on Russell's time).
Happy review.
-- Robert
This machine driver enables sound functions on Mitac mio a701 smartphone. Build upon ASoC v2, it handles : - rear speaker - front speaker - microphone - GSM
A global "Mio Mode" switch is provided to cope with audio path setup. It ensures balance on audio chip line, which if not respected can produce a lot of heat and even fry the battery behind the wm9713 and the speaker amplificator.
It doesn't cope with : - headset jack (will be integrade after jack support has hit ASoC v2) - master volume control (depending on current Mio Mode, will be submitted in a second patch)
Signed-off-by: Robert Jarzmik robert.jarzmik@free.fr --- sound/soc/pxa/Kconfig | 9 + sound/soc/pxa/Makefile | 2 + sound/soc/pxa/mioa701_wm9713.c | 639 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 650 insertions(+), 0 deletions(-) create mode 100644 sound/soc/pxa/mioa701_wm9713.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 7530227..ca60dec 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -139,3 +139,12 @@ config SND_PXA2XX_SOC_E800 help Say Y if you want to add support for SoC audio on the Toshiba e800 PDA + +config SND_PXA2XX_SOC_MIOA701 + tristate "SoC Audio support for MIO A701" + depends on SND_PXA2XX_SOC + select SND_PXA2XX_SOC_AC97 + select SND_SOC_WM9713 + help + Say Y if you want to add support for SoC audio on the + MIO A701. diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index fd45a09..ce171c3 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -21,6 +21,7 @@ snd-soc-spitz-objs := spitz.o snd-soc-amesom-tlv320-objs := amesom_tlv320.o snd-soc-magician-objs := magician.o snd-soc-h5000-objs := h5000.o +snd-soc-mioa701-objs := mioa701_wm9713.o snd-soc-mainstone-wm8753-objs := mainstone_wm8753.o snd-soc-mainstone-wm9713-objs := mainstone_wm9713.o snd-soc-mainstone-wm9712-objs := mainstone_wm9712.o @@ -34,6 +35,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o obj-$(CONFIG_SND_PXA2XX_SOC_AMESOM_TLV320) += snd-soc-amesom-tlv320.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_H5000) += snd-soc-h5000.o +obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM8753) += snd-soc-mainstone-wm8753.o obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM9713) += snd-soc-mainstone-wm9713.o obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM9712) += snd-soc-mainstone-wm9712.o diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c new file mode 100644 index 0000000..4df42e0 --- /dev/null +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -0,0 +1,639 @@ +/* + * Handles the Mitac mioa701 SoC system + * + * 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/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/ac97_codec.h> + +#include <asm/arch/audio.h> + +#include "pxa2xx-pcm.h" +#include "../codecs/wm9713.h" + +#define ARRAY_AND_SIZE(x) (x), ARRAY_SIZE(x) + +#define AC97_GPIO_PULL 0x58 + +/* define the scenarios */ +#define MIO_AUDIO_OFF 0 +#define MIO_GSM_AUDIO_HANDSET 1 +#define MIO_GSM_AUDIO_HEADSET 2 +#define MIO_GSM_AUDIO_HANDSFREE 3 +#define MIO_GSM_AUDIO_BLUETOOTH 4 +#define MIO_STEREO_TO_SPEAKER 5 +#define MIO_STEREO_TO_HEADPHONES 6 +#define MIO_CAPTURE_HANDSET 7 +#define MIO_CAPTURE_HEADSET 8 +#define MIO_CAPTURE_BLUETOOTH 9 + +static int mio_scenario = MIO_AUDIO_OFF; + +static int phone_stream_start(struct snd_soc_card *card); +static int phone_stream_stop(struct snd_soc_card *card); + +struct mio_mixes_t { + char *mixname; + int val; +}; + +static const struct mio_mixes_t mixes_reset_all[] = { + /* left HP mixer */ + {"Left HP Mixer PC Beep Playback Switch", 0}, + {"Left HP Mixer Voice Playback Switch", 0}, + {"Left HP Mixer Aux Playback Switch", 0}, + {"Left HP Mixer Bypass Playback Switch", 0}, + {"Left HP Mixer PCM Playback Switch", 0}, + {"Left HP Mixer MonoIn Playback Switch", 0}, + {"Left HP Mixer Capture Headphone Mux", 0}, + + /* right HP mixer */ + {"Right HP Mixer PC Beep Playback Switch", 0}, + {"Right HP Mixer Voice Playback Switch", 0}, + {"Right HP Mixer Aux Playback Switch", 0}, + {"Right HP Mixer Bypass Playback Switch", 0}, + {"Right HP Mixer PCM Playback Switch", 0}, + {"Right HP Mixer MonoIn Playback Switch", 0}, + {"Right HP Mixer Capture Headphone Mux", 0}, + + /* speaker mixer */ + {"Speaker Mixer PC Beep Playback Switch", 0}, + {"Speaker Mixer Voice Playback Switch", 0}, + {"Speaker Mixer Aux Playback Switch", 0}, + {"Speaker Mixer Bypass Playback Switch", 0}, + {"Speaker Mixer PCM Playback Switch", 0}, + {"Speaker Mixer MonoIn Playback Switch", 0}, + {"Speaker Mixer Mic 1 Sidetone Switch", 0}, + + /* mono mixer */ + {"Mono Mixer PC Beep Playback Switch", 0}, + {"Mono Mixer Voice Playback Switch", 0}, + {"Mono Mixer Aux Playback Switch", 0}, + {"Mono Mixer Bypass Playback Switch", 0}, + {"Mono Mixer PCM Playback Switch", 0}, + {"Mono Mixer Capture Mono Mux", 0}, + {"Mono Mixer MonoIn Playback Switch", 0}, + {"Mono Mixer Mic 1 Sidetone Switch", 0}, + {"Mono Playback Switch", 0}, + + /* headphone muxers */ + {"Left Headphone Out Mux", 0}, + {"Right Headphone Out Mux", 0}, + + /* speaker muxer */ + {"Left Speaker Out Mux", 0}, + {"Right Speaker Out Mux", 0}, + + /* Out3 muxer */ + { "Out 3 Mux", 0}, + + { NULL, 0 } +}; + +static const struct mio_mixes_t mixes_gsm_call_headset[] = { + /* + * GSM Out to Headset HPL Path + * => PCBeep -> Headphone Mixer, Headphone Mixer -> HPL + */ + { "Left HP Mixer PC Beep Playback Switch", 1 }, + { "Left Headphone Out Mux", 2 }, + /* + * GSM Out to Headset HPR Path + * => MonoIn -> Headphone Mixer, Headphone Mixer -> HPR + */ + { "Right HP Mixer MonoIn Playback Switch" , 1 }, + { "Right Headphone Out Mux", 2 }, + /* + * LineL to GSM In + * LineL -> MonoMixer, MonoMixer -> Mono, Unmute Mono Mixer + */ + { "Mono Mixer Bypass Playback Switch", 1}, + { "Mono Out Mux", 2 }, + { "Mono Playback Switch", 1}, + { NULL, 0 } +}; + +static const struct mio_mixes_t mixes_gsm_call_handset[] = { + /* + * GSM Out to Front Speaker HPL Path + * => PCBeep -> Headphone Mixer, Headphone Mixer -> HPL + */ + { "Left HP Mixer PC Beep Playback Switch", 1 }, + { "Left Headphone Out Mux", 2 }, + /* + * GSM Out to Front Speaker Out3 Path + * => MonoIn -> Speaker Mixer, Speaker Mixer -> Inv1, Inv1 -> Out3 + */ + { "Speaker Mixer MonoIn Playback Switch" , 1 }, + { "DAC Inv Mux 1", 2 }, + { "Out 3 Mux", 2 }, + /* + * MIC1 to GSM In + * => MIC1 -> MICA, MICA -> Mono Mixer, Mono Mixer -> MONO, + * UnMute Mono Mixer + */ + { "Mic A Source", 0 }, + { "Mono Mixer Mic 1 Sidetone Switch", 1 }, + { "Mono Out Mux", 2 }, + { "Mono Playback Switch", 1}, + { NULL, 0 } +}; + +static const struct mio_mixes_t mixes_gsm_call_handsfree[] = { + /* + * GSM Out to Rear Speaker SPKL Path + * => PCBeep -> Speaker Mixer, Speaker Mixer -> Inv1, Inv1 -> SPKL + */ + { "Speaker Mixer PC Beep Playback Switch", 1 }, + { "DAC Inv Mux 1", 2 }, + { "Left Speaker Out Mux", 4 }, + /* + * GSM Out to Rear Speaker SPKR Path + * => MonoIn -> Speaker Mixer, Speaker Mixer -> SPKR + */ + { "Speaker Mixer MonoIn Playback Switch" , 1 }, + { "Right Speaker Out Mux", 3 }, + /* + * MIC1 to GSM In + * => MIC1 -> MICA, MICA -> Mono Mixer, Mono Mixer -> MONO, + * Unmute Mono Mixer + */ + { "Mic A Source", 0 }, + { "Mono Mixer Mic 1 Sidetone Switch", 1 }, + { "Mono Out Mux", 2 }, + { "Mono Playback Switch", 1}, + { NULL, 0 } +}; + +static const struct mio_mixes_t mixes_stereo_to_rearspeaker[] = { + /* + * PCM to Rear Speakers + * => PCM -> Speaker Mixer, Speaker Mixer -> Inv1, Inv1 -> SPKL, + * Speaker Mixer -> SPKR + */ + { "Speaker Mixer PCM Playback Switch", 1}, + { "DAC Inv Mux 1", 2 }, + { "Left Speaker Out Mux", 4 }, + { "Right Speaker Out Mux", 3 }, + { NULL, 0 } +}; + +struct snd_kcontrol *mioa701_kctrl_byname(struct snd_soc_card *card, 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(card->card, &rid); +} + +void setup_muxers(struct snd_soc_card *card, const struct mio_mixes_t mixes[]) +{ + int pos = 0; + struct snd_kcontrol *kctl; + struct snd_ctl_elem_value ucontrol; + char mname[44]; + + while (mixes[pos].mixname) { + memset(mname, 0, 44); + strncpy(mname, mixes[pos].mixname, 43); + kctl = mioa701_kctrl_byname(card, mname); + memset(&ucontrol, 0, sizeof(ucontrol)); + if (kctl) { + kctl->get(kctl, &ucontrol); + ucontrol.value.enumerated.item[0] = mixes[pos].val; + kctl->put(kctl, &ucontrol); + } + pos++; + } +} + +#define NB_ENDP ARRAY_SIZE(endpn) +static int set_scenario_endpoints(struct snd_soc_card *card, int scenario) +{ + static char *endpn[] = { "Front Speaker", "Rear Speaker", + "GSM Line Out", "GSM Line In", + "Headset Mic", "Front Mic", "Headset" }; + static const int typ_endps[][NB_ENDP] = { + { 0, 0, 0, 0, 0, 0, 0 }, /* MIO_AUDIO_OFF */ + { 1, 0, 1, 1, 0, 1, 0 }, /* MIO_GSM_AUDIO_HANDSET */ + { 0, 0, 1, 1, 1, 0, 1 }, /* MIO_GSM_AUDIO_HEADSET */ + { 0, 1, 1, 1, 0, 1, 0 }, /* MIO_GSM_AUDIO_HANDSFREE*/ + { 0, 0, 1, 1, 0, 0, 0 }, /* MIO_GSM_AUDIO_BLUETOOTH*/ + { 0, 1, 0, 0, 0, 0, 0 }, /* MIO_STEREO_TO_SPEAKER */ + { 0, 0, 0, 0, 0, 0, 1 }, /* MIO_STEREO_TO_HEADPHONES */ + { 0, 0, 0, 0, 0, 1, 0 }, /* MIO_CAPTURE_HANDSET */ + { 0, 0, 0, 0, 1, 0, 0 }, /* MIO_CAPTURE_HEADSET */ + { 0, 0, 0, 0, 0, 0, 0 }, /* MIO_CAPTURE_BLUETOOTH */ + }; + const int *endps = typ_endps[scenario]; + int i; + + for (i = 0; i < NB_ENDP; i++) + if (endps[i]) + snd_soc_dapm_enable_pin(card, endpn[i]); + else + snd_soc_dapm_disable_pin(card, endpn[i]); + snd_soc_dapm_sync(card); + + return 0; +} + +static int get_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = mio_scenario; + return 0; +} + +static int isPhoneMode(int scenario) +{ + int onPhone = 0; + + switch (scenario) { + case MIO_GSM_AUDIO_HANDSET: + case MIO_GSM_AUDIO_HEADSET: + case MIO_GSM_AUDIO_BLUETOOTH: + case MIO_GSM_AUDIO_HANDSFREE: + onPhone = 1; + } + + return onPhone; +} + +static void switch_mio_mode(struct snd_soc_card *card, int new_scenario) +{ + int wasPhone = 0, willPhone = 0; + + wasPhone = isPhoneMode(mio_scenario); + willPhone = isPhoneMode(new_scenario); + + mio_scenario = new_scenario; + set_scenario_endpoints(card, mio_scenario); + + if (!wasPhone && willPhone) + phone_stream_start(card); + if (wasPhone && !willPhone) + phone_stream_stop(card); + + setup_muxers(card, mixes_reset_all); + switch (mio_scenario) { + case MIO_STEREO_TO_SPEAKER: + setup_muxers(card, mixes_stereo_to_rearspeaker); + break; + case MIO_GSM_AUDIO_HANDSET: + setup_muxers(card, mixes_gsm_call_handset); + break; + case MIO_GSM_AUDIO_HANDSFREE: + setup_muxers(card, mixes_gsm_call_handsfree); + break; + case MIO_GSM_AUDIO_HEADSET: + setup_muxers(card, mixes_gsm_call_headset); + break; + default: + break; + } +} + +static int set_scenario(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + + if (mio_scenario == ucontrol->value.integer.value[0]) + return 0; + + switch_mio_mode(card, ucontrol->value.integer.value[0]); + return 1; +} + +static int phone_stream_start(struct snd_soc_card *card) +{ + snd_soc_dapm_stream_event(card, "AC97 HiFi", + SND_SOC_DAPM_STREAM_START); + return 0; +} + +static int phone_stream_stop(struct snd_soc_card *card) +{ + snd_soc_dapm_stream_event(card, "AC97 HiFi", + SND_SOC_DAPM_STREAM_STOP); + return 0; +} + +/* Use GPIO8 for rear speaker amplificator */ +static int rear_amp_power(struct snd_soc_codec *codec, int power) +{ + unsigned short reg; + + if (power) { + reg = snd_soc_read(codec, AC97_GPIO_CFG); + snd_soc_write(codec, AC97_GPIO_CFG, reg | 0x0100); + reg = snd_soc_read(codec, AC97_GPIO_PULL); + snd_soc_write(codec, AC97_GPIO_PULL, reg | (1<<15)); + } else { + reg = snd_soc_read(codec, AC97_GPIO_CFG); + snd_soc_write(codec, AC97_GPIO_CFG, reg & ~0x0100); + reg = snd_soc_read(codec, AC97_GPIO_PULL); + snd_soc_write(codec, AC97_GPIO_PULL, reg & ~(1<<15)); + } + + return 0; +} + +static int rear_amp_event(struct snd_soc_dapm_widget *widget, + struct snd_kcontrol *kctl, int event) +{ + struct snd_soc_codec *codec = widget->codec; + int rc; + + if (SND_SOC_DAPM_EVENT_ON(event)) + rc = rear_amp_power(codec, 1); + else + rc = rear_amp_power(codec, 0); + + return rc; +} + +static const char *mio_scenarios[] = { + "Off", + "GSM Handset", + "GSM Headset", + "GSM Handsfree", + "GSM Bluetooth", + "PCM Speaker", + "Headphones", + "Capture Handset", + "Capture Headset", + "Capture Bluetooth" +}; +static const struct soc_enum mio_scenario_enum[] = { + SOC_ENUM_SINGLE_EXT(10, mio_scenarios), +}; + +static const struct snd_kcontrol_new mioa701_controls[] = { + SOC_ENUM_EXT("Mio Mode", mio_scenario_enum[0], + get_scenario, set_scenario), +}; + +/* mioa701 machine dapm widgets */ +static const struct snd_soc_dapm_widget mioa701_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Front Speaker", NULL), + SND_SOC_DAPM_SPK("Rear Speaker", rear_amp_event), + SND_SOC_DAPM_MIC("Headset", NULL), + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Front Mic", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Call Mic */ + {"Mic Bias", NULL, "Front Mic"}, + {"MIC1", NULL, "Mic Bias"}, + + /* Headset Mic */ + {"LINEL", NULL, "Headset Mic"}, + {"LINER", NULL, "Headset Mic"}, + + /* GSM Module */ + {"MONOIN", NULL, "GSM Line Out"}, + {"PCBEEP", NULL, "GSM Line Out"}, + {"GSM Line In", NULL, "MONO"}, + + /* headphone connected to HPL, HPR */ + {"Headset", NULL, "HPL"}, + {"Headset", NULL, "HPR"}, + + /* front speaker connected to HPL, OUT3 */ + {"Front Speaker", NULL, "HPL"}, + {"Front Speaker", NULL, "OUT3"}, + + /* rear speaker connected to SPKL, SPKR */ + {"Rear Speaker", NULL, "SPKL"}, + {"Rear Speaker", NULL, "SPKR"}, +}; + +static int mioa701_wm9713_init(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec; + struct snd_ac97_bus_ops *ac97_ops; + int i, ret; + unsigned short reg; + + codec = snd_soc_card_get_codec(card, wm9713_codec_id, 0); + if (codec == NULL) + return -ENODEV; + + ac97_ops = snd_soc_card_get_ac97_ops(card, pxa_ac97_hifi_dai_id); + if (!ac97_ops) { + printk(KERN_ERR "Unable to obtain AC97 operations\n"); + return -ENODEV; + } + + /* register with AC97 bus for ad-hoc driver access */ + ret = snd_soc_new_ac97_codec(codec, ac97_ops, card->card, 0, 0); + if (ret < 0) + return ret; + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + ac97_ops->reset(codec->ac97); + ac97_ops->warm_reset(codec->ac97); + if (ac97_ops->read(codec->ac97, AC97_VENDOR_ID1) == 0) { + printk(KERN_ERR "AC97 link error\n"); + return ret; + } + + snd_soc_card_init_codec(codec, card); + + /* initialize mioa701 codec pins */ + set_scenario_endpoints(card, mio_scenario); + + /* Add mioa701 specific controls */ + for (i = 0; i < ARRAY_SIZE(mioa701_controls); i++) { + ret = snd_ctl_add(card->card, snd_soc_cnew(&mioa701_controls[i], + card, NULL)); + if (ret) + goto out; + } + + /* Add mioa701 specific widgets */ + ret = snd_soc_dapm_new_controls(card, codec, + ARRAY_AND_SIZE(mioa701_dapm_widgets)); + if (ret) + goto out; + + /* Set up mioa701 specific audio path audio_mapnects */ + ret = snd_soc_dapm_add_routes(card, ARRAY_AND_SIZE(audio_map)); + if (ret) + goto out; + + /* Prepare MIC input */ + reg = snd_soc_read(codec, AC97_3D_CONTROL); + snd_soc_write(codec, AC97_3D_CONTROL, reg | 0xc000); + + snd_soc_dapm_sync(card); + + return 0; +out: + return ret; +} + +static void mioa701_wm9713_exit(struct snd_soc_card *card) +{ +} + +#ifdef CONFIG_PM +static int mioa701_wm9713_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + + codec = snd_soc_card_get_codec(card, wm9713_codec_id, 0); + if (codec == NULL) + return -ENODEV; + + rear_amp_power(codec, 0); + return 0; /* snd_soc_card_suspend_pcms(card, state) doesn't work */ +} + +static int mioa701_wm9713_resume(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + + codec = snd_soc_card_get_codec(card, wm9713_codec_id, 0); + if (codec == NULL) + return -ENODEV; + + rear_amp_power(codec, 0); + return 0; /* snd_soc_card_resume_pcms(card) doesn't work */ +} + +#else +#define mioa701_wm9713_suspend NULL +#define mioa701_wm9713_resume NULL +#endif + +static struct snd_soc_ops mioa701_hifi_ops = { +}; + +static struct snd_soc_ops mioa701_aux_ops = { +}; + +static struct snd_soc_pcm_config pcm_configs[] = { + { + .name = "Aux", + .codec = wm9713_codec_id, + .codec_dai = wm9713_codec_aux_dai_id, + .platform = pxa_platform_id, + .cpu_dai = pxa_ac97_aux_dai_id, + .playback = 1, + .ops = &mioa701_aux_ops, + }, + { + .name = "HiFi", + .codec = wm9713_codec_id, + .codec_dai = wm9713_codec_hifi_dai_id, + .platform = pxa_platform_id, + .cpu_dai = pxa_ac97_hifi_dai_id, + .playback = 1, + .capture = 1, + .ops = &mioa701_hifi_ops, + }, +}; + +static int mioa701_wm9713_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + int ret; + + card = snd_soc_card_create("mioa701", &pdev->dev, + SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (card == NULL) + return -ENOMEM; + + card->longname = "WM9713"; + card->init = mioa701_wm9713_init; + card->exit = mioa701_wm9713_exit; + card->private_data = pdev; + platform_set_drvdata(pdev, card); + + ret = snd_soc_card_create_pcms(card, ARRAY_AND_SIZE(pcm_configs)); + if (ret < 0) + goto errpcms; + + ret = snd_soc_card_register(card); + if (ret < 0) + goto errcard; + return ret; + +errpcms: + snd_soc_card_free(card); +errcard: + return ret; +} + +static int __devexit mioa701_wm9713_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + + snd_soc_card_free(card); + return 0; +} + +static struct platform_driver mioa701_wm9713_driver = { + .probe = mioa701_wm9713_probe, + .remove = __devexit_p(mioa701_wm9713_remove), + .suspend = mioa701_wm9713_suspend, + .resume = mioa701_wm9713_resume, + .driver = { + .name = "mioa701-wm9713", + .owner = THIS_MODULE, + }, +}; + +static int __init mioa701_asoc_init(void) +{ + return platform_driver_register(&mioa701_wm9713_driver); +} + +static void __exit mioa701_asoc_exit(void) +{ + platform_driver_unregister(&mioa701_wm9713_driver); +} + +module_init(mioa701_asoc_init); +module_exit(mioa701_asoc_exit); + +/* Module information */ +MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)"); +MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701"); +MODULE_LICENSE("GPL");
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 ce171c3..53134ba 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -21,7 +21,7 @@ snd-soc-spitz-objs := spitz.o snd-soc-amesom-tlv320-objs := amesom_tlv320.o snd-soc-magician-objs := magician.o snd-soc-h5000-objs := h5000.o -snd-soc-mioa701-objs := mioa701_wm9713.o +snd-soc-mioa701-objs := mioa701_wm9713.o mioa701_masterctrl.o snd-soc-mainstone-wm8753-objs := mainstone_wm8753.o snd-soc-mainstone-wm9713-objs := mainstone_wm9713.o snd-soc-mainstone-wm9712-objs := mainstone_wm9712.o diff --git a/sound/soc/pxa/mioa701_masterctrl.c b/sound/soc/pxa/mioa701_masterctrl.c new file mode 100644 index 0000000..92055c8 --- /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 MAST2CARD(m) (((struct kctl_master_t *)m)->card) +#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_card *card; +}; + +#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_card *card, 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(card->card, &rid); +} + +static int master_find(struct snd_soc_card *card, 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(card, 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_card *card = MAST2CARD(master); + int i, ind, max, old; + + for (i = 0; i < master->count; i++) { + max = old = 0; + ind = master->nidx[i]; + if (!master_find(card, 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_card *card = MAST2CARD(master); + + int i, ind, max, new; + + for (i = 0; i < master->count; i++) { + max = new = 0; + ind = master->nidx[i]; + if (!master_find(card, 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->card = MAST2CARD(k->private_data); + if (k) + k->private_data = masterconf; +} + +struct snd_kcontrol *master_init(struct snd_soc_card *card, 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->card = card; + + kctl = snd_soc_cnew(kctln, master, NULL); + + rc = snd_ctl_add(card->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_card *card) +{ + miovol = master_init(card, "Mio Volume", &miomastervol[0]); + miomute = master_init(card, "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 4df42e0..f063023 100644 --- a/sound/soc/pxa/mioa701_wm9713.c +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -53,6 +53,8 @@
static int mio_scenario = MIO_AUDIO_OFF;
+extern int mioa701_master_init(struct snd_soc_card *card); +extern void mioa701_master_change(int scenario); static int phone_stream_start(struct snd_soc_card *card); static int phone_stream_stop(struct snd_soc_card *card);
@@ -318,6 +320,8 @@ static void switch_mio_mode(struct snd_soc_card *card, int new_scenario) default: break; } + + mioa701_master_change(mio_scenario); }
static int set_scenario(struct snd_kcontrol *kcontrol, @@ -483,6 +487,9 @@ static int mioa701_wm9713_init(struct snd_soc_card *card) goto out; }
+ /* Add mio Masters */ + mioa701_master_init(card); + /* Add mioa701 specific widgets */ ret = snd_soc_dapm_new_controls(card, codec, ARRAY_AND_SIZE(mioa701_dapm_widgets));
On Tue, 2008-07-08 at 22:45 +0200, Robert Jarzmik wrote:
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 userspace manager does the same thing here. I prefer the userspace approach for the long term instead of this being handled differently by each driver. It's in git atm though.
Thanks
Liam
Privacy & Confidentiality Notice ------------------------------------------------- This message and any attachments contain privileged and confidential information that is intended solely for the person(s) to whom it is addressed. If you are not an intended recipient you must not: read; copy; distribute; discuss; take any action in or make any reliance upon the contents of this message; nor open or read any attachment. If you have received this message in error, please notify us as soon as possible on the following telephone number and destroy this message including any attachments. Thank you. ------------------------------------------------- Wolfson Microelectronics plc Tel: +44 (0)131 272 7000 Fax: +44 (0)131 272 7001 Web: www.wolfsonmicro.com
Registered in Scotland
Company number SC089839
Registered office:
Westfield House, 26 Westfield Road, Edinburgh, EH11 2QB, UK
Liam Girdwood liam.girdwood@wolfsonmicro.com writes:
On Tue, 2008-07-08 at 22:45 +0200, Robert Jarzmik wrote:
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 userspace manager does the same thing here. I prefer the userspace approach for the long term instead of this being handled differently by each driver. It's in git atm though.
OK. So drop the patch. If the same functionnality is already available, I'll see if I can use it. May I know how stable that API is, and who actually uses it ?
-- Robert
On Wed, 2008-07-09 at 20:00 +0200, Robert Jarzmik wrote:
Liam Girdwood liam.girdwood@wolfsonmicro.com writes:
On Tue, 2008-07-08 at 22:45 +0200, Robert Jarzmik wrote:
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 userspace manager does the same thing here. I prefer the userspace approach for the long term instead of this being handled differently by each driver. It's in git atm though.
OK. So drop the patch. If the same functionnality is already available, I'll see if I can use it. May I know how stable that API is, and who actually uses it ?
I'm not going to drop the patch yet as it works for you :)
The API is very new and is in need of more users (i.e. more than just me atm). Openmoko folks have expressed interest and will hopefully start using this in the future. The overall aim would be to integrate this into alsa-lib and salsa-lib when it's mature.
Liam
On Tue, 2008-07-08 at 22:45 +0200, Robert Jarzmik wrote:
This machine driver enables sound functions on Mitac mio a701 smartphone. Build upon ASoC v2, it handles :
- rear speaker
- front speaker
- microphone
- GSM
I've committed both to git now. We can upstream when the dependencies are committed. Just some minor comments below.
A global "Mio Mode" switch is provided to cope with audio path setup. It ensures balance on audio chip line, which if not respected can produce a lot of heat and even fry the battery behind the wm9713 and the speaker amplificator.
Fwiw, there is a thermal IRQ on the WM9713 and you may want to use it for monitoring this. However it only interrupts at 150C so it may be a little high for this purpose.
It doesn't cope with :
- headset jack (will be integrade after jack support has hit ASoC v2)
This is still pending atm.
- master volume control (depending on current Mio Mode, will be submitted in a second patch)
If it depends on mode then have a look at the scenario/use case/mode manager for ALSA :-
http://opensource.wolfsonmicro.com/node/14
This is something I'd like to see used more as it does define a consistent interface to userspace.
Signed-off-by: Robert Jarzmik robert.jarzmik@free.fr
sound/soc/pxa/Kconfig | 9 + sound/soc/pxa/Makefile | 2 + sound/soc/pxa/mioa701_wm9713.c | 639 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 650 insertions(+), 0 deletions(-) create mode 100644 sound/soc/pxa/mioa701_wm9713.c
diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index 7530227..ca60dec 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -139,3 +139,12 @@ config SND_PXA2XX_SOC_E800 help Say Y if you want to add support for SoC audio on the Toshiba e800 PDA
+config SND_PXA2XX_SOC_MIOA701
tristate "SoC Audio support for MIO A701"
depends on SND_PXA2XX_SOC
select SND_PXA2XX_SOC_AC97
select SND_SOC_WM9713
help
Say Y if you want to add support for SoC audio on the
MIO A701.
diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index fd45a09..ce171c3 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -21,6 +21,7 @@ snd-soc-spitz-objs := spitz.o snd-soc-amesom-tlv320-objs := amesom_tlv320.o snd-soc-magician-objs := magician.o snd-soc-h5000-objs := h5000.o +snd-soc-mioa701-objs := mioa701_wm9713.o snd-soc-mainstone-wm8753-objs := mainstone_wm8753.o snd-soc-mainstone-wm9713-objs := mainstone_wm9713.o snd-soc-mainstone-wm9712-objs := mainstone_wm9712.o @@ -34,6 +35,7 @@ obj-$(CONFIG_SND_PXA2XX_SOC_SPITZ) += snd-soc-spitz.o obj-$(CONFIG_SND_PXA2XX_SOC_AMESOM_TLV320) += snd-soc-amesom-tlv320.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_H5000) += snd-soc-h5000.o +obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM8753) += snd-soc-mainstone-wm8753.o obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM9713) += snd-soc-mainstone-wm9713.o obj-$(CONFIG_SND_PXA2XX_SOC_MAINSTONE_WM9712) += snd-soc-mainstone-wm9712.o diff --git a/sound/soc/pxa/mioa701_wm9713.c b/sound/soc/pxa/mioa701_wm9713.c new file mode 100644 index 0000000..4df42e0 --- /dev/null +++ b/sound/soc/pxa/mioa701_wm9713.c @@ -0,0 +1,639 @@ +/*
- Handles the Mitac mioa701 SoC system
- 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/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h>
+#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/ac97_codec.h>
+#include <asm/arch/audio.h>
+#include "pxa2xx-pcm.h" +#include "../codecs/wm9713.h"
+#define ARRAY_AND_SIZE(x) (x), ARRAY_SIZE(x)
+#define AC97_GPIO_PULL 0x58
+/* define the scenarios */ +#define MIO_AUDIO_OFF 0 +#define MIO_GSM_AUDIO_HANDSET 1 +#define MIO_GSM_AUDIO_HEADSET 2 +#define MIO_GSM_AUDIO_HANDSFREE 3 +#define MIO_GSM_AUDIO_BLUETOOTH 4 +#define MIO_STEREO_TO_SPEAKER 5 +#define MIO_STEREO_TO_HEADPHONES 6 +#define MIO_CAPTURE_HANDSET 7 +#define MIO_CAPTURE_HEADSET 8 +#define MIO_CAPTURE_BLUETOOTH 9
+static int mio_scenario = MIO_AUDIO_OFF;
+static int phone_stream_start(struct snd_soc_card *card); +static int phone_stream_stop(struct snd_soc_card *card);
+struct mio_mixes_t {
- char *mixname;
- int val;
+};
+static const struct mio_mixes_t mixes_reset_all[] = {
- /* left HP mixer */
- {"Left HP Mixer PC Beep Playback Switch", 0},
- {"Left HP Mixer Voice Playback Switch", 0},
- {"Left HP Mixer Aux Playback Switch", 0},
- {"Left HP Mixer Bypass Playback Switch", 0},
- {"Left HP Mixer PCM Playback Switch", 0},
- {"Left HP Mixer MonoIn Playback Switch", 0},
- {"Left HP Mixer Capture Headphone Mux", 0},
- /* right HP mixer */
- {"Right HP Mixer PC Beep Playback Switch", 0},
- {"Right HP Mixer Voice Playback Switch", 0},
- {"Right HP Mixer Aux Playback Switch", 0},
- {"Right HP Mixer Bypass Playback Switch", 0},
- {"Right HP Mixer PCM Playback Switch", 0},
- {"Right HP Mixer MonoIn Playback Switch", 0},
- {"Right HP Mixer Capture Headphone Mux", 0},
- /* speaker mixer */
- {"Speaker Mixer PC Beep Playback Switch", 0},
- {"Speaker Mixer Voice Playback Switch", 0},
- {"Speaker Mixer Aux Playback Switch", 0},
- {"Speaker Mixer Bypass Playback Switch", 0},
- {"Speaker Mixer PCM Playback Switch", 0},
- {"Speaker Mixer MonoIn Playback Switch", 0},
- {"Speaker Mixer Mic 1 Sidetone Switch", 0},
- /* mono mixer */
- {"Mono Mixer PC Beep Playback Switch", 0},
- {"Mono Mixer Voice Playback Switch", 0},
- {"Mono Mixer Aux Playback Switch", 0},
- {"Mono Mixer Bypass Playback Switch", 0},
- {"Mono Mixer PCM Playback Switch", 0},
- {"Mono Mixer Capture Mono Mux", 0},
- {"Mono Mixer MonoIn Playback Switch", 0},
- {"Mono Mixer Mic 1 Sidetone Switch", 0},
- {"Mono Playback Switch", 0},
- /* headphone muxers */
- {"Left Headphone Out Mux", 0},
- {"Right Headphone Out Mux", 0},
- /* speaker muxer */
- {"Left Speaker Out Mux", 0},
- {"Right Speaker Out Mux", 0},
- /* Out3 muxer */
- { "Out 3 Mux", 0},
- { NULL, 0 }
+};
+static const struct mio_mixes_t mixes_gsm_call_headset[] = {
- /*
* GSM Out to Headset HPL Path
* => PCBeep -> Headphone Mixer, Headphone Mixer -> HPL
*/
- { "Left HP Mixer PC Beep Playback Switch", 1 },
- { "Left Headphone Out Mux", 2 },
- /*
* GSM Out to Headset HPR Path
* => MonoIn -> Headphone Mixer, Headphone Mixer -> HPR
*/
- { "Right HP Mixer MonoIn Playback Switch" , 1 },
- { "Right Headphone Out Mux", 2 },
- /*
* LineL to GSM In
* LineL -> MonoMixer, MonoMixer -> Mono, Unmute Mono Mixer
*/
- { "Mono Mixer Bypass Playback Switch", 1},
- { "Mono Out Mux", 2 },
- { "Mono Playback Switch", 1},
- { NULL, 0 }
+};
+static const struct mio_mixes_t mixes_gsm_call_handset[] = {
- /*
* GSM Out to Front Speaker HPL Path
* => PCBeep -> Headphone Mixer, Headphone Mixer -> HPL
*/
- { "Left HP Mixer PC Beep Playback Switch", 1 },
- { "Left Headphone Out Mux", 2 },
- /*
* GSM Out to Front Speaker Out3 Path
* => MonoIn -> Speaker Mixer, Speaker Mixer -> Inv1, Inv1 -> Out3
*/
- { "Speaker Mixer MonoIn Playback Switch" , 1 },
- { "DAC Inv Mux 1", 2 },
- { "Out 3 Mux", 2 },
- /*
* MIC1 to GSM In
* => MIC1 -> MICA, MICA -> Mono Mixer, Mono Mixer -> MONO,
* UnMute Mono Mixer
*/
- { "Mic A Source", 0 },
- { "Mono Mixer Mic 1 Sidetone Switch", 1 },
- { "Mono Out Mux", 2 },
- { "Mono Playback Switch", 1},
- { NULL, 0 }
+};
+static const struct mio_mixes_t mixes_gsm_call_handsfree[] = {
- /*
* GSM Out to Rear Speaker SPKL Path
* => PCBeep -> Speaker Mixer, Speaker Mixer -> Inv1, Inv1 -> SPKL
*/
- { "Speaker Mixer PC Beep Playback Switch", 1 },
- { "DAC Inv Mux 1", 2 },
- { "Left Speaker Out Mux", 4 },
- /*
* GSM Out to Rear Speaker SPKR Path
* => MonoIn -> Speaker Mixer, Speaker Mixer -> SPKR
*/
- { "Speaker Mixer MonoIn Playback Switch" , 1 },
- { "Right Speaker Out Mux", 3 },
- /*
* MIC1 to GSM In
* => MIC1 -> MICA, MICA -> Mono Mixer, Mono Mixer -> MONO,
* Unmute Mono Mixer
*/
- { "Mic A Source", 0 },
- { "Mono Mixer Mic 1 Sidetone Switch", 1 },
- { "Mono Out Mux", 2 },
- { "Mono Playback Switch", 1},
- { NULL, 0 }
+};
+static const struct mio_mixes_t mixes_stereo_to_rearspeaker[] = {
- /*
* PCM to Rear Speakers
* => PCM -> Speaker Mixer, Speaker Mixer -> Inv1, Inv1 -> SPKL,
* Speaker Mixer -> SPKR
*/
- { "Speaker Mixer PCM Playback Switch", 1},
- { "DAC Inv Mux 1", 2 },
- { "Left Speaker Out Mux", 4 },
- { "Right Speaker Out Mux", 3 },
- { NULL, 0 }
+};
+struct snd_kcontrol *mioa701_kctrl_byname(struct snd_soc_card *card, 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(card->card, &rid);
+}
+void setup_muxers(struct snd_soc_card *card, const struct mio_mixes_t mixes[]) +{
- int pos = 0;
- struct snd_kcontrol *kctl;
- struct snd_ctl_elem_value ucontrol;
- char mname[44];
- while (mixes[pos].mixname) {
memset(mname, 0, 44);
strncpy(mname, mixes[pos].mixname, 43);
kctl = mioa701_kctrl_byname(card, mname);
memset(&ucontrol, 0, sizeof(ucontrol));
if (kctl) {
kctl->get(kctl, &ucontrol);
ucontrol.value.enumerated.item[0] = mixes[pos].val;
kctl->put(kctl, &ucontrol);
}
pos++;
- }
+}
+#define NB_ENDP ARRAY_SIZE(endpn) +static int set_scenario_endpoints(struct snd_soc_card *card, int scenario) +{
- static char *endpn[] = { "Front Speaker", "Rear Speaker",
"GSM Line Out", "GSM Line In",
"Headset Mic", "Front Mic", "Headset" };
- static const int typ_endps[][NB_ENDP] = {
{ 0, 0, 0, 0, 0, 0, 0 }, /* MIO_AUDIO_OFF */
{ 1, 0, 1, 1, 0, 1, 0 }, /* MIO_GSM_AUDIO_HANDSET */
{ 0, 0, 1, 1, 1, 0, 1 }, /* MIO_GSM_AUDIO_HEADSET */
{ 0, 1, 1, 1, 0, 1, 0 }, /* MIO_GSM_AUDIO_HANDSFREE*/
{ 0, 0, 1, 1, 0, 0, 0 }, /* MIO_GSM_AUDIO_BLUETOOTH*/
{ 0, 1, 0, 0, 0, 0, 0 }, /* MIO_STEREO_TO_SPEAKER */
{ 0, 0, 0, 0, 0, 0, 1 }, /* MIO_STEREO_TO_HEADPHONES */
{ 0, 0, 0, 0, 0, 1, 0 }, /* MIO_CAPTURE_HANDSET */
{ 0, 0, 0, 0, 1, 0, 0 }, /* MIO_CAPTURE_HEADSET */
{ 0, 0, 0, 0, 0, 0, 0 }, /* MIO_CAPTURE_BLUETOOTH */
- };
- const int *endps = typ_endps[scenario];
- int i;
- for (i = 0; i < NB_ENDP; i++)
if (endps[i])
snd_soc_dapm_enable_pin(card, endpn[i]);
else
snd_soc_dapm_disable_pin(card, endpn[i]);
- snd_soc_dapm_sync(card);
- return 0;
+}
+static int get_scenario(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- ucontrol->value.integer.value[0] = mio_scenario;
- return 0;
+}
+static int isPhoneMode(int scenario) +{
- int onPhone = 0;
- switch (scenario) {
- case MIO_GSM_AUDIO_HANDSET:
- case MIO_GSM_AUDIO_HEADSET:
- case MIO_GSM_AUDIO_BLUETOOTH:
- case MIO_GSM_AUDIO_HANDSFREE:
onPhone = 1;
- }
- return onPhone;
+}
+static void switch_mio_mode(struct snd_soc_card *card, int new_scenario) +{
- int wasPhone = 0, willPhone = 0;
- wasPhone = isPhoneMode(mio_scenario);
- willPhone = isPhoneMode(new_scenario);
- mio_scenario = new_scenario;
- set_scenario_endpoints(card, mio_scenario);
- if (!wasPhone && willPhone)
phone_stream_start(card);
- if (wasPhone && !willPhone)
phone_stream_stop(card);
- setup_muxers(card, mixes_reset_all);
- switch (mio_scenario) {
- case MIO_STEREO_TO_SPEAKER:
setup_muxers(card, mixes_stereo_to_rearspeaker);
break;
- case MIO_GSM_AUDIO_HANDSET:
setup_muxers(card, mixes_gsm_call_handset);
break;
- case MIO_GSM_AUDIO_HANDSFREE:
setup_muxers(card, mixes_gsm_call_handsfree);
break;
- case MIO_GSM_AUDIO_HEADSET:
setup_muxers(card, mixes_gsm_call_headset);
break;
- default:
break;
- }
+}
+static int set_scenario(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+{
- struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
- if (mio_scenario == ucontrol->value.integer.value[0])
return 0;
- switch_mio_mode(card, ucontrol->value.integer.value[0]);
- return 1;
+}
+static int phone_stream_start(struct snd_soc_card *card) +{
- snd_soc_dapm_stream_event(card, "AC97 HiFi",
SND_SOC_DAPM_STREAM_START);
- return 0;
+}
+static int phone_stream_stop(struct snd_soc_card *card) +{
- snd_soc_dapm_stream_event(card, "AC97 HiFi",
SND_SOC_DAPM_STREAM_STOP);
- return 0;
+}
+/* Use GPIO8 for rear speaker amplificator */ +static int rear_amp_power(struct snd_soc_codec *codec, int power) +{
- unsigned short reg;
- if (power) {
reg = snd_soc_read(codec, AC97_GPIO_CFG);
snd_soc_write(codec, AC97_GPIO_CFG, reg | 0x0100);
reg = snd_soc_read(codec, AC97_GPIO_PULL);
snd_soc_write(codec, AC97_GPIO_PULL, reg | (1<<15));
- } else {
reg = snd_soc_read(codec, AC97_GPIO_CFG);
snd_soc_write(codec, AC97_GPIO_CFG, reg & ~0x0100);
reg = snd_soc_read(codec, AC97_GPIO_PULL);
snd_soc_write(codec, AC97_GPIO_PULL, reg & ~(1<<15));
- }
- return 0;
+}
+static int rear_amp_event(struct snd_soc_dapm_widget *widget,
struct snd_kcontrol *kctl, int event)
+{
- struct snd_soc_codec *codec = widget->codec;
- int rc;
- if (SND_SOC_DAPM_EVENT_ON(event))
rc = rear_amp_power(codec, 1);
- else
rc = rear_amp_power(codec, 0);
This could be shortened to :-
return rear_amp_power(codec, SND_SOC_DAPM_EVENT_ON(event));
- return rc;
+}
+static const char *mio_scenarios[] = {
- "Off",
- "GSM Handset",
- "GSM Headset",
- "GSM Handsfree",
- "GSM Bluetooth",
- "PCM Speaker",
- "Headphones",
- "Capture Handset",
- "Capture Headset",
- "Capture Bluetooth"
+}; +static const struct soc_enum mio_scenario_enum[] = {
- SOC_ENUM_SINGLE_EXT(10, mio_scenarios),
+};
+static const struct snd_kcontrol_new mioa701_controls[] = {
- SOC_ENUM_EXT("Mio Mode", mio_scenario_enum[0],
get_scenario, set_scenario),
+};
+/* mioa701 machine dapm widgets */ +static const struct snd_soc_dapm_widget mioa701_dapm_widgets[] = {
- SND_SOC_DAPM_SPK("Front Speaker", NULL),
- SND_SOC_DAPM_SPK("Rear Speaker", rear_amp_event),
- SND_SOC_DAPM_MIC("Headset", NULL),
- SND_SOC_DAPM_LINE("GSM Line Out", NULL),
- SND_SOC_DAPM_LINE("GSM Line In", NULL),
- SND_SOC_DAPM_MIC("Headset Mic", NULL),
- SND_SOC_DAPM_MIC("Front Mic", NULL),
+};
+static const struct snd_soc_dapm_route audio_map[] = {
- /* Call Mic */
- {"Mic Bias", NULL, "Front Mic"},
- {"MIC1", NULL, "Mic Bias"},
- /* Headset Mic */
- {"LINEL", NULL, "Headset Mic"},
- {"LINER", NULL, "Headset Mic"},
- /* GSM Module */
- {"MONOIN", NULL, "GSM Line Out"},
- {"PCBEEP", NULL, "GSM Line Out"},
- {"GSM Line In", NULL, "MONO"},
- /* headphone connected to HPL, HPR */
- {"Headset", NULL, "HPL"},
- {"Headset", NULL, "HPR"},
- /* front speaker connected to HPL, OUT3 */
- {"Front Speaker", NULL, "HPL"},
- {"Front Speaker", NULL, "OUT3"},
- /* rear speaker connected to SPKL, SPKR */
- {"Rear Speaker", NULL, "SPKL"},
- {"Rear Speaker", NULL, "SPKR"},
+};
+static int mioa701_wm9713_init(struct snd_soc_card *card) +{
- struct snd_soc_codec *codec;
- struct snd_ac97_bus_ops *ac97_ops;
- int i, ret;
- unsigned short reg;
- codec = snd_soc_card_get_codec(card, wm9713_codec_id, 0);
- if (codec == NULL)
return -ENODEV;
- ac97_ops = snd_soc_card_get_ac97_ops(card, pxa_ac97_hifi_dai_id);
- if (!ac97_ops) {
printk(KERN_ERR "Unable to obtain AC97 operations\n");
return -ENODEV;
- }
- /* register with AC97 bus for ad-hoc driver access */
- ret = snd_soc_new_ac97_codec(codec, ac97_ops, card->card, 0, 0);
- if (ret < 0)
return ret;
- /* do a cold reset for the controller and then try
* a warm reset followed by an optional cold reset for codec */
- ac97_ops->reset(codec->ac97);
- ac97_ops->warm_reset(codec->ac97);
- if (ac97_ops->read(codec->ac97, AC97_VENDOR_ID1) == 0) {
printk(KERN_ERR "AC97 link error\n");
return ret;
- }
- snd_soc_card_init_codec(codec, card);
- /* initialize mioa701 codec pins */
- set_scenario_endpoints(card, mio_scenario);
- /* Add mioa701 specific controls */
- for (i = 0; i < ARRAY_SIZE(mioa701_controls); i++) {
ret = snd_ctl_add(card->card, snd_soc_cnew(&mioa701_controls[i],
card, NULL));
if (ret)
goto out;
- }
- /* Add mioa701 specific widgets */
- ret = snd_soc_dapm_new_controls(card, codec,
ARRAY_AND_SIZE(mioa701_dapm_widgets));
- if (ret)
goto out;
- /* Set up mioa701 specific audio path audio_mapnects */
- ret = snd_soc_dapm_add_routes(card, ARRAY_AND_SIZE(audio_map));
- if (ret)
goto out;
- /* Prepare MIC input */
- reg = snd_soc_read(codec, AC97_3D_CONTROL);
- snd_soc_write(codec, AC97_3D_CONTROL, reg | 0xc000);
- snd_soc_dapm_sync(card);
- return 0;
+out:
- return ret;
+}
+static void mioa701_wm9713_exit(struct snd_soc_card *card) +{
I assume this will never be called in real life phone usage. If it is you could optionally shutdown/slow down the AC97 link to save power.
+}
+#ifdef CONFIG_PM +static int mioa701_wm9713_suspend(struct platform_device *pdev,
pm_message_t state)
+{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec;
- codec = snd_soc_card_get_codec(card, wm9713_codec_id, 0);
- if (codec == NULL)
return -ENODEV;
- rear_amp_power(codec, 0);
- return 0; /* snd_soc_card_suspend_pcms(card, state) doesn't work */
+}
+static int mioa701_wm9713_resume(struct platform_device *pdev) +{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec;
- codec = snd_soc_card_get_codec(card, wm9713_codec_id, 0);
- if (codec == NULL)
return -ENODEV;
- rear_amp_power(codec, 0);
- return 0; /* snd_soc_card_resume_pcms(card) doesn't work */
+}
+#else +#define mioa701_wm9713_suspend NULL +#define mioa701_wm9713_resume NULL +#endif
+static struct snd_soc_ops mioa701_hifi_ops = { +};
+static struct snd_soc_ops mioa701_aux_ops = { +};
+static struct snd_soc_pcm_config pcm_configs[] = {
- {
.name = "Aux",
.codec = wm9713_codec_id,
.codec_dai = wm9713_codec_aux_dai_id,
.platform = pxa_platform_id,
.cpu_dai = pxa_ac97_aux_dai_id,
.playback = 1,
.ops = &mioa701_aux_ops,
- },
- {
.name = "HiFi",
.codec = wm9713_codec_id,
.codec_dai = wm9713_codec_hifi_dai_id,
.platform = pxa_platform_id,
.cpu_dai = pxa_ac97_hifi_dai_id,
.playback = 1,
.capture = 1,
.ops = &mioa701_hifi_ops,
- },
+};
+static int mioa701_wm9713_probe(struct platform_device *pdev) +{
- struct snd_soc_card *card;
- int ret;
- card = snd_soc_card_create("mioa701", &pdev->dev,
SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
- if (card == NULL)
return -ENOMEM;
- card->longname = "WM9713";
- card->init = mioa701_wm9713_init;
- card->exit = mioa701_wm9713_exit;
- card->private_data = pdev;
- platform_set_drvdata(pdev, card);
- ret = snd_soc_card_create_pcms(card, ARRAY_AND_SIZE(pcm_configs));
- if (ret < 0)
goto errpcms;
- ret = snd_soc_card_register(card);
- if (ret < 0)
goto errcard;
- return ret;
+errpcms:
- snd_soc_card_free(card);
+errcard:
- return ret;
+}
+static int __devexit mioa701_wm9713_remove(struct platform_device *pdev) +{
- struct snd_soc_card *card = platform_get_drvdata(pdev);
- snd_soc_card_free(card);
- return 0;
+}
+static struct platform_driver mioa701_wm9713_driver = {
- .probe = mioa701_wm9713_probe,
- .remove = __devexit_p(mioa701_wm9713_remove),
- .suspend = mioa701_wm9713_suspend,
- .resume = mioa701_wm9713_resume,
- .driver = {
.name = "mioa701-wm9713",
.owner = THIS_MODULE,
- },
+};
+static int __init mioa701_asoc_init(void) +{
- return platform_driver_register(&mioa701_wm9713_driver);
+}
+static void __exit mioa701_asoc_exit(void) +{
- platform_driver_unregister(&mioa701_wm9713_driver);
+}
+module_init(mioa701_asoc_init); +module_exit(mioa701_asoc_exit);
+/* Module information */ +MODULE_AUTHOR("Robert Jarzmik (rjarzmik@free.fr)"); +MODULE_DESCRIPTION("ALSA SoC WM9713 MIO A701"); +MODULE_LICENSE("GPL");
Thanks
Liam
Privacy & Confidentiality Notice ------------------------------------------------- This message and any attachments contain privileged and confidential information that is intended solely for the person(s) to whom it is addressed. If you are not an intended recipient you must not: read; copy; distribute; discuss; take any action in or make any reliance upon the contents of this message; nor open or read any attachment. If you have received this message in error, please notify us as soon as possible on the following telephone number and destroy this message including any attachments. Thank you. ------------------------------------------------- Wolfson Microelectronics plc Tel: +44 (0)131 272 7000 Fax: +44 (0)131 272 7001 Web: www.wolfsonmicro.com
Registered in Scotland
Company number SC089839
Registered office:
Westfield House, 26 Westfield Road, Edinburgh, EH11 2QB, UK
Liam Girdwood liam.girdwood@wolfsonmicro.com writes:
I've committed both to git now. We can upstream when the dependencies are committed. Just some minor comments below.
Ah. So, how should I provide the corrections : - with another full patch ? - a differential patch to the previous one ?
Fwiw, there is a thermal IRQ on the WM9713 and you may want to use it for monitoring this. However it only interrupts at 150C so it may be a little high for this purpose.
Well, I compared the temperature to my frying pan. The pan is hotter, so I assume the heat dissipated raises the temperature to about 70 degrees Celsius.
This is still pending atm.
No problem, I'm very patient.
If it depends on mode then have a look at the scenario/use case/mode manager for ALSA :-
OK. Will look.
This could be shortened to :-
return rear_amp_power(codec, SND_SOC_DAPM_EVENT_ON(event));
Absolutely.
+static void mioa701_wm9713_exit(struct snd_soc_card *card) +{
I assume this will never be called in real life phone usage. If it is you could optionally shutdown/slow down the AC97 link to save power.
Yes, it's never called, unless build as module, and then it makes sense. I'll post an update for this.
-- Robert
Liam Girdwood liam.girdwood@wolfsonmicro.com writes:
+static void mioa701_wm9713_exit(struct snd_soc_card *card) +{
I assume this will never be called in real life phone usage. If it is you could optionally shutdown/slow down the AC97 link to save power.
After a second thought, I don't think I can. The AC97 link is needed to for the touchscreen and battery level calculation (ADC1). So I don't think we can shutdown the link.
Perhaps we could slow it down, but is there a way to _not_ disturb wm97xx_touchscreen drivers ?
-- Robert
On Wed, Jul 09, 2008 at 09:36:41PM +0200, Robert Jarzmik wrote:
After a second thought, I don't think I can. The AC97 link is needed to for the touchscreen and battery level calculation (ADC1). So I don't think we can shutdown the link.
Have you checked that this currently works? The AC97 bus should be unregistered when the audio device is unregistered. I have to confess that I haven't tested this in v2 but it is the intended behaviour - the AC97 controller is provided by the ASoC drivers so when it is stopped the link should be unregistered.
Mark Brown broonie@opensource.wolfsonmicro.com writes:
On Wed, Jul 09, 2008 at 09:36:41PM +0200, Robert Jarzmik wrote:
After a second thought, I don't think I can. The AC97 link is needed to for the touchscreen and battery level calculation (ADC1). So I don't think we can shutdown the link.
Have you checked that this currently works? The AC97 bus should be unregistered when the audio device is unregistered. I have to confess that I haven't tested this in v2 but it is the intended behaviour - the AC97 controller is provided by the ASoC drivers so when it is stopped the link should be unregistered.
No. And yes, just checked. You're right, once snd-soc-mioa701 is removed, touchscreen and battery don't exist anymore (input device gone, etc ...). So I could/will shutdown the link.
-- Robert
On Tue, Jul 08, 2008 at 10:45:05PM +0200, Robert Jarzmik wrote:
It doesn't cope with :
- headset jack (will be integrade after jack support has hit ASoC v2)
My intention is to provide some additional ASoC wrappers for the generic jack functionality with DAPM integration allowing pin status to be managed automatically based on the jack status. I'm waiting for the generic support to be merged before starting on this.
participants (4)
-
Liam Girdwood
-
Liam Girdwood
-
Mark Brown
-
Robert Jarzmik