aboutsummaryrefslogtreecommitdiffstats
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/Kconfig24
-rw-r--r--sound/soc/codecs/Makefile10
-rw-r--r--sound/soc/codecs/gtm601.c95
-rw-r--r--sound/soc/codecs/gtm601.h23
-rw-r--r--sound/soc/codecs/si47xx.c497
-rw-r--r--sound/soc/codecs/si47xx.h23
-rw-r--r--sound/soc/codecs/twl4030.c134
-rw-r--r--sound/soc/codecs/twl4030.h53
-rw-r--r--sound/soc/codecs/w2cbw003-bt.c100
-rw-r--r--sound/soc/codecs/w2cbw003-bt.h23
-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
19 files changed, 2124 insertions, 3 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 15106c0..f3531b2 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -132,6 +132,9 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
+ select SND_SOC_GTM601
+ select SND_SOC_SI47XX if I2C
+ select SND_SOC_W2CBW003
help
Normally ASoC codec drivers are only built if a machine driver which
uses them is also built since they are only usable with a machine
@@ -552,3 +555,24 @@ config SND_SOC_ML26124
config SND_SOC_TPA6130A2
tristate
+
+config SND_SOC_GTM601
+ tristate
+ help
+ PCM interface to a GTM601 UMTS modem chip. Does not control the Modem through USB.
+
+config SND_SOC_WM2000
+ tristate
+
+config SND_SOC_WM9090
+ tristate
+
+config SND_SOC_SI47XX
+ tristate
+ help
+ PCM interface to a Si47xx chip. Needs to enable some I2C or SPI driver.
+
+config SND_SOC_W2CBW003
+ tristate
+ help
+ PCM interface to Bluetooth part of W2CBW003. Does not control the bluetooth headset link.
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index bc12676..03258ec 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -128,6 +128,11 @@ snd-soc-wm-hubs-objs := wm_hubs.o
# Amp
snd-soc-max9877-objs := max9877.o
snd-soc-tpa6130a2-objs := tpa6130a2.o
+snd-soc-wm2000-objs := wm2000.o
+snd-soc-wm9090-objs := wm9090.o
+snd-soc-gtm601-objs := gtm601.o
+snd-soc-si47xx-objs := si47xx.o
+snd-soc-w2cbw003-bt-objs := w2cbw003-bt.o
obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o
obj-$(CONFIG_SND_SOC_AB8500_CODEC) += snd-soc-ab8500-codec.o
@@ -258,3 +263,8 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o
+obj-$(CONFIG_SND_SOC_WM2000) += snd-soc-wm2000.o
+obj-$(CONFIG_SND_SOC_WM9090) += snd-soc-wm9090.o
+obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o
+obj-$(CONFIG_SND_SOC_SI47XX) += snd-soc-si47xx.o
+obj-$(CONFIG_SND_SOC_W2CBW003) += snd-soc-w2cbw003-bt.o
diff --git a/sound/soc/codecs/gtm601.c b/sound/soc/codecs/gtm601.c
new file mode 100644
index 0000000..852ef71
--- /dev/null
+++ b/sound/soc/codecs/gtm601.c
@@ -0,0 +1,95 @@
+/*
+ * This is a simple driver for the GTM601 Voice PCM interface
+ *
+ * gtm601.c
+ *
+ * Created on: 15-Oct-2009
+ * Author: neil.jones@imgtec.com
+ *
+ * Copyright (C) 2009 Imagination Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "gtm601.h"
+/*
+ * Note this is a simple chip with no configuration interface, sample rate is
+ * determined automatically by examining the Master clock and Bit clock ratios
+ */
+
+#define GTM601_RATES (SNDRV_PCM_RATE_8000) // CHECKME
+
+struct snd_soc_dai_driver gtm601_dai = {
+ .name = "GTM601",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1, // CHECKME
+ .channels_max = 1,
+ .rates = GTM601_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE, /* this is the only format the
+ * omap-mcbsp-dai understands */
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = GTM601_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+};
+
+struct snd_soc_codec_driver soc_codec_dev_gtm601;
+
+static int gtm601_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_gtm601, &gtm601_dai, 1);
+}
+
+static int gtm601_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:gtm601_codec_audio");
+
+static struct platform_driver gtm601_codec_driver = {
+ .driver = {
+ .name = "gtm601_codec_audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = gtm601_platform_probe,
+ .remove = gtm601_platform_remove,
+};
+
+static int __init gtm601_init(void)
+{
+ return platform_driver_register(&gtm601_codec_driver);
+}
+module_init(gtm601_init);
+
+static void __exit gtm601_exit(void)
+{
+ platform_driver_unregister(&gtm601_codec_driver);
+}
+module_exit(gtm601_exit);
+
+MODULE_DESCRIPTION("ASoC GTM601 driver");
+MODULE_AUTHOR("Neil Jones");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/gtm601.h b/sound/soc/codecs/gtm601.h
new file mode 100644
index 0000000..b8fcf4f
--- /dev/null
+++ b/sound/soc/codecs/gtm601.h
@@ -0,0 +1,23 @@
+/*
+ * gtm601.h
+ *
+ * Created on: 15-Oct-2009
+ * Author: neil.jones@imgtec.com
+ *
+ * Copyright (C) 2009 Imagination Technologies Ltd.
+ *
+ * Adapted on: 20-Aug-2011
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef GTM601_H_
+#define GTM601_H_
+
+extern struct snd_soc_dai_driver gtm601_dai;
+extern struct snd_soc_codec_driver soc_codec_dev_gtm601;
+
+#endif /* GTM601_H_ */
diff --git a/sound/soc/codecs/si47xx.c b/sound/soc/codecs/si47xx.c
new file mode 100644
index 0000000..f515969
--- /dev/null
+++ b/sound/soc/codecs/si47xx.c
@@ -0,0 +1,497 @@
+/*
+ * FIXME: this is a blueprint for a Si47xx I2C driver
+ * it is based on the WM8728 driver and
+ * needs to be adapted for GTA04
+ *
+ * si47xx.c -- Si47xx ALSA SoC Audio driver
+ *
+ * Copyright 2008 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.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.
+ */
+
+// temporary hack until someone understands how to configure the I2C address in the
+// board file and gets the driver loaded with I2C address 0x11
+// all unused code and the #if USE_I2C_SPI should be removed as soon as I2C works
+
+#define USE_I2C_SPI 0
+
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "si47xx.h"
+
+// FIXME: for the Si47xx we don't need the register cache mechanism
+// FIXME: we must make sure that the digital clock (DCLK) is stable
+// and the sample rate is set to 0Hz if we disable the clock!
+// this happens when arecord is started/stopped, i.e. the clock is gated
+
+/*
+ * We can't read the WM8728 register space so we cache them instead.
+ * Note that the defaults here aren't the physical defaults, we latch
+ * the volume update bits, mute the output and enable infinite zero
+ * detect.
+ */
+static const struct reg_default si47xx_reg_defaults[] = {
+ { 0, 0x1ff },
+ { 1, 0x1ff },
+ { 2, 0x001 },
+ { 3, 0x100 },
+};
+
+/* codec private data */
+struct si47xx_priv {
+ struct regmap *regmap;
+};
+
+static const DECLARE_TLV_DB_SCALE(si47xx_tlv, -12750, 50, 1);
+
+static const struct snd_kcontrol_new si47xx_snd_controls[] = {
+#if USE_I2C_SPI // these controls call snd_soc_read(codev, ...)
+
+SOC_DOUBLE_R_TLV("Digital Playback Volume", SI47XX_DACLVOL, SI47XX_DACRVOL,
+ 0, 255, 0, si47xx_tlv),
+
+SOC_SINGLE("Deemphasis", SI47XX_DACCTL, 1, 1, 0),
+#endif
+ /* code fragment
+ SOC_SINGLE("Frequency", SI47XX_DACCTL, 1, 1, 0),
+ */
+};
+
+/*
+ * DAPM controls.
+ */
+static const struct snd_soc_dapm_widget si47xx_dapm_widgets[] = {
+SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_OUTPUT("VOUTL"),
+SND_SOC_DAPM_OUTPUT("VOUTR"),
+ /*
+ * here (???) we should add controls for
+ * - tuning the receiver
+ * - tuning the transmitter
+ * - scanning for stations
+ * - getting RSSI & SNR
+ * - RDS
+ * so that we can read/write them through the amixer get/set commands
+ */
+};
+
+static const struct snd_soc_dapm_route si47xx_intercon[] = {
+ {"VOUTL", NULL, "DAC"},
+ {"VOUTR", NULL, "DAC"},
+};
+
+static int si47xx_mute(struct snd_soc_dai *dai, int mute)
+{
+#if 0
+ struct snd_soc_codec *codec = dai->codec;
+ u16 mute_reg = snd_soc_read(codec, SI47XX_DACCTL);
+ if (mute)
+ snd_soc_write(codec, SI47XX_DACCTL, mute_reg | 1);
+ else
+ snd_soc_write(codec, SI47XX_DACCTL, mute_reg & ~1);
+#endif
+ return 0;
+}
+
+static int si47xx_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+#if 0
+ struct snd_soc_codec *codec = dai->codec;
+ u16 dac = snd_soc_read(codec, SI47XX_DACCTL);
+
+ dac &= ~0x18;
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ dac |= 0x10;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ dac |= 0x08;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_write(codec, SI47XX_DACCTL, dac);
+#endif
+ return 0;
+}
+
+static int si47xx_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+#if 0
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = snd_soc_read(codec, SI47XX_IFCTL);
+
+ /* Currently only I2S is supported by the driver, though the
+ * hardware is more flexible.
+ */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* The hardware only support full slave mode */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ iface &= ~0x22;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= 0x20;
+ iface &= ~0x02;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= 0x02;
+ iface &= ~0x20;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= 0x22;
+ break;
+ default:
+ return -EINVAL;
+ }
+ snd_soc_write(codec, SI47XX_IFCTL, iface);
+#endif
+ return 0;
+}
+
+static int si47xx_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+#if 0
+ struct si47xx_priv *si47xx = snd_soc_codec_get_drvdata(codec);
+ u16 reg;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
+ /* Power everything up... */
+ reg = snd_soc_read(codec, SI47XX_DACCTL);
+ snd_soc_write(codec, SI47XX_DACCTL, reg & ~0x4);
+
+ /* ..then sync in the register cache. */
+ regcache_sync(si47xx->regmap);
+ }
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ reg = snd_soc_read(codec, SI47XX_DACCTL);
+ snd_soc_write(codec, SI47XX_DACCTL, reg | 0x4);
+ break;
+ }
+ codec->dapm.bias_level = level;
+#endif
+ return 0;
+}
+
+// FIXME: adjust what the Si47xx chip needs...
+#define SI47XX_RATES (SNDRV_PCM_RATE_8000_192000)
+
+#define SI47XX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops si47xx_dai_ops = {
+ .hw_params = si47xx_hw_params,
+ .digital_mute = si47xx_mute,
+ .set_fmt = si47xx_set_dai_fmt,
+};
+
+struct snd_soc_dai_driver si47xx_dai = {
+ .name = "Si47xx",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SI47XX_RATES,
+ .formats = SI47XX_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SI47XX_RATES,
+ .formats = SI47XX_FORMATS,
+ },
+ .ops = &si47xx_dai_ops,
+};
+
+static int si47xx_suspend(struct snd_soc_codec *codec)
+{
+ printk("si47xx_suspend\n");
+ si47xx_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ /* send power off command */
+ return 0;
+}
+
+static int si47xx_resume(struct snd_soc_codec *codec)
+{
+ printk("si47xx_resume\n");
+ si47xx_set_bias_level(codec, codec->dapm.suspend_bias_level);
+ /* send power on command */
+ /* tune to current frequency */
+ return 0;
+}
+
+static int si47xx_probe(struct snd_soc_codec *codec)
+{
+ int ret;
+ printk("si47xx_probe\n");
+#if USE_I2C_SPI
+ ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_REGMAP);
+ if (ret < 0) {
+ printk(KERN_ERR "si47xx: failed to configure cache I/O: %d\n",
+ ret);
+ return ret;
+ }
+#else
+ ret = 0;
+#endif
+ /* power on device */
+ si47xx_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ /* power on and ask for chip id */
+
+ return ret;
+}
+
+static int si47xx_remove(struct snd_soc_codec *codec)
+{
+ printk("si47xx_remove\n");
+ si47xx_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_si47xx = {
+ .probe = si47xx_probe,
+ .remove = si47xx_remove,
+ .suspend = si47xx_suspend,
+ .resume = si47xx_resume,
+ .set_bias_level = si47xx_set_bias_level,
+ .controls = si47xx_snd_controls,
+ .num_controls = ARRAY_SIZE(si47xx_snd_controls),
+ .dapm_widgets = si47xx_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(si47xx_dapm_widgets),
+ .dapm_routes = si47xx_intercon,
+ .num_dapm_routes = ARRAY_SIZE(si47xx_intercon),
+};
+
+#if USE_I2C_SPI
+static const struct of_device_id si47xx_of_match[] = {
+ { .compatible = "si4705,si4721", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, si47xx_of_match);
+
+static const struct regmap_config si47xx_regmap = {
+ .reg_bits = 7,
+ .val_bits = 9,
+ .max_register = SI47XX_IFCTL,
+
+ .reg_defaults = si47xx_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(si47xx_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+#if defined(CONFIG_SPI_MASTER)
+static int si47xx_spi_probe(struct spi_device *spi)
+{
+ struct si47xx_priv *si47xx;
+ int ret;
+
+ printk("si47xx_spi_probe\n");
+ si47xx = devm_kzalloc(&spi->dev, sizeof(struct si47xx_priv),
+ GFP_KERNEL);
+ if (si47xx == NULL)
+ return -ENOMEM;
+
+ si47xx->regmap = devm_regmap_init_spi(spi, &si47xx_regmap);
+ if (IS_ERR(si47xx->regmap))
+ return PTR_ERR(si47xx->regmap);
+
+ spi_set_drvdata(spi, si47xx);
+
+ ret = snd_soc_register_codec(&spi->dev,
+ &soc_codec_dev_si47xx, &si47xx_dai, 1);
+
+ return ret;
+}
+
+static int si47xx_spi_remove(struct spi_device *spi)
+{
+ snd_soc_unregister_codec(&spi->dev);
+
+ return 0;
+}
+
+static struct spi_driver si47xx_spi_driver = {
+ .driver = {
+ .name = "si47xx_codec_audio",
+ .owner = THIS_MODULE,
+ .of_match_table = si47xx_of_match,
+ },
+ .probe = si47xx_spi_probe,
+ .remove = si47xx_spi_remove,
+};
+#endif /* CONFIG_SPI_MASTER */
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+static int si47xx_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct si47xx_priv *si47xx;
+ int ret;
+
+ printk("si47xx_i2c_probe\n");
+ si47xx = devm_kzalloc(&i2c->dev, sizeof(struct si47xx_priv),
+ GFP_KERNEL);
+ if (si47xx == NULL)
+ return -ENOMEM;
+
+ si47xx->regmap = devm_regmap_init_i2c(i2c, &si47xx_regmap);
+ if (IS_ERR(si47xx->regmap))
+ return PTR_ERR(si47xx->regmap);
+
+ i2c_set_clientdata(i2c, si47xx);
+
+ ret = snd_soc_register_codec(&i2c->dev,
+ &soc_codec_dev_si47xx, &si47xx_dai, 1);
+
+ return ret;
+}
+
+static int si47xx_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ return 0;
+}
+
+static const struct i2c_device_id si47xx_i2c_id[] = {
+ { "si4705", 0 },
+ { "si4721", 1 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, si47xx_i2c_id);
+
+static struct i2c_driver si47xx_i2c_driver = {
+ .driver = {
+ .name = "si47xx_codec_audio",
+ .owner = THIS_MODULE,
+ .of_match_table = si47xx_of_match,
+ },
+ .probe = si47xx_i2c_probe,
+ .remove = si47xx_i2c_remove,
+ .id_table = si47xx_i2c_id,
+};
+#endif
+
+#else // USE_I2C_SPI
+
+static int si47xx_platform_probe(struct platform_device *pdev)
+{
+ printk("si47xx_platform_probe\n");
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_si47xx, &si47xx_dai, 1);
+}
+
+static int si47xx_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:si47xx_codec_audio");
+
+static struct platform_driver si47xx_codec_driver = {
+ .driver = {
+ .name = "si47xx_codec_audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = si47xx_platform_probe,
+ .remove = si47xx_platform_remove,
+};
+#endif // USE_I2C_SPI
+
+static int __init si47xx_modinit(void)
+{
+#if USE_I2C_SPI
+ int ret = 0;
+ printk("si47xx_modinit I2C/SPI\n");
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&si47xx_i2c_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register si47xx I2C driver: %d\n",
+ ret);
+ }
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ ret = spi_register_driver(&si47xx_spi_driver);
+ if (ret != 0) {
+ printk(KERN_ERR "Failed to register si47xx SPI driver: %d\n",
+ ret);
+ }
+#endif
+ return ret;
+#else
+ printk("si47xx_modinit\n");
+ return platform_driver_register(&si47xx_codec_driver);
+#endif
+}
+module_init(si47xx_modinit);
+
+static void __exit si47xx_exit(void)
+{
+#if USE_I2C_SPI
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&si47xx_i2c_driver);
+#endif
+#if defined(CONFIG_SPI_MASTER)
+ spi_unregister_driver(&si47xx_spi_driver);
+#endif
+#else
+ platform_driver_unregister(&si47xx_codec_driver);
+#endif
+}
+module_exit(si47xx_exit);
+
+MODULE_DESCRIPTION("ASoC Si47xx driver");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/si47xx.h b/sound/soc/codecs/si47xx.h
new file mode 100644
index 0000000..6f3dd9a
--- /dev/null
+++ b/sound/soc/codecs/si47xx.h
@@ -0,0 +1,23 @@
+/*
+ * si47xx.h -- Si47xx ASoC codec driver
+ *
+ * Copyright 2008 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * Adapted: 2011, 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.
+ */
+
+#ifndef _SI47XX_H
+#define _SI47XX_H
+
+#define SI47XX_DACLVOL 0x00
+#define SI47XX_DACRVOL 0x01
+#define SI47XX_DACCTL 0x02
+#define SI47XX_IFCTL 0x03
+
+#endif
diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c
index 1e3884d..8fe46fb 100644
--- a/sound/soc/codecs/twl4030.c
+++ b/sound/soc/codecs/twl4030.c
@@ -37,7 +37,8 @@
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/tlv.h>
-
+/* ouch, should not be inclueded here! */
+#include "../../../arch/arm/mach-omap2/mux.h"
/* Register descriptions are here */
#include <linux/mfd/twl4030-audio.h>
@@ -157,10 +158,12 @@ struct twl4030_priv {
u8 earpiece_enabled;
u8 predrivel_enabled, predriver_enabled;
u8 carkitl_enabled, carkitr_enabled;
-
+ u8 voice_enabled;
struct twl4030_codec_data *pdata;
};
+static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction,
+ int enable);
/*
* read twl4030 register cache
*/
@@ -196,8 +199,8 @@ static int twl4030_write(struct snd_soc_codec *codec,
{
struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
int write_to_reg = 0;
-
twl4030_write_reg_cache(codec, reg, value);
+ dev_dbg(codec->dev, "Set %02x = %02x", reg, value);
if (likely(reg < TWL4030_REG_SW_SHADOW)) {
/* Decide if the given register can be written */
switch (reg) {
@@ -443,6 +446,15 @@ static void twl4030_init_chip(struct snd_soc_codec *codec)
twl4030_write_reg_cache(codec, TWL4030_REG_ANAMICL, byte);
twl4030_codec_enable(codec, 0);
+ twl4030_write(codec,
+ TWL4030_VIF_TRI_EN,
+ TWL4030_REG_VOICE_IF);
+ twl4030_voice_enable(codec, SNDRV_PCM_STREAM_PLAYBACK, 1);
+ twl4030_voice_enable(codec, SNDRV_PCM_STREAM_CAPTURE, 1);
+ printk("TPS Voice IF is tristated\n");
+
+
+ twl4030_codec_enable(codec, 1);
}
static void twl4030_apll_enable(struct snd_soc_codec *codec, int enable)
@@ -932,6 +944,39 @@ static int digimic_event(struct snd_soc_dapm_widget *w,
return 0;
}
+/* is for some reason never reached */
+static int voice_input_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol,
+ int event)
+{
+ dev_dbg(w->codec->dev, "GSMIN event");
+ switch(event) {
+ case SND_SOC_DAPM_POST_PMU:
+ dev_dbg(w->codec->dev, "GSMIN power up");
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ dev_dbg(w->codec->dev, "GSMIN power down");
+ break;
+ }
+ return 0;
+}
+
+/* is for some reason never reached */
+static int voice_output_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ dev_dbg(w->codec->dev,"GSMOUT event");
+ switch(event) {
+ case SND_SOC_DAPM_POST_PMU:
+ dev_dbg(w->codec->dev, "GSMOUT power up");
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ dev_dbg(w->codec->dev, "GSMOUT power down");
+ break;
+ }
+ return 0;
+}
+
/*
* Some of the gain controls in TWL (mostly those which are associated with
* the outputs) are implemented in an interesting way:
@@ -1150,6 +1195,68 @@ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0);
*/
static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0);
+/*
+ * switch GSM audio signal between SoC"
+ * and twl4030 voice input
+ */
+static const char *twl4030_voice_route_texts[] = {
+ "Voice to SoC", "Voice to twl4030"
+};
+
+static const struct soc_enum twl4030_voice_route_enum =
+ SOC_ENUM_SINGLE(0xff,0,
+ ARRAY_SIZE(twl4030_voice_route_texts),
+ twl4030_voice_route_texts);
+
+static int twl4030_voice_route_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ ucontrol->value.enumerated.item[0] = twl4030->voice_enabled;
+ return 0;
+}
+
+static int twl4030_voice_route_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct twl4030_priv *twl4030 = snd_soc_codec_get_drvdata(codec);
+ dev_dbg(codec->dev, "voice ctl route: %u\n",
+ ucontrol->value.enumerated.item[0]);
+ if (ucontrol->value.enumerated.item[0] != twl4030->voice_enabled) {
+ int powered = twl4030->codec_powered;
+ twl4030->voice_enabled = ucontrol->value.enumerated.item[0];
+ if (powered)
+ twl4030_codec_enable(codec, 0);
+
+ if (twl4030->voice_enabled) {
+ /*
+ * need to find a better place for this,
+ * disables mcbsp4_dx, so that it can be used by
+ * the twl4030_codec
+ */
+ omap_mux_set_gpio(OMAP_MUX_MODE7, 154);
+ twl4030_write(codec, TWL4030_REG_VOICE_IF,
+ TWL4030_VIF_SLAVE_EN | TWL4030_VIF_DIN_EN |
+ TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN);
+ } else {
+ twl4030_write(codec, TWL4030_REG_VOICE_IF,
+ TWL4030_VIF_TRI_EN);
+ /*
+ * need to find a better place for this,
+ * enables mcbsp4_dx, so that it can be used by
+ * the mcbsp4 interface
+ */
+ omap_mux_set_gpio(OMAP_MUX_MODE0 | OMAP_PIN_OUTPUT, 154);
+ }
+ if (powered)
+ twl4030_codec_enable(codec, 1);
+ return 1;
+ }
+ return 0;
+}
+
/* AVADC clock priority */
static const char *twl4030_avadc_clk_priority_texts[] = {
"Voice high priority", "HiFi high priority"
@@ -1277,6 +1384,10 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = {
SOC_ENUM("AVADC Clock Priority", twl4030_avadc_clk_priority_enum),
+ SOC_ENUM_EXT("Voice route", twl4030_voice_route_enum,
+ twl4030_voice_route_get,
+ twl4030_voice_route_put),
+
SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum),
SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum),
@@ -1297,6 +1408,7 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
/* Digital microphones (Stereo) */
SND_SOC_DAPM_INPUT("DIGIMIC0"),
SND_SOC_DAPM_INPUT("DIGIMIC1"),
+ SND_SOC_DAPM_INPUT("GSMIN"),
/* Outputs */
SND_SOC_DAPM_OUTPUT("EARPIECE"),
@@ -1309,6 +1421,7 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
SND_SOC_DAPM_OUTPUT("HFL"),
SND_SOC_DAPM_OUTPUT("HFR"),
SND_SOC_DAPM_OUTPUT("VIBRA"),
+ SND_SOC_DAPM_OUTPUT("GSMOUT"),
/* AIF and APLL clocks for running DAIs (including loopback) */
SND_SOC_DAPM_OUTPUT("Virtual HiFi OUT"),
@@ -1337,6 +1450,17 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
SND_SOC_DAPM_SWITCH("Voice Analog Loopback", SND_SOC_NOPM, 0, 0,
&twl4030_dapm_abypassv_control),
+ /* PGA is a lie here */
+ SND_SOC_DAPM_MIC("Voice DigiInput", voice_input_event),
+/*
+ SND_SOC_DAPM_PGA_E("Voice DigiInput", SND_SOC_NOPM,
+ 0, 0, NULL, 0, voice_input_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+*/
+ SND_SOC_DAPM_PGA_E("Voice DigiOutput", SND_SOC_NOPM,
+ 0, 0, NULL, 0, voice_output_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
+
/* Master analog loopback switch */
SND_SOC_DAPM_SUPPLY("FM Loop Enable", TWL4030_REG_MISC_SET_1, 5, 0,
NULL, 0),
@@ -1507,6 +1631,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
};
static const struct snd_soc_dapm_route intercon[] = {
+ {"Voice DigiInput",NULL,"GSMIN"},
+ {"Voice DigiOutput",NULL,"GSMOUT"},
/* Stream -> DAC mapping */
{"DAC Right1", NULL, "HiFi Playback"},
{"DAC Left1", NULL, "HiFi Playback"},
@@ -1531,6 +1657,7 @@ static const struct snd_soc_dapm_route intercon[] = {
/* Supply for the digital part (APLL) */
{"Digital Voice Playback Mixer", NULL, "APLL Enable"},
+ {"Digital Voice Playback Mixer", NULL, "Voice DigiInput"},
{"DAC Left1", NULL, "AIF Enable"},
{"DAC Right1", NULL, "AIF Enable"},
@@ -2227,6 +2354,7 @@ static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate)
{
struct snd_soc_codec *codec = dai->codec;
u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF);
+ printk("twl4030_voice_set_tristate codec=%p\n", codec);
if (tristate)
reg |= TWL4030_VIF_TRI_EN;
diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h
new file mode 100644
index 0000000..c5762af
--- /dev/null
+++ b/sound/soc/codecs/twl4030.h
@@ -0,0 +1,53 @@
+/*
+ * ALSA SoC TWL4030 codec driver
+ *
+ * Author: 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
+ * 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
+ *
+ */
+
+#ifndef __TWL4030_AUDIO_H__
+#define __TWL4030_AUDIO_H__
+
+/* Register descriptions are here */
+// #include <linux/mfd/twl4030-codec.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+
+/* Sgadow register used by the audio driver */
+#define TWL4030_REG_SW_SHADOW 0x4A
+#define TWL4030_CACHEREGNUM (TWL4030_REG_SW_SHADOW + 1)
+
+/* TWL4030_REG_SW_SHADOW (0x4A) Fields */
+#define TWL4030_HFL_EN 0x01
+#define TWL4030_HFR_EN 0x02
+
+#define TWL4030_DAI_HIFI 0
+#define TWL4030_DAI_VOICE 1
+
+extern struct snd_soc_dai twl4030_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_twl4030;
+
+struct twl4030_setup_data {
+ unsigned int ramp_delay_value;
+ unsigned int sysclk;
+ unsigned int hs_extmute:1;
+ void (*set_hs_extmute)(int mute);
+};
+
+#endif /* End of __TWL4030_AUDIO_H__ */
+
+
diff --git a/sound/soc/codecs/w2cbw003-bt.c b/sound/soc/codecs/w2cbw003-bt.c
new file mode 100644
index 0000000..faa2e33
--- /dev/null
+++ b/sound/soc/codecs/w2cbw003-bt.c
@@ -0,0 +1,100 @@
+/*
+ * Tis is a driver for the W2CBW003 Bluetooth PCM interface
+ *
+ * w2cbw003.c
+ *
+ * Created on: 15-Oct-2009
+ * Author: neil.jones@imgtec.com
+ *
+ * Copyright (C) 2009 Imagination Technologies Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "w2cbw003-bt.h"
+/*
+ * Note this is a simple chip with no configuration interface, sample rate is
+ * determined automatically by examining the Master clock and Bit clock ratios
+ */
+
+// FIXME: adjust what the W2CBW003 PCM I/F supports...
+
+#define W2CBW003_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 |\
+ SNDRV_PCM_RATE_192000)
+
+
+struct snd_soc_dai_driver w2cbw003_dai = {
+ .name = "W2CBW003",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = W2CBW003_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = W2CBW003_RATES,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE,
+ },
+};
+
+struct snd_soc_codec_driver soc_codec_dev_w2cbw003;
+
+
+static int w2cbw003_platform_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev,
+ &soc_codec_dev_w2cbw003, &w2cbw003_dai, 1);
+}
+
+static int w2cbw003_platform_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+MODULE_ALIAS("platform:w2cbw003_codec_audio");
+
+static struct platform_driver w2cbw003_codec_driver = {
+ .driver = {
+ .name = "w2cbw003_codec_audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = w2cbw003_platform_probe,
+ .remove = w2cbw003_platform_remove,
+};
+
+static int __init w2cbw003_init(void)
+{
+ return platform_driver_register(&w2cbw003_codec_driver);
+}
+module_init(w2cbw003_init);
+
+static void __exit w2cbw003_exit(void)
+{
+ platform_driver_unregister(&w2cbw003_codec_driver);
+}
+module_exit(w2cbw003_exit);
+
+MODULE_DESCRIPTION("ASoC W2CBW003 driver");
+MODULE_AUTHOR("Neil Jones");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/w2cbw003-bt.h b/sound/soc/codecs/w2cbw003-bt.h
new file mode 100644
index 0000000..27062b7
--- /dev/null
+++ b/sound/soc/codecs/w2cbw003-bt.h
@@ -0,0 +1,23 @@
+/*
+ * w2cbw003.h
+ *
+ * Created on: 15-Oct-2009
+ * Author: neil.jones@imgtec.com
+ *
+ * Copyright (C) 2009 Imagination Technologies Ltd.
+ *
+ * Adapted on: 20-Aug-2011
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+
+#ifndef W2CBW003_H_
+#define W2CBW003_H_
+
+extern struct snd_soc_dai_driver w2cbw003_dai;
+extern struct snd_soc_codec_driver soc_codec_dev_w2cbw003;
+
+#endif /* W2CBW003_H_ */
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;