root/sound/soc/au1x/i2sc.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * 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 provider */
        switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
        case SND_SOC_DAIFMT_BP_FP:      /* CODEC consumer */
                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_rate         = 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",
        .legacy_dai_naming      = 1,
};

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(&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 void 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 */
}

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 DEFINE_SIMPLE_DEV_PM_OPS(au1xi2sc_pmops, au1xi2s_drvsuspend,
                                au1xi2s_drvresume);

static struct platform_driver au1xi2s_driver = {
        .driver = {
                .name   = "alchemy-i2sc",
                .pm     = pm_ptr(&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");