Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/soc/samsung/spdif.c
26428 views
1
// SPDX-License-Identifier: GPL-2.0
2
//
3
// ALSA SoC Audio Layer - Samsung S/PDIF Controller driver
4
//
5
// Copyright (c) 2010 Samsung Electronics Co. Ltd
6
// http://www.samsung.com/
7
8
#include <linux/clk.h>
9
#include <linux/io.h>
10
#include <linux/module.h>
11
12
#include <sound/soc.h>
13
#include <sound/pcm_params.h>
14
15
#include <linux/platform_data/asoc-s3c.h>
16
17
#include "dma.h"
18
#include "spdif.h"
19
20
/* Registers */
21
#define CLKCON 0x00
22
#define CON 0x04
23
#define BSTAS 0x08
24
#define CSTAS 0x0C
25
#define DATA_OUTBUF 0x10
26
#define DCNT 0x14
27
#define BSTAS_S 0x18
28
#define DCNT_S 0x1C
29
30
#define CLKCTL_MASK 0x7
31
#define CLKCTL_MCLK_EXT (0x1 << 2)
32
#define CLKCTL_PWR_ON (0x1 << 0)
33
34
#define CON_MASK 0x3ffffff
35
#define CON_FIFO_TH_SHIFT 19
36
#define CON_FIFO_TH_MASK (0x7 << 19)
37
#define CON_USERDATA_23RDBIT (0x1 << 12)
38
39
#define CON_SW_RESET (0x1 << 5)
40
41
#define CON_MCLKDIV_MASK (0x3 << 3)
42
#define CON_MCLKDIV_256FS (0x0 << 3)
43
#define CON_MCLKDIV_384FS (0x1 << 3)
44
#define CON_MCLKDIV_512FS (0x2 << 3)
45
46
#define CON_PCM_MASK (0x3 << 1)
47
#define CON_PCM_16BIT (0x0 << 1)
48
#define CON_PCM_20BIT (0x1 << 1)
49
#define CON_PCM_24BIT (0x2 << 1)
50
51
#define CON_PCM_DATA (0x1 << 0)
52
53
#define CSTAS_MASK 0x3fffffff
54
#define CSTAS_SAMP_FREQ_MASK (0xF << 24)
55
#define CSTAS_SAMP_FREQ_44 (0x0 << 24)
56
#define CSTAS_SAMP_FREQ_48 (0x2 << 24)
57
#define CSTAS_SAMP_FREQ_32 (0x3 << 24)
58
#define CSTAS_SAMP_FREQ_96 (0xA << 24)
59
60
#define CSTAS_CATEGORY_MASK (0xFF << 8)
61
#define CSTAS_CATEGORY_CODE_CDP (0x01 << 8)
62
63
#define CSTAS_NO_COPYRIGHT (0x1 << 2)
64
65
/**
66
* struct samsung_spdif_info - Samsung S/PDIF Controller information
67
* @lock: Spin lock for S/PDIF.
68
* @dev: The parent device passed to use from the probe.
69
* @regs: The pointer to the device register block.
70
* @clk_rate: Current clock rate for calcurate ratio.
71
* @pclk: The peri-clock pointer for spdif master operation.
72
* @sclk: The source clock pointer for making sync signals.
73
* @saved_clkcon: Backup clkcon reg. in suspend.
74
* @saved_con: Backup con reg. in suspend.
75
* @saved_cstas: Backup cstas reg. in suspend.
76
* @dma_playback: DMA information for playback channel.
77
*/
78
struct samsung_spdif_info {
79
spinlock_t lock;
80
struct device *dev;
81
void __iomem *regs;
82
unsigned long clk_rate;
83
struct clk *pclk;
84
struct clk *sclk;
85
u32 saved_clkcon;
86
u32 saved_con;
87
u32 saved_cstas;
88
struct snd_dmaengine_dai_dma_data *dma_playback;
89
};
90
91
static struct snd_dmaengine_dai_dma_data spdif_stereo_out;
92
static struct samsung_spdif_info spdif_info;
93
94
static inline struct samsung_spdif_info
95
*component_to_info(struct snd_soc_component *component)
96
{
97
return snd_soc_component_get_drvdata(component);
98
}
99
100
static inline struct samsung_spdif_info *to_info(struct snd_soc_dai *cpu_dai)
101
{
102
return snd_soc_dai_get_drvdata(cpu_dai);
103
}
104
105
static void spdif_snd_txctrl(struct samsung_spdif_info *spdif, int on)
106
{
107
void __iomem *regs = spdif->regs;
108
u32 clkcon;
109
110
dev_dbg(spdif->dev, "Entered %s\n", __func__);
111
112
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
113
if (on)
114
writel(clkcon | CLKCTL_PWR_ON, regs + CLKCON);
115
else
116
writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
117
}
118
119
static int spdif_set_sysclk(struct snd_soc_dai *cpu_dai,
120
int clk_id, unsigned int freq, int dir)
121
{
122
struct samsung_spdif_info *spdif = to_info(cpu_dai);
123
u32 clkcon;
124
125
dev_dbg(spdif->dev, "Entered %s\n", __func__);
126
127
clkcon = readl(spdif->regs + CLKCON);
128
129
if (clk_id == SND_SOC_SPDIF_INT_MCLK)
130
clkcon &= ~CLKCTL_MCLK_EXT;
131
else
132
clkcon |= CLKCTL_MCLK_EXT;
133
134
writel(clkcon, spdif->regs + CLKCON);
135
136
spdif->clk_rate = freq;
137
138
return 0;
139
}
140
141
static int spdif_trigger(struct snd_pcm_substream *substream, int cmd,
142
struct snd_soc_dai *dai)
143
{
144
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
145
struct samsung_spdif_info *spdif = to_info(snd_soc_rtd_to_cpu(rtd, 0));
146
unsigned long flags;
147
148
dev_dbg(spdif->dev, "Entered %s\n", __func__);
149
150
switch (cmd) {
151
case SNDRV_PCM_TRIGGER_START:
152
case SNDRV_PCM_TRIGGER_RESUME:
153
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
154
spin_lock_irqsave(&spdif->lock, flags);
155
spdif_snd_txctrl(spdif, 1);
156
spin_unlock_irqrestore(&spdif->lock, flags);
157
break;
158
case SNDRV_PCM_TRIGGER_STOP:
159
case SNDRV_PCM_TRIGGER_SUSPEND:
160
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
161
spin_lock_irqsave(&spdif->lock, flags);
162
spdif_snd_txctrl(spdif, 0);
163
spin_unlock_irqrestore(&spdif->lock, flags);
164
break;
165
default:
166
return -EINVAL;
167
}
168
169
return 0;
170
}
171
172
static int spdif_sysclk_ratios[] = {
173
512, 384, 256,
174
};
175
176
static int spdif_hw_params(struct snd_pcm_substream *substream,
177
struct snd_pcm_hw_params *params,
178
struct snd_soc_dai *socdai)
179
{
180
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
181
struct samsung_spdif_info *spdif = to_info(snd_soc_rtd_to_cpu(rtd, 0));
182
void __iomem *regs = spdif->regs;
183
struct snd_dmaengine_dai_dma_data *dma_data;
184
u32 con, clkcon, cstas;
185
unsigned long flags;
186
int i, ratio;
187
188
dev_dbg(spdif->dev, "Entered %s\n", __func__);
189
190
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
191
dma_data = spdif->dma_playback;
192
else {
193
dev_err(spdif->dev, "Capture is not supported\n");
194
return -EINVAL;
195
}
196
197
snd_soc_dai_set_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream, dma_data);
198
199
spin_lock_irqsave(&spdif->lock, flags);
200
201
con = readl(regs + CON) & CON_MASK;
202
cstas = readl(regs + CSTAS) & CSTAS_MASK;
203
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
204
205
con &= ~CON_FIFO_TH_MASK;
206
con |= (0x7 << CON_FIFO_TH_SHIFT);
207
con |= CON_USERDATA_23RDBIT;
208
con |= CON_PCM_DATA;
209
210
con &= ~CON_PCM_MASK;
211
switch (params_width(params)) {
212
case 16:
213
con |= CON_PCM_16BIT;
214
break;
215
default:
216
dev_err(spdif->dev, "Unsupported data size.\n");
217
goto err;
218
}
219
220
ratio = spdif->clk_rate / params_rate(params);
221
for (i = 0; i < ARRAY_SIZE(spdif_sysclk_ratios); i++)
222
if (ratio == spdif_sysclk_ratios[i])
223
break;
224
if (i == ARRAY_SIZE(spdif_sysclk_ratios)) {
225
dev_err(spdif->dev, "Invalid clock ratio %ld/%d\n",
226
spdif->clk_rate, params_rate(params));
227
goto err;
228
}
229
230
con &= ~CON_MCLKDIV_MASK;
231
switch (ratio) {
232
case 256:
233
con |= CON_MCLKDIV_256FS;
234
break;
235
case 384:
236
con |= CON_MCLKDIV_384FS;
237
break;
238
case 512:
239
con |= CON_MCLKDIV_512FS;
240
break;
241
}
242
243
cstas &= ~CSTAS_SAMP_FREQ_MASK;
244
switch (params_rate(params)) {
245
case 44100:
246
cstas |= CSTAS_SAMP_FREQ_44;
247
break;
248
case 48000:
249
cstas |= CSTAS_SAMP_FREQ_48;
250
break;
251
case 32000:
252
cstas |= CSTAS_SAMP_FREQ_32;
253
break;
254
case 96000:
255
cstas |= CSTAS_SAMP_FREQ_96;
256
break;
257
default:
258
dev_err(spdif->dev, "Invalid sampling rate %d\n",
259
params_rate(params));
260
goto err;
261
}
262
263
cstas &= ~CSTAS_CATEGORY_MASK;
264
cstas |= CSTAS_CATEGORY_CODE_CDP;
265
cstas |= CSTAS_NO_COPYRIGHT;
266
267
writel(con, regs + CON);
268
writel(cstas, regs + CSTAS);
269
writel(clkcon, regs + CLKCON);
270
271
spin_unlock_irqrestore(&spdif->lock, flags);
272
273
return 0;
274
err:
275
spin_unlock_irqrestore(&spdif->lock, flags);
276
return -EINVAL;
277
}
278
279
static void spdif_shutdown(struct snd_pcm_substream *substream,
280
struct snd_soc_dai *dai)
281
{
282
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
283
struct samsung_spdif_info *spdif = to_info(snd_soc_rtd_to_cpu(rtd, 0));
284
void __iomem *regs = spdif->regs;
285
u32 con, clkcon;
286
287
dev_dbg(spdif->dev, "Entered %s\n", __func__);
288
289
con = readl(regs + CON) & CON_MASK;
290
clkcon = readl(regs + CLKCON) & CLKCTL_MASK;
291
292
writel(con | CON_SW_RESET, regs + CON);
293
cpu_relax();
294
295
writel(clkcon & ~CLKCTL_PWR_ON, regs + CLKCON);
296
}
297
298
#ifdef CONFIG_PM
299
static int spdif_suspend(struct snd_soc_component *component)
300
{
301
struct samsung_spdif_info *spdif = component_to_info(component);
302
u32 con = spdif->saved_con;
303
304
dev_dbg(spdif->dev, "Entered %s\n", __func__);
305
306
spdif->saved_clkcon = readl(spdif->regs + CLKCON) & CLKCTL_MASK;
307
spdif->saved_con = readl(spdif->regs + CON) & CON_MASK;
308
spdif->saved_cstas = readl(spdif->regs + CSTAS) & CSTAS_MASK;
309
310
writel(con | CON_SW_RESET, spdif->regs + CON);
311
cpu_relax();
312
313
return 0;
314
}
315
316
static int spdif_resume(struct snd_soc_component *component)
317
{
318
struct samsung_spdif_info *spdif = component_to_info(component);
319
320
dev_dbg(spdif->dev, "Entered %s\n", __func__);
321
322
writel(spdif->saved_clkcon, spdif->regs + CLKCON);
323
writel(spdif->saved_con, spdif->regs + CON);
324
writel(spdif->saved_cstas, spdif->regs + CSTAS);
325
326
return 0;
327
}
328
#else
329
#define spdif_suspend NULL
330
#define spdif_resume NULL
331
#endif
332
333
static const struct snd_soc_dai_ops spdif_dai_ops = {
334
.set_sysclk = spdif_set_sysclk,
335
.trigger = spdif_trigger,
336
.hw_params = spdif_hw_params,
337
.shutdown = spdif_shutdown,
338
};
339
340
static struct snd_soc_dai_driver samsung_spdif_dai = {
341
.name = "samsung-spdif",
342
.playback = {
343
.stream_name = "S/PDIF Playback",
344
.channels_min = 2,
345
.channels_max = 2,
346
.rates = (SNDRV_PCM_RATE_32000 |
347
SNDRV_PCM_RATE_44100 |
348
SNDRV_PCM_RATE_48000 |
349
SNDRV_PCM_RATE_96000),
350
.formats = SNDRV_PCM_FMTBIT_S16_LE, },
351
.ops = &spdif_dai_ops,
352
};
353
354
static const struct snd_soc_component_driver samsung_spdif_component = {
355
.name = "samsung-spdif",
356
.suspend = spdif_suspend,
357
.resume = spdif_resume,
358
.legacy_dai_naming = 1,
359
};
360
361
static int spdif_probe(struct platform_device *pdev)
362
{
363
struct s3c_audio_pdata *spdif_pdata;
364
struct resource *mem_res;
365
struct samsung_spdif_info *spdif;
366
dma_filter_fn filter;
367
int ret;
368
369
spdif_pdata = pdev->dev.platform_data;
370
371
dev_dbg(&pdev->dev, "Entered %s\n", __func__);
372
373
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
374
if (!mem_res) {
375
dev_err(&pdev->dev, "Unable to get register resource.\n");
376
return -ENXIO;
377
}
378
379
if (spdif_pdata && spdif_pdata->cfg_gpio
380
&& spdif_pdata->cfg_gpio(pdev)) {
381
dev_err(&pdev->dev, "Unable to configure GPIO pins\n");
382
return -EINVAL;
383
}
384
385
spdif = &spdif_info;
386
spdif->dev = &pdev->dev;
387
388
spin_lock_init(&spdif->lock);
389
390
spdif->pclk = devm_clk_get(&pdev->dev, "spdif");
391
if (IS_ERR(spdif->pclk)) {
392
dev_err(&pdev->dev, "failed to get peri-clock\n");
393
ret = -ENOENT;
394
goto err0;
395
}
396
ret = clk_prepare_enable(spdif->pclk);
397
if (ret)
398
goto err0;
399
400
spdif->sclk = devm_clk_get(&pdev->dev, "sclk_spdif");
401
if (IS_ERR(spdif->sclk)) {
402
dev_err(&pdev->dev, "failed to get internal source clock\n");
403
ret = -ENOENT;
404
goto err1;
405
}
406
ret = clk_prepare_enable(spdif->sclk);
407
if (ret)
408
goto err1;
409
410
/* Request S/PDIF Register's memory region */
411
if (!request_mem_region(mem_res->start,
412
resource_size(mem_res), "samsung-spdif")) {
413
dev_err(&pdev->dev, "Unable to request register region\n");
414
ret = -EBUSY;
415
goto err2;
416
}
417
418
spdif->regs = ioremap(mem_res->start, 0x100);
419
if (spdif->regs == NULL) {
420
dev_err(&pdev->dev, "Cannot ioremap registers\n");
421
ret = -ENXIO;
422
goto err3;
423
}
424
425
spdif_stereo_out.addr_width = 2;
426
spdif_stereo_out.addr = mem_res->start + DATA_OUTBUF;
427
filter = NULL;
428
if (spdif_pdata) {
429
spdif_stereo_out.filter_data = spdif_pdata->dma_playback;
430
filter = spdif_pdata->dma_filter;
431
}
432
spdif->dma_playback = &spdif_stereo_out;
433
434
ret = samsung_asoc_dma_platform_register(&pdev->dev, filter,
435
NULL, NULL, NULL);
436
if (ret) {
437
dev_err(&pdev->dev, "failed to register DMA: %d\n", ret);
438
goto err4;
439
}
440
441
dev_set_drvdata(&pdev->dev, spdif);
442
443
ret = devm_snd_soc_register_component(&pdev->dev,
444
&samsung_spdif_component, &samsung_spdif_dai, 1);
445
if (ret != 0) {
446
dev_err(&pdev->dev, "fail to register dai\n");
447
goto err4;
448
}
449
450
return 0;
451
err4:
452
iounmap(spdif->regs);
453
err3:
454
release_mem_region(mem_res->start, resource_size(mem_res));
455
err2:
456
clk_disable_unprepare(spdif->sclk);
457
err1:
458
clk_disable_unprepare(spdif->pclk);
459
err0:
460
return ret;
461
}
462
463
static void spdif_remove(struct platform_device *pdev)
464
{
465
struct samsung_spdif_info *spdif = &spdif_info;
466
struct resource *mem_res;
467
468
iounmap(spdif->regs);
469
470
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
471
release_mem_region(mem_res->start, resource_size(mem_res));
472
473
clk_disable_unprepare(spdif->sclk);
474
clk_disable_unprepare(spdif->pclk);
475
}
476
477
static struct platform_driver samsung_spdif_driver = {
478
.probe = spdif_probe,
479
.remove = spdif_remove,
480
.driver = {
481
.name = "samsung-spdif",
482
},
483
};
484
485
module_platform_driver(samsung_spdif_driver);
486
487
MODULE_AUTHOR("Seungwhan Youn, <[email protected]>");
488
MODULE_DESCRIPTION("Samsung S/PDIF Controller Driver");
489
MODULE_LICENSE("GPL");
490
MODULE_ALIAS("platform:samsung-spdif");
491
492