blob: f774a2d5e585169b60b9b8000a5c977bb21d27c5 [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{
Stephen Warrenc92a40e2012-06-06 17:15:05 -060080 struct device *dev = dai->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 = {
Stephen Warren9515c102012-06-06 17:15:07 -0600184 .stream_name = "Playback",
Stephen Warren774fec32011-07-05 10:55:27 -0600185 .channels_min = 2,
186 .channels_max = 2,
187 .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |
188 SNDRV_PCM_RATE_48000,
189 .formats = SNDRV_PCM_FMTBIT_S16_LE,
190 },
Stephen Warren896637a2012-04-06 10:30:52 -0600191 .ops = &tegra20_spdif_dai_ops,
Stephen Warren774fec32011-07-05 10:55:27 -0600192};
193
Stephen Warren5939ae72012-04-13 12:14:07 -0600194static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg)
195{
196 switch (reg) {
197 case TEGRA20_SPDIF_CTRL:
198 case TEGRA20_SPDIF_STATUS:
199 case TEGRA20_SPDIF_STROBE_CTRL:
200 case TEGRA20_SPDIF_DATA_FIFO_CSR:
201 case TEGRA20_SPDIF_DATA_OUT:
202 case TEGRA20_SPDIF_DATA_IN:
203 case TEGRA20_SPDIF_CH_STA_RX_A:
204 case TEGRA20_SPDIF_CH_STA_RX_B:
205 case TEGRA20_SPDIF_CH_STA_RX_C:
206 case TEGRA20_SPDIF_CH_STA_RX_D:
207 case TEGRA20_SPDIF_CH_STA_RX_E:
208 case TEGRA20_SPDIF_CH_STA_RX_F:
209 case TEGRA20_SPDIF_CH_STA_TX_A:
210 case TEGRA20_SPDIF_CH_STA_TX_B:
211 case TEGRA20_SPDIF_CH_STA_TX_C:
212 case TEGRA20_SPDIF_CH_STA_TX_D:
213 case TEGRA20_SPDIF_CH_STA_TX_E:
214 case TEGRA20_SPDIF_CH_STA_TX_F:
215 case TEGRA20_SPDIF_USR_STA_RX_A:
216 case TEGRA20_SPDIF_USR_DAT_TX_A:
217 return true;
218 default:
219 return false;
220 };
221}
222
223static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg)
224{
225 switch (reg) {
226 case TEGRA20_SPDIF_STATUS:
227 case TEGRA20_SPDIF_DATA_FIFO_CSR:
228 case TEGRA20_SPDIF_DATA_OUT:
229 case TEGRA20_SPDIF_DATA_IN:
230 case TEGRA20_SPDIF_CH_STA_RX_A:
231 case TEGRA20_SPDIF_CH_STA_RX_B:
232 case TEGRA20_SPDIF_CH_STA_RX_C:
233 case TEGRA20_SPDIF_CH_STA_RX_D:
234 case TEGRA20_SPDIF_CH_STA_RX_E:
235 case TEGRA20_SPDIF_CH_STA_RX_F:
236 case TEGRA20_SPDIF_USR_STA_RX_A:
237 case TEGRA20_SPDIF_USR_DAT_TX_A:
238 return true;
239 default:
240 return false;
241 };
242}
243
244static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg)
245{
246 switch (reg) {
247 case TEGRA20_SPDIF_DATA_OUT:
248 case TEGRA20_SPDIF_DATA_IN:
249 case TEGRA20_SPDIF_USR_STA_RX_A:
250 case TEGRA20_SPDIF_USR_DAT_TX_A:
251 return true;
252 default:
253 return false;
254 };
255}
256
257static const struct regmap_config tegra20_spdif_regmap_config = {
258 .reg_bits = 32,
259 .reg_stride = 4,
260 .val_bits = 32,
261 .max_register = TEGRA20_SPDIF_USR_DAT_TX_A,
262 .writeable_reg = tegra20_spdif_wr_rd_reg,
263 .readable_reg = tegra20_spdif_wr_rd_reg,
264 .volatile_reg = tegra20_spdif_volatile_reg,
265 .precious_reg = tegra20_spdif_precious_reg,
266 .cache_type = REGCACHE_RBTREE,
267};
268
Stephen Warren896637a2012-04-06 10:30:52 -0600269static __devinit int tegra20_spdif_platform_probe(struct platform_device *pdev)
Stephen Warren774fec32011-07-05 10:55:27 -0600270{
Stephen Warren896637a2012-04-06 10:30:52 -0600271 struct tegra20_spdif *spdif;
Stephen Warren774fec32011-07-05 10:55:27 -0600272 struct resource *mem, *memregion, *dmareq;
Stephen Warren5939ae72012-04-13 12:14:07 -0600273 void __iomem *regs;
Stephen Warren774fec32011-07-05 10:55:27 -0600274 int ret;
275
Stephen Warren17933db2012-04-06 11:14:04 -0600276 spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif),
277 GFP_KERNEL);
Stephen Warren774fec32011-07-05 10:55:27 -0600278 if (!spdif) {
Stephen Warren896637a2012-04-06 10:30:52 -0600279 dev_err(&pdev->dev, "Can't allocate tegra20_spdif\n");
Stephen Warren774fec32011-07-05 10:55:27 -0600280 ret = -ENOMEM;
Stephen Warren17933db2012-04-06 11:14:04 -0600281 goto err;
Stephen Warren774fec32011-07-05 10:55:27 -0600282 }
283 dev_set_drvdata(&pdev->dev, spdif);
284
285 spdif->clk_spdif_out = clk_get(&pdev->dev, "spdif_out");
286 if (IS_ERR(spdif->clk_spdif_out)) {
287 pr_err("Can't retrieve spdif clock\n");
288 ret = PTR_ERR(spdif->clk_spdif_out);
Stephen Warren17933db2012-04-06 11:14:04 -0600289 goto err;
Stephen Warren774fec32011-07-05 10:55:27 -0600290 }
291
292 mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
293 if (!mem) {
294 dev_err(&pdev->dev, "No memory resource\n");
295 ret = -ENODEV;
296 goto err_clk_put;
297 }
298
299 dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0);
300 if (!dmareq) {
301 dev_err(&pdev->dev, "No DMA resource\n");
302 ret = -ENODEV;
303 goto err_clk_put;
304 }
305
Stephen Warren17933db2012-04-06 11:14:04 -0600306 memregion = devm_request_mem_region(&pdev->dev, mem->start,
307 resource_size(mem), DRV_NAME);
Stephen Warren774fec32011-07-05 10:55:27 -0600308 if (!memregion) {
309 dev_err(&pdev->dev, "Memory region already claimed\n");
310 ret = -EBUSY;
311 goto err_clk_put;
312 }
313
Stephen Warren5939ae72012-04-13 12:14:07 -0600314 regs = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
315 if (!regs) {
Stephen Warren774fec32011-07-05 10:55:27 -0600316 dev_err(&pdev->dev, "ioremap failed\n");
317 ret = -ENOMEM;
Stephen Warren17933db2012-04-06 11:14:04 -0600318 goto err_clk_put;
Stephen Warren774fec32011-07-05 10:55:27 -0600319 }
320
Stephen Warren5939ae72012-04-13 12:14:07 -0600321 spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
322 &tegra20_spdif_regmap_config);
323 if (IS_ERR(spdif->regmap)) {
324 dev_err(&pdev->dev, "regmap init failed\n");
325 ret = PTR_ERR(spdif->regmap);
326 goto err_clk_put;
327 }
328
Stephen Warren896637a2012-04-06 10:30:52 -0600329 spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT;
Stephen Warren774fec32011-07-05 10:55:27 -0600330 spdif->playback_dma_data.wrap = 4;
331 spdif->playback_dma_data.width = 32;
332 spdif->playback_dma_data.req_sel = dmareq->start;
333
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600334 pm_runtime_enable(&pdev->dev);
335 if (!pm_runtime_enabled(&pdev->dev)) {
336 ret = tegra20_spdif_runtime_resume(&pdev->dev);
337 if (ret)
338 goto err_pm_disable;
339 }
340
Stephen Warren896637a2012-04-06 10:30:52 -0600341 ret = snd_soc_register_dai(&pdev->dev, &tegra20_spdif_dai);
Stephen Warren774fec32011-07-05 10:55:27 -0600342 if (ret) {
343 dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
344 ret = -ENOMEM;
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600345 goto err_suspend;
Stephen Warren774fec32011-07-05 10:55:27 -0600346 }
347
Stephen Warren518de862012-03-20 14:55:49 -0600348 ret = tegra_pcm_platform_register(&pdev->dev);
349 if (ret) {
350 dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
351 goto err_unregister_dai;
352 }
353
Stephen Warren774fec32011-07-05 10:55:27 -0600354 return 0;
355
Stephen Warren518de862012-03-20 14:55:49 -0600356err_unregister_dai:
357 snd_soc_unregister_dai(&pdev->dev);
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600358err_suspend:
359 if (!pm_runtime_status_suspended(&pdev->dev))
360 tegra20_spdif_runtime_suspend(&pdev->dev);
361err_pm_disable:
362 pm_runtime_disable(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600363err_clk_put:
364 clk_put(spdif->clk_spdif_out);
Stephen Warren17933db2012-04-06 11:14:04 -0600365err:
Stephen Warren774fec32011-07-05 10:55:27 -0600366 return ret;
367}
368
Stephen Warren896637a2012-04-06 10:30:52 -0600369static int __devexit tegra20_spdif_platform_remove(struct platform_device *pdev)
Stephen Warren774fec32011-07-05 10:55:27 -0600370{
Stephen Warren896637a2012-04-06 10:30:52 -0600371 struct tegra20_spdif *spdif = dev_get_drvdata(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600372
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600373 pm_runtime_disable(&pdev->dev);
374 if (!pm_runtime_status_suspended(&pdev->dev))
375 tegra20_spdif_runtime_suspend(&pdev->dev);
376
Stephen Warren518de862012-03-20 14:55:49 -0600377 tegra_pcm_platform_unregister(&pdev->dev);
Stephen Warren774fec32011-07-05 10:55:27 -0600378 snd_soc_unregister_dai(&pdev->dev);
379
Stephen Warren774fec32011-07-05 10:55:27 -0600380 clk_put(spdif->clk_spdif_out);
381
Stephen Warren774fec32011-07-05 10:55:27 -0600382 return 0;
383}
384
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600385static const struct dev_pm_ops tegra20_spdif_pm_ops __devinitconst = {
386 SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend,
387 tegra20_spdif_runtime_resume, NULL)
388};
389
Stephen Warren896637a2012-04-06 10:30:52 -0600390static struct platform_driver tegra20_spdif_driver = {
Stephen Warren774fec32011-07-05 10:55:27 -0600391 .driver = {
392 .name = DRV_NAME,
393 .owner = THIS_MODULE,
Stephen Warren82ef0ae2012-04-09 09:52:22 -0600394 .pm = &tegra20_spdif_pm_ops,
Stephen Warren774fec32011-07-05 10:55:27 -0600395 },
Stephen Warren896637a2012-04-06 10:30:52 -0600396 .probe = tegra20_spdif_platform_probe,
397 .remove = __devexit_p(tegra20_spdif_platform_remove),
Stephen Warren774fec32011-07-05 10:55:27 -0600398};
399
Stephen Warren896637a2012-04-06 10:30:52 -0600400module_platform_driver(tegra20_spdif_driver);
Stephen Warren774fec32011-07-05 10:55:27 -0600401
402MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
Stephen Warren896637a2012-04-06 10:30:52 -0600403MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver");
Stephen Warren774fec32011-07-05 10:55:27 -0600404MODULE_LICENSE("GPL");
405MODULE_ALIAS("platform:" DRV_NAME);