18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * tegra20_ac97.c - Tegra20 AC97 platform driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Partly based on code copyright/by:
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Copyright (c) 2011,2012 Toradex Inc.
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/clk.h>
138c2ecf20Sopenharmony_ci#include <linux/delay.h>
148c2ecf20Sopenharmony_ci#include <linux/device.h>
158c2ecf20Sopenharmony_ci#include <linux/gpio.h>
168c2ecf20Sopenharmony_ci#include <linux/io.h>
178c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
188c2ecf20Sopenharmony_ci#include <linux/module.h>
198c2ecf20Sopenharmony_ci#include <linux/of.h>
208c2ecf20Sopenharmony_ci#include <linux/of_gpio.h>
218c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
228c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h>
238c2ecf20Sopenharmony_ci#include <linux/regmap.h>
248c2ecf20Sopenharmony_ci#include <linux/slab.h>
258c2ecf20Sopenharmony_ci#include <sound/core.h>
268c2ecf20Sopenharmony_ci#include <sound/pcm.h>
278c2ecf20Sopenharmony_ci#include <sound/pcm_params.h>
288c2ecf20Sopenharmony_ci#include <sound/soc.h>
298c2ecf20Sopenharmony_ci#include <sound/dmaengine_pcm.h>
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#include "tegra20_ac97.h"
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define DRV_NAME "tegra20-ac97"
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic struct tegra20_ac97 *workdata;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistatic void tegra20_ac97_codec_reset(struct snd_ac97 *ac97)
388c2ecf20Sopenharmony_ci{
398c2ecf20Sopenharmony_ci	u32 readback;
408c2ecf20Sopenharmony_ci	unsigned long timeout;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	/* reset line is not driven by DAC pad group, have to toggle GPIO */
438c2ecf20Sopenharmony_ci	gpio_set_value(workdata->reset_gpio, 0);
448c2ecf20Sopenharmony_ci	udelay(2);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	gpio_set_value(workdata->reset_gpio, 1);
478c2ecf20Sopenharmony_ci	udelay(2);
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	do {
528c2ecf20Sopenharmony_ci		regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
538c2ecf20Sopenharmony_ci		if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
548c2ecf20Sopenharmony_ci			break;
558c2ecf20Sopenharmony_ci		usleep_range(1000, 2000);
568c2ecf20Sopenharmony_ci	} while (!time_after(jiffies, timeout));
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	u32 readback;
628c2ecf20Sopenharmony_ci	unsigned long timeout;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	/*
658c2ecf20Sopenharmony_ci	 * although sync line is driven by the DAC pad group warm reset using
668c2ecf20Sopenharmony_ci	 * the controller cmd is not working, have to toggle sync line
678c2ecf20Sopenharmony_ci	 * manually.
688c2ecf20Sopenharmony_ci	 */
698c2ecf20Sopenharmony_ci	gpio_request(workdata->sync_gpio, "codec-sync");
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	gpio_direction_output(workdata->sync_gpio, 1);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	udelay(2);
748c2ecf20Sopenharmony_ci	gpio_set_value(workdata->sync_gpio, 0);
758c2ecf20Sopenharmony_ci	udelay(2);
768c2ecf20Sopenharmony_ci	gpio_free(workdata->sync_gpio);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	do {
818c2ecf20Sopenharmony_ci		regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
828c2ecf20Sopenharmony_ci		if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)
838c2ecf20Sopenharmony_ci			break;
848c2ecf20Sopenharmony_ci		usleep_range(1000, 2000);
858c2ecf20Sopenharmony_ci	} while (!time_after(jiffies, timeout));
868c2ecf20Sopenharmony_ci}
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_cistatic unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd,
898c2ecf20Sopenharmony_ci					      unsigned short reg)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	u32 readback;
928c2ecf20Sopenharmony_ci	unsigned long timeout;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
958c2ecf20Sopenharmony_ci		     (((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
968c2ecf20Sopenharmony_ci		      TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
978c2ecf20Sopenharmony_ci		     TEGRA20_AC97_CMD_BUSY);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	do {
1028c2ecf20Sopenharmony_ci		regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);
1038c2ecf20Sopenharmony_ci		if (readback & TEGRA20_AC97_STATUS1_STA_VALID1)
1048c2ecf20Sopenharmony_ci			break;
1058c2ecf20Sopenharmony_ci		usleep_range(1000, 2000);
1068c2ecf20Sopenharmony_ci	} while (!time_after(jiffies, timeout));
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >>
1098c2ecf20Sopenharmony_ci		TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT);
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd,
1138c2ecf20Sopenharmony_ci				     unsigned short reg, unsigned short val)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	u32 readback;
1168c2ecf20Sopenharmony_ci	unsigned long timeout;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	regmap_write(workdata->regmap, TEGRA20_AC97_CMD,
1198c2ecf20Sopenharmony_ci		     ((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &
1208c2ecf20Sopenharmony_ci		      TEGRA20_AC97_CMD_CMD_ADDR_MASK) |
1218c2ecf20Sopenharmony_ci		     ((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) &
1228c2ecf20Sopenharmony_ci		      TEGRA20_AC97_CMD_CMD_DATA_MASK) |
1238c2ecf20Sopenharmony_ci		     TEGRA20_AC97_CMD_BUSY);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	timeout = jiffies + msecs_to_jiffies(100);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	do {
1288c2ecf20Sopenharmony_ci		regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback);
1298c2ecf20Sopenharmony_ci		if (!(readback & TEGRA20_AC97_CMD_BUSY))
1308c2ecf20Sopenharmony_ci			break;
1318c2ecf20Sopenharmony_ci		usleep_range(1000, 2000);
1328c2ecf20Sopenharmony_ci	} while (!time_after(jiffies, timeout));
1338c2ecf20Sopenharmony_ci}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_cistatic struct snd_ac97_bus_ops tegra20_ac97_ops = {
1368c2ecf20Sopenharmony_ci	.read		= tegra20_ac97_codec_read,
1378c2ecf20Sopenharmony_ci	.write		= tegra20_ac97_codec_write,
1388c2ecf20Sopenharmony_ci	.reset		= tegra20_ac97_codec_reset,
1398c2ecf20Sopenharmony_ci	.warm_reset	= tegra20_ac97_codec_warm_reset,
1408c2ecf20Sopenharmony_ci};
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97)
1438c2ecf20Sopenharmony_ci{
1448c2ecf20Sopenharmony_ci	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
1458c2ecf20Sopenharmony_ci			   TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN,
1468c2ecf20Sopenharmony_ci			   TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
1498c2ecf20Sopenharmony_ci			   TEGRA20_AC97_CTRL_PCM_DAC_EN |
1508c2ecf20Sopenharmony_ci			   TEGRA20_AC97_CTRL_STM_EN,
1518c2ecf20Sopenharmony_ci			   TEGRA20_AC97_CTRL_PCM_DAC_EN |
1528c2ecf20Sopenharmony_ci			   TEGRA20_AC97_CTRL_STM_EN);
1538c2ecf20Sopenharmony_ci}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_cistatic inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97)
1568c2ecf20Sopenharmony_ci{
1578c2ecf20Sopenharmony_ci	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
1588c2ecf20Sopenharmony_ci			   TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,
1618c2ecf20Sopenharmony_ci			   TEGRA20_AC97_CTRL_PCM_DAC_EN, 0);
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
1678c2ecf20Sopenharmony_ci			   TEGRA20_AC97_FIFO_SCR_REC_FULL_EN,
1688c2ecf20Sopenharmony_ci			   TEGRA20_AC97_FIFO_SCR_REC_FULL_EN);
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,
1748c2ecf20Sopenharmony_ci			   TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0);
1758c2ecf20Sopenharmony_ci}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cistatic int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd,
1788c2ecf20Sopenharmony_ci				struct snd_soc_dai *dai)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	switch (cmd) {
1838c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
1848c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
1858c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_RESUME:
1868c2ecf20Sopenharmony_ci		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
1878c2ecf20Sopenharmony_ci			tegra20_ac97_start_playback(ac97);
1888c2ecf20Sopenharmony_ci		else
1898c2ecf20Sopenharmony_ci			tegra20_ac97_start_capture(ac97);
1908c2ecf20Sopenharmony_ci		break;
1918c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
1928c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
1938c2ecf20Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
1948c2ecf20Sopenharmony_ci		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
1958c2ecf20Sopenharmony_ci			tegra20_ac97_stop_playback(ac97);
1968c2ecf20Sopenharmony_ci		else
1978c2ecf20Sopenharmony_ci			tegra20_ac97_stop_capture(ac97);
1988c2ecf20Sopenharmony_ci		break;
1998c2ecf20Sopenharmony_ci	default:
2008c2ecf20Sopenharmony_ci		return -EINVAL;
2018c2ecf20Sopenharmony_ci	}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	return 0;
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cistatic const struct snd_soc_dai_ops tegra20_ac97_dai_ops = {
2078c2ecf20Sopenharmony_ci	.trigger	= tegra20_ac97_trigger,
2088c2ecf20Sopenharmony_ci};
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic int tegra20_ac97_probe(struct snd_soc_dai *dai)
2118c2ecf20Sopenharmony_ci{
2128c2ecf20Sopenharmony_ci	struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	dai->capture_dma_data = &ac97->capture_dma_data;
2158c2ecf20Sopenharmony_ci	dai->playback_dma_data = &ac97->playback_dma_data;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	return 0;
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_cistatic struct snd_soc_dai_driver tegra20_ac97_dai = {
2218c2ecf20Sopenharmony_ci	.name = "tegra-ac97-pcm",
2228c2ecf20Sopenharmony_ci	.probe = tegra20_ac97_probe,
2238c2ecf20Sopenharmony_ci	.playback = {
2248c2ecf20Sopenharmony_ci		.stream_name = "PCM Playback",
2258c2ecf20Sopenharmony_ci		.channels_min = 2,
2268c2ecf20Sopenharmony_ci		.channels_max = 2,
2278c2ecf20Sopenharmony_ci		.rates = SNDRV_PCM_RATE_8000_48000,
2288c2ecf20Sopenharmony_ci		.formats = SNDRV_PCM_FMTBIT_S16_LE,
2298c2ecf20Sopenharmony_ci	},
2308c2ecf20Sopenharmony_ci	.capture = {
2318c2ecf20Sopenharmony_ci		.stream_name = "PCM Capture",
2328c2ecf20Sopenharmony_ci		.channels_min = 2,
2338c2ecf20Sopenharmony_ci		.channels_max = 2,
2348c2ecf20Sopenharmony_ci		.rates = SNDRV_PCM_RATE_8000_48000,
2358c2ecf20Sopenharmony_ci		.formats = SNDRV_PCM_FMTBIT_S16_LE,
2368c2ecf20Sopenharmony_ci	},
2378c2ecf20Sopenharmony_ci	.ops = &tegra20_ac97_dai_ops,
2388c2ecf20Sopenharmony_ci};
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_cistatic const struct snd_soc_component_driver tegra20_ac97_component = {
2418c2ecf20Sopenharmony_ci	.name		= DRV_NAME,
2428c2ecf20Sopenharmony_ci};
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg)
2458c2ecf20Sopenharmony_ci{
2468c2ecf20Sopenharmony_ci	switch (reg) {
2478c2ecf20Sopenharmony_ci	case TEGRA20_AC97_CTRL:
2488c2ecf20Sopenharmony_ci	case TEGRA20_AC97_CMD:
2498c2ecf20Sopenharmony_ci	case TEGRA20_AC97_STATUS1:
2508c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO1_SCR:
2518c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO_TX1:
2528c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO_RX1:
2538c2ecf20Sopenharmony_ci		return true;
2548c2ecf20Sopenharmony_ci	default:
2558c2ecf20Sopenharmony_ci		break;
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	return false;
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg)
2628c2ecf20Sopenharmony_ci{
2638c2ecf20Sopenharmony_ci	switch (reg) {
2648c2ecf20Sopenharmony_ci	case TEGRA20_AC97_STATUS1:
2658c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO1_SCR:
2668c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO_TX1:
2678c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO_RX1:
2688c2ecf20Sopenharmony_ci		return true;
2698c2ecf20Sopenharmony_ci	default:
2708c2ecf20Sopenharmony_ci		break;
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return false;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	switch (reg) {
2798c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO_TX1:
2808c2ecf20Sopenharmony_ci	case TEGRA20_AC97_FIFO_RX1:
2818c2ecf20Sopenharmony_ci		return true;
2828c2ecf20Sopenharmony_ci	default:
2838c2ecf20Sopenharmony_ci		break;
2848c2ecf20Sopenharmony_ci	}
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	return false;
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_cistatic const struct regmap_config tegra20_ac97_regmap_config = {
2908c2ecf20Sopenharmony_ci	.reg_bits = 32,
2918c2ecf20Sopenharmony_ci	.reg_stride = 4,
2928c2ecf20Sopenharmony_ci	.val_bits = 32,
2938c2ecf20Sopenharmony_ci	.max_register = TEGRA20_AC97_FIFO_RX1,
2948c2ecf20Sopenharmony_ci	.writeable_reg = tegra20_ac97_wr_rd_reg,
2958c2ecf20Sopenharmony_ci	.readable_reg = tegra20_ac97_wr_rd_reg,
2968c2ecf20Sopenharmony_ci	.volatile_reg = tegra20_ac97_volatile_reg,
2978c2ecf20Sopenharmony_ci	.precious_reg = tegra20_ac97_precious_reg,
2988c2ecf20Sopenharmony_ci	.cache_type = REGCACHE_FLAT,
2998c2ecf20Sopenharmony_ci};
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_cistatic int tegra20_ac97_platform_probe(struct platform_device *pdev)
3028c2ecf20Sopenharmony_ci{
3038c2ecf20Sopenharmony_ci	struct tegra20_ac97 *ac97;
3048c2ecf20Sopenharmony_ci	struct resource *mem;
3058c2ecf20Sopenharmony_ci	void __iomem *regs;
3068c2ecf20Sopenharmony_ci	int ret = 0;
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97),
3098c2ecf20Sopenharmony_ci			    GFP_KERNEL);
3108c2ecf20Sopenharmony_ci	if (!ac97) {
3118c2ecf20Sopenharmony_ci		ret = -ENOMEM;
3128c2ecf20Sopenharmony_ci		goto err;
3138c2ecf20Sopenharmony_ci	}
3148c2ecf20Sopenharmony_ci	dev_set_drvdata(&pdev->dev, ac97);
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL);
3178c2ecf20Sopenharmony_ci	if (IS_ERR(ac97->clk_ac97)) {
3188c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Can't retrieve ac97 clock\n");
3198c2ecf20Sopenharmony_ci		ret = PTR_ERR(ac97->clk_ac97);
3208c2ecf20Sopenharmony_ci		goto err;
3218c2ecf20Sopenharmony_ci	}
3228c2ecf20Sopenharmony_ci
3238c2ecf20Sopenharmony_ci	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
3248c2ecf20Sopenharmony_ci	regs = devm_ioremap_resource(&pdev->dev, mem);
3258c2ecf20Sopenharmony_ci	if (IS_ERR(regs)) {
3268c2ecf20Sopenharmony_ci		ret = PTR_ERR(regs);
3278c2ecf20Sopenharmony_ci		goto err_clk_put;
3288c2ecf20Sopenharmony_ci	}
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
3318c2ecf20Sopenharmony_ci					    &tegra20_ac97_regmap_config);
3328c2ecf20Sopenharmony_ci	if (IS_ERR(ac97->regmap)) {
3338c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "regmap init failed\n");
3348c2ecf20Sopenharmony_ci		ret = PTR_ERR(ac97->regmap);
3358c2ecf20Sopenharmony_ci		goto err_clk_put;
3368c2ecf20Sopenharmony_ci	}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	ac97->reset_gpio = of_get_named_gpio(pdev->dev.of_node,
3398c2ecf20Sopenharmony_ci					     "nvidia,codec-reset-gpio", 0);
3408c2ecf20Sopenharmony_ci	if (gpio_is_valid(ac97->reset_gpio)) {
3418c2ecf20Sopenharmony_ci		ret = devm_gpio_request_one(&pdev->dev, ac97->reset_gpio,
3428c2ecf20Sopenharmony_ci					    GPIOF_OUT_INIT_HIGH, "codec-reset");
3438c2ecf20Sopenharmony_ci		if (ret) {
3448c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "could not get codec-reset GPIO\n");
3458c2ecf20Sopenharmony_ci			goto err_clk_put;
3468c2ecf20Sopenharmony_ci		}
3478c2ecf20Sopenharmony_ci	} else {
3488c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "no codec-reset GPIO supplied\n");
3498c2ecf20Sopenharmony_ci		goto err_clk_put;
3508c2ecf20Sopenharmony_ci	}
3518c2ecf20Sopenharmony_ci
3528c2ecf20Sopenharmony_ci	ac97->sync_gpio = of_get_named_gpio(pdev->dev.of_node,
3538c2ecf20Sopenharmony_ci					    "nvidia,codec-sync-gpio", 0);
3548c2ecf20Sopenharmony_ci	if (!gpio_is_valid(ac97->sync_gpio)) {
3558c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "no codec-sync GPIO supplied\n");
3568c2ecf20Sopenharmony_ci		goto err_clk_put;
3578c2ecf20Sopenharmony_ci	}
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1;
3608c2ecf20Sopenharmony_ci	ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
3618c2ecf20Sopenharmony_ci	ac97->capture_dma_data.maxburst = 4;
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_ci	ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1;
3648c2ecf20Sopenharmony_ci	ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
3658c2ecf20Sopenharmony_ci	ac97->playback_dma_data.maxburst = 4;
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci	ret = clk_prepare_enable(ac97->clk_ac97);
3688c2ecf20Sopenharmony_ci	if (ret) {
3698c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "clk_enable failed: %d\n", ret);
3708c2ecf20Sopenharmony_ci		goto err_clk_put;
3718c2ecf20Sopenharmony_ci	}
3728c2ecf20Sopenharmony_ci
3738c2ecf20Sopenharmony_ci	ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops);
3748c2ecf20Sopenharmony_ci	if (ret) {
3758c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
3768c2ecf20Sopenharmony_ci		goto err_clk_disable_unprepare;
3778c2ecf20Sopenharmony_ci	}
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	ret = snd_soc_register_component(&pdev->dev, &tegra20_ac97_component,
3808c2ecf20Sopenharmony_ci					 &tegra20_ac97_dai, 1);
3818c2ecf20Sopenharmony_ci	if (ret) {
3828c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
3838c2ecf20Sopenharmony_ci		ret = -ENOMEM;
3848c2ecf20Sopenharmony_ci		goto err_clk_disable_unprepare;
3858c2ecf20Sopenharmony_ci	}
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	ret = tegra_pcm_platform_register(&pdev->dev);
3888c2ecf20Sopenharmony_ci	if (ret) {
3898c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
3908c2ecf20Sopenharmony_ci		goto err_unregister_component;
3918c2ecf20Sopenharmony_ci	}
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	/* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */
3948c2ecf20Sopenharmony_ci	workdata = ac97;
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	return 0;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_cierr_unregister_component:
3998c2ecf20Sopenharmony_ci	snd_soc_unregister_component(&pdev->dev);
4008c2ecf20Sopenharmony_cierr_clk_disable_unprepare:
4018c2ecf20Sopenharmony_ci	clk_disable_unprepare(ac97->clk_ac97);
4028c2ecf20Sopenharmony_cierr_clk_put:
4038c2ecf20Sopenharmony_cierr:
4048c2ecf20Sopenharmony_ci	snd_soc_set_ac97_ops(NULL);
4058c2ecf20Sopenharmony_ci	return ret;
4068c2ecf20Sopenharmony_ci}
4078c2ecf20Sopenharmony_ci
4088c2ecf20Sopenharmony_cistatic int tegra20_ac97_platform_remove(struct platform_device *pdev)
4098c2ecf20Sopenharmony_ci{
4108c2ecf20Sopenharmony_ci	struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev);
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	tegra_pcm_platform_unregister(&pdev->dev);
4138c2ecf20Sopenharmony_ci	snd_soc_unregister_component(&pdev->dev);
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	clk_disable_unprepare(ac97->clk_ac97);
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	snd_soc_set_ac97_ops(NULL);
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	return 0;
4208c2ecf20Sopenharmony_ci}
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_cistatic const struct of_device_id tegra20_ac97_of_match[] = {
4238c2ecf20Sopenharmony_ci	{ .compatible = "nvidia,tegra20-ac97", },
4248c2ecf20Sopenharmony_ci	{},
4258c2ecf20Sopenharmony_ci};
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_cistatic struct platform_driver tegra20_ac97_driver = {
4288c2ecf20Sopenharmony_ci	.driver = {
4298c2ecf20Sopenharmony_ci		.name = DRV_NAME,
4308c2ecf20Sopenharmony_ci		.of_match_table = tegra20_ac97_of_match,
4318c2ecf20Sopenharmony_ci	},
4328c2ecf20Sopenharmony_ci	.probe = tegra20_ac97_platform_probe,
4338c2ecf20Sopenharmony_ci	.remove = tegra20_ac97_platform_remove,
4348c2ecf20Sopenharmony_ci};
4358c2ecf20Sopenharmony_cimodule_platform_driver(tegra20_ac97_driver);
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lucas Stach");
4388c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Tegra20 AC97 ASoC driver");
4398c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
4408c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
4418c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, tegra20_ac97_of_match);
442