1/*
2 * imx-ssi.c  --  ALSA Soc Audio Layer
3 *
4 * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
5 *
6 * This code is based on code copyrighted by Freescale,
7 * Liam Girdwood, Javier Martin and probably others.
8 *
9 *  This program is free software; you can redistribute  it and/or modify it
10 *  under  the terms of  the GNU General  Public License as published by the
11 *  Free Software Foundation;  either version 2 of the  License, or (at your
12 *  option) any later version.
13 *
14 *
15 * The i.MX SSI core has some nasty limitations in AC97 mode. While most
16 * sane processor vendors have a FIFO per AC97 slot, the i.MX has only
17 * one FIFO which combines all valid receive slots. We cannot even select
18 * which slots we want to receive. The WM9712 with which this driver
19 * was developed with always sends GPIO status data in slot 12 which
20 * we receive in our (PCM-) data stream. The only chance we have is to
21 * manually skip this data in the FIQ handler. With sampling rates different
22 * from 48000Hz not every frame has valid receive data, so the ratio
23 * between pcm data and GPIO status data changes. Our FIQ handler is not
24 * able to handle this, hence this driver only works with 48000Hz sampling
25 * rate.
26 * Reading and writing AC97 registers is another challenge. The core
27 * provides us status bits when the read register is updated with *another*
28 * value. When we read the same register two times (and the register still
29 * contains the same value) these status bits are not set. We work
30 * around this by not polling these bits but only wait a fixed delay.
31 *
32 */
33
34#include <linux/clk.h>
35#include <linux/delay.h>
36#include <linux/device.h>
37#include <linux/dma-mapping.h>
38#include <linux/init.h>
39#include <linux/interrupt.h>
40#include <linux/module.h>
41#include <linux/platform_device.h>
42#include <linux/slab.h>
43
44#include <sound/core.h>
45#include <sound/initval.h>
46#include <sound/pcm.h>
47#include <sound/pcm_params.h>
48#include <sound/soc.h>
49
50#include <linux/platform_data/asoc-imx-ssi.h>
51
52#include "imx-ssi.h"
53#include "fsl_utils.h"
54
55#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
56
57/*
58 * SSI Network Mode or TDM slots configuration.
59 * Should only be called when port is inactive (i.e. SSIEN = 0).
60 */
61static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
62	unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
63{
64	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
65	u32 sccr;
66
67	sccr = readl(ssi->base + SSI_STCCR);
68	sccr &= ~SSI_STCCR_DC_MASK;
69	sccr |= SSI_STCCR_DC(slots - 1);
70	writel(sccr, ssi->base + SSI_STCCR);
71
72	sccr = readl(ssi->base + SSI_SRCCR);
73	sccr &= ~SSI_STCCR_DC_MASK;
74	sccr |= SSI_STCCR_DC(slots - 1);
75	writel(sccr, ssi->base + SSI_SRCCR);
76
77	writel(~tx_mask, ssi->base + SSI_STMSK);
78	writel(~rx_mask, ssi->base + SSI_SRMSK);
79
80	return 0;
81}
82
83/*
84 * SSI DAI format configuration.
85 * Should only be called when port is inactive (i.e. SSIEN = 0).
86 */
87static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
88{
89	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
90	u32 strcr = 0, scr;
91
92	scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
93
94	/* DAI mode */
95	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
96	case SND_SOC_DAIFMT_I2S:
97		/* data on rising edge of bclk, frame low 1clk before data */
98		strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
99		scr |= SSI_SCR_NET;
100		if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) {
101			scr &= ~SSI_I2S_MODE_MASK;
102			scr |= SSI_SCR_I2S_MODE_SLAVE;
103		}
104		break;
105	case SND_SOC_DAIFMT_LEFT_J:
106		/* data on rising edge of bclk, frame high with data */
107		strcr |= SSI_STCR_TXBIT0;
108		break;
109	case SND_SOC_DAIFMT_DSP_B:
110		/* data on rising edge of bclk, frame high with data */
111		strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0;
112		break;
113	case SND_SOC_DAIFMT_DSP_A:
114		/* data on rising edge of bclk, frame high 1clk before data */
115		strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0 | SSI_STCR_TEFS;
116		break;
117	}
118
119	/* DAI clock inversion */
120	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
121	case SND_SOC_DAIFMT_IB_IF:
122		strcr |= SSI_STCR_TFSI;
123		strcr &= ~SSI_STCR_TSCKP;
124		break;
125	case SND_SOC_DAIFMT_IB_NF:
126		strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
127		break;
128	case SND_SOC_DAIFMT_NB_IF:
129		strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
130		break;
131	case SND_SOC_DAIFMT_NB_NF:
132		strcr &= ~SSI_STCR_TFSI;
133		strcr |= SSI_STCR_TSCKP;
134		break;
135	}
136
137	/* DAI clock master masks */
138	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
139	case SND_SOC_DAIFMT_CBM_CFM:
140		break;
141	default:
142		/* Master mode not implemented, needs handling of clocks. */
143		return -EINVAL;
144	}
145
146	strcr |= SSI_STCR_TFEN0;
147
148	if (ssi->flags & IMX_SSI_NET)
149		scr |= SSI_SCR_NET;
150	if (ssi->flags & IMX_SSI_SYN)
151		scr |= SSI_SCR_SYN;
152
153	writel(strcr, ssi->base + SSI_STCR);
154	writel(strcr, ssi->base + SSI_SRCR);
155	writel(scr, ssi->base + SSI_SCR);
156
157	return 0;
158}
159
160/*
161 * SSI system clock configuration.
162 * Should only be called when port is inactive (i.e. SSIEN = 0).
163 */
164static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
165				  int clk_id, unsigned int freq, int dir)
166{
167	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
168	u32 scr;
169
170	scr = readl(ssi->base + SSI_SCR);
171
172	switch (clk_id) {
173	case IMX_SSP_SYS_CLK:
174		if (dir == SND_SOC_CLOCK_OUT)
175			scr |= SSI_SCR_SYS_CLK_EN;
176		else
177			scr &= ~SSI_SCR_SYS_CLK_EN;
178		break;
179	default:
180		return -EINVAL;
181	}
182
183	writel(scr, ssi->base + SSI_SCR);
184
185	return 0;
186}
187
188/*
189 * SSI Clock dividers
190 * Should only be called when port is inactive (i.e. SSIEN = 0).
191 */
192static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
193				  int div_id, int div)
194{
195	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
196	u32 stccr, srccr;
197
198	stccr = readl(ssi->base + SSI_STCCR);
199	srccr = readl(ssi->base + SSI_SRCCR);
200
201	switch (div_id) {
202	case IMX_SSI_TX_DIV_2:
203		stccr &= ~SSI_STCCR_DIV2;
204		stccr |= div;
205		break;
206	case IMX_SSI_TX_DIV_PSR:
207		stccr &= ~SSI_STCCR_PSR;
208		stccr |= div;
209		break;
210	case IMX_SSI_TX_DIV_PM:
211		stccr &= ~0xff;
212		stccr |= SSI_STCCR_PM(div);
213		break;
214	case IMX_SSI_RX_DIV_2:
215		stccr &= ~SSI_STCCR_DIV2;
216		stccr |= div;
217		break;
218	case IMX_SSI_RX_DIV_PSR:
219		stccr &= ~SSI_STCCR_PSR;
220		stccr |= div;
221		break;
222	case IMX_SSI_RX_DIV_PM:
223		stccr &= ~0xff;
224		stccr |= SSI_STCCR_PM(div);
225		break;
226	default:
227		return -EINVAL;
228	}
229
230	writel(stccr, ssi->base + SSI_STCCR);
231	writel(srccr, ssi->base + SSI_SRCCR);
232
233	return 0;
234}
235
236/*
237 * Should only be called when port is inactive (i.e. SSIEN = 0),
238 * although can be called multiple times by upper layers.
239 */
240static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
241			     struct snd_pcm_hw_params *params,
242			     struct snd_soc_dai *cpu_dai)
243{
244	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai);
245	u32 reg, sccr;
246
247	/* Tx/Rx config */
248	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
249		reg = SSI_STCCR;
250	else
251		reg = SSI_SRCCR;
252
253	if (ssi->flags & IMX_SSI_SYN)
254		reg = SSI_STCCR;
255
256	sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
257
258	/* DAI data (word) size */
259	switch (params_format(params)) {
260	case SNDRV_PCM_FORMAT_S16_LE:
261		sccr |= SSI_SRCCR_WL(16);
262		break;
263	case SNDRV_PCM_FORMAT_S20_3LE:
264		sccr |= SSI_SRCCR_WL(20);
265		break;
266	case SNDRV_PCM_FORMAT_S24_LE:
267		sccr |= SSI_SRCCR_WL(24);
268		break;
269	}
270
271	writel(sccr, ssi->base + reg);
272
273	return 0;
274}
275
276static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
277		struct snd_soc_dai *dai)
278{
279	struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai);
280	unsigned int sier_bits, sier;
281	unsigned int scr;
282
283	scr = readl(ssi->base + SSI_SCR);
284	sier = readl(ssi->base + SSI_SIER);
285
286	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
287		if (ssi->flags & IMX_SSI_DMA)
288			sier_bits = SSI_SIER_TDMAE;
289		else
290			sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
291	} else {
292		if (ssi->flags & IMX_SSI_DMA)
293			sier_bits = SSI_SIER_RDMAE;
294		else
295			sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
296	}
297
298	switch (cmd) {
299	case SNDRV_PCM_TRIGGER_START:
300	case SNDRV_PCM_TRIGGER_RESUME:
301	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
302		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
303			scr |= SSI_SCR_TE;
304		else
305			scr |= SSI_SCR_RE;
306		sier |= sier_bits;
307
308		scr |= SSI_SCR_SSIEN;
309
310		break;
311
312	case SNDRV_PCM_TRIGGER_STOP:
313	case SNDRV_PCM_TRIGGER_SUSPEND:
314	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
315		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
316			scr &= ~SSI_SCR_TE;
317		else
318			scr &= ~SSI_SCR_RE;
319		sier &= ~sier_bits;
320
321		if (!(scr & (SSI_SCR_TE | SSI_SCR_RE)))
322			scr &= ~SSI_SCR_SSIEN;
323
324		break;
325	default:
326		return -EINVAL;
327	}
328
329	if (!(ssi->flags & IMX_SSI_USE_AC97))
330		/* rx/tx are always enabled to access ac97 registers */
331		writel(scr, ssi->base + SSI_SCR);
332
333	writel(sier, ssi->base + SSI_SIER);
334
335	return 0;
336}
337
338static const struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
339	.hw_params	= imx_ssi_hw_params,
340	.set_fmt	= imx_ssi_set_dai_fmt,
341	.set_clkdiv	= imx_ssi_set_dai_clkdiv,
342	.set_sysclk	= imx_ssi_set_dai_sysclk,
343	.set_tdm_slot	= imx_ssi_set_dai_tdm_slot,
344	.trigger	= imx_ssi_trigger,
345};
346
347static int imx_ssi_dai_probe(struct snd_soc_dai *dai)
348{
349	struct imx_ssi *ssi = dev_get_drvdata(dai->dev);
350	uint32_t val;
351
352	snd_soc_dai_set_drvdata(dai, ssi);
353
354	val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.maxburst) |
355		SSI_SFCSR_RFWM0(ssi->dma_params_rx.maxburst);
356	writel(val, ssi->base + SSI_SFCSR);
357
358	/* Tx/Rx config */
359	dai->playback_dma_data = &ssi->dma_params_tx;
360	dai->capture_dma_data = &ssi->dma_params_rx;
361
362	return 0;
363}
364
365static struct snd_soc_dai_driver imx_ssi_dai = {
366	.probe = imx_ssi_dai_probe,
367	.playback = {
368		.channels_min = 1,
369		.channels_max = 2,
370		.rates = SNDRV_PCM_RATE_8000_96000,
371		.formats = SNDRV_PCM_FMTBIT_S16_LE,
372	},
373	.capture = {
374		.channels_min = 1,
375		.channels_max = 2,
376		.rates = SNDRV_PCM_RATE_8000_96000,
377		.formats = SNDRV_PCM_FMTBIT_S16_LE,
378	},
379	.ops = &imx_ssi_pcm_dai_ops,
380};
381
382static struct snd_soc_dai_driver imx_ac97_dai = {
383	.probe = imx_ssi_dai_probe,
384	.bus_control = true,
385	.playback = {
386		.stream_name = "AC97 Playback",
387		.channels_min = 2,
388		.channels_max = 2,
389		.rates = SNDRV_PCM_RATE_8000_48000,
390		.formats = SNDRV_PCM_FMTBIT_S16_LE,
391	},
392	.capture = {
393		.stream_name = "AC97 Capture",
394		.channels_min = 2,
395		.channels_max = 2,
396		.rates = SNDRV_PCM_RATE_48000,
397		.formats = SNDRV_PCM_FMTBIT_S16_LE,
398	},
399	.ops = &imx_ssi_pcm_dai_ops,
400};
401
402static const struct snd_soc_component_driver imx_component = {
403	.name		= DRV_NAME,
404};
405
406static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
407{
408	void __iomem *base = imx_ssi->base;
409
410	writel(0x0, base + SSI_SCR);
411	writel(0x0, base + SSI_STCR);
412	writel(0x0, base + SSI_SRCR);
413
414	writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
415
416	writel(SSI_SFCSR_RFWM0(8) |
417		SSI_SFCSR_TFWM0(8) |
418		SSI_SFCSR_RFWM1(8) |
419		SSI_SFCSR_TFWM1(8), base + SSI_SFCSR);
420
421	writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
422	writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
423
424	writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
425	writel(SSI_SOR_WAIT(3), base + SSI_SOR);
426
427	writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
428			SSI_SCR_TE | SSI_SCR_RE,
429			base + SSI_SCR);
430
431	writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
432	writel(0xff, base + SSI_SACCDIS);
433	writel(0x300, base + SSI_SACCEN);
434}
435
436static struct imx_ssi *ac97_ssi;
437
438static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
439		unsigned short val)
440{
441	struct imx_ssi *imx_ssi = ac97_ssi;
442	void __iomem *base = imx_ssi->base;
443	unsigned int lreg;
444	unsigned int lval;
445
446	if (reg > 0x7f)
447		return;
448
449	pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
450
451	lreg = reg <<  12;
452	writel(lreg, base + SSI_SACADD);
453
454	lval = val << 4;
455	writel(lval , base + SSI_SACDAT);
456
457	writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
458	udelay(100);
459}
460
461static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
462		unsigned short reg)
463{
464	struct imx_ssi *imx_ssi = ac97_ssi;
465	void __iomem *base = imx_ssi->base;
466
467	unsigned short val = -1;
468	unsigned int lreg;
469
470	lreg = (reg & 0x7f) <<  12 ;
471	writel(lreg, base + SSI_SACADD);
472	writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
473
474	udelay(100);
475
476	val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
477
478	pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
479
480	return val;
481}
482
483static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
484{
485	struct imx_ssi *imx_ssi = ac97_ssi;
486
487	if (imx_ssi->ac97_reset)
488		imx_ssi->ac97_reset(ac97);
489	/* First read sometimes fails, do a dummy read */
490	imx_ssi_ac97_read(ac97, 0);
491}
492
493static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
494{
495	struct imx_ssi *imx_ssi = ac97_ssi;
496
497	if (imx_ssi->ac97_warm_reset)
498		imx_ssi->ac97_warm_reset(ac97);
499
500	/* First read sometimes fails, do a dummy read */
501	imx_ssi_ac97_read(ac97, 0);
502}
503
504static struct snd_ac97_bus_ops imx_ssi_ac97_ops = {
505	.read		= imx_ssi_ac97_read,
506	.write		= imx_ssi_ac97_write,
507	.reset		= imx_ssi_ac97_reset,
508	.warm_reset	= imx_ssi_ac97_warm_reset
509};
510
511static int imx_ssi_probe(struct platform_device *pdev)
512{
513	struct resource *res;
514	struct imx_ssi *ssi;
515	struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
516	int ret = 0;
517	struct snd_soc_dai_driver *dai;
518
519	ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL);
520	if (!ssi)
521		return -ENOMEM;
522	dev_set_drvdata(&pdev->dev, ssi);
523
524	if (pdata) {
525		ssi->ac97_reset = pdata->ac97_reset;
526		ssi->ac97_warm_reset = pdata->ac97_warm_reset;
527		ssi->flags = pdata->flags;
528	}
529
530	ssi->irq = platform_get_irq(pdev, 0);
531
532	ssi->clk = devm_clk_get(&pdev->dev, NULL);
533	if (IS_ERR(ssi->clk)) {
534		ret = PTR_ERR(ssi->clk);
535		dev_err(&pdev->dev, "Cannot get the clock: %d\n",
536			ret);
537		goto failed_clk;
538	}
539	ret = clk_prepare_enable(ssi->clk);
540	if (ret)
541		goto failed_clk;
542
543	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
544	ssi->base = devm_ioremap_resource(&pdev->dev, res);
545	if (IS_ERR(ssi->base)) {
546		ret = PTR_ERR(ssi->base);
547		goto failed_register;
548	}
549
550	if (ssi->flags & IMX_SSI_USE_AC97) {
551		if (ac97_ssi) {
552			dev_err(&pdev->dev, "AC'97 SSI already registered\n");
553			ret = -EBUSY;
554			goto failed_register;
555		}
556		ac97_ssi = ssi;
557		setup_channel_to_ac97(ssi);
558		dai = &imx_ac97_dai;
559	} else
560		dai = &imx_ssi_dai;
561
562	writel(0x0, ssi->base + SSI_SIER);
563
564	ssi->dma_params_rx.addr = res->start + SSI_SRX0;
565	ssi->dma_params_tx.addr = res->start + SSI_STX0;
566
567	ssi->dma_params_tx.maxburst = 6;
568	ssi->dma_params_rx.maxburst = 4;
569
570	ssi->dma_params_tx.filter_data = &ssi->filter_data_tx;
571	ssi->dma_params_rx.filter_data = &ssi->filter_data_rx;
572
573	res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
574	if (res) {
575		imx_pcm_dma_params_init_data(&ssi->filter_data_tx, res->start,
576			IMX_DMATYPE_SSI);
577	}
578
579	res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
580	if (res) {
581		imx_pcm_dma_params_init_data(&ssi->filter_data_rx, res->start,
582			IMX_DMATYPE_SSI);
583	}
584
585	platform_set_drvdata(pdev, ssi);
586
587	ret = snd_soc_set_ac97_ops(&imx_ssi_ac97_ops);
588	if (ret != 0) {
589		dev_err(&pdev->dev, "Failed to set AC'97 ops: %d\n", ret);
590		goto failed_register;
591	}
592
593	ret = snd_soc_register_component(&pdev->dev, &imx_component,
594					 dai, 1);
595	if (ret) {
596		dev_err(&pdev->dev, "register DAI failed\n");
597		goto failed_register;
598	}
599
600	ssi->fiq_params.irq = ssi->irq;
601	ssi->fiq_params.base = ssi->base;
602	ssi->fiq_params.dma_params_rx = &ssi->dma_params_rx;
603	ssi->fiq_params.dma_params_tx = &ssi->dma_params_tx;
604
605	ssi->fiq_init = imx_pcm_fiq_init(pdev, &ssi->fiq_params);
606	ssi->dma_init = imx_pcm_dma_init(pdev);
607
608	if (ssi->fiq_init && ssi->dma_init) {
609		ret = ssi->fiq_init;
610		goto failed_pcm;
611	}
612
613	return 0;
614
615failed_pcm:
616	snd_soc_unregister_component(&pdev->dev);
617failed_register:
618	clk_disable_unprepare(ssi->clk);
619failed_clk:
620	snd_soc_set_ac97_ops(NULL);
621
622	return ret;
623}
624
625static int imx_ssi_remove(struct platform_device *pdev)
626{
627	struct imx_ssi *ssi = platform_get_drvdata(pdev);
628
629	if (!ssi->fiq_init)
630		imx_pcm_fiq_exit(pdev);
631
632	snd_soc_unregister_component(&pdev->dev);
633
634	if (ssi->flags & IMX_SSI_USE_AC97)
635		ac97_ssi = NULL;
636
637	clk_disable_unprepare(ssi->clk);
638	snd_soc_set_ac97_ops(NULL);
639
640	return 0;
641}
642
643static struct platform_driver imx_ssi_driver = {
644	.probe = imx_ssi_probe,
645	.remove = imx_ssi_remove,
646
647	.driver = {
648		.name = "imx-ssi",
649	},
650};
651
652module_platform_driver(imx_ssi_driver);
653
654/* Module information */
655MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
656MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
657MODULE_LICENSE("GPL");
658MODULE_ALIAS("platform:imx-ssi");
659