diff options
Diffstat (limited to 'sound/soc/kirkwood/kirkwood-i2s.c')
-rw-r--r-- | sound/soc/kirkwood/kirkwood-i2s.c | 235 |
1 files changed, 149 insertions, 86 deletions
diff --git a/sound/soc/kirkwood/kirkwood-i2s.c b/sound/soc/kirkwood/kirkwood-i2s.c index 1d5db484d2df..d3629d5927e9 100644 --- a/sound/soc/kirkwood/kirkwood-i2s.c +++ b/sound/soc/kirkwood/kirkwood-i2s.c @@ -99,6 +99,29 @@ static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate) } while (value == 0); } +static void kirkwood_set_rate(struct snd_soc_dai *dai, + struct kirkwood_dma_data *priv, unsigned long rate) +{ + uint32_t clks_ctrl; + + if (rate == 44100 || rate == 48000 || rate == 96000) { + /* use internal dco for supported rates */ + dev_dbg(dai->dev, "%s: dco set rate = %lu\n", + __func__, rate); + kirkwood_set_dco(priv->io, rate); + + clks_ctrl = KIRKWOOD_MCLK_SOURCE_DCO; + } else if (!IS_ERR(priv->extclk)) { + /* use optional external clk for other rates */ + dev_dbg(dai->dev, "%s: extclk set rate = %lu -> %lu\n", + __func__, rate, 256 * rate); + clk_set_rate(priv->extclk, 256 * rate); + + clks_ctrl = KIRKWOOD_MCLK_SOURCE_EXTCLK; + } + writel(clks_ctrl, priv->io + KIRKWOOD_CLOCKS_CTRL); +} + static int kirkwood_i2s_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -113,26 +136,21 @@ static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); - unsigned int i2s_reg, reg; - unsigned long i2s_value, value; + uint32_t ctl_play, ctl_rec; + unsigned int i2s_reg; + unsigned long i2s_value; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { i2s_reg = KIRKWOOD_I2S_PLAYCTL; - reg = KIRKWOOD_PLAYCTL; } else { i2s_reg = KIRKWOOD_I2S_RECCTL; - reg = KIRKWOOD_RECCTL; } - /* set dco conf */ - kirkwood_set_dco(priv->io, params_rate(params)); + kirkwood_set_rate(dai, priv, params_rate(params)); i2s_value = readl(priv->io+i2s_reg); i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK; - value = readl(priv->io+reg); - value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK; - /* * Size settings in play/rec i2s control regs and play/rec control * regs must be the same. @@ -140,38 +158,57 @@ static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream, switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16; - value |= KIRKWOOD_PLAYCTL_SIZE_16_C; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_16_C | + KIRKWOOD_PLAYCTL_I2S_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_16_C | + KIRKWOOD_RECCTL_I2S_EN; break; /* * doesn't work... S20_3LE != kirkwood 20bit format ? * case SNDRV_PCM_FORMAT_S20_3LE: i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20; - value |= KIRKWOOD_PLAYCTL_SIZE_20; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_20 | + KIRKWOOD_PLAYCTL_I2S_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_20 | + KIRKWOOD_RECCTL_I2S_EN; break; */ case SNDRV_PCM_FORMAT_S24_LE: i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24; - value |= KIRKWOOD_PLAYCTL_SIZE_24; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_24 | + KIRKWOOD_PLAYCTL_I2S_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_24 | + KIRKWOOD_RECCTL_I2S_EN; break; case SNDRV_PCM_FORMAT_S32_LE: i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32; - value |= KIRKWOOD_PLAYCTL_SIZE_32; + ctl_play = KIRKWOOD_PLAYCTL_SIZE_32 | + KIRKWOOD_PLAYCTL_I2S_EN; + ctl_rec = KIRKWOOD_RECCTL_SIZE_32 | + KIRKWOOD_RECCTL_I2S_EN; break; default: return -EINVAL; } if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - value &= ~KIRKWOOD_PLAYCTL_MONO_MASK; if (params_channels(params) == 1) - value |= KIRKWOOD_PLAYCTL_MONO_BOTH; + ctl_play |= KIRKWOOD_PLAYCTL_MONO_BOTH; else - value |= KIRKWOOD_PLAYCTL_MONO_OFF; + ctl_play |= KIRKWOOD_PLAYCTL_MONO_OFF; + + priv->ctl_play &= ~(KIRKWOOD_PLAYCTL_MONO_MASK | + KIRKWOOD_PLAYCTL_I2S_EN | + KIRKWOOD_PLAYCTL_SPDIF_EN | + KIRKWOOD_PLAYCTL_SIZE_MASK); + priv->ctl_play |= ctl_play; + } else { + priv->ctl_rec &= ~KIRKWOOD_RECCTL_SIZE_MASK; + priv->ctl_rec |= ctl_rec; } writel(i2s_value, priv->io+i2s_reg); - writel(value, priv->io+reg); return 0; } @@ -205,20 +242,18 @@ static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream, switch (cmd) { case SNDRV_PCM_TRIGGER_START: + /* configure */ + ctl = priv->ctl_play; + value = ctl & ~(KIRKWOOD_PLAYCTL_I2S_EN | + KIRKWOOD_PLAYCTL_SPDIF_EN); + writel(value, priv->io + KIRKWOOD_PLAYCTL); + + /* enable interrupts */ value = readl(priv->io + KIRKWOOD_INT_MASK); value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES; writel(value, priv->io + KIRKWOOD_INT_MASK); - /* configure audio & enable i2s playback */ - ctl &= ~KIRKWOOD_PLAYCTL_BURST_MASK; - ctl &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE - | KIRKWOOD_PLAYCTL_SPDIF_EN); - - if (priv->burst == 32) - ctl |= KIRKWOOD_PLAYCTL_BURST_32; - else - ctl |= KIRKWOOD_PLAYCTL_BURST_128; - ctl |= KIRKWOOD_PLAYCTL_I2S_EN; + /* enable playback */ writel(ctl, priv->io + KIRKWOOD_PLAYCTL); break; @@ -259,30 +294,24 @@ static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai); - unsigned long value; + uint32_t ctl, value; value = readl(priv->io + KIRKWOOD_RECCTL); switch (cmd) { case SNDRV_PCM_TRIGGER_START: + /* configure */ + ctl = priv->ctl_rec; + value = ctl & ~KIRKWOOD_RECCTL_I2S_EN; + writel(value, priv->io + KIRKWOOD_RECCTL); + + /* enable interrupts */ value = readl(priv->io + KIRKWOOD_INT_MASK); value |= KIRKWOOD_INT_CAUSE_REC_BYTES; writel(value, priv->io + KIRKWOOD_INT_MASK); - /* configure audio & enable i2s record */ - value = readl(priv->io + KIRKWOOD_RECCTL); - value &= ~KIRKWOOD_RECCTL_BURST_MASK; - value &= ~KIRKWOOD_RECCTL_MONO; - value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE - | KIRKWOOD_RECCTL_SPDIF_EN); - - if (priv->burst == 32) - value |= KIRKWOOD_RECCTL_BURST_32; - else - value |= KIRKWOOD_RECCTL_BURST_128; - value |= KIRKWOOD_RECCTL_I2S_EN; - - writel(value, priv->io + KIRKWOOD_RECCTL); + /* enable record */ + writel(ctl, priv->io + KIRKWOOD_RECCTL); break; case SNDRV_PCM_TRIGGER_STOP: @@ -389,90 +418,125 @@ static struct snd_soc_dai_driver kirkwood_i2s_dai = { .channels_min = 1, .channels_max = 2, .rates = KIRKWOOD_I2S_RATES, - .formats = KIRKWOOD_I2S_FORMATS,}, + .formats = KIRKWOOD_I2S_FORMATS, + }, .capture = { .channels_min = 1, .channels_max = 2, .rates = KIRKWOOD_I2S_RATES, - .formats = KIRKWOOD_I2S_FORMATS,}, + .formats = KIRKWOOD_I2S_FORMATS, + }, + .ops = &kirkwood_i2s_dai_ops, +}; + +static struct snd_soc_dai_driver kirkwood_i2s_dai_extclk = { + .probe = kirkwood_i2s_probe, + .remove = kirkwood_i2s_remove, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000 | + SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_KNOT, + .formats = KIRKWOOD_I2S_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000 | + SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_KNOT, + .formats = KIRKWOOD_I2S_FORMATS, + }, .ops = &kirkwood_i2s_dai_ops, }; static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev) { - struct resource *mem; - struct kirkwood_asoc_platform_data *data = - pdev->dev.platform_data; + struct kirkwood_asoc_platform_data *data = pdev->dev.platform_data; + struct snd_soc_dai_driver *soc_dai = &kirkwood_i2s_dai; struct kirkwood_dma_data *priv; + struct resource *mem; int err; - priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) { dev_err(&pdev->dev, "allocation failed\n"); - err = -ENOMEM; - goto error; + return -ENOMEM; } dev_set_drvdata(&pdev->dev, priv); mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(&pdev->dev, "platform_get_resource failed\n"); - err = -ENXIO; - goto err_alloc; - } - - priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME); - if (!priv->mem) { - dev_err(&pdev->dev, "request_mem_region failed\n"); - err = -EBUSY; - goto err_alloc; + return -ENXIO; } - priv->io = ioremap(priv->mem->start, SZ_16K); + priv->io = devm_request_and_ioremap(&pdev->dev, mem); if (!priv->io) { - dev_err(&pdev->dev, "ioremap failed\n"); - err = -ENOMEM; - goto err_iomem; + dev_err(&pdev->dev, "devm_request_and_ioremap failed\n"); + return -ENOMEM; } priv->irq = platform_get_irq(pdev, 0); if (priv->irq <= 0) { dev_err(&pdev->dev, "platform_get_irq failed\n"); - err = -ENXIO; - goto err_ioremap; + return -ENXIO; } if (!data) { dev_err(&pdev->dev, "no platform data ?!\n"); - err = -EINVAL; - goto err_ioremap; + return -EINVAL; } priv->burst = data->burst; - priv->clk = clk_get(&pdev->dev, NULL); + priv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, "no clock\n"); - err = PTR_ERR(priv->clk); - goto err_ioremap; + return PTR_ERR(priv->clk); + } + + err = clk_prepare_enable(priv->clk); + if (err < 0) + return err; + + priv->extclk = clk_get(&pdev->dev, "extclk"); + if (!IS_ERR(priv->extclk)) { + if (priv->extclk == priv->clk) { + clk_put(priv->extclk); + priv->extclk = ERR_PTR(-EINVAL); + } else { + dev_info(&pdev->dev, "found external clock\n"); + clk_prepare_enable(priv->extclk); + soc_dai = &kirkwood_i2s_dai_extclk; + } + } + + /* Some sensible defaults - this reflects the powerup values */ + priv->ctl_play = KIRKWOOD_PLAYCTL_SIZE_24; + priv->ctl_rec = KIRKWOOD_RECCTL_SIZE_24; + + /* Select the burst size */ + if (data->burst == 32) { + priv->ctl_play |= KIRKWOOD_PLAYCTL_BURST_32; + priv->ctl_rec |= KIRKWOOD_RECCTL_BURST_32; + } else { + priv->ctl_play |= KIRKWOOD_PLAYCTL_BURST_128; + priv->ctl_rec |= KIRKWOOD_RECCTL_BURST_128; } - clk_prepare_enable(priv->clk); - err = snd_soc_register_dai(&pdev->dev, &kirkwood_i2s_dai); + err = snd_soc_register_dai(&pdev->dev, soc_dai); if (!err) return 0; dev_err(&pdev->dev, "snd_soc_register_dai failed\n"); + if (!IS_ERR(priv->extclk)) { + clk_disable_unprepare(priv->extclk); + clk_put(priv->extclk); + } clk_disable_unprepare(priv->clk); - clk_put(priv->clk); - -err_ioremap: - iounmap(priv->io); -err_iomem: - release_mem_region(priv->mem->start, SZ_16K); -err_alloc: - kfree(priv); -error: + return err; } @@ -482,12 +546,11 @@ static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev) snd_soc_unregister_dai(&pdev->dev); + if (!IS_ERR(priv->extclk)) { + clk_disable_unprepare(priv->extclk); + clk_put(priv->extclk); + } clk_disable_unprepare(priv->clk); - clk_put(priv->clk); - - iounmap(priv->io); - release_mem_region(priv->mem->start, SZ_16K); - kfree(priv); return 0; } |