Commit | Line | Data |
---|---|---|
39582322 DH |
1 | /* |
2 | * IMG SPDIF output controller driver | |
3 | * | |
4 | * Copyright (C) 2015 Imagination Technologies Ltd. | |
5 | * | |
6 | * Author: Damien Horsley <Damien.Horsley@imgtec.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms and conditions of the GNU General Public License, | |
10 | * version 2, as published by the Free Software Foundation. | |
11 | */ | |
12 | ||
13 | #include <linux/clk.h> | |
14 | #include <linux/init.h> | |
15 | #include <linux/kernel.h> | |
16 | #include <linux/module.h> | |
17 | #include <linux/of.h> | |
18 | #include <linux/platform_device.h> | |
19 | #include <linux/pm_runtime.h> | |
20 | #include <linux/reset.h> | |
21 | ||
22 | #include <sound/core.h> | |
23 | #include <sound/dmaengine_pcm.h> | |
24 | #include <sound/initval.h> | |
25 | #include <sound/pcm.h> | |
26 | #include <sound/pcm_params.h> | |
27 | #include <sound/soc.h> | |
28 | ||
29 | #define IMG_SPDIF_OUT_TX_FIFO 0x0 | |
30 | ||
31 | #define IMG_SPDIF_OUT_CTL 0x4 | |
32 | #define IMG_SPDIF_OUT_CTL_FS_MASK BIT(4) | |
33 | #define IMG_SPDIF_OUT_CTL_CLK_MASK BIT(2) | |
34 | #define IMG_SPDIF_OUT_CTL_SRT_MASK BIT(0) | |
35 | ||
36 | #define IMG_SPDIF_OUT_CSL 0x14 | |
37 | ||
38 | #define IMG_SPDIF_OUT_CSH_UV 0x18 | |
39 | #define IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT 0 | |
40 | #define IMG_SPDIF_OUT_CSH_UV_CSH_MASK 0xff | |
41 | ||
42 | struct img_spdif_out { | |
43 | spinlock_t lock; | |
44 | void __iomem *base; | |
45 | struct clk *clk_sys; | |
46 | struct clk *clk_ref; | |
47 | struct snd_dmaengine_dai_dma_data dma_data; | |
48 | struct device *dev; | |
49 | struct reset_control *rst; | |
50 | }; | |
51 | ||
52 | static int img_spdif_out_suspend(struct device *dev) | |
53 | { | |
54 | struct img_spdif_out *spdif = dev_get_drvdata(dev); | |
55 | ||
56 | clk_disable_unprepare(spdif->clk_ref); | |
57 | ||
58 | return 0; | |
59 | } | |
60 | ||
61 | static int img_spdif_out_resume(struct device *dev) | |
62 | { | |
63 | struct img_spdif_out *spdif = dev_get_drvdata(dev); | |
64 | int ret; | |
65 | ||
66 | ret = clk_prepare_enable(spdif->clk_ref); | |
67 | if (ret) { | |
68 | dev_err(dev, "clk_enable failed: %d\n", ret); | |
69 | return ret; | |
70 | } | |
71 | ||
72 | return 0; | |
73 | } | |
74 | ||
75 | static inline void img_spdif_out_writel(struct img_spdif_out *spdif, u32 val, | |
76 | u32 reg) | |
77 | { | |
78 | writel(val, spdif->base + reg); | |
79 | } | |
80 | ||
81 | static inline u32 img_spdif_out_readl(struct img_spdif_out *spdif, u32 reg) | |
82 | { | |
83 | return readl(spdif->base + reg); | |
84 | } | |
85 | ||
86 | static void img_spdif_out_reset(struct img_spdif_out *spdif) | |
87 | { | |
88 | u32 ctl, status_low, status_high; | |
89 | ||
90 | ctl = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL) & | |
91 | ~IMG_SPDIF_OUT_CTL_SRT_MASK; | |
92 | status_low = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL); | |
93 | status_high = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); | |
94 | ||
95 | reset_control_assert(spdif->rst); | |
96 | reset_control_deassert(spdif->rst); | |
97 | ||
98 | img_spdif_out_writel(spdif, ctl, IMG_SPDIF_OUT_CTL); | |
99 | img_spdif_out_writel(spdif, status_low, IMG_SPDIF_OUT_CSL); | |
100 | img_spdif_out_writel(spdif, status_high, IMG_SPDIF_OUT_CSH_UV); | |
101 | } | |
102 | ||
103 | static int img_spdif_out_info(struct snd_kcontrol *kcontrol, | |
104 | struct snd_ctl_elem_info *uinfo) | |
105 | { | |
106 | uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; | |
107 | uinfo->count = 1; | |
108 | ||
109 | return 0; | |
110 | } | |
111 | ||
112 | static int img_spdif_out_get_status_mask(struct snd_kcontrol *kcontrol, | |
113 | struct snd_ctl_elem_value *ucontrol) | |
114 | { | |
115 | ucontrol->value.iec958.status[0] = 0xff; | |
116 | ucontrol->value.iec958.status[1] = 0xff; | |
117 | ucontrol->value.iec958.status[2] = 0xff; | |
118 | ucontrol->value.iec958.status[3] = 0xff; | |
119 | ucontrol->value.iec958.status[4] = 0xff; | |
120 | ||
121 | return 0; | |
122 | } | |
123 | ||
124 | static int img_spdif_out_get_status(struct snd_kcontrol *kcontrol, | |
125 | struct snd_ctl_elem_value *ucontrol) | |
126 | { | |
127 | struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); | |
128 | struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai); | |
129 | u32 reg; | |
130 | unsigned long flags; | |
131 | ||
132 | spin_lock_irqsave(&spdif->lock, flags); | |
133 | ||
134 | reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSL); | |
135 | ucontrol->value.iec958.status[0] = reg & 0xff; | |
136 | ucontrol->value.iec958.status[1] = (reg >> 8) & 0xff; | |
137 | ucontrol->value.iec958.status[2] = (reg >> 16) & 0xff; | |
138 | ucontrol->value.iec958.status[3] = (reg >> 24) & 0xff; | |
139 | ||
140 | reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); | |
141 | ucontrol->value.iec958.status[4] = | |
142 | (reg & IMG_SPDIF_OUT_CSH_UV_CSH_MASK) >> | |
143 | IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT; | |
144 | ||
145 | spin_unlock_irqrestore(&spdif->lock, flags); | |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | static int img_spdif_out_set_status(struct snd_kcontrol *kcontrol, | |
151 | struct snd_ctl_elem_value *ucontrol) | |
152 | { | |
153 | struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); | |
154 | struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(cpu_dai); | |
155 | u32 reg; | |
156 | unsigned long flags; | |
157 | ||
158 | reg = ((u32)ucontrol->value.iec958.status[3] << 24); | |
159 | reg |= ((u32)ucontrol->value.iec958.status[2] << 16); | |
160 | reg |= ((u32)ucontrol->value.iec958.status[1] << 8); | |
161 | reg |= (u32)ucontrol->value.iec958.status[0]; | |
162 | ||
163 | spin_lock_irqsave(&spdif->lock, flags); | |
164 | ||
165 | img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSL); | |
166 | ||
167 | reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CSH_UV); | |
168 | reg &= ~IMG_SPDIF_OUT_CSH_UV_CSH_MASK; | |
169 | reg |= (u32)ucontrol->value.iec958.status[4] << | |
170 | IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT; | |
171 | img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CSH_UV); | |
172 | ||
173 | spin_unlock_irqrestore(&spdif->lock, flags); | |
174 | ||
175 | return 0; | |
176 | } | |
177 | ||
178 | static struct snd_kcontrol_new img_spdif_out_controls[] = { | |
179 | { | |
180 | .access = SNDRV_CTL_ELEM_ACCESS_READ, | |
181 | .iface = SNDRV_CTL_ELEM_IFACE_PCM, | |
182 | .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK), | |
183 | .info = img_spdif_out_info, | |
184 | .get = img_spdif_out_get_status_mask | |
185 | }, | |
186 | { | |
187 | .iface = SNDRV_CTL_ELEM_IFACE_PCM, | |
188 | .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), | |
189 | .info = img_spdif_out_info, | |
190 | .get = img_spdif_out_get_status, | |
191 | .put = img_spdif_out_set_status | |
192 | } | |
193 | }; | |
194 | ||
195 | static int img_spdif_out_trigger(struct snd_pcm_substream *substream, int cmd, | |
196 | struct snd_soc_dai *dai) | |
197 | { | |
198 | struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); | |
199 | u32 reg; | |
200 | unsigned long flags; | |
201 | ||
202 | switch (cmd) { | |
203 | case SNDRV_PCM_TRIGGER_START: | |
204 | case SNDRV_PCM_TRIGGER_RESUME: | |
205 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | |
206 | reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL); | |
207 | reg |= IMG_SPDIF_OUT_CTL_SRT_MASK; | |
208 | img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL); | |
209 | break; | |
210 | case SNDRV_PCM_TRIGGER_STOP: | |
211 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
212 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | |
213 | spin_lock_irqsave(&spdif->lock, flags); | |
214 | img_spdif_out_reset(spdif); | |
215 | spin_unlock_irqrestore(&spdif->lock, flags); | |
216 | break; | |
217 | default: | |
218 | return -EINVAL; | |
219 | } | |
220 | ||
221 | return 0; | |
222 | } | |
223 | ||
224 | static int img_spdif_out_hw_params(struct snd_pcm_substream *substream, | |
225 | struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) | |
226 | { | |
227 | struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); | |
228 | unsigned int channels; | |
229 | long pre_div_a, pre_div_b, diff_a, diff_b, rate, clk_rate; | |
230 | u32 reg; | |
231 | snd_pcm_format_t format; | |
232 | ||
233 | rate = params_rate(params); | |
234 | format = params_format(params); | |
235 | channels = params_channels(params); | |
236 | ||
237 | dev_dbg(spdif->dev, "hw_params rate %ld channels %u format %u\n", | |
238 | rate, channels, format); | |
239 | ||
240 | if (format != SNDRV_PCM_FORMAT_S32_LE) | |
241 | return -EINVAL; | |
242 | ||
243 | if (channels != 2) | |
244 | return -EINVAL; | |
245 | ||
246 | pre_div_a = clk_round_rate(spdif->clk_ref, rate * 256); | |
247 | if (pre_div_a < 0) | |
248 | return pre_div_a; | |
249 | pre_div_b = clk_round_rate(spdif->clk_ref, rate * 384); | |
250 | if (pre_div_b < 0) | |
251 | return pre_div_b; | |
252 | ||
253 | diff_a = abs((pre_div_a / 256) - rate); | |
254 | diff_b = abs((pre_div_b / 384) - rate); | |
255 | ||
256 | /* If diffs are equal, use lower clock rate */ | |
257 | if (diff_a > diff_b) | |
258 | clk_set_rate(spdif->clk_ref, pre_div_b); | |
259 | else | |
260 | clk_set_rate(spdif->clk_ref, pre_div_a); | |
261 | ||
262 | /* | |
263 | * Another driver (eg machine driver) may have rejected the above | |
264 | * change. Get the current rate and set the register bit according to | |
265 | * the new min diff | |
266 | */ | |
267 | clk_rate = clk_get_rate(spdif->clk_ref); | |
268 | ||
269 | diff_a = abs((clk_rate / 256) - rate); | |
270 | diff_b = abs((clk_rate / 384) - rate); | |
271 | ||
272 | reg = img_spdif_out_readl(spdif, IMG_SPDIF_OUT_CTL); | |
273 | if (diff_a <= diff_b) | |
274 | reg &= ~IMG_SPDIF_OUT_CTL_CLK_MASK; | |
275 | else | |
276 | reg |= IMG_SPDIF_OUT_CTL_CLK_MASK; | |
277 | img_spdif_out_writel(spdif, reg, IMG_SPDIF_OUT_CTL); | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
282 | static const struct snd_soc_dai_ops img_spdif_out_dai_ops = { | |
283 | .trigger = img_spdif_out_trigger, | |
284 | .hw_params = img_spdif_out_hw_params | |
285 | }; | |
286 | ||
287 | static int img_spdif_out_dai_probe(struct snd_soc_dai *dai) | |
288 | { | |
289 | struct img_spdif_out *spdif = snd_soc_dai_get_drvdata(dai); | |
290 | ||
291 | snd_soc_dai_init_dma_data(dai, &spdif->dma_data, NULL); | |
292 | ||
293 | snd_soc_add_dai_controls(dai, img_spdif_out_controls, | |
294 | ARRAY_SIZE(img_spdif_out_controls)); | |
295 | ||
296 | return 0; | |
297 | } | |
298 | ||
299 | static struct snd_soc_dai_driver img_spdif_out_dai = { | |
300 | .probe = img_spdif_out_dai_probe, | |
301 | .playback = { | |
302 | .channels_min = 2, | |
303 | .channels_max = 2, | |
304 | .rates = SNDRV_PCM_RATE_8000_192000, | |
305 | .formats = SNDRV_PCM_FMTBIT_S32_LE | |
306 | }, | |
307 | .ops = &img_spdif_out_dai_ops | |
308 | }; | |
309 | ||
310 | static const struct snd_soc_component_driver img_spdif_out_component = { | |
311 | .name = "img-spdif-out" | |
312 | }; | |
313 | ||
314 | static int img_spdif_out_probe(struct platform_device *pdev) | |
315 | { | |
316 | struct img_spdif_out *spdif; | |
317 | struct resource *res; | |
318 | void __iomem *base; | |
319 | int ret; | |
320 | struct device *dev = &pdev->dev; | |
321 | ||
322 | spdif = devm_kzalloc(&pdev->dev, sizeof(*spdif), GFP_KERNEL); | |
323 | if (!spdif) | |
324 | return -ENOMEM; | |
325 | ||
326 | platform_set_drvdata(pdev, spdif); | |
327 | ||
328 | spdif->dev = &pdev->dev; | |
329 | ||
330 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
331 | base = devm_ioremap_resource(&pdev->dev, res); | |
332 | if (IS_ERR(base)) | |
333 | return PTR_ERR(base); | |
334 | ||
335 | spdif->base = base; | |
336 | ||
337 | spdif->rst = devm_reset_control_get(&pdev->dev, "rst"); | |
338 | if (IS_ERR(spdif->rst)) { | |
339 | if (PTR_ERR(spdif->rst) != -EPROBE_DEFER) | |
340 | dev_err(&pdev->dev, "No top level reset found\n"); | |
341 | return PTR_ERR(spdif->rst); | |
342 | } | |
343 | ||
344 | spdif->clk_sys = devm_clk_get(&pdev->dev, "sys"); | |
345 | if (IS_ERR(spdif->clk_sys)) { | |
346 | if (PTR_ERR(spdif->clk_sys) != -EPROBE_DEFER) | |
347 | dev_err(dev, "Failed to acquire clock 'sys'\n"); | |
348 | return PTR_ERR(spdif->clk_sys); | |
349 | } | |
350 | ||
351 | spdif->clk_ref = devm_clk_get(&pdev->dev, "ref"); | |
352 | if (IS_ERR(spdif->clk_ref)) { | |
353 | if (PTR_ERR(spdif->clk_ref) != -EPROBE_DEFER) | |
354 | dev_err(dev, "Failed to acquire clock 'ref'\n"); | |
355 | return PTR_ERR(spdif->clk_ref); | |
356 | } | |
357 | ||
358 | ret = clk_prepare_enable(spdif->clk_sys); | |
359 | if (ret) | |
360 | return ret; | |
361 | ||
362 | img_spdif_out_writel(spdif, IMG_SPDIF_OUT_CTL_FS_MASK, | |
363 | IMG_SPDIF_OUT_CTL); | |
364 | ||
365 | img_spdif_out_reset(spdif); | |
366 | ||
367 | pm_runtime_enable(&pdev->dev); | |
368 | if (!pm_runtime_enabled(&pdev->dev)) { | |
369 | ret = img_spdif_out_resume(&pdev->dev); | |
370 | if (ret) | |
371 | goto err_pm_disable; | |
372 | } | |
373 | ||
374 | spin_lock_init(&spdif->lock); | |
375 | ||
376 | spdif->dma_data.addr = res->start + IMG_SPDIF_OUT_TX_FIFO; | |
377 | spdif->dma_data.addr_width = 4; | |
378 | spdif->dma_data.maxburst = 4; | |
379 | ||
380 | ret = devm_snd_soc_register_component(&pdev->dev, | |
381 | &img_spdif_out_component, | |
382 | &img_spdif_out_dai, 1); | |
383 | if (ret) | |
384 | goto err_suspend; | |
385 | ||
386 | ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); | |
387 | if (ret) | |
388 | goto err_suspend; | |
389 | ||
390 | dev_dbg(&pdev->dev, "Probe successful\n"); | |
391 | ||
392 | return 0; | |
393 | ||
394 | err_suspend: | |
395 | if (!pm_runtime_status_suspended(&pdev->dev)) | |
396 | img_spdif_out_suspend(&pdev->dev); | |
397 | err_pm_disable: | |
398 | pm_runtime_disable(&pdev->dev); | |
399 | clk_disable_unprepare(spdif->clk_sys); | |
400 | ||
401 | return ret; | |
402 | } | |
403 | ||
404 | static int img_spdif_out_dev_remove(struct platform_device *pdev) | |
405 | { | |
406 | struct img_spdif_out *spdif = platform_get_drvdata(pdev); | |
407 | ||
408 | pm_runtime_disable(&pdev->dev); | |
409 | if (!pm_runtime_status_suspended(&pdev->dev)) | |
410 | img_spdif_out_suspend(&pdev->dev); | |
411 | ||
412 | clk_disable_unprepare(spdif->clk_sys); | |
413 | ||
414 | return 0; | |
415 | } | |
416 | ||
417 | static const struct of_device_id img_spdif_out_of_match[] = { | |
418 | { .compatible = "img,spdif-out" }, | |
419 | {} | |
420 | }; | |
421 | MODULE_DEVICE_TABLE(of, img_spdif_out_of_match); | |
422 | ||
423 | static const struct dev_pm_ops img_spdif_out_pm_ops = { | |
424 | SET_RUNTIME_PM_OPS(img_spdif_out_suspend, | |
425 | img_spdif_out_resume, NULL) | |
426 | }; | |
427 | ||
428 | static struct platform_driver img_spdif_out_driver = { | |
429 | .driver = { | |
430 | .name = "img-spdif-out", | |
431 | .of_match_table = img_spdif_out_of_match, | |
432 | .pm = &img_spdif_out_pm_ops | |
433 | }, | |
434 | .probe = img_spdif_out_probe, | |
435 | .remove = img_spdif_out_dev_remove | |
436 | }; | |
437 | module_platform_driver(img_spdif_out_driver); | |
438 | ||
439 | MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>"); | |
440 | MODULE_DESCRIPTION("IMG SPDIF Output driver"); | |
441 | MODULE_LICENSE("GPL v2"); |