Files
MilkV-Duo/linux_5.10/sound/soc/cvitek/cv1835_i2s.c
forum_service 7e1529d0dc linux-5.10: version release v4.1.0.3
0debf7abc [dma] When using DMA, if the DMA addr spans 128MB boundary   we have to split the DMA transfer into two so that each one     doesn't exceed the boundary.
4f7ed7449 Merge "[eth] change rxterm and vcm to link DianXin router" into v4.1.0
feb60bba5 Merge "[audio][lt9611]add drivers" into v4.1.0
a3dde371c [eth] change rxterm and vcm to link DianXin router
e615d3a43 [audio][lt9611]add drivers

Change-Id: If4fdcbc32df6aeaed04da0efbd23455376e2a53c
2023-12-21 16:51:57 +08:00

1356 lines
42 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* I2S driver on CVITEK CV1835
*
* Copyright 2018 CVITEK
*
* Author: EthanChen
*
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <sound/cv1835_i2s.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include "local.h"
#include "cv1835_i2s_subsys.h"
#include <linux/version.h>
struct proc_dir_entry *proc_audio_dir;
static int cvi_i2s_suspend(struct snd_soc_dai *dai);
static int cvi_i2s_resume(struct snd_soc_dai *dai);
static inline void i2s_write_reg(void __iomem *io_base, int reg, u32 val)
{
writel(val, io_base + reg);
}
static inline u32 i2s_read_reg(void __iomem *io_base, int reg)
{
return readl(io_base + reg);
}
static inline void i2s_clear_irqs(struct cvi_i2s_dev *dev, u32 stream)
{
u32 irq = i2s_read_reg(dev->i2s_base, I2S_INT);
/* I2S_INT is write 1 clear */
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
i2s_write_reg(dev->i2s_base, I2S_INT,
irq & (I2S_INT_TXDA | I2S_INT_TXFO | I2S_INT_TXFU
| I2S_INT_TXDA_RAW | I2S_INT_TXFO_RAW | I2S_INT_TXFU_RAW));
else
i2s_write_reg(dev->i2s_base, I2S_INT,
irq & (I2S_INT_RXDA | I2S_INT_RXFO | I2S_INT_RXFU
| I2S_INT_RXDA_RAW | I2S_INT_RXFO_RAW | I2S_INT_RXFU_RAW));
}
static inline void i2s_disable_irqs(struct cvi_i2s_dev *dev, u32 stream)
{
u32 irq = i2s_read_reg(dev->i2s_base, I2S_INT_EN);
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
i2s_write_reg(dev->i2s_base, I2S_INT_EN, irq & ~(I2S_INT_TXDA | I2S_INT_TXFO | I2S_INT_TXFU));
else
i2s_write_reg(dev->i2s_base, I2S_INT_EN, irq & ~(I2S_INT_RXDA | I2S_INT_RXFO | I2S_INT_RXFU));
}
static inline void i2s_enable_irqs(struct cvi_i2s_dev *dev, u32 stream)
{
u32 irq = i2s_read_reg(dev->i2s_base, I2S_INT_EN);
if (stream == SNDRV_PCM_STREAM_PLAYBACK)
i2s_write_reg(dev->i2s_base, I2S_INT_EN, irq | I2S_INT_TXFO | I2S_INT_TXFU);
else
i2s_write_reg(dev->i2s_base, I2S_INT_EN, irq | I2S_INT_RXFO | I2S_INT_RXFU);
}
static void i2s_fifo_reset(struct cvi_i2s_dev *dev, u32 stream)
{
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
i2s_write_reg(dev->i2s_base, FIFO_RESET, TX_FIFO_RESET_PULL_UP);
i2s_write_reg(dev->i2s_base, FIFO_RESET, TX_FIFO_RESET_PULL_DOWN);
} else { /* reset RX*/
i2s_write_reg(dev->i2s_base, FIFO_RESET, RX_FIFO_RESET_PULL_UP);
i2s_write_reg(dev->i2s_base, FIFO_RESET, RX_FIFO_RESET_PULL_DOWN);
}
}
#define I2S_RETRY_COUNT 30000
static void i2s_reset(struct cvi_i2s_dev *dev, u32 stream)
{
u32 retry = 0;
dev_dbg(dev->dev, "blk_mode=0x%08x, clk_ctrl=0x%08x, i2s_enable=0x%08x\n",
i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING),
i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0),
i2s_read_reg(dev->i2s_base, I2S_ENABLE));
if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
i2s_write_reg(dev->i2s_base, I2S_RESET, I2S_RESET_TX_PULL_UP);
while (!((i2s_read_reg(dev->i2s_base, TX_STATUS) & RESET_TX_SCLK) >> 23)) {
if ((retry++) > I2S_RETRY_COUNT)
break;
}
if (retry > I2S_RETRY_COUNT)
dev_err(dev->dev, "WARNING!!! I2S TX RESET failed\n");
i2s_write_reg(dev->i2s_base, I2S_RESET, I2S_RESET_TX_PULL_DOWN);
} else { /* reset RX*/
i2s_write_reg(dev->i2s_base, I2S_RESET, I2S_RESET_RX_PULL_UP);
while (!((i2s_read_reg(dev->i2s_base, RX_STATUS) & RESET_RX_SCLK) >> 23)) {
if ((retry++) > I2S_RETRY_COUNT)
break;
}
if (retry > I2S_RETRY_COUNT)
dev_err(dev->dev, "WARNING!!! I2S RX RESET failed\n");
i2s_write_reg(dev->i2s_base, I2S_RESET, I2S_RESET_TX_PULL_DOWN);
}
}
static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
{
struct cvi_i2s_dev *dev = dev_id;
u32 val = 0;
val = i2s_read_reg(dev->i2s_base, I2S_INT);
if (dev->active >= 1) { /* If I2S is really active */
if (val & (I2S_INT_RXFO | I2S_INT_RXFU)) {
dev_dbg(dev->dev, "WARNING!!! I2S RX FIFO exception occur int_status=0x%x\n", val);
i2s_write_reg(dev->i2s_base, I2S_ENABLE, I2S_OFF);
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0,
(i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0) | AUD_ENABLE));
i2s_fifo_reset(dev, SNDRV_PCM_STREAM_CAPTURE);
i2s_reset(dev, SNDRV_PCM_STREAM_CAPTURE);
i2s_write_reg(dev->i2s_base, I2S_ENABLE, I2S_ON);
} else if (val & (I2S_INT_TXFO | I2S_INT_TXFU)) {
dev_dbg(dev->dev, "WARNING!!! I2S TX FIFO exception occur int_status=0x%x\n", val);
i2s_write_reg(dev->i2s_base, I2S_ENABLE, I2S_OFF);
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0,
(i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0) | AUD_ENABLE));
i2s_fifo_reset(dev, SNDRV_PCM_STREAM_PLAYBACK);
i2s_reset(dev, SNDRV_PCM_STREAM_PLAYBACK);
i2s_write_reg(dev->i2s_base, I2S_ENABLE, I2S_ON);
}
}
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_PLAYBACK);
i2s_clear_irqs(dev, SNDRV_PCM_STREAM_CAPTURE);
return IRQ_HANDLED;
}
static void i2s_start(struct cvi_i2s_dev *dev,
struct snd_pcm_substream *substream)
{
u32 i2s_enable = i2s_read_reg(dev->i2s_base, I2S_ENABLE);
u32 clk_ctrl = i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0);
u32 blk_mode_setting = i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING);
if (!strcmp(substream->pcm->card->shortname, "cvi_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182x_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_adc")) {
if ((clk_ctrl & AUD_ENABLE) != AUD_ENABLE)
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, clk_ctrl | AUD_ENABLE);
} else {
if (((blk_mode_setting & ROLE_MASK) == MASTER_MODE) && ((clk_ctrl & AUD_ENABLE) != AUD_ENABLE))
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, clk_ctrl | AUD_ENABLE);
}
i2s_fifo_reset(dev, substream->stream);
i2s_reset(dev, substream->stream);
i2s_clear_irqs(dev, substream->stream);
i2s_enable_irqs(dev, substream->stream);
if (i2s_enable == I2S_OFF) {
//i2s_subsys_switch(dev->dev_id, I2S_ON);
i2s_write_reg(dev->i2s_base, I2S_ENABLE, I2S_ON);
udelay(10);
} else
dev_err(dev->dev, "WARNING!!! I2S SHOULD NOT be in ON state\n");
dev_dbg(dev->dev,
"blk_mode=0x%08x, clk_ctrl=0x%08x, int_en=0x%08x, frame_setting=0x%08x, slot_setting=0x%08x, data_format=0x%08x\n",
i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING),
i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0),
i2s_read_reg(dev->i2s_base, I2S_INT_EN),
i2s_read_reg(dev->i2s_base, FRAME_SETTING),
i2s_read_reg(dev->i2s_base, SLOT_SETTING1),
i2s_read_reg(dev->i2s_base, DATA_FORMAT));
}
static void i2s_stop(struct cvi_i2s_dev *dev,
struct snd_pcm_substream *substream)
{
u32 i2s_enable = i2s_read_reg(dev->i2s_base, I2S_ENABLE);
/* Don't to disalbe AUD_ENABLE due to external codec still need MCLK to do configuration */
/* Don't to disalbe AUD_ENABLE due to external codec still need MCLK to do configuration */
i2s_disable_irqs(dev, substream->stream);
if (dev->mclk_out == false) {
u32 blk_mode_setting = i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING);
u32 clk_ctrl = i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0);
if (((blk_mode_setting & ROLE_MASK) == MASTER_MODE) && ((clk_ctrl & AUD_ENABLE) == AUD_ENABLE)) {
dev_dbg(dev->dev, "Disable aud_en\n");
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, clk_ctrl & ~(AUD_ENABLE));
}
}
if (i2s_enable == I2S_ON) {
//i2s_subsys_switch(dev->dev_id, I2S_OFF);
i2s_write_reg(dev->i2s_base, I2S_ENABLE, I2S_OFF);
i2s_fifo_reset(dev, substream->stream);
} else
dev_err(dev->dev, "WARNING!!! I2S SHOULD NOT be in OFF state\n");
}
static int cvi_i2s_dai_probe(struct snd_soc_dai *cpu_dai)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
dev_dbg(cpu_dai->dev, "%s start *cpu_dai = %p name = %s\n", __func__, cpu_dai, cpu_dai->name);
cpu_dai->playback_dma_data = &dev->play_dma_data;
cpu_dai->capture_dma_data = &dev->capture_dma_data;
if (cpu_dai->playback_dma_data == NULL) {
dev_err(cpu_dai->dev, "%s playback_dma_data == NULL\n", __func__);
}
if (cpu_dai->capture_dma_data == NULL) {
dev_err(cpu_dai->dev, "%s capture_dma_data == NULL\n", __func__);
}
dev_dbg(cpu_dai->dev, "%s end cpu_dai->playback_dma_data = %p\n", __func__, cpu_dai->playback_dma_data);
return 0;
}
static int cvi_i2s_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *cpu_dai)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
union cvi_i2s_snd_dma_data *dma_data = NULL;
dev_dbg(dev->dev, "%s start *cpu_dai = %p name = %s\n", __func__, cpu_dai, cpu_dai->name);
if (!(dev->capability & CVI_I2S_RECORD) &&
(substream->stream == SNDRV_PCM_STREAM_CAPTURE)) {
dev_dbg(dev->dev, "%s return -EINVAL;\n", __func__);
return -EINVAL;
}
if (!(dev->capability & CVI_I2S_PLAY) &&
(substream->stream == SNDRV_PCM_STREAM_PLAYBACK)) {
dev_dbg(dev->dev, "%s return -EINVAL; 2\n", __func__);
return -EINVAL;
}
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = &dev->play_dma_data;
else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
dma_data = &dev->capture_dma_data;
if (dma_data == NULL) {
dev_dbg(dev->dev, "%s dma_data == NULL\n", __func__);
}
dev_dbg(dev->dev, "%s start *dma_data = %p\n", __func__, dma_data);
snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
dev_dbg(dev->dev, "%s end cpu_dai->playback_dma_data = %p\n",
__func__, cpu_dai->playback_dma_data);
return 0;
}
static void cvi_i2s_config(struct cvi_i2s_dev *dev, int stream)
{
u32 blk_mode_setting = i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING) & ~(DMA_MODE_MASK);
/* Configure to USE HW DMA*/
i2s_write_reg(dev->i2s_base, BLK_MODE_SETTING, blk_mode_setting | HW_DMA_MODE);
/* Configure FIFO thresholds */
i2s_write_reg(dev->i2s_base, FIFO_THRESHOLD,
RX_FIFO_THRESHOLD(7) | TX_FIFO_THRESHOLD(7) | TX_FIFO_HIGH_THRESHOLD(31));
}
static int cvi_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
struct i2s_clk_config_data *config = &dev->config;
u32 frame_setting = i2s_read_reg(dev->i2s_base, FRAME_SETTING) & ~(FRAME_LENGTH_MASK | FS_ACT_LENGTH_MASK);
u32 slot_setting1 = i2s_read_reg(dev->i2s_base, SLOT_SETTING1) & ~(SLOT_SIZE_MASK | DATA_SIZE_MASK);
u32 clk_ctrl1 = 0;
u32 data_format = i2s_read_reg(dev->i2s_base, DATA_FORMAT) & ~(WORD_LENGTH_MASK | SKIP_TX_INACT_SLOT_MASK);
u32 audio_clk = 0;
u32 mclk_div = 0;
u32 bclk_div = 0;
config->chan_nr = params_channels(params);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
case SNDRV_PCM_FORMAT_U16_LE:
config->data_size = 16;
dev->wss = WSS_16_CLKCYCLE;
switch (dev->mode) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_PDM:
if (!strcmp(substream->pcm->card->shortname, "cvi_dac")) {
frame_setting |= FRAME_LENGTH(64) | FS_ACT_LENGTH(32);
dev->wss = WSS_32_CLKCYCLE;
} else if (!strcmp(substream->pcm->card->shortname, "cv182x_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182x_dac") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_dac")) {
if (!strcmp(substream->pcm->card->shortname, "cv182x_dac") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_dac")) {
/* For cv182x and cv182xa DAC codec, while playing with mono audio data,
* need to assume there are 2 channels but skip 1. Thus, need
* to set frame length as 32, slot_num as 2, slot_en as 1 and
* skip tx inactivate slot. I2S will duplucate 16 bits into
* another skiped channel
*/
switch (config->chan_nr) {
case 1:
frame_setting |= FRAME_LENGTH(32) | FS_ACT_LENGTH(16);
break;
case 2:
default:
frame_setting |= FRAME_LENGTH(16) | FS_ACT_LENGTH(16);
break;
}
} else
frame_setting |= FRAME_LENGTH(16) | FS_ACT_LENGTH(16);
} else
frame_setting |= FRAME_LENGTH(32) | FS_ACT_LENGTH(16);
break;
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
frame_setting |= FRAME_LENGTH(config->data_size * config->chan_nr) | FS_ACT_LENGTH(1);
break;
}
//i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1 | SLOT_SIZE(16) | DATA_SIZE(16));
if (!strcmp(substream->pcm->card->shortname, "cvi_dac"))
slot_setting1 |= SLOT_SIZE(32) | DATA_SIZE(16);
else
slot_setting1 |= SLOT_SIZE(16) | DATA_SIZE(16);
break;
case SNDRV_PCM_FORMAT_S24_LE:
case SNDRV_PCM_FORMAT_U24_LE:
case SNDRV_PCM_FORMAT_S24_3LE:
case SNDRV_PCM_FORMAT_U24_3LE:
if (!strcmp(substream->pcm->card->shortname, "cv182x_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182x_dac") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_dac")) {
dev_err(dev->dev, "24 bit resolution is not supported\n");
return -EINVAL;
}
config->data_size = 24;
dev->wss = WSS_32_CLKCYCLE;
switch (dev->mode) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_PDM:
frame_setting |= FRAME_LENGTH(64) | FS_ACT_LENGTH(32);
break;
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
frame_setting |= FRAME_LENGTH(config->data_size * config->chan_nr) | FS_ACT_LENGTH(1);
break;
}
//i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1 | SLOT_SIZE(32) | DATA_SIZE(24));
slot_setting1 |= SLOT_SIZE(32) | DATA_SIZE(24);
break;
case SNDRV_PCM_FORMAT_S32_LE:
case SNDRV_PCM_FORMAT_U32_LE:
if (!strcmp(substream->pcm->card->shortname, "cv182x_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182x_dac") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_dac")
) {
dev_err(dev->dev, "32 bit resolution is not supported\n");
return -EINVAL;
}
config->data_size = 32;
dev->wss = WSS_32_CLKCYCLE;
switch (dev->mode) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_RIGHT_J:
case SND_SOC_DAIFMT_LEFT_J:
case SND_SOC_DAIFMT_PDM:
frame_setting |= FRAME_LENGTH(64) | FS_ACT_LENGTH(32);
break;
case SND_SOC_DAIFMT_DSP_A:
case SND_SOC_DAIFMT_DSP_B:
frame_setting |= FRAME_LENGTH(config->data_size * config->chan_nr) | FS_ACT_LENGTH(1);
break;
}
//i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1 | SLOT_SIZE(32) | DATA_SIZE(32));
slot_setting1 |= SLOT_SIZE(32) | DATA_SIZE(32);
break;
default:
dev_err(dev->dev, "CVI-i2s: unsupported PCM fmt\n");
return -EINVAL;
}
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, FRAME_SETTING, frame_setting);
#if defined(CONFIG_SND_SOC_CV1835_CONCURRENT_I2S)
if ((dev->dev_id != 0) && (dev->dev_id != 3) && (dev->dev_id != i2s_subsys_query_master()))
i2s_set_master_frame_setting(frame_setting);
#endif
slot_setting1 &= ~SLOT_NUM_MASK;
dev_dbg(dev->dev, "CVI-i2s: set slot number=%d\n", config->chan_nr);
switch (config->chan_nr) {
case EIGHT_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(8);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0xff); /* enable slot 0-7 for TDM */
break;
case SIX_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(6);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x3f); /* enable slot 0-5 for TDM */
break;
case FOUR_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(4);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x0f); /* enable slot 0-3 for TDM */
break;
case TWO_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(2);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x03); /* enable slot 0-1 for TDM */
break;
case ONE_CHANNEL_SUPPORT:
if (!strcmp(substream->pcm->card->shortname, "cv182x_dac") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_dac")) {
slot_setting1 |= SLOT_NUM(2);
data_format |= SKIP_TX_INACT_SLOT;
} else
slot_setting1 |= SLOT_NUM(1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x01); /* enable slot 0-3 for TDM */
break;
default:
dev_err(dev->dev, "channel not supported\n");
return -EINVAL;
}
cvi_i2s_config(dev, substream->stream); /* Config use HW DMA and FIFO threshold here */
config->sample_rate = params_rate(params);
//audio_clk = clk_get_rate(dev->clk);
/* set audio_clk depends on audio format */
switch (config->sample_rate) {
case 11025:
case 22050:
case 44100:
case 88200:
audio_clk = CVI_22579_MHZ;
break;
case 8000:
case 16000:
case 32000:
if (!strcmp(substream->pcm->card->shortname, "cv182xa_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_dac"))
audio_clk = CVI_16384_MHZ;
else
audio_clk = CVI_24576_MHZ;
break;
case 12000:
case 24000:
case 48000:
case 96000:
case 192000:
audio_clk = CVI_24576_MHZ;
break;
default:
dev_err(dev->dev, "Warning!!! this sample rate is not supported\n");
return -1;
}
if (strcmp(substream->pcm->card->shortname, "cv182x_adc")) {
/* cv182x adc doesnot need to set apll*/
dev_info(dev->dev, "Audio system clk=%d, sample rate=%d\n", audio_clk, config->sample_rate);
cv1835_set_mclk(audio_clk);
}
if (!strcmp(substream->pcm->card->shortname, "cvi_adc")) {
/* cv183x internal adc codec need dynamic MCLK frequency input */
int div = 1;
switch (config->sample_rate) {
case 8000:
case 11025:
case 12000:
div = audio_clk / (1024 * config->sample_rate);
clk_ctrl1 |= MCLK_DIV(div);
break;
case 16000:
case 22050:
case 24000:
div = audio_clk / (512 * config->sample_rate);
clk_ctrl1 |= MCLK_DIV(div);
break;
case 32000:
case 44100:
case 48000:
div = audio_clk / (256 * config->sample_rate);
clk_ctrl1 |= MCLK_DIV(div);
break;
}
} else if (!strcmp(substream->pcm->card->shortname, "cv182x_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182x_dac")) {
/* cv182x internal adc codec need dynamic MCLK frequency input */
dev_info(dev->dev, "%s set MCLK\n", __func__);
switch (config->sample_rate) {
case 8000:
clk_ctrl1 |= MCLK_DIV(6);
mclk_div = 6;
break;
case 11025:
clk_ctrl1 |= MCLK_DIV(4);
mclk_div = 4;
break;
case 16000:
case 32000:
clk_ctrl1 |= MCLK_DIV(3);
mclk_div = 3;
break;
case 22050:
case 44100:
case 48000:
clk_ctrl1 |= MCLK_DIV(2);
mclk_div = 2;
break;
default:
dev_err(dev->dev, "%s doesn't support this sample rate\n", __func__);
break;
}
} else if (!strcmp(substream->pcm->card->shortname, "cv182xa_adc") ||
!strcmp(substream->pcm->card->shortname, "cv182xa_dac")) {
/* cv182xa internal adc codec need dynamic MCLK frequency input */
switch (config->sample_rate) {
case 8000:
case 16000:
case 32000:
/* apll is 16.384Mhz, no need to divide */
clk_ctrl1 |= MCLK_DIV(1);
mclk_div = 1;
break;
case 11025:
case 22050:
case 44100:
case 48000:
clk_ctrl1 |= MCLK_DIV(2);
mclk_div = 2;
break;
default:
dev_err(dev->dev, "%s doesn't support this sample rate\n", __func__);
break;
}
} else {
if ((audio_clk == CVI_24576_MHZ) || (audio_clk == CVI_22579_MHZ)){
clk_ctrl1 |= MCLK_DIV(2);
mclk_div = 2;
}
else
dev_err(dev->dev, "Get unexpected audio system clk=%d\n", audio_clk);
}
/* Configure I2S word length, bclk_div and sync_div here*/
switch (dev->wss) {
case (WSS_32_CLKCYCLE):
#if defined(CONFIG_ARCH_CV183X_ASIC)
bclk_div = (audio_clk / 1000) / (WSS_32_CLKCYCLE * (config->sample_rate / 1000));
#else
bclk_div = (audio_clk / 1000) / (WSS_32_CLKCYCLE * (config->sample_rate / 1000) * mclk_div);
#endif
if (!strcmp(substream->pcm->card->shortname, "cvi_dac")) {
switch (config->data_size) {
case 32:
case 24:
data_format |= WORD_LEN_32;
break;
case 16:
data_format |= WORD_LEN_16;
break;
}
} else
data_format |= WORD_LEN_32;
break;
case (WSS_24_CLKCYCLE):
#if defined(CONFIG_ARCH_CV183X_ASIC)
bclk_div = (audio_clk / 1000) / (WSS_32_CLKCYCLE * (config->sample_rate / 1000));
#else
bclk_div = (audio_clk / 1000) / (WSS_32_CLKCYCLE * (config->sample_rate / 1000) * mclk_div);
#endif
data_format |= WORD_LEN_32;
break;
case (WSS_16_CLKCYCLE):
#if defined(CONFIG_ARCH_CV183X_ASIC)
bclk_div = (audio_clk / 1000) / (WSS_16_CLKCYCLE * (config->sample_rate / 1000));
#else
bclk_div = (audio_clk / 1000) / (WSS_16_CLKCYCLE * (config->sample_rate / 1000) * mclk_div);
#endif
data_format |= WORD_LEN_16;
break;
default:
dev_err(dev->dev, "resolution not supported\n");
}
/* Configure word length */
i2s_write_reg(dev->i2s_base, DATA_FORMAT, data_format);
clk_ctrl1 |= BCLK_DIV(bclk_div);
dev_dbg(dev->dev, "Set clock ctrl1=0x%08x\n", clk_ctrl1);
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL1, clk_ctrl1);
#if defined(CONFIG_SND_SOC_CV1835_CONCURRENT_I2S)
if ((dev->dev_id != 0) && (dev->dev_id != 3) && (dev->dev_id != i2s_subsys_query_master()))
i2s_set_master_clk(clk_ctrl1);
#endif
dev_dbg(dev->dev, "frame_setting=0x%08x, slot_setting1=0x%08x, clk_ctrl1=0x%08x, data_format=0x%08x\n",
i2s_read_reg(dev->i2s_base, FRAME_SETTING),
i2s_read_reg(dev->i2s_base, SLOT_SETTING1),
i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL1),
i2s_read_reg(dev->i2s_base, DATA_FORMAT));
return 0;
}
static void cvi_i2s_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
pr_info("%s not start *dai = %p, *dai->playback_dma_data = %p\n", __func__, dai, dai->playback_dma_data);
//snd_soc_dai_set_dma_data(dai, substream, NULL);
}
static int cvi_i2s_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
u32 blk_mode_setting = (i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING) & ~(TXRX_MODE_MASK));
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
blk_mode_setting |= TX_MODE;
else
blk_mode_setting |= RX_MODE;
i2s_write_reg(dev->i2s_base, BLK_MODE_SETTING, blk_mode_setting);
return 0;
}
static int cvi_i2s_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
int ret = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
snd_pcm_stream_unlock_irq(substream);
dev->active++;
#if defined(CONFIG_SND_SOC_CV1835_CONCURRENT_I2S)
if ((dev->dev_id != 0) && (dev->dev_id != 3) && (dev->dev_id != i2s_subsys_query_master())) {
dev_dbg(dev->dev, "enable master clk generation\n");
i2s_master_clk_switch_on(true);
}
#endif
cvi_i2s_resume(dai);
i2s_start(dev, substream);
snd_pcm_stream_lock_irq(substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
// snd_pcm_stream_unlock_irq(substream);
dev->active--;
i2s_stop(dev, substream);
#if defined(CONFIG_SND_SOC_CV1835_CONCURRENT_I2S)
if ((dev->dev_id != 0) && (dev->dev_id != 3) && (dev->dev_id != i2s_subsys_query_master())) {
dev_dbg(dev->dev, "disable master clk generation\n");
i2s_master_clk_switch_on(false);
}
#endif
cvi_i2s_suspend(dai);
// snd_pcm_stream_lock_irq(substream);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int cvi_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
u32 blk_mode_setting = i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING) & ~(SAMPLE_EDGE_MASK | ROLE_MASK);
u32 frame_setting = i2s_read_reg(dev->i2s_base, FRAME_SETTING) &
~(FS_POLARITY_MASK | FS_OFFSET_MASK | FS_IDEF_MASK | FS_ACT_LENGTH_MASK);
u32 slot_setting1 = i2s_read_reg(dev->i2s_base, SLOT_SETTING1);/* & ~(SLOT_NUM_MASK);*/
//int role = MASTER_MODE;
int ret = 0;
dev_dbg(dev->dev, "%s, fmt=0x%08x\n", __func__, fmt);
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM: /* Set codec to Master mode, so I2S IP need to be Slave mode */
blk_mode_setting |= SLAVE_MODE;
dev->role = SLAVE_MODE;
break;
case SND_SOC_DAIFMT_CBS_CFS: /* Set codec to Slave mode, so I2S IP need to be Master mode */
blk_mode_setting |= MASTER_MODE;
dev->role = MASTER_MODE;
break;
case SND_SOC_DAIFMT_CBM_CFS:
case SND_SOC_DAIFMT_CBS_CFM:
ret = -EINVAL;
break;
default:
dev_dbg(dev->dev, "cvitek : Invalid master/slave format\n");
ret = -EINVAL;
break;
}
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_NB_NF:
dev->sample_edge = FMT_NB_NF;
blk_mode_setting |= RX_SAMPLE_EDGE_N | TX_SAMPLE_EDGE_P | FS_SAMPLE_EDGE_P;
frame_setting |= FS_ACT_LOW;
break;
case SND_SOC_DAIFMT_NB_IF:
dev->sample_edge = FMT_NB_IF;
blk_mode_setting |= RX_SAMPLE_EDGE_N | TX_SAMPLE_EDGE_P | FS_SAMPLE_EDGE_P;
frame_setting |= FS_ACT_HIGH;
break;
case SND_SOC_DAIFMT_IB_NF:
dev->sample_edge = FMT_IB_NF;
blk_mode_setting |= RX_SAMPLE_EDGE_P | TX_SAMPLE_EDGE_N | FS_SAMPLE_EDGE_N;
frame_setting |= FS_ACT_LOW;
break;
case SND_SOC_DAIFMT_IB_IF:
dev->sample_edge = FMT_IB_IF;
blk_mode_setting |= RX_SAMPLE_EDGE_P | TX_SAMPLE_EDGE_N | FS_SAMPLE_EDGE_N;
frame_setting |= FS_ACT_HIGH;
break;
default:
dev_dbg(dev->dev, "cvitek : Invalid frame format\n");
ret = -EINVAL;
break;
}
i2s_write_reg(dev->i2s_base, BLK_MODE_SETTING, blk_mode_setting);
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_PDM:
dev->mode = SND_SOC_DAIFMT_I2S;
frame_setting |= FS_OFFSET_1_BIT | FS_IDEF_FRAME_SYNC
| FS_ACT_LENGTH(((frame_setting & FRAME_LENGTH_MASK) + 1) / 2);
i2s_write_reg(dev->i2s_base, FRAME_SETTING, frame_setting);
break;
case SND_SOC_DAIFMT_RIGHT_J:
dev->mode = SND_SOC_DAIFMT_RIGHT_J;
frame_setting |= NO_FS_OFFSET | FS_IDEF_FRAME_SYNC
| FS_ACT_LENGTH(((frame_setting & FRAME_LENGTH_MASK) + 1) / 2);
i2s_write_reg(dev->i2s_base, FRAME_SETTING, frame_setting);
slot_setting1 &= ~(FB_OFFSET_MASK);
slot_setting1 |= FB_OFFSET((((frame_setting & FS_ACT_LENGTH_MASK) >> 16)
- ((slot_setting1 & DATA_SIZE_MASK) >> 16)));
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
break;
case SND_SOC_DAIFMT_LEFT_J:
dev->mode = SND_SOC_DAIFMT_LEFT_J;
frame_setting |= NO_FS_OFFSET | FS_IDEF_FRAME_SYNC
| FS_ACT_LENGTH(((frame_setting & FRAME_LENGTH_MASK) + 1) / 2);
i2s_write_reg(dev->i2s_base, FRAME_SETTING, frame_setting);
break;
case SND_SOC_DAIFMT_DSP_A:
dev->mode = SND_SOC_DAIFMT_DSP_A;
frame_setting |= FS_OFFSET_1_BIT | FS_IDEF_FRAME_SYNC | FS_ACT_LENGTH(1);
i2s_write_reg(dev->i2s_base, FRAME_SETTING, frame_setting);
break;
case SND_SOC_DAIFMT_DSP_B:
dev->mode = SND_SOC_DAIFMT_DSP_B;
frame_setting |= NO_FS_OFFSET | FS_IDEF_FRAME_SYNC | FS_ACT_LENGTH(1);
i2s_write_reg(dev->i2s_base, FRAME_SETTING, frame_setting);
break;
default:
dev_dbg(dev->dev, "cvitek : Invalid I2S mode\n");
ret = -EINVAL;
break;
}
return ret;
}
static int cvi_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai, unsigned int tx_mask,
unsigned int rx_mask, int slots, int width)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
u32 slot_setting1 = i2s_read_reg(dev->i2s_base, SLOT_SETTING1) & ~(SLOT_NUM_MASK);
/* Mode other than PDM/TDM mode*/
if (slots == 0) {
/* The other settings dont matter in I2S mode */
return 0;
}
/* We have 16 channels anything outside that is not supported */
if ((tx_mask & ~0xffff) != 0 || (rx_mask & ~0xffff) != 0)
return -EINVAL;
switch (width) {
case 16:
break;
case 32:
break;
default:
return -EINVAL;
}
switch (slots) {
case EIGHT_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(8);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x00ff); /* enable slot 0-7 for TDM */
break;
case SIX_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(6);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x003f); /* enable slot 0-5 for TDM */
break;
case FOUR_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(4);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x000f); /* enable slot 0-3 for TDM */
break;
case TWO_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(2);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x0003); /* enable slot 0-1 for TDM */
break;
case ONE_CHANNEL_SUPPORT:
slot_setting1 |= SLOT_NUM(1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING1, slot_setting1);
i2s_write_reg(dev->i2s_base, SLOT_SETTING2, 0x0001); /* enable slot 0-3 for TDM */
break;
default:
dev_err(dev->dev, "slot number not supported\n");
return -EINVAL;
}
return 0;
}
static struct snd_soc_dai_ops cvi_i2s_dai_ops = {
.startup = cvi_i2s_startup,
.shutdown = cvi_i2s_shutdown,
.hw_params = cvi_i2s_hw_params,
.prepare = cvi_i2s_prepare,
.trigger = cvi_i2s_trigger,
.set_fmt = cvi_i2s_set_fmt,
.set_tdm_slot = cvi_i2s_set_tdm_slot,
};
static const struct snd_soc_component_driver cvi_i2s_component = {
.name = "cvitek-i2s",
};
#ifdef CONFIG_PM
static int cvi_i2s_runtime_suspend(struct device *dev)
{
struct cvi_i2s_dev *cvi_dev = dev_get_drvdata(dev);
if (cvi_dev->capability & CVI_I2S_MASTER)
clk_disable(cvi_dev->clk);
return 0;
}
static int cvi_i2s_runtime_resume(struct device *dev)
{
struct cvi_i2s_dev *cvi_dev = dev_get_drvdata(dev);
if (cvi_dev->capability & CVI_I2S_MASTER)
clk_enable(cvi_dev->clk);
return 0;
}
#endif
static int cvi_i2s_suspend(struct snd_soc_dai *dai)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (dev->capability & CVI_I2S_MASTER)
clk_disable(dev->clk);
return 0;
}
static int cvi_i2s_resume(struct snd_soc_dai *dai)
{
struct cvi_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (dev->capability & CVI_I2S_MASTER)
clk_enable(dev->clk);
return 0;
}
static int cvi_configure_dai(struct cvi_i2s_dev *dev,
struct snd_soc_dai_driver *cvi_i2s_dai,
unsigned int rates)
{
struct device_node *np = dev->dev->of_node;
const char *capability;
if (of_property_read_string(np, "capability", &capability) < 0)
return -EINVAL;
if ((!strcmp(capability, "tx")) || (!strcmp(capability, "txrx"))) {
dev_dbg(dev->dev, "CV: playback support\n");
cvi_i2s_dai->playback.channels_min = 1;
cvi_i2s_dai->playback.channels_max = 8;
cvi_i2s_dai->playback.formats = SNDRV_PCM_FMTBIT_S32_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FORMAT_S24_3LE
| SNDRV_PCM_FMTBIT_U24_LE
| SNDRV_PCM_FORMAT_U24_3LE
| SNDRV_PCM_FMTBIT_S16_LE;
cvi_i2s_dai->playback.rates = rates;
} else {
/* this device doesn't have playback capability */
dev_dbg(dev->dev, "CV: playback not support\n");
cvi_i2s_dai->playback.channels_min = 0;
cvi_i2s_dai->playback.channels_max = 0;
}
if ((!strcmp(capability, "rx")) || (!strcmp(capability, "txrx"))) {
dev_dbg(dev->dev, "CV: capature support\n");
cvi_i2s_dai->capture.channels_min = 1;
cvi_i2s_dai->capture.channels_max = 8;
cvi_i2s_dai->capture.formats = SNDRV_PCM_FMTBIT_S32_LE
| SNDRV_PCM_FMTBIT_S24_LE
| SNDRV_PCM_FMTBIT_S16_LE;
cvi_i2s_dai->capture.rates = rates;
} else {
/* this device doesn't have capature capability */
dev_dbg(dev->dev, "CV: capature not support\n");
cvi_i2s_dai->capture.channels_min = 0;
cvi_i2s_dai->capture.channels_max = 0;
}
dev_dbg(dev->dev, "CV: i2s master/slave mode supported\n");
dev->capability |= CVI_I2S_MASTER | CVI_I2S_SLAVE;
dev->fifo_th = I2STDM_FIFO_DEPTH / 2;
return 0;
}
static int cvi_configure_dai_by_dt(struct cvi_i2s_dev *dev,
struct snd_soc_dai_driver *cvi_i2s_dai,
struct resource *res)
{
int ret;
struct device_node *np = dev->dev->of_node;
dev_dbg(dev->dev, "%s start\n", __func__);
ret = cvi_configure_dai(dev, cvi_i2s_dai, SNDRV_PCM_RATE_8000_192000);
if (ret < 0)
return ret;
/* Set TX parameters */
if (of_property_match_string(np, "dma-names", "tx") >= 0) {
dev_dbg(dev->dev, "%s dma-names tx\n", __func__);
dev->capability |= CVI_I2S_PLAY;
dev->play_dma_data.dt.addr = res->start + TX_WR_PORT_CH0;
dev->play_dma_data.dt.addr_width = 4;
dev->play_dma_data.dt.fifo_size = I2STDM_FIFO_DEPTH * I2STDM_FIFO_WIDTH;
dev->play_dma_data.dt.maxburst = 8;
}
/* Set RX parameters */
if (of_property_match_string(np, "dma-names", "rx") >= 0) {
dev_dbg(dev->dev, "%s dma-names rx\n", __func__);
dev->capability |= CVI_I2S_RECORD;
dev->capture_dma_data.dt.addr = res->start + RX_RD_PORT_CH0;
dev->capture_dma_data.dt.addr_width = 4;
dev->capture_dma_data.dt.fifo_size = I2STDM_FIFO_DEPTH * I2STDM_FIFO_WIDTH;
dev->capture_dma_data.dt.maxburst = 8;
}
return 0;
}
static int i2s_proc_show(struct seq_file *m, void *v)
{
struct cvi_i2s_dev *dev = m->private;
if (i2s_read_reg(dev->i2s_base, I2S_ENABLE))
seq_printf(m, "\ni2s%d is enabled\n", dev->dev_id);
else
seq_printf(m, "\ni2s%d is disabled\n", dev->dev_id);
seq_printf(m, "\n===== Dump I2S%d register status =====\n", dev->dev_id);
seq_printf(m, "\nblk_mode=0x%08x, clk_ctrl=0x%08x, int_en=0x%08x\n",
i2s_read_reg(dev->i2s_base, BLK_MODE_SETTING),
i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0),
i2s_read_reg(dev->i2s_base, I2S_INT_EN));
seq_printf(m, "\nframe_setting=0x%08x, slot_setting=0x%08x, data_format=0x%08x\n",
i2s_read_reg(dev->i2s_base, FRAME_SETTING),
i2s_read_reg(dev->i2s_base, SLOT_SETTING1),
i2s_read_reg(dev->i2s_base, DATA_FORMAT));
seq_printf(m, "\ni2s_int=0x%08x, rx_status=0x%08x, tx_status=0x%08x\n",
i2s_read_reg(dev->i2s_base, I2S_INT),
i2s_read_reg(dev->i2s_base, RX_STATUS),
i2s_read_reg(dev->i2s_base, TX_STATUS));
seq_printf(m, "\ndma_req=0x%08x, dma_ack=0x%08x\n",
i2s_read_reg(dev->i2s_base, DMA_REQ_COUNT),
i2s_read_reg(dev->i2s_base, DMA_ACK_COUNT));
seq_printf(m, "\nclk_ctrl0=0x%08x, clk_ctrl1=0x%08x\n",
i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0),
i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL1));
return 0;
}
static int seq_i2s_open(struct inode *inode, struct file *file)
{
return single_open(file, i2s_proc_show, PDE_DATA(inode));
}
static const struct proc_ops i2s_proc_ops = {
.proc_read = seq_read,
.proc_open = seq_i2s_open,
.proc_release = single_release,
};
static int cvi_i2s_probe(struct platform_device *pdev)
{
const struct i2s_platform_data *pdata = pdev->dev.platform_data;
struct cvi_i2s_dev *dev;
struct resource *res;
int ret, irq;
struct snd_soc_dai_driver *cvi_i2s_dai;
const char *clk_id;
unsigned int val;
struct proc_dir_entry *proc_i2s;
char *i2s_dev_name;
const char *mclk_out;
dev_info(&pdev->dev, "%s\n", __func__);
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
cvi_i2s_dai = devm_kzalloc(&pdev->dev, sizeof(*cvi_i2s_dai), GFP_KERNEL);
if (!cvi_i2s_dai)
return -ENOMEM;
cvi_i2s_dai->ops = &cvi_i2s_dai_ops;
cvi_i2s_dai->name = "cvi_i2s_probe";
//for kernel version witch is less than 5.10.4
//cvi_i2s_dai->suspend = cvi_i2s_suspend;
//cvi_i2s_dai->resume = cvi_i2s_resume;
cvi_i2s_dai->probe = cvi_i2s_dai_probe;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
dev->i2s_base = devm_ioremap_resource(&pdev->dev, res);
dev_dbg(&pdev->dev, "I2S get i2s_base=0x%p\n", dev->i2s_base);
if (IS_ERR(dev->i2s_base))
return PTR_ERR(dev->i2s_base);
dev->dev = &pdev->dev;
irq = platform_get_irq(pdev, 0);
if (irq >= 0) {
dev_dbg(&pdev->dev, "I2S get IRQ=0x%x\n", irq);
ret = devm_request_irq(&pdev->dev, irq, i2s_irq_handler, 0,
pdev->name, dev);
if (ret < 0) {
dev_err(&pdev->dev, "failed to request irq\n");
return ret;
}
}
if (pdata) {
dev->capability = pdata->cap;
clk_id = NULL;
dev->quirks = pdata->quirks;
} else {
clk_id = "i2sclk";
ret = cvi_configure_dai_by_dt(dev, cvi_i2s_dai, res);
device_property_read_u32(&pdev->dev, "dev-id",
&dev->dev_id);
dev->clk = devm_clk_get(&pdev->dev, clk_id);
}
if (ret < 0)
return ret;
if (dev->capability & CVI_I2S_MASTER) {
if (pdata) {
dev->i2s_clk_cfg = pdata->i2s_clk_cfg;
if (!dev->i2s_clk_cfg) {
dev_err(&pdev->dev, "no clock configure method\n");
return -ENODEV;
}
}
dev->clk = devm_clk_get(&pdev->dev, clk_id);
if (IS_ERR(dev->clk))
return PTR_ERR(dev->clk);
ret = clk_prepare_enable(dev->clk);
if (ret < 0) {
dev_err(&pdev->dev, "I2S clock prepare failed\n");
return ret;
}
}
device_property_read_string(&pdev->dev, "mclk_out", &mclk_out);
if (!strcmp(mclk_out, "true"))
dev->mclk_out = true;
else
dev->mclk_out = false;
val = i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0);
val &= ~(AUD_CLK_SOURCE_MASK);
val &= ~(BCLK_OUT_FORCE_EN); /* blck_out output after transmission start */
#if defined(CONFIG_SND_SOC_CV1835_USE_AUDIO_PLL)
if (dev->mclk_out == true)
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, val | AUD_CLK_FROM_PLL | MCLK_OUT_EN | AUD_ENABLE);
/* Turn aud_en on due to external codec might need MCLK to do register initialization */
else
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, val | AUD_CLK_FROM_PLL);
#else
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, val | AUD_CLK_FROM_MCLK_IN);
#endif
dev_set_drvdata(&pdev->dev, dev);
ret = devm_snd_soc_register_component(&pdev->dev, &cvi_i2s_component,
cvi_i2s_dai, 1);
if (ret != 0) {
dev_err(&pdev->dev, "not able to register dai\n");
goto err_clk_disable;
}
if (!pdata) {
ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
if (ret == -EPROBE_DEFER) {
dev_err(&pdev->dev,
"failed to register PCM, deferring probe\n");
return ret;
} else if (ret) {
dev_err(&pdev->dev,
"Could not register DMA PCM: %d\n"
"falling back to PIO mode\n", ret);
ret = cvi_pcm_register(pdev);
if (ret) {
dev_err(&pdev->dev,
"Could not register PIO PCM: %d\n",
ret);
goto err_clk_disable;
}
}
}
if (!proc_audio_dir) {
proc_audio_dir = proc_mkdir("audio_debug", NULL);
if (!proc_audio_dir)
dev_err(&pdev->dev, "Error creating audio_debug proc folder entry\n");
}
if (proc_audio_dir) {
i2s_dev_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "i2s%d", dev->dev_id);
proc_i2s = proc_create_data(i2s_dev_name, 0664, proc_audio_dir, &i2s_proc_ops, dev);
if (!proc_i2s)
dev_err(&pdev->dev, "Create i2s%d status proc failed!\n", dev->dev_id);
devm_kfree(&pdev->dev, i2s_dev_name);
}
pm_runtime_enable(&pdev->dev);
return 0;
err_clk_disable:
if (dev->capability & CVI_I2S_MASTER)
clk_disable_unprepare(dev->clk);
return ret;
}
static int cvi_i2s_remove(struct platform_device *pdev)
{
struct cvi_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
if (dev->capability & CVI_I2S_MASTER)
clk_disable_unprepare(dev->clk);
pm_runtime_disable(&pdev->dev);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id cvi_i2s_of_match[] = {
{ .compatible = "cvitek,cv1835-i2s", },
{},
};
MODULE_DEVICE_TABLE(of, cvi_i2s_of_match);
#endif
#ifdef CONFIG_PM_SLEEP
static int cvi_i2s_pm_suspend(struct device *dev)
{
struct cvi_i2s_dev *i2s_dev = dev_get_drvdata(dev);
if (!i2s_dev->reg_ctx) {
i2s_dev->reg_ctx = devm_kzalloc(i2s_dev->dev, sizeof(struct cvi_i2s_reg_context), GFP_KERNEL);
if (!i2s_dev->reg_ctx)
return -ENOMEM;
}
i2s_dev->reg_ctx->blk_setting = i2s_read_reg(i2s_dev->i2s_base, BLK_MODE_SETTING);
i2s_dev->reg_ctx->frame_setting = i2s_read_reg(i2s_dev->i2s_base, FRAME_SETTING);
i2s_dev->reg_ctx->slot_setting1 = i2s_read_reg(i2s_dev->i2s_base, SLOT_SETTING1);
i2s_dev->reg_ctx->slot_setting2 = i2s_read_reg(i2s_dev->i2s_base, SLOT_SETTING2);
i2s_dev->reg_ctx->data_format = i2s_read_reg(i2s_dev->i2s_base, DATA_FORMAT);
i2s_dev->reg_ctx->blk_cfg = i2s_read_reg(i2s_dev->i2s_base, BLK_CFG);
i2s_dev->reg_ctx->i2s_en = i2s_read_reg(i2s_dev->i2s_base, I2S_ENABLE);
i2s_dev->reg_ctx->i2s_int_en = i2s_read_reg(i2s_dev->i2s_base, I2S_INT_EN);
i2s_dev->reg_ctx->fifo_threshold = i2s_read_reg(i2s_dev->i2s_base, FIFO_THRESHOLD);
i2s_dev->reg_ctx->i2s_lrck_master = i2s_read_reg(i2s_dev->i2s_base, I2S_LRCK_MASTER);
i2s_dev->reg_ctx->i2s_clk_ctl0 = i2s_read_reg(i2s_dev->i2s_base, I2S_CLK_CTRL0);
i2s_dev->reg_ctx->i2c_clk_ctl1 = i2s_read_reg(i2s_dev->i2s_base, I2S_CLK_CTRL1);
i2s_dev->reg_ctx->i2s_pcm_synth = i2s_read_reg(i2s_dev->i2s_base, I2S_PCM_SYNTH);
if (i2s_dev->capability & CVI_I2S_MASTER)
clk_disable(i2s_dev->clk);
return 0;
}
static int cvi_i2s_pm_resume(struct device *dev)
{
struct cvi_i2s_dev *i2s_dev = dev_get_drvdata(dev);
if (i2s_dev->capability & CVI_I2S_MASTER)
clk_enable(i2s_dev->clk);
i2s_write_reg(i2s_dev->i2s_base, BLK_MODE_SETTING, i2s_dev->reg_ctx->blk_setting);
i2s_write_reg(i2s_dev->i2s_base, FRAME_SETTING, i2s_dev->reg_ctx->frame_setting);
i2s_write_reg(i2s_dev->i2s_base, SLOT_SETTING1, i2s_dev->reg_ctx->slot_setting1);
i2s_write_reg(i2s_dev->i2s_base, SLOT_SETTING2, i2s_dev->reg_ctx->slot_setting2);
i2s_write_reg(i2s_dev->i2s_base, DATA_FORMAT, i2s_dev->reg_ctx->data_format);
i2s_write_reg(i2s_dev->i2s_base, BLK_CFG, i2s_dev->reg_ctx->blk_cfg);
i2s_write_reg(i2s_dev->i2s_base, I2S_ENABLE, i2s_dev->reg_ctx->i2s_en);
i2s_write_reg(i2s_dev->i2s_base, I2S_INT_EN, i2s_dev->reg_ctx->i2s_int_en);
i2s_write_reg(i2s_dev->i2s_base, FIFO_THRESHOLD, i2s_dev->reg_ctx->fifo_threshold);
i2s_write_reg(i2s_dev->i2s_base, I2S_LRCK_MASTER, i2s_dev->reg_ctx->i2s_lrck_master);
i2s_write_reg(i2s_dev->i2s_base, I2S_CLK_CTRL0, i2s_dev->reg_ctx->i2s_clk_ctl0);
i2s_write_reg(i2s_dev->i2s_base, I2S_CLK_CTRL1, i2s_dev->reg_ctx->i2c_clk_ctl1);
i2s_write_reg(i2s_dev->i2s_base, I2S_PCM_SYNTH, i2s_dev->reg_ctx->i2s_pcm_synth);
return 0;
}
#else
#define cvi_i2s_pm_suspend NULL
#define cvi_i2s_pm_resume NULL
#endif
#ifdef CONFIG_PM
static const struct dev_pm_ops cvi_pm_ops = {
SET_RUNTIME_PM_OPS(cvi_i2s_runtime_suspend, cvi_i2s_runtime_resume, NULL)
#ifdef CONFIG_PM_SLEEP
SET_SYSTEM_SLEEP_PM_OPS(cvi_i2s_pm_suspend, cvi_i2s_pm_resume)
#endif
};
#endif
static struct platform_driver cvi_i2s_driver = {
.probe = cvi_i2s_probe,
.remove = cvi_i2s_remove,
.driver = {
.name = "cvitek-i2s",
.of_match_table = of_match_ptr(cvi_i2s_of_match),
#ifdef CONFIG_PM
.pm = &cvi_pm_ops,
#endif
},
};
module_platform_driver(cvi_i2s_driver);
MODULE_AUTHOR("EthanChen <ethan.chen@wisecore.com.tw>");
MODULE_DESCRIPTION("CVITEK I2S SoC Interface");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:CVITEK_I2S");