From 10b823cd45fd688d25f5c82765cc5a90ea8208a7 Mon Sep 17 00:00:00 2001 From: Phani Kumar Uppalapati Date: Wed, 20 Jul 2016 16:43:15 -0700 Subject: [PATCH] ASoC: wcd934x: Add support for DSD audio playback Add DAPM (Dynamic Audio Power Management) widgets and routing to enable support for DSD (Direct Stream Digital) audio playback on wcd934x codec. Change-Id: I06e1b0134cea58adedbd9113a51529b2b73da835 Signed-off-by: Phani Kumar Uppalapati --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/wcd934x/Makefile | 2 + sound/soc/codecs/wcd934x/wcd934x-dsd.c | 557 +++++++++++++++++++++ sound/soc/codecs/wcd934x/wcd934x-dsd.h | 89 ++++ sound/soc/codecs/wcd934x/wcd934x-routing.h | 6 +- sound/soc/codecs/wcd934x/wcd934x.c | 197 +++++++- sound/soc/codecs/wcd934x/wcd934x.h | 17 +- 7 files changed, 852 insertions(+), 20 deletions(-) create mode 100644 sound/soc/codecs/wcd934x/wcd934x-dsd.c create mode 100644 sound/soc/codecs/wcd934x/wcd934x-dsd.h diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 49c4087c7408..38b8a806584f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -713,6 +713,9 @@ config SND_SOC_UDA134X config SND_SOC_UDA1380 tristate +config SND_SOC_WCD934X_DSD + tristate + config SND_SOC_WCD9320 tristate @@ -732,6 +735,7 @@ config SND_SOC_WCD934X select SND_SOC_WCD_DSP_MGR select SND_SOC_WCD_SPI select SND_SOC_WCD934X_MBHC + select SND_SOC_WCD934X_DSD config SND_SOC_WCD934X_MBHC tristate diff --git a/sound/soc/codecs/wcd934x/Makefile b/sound/soc/codecs/wcd934x/Makefile index 09adfba3f0a4..2843fa11d58e 100644 --- a/sound/soc/codecs/wcd934x/Makefile +++ b/sound/soc/codecs/wcd934x/Makefile @@ -5,3 +5,5 @@ snd-soc-wcd934x-objs := wcd934x.o wcd934x-dsp-cntl.o obj-$(CONFIG_SND_SOC_WCD934X) += snd-soc-wcd934x.o snd-soc-wcd934x-mbhc-objs := wcd934x-mbhc.o obj-$(CONFIG_SND_SOC_WCD934X_MBHC) += snd-soc-wcd934x-mbhc.o +snd-soc-wcd934x-dsd-objs := wcd934x-dsd.o +obj-$(CONFIG_SND_SOC_WCD934X_DSD) += snd-soc-wcd934x-dsd.o diff --git a/sound/soc/codecs/wcd934x/wcd934x-dsd.c b/sound/soc/codecs/wcd934x/wcd934x-dsd.c new file mode 100644 index 000000000000..ef20c53eeb96 --- /dev/null +++ b/sound/soc/codecs/wcd934x/wcd934x-dsd.c @@ -0,0 +1,557 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 +#include +#include +#include +#include "wcd934x-dsd.h" + +static const char *const dsd_if_text[] = { + "ZERO", "RX0", "RX1", "RX2", "RX3", "RX4", "RX5", "RX6", "RX7", + "DSD_DATA_PAD" +}; + +static const char * const dsd_filt0_mux_text[] = { + "ZERO", "DSD_L IF MUX", +}; + +static const char * const dsd_filt1_mux_text[] = { + "ZERO", "DSD_R IF MUX", +}; + +static const struct soc_enum dsd_filt0_mux_enum = + SOC_ENUM_SINGLE(WCD934X_CDC_DSD0_PATH_CTL, 0, + ARRAY_SIZE(dsd_filt0_mux_text), dsd_filt0_mux_text); + +static const struct soc_enum dsd_filt1_mux_enum = + SOC_ENUM_SINGLE(WCD934X_CDC_DSD1_PATH_CTL, 0, + ARRAY_SIZE(dsd_filt1_mux_text), dsd_filt1_mux_text); + +static SOC_ENUM_SINGLE_DECL(dsd_l_if_enum, WCD934X_CDC_DSD0_CFG0, + 2, dsd_if_text); +static SOC_ENUM_SINGLE_DECL(dsd_r_if_enum, WCD934X_CDC_DSD1_CFG0, + 2, dsd_if_text); + +static const struct snd_kcontrol_new dsd_filt0_mux = + SOC_DAPM_ENUM("DSD Filt0 Mux", dsd_filt0_mux_enum); + +static const struct snd_kcontrol_new dsd_filt1_mux = + SOC_DAPM_ENUM("DSD Filt1 Mux", dsd_filt1_mux_enum); + +static const struct snd_kcontrol_new dsd_l_if_mux = + SOC_DAPM_ENUM("DSD Left If Mux", dsd_l_if_enum); +static const struct snd_kcontrol_new dsd_r_if_mux = + SOC_DAPM_ENUM("DSD Right If Mux", dsd_r_if_enum); + +static const struct snd_soc_dapm_route tavil_dsd_audio_map[] = { + {"DSD_L IF MUX", "RX0", "CDC_IF RX0 MUX"}, + {"DSD_L IF MUX", "RX1", "CDC_IF RX1 MUX"}, + {"DSD_L IF MUX", "RX2", "CDC_IF RX2 MUX"}, + {"DSD_L IF MUX", "RX3", "CDC_IF RX3 MUX"}, + {"DSD_L IF MUX", "RX4", "CDC_IF RX4 MUX"}, + {"DSD_L IF MUX", "RX5", "CDC_IF RX5 MUX"}, + {"DSD_L IF MUX", "RX6", "CDC_IF RX6 MUX"}, + {"DSD_L IF MUX", "RX7", "CDC_IF RX7 MUX"}, + + {"DSD_FILTER_0", NULL, "DSD_L IF MUX"}, + {"DSD_FILTER_0", NULL, "RX INT1 NATIVE SUPPLY"}, + {"RX INT1 MIX3", "DSD HPHL Switch", "DSD_FILTER_0"}, + + {"DSD_R IF MUX", "RX0", "CDC_IF RX0 MUX"}, + {"DSD_R IF MUX", "RX1", "CDC_IF RX1 MUX"}, + {"DSD_R IF MUX", "RX2", "CDC_IF RX2 MUX"}, + {"DSD_R IF MUX", "RX3", "CDC_IF RX3 MUX"}, + {"DSD_R IF MUX", "RX4", "CDC_IF RX4 MUX"}, + {"DSD_R IF MUX", "RX5", "CDC_IF RX5 MUX"}, + {"DSD_R IF MUX", "RX6", "CDC_IF RX6 MUX"}, + {"DSD_R IF MUX", "RX7", "CDC_IF RX7 MUX"}, + + {"DSD_FILTER_1", NULL, "DSD_R IF MUX"}, + {"DSD_FILTER_1", NULL, "RX INT2 NATIVE SUPPLY"}, + {"RX INT2 MIX3", "DSD HPHR Switch", "DSD_FILTER_1"}, +}; + +static bool is_valid_dsd_interpolator(int interp_num) +{ + if ((interp_num == INTERP_HPHL) || (interp_num == INTERP_HPHR) || + (interp_num == INTERP_LO1) || (interp_num == INTERP_LO2)) + return true; + + return false; +} + +/** + * tavil_dsd_set_mixer_value - Set DSD HPH/LO mixer value + * + * @dsd_conf: pointer to dsd config + * @interp_num: Interpolator number (HPHL/R, LO1/2) + * @sw_value: Mixer switch value + * + * Returns 0 on success or -EINVAL on failure + */ +int tavil_dsd_set_mixer_value(struct tavil_dsd_config *dsd_conf, + int interp_num, int sw_value) +{ + if (!dsd_conf) + return -EINVAL; + + if (!is_valid_dsd_interpolator(interp_num)) + return -EINVAL; + + dsd_conf->dsd_interp_mixer[interp_num] = !!sw_value; + + return 0; +} +EXPORT_SYMBOL(tavil_dsd_set_mixer_value); + +/** + * tavil_dsd_get_current_mixer_value - Get DSD HPH/LO mixer value + * + * @dsd_conf: pointer to dsd config + * @interp_num: Interpolator number (HPHL/R, LO1/2) + * + * Returns current mixer val for success or -EINVAL for failure + */ +int tavil_dsd_get_current_mixer_value(struct tavil_dsd_config *dsd_conf, + int interp_num) +{ + if (!dsd_conf) + return -EINVAL; + + if (!is_valid_dsd_interpolator(interp_num)) + return -EINVAL; + + return dsd_conf->dsd_interp_mixer[interp_num]; +} +EXPORT_SYMBOL(tavil_dsd_get_current_mixer_value); + +/** + * tavil_dsd_set_out_select - DSD0/1 out select to HPH or LO + * + * @dsd_conf: pointer to dsd config + * @interp_num: Interpolator number (HPHL/R, LO1/2) + * + * Returns 0 for success or -EINVAL for failure + */ +int tavil_dsd_set_out_select(struct tavil_dsd_config *dsd_conf, + int interp_num) +{ + unsigned int reg, val; + struct snd_soc_codec *codec; + + if (!dsd_conf || !dsd_conf->codec) + return -EINVAL; + + codec = dsd_conf->codec; + + if (!is_valid_dsd_interpolator(interp_num)) { + dev_err(codec->dev, "%s: Invalid Interpolator: %d for DSD\n", + __func__, interp_num); + return -EINVAL; + } + + switch (interp_num) { + case INTERP_HPHL: + reg = WCD934X_CDC_DSD0_CFG0; + val = 0x00; + break; + case INTERP_HPHR: + reg = WCD934X_CDC_DSD1_CFG0; + val = 0x00; + break; + case INTERP_LO1: + reg = WCD934X_CDC_DSD0_CFG0; + val = 0x02; + break; + case INTERP_LO2: + reg = WCD934X_CDC_DSD1_CFG0; + val = 0x02; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, reg, 0x02, val); + + return 0; +} +EXPORT_SYMBOL(tavil_dsd_set_out_select); + +/** + * tavil_dsd_reset - Reset DSD block + * + * @dsd_conf: pointer to dsd config + * + */ +void tavil_dsd_reset(struct tavil_dsd_config *dsd_conf) +{ + if (!dsd_conf || !dsd_conf->codec) + return; + + snd_soc_update_bits(dsd_conf->codec, WCD934X_CDC_DSD0_PATH_CTL, + 0x02, 0x02); + snd_soc_update_bits(dsd_conf->codec, WCD934X_CDC_DSD0_PATH_CTL, + 0x01, 0x00); + snd_soc_update_bits(dsd_conf->codec, WCD934X_CDC_DSD1_PATH_CTL, + 0x02, 0x02); + snd_soc_update_bits(dsd_conf->codec, WCD934X_CDC_DSD1_PATH_CTL, + 0x01, 0x00); +} +EXPORT_SYMBOL(tavil_dsd_reset); + +/** + * tavil_dsd_set_interp_rate - Set interpolator rate for DSD + * + * @dsd_conf: pointer to dsd config + * @rx_port: RX port number + * @sample_rate: Sample rate of the RX interpolator + * @sample_rate_val: Interpolator rate value + */ +void tavil_dsd_set_interp_rate(struct tavil_dsd_config *dsd_conf, u16 rx_port, + u32 sample_rate, u8 sample_rate_val) +{ + u8 dsd_inp_sel; + u8 dsd0_inp, dsd1_inp; + u8 val0, val1; + u8 dsd0_out_sel, dsd1_out_sel; + u16 int_fs_reg, interp_num = 0; + struct snd_soc_codec *codec; + + if (!dsd_conf || !dsd_conf->codec) + return; + + codec = dsd_conf->codec; + + dsd_inp_sel = DSD_INP_SEL_RX0 + rx_port - WCD934X_RX_PORT_START_NUMBER; + + val0 = snd_soc_read(codec, WCD934X_CDC_DSD0_CFG0); + val1 = snd_soc_read(codec, WCD934X_CDC_DSD1_CFG0); + dsd0_inp = (val0 & 0x3C) >> 2; + dsd1_inp = (val1 & 0x3C) >> 2; + dsd0_out_sel = (val0 & 0x02) >> 1; + dsd1_out_sel = (val1 & 0x02) >> 1; + + /* Set HPHL or LO1 interp rate based on out select */ + if (dsd_inp_sel == dsd0_inp) { + interp_num = dsd0_out_sel ? INTERP_LO1 : INTERP_HPHL; + dsd_conf->base_sample_rate[DSD0] = sample_rate; + } + + /* Set HPHR or LO2 interp rate based on out select */ + if (dsd_inp_sel == dsd1_inp) { + interp_num = dsd1_out_sel ? INTERP_LO2 : INTERP_HPHR; + dsd_conf->base_sample_rate[DSD1] = sample_rate; + } + + if (interp_num) { + int_fs_reg = WCD934X_CDC_RX0_RX_PATH_CTL + 20 * interp_num; + if ((snd_soc_read(codec, int_fs_reg) & 0x0f) < 0x09) { + dev_dbg(codec->dev, "%s: Set Interp %d to sample_rate val 0x%x\n", + __func__, interp_num, sample_rate_val); + snd_soc_update_bits(codec, int_fs_reg, 0x0F, + sample_rate_val); + } + } +} +EXPORT_SYMBOL(tavil_dsd_set_interp_rate); + +static int tavil_set_dsd_mode(struct snd_soc_codec *codec, int dsd_num, + u8 *pcm_rate_val) +{ + unsigned int dsd_out_sel_reg; + u8 dsd_mode; + u32 sample_rate; + struct tavil_dsd_config *dsd_conf = tavil_get_dsd_config(codec); + + if (!dsd_conf) + return -EINVAL; + + if ((dsd_num < 0) || (dsd_num > 1)) + return -EINVAL; + + sample_rate = dsd_conf->base_sample_rate[dsd_num]; + dsd_out_sel_reg = WCD934X_CDC_DSD0_CFG0 + dsd_num * 16; + + switch (sample_rate) { + case 176400: + dsd_mode = 0; /* DSD_64 */ + *pcm_rate_val = 0xb; + break; + case 352800: + dsd_mode = 1; /* DSD_128 */ + *pcm_rate_val = 0xc; + break; + default: + dev_err(codec->dev, "%s: Invalid DSD rate: %d\n", + __func__, sample_rate); + return -EINVAL; + } + + snd_soc_update_bits(codec, dsd_out_sel_reg, 0x01, dsd_mode); + + return 0; +} + +static void tavil_dsd_data_pull(struct snd_soc_codec *codec, int dsd_num, + u8 pcm_rate_val, bool enable) +{ + u8 clk_en, mute_en; + u8 dsd_inp_sel; + + if (enable) { + clk_en = 0x20; + mute_en = 0x10; + } else { + clk_en = 0x00; + mute_en = 0x00; + } + + if (dsd_num & 0x01) { + snd_soc_update_bits(codec, WCD934X_CDC_RX7_RX_PATH_MIX_CTL, + 0x20, clk_en); + dsd_inp_sel = (snd_soc_read(codec, WCD934X_CDC_DSD0_CFG0) & + 0x3C) >> 2; + dsd_inp_sel = (enable) ? dsd_inp_sel : 0; + if (dsd_inp_sel < 9) { + snd_soc_update_bits(codec, + WCD934X_CDC_RX_INP_MUX_RX_INT7_CFG1, + 0x0F, dsd_inp_sel); + snd_soc_update_bits(codec, + WCD934X_CDC_RX7_RX_PATH_MIX_CTL, + 0x0F, pcm_rate_val); + snd_soc_update_bits(codec, + WCD934X_CDC_RX7_RX_PATH_MIX_CTL, + 0x10, mute_en); + } + } + if (dsd_num & 0x02) { + snd_soc_update_bits(codec, WCD934X_CDC_RX8_RX_PATH_MIX_CTL, + 0x20, clk_en); + dsd_inp_sel = (snd_soc_read(codec, WCD934X_CDC_DSD1_CFG0) & + 0x3C) >> 2; + dsd_inp_sel = (enable) ? dsd_inp_sel : 0; + if (dsd_inp_sel < 9) { + snd_soc_update_bits(codec, + WCD934X_CDC_RX_INP_MUX_RX_INT8_CFG1, + 0x0F, dsd_inp_sel); + snd_soc_update_bits(codec, + WCD934X_CDC_RX8_RX_PATH_MIX_CTL, + 0x0F, pcm_rate_val); + snd_soc_update_bits(codec, + WCD934X_CDC_RX8_RX_PATH_MIX_CTL, + 0x10, mute_en); + } + } +} + +static int tavil_enable_dsd(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + int rc, clk_users; + int interp_idx; + u8 pcm_rate_val; + + if (w->shift == DSD0) { + /* Read out select */ + if (snd_soc_read(codec, WCD934X_CDC_DSD0_CFG0) & 0x02) + interp_idx = INTERP_LO1; + else + interp_idx = INTERP_HPHL; + } else if (w->shift == DSD1) { + /* Read out select */ + if (snd_soc_read(codec, WCD934X_CDC_DSD1_CFG0) & 0x02) + interp_idx = INTERP_LO2; + else + interp_idx = INTERP_HPHR; + } else { + dev_err(codec->dev, "%s: Unsupported DSD:%d\n", + __func__, w->shift); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + clk_users = tavil_codec_enable_interp_clk(codec, event, + interp_idx); + + rc = tavil_set_dsd_mode(codec, w->shift, &pcm_rate_val); + if (rc) + return rc; + + tavil_dsd_data_pull(codec, (1 << w->shift), pcm_rate_val, + true); + + snd_soc_update_bits(codec, + WCD934X_CDC_CLK_RST_CTRL_DSD_CONTROL, 0x01, + 0x01); + if (w->shift == DSD0) { + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_PATH_CTL, + 0x02, 0x02); + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_PATH_CTL, + 0x02, 0x00); + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_PATH_CTL, + 0x01, 0x01); + /* Apply Gain */ + snd_soc_write(codec, WCD934X_CDC_DSD0_CFG1, + snd_soc_read(codec, WCD934X_CDC_DSD0_CFG1)); + + if (clk_users > 1) + snd_soc_update_bits(codec, + WCD934X_CDC_DSD0_CFG2, + 0x04, 0x00); + } else if (w->shift == DSD1) { + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_PATH_CTL, + 0x02, 0x02); + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_PATH_CTL, + 0x02, 0x00); + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_PATH_CTL, + 0x01, 0x01); + /* Apply Gain */ + snd_soc_write(codec, WCD934X_CDC_DSD1_CFG1, + snd_soc_read(codec, WCD934X_CDC_DSD1_CFG1)); + + if (clk_users > 1) + snd_soc_update_bits(codec, + WCD934X_CDC_DSD1_CFG2, + 0x04, 0x00); + } + /* 10msec sleep required after DSD clock is set */ + usleep_range(10000, 10100); + break; + case SND_SOC_DAPM_POST_PMD: + if (w->shift == DSD0) { + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_PATH_CTL, + 0x01, 0x00); + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_CFG2, + 0x04, 0x04); + } else if (w->shift == DSD1) { + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_PATH_CTL, + 0x01, 0x00); + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_CFG2, + 0x04, 0x04); + } + + tavil_codec_enable_interp_clk(codec, event, interp_idx); + + if (!(snd_soc_read(codec, WCD934X_CDC_DSD0_PATH_CTL) & 0x01) && + !(snd_soc_read(codec, WCD934X_CDC_DSD1_PATH_CTL) & 0x01)) { + snd_soc_update_bits(codec, + WCD934X_CDC_CLK_RST_CTRL_DSD_CONTROL, + 0x01, 0x00); + tavil_dsd_data_pull(codec, 0x03, 0x04, false); + } + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget tavil_dsd_widgets[] = { + SND_SOC_DAPM_MUX("DSD_L IF MUX", SND_SOC_NOPM, 0, 0, &dsd_l_if_mux), + SND_SOC_DAPM_MUX_E("DSD_FILTER_0", SND_SOC_NOPM, 0, 0, &dsd_filt0_mux, + tavil_enable_dsd, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + SND_SOC_DAPM_MUX("DSD_R IF MUX", SND_SOC_NOPM, 0, 0, &dsd_r_if_mux), + SND_SOC_DAPM_MUX_E("DSD_FILTER_1", SND_SOC_NOPM, 1, 0, &dsd_filt1_mux, + tavil_enable_dsd, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +}; + +/** + * tavil_dsd_init - DSD intialization + * + * @codec: pointer to snd_soc_codec + * + * Returns pointer to tavil_dsd_config for success or NULL for failure + */ +struct tavil_dsd_config *tavil_dsd_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm; + struct tavil_dsd_config *dsd_conf; + u8 val; + + if (!codec) + return NULL; + + dapm = snd_soc_codec_get_dapm(codec); + + /* Read efuse register to check if DSD is supported */ + val = snd_soc_read(codec, WCD934X_CHIP_TIER_CTRL_EFUSE_VAL_OUT14); + if (val & 0x80) { + dev_info(codec->dev, "%s: DSD unsupported for this codec version\n", + __func__); + return NULL; + } + + dsd_conf = devm_kzalloc(codec->dev, sizeof(struct tavil_dsd_config), + GFP_KERNEL); + if (!dsd_conf) + return NULL; + + dsd_conf->codec = codec; + + /* DSD registers init */ + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_CFG2, 0x02, 0x00); + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_CFG2, 0x02, 0x00); + /* DSD0: Mute EN */ + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_CFG2, 0x04, 0x04); + /* DSD1: Mute EN */ + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_CFG2, 0x04, 0x04); + snd_soc_update_bits(codec, WCD934X_CDC_DEBUG_DSD0_DEBUG_CFG3, 0x10, + 0x10); + snd_soc_update_bits(codec, WCD934X_CDC_DEBUG_DSD1_DEBUG_CFG3, 0x10, + 0x10); + snd_soc_update_bits(codec, WCD934X_CDC_DEBUG_DSD0_DEBUG_CFG0, 0x0E, + 0x0A); + snd_soc_update_bits(codec, WCD934X_CDC_DEBUG_DSD1_DEBUG_CFG0, 0x0E, + 0x0A); + snd_soc_update_bits(codec, WCD934X_CDC_DEBUG_DSD0_DEBUG_CFG1, 0x07, + 0x04); + snd_soc_update_bits(codec, WCD934X_CDC_DEBUG_DSD1_DEBUG_CFG1, 0x07, + 0x04); + + snd_soc_dapm_new_controls(dapm, tavil_dsd_widgets, + ARRAY_SIZE(tavil_dsd_widgets)); + + snd_soc_dapm_add_routes(dapm, tavil_dsd_audio_map, + ARRAY_SIZE(tavil_dsd_audio_map)); + + /* Enable DSD Interrupts */ + snd_soc_update_bits(codec, WCD934X_INTR_CODEC_MISC_MASK, 0x08, 0x00); + + return dsd_conf; +} +EXPORT_SYMBOL(tavil_dsd_init); + +/** + * tavil_dsd_deinit - DSD de-intialization + * + * @dsd_conf: pointer to tavil_dsd_config + */ +void tavil_dsd_deinit(struct tavil_dsd_config *dsd_conf) +{ + struct snd_soc_codec *codec; + + if (!dsd_conf) + return; + + codec = dsd_conf->codec; + + /* Disable DSD Interrupts */ + snd_soc_update_bits(codec, WCD934X_INTR_CODEC_MISC_MASK, 0x08, 0x08); + + devm_kfree(codec->dev, dsd_conf); +} +EXPORT_SYMBOL(tavil_dsd_deinit); diff --git a/sound/soc/codecs/wcd934x/wcd934x-dsd.h b/sound/soc/codecs/wcd934x/wcd934x-dsd.h new file mode 100644 index 000000000000..884e41d797d9 --- /dev/null +++ b/sound/soc/codecs/wcd934x/wcd934x-dsd.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#ifndef __WCD934X_DSD_H__ +#define __WCD934X_DSD_H__ + +#include +#include "wcd934x.h" + +enum { + DSD0, + DSD1, + DSD_MAX, +}; + +enum { + DSD_INP_SEL_ZERO = 0, + DSD_INP_SEL_RX0, + DSD_INP_SEL_RX1, + DSD_INP_SEL_RX2, + DSD_INP_SEL_RX3, + DSD_INP_SEL_RX4, + DSD_INP_SEL_RX5, + DSD_INP_SEL_RX6, + DSD_INP_SEL_RX7, +}; + +struct tavil_dsd_config { + struct snd_soc_codec *codec; + unsigned int dsd_interp_mixer[INTERP_MAX]; + u32 base_sample_rate[DSD_MAX]; +}; + +#ifdef CONFIG_SND_SOC_WCD934X_DSD +int tavil_dsd_set_mixer_value(struct tavil_dsd_config *dsd_conf, + int interp_num, int sw_value); +int tavil_dsd_get_current_mixer_value(struct tavil_dsd_config *dsd_conf, + int interp_num); +int tavil_dsd_set_out_select(struct tavil_dsd_config *dsd_conf, + int interp_num); +void tavil_dsd_reset(struct tavil_dsd_config *dsd_conf); +void tavil_dsd_set_interp_rate(struct tavil_dsd_config *dsd_conf, u16 rx_port, + u32 sample_rate, u8 sample_rate_val); +struct tavil_dsd_config *tavil_dsd_init(struct snd_soc_codec *codec); +void tavil_dsd_deinit(struct tavil_dsd_config *dsd_config); +#else +int tavil_dsd_set_mixer_value(struct tavil_dsd_config *dsd_conf, + int interp_num, int sw_value) +{ + return 0; +} + +int tavil_dsd_get_current_mixer_value(struct tavil_dsd_config *dsd_conf, + int interp_num) +{ + return 0; +} + +int tavil_dsd_set_out_select(struct tavil_dsd_config *dsd_conf, + int interp_num) +{ + return 0; +} + +void tavil_dsd_reset(struct tavil_dsd_config *dsd_conf) +{ } + +void tavil_dsd_set_interp_rate(struct tavil_dsd_config *dsd_conf, u16 rx_port, + u32 sample_rate, u8 sample_rate_val) +{ } + +struct tavil_dsd_config *tavil_dsd_init(struct snd_soc_codec *codec) +{ + return NULL; +} + +void tavil_dsd_deinit(struct tavil_dsd_config *dsd_config) +{ } +#endif +#endif diff --git a/sound/soc/codecs/wcd934x/wcd934x-routing.h b/sound/soc/codecs/wcd934x/wcd934x-routing.h index bdb9ab22293d..8b20805de6f9 100644 --- a/sound/soc/codecs/wcd934x/wcd934x-routing.h +++ b/sound/soc/codecs/wcd934x/wcd934x-routing.h @@ -808,7 +808,8 @@ const struct snd_soc_dapm_route tavil_audio_map[] = { {"RX INT1 SEC MIX", NULL, "RX INT1_1 INTERP"}, {"RX INT1 MIX2", NULL, "RX INT1 SEC MIX"}, {"RX INT1 MIX2", NULL, "RX INT1 MIX2 INP"}, - {"RX INT1 DEM MUX", "CLSH_DSM_OUT", "RX INT1 MIX2"}, + {"RX INT1 MIX3", NULL, "RX INT1 MIX2"}, + {"RX INT1 DEM MUX", "CLSH_DSM_OUT", "RX INT1 MIX3"}, {"RX INT1 DAC", NULL, "RX INT1 DEM MUX"}, {"RX INT1 DAC", NULL, "RX_BIAS"}, {"HPHL PA", NULL, "RX INT1 DAC"}, @@ -818,7 +819,8 @@ const struct snd_soc_dapm_route tavil_audio_map[] = { {"RX INT2 SEC MIX", NULL, "RX INT2_1 INTERP"}, {"RX INT2 MIX2", NULL, "RX INT2 SEC MIX"}, {"RX INT2 MIX2", NULL, "RX INT2 MIX2 INP"}, - {"RX INT2 DEM MUX", "CLSH_DSM_OUT", "RX INT2 MIX2"}, + {"RX INT2 MIX3", NULL, "RX INT2 MIX2"}, + {"RX INT2 DEM MUX", "CLSH_DSM_OUT", "RX INT2 MIX3"}, {"RX INT2 DAC", NULL, "RX INT2 DEM MUX"}, {"RX INT2 DAC", NULL, "RX_BIAS"}, {"HPHR PA", NULL, "RX INT2 DAC"}, diff --git a/sound/soc/codecs/wcd934x/wcd934x.c b/sound/soc/codecs/wcd934x/wcd934x.c index 9fb40274d9f9..50a023013fa8 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.c +++ b/sound/soc/codecs/wcd934x/wcd934x.c @@ -47,8 +47,7 @@ #include "../wcd9xxx-common-v2.h" #include "../wcd9xxx-resmgr-v2.h" #include "../wcdcal-hwdep.h" - -#define WCD934X_RX_PORT_START_NUMBER 16 +#include "wcd934x-dsd.h" #define WCD934X_RATES_MASK (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ @@ -178,18 +177,6 @@ enum { INTn_2_INP_SEL_PROXIMITY, }; -enum { - INTERP_EAR = 0, - INTERP_HPHL, - INTERP_HPHR, - INTERP_LO1, - INTERP_LO2, - INTERP_LO3_NA, /* LO3 not avalible in Tavil*/ - INTERP_LO4_NA, - INTERP_SPKR1, - INTERP_SPKR2, -}; - static const struct intr_data wcd934x_intr_table[] = { {WCD9XXX_IRQ_SLIMBUS, false}, {WCD934X_IRQ_MBHC_SW_DET, true}, @@ -197,7 +184,7 @@ static const struct intr_data wcd934x_intr_table[] = { {WCD934X_IRQ_MBHC_BUTTON_RELEASE_DET, true}, {WCD934X_IRQ_MBHC_ELECT_INS_REM_DET, true}, {WCD934X_IRQ_MBHC_ELECT_INS_REM_LEG_DET, true}, - {WCD934X_IRQ_FLL_LOCK_LOSS, false}, + {WCD934X_IRQ_MISC, false}, {WCD934X_IRQ_HPH_PA_CNPL_COMPLETE, false}, {WCD934X_IRQ_HPH_PA_CNPR_COMPLETE, false}, {WCD934X_IRQ_EAR_PA_CNP_COMPLETE, false}, @@ -516,6 +503,7 @@ struct tavil_priv { int asrc_users[ASRC_MAX]; /* Main path clock users count */ int main_clk_users[WCD934X_NUM_INTERPOLATORS]; + struct tavil_dsd_config *dsd_config; }; static const struct tavil_reg_mask_val tavil_spkr_default[] = { @@ -1402,6 +1390,7 @@ static int tavil_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + struct tavil_dsd_config *dsd_conf = tavil->dsd_config; dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event); @@ -1430,8 +1419,25 @@ static int tavil_codec_enable_hphr_pa(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, WCD934X_CDC_RX2_RX_PATH_MIX_CTL, 0x10, 0x00); + if (dsd_conf && + (snd_soc_read(codec, WCD934X_CDC_DSD1_PATH_CTL) & 0x01)) { + /* Set regulator mode to AB if DSD is enabled */ + snd_soc_update_bits(codec, WCD934X_ANA_RX_SUPPLIES, + 0x02, 0x02); + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_CFG2, + 0x04, 0x00); + } break; - default: + case SND_SOC_DAPM_PRE_PMD: + /* Enable DSD Mute before PA disable */ + if (dsd_conf && + (snd_soc_read(codec, WCD934X_CDC_DSD1_PATH_CTL) & 0x01)) + snd_soc_update_bits(codec, WCD934X_CDC_DSD1_CFG2, + 0x04, 0x04); + break; + case SND_SOC_DAPM_POST_PMD: + /* 5ms sleep is required after PA disable */ + usleep_range(5000, 5100); break; }; @@ -1444,6 +1450,7 @@ static int tavil_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, { struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + struct tavil_dsd_config *dsd_conf = tavil->dsd_config; dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event); @@ -1469,8 +1476,25 @@ static int tavil_codec_enable_hphl_pa(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, WCD934X_CDC_RX1_RX_PATH_MIX_CTL, 0x10, 0x00); + if (dsd_conf && + (snd_soc_read(codec, WCD934X_CDC_DSD0_PATH_CTL) & 0x01)) { + /* Set regulator mode to AB if DSD is enabled */ + snd_soc_update_bits(codec, WCD934X_ANA_RX_SUPPLIES, + 0x02, 0x02); + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_CFG2, + 0x04, 0x00); + } break; - default: + case SND_SOC_DAPM_PRE_PMD: + /* Enable DSD Mute before PA disable */ + if (dsd_conf && + (snd_soc_read(codec, WCD934X_CDC_DSD0_PATH_CTL) & 0x01)) + snd_soc_update_bits(codec, WCD934X_CDC_DSD0_CFG2, + 0x04, 0x04); + break; + case SND_SOC_DAPM_POST_PMD: + /* 5ms sleep is required after PA disable */ + usleep_range(5000, 5100); break; }; @@ -1562,6 +1586,7 @@ static int tavil_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); int hph_mode = tavil->hph_mode; u8 dem_inp; + struct tavil_dsd_config *dsd_conf = tavil->dsd_config; dev_dbg(codec->dev, "%s wname: %s event: %d hph_mode: %d\n", __func__, w->name, event, hph_mode); @@ -1580,6 +1605,11 @@ static int tavil_codec_hphr_dac_event(struct snd_soc_dapm_widget *w, /* Disable AutoChop timer during power up */ snd_soc_update_bits(codec, WCD934X_HPH_NEW_INT_HPH_TIMER1, 0x02, 0x00); + + if (dsd_conf && + (snd_soc_read(codec, WCD934X_CDC_DSD1_PATH_CTL) & 0x01)) + hph_mode = CLS_H_HIFI; + wcd_clsh_fsm(codec, &tavil->clsh_d, WCD_CLSH_EVENT_PRE_DAC, WCD_CLSH_STATE_HPHR, @@ -1611,6 +1641,7 @@ static int tavil_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, int hph_mode = tavil->hph_mode; u8 dem_inp; int ret = 0; + struct tavil_dsd_config *dsd_conf = tavil->dsd_config; dev_dbg(codec->dev, "%s wname: %s event: %d hph_mode: %d\n", __func__, w->name, event, hph_mode); @@ -1626,6 +1657,10 @@ static int tavil_codec_hphl_dac_event(struct snd_soc_dapm_widget *w, __func__, hph_mode); return -EINVAL; } + if (dsd_conf && + (snd_soc_read(codec, WCD934X_CDC_DSD0_PATH_CTL) & 0x01)) + hph_mode = CLS_H_HIFI; + wcd_clsh_fsm(codec, &tavil->clsh_d, WCD_CLSH_EVENT_PRE_DAC, WCD_CLSH_STATE_HPHL, @@ -2389,6 +2424,29 @@ static int tavil_codec_enable_mix_path(struct snd_soc_dapm_widget *w, return 0; } +/** + * tavil_get_dsd_config - Get pointer to dsd config structure + * + * @codec: pointer to snd_soc_codec structure + * + * Returns pointer to tavil_dsd_config structure + */ +struct tavil_dsd_config *tavil_get_dsd_config(struct snd_soc_codec *codec) +{ + struct tavil_priv *tavil; + + if (!codec) + return NULL; + + tavil = snd_soc_codec_get_drvdata(codec); + + if (!tavil) + return NULL; + + return tavil->dsd_config; +} +EXPORT_SYMBOL(tavil_get_dsd_config); + static int tavil_codec_enable_main_path(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) @@ -4824,6 +4882,61 @@ static const struct snd_kcontrol_new rx_int4_asrc_switch[] = { SOC_DAPM_SINGLE("LO2 Switch", SND_SOC_NOPM, 0, 1, 0), }; +static int tavil_dsd_mixer_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); + struct tavil_priv *tavil_p = snd_soc_codec_get_drvdata(codec); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct tavil_dsd_config *dsd_conf = tavil_p->dsd_config; + int val; + + val = tavil_dsd_get_current_mixer_value(dsd_conf, mc->shift); + + ucontrol->value.integer.value[0] = ((val < 0) ? 0 : val); + + return 0; +} + +static int tavil_dsd_mixer_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(dapm); + struct tavil_priv *tavil_p = snd_soc_codec_get_drvdata(codec); + unsigned int wval = ucontrol->value.integer.value[0]; + struct tavil_dsd_config *dsd_conf = tavil_p->dsd_config; + + if (!dsd_conf) + return 0; + + mutex_lock(&tavil_p->codec_mutex); + + tavil_dsd_set_out_select(dsd_conf, mc->shift); + tavil_dsd_set_mixer_value(dsd_conf, mc->shift, wval); + + mutex_unlock(&tavil_p->codec_mutex); + snd_soc_dapm_mixer_update_power(dapm, kcontrol, wval, NULL); + + return 0; +} + +static const struct snd_kcontrol_new hphl_mixer[] = { + SOC_SINGLE_EXT("DSD HPHL Switch", SND_SOC_NOPM, INTERP_HPHL, 1, 0, + tavil_dsd_mixer_get, tavil_dsd_mixer_put), +}; + +static const struct snd_kcontrol_new hphr_mixer[] = { + SOC_SINGLE_EXT("DSD HPHR Switch", SND_SOC_NOPM, INTERP_HPHR, 1, 0, + tavil_dsd_mixer_get, tavil_dsd_mixer_put), +}; + static const struct snd_soc_dapm_widget tavil_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN_E("AIF1 PB", "AIF1 Playback", 0, SND_SOC_NOPM, AIF1_PB, 0, tavil_codec_enable_slimrx, @@ -4950,7 +5063,11 @@ static const struct snd_soc_dapm_widget tavil_dapm_widgets[] = { SND_SOC_DAPM_MIXER("RX INT0 MIX2", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("RX INT1 MIX2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX INT1 MIX3", SND_SOC_NOPM, 0, 0, hphl_mixer, + ARRAY_SIZE(hphl_mixer)), SND_SOC_DAPM_MIXER("RX INT2 MIX2", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("RX INT2 MIX3", SND_SOC_NOPM, 0, 0, hphr_mixer, + ARRAY_SIZE(hphr_mixer)), SND_SOC_DAPM_MIXER("RX INT3 MIX2", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("RX INT4 MIX2", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("RX INT7 MIX2", SND_SOC_NOPM, 0, 0, NULL, 0), @@ -5665,6 +5782,7 @@ static int tavil_set_prim_interpolator_rate(struct snd_soc_dai *dai, struct snd_soc_codec *codec = dai->codec; struct wcd9xxx_ch *ch; struct tavil_priv *tavil = snd_soc_codec_get_drvdata(codec); + struct tavil_dsd_config *dsd_conf = tavil->dsd_config; list_for_each_entry(ch, &tavil->dai[dai->id].wcd9xxx_ch_list, list) { int_1_mix1_inp = INTn_1_INP_SEL_RX0 + ch->port - @@ -5727,6 +5845,9 @@ static int tavil_set_prim_interpolator_rate(struct snd_soc_dai *dai, } int_mux_cfg0 += 2; } + if (dsd_conf) + tavil_dsd_set_interp_rate(dsd_conf, ch->port, + sample_rate, rate_reg_val); } return 0; @@ -6239,6 +6360,32 @@ static void tavil_slim_interface_init_reg(struct snd_soc_codec *codec) 0xFF); } +static irqreturn_t tavil_misc_irq(int irq, void *data) +{ + struct tavil_priv *tavil = data; + int misc_val; + + /* Find source of interrupt */ + regmap_read(tavil->wcd9xxx->regmap, WCD934X_INTR_CODEC_MISC_STATUS, + &misc_val); + + if (misc_val & 0x08) { + dev_info(tavil->dev, "%s: irq: %d, DSD DC detected!\n", + __func__, irq); + /* DSD DC interrupt, reset DSD path */ + tavil_dsd_reset(tavil->dsd_config); + } else { + dev_err(tavil->dev, "%s: Codec misc irq: %d, val: 0x%x\n", + __func__, irq, misc_val); + } + + /* Clear interrupt status */ + regmap_update_bits(tavil->wcd9xxx->regmap, + WCD934X_INTR_CODEC_MISC_CLEAR, misc_val, 0x00); + + return IRQ_HANDLED; +} + static irqreturn_t tavil_slimbus_irq(int irq, void *data) { struct tavil_priv *tavil = data; @@ -6352,6 +6499,13 @@ static int tavil_setup_irqs(struct tavil_priv *tavil) else tavil_slim_interface_init_reg(codec); + /* Register for misc interrupts as well */ + ret = wcd9xxx_request_irq(core_res, WCD934X_IRQ_MISC, + tavil_misc_irq, "CDC MISC Irq", tavil); + if (ret) + dev_err(codec->dev, "%s: Failed to request cdc misc irq\n", + __func__); + return ret; } @@ -6729,6 +6883,11 @@ static int tavil_soc_codec_probe(struct snd_soc_codec *codec) tavil_mclk2_reg_defaults(tavil); + /* DSD initialization */ + tavil->dsd_config = tavil_dsd_init(codec); + if (IS_ERR_OR_NULL(tavil->dsd_config)) + dev_dbg(tavil->dev, "%s: DSD init failed\n", __func__); + snd_soc_dapm_sync(dapm); tavil_wdsp_initialize(codec); @@ -7381,6 +7540,10 @@ static int tavil_remove(struct platform_device *pdev) snd_soc_unregister_codec(&pdev->dev); clk_put(tavil->wcd_ext_clk); wcd_resmgr_remove(tavil->resmgr); + if (tavil->dsd_config) { + tavil_dsd_deinit(tavil->dsd_config); + tavil->dsd_config = NULL; + } devm_kfree(&pdev->dev, tavil); return 0; } diff --git a/sound/soc/codecs/wcd934x/wcd934x.h b/sound/soc/codecs/wcd934x/wcd934x.h index 4690d344a1d6..9cffa168c298 100644 --- a/sound/soc/codecs/wcd934x/wcd934x.h +++ b/sound/soc/codecs/wcd934x/wcd934x.h @@ -22,6 +22,7 @@ #define WCD934X_REGISTER_START_OFFSET 0x800 #define WCD934X_SB_PGD_PORT_RX_BASE 0x40 #define WCD934X_SB_PGD_PORT_TX_BASE 0x50 +#define WCD934X_RX_PORT_START_NUMBER 16 #define WCD934X_DMIC_CLK_DIV_2 0x0 #define WCD934X_DMIC_CLK_DIV_3 0x1 @@ -71,9 +72,22 @@ enum { WCD934X_TX_MAX, }; +enum { + INTERP_EAR = 0, + INTERP_HPHL, + INTERP_HPHR, + INTERP_LO1, + INTERP_LO2, + INTERP_LO3_NA, /* LO3 not avalible in Tavil*/ + INTERP_LO4_NA, + INTERP_SPKR1, + INTERP_SPKR2, + INTERP_MAX, +}; + enum { /* INTR_REG 0 */ - WCD934X_IRQ_FLL_LOCK_LOSS = 1, + WCD934X_IRQ_MISC = 1, WCD934X_IRQ_HPH_PA_OCPL_FAULT, WCD934X_IRQ_HPH_PA_OCPR_FAULT, WCD934X_IRQ_EAR_PA_OCP_FAULT, @@ -165,4 +179,5 @@ extern int tavil_mbhc_micb_adjust_voltage(struct snd_soc_codec *codec, extern struct wcd934x_mbhc *tavil_soc_get_mbhc(struct snd_soc_codec *codec); extern int tavil_codec_enable_interp_clk(struct snd_soc_codec *codec, int event, int intp_idx); +extern struct tavil_dsd_config *tavil_get_dsd_config(struct snd_soc_codec *); #endif