From 1f93a7dfd1f8d5ff7a5c53246c7534fe2332d6f4 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Mon, 11 Dec 2023 02:46:07 +0000 Subject: [PATCH] add audio --- kernel/sound/soc/fsl/fsl_esai.c | 500 ++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 343 insertions(+), 157 deletions(-) diff --git a/kernel/sound/soc/fsl/fsl_esai.c b/kernel/sound/soc/fsl/fsl_esai.c index baa7633..9f5f217 100644 --- a/kernel/sound/soc/fsl/fsl_esai.c +++ b/kernel/sound/soc/fsl/fsl_esai.c @@ -9,6 +9,7 @@ #include <linux/module.h> #include <linux/of_irq.h> #include <linux/of_platform.h> +#include <linux/pm_runtime.h> #include <sound/dmaengine_pcm.h> #include <sound/pcm_params.h> @@ -21,8 +22,17 @@ SNDRV_PCM_FMTBIT_S24_LE) /** - * fsl_esai: ESAI private data - * + * struct fsl_esai_soc_data - soc specific data + * @imx: for imx platform + * @reset_at_xrun: flags for enable reset operaton + */ +struct fsl_esai_soc_data { + bool imx; + bool reset_at_xrun; +}; + +/** + * struct fsl_esai - ESAI private data * @dma_params_rx: DMA parameters for receive channel * @dma_params_tx: DMA parameters for transmit channel * @pdev: platform device pointer @@ -31,9 +41,15 @@ * @extalclk: esai clock source to derive HCK, SCK and FS * @fsysclk: system clock source to derive HCK, SCK and FS * @spbaclk: SPBA clock (optional, depending on SoC design) + * @work: work to handle the reset operation + * @soc: soc specific data + * @lock: spin lock between hw_reset() and trigger() * @fifo_depth: depth of tx/rx FIFO * @slot_width: width of each DAI slot * @slots: number of slots + * @tx_mask: slot mask for TX + * @rx_mask: slot mask for RX + * @channels: channel num for tx or rx * @hck_rate: clock rate of desired HCKx clock * @sck_rate: clock rate of desired SCKx clock * @hck_dir: the direction of HCKx pads @@ -51,11 +67,15 @@ struct clk *extalclk; struct clk *fsysclk; struct clk *spbaclk; + struct work_struct work; + const struct fsl_esai_soc_data *soc; + spinlock_t lock; /* Protect hw_reset and trigger */ u32 fifo_depth; u32 slot_width; u32 slots; u32 tx_mask; u32 rx_mask; + u32 channels[2]; u32 hck_rate[2]; u32 sck_rate[2]; bool hck_dir[2]; @@ -65,13 +85,40 @@ char name[32]; }; +static struct fsl_esai_soc_data fsl_esai_vf610 = { + .imx = false, + .reset_at_xrun = true, +}; + +static struct fsl_esai_soc_data fsl_esai_imx35 = { + .imx = true, + .reset_at_xrun = true, +}; + +static struct fsl_esai_soc_data fsl_esai_imx6ull = { + .imx = true, + .reset_at_xrun = false, +}; + static irqreturn_t esai_isr(int irq, void *devid) { struct fsl_esai *esai_priv = (struct fsl_esai *)devid; struct platform_device *pdev = esai_priv->pdev; u32 esr; + u32 saisr; regmap_read(esai_priv->regmap, REG_ESAI_ESR, &esr); + regmap_read(esai_priv->regmap, REG_ESAI_SAISR, &saisr); + + if ((saisr & (ESAI_SAISR_TUE | ESAI_SAISR_ROE)) && + esai_priv->soc->reset_at_xrun) { + dev_dbg(&pdev->dev, "reset module for xrun\n"); + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, + ESAI_xCR_xEIE_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, + ESAI_xCR_xEIE_MASK, 0); + schedule_work(&esai_priv->work); + } if (esr & ESAI_ESR_TINIT_MASK) dev_dbg(&pdev->dev, "isr: Transmission Initialized\n"); @@ -110,13 +157,15 @@ } /** - * This function is used to calculate the divisors of psr, pm, fp and it is - * supposed to be called in set_dai_sysclk() and set_bclk(). + * fsl_esai_divisor_cal - This function is used to calculate the + * divisors of psr, pm, fp and it is supposed to be called in + * set_dai_sysclk() and set_bclk(). * + * @dai: pointer to DAI + * @tx: current setting is for playback or capture * @ratio: desired overall ratio for the paticipating dividers * @usefp: for HCK setting, there is no need to set fp divider * @fp: bypass other dividers by setting fp directly if fp != 0 - * @tx: current setting is for playback or capture */ static int fsl_esai_divisor_cal(struct snd_soc_dai *dai, bool tx, u32 ratio, bool usefp, u32 fp) @@ -203,13 +252,12 @@ } /** - * This function mainly configures the clock frequency of MCLK (HCKT/HCKR) - * - * @Parameters: - * clk_id: The clock source of HCKT/HCKR + * fsl_esai_set_dai_sysclk - configure the clock frequency of MCLK (HCKT/HCKR) + * @dai: pointer to DAI + * @clk_id: The clock source of HCKT/HCKR * (Input from outside; output from inside, FSYS or EXTAL) - * freq: The required clock rate of HCKT/HCKR - * dir: The clock direction of HCKT/HCKR + * @freq: The required clock rate of HCKT/HCKR + * @dir: The clock direction of HCKT/HCKR * * Note: If the direction is input, we do not care about clk_id. */ @@ -218,7 +266,7 @@ { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); struct clk *clksrc = esai_priv->extalclk; - bool tx = clk_id <= ESAI_HCKT_EXTAL; + bool tx = (clk_id <= ESAI_HCKT_EXTAL || esai_priv->synchronous); bool in = dir == SND_SOC_CLOCK_IN; u32 ratio, ecr = 0; unsigned long clk_rate; @@ -253,7 +301,7 @@ ecr |= ESAI_ECR_ETI; break; case ESAI_HCKR_EXTAL: - ecr |= ESAI_ECR_ERI; + ecr |= esai_priv->synchronous ? ESAI_ECR_ETI : ESAI_ECR_ERI; break; default: return -EINVAL; @@ -311,7 +359,10 @@ } /** - * This function configures the related dividers according to the bclk rate + * fsl_esai_set_bclk - configure the related dividers according to the bclk rate + * @dai: pointer to DAI + * @tx: direction boolean + * @freq: bclk freq */ static int fsl_esai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq) { @@ -466,32 +517,8 @@ struct snd_soc_dai *dai) { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); - int ret; - /* - * Some platforms might use the same bit to gate all three or two of - * clocks, so keep all clocks open/close at the same time for safety - */ - ret = clk_prepare_enable(esai_priv->coreclk); - if (ret) - return ret; - if (!IS_ERR(esai_priv->spbaclk)) { - ret = clk_prepare_enable(esai_priv->spbaclk); - if (ret) - goto err_spbaclk; - } - if (!IS_ERR(esai_priv->extalclk)) { - ret = clk_prepare_enable(esai_priv->extalclk); - if (ret) - goto err_extalck; - } - if (!IS_ERR(esai_priv->fsysclk)) { - ret = clk_prepare_enable(esai_priv->fsysclk); - if (ret) - goto err_fsysclk; - } - - if (!dai->active) { + if (!snd_soc_dai_active(dai)) { /* Set synchronous mode */ regmap_update_bits(esai_priv->regmap, REG_ESAI_SAICR, ESAI_SAICR_SYNC, esai_priv->synchronous ? @@ -508,16 +535,6 @@ return 0; -err_fsysclk: - if (!IS_ERR(esai_priv->extalclk)) - clk_disable_unprepare(esai_priv->extalclk); -err_extalck: - if (!IS_ERR(esai_priv->spbaclk)) - clk_disable_unprepare(esai_priv->spbaclk); -err_spbaclk: - clk_disable_unprepare(esai_priv->coreclk); - - return ret; } static int fsl_esai_hw_params(struct snd_pcm_substream *substream, @@ -539,9 +556,17 @@ bclk = params_rate(params) * slot_width * esai_priv->slots; - ret = fsl_esai_set_bclk(dai, tx, bclk); + ret = fsl_esai_set_bclk(dai, esai_priv->synchronous || tx, bclk); if (ret) return ret; + + mask = ESAI_xCR_xSWS_MASK; + val = ESAI_xCR_xSWS(slot_width, width); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); + /* Recording in synchronous mode needs to set TCR also */ + if (!tx && esai_priv->synchronous) + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, mask, val); /* Use Normal mode to support monaural audio */ regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), @@ -558,10 +583,9 @@ regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), mask, val); - mask = ESAI_xCR_xSWS_MASK | (tx ? ESAI_xCR_PADC : 0); - val = ESAI_xCR_xSWS(slot_width, width) | (tx ? ESAI_xCR_PADC : 0); - - regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), mask, val); + if (tx) + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, + ESAI_xCR_PADC, ESAI_xCR_PADC); /* Remove ESAI personal reset by configuring ESAI_PCRC and ESAI_PRRC */ regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, @@ -571,18 +595,168 @@ return 0; } -static void fsl_esai_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int fsl_esai_hw_init(struct fsl_esai *esai_priv) { - struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); + struct platform_device *pdev = esai_priv->pdev; + int ret; - if (!IS_ERR(esai_priv->fsysclk)) - clk_disable_unprepare(esai_priv->fsysclk); - if (!IS_ERR(esai_priv->extalclk)) - clk_disable_unprepare(esai_priv->extalclk); - if (!IS_ERR(esai_priv->spbaclk)) - clk_disable_unprepare(esai_priv->spbaclk); - clk_disable_unprepare(esai_priv->coreclk); + /* Reset ESAI unit */ + ret = regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR, + ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK, + ESAI_ECR_ESAIEN | ESAI_ECR_ERST); + if (ret) { + dev_err(&pdev->dev, "failed to reset ESAI: %d\n", ret); + return ret; + } + + /* + * We need to enable ESAI so as to access some of its registers. + * Otherwise, we would fail to dump regmap from user space. + */ + ret = regmap_update_bits(esai_priv->regmap, REG_ESAI_ECR, + ESAI_ECR_ESAIEN_MASK | ESAI_ECR_ERST_MASK, + ESAI_ECR_ESAIEN); + if (ret) { + dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret); + return ret; + } + + regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, + ESAI_PRRC_PDC_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, + ESAI_PCRC_PC_MASK, 0); + + return 0; +} + +static int fsl_esai_register_restore(struct fsl_esai *esai_priv) +{ + int ret; + + /* FIFO reset for safety */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR, + ESAI_xFCR_xFR, ESAI_xFCR_xFR); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR, + ESAI_xFCR_xFR, ESAI_xFCR_xFR); + + regcache_mark_dirty(esai_priv->regmap); + ret = regcache_sync(esai_priv->regmap); + if (ret) + return ret; + + /* FIFO reset done */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR, 0); + + return 0; +} + +static void fsl_esai_trigger_start(struct fsl_esai *esai_priv, bool tx) +{ + u8 i, channels = esai_priv->channels[tx]; + u32 pins = DIV_ROUND_UP(channels, esai_priv->slots); + u32 mask; + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFEN_MASK, ESAI_xFCR_xFEN); + + /* Write initial words reqiured by ESAI as normal procedure */ + for (i = 0; tx && i < channels; i++) + regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0); + + /* + * When set the TE/RE in the end of enablement flow, there + * will be channel swap issue for multi data line case. + * In order to workaround this issue, we switch the bit + * enablement sequence to below sequence + * 1) clear the xSMB & xSMA: which is done in probe and + * stop state. + * 2) set TE/RE + * 3) set xSMB + * 4) set xSMA: xSMA is the last one in this flow, which + * will trigger esai to start. + */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, + tx ? ESAI_xCR_TE(pins) : ESAI_xCR_RE(pins)); + mask = tx ? esai_priv->tx_mask : esai_priv->rx_mask; + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx), + ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(mask)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx), + ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(mask)); + + /* Enable Exception interrupt */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + ESAI_xCR_xEIE_MASK, ESAI_xCR_xEIE); +} + +static void fsl_esai_trigger_stop(struct fsl_esai *esai_priv, bool tx) +{ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + ESAI_xCR_xEIE_MASK, 0); + + regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), + tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx), + ESAI_xSMA_xS_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx), + ESAI_xSMB_xS_MASK, 0); + + /* Disable and reset FIFO */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR); + regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), + ESAI_xFCR_xFR, 0); +} + +static void fsl_esai_hw_reset(struct work_struct *work) +{ + struct fsl_esai *esai_priv = container_of(work, struct fsl_esai, work); + bool tx = true, rx = false, enabled[2]; + unsigned long lock_flags; + u32 tfcr, rfcr; + + spin_lock_irqsave(&esai_priv->lock, lock_flags); + /* Save the registers */ + regmap_read(esai_priv->regmap, REG_ESAI_TFCR, &tfcr); + regmap_read(esai_priv->regmap, REG_ESAI_RFCR, &rfcr); + enabled[tx] = tfcr & ESAI_xFCR_xFEN; + enabled[rx] = rfcr & ESAI_xFCR_xFEN; + + /* Stop the tx & rx */ + fsl_esai_trigger_stop(esai_priv, tx); + fsl_esai_trigger_stop(esai_priv, rx); + + /* Reset the esai, and ignore return value */ + fsl_esai_hw_init(esai_priv); + + /* Enforce ESAI personal resets for both TX and RX */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, + ESAI_xCR_xPR_MASK, ESAI_xCR_xPR); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, + ESAI_xCR_xPR_MASK, ESAI_xCR_xPR); + + /* Restore registers by regcache_sync, and ignore return value */ + fsl_esai_register_restore(esai_priv); + + /* Remove ESAI personal resets by configuring PCRC and PRRC also */ + regmap_update_bits(esai_priv->regmap, REG_ESAI_TCR, + ESAI_xCR_xPR_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_RCR, + ESAI_xCR_xPR_MASK, 0); + regmap_update_bits(esai_priv->regmap, REG_ESAI_PRRC, + ESAI_PRRC_PDC_MASK, ESAI_PRRC_PDC(ESAI_GPIO)); + regmap_update_bits(esai_priv->regmap, REG_ESAI_PCRC, + ESAI_PCRC_PC_MASK, ESAI_PCRC_PC(ESAI_GPIO)); + + /* Restart tx / rx, if they already enabled */ + if (enabled[tx]) + fsl_esai_trigger_start(esai_priv, tx); + if (enabled[rx]) + fsl_esai_trigger_start(esai_priv, rx); + + spin_unlock_irqrestore(&esai_priv->lock, lock_flags); } static int fsl_esai_trigger(struct snd_pcm_substream *substream, int cmd, @@ -590,59 +764,24 @@ { struct fsl_esai *esai_priv = snd_soc_dai_get_drvdata(dai); bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; - u8 i, channels = substream->runtime->channels; - u32 pins = DIV_ROUND_UP(channels, esai_priv->slots); - u32 mask; + unsigned long lock_flags; + + esai_priv->channels[tx] = substream->runtime->channels; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), - ESAI_xFCR_xFEN_MASK, ESAI_xFCR_xFEN); - - /* Write initial words reqiured by ESAI as normal procedure */ - for (i = 0; tx && i < channels; i++) - regmap_write(esai_priv->regmap, REG_ESAI_ETDR, 0x0); - - /* - * When set the TE/RE in the end of enablement flow, there - * will be channel swap issue for multi data line case. - * In order to workaround this issue, we switch the bit - * enablement sequence to below sequence - * 1) clear the xSMB & xSMA: which is done in probe and - * stop state. - * 2) set TE/RE - * 3) set xSMB - * 4) set xSMA: xSMA is the last one in this flow, which - * will trigger esai to start. - */ - regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), - tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, - tx ? ESAI_xCR_TE(pins) : ESAI_xCR_RE(pins)); - mask = tx ? esai_priv->tx_mask : esai_priv->rx_mask; - - regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx), - ESAI_xSMB_xS_MASK, ESAI_xSMB_xS(mask)); - regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx), - ESAI_xSMA_xS_MASK, ESAI_xSMA_xS(mask)); - + spin_lock_irqsave(&esai_priv->lock, lock_flags); + fsl_esai_trigger_start(esai_priv, tx); + spin_unlock_irqrestore(&esai_priv->lock, lock_flags); break; case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - regmap_update_bits(esai_priv->regmap, REG_ESAI_xCR(tx), - tx ? ESAI_xCR_TE_MASK : ESAI_xCR_RE_MASK, 0); - regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMA(tx), - ESAI_xSMA_xS_MASK, 0); - regmap_update_bits(esai_priv->regmap, REG_ESAI_xSMB(tx), - ESAI_xSMB_xS_MASK, 0); - - /* Disable and reset FIFO */ - regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), - ESAI_xFCR_xFR | ESAI_xFCR_xFEN, ESAI_xFCR_xFR); - regmap_update_bits(esai_priv->regmap, REG_ESAI_xFCR(tx), - ESAI_xFCR_xFR, 0); + spin_lock_irqsave(&esai_priv->lock, lock_flags); + fsl_esai_trigger_stop(esai_priv, tx); + spin_unlock_irqrestore(&esai_priv->lock, lock_flags); break; default: return -EINVAL; @@ -653,7 +792,6 @@ static const struct snd_soc_dai_ops fsl_esai_dai_ops = { .startup = fsl_esai_startup, - .shutdown = fsl_esai_shutdown, .trigger = fsl_esai_trigger, .hw_params = fsl_esai_hw_params, .set_sysclk = fsl_esai_set_dai_sysclk, @@ -828,7 +966,13 @@ return -ENOMEM; esai_priv->pdev = pdev; - strncpy(esai_priv->name, np->name, sizeof(esai_priv->name) - 1); + snprintf(esai_priv->name, sizeof(esai_priv->name), "%pOFn", np); + + esai_priv->soc = of_device_get_match_data(&pdev->dev); + if (!esai_priv->soc) { + dev_err(&pdev->dev, "failed to get soc data\n"); + return -ENODEV; + } /* Get the addresses and IRQ */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); @@ -867,12 +1011,10 @@ PTR_ERR(esai_priv->spbaclk)); irq = platform_get_irq(pdev, 0); - if (irq < 0) { - dev_err(&pdev->dev, "no irq for node %s\n", pdev->name); + if (irq < 0) return irq; - } - ret = devm_request_irq(&pdev->dev, irq, esai_isr, 0, + ret = devm_request_irq(&pdev->dev, irq, esai_isr, IRQF_SHARED, esai_priv->name, esai_priv); if (ret) { dev_err(&pdev->dev, "failed to claim irq %u\n", irq); @@ -909,22 +1051,10 @@ dev_set_drvdata(&pdev->dev, esai_priv); - /* Reset ESAI unit */ - ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ERST); - if (ret) { - dev_err(&pdev->dev, "failed to reset ESAI: %d\n", ret); + spin_lock_init(&esai_priv->lock); + ret = fsl_esai_hw_init(esai_priv); + if (ret) return ret; - } - - /* - * We need to enable ESAI so as to access some of its registers. - * Otherwise, we would fail to dump regmap from user space. - */ - ret = regmap_write(esai_priv->regmap, REG_ESAI_ECR, ESAI_ECR_ESAIEN); - if (ret) { - dev_err(&pdev->dev, "failed to enable ESAI: %d\n", ret); - return ret; - } esai_priv->tx_mask = 0xFFFFFFFF; esai_priv->rx_mask = 0xFFFFFFFF; @@ -942,6 +1072,12 @@ return ret; } + INIT_WORK(&esai_priv->work, fsl_esai_hw_reset); + + pm_runtime_enable(&pdev->dev); + + regcache_cache_only(esai_priv->regmap, true); + ret = imx_pcm_dma_init(pdev, IMX_ESAI_DMABUF_SIZE); if (ret) dev_err(&pdev->dev, "failed to init imx pcm dma: %d\n", ret); @@ -949,55 +1085,105 @@ return ret; } +static int fsl_esai_remove(struct platform_device *pdev) +{ + struct fsl_esai *esai_priv = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + cancel_work_sync(&esai_priv->work); + + return 0; +} + static const struct of_device_id fsl_esai_dt_ids[] = { - { .compatible = "fsl,imx35-esai", }, - { .compatible = "fsl,vf610-esai", }, + { .compatible = "fsl,imx35-esai", .data = &fsl_esai_imx35 }, + { .compatible = "fsl,vf610-esai", .data = &fsl_esai_vf610 }, + { .compatible = "fsl,imx6ull-esai", .data = &fsl_esai_imx6ull }, {} }; MODULE_DEVICE_TABLE(of, fsl_esai_dt_ids); -#ifdef CONFIG_PM_SLEEP -static int fsl_esai_suspend(struct device *dev) -{ - struct fsl_esai *esai = dev_get_drvdata(dev); - - regcache_cache_only(esai->regmap, true); - regcache_mark_dirty(esai->regmap); - - return 0; -} - -static int fsl_esai_resume(struct device *dev) +#ifdef CONFIG_PM +static int fsl_esai_runtime_resume(struct device *dev) { struct fsl_esai *esai = dev_get_drvdata(dev); int ret; - regcache_cache_only(esai->regmap, false); - - /* FIFO reset for safety */ - regmap_update_bits(esai->regmap, REG_ESAI_TFCR, - ESAI_xFCR_xFR, ESAI_xFCR_xFR); - regmap_update_bits(esai->regmap, REG_ESAI_RFCR, - ESAI_xFCR_xFR, ESAI_xFCR_xFR); - - ret = regcache_sync(esai->regmap); + /* + * Some platforms might use the same bit to gate all three or two of + * clocks, so keep all clocks open/close at the same time for safety + */ + ret = clk_prepare_enable(esai->coreclk); if (ret) return ret; + if (!IS_ERR(esai->spbaclk)) { + ret = clk_prepare_enable(esai->spbaclk); + if (ret) + goto err_spbaclk; + } + if (!IS_ERR(esai->extalclk)) { + ret = clk_prepare_enable(esai->extalclk); + if (ret) + goto err_extalclk; + } + if (!IS_ERR(esai->fsysclk)) { + ret = clk_prepare_enable(esai->fsysclk); + if (ret) + goto err_fsysclk; + } - /* FIFO reset done */ - regmap_update_bits(esai->regmap, REG_ESAI_TFCR, ESAI_xFCR_xFR, 0); - regmap_update_bits(esai->regmap, REG_ESAI_RFCR, ESAI_xFCR_xFR, 0); + regcache_cache_only(esai->regmap, false); + + ret = fsl_esai_register_restore(esai); + if (ret) + goto err_regcache_sync; + + return 0; + +err_regcache_sync: + if (!IS_ERR(esai->fsysclk)) + clk_disable_unprepare(esai->fsysclk); +err_fsysclk: + if (!IS_ERR(esai->extalclk)) + clk_disable_unprepare(esai->extalclk); +err_extalclk: + if (!IS_ERR(esai->spbaclk)) + clk_disable_unprepare(esai->spbaclk); +err_spbaclk: + clk_disable_unprepare(esai->coreclk); + + return ret; +} + +static int fsl_esai_runtime_suspend(struct device *dev) +{ + struct fsl_esai *esai = dev_get_drvdata(dev); + + regcache_cache_only(esai->regmap, true); + + if (!IS_ERR(esai->fsysclk)) + clk_disable_unprepare(esai->fsysclk); + if (!IS_ERR(esai->extalclk)) + clk_disable_unprepare(esai->extalclk); + if (!IS_ERR(esai->spbaclk)) + clk_disable_unprepare(esai->spbaclk); + clk_disable_unprepare(esai->coreclk); return 0; } -#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ static const struct dev_pm_ops fsl_esai_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(fsl_esai_suspend, fsl_esai_resume) + SET_RUNTIME_PM_OPS(fsl_esai_runtime_suspend, + fsl_esai_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) }; static struct platform_driver fsl_esai_driver = { .probe = fsl_esai_probe, + .remove = fsl_esai_remove, .driver = { .name = "fsl-esai-dai", .pm = &fsl_esai_pm_ops, -- Gitblit v1.6.2