[ALSA] ASoC: Add drivers for the Texas Instruments OMAP processors

Add common OMAP ASoC drivers and machine driver for Nokia N810. Currently
supported features are:

- Covers OMAPs from 1510 to 2420
- Common DMA driver
- DAI link driver using McBSP port in I2S mode
- Basic machine driver for Nokia N810

Signed-off-by: Jarkko Nikula <jarkko.nikula@nokia.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c
new file mode 100644
index 0000000..83b1eb4
--- /dev/null
+++ b/sound/soc/omap/n810.c
@@ -0,0 +1,336 @@
+/*
+ * n810.c  --  SoC audio for Nokia N810
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include <asm/mach-types.h>
+#include <asm/arch/hardware.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/mcbsp.h>
+
+#include "omap-mcbsp.h"
+#include "omap-pcm.h"
+#include "../codecs/tlv320aic3x.h"
+
+#define RX44_HEADSET_AMP_GPIO	10
+#define RX44_SPEAKER_AMP_GPIO	101
+
+static struct clk *sys_clkout2;
+static struct clk *sys_clkout2_src;
+static struct clk *func96m_clk;
+
+static int n810_spk_func;
+static int n810_jack_func;
+
+static void n810_ext_control(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_set_endpoint(codec, "Ext Spk", n810_spk_func);
+	snd_soc_dapm_set_endpoint(codec, "Headphone Jack", n810_jack_func);
+
+	snd_soc_dapm_sync_endpoints(codec);
+}
+
+static int n810_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->socdev->codec;
+
+	n810_ext_control(codec);
+	return clk_enable(sys_clkout2);
+}
+
+static void n810_shutdown(struct snd_pcm_substream *substream)
+{
+	clk_disable(sys_clkout2);
+}
+
+static int n810_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_codec_dai *codec_dai = rtd->dai->codec_dai;
+	struct snd_soc_cpu_dai *cpu_dai = rtd->dai->cpu_dai;
+	int err;
+
+	/* Set codec DAI configuration */
+	err = codec_dai->dai_ops.set_fmt(codec_dai,
+					 SND_SOC_DAIFMT_I2S |
+					 SND_SOC_DAIFMT_NB_NF |
+					 SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0)
+		return err;
+
+	/* Set cpu DAI configuration */
+	err = cpu_dai->dai_ops.set_fmt(cpu_dai,
+				       SND_SOC_DAIFMT_I2S |
+				       SND_SOC_DAIFMT_NB_NF |
+				       SND_SOC_DAIFMT_CBM_CFM);
+	if (err < 0)
+		return err;
+
+	/* Set the codec system clock for DAC and ADC */
+	err = codec_dai->dai_ops.set_sysclk(codec_dai, 0, 12000000,
+					    SND_SOC_CLOCK_IN);
+
+	return err;
+}
+
+static struct snd_soc_ops n810_ops = {
+	.startup = n810_startup,
+	.hw_params = n810_hw_params,
+	.shutdown = n810_shutdown,
+};
+
+static int n810_get_spk(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = n810_spk_func;
+
+	return 0;
+}
+
+static int n810_set_spk(struct snd_kcontrol *kcontrol,
+			struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (n810_spk_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	n810_spk_func = ucontrol->value.integer.value[0];
+	n810_ext_control(codec);
+
+	return 1;
+}
+
+static int n810_get_jack(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = n810_jack_func;
+
+	return 0;
+}
+
+static int n810_set_jack(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec =  snd_kcontrol_chip(kcontrol);
+
+	if (n810_jack_func == ucontrol->value.integer.value[0])
+		return 0;
+
+	n810_jack_func = ucontrol->value.integer.value[0];
+	n810_ext_control(codec);
+
+	return 1;
+}
+
+static int n810_spk_event(struct snd_soc_dapm_widget *w,
+			  struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		omap_set_gpio_dataout(RX44_SPEAKER_AMP_GPIO, 1);
+	else
+		omap_set_gpio_dataout(RX44_SPEAKER_AMP_GPIO, 0);
+
+	return 0;
+}
+
+static int n810_jack_event(struct snd_soc_dapm_widget *w,
+			   struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		omap_set_gpio_dataout(RX44_HEADSET_AMP_GPIO, 1);
+	else
+		omap_set_gpio_dataout(RX44_HEADSET_AMP_GPIO, 0);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Ext Spk", n810_spk_event),
+	SND_SOC_DAPM_HP("Headphone Jack", n810_jack_event),
+};
+
+static const char *audio_map[][3] = {
+	{"Headphone Jack", NULL, "HPLOUT"},
+	{"Headphone Jack", NULL, "HPROUT"},
+
+	{"Ext Spk", NULL, "LLOUT"},
+	{"Ext Spk", NULL, "RLOUT"},
+};
+
+static const char *spk_function[] = {"Off", "On"};
+static const char *jack_function[] = {"Off", "Headphone"};
+static const struct soc_enum n810_enum[] = {
+	SOC_ENUM_SINGLE_EXT(2, spk_function),
+	SOC_ENUM_SINGLE_EXT(3, jack_function),
+};
+
+static const struct snd_kcontrol_new aic33_n810_controls[] = {
+	SOC_ENUM_EXT("Speaker Function", n810_enum[0],
+		     n810_get_spk, n810_set_spk),
+	SOC_ENUM_EXT("Jack Function", n810_enum[1],
+		     n810_get_jack, n810_set_jack),
+};
+
+static int n810_aic33_init(struct snd_soc_codec *codec)
+{
+	int i, err;
+
+	/* Not connected */
+	snd_soc_dapm_set_endpoint(codec, "MONO_LOUT", 0);
+	snd_soc_dapm_set_endpoint(codec, "HPLCOM", 0);
+	snd_soc_dapm_set_endpoint(codec, "HPRCOM", 0);
+
+	/* Add N810 specific controls */
+	for (i = 0; i < ARRAY_SIZE(aic33_n810_controls); i++) {
+		err = snd_ctl_add(codec->card,
+			snd_soc_cnew(&aic33_n810_controls[i], codec, NULL));
+		if (err < 0)
+			return err;
+	}
+
+	/* Add N810 specific widgets */
+	for (i = 0; i < ARRAY_SIZE(aic33_dapm_widgets); i++)
+		snd_soc_dapm_new_control(codec, &aic33_dapm_widgets[i]);
+
+	/* Set up N810 specific audio path audio_map */
+	for (i = 0; i < ARRAY_SIZE(audio_map); i++)
+		snd_soc_dapm_connect_input(codec, audio_map[i][0],
+			audio_map[i][1], audio_map[i][2]);
+
+	snd_soc_dapm_sync_endpoints(codec);
+
+	return 0;
+}
+
+/* Digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link n810_dai = {
+	.name = "TLV320AIC33",
+	.stream_name = "AIC33",
+	.cpu_dai = &omap_mcbsp_dai[0],
+	.codec_dai = &aic3x_dai,
+	.init = n810_aic33_init,
+	.ops = &n810_ops,
+};
+
+/* Audio machine driver */
+static struct snd_soc_machine snd_soc_machine_n810 = {
+	.name = "N810",
+	.dai_link = &n810_dai,
+	.num_links = 1,
+};
+
+/* Audio private data */
+static struct aic3x_setup_data n810_aic33_setup = {
+	.i2c_address = 0x18,
+};
+
+/* Audio subsystem */
+static struct snd_soc_device n810_snd_devdata = {
+	.machine = &snd_soc_machine_n810,
+	.platform = &omap_soc_platform,
+	.codec_dev = &soc_codec_dev_aic3x,
+	.codec_data = &n810_aic33_setup,
+};
+
+static struct platform_device *n810_snd_device;
+
+static int __init n810_soc_init(void)
+{
+	int err;
+	struct device *dev;
+
+	if (!machine_is_nokia_n810())
+		return -ENODEV;
+
+	n810_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!n810_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(n810_snd_device, &n810_snd_devdata);
+	n810_snd_devdata.dev = &n810_snd_device->dev;
+	*(unsigned int *)n810_dai.cpu_dai->private_data = 1; /* McBSP2 */
+	err = platform_device_add(n810_snd_device);
+	if (err)
+		goto err1;
+
+	dev = &n810_snd_device->dev;
+
+	sys_clkout2_src = clk_get(dev, "sys_clkout2_src");
+	if (IS_ERR(sys_clkout2_src)) {
+		dev_err(dev, "Could not get sys_clkout2_src clock\n");
+		return -ENODEV;
+	}
+	sys_clkout2 = clk_get(dev, "sys_clkout2");
+	if (IS_ERR(sys_clkout2)) {
+		dev_err(dev, "Could not get sys_clkout2\n");
+		goto err1;
+	}
+	/*
+	 * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use
+	 * 96 MHz as its parent in order to get 12 MHz
+	 */
+	func96m_clk = clk_get(dev, "func_96m_ck");
+	if (IS_ERR(func96m_clk)) {
+		dev_err(dev, "Could not get func 96M clock\n");
+		goto err2;
+	}
+	clk_set_parent(sys_clkout2_src, func96m_clk);
+	clk_set_rate(sys_clkout2, 12000000);
+
+	if (omap_request_gpio(RX44_HEADSET_AMP_GPIO) < 0)
+		BUG();
+	if (omap_request_gpio(RX44_SPEAKER_AMP_GPIO) < 0)
+		BUG();
+	omap_set_gpio_direction(RX44_HEADSET_AMP_GPIO, 0);
+	omap_set_gpio_direction(RX44_SPEAKER_AMP_GPIO, 0);
+
+	return 0;
+err2:
+	clk_put(sys_clkout2);
+	platform_device_del(n810_snd_device);
+err1:
+	platform_device_put(n810_snd_device);
+
+	return err;
+
+}
+
+static void __exit n810_soc_exit(void)
+{
+	platform_device_unregister(n810_snd_device);
+}
+
+module_init(n810_soc_init);
+module_exit(n810_soc_exit);
+
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>");
+MODULE_DESCRIPTION("ALSA SoC Nokia N810");
+MODULE_LICENSE("GPL");