Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / sound / soc / au1x / i2sc.c
diff --git a/kernel/sound/soc/au1x/i2sc.c b/kernel/sound/soc/au1x/i2sc.c
new file mode 100644 (file)
index 0000000..450c842
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * Au1000/Au1500/Au1100 I2S controller driver for ASoC
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ * Note: clock supplied to the I2S controller must be 256x samplerate.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <asm/mach-au1x00/au1000.h>
+
+#include "psc.h"
+
+#define I2S_RXTX       0x00
+#define I2S_CFG                0x04
+#define I2S_ENABLE     0x08
+
+#define CFG_XU         (1 << 25)       /* tx underflow */
+#define CFG_XO         (1 << 24)
+#define CFG_RU         (1 << 23)
+#define CFG_RO         (1 << 22)
+#define CFG_TR         (1 << 21)
+#define CFG_TE         (1 << 20)
+#define CFG_TF         (1 << 19)
+#define CFG_RR         (1 << 18)
+#define CFG_RF         (1 << 17)
+#define CFG_ICK                (1 << 12)       /* clock invert */
+#define CFG_PD         (1 << 11)       /* set to make I2SDIO INPUT */
+#define CFG_LB         (1 << 10)       /* loopback */
+#define CFG_IC         (1 << 9)        /* word select invert */
+#define CFG_FM_I2S     (0 << 7)        /* I2S format */
+#define CFG_FM_LJ      (1 << 7)        /* left-justified */
+#define CFG_FM_RJ      (2 << 7)        /* right-justified */
+#define CFG_FM_MASK    (3 << 7)
+#define CFG_TN         (1 << 6)        /* tx fifo en */
+#define CFG_RN         (1 << 5)        /* rx fifo en */
+#define CFG_SZ_8       (0x08)
+#define CFG_SZ_16      (0x10)
+#define CFG_SZ_18      (0x12)
+#define CFG_SZ_20      (0x14)
+#define CFG_SZ_24      (0x18)
+#define CFG_SZ_MASK    (0x1f)
+#define EN_D           (1 << 1)        /* DISable */
+#define EN_CE          (1 << 0)        /* clock enable */
+
+/* only limited by clock generator and board design */
+#define AU1XI2SC_RATES \
+       SNDRV_PCM_RATE_CONTINUOUS
+
+#define AU1XI2SC_FMTS \
+       (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |            \
+       SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE |     \
+       SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE |     \
+       SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE |   \
+       SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE |   \
+       SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |   \
+       SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE |   \
+       SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |     \
+       SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |     \
+       0)
+
+static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
+{
+       return __raw_readl(ctx->mmio + reg);
+}
+
+static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
+{
+       __raw_writel(v, ctx->mmio + reg);
+       wmb();
+}
+
+static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai);
+       unsigned long c;
+       int ret;
+
+       ret = -EINVAL;
+       c = ctx->cfg;
+
+       c &= ~CFG_FM_MASK;
+       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_I2S:
+               c |= CFG_FM_I2S;
+               break;
+       case SND_SOC_DAIFMT_MSB:
+               c |= CFG_FM_RJ;
+               break;
+       case SND_SOC_DAIFMT_LSB:
+               c |= CFG_FM_LJ;
+               break;
+       default:
+               goto out;
+       }
+
+       c &= ~(CFG_IC | CFG_ICK);               /* IB-IF */
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_NB_NF:
+               c |= CFG_IC | CFG_ICK;
+               break;
+       case SND_SOC_DAIFMT_NB_IF:
+               c |= CFG_IC;
+               break;
+       case SND_SOC_DAIFMT_IB_NF:
+               c |= CFG_ICK;
+               break;
+       case SND_SOC_DAIFMT_IB_IF:
+               break;
+       default:
+               goto out;
+       }
+
+       /* I2S controller only supports master */
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBS_CFS:    /* CODEC slave */
+               break;
+       default:
+               goto out;
+       }
+
+       ret = 0;
+       ctx->cfg = c;
+out:
+       return ret;
+}
+
+static int au1xi2s_trigger(struct snd_pcm_substream *substream,
+                          int cmd, struct snd_soc_dai *dai)
+{
+       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+       int stype = SUBSTREAM_TYPE(substream);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+               /* power up */
+               WR(ctx, I2S_ENABLE, EN_D | EN_CE);
+               WR(ctx, I2S_ENABLE, EN_CE);
+               ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN;
+               WR(ctx, I2S_CFG, ctx->cfg);
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+               ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN);
+               WR(ctx, I2S_CFG, ctx->cfg);
+               WR(ctx, I2S_ENABLE, EN_D);              /* power off */
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static unsigned long msbits_to_reg(int msbits)
+{
+       switch (msbits) {
+       case 8:
+               return CFG_SZ_8;
+       case 16:
+               return CFG_SZ_16;
+       case 18:
+               return CFG_SZ_18;
+       case 20:
+               return CFG_SZ_20;
+       case 24:
+               return CFG_SZ_24;
+       }
+       return 0;
+}
+
+static int au1xi2s_hw_params(struct snd_pcm_substream *substream,
+                            struct snd_pcm_hw_params *params,
+                            struct snd_soc_dai *dai)
+{
+       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+       unsigned long v;
+
+       v = msbits_to_reg(params->msbits);
+       if (!v)
+               return -EINVAL;
+
+       ctx->cfg &= ~CFG_SZ_MASK;
+       ctx->cfg |= v;
+       return 0;
+}
+
+static int au1xi2s_startup(struct snd_pcm_substream *substream,
+                          struct snd_soc_dai *dai)
+{
+       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+       snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
+       return 0;
+}
+
+static const struct snd_soc_dai_ops au1xi2s_dai_ops = {
+       .startup        = au1xi2s_startup,
+       .trigger        = au1xi2s_trigger,
+       .hw_params      = au1xi2s_hw_params,
+       .set_fmt        = au1xi2s_set_fmt,
+};
+
+static struct snd_soc_dai_driver au1xi2s_dai_driver = {
+       .symmetric_rates        = 1,
+       .playback = {
+               .rates          = AU1XI2SC_RATES,
+               .formats        = AU1XI2SC_FMTS,
+               .channels_min   = 2,
+               .channels_max   = 2,
+       },
+       .capture = {
+               .rates          = AU1XI2SC_RATES,
+               .formats        = AU1XI2SC_FMTS,
+               .channels_min   = 2,
+               .channels_max   = 2,
+       },
+       .ops = &au1xi2s_dai_ops,
+};
+
+static const struct snd_soc_component_driver au1xi2s_component = {
+       .name           = "au1xi2s",
+};
+
+static int au1xi2s_drvprobe(struct platform_device *pdev)
+{
+       struct resource *iores, *dmares;
+       struct au1xpsc_audio_data *ctx;
+
+       ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx)
+               return -ENOMEM;
+
+       iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!iores)
+               return -ENODEV;
+
+       if (!devm_request_mem_region(&pdev->dev, iores->start,
+                                    resource_size(iores),
+                                    pdev->name))
+               return -EBUSY;
+
+       ctx->mmio = devm_ioremap_nocache(&pdev->dev, iores->start,
+                                        resource_size(iores));
+       if (!ctx->mmio)
+               return -EBUSY;
+
+       dmares = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+       if (!dmares)
+               return -EBUSY;
+       ctx->dmaids[SNDRV_PCM_STREAM_PLAYBACK] = dmares->start;
+
+       dmares = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+       if (!dmares)
+               return -EBUSY;
+       ctx->dmaids[SNDRV_PCM_STREAM_CAPTURE] = dmares->start;
+
+       platform_set_drvdata(pdev, ctx);
+
+       return snd_soc_register_component(&pdev->dev, &au1xi2s_component,
+                                         &au1xi2s_dai_driver, 1);
+}
+
+static int au1xi2s_drvremove(struct platform_device *pdev)
+{
+       struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
+
+       snd_soc_unregister_component(&pdev->dev);
+
+       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xi2s_drvsuspend(struct device *dev)
+{
+       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
+
+       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
+
+       return 0;
+}
+
+static int au1xi2s_drvresume(struct device *dev)
+{
+       return 0;
+}
+
+static const struct dev_pm_ops au1xi2sc_pmops = {
+       .suspend        = au1xi2s_drvsuspend,
+       .resume         = au1xi2s_drvresume,
+};
+
+#define AU1XI2SC_PMOPS (&au1xi2sc_pmops)
+
+#else
+
+#define AU1XI2SC_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xi2s_driver = {
+       .driver = {
+               .name   = "alchemy-i2sc",
+               .pm     = AU1XI2SC_PMOPS,
+       },
+       .probe          = au1xi2s_drvprobe,
+       .remove         = au1xi2s_drvremove,
+};
+
+module_platform_driver(au1xi2s_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver");
+MODULE_AUTHOR("Manuel Lauss");