#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
#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
#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
#define BLK_TX_MODE_MASK GENMASK(0, 0)
#define BLK_MASTER_MODE_MASK GENMASK(1, 1)
#define BLK_DMA_MODE_MASK GENMASK(7, 7)
#define CLK_MCLK_DIV_MASK GENMASK(15, 0)
#define CLK_BCLK_DIV_MASK GENMASK(31, 16)
#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)
#define RST_I2S_RESET_RX_MASK GENMASK(0, 0)
#define RST_I2S_RESET_TX_MASK GENMASK(1, 1)
#define FIFO_RX_RESET_MASK GENMASK(0, 0)
#define FIFO_TX_RESET_MASK GENMASK(16, 16)
#define I2S_ENABLE_MASK GENMASK(0, 0)
#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)
#define FRAME_LENGTH_MASK GENMASK(8, 0)
#define FS_ACTIVE_LENGTH_MASK GENMASK(23, 16)
#define INT_I2S_INT_EN_MASK GENMASK(8, 8)
#define SLOT_EN_MASK GENMASK(15, 0)
#define LRCK_MASTER_ENABLE_MASK GENMASK(0, 0)
#define DF_WORD_LENGTH_MASK GENMASK(2, 1)
#define DF_TX_SOURCE_LEFT_ALIGN_MASK GENMASK(6, 6)
#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)
#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;
}
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;
}
static int cv1800b_calc_mclk_div(unsigned int target_mclk, u32 *mclk_div)
{
*mclk_div = 1;
if (target_mclk == 0)
return -EINVAL;
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;
}
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);
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;
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" },
{ }
};
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");