blob: 3e23e3749bdac312c76b4e5c05ad8cffe6c5dc7f [file] [log] [blame]
/* Copyright (c) 2016-2017, 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 <linux/module.h>
#include <linux/delay.h>
#include <linux/mfd/wcd934x/registers.h>
#include <sound/tlv.h>
#include <sound/control.h>
#include "wcd934x-dsd.h"
#define DSD_VOLUME_MAX_0dB 0
#define DSD_VOLUME_MIN_M110dB -110
#define DSD_VOLUME_RANGE_CHECK(x) ((x >= DSD_VOLUME_MIN_M110dB) &&\
(x <= DSD_VOLUME_MAX_0dB))
#define DSD_VOLUME_STEPS 3
#define DSD_VOLUME_UPDATE_DELAY_MS 30
#define DSD_VOLUME_USLEEP_MARGIN_US 100
#define DSD_VOLUME_STEP_DELAY_US ((1000 * DSD_VOLUME_UPDATE_DELAY_MS) / \
(2 * DSD_VOLUME_STEPS))
#define TAVIL_VERSION_1_0 0
#define TAVIL_VERSION_1_1 1
static const DECLARE_TLV_DB_MINMAX(tavil_dsd_db_scale, DSD_VOLUME_MIN_M110dB,
DSD_VOLUME_MAX_0dB);
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"},
{"DSD_FILTER_0", NULL, "RX INT3 NATIVE SUPPLY"},
{"RX INT3 MIX3", "DSD LO1 Switch", "DSD_FILTER_0"},
{"DSD_FILTER_1", NULL, "RX INT4 NATIVE SUPPLY"},
{"RX INT4 MIX3", "DSD LO2 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 void tavil_dsd_update_volume(struct tavil_dsd_config *dsd_conf)
{
snd_soc_update_bits(dsd_conf->codec, WCD934X_CDC_TOP_TOP_CFG0,
0x01, 0x01);
snd_soc_update_bits(dsd_conf->codec, WCD934X_CDC_TOP_TOP_CFG0,
0x01, 0x00);
}
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);
struct tavil_dsd_config *dsd_conf = tavil_get_dsd_config(codec);
int rc, clk_users;
int interp_idx;
u8 pcm_rate_val;
if (!dsd_conf) {
dev_err(codec->dev, "%s: null dsd_config pointer\n", __func__);
return -EINVAL;
}
dev_dbg(codec->dev, "%s: DSD%d, event: %d\n", __func__,
w->shift, event);
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,
dsd_conf->volume[DSD0]);
if (dsd_conf->version == TAVIL_VERSION_1_1)
tavil_dsd_update_volume(dsd_conf);
} 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,
dsd_conf->volume[DSD1]);
if (dsd_conf->version == TAVIL_VERSION_1_1)
tavil_dsd_update_volume(dsd_conf);
}
/* 10msec sleep required after DSD clock is set */
usleep_range(10000, 10100);
if (clk_users > 1) {
snd_soc_update_bits(codec, WCD934X_ANA_RX_SUPPLIES,
0x02, 0x02);
if (w->shift == DSD0)
snd_soc_update_bits(codec,
WCD934X_CDC_DSD0_CFG2,
0x04, 0x00);
if (w->shift == DSD1)
snd_soc_update_bits(codec,
WCD934X_CDC_DSD1_CFG2,
0x04, 0x00);
}
break;
case SND_SOC_DAPM_POST_PMD:
if (w->shift == DSD0) {
snd_soc_update_bits(codec, WCD934X_CDC_DSD0_CFG2,
0x04, 0x04);
snd_soc_update_bits(codec, WCD934X_CDC_DSD0_PATH_CTL,
0x01, 0x00);
} else if (w->shift == DSD1) {
snd_soc_update_bits(codec, WCD934X_CDC_DSD1_CFG2,
0x04, 0x04);
snd_soc_update_bits(codec, WCD934X_CDC_DSD1_PATH_CTL,
0x01, 0x00);
}
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);
tavil_dsd_reset(dsd_conf);
}
break;
}
return 0;
}
static int tavil_dsd_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = DSD_VOLUME_MIN_M110dB;
uinfo->value.integer.max = DSD_VOLUME_MAX_0dB;
return 0;
}
static int tavil_dsd_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct tavil_dsd_config *dsd_conf = tavil_get_dsd_config(codec);
int nv[DSD_MAX], cv[DSD_MAX];
int step_size, nv1;
int i, dsd_idx;
if (!dsd_conf)
return 0;
mutex_lock(&dsd_conf->vol_mutex);
for (dsd_idx = DSD0; dsd_idx < DSD_MAX; dsd_idx++) {
cv[dsd_idx] = dsd_conf->volume[dsd_idx];
nv[dsd_idx] = ucontrol->value.integer.value[dsd_idx];
}
if ((!DSD_VOLUME_RANGE_CHECK(nv[DSD0])) ||
(!DSD_VOLUME_RANGE_CHECK(nv[DSD1])))
goto done;
for (dsd_idx = DSD0; dsd_idx < DSD_MAX; dsd_idx++) {
if (cv[dsd_idx] == nv[dsd_idx])
continue;
dev_dbg(codec->dev, "%s: DSD%d cur.vol: %d, new vol: %d\n",
__func__, dsd_idx, cv[dsd_idx], nv[dsd_idx]);
step_size = (nv[dsd_idx] - cv[dsd_idx]) /
DSD_VOLUME_STEPS;
nv1 = cv[dsd_idx];
for (i = 0; i < DSD_VOLUME_STEPS; i++) {
nv1 += step_size;
snd_soc_write(codec,
WCD934X_CDC_DSD0_CFG1 + 16 * dsd_idx,
nv1);
if (dsd_conf->version == TAVIL_VERSION_1_1)
tavil_dsd_update_volume(dsd_conf);
/* sleep required after each volume step */
usleep_range(DSD_VOLUME_STEP_DELAY_US,
(DSD_VOLUME_STEP_DELAY_US +
DSD_VOLUME_USLEEP_MARGIN_US));
}
if (nv1 != nv[dsd_idx]) {
snd_soc_write(codec,
WCD934X_CDC_DSD0_CFG1 + 16 * dsd_idx,
nv[dsd_idx]);
if (dsd_conf->version == TAVIL_VERSION_1_1)
tavil_dsd_update_volume(dsd_conf);
}
dsd_conf->volume[dsd_idx] = nv[dsd_idx];
}
done:
mutex_unlock(&dsd_conf->vol_mutex);
return 0;
}
static int tavil_dsd_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
struct tavil_dsd_config *dsd_conf = tavil_get_dsd_config(codec);
if (dsd_conf) {
ucontrol->value.integer.value[0] = dsd_conf->volume[DSD0];
ucontrol->value.integer.value[1] = dsd_conf->volume[DSD1];
}
return 0;
}
static const struct snd_kcontrol_new tavil_dsd_vol_controls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ),
.name = "DSD Volume",
.info = tavil_dsd_vol_info,
.get = tavil_dsd_vol_get,
.put = tavil_dsd_vol_put,
.tlv = { .p = tavil_dsd_db_scale },
},
};
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_post_ssr_init - DSD intialization after subsystem restart
*
* @codec: pointer to snd_soc_codec
*
* Returns 0 on success or error on failure
*/
int tavil_dsd_post_ssr_init(struct tavil_dsd_config *dsd_conf)
{
struct snd_soc_codec *codec;
if (!dsd_conf || !dsd_conf->codec)
return -EINVAL;
codec = dsd_conf->codec;
/* Disable DSD Interrupts */
snd_soc_update_bits(codec, WCD934X_INTR_CODEC_MISC_MASK, 0x08, 0x08);
/* DSD registers init */
if (dsd_conf->version == TAVIL_VERSION_1_0) {
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);
/* Enable DSD Interrupts */
snd_soc_update_bits(codec, WCD934X_INTR_CODEC_MISC_MASK, 0x08, 0x00);
return 0;
}
EXPORT_SYMBOL(tavil_dsd_post_ssr_init);
/**
* 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;
/* Read version */
dsd_conf->version = snd_soc_read(codec,
WCD934X_CHIP_TIER_CTRL_CHIP_ID_BYTE0);
/* DSD registers init */
if (dsd_conf->version == TAVIL_VERSION_1_0) {
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));
mutex_init(&dsd_conf->vol_mutex);
dsd_conf->volume[DSD0] = DSD_VOLUME_MAX_0dB;
dsd_conf->volume[DSD1] = DSD_VOLUME_MAX_0dB;
snd_soc_add_codec_controls(codec, tavil_dsd_vol_controls,
ARRAY_SIZE(tavil_dsd_vol_controls));
/* 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;
mutex_destroy(&dsd_conf->vol_mutex);
/* 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);