brintos

brintos / linux-shallow public Read only

0
0
Text · 11.4 KiB · 08c58e8 Raw
460 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * tegra20_ac97.c - Tegra20 AC97 platform driver4 *5 * Copyright (c) 2012 Lucas Stach <dev@lynxeye.de>6 *7 * Partly based on code copyright/by:8 *9 * Copyright (c) 2011,2012 Toradex Inc.10 */11 12#include <linux/clk.h>13#include <linux/delay.h>14#include <linux/device.h>15#include <linux/gpio/consumer.h>16#include <linux/io.h>17#include <linux/jiffies.h>18#include <linux/module.h>19#include <linux/of.h>20#include <linux/platform_device.h>21#include <linux/regmap.h>22#include <linux/reset.h>23#include <linux/slab.h>24#include <sound/core.h>25#include <sound/pcm.h>26#include <sound/pcm_params.h>27#include <sound/soc.h>28#include <sound/dmaengine_pcm.h>29 30#include "tegra20_ac97.h"31 32#define DRV_NAME "tegra20-ac97"33 34static struct tegra20_ac97 *workdata;35 36static void tegra20_ac97_codec_reset(struct snd_ac97 *ac97)37{38	u32 readback;39	unsigned long timeout;40 41	/*42	 * The reset line is not driven by DAC pad group, have to toggle GPIO.43	 * The RESET line is active low but this is abstracted by the GPIO44	 * library.45	 */46	gpiod_set_value(workdata->reset_gpio, 1);47	udelay(2);48 49	gpiod_set_value(workdata->reset_gpio, 0);50	udelay(2);51 52	timeout = jiffies + msecs_to_jiffies(100);53 54	do {55		regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);56		if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)57			break;58		usleep_range(1000, 2000);59	} while (!time_after(jiffies, timeout));60}61 62static void tegra20_ac97_codec_warm_reset(struct snd_ac97 *ac97)63{64	u32 readback;65	unsigned long timeout;66 67	/*68	 * although sync line is driven by the DAC pad group warm reset using69	 * the controller cmd is not working, have to toggle sync line70	 * manually.71	 */72	gpiod_direction_output(workdata->sync_gpio, 1);73	udelay(2);74	gpiod_set_value(workdata->sync_gpio, 0);75	udelay(2);76 77	timeout = jiffies + msecs_to_jiffies(100);78 79	do {80		regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);81		if (readback & TEGRA20_AC97_STATUS1_CODEC1_RDY)82			break;83		usleep_range(1000, 2000);84	} while (!time_after(jiffies, timeout));85}86 87static unsigned short tegra20_ac97_codec_read(struct snd_ac97 *ac97_snd,88					      unsigned short reg)89{90	u32 readback;91	unsigned long timeout;92 93	regmap_write(workdata->regmap, TEGRA20_AC97_CMD,94		     (((reg | 0x80) << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &95		      TEGRA20_AC97_CMD_CMD_ADDR_MASK) |96		     TEGRA20_AC97_CMD_BUSY);97 98	timeout = jiffies + msecs_to_jiffies(100);99 100	do {101		regmap_read(workdata->regmap, TEGRA20_AC97_STATUS1, &readback);102		if (readback & TEGRA20_AC97_STATUS1_STA_VALID1)103			break;104		usleep_range(1000, 2000);105	} while (!time_after(jiffies, timeout));106 107	return ((readback & TEGRA20_AC97_STATUS1_STA_DATA1_MASK) >>108		TEGRA20_AC97_STATUS1_STA_DATA1_SHIFT);109}110 111static void tegra20_ac97_codec_write(struct snd_ac97 *ac97_snd,112				     unsigned short reg, unsigned short val)113{114	u32 readback;115	unsigned long timeout;116 117	regmap_write(workdata->regmap, TEGRA20_AC97_CMD,118		     ((reg << TEGRA20_AC97_CMD_CMD_ADDR_SHIFT) &119		      TEGRA20_AC97_CMD_CMD_ADDR_MASK) |120		     ((val << TEGRA20_AC97_CMD_CMD_DATA_SHIFT) &121		      TEGRA20_AC97_CMD_CMD_DATA_MASK) |122		     TEGRA20_AC97_CMD_BUSY);123 124	timeout = jiffies + msecs_to_jiffies(100);125 126	do {127		regmap_read(workdata->regmap, TEGRA20_AC97_CMD, &readback);128		if (!(readback & TEGRA20_AC97_CMD_BUSY))129			break;130		usleep_range(1000, 2000);131	} while (!time_after(jiffies, timeout));132}133 134static struct snd_ac97_bus_ops tegra20_ac97_ops = {135	.read		= tegra20_ac97_codec_read,136	.write		= tegra20_ac97_codec_write,137	.reset		= tegra20_ac97_codec_reset,138	.warm_reset	= tegra20_ac97_codec_warm_reset,139};140 141static inline void tegra20_ac97_start_playback(struct tegra20_ac97 *ac97)142{143	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,144			   TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN,145			   TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN);146 147	regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,148			   TEGRA20_AC97_CTRL_PCM_DAC_EN |149			   TEGRA20_AC97_CTRL_STM_EN,150			   TEGRA20_AC97_CTRL_PCM_DAC_EN |151			   TEGRA20_AC97_CTRL_STM_EN);152}153 154static inline void tegra20_ac97_stop_playback(struct tegra20_ac97 *ac97)155{156	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,157			   TEGRA20_AC97_FIFO_SCR_PB_QRT_MT_EN, 0);158 159	regmap_update_bits(ac97->regmap, TEGRA20_AC97_CTRL,160			   TEGRA20_AC97_CTRL_PCM_DAC_EN, 0);161}162 163static inline void tegra20_ac97_start_capture(struct tegra20_ac97 *ac97)164{165	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,166			   TEGRA20_AC97_FIFO_SCR_REC_FULL_EN,167			   TEGRA20_AC97_FIFO_SCR_REC_FULL_EN);168}169 170static inline void tegra20_ac97_stop_capture(struct tegra20_ac97 *ac97)171{172	regmap_update_bits(ac97->regmap, TEGRA20_AC97_FIFO1_SCR,173			   TEGRA20_AC97_FIFO_SCR_REC_FULL_EN, 0);174}175 176static int tegra20_ac97_trigger(struct snd_pcm_substream *substream, int cmd,177				struct snd_soc_dai *dai)178{179	struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);180 181	switch (cmd) {182	case SNDRV_PCM_TRIGGER_START:183	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:184	case SNDRV_PCM_TRIGGER_RESUME:185		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)186			tegra20_ac97_start_playback(ac97);187		else188			tegra20_ac97_start_capture(ac97);189		break;190	case SNDRV_PCM_TRIGGER_STOP:191	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:192	case SNDRV_PCM_TRIGGER_SUSPEND:193		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)194			tegra20_ac97_stop_playback(ac97);195		else196			tegra20_ac97_stop_capture(ac97);197		break;198	default:199		return -EINVAL;200	}201 202	return 0;203}204 205static int tegra20_ac97_probe(struct snd_soc_dai *dai)206{207	struct tegra20_ac97 *ac97 = snd_soc_dai_get_drvdata(dai);208 209	snd_soc_dai_init_dma_data(dai,	&ac97->playback_dma_data,210					&ac97->capture_dma_data);211 212	return 0;213}214 215static const struct snd_soc_dai_ops tegra20_ac97_dai_ops = {216	.probe		= tegra20_ac97_probe,217	.trigger	= tegra20_ac97_trigger,218};219 220static struct snd_soc_dai_driver tegra20_ac97_dai = {221	.name = "tegra-ac97-pcm",222	.playback = {223		.stream_name = "PCM Playback",224		.channels_min = 2,225		.channels_max = 2,226		.rates = SNDRV_PCM_RATE_8000_48000,227		.formats = SNDRV_PCM_FMTBIT_S16_LE,228	},229	.capture = {230		.stream_name = "PCM Capture",231		.channels_min = 2,232		.channels_max = 2,233		.rates = SNDRV_PCM_RATE_8000_48000,234		.formats = SNDRV_PCM_FMTBIT_S16_LE,235	},236	.ops = &tegra20_ac97_dai_ops,237};238 239static const struct snd_soc_component_driver tegra20_ac97_component = {240	.name			= DRV_NAME,241	.legacy_dai_naming	= 1,242};243 244static bool tegra20_ac97_wr_rd_reg(struct device *dev, unsigned int reg)245{246	switch (reg) {247	case TEGRA20_AC97_CTRL:248	case TEGRA20_AC97_CMD:249	case TEGRA20_AC97_STATUS1:250	case TEGRA20_AC97_FIFO1_SCR:251	case TEGRA20_AC97_FIFO_TX1:252	case TEGRA20_AC97_FIFO_RX1:253		return true;254	default:255		break;256	}257 258	return false;259}260 261static bool tegra20_ac97_volatile_reg(struct device *dev, unsigned int reg)262{263	switch (reg) {264	case TEGRA20_AC97_STATUS1:265	case TEGRA20_AC97_FIFO1_SCR:266	case TEGRA20_AC97_FIFO_TX1:267	case TEGRA20_AC97_FIFO_RX1:268		return true;269	default:270		break;271	}272 273	return false;274}275 276static bool tegra20_ac97_precious_reg(struct device *dev, unsigned int reg)277{278	switch (reg) {279	case TEGRA20_AC97_FIFO_TX1:280	case TEGRA20_AC97_FIFO_RX1:281		return true;282	default:283		break;284	}285 286	return false;287}288 289static const struct regmap_config tegra20_ac97_regmap_config = {290	.reg_bits = 32,291	.reg_stride = 4,292	.val_bits = 32,293	.max_register = TEGRA20_AC97_FIFO_RX1,294	.writeable_reg = tegra20_ac97_wr_rd_reg,295	.readable_reg = tegra20_ac97_wr_rd_reg,296	.volatile_reg = tegra20_ac97_volatile_reg,297	.precious_reg = tegra20_ac97_precious_reg,298	.cache_type = REGCACHE_FLAT,299};300 301static int tegra20_ac97_platform_probe(struct platform_device *pdev)302{303	struct tegra20_ac97 *ac97;304	struct resource *mem;305	void __iomem *regs;306	int ret = 0;307 308	ac97 = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_ac97),309			    GFP_KERNEL);310	if (!ac97) {311		ret = -ENOMEM;312		goto err;313	}314	dev_set_drvdata(&pdev->dev, ac97);315 316	ac97->reset = devm_reset_control_get_exclusive(&pdev->dev, "ac97");317	if (IS_ERR(ac97->reset)) {318		dev_err(&pdev->dev, "Can't retrieve ac97 reset\n");319		ret = PTR_ERR(ac97->reset);320		goto err;321	}322 323	ac97->clk_ac97 = devm_clk_get(&pdev->dev, NULL);324	if (IS_ERR(ac97->clk_ac97)) {325		dev_err(&pdev->dev, "Can't retrieve ac97 clock\n");326		ret = PTR_ERR(ac97->clk_ac97);327		goto err;328	}329 330	regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);331	if (IS_ERR(regs)) {332		ret = PTR_ERR(regs);333		goto err_clk_put;334	}335 336	ac97->regmap = devm_regmap_init_mmio(&pdev->dev, regs,337					    &tegra20_ac97_regmap_config);338	if (IS_ERR(ac97->regmap)) {339		dev_err(&pdev->dev, "regmap init failed\n");340		ret = PTR_ERR(ac97->regmap);341		goto err_clk_put;342	}343 344	/* Obtain RESET de-asserted */345	ac97->reset_gpio = devm_gpiod_get(&pdev->dev,346					  "nvidia,codec-reset",347					  GPIOD_OUT_LOW);348	if (IS_ERR(ac97->reset_gpio)) {349		ret = PTR_ERR(ac97->reset_gpio);350		dev_err(&pdev->dev, "no RESET GPIO supplied: %d\n", ret);351		goto err_clk_put;352	}353	gpiod_set_consumer_name(ac97->reset_gpio, "codec-reset");354 355	ac97->sync_gpio = devm_gpiod_get(&pdev->dev,356					 "nvidia,codec-sync",357					 GPIOD_OUT_LOW);358	if (IS_ERR(ac97->sync_gpio)) {359		ret = PTR_ERR(ac97->sync_gpio);360		dev_err(&pdev->dev, "no codec-sync GPIO supplied: %d\n", ret);361		goto err_clk_put;362	}363	gpiod_set_consumer_name(ac97->sync_gpio, "codec-sync");364 365	ac97->capture_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_RX1;366	ac97->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;367	ac97->capture_dma_data.maxburst = 4;368 369	ac97->playback_dma_data.addr = mem->start + TEGRA20_AC97_FIFO_TX1;370	ac97->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;371	ac97->playback_dma_data.maxburst = 4;372 373	ret = reset_control_assert(ac97->reset);374	if (ret) {375		dev_err(&pdev->dev, "Failed to assert AC'97 reset: %d\n", ret);376		goto err_clk_put;377	}378 379	ret = clk_prepare_enable(ac97->clk_ac97);380	if (ret) {381		dev_err(&pdev->dev, "clk_enable failed: %d\n", ret);382		goto err_clk_put;383	}384 385	usleep_range(10, 100);386 387	ret = reset_control_deassert(ac97->reset);388	if (ret) {389		dev_err(&pdev->dev, "Failed to deassert AC'97 reset: %d\n", ret);390		goto err_clk_disable_unprepare;391	}392 393	ret = snd_soc_set_ac97_ops(&tegra20_ac97_ops);394	if (ret) {395		dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);396		goto err_clk_disable_unprepare;397	}398 399	ret = snd_soc_register_component(&pdev->dev, &tegra20_ac97_component,400					 &tegra20_ac97_dai, 1);401	if (ret) {402		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);403		ret = -ENOMEM;404		goto err_clk_disable_unprepare;405	}406 407	ret = tegra_pcm_platform_register(&pdev->dev);408	if (ret) {409		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);410		goto err_unregister_component;411	}412 413	/* XXX: crufty ASoC AC97 API - only one AC97 codec allowed */414	workdata = ac97;415 416	return 0;417 418err_unregister_component:419	snd_soc_unregister_component(&pdev->dev);420err_clk_disable_unprepare:421	clk_disable_unprepare(ac97->clk_ac97);422err_clk_put:423err:424	snd_soc_set_ac97_ops(NULL);425	return ret;426}427 428static void tegra20_ac97_platform_remove(struct platform_device *pdev)429{430	struct tegra20_ac97 *ac97 = dev_get_drvdata(&pdev->dev);431 432	tegra_pcm_platform_unregister(&pdev->dev);433	snd_soc_unregister_component(&pdev->dev);434 435	clk_disable_unprepare(ac97->clk_ac97);436 437	snd_soc_set_ac97_ops(NULL);438}439 440static const struct of_device_id tegra20_ac97_of_match[] = {441	{ .compatible = "nvidia,tegra20-ac97", },442	{},443};444 445static struct platform_driver tegra20_ac97_driver = {446	.driver = {447		.name = DRV_NAME,448		.of_match_table = tegra20_ac97_of_match,449	},450	.probe = tegra20_ac97_platform_probe,451	.remove = tegra20_ac97_platform_remove,452};453module_platform_driver(tegra20_ac97_driver);454 455MODULE_AUTHOR("Lucas Stach");456MODULE_DESCRIPTION("Tegra20 AC97 ASoC driver");457MODULE_LICENSE("GPL v2");458MODULE_ALIAS("platform:" DRV_NAME);459MODULE_DEVICE_TABLE(of, tegra20_ac97_of_match);460