blob: 9efd71e13a0ad6f562f2943d5ac0f9f7360dbde6 [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/debugfs.h>
25#include <linux/device.h>
Stephen Warren7613c502012-04-06 11:12:25 -060026#include <linux/io.h>
27#include <linux/module.h>
Stephen Warren774fec32011-07-05 10:55:27 -060028#include <linux/platform_device.h>
Stephen Warren82ef0ae2012-04-09 09:52:22 -060029#include <linux/pm_runtime.h>
Stephen Warren774fec32011-07-05 10:55:27 -060030#include <linux/seq_file.h>
31#include <linux/slab.h>
Stephen Warren774fec32011-07-05 10:55:27 -060032#include <sound/core.h>
33#include <sound/pcm.h>
34#include <sound/pcm_params.h>
35#include <sound/soc.h>
36
Stephen Warrenef280d32012-04-05 15:54:53 -060037#include "tegra20_spdif.h"
Stephen Warren774fec32011-07-05 10:55:27 -060038
Stephen Warren896637a2012-04-06 10:30:52 -060039#define DRV_NAME "tegra20-spdif"
Stephen Warren774fec32011-07-05 10:55:27 -060040
Stephen Warren896637a2012-04-06 10:30:52 -060041static inline void tegra20_spdif_write(struct tegra20_spdif *spdif, u32 reg,
Stephen Warren774fec32011-07-05 10:55:27 -060042 u32 val)
43{
44 __raw_writel(val, spdif->regs + reg);
45}
46
Stephen Warren896637a2012-04-06 10:30:52 -060047static inline u32 tegra20_spdif_read(struct tegra20_spdif *spdif, u32 reg)
Stephen Warren774fec32011-07-05 10:55:27 -060048{
49 return __raw_readl(spdif->regs + reg);
50}
51
Stephen Warren82ef0ae2012-04-09 09:52:22 -060052static int tegra20_spdif_runtime_suspend(struct device *dev)
53{
54 struct tegra20_spdif *spdif = dev_get_drvdata(dev);
55
56 clk_disable(spdif->clk_spdif_out);
57
58 return 0;
59}
60
61static int tegra20_spdif_runtime_resume(struct device *dev)
62{
63 struct tegra20_spdif *spdif = dev_get_drvdata(dev);
64 int ret;
65
66 ret = clk_enable(spdif->clk_spdif_out);
67 if (ret) {
68 dev_err(dev, "clk_enable failed: %d\n", ret);
69 return ret;
70 }
71
72 return 0;
73}
74
Stephen Warren774fec32011-07-05 10:55:27 -060075#ifdef CONFIG_DEBUG_FS
Stephen Warren896637a2012-04-06 10:30:52 -060076static int tegra20_spdif_show(struct seq_file *s, void *unused)
Stephen Warren774fec32011-07-05 10:55:27 -060077{
78#define REG(r) { r, #r }
79 static const struct {
80 int offset;
81 const char *name;
82 } regs[] = {
Stephen Warren896637a2012-04-06 10:30:52 -060083 REG(TEGRA20_SPDIF_CTRL),
84 REG(TEGRA20_SPDIF_STATUS),
85 REG(TEGRA20_SPDIF_STROBE_CTRL),
86 REG(TEGRA20_SPDIF_DATA_FIFO_CSR),
87 REG(TEGRA20_SPDIF_CH_STA_RX_A),
88 REG(TEGRA20_SPDIF_CH_STA_RX_B),
89 REG(TEGRA20_SPDIF_CH_STA_RX_C),
90 REG(TEGRA20_SPDIF_CH_STA_RX_D),
91 REG(TEGRA20_SPDIF_CH_STA_RX_E),
92 REG(TEGRA20_SPDIF_CH_STA_RX_F),
93 REG(TEGRA20_SPDIF_CH_STA_TX_A),
94 REG(TEGRA20_SPDIF_CH_STA_TX_B),
95 REG(TEGRA20_SPDIF_CH_STA_TX_C),
96 REG(TEGRA20_SPDIF_CH_STA_TX_D),
97 REG(TEGRA20_SPDIF_CH_STA_TX_E),
98 REG(TEGRA20_SPDIF_CH_STA_TX_F),
Stephen Warren774fec32011-07-05 10:55:27 -060099 };
100#undef REG
101
Stephen Warren896637a2012-04-06 10:30:52 -0600102 struct tegra20_spdif *spdif = s->private;
Stephen Warren774fec32011-07-05 10:55:27 -0600103 int i;
104
105 for (i = 0; i < ARRAY_SIZE(regs); i++) {
Stephen Warren896637a2012-04-06 10:30:52 -0600106 u32 val = tegra20_spdif_read(spdif, regs[i].offset);
Stephen Warren774fec32011-07-05 10:55:27 -0600107 seq_printf(s, "%s = %08x\n", regs[i].name, val);
108 }
109
110 return 0;
111}
112
Stephen Warren896637a2012-04-06 10:30:52 -0600113static int tegra20_spdif_debug_open(struct inode *inode, struct file *file)
Stephen Warren774fec32011-07-05 10:55:27 -0600114{
Stephen Warren896637a2012-04-06 10:30:52 -0600115 return single_open(file, tegra20_spdif_show, inode->i_private);
Stephen Warren774fec32011-07-05 10:55:27 -0600116}
117
Stephen Warren896637a2012-04-06 10:30:52 -0600118static const struct file_operations tegra20_spdif_debug_fops = {
119 .open = tegra20_spdif_debug_open,
Stephen Warren774fec32011-07-05 10:55:27 -0600120 .read = seq_read,
121 .llseek = seq_lseek,
122 .release = single_release,
123};
124
Stephen Warren896637a2012-04-06 10:30:52 -0600125static void tegra20_spdif_debug_add(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600126{
127 spdif->debug = debugfs_create_file(DRV_NAME, S_IRUGO,
128 snd_soc_debugfs_root, spdif,
Stephen Warren896637a2012-04-06 10:30:52 -0600129 &tegra20_spdif_debug_fops);
Stephen Warren774fec32011-07-05 10:55:27 -0600130}
131
Stephen Warren896637a2012-04-06 10:30:52 -0600132static void tegra20_spdif_debug_remove(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600133{
134 if (spdif->debug)
135 debugfs_remove(spdif->debug);
136}
137#else
Stephen Warren896637a2012-04-06 10:30:52 -0600138static inline void tegra20_spdif_debug_add(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600139{
140}
141
Stephen Warren896637a2012-04-06 10:30:52 -0600142static inline void tegra20_spdif_debug_remove(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600143{
144}
145#endif
146
Stephen Warren896637a2012-04-06 10:30:52 -0600147static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream,
Stephen Warren774fec32011-07-05 10:55:27 -0600148 struct snd_pcm_hw_params *params,
149 struct snd_soc_dai *dai)
150{
151 struct device *dev = substream->pcm->card->dev;
Stephen Warren896637a2012-04-06 10:30:52 -0600152 struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
Axel Lin4b8713f2011-10-02 21:07:02 +0800153 int ret, spdifclock;
Stephen Warren774fec32011-07-05 10:55:27 -0600154
Stephen Warren896637a2012-04-06 10:30:52 -0600155 spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_PACK;
156 spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_BIT_MODE_MASK;
Stephen Warren774fec32011-07-05 10:55:27 -0600157 switch (params_format(params)) {
158 case SNDRV_PCM_FORMAT_S16_LE:
Stephen Warren896637a2012-04-06 10:30:52 -0600159 spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_PACK;
160 spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT;
Stephen Warren774fec32011-07-05 10:55:27 -0600161 break;
162 default:
163 return -EINVAL;
164 }
165
Stephen Warren774fec32011-07-05 10:55:27 -0600166 switch (params_rate(params)) {
167 case 32000:
168 spdifclock = 4096000;
169 break;
170 case 44100:
171 spdifclock = 5644800;
172 break;
173 case 48000:
174 spdifclock = 6144000;
175 break;
176 case 88200:
177 spdifclock = 11289600;
178 break;
179 case 96000:
180 spdifclock = 12288000;
181 break;
182 case 176400:
183 spdifclock = 22579200;
184 break;
185 case 192000:
186 spdifclock = 24576000;
187 break;
188 default:
189 return -EINVAL;
190 }
191
192 ret = clk_set_rate(spdif->clk_spdif_out, spdifclock);
193 if (ret) {
194 dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret);
195 return ret;
196 }
197
198 return 0;
199}
200
Stephen Warren896637a2012-04-06 10:30:52 -0600201static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600202{
Stephen Warren896637a2012-04-06 10:30:52 -0600203 spdif->reg_ctrl |= TEGRA20_SPDIF_CTRL_TX_EN;
204 tegra20_spdif_write(spdif, TEGRA20_SPDIF_CTRL, spdif->reg_ctrl);
Stephen Warren774fec32011-07-05 10:55:27 -0600205}
206
Stephen Warren896637a2012-04-06 10:30:52 -0600207static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif)
Stephen Warren774fec32011-07-05 10:55:27 -0600208{
Stephen Warren896637a2012-04-06 10:30:52 -0600209 spdif->reg_ctrl &= ~TEGRA20_SPDIF_CTRL_TX_EN;
210 tegra20_spdif_write(spdif, TEGRA20_SPDIF_CTRL, spdif->reg_ctrl);
Stephen Warren774fec32011-07-05 10:55:27 -0600211}
212
Stephen Warren896637a2012-04-06 10:30:52 -0600213static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd,
Stephen Warren774fec32011-07-05 10:55:27 -0600214 struct snd_soc_dai *dai)
215{
Stephen Warren896637a2012-04-06 10:30:52 -0600216 struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
Stephen Warren774fec32011-07-05 10:55:27 -0600217
218 switch (cmd) {
219 case SNDRV_PCM_TRIGGER_START:
220 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
221 case SNDRV_PCM_TRIGGER_RESUME:
Stephen Warren896637a2012-04-06 10:30:52 -0600222 tegra20_spdif_start_playback(spdif);
Stephen Warren774fec32011-07-05 10:55:27 -0600223 break;
224 case SNDRV_PCM_TRIGGER_STOP:
225 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
226 case SNDRV_PCM_TRIGGER_SUSPEND:
Stephen Warren896637a2012-04-06 10:30:52 -0600227 tegra20_spdif_stop_playback(spdif);
Stephen Warren774fec32011-07-05 10:55:27 -0600228 break;
229 default:
230 return -EINVAL;
231 }
232
233 return 0;
234}
235
Stephen Warren896637a2012-04-06 10:30:52 -0600236static int tegra20_spdif_probe(struct snd_soc_dai *dai)
Stephen Warren774fec32011-07-05 10:55:27 -0600237{
Stephen Warren896637a2012-04-06 10:30:52 -0600238 struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai);
Stephen Warren774fec32011-07-05 10:55:27 -0600239
240 dai->capture_dma_data = NULL;
241 dai->playback_dma_data = &spdif->playback_dma_data;
242
243 return 0;
244}
245
Stephen Warren896637a2012-04-06 10:30:52 -0600246static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = {
247 .hw_params = tegra20_spdif_hw_params,
248 .trigger = tegra20_spdif_trigger,
Stephen Warren774fec32011-07-05 10:55:27 -0600249};
250
Stephen Warren896637a2012-04-06 10:30:52 -0600251static struct snd_soc_dai_driver tegra20_spdif_dai = {
Stephen Warren774fec32011-07-05 10:55:27 -0600252 .name = DRV_NAME,
Stephen Warren896637a2012-04-06 10:30:52 -0600253 .probe = tegra20_spdif_probe,
Stephen Warren774fec32011-07-05 10:55:27 -0600254 .playback = {
255 .channels_min = 2,
256 .channels_max = 2,
257 .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
258 SNDRV_PCM_RATE_48000,
259 .formats = SNDRV_PCM_FMTBIT_S16_LE,
260 },
Stephen Warren896637a2012-04-06 10:30:52 -0600261 .ops = &tegra20_spdif_dai_ops,
Stephen Warren774fec32011-07-05 10:55:27 -0600262};
263
Stephen Warren896637a2012-04-06 10:30:52 -0600264static __devinit int tegra20_spdif_platform_probe(struct platform_device *pdev)
Stephen Warren774fec32011-07-05 10:55:27 -0600265{
Stephen Warren896637a2012-04-06 10:30:52 -0600266 struct tegra20_spdif *spdif;
Stephen Warren774fec32011-07-05 10:55:27 -0600267 struct resource *mem, *memregion, *dmareq;
268 int ret;
269
Stephen Warren17933db2012-04-06 11:14:04 -0600270 spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif),
271 GFP_KERNEL);
Stephen Warren774fec32011-07-05 10:55:27 -0600272 if (!spdif) {
Stephen Warren896637a2012-04-06 10:30:52 -0600273 dev_err(&pdev->dev, "Can't allocate tegra20_spdif\n");
Stephen Warren774fec32011-07-05 10:55:27 -0600274 ret = -ENOMEM;
Stephen Warren17933db2012-04-06 11:14:04 -0600275 goto err;
Stephen Warren774fec32011-07-05 10:55:27 -0600276 }
277 dev_set_drvdata(&pdev->dev, spdif);
278
279 spdif->clk_spdif_out = clk_get(&pdev->dev, "spdif_out");
280 if (IS_ERR(spdif->clk_spdif_out)) {
281 pr_err("Can't retrieve spdif clock\n");
282 ret = PTR_ERR(spdif->clk_spdif_out);
Stephen Warren17933db2012-04-06 11:14:04 -0600283 goto err;
Stephen Warren774fec32011-07-05 10:55:27 -0600284 }
285
286 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
287 if (!mem) {
288 dev_err(&pdev->dev, "No memory resource\n");
289 ret = -ENODEV;
290 goto err_clk_put;
291 }
292
293 dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
294 if (!dmareq) {
295 dev_err(&pdev->dev, "No DMA resource\n");
296 ret = -ENODEV;
297 goto err_clk_put;
298 }
299
Stephen Warren17933db2012-04-06 11:14:04 -0600300 memregion = devm_request_mem_region(&pdev->dev, mem->start,
301 resource_size(mem), DRV_NAME);
Stephen Warren774fec32011-07-05 10:55:27 -0600302 if (!memregion) {
303 dev_err(&pdev->dev, "Memory region already claimed\n");
304 ret = -EBUSY;
305 goto err_clk_put;
306 }
307
Stephen Warren17933db2012-04-06 11:14:04 -0600308 spdif->regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
Stephen Warren774fec32011-07-05 10:55:27 -0600309 if (!spdif->regs) {
310 dev_err(&pdev->dev, "ioremap failed\n");
311 ret = -ENOMEM;
Stephen Warren17933db2012-04-06 11:14:04 -0600312 goto err_clk_put;
Stephen Warren774fec32011-07-05 10:55:27 -0600313 }
314
Stephen Warren896637a2012-04-06 10:30:52 -0600315 spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT;
Stephen Warren774fec32011-07-05 10:55:27 -0600316 spdif->playback_dma_data.wrap = 4;
317 spdif->playback_dma_data.width = 32;
318 spdif->playback_dma_data.req_sel = dmareq->start;
319
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600320 pm_runtime_enable(&pdev->dev);
321 if (!pm_runtime_enabled(&pdev->dev)) {
322 ret = tegra20_spdif_runtime_resume(&pdev->dev);
323 if (ret)
324 goto err_pm_disable;
325 }
326
Stephen Warren896637a2012-04-06 10:30:52 -0600327 ret = snd_soc_register_dai(&pdev->dev, &tegra20_spdif_dai);
Stephen Warren774fec32011-07-05 10:55:27 -0600328 if (ret) {
329 dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
330 ret = -ENOMEM;
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600331 goto err_suspend;
Stephen Warren774fec32011-07-05 10:55:27 -0600332 }
333
Stephen Warren518de862012-03-20 14:55:49 -0600334 ret = tegra_pcm_platform_register(&pdev->dev);
335 if (ret) {
336 dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
337 goto err_unregister_dai;
338 }
339
Stephen Warren896637a2012-04-06 10:30:52 -0600340 tegra20_spdif_debug_add(spdif);
Stephen Warren774fec32011-07-05 10:55:27 -0600341
342 return 0;
343
Stephen Warren518de862012-03-20 14:55:49 -0600344err_unregister_dai:
345 snd_soc_unregister_dai(&pdev->dev);
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600346err_suspend:
347 if (!pm_runtime_status_suspended(&pdev->dev))
348 tegra20_spdif_runtime_suspend(&pdev->dev);
349err_pm_disable:
350 pm_runtime_disable(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600351err_clk_put:
352 clk_put(spdif->clk_spdif_out);
Stephen Warren17933db2012-04-06 11:14:04 -0600353err:
Stephen Warren774fec32011-07-05 10:55:27 -0600354 return ret;
355}
356
Stephen Warren896637a2012-04-06 10:30:52 -0600357static int __devexit tegra20_spdif_platform_remove(struct platform_device *pdev)
Stephen Warren774fec32011-07-05 10:55:27 -0600358{
Stephen Warren896637a2012-04-06 10:30:52 -0600359 struct tegra20_spdif *spdif = dev_get_drvdata(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600360
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600361 pm_runtime_disable(&pdev->dev);
362 if (!pm_runtime_status_suspended(&pdev->dev))
363 tegra20_spdif_runtime_suspend(&pdev->dev);
364
Stephen Warren518de862012-03-20 14:55:49 -0600365 tegra_pcm_platform_unregister(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600366 snd_soc_unregister_dai(&pdev->dev);
367
Stephen Warren896637a2012-04-06 10:30:52 -0600368 tegra20_spdif_debug_remove(spdif);
Stephen Warren774fec32011-07-05 10:55:27 -0600369
Stephen Warren774fec32011-07-05 10:55:27 -0600370 clk_put(spdif->clk_spdif_out);
371
Stephen Warren774fec32011-07-05 10:55:27 -0600372 return 0;
373}
374
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600375static const struct dev_pm_ops tegra20_spdif_pm_ops __devinitconst = {
376 SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend,
377 tegra20_spdif_runtime_resume, NULL)
378};
379
Stephen Warren896637a2012-04-06 10:30:52 -0600380static struct platform_driver tegra20_spdif_driver = {
Stephen Warren774fec32011-07-05 10:55:27 -0600381 .driver = {
382 .name = DRV_NAME,
383 .owner = THIS_MODULE,
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600384 .pm = &tegra20_spdif_pm_ops,
Stephen Warren774fec32011-07-05 10:55:27 -0600385 },
Stephen Warren896637a2012-04-06 10:30:52 -0600386 .probe = tegra20_spdif_platform_probe,
387 .remove = __devexit_p(tegra20_spdif_platform_remove),
Stephen Warren774fec32011-07-05 10:55:27 -0600388};
389
Stephen Warren896637a2012-04-06 10:30:52 -0600390module_platform_driver(tegra20_spdif_driver);
Stephen Warren774fec32011-07-05 10:55:27 -0600391
392MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
Stephen Warren896637a2012-04-06 10:30:52 -0600393MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver");
Stephen Warren774fec32011-07-05 10:55:27 -0600394MODULE_LICENSE("GPL");
395MODULE_ALIAS("platform:" DRV_NAME);