diff options
Diffstat (limited to 'sound/soc/fsl/fsl_ssi.c')
-rw-r--r-- | sound/soc/fsl/fsl_ssi.c | 435 |
1 files changed, 327 insertions, 108 deletions
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 35e277379b86..b2ebaf811599 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -38,6 +38,7 @@ #include <linux/device.h> #include <linux/delay.h> #include <linux/slab.h> +#include <linux/spinlock.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <linux/of_platform.h> @@ -119,8 +120,6 @@ static inline void write_ssi_mask(u32 __iomem *addr, u32 clear, u32 set) * @ssi: pointer to the SSI's registers * @ssi_phys: physical address of the SSI registers * @irq: IRQ of this SSI - * @first_stream: pointer to the stream that was opened first - * @second_stream: pointer to second stream * @playback: the number of playback streams opened * @capture: the number of capture streams opened * @cpu_dai: the CPU DAI for this device @@ -132,8 +131,6 @@ struct fsl_ssi_private { struct ccsr_ssi __iomem *ssi; dma_addr_t ssi_phys; unsigned int irq; - struct snd_pcm_substream *first_stream; - struct snd_pcm_substream *second_stream; unsigned int fifo_depth; struct snd_soc_dai_driver cpu_dai_drv; struct device_attribute dev_attr; @@ -143,6 +140,10 @@ struct fsl_ssi_private { bool ssi_on_imx; bool imx_ac97; bool use_dma; + bool baudclk_locked; + u8 i2s_mode; + spinlock_t baudclk_lock; + struct clk *baudclk; struct clk *clk; struct snd_dmaengine_dai_dma_data dma_params_tx; struct snd_dmaengine_dai_dma_data dma_params_rx; @@ -321,17 +322,46 @@ static irqreturn_t fsl_ssi_isr(int irq, void *dev_id) return ret; } +static void fsl_ssi_setup_ac97(struct fsl_ssi_private *ssi_private) +{ + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + + /* + * Setup the clock control register + */ + write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13), + &ssi->stccr); + write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13), + &ssi->srccr); + + /* + * Enable AC97 mode and startup the SSI + */ + write_ssi(CCSR_SSI_SACNT_AC97EN | CCSR_SSI_SACNT_FV, + &ssi->sacnt); + write_ssi(0xff, &ssi->saccdis); + write_ssi(0x300, &ssi->saccen); + + /* + * Enable SSI, Transmit and Receive. AC97 has to communicate with the + * codec before a stream is started. + */ + write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN | + CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE); + + write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor); +} + static int fsl_ssi_setup(struct fsl_ssi_private *ssi_private) { struct ccsr_ssi __iomem *ssi = ssi_private->ssi; - u8 i2s_mode; u8 wm; int synchronous = ssi_private->cpu_dai_drv.symmetric_rates; if (ssi_private->imx_ac97) - i2s_mode = CCSR_SSI_SCR_I2S_MODE_NORMAL | CCSR_SSI_SCR_NET; + ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_NORMAL | CCSR_SSI_SCR_NET; else - i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE; + ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE; /* * Section 16.5 of the MPC8610 reference manual says that the SSI needs @@ -348,7 +378,7 @@ static int fsl_ssi_setup(struct fsl_ssi_private *ssi_private) write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_SYN, CCSR_SSI_SCR_TFR_CLK_DIS | - i2s_mode | + ssi_private->i2s_mode | (synchronous ? CCSR_SSI_SCR_SYN : 0)); write_ssi(CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFEN0 | @@ -387,31 +417,8 @@ static int fsl_ssi_setup(struct fsl_ssi_private *ssi_private) * because it is also running without an active substream. Normally SSI * is only enabled when there is a substream. */ - if (ssi_private->imx_ac97) { - /* - * Setup the clock control register - */ - write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13), - &ssi->stccr); - write_ssi(CCSR_SSI_SxCCR_WL(17) | CCSR_SSI_SxCCR_DC(13), - &ssi->srccr); - - /* - * Enable AC97 mode and startup the SSI - */ - write_ssi(CCSR_SSI_SACNT_AC97EN | CCSR_SSI_SACNT_FV, - &ssi->sacnt); - write_ssi(0xff, &ssi->saccdis); - write_ssi(0x300, &ssi->saccen); - - /* - * Enable SSI, Transmit and Receive - */ - write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN | - CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE); - - write_ssi(CCSR_SSI_SOR_WAIT(3), &ssi->sor); - } + if (ssi_private->imx_ac97) + fsl_ssi_setup_ac97(ssi_private); return 0; } @@ -431,53 +438,17 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai); - int synchronous = ssi_private->cpu_dai_drv.symmetric_rates; + unsigned long flags; - /* - * If this is the first stream opened, then request the IRQ - * and initialize the SSI registers. + /* First, we only do fsl_ssi_setup() when SSI is going to be active. + * Second, fsl_ssi_setup was already called by ac97_init earlier if + * the driver is in ac97 mode. */ - if (!ssi_private->first_stream) { - ssi_private->first_stream = substream; - - /* - * fsl_ssi_setup was already called by ac97_init earlier if - * the driver is in ac97 mode. - */ - if (!ssi_private->imx_ac97) - fsl_ssi_setup(ssi_private); - } else { - if (synchronous) { - struct snd_pcm_runtime *first_runtime = - ssi_private->first_stream->runtime; - /* - * This is the second stream open, and we're in - * synchronous mode, so we need to impose sample - * sample size constraints. This is because STCCR is - * used for playback and capture in synchronous mode, - * so there's no way to specify different word - * lengths. - * - * Note that this can cause a race condition if the - * second stream is opened before the first stream is - * fully initialized. We provide some protection by - * checking to make sure the first stream is - * initialized, but it's not perfect. ALSA sometimes - * re-initializes the driver with a different sample - * rate or size. If the second stream is opened - * before the first stream has received its final - * parameters, then the second stream may be - * constrained to the wrong sample rate or size. - */ - if (first_runtime->sample_bits) { - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_SAMPLE_BITS, - first_runtime->sample_bits, - first_runtime->sample_bits); - } - } - - ssi_private->second_stream = substream; + if (!dai->active && !ssi_private->imx_ac97) { + fsl_ssi_setup(ssi_private); + spin_lock_irqsave(&ssi_private->baudclk_lock, flags); + ssi_private->baudclk_locked = false; + spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags); } return 0; @@ -501,6 +472,7 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, { struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + unsigned int channels = params_channels(hw_params); unsigned int sample_size = snd_pcm_format_width(params_format(hw_params)); u32 wl = CCSR_SSI_SxCCR_WL(sample_size); @@ -530,6 +502,248 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, else write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); + if (!ssi_private->imx_ac97) + write_ssi_mask(&ssi->scr, + CCSR_SSI_SCR_NET | CCSR_SSI_SCR_I2S_MODE_MASK, + channels == 1 ? 0 : ssi_private->i2s_mode); + + return 0; +} + +/** + * fsl_ssi_set_dai_fmt - configure Digital Audio Interface Format. + */ +static int fsl_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + u32 strcr = 0, stcr, srcr, scr, mask; + + scr = read_ssi(&ssi->scr) & ~(CCSR_SSI_SCR_SYN | CCSR_SSI_SCR_I2S_MODE_MASK); + scr |= CCSR_SSI_SCR_NET; + + mask = CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR | + CCSR_SSI_STCR_TSCKP | CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TFSL | + CCSR_SSI_STCR_TEFS; + stcr = read_ssi(&ssi->stcr) & ~mask; + srcr = read_ssi(&ssi->srcr) & ~mask; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + ssi_private->i2s_mode = CCSR_SSI_SCR_I2S_MODE_SLAVE; + break; + default: + return -EINVAL; + } + scr |= ssi_private->i2s_mode; + + /* Data on rising edge of bclk, frame low, 1clk before data */ + strcr |= CCSR_SSI_STCR_TFSI | CCSR_SSI_STCR_TSCKP | + CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* Data on rising edge of bclk, frame high */ + strcr |= CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_DSP_A: + /* Data on rising edge of bclk, frame high, 1clk before data */ + strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP | + CCSR_SSI_STCR_TXBIT0 | CCSR_SSI_STCR_TEFS; + break; + case SND_SOC_DAIFMT_DSP_B: + /* Data on rising edge of bclk, frame high */ + strcr |= CCSR_SSI_STCR_TFSL | CCSR_SSI_STCR_TSCKP | + CCSR_SSI_STCR_TXBIT0; + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* Nothing to do for both normal cases */ + break; + case SND_SOC_DAIFMT_IB_NF: + /* Invert bit clock */ + strcr ^= CCSR_SSI_STCR_TSCKP; + break; + case SND_SOC_DAIFMT_NB_IF: + /* Invert frame clock */ + strcr ^= CCSR_SSI_STCR_TFSI; + break; + case SND_SOC_DAIFMT_IB_IF: + /* Invert both clocks */ + strcr ^= CCSR_SSI_STCR_TSCKP; + strcr ^= CCSR_SSI_STCR_TFSI; + break; + default: + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + strcr |= CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR; + scr |= CCSR_SSI_SCR_SYS_CLK_EN; + break; + case SND_SOC_DAIFMT_CBM_CFM: + scr &= ~CCSR_SSI_SCR_SYS_CLK_EN; + break; + default: + return -EINVAL; + } + + stcr |= strcr; + srcr |= strcr; + + if (ssi_private->cpu_dai_drv.symmetric_rates) { + /* Need to clear RXDIR when using SYNC mode */ + srcr &= ~CCSR_SSI_SRCR_RXDIR; + scr |= CCSR_SSI_SCR_SYN; + } + + write_ssi(stcr, &ssi->stcr); + write_ssi(srcr, &ssi->srcr); + write_ssi(scr, &ssi->scr); + + return 0; +} + +/** + * fsl_ssi_set_dai_sysclk - configure Digital Audio Interface bit clock + * + * Note: This function can be only called when using SSI as DAI master + * + * Quick instruction for parameters: + * freq: Output BCLK frequency = samplerate * 32 (fixed) * channels + * dir: SND_SOC_CLOCK_OUT -> TxBCLK, SND_SOC_CLOCK_IN -> RxBCLK. + */ +static int fsl_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + int synchronous = ssi_private->cpu_dai_drv.symmetric_rates, ret; + u32 pm = 999, div2, psr, stccr, mask, afreq, factor, i; + unsigned long flags, clkrate, baudrate, tmprate; + u64 sub, savesub = 100000; + + /* Don't apply it to any non-baudclk circumstance */ + if (IS_ERR(ssi_private->baudclk)) + return -EINVAL; + + /* It should be already enough to divide clock by setting pm alone */ + psr = 0; + div2 = 0; + + factor = (div2 + 1) * (7 * psr + 1) * 2; + + for (i = 0; i < 255; i++) { + /* The bclk rate must be smaller than 1/5 sysclk rate */ + if (factor * (i + 1) < 5) + continue; + + tmprate = freq * factor * (i + 2); + clkrate = clk_round_rate(ssi_private->baudclk, tmprate); + + do_div(clkrate, factor); + afreq = (u32)clkrate / (i + 1); + + if (freq == afreq) + sub = 0; + else if (freq / afreq == 1) + sub = freq - afreq; + else if (afreq / freq == 1) + sub = afreq - freq; + else + continue; + + /* Calculate the fraction */ + sub *= 100000; + do_div(sub, freq); + + if (sub < savesub) { + baudrate = tmprate; + savesub = sub; + pm = i; + } + + /* We are lucky */ + if (savesub == 0) + break; + } + + /* No proper pm found if it is still remaining the initial value */ + if (pm == 999) { + dev_err(cpu_dai->dev, "failed to handle the required sysclk\n"); + return -EINVAL; + } + + stccr = CCSR_SSI_SxCCR_PM(pm + 1) | (div2 ? CCSR_SSI_SxCCR_DIV2 : 0) | + (psr ? CCSR_SSI_SxCCR_PSR : 0); + mask = CCSR_SSI_SxCCR_PM_MASK | CCSR_SSI_SxCCR_DIV2 | CCSR_SSI_SxCCR_PSR; + + if (dir == SND_SOC_CLOCK_OUT || synchronous) + write_ssi_mask(&ssi->stccr, mask, stccr); + else + write_ssi_mask(&ssi->srccr, mask, stccr); + + spin_lock_irqsave(&ssi_private->baudclk_lock, flags); + if (!ssi_private->baudclk_locked) { + ret = clk_set_rate(ssi_private->baudclk, baudrate); + if (ret) { + spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags); + dev_err(cpu_dai->dev, "failed to set baudclk rate\n"); + return -EINVAL; + } + ssi_private->baudclk_locked = true; + } + spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags); + + return 0; +} + +/** + * fsl_ssi_set_dai_tdm_slot - set TDM slot number + * + * Note: This function can be only called when using SSI as DAI master + */ +static int fsl_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, u32 tx_mask, + u32 rx_mask, int slots, int slot_width) +{ + struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(cpu_dai); + struct ccsr_ssi __iomem *ssi = ssi_private->ssi; + u32 val; + + /* The slot number should be >= 2 if using Network mode or I2S mode */ + val = read_ssi(&ssi->scr) & (CCSR_SSI_SCR_I2S_MODE_MASK | CCSR_SSI_SCR_NET); + if (val && slots < 2) { + dev_err(cpu_dai->dev, "slot number should be >= 2 in I2S or NET\n"); + return -EINVAL; + } + + write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_DC_MASK, + CCSR_SSI_SxCCR_DC(slots)); + write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_DC_MASK, + CCSR_SSI_SxCCR_DC(slots)); + + /* The register SxMSKs needs SSI to provide essential clock due to + * hardware design. So we here temporarily enable SSI to set them. + */ + val = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN; + write_ssi_mask(&ssi->scr, 0, CCSR_SSI_SCR_SSIEN); + + write_ssi(tx_mask, &ssi->stmsk); + write_ssi(rx_mask, &ssi->srmsk); + + write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, val); + return 0; } @@ -549,6 +763,7 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai); struct ccsr_ssi __iomem *ssi = ssi_private->ssi; unsigned int sier_bits; + unsigned long flags; /* * Enable only the interrupts and DMA requests @@ -589,8 +804,12 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_RE, 0); if (!ssi_private->imx_ac97 && (read_ssi(&ssi->scr) & - (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0) + (CCSR_SSI_SCR_TE | CCSR_SSI_SCR_RE)) == 0) { write_ssi_mask(&ssi->scr, CCSR_SSI_SCR_SSIEN, 0); + spin_lock_irqsave(&ssi_private->baudclk_lock, flags); + ssi_private->baudclk_locked = false; + spin_unlock_irqrestore(&ssi_private->baudclk_lock, flags); + } break; default: @@ -602,23 +821,6 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } -/** - * fsl_ssi_shutdown: shutdown the SSI - * - * Shutdown the SSI if there are no other substreams open. - */ -static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(rtd->cpu_dai); - - if (ssi_private->first_stream == substream) - ssi_private->first_stream = ssi_private->second_stream; - - ssi_private->second_stream = NULL; -} - static int fsl_ssi_dai_probe(struct snd_soc_dai *dai) { struct fsl_ssi_private *ssi_private = snd_soc_dai_get_drvdata(dai); @@ -634,7 +836,9 @@ static int fsl_ssi_dai_probe(struct snd_soc_dai *dai) static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { .startup = fsl_ssi_startup, .hw_params = fsl_ssi_hw_params, - .shutdown = fsl_ssi_shutdown, + .set_fmt = fsl_ssi_set_dai_fmt, + .set_sysclk = fsl_ssi_set_dai_sysclk, + .set_tdm_slot = fsl_ssi_set_dai_tdm_slot, .trigger = fsl_ssi_trigger, }; @@ -642,14 +846,13 @@ static const struct snd_soc_dai_ops fsl_ssi_dai_ops = { static struct snd_soc_dai_driver fsl_ssi_dai_template = { .probe = fsl_ssi_dai_probe, .playback = { - /* The SSI does not support monaural audio. */ - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .rates = FSLSSI_I2S_RATES, .formats = FSLSSI_I2S_FORMATS, }, .capture = { - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .rates = FSLSSI_I2S_RATES, .formats = FSLSSI_I2S_FORMATS, @@ -710,7 +913,6 @@ static int fsl_ssi_ac97_trigger(struct snd_pcm_substream *substream, int cmd, static const struct snd_soc_dai_ops fsl_ssi_ac97_dai_ops = { .startup = fsl_ssi_startup, - .shutdown = fsl_ssi_shutdown, .trigger = fsl_ssi_ac97_trigger, }; @@ -935,8 +1137,11 @@ static int fsl_ssi_probe(struct platform_device *pdev) } /* Are the RX and the TX clocks locked? */ - if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) + if (!of_find_property(np, "fsl,ssi-asynchronous", NULL)) { ssi_private->cpu_dai_drv.symmetric_rates = 1; + ssi_private->cpu_dai_drv.symmetric_channels = 1; + ssi_private->cpu_dai_drv.symmetric_samplebits = 1; + } /* Determine the FIFO depth. */ iprop = of_get_property(np, "fsl,fifo-depth", NULL); @@ -946,6 +1151,9 @@ static int fsl_ssi_probe(struct platform_device *pdev) /* Older 8610 DTs didn't have the fifo-depth property */ ssi_private->fifo_depth = 8; + ssi_private->baudclk_locked = false; + spin_lock_init(&ssi_private->baudclk_lock); + if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx21-ssi")) { u32 dma_events[2]; ssi_private->ssi_on_imx = true; @@ -963,6 +1171,15 @@ static int fsl_ssi_probe(struct platform_device *pdev) goto error_irqmap; } + /* For those SLAVE implementations, we ingore non-baudclk cases + * and, instead, abandon MASTER mode that needs baud clock. + */ + ssi_private->baudclk = devm_clk_get(&pdev->dev, "baud"); + if (IS_ERR(ssi_private->baudclk)) + dev_warn(&pdev->dev, "could not get baud clock: %d\n", ret); + else + clk_prepare_enable(ssi_private->baudclk); + /* * We have burstsize be "fifo_depth - 2" to match the SSI * watermark setting in fsl_ssi_startup(). @@ -1102,16 +1319,17 @@ done: return 0; error_dai: - if (ssi_private->ssi_on_imx) - imx_pcm_dma_exit(pdev); snd_soc_unregister_component(&pdev->dev); error_dev: device_remove_file(&pdev->dev, dev_attr); error_clk: - if (ssi_private->ssi_on_imx) + if (ssi_private->ssi_on_imx) { + if (!IS_ERR(ssi_private->baudclk)) + clk_disable_unprepare(ssi_private->baudclk); clk_disable_unprepare(ssi_private->clk); + } error_irqmap: irq_dispose_mapping(ssi_private->irq); @@ -1125,12 +1343,13 @@ static int fsl_ssi_remove(struct platform_device *pdev) if (!ssi_private->new_binding) platform_device_unregister(ssi_private->pdev); - if (ssi_private->ssi_on_imx) - imx_pcm_dma_exit(pdev); snd_soc_unregister_component(&pdev->dev); device_remove_file(&pdev->dev, &ssi_private->dev_attr); - if (ssi_private->ssi_on_imx) + if (ssi_private->ssi_on_imx) { + if (!IS_ERR(ssi_private->baudclk)) + clk_disable_unprepare(ssi_private->baudclk); clk_disable_unprepare(ssi_private->clk); + } irq_dispose_mapping(ssi_private->irq); return 0; |