Signed-off-by: Stephen Warren <swarren@xxxxxxxxxx> --- sound/soc/tegra/Kconfig | 10 ++ sound/soc/tegra/Makefile | 4 + sound/soc/tegra/harmony.c | 313 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 327 insertions(+), 0 deletions(-) create mode 100644 sound/soc/tegra/harmony.c diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index d6b0444..31a310d 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -17,3 +17,13 @@ config SND_TEGRA_SOC_I2S This option conflicts with TEGRA_I2S_AUDIO. +config SND_TEGRA_SOC_HARMONY + tristate "SoC Audio support for Tegra Harmony reference board" + depends on SND_TEGRA_SOC && MACH_HARMONY && I2C + default m + select SND_TEGRA_SOC_I2S + select SND_SOC_WM8903 + help + Say Y or M here if you want to add support for SoC audio on the + Tegra Harmony reference board. + diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index d7712a2..f94bce8 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -7,3 +7,7 @@ obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-das.o obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-pcm.o obj-$(CONFIG_SND_TEGRA_SOC_I2S) += snd-soc-tegra-i2s.o +# Tegra machine Support +snd-soc-tegra-harmony-objs := harmony.o + +obj-$(CONFIG_SND_TEGRA_SOC_HARMONY) += snd-soc-tegra-harmony.o diff --git a/sound/soc/tegra/harmony.c b/sound/soc/tegra/harmony.c new file mode 100644 index 0000000..158308b --- /dev/null +++ b/sound/soc/tegra/harmony.c @@ -0,0 +1,313 @@ +/* + * harmony.c - Harmony machine ASoC driver + * + * Author: Stephen Warren <swarren@xxxxxxxxxx> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@xxxxxxxxxx> + * Vijay Mali <vmali@xxxxxxxxxx> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@xxxxxxxxxx> + * + * 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 <asm/mach-types.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/soc.h> + +#include "../codecs/wm8903.h" +#include "tegra_das.h" +#include "tegra_i2s.h" +#include "tegra_pcm.h" + +#define PREFIX "ASoC Harmony: " + +static struct platform_device *harmony_snd_device; + +struct clk *clk_pll_a; +struct clk *clk_pll_a_out0; +struct clk *clk_i2s1; +struct clk *clk_audio; +struct clk *clk_audio_2x; + +static int harmony_asoc_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->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int srate, bitclock; + int err; + + srate = params_rate(params); + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + bitclock = 11289600; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + bitclock = 12288000; + break; + default: + return -EINVAL; + } + + clk_disable(clk_audio_2x); + clk_disable(clk_audio); + clk_disable(clk_i2s1); + clk_disable(clk_pll_a_out0); + clk_disable(clk_pll_a); + + err = clk_set_rate(clk_pll_a, bitclock); + if (err) { + pr_err(PREFIX "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(clk_pll_a_out0, bitclock); + if (err) { + pr_err(PREFIX "Can't set pll_a_out0 rate: %d\n", err); + return err; + } + + err = clk_set_rate(clk_i2s1, bitclock); + if (err) { + pr_err(PREFIX "Can't set i2s1 rate: %d\n", err); + return err; + } + + /* Don't set audio/audio_2x rate; they're locked to pll_a_out0 */ + + err = clk_enable(clk_pll_a); + if (err) { + pr_err(PREFIX "Can't enable pll_a: %d\n", err); + return err; + } + + err = clk_enable(clk_pll_a_out0); + if (err) { + pr_err(PREFIX "Can't enable pll_a_out0: %d\n", err); + return err; + } + + err = clk_enable(clk_i2s1); + if (err) { + pr_err(PREFIX "Can't enable i2s1: %d\n", err); + return err; + } + + err = clk_enable(clk_audio); + if (err) { + pr_err(PREFIX "Can't enable audio: %d\n", err); + return err; + } + + err = clk_enable(clk_audio_2x); + if (err) { + pr_err(PREFIX "Can't enable audio_2x: %d\n", err); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, 0, bitclock, SND_SOC_CLOCK_IN); + if (err < 0) { + pr_err(PREFIX "codec_dai clock not set\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(cpu_dai, 0, bitclock, SND_SOC_CLOCK_IN); + if (err < 0) { + pr_err(PREFIX "cpu_dai clock not set\n"); + return err; + } + + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + pr_err(PREFIX "codec_dai fmt not set\n"); + return err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | \ + SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + pr_err(PREFIX "cpu_dai fmt not set\n"); + return err; + } + + return 0; +} + +static struct snd_soc_ops harmony_asoc_ops = { + .hw_params = harmony_asoc_hw_params, +}; + +static struct snd_soc_dai_link harmony_wm8903_dai = { + .name = "WM8903", + .stream_name = "WM8903 PCM", + .cpu_dai = &tegra_i2s_dai[0], + .codec_dai = &wm8903_dai, + .ops = &harmony_asoc_ops, +}; + +static struct snd_soc_card snd_soc_harmony = { + .name = "tegra-harmony", + .platform = &tegra_soc_platform, + .dai_link = &harmony_wm8903_dai, + .num_links = 1, +}; + +static struct snd_soc_device harmony_wm8903_snd_devdata = { + .card = &snd_soc_harmony, + .codec_dev = &soc_codec_dev_wm8903, +}; + +static int __init harmony_soc_modinit(void) +{ + int ret; + + if (!machine_is_harmony()) { + pr_err("Harmony ASoC: Not running on Tegra Harmony!\n"); + return -ENODEV; + } + + clk_pll_a = clk_get_sys(NULL, "pll_a"); + if (IS_ERR_OR_NULL(clk_pll_a)) { + pr_err("Can't retrieve clk pll_a\n"); + ret = PTR_ERR(clk_pll_a); + goto err_clock_put; + } + + clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0"); + if (IS_ERR_OR_NULL(clk_pll_a_out0)) { + pr_err("Can't retrieve clk pll_a_out0\n"); + ret = PTR_ERR(clk_pll_a_out0); + goto err_clock_put; + } + + clk_i2s1 = clk_get_sys("tegra-i2s.0", NULL); + if (IS_ERR_OR_NULL(clk_i2s1)) { + pr_err("Can't retrieve clk i2s1\n"); + ret = PTR_ERR(clk_i2s1); + goto err_clock_put; + } + + clk_audio = clk_get_sys(NULL, "audio"); + if (IS_ERR_OR_NULL(clk_audio)) { + pr_err("Can't retrieve clk audio\n"); + ret = PTR_ERR(clk_audio); + goto err_clock_put; + } + + clk_audio_2x = clk_get_sys(NULL, "audio_2x"); + if (IS_ERR_OR_NULL(clk_audio_2x)) { + pr_err("Can't retrieve clk audio_2x\n"); + ret = PTR_ERR(clk_audio_2x); + goto err_clock_put; + } + + /* IDs 0..n-1, params 1..n */ + ret = tegra_das_configure_dap_for_dac(0, TEGRA_DAS_DAP_SEL_DAC1); + if (ret) { + pr_err("Harmony ASoC: tegra_das_configure_dap_for_dac failed\n"); + goto err_clock_put; + } + + ret = tegra_das_configure_dac(0, TEGRA_DAS_DAC_SEL_DAP1); + if (ret) { + pr_err("Harmony ASoC: tegra_das_configure_dac failed\n"); + goto err_clock_put; + } + + /* + * Create and register platform device + */ + harmony_snd_device = platform_device_alloc("soc-audio", -1); + if (harmony_snd_device == NULL) { + pr_err("Harmony ASoC: platform_device_alloc failed\n"); + ret = -ENOMEM; + goto err_device_put; + } + + platform_set_drvdata(harmony_snd_device, &harmony_wm8903_snd_devdata); + harmony_wm8903_snd_devdata.dev = &harmony_snd_device->dev; + + ret = platform_device_add(harmony_snd_device); + if (ret) { + pr_err("Harmony ASoC: platform_device_add failed (%d)\n", + ret); + goto err_device_put; + } + + return 0; + +err_device_put: + if (harmony_snd_device != NULL) { + platform_device_put(harmony_snd_device); + harmony_snd_device = NULL; + } +err_clock_put: + if (!IS_ERR_OR_NULL(clk_audio_2x)) + clk_put(clk_audio_2x); + if (!IS_ERR_OR_NULL(clk_audio)) + clk_put(clk_audio); + if (!IS_ERR_OR_NULL(clk_i2s1)) + clk_put(clk_i2s1); + if (!IS_ERR_OR_NULL(clk_pll_a_out0)) + clk_put(clk_pll_a_out0); + if (!IS_ERR_OR_NULL(clk_pll_a)) + clk_put(clk_pll_a); + return ret; +} +module_init(harmony_soc_modinit); + +static void __exit harmony_soc_modexit(void) +{ + platform_device_unregister(harmony_snd_device); + + clk_put(clk_audio_2x); + clk_put(clk_audio); + clk_put(clk_i2s1); + clk_put(clk_pll_a_out0); + clk_put(clk_pll_a); + + harmony_snd_device = NULL; +} +module_exit(harmony_soc_modexit); + +MODULE_AUTHOR("Stephen Warren <swarren@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Harmony machine ASoC driver"); +MODULE_LICENSE("GPL"); -- 1.7.0.4 nvpublic -- To unsubscribe from this list: send the line "unsubscribe linux-tegra" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html