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