Path: blob/master/sound/soc/kirkwood/kirkwood-i2s.c
10817 views
/*1* kirkwood-i2s.c2*3* (c) 2010 Arnaud Patard <[email protected]>4* (c) 2010 Arnaud Patard <[email protected]>5*6* This program is free software; you can redistribute it and/or modify it7* under the terms of the GNU General Public License as published by the8* Free Software Foundation; either version 2 of the License, or (at your9* option) any later version.10*/1112#include <linux/init.h>13#include <linux/module.h>14#include <linux/platform_device.h>15#include <linux/io.h>16#include <linux/slab.h>17#include <linux/mbus.h>18#include <linux/delay.h>19#include <sound/pcm.h>20#include <sound/pcm_params.h>21#include <sound/soc.h>22#include <plat/audio.h>23#include "kirkwood.h"2425#define DRV_NAME "kirkwood-i2s"2627#define KIRKWOOD_I2S_RATES \28(SNDRV_PCM_RATE_44100 | \29SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000)30#define KIRKWOOD_I2S_FORMATS \31(SNDRV_PCM_FMTBIT_S16_LE | \32SNDRV_PCM_FMTBIT_S24_LE | \33SNDRV_PCM_FMTBIT_S32_LE)3435static int kirkwood_i2s_set_fmt(struct snd_soc_dai *cpu_dai,36unsigned int fmt)37{38struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(cpu_dai);39unsigned long mask;40unsigned long value;4142switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {43case SND_SOC_DAIFMT_RIGHT_J:44mask = KIRKWOOD_I2S_CTL_RJ;45break;46case SND_SOC_DAIFMT_LEFT_J:47mask = KIRKWOOD_I2S_CTL_LJ;48break;49case SND_SOC_DAIFMT_I2S:50mask = KIRKWOOD_I2S_CTL_I2S;51break;52default:53return -EINVAL;54}5556/*57* Set same format for playback and record58* This avoids some troubles.59*/60value = readl(priv->io+KIRKWOOD_I2S_PLAYCTL);61value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;62value |= mask;63writel(value, priv->io+KIRKWOOD_I2S_PLAYCTL);6465value = readl(priv->io+KIRKWOOD_I2S_RECCTL);66value &= ~KIRKWOOD_I2S_CTL_JUST_MASK;67value |= mask;68writel(value, priv->io+KIRKWOOD_I2S_RECCTL);6970return 0;71}7273static inline void kirkwood_set_dco(void __iomem *io, unsigned long rate)74{75unsigned long value;7677value = KIRKWOOD_DCO_CTL_OFFSET_0;78switch (rate) {79default:80case 44100:81value |= KIRKWOOD_DCO_CTL_FREQ_11;82break;83case 48000:84value |= KIRKWOOD_DCO_CTL_FREQ_12;85break;86case 96000:87value |= KIRKWOOD_DCO_CTL_FREQ_24;88break;89}90writel(value, io + KIRKWOOD_DCO_CTL);9192/* wait for dco locked */93do {94cpu_relax();95value = readl(io + KIRKWOOD_DCO_SPCR_STATUS);96value &= KIRKWOOD_DCO_SPCR_STATUS;97} while (value == 0);98}99100static int kirkwood_i2s_startup(struct snd_pcm_substream *substream,101struct snd_soc_dai *dai)102{103struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);104105snd_soc_dai_set_dma_data(dai, substream, priv);106return 0;107}108109static int kirkwood_i2s_hw_params(struct snd_pcm_substream *substream,110struct snd_pcm_hw_params *params,111struct snd_soc_dai *dai)112{113struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);114unsigned int i2s_reg, reg;115unsigned long i2s_value, value;116117if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {118i2s_reg = KIRKWOOD_I2S_PLAYCTL;119reg = KIRKWOOD_PLAYCTL;120} else {121i2s_reg = KIRKWOOD_I2S_RECCTL;122reg = KIRKWOOD_RECCTL;123}124125/* set dco conf */126kirkwood_set_dco(priv->io, params_rate(params));127128i2s_value = readl(priv->io+i2s_reg);129i2s_value &= ~KIRKWOOD_I2S_CTL_SIZE_MASK;130131value = readl(priv->io+reg);132value &= ~KIRKWOOD_PLAYCTL_SIZE_MASK;133134/*135* Size settings in play/rec i2s control regs and play/rec control136* regs must be the same.137*/138switch (params_format(params)) {139case SNDRV_PCM_FORMAT_S16_LE:140i2s_value |= KIRKWOOD_I2S_CTL_SIZE_16;141value |= KIRKWOOD_PLAYCTL_SIZE_16_C;142break;143/*144* doesn't work... S20_3LE != kirkwood 20bit format ?145*146case SNDRV_PCM_FORMAT_S20_3LE:147i2s_value |= KIRKWOOD_I2S_CTL_SIZE_20;148value |= KIRKWOOD_PLAYCTL_SIZE_20;149break;150*/151case SNDRV_PCM_FORMAT_S24_LE:152i2s_value |= KIRKWOOD_I2S_CTL_SIZE_24;153value |= KIRKWOOD_PLAYCTL_SIZE_24;154break;155case SNDRV_PCM_FORMAT_S32_LE:156i2s_value |= KIRKWOOD_I2S_CTL_SIZE_32;157value |= KIRKWOOD_PLAYCTL_SIZE_32;158break;159default:160return -EINVAL;161}162163if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {164value &= ~KIRKWOOD_PLAYCTL_MONO_MASK;165if (params_channels(params) == 1)166value |= KIRKWOOD_PLAYCTL_MONO_BOTH;167else168value |= KIRKWOOD_PLAYCTL_MONO_OFF;169}170171writel(i2s_value, priv->io+i2s_reg);172writel(value, priv->io+reg);173174return 0;175}176177static int kirkwood_i2s_play_trigger(struct snd_pcm_substream *substream,178int cmd, struct snd_soc_dai *dai)179{180struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);181unsigned long value;182183/*184* specs says KIRKWOOD_PLAYCTL must be read 2 times before185* changing it. So read 1 time here and 1 later.186*/187value = readl(priv->io + KIRKWOOD_PLAYCTL);188189switch (cmd) {190case SNDRV_PCM_TRIGGER_START:191/* stop audio, enable interrupts */192value = readl(priv->io + KIRKWOOD_PLAYCTL);193value |= KIRKWOOD_PLAYCTL_PAUSE;194writel(value, priv->io + KIRKWOOD_PLAYCTL);195196value = readl(priv->io + KIRKWOOD_INT_MASK);197value |= KIRKWOOD_INT_CAUSE_PLAY_BYTES;198writel(value, priv->io + KIRKWOOD_INT_MASK);199200/* configure audio & enable i2s playback */201value = readl(priv->io + KIRKWOOD_PLAYCTL);202value &= ~KIRKWOOD_PLAYCTL_BURST_MASK;203value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE204| KIRKWOOD_PLAYCTL_SPDIF_EN);205206if (priv->burst == 32)207value |= KIRKWOOD_PLAYCTL_BURST_32;208else209value |= KIRKWOOD_PLAYCTL_BURST_128;210value |= KIRKWOOD_PLAYCTL_I2S_EN;211writel(value, priv->io + KIRKWOOD_PLAYCTL);212break;213214case SNDRV_PCM_TRIGGER_STOP:215/* stop audio, disable interrupts */216value = readl(priv->io + KIRKWOOD_PLAYCTL);217value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;218writel(value, priv->io + KIRKWOOD_PLAYCTL);219220value = readl(priv->io + KIRKWOOD_INT_MASK);221value &= ~KIRKWOOD_INT_CAUSE_PLAY_BYTES;222writel(value, priv->io + KIRKWOOD_INT_MASK);223224/* disable all playbacks */225value = readl(priv->io + KIRKWOOD_PLAYCTL);226value &= ~(KIRKWOOD_PLAYCTL_I2S_EN | KIRKWOOD_PLAYCTL_SPDIF_EN);227writel(value, priv->io + KIRKWOOD_PLAYCTL);228break;229230case SNDRV_PCM_TRIGGER_PAUSE_PUSH:231case SNDRV_PCM_TRIGGER_SUSPEND:232value = readl(priv->io + KIRKWOOD_PLAYCTL);233value |= KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE;234writel(value, priv->io + KIRKWOOD_PLAYCTL);235break;236237case SNDRV_PCM_TRIGGER_RESUME:238case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:239value = readl(priv->io + KIRKWOOD_PLAYCTL);240value &= ~(KIRKWOOD_PLAYCTL_PAUSE | KIRKWOOD_PLAYCTL_I2S_MUTE);241writel(value, priv->io + KIRKWOOD_PLAYCTL);242break;243244default:245return -EINVAL;246}247248return 0;249}250251static int kirkwood_i2s_rec_trigger(struct snd_pcm_substream *substream,252int cmd, struct snd_soc_dai *dai)253{254struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);255unsigned long value;256257value = readl(priv->io + KIRKWOOD_RECCTL);258259switch (cmd) {260case SNDRV_PCM_TRIGGER_START:261/* stop audio, enable interrupts */262value = readl(priv->io + KIRKWOOD_RECCTL);263value |= KIRKWOOD_RECCTL_PAUSE;264writel(value, priv->io + KIRKWOOD_RECCTL);265266value = readl(priv->io + KIRKWOOD_INT_MASK);267value |= KIRKWOOD_INT_CAUSE_REC_BYTES;268writel(value, priv->io + KIRKWOOD_INT_MASK);269270/* configure audio & enable i2s record */271value = readl(priv->io + KIRKWOOD_RECCTL);272value &= ~KIRKWOOD_RECCTL_BURST_MASK;273value &= ~KIRKWOOD_RECCTL_MONO;274value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE275| KIRKWOOD_RECCTL_SPDIF_EN);276277if (priv->burst == 32)278value |= KIRKWOOD_RECCTL_BURST_32;279else280value |= KIRKWOOD_RECCTL_BURST_128;281value |= KIRKWOOD_RECCTL_I2S_EN;282283writel(value, priv->io + KIRKWOOD_RECCTL);284break;285286case SNDRV_PCM_TRIGGER_STOP:287/* stop audio, disable interrupts */288value = readl(priv->io + KIRKWOOD_RECCTL);289value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;290writel(value, priv->io + KIRKWOOD_RECCTL);291292value = readl(priv->io + KIRKWOOD_INT_MASK);293value &= ~KIRKWOOD_INT_CAUSE_REC_BYTES;294writel(value, priv->io + KIRKWOOD_INT_MASK);295296/* disable all records */297value = readl(priv->io + KIRKWOOD_RECCTL);298value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);299writel(value, priv->io + KIRKWOOD_RECCTL);300break;301302case SNDRV_PCM_TRIGGER_PAUSE_PUSH:303case SNDRV_PCM_TRIGGER_SUSPEND:304value = readl(priv->io + KIRKWOOD_RECCTL);305value |= KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE;306writel(value, priv->io + KIRKWOOD_RECCTL);307break;308309case SNDRV_PCM_TRIGGER_RESUME:310case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:311value = readl(priv->io + KIRKWOOD_RECCTL);312value &= ~(KIRKWOOD_RECCTL_PAUSE | KIRKWOOD_RECCTL_MUTE);313writel(value, priv->io + KIRKWOOD_RECCTL);314break;315316default:317return -EINVAL;318}319320return 0;321}322323static int kirkwood_i2s_trigger(struct snd_pcm_substream *substream, int cmd,324struct snd_soc_dai *dai)325{326if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)327return kirkwood_i2s_play_trigger(substream, cmd, dai);328else329return kirkwood_i2s_rec_trigger(substream, cmd, dai);330331return 0;332}333334static int kirkwood_i2s_probe(struct snd_soc_dai *dai)335{336struct kirkwood_dma_data *priv = snd_soc_dai_get_drvdata(dai);337unsigned long value;338unsigned int reg_data;339340/* put system in a "safe" state : */341/* disable audio interrupts */342writel(0xffffffff, priv->io + KIRKWOOD_INT_CAUSE);343writel(0, priv->io + KIRKWOOD_INT_MASK);344345reg_data = readl(priv->io + 0x1200);346reg_data &= (~(0x333FF8));347reg_data |= 0x111D18;348writel(reg_data, priv->io + 0x1200);349350msleep(500);351352reg_data = readl(priv->io + 0x1200);353reg_data &= (~(0x333FF8));354reg_data |= 0x111D18;355writel(reg_data, priv->io + 0x1200);356357/* disable playback/record */358value = readl(priv->io + KIRKWOOD_PLAYCTL);359value &= ~(KIRKWOOD_PLAYCTL_I2S_EN|KIRKWOOD_PLAYCTL_SPDIF_EN);360writel(value, priv->io + KIRKWOOD_PLAYCTL);361362value = readl(priv->io + KIRKWOOD_RECCTL);363value &= ~(KIRKWOOD_RECCTL_I2S_EN | KIRKWOOD_RECCTL_SPDIF_EN);364writel(value, priv->io + KIRKWOOD_RECCTL);365366return 0;367368}369370static int kirkwood_i2s_remove(struct snd_soc_dai *dai)371{372return 0;373}374375static struct snd_soc_dai_ops kirkwood_i2s_dai_ops = {376.startup = kirkwood_i2s_startup,377.trigger = kirkwood_i2s_trigger,378.hw_params = kirkwood_i2s_hw_params,379.set_fmt = kirkwood_i2s_set_fmt,380};381382383static struct snd_soc_dai_driver kirkwood_i2s_dai = {384.probe = kirkwood_i2s_probe,385.remove = kirkwood_i2s_remove,386.playback = {387.channels_min = 1,388.channels_max = 2,389.rates = KIRKWOOD_I2S_RATES,390.formats = KIRKWOOD_I2S_FORMATS,},391.capture = {392.channels_min = 1,393.channels_max = 2,394.rates = KIRKWOOD_I2S_RATES,395.formats = KIRKWOOD_I2S_FORMATS,},396.ops = &kirkwood_i2s_dai_ops,397};398399static __devinit int kirkwood_i2s_dev_probe(struct platform_device *pdev)400{401struct resource *mem;402struct kirkwood_asoc_platform_data *data =403pdev->dev.platform_data;404struct kirkwood_dma_data *priv;405int err;406407priv = kzalloc(sizeof(struct kirkwood_dma_data), GFP_KERNEL);408if (!priv) {409dev_err(&pdev->dev, "allocation failed\n");410err = -ENOMEM;411goto error;412}413dev_set_drvdata(&pdev->dev, priv);414415mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);416if (!mem) {417dev_err(&pdev->dev, "platform_get_resource failed\n");418err = -ENXIO;419goto err_alloc;420}421422priv->mem = request_mem_region(mem->start, SZ_16K, DRV_NAME);423if (!priv->mem) {424dev_err(&pdev->dev, "request_mem_region failed\n");425err = -EBUSY;426goto error;427}428429priv->io = ioremap(priv->mem->start, SZ_16K);430if (!priv->io) {431dev_err(&pdev->dev, "ioremap failed\n");432err = -ENOMEM;433goto err_iomem;434}435436priv->irq = platform_get_irq(pdev, 0);437if (priv->irq <= 0) {438dev_err(&pdev->dev, "platform_get_irq failed\n");439err = -ENXIO;440goto err_ioremap;441}442443if (!data || !data->dram) {444dev_err(&pdev->dev, "no platform data ?!\n");445err = -EINVAL;446goto err_ioremap;447}448449priv->dram = data->dram;450priv->burst = data->burst;451452return snd_soc_register_dai(&pdev->dev, &kirkwood_i2s_dai);453454err_ioremap:455iounmap(priv->io);456err_iomem:457release_mem_region(priv->mem->start, SZ_16K);458err_alloc:459kfree(priv);460error:461return err;462}463464static __devexit int kirkwood_i2s_dev_remove(struct platform_device *pdev)465{466struct kirkwood_dma_data *priv = dev_get_drvdata(&pdev->dev);467468snd_soc_unregister_dai(&pdev->dev);469iounmap(priv->io);470release_mem_region(priv->mem->start, SZ_16K);471kfree(priv);472473return 0;474}475476static struct platform_driver kirkwood_i2s_driver = {477.probe = kirkwood_i2s_dev_probe,478.remove = kirkwood_i2s_dev_remove,479.driver = {480.name = DRV_NAME,481.owner = THIS_MODULE,482},483};484485static int __init kirkwood_i2s_init(void)486{487return platform_driver_register(&kirkwood_i2s_driver);488}489module_init(kirkwood_i2s_init);490491static void __exit kirkwood_i2s_exit(void)492{493platform_driver_unregister(&kirkwood_i2s_driver);494}495module_exit(kirkwood_i2s_exit);496497/* Module information */498MODULE_AUTHOR("Arnaud Patard, <[email protected]>");499MODULE_DESCRIPTION("Kirkwood I2S SoC Interface");500MODULE_LICENSE("GPL");501MODULE_ALIAS("platform:kirkwood-i2s");502503504