This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TLV320AIC3204: TLV320AIC32X4 - Audio Coded not getting registered - audio_probe() - register card failed - 517

Part Number: TLV320AIC3204
Other Parts Discussed in Thread: TLV320AIC32

Hello Team,

We are stuck in configuring the “tlv320aic32x4” module. We want to use “tlv320aic32x4” for Audio routing in one of our VoIP projects as we found this IC suitable for our needs. So we had purchased an evaluation kit (Image attached as “ti_module.jpeg”), but now we are not able to configure it. We need the “TI” team’s help to come out of this problem.

 

We have the following :

 

  1. Codec driver as default provides in Linux kernel. (Attached file “tlv320aic32x4.c”).
  2. Rockchip Platform - Platform Driver.(Attached file “rk_i2s.c”).
  3. Rockchip Platform Machine Driver. (Attached file “rk_aic32x4.c”)[Taken reference of “es8323” machine driver].

 

We have Device tree entry as follow :

 

rockchip-spdif-card {

                compatible = "rockchip-spdif-card";

                dais {

                        dai0 {

                                audio-codec = <&codec_hdmi_spdif>;

                                audio-controller = <&spdif>;

                        };

                };

        };

 

rockchip-aic32X4 {

                status = "okay";

                compatible = "rockchip-aic32X4";

                 dais {

                        dai0 {

                                audio-codec = <&codec>;

                                audio-controller = <&i2s>;

                                format = "i2s";

                                //continuous-clock;

                                //bitclock-inversion;

                                //frame-inversion;

                                //bitclock-master;

                                //frame-master;

                        };

                };

 

tlv320aic32@18 {

                compatible = "ti,tlv320aic32x4";

                reg = <0x18>;

                clocks = <&clk_i2s>, <&clk_i2s_out>;

                clock-names = "i2s_clk","i2s_mclk";

                pinctrl-names = "default";

                pinctrl-0 = <&i2s_mclk>;

        };

 

Problem:  We have followed the above steps but getting the following errors ( Card not registered ) (Error attached as “32x4logs.PNG” & “32x4log.PNG”). Now we are not sure which part we are doing wrong.

All reference file as attached : 

2605.tlv320aic32x4.c
/*
 * linux/sound/soc/codecs/tlv320aic32x4.c
 *
 * Copyright 2011 Vista Silicon S.L.
 *
 * Author: Javier Martin <javier.martin@vista-silicon.com>
 *
 * Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/slab.h>

#include <sound/tlv320aic32x4.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <sound/tlv.h>

#include "tlv320aic32x4.h"

struct aic32x4_rate_divs {
	u32 mclk;
	u32 rate;
	u8 p_val;
	u8 pll_j;
	u16 pll_d;
	u16 dosr;
	u8 ndac;
	u8 mdac;
	u8 aosr;
	u8 nadc;
	u8 madc;
	u8 blck_N;
};

struct aic32x4_priv {
	u32 sysclk;
	u8 page_no;
	void *control_data;
	u32 power_cfg;
	u32 micpga_routing;
	bool swapdacs;
	int rstn_gpio;
};

/* 0dB min, 1dB steps */
static DECLARE_TLV_DB_SCALE(tlv_step_1, 0, 100, 0);
/* 0dB min, 0.5dB steps */
static DECLARE_TLV_DB_SCALE(tlv_step_0_5, 0, 50, 0);

static const struct snd_kcontrol_new aic32x4_snd_controls[] = {
	SOC_DOUBLE_R_TLV("PCM Playback Volume", AIC32X4_LDACVOL,
			AIC32X4_RDACVOL, 0, 0x30, 0, tlv_step_0_5),
	SOC_DOUBLE_R_TLV("HP Driver Gain Volume", AIC32X4_HPLGAIN,
			AIC32X4_HPRGAIN, 0, 0x1D, 0, tlv_step_1),
	SOC_DOUBLE_R_TLV("LO Driver Gain Volume", AIC32X4_LOLGAIN,
			AIC32X4_LORGAIN, 0, 0x1D, 0, tlv_step_1),
	SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN,
			AIC32X4_HPRGAIN, 6, 0x01, 1),
	SOC_DOUBLE_R("LO DAC Playback Switch", AIC32X4_LOLGAIN,
			AIC32X4_LORGAIN, 6, 0x01, 1),
	SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL,
			AIC32X4_RMICPGAVOL, 7, 0x01, 1),

	SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0),
	SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0),

	SOC_DOUBLE_R_TLV("ADC Level Volume", AIC32X4_LADCVOL,
			AIC32X4_RADCVOL, 0, 0x28, 0, tlv_step_0_5),
	SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL,
			AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5),

	SOC_SINGLE("Auto-mute Switch", AIC32X4_DACMUTE, 4, 7, 0),

	SOC_SINGLE("AGC Left Switch", AIC32X4_LAGC1, 7, 1, 0),
	SOC_SINGLE("AGC Right Switch", AIC32X4_RAGC1, 7, 1, 0),
	SOC_DOUBLE_R("AGC Target Level", AIC32X4_LAGC1, AIC32X4_RAGC1,
			4, 0x07, 0),
	SOC_DOUBLE_R("AGC Gain Hysteresis", AIC32X4_LAGC1, AIC32X4_RAGC1,
			0, 0x03, 0),
	SOC_DOUBLE_R("AGC Hysteresis", AIC32X4_LAGC2, AIC32X4_RAGC2,
			6, 0x03, 0),
	SOC_DOUBLE_R("AGC Noise Threshold", AIC32X4_LAGC2, AIC32X4_RAGC2,
			1, 0x1F, 0),
	SOC_DOUBLE_R("AGC Max PGA", AIC32X4_LAGC3, AIC32X4_RAGC3,
			0, 0x7F, 0),
	SOC_DOUBLE_R("AGC Attack Time", AIC32X4_LAGC4, AIC32X4_RAGC4,
			3, 0x1F, 0),
	SOC_DOUBLE_R("AGC Decay Time", AIC32X4_LAGC5, AIC32X4_RAGC5,
			3, 0x1F, 0),
	SOC_DOUBLE_R("AGC Noise Debounce", AIC32X4_LAGC6, AIC32X4_RAGC6,
			0, 0x1F, 0),
	SOC_DOUBLE_R("AGC Signal Debounce", AIC32X4_LAGC7, AIC32X4_RAGC7,
			0, 0x0F, 0),
};

static const struct aic32x4_rate_divs aic32x4_divs[] = {
	/* 8k rate */
	{AIC32X4_FREQ_12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24},
	{AIC32X4_FREQ_24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24},
	{AIC32X4_FREQ_25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24},
	/* 11.025k rate */
	{AIC32X4_FREQ_12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16},
	{AIC32X4_FREQ_24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16},
	/* 16k rate */
	{AIC32X4_FREQ_12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12},
	{AIC32X4_FREQ_24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12},
	{AIC32X4_FREQ_25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12},
	/* 22.05k rate */
	{AIC32X4_FREQ_12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8},
	{AIC32X4_FREQ_24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8},
	{AIC32X4_FREQ_25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8},
	/* 32k rate */
	{AIC32X4_FREQ_12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6},
	{AIC32X4_FREQ_24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6},
	/* 44.1k rate */
	{AIC32X4_FREQ_12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4},
	{AIC32X4_FREQ_24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4},
	{AIC32X4_FREQ_25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4},
	/* 48k rate */
	{AIC32X4_FREQ_12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4},
	{AIC32X4_FREQ_24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4},
	{AIC32X4_FREQ_25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4}
};

static const struct snd_kcontrol_new hpl_output_mixer_controls[] = {
	SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 0),
	SOC_DAPM_SINGLE("IN1_L Switch", AIC32X4_HPLROUTE, 2, 1, 0),
};

static const struct snd_kcontrol_new hpr_output_mixer_controls[] = {
	SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_HPRROUTE, 3, 1, 0),
	SOC_DAPM_SINGLE("IN1_R Switch", AIC32X4_HPRROUTE, 2, 1, 0),
};

static const struct snd_kcontrol_new lol_output_mixer_controls[] = {
	SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_LOLROUTE, 3, 1, 0),
};

static const struct snd_kcontrol_new lor_output_mixer_controls[] = {
	SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_LORROUTE, 3, 1, 0),
};

static const struct snd_kcontrol_new left_input_mixer_controls[] = {
	SOC_DAPM_SINGLE("IN1_L P Switch", AIC32X4_LMICPGAPIN, 6, 1, 0),
	SOC_DAPM_SINGLE("IN2_L P Switch", AIC32X4_LMICPGAPIN, 4, 1, 0),
	SOC_DAPM_SINGLE("IN3_L P Switch", AIC32X4_LMICPGAPIN, 2, 1, 0),
};

static const struct snd_kcontrol_new right_input_mixer_controls[] = {
	SOC_DAPM_SINGLE("IN1_R P Switch", AIC32X4_RMICPGAPIN, 6, 1, 0),
	SOC_DAPM_SINGLE("IN2_R P Switch", AIC32X4_RMICPGAPIN, 4, 1, 0),
	SOC_DAPM_SINGLE("IN3_R P Switch", AIC32X4_RMICPGAPIN, 2, 1, 0),
};

static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
	SND_SOC_DAPM_DAC("Left DAC", "Left Playback", AIC32X4_DACSETUP, 7, 0),
	SND_SOC_DAPM_MIXER("HPL Output Mixer", SND_SOC_NOPM, 0, 0,
			   &hpl_output_mixer_controls[0],
			   ARRAY_SIZE(hpl_output_mixer_controls)),
	SND_SOC_DAPM_PGA("HPL Power", AIC32X4_OUTPWRCTL, 5, 0, NULL, 0),

	SND_SOC_DAPM_MIXER("LOL Output Mixer", SND_SOC_NOPM, 0, 0,
			   &lol_output_mixer_controls[0],
			   ARRAY_SIZE(lol_output_mixer_controls)),
	SND_SOC_DAPM_PGA("LOL Power", AIC32X4_OUTPWRCTL, 3, 0, NULL, 0),

	SND_SOC_DAPM_DAC("Right DAC", "Right Playback", AIC32X4_DACSETUP, 6, 0),
	SND_SOC_DAPM_MIXER("HPR Output Mixer", SND_SOC_NOPM, 0, 0,
			   &hpr_output_mixer_controls[0],
			   ARRAY_SIZE(hpr_output_mixer_controls)),
	SND_SOC_DAPM_PGA("HPR Power", AIC32X4_OUTPWRCTL, 4, 0, NULL, 0),
	SND_SOC_DAPM_MIXER("LOR Output Mixer", SND_SOC_NOPM, 0, 0,
			   &lor_output_mixer_controls[0],
			   ARRAY_SIZE(lor_output_mixer_controls)),
	SND_SOC_DAPM_PGA("LOR Power", AIC32X4_OUTPWRCTL, 2, 0, NULL, 0),
	SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
			   &left_input_mixer_controls[0],
			   ARRAY_SIZE(left_input_mixer_controls)),
	SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
			   &right_input_mixer_controls[0],
			   ARRAY_SIZE(right_input_mixer_controls)),
	SND_SOC_DAPM_ADC("Left ADC", "Left Capture", AIC32X4_ADCSETUP, 7, 0),
	SND_SOC_DAPM_ADC("Right ADC", "Right Capture", AIC32X4_ADCSETUP, 6, 0),
	SND_SOC_DAPM_MICBIAS("Mic Bias", AIC32X4_MICBIAS, 6, 0),

	SND_SOC_DAPM_OUTPUT("HPL"),
	SND_SOC_DAPM_OUTPUT("HPR"),
	SND_SOC_DAPM_OUTPUT("LOL"),
	SND_SOC_DAPM_OUTPUT("LOR"),
	SND_SOC_DAPM_INPUT("IN1_L"),
	SND_SOC_DAPM_INPUT("IN1_R"),
	SND_SOC_DAPM_INPUT("IN2_L"),
	SND_SOC_DAPM_INPUT("IN2_R"),
	SND_SOC_DAPM_INPUT("IN3_L"),
	SND_SOC_DAPM_INPUT("IN3_R"),
};

static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = {
	/* Left Output */
	{"HPL Output Mixer", "L_DAC Switch", "Left DAC"},
	{"HPL Output Mixer", "IN1_L Switch", "IN1_L"},

	{"HPL Power", NULL, "HPL Output Mixer"},
	{"HPL", NULL, "HPL Power"},

	{"LOL Output Mixer", "L_DAC Switch", "Left DAC"},

	{"LOL Power", NULL, "LOL Output Mixer"},
	{"LOL", NULL, "LOL Power"},

	/* Right Output */
	{"HPR Output Mixer", "R_DAC Switch", "Right DAC"},
	{"HPR Output Mixer", "IN1_R Switch", "IN1_R"},

	{"HPR Power", NULL, "HPR Output Mixer"},
	{"HPR", NULL, "HPR Power"},

	{"LOR Output Mixer", "R_DAC Switch", "Right DAC"},

	{"LOR Power", NULL, "LOR Output Mixer"},
	{"LOR", NULL, "LOR Power"},

	/* Left input */
	{"Left Input Mixer", "IN1_L P Switch", "IN1_L"},
	{"Left Input Mixer", "IN2_L P Switch", "IN2_L"},
	{"Left Input Mixer", "IN3_L P Switch", "IN3_L"},

	{"Left ADC", NULL, "Left Input Mixer"},

	/* Right Input */
	{"Right Input Mixer", "IN1_R P Switch", "IN1_R"},
	{"Right Input Mixer", "IN2_R P Switch", "IN2_R"},
	{"Right Input Mixer", "IN3_R P Switch", "IN3_R"},

	{"Right ADC", NULL, "Right Input Mixer"},
};

static inline int aic32x4_change_page(struct snd_soc_codec *codec,
					unsigned int new_page)
{
	struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
	u8 data[2];
	int ret;

	data[0] = 0x00;
	data[1] = new_page & 0xff;

	ret = codec->hw_write(codec->control_data, data, 2);
	if (ret == 2) {
		aic32x4->page_no = new_page;
		return 0;
	} else {
		return ret;
	}
}

static int aic32x4_write(struct snd_soc_codec *codec, unsigned int reg,
				unsigned int val)
{
	struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
	unsigned int page = reg / 128;
	unsigned int fixed_reg = reg % 128;
	u8 data[2];
	int ret;

	/* A write to AIC32X4_PSEL is really a non-explicit page change */
	if (reg == AIC32X4_PSEL)
		return aic32x4_change_page(codec, val);

	if (aic32x4->page_no != page) {
		ret = aic32x4_change_page(codec, page);
		if (ret != 0)
			return ret;
	}

	data[0] = fixed_reg & 0xff;
	data[1] = val & 0xff;

	if (codec->hw_write(codec->control_data, data, 2) == 2)
		return 0;
	else
		return -EIO;
}

static unsigned int aic32x4_read(struct snd_soc_codec *codec, unsigned int reg)
{
	struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
	unsigned int page = reg / 128;
	unsigned int fixed_reg = reg % 128;
	int ret;

	if (aic32x4->page_no != page) {
		ret = aic32x4_change_page(codec, page);
		if (ret != 0)
			return ret;
	}
	return i2c_smbus_read_byte_data(codec->control_data, fixed_reg & 0xff);
}

static inline int aic32x4_get_divs(int mclk, int rate)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) {
		if ((aic32x4_divs[i].rate == rate)
		    && (aic32x4_divs[i].mclk == mclk)) {
			return i;
		}
	}
	printk(KERN_ERR "aic32x4: master clock and sample rate is not supported\n");
	return -EINVAL;
}

static int aic32x4_add_widgets(struct snd_soc_codec *codec)
{
	snd_soc_dapm_new_controls(&codec->dapm, aic32x4_dapm_widgets,
				  ARRAY_SIZE(aic32x4_dapm_widgets));

	snd_soc_dapm_add_routes(&codec->dapm, aic32x4_dapm_routes,
				ARRAY_SIZE(aic32x4_dapm_routes));

	snd_soc_dapm_new_widgets(&codec->dapm);
	return 0;
}

static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai,
				  int clk_id, unsigned int freq, int dir)
{
	struct snd_soc_codec *codec = codec_dai->codec;
	struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);

	switch (freq) {
	case AIC32X4_FREQ_12000000:
	case AIC32X4_FREQ_24000000:
	case AIC32X4_FREQ_25000000:
		aic32x4->sysclk = freq;
		return 0;
	}
	printk(KERN_ERR "aic32x4: invalid frequency to set DAI system clock\n");
	return -EINVAL;
}

static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
	struct snd_soc_codec *codec = codec_dai->codec;
	u8 iface_reg_1;
	u8 iface_reg_2;
	u8 iface_reg_3;

	iface_reg_1 = snd_soc_read(codec, AIC32X4_IFACE1);
	iface_reg_1 = iface_reg_1 & ~(3 << 6 | 3 << 2);
	iface_reg_2 = snd_soc_read(codec, AIC32X4_IFACE2);
	iface_reg_2 = 0;
	iface_reg_3 = snd_soc_read(codec, AIC32X4_IFACE3);
	iface_reg_3 = iface_reg_3 & ~(1 << 3);

	/* set master/slave audio interface */
	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	case SND_SOC_DAIFMT_CBM_CFM:
		iface_reg_1 |= AIC32X4_BCLKMASTER | AIC32X4_WCLKMASTER;
		break;
	case SND_SOC_DAIFMT_CBS_CFS:
		break;
	default:
		printk(KERN_ERR "aic32x4: invalid DAI master/slave interface\n");
		return -EINVAL;
	}

	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_I2S:
		break;
	case SND_SOC_DAIFMT_DSP_A:
		iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT);
		iface_reg_3 |= (1 << 3); /* invert bit clock */
		iface_reg_2 = 0x01; /* add offset 1 */
		break;
	case SND_SOC_DAIFMT_DSP_B:
		iface_reg_1 |= (AIC32X4_DSP_MODE << AIC32X4_PLLJ_SHIFT);
		iface_reg_3 |= (1 << 3); /* invert bit clock */
		break;
	case SND_SOC_DAIFMT_RIGHT_J:
		iface_reg_1 |=
			(AIC32X4_RIGHT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT);
		break;
	case SND_SOC_DAIFMT_LEFT_J:
		iface_reg_1 |=
			(AIC32X4_LEFT_JUSTIFIED_MODE << AIC32X4_PLLJ_SHIFT);
		break;
	default:
		printk(KERN_ERR "aic32x4: invalid DAI interface format\n");
		return -EINVAL;
	}

	snd_soc_write(codec, AIC32X4_IFACE1, iface_reg_1);
	snd_soc_write(codec, AIC32X4_IFACE2, iface_reg_2);
	snd_soc_write(codec, AIC32X4_IFACE3, iface_reg_3);
	return 0;
}

static int aic32x4_hw_params(struct snd_pcm_substream *substream,
			     struct snd_pcm_hw_params *params,
			     struct snd_soc_dai *dai)
{
	struct snd_soc_codec *codec = dai->codec;
	struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
	u8 data;
	int i;

	i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params));
	if (i < 0) {
		printk(KERN_ERR "aic32x4: sampling rate not supported\n");
		return i;
	}

	/* Use PLL as CODEC_CLKIN and DAC_MOD_CLK as BDIV_CLKIN */
	snd_soc_write(codec, AIC32X4_CLKMUX, AIC32X4_PLLCLKIN);
	snd_soc_write(codec, AIC32X4_IFACE3, AIC32X4_DACMOD2BCLK);

	/* We will fix R value to 1 and will make P & J=K.D as varialble */
	data = snd_soc_read(codec, AIC32X4_PLLPR);
	data &= ~(7 << 4);
	snd_soc_write(codec, AIC32X4_PLLPR,
		      (data | (aic32x4_divs[i].p_val << 4) | 0x01));

	snd_soc_write(codec, AIC32X4_PLLJ, aic32x4_divs[i].pll_j);

	snd_soc_write(codec, AIC32X4_PLLDMSB, (aic32x4_divs[i].pll_d >> 8));
	snd_soc_write(codec, AIC32X4_PLLDLSB,
		      (aic32x4_divs[i].pll_d & 0xff));

	/* NDAC divider value */
	data = snd_soc_read(codec, AIC32X4_NDAC);
	data &= ~(0x7f);
	snd_soc_write(codec, AIC32X4_NDAC, data | aic32x4_divs[i].ndac);

	/* MDAC divider value */
	data = snd_soc_read(codec, AIC32X4_MDAC);
	data &= ~(0x7f);
	snd_soc_write(codec, AIC32X4_MDAC, data | aic32x4_divs[i].mdac);

	/* DOSR MSB & LSB values */
	snd_soc_write(codec, AIC32X4_DOSRMSB, aic32x4_divs[i].dosr >> 8);
	snd_soc_write(codec, AIC32X4_DOSRLSB,
		      (aic32x4_divs[i].dosr & 0xff));

	/* NADC divider value */
	data = snd_soc_read(codec, AIC32X4_NADC);
	data &= ~(0x7f);
	snd_soc_write(codec, AIC32X4_NADC, data | aic32x4_divs[i].nadc);

	/* MADC divider value */
	data = snd_soc_read(codec, AIC32X4_MADC);
	data &= ~(0x7f);
	snd_soc_write(codec, AIC32X4_MADC, data | aic32x4_divs[i].madc);

	/* AOSR value */
	snd_soc_write(codec, AIC32X4_AOSR, aic32x4_divs[i].aosr);

	/* BCLK N divider */
	data = snd_soc_read(codec, AIC32X4_BCLKN);
	data &= ~(0x7f);
	snd_soc_write(codec, AIC32X4_BCLKN, data | aic32x4_divs[i].blck_N);

	data = snd_soc_read(codec, AIC32X4_IFACE1);
	data = data & ~(3 << 4);
	switch (params_format(params)) {
	case SNDRV_PCM_FORMAT_S16_LE:
		break;
	case SNDRV_PCM_FORMAT_S20_3LE:
		data |= (AIC32X4_WORD_LEN_20BITS << AIC32X4_DOSRMSB_SHIFT);
		break;
	case SNDRV_PCM_FORMAT_S24_LE:
		data |= (AIC32X4_WORD_LEN_24BITS << AIC32X4_DOSRMSB_SHIFT);
		break;
	case SNDRV_PCM_FORMAT_S32_LE:
		data |= (AIC32X4_WORD_LEN_32BITS << AIC32X4_DOSRMSB_SHIFT);
		break;
	}
	snd_soc_write(codec, AIC32X4_IFACE1, data);

	return 0;
}

static int aic32x4_mute(struct snd_soc_dai *dai, int mute)
{
	struct snd_soc_codec *codec = dai->codec;
	u8 dac_reg;

	dac_reg = snd_soc_read(codec, AIC32X4_DACMUTE) & ~AIC32X4_MUTEON;
	if (mute)
		snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg | AIC32X4_MUTEON);
	else
		snd_soc_write(codec, AIC32X4_DACMUTE, dac_reg);
	return 0;
}

static int aic32x4_set_bias_level(struct snd_soc_codec *codec,
				  enum snd_soc_bias_level level)
{
	switch (level) {
	case SND_SOC_BIAS_ON:
		/* Switch on PLL */
		snd_soc_update_bits(codec, AIC32X4_PLLPR,
				    AIC32X4_PLLEN, AIC32X4_PLLEN);

		/* Switch on NDAC Divider */
		snd_soc_update_bits(codec, AIC32X4_NDAC,
				    AIC32X4_NDACEN, AIC32X4_NDACEN);

		/* Switch on MDAC Divider */
		snd_soc_update_bits(codec, AIC32X4_MDAC,
				    AIC32X4_MDACEN, AIC32X4_MDACEN);

		/* Switch on NADC Divider */
		snd_soc_update_bits(codec, AIC32X4_NADC,
				    AIC32X4_NADCEN, AIC32X4_NADCEN);

		/* Switch on MADC Divider */
		snd_soc_update_bits(codec, AIC32X4_MADC,
				    AIC32X4_MADCEN, AIC32X4_MADCEN);

		/* Switch on BCLK_N Divider */
		snd_soc_update_bits(codec, AIC32X4_BCLKN,
				    AIC32X4_BCLKEN, AIC32X4_BCLKEN);
		break;
	case SND_SOC_BIAS_PREPARE:
		break;
	case SND_SOC_BIAS_STANDBY:
		/* Switch off PLL */
		snd_soc_update_bits(codec, AIC32X4_PLLPR,
				    AIC32X4_PLLEN, 0);

		/* Switch off NDAC Divider */
		snd_soc_update_bits(codec, AIC32X4_NDAC,
				    AIC32X4_NDACEN, 0);

		/* Switch off MDAC Divider */
		snd_soc_update_bits(codec, AIC32X4_MDAC,
				    AIC32X4_MDACEN, 0);

		/* Switch off NADC Divider */
		snd_soc_update_bits(codec, AIC32X4_NADC,
				    AIC32X4_NADCEN, 0);

		/* Switch off MADC Divider */
		snd_soc_update_bits(codec, AIC32X4_MADC,
				    AIC32X4_MADCEN, 0);

		/* Switch off BCLK_N Divider */
		snd_soc_update_bits(codec, AIC32X4_BCLKN,
				    AIC32X4_BCLKEN, 0);
		break;
	case SND_SOC_BIAS_OFF:
		break;
	}
	codec->dapm.bias_level = level;
	return 0;
}

#define AIC32X4_RATES	SNDRV_PCM_RATE_8000_48000
#define AIC32X4_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
			 | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)

static const struct snd_soc_dai_ops aic32x4_ops = {
	.hw_params = aic32x4_hw_params,
	.digital_mute = aic32x4_mute,
	.set_fmt = aic32x4_set_dai_fmt,
	.set_sysclk = aic32x4_set_dai_sysclk,
};

static struct snd_soc_dai_driver aic32x4_dai = {
	.name = "tlv320aic32x4-hifi",
	.playback = {
		     .stream_name = "Playback",
		     .channels_min = 1,
		     .channels_max = 2,
		     .rates = AIC32X4_RATES,
		     .formats = AIC32X4_FORMATS,},
	.capture = {
		    .stream_name = "Capture",
		    .channels_min = 1,
		    .channels_max = 2,
		    .rates = AIC32X4_RATES,
		    .formats = AIC32X4_FORMATS,},
	.ops = &aic32x4_ops,
	.symmetric_rates = 1,
};

static int aic32x4_suspend(struct snd_soc_codec *codec)
{
	aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
	return 0;
}

static int aic32x4_resume(struct snd_soc_codec *codec)
{
	aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
	return 0;
}

static int aic32x4_probe(struct snd_soc_codec *codec)
{
	struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
	u32 tmp_reg;
	int ret;

	codec->hw_write = (hw_write_t) i2c_master_send;
	codec->control_data = aic32x4->control_data;
        printk(KERN_INFO "AIC32\n");
	if (aic32x4->rstn_gpio >= 0) {
		ret = devm_gpio_request_one(codec->dev, aic32x4->rstn_gpio,
				GPIOF_OUT_INIT_LOW, "tlv320aic32x4 rstn");
		if (ret != 0)
			return ret;
		ndelay(10);
		gpio_set_value(aic32x4->rstn_gpio, 1);
	}

	snd_soc_write(codec, AIC32X4_RESET, 0x01);

	/* Power platform configuration */
	if (aic32x4->power_cfg & AIC32X4_PWR_MICBIAS_2075_LDOIN) {
		snd_soc_write(codec, AIC32X4_MICBIAS, AIC32X4_MICBIAS_LDOIN |
						      AIC32X4_MICBIAS_2075V);
	}
	if (aic32x4->power_cfg & AIC32X4_PWR_AVDD_DVDD_WEAK_DISABLE) {
		snd_soc_write(codec, AIC32X4_PWRCFG, AIC32X4_AVDDWEAKDISABLE);
	}

	tmp_reg = (aic32x4->power_cfg & AIC32X4_PWR_AIC32X4_LDO_ENABLE) ?
			AIC32X4_LDOCTLEN : 0;
	snd_soc_write(codec, AIC32X4_LDOCTL, tmp_reg);

	tmp_reg = snd_soc_read(codec, AIC32X4_CMMODE);
	if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_LDOIN_RANGE_18_36) {
		tmp_reg |= AIC32X4_LDOIN_18_36;
	}
	if (aic32x4->power_cfg & AIC32X4_PWR_CMMODE_HP_LDOIN_POWERED) {
		tmp_reg |= AIC32X4_LDOIN2HP;
	}
	snd_soc_write(codec, AIC32X4_CMMODE, tmp_reg);

	/* Do DACs need to be swapped? */
	if (aic32x4->swapdacs) {
		snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2RCHN | AIC32X4_RDAC2LCHN);
	} else {
		snd_soc_write(codec, AIC32X4_DACSETUP, AIC32X4_LDAC2LCHN | AIC32X4_RDAC2RCHN);
	}

	/* Mic PGA routing */
	if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_LMIC_IN2R_10K) {
		snd_soc_write(codec, AIC32X4_LMICPGANIN, AIC32X4_LMICPGANIN_IN2R_10K);
	}
	if (aic32x4->micpga_routing & AIC32X4_MICPGA_ROUTE_RMIC_IN1L_10K) {
		snd_soc_write(codec, AIC32X4_RMICPGANIN, AIC32X4_RMICPGANIN_IN1L_10K);
	}

	aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
	snd_soc_add_codec_controls(codec, aic32x4_snd_controls,
			     ARRAY_SIZE(aic32x4_snd_controls));
	aic32x4_add_widgets(codec);

	/*
	 * Workaround: for an unknown reason, the ADC needs to be powered up
	 * and down for the first capture to work properly. It seems related to
	 * a HW BUG or some kind of behavior not documented in the datasheet.
	 */
	tmp_reg = snd_soc_read(codec, AIC32X4_ADCSETUP);
	snd_soc_write(codec, AIC32X4_ADCSETUP, tmp_reg |
				AIC32X4_LADC_EN | AIC32X4_RADC_EN);
	snd_soc_write(codec, AIC32X4_ADCSETUP, tmp_reg);

	return 0;
}

static int aic32x4_remove(struct snd_soc_codec *codec)
{
	aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
	return 0;
}

static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = {
	.read = aic32x4_read,
	.write = aic32x4_write,
	.probe = aic32x4_probe,
	.remove = aic32x4_remove,
	.suspend = aic32x4_suspend,
	.resume = aic32x4_resume,
	.set_bias_level = aic32x4_set_bias_level,
};

static int aic32x4_i2c_probe(struct i2c_client *i2c,
			     const struct i2c_device_id *id)
{
	struct aic32x4_pdata *pdata = i2c->dev.platform_data;
	struct aic32x4_priv *aic32x4;
	int ret;

	aic32x4 = devm_kzalloc(&i2c->dev, sizeof(struct aic32x4_priv),
			       GFP_KERNEL);
	if (aic32x4 == NULL)
		return -ENOMEM;

	aic32x4->control_data = i2c;
	i2c_set_clientdata(i2c, aic32x4);

	if (pdata) {
		aic32x4->power_cfg = pdata->power_cfg;
		aic32x4->swapdacs = pdata->swapdacs;
		aic32x4->micpga_routing = pdata->micpga_routing;
		aic32x4->rstn_gpio = pdata->rstn_gpio;
	} else {
		aic32x4->power_cfg = 0;
		aic32x4->swapdacs = false;
		aic32x4->micpga_routing = 0;
		aic32x4->rstn_gpio = -1;
	}

	ret = snd_soc_register_codec(&i2c->dev,
			&soc_codec_dev_aic32x4, &aic32x4_dai, 1);
	return ret;
}

static int aic32x4_i2c_remove(struct i2c_client *client)
{
	snd_soc_unregister_codec(&client->dev);
	return 0;
}

static const struct i2c_device_id aic32x4_i2c_id[] = {
	{ "tlv320aic32x4", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id);

static struct i2c_driver aic32x4_i2c_driver = {
	.driver = {
		.name = "tlv320aic32x4",
		.owner = THIS_MODULE,
	},
	.probe =    aic32x4_i2c_probe,
	.remove =   aic32x4_i2c_remove,
	.id_table = aic32x4_i2c_id,
};

module_i2c_driver(aic32x4_i2c_driver);

MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver");
MODULE_AUTHOR("Javier Martin <javier.martin@vista-silicon.com>");
MODULE_LICENSE("GPL");
rk_aic32x4.c
/*
 * rk29_tlv320dac32x4.c  --  SoC audio for rockchip
 *
 * Driver for rockchip tlv320aic32x4 audio
 *
 *  This program is free software; you can redistribute  it and/or modify it
 *  under  the terms of  the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the  License, or (at your
 *  option) any later version.
 *
 *
 */

#include <linux/module.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>

#include "../codecs/tlv320aic32x4.h"
#include "card_info.h"
#include "rk_pcm.h"
#include "rk_i2s.h"

#if 0
#define	AIC_DBG(x...)	printk(KERN_INFO x)
#else
#define	AIC_DBG(x...)	do { } while (0)
#endif

#ifdef CODECHPDET
	#define HP_DET_PIN 		RK29_PIN6_PA0
#endif



static int rk29_hw_params(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *codec_dai = rtd->codec_dai;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	unsigned int pll_out = 0, dai_fmt = rtd->dai_link->dai_fmt;
	int ret;

	AIC_DBG("Enter::%s----%d\n", __FUNCTION__, __LINE__);

        printk(KERN_INFO "Machine ..... \n");
	/* set codec DAI configuration */
	ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
	if (ret < 0) {
		printk("%s():failed to set the format for codec side\n", __FUNCTION__);
		return ret;
	}

	/* set cpu DAI configuration */
	ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt);
	if (ret < 0) {
		printk("%s():failed to set the format for cpu side\n", __FUNCTION__);
		return ret;
	}

	switch(params_rate(params)) {
	case 8000:
	case 16000:
	case 24000:
	case 32000:
	case 48000:
			pll_out = 12288000;
			break;
	case 11025:
	case 22050:
	case 44100:
			pll_out = 11289600;
			break;
	default:
			printk("Enter:%s, %d, Error rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
			return -EINVAL;
			break;
	}
	AIC_DBG("Enter:%s, %d, rate=%d, pll_out = %d\n",__FUNCTION__,__LINE__,params_rate(params), pll_out);
	//pll_out = 12000000;
	snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0);
	snd_soc_dai_set_sysclk(codec_dai, 0, pll_out, SND_SOC_CLOCK_IN);

	return 0;
}

static const struct snd_soc_dapm_widget dac32x4_dapm_widgets[] = {
	SND_SOC_DAPM_LINE("Audio Out", NULL),
	SND_SOC_DAPM_LINE("Line in", NULL),
	SND_SOC_DAPM_MIC("Micn", NULL),
	SND_SOC_DAPM_MIC("Micp", NULL),
};

static const struct snd_soc_dapm_route audio_map[]= {
	{"Audio Out", NULL, "HPL"},
	{"Audio Out", NULL, "HPR"},
	{"Line in", NULL, "RINPUT1"},
	{"Line in", NULL, "LINPUT1"},
	{"Micn", NULL, "RINPUT2"},
	{"Micp", NULL, "LINPUT2"},
};

/*
 * Logic for a tlv320dac32x4 as connected on a rockchip board.
 */
static int rk29_aic32x4_init(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_codec *codec = rtd->codec;
	struct snd_soc_dapm_context *dapm = &codec->dapm;

	AIC_DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);

	/* Add specific widgets */
	snd_soc_dapm_new_controls(dapm, dac32x4_dapm_widgets,
				  ARRAY_SIZE(dac32x4_dapm_widgets));

	/* Set up specific audio path audio_mapnects */
	snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map));
		AIC_DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
	snd_soc_dapm_nc_pin(dapm, "HPL");
		AIC_DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
	snd_soc_dapm_nc_pin(dapm, "HPR");
		AIC_DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
	snd_soc_dapm_sync(dapm);
		AIC_DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
	return 0;
}

static struct snd_soc_ops rk29_ops = {
	  .hw_params = rk29_hw_params,
};

static struct snd_soc_dai_link rk29_dai = {
	.name = "AIC32X4",
	.stream_name = "AIC32X4 PCM",
	.codec_dai_name = "AIC32X4 HiFi",
	.init = rk29_aic32x4_init,
	.ops = &rk29_ops,
};

static struct snd_soc_card rockchip_aic32x4_snd_card = {
	.name = "RK_AIC32X4",
	.dai_link = &rk29_dai,
	.num_links = 1,
};

static int rockchip_aic32x4_audio_probe(struct platform_device *pdev)
{
	int ret;
	struct snd_soc_card *card = &rockchip_aic32x4_snd_card;

	card->dev = &pdev->dev;
        printk(KERN_INFO "Telecom %d, %s \n",card->dev,card->dev);

	ret = rockchip_of_get_sound_card_info(card);
	if (ret) {
		printk("%s() get sound card info failed:%d\n", __FUNCTION__, ret);
		return ret;
	}

	ret = snd_soc_register_card(card);
	if (ret)
		printk("%s() register card failed:%d\n", __FUNCTION__, ret);

	return ret;
}

static int rockchip_aic32x4_audio_remove(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

	snd_soc_unregister_card(card);

	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id rockchip_aic32x4_of_match[] = {
	{ .compatible = "rockchip-aic32X4", },
	{},
};
MODULE_DEVICE_TABLE(of, rockchip_aic32x4_of_match);
#endif /* CONFIG_OF */

static struct platform_driver rockchip_aic32x4_audio_driver = {
	.driver         = {
		.name   = "rockchip-aic32X4",
		.owner  = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
		.of_match_table = of_match_ptr(rockchip_aic32x4_of_match),
	},
	.probe          = rockchip_aic32x4_audio_probe,
	.remove         = rockchip_aic32x4_audio_remove,
};

module_platform_driver(rockchip_aic32x4_audio_driver);

/* Module information */
MODULE_AUTHOR("rockchip");
MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface");
MODULE_LICENSE("GPL");
rk_i2s.c
/*
 * Rockchip I2S ALSA SoC Digital Audio Interface(DAI)  driver
 *
 * Copyright (C) 2015 Fuzhou Rockchip Electronics Co., Ltd
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/version.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/rockchip/cpu.h>
#include <linux/rockchip/cru.h>
#include <linux/rockchip/grf.h>
#include <linux/slab.h>
#include <asm/dma.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/dmaengine_pcm.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>

#include "rk_pcm.h"
#include "rk_i2s.h"

#define CLK_SET_LATER
#define I2S_DEFAULT_FREQ	(11289600)
#define I2S_DMA_BURST_SIZE	(16) /* size * width: 16*4 = 64 bytes */
static DEFINE_SPINLOCK(lock);

#if defined(CONFIG_RK_HDMI) && defined(CONFIG_SND_RK_SOC_HDMI_I2S)
extern int snd_config_hdmi_audio(struct snd_pcm_hw_params *params);
#endif

struct rk_i2s_dev {
	struct device *dev;
	struct clk *clk; /* bclk */
	struct clk *mclk; /*mclk output only */
	struct clk *hclk; /*ahb clk */
	struct snd_dmaengine_dai_dma_data capture_dma_data;
	struct snd_dmaengine_dai_dma_data playback_dma_data;
	struct regmap *regmap;
	bool tx_start;
	bool rx_start;
	int xfer_mode; /* 0: i2s, 1: pcm */
#ifdef CLK_SET_LATER
	struct delayed_work clk_delayed_work;
#endif
};

struct rk_i2s_dev *g_rk_i2s;

static inline struct rk_i2s_dev *to_info(struct snd_soc_dai *dai)
{
	return snd_soc_dai_get_drvdata(dai);
}

static void rockchip_snd_txctrl(struct rk_i2s_dev *i2s, int on)
{
	unsigned long flags;
	unsigned int val = 0;
	int retry = 10;

	spin_lock_irqsave(&lock, flags);

	dev_dbg(i2s->dev, "%s: %d: on: %d\n", __func__, __LINE__, on);

	if (on) {
		regmap_update_bits(i2s->regmap, I2S_DMACR,
				   I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_ENABLE);

		regmap_update_bits(i2s->regmap, I2S_XFER,
				   I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK,
				   I2S_XFER_TXS_START | I2S_XFER_RXS_START);

		i2s->tx_start = true;
	} else {
		i2s->tx_start = false;

		regmap_update_bits(i2s->regmap, I2S_DMACR,
				   I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_DISABLE);


		if (!i2s->rx_start) {
			regmap_update_bits(i2s->regmap, I2S_XFER,
					   I2S_XFER_TXS_MASK |
					   I2S_XFER_RXS_MASK,
					   I2S_XFER_TXS_STOP |
					   I2S_XFER_RXS_STOP);

			regmap_update_bits(i2s->regmap, I2S_CLR,
					   I2S_CLR_TXC_MASK | I2S_CLR_RXC_MASK,
					   I2S_CLR_TXC | I2S_CLR_RXC);

			regmap_read(i2s->regmap, I2S_CLR, &val);

			/* Should wait for clear operation to finish */
			while (val) {
				regmap_read(i2s->regmap, I2S_CLR, &val);
				retry--;
				if (!retry) {
					dev_warn(i2s->dev, "fail to clear\n");
					break;
				}
			}
			dev_dbg(i2s->dev, "%s: %d: stop xfer\n",
				__func__, __LINE__);
		}
	}

	spin_unlock_irqrestore(&lock, flags);
}

static void rockchip_snd_rxctrl(struct rk_i2s_dev *i2s, int on)
{
	unsigned long flags;
	unsigned int val = 0;
	int retry = 10;

	spin_lock_irqsave(&lock, flags);

	dev_dbg(i2s->dev, "%s: %d: on: %d\n", __func__, __LINE__, on);

	if (on) {
		regmap_update_bits(i2s->regmap, I2S_DMACR,
				   I2S_DMACR_RDE_MASK, I2S_DMACR_RDE_ENABLE);

		regmap_update_bits(i2s->regmap, I2S_XFER,
				   I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK,
				   I2S_XFER_TXS_START | I2S_XFER_RXS_START);

		i2s->rx_start = true;
	} else {
		i2s->rx_start = false;

		regmap_update_bits(i2s->regmap, I2S_DMACR,
				   I2S_DMACR_RDE_MASK, I2S_DMACR_RDE_DISABLE);

		if (!i2s->tx_start) {
			regmap_update_bits(i2s->regmap, I2S_XFER,
					   I2S_XFER_TXS_MASK |
					   I2S_XFER_RXS_MASK,
					   I2S_XFER_TXS_STOP |
					   I2S_XFER_RXS_STOP);

			regmap_update_bits(i2s->regmap, I2S_CLR,
					   I2S_CLR_TXC_MASK | I2S_CLR_RXC_MASK,
					   I2S_CLR_TXC | I2S_CLR_RXC);

			regmap_read(i2s->regmap, I2S_CLR, &val);

			/* Should wait for clear operation to finish */
			while (val) {
				regmap_read(i2s->regmap, I2S_CLR, &val);
				retry--;
				if (!retry) {
					dev_warn(i2s->dev, "fail to clear\n");
					break;
				}
			}
			dev_dbg(i2s->dev, "%s: %d: stop xfer\n",
				__func__, __LINE__);
		}
	}

	spin_unlock_irqrestore(&lock, flags);
}

static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
				unsigned int fmt)
{
	struct rk_i2s_dev *i2s = to_info(cpu_dai);
	unsigned int mask = 0, val = 0;
	int ret = 0;
	unsigned long flags;

	spin_lock_irqsave(&lock, flags);

	mask = I2S_CKR_MSS_MASK;
	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
	case SND_SOC_DAIFMT_CBS_CFS:
		/* Codec is slave, so set cpu master */
		val = I2S_CKR_MSS_MASTER;
		break;
	case SND_SOC_DAIFMT_CBM_CFM:
		/* Codec is master, so set cpu slave */
		val = I2S_CKR_MSS_SLAVE;
		break;
	default:
		ret = -EINVAL;
		goto err_fmt;
	}

	regmap_update_bits(i2s->regmap, I2S_CKR, mask, val);

	mask = I2S_TXCR_IBM_MASK;
	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_RIGHT_J:
		val = I2S_TXCR_IBM_RSJM;
		break;
	case SND_SOC_DAIFMT_LEFT_J:
		val = I2S_TXCR_IBM_LSJM;
		break;
	case SND_SOC_DAIFMT_I2S:
		val = I2S_TXCR_IBM_NORMAL;
		break;
	default:
		ret = -EINVAL;
		goto err_fmt;
	}

	regmap_update_bits(i2s->regmap, I2S_TXCR, mask, val);

	mask = I2S_RXCR_IBM_MASK;
	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
	case SND_SOC_DAIFMT_RIGHT_J:
		val = I2S_RXCR_IBM_RSJM;
		break;
	case SND_SOC_DAIFMT_LEFT_J:
		val = I2S_RXCR_IBM_LSJM;
		break;
	case SND_SOC_DAIFMT_I2S:
		val = I2S_RXCR_IBM_NORMAL;
		break;
	default:
		ret = -EINVAL;
		goto err_fmt;
	}

	regmap_update_bits(i2s->regmap, I2S_RXCR, mask, val);

err_fmt:

	spin_unlock_irqrestore(&lock, flags);
	return ret;
}

static int rockchip_i2s_hw_params(struct snd_pcm_substream *substream,
				  struct snd_pcm_hw_params *params,
				  struct snd_soc_dai *dai)
{
	struct rk_i2s_dev *i2s = to_info(dai);
	unsigned int val = 0;
	unsigned long flags;

	spin_lock_irqsave(&lock, flags);

	dev_dbg(i2s->dev, "%s: %d\n", __func__, __LINE__);

	switch (params_format(params)) {
	case SNDRV_PCM_FORMAT_S8:
		val |= I2S_TXCR_VDW(8);
		break;
	case SNDRV_PCM_FORMAT_S16_LE:
		val |= I2S_TXCR_VDW(16);
		break;
	case SNDRV_PCM_FORMAT_S20_3LE:
		val |= I2S_TXCR_VDW(20);
		break;
	case SNDRV_PCM_FORMAT_S24_LE:
	case SNDRV_PCM_FORMAT_S24_3LE:
		val |= I2S_TXCR_VDW(24);
		break;
	case SNDRV_PCM_FORMAT_S32_LE:
		val |= I2S_TXCR_VDW(32);
		break;
	default:
		dev_err(i2s->dev, "invalid fmt: %d\n", params_format(params));
		spin_unlock_irqrestore(&lock, flags);
		return -EINVAL;
	}

	switch (params_channels(params)) {
	case I2S_CHANNEL_8:
		val |= I2S_TXCR_CHN_8;
		break;
	case I2S_CHANNEL_6:
		val |= I2S_TXCR_CHN_6;
		break;
	case I2S_CHANNEL_4:
		val |= I2S_TXCR_CHN_4;
		break;
	case I2S_CHANNEL_2:
		val |= I2S_TXCR_CHN_2;
		break;
	default:
		dev_err(i2s->dev, "invalid channel: %d\n",
			params_channels(params));
		spin_unlock_irqrestore(&lock, flags);
		return -EINVAL;
	}

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		regmap_update_bits(i2s->regmap, I2S_TXCR,
				   I2S_TXCR_VDW_MASK |
				   I2S_TXCR_CSR_MASK,
				   val);
	} else {
		regmap_update_bits(i2s->regmap, I2S_RXCR,
				   I2S_RXCR_VDW_MASK, val);
	}

	regmap_update_bits(i2s->regmap, I2S_DMACR,
			   I2S_DMACR_TDL_MASK | I2S_DMACR_RDL_MASK,
			   I2S_DMACR_TDL(16) | I2S_DMACR_RDL(16));

#if defined(CONFIG_RK_HDMI) && defined(CONFIG_SND_RK_SOC_HDMI_I2S)
	snd_config_hdmi_audio(params);
#endif
	spin_unlock_irqrestore(&lock, flags);

	return 0;
}

static int rockchip_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
				struct snd_soc_dai *dai)
{
	struct rk_i2s_dev *i2s = to_info(dai);
	int ret = 0;

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
	case SNDRV_PCM_TRIGGER_RESUME:
	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			rockchip_snd_rxctrl(i2s, 1);
		else
			rockchip_snd_txctrl(i2s, 1);
		break;
	case SNDRV_PCM_TRIGGER_SUSPEND:
	case SNDRV_PCM_TRIGGER_STOP:
	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
			rockchip_snd_rxctrl(i2s, 0);
		else
			rockchip_snd_txctrl(i2s, 0);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	return ret;
}

static int rockchip_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
				   int clk_id, unsigned int freq, int dir)
{
	struct rk_i2s_dev *i2s = to_info(cpu_dai);
	int ret;

	ret = clk_set_rate(i2s->clk, freq);
	if (ret)
		dev_err(i2s->dev, "fail set clk: freq: %d\n", freq);

	return ret;
}

static int rockchip_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
				   int div_id, int div)
{
	struct rk_i2s_dev *i2s = to_info(cpu_dai);
	unsigned int val = 0;
	unsigned long flags;

	spin_lock_irqsave(&lock, flags);

	dev_dbg(i2s->dev, "%s: div_id=%d, div=%d\n", __func__, div_id, div);

	switch (div_id) {
	case ROCKCHIP_DIV_BCLK:
		val |= I2S_CKR_TSD(div);
		val |= I2S_CKR_RSD(div);
		regmap_update_bits(i2s->regmap, I2S_CKR,
				   I2S_CKR_TSD_MASK | I2S_CKR_RSD_MASK,
				   val);
		break;
	case ROCKCHIP_DIV_MCLK:
		val |= I2S_CKR_MDIV(div);
		regmap_update_bits(i2s->regmap, I2S_CKR,
				   I2S_CKR_MDIV_MASK, val);
		break;
	default:
		spin_unlock_irqrestore(&lock, flags);
		return -EINVAL;
	}

	spin_unlock_irqrestore(&lock, flags);

	return 0;
}

static int rockchip_i2s_dai_probe(struct snd_soc_dai *dai)
{
	struct rk_i2s_dev *i2s = to_info(dai);

	dai->capture_dma_data = &i2s->capture_dma_data;
	dai->playback_dma_data = &i2s->playback_dma_data;

	return 0;
}

static struct snd_soc_dai_ops rockchip_i2s_dai_ops = {
	.trigger = rockchip_i2s_trigger,
	.hw_params = rockchip_i2s_hw_params,
	.set_fmt = rockchip_i2s_set_fmt,
	.set_clkdiv = rockchip_i2s_set_clkdiv,
	.set_sysclk = rockchip_i2s_set_sysclk,
};

#define ROCKCHIP_I2S_RATES SNDRV_PCM_RATE_8000_192000
#define ROCKCHIP_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
			      SNDRV_PCM_FMTBIT_S20_3LE | \
			      SNDRV_PCM_FMTBIT_S24_LE | \
			      SNDRV_PCM_FORMAT_S32_LE)

struct snd_soc_dai_driver rockchip_i2s_dai[] = {
	{
		.probe = rockchip_i2s_dai_probe,
		.name = "rockchip-i2s.0",
		.id = 0,
		.playback = {
			.channels_min = 2,
			.channels_max = 8,
			.rates = ROCKCHIP_I2S_RATES,
			.formats = ROCKCHIP_I2S_FORMATS,
		},
		.capture = {
			.channels_min = 2,
			.channels_max = 2,
			.rates = ROCKCHIP_I2S_RATES,
			.formats = ROCKCHIP_I2S_FORMATS,
		},
		.ops = &rockchip_i2s_dai_ops,
		.symmetric_rates = 1,
	},
	{
		.probe = rockchip_i2s_dai_probe,
		.name = "rockchip-i2s.1",
		.id = 1,
		.playback = {
			.channels_min = 2,
			.channels_max = 2,
			.rates = ROCKCHIP_I2S_RATES,
			.formats = ROCKCHIP_I2S_FORMATS,
		},
		.capture = {
			.channels_min = 2,
			.channels_max = 2,
			.rates = ROCKCHIP_I2S_RATES,
			.formats = ROCKCHIP_I2S_FORMATS,
		},
		.ops = &rockchip_i2s_dai_ops,
		.symmetric_rates = 1,
	},
};

static const struct snd_soc_component_driver rockchip_i2s_component = {
	.name = "rockchip-i2s",
};

#ifdef CONFIG_PM
static int rockchip_i2s_runtime_suspend(struct device *dev)
{
	struct rk_i2s_dev *i2s = dev_get_drvdata(dev);

	dev_dbg(i2s->dev, "%s\n", __func__);
	return 0;
}

static int rockchip_i2s_runtime_resume(struct device *dev)
{
	struct rk_i2s_dev *i2s = dev_get_drvdata(dev);

	dev_dbg(i2s->dev, "%s\n", __func__);
	return 0;
}
#else
#define i2s_runtime_suspend NULL
#define i2s_runtime_resume NULL
#endif

extern int snd_start_hdmi_in_audio_route(void);

#ifdef CLK_SET_LATER
static void set_clk_later_work(struct work_struct *work)
{
	struct rk_i2s_dev *i2s = container_of(work, struct rk_i2s_dev,
						 clk_delayed_work.work);

	clk_set_rate(i2s->clk, I2S_DEFAULT_FREQ);
	if (!IS_ERR(i2s->mclk))
		clk_set_rate(i2s->mclk, I2S_DEFAULT_FREQ);
	//snd_start_hdmi_in_audio_route();
}
#endif

static bool rockchip_i2s_wr_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case I2S_TXCR:
	case I2S_RXCR:
	case I2S_CKR:
	case I2S_DMACR:
	case I2S_INTCR:
	case I2S_XFER:
	case I2S_CLR:
	case I2S_TXDR:
		return true;
	default:
		return false;
	}
}

static bool rockchip_i2s_rd_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case I2S_TXCR:
	case I2S_RXCR:
	case I2S_CKR:
	case I2S_DMACR:
	case I2S_INTCR:
	case I2S_XFER:
	case I2S_CLR:
	case I2S_RXDR:
	case I2S_FIFOLR:
	case I2S_INTSR:
		return true;
	default:
		return false;
	}
}

static bool rockchip_i2s_volatile_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case I2S_INTSR:
	case I2S_CLR:
		return true;
	default:
		return false;
	}
}

static bool rockchip_i2s_precious_reg(struct device *dev, unsigned int reg)
{
	switch (reg) {
	default:
		return false;
	}
}

static const struct regmap_config rockchip_i2s_regmap_config = {
	.reg_bits = 32,
	.reg_stride = 4,
	.val_bits = 32,
	.max_register = I2S_RXDR,
	.writeable_reg = rockchip_i2s_wr_reg,
	.readable_reg = rockchip_i2s_rd_reg,
	.volatile_reg = rockchip_i2s_volatile_reg,
	.precious_reg = rockchip_i2s_precious_reg,
	.cache_type = REGCACHE_FLAT,
};

static int rockchip_i2s_probe(struct platform_device *pdev)
{
	struct device_node *node = pdev->dev.of_node;
	struct rk_i2s_dev *i2s;
	struct resource *res;
	void __iomem *regs;
	int ret;

	ret = of_property_read_u32(node, "i2s-id", &pdev->id);
	if (ret < 0) {
		dev_err(&pdev->dev, "Property 'i2s-id' missing or invalid\n");
		ret = -EINVAL;
		goto err;
	}

	if (soc_is_rk3126b()) {
		int sdi_src = 0;

		/* rk3126b has no i2s1 controller(i2s_8ch) */
		if (1 == pdev->id) {
			pr_info("rk3126b has no i2s1 controller\n");
			ret = -ENODEV;
			goto err;
		}

		ret = of_property_read_u32(node, "sdi_source",
					   &sdi_src);
		if (ret < 0)
			sdi_src = 0;

		if (1 == sdi_src) {
			int val;

			/*GRF_SOC_CON*/
			val = readl_relaxed(RK_GRF_VIRT + 0x0140);
			val = val | 0x04000400;
			writel_relaxed(val, RK_GRF_VIRT + 0x0140);
		}
	}

	i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL);
	if (!i2s) {
		dev_err(&pdev->dev, "Can't allocate rk_i2s_dev\n");
		ret = -ENOMEM;
		goto err;
	}

	i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk");
	if (IS_ERR(i2s->hclk)) {
		dev_err(&pdev->dev, "Can't retrieve i2s bus clock\n");
		ret = PTR_ERR(i2s->hclk);
		goto err;
	} else {
		clk_prepare_enable(i2s->hclk);
	}

	i2s->clk = devm_clk_get(&pdev->dev, "i2s_clk");
	if (IS_ERR(i2s->clk)) {
		dev_err(&pdev->dev, "Can't retrieve i2s clock\n");
		ret = PTR_ERR(i2s->clk);
		goto err;
	}
#ifdef CLK_SET_LATER
	INIT_DELAYED_WORK(&i2s->clk_delayed_work, set_clk_later_work);
	schedule_delayed_work(&i2s->clk_delayed_work, msecs_to_jiffies(10));
#else
	clk_set_rate(i2s->clk, I2S_DEFAULT_FREQ);
#endif
	clk_prepare_enable(i2s->clk);

	i2s->mclk = devm_clk_get(&pdev->dev, "i2s_mclk");
	if (IS_ERR(i2s->mclk)) {
		dev_info(&pdev->dev, "i2s%d has no mclk\n", pdev->id);
	} else {
	#ifndef CLK_SET_LATER
		clk_set_rate(i2s->mclk, I2S_DEFAULT_FREQ);
	#endif
		clk_prepare_enable(i2s->mclk);
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	regs = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(regs)) {
		ret = PTR_ERR(regs);
		goto err;
	}

	i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,
					    &rockchip_i2s_regmap_config);
	if (IS_ERR(i2s->regmap)) {
		dev_err(&pdev->dev,
			"Failed to initialise managed register map\n");
		ret = PTR_ERR(i2s->regmap);
		goto err;
	}

	i2s->playback_dma_data.addr = res->start + I2S_TXDR;
	i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	i2s->playback_dma_data.maxburst = I2S_DMA_BURST_SIZE;

	i2s->capture_dma_data.addr = res->start + I2S_RXDR;
	i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
	i2s->capture_dma_data.maxburst = I2S_DMA_BURST_SIZE;

	i2s->tx_start = false;
	i2s->rx_start = false;

	i2s->dev = &pdev->dev;
	dev_set_drvdata(&pdev->dev, i2s);

	g_rk_i2s = i2s;

	pm_runtime_enable(&pdev->dev);
	if (!pm_runtime_enabled(&pdev->dev)) {
		ret = rockchip_i2s_runtime_resume(&pdev->dev);
		if (ret)
			goto err_pm_disable;
	}

	ret = snd_soc_register_component(&pdev->dev, &rockchip_i2s_component,
					 &rockchip_i2s_dai[pdev->id], 1);

	if (ret) {
		dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
		ret = -ENOMEM;
		goto err_suspend;
	}

	ret = rockchip_pcm_platform_register(&pdev->dev);
	if (ret) {
		dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
		goto err_unregister_component;
	}

	ret = of_property_read_u32(node, "rockchip,xfer-mode", &i2s->xfer_mode);
	if (ret < 0)
		i2s->xfer_mode = I2S_XFER_MODE;

	if (PCM_XFER_MODE == i2s->xfer_mode) {
		regmap_update_bits(i2s->regmap, I2S_TXCR,
				   I2S_TXCR_TFS_MASK,
				   I2S_TXCR_TFS_PCM);
		regmap_update_bits(i2s->regmap, I2S_RXCR,
				   I2S_RXCR_TFS_MASK,
				   I2S_RXCR_TFS_PCM);
	}

	rockchip_snd_txctrl(i2s, 0);
	rockchip_snd_rxctrl(i2s, 0);

	return 0;

err_unregister_component:
	snd_soc_unregister_component(&pdev->dev);
err_suspend:
	if (!pm_runtime_status_suspended(&pdev->dev))
		rockchip_i2s_runtime_suspend(&pdev->dev);
err_pm_disable:
	pm_runtime_disable(&pdev->dev);
err:
	return ret;
}

static int rockchip_i2s_remove(struct platform_device *pdev)
{
	struct rk_i2s_dev *i2s = dev_get_drvdata(&pdev->dev);

	pm_runtime_disable(&pdev->dev);
	if (!pm_runtime_status_suspended(&pdev->dev))
		rockchip_i2s_runtime_suspend(&pdev->dev);

	if (!IS_ERR(i2s->mclk))
		clk_disable_unprepare(i2s->mclk);

	clk_disable_unprepare(i2s->clk);
	clk_disable_unprepare(i2s->hclk);
	rockchip_pcm_platform_unregister(&pdev->dev);
	snd_soc_unregister_component(&pdev->dev);

	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id rockchip_i2s_match[] = {
	{ .compatible = "rockchip-i2s", },
	{},
};
MODULE_DEVICE_TABLE(of, rockchip_i2s_match);
#endif

#ifdef CONFIG_PM_SLEEP
static int rockchip_i2s_suspend(struct device *dev)
{
	struct rk_i2s_dev *i2s = dev_get_drvdata(dev);

	dev_dbg(i2s->dev, "%s\n", __func__);
	return pinctrl_pm_select_sleep_state(dev);
}

static int rockchip_i2s_resume(struct device *dev)
{
	struct rk_i2s_dev *i2s = dev_get_drvdata(dev);
	int ret;

	ret = pm_runtime_get_sync(dev);
	if (ret < 0)
		return ret;
	ret = pinctrl_pm_select_default_state(dev);
	if (ret < 0)
		return ret;
	ret = regmap_reinit_cache(i2s->regmap, &rockchip_i2s_regmap_config);

	if (PCM_XFER_MODE == i2s->xfer_mode) {
		regmap_update_bits(i2s->regmap, I2S_TXCR,
				   I2S_TXCR_TFS_MASK,
				   I2S_TXCR_TFS_PCM);
		regmap_update_bits(i2s->regmap, I2S_RXCR,
				   I2S_RXCR_TFS_MASK,
				   I2S_RXCR_TFS_PCM);
	}

	pm_runtime_put(dev);

	dev_dbg(i2s->dev, "%s\n", __func__);
	return ret;
}
#endif

static const struct dev_pm_ops rockchip_i2s_pm_ops = {
	SET_RUNTIME_PM_OPS(rockchip_i2s_runtime_suspend, rockchip_i2s_runtime_resume,
			   NULL)
	SET_SYSTEM_SLEEP_PM_OPS(rockchip_i2s_suspend, rockchip_i2s_resume)
};

static struct platform_driver rockchip_i2s_driver = {
	.probe  = rockchip_i2s_probe,
	.remove = rockchip_i2s_remove,
	.driver = {
		.name   = "rockchip-i2s",
		.owner  = THIS_MODULE,
		.of_match_table = of_match_ptr(rockchip_i2s_match),
		.pm	= &rockchip_i2s_pm_ops,
	},
};

static int __init rockchip_i2s_init(void)
{
	return platform_driver_register(&rockchip_i2s_driver);
}
subsys_initcall_sync(rockchip_i2s_init);

static void __exit rockchip_i2s_exit(void)
{
	platform_driver_unregister(&rockchip_i2s_driver);
}
module_exit(rockchip_i2s_exit);

MODULE_AUTHOR("Sugar <sugar.zhang@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip I2S Controller Driver");
MODULE_LICENSE("GPL v2");

  • Hi,

    Ok, so you are connecting a SoC to the AIC3204 digital audio interface in the EVM through J14, how about your I2C signals?

    I'd like to know the following also:

    • Is the codec master/slave?
    • What's the MCLK, WCLK and BCLK frequency? Have you checked them on the scope if they are correct and please provide the capture?
    • Can you get the i2cdump for the codec registers?
    • Are you able to communicate with the codec I2C?
    • If you just use the EVM and connect to PC, are you able to see it in device manager and test with the EVM?

    Regards,

    Peter

  • #I2C signals are going through J6.

    I'd like to know the following also:

    • Is the codec master/slave? 
    • What's the MCLK, WCLK, and BCLK frequency? Have you checked them on the scope if they are correct and please provide the capture?
    •  
    • Can you get the i2cdump for the codec registers?
    • # When we disable the driver then we can able to read its register through the I2Cget command.
    • Are you able to communicate with the codec I2C?
    • # I2c probe is getting called. if we enable the driver then we are getting UU, when we tried to read its address through the I2Cget command.
    • If you just use the EVM and connect to the PC, are you able to see it in the device manager and test with the EVM?
    • # we are able to see it in device manager but never tested further.

      Rest details i will update you in a while.

  • #I2C signals are going through J6.

    I'd like to know the following also:

    • Is the codec master/slave? 
    • What's the MCLK, WCLK, and BCLK frequency? Have you checked them on the scope if they are correct and please provide the capture?
    •  
    • Can you get the i2cdump for the codec registers?
    • # When we disable the driver then we can able to read its register through the I2Cget command.
    • Are you able to communicate with the codec I2C?
    • # I2c probe is getting called. if we enable the driver then we are getting UU, when we tried to read its address through the I2Cget command.
    • If you just use the EVM and connect to the PC, are you able to see it in the device manager and test with the EVM?
    • # we are able to see it in device manager but never tested further.

      Rest details i will update you in a while.


  • Hi,

    You need to make sure MCLK is provided to codec and your I2C transactions are working as expected.

    If codec is slave then WCLK and BCLK should come from your SoC and then get the i2cdump so we can verify the register settings.

    Regards,

    Peter

  • Hi,

    MCLK is provided to the codec. The current frequency we are providing is 11.2 MHZ.

    One more thing we have Kernel Version 3.10.79 (Tossug Baby Fish). Is TLV320AIC32x4 is compatible with this Kernel Version?

    Thanks in advance.

  • Hi,

    As to sound card register, I have not found code involve following information.

    static const struct of_device_id aic32x4_of_id[] = {
    { .compatible = "ti,tlv320aic32x4", },
    { .compatible = "ti,tlv320aic32x6", },
    { /* senitel */ }
    };
    MODULE_DEVICE_TABLE(of, aic32x4_of_id);

    static struct i2c_driver aic32x4_i2c_driver = {
    .driver = {
    .name = "tlv320aic32x4",
    .of_match_table = aic32x4_of_id,
    },
    .probe = aic32x4_i2c_probe,
    .remove = aic32x4_i2c_remove,
    .id_table = aic32x4_i2c_id,
    };

    As you know, DTS file has referenced following info. But system have not found which sound called "ti,tlv32aic32x4"

    compatible = "ti,tlv320aic32x4";

  • Hi shenghao,

    Let me add this piece of code in driver and let you know.

    Thanks. 

  • Hi, shenghao,


    One more thing we have Kernel Version 3.10.79 (Tossug Baby Fish). Is TLV320AIC32x4 is compatible with this Kernel Version?

    because the following link suggested that TLV320AIC32x4 is supported by kernel version "v4.19 or later"

    e2e.ti.com/.../faq-linux-drivers-device-drivers-for-aic31xx-dac31xx-aic325x-aic320x-aic326x-aic321x

    Please let us know.

  • Sorry, I have not tested the code on kernel  3.10.79, but in my view, although kernel version is different, yet the processing sequence, architecture and API  is same. You will have a try to port the driver for kernel 4.19 to kernel 3.10.79. If you met some issue, be free to post your issue over E2E.

  • Hi, shenghao,

    We are able to register TLV320AIC32x4  on our Kernel version 3.10.79 (Tossug Baby Fish). But whenever we are playing audio/video files on our device. Audio is not coming out from the TLV320AIC32x4  evaluation kit. Please suggest how to debug.

    Below are the logs that the device got registered.

    D/AudioHardwareTiny( 165): Device : 0x80400
    D/AudioHardwareTiny( 165): SampleRate : 44100
    D/AudioHardwareTiny( 165): Channels : 2
    D/AudioHardwareTiny( 165): Formate : 0
    D/AudioHardwareTiny( 165): PreiodSize : 2048
    D/AudioHardwareTiny( 165): The current HDMI is DVI mode


    shell@ccc:/sys/class/sound $ ls -l
    lrwxrwxrwx root root 2013-01-21 08:54 card0 -> ../../devices/rockchip-aic32X4.26/sound/card0
    lrwxrwxrwx root root 2013-01-21 08:54 card1 -> ../../devices/rockchip-spdif-card.25/sound/card1
    lrwxrwxrwx root root 2013-01-21 08:56 controlC0 -> ../../devices/rockchip-aic32X4.26/sound/card0/controlC0
    lrwxrwxrwx root root 2013-01-21 08:56 controlC1 -> ../../devices/rockchip-spdif-card.25/sound/card1/controlC1
    lrwxrwxrwx root root 2013-01-21 08:56 pcmC0D0c -> ../../devices/rockchip-aic32X4.26/sound/card0/pcmC0D0c
    lrwxrwxrwx root root 2013-01-21 08:56 pcmC0D0p -> ../../devices/rockchip-aic32X4.26/sound/card0/pcmC0D0p
    lrwxrwxrwx root root 2013-01-21 08:56 pcmC1D0p -> ../../devices/rockchip-spdif-card.25/sound/card1/pcmC1D0p
    lrwxrwxrwx root root 2013-01-21 08:56 timer -> ../../devices/virtual/sound/timer

    <6>[ 1.523683] ALSA device list:
    <6>[ 1.523690] #0: RK_AIC32X4
    <6>[ 1.523696] #1: RK-SPDIF-CARD

    Thanks,

    Amit Sharma

  • Tell me the playback command, please

    And dump the register during playback.

  • Hi shenghao,

    Actually, we have android os loaded over our Kernel. So I have opened youtube and played a video and expected audio through a connected TLV320AIC32x4  evolution kit.

    This is how I am testing it.

  • Use tinyplay to play a wav file. You can add log into every function in the  code, make sure the code has been executed during playback. 

  • Ok. Let me check. I will update you after checking.

  • Hi shenghao,

    I am unable to take the register dump it is showing a resource busy message.

    Please check the screenshot.

    Please let me know which register i need to set to make it work.

  • Hi shenghao,

    Please suggest. 

  • You can use i2cdump to get other i2c device register? Are they OK? According to the log above, it seem that i2c communication abnormal. Pls check the hardware connection. You can add a test code to write a value to an RW register, and then read back into the code, and print it into log, and check the log. 

    You can use i2cget to get one register if the i2c device? Are they OK, either? 

    Following is the example of I2C tool

    1)i2cdetect -y 0
    Detect all the device on i2c bus 0

    2)i2cdump -y -f 0 4c
    0 is i2c bus no
    4c is 7-bit i2c device address

    3)i2cset -f -y 0 4c 2 0
    0 is i2c bus no
    4c is 7-bit i2c device address
    2 is the reg no
    0 the value to be set to reg0x2

    4) How to dump regs of B0x00P0x0
    i2cset -y -f 0 4c 0 0
    i2cset -y -f 0 4c 7f 0
    i2cset -y -f 0 4c 0 0
    i2cdump -y -f 0 4c

  • Hi Shenghao.

    Yes, there was some issue I2c communication. We have sorted it out. Please look at the below image.

    But still no audio. While playing media the value of the register got changed but no success. Please suggest what else need to do.

    Waiting for your reply.

  • Glad to hear from your big progress on i2c access. Tell me what the aplay command you use and where the error log "Error applying setting ..." in the code. I can't find this log in the open source code. One more thing, after you run the aplay command, kindly use scope to measure the bck and WS signal to the 32x4. Thanks.

  • Hi Shenghao,

    The Error "Error applying setting" is coming from code file "drivers/pinctrl/core.c". In Method "pinctrl_select_state" while 

    unapply_new_state:
    dev_err(p->dev, "Error applying setting, reverse things back\n");

    Second, aplay is not installed so as of now I am using the following command to play the file : 

    "am start -a android.intent.action.VIEW -d file:///storage/sdcard0/audio.mp3 -t audio/mp3".

    Can I use am command to play file and aplay is must?

    Thanks in advance.

  • Hi Shenghao,

    Can you tell which registers need to be set and where to set these registers in the driver code? Because when i set any register in Probe it will overwrite.

    Thanks,

  • You can use tinyplay, too. am command is the similar tool to debug.

  • Hi Shenghao,

    After seeing registered values can you suggest which registers need to be set and where to set these registers in the driver code? Because when I set any register in Probe it will overwrite.

    When I search the same issue in the TI community everyone is saying that the register needs to be set, but can you please tell me which specific register I need to set for audio to work.

    Thanks,

  • Kindly share the Page 1 register too. Before I give your advice on this issue, you may as well go thru this document, it will help you too.

    slaa557_TLV320AIC3204 Application Reference Guide.pdf

  • How to use tinyplay: adb shell tinyplay /data/01kHz-24dbfs_48kHz_S16_LE.wav

    According to the register dump, following status bit is abnormal
    PAGE0x00-REGISTER0x25-D7=0 : LeftDACPoweredDown
    PAGE0x00-REGISTER0x25-D3=0 : RightDACPoweredDown

  • Hi Shenghao,

    Let me work on your input and I will get back to you.

    Thanks.

  • Hi, Amit.

    Any progress? Looking forward to your feedback:)

  • Hi Shenghao,

    Sorry for the late reply.

    We have tried to set the following register which you had mentioned 

    PAGE0x00-REGISTER0x25-D7=0 : LeftDACPoweredDown
    PAGE0x00-REGISTER0x25-D3=0 : RightDACPoweredDown

    but still no success. 

    Can you also guide how to check the page 1 register? Means is any command available for it or I need to print from code only. We are using the i2cdump command to read the registers. We have followed the reference guide and tried to set the register mentioned in the reference guide. But still no success.

    Thanks in advance...

  • PAGE0x00-REGISTER0x25 is the read-only register, you can't set it.

    Kindly share the run-time log.

    how to select page 1:

    i2cset -y -f 2 0x18 0x0 0x1

    i2cdump -y -f 2 0x18

    Is it possible to set up a conf-call and share the screen? I can remotely support you how to debug.

  • Hi Shenghao,

    Yes, conf-call is possible. I will create and set up and ley you know.

    Thanks,

  • Hi Shenghao,

    Please share your Email id so I can send you the conf link for tomorrow for the time feasible to you.

    Thanks in advance.

  • Shenghao-ding@ti.com

  • After yesterday's conf-call support, I2C communication to the EVM is OK and codec register to the system is fine.

    Next step is to focus on the following mixer controls. Set following controls properly before start playback.

  • Hi Shenghao,

    Thanks for your time yesterday, I will work on amix settings and get back to you.

    Thanks,

    Amit Sharma

  • Hi Shenghao,

    Sorry for replying late. Thanks for your guidance to set amix control, same I have done(change amix settings) and now audio is coming out. but the following are the scenario now :

    1) Audio is choppy.

    2) I had set it from amix control command which got reset on every reboot. Should I manage it from Driver?

    3) How to enable mic. 

    Please suggest.

    Thanks in advance...

  • 1) choppy audio: kindlu check whether the bit clk and WS clk match the bitstream with scope.
    2) Kindly share what the commands you used. And share the log with me?
    3) Following controls are for recorder:
    SOC_DOUBLE_R("Mic PGA Switch", AIC32X4_LMICPGAVOL,
    AIC32X4_RMICPGAVOL, 7, 0x01, 1),

    SOC_SINGLE("ADCFGA Left Mute Switch", AIC32X4_ADCFGA, 7, 1, 0), 
    SOC_SINGLE("ADCFGA Right Mute Switch", AIC32X4_ADCFGA, 3, 1, 0),

    SOC_DOUBLE_R_S_TLV("ADC Level Volume", AIC32X4_LADCVOL,
    AIC32X4_RADCVOL, 0, -0x18, 0x28, 6, 0, tlv_adc_vol),
    SOC_DOUBLE_R_TLV("PGA Level Volume", AIC32X4_LMICPGAVOL,
    AIC32X4_RMICPGAVOL, 0, 0x5f, 0, tlv_step_0_5),

    In slaa557.pdf, charpter 4.0.4 Stereo ADC with 48ksps Sample Rate and High Performance introduces the detail on recorder.

  • Hi Shenghao,

    ## Choppy Audio: You are right WCLK fluctuating and when this WCLK goes down, I hear no audio.

    For reference, I have attached the video.

    The amix commands I used for audio are as follow : 

    1) amix 'HPL Output Mixer L_DAC Switch' 1

    2) amix 'HPR Output Mixer R_DAC Switch' 1

    3) amix 'HP DAC Playback Switch' 1

    The above 3 commands enable audio, but choppy one.


    ========================= 2nd issue =================

    When i tried the set register through driver code. it set the register but no audio. The code i enable in driver is as follow. It shows same values for amix control but no audio. But Register 12,13,16,17 on Page1 is not changing by below code.

    SOC_DAPM_SINGLE("L_DAC Switch", AIC32X4_HPLROUTE, 3, 1, 1) 

    SOC_DAPM_SINGLE("R_DAC Switch", AIC32X4_HPRROUTE, 3, 1, 1) 

     SOC_DOUBLE_R("HP DAC Playback Switch", AIC32X4_HPLGAIN, AIC32X4_HPRGAIN, 6, 0x01, 0)

    ========================= 3rd issue =================

    No able to enable mic. 

    Thanks...

  • For the first issue, do you mean whether the WS clk only output for a very shiort time, then stop?

    For the second issue, kindly add log to check whether the code was called?

    For the 3rd issue, we may as well setup a conf-call this Friday.

  • Hi,

    For the first issue, do you mean whether the WS clk only output for a very short time, then stop?  : No WS Clock down alternately for 1 second and come up for 2 seconds. Audio issues start when it goes down and audio remains fine when WS Clock goes up. So throughout the full audio, the audio goes down alternately for 1 second and remains good for alternately 2 seconds.

    # For the second issue, kindly add a log to check whether the code was called? : Yes code is running even when I run amix command after boot, the changes reflect and are visible on amix output. 

    # For the 3rd issue, we may as well set up a conf-call this Friday. : Sure we will do a conference on Friday, till then please let me know what can I try, So I will have logs when we connect on Friday.

    Thanks.

  • Hi Shenghao,

    Hope you are doing well here. With lots of tries, we are able to start the capture. But now an interesting issue is coming that our capture is capturing only 7 seconds of the file after that capture stops working. This 7-8 second is consistent. Please help to get rid of it.

    Thanks.

  • Hi Amit

    As discussed over conf-call, 

    P0R0x12 and P0R0x13 is the clk register

    Kindly first power off Left and Right ADC Channels

    Then set the value to P0R0x12 and P0R0x13

    Then power on Left and Right ADC Channels

  • Hi Shenghao,

    We are able to find out the route cause for the 7-8 sec mic issue. What is happening in every 7 to 8-sec "SND_SOC_BIAS_STANDBY" event is triggered and it is written in the driver that if "SND_SOC_BIAS_STANDBY" event comes it will do following: 

    /* Switch off PLL */
    snd_soc_update_bits(codec, AIC32X4_PLLPR,
    AIC32X4_PLLEN, 0);

    /* Switch off NDAC Divider */
    snd_soc_update_bits(codec, AIC32X4_NDAC,
    AIC32X4_NDACEN, 0);

    /* Switch off MDAC Divider */
    snd_soc_update_bits(codec, AIC32X4_MDAC,
    AIC32X4_MDACEN, 0);

    /* Switch off NADC Divider */
    snd_soc_update_bits(codec, AIC32X4_NADC,
    AIC32X4_NADCEN, 0);

    /* Switch off MADC Divider */
    snd_soc_update_bits(codec, AIC32X4_MADC,
    AIC32X4_MADCEN, 0);

    /* Switch off BCLK_N Divider */
    snd_soc_update_bits(codec, AIC32X4_BCLKN,
    AIC32X4_BCLKEN, 0);

     As a result, clk switched off and so as the capture. If I comment on these lines the 7-sec issue got resolved. Why does this "SND_SOC_BIAS_STANDBY" event come every 7-8 sec while the codec is in use.

    Thanks,

    Amit Sharma

  • Hi Amit,

    As far as I know, it is a system behavior. When capturing, app should vote to system against entering into standby mode. Have you used tinycap or arecord command to run this test? If so, kindly disable low power mode or sleep mode in your system.

    BR

    Shenghao

  • Ok Shenghao,

    This I will check because I think this is related to the system not related to the "TLV320AIC32X4" Codec. Please help me with below point : 

    # For the first issue, do you mean whether the WS clk only output for a very short time, then stop?  : No WS Clock down alternately for 1 second and come up for 2 seconds. Audio issues start when it goes down and audio remains fine when WS Clock goes up. So throughout the full audio, the audio goes down alternately for 1 second and remains good for alternately 2 seconds.

    What and where to check as no log is coming in this case.

  • What about conf-call on your first issue?