Merge remote-tracking branch 'asoc/topic/tegra' into asoc-next

This commit is contained in:
Mark Brown 2013-02-11 11:06:44 +00:00
commit 56b3f31fb3
12 changed files with 918 additions and 4 deletions

View file

@ -0,0 +1,51 @@
NVIDIA Tegra audio complex
Required properties:
- compatible : "nvidia,tegra-audio-wm9712"
- nvidia,model : The user-visible name of this sound complex.
- nvidia,audio-routing : A list of the connections between audio components.
Each entry is a pair of strings, the first being the connection's sink,
the second being the connection's source. Valid names for sources and
sinks are the WM9712's pins, and the jacks on the board:
WM9712 pins:
* MONOOUT
* HPOUTL
* HPOUTR
* LOUT2
* ROUT2
* OUT3
* LINEINL
* LINEINR
* PHONE
* PCBEEP
* MIC1
* MIC2
* Mic Bias
Board connectors:
* Headphone
* LineIn
* Mic
- nvidia,ac97-controller : The phandle of the Tegra AC97 controller
Example:
sound {
compatible = "nvidia,tegra-audio-wm9712-colibri_t20",
"nvidia,tegra-audio-wm9712";
nvidia,model = "Toradex Colibri T20";
nvidia,audio-routing =
"Headphone", "HPOUTL",
"Headphone", "HPOUTR",
"LineIn", "LINEINL",
"LineIn", "LINEINR",
"Mic", "MIC1";
nvidia,ac97-controller = <&ac97>;
};

View file

@ -0,0 +1,22 @@
NVIDIA Tegra 20 AC97 controller
Required properties:
- compatible : "nvidia,tegra20-ac97"
- reg : Should contain AC97 controller registers location and length
- interrupts : Should contain AC97 interrupt
- nvidia,dma-request-selector : The Tegra DMA controller's phandle and
request selector for the AC97 controller
- nvidia,codec-reset-gpio : The Tegra GPIO controller's phandle and the number
of the GPIO used to reset the external AC97 codec
- nvidia,codec-sync-gpio : The Tegra GPIO controller's phandle and the number
of the GPIO corresponding with the AC97 DAP _FS line
Example:
ac97@70002000 {
compatible = "nvidia,tegra20-ac97";
reg = <0x70002000 0x200>;
interrupts = <0 81 0x04>;
nvidia,dma-request-selector = <&apbdma 12>;
nvidia,codec-reset-gpio = <&gpio 170 0>;
nvidia,codec-sync-gpio = <&gpio 120 0>;
};

View file

@ -6,6 +6,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
depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC
select SND_SOC_AC97_BUS
select SND_SOC_TEGRA20_DAS
help
Say Y or M if you want to add support for codecs attached to the
Tegra20 AC97 interface. You will also need to select the individual
machine drivers to support below.
config SND_SOC_TEGRA20_DAS
tristate
depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC
@ -70,6 +80,15 @@ 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_WM9712
tristate "SoC Audio support for Tegra boards using a WM9712 codec"
depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC
select SND_SOC_TEGRA20_AC97
select SND_SOC_WM9712
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the WM9712 (or compatible) codec.
config SND_SOC_TEGRA_TRIMSLICE
tristate "SoC Audio support for TrimSlice board"
depends on SND_SOC_TEGRA && I2C

View file

@ -1,6 +1,7 @@
# Tegra platform Support
snd-soc-tegra-pcm-objs := tegra_pcm.o
snd-soc-tegra-utils-objs += tegra_asoc_utils.o
snd-soc-tegra20-ac97-objs := tegra20_ac97.o
snd-soc-tegra20-das-objs := tegra20_das.o
snd-soc-tegra20-i2s-objs := tegra20_i2s.o
snd-soc-tegra20-spdif-objs := tegra20_spdif.o
@ -9,6 +10,7 @@ snd-soc-tegra30-i2s-objs := tegra30_i2s.o
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-pcm.o
obj-$(CONFIG_SND_SOC_TEGRA) += snd-soc-tegra-utils.o
obj-$(CONFIG_SND_SOC_TEGRA20_AC97) += snd-soc-tegra20-ac97.o
obj-$(CONFIG_SND_SOC_TEGRA20_DAS) += snd-soc-tegra20-das.o
obj-$(CONFIG_SND_SOC_TEGRA20_I2S) += snd-soc-tegra20-i2s.o
obj-$(CONFIG_SND_SOC_TEGRA20_SPDIF) += snd-soc-tegra20-spdif.o
@ -18,10 +20,12 @@ obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o
# Tegra machine Support
snd-soc-tegra-wm8753-objs := tegra_wm8753.o
snd-soc-tegra-wm8903-objs := tegra_wm8903.o
snd-soc-tegra-wm9712-objs := tegra_wm9712.o
snd-soc-tegra-trimslice-objs := trimslice.o
snd-soc-tegra-alc5632-objs := tegra_alc5632.o
obj-$(CONFIG_SND_SOC_TEGRA_WM8753) += snd-soc-tegra-wm8753.o
obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o
obj-$(CONFIG_SND_SOC_TEGRA_WM9712) += snd-soc-tegra-wm9712.o
obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o
obj-$(CONFIG_SND_SOC_TEGRA_ALC5632) += snd-soc-tegra-alc5632.o

View file

@ -0,0 +1,480 @@
/*
* tegra20_ac97.c - Tegra20 AC97 platform driver
*
* Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>
*
* Partly based on code copyright/by:
*
* Copyright (c) 2011,2012 Toradex Inc.
*
* 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.
*
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "tegra_asoc_utils.h"
#include "tegra20_ac97.h"
#define DRV_NAME "tegra20-ac97"
static struct tegra20_ac97 *workdata;
static void tegra20_ac97_codec_reset(struct snd_ac97 *ac97)
{
u32 readback;
unsigned long timeout;
/* reset line is not driven by DAC pad group, have to toggle GPIO */
gpio_set_value(workdata->reset_gpio, 0);
udelay(2);
gpio_set_value(workdata->reset_gpio, 1);
udelay(2);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
}
static void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97)
{
u32 readback;
unsigned long timeout;
/*
* although sync line is driven by the DAC pad group warm reset using
* the controller cmd is not working, have to toggle sync line
* manually.
*/
gpio_request(workdata->sync_gpio, "codec-sync");
gpio_direction_output(workdata->sync_gpio, 1);
udelay(2);
gpio_set_value(workdata->sync_gpio, 0);
udelay(2);
gpio_free(workdata->sync_gpio);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
}
static unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd,
unsigned short reg)
{
u32 readback;
unsigned long timeout;
regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
(((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
TEGRA20_AC97_CMD_BUSY);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
if (readback & TEGRA20_AC97_STATUS1_STA_VALID1)
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >>
TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT);
}
static void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd,
unsigned short reg, unsigned short val)
{
u32 readback;
unsigned long timeout;
regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) &
TEGRA20_AC97_CMD_CMD_DATA_MASK) |
TEGRA20_AC97_CMD_BUSY);
timeout = jiffies + msecs_to_jiffies(100);
do {
regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback);
if (!(readback & TEGRA20_AC97_CMD_BUSY))
break;
usleep_range(1000, 2000);
} while (!time_after(jiffies, timeout));
}
struct snd_ac97_bus_ops soc_ac97_ops = {
.read = tegra20_ac97_codec_read,
.write = tegra20_ac97_codec_write,
.reset = tegra20_ac97_codec_reset,
.warm_reset = tegra20_ac97_codec_warm_reset,
};
EXPORT_SYMBOL_GPL(soc_ac97_ops);
static inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN);
regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
TEGRA20_AC97_CTRL_PCM_DAC_EN |
TEGRA20_AC97_CTRL_STM_EN,
TEGRA20_AC97_CTRL_PCM_DAC_EN |
TEGRA20_AC97_CTRL_STM_EN);
}
static inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0);
regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
TEGRA20_AC97_CTRL_PCM_DAC_EN, 0);
}
static inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN);
}
static inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97)
{
regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0);
}
static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra20_ac97_start_playback(ac97);
else
tegra20_ac97_start_capture(ac97);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
tegra20_ac97_stop_playback(ac97);
else
tegra20_ac97_stop_capture(ac97);
break;
default:
return -EINVAL;
}
return 0;
}
static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = {
.trigger = tegra20_ac97_trigger,
};
static int tegra20_ac97_probe(struct snd_soc_dai *dai)
{
struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
dai->capture_dma_data = &ac97->capture_dma_data;
dai->playback_dma_data = &ac97->playback_dma_data;
return 0;
}
static struct snd_soc_dai_driver tegra20_ac97_dai = {
.name = "tegra-ac97-pcm",
.ac97_control = 1,
.probe = tegra20_ac97_probe,
.playback = {
.stream_name = "PCM Playback",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.capture = {
.stream_name = "PCM Capture",
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000_48000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
.ops = &tegra20_ac97_dai_ops,
};
static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_AC97_CTRL:
case TEGRA20_AC97_CMD:
case TEGRA20_AC97_STATUS1:
case TEGRA20_AC97_FIFO1_SCR:
case TEGRA20_AC97_FIFO_TX1:
case TEGRA20_AC97_FIFO_RX1:
return true;
default:
break;
}
return false;
}
static bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_AC97_STATUS1:
case TEGRA20_AC97_FIFO1_SCR:
case TEGRA20_AC97_FIFO_TX1:
case TEGRA20_AC97_FIFO_RX1:
return true;
default:
break;
}
return false;
}
static bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case TEGRA20_AC97_FIFO_TX1:
case TEGRA20_AC97_FIFO_RX1:
return true;
default:
break;
}
return false;
}
static const struct regmap_config tegra20_ac97_regmap_config = {
.reg_bits = 32,
.reg_stride = 4,
.val_bits = 32,
.max_register = TEGRA20_AC97_FIFO_RX1,
.writeable_reg = tegra20_ac97_wr_rd_reg,
.readable_reg = tegra20_ac97_wr_rd_reg,
.volatile_reg = tegra20_ac97_volatile_reg,
.precious_reg = tegra20_ac97_precious_reg,
.cache_type = REGCACHE_RBTREE,
};
static int tegra20_ac97_platform_probe(struct platform_device *pdev)
{
struct tegra20_ac97 *ac97;
struct resource *mem, *memregion;
u32 of_dma[2];
void __iomem *regs;
int ret = 0;
ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97),
GFP_KERNEL);
if (!ac97) {
dev_err(&pdev->dev, "Can't allocate tegra20_ac97\n");
ret = -ENOMEM;
goto err;
}
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;
}
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem) {
dev_err(&pdev->dev, "No memory resource\n");
ret = -ENODEV;
goto err_clk_put;
}
memregion = devm_request_mem_region(&pdev->dev, mem->start,
resource_size(mem), DRV_NAME);
if (!memregion) {
dev_err(&pdev->dev, "Memory region already claimed\n");
ret = -EBUSY;
goto err_clk_put;
}
regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
if (!regs) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = -ENOMEM;
goto err_clk_put;
}
ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
&tegra20_ac97_regmap_config);
if (IS_ERR(ac97->regmap)) {
dev_err(&pdev->dev, "regmap init failed\n");
ret = PTR_ERR(ac97->regmap);
goto err_clk_put;
}
if (of_property_read_u32_array(pdev->dev.of_node,
"nvidia,dma-request-selector",
of_dma, 2) < 0) {
dev_err(&pdev->dev, "No DMA resource\n");
ret = -ENODEV;
goto err_clk_put;
}
ac97->reset_gpio = of_get_named_gpio(pdev->dev.of_node,
"nvidia,codec-reset-gpio", 0);
if (gpio_is_valid(ac97->reset_gpio)) {
ret = devm_gpio_request_one(&pdev->dev, ac97->reset_gpio,
GPIOF_OUT_INIT_HIGH, "codec-reset");
if (ret) {
dev_err(&pdev->dev, "could not get codec-reset GPIO\n");
goto err_clk_put;
}
} else {
dev_err(&pdev->dev, "no codec-reset GPIO supplied\n");
goto err_clk_put;
}
ac97->sync_gpio = of_get_named_gpio(pdev->dev.of_node,
"nvidia,codec-sync-gpio", 0);
if (!gpio_is_valid(ac97->sync_gpio)) {
dev_err(&pdev->dev, "no codec-sync GPIO supplied\n");
goto err_clk_put;
}
ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1;
ac97->capture_dma_data.wrap = 4;
ac97->capture_dma_data.width = 32;
ac97->capture_dma_data.req_sel = of_dma[1];
ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1;
ac97->playback_dma_data.wrap = 4;
ac97->playback_dma_data.width = 32;
ac97->playback_dma_data.req_sel = of_dma[1];
ret = snd_soc_register_dais(&pdev->dev, &tegra20_ac97_dai, 1);
if (ret) {
dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
ret = -ENOMEM;
goto err_clk_put;
}
ret = tegra_pcm_platform_register(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
goto err_unregister_dai;
}
ret = tegra_asoc_utils_init(&ac97->util_data, &pdev->dev);
if (ret)
goto err_unregister_pcm;
ret = tegra_asoc_utils_set_ac97_rate(&ac97->util_data);
if (ret)
goto err_asoc_utils_fini;
ret = clk_prepare_enable(ac97->clk_ac97);
if (ret) {
dev_err(&pdev->dev, "clk_enable failed: %d\n", ret);
goto err_asoc_utils_fini;
}
/* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */
workdata = ac97;
return 0;
err_asoc_utils_fini:
tegra_asoc_utils_fini(&ac97->util_data);
err_unregister_pcm:
tegra_pcm_platform_unregister(&pdev->dev);
err_unregister_dai:
snd_soc_unregister_dai(&pdev->dev);
err_clk_put:
clk_put(ac97->clk_ac97);
err:
return ret;
}
static int tegra20_ac97_platform_remove(struct platform_device *pdev)
{
struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev);
tegra_pcm_platform_unregister(&pdev->dev);
snd_soc_unregister_dai(&pdev->dev);
tegra_asoc_utils_fini(&ac97->util_data);
clk_disable_unprepare(ac97->clk_ac97);
clk_put(ac97->clk_ac97);
return 0;
}
static const struct of_device_id tegra20_ac97_of_match[] = {
{ .compatible = "nvidia,tegra20-ac97", },
{},
};
static struct platform_driver tegra20_ac97_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.of_match_table = tegra20_ac97_of_match,
},
.probe = tegra20_ac97_platform_probe,
.remove = tegra20_ac97_platform_remove,
};
module_platform_driver(tegra20_ac97_driver);
MODULE_AUTHOR("Lucas Stach");
MODULE_DESCRIPTION("Tegra20 AC97 ASoC driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra20_ac97_of_match);

View file

@ -0,0 +1,95 @@
/*
* tegra20_ac97.h - Definitions for the Tegra20 AC97 controller driver
*
* Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>
*
* Partly based on code copyright/by:
*
* Copyright (c) 2011,2012 Toradex Inc.
*
* 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.
*
*/
#ifndef __TEGRA20_AC97_H__
#define __TEGRA20_AC97_H__
#include "tegra_pcm.h"
#define TEGRA20_AC97_CTRL 0x00
#define TEGRA20_AC97_CMD 0x04
#define TEGRA20_AC97_STATUS1 0x08
/* ... */
#define TEGRA20_AC97_FIFO1_SCR 0x1c
/* ... */
#define TEGRA20_AC97_FIFO_TX1 0x40
#define TEGRA20_AC97_FIFO_RX1 0x80
/* TEGRA20_AC97_CTRL */
#define TEGRA20_AC97_CTRL_STM2_EN (1 << 16)
#define TEGRA20_AC97_CTRL_DOUBLE_SAMPLING_EN (1 << 11)
#define TEGRA20_AC97_CTRL_IO_CNTRL_EN (1 << 10)
#define TEGRA20_AC97_CTRL_HSET_DAC_EN (1 << 9)
#define TEGRA20_AC97_CTRL_LINE2_DAC_EN (1 << 8)
#define TEGRA20_AC97_CTRL_PCM_LFE_EN (1 << 7)
#define TEGRA20_AC97_CTRL_PCM_SUR_EN (1 << 6)
#define TEGRA20_AC97_CTRL_PCM_CEN_DAC_EN (1 << 5)
#define TEGRA20_AC97_CTRL_LINE1_DAC_EN (1 << 4)
#define TEGRA20_AC97_CTRL_PCM_DAC_EN (1 << 3)
#define TEGRA20_AC97_CTRL_COLD_RESET (1 << 2)
#define TEGRA20_AC97_CTRL_WARM_RESET (1 << 1)
#define TEGRA20_AC97_CTRL_STM_EN (1 << 0)
/* TEGRA20_AC97_CMD */
#define TEGRA20_AC97_CMD_CMD_ADDR_SHIFT 24
#define TEGRA20_AC97_CMD_CMD_ADDR_MASK (0xff << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT)
#define TEGRA20_AC97_CMD_CMD_DATA_SHIFT 8
#define TEGRA20_AC97_CMD_CMD_DATA_MASK (0xffff << TEGRA20_AC97_CMD_CMD_DATA_SHIFT)
#define TEGRA20_AC97_CMD_CMD_ID_SHIFT 2
#define TEGRA20_AC97_CMD_CMD_ID_MASK (0x3 << TEGRA20_AC97_CMD_CMD_ID_SHIFT)
#define TEGRA20_AC97_CMD_BUSY (1 << 0)
/* TEGRA20_AC97_STATUS1 */
#define TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT 24
#define TEGRA20_AC97_STATUS1_STA_ADDR1_MASK (0xff << TEGRA20_AC97_STATUS1_STA_ADDR1_SHIFT)
#define TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT 8
#define TEGRA20_AC97_STATUS1_STA_DATA1_MASK (0xffff << TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT)
#define TEGRA20_AC97_STATUS1_STA_VALID1 (1 << 2)
#define TEGRA20_AC97_STATUS1_STANDBY1 (1 << 1)
#define TEGRA20_AC97_STATUS1_CODEC1_RDY (1 << 0)
/* TEGRA20_AC97_FIFO1_SCR */
#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT 27
#define TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_REC_MT_CNT_SHIFT)
#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT 22
#define TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_MASK (0x1f << TEGRA20_AC97_FIFO_SCR_PB_MT_CNT_SHIFT)
#define TEGRA20_AC97_FIFO_SCR_REC_OVERRUN_INT_STA (1 << 19)
#define TEGRA20_AC97_FIFO_SCR_PB_UNDERRUN_INT_STA (1 << 18)
#define TEGRA20_AC97_FIFO_SCR_REC_FORCE_MT (1 << 17)
#define TEGRA20_AC97_FIFO_SCR_PB_FORCE_MT (1 << 16)
#define TEGRA20_AC97_FIFO_SCR_REC_FULL_EN (1 << 15)
#define TEGRA20_AC97_FIFO_SCR_REC_3QRT_FULL_EN (1 << 14)
#define TEGRA20_AC97_FIFO_SCR_REC_QRT_FULL_EN (1 << 13)
#define TEGRA20_AC97_FIFO_SCR_REC_EMPTY_EN (1 << 12)
#define TEGRA20_AC97_FIFO_SCR_PB_NOT_FULL_EN (1 << 11)
#define TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN (1 << 10)
#define TEGRA20_AC97_FIFO_SCR_PB_3QRT_MT_EN (1 << 9)
#define TEGRA20_AC97_FIFO_SCR_PB_EMPTY_MT_EN (1 << 8)
struct tegra20_ac97 {
struct clk *clk_ac97;
struct tegra_pcm_dma_params capture_dma_data;
struct tegra_pcm_dma_params playback_dma_data;
struct regmap *regmap;
int reset_gpio;
int sync_gpio;
struct tegra_asoc_utils_data util_data;
};
#endif /* __TEGRA20_AC97_H__ */

View file

@ -191,6 +191,19 @@ static int tegra20_das_probe(struct platform_device *pdev)
goto err;
}
ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_3,
TEGRA20_DAS_DAP_SEL_DAC3);
if (ret) {
dev_err(&pdev->dev, "Can't set up DAS DAP connection\n");
goto err;
}
ret = tegra20_das_connect_dac_to_dap(TEGRA20_DAS_DAC_ID_3,
TEGRA20_DAS_DAC_SEL_DAP3);
if (ret) {
dev_err(&pdev->dev, "Can't set up DAS DAC connection\n");
goto err;
}
platform_set_drvdata(pdev, das);
return 0;

View file

@ -580,7 +580,7 @@ static int tegra30_ahub_probe(struct platform_device *pdev)
clk_put(ahub->clk_apbif);
err_clk_put_d_audio:
clk_put(ahub->clk_d_audio);
ahub = 0;
ahub = NULL;
err:
return ret;
}
@ -597,7 +597,7 @@ static int tegra30_ahub_remove(struct platform_device *pdev)
clk_put(ahub->clk_apbif);
clk_put(ahub->clk_d_audio);
ahub = 0;
ahub = NULL;
return 0;
}

View file

@ -71,7 +71,7 @@ static int tegra30_i2s_runtime_resume(struct device *dev)
return 0;
}
int tegra30_i2s_startup(struct snd_pcm_substream *substream,
static int tegra30_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);
@ -98,7 +98,7 @@ int tegra30_i2s_startup(struct snd_pcm_substream *substream,
return ret;
}
void tegra30_i2s_shutdown(struct snd_pcm_substream *substream,
static void tegra30_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai);

View file

@ -112,6 +112,59 @@ int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_rate);
int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data)
{
const int pll_rate = 73728000;
const int ac97_rate = 24576000;
int err;
clk_disable_unprepare(data->clk_cdev1);
clk_disable_unprepare(data->clk_pll_a_out0);
clk_disable_unprepare(data->clk_pll_a);
/*
* AC97 rate is fixed at 24.576MHz and is used for both the host
* controller and the external codec
*/
err = clk_set_rate(data->clk_pll_a, pll_rate);
if (err) {
dev_err(data->dev, "Can't set pll_a rate: %d\n", err);
return err;
}
err = clk_set_rate(data->clk_pll_a_out0, ac97_rate);
if (err) {
dev_err(data->dev, "Can't set pll_a_out0 rate: %d\n", err);
return err;
}
/* Don't set cdev1/extern1 rate; it's locked to pll_a_out0 */
err = clk_prepare_enable(data->clk_pll_a);
if (err) {
dev_err(data->dev, "Can't enable pll_a: %d\n", err);
return err;
}
err = clk_prepare_enable(data->clk_pll_a_out0);
if (err) {
dev_err(data->dev, "Can't enable pll_a_out0: %d\n", err);
return err;
}
err = clk_prepare_enable(data->clk_cdev1);
if (err) {
dev_err(data->dev, "Can't enable cdev1: %d\n", err);
return err;
}
data->set_baseclock = pll_rate;
data->set_mclk = ac97_rate;
return 0;
}
EXPORT_SYMBOL_GPL(tegra_asoc_utils_set_ac97_rate);
int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
struct device *dev)
{

View file

@ -43,6 +43,7 @@ struct tegra_asoc_utils_data {
int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate,
int mclk);
int tegra_asoc_utils_set_ac97_rate(struct tegra_asoc_utils_data *data);
int tegra_asoc_utils_init(struct tegra_asoc_utils_data *data,
struct device *dev);
void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data);

View file

@ -0,0 +1,176 @@
/*
* tegra20_wm9712.c - Tegra machine ASoC driver for boards using WM9712 codec.
*
* Copyright 2012 Lucas Stach <dev@lynxeye.de>
*
* Partly based on code copyright/by:
* Copyright 2011,2012 Toradex Inc.
*
* 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.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#define DRV_NAME "tegra-snd-wm9712"
struct tegra_wm9712 {
struct platform_device *codec;
};
static const struct snd_soc_dapm_widget tegra_wm9712_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_LINE("LineIn", NULL),
SND_SOC_DAPM_MIC("Mic", NULL),
};
static int tegra_wm9712_init(struct snd_soc_pcm_runtime *rtd)
{
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct snd_soc_codec *codec = codec_dai->codec;
struct snd_soc_dapm_context *dapm = &codec->dapm;
snd_soc_dapm_force_enable_pin(dapm, "Mic Bias");
return snd_soc_dapm_sync(dapm);
}
static struct snd_soc_dai_link tegra_wm9712_dai = {
.name = "AC97 HiFi",
.stream_name = "AC97 HiFi",
.cpu_dai_name = "tegra-ac97-pcm",
.codec_dai_name = "wm9712-hifi",
.codec_name = "wm9712-codec",
.init = tegra_wm9712_init,
};
static struct snd_soc_card snd_soc_tegra_wm9712 = {
.name = "tegra-wm9712",
.owner = THIS_MODULE,
.dai_link = &tegra_wm9712_dai,
.num_links = 1,
.dapm_widgets = tegra_wm9712_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(tegra_wm9712_dapm_widgets),
.fully_routed = true,
};
static int tegra_wm9712_driver_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &snd_soc_tegra_wm9712;
struct tegra_wm9712 *machine;
int ret;
if (!pdev->dev.of_node) {
dev_err(&pdev->dev, "No platform data supplied\n");
return -EINVAL;
}
machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm9712),
GFP_KERNEL);
if (!machine) {
dev_err(&pdev->dev, "Can't allocate tegra_wm9712 struct\n");
return -ENOMEM;
}
card->dev = &pdev->dev;
platform_set_drvdata(pdev, card);
snd_soc_card_set_drvdata(card, machine);
machine->codec = platform_device_alloc("wm9712-codec", -1);
if (!machine->codec) {
dev_err(&pdev->dev, "Can't allocate wm9712 platform device\n");
return -ENOMEM;
}
ret = platform_device_add(machine->codec);
if (ret)
goto codec_put;
ret = snd_soc_of_parse_card_name(card, "nvidia,model");
if (ret)
goto codec_unregister;
ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing");
if (ret)
goto codec_unregister;
tegra_wm9712_dai.cpu_of_node = of_parse_phandle(np,
"nvidia,ac97-controller", 0);
if (!tegra_wm9712_dai.cpu_of_node) {
dev_err(&pdev->dev,
"Property 'nvidia,ac97-controller' missing or invalid\n");
ret = -EINVAL;
goto codec_unregister;
}
tegra_wm9712_dai.platform_of_node = tegra_wm9712_dai.cpu_of_node;
ret = snd_soc_register_card(card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
ret);
goto codec_unregister;
}
return 0;
codec_unregister:
platform_device_del(machine->codec);
codec_put:
platform_device_put(machine->codec);
return ret;
}
static int tegra_wm9712_driver_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct tegra_wm9712 *machine = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
platform_device_unregister(machine->codec);
return 0;
}
static const struct of_device_id tegra_wm9712_of_match[] = {
{ .compatible = "nvidia,tegra-audio-wm9712", },
{},
};
static struct platform_driver tegra_wm9712_driver = {
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = tegra_wm9712_of_match,
},
.probe = tegra_wm9712_driver_probe,
.remove = tegra_wm9712_driver_remove,
};
module_platform_driver(tegra_wm9712_driver);
MODULE_AUTHOR("Lucas Stach");
MODULE_DESCRIPTION("Tegra+WM9712 machine ASoC driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_DEVICE_TABLE(of, tegra_wm9712_of_match);