diff options
Diffstat (limited to 'sound/soc/tegra')
25 files changed, 7050 insertions, 0 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig new file mode 100644 index 00000000000..31198cf7f88 --- /dev/null +++ b/sound/soc/tegra/Kconfig @@ -0,0 +1,130 @@ +config SND_SOC_TEGRA +	tristate "SoC Audio for the Tegra System-on-Chip" +	depends on (ARCH_TEGRA && TEGRA20_APB_DMA) || COMPILE_TEST +	depends on COMMON_CLK +	depends on RESET_CONTROLLER +	select REGMAP_MMIO +	select SND_SOC_GENERIC_DMAENGINE_PCM +	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 +	help +	  Say Y or M if you want to add support for the Tegra20 DAS module. +	  You will also need to select the individual machine drivers to +	  support below. + +config SND_SOC_TEGRA20_I2S +	tristate +	depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC +	select SND_SOC_TEGRA20_DAS +	help +	  Say Y or M if you want to add support for codecs attached to the +	  Tegra20 I2S interface. You will also need to select the individual +	  machine drivers to support below. + +config SND_SOC_TEGRA20_SPDIF +	tristate +	depends on SND_SOC_TEGRA && ARCH_TEGRA_2x_SOC +	default m +	help +	  Say Y or M if you want to add support for the Tegra20 SPDIF interface. +	  You will also need to select the individual machine drivers to support +	  below. + +config SND_SOC_TEGRA30_AHUB +	tristate +	depends on SND_SOC_TEGRA && ARCH_TEGRA_3x_SOC +	help +	  Say Y or M if you want to add support for the Tegra20 AHUB module. +	  You will also need to select the individual machine drivers to +	  support below. + +config SND_SOC_TEGRA30_I2S +	tristate +	depends on SND_SOC_TEGRA && ARCH_TEGRA_3x_SOC +	select SND_SOC_TEGRA30_AHUB +	help +	  Say Y or M if you want to add support for codecs attached to the +	  Tegra30 I2S interface. You will also need to select the individual +	  machine drivers to support below. + +config SND_SOC_TEGRA_RT5640 +	tristate "SoC Audio support for Tegra boards using an RT5640 codec" +	depends on SND_SOC_TEGRA && I2C && GPIOLIB +	select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC +	select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC +	select SND_SOC_RT5640 +	help +	  Say Y or M here if you want to add support for SoC audio on Tegra +	  boards using the RT5640 codec, such as Dalmore. + +config SND_SOC_TEGRA_WM8753 +	tristate "SoC Audio support for Tegra boards using a WM8753 codec" +	depends on SND_SOC_TEGRA && I2C && GPIOLIB +	select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC +	select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC +	select SND_SOC_WM8753 +	help +	  Say Y or M here if you want to add support for SoC audio on Tegra +	  boards using the WM8753 codec, such as Whistler. + +config SND_SOC_TEGRA_WM8903 +	tristate "SoC Audio support for Tegra boards using a WM8903 codec" +	depends on SND_SOC_TEGRA && I2C && GPIOLIB +	select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC +	select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC +	select SND_SOC_WM8903 +	help +	  Say Y or M here if you want to add support for SoC audio on Tegra +	  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 && GPIOLIB +	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 +	select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC +	select SND_SOC_TLV320AIC23_I2C +	help +	  Say Y or M here if you want to add support for SoC audio on the +	  TrimSlice platform. + +config SND_SOC_TEGRA_ALC5632 +	tristate "SoC Audio support for Tegra boards using an ALC5632 codec" +	depends on SND_SOC_TEGRA && I2C && GPIOLIB +	select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC +	select SND_SOC_ALC5632 +	help +	  Say Y or M here if you want to add support for SoC audio on the +	  Toshiba AC100 netbook. + +config SND_SOC_TEGRA_MAX98090 +	tristate "SoC Audio support for Tegra boards using a MAX98090 codec" +	depends on SND_SOC_TEGRA && I2C && GPIOLIB +	select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC +	select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC +	select SND_SOC_MAX98090 +	help +	  Say Y or M here if you want to add support for SoC audio on Tegra +	  boards using the MAX98090 codec, such as Venice2. diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile new file mode 100644 index 00000000000..5ae588cd96c --- /dev/null +++ b/sound/soc/tegra/Makefile @@ -0,0 +1,35 @@ +# 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 +snd-soc-tegra30-ahub-objs := tegra30_ahub.o +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 +obj-$(CONFIG_SND_SOC_TEGRA30_AHUB) += snd-soc-tegra30-ahub.o +obj-$(CONFIG_SND_SOC_TEGRA30_I2S) += snd-soc-tegra30-i2s.o + +# Tegra machine Support +snd-soc-tegra-rt5640-objs := tegra_rt5640.o +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 +snd-soc-tegra-max98090-objs := tegra_max98090.o + +obj-$(CONFIG_SND_SOC_TEGRA_RT5640) += snd-soc-tegra-rt5640.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 +obj-$(CONFIG_SND_SOC_TEGRA_MAX98090) += snd-soc-tegra-max98090.o diff --git a/sound/soc/tegra/tegra20_ac97.c b/sound/soc/tegra/tegra20_ac97.c new file mode 100644 index 00000000000..3b0fa12dbff --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.c @@ -0,0 +1,453 @@ +/* + * 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 <sound/dmaengine_pcm.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)); +} + +static struct snd_ac97_bus_ops tegra20_ac97_ops = { +	.read		= tegra20_ac97_codec_read, +	.write		= tegra20_ac97_codec_write, +	.reset		= tegra20_ac97_codec_reset, +	.warm_reset	= tegra20_ac97_codec_warm_reset, +}; + +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 const struct snd_soc_component_driver tegra20_ac97_component = { +	.name		= DRV_NAME, +}; + +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_FLAT, +}; + +static int tegra20_ac97_platform_probe(struct platform_device *pdev) +{ +	struct tegra20_ac97 *ac97; +	struct resource *mem; +	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 = devm_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); +	regs = devm_ioremap_resource(&pdev->dev, mem); +	if (IS_ERR(regs)) { +		ret = PTR_ERR(regs); +		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; +	} + +	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.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	ac97->capture_dma_data.maxburst = 4; + +	ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1; +	ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	ac97->playback_dma_data.maxburst = 4; + +	ret = clk_prepare_enable(ac97->clk_ac97); +	if (ret) { +		dev_err(&pdev->dev, "clk_enable failed: %d\n", ret); +		goto err; +	} + +	ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops); +	if (ret) { +		dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret); +		goto err_clk_disable_unprepare; +	} + +	ret = snd_soc_register_component(&pdev->dev, &tegra20_ac97_component, +					 &tegra20_ac97_dai, 1); +	if (ret) { +		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); +		ret = -ENOMEM; +		goto err_clk_disable_unprepare; +	} + +	ret = tegra_pcm_platform_register(&pdev->dev); +	if (ret) { +		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); +		goto err_unregister_component; +	} + +	/* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */ +	workdata = ac97; + +	return 0; + +err_unregister_component: +	snd_soc_unregister_component(&pdev->dev); +err_clk_disable_unprepare: +	clk_disable_unprepare(ac97->clk_ac97); +err_clk_put: +err: +	snd_soc_set_ac97_ops(NULL); +	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_component(&pdev->dev); + +	clk_disable_unprepare(ac97->clk_ac97); + +	snd_soc_set_ac97_ops(NULL); + +	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); diff --git a/sound/soc/tegra/tegra20_ac97.h b/sound/soc/tegra/tegra20_ac97.h new file mode 100644 index 00000000000..0a39d823edc --- /dev/null +++ b/sound/soc/tegra/tegra20_ac97.h @@ -0,0 +1,94 @@ +/* + * 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 snd_dmaengine_dai_dma_data capture_dma_data; +	struct snd_dmaengine_dai_dma_data playback_dma_data; +	struct regmap *regmap; +	int reset_gpio; +	int sync_gpio; +}; +#endif /* __TEGRA20_AC97_H__ */ diff --git a/sound/soc/tegra/tegra20_das.c b/sound/soc/tegra/tegra20_das.c new file mode 100644 index 00000000000..a634f13b3ff --- /dev/null +++ b/sound/soc/tegra/tegra20_das.c @@ -0,0 +1,246 @@ +/* + * tegra20_das.c - Tegra20 DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, 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. + * + * 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 <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include "tegra20_das.h" + +#define DRV_NAME "tegra20-das" + +static struct tegra20_das *das; + +static inline void tegra20_das_write(u32 reg, u32 val) +{ +	regmap_write(das->regmap, reg, val); +} + +static inline u32 tegra20_das_read(u32 reg) +{ +	u32 val; +	regmap_read(das->regmap, reg, &val); +	return val; +} + +int tegra20_das_connect_dap_to_dac(int dap, int dac) +{ +	u32 addr; +	u32 reg; + +	if (!das) +		return -ENODEV; + +	addr = TEGRA20_DAS_DAP_CTRL_SEL + +		(dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); +	reg = dac << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; + +	tegra20_das_write(addr, reg); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dac); + +int tegra20_das_connect_dap_to_dap(int dap, int otherdap, int master, +				   int sdata1rx, int sdata2rx) +{ +	u32 addr; +	u32 reg; + +	if (!das) +		return -ENODEV; + +	addr = TEGRA20_DAS_DAP_CTRL_SEL + +		(dap * TEGRA20_DAS_DAP_CTRL_SEL_STRIDE); +	reg = otherdap << TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P | +		!!sdata2rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P | +		!!sdata1rx << TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P | +		!!master << TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P; + +	tegra20_das_write(addr, reg); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dap_to_dap); + +int tegra20_das_connect_dac_to_dap(int dac, int dap) +{ +	u32 addr; +	u32 reg; + +	if (!das) +		return -ENODEV; + +	addr = TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL + +		(dac * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); +	reg = dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | +		dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | +		dap << TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; + +	tegra20_das_write(addr, reg); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra20_das_connect_dac_to_dap); + +#define LAST_REG(name) \ +	(TEGRA20_DAS_##name + \ +	 (TEGRA20_DAS_##name##_STRIDE * (TEGRA20_DAS_##name##_COUNT - 1))) + +static bool tegra20_das_wr_rd_reg(struct device *dev, unsigned int reg) +{ +	if ((reg >= TEGRA20_DAS_DAP_CTRL_SEL) && +	    (reg <= LAST_REG(DAP_CTRL_SEL))) +		return true; +	if ((reg >= TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL) && +	    (reg <= LAST_REG(DAC_INPUT_DATA_CLK_SEL))) +		return true; + +	return false; +} + +static const struct regmap_config tegra20_das_regmap_config = { +	.reg_bits = 32, +	.reg_stride = 4, +	.val_bits = 32, +	.max_register = LAST_REG(DAC_INPUT_DATA_CLK_SEL), +	.writeable_reg = tegra20_das_wr_rd_reg, +	.readable_reg = tegra20_das_wr_rd_reg, +	.cache_type = REGCACHE_FLAT, +}; + +static int tegra20_das_probe(struct platform_device *pdev) +{ +	struct resource *res, *region; +	void __iomem *regs; +	int ret = 0; + +	if (das) +		return -ENODEV; + +	das = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_das), GFP_KERNEL); +	if (!das) { +		dev_err(&pdev->dev, "Can't allocate tegra20_das\n"); +		ret = -ENOMEM; +		goto err; +	} +	das->dev = &pdev->dev; + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res) { +		dev_err(&pdev->dev, "No memory resource\n"); +		ret = -ENODEV; +		goto err; +	} + +	region = devm_request_mem_region(&pdev->dev, res->start, +					 resource_size(res), pdev->name); +	if (!region) { +		dev_err(&pdev->dev, "Memory region already claimed\n"); +		ret = -EBUSY; +		goto err; +	} + +	regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); +	if (!regs) { +		dev_err(&pdev->dev, "ioremap failed\n"); +		ret = -ENOMEM; +		goto err; +	} + +	das->regmap = devm_regmap_init_mmio(&pdev->dev, regs, +					    &tegra20_das_regmap_config); +	if (IS_ERR(das->regmap)) { +		dev_err(&pdev->dev, "regmap init failed\n"); +		ret = PTR_ERR(das->regmap); +		goto err; +	} + +	ret = tegra20_das_connect_dap_to_dac(TEGRA20_DAS_DAP_ID_1, +					     TEGRA20_DAS_DAP_SEL_DAC1); +	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_1, +					     TEGRA20_DAS_DAC_SEL_DAP1); +	if (ret) { +		dev_err(&pdev->dev, "Can't set up DAS DAC connection\n"); +		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; + +err: +	das = NULL; +	return ret; +} + +static int tegra20_das_remove(struct platform_device *pdev) +{ +	if (!das) +		return -ENODEV; + +	das = NULL; + +	return 0; +} + +static const struct of_device_id tegra20_das_of_match[] = { +	{ .compatible = "nvidia,tegra20-das", }, +	{}, +}; + +static struct platform_driver tegra20_das_driver = { +	.probe = tegra20_das_probe, +	.remove = tegra20_das_remove, +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.of_match_table = tegra20_das_of_match, +	}, +}; +module_platform_driver(tegra20_das_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra20 DAS driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_das_of_match); diff --git a/sound/soc/tegra/tegra20_das.h b/sound/soc/tegra/tegra20_das.h new file mode 100644 index 00000000000..be217f3d3a7 --- /dev/null +++ b/sound/soc/tegra/tegra20_das.h @@ -0,0 +1,134 @@ +/* + * tegra20_das.h - Definitions for Tegra20 DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, 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. + * + * 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 + * + */ + +#ifndef __TEGRA20_DAS_H__ +#define __TEGRA20_DAS_H__ + +/* Register TEGRA20_DAS_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_CTRL_SEL			0x00 +#define TEGRA20_DAS_DAP_CTRL_SEL_COUNT			5 +#define TEGRA20_DAS_DAP_CTRL_SEL_STRIDE			4 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P		31 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S		1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P	30 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S	1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P	29 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S	1 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P		0 +#define TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S		5 + +/* Values for field TEGRA20_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ +#define TEGRA20_DAS_DAP_SEL_DAC1	0 +#define TEGRA20_DAS_DAP_SEL_DAC2	1 +#define TEGRA20_DAS_DAP_SEL_DAC3	2 +#define TEGRA20_DAS_DAP_SEL_DAP1	16 +#define TEGRA20_DAS_DAP_SEL_DAP2	17 +#define TEGRA20_DAS_DAP_SEL_DAP3	18 +#define TEGRA20_DAS_DAP_SEL_DAP4	19 +#define TEGRA20_DAS_DAP_SEL_DAP5	20 + +/* Register TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL */ +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL			0x40 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT		3 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE		4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P	28 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S	4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P	24 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S	4 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P	0 +#define TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S	4 + +/* + * Values for: + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL + * TEGRA20_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL + */ +#define TEGRA20_DAS_DAC_SEL_DAP1	0 +#define TEGRA20_DAS_DAC_SEL_DAP2	1 +#define TEGRA20_DAS_DAC_SEL_DAP3	2 +#define TEGRA20_DAS_DAC_SEL_DAP4	3 +#define TEGRA20_DAS_DAC_SEL_DAP5	4 + +/* + * Names/IDs of the DACs/DAPs. + */ + +#define TEGRA20_DAS_DAP_ID_1 0 +#define TEGRA20_DAS_DAP_ID_2 1 +#define TEGRA20_DAS_DAP_ID_3 2 +#define TEGRA20_DAS_DAP_ID_4 3 +#define TEGRA20_DAS_DAP_ID_5 4 + +#define TEGRA20_DAS_DAC_ID_1 0 +#define TEGRA20_DAS_DAC_ID_2 1 +#define TEGRA20_DAS_DAC_ID_3 2 + +struct tegra20_das { +	struct device *dev; +	struct regmap *regmap; +}; + +/* + * Terminology: + * DAS: Digital audio switch (HW module controlled by this driver) + * DAP: Digital audio port (port/pins on Tegra device) + * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) + * + * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific + * DAC, or another DAP. When DAPs are connected, one must be the master and + * one the slave. Each DAC allows selection of a specific DAP for input, to + * cater for the case where N DAPs are connected to 1 DAC for broadcast + * output. + * + * This driver is dumb; no attempt is made to ensure that a valid routing + * configuration is programmed. + */ + +/* + * Connect a DAP to to a DAC + * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* + * dac_sel: DAC to connect to: TEGRA20_DAS_DAP_SEL_DAC* + */ +extern int tegra20_das_connect_dap_to_dac(int dap_id, int dac_sel); + +/* + * Connect a DAP to to another DAP + * dap_id: DAP to connect: TEGRA20_DAS_DAP_ID_* + * other_dap_sel: DAP to connect to: TEGRA20_DAS_DAP_SEL_DAP* + * master: Is this DAP the master (1) or slave (0) + * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) + * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) + */ +extern int tegra20_das_connect_dap_to_dap(int dap_id, int other_dap_sel, +					  int master, int sdata1rx, +					  int sdata2rx); + +/* + * Connect a DAC's input to a DAP + * (DAC outputs are selected by the DAP) + * dac_id: DAC ID to connect: TEGRA20_DAS_DAC_ID_* + * dap_sel: DAP to receive input from: TEGRA20_DAS_DAC_SEL_DAP* + */ +extern int tegra20_das_connect_dac_to_dap(int dac_id, int dap_sel); + +#endif diff --git a/sound/soc/tegra/tegra20_i2s.c b/sound/soc/tegra/tegra20_i2s.c new file mode 100644 index 00000000000..79a9932ffe6 --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.c @@ -0,0 +1,480 @@ +/* + * tegra20_i2s.c - Tegra20 I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * 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 <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.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 <sound/dmaengine_pcm.h> + +#include "tegra20_i2s.h" + +#define DRV_NAME "tegra20-i2s" + +static int tegra20_i2s_runtime_suspend(struct device *dev) +{ +	struct tegra20_i2s *i2s = dev_get_drvdata(dev); + +	clk_disable_unprepare(i2s->clk_i2s); + +	return 0; +} + +static int tegra20_i2s_runtime_resume(struct device *dev) +{ +	struct tegra20_i2s *i2s = dev_get_drvdata(dev); +	int ret; + +	ret = clk_prepare_enable(i2s->clk_i2s); +	if (ret) { +		dev_err(dev, "clk_enable failed: %d\n", ret); +		return ret; +	} + +	return 0; +} + +static int tegra20_i2s_set_fmt(struct snd_soc_dai *dai, +				unsigned int fmt) +{ +	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); +	unsigned int mask = 0, val = 0; + +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_NB_NF: +		break; +	default: +		return -EINVAL; +	} + +	mask |= TEGRA20_I2S_CTRL_MASTER_ENABLE; +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBS_CFS: +		val |= TEGRA20_I2S_CTRL_MASTER_ENABLE; +		break; +	case SND_SOC_DAIFMT_CBM_CFM: +		break; +	default: +		return -EINVAL; +	} + +	mask |= TEGRA20_I2S_CTRL_BIT_FORMAT_MASK | +		TEGRA20_I2S_CTRL_LRCK_MASK; +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case SND_SOC_DAIFMT_DSP_A: +		val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; +		val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; +		break; +	case SND_SOC_DAIFMT_DSP_B: +		val |= TEGRA20_I2S_CTRL_BIT_FORMAT_DSP; +		val |= TEGRA20_I2S_CTRL_LRCK_R_LOW; +		break; +	case SND_SOC_DAIFMT_I2S: +		val |= TEGRA20_I2S_CTRL_BIT_FORMAT_I2S; +		val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; +		break; +	case SND_SOC_DAIFMT_RIGHT_J: +		val |= TEGRA20_I2S_CTRL_BIT_FORMAT_RJM; +		val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; +		break; +	case SND_SOC_DAIFMT_LEFT_J: +		val |= TEGRA20_I2S_CTRL_BIT_FORMAT_LJM; +		val |= TEGRA20_I2S_CTRL_LRCK_L_LOW; +		break; +	default: +		return -EINVAL; +	} + +	regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val); + +	return 0; +} + +static int tegra20_i2s_hw_params(struct snd_pcm_substream *substream, +				 struct snd_pcm_hw_params *params, +				 struct snd_soc_dai *dai) +{ +	struct device *dev = dai->dev; +	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); +	unsigned int mask, val; +	int ret, sample_size, srate, i2sclock, bitcnt; + +	mask = TEGRA20_I2S_CTRL_BIT_SIZE_MASK; +	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_S16_LE: +		val = TEGRA20_I2S_CTRL_BIT_SIZE_16; +		sample_size = 16; +		break; +	case SNDRV_PCM_FORMAT_S24_LE: +		val = TEGRA20_I2S_CTRL_BIT_SIZE_24; +		sample_size = 24; +		break; +	case SNDRV_PCM_FORMAT_S32_LE: +		val = TEGRA20_I2S_CTRL_BIT_SIZE_32; +		sample_size = 32; +		break; +	default: +		return -EINVAL; +	} + +	mask |= TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK; +	val |= TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED; + +	regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, mask, val); + +	srate = params_rate(params); + +	/* Final "* 2" required by Tegra hardware */ +	i2sclock = srate * params_channels(params) * sample_size * 2; + +	ret = clk_set_rate(i2s->clk_i2s, i2sclock); +	if (ret) { +		dev_err(dev, "Can't set I2S clock rate: %d\n", ret); +		return ret; +	} + +	bitcnt = (i2sclock / (2 * srate)) - 1; +	if (bitcnt < 0 || bitcnt > TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) +		return -EINVAL; +	val = bitcnt << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + +	if (i2sclock % (2 * srate)) +		val |= TEGRA20_I2S_TIMING_NON_SYM_ENABLE; + +	regmap_write(i2s->regmap, TEGRA20_I2S_TIMING, val); + +	regmap_write(i2s->regmap, TEGRA20_I2S_FIFO_SCR, +		     TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | +		     TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); + +	return 0; +} + +static void tegra20_i2s_start_playback(struct tegra20_i2s *i2s) +{ +	regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, +			   TEGRA20_I2S_CTRL_FIFO1_ENABLE, +			   TEGRA20_I2S_CTRL_FIFO1_ENABLE); +} + +static void tegra20_i2s_stop_playback(struct tegra20_i2s *i2s) +{ +	regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, +			   TEGRA20_I2S_CTRL_FIFO1_ENABLE, 0); +} + +static void tegra20_i2s_start_capture(struct tegra20_i2s *i2s) +{ +	regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, +			   TEGRA20_I2S_CTRL_FIFO2_ENABLE, +			   TEGRA20_I2S_CTRL_FIFO2_ENABLE); +} + +static void tegra20_i2s_stop_capture(struct tegra20_i2s *i2s) +{ +	regmap_update_bits(i2s->regmap, TEGRA20_I2S_CTRL, +			   TEGRA20_I2S_CTRL_FIFO2_ENABLE, 0); +} + +static int tegra20_i2s_trigger(struct snd_pcm_substream *substream, int cmd, +			       struct snd_soc_dai *dai) +{ +	struct tegra20_i2s *i2s = 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_i2s_start_playback(i2s); +		else +			tegra20_i2s_start_capture(i2s); +		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_i2s_stop_playback(i2s); +		else +			tegra20_i2s_stop_capture(i2s); +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int tegra20_i2s_probe(struct snd_soc_dai *dai) +{ +	struct tegra20_i2s *i2s = snd_soc_dai_get_drvdata(dai); + +	dai->capture_dma_data = &i2s->capture_dma_data; +	dai->playback_dma_data = &i2s->playback_dma_data; + +	return 0; +} + +static const struct snd_soc_dai_ops tegra20_i2s_dai_ops = { +	.set_fmt	= tegra20_i2s_set_fmt, +	.hw_params	= tegra20_i2s_hw_params, +	.trigger	= tegra20_i2s_trigger, +}; + +static const struct snd_soc_dai_driver tegra20_i2s_dai_template = { +	.probe = tegra20_i2s_probe, +	.playback = { +		.stream_name = "Playback", +		.channels_min = 2, +		.channels_max = 2, +		.rates = SNDRV_PCM_RATE_8000_96000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +	.capture = { +		.stream_name = "Capture", +		.channels_min = 2, +		.channels_max = 2, +		.rates = SNDRV_PCM_RATE_8000_96000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +	.ops = &tegra20_i2s_dai_ops, +	.symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver tegra20_i2s_component = { +	.name		= DRV_NAME, +}; + +static bool tegra20_i2s_wr_rd_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA20_I2S_CTRL: +	case TEGRA20_I2S_STATUS: +	case TEGRA20_I2S_TIMING: +	case TEGRA20_I2S_FIFO_SCR: +	case TEGRA20_I2S_PCM_CTRL: +	case TEGRA20_I2S_NW_CTRL: +	case TEGRA20_I2S_TDM_CTRL: +	case TEGRA20_I2S_TDM_TX_RX_CTRL: +	case TEGRA20_I2S_FIFO1: +	case TEGRA20_I2S_FIFO2: +		return true; +	default: +		return false; +	} +} + +static bool tegra20_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA20_I2S_STATUS: +	case TEGRA20_I2S_FIFO_SCR: +	case TEGRA20_I2S_FIFO1: +	case TEGRA20_I2S_FIFO2: +		return true; +	default: +		return false; +	} +} + +static bool tegra20_i2s_precious_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA20_I2S_FIFO1: +	case TEGRA20_I2S_FIFO2: +		return true; +	default: +		return false; +	} +} + +static const struct regmap_config tegra20_i2s_regmap_config = { +	.reg_bits = 32, +	.reg_stride = 4, +	.val_bits = 32, +	.max_register = TEGRA20_I2S_FIFO2, +	.writeable_reg = tegra20_i2s_wr_rd_reg, +	.readable_reg = tegra20_i2s_wr_rd_reg, +	.volatile_reg = tegra20_i2s_volatile_reg, +	.precious_reg = tegra20_i2s_precious_reg, +	.cache_type = REGCACHE_FLAT, +}; + +static int tegra20_i2s_platform_probe(struct platform_device *pdev) +{ +	struct tegra20_i2s *i2s; +	struct resource *mem, *memregion; +	void __iomem *regs; +	int ret; + +	i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_i2s), GFP_KERNEL); +	if (!i2s) { +		dev_err(&pdev->dev, "Can't allocate tegra20_i2s\n"); +		ret = -ENOMEM; +		goto err; +	} +	dev_set_drvdata(&pdev->dev, i2s); + +	i2s->dai = tegra20_i2s_dai_template; +	i2s->dai.name = dev_name(&pdev->dev); + +	i2s->clk_i2s = clk_get(&pdev->dev, NULL); +	if (IS_ERR(i2s->clk_i2s)) { +		dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); +		ret = PTR_ERR(i2s->clk_i2s); +		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; +	} + +	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, +					    &tegra20_i2s_regmap_config); +	if (IS_ERR(i2s->regmap)) { +		dev_err(&pdev->dev, "regmap init failed\n"); +		ret = PTR_ERR(i2s->regmap); +		goto err_clk_put; +	} + +	i2s->capture_dma_data.addr = mem->start + TEGRA20_I2S_FIFO2; +	i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	i2s->capture_dma_data.maxburst = 4; + +	i2s->playback_dma_data.addr = mem->start + TEGRA20_I2S_FIFO1; +	i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	i2s->playback_dma_data.maxburst = 4; + +	pm_runtime_enable(&pdev->dev); +	if (!pm_runtime_enabled(&pdev->dev)) { +		ret = tegra20_i2s_runtime_resume(&pdev->dev); +		if (ret) +			goto err_pm_disable; +	} + +	ret = snd_soc_register_component(&pdev->dev, &tegra20_i2s_component, +					 &i2s->dai, 1); +	if (ret) { +		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); +		ret = -ENOMEM; +		goto err_suspend; +	} + +	ret = tegra_pcm_platform_register(&pdev->dev); +	if (ret) { +		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); +		goto err_unregister_component; +	} + +	return 0; + +err_unregister_component: +	snd_soc_unregister_component(&pdev->dev); +err_suspend: +	if (!pm_runtime_status_suspended(&pdev->dev)) +		tegra20_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: +	pm_runtime_disable(&pdev->dev); +err_clk_put: +	clk_put(i2s->clk_i2s); +err: +	return ret; +} + +static int tegra20_i2s_platform_remove(struct platform_device *pdev) +{ +	struct tegra20_i2s *i2s = dev_get_drvdata(&pdev->dev); + +	pm_runtime_disable(&pdev->dev); +	if (!pm_runtime_status_suspended(&pdev->dev)) +		tegra20_i2s_runtime_suspend(&pdev->dev); + +	tegra_pcm_platform_unregister(&pdev->dev); +	snd_soc_unregister_component(&pdev->dev); + +	clk_put(i2s->clk_i2s); + +	return 0; +} + +static const struct of_device_id tegra20_i2s_of_match[] = { +	{ .compatible = "nvidia,tegra20-i2s", }, +	{}, +}; + +static const struct dev_pm_ops tegra20_i2s_pm_ops = { +	SET_RUNTIME_PM_OPS(tegra20_i2s_runtime_suspend, +			   tegra20_i2s_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_i2s_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.of_match_table = tegra20_i2s_of_match, +		.pm = &tegra20_i2s_pm_ops, +	}, +	.probe = tegra20_i2s_platform_probe, +	.remove = tegra20_i2s_platform_remove, +}; +module_platform_driver(tegra20_i2s_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra20 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra20_i2s_of_match); diff --git a/sound/soc/tegra/tegra20_i2s.h b/sound/soc/tegra/tegra20_i2s.h new file mode 100644 index 00000000000..fa6c29cc12b --- /dev/null +++ b/sound/soc/tegra/tegra20_i2s.h @@ -0,0 +1,163 @@ +/* + * tegra20_i2s.h - Definitions for Tegra20 I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * 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 + * + */ + +#ifndef __TEGRA20_I2S_H__ +#define __TEGRA20_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA20_I2S1_BASE and TEGRA20_I2S2_BASE */ + +#define TEGRA20_I2S_CTRL				0x00 +#define TEGRA20_I2S_STATUS				0x04 +#define TEGRA20_I2S_TIMING				0x08 +#define TEGRA20_I2S_FIFO_SCR				0x0c +#define TEGRA20_I2S_PCM_CTRL				0x10 +#define TEGRA20_I2S_NW_CTRL				0x14 +#define TEGRA20_I2S_TDM_CTRL				0x20 +#define TEGRA20_I2S_TDM_TX_RX_CTRL			0x24 +#define TEGRA20_I2S_FIFO1				0x40 +#define TEGRA20_I2S_FIFO2				0x80 + +/* Fields in TEGRA20_I2S_CTRL */ + +#define TEGRA20_I2S_CTRL_FIFO2_TX_ENABLE		(1 << 30) +#define TEGRA20_I2S_CTRL_FIFO1_ENABLE			(1 << 29) +#define TEGRA20_I2S_CTRL_FIFO2_ENABLE			(1 << 28) +#define TEGRA20_I2S_CTRL_FIFO1_RX_ENABLE		(1 << 27) +#define TEGRA20_I2S_CTRL_FIFO_LPBK_ENABLE		(1 << 26) +#define TEGRA20_I2S_CTRL_MASTER_ENABLE			(1 << 25) + +#define TEGRA20_I2S_LRCK_LEFT_LOW				0 +#define TEGRA20_I2S_LRCK_RIGHT_LOW			1 + +#define TEGRA20_I2S_CTRL_LRCK_SHIFT			24 +#define TEGRA20_I2S_CTRL_LRCK_MASK			(1                          << TEGRA20_I2S_CTRL_LRCK_SHIFT) +#define TEGRA20_I2S_CTRL_LRCK_L_LOW			(TEGRA20_I2S_LRCK_LEFT_LOW  << TEGRA20_I2S_CTRL_LRCK_SHIFT) +#define TEGRA20_I2S_CTRL_LRCK_R_LOW			(TEGRA20_I2S_LRCK_RIGHT_LOW << TEGRA20_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA20_I2S_BIT_FORMAT_I2S			0 +#define TEGRA20_I2S_BIT_FORMAT_RJM			1 +#define TEGRA20_I2S_BIT_FORMAT_LJM			2 +#define TEGRA20_I2S_BIT_FORMAT_DSP			3 + +#define TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT		10 +#define TEGRA20_I2S_CTRL_BIT_FORMAT_MASK		(3                          << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_I2S			(TEGRA20_I2S_BIT_FORMAT_I2S << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_RJM			(TEGRA20_I2S_BIT_FORMAT_RJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_LJM			(TEGRA20_I2S_BIT_FORMAT_LJM << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_FORMAT_DSP			(TEGRA20_I2S_BIT_FORMAT_DSP << TEGRA20_I2S_CTRL_BIT_FORMAT_SHIFT) + +#define TEGRA20_I2S_BIT_SIZE_16				0 +#define TEGRA20_I2S_BIT_SIZE_20				1 +#define TEGRA20_I2S_BIT_SIZE_24				2 +#define TEGRA20_I2S_BIT_SIZE_32				3 + +#define TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT			8 +#define TEGRA20_I2S_CTRL_BIT_SIZE_MASK			(3                       << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_16			(TEGRA20_I2S_BIT_SIZE_16 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_20			(TEGRA20_I2S_BIT_SIZE_20 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_24			(TEGRA20_I2S_BIT_SIZE_24 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA20_I2S_CTRL_BIT_SIZE_32			(TEGRA20_I2S_BIT_SIZE_32 << TEGRA20_I2S_CTRL_BIT_SIZE_SHIFT) + +#define TEGRA20_I2S_FIFO_16_LSB				0 +#define TEGRA20_I2S_FIFO_20_LSB				1 +#define TEGRA20_I2S_FIFO_24_LSB				2 +#define TEGRA20_I2S_FIFO_32				3 +#define TEGRA20_I2S_FIFO_PACKED				7 + +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT		4 +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_MASK		(7                       << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_16_LSB		(TEGRA20_I2S_FIFO_16_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_20_LSB		(TEGRA20_I2S_FIFO_20_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_24_LSB		(TEGRA20_I2S_FIFO_24_LSB << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_32			(TEGRA20_I2S_FIFO_32     << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA20_I2S_CTRL_FIFO_FORMAT_PACKED		(TEGRA20_I2S_FIFO_PACKED << TEGRA20_I2S_CTRL_FIFO_FORMAT_SHIFT) + +#define TEGRA20_I2S_CTRL_IE_FIFO1_ERR			(1 << 3) +#define TEGRA20_I2S_CTRL_IE_FIFO2_ERR			(1 << 2) +#define TEGRA20_I2S_CTRL_QE_FIFO1			(1 << 1) +#define TEGRA20_I2S_CTRL_QE_FIFO2			(1 << 0) + +/* Fields in TEGRA20_I2S_STATUS */ + +#define TEGRA20_I2S_STATUS_FIFO1_RDY			(1 << 31) +#define TEGRA20_I2S_STATUS_FIFO2_RDY			(1 << 30) +#define TEGRA20_I2S_STATUS_FIFO1_BSY			(1 << 29) +#define TEGRA20_I2S_STATUS_FIFO2_BSY			(1 << 28) +#define TEGRA20_I2S_STATUS_FIFO1_ERR			(1 << 3) +#define TEGRA20_I2S_STATUS_FIFO2_ERR			(1 << 2) +#define TEGRA20_I2S_STATUS_QS_FIFO1			(1 << 1) +#define TEGRA20_I2S_STATUS_QS_FIFO2			(1 << 0) + +/* Fields in TEGRA20_I2S_TIMING */ + +#define TEGRA20_I2S_TIMING_NON_SYM_ENABLE		(1 << 12) +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT	0 +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US	0x7ff +#define TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK	(TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA20_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA20_I2S_FIFO_SCR */ + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT	24 +#define TEGRA20_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT	16 +#define TEGRA20_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK		0x3f + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_CLR			(1 << 12) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_CLR			(1 << 8) + +#define TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT		0 +#define TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS		1 +#define TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS		2 +#define TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS		3 + +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT	4 +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK		(3 << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT	(TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT     << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS	(TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS   << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS	(TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS  << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS	(TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) + +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT	0 +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK		(3 << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT	(TEGRA20_I2S_FIFO_ATN_LVL_ONE_SLOT     << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS	(TEGRA20_I2S_FIFO_ATN_LVL_FOUR_SLOTS   << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS	(TEGRA20_I2S_FIFO_ATN_LVL_EIGHT_SLOTS  << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS	(TEGRA20_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA20_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) + +struct tegra20_i2s { +	struct snd_soc_dai_driver dai; +	struct clk *clk_i2s; +	struct snd_dmaengine_dai_dma_data capture_dma_data; +	struct snd_dmaengine_dai_dma_data playback_dma_data; +	struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra20_spdif.c b/sound/soc/tegra/tegra20_spdif.c new file mode 100644 index 00000000000..a0ce92400fa --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.c @@ -0,0 +1,402 @@ +/* + * tegra20_spdif.c - Tegra20 SPDIF driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2011-2012 - NVIDIA, 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. + * + * 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 <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.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 <sound/dmaengine_pcm.h> + +#include "tegra20_spdif.h" + +#define DRV_NAME "tegra20-spdif" + +static int tegra20_spdif_runtime_suspend(struct device *dev) +{ +	struct tegra20_spdif *spdif = dev_get_drvdata(dev); + +	clk_disable_unprepare(spdif->clk_spdif_out); + +	return 0; +} + +static int tegra20_spdif_runtime_resume(struct device *dev) +{ +	struct tegra20_spdif *spdif = dev_get_drvdata(dev); +	int ret; + +	ret = clk_prepare_enable(spdif->clk_spdif_out); +	if (ret) { +		dev_err(dev, "clk_enable failed: %d\n", ret); +		return ret; +	} + +	return 0; +} + +static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, +				struct snd_pcm_hw_params *params, +				struct snd_soc_dai *dai) +{ +	struct device *dev = dai->dev; +	struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); +	unsigned int mask = 0, val = 0; +	int ret, spdifclock; + +	mask |= TEGRA20_SPDIF_CTRL_PACK | +		TEGRA20_SPDIF_CTRL_BIT_MODE_MASK; +	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_S16_LE: +		val |= TEGRA20_SPDIF_CTRL_PACK | +		       TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT; +		break; +	default: +		return -EINVAL; +	} + +	regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val); + +	switch (params_rate(params)) { +	case 32000: +		spdifclock = 4096000; +		break; +	case 44100: +		spdifclock = 5644800; +		break; +	case 48000: +		spdifclock = 6144000; +		break; +	case 88200: +		spdifclock = 11289600; +		break; +	case 96000: +		spdifclock = 12288000; +		break; +	case 176400: +		spdifclock = 22579200; +		break; +	case 192000: +		spdifclock = 24576000; +		break; +	default: +		return -EINVAL; +	} + +	ret = clk_set_rate(spdif->clk_spdif_out, spdifclock); +	if (ret) { +		dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret); +		return ret; +	} + +	return 0; +} + +static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif) +{ +	regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, +			   TEGRA20_SPDIF_CTRL_TX_EN, +			   TEGRA20_SPDIF_CTRL_TX_EN); +} + +static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif) +{ +	regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, +			   TEGRA20_SPDIF_CTRL_TX_EN, 0); +} + +static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, +				struct snd_soc_dai *dai) +{ +	struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_START: +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +	case SNDRV_PCM_TRIGGER_RESUME: +		tegra20_spdif_start_playback(spdif); +		break; +	case SNDRV_PCM_TRIGGER_STOP: +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +	case SNDRV_PCM_TRIGGER_SUSPEND: +		tegra20_spdif_stop_playback(spdif); +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int tegra20_spdif_probe(struct snd_soc_dai *dai) +{ +	struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); + +	dai->capture_dma_data = NULL; +	dai->playback_dma_data = &spdif->playback_dma_data; + +	return 0; +} + +static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = { +	.hw_params	= tegra20_spdif_hw_params, +	.trigger	= tegra20_spdif_trigger, +}; + +static struct snd_soc_dai_driver tegra20_spdif_dai = { +	.name = DRV_NAME, +	.probe = tegra20_spdif_probe, +	.playback = { +		.stream_name = "Playback", +		.channels_min = 2, +		.channels_max = 2, +		.rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | +				SNDRV_PCM_RATE_48000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +	.ops = &tegra20_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver tegra20_spdif_component = { +	.name		= DRV_NAME, +}; + +static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA20_SPDIF_CTRL: +	case TEGRA20_SPDIF_STATUS: +	case TEGRA20_SPDIF_STROBE_CTRL: +	case TEGRA20_SPDIF_DATA_FIFO_CSR: +	case TEGRA20_SPDIF_DATA_OUT: +	case TEGRA20_SPDIF_DATA_IN: +	case TEGRA20_SPDIF_CH_STA_RX_A: +	case TEGRA20_SPDIF_CH_STA_RX_B: +	case TEGRA20_SPDIF_CH_STA_RX_C: +	case TEGRA20_SPDIF_CH_STA_RX_D: +	case TEGRA20_SPDIF_CH_STA_RX_E: +	case TEGRA20_SPDIF_CH_STA_RX_F: +	case TEGRA20_SPDIF_CH_STA_TX_A: +	case TEGRA20_SPDIF_CH_STA_TX_B: +	case TEGRA20_SPDIF_CH_STA_TX_C: +	case TEGRA20_SPDIF_CH_STA_TX_D: +	case TEGRA20_SPDIF_CH_STA_TX_E: +	case TEGRA20_SPDIF_CH_STA_TX_F: +	case TEGRA20_SPDIF_USR_STA_RX_A: +	case TEGRA20_SPDIF_USR_DAT_TX_A: +		return true; +	default: +		return false; +	} +} + +static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA20_SPDIF_STATUS: +	case TEGRA20_SPDIF_DATA_FIFO_CSR: +	case TEGRA20_SPDIF_DATA_OUT: +	case TEGRA20_SPDIF_DATA_IN: +	case TEGRA20_SPDIF_CH_STA_RX_A: +	case TEGRA20_SPDIF_CH_STA_RX_B: +	case TEGRA20_SPDIF_CH_STA_RX_C: +	case TEGRA20_SPDIF_CH_STA_RX_D: +	case TEGRA20_SPDIF_CH_STA_RX_E: +	case TEGRA20_SPDIF_CH_STA_RX_F: +	case TEGRA20_SPDIF_USR_STA_RX_A: +	case TEGRA20_SPDIF_USR_DAT_TX_A: +		return true; +	default: +		return false; +	} +} + +static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA20_SPDIF_DATA_OUT: +	case TEGRA20_SPDIF_DATA_IN: +	case TEGRA20_SPDIF_USR_STA_RX_A: +	case TEGRA20_SPDIF_USR_DAT_TX_A: +		return true; +	default: +		return false; +	} +} + +static const struct regmap_config tegra20_spdif_regmap_config = { +	.reg_bits = 32, +	.reg_stride = 4, +	.val_bits = 32, +	.max_register = TEGRA20_SPDIF_USR_DAT_TX_A, +	.writeable_reg = tegra20_spdif_wr_rd_reg, +	.readable_reg = tegra20_spdif_wr_rd_reg, +	.volatile_reg = tegra20_spdif_volatile_reg, +	.precious_reg = tegra20_spdif_precious_reg, +	.cache_type = REGCACHE_FLAT, +}; + +static int tegra20_spdif_platform_probe(struct platform_device *pdev) +{ +	struct tegra20_spdif *spdif; +	struct resource *mem, *memregion, *dmareq; +	void __iomem *regs; +	int ret; + +	spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif), +			     GFP_KERNEL); +	if (!spdif) { +		dev_err(&pdev->dev, "Can't allocate tegra20_spdif\n"); +		ret = -ENOMEM; +		goto err; +	} +	dev_set_drvdata(&pdev->dev, spdif); + +	spdif->clk_spdif_out = clk_get(&pdev->dev, "spdif_out"); +	if (IS_ERR(spdif->clk_spdif_out)) { +		pr_err("Can't retrieve spdif clock\n"); +		ret = PTR_ERR(spdif->clk_spdif_out); +		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; +	} + +	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 = 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; +	} + +	spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, +					    &tegra20_spdif_regmap_config); +	if (IS_ERR(spdif->regmap)) { +		dev_err(&pdev->dev, "regmap init failed\n"); +		ret = PTR_ERR(spdif->regmap); +		goto err_clk_put; +	} + +	spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; +	spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	spdif->playback_dma_data.maxburst = 4; +	spdif->playback_dma_data.slave_id = dmareq->start; + +	pm_runtime_enable(&pdev->dev); +	if (!pm_runtime_enabled(&pdev->dev)) { +		ret = tegra20_spdif_runtime_resume(&pdev->dev); +		if (ret) +			goto err_pm_disable; +	} + +	ret = snd_soc_register_component(&pdev->dev, &tegra20_spdif_component, +				   &tegra20_spdif_dai, 1); +	if (ret) { +		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); +		ret = -ENOMEM; +		goto err_suspend; +	} + +	ret = tegra_pcm_platform_register(&pdev->dev); +	if (ret) { +		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); +		goto err_unregister_component; +	} + +	return 0; + +err_unregister_component: +	snd_soc_unregister_component(&pdev->dev); +err_suspend: +	if (!pm_runtime_status_suspended(&pdev->dev)) +		tegra20_spdif_runtime_suspend(&pdev->dev); +err_pm_disable: +	pm_runtime_disable(&pdev->dev); +err_clk_put: +	clk_put(spdif->clk_spdif_out); +err: +	return ret; +} + +static int tegra20_spdif_platform_remove(struct platform_device *pdev) +{ +	struct tegra20_spdif *spdif = dev_get_drvdata(&pdev->dev); + +	pm_runtime_disable(&pdev->dev); +	if (!pm_runtime_status_suspended(&pdev->dev)) +		tegra20_spdif_runtime_suspend(&pdev->dev); + +	tegra_pcm_platform_unregister(&pdev->dev); +	snd_soc_unregister_component(&pdev->dev); + +	clk_put(spdif->clk_spdif_out); + +	return 0; +} + +static const struct dev_pm_ops tegra20_spdif_pm_ops = { +	SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend, +			   tegra20_spdif_runtime_resume, NULL) +}; + +static struct platform_driver tegra20_spdif_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.pm = &tegra20_spdif_pm_ops, +	}, +	.probe = tegra20_spdif_platform_probe, +	.remove = tegra20_spdif_platform_remove, +}; + +module_platform_driver(tegra20_spdif_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/tegra/tegra20_spdif.h b/sound/soc/tegra/tegra20_spdif.h new file mode 100644 index 00000000000..85a9aefcc28 --- /dev/null +++ b/sound/soc/tegra/tegra20_spdif.h @@ -0,0 +1,470 @@ +/* + * tegra20_spdif.h - Definitions for Tegra20 SPDIF driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2011 - NVIDIA, Inc. + * + * Based on code copyright/by: + * Copyright (c) 2008-2009, NVIDIA Corporation + * + * 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 + * + */ + +#ifndef __TEGRA20_SPDIF_H__ +#define __TEGRA20_SPDIF_H__ + +#include "tegra_pcm.h" + +/* Offsets from TEGRA20_SPDIF_BASE */ + +#define TEGRA20_SPDIF_CTRL					0x0 +#define TEGRA20_SPDIF_STATUS					0x4 +#define TEGRA20_SPDIF_STROBE_CTRL				0x8 +#define TEGRA20_SPDIF_DATA_FIFO_CSR				0x0C +#define TEGRA20_SPDIF_DATA_OUT					0x40 +#define TEGRA20_SPDIF_DATA_IN					0x80 +#define TEGRA20_SPDIF_CH_STA_RX_A				0x100 +#define TEGRA20_SPDIF_CH_STA_RX_B				0x104 +#define TEGRA20_SPDIF_CH_STA_RX_C				0x108 +#define TEGRA20_SPDIF_CH_STA_RX_D				0x10C +#define TEGRA20_SPDIF_CH_STA_RX_E				0x110 +#define TEGRA20_SPDIF_CH_STA_RX_F				0x114 +#define TEGRA20_SPDIF_CH_STA_TX_A				0x140 +#define TEGRA20_SPDIF_CH_STA_TX_B				0x144 +#define TEGRA20_SPDIF_CH_STA_TX_C				0x148 +#define TEGRA20_SPDIF_CH_STA_TX_D				0x14C +#define TEGRA20_SPDIF_CH_STA_TX_E				0x150 +#define TEGRA20_SPDIF_CH_STA_TX_F				0x154 +#define TEGRA20_SPDIF_USR_STA_RX_A				0x180 +#define TEGRA20_SPDIF_USR_DAT_TX_A				0x1C0 + +/* Fields in TEGRA20_SPDIF_CTRL */ + +/* Start capturing from 0=right, 1=left channel */ +#define TEGRA20_SPDIF_CTRL_CAP_LC				(1 << 30) + +/* SPDIF receiver(RX) enable */ +#define TEGRA20_SPDIF_CTRL_RX_EN				(1 << 29) + +/* SPDIF Transmitter(TX) enable */ +#define TEGRA20_SPDIF_CTRL_TX_EN				(1 << 28) + +/* Transmit Channel status */ +#define TEGRA20_SPDIF_CTRL_TC_EN				(1 << 27) + +/* Transmit user Data */ +#define TEGRA20_SPDIF_CTRL_TU_EN				(1 << 26) + +/* Interrupt on transmit error */ +#define TEGRA20_SPDIF_CTRL_IE_TXE				(1 << 25) + +/* Interrupt on receive error */ +#define TEGRA20_SPDIF_CTRL_IE_RXE				(1 << 24) + +/* Interrupt on invalid preamble */ +#define TEGRA20_SPDIF_CTRL_IE_P					(1 << 23) + +/* Interrupt on "B" preamble */ +#define TEGRA20_SPDIF_CTRL_IE_B					(1 << 22) + +/* Interrupt when block of channel status received */ +#define TEGRA20_SPDIF_CTRL_IE_C					(1 << 21) + +/* Interrupt when a valid information unit (IU) is received */ +#define TEGRA20_SPDIF_CTRL_IE_U					(1 << 20) + +/* Interrupt when RX user FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_RU				(1 << 19) + +/* Interrupt when TX user FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_TU				(1 << 18) + +/* Interrupt when RX data FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_RX				(1 << 17) + +/* Interrupt when TX data FIFO attention level is reached */ +#define TEGRA20_SPDIF_CTRL_QE_TX				(1 << 16) + +/* Loopback test mode enable */ +#define TEGRA20_SPDIF_CTRL_LBK_EN				(1 << 15) + +/* + * Pack data mode: + * 0 = Single data (16 bit needs to be  padded to match the + *     interface data bit size). + * 1 = Packeted left/right channel data into a single word. + */ +#define TEGRA20_SPDIF_CTRL_PACK					(1 << 14) + +/* + * 00 = 16bit data + * 01 = 20bit data + * 10 = 24bit data + * 11 = raw data + */ +#define TEGRA20_SPDIF_BIT_MODE_16BIT				0 +#define TEGRA20_SPDIF_BIT_MODE_20BIT				1 +#define TEGRA20_SPDIF_BIT_MODE_24BIT				2 +#define TEGRA20_SPDIF_BIT_MODE_RAW				3 + +#define TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT			12 +#define TEGRA20_SPDIF_CTRL_BIT_MODE_MASK			(3                            << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT			(TEGRA20_SPDIF_BIT_MODE_16BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_20BIT			(TEGRA20_SPDIF_BIT_MODE_20BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_24BIT			(TEGRA20_SPDIF_BIT_MODE_24BIT << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) +#define TEGRA20_SPDIF_CTRL_BIT_MODE_RAW				(TEGRA20_SPDIF_BIT_MODE_RAW   << TEGRA20_SPDIF_CTRL_BIT_MODE_SHIFT) + +/* Fields in TEGRA20_SPDIF_STATUS */ + +/* + * Note: IS_P, IS_B, IS_C, and IS_U are sticky bits. Software must + * write a 1 to the corresponding bit location to clear the status. + */ + +/* + * Receiver(RX) shifter is busy receiving data. + * This bit is asserted when the receiver first locked onto the + * preamble of the data stream after RX_EN is asserted. This bit is + * deasserted when either, + * (a) the end of a frame is reached after RX_EN is deeasserted, or + * (b) the SPDIF data stream becomes inactive. + */ +#define TEGRA20_SPDIF_STATUS_RX_BSY				(1 << 29) + +/* + * Transmitter(TX) shifter is busy transmitting data. + * This bit is asserted when TX_EN is asserted. + * This bit is deasserted when the end of a frame is reached after + * TX_EN is deasserted. + */ +#define TEGRA20_SPDIF_STATUS_TX_BSY				(1 << 28) + +/* + * TX is busy shifting out channel status. + * This bit is asserted when both TX_EN and TC_EN are asserted and + * data from CH_STA_TX_A register is loaded into the internal shifter. + * This bit is deasserted when either, + * (a) the end of a frame is reached after TX_EN is deasserted, or + * (b) CH_STA_TX_F register is loaded into the internal shifter. + */ +#define TEGRA20_SPDIF_STATUS_TC_BSY				(1 << 27) + +/* + * TX User data FIFO busy. + * This bit is asserted when TX_EN and TXU_EN are asserted and + * there's data in the TX user FIFO.  This bit is deassert when either, + * (a) the end of a frame is reached after TX_EN is deasserted, or + * (b) there's no data left in the TX user FIFO. + */ +#define TEGRA20_SPDIF_STATUS_TU_BSY				(1 << 26) + +/* TX FIFO Underrun error status */ +#define TEGRA20_SPDIF_STATUS_TX_ERR				(1 << 25) + +/* RX FIFO Overrun error status */ +#define TEGRA20_SPDIF_STATUS_RX_ERR				(1 << 24) + +/* Preamble status: 0=Preamble OK, 1=bad/missing preamble */ +#define TEGRA20_SPDIF_STATUS_IS_P				(1 << 23) + +/* B-preamble detection status: 0=not detected, 1=B-preamble detected */ +#define TEGRA20_SPDIF_STATUS_IS_B				(1 << 22) + +/* + * RX channel block data receive status: + * 0=entire block not recieved yet. + * 1=received entire block of channel status, + */ +#define TEGRA20_SPDIF_STATUS_IS_C				(1 << 21) + +/* RX User Data Valid flag:  1=valid IU detected, 0 = no IU detected. */ +#define TEGRA20_SPDIF_STATUS_IS_U				(1 << 20) + +/* + * RX User FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_RU				(1 << 19) + +/* + * TX User FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_TU				(1 << 18) + +/* + * RX Data FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_RX				(1 << 17) + +/* + * TX Data FIFO Status: + * 1=attention level reached, 0=attention level not reached. + */ +#define TEGRA20_SPDIF_STATUS_QS_TX				(1 << 16) + +/* Fields in TEGRA20_SPDIF_STROBE_CTRL */ + +/* + * Indicates the approximate number of detected SPDIFIN clocks within a + * bi-phase period. + */ +#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT			16 +#define TEGRA20_SPDIF_STROBE_CTRL_PERIOD_MASK			(0xff << TEGRA20_SPDIF_STROBE_CTRL_PERIOD_SHIFT) + +/* Data strobe mode: 0=Auto-locked 1=Manual locked */ +#define TEGRA20_SPDIF_STROBE_CTRL_STROBE			(1 << 15) + +/* + * Manual data strobe time within the bi-phase clock period (in terms of + * the number of over-sampling clocks). + */ +#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT		8 +#define TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_MASK		(0x1f << TEGRA20_SPDIF_STROBE_CTRL_DATA_STROBES_SHIFT) + +/* + * Manual SPDIFIN bi-phase clock period (in terms of the number of + * over-sampling clocks). + */ +#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT		0 +#define TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_MASK		(0x3f << TEGRA20_SPDIF_STROBE_CTRL_CLOCK_PERIOD_SHIFT) + +/* Fields in SPDIF_DATA_FIFO_CSR */ + +/* Clear Receiver User FIFO (RX USR.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_CLR			(1 << 31) + +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT			0 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS			1 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS		2 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS			3 + +/* RU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT		29 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_MASK		\ +		(0x3                                      << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU1_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT    << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU2_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS   << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU3_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_RU4_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS  << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_ATN_LVL_SHIFT) + +/* Number of RX USR.FIFO levels with valid data. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT		24 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_MASK		(0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RU_FULL_COUNT_SHIFT) + +/* Clear Transmitter User FIFO (TX USR.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_CLR			(1 << 23) + +/* TU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT		21 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_MASK		\ +		(0x3                                      << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU1_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_ONE_SLOT    << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU2_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_TWO_SLOTS   << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU3_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_THREE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_TU4_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_U_FOUR_SLOTS  << TEGRA20_SPDIF_DATA_FIFO_CSR_TU_ATN_LVL_SHIFT) + +/* Number of TX USR.FIFO levels that could be filled. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT	16 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_MASK		(0x1f << SPDIF_DATA_FIFO_CSR_TU_EMPTY_COUNT_SHIFT) + +/* Clear Receiver Data FIFO (RX DATA.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_CLR			(1 << 15) + +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT			0 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS			1 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS		2 +#define TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS		3 + +/* RU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT		13 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_MASK		\ +		(0x3                                       << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU1_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT     << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU4_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS   << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU8_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS  << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_RU12_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_ATN_LVL_SHIFT) + +/* Number of RX DATA.FIFO levels with valid data. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT		8 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_MASK		(0x1f << TEGRA20_SPDIF_DATA_FIFO_CSR_RX_FULL_COUNT_SHIFT) + +/* Clear Transmitter Data FIFO (TX DATA.FIFO) */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_CLR			(1 << 7) + +/* TU FIFO attention level */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT		5 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK		\ +		(0x3                                       << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU1_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_ONE_SLOT     << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_FOUR_SLOTS   << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU8_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_EIGHT_SLOTS  << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU12_WORD_FULL	\ +		(TEGRA20_SPDIF_FIFO_ATN_LVL_D_TWELVE_SLOTS << TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_SHIFT) + +/* Number of TX DATA.FIFO levels that could be filled. */ +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT	0 +#define TEGRA20_SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_MASK		(0x1f << SPDIF_DATA_FIFO_CSR_TX_EMPTY_COUNT_SHIFT) + +/* Fields in TEGRA20_SPDIF_DATA_OUT */ + +/* + * This register has 5 different formats: + * 16-bit        (BIT_MODE=00, PACK=0) + * 20-bit        (BIT_MODE=01, PACK=0) + * 24-bit        (BIT_MODE=10, PACK=0) + * raw           (BIT_MODE=11, PACK=0) + * 16-bit packed (BIT_MODE=00, PACK=1) + */ + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT			0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_MASK			(0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT			0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_20_MASK			(0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_20_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT			0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_24_MASK			(0xffffff << TEGRA20_SPDIF_DATA_OUT_DATA_24_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_P			(1 << 31) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_C			(1 << 30) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_U			(1 << 29) +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_V			(1 << 28) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT		8 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_MASK		(0xfffff << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_DATA_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT		4 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_MASK		(0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_AUX_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT		0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_MASK		(0xf << TEGRA20_SPDIF_DATA_OUT_DATA_RAW_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT	16 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_MASK	(0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_RIGHT_SHIFT) + +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT	0 +#define TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_MASK		(0xffff << TEGRA20_SPDIF_DATA_OUT_DATA_16_PACKED_LEFT_SHIFT) + +/* Fields in TEGRA20_SPDIF_DATA_IN */ + +/* + * This register has 5 different formats: + * 16-bit        (BIT_MODE=00, PACK=0) + * 20-bit        (BIT_MODE=01, PACK=0) + * 24-bit        (BIT_MODE=10, PACK=0) + * raw           (BIT_MODE=11, PACK=0) + * 16-bit packed (BIT_MODE=00, PACK=1) + * + * Bits 31:24 are common to all modes except 16-bit packed + */ + +#define TEGRA20_SPDIF_DATA_IN_DATA_P				(1 << 31) +#define TEGRA20_SPDIF_DATA_IN_DATA_C				(1 << 30) +#define TEGRA20_SPDIF_DATA_IN_DATA_U				(1 << 29) +#define TEGRA20_SPDIF_DATA_IN_DATA_V				(1 << 28) + +#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT		24 +#define TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_MASK		(0xf << TEGRA20_SPDIF_DATA_IN_DATA_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT			0 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_MASK			(0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT			0 +#define TEGRA20_SPDIF_DATA_IN_DATA_20_MASK			(0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_20_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT			0 +#define TEGRA20_SPDIF_DATA_IN_DATA_24_MASK			(0xffffff << TEGRA20_SPDIF_DATA_IN_DATA_24_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT		8 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_MASK		(0xfffff << TEGRA20_SPDIF_DATA_IN_DATA_RAW_DATA_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT		4 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_MASK			(0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_AUX_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT		0 +#define TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_MASK		(0xf << TEGRA20_SPDIF_DATA_IN_DATA_RAW_PREAMBLE_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT	16 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_MASK		(0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_RIGHT_SHIFT) + +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT		0 +#define TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_MASK		(0xffff << TEGRA20_SPDIF_DATA_IN_DATA_16_PACKED_LEFT_SHIFT) + +/* Fields in TEGRA20_SPDIF_CH_STA_RX_A */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_B */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_C */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_D */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_E */ +/* Fields in TEGRA20_SPDIF_CH_STA_RX_F */ + +/* + * The 6-word receive channel data page buffer holds a block (192 frames) of + * channel status information. The order of receive is from LSB to MSB + * bit, and from CH_STA_RX_A to CH_STA_RX_F then back to CH_STA_RX_A. + */ + +/* Fields in TEGRA20_SPDIF_CH_STA_TX_A */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_B */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_C */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_D */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_E */ +/* Fields in TEGRA20_SPDIF_CH_STA_TX_F */ + +/* + * The 6-word transmit channel data page buffer holds a block (192 frames) of + * channel status information. The order of transmission is from LSB to MSB + * bit, and from CH_STA_TX_A to CH_STA_TX_F then back to CH_STA_TX_A. + */ + +/* Fields in TEGRA20_SPDIF_USR_STA_RX_A */ + +/* + * This 4-word deep FIFO receives user FIFO field information. The order of + * receive is from LSB to MSB bit. + */ + +/* Fields in TEGRA20_SPDIF_USR_DAT_TX_A */ + +/* + * This 4-word deep FIFO transmits user FIFO field information. The order of + * transmission is from LSB to MSB bit. + */ + +struct tegra20_spdif { +	struct clk *clk_spdif_out; +	struct snd_dmaengine_dai_dma_data capture_dma_data; +	struct snd_dmaengine_dai_dma_data playback_dma_data; +	struct regmap *regmap; +}; + +#endif diff --git a/sound/soc/tegra/tegra30_ahub.c b/sound/soc/tegra/tegra30_ahub.c new file mode 100644 index 00000000000..0db68f49f4d --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.c @@ -0,0 +1,801 @@ +/* + * tegra30_ahub.c - Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include "tegra30_ahub.h" + +#define DRV_NAME "tegra30-ahub" + +static struct tegra30_ahub *ahub; + +static inline void tegra30_apbif_write(u32 reg, u32 val) +{ +	regmap_write(ahub->regmap_apbif, reg, val); +} + +static inline u32 tegra30_apbif_read(u32 reg) +{ +	u32 val; +	regmap_read(ahub->regmap_apbif, reg, &val); +	return val; +} + +static inline void tegra30_audio_write(u32 reg, u32 val) +{ +	regmap_write(ahub->regmap_ahub, reg, val); +} + +static int tegra30_ahub_runtime_suspend(struct device *dev) +{ +	regcache_cache_only(ahub->regmap_apbif, true); +	regcache_cache_only(ahub->regmap_ahub, true); + +	clk_disable_unprepare(ahub->clk_apbif); +	clk_disable_unprepare(ahub->clk_d_audio); + +	return 0; +} + +/* + * clk_apbif isn't required for an I2S<->I2S configuration where no PCM data + * is read from or sent to memory. However, that's not something the rest of + * the driver supports right now, so we'll just treat the two clocks as one + * for now. + * + * These functions should not be a plain ref-count. Instead, each active stream + * contributes some requirement to the minimum clock rate, so starting or + * stopping streams should dynamically adjust the clock as required.  However, + * this is not yet implemented. + */ +static int tegra30_ahub_runtime_resume(struct device *dev) +{ +	int ret; + +	ret = clk_prepare_enable(ahub->clk_d_audio); +	if (ret) { +		dev_err(dev, "clk_enable d_audio failed: %d\n", ret); +		return ret; +	} +	ret = clk_prepare_enable(ahub->clk_apbif); +	if (ret) { +		dev_err(dev, "clk_enable apbif failed: %d\n", ret); +		clk_disable(ahub->clk_d_audio); +		return ret; +	} + +	regcache_cache_only(ahub->regmap_apbif, false); +	regcache_cache_only(ahub->regmap_ahub, false); + +	return 0; +} + +int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif, +				  char *dmachan, int dmachan_len, +				  dma_addr_t *fiforeg) +{ +	int channel; +	u32 reg, val; +	struct tegra30_ahub_cif_conf cif_conf; + +	channel = find_first_zero_bit(ahub->rx_usage, +				      TEGRA30_AHUB_CHANNEL_CTRL_COUNT); +	if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT) +		return -EBUSY; + +	__set_bit(channel, ahub->rx_usage); + +	*rxcif = TEGRA30_AHUB_RXCIF_APBIF_RX0 + channel; +	snprintf(dmachan, dmachan_len, "rx%d", channel); +	*fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_RXFIFO + +		   (channel * TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE); + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_CHANNEL_CTRL + +	      (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); +	val = tegra30_apbif_read(reg); +	val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK | +		 TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK); +	val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) | +	       TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN | +	       TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16; +	tegra30_apbif_write(reg, val); + +	cif_conf.threshold = 0; +	cif_conf.audio_channels = 2; +	cif_conf.client_channels = 2; +	cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; +	cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; +	cif_conf.expand = 0; +	cif_conf.stereo_conv = 0; +	cif_conf.replicate = 0; +	cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; +	cif_conf.truncate = 0; +	cif_conf.mono_conv = 0; + +	reg = TEGRA30_AHUB_CIF_RX_CTRL + +	      (channel * TEGRA30_AHUB_CIF_RX_CTRL_STRIDE); +	ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_rx_fifo); + +int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ +	int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; +	int reg, val; + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_CHANNEL_CTRL + +	      (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); +	val = tegra30_apbif_read(reg); +	val |= TEGRA30_AHUB_CHANNEL_CTRL_RX_EN; +	tegra30_apbif_write(reg, val); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_enable_rx_fifo); + +int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ +	int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; +	int reg, val; + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_CHANNEL_CTRL + +	      (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); +	val = tegra30_apbif_read(reg); +	val &= ~TEGRA30_AHUB_CHANNEL_CTRL_RX_EN; +	tegra30_apbif_write(reg, val); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_disable_rx_fifo); + +int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif) +{ +	int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; + +	__clear_bit(channel, ahub->rx_usage); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_free_rx_fifo); + +int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif, +				  char *dmachan, int dmachan_len, +				  dma_addr_t *fiforeg) +{ +	int channel; +	u32 reg, val; +	struct tegra30_ahub_cif_conf cif_conf; + +	channel = find_first_zero_bit(ahub->tx_usage, +				      TEGRA30_AHUB_CHANNEL_CTRL_COUNT); +	if (channel >= TEGRA30_AHUB_CHANNEL_CTRL_COUNT) +		return -EBUSY; + +	__set_bit(channel, ahub->tx_usage); + +	*txcif = TEGRA30_AHUB_TXCIF_APBIF_TX0 + channel; +	snprintf(dmachan, dmachan_len, "tx%d", channel); +	*fiforeg = ahub->apbif_addr + TEGRA30_AHUB_CHANNEL_TXFIFO + +		   (channel * TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE); + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_CHANNEL_CTRL + +	      (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); +	val = tegra30_apbif_read(reg); +	val &= ~(TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK | +		 TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK); +	val |= (7 << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) | +	       TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN | +	       TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16; +	tegra30_apbif_write(reg, val); + +	cif_conf.threshold = 0; +	cif_conf.audio_channels = 2; +	cif_conf.client_channels = 2; +	cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; +	cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; +	cif_conf.expand = 0; +	cif_conf.stereo_conv = 0; +	cif_conf.replicate = 0; +	cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; +	cif_conf.truncate = 0; +	cif_conf.mono_conv = 0; + +	reg = TEGRA30_AHUB_CIF_TX_CTRL + +	      (channel * TEGRA30_AHUB_CIF_TX_CTRL_STRIDE); +	ahub->soc_data->set_audio_cif(ahub->regmap_apbif, reg, &cif_conf); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_allocate_tx_fifo); + +int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif) +{ +	int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; +	int reg, val; + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_CHANNEL_CTRL + +	      (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); +	val = tegra30_apbif_read(reg); +	val |= TEGRA30_AHUB_CHANNEL_CTRL_TX_EN; +	tegra30_apbif_write(reg, val); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_enable_tx_fifo); + +int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif) +{ +	int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; +	int reg, val; + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_CHANNEL_CTRL + +	      (channel * TEGRA30_AHUB_CHANNEL_CTRL_STRIDE); +	val = tegra30_apbif_read(reg); +	val &= ~TEGRA30_AHUB_CHANNEL_CTRL_TX_EN; +	tegra30_apbif_write(reg, val); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_disable_tx_fifo); + +int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif) +{ +	int channel = txcif - TEGRA30_AHUB_TXCIF_APBIF_TX0; + +	__clear_bit(channel, ahub->tx_usage); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_free_tx_fifo); + +int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif, +				   enum tegra30_ahub_txcif txcif) +{ +	int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; +	int reg; + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_AUDIO_RX + +	      (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); +	tegra30_audio_write(reg, 1 << txcif); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_set_rx_cif_source); + +int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif) +{ +	int channel = rxcif - TEGRA30_AHUB_RXCIF_APBIF_RX0; +	int reg; + +	pm_runtime_get_sync(ahub->dev); + +	reg = TEGRA30_AHUB_AUDIO_RX + +	      (channel * TEGRA30_AHUB_AUDIO_RX_STRIDE); +	tegra30_audio_write(reg, 0); + +	pm_runtime_put(ahub->dev); + +	return 0; +} +EXPORT_SYMBOL_GPL(tegra30_ahub_unset_rx_cif_source); + +#define MOD_LIST_MASK_TEGRA30	BIT(0) +#define MOD_LIST_MASK_TEGRA114	BIT(1) +#define MOD_LIST_MASK_TEGRA124	BIT(2) + +#define MOD_LIST_MASK_TEGRA30_OR_LATER \ +		(MOD_LIST_MASK_TEGRA30 | MOD_LIST_MASK_TEGRA114 | \ +			MOD_LIST_MASK_TEGRA124) +#define MOD_LIST_MASK_TEGRA114_OR_LATER \ +		(MOD_LIST_MASK_TEGRA114 | MOD_LIST_MASK_TEGRA124) + +static const struct { +	const char *rst_name; +	u32 mod_list_mask; +} configlink_mods[] = { +	{ "i2s0", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "i2s1", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "i2s2", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "i2s3", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "i2s4", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "dam0", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "dam1", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "dam2", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "spdif", MOD_LIST_MASK_TEGRA30_OR_LATER }, +	{ "amx", MOD_LIST_MASK_TEGRA114_OR_LATER }, +	{ "adx", MOD_LIST_MASK_TEGRA114_OR_LATER }, +	{ "amx1", MOD_LIST_MASK_TEGRA124 }, +	{ "adx1", MOD_LIST_MASK_TEGRA124 }, +	{ "afc0", MOD_LIST_MASK_TEGRA124 }, +	{ "afc1", MOD_LIST_MASK_TEGRA124 }, +	{ "afc2", MOD_LIST_MASK_TEGRA124 }, +	{ "afc3", MOD_LIST_MASK_TEGRA124 }, +	{ "afc4", MOD_LIST_MASK_TEGRA124 }, +	{ "afc5", MOD_LIST_MASK_TEGRA124 }, +}; + +#define LAST_REG(name) \ +	(TEGRA30_AHUB_##name + \ +	 (TEGRA30_AHUB_##name##_STRIDE * TEGRA30_AHUB_##name##_COUNT) - 4) + +#define REG_IN_ARRAY(reg, name) \ +	((reg >= TEGRA30_AHUB_##name) && \ +	 (reg <= LAST_REG(name) && \ +	 (!((reg - TEGRA30_AHUB_##name) % TEGRA30_AHUB_##name##_STRIDE)))) + +static bool tegra30_ahub_apbif_wr_rd_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA30_AHUB_CONFIG_LINK_CTRL: +	case TEGRA30_AHUB_MISC_CTRL: +	case TEGRA30_AHUB_APBDMA_LIVE_STATUS: +	case TEGRA30_AHUB_I2S_LIVE_STATUS: +	case TEGRA30_AHUB_SPDIF_LIVE_STATUS: +	case TEGRA30_AHUB_I2S_INT_MASK: +	case TEGRA30_AHUB_DAM_INT_MASK: +	case TEGRA30_AHUB_SPDIF_INT_MASK: +	case TEGRA30_AHUB_APBIF_INT_MASK: +	case TEGRA30_AHUB_I2S_INT_STATUS: +	case TEGRA30_AHUB_DAM_INT_STATUS: +	case TEGRA30_AHUB_SPDIF_INT_STATUS: +	case TEGRA30_AHUB_APBIF_INT_STATUS: +	case TEGRA30_AHUB_I2S_INT_SOURCE: +	case TEGRA30_AHUB_DAM_INT_SOURCE: +	case TEGRA30_AHUB_SPDIF_INT_SOURCE: +	case TEGRA30_AHUB_APBIF_INT_SOURCE: +	case TEGRA30_AHUB_I2S_INT_SET: +	case TEGRA30_AHUB_DAM_INT_SET: +	case TEGRA30_AHUB_SPDIF_INT_SET: +	case TEGRA30_AHUB_APBIF_INT_SET: +		return true; +	default: +		break; +	} + +	if (REG_IN_ARRAY(reg, CHANNEL_CTRL) || +	    REG_IN_ARRAY(reg, CHANNEL_CLEAR) || +	    REG_IN_ARRAY(reg, CHANNEL_STATUS) || +	    REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || +	    REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || +	    REG_IN_ARRAY(reg, CIF_TX_CTRL) || +	    REG_IN_ARRAY(reg, CIF_RX_CTRL) || +	    REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) +		return true; + +	return false; +} + +static bool tegra30_ahub_apbif_volatile_reg(struct device *dev, +					    unsigned int reg) +{ +	switch (reg) { +	case TEGRA30_AHUB_CONFIG_LINK_CTRL: +	case TEGRA30_AHUB_MISC_CTRL: +	case TEGRA30_AHUB_APBDMA_LIVE_STATUS: +	case TEGRA30_AHUB_I2S_LIVE_STATUS: +	case TEGRA30_AHUB_SPDIF_LIVE_STATUS: +	case TEGRA30_AHUB_I2S_INT_STATUS: +	case TEGRA30_AHUB_DAM_INT_STATUS: +	case TEGRA30_AHUB_SPDIF_INT_STATUS: +	case TEGRA30_AHUB_APBIF_INT_STATUS: +	case TEGRA30_AHUB_I2S_INT_SET: +	case TEGRA30_AHUB_DAM_INT_SET: +	case TEGRA30_AHUB_SPDIF_INT_SET: +	case TEGRA30_AHUB_APBIF_INT_SET: +		return true; +	default: +		break; +	} + +	if (REG_IN_ARRAY(reg, CHANNEL_CLEAR) || +	    REG_IN_ARRAY(reg, CHANNEL_STATUS) || +	    REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || +	    REG_IN_ARRAY(reg, CHANNEL_RXFIFO) || +	    REG_IN_ARRAY(reg, DAM_LIVE_STATUS)) +		return true; + +	return false; +} + +static bool tegra30_ahub_apbif_precious_reg(struct device *dev, +					    unsigned int reg) +{ +	if (REG_IN_ARRAY(reg, CHANNEL_TXFIFO) || +	    REG_IN_ARRAY(reg, CHANNEL_RXFIFO)) +		return true; + +	return false; +} + +static const struct regmap_config tegra30_ahub_apbif_regmap_config = { +	.name = "apbif", +	.reg_bits = 32, +	.val_bits = 32, +	.reg_stride = 4, +	.max_register = TEGRA30_AHUB_APBIF_INT_SET, +	.writeable_reg = tegra30_ahub_apbif_wr_rd_reg, +	.readable_reg = tegra30_ahub_apbif_wr_rd_reg, +	.volatile_reg = tegra30_ahub_apbif_volatile_reg, +	.precious_reg = tegra30_ahub_apbif_precious_reg, +	.cache_type = REGCACHE_FLAT, +}; + +static bool tegra30_ahub_ahub_wr_rd_reg(struct device *dev, unsigned int reg) +{ +	if (REG_IN_ARRAY(reg, AUDIO_RX)) +		return true; + +	return false; +} + +static const struct regmap_config tegra30_ahub_ahub_regmap_config = { +	.name = "ahub", +	.reg_bits = 32, +	.val_bits = 32, +	.reg_stride = 4, +	.max_register = LAST_REG(AUDIO_RX), +	.writeable_reg = tegra30_ahub_ahub_wr_rd_reg, +	.readable_reg = tegra30_ahub_ahub_wr_rd_reg, +	.cache_type = REGCACHE_FLAT, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra30 = { +	.mod_list_mask = MOD_LIST_MASK_TEGRA30, +	.set_audio_cif = tegra30_ahub_set_cif, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra114 = { +	.mod_list_mask = MOD_LIST_MASK_TEGRA114, +	.set_audio_cif = tegra30_ahub_set_cif, +}; + +static struct tegra30_ahub_soc_data soc_data_tegra124 = { +	.mod_list_mask = MOD_LIST_MASK_TEGRA124, +	.set_audio_cif = tegra124_ahub_set_cif, +}; + +static const struct of_device_id tegra30_ahub_of_match[] = { +	{ .compatible = "nvidia,tegra124-ahub", .data = &soc_data_tegra124 }, +	{ .compatible = "nvidia,tegra114-ahub", .data = &soc_data_tegra114 }, +	{ .compatible = "nvidia,tegra30-ahub",  .data = &soc_data_tegra30 }, +	{}, +}; + +static int tegra30_ahub_probe(struct platform_device *pdev) +{ +	const struct of_device_id *match; +	const struct tegra30_ahub_soc_data *soc_data; +	struct reset_control *rst; +	int i; +	struct resource *res0, *res1, *region; +	void __iomem *regs_apbif, *regs_ahub; +	int ret = 0; + +	if (ahub) +		return -ENODEV; + +	match = of_match_device(tegra30_ahub_of_match, &pdev->dev); +	if (!match) +		return -EINVAL; +	soc_data = match->data; + +	/* +	 * The AHUB hosts a register bus: the "configlink". For this to +	 * operate correctly, all devices on this bus must be out of reset. +	 * Ensure that here. +	 */ +	for (i = 0; i < ARRAY_SIZE(configlink_mods); i++) { +		if (!(configlink_mods[i].mod_list_mask & +					soc_data->mod_list_mask)) +			continue; + +		rst = reset_control_get(&pdev->dev, +					configlink_mods[i].rst_name); +		if (IS_ERR(rst)) { +			dev_err(&pdev->dev, "Can't get reset %s\n", +				configlink_mods[i].rst_name); +			ret = PTR_ERR(rst); +			goto err; +		} + +		ret = reset_control_deassert(rst); +		reset_control_put(rst); +		if (ret) +			goto err; +	} + +	ahub = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_ahub), +			    GFP_KERNEL); +	if (!ahub) { +		dev_err(&pdev->dev, "Can't allocate tegra30_ahub\n"); +		ret = -ENOMEM; +		goto err; +	} +	dev_set_drvdata(&pdev->dev, ahub); + +	ahub->soc_data = soc_data; +	ahub->dev = &pdev->dev; + +	ahub->clk_d_audio = clk_get(&pdev->dev, "d_audio"); +	if (IS_ERR(ahub->clk_d_audio)) { +		dev_err(&pdev->dev, "Can't retrieve ahub d_audio clock\n"); +		ret = PTR_ERR(ahub->clk_d_audio); +		goto err; +	} + +	ahub->clk_apbif = clk_get(&pdev->dev, "apbif"); +	if (IS_ERR(ahub->clk_apbif)) { +		dev_err(&pdev->dev, "Can't retrieve ahub apbif clock\n"); +		ret = PTR_ERR(ahub->clk_apbif); +		goto err_clk_put_d_audio; +	} + +	res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0); +	if (!res0) { +		dev_err(&pdev->dev, "No apbif memory resource\n"); +		ret = -ENODEV; +		goto err_clk_put_apbif; +	} + +	region = devm_request_mem_region(&pdev->dev, res0->start, +					 resource_size(res0), DRV_NAME); +	if (!region) { +		dev_err(&pdev->dev, "request region apbif failed\n"); +		ret = -EBUSY; +		goto err_clk_put_apbif; +	} +	ahub->apbif_addr = res0->start; + +	regs_apbif = devm_ioremap(&pdev->dev, res0->start, +				  resource_size(res0)); +	if (!regs_apbif) { +		dev_err(&pdev->dev, "ioremap apbif failed\n"); +		ret = -ENOMEM; +		goto err_clk_put_apbif; +	} + +	ahub->regmap_apbif = devm_regmap_init_mmio(&pdev->dev, regs_apbif, +					&tegra30_ahub_apbif_regmap_config); +	if (IS_ERR(ahub->regmap_apbif)) { +		dev_err(&pdev->dev, "apbif regmap init failed\n"); +		ret = PTR_ERR(ahub->regmap_apbif); +		goto err_clk_put_apbif; +	} +	regcache_cache_only(ahub->regmap_apbif, true); + +	res1 = platform_get_resource(pdev, IORESOURCE_MEM, 1); +	if (!res1) { +		dev_err(&pdev->dev, "No ahub memory resource\n"); +		ret = -ENODEV; +		goto err_clk_put_apbif; +	} + +	region = devm_request_mem_region(&pdev->dev, res1->start, +					 resource_size(res1), DRV_NAME); +	if (!region) { +		dev_err(&pdev->dev, "request region ahub failed\n"); +		ret = -EBUSY; +		goto err_clk_put_apbif; +	} + +	regs_ahub = devm_ioremap(&pdev->dev, res1->start, +				 resource_size(res1)); +	if (!regs_ahub) { +		dev_err(&pdev->dev, "ioremap ahub failed\n"); +		ret = -ENOMEM; +		goto err_clk_put_apbif; +	} + +	ahub->regmap_ahub = devm_regmap_init_mmio(&pdev->dev, regs_ahub, +					&tegra30_ahub_ahub_regmap_config); +	if (IS_ERR(ahub->regmap_ahub)) { +		dev_err(&pdev->dev, "ahub regmap init failed\n"); +		ret = PTR_ERR(ahub->regmap_ahub); +		goto err_clk_put_apbif; +	} +	regcache_cache_only(ahub->regmap_ahub, true); + +	pm_runtime_enable(&pdev->dev); +	if (!pm_runtime_enabled(&pdev->dev)) { +		ret = tegra30_ahub_runtime_resume(&pdev->dev); +		if (ret) +			goto err_pm_disable; +	} + +	of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); + +	return 0; + +err_pm_disable: +	pm_runtime_disable(&pdev->dev); +err_clk_put_apbif: +	clk_put(ahub->clk_apbif); +err_clk_put_d_audio: +	clk_put(ahub->clk_d_audio); +	ahub = NULL; +err: +	return ret; +} + +static int tegra30_ahub_remove(struct platform_device *pdev) +{ +	if (!ahub) +		return -ENODEV; + +	pm_runtime_disable(&pdev->dev); +	if (!pm_runtime_status_suspended(&pdev->dev)) +		tegra30_ahub_runtime_suspend(&pdev->dev); + +	clk_put(ahub->clk_apbif); +	clk_put(ahub->clk_d_audio); + +	ahub = NULL; + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra30_ahub_suspend(struct device *dev) +{ +	regcache_mark_dirty(ahub->regmap_ahub); +	regcache_mark_dirty(ahub->regmap_apbif); + +	return 0; +} + +static int tegra30_ahub_resume(struct device *dev) +{ +	int ret; + +	ret = pm_runtime_get_sync(dev); +	if (ret < 0) +		return ret; +	ret = regcache_sync(ahub->regmap_ahub); +	ret |= regcache_sync(ahub->regmap_apbif); +	pm_runtime_put(dev); + +	return ret; +} +#endif + +static const struct dev_pm_ops tegra30_ahub_pm_ops = { +	SET_RUNTIME_PM_OPS(tegra30_ahub_runtime_suspend, +			   tegra30_ahub_runtime_resume, NULL) +	SET_SYSTEM_SLEEP_PM_OPS(tegra30_ahub_suspend, tegra30_ahub_resume) +}; + +static struct platform_driver tegra30_ahub_driver = { +	.probe = tegra30_ahub_probe, +	.remove = tegra30_ahub_remove, +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.of_match_table = tegra30_ahub_of_match, +		.pm = &tegra30_ahub_pm_ops, +	}, +}; +module_platform_driver(tegra30_ahub_driver); + +void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg, +			  struct tegra30_ahub_cif_conf *conf) +{ +	unsigned int value; + +	value = (conf->threshold << +			TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | +		((conf->audio_channels - 1) << +			TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | +		((conf->client_channels - 1) << +			TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | +		(conf->audio_bits << +			TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | +		(conf->client_bits << +			TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | +		(conf->expand << +			TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | +		(conf->stereo_conv << +			TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | +		(conf->replicate << +			TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | +		(conf->direction << +			TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | +		(conf->truncate << +			TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | +		(conf->mono_conv << +			TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + +	regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra30_ahub_set_cif); + +void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg, +			   struct tegra30_ahub_cif_conf *conf) +{ +	unsigned int value; + +	value = (conf->threshold << +			TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) | +		((conf->audio_channels - 1) << +			TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) | +		((conf->client_channels - 1) << +			TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) | +		(conf->audio_bits << +			TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) | +		(conf->client_bits << +			TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) | +		(conf->expand << +			TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) | +		(conf->stereo_conv << +			TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) | +		(conf->replicate << +			TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT) | +		(conf->direction << +			TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) | +		(conf->truncate << +			TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) | +		(conf->mono_conv << +			TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT); + +	regmap_write(regmap, reg, value); +} +EXPORT_SYMBOL_GPL(tegra124_ahub_set_cif); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 AHUB driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra30_ahub_of_match); diff --git a/sound/soc/tegra/tegra30_ahub.h b/sound/soc/tegra/tegra30_ahub.h new file mode 100644 index 00000000000..fd7ba75ed81 --- /dev/null +++ b/sound/soc/tegra/tegra30_ahub.h @@ -0,0 +1,534 @@ +/* + * tegra30_ahub.h - Definitions for Tegra30 AHUB driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TEGRA30_AHUB_H__ +#define __TEGRA30_AHUB_H__ + +/* Fields in *_CIF_RX/TX_CTRL; used by AHUB FIFOs, and all other audio modules */ + +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT	28 +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US	0xf +#define TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK	(TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA30_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) + +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT	24 +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US	0x3f +#define TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK	(TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_MASK_US << TEGRA124_AUDIOCIF_CTRL_FIFO_THRESHOLD_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT	24 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US	7 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK	(TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT	20 +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US	0xf +#define TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK	(TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_MASK_US << TEGRA124_AUDIOCIF_CTRL_AUDIO_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT	16 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US	7 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK	(TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) + +/* Channel count minus 1 */ +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT	16 +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US	0xf +#define TEGRA124_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK	(TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_MASK_US << TEGRA30_AUDIOCIF_CTRL_CLIENT_CHANNELS_SHIFT) + +#define TEGRA30_AUDIOCIF_BITS_4				0 +#define TEGRA30_AUDIOCIF_BITS_8				1 +#define TEGRA30_AUDIOCIF_BITS_12			2 +#define TEGRA30_AUDIOCIF_BITS_16			3 +#define TEGRA30_AUDIOCIF_BITS_20			4 +#define TEGRA30_AUDIOCIF_BITS_24			5 +#define TEGRA30_AUDIOCIF_BITS_28			6 +#define TEGRA30_AUDIOCIF_BITS_32			7 + +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT		12 +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_MASK		(7                        << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_4		(TEGRA30_AUDIOCIF_BITS_4  << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_8		(TEGRA30_AUDIOCIF_BITS_8  << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_12		(TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_16		(TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_20		(TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_24		(TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_28		(TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_32		(TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_AUDIO_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT		8 +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_MASK		(7                        << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_4		(TEGRA30_AUDIOCIF_BITS_4  << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_8		(TEGRA30_AUDIOCIF_BITS_8  << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_12		(TEGRA30_AUDIOCIF_BITS_12 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_16		(TEGRA30_AUDIOCIF_BITS_16 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_20		(TEGRA30_AUDIOCIF_BITS_20 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_24		(TEGRA30_AUDIOCIF_BITS_24 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_28		(TEGRA30_AUDIOCIF_BITS_28 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_32		(TEGRA30_AUDIOCIF_BITS_32 << TEGRA30_AUDIOCIF_CTRL_CLIENT_BITS_SHIFT) + +#define TEGRA30_AUDIOCIF_EXPAND_ZERO			0 +#define TEGRA30_AUDIOCIF_EXPAND_ONE			1 +#define TEGRA30_AUDIOCIF_EXPAND_LFSR			2 + +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT		6 +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_MASK		(3                            << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ZERO		(TEGRA30_AUDIOCIF_EXPAND_ZERO << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_ONE		(TEGRA30_AUDIOCIF_EXPAND_ONE  << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_EXPAND_LFSR		(TEGRA30_AUDIOCIF_EXPAND_LFSR << TEGRA30_AUDIOCIF_CTRL_EXPAND_SHIFT) + +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH0		0 +#define TEGRA30_AUDIOCIF_STEREO_CONV_CH1		1 +#define TEGRA30_AUDIOCIF_STEREO_CONV_AVG		2 + +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT		4 +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_MASK		(3                                << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH0		(TEGRA30_AUDIOCIF_STEREO_CONV_CH0 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_CH1		(TEGRA30_AUDIOCIF_STEREO_CONV_CH1 << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_AVG		(TEGRA30_AUDIOCIF_STEREO_CONV_AVG << TEGRA30_AUDIOCIF_CTRL_STEREO_CONV_SHIFT) + +#define TEGRA30_AUDIOCIF_CTRL_REPLICATE_SHIFT		3 + +#define TEGRA30_AUDIOCIF_DIRECTION_TX			0 +#define TEGRA30_AUDIOCIF_DIRECTION_RX			1 + +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT		2 +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_MASK		(1                             << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_TX		(TEGRA30_AUDIOCIF_DIRECTION_TX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_DIRECTION_RX		(TEGRA30_AUDIOCIF_DIRECTION_RX << TEGRA30_AUDIOCIF_CTRL_DIRECTION_SHIFT) + +#define TEGRA30_AUDIOCIF_TRUNCATE_ROUND			0 +#define TEGRA30_AUDIOCIF_TRUNCATE_CHOP			1 + +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT		1 +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_MASK		(1                               << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_ROUND		(TEGRA30_AUDIOCIF_TRUNCATE_ROUND << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_TRUNCATE_CHOP		(TEGRA30_AUDIOCIF_TRUNCATE_CHOP  << TEGRA30_AUDIOCIF_CTRL_TRUNCATE_SHIFT) + +#define TEGRA30_AUDIOCIF_MONO_CONV_ZERO			0 +#define TEGRA30_AUDIOCIF_MONO_CONV_COPY			1 + +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT		0 +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_MASK		(1                               << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_ZERO		(TEGRA30_AUDIOCIF_MONO_CONV_ZERO << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) +#define TEGRA30_AUDIOCIF_CTRL_MONO_CONV_COPY		(TEGRA30_AUDIOCIF_MONO_CONV_COPY << TEGRA30_AUDIOCIF_CTRL_MONO_CONV_SHIFT) + +/* Registers within TEGRA30_AUDIO_CLUSTER_BASE */ + +/* TEGRA30_AHUB_CHANNEL_CTRL */ + +#define TEGRA30_AHUB_CHANNEL_CTRL			0x0 +#define TEGRA30_AHUB_CHANNEL_CTRL_STRIDE		0x20 +#define TEGRA30_AHUB_CHANNEL_CTRL_COUNT			4 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_EN			(1 << 31) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_EN			(1 << 30) +#define TEGRA30_AHUB_CHANNEL_CTRL_LOOPBACK		(1 << 29) + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT	16 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US	0xff +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK	(TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_THRESHOLD_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT	8 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US	0xff +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK	(TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_THRESHOLD_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_EN		(1 << 6) + +#define TEGRA30_PACK_8_4				2 +#define TEGRA30_PACK_16					3 + +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT		4 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US	3 +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK		(TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_8_4		(TEGRA30_PACK_8_4                          << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_16		(TEGRA30_PACK_16                           << TEGRA30_AHUB_CHANNEL_CTRL_TX_PACK_SHIFT) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_EN		(1 << 2) + +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT		0 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US	3 +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK		(TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_MASK_US << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_8_4		(TEGRA30_PACK_8_4                          << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) +#define TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_16		(TEGRA30_PACK_16                           << TEGRA30_AHUB_CHANNEL_CTRL_RX_PACK_SHIFT) + +/* TEGRA30_AHUB_CHANNEL_CLEAR */ + +#define TEGRA30_AHUB_CHANNEL_CLEAR			0x4 +#define TEGRA30_AHUB_CHANNEL_CLEAR_STRIDE		0x20 +#define TEGRA30_AHUB_CHANNEL_CLEAR_COUNT		4 +#define TEGRA30_AHUB_CHANNEL_CLEAR_TX_SOFT_RESET	(1 << 31) +#define TEGRA30_AHUB_CHANNEL_CLEAR_RX_SOFT_RESET	(1 << 30) + +/* TEGRA30_AHUB_CHANNEL_STATUS */ + +#define TEGRA30_AHUB_CHANNEL_STATUS			0x8 +#define TEGRA30_AHUB_CHANNEL_STATUS_STRIDE		0x20 +#define TEGRA30_AHUB_CHANNEL_STATUS_COUNT		4 +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT	24 +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US	0xff +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK	(TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_TX_FREE_SHIFT) +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT	16 +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US	0xff +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK	(TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_MASK_US << TEGRA30_AHUB_CHANNEL_STATUS_RX_FREE_SHIFT) +#define TEGRA30_AHUB_CHANNEL_STATUS_TX_TRIG		(1 << 1) +#define TEGRA30_AHUB_CHANNEL_STATUS_RX_TRIG		(1 << 0) + +/* TEGRA30_AHUB_CHANNEL_TXFIFO */ + +#define TEGRA30_AHUB_CHANNEL_TXFIFO			0xc +#define TEGRA30_AHUB_CHANNEL_TXFIFO_STRIDE		0x20 +#define TEGRA30_AHUB_CHANNEL_TXFIFO_COUNT		4 + +/* TEGRA30_AHUB_CHANNEL_RXFIFO */ + +#define TEGRA30_AHUB_CHANNEL_RXFIFO			0x10 +#define TEGRA30_AHUB_CHANNEL_RXFIFO_STRIDE		0x20 +#define TEGRA30_AHUB_CHANNEL_RXFIFO_COUNT		4 + +/* TEGRA30_AHUB_CIF_TX_CTRL */ + +#define TEGRA30_AHUB_CIF_TX_CTRL			0x14 +#define TEGRA30_AHUB_CIF_TX_CTRL_STRIDE			0x20 +#define TEGRA30_AHUB_CIF_TX_CTRL_COUNT			4 +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */ + +/* TEGRA30_AHUB_CIF_RX_CTRL */ + +#define TEGRA30_AHUB_CIF_RX_CTRL			0x18 +#define TEGRA30_AHUB_CIF_RX_CTRL_STRIDE			0x20 +#define TEGRA30_AHUB_CIF_RX_CTRL_COUNT			4 +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* */ + +/* TEGRA30_AHUB_CONFIG_LINK_CTRL */ + +#define TEGRA30_AHUB_CONFIG_LINK_CTRL					0x80 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT	28 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US	0xf +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK		(TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_MASTER_FIFO_FULL_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT			16 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US		0xfff +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK			(TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_TIMEOUT_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT			4 +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US			0xfff +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK			(TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_MASK_US << TEGRA30_AHUB_CONFIG_LINK_CTRL_IDLE_CNT_SHIFT) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CG_EN				(1 << 2) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_CLEAR_TIMEOUT_CNTR		(1 << 1) +#define TEGRA30_AHUB_CONFIG_LINK_CTRL_SOFT_RESET			(1 << 0) + +/* TEGRA30_AHUB_MISC_CTRL */ + +#define TEGRA30_AHUB_MISC_CTRL				0x84 +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_ACTIVE		(1 << 31) +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_CG_EN		(1 << 8) +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT	0 +#define TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_MASK	(0x1f << TEGRA30_AHUB_MISC_CTRL_AUDIO_OBS_SEL_SHIFT) + +/* TEGRA30_AHUB_APBDMA_LIVE_STATUS */ + +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS				0x88 +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_FULL	(1 << 31) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_FULL	(1 << 30) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_FULL	(1 << 29) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_FULL	(1 << 28) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_FULL	(1 << 27) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_FULL	(1 << 26) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_FULL	(1 << 25) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_FULL	(1 << 24) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_CIF_FIFO_EMPTY	(1 << 23) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_CIF_FIFO_EMPTY	(1 << 22) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_CIF_FIFO_EMPTY	(1 << 21) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_CIF_FIFO_EMPTY	(1 << 20) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_CIF_FIFO_EMPTY	(1 << 19) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_CIF_FIFO_EMPTY	(1 << 18) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_CIF_FIFO_EMPTY	(1 << 17) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_CIF_FIFO_EMPTY	(1 << 16) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_FULL	(1 << 15) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_FULL	(1 << 14) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_FULL	(1 << 13) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_FULL	(1 << 12) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_FULL	(1 << 11) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_FULL	(1 << 10) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_FULL	(1 << 9) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_FULL	(1 << 8) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_RX_DMA_FIFO_EMPTY	(1 << 7) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH3_TX_DMA_FIFO_EMPTY	(1 << 6) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_RX_DMA_FIFO_EMPTY	(1 << 5) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH2_TX_DMA_FIFO_EMPTY	(1 << 4) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_RX_DMA_FIFO_EMPTY	(1 << 3) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH1_TX_DMA_FIFO_EMPTY	(1 << 2) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_RX_DMA_FIFO_EMPTY	(1 << 1) +#define TEGRA30_AHUB_APBDMA_LIVE_STATUS_CH0_TX_DMA_FIFO_EMPTY	(1 << 0) + +/* TEGRA30_AHUB_I2S_LIVE_STATUS */ + +#define TEGRA30_AHUB_I2S_LIVE_STATUS				0x8c +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_FULL		(1 << 29) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_FULL		(1 << 28) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_FULL		(1 << 27) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_FULL		(1 << 26) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_FULL		(1 << 25) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_FULL		(1 << 24) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_FULL		(1 << 23) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_FULL		(1 << 22) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_FULL		(1 << 21) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_FULL		(1 << 20) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_ENABLED	(1 << 19) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_ENABLED	(1 << 18) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_ENABLED	(1 << 17) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_ENABLED	(1 << 16) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_ENABLED	(1 << 15) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_ENABLED	(1 << 14) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_ENABLED	(1 << 13) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_ENABLED	(1 << 12) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_ENABLED	(1 << 11) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_ENABLED	(1 << 10) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_RX_FIFO_EMPTY		(1 << 9) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S4_TX_FIFO_EMPTY		(1 << 8) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_RX_FIFO_EMPTY		(1 << 7) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S3_TX_FIFO_EMPTY		(1 << 6) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_RX_FIFO_EMPTY		(1 << 5) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S2_TX_FIFO_EMPTY		(1 << 4) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_RX_FIFO_EMPTY		(1 << 3) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S1_TX_FIFO_EMPTY		(1 << 2) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_RX_FIFO_EMPTY		(1 << 1) +#define TEGRA30_AHUB_I2S_LIVE_STATUS_I2S0_TX_FIFO_EMPTY		(1 << 0) + +/* TEGRA30_AHUB_DAM0_LIVE_STATUS */ + +#define TEGRA30_AHUB_DAM_LIVE_STATUS				0x90 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_STRIDE			0x8 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_COUNT			3 +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TX_ENABLED			(1 << 26) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1_ENABLED		(1 << 25) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0_ENABLED		(1 << 24) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_FULL		(1 << 15) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_FULL		(1 << 9) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_FULL		(1 << 8) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_TXFIFO_EMPTY		(1 << 7) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX1FIFO_EMPTY		(1 << 1) +#define TEGRA30_AHUB_DAM_LIVE_STATUS_RX0FIFO_EMPTY		(1 << 0) + +/* TEGRA30_AHUB_SPDIF_LIVE_STATUS */ + +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS				0xa8 +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TX_ENABLED		(1 << 11) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RX_ENABLED		(1 << 10) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TX_ENABLED		(1 << 9) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RX_ENABLED		(1 << 8) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_FULL		(1 << 7) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_FULL		(1 << 6) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_FULL		(1 << 5) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_FULL		(1 << 4) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_TXFIFO_EMPTY	(1 << 3) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_USER_RXFIFO_EMPTY	(1 << 2) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_TXFIFO_EMPTY	(1 << 1) +#define TEGRA30_AHUB_SPDIF_LIVE_STATUS_DATA_RXFIFO_EMPTY	(1 << 0) + +/* TEGRA30_AHUB_I2S_INT_MASK */ + +#define TEGRA30_AHUB_I2S_INT_MASK				0xb0 + +/* TEGRA30_AHUB_DAM_INT_MASK */ + +#define TEGRA30_AHUB_DAM_INT_MASK				0xb4 + +/* TEGRA30_AHUB_SPDIF_INT_MASK */ + +#define TEGRA30_AHUB_SPDIF_INT_MASK				0xbc + +/* TEGRA30_AHUB_APBIF_INT_MASK */ + +#define TEGRA30_AHUB_APBIF_INT_MASK				0xc0 + +/* TEGRA30_AHUB_I2S_INT_STATUS */ + +#define TEGRA30_AHUB_I2S_INT_STATUS				0xc8 + +/* TEGRA30_AHUB_DAM_INT_STATUS */ + +#define TEGRA30_AHUB_DAM_INT_STATUS				0xcc + +/* TEGRA30_AHUB_SPDIF_INT_STATUS */ + +#define TEGRA30_AHUB_SPDIF_INT_STATUS				0xd4 + +/* TEGRA30_AHUB_APBIF_INT_STATUS */ + +#define TEGRA30_AHUB_APBIF_INT_STATUS				0xd8 + +/* TEGRA30_AHUB_I2S_INT_SOURCE */ + +#define TEGRA30_AHUB_I2S_INT_SOURCE				0xe0 + +/* TEGRA30_AHUB_DAM_INT_SOURCE */ + +#define TEGRA30_AHUB_DAM_INT_SOURCE				0xe4 + +/* TEGRA30_AHUB_SPDIF_INT_SOURCE */ + +#define TEGRA30_AHUB_SPDIF_INT_SOURCE				0xec + +/* TEGRA30_AHUB_APBIF_INT_SOURCE */ + +#define TEGRA30_AHUB_APBIF_INT_SOURCE				0xf0 + +/* TEGRA30_AHUB_I2S_INT_SET */ + +#define TEGRA30_AHUB_I2S_INT_SET				0xf8 + +/* TEGRA30_AHUB_DAM_INT_SET */ + +#define TEGRA30_AHUB_DAM_INT_SET				0xfc + +/* TEGRA30_AHUB_SPDIF_INT_SET */ + +#define TEGRA30_AHUB_SPDIF_INT_SET				0x100 + +/* TEGRA30_AHUB_APBIF_INT_SET */ + +#define TEGRA30_AHUB_APBIF_INT_SET				0x104 + +/* Registers within TEGRA30_AHUB_BASE */ + +#define TEGRA30_AHUB_AUDIO_RX					0x0 +#define TEGRA30_AHUB_AUDIO_RX_STRIDE				0x4 +#define TEGRA30_AHUB_AUDIO_RX_COUNT				17 +/* This register repeats once for each entry in enum tegra30_ahub_rxcif */ +/* The fields in this register are 1 bit per entry in tegra30_ahub_txcif */ + +/* + * Terminology: + * AHUB: Audio Hub; a cross-bar switch between the audio devices: DMA FIFOs, + *       I2S controllers, SPDIF controllers, and DAMs. + * XBAR: The core cross-bar component of the AHUB. + * CIF:  Client Interface; the HW module connecting an audio device to the + *       XBAR. + * DAM:  Digital Audio Mixer: A HW module that mixes multiple audio streams, + *       possibly including sample-rate conversion. + * + * Each TX CIF transmits data into the XBAR. Each RX CIF can receive audio + * transmitted by a particular TX CIF. + * + * This driver is currently very simplistic; many HW features are not + * exposed; DAMs are not supported, only 16-bit stereo audio is supported, + * etc. + */ + +enum tegra30_ahub_txcif { +	TEGRA30_AHUB_TXCIF_APBIF_TX0, +	TEGRA30_AHUB_TXCIF_APBIF_TX1, +	TEGRA30_AHUB_TXCIF_APBIF_TX2, +	TEGRA30_AHUB_TXCIF_APBIF_TX3, +	TEGRA30_AHUB_TXCIF_I2S0_TX0, +	TEGRA30_AHUB_TXCIF_I2S1_TX0, +	TEGRA30_AHUB_TXCIF_I2S2_TX0, +	TEGRA30_AHUB_TXCIF_I2S3_TX0, +	TEGRA30_AHUB_TXCIF_I2S4_TX0, +	TEGRA30_AHUB_TXCIF_DAM0_TX0, +	TEGRA30_AHUB_TXCIF_DAM1_TX0, +	TEGRA30_AHUB_TXCIF_DAM2_TX0, +	TEGRA30_AHUB_TXCIF_SPDIF_TX0, +	TEGRA30_AHUB_TXCIF_SPDIF_TX1, +}; + +enum tegra30_ahub_rxcif { +	TEGRA30_AHUB_RXCIF_APBIF_RX0, +	TEGRA30_AHUB_RXCIF_APBIF_RX1, +	TEGRA30_AHUB_RXcIF_APBIF_RX2, +	TEGRA30_AHUB_RXCIF_APBIF_RX3, +	TEGRA30_AHUB_RXCIF_I2S0_RX0, +	TEGRA30_AHUB_RXCIF_I2S1_RX0, +	TEGRA30_AHUB_RXCIF_I2S2_RX0, +	TEGRA30_AHUB_RXCIF_I2S3_RX0, +	TEGRA30_AHUB_RXCIF_I2S4_RX0, +	TEGRA30_AHUB_RXCIF_DAM0_RX0, +	TEGRA30_AHUB_RXCIF_DAM0_RX1, +	TEGRA30_AHUB_RXCIF_DAM1_RX0, +	TEGRA30_AHUB_RXCIF_DAM2_RX1, +	TEGRA30_AHUB_RXCIF_DAM3_RX0, +	TEGRA30_AHUB_RXCIF_DAM3_RX1, +	TEGRA30_AHUB_RXCIF_SPDIF_RX0, +	TEGRA30_AHUB_RXCIF_SPDIF_RX1, +}; + +extern int tegra30_ahub_allocate_rx_fifo(enum tegra30_ahub_rxcif *rxcif, +					 char *dmachan, int dmachan_len, +					 dma_addr_t *fiforeg); +extern int tegra30_ahub_enable_rx_fifo(enum tegra30_ahub_rxcif rxcif); +extern int tegra30_ahub_disable_rx_fifo(enum tegra30_ahub_rxcif rxcif); +extern int tegra30_ahub_free_rx_fifo(enum tegra30_ahub_rxcif rxcif); + +extern int tegra30_ahub_allocate_tx_fifo(enum tegra30_ahub_txcif *txcif, +					 char *dmachan, int dmachan_len, +					 dma_addr_t *fiforeg); +extern int tegra30_ahub_enable_tx_fifo(enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_disable_tx_fifo(enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_free_tx_fifo(enum tegra30_ahub_txcif txcif); + +extern int tegra30_ahub_set_rx_cif_source(enum tegra30_ahub_rxcif rxcif, +					  enum tegra30_ahub_txcif txcif); +extern int tegra30_ahub_unset_rx_cif_source(enum tegra30_ahub_rxcif rxcif); + +struct tegra30_ahub_cif_conf { +	unsigned int threshold; +	unsigned int audio_channels; +	unsigned int client_channels; +	unsigned int audio_bits; +	unsigned int client_bits; +	unsigned int expand; +	unsigned int stereo_conv; +	unsigned int replicate; +	unsigned int direction; +	unsigned int truncate; +	unsigned int mono_conv; +}; + +void tegra30_ahub_set_cif(struct regmap *regmap, unsigned int reg, +			  struct tegra30_ahub_cif_conf *conf); +void tegra124_ahub_set_cif(struct regmap *regmap, unsigned int reg, +			   struct tegra30_ahub_cif_conf *conf); + +struct tegra30_ahub_soc_data { +	u32 mod_list_mask; +	void (*set_audio_cif)(struct regmap *regmap, +			      unsigned int reg, +			      struct tegra30_ahub_cif_conf *conf); +	/* +	 * FIXME: There are many more differences in HW, such as: +	 * - More APBIF channels. +	 * - Extra separate chunks of register address space to represent +	 *   the extra APBIF channels. +	 * - More units connected to the AHUB, so that tegra30_ahub_[rt]xcif +	 *   need expansion, coupled with there being more defined bits in +	 *   the AHUB routing registers. +	 * However, the driver doesn't support those new features yet, so we +	 * don't represent them here yet. +	 */ +}; + +struct tegra30_ahub { +	const struct tegra30_ahub_soc_data *soc_data; +	struct device *dev; +	struct clk *clk_d_audio; +	struct clk *clk_apbif; +	resource_size_t apbif_addr; +	struct regmap *regmap_apbif; +	struct regmap *regmap_ahub; +	DECLARE_BITMAP(rx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT); +	DECLARE_BITMAP(tx_usage, TEGRA30_AHUB_CHANNEL_CTRL_COUNT); +}; + +#endif diff --git a/sound/soc/tegra/tegra30_i2s.c b/sound/soc/tegra/tegra30_i2s.c new file mode 100644 index 00000000000..f146c41dd3e --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.c @@ -0,0 +1,601 @@ +/* + * tegra30_i2s.c - Tegra30 I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (c) 2010-2012, NVIDIA CORPORATION.  All rights reserved. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.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 <sound/dmaengine_pcm.h> + +#include "tegra30_ahub.h" +#include "tegra30_i2s.h" + +#define DRV_NAME "tegra30-i2s" + +static int tegra30_i2s_runtime_suspend(struct device *dev) +{ +	struct tegra30_i2s *i2s = dev_get_drvdata(dev); + +	regcache_cache_only(i2s->regmap, true); + +	clk_disable_unprepare(i2s->clk_i2s); + +	return 0; +} + +static int tegra30_i2s_runtime_resume(struct device *dev) +{ +	struct tegra30_i2s *i2s = dev_get_drvdata(dev); +	int ret; + +	ret = clk_prepare_enable(i2s->clk_i2s); +	if (ret) { +		dev_err(dev, "clk_enable failed: %d\n", ret); +		return ret; +	} + +	regcache_cache_only(i2s->regmap, false); + +	return 0; +} + +static int tegra30_i2s_set_fmt(struct snd_soc_dai *dai, +				unsigned int fmt) +{ +	struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); +	unsigned int mask = 0, val = 0; + +	switch (fmt & SND_SOC_DAIFMT_INV_MASK) { +	case SND_SOC_DAIFMT_NB_NF: +		break; +	default: +		return -EINVAL; +	} + +	mask |= TEGRA30_I2S_CTRL_MASTER_ENABLE; +	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { +	case SND_SOC_DAIFMT_CBS_CFS: +		val |= TEGRA30_I2S_CTRL_MASTER_ENABLE; +		break; +	case SND_SOC_DAIFMT_CBM_CFM: +		break; +	default: +		return -EINVAL; +	} + +	mask |= TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK | +		TEGRA30_I2S_CTRL_LRCK_MASK; +	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { +	case SND_SOC_DAIFMT_DSP_A: +		val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; +		val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; +		break; +	case SND_SOC_DAIFMT_DSP_B: +		val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC; +		val |= TEGRA30_I2S_CTRL_LRCK_R_LOW; +		break; +	case SND_SOC_DAIFMT_I2S: +		val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; +		val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; +		break; +	case SND_SOC_DAIFMT_RIGHT_J: +		val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; +		val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; +		break; +	case SND_SOC_DAIFMT_LEFT_J: +		val |= TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK; +		val |= TEGRA30_I2S_CTRL_LRCK_L_LOW; +		break; +	default: +		return -EINVAL; +	} + +	pm_runtime_get_sync(dai->dev); +	regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); +	pm_runtime_put(dai->dev); + +	return 0; +} + +static int tegra30_i2s_hw_params(struct snd_pcm_substream *substream, +				 struct snd_pcm_hw_params *params, +				 struct snd_soc_dai *dai) +{ +	struct device *dev = dai->dev; +	struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); +	unsigned int mask, val, reg; +	int ret, sample_size, srate, i2sclock, bitcnt; +	struct tegra30_ahub_cif_conf cif_conf; + +	if (params_channels(params) != 2) +		return -EINVAL; + +	mask = TEGRA30_I2S_CTRL_BIT_SIZE_MASK; +	switch (params_format(params)) { +	case SNDRV_PCM_FORMAT_S16_LE: +		val = TEGRA30_I2S_CTRL_BIT_SIZE_16; +		sample_size = 16; +		break; +	default: +		return -EINVAL; +	} + +	regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, mask, val); + +	srate = params_rate(params); + +	/* Final "* 2" required by Tegra hardware */ +	i2sclock = srate * params_channels(params) * sample_size * 2; + +	bitcnt = (i2sclock / (2 * srate)) - 1; +	if (bitcnt < 0 || bitcnt > TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) +		return -EINVAL; + +	ret = clk_set_rate(i2s->clk_i2s, i2sclock); +	if (ret) { +		dev_err(dev, "Can't set I2S clock rate: %d\n", ret); +		return ret; +	} + +	val = bitcnt << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + +	if (i2sclock % (2 * srate)) +		val |= TEGRA30_I2S_TIMING_NON_SYM_ENABLE; + +	regmap_write(i2s->regmap, TEGRA30_I2S_TIMING, val); + +	cif_conf.threshold = 0; +	cif_conf.audio_channels = 2; +	cif_conf.client_channels = 2; +	cif_conf.audio_bits = TEGRA30_AUDIOCIF_BITS_16; +	cif_conf.client_bits = TEGRA30_AUDIOCIF_BITS_16; +	cif_conf.expand = 0; +	cif_conf.stereo_conv = 0; +	cif_conf.replicate = 0; +	cif_conf.truncate = 0; +	cif_conf.mono_conv = 0; + +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { +		cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_RX; +		reg = TEGRA30_I2S_CIF_RX_CTRL; +	} else { +		cif_conf.direction = TEGRA30_AUDIOCIF_DIRECTION_TX; +		reg = TEGRA30_I2S_CIF_TX_CTRL; +	} + +	i2s->soc_data->set_audio_cif(i2s->regmap, reg, &cif_conf); + +	val = (1 << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) | +	      (1 << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT); +	regmap_write(i2s->regmap, TEGRA30_I2S_OFFSET, val); + +	return 0; +} + +static void tegra30_i2s_start_playback(struct tegra30_i2s *i2s) +{ +	tegra30_ahub_enable_tx_fifo(i2s->playback_fifo_cif); +	regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, +			   TEGRA30_I2S_CTRL_XFER_EN_TX, +			   TEGRA30_I2S_CTRL_XFER_EN_TX); +} + +static void tegra30_i2s_stop_playback(struct tegra30_i2s *i2s) +{ +	tegra30_ahub_disable_tx_fifo(i2s->playback_fifo_cif); +	regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, +			   TEGRA30_I2S_CTRL_XFER_EN_TX, 0); +} + +static void tegra30_i2s_start_capture(struct tegra30_i2s *i2s) +{ +	tegra30_ahub_enable_rx_fifo(i2s->capture_fifo_cif); +	regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, +			   TEGRA30_I2S_CTRL_XFER_EN_RX, +			   TEGRA30_I2S_CTRL_XFER_EN_RX); +} + +static void tegra30_i2s_stop_capture(struct tegra30_i2s *i2s) +{ +	tegra30_ahub_disable_rx_fifo(i2s->capture_fifo_cif); +	regmap_update_bits(i2s->regmap, TEGRA30_I2S_CTRL, +			   TEGRA30_I2S_CTRL_XFER_EN_RX, 0); +} + +static int tegra30_i2s_trigger(struct snd_pcm_substream *substream, int cmd, +				struct snd_soc_dai *dai) +{ +	struct tegra30_i2s *i2s = 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) +			tegra30_i2s_start_playback(i2s); +		else +			tegra30_i2s_start_capture(i2s); +		break; +	case SNDRV_PCM_TRIGGER_STOP: +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +	case SNDRV_PCM_TRIGGER_SUSPEND: +		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) +			tegra30_i2s_stop_playback(i2s); +		else +			tegra30_i2s_stop_capture(i2s); +		break; +	default: +		return -EINVAL; +	} + +	return 0; +} + +static int tegra30_i2s_probe(struct snd_soc_dai *dai) +{ +	struct tegra30_i2s *i2s = snd_soc_dai_get_drvdata(dai); + +	dai->capture_dma_data = &i2s->capture_dma_data; +	dai->playback_dma_data = &i2s->playback_dma_data; + +	return 0; +} + +static struct snd_soc_dai_ops tegra30_i2s_dai_ops = { +	.set_fmt	= tegra30_i2s_set_fmt, +	.hw_params	= tegra30_i2s_hw_params, +	.trigger	= tegra30_i2s_trigger, +}; + +static const struct snd_soc_dai_driver tegra30_i2s_dai_template = { +	.probe = tegra30_i2s_probe, +	.playback = { +		.stream_name = "Playback", +		.channels_min = 2, +		.channels_max = 2, +		.rates = SNDRV_PCM_RATE_8000_96000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +	.capture = { +		.stream_name = "Capture", +		.channels_min = 2, +		.channels_max = 2, +		.rates = SNDRV_PCM_RATE_8000_96000, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +	}, +	.ops = &tegra30_i2s_dai_ops, +	.symmetric_rates = 1, +}; + +static const struct snd_soc_component_driver tegra30_i2s_component = { +	.name		= DRV_NAME, +}; + +static bool tegra30_i2s_wr_rd_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA30_I2S_CTRL: +	case TEGRA30_I2S_TIMING: +	case TEGRA30_I2S_OFFSET: +	case TEGRA30_I2S_CH_CTRL: +	case TEGRA30_I2S_SLOT_CTRL: +	case TEGRA30_I2S_CIF_RX_CTRL: +	case TEGRA30_I2S_CIF_TX_CTRL: +	case TEGRA30_I2S_FLOWCTL: +	case TEGRA30_I2S_TX_STEP: +	case TEGRA30_I2S_FLOW_STATUS: +	case TEGRA30_I2S_FLOW_TOTAL: +	case TEGRA30_I2S_FLOW_OVER: +	case TEGRA30_I2S_FLOW_UNDER: +	case TEGRA30_I2S_LCOEF_1_4_0: +	case TEGRA30_I2S_LCOEF_1_4_1: +	case TEGRA30_I2S_LCOEF_1_4_2: +	case TEGRA30_I2S_LCOEF_1_4_3: +	case TEGRA30_I2S_LCOEF_1_4_4: +	case TEGRA30_I2S_LCOEF_1_4_5: +	case TEGRA30_I2S_LCOEF_2_4_0: +	case TEGRA30_I2S_LCOEF_2_4_1: +	case TEGRA30_I2S_LCOEF_2_4_2: +		return true; +	default: +		return false; +	} +} + +static bool tegra30_i2s_volatile_reg(struct device *dev, unsigned int reg) +{ +	switch (reg) { +	case TEGRA30_I2S_FLOW_STATUS: +	case TEGRA30_I2S_FLOW_TOTAL: +	case TEGRA30_I2S_FLOW_OVER: +	case TEGRA30_I2S_FLOW_UNDER: +		return true; +	default: +		return false; +	} +} + +static const struct regmap_config tegra30_i2s_regmap_config = { +	.reg_bits = 32, +	.reg_stride = 4, +	.val_bits = 32, +	.max_register = TEGRA30_I2S_LCOEF_2_4_2, +	.writeable_reg = tegra30_i2s_wr_rd_reg, +	.readable_reg = tegra30_i2s_wr_rd_reg, +	.volatile_reg = tegra30_i2s_volatile_reg, +	.cache_type = REGCACHE_FLAT, +}; + +static const struct tegra30_i2s_soc_data tegra30_i2s_config = { +	.set_audio_cif = tegra30_ahub_set_cif, +}; + +static const struct tegra30_i2s_soc_data tegra124_i2s_config = { +	.set_audio_cif = tegra124_ahub_set_cif, +}; + +static const struct of_device_id tegra30_i2s_of_match[] = { +	{ .compatible = "nvidia,tegra124-i2s", .data = &tegra124_i2s_config }, +	{ .compatible = "nvidia,tegra30-i2s", .data = &tegra30_i2s_config }, +	{}, +}; + +static int tegra30_i2s_platform_probe(struct platform_device *pdev) +{ +	struct tegra30_i2s *i2s; +	const struct of_device_id *match; +	u32 cif_ids[2]; +	struct resource *mem, *memregion; +	void __iomem *regs; +	int ret; + +	i2s = devm_kzalloc(&pdev->dev, sizeof(struct tegra30_i2s), GFP_KERNEL); +	if (!i2s) { +		dev_err(&pdev->dev, "Can't allocate tegra30_i2s\n"); +		ret = -ENOMEM; +		goto err; +	} +	dev_set_drvdata(&pdev->dev, i2s); + +	match = of_match_device(tegra30_i2s_of_match, &pdev->dev); +	if (!match) { +		dev_err(&pdev->dev, "Error: No device match found\n"); +		ret = -ENODEV; +		goto err; +	} +	i2s->soc_data = (struct tegra30_i2s_soc_data *)match->data; + +	i2s->dai = tegra30_i2s_dai_template; +	i2s->dai.name = dev_name(&pdev->dev); + +	ret = of_property_read_u32_array(pdev->dev.of_node, +					 "nvidia,ahub-cif-ids", cif_ids, +					 ARRAY_SIZE(cif_ids)); +	if (ret < 0) +		goto err; + +	i2s->playback_i2s_cif = cif_ids[0]; +	i2s->capture_i2s_cif = cif_ids[1]; + +	i2s->clk_i2s = clk_get(&pdev->dev, NULL); +	if (IS_ERR(i2s->clk_i2s)) { +		dev_err(&pdev->dev, "Can't retrieve i2s clock\n"); +		ret = PTR_ERR(i2s->clk_i2s); +		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; +	} + +	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs, +					    &tegra30_i2s_regmap_config); +	if (IS_ERR(i2s->regmap)) { +		dev_err(&pdev->dev, "regmap init failed\n"); +		ret = PTR_ERR(i2s->regmap); +		goto err_clk_put; +	} +	regcache_cache_only(i2s->regmap, true); + +	pm_runtime_enable(&pdev->dev); +	if (!pm_runtime_enabled(&pdev->dev)) { +		ret = tegra30_i2s_runtime_resume(&pdev->dev); +		if (ret) +			goto err_pm_disable; +	} + +	i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	i2s->playback_dma_data.maxburst = 4; +	ret = tegra30_ahub_allocate_tx_fifo(&i2s->playback_fifo_cif, +					    i2s->playback_dma_chan, +					    sizeof(i2s->playback_dma_chan), +					    &i2s->playback_dma_data.addr); +	if (ret) { +		dev_err(&pdev->dev, "Could not alloc TX FIFO: %d\n", ret); +		goto err_suspend; +	} +	ret = tegra30_ahub_set_rx_cif_source(i2s->playback_i2s_cif, +					     i2s->playback_fifo_cif); +	if (ret) { +		dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret); +		goto err_free_tx_fifo; +	} + +	i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; +	i2s->capture_dma_data.maxburst = 4; +	ret = tegra30_ahub_allocate_rx_fifo(&i2s->capture_fifo_cif, +					    i2s->capture_dma_chan, +					    sizeof(i2s->capture_dma_chan), +					    &i2s->capture_dma_data.addr); +	if (ret) { +		dev_err(&pdev->dev, "Could not alloc RX FIFO: %d\n", ret); +		goto err_unroute_tx_fifo; +	} +	ret = tegra30_ahub_set_rx_cif_source(i2s->capture_fifo_cif, +					     i2s->capture_i2s_cif); +	if (ret) { +		dev_err(&pdev->dev, "Could not route TX FIFO: %d\n", ret); +		goto err_free_rx_fifo; +	} + +	ret = snd_soc_register_component(&pdev->dev, &tegra30_i2s_component, +				   &i2s->dai, 1); +	if (ret) { +		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); +		ret = -ENOMEM; +		goto err_unroute_rx_fifo; +	} + +	ret = tegra_pcm_platform_register_with_chan_names(&pdev->dev, +				&i2s->dma_config, i2s->playback_dma_chan, +				i2s->capture_dma_chan); +	if (ret) { +		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); +		goto err_unregister_component; +	} + +	return 0; + +err_unregister_component: +	snd_soc_unregister_component(&pdev->dev); +err_unroute_rx_fifo: +	tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif); +err_free_rx_fifo: +	tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif); +err_unroute_tx_fifo: +	tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); +err_free_tx_fifo: +	tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); +err_suspend: +	if (!pm_runtime_status_suspended(&pdev->dev)) +		tegra30_i2s_runtime_suspend(&pdev->dev); +err_pm_disable: +	pm_runtime_disable(&pdev->dev); +err_clk_put: +	clk_put(i2s->clk_i2s); +err: +	return ret; +} + +static int tegra30_i2s_platform_remove(struct platform_device *pdev) +{ +	struct tegra30_i2s *i2s = dev_get_drvdata(&pdev->dev); + +	pm_runtime_disable(&pdev->dev); +	if (!pm_runtime_status_suspended(&pdev->dev)) +		tegra30_i2s_runtime_suspend(&pdev->dev); + +	tegra_pcm_platform_unregister(&pdev->dev); +	snd_soc_unregister_component(&pdev->dev); + +	tegra30_ahub_unset_rx_cif_source(i2s->capture_fifo_cif); +	tegra30_ahub_free_rx_fifo(i2s->capture_fifo_cif); + +	tegra30_ahub_unset_rx_cif_source(i2s->playback_i2s_cif); +	tegra30_ahub_free_tx_fifo(i2s->playback_fifo_cif); + +	clk_put(i2s->clk_i2s); + +	return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int tegra30_i2s_suspend(struct device *dev) +{ +	struct tegra30_i2s *i2s = dev_get_drvdata(dev); + +	regcache_mark_dirty(i2s->regmap); + +	return 0; +} + +static int tegra30_i2s_resume(struct device *dev) +{ +	struct tegra30_i2s *i2s = dev_get_drvdata(dev); +	int ret; + +	ret = pm_runtime_get_sync(dev); +	if (ret < 0) +		return ret; +	ret = regcache_sync(i2s->regmap); +	pm_runtime_put(dev); + +	return ret; +} +#endif + +static const struct dev_pm_ops tegra30_i2s_pm_ops = { +	SET_RUNTIME_PM_OPS(tegra30_i2s_runtime_suspend, +			   tegra30_i2s_runtime_resume, NULL) +	SET_SYSTEM_SLEEP_PM_OPS(tegra30_i2s_suspend, tegra30_i2s_resume) +}; + +static struct platform_driver tegra30_i2s_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.of_match_table = tegra30_i2s_of_match, +		.pm = &tegra30_i2s_pm_ops, +	}, +	.probe = tegra30_i2s_platform_probe, +	.remove = tegra30_i2s_platform_remove, +}; +module_platform_driver(tegra30_i2s_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra30 I2S ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra30_i2s_of_match); diff --git a/sound/soc/tegra/tegra30_i2s.h b/sound/soc/tegra/tegra30_i2s.h new file mode 100644 index 00000000000..774fc6ad202 --- /dev/null +++ b/sound/soc/tegra/tegra30_i2s.h @@ -0,0 +1,251 @@ +/* + * tegra30_i2s.h - Definitions for Tegra30 I2S driver + * + * Copyright (c) 2011,2012, NVIDIA CORPORATION.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __TEGRA30_I2S_H__ +#define __TEGRA30_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA30_I2S*_BASE */ + +#define TEGRA30_I2S_CTRL				0x0 +#define TEGRA30_I2S_TIMING				0x4 +#define TEGRA30_I2S_OFFSET				0x08 +#define TEGRA30_I2S_CH_CTRL				0x0c +#define TEGRA30_I2S_SLOT_CTRL				0x10 +#define TEGRA30_I2S_CIF_RX_CTRL				0x14 +#define TEGRA30_I2S_CIF_TX_CTRL				0x18 +#define TEGRA30_I2S_FLOWCTL				0x1c +#define TEGRA30_I2S_TX_STEP				0x20 +#define TEGRA30_I2S_FLOW_STATUS				0x24 +#define TEGRA30_I2S_FLOW_TOTAL				0x28 +#define TEGRA30_I2S_FLOW_OVER				0x2c +#define TEGRA30_I2S_FLOW_UNDER				0x30 +#define TEGRA30_I2S_LCOEF_1_4_0				0x34 +#define TEGRA30_I2S_LCOEF_1_4_1				0x38 +#define TEGRA30_I2S_LCOEF_1_4_2				0x3c +#define TEGRA30_I2S_LCOEF_1_4_3				0x40 +#define TEGRA30_I2S_LCOEF_1_4_4				0x44 +#define TEGRA30_I2S_LCOEF_1_4_5				0x48 +#define TEGRA30_I2S_LCOEF_2_4_0				0x4c +#define TEGRA30_I2S_LCOEF_2_4_1				0x50 +#define TEGRA30_I2S_LCOEF_2_4_2				0x54 + +/* Fields in TEGRA30_I2S_CTRL */ + +#define TEGRA30_I2S_CTRL_XFER_EN_TX			(1 << 31) +#define TEGRA30_I2S_CTRL_XFER_EN_RX			(1 << 30) +#define TEGRA30_I2S_CTRL_CG_EN				(1 << 29) +#define TEGRA30_I2S_CTRL_SOFT_RESET			(1 << 28) +#define TEGRA30_I2S_CTRL_TX_FLOWCTL_EN			(1 << 27) + +#define TEGRA30_I2S_CTRL_OBS_SEL_SHIFT			24 +#define TEGRA30_I2S_CTRL_OBS_SEL_MASK			(7 << TEGRA30_I2S_CTRL_OBS_SEL_SHIFT) + +#define TEGRA30_I2S_FRAME_FORMAT_LRCK			0 +#define TEGRA30_I2S_FRAME_FORMAT_FSYNC			1 + +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT		12 +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_MASK		(7                              << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_LRCK		(TEGRA30_I2S_FRAME_FORMAT_LRCK  << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) +#define TEGRA30_I2S_CTRL_FRAME_FORMAT_FSYNC		(TEGRA30_I2S_FRAME_FORMAT_FSYNC << TEGRA30_I2S_CTRL_FRAME_FORMAT_SHIFT) + +#define TEGRA30_I2S_CTRL_MASTER_ENABLE			(1 << 10) + +#define TEGRA30_I2S_LRCK_LEFT_LOW			0 +#define TEGRA30_I2S_LRCK_RIGHT_LOW			1 + +#define TEGRA30_I2S_CTRL_LRCK_SHIFT			9 +#define TEGRA30_I2S_CTRL_LRCK_MASK			(1                          << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_L_LOW			(TEGRA30_I2S_LRCK_LEFT_LOW  << TEGRA30_I2S_CTRL_LRCK_SHIFT) +#define TEGRA30_I2S_CTRL_LRCK_R_LOW			(TEGRA30_I2S_LRCK_RIGHT_LOW << TEGRA30_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA30_I2S_CTRL_LPBK_ENABLE			(1 << 8) + +#define TEGRA30_I2S_BIT_CODE_LINEAR			0 +#define TEGRA30_I2S_BIT_CODE_ULAW			1 +#define TEGRA30_I2S_BIT_CODE_ALAW			2 + +#define TEGRA30_I2S_CTRL_BIT_CODE_SHIFT			4 +#define TEGRA30_I2S_CTRL_BIT_CODE_MASK			(3                           << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_LINEAR		(TEGRA30_I2S_BIT_CODE_LINEAR << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ULAW			(TEGRA30_I2S_BIT_CODE_ULAW   << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_CODE_ALAW			(TEGRA30_I2S_BIT_CODE_ALAW   << TEGRA30_I2S_CTRL_BIT_CODE_SHIFT) + +#define TEGRA30_I2S_BITS_8				1 +#define TEGRA30_I2S_BITS_12				2 +#define TEGRA30_I2S_BITS_16				3 +#define TEGRA30_I2S_BITS_20				4 +#define TEGRA30_I2S_BITS_24				5 +#define TEGRA30_I2S_BITS_28				6 +#define TEGRA30_I2S_BITS_32				7 + +/* Sample container size; see {RX,TX}_MASK field in CH_CTRL below */ +#define TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT			0 +#define TEGRA30_I2S_CTRL_BIT_SIZE_MASK			(7                   << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_8			(TEGRA30_I2S_BITS_8  << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_12			(TEGRA30_I2S_BITS_12 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_16			(TEGRA30_I2S_BITS_16 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_20			(TEGRA30_I2S_BITS_20 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_24			(TEGRA30_I2S_BITS_24 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_28			(TEGRA30_I2S_BITS_28 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA30_I2S_CTRL_BIT_SIZE_32			(TEGRA30_I2S_BITS_32 << TEGRA30_I2S_CTRL_BIT_SIZE_SHIFT) + +/* Fields in TEGRA30_I2S_TIMING */ + +#define TEGRA30_I2S_TIMING_NON_SYM_ENABLE		(1 << 12) +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT	0 +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US	0x7ff +#define TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK	(TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA30_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA30_I2S_OFFSET */ + +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT		16 +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US	0x7ff +#define TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK		(TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_RX_DATA_OFFSET_SHIFT) +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT		0 +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US	0x7ff +#define TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK		(TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_MASK_US << TEGRA30_I2S_OFFSET_TX_DATA_OFFSET_SHIFT) + +/* Fields in TEGRA30_I2S_CH_CTRL */ + +/* (FSYNC width - 1) in bit clocks */ +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT		24 +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US		0xff +#define TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK		(TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_MASK_US << TEGRA30_I2S_CH_CTRL_FSYNC_WIDTH_SHIFT) + +#define TEGRA30_I2S_HIGHZ_NO				0 +#define TEGRA30_I2S_HIGHZ_YES				1 +#define TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK		2 + +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT		12 +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_MASK		(3                                 << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_NO		(TEGRA30_I2S_HIGHZ_NO              << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_YES		(TEGRA30_I2S_HIGHZ_YES             << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_ON_HALF_BIT_CLK	(TEGRA30_I2S_HIGHZ_ON_HALF_BIT_CLK << TEGRA30_I2S_CH_CTRL_HIGHZ_CTRL_SHIFT) + +#define TEGRA30_I2S_MSB_FIRST				0 +#define TEGRA30_I2S_LSB_FIRST				1 + +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT		10 +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MASK		(1                     << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_MSB_FIRST	(TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_LSB_FIRST	(TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_RX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT		9 +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MASK		(1                     << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_MSB_FIRST	(TEGRA30_I2S_MSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) +#define TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_LSB_FIRST	(TEGRA30_I2S_LSB_FIRST << TEGRA30_I2S_CH_CTRL_TX_BIT_ORDER_SHIFT) + +#define TEGRA30_I2S_POS_EDGE				0 +#define TEGRA30_I2S_NEG_EDGE				1 + +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT		8 +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_MASK		(1                    << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_POS_EDGE		(TEGRA30_I2S_POS_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) +#define TEGRA30_I2S_CH_CTRL_EGDE_CTRL_NEG_EDGE		(TEGRA30_I2S_NEG_EDGE << TEGRA30_I2S_CH_CTRL_EGDE_CTRL_SHIFT) + +/* Sample size is # bits from BIT_SIZE minus this field */ +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT		4 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US	7 +#define TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK		(TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_RX_MASK_BITS_SHIFT) + +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT		0 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US	7 +#define TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK		(TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_MASK_US << TEGRA30_I2S_CH_CTRL_TX_MASK_BITS_SHIFT) + +/* Fields in TEGRA30_I2S_SLOT_CTRL */ + +/* Number of slots in frame, minus 1 */ +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_SHIFT		16 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK_US	7 +#define TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOTS_MASK		(TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOT_MASK_US << TEGRA30_I2S_SLOT_CTRL_TOTAL_SLOT_SHIFT) + +/* TDM mode slot enable bitmask */ +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT	8 +#define TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_MASK	(0xff << TEGRA30_I2S_SLOT_CTRL_RX_SLOT_ENABLES_SHIFT) + +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT	0 +#define TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_MASK	(0xff << TEGRA30_I2S_SLOT_CTRL_TX_SLOT_ENABLES_SHIFT) + +/* Fields in TEGRA30_I2S_CIF_RX_CTRL */ +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */ + +/* Fields in TEGRA30_I2S_CIF_TX_CTRL */ +/* Uses field from TEGRA30_AUDIOCIF_CTRL_* in tegra30_ahub.h */ + +/* Fields in TEGRA30_I2S_FLOWCTL */ + +#define TEGRA30_I2S_FILTER_LINEAR			0 +#define TEGRA30_I2S_FILTER_QUAD				1 + +#define TEGRA30_I2S_FLOWCTL_FILTER_SHIFT		31 +#define TEGRA30_I2S_FLOWCTL_FILTER_MASK			(1                         << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_LINEAR		(TEGRA30_I2S_FILTER_LINEAR << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) +#define TEGRA30_I2S_FLOWCTL_FILTER_QUAD			(TEGRA30_I2S_FILTER_QUAD   << TEGRA30_I2S_FLOWCTL_FILTER_SHIFT) + +/* Fields in TEGRA30_I2S_TX_STEP */ + +#define TEGRA30_I2S_TX_STEP_SHIFT			0 +#define TEGRA30_I2S_TX_STEP_MASK_US			0xffff +#define TEGRA30_I2S_TX_STEP_MASK			(TEGRA30_I2S_TX_STEP_MASK_US << TEGRA30_I2S_TX_STEP_SHIFT) + +/* Fields in TEGRA30_I2S_FLOW_STATUS */ + +#define TEGRA30_I2S_FLOW_STATUS_UNDERFLOW		(1 << 31) +#define TEGRA30_I2S_FLOW_STATUS_OVERFLOW		(1 << 30) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_INT_EN		(1 << 4) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_CLR		(1 << 3) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_CLR		(1 << 2) +#define TEGRA30_I2S_FLOW_STATUS_COUNTER_EN		(1 << 1) +#define TEGRA30_I2S_FLOW_STATUS_MONITOR_EN		(1 << 0) + +/* + * There are no fields in TEGRA30_I2S_FLOW_TOTAL, TEGRA30_I2S_FLOW_OVER, + * TEGRA30_I2S_FLOW_UNDER; they are counters taking the whole register. + */ + +/* Fields in TEGRA30_I2S_LCOEF_* */ + +#define TEGRA30_I2S_LCOEF_COEF_SHIFT			0 +#define TEGRA30_I2S_LCOEF_COEF_MASK_US			0xffff +#define TEGRA30_I2S_LCOEF_COEF_MASK			(TEGRA30_I2S_LCOEF_COEF_MASK_US << TEGRA30_I2S_LCOEF_COEF_SHIFT) + +struct tegra30_i2s_soc_data { +	void (*set_audio_cif)(struct regmap *regmap, +			      unsigned int reg, +			      struct tegra30_ahub_cif_conf *conf); +}; + +struct tegra30_i2s { +	const struct tegra30_i2s_soc_data *soc_data; +	struct snd_soc_dai_driver dai; +	int cif_id; +	struct clk *clk_i2s; +	enum tegra30_ahub_txcif capture_i2s_cif; +	enum tegra30_ahub_rxcif capture_fifo_cif; +	char capture_dma_chan[8]; +	struct snd_dmaengine_dai_dma_data capture_dma_data; +	enum tegra30_ahub_rxcif playback_i2s_cif; +	enum tegra30_ahub_txcif playback_fifo_cif; +	char playback_dma_chan[8]; +	struct snd_dmaengine_dai_dma_data playback_dma_data; +	struct regmap *regmap; +	struct snd_dmaengine_pcm_config dma_config; +}; + +#endif diff --git a/sound/soc/tegra/tegra_alc5632.c b/sound/soc/tegra/tegra_alc5632.c new file mode 100644 index 00000000000..02734bd4f09 --- /dev/null +++ b/sound/soc/tegra/tegra_alc5632.c @@ -0,0 +1,267 @@ +/* +* tegra_alc5632.c  --  Toshiba AC100(PAZ00) machine ASoC driver + * + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * Copyright (C) 2012 - NVIDIA, Inc. + * + * Authors:  Leon Romanovsky <leon@leon.nu> + *           Andrey Danin <danindrey@mail.ru> + *           Marc Dietrich <marvin24@gmx.de> + * + * 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. + */ + +#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> + +#include "../codecs/alc5632.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-alc5632" + +struct tegra_alc5632 { +	struct tegra_asoc_utils_data util_data; +	int gpio_hp_det; +}; + +static int tegra_alc5632_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->codec_dai; +	struct snd_soc_codec *codec = codec_dai->codec; +	struct snd_soc_card *card = codec->card; +	struct tegra_alc5632 *alc5632 = snd_soc_card_get_drvdata(card); +	int srate, mclk; +	int err; + +	srate = params_rate(params); +	mclk = 512 * srate; + +	err = tegra_asoc_utils_set_rate(&alc5632->util_data, srate, mclk); +	if (err < 0) { +		dev_err(card->dev, "Can't configure clocks\n"); +		return err; +	} + +	err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, +					SND_SOC_CLOCK_IN); +	if (err < 0) { +		dev_err(card->dev, "codec_dai clock not set\n"); +		return err; +	} + +	return 0; +} + +static struct snd_soc_ops tegra_alc5632_asoc_ops = { +	.hw_params = tegra_alc5632_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_alc5632_hs_jack; + +static struct snd_soc_jack_pin tegra_alc5632_hs_jack_pins[] = { +	{ +		.pin = "Headset Mic", +		.mask = SND_JACK_MICROPHONE, +	}, +	{ +		.pin = "Headset Stereophone", +		.mask = SND_JACK_HEADPHONE, +	}, +}; + +static struct snd_soc_jack_gpio tegra_alc5632_hp_jack_gpio = { +	.name = "Headset detection", +	.report = SND_JACK_HEADSET, +	.debounce_time = 150, +}; + +static const struct snd_soc_dapm_widget tegra_alc5632_dapm_widgets[] = { +	SND_SOC_DAPM_SPK("Int Spk", NULL), +	SND_SOC_DAPM_HP("Headset Stereophone", NULL), +	SND_SOC_DAPM_MIC("Headset Mic", NULL), +	SND_SOC_DAPM_MIC("Digital Mic", NULL), +}; + +static const struct snd_kcontrol_new tegra_alc5632_controls[] = { +	SOC_DAPM_PIN_SWITCH("Int Spk"), +}; + +static int tegra_alc5632_asoc_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; +	struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(codec->card); + +	snd_soc_jack_new(codec, "Headset Jack", SND_JACK_HEADSET, +			 &tegra_alc5632_hs_jack); +	snd_soc_jack_add_pins(&tegra_alc5632_hs_jack, +			ARRAY_SIZE(tegra_alc5632_hs_jack_pins), +			tegra_alc5632_hs_jack_pins); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		tegra_alc5632_hp_jack_gpio.gpio = machine->gpio_hp_det; +		snd_soc_jack_add_gpios(&tegra_alc5632_hs_jack, +						1, +						&tegra_alc5632_hp_jack_gpio); +	} + +	snd_soc_dapm_force_enable_pin(dapm, "MICBIAS1"); + +	return 0; +} + +static int tegra_alc5632_card_remove(struct snd_soc_card *card) +{ +	struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(card); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		snd_soc_jack_free_gpios(&tegra_alc5632_hs_jack, 1, +					&tegra_alc5632_hp_jack_gpio); +	} + +	return 0; +} + +static struct snd_soc_dai_link tegra_alc5632_dai = { +	.name = "ALC5632", +	.stream_name = "ALC5632 PCM", +	.codec_dai_name = "alc5632-hifi", +	.init = tegra_alc5632_asoc_init, +	.ops = &tegra_alc5632_asoc_ops, +	.dai_fmt = SND_SOC_DAIFMT_I2S +			   | SND_SOC_DAIFMT_NB_NF +			   | SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_alc5632 = { +	.name = "tegra-alc5632", +	.owner = THIS_MODULE, +	.remove = tegra_alc5632_card_remove, +	.dai_link = &tegra_alc5632_dai, +	.num_links = 1, +	.controls = tegra_alc5632_controls, +	.num_controls = ARRAY_SIZE(tegra_alc5632_controls), +	.dapm_widgets = tegra_alc5632_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(tegra_alc5632_dapm_widgets), +	.fully_routed = true, +}; + +static int tegra_alc5632_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct snd_soc_card *card = &snd_soc_tegra_alc5632; +	struct tegra_alc5632 *alc5632; +	int ret; + +	alc5632 = devm_kzalloc(&pdev->dev, +			sizeof(struct tegra_alc5632), GFP_KERNEL); +	if (!alc5632) { +		dev_err(&pdev->dev, "Can't allocate tegra_alc5632\n"); +		return -ENOMEM; +	} + +	card->dev = &pdev->dev; +	platform_set_drvdata(pdev, card); +	snd_soc_card_set_drvdata(card, alc5632); + +	alc5632->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); +	if (alc5632->gpio_hp_det == -EPROBE_DEFER) +		return -EPROBE_DEFER; + +	ret = snd_soc_of_parse_card_name(card, "nvidia,model"); +	if (ret) +		goto err; + +	ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); +	if (ret) +		goto err; + +	tegra_alc5632_dai.codec_of_node = of_parse_phandle( +			pdev->dev.of_node, "nvidia,audio-codec", 0); + +	if (!tegra_alc5632_dai.codec_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,audio-codec' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_alc5632_dai.cpu_of_node = of_parse_phandle(np, +			"nvidia,i2s-controller", 0); +	if (!tegra_alc5632_dai.cpu_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,i2s-controller' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_alc5632_dai.platform_of_node = tegra_alc5632_dai.cpu_of_node; + +	ret = tegra_asoc_utils_init(&alc5632->util_data, &pdev->dev); +	if (ret) +		goto err; + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		goto err_fini_utils; +	} + +	return 0; + +err_fini_utils: +	tegra_asoc_utils_fini(&alc5632->util_data); +err: +	return ret; +} + +static int tegra_alc5632_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	struct tegra_alc5632 *machine = snd_soc_card_get_drvdata(card); + +	snd_soc_unregister_card(card); + +	tegra_asoc_utils_fini(&machine->util_data); + +	return 0; +} + +static const struct of_device_id tegra_alc5632_of_match[] = { +	{ .compatible = "nvidia,tegra-audio-alc5632", }, +	{}, +}; + +static struct platform_driver tegra_alc5632_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.pm = &snd_soc_pm_ops, +		.of_match_table = tegra_alc5632_of_match, +	}, +	.probe = tegra_alc5632_probe, +	.remove = tegra_alc5632_remove, +}; +module_platform_driver(tegra_alc5632_driver); + +MODULE_AUTHOR("Leon Romanovsky <leon@leon.nu>"); +MODULE_DESCRIPTION("Tegra+ALC5632 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_alc5632_of_match); diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c new file mode 100644 index 00000000000..1be311c51a1 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.c @@ -0,0 +1,240 @@ +/* + * tegra_asoc_utils.c - Harmony machine ASoC driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, 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. + * + * 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 <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> + +#include "tegra_asoc_utils.h" + +int tegra_asoc_utils_set_rate(struct tegra_asoc_utils_data *data, int srate, +			      int mclk) +{ +	int new_baseclock; +	bool clk_change; +	int err; + +	switch (srate) { +	case 11025: +	case 22050: +	case 44100: +	case 88200: +		if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) +			new_baseclock = 56448000; +		else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) +			new_baseclock = 564480000; +		else +			new_baseclock = 282240000; +		break; +	case 8000: +	case 16000: +	case 32000: +	case 48000: +	case 64000: +	case 96000: +		if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA20) +			new_baseclock = 73728000; +		else if (data->soc == TEGRA_ASOC_UTILS_SOC_TEGRA30) +			new_baseclock = 552960000; +		else +			new_baseclock = 368640000; +		break; +	default: +		return -EINVAL; +	} + +	clk_change = ((new_baseclock != data->set_baseclock) || +			(mclk != data->set_mclk)); +	if (!clk_change) +		return 0; + +	data->set_baseclock = 0; +	data->set_mclk = 0; + +	clk_disable_unprepare(data->clk_cdev1); +	clk_disable_unprepare(data->clk_pll_a_out0); +	clk_disable_unprepare(data->clk_pll_a); + +	err = clk_set_rate(data->clk_pll_a, new_baseclock); +	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, mclk); +	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 = new_baseclock; +	data->set_mclk = mclk; + +	return 0; +} +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) +{ +	int ret; + +	data->dev = dev; + +	if (of_machine_is_compatible("nvidia,tegra20")) +		data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA20; +	else if (of_machine_is_compatible("nvidia,tegra30")) +		data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA30; +	else if (of_machine_is_compatible("nvidia,tegra114")) +		data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA114; +	else if (of_machine_is_compatible("nvidia,tegra124")) +		data->soc = TEGRA_ASOC_UTILS_SOC_TEGRA124; +	else { +		dev_err(data->dev, "SoC unknown to Tegra ASoC utils\n"); +		return -EINVAL; +	} + +	data->clk_pll_a = clk_get(dev, "pll_a"); +	if (IS_ERR(data->clk_pll_a)) { +		dev_err(data->dev, "Can't retrieve clk pll_a\n"); +		ret = PTR_ERR(data->clk_pll_a); +		goto err; +	} + +	data->clk_pll_a_out0 = clk_get(dev, "pll_a_out0"); +	if (IS_ERR(data->clk_pll_a_out0)) { +		dev_err(data->dev, "Can't retrieve clk pll_a_out0\n"); +		ret = PTR_ERR(data->clk_pll_a_out0); +		goto err_put_pll_a; +	} + +	data->clk_cdev1 = clk_get(dev, "mclk"); +	if (IS_ERR(data->clk_cdev1)) { +		dev_err(data->dev, "Can't retrieve clk cdev1\n"); +		ret = PTR_ERR(data->clk_cdev1); +		goto err_put_pll_a_out0; +	} + +	ret = tegra_asoc_utils_set_rate(data, 44100, 256 * 44100); +	if (ret) +		goto err_put_cdev1; + +	return 0; + +err_put_cdev1: +	clk_put(data->clk_cdev1); +err_put_pll_a_out0: +	clk_put(data->clk_pll_a_out0); +err_put_pll_a: +	clk_put(data->clk_pll_a); +err: +	return ret; +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_init); + +void tegra_asoc_utils_fini(struct tegra_asoc_utils_data *data) +{ +	clk_put(data->clk_cdev1); +	clk_put(data->clk_pll_a_out0); +	clk_put(data->clk_pll_a); +} +EXPORT_SYMBOL_GPL(tegra_asoc_utils_fini); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra ASoC utility code"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h new file mode 100644 index 00000000000..9577121ce97 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.h @@ -0,0 +1,53 @@ +/* + * tegra_asoc_utils.h - Definitions for Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, 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. + * + * 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 + * + */ + +#ifndef __TEGRA_ASOC_UTILS_H__ +#define __TEGRA_ASOC_UTILS_H_ + +struct clk; +struct device; + +enum tegra_asoc_utils_soc { +	TEGRA_ASOC_UTILS_SOC_TEGRA20, +	TEGRA_ASOC_UTILS_SOC_TEGRA30, +	TEGRA_ASOC_UTILS_SOC_TEGRA114, +	TEGRA_ASOC_UTILS_SOC_TEGRA124, +}; + +struct tegra_asoc_utils_data { +	struct device *dev; +	enum tegra_asoc_utils_soc soc; +	struct clk *clk_pll_a; +	struct clk *clk_pll_a_out0; +	struct clk *clk_cdev1; +	int set_baseclock; +	int set_mclk; +}; + +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); + +#endif diff --git a/sound/soc/tegra/tegra_max98090.c b/sound/soc/tegra/tegra_max98090.c new file mode 100644 index 00000000000..ce73e1f62c4 --- /dev/null +++ b/sound/soc/tegra/tegra_max98090.c @@ -0,0 +1,285 @@ +/* + * Tegra machine ASoC driver for boards using a MAX90809 CODEC. + * + * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#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> + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-max98090" + +struct tegra_max98090 { +	struct tegra_asoc_utils_data util_data; +	int gpio_hp_det; +}; + +static int tegra_max98090_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->codec_dai; +	struct snd_soc_codec *codec = codec_dai->codec; +	struct snd_soc_card *card = codec->card; +	struct tegra_max98090 *machine = snd_soc_card_get_drvdata(card); +	int srate, mclk; +	int err; + +	srate = params_rate(params); +	switch (srate) { +	case 8000: +	case 16000: +	case 24000: +	case 32000: +	case 48000: +	case 64000: +	case 96000: +		mclk = 12288000; +		break; +	case 11025: +	case 22050: +	case 44100: +	case 88200: +		mclk = 11289600; +		break; +	default: +		mclk = 12000000; +		break; +	} + +	err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); +	if (err < 0) { +		dev_err(card->dev, "Can't configure clocks\n"); +		return err; +	} + +	err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, +					SND_SOC_CLOCK_IN); +	if (err < 0) { +		dev_err(card->dev, "codec_dai clock not set\n"); +		return err; +	} + +	return 0; +} + +static struct snd_soc_ops tegra_max98090_ops = { +	.hw_params = tegra_max98090_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_max98090_hp_jack; + +static struct snd_soc_jack_pin tegra_max98090_hp_jack_pins[] = { +	{ +		.pin = "Headphones", +		.mask = SND_JACK_HEADPHONE, +	}, +}; + +static struct snd_soc_jack_gpio tegra_max98090_hp_jack_gpio = { +	.name = "Headphone detection", +	.report = SND_JACK_HEADPHONE, +	.debounce_time = 150, +	.invert = 1, +}; + +static const struct snd_soc_dapm_widget tegra_max98090_dapm_widgets[] = { +	SND_SOC_DAPM_HP("Headphones", NULL), +	SND_SOC_DAPM_SPK("Speakers", NULL), +	SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_kcontrol_new tegra_max98090_controls[] = { +	SOC_DAPM_PIN_SWITCH("Speakers"), +}; + +static int tegra_max98090_asoc_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 tegra_max98090 *machine = snd_soc_card_get_drvdata(codec->card); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		snd_soc_jack_new(codec, "Headphones", SND_JACK_HEADPHONE, +				&tegra_max98090_hp_jack); +		snd_soc_jack_add_pins(&tegra_max98090_hp_jack, +				ARRAY_SIZE(tegra_max98090_hp_jack_pins), +				tegra_max98090_hp_jack_pins); + +		tegra_max98090_hp_jack_gpio.gpio = machine->gpio_hp_det; +		snd_soc_jack_add_gpios(&tegra_max98090_hp_jack, +					1, +					&tegra_max98090_hp_jack_gpio); +	} + +	return 0; +} + +static int tegra_max98090_card_remove(struct snd_soc_card *card) +{ +	struct tegra_max98090 *machine = snd_soc_card_get_drvdata(card); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		snd_soc_jack_free_gpios(&tegra_max98090_hp_jack, 1, +					&tegra_max98090_hp_jack_gpio); +	} + +	return 0; +} + +static struct snd_soc_dai_link tegra_max98090_dai = { +	.name = "max98090", +	.stream_name = "max98090 PCM", +	.codec_dai_name = "HiFi", +	.init = tegra_max98090_asoc_init, +	.ops = &tegra_max98090_ops, +	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | +			SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_max98090 = { +	.name = "tegra-max98090", +	.owner = THIS_MODULE, +	.remove = tegra_max98090_card_remove, +	.dai_link = &tegra_max98090_dai, +	.num_links = 1, +	.controls = tegra_max98090_controls, +	.num_controls = ARRAY_SIZE(tegra_max98090_controls), +	.dapm_widgets = tegra_max98090_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(tegra_max98090_dapm_widgets), +	.fully_routed = true, +}; + +static int tegra_max98090_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct snd_soc_card *card = &snd_soc_tegra_max98090; +	struct tegra_max98090 *machine; +	int ret; + +	machine = devm_kzalloc(&pdev->dev, +			sizeof(struct tegra_max98090), GFP_KERNEL); +	if (!machine) { +		dev_err(&pdev->dev, "Can't allocate tegra_max98090\n"); +		return -ENOMEM; +	} + +	card->dev = &pdev->dev; +	platform_set_drvdata(pdev, card); +	snd_soc_card_set_drvdata(card, machine); + +	machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); +	if (machine->gpio_hp_det == -EPROBE_DEFER) +		return -EPROBE_DEFER; + +	ret = snd_soc_of_parse_card_name(card, "nvidia,model"); +	if (ret) +		goto err; + +	ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); +	if (ret) +		goto err; + +	tegra_max98090_dai.codec_of_node = of_parse_phandle(np, +			"nvidia,audio-codec", 0); +	if (!tegra_max98090_dai.codec_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,audio-codec' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_max98090_dai.cpu_of_node = of_parse_phandle(np, +			"nvidia,i2s-controller", 0); +	if (!tegra_max98090_dai.cpu_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,i2s-controller' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_max98090_dai.platform_of_node = tegra_max98090_dai.cpu_of_node; + +	ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); +	if (ret) +		goto err; + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		goto err_fini_utils; +	} + +	return 0; + +err_fini_utils: +	tegra_asoc_utils_fini(&machine->util_data); +err: +	return ret; +} + +static int tegra_max98090_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	struct tegra_max98090 *machine = snd_soc_card_get_drvdata(card); + +	snd_soc_unregister_card(card); + +	tegra_asoc_utils_fini(&machine->util_data); + +	return 0; +} + +static const struct of_device_id tegra_max98090_of_match[] = { +	{ .compatible = "nvidia,tegra-audio-max98090", }, +	{}, +}; + +static struct platform_driver tegra_max98090_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.pm = &snd_soc_pm_ops, +		.of_match_table = tegra_max98090_of_match, +	}, +	.probe = tegra_max98090_probe, +	.remove = tegra_max98090_remove, +}; +module_platform_driver(tegra_max98090_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra max98090 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_max98090_of_match); diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 00000000000..93caed50056 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c @@ -0,0 +1,86 @@ +/* + * tegra_pcm.c - Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * Vijay Mali <vmali@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * 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 <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "tegra_pcm.h" + +static const struct snd_pcm_hardware tegra_pcm_hardware = { +	.info			= SNDRV_PCM_INFO_MMAP | +				  SNDRV_PCM_INFO_MMAP_VALID | +				  SNDRV_PCM_INFO_INTERLEAVED, +	.period_bytes_min	= 1024, +	.period_bytes_max	= PAGE_SIZE, +	.periods_min		= 2, +	.periods_max		= 8, +	.buffer_bytes_max	= PAGE_SIZE * 8, +	.fifo_size		= 4, +}; + +static const struct snd_dmaengine_pcm_config tegra_dmaengine_pcm_config = { +	.pcm_hardware = &tegra_pcm_hardware, +	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config, +	.prealloc_buffer_size = PAGE_SIZE * 8, +}; + +int tegra_pcm_platform_register(struct device *dev) +{ +	return snd_dmaengine_pcm_register(dev, &tegra_dmaengine_pcm_config, 0); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_register); + +int tegra_pcm_platform_register_with_chan_names(struct device *dev, +				struct snd_dmaengine_pcm_config *config, +				char *txdmachan, char *rxdmachan) +{ +	*config = tegra_dmaengine_pcm_config; +	config->dma_dev = dev->parent; +	config->chan_names[0] = txdmachan; +	config->chan_names[1] = rxdmachan; + +	return snd_dmaengine_pcm_register(dev, config, 0); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_register_with_chan_names); + +void tegra_pcm_platform_unregister(struct device *dev) +{ +	return snd_dmaengine_pcm_unregister(dev); +} +EXPORT_SYMBOL_GPL(tegra_pcm_platform_unregister); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra PCM ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h new file mode 100644 index 00000000000..7883dec748a --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.h @@ -0,0 +1,42 @@ +/* + * tegra_pcm.h - Definitions for Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010,2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * 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 + * + */ + +#ifndef __TEGRA_PCM_H__ +#define __TEGRA_PCM_H__ + +struct snd_dmaengine_pcm_config; + +int tegra_pcm_platform_register(struct device *dev); +int tegra_pcm_platform_register_with_chan_names(struct device *dev, +				struct snd_dmaengine_pcm_config *config, +				char *txdmachan, char *rxdmachan); +void tegra_pcm_platform_unregister(struct device *dev); + +#endif diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c new file mode 100644 index 00000000000..4feb16a99e0 --- /dev/null +++ b/sound/soc/tegra/tegra_rt5640.c @@ -0,0 +1,268 @@ +/* +* tegra_rt5640.c - Tegra machine ASoC driver for boards using WM8903 codec. + * + * Copyright (c) 2013, NVIDIA CORPORATION.  All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * Copyright (C) 2011 The AC100 Kernel Team <ac100@lists.lauchpad.net> + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#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> + +#include "../codecs/rt5640.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-rt5640" + +struct tegra_rt5640 { +	struct tegra_asoc_utils_data util_data; +	int gpio_hp_det; +}; + +static int tegra_rt5640_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->codec_dai; +	struct snd_soc_codec *codec = codec_dai->codec; +	struct snd_soc_card *card = codec->card; +	struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); +	int srate, mclk; +	int err; + +	srate = params_rate(params); +	mclk = 256 * srate; + +	err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); +	if (err < 0) { +		dev_err(card->dev, "Can't configure clocks\n"); +		return err; +	} + +	err = snd_soc_dai_set_sysclk(codec_dai, RT5640_SCLK_S_MCLK, mclk, +					SND_SOC_CLOCK_IN); +	if (err < 0) { +		dev_err(card->dev, "codec_dai clock not set\n"); +		return err; +	} + +	return 0; +} + +static struct snd_soc_ops tegra_rt5640_ops = { +	.hw_params = tegra_rt5640_asoc_hw_params, +}; + +static struct snd_soc_jack tegra_rt5640_hp_jack; + +static struct snd_soc_jack_pin tegra_rt5640_hp_jack_pins[] = { +	{ +		.pin = "Headphones", +		.mask = SND_JACK_HEADPHONE, +	}, +}; + +static struct snd_soc_jack_gpio tegra_rt5640_hp_jack_gpio = { +	.name = "Headphone detection", +	.report = SND_JACK_HEADPHONE, +	.debounce_time = 150, +	.invert = 1, +}; + +static const struct snd_soc_dapm_widget tegra_rt5640_dapm_widgets[] = { +	SND_SOC_DAPM_HP("Headphones", NULL), +	SND_SOC_DAPM_SPK("Speakers", NULL), +	SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_kcontrol_new tegra_rt5640_controls[] = { +	SOC_DAPM_PIN_SWITCH("Speakers"), +}; + +static int tegra_rt5640_asoc_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 tegra_rt5640 *machine = snd_soc_card_get_drvdata(codec->card); + +	snd_soc_jack_new(codec, "Headphones", SND_JACK_HEADPHONE, +			 &tegra_rt5640_hp_jack); +	snd_soc_jack_add_pins(&tegra_rt5640_hp_jack, +			ARRAY_SIZE(tegra_rt5640_hp_jack_pins), +			tegra_rt5640_hp_jack_pins); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		tegra_rt5640_hp_jack_gpio.gpio = machine->gpio_hp_det; +		snd_soc_jack_add_gpios(&tegra_rt5640_hp_jack, +						1, +						&tegra_rt5640_hp_jack_gpio); +	} + +	return 0; +} + +static int tegra_rt5640_card_remove(struct snd_soc_card *card) +{ +	struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		snd_soc_jack_free_gpios(&tegra_rt5640_hp_jack, 1, +					&tegra_rt5640_hp_jack_gpio); +	} + +	return 0; +} + +static struct snd_soc_dai_link tegra_rt5640_dai = { +	.name = "RT5640", +	.stream_name = "RT5640 PCM", +	.codec_dai_name = "rt5640-aif1", +	.init = tegra_rt5640_asoc_init, +	.ops = &tegra_rt5640_ops, +	.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | +			SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_rt5640 = { +	.name = "tegra-rt5640", +	.owner = THIS_MODULE, +	.remove = tegra_rt5640_card_remove, +	.dai_link = &tegra_rt5640_dai, +	.num_links = 1, +	.controls = tegra_rt5640_controls, +	.num_controls = ARRAY_SIZE(tegra_rt5640_controls), +	.dapm_widgets = tegra_rt5640_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(tegra_rt5640_dapm_widgets), +	.fully_routed = true, +}; + +static int tegra_rt5640_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct snd_soc_card *card = &snd_soc_tegra_rt5640; +	struct tegra_rt5640 *machine; +	int ret; + +	machine = devm_kzalloc(&pdev->dev, +			sizeof(struct tegra_rt5640), GFP_KERNEL); +	if (!machine) { +		dev_err(&pdev->dev, "Can't allocate tegra_rt5640\n"); +		return -ENOMEM; +	} + +	card->dev = &pdev->dev; +	platform_set_drvdata(pdev, card); +	snd_soc_card_set_drvdata(card, machine); + +	machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); +	if (machine->gpio_hp_det == -EPROBE_DEFER) +		return -EPROBE_DEFER; + +	ret = snd_soc_of_parse_card_name(card, "nvidia,model"); +	if (ret) +		goto err; + +	ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); +	if (ret) +		goto err; + +	tegra_rt5640_dai.codec_of_node = of_parse_phandle(np, +			"nvidia,audio-codec", 0); +	if (!tegra_rt5640_dai.codec_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,audio-codec' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_rt5640_dai.cpu_of_node = of_parse_phandle(np, +			"nvidia,i2s-controller", 0); +	if (!tegra_rt5640_dai.cpu_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,i2s-controller' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_rt5640_dai.platform_of_node = tegra_rt5640_dai.cpu_of_node; + +	ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); +	if (ret) +		goto err; + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		goto err_fini_utils; +	} + +	return 0; + +err_fini_utils: +	tegra_asoc_utils_fini(&machine->util_data); +err: +	return ret; +} + +static int tegra_rt5640_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card); + +	snd_soc_unregister_card(card); + +	tegra_asoc_utils_fini(&machine->util_data); + +	return 0; +} + +static const struct of_device_id tegra_rt5640_of_match[] = { +	{ .compatible = "nvidia,tegra-audio-rt5640", }, +	{}, +}; + +static struct platform_driver tegra_rt5640_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.pm = &snd_soc_pm_ops, +		.of_match_table = tegra_rt5640_of_match, +	}, +	.probe = tegra_rt5640_probe, +	.remove = tegra_rt5640_remove, +}; +module_platform_driver(tegra_rt5640_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra+RT5640 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_rt5640_of_match); diff --git a/sound/soc/tegra/tegra_wm8753.c b/sound/soc/tegra/tegra_wm8753.c new file mode 100644 index 00000000000..8e774d1a243 --- /dev/null +++ b/sound/soc/tegra/tegra_wm8753.c @@ -0,0 +1,221 @@ +/* + * tegra_wm8753.c - Tegra machine ASoC driver for boards using WM8753 codec. + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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 <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> + +#include "../codecs/wm8753.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm8753" + +struct tegra_wm8753 { +	struct tegra_asoc_utils_data util_data; +}; + +static int tegra_wm8753_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_codec *codec = codec_dai->codec; +	struct snd_soc_card *card = codec->card; +	struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card); +	int srate, mclk; +	int err; + +	srate = params_rate(params); +	switch (srate) { +	case 11025: +	case 22050: +	case 44100: +	case 88200: +		mclk = 11289600; +		break; +	default: +		mclk = 12288000; +		break; +	} + +	err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); +	if (err < 0) { +		dev_err(card->dev, "Can't configure clocks\n"); +		return err; +	} + +	err = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, mclk, +					SND_SOC_CLOCK_IN); +	if (err < 0) { +		dev_err(card->dev, "codec_dai clock not set\n"); +		return err; +	} + +	return 0; +} + +static struct snd_soc_ops tegra_wm8753_ops = { +	.hw_params = tegra_wm8753_hw_params, +}; + +static const struct snd_soc_dapm_widget tegra_wm8753_dapm_widgets[] = { +	SND_SOC_DAPM_HP("Headphone Jack", NULL), +	SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static struct snd_soc_dai_link tegra_wm8753_dai = { +	.name = "WM8753", +	.stream_name = "WM8753 PCM", +	.codec_dai_name = "wm8753-hifi", +	.ops = &tegra_wm8753_ops, +	.dai_fmt = SND_SOC_DAIFMT_I2S | +			SND_SOC_DAIFMT_NB_NF | +			SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_wm8753 = { +	.name = "tegra-wm8753", +	.owner = THIS_MODULE, +	.dai_link = &tegra_wm8753_dai, +	.num_links = 1, + +	.dapm_widgets = tegra_wm8753_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(tegra_wm8753_dapm_widgets), +	.fully_routed = true, +}; + +static int tegra_wm8753_driver_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct snd_soc_card *card = &snd_soc_tegra_wm8753; +	struct tegra_wm8753 *machine; +	int ret; + +	machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8753), +			       GFP_KERNEL); +	if (!machine) { +		dev_err(&pdev->dev, "Can't allocate tegra_wm8753 struct\n"); +		return -ENOMEM; +	} + +	card->dev = &pdev->dev; +	platform_set_drvdata(pdev, card); +	snd_soc_card_set_drvdata(card, machine); + +	ret = snd_soc_of_parse_card_name(card, "nvidia,model"); +	if (ret) +		goto err; + +	ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); +	if (ret) +		goto err; + +	tegra_wm8753_dai.codec_of_node = of_parse_phandle(np, +			"nvidia,audio-codec", 0); +	if (!tegra_wm8753_dai.codec_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,audio-codec' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_wm8753_dai.cpu_of_node = of_parse_phandle(np, +			"nvidia,i2s-controller", 0); +	if (!tegra_wm8753_dai.cpu_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,i2s-controller' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_wm8753_dai.platform_of_node = tegra_wm8753_dai.cpu_of_node; + +	ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); +	if (ret) +		goto err; + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		goto err_fini_utils; +	} + +	return 0; + +err_fini_utils: +	tegra_asoc_utils_fini(&machine->util_data); +err: +	return ret; +} + +static int tegra_wm8753_driver_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	struct tegra_wm8753 *machine = snd_soc_card_get_drvdata(card); + +	snd_soc_unregister_card(card); + +	tegra_asoc_utils_fini(&machine->util_data); + +	return 0; +} + +static const struct of_device_id tegra_wm8753_of_match[] = { +	{ .compatible = "nvidia,tegra-audio-wm8753", }, +	{}, +}; + +static struct platform_driver tegra_wm8753_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.pm = &snd_soc_pm_ops, +		.of_match_table = tegra_wm8753_of_match, +	}, +	.probe = tegra_wm8753_driver_probe, +	.remove = tegra_wm8753_driver_remove, +}; +module_platform_driver(tegra_wm8753_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra+WM8753 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm8753_of_match); diff --git a/sound/soc/tegra/tegra_wm8903.c b/sound/soc/tegra/tegra_wm8903.c new file mode 100644 index 00000000000..0939661df60 --- /dev/null +++ b/sound/soc/tegra/tegra_wm8903.c @@ -0,0 +1,403 @@ +/* + * tegra_wm8903.c - Tegra machine ASoC driver for boards using WM8903 codec. + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + *         graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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 <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> + +#include "../codecs/wm8903.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm8903" + +struct tegra_wm8903 { +	int gpio_spkr_en; +	int gpio_hp_det; +	int gpio_hp_mute; +	int gpio_int_mic_en; +	int gpio_ext_mic_en; +	struct tegra_asoc_utils_data util_data; +}; + +static int tegra_wm8903_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_codec *codec = codec_dai->codec; +	struct snd_soc_card *card = codec->card; +	struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); +	int srate, mclk; +	int err; + +	srate = params_rate(params); +	switch (srate) { +	case 64000: +	case 88200: +	case 96000: +		mclk = 128 * srate; +		break; +	default: +		mclk = 256 * srate; +		break; +	} +	/* FIXME: Codec only requires >= 3MHz if OSR==0 */ +	while (mclk < 6000000) +		mclk *= 2; + +	err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); +	if (err < 0) { +		dev_err(card->dev, "Can't configure clocks\n"); +		return err; +	} + +	err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, +					SND_SOC_CLOCK_IN); +	if (err < 0) { +		dev_err(card->dev, "codec_dai clock not set\n"); +		return err; +	} + +	return 0; +} + +static struct snd_soc_ops tegra_wm8903_ops = { +	.hw_params = tegra_wm8903_hw_params, +}; + +static struct snd_soc_jack tegra_wm8903_hp_jack; + +static struct snd_soc_jack_pin tegra_wm8903_hp_jack_pins[] = { +	{ +		.pin = "Headphone Jack", +		.mask = SND_JACK_HEADPHONE, +	}, +}; + +static struct snd_soc_jack_gpio tegra_wm8903_hp_jack_gpio = { +	.name = "headphone detect", +	.report = SND_JACK_HEADPHONE, +	.debounce_time = 150, +	.invert = 1, +}; + +static struct snd_soc_jack tegra_wm8903_mic_jack; + +static struct snd_soc_jack_pin tegra_wm8903_mic_jack_pins[] = { +	{ +		.pin = "Mic Jack", +		.mask = SND_JACK_MICROPHONE, +	}, +}; + +static int tegra_wm8903_event_int_spk(struct snd_soc_dapm_widget *w, +					struct snd_kcontrol *k, int event) +{ +	struct snd_soc_dapm_context *dapm = w->dapm; +	struct snd_soc_card *card = dapm->card; +	struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + +	if (!gpio_is_valid(machine->gpio_spkr_en)) +		return 0; + +	gpio_set_value_cansleep(machine->gpio_spkr_en, +				SND_SOC_DAPM_EVENT_ON(event)); + +	return 0; +} + +static int tegra_wm8903_event_hp(struct snd_soc_dapm_widget *w, +					struct snd_kcontrol *k, int event) +{ +	struct snd_soc_dapm_context *dapm = w->dapm; +	struct snd_soc_card *card = dapm->card; +	struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + +	if (!gpio_is_valid(machine->gpio_hp_mute)) +		return 0; + +	gpio_set_value_cansleep(machine->gpio_hp_mute, +				!SND_SOC_DAPM_EVENT_ON(event)); + +	return 0; +} + +static const struct snd_soc_dapm_widget tegra_wm8903_dapm_widgets[] = { +	SND_SOC_DAPM_SPK("Int Spk", tegra_wm8903_event_int_spk), +	SND_SOC_DAPM_HP("Headphone Jack", tegra_wm8903_event_hp), +	SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_kcontrol_new tegra_wm8903_controls[] = { +	SOC_DAPM_PIN_SWITCH("Int Spk"), +}; + +static int tegra_wm8903_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; +	struct snd_soc_card *card = codec->card; +	struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		tegra_wm8903_hp_jack_gpio.gpio = machine->gpio_hp_det; +		snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE, +				&tegra_wm8903_hp_jack); +		snd_soc_jack_add_pins(&tegra_wm8903_hp_jack, +					ARRAY_SIZE(tegra_wm8903_hp_jack_pins), +					tegra_wm8903_hp_jack_pins); +		snd_soc_jack_add_gpios(&tegra_wm8903_hp_jack, +					1, +					&tegra_wm8903_hp_jack_gpio); +	} + +	snd_soc_jack_new(codec, "Mic Jack", SND_JACK_MICROPHONE, +			 &tegra_wm8903_mic_jack); +	snd_soc_jack_add_pins(&tegra_wm8903_mic_jack, +			      ARRAY_SIZE(tegra_wm8903_mic_jack_pins), +			      tegra_wm8903_mic_jack_pins); +	wm8903_mic_detect(codec, &tegra_wm8903_mic_jack, SND_JACK_MICROPHONE, +				0); + +	snd_soc_dapm_force_enable_pin(dapm, "MICBIAS"); + +	return 0; +} + +static int tegra_wm8903_remove(struct snd_soc_card *card) +{ +	struct snd_soc_pcm_runtime *rtd = &(card->rtd[0]); +	struct snd_soc_dai *codec_dai = rtd->codec_dai; +	struct snd_soc_codec *codec = codec_dai->codec; +	struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + +	if (gpio_is_valid(machine->gpio_hp_det)) { +		snd_soc_jack_free_gpios(&tegra_wm8903_hp_jack, 1, +					&tegra_wm8903_hp_jack_gpio); +	} + +	wm8903_mic_detect(codec, NULL, 0, 0); + +	return 0; +} + +static struct snd_soc_dai_link tegra_wm8903_dai = { +	.name = "WM8903", +	.stream_name = "WM8903 PCM", +	.codec_dai_name = "wm8903-hifi", +	.init = tegra_wm8903_init, +	.ops = &tegra_wm8903_ops, +	.dai_fmt = SND_SOC_DAIFMT_I2S | +		   SND_SOC_DAIFMT_NB_NF | +		   SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_wm8903 = { +	.name = "tegra-wm8903", +	.owner = THIS_MODULE, +	.dai_link = &tegra_wm8903_dai, +	.num_links = 1, +	.remove = tegra_wm8903_remove, +	.controls = tegra_wm8903_controls, +	.num_controls = ARRAY_SIZE(tegra_wm8903_controls), +	.dapm_widgets = tegra_wm8903_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(tegra_wm8903_dapm_widgets), +	.fully_routed = true, +}; + +static int tegra_wm8903_driver_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct snd_soc_card *card = &snd_soc_tegra_wm8903; +	struct tegra_wm8903 *machine; +	int ret; + +	machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_wm8903), +			       GFP_KERNEL); +	if (!machine) { +		dev_err(&pdev->dev, "Can't allocate tegra_wm8903 struct\n"); +		return -ENOMEM; +	} + +	card->dev = &pdev->dev; +	platform_set_drvdata(pdev, card); +	snd_soc_card_set_drvdata(card, machine); + +	machine->gpio_spkr_en = of_get_named_gpio(np, "nvidia,spkr-en-gpios", +						  0); +	if (machine->gpio_spkr_en == -EPROBE_DEFER) +		return -EPROBE_DEFER; +	if (gpio_is_valid(machine->gpio_spkr_en)) { +		ret = devm_gpio_request_one(&pdev->dev, machine->gpio_spkr_en, +					    GPIOF_OUT_INIT_LOW, "spkr_en"); +		if (ret) { +			dev_err(card->dev, "cannot get spkr_en gpio\n"); +			return ret; +		} +	} + +	machine->gpio_hp_mute = of_get_named_gpio(np, "nvidia,hp-mute-gpios", +						  0); +	if (machine->gpio_hp_mute == -EPROBE_DEFER) +		return -EPROBE_DEFER; +	if (gpio_is_valid(machine->gpio_hp_mute)) { +		ret = devm_gpio_request_one(&pdev->dev, machine->gpio_hp_mute, +					    GPIOF_OUT_INIT_HIGH, "hp_mute"); +		if (ret) { +			dev_err(card->dev, "cannot get hp_mute gpio\n"); +			return ret; +		} +	} + +	machine->gpio_hp_det = of_get_named_gpio(np, "nvidia,hp-det-gpios", 0); +	if (machine->gpio_hp_det == -EPROBE_DEFER) +		return -EPROBE_DEFER; + +	machine->gpio_int_mic_en = of_get_named_gpio(np, +						"nvidia,int-mic-en-gpios", 0); +	if (machine->gpio_int_mic_en == -EPROBE_DEFER) +		return -EPROBE_DEFER; +	if (gpio_is_valid(machine->gpio_int_mic_en)) { +		/* Disable int mic; enable signal is active-high */ +		ret = devm_gpio_request_one(&pdev->dev, +					    machine->gpio_int_mic_en, +					    GPIOF_OUT_INIT_LOW, "int_mic_en"); +		if (ret) { +			dev_err(card->dev, "cannot get int_mic_en gpio\n"); +			return ret; +		} +	} + +	machine->gpio_ext_mic_en = of_get_named_gpio(np, +						"nvidia,ext-mic-en-gpios", 0); +	if (machine->gpio_ext_mic_en == -EPROBE_DEFER) +		return -EPROBE_DEFER; +	if (gpio_is_valid(machine->gpio_ext_mic_en)) { +		/* Enable ext mic; enable signal is active-low */ +		ret = devm_gpio_request_one(&pdev->dev, +					    machine->gpio_ext_mic_en, +					    GPIOF_OUT_INIT_LOW, "ext_mic_en"); +		if (ret) { +			dev_err(card->dev, "cannot get ext_mic_en gpio\n"); +			return ret; +		} +	} + +	ret = snd_soc_of_parse_card_name(card, "nvidia,model"); +	if (ret) +		goto err; + +	ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); +	if (ret) +		goto err; + +	tegra_wm8903_dai.codec_of_node = of_parse_phandle(np, +						"nvidia,audio-codec", 0); +	if (!tegra_wm8903_dai.codec_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,audio-codec' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_wm8903_dai.cpu_of_node = of_parse_phandle(np, +			"nvidia,i2s-controller", 0); +	if (!tegra_wm8903_dai.cpu_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,i2s-controller' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	tegra_wm8903_dai.platform_of_node = tegra_wm8903_dai.cpu_of_node; + +	ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); +	if (ret) +		goto err; + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		goto err_fini_utils; +	} + +	return 0; + +err_fini_utils: +	tegra_asoc_utils_fini(&machine->util_data); +err: +	return ret; +} + +static int tegra_wm8903_driver_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	struct tegra_wm8903 *machine = snd_soc_card_get_drvdata(card); + +	snd_soc_unregister_card(card); + +	tegra_asoc_utils_fini(&machine->util_data); + +	return 0; +} + +static const struct of_device_id tegra_wm8903_of_match[] = { +	{ .compatible = "nvidia,tegra-audio-wm8903", }, +	{}, +}; + +static struct platform_driver tegra_wm8903_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.pm = &snd_soc_pm_ops, +		.of_match_table = tegra_wm8903_of_match, +	}, +	.probe = tegra_wm8903_driver_probe, +	.remove = tegra_wm8903_driver_remove, +}; +module_platform_driver(tegra_wm8903_driver); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra+WM8903 machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_wm8903_of_match); diff --git a/sound/soc/tegra/tegra_wm9712.c b/sound/soc/tegra/tegra_wm9712.c new file mode 100644 index 00000000000..de087ee3458 --- /dev/null +++ b/sound/soc/tegra/tegra_wm9712.c @@ -0,0 +1,183 @@ +/* + * 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> + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-wm9712" + +struct tegra_wm9712 { +	struct platform_device *codec; +	struct tegra_asoc_utils_data util_data; +}; + +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; + +	return snd_soc_dapm_force_enable_pin(dapm, "Mic Bias"); +} + +static struct snd_soc_dai_link tegra_wm9712_dai = { +	.name = "AC97 HiFi", +	.stream_name = "AC97 HiFi", +	.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; + +	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 = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); +	if (ret) +		goto codec_unregister; + +	ret = tegra_asoc_utils_set_ac97_rate(&machine->util_data); +	if (ret) +		goto asoc_utils_fini; + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		goto asoc_utils_fini; +	} + +	return 0; + +asoc_utils_fini: +	tegra_asoc_utils_fini(&machine->util_data); +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); + +	tegra_asoc_utils_fini(&machine->util_data); + +	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); diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c new file mode 100644 index 00000000000..734bfcd2114 --- /dev/null +++ b/sound/soc/tegra/trimslice.c @@ -0,0 +1,208 @@ +/* + * trimslice.c - TrimSlice machine ASoC driver + * + * Copyright (C) 2011 - CompuLab, Ltd. + * Author: Mike Rapoport <mike@compulab.co.il> + * + * Based on code copyright/by: + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010-2011 - NVIDIA, 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. + * + * 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 <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/jack.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "../codecs/tlv320aic23.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-trimslice" + +struct tegra_trimslice { +	struct tegra_asoc_utils_data util_data; +}; + +static int trimslice_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->codec_dai; +	struct snd_soc_codec *codec = codec_dai->codec; +	struct snd_soc_card *card = codec->card; +	struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); +	int srate, mclk; +	int err; + +	srate = params_rate(params); +	mclk = 128 * srate; + +	err = tegra_asoc_utils_set_rate(&trimslice->util_data, srate, mclk); +	if (err < 0) { +		dev_err(card->dev, "Can't configure clocks\n"); +		return err; +	} + +	err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, +					SND_SOC_CLOCK_IN); +	if (err < 0) { +		dev_err(card->dev, "codec_dai clock not set\n"); +		return err; +	} + +	return 0; +} + +static struct snd_soc_ops trimslice_asoc_ops = { +	.hw_params = trimslice_asoc_hw_params, +}; + +static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { +	SND_SOC_DAPM_HP("Line Out", NULL), +	SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route trimslice_audio_map[] = { +	{"Line Out", NULL, "LOUT"}, +	{"Line Out", NULL, "ROUT"}, + +	{"LLINEIN", NULL, "Line In"}, +	{"RLINEIN", NULL, "Line In"}, +}; + +static struct snd_soc_dai_link trimslice_tlv320aic23_dai = { +	.name = "TLV320AIC23", +	.stream_name = "AIC23", +	.codec_dai_name = "tlv320aic23-hifi", +	.ops = &trimslice_asoc_ops, +	.dai_fmt = SND_SOC_DAIFMT_I2S | +		   SND_SOC_DAIFMT_NB_NF | +		   SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_trimslice = { +	.name = "tegra-trimslice", +	.owner = THIS_MODULE, +	.dai_link = &trimslice_tlv320aic23_dai, +	.num_links = 1, + +	.dapm_widgets = trimslice_dapm_widgets, +	.num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), +	.dapm_routes = trimslice_audio_map, +	.num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), +	.fully_routed = true, +}; + +static int tegra_snd_trimslice_probe(struct platform_device *pdev) +{ +	struct device_node *np = pdev->dev.of_node; +	struct snd_soc_card *card = &snd_soc_trimslice; +	struct tegra_trimslice *trimslice; +	int ret; + +	trimslice = devm_kzalloc(&pdev->dev, sizeof(struct tegra_trimslice), +				 GFP_KERNEL); +	if (!trimslice) { +		dev_err(&pdev->dev, "Can't allocate tegra_trimslice\n"); +		return -ENOMEM; +	} + +	card->dev = &pdev->dev; +	platform_set_drvdata(pdev, card); +	snd_soc_card_set_drvdata(card, trimslice); + +	trimslice_tlv320aic23_dai.codec_of_node = of_parse_phandle(np, +			"nvidia,audio-codec", 0); +	if (!trimslice_tlv320aic23_dai.codec_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,audio-codec' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	trimslice_tlv320aic23_dai.cpu_of_node = of_parse_phandle(np, +			"nvidia,i2s-controller", 0); +	if (!trimslice_tlv320aic23_dai.cpu_of_node) { +		dev_err(&pdev->dev, +			"Property 'nvidia,i2s-controller' missing or invalid\n"); +		ret = -EINVAL; +		goto err; +	} + +	trimslice_tlv320aic23_dai.platform_of_node = +			trimslice_tlv320aic23_dai.cpu_of_node; + +	ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev); +	if (ret) +		goto err; + +	ret = snd_soc_register_card(card); +	if (ret) { +		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", +			ret); +		goto err_fini_utils; +	} + +	return 0; + +err_fini_utils: +	tegra_asoc_utils_fini(&trimslice->util_data); +err: +	return ret; +} + +static int tegra_snd_trimslice_remove(struct platform_device *pdev) +{ +	struct snd_soc_card *card = platform_get_drvdata(pdev); +	struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); + +	snd_soc_unregister_card(card); + +	tegra_asoc_utils_fini(&trimslice->util_data); + +	return 0; +} + +static const struct of_device_id trimslice_of_match[] = { +	{ .compatible = "nvidia,tegra-audio-trimslice", }, +	{}, +}; +MODULE_DEVICE_TABLE(of, trimslice_of_match); + +static struct platform_driver tegra_snd_trimslice_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +		.of_match_table = trimslice_of_match, +	}, +	.probe = tegra_snd_trimslice_probe, +	.remove = tegra_snd_trimslice_remove, +}; +module_platform_driver(tegra_snd_trimslice_driver); + +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); +MODULE_DESCRIPTION("Trimslice machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME);  | 
