root/sound/soc/renesas/rcar/msiof.c
// SPDX-License-Identifier: GPL-2.0
//
// Renesas R-Car MSIOF (Clock-Synchronized Serial Interface with FIFO) I2S driver
//
// Copyright (C) 2025 Renesas Solutions Corp.
// Author: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
//

/*
 * [NOTE-CLOCK-MODE]
 *
 * This driver doesn't support Clock/Frame Provider Mode
 *
 * Basically MSIOF is created for SPI, but we can use it as I2S (Sound), etc. Because of it, when
 * we use it as I2S (Sound) with Provider Mode, we need to send dummy TX data even though it was
 * used for RX. Because SPI HW needs TX Clock/Frame output for RX purpose.
 * But it makes driver code complex in I2S (Sound).
 *
 * And when we use it as I2S (Sound) as Provider Mode, the clock source is [MSO clock] (= 133.33MHz)
 * SoC internal clock. It is not for 48kHz/44.1kHz base clock. Thus the output/input will not be
 * accurate sound.
 *
 * Because of these reasons, this driver doesn't support Clock/Frame Provider Mode. Use it as
 * Clock/Frame Consumer Mode.
 */

/*
 * [NOTE-RESET]
 *
 * MSIOF has TXRST/RXRST to reset FIFO, but it shouldn't be used during SYNC signal was asserted,
 * because it will be cause of HW issue.
 *
 * When MSIOF is used as Sound driver, this driver is assuming it is used as clock consumer mode
 * (= Codec is clock provider). This means, it can't control SYNC signal by itself.
 *
 * We need to use SW reset (= reset_control_xxx()) instead of TXRST/RXRST.
 */

/*
 * [NOTE-BOTH-SETTING]
 *
 * SITMDRn / SIRMDRn and some other registers should not be updated during working even though it
 * was not related the target direction (for example, do TX settings during RX is working),
 * otherwise it cause a FSERR.
 *
 * Setup both direction (Playback/Capture) in the same time.
 */

/*
 * [NOTE-R/L]
 *
 * The data of Captured might be R/L opposite.
 *
 * This driver is assuming MSIOF is used as Clock/Frame Consumer Mode, and there is a case that some
 * Codec (= Clock/Frame Provider) might output Clock/Frame before setup MSIOF. It depends on Codec
 * driver implementation.
 *
 * MSIOF will capture data without checking SYNC signal Hi/Low (= R/L).
 *
 * This means, if MSIOF RXE bit was set as 1 in case of SYNC signal was Hi (= R) timing, it will
 * start capture data since next SYNC low singla (= L). Because Linux assumes sound data is lined
 * up as R->L->R->L->..., the data R/L will be opposite.
 *
 * The only solution in this case is start CLK/SYNC *after* MSIOF settings, but it depends when and
 * how Codec driver start it.
 */

/*
 * [NOTE-FSERR]
 *
 * We can't remove all FSERR.
 *
 * Renesas have tried to minimize the occurrence of FSERR errors as much as possible, but
 * unfortunately we cannot remove them completely, because MSIOF might setup its register during
 * CLK/SYNC are inputed. It can be happen because MSIOF is working as Clock/Frame Consumer.
 */

#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_dma.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/spi/sh_msiof.h>
#include <sound/dmaengine_pcm.h>
#include <sound/soc.h>

/* SISTR */
#define SISTR_ERR_TX    (SISTR_TFSERR | SISTR_TFOVF | SISTR_TFUDF)
#define SISTR_ERR_RX    (SISTR_RFSERR | SISTR_RFOVF | SISTR_RFUDF)

/*
 * The data on memory in 24bit case is located at <right> side
 *      [  xxxxxx]
 *      [  xxxxxx]
 *      [  xxxxxx]
 *
 * HW assuming signal in 24bit case is located at <left> side
 *      ---+         +---------+
 *         +---------+         +---------+...
 *         [xxxxxx  ][xxxxxx  ][xxxxxx  ]
 *
 * When we use 24bit data, it will be transferred via 32bit width via DMA,
 * and MSIOF/DMA doesn't support data shift, we can't use 24bit data correctly.
 * There is no such issue on 16/32bit data case.
 */
#define MSIOF_RATES     SNDRV_PCM_RATE_8000_192000
#define MSIOF_FMTS      (SNDRV_PCM_FMTBIT_S16_LE |\
                         SNDRV_PCM_FMTBIT_S32_LE)

struct msiof_priv {
        struct device *dev;
        struct snd_pcm_substream *substream[SNDRV_PCM_STREAM_LAST + 1];
        struct reset_control *reset;
        spinlock_t lock;
        void __iomem *base;
        resource_size_t phy_addr;

        int count;

        /* for error */
        int err_syc[SNDRV_PCM_STREAM_LAST + 1];
        int err_ovf[SNDRV_PCM_STREAM_LAST + 1];
        int err_udf[SNDRV_PCM_STREAM_LAST + 1];

        /* bit field */
        u32 flags;
#define MSIOF_FLAGS_NEED_DELAY          (1 << 0)
};
#define msiof_flag_has(priv, flag)      (priv->flags &  flag)
#define msiof_flag_set(priv, flag)      (priv->flags |= flag)

#define msiof_is_play(substream)        ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK)
#define msiof_read(priv, reg)           ioread32((priv)->base + reg)
#define msiof_write(priv, reg, val)     iowrite32(val, (priv)->base + reg)

static int msiof_update(struct msiof_priv *priv, u32 reg, u32 mask, u32 val)
{
        u32 old = msiof_read(priv, reg);
        u32 new = (old & ~mask) | (val & mask);
        int updated = false;

        if (old != new) {
                msiof_write(priv, reg, new);
                updated = true;
        }

        return updated;
}

static void msiof_update_and_wait(struct msiof_priv *priv, u32 reg, u32 mask, u32 val, u32 expect)
{
        u32 data;
        int ret;

        ret = msiof_update(priv, reg, mask, val);
        if (!ret) /* no update */
                return;

        ret = readl_poll_timeout_atomic(priv->base + reg, data,
                                        (data & mask) == expect, 1, 128);
        if (ret)
                dev_warn(priv->dev, "write timeout [0x%02x] 0x%08x / 0x%08x\n",
                         reg, data, expect);
}

static int msiof_hw_start(struct snd_soc_component *component,
                          struct snd_pcm_substream *substream, int cmd)
{
        struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
        struct snd_pcm_runtime *runtime = substream->runtime;
        int is_play = msiof_is_play(substream);
        int width = snd_pcm_format_width(runtime->format);
        u32 val;

        /*
         * see
         *      [NOTE-CLOCK-MODE] on top of this driver
         */
        /*
         * see
         *      Datasheet 109.3.6 [Transmit and Receive Procedures]
         *
         *      TX: Fig 109.14  - Fig 109.23
         *      RX: Fig 109.15
         */

        /*
         * Use reset_control_xx() instead of TXRST/RXRST.
         * see
         *      [NOTE-RESET]
         */
        if (!priv->count)
                reset_control_deassert(priv->reset);

        priv->count++;

        /*
         * Reset errors. ignore 1st FSERR
         *
         * see
         *      [NOTE-FSERR]
         */
        priv->err_syc[substream->stream] = -1;
        priv->err_ovf[substream->stream] =
        priv->err_udf[substream->stream] = 0;

        /* Start DMAC */
        snd_dmaengine_pcm_trigger(substream, cmd);

        /*
         * setup both direction (Playback/Capture) in the same time.
         * see
         *      above [NOTE-BOTH-SETTING]
         */

        /* SITMDRx */
        val = SITMDR1_PCON | SIMDR1_SYNCAC | SIMDR1_XXSTP |
                FIELD_PREP(SIMDR1_SYNCMD, SIMDR1_SYNCMD_LR);
        if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
                val |= FIELD_PREP(SIMDR1_DTDL, 1);

        msiof_write(priv, SITMDR1, val);

        val = FIELD_PREP(SIMDR2_BITLEN1, width - 1);
        msiof_write(priv, SITMDR2, val | FIELD_PREP(SIMDR2_GRP, 1));
        msiof_write(priv, SITMDR3, val);

        /* SIRMDRx */
        val = SIMDR1_SYNCAC |
                FIELD_PREP(SIMDR1_SYNCMD, SIMDR1_SYNCMD_LR);
        if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
                val |= FIELD_PREP(SIMDR1_DTDL, 1);

        msiof_write(priv, SIRMDR1, val);

        val = FIELD_PREP(SIMDR2_BITLEN1, width - 1);
        msiof_write(priv, SIRMDR2, val | FIELD_PREP(SIMDR2_GRP, 1));
        msiof_write(priv, SIRMDR3, val);

        /* SIFCTR */
        msiof_write(priv, SIFCTR,
                    FIELD_PREP(SIFCTR_TFWM, SIFCTR_TFWM_1) |
                    FIELD_PREP(SIFCTR_RFWM, SIFCTR_RFWM_1));

        /* SIIER */
        if (is_play)
                val = SIIER_TDREQE | SIIER_TDMAE | SISTR_ERR_TX;
        else
                val = SIIER_RDREQE | SIIER_RDMAE | SISTR_ERR_RX;
        msiof_update(priv, SIIER, val, val);

        /* clear status */
        if (is_play)
                val = SISTR_ERR_TX;
        else
                val = SISTR_ERR_RX;
        msiof_update(priv, SISTR, val, val);

        /* SICTR */
        val = SICTR_TEDG | SICTR_REDG;
        if (is_play)
                val |= SICTR_TXE;
        else
                val |= SICTR_RXE;
        msiof_update_and_wait(priv, SICTR, val, val, val);

        return 0;
}

static int msiof_hw_stop(struct snd_soc_component *component,
                         struct snd_pcm_substream *substream, int cmd)
{
        struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
        struct device *dev = component->dev;
        int is_play = msiof_is_play(substream);
        u32 val;

        /* SIIER */
        if (is_play)
                val = SIIER_TDREQE | SIIER_TDMAE | SISTR_ERR_TX;
        else
                val = SIIER_RDREQE | SIIER_RDMAE | SISTR_ERR_RX;
        msiof_update(priv, SIIER, val, 0);

        /* SICTR */
        if (is_play)
                val = SICTR_TXE;
        else
                val = SICTR_RXE;
        msiof_update_and_wait(priv, SICTR, val, 0, 0);

        /* Stop DMAC */
        snd_dmaengine_pcm_trigger(substream, cmd);

        /*
         * Ignore 1st FSERR
         *
         * see
         *      [NOTE-FSERR]
         */
        if (priv->err_syc[substream->stream] < 0)
                priv->err_syc[substream->stream] = 0;

        /* indicate error status if exist */
        if (priv->err_syc[substream->stream] ||
            priv->err_ovf[substream->stream] ||
            priv->err_udf[substream->stream])
                dev_warn(dev, "%s: FSERR = %d, FOVF = %d, FUDF = %d\n",
                         snd_pcm_direction_name(substream->stream),
                         priv->err_syc[substream->stream],
                         priv->err_ovf[substream->stream],
                         priv->err_udf[substream->stream]);

        priv->count--;

        if (!priv->count)
                reset_control_assert(priv->reset);

        return 0;
}

static int msiof_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
        struct msiof_priv *priv = snd_soc_dai_get_drvdata(dai);

        switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
        /*
         * It supports Clock/Frame Consumer Mode only
         * see
         *      [NOTE] on top of this driver
         */
        case SND_SOC_DAIFMT_BC_FC:
                break;
        /* others are error */
        default:
                return -EINVAL;
        }

        switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
        /* it supports NB_NF only */
        case SND_SOC_DAIFMT_NB_NF:
        default:
                break;
        /* others are error */
        case SND_SOC_DAIFMT_NB_IF:
        case SND_SOC_DAIFMT_IB_NF:
        case SND_SOC_DAIFMT_IB_IF:
                return -EINVAL;
        }

        switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
        case SND_SOC_DAIFMT_I2S:
                msiof_flag_set(priv, MSIOF_FLAGS_NEED_DELAY);
                break;
        case SND_SOC_DAIFMT_LEFT_J:
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

/*
 * Select below from Sound Card, not auto
 *      SND_SOC_DAIFMT_CBC_CFC
 *      SND_SOC_DAIFMT_CBP_CFP
 */
static const u64 msiof_dai_formats = SND_SOC_POSSIBLE_DAIFMT_I2S        |
                                     SND_SOC_POSSIBLE_DAIFMT_LEFT_J     |
                                     SND_SOC_POSSIBLE_DAIFMT_NB_NF;

static const struct snd_soc_dai_ops msiof_dai_ops = {
        .set_fmt                        = msiof_dai_set_fmt,
        .auto_selectable_formats        = &msiof_dai_formats,
        .num_auto_selectable_formats    = 1,
};

static struct snd_soc_dai_driver msiof_dai_driver = {
        .name = "msiof-dai",
        .playback = {
                .rates          = MSIOF_RATES,
                .formats        = MSIOF_FMTS,
                .channels_min   = 2,
                .channels_max   = 2,
        },
        .capture = {
                .rates          = MSIOF_RATES,
                .formats        = MSIOF_FMTS,
                .channels_min   = 2,
                .channels_max   = 2,
        },
        .ops = &msiof_dai_ops,
        .symmetric_rate         = 1,
        .symmetric_channels     = 1,
        .symmetric_sample_bits  = 1,
};

static struct snd_pcm_hardware msiof_pcm_hardware = {
        .info = SNDRV_PCM_INFO_INTERLEAVED      |
                SNDRV_PCM_INFO_MMAP             |
                SNDRV_PCM_INFO_MMAP_VALID,
        .buffer_bytes_max       = 64 * 1024,
        .period_bytes_min       = 32,
        .period_bytes_max       = 8192,
        .periods_min            = 1,
        .periods_max            = 32,
        .fifo_size              = 64,
};

static int msiof_open(struct snd_soc_component *component,
                      struct snd_pcm_substream *substream)
{
        struct device *dev = component->dev;
        struct dma_chan *chan;
        static const char * const dma_names[] = {"rx", "tx"};
        int is_play = msiof_is_play(substream);
        int ret;

        chan = of_dma_request_slave_channel(dev->of_node, dma_names[is_play]);
        if (IS_ERR(chan))
                return PTR_ERR(chan);

        ret = snd_dmaengine_pcm_open(substream, chan);
        if (ret < 0)
                goto open_err_dma;

        snd_soc_set_runtime_hwparams(substream, &msiof_pcm_hardware);

        ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);

open_err_dma:
        if (ret < 0)
                dma_release_channel(chan);

        return ret;
}

static int msiof_close(struct snd_soc_component *component,
                       struct snd_pcm_substream *substream)
{
        return snd_dmaengine_pcm_close_release_chan(substream);
}

static snd_pcm_uframes_t msiof_pointer(struct snd_soc_component *component,
                                       struct snd_pcm_substream *substream)
{
        return snd_dmaengine_pcm_pointer(substream);
}

#define PREALLOC_BUFFER         (32 * 1024)
#define PREALLOC_BUFFER_MAX     (32 * 1024)
static int msiof_new(struct snd_soc_component *component,
                     struct snd_soc_pcm_runtime *rtd)
{
        snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
                                       rtd->card->snd_card->dev,
                                       PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
        return 0;
}

static int msiof_trigger(struct snd_soc_component *component,
                         struct snd_pcm_substream *substream, int cmd)
{
        struct device *dev = component->dev;
        struct msiof_priv *priv = dev_get_drvdata(dev);
        int ret = -EINVAL;

        guard(spinlock_irqsave)(&priv->lock);

        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
                priv->substream[substream->stream] = substream;
                fallthrough;
        case SNDRV_PCM_TRIGGER_RESUME:
                ret = msiof_hw_start(component, substream, cmd);
                break;
        case SNDRV_PCM_TRIGGER_STOP:
                priv->substream[substream->stream] = NULL;
                fallthrough;
        case SNDRV_PCM_TRIGGER_SUSPEND:
                ret = msiof_hw_stop(component, substream, cmd);
                break;
        }

        return ret;
}

static int msiof_hw_params(struct snd_soc_component *component,
                           struct snd_pcm_substream *substream,
                           struct snd_pcm_hw_params *params)
{
        struct msiof_priv *priv = dev_get_drvdata(component->dev);
        struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
        struct dma_slave_config cfg = {};
        int ret;

        guard(spinlock_irqsave)(&priv->lock);

        ret = snd_hwparams_to_dma_slave_config(substream, params, &cfg);
        if (ret < 0)
                return ret;

        cfg.dst_addr = priv->phy_addr + SITFDR;
        cfg.src_addr = priv->phy_addr + SIRFDR;

        return dmaengine_slave_config(chan, &cfg);
}

static const struct snd_soc_component_driver msiof_component_driver = {
        .name           = "msiof",
        .open           = msiof_open,
        .close          = msiof_close,
        .pointer        = msiof_pointer,
        .pcm_construct  = msiof_new,
        .trigger        = msiof_trigger,
        .hw_params      = msiof_hw_params,
};

static irqreturn_t msiof_interrupt(int irq, void *data)
{
        struct msiof_priv *priv = data;
        struct snd_pcm_substream *substream;
        u32 sistr;

        scoped_guard(spinlock, &priv->lock) {
                sistr = msiof_read(priv, SISTR);
                msiof_write(priv, SISTR, SISTR_ERR_TX | SISTR_ERR_RX);
        }

        /* overflow/underflow error */
        substream = priv->substream[SNDRV_PCM_STREAM_PLAYBACK];
        if (substream && (sistr & SISTR_ERR_TX)) {
                // snd_pcm_stop_xrun(substream);
                if (sistr & SISTR_TFSERR)
                        priv->err_syc[SNDRV_PCM_STREAM_PLAYBACK]++;
                if (sistr & SISTR_TFOVF)
                        priv->err_ovf[SNDRV_PCM_STREAM_PLAYBACK]++;
                if (sistr & SISTR_TFUDF)
                        priv->err_udf[SNDRV_PCM_STREAM_PLAYBACK]++;
        }

        substream = priv->substream[SNDRV_PCM_STREAM_CAPTURE];
        if (substream && (sistr & SISTR_ERR_RX)) {
                // snd_pcm_stop_xrun(substream);
                if (sistr & SISTR_RFSERR)
                        priv->err_syc[SNDRV_PCM_STREAM_CAPTURE]++;
                if (sistr & SISTR_RFOVF)
                        priv->err_ovf[SNDRV_PCM_STREAM_CAPTURE]++;
                if (sistr & SISTR_RFUDF)
                        priv->err_udf[SNDRV_PCM_STREAM_CAPTURE]++;
        }

        return IRQ_HANDLED;
}

static int msiof_probe(struct platform_device *pdev)
{
        struct msiof_priv *priv;
        struct device *dev = &pdev->dev;
        struct resource *res;
        int irq, ret;

        /* Check MSIOF as Sound mode or SPI mode */
        struct device_node *port __free(device_node) = of_graph_get_next_port(dev->of_node, NULL);
        if (!port)
                return -ENODEV;

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res)
                return -ENODEV;

        irq = platform_get_irq(pdev, 0);
        if (irq <= 0)
                return irq;

        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;

        priv->base = devm_ioremap_resource(dev, res);
        if (IS_ERR(priv->base))
                return PTR_ERR(priv->base);

        priv->reset = devm_reset_control_get_exclusive(dev, NULL);
        if (IS_ERR(priv->reset))
                return PTR_ERR(priv->reset);

        reset_control_assert(priv->reset);

        ret = devm_request_irq(dev, irq, msiof_interrupt, 0, dev_name(dev), priv);
        if (ret)
                return ret;

        priv->dev       = dev;
        priv->phy_addr  = res->start;
        priv->count     = 0;

        spin_lock_init(&priv->lock);
        platform_set_drvdata(pdev, priv);

        devm_pm_runtime_enable(dev);

        ret = devm_snd_soc_register_component(dev, &msiof_component_driver,
                                              &msiof_dai_driver, 1);

        return ret;
}

static const struct of_device_id msiof_of_match[] = {
        { .compatible = "renesas,rcar-gen4-msiof", },
        {},
};
MODULE_DEVICE_TABLE(of, msiof_of_match);

static struct platform_driver msiof_driver = {
        .driver = {
                .name   = "msiof-pcm-audio",
                .of_match_table = msiof_of_match,
        },
        .probe          = msiof_probe,
};
module_platform_driver(msiof_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Renesas R-Car MSIOF I2S audio driver");
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");