diff options
author | Eric Miao <eric.miao@linaro.org> | 2011-09-08 17:24:01 -0700 |
---|---|---|
committer | Eric Miao <eric.miao@linaro.org> | 2011-11-10 11:20:35 +0800 |
commit | a16cc8b5b101560cf9dbd1210b01866d55714d96 (patch) | |
tree | 577f05e16dd75004e0ecda4c09ce4b81d9e63e21 | |
parent | 331e4ef4f156aa2bfbec5e0079ed6f6f6f552129 (diff) |
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.h | 26 | ||||
-rw-r--r-- | sound/soc/imx/Kconfig | 11 | ||||
-rw-r--r-- | sound/soc/imx/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/imx/imx-sgtl5000.c | 396 |
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 b133bfcc584..8cf2bb6fd01 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"); |