1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* sound/soc/rockchip/rk_spdif.c |
3 | * |
4 | * ALSA SoC Audio Layer - Rockchip I2S Controller driver |
5 | * |
6 | * Copyright (c) 2014 Rockchip Electronics Co. Ltd. |
7 | * Author: Jianqun <jay.xu@rock-chips.com> |
8 | * Copyright (c) 2015 Collabora Ltd. |
9 | * Author: Sjoerd Simons <sjoerd.simons@collabora.co.uk> |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/delay.h> |
14 | #include <linux/of_gpio.h> |
15 | #include <linux/clk.h> |
16 | #include <linux/pm_runtime.h> |
17 | #include <linux/mfd/syscon.h> |
18 | #include <linux/regmap.h> |
19 | #include <sound/pcm_params.h> |
20 | #include <sound/dmaengine_pcm.h> |
21 | |
22 | #include "rockchip_spdif.h" |
23 | |
24 | enum rk_spdif_type { |
25 | RK_SPDIF_RK3066, |
26 | RK_SPDIF_RK3188, |
27 | RK_SPDIF_RK3288, |
28 | RK_SPDIF_RK3366, |
29 | }; |
30 | |
31 | #define RK3288_GRF_SOC_CON2 0x24c |
32 | |
33 | struct rk_spdif_dev { |
34 | struct device *dev; |
35 | |
36 | struct clk *mclk; |
37 | struct clk *hclk; |
38 | |
39 | struct snd_dmaengine_dai_dma_data playback_dma_data; |
40 | |
41 | struct regmap *regmap; |
42 | }; |
43 | |
44 | static const struct of_device_id rk_spdif_match[] __maybe_unused = { |
45 | { .compatible = "rockchip,rk3066-spdif" , |
46 | .data = (void *)RK_SPDIF_RK3066 }, |
47 | { .compatible = "rockchip,rk3188-spdif" , |
48 | .data = (void *)RK_SPDIF_RK3188 }, |
49 | { .compatible = "rockchip,rk3228-spdif" , |
50 | .data = (void *)RK_SPDIF_RK3366 }, |
51 | { .compatible = "rockchip,rk3288-spdif" , |
52 | .data = (void *)RK_SPDIF_RK3288 }, |
53 | { .compatible = "rockchip,rk3328-spdif" , |
54 | .data = (void *)RK_SPDIF_RK3366 }, |
55 | { .compatible = "rockchip,rk3366-spdif" , |
56 | .data = (void *)RK_SPDIF_RK3366 }, |
57 | { .compatible = "rockchip,rk3368-spdif" , |
58 | .data = (void *)RK_SPDIF_RK3366 }, |
59 | { .compatible = "rockchip,rk3399-spdif" , |
60 | .data = (void *)RK_SPDIF_RK3366 }, |
61 | { .compatible = "rockchip,rk3568-spdif" , |
62 | .data = (void *)RK_SPDIF_RK3366 }, |
63 | {}, |
64 | }; |
65 | MODULE_DEVICE_TABLE(of, rk_spdif_match); |
66 | |
67 | static int __maybe_unused rk_spdif_runtime_suspend(struct device *dev) |
68 | { |
69 | struct rk_spdif_dev *spdif = dev_get_drvdata(dev); |
70 | |
71 | regcache_cache_only(map: spdif->regmap, enable: true); |
72 | clk_disable_unprepare(clk: spdif->mclk); |
73 | clk_disable_unprepare(clk: spdif->hclk); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static int __maybe_unused rk_spdif_runtime_resume(struct device *dev) |
79 | { |
80 | struct rk_spdif_dev *spdif = dev_get_drvdata(dev); |
81 | int ret; |
82 | |
83 | ret = clk_prepare_enable(clk: spdif->mclk); |
84 | if (ret) { |
85 | dev_err(spdif->dev, "mclk clock enable failed %d\n" , ret); |
86 | return ret; |
87 | } |
88 | |
89 | ret = clk_prepare_enable(clk: spdif->hclk); |
90 | if (ret) { |
91 | clk_disable_unprepare(clk: spdif->mclk); |
92 | dev_err(spdif->dev, "hclk clock enable failed %d\n" , ret); |
93 | return ret; |
94 | } |
95 | |
96 | regcache_cache_only(map: spdif->regmap, enable: false); |
97 | regcache_mark_dirty(map: spdif->regmap); |
98 | |
99 | ret = regcache_sync(map: spdif->regmap); |
100 | if (ret) { |
101 | clk_disable_unprepare(clk: spdif->mclk); |
102 | clk_disable_unprepare(clk: spdif->hclk); |
103 | } |
104 | |
105 | return ret; |
106 | } |
107 | |
108 | static int rk_spdif_hw_params(struct snd_pcm_substream *substream, |
109 | struct snd_pcm_hw_params *params, |
110 | struct snd_soc_dai *dai) |
111 | { |
112 | struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); |
113 | unsigned int val = SPDIF_CFGR_HALFWORD_ENABLE; |
114 | int srate, mclk; |
115 | int ret; |
116 | |
117 | srate = params_rate(p: params); |
118 | mclk = srate * 128; |
119 | |
120 | switch (params_format(p: params)) { |
121 | case SNDRV_PCM_FORMAT_S16_LE: |
122 | val |= SPDIF_CFGR_VDW_16; |
123 | break; |
124 | case SNDRV_PCM_FORMAT_S20_3LE: |
125 | val |= SPDIF_CFGR_VDW_20; |
126 | break; |
127 | case SNDRV_PCM_FORMAT_S24_LE: |
128 | val |= SPDIF_CFGR_VDW_24; |
129 | break; |
130 | default: |
131 | return -EINVAL; |
132 | } |
133 | |
134 | /* Set clock and calculate divider */ |
135 | ret = clk_set_rate(clk: spdif->mclk, rate: mclk); |
136 | if (ret != 0) { |
137 | dev_err(spdif->dev, "Failed to set module clock rate: %d\n" , |
138 | ret); |
139 | return ret; |
140 | } |
141 | |
142 | ret = regmap_update_bits(map: spdif->regmap, SPDIF_CFGR, |
143 | SPDIF_CFGR_CLK_DIV_MASK | |
144 | SPDIF_CFGR_HALFWORD_ENABLE | |
145 | SDPIF_CFGR_VDW_MASK, val); |
146 | |
147 | return ret; |
148 | } |
149 | |
150 | static int rk_spdif_trigger(struct snd_pcm_substream *substream, |
151 | int cmd, struct snd_soc_dai *dai) |
152 | { |
153 | struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); |
154 | int ret; |
155 | |
156 | switch (cmd) { |
157 | case SNDRV_PCM_TRIGGER_START: |
158 | case SNDRV_PCM_TRIGGER_RESUME: |
159 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
160 | ret = regmap_update_bits(map: spdif->regmap, SPDIF_DMACR, |
161 | SPDIF_DMACR_TDE_ENABLE | |
162 | SPDIF_DMACR_TDL_MASK, |
163 | SPDIF_DMACR_TDE_ENABLE | |
164 | SPDIF_DMACR_TDL(16)); |
165 | |
166 | if (ret != 0) |
167 | return ret; |
168 | |
169 | ret = regmap_update_bits(map: spdif->regmap, SPDIF_XFER, |
170 | SPDIF_XFER_TXS_START, |
171 | SPDIF_XFER_TXS_START); |
172 | break; |
173 | case SNDRV_PCM_TRIGGER_SUSPEND: |
174 | case SNDRV_PCM_TRIGGER_STOP: |
175 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
176 | ret = regmap_update_bits(map: spdif->regmap, SPDIF_DMACR, |
177 | SPDIF_DMACR_TDE_ENABLE, |
178 | SPDIF_DMACR_TDE_DISABLE); |
179 | |
180 | if (ret != 0) |
181 | return ret; |
182 | |
183 | ret = regmap_update_bits(map: spdif->regmap, SPDIF_XFER, |
184 | SPDIF_XFER_TXS_START, |
185 | SPDIF_XFER_TXS_STOP); |
186 | break; |
187 | default: |
188 | ret = -EINVAL; |
189 | break; |
190 | } |
191 | |
192 | return ret; |
193 | } |
194 | |
195 | static int rk_spdif_dai_probe(struct snd_soc_dai *dai) |
196 | { |
197 | struct rk_spdif_dev *spdif = snd_soc_dai_get_drvdata(dai); |
198 | |
199 | snd_soc_dai_dma_data_set_playback(dai, &spdif->playback_dma_data); |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static const struct snd_soc_dai_ops rk_spdif_dai_ops = { |
205 | .probe = rk_spdif_dai_probe, |
206 | .hw_params = rk_spdif_hw_params, |
207 | .trigger = rk_spdif_trigger, |
208 | }; |
209 | |
210 | static struct snd_soc_dai_driver rk_spdif_dai = { |
211 | .playback = { |
212 | .stream_name = "Playback" , |
213 | .channels_min = 2, |
214 | .channels_max = 2, |
215 | .rates = (SNDRV_PCM_RATE_32000 | |
216 | SNDRV_PCM_RATE_44100 | |
217 | SNDRV_PCM_RATE_48000 | |
218 | SNDRV_PCM_RATE_96000 | |
219 | SNDRV_PCM_RATE_192000), |
220 | .formats = (SNDRV_PCM_FMTBIT_S16_LE | |
221 | SNDRV_PCM_FMTBIT_S20_3LE | |
222 | SNDRV_PCM_FMTBIT_S24_LE), |
223 | }, |
224 | .ops = &rk_spdif_dai_ops, |
225 | }; |
226 | |
227 | static const struct snd_soc_component_driver rk_spdif_component = { |
228 | .name = "rockchip-spdif" , |
229 | .legacy_dai_naming = 1, |
230 | }; |
231 | |
232 | static bool rk_spdif_wr_reg(struct device *dev, unsigned int reg) |
233 | { |
234 | switch (reg) { |
235 | case SPDIF_CFGR: |
236 | case SPDIF_DMACR: |
237 | case SPDIF_INTCR: |
238 | case SPDIF_XFER: |
239 | case SPDIF_SMPDR: |
240 | return true; |
241 | default: |
242 | return false; |
243 | } |
244 | } |
245 | |
246 | static bool rk_spdif_rd_reg(struct device *dev, unsigned int reg) |
247 | { |
248 | switch (reg) { |
249 | case SPDIF_CFGR: |
250 | case SPDIF_SDBLR: |
251 | case SPDIF_INTCR: |
252 | case SPDIF_INTSR: |
253 | case SPDIF_XFER: |
254 | case SPDIF_SMPDR: |
255 | return true; |
256 | default: |
257 | return false; |
258 | } |
259 | } |
260 | |
261 | static bool rk_spdif_volatile_reg(struct device *dev, unsigned int reg) |
262 | { |
263 | switch (reg) { |
264 | case SPDIF_INTSR: |
265 | case SPDIF_SDBLR: |
266 | case SPDIF_SMPDR: |
267 | return true; |
268 | default: |
269 | return false; |
270 | } |
271 | } |
272 | |
273 | static const struct regmap_config rk_spdif_regmap_config = { |
274 | .reg_bits = 32, |
275 | .reg_stride = 4, |
276 | .val_bits = 32, |
277 | .max_register = SPDIF_SMPDR, |
278 | .writeable_reg = rk_spdif_wr_reg, |
279 | .readable_reg = rk_spdif_rd_reg, |
280 | .volatile_reg = rk_spdif_volatile_reg, |
281 | .cache_type = REGCACHE_FLAT, |
282 | }; |
283 | |
284 | static int rk_spdif_probe(struct platform_device *pdev) |
285 | { |
286 | struct device_node *np = pdev->dev.of_node; |
287 | struct rk_spdif_dev *spdif; |
288 | const struct of_device_id *match; |
289 | struct resource *res; |
290 | void __iomem *regs; |
291 | int ret; |
292 | |
293 | match = of_match_node(matches: rk_spdif_match, node: np); |
294 | if (match->data == (void *)RK_SPDIF_RK3288) { |
295 | struct regmap *grf; |
296 | |
297 | grf = syscon_regmap_lookup_by_phandle(np, property: "rockchip,grf" ); |
298 | if (IS_ERR(ptr: grf)) { |
299 | dev_err(&pdev->dev, |
300 | "rockchip_spdif missing 'rockchip,grf'\n" ); |
301 | return PTR_ERR(ptr: grf); |
302 | } |
303 | |
304 | /* Select the 8 channel SPDIF solution on RK3288 as |
305 | * the 2 channel one does not appear to work |
306 | */ |
307 | regmap_write(map: grf, RK3288_GRF_SOC_CON2, BIT(1) << 16); |
308 | } |
309 | |
310 | spdif = devm_kzalloc(dev: &pdev->dev, size: sizeof(*spdif), GFP_KERNEL); |
311 | if (!spdif) |
312 | return -ENOMEM; |
313 | |
314 | spdif->hclk = devm_clk_get(dev: &pdev->dev, id: "hclk" ); |
315 | if (IS_ERR(ptr: spdif->hclk)) |
316 | return PTR_ERR(ptr: spdif->hclk); |
317 | |
318 | spdif->mclk = devm_clk_get(dev: &pdev->dev, id: "mclk" ); |
319 | if (IS_ERR(ptr: spdif->mclk)) |
320 | return PTR_ERR(ptr: spdif->mclk); |
321 | |
322 | regs = devm_platform_get_and_ioremap_resource(pdev, index: 0, res: &res); |
323 | if (IS_ERR(ptr: regs)) |
324 | return PTR_ERR(ptr: regs); |
325 | |
326 | spdif->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "hclk" , regs, |
327 | &rk_spdif_regmap_config); |
328 | if (IS_ERR(ptr: spdif->regmap)) |
329 | return PTR_ERR(ptr: spdif->regmap); |
330 | |
331 | spdif->playback_dma_data.addr = res->start + SPDIF_SMPDR; |
332 | spdif->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; |
333 | spdif->playback_dma_data.maxburst = 4; |
334 | |
335 | spdif->dev = &pdev->dev; |
336 | dev_set_drvdata(dev: &pdev->dev, data: spdif); |
337 | |
338 | pm_runtime_enable(dev: &pdev->dev); |
339 | if (!pm_runtime_enabled(dev: &pdev->dev)) { |
340 | ret = rk_spdif_runtime_resume(dev: &pdev->dev); |
341 | if (ret) |
342 | goto err_pm_runtime; |
343 | } |
344 | |
345 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
346 | component_driver: &rk_spdif_component, |
347 | dai_drv: &rk_spdif_dai, num_dai: 1); |
348 | if (ret) { |
349 | dev_err(&pdev->dev, "Could not register DAI\n" ); |
350 | goto err_pm_suspend; |
351 | } |
352 | |
353 | ret = devm_snd_dmaengine_pcm_register(dev: &pdev->dev, NULL, flags: 0); |
354 | if (ret) { |
355 | dev_err(&pdev->dev, "Could not register PCM\n" ); |
356 | goto err_pm_suspend; |
357 | } |
358 | |
359 | return 0; |
360 | |
361 | err_pm_suspend: |
362 | if (!pm_runtime_status_suspended(dev: &pdev->dev)) |
363 | rk_spdif_runtime_suspend(dev: &pdev->dev); |
364 | err_pm_runtime: |
365 | pm_runtime_disable(dev: &pdev->dev); |
366 | |
367 | return ret; |
368 | } |
369 | |
370 | static void rk_spdif_remove(struct platform_device *pdev) |
371 | { |
372 | pm_runtime_disable(dev: &pdev->dev); |
373 | if (!pm_runtime_status_suspended(dev: &pdev->dev)) |
374 | rk_spdif_runtime_suspend(dev: &pdev->dev); |
375 | } |
376 | |
377 | static const struct dev_pm_ops rk_spdif_pm_ops = { |
378 | SET_RUNTIME_PM_OPS(rk_spdif_runtime_suspend, rk_spdif_runtime_resume, |
379 | NULL) |
380 | }; |
381 | |
382 | static struct platform_driver rk_spdif_driver = { |
383 | .probe = rk_spdif_probe, |
384 | .remove_new = rk_spdif_remove, |
385 | .driver = { |
386 | .name = "rockchip-spdif" , |
387 | .of_match_table = of_match_ptr(rk_spdif_match), |
388 | .pm = &rk_spdif_pm_ops, |
389 | }, |
390 | }; |
391 | module_platform_driver(rk_spdif_driver); |
392 | |
393 | MODULE_ALIAS("platform:rockchip-spdif" ); |
394 | MODULE_DESCRIPTION("ROCKCHIP SPDIF transceiver Interface" ); |
395 | MODULE_AUTHOR("Sjoerd Simons <sjoerd.simons@collabora.co.uk>" ); |
396 | MODULE_LICENSE("GPL v2" ); |
397 | |