diff options
Diffstat (limited to 'sound/soc/omap')
-rw-r--r-- | sound/soc/omap/Kconfig | 23 | ||||
-rw-r--r-- | sound/soc/omap/Makefile | 6 | ||||
-rw-r--r-- | sound/soc/omap/ams-delta.c | 646 | ||||
-rw-r--r-- | sound/soc/omap/n810.c | 7 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcbsp.c | 53 | ||||
-rw-r--r-- | sound/soc/omap/omap-pcm.c | 59 | ||||
-rw-r--r-- | sound/soc/omap/omap2evm.c | 2 | ||||
-rw-r--r-- | sound/soc/omap/omap3beagle.c | 28 | ||||
-rw-r--r-- | sound/soc/omap/omap3evm.c | 147 | ||||
-rw-r--r-- | sound/soc/omap/omap3pandora.c | 4 | ||||
-rw-r--r-- | sound/soc/omap/overo.c | 2 | ||||
-rw-r--r-- | sound/soc/omap/sdp3430.c | 103 | ||||
-rw-r--r-- | sound/soc/omap/zoom2.c | 314 |
13 files changed, 1333 insertions, 61 deletions
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 675732e724d5..2dee9839be86 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -15,6 +15,14 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810. +config SND_OMAP_SOC_AMS_DELTA + tristate "SoC Audio support for Amstrad E3 (Delta) videophone" + depends on SND_OMAP_SOC && MACH_AMS_DELTA + select SND_OMAP_SOC_MCBSP + select SND_SOC_CX20442 + help + Say Y if you want to add support for SoC audio on Amstrad Delta. + config SND_OMAP_SOC_OSK5912 tristate "SoC Audio support for omap osk5912" depends on SND_OMAP_SOC && MACH_OMAP_OSK && I2C @@ -39,6 +47,14 @@ config SND_OMAP_SOC_OMAP2EVM help Say Y if you want to add support for SoC audio on the omap2evm board. +config SND_OMAP_SOC_OMAP3EVM + tristate "SoC Audio support for OMAP3EVM board" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3EVM + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for SoC audio on the omap3evm board. + config SND_OMAP_SOC_SDP3430 tristate "SoC Audio support for Texas Instruments SDP3430" depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_3430SDP @@ -64,4 +80,11 @@ config SND_OMAP_SOC_OMAP3_BEAGLE help Say Y if you want to add support for SoC audio on the Beagleboard. +config SND_OMAP_SOC_ZOOM2 + tristate "SoC Audio support for Zoom2" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_ZOOM2 + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for Soc audio on Zoom2 board. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 0c9e4ac37660..02d69471dcb5 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -7,17 +7,23 @@ obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o # OMAP Machine Support snd-soc-n810-objs := n810.o +snd-soc-ams-delta-objs := ams-delta.o snd-soc-osk5912-objs := osk5912.o snd-soc-overo-objs := overo.o snd-soc-omap2evm-objs := omap2evm.o +snd-soc-omap3evm-objs := omap3evm.o snd-soc-sdp3430-objs := sdp3430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o +snd-soc-zoom2-objs := zoom2.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o obj-$(CONFIG_MACH_OMAP2EVM) += snd-soc-omap2evm.o +obj-$(CONFIG_MACH_OMAP3EVM) += snd-soc-omap3evm.o obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o +obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c new file mode 100644 index 000000000000..5a5166ac7279 --- /dev/null +++ b/sound/soc/omap/ams-delta.c @@ -0,0 +1,646 @@ +/* + * ams-delta.c -- SoC audio for Amstrad E3 (Delta) videophone + * + * Copyright (C) 2009 Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> + * + * Initially based on sound/soc/omap/osk5912.x + * Copyright (C) 2008 Mistral Solutions + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/gpio.h> +#include <linux/spinlock.h> +#include <linux/tty.h> + +#include <sound/soc-dapm.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> + +#include <mach/board-ams-delta.h> +#include <mach/mcbsp.h> + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/cx20442.h" + + +/* Board specific DAPM widgets */ + const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = { + /* Handset */ + SND_SOC_DAPM_MIC("Mouthpiece", NULL), + SND_SOC_DAPM_HP("Earpiece", NULL), + /* Handsfree/Speakerphone */ + SND_SOC_DAPM_MIC("Microphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +/* How they are connected to codec pins */ +static const struct snd_soc_dapm_route ams_delta_audio_map[] = { + {"TELIN", NULL, "Mouthpiece"}, + {"Earpiece", NULL, "TELOUT"}, + + {"MIC", NULL, "Microphone"}, + {"Speaker", NULL, "SPKOUT"}, +}; + +/* + * Controls, functional after the modem line discipline is activated. + */ + +/* Virtual switch: audio input/output constellations */ +static const char *ams_delta_audio_mode[] = + {"Mixed", "Handset", "Handsfree", "Speakerphone"}; + +/* Selection <-> pin translation */ +#define AMS_DELTA_MOUTHPIECE 0 +#define AMS_DELTA_EARPIECE 1 +#define AMS_DELTA_MICROPHONE 2 +#define AMS_DELTA_SPEAKER 3 +#define AMS_DELTA_AGC 4 + +#define AMS_DELTA_MIXED ((1 << AMS_DELTA_EARPIECE) | \ + (1 << AMS_DELTA_MICROPHONE)) +#define AMS_DELTA_HANDSET ((1 << AMS_DELTA_MOUTHPIECE) | \ + (1 << AMS_DELTA_EARPIECE)) +#define AMS_DELTA_HANDSFREE ((1 << AMS_DELTA_MICROPHONE) | \ + (1 << AMS_DELTA_SPEAKER)) +#define AMS_DELTA_SPEAKERPHONE (AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC)) + +unsigned short ams_delta_audio_mode_pins[] = { + AMS_DELTA_MIXED, + AMS_DELTA_HANDSET, + AMS_DELTA_HANDSFREE, + AMS_DELTA_SPEAKERPHONE, +}; + +static unsigned short ams_delta_audio_agc; + +static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_enum *control = (struct soc_enum *)kcontrol->private_value; + unsigned short pins; + int pin, changed = 0; + + /* Refuse any mode changes if we are not able to control the codec. */ + if (!codec->control_data) + return -EUNATCH; + + if (ucontrol->value.enumerated.item[0] >= control->max) + return -EINVAL; + + mutex_lock(&codec->mutex); + + /* Translate selection to bitmap */ + pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]]; + + /* Setup pins after corresponding bits if changed */ + pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Mouthpiece")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Mouthpiece"); + else + snd_soc_dapm_disable_pin(codec, "Mouthpiece"); + } + pin = !!(pins & (1 << AMS_DELTA_EARPIECE)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Earpiece")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Earpiece"); + else + snd_soc_dapm_disable_pin(codec, "Earpiece"); + } + pin = !!(pins & (1 << AMS_DELTA_MICROPHONE)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Microphone")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Microphone"); + else + snd_soc_dapm_disable_pin(codec, "Microphone"); + } + pin = !!(pins & (1 << AMS_DELTA_SPEAKER)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Speaker")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Speaker"); + else + snd_soc_dapm_disable_pin(codec, "Speaker"); + } + pin = !!(pins & (1 << AMS_DELTA_AGC)); + if (pin != ams_delta_audio_agc) { + ams_delta_audio_agc = pin; + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "AGCIN"); + else + snd_soc_dapm_disable_pin(codec, "AGCIN"); + } + if (changed) + snd_soc_dapm_sync(codec); + + mutex_unlock(&codec->mutex); + + return changed; +} + +static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned short pins, mode; + + pins = ((snd_soc_dapm_get_pin_status(codec, "Mouthpiece") << + AMS_DELTA_MOUTHPIECE) | + (snd_soc_dapm_get_pin_status(codec, "Earpiece") << + AMS_DELTA_EARPIECE)); + if (pins) + pins |= (snd_soc_dapm_get_pin_status(codec, "Microphone") << + AMS_DELTA_MICROPHONE); + else + pins = ((snd_soc_dapm_get_pin_status(codec, "Microphone") << + AMS_DELTA_MICROPHONE) | + (snd_soc_dapm_get_pin_status(codec, "Speaker") << + AMS_DELTA_SPEAKER) | + (ams_delta_audio_agc << AMS_DELTA_AGC)); + + for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++) + if (pins == ams_delta_audio_mode_pins[mode]) + break; + + if (mode >= ARRAY_SIZE(ams_delta_audio_mode)) + return -EINVAL; + + ucontrol->value.enumerated.item[0] = mode; + + return 0; +} + +static const struct soc_enum ams_delta_audio_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ams_delta_audio_mode), + ams_delta_audio_mode), +}; + +static const struct snd_kcontrol_new ams_delta_audio_controls[] = { + SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum[0], + ams_delta_get_audio_mode, ams_delta_set_audio_mode), +}; + +/* Hook switch */ +static struct snd_soc_jack ams_delta_hook_switch; +static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = { + { + .gpio = 4, + .name = "hook_switch", + .report = SND_JACK_HEADSET, + .invert = 1, + .debounce_time = 150, + } +}; + +/* After we are able to control the codec over the modem, + * the hook switch can be used for dynamic DAPM reconfiguration. */ +static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = { + /* Handset */ + { + .pin = "Mouthpiece", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Earpiece", + .mask = SND_JACK_HEADPHONE, + }, + /* Handsfree */ + { + .pin = "Microphone", + .mask = SND_JACK_MICROPHONE, + .invert = 1, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + + +/* + * Modem line discipline, required for making above controls functional. + * Activated from userspace with ldattach, possibly invoked from udev rule. + */ + +/* To actually apply any modem controlled configuration changes to the codec, + * we must connect codec DAI pins to the modem for a moment. Be carefull not + * to interfere with our digital mute function that shares the same hardware. */ +static struct timer_list cx81801_timer; +static bool cx81801_cmd_pending; +static bool ams_delta_muted; +static DEFINE_SPINLOCK(ams_delta_lock); + +static void cx81801_timeout(unsigned long data) +{ + int muted; + + spin_lock(&ams_delta_lock); + cx81801_cmd_pending = 0; + muted = ams_delta_muted; + spin_unlock(&ams_delta_lock); + + /* Reconnect the codec DAI back from the modem to the CPU DAI + * only if digital mute still off */ + if (!muted) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, 0); +} + +/* Line discipline .open() */ +static int cx81801_open(struct tty_struct *tty) +{ + return v253_ops.open(tty); +} + +/* Line discipline .close() */ +static void cx81801_close(struct tty_struct *tty) +{ + struct snd_soc_codec *codec = tty->disc_data; + + del_timer_sync(&cx81801_timer); + + v253_ops.close(tty); + + /* Prevent the hook switch from further changing the DAPM pins */ + INIT_LIST_HEAD(&ams_delta_hook_switch.pins); + + /* Revert back to default audio input/output constellation */ + snd_soc_dapm_disable_pin(codec, "Mouthpiece"); + snd_soc_dapm_enable_pin(codec, "Earpiece"); + snd_soc_dapm_enable_pin(codec, "Microphone"); + snd_soc_dapm_disable_pin(codec, "Speaker"); + snd_soc_dapm_disable_pin(codec, "AGCIN"); + snd_soc_dapm_sync(codec); +} + +/* Line discipline .hangup() */ +static int cx81801_hangup(struct tty_struct *tty) +{ + cx81801_close(tty); + return 0; +} + +/* Line discipline .recieve_buf() */ +static void cx81801_receive(struct tty_struct *tty, + const unsigned char *cp, char *fp, int count) +{ + struct snd_soc_codec *codec = tty->disc_data; + const unsigned char *c; + int apply, ret; + + if (!codec->control_data) { + /* First modem response, complete setup procedure */ + + /* Initialize timer used for config pulse generation */ + setup_timer(&cx81801_timer, cx81801_timeout, 0); + + v253_ops.receive_buf(tty, cp, fp, count); + + /* Link hook switch to DAPM pins */ + ret = snd_soc_jack_add_pins(&ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_pins), + ams_delta_hook_switch_pins); + if (ret) + dev_warn(codec->socdev->card->dev, + "Failed to link hook switch to DAPM pins, " + "will continue with hook switch unlinked.\n"); + + return; + } + + v253_ops.receive_buf(tty, cp, fp, count); + + for (c = &cp[count - 1]; c >= cp; c--) { + if (*c != '\r') + continue; + /* Complete modem response received, apply config to codec */ + + spin_lock_bh(&ams_delta_lock); + mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150)); + apply = !ams_delta_muted && !cx81801_cmd_pending; + cx81801_cmd_pending = 1; + spin_unlock_bh(&ams_delta_lock); + + /* Apply config pulse by connecting the codec to the modem + * if not already done */ + if (apply) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, + AMS_DELTA_LATCH2_MODEM_CODEC); + break; + } +} + +/* Line discipline .write_wakeup() */ +static void cx81801_wakeup(struct tty_struct *tty) +{ + v253_ops.write_wakeup(tty); +} + +static struct tty_ldisc_ops cx81801_ops = { + .magic = TTY_LDISC_MAGIC, + .name = "cx81801", + .owner = THIS_MODULE, + .open = cx81801_open, + .close = cx81801_close, + .hangup = cx81801_hangup, + .receive_buf = cx81801_receive, + .write_wakeup = cx81801_wakeup, +}; + + +/* + * Even if not very usefull, the sound card can still work without any of the + * above functonality activated. You can still control its audio input/output + * constellation and speakerphone gain from userspace by issueing AT commands + * over the modem port. + */ + +static int ams_delta_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* Set cpu DAI configuration */ + return snd_soc_dai_set_fmt(rtd->dai->cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); +} + +static struct snd_soc_ops ams_delta_ops = { + .hw_params = ams_delta_hw_params, +}; + + +/* Board specific codec bias level control */ +static int ams_delta_set_bias_level(struct snd_soc_card *card, + enum snd_soc_bias_level level) +{ + struct snd_soc_codec *codec = card->codec; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET, + AMS_DELTA_LATCH2_MODEM_NRESET); + break; + case SND_SOC_BIAS_OFF: + if (codec->bias_level != SND_SOC_BIAS_OFF) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET, + 0); + } + codec->bias_level = level; + + return 0; +} + +/* Digital mute implemented using modem/CPU multiplexer. + * Shares hardware with codec config pulse generation */ +static bool ams_delta_muted = 1; + +static int ams_delta_digital_mute(struct snd_soc_dai *dai, int mute) +{ + int apply; + + if (ams_delta_muted == mute) + return 0; + + spin_lock_bh(&ams_delta_lock); + ams_delta_muted = mute; + apply = !cx81801_cmd_pending; + spin_unlock_bh(&ams_delta_lock); + + if (apply) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, + mute ? AMS_DELTA_LATCH2_MODEM_CODEC : 0); + return 0; +} + +/* Our codec DAI probably doesn't have its own .ops structure */ +static struct snd_soc_dai_ops ams_delta_dai_ops = { + .digital_mute = ams_delta_digital_mute, +}; + +/* Will be used if the codec ever has its own digital_mute function */ +static int ams_delta_startup(struct snd_pcm_substream *substream) +{ + return ams_delta_digital_mute(NULL, 0); +} + +static void ams_delta_shutdown(struct snd_pcm_substream *substream) +{ + ams_delta_digital_mute(NULL, 1); +} + + +/* + * Card initialization + */ + +static int ams_delta_cx20442_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dai *codec_dai = codec->dai; + struct snd_soc_card *card = codec->socdev->card; + int ret; + /* Codec is ready, now add/activate board specific controls */ + + /* Set up digital mute if not provided by the codec */ + if (!codec_dai->ops) { + codec_dai->ops = &ams_delta_dai_ops; + } else if (!codec_dai->ops->digital_mute) { + codec_dai->ops->digital_mute = ams_delta_digital_mute; + } else { + ams_delta_ops.startup = ams_delta_startup; + ams_delta_ops.shutdown = ams_delta_shutdown; + } + + /* Set codec bias level */ + ams_delta_set_bias_level(card, SND_SOC_BIAS_STANDBY); + + /* Add hook switch - can be used to control the codec from userspace + * even if line discipline fails */ + ret = snd_soc_jack_new(card, "hook_switch", + SND_JACK_HEADSET, &ams_delta_hook_switch); + if (ret) + dev_warn(card->dev, + "Failed to allocate resources for hook switch, " + "will continue without one.\n"); + else { + ret = snd_soc_jack_add_gpios(&ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_gpios), + ams_delta_hook_switch_gpios); + if (ret) + dev_warn(card->dev, + "Failed to set up hook switch GPIO line, " + "will continue with hook switch inactive.\n"); + } + + /* Register optional line discipline for over the modem control */ + ret = tty_register_ldisc(N_V253, &cx81801_ops); + if (ret) { + dev_warn(card->dev, + "Failed to register line discipline, " + "will continue without any controls.\n"); + return 0; + } + + /* Add board specific DAPM widgets and routes */ + ret = snd_soc_dapm_new_controls(codec, ams_delta_dapm_widgets, + ARRAY_SIZE(ams_delta_dapm_widgets)); + if (ret) { + dev_warn(card->dev, + "Failed to register DAPM controls, " + "will continue without any.\n"); + return 0; + } + + ret = snd_soc_dapm_add_routes(codec, ams_delta_audio_map, + ARRAY_SIZE(ams_delta_audio_map)); + if (ret) { + dev_warn(card->dev, + "Failed to set up DAPM routes, " + "will continue with codec default map.\n"); + return 0; + } + + /* Set up initial pin constellation */ + snd_soc_dapm_disable_pin(codec, "Mouthpiece"); + snd_soc_dapm_enable_pin(codec, "Earpiece"); + snd_soc_dapm_enable_pin(codec, "Microphone"); + snd_soc_dapm_disable_pin(codec, "Speaker"); + snd_soc_dapm_disable_pin(codec, "AGCIN"); + snd_soc_dapm_disable_pin(codec, "AGCOUT"); + snd_soc_dapm_sync(codec); + + /* Add virtual switch */ + ret = snd_soc_add_controls(codec, ams_delta_audio_controls, + ARRAY_SIZE(ams_delta_audio_controls)); + if (ret) + dev_warn(card->dev, + "Failed to register audio mode control, " + "will continue without it.\n"); + + return 0; +} + +/* DAI glue - connects codec <--> CPU */ +static struct snd_soc_dai_link ams_delta_dai_link = { + .name = "CX20442", + .stream_name = "CX20442", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &cx20442_dai, + .init = ams_delta_cx20442_init, + .ops = &ams_delta_ops, +}; + +/* Audio card driver */ +static struct snd_soc_card ams_delta_audio_card = { + .name = "AMS_DELTA", + .platform = &omap_soc_platform, + .dai_link = &ams_delta_dai_link, + .num_links = 1, + .set_bias_level = ams_delta_set_bias_level, +}; + +/* Audio subsystem */ +static struct snd_soc_device ams_delta_snd_soc_device = { + .card = &ams_delta_audio_card, + .codec_dev = &cx20442_codec_dev, +}; + +/* Module init/exit */ +static struct platform_device *ams_delta_audio_platform_device; +static struct platform_device *cx20442_platform_device; + +static int __init ams_delta_module_init(void) +{ + int ret; + + if (!(machine_is_ams_delta())) + return -ENODEV; + + ams_delta_audio_platform_device = + platform_device_alloc("soc-audio", -1); + if (!ams_delta_audio_platform_device) + return -ENOMEM; + + platform_set_drvdata(ams_delta_audio_platform_device, + &ams_delta_snd_soc_device); + ams_delta_snd_soc_device.dev = &ams_delta_audio_platform_device->dev; + *(unsigned int *)ams_delta_dai_link.cpu_dai->private_data = OMAP_MCBSP1; + + ret = platform_device_add(ams_delta_audio_platform_device); + if (ret) + goto err; + + /* + * Codec platform device could be registered from elsewhere (board?), + * but I do it here as it makes sense only if used with the card. + */ + cx20442_platform_device = platform_device_register_simple("cx20442", + -1, NULL, 0); + return 0; +err: + platform_device_put(ams_delta_audio_platform_device); + return ret; +} +module_init(ams_delta_module_init); + +static void __exit ams_delta_module_exit(void) +{ + struct snd_soc_codec *codec; + struct tty_struct *tty; + + if (ams_delta_audio_card.codec) { + codec = ams_delta_audio_card.codec; + + if (codec->control_data) { + tty = codec->control_data; + + tty_hangup(tty); + } + } + + if (tty_unregister_ldisc(N_V253) != 0) + dev_warn(&ams_delta_audio_platform_device->dev, + "failed to unregister V253 line discipline\n"); + + snd_soc_jack_free_gpios(&ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_gpios), + ams_delta_hook_switch_gpios); + + /* Keep modem power on */ + ams_delta_set_bias_level(&ams_delta_audio_card, SND_SOC_BIAS_STANDBY); + + platform_device_unregister(cx20442_platform_device); + platform_device_unregister(ams_delta_audio_platform_device); +} +module_exit(ams_delta_module_exit); + +MODULE_AUTHOR("Janusz Krzysztofik <jkrzyszt@tis.icnet.pl>"); +MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index 91ef17992de5..b60b1dfbc435 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -383,10 +383,9 @@ static int __init n810_soc_init(void) clk_set_parent(sys_clkout2_src, func96m_clk); clk_set_rate(sys_clkout2, 12000000); - if (gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) - BUG(); - if (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0) - BUG(); + BUG_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) || + (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0)); + gpio_direction_output(N810_HEADSET_AMP_GPIO, 0); gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0); diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 912614283848..6a837ffd5d0b 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -183,21 +183,21 @@ static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); - int err = 0; + int err = 0, play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (!mcbsp_data->active++) - omap_mcbsp_start(mcbsp_data->bus_id); + mcbsp_data->active++; + omap_mcbsp_start(mcbsp_data->bus_id, play, !play); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (!--mcbsp_data->active) - omap_mcbsp_stop(mcbsp_data->bus_id); + omap_mcbsp_stop(mcbsp_data->bus_id, play, !play); + mcbsp_data->active--; break; default: err = -EINVAL; @@ -215,8 +215,9 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs; int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id; - int wlen, channels; + int wlen, channels, wpf; unsigned long port; + unsigned int format; if (cpu_class_is_omap1()) { dma = omap1_dma_reqs[bus_id][substream->stream]; @@ -244,18 +245,24 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, return 0; } - channels = params_channels(params); + format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + wpf = channels = params_channels(params); switch (channels) { case 2: - /* Use dual-phase frames */ - regs->rcr2 |= RPHASE; - regs->xcr2 |= XPHASE; + if (format == SND_SOC_DAIFMT_I2S) { + /* Use dual-phase frames */ + regs->rcr2 |= RPHASE; + regs->xcr2 |= XPHASE; + /* Set 1 word per (McBSP) frame for phase1 and phase2 */ + wpf--; + regs->rcr2 |= RFRLEN2(wpf - 1); + regs->xcr2 |= XFRLEN2(wpf - 1); + } case 1: - /* Set 1 word per (McBSP) frame */ - regs->rcr2 |= RFRLEN2(1 - 1); - regs->rcr1 |= RFRLEN1(1 - 1); - regs->xcr2 |= XFRLEN2(1 - 1); - regs->xcr1 |= XFRLEN1(1 - 1); + case 4: + /* Set word per (McBSP) frame for phase1 */ + regs->rcr1 |= RFRLEN1(wpf - 1); + regs->xcr1 |= XFRLEN1(wpf - 1); break; default: /* Unsupported number of channels */ @@ -277,11 +284,12 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, } /* Set FS period and length in terms of bit clock periods */ - switch (mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + switch (format) { case SND_SOC_DAIFMT_I2S: - regs->srgr2 |= FPER(wlen * 2 - 1); + regs->srgr2 |= FPER(wlen * channels - 1); regs->srgr1 |= FWID(wlen - 1); break; + case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: regs->srgr2 |= FPER(wlen * channels - 1); regs->srgr1 |= FWID(0); @@ -326,6 +334,13 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); break; + case SND_SOC_DAIFMT_DSP_A: + /* 1-bit data delay */ + regs->rcr2 |= RDATDLY(1); + regs->xcr2 |= XDATDLY(1); + /* Invert FS polarity configuration */ + temp_fmt ^= SND_SOC_DAIFMT_NB_IF; + break; case SND_SOC_DAIFMT_DSP_B: /* 0-bit data delay */ regs->rcr2 |= RDATDLY(0); @@ -492,13 +507,13 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = { .id = (link_id), \ .playback = { \ .channels_min = 1, \ - .channels_max = 2, \ + .channels_max = 4, \ .rates = OMAP_MCBSP_RATES, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .capture = { \ .channels_min = 1, \ - .channels_max = 2, \ + .channels_max = 4, \ .rates = OMAP_MCBSP_RATES, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 07cf7f46b584..12e14c01068e 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -59,16 +59,31 @@ static void omap_pcm_dma_irq(int ch, u16 stat, void *data) struct omap_runtime_data *prtd = runtime->private_data; unsigned long flags; - if (cpu_is_omap1510()) { + if ((cpu_is_omap1510()) && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) { /* - * OMAP1510 doesn't support DMA chaining so have to restart - * the transfer after all periods are transferred + * OMAP1510 doesn't fully support DMA progress counter + * and there is no software emulation implemented yet, + * so have to maintain our own playback progress counter + * that can be used by omap_pcm_pointer() instead. */ spin_lock_irqsave(&prtd->lock, flags); + if ((stat == OMAP_DMA_LAST_IRQ) && + (prtd->period_index == runtime->periods - 1)) { + /* we are in sync, do nothing */ + spin_unlock_irqrestore(&prtd->lock, flags); + return; + } if (prtd->period_index >= 0) { - if (++prtd->period_index == runtime->periods) { + if (stat & OMAP_DMA_BLOCK_IRQ) { + /* end of buffer reached, loop back */ + prtd->period_index = 0; + } else if (stat & OMAP_DMA_LAST_IRQ) { + /* update the counter for the last period */ + prtd->period_index = runtime->periods - 1; + } else if (++prtd->period_index >= runtime->periods) { + /* end of buffer missed? loop back */ prtd->period_index = 0; - omap_start_dma(prtd->dma_ch); } } spin_unlock_irqrestore(&prtd->lock, flags); @@ -87,8 +102,10 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, struct omap_pcm_dma_data *dma_data = rtd->dai->cpu_dai->dma_data; int err = 0; + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ if (!dma_data) - return -ENODEV; + return 0; snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->dma_bytes = params_buffer_bytes(params); @@ -98,7 +115,7 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, prtd->dma_data = dma_data; err = omap_request_dma(dma_data->dma_req, dma_data->name, omap_pcm_dma_irq, substream, &prtd->dma_ch); - if (!err && !cpu_is_omap1510()) { + if (!err) { /* * Link channel with itself so DMA doesn't need any * reprogramming while looping the buffer @@ -117,8 +134,7 @@ static int omap_pcm_hw_free(struct snd_pcm_substream *substream) if (prtd->dma_data == NULL) return 0; - if (!cpu_is_omap1510()) - omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch); + omap_dma_unlink_lch(prtd->dma_ch, prtd->dma_ch); omap_free_dma(prtd->dma_ch); prtd->dma_data = NULL; @@ -134,6 +150,11 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) struct omap_pcm_dma_data *dma_data = prtd->dma_data; struct omap_dma_channel_params dma_params; + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!prtd->dma_data) + return 0; + memset(&dma_params, 0, sizeof(dma_params)); /* * Note: Regardless of interface data formats supported by OMAP McBSP @@ -167,7 +188,12 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) dma_params.frame_count = runtime->periods; omap_set_dma_params(prtd->dma_ch, &dma_params); - omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ); + if ((cpu_is_omap1510()) && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) + omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ | + OMAP_DMA_LAST_IRQ | OMAP_DMA_BLOCK_IRQ); + else + omap_enable_dma_irq(prtd->dma_ch, OMAP_DMA_FRAME_IRQ); return 0; } @@ -209,12 +235,15 @@ static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream) dma_addr_t ptr; snd_pcm_uframes_t offset; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - ptr = omap_get_dma_src_pos(prtd->dma_ch); - else + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ptr = omap_get_dma_dst_pos(prtd->dma_ch); + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } else if (!(cpu_is_omap1510())) { + ptr = omap_get_dma_src_pos(prtd->dma_ch); + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } else + offset = prtd->period_index * runtime->period_size; - offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); if (offset >= runtime->buffer_size) offset = 0; @@ -320,7 +349,7 @@ static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm) } } -int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, +static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; diff --git a/sound/soc/omap/omap2evm.c b/sound/soc/omap/omap2evm.c index 0c2322dcf02a..027e1a40f8a1 100644 --- a/sound/soc/omap/omap2evm.c +++ b/sound/soc/omap/omap2evm.c @@ -86,7 +86,7 @@ static struct snd_soc_dai_link omap2evm_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap2evm_ops, }; diff --git a/sound/soc/omap/omap3beagle.c b/sound/soc/omap/omap3beagle.c index fd24a4acd2f5..b0cff9f33b7e 100644 --- a/sound/soc/omap/omap3beagle.c +++ b/sound/soc/omap/omap3beagle.c @@ -41,23 +41,33 @@ static int omap3beagle_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + unsigned int fmt; int ret; + switch (params_channels(params)) { + case 2: /* Stereo I2S mode */ + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + case 4: /* Four channel TDM mode */ + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + default: + return -EINVAL; + } + /* Set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); + ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set codec DAI configuration\n"); return ret; } /* Set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration\n"); return ret; @@ -83,7 +93,7 @@ static struct snd_soc_dai_link omap3beagle_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3beagle_ops, }; diff --git a/sound/soc/omap/omap3evm.c b/sound/soc/omap/omap3evm.c new file mode 100644 index 000000000000..9114c263077b --- /dev/null +++ b/sound/soc/omap/omap3evm.c @@ -0,0 +1,147 @@ +/* + * omap3evm.c -- ALSA SoC support for OMAP3 EVM + * + * Author: Anuj Aggarwal <anuj.aggarwal@ti.com> + * + * Based on sound/soc/omap/beagle.c by Steve Sakoman + * + * Copyright (C) 2008 Texas Instruments, Incorporated + * + * 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 version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <mach/mcbsp.h> + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/twl4030.h" + +static int omap3evm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "Can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "Can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "Can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops omap3evm_ops = { + .hw_params = omap3evm_hw_params, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link omap3evm_dai = { + .name = "TWL4030", + .stream_name = "TWL4030", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .ops = &omap3evm_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_omap3evm = { + .name = "omap3evm", + .platform = &omap_soc_platform, + .dai_link = &omap3evm_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device omap3evm_snd_devdata = { + .card = &snd_soc_omap3evm, + .codec_dev = &soc_codec_dev_twl4030, +}; + +static struct platform_device *omap3evm_snd_device; + +static int __init omap3evm_soc_init(void) +{ + int ret; + + if (!machine_is_omap3evm()) { + pr_err("Not OMAP3 EVM!\n"); + return -ENODEV; + } + pr_info("OMAP3 EVM SoC init\n"); + + omap3evm_snd_device = platform_device_alloc("soc-audio", -1); + if (!omap3evm_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(omap3evm_snd_device, &omap3evm_snd_devdata); + omap3evm_snd_devdata.dev = &omap3evm_snd_device->dev; + *(unsigned int *)omap3evm_dai.cpu_dai->private_data = 1; + + ret = platform_device_add(omap3evm_snd_device); + if (ret) + goto err1; + + return 0; + +err1: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(omap3evm_snd_device); + + return ret; +} + +static void __exit omap3evm_soc_exit(void) +{ + platform_device_unregister(omap3evm_snd_device); +} + +module_init(omap3evm_soc_init); +module_exit(omap3evm_soc_exit); + +MODULE_AUTHOR("Anuj Aggarwal <anuj.aggarwal@ti.com>"); +MODULE_DESCRIPTION("ALSA SoC OMAP3 EVM"); +MODULE_LICENSE("GPLv2"); diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index fe282d4ef422..ad219aaf7cb8 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -228,14 +228,14 @@ static struct snd_soc_dai_link omap3pandora_dai[] = { .name = "PCM1773", .stream_name = "HiFi Out", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3pandora_out_ops, .init = omap3pandora_out_init, }, { .name = "TWL4030", .stream_name = "Line/Mic In", .cpu_dai = &omap_mcbsp_dai[1], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3pandora_in_ops, .init = omap3pandora_in_init, } diff --git a/sound/soc/omap/overo.c b/sound/soc/omap/overo.c index a72dc4e159e5..ec4f8fd8b3a2 100644 --- a/sound/soc/omap/overo.c +++ b/sound/soc/omap/overo.c @@ -83,7 +83,7 @@ static struct snd_soc_dai_link overo_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &overo_ops, }; diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index 10f1c867f11d..f7e5b7488c35 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -24,6 +24,7 @@ #include <linux/clk.h> #include <linux/platform_device.h> +#include <linux/i2c/twl4030.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> @@ -39,6 +40,9 @@ #include "omap-pcm.h" #include "../codecs/twl4030.h" +#define TWL4030_INTBR_PMBR1 0x0D +#define EXTMUTE(value) (value << 2) + static struct snd_soc_card snd_soc_sdp3430; static int sdp3430_hw_params(struct snd_pcm_substream *substream, @@ -84,6 +88,49 @@ static struct snd_soc_ops sdp3430_ops = { .hw_params = sdp3430_hw_params, }; +static int sdp3430_hw_voice_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops sdp3430_voice_ops = { + .hw_params = sdp3430_hw_voice_params, +}; + /* Headset jack */ static struct snd_soc_jack hs_jack; @@ -192,28 +239,59 @@ static int sdp3430_twl4030_init(struct snd_soc_codec *codec) return ret; } +static int sdp3430_twl4030_voice_init(struct snd_soc_codec *codec) +{ + unsigned short reg; + + /* Enable voice interface */ + reg = codec->read(codec, TWL4030_REG_VOICE_IF); + reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN; + codec->write(codec, TWL4030_REG_VOICE_IF, reg); + + return 0; +} + + /* Digital audio interface glue - connects codec <--> CPU */ -static struct snd_soc_dai_link sdp3430_dai = { - .name = "TWL4030", - .stream_name = "TWL4030", - .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, - .init = sdp3430_twl4030_init, - .ops = &sdp3430_ops, +static struct snd_soc_dai_link sdp3430_dai[] = { + { + .name = "TWL4030 I2S", + .stream_name = "TWL4030 Audio", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .init = sdp3430_twl4030_init, + .ops = &sdp3430_ops, + }, + { + .name = "TWL4030 PCM", + .stream_name = "TWL4030 Voice", + .cpu_dai = &omap_mcbsp_dai[1], + .codec_dai = &twl4030_dai[TWL4030_DAI_VOICE], + .init = sdp3430_twl4030_voice_init, + .ops = &sdp3430_voice_ops, + }, }; /* Audio machine driver */ static struct snd_soc_card snd_soc_sdp3430 = { .name = "SDP3430", .platform = &omap_soc_platform, - .dai_link = &sdp3430_dai, - .num_links = 1, + .dai_link = sdp3430_dai, + .num_links = ARRAY_SIZE(sdp3430_dai), +}; + +/* twl4030 setup */ +static struct twl4030_setup_data twl4030_setup = { + .ramp_delay_value = 3, + .sysclk = 26000, + .hs_extmute = 1, }; /* Audio subsystem */ static struct snd_soc_device sdp3430_snd_devdata = { .card = &snd_soc_sdp3430, .codec_dev = &soc_codec_dev_twl4030, + .codec_data = &twl4030_setup, }; static struct platform_device *sdp3430_snd_device; @@ -236,7 +314,12 @@ static int __init sdp3430_soc_init(void) platform_set_drvdata(sdp3430_snd_device, &sdp3430_snd_devdata); sdp3430_snd_devdata.dev = &sdp3430_snd_device->dev; - *(unsigned int *)sdp3430_dai.cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)sdp3430_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)sdp3430_dai[1].cpu_dai->private_data = 2; /* McBSP3 */ + + /* Set TWL4030 GPIO6 as EXTMUTE signal */ + twl4030_i2c_write_u8(TWL4030_MODULE_INTBR, EXTMUTE(0x02), + TWL4030_MODULE_INTBR); ret = platform_device_add(sdp3430_snd_device); if (ret) diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c new file mode 100644 index 000000000000..f90b45f56220 --- /dev/null +++ b/sound/soc/omap/zoom2.c @@ -0,0 +1,314 @@ +/* + * zoom2.c -- SoC audio for Zoom2 + * + * Author: Misael Lopez Cruz <x0052729@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <mach/mcbsp.h> + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/twl4030.h" + +#define ZOOM2_HEADSET_MUX_GPIO (OMAP_MAX_GPIO_LINES + 15) +#define ZOOM2_HEADSET_EXTMUTE_GPIO 153 + +static int zoom2_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops zoom2_ops = { + .hw_params = zoom2_hw_params, +}; + +static int zoom2_hw_voice_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops zoom2_voice_ops = { + .hw_params = zoom2_hw_voice_params, +}; + +/* Zoom2 machine DAPM */ +static const struct snd_soc_dapm_widget zoom2_twl4030_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Ext Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_LINE("Aux In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* External Mics: MAINMIC, SUBMIC with bias*/ + {"MAINMIC", NULL, "Mic Bias 1"}, + {"SUBMIC", NULL, "Mic Bias 2"}, + {"Mic Bias 1", NULL, "Ext Mic"}, + {"Mic Bias 2", NULL, "Ext Mic"}, + + /* External Speakers: HFL, HFR */ + {"Ext Spk", NULL, "HFL"}, + {"Ext Spk", NULL, "HFR"}, + + /* Headset Stereophone: HSOL, HSOR */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, + + /* Headset Mic: HSMIC with bias */ + {"HSMIC", NULL, "Headset Mic Bias"}, + {"Headset Mic Bias", NULL, "Headset Mic"}, + + /* Aux In: AUXL, AUXR */ + {"Aux In", NULL, "AUXL"}, + {"Aux In", NULL, "AUXR"}, +}; + +static int zoom2_twl4030_init(struct snd_soc_codec *codec) +{ + int ret; + + /* Add Zoom2 specific widgets */ + ret = snd_soc_dapm_new_controls(codec, zoom2_twl4030_dapm_widgets, + ARRAY_SIZE(zoom2_twl4030_dapm_widgets)); + if (ret) + return ret; + + /* Set up Zoom2 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* Zoom2 connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Headset Stereophone"); + snd_soc_dapm_enable_pin(codec, "Aux In"); + + /* TWL4030 not connected pins */ + snd_soc_dapm_nc_pin(codec, "CARKITMIC"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC0"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC1"); + + snd_soc_dapm_nc_pin(codec, "OUTL"); + snd_soc_dapm_nc_pin(codec, "OUTR"); + snd_soc_dapm_nc_pin(codec, "EARPIECE"); + snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); + snd_soc_dapm_nc_pin(codec, "PREDRIVER"); + snd_soc_dapm_nc_pin(codec, "CARKITL"); + snd_soc_dapm_nc_pin(codec, "CARKITR"); + + ret = snd_soc_dapm_sync(codec); + + return ret; +} + +static int zoom2_twl4030_voice_init(struct snd_soc_codec *codec) +{ + unsigned short reg; + + /* Enable voice interface */ + reg = codec->read(codec, TWL4030_REG_VOICE_IF); + reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN; + codec->write(codec, TWL4030_REG_VOICE_IF, reg); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link zoom2_dai[] = { + { + .name = "TWL4030 I2S", + .stream_name = "TWL4030 Audio", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .init = zoom2_twl4030_init, + .ops = &zoom2_ops, + }, + { + .name = "TWL4030 PCM", + .stream_name = "TWL4030 Voice", + .cpu_dai = &omap_mcbsp_dai[1], + .codec_dai = &twl4030_dai[TWL4030_DAI_VOICE], + .init = zoom2_twl4030_voice_init, + .ops = &zoom2_voice_ops, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_zoom2 = { + .name = "Zoom2", + .platform = &omap_soc_platform, + .dai_link = zoom2_dai, + .num_links = ARRAY_SIZE(zoom2_dai), +}; + +/* EXTMUTE callback function */ +void zoom2_set_hs_extmute(int mute) +{ + gpio_set_value(ZOOM2_HEADSET_EXTMUTE_GPIO, mute); +} + +/* twl4030 setup */ +static struct twl4030_setup_data twl4030_setup = { + .ramp_delay_value = 3, /* 161 ms */ + .sysclk = 26000, + .hs_extmute = 1, + .set_hs_extmute = zoom2_set_hs_extmute, +}; + +/* Audio subsystem */ +static struct snd_soc_device zoom2_snd_devdata = { + .card = &snd_soc_zoom2, + .codec_dev = &soc_codec_dev_twl4030, + .codec_data = &twl4030_setup, +}; + +static struct platform_device *zoom2_snd_device; + +static int __init zoom2_soc_init(void) +{ + int ret; + + if (!machine_is_omap_zoom2()) { + pr_debug("Not Zoom2!\n"); + return -ENODEV; + } + printk(KERN_INFO "Zoom2 SoC init\n"); + + zoom2_snd_device = platform_device_alloc("soc-audio", -1); + if (!zoom2_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(zoom2_snd_device, &zoom2_snd_devdata); + zoom2_snd_devdata.dev = &zoom2_snd_device->dev; + *(unsigned int *)zoom2_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)zoom2_dai[1].cpu_dai->private_data = 2; /* McBSP3 */ + + ret = platform_device_add(zoom2_snd_device); + if (ret) + goto err1; + + BUG_ON(gpio_request(ZOOM2_HEADSET_MUX_GPIO, "hs_mux") < 0); + gpio_direction_output(ZOOM2_HEADSET_MUX_GPIO, 0); + + BUG_ON(gpio_request(ZOOM2_HEADSET_EXTMUTE_GPIO, "ext_mute") < 0); + gpio_direction_output(ZOOM2_HEADSET_EXTMUTE_GPIO, 0); + + return 0; + +err1: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(zoom2_snd_device); + + return ret; +} +module_init(zoom2_soc_init); + +static void __exit zoom2_soc_exit(void) +{ + gpio_free(ZOOM2_HEADSET_MUX_GPIO); + gpio_free(ZOOM2_HEADSET_EXTMUTE_GPIO); + + platform_device_unregister(zoom2_snd_device); +} +module_exit(zoom2_soc_exit); + +MODULE_AUTHOR("Misael Lopez Cruz <x0052729@ti.com>"); +MODULE_DESCRIPTION("ALSA SoC Zoom2"); +MODULE_LICENSE("GPL"); + |