Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.9 680 lines 17 kB view raw
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 54#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV) 55 56/* 57 * SSI Network Mode or TDM slots configuration. 58 * Should only be called when port is inactive (i.e. SSIEN = 0). 59 */ 60static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, 61 unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) 62{ 63 struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 64 u32 sccr; 65 66 sccr = readl(ssi->base + SSI_STCCR); 67 sccr &= ~SSI_STCCR_DC_MASK; 68 sccr |= SSI_STCCR_DC(slots - 1); 69 writel(sccr, ssi->base + SSI_STCCR); 70 71 sccr = readl(ssi->base + SSI_SRCCR); 72 sccr &= ~SSI_STCCR_DC_MASK; 73 sccr |= SSI_STCCR_DC(slots - 1); 74 writel(sccr, ssi->base + SSI_SRCCR); 75 76 writel(tx_mask, ssi->base + SSI_STMSK); 77 writel(rx_mask, ssi->base + SSI_SRMSK); 78 79 return 0; 80} 81 82/* 83 * SSI DAI format configuration. 84 * Should only be called when port is inactive (i.e. SSIEN = 0). 85 */ 86static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) 87{ 88 struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 89 u32 strcr = 0, scr; 90 91 scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET); 92 93 /* DAI mode */ 94 switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { 95 case SND_SOC_DAIFMT_I2S: 96 /* data on rising edge of bclk, frame low 1clk before data */ 97 strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0; 98 scr |= SSI_SCR_NET; 99 if (ssi->flags & IMX_SSI_USE_I2S_SLAVE) { 100 scr &= ~SSI_I2S_MODE_MASK; 101 scr |= SSI_SCR_I2S_MODE_SLAVE; 102 } 103 break; 104 case SND_SOC_DAIFMT_LEFT_J: 105 /* data on rising edge of bclk, frame high with data */ 106 strcr |= SSI_STCR_TXBIT0; 107 break; 108 case SND_SOC_DAIFMT_DSP_B: 109 /* data on rising edge of bclk, frame high with data */ 110 strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0; 111 break; 112 case SND_SOC_DAIFMT_DSP_A: 113 /* data on rising edge of bclk, frame high 1clk before data */ 114 strcr |= SSI_STCR_TFSL | SSI_STCR_TXBIT0 | SSI_STCR_TEFS; 115 break; 116 } 117 118 /* DAI clock inversion */ 119 switch (fmt & SND_SOC_DAIFMT_INV_MASK) { 120 case SND_SOC_DAIFMT_IB_IF: 121 strcr |= SSI_STCR_TFSI; 122 strcr &= ~SSI_STCR_TSCKP; 123 break; 124 case SND_SOC_DAIFMT_IB_NF: 125 strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI); 126 break; 127 case SND_SOC_DAIFMT_NB_IF: 128 strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP; 129 break; 130 case SND_SOC_DAIFMT_NB_NF: 131 strcr &= ~SSI_STCR_TFSI; 132 strcr |= SSI_STCR_TSCKP; 133 break; 134 } 135 136 /* DAI clock master masks */ 137 switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { 138 case SND_SOC_DAIFMT_CBM_CFM: 139 break; 140 default: 141 /* Master mode not implemented, needs handling of clocks. */ 142 return -EINVAL; 143 } 144 145 strcr |= SSI_STCR_TFEN0; 146 147 if (ssi->flags & IMX_SSI_NET) 148 scr |= SSI_SCR_NET; 149 if (ssi->flags & IMX_SSI_SYN) 150 scr |= SSI_SCR_SYN; 151 152 writel(strcr, ssi->base + SSI_STCR); 153 writel(strcr, ssi->base + SSI_SRCR); 154 writel(scr, ssi->base + SSI_SCR); 155 156 return 0; 157} 158 159/* 160 * SSI system clock configuration. 161 * Should only be called when port is inactive (i.e. SSIEN = 0). 162 */ 163static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, 164 int clk_id, unsigned int freq, int dir) 165{ 166 struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 167 u32 scr; 168 169 scr = readl(ssi->base + SSI_SCR); 170 171 switch (clk_id) { 172 case IMX_SSP_SYS_CLK: 173 if (dir == SND_SOC_CLOCK_OUT) 174 scr |= SSI_SCR_SYS_CLK_EN; 175 else 176 scr &= ~SSI_SCR_SYS_CLK_EN; 177 break; 178 default: 179 return -EINVAL; 180 } 181 182 writel(scr, ssi->base + SSI_SCR); 183 184 return 0; 185} 186 187/* 188 * SSI Clock dividers 189 * Should only be called when port is inactive (i.e. SSIEN = 0). 190 */ 191static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, 192 int div_id, int div) 193{ 194 struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 195 u32 stccr, srccr; 196 197 stccr = readl(ssi->base + SSI_STCCR); 198 srccr = readl(ssi->base + SSI_SRCCR); 199 200 switch (div_id) { 201 case IMX_SSI_TX_DIV_2: 202 stccr &= ~SSI_STCCR_DIV2; 203 stccr |= div; 204 break; 205 case IMX_SSI_TX_DIV_PSR: 206 stccr &= ~SSI_STCCR_PSR; 207 stccr |= div; 208 break; 209 case IMX_SSI_TX_DIV_PM: 210 stccr &= ~0xff; 211 stccr |= SSI_STCCR_PM(div); 212 break; 213 case IMX_SSI_RX_DIV_2: 214 stccr &= ~SSI_STCCR_DIV2; 215 stccr |= div; 216 break; 217 case IMX_SSI_RX_DIV_PSR: 218 stccr &= ~SSI_STCCR_PSR; 219 stccr |= div; 220 break; 221 case IMX_SSI_RX_DIV_PM: 222 stccr &= ~0xff; 223 stccr |= SSI_STCCR_PM(div); 224 break; 225 default: 226 return -EINVAL; 227 } 228 229 writel(stccr, ssi->base + SSI_STCCR); 230 writel(srccr, ssi->base + SSI_SRCCR); 231 232 return 0; 233} 234 235static int imx_ssi_startup(struct snd_pcm_substream *substream, 236 struct snd_soc_dai *cpu_dai) 237{ 238 struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 239 struct imx_pcm_dma_params *dma_data; 240 241 /* Tx/Rx config */ 242 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 243 dma_data = &ssi->dma_params_tx; 244 else 245 dma_data = &ssi->dma_params_rx; 246 247 snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); 248 249 return 0; 250} 251 252/* 253 * Should only be called when port is inactive (i.e. SSIEN = 0), 254 * although can be called multiple times by upper layers. 255 */ 256static int imx_ssi_hw_params(struct snd_pcm_substream *substream, 257 struct snd_pcm_hw_params *params, 258 struct snd_soc_dai *cpu_dai) 259{ 260 struct imx_ssi *ssi = snd_soc_dai_get_drvdata(cpu_dai); 261 u32 reg, sccr; 262 263 /* Tx/Rx config */ 264 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 265 reg = SSI_STCCR; 266 else 267 reg = SSI_SRCCR; 268 269 if (ssi->flags & IMX_SSI_SYN) 270 reg = SSI_STCCR; 271 272 sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK; 273 274 /* DAI data (word) size */ 275 switch (params_format(params)) { 276 case SNDRV_PCM_FORMAT_S16_LE: 277 sccr |= SSI_SRCCR_WL(16); 278 break; 279 case SNDRV_PCM_FORMAT_S20_3LE: 280 sccr |= SSI_SRCCR_WL(20); 281 break; 282 case SNDRV_PCM_FORMAT_S24_LE: 283 sccr |= SSI_SRCCR_WL(24); 284 break; 285 } 286 287 writel(sccr, ssi->base + reg); 288 289 return 0; 290} 291 292static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd, 293 struct snd_soc_dai *dai) 294{ 295 struct imx_ssi *ssi = snd_soc_dai_get_drvdata(dai); 296 unsigned int sier_bits, sier; 297 unsigned int scr; 298 299 scr = readl(ssi->base + SSI_SCR); 300 sier = readl(ssi->base + SSI_SIER); 301 302 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 303 if (ssi->flags & IMX_SSI_DMA) 304 sier_bits = SSI_SIER_TDMAE; 305 else 306 sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN; 307 } else { 308 if (ssi->flags & IMX_SSI_DMA) 309 sier_bits = SSI_SIER_RDMAE; 310 else 311 sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN; 312 } 313 314 switch (cmd) { 315 case SNDRV_PCM_TRIGGER_START: 316 case SNDRV_PCM_TRIGGER_RESUME: 317 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 318 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 319 scr |= SSI_SCR_TE; 320 else 321 scr |= SSI_SCR_RE; 322 sier |= sier_bits; 323 324 if (++ssi->enabled == 1) 325 scr |= SSI_SCR_SSIEN; 326 327 break; 328 329 case SNDRV_PCM_TRIGGER_STOP: 330 case SNDRV_PCM_TRIGGER_SUSPEND: 331 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 332 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 333 scr &= ~SSI_SCR_TE; 334 else 335 scr &= ~SSI_SCR_RE; 336 sier &= ~sier_bits; 337 338 if (--ssi->enabled == 0) 339 scr &= ~SSI_SCR_SSIEN; 340 341 break; 342 default: 343 return -EINVAL; 344 } 345 346 if (!(ssi->flags & IMX_SSI_USE_AC97)) 347 /* rx/tx are always enabled to access ac97 registers */ 348 writel(scr, ssi->base + SSI_SCR); 349 350 writel(sier, ssi->base + SSI_SIER); 351 352 return 0; 353} 354 355static const struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = { 356 .startup = imx_ssi_startup, 357 .hw_params = imx_ssi_hw_params, 358 .set_fmt = imx_ssi_set_dai_fmt, 359 .set_clkdiv = imx_ssi_set_dai_clkdiv, 360 .set_sysclk = imx_ssi_set_dai_sysclk, 361 .set_tdm_slot = imx_ssi_set_dai_tdm_slot, 362 .trigger = imx_ssi_trigger, 363}; 364 365static int imx_ssi_dai_probe(struct snd_soc_dai *dai) 366{ 367 struct imx_ssi *ssi = dev_get_drvdata(dai->dev); 368 uint32_t val; 369 370 snd_soc_dai_set_drvdata(dai, ssi); 371 372 val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) | 373 SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize); 374 writel(val, ssi->base + SSI_SFCSR); 375 376 return 0; 377} 378 379static struct snd_soc_dai_driver imx_ssi_dai = { 380 .probe = imx_ssi_dai_probe, 381 .playback = { 382 .channels_min = 1, 383 .channels_max = 2, 384 .rates = SNDRV_PCM_RATE_8000_96000, 385 .formats = SNDRV_PCM_FMTBIT_S16_LE, 386 }, 387 .capture = { 388 .channels_min = 1, 389 .channels_max = 2, 390 .rates = SNDRV_PCM_RATE_8000_96000, 391 .formats = SNDRV_PCM_FMTBIT_S16_LE, 392 }, 393 .ops = &imx_ssi_pcm_dai_ops, 394}; 395 396static struct snd_soc_dai_driver imx_ac97_dai = { 397 .probe = imx_ssi_dai_probe, 398 .ac97_control = 1, 399 .playback = { 400 .stream_name = "AC97 Playback", 401 .channels_min = 2, 402 .channels_max = 2, 403 .rates = SNDRV_PCM_RATE_48000, 404 .formats = SNDRV_PCM_FMTBIT_S16_LE, 405 }, 406 .capture = { 407 .stream_name = "AC97 Capture", 408 .channels_min = 2, 409 .channels_max = 2, 410 .rates = SNDRV_PCM_RATE_48000, 411 .formats = SNDRV_PCM_FMTBIT_S16_LE, 412 }, 413 .ops = &imx_ssi_pcm_dai_ops, 414}; 415 416static void setup_channel_to_ac97(struct imx_ssi *imx_ssi) 417{ 418 void __iomem *base = imx_ssi->base; 419 420 writel(0x0, base + SSI_SCR); 421 writel(0x0, base + SSI_STCR); 422 writel(0x0, base + SSI_SRCR); 423 424 writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR); 425 426 writel(SSI_SFCSR_RFWM0(8) | 427 SSI_SFCSR_TFWM0(8) | 428 SSI_SFCSR_RFWM1(8) | 429 SSI_SFCSR_TFWM1(8), base + SSI_SFCSR); 430 431 writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR); 432 writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR); 433 434 writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR); 435 writel(SSI_SOR_WAIT(3), base + SSI_SOR); 436 437 writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN | 438 SSI_SCR_TE | SSI_SCR_RE, 439 base + SSI_SCR); 440 441 writel(SSI_SACNT_DEFAULT, base + SSI_SACNT); 442 writel(0xff, base + SSI_SACCDIS); 443 writel(0x300, base + SSI_SACCEN); 444} 445 446static struct imx_ssi *ac97_ssi; 447 448static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg, 449 unsigned short val) 450{ 451 struct imx_ssi *imx_ssi = ac97_ssi; 452 void __iomem *base = imx_ssi->base; 453 unsigned int lreg; 454 unsigned int lval; 455 456 if (reg > 0x7f) 457 return; 458 459 pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); 460 461 lreg = reg << 12; 462 writel(lreg, base + SSI_SACADD); 463 464 lval = val << 4; 465 writel(lval , base + SSI_SACDAT); 466 467 writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT); 468 udelay(100); 469} 470 471static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97, 472 unsigned short reg) 473{ 474 struct imx_ssi *imx_ssi = ac97_ssi; 475 void __iomem *base = imx_ssi->base; 476 477 unsigned short val = -1; 478 unsigned int lreg; 479 480 lreg = (reg & 0x7f) << 12 ; 481 writel(lreg, base + SSI_SACADD); 482 writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT); 483 484 udelay(100); 485 486 val = (readl(base + SSI_SACDAT) >> 4) & 0xffff; 487 488 pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val); 489 490 return val; 491} 492 493static void imx_ssi_ac97_reset(struct snd_ac97 *ac97) 494{ 495 struct imx_ssi *imx_ssi = ac97_ssi; 496 497 if (imx_ssi->ac97_reset) 498 imx_ssi->ac97_reset(ac97); 499 /* First read sometimes fails, do a dummy read */ 500 imx_ssi_ac97_read(ac97, 0); 501} 502 503static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97) 504{ 505 struct imx_ssi *imx_ssi = ac97_ssi; 506 507 if (imx_ssi->ac97_warm_reset) 508 imx_ssi->ac97_warm_reset(ac97); 509 510 /* First read sometimes fails, do a dummy read */ 511 imx_ssi_ac97_read(ac97, 0); 512} 513 514struct snd_ac97_bus_ops soc_ac97_ops = { 515 .read = imx_ssi_ac97_read, 516 .write = imx_ssi_ac97_write, 517 .reset = imx_ssi_ac97_reset, 518 .warm_reset = imx_ssi_ac97_warm_reset 519}; 520EXPORT_SYMBOL_GPL(soc_ac97_ops); 521 522static int imx_ssi_probe(struct platform_device *pdev) 523{ 524 struct resource *res; 525 struct imx_ssi *ssi; 526 struct imx_ssi_platform_data *pdata = pdev->dev.platform_data; 527 int ret = 0; 528 struct snd_soc_dai_driver *dai; 529 530 ssi = devm_kzalloc(&pdev->dev, sizeof(*ssi), GFP_KERNEL); 531 if (!ssi) 532 return -ENOMEM; 533 dev_set_drvdata(&pdev->dev, ssi); 534 535 if (pdata) { 536 ssi->ac97_reset = pdata->ac97_reset; 537 ssi->ac97_warm_reset = pdata->ac97_warm_reset; 538 ssi->flags = pdata->flags; 539 } 540 541 ssi->irq = platform_get_irq(pdev, 0); 542 543 ssi->clk = devm_clk_get(&pdev->dev, NULL); 544 if (IS_ERR(ssi->clk)) { 545 ret = PTR_ERR(ssi->clk); 546 dev_err(&pdev->dev, "Cannot get the clock: %d\n", 547 ret); 548 goto failed_clk; 549 } 550 clk_prepare_enable(ssi->clk); 551 552 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 553 if (!res) { 554 ret = -ENODEV; 555 goto failed_get_resource; 556 } 557 558 ssi->base = devm_ioremap_resource(&pdev->dev, res); 559 if (IS_ERR(ssi->base)) { 560 ret = PTR_ERR(ssi->base); 561 goto failed_register; 562 } 563 564 if (ssi->flags & IMX_SSI_USE_AC97) { 565 if (ac97_ssi) { 566 dev_err(&pdev->dev, "AC'97 SSI already registered\n"); 567 ret = -EBUSY; 568 goto failed_register; 569 } 570 ac97_ssi = ssi; 571 setup_channel_to_ac97(ssi); 572 dai = &imx_ac97_dai; 573 } else 574 dai = &imx_ssi_dai; 575 576 writel(0x0, ssi->base + SSI_SIER); 577 578 ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0; 579 ssi->dma_params_tx.dma_addr = res->start + SSI_STX0; 580 581 ssi->dma_params_tx.burstsize = 6; 582 ssi->dma_params_rx.burstsize = 4; 583 584 res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0"); 585 if (res) 586 ssi->dma_params_tx.dma = res->start; 587 588 res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0"); 589 if (res) 590 ssi->dma_params_rx.dma = res->start; 591 592 platform_set_drvdata(pdev, ssi); 593 594 ret = snd_soc_register_dai(&pdev->dev, dai); 595 if (ret) { 596 dev_err(&pdev->dev, "register DAI failed\n"); 597 goto failed_register; 598 } 599 600 ssi->soc_platform_pdev_fiq = platform_device_alloc("imx-fiq-pcm-audio", pdev->id); 601 if (!ssi->soc_platform_pdev_fiq) { 602 ret = -ENOMEM; 603 goto failed_pdev_fiq_alloc; 604 } 605 606 platform_set_drvdata(ssi->soc_platform_pdev_fiq, ssi); 607 ret = platform_device_add(ssi->soc_platform_pdev_fiq); 608 if (ret) { 609 dev_err(&pdev->dev, "failed to add platform device\n"); 610 goto failed_pdev_fiq_add; 611 } 612 613 ssi->soc_platform_pdev = platform_device_alloc("imx-pcm-audio", pdev->id); 614 if (!ssi->soc_platform_pdev) { 615 ret = -ENOMEM; 616 goto failed_pdev_alloc; 617 } 618 619 platform_set_drvdata(ssi->soc_platform_pdev, ssi); 620 ret = platform_device_add(ssi->soc_platform_pdev); 621 if (ret) { 622 dev_err(&pdev->dev, "failed to add platform device\n"); 623 goto failed_pdev_add; 624 } 625 626 return 0; 627 628failed_pdev_add: 629 platform_device_put(ssi->soc_platform_pdev); 630failed_pdev_alloc: 631 platform_device_del(ssi->soc_platform_pdev_fiq); 632failed_pdev_fiq_add: 633 platform_device_put(ssi->soc_platform_pdev_fiq); 634failed_pdev_fiq_alloc: 635 snd_soc_unregister_dai(&pdev->dev); 636failed_register: 637 release_mem_region(res->start, resource_size(res)); 638failed_get_resource: 639 clk_disable_unprepare(ssi->clk); 640failed_clk: 641 642 return ret; 643} 644 645static int imx_ssi_remove(struct platform_device *pdev) 646{ 647 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 648 struct imx_ssi *ssi = platform_get_drvdata(pdev); 649 650 platform_device_unregister(ssi->soc_platform_pdev); 651 platform_device_unregister(ssi->soc_platform_pdev_fiq); 652 653 snd_soc_unregister_dai(&pdev->dev); 654 655 if (ssi->flags & IMX_SSI_USE_AC97) 656 ac97_ssi = NULL; 657 658 release_mem_region(res->start, resource_size(res)); 659 clk_disable_unprepare(ssi->clk); 660 661 return 0; 662} 663 664static struct platform_driver imx_ssi_driver = { 665 .probe = imx_ssi_probe, 666 .remove = imx_ssi_remove, 667 668 .driver = { 669 .name = "imx-ssi", 670 .owner = THIS_MODULE, 671 }, 672}; 673 674module_platform_driver(imx_ssi_driver); 675 676/* Module information */ 677MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>"); 678MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface"); 679MODULE_LICENSE("GPL"); 680MODULE_ALIAS("platform:imx-ssi");