aboutsummaryrefslogtreecommitdiffstats
path: root/sound/soc/omap/omap-mcasp.c
blob: e0a6e249cd7fbf57cf10a9938502aff38ec51f3d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
/*
 * ALSA SoC McASP Audio Layer for TI OMAP processor
 *
 * Multi-channel Audio Serial Port Driver
 *
 * Author: Jon Hunter <jon-hunter@ti.com>,
 *         Dan Milea <dan.milea@ti.com>,
 *
 * Based upon McASP driver written for TI DaVinci
 *
 * Copyright:   (C) 2011  Texas Instruments
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/pm_runtime.h>

#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>

#include <plat/omap_hwmod.h>
#include <plat/clock.h>
#include <plat/dma.h>
#include <plat/dma-44xx.h>

#include "omap-pcm.h"
#include "omap-mcasp.h"

/*
 * McASP register definitions
 */
#define OMAP_MCASP_PID_REG		0x00
#define OMAP_MCASP_SYSCONFIG_REG	0x04

#define OMAP_MCASP_PFUNC_REG		0x10
#define OMAP_MCASP_PDIR_REG		0x14
#define OMAP_MCASP_PDOUT_REG		0x18
#define OMAP_MCASP_PDIN_REG		0x1c
#define OMAP_MCASP_PDSET_REG		0x1c
#define OMAP_MCASP_PDCLR_REG		0x20

#define OMAP_MCASP_GBLCTL_REG		0x44
#define OMAP_MCASP_AMUTE_REG		0x48

#define OMAP_MCASP_TXDITCTL_REG		0x50

#define OMAP_MCASP_TXMASK_REG		0xa4
#define OMAP_MCASP_TXFMT_REG		0xa8
#define OMAP_MCASP_TXFMCTL_REG		0xac

#define OMAP_MCASP_ACLKXCTL_REG		0xb0
#define OMAP_MCASP_AHCLKXCTL_REG	0xb4
#define OMAP_MCASP_TXTDM_REG		0xb8
#define OMAP_MCASP_EVTCTLX_REG		0xbc

#define OMAP_MCASP_TXSTAT_REG		0xc0
#define OMAP_MCASP_TXSTAT_MASK		0x1ff

#define OMAP_MCASP_TXTDMSLOT_REG	0xc4
#define OMAP_MCASP_TXCLKCHK_REG		0xc8
#define OMAP_MCASP_TXEVTCTL_REG		0xcc

/* Left(even TDM Slot) Channel Status Register File */
#define OMAP_MCASP_DITCSRA_REG	0x100
/* Right(odd TDM slot) Channel Status Register File */
#define OMAP_MCASP_DITCSRB_REG	0x118
/* Left(even TDM slot) User Data Register File */
#define OMAP_MCASP_DITUDRA_REG	0x130
/* Right(odd TDM Slot) User Data Register File */
#define OMAP_MCASP_DITUDRB_REG	0x148

/* Serializer n Control Register */
#define OMAP_MCASP_XRSRCTL0_REG	0x180

/* Transmit Buffer for Serializer */
#define OMAP_MCASP_TXBUF0_REG	0x200

/*
 * OMAP_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits
 */
#define AXR0		BIT(0)
#define PFUNC_AMUTE	BIT(25)
#define ACLKX		BIT(26)
#define AHCLKX		BIT(27)
#define AFSX		BIT(28)

/*
 * OMAP_MCASP_PDIR_REG - Pin Direction Register Bits
 */
#define AXR0		BIT(0)
#define PDIR_AMUTE	BIT(25)
#define ACLKX		BIT(26)
#define AHCLKX		BIT(27)
#define AFSX		BIT(28)

/*
 * OMAP_MCASP_TXDITCTL_REG - Transmit DIT Control Register Bits
 */
#define DITEN	BIT(0)	/* Transmit DIT mode enable/disable */
#define VA	BIT(2)
#define VB	BIT(3)

/*
 * OMAP_MCASP_TXFMT_REG - Transmit Bitstream Format Register Bits
 */
#define TXROT(val)	(val)
#define TXROT_MASK	TXROT(0x7)
#define TXSEL		BIT(3)
#define TXSSZ(val)	(val<<4)
#define TXSSZ_MASK	TXSSZ(0xf<<4)
#define TXPAD(val)	(val<<13)
#define TXORD		BIT(15)
#define FSXDLY(val)	(val<<16)

#define ROTATE_24	0x6
#define SLOTSIZE_32	0xf

/*
 * OMAP_MCASP_TXFMCTL_REG -  Transmit Frame Control Register Bits
 */
#define FSXPOL		BIT(0)
#define AFSXE		BIT(1)
#define FSXDUR		BIT(4)
#define FSXMOD(val)	(val<<7)

/*
 * OMAP_MCASP_ACLKXCTL_REG - Transmit Clock Control Register Bits
 */
#define ACLKXDIV(val)	(val)
#define ACLKXE		BIT(5)
#define TX_ASYNC	BIT(6)

/*
 * OMAP_MCASP_AHCLKXCTL_REG - High Frequency Transmit Clock Control
 *     Register Bits
 */
#define AHCLKXDIV(val)	(val)
#define AHCLKXE		BIT(15)

/*
 * OMAP_MCASP_EVTCTLX_REG - Transmitter Interrupt Control Register bits
 */
#define EVTCTLX_XUNDRN		BIT(0)

/*
 * OMAP_MCASP_TXSTAT_REG - Transmit Status Register Bits
 */
#define TXSTAT_XUNDRN	(0x1 << 0)
#define TXSTAT_XSYNCERR	(0x1 << 1)
#define TXSTAT_XCKFAIL	(0x1 << 2)
#define TXSTAT_XDMSLOT	(0x1 << 3)
#define TXSTAT_XLAST	(0x1 << 4)
#define TXSTAT_XDATA	(0x1 << 5)
#define TXSTAT_XSTAFRM	(0x1 << 6)
#define TXSTAT_XDMAERR	(0x1 << 7)
#define TXSTAT_XERR	(0x1 << 8)

/*
 * OMAP_MCASP_XRSRCTL_BASE_REG -  Serializer Control Register Bits
 */
#define MODE(val)	(val)
#define TXSTATE		BIT(4)

/*
 * OMAP_MCASP_TXTDMSLOT_REG - Transmit TDM Slot Register configuration
 */
#define TXTDMS(n)	(1<<n)

/*
 * OMAP_MCASP_GBLCTL_REG -  Global Control Register Bits
 */
#define TXCLKRST	BIT(8)	/* Transmitter Clock Divider Reset */
#define TXHCLKRST	BIT(9)	/* Transmitter High Frequency Clock Divider*/
#define TXSERCLR	BIT(10)	/* Transmit Serializer Clear */
#define TXSMRST		BIT(11)	/* Transmitter State Machine Reset */
#define TXFSRST		BIT(12)	/* Frame Sync Generator Reset */

/*
 * OMAP_MCASP_AMUTE_REG -  Mute Control Register Bits
 */
#define MUTENA(val)	(val)
#define MUTEINPOL	BIT(2)
#define MUTEINENA	BIT(3)
#define MUTEIN		BIT(4)
#define MUTEX		BIT(6)
#define MUTEFSX		BIT(8)
#define MUTEBADCLKX	BIT(10)
#define MUTETXDMAERR	BIT(12)

/*
 * OMAP_MCASP_TXEVTCTL_REG - Transmitter DMA Event Control Register bits
 */
#define TXDATADMADIS	BIT(0)

#define MCASP_ALLOWED_PPM	100

/*
 * OMAP_MCASP_DITCSRA_REG/OMAP_MCASP_DITCSRB_REG
 */
#define OMAP_MCASP_DITCSR_44100HZ	(0x0 << 24)
#define OMAP_MCASP_DITCSR_48000HZ	(0x2 << 24)
#define OMAP_MCASP_DITCSR_32000HZ	(0x3 << 24)
#define OMAP_MCASP_DITCSR_22050HZ	(0x4 << 24)
#define OMAP_MCASP_DITCSR_24000HZ	(0x6 << 24)
#define OMAP_MCASP_DITCSR_88200HZ	(0x8 << 24)
#define OMAP_MCASP_DITCSR_96000HZ	(0xA << 24)
#define OMAP_MCASP_DITCSR_176400HZ	(0xC << 24)
#define OMAP_MCASP_DITCSR_192000HZ	(0xE << 24)

/*
 * Stream DMA parameters
 */
static struct omap_pcm_dma_data omap_mcasp_dai_dma_params[] = {
	{
		.name = "Audio playback",
		.dma_req = OMAP44XX_DMA_MCASP1_AXEVT,
		.data_type = OMAP_DMA_DATA_TYPE_S16,
		.sync_mode = OMAP_DMA_SYNC_ELEMENT,
		.port_addr = OMAP44XX_MCASP_DAT_BASE + OMAP_MCASP_TXBUF0_REG,
	},
};

static inline void mcasp_set_bits(void __iomem *reg, u32 val)
{
	__raw_writel(__raw_readl(reg) | val, reg);
}

static inline void mcasp_clr_bits(void __iomem *reg, u32 val)
{
	__raw_writel((__raw_readl(reg) & ~(val)), reg);
}

static inline void mcasp_mod_bits(void __iomem *reg, u32 val, u32 mask)
{
	__raw_writel((__raw_readl(reg) & ~mask) | val, reg);
}

static inline void mcasp_set_reg(void __iomem *reg, u32 val)
{
	__raw_writel(val, reg);
}

static inline u32 mcasp_get_reg(void __iomem *reg)
{
	return (unsigned int)__raw_readl(reg);
}

static inline void mcasp_set_ctl_reg(void __iomem *regs, u32 val)
{
	int i = 0;

	mcasp_set_bits(regs, val);

	/* programming GBLCTL needs to read back from GBLCTL and verfiy */
	/* loop count is to avoid the lock-up */
	for (i = 0; i < 1000; i++) {
		if ((mcasp_get_reg(regs) & val) == val)
			break;
	}

	if (i == 1000 && ((mcasp_get_reg(regs) & val) != val))
		printk(KERN_ERR "GBLCTL write error\n");
}

static int mcasp_compute_clock_dividers(long fclk_rate, int tgt_sample_rate,
			int *out_div_lo, int *out_div_hi)
{
	/* Given a particular functional clock rate and a target audio sample
	 * rate, determine the proper values for the ACLKXCTL and AHCLKXCTL, the
	 * dividers which produce the high frequency transmit master clock and
	 * the transmit clock.
	 */
	long divisor;
	unsigned long ppm;
	int sample_rate, i;
	BUG_ON(!out_div_lo);
	BUG_ON(!out_div_hi);

	/* A single S/PDIF frame requires 128 clocks */
	divisor = DIV_ROUND_CLOSEST(fclk_rate, tgt_sample_rate << 7);
	if (!divisor)
		return -EINVAL;

	sample_rate = (fclk_rate >> 7) / divisor;

	/* ppm calculation in two steps to avoid overflow */
	ppm = abs(tgt_sample_rate - sample_rate);
	ppm = (1000000 * ppm) / tgt_sample_rate;

	if (ppm > MCASP_ALLOWED_PPM)
		return -EINVAL;

	/* At this point, divisor holds the product of the two divider values we
	 * need to use for ACLKXCTL and AHCLKXCTL.  ACLKXCTL holds a 5 bit
	 * divider [1, 32], while AHCLKXCTL holds a 12 bit divider [1, 4096].
	 * We need to make sure that we can factor divisor into two integers
	 * which will fit into these divider registers.  Find the largest 5-bit
	 * + 1 value which divides divisor and use that as our smaller divider.
	 * After removing this factor from divisor, if the result is <= 4096,
	 * then we have succeeded and will be able to produce the target sample
	 * rate.
	 */
	for (i = 32; (i > 1) && (divisor % i); --i)
		; /* no body */

	/* Make sure to subtract one, registers hold the value of the divider
	 * minus one (IOW, to divide by 5, the register gets programmed with the
	 * value 4. */
	*out_div_lo = i - 1;
	*out_div_hi = (divisor / i) - 1;

	return (*out_div_hi <= 4096) ? 0 : -EINVAL;
}

static int omap_mcasp_start(struct omap_mcasp *mcasp)
{
	int i;
	mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXHCLKRST);
	mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXCLKRST);
	mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXSERCLR);

	/* Wait until the DMA has loaded the first sample into TXBUF before we
	 * let the TX state machine and frame sync generator out of reset. */
	i = 0;
	while (1) {
		u32 reg = mcasp_get_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG);
		if (!(reg & TXSTAT_XDATA))
			break;

		if (++i > 1000) {
			printk(KERN_ERR "Timeout waiting for DMA to load first"
					" sample of audio.\n");
			return -ETIMEDOUT;
		}

		udelay(1);
	}

	mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXSMRST);
	mcasp_set_ctl_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, TXFSRST);
	mcasp_clr_bits(mcasp->base + OMAP_MCASP_TXEVTCTL_REG, TXDATADMADIS);

	/* enable IRQ sources */
	mcasp_set_bits(mcasp->base + OMAP_MCASP_EVTCTLX_REG, EVTCTLX_XUNDRN);

	return 0;
}

static void omap_mcasp_stop(struct omap_mcasp *mcasp)
{
	/* disable IRQ sources */
	mcasp_set_reg(mcasp->base + OMAP_MCASP_EVTCTLX_REG, 0);

	mcasp_set_reg(mcasp->base + OMAP_MCASP_GBLCTL_REG, 0);
	mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG,
			OMAP_MCASP_TXSTAT_MASK);
}

/* S/PDIF */
static int omap_mcasp_setup(struct omap_mcasp *mcasp, unsigned int rate)
{
	u32 aclkxdiv, ahclkxdiv, ditcsr;
	int res;

	/* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */
	mcasp_set_reg(mcasp->base + OMAP_MCASP_TXFMCTL_REG,
						AFSXE | FSXMOD(0x180));

	/* Set the TX clock controls : div = 1 and internal */
	mcasp_set_reg(mcasp->base + OMAP_MCASP_ACLKXCTL_REG,
						ACLKXE | TX_ASYNC);

	/* Set the HS TX clock controls : div = 1 and internal */
	mcasp_set_reg(mcasp->base + OMAP_MCASP_AHCLKXCTL_REG, AHCLKXE);

	/* The SPDIF bit clock is derived from the McASP functional clock.
	 * The McASP has two programmable clock dividers (aclkxdiv and
	 * ahclkxdiv) that are configured via the registers MCASP_ACLKXCTL
	 * and MCASP_AHCLKXCTL. For SPDIF the bit clock frequency should be
	 * 128 * sample rate freq. The dividers are defined as part of
	 * platform data as they are dependent upon the functional clock
	 * setting. Lookup the appropriate dividers for the sampling
	 * frequency that we are playing.
	 */
	res = mcasp_compute_clock_dividers(clk_get_rate(mcasp->fclk),
				rate,
				&aclkxdiv,
				&ahclkxdiv);
	if (res) {
		dev_err(mcasp->dev,
			"%s: No valid McASP config for sampling rate (%d)!\n",
			__func__, rate);
		return res;
	}

	switch (rate) {
	case 22050:
		ditcsr = OMAP_MCASP_DITCSR_22050HZ;
		break;
	case 24000:
		ditcsr = OMAP_MCASP_DITCSR_24000HZ;
		break;
	case 32000:
		ditcsr = OMAP_MCASP_DITCSR_32000HZ;
		break;
	case 44100:
		ditcsr = OMAP_MCASP_DITCSR_44100HZ;
		break;
	case 48000:
		ditcsr = OMAP_MCASP_DITCSR_48000HZ;
		break;
	case 88200:
		ditcsr = OMAP_MCASP_DITCSR_88200HZ;
		break;
	case 96000:
		ditcsr = OMAP_MCASP_DITCSR_96000HZ;
		break;
	case 176400:
		ditcsr = OMAP_MCASP_DITCSR_176400HZ;
		break;
	case 192000:
		ditcsr = OMAP_MCASP_DITCSR_192000HZ;
		break;
	default:
		dev_err(mcasp->dev, "%s: Invalid sampling rate: %d\n",
			__func__, rate);
		return -EINVAL;
	}
	mcasp_set_reg(mcasp->base + OMAP_MCASP_DITCSRA_REG, ditcsr);
	mcasp_set_reg(mcasp->base + OMAP_MCASP_DITCSRB_REG, ditcsr);
	mcasp_set_bits(mcasp->base + OMAP_MCASP_AHCLKXCTL_REG,
					AHCLKXDIV(ahclkxdiv));
	mcasp_set_bits(mcasp->base + OMAP_MCASP_ACLKXCTL_REG,
					AHCLKXDIV(aclkxdiv));

	/* Configure McASP formatter */
	mcasp_mod_bits(mcasp->base + OMAP_MCASP_TXFMT_REG,
					TXSSZ(SLOTSIZE_32), TXSSZ_MASK);
	mcasp_mod_bits(mcasp->base + OMAP_MCASP_TXFMT_REG, TXROT(ROTATE_24),
							TXROT_MASK);
	mcasp_set_reg(mcasp->base + OMAP_MCASP_TXMASK_REG, 0xFFFF);

	/* Set the TX tdm : for all the slots */
	mcasp_set_reg(mcasp->base + OMAP_MCASP_TXTDM_REG, 0xFFFFFFFF);

	/* configure the serializer for transmit mode operation */
	mcasp_set_bits(mcasp->base + OMAP_MCASP_XRSRCTL0_REG, MODE(1));

	/* All PINS as McASP */
	mcasp_set_reg(mcasp->base + OMAP_MCASP_PFUNC_REG, 0);

	mcasp_set_bits(mcasp->base + OMAP_MCASP_PDIR_REG, AXR0);

	/* Enable the DIT */
	mcasp_set_bits(mcasp->base + OMAP_MCASP_TXDITCTL_REG, DITEN);

	mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG, 0xFF);

	return 0;
}

static irqreturn_t omap_mcasp_irq_handler(int irq, void *data)
{
	struct omap_mcasp *mcasp = data;
	u32 txstat;

	txstat = mcasp_get_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG);
	if (txstat & TXSTAT_XUNDRN) {
		dev_err(mcasp->dev, "%s: Underrun (0x%08x)\n", __func__,
			txstat);

		/* Try to recover from this state */
		spin_lock(&mcasp->lock);
		if (likely(mcasp->stream_rate)) {
			dev_err(mcasp->dev, "%s: Trying to recover\n",
				__func__);
			omap_mcasp_stop(mcasp);
			omap_mcasp_setup(mcasp, mcasp->stream_rate);
			omap_mcasp_start(mcasp);
		}
		spin_unlock(&mcasp->lock);
	}

	mcasp_set_reg(mcasp->base + OMAP_MCASP_TXSTAT_REG, txstat);

	return IRQ_HANDLED;
}

static int omap_mcasp_startup(struct snd_pcm_substream *substream,
			      struct snd_soc_dai *dai)
{
	struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai);

	/* HACK: Only allow C2 state */
	pm_qos_add_request(mcasp->pm_qos, PM_QOS_CPU_DMA_LATENCY, 1150);

	pm_runtime_get_sync(mcasp->dev);

	return 0;
}

static void omap_mcasp_shutdown(struct snd_pcm_substream *substream,
				struct snd_soc_dai *dai)
{
	struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai);

	pm_runtime_put_sync(mcasp->dev);

	/* HACK: remove qos */
	pm_qos_remove_request(mcasp->pm_qos);
}

static int omap_mcasp_hw_params(struct snd_pcm_substream *substream,
					struct snd_pcm_hw_params *params,
					struct snd_soc_dai *dai)
{
	struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(dai);
	int stream = substream->stream;

	omap_mcasp_stop(mcasp);

	if (omap_mcasp_setup(mcasp, params_rate(params)) < 0)
		return -EPERM;

	snd_soc_dai_set_dma_data(dai, substream,
				 &omap_mcasp_dai_dma_params[stream]);

	return 0;
}

static int omap_mcasp_trigger(struct snd_pcm_substream *substream,
				     int cmd, struct snd_soc_dai *cpu_dai)
{
	struct omap_mcasp *mcasp = snd_soc_dai_get_drvdata(cpu_dai);
	unsigned long flags;
	int ret = 0;

	spin_lock_irqsave(&mcasp->lock, flags);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		mcasp->stream_rate = substream->runtime->rate;
		ret = omap_mcasp_start(mcasp);
		break;

	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		mcasp->stream_rate = 0;
		omap_mcasp_stop(mcasp);
		break;

	default:
		ret = -EINVAL;
	}

	spin_unlock_irqrestore(&mcasp->lock, flags);

	return ret;
}

static struct snd_soc_dai_ops omap_mcasp_dai_ops = {
	.startup	= omap_mcasp_startup,
	.shutdown	= omap_mcasp_shutdown,
	.trigger	= omap_mcasp_trigger,
	.hw_params	= omap_mcasp_hw_params,

};

#define MCASP_RATES	(SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \
			 SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
			 SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 | \
			 SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)

static struct snd_soc_dai_driver omap_mcasp_dai[] = {
	{
		.name		= "omap-mcasp-dai",
		.playback	= {
			.channels_min	= 1,
			.channels_max	= 384,
			.formats	= SNDRV_PCM_FMTBIT_S16_LE,
			.rates		= MCASP_RATES,
		},
		.ops		= &omap_mcasp_dai_ops,
	},
};

static __devinit int omap_mcasp_probe(struct platform_device *pdev)
{
	struct omap_mcasp *mcasp;
	struct resource *res;
	long fclk_rate;
	int ret = 0;

	mcasp = kzalloc(sizeof(struct omap_mcasp), GFP_KERNEL);
	if (!mcasp)
		return -ENOMEM;

	spin_lock_init(&mcasp->lock);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "no resource\n");
		ret = -ENODEV;
		goto err_res;
	}

	mcasp->base = ioremap(res->start, resource_size(res));
	if (!mcasp->base) {
		ret = -ENOMEM;
		goto err_res;
	}

	mcasp->irq = platform_get_irq(pdev, 0);
	if (mcasp->irq < 0) {
		ret = mcasp->irq;
		goto err_irq;
	}

	ret = request_threaded_irq(mcasp->irq, NULL, omap_mcasp_irq_handler,
				0, "McASP", mcasp);
	if (ret) {
		dev_err(mcasp->dev, "IRQ request failed\n");
		goto err_irq;
	}

	mcasp->fclk = clk_get(&pdev->dev, "mcasp_fck");
	if (!mcasp->fclk) {
		ret = -ENODEV;
		goto err_clk;
	}

	pm_runtime_enable(&pdev->dev);
	pm_runtime_get_sync(&pdev->dev);

	fclk_rate = clk_get_rate(mcasp->fclk);

	platform_set_drvdata(pdev, mcasp);
	mcasp->dev = &pdev->dev;

	ret = snd_soc_register_dai(&pdev->dev, omap_mcasp_dai);
	if (ret < 0)
		goto err_dai;

	/* HACK: qos */
	mcasp->pm_qos = kzalloc(sizeof(struct pm_qos_request_list), GFP_KERNEL);
	if (!mcasp->pm_qos) {
		ret = -ENOMEM;
		goto err_dai;
	}

	pm_runtime_put_sync(&pdev->dev);

	return 0;

err_dai:
	pm_runtime_put_sync(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
err_clk:
	free_irq(mcasp->irq, (void *)mcasp);
err_irq:
	iounmap(mcasp->base);
err_res:
	kfree(mcasp);
	return ret;
}

static __devexit int omap_mcasp_remove(struct platform_device *pdev)
{
	struct omap_mcasp *mcasp = dev_get_drvdata(&pdev->dev);

	snd_soc_unregister_dai(&pdev->dev);
	pm_runtime_disable(&pdev->dev);
	clk_put(mcasp->fclk);
	free_irq(mcasp->irq, (void *)mcasp);
	iounmap(mcasp->base);
	/* HACK: qos */
	kfree(mcasp->pm_qos);
	kfree(mcasp);

	return 0;
}

static struct platform_driver omap_mcasp_driver = {
	.probe		= omap_mcasp_probe,
	.remove		= omap_mcasp_remove,
	.driver		= {
		.name	= "omap-mcasp-dai",
		.owner	= THIS_MODULE,
	},
};

static int __init omap_mcasp_init(void)
{
	return platform_driver_register(&omap_mcasp_driver);
}
module_init(omap_mcasp_init);

static void __exit omap_mcasp_exit(void)
{
	platform_driver_unregister(&omap_mcasp_driver);
}
module_exit(omap_mcasp_exit);

MODULE_AUTHOR("Jon Hunter <jon-hunter@ti.com>");
MODULE_DESCRIPTION("TI OMAP McASP SoC Interface");
MODULE_LICENSE("GPL");