From 37440f3ed07a6f588b05b8f98d0b3025c1949371 Mon Sep 17 00:00:00 2001 From: Marcel Ziswiler Date: Fri, 8 Jun 2012 20:36:31 +0200 Subject: Initial Toradex Colibri T20 L4T R15 support. --- sound/soc/tegra/Kconfig | 21 ++ sound/soc/tegra/Makefile | 4 + sound/soc/tegra/colibri_t20.c | 391 ++++++++++++++++++++++++ sound/soc/tegra/tegra20_ac97.c | 654 +++++++++++++++++++++++++++++++++++++++++ sound/soc/tegra/tegra20_ac97.h | 43 +++ 5 files changed, 1113 insertions(+) create mode 100644 sound/soc/tegra/colibri_t20.c create mode 100644 sound/soc/tegra/tegra20_ac97.c create mode 100644 sound/soc/tegra/tegra20_ac97.h (limited to 'sound/soc/tegra') diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index 2a8b4f1cef64..40717a6f2ee3 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -4,6 +4,16 @@ config SND_SOC_TEGRA help Say Y or M here if you want support for SoC audio on Tegra. +config SND_SOC_TEGRA20_AC97 + tristate "Tegra 20 AC97 driver" + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + help + Say Y or M if you want to add support for codecs attached to the + Tegra AC97 interface. You will also need to select the individual + machine drivers to support below. + config SND_SOC_TEGRA20_DAS tristate "Tegra 20 Digital Audio Switch driver" depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC @@ -76,6 +86,17 @@ config SND_SOC_TEGRA_WM8903 boards using the WM8093 codec. Currently, the supported boards are Harmony, Ventana, Seaboard, Kaen, and Aebl. +config SND_SOC_TEGRA_COLIBRI_T20 + tristate "SoC Audio support for Colibri T20 module" + depends on SND_SOC_TEGRA && MACH_COLIBRI_T20 + select SND_SOC_SPDIF + select SND_SOC_TEGRA20_AC97 + select SND_SOC_TEGRA20_SPDIF + select SND_SOC_WM9712 + help + Say Y or M here if you want to add support for SoC audio on the + Toradex Colibri T20 module. + config SND_SOC_TEGRA_TRIMSLICE tristate "SoC Audio support for TrimSlice board" depends on SND_SOC_TEGRA && MACH_TRIMSLICE && I2C diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 9c4346aa265c..274421b483de 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -6,6 +6,7 @@ snd-soc-tegra-tdm-pcm-objs := tegra_tdm_pcm.o snd-soc-tegra20-spdif-objs := tegra20_spdif.o snd-soc-tegra-utils-objs += tegra_asoc_utils.o snd-soc-tegra20-das-objs := tegra20_das.o +snd-soc-tegra20-ac97-objs := tegra20_ac97.o snd-soc-tegra20-i2s-objs := tegra20_i2s.o snd-soc-tegra30-ahub-objs := tegra30_ahub.o snd-soc-tegra30-i2s-objs := tegra30_i2s.o @@ -16,6 +17,7 @@ obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-tdm-pcm.o obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o +obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o obj-$(CONFIG_SND_SOC_TEGRA30_DAM) += snd-soc-tegra30-dam.o @@ -25,6 +27,7 @@ obj-$(CONFIG_SND_SOC_TEGRA30_SPDIF) += snd-soc-tegra30-spdif.o # Tegra machine Support snd-soc-tegra-wm8903-objs := tegra_wm8903.o +snd-soc-tegra-colibri_t20-objs := colibri_t20.o snd-soc-tegra-trimslice-objs := trimslice.o snd-soc-tegra-wm8753-objs := tegra_wm8753.o snd-soc-tegra-max98088-objs := tegra_max98088.o @@ -34,6 +37,7 @@ snd-soc-tegra-max98095-objs := tegra_max98095.o snd-soc-tegra-p1852-objs := tegra_p1852.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o +obj-$(CONFIG_SND_SOC_TEGRA_COLIBRI_T20) += snd-soc-tegra-colibri_t20.o obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o obj-$(CONFIG_SND_SOC_TEGRA_WM8753) += snd-soc-tegra-wm8753.o obj-$(CONFIG_SND_SOC_TEGRA_MAX98088) += snd-soc-tegra-max98088.o diff --git a/sound/soc/tegra/colibri_t20.c b/sound/soc/tegra/colibri_t20.c new file mode 100644 index 000000000000..b07f814e4731 --- /dev/null +++ b/sound/soc/tegra/colibri_t20.c @@ -0,0 +1,391 @@ +/* + * SoC audio driver for Toradex Colibri T20 + * + * Copyright (C) 2012 Toradex Inc. + * + * 2010-11-19: Marcel Ziswiler + * initial version (note: WM9715L is fully WM9712 compatible) + * + * Copied from tosa.c: + * Copyright 2005 Wolfson Microelectronics PLC. + * Copyright 2005 Openedhand Ltd. + * + * Authors: Liam Girdwood + * Richard Purdie + * + * 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 + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include /* order crucial */ +#include + +#include "../codecs/wm9712.h" +#include "tegra_asoc_utils.h" +#include "tegra_pcm.h" +#include "tegra20_ac97.h" + +#define DRV_NAME "colibri_t20-snd-wm9715l" + +struct colibri_t20_wm9715l { + struct tegra_asoc_utils_data util_data; +}; + +static int colibri_t20_wm9715l_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; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = codec->card; + struct colibri_t20_wm9715l *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + + /* AC97 clock is really fixed */ + mclk = 24576000; + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + if (!(machine->util_data.set_mclk % mclk)) + mclk = machine->util_data.set_mclk; + else { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + } + + tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1); + +//DAS AC97 DAC to DAP switching already done at probe + + return 0; +} + +static int tegra_spdif_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_card *card = rtd->card; + struct colibri_t20_wm9715l *machine = snd_soc_card_get_drvdata(card); + int srate, mclk, min_mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + mclk = 12288000; + break; + default: + return -EINVAL; + } + min_mclk = 128 * srate; + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + if (!(machine->util_data.set_mclk % min_mclk)) + mclk = machine->util_data.set_mclk; + else { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + } + + tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1); + + return 0; +} + +static int tegra_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct colibri_t20_wm9715l *machine = snd_soc_card_get_drvdata(rtd->card); + + tegra_asoc_utils_lock_clk_rate(&machine->util_data, 0); + + return 0; +} + +static struct snd_soc_ops colibri_t20_wm9715l_ops = { + .hw_params = colibri_t20_wm9715l_hw_params, + .hw_free = tegra_hw_free, +}; + +static struct snd_soc_ops tegra_spdif_ops = { + .hw_params = tegra_spdif_hw_params, + .hw_free = tegra_hw_free, +}; + +static const struct snd_soc_dapm_widget colibri_t20_wm9715l_dapm_widgets[] = { + SND_SOC_DAPM_HP("HEADPHONE", NULL), + SND_SOC_DAPM_LINE("LINEIN", NULL), + SND_SOC_DAPM_MIC("MIC_IN", NULL), +}; + +/* Currently supported audio map */ +static const struct snd_soc_dapm_route colibri_t20_wm9715l_audio_map[] = { + /* Colibri SODIMM pin 1 (MIC_IN) + Colibri Evaluation Board: Audio jack X26 bottom pink + Iris: Audio header X9 pin 2 + Orchid: Audio jack X11 bottom pink MIC in */ + { "MIC_IN", NULL, "MIC1" }, + + /* Colibri SODIMM pin 5 & 7 (LINEIN_L/R) + Colibri Evaluation Board: Audio jack X26 top blue + Iris: Audio header X9 pin 4 & 3 + MECS Tellurium: Audio jack X11 pin 1 & 2 + Orchid: Audio jack X11 top blue line in */ + { "LINEIN", NULL, "LINEINL" }, + { "LINEIN", NULL, "LINEINR" }, + + /* Colibri SODIMM pin 15 & 17 (HEADPHONE_L/R) + Colibri Evaluation Board: Audio jack X26 middle green + Iris: Audio jack X8 + MECS Tellurium: Audio jack X11 pin 4 & 5 (HEADPHONE_LF/RF) + Orchid: Audio jack X11 middle green line out + Protea: Audio jack X53 line out */ + { "HEADPHONE", NULL, "LOUT2" }, + { "HEADPHONE", NULL, "ROUT2" }, +}; + +static int colibri_t20_wm9715l_init(struct snd_soc_pcm_runtime *rtd) +{ + int err; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + pr_info("%s()\n", __func__); + +//GPIOs + + /* add Colibri T20 specific widgets */ + err = snd_soc_dapm_new_controls(dapm, colibri_t20_wm9715l_dapm_widgets, + ARRAY_SIZE(colibri_t20_wm9715l_dapm_widgets)); + if (err) + return err; + + /* set up Colibri T20 specific audio path audio_map */ + err = snd_soc_dapm_add_routes(dapm, colibri_t20_wm9715l_audio_map, ARRAY_SIZE(colibri_t20_wm9715l_audio_map)); + if (err) + return err; + +//jack detection + + /* connected pins */ + snd_soc_dapm_enable_pin(dapm, "HPOUTL"); + snd_soc_dapm_enable_pin(dapm, "HPOUTR"); + snd_soc_dapm_enable_pin(dapm, "LINEINL"); + snd_soc_dapm_enable_pin(dapm, "LINEINR"); + snd_soc_dapm_enable_pin(dapm, "MIC1"); + + /* not connected pins */ + snd_soc_dapm_nc_pin(dapm, "LOUT2"); + snd_soc_dapm_nc_pin(dapm, "MIC2"); + snd_soc_dapm_nc_pin(dapm, "MONOOUT"); + snd_soc_dapm_nc_pin(dapm, "OUT3"); + snd_soc_dapm_nc_pin(dapm, "PCBEEP"); + snd_soc_dapm_nc_pin(dapm, "PHONE"); + snd_soc_dapm_nc_pin(dapm, "ROUT2"); + + err = snd_soc_dapm_sync(dapm); + if (err) + return err; + + return 0; +} + +static struct snd_soc_dai_link colibri_t20_wm9715l_dai[] = { + { + .name = "AC97", +// .name = "AC97 HiFi", + .stream_name = "AC97 HiFi", + .cpu_dai_name = "tegra20-ac97-pcm", + .codec_dai_name = "wm9712-hifi", + .platform_name = "tegra-pcm-audio", + .codec_name = "wm9712-codec", + .init = colibri_t20_wm9715l_init, + .ops = &colibri_t20_wm9715l_ops, + }, +//order + { + .name = "SPDIF", + .stream_name = "SPDIF PCM", + .codec_name = "spdif-dit.0", + .platform_name = "tegra-pcm-audio", + .cpu_dai_name = "tegra20-spdif", + .codec_dai_name = "dit-hifi", + .ops = &tegra_spdif_ops, + }, +#if 0 + { + .name = "AC97 Aux", + .stream_name = "AC97 Aux", + .cpu_dai_name = "tegra20-ac97-modem", + .codec_dai_name = "wm9712-aux", + .platform_name = "tegra-pcm-audio", + .codec_name = "wm9712-codec", + }, +#endif +}; + +//power management + +static struct snd_soc_card snd_soc_colibri_t20_wm9715l = { + .name = "colibri_t20-wm9715l", + .dai_link = colibri_t20_wm9715l_dai, + .num_links = ARRAY_SIZE(colibri_t20_wm9715l_dai), +// .suspend_post = colibri_t20_wm9715l_suspend_post, +// .resume_pre = colibri_t20_wm9715l_resume_pre, +}; + +// +static struct platform_device *colibri_t20_snd_wm9715l_device; +// + +static __devinit int colibri_t20_wm9715l_driver_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_colibri_t20_wm9715l; + struct colibri_t20_wm9715l *machine; + int ret; + + pr_info("%s()\n", __func__); + + if (!machine_is_colibri_t20()) + return -ENODEV; + +//make sure tegra20-ac97 is properly loaded to avoid subsequent crash + + machine = kzalloc(sizeof(struct colibri_t20_wm9715l), GFP_KERNEL); + if (!machine) { + dev_err(&pdev->dev, "Can't allocate colibri_t20_wm9715l struct\n"); + return -ENOMEM; + } + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev, card); + if (ret) + goto err_free_machine; + +//regulator handling + +//switch handling + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + /* explicitly instanciate AC97 codec */ + + colibri_t20_snd_wm9715l_device = platform_device_alloc("wm9712-codec", -1); + if (!colibri_t20_snd_wm9715l_device) { + dev_err(&pdev->dev, "platform_device_alloc of wm9712-codec failed (%d)\n", + ret); + goto err_fini_utils; + } + + ret = platform_device_add(colibri_t20_snd_wm9715l_device); + if (ret) { + dev_err(&pdev->dev, "platform_device_add of wm9712-codec failed (%d)\n", + ret); + goto err_fini_utils; + } + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + if (!card->instantiated) { + ret = -ENODEV; + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_unregister_card; + } + + return 0; + +err_unregister_card: + snd_soc_unregister_card(card); +err_fini_utils: + tegra_asoc_utils_fini(&machine->util_data); +err_free_machine: + kfree(machine); + return ret; +} + +static int __devexit colibri_t20_wm9715l_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct colibri_t20_wm9715l *machine = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + +//how to revert? +// platform_device_alloc("wm9712-codec"); + + tegra_asoc_utils_fini(&machine->util_data); + + kfree(machine); + + return 0; +} + +static struct platform_driver colibri_t20_wm9715l_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, +// .pm = &snd_soc_pm_ops, + }, + .probe = colibri_t20_wm9715l_driver_probe, + .remove = __devexit_p(colibri_t20_wm9715l_driver_remove), +}; + +static int __init colibri_t20_wm9715l_modinit(void) +{ + return platform_driver_register(&colibri_t20_wm9715l_driver); +} + +static void __exit colibri_t20_wm9715l_modexit(void) +{ + platform_driver_unregister(&colibri_t20_wm9715l_driver); +} + +module_init(colibri_t20_wm9715l_modinit); +module_exit(colibri_t20_wm9715l_modexit); + +/* Module information */ +MODULE_AUTHOR("Marcel Ziswiler "); +MODULE_DESCRIPTION("ALSA SoC WM9715L on Toradex Colibri T20"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c new file mode 100644 index 000000000000..2682ff9fb391 --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.c @@ -0,0 +1,654 @@ +/* + * sound/soc/tegra/tegra20_ac97.c + * + * Copyright (C) 2012 Toradex, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "../../../arch/arm/mach-tegra/gpio-names.h" +#include "tegra_pcm.h" +#include "tegra20_ac97.h" +#include "tegra20_das.h" + +#define DRV_NAME "tegra20-ac97" + +//required? +static DEFINE_MUTEX(car_mutex); + +#define check_ifc(n, ...) if ((n) > TEGRA_DAI_AC97_MODEM) { \ + pr_err("%s: invalid AC97 interface %d\n", __func__, (n)); \ + return __VA_ARGS__; \ +} + +/* required due to AC97 codec drivers not adhering to proper platform driver + model */ +static struct tegra20_ac97 *ac97; + +static int tegra20_ac97_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ +pr_info("%s %u fmt=%d", __func__, __LINE__, fmt); + return 0; +} + +phys_addr_t ac97_get_fifo_phy_base(struct tegra20_ac97 *ac97, int ifc, int fifo) +{ + check_ifc(ifc, 0); + + if (ifc == TEGRA_DAI_AC97_PCM) + return (phys_addr_t)ac97->phys + (fifo ? AC_AC_FIFO_IN1_0 : AC_AC_FIFO_OUT1_0); + else + return (phys_addr_t)ac97->phys + (fifo ? AC_AC_FIFO_IN2_0 : AC_AC_FIFO_OUT2_0); +} + +static int tegra20_ac97_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + pr_info("%s(): dai->id=%d, %s\n", __func__, dai->id, (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)?"play":"rec"); + +//TODO: adaptable sample size + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ac97->playback_dma_data.addr = + ac97_get_fifo_phy_base(ac97, dai->id, AC97_FIFO_TX); + ac97->playback_dma_data.wrap = 4; + ac97->playback_dma_data.width = 32; + } else { + ac97->capture_dma_data.addr = + ac97_get_fifo_phy_base(ac97, dai->id, AC97_FIFO_RX); + ac97->capture_dma_data.wrap = 4; + ac97->capture_dma_data.width = 32; + } + + return 0; +} + +int ac97_fifo_set_attention_level(struct tegra20_ac97 *ac97, int ifc, int fifo, unsigned level) +{ + u32 val; + + pr_info("%s()\n", __func__); + + check_ifc(ifc, -EINVAL); + + if (ifc == TEGRA_DAI_AC97_PCM) + val = readl(ac97->regs + AC_AC_FIFO1_SCR_0); + else + val = readl(ac97->regs + AC_AC_FIFO2_SCR_0); + + if (fifo) { + val &= ~(AC_AC_FIFOx_SCR_REC_FIFOx_FULL_EN | + AC_AC_FIFOx_SCR_REC_FIFOx_3QRT_FULL_EN | + AC_AC_FIFOx_SCR_REC_FIFOx_QRT_FULL_EN | + AC_AC_FIFOx_SCR_REC_FIFOx_NOT_MT_EN); + switch (level) { + case AC97_FIFO_ATN_LVL_NONE: + break; + case AC97_FIFO_ATN_LVL_FULL: + val |= AC_AC_FIFOx_SCR_REC_FIFOx_FULL_EN; + break; + case AC97_FIFO_ATN_LVL_3QUART: + val |= AC_AC_FIFOx_SCR_REC_FIFOx_3QRT_FULL_EN; + break; + case AC97_FIFO_ATN_LVL_QUART: + val |= AC_AC_FIFOx_SCR_REC_FIFOx_QRT_FULL_EN; + break; + case AC97_FIFO_ATN_LVL_EMPTY: + val |= AC_AC_FIFOx_SCR_REC_FIFOx_NOT_MT_EN; + break; + default: + pr_err("%s: invalid FIFO level selector %d\n", __func__, + level); + return -EINVAL; + } + } + else { + val &= ~(AC_AC_FIFOx_SCR_PB_FIFOx_NOT_FULL_EN | + AC_AC_FIFOx_SCR_PB_FIFOx_QRT_MT_EN | + AC_AC_FIFOx_SCR_PB_FIFOx_3QRT_MT_EN | + AC_AC_FIFOx_SCR_PB_FIFOx_MT_EN); + switch (level) { + case AC97_FIFO_ATN_LVL_NONE: + break; + case AC97_FIFO_ATN_LVL_FULL: + val |= AC_AC_FIFOx_SCR_PB_FIFOx_NOT_FULL_EN; + break; + case AC97_FIFO_ATN_LVL_3QUART: + val |= AC_AC_FIFOx_SCR_PB_FIFOx_3QRT_MT_EN; + break; + case AC97_FIFO_ATN_LVL_QUART: + val |= AC_AC_FIFOx_SCR_PB_FIFOx_QRT_MT_EN; + break; + case AC97_FIFO_ATN_LVL_EMPTY: + val |= AC_AC_FIFOx_SCR_PB_FIFOx_MT_EN; + break; + default: + pr_err("%s: invalid FIFO level selector %d\n", __func__, + level); + return -EINVAL; + } + } + + if (ifc == TEGRA_DAI_AC97_PCM) + writel(val, ac97->regs + AC_AC_FIFO1_SCR_0); + else + writel(val, ac97->regs + AC_AC_FIFO2_SCR_0); + + return 0; +} + +void ac97_slot_enable(struct tegra20_ac97 *ac97, int ifc, int fifo, int on) +{ + pr_info("%s()\n", __func__); + + check_ifc(ifc); + + if (!fifo) { + u32 val; + + val = readl(ac97->regs + AC_AC_CTRL_0); + + if (ifc == TEGRA_DAI_AC97_PCM) + if (on) { +#ifndef TEGRA_AC97_32BIT_PLAYBACK + /* Enable packed mode for now */ + val |= AC_AC_CTRL_STM_EN; +#endif + val |= AC_AC_CTRL_PCM_DAC_EN; + } else + val &= ~AC_AC_CTRL_PCM_DAC_EN; + else + if (on) { +#ifndef TEGRA_AC97_32BIT_PLAYBACK + /* Enable packed mode for now */ + val |= AC_AC_CTRL_STM2_EN; +#endif + val |= AC_AC_CTRL_LINE1_DAC_EN; + } else + val &= ~AC_AC_CTRL_LINE1_DAC_EN; + + writel(val, ac97->regs + AC_AC_CTRL_0); + } +} + +/* playback */ +static inline void tegra20_ac97_start_playback(struct snd_soc_dai *cpu_dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(cpu_dai); + + pr_info("%s()\n", __func__); + + ac97_fifo_set_attention_level(ac97, cpu_dai->id, AC97_FIFO_TX, + /* Only FIFO level proven stable for video playback */ +#ifdef TEGRA_AC97_32BIT_PLAYBACK + AC97_FIFO_ATN_LVL_QUART); +#else + AC97_FIFO_ATN_LVL_EMPTY); +#endif + ac97_slot_enable(ac97, cpu_dai->id, AC97_FIFO_TX, 1); +} + +static inline void tegra20_ac97_stop_playback(struct snd_soc_dai *cpu_dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(cpu_dai); + int delay_cnt = 10; /* 1ms max wait for fifo to drain */ + + pr_info("%s()\n", __func__); + + ac97_fifo_set_attention_level(ac97, cpu_dai->id, AC97_FIFO_TX, + AC97_FIFO_ATN_LVL_NONE); + +//something wrong? + while (!(readl(ac97->regs + AC_AC_CTRL_0) & + AC_AC_FIFOx_SCR_PB_FIFOx_UNDERRUN_INT_STA) && + delay_cnt) + { + udelay(100); + delay_cnt--; + } + + ac97_slot_enable(ac97, cpu_dai->id, AC97_FIFO_TX, 0); +} + +/* recording */ +static inline void tegra20_ac97_start_capture(struct snd_soc_dai *cpu_dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(cpu_dai); +//check slot validity in received tag information + ac97_fifo_set_attention_level(ac97, cpu_dai->id, AC97_FIFO_RX, + AC97_FIFO_ATN_LVL_FULL); +} + +static inline void tegra20_ac97_stop_capture(struct snd_soc_dai *cpu_dai) +{ + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(cpu_dai); + ac97_fifo_set_attention_level(ac97, cpu_dai->id, AC97_FIFO_RX, + AC97_FIFO_ATN_LVL_NONE); + +//wait? +} + +static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int ret = 0; + + pr_info("%s()\n", __func__); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_start_playback(dai); + else + tegra20_ac97_start_capture(dai); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra20_ac97_stop_playback(dai); + else + tegra20_ac97_stop_capture(dai); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static void tegra20_ac97_reset(struct snd_ac97 *ac97) +{ + int gpio_status; + + pr_info("%s()\n", __func__); + + /* do wolfson hard reset */ +#define GPIO_AC97_nRESET TEGRA_GPIO_PV0 + gpio_status = gpio_request(GPIO_AC97_nRESET, "WOLFSON_RESET"); + if (gpio_status < 0) { + pr_info("WOLFSON_RESET request GPIO FAILED\n"); + WARN_ON(1); + } + tegra_gpio_enable(GPIO_AC97_nRESET); + gpio_status = gpio_direction_output(GPIO_AC97_nRESET, 0); + if (gpio_status < 0) { + pr_info("WOLFSON_RESET request GPIO DIRECTION FAILED\n"); + WARN_ON(1); + } + udelay(2); + gpio_set_value(GPIO_AC97_nRESET, 1); + udelay(2); +} + +static void tegra20_ac97_warm_reset(struct snd_ac97 *ac97) +{ + int gpio_status; + + pr_info("%s()\n", __func__); + + /* do wolfson warm reset by toggling SYNC */ +#define GPIO_AC97_SYNC TEGRA_GPIO_PP0 + gpio_status = gpio_request(GPIO_AC97_SYNC, "WOLFSON_SYNC"); + if (gpio_status < 0) { + pr_info("WOLFSON_SYNC request GPIO FAILED\n"); + WARN_ON(1); + } + tegra_gpio_enable(GPIO_AC97_SYNC); + gpio_status = gpio_direction_output(GPIO_AC97_SYNC, 1); + if (gpio_status < 0) { + pr_info("WOLFSON_SYNC request GPIO DIRECTION FAILED\n"); + WARN_ON(1); + } + udelay(2); + gpio_set_value(GPIO_AC97_SYNC, 0); + udelay(2); + gpio_free(GPIO_AC97_SYNC); + tegra_gpio_disable(GPIO_AC97_SYNC); +} + +static unsigned short tegra20_ac97_read(struct snd_ac97 *ac97_snd, unsigned short reg) +{ +// struct tegra20_ac97 *ac97 = ac97_snd->private_data; + u32 val; + int timeout = 100; + +//pr_info("%s(0x%04x)", __func__, reg); + +// mutex_lock(&car_mutex); + + /* Set MSB=1 to indicate Read Command! */ + writel((((reg | 0x80) << AC_AC_CMD_CMD_ADDR_SHIFT) & + AC_AC_CMD_CMD_ADDR_MASK) | + /* Set Busy Bit to start Command!! */ + AC_AC_CMD_BUSY, ac97->regs + AC_AC_CMD_0); + + while (!((val = readl(ac97->regs + AC_AC_STATUS1_0)) & + AC_AC_STATUS1_STA_VALID1) && timeout--) + mdelay(1); + +// mutex_unlock(&car_mutex); + +//pr_info(" = 0x%04x\n", (val & AC_AC_STATUS1_STA_DATA1_MASK) >> AC_AC_STATUS1_STA_DATA1_SHIFT); + + return (val & AC_AC_STATUS1_STA_DATA1_MASK) >> + AC_AC_STATUS1_STA_DATA1_SHIFT; +} + +static void tegra20_ac97_write(struct snd_ac97 *ac97_snd, unsigned short reg, + unsigned short val) +{ +// struct tegra20_ac97 *ac97 = ac97_snd->private_data; + int timeout = 100; + +//pr_info("%s(0x%04x, 0x%04x)\n", __func__, reg, val); + +// mutex_lock(&car_mutex); + + writel(((reg << AC_AC_CMD_CMD_ADDR_SHIFT) & AC_AC_CMD_CMD_ADDR_MASK) | + ((val << AC_AC_CMD_CMD_DATA_SHIFT) & + AC_AC_CMD_CMD_DATA_MASK) | + /* Set Busy Bit to start Command!! */ + AC_AC_CMD_BUSY, ac97->regs + AC_AC_CMD_0); + + while (((val = readl(ac97->regs + AC_AC_CMD_0)) & + AC_AC_CMD_BUSY) && timeout--) + mdelay(1); + +// mutex_unlock(&car_mutex); +} + +/* required by sound/soc/codecs/wm9712.c */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = tegra20_ac97_read, + .reset = tegra20_ac97_reset, + .warm_reset = tegra20_ac97_warm_reset, + .write = tegra20_ac97_write, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static struct snd_ac97_bus_ops tegra20_ac97_ops = { + .read = tegra20_ac97_read, + /* reset already done above */ + .write = tegra20_ac97_write, +}; + +static int tegra20_ac97_probe(struct snd_soc_dai *dai) +{ +//hw_probe: reset GPIO, clk_get, clk_enable, request_irq + struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai); + + pr_info("%s()\n", __func__); + pr_info("ac97->capture_dma_data=%p\n", &ac97->capture_dma_data); + pr_info("ac97->playback_dma_data=%p\n", &ac97->playback_dma_data); + + dai->capture_dma_data = &ac97->capture_dma_data; + dai->playback_dma_data = &ac97->playback_dma_data; + + return 0; +} + +//TODO: power management + +static struct snd_soc_dai_ops tegra20_ac97_dai_ops = { + .hw_params = tegra20_ac97_hw_params, +// + .set_fmt = tegra20_ac97_set_fmt, +// + .trigger = tegra20_ac97_trigger, +}; + +struct snd_soc_dai_driver tegra20_ac97_dai[] = { + { + .name = DRV_NAME "-pcm", +// .id = 0, + .probe = tegra20_ac97_probe, +//.resume + .playback = { +// .stream_name = "AC97 PCM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AC97_SAMPLE_RATES, +#ifndef TEGRA_AC97_32BIT_PLAYBACK + .formats = SNDRV_PCM_FMTBIT_S16_LE, +#else + .formats = SNDRV_PCM_FMTBIT_S32_LE, +#endif + }, + .capture = { +// .stream_name = "AC97 PCM Recording", + .channels_min = 1, + .channels_max = 2, + .rates = AC97_SAMPLE_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra20_ac97_dai_ops, + .symmetric_rates = 1, + }, +#if 0 + { + .name = DRV_NAME "-modem", +// .id = 1, + .playback = { + .stream_name = "AC97 Modem Playback", + .channels_min = 1, + .channels_max = 1, + .rates = AC97_SAMPLE_RATES, +#ifndef TEGRA_AC97_32BIT_PLAYBACK + .formats = SNDRV_PCM_FMTBIT_S16_LE, +#else + .formats = SNDRV_PCM_FMTBIT_S32_LE, +#endif + }, + .capture = { + .stream_name = "AC97 Modem Recording", + .channels_min = 1, + .channels_max = 1, + .rates = AC97_SAMPLE_RATES, + .formats = SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &tegra20_ac97_dai_ops, + .symmetric_rates = 1, + }, +#endif +}; + +static __devinit int tegra20_ac97_platform_probe(struct platform_device *pdev) +{ + struct resource *mem, *memregion, *dmareq; + int ret; + struct snd_ac97_bus *ac97_bus; + + pr_info("%s()\n", __func__); + + ac97 = kzalloc(sizeof(struct tegra20_ac97), GFP_KERNEL); + if (!ac97) { + dev_err(&pdev->dev, "Can't allocate tegra20_ac97\n"); + ret = -ENOMEM; + goto exit; + } + dev_set_drvdata(&pdev->dev, ac97); + + ac97->clk_ac97 = clk_get(&pdev->dev, NULL); + if (IS_ERR(ac97->clk_ac97)) { + dev_err(&pdev->dev, "Can't retrieve AC97 clock\n"); + ret = PTR_ERR(ac97->clk_ac97); + goto err_free; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + ac97->phys = mem->start; + + dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmareq) { + dev_err(&pdev->dev, "No DMA resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + memregion = request_mem_region(mem->start, resource_size(mem), + DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_clk_put; + } + + ac97->regs = ioremap(mem->start, resource_size(mem)); + if (!ac97->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_release; + } + + ac97->capture_dma_data.req_sel = dmareq->start; + ac97->playback_dma_data.req_sel = dmareq->start; + + ret = snd_soc_register_dais(&pdev->dev, tegra20_ac97_dai, ARRAY_SIZE(tegra20_ac97_dai)); + if (ret) { + dev_err(&pdev->dev, "Could not register DAIs\n"); + goto err_unmap; + } + +//required? +#if 1 +//use 1 in order for actual card to get 0 which is used as default e.g. in Android +// ret = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + ret = snd_card_create(1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &ac97->card); + if (ret < 0) { + dev_err(&pdev->dev, "failed creating snd_card!\n"); + goto err_create; + } + + ac97->card->dev = &pdev->dev; + strncpy(ac97->card->driver, pdev->dev.driver->name, sizeof(ac97->card->driver)); +#endif + +pr_info("%s() %u\n", __func__, __LINE__); + /* put propper DAC to DAP DAS path in place */ + + ret = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAP_SEL_DAC3, + TEGRA20_DAS_DAP_ID_3); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set dap-dac path\n"); + goto err_create; + } + + ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_3, + TEGRA20_DAS_DAP_SEL_DAC3); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set dac-dap path\n"); + goto err_create; + } + +pr_info("%s() %u\n", __func__, __LINE__); + ret = snd_ac97_bus(ac97->card, 0, &tegra20_ac97_ops, NULL, &ac97_bus); + if (ret) { + dev_err(&pdev->dev, "failed registerign ac97_bus!\n"); + goto err_create; + } + + return 0; + +err_create: + snd_card_free(ac97->card); +err_unmap: + iounmap(ac97->regs); +err_release: + release_mem_region(mem->start, resource_size(mem)); +err_clk_put: + clk_put(ac97->clk_ac97); +err_free: + kfree(ac97); +exit: +pr_info("%s() %u\n", __func__, __LINE__); + return ret; +} + +static int __devexit tegra20_ac97_platform_remove(struct platform_device *pdev) +{ + struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev); + struct resource *res; + + snd_card_free(ac97->card); + + snd_soc_unregister_dai(&pdev->dev); + + iounmap(ac97->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + clk_put(ac97->clk_ac97); + + kfree(ac97); + + return 0; +} + +static struct platform_driver tegra20_ac97_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = tegra20_ac97_platform_probe, + .remove = __devexit_p(tegra20_ac97_platform_remove), +}; + +static int __init snd_tegra20_ac97_init(void) +{ + return platform_driver_register(&tegra20_ac97_driver); +} +module_init(snd_tegra20_ac97_init); + +static void __exit snd_tegra20_ac97_exit(void) +{ + platform_driver_unregister(&tegra20_ac97_driver); +} +module_exit(snd_tegra20_ac97_exit); + +MODULE_AUTHOR("Marcel Ziswiler "); +MODULE_DESCRIPTION("Tegra AC97 ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra20_ac97.h b/sound/soc/tegra/tegra20_ac97.h new file mode 100644 index 000000000000..430ecc1500f4 --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.h @@ -0,0 +1,43 @@ +/* + * linux/sound/soc/tegra/tegra20_ac97.h + * + * 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 _TEGRA_AC97_H +#define _TEGRA_AC97_H + +#include "tegra_pcm.h" + +/* Tegra DAI ID's */ +#define TEGRA_DAI_AC97_PCM 0 /* slot 3: PCM left channel */ + /* slot 4: PCM right channel */ +#define TEGRA_DAI_AC97_MODEM 1 /* slot 5: modem line 1 */ + + /* slot 11: touch panel digitizer data */ + +#define AC97_FIFO_ATN_LVL_NONE 0 +#define AC97_FIFO_ATN_LVL_EMPTY 1 +#define AC97_FIFO_ATN_LVL_QUART 2 +#define AC97_FIFO_ATN_LVL_3QUART 3 +#define AC97_FIFO_ATN_LVL_FULL 4 + +#define AC97_FIFO_TX 0 +#define AC97_FIFO_RX 1 + +#define AC97_SAMPLE_RATES SNDRV_PCM_RATE_8000_48000 + +/* AC97 controller */ +struct tegra20_ac97 { + struct clk *dap_mclk; + struct clk *clk_ac97; + struct snd_card *card; + struct tegra_pcm_dma_params capture_dma_data; + phys_addr_t phys; + struct tegra_pcm_dma_params playback_dma_data; + void __iomem *regs; +}; + +#endif -- cgit v1.2.3