Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Anton D. Stavinskii 3295 100.00% 1 100.00%
Total 3295 1


// 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");