diff options
author | Nicolin Chen <b42378@freescale.com> | 2013-08-23 19:31:52 +0800 |
---|---|---|
committer | Nicolin Chen <b42378@freescale.com> | 2013-08-29 17:43:27 +0800 |
commit | b1d6221943c487e47a4cab9fe16cf507757b3e2b (patch) | |
tree | d5dd229dc535f1a9fd664238de83565c619d62fb /sound | |
parent | 6db31a90fce75fd4973be7efa06b5161e864a4c7 (diff) |
ENGR00276567-2 ASoC: fsl: Add I2S Master support for ssi
Add I2S master/PCM(DSP_A, DSP_B)/LEFT_J formats support for ssi,
also dropped the constrain of i2s-slave in probe().
Signed-off-by: Nicolin Chen <b42378@freescale.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/fsl/fsl_ssi.c | 219 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_ssi.h | 6 |
2 files changed, 215 insertions, 10 deletions
diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index ad9875f7bb30..894e2109c8a0 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -138,6 +138,7 @@ struct fsl_ssi_private { struct device_attribute dev_attr; struct platform_device *pdev; + unsigned long sysrate; bool new_binding; bool ssi_on_imx; bool use_dual_fifo; @@ -488,6 +489,8 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, snd_pcm_format_width(params_format(hw_params)); u32 wl = CCSR_SSI_SxCCR_WL(sample_size); int enabled = read_ssi(&ssi->scr) & CCSR_SSI_SCR_SSIEN; + u32 dc = CCSR_SSI_SxCCR_DC(params_channels(hw_params)); + int ret; /* * If we're in synchronous mode, and the SSI is already enabled, @@ -496,6 +499,14 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, if (enabled && ssi_private->cpu_dai_drv.symmetric_rates) return 0; + if (ssi_private->sysrate) { + ret = clk_set_rate(ssi_private->clk, ssi_private->sysrate); + if (ret) { + dev_err(cpu_dai->dev, "failed to set clock rate\n"); + return ret; + } + } + /* * FIXME: The documentation says that SxCCR[WL] should not be * modified while the SSI is enabled. The only time this can @@ -508,10 +519,13 @@ static int fsl_ssi_hw_params(struct snd_pcm_substream *substream, /* In synchronous mode, the SSI uses STCCR for capture */ if ((substream->stream == SNDRV_PCM_STREAM_PLAYBACK) || - ssi_private->cpu_dai_drv.symmetric_rates) + ssi_private->cpu_dai_drv.symmetric_rates) { write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_WL_MASK, wl); - else + write_ssi_mask(&ssi->stccr, CCSR_SSI_SxCCR_DC_MASK, dc); + } else { write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, wl); + write_ssi_mask(&ssi->srccr, CCSR_SSI_SxCCR_WL_MASK, dc); + } return 0; } @@ -567,6 +581,194 @@ static int fsl_ssi_trigger(struct snd_pcm_substream *substream, int cmd, return 0; } +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_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; + + /* DAI format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + scr |= CCSR_SSI_SCR_NET; + + /* Pre-set SSI I2S mode */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + scr &= ~CCSR_SSI_SCR_I2S_MODE_MASK; + scr |= CCSR_SSI_SCR_I2S_MODE_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFM: + scr &= ~CCSR_SSI_SCR_I2S_MODE_MASK; + scr |= CCSR_SSI_SCR_I2S_MODE_SLAVE; + break; + default: + dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT_MASTER: %d", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + /* 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: + dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT: %d", + fmt & SND_SOC_DAIFMT_FORMAT_MASK); + 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: + dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT_INV: %d", + fmt & SND_SOC_DAIFMT_INV_MASK); + return -EINVAL; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + scr |= CCSR_SSI_SCR_SYS_CLK_EN; + strcr |= CCSR_SSI_STCR_TFDIR | CCSR_SSI_STCR_TXDIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + scr &= ~CCSR_SSI_SCR_SYS_CLK_EN; + break; + default: + dev_err(cpu_dai->dev, "unsupported SND_SOC_DAIFMT_MASTER: %d", + fmt & SND_SOC_DAIFMT_MASTER_MASK); + return -EINVAL; + } + + stcr |= strcr; + srcr |= strcr; + + if (ssi_private->cpu_dai_drv.symmetric_rates) { + scr |= CCSR_SSI_SCR_SYN; + + /* Need to clear RXDIR when using SYNC mode */ + srcr &= ~CCSR_SSI_SRCR_RXDIR; + } + + write_ssi(stcr, &ssi->stcr); + write_ssi(srcr, &ssi->srcr); + write_ssi(scr, &ssi->scr); + + return 0; +} + +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; + u32 pm = 999, div2, psr, stccr, mask, afreq, factor, i; + unsigned long clkrate, sysrate = 0; + u64 sub, savesub = 100000; + + /* + * It should be already enough to divide clock by setting pm. + * So we here keep psr and div2 to 0. + */ + 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; + + sysrate = freq * factor * (i + 2); + clkrate = clk_round_rate(ssi_private->clk, sysrate); + + 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) { + ssi_private->sysrate = sysrate; + 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_MASK + | CCSR_SSI_SxCCR_PSR_MASK; + + if (dir == SND_SOC_CLOCK_OUT || synchronous) + write_ssi_mask(&ssi->stccr, mask, stccr); + else + write_ssi_mask(&ssi->srccr, mask, stccr); + + return 0; +} + /** * fsl_ssi_shutdown: shutdown the SSI * @@ -588,6 +790,8 @@ static void fsl_ssi_shutdown(struct snd_pcm_substream *substream, struct ccsr_ssi __iomem *ssi = ssi_private->ssi; write_ssi_mask(&ssi->sier, SIER_FLAGS, 0); + + ssi_private->sysrate = 0; } if (ssi_private->ssi_on_imx) { @@ -613,6 +817,8 @@ 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, + .set_fmt = fsl_ssi_set_dai_fmt, + .set_sysclk = fsl_ssi_set_dai_sysclk, .shutdown = fsl_ssi_shutdown, .trigger = fsl_ssi_trigger, }; @@ -723,13 +929,6 @@ static int fsl_ssi_probe(struct platform_device *pdev) if (!of_device_is_available(np)) return -ENODEV; - /* We only support the SSI in "I2S Slave" mode */ - sprop = of_get_property(np, "fsl,mode", NULL); - if (!sprop || strcmp(sprop, "i2s-slave")) { - dev_notice(&pdev->dev, "mode %s is unsupported\n", sprop); - return -ENODEV; - } - /* The DAI name is the last part of the full name of the node. */ p = strrchr(np->full_name, '/') + 1; ssi_private = kzalloc(sizeof(struct fsl_ssi_private) + strlen(p), @@ -817,6 +1016,8 @@ static int fsl_ssi_probe(struct platform_device *pdev) ssi_private->ssi_phys + offsetof(struct ccsr_ssi, srx0); } + ssi_private->sysrate = 0; + /* Initialize the the device_attribute structure */ dev_attr = &ssi_private->dev_attr; sysfs_attr_init(&dev_attr->attr); diff --git a/sound/soc/fsl/fsl_ssi.h b/sound/soc/fsl/fsl_ssi.h index e6b9a69e2a68..5ff9b1d5270d 100644 --- a/sound/soc/fsl/fsl_ssi.h +++ b/sound/soc/fsl/fsl_ssi.h @@ -3,7 +3,7 @@ * * Author: Timur Tabi <timur@freescale.com> * - * Copyright 2007-2008 Freescale Semiconductor, Inc. This file is licensed + * Copyright 2007-2013 Freescale Semiconductor, Inc. This file is licensed * under the terms of the GNU General Public License version 2. This * program is licensed "as is" without any warranty of any kind, whether * express or implied. @@ -125,7 +125,11 @@ struct ccsr_ssi { #define CCSR_SSI_SRCR_REFS 0x00000001 /* STCCR and SRCCR */ +#define CCSR_SSI_SxCCR_DIV2_SHIFT 18 +#define CCSR_SSI_SxCCR_DIV2_MASK 0x00040000 #define CCSR_SSI_SxCCR_DIV2 0x00040000 +#define CCSR_SSI_SxCCR_PSR_SHIFT 17 +#define CCSR_SSI_SxCCR_PSR_MASK 0x00020000 #define CCSR_SSI_SxCCR_PSR 0x00020000 #define CCSR_SSI_SxCCR_WL_SHIFT 13 #define CCSR_SSI_SxCCR_WL_MASK 0x0001E000 |