Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/sound/soc/s6000/s6000-i2s.c
10817 views
1
/*
2
* ALSA SoC I2S Audio Layer for the Stretch S6000 family
3
*
4
* Author: Daniel Gloeckner, <[email protected]>
5
* Copyright: (C) 2009 emlix GmbH <[email protected]>
6
*
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License version 2 as
9
* published by the Free Software Foundation.
10
*/
11
12
#include <linux/init.h>
13
#include <linux/module.h>
14
#include <linux/device.h>
15
#include <linux/delay.h>
16
#include <linux/clk.h>
17
#include <linux/interrupt.h>
18
#include <linux/io.h>
19
#include <linux/slab.h>
20
21
#include <sound/core.h>
22
#include <sound/pcm.h>
23
#include <sound/pcm_params.h>
24
#include <sound/initval.h>
25
#include <sound/soc.h>
26
27
#include "s6000-i2s.h"
28
#include "s6000-pcm.h"
29
30
struct s6000_i2s_dev {
31
dma_addr_t sifbase;
32
u8 __iomem *scbbase;
33
unsigned int wide;
34
unsigned int channel_in;
35
unsigned int channel_out;
36
unsigned int lines_in;
37
unsigned int lines_out;
38
struct s6000_pcm_dma_params dma_params;
39
};
40
41
#define S6_I2S_INTERRUPT_STATUS 0x00
42
#define S6_I2S_INT_OVERRUN 1
43
#define S6_I2S_INT_UNDERRUN 2
44
#define S6_I2S_INT_ALIGNMENT 4
45
#define S6_I2S_INTERRUPT_ENABLE 0x04
46
#define S6_I2S_INTERRUPT_RAW 0x08
47
#define S6_I2S_INTERRUPT_CLEAR 0x0C
48
#define S6_I2S_INTERRUPT_SET 0x10
49
#define S6_I2S_MODE 0x20
50
#define S6_I2S_DUAL 0
51
#define S6_I2S_WIDE 1
52
#define S6_I2S_TX_DEFAULT 0x24
53
#define S6_I2S_DATA_CFG(c) (0x40 + 0x10 * (c))
54
#define S6_I2S_IN 0
55
#define S6_I2S_OUT 1
56
#define S6_I2S_UNUSED 2
57
#define S6_I2S_INTERFACE_CFG(c) (0x44 + 0x10 * (c))
58
#define S6_I2S_DIV_MASK 0x001fff
59
#define S6_I2S_16BIT 0x000000
60
#define S6_I2S_20BIT 0x002000
61
#define S6_I2S_24BIT 0x004000
62
#define S6_I2S_32BIT 0x006000
63
#define S6_I2S_BITS_MASK 0x006000
64
#define S6_I2S_MEM_16BIT 0x000000
65
#define S6_I2S_MEM_32BIT 0x008000
66
#define S6_I2S_MEM_MASK 0x008000
67
#define S6_I2S_CHANNELS_SHIFT 16
68
#define S6_I2S_CHANNELS_MASK 0x030000
69
#define S6_I2S_SCK_IN 0x000000
70
#define S6_I2S_SCK_OUT 0x040000
71
#define S6_I2S_SCK_DIR 0x040000
72
#define S6_I2S_WS_IN 0x000000
73
#define S6_I2S_WS_OUT 0x080000
74
#define S6_I2S_WS_DIR 0x080000
75
#define S6_I2S_LEFT_FIRST 0x000000
76
#define S6_I2S_RIGHT_FIRST 0x100000
77
#define S6_I2S_FIRST 0x100000
78
#define S6_I2S_CUR_SCK 0x200000
79
#define S6_I2S_CUR_WS 0x400000
80
#define S6_I2S_ENABLE(c) (0x48 + 0x10 * (c))
81
#define S6_I2S_DISABLE_IF 0x02
82
#define S6_I2S_ENABLE_IF 0x03
83
#define S6_I2S_IS_BUSY 0x04
84
#define S6_I2S_DMA_ACTIVE 0x08
85
#define S6_I2S_IS_ENABLED 0x10
86
87
#define S6_I2S_NUM_LINES 4
88
89
#define S6_I2S_SIF_PORT0 0x0000000
90
#define S6_I2S_SIF_PORT1 0x0000080 /* docs say 0x0000010 */
91
92
static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val)
93
{
94
writel(val, dev->scbbase + reg);
95
}
96
97
static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg)
98
{
99
return readl(dev->scbbase + reg);
100
}
101
102
static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg,
103
u32 mask, u32 val)
104
{
105
val ^= s6_i2s_read_reg(dev, reg) & ~mask;
106
s6_i2s_write_reg(dev, reg, val);
107
}
108
109
static void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, int channel)
110
{
111
int i, j, cur, prev;
112
113
/*
114
* Wait for WCLK to toggle 5 times before enabling the channel
115
* s6000 Family Datasheet 3.6.4:
116
* "At least two cycles of WS must occur between commands
117
* to disable or enable the interface"
118
*/
119
j = 0;
120
prev = ~S6_I2S_CUR_WS;
121
for (i = 1000000; --i && j < 6; ) {
122
cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel))
123
& S6_I2S_CUR_WS;
124
if (prev != cur) {
125
prev = cur;
126
j++;
127
}
128
}
129
if (j < 6)
130
printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n");
131
132
s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF);
133
}
134
135
static void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, int channel)
136
{
137
s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF);
138
}
139
140
static void s6000_i2s_start(struct snd_pcm_substream *substream)
141
{
142
struct snd_soc_pcm_runtime *rtd = substream->private_data;
143
struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
144
int channel;
145
146
channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
147
dev->channel_out : dev->channel_in;
148
149
s6000_i2s_start_channel(dev, channel);
150
}
151
152
static void s6000_i2s_stop(struct snd_pcm_substream *substream)
153
{
154
struct snd_soc_pcm_runtime *rtd = substream->private_data;
155
struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
156
int channel;
157
158
channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
159
dev->channel_out : dev->channel_in;
160
161
s6000_i2s_stop_channel(dev, channel);
162
}
163
164
static int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
165
int after)
166
{
167
switch (cmd) {
168
case SNDRV_PCM_TRIGGER_START:
169
case SNDRV_PCM_TRIGGER_RESUME:
170
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
171
if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after)
172
s6000_i2s_start(substream);
173
break;
174
case SNDRV_PCM_TRIGGER_STOP:
175
case SNDRV_PCM_TRIGGER_SUSPEND:
176
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
177
if (!after)
178
s6000_i2s_stop(substream);
179
}
180
return 0;
181
}
182
183
static unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev)
184
{
185
unsigned int pending;
186
pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW);
187
pending &= S6_I2S_INT_ALIGNMENT |
188
S6_I2S_INT_UNDERRUN |
189
S6_I2S_INT_OVERRUN;
190
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending);
191
192
return pending;
193
}
194
195
static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai)
196
{
197
struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
198
unsigned int errors;
199
unsigned int ret;
200
201
errors = s6000_i2s_int_sources(dev);
202
if (likely(!errors))
203
return 0;
204
205
ret = 0;
206
if (errors & S6_I2S_INT_ALIGNMENT)
207
printk(KERN_ERR "s6000-i2s: WCLK misaligned\n");
208
if (errors & S6_I2S_INT_UNDERRUN)
209
ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK;
210
if (errors & S6_I2S_INT_OVERRUN)
211
ret |= 1 << SNDRV_PCM_STREAM_CAPTURE;
212
return ret;
213
}
214
215
static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev)
216
{
217
int channel;
218
int n = 50;
219
for (channel = 0; channel < 2; channel++) {
220
while (--n >= 0) {
221
int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel));
222
if ((v & S6_I2S_IS_ENABLED)
223
|| !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY)))
224
break;
225
udelay(20);
226
}
227
}
228
if (n < 0)
229
printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces");
230
}
231
232
static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai,
233
unsigned int fmt)
234
{
235
struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
236
u32 w;
237
238
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
239
case SND_SOC_DAIFMT_CBM_CFM:
240
w = S6_I2S_SCK_IN | S6_I2S_WS_IN;
241
break;
242
case SND_SOC_DAIFMT_CBS_CFM:
243
w = S6_I2S_SCK_OUT | S6_I2S_WS_IN;
244
break;
245
case SND_SOC_DAIFMT_CBM_CFS:
246
w = S6_I2S_SCK_IN | S6_I2S_WS_OUT;
247
break;
248
case SND_SOC_DAIFMT_CBS_CFS:
249
w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT;
250
break;
251
default:
252
return -EINVAL;
253
}
254
255
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
256
case SND_SOC_DAIFMT_NB_NF:
257
w |= S6_I2S_LEFT_FIRST;
258
break;
259
case SND_SOC_DAIFMT_NB_IF:
260
w |= S6_I2S_RIGHT_FIRST;
261
break;
262
default:
263
return -EINVAL;
264
}
265
266
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0),
267
S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
268
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1),
269
S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w);
270
271
return 0;
272
}
273
274
static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
275
{
276
struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
277
278
if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2)
279
return -EINVAL;
280
281
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id),
282
S6_I2S_DIV_MASK, div / 2 - 1);
283
return 0;
284
}
285
286
static int s6000_i2s_hw_params(struct snd_pcm_substream *substream,
287
struct snd_pcm_hw_params *params,
288
struct snd_soc_dai *dai)
289
{
290
struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
291
int interf;
292
u32 w = 0;
293
294
if (dev->wide)
295
interf = 0;
296
else {
297
w |= (((params_channels(params) - 2) / 2)
298
<< S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK;
299
interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
300
? dev->channel_out : dev->channel_in;
301
}
302
303
switch (params_format(params)) {
304
case SNDRV_PCM_FORMAT_S16_LE:
305
w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT;
306
break;
307
case SNDRV_PCM_FORMAT_S32_LE:
308
w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT;
309
break;
310
default:
311
printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n",
312
params_format(params));
313
return -EINVAL;
314
}
315
316
if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf))
317
& S6_I2S_IS_ENABLED) {
318
printk(KERN_ERR "s6000-i2s: interface already enabled\n");
319
return -EBUSY;
320
}
321
322
s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf),
323
S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK,
324
w);
325
326
return 0;
327
}
328
329
static int s6000_i2s_dai_probe(struct snd_soc_dai *dai)
330
{
331
struct s6000_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
332
struct s6000_snd_platform_data *pdata = dai->dev->platform_data;
333
334
if (!pdata)
335
return -EINVAL;
336
337
dai->capture_dma_data = &dev->dma_params;
338
dai->playback_dma_data = &dev->dma_params;
339
340
dev->wide = pdata->wide;
341
dev->channel_in = pdata->channel_in;
342
dev->channel_out = pdata->channel_out;
343
dev->lines_in = pdata->lines_in;
344
dev->lines_out = pdata->lines_out;
345
346
s6_i2s_write_reg(dev, S6_I2S_MODE,
347
dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL);
348
349
if (dev->wide) {
350
int i;
351
352
if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES)
353
return -EINVAL;
354
355
dev->channel_in = 0;
356
dev->channel_out = 1;
357
dai->driver->capture.channels_min = 2 * dev->lines_in;
358
dai->driver->capture.channels_max = dai->driver->capture.channels_min;
359
dai->driver->playback.channels_min = 2 * dev->lines_out;
360
dai->driver->playback.channels_max = dai->driver->playback.channels_min;
361
362
for (i = 0; i < dev->lines_out; i++)
363
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT);
364
365
for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++)
366
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i),
367
S6_I2S_UNUSED);
368
369
for (; i < S6_I2S_NUM_LINES; i++)
370
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN);
371
} else {
372
unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED};
373
374
if (dev->lines_in > 1 || dev->lines_out > 1)
375
return -EINVAL;
376
377
dai->driver->capture.channels_min = 2 * dev->lines_in;
378
dai->driver->capture.channels_max = 8 * dev->lines_in;
379
dai->driver->playback.channels_min = 2 * dev->lines_out;
380
dai->driver->playback.channels_max = 8 * dev->lines_out;
381
382
if (dev->lines_in)
383
cfg[dev->channel_in] = S6_I2S_IN;
384
if (dev->lines_out)
385
cfg[dev->channel_out] = S6_I2S_OUT;
386
387
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]);
388
s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]);
389
}
390
391
if (dev->lines_out) {
392
if (dev->lines_in) {
393
if (!dev->dma_params.dma_out)
394
return -ENODEV;
395
} else {
396
dev->dma_params.dma_out = dev->dma_params.dma_in;
397
dev->dma_params.dma_in = 0;
398
}
399
}
400
dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ?
401
S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
402
dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ?
403
S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0);
404
dev->dma_params.same_rate = pdata->same_rate | pdata->wide;
405
return 0;
406
}
407
408
#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \
409
SNDRV_PCM_RATE_8000_192000)
410
#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE)
411
412
static struct snd_soc_dai_ops s6000_i2s_dai_ops = {
413
.set_fmt = s6000_i2s_set_dai_fmt,
414
.set_clkdiv = s6000_i2s_set_clkdiv,
415
.hw_params = s6000_i2s_hw_params,
416
};
417
418
static struct snd_soc_dai_driver s6000_i2s_dai = {
419
.probe = s6000_i2s_dai_probe,
420
.playback = {
421
.channels_min = 2,
422
.channels_max = 8,
423
.formats = S6000_I2S_FORMATS,
424
.rates = S6000_I2S_RATES,
425
.rate_min = 0,
426
.rate_max = 1562500,
427
},
428
.capture = {
429
.channels_min = 2,
430
.channels_max = 8,
431
.formats = S6000_I2S_FORMATS,
432
.rates = S6000_I2S_RATES,
433
.rate_min = 0,
434
.rate_max = 1562500,
435
},
436
.ops = &s6000_i2s_dai_ops,
437
};
438
439
static int __devinit s6000_i2s_probe(struct platform_device *pdev)
440
{
441
struct s6000_i2s_dev *dev;
442
struct resource *scbmem, *sifmem, *region, *dma1, *dma2;
443
u8 __iomem *mmio;
444
int ret;
445
446
scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
447
if (!scbmem) {
448
dev_err(&pdev->dev, "no mem resource?\n");
449
ret = -ENODEV;
450
goto err_release_none;
451
}
452
453
region = request_mem_region(scbmem->start, resource_size(scbmem),
454
pdev->name);
455
if (!region) {
456
dev_err(&pdev->dev, "I2S SCB region already claimed\n");
457
ret = -EBUSY;
458
goto err_release_none;
459
}
460
461
mmio = ioremap(scbmem->start, resource_size(scbmem));
462
if (!mmio) {
463
dev_err(&pdev->dev, "can't ioremap SCB region\n");
464
ret = -ENOMEM;
465
goto err_release_scb;
466
}
467
468
sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1);
469
if (!sifmem) {
470
dev_err(&pdev->dev, "no second mem resource?\n");
471
ret = -ENODEV;
472
goto err_release_map;
473
}
474
475
region = request_mem_region(sifmem->start, resource_size(sifmem),
476
pdev->name);
477
if (!region) {
478
dev_err(&pdev->dev, "I2S SIF region already claimed\n");
479
ret = -EBUSY;
480
goto err_release_map;
481
}
482
483
dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0);
484
if (!dma1) {
485
dev_err(&pdev->dev, "no dma resource?\n");
486
ret = -ENODEV;
487
goto err_release_sif;
488
}
489
490
region = request_mem_region(dma1->start, resource_size(dma1),
491
pdev->name);
492
if (!region) {
493
dev_err(&pdev->dev, "I2S DMA region already claimed\n");
494
ret = -EBUSY;
495
goto err_release_sif;
496
}
497
498
dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1);
499
if (dma2) {
500
region = request_mem_region(dma2->start, resource_size(dma2),
501
pdev->name);
502
if (!region) {
503
dev_err(&pdev->dev,
504
"I2S DMA region already claimed\n");
505
ret = -EBUSY;
506
goto err_release_dma1;
507
}
508
}
509
510
dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL);
511
if (!dev) {
512
ret = -ENOMEM;
513
goto err_release_dma2;
514
}
515
dev_set_drvdata(&pdev->dev, dev);
516
517
dev->sifbase = sifmem->start;
518
dev->scbbase = mmio;
519
520
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
521
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR,
522
S6_I2S_INT_ALIGNMENT |
523
S6_I2S_INT_UNDERRUN |
524
S6_I2S_INT_OVERRUN);
525
526
s6000_i2s_stop_channel(dev, 0);
527
s6000_i2s_stop_channel(dev, 1);
528
s6000_i2s_wait_disabled(dev);
529
530
dev->dma_params.check_xrun = s6000_i2s_check_xrun;
531
dev->dma_params.trigger = s6000_i2s_trigger;
532
dev->dma_params.dma_in = dma1->start;
533
dev->dma_params.dma_out = dma2 ? dma2->start : 0;
534
dev->dma_params.irq = platform_get_irq(pdev, 0);
535
if (dev->dma_params.irq < 0) {
536
dev_err(&pdev->dev, "no irq resource?\n");
537
ret = -ENODEV;
538
goto err_release_dev;
539
}
540
541
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE,
542
S6_I2S_INT_ALIGNMENT |
543
S6_I2S_INT_UNDERRUN |
544
S6_I2S_INT_OVERRUN);
545
546
ret = snd_soc_register_dai(&pdev->dev, &s6000_i2s_dai);
547
if (ret)
548
goto err_release_dev;
549
550
return 0;
551
552
err_release_dev:
553
kfree(dev);
554
err_release_dma2:
555
if (dma2)
556
release_mem_region(dma2->start, resource_size(dma2));
557
err_release_dma1:
558
release_mem_region(dma1->start, resource_size(dma1));
559
err_release_sif:
560
release_mem_region(sifmem->start, resource_size(sifmem));
561
err_release_map:
562
iounmap(mmio);
563
err_release_scb:
564
release_mem_region(scbmem->start, resource_size(scbmem));
565
err_release_none:
566
return ret;
567
}
568
569
static void __devexit s6000_i2s_remove(struct platform_device *pdev)
570
{
571
struct s6000_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
572
struct resource *region;
573
void __iomem *mmio = dev->scbbase;
574
575
snd_soc_unregister_dai(&pdev->dev);
576
577
s6000_i2s_stop_channel(dev, 0);
578
s6000_i2s_stop_channel(dev, 1);
579
580
s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0);
581
kfree(dev);
582
583
region = platform_get_resource(pdev, IORESOURCE_DMA, 0);
584
release_mem_region(region->start, resource_size(region));
585
586
region = platform_get_resource(pdev, IORESOURCE_DMA, 1);
587
if (region)
588
release_mem_region(region->start, resource_size(region));
589
590
region = platform_get_resource(pdev, IORESOURCE_MEM, 0);
591
release_mem_region(region->start, resource_size(region));
592
593
iounmap(mmio);
594
region = platform_get_resource(pdev, IORESOURCE_IO, 0);
595
release_mem_region(region->start, resource_size(region));
596
}
597
598
static struct platform_driver s6000_i2s_driver = {
599
.probe = s6000_i2s_probe,
600
.remove = __devexit_p(s6000_i2s_remove),
601
.driver = {
602
.name = "s6000-i2s",
603
.owner = THIS_MODULE,
604
},
605
};
606
607
static int __init s6000_i2s_init(void)
608
{
609
return platform_driver_register(&s6000_i2s_driver);
610
}
611
module_init(s6000_i2s_init);
612
613
static void __exit s6000_i2s_exit(void)
614
{
615
platform_driver_unregister(&s6000_i2s_driver);
616
}
617
module_exit(s6000_i2s_exit);
618
619
MODULE_AUTHOR("Daniel Gloeckner");
620
MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface");
621
MODULE_LICENSE("GPL");
622
623