aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/omap
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/omap')
-rw-r--r--sound/soc/omap/Kconfig8
-rw-r--r--sound/soc/omap/Makefile3
-rw-r--r--sound/soc/omap/gta04-audio.c372
-rw-r--r--sound/soc/omap/gta04-fm.c187
-rw-r--r--sound/soc/omap/gta04-headset.c146
-rw-r--r--sound/soc/omap/gta04-jack.c230
-rw-r--r--sound/soc/omap/gta04-voice.c159
-rw-r--r--sound/soc/omap/omap-mcbsp.c33
-rw-r--r--sound/soc/omap/omap-twl4030.c7
9 files changed, 1145 insertions, 0 deletions
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig
index daa78a0..1132039 100644
--- a/sound/soc/omap/Kconfig
+++ b/sound/soc/omap/Kconfig
@@ -116,3 +116,11 @@ config SND_OMAP_SOC_OMAP3_PANDORA
select SND_SOC_TWL4030
help
Say Y if you want to add support for SoC audio on the OMAP3 Pandora.
+
+config SND_OMAP_SOC_GTA04
+ tristate "SoC Audio support for GTA04"
+ depends on TWL4030_CORE && SND_OMAP_SOC && (MACH_GTA04 || MACH_OMAP3_BEAGLE)
+ select SND_OMAP_SOC_MCBSP
+ select SND_SOC_TWL4030
+ help
+ Say Y if you want to add support for SoC audio on the GTA04.
diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile
index a725905..3bc355e 100644
--- a/sound/soc/omap/Makefile
+++ b/sound/soc/omap/Makefile
@@ -20,6 +20,8 @@ snd-soc-am3517evm-objs := am3517evm.o
snd-soc-omap-abe-twl6040-objs := omap-abe-twl6040.o
snd-soc-omap-twl4030-objs := omap-twl4030.o
snd-soc-omap3pandora-objs := omap3pandora.o
+snd-soc-gta04-objs := gta04-audio.o gta04-voice.o gta04-headset.o gta04-jack.o gta04-fm.o
+snd-soc-zoom2-objs := zoom2.o
snd-soc-omap-hdmi-card-objs := omap-hdmi-card.o
obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o
@@ -30,4 +32,5 @@ obj-$(CONFIG_SND_OMAP_SOC_AM3517EVM) += snd-soc-am3517evm.o
obj-$(CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040) += snd-soc-omap-abe-twl6040.o
obj-$(CONFIG_SND_OMAP_SOC_OMAP_TWL4030) += snd-soc-omap-twl4030.o
obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o
+obj-$(CONFIG_SND_OMAP_SOC_GTA04) += snd-soc-gta04.o
obj-$(CONFIG_SND_OMAP_SOC_OMAP_HDMI) += snd-soc-omap-hdmi-card.o
diff --git a/sound/soc/omap/gta04-audio.c b/sound/soc/omap/gta04-audio.c
new file mode 100644
index 0000000..de8679a
--- /dev/null
+++ b/sound/soc/omap/gta04-audio.c
@@ -0,0 +1,372 @@
+/*
+ * gta04.c -- SoC audio for GTA04 (based on OMAP3 Beagle)
+ *
+ * Author: Steve Sakoman <steve@sakoman.com>
+ * Author: Nikolaus Schaller <hns@goldelico.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 <linux/module.h>
+#include <linux/input.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+
+#include <linux/i2c/twl4030-madc.h>
+
+#include <linux/i2c/twl4030-madc.h>
+
+#include "omap-mcbsp.h"
+#include "../codecs/twl4030.h"
+
+static int omap3gta04_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->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->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, 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, fmt);
+ 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;
+}
+
+/* this shows how we could control the AUX in/out switch or the Video in/out */
+
+static int omap3pandora_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ /*
+ if (SND_SOC_DAPM_EVENT_ON(event)) {
+ gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 1);
+ gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 1);
+ } else {
+ gpio_set_value(OMAP3_PANDORA_AMP_POWER_GPIO, 0);
+ mdelay(1);
+ gpio_set_value(OMAP3_PANDORA_DAC_POWER_GPIO, 0);
+ }
+ */
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget gta04_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("PCM DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_PGA_E("Headphone Amplifier", SND_SOC_NOPM,
+ 0, 0, NULL, 0, omap3pandora_hp_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line Out", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+ SND_SOC_DAPM_MIC("Headphone Mic", NULL),
+ SND_SOC_DAPM_LINE("Line In", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ {"Headphone Amplifier", NULL, "PCM DAC"},
+ {"Line Out", NULL, "PCM DAC"},
+ {"Headphone Jack", NULL, "Headphone Amplifier"},
+
+ {"AUXL", NULL, "Line In"},
+ {"AUXR", NULL, "Line In"},
+
+ /* Headset Mic: HSMIC with bias */
+ {"HSMIC", NULL, "Headset Mic Bias"},
+ {"Headset Mic Bias", NULL, "Headphone Mic"},
+
+ {"MAINMIC", NULL, "Mic Bias 1"},
+ {"Mic Bias 1", NULL, "Internal Mic"},
+ /*
+ {"SUBMIC", NULL, "Mic Bias 2"},
+ {"Mic Bias 2", NULL, "Mic (external)"},
+ */
+};
+
+static struct {
+ struct snd_soc_jack hs_jack;
+ struct delayed_work jack_work;
+ struct snd_soc_codec *codec;
+ int open;
+ /* When any jack is present, we:
+ * - poll more quickly to catch button presses
+ * - assume a 'short' is 'button press', not 'headset has
+ * no mic
+ * 'present' stores SND_JACK_HEADPHONE and SND_JACK_MICROPHONE
+ * indication what we thing is present.
+ */
+ int present;
+} jack;
+
+static void gta04_audio_jack_work(struct work_struct *work)
+{
+ long val;
+ long delay = msecs_to_jiffies(500);
+ int jackbits;
+
+ /* choose delay *before* checking presence so we still get
+ * one long delay on first insertion to help with debounce.
+ */
+ if (jack.present)
+ delay = msecs_to_jiffies(50);
+
+ val = twl4030_get_madc_conversion(7);
+ if (val < 0)
+ goto out;
+ /* On my device:
+ * open circuit = around 20
+ * short circuit = around 800
+ * microphone = around 830-840 !!!
+ */
+ if (val < 100) {
+ /* open circuit */
+ jackbits = 0;
+ jack.present = 0;
+ /* debounce */
+ delay = msecs_to_jiffies(500);
+ } else if (val < 820) {
+ /* short */
+ if (jack.present == 0) {
+ /* Inserted headset with no mic */
+ jack.present = SND_JACK_HEADPHONE;
+ jackbits = jack.present;
+ } else if (jack.present & SND_JACK_MICROPHONE) {
+ /* mic shorter == button press */
+ jackbits = SND_JACK_BTN_0 | jack.present;
+ } else {
+ /* headphones still present */
+ jackbits = jack.present;
+ }
+ } else {
+ /* There is a microphone there */
+ jack.present = SND_JACK_HEADSET;
+ jackbits = jack.present;
+ }
+ snd_soc_jack_report(&jack.hs_jack, jackbits,
+ SND_JACK_HEADSET | SND_JACK_BTN_0);
+
+out:
+ if (jack.open)
+ schedule_delayed_work(&jack.jack_work, delay);
+}
+
+static int gta04_audio_suspend(struct snd_soc_card *card)
+{
+ if (jack.codec) {
+ snd_soc_dapm_disable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+ }
+ return 0;
+}
+
+static int gta04_audio_resume(struct snd_soc_card *card)
+{
+ if (jack.codec && jack.open) {
+ snd_soc_dapm_force_enable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+ }
+ return 0;
+}
+
+static int gta04_jack_open(struct input_dev *dev)
+{
+ snd_soc_dapm_force_enable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+ jack.open = 1;
+ schedule_delayed_work(&jack.jack_work, msecs_to_jiffies(100));
+ return 0;
+}
+
+static void gta04_jack_close(struct input_dev *dev)
+{
+ jack.open = 0;
+ cancel_delayed_work_sync(&jack.jack_work);
+ snd_soc_dapm_disable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+}
+
+static int omap3gta04_init(struct snd_soc_pcm_runtime *runtime)
+{
+ int ret;
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+
+ ret = snd_soc_dapm_new_controls(dapm, gta04_dapm_widgets,
+ ARRAY_SIZE(gta04_dapm_widgets));
+ if (ret < 0)
+ return ret;
+
+ snd_soc_dapm_add_routes(dapm, audio_map,
+ ARRAY_SIZE(audio_map));
+
+// snd_soc_dapm_enable_pin(codec, "Ext Mic");
+// snd_soc_dapm_enable_pin(codec, "Ext Spk");
+// snd_soc_dapm_disable_pin(codec, "Headphone Mic");
+// snd_soc_dapm_disable_pin(codec, "Headphone Amplifier");
+
+ /* TWL4030 not connected pins */
+ // 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(dapm, "PREDRIVEL");
+ snd_soc_dapm_nc_pin(dapm, "PREDRIVER");
+ // snd_soc_dapm_nc_pin(codec, "HSOL");
+ // snd_soc_dapm_nc_pin(codec, "HSOR");
+ snd_soc_dapm_nc_pin(dapm, "CARKITMIC");
+ snd_soc_dapm_nc_pin(dapm, "CARKITL");
+ snd_soc_dapm_nc_pin(dapm, "CARKITR");
+ // snd_soc_dapm_nc_pin(codec, "HFL");
+ // snd_soc_dapm_nc_pin(codec, "HFR");
+ // snd_soc_dapm_nc_pin(codec, "VIBRA");
+ // snd_soc_dapm_nc_pin(codec, "HSMIC");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC0");
+ snd_soc_dapm_nc_pin(dapm, "DIGIMIC1");
+
+ /* We can detect when something is plugged in,
+ * but we need to poll :-(
+ */
+ ret = snd_soc_jack_new(codec, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &jack.hs_jack);
+ if (ret)
+ return ret;
+ INIT_DELAYED_WORK(&jack.jack_work, gta04_audio_jack_work);
+ jack.codec = codec;
+ jack.hs_jack.jack->input_dev->open = gta04_jack_open;
+ jack.hs_jack.jack->input_dev->close = gta04_jack_close;
+
+ return snd_soc_dapm_sync(dapm);
+}
+
+static struct snd_soc_ops omap3gta04_ops = {
+ .hw_params = omap3gta04_hw_params,
+};
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link omap3gta04_dai = {
+ .name = "TWL4030",
+ .stream_name = "TWL4030",
+ .cpu_dai_name = "omap-mcbsp.2",
+ .platform_name = "omap-pcm-audio",
+ .codec_dai_name = "twl4030-hifi",
+ .codec_name = "twl4030-codec",
+ .dai_fmt = (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM),
+ .ops = &omap3gta04_ops,
+ .init = &omap3gta04_init
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_omap3gta04 = {
+ .name = "gta04",
+ .owner = THIS_MODULE,
+ .dai_link = &omap3gta04_dai,
+ .num_links = 1,
+ .suspend_pre = gta04_audio_suspend,
+ .resume_post = gta04_audio_resume,
+};
+
+static struct platform_device *omap3gta04_snd_device;
+
+static int __init omap3gta04_soc_init(void)
+{
+ int ret;
+
+#if 0
+ if (!machine_is_gta04() && !machine_is_omap3_gta04()) {
+ pr_debug("Not GTA04!\n");
+ return -ENODEV;
+ }
+#endif
+ pr_info("GTA04 OMAP3 SoC snd init\n");
+
+ // FIXME: set any GPIOs i.e. enable Audio in/out switch
+ // microphone power etc.
+
+ omap3gta04_snd_device = platform_device_alloc("soc-audio", 0);
+ if (!omap3gta04_snd_device) {
+ printk(KERN_ERR "Platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(omap3gta04_snd_device, &snd_soc_omap3gta04);
+
+ ret = platform_device_add(omap3gta04_snd_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ printk(KERN_ERR "Unable to add platform device\n");
+ platform_device_put(omap3gta04_snd_device);
+
+ return ret;
+}
+
+static void __exit omap3gta04_soc_exit(void)
+{
+ platform_device_unregister(omap3gta04_snd_device);
+}
+
+module_init(omap3gta04_soc_init);
+module_exit(omap3gta04_soc_exit);
+
+MODULE_AUTHOR("Steve Sakoman <steve@sakoman.com>");
+MODULE_DESCRIPTION("ALSA SoC OMAP3 GTA04");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/omap/gta04-fm.c b/sound/soc/omap/gta04-fm.c
new file mode 100644
index 0000000..3bc0a0f
--- /dev/null
+++ b/sound/soc/omap/gta04-fm.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2011 John Ogness
+ * Author: John Ogness <john.ogness@linutronix.de>
+ *
+ * based on sound/soc/omap/omap3beagle.c by
+ * Steve Sakoman <steve@sakoman.com>
+ *
+ * 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/platform_device.h>
+
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "mcbsp.h"
+#include "omap-mcbsp.h"
+#include "../codecs/si47xx.h"
+
+/* FCLK is 96 MHz and is divided by the CLOCK_DIVISOR to generate the McBSP CLKX signal
+ going to DCLK of the Si47xx.
+
+ The DCLK clock cycle time should be between 26 ns (38.4 MHz) and 420 ns (2.38 MHz).
+
+ Please note that the Si47xx requires to set the sampling rate property to 0 Hz
+ through I2C/SPI before removing (lowering < 2 MHz) or applying the clock.
+ Otherwise the chip may need a reset.
+
+ 2.5 MHz is sufficient for 24 bit stereo with 48 kHz sample rate while 2.594 MHz
+ tries to avoid harmonics at typical FM frequencies like 93.5 or 98.5 MHz. But
+ we can't avoid the 96.0 MHz jamming. So we choose a CLOCK_DIVISOR of 37.
+
+ */
+
+#define IN_FREQUENCY (96000000)
+#define CLOCK_DIVISOR (IN_FREQUENCY / 2594000) /* 37 */
+
+static int gta04_fm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ /* setup codec dai and cpu dai hardware 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->cpu_dai;
+ unsigned int fmt;
+ int ret;
+
+ printk("gta04_fm_hw_params\n");
+ fmt = SND_SOC_DAIFMT_I2S | /* I2S */
+ SND_SOC_DAIFMT_NB_NF | // is this the right setting?
+ SND_SOC_DAIFMT_CBS_CFS; /* clock and frame signals go from McBSP to Si47xx receiver */
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKS_FCLK, IN_FREQUENCY,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu system clock\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_clkdiv(cpu_dai, OMAP_MCBSP_CLKGDV, CLOCK_DIVISOR);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu clock divisor\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int gta04_fm_init(struct snd_soc_pcm_runtime *runtime)
+{
+ /* add controls */
+ /* add routes */
+ /* setup pins */
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+static int gta04_fm_startup(struct snd_pcm_substream *substream)
+{
+ printk("gta04_fm_startup\n");
+ /* enable clock used by codec */
+ return 0;
+}
+
+static void gta04_fm_shutdown(struct snd_pcm_substream *substream)
+{
+ printk("gta04_fm_shutdown\n");
+ /* disable clock used by codec */
+}
+
+static struct snd_soc_ops gta04_fm_ops = {
+ .startup = gta04_fm_startup,
+ .hw_params = gta04_fm_hw_params,
+ .shutdown = gta04_fm_shutdown,
+};
+
+/* digital fm interface glue - connects codec <--> cpu */
+static struct snd_soc_dai_link gta04_fm_dai = {
+ .name = "Si47xx",
+ .stream_name = "Si47xx",
+ .cpu_dai_name = "omap-mcbsp.1",
+ .platform_name = "omap-pcm-audio",
+ .codec_dai_name = "Si47xx",
+ .codec_name = "si47xx_codec_audio",
+ .init = gta04_fm_init,
+ .ops = &gta04_fm_ops,
+};
+
+/* fm machine driver */
+static struct snd_soc_card gta04_fm_card = {
+ .name = "gta04-fm",
+ .dai_link = &gta04_fm_dai,
+ .num_links = 1,
+};
+
+#if 0 // this code did configure for I2C in 2.6.32 - how to do it now?
+
+/* fm subsystem */
+static struct si47xx_setup_data gta04_fm_soc_data = {
+ .i2c_bus = 2,
+ .i2c_address = 0x11,
+};
+static struct snd_soc_device gta04_fm_devdata = {
+ .card = &gta04_fm_card,
+ .codec_dev = &soc_codec_dev_si47xx,
+ .codec_data = &gta04_fm_soc_data,
+};
+#endif
+
+static struct platform_device *gta04_fm_snd_device;
+
+static int __init gta04_fm_soc_init(void)
+{
+ struct device *dev;
+ int ret;
+
+ pr_info("gta04-fm SoC init\n");
+
+ gta04_fm_snd_device = platform_device_alloc("soc-audio", 3);
+ if (!gta04_fm_snd_device) {
+ printk(KERN_ERR "platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ dev = &gta04_fm_snd_device->dev;
+
+ platform_set_drvdata(gta04_fm_snd_device, &gta04_fm_card);
+
+ ret = platform_device_add(gta04_fm_snd_device);
+ if (ret) {
+ printk(KERN_ERR "unable to add platform device\n");
+ platform_device_put(gta04_fm_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit gta04_fm_soc_exit(void)
+{
+ platform_device_unregister(gta04_fm_snd_device);
+}
+
+module_init(gta04_fm_soc_init);
+module_exit(gta04_fm_soc_exit);
+
+MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
+MODULE_DESCRIPTION("ALSA SoC GTA04 FM");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/omap/gta04-headset.c b/sound/soc/omap/gta04-headset.c
new file mode 100644
index 0000000..61162ab
--- /dev/null
+++ b/sound/soc/omap/gta04-headset.c
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011 John Ogness
+ * Author: John Ogness <john.ogness@linutronix.de>
+ *
+ * based on sound/soc/omap/omap3beagle.c by
+ * Steve Sakoman <steve@sakoman.com>
+ *
+ * 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/platform_device.h>
+
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "omap-mcbsp.h"
+#include "../codecs/w2cbw003-bt.h"
+
+static int gta04_headset_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ /* setup codec dai and cpu dai hardware 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->cpu_dai;
+ unsigned int fmt;
+ int ret;
+
+ fmt = SND_SOC_DAIFMT_I2S | // I2S
+ SND_SOC_DAIFMT_IB_IF | // positive sync pulse, driven on rising, sampled on falling clock
+ SND_SOC_DAIFMT_CBM_CFM; // clocks come from bluetooth modem - but this can be configured in the Modem chip
+
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(cpu_dai, OMAP_MCBSP_SYSCLK_CLKX_EXT, 0,
+ SND_SOC_CLOCK_IN);
+ // FIXME: set clock divisor
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int gta04_headset_init(struct snd_soc_pcm_runtime *runtime)
+{
+ /* add controls */
+ /* add routes */
+ /* setup pins */
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+static int gta04_headset_startup(struct snd_pcm_substream *substream)
+{
+ /* enable clock used by codec */
+ return 0;
+}
+
+static void gta04_headset_shutdown(struct snd_pcm_substream *substream)
+{
+ /* disable clock used by codec */
+}
+
+static struct snd_soc_ops gta04_headset_ops = {
+ .startup = gta04_headset_startup,
+ .hw_params = gta04_headset_hw_params,
+ .shutdown = gta04_headset_shutdown,
+};
+
+/* digital headset interface glue - connects codec <--> cpu */
+static struct snd_soc_dai_link gta04_headset_dai = {
+ .name = "W2CBW003",
+ .stream_name = "W2CBW003",
+ .cpu_dai_name = "omap-mcbsp.3",
+ .platform_name = "omap-pcm-audio",
+ .codec_dai_name = "W2CBW003",
+ .codec_name = "w2cbw003_codec_audio",
+ .init = gta04_headset_init,
+ .ops = &gta04_headset_ops,
+};
+
+/* headset machine driver */
+static struct snd_soc_card gta04_headset_card = {
+ .name = "gta04-headset",
+ .dai_link = &gta04_headset_dai,
+ .num_links = 1,
+};
+
+static struct platform_device *gta04_headset_snd_device;
+
+static int __init gta04_headset_soc_init(void)
+{
+ struct device *dev;
+ int ret;
+
+ pr_info("gta04-headset SoC init\n");
+
+ gta04_headset_snd_device = platform_device_alloc("soc-audio", 2);
+ if (!gta04_headset_snd_device) {
+ printk(KERN_ERR "platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ dev = &gta04_headset_snd_device->dev;
+
+ platform_set_drvdata(gta04_headset_snd_device, &gta04_headset_card);
+
+ ret = platform_device_add(gta04_headset_snd_device);
+ if (ret) {
+ printk(KERN_ERR "unable to add platform device\n");
+ platform_device_put(gta04_headset_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit gta04_headset_soc_exit(void)
+{
+ platform_device_unregister(gta04_headset_snd_device);
+}
+
+module_init(gta04_headset_soc_init);
+module_exit(gta04_headset_soc_exit);
+
+MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
+MODULE_DESCRIPTION("ALSA SoC GTA04 Headset");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/omap/gta04-jack.c b/sound/soc/omap/gta04-jack.c
new file mode 100644
index 0000000..0d96582
--- /dev/null
+++ b/sound/soc/omap/gta04-jack.c
@@ -0,0 +1,230 @@
+
+/*
+ * Jack driver for GTA04.
+ * Copyright Neil Brown <neilb@suse.de> 2013
+ *
+ * The DC current through the headset microphone pins is
+ * converted to a voltage which is presented on TWL4030 madc 7.
+ * To be able to read a current, the Headset Mic Bias must
+ * be enabled.
+ *
+ * When the jack device is open, enable the Headset Mic Bias
+ * and poll mdac 7 every 500msec. Once we see an insertion,
+ * we increase the rate to ever 50msec until we see a removal.
+ *
+ * There are 4 possible states:
+ * - Nothing plugged in, open circuit - voltage is low
+ * - short circuit due to headphone with no mic (3-contact TRS)
+ * inserted. Voltage is high.
+ * - short circuit due to button on headset being pushed.
+ * Voltage is also high.
+ * - Microphone is in circuit. Voltage is even higher. I don't
+ * understand how it can be higher than than with a short
+ * circuit, but that is what I measure.
+ *
+ * To differentiate between the two short circuits we look at how
+ * we got there. A transition from open to short means a 3-contact
+ * TRS with no mic. A transition from mic to short means the button
+ * on the mic was pressed.
+ *
+ * As different devices report different actual voltages we need
+ * some calibration. As we cannot do this automatically we complete
+ * precision, we allow user-space to tell us the calibration.
+ * We assume that open-circuit is always below 100, and other
+ * readings are above that.
+ * The highest level we see for 3 consecutive readings is assumed
+ * to be the 'microphone' level, and short-circuit is 5% below that.
+ * If headphones with no mic are inserted this will be wrong, but not
+ * terribly wrong. As soon as a headset with a mic is inserted it
+ * will get corrected and stay corrected.
+ * In order to keep this correct across a reboot, user-space can
+ * read the current setting from
+ * /sys/modules/snd_soc_gta04/parameters/jack_level.
+ * and then write back the value after reboot. Once a value is
+ * written, auto-calibration is disabled.
+ * Writing the value '0' can re-enable auto-calibration.
+ */
+
+#include <linux/input.h>
+#include <sound/jack.h>
+#include <sound/soc.h>
+#include <linux/suspend.h>
+#include <linux/i2c/twl4030-madc.h>
+#include <linux/module.h>
+
+static struct {
+ struct snd_soc_jack hs_jack;
+ struct delayed_work jack_work;
+ struct snd_soc_codec *codec;
+ int open;
+ /* When any jack is present, we:
+ * - poll more quickly to catch button presses
+ * - assume a 'short' is 'button press', not 'headset has
+ * no mic
+ * 'present' stores SND_JACK_HEADPHONE or SND_JACK_HEADSET
+ * indicating what we think is present.
+ */
+ int present;
+ /* Calibration reports a single number which roughly
+ * points to 'short'.
+ * Less than half this is 'open circuit'.
+ * More than this is 'microphone
+ */
+ long level;
+ int level_fixed;
+ long level_new;
+ int level_count;
+} jack;
+
+static void gta04_jack_work(struct work_struct *work)
+{
+ long val;
+ long delay = msecs_to_jiffies(500);
+ int jackbits;
+
+ /* choose delay *before* checking presence so we still get
+ * one long delay on first insertion to help with debounce.
+ */
+ if (jack.present)
+ delay = msecs_to_jiffies(50);
+
+ val = twl4030_get_madc_conversion(7);
+ if (val < 0)
+ goto out;
+ /* On my device:
+ * open circuit = around 20
+ * short circuit = around 800, or 325 on another device
+ * microphone = around 830-840 !!! 345 on other device.
+ */
+ if (!jack.level_fixed) {
+ if (jack.level * 21/20 + 2 < val) {
+ if (jack.level_count == 0 ||
+ val < jack.level_new*21/20)
+ jack.level_new = val*20/21;
+ if (jack.level_count >= 3) {
+ jack.level = jack.level_new;
+ jack.level_count = 0;
+ } else
+ jack.level_count += 1;
+ } else
+ jack.level_count = 0;
+ }
+ if (val < jack.level / 2) {
+ /* open circuit */
+ jackbits = 0;
+ jack.present = 0;
+ /* debounce */
+ delay = msecs_to_jiffies(500);
+ } else if (val < jack.level) {
+ /* short */
+ if (jack.present == 0) {
+ /* Inserted headset with no mic */
+ jack.present = SND_JACK_HEADPHONE;
+ jackbits = jack.present;
+ } else if (jack.present & SND_JACK_MICROPHONE) {
+ /* mic shorted -> button press */
+ jackbits = SND_JACK_BTN_0 | jack.present;
+ } else {
+ /* headphones still present */
+ jackbits = jack.present;
+ }
+ } else {
+ /* There is a microphone there */
+ jack.present = SND_JACK_HEADSET;
+ jackbits = jack.present;
+ }
+ snd_soc_jack_report(&jack.hs_jack, jackbits,
+ SND_JACK_HEADSET | SND_JACK_BTN_0);
+
+out:
+ if (jack.open)
+ schedule_delayed_work(&jack.jack_work, delay);
+}
+
+static int gta04_jack_pm_notify(struct notifier_block *b, unsigned long v, void *d)
+{
+ if (!jack.codec || !jack.open)
+ return 0;
+ switch(v) {
+ case PM_SUSPEND_PREPARE:
+ /* Disable Headset Mic Bias while asleep */
+ snd_soc_dapm_disable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+ break;
+
+ case PM_POST_SUSPEND:
+ snd_soc_dapm_force_enable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+ break;
+ default: break;
+ }
+ return 0;
+}
+
+static struct notifier_block gta04_jack_pm_notify_block = {
+ .notifier_call = gta04_jack_pm_notify,
+};
+
+static int gta04_jack_open(struct input_dev *dev)
+{
+ snd_soc_dapm_force_enable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+ jack.open = 1;
+ schedule_delayed_work(&jack.jack_work, msecs_to_jiffies(100));
+ return 0;
+}
+
+static void gta04_jack_close(struct input_dev *dev)
+{
+ jack.open = 0;
+ cancel_delayed_work_sync(&jack.jack_work);
+ snd_soc_dapm_disable_pin(&jack.codec->dapm, "Headset Mic Bias");
+ snd_soc_dapm_sync(&jack.codec->dapm);
+}
+
+int gta04_jack_probe(struct snd_soc_codec *codec)
+{
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ int ret;
+ ret = snd_soc_jack_new(codec, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0,
+ &jack.hs_jack);
+ if (ret)
+ return ret;
+ register_pm_notifier(&gta04_jack_pm_notify_block);
+
+ INIT_DELAYED_WORK(&jack.jack_work, gta04_jack_work);
+ jack.codec = codec;
+ if (jack.level < 100)
+ jack.level = 100;
+ jack.hs_jack.jack->input_dev->open = gta04_jack_open;
+ jack.hs_jack.jack->input_dev->close = gta04_jack_close;
+
+ return snd_soc_dapm_sync(dapm);
+}
+
+void gta04_jack_remove(struct snd_soc_codec *codec)
+{
+ unregister_pm_notifier(&gta04_jack_pm_notify_block);
+ cancel_delayed_work(&jack.jack_work);
+}
+
+static int get_level(char *buffer, struct kernel_param *kp)
+{
+ return sprintf(buffer, "%ld", jack.level);
+}
+static int set_level(const char *val, struct kernel_param *kp)
+{
+ long num;
+ if (kstrtol(val, 10, &num) < 0)
+ return -EINVAL;
+ if (num == 0) {
+ jack.level = 100;
+ jack.level_fixed = 0;
+ } else {
+ jack.level = num;
+ jack.level_fixed = 1;
+ }
+ return 0;
+}
+module_param_call(jack_level, set_level, get_level, NULL, S_IRUSR|S_IWUSR);
diff --git a/sound/soc/omap/gta04-voice.c b/sound/soc/omap/gta04-voice.c
new file mode 100644
index 0000000..bb54381
--- /dev/null
+++ b/sound/soc/omap/gta04-voice.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2011 John Ogness
+ * Author: John Ogness <john.ogness@linutronix.de>
+ *
+ * based on sound/soc/omap/omap3beagle.c by
+ * Steve Sakoman <steve@sakoman.com>
+ *
+ * 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/platform_device.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "omap-mcbsp.h"
+#include "../codecs/gtm601.h"
+
+static int gta04_voice_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ /* setup codec dai and cpu dai hardware 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->cpu_dai;
+ unsigned int fmt;
+ int ret;
+
+ fmt = SND_SOC_DAIFMT_I2S | // I2S
+ // SND_SOC_DAIFMT_GATED | // try to power down if not needed
+ SND_SOC_DAIFMT_IB_IF | // positive sync pulse, driven on rising, sampled on falling clock
+ SND_SOC_DAIFMT_CBM_CFM; // clocks come from GSM modem
+
+#if 0 // set clocks to be outputs
+
+ fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_IB_IF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+#endif
+ /* Set cpu DAI configuration */
+ ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu DAI configuration\n");
+ return ret;
+ }
+
+ if (ret < 0) {
+ printk(KERN_ERR "can't set cpu system clock\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int gta04_voice_init(struct snd_soc_pcm_runtime *runtime)
+{
+ /* add controls */
+ /* add routes */
+ /* setup pins */
+ struct snd_soc_codec *codec = runtime->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ snd_soc_dapm_sync(dapm);
+ return 0;
+}
+
+static int gta04_voice_startup(struct snd_pcm_substream *substream)
+{
+ /* enable clock used by codec */
+ /* clock is provided by the GTM601 */
+ return 0;
+}
+
+static void gta04_voice_shutdown(struct snd_pcm_substream *substream)
+{
+ /* disable clock used by codec */
+ /* clock is provided by the GTM601 */
+}
+
+static struct snd_soc_ops gta04_voice_ops = {
+ .startup = gta04_voice_startup,
+ .hw_params = gta04_voice_hw_params,
+ .shutdown = gta04_voice_shutdown,
+};
+
+/* digital voice interface glue - connects codec <--> cpu */
+static struct snd_soc_dai_link gta04_voice_dai = {
+ .name = "GTM601",
+ .stream_name = "GTM601",
+ .cpu_dai_name = "omap-mcbsp.4",
+ .platform_name = "omap-pcm-audio",
+ .codec_dai_name = "GTM601",
+ .codec_name = "gtm601_codec_audio",
+ .init = gta04_voice_init,
+ .ops = &gta04_voice_ops,
+};
+
+/* voice machine driver */
+static struct snd_soc_card gta04_voice_card = {
+ .name = "gta04-voice",
+ .dai_link = &gta04_voice_dai,
+ .num_links = 1,
+};
+
+/* voice subsystem */
+/*static struct snd_soc_device gta04_voice_devdata = {
+ .card = &gta04_voice_card,
+ .codec_dev = &soc_codec_dev_gtm601,
+};*/
+
+static struct platform_device *gta04_voice_snd_device;
+
+static int __init gta04_voice_soc_init(void)
+{
+ struct device *dev;
+ int ret;
+
+ pr_info("gta04-voice SoC init\n");
+
+ gta04_voice_snd_device = platform_device_alloc("soc-audio", 1);
+ if (!gta04_voice_snd_device) {
+ printk(KERN_ERR "platform device allocation failed\n");
+ return -ENOMEM;
+ }
+
+ dev = &gta04_voice_snd_device->dev;
+
+ platform_set_drvdata(gta04_voice_snd_device, &gta04_voice_card);
+
+ ret = platform_device_add(gta04_voice_snd_device);
+ if (ret) {
+ printk(KERN_ERR "unable to add platform device\n");
+ platform_device_put(gta04_voice_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit gta04_voice_soc_exit(void)
+{
+ platform_device_unregister(gta04_voice_snd_device);
+}
+
+module_init(gta04_voice_soc_init);
+module_exit(gta04_voice_soc_exit);
+
+MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
+MODULE_DESCRIPTION("ALSA SoC GTA04 Voice");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c
index 6c19bba..c10ddf1 100644
--- a/sound/soc/omap/omap-mcbsp.c
+++ b/sound/soc/omap/omap-mcbsp.c
@@ -39,6 +39,8 @@
#include "mcbsp.h"
#include "omap-mcbsp.h"
+#include "../../../arch/arm/mach-omap2/mux.h"
+
#define OMAP_MCBSP_RATES (SNDRV_PCM_RATE_8000_96000)
#define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \
@@ -542,6 +544,36 @@ static int omap_mcbsp_dai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
return err;
}
+/* tristate the McBSP-DX line so that we can build a PCM bus
+ with several sources (e.g. McBSP4, TPS65950 and a Modem) */
+
+static int omap_mcbsp_dai_set_tristate(struct snd_soc_dai *dai,
+ int tristate)
+{
+ int mode = OMAP_MUX_MODE0;
+ int gpio; /* the GPIO name of the McBSP-DX line so that we can reference it */
+
+ // check for OMAP3 (no need to support OMAP1/2 any more - and for OMAP4 we don't care yet)
+
+ /* unfortunately the mapping between McBSP channels and GPIO pins
+ is not fixed, it depends on how the hardware is set up. */
+
+ switch(dai->id + 1) {
+ case 1: gpio=158; break;
+ case 2: gpio=119; break;
+ case 3: gpio=140; break; /* may also be GPIO 144 or 158 in different modes! */
+ case 4: gpio=154; break; /* may also be on GPIO 57 in different mode! */
+ case 5: gpio=20; mode = OMAP_MUX_MODE1; break;
+ default: return -EIO;
+ }
+
+ /* should check for errors */
+
+ omap_mux_set_gpio((tristate ? OMAP_MUX_MODE7 : (mode | OMAP_PIN_OUTPUT)), gpio);
+
+ return 0;
+}
+
static const struct snd_soc_dai_ops mcbsp_dai_ops = {
.startup = omap_mcbsp_dai_startup,
.shutdown = omap_mcbsp_dai_shutdown,
@@ -551,6 +583,7 @@ static const struct snd_soc_dai_ops mcbsp_dai_ops = {
.set_fmt = omap_mcbsp_dai_set_dai_fmt,
.set_clkdiv = omap_mcbsp_dai_set_clkdiv,
.set_sysclk = omap_mcbsp_dai_set_dai_sysclk,
+ .set_tristate = omap_mcbsp_dai_set_tristate,
};
static int omap_mcbsp_probe(struct snd_soc_dai *dai)
diff --git a/sound/soc/omap/omap-twl4030.c b/sound/soc/omap/omap-twl4030.c
index 2a9324f..3824f15 100644
--- a/sound/soc/omap/omap-twl4030.c
+++ b/sound/soc/omap/omap-twl4030.c
@@ -47,6 +47,7 @@
struct omap_twl4030 {
int jack_detect; /* board can detect jack events */
struct snd_soc_jack hs_jack;
+ void (*jack_remove)(struct snd_soc_codec *codec);
};
static int omap_twl4030_hw_params(struct snd_pcm_substream *substream,
@@ -229,6 +230,10 @@ static int omap_twl4030_init(struct snd_soc_pcm_runtime *rtd)
twl4030_disconnect_pin(dapm, pdata->has_digimic1, "Digital1 Mic");
twl4030_disconnect_pin(dapm, pdata->has_linein, "Line In");
+ if (pdata->jack_init &&
+ pdata->jack_init(codec))
+ priv->jack_remove = pdata->jack_remove;
+
return ret;
}
@@ -357,6 +362,8 @@ static int omap_twl4030_remove(struct platform_device *pdev)
snd_soc_jack_free_gpios(&priv->hs_jack,
ARRAY_SIZE(hs_jack_gpios),
hs_jack_gpios);
+ if (priv->jack_remove)
+ priv->jack_remove(NULL);
snd_soc_unregister_card(card);
return 0;