blob: f9b57418bd088c42f83f7e4bfde699dd7407298d [file] [log] [blame]
Stephen Warren774fec32011-07-05 10:55:27 -06001/*
Stephen Warrenef280d32012-04-05 15:54:53 -06002 * tegra20_spdif.c - Tegra20 SPDIF driver
Stephen Warren774fec32011-07-05 10:55:27 -06003 *
4 * Author: Stephen Warren <swarren@nvidia.com>
Stephen Warren518de862012-03-20 14:55:49 -06005 * Copyright (C) 2011-2012 - NVIDIA, Inc.
Stephen Warren774fec32011-07-05 10:55:27 -06006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * version 2 as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301 USA
20 *
21 */
22
23#include <linux/clk.h>
Stephen Warren774fec32011-07-05 10:55:27 -060024#include <linux/device.h>
Stephen Warren7613c502012-04-06 11:12:25 -060025#include <linux/io.h>
26#include <linux/module.h>
Stephen Warren774fec32011-07-05 10:55:27 -060027#include <linux/platform_device.h>
Stephen Warren82ef0ae2012-04-09 09:52:22 -060028#include <linux/pm_runtime.h>
Stephen Warren5939ae72012-04-13 12:14:07 -060029#include <linux/regmap.h>
Stephen Warren774fec32011-07-05 10:55:27 -060030#include <linux/slab.h>
Stephen Warren774fec32011-07-05 10:55:27 -060031#include <sound/core.h>
32#include <sound/pcm.h>
33#include <sound/pcm_params.h>
34#include <sound/soc.h>
35
Stephen Warrenef280d32012-04-05 15:54:53 -060036#include "tegra20_spdif.h"
Stephen Warren774fec32011-07-05 10:55:27 -060037
Stephen Warren896637a2012-04-06 10:30:52 -060038#define DRV_NAME "tegra20-spdif"
Stephen Warren774fec32011-07-05 10:55:27 -060039
Stephen Warren896637a2012-04-06 10:30:52 -060040static inline void tegra20_spdif_write(struct tegra20_spdif *spdif, u32 reg,
Stephen Warren774fec32011-07-05 10:55:27 -060041 u32 val)
42{
Stephen Warren5939ae72012-04-13 12:14:07 -060043 regmap_write(spdif->regmap, reg, val);
Stephen Warren774fec32011-07-05 10:55:27 -060044}
45
Stephen Warren896637a2012-04-06 10:30:52 -060046static inline u32 tegra20_spdif_read(struct tegra20_spdif *spdif, u32 reg)
Stephen Warren774fec32011-07-05 10:55:27 -060047{
Stephen Warren5939ae72012-04-13 12:14:07 -060048 u32 val;
49 regmap_read(spdif->regmap, reg, &val);
50 return val;
Stephen Warren774fec32011-07-05 10:55:27 -060051}
52
Stephen Warren82ef0ae2012-04-09 09:52:22 -060053static int tegra20_spdif_runtime_suspend(struct device *dev)
54{
55 struct tegra20_spdif *spdif = dev_get_drvdata(dev);
56
57 clk_disable(spdif->clk_spdif_out);
58
59 return 0;
60}
61
62static int tegra20_spdif_runtime_resume(struct device *dev)
63{
64 struct tegra20_spdif *spdif = dev_get_drvdata(dev);
65 int ret;
66
67 ret = clk_enable(spdif->clk_spdif_out);
68 if (ret) {
69 dev_err(dev, "clk_enable failed: %d\n", ret);
70 return ret;
71 }
72
73 return 0;
74}
75
Stephen Warren896637a2012-04-06 10:30:52 -060076static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
Stephen Warren774fec32011-07-05 10:55:27 -060077 struct snd_pcm_hw_params *params,
78 struct snd_soc_dai *dai)
79{
80 struct device *dev = substream->pcm->card->dev;
Stephen Warren896637a2012-04-06 10:30:52 -060081 struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
Axel Lin4b8713f2011-10-02 21:07:02 +080082 int ret, spdifclock;
Stephen Warren774fec32011-07-05 10:55:27 -060083
Stephen Warren896637a2012-04-06 10:30:52 -060084 spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_PACK;
85 spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_BIT_MODE_MASK;
Stephen Warren774fec32011-07-05 10:55:27 -060086 switch (params_format(params)) {
87 case SNDRV_PCM_FORMAT_S16_LE:
Stephen Warren896637a2012-04-06 10:30:52 -060088 spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_PACK;
89 spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT;
Stephen Warren774fec32011-07-05 10:55:27 -060090 break;
91 default:
92 return -EINVAL;
93 }
94
Stephen Warren774fec32011-07-05 10:55:27 -060095 switch (params_rate(params)) {
96 case 32000:
97 spdifclock = 4096000;
98 break;
99 case 44100:
100 spdifclock = 5644800;
101 break;
102 case 48000:
103 spdifclock = 6144000;
104 break;
105 case 88200:
106 spdifclock = 11289600;
107 break;
108 case 96000:
109 spdifclock = 12288000;
110 break;
111 case 176400:
112 spdifclock = 22579200;
113 break;
114 case 192000:
115 spdifclock = 24576000;
116 break;
117 default:
118 return -EINVAL;
119 }
120
121 ret = clk_set_rate(spdif->clk_spdif_out, spdifclock);
122 if (ret) {
123 dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret);
124 return ret;
125 }
126
127 return 0;
128}
129
Stephen Warren896637a2012-04-06 10:30:52 -0600130static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600131{
Stephen Warren896637a2012-04-06 10:30:52 -0600132 spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_TX_EN;
133 tegra20_spdif_write(spdif, TEGRA20_SPDIF_CTRL, spdif->reg_ctrl);
Stephen Warren774fec32011-07-05 10:55:27 -0600134}
135
Stephen Warren896637a2012-04-06 10:30:52 -0600136static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600137{
Stephen Warren896637a2012-04-06 10:30:52 -0600138 spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_TX_EN;
139 tegra20_spdif_write(spdif, TEGRA20_SPDIF_CTRL, spdif->reg_ctrl);
Stephen Warren774fec32011-07-05 10:55:27 -0600140}
141
Stephen Warren896637a2012-04-06 10:30:52 -0600142static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
Stephen Warren774fec32011-07-05 10:55:27 -0600143 struct snd_soc_dai *dai)
144{
Stephen Warren896637a2012-04-06 10:30:52 -0600145 struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
Stephen Warren774fec32011-07-05 10:55:27 -0600146
147 switch (cmd) {
148 case SNDRV_PCM_TRIGGER_START:
149 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
150 case SNDRV_PCM_TRIGGER_RESUME:
Stephen Warren896637a2012-04-06 10:30:52 -0600151 tegra20_spdif_start_playback(spdif);
Stephen Warren774fec32011-07-05 10:55:27 -0600152 break;
153 case SNDRV_PCM_TRIGGER_STOP:
154 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
155 case SNDRV_PCM_TRIGGER_SUSPEND:
Stephen Warren896637a2012-04-06 10:30:52 -0600156 tegra20_spdif_stop_playback(spdif);
Stephen Warren774fec32011-07-05 10:55:27 -0600157 break;
158 default:
159 return -EINVAL;
160 }
161
162 return 0;
163}
164
Stephen Warren896637a2012-04-06 10:30:52 -0600165static int tegra20_spdif_probe(struct snd_soc_dai *dai)
Stephen Warren774fec32011-07-05 10:55:27 -0600166{
Stephen Warren896637a2012-04-06 10:30:52 -0600167 struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
Stephen Warren774fec32011-07-05 10:55:27 -0600168
169 dai->capture_dma_data = NULL;
170 dai->playback_dma_data = &spdif->playback_dma_data;
171
172 return 0;
173}
174
Stephen Warren896637a2012-04-06 10:30:52 -0600175static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = {
176 .hw_params = tegra20_spdif_hw_params,
177 .trigger = tegra20_spdif_trigger,
Stephen Warren774fec32011-07-05 10:55:27 -0600178};
179
Stephen Warren896637a2012-04-06 10:30:52 -0600180static struct snd_soc_dai_driver tegra20_spdif_dai = {
Stephen Warren774fec32011-07-05 10:55:27 -0600181 .name = DRV_NAME,
Stephen Warren896637a2012-04-06 10:30:52 -0600182 .probe = tegra20_spdif_probe,
Stephen Warren774fec32011-07-05 10:55:27 -0600183 .playback = {
184 .channels_min = 2,
185 .channels_max = 2,
186 .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
187 SNDRV_PCM_RATE_48000,
188 .formats = SNDRV_PCM_FMTBIT_S16_LE,
189 },
Stephen Warren896637a2012-04-06 10:30:52 -0600190 .ops = &tegra20_spdif_dai_ops,
Stephen Warren774fec32011-07-05 10:55:27 -0600191};
192
Stephen Warren5939ae72012-04-13 12:14:07 -0600193static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg)
194{
195 switch (reg) {
196 case TEGRA20_SPDIF_CTRL:
197 case TEGRA20_SPDIF_STATUS:
198 case TEGRA20_SPDIF_STROBE_CTRL:
199 case TEGRA20_SPDIF_DATA_FIFO_CSR:
200 case TEGRA20_SPDIF_DATA_OUT:
201 case TEGRA20_SPDIF_DATA_IN:
202 case TEGRA20_SPDIF_CH_STA_RX_A:
203 case TEGRA20_SPDIF_CH_STA_RX_B:
204 case TEGRA20_SPDIF_CH_STA_RX_C:
205 case TEGRA20_SPDIF_CH_STA_RX_D:
206 case TEGRA20_SPDIF_CH_STA_RX_E:
207 case TEGRA20_SPDIF_CH_STA_RX_F:
208 case TEGRA20_SPDIF_CH_STA_TX_A:
209 case TEGRA20_SPDIF_CH_STA_TX_B:
210 case TEGRA20_SPDIF_CH_STA_TX_C:
211 case TEGRA20_SPDIF_CH_STA_TX_D:
212 case TEGRA20_SPDIF_CH_STA_TX_E:
213 case TEGRA20_SPDIF_CH_STA_TX_F:
214 case TEGRA20_SPDIF_USR_STA_RX_A:
215 case TEGRA20_SPDIF_USR_DAT_TX_A:
216 return true;
217 default:
218 return false;
219 };
220}
221
222static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg)
223{
224 switch (reg) {
225 case TEGRA20_SPDIF_STATUS:
226 case TEGRA20_SPDIF_DATA_FIFO_CSR:
227 case TEGRA20_SPDIF_DATA_OUT:
228 case TEGRA20_SPDIF_DATA_IN:
229 case TEGRA20_SPDIF_CH_STA_RX_A:
230 case TEGRA20_SPDIF_CH_STA_RX_B:
231 case TEGRA20_SPDIF_CH_STA_RX_C:
232 case TEGRA20_SPDIF_CH_STA_RX_D:
233 case TEGRA20_SPDIF_CH_STA_RX_E:
234 case TEGRA20_SPDIF_CH_STA_RX_F:
235 case TEGRA20_SPDIF_USR_STA_RX_A:
236 case TEGRA20_SPDIF_USR_DAT_TX_A:
237 return true;
238 default:
239 return false;
240 };
241}
242
243static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg)
244{
245 switch (reg) {
246 case TEGRA20_SPDIF_DATA_OUT:
247 case TEGRA20_SPDIF_DATA_IN:
248 case TEGRA20_SPDIF_USR_STA_RX_A:
249 case TEGRA20_SPDIF_USR_DAT_TX_A:
250 return true;
251 default:
252 return false;
253 };
254}
255
256static const struct regmap_config tegra20_spdif_regmap_config = {
257 .reg_bits = 32,
258 .reg_stride = 4,
259 .val_bits = 32,
260 .max_register = TEGRA20_SPDIF_USR_DAT_TX_A,
261 .writeable_reg = tegra20_spdif_wr_rd_reg,
262 .readable_reg = tegra20_spdif_wr_rd_reg,
263 .volatile_reg = tegra20_spdif_volatile_reg,
264 .precious_reg = tegra20_spdif_precious_reg,
265 .cache_type = REGCACHE_RBTREE,
266};
267
Stephen Warren896637a2012-04-06 10:30:52 -0600268static __devinit int tegra20_spdif_platform_probe(struct platform_device *pdev)
Stephen Warren774fec32011-07-05 10:55:27 -0600269{
Stephen Warren896637a2012-04-06 10:30:52 -0600270 struct tegra20_spdif *spdif;
Stephen Warren774fec32011-07-05 10:55:27 -0600271 struct resource *mem, *memregion, *dmareq;
Stephen Warren5939ae72012-04-13 12:14:07 -0600272 void __iomem *regs;
Stephen Warren774fec32011-07-05 10:55:27 -0600273 int ret;
274
Stephen Warren17933db2012-04-06 11:14:04 -0600275 spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif),
276 GFP_KERNEL);
Stephen Warren774fec32011-07-05 10:55:27 -0600277 if (!spdif) {
Stephen Warren896637a2012-04-06 10:30:52 -0600278 dev_err(&pdev->dev, "Can't allocate tegra20_spdif\n");
Stephen Warren774fec32011-07-05 10:55:27 -0600279 ret = -ENOMEM;
Stephen Warren17933db2012-04-06 11:14:04 -0600280 goto err;
Stephen Warren774fec32011-07-05 10:55:27 -0600281 }
282 dev_set_drvdata(&pdev->dev, spdif);
283
284 spdif->clk_spdif_out = clk_get(&pdev->dev, "spdif_out");
285 if (IS_ERR(spdif->clk_spdif_out)) {
286 pr_err("Can't retrieve spdif clock\n");
287 ret = PTR_ERR(spdif->clk_spdif_out);
Stephen Warren17933db2012-04-06 11:14:04 -0600288 goto err;
Stephen Warren774fec32011-07-05 10:55:27 -0600289 }
290
291 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
292 if (!mem) {
293 dev_err(&pdev->dev, "No memory resource\n");
294 ret = -ENODEV;
295 goto err_clk_put;
296 }
297
298 dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
299 if (!dmareq) {
300 dev_err(&pdev->dev, "No DMA resource\n");
301 ret = -ENODEV;
302 goto err_clk_put;
303 }
304
Stephen Warren17933db2012-04-06 11:14:04 -0600305 memregion = devm_request_mem_region(&pdev->dev, mem->start,
306 resource_size(mem), DRV_NAME);
Stephen Warren774fec32011-07-05 10:55:27 -0600307 if (!memregion) {
308 dev_err(&pdev->dev, "Memory region already claimed\n");
309 ret = -EBUSY;
310 goto err_clk_put;
311 }
312
Stephen Warren5939ae72012-04-13 12:14:07 -0600313 regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
314 if (!regs) {
Stephen Warren774fec32011-07-05 10:55:27 -0600315 dev_err(&pdev->dev, "ioremap failed\n");
316 ret = -ENOMEM;
Stephen Warren17933db2012-04-06 11:14:04 -0600317 goto err_clk_put;
Stephen Warren774fec32011-07-05 10:55:27 -0600318 }
319
Stephen Warren5939ae72012-04-13 12:14:07 -0600320 spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
321 &tegra20_spdif_regmap_config);
322 if (IS_ERR(spdif->regmap)) {
323 dev_err(&pdev->dev, "regmap init failed\n");
324 ret = PTR_ERR(spdif->regmap);
325 goto err_clk_put;
326 }
327
Stephen Warren896637a2012-04-06 10:30:52 -0600328 spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT;
Stephen Warren774fec32011-07-05 10:55:27 -0600329 spdif->playback_dma_data.wrap = 4;
330 spdif->playback_dma_data.width = 32;
331 spdif->playback_dma_data.req_sel = dmareq->start;
332
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600333 pm_runtime_enable(&pdev->dev);
334 if (!pm_runtime_enabled(&pdev->dev)) {
335 ret = tegra20_spdif_runtime_resume(&pdev->dev);
336 if (ret)
337 goto err_pm_disable;
338 }
339
Stephen Warren896637a2012-04-06 10:30:52 -0600340 ret = snd_soc_register_dai(&pdev->dev, &tegra20_spdif_dai);
Stephen Warren774fec32011-07-05 10:55:27 -0600341 if (ret) {
342 dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
343 ret = -ENOMEM;
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600344 goto err_suspend;
Stephen Warren774fec32011-07-05 10:55:27 -0600345 }
346
Stephen Warren518de862012-03-20 14:55:49 -0600347 ret = tegra_pcm_platform_register(&pdev->dev);
348 if (ret) {
349 dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
350 goto err_unregister_dai;
351 }
352
Stephen Warren774fec32011-07-05 10:55:27 -0600353 return 0;
354
Stephen Warren518de862012-03-20 14:55:49 -0600355err_unregister_dai:
356 snd_soc_unregister_dai(&pdev->dev);
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600357err_suspend:
358 if (!pm_runtime_status_suspended(&pdev->dev))
359 tegra20_spdif_runtime_suspend(&pdev->dev);
360err_pm_disable:
361 pm_runtime_disable(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600362err_clk_put:
363 clk_put(spdif->clk_spdif_out);
Stephen Warren17933db2012-04-06 11:14:04 -0600364err:
Stephen Warren774fec32011-07-05 10:55:27 -0600365 return ret;
366}
367
Stephen Warren896637a2012-04-06 10:30:52 -0600368static int __devexit tegra20_spdif_platform_remove(struct platform_device *pdev)
Stephen Warren774fec32011-07-05 10:55:27 -0600369{
Stephen Warren896637a2012-04-06 10:30:52 -0600370 struct tegra20_spdif *spdif = dev_get_drvdata(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600371
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600372 pm_runtime_disable(&pdev->dev);
373 if (!pm_runtime_status_suspended(&pdev->dev))
374 tegra20_spdif_runtime_suspend(&pdev->dev);
375
Stephen Warren518de862012-03-20 14:55:49 -0600376 tegra_pcm_platform_unregister(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600377 snd_soc_unregister_dai(&pdev->dev);
378
Stephen Warren774fec32011-07-05 10:55:27 -0600379 clk_put(spdif->clk_spdif_out);
380
Stephen Warren774fec32011-07-05 10:55:27 -0600381 return 0;
382}
383
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600384static const struct dev_pm_ops tegra20_spdif_pm_ops __devinitconst = {
385 SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend,
386 tegra20_spdif_runtime_resume, NULL)
387};
388
Stephen Warren896637a2012-04-06 10:30:52 -0600389static struct platform_driver tegra20_spdif_driver = {
Stephen Warren774fec32011-07-05 10:55:27 -0600390 .driver = {
391 .name = DRV_NAME,
392 .owner = THIS_MODULE,
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600393 .pm = &tegra20_spdif_pm_ops,
Stephen Warren774fec32011-07-05 10:55:27 -0600394 },
Stephen Warren896637a2012-04-06 10:30:52 -0600395 .probe = tegra20_spdif_platform_probe,
396 .remove = __devexit_p(tegra20_spdif_platform_remove),
Stephen Warren774fec32011-07-05 10:55:27 -0600397};
398
Stephen Warren896637a2012-04-06 10:30:52 -0600399module_platform_driver(tegra20_spdif_driver);
Stephen Warren774fec32011-07-05 10:55:27 -0600400
401MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
Stephen Warren896637a2012-04-06 10:30:52 -0600402MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver");
Stephen Warren774fec32011-07-05 10:55:27 -0600403MODULE_LICENSE("GPL");
404MODULE_ALIAS("platform:" DRV_NAME);