diff options
Diffstat (limited to 'sound/soc/omap')
-rw-r--r-- | sound/soc/omap/Kconfig | 15 | ||||
-rw-r--r-- | sound/soc/omap/Makefile | 4 | ||||
-rw-r--r-- | sound/soc/omap/ams-delta.c | 646 | ||||
-rw-r--r-- | sound/soc/omap/n810.c | 12 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcbsp.c | 123 | ||||
-rw-r--r-- | sound/soc/omap/omap-mcbsp.h | 4 | ||||
-rw-r--r-- | sound/soc/omap/omap-pcm.c | 53 | ||||
-rw-r--r-- | sound/soc/omap/omap-pcm.h | 2 | ||||
-rw-r--r-- | sound/soc/omap/sdp3430.c | 18 | ||||
-rw-r--r-- | sound/soc/omap/zoom2.c | 314 |
10 files changed, 1159 insertions, 32 deletions
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index b771238662b..2dee9839be8 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 @@ -72,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 a37f4986238..02d69471dcb 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -7,6 +7,7 @@ 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 @@ -14,8 +15,10 @@ 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 @@ -23,3 +26,4 @@ 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 00000000000..5a5166ac727 --- /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 b60b1dfbc43..0a505938e42 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -22,6 +22,7 @@ */ #include <linux/clk.h> +#include <linux/i2c.h> #include <linux/platform_device.h> #include <sound/core.h> #include <sound/pcm.h> @@ -322,8 +323,6 @@ static struct snd_soc_card snd_soc_n810 = { /* Audio private data */ static struct aic3x_setup_data n810_aic33_setup = { - .i2c_bus = 2, - .i2c_address = 0x18, .gpio_func[0] = AIC3X_GPIO1_FUNC_DISABLED, .gpio_func[1] = AIC3X_GPIO2_FUNC_DIGITAL_MIC_INPUT, }; @@ -337,6 +336,13 @@ static struct snd_soc_device n810_snd_devdata = { static struct platform_device *n810_snd_device; +/* temporary i2c device creation until this can be moved into the machine + * support file. +*/ +static struct i2c_board_info i2c_device[] = { + { I2C_BOARD_INFO("tlv320aic3x", 0x1b), } +}; + static int __init n810_soc_init(void) { int err; @@ -345,6 +351,8 @@ static int __init n810_soc_init(void) if (!(machine_is_nokia_n810() || machine_is_nokia_n810_wimax())) return -ENODEV; + i2c_register_board_info(1, i2c_device, ARRAY_SIZE(i2c_device)); + n810_snd_device = platform_device_alloc("soc-audio", -1); if (!n810_snd_device) return -ENOMEM; diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index a5d46a7b196..3341f49402c 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -139,27 +139,67 @@ static const unsigned long omap34xx_mcbsp_port[][2] = { static const unsigned long omap34xx_mcbsp_port[][2] = {}; #endif +static void omap_mcbsp_set_threshold(struct snd_pcm_substream *substream) +{ + 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 dma_op_mode = omap_mcbsp_get_dma_op_mode(mcbsp_data->bus_id); + int samples; + + /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */ + if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) + samples = snd_pcm_lib_period_bytes(substream) >> 1; + else + samples = 1; + + /* Configure McBSP internal buffer usage */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + omap_mcbsp_set_tx_threshold(mcbsp_data->bus_id, samples - 1); + else + omap_mcbsp_set_rx_threshold(mcbsp_data->bus_id, samples - 1); +} + static int omap_mcbsp_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { 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 bus_id = mcbsp_data->bus_id; int err = 0; - if (cpu_is_omap343x() && mcbsp_data->bus_id == 1) { + if (!cpu_dai->active) + err = omap_mcbsp_request(bus_id); + + if (cpu_is_omap343x()) { + int dma_op_mode = omap_mcbsp_get_dma_op_mode(bus_id); + int max_period; + /* * McBSP2 in OMAP3 has 1024 * 32-bit internal audio buffer. * Set constraint for minimum buffer size to the same than FIFO * size in order to avoid underruns in playback startup because * HW is keeping the DMA request active until FIFO is filled. */ - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 4096, UINT_MAX); - } + if (bus_id == 1) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 4096, UINT_MAX); - if (!cpu_dai->active) - err = omap_mcbsp_request(mcbsp_data->bus_id); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + max_period = omap_mcbsp_get_max_tx_threshold(bus_id); + else + max_period = omap_mcbsp_get_max_rx_threshold(bus_id); + + max_period++; + max_period <<= 1; + + if (dma_op_mode == MCBSP_DMA_MODE_THRESHOLD) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + 32, max_period); + } return err; } @@ -183,21 +223,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,7 +255,7 @@ 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, wpf; + int wlen, channels, wpf, sync_mode = OMAP_DMA_SYNC_ELEMENT; unsigned long port; unsigned int format; @@ -231,6 +271,12 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, } else if (cpu_is_omap343x()) { dma = omap24xx_dma_reqs[bus_id][substream->stream]; port = omap34xx_mcbsp_port[bus_id][substream->stream]; + omap_mcbsp_dai_dma_params[id][substream->stream].set_threshold = + omap_mcbsp_set_threshold; + /* TODO: Currently, MODE_ELEMENT == MODE_FRAME */ + if (omap_mcbsp_get_dma_op_mode(bus_id) == + MCBSP_DMA_MODE_THRESHOLD) + sync_mode = OMAP_DMA_SYNC_FRAME; } else { return -ENODEV; } @@ -238,6 +284,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, substream->stream ? "Audio Capture" : "Audio Playback"; omap_mcbsp_dai_dma_params[id][substream->stream].dma_req = dma; omap_mcbsp_dai_dma_params[id][substream->stream].port_addr = port; + omap_mcbsp_dai_dma_params[id][substream->stream].sync_mode = sync_mode; cpu_dai->dma_data = &omap_mcbsp_dai_dma_params[id][substream->stream]; if (mcbsp_data->configured) { @@ -321,11 +368,14 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, /* Generic McBSP register settings */ regs->spcr2 |= XINTM(3) | FREE; regs->spcr1 |= RINTM(3); - regs->rcr2 |= RFIG; - regs->xcr2 |= XFIG; + /* RFIG and XFIG are not defined in 34xx */ + if (!cpu_is_omap34xx()) { + regs->rcr2 |= RFIG; + regs->xcr2 |= XFIG; + } if (cpu_is_omap2430() || cpu_is_omap34xx()) { - regs->xccr = DXENDLY(1) | XDMAEN; - regs->rccr = RFULL_CYCLE | RDMAEN; + regs->xccr = DXENDLY(1) | XDMAEN | XDISABLE; + regs->rccr = RFULL_CYCLE | RDMAEN | RDISABLE; } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { @@ -462,6 +512,40 @@ static int omap_mcbsp_dai_set_clks_src(struct omap_mcbsp_data *mcbsp_data, return 0; } +static int omap_mcbsp_dai_set_rcvr_src(struct omap_mcbsp_data *mcbsp_data, + int clk_id) +{ + int sel_bit, set = 0; + u16 reg = OMAP2_CONTROL_DEVCONF0; + + if (cpu_class_is_omap1()) + return -EINVAL; /* TODO: Can this be implemented for OMAP1? */ + if (mcbsp_data->bus_id != 0) + return -EINVAL; + + switch (clk_id) { + case OMAP_MCBSP_CLKR_SRC_CLKX: + set = 1; + case OMAP_MCBSP_CLKR_SRC_CLKR: + sel_bit = 3; + break; + case OMAP_MCBSP_FSR_SRC_FSX: + set = 1; + case OMAP_MCBSP_FSR_SRC_FSR: + sel_bit = 4; + break; + default: + return -EINVAL; + } + + if (set) + omap_ctrl_writel(omap_ctrl_readl(reg) | (1 << sel_bit), reg); + else + omap_ctrl_writel(omap_ctrl_readl(reg) & ~(1 << sel_bit), reg); + + return 0; +} + static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) @@ -484,6 +568,13 @@ static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, case OMAP_MCBSP_SYSCLK_CLKR_EXT: regs->pcr0 |= SCLKME; break; + + case OMAP_MCBSP_CLKR_SRC_CLKR: + case OMAP_MCBSP_CLKR_SRC_CLKX: + case OMAP_MCBSP_FSR_SRC_FSR: + case OMAP_MCBSP_FSR_SRC_FSX: + err = omap_mcbsp_dai_set_rcvr_src(mcbsp_data, clk_id); + break; default: err = -ENODEV; } diff --git a/sound/soc/omap/omap-mcbsp.h b/sound/soc/omap/omap-mcbsp.h index c8147aace81..647d2f981ab 100644 --- a/sound/soc/omap/omap-mcbsp.h +++ b/sound/soc/omap/omap-mcbsp.h @@ -32,6 +32,10 @@ enum omap_mcbsp_clksrg_clk { OMAP_MCBSP_SYSCLK_CLK, /* Internal ICLK */ OMAP_MCBSP_SYSCLK_CLKX_EXT, /* External CLKX pin */ OMAP_MCBSP_SYSCLK_CLKR_EXT, /* External CLKR pin */ + OMAP_MCBSP_CLKR_SRC_CLKR, /* CLKR from CLKR pin */ + OMAP_MCBSP_CLKR_SRC_CLKX, /* CLKR from CLKX pin */ + OMAP_MCBSP_FSR_SRC_FSR, /* FSR from FSR pin */ + OMAP_MCBSP_FSR_SRC_FSX, /* FSR from FSX pin */ }; /* McBSP dividers */ diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 84a1950880e..5735945788b 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->peri |