2 * IMG SPDIF output controller driver
4 * Copyright (C) 2015 Imagination Technologies Ltd.
6 * Author: Damien Horsley <Damien.Horsley@imgtec.com>
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.
13 #include <linux/clk.h>
14 #include <linux/init.h>
15 #include <linux/kernel.h>
16 #include <linux/module.h>
18 #include <linux/platform_device.h>
19 #include <linux/pm_runtime.h>
20 #include <linux/reset.h>
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>
29 #define IMG_SPDIF_OUT_TX_FIFO 0x0
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)
36 #define IMG_SPDIF_OUT_CSL 0x14
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
42 struct img_spdif_out
{
47 struct snd_dmaengine_dai_dma_data dma_data
;
49 struct reset_control
*rst
;
55 static int img_spdif_out_runtime_suspend(struct device
*dev
)
57 struct img_spdif_out
*spdif
= dev_get_drvdata(dev
);
59 clk_disable_unprepare(spdif
->clk_ref
);
60 clk_disable_unprepare(spdif
->clk_sys
);
65 static int img_spdif_out_runtime_resume(struct device
*dev
)
67 struct img_spdif_out
*spdif
= dev_get_drvdata(dev
);
70 ret
= clk_prepare_enable(spdif
->clk_sys
);
72 dev_err(dev
, "clk_enable failed: %d\n", ret
);
76 ret
= clk_prepare_enable(spdif
->clk_ref
);
78 dev_err(dev
, "clk_enable failed: %d\n", ret
);
79 clk_disable_unprepare(spdif
->clk_sys
);
86 static inline void img_spdif_out_writel(struct img_spdif_out
*spdif
, u32 val
,
89 writel(val
, spdif
->base
+ reg
);
92 static inline u32
img_spdif_out_readl(struct img_spdif_out
*spdif
, u32 reg
)
94 return readl(spdif
->base
+ reg
);
97 static void img_spdif_out_reset(struct img_spdif_out
*spdif
)
99 u32 ctl
, status_low
, status_high
;
101 ctl
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CTL
) &
102 ~IMG_SPDIF_OUT_CTL_SRT_MASK
;
103 status_low
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CSL
);
104 status_high
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CSH_UV
);
106 reset_control_assert(spdif
->rst
);
107 reset_control_deassert(spdif
->rst
);
109 img_spdif_out_writel(spdif
, ctl
, IMG_SPDIF_OUT_CTL
);
110 img_spdif_out_writel(spdif
, status_low
, IMG_SPDIF_OUT_CSL
);
111 img_spdif_out_writel(spdif
, status_high
, IMG_SPDIF_OUT_CSH_UV
);
114 static int img_spdif_out_info(struct snd_kcontrol
*kcontrol
,
115 struct snd_ctl_elem_info
*uinfo
)
117 uinfo
->type
= SNDRV_CTL_ELEM_TYPE_IEC958
;
123 static int img_spdif_out_get_status_mask(struct snd_kcontrol
*kcontrol
,
124 struct snd_ctl_elem_value
*ucontrol
)
126 ucontrol
->value
.iec958
.status
[0] = 0xff;
127 ucontrol
->value
.iec958
.status
[1] = 0xff;
128 ucontrol
->value
.iec958
.status
[2] = 0xff;
129 ucontrol
->value
.iec958
.status
[3] = 0xff;
130 ucontrol
->value
.iec958
.status
[4] = 0xff;
135 static int img_spdif_out_get_status(struct snd_kcontrol
*kcontrol
,
136 struct snd_ctl_elem_value
*ucontrol
)
138 struct snd_soc_dai
*cpu_dai
= snd_kcontrol_chip(kcontrol
);
139 struct img_spdif_out
*spdif
= snd_soc_dai_get_drvdata(cpu_dai
);
143 spin_lock_irqsave(&spdif
->lock
, flags
);
145 reg
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CSL
);
146 ucontrol
->value
.iec958
.status
[0] = reg
& 0xff;
147 ucontrol
->value
.iec958
.status
[1] = (reg
>> 8) & 0xff;
148 ucontrol
->value
.iec958
.status
[2] = (reg
>> 16) & 0xff;
149 ucontrol
->value
.iec958
.status
[3] = (reg
>> 24) & 0xff;
151 reg
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CSH_UV
);
152 ucontrol
->value
.iec958
.status
[4] =
153 (reg
& IMG_SPDIF_OUT_CSH_UV_CSH_MASK
) >>
154 IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT
;
156 spin_unlock_irqrestore(&spdif
->lock
, flags
);
161 static int img_spdif_out_set_status(struct snd_kcontrol
*kcontrol
,
162 struct snd_ctl_elem_value
*ucontrol
)
164 struct snd_soc_dai
*cpu_dai
= snd_kcontrol_chip(kcontrol
);
165 struct img_spdif_out
*spdif
= snd_soc_dai_get_drvdata(cpu_dai
);
169 reg
= ((u32
)ucontrol
->value
.iec958
.status
[3] << 24);
170 reg
|= ((u32
)ucontrol
->value
.iec958
.status
[2] << 16);
171 reg
|= ((u32
)ucontrol
->value
.iec958
.status
[1] << 8);
172 reg
|= (u32
)ucontrol
->value
.iec958
.status
[0];
174 spin_lock_irqsave(&spdif
->lock
, flags
);
176 img_spdif_out_writel(spdif
, reg
, IMG_SPDIF_OUT_CSL
);
178 reg
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CSH_UV
);
179 reg
&= ~IMG_SPDIF_OUT_CSH_UV_CSH_MASK
;
180 reg
|= (u32
)ucontrol
->value
.iec958
.status
[4] <<
181 IMG_SPDIF_OUT_CSH_UV_CSH_SHIFT
;
182 img_spdif_out_writel(spdif
, reg
, IMG_SPDIF_OUT_CSH_UV
);
184 spin_unlock_irqrestore(&spdif
->lock
, flags
);
189 static struct snd_kcontrol_new img_spdif_out_controls
[] = {
191 .access
= SNDRV_CTL_ELEM_ACCESS_READ
,
192 .iface
= SNDRV_CTL_ELEM_IFACE_PCM
,
193 .name
= SNDRV_CTL_NAME_IEC958("", PLAYBACK
, MASK
),
194 .info
= img_spdif_out_info
,
195 .get
= img_spdif_out_get_status_mask
198 .iface
= SNDRV_CTL_ELEM_IFACE_PCM
,
199 .name
= SNDRV_CTL_NAME_IEC958("", PLAYBACK
, DEFAULT
),
200 .info
= img_spdif_out_info
,
201 .get
= img_spdif_out_get_status
,
202 .put
= img_spdif_out_set_status
206 static int img_spdif_out_trigger(struct snd_pcm_substream
*substream
, int cmd
,
207 struct snd_soc_dai
*dai
)
209 struct img_spdif_out
*spdif
= snd_soc_dai_get_drvdata(dai
);
214 case SNDRV_PCM_TRIGGER_START
:
215 case SNDRV_PCM_TRIGGER_RESUME
:
216 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE
:
217 reg
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CTL
);
218 reg
|= IMG_SPDIF_OUT_CTL_SRT_MASK
;
219 img_spdif_out_writel(spdif
, reg
, IMG_SPDIF_OUT_CTL
);
221 case SNDRV_PCM_TRIGGER_STOP
:
222 case SNDRV_PCM_TRIGGER_SUSPEND
:
223 case SNDRV_PCM_TRIGGER_PAUSE_PUSH
:
224 spin_lock_irqsave(&spdif
->lock
, flags
);
225 img_spdif_out_reset(spdif
);
226 spin_unlock_irqrestore(&spdif
->lock
, flags
);
235 static int img_spdif_out_hw_params(struct snd_pcm_substream
*substream
,
236 struct snd_pcm_hw_params
*params
, struct snd_soc_dai
*dai
)
238 struct img_spdif_out
*spdif
= snd_soc_dai_get_drvdata(dai
);
239 unsigned int channels
;
240 long pre_div_a
, pre_div_b
, diff_a
, diff_b
, rate
, clk_rate
;
242 snd_pcm_format_t format
;
244 rate
= params_rate(params
);
245 format
= params_format(params
);
246 channels
= params_channels(params
);
248 dev_dbg(spdif
->dev
, "hw_params rate %ld channels %u format %u\n",
249 rate
, channels
, format
);
251 if (format
!= SNDRV_PCM_FORMAT_S32_LE
)
257 pre_div_a
= clk_round_rate(spdif
->clk_ref
, rate
* 256);
260 pre_div_b
= clk_round_rate(spdif
->clk_ref
, rate
* 384);
264 diff_a
= abs((pre_div_a
/ 256) - rate
);
265 diff_b
= abs((pre_div_b
/ 384) - rate
);
267 /* If diffs are equal, use lower clock rate */
269 clk_set_rate(spdif
->clk_ref
, pre_div_b
);
271 clk_set_rate(spdif
->clk_ref
, pre_div_a
);
274 * Another driver (eg machine driver) may have rejected the above
275 * change. Get the current rate and set the register bit according to
278 clk_rate
= clk_get_rate(spdif
->clk_ref
);
280 diff_a
= abs((clk_rate
/ 256) - rate
);
281 diff_b
= abs((clk_rate
/ 384) - rate
);
283 reg
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CTL
);
284 if (diff_a
<= diff_b
)
285 reg
&= ~IMG_SPDIF_OUT_CTL_CLK_MASK
;
287 reg
|= IMG_SPDIF_OUT_CTL_CLK_MASK
;
288 img_spdif_out_writel(spdif
, reg
, IMG_SPDIF_OUT_CTL
);
293 static const struct snd_soc_dai_ops img_spdif_out_dai_ops
= {
294 .trigger
= img_spdif_out_trigger
,
295 .hw_params
= img_spdif_out_hw_params
298 static int img_spdif_out_dai_probe(struct snd_soc_dai
*dai
)
300 struct img_spdif_out
*spdif
= snd_soc_dai_get_drvdata(dai
);
302 snd_soc_dai_init_dma_data(dai
, &spdif
->dma_data
, NULL
);
304 snd_soc_add_dai_controls(dai
, img_spdif_out_controls
,
305 ARRAY_SIZE(img_spdif_out_controls
));
310 static struct snd_soc_dai_driver img_spdif_out_dai
= {
311 .probe
= img_spdif_out_dai_probe
,
315 .rates
= SNDRV_PCM_RATE_8000_192000
,
316 .formats
= SNDRV_PCM_FMTBIT_S32_LE
318 .ops
= &img_spdif_out_dai_ops
321 static const struct snd_soc_component_driver img_spdif_out_component
= {
322 .name
= "img-spdif-out"
325 static int img_spdif_out_probe(struct platform_device
*pdev
)
327 struct img_spdif_out
*spdif
;
328 struct resource
*res
;
331 struct device
*dev
= &pdev
->dev
;
333 spdif
= devm_kzalloc(&pdev
->dev
, sizeof(*spdif
), GFP_KERNEL
);
337 platform_set_drvdata(pdev
, spdif
);
339 spdif
->dev
= &pdev
->dev
;
341 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
342 base
= devm_ioremap_resource(&pdev
->dev
, res
);
344 return PTR_ERR(base
);
348 spdif
->rst
= devm_reset_control_get_exclusive(&pdev
->dev
, "rst");
349 if (IS_ERR(spdif
->rst
)) {
350 if (PTR_ERR(spdif
->rst
) != -EPROBE_DEFER
)
351 dev_err(&pdev
->dev
, "No top level reset found\n");
352 return PTR_ERR(spdif
->rst
);
355 spdif
->clk_sys
= devm_clk_get(&pdev
->dev
, "sys");
356 if (IS_ERR(spdif
->clk_sys
)) {
357 if (PTR_ERR(spdif
->clk_sys
) != -EPROBE_DEFER
)
358 dev_err(dev
, "Failed to acquire clock 'sys'\n");
359 return PTR_ERR(spdif
->clk_sys
);
362 spdif
->clk_ref
= devm_clk_get(&pdev
->dev
, "ref");
363 if (IS_ERR(spdif
->clk_ref
)) {
364 if (PTR_ERR(spdif
->clk_ref
) != -EPROBE_DEFER
)
365 dev_err(dev
, "Failed to acquire clock 'ref'\n");
366 return PTR_ERR(spdif
->clk_ref
);
369 pm_runtime_enable(&pdev
->dev
);
370 if (!pm_runtime_enabled(&pdev
->dev
)) {
371 ret
= img_spdif_out_runtime_resume(&pdev
->dev
);
375 ret
= pm_runtime_get_sync(&pdev
->dev
);
379 img_spdif_out_writel(spdif
, IMG_SPDIF_OUT_CTL_FS_MASK
,
382 img_spdif_out_reset(spdif
);
383 pm_runtime_put(&pdev
->dev
);
385 spin_lock_init(&spdif
->lock
);
387 spdif
->dma_data
.addr
= res
->start
+ IMG_SPDIF_OUT_TX_FIFO
;
388 spdif
->dma_data
.addr_width
= 4;
389 spdif
->dma_data
.maxburst
= 4;
391 ret
= devm_snd_soc_register_component(&pdev
->dev
,
392 &img_spdif_out_component
,
393 &img_spdif_out_dai
, 1);
397 ret
= devm_snd_dmaengine_pcm_register(&pdev
->dev
, NULL
, 0);
401 dev_dbg(&pdev
->dev
, "Probe successful\n");
406 if (!pm_runtime_status_suspended(&pdev
->dev
))
407 img_spdif_out_runtime_suspend(&pdev
->dev
);
409 pm_runtime_disable(&pdev
->dev
);
414 static int img_spdif_out_dev_remove(struct platform_device
*pdev
)
416 pm_runtime_disable(&pdev
->dev
);
417 if (!pm_runtime_status_suspended(&pdev
->dev
))
418 img_spdif_out_runtime_suspend(&pdev
->dev
);
423 #ifdef CONFIG_PM_SLEEP
424 static int img_spdif_out_suspend(struct device
*dev
)
426 struct img_spdif_out
*spdif
= dev_get_drvdata(dev
);
429 if (pm_runtime_status_suspended(dev
)) {
430 ret
= img_spdif_out_runtime_resume(dev
);
435 spdif
->suspend_ctl
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CTL
);
436 spdif
->suspend_csl
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CSL
);
437 spdif
->suspend_csh
= img_spdif_out_readl(spdif
, IMG_SPDIF_OUT_CSH_UV
);
439 img_spdif_out_runtime_suspend(dev
);
444 static int img_spdif_out_resume(struct device
*dev
)
446 struct img_spdif_out
*spdif
= dev_get_drvdata(dev
);
449 ret
= img_spdif_out_runtime_resume(dev
);
453 img_spdif_out_writel(spdif
, spdif
->suspend_ctl
, IMG_SPDIF_OUT_CTL
);
454 img_spdif_out_writel(spdif
, spdif
->suspend_csl
, IMG_SPDIF_OUT_CSL
);
455 img_spdif_out_writel(spdif
, spdif
->suspend_csh
, IMG_SPDIF_OUT_CSH_UV
);
457 if (pm_runtime_status_suspended(dev
))
458 img_spdif_out_runtime_suspend(dev
);
463 static const struct of_device_id img_spdif_out_of_match
[] = {
464 { .compatible
= "img,spdif-out" },
467 MODULE_DEVICE_TABLE(of
, img_spdif_out_of_match
);
469 static const struct dev_pm_ops img_spdif_out_pm_ops
= {
470 SET_RUNTIME_PM_OPS(img_spdif_out_runtime_suspend
,
471 img_spdif_out_runtime_resume
, NULL
)
472 SET_SYSTEM_SLEEP_PM_OPS(img_spdif_out_suspend
, img_spdif_out_resume
)
475 static struct platform_driver img_spdif_out_driver
= {
477 .name
= "img-spdif-out",
478 .of_match_table
= img_spdif_out_of_match
,
479 .pm
= &img_spdif_out_pm_ops
481 .probe
= img_spdif_out_probe
,
482 .remove
= img_spdif_out_dev_remove
484 module_platform_driver(img_spdif_out_driver
);
486 MODULE_AUTHOR("Damien Horsley <Damien.Horsley@imgtec.com>");
487 MODULE_DESCRIPTION("IMG SPDIF Output driver");
488 MODULE_LICENSE("GPL v2");