Commit | Line | Data |
---|---|---|
2b27bdcc | 1 | // SPDX-License-Identifier: GPL-2.0-only |
774fec33 | 2 | /* |
ef280d39 | 3 | * tegra20_spdif.c - Tegra20 SPDIF driver |
774fec33 SW |
4 | * |
5 | * Author: Stephen Warren <swarren@nvidia.com> | |
518de86b | 6 | * Copyright (C) 2011-2012 - NVIDIA, Inc. |
774fec33 SW |
7 | */ |
8 | ||
9 | #include <linux/clk.h> | |
774fec33 | 10 | #include <linux/device.h> |
7613c508 SW |
11 | #include <linux/io.h> |
12 | #include <linux/module.h> | |
c0000fc6 | 13 | #include <linux/of_device.h> |
774fec33 | 14 | #include <linux/platform_device.h> |
82ef0ae4 | 15 | #include <linux/pm_runtime.h> |
5939ae74 | 16 | #include <linux/regmap.h> |
774fec33 | 17 | #include <linux/slab.h> |
774fec33 SW |
18 | #include <sound/core.h> |
19 | #include <sound/pcm.h> | |
20 | #include <sound/pcm_params.h> | |
21 | #include <sound/soc.h> | |
3489d506 | 22 | #include <sound/dmaengine_pcm.h> |
774fec33 | 23 | |
ef280d39 | 24 | #include "tegra20_spdif.h" |
774fec33 | 25 | |
896637ac | 26 | #define DRV_NAME "tegra20-spdif" |
774fec33 | 27 | |
ccd4cc3e | 28 | static __maybe_unused int tegra20_spdif_runtime_suspend(struct device *dev) |
82ef0ae4 SW |
29 | { |
30 | struct tegra20_spdif *spdif = dev_get_drvdata(dev); | |
31 | ||
65d2bdd3 | 32 | clk_disable_unprepare(spdif->clk_spdif_out); |
82ef0ae4 SW |
33 | |
34 | return 0; | |
35 | } | |
36 | ||
ccd4cc3e | 37 | static __maybe_unused int tegra20_spdif_runtime_resume(struct device *dev) |
82ef0ae4 SW |
38 | { |
39 | struct tegra20_spdif *spdif = dev_get_drvdata(dev); | |
40 | int ret; | |
41 | ||
65d2bdd3 | 42 | ret = clk_prepare_enable(spdif->clk_spdif_out); |
82ef0ae4 SW |
43 | if (ret) { |
44 | dev_err(dev, "clk_enable failed: %d\n", ret); | |
45 | return ret; | |
46 | } | |
47 | ||
48 | return 0; | |
49 | } | |
50 | ||
896637ac | 51 | static int tegra20_spdif_hw_params(struct snd_pcm_substream *substream, |
774fec33 SW |
52 | struct snd_pcm_hw_params *params, |
53 | struct snd_soc_dai *dai) | |
54 | { | |
c92a40e3 | 55 | struct device *dev = dai->dev; |
896637ac | 56 | struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); |
241bf433 | 57 | unsigned int mask = 0, val = 0; |
4b8713fd | 58 | int ret, spdifclock; |
774fec33 | 59 | |
241bf433 SW |
60 | mask |= TEGRA20_SPDIF_CTRL_PACK | |
61 | TEGRA20_SPDIF_CTRL_BIT_MODE_MASK; | |
774fec33 SW |
62 | switch (params_format(params)) { |
63 | case SNDRV_PCM_FORMAT_S16_LE: | |
241bf433 SW |
64 | val |= TEGRA20_SPDIF_CTRL_PACK | |
65 | TEGRA20_SPDIF_CTRL_BIT_MODE_16BIT; | |
774fec33 SW |
66 | break; |
67 | default: | |
68 | return -EINVAL; | |
69 | } | |
70 | ||
0f163546 SW |
71 | regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, mask, val); |
72 | ||
16736a02 DO |
73 | /* |
74 | * FIFO trigger level must be bigger than DMA burst or equal to it, | |
75 | * otherwise data is discarded on overflow. | |
76 | */ | |
77 | regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_DATA_FIFO_CSR, | |
78 | TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_MASK, | |
79 | TEGRA20_SPDIF_DATA_FIFO_CSR_TX_ATN_LVL_TU4_WORD_FULL); | |
80 | ||
774fec33 SW |
81 | switch (params_rate(params)) { |
82 | case 32000: | |
83 | spdifclock = 4096000; | |
84 | break; | |
85 | case 44100: | |
86 | spdifclock = 5644800; | |
87 | break; | |
88 | case 48000: | |
89 | spdifclock = 6144000; | |
90 | break; | |
91 | case 88200: | |
92 | spdifclock = 11289600; | |
93 | break; | |
94 | case 96000: | |
95 | spdifclock = 12288000; | |
96 | break; | |
97 | case 176400: | |
98 | spdifclock = 22579200; | |
99 | break; | |
100 | case 192000: | |
101 | spdifclock = 24576000; | |
102 | break; | |
103 | default: | |
104 | return -EINVAL; | |
105 | } | |
106 | ||
107 | ret = clk_set_rate(spdif->clk_spdif_out, spdifclock); | |
108 | if (ret) { | |
109 | dev_err(dev, "Can't set SPDIF clock rate: %d\n", ret); | |
110 | return ret; | |
111 | } | |
112 | ||
113 | return 0; | |
114 | } | |
115 | ||
896637ac | 116 | static void tegra20_spdif_start_playback(struct tegra20_spdif *spdif) |
774fec33 | 117 | { |
0f163546 SW |
118 | regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, |
119 | TEGRA20_SPDIF_CTRL_TX_EN, | |
120 | TEGRA20_SPDIF_CTRL_TX_EN); | |
774fec33 SW |
121 | } |
122 | ||
896637ac | 123 | static void tegra20_spdif_stop_playback(struct tegra20_spdif *spdif) |
774fec33 | 124 | { |
0f163546 SW |
125 | regmap_update_bits(spdif->regmap, TEGRA20_SPDIF_CTRL, |
126 | TEGRA20_SPDIF_CTRL_TX_EN, 0); | |
774fec33 SW |
127 | } |
128 | ||
896637ac | 129 | static int tegra20_spdif_trigger(struct snd_pcm_substream *substream, int cmd, |
774fec33 SW |
130 | struct snd_soc_dai *dai) |
131 | { | |
896637ac | 132 | struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); |
774fec33 SW |
133 | |
134 | switch (cmd) { | |
135 | case SNDRV_PCM_TRIGGER_START: | |
136 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
137 | case SNDRV_PCM_TRIGGER_RESUME: | |
896637ac | 138 | tegra20_spdif_start_playback(spdif); |
774fec33 SW |
139 | break; |
140 | case SNDRV_PCM_TRIGGER_STOP: | |
141 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
142 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
896637ac | 143 | tegra20_spdif_stop_playback(spdif); |
774fec33 SW |
144 | break; |
145 | default: | |
146 | return -EINVAL; | |
147 | } | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
896637ac | 152 | static int tegra20_spdif_probe(struct snd_soc_dai *dai) |
774fec33 | 153 | { |
896637ac | 154 | struct tegra20_spdif *spdif = snd_soc_dai_get_drvdata(dai); |
774fec33 SW |
155 | |
156 | dai->capture_dma_data = NULL; | |
157 | dai->playback_dma_data = &spdif->playback_dma_data; | |
158 | ||
159 | return 0; | |
160 | } | |
161 | ||
896637ac SW |
162 | static const struct snd_soc_dai_ops tegra20_spdif_dai_ops = { |
163 | .hw_params = tegra20_spdif_hw_params, | |
164 | .trigger = tegra20_spdif_trigger, | |
774fec33 SW |
165 | }; |
166 | ||
896637ac | 167 | static struct snd_soc_dai_driver tegra20_spdif_dai = { |
774fec33 | 168 | .name = DRV_NAME, |
896637ac | 169 | .probe = tegra20_spdif_probe, |
774fec33 | 170 | .playback = { |
9515c101 | 171 | .stream_name = "Playback", |
774fec33 SW |
172 | .channels_min = 2, |
173 | .channels_max = 2, | |
174 | .rates = SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | | |
175 | SNDRV_PCM_RATE_48000, | |
176 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | |
177 | }, | |
896637ac | 178 | .ops = &tegra20_spdif_dai_ops, |
774fec33 SW |
179 | }; |
180 | ||
094e1a3d KM |
181 | static const struct snd_soc_component_driver tegra20_spdif_component = { |
182 | .name = DRV_NAME, | |
183 | }; | |
184 | ||
5939ae74 SW |
185 | static bool tegra20_spdif_wr_rd_reg(struct device *dev, unsigned int reg) |
186 | { | |
187 | switch (reg) { | |
188 | case TEGRA20_SPDIF_CTRL: | |
189 | case TEGRA20_SPDIF_STATUS: | |
190 | case TEGRA20_SPDIF_STROBE_CTRL: | |
191 | case TEGRA20_SPDIF_DATA_FIFO_CSR: | |
192 | case TEGRA20_SPDIF_DATA_OUT: | |
193 | case TEGRA20_SPDIF_DATA_IN: | |
194 | case TEGRA20_SPDIF_CH_STA_RX_A: | |
195 | case TEGRA20_SPDIF_CH_STA_RX_B: | |
196 | case TEGRA20_SPDIF_CH_STA_RX_C: | |
197 | case TEGRA20_SPDIF_CH_STA_RX_D: | |
198 | case TEGRA20_SPDIF_CH_STA_RX_E: | |
199 | case TEGRA20_SPDIF_CH_STA_RX_F: | |
200 | case TEGRA20_SPDIF_CH_STA_TX_A: | |
201 | case TEGRA20_SPDIF_CH_STA_TX_B: | |
202 | case TEGRA20_SPDIF_CH_STA_TX_C: | |
203 | case TEGRA20_SPDIF_CH_STA_TX_D: | |
204 | case TEGRA20_SPDIF_CH_STA_TX_E: | |
205 | case TEGRA20_SPDIF_CH_STA_TX_F: | |
206 | case TEGRA20_SPDIF_USR_STA_RX_A: | |
207 | case TEGRA20_SPDIF_USR_DAT_TX_A: | |
208 | return true; | |
209 | default: | |
210 | return false; | |
1d198f26 | 211 | } |
5939ae74 SW |
212 | } |
213 | ||
214 | static bool tegra20_spdif_volatile_reg(struct device *dev, unsigned int reg) | |
215 | { | |
216 | switch (reg) { | |
217 | case TEGRA20_SPDIF_STATUS: | |
218 | case TEGRA20_SPDIF_DATA_FIFO_CSR: | |
219 | case TEGRA20_SPDIF_DATA_OUT: | |
220 | case TEGRA20_SPDIF_DATA_IN: | |
221 | case TEGRA20_SPDIF_CH_STA_RX_A: | |
222 | case TEGRA20_SPDIF_CH_STA_RX_B: | |
223 | case TEGRA20_SPDIF_CH_STA_RX_C: | |
224 | case TEGRA20_SPDIF_CH_STA_RX_D: | |
225 | case TEGRA20_SPDIF_CH_STA_RX_E: | |
226 | case TEGRA20_SPDIF_CH_STA_RX_F: | |
227 | case TEGRA20_SPDIF_USR_STA_RX_A: | |
228 | case TEGRA20_SPDIF_USR_DAT_TX_A: | |
229 | return true; | |
230 | default: | |
231 | return false; | |
1d198f26 | 232 | } |
5939ae74 SW |
233 | } |
234 | ||
235 | static bool tegra20_spdif_precious_reg(struct device *dev, unsigned int reg) | |
236 | { | |
237 | switch (reg) { | |
238 | case TEGRA20_SPDIF_DATA_OUT: | |
239 | case TEGRA20_SPDIF_DATA_IN: | |
240 | case TEGRA20_SPDIF_USR_STA_RX_A: | |
241 | case TEGRA20_SPDIF_USR_DAT_TX_A: | |
242 | return true; | |
243 | default: | |
244 | return false; | |
1d198f26 | 245 | } |
5939ae74 SW |
246 | } |
247 | ||
248 | static const struct regmap_config tegra20_spdif_regmap_config = { | |
249 | .reg_bits = 32, | |
250 | .reg_stride = 4, | |
251 | .val_bits = 32, | |
252 | .max_register = TEGRA20_SPDIF_USR_DAT_TX_A, | |
253 | .writeable_reg = tegra20_spdif_wr_rd_reg, | |
254 | .readable_reg = tegra20_spdif_wr_rd_reg, | |
255 | .volatile_reg = tegra20_spdif_volatile_reg, | |
256 | .precious_reg = tegra20_spdif_precious_reg, | |
591d14f0 | 257 | .cache_type = REGCACHE_FLAT, |
5939ae74 SW |
258 | }; |
259 | ||
4652a0d0 | 260 | static int tegra20_spdif_platform_probe(struct platform_device *pdev) |
774fec33 | 261 | { |
896637ac | 262 | struct tegra20_spdif *spdif; |
f57ddcdf | 263 | struct resource *mem, *dmareq; |
5939ae74 | 264 | void __iomem *regs; |
774fec33 SW |
265 | int ret; |
266 | ||
17933db2 SW |
267 | spdif = devm_kzalloc(&pdev->dev, sizeof(struct tegra20_spdif), |
268 | GFP_KERNEL); | |
e2c187a6 | 269 | if (!spdif) |
470805eb | 270 | return -ENOMEM; |
e2c187a6 | 271 | |
774fec33 SW |
272 | dev_set_drvdata(&pdev->dev, spdif); |
273 | ||
c0000fc6 | 274 | spdif->clk_spdif_out = devm_clk_get(&pdev->dev, "out"); |
774fec33 SW |
275 | if (IS_ERR(spdif->clk_spdif_out)) { |
276 | pr_err("Can't retrieve spdif clock\n"); | |
277 | ret = PTR_ERR(spdif->clk_spdif_out); | |
470805eb | 278 | return ret; |
774fec33 SW |
279 | } |
280 | ||
8d81f0da | 281 | regs = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); |
f57ddcdf AL |
282 | if (IS_ERR(regs)) |
283 | return PTR_ERR(regs); | |
774fec33 SW |
284 | |
285 | dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); | |
286 | if (!dmareq) { | |
287 | dev_err(&pdev->dev, "No DMA resource\n"); | |
470805eb | 288 | return -ENODEV; |
774fec33 SW |
289 | } |
290 | ||
5939ae74 SW |
291 | spdif->regmap = devm_regmap_init_mmio(&pdev->dev, regs, |
292 | &tegra20_spdif_regmap_config); | |
293 | if (IS_ERR(spdif->regmap)) { | |
294 | dev_err(&pdev->dev, "regmap init failed\n"); | |
295 | ret = PTR_ERR(spdif->regmap); | |
470805eb | 296 | return ret; |
5939ae74 SW |
297 | } |
298 | ||
896637ac | 299 | spdif->playback_dma_data.addr = mem->start + TEGRA20_SPDIF_DATA_OUT; |
647ab784 RZ |
300 | spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
301 | spdif->playback_dma_data.maxburst = 4; | |
774fec33 | 302 | |
82ef0ae4 | 303 | pm_runtime_enable(&pdev->dev); |
82ef0ae4 | 304 | |
1d15f21d VT |
305 | ret = snd_soc_register_component(&pdev->dev, &tegra20_spdif_component, |
306 | &tegra20_spdif_dai, 1); | |
774fec33 SW |
307 | if (ret) { |
308 | dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); | |
309 | ret = -ENOMEM; | |
c53b396f | 310 | goto err_pm_disable; |
774fec33 SW |
311 | } |
312 | ||
518de86b SW |
313 | ret = tegra_pcm_platform_register(&pdev->dev); |
314 | if (ret) { | |
315 | dev_err(&pdev->dev, "Could not register PCM: %d\n", ret); | |
1d15f21d | 316 | goto err_unregister_component; |
518de86b SW |
317 | } |
318 | ||
774fec33 SW |
319 | return 0; |
320 | ||
1d15f21d VT |
321 | err_unregister_component: |
322 | snd_soc_unregister_component(&pdev->dev); | |
82ef0ae4 SW |
323 | err_pm_disable: |
324 | pm_runtime_disable(&pdev->dev); | |
470805eb | 325 | |
774fec33 SW |
326 | return ret; |
327 | } | |
328 | ||
4652a0d0 | 329 | static int tegra20_spdif_platform_remove(struct platform_device *pdev) |
774fec33 | 330 | { |
0911f154 DO |
331 | tegra_pcm_platform_unregister(&pdev->dev); |
332 | snd_soc_unregister_component(&pdev->dev); | |
333 | ||
82ef0ae4 | 334 | pm_runtime_disable(&pdev->dev); |
82ef0ae4 | 335 | |
774fec33 SW |
336 | return 0; |
337 | } | |
338 | ||
f6e65744 | 339 | static const struct dev_pm_ops tegra20_spdif_pm_ops = { |
82ef0ae4 SW |
340 | SET_RUNTIME_PM_OPS(tegra20_spdif_runtime_suspend, |
341 | tegra20_spdif_runtime_resume, NULL) | |
342 | }; | |
343 | ||
c0000fc6 DO |
344 | static const struct of_device_id tegra20_spdif_of_match[] = { |
345 | { .compatible = "nvidia,tegra20-spdif", }, | |
346 | {}, | |
347 | }; | |
348 | MODULE_DEVICE_TABLE(of, tegra20_spdif_of_match); | |
349 | ||
896637ac | 350 | static struct platform_driver tegra20_spdif_driver = { |
774fec33 SW |
351 | .driver = { |
352 | .name = DRV_NAME, | |
82ef0ae4 | 353 | .pm = &tegra20_spdif_pm_ops, |
c0000fc6 | 354 | .of_match_table = tegra20_spdif_of_match, |
774fec33 | 355 | }, |
896637ac | 356 | .probe = tegra20_spdif_platform_probe, |
4652a0d0 | 357 | .remove = tegra20_spdif_platform_remove, |
774fec33 SW |
358 | }; |
359 | ||
896637ac | 360 | module_platform_driver(tegra20_spdif_driver); |
774fec33 SW |
361 | |
362 | MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); | |
896637ac | 363 | MODULE_DESCRIPTION("Tegra20 SPDIF ASoC driver"); |
774fec33 | 364 | MODULE_LICENSE("GPL"); |