root/sound/soc/sophgo/cv1800b-tdm.c
// SPDX-License-Identifier: GPL-2.0

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <sound/dmaengine_pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/string.h>
#include <linux/dev_printk.h>

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/limits.h>
#include <linux/overflow.h>

#define TX_FIFO_SIZE (1024)
#define RX_FIFO_SIZE (1024)
#define TX_MAX_BURST (8)
#define RX_MAX_BURST (8)

#define CV1800B_DEF_FREQ          24576000
#define CV1800B_DEF_MCLK_FS_RATIO 256

/* tdm registers */
#define CV1800B_BLK_MODE_SETTING  0x000
#define CV1800B_FRAME_SETTING     0x004
#define CV1800B_SLOT_SETTING1     0x008
#define CV1800B_SLOT_SETTING2     0x00C
#define CV1800B_DATA_FORMAT       0x010
#define CV1800B_BLK_CFG           0x014
#define CV1800B_I2S_ENABLE        0x018
#define CV1800B_I2S_RESET         0x01C
#define CV1800B_I2S_INT_EN        0x020
#define CV1800B_I2S_INT           0x024
#define CV1800B_FIFO_THRESHOLD    0x028
#define CV1800B_LRCK_MASTER       0x02C /* special clock only mode */
#define CV1800B_FIFO_RESET        0x030
#define CV1800B_RX_STATUS         0x040
#define CV1800B_TX_STATUS         0x048
#define CV1800B_CLK_CTRL0         0x060
#define CV1800B_CLK_CTRL1         0x064
#define CV1800B_PCM_SYNTH         0x068
#define CV1800B_RX_RD_PORT        0x080
#define CV1800B_TX_WR_PORT        0x0C0

/* CV1800B_BLK_MODE_SETTING (0x000) */
#define BLK_TX_MODE_MASK                   GENMASK(0, 0)
#define BLK_MASTER_MODE_MASK               GENMASK(1, 1)
#define BLK_DMA_MODE_MASK                  GENMASK(7, 7)

/* CV1800B_CLK_CTRL1 (0x064) */
#define CLK_MCLK_DIV_MASK                  GENMASK(15, 0)
#define CLK_BCLK_DIV_MASK                  GENMASK(31, 16)

/* CV1800B_CLK_CTRL0 (0x060) */
#define CLK_AUD_CLK_SEL_MASK               GENMASK(0, 0)
#define CLK_BCLK_OUT_CLK_FORCE_EN_MASK     GENMASK(6, 6)
#define CLK_MCLK_OUT_EN_MASK               GENMASK(7, 7)
#define CLK_AUD_EN_MASK                    GENMASK(8, 8)

/* CV1800B_I2S_RESET (0x01C) */
#define RST_I2S_RESET_RX_MASK              GENMASK(0, 0)
#define RST_I2S_RESET_TX_MASK              GENMASK(1, 1)

/* CV1800B_FIFO_RESET (0x030) */
#define FIFO_RX_RESET_MASK                 GENMASK(0, 0)
#define FIFO_TX_RESET_MASK                 GENMASK(16, 16)

/* CV1800B_I2S_ENABLE (0x018) */
#define I2S_ENABLE_MASK                    GENMASK(0, 0)

/* CV1800B_BLK_CFG (0x014) */
#define BLK_AUTO_DISABLE_WITH_CH_EN_MASK   GENMASK(4, 4)
#define BLK_RX_BLK_CLK_FORCE_EN_MASK       GENMASK(8, 8)
#define BLK_RX_FIFO_DMA_CLK_FORCE_EN_MASK  GENMASK(9, 9)
#define BLK_TX_BLK_CLK_FORCE_EN_MASK       GENMASK(16, 16)
#define BLK_TX_FIFO_DMA_CLK_FORCE_EN_MASK  GENMASK(17, 17)

/* CV1800B_FRAME_SETTING (0x004) */
#define FRAME_LENGTH_MASK                  GENMASK(8, 0)
#define FS_ACTIVE_LENGTH_MASK              GENMASK(23, 16)

/* CV1800B_I2S_INT_EN (0x020) */
#define INT_I2S_INT_EN_MASK                GENMASK(8, 8)

/* CV1800B_SLOT_SETTING2 (0x00C) */
#define SLOT_EN_MASK                       GENMASK(15, 0)

/* CV1800B_LRCK_MASTER (0x02C) */
#define LRCK_MASTER_ENABLE_MASK            GENMASK(0, 0)

/* CV1800B_DATA_FORMAT (0x010) */
#define DF_WORD_LENGTH_MASK                GENMASK(2, 1)
#define DF_TX_SOURCE_LEFT_ALIGN_MASK       GENMASK(6, 6)

/* CV1800B_FIFO_THRESHOLD (0x028) */
#define FIFO_RX_THRESHOLD_MASK             GENMASK(4, 0)
#define FIFO_TX_THRESHOLD_MASK             GENMASK(20, 16)
#define FIFO_TX_HIGH_THRESHOLD_MASK        GENMASK(28, 24)

/* CV1800B_SLOT_SETTING1 (0x008) */
#define SLOT_NUM_MASK                      GENMASK(3, 0)
#define SLOT_SIZE_MASK                     GENMASK(13, 8)
#define DATA_SIZE_MASK                     GENMASK(20, 16)
#define FB_OFFSET_MASK                     GENMASK(28, 24)

enum cv1800b_tdm_word_length {
        CV1800B_WORD_LENGTH_8_BIT = 0,
        CV1800B_WORD_LENGTH_16_BIT = 1,
        CV1800B_WORD_LENGTH_32_BIT = 2,
};

struct cv1800b_i2s {
        void __iomem *base;
        struct clk *clk;
        struct clk *sysclk;
        struct device *dev;
        struct snd_dmaengine_dai_dma_data playback_dma;
        struct snd_dmaengine_dai_dma_data capture_dma;
        u32 mclk_rate;
        bool bclk_ratio_fixed;
        u32 bclk_ratio;

};

static void cv1800b_setup_dma_struct(struct cv1800b_i2s *i2s,
                                     phys_addr_t phys_base)
{
        i2s->playback_dma.addr = phys_base + CV1800B_TX_WR_PORT;
        i2s->playback_dma.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
        i2s->playback_dma.fifo_size = TX_FIFO_SIZE;
        i2s->playback_dma.maxburst = TX_MAX_BURST;

        i2s->capture_dma.addr = phys_base + CV1800B_RX_RD_PORT;
        i2s->capture_dma.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
        i2s->capture_dma.fifo_size = RX_FIFO_SIZE;
        i2s->capture_dma.maxburst = RX_MAX_BURST;
}

static const struct snd_dmaengine_pcm_config cv1800b_i2s_pcm_config = {
        .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};

static void cv1800b_reset_fifo(struct cv1800b_i2s *i2s)
{
        u32 val;

        val = readl(i2s->base + CV1800B_FIFO_RESET);
        val = u32_replace_bits(val, 1, FIFO_RX_RESET_MASK);
        val = u32_replace_bits(val, 1, FIFO_TX_RESET_MASK);
        writel(val, i2s->base + CV1800B_FIFO_RESET);

        usleep_range(10, 20);

        val = readl(i2s->base + CV1800B_FIFO_RESET);
        val = u32_replace_bits(val, 0, FIFO_RX_RESET_MASK);
        val = u32_replace_bits(val, 0, FIFO_TX_RESET_MASK);
        writel(val, i2s->base + CV1800B_FIFO_RESET);
}

static void cv1800b_reset_i2s(struct cv1800b_i2s *i2s)
{
        u32 val;

        val = readl(i2s->base + CV1800B_I2S_RESET);
        val = u32_replace_bits(val, 1, RST_I2S_RESET_RX_MASK);
        val = u32_replace_bits(val, 1, RST_I2S_RESET_TX_MASK);
        writel(val, i2s->base + CV1800B_I2S_RESET);

        usleep_range(10, 20);

        val = readl(i2s->base + CV1800B_I2S_RESET);
        val = u32_replace_bits(val, 0, RST_I2S_RESET_RX_MASK);
        val = u32_replace_bits(val, 0, RST_I2S_RESET_TX_MASK);
        writel(val, i2s->base + CV1800B_I2S_RESET);
}

static void cv1800b_set_mclk_div(struct cv1800b_i2s *i2s, u32 mclk_div)
{
        u32 val;

        val = readl(i2s->base + CV1800B_CLK_CTRL1);
        val = u32_replace_bits(val, mclk_div, CLK_MCLK_DIV_MASK);
        writel(val, i2s->base + CV1800B_CLK_CTRL1);
        dev_dbg(i2s->dev, "mclk_div is set to %u\n", mclk_div);
}

static void cv1800b_set_tx_mode(struct cv1800b_i2s *i2s, bool is_tx)
{
        u32 val;

        val = readl(i2s->base + CV1800B_BLK_MODE_SETTING);
        val = u32_replace_bits(val, is_tx, BLK_TX_MODE_MASK);
        writel(val, i2s->base + CV1800B_BLK_MODE_SETTING);
        dev_dbg(i2s->dev, "tx_mode is set to %u\n", is_tx);
}

static int cv1800b_set_bclk_div(struct cv1800b_i2s *i2s, u32 bclk_div)
{
        u32 val;

        if (bclk_div == 0 || bclk_div > 0xFFFF)
                return -EINVAL;

        val = readl(i2s->base + CV1800B_CLK_CTRL1);
        val = u32_replace_bits(val, bclk_div, CLK_BCLK_DIV_MASK);
        writel(val, i2s->base + CV1800B_CLK_CTRL1);
        dev_dbg(i2s->dev, "bclk_div is set to %u\n", bclk_div);
        return 0;
}

/* set memory width of audio data , reg word_length */
static int cv1800b_set_word_length(struct cv1800b_i2s *i2s,
                                   unsigned int physical_width)
{
        u8 word_length_val;
        u32 val;

        switch (physical_width) {
        case 8:
                word_length_val = CV1800B_WORD_LENGTH_8_BIT;
                break;
        case 16:
                word_length_val = CV1800B_WORD_LENGTH_16_BIT;
                break;
        case 32:
                word_length_val = CV1800B_WORD_LENGTH_32_BIT;
                break;
        default:
                dev_dbg(i2s->dev, "can't set word_length field\n");
                return -EINVAL;
        }

        val = readl(i2s->base + CV1800B_DATA_FORMAT);
        val = u32_replace_bits(val, word_length_val, DF_WORD_LENGTH_MASK);
        writel(val, i2s->base + CV1800B_DATA_FORMAT);
        return 0;
}

static void cv1800b_enable_clocks(struct cv1800b_i2s *i2s, bool enabled)
{
        u32 val;

        val = readl(i2s->base + CV1800B_CLK_CTRL0);
        val = u32_replace_bits(val, enabled, CLK_AUD_EN_MASK);
        writel(val, i2s->base + CV1800B_CLK_CTRL0);
}

static int cv1800b_set_slot_settings(struct cv1800b_i2s *i2s, u32 slots,
                                     u32 physical_width, u32 data_size)
{
        u32 slot_num;
        u32 slot_size;
        u32 frame_length;
        u32 frame_active_length;
        u32 val;

        if (!slots || !physical_width || !data_size) {
                dev_err(i2s->dev, "frame or slot settings are not valid\n");
                return -EINVAL;
        }
        if (slots > 16 || physical_width > 64 || data_size > 32) {
                dev_err(i2s->dev, "frame or slot settings are not valid\n");
                return -EINVAL;
        }

        slot_num = slots - 1;
        slot_size = physical_width - 1;
        frame_length = (physical_width * slots) - 1;
        frame_active_length = physical_width - 1;

        if (frame_length > 511 || frame_active_length > 255) {
                dev_err(i2s->dev, "frame or slot settings are not valid\n");
                return -EINVAL;
        }

        val = readl(i2s->base + CV1800B_SLOT_SETTING1);
        val = u32_replace_bits(val, slot_size, SLOT_SIZE_MASK);
        val = u32_replace_bits(val, data_size - 1, DATA_SIZE_MASK);
        val = u32_replace_bits(val, slot_num, SLOT_NUM_MASK);
        writel(val, i2s->base + CV1800B_SLOT_SETTING1);

        val = readl(i2s->base + CV1800B_FRAME_SETTING);
        val = u32_replace_bits(val, frame_length, FRAME_LENGTH_MASK);
        val = u32_replace_bits(val, frame_active_length, FS_ACTIVE_LENGTH_MASK);
        writel(val, i2s->base + CV1800B_FRAME_SETTING);

        dev_dbg(i2s->dev, "slot settings num: %u width: %u\n", slots, physical_width);
        return 0;
}

/*
 * calculate mclk_div.
 * if requested value is bigger than optimal
 * leave mclk_div as 1. cff clock is capable
 * to handle it
 */
static int cv1800b_calc_mclk_div(unsigned int target_mclk, u32 *mclk_div)
{
        *mclk_div = 1;

        if (target_mclk == 0)
                return -EINVAL;

        /* optimal parent frequency is close to CV1800B_DEF_FREQ */
        if (target_mclk < CV1800B_DEF_FREQ) {
                *mclk_div = DIV_ROUND_CLOSEST(CV1800B_DEF_FREQ, target_mclk);
                if (!*mclk_div || *mclk_div > 0xFFFF)
                        return -EINVAL;
        }
        return 0;
}

/*
 * set CCF clock and divider for this clock
 * mclk_clock = ccf_clock / mclk_div
 */
static int cv1800b_i2s_set_rate_for_mclk(struct cv1800b_i2s *i2s,
                                         unsigned int target_mclk)
{
        u32 mclk_div = 1;
        u64 tmp;
        int ret;
        unsigned long clk_rate;
        unsigned long actual;

        ret = cv1800b_calc_mclk_div(target_mclk, &mclk_div);
        if (ret) {
                dev_dbg(i2s->dev, "can't calc mclk_div for freq %u\n",
                        target_mclk);
                return ret;
        }

        tmp = (u64)target_mclk * mclk_div;
        if (tmp > ULONG_MAX) {
                dev_err(i2s->dev, "clk_rate overflow: freq=%u div=%u\n",
                        target_mclk, mclk_div);
                return -ERANGE;
        }

        clk_rate = (unsigned long)tmp;

        cv1800b_enable_clocks(i2s, false);

        ret = clk_set_rate(i2s->sysclk, clk_rate);
        if (ret)
                return ret;

        actual = clk_get_rate(i2s->sysclk);
        if (clk_rate != actual) {
                dev_err_ratelimited(i2s->dev,
                                    "clk_set_rate failed %lu, actual is %lu\n",
                                    clk_rate, actual);
        }

        cv1800b_set_mclk_div(i2s, mclk_div);
        cv1800b_enable_clocks(i2s, true);

        return 0;
}

static int cv1800b_i2s_hw_params(struct snd_pcm_substream *substream,
                                 struct snd_pcm_hw_params *params,
                                 struct snd_soc_dai *dai)
{
        struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai);
        unsigned int rate = params_rate(params);
        unsigned int channels = params_channels(params);
        unsigned int physical_width = params_physical_width(params);
        int data_width = params_width(params);
        bool tx_mode = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 1 : 0;
        int ret;
        u32 bclk_div;
        u32 bclk_ratio;
        u32 mclk_rate;
        u32 tmp;

        if (data_width < 0)
                return data_width;

        if (!channels || !rate || !physical_width)
                return -EINVAL;

        ret = cv1800b_set_slot_settings(i2s, channels, physical_width, data_width);
        if (ret)
                return ret;

        if (i2s->mclk_rate) {
                mclk_rate = i2s->mclk_rate;
        } else {
                dev_dbg(i2s->dev, "mclk is not set by machine driver\n");
                ret = cv1800b_i2s_set_rate_for_mclk(i2s,
                                                    rate * CV1800B_DEF_MCLK_FS_RATIO);
                if (ret)
                        return ret;
                mclk_rate = rate * CV1800B_DEF_MCLK_FS_RATIO;
        }

        bclk_ratio = (i2s->bclk_ratio_fixed) ? i2s->bclk_ratio :
                                               (physical_width * channels);

        if (check_mul_overflow(rate, bclk_ratio, &tmp))
                return -EOVERFLOW;

        if (!tmp)
                return -EINVAL;
        if (mclk_rate % tmp)
                dev_warn(i2s->dev, "mclk rate is not aligned to bclk or rate\n");

        bclk_div = DIV_ROUND_CLOSEST(mclk_rate, tmp);

        ret = cv1800b_set_bclk_div(i2s, bclk_div);
        if (ret)
                return ret;

        ret = cv1800b_set_word_length(i2s, physical_width);
        if (ret)
                return ret;

        cv1800b_set_tx_mode(i2s, tx_mode);

        cv1800b_reset_fifo(i2s);
        cv1800b_reset_i2s(i2s);
        return 0;
}

static int cv1800b_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
                               struct snd_soc_dai *dai)
{
        struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai);
        u32 val;

        val = readl(i2s->base + CV1800B_I2S_ENABLE);

        switch (cmd) {
        case SNDRV_PCM_TRIGGER_START:
        case SNDRV_PCM_TRIGGER_RESUME:
        case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
                val = u32_replace_bits(val, 1, I2S_ENABLE_MASK);
                break;

        case SNDRV_PCM_TRIGGER_STOP:
        case SNDRV_PCM_TRIGGER_SUSPEND:
        case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
                val = u32_replace_bits(val, 0, I2S_ENABLE_MASK);
                break;
        default:
                return -EINVAL;
        }
        writel(val, i2s->base + CV1800B_I2S_ENABLE);
        return 0;
}

static int cv1800b_i2s_startup(struct snd_pcm_substream *substream,
                               struct snd_soc_dai *dai)
{
        struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
        struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai);
        struct snd_soc_dai_link *dai_link = rtd->dai_link;

        dev_dbg(i2s->dev, "%s: dai=%s substream=%d\n", __func__, dai->name,
                substream->stream);
        /**
         * Ensure DMA is stopped before DAI
         * shutdown (prevents DW AXI DMAC stop/busy on next open).
         */
        dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
        return 0;
}

static int cv1800b_i2s_dai_probe(struct snd_soc_dai *dai)
{
        struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai);

        if (!i2s) {
                dev_err(dai->dev, "no drvdata in DAI probe\n");
                return -ENODEV;
        }

        snd_soc_dai_init_dma_data(dai, &i2s->playback_dma, &i2s->capture_dma);
        return 0;
}

static int cv1800b_i2s_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
        struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai);
        u32 val;
        u32 master;

        /* only i2s format is supported */
        if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S)
                return -EINVAL;

        switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
        case SND_SOC_DAIFMT_CBP_CFP:
                dev_dbg(i2s->dev, "set to master mode\n");
                master = 1;
                break;

        case SND_SOC_DAIFMT_CBC_CFC:
                dev_dbg(i2s->dev, "set to slave mode\n");
                master = 0;
                break;
        default:
                return -EINVAL;
        }

        val = readl(i2s->base + CV1800B_BLK_MODE_SETTING);
        val = u32_replace_bits(val, master, BLK_MASTER_MODE_MASK);
        writel(val, i2s->base + CV1800B_BLK_MODE_SETTING);
        return 0;
}

static int cv1800b_i2s_dai_set_bclk_ratio(struct snd_soc_dai *dai,
                                          unsigned int ratio)
{
        struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai);

        if (ratio == 0)
                return -EINVAL;
        i2s->bclk_ratio = ratio;
        i2s->bclk_ratio_fixed = true;
        return 0;
}

static int cv1800b_i2s_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
                                      unsigned int freq, int dir)
{
        struct cv1800b_i2s *i2s = snd_soc_dai_get_drvdata(dai);
        int ret;
        u32 val;
        bool output_enable = (dir == SND_SOC_CLOCK_OUT) ? true : false;

        dev_dbg(i2s->dev, "%s called with %u\n", __func__, freq);
        ret = cv1800b_i2s_set_rate_for_mclk(i2s, freq);
        if (ret)
                return ret;

        val = readl(i2s->base + CV1800B_CLK_CTRL0);
        val = u32_replace_bits(val, output_enable, CLK_MCLK_OUT_EN_MASK);
        writel(val, i2s->base + CV1800B_CLK_CTRL0);

        i2s->mclk_rate = freq;
        return 0;
}

static const struct snd_soc_dai_ops cv1800b_i2s_dai_ops = {
        .probe = cv1800b_i2s_dai_probe,
        .startup = cv1800b_i2s_startup,
        .hw_params = cv1800b_i2s_hw_params,
        .trigger = cv1800b_i2s_trigger,
        .set_fmt = cv1800b_i2s_dai_set_fmt,
        .set_bclk_ratio = cv1800b_i2s_dai_set_bclk_ratio,
        .set_sysclk = cv1800b_i2s_dai_set_sysclk,
};

static const struct snd_soc_dai_driver cv1800b_i2s_dai_template = {
        .name = "cv1800b-i2s",
        .playback = {
                .stream_name = "Playback",
                .channels_min = 1,
                .channels_max = 2,
                .rates = SNDRV_PCM_RATE_8000_192000,
                .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
        },
        .capture = {
                .stream_name = "Capture",
                .channels_min = 1,
                .channels_max = 2,
                .rates = SNDRV_PCM_RATE_8000_192000,
                .formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE,
        },
        .ops = &cv1800b_i2s_dai_ops,
};

static const struct snd_soc_component_driver cv1800b_i2s_component = {
        .name = "cv1800b-i2s",
};

static void cv1800b_i2s_hw_disable(struct cv1800b_i2s *i2s)
{
        u32 val;

        val = readl(i2s->base + CV1800B_I2S_ENABLE);
        val = u32_replace_bits(val, 0, I2S_ENABLE_MASK);
        writel(val, i2s->base + CV1800B_I2S_ENABLE);

        val = readl(i2s->base + CV1800B_CLK_CTRL0);
        val = u32_replace_bits(val, 0, CLK_AUD_EN_MASK);
        val = u32_replace_bits(val, 0, CLK_MCLK_OUT_EN_MASK);
        writel(val, i2s->base + CV1800B_CLK_CTRL0);

        val = readl(i2s->base + CV1800B_I2S_RESET);
        val = u32_replace_bits(val, 1, RST_I2S_RESET_RX_MASK);
        val = u32_replace_bits(val, 1, RST_I2S_RESET_TX_MASK);
        writel(val, i2s->base + CV1800B_I2S_RESET);

        val = readl(i2s->base + CV1800B_FIFO_RESET);
        val = u32_replace_bits(val, 1, FIFO_RX_RESET_MASK);
        val = u32_replace_bits(val, 1, FIFO_TX_RESET_MASK);
        writel(val, i2s->base + CV1800B_FIFO_RESET);
}

static void cv1800b_i2s_setup_tdm(struct cv1800b_i2s *i2s)
{
        u32 val;

        val = readl(i2s->base + CV1800B_BLK_MODE_SETTING);
        val = u32_replace_bits(val, 1, BLK_DMA_MODE_MASK);
        writel(val, i2s->base + CV1800B_BLK_MODE_SETTING);

        val = readl(i2s->base + CV1800B_CLK_CTRL0);
        val = u32_replace_bits(val, 0, CLK_AUD_CLK_SEL_MASK);
        val = u32_replace_bits(val, 0, CLK_MCLK_OUT_EN_MASK);
        val = u32_replace_bits(val, 0, CLK_AUD_EN_MASK);
        writel(val, i2s->base + CV1800B_CLK_CTRL0);

        val = readl(i2s->base + CV1800B_FIFO_THRESHOLD);
        val = u32_replace_bits(val, 4, FIFO_RX_THRESHOLD_MASK);
        val = u32_replace_bits(val, 4, FIFO_TX_THRESHOLD_MASK);
        val = u32_replace_bits(val, 4, FIFO_TX_HIGH_THRESHOLD_MASK);
        writel(val, i2s->base + CV1800B_FIFO_THRESHOLD);

        val = readl(i2s->base + CV1800B_I2S_ENABLE);
        val = u32_replace_bits(val, 0, I2S_ENABLE_MASK);
        writel(val, i2s->base + CV1800B_I2S_ENABLE);
}

static int cv1800b_i2s_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct cv1800b_i2s *i2s;
        struct resource *res;
        void __iomem *regs;
        struct snd_soc_dai_driver *dai;
        int ret;

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

        regs = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(regs))
                return PTR_ERR(regs);
        i2s->dev = &pdev->dev;
        i2s->base = regs;

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res)
                return -ENODEV;
        cv1800b_setup_dma_struct(i2s, res->start);

        i2s->clk = devm_clk_get_enabled(dev, "i2s");
        if (IS_ERR(i2s->clk))
                return dev_err_probe(dev, PTR_ERR(i2s->clk),
                                     "failed to get+enable i2s\n");
        i2s->sysclk = devm_clk_get_enabled(dev, "mclk");
        if (IS_ERR(i2s->sysclk))
                return dev_err_probe(dev, PTR_ERR(i2s->sysclk),
                                     "failed to get+enable mclk\n");

        platform_set_drvdata(pdev, i2s);
        cv1800b_i2s_setup_tdm(i2s);

        dai = devm_kmemdup(dev, &cv1800b_i2s_dai_template, sizeof(*dai),
                           GFP_KERNEL);
        if (!dai)
                return -ENOMEM;

        ret = devm_snd_soc_register_component(dev, &cv1800b_i2s_component, dai,
                                              1);
        if (ret)
                return ret;

        ret = devm_snd_dmaengine_pcm_register(dev, &cv1800b_i2s_pcm_config, 0);
        if (ret) {
                dev_err(dev, "dmaengine_pcm_register failed: %d\n", ret);
                return ret;
        }

        return 0;
}

static void cv1800b_i2s_remove(struct platform_device *pdev)
{
        struct cv1800b_i2s *i2s = platform_get_drvdata(pdev);

        if (!i2s)
                return;
        cv1800b_i2s_hw_disable(i2s);
}

static const struct of_device_id cv1800b_i2s_of_match[] = {
        { .compatible = "sophgo,cv1800b-i2s" },
        { /* sentinel */ }
};

MODULE_DEVICE_TABLE(of, cv1800b_i2s_of_match);

static struct platform_driver cv1800b_i2s_driver = {
        .probe = cv1800b_i2s_probe,
        .remove = cv1800b_i2s_remove,
        .driver = {
                .name = "cv1800b-i2s",
                .of_match_table = cv1800b_i2s_of_match,
        },
};
module_platform_driver(cv1800b_i2s_driver);

MODULE_DESCRIPTION("Sophgo cv1800b I2S/TDM driver");
MODULE_AUTHOR("Anton D. Stavinsky <stavinsky@gmail.com>");
MODULE_LICENSE("GPL");