1. pinctrl配置

高通平台配置一组I2S一般对应四个或者六个GPIO,

QUIN MI2S对应GPIO88 GPIO91 GPIO92 GPIO93

对应代码文件为:kernel/msm-3.18/arch/arm/boot/dts/qcom/msm8953-pinctrl.dtsi

		pri-tlmm-lines {
			pri_tlmm_lines_act: pri_tlmm_lines_act {
				mux {
					pins = "gpio91", "gpio88", "gpio93";
					function = "pri_mi2s";
				};

				config {
					pins = "gpio91", "gpio88", "gpio93";
					drive-strength = <8>;
				};
			};

			pri_tlmm_lines_sus: pri_tlmm_lines_sus {
				mux {
					pins = "gpio91", "gpio88", "gpio93";
					function = "pri_mi2s";
				};

				config {
					pins = "gpio91", "gpio88", "gpio93";
					drive-strength = <2>;
					bias-pull-down;
				};
			};
		};

配置pinctrl还需要挂载到声卡节点下。

修改文件:kernel/msm-3.18/arch/arm/boot/dts/qcom/msm8953-audio.dtsi

	qcom,msm-gpios =
		"pri_i2s",
		"quin_i2s";

	qcom,pinctrl-names =
		"all_off",
		"pri_i2s_act",
		"quin_act",
		"quin_pri_i2s_act";

	pinctrl-names =
		"all_off",
		"pri_i2s_act",
		"quin_act",
		"quin_pri_i2s_act";

	pinctrl-0 = <&cdc_pdm_lines_sus &cdc_pdm_lines_2_sus &pri_tlmm_lines_sus &pri_tlmm_ws_sus>;
	pinctrl-1 = <&cdc_pdm_lines_act &cdc_pdm_lines_2_act &pri_tlmm_lines_sus &pri_tlmm_ws_sus>;
	pinctrl-2 = <&cdc_pdm_lines_sus &cdc_pdm_lines_2_sus &pri_tlmm_lines_act &pri_tlmm_ws_act>;
	pinctrl-3 = <&cdc_pdm_lines_act &cdc_pdm_lines_2_act &pri_tlmm_lines_act &pri_tlmm_ws_act>;

2. 寄存器配置

从高通文档MSM8953 External MI2S Interface Overview上了解到配置QUIN MI2S需要配置以下寄存器。

那么如何在代码中配置这些寄存器呢?这就需要修改到以下几个文件。

修改文件:

kernel/msm-3.18/sound/soc/msm/msm8952.c

kernel/msm-3.18/sound/soc/codecs/msm8x16-wcd.h

kernel/msm-3.18/arch/arm/boot/dts/qcom/msm-audio.dtsi

其中kernel/msm-3.18/arch/arm/boot/dts/qcom/msm-audio.dtsi配置如下:

	int_codec: sound {
		compatible = "qcom,msm8952-audio-codec";

//added by kuangjincheng @20171221
		reg =   <0xc051000 0x4>,//LPASS_CSR_GP_IO_MUX_MIC_CTL
				<0xc051004 0x4>,//LPASS_CSR_GP_IO_MUX_SPKR_CTL
				<0xc055000 0x4>,//LPASS_CSR_GP_LPAIF_PRI_PCM_PRI_MODE_MUXSEL
				<0xc052000 0x4>,//LPASS_ CSR_GP_IO_MUX_QUI_CTL
				<0xc054000 0x4>,//LPASS_CSR_GP_IO_MUX_MIC_EXT_CLK_CTL
				<0xc056000 0x4>,// LPASS_CSR_GP_LPAIF_QUI_PCM_SEC_MODE_MUXSEL
				<0xc054008 0x4>;//LPASS_CSR_GP_IO_MUX_QUI_EXT_CLK_CTL
		reg-names = "csr_gp_io_mux_mic_ctl",
					"csr_gp_io_mux_spkr_ctl",
					"csr_gp_io_lpaif_pri_pcm_pri_mode_muxsel",
					"csr_gp_io_mux_quin_ctl",
					"csr_gp_io_mux_mic_ext_clk_ctl",
					"csr_gp_io_lpaif_qui_pcm_sec_mode_muxsel",
					"csr_gp_io_mux_quin_ext_clk_ctl";
    };

在头文件kernel/msm-3.18/sound/soc/codecs/msm8x16-wcd.h中的设备结构体中增加指针元素,存储寄存器地址。

struct msm8916_asoc_mach_data {
	int codec_type;
	int ext_pa;
	int us_euro_gpio;
	int spk_ext_pa_gpio;
	int mclk_freq;
	int lb_mode;
	int afe_clk_ver;
	u8 micbias1_cap_mode;
	u8 micbias2_cap_mode;
	atomic_t mclk_rsc_ref;
	atomic_t mclk_enabled;
	atomic_t wsa_mclk_rsc_ref;
	struct mutex cdc_mclk_mutex;
	struct mutex wsa_mclk_mutex;
	struct delayed_work disable_mclk_work;
	struct afe_digital_clk_cfg digital_cdc_clk;
	struct afe_clk_set digital_cdc_core_clk;
	void __iomem *vaddr_gpio_mux_spkr_ctl;
	void __iomem *vaddr_gpio_mux_mic_ctl;
	void __iomem *vaddr_gpio_mux_quin_ctl;
	void __iomem *vaddr_gpio_mux_pcm_ctl;
//added by kuangjincheng @20171221 QUAT_MI2S_SLAVE_MODE config
	void __iomem *vaddr_gpio_mux_mic_ext_clk_ctl;
//added by kuangjincheng @20180521 QUIN_MI2S_SLAVE_MODE config
	void __iomem *vaddr_gpio_quin_sec_muxsel;
	void __iomem *vaddr_gpio_mux_quin_ext_clk_ctl;
	struct on_demand_supply wsa_switch_supply;
};

在kernel/msm-3.18/sound/soc/msm/msm8952.c的msm8952_asoc_machine_probe函数中解析对应的寄存器地址,并保存到设备结构体对应的元素中去。

//added by kuangjincheng @20180521 QUIN_MI2S_SLAVE_MODE config
	muxsel = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr_gp_io_lpaif_qui_pcm_sec_mode_muxsel");
	if (!muxsel) {
		dev_dbg(&pdev->dev, "MUX addr invalid for MI2S\n");
		goto parse_mclk_freq;
	}
	pdata->vaddr_gpio_quin_sec_muxsel = ioremap(muxsel->start, resource_size(muxsel));
	if (pdata->vaddr_gpio_quin_sec_muxsel == NULL) {
		pr_err("%s ==4ioremap failure for muxsel virt addr\n", __func__);
		ret = -ENOMEM;
		goto err;
	}

	muxsel = platform_get_resource_byname(pdev, IORESOURCE_MEM, "csr_gp_io_mux_quin_ext_clk_ctl");
	if (!muxsel) {
		dev_dbg(&pdev->dev, "MUX addr invalid for MI2S\n");
		goto parse_mclk_freq;
	}
	pdata->vaddr_gpio_mux_quin_ext_clk_ctl = ioremap(muxsel->start, resource_size(muxsel));
	if (pdata->vaddr_gpio_mux_quin_ext_clk_ctl == NULL) {
		pr_err("%s ==5ioremap failure for muxsel virt addr\n", __func__);
		ret = -ENOMEM;
		goto err;
	}

寄存器配置还有最后一步就是在I2S对应msm_quin_mi2s_snd_startup中进行寄存器配置。

static int msm_quin_mi2s_snd_startup(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_card *card = rtd->card;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct msm8916_asoc_mach_data *pdata =
			snd_soc_card_get_drvdata(card);
	int ret = 0, val = 0;

	pr_err("honeywell_msm8952>>>%s: substream = %s  stream = %d\n", __func__, substream->name, substream->stream);
#ifdef QUIN_MI2S_USE_SLAVE_MODE
//0xc051000 LPASS_CSR_GP_IO_MUX_MIC_CTL
	if(pdata->vaddr_gpio_mux_mic_ctl){
		val = ioread32(pdata->vaddr_gpio_mux_mic_ctl);
		val = 0x00000000;
		iowrite32(val, pdata->vaddr_gpio_mux_mic_ctl);
	}
	pr_err("msm8952>>>%s:  iowrite32 vaddr_gpio_mux_mic_ctl\n", __func__);
//0xc056000 LPASS_CSR_GP_LPAIF_QUI_PCM_SEC_MODE_MUXSEL
	if(pdata->vaddr_gpio_quin_sec_muxsel){
		val = ioread32(pdata->vaddr_gpio_quin_sec_muxsel);
		val = 0x00000000;
		iowrite32(val, pdata->vaddr_gpio_quin_sec_muxsel);
	}
	pr_err("msm8952>>>%s:  iowrite32 vaddr_gpio_quin_sec_muxsel\n", __func__);
//0xc052000 LPASS_ CSR_GP_IO_MUX_QUI_CTL
	if(pdata->vaddr_gpio_mux_quin_ctl){
		val = ioread32(pdata->vaddr_gpio_mux_quin_ctl);
		val = 0x00000001;
		iowrite32(val, pdata->vaddr_gpio_mux_quin_ctl);
	}
	pr_err("msm8952>>>%s:  iowrite32 vaddr_gpio_mux_quin_ctl\n", __func__);
//0xc054008 LPASS_CSR_GP_IO_MUX_QUI_EXT_CLK_CTL
	if(pdata->vaddr_gpio_mux_quin_ext_clk_ctl){
		val = ioread32(pdata->vaddr_gpio_mux_quin_ext_clk_ctl);
		val = 0x00000001;
		iowrite32(val, pdata->vaddr_gpio_mux_quin_ext_clk_ctl);
	}
	pr_err("msm8952>>>%s:  iowrite32 vaddr_gpio_mux_quin_ext_clk_ctl\n", __func__);
#else
	if (pdata->vaddr_gpio_mux_quin_ctl) {
		val = ioread32(pdata->vaddr_gpio_mux_quin_ctl);
		val = val | 0x00000001;
		iowrite32(val, pdata->vaddr_gpio_mux_quin_ctl);
	} else {
		return -EINVAL;
	}
#endif
	pr_err("honeywell_msm8952>>>%s: ---001\n", __func__);

#ifdef QUIN_MI2S_USE_SLAVE_MODE
	ret = msm_quin_i2s_sclk_ctl(substream, true);
#else
	ret = msm_mi2s_sclk_ctl(substream, true);
#endif
	if (ret < 0) {
		pr_err("honeywell_msm8952>>>%s: failed to enable sclk\n", __func__);
		return ret;
	}
	pr_err("honeywell_msm8952>>>%s: ---002\n", __func__);

	ret = msm_gpioset_activate(CLIENT_WCD_INT, "quin_i2s");
	if (ret < 0) {
		pr_err("honeywell_msm8952>>>%s: failed to enable codec gpios\n", __func__);
		goto err;
	}
	pr_err("honeywell_msm8952>>>%s: ---003\n", __func__);

	if (atomic_inc_return(&quin_mi2s_clk_ref) == 1) {
#ifdef QUIN_MI2S_USE_SLAVE_MODE
		ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM);
#else
		ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBS_CFS);
#endif
		if (ret < 0)
			pr_err("honeywell_msm8952>>>%s: set fmt cpu dai failed\n", __func__);
	}
	pr_err("honeywell_msm8952>>>%s: ---004\n", __func__);

	return ret;
err:
#ifdef QUIN_MI2S_USE_SLAVE_MODE
	ret = msm_quin_i2s_sclk_ctl(substream, false);
#else
	ret = msm_mi2s_sclk_ctl(substream, false);
#endif
	if (ret < 0)
		pr_err("failed to disable sclk\n");
	return ret;
}

static void msm_quin_mi2s_snd_shutdown(struct snd_pcm_substream *substream)
{
	int ret;
	pr_err("honeywell_msm8952>>>%s: substream = %s  stream = %d\n", __func__, substream->name, substream->stream);
#ifdef QUIN_MI2S_USE_SLAVE_MODE
	ret = msm_quin_i2s_sclk_ctl(substream, false);
#else
	ret = msm_mi2s_sclk_ctl(substream, false);
#endif
	if (ret < 0)
		pr_err("honeywell_msm8952>>>%s: clock disable failed\n", __func__);
	if (atomic_read(&quin_mi2s_clk_ref) > 0)
		atomic_dec(&quin_mi2s_clk_ref);
	ret = msm_gpioset_suspend(CLIENT_WCD_INT, "quin_i2s");
	if (ret < 0) {
		pr_err("honeywell_msm8952>>>%s: gpio set cannot be de-activated %sd", __func__, "quin_i2s");
		return;
	}
}

3.  时钟配置

(1)谁提供时钟?

时钟配置中的时钟是由MSM8953提供还是由外接晶振的第三方CODEC芯片提供,这个直接关系到I2S的主从模式设定。

I2S为主模式时,时钟有MSM8953内部晶振提供;反之,由第三方codec提供。

这里配置的代码也是在msm_quin_mi2s_snd_startup函数中设置的。

对应代码段单独截取出来就是:

#ifdef QUIN_MI2S_USE_SLAVE_MODE
		ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM);
#else
		ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBS_CFS);
#endif

(2)时钟频率如何设定?

这里仿照msm_mi2s_sclk_ctl写了个函数msm_quin_i2s_sclk_ctl用来进行从模式时钟配置。

//added by kuangjincheng @20180521 QUIN_MI2S_SLAVE_MODE config
#ifdef QUIN_MI2S_USE_SLAVE_MODE
static struct afe_clk_set quin_i2s_rx_clk = {
	AFE_API_VERSION_I2S_CONFIG,
	Q6AFE_LPASS_CLK_ID_QUI_MI2S_EBIT,
	Q6AFE_LPASS_OSR_CLK_1_P536_MHZ,
	Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO,
	Q6AFE_LPASS_CLK_ROOT_DEFAULT,
	0,
};

static struct afe_clk_set quin_i2s_tx_clk = {
	AFE_API_VERSION_I2S_CONFIG,
	Q6AFE_LPASS_CLK_ID_QUI_MI2S_EBIT,
	Q6AFE_LPASS_OSR_CLK_3_P072_MHZ,
	Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO,
	Q6AFE_LPASS_CLK_ROOT_DEFAULT,
	0,
};
#endif

//added by kuangjincheng @20180521 QUIN_MI2S_SLAVE_MODE config
#ifdef QUIN_MI2S_USE_SLAVE_MODE
static int msm_quin_i2s_sclk_ctl(struct snd_pcm_substream *substream, bool enable)
{
	int ret = 0;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	int port_id = 0;
	pr_err("msm8952>>>%s:  enter\n", __func__);
	port_id = msm8952_get_port_id(rtd->dai_link->be_id);
	if (port_id < 0) {
		pr_err("msm8952>>>%s:  Invalid port_id\n", __func__);
		return -EINVAL;
	}

	if (enable) {
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
			quin_i2s_rx_clk.enable = enable;
			quin_i2s_rx_clk.clk_id = msm8952_get_clk_id(port_id);
			quin_i2s_rx_clk.clk_freq_in_hz = get_mi2s_rx_clk_val(port_id);
			ret = afe_set_lpass_clock_v2(port_id, &quin_i2s_rx_clk);
		} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
			quin_i2s_tx_clk.enable = enable;
			quin_i2s_tx_clk.clk_id = msm8952_get_clk_id(port_id);
			quin_i2s_tx_clk.clk_freq_in_hz = Q6AFE_LPASS_OSR_CLK_1_P536_MHZ;
			ret = afe_set_lpass_clock_v2(port_id, &quin_i2s_tx_clk);
		} else {
			pr_err("msm8952>>>%s:  Not valid substream.\n", __func__);
		}

		if (ret < 0)
			pr_err("msm8952>>>%s:  enable: %d, afe_set_lpass_clock_v2 failed\n", __func__, enable);
	} else {
		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
			quin_i2s_rx_clk.enable = enable;
			quin_i2s_rx_clk.clk_id = msm8952_get_clk_id(port_id);
			ret = afe_set_lpass_clock_v2(port_id, &quin_i2s_rx_clk);
		} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
			quin_i2s_tx_clk.enable = enable;
			quin_i2s_tx_clk.clk_id = msm8952_get_clk_id(port_id);
			ret = afe_set_lpass_clock_v2(port_id, &quin_i2s_tx_clk);
		} else {
			pr_err("msm8952>>>%s:  Not valid substream.\n", __func__);
		}

		if (ret < 0)
			pr_err("msm8952>>>%s: enable: %d, afe_set_lpass_clock_v2 failed\n", __func__, enable);
	}
	return ret;
}
#endif

4. 综述以及延伸

通过以上代码修改基本就完成了I2S总线从模式配置,可以通过宏定义QUIN_MI2S_USE_SLAVE_MODE来切换主副麦克风。

延伸内容:

kernel/msm-3.18/include/uapi/sound/asound.h

kernel/msm-3.18/include/sound/apr_audio-v2.h

PCM格式在安卓7代码中可以设置为:

SNDRV_PCM_FORMAT_S16_LE

SNDRV_PCM_FORMAT_S24_LE

SNDRV_PCM_FORMAT_S24_3LE

位时钟可选择设置为:

/* Supported OSR clock values */
#define Q6AFE_LPASS_OSR_CLK_12_P288_MHZ         0xBB8000
#define Q6AFE_LPASS_OSR_CLK_9_P600_MHZ          0x927C00
#define Q6AFE_LPASS_OSR_CLK_8_P192_MHZ          0x7D0000
#define Q6AFE_LPASS_OSR_CLK_6_P144_MHZ          0x5DC000
#define Q6AFE_LPASS_OSR_CLK_4_P096_MHZ          0x3E8000
#define Q6AFE_LPASS_OSR_CLK_3_P072_MHZ          0x2EE000
#define Q6AFE_LPASS_OSR_CLK_2_P048_MHZ          0x1F4000
#define Q6AFE_LPASS_OSR_CLK_1_P536_MHZ          0x177000
#define Q6AFE_LPASS_OSR_CLK_1_P024_MHZ           0xFA000
#define Q6AFE_LPASS_OSR_CLK_768_kHZ              0xBB800
#define Q6AFE_LPASS_OSR_CLK_512_kHZ              0x7D000
#define Q6AFE_LPASS_OSR_CLK_DISABLE                  0x0

/* Supported Bit clock values */
#define Q6AFE_LPASS_IBIT_CLK_12_P288_MHZ        0xBB8000
#define Q6AFE_LPASS_IBIT_CLK_11_P2896_MHZ       0xAC4400
#define Q6AFE_LPASS_IBIT_CLK_8_P192_MHZ         0x7D0000
#define Q6AFE_LPASS_IBIT_CLK_6_P144_MHZ         0x5DC000
#define Q6AFE_LPASS_IBIT_CLK_4_P096_MHZ         0x3E8000
#define Q6AFE_LPASS_IBIT_CLK_3_P072_MHZ         0x2EE000
#define Q6AFE_LPASS_IBIT_CLK_2_P8224_MHZ                0x2b1100
#define Q6AFE_LPASS_IBIT_CLK_2_P048_MHZ         0x1F4000
#define Q6AFE_LPASS_IBIT_CLK_1_P536_MHZ         0x177000
#define Q6AFE_LPASS_IBIT_CLK_1_P4112_MHZ                0x158880
#define Q6AFE_LPASS_IBIT_CLK_1_P024_MHZ          0xFA000
#define Q6AFE_LPASS_IBIT_CLK_768_KHZ             0xBB800
#define Q6AFE_LPASS_IBIT_CLK_512_KHZ             0x7D000
#define Q6AFE_LPASS_IBIT_CLK_256_KHZ             0x3E800
#define Q6AFE_LPASS_IBIT_CLK_DISABLE                 0x0

位深度可选择设置为:16、24

采样率可选择设置为:

#define BTSCO_RATE_8KHZ 8000
#define BTSCO_RATE_16KHZ 16000

#define SAMPLING_RATE_48KHZ     48000
#define SAMPLING_RATE_96KHZ     96000
#define SAMPLING_RATE_192KHZ    192000

这部分在高通文档上的介绍如下:

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐