aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Miao <eric.miao@linaro.org>2011-09-08 17:24:01 -0700
committerEric Miao <eric.miao@linaro.org>2011-10-14 09:57:03 +0800
commit7bd46b11ce7a74d932f5218bfc4495b37ba9f544 (patch)
tree55cb99773df656fb280b9f6edc08059f844eba2e
parentd58931a349410e06ba2974fad8e6f949a5a6f278 (diff)
downloadlinux-linaro-7bd46b11ce7a74d932f5218bfc4495b37ba9f544.tar.gz
sound: add imx-sgtl5000
Signed-off-by: Richard Zhu <richard.zhu@linaro.org> Signed-off-by: Eric Miao <eric.miao@linaro.org>
-rw-r--r--include/linux/fsl_devices.h26
-rw-r--r--sound/soc/imx/Kconfig11
-rw-r--r--sound/soc/imx/Makefile2
-rw-r--r--sound/soc/imx/imx-sgtl5000.c396
4 files changed, 435 insertions, 0 deletions
diff --git a/include/linux/fsl_devices.h b/include/linux/fsl_devices.h
index 4386d3c0f97..f82c91f8319 100644
--- a/include/linux/fsl_devices.h
+++ b/include/linux/fsl_devices.h
@@ -250,6 +250,32 @@ struct mpc8xx_pcmcia_ops {
int(*voltage_set)(int slot, int vcc, int vpp);
};
+/*
+ * This struct is to define the number of SSIs on a platform,
+ * DAM source port config, DAM external port config,
+ * regulator names, and other stuff audio needs.
+ */
+struct mxc_audio_platform_data {
+ int ssi_num;
+ int src_port;
+ int ext_port;
+
+ int intr_id_hp;
+ int ext_ram;
+ struct clk *ssi_clk[2];
+
+ int hp_gpio;
+ int hp_active_low; /* headphone irq is active loaw */
+
+ int sysclk;
+
+ int (*init) (void); /* board specific init */
+ int (*amp_enable) (int enable);
+ int (*clock_enable) (int enable);
+ int (*finit) (void); /* board specific finit */
+ void *priv; /* used by board specific functions */
+};
+
/* Returns non-zero if the current suspend operation would
* lead to a deep sleep (i.e. power removed from the core,
* instead of just the clock).
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig
index bb699bb55a5..351a30b8d16 100644
--- a/sound/soc/imx/Kconfig
+++ b/sound/soc/imx/Kconfig
@@ -44,6 +44,17 @@ config SND_SOC_PHYCORE_AC97
Say Y if you want to add support for SoC audio on Phytec phyCORE
and phyCARD boards in AC97 mode
+config SND_SOC_IMX_SGTL5000
+ tristate "SoC Audio support for i.MX boards with sgtl5000"
+ depends on MACH_MX35_3DS || MACH_MX51_BABBAGE || MACH_MX53_LOCO
+ select SND_SOC_SGTL5000
+ select SND_MXC_SOC_MX2
+ help
+ Say Y if you want to add support for SoC audio
+ on an i.MX board with a sgtl5000 codec, Say N
+ if you don't make sure that the sgtl5000 is
+ supported or not.
+
config SND_SOC_EUKREA_TLV320
tristate "Eukrea TLV320"
depends on MACH_EUKREA_MBIMX27_BASEBOARD \
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
index d6d609ba7e2..38693ed074a 100644
--- a/sound/soc/imx/Makefile
+++ b/sound/soc/imx/Makefile
@@ -12,8 +12,10 @@ snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
snd-soc-phycore-ac97-objs := phycore-ac97.o
snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
snd-soc-wm1133-ev1-objs := wm1133-ev1.o
+snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
+obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
diff --git a/sound/soc/imx/imx-sgtl5000.c b/sound/soc/imx/imx-sgtl5000.c
new file mode 100644
index 00000000000..82ee8eca868
--- /dev/null
+++ b/sound/soc/imx/imx-sgtl5000.c
@@ -0,0 +1,396 @@
+/*
+ * sound/soc/imx/3ds-sgtl5000.c -- SoC audio for i.MX 3ds boards with
+ * sgtl5000 codec
+ *
+ * Copyright 2009 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ * Copyright (C) 2011 Freescale Semiconductor, Inc.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/fsl_devices.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+#include <mach/audmux.h>
+
+#include "../codecs/sgtl5000.h"
+#include "imx-ssi.h"
+
+static struct imx_sgtl5000_priv {
+ int sysclk;
+ int hw;
+ struct platform_device *pdev;
+} card_priv;
+
+static struct snd_soc_jack hs_jack;
+static struct snd_soc_card imx_sgtl5000;
+
+/* Headphones jack detection DAPM pins */
+static struct snd_soc_jack_pin hs_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+/* Headphones jack detection gpios */
+static struct snd_soc_jack_gpio hs_jack_gpios[] = {
+ [0] = {
+ /* gpio is set on per-platform basis */
+ .name = "hp-gpio",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 200,
+ },
+};
+
+static int sgtl5000_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;
+ u32 dai_format;
+ int ret;
+ unsigned int channels = params_channels(params);
+
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, card_priv.sysclk, 0);
+
+ snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, params_rate(params), 0);
+
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM;
+
+ /* set codec DAI configuration */
+ ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+
+ /* TODO: The SSI driver should figure this out for us */
+ switch (channels) {
+ case 2:
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffc, 0xfffffffc, 2, 0);
+ break;
+ case 1:
+ snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffe, 0xfffffffe, 1, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set cpu DAI configuration */
+ dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
+ SND_SOC_DAIFMT_CBM_CFM;
+ ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static struct snd_soc_ops imx_sgtl5000_hifi_ops = {
+ .hw_params = sgtl5000_params,
+};
+
+static int sgtl5000_jack_func;
+static int sgtl5000_spk_func;
+static int sgtl5000_line_in_func;
+
+static const char *jack_function[] = { "off", "on"};
+
+static const char *spk_function[] = { "off", "on" };
+
+static const char *line_in_function[] = { "off", "on" };
+
+static const struct soc_enum sgtl5000_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, jack_function),
+ SOC_ENUM_SINGLE_EXT(2, spk_function),
+ SOC_ENUM_SINGLE_EXT(2, line_in_function),
+};
+
+static int sgtl5000_get_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_jack_func;
+ return 0;
+}
+
+static int sgtl5000_set_jack(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_jack_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_jack_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_jack_func)
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+ else
+ snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack");
+
+ snd_soc_dapm_sync(&codec->dapm);
+ return 1;
+}
+
+static int sgtl5000_get_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_spk_func;
+ return 0;
+}
+
+static int sgtl5000_set_spk(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_spk_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_spk_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_spk_func)
+ snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk");
+ else
+ snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk");
+
+ snd_soc_dapm_sync(&codec->dapm);
+ return 1;
+}
+
+static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func;
+ return 0;
+}
+
+static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0])
+ return 0;
+
+ sgtl5000_line_in_func = ucontrol->value.enumerated.item[0];
+ if (sgtl5000_line_in_func)
+ snd_soc_dapm_enable_pin(&codec->dapm, "Line In Jack");
+ else
+ snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack");
+
+ snd_soc_dapm_sync(&codec->dapm);
+ return 1;
+}
+
+/* imx_3stack card dapm widgets */
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Mic Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_SPK("Ext Spk", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new sgtl5000_machine_controls[] = {
+ SOC_ENUM_EXT("Jack Function", sgtl5000_enum[0], sgtl5000_get_jack,
+ sgtl5000_set_jack),
+ SOC_ENUM_EXT("Speaker Function", sgtl5000_enum[1], sgtl5000_get_spk,
+ sgtl5000_set_spk),
+ SOC_ENUM_EXT("Line In Function", sgtl5000_enum[1], sgtl5000_get_line_in,
+ sgtl5000_set_line_in),
+};
+
+/* imx_3stack machine connections to the codec pins */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* Mic Jack --> MIC_IN (with automatic bias) */
+ {"MIC_IN", NULL, "Mic Jack"},
+
+ /* Line in Jack --> LINE_IN */
+ {"LINE_IN", NULL, "Line In Jack"},
+
+ /* HP_OUT --> Headphone Jack */
+ {"Headphone Jack", NULL, "HP_OUT"},
+
+ /* LINE_OUT --> Ext Speaker */
+ {"Ext Spk", NULL, "LINE_OUT"},
+};
+
+static int imx_3stack_sgtl5000_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ int ret;
+
+ ret = snd_soc_add_controls(codec, sgtl5000_machine_controls,
+ ARRAY_SIZE(sgtl5000_machine_controls));
+ if (ret)
+ return ret;
+
+ /* Add imx_3stack specific widgets */
+ snd_soc_dapm_new_controls(&codec->dapm, imx_3stack_dapm_widgets,
+ ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+ /* Set up imx_3stack specific audio path audio_map */
+ snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack");
+ snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+ snd_soc_dapm_sync(&codec->dapm);
+
+ /* Jack detection API stuff */
+ ret = snd_soc_jack_new(codec, "Headphone Jack",
+ SND_JACK_HEADPHONE, &hs_jack);
+ if (ret)
+ return ret;
+
+ ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins),
+ hs_jack_pins);
+ if (ret) {
+ printk(KERN_ERR "failed to call snd_soc_jack_add_pins\n");
+ return ret;
+ }
+
+ ret = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios),
+ hs_jack_gpios);
+ if (ret)
+ printk(KERN_WARNING "failed to call snd_soc_jack_add_gpios\n");
+
+ return 0;
+}
+
+static struct snd_soc_dai_link imx_sgtl5000_dai[] = {
+ {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "sgtl5000",
+ .codec_name = "sgtl5000.1-000a",
+ .cpu_dai_name = "imx-ssi.1",
+ .platform_name = "imx-pcm-audio.1",
+ .init = imx_3stack_sgtl5000_init,
+ .ops = &imx_sgtl5000_hifi_ops,
+ },
+};
+
+static struct snd_soc_card imx_sgtl5000 = {
+ .name = "sgtl5000-audio",
+ .dai_link = imx_sgtl5000_dai,
+ .num_links = ARRAY_SIZE(imx_sgtl5000_dai),
+};
+
+static struct platform_device *imx_sgtl5000_snd_device;
+
+static int imx_audmux_config(int slave, int master)
+{
+ unsigned int ptcr, pdcr;
+ slave = slave - 1;
+ master = master - 1;
+
+ /* SSI0 mastered by port 5 */
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN |
+ MXC_AUDMUX_V2_PTCR_TFSDIR |
+ MXC_AUDMUX_V2_PTCR_TFSEL(master) |
+ MXC_AUDMUX_V2_PTCR_TCLKDIR |
+ MXC_AUDMUX_V2_PTCR_TCSEL(master);
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master);
+ mxc_audmux_v2_configure_port(slave, ptcr, pdcr);
+
+ ptcr = MXC_AUDMUX_V2_PTCR_SYN;
+ pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave);
+ mxc_audmux_v2_configure_port(master, ptcr, pdcr);
+
+ return 0;
+}
+
+static int __devinit imx_sgtl5000_probe(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ int ret = 0;
+
+ card_priv.pdev = pdev;
+
+ imx_audmux_config(plat->src_port, plat->ext_port);
+
+ ret = -EINVAL;
+ if (plat->init && plat->init())
+ return ret;
+
+ card_priv.sysclk = plat->sysclk;
+
+ hs_jack_gpios[0].gpio = plat->hp_gpio;
+ hs_jack_gpios[0].invert = plat->hp_active_low;
+
+ return 0;
+}
+
+static int imx_sgtl5000_remove(struct platform_device *pdev)
+{
+ struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+ if (plat->finit)
+ plat->finit();
+
+ return 0;
+}
+
+static struct platform_driver imx_sgtl5000_audio_driver = {
+ .probe = imx_sgtl5000_probe,
+ .remove = imx_sgtl5000_remove,
+ .driver = {
+ .name = "imx-sgtl5000",
+ },
+};
+
+static int __init imx_sgtl5000_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&imx_sgtl5000_audio_driver);
+ if (ret)
+ return -ENOMEM;
+
+ if (machine_is_mx35_3ds())
+ imx_sgtl5000_dai[0].codec_name = "sgtl5000.0-000a";
+ else
+ imx_sgtl5000_dai[0].codec_name = "sgtl5000.1-000a";
+
+ imx_sgtl5000_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!imx_sgtl5000_snd_device)
+ return -ENOMEM;
+
+ platform_set_drvdata(imx_sgtl5000_snd_device, &imx_sgtl5000);
+
+ ret = platform_device_add(imx_sgtl5000_snd_device);
+
+ if (ret) {
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+ platform_device_put(imx_sgtl5000_snd_device);
+ }
+
+ return ret;
+}
+
+static void __exit imx_sgtl5000_exit(void)
+{
+ platform_driver_unregister(&imx_sgtl5000_audio_driver);
+ platform_device_unregister(imx_sgtl5000_snd_device);
+}
+
+late_initcall(imx_sgtl5000_init);
+module_exit(imx_sgtl5000_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
+MODULE_LICENSE("GPL");