1
2
3
4
5
6
7
8#include <linux/clk.h>
9#include <linux/io.h>
10#include <linux/module.h>
11#include <sound/pcm.h>
12#include <sound/pcm_params.h>
13#include <sound/soc.h>
14#include "kmb_platform.h"
15
16#define PERIODS_MIN 2
17#define PERIODS_MAX 48
18#define PERIOD_BYTES_MIN 4096
19#define BUFFER_BYTES_MAX (PERIODS_MAX * PERIOD_BYTES_MIN)
20#define TDM_OPERATION 1
21#define I2S_OPERATION 0
22#define DATA_WIDTH_CONFIG_BIT 6
23#define TDM_CHANNEL_CONFIG_BIT 3
24
25static const struct snd_pcm_hardware kmb_pcm_hardware = {
26 .info = SNDRV_PCM_INFO_INTERLEAVED |
27 SNDRV_PCM_INFO_MMAP |
28 SNDRV_PCM_INFO_MMAP_VALID |
29 SNDRV_PCM_INFO_BATCH |
30 SNDRV_PCM_INFO_BLOCK_TRANSFER,
31 .rates = SNDRV_PCM_RATE_8000 |
32 SNDRV_PCM_RATE_16000 |
33 SNDRV_PCM_RATE_48000,
34 .rate_min = 8000,
35 .rate_max = 48000,
36 .formats = SNDRV_PCM_FMTBIT_S16_LE |
37 SNDRV_PCM_FMTBIT_S24_LE |
38 SNDRV_PCM_FMTBIT_S32_LE,
39 .channels_min = 2,
40 .channels_max = 2,
41 .buffer_bytes_max = BUFFER_BYTES_MAX,
42 .period_bytes_min = PERIOD_BYTES_MIN,
43 .period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
44 .periods_min = PERIODS_MIN,
45 .periods_max = PERIODS_MAX,
46 .fifo_size = 16,
47};
48
49static unsigned int kmb_pcm_tx_fn(struct kmb_i2s_info *kmb_i2s,
50 struct snd_pcm_runtime *runtime,
51 unsigned int tx_ptr, bool *period_elapsed)
52{
53 unsigned int period_pos = tx_ptr % runtime->period_size;
54 void __iomem *i2s_base = kmb_i2s->i2s_base;
55 void *buf = runtime->dma_area;
56 int i;
57
58
59 for (i = 0; i < kmb_i2s->fifo_th; i++) {
60 if (kmb_i2s->config.data_width == 16) {
61 writel(((u16(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));
62 writel(((u16(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));
63 } else {
64 writel(((u32(*)[2])buf)[tx_ptr][0], i2s_base + LRBR_LTHR(0));
65 writel(((u32(*)[2])buf)[tx_ptr][1], i2s_base + RRBR_RTHR(0));
66 }
67
68 period_pos++;
69
70 if (++tx_ptr >= runtime->buffer_size)
71 tx_ptr = 0;
72 }
73
74 *period_elapsed = period_pos >= runtime->period_size;
75
76 return tx_ptr;
77}
78
79static unsigned int kmb_pcm_rx_fn(struct kmb_i2s_info *kmb_i2s,
80 struct snd_pcm_runtime *runtime,
81 unsigned int rx_ptr, bool *period_elapsed)
82{
83 unsigned int period_pos = rx_ptr % runtime->period_size;
84 void __iomem *i2s_base = kmb_i2s->i2s_base;
85 void *buf = runtime->dma_area;
86 int i;
87
88
89 for (i = 0; i < kmb_i2s->fifo_th; i++) {
90 if (kmb_i2s->config.data_width == 16) {
91 ((u16(*)[2])buf)[rx_ptr][0] = readl(i2s_base + LRBR_LTHR(0));
92 ((u16(*)[2])buf)[rx_ptr][1] = readl(i2s_base + RRBR_RTHR(0));
93 } else {
94 ((u32(*)[2])buf)[rx_ptr][0] = readl(i2s_base + LRBR_LTHR(0));
95 ((u32(*)[2])buf)[rx_ptr][1] = readl(i2s_base + RRBR_RTHR(0));
96 }
97
98 period_pos++;
99
100 if (++rx_ptr >= runtime->buffer_size)
101 rx_ptr = 0;
102 }
103
104 *period_elapsed = period_pos >= runtime->period_size;
105
106 return rx_ptr;
107}
108
109static inline void kmb_i2s_disable_channels(struct kmb_i2s_info *kmb_i2s,
110 u32 stream)
111{
112 u32 i;
113
114
115 if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
116 for (i = 0; i < MAX_ISR; i++)
117 writel(0, kmb_i2s->i2s_base + TER(i));
118 } else {
119 for (i = 0; i < MAX_ISR; i++)
120 writel(0, kmb_i2s->i2s_base + RER(i));
121 }
122}
123
124static inline void kmb_i2s_clear_irqs(struct kmb_i2s_info *kmb_i2s, u32 stream)
125{
126 struct i2s_clk_config_data *config = &kmb_i2s->config;
127 u32 i;
128
129 if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
130 for (i = 0; i < config->chan_nr / 2; i++)
131 readl(kmb_i2s->i2s_base + TOR(i));
132 } else {
133 for (i = 0; i < config->chan_nr / 2; i++)
134 readl(kmb_i2s->i2s_base + ROR(i));
135 }
136}
137
138static inline void kmb_i2s_irq_trigger(struct kmb_i2s_info *kmb_i2s,
139 u32 stream, int chan_nr, bool trigger)
140{
141 u32 i, irq;
142 u32 flag;
143
144 if (stream == SNDRV_PCM_STREAM_PLAYBACK)
145 flag = TX_INT_FLAG;
146 else
147 flag = RX_INT_FLAG;
148
149 for (i = 0; i < chan_nr / 2; i++) {
150 irq = readl(kmb_i2s->i2s_base + IMR(i));
151
152 if (trigger)
153 irq = irq & ~flag;
154 else
155 irq = irq | flag;
156
157 writel(irq, kmb_i2s->i2s_base + IMR(i));
158 }
159}
160
161static void kmb_pcm_operation(struct kmb_i2s_info *kmb_i2s, bool playback)
162{
163 struct snd_pcm_substream *substream;
164 bool period_elapsed;
165 unsigned int new_ptr;
166 unsigned int ptr;
167
168 if (playback)
169 substream = kmb_i2s->tx_substream;
170 else
171 substream = kmb_i2s->rx_substream;
172
173 if (!substream || !snd_pcm_running(substream))
174 return;
175
176 if (playback) {
177 ptr = kmb_i2s->tx_ptr;
178 new_ptr = kmb_pcm_tx_fn(kmb_i2s, substream->runtime,
179 ptr, &period_elapsed);
180 cmpxchg(&kmb_i2s->tx_ptr, ptr, new_ptr);
181 } else {
182 ptr = kmb_i2s->rx_ptr;
183 new_ptr = kmb_pcm_rx_fn(kmb_i2s, substream->runtime,
184 ptr, &period_elapsed);
185 cmpxchg(&kmb_i2s->rx_ptr, ptr, new_ptr);
186 }
187
188 if (period_elapsed)
189 snd_pcm_period_elapsed(substream);
190}
191
192static int kmb_pcm_open(struct snd_soc_component *component,
193 struct snd_pcm_substream *substream)
194{
195 struct snd_pcm_runtime *runtime = substream->runtime;
196 struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
197 struct kmb_i2s_info *kmb_i2s;
198
199 kmb_i2s = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0));
200 snd_soc_set_runtime_hwparams(substream, &kmb_pcm_hardware);
201 snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
202 runtime->private_data = kmb_i2s;
203
204 return 0;
205}
206
207static int kmb_pcm_trigger(struct snd_soc_component *component,
208 struct snd_pcm_substream *substream, int cmd)
209{
210 struct snd_pcm_runtime *runtime = substream->runtime;
211 struct kmb_i2s_info *kmb_i2s = runtime->private_data;
212
213 switch (cmd) {
214 case SNDRV_PCM_TRIGGER_START:
215 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
216 kmb_i2s->tx_ptr = 0;
217 kmb_i2s->tx_substream = substream;
218 } else {
219 kmb_i2s->rx_ptr = 0;
220 kmb_i2s->rx_substream = substream;
221 }
222 break;
223 case SNDRV_PCM_TRIGGER_STOP:
224 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
225 kmb_i2s->tx_substream = NULL;
226 else
227 kmb_i2s->rx_substream = NULL;
228 break;
229 default:
230 return -EINVAL;
231 }
232
233 return 0;
234}
235
236static irqreturn_t kmb_i2s_irq_handler(int irq, void *dev_id)
237{
238 struct kmb_i2s_info *kmb_i2s = dev_id;
239 struct i2s_clk_config_data *config = &kmb_i2s->config;
240 irqreturn_t ret = IRQ_NONE;
241 u32 isr[4];
242 int i;
243
244 for (i = 0; i < config->chan_nr / 2; i++)
245 isr[i] = readl(kmb_i2s->i2s_base + ISR(i));
246
247 kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);
248 kmb_i2s_clear_irqs(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);
249
250 for (i = 0; i < config->chan_nr / 2; i++) {
251
252
253
254 if ((isr[i] & ISR_TXFE)) {
255 kmb_pcm_operation(kmb_i2s, true);
256 ret = IRQ_HANDLED;
257 }
258
259
260
261 if ((isr[i] & ISR_RXDA)) {
262 kmb_pcm_operation(kmb_i2s, false);
263 ret = IRQ_HANDLED;
264 }
265
266 if (isr[i] & ISR_TXFO) {
267 dev_dbg(kmb_i2s->dev, "TX overrun (ch_id=%d)\n", i);
268 ret = IRQ_HANDLED;
269 }
270
271 if (isr[i] & ISR_RXFO) {
272 dev_dbg(kmb_i2s->dev, "RX overrun (ch_id=%d)\n", i);
273 ret = IRQ_HANDLED;
274 }
275 }
276
277 return ret;
278}
279
280static int kmb_platform_pcm_new(struct snd_soc_component *component,
281 struct snd_soc_pcm_runtime *soc_runtime)
282{
283 size_t size = kmb_pcm_hardware.buffer_bytes_max;
284
285 snd_pcm_set_managed_buffer_all(soc_runtime->pcm,
286 SNDRV_DMA_TYPE_CONTINUOUS,
287 NULL, size, size);
288 return 0;
289}
290
291static snd_pcm_uframes_t kmb_pcm_pointer(struct snd_soc_component *component,
292 struct snd_pcm_substream *substream)
293{
294 struct snd_pcm_runtime *runtime = substream->runtime;
295 struct kmb_i2s_info *kmb_i2s = runtime->private_data;
296 snd_pcm_uframes_t pos;
297
298 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
299 pos = kmb_i2s->tx_ptr;
300 else
301 pos = kmb_i2s->rx_ptr;
302
303 return pos < runtime->buffer_size ? pos : 0;
304}
305
306static const struct snd_soc_component_driver kmb_component = {
307 .name = "kmb",
308 .pcm_construct = kmb_platform_pcm_new,
309 .open = kmb_pcm_open,
310 .trigger = kmb_pcm_trigger,
311 .pointer = kmb_pcm_pointer,
312};
313
314static void kmb_i2s_start(struct kmb_i2s_info *kmb_i2s,
315 struct snd_pcm_substream *substream)
316{
317 struct i2s_clk_config_data *config = &kmb_i2s->config;
318
319
320 writel(1, kmb_i2s->i2s_base + IER);
321
322 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
323 writel(1, kmb_i2s->i2s_base + ITER);
324 else
325 writel(1, kmb_i2s->i2s_base + IRER);
326
327 kmb_i2s_irq_trigger(kmb_i2s, substream->stream, config->chan_nr, true);
328
329 if (kmb_i2s->master)
330 writel(1, kmb_i2s->i2s_base + CER);
331 else
332 writel(0, kmb_i2s->i2s_base + CER);
333}
334
335static void kmb_i2s_stop(struct kmb_i2s_info *kmb_i2s,
336 struct snd_pcm_substream *substream)
337{
338
339 kmb_i2s_clear_irqs(kmb_i2s, substream->stream);
340
341 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
342 writel(0, kmb_i2s->i2s_base + ITER);
343 else
344 writel(0, kmb_i2s->i2s_base + IRER);
345
346 kmb_i2s_irq_trigger(kmb_i2s, substream->stream, 8, false);
347
348 if (!kmb_i2s->active) {
349 writel(0, kmb_i2s->i2s_base + CER);
350 writel(0, kmb_i2s->i2s_base + IER);
351 }
352}
353
354static void kmb_disable_clk(void *clk)
355{
356 clk_disable_unprepare(clk);
357}
358
359static int kmb_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
360{
361 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
362 int ret;
363
364 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
365 case SND_SOC_DAIFMT_CBM_CFM:
366 kmb_i2s->master = false;
367 ret = 0;
368 break;
369 case SND_SOC_DAIFMT_CBS_CFS:
370 writel(MASTER_MODE, kmb_i2s->pss_base + I2S_GEN_CFG_0);
371
372 ret = clk_prepare_enable(kmb_i2s->clk_i2s);
373 if (ret < 0)
374 return ret;
375
376 ret = devm_add_action_or_reset(kmb_i2s->dev, kmb_disable_clk,
377 kmb_i2s->clk_i2s);
378 if (ret)
379 return ret;
380
381 kmb_i2s->master = true;
382 break;
383 default:
384 return -EINVAL;
385 }
386
387 return ret;
388}
389
390static int kmb_dai_trigger(struct snd_pcm_substream *substream,
391 int cmd, struct snd_soc_dai *cpu_dai)
392{
393 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
394
395 switch (cmd) {
396 case SNDRV_PCM_TRIGGER_START:
397
398
399
400 kmb_i2s->active++;
401 kmb_i2s_start(kmb_i2s, substream);
402 break;
403 case SNDRV_PCM_TRIGGER_STOP:
404 kmb_i2s->active--;
405 kmb_i2s_stop(kmb_i2s, substream);
406 break;
407 default:
408 return -EINVAL;
409 }
410
411 return 0;
412}
413
414static void kmb_i2s_config(struct kmb_i2s_info *kmb_i2s, int stream)
415{
416 struct i2s_clk_config_data *config = &kmb_i2s->config;
417 u32 ch_reg;
418
419 kmb_i2s_disable_channels(kmb_i2s, stream);
420
421 for (ch_reg = 0; ch_reg < config->chan_nr / 2; ch_reg++) {
422 if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
423 writel(kmb_i2s->xfer_resolution,
424 kmb_i2s->i2s_base + TCR(ch_reg));
425
426 writel(kmb_i2s->fifo_th - 1,
427 kmb_i2s->i2s_base + TFCR(ch_reg));
428
429 writel(1, kmb_i2s->i2s_base + TER(ch_reg));
430 } else {
431 writel(kmb_i2s->xfer_resolution,
432 kmb_i2s->i2s_base + RCR(ch_reg));
433
434 writel(kmb_i2s->fifo_th - 1,
435 kmb_i2s->i2s_base + RFCR(ch_reg));
436
437 writel(1, kmb_i2s->i2s_base + RER(ch_reg));
438 }
439 }
440}
441
442static int kmb_dai_hw_params(struct snd_pcm_substream *substream,
443 struct snd_pcm_hw_params *hw_params,
444 struct snd_soc_dai *cpu_dai)
445{
446 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
447 struct i2s_clk_config_data *config = &kmb_i2s->config;
448 u32 register_val, write_val;
449 int ret;
450
451 switch (params_format(hw_params)) {
452 case SNDRV_PCM_FORMAT_S16_LE:
453 config->data_width = 16;
454 kmb_i2s->ccr = 0x00;
455 kmb_i2s->xfer_resolution = 0x02;
456 break;
457 case SNDRV_PCM_FORMAT_S24_LE:
458 config->data_width = 24;
459 kmb_i2s->ccr = 0x08;
460 kmb_i2s->xfer_resolution = 0x04;
461 break;
462 case SNDRV_PCM_FORMAT_S32_LE:
463 config->data_width = 32;
464 kmb_i2s->ccr = 0x10;
465 kmb_i2s->xfer_resolution = 0x05;
466 break;
467 default:
468 dev_err(kmb_i2s->dev, "kmb: unsupported PCM fmt");
469 return -EINVAL;
470 }
471
472 config->chan_nr = params_channels(hw_params);
473
474 switch (config->chan_nr) {
475
476 case TWO_CHANNEL_SUPPORT:
477 write_val = ((config->chan_nr / 2) << TDM_CHANNEL_CONFIG_BIT) |
478 (config->data_width << DATA_WIDTH_CONFIG_BIT) |
479 MASTER_MODE | I2S_OPERATION;
480
481 writel(write_val, kmb_i2s->pss_base + I2S_GEN_CFG_0);
482
483 register_val = readl(kmb_i2s->pss_base + I2S_GEN_CFG_0);
484 dev_dbg(kmb_i2s->dev, "pss register = 0x%X", register_val);
485 break;
486 default:
487 dev_dbg(kmb_i2s->dev, "channel not supported\n");
488 return -EINVAL;
489 }
490
491 kmb_i2s_config(kmb_i2s, substream->stream);
492
493 writel(kmb_i2s->ccr, kmb_i2s->i2s_base + CCR);
494
495 config->sample_rate = params_rate(hw_params);
496
497 if (kmb_i2s->master) {
498
499 u32 bitclk = config->sample_rate * config->data_width * 2;
500
501 ret = clk_set_rate(kmb_i2s->clk_i2s, bitclk);
502 if (ret) {
503 dev_err(kmb_i2s->dev,
504 "Can't set I2S clock rate: %d\n", ret);
505 return ret;
506 }
507 }
508
509 return 0;
510}
511
512static int kmb_dai_prepare(struct snd_pcm_substream *substream,
513 struct snd_soc_dai *cpu_dai)
514{
515 struct kmb_i2s_info *kmb_i2s = snd_soc_dai_get_drvdata(cpu_dai);
516
517 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
518 writel(1, kmb_i2s->i2s_base + TXFFR);
519 else
520 writel(1, kmb_i2s->i2s_base + RXFFR);
521
522 return 0;
523}
524
525static struct snd_soc_dai_ops kmb_dai_ops = {
526 .trigger = kmb_dai_trigger,
527 .hw_params = kmb_dai_hw_params,
528 .prepare = kmb_dai_prepare,
529 .set_fmt = kmb_set_dai_fmt,
530};
531
532static struct snd_soc_dai_driver intel_kmb_platform_dai[] = {
533 {
534 .name = "kmb-plat-dai",
535 .playback = {
536 .channels_min = 2,
537 .channels_max = 2,
538 .rates = SNDRV_PCM_RATE_8000 |
539 SNDRV_PCM_RATE_16000 |
540 SNDRV_PCM_RATE_48000,
541 .rate_min = 8000,
542 .rate_max = 48000,
543 .formats = (SNDRV_PCM_FMTBIT_S32_LE |
544 SNDRV_PCM_FMTBIT_S24_LE |
545 SNDRV_PCM_FMTBIT_S16_LE),
546 },
547 .capture = {
548 .channels_min = 2,
549 .channels_max = 2,
550
551
552
553
554 .rates = SNDRV_PCM_RATE_8000 |
555 SNDRV_PCM_RATE_16000 |
556 SNDRV_PCM_RATE_48000,
557 .rate_min = 8000,
558 .rate_max = 48000,
559 .formats = (SNDRV_PCM_FMTBIT_S32_LE |
560 SNDRV_PCM_FMTBIT_S24_LE |
561 SNDRV_PCM_FMTBIT_S16_LE),
562 },
563 .ops = &kmb_dai_ops,
564 },
565};
566
567static int kmb_plat_dai_probe(struct platform_device *pdev)
568{
569 struct snd_soc_dai_driver *kmb_i2s_dai;
570 struct device *dev = &pdev->dev;
571 struct kmb_i2s_info *kmb_i2s;
572 int ret, irq;
573 u32 comp1_reg;
574
575 kmb_i2s = devm_kzalloc(dev, sizeof(*kmb_i2s), GFP_KERNEL);
576 if (!kmb_i2s)
577 return -ENOMEM;
578
579 kmb_i2s_dai = devm_kzalloc(dev, sizeof(*kmb_i2s_dai), GFP_KERNEL);
580 if (!kmb_i2s_dai)
581 return -ENOMEM;
582
583 kmb_i2s_dai->ops = &kmb_dai_ops;
584
585
586 kmb_i2s->clk_apb = devm_clk_get(dev, "apb_clk");
587 if (IS_ERR(kmb_i2s->clk_apb)) {
588 dev_err(dev, "Failed to get apb clock\n");
589 return PTR_ERR(kmb_i2s->clk_apb);
590 }
591
592 ret = clk_prepare_enable(kmb_i2s->clk_apb);
593 if (ret < 0)
594 return ret;
595
596 ret = devm_add_action_or_reset(dev, kmb_disable_clk, kmb_i2s->clk_apb);
597 if (ret) {
598 dev_err(dev, "Failed to add clk_apb reset action\n");
599 return ret;
600 }
601
602 kmb_i2s->clk_i2s = devm_clk_get(dev, "osc");
603 if (IS_ERR(kmb_i2s->clk_i2s)) {
604 dev_err(dev, "Failed to get osc clock\n");
605 return PTR_ERR(kmb_i2s->clk_i2s);
606 }
607
608 kmb_i2s->i2s_base = devm_platform_ioremap_resource(pdev, 0);
609 if (IS_ERR(kmb_i2s->i2s_base))
610 return PTR_ERR(kmb_i2s->i2s_base);
611
612 kmb_i2s->pss_base = devm_platform_ioremap_resource(pdev, 1);
613 if (IS_ERR(kmb_i2s->pss_base))
614 return PTR_ERR(kmb_i2s->pss_base);
615
616 kmb_i2s->dev = &pdev->dev;
617
618 irq = platform_get_irq_optional(pdev, 0);
619 if (irq > 0) {
620 ret = devm_request_irq(dev, irq, kmb_i2s_irq_handler, 0,
621 pdev->name, kmb_i2s);
622 if (ret < 0) {
623 dev_err(dev, "failed to request irq\n");
624 return ret;
625 }
626 }
627
628 comp1_reg = readl(kmb_i2s->i2s_base + I2S_COMP_PARAM_1);
629
630 kmb_i2s->fifo_th = (1 << COMP1_FIFO_DEPTH(comp1_reg)) / 2;
631
632 ret = devm_snd_soc_register_component(dev, &kmb_component,
633 intel_kmb_platform_dai,
634 ARRAY_SIZE(intel_kmb_platform_dai));
635 if (ret) {
636 dev_err(dev, "not able to register dai\n");
637 return ret;
638 }
639
640
641 kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_PLAYBACK);
642 kmb_i2s_disable_channels(kmb_i2s, SNDRV_PCM_STREAM_CAPTURE);
643
644 dev_set_drvdata(dev, kmb_i2s);
645
646 return ret;
647}
648
649static const struct of_device_id kmb_plat_of_match[] = {
650 { .compatible = "intel,keembay-i2s", },
651 {}
652};
653
654static struct platform_driver kmb_plat_dai_driver = {
655 .driver = {
656 .name = "kmb-plat-dai",
657 .of_match_table = kmb_plat_of_match,
658 },
659 .probe = kmb_plat_dai_probe,
660};
661
662module_platform_driver(kmb_plat_dai_driver);
663
664MODULE_DESCRIPTION("ASoC Intel KeemBay Platform driver");
665MODULE_AUTHOR("Sia Jee Heng <jee.heng.sia@intel.com>");
666MODULE_AUTHOR("Sit, Michael Wei Hong <michael.wei.hong.sit@intel.com>");
667MODULE_LICENSE("GPL v2");
668MODULE_ALIAS("platform:kmb_platform");
669